├── .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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /resources/public/images/cljs.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 31 | 32 | 33 | 34 | 36 | 37 | 41 | 42 | 46 | 51 | 52 | 54 | 56 | 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 (