├── .circleci
└── config.yml
├── .gitignore
├── LICENSE
├── README.md
├── deploy.sh
├── project.clj
├── resources
└── public
│ ├── css
│ └── site.css
│ ├── images
│ ├── cljs-white.svg
│ └── cljs.svg
│ ├── index.html
│ └── manifest.json
├── src-backend
├── cljs-dev
│ └── cljs_webrepl
│ │ └── backend.cljs
├── cljs-prod
│ └── cljs_webrepl
│ │ └── backend.cljs
└── cljs
│ └── cljs_webrepl
│ ├── io.cljs
│ └── repl.cljs
├── src-frontend
├── cljs-dev
│ └── cljs_webrepl
│ │ └── frontend.cljs
├── cljs-prod
│ └── cljs_webrepl
│ │ ├── frontend.cljs
│ │ └── prod.cljs
└── cljs
│ └── cljs_webrepl
│ ├── core.cljs
│ ├── editor.cljs
│ └── io.cljs
├── src-shared
├── cljs
│ └── cljs_webrepl
│ │ └── repl_thread.cljs
└── sass
│ ├── index.sass
│ └── profile.scss
└── test
└── cljs
└── cljs_webrepl
├── core_test.cljs
└── doo_runner.cljs
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Clojure CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-clojure/ for more details
4 | #
5 | version: 2
6 | jobs:
7 | build:
8 | docker:
9 | # specify the version you desire here
10 | - image: circleci/clojure:lein-2.7.1
11 |
12 | # Specify service dependencies here if necessary
13 | # CircleCI maintains a library of pre-built images
14 | # documented at https://circleci.com/docs/2.0/circleci-images/
15 | # - image: circleci/postgres:9.4
16 |
17 | working_directory: ~/app
18 |
19 | environment:
20 | LEIN_ROOT: "true"
21 | # Customize the JVM maximum heap limit
22 | JVM_OPTS: -Xmx3200m
23 | LEIN_FAST_TRAMPOLINE: yes
24 |
25 | steps:
26 | - checkout
27 |
28 | # Download and cache dependencies
29 | - restore_cache:
30 | keys:
31 | - v1-dependencies-{{ checksum "project.clj" }}
32 | # fallback to using the latest cache if no exact match is found
33 | - v1-dependencies-
34 |
35 | - run: lein do deps
36 |
37 | - save_cache:
38 | paths:
39 | - ~/.m2
40 | - ~/app/node_modules
41 | key: v1-dependencies-{{ checksum "project.clj" }}
42 |
43 | - run: lein build-min
44 | - run: ./deploy.sh
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | profiles.clj
5 | pom.xml
6 | pom.xml.asc
7 | *.jar
8 | *.class
9 | /.lein-*
10 | /.nrepl-port
11 | /resources/public/js
12 | /out
13 | /.repl
14 | *.log
15 | /.env
16 | /resources/public/css/site.min.css
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
4 |
5 | 1. DEFINITIONS
6 |
7 | "Contribution" means:
8 |
9 | a) in the case of the initial Contributor, the initial code and
10 | documentation distributed under this Agreement, and
11 |
12 | b) in the case of each subsequent Contributor:
13 |
14 | i) changes to the Program, and
15 |
16 | ii) additions to the Program;
17 |
18 | where such changes and/or additions to the Program originate from and are
19 | distributed by that particular Contributor. A Contribution 'originates' from
20 | a Contributor if it was added to the Program by such Contributor itself or
21 | anyone acting on such Contributor's behalf. Contributions do not include
22 | additions to the Program which: (i) are separate modules of software
23 | distributed in conjunction with the Program under their own license
24 | agreement, and (ii) are not derivative works of the Program.
25 |
26 | "Contributor" means any person or entity that distributes the Program.
27 |
28 | "Licensed Patents" mean patent claims licensable by a Contributor which are
29 | necessarily infringed by the use or sale of its Contribution alone or when
30 | combined with the Program.
31 |
32 | "Program" means the Contributions distributed in accordance with this
33 | Agreement.
34 |
35 | "Recipient" means anyone who receives the Program under this Agreement,
36 | including all Contributors.
37 |
38 | 2. GRANT OF RIGHTS
39 |
40 | a) Subject to the terms of this Agreement, each Contributor hereby grants
41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
42 | reproduce, prepare derivative works of, publicly display, publicly perform,
43 | distribute and sublicense the Contribution of such Contributor, if any, and
44 | such derivative works, in source code and object code form.
45 |
46 | b) Subject to the terms of this Agreement, each Contributor hereby grants
47 | Recipient a non-exclusive, worldwide, royalty-free patent license under
48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
49 | transfer the Contribution of such Contributor, if any, in source code and
50 | object code form. This patent license shall apply to the combination of the
51 | Contribution and the Program if, at the time the Contribution is added by the
52 | Contributor, such addition of the Contribution causes such combination to be
53 | covered by the Licensed Patents. The patent license shall not apply to any
54 | other combinations which include the Contribution. No hardware per se is
55 | licensed hereunder.
56 |
57 | c) Recipient understands that although each Contributor grants the licenses
58 | to its Contributions set forth herein, no assurances are provided by any
59 | Contributor that the Program does not infringe the patent or other
60 | intellectual property rights of any other entity. Each Contributor disclaims
61 | any liability to Recipient for claims brought by any other entity based on
62 | infringement of intellectual property rights or otherwise. As a condition to
63 | exercising the rights and licenses granted hereunder, each Recipient hereby
64 | assumes sole responsibility to secure any other intellectual property rights
65 | needed, if any. For example, if a third party patent license is required to
66 | allow Recipient to distribute the Program, it is Recipient's responsibility
67 | to acquire that license before distributing the Program.
68 |
69 | d) Each Contributor represents that to its knowledge it has sufficient
70 | copyright rights in its Contribution, if any, to grant the copyright license
71 | set forth in this Agreement.
72 |
73 | 3. REQUIREMENTS
74 |
75 | A Contributor may choose to distribute the Program in object code form under
76 | its own license agreement, provided that:
77 |
78 | a) it complies with the terms and conditions of this Agreement; and
79 |
80 | b) its license agreement:
81 |
82 | i) effectively disclaims on behalf of all Contributors all warranties and
83 | conditions, express and implied, including warranties or conditions of title
84 | and non-infringement, and implied warranties or conditions of merchantability
85 | and fitness for a particular purpose;
86 |
87 | ii) effectively excludes on behalf of all Contributors all liability for
88 | damages, including direct, indirect, special, incidental and consequential
89 | damages, such as lost profits;
90 |
91 | iii) states that any provisions which differ from this Agreement are offered
92 | by that Contributor alone and not by any other party; and
93 |
94 | iv) states that source code for the Program is available from such
95 | Contributor, and informs licensees how to obtain it in a reasonable manner on
96 | or through a medium customarily used for software exchange.
97 |
98 | When the Program is made available in source code form:
99 |
100 | a) it must be made available under this Agreement; and
101 |
102 | b) a copy of this Agreement must be included with each copy of the Program.
103 |
104 | Contributors may not remove or alter any copyright notices contained within
105 | the Program.
106 |
107 | Each Contributor must identify itself as the originator of its Contribution,
108 | if any, in a manner that reasonably allows subsequent Recipients to identify
109 | the originator of the Contribution.
110 |
111 | 4. COMMERCIAL DISTRIBUTION
112 |
113 | Commercial distributors of software may accept certain responsibilities with
114 | respect to end users, business partners and the like. While this license is
115 | intended to facilitate the commercial use of the Program, the Contributor who
116 | includes the Program in a commercial product offering should do so in a
117 | manner which does not create potential liability for other Contributors.
118 | Therefore, if a Contributor includes the Program in a commercial product
119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend
120 | and indemnify every other Contributor ("Indemnified Contributor") against any
121 | losses, damages and costs (collectively "Losses") arising from claims,
122 | lawsuits and other legal actions brought by a third party against the
123 | Indemnified Contributor to the extent caused by the acts or omissions of such
124 | Commercial Contributor in connection with its distribution of the Program in
125 | a commercial product offering. The obligations in this section do not apply
126 | to any claims or Losses relating to any actual or alleged intellectual
127 | property infringement. In order to qualify, an Indemnified Contributor must:
128 | a) promptly notify the Commercial Contributor in writing of such claim, and
129 | b) allow the Commercial Contributor tocontrol, and cooperate with the
130 | Commercial Contributor in, the defense and any related settlement
131 | negotiations. The Indemnified Contributor may participate in any such claim
132 | at its own expense.
133 |
134 | For example, a Contributor might include the Program in a commercial product
135 | offering, Product X. That Contributor is then a Commercial Contributor. If
136 | that Commercial Contributor then makes performance claims, or offers
137 | warranties related to Product X, those performance claims and warranties are
138 | such Commercial Contributor's responsibility alone. Under this section, the
139 | Commercial Contributor would have to defend claims against the other
140 | Contributors related to those performance claims and warranties, and if a
141 | court requires any other Contributor to pay any damages as a result, the
142 | Commercial Contributor must pay those damages.
143 |
144 | 5. NO WARRANTY
145 |
146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
151 | appropriateness of using and distributing the Program and assumes all risks
152 | associated with its exercise of rights under this Agreement , including but
153 | not limited to the risks and costs of program errors, compliance with
154 | applicable laws, damage to or loss of data, programs or equipment, and
155 | unavailability or interruption of operations.
156 |
157 | 6. DISCLAIMER OF LIABILITY
158 |
159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
166 | OF SUCH DAMAGES.
167 |
168 | 7. GENERAL
169 |
170 | If any provision of this Agreement is invalid or unenforceable under
171 | applicable law, it shall not affect the validity or enforceability of the
172 | remainder of the terms of this Agreement, and without further action by the
173 | parties hereto, such provision shall be reformed to the minimum extent
174 | necessary to make such provision valid and enforceable.
175 |
176 | If Recipient institutes patent litigation against any entity (including a
177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
178 | (excluding combinations of the Program with other software or hardware)
179 | infringes such Recipient's patent(s), then such Recipient's rights granted
180 | under Section 2(b) shall terminate as of the date such litigation is filed.
181 |
182 | All Recipient's rights under this Agreement shall terminate if it fails to
183 | comply with any of the material terms or conditions of this Agreement and
184 | does not cure such failure in a reasonable period of time after becoming
185 | aware of such noncompliance. If all Recipient's rights under this Agreement
186 | terminate, Recipient agrees to cease use and distribution of the Program as
187 | soon as reasonably practicable. However, Recipient's obligations under this
188 | Agreement and any licenses granted by Recipient relating to the Program shall
189 | continue and survive.
190 |
191 | Everyone is permitted to copy and distribute copies of this Agreement, but in
192 | order to avoid inconsistency the Agreement is copyrighted and may only be
193 | modified in the following manner. The Agreement Steward reserves the right to
194 | publish new versions (including revisions) of this Agreement from time to
195 | time. No one other than the Agreement Steward has the right to modify this
196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
197 | Eclipse Foundation may assign the responsibility to serve as the Agreement
198 | Steward to a suitable separate entity. Each new version of the Agreement will
199 | be given a distinguishing version number. The Program (including
200 | Contributions) may always be distributed subject to the version of the
201 | Agreement under which it was received. In addition, after a new version of
202 | the Agreement is published, Contributor may elect to distribute the Program
203 | (including its Contributions) under the new version. Except as expressly
204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
205 | licenses to the intellectual property of any Contributor under this
206 | Agreement, whether expressly, by implication, estoppel or otherwise. All
207 | rights in the Program not expressly granted under this Agreement are
208 | reserved.
209 |
210 | This Agreement is governed by the laws of the State of New York and the
211 | intellectual property laws of the United States of America. No party to this
212 | Agreement will bring a legal action under this Agreement more than one year
213 | after the cause of action arose. Each party waives its rights to a jury trial
214 | in any resulting litigation.
215 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CLJS-WebREPL
2 |
3 | An attempt at a nice looking in browser REPL for ClojureScript. Try out the [live version](http://theasp.github.io/cljs-webrepl/)!
4 |
5 | # Thanks
6 | - Joel Martin and David Nolen for the cljs-bootstrap REPL bits:
7 | - https://github.com/kanaka/cljs-bootstrap
8 | - https://github.com/swannodette/cljs-bootstrap
9 | - Mike Fikes for the parts of the REPL taken from Planck
10 | - https://github.com/mfikes/planck
11 | - Dan Holmsand for the syntax hilighting, from the Reagent demo:
12 | - https://github.com/reagent-project/reagent/tree/master/demo/reagentdemo
13 |
14 | *PULL REQUESTS WELCOME!*
15 |
16 | # License
17 |
18 | Copyright © 2016 Andrew Phillips, Dan Holmsand, Mike Fikes, David Nolen, Rich Hickey, Joel Martin & Contributors
19 |
20 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
21 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -ex
4 |
5 | # Variables
6 | ORIGIN_URL=`git config --get remote.origin.url`
7 | GOOGLETAG="
8 |
9 |
11 |
16 | "
17 |
18 | # Set identity
19 | git config user.name "Automated Deployment"
20 | git config user.email "auto@example.com"
21 |
22 | # Delete existing gh-pages branch
23 | if git branch | grep -q gh-pages; then
24 | git branch -D gh-pages
25 | fi
26 |
27 | # Make new gh-pages branch
28 | git checkout --orphan gh-pages
29 |
30 | # Move everything to .dist
31 | rm -rf .dist
32 | mkdir .dist
33 | mv * .dist
34 |
35 | # Move what we want into place
36 | mv .dist/resources/public/* .
37 | mv .dist/target/cljsbuild/public/js .
38 | for i in js/*.min.js; do
39 | mv $i ${i%.min.js}.js
40 | done
41 |
42 | # Remove the old files
43 | git rm --cached -r .
44 | rm -rf .dist
45 |
46 | # Add google tag
47 | perl -p -i -e "s%%${GOOGLETAG}%" index.html
48 |
49 | # Push to gh-pages.
50 | git add -fA
51 | git commit --allow-empty -m "Automated Deployment [ci skip]"
52 | git push -f $ORIGIN_URL gh-pages
53 |
54 | # Move back to previous branch.
55 | git checkout -
56 |
57 | echo "Deployed Successfully!"
58 |
59 | exit 0
60 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject cljs-webrepl "0.1.0-SNAPSHOT"
2 | :description "FIXME: write description"
3 | :url "http://example.com/FIXME"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 |
7 | :dependencies [[org.clojure/clojure "1.8.0"]
8 | [org.clojure/clojurescript "1.9.946"]
9 | [com.cognitect/transit-cljs "0.8.243"]
10 | [com.taoensso/timbre "4.10.0"]
11 | [figwheel "0.5.14"]
12 | [fipp "0.6.12"]
13 | [hiccup "1.0.5"]
14 | [doo "0.1.8"]
15 | [reagent "0.7.0"]
16 | [reagent-utils "0.2.1"]
17 | [replumb "0.2.4"]
18 | [com.cemerick/piggieback "0.2.2"]]
19 |
20 | :plugins [[lein-cljsbuild "1.1.7"]
21 | [lein-asset-minifier "0.4.4"]
22 | [lein-doo "0.1.8"]]
23 |
24 | :min-lein-version "2.5.0"
25 | :resource-paths ["resources" "target/cljsbuild"]
26 | :minify-assets {:assets {"resources/public/css/site.min.css" "resources/public/css/site.css"}}
27 |
28 | :profiles {:frontend {:dependencies [[ca.gt0.theasp/reagent-mdl "0.1.0-SNAPSHOT"]
29 | [cljsjs/clipboard "1.6.1-1"]
30 | [cljsjs/codemirror "5.31.0-0"]]
31 |
32 | :clean-targets ^{:protect false} [:target-path
33 | [:cljsbuild :builds :frontend-dev :compiler :output-to]
34 | [:cljsbuild :builds :frontend-dev :compiler :output-dir]
35 | [:cljsbuild :builds :frontend-min :compiler :output-to]]
36 |
37 | :cljsbuild {:builds {:frontend-dev
38 | {:source-paths ["src-frontend/cljs" "src-frontend/cljs-dev" "src-shared/cljs" ]
39 | :compiler {:output-to "target/cljsbuild/public/js/frontend.js"
40 | :output-dir "target/cljsbuild/public/js/frontend"
41 | :source-map true
42 | :asset-path "js/frontend"
43 | :main cljs-webrepl.frontend
44 | :optimizations :none
45 | :pretty-print true
46 | :parallel-build true}
47 | :figwheel {:websocket-url "wss://figwheel.industrial.gt0.ca/figwheel-ws"}}
48 |
49 | :frontend-min
50 | {:source-paths ["src-frontend/cljs" "src-frontend/cljs-prod" "src-shared/cljs" ]
51 | :compiler {:output-to "target/cljsbuild/public/js/frontend.min.js"
52 | :asset-path "js/frontend"
53 | :main cljs-webrepl.frontend
54 | :optimizations :advanced
55 | :pretty-print false
56 | :parallel-build true}}}}
57 |
58 | :plugins [[lein-figwheel "0.5.14"]]
59 |
60 | :figwheel {:http-server-root "public"
61 | :server-port 3449
62 | :nrepl-port 7001
63 | :css-dirs ["resources/public/css"]}}
64 |
65 | :backend {:clean-targets ^{:protect false} [:target-path
66 | [:cljsbuild :builds :backend-dev :compiler :output-to]
67 | [:cljsbuild :builds :backend-dev :compiler :output-dir]
68 | [:cljsbuild :builds :backend-dev :compiler :source-map]
69 | [:cljsbuild :builds :backend-min :compiler :output-to]]
70 |
71 | :cljsbuild {:builds {:backend-dev
72 | {:source-paths ["src-backend/cljs" "src-backend/cljs-dev" "src-shared/cljs"]
73 | :compiler {:output-to "target/cljsbuild/public/js/backend.js"
74 | :output-dir "target/cljsbuild/public/js/backend"
75 | :source-map "target/cljsbuild/public/js/backend.js.map"
76 | :asset-path "js/backend"
77 | :main cljs-webrepl.backend
78 | :static-fns true
79 | :optimizations :whitespace
80 | :pretty-print true
81 | :parallel-build true}}
82 |
83 | :backend-min
84 | {:source-paths ["src-backend/cljs" "src-backend/cljs-prod" "src-shared/cljs"]
85 | :compiler {:output-to "target/cljsbuild/public/js/backend.min.js"
86 | :asset-path "js/backend"
87 | :main cljs-webrepl.backend
88 | :static-fns true
89 | :optimizations :simple
90 | :pretty-print false
91 | :parallel-build true}}}}}}
92 |
93 | :aliases {"clean"
94 | ["do" "with-profile" "backend" "clean," "with-profile" "frontend" "clean"]
95 |
96 | "build"
97 | ["do" "with-profile" "backend" "cljsbuild" "once," "with-profile" "frontend" "cljsbuild" "once"]
98 |
99 | "build-dev"
100 | ["do" "with-profile" "backend" "cljsbuild" "once" "backend-dev," "with-profile" "frontend" "cljsbuild" "once" "frontend-dev"]
101 |
102 | "build-backend-dev"
103 | ["do" "with-profile" "backend" "cljsbuild" "auto" "backend-dev,"]
104 |
105 | "build-min"
106 | ["do" "with-profile" "backend" "cljsbuild" "once" "backend-min," "with-profile" "frontend" "cljsbuild" "once" "frontend-min"]
107 |
108 | "figwheel"
109 | ["do" "with-profile" "backend" "cljsbuild" "once," "with-profile" "frontend" "figwheel"]})
110 |
--------------------------------------------------------------------------------
/resources/public/css/site.css:
--------------------------------------------------------------------------------
1 | #app {
2 | width: 100vw;
3 | height: 100vh;
4 | }
5 |
6 | body {
7 | font-family: Roboto, Helvetica, Arial, sans-serif;
8 | /* background-color: #FAFAFA; */
9 | background-color: #F2F2F2;
10 | overflow-y: hidden; /* Disable pull down to refresh in chrome */
11 | }
12 |
13 | .mdl-dialog__wide {
14 | min-width: 280px;
15 | width: 280px;
16 | width: calc(50%);
17 | }
18 |
19 | .mdl-card__supporting-text {
20 | width: 90%; /* fallback if needed */
21 | width: calc(100% - 32px);
22 | }
23 |
24 | .mdl-card {
25 | width: 100%;
26 | min-height: 0px;
27 | }
28 |
29 | .mdl-card__menu > button {
30 | z-index: 2;
31 | }
32 |
33 |
34 | hr.border {
35 | margin: 0px;
36 | }
37 |
38 | .wide {
39 | width: 100%;
40 | }
41 |
42 | .tall {
43 | height: 100%;
44 | }
45 |
46 | .flex-v {
47 | display: flex;
48 | flex-direction: column;
49 | }
50 |
51 | .flex-h {
52 | display: flex;
53 | flex-direction: row;
54 | vertical-align: center;
55 | }
56 |
57 | .history {
58 | flex: auto;
59 | overflow-y: scroll;
60 | }
61 |
62 | .input {
63 | flex: none;
64 | }
65 |
66 | .input-field {
67 | flex: auto;
68 | }
69 |
70 | .run-button {
71 | flex: none;
72 | }
73 |
74 | .input textarea {
75 | resize: none;
76 | }
77 |
78 | .no-padding {
79 | padding: 0px;
80 | }
81 |
82 | .no-padding-top {
83 | padding-top: 0px;
84 | }
85 |
86 | .no-padding-bottom {
87 | padding-bottom: 0px;
88 | }
89 |
90 | .svg-size {
91 | height: 2.25em;
92 | }
93 |
94 | /* See: https://github.com/google/material-design-lite/issues/1407 */
95 | .mdl-card {
96 | overflow: visible;
97 | z-index: auto;
98 | }
99 |
100 | .white-bg {
101 | background-color: #ffffff;
102 | }
103 |
104 | .card-data, .padding-left {
105 | padding-left: 16px;
106 | }
107 |
108 | .error {
109 | color: #f00;
110 | }
111 |
112 | .card-data, .padding-right {
113 | padding-right: 16px;
114 | }
115 |
116 | .output, .result, .expression, .padding-top {
117 | padding-top: 16px;
118 | }
119 |
120 | .output, .result, .expression, .padding-bottom {
121 | padding-bottom: 16px;
122 | }
123 |
124 | .CodeMirror {
125 | height: auto;
126 | font-family: 'Roboto Mono', 'Courier New', monospace;
127 | }
128 |
129 | .CodeMirror-lines {
130 | padding: 0px;
131 | font-family: 'Roboto Mono', 'Courier New', monospace;
132 | }
133 |
--------------------------------------------------------------------------------
/resources/public/images/cljs-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
31 |
32 |
46 |
--------------------------------------------------------------------------------
/resources/public/images/cljs.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
31 |
32 |
33 |
34 |
57 |
--------------------------------------------------------------------------------
/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
17 |
18 |
19 | Cljs-WebREPL
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/resources/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "lang": "en",
3 | "dir": "ltr",
4 | "name": "CLJS-WebREPL",
5 | "description": "ClojureScript REPL",
6 | "short_name": "CLJS-WebREPL",
7 | "icons": [{
8 | "src": "images/cljs-white.svg",
9 | "type": "image/svg"
10 | }],
11 | "scope": "/cljs-webrepl/",
12 | "start_url": "/cljs-webrepl/",
13 | "display": "fullscreen"
14 | }
15 |
--------------------------------------------------------------------------------
/src-backend/cljs-dev/cljs_webrepl/backend.cljs:
--------------------------------------------------------------------------------
1 | (ns ^:figwheel-no-load cljs-webrepl.backend
2 | (:require
3 | [cljs-webrepl.repl-thread :as repl-thread]
4 | [cljs-webrepl.repl :as repl]
5 | [taoensso.timbre :as timbre
6 | :refer-macros (tracef debugf infof warnf errorf)]))
7 |
8 | (enable-console-print!)
9 | (timbre/set-level! :trace)
10 |
11 | (repl-thread/worker (repl/repl-chan-pair))
12 |
--------------------------------------------------------------------------------
/src-backend/cljs-prod/cljs_webrepl/backend.cljs:
--------------------------------------------------------------------------------
1 | (ns cljs-webrepl.backend
2 | (:require
3 | [cljs-webrepl.repl-thread :as repl-thread]
4 | [cljs-webrepl.repl :as repl]
5 | [taoensso.timbre :as timbre
6 | :refer-macros (tracef debugf infof warnf errorf)]))
7 |
8 | ;;ignore println statements in prod
9 | ;;(set! *print-fn* (fn [& _]))
10 | (enable-console-print!)
11 | (timbre/set-level! :info)
12 |
13 | (repl-thread/worker (repl/repl-chan-pair))
14 |
--------------------------------------------------------------------------------
/src-backend/cljs/cljs_webrepl/io.cljs:
--------------------------------------------------------------------------------
1 | (ns cljs-webrepl.io
2 | (:import goog.net.XhrIo))
3 |
4 | (defn fetch-file!
5 | "Very simple implementation of XMLHttpRequests that given a file path
6 | calls src-cb with the string fetched of nil in case of error.
7 | See doc at https://developers.google.com/closure/library/docs/xhrio"
8 | [file-url src-cb]
9 | (try
10 | (.send XhrIo file-url
11 | (fn [e]
12 | (if (.isSuccess (.-target e))
13 | (src-cb (.. e -target getResponseText))
14 | (src-cb nil))))
15 | (catch :default e
16 | (src-cb nil))))
17 |
--------------------------------------------------------------------------------
/src-backend/cljs/cljs_webrepl/repl.cljs:
--------------------------------------------------------------------------------
1 | (ns cljs-webrepl.repl
2 | (:require
3 | [clojure.string :as str :refer [blank? trim]]
4 | [cljs.core.async :refer [chan close! timeout put!]]
5 | [cljs-webrepl.io :as replumb-io]
6 | [replumb.core :as replumb]
7 | [replumb.repl :as replumb-repl]
8 | [taoensso.timbre :as timbre
9 | :refer-macros (tracef debugf infof warnf errorf)])
10 | (:require-macros
11 | [cljs.core.async.macros :refer [go go-loop]]))
12 |
13 | (def default-repl-opts
14 | (merge (replumb/options :browser
15 | ["/src/cljs" "/js/compiled/out"]
16 | replumb-io/fetch-file!)
17 | {:no-pr-str-on-value true
18 | :warning-as-error true
19 | :verbose false}))
20 |
21 | (def current-ns replumb-repl/current-ns)
22 |
23 | (defn native? [obj]
24 | (or (nil? obj) (boolean? obj) (string? obj) (number? obj) (keyword? obj) (coll? obj)))
25 |
26 |
27 | (defn obj->map* [obj acc key obj->map]
28 | (let [value (aget obj key)]
29 | (cond
30 | (fn? value) acc
31 | (native? value) (assoc acc (keyword key) value)
32 | (object? value) (obj->map obj)
33 | :else (assoc acc (keyword key) value))))
34 |
35 | (defn- obj->map
36 | "Workaround for `TypeError: Cannot convert object to primitive values`
37 | caused by `(js->clj (.-body exp-req) :keywordize-keys true)` apparently
38 | failing to correctly identify `(.-body exp-req)` as an object. Not sure
39 | what's causing this problem."
40 | [o]
41 | (when o
42 | (reduce #(obj->map* o %1 %2 obj->map) {} (js-keys o))))
43 |
44 | (defn add-cause [m cause]
45 | (if (some? cause)
46 | (assoc m :cause (str cause))
47 | m))
48 |
49 | (defn err->map [err]
50 | (when err
51 | (errorf "Evaluation: %s" err)
52 | (-> {:message (.-message err)
53 | :data (.-data err)}
54 | (add-cause (.-cause err)))))
55 |
56 | (defn fix-value [value]
57 | (cond (native? value) value
58 | (object? value) (obj->map value)
59 | :default (str value)))
60 |
61 | (defn on-repl-eval [[num expression] from-repl repl-opts]
62 | (debugf "on-repl-eval: %s %s" num expression)
63 | (put! from-repl [:repl/eval num (replumb-repl/current-ns) expression])
64 |
65 | (let [print-fn #(put! from-repl [:repl/print num %])
66 | result-fn #(put! from-repl [:repl/result num (replumb-repl/current-ns) %])]
67 | (binding [cljs.core/*print-newline* true
68 | cljs.core/*print-fn* print-fn]
69 | (replumb/read-eval-call repl-opts result-fn expression))))
70 |
71 | (defn repl-loop [from-repl to-repl repl-opts]
72 | (go
73 | (put! to-repl [:repl/eval nil "true"])
74 | (loop []
75 | (when-let [msg ( (.querySelector js/document (str "#" id))
46 | (.close)))
47 |
48 | (defn show-dialog [id]
49 | (when-let [dialog (.querySelector js/document (str "#" id))]
50 | (when-not (.-showModal dialog)
51 | (.registerDialog js/dialogPolyfill dialog))
52 | (.showModal dialog)))
53 |
54 | (defn show-reset-dialog []
55 | (show-dialog "reset-dialog"))
56 |
57 | (defn show-about-dialog []
58 | (show-dialog "about-dialog"))
59 |
60 | (defn pprint-str [data]
61 | (-> (with-out-str (fipp/pprint data))
62 | (str/trim-newline)))
63 |
64 | (defn unescape-string [data]
65 | (-> (println-str data)
66 | (str/trim-newline)))
67 |
68 | (defn trigger
69 | "Returns a reagent class that can be used to easily add triggers
70 | from the map in `props`, such as :component-did-mount. See
71 | `reagent.core/create-class` for more information."
72 | [props content]
73 | (r/create-class
74 | (-> {:display-name "trigger"}
75 | (merge props)
76 | (assoc :reagent-render (fn [_ content] content)))))
77 |
78 | (defn clipboard [child]
79 | (let [clipboard-atom (atom nil)]
80 | (r/create-class
81 | {:display-name "clipboard-button"
82 | :component-did-mount (fn [node]
83 | (let [clipboard (new js/Clipboard (r/dom-node node))]
84 | (reset! clipboard-atom clipboard)))
85 | :component-will-unmount (fn []
86 | (when-not (nil? @clipboard-atom)
87 | (.destroy @clipboard-atom)
88 | (reset! clipboard-atom nil)))
89 | :reagent-render (fn [child] child)})))
90 |
91 |
92 |
93 | (defn focus-node [node]
94 | (-> (r/dom-node node)
95 | (.focus)))
96 |
97 | (defn on-repl-eval [state num [ns expression]]
98 | (when num
99 | (swap! state update-in [:history num] assoc :ns ns :expression expression)))
100 |
101 | (defn on-repl-result [state num [ns result]]
102 | (if num
103 | (swap! state #(-> %
104 | (assoc :ns ns :ready? true)
105 | (update-in [:history num] assoc :result result)))
106 | (swap! state assoc :ns ns :ready? true)))
107 |
108 | (defn on-repl-print [state num [s]]
109 | (when num
110 | (swap! state update-in [:history num] update :output str s)))
111 |
112 | (defn on-repl-error [state num [err]]
113 | (when num
114 | (swap! state update-in [:history num] assoc :error err)))
115 |
116 | (defn on-repl-crash [state num [err]]
117 | (swap! state assoc :crashed? true :ready? false)
118 | (show-reset-dialog))
119 |
120 | (defn on-repl-event [state [name num & value]]
121 | (condp = name
122 | :repl/eval (on-repl-eval state num value)
123 | :repl/result (on-repl-result state num value)
124 | :repl/error (on-repl-error state num value)
125 | :repl/print (on-repl-print state num value)
126 | :webworker/error (on-repl-crash state num value)
127 | (warnf "Unknown repl event: %s %s" name value)))
128 |
129 | (defn repl-event-loop [state from-repl]
130 | (go-loop []
131 | (when-let [event ( %
154 | (merge default-state)
155 | (assoc :running? true :crashed? false :ready? false)
156 | (assoc :repl {:to-repl to-eval
157 | :from-repl from-repl})))
158 | (repl-event-loop state from-repl)
159 | (repl-eval-loop to-eval to-repl from-repl)))
160 |
161 | (defn eval-str! [expression]
162 | (let [{:keys [to-repl]} (:repl @state)
163 | expression (some-> expression str/trim)]
164 | (when (and (some? to-repl) (some? expression))
165 | (put! to-repl expression))))
166 |
167 | (defn history-prev [{:keys [cursor history] :as state}]
168 | (let [c (count history)
169 | new-cursor (inc cursor)]
170 | (if (<= new-cursor c)
171 | (assoc state
172 | :cursor new-cursor
173 | :input (:expression (get history (- c new-cursor))))
174 | state)))
175 |
176 | (defn history-next [{:keys [cursor history] :as state}]
177 | (let [c (count history)
178 | new-cursor (dec cursor)]
179 | (if (> new-cursor 0)
180 | (assoc state
181 | :cursor new-cursor
182 | :input (:expression (get history (- c new-cursor) "")))
183 | (assoc state :input ""))))
184 |
185 | (defn clear-input [state]
186 | (assoc state
187 | :cursor 0
188 | :input ""))
189 |
190 | (defn eval-input [state input]
191 | (let [expression (str/trim input)]
192 | (when-not (str/blank? expression)
193 | (eval-str! expression)
194 | (swap! state assoc :ready? false :input "" :cursor 0))))
195 |
196 | (defn input-on-change [state value]
197 | (assoc state :cursor 0 :input value))
198 |
199 | (defn scroll [node]
200 | (let [node (r/dom-node node)]
201 | (aset node "scrollTop" (.-scrollHeight node))))
202 |
203 | (defn scroll-on-update
204 | [child]
205 | (r/create-class
206 | {:display-name "scroll-on-update"
207 | :component-did-mount scroll
208 | :reagent-render identity}))
209 |
210 | (defn history-card-menu [props {:keys [num ns expression result output] :as history-item}]
211 | [mdl/upgrade
212 | [:div.mdl-card__menu
213 | [:button.mdl-button.mdl-js-button.mdl-button--icon.mdl-js-ripple-effect {:id (str "menu-" num)}
214 | [:i.material-icons "more_vert"]]
215 | [:ul.mdl-menu.mdl-menu--bottom-right.mdl-js-menu.mdl-js-ripple-effect
216 | {:for (str "menu-" num)}
217 | [:li.mdl-menu__item
218 | {:on-click #(eval-str! expression)}
219 | "Evaluate Again"]
220 | [clipboard
221 | [:li.mdl-menu__item
222 | {:data-clipboard-text expression}
223 | "Copy Expression"]]
224 | (if (some? output)
225 | [clipboard
226 | [:li.mdl-menu__item
227 | {:data-clipboard-text output}
228 | "Copy Output"]]
229 | [:li.mdl-menu__item
230 | {:disabled true}
231 | "Copy Output"])
232 | [clipboard
233 | [:li.mdl-menu__item
234 | {:data-clipboard-text (pr-str (:value result))}
235 | "Copy Result"]]]]])
236 |
237 | (defn history-card-expression [props ns expression]
238 | [:div.mdl-card__title
239 | [:div
240 | [:code.CodeMirror-lines (str ns "=>")]]
241 | [:div
242 | [editor/code expression]]])
243 |
244 | (defn history-card-output [props output]
245 | [:div
246 | [:div.card-data.output
247 | [editor/text (str/trim-newline output)]]
248 | [:hr.border]])
249 |
250 | (defn render-value [value]
251 | (cond
252 | (string? value)
253 | [editor/code (-> value unescape-string pprint-str)]
254 |
255 | (map? value)
256 | (case (:type value)
257 | :hiccup (:content value)
258 | [editor/code (pprint-str value)])
259 |
260 | :default
261 | [editor/code (pprint-str value)]))
262 |
263 | (defn render-error [error]
264 | [:pre.error
265 | (or (:stack error)
266 | (if (and (:message error) (not= "ERROR" (:message error)))
267 | (:message error)
268 | (str (:cause error))
269 | #_(or (str (:cause error)) (str error))))])
270 |
271 |
272 | (defn render-progress []
273 | [mdl/upgrade
274 | [:div.mdl-progress.mdl-js-progress.mdl-progress__indeterminate {:style {:width "100%"}}]])
275 |
276 | (defn history-card-result [props {:keys [success? value error] :as result}]
277 | [:div.card-data.result
278 | (if result
279 | (if success?
280 | [render-value value]
281 | [render-error error])
282 | [render-progress])])
283 |
284 | (defn history-card [{:keys [state columns] :as props} {:keys [ns expression result output] :as history-item}]
285 | [:div
286 | {:class (str "mdl-cell " (card-size-class columns))}
287 | [:div.mdl-card.mdl-shadow--2dp
288 | [history-card-expression props ns expression]
289 | [history-card-menu props history-item]
290 | [:hr.border]
291 | (when (seq output)
292 | [history-card-output props output])
293 | [history-card-result props result]]])
294 |
295 | (defn please-wait [props]
296 | [:div.history
297 | [:div.mdl-grid
298 | [:div.mdl-cell.mdl-cell--12-col
299 | [:p "REPL initializing..."]]]])
300 |
301 | (defn run-button [{:keys [state on-submit ready?] :as props}]
302 | (let [disabled? (or ready? (str/blank? (:input @state)))]
303 | [:div.padding-left
304 | ^{:key disabled?}
305 | [mdl/upgrade
306 | [:button.mdl-button.mdl-js-button.mdl-button--fab.mdl-js-ripple-effect.mdl-button--colored
307 | {:disabled disabled?
308 | :on-click #(on-submit (:input @state))}
309 | [:i.material-icons "send"]]]]))
310 |
311 | (defn repl-input [{:keys [state] :as props}]
312 | (let [on-change #(swap! state assoc :input %)
313 | props (assoc props :on-change on-change)]
314 | (fn []
315 | [mdl/upgrade
316 | [:div.input-field.mdl-textfield.mdl-js-textfield.mdl-textfield--floating-label
317 | [:div.mdl-textfield__label {:for "repl-input"}
318 | (str (:ns @state) "=>")]
319 | [:div.mdl-textfield__input {:id "repl-input"}
320 | [editor/editor props (:input @state)]]]])))
321 |
322 | (defn input-card [props num]
323 | [:div.mdl-cell.mdl-cell--12-col
324 | [:div.mdl-card.mdl-shadow--2dp
325 | [:div.card-data.expression
326 | [:div.flex-h
327 | [repl-input props]
328 | [run-button props]]]]])
329 |
330 |
331 | (defn history [props]
332 | ^{:key (count (:history @state))}
333 | [scroll-on-update
334 | [:div.history
335 | [:div.mdl-grid
336 | (doall
337 | (for [[num history-item] (:history @state)]
338 | ^{:key num}
339 | [history-card props (assoc history-item :num num)]))
340 | (when (:ready? @state)
341 | [input-card props])]]])
342 |
343 |
344 | (defn reset-dialog [{:keys [state]}]
345 | [:dialog.mdl-dialog {:id "reset-dialog"}
346 | (when (:crashed? @state)
347 | [:div.mdl-dialog__title
348 | [:p "The REPL has crashed!"]])
349 | [:div.mdl-dialog__content
350 | [:p "Reset REPL? All data will be lost."]]
351 | [:div.mdl-dialog__actions
352 | [:button.mdl-button
353 | {:on-click (fn []
354 | (close-dialog "reset-dialog")
355 | (reset-repl! state))}
356 | "Reset"]
357 | [:button.mdl-button
358 | {:on-click #(close-dialog "reset-dialog")}
359 | "Cancel"]]])
360 |
361 | (defn crash-dialog [{:keys [state]}]
362 | [:dialog.mdl-dialog {:id "crash-dialog"}
363 | [:div.mdl-dialog__content
364 | [:p "The REPL has crashed!"]]
365 | [:div.mdl-dialog__actions
366 | [:button.mdl-button
367 | {:on-click (fn []
368 | (close-dialog "crash-dialog")
369 | (reset-repl! state))}
370 | "Reset"]
371 | [:button.mdl-button
372 | {:on-click #(close-dialog "crash-dialog")}
373 | "Cancel"]]])
374 |
375 | (defn about-dialog [props]
376 | [:dialog.mdl-dialog.mdl-dialog__wide {:id "about-dialog"}
377 | [:h4.mdl-dialog__title (str "CLJS-WebREPL")]
378 | [:div.mdl-dialog__content
379 | [:p "A ClojureScript browser based REPL"]
380 | [:p (str "Using ClojureScript version " *clojurescript-version*)]
381 | [:p
382 | "Running: " (if (:running? @state) "Yes" "No") [:br]
383 | "Ready: " (if (:ready? @state) "Yes" "No") [:br]
384 | "Crashed: " (if (:crashed? @state) "Yes" "No")]
385 | [:h5 "License"]
386 | [:p
387 | "Copyright © 2016 Andrew Phillips, Dan Holmsand, Mike Fikes, David Nolen, Rich Hickey, Joel Martin & Contributors"]
388 | [:p
389 | "Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version."]]
390 | [:div.mdl-dialog__actions
391 | [:button.mdl-button
392 | {:on-click #(close-dialog "about-dialog")}
393 | "Ok"]]])
394 |
395 | (defn reset-repl-button [props]
396 | [:button.mdl-button.mdl-js-button.mdl-js-ripple-effect
397 | {:on-click show-reset-dialog
398 | :style {:color "#fff"}}
399 | [:i.material-icons "report"]
400 | [:span.mdl-cell--hide-phone "RESET"]])
401 |
402 | (defn more-columns-button [{:keys [more-columns]}]
403 | [:button.mdl-button.mdl-js-button.mdl-js-ripple-effect.mdl-cell--hide-phone
404 | {:on-click more-columns
405 | :style {:color "#fff"}}
406 | [:i.material-icons "view_column"]])
407 |
408 | (defn less-columns-button [{:keys [less-columns]}]
409 | [:button.mdl-button.mdl-js-button.mdl-js-ripple-effect.mdl-cell--hide-phone
410 | {:on-click less-columns
411 | :style {:color "#fff"}}
412 | [:i.material-icons "view_stream"]])
413 |
414 | (defn menu-button [id]
415 | [:button.mdl-button.mdl-js-button.mdl-button--icon.mdl-js-ripple-effect {:id "main-menu"}
416 | [:i.material-icons "more_vert"]])
417 |
418 | (defn more-columns-menu-item [{:keys [more-columns]}]
419 | [:li.mdl-menu__item {:on-click more-columns} "More Columns"])
420 |
421 | (defn less-columns-menu-item [{:keys [less-columns]}]
422 | [:li.mdl-menu__item {:on-click less-columns} "Less Columns"])
423 |
424 | (defn github-menu-item [{:keys [github]}]
425 | [:li.mdl-menu__item {:on-click github} "GitHub"])
426 |
427 | (defn about-menu-item [props]
428 | [:li.mdl-menu__item {:on-click show-about-dialog} "About"])
429 |
430 | (defn reset-menu-item [props]
431 | [:li.mdl-menu__item {:on-click show-reset-dialog} "Reset REPL"])
432 |
433 | (defn home-page-menu [props]
434 | [:ul.mdl-menu.mdl-menu--bottom-right.mdl-js-menu.mdl-js-ripple-effect
435 | {:for "main-menu"}
436 | [more-columns-menu-item props]
437 | [less-columns-menu-item props]
438 | [reset-menu-item props]
439 | [github-menu-item props]
440 | [about-menu-item props]])
441 |
442 | (defn home-page-title [{:keys [title title-icon] :as props}]
443 | [:span.mdl-layout-title
444 | [:img.svg-size {:src title-icon}]
445 | (str " " title)])
446 |
447 | (defn home-page-header [{:keys [reset-repl more-columns] :as props}]
448 | [:header.mdl-layout__header
449 | [:div.mdl-layout__header-row
450 | [home-page-title props]
451 | [:div.mdl-layout-spacer]
452 | [reset-repl-button props]
453 | [less-columns-button props]
454 | [more-columns-button props]
455 | [menu-button "main-menu"]
456 | [home-page-menu props]]])
457 |
458 | (defn home-page [{:keys [state] :as props}]
459 | [:div
460 | [about-dialog props]
461 | [reset-dialog props]
462 | [:div.tall
463 | [mdl/upgrade
464 | [:div.flex-v.tall.mdl-layout.mdl-js-layout.mdl-layout--fixed-header.mdl-layout--no-drawer-button
465 | [home-page-header props]
466 | (if (:ns @state)
467 | [history (assoc props :columns (:columns @state))]
468 | [please-wait props])]]]])
469 |
470 | (defn mount-root []
471 | (let [input (r/cursor state [:input])
472 | props {:state state
473 | :input input
474 | :on-submit #(eval-input state %)
475 | :history-prev #(swap! state history-prev)
476 | :history-next #(swap! state history-next)
477 | :more-columns #(swap! state more-columns)
478 | :less-columns #(swap! state less-columns)
479 | :github #(set! (.-location js/window) "https://github.com/theasp/cljs-webrepl")
480 | :title-icon "images/cljs-white.svg"
481 | :title "Cljs-WebREPL"}]
482 | (r/render [home-page props] (.getElementById js/document "app"))))
483 |
484 | (defn init! []
485 | (reset-repl! state)
486 | (mount-root))
487 |
--------------------------------------------------------------------------------
/src-frontend/cljs/cljs_webrepl/editor.cljs:
--------------------------------------------------------------------------------
1 | (ns cljs-webrepl.editor
2 | (:require
3 | [reagent.core :as r :refer [atom]]
4 | [clojure.string :as str]
5 | [cljsjs.codemirror]
6 | [cljsjs.codemirror.mode.clojure]
7 | [cljsjs.codemirror.keymap.emacs]
8 | [taoensso.timbre :as timbre
9 | :refer-macros (tracef debugf infof warnf errorf)]))
10 |
11 | (defn cm-options [options]
12 | (js-obj "readOnly" (:read-only? options false)
13 | "height" (name (:height options :auto))
14 | "autofocus" (:focus? options false)
15 | "lineWrapping" (:line-wrap? options false)
16 | "lineNumbers" (:line-numbers? options false)
17 | "mode" (name (:mode options :clojure))
18 | "indentUnit" (:indent options 2)
19 | "electricChars" (:electric-chars? options true)
20 | "viewportMargin" (:viewport-margin options js/Infinity)
21 | "extraKeys" (-> (:extra-keys options nil)
22 | (clj->js)
23 | (js/CodeMirror.normalizeKeyMap))))
24 |
25 | (defn cm-did-mount [node editor {:keys [on-input on-change on-key-down] :as options} text]
26 | (let [element (r/dom-node node)
27 | cm-opts (cm-options options)
28 | editor (reset! editor (js/CodeMirror.fromTextArea element cm-opts))]
29 | (.setValue editor text)
30 | (when on-change
31 | (.on editor "change" on-change))
32 | (when on-input
33 | (.on editor "input" on-input))))
34 |
35 | (defn cm-will-update [node editor [_ _ text]]
36 | (debugf "Update")
37 | (when-let [editor @editor]
38 | (.setValue editor text)
39 | (.refresh editor)))
40 |
41 | (defn cm-will-unmount [node editor]
42 | (when-let [editor @editor]
43 | true)
44 | (reset! editor nil))
45 |
46 | (defn cm-render [props text]
47 | [:textarea {:value text :read-only true}])
48 |
49 | (defn codemirror [props text]
50 | (let [editor (atom nil)]
51 | (r/create-class
52 | {:display-name "codemirror"
53 | :component-did-mount #(cm-did-mount %1 editor props text)
54 | :component-will-update #(cm-will-update %1 editor %2)
55 | :component-will-unmount #(cm-will-unmount %1 editor)
56 | :reagent-render cm-render})))
57 |
58 | (defn editor-update [{:keys [state] :as props} editor change]
59 | (swap! state assoc :input (.getValue editor)))
60 |
61 | (defn multi-line? [editor]
62 | (-> (.getValue editor)
63 | (str/includes? "\n")))
64 |
65 | (defn wrap-on-change [{:keys [on-change]}]
66 | (when on-change
67 | (fn [cm]
68 | (on-change (.getValue cm)))))
69 |
70 | (defn wrap-on-submit [{:keys [on-submit] :as props}]
71 | (fn [cm]
72 | (on-submit (.getValue cm))))
73 |
74 | (defn wrap-history-prev [{:keys [history-prev state] :as props}]
75 | (fn [cm]
76 | (.setValue cm (:input (history-prev)))
77 | (.refresh cm)))
78 |
79 | (defn wrap-history-next [{:keys [history-next state] :as props}]
80 | (fn [cm]
81 | (.setValue cm (:input (history-next)))
82 | (.refresh cm)))
83 |
84 | (defn wrap-ignore-multi [f]
85 | (fn [cm]
86 | (if (multi-line? cm)
87 | js/CodeMirror.Pass
88 | (f cm))))
89 |
90 | (defn insert-pair [cm pair]
91 | (doto cm
92 | (.replaceSelection pair)
93 | (.execCommand "goCharLeft")))
94 |
95 | (defn editor [props text]
96 | (let [on-change (wrap-on-change props)
97 | history-next (wrap-history-next props)
98 | history-prev (wrap-history-prev props)
99 | on-submit (wrap-on-submit props)
100 | extra-keys {:Up (-> history-prev wrap-ignore-multi)
101 | :Down (-> history-next wrap-ignore-multi)
102 | :Ctrl-Up history-prev
103 | :Ctrl-Down history-next
104 | :Enter (-> on-submit wrap-ignore-multi)
105 | :Ctrl-Enter on-submit
106 | :Shift-9 #(insert-pair % "()")
107 | "(" #(insert-pair % "()")
108 | "[" #(insert-pair % "[]")
109 | "{" #(insert-pair % "{}")
110 | "Shift-{" #(insert-pair % "{}")
111 | "Shift-'" #(insert-pair % "\"\"")}]
112 | (fn []
113 | [codemirror (-> {:editable? true
114 | :numbers? false
115 | :focus? true
116 | :extra-keys extra-keys
117 | :on-change on-change}
118 | (merge (dissoc props :on-change)))
119 | text])))
120 |
121 | (defn code [text]
122 | [codemirror {:read-only? true :mode "clojure"} text])
123 |
124 | (defn text [text]
125 | [codemirror {:read-only? true :mode "text"} text])
126 |
--------------------------------------------------------------------------------
/src-frontend/cljs/cljs_webrepl/io.cljs:
--------------------------------------------------------------------------------
1 | (ns cljs-webrepl.io
2 | (:import goog.net.XhrIo))
3 |
4 | (defn fetch-file!
5 | "Very simple implementation of XMLHttpRequests that given a file path
6 | calls src-cb with the string fetched of nil in case of error.
7 | See doc at https://developers.google.com/closure/library/docs/xhrio"
8 | [file-url src-cb]
9 | (try
10 | (.send XhrIo file-url
11 | (fn [e]
12 | (if (.isSuccess (.-target e))
13 | (src-cb (.. e -target getResponseText))
14 | (src-cb nil))))
15 | (catch :default e
16 | (src-cb nil))))
17 |
--------------------------------------------------------------------------------
/src-shared/cljs/cljs_webrepl/repl_thread.cljs:
--------------------------------------------------------------------------------
1 | (ns cljs-webrepl.repl-thread
2 | (:require
3 | [cljs.core.async :refer [chan close! timeout put! pipe]]
4 | [cognitect.transit :as transit]
5 | [cljs.tools.reader :as reader]
6 | [taoensso.timbre :as timbre
7 | :refer-macros (tracef debugf infof warnf errorf)])
8 | (:require-macros
9 | [cljs.core.async.macros :refer [go go-loop]]))
10 |
11 | (def script-name "js/backend.js")
12 |
13 | (def transit-writer (transit/writer :json))
14 | (def transit-reader (transit/reader :json))
15 |
16 | (defn write-transit [data]
17 | (transit/write transit-writer data))
18 |
19 | (defn read-transit [data]
20 | (transit/read transit-reader data))
21 |
22 | (defn write-edn [s]
23 | (binding [*print-level* nil
24 | *print-length* nil
25 | *print-dup* true]
26 | (pr-str s)))
27 |
28 | (extend-type js/Error
29 | IPrintWithWriter
30 | (-pr-writer [obj writer opts] (write-all writer "#error \"" (str obj) "\"")))
31 |
32 |
33 | (def edn-readers {'js #(clj->js %)
34 | 'uuid #(when (string? %)
35 | (uuid %))
36 | 'inst #(when (string? %)
37 | (js/Date. %))
38 | 'queue #(when (vector? %)
39 | (into cljs.core.PersistentQueue.EMPTY %))})
40 |
41 | (defn read-edn [s]
42 | (binding [reader/*default-data-reader-fn* (fn [tag value] value)
43 | reader/*data-readers* edn-readers]
44 | (reader/read-string s)))
45 |
46 | (defn worker? []
47 | (nil? js/self.document))
48 |
49 | (def thread-type (if (worker?)
50 | :worker
51 | :master))
52 |
53 | (defn read-transit-message [message]
54 | (let [message (aget message "content")]
55 | (try
56 | (read-transit message)
57 | (catch js/Error e
58 | (errorf "read-transit-message: %s %s: %s" thread-type (:message e) message)
59 | [:webworker/error nil e]))))
60 |
61 | (defn write-transit-message [message]
62 | (try
63 | (js-obj "format" "transit"
64 | "content" (write-transit message))
65 | (catch js/Error e nil)))
66 |
67 | (defn read-edn-message [message]
68 | (let [message (aget message "content")]
69 | (try
70 | (read-edn message)
71 | (catch js/Error e
72 | (errorf "read-edn-message: %s %s %s" thread-type e message)
73 | [:webworker/error nil e]))))
74 |
75 | (defn write-edn-message [message]
76 | (try
77 | (js-obj "format" "edn"
78 | "content" (write-edn message))
79 | (catch js/Error e nil)))
80 |
81 | (defn write-message [message]
82 | #_(debugf "write-message: %s %s" thread-type (pr-str message))
83 | (or (write-transit-message message)
84 | (write-edn-message message)
85 | (write-transit-message [:webworker/error nil (str "Unable to format message: " (pr message))])))
86 |
87 | (defn read-message [message]
88 | #_(debugf "read-message: %s %s" thread-type (pr-str message))
89 | (case (.-format message)
90 | "json" (read-transit-message message)
91 | "transit" (read-transit-message message)
92 | "edn" (read-edn-message message)
93 | [:webworker/error nil (str "Unknown message format: " (.-format message))]))
94 |
95 | (defn post-message [target message]
96 | (let [message (write-message message)]
97 | (debugf "post-message: %s %s" thread-type (pr-str message))
98 | (.postMessage target message)))
99 |
100 | (defn on-message [output-ch message]
101 | #_(debugf "on-message: %s %s" thread-type (pr-str message))
102 | (if message
103 | (put! output-ch (read-message message))
104 | (warnf "on-message: No message %s" thread-type)))
105 |
106 | (defn on-error [output-ch err]
107 | (errorf "on-error: %s %s" thread-type (pr-str err))
108 | (put! output-ch [:webworker/error nil (pr-str err)]))
109 |
110 | (defn- async-worker [& [target close-fn]]
111 | (let [is-worker? (not (some? target))
112 | target (or target js/self)
113 | input-ch (chan)
114 | output-ch (chan)
115 |
116 | finally-fn (fn []
117 | (debugf "Cleaning up WebWorker: %s" thread-type)
118 | (close! input-ch)
119 | (close! output-ch)
120 | (when close-fn
121 | (close-fn)))
122 | recv-fn (fn [event]
123 | (on-message output-ch (aget event "data")))
124 | error-fn (fn [err]
125 | (on-error output-ch err)
126 | (finally-fn))]
127 | (.addEventListener target "message" recv-fn)
128 | (.addEventListener target "error" error-fn)
129 | (go
130 | (loop []
131 | (when-let [message (