diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e45777 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.lein-deps-sum +classes/ +lib/ diff --git a/project.clj b/project.clj index de0ccc6..2ffc9a7 100644 --- a/project.clj +++ b/project.clj @@ -1,12 +1,12 @@ -(defproject clooj "0.4.4" +(defproject clooj "0.5" :description "clooj, a small IDE for clojure" :url "https://github.com/arthuredelstein/clooj" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :main clooj.core - :dependencies [[org.clojure/clojure "1.5.0"] + :dependencies [[org.clojure/clojure "1.5.1"] [clj-inspector "0.0.12"] [slamhound "1.2.0"] [com.cemerick/pomegranate "0.0.11"] - [com.fifesoft/rsyntaxtextarea "2.0.2"] - [org.clojure/tools.nrepl "0.2.2"]]) + [com.fifesoft/rsyntaxtextarea "2.5.0"] + [org.clojure/tools.nrepl "0.2.3"]]) diff --git a/src/clooj/core.clj b/src/clooj/core.clj index 6df32d7..81f9a45 100644 --- a/src/clooj/core.clj +++ b/src/clooj/core.clj @@ -7,7 +7,8 @@ (:import (javax.swing AbstractListModel BorderFactory JDialog JFrame JLabel JList JMenuBar JOptionPane JPanel JScrollPane JSplitPane JTextArea - JTextField JTree KeyStroke SpringLayout JTextPane + JTextField JTree KeyStroke SpringLayout + JTextPane JCheckBox JButton ListSelectionModel UIManager) (javax.swing.event TreeSelectionListener @@ -15,8 +16,9 @@ (javax.swing.tree DefaultMutableTreeNode DefaultTreeModel TreePath TreeSelectionModel) (java.awt Insets Rectangle Window) - (java.awt.event AWTEventListener FocusAdapter - MouseAdapter WindowAdapter KeyAdapter) + (java.awt.event AWTEventListener FocusAdapter + MouseAdapter WindowAdapter + ActionListener KeyAdapter) (java.awt AWTEvent Color Font GridLayout Toolkit) (java.net URL) (java.util.concurrent LinkedBlockingQueue) @@ -34,10 +36,10 @@ [clooj.navigate :as navigate] [clooj.project :as project] [clooj.indent :as indent] - [clooj.style :as style] [clooj.brackets :as brackets] [clooj.highlighting :as highlighting] - [clooj.search :as search]) + [clooj.search :as search] + [clooj.settings :as settings]) (:gen-class :methods [^{:static true} [show [] void]])) @@ -72,6 +74,7 @@ (.setBracketMatchingEnabled false) (.setAutoIndentEnabled false) (.setAntiAliasingEnabled true) + (.setLineWrap wrap) )) (def get-clooj-version @@ -84,46 +87,74 @@ URL. slurp read-string (nth 2)) (catch Exception _ nil))))) -;; font - -(defonce current-font (atom nil)) - -(defn font [name size] - (Font. name Font/PLAIN size)) - -(def default-font - (cond (utils/is-mac) ["Monaco" 11] - (utils/is-win) ["Courier New" 12] - :else ["Monospaced" 12])) +;; settings + +(def default-settings + (merge + (zipmap [:font-name :font-size] + (cond (utils/is-mac) ["Monaco" 11] + (utils/is-win) ["Courier New" 12] + :else ["Monospaced" 12])) + {:line-wrap-doc false + :line-wrap-repl-out false + :line-wrap-repl-in false + :show-only-monospaced-fonts true + })) + +(defn load-settings [] + (atom + (merge default-settings + (utils/read-value-from-prefs utils/clooj-prefs "settings")))) + +(defn save-settings [settings] + (utils/write-value-to-prefs + utils/clooj-prefs + "settings" + settings)) + +(defn apply-settings [app settings] + + (defn set-line-wrapping [text-area mode] + (.setLineWrap text-area mode)) -(defn set-font - ([app font-name size] - (let [f (font font-name size)] + (defn set-font + [app font-name size] + (let [f (Font. font-name Font/PLAIN size)] (utils/awt-event - (utils/write-value-to-prefs utils/clooj-prefs "app-font" - [font-name size]) (dorun (map #(.setFont (app %) f) [:doc-text-area :repl-in-text-area :repl-out-text-area :arglist-label :search-text-area :help-text-area - :completion-list])) - (reset! current-font [font-name size])))) - ([app font-name] - (let [size (second @current-font)] - (set-font app font-name size)))) - -(defn load-font [app] - (apply set-font app (or (utils/read-value-from-prefs utils/clooj-prefs "app-font") - default-font))) + :completion-list]))))) + + (set-line-wrapping + (:doc-text-area app) + (:line-wrap-doc settings)) + (set-line-wrapping + (:repl-in-text-area app) + (:line-wrap-repl-in settings)) + (set-line-wrapping + (:repl-out-text-area app) + (:line-wrap-repl-out settings)) + (set-font app + (:font-name settings) + (:font-size settings)) + (reset! (:settings app) settings) + (save-settings settings)) + +;; font + (defn resize-font [app fun] - (let [[name size] @current-font] - (set-font app name (fun size)))) + (apply-settings app (update-in @(:settings app) + [:font-size] + fun))) (defn grow-font [app] (resize-font app inc)) (defn shrink-font [app] (resize-font app dec)) + ;; caret finding (def highlight-agent (agent nil)) @@ -285,11 +316,18 @@ (defn make-scroll-pane [text-area] (RTextScrollPane. text-area)) -(defn setup-search-text-area [app] +(defn setup-search-elements [app] + (.setVisible (:search-match-case-checkbox app) false) + (.setVisible (:search-regex-checkbox app) false) + (doto (:search-close-button app) + (.setVisible false) + (.setBorder nil) + (.addActionListener + (reify ActionListener + (actionPerformed [_ _] (search/stop-find app))))) (let [sta (doto (app :search-text-area) (.setVisible false) - (.setBorder (BorderFactory/createLineBorder Color/DARK_GRAY)) - (.addFocusListener (proxy [FocusAdapter] [] (focusLost [_] (search/stop-find app)))))] + (.setBorder (BorderFactory/createLineBorder Color/DARK_GRAY)))] (utils/add-text-change-listener sta #(search/update-find-highlight % app false)) (utils/attach-action-keys sta ["ENTER" #(search/highlight-step app false)] ["shift ENTER" #(search/highlight-step app true)] @@ -300,11 +338,12 @@ (.setVisible true) )) -(defn exit-if-closed [^java.awt.Window f] +(defn exit-if-closed [^java.awt.Window f app] (when-not @embedded (.addWindowListener f (proxy [WindowAdapter] [] (windowClosing [_] + (save-caret-position app) (System/exit 0)))))) (def no-project-txt @@ -326,6 +365,19 @@ (select menu File > New...)
 2. edit an existing file by selecting one at left.") +(defn move-caret-to-line [textarea] + "Move caret to choosen line" + + (defn current-line [] + (inc (.getLineOfOffset textarea (.getCaretPosition textarea)))) + + (let [line-str (utils/ask-value "Line number:" "Go to Line") + line-num (Integer. + (if (or (nil? line-str) (nil? (re-find #"\d+" line-str))) + (current-line) + (re-find #"\d+" line-str)))] + (utils/scroll-to-line textarea line-num) + (.requestFocus textarea))) (defn open-project [app] (when-let [dir (utils/choose-directory (app :f) "Choose a project directory")] @@ -369,7 +421,7 @@ (defn create-app [] (let [doc-text-panel (JPanel.) doc-label (JLabel. "Source Editor") - repl-out-text-area (JTextArea.) + repl-out-text-area (make-text-area false) repl-out-scroll-pane (repl-output/tailing-scroll-pane repl-out-text-area) repl-out-writer (repl/make-repl-writer repl-out-text-area) repl-in-text-area (make-text-area false) @@ -380,6 +432,9 @@ completion-list (JList.) completion-scroll-pane (JScrollPane. completion-list) search-text-area (JTextField.) + search-match-case-checkbox (JCheckBox. "Match case") + search-regex-checkbox (JCheckBox. "Regex") + search-close-button (JButton. "X") arglist-label (create-arglist-label) pos-label (JLabel.) frame (JFrame.) @@ -418,6 +473,9 @@ docs-tree-panel docs-tree-label search-text-area + search-match-case-checkbox + search-regex-checkbox + search-close-button pos-label repl-out-writer doc-split-pane @@ -430,7 +488,8 @@ )) doc-text-area (new-doc-text-area app) doc-scroll-pane (make-scroll-pane doc-text-area) - app (assoc app :doc-text-area doc-text-area)] + app (assoc app :doc-text-area doc-text-area) + app (assoc app :settings (load-settings))] (doto frame (.setBounds 25 50 950 700) (.setLayout layout) @@ -442,7 +501,10 @@ (.add doc-label) (.add pos-label) (.add search-text-area) - (.add arglist-label)) + (.add arglist-label) + (.add search-match-case-checkbox) + (.add search-regex-checkbox) + (.add search-close-button)) (doto docs-tree-panel (.setLayout (SpringLayout.)) (.add docs-tree-label) @@ -471,16 +533,21 @@ navigate/attach-navigation-keys) (.setSyntaxEditingStyle repl-in-text-area SyntaxConstants/SYNTAX_STYLE_CLOJURE) + (.setSyntaxEditingStyle repl-out-text-area + SyntaxConstants/SYNTAX_STYLE_CLOJURE) (.setModel docs-tree (DefaultTreeModel. nil)) (utils/constrain-to-parent split-pane :n gap :w gap :s (- gap) :e (- gap)) (utils/constrain-to-parent doc-label :n 0 :w 0 :n 15 :e 0) (utils/constrain-to-parent doc-scroll-pane :n 16 :w 0 :s -16 :e 0) (utils/constrain-to-parent pos-label :s -14 :w 0 :s 0 :w 100) - (utils/constrain-to-parent search-text-area :s -15 :w 80 :s 0 :w 300) + (utils/constrain-to-parent search-text-area :s -15 :w 100 :s 0 :w 350) + (utils/constrain-to-parent search-match-case-checkbox :s -15 :w 355 :s 0 :w 470) + (utils/constrain-to-parent search-regex-checkbox :s -15 :w 475 :s 0 :w 550) + (utils/constrain-to-parent search-close-button :s -15 :w 65 :s 0 :w 95) (utils/constrain-to-parent arglist-label :s -14 :w 80 :s -1 :e -10) (.layoutContainer layout frame) - (exit-if-closed frame) - (setup-search-text-area app) + (exit-if-closed frame app) + (setup-search-elements app) (activate-caret-highlighter app) (setup-temp-writer app) (utils/attach-action-keys doc-text-area @@ -522,7 +589,7 @@ (.setText doc-label (str "Source Editor \u2014 " (.getPath file))) (.setEditable text-area true) (.setSyntaxEditingStyle text-area - (let [file-name (.getName file-to-open)] + (let [file-name (.getName file-to-open)] (if (or (.endsWith file-name ".clj") (.endsWith file-name ".clj~")) SyntaxConstants/SYNTAX_STYLE_CLOJURE @@ -530,9 +597,11 @@ (do (.setText text-area no-project-txt) (.setText doc-label (str "Source Editor (No file selected)")) (.setEditable text-area false))) - (update-caret-position text-area) + (indent/setup-autoindent text-area) (reset! (app :file) file) + (load-caret-position app) + (update-caret-position text-area) (repl/apply-namespace-to-repl app) (reset! changing-file false))) @@ -556,8 +625,8 @@ (def project-clj-text (.trim " (defproject PROJECTNAME \"1.0.0-SNAPSHOT\" - :description \"FIXME: write\" - :dependencies [[org.clojure/clojure \"1.3.0\"]]) + :description \"FIXME: write description\" + :dependencies [[org.clojure/clojure \"1.5.1\"]]) ")) (defn specify-source [project-dir title default-namespace] @@ -690,12 +759,12 @@ ["Move/Rename" "M" nil #(project/rename-project app)] ["Remove" nil nil #(remove-project app)]) (utils/add-menu menu-bar "Source" "U" - ["Comment-out" "C" "cmd1 SEMICOLON" #(utils/comment-out (:doc-text-area app))] - ["Uncomment-out" "U" "cmd1 shift SEMICOLON" #(utils/uncomment-out (:doc-text-area app))] + ["Comment" "C" "cmd1 SEMICOLON" #(utils/toggle-comment (:doc-text-area app))] ["Fix indentation" "F" "cmd1 BACK_SLASH" #(indent/fix-indent-selected-lines (:doc-text-area app))] ["Indent lines" "I" "cmd1 CLOSE_BRACKET" #(utils/indent (:doc-text-area app))] ["Unindent lines" "D" "cmd1 OPEN_BRACKET" #(utils/unindent (:doc-text-area app))] ["Name search/docs" "S" "TAB" #(help/show-tab-help app (help/find-focused-text-pane app) inc)] + ["Go to line..." "G" "cmd1 L" #(move-caret-to-line (:doc-text-area app))] ;["Go to definition" "G" "cmd1 D" #(goto-definition (repl/get-file-ns app) app)] ) (utils/add-menu menu-bar "REPL" "R" @@ -716,8 +785,8 @@ ["Go to Project Tree" "P" "cmd1 1" #(.requestFocusInWindow (:docs-tree app))] ["Increase font size" nil "cmd1 PLUS" #(grow-font app)] ["Decrease font size" nil "cmd1 MINUS" #(shrink-font app)] - ["Choose font..." nil nil #(apply style/show-font-window - app set-font @current-font)]))) + ["Settings" nil nil #(settings/show-settings-window + app apply-settings)]))) (defn add-visibility-shortcut [app] @@ -757,8 +826,9 @@ (setup-tree app) (let [tree (app :docs-tree)] (project/load-expanded-paths tree) - (project/load-tree-selection tree)) - (load-font app))) + (when (false? (project/load-tree-selection tree)) + (repl/start-repl app nil))) + (apply-settings app @(:settings app)))) (defn -show [] (reset! embedded true) diff --git a/src/clooj/project.clj b/src/clooj/project.clj index 9b63347..58760c4 100644 --- a/src/clooj/project.clj +++ b/src/clooj/project.clj @@ -121,7 +121,11 @@ (defn load-tree-selection [tree] (let [path (utils/read-value-from-prefs utils/clooj-prefs "tree-selection")] - (set-tree-selection tree path))) + (if (nil? path) + false + (do + (set-tree-selection tree path) + true)))) ;;;;;;;;;;;;;;;;;;; diff --git a/src/clooj/repl/main.clj b/src/clooj/repl/main.clj index aa3015c..bf2f151 100644 --- a/src/clooj/repl/main.clj +++ b/src/clooj/repl/main.clj @@ -72,14 +72,6 @@ (swap! (:items repl-history) replace-first (utils/get-text-str (app :repl-in-text-area)))) -(defn correct-expression? [cmd] - (when-not (empty? (.trim cmd)) - (let [rdr (-> cmd StringReader. PushbackReader.)] - (try (while (read rdr nil nil)) - true - (catch IllegalArgumentException e true) ;explicitly show duplicate keys etc. - (catch Exception e false))))) - (defn read-string-at [source-text start-line] `(let [sr# (java.io.StringReader. (str (apply str (repeat ~start-line "\n")) ~source-text)) @@ -106,30 +98,23 @@ [app cmd-str silent?] (when-let [repl @(app :repl)] (.evaluate repl - (str (if silent? - "(clooj.repl.remote/silent" - "(do") - cmd-str ")")))) + (if silent? + (str "(clooj.repl.remote/silent" cmd-str ")") + cmd-str)))) (defn send-to-repl ([app cmd silent?] (send-to-repl app cmd "NO_SOURCE_PATH" 0 silent?)) ([app cmd file line silent?] - (let [cmd-ln (str \newline (.trim cmd) \newline) - cmd-trim (.trim cmd) - classpaths (filter identity - (map #(.getAbsolutePath %) - (-> app :classpath-queue))) - ] - (when-not silent? - (utils/append-text (app :repl-out-text-area) cmd-ln)) - (let [cmd-str (cmd-attach-file-and-line cmd file line classpaths)] - (print-to-repl app cmd-str silent?)) - (when-not silent? - (when (not= cmd-trim (second @(:items repl-history))) - (swap! (:items repl-history) - replace-first cmd-trim) - (swap! (:items repl-history) conj "")) - (reset! (:pos repl-history) 0))))) + (let [cmd-ln (str cmd \newline)] + (when-not silent? + (utils/append-text (app :repl-out-text-area) cmd-ln)) + (print-to-repl app cmd silent?) + (when-not silent? + (when (not= cmd (second @(:items repl-history))) + (swap! (:items repl-history) + replace-first cmd) + (swap! (:items repl-history) conj "")) + (reset! (:pos repl-history) 0))))) (defn relative-file [app] (let [prefix (str (get-project-path app) File/separator @@ -153,7 +138,7 @@ (let [ta (app :doc-text-area) region (selected-region ta) txt (:text region)] - (if-not (and txt (correct-expression? txt)) + (if-not txt (.setText (app :arglist-label) "Malformed expression") (let [line (.getLineOfOffset ta (:start region))] (send-to-repl app txt (relative-file app) line false))))) @@ -205,21 +190,22 @@ (catch Exception e))) (defn start-repl [app project-path] - (utils/append-text (app :repl-out-text-area) - (str "\n=== Starting new REPL at " project-path " ===\n")) + (let [project-path (if (utils/file-exists? project-path) project-path nil)] + (utils/append-text (app :repl-out-text-area) + (str "\n=== Starting new REPL at " project-path " ===\n")) (let [classpath-items ;(lein/lein-classpath-items project-path) - (external/repl-classpath-items project-path) + (external/repl-classpath-items project-path) repl ;(lein/lein-repl project-path (app :repl-out-writer)) - (external/repl project-path classpath-items - (app :repl-out-writer)) + (external/repl project-path classpath-items + (app :repl-out-writer)) ] (initialize-repl repl) (help/update-var-maps! project-path classpath-items) - (reset! (:repl app) repl))) + (reset! (:repl app) repl)))) (defn stop-repl [app] (utils/append-text (app :repl-out-text-area) - "\n=== Shutting down REPL ===") + "\n=== Shutting down REPL ===") (when-let [repl @(:repl app)] (.close repl))) @@ -248,10 +234,8 @@ txt caret-pos))))) submit #(when-let [txt (.getText ta-in)] - (if (correct-expression? txt) - (do (send-to-repl app txt false) - (.setText ta-in "")) - (.setText (app :arglist-label) "Malformed expression"))) + (do (send-to-repl app txt false) + (.setText ta-in ""))) at-top #(zero? (.getLineOfOffset ta-in (get-caret-pos))) at-bottom #(= (.getLineOfOffset ta-in (get-caret-pos)) (.getLineOfOffset ta-in (.. ta-in getText length))) diff --git a/src/clooj/search.clj b/src/clooj/search.clj index da31345..d3ab9fb 100644 --- a/src/clooj/search.clj +++ b/src/clooj/search.clj @@ -9,61 +9,73 @@ (:require [clooj.highlighting :as highlighting] [clooj.utils :as utils])) -(def case-insensitive-search - (reduce bit-or - [Pattern/LITERAL - Pattern/UNICODE_CASE - Pattern/CASE_INSENSITIVE - Pattern/CANON_EQ])) +(defn configure-search [match-case use-regex] + (bit-or Pattern/CANON_EQ + Pattern/UNICODE_CASE + (if match-case 0 Pattern/CASE_INSENSITIVE) + (if use-regex 0 Pattern/LITERAL))) (defn find-all-in-string - [s t] - (when (pos? (.length t)) - (let [p (Pattern/compile t case-insensitive-search) - m (re-matcher p s)] - (loop [positions []] - (if (.find m) - (recur (conj positions (.start m))) - positions))))) + [s t match-case use-regex] + (try + (when (pos? (.length t)) + (let [p (Pattern/compile t (configure-search match-case use-regex)) + m (re-matcher p s)] + (loop [positions []] + (if (.find m) + (recur (conj positions [(.start m) (.end m)] ) ) + positions)))) + (catch Exception _ []))) -(defn highlight-found [text-comp posns length] - (when (pos? length) +(defn highlight-found [text-comp posns] (doall - (map #(highlighting/highlight text-comp % (+ % length) Color/YELLOW) - posns)))) + (map #(highlighting/highlight text-comp (first %) (second %) Color/YELLOW) + posns))) (defn next-item [cur-pos posns] - (or (first (drop-while #(> cur-pos %) posns)) (first posns))) + (or (first (drop-while #(> cur-pos (first %)) posns)) (first posns))) (defn prev-item [cur-pos posns] - (or (first (drop-while #(< cur-pos %) (reverse posns))) (last posns))) + (or (first (drop-while #(< cur-pos (first %)) (reverse posns))) (last posns))) (def search-highlights (atom nil)) +(def current-pos (atom 0)) + (defn update-find-highlight [sta app back] (let [dta (:doc-text-area app) - length (.length (utils/get-text-str sta)) - posns (find-all-in-string (utils/get-text-str dta) (utils/get-text-str sta))] + match-case (.isSelected (:search-match-case-checkbox app)) + use-regex (.isSelected (:search-regex-checkbox app)) + posns (find-all-in-string (utils/get-text-str dta) + (utils/get-text-str sta) + match-case + use-regex)] (highlighting/remove-highlights dta @search-highlights) (if (pos? (count posns)) (let [selected-pos - (if back (prev-item (dec (.getSelectionStart dta)) posns) - (next-item (.getSelectionStart dta) posns)) - posns (remove #(= selected-pos %) posns)] + (if back (prev-item (dec @current-pos) posns) + (next-item @current-pos posns)) + posns (remove #(= selected-pos %) posns) + pos-start (first selected-pos) + pos-end (second selected-pos)] (.setBackground sta Color/WHITE) - (when (pos? length) - (reset! search-highlights - (conj (highlight-found dta posns length) - (highlighting/highlight dta selected-pos - (+ selected-pos length) (.getSelectionColor dta)))) - (do (utils/scroll-to-pos dta selected-pos) - (utils/set-selection dta selected-pos (+ selected-pos length))))) - (do (.setSelectionEnd dta (.getSelectionStart dta)) - (.setBackground sta (if (pos? length) Color/PINK Color/WHITE)))))) + (doto dta + (.setSelectionStart pos-end) + (.setSelectionEnd pos-end)) + (reset! current-pos pos-start) + (reset! search-highlights + (conj (highlight-found dta posns) + (highlighting/highlight dta pos-start + pos-end (.getSelectionColor dta)))) + (utils/scroll-to-pos dta pos-start)) + (.setBackground sta Color/PINK)))) (defn start-find [app] - (let [sta (app :search-text-area) - arg (app :arglist-label) + (let [sta (:search-text-area app) + case-checkbox (:search-match-case-checkbox app) + regex-checkbox (:search-regex-checkbox app) + close-button (:search-close-button app) + arg (:arglist-label app) dta (:doc-text-area app) sel-text (.getSelectedText dta)] (.setVisible arg false) @@ -71,17 +83,27 @@ (.setVisible true) (.requestFocus) (.selectAll)) + (.setVisible case-checkbox true) + (.setVisible regex-checkbox true) + (.setVisible close-button true) (if (not (empty? sel-text)) (.setText sta sel-text)))) (defn stop-find [app] (let [sta (app :search-text-area) dta (app :doc-text-area) + case-checkbox (:search-match-case-checkbox app) + regex-checkbox (:search-regex-checkbox app) + close-button (:search-close-button app) arg (app :arglist-label)] (.setVisible arg true) (.setVisible sta false) + (.setVisible case-checkbox false) + (.setVisible regex-checkbox false) + (.setVisible close-button false) (highlighting/remove-highlights dta @search-highlights) - (reset! search-highlights nil))) + (reset! search-highlights nil) + (reset! current-pos 0))) (defn escape-find [app] (stop-find app) @@ -91,7 +113,7 @@ (let [doc-text-area (:doc-text-area app) search-text-area (:search-text-area app)] (start-find app) - (if (not back) - (.setSelectionStart doc-text-area (.getSelectionEnd doc-text-area))) + (when-not back + (swap! current-pos inc)) (update-find-highlight search-text-area app back))) diff --git a/src/clooj/settings.clj b/src/clooj/settings.clj new file mode 100644 index 0000000..38b8503 --- /dev/null +++ b/src/clooj/settings.clj @@ -0,0 +1,188 @@ +; Copyright (c) 2011-2013, Arthur Edelstein +; All rights reserved. +; Eclipse Public License 1.0 +; arthuredelstein@gmail.com + +(ns clooj.settings + (:import + (javax.swing JFrame JTabbedPane JLabel + JPanel JComboBox Box + JTextField JTextArea + BoxLayout SpringLayout + JButton JCheckBox) + (java.awt Font GraphicsEnvironment Dimension) + (java.awt.image BufferedImage) + (javax.swing.event DocumentListener) + (java.awt.event ActionListener ItemListener ItemEvent)) + (:require + [clooj.utils :as utils])) + +(def settings (atom nil)) + +(defn combo-box [items default-item change-fun] + (doto (JComboBox. (into-array items)) + (.setSelectedItem default-item) + (.addActionListener + (reify ActionListener + (actionPerformed [_ e] + (change-fun (.. e getSource getSelectedItem))))))) + +(defn text-field [default-value change-fun] + (let [tf (JTextField. (str default-value))] + (.addDocumentListener + (.getDocument tf) + (reify DocumentListener + (insertUpdate [_ e] + (change-fun (.getText tf))) + (removeUpdate [_ e] + (change-fun (.getText tf))) + (changedUpdate [_ e]))) + tf)) + +(defn check-box [text checked? change-fun] + (doto (JCheckBox. text checked?) + (.addItemListener + (reify ItemListener + (itemStateChanged [_ e] + (change-fun + (= + (.getStateChange e) + ItemEvent/SELECTED))))))) + +(defn font-panel [] + + + (def graphics-object + (memoize (fn [] (.createGraphics + (BufferedImage. 1 1 BufferedImage/TYPE_INT_ARGB))))) + + (def monospaced? + (fn [font] + (let [g (graphics-object) + m (.getFontMetrics g font)] + (apply == (map #(.charWidth m %) [\m \n \. \M \-]))))) + + (defn get-all-font-names [] + (.. GraphicsEnvironment + getLocalGraphicsEnvironment + getAvailableFontFamilyNames)) + + (defn get-all-fonts-12 [] + (map #(Font. % Font/PLAIN 12) (get-all-font-names))) + + (defn get-monospaced-font-names [] + (map #(.getName %) (filter monospaced? (get-all-fonts-12)))) + + (defn get-necessary-fonts [] + (if (:show-only-monospaced-fonts @settings) + (get-monospaced-font-names) + (get-all-font-names))) + (let [example-text-area (doto (JTextArea. + "abcdefghijklmnopqrstuvwxyz 0123456789 (){}[]\nABCDEFGHIJKLMNOPQRSTUVWXYZ +-*/= .,;:!? #&$%@|^") + (.setFont (Font. (:font-name @settings) Font/PLAIN (:font-size @settings)))) + example-pane (doto (JPanel. (SpringLayout.)) + (.add example-text-area)) + font-box (combo-box + (get-necessary-fonts) + (:font-name @settings) + #(do + (swap! settings assoc :font-name %) + (.setFont + example-text-area + (Font. % Font/PLAIN (:font-size @settings))))) + size-box (combo-box + (range 5 49) + (:font-size @settings) + #(do + (swap! settings assoc :font-size %) + (.setFont + example-text-area + (Font. (:font-name @settings) Font/PLAIN %)))) + monospaced-check-box (check-box + "Show only monospaced fonts" + (:show-only-monospaced-fonts @settings) + #(do + (swap! settings + assoc :show-only-monospaced-fonts %) + (doto font-box + (.setModel + (.getModel + (JComboBox. + (into-array + (get-necessary-fonts))))) + (.setSelectedItem (:font-name @settings))))) + controls-pane (JPanel.) + font-pane (JPanel.)] + + (utils/constrain-to-parent example-text-area :n 20 :w 15 :s -15 :e -15) + + (doto controls-pane + (.setLayout (BoxLayout. controls-pane BoxLayout/X_AXIS)) + (.add (Box/createRigidArea (Dimension. 20 0))) + (.add (JLabel. "Font:")) + (.add (Box/createRigidArea (Dimension. 25 0))) + (.add font-box) + (.add (Box/createRigidArea (Dimension. 25 0))) + (.add (JLabel. "Size:")) + (.add (Box/createRigidArea (Dimension. 25 0))) + (.add size-box) + (.add (Box/createHorizontalGlue))) + + (doto font-pane + (.setLayout (BoxLayout. font-pane BoxLayout/Y_AXIS)) + (.add controls-pane) + (.add monospaced-check-box) + (.add example-pane)))) + +(defn editor-options-panel [] + (let [options-pane (JPanel.)] + (doto options-pane + (.setLayout (BoxLayout. options-pane BoxLayout/Y_AXIS)) + (.add (check-box + "Wrap lines in source editor" + (:line-wrap-doc @settings) + #(swap! settings assoc :line-wrap-doc %))) + (.add (check-box + "Wrap lines in repl output" + (:line-wrap-repl-out @settings) + #(swap! settings assoc :line-wrap-repl-out %))) + (.add (check-box + "Wrap lines in repl input" + (:line-wrap-repl-in @settings) + #(swap! settings assoc :line-wrap-repl-in %)))))) + +(defmacro tabs [& elements] + `(doto (JTabbedPane.) + ~@(map #(list '.addTab (first %) (second %)) elements))) + +(defn make-settings-window [app apply-fn] + (let [bounds (.getBounds (:frame app)) + x (+ (.x bounds) (/ (.width bounds) 2)) + y (+ (.y bounds) (/ (.height bounds) 2)) + settings-frame (JFrame. "Settings") + button-pane (JPanel.)] + + (doto button-pane + (.setLayout (BoxLayout. button-pane BoxLayout/X_AXIS)) + (.add (utils/create-button "OK" #(do + (apply-fn app @settings) + (.dispose settings-frame)))) + (.add (utils/create-button "Apply" #(apply-fn app @settings))) + (.add (utils/create-button "Cancel" #(.dispose settings-frame)))) + + (doto settings-frame + (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE) + (.setLayout (BoxLayout. (.getContentPane settings-frame) BoxLayout/Y_AXIS)) + (.setBounds (- x 250) (- y 250) 500 500) + + (.add (tabs + ["Font" (font-panel)] + ["Editor options" (editor-options-panel)])) + (.add (Box/createRigidArea (Dimension. 0 25))) + (.add button-pane)))) + + + +(defn show-settings-window [app apply-fn] + (reset! settings @(:settings app)) + (.show (make-settings-window app apply-fn))) diff --git a/src/clooj/style.clj b/src/clooj/style.clj deleted file mode 100644 index 13ca177..0000000 --- a/src/clooj/style.clj +++ /dev/null @@ -1,92 +0,0 @@ -; Copyright (c) 2011-2013, Arthur Edelstein -; All rights reserved. -; Eclipse Public License 1.0 -; arthuredelstein@gmail.com - -(ns clooj.style - (:import - (javax.swing JComboBox JFrame JList JScrollPane JSplitPane - ListSelectionModel SpringLayout) - (javax.swing.text SimpleAttributeSet StyleConstants) - (java.awt Color Font FontMetrics GraphicsEnvironment) - (java.awt.image BufferedImage) - (javax.swing.event ListSelectionListener) - (java.util Vector)) - (:require [clooj.utils :as utils])) - -(def graphics-object - (memoize (fn [] (.createGraphics - (BufferedImage. 1 1 BufferedImage/TYPE_INT_ARGB))))) - -(defn set-text-color [text-comp offset length color] - (let [style-doc (.getStyledDocument text-comp) - attrs (SimpleAttributeSet.)] - (StyleConstants/setForeground attrs color) - (.setCharacterAttributes style-doc offset length attrs true))) - -(defn get-tokens [txt] - (re-seq #"[:|a-z|A-Z|/|\\|\.|-|0-9|\+\"]+" txt)) ;" - -;; fonts - -(def monospaced? - (fn [font] - (let [g (graphics-object) - m (.getFontMetrics g font)] - (apply == (map #(.charWidth m %) [\m \n \. \M \-]))))) - -(defn get-all-fonts-12 [] - (let [font-names (.. GraphicsEnvironment - getLocalGraphicsEnvironment - getAvailableFontFamilyNames)] - (map #(Font. % Font/PLAIN 12) font-names))) - -(defn get-monospaced-fonts [] - (map #(.getName %) (filter monospaced? (get-all-fonts-12)))) - -(defn simple-list [choice-fun data init-val] - (let [list (JList. (Vector. data))] - (doto list - (.setSelectedValue init-val true) - (.setSelectionMode ListSelectionModel/SINGLE_SELECTION) - (.addListSelectionListener - (reify ListSelectionListener - (valueChanged [_ e] - (when-not (.getValueIsAdjusting e)) - (choice-fun - (.. list getModel (getElementAt (.getSelectedIndex list)))))))) - (JScrollPane. list))) - -(def font-window (atom nil)) - -(defn create-font-window [app set-font init-name init-size] - (let [name (atom init-name) - size (atom init-size) - bounds (.getBounds (:frame app)) - x (+ (.x bounds) (/ (.width bounds) 2)) - y (+ (.y bounds) (/ (.height bounds) 2)) - layout (SpringLayout.) - font-list (simple-list #(set-font app (reset! name %) @size) - (get-monospaced-fonts) init-name) - size-list (simple-list #(set-font app @name (reset! size)) - (concat (range 5 49)) init-size) - split (utils/make-split-pane font-list size-list true 5 0.5) - frame (doto (JFrame. "Choose Font") - (.setBounds (- x 250) (- y 250) 500 500) - (.setLayout layout) - (.add split) - ; (.setMenuBar (-> app :frame .getMenuBar)) - )] - (doto split - (.setLeftComponent font-list) - (.setRightComponent size-list)) - (utils/constrain-to-parent split :n 5 :w 5 :s -5 :e -5) - (.show font-list) - (.show size-list) - frame)) - -(defn show-font-window [app set-font init-name init-size] - (when-not @font-window - (reset! font-window (create-font-window app set-font init-name init-size))) - (.show @font-window)) - diff --git a/src/clooj/utils.clj b/src/clooj/utils.clj index d18bd56..693c8d7 100644 --- a/src/clooj/utils.clj +++ b/src/clooj/utils.clj @@ -246,12 +246,19 @@ (doseq [start (get-selected-line-starts text-comp)] (when (= (.getText (.getDocument text-comp) start len) txt) (.remove document start len)))))) - + (defn comment-out [text-comp] (insert-in-selected-row-headers text-comp ";")) (defn uncomment-out [text-comp] (remove-from-selected-row-headers text-comp ";")) + +(defn toggle-comment [text-comp] + (if (= (.getText (.getDocument text-comp) + (first (get-selected-line-starts text-comp)) 1) + ";") + (uncomment-out text-comp) + (comment-out text-comp))) (defn indent [text-comp] (when (.isFocusOwner text-comp) @@ -427,6 +434,9 @@ (not (.startsWith (.getName %) "."))) (.listFiles path))) +(defn file-exists? [file] + (and file (.. file exists))) + ;; tree seq on widgets (awt or swing) (defn widget-seq [^java.awt.Component comp] @@ -490,6 +500,9 @@ (JOptionPane/showConfirmDialog nil question title JOptionPane/YES_NO_OPTION))) +(defn ask-value [question title] + (JOptionPane/showInputDialog nil question title JOptionPane/QUESTION_MESSAGE)) + (defn persist-window-shape [prefs name ^java.awt.Window window] (let [components (widget-seq window) shape-persister (agent nil)]