├── .gitattributes
├── .gitignore
├── README.md
├── UNLICENSE
├── classes
└── .gitignore
├── cljs.clj
├── deps.edn
├── dev-resources
├── deps.cljs
└── externs.js
├── dev.clj
├── graphics
├── file-clj.svg
├── file-cljc.svg
├── file-cljs.svg
├── file-java.svg
├── file.svg
├── logo_launcher.svg
└── logo_splash.svg
├── package-lin.sh
├── package-win.bat
├── package
├── linux
│ └── Nightcode.png
├── macosx
│ └── Nightcode.icns
└── windows
│ └── Nightcode.ico
├── prod.clj
├── project.clj
├── resources
├── boot-2.7.2.jar
├── dark.css
├── dir.fxml
├── editor.fxml
├── home.fxml
├── images
│ ├── file-clj.png
│ ├── file-cljc.png
│ ├── file-cljs.png
│ ├── file-java.png
│ ├── file.png
│ ├── logo_launcher.png
│ ├── logo_splash.png
│ ├── player_walk1.png
│ ├── player_walk2.png
│ └── player_walk3.png
├── leiningen
│ └── new
│ │ ├── console.clj
│ │ ├── console
│ │ ├── README.md
│ │ ├── core.clj
│ │ ├── gitignore
│ │ └── project.clj
│ │ ├── edna.clj
│ │ ├── edna
│ │ ├── README.md
│ │ ├── boot.properties
│ │ ├── build.boot
│ │ ├── core.clj
│ │ ├── core.cljs
│ │ ├── gitignore
│ │ ├── index.html
│ │ └── main.cljs.edn
│ │ ├── graphics.clj
│ │ ├── graphics
│ │ ├── README.md
│ │ ├── core.clj
│ │ ├── gitignore
│ │ └── project.clj
│ │ ├── play_cljc.clj
│ │ ├── play_cljc
│ │ ├── core.cljc
│ │ ├── gitignore
│ │ ├── index.html
│ │ ├── move.cljc
│ │ ├── music.clj
│ │ ├── project.clj
│ │ ├── start.clj
│ │ ├── start.cljs
│ │ ├── start_dev.clj
│ │ └── utils.cljc
│ │ ├── web.clj
│ │ └── web
│ │ ├── README.md
│ │ ├── boot.properties
│ │ ├── build.boot
│ │ ├── core.clj
│ │ ├── core.cljs
│ │ ├── gitignore
│ │ ├── index.html
│ │ └── main.cljs.edn.txt
├── main.fxml
├── project.fxml
└── public
│ ├── cheatsheet-full.html
│ ├── cheatsheet_files
│ ├── jquery.js
│ ├── jquery.tipTip.js
│ ├── style.css
│ └── tipTip.css
│ ├── fonts
│ ├── FiraCode-Bold.otf
│ ├── FiraCode-Light.otf
│ ├── FiraCode-Medium.otf
│ ├── FiraCode-Regular.otf
│ └── FiraCode-Retina.otf
│ ├── modal.css
│ ├── paren-soup-dark.css
│ ├── paren-soup-light.css
│ ├── paren-soup.html
│ └── paren-soup.js
├── screenshot.png
└── src
├── clj
└── nightcode
│ ├── builders.clj
│ ├── controller.clj
│ ├── core.clj
│ ├── editors.clj
│ ├── git.clj
│ ├── lein.clj
│ ├── process.clj
│ ├── projects.clj
│ ├── shortcuts.clj
│ ├── spec.clj
│ ├── start.clj
│ ├── state.clj
│ └── utils.clj
└── cljs
└── nightcode
└── paren_soup.cljs
/.gitattributes:
--------------------------------------------------------------------------------
1 | resources/* linguist-vendored
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | hs_err_pid*.log
2 | pom.xml
3 | pom.xml.asc
4 | target/
5 | .*
6 | !.gitignore
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## Introduction
4 |
5 | Nightcode is a simple IDE for Clojure and ClojureScript. See [the website](https://sekao.net/nightcode/) for more information.
6 |
7 | ## Development
8 |
9 | * Install JDK 11 or above
10 | * Install [the Clojure CLI tool](https://clojure.org/guides/getting_started#_clojure_installer_and_cli_tools)
11 | * To develop: `clj -A:dev`
12 | * To build the ClojureScript files: `clj -A:cljs`
13 | * To build the uberjar for each OS:
14 | * `clj -A:prod uberjar windows`
15 | * `clj -A:prod uberjar macos`
16 | * `clj -A:prod uberjar linux`
17 |
18 | ## Licensing
19 |
20 | All files that originate from this project are dedicated to the public domain. I would love pull requests, and will assume that they are also dedicated to the public domain.
21 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/classes/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/cljs.clj:
--------------------------------------------------------------------------------
1 | (require
2 | '[cljs.build.api :as api]
3 | '[leiningen.core.project :as p :refer [defproject]]
4 | '[leiningen.clean :refer [clean]])
5 |
6 | (defn read-project-clj []
7 | (p/ensure-dynamic-classloader)
8 | (-> "project.clj" load-file var-get))
9 |
10 | (-> (read-project-clj)
11 | p/init-project
12 | clean)
13 |
14 | (println "Building paren-soup.js")
15 | (api/build "src" {:main 'nightcode.paren-soup
16 | :optimizations :advanced
17 | :output-to "resources/public/paren-soup.js"
18 | :output-dir "target/public/paren-soup.out"})
19 |
20 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src/clj" "src/cljs" "resources" "classes"]
2 | :deps {org.clojure/clojure {:mvn/version "1.10.1"}
3 | leiningen {:mvn/version "2.9.0" :exclusions [leiningen.search]}
4 | ring {:mvn/version "1.7.1"}
5 | hawk {:mvn/version "0.2.11"}
6 | eval-soup {:mvn/version "1.5.0" :exclusions [org.clojure/core.async]}
7 | org.eclipse.jgit/org.eclipse.jgit {:mvn/version "4.6.0.201612231935-r"}
8 | org.openjfx/javafx-base {:mvn/version "13.0.1"}
9 | org.openjfx/javafx-fxml {:mvn/version "13.0.1"}
10 | org.openjfx/javafx-graphics {:mvn/version "13.0.1"}
11 | org.openjfx/javafx-web {:mvn/version "13.0.1"}}
12 | :aliases {:cljs {:extra-deps {org.clojure/clojurescript {:mvn/version "1.10.597"}
13 | paren-soup {:mvn/version "2.16.0"}}
14 | :extra-paths ["dev-resources"]
15 | :main-opts ["cljs.clj"]}
16 | :dev {:extra-deps {orchestra {:mvn/version "2018.12.06-2"}
17 | expound {:mvn/version "0.7.2"}}
18 | :main-opts ["dev.clj"]}
19 | :prod {:main-opts ["prod.clj"]}
20 | :windows {:extra-deps {org.openjfx/javafx-graphics$win {:mvn/version "13.0.1"}
21 | org.openjfx/javafx-web$win {:mvn/version "13.0.1"}}}
22 | :macos {:extra-deps {org.openjfx/javafx-graphics$mac {:mvn/version "13.0.1"}
23 | org.openjfx/javafx-web$mac {:mvn/version "13.0.1"}}}
24 | :linux {:extra-deps {org.openjfx/javafx-graphics$linux {:mvn/version "13.0.1"}
25 | org.openjfx/javafx-web$linux {:mvn/version "13.0.1"}}}}}
26 |
--------------------------------------------------------------------------------
/dev-resources/deps.cljs:
--------------------------------------------------------------------------------
1 | {:externs ["externs.js"]}
2 |
--------------------------------------------------------------------------------
/dev-resources/externs.js:
--------------------------------------------------------------------------------
1 | window.java = {};
2 | window.java.onload = function(){};
3 | window.java.onautosave = function(){};
4 | window.java.onchange = function(){};
5 | window.java.onenter = function(){};
6 | window.java.oneval = function(){};
7 |
--------------------------------------------------------------------------------
/dev.clj:
--------------------------------------------------------------------------------
1 | (require
2 | '[orchestra.spec.test :as st]
3 | '[expound.alpha :as expound]
4 | '[clojure.spec.alpha :as s])
5 |
6 | (st/instrument)
7 | (alter-var-root #'s/*explain-out* (constantly expound/printer))
8 |
9 | (-> "classes" java.io.File. .mkdir)
10 | (compile 'nightcode.lein)
11 | (compile 'nightcode.core)
12 | (require '[nightcode.core :refer [dev-main]])
13 | (dev-main)
14 |
15 |
--------------------------------------------------------------------------------
/graphics/file-clj.svg:
--------------------------------------------------------------------------------
1 |
2 |
212 |
--------------------------------------------------------------------------------
/graphics/file-cljc.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/graphics/file-cljs.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/graphics/file-java.svg:
--------------------------------------------------------------------------------
1 |
2 |
227 |
--------------------------------------------------------------------------------
/graphics/file.svg:
--------------------------------------------------------------------------------
1 |
2 |
182 |
--------------------------------------------------------------------------------
/graphics/logo_launcher.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
88 |
--------------------------------------------------------------------------------
/graphics/logo_splash.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
88 |
--------------------------------------------------------------------------------
/package-lin.sh:
--------------------------------------------------------------------------------
1 | set -e
2 |
3 | jpackage --type deb --name Nightcode --app-version $1 --input target --main-jar Nightcode-$1-linux.jar --icon package/linux/Nightcode.png --copyright "Public Domain" --linux-shortcut --linux-app-category Development
4 |
5 | jpackage --type app-image --name Nightcode --app-version $1 --input target --main-jar Nightcode-$1-linux.jar --icon package/linux/Nightcode.png --copyright "Public Domain"
6 |
7 | echo "#!/bin/sh
8 |
9 | cd \"\$(dirname \"\$0\")\"
10 | ./bin/Nightcode" >> Nightcode/AppRun
11 |
12 | chmod +x Nightcode/AppRun
13 |
14 | echo "[Desktop Entry]
15 | Name=Nightcode
16 | Exec=/bin/Nightcode
17 | Icon=/lib/Nightcode
18 | Terminal=false
19 | Type=Application
20 | Categories=Development;" >> Nightcode/nightcode.desktop
21 |
22 | appimagetool Nightcode
23 |
--------------------------------------------------------------------------------
/package-win.bat:
--------------------------------------------------------------------------------
1 | jpackage --type app-image --name Nightcode --app-version %1 --input target --main-jar Nightcode-%1-windows.jar --icon package/windows/Nightcode.ico --copyright "Public Domain"
2 |
3 | jpackage --type exe --name Nightcode --app-version %1 --input target --main-jar Nightcode-%1-windows.jar --icon package/windows/Nightcode.ico --copyright "Public Domain" --win-menu --win-per-user-install
4 |
--------------------------------------------------------------------------------
/package/linux/Nightcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/package/linux/Nightcode.png
--------------------------------------------------------------------------------
/package/macosx/Nightcode.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/package/macosx/Nightcode.icns
--------------------------------------------------------------------------------
/package/windows/Nightcode.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/package/windows/Nightcode.ico
--------------------------------------------------------------------------------
/prod.clj:
--------------------------------------------------------------------------------
1 | (require
2 | '[clojure.string :as str]
3 | '[leiningen.core.project :as p :refer [defproject]]
4 | '[leiningen.clean :refer [clean]]
5 | '[leiningen.install :refer [install]]
6 | '[leiningen.deploy :refer [deploy]]
7 | '[leiningen.uberjar :refer [uberjar]])
8 |
9 | (defn read-project-clj []
10 | (p/ensure-dynamic-classloader)
11 | (-> "project.clj" load-file var-get))
12 |
13 | (defn read-deps-edn [aliases-to-include]
14 | (let [{:keys [paths deps aliases]} (-> "deps.edn" slurp clojure.edn/read-string)
15 | deps (->> (select-keys aliases aliases-to-include)
16 | vals
17 | (mapcat :extra-deps)
18 | (into deps)
19 | (map (fn parse-coord [coord]
20 | (let [[artifact info] coord
21 | s (str artifact)]
22 | (if-let [i (str/index-of s "$")]
23 | [(symbol (subs s 0 i))
24 | (assoc info :classifier (subs s (inc i)))]
25 | coord))))
26 | (reduce
27 | (fn [deps [artifact info]]
28 | (if-let [version (:mvn/version info)]
29 | (conj deps
30 | (transduce cat conj [artifact version]
31 | (select-keys info [:exclusions :classifier])))
32 | deps))
33 | []))
34 | paths (->> (select-keys aliases aliases-to-include)
35 | vals
36 | (mapcat :extra-paths)
37 | (into paths))]
38 | {:dependencies deps
39 | :source-paths []
40 | :resource-paths paths}))
41 |
42 | (defmulti task first)
43 |
44 | (defmethod task :default
45 | [_]
46 | (let [all-tasks (-> task methods (dissoc :default) keys sort)
47 | interposed (->> all-tasks (interpose ", ") (apply str))]
48 | (println "Unknown or missing task. Choose one of:" interposed)
49 | (System/exit 1)))
50 |
51 | (defmethod task "uberjar"
52 | [[_ os-name]]
53 | (when-not (#{"windows" "macos" "linux"} os-name)
54 | (throw (ex-info "Invalid OS name provided" {})))
55 | (let [project (-> (read-project-clj)
56 | (merge (read-deps-edn [(keyword os-name)]))
57 | (assoc
58 | :aot '[nightcode.start nightcode.core nightcode.lein]
59 | :main 'nightcode.start)
60 | p/init-project)]
61 | (clean project)
62 | (uberjar project))
63 | (System/exit 0))
64 |
65 | (defmethod task "install"
66 | [_]
67 | (-> (read-project-clj)
68 | (merge (read-deps-edn []))
69 | p/init-project
70 | install)
71 | (System/exit 0))
72 |
73 | (defmethod task "deploy"
74 | [_]
75 | (-> (read-project-clj)
76 | (merge (read-deps-edn []))
77 | p/init-project
78 | (deploy "clojars"))
79 | (System/exit 0))
80 |
81 | ;; entry point
82 |
83 | (task *command-line-args*)
84 |
85 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject nightcode "2.8.4-SNAPSHOT"
2 | :description "An IDE for Clojure"
3 | :url "https://github.com/oakes/Nightcode"
4 | :license {:name "Public Domain"
5 | :url "http://unlicense.org/UNLICENSE"}
6 | :repositories [["clojars" {:url "https://clojars.org/repo"
7 | :sign-releases false}]])
8 |
--------------------------------------------------------------------------------
/resources/boot-2.7.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/boot-2.7.2.jar
--------------------------------------------------------------------------------
/resources/dark.css:
--------------------------------------------------------------------------------
1 | .root {
2 | -fx-base: rgb(50, 50, 50);
3 | -fx-background: rgb(50, 50, 50);
4 | -fx-control-inner-background: rgb(50, 50, 50);
5 | }
6 |
7 | .tooltip {
8 | -fx-effect: null;
9 | }
10 |
--------------------------------------------------------------------------------
/resources/dir.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/editor.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/home.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/images/file-clj.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/images/file-clj.png
--------------------------------------------------------------------------------
/resources/images/file-cljc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/images/file-cljc.png
--------------------------------------------------------------------------------
/resources/images/file-cljs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/images/file-cljs.png
--------------------------------------------------------------------------------
/resources/images/file-java.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/images/file-java.png
--------------------------------------------------------------------------------
/resources/images/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/images/file.png
--------------------------------------------------------------------------------
/resources/images/logo_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/images/logo_launcher.png
--------------------------------------------------------------------------------
/resources/images/logo_splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/images/logo_splash.png
--------------------------------------------------------------------------------
/resources/images/player_walk1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/images/player_walk1.png
--------------------------------------------------------------------------------
/resources/images/player_walk2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/images/player_walk2.png
--------------------------------------------------------------------------------
/resources/images/player_walk3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/images/player_walk3.png
--------------------------------------------------------------------------------
/resources/leiningen/new/console.clj:
--------------------------------------------------------------------------------
1 | (ns leiningen.new.console
2 | (:require [leiningen.new.templates :as t]))
3 |
4 | (defn console
5 | [name package-name]
6 | (let [render (t/renderer "console")
7 | package-name (t/sanitize (t/multi-segment (or package-name name)))
8 | main-ns (t/sanitize-ns package-name)
9 | data {:app-name name
10 | :name (t/project-name name)
11 | :namespace main-ns
12 | :path (t/name-to-path main-ns)}]
13 | (t/->files data
14 | ["project.clj" (render "project.clj" data)]
15 | ["README.md" (render "README.md" data)]
16 | [".gitignore" (render "gitignore" data)]
17 | ["src/{{path}}.clj" (render "core.clj" data)]
18 | "resources")))
19 |
--------------------------------------------------------------------------------
/resources/leiningen/new/console/README.md:
--------------------------------------------------------------------------------
1 | # {{name}}
2 |
3 | A Clojure app to ... well, that part is up to you.
4 |
5 | ## Usage
6 |
7 | FIXME
8 |
--------------------------------------------------------------------------------
/resources/leiningen/new/console/core.clj:
--------------------------------------------------------------------------------
1 | (ns {{namespace}}
2 | (:gen-class))
3 |
4 | (defn -main []
5 | (println "Hello, World!"))
6 |
--------------------------------------------------------------------------------
/resources/leiningen/new/console/gitignore:
--------------------------------------------------------------------------------
1 | hs_err_pid*.log
2 | pom.xml
3 | pom.xml.asc
4 | **/gen
5 | **/target
6 | .*
7 | !.gitignore
8 |
--------------------------------------------------------------------------------
/resources/leiningen/new/console/project.clj:
--------------------------------------------------------------------------------
1 | (defproject {{app-name}} "0.0.1-SNAPSHOT"
2 | :description "FIXME: write description"
3 | :dependencies [[org.clojure/clojure "1.10.1"]]
4 | :aot [{{namespace}}]
5 | :main {{namespace}})
6 |
--------------------------------------------------------------------------------
/resources/leiningen/new/edna.clj:
--------------------------------------------------------------------------------
1 | (ns leiningen.new.edna
2 | (:require [leiningen.new.templates :as t]
3 | [clojure.string :as str]))
4 |
5 | (defn sanitize-name [s]
6 | (as-> s $
7 | (str/trim $)
8 | (str/lower-case $)
9 | (str/replace $ "'" "")
10 | (str/replace $ #"[^a-z0-9]" " ")
11 | (str/split $ #" ")
12 | (remove empty? $)
13 | (str/join "-" $)))
14 |
15 | (def initial-score
16 | "[:piano {:octave 4
17 | :tempo 74}
18 |
19 | 1/8 #{:-d :-a :e :f#} :a 1/2 #{:f# :+d}
20 | 1/8 #{:-e :e :+c} :a 1/2 #{:c :e}
21 |
22 | 1/8 #{:-d :-a :e :f#} :a :+d :+c# :+e :+d :b :+c#
23 | 1/2 #{:-e :c :a} 1/2 #{:c :e}]")
24 |
25 | (defn edna-data [name]
26 | (let [sanitized-name (sanitize-name name)]
27 | (when-not (seq sanitized-name)
28 | (throw (Exception. (str "Invalid name: " name))))
29 | {:name sanitized-name
30 | :dir (str/replace sanitized-name "-" "_")
31 | :initial-score initial-score}))
32 |
33 | (defn edna*
34 | [{:keys [dir] :as data}]
35 | (let [render (t/renderer "edna")
36 | music (str "(ns " (:name data) ".music)\n\n" (:initial-score data))]
37 | {"README.md" (render "README.md" data)
38 | ".gitignore" (render "gitignore" data)
39 | "build.boot" (render "build.boot" data)
40 | "boot.properties" (render "boot.properties" data)
41 | (str "src/" dir "/music.clj") music
42 | (str "src/" dir "/core.cljs") (render "core.cljs" data)
43 | (str "src/" dir "/core.clj") (render "core.clj" data)
44 | "resources/public/index.html" (render "index.html" data)
45 | "resources/public/main.cljs.edn" (render "main.cljs.edn" data)}))
46 |
47 | (defn edna
48 | [name & _]
49 | (let [data (edna-data name)
50 | path->content (edna* data)]
51 | (apply t/->files data (vec path->content))))
52 |
53 |
--------------------------------------------------------------------------------
/resources/leiningen/new/edna/README.md:
--------------------------------------------------------------------------------
1 | ## Introduction
2 |
3 | An edna project in which ... well, that part is up to you.
4 |
5 | * `boot run` plays the song and runs Nightlight for live editing
6 | * `boot build` exports an mp3 of the song
7 | * `boot run-cljs` hosts a browser-based version on http://localhost:3000 with live reloading
8 | * `boot build-cljs` creates a browser-based version suitable for putting online
9 |
10 |
--------------------------------------------------------------------------------
/resources/leiningen/new/edna/boot.properties:
--------------------------------------------------------------------------------
1 | BOOT_CLOJURE_VERSION=1.10.1
2 |
--------------------------------------------------------------------------------
/resources/leiningen/new/edna/build.boot:
--------------------------------------------------------------------------------
1 | (set-env!
2 | :source-paths #{"src"}
3 | :resource-paths #{"resources"}
4 | :dependencies '[[org.clojure/clojure "1.10.1" :scope "provided"]
5 | [adzerk/boot-cljs "2.1.5" :scope "test"]
6 | [adzerk/boot-reload "0.6.0" :scope "test"]
7 | [pandeiro/boot-http "0.8.3" :scope "test"
8 | :exclusions [org.clojure/clojure]]
9 | [javax.xml.bind/jaxb-api "2.3.0" :scope "test"] ; necessary for Java 9 compatibility
10 | [org.clojure/clojurescript "1.10.439" :scope "test"]
11 | [edna "1.6.0"]])
12 |
13 | (require
14 | '[edna.core]
15 | '[{{name}}.core]
16 | '[adzerk.boot-cljs :refer [cljs]]
17 | '[adzerk.boot-reload :refer [reload]]
18 | '[pandeiro.boot-http :refer [serve]]
19 | '[clojure.java.io :as io])
20 |
21 | (deftask run []
22 | (comp
23 | (watch)
24 | (with-pass-thru _
25 | ({{name}}.core/-main))))
26 |
27 | (deftask build []
28 | (let [output (io/file "target" "{{name}}.mp3")]
29 | (.mkdir (.getParentFile output))
30 | (with-pass-thru _
31 | (edna.core/export!
32 | ({{name}}.core/read-music)
33 | {:type :mp3
34 | :out output})
35 | (println "Built" (.getCanonicalPath output)))))
36 |
37 | (def cljs-port 3000)
38 |
39 | (deftask run-cljs []
40 | (comp
41 | (serve :dir "target/public" :port cljs-port)
42 | (watch)
43 | (reload)
44 | (cljs
45 | :optimizations :none
46 | :compiler-options {:asset-path "main.out"})
47 | (target)
48 | (with-pass-thru _
49 | (println (str "Serving on http://localhost:" cljs-port)))))
50 |
51 | (deftask build-cljs []
52 | (comp
53 | (cljs :optimizations :advanced)
54 | (target)))
55 |
56 |
--------------------------------------------------------------------------------
/resources/leiningen/new/edna/core.clj:
--------------------------------------------------------------------------------
1 | (ns {{name}}.core
2 | (:require [edna.core :as edna]))
3 |
4 | (defn read-music []
5 | (load-file "src/{{dir}}/music.clj"))
6 |
7 | (defonce state (atom nil))
8 |
9 | (defn -main []
10 | (swap! state edna/stop!)
11 | (reset! state (edna/play! (read-music))))
12 |
13 | (defmacro build-for-cljs []
14 | (edna/edna->data-uri (read-music)))
15 |
16 |
--------------------------------------------------------------------------------
/resources/leiningen/new/edna/core.cljs:
--------------------------------------------------------------------------------
1 | (ns {{name}}.core
2 | (:require-macros [{{name}}.music]
3 | [{{name}}.core :refer [build-for-cljs]]))
4 |
5 | (defonce audio (js/document.createElement "audio"))
6 | (set! (.-src audio) (build-for-cljs))
7 | (set! (.-controls audio) true)
8 | (js/document.body.appendChild audio)
9 | (.play audio)
10 |
11 |
--------------------------------------------------------------------------------
/resources/leiningen/new/edna/gitignore:
--------------------------------------------------------------------------------
1 | data_readers.clj
2 | hs_err_pid*.log
3 | pom.xml
4 | pom.xml.asc
5 | **/gen
6 | **/target
7 | .*
8 | !.gitignore
9 |
--------------------------------------------------------------------------------
/resources/leiningen/new/edna/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/resources/leiningen/new/edna/main.cljs.edn:
--------------------------------------------------------------------------------
1 | {:require [{{name}}.core]
2 | :init-fns []}
3 |
--------------------------------------------------------------------------------
/resources/leiningen/new/graphics.clj:
--------------------------------------------------------------------------------
1 | (ns leiningen.new.graphics
2 | (:require [leiningen.new.templates :as t]))
3 |
4 | (defn graphics
5 | [name package-name]
6 | (let [render (t/renderer "graphics")
7 | package-name (t/sanitize (t/multi-segment (or package-name name)))
8 | main-ns (t/sanitize-ns package-name)
9 | data {:app-name name
10 | :name (t/project-name name)
11 | :namespace main-ns
12 | :path (t/name-to-path main-ns)
13 | :year (t/year)}]
14 | (t/->files data
15 | ["project.clj" (render "project.clj" data)]
16 | ["README.md" (render "README.md" data)]
17 | [".gitignore" (render "gitignore" data)]
18 | ["src/{{path}}.clj" (render "core.clj" data)]
19 | "resources")))
20 |
--------------------------------------------------------------------------------
/resources/leiningen/new/graphics/README.md:
--------------------------------------------------------------------------------
1 | # {{name}}
2 |
3 | A project using Quil in which ... well, that part is up to you.
4 |
--------------------------------------------------------------------------------
/resources/leiningen/new/graphics/core.clj:
--------------------------------------------------------------------------------
1 | (ns {{namespace}}
2 | (:require [quil.core :refer :all]))
3 |
4 | (defn setup []
5 | (smooth))
6 |
7 | (defn draw []
8 | (background 255)
9 | (fill 192)
10 | (ellipse 100 100 30 30))
11 |
12 | (defsketch example
13 | :title "Example"
14 | :setup setup
15 | :draw draw
16 | :size [200 200])
17 |
18 | (defn -main [& args])
19 |
--------------------------------------------------------------------------------
/resources/leiningen/new/graphics/gitignore:
--------------------------------------------------------------------------------
1 | hs_err_pid*.log
2 | pom.xml
3 | pom.xml.asc
4 | **/gen
5 | **/target
6 | .*
7 | !.gitignore
8 |
--------------------------------------------------------------------------------
/resources/leiningen/new/graphics/project.clj:
--------------------------------------------------------------------------------
1 | (defproject {{app-name}} "0.0.1-SNAPSHOT"
2 | :description "FIXME: write description"
3 | :url "http://example.com/FIXME"
4 | :dependencies [[org.clojure/clojure "1.10.1"]
5 | [quil "3.1.0" :exclusions [org.clojure/clojure]]]
6 | :main {{namespace}})
7 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc.clj:
--------------------------------------------------------------------------------
1 | (ns leiningen.new.play-cljc
2 | (:require [leiningen.new.templates :as t]
3 | [clojure.string :as str]
4 | [clojure.java.io :as io]))
5 |
6 | (defn sanitize-name [s]
7 | (as-> s $
8 | (str/trim $)
9 | (str/lower-case $)
10 | (str/replace $ "'" "")
11 | (str/replace $ #"[^a-z0-9]" " ")
12 | (str/split $ #" ")
13 | (remove empty? $)
14 | (str/join "-" $)))
15 |
16 | (defn play-cljc-data [name]
17 | (let [project-name (sanitize-name name)
18 | core-name "core"]
19 | (when-not (seq project-name)
20 | (throw (Exception. (str "Invalid name: " name))))
21 | {:name project-name
22 | :core-name core-name
23 | :project_name (str/replace project-name "-" "_")
24 | :core_name (str/replace core-name "-" "_")}))
25 |
26 | (defn play-cljc*
27 | [{:keys [project_name core_name] :as data}]
28 | (let [render (t/renderer "play-cljc")]
29 | {".gitignore" (render "gitignore" data)
30 | "project.clj" (render "project.clj" data)
31 | (str "src/" project_name "/music.clj") (render "music.clj" data)
32 | (str "src/" project_name "/" core_name ".cljc") (render "core.cljc" data)
33 | (str "src/" project_name "/utils.cljc") (render "utils.cljc" data)
34 | (str "src/" project_name "/move.cljc") (render "move.cljc" data)
35 | (str "src/" project_name "/start.clj") (render "start.clj" data)
36 | (str "src/" project_name "/start_dev.clj") (render "start_dev.clj" data)
37 | "resources/public/index.html" (render "index.html" data)
38 | "resources/public/player_walk1.png" (-> "images/player_walk1.png" io/resource io/input-stream)
39 | "resources/public/player_walk2.png" (-> "images/player_walk2.png" io/resource io/input-stream)
40 | "resources/public/player_walk3.png" (-> "images/player_walk3.png" io/resource io/input-stream)}))
41 |
42 | (defn play-cljc
43 | [name & _]
44 | (let [data (play-cljc-data name)
45 | path->content (play-cljc* data)]
46 | (apply t/->files data (vec path->content))))
47 |
48 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc/core.cljc:
--------------------------------------------------------------------------------
1 | (ns {{name}}.{{core-name}}
2 | (:require [{{name}}.utils :as utils]
3 | [{{name}}.move :as move]
4 | [play-cljc.gl.core :as c]
5 | [play-cljc.gl.entities-2d :as e]
6 | [play-cljc.transforms :as t]
7 | #?(:clj [play-cljc.macros-java :refer [gl math]]
8 | :cljs [play-cljc.macros-js :refer-macros [gl math]])))
9 |
10 | (defonce *state (atom {:mouse-x 0
11 | :mouse-y 0
12 | :pressed-keys #{}
13 | :x-velocity 0
14 | :y-velocity 0
15 | :player-x 0
16 | :player-y 0
17 | :can-jump? false
18 | :direction :right
19 | :player-images {}
20 | :player-image-key :walk1}))
21 |
22 | (defn init [game]
23 | ;; allow transparency in images
24 | (gl game enable (gl game BLEND))
25 | (gl game blendFunc (gl game SRC_ALPHA) (gl game ONE_MINUS_SRC_ALPHA))
26 | ;; load images and put them in the state atom
27 | (doseq [[k path] {:walk1 "player_walk1.png"
28 | :walk2 "player_walk2.png"
29 | :walk3 "player_walk3.png"}]
30 | (utils/get-image path
31 | (fn [{:keys [data width height]}]
32 | (let [;; create an image entity (a map with info necessary to display it)
33 | entity (e/->image-entity game data width height)
34 | ;; compile the shaders so it is ready to render
35 | entity (c/compile game entity)
36 | ;; assoc the width and height to we can reference it later
37 | entity (assoc entity :width width :height height)]
38 | ;; add it to the state
39 | (swap! *state update :player-images assoc k entity))))))
40 |
41 | (def screen-entity
42 | {:viewport {:x 0 :y 0 :width 0 :height 0}
43 | :clear {:color [(/ 173 255) (/ 216 255) (/ 230 255) 1] :depth 1}})
44 |
45 | (defn tick [game]
46 | (let [{:keys [pressed-keys
47 | player-x
48 | player-y
49 | direction
50 | player-images
51 | player-image-key]
52 | :as state} @*state
53 | game-width (utils/get-width game)
54 | game-height (utils/get-height game)]
55 | (when (and (pos? game-width) (pos? game-height))
56 | ;; render the blue background
57 | (c/render game (update screen-entity :viewport
58 | assoc :width game-width :height game-height))
59 | ;; get the current player image to display
60 | (when-let [player (get player-images player-image-key)]
61 | (let [player-width (/ game-width 10)
62 | player-height (* player-width (/ (:height player) (:width player)))]
63 | ;; render the player
64 | (c/render game
65 | (-> player
66 | (t/project game-width game-height)
67 | (t/translate (cond-> player-x
68 | (= direction :left)
69 | (+ player-width))
70 | player-y)
71 | (t/scale (cond-> player-width
72 | (= direction :left)
73 | (* -1))
74 | player-height)))
75 | ;; change the state to move the player
76 | (swap! *state
77 | (fn [state]
78 | (->> (assoc state
79 | :player-width player-width
80 | :player-height player-height)
81 | (move/move game)
82 | (move/prevent-move game)
83 | (move/animate game))))))))
84 | ;; return the game map
85 | game)
86 |
87 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc/gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | resources/public/*.js
3 | resources/public/*.out
4 | .*
5 | !.gitignore
6 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello, world!
4 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc/move.cljc:
--------------------------------------------------------------------------------
1 | (ns {{name}}.move
2 | (:require [{{name}}.utils :as utils]
3 | #?(:clj [play-cljc.macros-java :refer [gl math]]
4 | :cljs [play-cljc.macros-js :refer-macros [gl math]])))
5 |
6 | (def ^:const damping 0.1)
7 | (def ^:const max-velocity 1000)
8 | (def ^:const max-jump-velocity (* max-velocity 8))
9 | (def ^:const deceleration 0.7)
10 | (def ^:const gravity 500)
11 | (def ^:const animation-secs 0.2)
12 |
13 | (defn decelerate
14 | [velocity]
15 | (let [velocity (* velocity deceleration)]
16 | (if (< (math abs velocity) damping)
17 | 0
18 | velocity)))
19 |
20 | (defn get-x-velocity
21 | [{:keys [pressed-keys x-velocity]}]
22 | (cond
23 | (contains? pressed-keys :left)
24 | (* -1 max-velocity)
25 | (contains? pressed-keys :right)
26 | max-velocity
27 | :else
28 | x-velocity))
29 |
30 | (defn get-y-velocity
31 | [{:keys [pressed-keys y-velocity can-jump?]}]
32 | (cond
33 | (and can-jump? (contains? pressed-keys :up))
34 | (* -1 max-jump-velocity)
35 | :else
36 | y-velocity))
37 |
38 | (defn get-direction
39 | [{:keys [x-velocity direction]}]
40 | (cond
41 | (> x-velocity 0) :right
42 | (< x-velocity 0) :left
43 | :else
44 | direction))
45 |
46 | (defn move
47 | [{:keys [delta-time] :as game} {:keys [player-x player-y can-jump?] :as state}]
48 | (let [x-velocity (get-x-velocity state)
49 | y-velocity (+ (get-y-velocity state) gravity)
50 | x-change (* x-velocity delta-time)
51 | y-change (* y-velocity delta-time)]
52 | (if (or (not= 0 x-change) (not= 0 y-change))
53 | (assoc state
54 | :x-velocity (decelerate x-velocity)
55 | :y-velocity (decelerate y-velocity)
56 | :x-change x-change
57 | :y-change y-change
58 | :player-x (+ player-x x-change)
59 | :player-y (+ player-y y-change)
60 | :can-jump? (if (neg? y-velocity) false can-jump?))
61 | state)))
62 |
63 | (defn prevent-move
64 | [game {:keys [player-x player-y
65 | player-width player-height
66 | x-change y-change]
67 | :as state}]
68 | (let [old-x (- player-x x-change)
69 | old-y (- player-y y-change)
70 | up? (neg? y-change)
71 | game-width (utils/get-width game)
72 | game-height (utils/get-height game)]
73 | (merge state
74 | (when (or (< player-x 0)
75 | (> player-x (- game-width player-width)))
76 | {:x-velocity 0 :x-change 0 :player-x old-x})
77 | (when (> player-y (- game-height player-height))
78 | {:y-velocity 0 :y-change 0 :player-y old-y :can-jump? (not up?)}))))
79 |
80 | (defn animate
81 | [{:keys [total-time]}
82 | {:keys [x-velocity y-velocity direction
83 | player-images player-image-key]
84 | :as state}]
85 | (let [direction (get-direction state)]
86 | (-> state
87 | (assoc :player-image-key
88 | (if (and (not= x-velocity 0)
89 | (= y-velocity 0))
90 | (let [image-keys (->> player-images keys sort vec)
91 | cycle-time (mod total-time (* animation-secs (count image-keys)))]
92 | (nth image-keys (int (/ cycle-time animation-secs))))
93 | player-image-key))
94 | (assoc :direction direction))))
95 |
96 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc/music.clj:
--------------------------------------------------------------------------------
1 | (ns {{name}}.music
2 | (:require [edna.core :as edna]
3 | [clojure.java.io :as io]))
4 |
5 | (def music
6 | [:electric-guitar-clean {:tempo 130}
7 | 1/8 :c :c 1/2 :a 1/8 :f :g :f
8 | 1/2 :d 1/8 :d :d 1/2 :a# 1/8 :g :a :g
9 | 1/2 :e 1/8 :e :e 1/2 :+c 1/8 :a :a# :+c
10 | 1/2 :+d 1/8 :f :g 1/4 :a 3/8 :f 1/8 :e :f :g :d])
11 |
12 | (defn build-for-clj []
13 | (-> (edna/export! music {:type :wav})
14 | .toByteArray
15 | io/input-stream))
16 |
17 | (def edna->data-uri
18 | (memoize edna/edna->data-uri))
19 |
20 | (defmacro build-for-cljs []
21 | (edna->data-uri music))
22 |
23 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc/project.clj:
--------------------------------------------------------------------------------
1 | (defproject {{name}} "0.1.0-SNAPSHOT"
2 | :repositories [["clojars" {:url "https://clojars.org/repo"
3 | :sign-releases false}]]
4 | :clean-targets ^{:protect false} ["target"]
5 | :dependencies [[org.clojure/clojure "1.10.1"]
6 | [edna "1.6.0"]]
7 | :jvm-opts ~(if (= "Mac OS X" (System/getProperty "os.name"))
8 | ["-XstartOnFirstThread"]
9 | [])
10 | :profiles {:dev {:main {{name}}.start-dev
11 | :dependencies [[paravim "RELEASE"]
12 | [orchestra "2018.12.06-2"]
13 | [expound "0.7.2"]]}
14 | :uberjar {:main {{name}}.start
15 | :aot [{{name}}.start]
16 | :dependencies [[play-cljc "0.8.5"]
17 | [org.lwjgl/lwjgl "3.2.3"]
18 | [org.lwjgl/lwjgl-glfw "3.2.3"]
19 | [org.lwjgl/lwjgl-opengl "3.2.3"]
20 | [org.lwjgl/lwjgl-stb "3.2.3"]
21 | [org.lwjgl/lwjgl "3.2.3" :classifier "natives-linux"]
22 | [org.lwjgl/lwjgl-glfw "3.2.3" :classifier "natives-linux"]
23 | [org.lwjgl/lwjgl-opengl "3.2.3" :classifier "natives-linux"]
24 | [org.lwjgl/lwjgl-stb "3.2.3" :classifier "natives-linux"]
25 | [org.lwjgl/lwjgl "3.2.3" :classifier "natives-macos"]
26 | [org.lwjgl/lwjgl-glfw "3.2.3" :classifier "natives-macos"]
27 | [org.lwjgl/lwjgl-opengl "3.2.3" :classifier "natives-macos"]
28 | [org.lwjgl/lwjgl-stb "3.2.3" :classifier "natives-macos"]
29 | [org.lwjgl/lwjgl "3.2.3" :classifier "natives-windows"]
30 | [org.lwjgl/lwjgl-glfw "3.2.3" :classifier "natives-windows"]
31 | [org.lwjgl/lwjgl-opengl "3.2.3" :classifier "natives-windows"]
32 | [org.lwjgl/lwjgl-stb "3.2.3" :classifier "natives-windows"]]}})
33 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc/start.clj:
--------------------------------------------------------------------------------
1 | (ns {{name}}.start
2 | (:require [{{name}}.{{core-name}} :as c]
3 | [{{name}}.music :as m]
4 | [play-cljc.gl.core :as pc])
5 | (:import [org.lwjgl.glfw GLFW Callbacks
6 | GLFWCursorPosCallbackI GLFWKeyCallbackI GLFWMouseButtonCallbackI
7 | GLFWCharCallbackI GLFWFramebufferSizeCallbackI]
8 | [org.lwjgl.opengl GL GL41]
9 | [org.lwjgl.system MemoryUtil]
10 | [javax.sound.sampled AudioSystem Clip])
11 | (:gen-class))
12 |
13 | (defn play-music! []
14 | (doto (AudioSystem/getClip)
15 | (.open (AudioSystem/getAudioInputStream (m/build-for-clj)))
16 | (.loop Clip/LOOP_CONTINUOUSLY)
17 | (.start)))
18 |
19 | (defn mousecode->keyword [mousecode]
20 | (condp = mousecode
21 | GLFW/GLFW_MOUSE_BUTTON_LEFT :left
22 | GLFW/GLFW_MOUSE_BUTTON_RIGHT :right
23 | nil))
24 |
25 | (defn on-mouse-move! [window xpos ypos]
26 | (let [*fb-width (MemoryUtil/memAllocInt 1)
27 | *fb-height (MemoryUtil/memAllocInt 1)
28 | *window-width (MemoryUtil/memAllocInt 1)
29 | *window-height (MemoryUtil/memAllocInt 1)
30 | _ (GLFW/glfwGetFramebufferSize window *fb-width *fb-height)
31 | _ (GLFW/glfwGetWindowSize window *window-width *window-height)
32 | fb-width (.get *fb-width)
33 | fb-height (.get *fb-height)
34 | window-width (.get *window-width)
35 | window-height (.get *window-height)
36 | width-ratio (/ fb-width window-width)
37 | height-ratio (/ fb-height window-height)
38 | x (* xpos width-ratio)
39 | y (* ypos height-ratio)]
40 | (MemoryUtil/memFree *fb-width)
41 | (MemoryUtil/memFree *fb-height)
42 | (MemoryUtil/memFree *window-width)
43 | (MemoryUtil/memFree *window-height)
44 | (swap! c/*state assoc :mouse-x x :mouse-y y)))
45 |
46 | (defn on-mouse-click! [window button action mods]
47 | (swap! c/*state assoc :mouse-button (when (= action GLFW/GLFW_PRESS)
48 | (mousecode->keyword button))))
49 |
50 | (defn keycode->keyword [keycode]
51 | (condp = keycode
52 | GLFW/GLFW_KEY_LEFT :left
53 | GLFW/GLFW_KEY_RIGHT :right
54 | GLFW/GLFW_KEY_UP :up
55 | GLFW/GLFW_KEY_DOWN :down
56 | nil))
57 |
58 | (defn on-key! [window keycode scancode action mods]
59 | (when-let [k (keycode->keyword keycode)]
60 | (condp = action
61 | GLFW/GLFW_PRESS (swap! c/*state update :pressed-keys conj k)
62 | GLFW/GLFW_RELEASE (swap! c/*state update :pressed-keys disj k)
63 | nil)))
64 |
65 | (defn on-char! [window codepoint])
66 |
67 | (defn on-resize! [window width height])
68 |
69 | (defprotocol Events
70 | (on-mouse-move [this xpos ypos])
71 | (on-mouse-click [this button action mods])
72 | (on-key [this keycode scancode action mods])
73 | (on-char [this codepoint])
74 | (on-resize [this width height])
75 | (on-tick [this game]))
76 |
77 | (defrecord Window [handle])
78 |
79 | (extend-type Window
80 | Events
81 | (on-mouse-move [{:keys [handle]} xpos ypos]
82 | (on-mouse-move! handle xpos ypos))
83 | (on-mouse-click [{:keys [handle]} button action mods]
84 | (on-mouse-click! handle button action mods))
85 | (on-key [{:keys [handle]} keycode scancode action mods]
86 | (on-key! handle keycode scancode action mods))
87 | (on-char [{:keys [handle]} codepoint]
88 | (on-char! handle codepoint))
89 | (on-resize [{:keys [handle]} width height]
90 | (on-resize! handle width height))
91 | (on-tick [this game]
92 | (c/tick game)))
93 |
94 | (defn listen-for-events [{:keys [handle] :as window}]
95 | (doto handle
96 | (GLFW/glfwSetCursorPosCallback
97 | (reify GLFWCursorPosCallbackI
98 | (invoke [this _ xpos ypos]
99 | (on-mouse-move window xpos ypos))))
100 | (GLFW/glfwSetMouseButtonCallback
101 | (reify GLFWMouseButtonCallbackI
102 | (invoke [this _ button action mods]
103 | (on-mouse-click window button action mods))))
104 | (GLFW/glfwSetKeyCallback
105 | (reify GLFWKeyCallbackI
106 | (invoke [this _ keycode scancode action mods]
107 | (on-key window keycode scancode action mods))))
108 | (GLFW/glfwSetCharCallback
109 | (reify GLFWCharCallbackI
110 | (invoke [this _ codepoint]
111 | (on-char window codepoint))))
112 | (GLFW/glfwSetFramebufferSizeCallback
113 | (reify GLFWFramebufferSizeCallbackI
114 | (invoke [this _ width height]
115 | (on-resize window width height))))))
116 |
117 | (defn ->window []
118 | (when-not (GLFW/glfwInit)
119 | (throw (Exception. "Unable to initialize GLFW")))
120 | (GLFW/glfwWindowHint GLFW/GLFW_VISIBLE GLFW/GLFW_FALSE)
121 | (GLFW/glfwWindowHint GLFW/GLFW_RESIZABLE GLFW/GLFW_TRUE)
122 | (GLFW/glfwWindowHint GLFW/GLFW_CONTEXT_VERSION_MAJOR 4)
123 | (GLFW/glfwWindowHint GLFW/GLFW_CONTEXT_VERSION_MINOR 1)
124 | (GLFW/glfwWindowHint GLFW/GLFW_OPENGL_FORWARD_COMPAT GL41/GL_TRUE)
125 | (GLFW/glfwWindowHint GLFW/GLFW_OPENGL_PROFILE GLFW/GLFW_OPENGL_CORE_PROFILE)
126 | (if-let [window (GLFW/glfwCreateWindow 1024 768 "Hello, world!" 0 0)]
127 | (do
128 | (GLFW/glfwMakeContextCurrent window)
129 | (GLFW/glfwSwapInterval 1)
130 | (GL/createCapabilities)
131 | (->Window window))
132 | (throw (Exception. "Failed to create window"))))
133 |
134 | (defn start [game window]
135 | (let [handle (:handle window)
136 | game (assoc game :delta-time 0 :total-time 0)]
137 | (GLFW/glfwShowWindow handle)
138 | (listen-for-events window)
139 | (c/init game)
140 | ; uncomment this to hear music when the game begins!
141 | ;(play-music!)
142 | (loop [game game]
143 | (when-not (GLFW/glfwWindowShouldClose handle)
144 | (let [ts (GLFW/glfwGetTime)
145 | game (assoc game
146 | :delta-time (- ts (:total-time game))
147 | :total-time ts)
148 | game (on-tick window game)]
149 | (GLFW/glfwSwapBuffers handle)
150 | (GLFW/glfwPollEvents)
151 | (recur game))))
152 | (Callbacks/glfwFreeCallbacks handle)
153 | (GLFW/glfwDestroyWindow handle)
154 | (GLFW/glfwTerminate)))
155 |
156 | (defn -main [& args]
157 | (let [window (->window)]
158 | (start (pc/->game (:handle window)) window)))
159 |
160 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc/start.cljs:
--------------------------------------------------------------------------------
1 | (ns {{name}}.start
2 | (:require [{{name}}.{{core-name}} :as c]
3 | [play-cljc.gl.core :as pc]
4 | [goog.events :as events])
5 | (:require-macros [{{name}}.music :refer [build-for-cljs]]))
6 |
7 | (defn game-loop [game]
8 | (let [game (c/tick game)]
9 | (js/requestAnimationFrame
10 | (fn [ts]
11 | (let [ts (* ts 0.001)]
12 | (game-loop (assoc game
13 | :delta-time (- ts (:total-time game))
14 | :total-time ts)))))))
15 |
16 | (defn listen-for-mouse [canvas]
17 | (events/listen js/window "mousemove"
18 | (fn [event]
19 | (swap! c/*state
20 | (fn [state]
21 | (let [bounds (.getBoundingClientRect canvas)
22 | x (- (.-clientX event) (.-left bounds))
23 | y (- (.-clientY event) (.-top bounds))]
24 | (assoc state :mouse-x x :mouse-y y)))))))
25 |
26 | (defn keycode->keyword [keycode]
27 | (condp = keycode
28 | 37 :left
29 | 39 :right
30 | 38 :up
31 | nil))
32 |
33 | (defn listen-for-keys []
34 | (events/listen js/window "keydown"
35 | (fn [event]
36 | (when-let [k (keycode->keyword (.-keyCode event))]
37 | (swap! c/*state update :pressed-keys conj k))))
38 | (events/listen js/window "keyup"
39 | (fn [event]
40 | (when-let [k (keycode->keyword (.-keyCode event))]
41 | (swap! c/*state update :pressed-keys disj k)))))
42 |
43 | (defn resize [context]
44 | (let [display-width context.canvas.clientWidth
45 | display-height context.canvas.clientHeight]
46 | (set! context.canvas.width display-width)
47 | (set! context.canvas.height display-height)))
48 |
49 | (defn listen-for-resize [context]
50 | (events/listen js/window "resize"
51 | (fn [event]
52 | (resize context))))
53 |
54 | ;; start the game
55 |
56 | (defonce context
57 | (let [canvas (js/document.querySelector "canvas")
58 | context (.getContext canvas "webgl2")
59 | initial-game (assoc (pc/->game context)
60 | :delta-time 0
61 | :total-time 0)]
62 | (listen-for-mouse canvas)
63 | (listen-for-keys)
64 | (resize context)
65 | (listen-for-resize context)
66 | (c/init initial-game)
67 | (game-loop initial-game)
68 | context))
69 |
70 | ;; build music, put it in the audio tag, and make the button toggle it on and off
71 |
72 | (defonce play-music? (atom false))
73 |
74 | (defonce audio (js/document.querySelector "#audio"))
75 | (set! (.-src audio) (build-for-cljs))
76 | (when @play-music? (.play audio))
77 |
78 | (defonce button (js/document.querySelector "#audio-button"))
79 | (set! (.-onclick button)
80 | (fn [e]
81 | (if (swap! play-music? not)
82 | (.play audio)
83 | (.pause audio))))
84 |
85 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc/start_dev.clj:
--------------------------------------------------------------------------------
1 | (ns {{name}}.start-dev
2 | (:require [{{name}}.start :as start]
3 | [{{name}}.{{core-name}} :as c]
4 | [orchestra.spec.test :as st]
5 | [expound.alpha :as expound]
6 | [clojure.spec.alpha :as s]
7 | [play-cljc.gl.core :as pc]
8 | [paravim.start]
9 | [paravim.core])
10 | (:import [org.lwjgl.glfw GLFW]
11 | [{{project_name}}.start Window]))
12 |
13 | (defn start-paravim [game]
14 | (let [game (paravim.start/init game)
15 | *focus-on-game? (atom true)
16 | *last-tick (atom 0)]
17 | (extend-type Window
18 | start/Events
19 | (on-mouse-move [{:keys [handle]} xpos ypos]
20 | (if @*focus-on-game?
21 | (try
22 | (start/on-mouse-move! handle xpos ypos)
23 | (catch Exception e (.printStackTrace e)))
24 | (paravim.start/on-mouse-move! game handle xpos ypos)))
25 | (on-mouse-click [{:keys [handle]} button action mods]
26 | (if @*focus-on-game?
27 | (try
28 | (start/on-mouse-click! handle button action mods)
29 | (catch Exception e (.printStackTrace e)))
30 | (paravim.start/on-mouse-click! game handle button action mods)))
31 | (on-key [{:keys [handle]} keycode scancode action mods]
32 | (if (and (= action GLFW/GLFW_PRESS)
33 | (= keycode GLFW/GLFW_KEY_ESCAPE)
34 | (= (paravim.core/get-mode) 'NORMAL))
35 | (swap! *focus-on-game? not)
36 | (if @*focus-on-game?
37 | (try
38 | (start/on-key! handle keycode scancode action mods)
39 | (catch Exception e (.printStackTrace e)))
40 | (paravim.start/on-key! game handle keycode scancode action mods))))
41 | (on-char [{:keys [handle]} codepoint]
42 | (if @*focus-on-game?
43 | (try
44 | (start/on-char! handle codepoint)
45 | (catch Exception e (.printStackTrace e)))
46 | (paravim.start/on-char! game handle codepoint)))
47 | (on-resize [{:keys [handle]} width height]
48 | (try
49 | (start/on-resize! handle width height)
50 | (catch Exception e (.printStackTrace e)))
51 | (paravim.start/on-resize! game handle width height))
52 | (on-tick [this game]
53 | (cond-> (try
54 | (assoc (c/tick game) :paravim.core/clear? false)
55 | (catch Exception e
56 | (let [current-ms (System/currentTimeMillis)]
57 | (when (> (- current-ms @*last-tick) 1000)
58 | (reset! *last-tick current-ms)
59 | (.printStackTrace e)))
60 | (reset! *focus-on-game? false)
61 | (assoc game :paravim.core/clear? true)))
62 | (not @*focus-on-game?)
63 | paravim.core/tick)))))
64 |
65 | (defn -main []
66 | (st/instrument)
67 | (set! s/*explain-out* expound/printer)
68 | (let [window (start/->window)
69 | game (pc/->game (:handle window))]
70 | (try
71 | (start-paravim game)
72 | (catch Throwable e (.printStackTrace e)))
73 | (start/start game window)))
74 |
75 |
--------------------------------------------------------------------------------
/resources/leiningen/new/play_cljc/utils.cljc:
--------------------------------------------------------------------------------
1 | (ns {{name}}.utils
2 | #?(:clj (:require [clojure.java.io :as io]))
3 | #?(:clj (:import [java.nio ByteBuffer]
4 | [org.lwjgl.glfw GLFW]
5 | [org.lwjgl.system MemoryUtil]
6 | [org.lwjgl.stb STBImage])))
7 |
8 | (defn get-image [fname callback]
9 | #?(:clj (let [is (io/input-stream (io/resource (str "public/" fname)))
10 | ^bytes barray (with-open [out (java.io.ByteArrayOutputStream.)]
11 | (io/copy is out)
12 | (.toByteArray out))
13 | *width (MemoryUtil/memAllocInt 1)
14 | *height (MemoryUtil/memAllocInt 1)
15 | *components (MemoryUtil/memAllocInt 1)
16 | direct-buffer (doto ^ByteBuffer (ByteBuffer/allocateDirect (alength barray))
17 | (.put barray)
18 | (.flip))
19 | decoded-image (STBImage/stbi_load_from_memory
20 | direct-buffer *width *height *components
21 | STBImage/STBI_rgb_alpha)
22 | image {:data decoded-image
23 | :width (.get *width)
24 | :height (.get *height)}]
25 | (MemoryUtil/memFree *width)
26 | (MemoryUtil/memFree *height)
27 | (MemoryUtil/memFree *components)
28 | (callback image))
29 | :cljs (let [image (js/Image.)]
30 | (doto image
31 | (-> .-src (set! fname))
32 | (-> .-onload (set! #(callback {:data image
33 | :width image.width
34 | :height image.height})))))))
35 |
36 | (defn get-width [game]
37 | #?(:clj (let [*width (MemoryUtil/memAllocInt 1)
38 | _ (GLFW/glfwGetFramebufferSize ^long (:context game) *width nil)
39 | n (.get *width)]
40 | (MemoryUtil/memFree *width)
41 | n)
42 | :cljs (-> game :context .-canvas .-clientWidth)))
43 |
44 | (defn get-height [game]
45 | #?(:clj (let [*height (MemoryUtil/memAllocInt 1)
46 | _ (GLFW/glfwGetFramebufferSize ^long (:context game) nil *height)
47 | n (.get *height)]
48 | (MemoryUtil/memFree *height)
49 | n)
50 | :cljs (-> game :context .-canvas .-clientHeight)))
51 |
52 |
--------------------------------------------------------------------------------
/resources/leiningen/new/web.clj:
--------------------------------------------------------------------------------
1 | (ns leiningen.new.web
2 | (:require [leiningen.new.templates :as t]))
3 |
4 | (defn web
5 | [name package-name]
6 | (let [render (t/renderer "web")
7 | package-name (t/sanitize (t/multi-segment (or package-name name)))
8 | main-ns (t/sanitize-ns package-name)
9 | data {:app-name name
10 | :name (t/project-name name)
11 | :namespace main-ns
12 | :path (t/name-to-path main-ns)}]
13 | (t/->files data
14 | ["boot.properties" (render "boot.properties" data)]
15 | ["build.boot" (render "build.boot" data)]
16 | ["README.md" (render "README.md" data)]
17 | [".gitignore" (render "gitignore" data)]
18 | ["src/clj/{{path}}.clj" (render "core.clj" data)]
19 | ["src/cljs/{{path}}.cljs" (render "core.cljs" data)]
20 | ["resources/public/index.html" (render "index.html" data)]
21 | ["resources/public/main.cljs.edn" (render "main.cljs.edn.txt" data)])))
22 |
23 |
--------------------------------------------------------------------------------
/resources/leiningen/new/web/README.md:
--------------------------------------------------------------------------------
1 | # {{name}}
2 |
3 | A ClojureScript app to ... well, that part is up to you.
4 |
5 | ## Contents
6 |
7 | * `resources` The assets
8 | * `src/cljs` The client-side code
9 | * `src/clj` The server-side code
10 |
--------------------------------------------------------------------------------
/resources/leiningen/new/web/boot.properties:
--------------------------------------------------------------------------------
1 | BOOT_CLOJURE_VERSION=1.10.1
2 |
--------------------------------------------------------------------------------
/resources/leiningen/new/web/build.boot:
--------------------------------------------------------------------------------
1 | (set-env!
2 | :source-paths #{"src/clj" "src/cljs"}
3 | :resource-paths #{"resources"}
4 | :dependencies '[[adzerk/boot-cljs "2.1.5" :scope "test"]
5 | [adzerk/boot-reload "0.6.0" :scope "test"]
6 | [javax.xml.bind/jaxb-api "2.3.0" :scope "test"] ; necessary for Java 9 compatibility
7 | ; project deps
8 | [org.clojure/clojure "1.10.1"]
9 | [org.clojure/clojurescript "1.10.439" :scope "test"]
10 | [reagent "0.8.1" :scope "test"]
11 | [ring "1.7.1"]])
12 |
13 | (task-options!
14 | pom {:project '{{app-name}}
15 | :version "1.0.0-SNAPSHOT"
16 | :description "FIXME: write description"}
17 | aot {:namespace #{'{{namespace}}}}
18 | jar {:main '{{namespace}}})
19 |
20 | (require
21 | '[adzerk.boot-cljs :refer [cljs]]
22 | '[adzerk.boot-reload :refer [reload]]
23 | '{{namespace}})
24 |
25 | (deftask run []
26 | (comp
27 | (with-pass-thru _
28 | ({{namespace}}/dev-main))
29 | (watch)
30 | (reload :asset-path "public")
31 | (cljs
32 | :source-map true
33 | :optimizations :none
34 | :compiler-options {:asset-path "main.out"})
35 | (target)))
36 |
37 | (deftask build []
38 | (comp
39 | (cljs :optimizations :advanced)
40 | (aot)
41 | (pom)
42 | (uber)
43 | (jar)
44 | (sift :include #{#"\.jar$"})
45 | (target)))
46 |
47 |
--------------------------------------------------------------------------------
/resources/leiningen/new/web/core.clj:
--------------------------------------------------------------------------------
1 | (ns {{namespace}}
2 | (:require [ring.adapter.jetty :refer [run-jetty]]
3 | [ring.middleware.file :refer [wrap-file]]
4 | [ring.middleware.resource :refer [wrap-resource]]
5 | [ring.middleware.content-type :refer [wrap-content-type]]
6 | [ring.util.response :refer [not-found]]
7 | [clojure.java.io :as io])
8 | (:gen-class))
9 |
10 | (defn start-web-server! [handler]
11 | (run-jetty handler {:port 3000 :join? false}))
12 |
13 | (defn web-handler [request]
14 | (case (:uri request)
15 | "/" {:status 200
16 | :headers {"Content-Type" "text/html"}
17 | :body (-> "public/index.html" io/resource slurp)}
18 | (not-found "Page not found")))
19 |
20 | (defn dev-main []
21 | (.mkdirs (io/file "target" "public"))
22 | (start-web-server! (-> web-handler
23 | (wrap-content-type)
24 | (wrap-file "target/public"))))
25 |
26 | (defn -main [& args]
27 | (start-web-server! (-> web-handler
28 | (wrap-content-type)
29 | (wrap-resource "public"))))
30 |
31 |
--------------------------------------------------------------------------------
/resources/leiningen/new/web/core.cljs:
--------------------------------------------------------------------------------
1 | (ns {{namespace}}
2 | (:require [reagent.core :as r]))
3 |
4 | (defn content [state]
5 | [:div (:text @state)])
6 |
7 | (defn init []
8 | (let [state (r/atom {:text "Hello, world!"})]
9 | (r/render-component [content state]
10 | (.querySelector js/document "#content"))))
11 |
12 | (init)
13 |
14 |
--------------------------------------------------------------------------------
/resources/leiningen/new/web/gitignore:
--------------------------------------------------------------------------------
1 | hs_err_pid*.log
2 | pom.xml
3 | pom.xml.asc
4 | **/gen
5 | **/target
6 | .*
7 | !.gitignore
8 | cljs.js
9 |
--------------------------------------------------------------------------------
/resources/leiningen/new/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/resources/leiningen/new/web/main.cljs.edn.txt:
--------------------------------------------------------------------------------
1 | {:require [{{namespace}}]
2 | :init-fns []
3 | :compiler-options {}}
4 |
--------------------------------------------------------------------------------
/resources/main.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/resources/project.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/resources/public/cheatsheet_files/jquery.tipTip.js:
--------------------------------------------------------------------------------
1 | /*
2 | * TipTip
3 | * Copyright 2010 Drew Wilson
4 | * www.drewwilson.com
5 | * code.drewwilson.com/entry/tiptip-jquery-plugin
6 | *
7 | * Version 1.3 - Updated: Mar. 23, 2010
8 | *
9 | * This Plug-In will create a custom tooltip to replace the default
10 | * browser tooltip. It is extremely lightweight and very smart in
11 | * that it detects the edges of the browser window and will make sure
12 | * the tooltip stays within the current window size. As a result the
13 | * tooltip will adjust itself to be displayed above, below, to the left
14 | * or to the right depending on what is necessary to stay within the
15 | * browser window. It is completely customizable as well via CSS.
16 | *
17 | * This TipTip jQuery plug-in is dual licensed under the MIT and GPL licenses:
18 | * http://www.opensource.org/licenses/mit-license.php
19 | * http://www.gnu.org/licenses/gpl.html
20 | */
21 |
22 | (function($){
23 | $.fn.tipTip = function(options) {
24 | var defaults = {
25 | activation: "hover",
26 | keepAlive: false,
27 | maxWidth: "620px",
28 | edgeOffset: 3,
29 | defaultPosition: "bottom",
30 | delay: 200,
31 | fadeIn: 100,
32 | fadeOut: 100,
33 | attribute: "title",
34 | content: false, // HTML or String to fill TipTIp with
35 | enter: function(){},
36 | exit: function(){}
37 | };
38 | var opts = $.extend(defaults, options);
39 |
40 | // Setup tip tip elements and render them to the DOM
41 | if($("#tiptip_holder").length <= 0){
42 | var tiptip_holder = $('');
43 | var tiptip_content = $('');
44 | var tiptip_arrow = $('');
45 | $("body").append(tiptip_holder.html(tiptip_content).prepend(tiptip_arrow.html('')));
46 | } else {
47 | var tiptip_holder = $("#tiptip_holder");
48 | var tiptip_content = $("#tiptip_content");
49 | var tiptip_arrow = $("#tiptip_arrow");
50 | }
51 |
52 | return this.each(function(){
53 | var org_elem = $(this);
54 | if(opts.content){
55 | var org_title = opts.content;
56 | } else {
57 | var org_title = org_elem.attr(opts.attribute);
58 | }
59 | if(org_title != ""){
60 | if(!opts.content){
61 | org_elem.removeAttr(opts.attribute); //remove original Attribute
62 | }
63 | var timeout = false;
64 |
65 | if(opts.activation == "hover"){
66 | org_elem.hover(function(){
67 | active_tiptip();
68 | }, function(){
69 | if(!opts.keepAlive){
70 | deactive_tiptip();
71 | }
72 | });
73 | if(opts.keepAlive){
74 | tiptip_holder.hover(function(){}, function(){
75 | deactive_tiptip();
76 | });
77 | }
78 | } else if(opts.activation == "focus"){
79 | org_elem.focus(function(){
80 | active_tiptip();
81 | }).blur(function(){
82 | deactive_tiptip();
83 | });
84 | } else if(opts.activation == "click"){
85 | org_elem.click(function(){
86 | active_tiptip();
87 | return false;
88 | }).hover(function(){},function(){
89 | if(!opts.keepAlive){
90 | deactive_tiptip();
91 | }
92 | });
93 | if(opts.keepAlive){
94 | tiptip_holder.hover(function(){}, function(){
95 | deactive_tiptip();
96 | });
97 | }
98 | }
99 |
100 | function active_tiptip(){
101 | opts.enter.call(this);
102 | tiptip_content.html(org_title);
103 | tiptip_holder.hide().removeAttr("class").css("margin","0");
104 | tiptip_arrow.removeAttr("style");
105 |
106 | var top = parseInt(org_elem.offset()['top']);
107 | var left = parseInt(org_elem.offset()['left']);
108 | var org_width = parseInt(org_elem.outerWidth());
109 | var org_height = parseInt(org_elem.outerHeight());
110 | var tip_w = tiptip_holder.outerWidth();
111 | var tip_h = tiptip_holder.outerHeight();
112 | var w_compare = Math.round((org_width - tip_w) / 2);
113 | var t_class = "";
114 |
115 | var tip_height_with_margin = (tip_h + 15);
116 | var tip_top_if_below = Math.round(top + org_height + opts.edgeOffset);
117 | var tip_top_if_above = Math.round(top - (opts.edgeOffset + tip_height_with_margin));
118 | var window_top = parseInt($(window).scrollTop());
119 | var window_bottom = window_top + parseInt($(window).height());
120 |
121 | if ((tip_top_if_below + tip_height_with_margin) <= window_bottom){
122 | t_class = "_bottom";
123 | marg_top = tip_top_if_below;
124 | arrow_top = -12;
125 | } else if (tip_top_if_above >= window_top){
126 | t_class = "_top";
127 | marg_top = tip_top_if_above;
128 | arrow_top = tip_h;
129 | } else {
130 | t_class = "_bottom";
131 | marg_top = tip_top_if_below;
132 | arrow_top = -12;
133 | }
134 |
135 | var window_left = parseInt($(window).scrollLeft());
136 | var window_right = window_left + parseInt($(window).width());
137 | var marg_left = Math.round(left + w_compare);
138 | if (marg_left < window_left + 10){
139 | marg_left = window_left + 10;
140 | } else if ((marg_left + tip_w) > window_right - 10){
141 | marg_left = window_right - 10 - tip_w;
142 | }
143 | var arrow_left = Math.round(left + (org_width / 2) - marg_left);
144 |
145 | tiptip_arrow.css({"margin-left": arrow_left+"px", "margin-top": arrow_top+"px"});
146 | tiptip_holder.css({"margin-left": marg_left+"px", "margin-top": marg_top+"px"}).attr("class","tip"+t_class);
147 |
148 | if (timeout){ clearTimeout(timeout); }
149 | timeout = setTimeout(function(){ tiptip_holder.stop(true,true).fadeIn(opts.fadeIn); }, opts.delay);
150 | }
151 |
152 | function deactive_tiptip(){
153 | opts.exit.call(this);
154 | if (timeout){ clearTimeout(timeout); }
155 | tiptip_holder.fadeOut(opts.fadeOut);
156 | }
157 | }
158 | });
159 | }
160 | })(jQuery);
161 |
--------------------------------------------------------------------------------
/resources/public/cheatsheet_files/style.css:
--------------------------------------------------------------------------------
1 | body {margin: 0;padding:0;}
2 |
3 | .wiki ol.includeWikiList { list-style: none; margin-left: 6px; padding: 0; text-indent: -6px; }
4 | /* Wiki Rendered Styles */
5 | .wiki { line-height: 150%; font-family: arial, helvetica, sans-serif; font-size: small; }
6 | .wiki h1 { font-weight: bold; padding: 5px 0 0 0; margin: 0; font-size: 1.4em; }
7 | .wiki h2 { font-weight: bold; padding: 5px 0 0 0; margin: 0; font-size: 1.3em; }
8 | .wiki h3 { font-weight: bold; padding: 5px 0 0 0; margin: 0; font-size: 1.1em; }
9 | .wiki h4 { font-weight: normal; padding: 5px 0 0 0; margin: 0; font-size: 1.066em; }
10 | .wiki h5 { font-weight: normal; padding: 5px 0 0 0; margin: 0; font-size: 1.033em; }
11 | .wiki h6 { font-weight: normal; padding: 5px 0 0 0; margin: 0; font-size: 1.0em; }
12 | .wiki table { border-collapse: collapse; margin: 10px 0; font-size: 1em; }
13 | .wiki p { margin: 0; padding: 0; padding: 5px 0; }
14 | .wiki td { border: 1px solid #DDD; }
15 | .wiki #toc { border: 1px solid #AAA; background: #fff; padding: 5px; margin: 0 0 10px 10px; width: 25%; float: right; clear: right;}
16 | .wiki #toc h1 { font-weight: normal; font-size: 1.2em; padding: 0; }
17 | .wiki #toc a:hover { color: #00D; background: #FFD; }
18 | .wiki pre { background-color: #EEE; border: 1px solid #CCC; padding: 10px; font-size: small;}
19 | .wiki .nob td { border: 0; }
20 | .wiki hr { height: 1px; color: #AAA; background-color: #AAA; border: 0; margin: 0 10px; }
21 | .wiki ul { padding: .5em 0 0 3em; margin: 0; }
22 | .wiki ol { padding: .5em 0 0 3em; margin: 0; }
23 | .wiki ul.quotelist { list-style: none; }
24 | .wiki a.ext { background: url(/i/a.gif) center right no-repeat; padding-right: 10px; }
25 | .wiki a.wiki_link_new { color: #600; }
26 | .wiki a.wiki_link_ext { background: url(/i/a.gif) center right no-repeat; padding-right: 10px; }
27 | .wiki tt { font-size: small; }
28 | .wiki img { border: 0; padding: 4px; }
29 |
30 | .wiki .includeBodyActive { border-right: 1px #888888 solid; border-bottom: 1px #888888 solid; border-left: 1px #CCCCCC solid; border-top: 1px #CCCCCC solid; background: #fffacd; clear: left; }
31 | .wiki .includeBody { background: inherit; border: 1px transparent solid; clear: left; }
32 | .wiki .includeHeader { padding: 2px; padding-top: 2px; }
33 | .wiki .includeTitle { font-size: 120%; font-weight: bold; }
34 | .wiki .includeEditButton { float: right; padding-left: 10px; padding-right: 10px;}
35 | .wiki .includeEditButtonActive { float: right; padding-left: 10px; padding-right: 10px; }
36 | .wiki .includeEditButton a { color: #888888; font-size: 80%; text-decoration: none; }
37 | .wiki .includeEditButtonActive a { color: black; font-size: 80%; text-decoration: none; }
38 |
39 | .wiki .captionBox { border: thin gray solid; padding: 5px; margin: 2px; }
40 | .wiki .imageCaption { text-align: center; }
41 |
42 | .wiki .RssAuthor { color: #666; font-size: 80%; }
43 | .wiki .RssDate { color: #666; font-size: 80%; }
44 |
45 | .wiki .userLink { font-weight: bold; }
46 | .wiki .userLinkGuest { font-weight: bold; }
47 |
48 | /* Comments Include styles */
49 | .wiki table.includeComments { border-collapse: collapse; padding: 0; margin: 0; width: 100%; }
50 | .wiki table.includeComments td, .wiki table.includeComments th { border: 1px solid #DDD; padding: 4px; margin: 0; }
51 | .wiki table.includeComments th { color: #333; padding: 4px; margin: 0; text-align: center; white-space: nowrap; }
52 | .wiki table.includeComments thead th { background:url(/i/gradient_grey.gif) top left repeat-x #FFF; }
53 | .wiki table.includeComments tfoot th { background-color: #EEE; }
54 |
55 | /* History Include styles */
56 | .wiki table.includeHistory { border-collapse: collapse; padding: 0; margin: 0; width: 100%; }
57 | .wiki table.includeHistory td, .wiki table.includeHistory th { border: 1px solid #DDD; padding: 4px; margin: 0; }
58 | .wiki table.includeHistory th { color: #333; padding: 4px; margin: 0; text-align: center; white-space: nowrap; }
59 | .wiki table.includeHistory thead th { background:url(/i/gradient_grey.gif) top left repeat-x #FFF; }
60 | .wiki table.includeHistory tfoot th { background-color: #EEE; }
61 |
62 | /* Backlinks Include styles */
63 | .wiki table.includeBacklinks { border-collapse: collapse; padding: 0; margin: 0; width: 100%; }
64 | .wiki table.includeBacklinks td, .wiki table.includeBacklinks th { border: 1px solid #DDD; padding: 4px; margin: 0; }
65 | .wiki table.includeBacklinks th { color: #333; padding: 4px; margin: 0; text-align: center; white-space: nowrap; }
66 | .wiki table.includeBacklinks thead th { background:url(/i/gradient_grey.gif) top left repeat-x #FFF; }
67 | .wiki table.includeBacklinks tfoot th { background-color: #EEE; }
68 |
69 | /* Editors Include Styles */
70 | .wiki ul.includeEditorsLarge { list-style: none; display: inline; padding: 0; }
71 | .wiki ul.includeEditorsSmall { list-style: none; display: inline; padding: 0; }
72 | .wiki ul.includeEditorsLarge li { display: inline; padding: 2px; }
73 | .wiki ul.includeEditorsSmall li { display: block; padding: 2px; }
74 |
75 | /* Page List Include Styles */
76 | .wiki ol.includePageList { list-style: none; padding: 0; }
77 |
78 | /* Tag Cloud Include Styles */
79 | .wiki ol.includeTagCloud { list-style: none; display: inline; padding: 0; }
80 | .wiki ol.includeTagCloud li { display: inline; padding: 2px;}
81 |
82 | /* Adjust custom nav list padding */
83 | .WikiCustomNav ol, .WikiCustomNav ul { padding-left: 0; }
84 |
85 |
86 | /* Search styles */
87 |
88 | .search {
89 | padding: 0.3em 0.3em 0;
90 | }
91 |
92 | #search {
93 | margin: 0;
94 | display: block;
95 | border-radius: 5px;
96 | border: 1px solid #ddd;
97 | background: #ebebeb;
98 | padding: 5px;
99 | font-size: 1.5em;
100 | font-weight: bold;
101 | outline: none;
102 | box-sizing: border-box;
103 | width: 100%;
104 | }
105 |
106 | #search:focus {
107 | border: 1px solid #999;
108 | }
109 |
110 | #search.highlight {
111 | background: red;
112 | }
113 |
114 | .highlight {
115 | background: yellow;
116 | }
117 |
118 | /* Responsive styles */
119 |
120 | .page {
121 | float: left;
122 | }
123 |
124 | .column {
125 | min-width: 25em;
126 | }
127 |
128 | @media only screen and (min-width: 801px) {
129 | .page {
130 | max-width: 73em;
131 | }
132 |
133 | .search {
134 | max-width: 29em;
135 | }
136 | }
137 |
138 |
139 | @media only screen and (max-width: 800px) {
140 | .wiki {
141 | /* font-size: 10px; */
142 | }
143 | }
144 |
145 | @media only screen and (max-width: 620px) {
146 | .page .column {
147 | width: 100%;
148 | }
149 | }
150 |
151 | .wiki { font-size: 16px; }
152 |
--------------------------------------------------------------------------------
/resources/public/cheatsheet_files/tipTip.css:
--------------------------------------------------------------------------------
1 | /* TipTip CSS - Version 1.2 */
2 |
3 | #tiptip_holder {
4 | display: none;
5 | position: absolute;
6 | top: 0;
7 | left: 0;
8 | z-index: 99999;
9 | }
10 |
11 | #tiptip_holder.tip_top {
12 | padding-bottom: 15px;
13 | }
14 |
15 | #tiptip_holder.tip_bottom {
16 | padding-top: 15px;
17 | }
18 |
19 | #tiptip_holder.tip_right {
20 | padding-left: 15px;
21 | }
22 |
23 | #tiptip_holder.tip_left {
24 | padding-right: 15px;
25 | }
26 |
27 | #tiptip_content {
28 | font-size: 12px;
29 | color: #fff;
30 | text-shadow: 0 0 2px #000;
31 | padding: 4px 8px;
32 | border: 1px solid rgba(255,255,255,0.25);
33 | background-color: rgb(35,35,35);
34 | border-radius: 3px;
35 | -webkit-border-radius: 3px;
36 | -moz-border-radius: 3px;
37 | box-shadow: 0 0 3px #555;
38 | -webkit-box-shadow: 0 0 3px #555;
39 | -moz-box-shadow: 0 0 3px #555;
40 | width: 580px;
41 | }
42 |
43 | #tiptip_arrow, #tiptip_arrow_inner {
44 | position: absolute;
45 | border-color: transparent;
46 | border-style: solid;
47 | border-width: 6px;
48 | height: 0;
49 | width: 0;
50 | }
51 |
52 | #tiptip_holder.tip_top #tiptip_arrow {
53 | border-top-color: #fff;
54 | }
55 |
56 | #tiptip_holder.tip_bottom #tiptip_arrow {
57 | border-bottom-color: #fff;
58 | }
59 |
60 | #tiptip_holder.tip_right #tiptip_arrow {
61 | border-right-color: #fff;
62 | }
63 |
64 | #tiptip_holder.tip_left #tiptip_arrow {
65 | border-left-color: #fff;
66 | }
67 |
68 | #tiptip_holder.tip_top #tiptip_arrow_inner {
69 | margin-top: -7px;
70 | margin-left: -6px;
71 | border-top-color: rgb(25,25,25);
72 | }
73 |
74 | #tiptip_holder.tip_bottom #tiptip_arrow_inner {
75 | margin-top: -5px;
76 | margin-left: -6px;
77 | border-bottom-color: rgb(25,25,25);
78 | }
79 |
80 | #tiptip_holder.tip_right #tiptip_arrow_inner {
81 | margin-top: -6px;
82 | margin-left: -5px;
83 | border-right-color: rgb(25,25,25);
84 | }
85 |
86 | #tiptip_holder.tip_left #tiptip_arrow_inner {
87 | margin-top: -6px;
88 | margin-left: -7px;
89 | border-left-color: rgb(25,25,25);
90 | }
91 |
92 | /* Webkit Hacks */
93 | @media screen and (-webkit-min-device-pixel-ratio:0) {
94 | #tiptip_content {
95 | padding: 4px 8px 5px 8px;
96 | background-color: rgba(45,45,45,1.0);
97 | }
98 | #tiptip_holder.tip_bottom #tiptip_arrow_inner {
99 | border-bottom-color: rgba(45,45,45,1.0);
100 | }
101 | #tiptip_holder.tip_top #tiptip_arrow_inner {
102 | border-top-color: rgba(20,20,20,1.0);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/resources/public/fonts/FiraCode-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/public/fonts/FiraCode-Bold.otf
--------------------------------------------------------------------------------
/resources/public/fonts/FiraCode-Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/public/fonts/FiraCode-Light.otf
--------------------------------------------------------------------------------
/resources/public/fonts/FiraCode-Medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/public/fonts/FiraCode-Medium.otf
--------------------------------------------------------------------------------
/resources/public/fonts/FiraCode-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/public/fonts/FiraCode-Regular.otf
--------------------------------------------------------------------------------
/resources/public/fonts/FiraCode-Retina.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/resources/public/fonts/FiraCode-Retina.otf
--------------------------------------------------------------------------------
/resources/public/modal.css:
--------------------------------------------------------------------------------
1 | /* The Modal (background) */
2 | .modal {
3 | display: none; /* Hidden by default */
4 | position: fixed; /* Stay in place */
5 | z-index: 1000; /* Sit on top */
6 | left: 0;
7 | top: 0;
8 | width: 100%; /* Full width */
9 | height: 100%; /* Full height */
10 | overflow: auto; /* Enable scroll if needed */
11 | background-color: rgb(0,0,0); /* Fallback color */
12 | background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
13 | }
14 |
15 | /* Modal Content/Box */
16 | .modal-content {
17 | background-color: #fefefe;
18 | margin: 15% auto; /* 15% from the top and centered */
19 | padding: 20px;
20 | border: 1px solid #888;
21 | width: 80%; /* Could be more or less, depending on screen size */
22 | }
23 |
24 | /* The Close Button */
25 | .close {
26 | color: #aaa;
27 | float: right;
28 | font-size: 28px;
29 | font-weight: bold;
30 | }
31 |
32 | .close:hover,
33 | .close:focus {
34 | color: black;
35 | text-decoration: none;
36 | cursor: pointer;
37 | }
--------------------------------------------------------------------------------
/resources/public/paren-soup-dark.css:
--------------------------------------------------------------------------------
1 | .paren-soup {
2 | font-size: 16px;
3 | font-family: monospace;
4 | color: lightgray;
5 | background-color: #333333;
6 | outline: 1px solid;
7 | }
8 |
9 | .paren-soup .error-text {
10 | position: fixed;
11 | background-color: #293134;
12 | padding-left: 10px;
13 | }
14 |
15 | .paren-soup .instarepl {
16 | float: left;
17 | position: relative;
18 | display: list-item;
19 | padding-right: 5px;
20 | min-width: 200px;
21 | max-width: 200px;
22 | }
23 |
24 | .paren-soup .instarepl .result {
25 | position: absolute;
26 | overflow: hidden;
27 | background-color: darkgreen;
28 | outline: 1px solid;
29 | max-width: inherit;
30 | word-wrap: break-word;
31 | right: 0px;
32 | /*opacity: 0.7;*/
33 | white-space: break-spaces;
34 | }
35 |
36 | .paren-soup .instarepl .result:hover {
37 | cursor: pointer;
38 | height: auto !important;
39 | z-index: 1;
40 | /*opacity: 1;*/
41 | }
42 |
43 | .paren-soup .instarepl .error {
44 | background-color: darkred;
45 | }
46 |
47 | .paren-soup .numbers {
48 | float: left;
49 | padding: 0px 5px;
50 | text-align: right;
51 | /*opacity: 0.7;*/
52 | }
53 |
54 | .paren-soup .content {
55 | margin: 0px;
56 | padding: 0px 5px;
57 | outline: 0px solid transparent;
58 | white-space: pre;
59 | word-wrap: normal;
60 | overflow: scroll;
61 | border-left: 1px solid #ddd;
62 | }
63 |
64 | .paren-soup .content .symbol {
65 | color: white;
66 | }
67 |
68 | .paren-soup .content .number {
69 | color: gold;
70 | }
71 |
72 | .paren-soup .content .string {
73 | color: darksalmon;
74 | }
75 |
76 | .paren-soup .content .keyword {
77 | color: dodgerblue;
78 | }
79 |
80 | .paren-soup .content .nil {
81 | color: lightblue;
82 | }
83 |
84 | .paren-soup .content .boolean {
85 | color: lightblue;
86 | }
87 |
88 | .paren-soup .content .error {
89 | display: none;
90 | width: 0.9em;
91 | height: 0.9em;
92 | background-color: red;
93 | border-radius: 0.3em;
94 | }
95 |
96 | .paren-soup .content .rainbow-0 {
97 | color: aqua;
98 | }
99 |
100 | .paren-soup .content .rainbow-1 {
101 | color: orange;
102 | }
103 |
104 | .paren-soup .content .rainbow-2 {
105 | color: cornflowerblue;
106 | }
107 |
108 | .paren-soup .content .rainbow-3 {
109 | color: fuchsia;
110 | }
111 |
112 | .paren-soup .content .rainbow-4 {
113 | color: lime;
114 | }
115 |
--------------------------------------------------------------------------------
/resources/public/paren-soup-light.css:
--------------------------------------------------------------------------------
1 | .paren-soup {
2 | font-size: 16px;
3 | font-family: monospace;
4 | color: gray;
5 | background-color: #f7f7f7;
6 | outline: 1px solid;
7 | caret-color: black;
8 | }
9 |
10 | .paren-soup .error-text {
11 | position: fixed;
12 | background-color: white;
13 | padding-left: 10px;
14 | }
15 |
16 | .paren-soup .instarepl {
17 | float: left;
18 | position: relative;
19 | display: list-item;
20 | padding-right: 5px;
21 | min-width: 200px;
22 | max-width: 200px;
23 | }
24 |
25 | .paren-soup .instarepl .result {
26 | position: absolute;
27 | overflow: hidden;
28 | background-color: lightgreen;
29 | outline: 1px solid;
30 | max-width: inherit;
31 | word-wrap: break-word;
32 | right: 0px;
33 | /*opacity: 0.7;*/
34 | white-space: break-spaces;
35 | }
36 |
37 | .paren-soup .instarepl .result:hover {
38 | cursor: pointer;
39 | height: auto !important;
40 | z-index: 1;
41 | /*opacity: 1;*/
42 | }
43 |
44 | .paren-soup .instarepl .error {
45 | background-color: pink;
46 | }
47 |
48 | .paren-soup .numbers {
49 | float: left;
50 | padding: 0px 5px;
51 | text-align: right;
52 | /*opacity: 0.7;*/
53 | }
54 |
55 | .paren-soup .content {
56 | margin: 0px;
57 | padding: 0px 5px;
58 | outline: 0px solid transparent;
59 | white-space: pre;
60 | word-wrap: normal;
61 | overflow: scroll;
62 | border-left: 1px solid #ddd;
63 | background-color: white;
64 | }
65 |
66 | .paren-soup .content .symbol {
67 | color: black;
68 | }
69 |
70 | .paren-soup .content .number {
71 | color: gold;
72 | }
73 |
74 | .paren-soup .content .string {
75 | color: red;
76 | }
77 |
78 | .paren-soup .content .keyword {
79 | color: blue;
80 | }
81 |
82 | .paren-soup .content .nil {
83 | color: lightblue;
84 | }
85 |
86 | .paren-soup .content .boolean {
87 | color: lightblue;
88 | }
89 |
90 | .paren-soup .content .error {
91 | display: none;
92 | width: 0.9em;
93 | height: 0.9em;
94 | background-color: red;
95 | border-radius: 0.3em;
96 | }
97 |
98 | .paren-soup .content .rainbow-0 {
99 | color: aqua;
100 | }
101 |
102 | .paren-soup .content .rainbow-1 {
103 | color: orange;
104 | }
105 |
106 | .paren-soup .content .rainbow-2 {
107 | color: cornflowerblue;
108 | }
109 |
110 | .paren-soup .content .rainbow-3 {
111 | color: fuchsia;
112 | }
113 |
114 | .paren-soup .content .rainbow-4 {
115 | color: lime;
116 | }
117 |
--------------------------------------------------------------------------------
/resources/public/paren-soup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
30 |
31 |
32 |
37 |
38 |
39 |
×
40 |
41 | This file was modified externally.
42 | You can either reload
43 | the file or close this dialog and continue editing.
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oakes/Nightcode/2e112c59cddc5fdec96059a08912c73b880f9ae8/screenshot.png
--------------------------------------------------------------------------------
/src/clj/nightcode/builders.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.builders
2 | (:require [nightcode.shortcuts :as shortcuts]
3 | [nightcode.lein :as l]
4 | [nightcode.spec :as spec]
5 | [nightcode.utils :as u]
6 | [nightcode.process :as proc]
7 | [clojure.spec.alpha :as s :refer [fdef]]
8 | [clojure.set :as set]
9 | [clojure.string :as str])
10 | (:import [clojure.lang LineNumberingPushbackReader]
11 | [javafx.scene.web WebEngine]
12 | [javafx.scene.control Button]
13 | [java.io PipedWriter PipedReader PrintWriter]
14 | [javafx.application Platform]
15 | [javafx.beans.value ChangeListener]
16 | [javafx.event EventHandler]
17 | [nightcode.utils Bridge]))
18 |
19 | (fdef pipe-into-console!
20 | :args (s/cat :engine any? :in-pipe #(instance? java.io.Reader %)))
21 |
22 | (defn pipe-into-console! [^WebEngine engine in-pipe]
23 | (let [ca (char-array 256)]
24 | (.start
25 | (Thread.
26 | (fn []
27 | (loop []
28 | (when-let [read (try (.read in-pipe ca)
29 | (catch Exception _))]
30 | (when (pos? read)
31 | (let [s (u/remove-returns (String. ca 0 read))]
32 | (Platform/runLater
33 | (fn []
34 | (-> engine
35 | (.executeScript "window")
36 | (.call "append" (into-array [s])))))
37 | (Thread/sleep 100) ; prevent application thread from being flooded
38 | (recur))))))))))
39 |
40 | (fdef create-pipes
41 | :args (s/cat)
42 | :ret map?)
43 |
44 | (defn create-pipes []
45 | (let [out-pipe (PipedWriter.)
46 | in (LineNumberingPushbackReader. (PipedReader. out-pipe))
47 | pout (PipedWriter.)
48 | out (PrintWriter. pout)
49 | in-pipe (PipedReader. pout)]
50 | {:in in :out out :in-pipe in-pipe :out-pipe out-pipe}))
51 |
52 | (fdef start-builder-thread!
53 | :args (s/cat :webview spec/node? :pipes map? :finish-str (s/nilable string?) :work-fn fn?))
54 |
55 | (defn start-builder-thread! [webview pipes finish-str work-fn]
56 | (let [engine (.getEngine webview)
57 | {:keys [in-pipe in out]} pipes]
58 | (pipe-into-console! engine in-pipe)
59 | (.start
60 | (Thread.
61 | (fn []
62 | (binding [*out* out
63 | *err* out
64 | *in* in]
65 | (try
66 | (work-fn)
67 | (catch Exception e (some-> (.getMessage e) println))
68 | (finally (some-> finish-str println)))))))))
69 |
70 | (fdef start-builder-process!
71 | :args (s/alt
72 | :args (s/cat :webview spec/node? :pipes map? :*process spec/atom? :start-str (s/nilable string?) :project-path string? :args (s/coll-of string?))
73 | :start-fn (s/cat :webview spec/node? :pipes map? :*process spec/atom? :start-str (s/nilable string?) :start-fn fn?)))
74 |
75 | (defn start-builder-process!
76 | ([webview pipes *process start-str project-path args]
77 | (start-builder-process! webview pipes *process start-str
78 | #(proc/start-java-process! *process project-path args)))
79 | ([webview pipes *process start-str start-fn]
80 | (proc/stop-process! *process)
81 | (start-builder-thread! webview pipes "\n=== Finished ==="
82 | (fn []
83 | (some-> start-str println)
84 | (start-fn)))))
85 |
86 | (fdef stop-builder-process!
87 | :args (s/cat :runtime-state map? :project-path string?))
88 |
89 | (defn stop-builder-process! [runtime-state project-path]
90 | (when-let [*process (get-in runtime-state [:processes project-path])]
91 | (proc/stop-process! *process)))
92 |
93 | (fdef init-console!
94 | :args (s/cat :project-path string? :*runtime-state spec/atom? :webview spec/node? :pipes map? :web-port number? :callback fn?))
95 |
96 | (defn init-console! [project-path *runtime-state webview pipes web-port cb]
97 | (doto webview
98 | (.setVisible true)
99 | (.setContextMenuEnabled false))
100 | (let [engine (.getEngine webview)]
101 | (.setOnStatusChanged engine
102 | (reify EventHandler
103 | (handle [this event]
104 | (let [bridge (reify Bridge
105 | (onload [this]
106 | (try
107 | (cb)
108 | (catch Exception e (.printStackTrace e))))
109 | (onautosave [this])
110 | (onchange [this])
111 | (onenter [this text]
112 | (doto (:out-pipe pipes)
113 | (.write text)
114 | (.flush)))
115 | (oneval [this code]))]
116 | ; prevent bridge from being GC'ed
117 | (swap! *runtime-state update :bridges assoc project-path bridge)
118 | (-> engine
119 | (.executeScript "window")
120 | (.setMember "java" bridge))))))
121 | (.load engine (str "http://localhost:" web-port "/paren-soup.html"))))
122 |
123 | (def index->system {0 :boot 1 :lein})
124 | (def system->index (set/map-invert index->system))
125 |
126 | (fdef get-tab
127 | :args (s/cat :pane spec/pane? :system keyword?)
128 | :ret #(instance? javafx.scene.control.Tab %))
129 |
130 | (defn get-tab [pane system]
131 | (-> (.lookup pane "#build_tabs")
132 | .getTabs
133 | (.get (system->index system))))
134 |
135 | (fdef get-selected-build-system
136 | :args (s/cat :pane spec/pane?)
137 | :ret (s/nilable keyword?))
138 |
139 | (defn get-selected-build-system [pane]
140 | (-> (.lookup pane "#build_tabs") .getSelectionModel .getSelectedIndex index->system))
141 |
142 | (fdef select-build-system!
143 | :args (s/cat :pane spec/pane? :system keyword? :ids (s/coll-of keyword?)))
144 |
145 | (defn select-build-system! [pane system ids]
146 | (-> (.lookup pane "#build_tabs") .getSelectionModel (.select (system->index system)))
147 | (-> (get-tab pane system) .getContent (shortcuts/add-tooltips! ids)))
148 |
149 | (fdef refresh-builder!
150 | :args (s/cat :webview spec/node? :repl? boolean? :pref-state map?))
151 |
152 | (defn refresh-builder! [webview repl? pref-state]
153 | (doto (.getEngine webview)
154 | (.executeScript (if repl? "initConsole(true)" "initConsole(false)"))
155 | (.executeScript (case (:theme pref-state)
156 | :dark "changeTheme(true)"
157 | :light "changeTheme(false)"))
158 | (.executeScript (format "setTextSize(%s)" (:text-size pref-state)))))
159 |
160 | (def ^:const ids [:.run :.build :.run-with-repl :.reload-file :.reload-selection :.clean :.test :.stop])
161 | (def ^:const disable-when-running [:.run :.build :.run-with-repl :.clean :.test :.custom])
162 | (def ^:const disable-when-not-running [:.reload-file :.reload-selection :.stop])
163 | (def ^:const custom-task-ids [:.run :.build])
164 |
165 | (fdef update-when-process-changes!
166 | :args (s/cat :pane spec/pane? :process-running? boolean?))
167 |
168 | (defn update-when-process-changes! [pane process-running?]
169 | (doseq [id disable-when-running]
170 | (doseq [node (.lookupAll pane (name id))]
171 | (.setDisable node process-running?)))
172 | (doseq [id disable-when-not-running]
173 | (doseq [node (.lookupAll pane (name id))]
174 | (.setDisable node (not process-running?)))))
175 |
176 | (fdef get-builder-webview
177 | :args (s/cat :pref-state map? :runtime-state map?)
178 | :ret spec/node?)
179 |
180 | (defn get-builder-webview [pref-state runtime-state]
181 | (when-let [project-path (u/get-project-path pref-state)]
182 | (when-let [pane (get-in runtime-state [:projects project-path :pane])]
183 | (when-let [system (get-selected-build-system pane)]
184 | (let [tab-content (.getContent (get-tab pane system))]
185 | (.lookup tab-content "#build_webview"))))))
186 |
187 | (fdef start-builder!
188 | :args (s/cat :pref-state map? :*runtime-state spec/atom? :start-str string? :cmd string?))
189 |
190 | (defn start-builder! [pref-state *runtime-state start-str cmd]
191 | (when-let [project-path (u/get-project-path pref-state)]
192 | (when-let [pane (get-in @*runtime-state [:projects project-path :pane])]
193 | (when-let [system (get-selected-build-system pane)]
194 | (let [tab-content (.getContent (get-tab pane system))
195 | webview (.lookup tab-content "#build_webview")
196 | pipes (create-pipes)
197 | *process (or (get-in @*runtime-state [:processes project-path])
198 | (doto (atom nil)
199 | (add-watch :process-changed
200 | (fn [_ _ _ new-process]
201 | (update-when-process-changes! pane (some? new-process))))))]
202 | (init-console! project-path *runtime-state webview pipes (:web-port @*runtime-state)
203 | (fn []
204 | (refresh-builder! webview (= cmd "repl") pref-state)
205 | (start-builder-process! webview pipes *process start-str
206 | (case system
207 | :lein #(proc/start-java-process! *process project-path [l/class-name cmd])
208 | :boot #(proc/start-process! *process project-path ["java" "-jar" (u/get-boot-path!) "--no-colors"
209 | (if (= cmd "repl") "bare-repl" cmd)])))))
210 | (swap! *runtime-state assoc-in [:processes project-path] *process))))))
211 |
212 | (fdef stop-builder!
213 | :args (s/cat :pref-state map? :runtime-state map?))
214 |
215 | (defn stop-builder! [pref-state runtime-state]
216 | (when-let [project-path (u/get-project-path pref-state)]
217 | (stop-builder-process! runtime-state project-path)))
218 |
219 | (fdef show-boot-buttons!
220 | :args (s/cat :pane spec/pane? :path string? :pref-state map? :*runtime-state spec/atom?))
221 |
222 | (defn show-boot-buttons! [pane path pref-state *runtime-state]
223 | (when-let [task-buttons (some-> (get-tab pane :boot) .getContent (.lookup "#tasks"))]
224 | (when-let [perm-tasks (.lookup task-buttons "#permanent_tasks")]
225 | (doto task-buttons
226 | shortcuts/hide-tooltips!
227 | (shortcuts/remove-tooltips! custom-task-ids))
228 | (-> task-buttons .getChildren .clear)
229 | (doseq [task-name (u/get-boot-tasks path)]
230 | (let [btn (Button.)]
231 | (doto (.getStyleClass btn)
232 | (.add task-name)
233 | (.add "custom"))
234 | (doto btn
235 | (.setText (->> (str/split task-name #"-")
236 | (map str/capitalize)
237 | (str/join " ")))
238 | (.setOnAction
239 | (reify EventHandler
240 | (handle [this event]
241 | (start-builder! pref-state *runtime-state (str "Starting " task-name " task...") task-name)))))
242 | (-> task-buttons .getChildren (.add btn))))
243 | (-> task-buttons .getChildren (.add perm-tasks))
244 | ; for certain custom tasks, add tooltips
245 | (doto task-buttons
246 | (shortcuts/add-tooltips! custom-task-ids)
247 | shortcuts/hide-tooltips!))))
248 |
249 | (fdef init-builder!
250 | :args (s/cat :pane spec/pane? :path string? :pref-state map? :*runtime-state spec/atom?))
251 |
252 | (defn init-builder! [pane path pref-state *runtime-state]
253 | (let [systems (u/build-systems path)]
254 | ; add/remove tooltips
255 | (.addListener (-> (.lookup pane "#build_tabs") .getSelectionModel .selectedItemProperty)
256 | (reify ChangeListener
257 | (changed [this observable old-value new-value]
258 | (some-> old-value .getContent shortcuts/hide-tooltips!)
259 | (some-> old-value .getContent (shortcuts/remove-tooltips! ids))
260 | (some-> new-value .getContent (shortcuts/add-tooltips! ids)))))
261 | ; select/disable build tabs
262 | (cond
263 | (:boot systems) (do
264 | (select-build-system! pane :boot ids)
265 | (show-boot-buttons! pane path pref-state *runtime-state))
266 | (:lein systems) (select-build-system! pane :lein ids))
267 | (.setDisable (get-tab pane :boot) (not (:boot systems)))
268 | (.setDisable (get-tab pane :lein) (not (:lein systems)))
269 | ; init the tabs
270 | (doseq [system systems]
271 | (.setDisable (get-tab pane system) false))))
272 |
273 |
--------------------------------------------------------------------------------
/src/clj/nightcode/controller.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.controller
2 | (:require [clojure.java.io :as io]
3 | [clojure.string :as str]
4 | [nightcode.builders :as b]
5 | [nightcode.editors :as e]
6 | [nightcode.git :as git]
7 | [nightcode.lein :as lein]
8 | [nightcode.process :as proc]
9 | [nightcode.projects :as p]
10 | [nightcode.state :refer [*pref-state *runtime-state]]
11 | [nightcode.utils :as u]
12 | [eval-soup.core :as es])
13 | (:import [javafx.event ActionEvent]
14 | [javafx.scene.control Alert Alert$AlertType ButtonType TextInputDialog]
15 | [javafx.stage DirectoryChooser FileChooser StageStyle Window Modality]
16 | [javafx.application Platform]
17 | [javafx.scene Scene]
18 | [javafx.scene.input KeyEvent KeyCode]
19 | [java.awt Desktop])
20 | (:gen-class
21 | :methods [[onNewConsoleProject [javafx.event.ActionEvent] void]
22 | [onNewGraphicsProject [javafx.event.ActionEvent] void]
23 | [onNewWebProject [javafx.event.ActionEvent] void]
24 | [onNewGameProject [javafx.event.ActionEvent] void]
25 | [onNewMusicProject [javafx.event.ActionEvent] void]
26 | [onCloneFromGit [javafx.event.ActionEvent] void]
27 | [onImport [javafx.event.ActionEvent] void]
28 | [onRename [javafx.event.ActionEvent] void]
29 | [onRemove [javafx.event.ActionEvent] void]
30 | [onUp [javafx.event.ActionEvent] void]
31 | [onSave [javafx.event.ActionEvent] void]
32 | [onUndo [javafx.event.ActionEvent] void]
33 | [onRedo [javafx.event.ActionEvent] void]
34 | [onInstaRepl [javafx.event.ActionEvent] void]
35 | [onFind [javafx.scene.input.KeyEvent] void]
36 | [onClose [javafx.event.ActionEvent] void]
37 | [onRun [javafx.event.ActionEvent] void]
38 | [onRunWithRepl [javafx.event.ActionEvent] void]
39 | [onReloadFile [javafx.event.ActionEvent] void]
40 | [onReloadSelection [javafx.event.ActionEvent] void]
41 | [onBuild [javafx.event.ActionEvent] void]
42 | [onClean [javafx.event.ActionEvent] void]
43 | [onTest [javafx.event.ActionEvent] void]
44 | [onStop [javafx.event.ActionEvent] void]
45 | [onDarkTheme [javafx.event.ActionEvent] void]
46 | [onLightTheme [javafx.event.ActionEvent] void]
47 | [onFontDec [javafx.event.ActionEvent] void]
48 | [onFontInc [javafx.event.ActionEvent] void]
49 | [onAutoSave [javafx.event.ActionEvent] void]
50 | [onNewFile [javafx.event.ActionEvent] void]
51 | [onOpenInFileBrowser [javafx.event.ActionEvent] void]]))
52 |
53 | ; new project
54 |
55 | (defn show-start-menu! [^Scene scene]
56 | (some-> (.lookup scene "#start") .show))
57 |
58 | (defn new-project! [^Scene scene project-type]
59 | (let [chooser (doto (FileChooser.)
60 | (.setTitle "New Project"))
61 | project-tree (.lookup scene "#project_tree")]
62 | (when-let [file (.showSaveDialog chooser (.getWindow scene))]
63 | (let [dir (-> file .getParentFile .getCanonicalPath)
64 | project-name (-> file .getName u/sanitize-name)
65 | file (io/file dir project-name)]
66 | (try
67 | (cond-> (fn []
68 | (lein/new! dir project-type project-name)
69 | (when (.exists file)
70 | (swap! *pref-state update :project-set conj (.getCanonicalPath file))
71 | (p/update-project-tree! *pref-state project-tree (.getCanonicalPath file))))
72 | (not (:dev? @*runtime-state))
73 | es/wrap-security
74 | true
75 | (apply []))
76 | (catch Exception e
77 | (.printStackTrace e)))))))
78 |
79 | (defn -onNewConsoleProject [this ^ActionEvent event]
80 | (-> event .getSource .getParentPopup .getOwnerWindow .getScene (new-project! :console)))
81 |
82 | (defn -onNewGraphicsProject [this ^ActionEvent event]
83 | (-> event .getSource .getParentPopup .getOwnerWindow .getScene (new-project! :graphics)))
84 |
85 | (defn -onNewWebProject [this ^ActionEvent event]
86 | (-> event .getSource .getParentPopup .getOwnerWindow .getScene (new-project! :web)))
87 |
88 | (defn -onNewGameProject [this ^ActionEvent event]
89 | (-> event .getSource .getParentPopup .getOwnerWindow .getScene (new-project! :play-cljc)))
90 |
91 | (defn -onNewMusicProject [this ^ActionEvent event]
92 | (-> event .getSource .getParentPopup .getOwnerWindow .getScene (new-project! :edna)))
93 |
94 | ; clone from git
95 |
96 | (defn clone-from-git! [^Scene scene]
97 | (when-let [path (git/clone-with-dialog! scene)]
98 | (swap! *pref-state update :project-set conj path)
99 | (p/update-project-tree! *pref-state (.lookup scene "#project_tree") path)))
100 |
101 | (defn -onCloneFromGit [this ^ActionEvent event]
102 | (-> event .getSource .getParentPopup .getOwnerWindow .getScene clone-from-git!))
103 |
104 | ; import
105 |
106 | (defn import! [^Scene scene]
107 | (let [chooser (doto (DirectoryChooser.)
108 | (.setTitle "Import"))
109 | project-tree (.lookup scene "#project_tree")]
110 | (when-let [file (.showDialog chooser (.getWindow scene))]
111 | (let [path (.getCanonicalPath file)]
112 | (swap! *pref-state update :project-set conj path)
113 | (p/update-project-tree! *pref-state project-tree path)))))
114 |
115 | (defn -onImport [this ^ActionEvent event]
116 | (-> event .getSource .getScene import!))
117 |
118 | ; rename
119 |
120 | (defn rename! [^Scene scene]
121 | (let [dialog (doto (TextInputDialog.)
122 | (.setTitle "Rename")
123 | (.setHeaderText "Enter a path relative to the project root.")
124 | (.setGraphic nil)
125 | (.initOwner (.getWindow scene))
126 | (.initModality Modality/WINDOW_MODAL))
127 | project-root-path (u/get-project-root-path @*pref-state)
128 | selected-path (:selection @*pref-state)
129 | relative-path (u/get-relative-path project-root-path selected-path)]
130 | (-> dialog .getEditor (.setText relative-path))
131 | (when-let [new-relative-path (-> dialog .showAndWait (.orElse nil))]
132 | (when (not= relative-path new-relative-path)
133 | (let [new-file (io/file project-root-path new-relative-path)
134 | new-path (.getCanonicalPath new-file)
135 | project-tree (.lookup scene "#project_tree")]
136 | (.mkdirs (.getParentFile new-file))
137 | (.renameTo (io/file selected-path) new-file)
138 | (u/delete-parents-recursively! (:project-set @*pref-state) selected-path)
139 | (e/remove-editors! selected-path *runtime-state)
140 | (p/update-project-tree! *pref-state project-tree new-path))))))
141 |
142 | (defn -onRename [this ^ActionEvent event]
143 | (-> event .getSource .getScene rename!))
144 |
145 | ; remove
146 |
147 | (defn should-remove? [^Scene scene ^String path]
148 | (let [paths-to-delete (->> @*runtime-state :editor-panes keys (filter #(u/parent-path? path %)))
149 | get-pane #(get-in @*runtime-state [:editor-panes %])
150 | get-engine #(-> % get-pane (.lookup "#webview") .getEngine)
151 | unsaved? #(-> % get-engine (.executeScript "isClean()") not)
152 | unsaved-paths (filter unsaved? paths-to-delete)]
153 | (or (empty? unsaved-paths)
154 | (->> (map #(-> % io/file .getName) unsaved-paths)
155 | (str/join \newline)
156 | (str "The below files are not saved. Proceed?" \newline \newline)
157 | (u/show-warning! scene "Unsaved Files")))))
158 |
159 | (defn remove! [^Scene scene]
160 | (let [{:keys [project-set selection]} @*pref-state
161 | message (if (contains? project-set selection)
162 | "Remove this project? It WILL NOT be deleted from the disk."
163 | "Remove this file? It WILL be deleted from the disk.")
164 | dialog (doto (Alert. Alert$AlertType/CONFIRMATION)
165 | (.setTitle "Remove")
166 | (.setHeaderText message)
167 | (.setGraphic nil)
168 | (.initOwner (.getWindow scene))
169 | (.initModality Modality/WINDOW_MODAL))
170 | project-tree (.lookup scene "#project_tree")]
171 | (when (and (-> dialog .showAndWait (.orElse nil) (= ButtonType/OK))
172 | (should-remove? scene selection))
173 | (p/remove-from-project-tree! *pref-state selection)
174 | (e/remove-editors! selection *runtime-state)
175 | (p/remove-project! selection *runtime-state)
176 | (p/update-project-tree! *pref-state project-tree))))
177 |
178 | (defn -onRemove [this ^ActionEvent event]
179 | (-> event .getSource .getScene remove!))
180 |
181 | ; up
182 |
183 | (defn up! [^Scene scene]
184 | (when-let [path (:selection @*pref-state)]
185 | (let [project-tree (.lookup scene "#project_tree")]
186 | (->> path io/file .getParentFile .getCanonicalPath
187 | (p/update-project-tree! *pref-state project-tree)))))
188 |
189 | (defn -onUp [this ^ActionEvent event]
190 | (-> event .getSource .getScene up!))
191 |
192 | ; save
193 |
194 | (defn save! [^Scene scene]
195 | (when-let [path (:selection @*pref-state)]
196 | (when-let [pane (get-in @*runtime-state [:editor-panes path])]
197 | (when-let [engine (some-> pane (.lookup "#webview") .getEngine)]
198 | (e/save-file! path engine)))))
199 |
200 | (defn -onSave [this ^ActionEvent event]
201 | (-> event .getSource .getScene save!))
202 |
203 | ; undo
204 |
205 | (defn undo! [^Scene scene]
206 | (when-let [path (:selection @*pref-state)]
207 | (when-let [pane (get-in @*runtime-state [:editor-panes path])]
208 | (let [webview (.lookup pane "#webview")
209 | engine (.getEngine webview)]
210 | (.executeScript engine "undo()")
211 | (e/update-editor-buttons! pane engine)))))
212 |
213 | (defn -onUndo [this ^ActionEvent event]
214 | (-> event .getSource .getScene undo!))
215 |
216 | ; redo
217 |
218 | (defn redo! [^Scene scene]
219 | (when-let [path (:selection @*pref-state)]
220 | (when-let [pane (get-in @*runtime-state [:editor-panes path])]
221 | (let [webview (.lookup pane "#webview")
222 | engine (.getEngine webview)]
223 | (.executeScript engine "redo()")
224 | (e/update-editor-buttons! pane engine)))))
225 |
226 | (defn -onRedo [this ^ActionEvent event]
227 | (-> event .getSource .getScene redo!))
228 |
229 | ; instaREPL
230 |
231 | (defn toggle-instarepl! [^Scene scene & [from-button?]]
232 | (when-let [path (:selection @*pref-state)]
233 | (when-let [pane (get-in @*runtime-state [:editor-panes path])]
234 | (let [webview (.lookup pane "#webview")
235 | instarepl (.lookup pane "#instarepl")]
236 | (when-not from-button?
237 | (.setSelected instarepl (not (.isSelected instarepl))))
238 | (e/toggle-instarepl! (.getEngine webview) (.isSelected instarepl))))))
239 |
240 | (defn -onInstaRepl [this ^ActionEvent event]
241 | (-> event .getSource .getScene (toggle-instarepl! true)))
242 |
243 | ; find
244 |
245 | (defn focus-on-find! [^Scene scene]
246 | (when-let [path (:selection @*pref-state)]
247 | (when-let [pane (get-in @*runtime-state [:editor-panes path])]
248 | (when-let [find (.lookup pane "#find")]
249 | (doto find .requestFocus .selectAll)))))
250 |
251 | (defn find! [^Scene scene ^KeyEvent event]
252 | (when (= KeyCode/ENTER (.getCode event))
253 | (when-let [path (:selection @*pref-state)]
254 | (when-let [pane (get-in @*runtime-state [:editor-panes path])]
255 | (let [webview (.lookup pane "#webview")
256 | engine (.getEngine webview)
257 | find (.lookup pane "#find")
258 | find-text (.getText find)]
259 | (-> engine
260 | (.executeScript "window")
261 | (.call "find" (into-array Object [find-text true (.isShiftDown event)]))))))))
262 |
263 | (defn -onFind [this ^KeyEvent event]
264 | (-> event .getSource .getScene (find! event)))
265 |
266 | ; close
267 |
268 | (defn close! [^Scene scene]
269 | (when-let [path (:selection @*pref-state)]
270 | (when (should-remove? scene path)
271 | (let [file (io/file path)
272 | new-path (if (.isDirectory file)
273 | path
274 | (.getCanonicalPath (.getParentFile file)))
275 | project-tree (.lookup scene "#project_tree")]
276 | (e/remove-editors! path *runtime-state)
277 | (p/update-project-tree-selection! project-tree new-path))
278 | (p/remove-project! path *runtime-state))))
279 |
280 | (defn -onClose [this ^ActionEvent event]
281 | (-> event .getSource .getScene close!))
282 |
283 | ; run
284 |
285 | (defn run-normal! [^Scene scene]
286 | (b/start-builder! @*pref-state *runtime-state "Running..." "run"))
287 |
288 | (defn -onRun [this ^ActionEvent event]
289 | (-> event .getSource .getScene run-normal!))
290 |
291 | ; run with repl
292 |
293 | (defn run-with-repl! [^Scene scene]
294 | (b/start-builder! @*pref-state *runtime-state "Running with REPL..." "repl"))
295 |
296 | (defn -onRunWithRepl [this ^ActionEvent event]
297 | (-> event .getSource .getScene run-with-repl!))
298 |
299 | ; reload file
300 |
301 | (defn reload-file! [^Scene scene]
302 | (when-let [webview (.lookup scene "#webview")]
303 | (let [text (.executeScript (.getEngine webview) "getTextContent()")
304 | text (str "(do" \newline text \newline ")" \newline)
305 | bridge (e/get-bridge @*pref-state @*runtime-state)]
306 | (.onenter bridge text))))
307 |
308 | (defn -onReloadFile [this ^ActionEvent event]
309 | (-> event .getSource .getScene reload-file!))
310 |
311 | ; reload selection
312 |
313 | (defn reload-selection! [^Scene scene]
314 | (when-let [webview (.lookup scene "#webview")]
315 | (when-let [text (.executeScript (.getEngine webview) "getSelectedText()")]
316 | (let [text (str "(do" \newline text \newline ")" \newline)
317 | bridge (e/get-bridge @*pref-state @*runtime-state)]
318 | (.onenter bridge text)))))
319 |
320 | (defn -onReloadSelection [this ^ActionEvent event]
321 | (-> event .getSource .getScene reload-selection!))
322 |
323 | ; build
324 |
325 | (defn build! [^Scene scene]
326 | (b/start-builder! @*pref-state *runtime-state "Building..." "build"))
327 |
328 | (defn -onBuild [this ^ActionEvent event]
329 | (-> event .getSource .getScene build!))
330 |
331 | ; clean
332 |
333 | (defn clean! [^Scene scene]
334 | (b/start-builder! @*pref-state *runtime-state "Cleaning..." "clean"))
335 |
336 | (defn -onClean [this ^ActionEvent event]
337 | (-> event .getSource .getScene clean!))
338 |
339 | ; test
340 |
341 | (defn test! [^Scene scene]
342 | (b/start-builder! @*pref-state *runtime-state "Testing..." "test"))
343 |
344 | (defn -onTest [this ^ActionEvent event]
345 | (-> event .getSource .getScene test!))
346 |
347 | ; stop
348 |
349 | (defn stop! [^Scene scene]
350 | (b/stop-builder! @*pref-state @*runtime-state))
351 |
352 | (defn -onStop [this ^ActionEvent event]
353 | (-> event .getSource .getScene stop!))
354 |
355 | ; theme
356 |
357 | (defn dark-theme! [^Scene scene]
358 | (swap! *pref-state assoc :theme :dark)
359 | (-> scene .getStylesheets (.add "dark.css"))
360 | (u/update-webviews! @*pref-state @*runtime-state))
361 |
362 | (defn -onDarkTheme [this ^ActionEvent event]
363 | (-> @*runtime-state :stage .getScene dark-theme!))
364 |
365 | (defn light-theme! [^Scene scene]
366 | (swap! *pref-state assoc :theme :light)
367 | (-> scene .getStylesheets .clear)
368 | (u/update-webviews! @*pref-state @*runtime-state))
369 |
370 | (defn -onLightTheme [this ^ActionEvent event]
371 | (-> @*runtime-state :stage .getScene light-theme!))
372 |
373 | ; font
374 |
375 | (defn font! [^Scene scene]
376 | (-> scene .getRoot (.setStyle (str "-fx-font-size: " (u/normalize-text-size (:text-size @*pref-state)))))
377 | (u/update-webviews! @*pref-state @*runtime-state))
378 |
379 | (defn font-dec! [^Scene scene]
380 | (swap! *pref-state update :text-size #(-> % (- 2) u/normalize-text-size))
381 | (font! scene))
382 |
383 | (defn -onFontDec [this ^ActionEvent event]
384 | (-> @*runtime-state :stage .getScene font-dec!))
385 |
386 | (defn font-inc! [^Scene scene]
387 | (swap! *pref-state update :text-size #(-> % (+ 2) u/normalize-text-size))
388 | (font! scene))
389 |
390 | (defn -onFontInc [this ^ActionEvent event]
391 | (-> @*runtime-state :stage .getScene font-inc!))
392 |
393 | ; auto save
394 |
395 | (defn -onAutoSave [this ^ActionEvent event]
396 | (swap! *pref-state assoc :auto-save? (-> event .getTarget .isSelected)))
397 |
398 | ; new file
399 |
400 | (defn new-file! [^Scene scene]
401 | (let [dialog (doto (TextInputDialog.)
402 | (.setTitle "New File")
403 | (.setHeaderText "Enter a path relative to the selected directory.")
404 | (.setGraphic nil)
405 | (.initOwner (.getWindow scene))
406 | (.initModality Modality/WINDOW_MODAL))
407 | selected-path (:selection @*pref-state)]
408 | (-> dialog .getEditor (.setText "example.clj"))
409 | (when-let [new-relative-path (-> dialog .showAndWait (.orElse nil))]
410 | (let [new-file (io/file selected-path new-relative-path)
411 | new-path (.getCanonicalPath new-file)
412 | project-tree (.lookup scene "#project_tree")]
413 | (.mkdirs (.getParentFile new-file))
414 | (.createNewFile new-file)
415 | (p/update-project-tree! *pref-state project-tree new-path)))))
416 |
417 | (defn -onNewFile [this ^ActionEvent event]
418 | (-> event .getSource .getScene new-file!))
419 |
420 | ; open in file browser
421 |
422 | (defn open-in-file-browser! [^Scene scene]
423 | (when-let [path (:selection @*pref-state)]
424 | (javax.swing.SwingUtilities/invokeLater
425 | (fn []
426 | (when (Desktop/isDesktopSupported)
427 | (.open (Desktop/getDesktop) (io/file path)))))))
428 |
429 | (defn -onOpenInFileBrowser [this ^ActionEvent event]
430 | (-> event .getSource .getScene open-in-file-browser!))
431 |
432 |
--------------------------------------------------------------------------------
/src/clj/nightcode/core.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.core
2 | (:require [clojure.java.io :as io]
3 | [nightcode.controller :as c]
4 | [nightcode.editors :as e]
5 | [nightcode.projects :as p]
6 | [nightcode.shortcuts :as shortcuts]
7 | [nightcode.state :refer [*pref-state *runtime-state init-pref-state!]]
8 | [nightcode.utils :as u])
9 | (:import [javafx.application Application]
10 | [javafx.fxml FXMLLoader]
11 | [javafx.stage Stage]
12 | [javafx.scene Scene]
13 | [java.util.prefs Preferences])
14 | (:gen-class :extends javafx.application.Application))
15 |
16 | (def actions {:#start c/show-start-menu!
17 | :#import_project c/import!
18 | :#rename c/rename!
19 | :#remove c/remove!
20 | :#up c/up!
21 | :#save c/save!
22 | :#undo c/undo!
23 | :#redo c/redo!
24 | :#instarepl c/toggle-instarepl!
25 | :#find c/focus-on-find!
26 | :#close c/close!
27 | :.run c/run-normal!
28 | :.run-with-repl c/run-with-repl!
29 | :.reload-file c/reload-file!
30 | :.reload-selection c/reload-selection!
31 | :.build c/build!
32 | :.clean c/clean!
33 | :.test c/test!
34 | :.stop c/stop!
35 | :#new_file c/new-file!
36 | :#open_in_file_browser c/open-in-file-browser!})
37 |
38 | (defn -start [^nightcode.core app ^Stage stage]
39 | (let [root (FXMLLoader/load (io/resource "main.fxml"))
40 | scene (Scene. root 1200 600)
41 | project-tree (.lookup scene "#project_tree")
42 | content (.lookup scene "#content")]
43 | (swap! *runtime-state assoc
44 | :stage stage
45 | :prefs (.node (Preferences/userRoot) "nightcode"))
46 | (init-pref-state! {:project-set #{}
47 | :expansion-set #{}
48 | :selection nil
49 | :theme :dark
50 | :text-size 16
51 | :auto-save? true})
52 | (swap! *pref-state update :expansion-set u/filter-paths)
53 | (doto stage
54 | (.setTitle "Nightcode 2.8.3")
55 | (.setScene scene)
56 | (.show))
57 | (shortcuts/init-tabs! scene)
58 | (shortcuts/add-tooltips! scene [:#project_tree :#start :#import_project :#rename :#remove])
59 | (-> content .getChildren .clear)
60 | ; create listeners
61 | (p/set-selection-listener! *pref-state *runtime-state stage)
62 | (p/set-focused-listener! *pref-state *runtime-state stage project-tree)
63 | (p/set-project-key-listener! stage *pref-state *runtime-state)
64 | (shortcuts/set-shortcut-listeners! stage *pref-state *runtime-state actions)
65 | ; update the ui
66 | (p/update-project-tree! *pref-state project-tree)
67 | (p/update-project-buttons! @*pref-state scene)
68 | ; apply the prefs
69 | (let [theme-buttons (->> (.lookup scene "#start")
70 | .getItems
71 | (filter #(= "theme_buttons" (.getId %)))
72 | first
73 | .getContent
74 | .getChildren)]
75 | (case (:theme @*pref-state)
76 | :dark (.fire (.get theme-buttons 0))
77 | :light (.fire (.get theme-buttons 1))
78 | nil))
79 | (c/font! scene)
80 | (let [auto-save-button (->> (.lookup scene "#start")
81 | .getItems
82 | (filter #(= "auto_save" (.getId %)))
83 | first)]
84 | (.setSelected auto-save-button (:auto-save? @*pref-state)))))
85 |
86 | (defn main []
87 | (when (= "Linux" (System/getProperty "os.name"))
88 | (System/setProperty "prism.lcdtext" "false")
89 | (System/setProperty "prism.text" "t2k"))
90 | (swap! *runtime-state assoc :web-port (e/start-web-server!))
91 | (Application/launch nightcode.core (into-array String [])))
92 |
93 | (defn dev-main []
94 | (swap! *runtime-state assoc :dev? true)
95 | (main))
96 |
97 |
--------------------------------------------------------------------------------
/src/clj/nightcode/editors.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.editors
2 | (:require [clojure.java.io :as io]
3 | [clojure.edn :as edn]
4 | [ring.adapter.jetty :refer [run-jetty]]
5 | [ring.middleware.resource :refer [wrap-resource]]
6 | [ring.middleware.content-type :refer [wrap-content-type]]
7 | [ring.util.response :refer [redirect not-found]]
8 | [clojure.spec.alpha :as s :refer [fdef]]
9 | [nightcode.shortcuts :as shortcuts]
10 | [nightcode.utils :as u]
11 | [nightcode.spec :as spec]
12 | [eval-soup.core :as es]
13 | [hawk.core :as hawk])
14 | (:import [javafx.fxml FXMLLoader]
15 | [javafx.scene.web WebEngine]
16 | [javafx.application Platform]
17 | [java.io File]
18 | [nightcode.utils Bridge]))
19 |
20 | (def ^:const max-file-size (* 1024 1024 2))
21 |
22 | (fdef form->serializable
23 | :args (s/cat :form any?)
24 | :ret (s/or :error vector? :value string?))
25 |
26 | (defn form->serializable [form]
27 | (if (instance? Exception form)
28 | [(.getMessage form)]
29 | (pr-str form)))
30 |
31 | (fdef handler
32 | :args (s/cat :request map?)
33 | :ret (s/nilable map?))
34 |
35 | (defn handler [request]
36 | (case (:uri request)
37 | "/" (redirect "/paren-soup.html")
38 | (not-found "")))
39 |
40 | (fdef start-web-server!
41 | :args (s/cat)
42 | :ret integer?)
43 |
44 | (defn start-web-server! []
45 | (-> handler
46 | (wrap-resource "public")
47 | (wrap-content-type)
48 | (run-jetty {:port 0 :join? false})
49 | .getConnectors
50 | (aget 0)
51 | .getLocalPort))
52 |
53 | (fdef remove-editor!
54 | :args (s/cat :path string? :pane spec/pane? :*runtime-state spec/atom?))
55 |
56 | (defn remove-editor! [^String path pane *runtime-state]
57 | (swap! *runtime-state update :editor-panes dissoc path)
58 | (swap! *runtime-state update :bridges dissoc path)
59 | (shortcuts/hide-tooltips! pane)
60 | (some-> pane .getParent .getChildren (.remove pane)))
61 |
62 | (fdef remove-editors!
63 | :args (s/cat :path string? :*runtime-state spec/atom?))
64 |
65 | (defn remove-editors! [^String path *runtime-state]
66 | (doseq [[editor-path pane] (:editor-panes @*runtime-state)]
67 | (when (u/parent-path? path editor-path)
68 | (remove-editor! editor-path pane *runtime-state))))
69 |
70 | (fdef remove-non-existing-editors!
71 | :args (s/cat :*runtime-state spec/atom?))
72 |
73 | (defn remove-non-existing-editors! [*runtime-state]
74 | (doseq [[editor-path pane] (:editor-panes @*runtime-state)]
75 | (when-not (.exists (io/file editor-path))
76 | (remove-editor! editor-path pane *runtime-state))))
77 |
78 | (fdef toggle-instarepl!
79 | :args (s/cat :engine any? :selected? boolean?))
80 |
81 | (defn toggle-instarepl! [^WebEngine engine selected?]
82 | (if selected?
83 | (.executeScript engine "showInstaRepl()")
84 | (.executeScript engine "hideInstaRepl()")))
85 |
86 | (fdef update-editor-buttons!
87 | :args (s/cat :pane spec/pane? :engine any?))
88 |
89 | (defn update-editor-buttons! [pane ^WebEngine engine]
90 | (.setDisable (.lookup pane "#save") (.executeScript engine "isClean()"))
91 | (.setDisable (.lookup pane "#undo") (not (.executeScript engine "canUndo()")))
92 | (.setDisable (.lookup pane "#redo") (not (.executeScript engine "canRedo()"))))
93 |
94 | (fdef onload
95 | :args (s/cat :engine any? :file spec/file? :pref-state map? :clojure? boolean?))
96 |
97 | (defn onload [^WebEngine engine ^File file pref-state clojure?]
98 | (-> engine
99 | (.executeScript "window")
100 | (.call "setTextContent" (into-array [(u/remove-returns (slurp file))])))
101 | (doto engine
102 | (.executeScript (if clojure? "init()" "initPlainText()"))
103 | (.executeScript (case (:theme pref-state)
104 | :dark "changeTheme(true)"
105 | :light "changeTheme(false)"))
106 | (.executeScript (format "setTextSize(%s)" (:text-size pref-state)))))
107 |
108 | (fdef should-open?
109 | :args (s/cat :file spec/file?)
110 | :ret boolean?)
111 |
112 | (defn should-open? [^File file]
113 | (-> file .length (< max-file-size)))
114 |
115 | (def ^:const ids [:#up :#save :#undo :#redo :#instarepl :#find :#close])
116 |
117 | (fdef save-file!
118 | :args (s/cat :path string? :engine any?))
119 |
120 | (defn save-file! [^String path ^WebEngine engine]
121 | (spit (io/file path) (.executeScript engine "getTextContent()"))
122 | (.executeScript engine "markClean()"))
123 |
124 | (fdef eval-code
125 | :args (s/cat :disable-security? boolean? :code string?)
126 | :ret string?)
127 |
128 | (defn eval-code [disable-security? code]
129 | (->> (es/code->results (edn/read-string code)
130 | {:disable-security? disable-security?
131 | :disable-timeout? true})
132 | (mapv form->serializable)
133 | pr-str))
134 |
135 | (fdef editor-pane
136 | :args (s/cat :*pref-state spec/atom? :*runtime-state spec/atom? :file spec/file? :eval-fn (s/nilable fn?))
137 | :ret spec/pane?)
138 |
139 | (defn editor-pane [*pref-state *runtime-state ^File file eval-fn]
140 | (when (should-open? file)
141 | (let [runtime-state @*runtime-state
142 | pane (FXMLLoader/load (io/resource "editor.fxml"))
143 | webview (-> pane .getChildren (.get 1))
144 | engine (.getEngine webview)
145 | clojure? (-> file .getName u/get-extension u/clojure-exts some?)
146 | path (.getCanonicalPath file)
147 | bridge (reify Bridge
148 | (onload [this]
149 | (try
150 | (onload engine file @*pref-state clojure?)
151 | (catch Exception e (.printStackTrace e))))
152 | (onautosave [this]
153 | (try
154 | (let [save-btn (.lookup pane "#save")]
155 | (when (and (:auto-save? @*pref-state)
156 | (not (.isDisabled save-btn)))
157 | (save-file! path engine)))
158 | (catch Exception e (.printStackTrace e))))
159 | (onchange [this]
160 | (try
161 | (update-editor-buttons! pane engine)
162 | (catch Exception e (.printStackTrace e))))
163 | (onenter [this text])
164 | (oneval [this code]
165 | (when (and eval-fn
166 | (-> pane
167 | (.lookup "#instarepl")
168 | .isSelected))
169 | (try
170 | (eval-fn code)
171 | (catch Exception e (.printStackTrace e))))))]
172 | (.setContextMenuEnabled webview false)
173 | (-> pane (.lookup "#instarepl") (.setManaged (some? eval-fn)))
174 | (shortcuts/add-tooltips! pane ids)
175 | ; prevent bridge from being GC'ed
176 | (swap! *runtime-state update :bridges assoc path bridge)
177 | (-> engine
178 | (.executeScript "window")
179 | (.setMember "java" bridge))
180 | (.load engine (str "http://localhost:"
181 | (:web-port runtime-state)
182 | "/paren-soup.html"))
183 | pane)))
184 |
185 | (fdef get-bridge
186 | :args (s/cat :pref-state map? :runtime-state map?)
187 | :ret (s/nilable #(instance? Bridge %)))
188 |
189 | (defn get-bridge [pref-state runtime-state]
190 | (when-let [project-path (u/get-project-path pref-state)]
191 | (get-in runtime-state [:bridges project-path])))
192 |
193 | (fdef create-file-watcher
194 | :args (s/cat :project-dir string? :*runtime-state spec/atom?))
195 |
196 | (defn create-file-watcher [project-dir *runtime-state]
197 | (hawk/watch! [{:paths [project-dir]
198 | :handler (fn [ctx {:keys [file]}]
199 | (when (.exists file)
200 | (when-let [editor (get-in @*runtime-state [:editor-panes (.getCanonicalPath file)])]
201 | (Platform/runLater
202 | (fn []
203 | (when-let [webview (.lookup editor "#webview")]
204 | (when (-> (.getEngine webview)
205 | (.executeScript "getSavedText()")
206 | u/remove-returns
207 | (not= (u/remove-returns (slurp file))))
208 | (-> (.getEngine webview)
209 | (.executeScript "openModal()"))))))))
210 | ctx)}]))
211 |
212 |
--------------------------------------------------------------------------------
/src/clj/nightcode/git.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.git
2 | (:import [javafx.scene.control Alert Alert$AlertType ButtonType TextInputDialog]
3 | [javafx.stage FileChooser Modality]
4 | [javafx.application Platform]
5 | [javafx.scene Scene]
6 | [org.eclipse.jgit.api Git]
7 | [org.eclipse.jgit.lib ProgressMonitor]
8 | [org.eclipse.jgit.transport
9 | CredentialItem
10 | CredentialItem$CharArrayType
11 | CredentialItem$StringType
12 | CredentialItem$YesNoType
13 | CredentialsProvider
14 | CredentialsProviderUserInfo
15 | URIish]))
16 |
17 | (defn boolean-dialog! [^Scene scene ^String text]
18 | (-> (doto (Alert. Alert$AlertType/CONFIRMATION)
19 | (.setHeaderText text)
20 | (.setGraphic nil)
21 | (.initOwner (.getWindow scene))
22 | (.initModality Modality/WINDOW_MODAL))
23 | .showAndWait
24 | (.orElse nil)
25 | (= ButtonType/OK)))
26 |
27 | (defn input-dialog! [^Scene scene ^String text]
28 | (-> (doto (TextInputDialog.)
29 | (.setHeaderText text)
30 | (.setGraphic nil)
31 | (.initOwner (.getWindow scene))
32 | (.initModality Modality/WINDOW_MODAL))
33 | .showAndWait
34 | (.orElse nil)))
35 |
36 | (defn progress-dialog [^Scene scene]
37 | (let [dialog (doto (Alert. Alert$AlertType/CONFIRMATION)
38 | (.setTitle "Cloning...")
39 | (.setGraphic nil)
40 | (.initOwner (.getWindow scene))
41 | (.initModality Modality/WINDOW_MODAL))]
42 | (doto (.getButtonTypes dialog)
43 | .clear
44 | (.add ButtonType/CANCEL))
45 | dialog))
46 |
47 | (defn create-creds [^Scene scene]
48 | (proxy [CredentialsProvider] []
49 | (isInteractive [] false)
50 | (supports [& items] true)
51 | (get [uri items]
52 | (let [success? (promise)]
53 | (Platform/runLater
54 | (fn []
55 | (doseq [item items]
56 | (when-not (realized? success?)
57 | (if-let [ret (if (isa? (type item) CredentialItem$YesNoType)
58 | (boolean-dialog! scene (.getPromptText item))
59 | (input-dialog! scene (.getPromptText item)))]
60 | (cond
61 | (isa? (type item) CredentialItem$CharArrayType)
62 | (.setValue item (char-array ret))
63 | (or (isa? (type item) CredentialItem$StringType)
64 | (isa? (type item) CredentialItem$YesNoType))
65 | (.setValue item ret))
66 | (deliver success? false))))
67 | (deliver success? true)))
68 | @success?))))
69 |
70 | (defn progress-monitor [^Alert dialog cancelled?]
71 | (reify ProgressMonitor
72 | (beginTask [this title total-work]
73 | (Platform/runLater #(.setHeaderText dialog title)))
74 | (endTask [this])
75 | (isCancelled [this] @cancelled?)
76 | (start [this total-tasks])
77 | (update [this unit])))
78 |
79 | (defn clone! [^Scene scene ^String uri f progress]
80 | (-> (Git/cloneRepository)
81 | (.setURI uri)
82 | (.setDirectory f)
83 | (.setCredentialsProvider (create-creds scene))
84 | (.setProgressMonitor progress)
85 | .call
86 | .close))
87 |
88 | (defn address->name [^String s]
89 | (when (.endsWith s ".git")
90 | (-> s URIish. .getHumanishName)))
91 |
92 | (defn clone-with-dialog!
93 | ([^Scene scene]
94 | (let [dialog (doto (TextInputDialog.)
95 | (.setTitle "Clone from Git")
96 | (.setHeaderText "Enter a URL that ends in \".git\"")
97 | (.setGraphic nil)
98 | (.initOwner (.getWindow scene))
99 | (.initModality Modality/WINDOW_MODAL))]
100 | (when-let [git-url (-> dialog .showAndWait (.orElse nil))]
101 | (when-let [dir (-> (doto (FileChooser.)
102 | (.setInitialFileName (address->name git-url))
103 | (.setTitle "Choose directory to clone into"))
104 | (.showSaveDialog (.getWindow scene)))]
105 | (clone-with-dialog! scene git-url dir)))))
106 | ([^Scene scene ^String uri f]
107 | (let [cancelled? (atom false)
108 | exception (atom nil)
109 | path (promise)
110 | dialog (progress-dialog scene)
111 | monitor (progress-monitor dialog cancelled?)]
112 | (future (try
113 | (clone! scene uri f monitor)
114 | (catch Exception e
115 | (when-not @cancelled?
116 | (reset! exception e)))
117 | (finally
118 | (Platform/runLater #(.close dialog))
119 | (if (or @cancelled? @exception)
120 | (deliver path nil)
121 | (deliver path (.getCanonicalPath f))))))
122 | (reset! cancelled? (-> dialog .showAndWait (.orElse nil) (= ButtonType/CANCEL)))
123 | (when-let [e @exception]
124 | (doto (Alert. Alert$AlertType/ERROR)
125 | (.setContentText (.getMessage e))
126 | (.setGraphic nil)
127 | (.initOwner (.getWindow scene))
128 | (.initModality Modality/WINDOW_MODAL)
129 | .showAndWait))
130 | @path)))
131 |
132 |
--------------------------------------------------------------------------------
/src/clj/nightcode/lein.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.lein
2 | (:require [clojure.java.io :as io]
3 | [leiningen.core.project :as project]
4 | [leiningen.clean]
5 | [leiningen.repl]
6 | [leiningen.run]
7 | [leiningen.uberjar]
8 | [leiningen.test]
9 | [leiningen.new]
10 | [clojure.spec.alpha :as s :refer [fdef]]
11 | [nightcode.spec :as spec])
12 | (:gen-class))
13 |
14 | (def class-name (str *ns*))
15 |
16 | (fdef get-project-clj-path
17 | :args (s/cat :path string?)
18 | :ret string?)
19 |
20 | (defn get-project-clj-path
21 | [path]
22 | (.getCanonicalPath (io/file path "project.clj")))
23 |
24 | (fdef read-project-clj
25 | :args (s/cat :path string?)
26 | :ret map?)
27 |
28 | (defn read-project-clj
29 | [path]
30 | (-> path
31 | get-project-clj-path
32 | leiningen.core.project/read-raw
33 | (assoc-in [:repl-options :subsequent-prompt] (fn [ns] ""))))
34 |
35 | (fdef lein!
36 | :args (s/cat :args (s/coll-of string?)))
37 |
38 | (defn lein! [[cmd & args]]
39 | (try
40 | (project/ensure-dynamic-classloader)
41 | (catch Exception _))
42 | (let [path "."
43 | project (-> path read-project-clj leiningen.core.project/init-project)]
44 | (case cmd
45 | "run" (leiningen.run/run project)
46 | "repl" (leiningen.repl/repl project)
47 | "build" (leiningen.uberjar/uberjar project)
48 | "test" (leiningen.test/test project)
49 | "clean" (leiningen.clean/clean project))))
50 |
51 | (fdef new!
52 | :args (s/cat :parent-path string? :project-type keyword? :project-name string?))
53 |
54 | (defn new! [parent-path project-type project-name]
55 | (System/setProperty "leiningen.original.pwd" parent-path)
56 | (leiningen.new/new {} (name project-type) project-name (str project-name ".core")))
57 |
58 | (defn -main [& args]
59 | (System/setProperty "jline.terminal" "dumb")
60 | (lein! args)
61 | (System/exit 0))
62 |
63 |
--------------------------------------------------------------------------------
/src/clj/nightcode/process.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.process
2 | (:require [clojure.java.io :as io]
3 | [nightcode.spec :as spec]
4 | [nightcode.utils :as u]
5 | [clojure.spec.alpha :as s :refer [fdef]])
6 | (:import [com.hypirion.io ClosingPipe Pipe]))
7 |
8 | (fdef start-process!
9 | :args (s/cat :*process spec/atom? :path string? :args (s/coll-of string?)))
10 |
11 | (defn start-process!
12 | [*process path args]
13 | (reset! *process (.exec (Runtime/getRuntime)
14 | (into-array args)
15 | nil
16 | (io/file path)))
17 | (.addShutdownHook (Runtime/getRuntime)
18 | (Thread. #(when @*process (.destroy @*process))))
19 | (with-open [out (io/reader (.getInputStream @*process))
20 | err (io/reader (.getErrorStream @*process))
21 | in (io/writer (.getOutputStream @*process))]
22 | (let [pump-out (doto (Pipe. out *out*) .start)
23 | pump-err (doto (Pipe. err *err*) .start)
24 | pump-in (doto (ClosingPipe. *in* in) .start)]
25 | (.join pump-out)
26 | (.join pump-err)
27 | (.waitFor @*process)
28 | (reset! *process nil))))
29 |
30 | (fdef start-java-process!
31 | :args (s/cat :*process spec/atom? :path string? :args (s/coll-of string?)))
32 |
33 | (defn start-java-process!
34 | [*process path args]
35 | (let [java-cmd (or (System/getenv "JAVA_CMD") "java")
36 | jar-uri (u/get-exec-uri "nightcode.core")]
37 | (start-process! *process path
38 | (flatten [java-cmd
39 | "-cp"
40 | (if (.isDirectory (io/file jar-uri))
41 | (System/getProperty "java.class.path")
42 | (u/uri->str jar-uri))
43 | args]))))
44 |
45 | (fdef stop-process!
46 | :args (s/cat :*process spec/atom?))
47 |
48 | (defn stop-process!
49 | [*process]
50 | (when-let [p @*process]
51 | ; kill child processes if running on java 9 or later
52 | (when (->> Process .getDeclaredMethods seq (some #(= (.getName %) "descendants")))
53 | (doseq [child (-> p .descendants .iterator iterator-seq)]
54 | (.destroyForcibly child)))
55 | (.destroyForcibly p))
56 | (reset! *process nil))
57 |
58 |
--------------------------------------------------------------------------------
/src/clj/nightcode/shortcuts.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.shortcuts
2 | (:require [clojure.string :as str]
3 | [clojure.set :as set]
4 | [clojure.java.io :as io]
5 | [nightcode.spec :as spec]
6 | [clojure.spec.alpha :as s :refer [fdef]]
7 | [nightcode.utils :as u])
8 | (:import [javafx.scene Node]
9 | [javafx.scene Scene]
10 | [javafx.scene.control Tooltip]
11 | [javafx.scene.input KeyEvent KeyCode]
12 | [javafx.stage Stage]
13 | [javafx.event EventHandler]
14 | [javafx.beans.value ChangeListener]
15 | [javafx.application Platform]))
16 |
17 | (def id->key-char {; project pane
18 | :#start "p"
19 | :#import_project "o"
20 | :#rename "m"
21 | :#remove "g"
22 | :#project_tree "↑ ↓ ↲"
23 | ; editor pane
24 | :#up "u"
25 | :#save "s"
26 | :#undo "z"
27 | :#redo "Z"
28 | :#instarepl "l"
29 | :#find "f"
30 | :#close "w"
31 | ; build pane
32 | :.run "r"
33 | :.run-with-repl "X"
34 | :.reload-file "S"
35 | :.reload-selection "R"
36 | :.build "b"
37 | :.clean "L"
38 | :.test "t"
39 | :.stop "i"
40 | ; directory pane
41 | :#new_file "n"
42 | :#open_in_file_browser "F"})
43 |
44 | (def key-char->id (set/map-invert id->key-char))
45 |
46 | (fdef add-tooltip!
47 | :args (s/cat :node spec/node? :text string?))
48 |
49 | (defn add-tooltip! [^Node node ^String text]
50 | (.setTooltip node
51 | (doto (Tooltip.)
52 | (.setOpacity 0)
53 | (.setText text))))
54 |
55 | (fdef add-tooltips!
56 | :args (s/cat :node (s/or :node spec/node? :stage spec/scene?) :ids (s/coll-of keyword?)))
57 |
58 | (defn add-tooltips!
59 | [node ids]
60 | (doseq [id ids]
61 | (let [node (.lookup node (name id))
62 | text (id->key-char id)]
63 | (when (and node text)
64 | (add-tooltip! node text)))))
65 |
66 | (fdef remove-tooltip!
67 | :args (s/cat :node spec/node?))
68 |
69 | (defn remove-tooltip! [node]
70 | (.setTooltip node nil))
71 |
72 | (fdef remove-tooltips!
73 | :args (s/cat :node spec/node? :ids (s/coll-of keyword?)))
74 |
75 | (defn remove-tooltips! [node ids]
76 | (doseq [id ids]
77 | (when-let [node (.lookup node (name id))]
78 | (remove-tooltip! node))))
79 |
80 | (fdef show-tooltip!
81 | :args (s/alt
82 | :two-args (s/cat :stage spec/stage? :node spec/node?)
83 | :three-args (s/cat :stage spec/stage? :node spec/node? :relative-node (s/nilable spec/node?))))
84 |
85 | (defn show-tooltip!
86 | ([^Stage stage ^Node node]
87 | (show-tooltip! stage node node))
88 | ([^Stage stage ^Node node ^Node relative-node]
89 | (when (.isManaged relative-node)
90 | (when-let [^Tooltip tooltip (.getTooltip node)]
91 | (let [node relative-node
92 | point (.localToScene node (double 0) (double 0))
93 | scene (.getScene stage)
94 | _ (.show tooltip stage (double 0) (double 0))
95 | half-width (- (/ (.getWidth node) 2)
96 | (/ (.getWidth tooltip) 4))
97 | half-height (- (/ (.getHeight node) 2)
98 | (/ (.getHeight tooltip) 4))]
99 | (doto tooltip
100 | (.setOpacity 1)
101 | (.show stage
102 | (double (+ (.getX point) (.getX scene) (-> scene .getWindow .getX) half-width))
103 | (double (+ (.getY point) (.getY scene) (-> scene .getWindow .getY) half-height)))))))))
104 |
105 | (fdef show-tooltips!
106 | :args (s/cat :stage spec/stage? :node spec/node?))
107 |
108 | (defn show-tooltips! [^Stage stage ^Node node]
109 | (doseq [id (keys id->key-char)]
110 | (doseq [node (.lookupAll node (name id))]
111 | (show-tooltip! stage node))))
112 |
113 | (fdef hide-tooltip!
114 | :args (s/cat :node spec/node?))
115 |
116 | (defn hide-tooltip! [^Node node]
117 | (when-let [tooltip (.getTooltip node)]
118 | (doto tooltip
119 | (.setOpacity 0)
120 | (.hide))))
121 |
122 | (fdef hide-tooltips!
123 | :args (s/cat :node spec/node?))
124 |
125 | (defn hide-tooltips! [^Node node]
126 | (doseq [id (keys id->key-char)]
127 | (doseq [node (.lookupAll node (name id))]
128 | (hide-tooltip! node))))
129 |
130 | (fdef init-tabs!
131 | :args (s/cat :scene spec/scene?))
132 |
133 | (defn init-tabs! [^Scene scene]
134 | (doto (.lookup scene "#tabs")
135 | (.setManaged false)
136 | (add-tooltip! "")))
137 |
138 | (fdef update-tabs!
139 | :args (s/cat :scene spec/scene? :pref-state map? :runtime-state map?))
140 |
141 | (defn update-tabs! [^Scene scene pref-state runtime-state]
142 | (when-let [tabs (.lookup scene "#tabs")]
143 | (let [tooltip (.getTooltip tabs)
144 | selected-path (:selection pref-state)
145 | names (map (fn [path]
146 | (let [format-str (if (u/parent-path? selected-path path) "-> %s <-" " %s ")
147 | file-name (-> path io/file .getName)]
148 | (format format-str file-name)))
149 | (-> runtime-state :editor-panes keys))
150 | names (str/join "\n" names)]
151 | (.setText tooltip (str " PgUp PgDn \n\n" names)))))
152 |
153 | (fdef show-tabs!
154 | :args (s/cat :stage spec/stage? :node spec/node?))
155 |
156 | (defn show-tabs! [^Stage stage ^Node node]
157 | (when-let [tabs (.lookup node "#tabs")]
158 | (when-let [content (.lookup node "#content")]
159 | (show-tooltip! stage tabs content))))
160 |
161 | (fdef hide-tabs!
162 | :args (s/cat :node spec/node?))
163 |
164 | (defn hide-tabs! [^Node node]
165 | (when-let [tabs (.lookup node "#tabs")]
166 | (hide-tooltip! tabs)))
167 |
168 | (fdef run-shortcut!
169 | :args (s/cat :scene spec/scene? :actions map? :text string? :shift? boolean?))
170 |
171 | (defn run-shortcut! [^Scene scene actions ^String text shift?]
172 | (when-let [id (key-char->id (if shift? text (.toLowerCase text)))]
173 | (when-let [action (get actions id)]
174 | (when (->> (.lookupAll (.getRoot scene) (name id))
175 | (filter #(and (not (.isDisabled %))
176 | (.isManaged %)
177 | (some? (.getTooltip %))))
178 | first)
179 | (Platform/runLater
180 | (fn []
181 | (action scene)))))))
182 |
183 | (fdef set-shortcut-listeners!
184 | :args (s/cat :stage spec/stage? :*pref-state spec/atom? :*runtime-state spec/atom? :actions map?))
185 |
186 | (defn set-shortcut-listeners! [^Stage stage *pref-state *runtime-state actions]
187 | (let [^Scene scene (.getScene stage)]
188 | ; show exit dialog
189 | (.setOnCloseRequest stage
190 | (reify EventHandler
191 | (handle [this e]
192 | (if (u/show-warning! (.getScene stage) "Quit" "Are you sure you want to quit?")
193 | (do
194 | (Platform/exit)
195 | (System/exit 0))
196 | (.consume e)))))
197 | ; update tabs when editor panes change
198 | (add-watch *runtime-state :runtime-state-changed
199 | (fn [_ _ _ new-runtime-state]
200 | (update-tabs! scene @*pref-state new-runtime-state)))
201 | ; show tooltips on key pressed
202 | (.addEventHandler scene KeyEvent/KEY_PRESSED
203 | (reify EventHandler
204 | (handle [this e]
205 | (when (#{KeyCode/COMMAND KeyCode/CONTROL} (.getCode e))
206 | (Platform/runLater
207 | (fn []
208 | (show-tooltips! stage (.getRoot scene))
209 | (show-tabs! stage (.getRoot scene))))))))
210 | ; hide tooltips and run shortcut on key released
211 | (.addEventHandler scene KeyEvent/KEY_RELEASED
212 | (reify EventHandler
213 | (handle [this e]
214 | (cond
215 | (#{KeyCode/COMMAND KeyCode/CONTROL} (.getCode e))
216 | (Platform/runLater
217 | (fn []
218 | (doto (.getRoot scene)
219 | hide-tooltips!
220 | hide-tabs!)))
221 | (.isShortcutDown e)
222 | (if (#{KeyCode/UP KeyCode/DOWN KeyCode/PAGE_UP KeyCode/PAGE_DOWN} (.getCode e))
223 | ; if any new nodes have appeared, make sure their tooltips are showing
224 | (Platform/runLater
225 | (fn []
226 | (show-tooltips! stage (.getRoot scene))
227 | (show-tabs! stage (.getRoot scene))))
228 | ; run the action for the given key
229 | (run-shortcut! scene actions (-> e .getCode .getName) (.isShiftDown e)))))))
230 | ; hide tooltips on window focus
231 | (.addListener (.focusedProperty stage)
232 | (reify ChangeListener
233 | (changed [this observable old-value new-value]
234 | (when new-value
235 | (doto (.getRoot scene)
236 | hide-tooltips!
237 | hide-tabs!)))))))
238 |
239 |
--------------------------------------------------------------------------------
/src/clj/nightcode/spec.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.spec
2 | (:require [clojure.spec.alpha :as s]))
3 |
4 | (defn file? [x]
5 | (instance? java.io.File x))
6 |
7 | (def file-array-type (type (make-array java.io.File 0)))
8 |
9 | (defn file-array? [x]
10 | (instance? file-array-type x))
11 |
12 | (defn node? [x]
13 | (instance? javafx.scene.Node x))
14 |
15 | (defn pane? [x]
16 | (instance? javafx.scene.layout.Region x))
17 |
18 | (defn tree-item? [x]
19 | (instance? javafx.scene.control.TreeItem x))
20 |
21 | (defn obs-list? [x]
22 | (instance? javafx.collections.ObservableList x))
23 |
24 | (defn atom? [x]
25 | (instance? clojure.lang.Atom x))
26 |
27 | (defn scene? [x]
28 | (instance? javafx.scene.Scene x))
29 |
30 | (defn stage? [x]
31 | (instance? javafx.stage.Stage x))
32 |
33 | (defn ns? [x]
34 | (instance? clojure.lang.Namespace x))
35 |
36 | (s/def ::files
37 | (s/or :primitive-array file-array?
38 | :collection (s/coll-of file?)))
39 |
--------------------------------------------------------------------------------
/src/clj/nightcode/start.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.start
2 | "This ns is a workaround for an issue that prevented making uberjars
3 | with JavaFX 11. See https://stackoverflow.com/a/52571719"
4 | (:require [nightcode.core :as c])
5 | (:gen-class))
6 |
7 | (defn -main [& args]
8 | (c/main))
9 |
--------------------------------------------------------------------------------
/src/clj/nightcode/state.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.state
2 | (:require [clojure.edn :as edn]
3 | [clojure.spec.alpha :as s :refer [fdef]]))
4 |
5 | ; preferences
6 |
7 | (declare *runtime-state)
8 |
9 | (fdef write-pref!
10 | :args (s/cat :key keyword? :val any?))
11 |
12 | (defn write-pref!
13 | "Writes a key-value pair to the preference file."
14 | [k v]
15 | (when-let [prefs (:prefs @*runtime-state)]
16 | (doto prefs
17 | (.put (name k) (pr-str v))
18 | .flush)))
19 |
20 | (fdef remove-pref!
21 | :args (s/cat :key keyword?))
22 |
23 | (defn remove-pref!
24 | "Removes a key-value pair from the preference file."
25 | [k]
26 | (when-let [prefs (:prefs @*runtime-state)]
27 | (doto prefs
28 | (.remove (name k))
29 | .flush)))
30 |
31 | (fdef read-pref
32 | :args (s/alt
33 | :key-only (s/cat :key keyword?)
34 | :key-and-val (s/cat :key keyword? :default-val any?)))
35 |
36 | (defn read-pref
37 | "Reads value from the given key in the preference file."
38 | ([k]
39 | (read-pref k nil))
40 | ([k default-val]
41 | (when-let [prefs (:prefs @*runtime-state)]
42 | (if-let [string (.get prefs (name k) nil)]
43 | (edn/read-string string)
44 | default-val))))
45 |
46 | ; state
47 |
48 | (defonce *pref-state (atom {}))
49 |
50 | (defonce *runtime-state (atom {:web-port nil
51 | :projects {}
52 | :editor-panes {}
53 | :bridges {}
54 | :processes {}
55 | :stage nil
56 | :prefs nil}))
57 |
58 | (defn init-pref-state! [defaults]
59 | (->> defaults
60 | (map (fn [[k v]]
61 | [k (read-pref k v)]))
62 | flatten
63 | (apply hash-map)
64 | (reset! *pref-state))
65 | (add-watch *pref-state :write-prefs
66 | (fn [_ _ old-state new-state]
67 | (doseq [key (keys defaults)]
68 | (let [old-val (get old-state key)
69 | new-val (get new-state key)]
70 | (when (not= old-val new-val)
71 | (write-pref! key new-val)))))))
72 |
73 |
--------------------------------------------------------------------------------
/src/clj/nightcode/utils.clj:
--------------------------------------------------------------------------------
1 | (ns nightcode.utils
2 | (:require [clojure.java.io :as io]
3 | [clojure.string :as str]
4 | [nightcode.spec :as spec]
5 | [clojure.spec.alpha :as s :refer [fdef]])
6 | (:import [java.io File]
7 | [java.nio.file Paths]
8 | [javafx.scene.control Alert Alert$AlertType ButtonType]
9 | [javafx.scene Scene]
10 | [javafx.stage Modality]))
11 |
12 | (def ^:const clojure-exts #{"boot" "clj" "cljc" "cljs" "cljx" "edn" "pxi" "hl" "carp"})
13 |
14 | (definterface Bridge
15 | (onload [])
16 | (onautosave [])
17 | (onchange [])
18 | (onenter [text])
19 | (oneval [code]))
20 |
21 | (fdef get-relative-path
22 | :args (s/cat :project-path string? :selected-path string?)
23 | :ret string?)
24 |
25 | (defn get-relative-path
26 | "Returns the selected path as a relative URI to the project path."
27 | [project-path selected-path]
28 | (-> (Paths/get (.toURI (io/file project-path)))
29 | (.relativize (Paths/get (.toURI (io/file selected-path))))
30 | (.toString)))
31 |
32 | (fdef delete-parents-recursively!
33 | :args (s/cat :project-set set? :path string?))
34 |
35 | (defn delete-parents-recursively!
36 | "Deletes the given file along with all empty parents unless they are in project-set."
37 | [project-set path]
38 | (let [f (io/file path)]
39 | (when (and (zero? (count (.listFiles f)))
40 | (not (contains? project-set path)))
41 | (io/delete-file f true)
42 | (->> f
43 | .getParentFile
44 | .getCanonicalPath
45 | (delete-parents-recursively! project-set))))
46 | nil)
47 |
48 | (fdef delete-children-recursively!
49 | :args (s/cat :path spec/file?))
50 |
51 | (defn delete-children-recursively!
52 | "Deletes the children of the given dir along with the dir itself."
53 | [f]
54 | (when (.isDirectory f)
55 | (doseq [f2 (.listFiles f)]
56 | (delete-children-recursively! f2)))
57 | (io/delete-file f)
58 | nil)
59 |
60 | (fdef get-project-root-path
61 | :args (s/cat :pref-state map?)
62 | :ret (s/nilable string?))
63 |
64 | (defn get-project-root-path
65 | "Returns the root path that the selected path is contained within."
66 | [pref-state]
67 | (when-let [^String selected-path (:selection pref-state)]
68 | (-> #(or (.startsWith selected-path (str % File/separator))
69 | (= selected-path %))
70 | (filter (:project-set pref-state))
71 | first)))
72 |
73 | (fdef build-systems
74 | :args (s/cat :path string?)
75 | :ret (s/coll-of keyword?))
76 |
77 | (defn build-systems
78 | "Returns a set containing :boot and/or :lein if the given path contains the
79 | requisite project files, or empty if neither exists."
80 | [^String path]
81 | (let [file (io/file path)
82 | dir? (.isDirectory file)
83 | types #{}
84 | types (if (and dir? (.exists (io/file file "build.boot")))
85 | (conj types :boot)
86 | types)
87 | types (if (and dir? (.exists (io/file file "project.clj")))
88 | (conj types :lein)
89 | types)]
90 | types))
91 |
92 | (fdef get-project-path
93 | :args (s/alt
94 | :one-arg (s/cat :pref-state map?)
95 | :two-args (s/cat :path string? :pref-state map?))
96 | :ret (s/nilable string?))
97 |
98 | (defn get-project-path
99 | "Returns the project path that the given path is contained within."
100 | ([pref-state]
101 | (when-let [^String selected-path (:selection pref-state)]
102 | (get-project-path selected-path pref-state)))
103 | ([path pref-state]
104 | (if-let [file (try (io/file path) (catch Exception _))]
105 | (if (or (-> path build-systems count pos?)
106 | (contains? (:project-set pref-state) path))
107 | path
108 | (when-let [parent-file (.getParentFile file)]
109 | (get-project-path (.getCanonicalPath parent-file) pref-state)))
110 | path)))
111 |
112 | (fdef parent-path?
113 | :args (s/cat :parent-path string? :child-path (s/nilable string?))
114 | :ret boolean?)
115 |
116 | (defn parent-path?
117 | "Determines if the given parent path is equal to or a parent of the child."
118 | [^String parent-path ^String child-path]
119 | (or (= parent-path child-path)
120 | (and parent-path
121 | child-path
122 | (.isDirectory (io/file parent-path))
123 | (.startsWith child-path (str parent-path File/separator)))
124 | false))
125 |
126 | (fdef get-extension
127 | :args (s/cat :path string?)
128 | :ret string?)
129 |
130 | (defn get-extension
131 | "Returns the extension in the given path name."
132 | [^String path]
133 | (->> (.lastIndexOf path ".")
134 | (+ 1)
135 | (subs path)
136 | str/lower-case))
137 |
138 | (fdef uri->str
139 | :args (s/cat :uri #(instance? java.net.URI %))
140 | :ret string?)
141 |
142 | (defn uri->str
143 | "Converts a java.net.URI to a String."
144 | [uri]
145 | (-> uri Paths/get .normalize .toString))
146 |
147 | (fdef get-exec-uri
148 | :args (s/cat :class-name string?)
149 | :ret #(instance? java.net.URI %))
150 |
151 | (defn get-exec-uri
152 | "Returns the executable as a java.net.URI."
153 | [class-name]
154 | (-> (Class/forName class-name)
155 | .getProtectionDomain
156 | .getCodeSource
157 | .getLocation
158 | .toURI))
159 |
160 | (fdef remove-returns
161 | :args (s/cat :s string?)
162 | :ret string?)
163 |
164 | (defn remove-returns [^String s]
165 | (-> s
166 | (str/escape {\return ""})
167 | (str/replace #"\u001b[^\n]*" "")))
168 |
169 | (fdef get-boot-path!
170 | :args (s/cat)
171 | :ret string?)
172 |
173 | (defn get-boot-path! []
174 | (let [file-name "boot-2.7.2.jar"
175 | file (io/file (System/getProperty "user.home") (str ".nightcode-" file-name))]
176 | (when-not (.exists file)
177 | (-> file-name io/resource io/input-stream (io/copy file)))
178 | (.getCanonicalPath file)))
179 |
180 | (fdef get-boot-tasks
181 | :args (s/cat :project-path string?)
182 | :ret (s/coll-of string?))
183 |
184 | (defn get-boot-tasks [project-path]
185 | (try
186 | (let [path (.getCanonicalPath (io/file project-path "build.boot"))
187 | rdr (java.io.PushbackReader. (io/reader path))]
188 | (loop [tasks []]
189 | (if-let [form (try (read rdr)
190 | (catch Exception _))]
191 | (if (= 'deftask (first form))
192 | (recur (conj tasks (str (second form))))
193 | (recur tasks))
194 | tasks)))
195 | (catch Exception _ [])))
196 |
197 | (fdef normalize-text-size
198 | :args (s/cat :num number?)
199 | :ret (s/and number? even?))
200 |
201 | (defn normalize-text-size [n]
202 | (-> n
203 | (/ 2)
204 | (Math/ceil)
205 | (* 2)
206 | (max 12)
207 | (min 24)
208 | int))
209 |
210 | (fdef filter-paths
211 | :args (s/cat :paths (s/coll-of string?))
212 | :ret (s/coll-of string?))
213 |
214 | (defn filter-paths [paths]
215 | (set (filter (fn [path]
216 | (try (-> path io/file .exists)
217 | (catch Exception _ false)))
218 | paths)))
219 |
220 | (fdef show-warning!
221 | :args (s/cat :scene spec/scene? :title string? :header-text string?)
222 | :ret boolean?)
223 |
224 | (defn show-warning! [^Scene scene ^String title ^String header-text]
225 | (let [dialog (doto (Alert. Alert$AlertType/CONFIRMATION)
226 | (.setTitle title)
227 | (.setHeaderText header-text)
228 | (.setGraphic nil)
229 | (.initOwner (.getWindow scene))
230 | (.initModality Modality/WINDOW_MODAL))]
231 | (-> dialog .showAndWait (.orElse nil) (= ButtonType/OK))))
232 |
233 | (fdef update-webviews!
234 | :args (s/cat :pref-state map? :runtime-state map?))
235 |
236 | (defn update-webviews! [pref-state {:keys [editor-panes projects]}]
237 | (doseq [pane (concat (vals editor-panes) (map :pane (vals projects)))
238 | :when pane]
239 | (doseq [webview (.lookupAll pane "WebView")]
240 | (try
241 | (doto (.getEngine webview)
242 | (.executeScript (case (:theme pref-state)
243 | :dark "changeTheme(true)"
244 | :light "changeTheme(false)"))
245 | (.executeScript (format "setTextSize(%s)" (:text-size pref-state))))
246 | (catch Exception _)))))
247 |
248 | (fdef get-icon-path
249 | :args (s/cat :file spec/file?)
250 | :ret (s/nilable string?))
251 |
252 | (defn get-icon-path
253 | [f]
254 | (when-not (.isDirectory f)
255 | (let [ext (get-extension (.getName f))]
256 | (case ext
257 | "clj" "images/file-clj.png"
258 | "cljc" "images/file-cljc.png"
259 | "cljs" "images/file-cljs.png"
260 | "java" "images/file-java.png"
261 | (if (clojure-exts ext)
262 | "images/file-clj.png"
263 | "images/file.png")))))
264 |
265 | (fdef sanitize-name
266 | :args (s/cat :s string?)
267 | :ret string?)
268 |
269 | (defn sanitize-name [s]
270 | (as-> s $
271 | (str/trim $)
272 | (str/lower-case $)
273 | (str/replace $ "'" "")
274 | (str/replace $ #"[^a-z0-9]" " ")
275 | (str/split $ #" ")
276 | (remove empty? $)
277 | (str/join "-" $)))
278 |
279 |
--------------------------------------------------------------------------------
/src/cljs/nightcode/paren_soup.cljs:
--------------------------------------------------------------------------------
1 | (ns nightcode.paren-soup
2 | (:require [goog.functions :refer [debounce]]
3 | [paren-soup.core :as p]
4 | [mistakes-were-made.core :as mwm]
5 | [cross-parinfer.core :as cp]
6 | [cljs.reader :refer [read-string]]
7 | [goog.dom :as gdom]
8 | [goog.object :as gobj])
9 | (:import goog.net.XhrIo))
10 |
11 | (def *state (atom {:text-content "" :editor nil}))
12 |
13 | (def modal (.querySelector js/document "#modal"))
14 |
15 | (def auto-save
16 | (debounce
17 | #(.onautosave js/window.java)
18 | 1000))
19 |
20 | (defn undo []
21 | (some-> @*state :editor p/undo)
22 | (.onautosave js/window.java))
23 |
24 | (defn redo []
25 | (some-> @*state :editor p/redo)
26 | (.onautosave js/window.java))
27 |
28 | (defn can-undo? []
29 | (some-> @*state :editor p/can-undo?))
30 |
31 | (defn can-redo? []
32 | (some-> @*state :editor p/can-redo?))
33 |
34 | (defn set-text-content [content]
35 | (gdom/setTextContent (.querySelector js/document "#content") content))
36 |
37 | (defn get-text-content []
38 | (.-textContent (.querySelector js/document "#content")))
39 |
40 | (defn get-saved-text []
41 | (:text-content @*state))
42 |
43 | (defn get-selected-text []
44 | (when-let [text (or (p/selected-text) (p/focused-text))]
45 | (:text (cp/mode :both text 0 0))))
46 |
47 | (defn mark-clean []
48 | (swap! *state assoc :text-content (get-text-content))
49 | (.onchange js/window.java))
50 |
51 | (defn clean? []
52 | (some-> @*state :text-content (= (get-text-content))))
53 |
54 | (defn append [text]
55 | (some-> @*state :editor (p/append-text! text))
56 | (let [paren-soup (.querySelector js/document "#paren-soup")]
57 | (set! (.-scrollTop paren-soup) (.-scrollHeight paren-soup))))
58 |
59 | (defn change-theme [dark?]
60 | (let [old-link (-> js/document (.getElementsByTagName "link") (.item 0))
61 | new-link (.createElement js/document "link")]
62 | (doto new-link
63 | (.setAttribute "rel" "stylesheet")
64 | (.setAttribute "type" "text/css")
65 | (.setAttribute "href" (if dark? "paren-soup-dark.css" "paren-soup-light.css")))
66 | (-> js/document (.getElementsByTagName "head") (.item 0) (.replaceChild new-link old-link))))
67 |
68 | (defn set-text-size [size]
69 | (-> js/document
70 | (.querySelector "#paren-soup")
71 | .-style
72 | .-fontSize
73 | (set! (str size "px"))))
74 |
75 | (defn open-modal []
76 | (-> modal
77 | .-style
78 | .-display
79 | (set! "block")))
80 |
81 | (defn compiler-fn [forms cb]
82 | (->> (pr-str (into [] forms))
83 | (.oneval js/window.java)
84 | read-string
85 | (mapv #(if (vector? %) (into-array %) %))
86 | cb))
87 |
88 | (defn init []
89 | (mark-clean)
90 | (let [paren-soup (.querySelector js/document "#paren-soup")
91 | content (.querySelector js/document "#content")
92 | _ (-> content .-style .-whiteSpace (set! "pre"))
93 | _ (-> content .-style .-paddingBottom (set! "20px"))
94 | _ (-> content .-style .-paddingRight (set! "20px"))
95 | editor (p/init paren-soup
96 | (clj->js {:before-change-callback
97 | (fn [e]
98 | ; don't refresh editor when this is true
99 | (and (= (.-type e) "keyup")
100 | (= (.-keyCode e) 0)))
101 | :change-callback
102 | (fn [e]
103 | (let [{:keys [text-after-parinfer]} @*state]
104 | (when (= (.-type e) "keyup")
105 | (cond
106 | (nil? text-after-parinfer)
107 | (auto-save)
108 | ; if parinfer changed the initial text,
109 | ; don't autosave unless user changed
110 | ; the text afterwards
111 | (not= text-after-parinfer (get-text-content))
112 | (do
113 | (swap! *state dissoc :text-after-parinfer)
114 | (auto-save)))))
115 | (.onchange js/window.java))
116 | :disable-undo-redo? true
117 | :compiler-fn compiler-fn
118 | :edit-history (:edit-history @*state)}))
119 | text-after-parinfer (when-not (clean?)
120 | (get-text-content))]
121 | (swap! *state assoc
122 | :editor editor
123 | :text-after-parinfer text-after-parinfer
124 | :edit-history (mwm/create-edit-history))
125 | (.focus content)))
126 |
127 | (defn init-plain-text []
128 | (mark-clean)
129 | (let [paren-soup (.querySelector js/document "#paren-soup")
130 | content (.querySelector js/document "#content")
131 | _ (-> content .-style .-whiteSpace (set! "pre"))
132 | _ (-> content .-style .-paddingBottom (set! "20px"))
133 | _ (-> content .-style .-paddingRight (set! "20px"))
134 | editor (p/init paren-soup
135 | (clj->js {:before-change-callback
136 | (fn [e]
137 | ; don't refresh editor when this is true
138 | (and (= (.-type e) "keyup")
139 | (= (.-keyCode e) 0)))
140 | :change-callback
141 | (fn [e]
142 | (when (= (.-type e) "keyup")
143 | (auto-save))
144 | (.onchange js/window.java))
145 | :disable-undo-redo? true
146 | :disable-clj? true
147 | :edit-history (:edit-history @*state)}))]
148 | (swap! *state assoc
149 | :editor editor
150 | :edit-history (mwm/create-edit-history))
151 | (.focus content)))
152 |
153 | (defn init-console [repl?]
154 | (let [paren-soup (.querySelector js/document "#paren-soup")
155 | content (.querySelector js/document "#content")]
156 | (-> content .-style .-whiteSpace (set! "pre-wrap"))
157 | (swap! *state assoc :editor
158 | (p/init paren-soup
159 | (clj->js {:before-change-callback
160 | (fn [e]
161 | ; don't refresh editor when this is true
162 | (and (= (.-type e) "keyup")
163 | (= (.-keyCode e) 0)))
164 | :change-callback
165 | (fn [e]
166 | (when (= (.-type e) "keyup")
167 | (set! (.-scrollTop paren-soup) (.-scrollHeight paren-soup)))
168 | (.onchange js/window.java))
169 | :disable-undo-redo? true
170 | :console-callback
171 | (fn [text]
172 | (.onenter js/window.java (str text "\n")))
173 | :disable-clj? (not repl?)
174 | :append-limit 10000})))))
175 |
176 | (defn show-instarepl []
177 | (-> (.querySelector js/document "#instarepl")
178 | .-style
179 | .-display
180 | (set! "list-item"))
181 | (init))
182 |
183 | (defn hide-instarepl []
184 | (-> (.querySelector js/document "#instarepl")
185 | .-style
186 | .-display
187 | (set! "none"))
188 | (init))
189 |
190 | (doto js/window
191 | (gobj/set "undo" undo)
192 | (gobj/set "redo" redo)
193 | (gobj/set "canUndo" can-undo?)
194 | (gobj/set "canRedo" can-redo?)
195 | (gobj/set "setTextContent" set-text-content)
196 | (gobj/set "getTextContent" get-text-content)
197 | (gobj/set "getSelectedText" get-selected-text)
198 | (gobj/set "getSavedText" get-saved-text)
199 | (gobj/set "markClean" mark-clean)
200 | (gobj/set "isClean" clean?)
201 | (gobj/set "append" append)
202 | (gobj/set "changeTheme" change-theme)
203 | (gobj/set "setTextSize" set-text-size)
204 | (gobj/set "openModal" open-modal)
205 | (gobj/set "init" init)
206 | (gobj/set "initPlainText" init-plain-text)
207 | (gobj/set "initConsole" init-console)
208 | (gobj/set "showInstaRepl" show-instarepl)
209 | (gobj/set "hideInstaRepl" hide-instarepl))
210 |
211 | (set! (.-onload js/window)
212 | (fn []
213 | ; hack thanks to http://stackoverflow.com/a/28414332/1663009
214 | (set! (.-status js/window) "MY-MAGIC-VALUE")
215 | (set! (.-status js/window) "")
216 | (.onload js/window.java)
217 | (.onchange js/window.java)))
218 |
219 | (set! (.-onkeydown js/window)
220 | (fn [e]
221 | (when (or (and (or (.-metaKey e) (.-ctrlKey e))
222 | (#{38 40} (.-keyCode e)))
223 | (-> modal
224 | .-style
225 | .-display
226 | (= "block")))
227 | (.preventDefault e))))
228 |
229 |
--------------------------------------------------------------------------------