├── .gitignore
├── LICENSE
├── README.md
├── boot.properties
├── build.boot
├── demo
└── re_console
│ ├── example.cljs
│ ├── example
│ ├── handlers.cljs
│ └── subs.cljs
│ ├── io.cljs
│ └── replumb_proxy.cljs
├── html
├── css
│ ├── main.css
│ └── re-console.scss
├── index.html
└── js
│ └── clojure-parinfer.js
├── screenshot.gif
├── src
└── re_console
│ ├── app.cljs
│ ├── common.cljs
│ ├── core.cljs
│ ├── editor.cljs
│ ├── handlers.cljs
│ ├── parinfer.cljs
│ ├── subs.cljs
│ └── utils.cljs
├── test
└── re_console
│ ├── app_test.cljs
│ └── example_test.cljs
└── version.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | ### BOOT #######################################################################
2 |
3 | /.boot/
4 |
5 | ### LEININGEN ##################################################################
6 |
7 | .lein-*
8 |
9 | ### MAVEN (include pom.xml in /base) ###########################################
10 |
11 | *.jar
12 | *.war
13 | pom.xml
14 | pom.xml.asc
15 |
16 | ### NREPL ######################################################################
17 |
18 | .repl-*
19 | .nrepl-*
20 |
21 | ### JAVA #######################################################################
22 |
23 | /hs_err_pid*.log
24 |
25 | ### OSX ########################################################################
26 |
27 | .DS_Store
28 |
29 | ### EMACS ######################################################################
30 |
31 | [#]*[#]
32 |
33 | ### VIM ########################################################################
34 |
35 | *.swn
36 | *.swo
37 | *.swp
38 |
39 | ### PROJECT ####################################################################
40 |
41 | /target/
42 | /out
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Eclipse Public License - v 1.0
2 |
3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
6 |
7 | 1. DEFINITIONS
8 |
9 | "Contribution" means:
10 |
11 | a) in the case of the initial Contributor, the initial code and documentation
12 | distributed under this Agreement, and
13 | b) in the case of each subsequent Contributor:
14 | i) changes to the Program, and
15 | ii) additions to the Program;
16 |
17 | where such changes and/or additions to the Program originate from and are
18 | distributed by that particular Contributor. A Contribution 'originates'
19 | from a Contributor if it was added to the Program by such Contributor
20 | itself or anyone acting on such Contributor's behalf. Contributions do not
21 | include additions to the Program which: (i) are separate modules of
22 | software distributed in conjunction with the Program under their own
23 | license agreement, and (ii) are not derivative works of the Program.
24 |
25 | "Contributor" means any person or entity that distributes the Program.
26 |
27 | "Licensed Patents" mean patent claims licensable by a Contributor which are
28 | necessarily infringed by the use or sale of its Contribution alone or when
29 | combined with the Program.
30 |
31 | "Program" means the Contributions distributed in accordance with this
32 | Agreement.
33 |
34 | "Recipient" means anyone who receives the Program under this Agreement,
35 | including all Contributors.
36 |
37 | 2. GRANT OF RIGHTS
38 | a) Subject to the terms of this Agreement, each Contributor hereby grants
39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
40 | reproduce, prepare derivative works of, publicly display, publicly
41 | perform, distribute and sublicense the Contribution of such Contributor,
42 | if any, and such derivative works, in source code and object code form.
43 | b) Subject to the terms of this Agreement, each Contributor hereby grants
44 | Recipient a non-exclusive, worldwide, royalty-free patent license under
45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
46 | transfer the Contribution of such Contributor, if any, in source code and
47 | object code form. This patent license shall apply to the combination of
48 | the Contribution and the Program if, at the time the Contribution is
49 | added by the Contributor, such addition of the Contribution causes such
50 | combination to be covered by the Licensed Patents. The patent license
51 | shall not apply to any other combinations which include the Contribution.
52 | No hardware per se is licensed hereunder.
53 | c) Recipient understands that although each Contributor grants the licenses
54 | to its Contributions set forth herein, no assurances are provided by any
55 | Contributor that the Program does not infringe the patent or other
56 | intellectual property rights of any other entity. Each Contributor
57 | disclaims any liability to Recipient for claims brought by any other
58 | entity based on infringement of intellectual property rights or
59 | otherwise. As a condition to exercising the rights and licenses granted
60 | hereunder, each Recipient hereby assumes sole responsibility to secure
61 | any other intellectual property rights needed, if any. For example, if a
62 | third party patent license is required to allow Recipient to distribute
63 | the Program, it is Recipient's responsibility to acquire that license
64 | before distributing the Program.
65 | d) Each Contributor represents that to its knowledge it has sufficient
66 | copyright rights in its Contribution, if any, to grant the copyright
67 | license set forth in this Agreement.
68 |
69 | 3. REQUIREMENTS
70 |
71 | A Contributor may choose to distribute the Program in object code form under
72 | its own license agreement, provided that:
73 |
74 | a) it complies with the terms and conditions of this Agreement; and
75 | b) its license agreement:
76 | i) effectively disclaims on behalf of all Contributors all warranties
77 | and conditions, express and implied, including warranties or
78 | conditions of title and non-infringement, and implied warranties or
79 | conditions of merchantability and fitness for a particular purpose;
80 | ii) effectively excludes on behalf of all Contributors all liability for
81 | damages, including direct, indirect, special, incidental and
82 | consequential damages, such as lost profits;
83 | iii) states that any provisions which differ from this Agreement are
84 | offered by that Contributor alone and not by any other party; and
85 | iv) states that source code for the Program is available from such
86 | Contributor, and informs licensees how to obtain it in a reasonable
87 | manner on or through a medium customarily used for software exchange.
88 |
89 | When the Program is made available in source code form:
90 |
91 | a) it must be made available under this Agreement; and
92 | b) a copy of this Agreement must be included with each copy of the Program.
93 | Contributors may not remove or alter any copyright notices contained
94 | within the Program.
95 |
96 | Each Contributor must identify itself as the originator of its Contribution,
97 | if
98 | any, in a manner that reasonably allows subsequent Recipients to identify the
99 | originator of the Contribution.
100 |
101 | 4. COMMERCIAL DISTRIBUTION
102 |
103 | Commercial distributors of software may accept certain responsibilities with
104 | respect to end users, business partners and the like. While this license is
105 | intended to facilitate the commercial use of the Program, the Contributor who
106 | includes the Program in a commercial product offering should do so in a manner
107 | which does not create potential liability for other Contributors. Therefore,
108 | if a Contributor includes the Program in a commercial product offering, such
109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
110 | every other Contributor ("Indemnified Contributor") against any losses,
111 | damages and costs (collectively "Losses") arising from claims, lawsuits and
112 | other legal actions brought by a third party against the Indemnified
113 | Contributor to the extent caused by the acts or omissions of such Commercial
114 | Contributor in connection with its distribution of the Program in a commercial
115 | product offering. The obligations in this section do not apply to any claims
116 | or Losses relating to any actual or alleged intellectual property
117 | infringement. In order to qualify, an Indemnified Contributor must:
118 | a) promptly notify the Commercial Contributor in writing of such claim, and
119 | b) allow the Commercial Contributor to control, and cooperate with the
120 | Commercial Contributor in, the defense and any related settlement
121 | negotiations. The Indemnified Contributor may participate in any such claim at
122 | its own expense.
123 |
124 | For example, a Contributor might include the Program in a commercial product
125 | offering, Product X. That Contributor is then a Commercial Contributor. If
126 | that Commercial Contributor then makes performance claims, or offers
127 | warranties related to Product X, those performance claims and warranties are
128 | such Commercial Contributor's responsibility alone. Under this section, the
129 | Commercial Contributor would have to defend claims against the other
130 | Contributors related to those performance claims and warranties, and if a
131 | court requires any other Contributor to pay any damages as a result, the
132 | Commercial Contributor must pay those damages.
133 |
134 | 5. NO WARRANTY
135 |
136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
140 | Recipient is solely responsible for determining the appropriateness of using
141 | and distributing the Program and assumes all risks associated with its
142 | exercise of rights under this Agreement , including but not limited to the
143 | risks and costs of program errors, compliance with applicable laws, damage to
144 | or loss of data, programs or equipment, and unavailability or interruption of
145 | operations.
146 |
147 | 6. DISCLAIMER OF LIABILITY
148 |
149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
156 | OF SUCH DAMAGES.
157 |
158 | 7. GENERAL
159 |
160 | If any provision of this Agreement is invalid or unenforceable under
161 | applicable law, it shall not affect the validity or enforceability of the
162 | remainder of the terms of this Agreement, and without further action by the
163 | parties hereto, such provision shall be reformed to the minimum extent
164 | necessary to make such provision valid and enforceable.
165 |
166 | If Recipient institutes patent litigation against any entity (including a
167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
168 | (excluding combinations of the Program with other software or hardware)
169 | infringes such Recipient's patent(s), then such Recipient's rights granted
170 | under Section 2(b) shall terminate as of the date such litigation is filed.
171 |
172 | All Recipient's rights under this Agreement shall terminate if it fails to
173 | comply with any of the material terms or conditions of this Agreement and does
174 | not cure such failure in a reasonable period of time after becoming aware of
175 | such noncompliance. If all Recipient's rights under this Agreement terminate,
176 | Recipient agrees to cease use and distribution of the Program as soon as
177 | reasonably practicable. However, Recipient's obligations under this Agreement
178 | and any licenses granted by Recipient relating to the Program shall continue
179 | and survive.
180 |
181 | Everyone is permitted to copy and distribute copies of this Agreement, but in
182 | order to avoid inconsistency the Agreement is copyrighted and may only be
183 | modified in the following manner. The Agreement Steward reserves the right to
184 | publish new versions (including revisions) of this Agreement from time to
185 | time. No one other than the Agreement Steward has the right to modify this
186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
187 | Eclipse Foundation may assign the responsibility to serve as the Agreement
188 | Steward to a suitable separate entity. Each new version of the Agreement will
189 | be given a distinguishing version number. The Program (including
190 | Contributions) may always be distributed subject to the version of the
191 | Agreement under which it was received. In addition, after a new version of the
192 | Agreement is published, Contributor may elect to distribute the Program
193 | (including its Contributions) under the new version. Except as expressly
194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
195 | licenses to the intellectual property of any Contributor under this Agreement,
196 | whether expressly, by implication, estoppel or otherwise. All rights in the
197 | Program not expressly granted under this Agreement are reserved.
198 |
199 | This Agreement is governed by the laws of the State of New York and the
200 | intellectual property laws of the United States of America. No party to this
201 | Agreement will bring a legal action under this Agreement more than one year
202 | after the cause of action arose. Each party waives its rights to a jury trial in
203 | any resulting litigation.
204 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # re-console
2 |
3 | [](https://clojars.org/re-console)
4 |
5 | ## Overview
6 | re-console is an interactive terminal-like REPL. It is implemented using a
7 | [CodeMirror](https://codemirror.net/)
8 | component for user input and
9 | [re-frame](https://github.com/Day8/re-frame) for state management. It
10 | provides a user-friendly interface which allows to easily embed it in a web page
11 | and integrate it with an evaluation library (e.g.
12 | [replumb](https://github.com/Lambda-X/replumb)).
13 |
14 | ## Demo
15 |
16 | The sources are located in `src/re_console`,
17 | while the interactive demo is located in `demo/re_console`.
18 | It uses `replumb` as evaluation library.
19 |
20 | To start the demo run `boot dev` and browse to
21 | [localhost:3000](http://localhost:3000).
22 |
23 | 
24 |
25 | ## Usage
26 |
27 | Using re-console is as simple as rendering the `console` component, which
28 | accepts two parameters: a unique key and a map of options.
29 |
30 | ```clojure
31 | (reagent/render [console/console :key
32 | {:eval-opts (replumb-proxy/eval-opts false ["/js/compiled/out"])
33 | :mode-line? true}]
34 | (.getElementById js/document "app"))
35 | ```
36 |
37 | The unique key is used to identify the console and the map of options
38 | should contain the following keys:
39 |
40 | * `:mode`: the input mode, one of `#{:indent-mode, :paren-mode, :none}`, defaults
41 | to `:none`.
42 | * `:mode-line?`: if `true`, an Emacs-like modeline will be displayed under the
43 | console.
44 | * `:eval-opts`: a map of evaluation options, which in turn contains:
45 |
46 | * `:get-prompt`: a zero-arity function, returns a string (the prompt
47 | displayed after each evaluation).
48 | * `:should-eval`: a predicate function that takes the source as input (the
49 | expression to evaluate) and returns true or false depending on whether
50 | it can be evaluated. If `false`, the cursor will be placed on a new line.
51 | * `to-str-fn`: a one-arity function that converts the result to a string.
52 | If the result is already a string simply use `identity`.
53 | * `evaluate`: the main evaluation function, takes as arguments a callback
54 | function (called after evaluation) and the source to evaluate.
55 | Will be called if `should-eval` returns `true`.
56 |
57 | The callback function will be called with a map as parameter containing the
58 | following keys:
59 |
60 | * `success?`: if `true` the evaluation has succeded
61 | * `result`: the result of the evaluation (as it is), can be a valid result or an
62 | error
63 | * `prev-ns`: a string indicating the namespace the evaluation took place in
64 | * `source`: the original source
65 |
66 | The whole state is kept under the `:consoles` keyword in the global state and
67 | you can nicely interact with it via re-frame
68 | [handlers](https://github.com/Lambda-X/re-console/blob/0.1.3/src/re_console/handlers.cljs)
69 | (for example for changing the evaluation options or clearing the history).
70 |
71 | ## Tests
72 |
73 | Run `boot test` or `boot auto-test`.
74 |
75 | ## CSS styling
76 |
77 | The re-console component needs a custom CSS file in order to be displayed
78 | correctly. Be sure to add it to your application. A template is found in
79 | [the demo](https://github.com/Lambda-X/re-console/blob/0.1.3/html/css/re-console.scss)
80 | It's a `scss` file but it's easily convertible to CSS manually or via
81 | [boot](https://github.com/Lambda-X/re-console/blob/0.1.3/build.boot#L80).
82 |
83 | ## Parinfer
84 |
85 | In order to take advantage of [parinfer](https://shaunlebron.github.io/parinfer/)
86 | you need to do two things:
87 |
88 | * include the appropriate JavaScript file in the the compilation set. To do this
89 | add the [:foreign-libs](https://github.com/Lambda-X/re-console/blob/0.1.3/build.boot#L69)
90 | value.
91 |
92 | * Switch to parinfer by invokating the `:set-console-mode` handler, passing as
93 | value `:indent-mode`. To disable parinfer, call the same handler with value `:none`.
94 |
95 | # Community
96 |
97 | Many thanks to jaredly's
98 | [reepl](https://github.com/jaredly/reepl), amasad's
99 | [jq-console](https://github.com/replit/jq-console) and hiram-madelaine's
100 | [codemirror-parinfer](https://github.com/hiram-madelaine/codemirror-parinfer)
101 | for inspiration.
102 |
103 | # License
104 |
105 | Copyright © 2016
106 |
107 | Distributed under the Eclipse Public License.
108 |
--------------------------------------------------------------------------------
/boot.properties:
--------------------------------------------------------------------------------
1 | BOOT_CLOJURE_NAME=org.clojure/clojure
2 | BOOT_CLOJURE_VERSION=1.7.0
3 | BOOT_VERSION=2.5.5
4 | BOOT_EMIT_TARGET=no
5 |
--------------------------------------------------------------------------------
/build.boot:
--------------------------------------------------------------------------------
1 | (set-env!
2 | :resource-paths #{"html" "demo" "src"}
3 | :dependencies '[[adzerk/boot-cljs "1.7.228-1" :scope "test"]
4 | [pandeiro/boot-http "0.7.1-SNAPSHOT" :scope "test"]
5 | [adzerk/boot-reload "0.4.5" :scope "test"]
6 | [degree9/boot-semver "1.2.4" :scope "test"]
7 | [adzerk/boot-cljs-repl "0.3.0" :scope "test"]
8 | [com.cemerick/piggieback "0.2.1" :scope "test"]
9 | [weasel "0.7.0" :scope "test"]
10 | [org.clojure/tools.nrepl "0.2.12" :scope "test"]
11 | [crisptrutski/boot-cljs-test "0.2.2-SNAPSHOT" :scope "test"]
12 | [org.clojars.stumitchell/clairvoyant "0.1.0-SNAPSHOT" :scope "test"]
13 | [day8/re-frame-tracer "0.1.0-SNAPSHOT" :scope "test"]
14 | [deraen/boot-sass "0.2.1" :scope "test"]
15 | [adzerk/bootlaces "0.1.13" :scope "test"]
16 | [org.clojure/clojure "1.7.0"]
17 | [org.clojure/clojurescript "1.7.228"]
18 | [reagent "0.5.0"]
19 | [re-frame "0.5.0"]
20 | [replumb/replumb "0.2.1"]
21 | [cljsjs/codemirror "5.10.0-0"]
22 | [parinfer "0.2.3"]])
23 |
24 | (require
25 | '[adzerk.boot-cljs :refer [cljs]]
26 | '[adzerk.boot-cljs-repl :refer [cljs-repl start-repl]]
27 | '[adzerk.boot-reload :refer [reload]]
28 | '[crisptrutski.boot-cljs-test :refer [test-cljs]]
29 | '[pandeiro.boot-http :refer [serve]]
30 | '[deraen.boot-sass :refer [sass]]
31 | '[boot-semver.core :refer :all]
32 | '[adzerk.bootlaces :refer :all])
33 |
34 | (def +version+ (get-version))
35 |
36 | (bootlaces! +version+)
37 |
38 | (task-options! pom {:project 're-console
39 | :version +version+
40 | :url "https://github.com/Lambda-X/re-console"
41 | :description "A reactive console based on re-frame"}
42 | test-cljs {:js-env :phantom
43 | :out-file "phantom-tests.js"})
44 |
45 | ;; This prevents a name collision WARNING between the test task and
46 | ;; clojure.core/test, a function that nobody really uses or cares
47 | ;; about.
48 | (ns-unmap 'boot.user 'test)
49 |
50 | (deftask version-file
51 | "A task that includes the version.properties file in the fileset."
52 | []
53 | (with-pre-wrap [fileset]
54 | (boot.util/info "Add version.properties...\n")
55 | (-> fileset
56 | (add-resource (java.io.File. ".") :include #{#"^version\.properties$"})
57 | commit!)))
58 |
59 | (deftask test []
60 | (merge-env! :source-paths #{"test"})
61 | (comp (speak)
62 | (test-cljs)))
63 |
64 | (deftask auto-test []
65 | (merge-env! :source-paths #{"test"})
66 | (comp (watch)
67 | (test)))
68 |
69 | (def foreign-libs
70 | [{:file "html/js/clojure-parinfer.js"
71 | :provides ["parinfer.codemirror.mode.clojure.clojure-parinfer"]}])
72 |
73 | (deftask dev []
74 | (comp (version-file)
75 | (serve)
76 | (watch)
77 | (speak)
78 | (cljs-repl)
79 | (reload :on-jsload 're-console.example/main)
80 | (sass)
81 | (cljs :optimizations :none
82 | :source-map true
83 | :compiler-options {:source-map-timestamp true
84 | :foreign-libs foreign-libs})))
85 |
86 | (deftask build []
87 | (merge-env! :source-paths #{"src" "demo"} :resource-paths #{"html"})
88 | (comp (version-file)
89 | (sass)
90 | (cljs :optimizations :advanced
91 | :compiler-options {:foreign-libs foreign-libs})))
92 |
--------------------------------------------------------------------------------
/demo/re_console/example.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.example
2 | (:require [re-console.replumb-proxy :as replumb-proxy]
3 | [re-console.core :as console]
4 | [re-frame.core :refer [dispatch subscribe]]
5 | [reagent.core :as reagent]
6 | [cljsjs.codemirror]
7 | [cljsjs.codemirror.addon.edit.matchbrackets]
8 | [cljsjs.codemirror.addon.runmode.runmode]
9 | [cljsjs.codemirror.addon.runmode.colorize]
10 | [cljsjs.codemirror.mode.clojure]
11 | [re-console.parinfer :as parinfer]
12 | [parinfer.codemirror.mode.clojure.clojure-parinfer]))
13 |
14 | (defonce console-key :cljs-console)
15 |
16 | (defn toggle-verbose []
17 | (let [verbose? (subscribe [:get-console-verbose])]
18 | (fn []
19 | [:div
20 | [:input.button-element
21 | {:type "button"
22 | :value "Toggle verbose"
23 | :on-click #(do (dispatch [:toggle-verbose])
24 | (dispatch [:set-console-eval-opts console-key
25 | (replumb-proxy/eval-opts (not @verbose?) ["/js/compiled/out"])]))}]
26 | [:span
27 | "Now is " [:strong (if (false? @verbose?) "false" "true")]]])))
28 |
29 | (defn toggle-parinfer []
30 | (let [mode (subscribe [:get-console-mode console-key])]
31 | (fn []
32 | [:div
33 | [:input.button-element
34 | {:type "button"
35 | :value "Toggle parinfer"
36 | :on-click #(let [new-mode (if (= @mode :none) :indent-mode :none)]
37 | (dispatch [:set-console-mode console-key new-mode]))}]
38 | [:span
39 | "Now is " [:strong (name @mode)]]])))
40 |
41 | (defn buttons
42 | []
43 | [:div.buttons-container
44 | [toggle-verbose]
45 | [toggle-parinfer]])
46 |
47 | (defn ^:export main []
48 | (enable-console-print!)
49 | (dispatch [:init-options])
50 | (reagent/render [console/console console-key {:eval-opts (replumb-proxy/eval-opts false ["/js/compiled/out"])
51 | :mode-line? true}]
52 | (.getElementById js/document "app"))
53 | (reagent/render [buttons]
54 | (.getElementById js/document "buttons")))
55 |
--------------------------------------------------------------------------------
/demo/re_console/example/handlers.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.example.handlers
2 | (:require [re-frame.core :refer [register-handler dispatch]]
3 | [clairvoyant.core :refer-macros [trace-forms]]
4 | [re-frame-tracer.core :refer [tracer]]))
5 |
6 | ;; (trace-forms {:tracer (tracer :color "green")}
7 |
8 | (register-handler
9 | :init-options
10 | (fn init-verbose [db [_]]
11 | (assoc-in db [:options :verbose] false)))
12 |
13 |
14 | (register-handler
15 | :toggle-verbose
16 | (fn toggle-verbose [db [_]]
17 | (update-in db [:options :verbose] not)))
18 |
19 | ;; )
20 |
--------------------------------------------------------------------------------
/demo/re_console/example/subs.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.example.subs
2 | (:require
3 | [re-frame.core :refer [register-sub]]
4 | [clairvoyant.core :refer-macros [trace-forms]]
5 | [re-frame-tracer.core :refer [tracer]])
6 | (:require-macros
7 | [reagent.ratom :refer [reaction]]))
8 |
9 | ;; (trace-forms {:tracer (tracer :color "brown")}
10 |
11 | (register-sub
12 | :get-console-verbose
13 | (fn [db [_]]
14 | (reaction (get-in @db [:options :verbose]))))
15 |
16 | ;; )
17 |
--------------------------------------------------------------------------------
/demo/re_console/io.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.io
2 | (:import [goog.events EventType]
3 | [goog.net XhrIo]))
4 |
5 | (defn fetch-file!
6 | "Very simple implementation of XMLHttpRequests that given a file path
7 | calls src-cb with the string fetched of nil in case of error.
8 |
9 | See doc at https://developers.google.com/closure/library/docs/xhrio"
10 | [file-url src-cb]
11 | (try
12 | (.send XhrIo file-url
13 | (fn [e]
14 | (if (.isSuccess (.-target e))
15 | (src-cb (.. e -target getResponseText))
16 | (src-cb nil))))
17 | (catch :default e
18 | (src-cb nil))))
19 |
--------------------------------------------------------------------------------
/demo/re_console/replumb_proxy.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.replumb-proxy
2 | (:require [replumb.core :as replumb]
3 | [replumb.repl :as replumb-repl]
4 | [re-console.io :as io]))
5 |
6 | (defn replumb-options
7 | [verbose? src-paths]
8 | (merge (replumb/options :browser src-paths io/fetch-file!)
9 | {:warning-as-error true
10 | :verbose verbose?}))
11 |
12 | (defn read-eval-call [opts cb source]
13 | (let [ns (replumb-repl/current-ns)]
14 | (replumb/read-eval-call opts
15 | #(cb {:success? (replumb/success? %)
16 | :result %
17 | :prev-ns ns
18 | :source source})
19 | source)))
20 |
21 | (defn multiline?
22 | [input]
23 | (try
24 | (replumb-repl/read-string {} input)
25 | false
26 | (catch :default e
27 | (= "EOF" (subs (.-message e) 0 3)))))
28 |
29 | (defn eval-opts
30 | [verbose src-path]
31 | {:get-prompt replumb/get-prompt
32 | :should-eval (complement multiline?)
33 | :to-str-fn (partial replumb/result->string false true)
34 | :evaluate (partial read-eval-call
35 | (replumb-options verbose src-path))})
36 |
--------------------------------------------------------------------------------
/html/css/main.css:
--------------------------------------------------------------------------------
1 | /* workaround for refresh issues in webkit-based browsers */
2 | @-webkit-keyframes androidBugfix {from { padding: 0; } to { padding: 0; }}
3 | body { -webkit-animation: androidBugfix infinite 1s; }
4 | /* end workaround */
5 |
6 | body, html {
7 | background-color: #E0E0E0;
8 | }
9 |
10 | .buttons-container {
11 | padding: 10px;
12 | }
13 |
14 | .button-element {
15 | margin-right: 5px;
16 | color: #fff !important;
17 | word-spacing: 0.25em;
18 | font-family: 'Open Sans', sans-serif;
19 | text-transform: uppercase;
20 | border: none;
21 | line-height: 22px;
22 | padding: 12px 13px 11px;
23 | text-align: center;
24 | display: inline-block;
25 | margin-top: 5px;
26 | margin-bottom: 18px;
27 | text-decoration: none;
28 | }
29 |
30 | #main {
31 | width: 80%;
32 | margin: 40px auto 0;
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/html/css/re-console.scss:
--------------------------------------------------------------------------------
1 | $background-color: #FFF;
2 | $font-size: 15px;
3 | $font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
4 |
5 | /* For status bar a the bottom *inside* see
6 | http://jsfiddle.net/AwH9S/ */
7 | /* .re-console-wrapper { */
8 | /* position: relative; */
9 | /* } */
10 |
11 | .re-console-container {
12 | background-color: $background-color;
13 | overflow-y: scroll;
14 | height: 400px;
15 | padding: 10px;
16 | border-top-left-radius: 10px;
17 | border-top-right-radius: 10px;
18 | -webkit-transform: translateZ(0);
19 | -webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%);
20 | }
21 |
22 | .re-console {
23 |
24 | .CodeMirror {
25 | height: auto;
26 | font-size: $font-size;
27 | font-family: $font-family;
28 | margin-bottom: 20px;
29 | }
30 |
31 | .CodeMirror-code {
32 | background-color: $background-color;
33 | }
34 | }
35 |
36 | .re-console .CodeMirror-lines, .re-console .CodeMirror pre {
37 | padding: 0;
38 | }
39 |
40 | .re-console-item, .re-console-item pre, .re-console-item-error pre {
41 | font-family: $font-family;
42 | font-size: $font-size;
43 | opacity: 0.7;
44 | background: transparent;
45 | border: none;
46 | margin: 0;
47 | padding: 0px;
48 | white-space: pre-wrap; /* CSS 3 */
49 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
50 | white-space: -pre-wrap; /* Opera 4-6 */
51 | white-space: -o-pre-wrap; /* Opera 7 */
52 | word-wrap: break-word;
53 | }
54 |
55 | .re-console-item-error {
56 | color: #e60000;
57 | }
58 |
59 | .re-console-mode-line {
60 | font-family: $font-family;
61 | font-size: 10px;
62 | text-align: center;
63 | background-color: #414141;
64 | color: white;
65 | padding: 10px;
66 | border-bottom-left-radius: 10px;
67 | border-bottom-right-radius: 10px;
68 | -webkit-transform: translateZ(0);
69 | -webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%);
70 | white-space: pre-wrap; /* CSS 3 */
71 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
72 | white-space: -pre-wrap; /* Opera 4-6 */
73 | white-space: -o-pre-wrap; /* Opera 7 */
74 | word-wrap: break-word;
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | re-console
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/html/js/clojure-parinfer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * To Parinfer developers,
3 | *
4 | * This is a syntax-highlighting mode for Clojure, copied from CodeMirror.
5 | * We modify it for Parinfer so that it dims the inferred parens at the end of a line.
6 | * (Search for Parinfer below for the relevant edit)
7 | *
8 | * For the purpose of extra-highlighting, we also modify it by tracking a previousToken
9 | * so we can highlight def'd symbols and symbols that are called to. Example:
10 | *
11 | * (def foo 123) (bar 123)
12 | * ^^^ ^^^
13 | * |------------------|------------- highlighted as 'def' token type
14 | *
15 | * This Clojure mode also has logic for where to indent the cursor when pressing enter.
16 | * We do not modify this.
17 | *
18 | */
19 |
20 |
21 |
22 |
23 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
24 | // Distributed under an MIT license: http://codemirror.net/LICENSE
25 |
26 | /**
27 | * Author: Hans Engel
28 | * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun)
29 | */
30 |
31 | (function(mod) {
32 | if (typeof exports == "object" && typeof module == "object") // CommonJS
33 | mod(require("../../lib/codemirror"));
34 | else if (typeof define == "function" && define.amd) // AMD
35 | define(["../../lib/codemirror"], mod);
36 | else // Plain browser env
37 | mod(CodeMirror);
38 | })(function(CodeMirror) {
39 | "use strict";
40 |
41 | CodeMirror.defineMode("clojure-parinfer", function (options) {
42 | var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", CHARACTER = "string-2",
43 | ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword", VAR = "variable",
44 | SOL = "sol", EOL = "eol", MOL = "mol", // close-paren styles
45 | DEF = "def";
46 | var INDENT_WORD_SKIP = options.indentUnit || 2;
47 | var NORMAL_INDENT_UNIT = options.indentUnit || 2;
48 |
49 | function makeKeywords(str) {
50 | var obj = {}, words = str.split(" ");
51 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
52 | return obj;
53 | }
54 |
55 | var atoms = makeKeywords("true false nil");
56 |
57 | var defs = makeKeywords(
58 | "defn defn- def defonce defmulti defmethod defmacro defstruct deftype ns");
59 |
60 | var keywords = makeKeywords(
61 | "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle");
62 |
63 | var builtins = makeKeywords(
64 | "* *' *1 *2 *3 *agent* *allow-unresolved-vars* *assert* *clojure-version* *command-line-args* *compile-files* *compile-path* *compiler-options* *data-readers* *e *err* *file* *flush-on-newline* *fn-loader* *in* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *unchecked-math* *use-context-classloader* *verbose-defrecords* *warn-on-reflection* + +' - -' -> ->> ->ArrayChunk ->Vec ->VecNode ->VecSeq -cache-protocol-fn -reset-methods .. / < <= = == > >= EMPTY-NODE accessor aclone add-classpath add-watch agent agent-error agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint biginteger binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* bound? butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec dec' decimal? declare default-data-readers definline definterface defmacro defmethod defmulti defn defn- defonce defprotocol defrecord defstruct deftype delay delay? deliver denominator deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq error-handler error-mode eval even? every-pred every? ex-data ex-info extend extend-protocol extend-type extenders extends? false? ffirst file-seq filter filterv find find-keyword find-ns find-protocol-impl find-protocol-method find-var first flatten float float-array float? floats flush fn fn? fnext fnil for force format frequencies future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator group-by hash hash-combine hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc inc' init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt keep keep-indexed key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map-indexed map? mapcat mapv max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod munge name namespace namespace-munge neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext nthrest num number? numerator object-array odd? or parents partial partition partition-all partition-by pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-dup print-method print-simple print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int rand-nth range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string realized? reduce reduce-kv reductions ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure reify release-pending-sends rem remove remove-all-methods remove-method remove-ns remove-watch repeat repeatedly replace replicate require reset! reset-meta! resolve rest restart-agent resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-error-handler! set-error-mode! set-validator! set? short short-array shorts shuffle shutdown-agents slurp some some-fn sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-symbol? spit split-at split-with str string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync take take-last take-nth take-while test the-ns thread-bound? time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-add-int unchecked-byte unchecked-char unchecked-dec unchecked-dec-int unchecked-divide-int unchecked-double unchecked-float unchecked-inc unchecked-inc-int unchecked-int unchecked-long unchecked-multiply unchecked-multiply-int unchecked-negate unchecked-negate-int unchecked-remainder-int unchecked-short unchecked-subtract unchecked-subtract-int underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector-of vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision with-redefs with-redefs-fn xml-seq zero? zipmap *default-data-reader-fn* as-> cond-> cond->> reduced reduced? send-via set-agent-send-executor! set-agent-send-off-executor! some-> some->>");
65 |
66 | var indentKeys = makeKeywords(
67 | // Built-ins
68 | "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " +
69 |
70 | // Binding forms
71 | "let letfn binding loop for doseq dotimes when-let if-let " +
72 |
73 | // Data structures
74 | "defstruct struct-map assoc " +
75 |
76 | // clojure.test
77 | "testing deftest " +
78 |
79 | // contrib
80 | "handler-case handle dotrace deftrace");
81 |
82 | var tests = {
83 | digit: /\d/,
84 | digit_or_colon: /[\d:]/,
85 | hex: /[0-9a-f]/i,
86 | sign: /[+-]/,
87 | exponent: /e/i,
88 | keyword_char: /[^\s\(\[\;\)\]]/,
89 | symbol: /[\w*+!\-\._?:<>\/\xa1-\uffff]/
90 | };
91 |
92 | function stateStack(indent, type, prev) { // represents a state stack object
93 | this.indent = indent;
94 | this.type = type;
95 | this.prev = prev;
96 | }
97 |
98 | function pushStack(state, indent, type) {
99 | state.indentStack = new stateStack(indent, type, state.indentStack);
100 | }
101 |
102 | function popStack(state) {
103 | state.indentStack = state.indentStack.prev;
104 | }
105 |
106 | function isNumber(ch, stream){
107 | // hex
108 | if ( ch === '0' && stream.eat(/x/i) ) {
109 | stream.eatWhile(tests.hex);
110 | return true;
111 | }
112 |
113 | // leading sign
114 | if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) {
115 | stream.eat(tests.sign);
116 | ch = stream.next();
117 | }
118 |
119 | if ( tests.digit.test(ch) ) {
120 | stream.eat(ch);
121 | stream.eatWhile(tests.digit);
122 |
123 | if ( '.' == stream.peek() ) {
124 | stream.eat('.');
125 | stream.eatWhile(tests.digit);
126 | }
127 |
128 | if ( stream.eat(tests.exponent) ) {
129 | stream.eat(tests.sign);
130 | stream.eatWhile(tests.digit);
131 | }
132 |
133 | return true;
134 | }
135 |
136 | return false;
137 | }
138 |
139 | // Eat character that starts after backslash \
140 | function eatCharacter(stream) {
141 | var first = stream.next();
142 | // Read special literals: backspace, newline, space, return.
143 | // Just read all lowercase letters.
144 | if (first && first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) {
145 | return;
146 | }
147 | // Read unicode character: \u1000 \uA0a1
148 | if (first === "u") {
149 | stream.match(/[0-9a-z]{4}/i, true);
150 | }
151 | }
152 |
153 | return {
154 | startState: function () {
155 | return {
156 | previousToken: null,
157 | indentStack: null,
158 | indentation: 0,
159 | mode: false,
160 | atStart: false
161 | };
162 | },
163 |
164 | token: function (stream, state) {
165 |
166 | if (stream.sol()) {
167 | state.atStart = (state.mode != "string");
168 | }
169 |
170 | if (state.indentStack == null && stream.sol()) {
171 | // update indentation, but only if indentStack is empty
172 | state.indentation = stream.indentation();
173 | }
174 |
175 | // skip spaces
176 | if (stream.eatSpace()) {
177 | return null;
178 | }
179 | var returnType = null;
180 | var previousToken = null;
181 |
182 | switch(state.mode){
183 | case "string": // multi-line string parsing mode
184 | var next, escaped = false;
185 | while ((next = stream.next()) != null) {
186 | if (next == "\"" && !escaped) {
187 |
188 | state.mode = false;
189 | break;
190 | }
191 | escaped = !escaped && next == "\\";
192 | }
193 | returnType = STRING; // continue on in string mode
194 | break;
195 | default: // default parsing mode
196 | var ch = stream.next();
197 |
198 | if (ch == "\"") {
199 | state.mode = "string";
200 | returnType = STRING;
201 | } else if (ch == "\\") {
202 | eatCharacter(stream);
203 | returnType = CHARACTER;
204 | } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) {
205 | returnType = ATOM;
206 | } else if (ch == ";") { // comment
207 | stream.skipToEnd(); // rest of the line is a comment
208 | returnType = COMMENT;
209 | } else if (isNumber(ch,stream)){
210 | returnType = NUMBER;
211 | } else if (ch == "(" || ch == "[" || ch == "{" ) {
212 | if (ch == "(") {
213 | previousToken = "(";
214 | }
215 |
216 | var keyWord = '', indentTemp = stream.column(), letter;
217 | /**
218 | Either
219 | (indent-word ..
220 | (non-indent-word ..
221 | (;something else, bracket, etc.
222 | */
223 |
224 | if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) {
225 | keyWord += letter;
226 | }
227 |
228 | if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) ||
229 | /^(?:def|with)/.test(keyWord))) { // indent-word
230 | pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
231 | } else { // non-indent word
232 | // we continue eating the spaces
233 | stream.eatSpace();
234 | if (stream.eol() || stream.peek() == ";") {
235 | // nothing significant after
236 | // we restart indentation the user defined spaces after
237 | pushStack(state, indentTemp + NORMAL_INDENT_UNIT, ch);
238 | } else {
239 | pushStack(state, indentTemp + stream.current().length, ch); // else we match
240 | }
241 | }
242 | stream.backUp(stream.current().length - 1); // undo all the eating
243 |
244 | returnType = BRACKET;
245 | } else if (ch == ")" || ch == "]" || ch == "}") {
246 | returnType = BRACKET;
247 |
248 | // Parinfer: (style trailing delimiters)
249 | stream.eatWhile(/[\s,\]})]/);
250 | if (stream.eol() || stream.peek() == ";") {
251 | returnType += " " + EOL;
252 | } else if (state.atStart) {
253 | returnType += " " + SOL;
254 | } else {
255 | returnType += " " + MOL;
256 | }
257 | stream.backUp(stream.current().length - 1);
258 |
259 | if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : (ch == "]" ? "[" :"{"))) {
260 | popStack(state);
261 | }
262 | } else if ( ch == ":" ) {
263 | stream.eatWhile(tests.symbol);
264 | return ATOM;
265 | } else {
266 | stream.eatWhile(tests.symbol);
267 |
268 | if (keywords && keywords.propertyIsEnumerable(stream.current())) {
269 | returnType = KEYWORD;
270 | } else if (builtins && builtins.propertyIsEnumerable(stream.current())) {
271 | returnType = BUILTIN;
272 | } else if (atoms && atoms.propertyIsEnumerable(stream.current())) {
273 | returnType = ATOM;
274 | } else if (state.previousToken == "def" || state.previousToken == "(") {
275 | returnType = DEF;
276 | } else {
277 | returnType = VAR;
278 | }
279 |
280 | if (state.previousToken == "(" && defs && defs.propertyIsEnumerable(stream.current())) {
281 | previousToken = "def";
282 | }
283 | }
284 |
285 | if (!(ch == ")" || ch == "]" || ch == "}")) {
286 | state.atStart = false;
287 | }
288 | }
289 |
290 | state.previousToken = previousToken;
291 |
292 | return returnType;
293 | },
294 |
295 | indent: function (state) {
296 | if (state.indentStack == null) return state.indentation;
297 | return state.indentStack.indent;
298 | },
299 |
300 | closeBrackets: {pairs: "()[]{}\"\""},
301 | lineComment: ";;"
302 | };
303 | });
304 |
305 | CodeMirror.defineMIME("text/x-clojure", "clojure");
306 |
307 | });
308 |
--------------------------------------------------------------------------------
/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lambda-X/re-console/a7a31046ba5066163d46ac586072c92b7414c52b/screenshot.gif
--------------------------------------------------------------------------------
/src/re_console/app.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.app)
2 |
3 | (def initial-console-state {:items []
4 | :hist-pos 0
5 | :queued-forms []
6 | :history [""]
7 | :cm-instance nil
8 | :eval-opts {}
9 | :mode :none
10 | ;; parinfer technical details
11 | :frame-updated? false
12 | :on-before-change nil
13 | :on-after-change nil})
14 |
15 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
16 | ;;; Getters ;;;
17 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
18 |
19 | (defn console
20 | [db k]
21 | (get-in db [:consoles (name k) :cm-instance]))
22 |
23 | (def console-created?
24 | "Was the console created? Returns a truey or falsey value."
25 | (comp not nil? console))
26 |
27 | (defn console-items
28 | [db k]
29 | (get-in db [:consoles (name k) :items]))
30 |
31 | (defn console-mode
32 | [db k]
33 | (get-in db [:consoles (name k) :mode]))
34 |
35 | (defn console-frame-updated
36 | [db k]
37 | (get-in db [:consoles (name k) :frame-updated?]))
38 |
39 | (defn console-history
40 | [db k]
41 | (get-in db [:consoles (name k) :history]))
42 |
43 | (defn console-history-pos
44 | [db k]
45 | (get-in db [:consoles (name k) :hist-pos]))
46 |
47 | (defn queued-forms
48 | [db k]
49 | (get-in db [:consoles (name k) :queued-forms]))
50 |
51 | (defn console-eval-opts
52 | [db k]
53 | (get-in db [:consoles (name k) :eval-opts]))
54 |
55 | (defn console-full-text
56 | [db k]
57 | (let [items (console-items db k)
58 | to-str-fn (:to-str-fn (console-eval-opts db k))]
59 | (apply str (interpose \newline (map (fn [item]
60 | (if-let [text (:text item)]
61 | (str (:ns item) "=> " text)
62 | (to-str-fn (:value item))))
63 | items)))))
64 |
65 | (defn console-on-before-change
66 | [db k]
67 | (get-in db [:consoles (name k) :on-before-change]))
68 |
69 | (defn console-on-after-change
70 | [db k]
71 | (get-in db [:consoles (name k) :on-after-change]))
72 |
73 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
74 | ;;; State modifiers ;;;
75 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
76 |
77 | (defn init-console
78 | [db k initial-user-state]
79 | (assoc-in db [:consoles (name k)] (merge initial-console-state initial-user-state)))
80 |
81 | (defn add-console-instance
82 | [db k instance]
83 | (assoc-in db [:consoles (name k) :cm-instance] instance))
84 |
85 | (defn set-console-eval-opts
86 | [db k eval-opts]
87 | (assoc-in db [:consoles (name k) :eval-opts] eval-opts))
88 |
89 | (defn set-console-mode [db console-key mode]
90 | (assoc-in db [:consoles (name console-key) :mode] mode))
91 |
92 | (defn set-console-frame-updated [db console-key value]
93 | (assoc-in db [:consoles (name console-key) :frame-updated?] value))
94 |
95 | (defn update-console-history
96 | [db k idx pos text]
97 | (update-in db [:consoles (name k) :history]
98 | (fn [current-history]
99 | (if (= pos 0)
100 | (assoc current-history idx text)
101 | (filterv seq (conj current-history text))))))
102 |
103 | (defn set-console-history-position
104 | [db k new-pos]
105 | (assoc-in db [:consoles (name k) :hist-pos] new-pos))
106 |
107 | (defn set-console-text
108 | [db k text]
109 | (let [history (console-history db k)
110 | pos (console-history-pos db k)
111 | idx (- (count history) pos 1)]
112 | (-> db
113 | (set-console-history-position k 0)
114 | (update-console-history k idx pos text))))
115 |
116 | (defn clear-console-items
117 | [db k]
118 | (-> db
119 | (assoc-in [:consoles (name k) :items] [])
120 | (set-console-text k "")))
121 |
122 | (defn reset-console-items
123 | [db k]
124 | (update-in db [:consoles (name k)]
125 | (fn [current-state]
126 | (merge current-state
127 | (select-keys initial-console-state [:items :hist-pos :history])))))
128 |
129 | (defn add-console-item
130 | [db k item]
131 | (update-in db [:consoles (name k) :items] conj item))
132 |
133 | (defn add-console-items
134 | [db k items]
135 | (update-in db [:consoles (name k) :items] concat items))
136 |
137 | (defn add-console-history-item
138 | [db k item]
139 | (update-in db [:consoles (name k) :history] conj item))
140 |
141 | (defn add-console-input-item
142 | [db k inum input ns]
143 | (update-in db [:consoles (name k) :items] conj {:type :input
144 | :text input
145 | :num inum
146 | :ns ns}))
147 |
148 | (defn add-console-input
149 | [db k input ns]
150 | (let [inum (count (console-history db k))]
151 | (add-console-input-item db k inum input ns)))
152 |
153 | (defn add-console-result
154 | [db k error? value]
155 | (update-in db [:consoles (name k) :items] conj {:type (if error? :error :output)
156 | :value value}))
157 |
158 | (defn add-console-log
159 | [db k item]
160 | (update-in db [:consoles (name k) :items] conj {:type :log
161 | :value item}))
162 |
163 | (defn console-go-up
164 | [db k]
165 | (let [pos (console-history-pos db k)
166 | len (count (console-history db k))
167 | new-pos (if (>= pos (dec len))
168 | pos
169 | (inc pos))]
170 | (set-console-history-position db k new-pos)))
171 |
172 | (defn console-go-down
173 | [db k]
174 | (update-in db [:consoles (name k) :hist-pos]
175 | (fn [pos] (if (<= pos 0)
176 | 0
177 | (dec pos)))))
178 |
179 | (defn clear-console-queued-forms
180 | [db k]
181 | (-> db
182 | (assoc-in [:consoles (name k) :queued-forms] [])
183 | (set-console-text k "")))
184 |
185 | (defn set-console-queued-forms
186 | [db k forms]
187 | (-> db
188 | (set-console-text k (first forms))
189 | (assoc-in [:consoles (name k) :queued-forms] (rest forms))))
190 |
191 | (defn drop-first-queued-form
192 | [db k]
193 | (update-in db [:consoles (name k) :queued-forms] (partial drop 1)))
194 |
195 | (defn set-next-queued-form-if-any
196 | [db k]
197 | (if-let [form (first (queued-forms db k))]
198 | (-> db
199 | (set-console-text k form)
200 | (drop-first-queued-form k))
201 | db))
202 |
203 | (defn on-eval-complete
204 | [db k {:keys [prev-ns source success? result]}]
205 | (let [db (if (seq source)
206 | (-> db
207 | (set-console-text k source)
208 | (set-console-history-position k 0)
209 | (add-console-history-item k ""))
210 | db)]
211 | (-> db
212 | (add-console-input k source prev-ns)
213 | (add-console-result k (not success?) result)
214 | (set-next-queued-form-if-any k))))
215 |
216 | (defn set-console-on-before-change
217 | [db k on-before-change]
218 | (assoc-in db [:consoles (name k) :on-before-change] on-before-change))
219 |
220 | (defn set-console-on-after-change
221 | [db k on-after-change]
222 | (assoc-in db [:consoles (name k) :on-after-change] on-after-change))
223 |
224 |
--------------------------------------------------------------------------------
/src/re_console/common.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.common)
2 |
3 | (defn beginning-of-source
4 | [exp]
5 | (let [idx (.indexOf exp "=> ")]
6 | (if (not= -1 idx)
7 | (+ 3 (.indexOf exp "=> "))
8 | 0)))
9 |
10 | (defn source-without-prompt
11 | [exp]
12 | exp
13 | (subs exp (beginning-of-source exp)))
14 |
15 | (defn scroll-to-el-bottom!
16 | [el]
17 | (set! (.-scrollTop el) (.-scrollHeight el)))
18 |
--------------------------------------------------------------------------------
/src/re_console/core.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.core
2 | (:require [reagent.core :as reagent :refer [atom]]
3 | [re-frame.core :refer [subscribe dispatch dispatch-sync]]
4 | [re-console.handlers :as handlers]
5 | [re-console.subs :as subs]
6 | [re-console.editor :as editor]
7 | [re-console.common :as common]
8 | [re-console.utils :as utils]))
9 |
10 | ;;; many parts are taken from jaredly's reepl
11 | ;;; https://github.com/jaredly/reepl
12 |
13 | (defn display-console-output-item
14 | ([console-key value]
15 | (display-console-output-item console-key value false))
16 | ([console-key value error?]
17 | [:div
18 | {:on-click #(dispatch [:focus-console-editor console-key])}
19 | [:div [:pre {:style {:margin 0 :padding 0}
20 | :class (str "re-console-item" (when error? " re-console-item-error"))}
21 | value]]]))
22 |
23 | (defn display-console-item
24 | [console-key to-str-fn item]
25 | (if-let [text (:text item)]
26 | [:div.re-console-item {:on-click #(do (dispatch [:console-set-text console-key text])
27 | (dispatch [:focus-console-editor console-key]))}
28 | [utils/colored-text (str (:ns item) "=> " text)]]
29 | (display-console-output-item console-key
30 | (to-str-fn (:value item))
31 | (= :error (:type item)))))
32 |
33 | (defn console-items
34 | ([console-key items]
35 | (console-items console-key items identity))
36 | ([console-key items to-str-fn]
37 | (into [:div {:on-click #(.stopPropagation %)}]
38 | (map (partial display-console-item console-key to-str-fn) items))))
39 |
40 | (defn mode-line
41 | [console-key]
42 | (let [mode (subscribe [:get-console-mode console-key])
43 | queued-forms-count (subscribe [:queued-forms-count console-key])]
44 | [:div.re-console-mode-line
45 | (str "Current mode: " (name @mode) " | "
46 | @queued-forms-count " form(s) in the evaluation queue")]))
47 |
48 | (defn console [console-key opts]
49 | (let [items (subscribe [:get-console-items console-key])
50 | text (subscribe [:get-console-current-text console-key])]
51 | (dispatch-sync [:init-console console-key opts])
52 | (reagent/create-class
53 | {:reagent-render
54 | (fn []
55 | [:div
56 | [:div.re-console-container
57 | {:on-click #(dispatch [:focus-console-editor console-key])}
58 | [:div.re-console
59 | [console-items console-key @items (-> opts :eval-opts :to-str-fn)]
60 | [editor/console-editor console-key text]]]
61 | (when (:mode-line? opts)
62 | [mode-line console-key])])
63 | :component-did-update
64 | (fn [this]
65 | (common/scroll-to-el-bottom! (.-firstChild (reagent/dom-node this))))})))
66 |
--------------------------------------------------------------------------------
/src/re_console/editor.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.editor
2 | (:require [reagent.core :as reagent]
3 | [re-frame.core :refer [dispatch subscribe dispatch-sync]]
4 | [re-console.common :as common]
5 | [re-console.parinfer :as parinfer]
6 | [cljsjs.codemirror]
7 | [cljsjs.codemirror.addon.edit.matchbrackets]
8 | [cljsjs.codemirror.addon.runmode.runmode]
9 | [cljsjs.codemirror.addon.runmode.colorize]
10 | [cljsjs.codemirror.mode.clojure]
11 | [parinfer.codemirror.mode.clojure.clojure-parinfer]))
12 |
13 | (defn default-cm-options
14 | [initial-prompt mode]
15 | {:lineNumbers false
16 | :viewportMargin js/Infinity
17 | :showCursorWhenSelecting true
18 | :lineWrapping true
19 | :matchBrackets true
20 | :autofocus true
21 | :extraKeys #js {"Shift-Enter" "newlineAndIndent"}
22 | :value initial-prompt
23 | :mode (if (= mode :none) "clojure" "clojure-parinfer")})
24 |
25 | (def default-cm-handlers
26 | {:should-go-up
27 | (fn [source inst]
28 | (let [pos (.getCursor inst)]
29 | (= 0 (.-line pos))))
30 |
31 | :should-go-down
32 | (fn [source inst]
33 | (let [pos (.getCursor inst)
34 | last-line (.lastLine inst)]
35 | (= last-line (.-line pos))))})
36 |
37 | (defn handlers [console-key]
38 | {:add-input #(dispatch [:add-console-input console-key %1 %2])
39 | :add-result #(dispatch [:add-console-result console-key %1 %2])
40 | :go-up #(dispatch [:console-go-up console-key %])
41 | :go-down #(dispatch [:console-go-down console-key %])
42 | :clear-items #(dispatch [:clear-console-items console-key %])
43 | :set-text #(dispatch [:console-set-text console-key %1])
44 | :add-log #(dispatch [:add-console-log console-key %])})
45 |
46 | (defn move-to-end
47 | [cm]
48 | (let [last-line (.lastLine cm)
49 | last-ch (count (.getLine cm last-line))]
50 | (.setCursor cm last-line last-ch)))
51 |
52 | (defn modifying-prompt?
53 | [inst key]
54 | (let [pos-to (.getCursor inst "to")
55 | pos-from (.getCursor inst "from")
56 | lno (.-line pos-to)
57 | cno-to (.-ch pos-to)
58 | cno-from (.-ch pos-from)
59 | compare-position-fn (if (= 8 key) <= <)
60 | begin-source (common/beginning-of-source (.getValue inst))
61 | same-pos (= pos-to pos-from)]
62 | (and ((complement #{37 38 39 40}) key)
63 | (zero? lno)
64 | (or (compare-position-fn cno-to begin-source)
65 | (and (not same-pos)
66 | (compare-position-fn cno-from (dec begin-source)))))))
67 |
68 | (defn on-viewport-change [this]
69 | (fn []
70 | (let [el (reagent/dom-node this)
71 | re-console (.-parentElement el)
72 | box (.-parentElement re-console)]
73 | (common/scroll-to-el-bottom! box))))
74 |
75 | (defn on-change [inst console-key text {:keys [set-text]} mode]
76 | (let [on-after-change (subscribe [:get-console-on-after-change console-key])
77 | on-before-change (subscribe [:get-console-on-before-change console-key])]
78 | (fn [inst evt]
79 | (when (= :none @mode)
80 | (let [value (common/source-without-prompt (.getValue inst))]
81 | (when-not (= value @text)
82 | (when @on-before-change
83 | ( @on-before-change inst evt))
84 | (set-text value)
85 | (when @on-after-change
86 | (@on-after-change inst evt))))))))
87 |
88 | (defn on-keydown [console-key
89 | {:keys [should-go-up should-go-down go-up go-down]}
90 | eval-opts]
91 | (fn [inst evt]
92 | (if (modifying-prompt? inst (.-keyCode evt))
93 | (.preventDefault evt)
94 | (case (.-keyCode evt)
95 | ;; enter
96 | 13 (let [source (common/source-without-prompt (.getValue inst))]
97 | (when (and (not (.-shiftKey evt)) ((:should-eval @eval-opts) source))
98 | (.preventDefault evt)
99 | ((:evaluate @eval-opts) #(dispatch [:on-eval-complete console-key %]) source)))
100 | ;; up
101 | 38 (let [source (common/source-without-prompt (.getValue inst))]
102 | (when (and (not (.-shiftKey evt))
103 | (should-go-up source inst))
104 | (.preventDefault evt)
105 | (go-up)))
106 | ;; down
107 | 40 (let [source (common/source-without-prompt (.getValue inst))]
108 | (when (and (not (.-shiftKey evt))
109 | (should-go-down source inst))
110 | (.preventDefault evt)
111 | (go-down)))
112 | :none))))
113 |
114 | (defn console-editor
115 | [console-key text]
116 | (let [cm (subscribe [:get-console console-key])
117 | handlers (merge (handlers console-key) default-cm-handlers)
118 | eval-opts (subscribe [:get-console-eval-opts console-key])
119 | mode (subscribe [:get-console-mode console-key])
120 | on-before-change (subscribe [:get-console-on-before-change console-key])]
121 | (reagent/create-class
122 | {:component-did-mount
123 | (fn [this]
124 | (let [el (reagent/dom-node this)
125 | inst (js/CodeMirror.
126 | el
127 | (clj->js (default-cm-options (str ((:get-prompt @eval-opts)) @text) @mode)))]
128 | (dispatch-sync [:add-console-instance console-key inst])
129 | (move-to-end inst)
130 | (.on inst "viewportChange" (on-viewport-change this))
131 | (.on inst "change" (on-change inst console-key text handlers mode))
132 | (.on inst "keydown" (on-keydown console-key handlers eval-opts))
133 | (parinfer/parinferize! inst console-key)))
134 | :component-did-update
135 | (fn [this old-argv]
136 | (when-not (= @text (common/source-without-prompt (.getValue @cm)))
137 | (let [cursor (.getCursor @cm)
138 | line-idx (.-line cursor)
139 | cursor-idx (.-ch cursor)]
140 | (.setValue @cm (str ((:get-prompt @eval-opts)) @text))
141 | (let [current-line (.getLine @cm (min (dec (.lineCount @cm)) line-idx))
142 | last-idx (loop [exclude-chars #{\( \[ \{ \) \] \} \space}]
143 | (if (seq exclude-chars)
144 | (let [char (first exclude-chars)
145 | char-index (.indexOf current-line char cursor-idx)]
146 | (if (= char-index -1)
147 | (recur (rest exclude-chars))
148 | char-index))
149 | (count current-line)))]
150 | (.setCursor @cm line-idx last-idx)))))
151 | :reagent-render
152 | (fn []
153 | @text
154 | [:div])})))
155 |
156 |
--------------------------------------------------------------------------------
/src/re_console/handlers.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.handlers
2 | (:require [re-frame.core :refer [register-handler dispatch]]
3 | [clairvoyant.core :refer-macros [trace-forms]]
4 | [re-frame-tracer.core :refer [tracer]]
5 | [re-console.app :as app]))
6 |
7 | ;; (trace-forms {:tracer (tracer :color "green")}
8 |
9 | (register-handler
10 | :init-console
11 | (fn add-console [db [_ console-key eval-opts]]
12 | (app/init-console db console-key eval-opts)))
13 |
14 | (register-handler
15 | :add-console-instance
16 | (fn add-console [db [_ console-key instance]]
17 | (app/add-console-instance db console-key instance)))
18 |
19 | (register-handler
20 | :focus-console-editor
21 | (fn focus-console-editor [db [_ console-key]]
22 | (when-let [instance (app/console db console-key)]
23 | (.focus instance))
24 | db))
25 |
26 | (register-handler
27 | :set-console-theme
28 | (fn set-console-theme [db [_ console-key theme]]
29 | (when-let [instance (app/console db console-key)]
30 | (.setOption instance "theme" theme))
31 | db))
32 |
33 | (register-handler
34 | :clear-console-items
35 | (fn clear-console-items [db [_ console-key]]
36 | (dispatch [:focus-console-editor console-key])
37 | (app/clear-console-items db console-key)))
38 |
39 | (register-handler
40 | :reset-console-items
41 | (fn reset-console-items [db [_ console-key]]
42 | (dispatch [:focus-console-editor console-key])
43 | (app/reset-console-items db console-key)))
44 |
45 | (register-handler
46 | :add-console-item
47 | (fn add-console-item [db [_ console-key item]]
48 | (app/add-console-item db console-key item)))
49 |
50 | (register-handler
51 | :add-console-items
52 | (fn add-console-items [db [_ console-key items]]
53 | (app/add-console-item db console-key items)))
54 |
55 | (register-handler
56 | :add-console-input
57 | (fn add-console-input [db [_ console-key input ns]]
58 | (app/add-console-input db console-key input ns)))
59 |
60 | (register-handler
61 | :add-console-result
62 | (fn add-console-result [db [_ console-key error? value]]
63 | (app/add-console-result db console-key error? value)))
64 |
65 | (register-handler
66 | :add-console-log
67 | (fn add-console-log [db [_ console-key item]]
68 | (app/add-console-log db console-key item)))
69 |
70 | (register-handler
71 | :console-set-text
72 | (fn console-set-text [db [_ console-key text]]
73 | (app/set-console-text db console-key text)))
74 |
75 | (register-handler
76 | :console-go-up
77 | (fn console-go-up [db [_ console-key]]
78 | (app/console-go-up db console-key)))
79 |
80 | (register-handler
81 | :console-go-down
82 | (fn console-go-down [db [_ console-key]]
83 | (app/console-go-down db console-key)))
84 |
85 | (register-handler
86 | :set-console-queued-forms
87 | (fn set-queued-forms [db [_ console-key forms]]
88 | (dispatch [:focus-console-editor console-key])
89 | (app/set-console-queued-forms db console-key forms)))
90 |
91 | (register-handler
92 | :clear-console-queued-forms
93 | (fn clear-console-queued-forms [db [_ console-key]]
94 | (dispatch [:focus-console-editor console-key])
95 | (app/clear-console-queued-forms db console-key)))
96 |
97 | (register-handler
98 | :on-eval-complete
99 | (fn on-eval-complete [db [_ console-key result-map]]
100 | (app/on-eval-complete db console-key result-map)))
101 |
102 | (register-handler
103 | :set-console-eval-opts
104 | (fn set-console-eval-opts [db [_ console-key eval-opts]]
105 | (app/set-console-eval-opts db console-key eval-opts)))
106 |
107 | (register-handler
108 | :set-console-mode
109 | (fn set-console-mode [db [_ console-key mode]]
110 | (dispatch [:focus-console-editor console-key])
111 | (app/set-console-mode db console-key mode)))
112 |
113 | (register-handler
114 | :set-console-frame-updated
115 | (fn set-console-frame-updated [db [_ console-key value]]
116 | (app/set-console-frame-updated db console-key value)))
117 |
118 | (register-handler
119 | :set-console-on-before-change
120 | (fn set-console-on-before-change [db [_ console-key on-before-change]]
121 | (app/set-console-on-before-change db console-key on-before-change)))
122 |
123 | (register-handler
124 | :set-console-on-after-change
125 | (fn set-console-on-after-change [db [_ console-key on-after-change]]
126 | (app/set-console-on-after-change db console-key on-after-change)))
127 |
128 | ;; )
129 |
--------------------------------------------------------------------------------
/src/re_console/parinfer.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.parinfer
2 | "Glues Parinfer's formatter to a CodeMirror editor"
3 | (:require [clojure.string :refer [join]]
4 | [re-frame.core :refer [subscribe dispatch dispatch-sync]]
5 | [parinfer.indent-mode :as indent-mode]
6 | [parinfer.paren-mode :as paren-mode]
7 | [re-console.common :as common]))
8 |
9 | ;;; Editor support
10 |
11 | (defn update-cursor-fn
12 | "Correctly position cursor after text that was just typed.
13 | We need this since reformatting the text can shift things forward past our cursor."
14 | [console-key]
15 | (let [cm (subscribe [:get-console console-key])]
16 | (fn [change]
17 | (when (= "+input" (.-origin change))
18 | (let [selection? (.somethingSelected @cm)
19 | text (join "\n" (.-text change))
20 | from-x (.. change -from -ch)
21 | line-no (.. change -from -line)
22 | line (.getLine @cm line-no)
23 | insert-x (.indexOf line text from-x)
24 | after-x (+ insert-x (count text))]
25 | (cond
26 | ;; something is selected, don't touch the cursor
27 | selection?
28 | nil
29 |
30 | ;; pressing return, keep current position then.
31 | (= text "\n")
32 | nil
33 |
34 | ;; only move the semicolon ahead since it can be pushed forward by
35 | ;; commenting out inferred parens meaning they are immediately
36 | ;; reinserted behind it.
37 | (= text ";")
38 | (.setCursor @cm line-no after-x)
39 |
40 | ;; typed character not found where expected it, we probably prevented it. keep cursor where it was.
41 | (or (= -1 insert-x)
42 | (> insert-x from-x))
43 | (.setCursor @cm line-no from-x)
44 |
45 | :else nil))))))
46 |
47 | (defn compute-cursor-dx
48 | [cursor change]
49 | (when change
50 | (let [;; This is a hack for codemirror.
51 | ;; For some reason codemirror triggers an "+input" change after the
52 | ;; indent spaces are already applied. So I modified codemirror to
53 | ;; label these changes as +indenthack so we can ignore them.
54 | ignore? (= "+indenthack" (.-origin change))]
55 | (if ignore?
56 | 0
57 | (let [start-x (.. change -to -ch)
58 | new-lines (.. change -text)
59 | len-last-line (count (last new-lines))
60 | end-x (if (> (count new-lines) 1)
61 | len-last-line
62 | (+ len-last-line (.. change -from -ch)))]
63 | (- end-x start-x))))))
64 |
65 | (defn compute-cm-change
66 | [cm change options prev-state]
67 | (let [{:keys [start-line end-line num-new-lines]}
68 | (if change
69 | {:start-line (.. change -from -line)
70 | :end-line (inc (.. change -to -line))
71 | :num-new-lines (alength (.-text change))}
72 |
73 | (let [start (:cursor-line prev-state)
74 | end (inc start)]
75 | {:start-line start
76 | :end-line end
77 | :num-new-lines (- end start)}))
78 |
79 | lines (for [i (range start-line (+ start-line num-new-lines))]
80 | (.getLine cm i))]
81 | {:line-no [start-line end-line]
82 | :new-line lines}))
83 |
84 | (defn fix-text-fn
85 | "Correctly format the text from the given editor."
86 | [console-key]
87 | (let [cm (subscribe [:get-console console-key])
88 | mode (subscribe [:get-console-mode console-key])
89 | eval-opts (subscribe [:get-console-eval-opts console-key])
90 | prev-state (atom nil)]
91 | (fn [& {:keys [change use-cache?]
92 | :or {change nil, use-cache? false}}]
93 | (let [current-text (.getValue @cm)
94 | selection? (.somethingSelected @cm)
95 | selections (.listSelections @cm)
96 | cursor (.getCursor @cm)
97 | scroller (.getScrollerElement @cm)
98 | scroll-x (.-scrollLeft scroller)
99 | scroll-y (.-scrollTop scroller)
100 | options {:cursor-line (.-line cursor)
101 | :cursor-x (.-ch cursor)
102 | :cursor-dx (compute-cursor-dx cursor change)}
103 | new-text (case @mode
104 | :indent-mode
105 | (let [result (if (and use-cache? @prev-state)
106 | (indent-mode/format-text-change
107 | current-text
108 | @prev-state
109 | (compute-cm-change @cm change options @prev-state)
110 | options)
111 | (indent-mode/format-text current-text options))]
112 | (when (:valid? result)
113 | (reset! prev-state (:state result)))
114 | (:text result))
115 | :paren-mode
116 | (let [result (paren-mode/format-text current-text options)]
117 | (:text result))
118 | nil)]
119 |
120 | (dispatch [:console-set-text console-key (common/source-without-prompt new-text)])
121 | (.setValue @cm (str ((:get-prompt @eval-opts)) (common/source-without-prompt new-text)))
122 |
123 | ;; restore the selection, cursor, and scroll
124 | ;; since these are reset when overwriting codemirror's value.
125 | (if selection?
126 | (.setSelections @cm selections)
127 | (.setCursor @cm cursor))
128 | (.scrollTo @cm scroll-x scroll-y)))))
129 |
130 | ;; NOTE:
131 | ;; Text is either updated after a change in text or
132 | ;; a cursor movement, but not both.
133 | ;; When typing, on-change is called, then on-cursor-activity.
134 | ;; So we prevent updating the text twice by using an update flag.
135 |
136 | (defn before-change
137 | "Called before any change is applied to the editor."
138 | [cm change]
139 | ;; keep CodeMirror from reacting to a change from "setValue"
140 | ;; if it is not a new value.
141 | (when (and (= "setValue" (.-origin change))
142 | (= (.getValue cm) (join "\n" (.-text change))))
143 | (.cancel change)))
144 |
145 | (defn on-change
146 | "Called after any change is applied to the editor."
147 | [console-key]
148 | (let [fix-text! (fix-text-fn console-key)
149 | update-cursor! (update-cursor-fn console-key)
150 | mode (subscribe [:get-console-mode console-key])
151 | on-before-change (subscribe [:get-console-on-before-change console-key])
152 | on-after-change (subscribe [:get-console-on-after-change console-key])]
153 | (fn [inst change]
154 | (when-not (= :none @mode)
155 | (when (not= "setValue" (.-origin change))
156 | (when @on-before-change
157 | (@on-before-change inst change))
158 | (fix-text! :change change)
159 | (update-cursor! change)
160 | (dispatch-sync [:set-console-frame-updated console-key true])
161 | (when @on-after-change
162 | (@on-after-change inst change)))))))
163 |
164 | (defn on-cursor-activity
165 | "Called after the cursor moves in the editor."
166 | [console-key]
167 | (let [frame-updated? (subscribe [:get-console-frame-updated console-key])
168 | fix-text! (fix-text-fn console-key)
169 | mode (subscribe [:get-console-mode console-key])
170 | on-before-change (subscribe [:get-console-on-before-change console-key])
171 | on-after-change (subscribe [:get-console-on-after-change console-key])]
172 | (fn [inst evt]
173 | (when-not (= :none @mode)
174 | (when-not @frame-updated?
175 | (when @on-before-change
176 | (@on-before-change inst evt))
177 | (fix-text!)
178 | (when @on-after-change
179 | (@on-after-change inst evt)))
180 | (dispatch-sync [:set-console-frame-updated console-key false])))))
181 |
182 | (defn parinferize!
183 | "Add parinfer goodness to a codemirror editor"
184 | [cm console-key]
185 | (.on cm "change" (on-change console-key))
186 | (.on cm "beforeChange" before-change))
187 |
--------------------------------------------------------------------------------
/src/re_console/subs.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.subs
2 | (:require
3 | [re-frame.core :refer [register-sub]]
4 | [clairvoyant.core :refer-macros [trace-forms]]
5 | [re-frame-tracer.core :refer [tracer]]
6 | [re-console.app :as app])
7 | (:require-macros
8 | [reagent.ratom :refer [reaction]]))
9 |
10 | ;; (trace-forms {:tracer (tracer :color "brown")}
11 |
12 | (register-sub
13 | :console-created?
14 | (fn [db [_ console-key]]
15 | (reaction (app/console-created? @db console-key))))
16 |
17 | (register-sub
18 | :get-console-items
19 | (fn [db [_ console-key]]
20 | (reaction (app/console-items @db console-key))))
21 |
22 | (register-sub
23 | :get-console-mode
24 | (fn [db [_ console-key]]
25 | (reaction (app/console-mode @db console-key))))
26 |
27 | (register-sub
28 | :get-console-frame-updated
29 | (fn [db [_ console-key]]
30 | (reaction (app/console-frame-updated @db console-key))))
31 |
32 | (register-sub
33 | :get-console-current-text
34 | (fn [db [_ console-key]]
35 | (let [idx (reaction (get-in @db [:consoles (name console-key) :hist-pos]))
36 | history (reaction (get-in @db [:consoles (name console-key) :history]))]
37 | (reaction (let [items @history
38 | pos (- (count items) @idx 1)]
39 | (get items pos))))))
40 |
41 | (register-sub
42 | :get-console
43 | (fn [db [_ console-key]]
44 | (reaction (app/console @db console-key))))
45 |
46 | (register-sub
47 | :queued-forms-count
48 | (fn [db [_ console-key]]
49 | (reaction (count (app/queued-forms @db console-key)))))
50 |
51 | (register-sub
52 | :queued-forms-empty?
53 | (fn [db [_ console-key]]
54 | (reaction (not (empty? (app/queued-forms @db console-key))))))
55 |
56 | (register-sub
57 | :get-console-eval-opts
58 | (fn [db [_ console-key]]
59 | (reaction (app/console-eval-opts @db console-key))))
60 |
61 | (register-sub
62 | :get-console-on-before-change
63 | (fn [db [_ console-key]]
64 | (reaction (app/console-on-before-change @db console-key))))
65 |
66 | (register-sub
67 | :get-console-on-after-change
68 | (fn [db [_ console-key]]
69 | (reaction (app/console-on-after-change @db console-key))))
70 |
71 | (register-sub
72 | :get-console-full-text
73 | (fn [db [_ console-key]]
74 | (reaction (app/console-full-text @db console-key))))
75 |
76 | ;; )
77 |
--------------------------------------------------------------------------------
/src/re_console/utils.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.utils
2 | (:require [reagent.core :as reagent]))
3 |
4 | (defn colored-text [text style]
5 | (reagent/create-class
6 | {:component-did-mount
7 | (fn [this]
8 | (let [node (reagent/dom-node this)]
9 | ((aget js/CodeMirror "colorize") #js[node] "clojure")))
10 | :reagent-render
11 | (fn [_]
12 | [:pre {:style (merge {:padding 0 :margin 0} style)}
13 | text])}))
14 |
--------------------------------------------------------------------------------
/test/re_console/app_test.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.app-test
2 | (:require [re-console.app :as app]
3 | [cljs.test :refer-macros [deftest is async]]
4 | [re-console.replumb-proxy :as replumb-proxy]))
5 |
6 | ;; Or doo will exit with an error, see:
7 | ;; https://github.com/bensu/doo/issues/83#issuecomment-165498172
8 | (set! (.-error js/console) (fn [x] (.log js/console x)))
9 |
10 | (def db (atom {:consoles {}}))
11 |
12 | (def console-key :cljs-console)
13 |
14 | (def eval-opts (replumb-proxy/eval-opts false ["/js/compiled/out"]))
15 |
16 | (defn submit
17 | [db k source]
18 | (let [evaluate (:evaluate eval-opts)]
19 | (evaluate (partial app/on-eval-complete db k)
20 | source)))
21 |
22 | (deftest initial-configuration
23 | (let [created? (app/console-created? @db console-key)]
24 | (is (= false created?) "The console should be nil"))
25 |
26 | (let [db (-> @db
27 | (app/init-console console-key eval-opts)
28 | (app/add-console-instance console-key {:placeholder "js-object"}))
29 | created? (app/console-created? db console-key)
30 | items (app/console-items db console-key)
31 | queued-forms (app/queued-forms db console-key)
32 | history (app/console-history db console-key)
33 | hist-pos (app/console-history-pos db console-key)]
34 | (is (= true created?) "The console should be created")
35 | (is (empty? items) "The items should be empty")
36 | (is (empty? queued-forms) "The queued forms should be empty")
37 | (is (= (-> history count) 1) "The history should contain an empty string")
38 | (is (= (-> history first) "") "The first and only item should be an empty string")
39 | (is (= 0 hist-pos) "The history position should be 0")))
40 |
41 | (deftest evaluate
42 | (let [db (-> @db
43 | (app/init-console console-key eval-opts)
44 | (app/add-console-instance console-key {:placeholder "js-object"})
45 | (submit console-key "123")
46 | (submit console-key "(inc 10)"))
47 | items (app/console-items db console-key)
48 | history (app/console-history db console-key)
49 | hist-pos (app/console-history-pos db console-key)]
50 | (is (= 4 (-> items count)) "The items should contain 4 elements (2 input, 2 output)")
51 | (is (= 3 (-> history count)) "The history should contain 3 items (2 previous, 1 current)")
52 | (is (= (-> items first :type) :input) "The first item should be of type :input")
53 | (is (= (-> items second :type) :output) "The second item should be of type :output")))
54 |
--------------------------------------------------------------------------------
/test/re_console/example_test.cljs:
--------------------------------------------------------------------------------
1 | (ns re-console.example-test
2 | (:require [cljs.test :refer-macros [deftest testing is]]))
3 |
4 | (deftest silly-test
5 | (is (= (+ 1 1)
6 | 2)))
7 |
--------------------------------------------------------------------------------
/version.properties:
--------------------------------------------------------------------------------
1 | VERSION=0.1.4-SNAPSHOT
2 |
--------------------------------------------------------------------------------