├── test-data ├── one_line.clj ├── test.sh ├── test3.clj ├── nom.clj ├── test2.clj ├── projects │ └── pirate-lang │ │ ├── deps.edn │ │ ├── test │ │ └── pez │ │ │ └── pirate_talk_test.clj │ │ └── src │ │ └── pez │ │ └── pirate_lang.clj ├── issue418.cljc ├── lab │ └── pirate_lang.clj └── test.clj ├── docs ├── readthedocs │ ├── source │ │ ├── assets │ │ ├── _static │ │ │ └── css │ │ │ │ └── custom.css │ │ ├── images │ │ │ ├── debugger │ │ │ │ ├── break.png │ │ │ │ ├── dbg.png │ │ │ │ ├── eval-editor.png │ │ │ │ ├── break-conditional.png │ │ │ │ ├── eval-repl-window.png │ │ │ │ ├── navigation-buttons.png │ │ │ │ └── viewing-variable-values.png │ │ │ └── paredit │ │ │ │ ├── join.gif │ │ │ │ ├── raise.gif │ │ │ │ ├── rewrap.gif │ │ │ │ ├── splice.gif │ │ │ │ ├── split.gif │ │ │ │ ├── close-list.gif │ │ │ │ ├── convolute.gif │ │ │ │ ├── open-list.gif │ │ │ │ ├── raise-sexp.gif │ │ │ │ ├── transpose.gif │ │ │ │ ├── backward-sexp.gif │ │ │ │ ├── barf-backward.gif │ │ │ │ ├── barf-forward.gif │ │ │ │ ├── force-delete.gif │ │ │ │ ├── forward-sexp.gif │ │ │ │ ├── lateral-grow.gif │ │ │ │ ├── slurp-forward.gif │ │ │ │ ├── strict-delete.gif │ │ │ │ ├── backward-up-sexp.gif │ │ │ │ ├── force-backspace.gif │ │ │ │ ├── forward-up-sexp.gif │ │ │ │ ├── grow-selection.gif │ │ │ │ ├── kill-close-list.gif │ │ │ │ ├── kill-open-list.gif │ │ │ │ ├── select-open-list.gif │ │ │ │ ├── shrink-selection.gif │ │ │ │ ├── slurp-backward.gif │ │ │ │ ├── strict-backspace.gif │ │ │ │ ├── string-as-list.gif │ │ │ │ ├── backward-down-sexp.gif │ │ │ │ ├── forward-down-sexp.gif │ │ │ │ ├── kill-backward-sexp.gif │ │ │ │ ├── kill-forward-sexp.gif │ │ │ │ ├── select-close-list.gif │ │ │ │ ├── wrap-around-parens.gif │ │ │ │ ├── wrap-around-quotes.gif │ │ │ │ ├── drag-backward-forward.gif │ │ │ │ ├── select-backward-sexp.gif │ │ │ │ ├── select-forward-sexp.gif │ │ │ │ ├── select-top-level-form.gif │ │ │ │ ├── wrap-around-brackets.gif │ │ │ │ ├── wrap-around-curlies.gif │ │ │ │ ├── select-backward-up-sexp.gif │ │ │ │ ├── select-forward-up-sexp.gif │ │ │ │ ├── splice-killing-backward.gif │ │ │ │ ├── splice-killing-forward.gif │ │ │ │ ├── select-backward-down-sexp.gif │ │ │ │ ├── select-forward-down-sexp.gif │ │ │ │ ├── drag-backward-up-forward-down.gif │ │ │ │ └── drag-forward-up-backward-down.gif │ │ ├── using-with.md │ │ ├── wsl.md │ │ ├── remote-development.md │ │ ├── try-first.md │ │ ├── finding-commands.md │ │ ├── linting.md │ │ ├── index.md │ │ ├── workspace-layouts.md │ │ ├── custom-commands.md │ │ ├── vim.md │ │ ├── quirks.md │ │ ├── commands-top10.md │ │ ├── connect.md │ │ ├── eval-tips.md │ │ └── debugger.md │ └── requirements.txt ├── Overview.png ├── grammar │ └── things-to-consider.md ├── things-to-do.md ├── pull_request_template.md ├── overview.plantuml └── integration.md ├── src ├── calva-fmt │ ├── atom-language-clojure │ │ ├── .coffeelintignore │ │ ├── settings │ │ │ └── language-clojure.cson │ │ ├── .travis.yml │ │ ├── appveyor.yml │ │ ├── package.json │ │ ├── coffeelint.json │ │ ├── README.md │ │ ├── snippets │ │ │ └── language-clojure.cson │ │ └── LICENSE.md │ ├── assets │ │ ├── calva-fmt.png │ │ ├── parinfer.gif │ │ ├── align-items.gif │ │ ├── infer-parens.gif │ │ └── format-current-form.gif │ ├── .vscodeignore │ ├── src │ │ ├── providers │ │ │ ├── range_formatter.ts │ │ │ └── ontype_formatter.ts │ │ ├── state.ts │ │ ├── config.ts │ │ └── extension.ts │ └── update-grammar.js ├── extension-test │ ├── integration │ │ ├── examples │ │ │ └── test.clj │ │ ├── suite │ │ │ ├── extension-test.ts │ │ │ ├── foo-test.ts │ │ │ └── index.ts │ │ └── runTests.ts │ └── unit │ │ ├── debugger │ │ ├── test-files │ │ │ ├── simple.clj │ │ │ ├── fn-shorthand.clj │ │ │ ├── syntax-quote.clj │ │ │ ├── ignored-forms.clj │ │ │ ├── map.clj │ │ │ └── metadata-symbol.clj │ │ └── util-test.ts │ │ ├── util │ │ └── string-test.ts │ │ └── common │ │ └── mock.ts ├── cljs-lib │ ├── src │ │ └── calva │ │ │ ├── main.cljs │ │ │ ├── js_utils.cljs │ │ │ ├── fmt │ │ │ ├── util.cljs │ │ │ ├── editor.cljs │ │ │ └── playground.cljs │ │ │ └── parse.cljs │ └── test │ │ └── calva │ │ └── fmt │ │ ├── editor_test.cljs │ │ └── util_test.cljs ├── status.ts ├── webview │ ├── main.ts │ ├── tsconfig.json │ └── hotkeys.ts ├── greet.ts ├── util │ └── string.ts ├── providers │ ├── definition.ts │ ├── hover.ts │ ├── content.ts │ ├── completion.ts │ └── signature.ts ├── select.ts ├── highlight │ ├── LICENSE.txt │ ├── CHANGELOG.md │ └── README.md ├── files-cache.ts ├── refresh.ts ├── analytics.ts ├── paredit │ └── statusbar.ts ├── printer.ts ├── debugger │ └── util.ts └── cursor-doc │ ├── lexer.ts │ └── undo.ts ├── assets ├── calva.png ├── calva-64h.png ├── calva128x128.png ├── cw_screenshot.png ├── howto │ ├── error.png │ ├── evaluate.gif │ ├── features.gif │ ├── status_clj.png │ ├── status_cljs.png │ ├── cljc-clj-cljs.gif │ ├── rainbow-guides.gif │ ├── signature-help.gif │ ├── dimming-ignores.gif │ ├── paredit-up-wrap.gif │ ├── status_not_connected.png │ └── top-level-comment-eval.gif ├── top-level-eval.gif ├── images │ ├── calva-icon.png │ ├── clj-type-leiningen.png │ ├── clj-type-shadow-cljs.png │ ├── cljs-type-shadow-cljs.png │ ├── empty.svg │ ├── calva-symbol.svg │ ├── cljs.svg │ └── clj.svg ├── fonts │ ├── FiraCode-Bold.woff2 │ ├── FiraCode-Light.woff2 │ ├── FiraCode-Medium.woff2 │ └── FiraCode-Regular.woff2 ├── repl-window-screenshot.png ├── mission-control-shortcuts.gif ├── styles │ ├── fonts.css │ ├── _theme-dark.scss │ └── _theme-light.scss └── webview.html ├── etc ├── repl-window-screenshot.png └── keys.json ├── .gitpod.dockerfile ├── tslint.json ├── jsconfig.json ├── CONTRIBUTING.md ├── .vscodeignore ├── .readthedocs.yml ├── .gitpod.yml ├── .vscode ├── tasks.json ├── settings.json └── launch.json ├── .eslintrc.json ├── .github └── FUNDING.yml ├── mkdocs.yml ├── LICENSE ├── .gitignore ├── shadow-cljs.edn ├── tsconfig.json └── todo.org /test-data/one_line.clj: -------------------------------------------------------------------------------- 1 | foo bar -------------------------------------------------------------------------------- /test-data/test.sh: -------------------------------------------------------------------------------- 1 | { :keys [{1 2 ")" 3}] } -------------------------------------------------------------------------------- /docs/readthedocs/source/assets: -------------------------------------------------------------------------------- 1 | ../../../assets -------------------------------------------------------------------------------- /docs/readthedocs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs==1.1 2 | mkdocs-material==5.0.1 -------------------------------------------------------------------------------- /src/calva-fmt/atom-language-clojure/.coffeelintignore: -------------------------------------------------------------------------------- 1 | spec/fixtures 2 | -------------------------------------------------------------------------------- /assets/calva.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/calva.png -------------------------------------------------------------------------------- /docs/Overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/Overview.png -------------------------------------------------------------------------------- /assets/calva-64h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/calva-64h.png -------------------------------------------------------------------------------- /assets/calva128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/calva128x128.png -------------------------------------------------------------------------------- /assets/cw_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/cw_screenshot.png -------------------------------------------------------------------------------- /assets/howto/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/error.png -------------------------------------------------------------------------------- /assets/howto/evaluate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/evaluate.gif -------------------------------------------------------------------------------- /assets/howto/features.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/features.gif -------------------------------------------------------------------------------- /assets/top-level-eval.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/top-level-eval.gif -------------------------------------------------------------------------------- /src/extension-test/integration/examples/test.clj: -------------------------------------------------------------------------------- 1 | (ns test) 2 | 3 | (defn foo [] 4 | (println "bar")) -------------------------------------------------------------------------------- /assets/howto/status_clj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/status_clj.png -------------------------------------------------------------------------------- /assets/howto/status_cljs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/status_cljs.png -------------------------------------------------------------------------------- /assets/images/calva-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/images/calva-icon.png -------------------------------------------------------------------------------- /src/extension-test/unit/debugger/test-files/simple.clj: -------------------------------------------------------------------------------- 1 | ;; 3, 2 2 | (defn simple [x] 3 | (+ 1 #break x|)) -------------------------------------------------------------------------------- /assets/howto/cljc-clj-cljs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/cljc-clj-cljs.gif -------------------------------------------------------------------------------- /assets/howto/rainbow-guides.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/rainbow-guides.gif -------------------------------------------------------------------------------- /assets/howto/signature-help.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/signature-help.gif -------------------------------------------------------------------------------- /etc/repl-window-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/etc/repl-window-screenshot.png -------------------------------------------------------------------------------- /assets/fonts/FiraCode-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/fonts/FiraCode-Bold.woff2 -------------------------------------------------------------------------------- /assets/fonts/FiraCode-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/fonts/FiraCode-Light.woff2 -------------------------------------------------------------------------------- /assets/fonts/FiraCode-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/fonts/FiraCode-Medium.woff2 -------------------------------------------------------------------------------- /assets/howto/dimming-ignores.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/dimming-ignores.gif -------------------------------------------------------------------------------- /assets/howto/paredit-up-wrap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/paredit-up-wrap.gif -------------------------------------------------------------------------------- /assets/repl-window-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/repl-window-screenshot.png -------------------------------------------------------------------------------- /src/calva-fmt/assets/calva-fmt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/src/calva-fmt/assets/calva-fmt.png -------------------------------------------------------------------------------- /src/calva-fmt/assets/parinfer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/src/calva-fmt/assets/parinfer.gif -------------------------------------------------------------------------------- /.gitpod.dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | RUN brew install clojure/tools/clojure 4 | RUN brew install leiningen -------------------------------------------------------------------------------- /assets/fonts/FiraCode-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/fonts/FiraCode-Regular.woff2 -------------------------------------------------------------------------------- /assets/images/clj-type-leiningen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/images/clj-type-leiningen.png -------------------------------------------------------------------------------- /assets/mission-control-shortcuts.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/mission-control-shortcuts.gif -------------------------------------------------------------------------------- /src/calva-fmt/assets/align-items.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/src/calva-fmt/assets/align-items.gif -------------------------------------------------------------------------------- /assets/howto/status_not_connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/status_not_connected.png -------------------------------------------------------------------------------- /assets/howto/top-level-comment-eval.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/howto/top-level-comment-eval.gif -------------------------------------------------------------------------------- /assets/images/clj-type-shadow-cljs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/images/clj-type-shadow-cljs.png -------------------------------------------------------------------------------- /assets/images/cljs-type-shadow-cljs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/assets/images/cljs-type-shadow-cljs.png -------------------------------------------------------------------------------- /src/calva-fmt/assets/infer-parens.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/src/calva-fmt/assets/infer-parens.gif -------------------------------------------------------------------------------- /src/cljs-lib/src/calva/main.cljs: -------------------------------------------------------------------------------- 1 | (ns calva.main) 2 | 3 | (defn main [& args] 4 | (js/console.log "Hello from calva-lib")) 5 | -------------------------------------------------------------------------------- /src/calva-fmt/assets/format-current-form.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/src/calva-fmt/assets/format-current-form.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | .wy-table-responsive table td, .wy-table-responsive table th { 2 | white-space: normal; 3 | } -------------------------------------------------------------------------------- /src/extension-test/unit/debugger/test-files/fn-shorthand.clj: -------------------------------------------------------------------------------- 1 | ;; 3, 1, 2, 2 2 | (defn fn-shorthand 3 | [s x] 4 | (filter #(= % #break x|) s)) -------------------------------------------------------------------------------- /docs/readthedocs/source/images/debugger/break.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/debugger/break.png -------------------------------------------------------------------------------- /docs/readthedocs/source/images/debugger/dbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/debugger/dbg.png -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/join.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/join.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/raise.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/raise.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/rewrap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/rewrap.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/splice.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/splice.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/split.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/split.gif -------------------------------------------------------------------------------- /src/extension-test/unit/debugger/test-files/syntax-quote.clj: -------------------------------------------------------------------------------- 1 | ;; 3, 2, 1, 2, 1, 2 2 | (defn test-syntax-quote 3 | [y] 4 | `{:hello ~(+ 1 #break y|)}) -------------------------------------------------------------------------------- /test-data/test3.clj: -------------------------------------------------------------------------------- 1 | (let [#_#_x (get c :x "x") 2 | y (get c :y "y") 3 | #_#_z (get c :z "z") 4 | å {:ä :ö}] 5 | (str y å #_#_x z)) -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/close-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/close-list.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/convolute.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/convolute.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/open-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/open-list.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/raise-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/raise-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/transpose.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/transpose.gif -------------------------------------------------------------------------------- /src/extension-test/unit/debugger/test-files/ignored-forms.clj: -------------------------------------------------------------------------------- 1 | ;; 3, 1, 1 2 | (defn test-ignore 3 | [x] 4 | (let [y #_#_#_2 3 4 #break x|] 5 | (+ x y))) -------------------------------------------------------------------------------- /src/extension-test/unit/debugger/test-files/map.clj: -------------------------------------------------------------------------------- 1 | ;; 3, "[1 (+ 1 2)]", 2 2 | (defn test-map 3 | [x] 4 | {:hello "world" [1 (+ 1 2)] (+ 5 #break x|)}) -------------------------------------------------------------------------------- /docs/readthedocs/source/images/debugger/eval-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/debugger/eval-editor.png -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/backward-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/backward-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/barf-backward.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/barf-backward.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/barf-forward.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/barf-forward.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/force-delete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/force-delete.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/forward-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/forward-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/lateral-grow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/lateral-grow.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/slurp-forward.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/slurp-forward.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/strict-delete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/strict-delete.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/backward-up-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/backward-up-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/force-backspace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/force-backspace.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/forward-up-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/forward-up-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/grow-selection.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/grow-selection.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/kill-close-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/kill-close-list.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/kill-open-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/kill-open-list.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/select-open-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/select-open-list.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/shrink-selection.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/shrink-selection.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/slurp-backward.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/slurp-backward.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/strict-backspace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/strict-backspace.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/string-as-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/string-as-list.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/debugger/break-conditional.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/debugger/break-conditional.png -------------------------------------------------------------------------------- /docs/readthedocs/source/images/debugger/eval-repl-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/debugger/eval-repl-window.png -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/backward-down-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/backward-down-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/forward-down-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/forward-down-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/kill-backward-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/kill-backward-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/kill-forward-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/kill-forward-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/select-close-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/select-close-list.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/wrap-around-parens.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/wrap-around-parens.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/wrap-around-quotes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/wrap-around-quotes.gif -------------------------------------------------------------------------------- /src/extension-test/unit/debugger/test-files/metadata-symbol.clj: -------------------------------------------------------------------------------- 1 | ;; 3, 2, 2 2 | (defn test-metadata-symbol 3 | [x] 4 | (let [y x] 5 | (+ ^String x #break ^String y|))) -------------------------------------------------------------------------------- /docs/readthedocs/source/images/debugger/navigation-buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/debugger/navigation-buttons.png -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/drag-backward-forward.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/drag-backward-forward.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/select-backward-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/select-backward-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/select-forward-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/select-forward-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/select-top-level-form.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/select-top-level-form.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/wrap-around-brackets.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/wrap-around-brackets.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/wrap-around-curlies.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/wrap-around-curlies.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/select-backward-up-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/select-backward-up-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/select-forward-up-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/select-forward-up-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/splice-killing-backward.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/splice-killing-backward.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/splice-killing-forward.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/splice-killing-forward.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/debugger/viewing-variable-values.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/debugger/viewing-variable-values.png -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/select-backward-down-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/select-backward-down-sexp.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/select-forward-down-sexp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/select-forward-down-sexp.gif -------------------------------------------------------------------------------- /src/calva-fmt/atom-language-clojure/settings/language-clojure.cson: -------------------------------------------------------------------------------- 1 | '.source.clojure': 2 | 'editor': 3 | 'commentStart': '; ' 4 | 'autocomplete': 5 | 'extraWordCharacters': '-' 6 | -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/drag-backward-up-forward-down.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/drag-backward-up-forward-down.gif -------------------------------------------------------------------------------- /docs/readthedocs/source/images/paredit/drag-forward-up-backward-down.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/calva/master/docs/readthedocs/source/images/paredit/drag-forward-up-backward-down.gif -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [], 4 | "jsRules": {}, 5 | "rules": { 6 | "no-floating-promises": true 7 | }, 8 | "rulesDirectory": [] 9 | } 10 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "lib": [ 6 | "es6" 7 | ] 8 | }, 9 | "exclude": [ 10 | "node_modules" 11 | ] 12 | } -------------------------------------------------------------------------------- /src/status.ts: -------------------------------------------------------------------------------- 1 | import * as util from './utilities'; 2 | import statusbar from './statusbar'; 3 | 4 | function update() { 5 | util.updateREPLSessionType(); 6 | statusbar.update(); 7 | } 8 | 9 | export default { 10 | update 11 | }; -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | While you are here, please consider [Sponsoring Calva Development](https://github.com/sponsors/PEZ) ❤️ 2 | 3 | See also this wiki page: [How to Contribute](https://github.com/BetterThanTomorrow/calva/wiki/How-to-Contribute) 4 | 5 | Happy Coding! 6 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | # Put everything on the blacklist by default 2 | * 3 | */** 4 | **/.DS_Store 5 | */**/.DS_Store 6 | 7 | # Whitelist 8 | !clojure.tmLanguage.json 9 | !assets/** 10 | !out/** 11 | !package.json 12 | !README.md 13 | !CHANGELOG.md 14 | !LICENSE.txt 15 | -------------------------------------------------------------------------------- /src/calva-fmt/.vscodeignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | jsconfig.json 3 | tsconfig.json 4 | vsc-extension-quickstart.md 5 | .eslintrc.json 6 | .nrepl-port 7 | 8 | .vscode/** 9 | .vscode-test/** 10 | test/** 11 | cljc/** 12 | lib/** 13 | test_js/** 14 | js/** 15 | 16 | .shadow-cljs/** 17 | atom-language-clojure/** 18 | -------------------------------------------------------------------------------- /test-data/nom.clj: -------------------------------------------------------------------------------- 1 | (ns nom 2 | (:gen-class)) 3 | 4 | failure-expected-here-dont-freak-out 5 | 6 | This noming squirrel will cause compilation of this file to fail. 7 | 8 | ,;;:;, 9 | ;;;;; 10 | ,:;;:; ,'=. 11 | ;:;:;' .=" ,'_\ 12 | ':;:;,/ ,__:=@ 13 | ';;:; =./)_ 14 | `"=\_ )_"` 15 | ``'" -------------------------------------------------------------------------------- /src/webview/main.ts: -------------------------------------------------------------------------------- 1 | import { ReplConsole } from "./repl-console"; 2 | 3 | let console = new ReplConsole(document.querySelector(".repl"), (line) => { 4 | console.requestPrompt("user=> "); 5 | }); 6 | 7 | console.requestPrompt("user=> ") 8 | document.addEventListener("DOMContentLoaded", () => { 9 | console.input.focus(); 10 | }) -------------------------------------------------------------------------------- /src/calva-fmt/atom-language-clojure/.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | notifications: 4 | email: 5 | on_success: never 6 | on_failure: change 7 | 8 | script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 9 | 10 | git: 11 | depth: 10 12 | 13 | branches: 14 | only: 15 | - master 16 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | mkdocs: 9 | configuration: mkdocs.yml 10 | 11 | python: 12 | version: 3.7 13 | install: 14 | - requirements: docs/readthedocs/requirements.txt -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.dockerfile 3 | tasks: 4 | - init: npm i 5 | # command: clojure -Sdeps '{:deps {nrepl {:mvn/version "0.6.0"} cider/cider-nrepl {:mvn/version "0.23.0"}}}' -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware]" 6 | vscode: 7 | extensions: 8 | - betterthantomorrow.calva@2.0.90:QqaT9WwNey/mBcvg2i0WUA== 9 | - borkdude.clj-kondo@2020.4.5:yW4qKh9L7nu9rnAAEWf7Jw== -------------------------------------------------------------------------------- /src/calva-fmt/src/providers/range_formatter.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as formatter from '../format'; 3 | 4 | 5 | export class RangeEditProvider implements vscode.DocumentRangeFormattingEditProvider { 6 | provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, _options, _token) { 7 | return formatter.formatRangeEdits(document, range); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/images/empty.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Calva Watch", 8 | "type": "npm", 9 | "script": "watch", 10 | "isBackground": true, 11 | "group": { 12 | "kind": "build", 13 | "isDefault": true 14 | }, 15 | "problemMatcher": "$tsc-watch" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test-data/test2.clj: -------------------------------------------------------------------------------- 1 | (a(b 2 | (c 3 | #f 4 | (#b 5 | [:f :b :z]) 6 | #z 7 | 1))) 8 | (comment 9 | 1 10 | #foo 11 | #bar 12 | #baz 13 | #{:foo (#foo '[1 2 3])}) 14 | 15 | (comment 16 | 17 | #foo :bar 18 | (foo #foo ('foo^' :bar)) 19 | #_ 'abc 20 | #foo @~#(foo :bar) \space 21 | \' {:foo (#foo '[1 2 3])} 'abc 22 | #_#_ 23 | #foo 24 | #bar 25 | #baz 26 | #{:foo (#foo '[1 2 3])} 27 | #foo 28 | 'bar 29 | \a [] "a" 30 | 31 | ) -------------------------------------------------------------------------------- /test-data/projects/pirate-lang/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["resources" "src"] 2 | :deps {org.clojure/clojure {:mvn/version "1.10.1"}} 3 | :aliases 4 | {:test {:extra-paths ["test"] 5 | :extra-deps {org.clojure/test.check {:mvn/version "0.10.0"}}} 6 | :runner 7 | {:extra-deps {com.cognitect/test-runner 8 | {:git/url "https://github.com/cognitect-labs/test-runner" 9 | :sha "76568540e7f40268ad2b646110f237a60295fa3c"}} 10 | :main-opts ["-m" "cognitect.test-runner" 11 | "-d" "test"]}}} 12 | -------------------------------------------------------------------------------- /src/calva-fmt/atom-language-clojure/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | platform: x64 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | clone_depth: 10 10 | 11 | skip_tags: true 12 | 13 | environment: 14 | APM_TEST_PACKAGES: 15 | 16 | matrix: 17 | - ATOM_CHANNEL: stable 18 | - ATOM_CHANNEL: beta 19 | 20 | install: 21 | - ps: Install-Product node 4 22 | 23 | build_script: 24 | - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) 25 | 26 | test: off 27 | deploy: off 28 | -------------------------------------------------------------------------------- /src/extension-test/integration/suite/extension-test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { after } from 'mocha'; 3 | 4 | // You can import and use all API from the 'vscode' module 5 | // as well as import your extension to test it 6 | import * as vscode from 'vscode'; 7 | // import * as myExtension from '../extension'; 8 | 9 | suite('Extension Test Suite', () => { 10 | after(() => { 11 | vscode.window.showInformationMessage('All tests done!'); 12 | }); 13 | 14 | // TODO: Add some smoke tests for the extension 15 | // TODO: Start building integration test coverage 16 | }); -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "no-const-assign": "warn", 16 | "no-this-before-super": "warn", 17 | "no-undef": "warn", 18 | "no-unreachable": "warn", 19 | "no-unused-vars": "warn", 20 | "constructor-super": "warn", 21 | "valid-typeof": "warn" 22 | } 23 | } -------------------------------------------------------------------------------- /docs/readthedocs/source/using-with.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Using Calva with X 8 | 9 | Calva can be used in conjunction with many other Clojure related technologies and projects. Please consider contributing with guides about how you have set things up to achieve your workflow with this or that awesome thing. 10 | 11 | 12 | .. toctree:: 13 | :maxdepth: 3 14 | :caption: Contents: 15 | 16 | rebl 17 | vim 18 | wsl 19 | remote-development 20 | luminus -------------------------------------------------------------------------------- /src/greet.ts: -------------------------------------------------------------------------------- 1 | export function activationGreetings(chan) { 2 | chan.appendLine("Happy Clojure(Script) coding! ❤️"); 3 | chan.appendLine("If you like Calva, please consider how you can contribute: https://github.com/BetterThanTomorrow/calva/wiki/How-to-Contribute"); 4 | chan.appendLine("Please check these resources out:"); 5 | chan.appendLine("* Calva Documentation: https://calva.io/"); 6 | chan.appendLine("* #calva at the Clojurians Slack: https://clojurians.slack.com/messages/calva/"); 7 | chan.appendLine("* Bug reports: https://github.com/BetterThanTomorrow/calva/issues"); 8 | chan.appendLine("--"); 9 | } -------------------------------------------------------------------------------- /src/cljs-lib/src/calva/js_utils.cljs: -------------------------------------------------------------------------------- 1 | (ns calva.js-utils 2 | (:require [cljs.reader] 3 | [cljs.tools.reader :as tr] 4 | [cljs.tools.reader.reader-types :as rt] 5 | [cljs.test :refer [is]])) 6 | 7 | (defn jsify 8 | "Converts clojure data to js data" 9 | {:test (fn [] 10 | (is (= (pr-str (jsify {:foo [1 {:bar :baz}]})) 11 | (pr-str #js {:foo #js [1 #js {:bar "baz"}]}))) 12 | (is (= (pr-str (jsify {:foo/bar :foo/bar})) 13 | (pr-str #js {"foo/bar" "foo/bar"}))))} 14 | [o] 15 | (clj->js o :keyword-fn (fn [kw] (str (symbol kw))))) 16 | 17 | (defn cljify [o] 18 | (js->clj o :keywordize-keys true)) 19 | 20 | -------------------------------------------------------------------------------- /src/util/string.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prepends a `:` to a string, so it can be used as an EDN keyword. 3 | * (Or at least made to look like one). 4 | * @param {string} s the string to be keywordized 5 | * @return {string} keywordized string 6 | */ 7 | export function keywordize(s: string): string { 8 | return s.replace(/^[\s,:]*/, ":"); 9 | } 10 | 11 | /** 12 | * Remove the leading `:` from strings (EDN keywords)' 13 | * NB: Does not check if the leading character is really a `:`. 14 | * @param {string} kw 15 | * @return {string} kw without the first character 16 | */ 17 | export function unKeywordize(kw: string): string { 18 | return kw.replace(/^[\s,:]*/, "").replace(/[\s,:]*$/, "") 19 | } 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [PEZ] 4 | # patreon: # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # otechie: # Replace with a single Otechie username 12 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] -------------------------------------------------------------------------------- /src/webview/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es5", 5 | "lib": [ 6 | "es2016", 7 | "dom" 8 | ], 9 | "jsx": "react", 10 | "downlevelIteration": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "declaration": false, 14 | "noImplicitAny": false, 15 | "noImplicitUseStrict": false, 16 | "removeComments": true, 17 | "noLib": false, 18 | "preserveConstEnums": true, 19 | "suppressImplicitAnyIndexErrors": true 20 | }, 21 | "compileOnSave": false, 22 | "buildOnSave": false 23 | } -------------------------------------------------------------------------------- /assets/styles/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Fira Code'; 3 | src: url('../fonts/FiraCode-Light.woff2') format('woff2'); 4 | font-weight: 300; 5 | font-style: normal; 6 | } 7 | 8 | @font-face{ 9 | font-family: 'Fira Code'; 10 | src: url('../fonts/FiraCode-Regular.woff2') format('woff2'); 11 | font-weight: 400; 12 | font-style: normal; 13 | } 14 | 15 | @font-face{ 16 | font-family: 'Fira Code'; 17 | src: url('../fonts/FiraCode-Medium.woff2') format('woff2'); 18 | font-weight: 500; 19 | font-style: normal; 20 | } 21 | 22 | @font-face{ 23 | font-family: 'Fira Code'; 24 | src: url('../fonts/FiraCode-Bold.woff2') format('woff2'); 25 | font-weight: 700; 26 | font-style: normal; 27 | } -------------------------------------------------------------------------------- /src/calva-fmt/atom-language-clojure/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "language-clojure", 3 | "version": "0.22.7", 4 | "description": "Clojure language support in Atom", 5 | "engines": { 6 | "atom": "*", 7 | "node": "*" 8 | }, 9 | "homepage": "http://atom.github.io/language-clojure", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/atom/language-clojure" 13 | }, 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/atom/language-clojure/issues" 17 | }, 18 | "scripts": { 19 | "preinstall": "npx npm-force-resolutions" 20 | }, 21 | "devDependencies": { 22 | "coffeelint": "^1.10.1", 23 | "minimist": "^1.2.5" 24 | }, 25 | "resolutions": { 26 | "minimist": "^1.2.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Calva Documentation 2 | docs_dir: docs/readthedocs/source 3 | theme: readthedocs 4 | nav: 5 | - index.md 6 | - Getting Started: 7 | - connect.md 8 | - try-first.md 9 | - finding-commands.md 10 | - eval-tips.md 11 | - Features: 12 | - commands-top10.md 13 | - formatting.md 14 | - paredit.md 15 | - debugger.md 16 | - pprint.md 17 | - linting.md 18 | - Customizing Calva: 19 | - customizing.md 20 | - connect-sequences.md 21 | - custom-commands.md 22 | - Using Calva with X: 23 | - rebl.md 24 | - vim.md 25 | - wsl.md 26 | - remote-development.md 27 | - luminus.md 28 | - Etcetera: 29 | - workspace-layouts.md 30 | - quirks.md 31 | - In Depth: 32 | - jack-in-guide.md 33 | -------------------------------------------------------------------------------- /src/cljs-lib/test/calva/fmt/editor_test.cljs: -------------------------------------------------------------------------------- 1 | (ns calva.fmt.editor-test 2 | (:require [cljs.test :include-macros true :refer [deftest is]] 3 | [calva.fmt.editor :as sut])) 4 | 5 | 6 | (deftest raplacement-edits-for-diffing-lines 7 | (is (= [] 8 | (sut/raplacement-edits-for-diffing-lines "foo\nfoo\nbar\nbar" 9 | "foo\nfoo\nbar\nbar"))) 10 | (is (= [{:edit "replace", :start {:line 1, :character 0}, :end {:line 1, :character 6}, :text "bar"} 11 | {:edit "replace", :start {:line 2, :character 0}, :end {:line 2, :character 3}, :text "baz"}] 12 | (sut/raplacement-edits-for-diffing-lines "foo\nfooooo\nbar\nbar" 13 | "foo\nbar\nbaz\nbar")))) 14 | -------------------------------------------------------------------------------- /src/extension-test/unit/util/string-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { keywordize, unKeywordize } from '../../../util/string'; 3 | 4 | 5 | describe('string', () => { 6 | describe('keywordize', function() { 7 | it('keywordizes non-keywords', function() { 8 | expect(":test").equal(keywordize("test")); 9 | }); 10 | it('leaves keywords alone', function() { 11 | expect(":test").equal(keywordize(":test")); 12 | }); 13 | }); 14 | 15 | describe('unKeywordize', function() { 16 | it('un-keywordizes keywords', function() { 17 | expect("test").equal(unKeywordize(":test")); 18 | }); 19 | it('leaves non-keywords alone', function() { 20 | expect("test").equal(unKeywordize("test")); 21 | }); 22 | }); 23 | }); -------------------------------------------------------------------------------- /test-data/projects/pirate-lang/test/pez/pirate_talk_test.clj: -------------------------------------------------------------------------------- 1 | (ns pez.pirate-talk-test 2 | (:require [clojure.test :refer [deftest is testing]] 3 | [pez.pirate-talk :as sut])) 4 | 5 | (def swedish-o {:alphabet "abcdefghijklmnopqrstuvwxyzåäö" 6 | :vowels "aeiouåäö" 7 | :pirate-char "o"}) 8 | (deftest a-test 9 | (testing "Speak rövarspråk" 10 | (is (= "HoHaror dodu hohörortot totalolasos omom rorövovarorsospoproråkoketot?" 11 | (sut/to-pirate-talk "Har du hört talas om rövarspråket?" sut/swedish-o)))) 12 | (testing "Hear rövarspråk" 13 | (is (= "Har du hört talas om rövarspråket?" 14 | (sut/from-pirate-talk "HoHaror dodu hohörortot totalolasos omom rorövovarorsospoproråkoketot?" sut/swedish-o))))) 15 | -------------------------------------------------------------------------------- /src/calva-fmt/atom-language-clojure/coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_line_length": { 3 | "level": "ignore" 4 | }, 5 | "no_empty_param_list": { 6 | "level": "error" 7 | }, 8 | "arrow_spacing": { 9 | "level": "error" 10 | }, 11 | "no_interpolation_in_single_quotes": { 12 | "level": "error" 13 | }, 14 | "no_debugger": { 15 | "level": "error" 16 | }, 17 | "prefer_english_operator": { 18 | "level": "error" 19 | }, 20 | "colon_assignment_spacing": { 21 | "spacing": { 22 | "left": 0, 23 | "right": 1 24 | }, 25 | "level": "error" 26 | }, 27 | "braces_spacing": { 28 | "spaces": 0, 29 | "level": "error" 30 | }, 31 | "spacing_after_comma": { 32 | "level": "error" 33 | }, 34 | "no_stand_alone_at": { 35 | "level": "error" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/extension-test/integration/runTests.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../../'); 10 | 11 | // The path to the extension test runner script 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | const launchArgs = ["--disable-extensions"]; 16 | 17 | // Download VS Code, unzip it and run the integration test 18 | await runTests({ extensionDevelopmentPath, extensionTestsPath, launchArgs }); 19 | } catch (err) { 20 | console.error('Failed to run tests'); 21 | process.exit(1); 22 | } 23 | } 24 | 25 | main(); -------------------------------------------------------------------------------- /etc/keys.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "key": "ctrl+right", 4 | "command": "paredit.slurpSexpForward", 5 | "when": "editorTextFocus" 6 | }, 7 | { 8 | "key": "ctrl+left", 9 | "command": "paredit.barfSexpForward", 10 | "when": "editorTextFocus" 11 | }, 12 | { 13 | "key": "ctrl+cmd+right", 14 | "command": "paredit.forwardSexp", 15 | "when": "editorTextFocus" 16 | }, 17 | { 18 | "key": "ctrl+cmd+left", 19 | "command": "paredit.backwardSexp", 20 | "when": "editorTextFocus" 21 | }, 22 | { 23 | "key": "ctrl+shift+left", 24 | "command": "paredit.slurpSexpBackward", 25 | "when": "editorTextFocus" 26 | }, 27 | { 28 | "key": "ctrl+shift+right", 29 | "command": "paredit.barfSexpBackward", 30 | "when": "editorTextFocus" 31 | } 32 | ] -------------------------------------------------------------------------------- /src/providers/definition.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as state from '../state'; 3 | import * as util from '../utilities'; 4 | 5 | export class DefinitionProvider implements vscode.DefinitionProvider { 6 | state: any; 7 | constructor() { 8 | this.state = state; 9 | } 10 | 11 | async provideDefinition(document, position, token) { 12 | if (util.getConnectedState()) { 13 | const text = util.getWordAtPosition(document, position), 14 | client = util.getSession(util.getFileType(document)), 15 | info = await client.info(util.getNamespace(document), text); 16 | if (info.file && info.file.length > 0) { 17 | const pos = new vscode.Position(info.line - 1, info.column); 18 | try { 19 | return new vscode.Location(vscode.Uri.parse(info.file, true), pos); 20 | } catch(e) { /* ignore */ } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /docs/readthedocs/source/wsl.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Calva ❤️ WSL 8 | 9 | The use of Calva with WSL (Windows Subsystem for Linux) is fully supported through the [Remote - WSL](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl) extension. Simply install the extension an open your project with one of the `Remote-WSL` commands. Calva will run directly in the WSL environment and no further configuration is required. 10 | 11 | ## Steps Involved 12 | 13 | 1. Enable WSL 14 | 1. Install Ubuntu in WSL 15 | 1. Install Java in WSL 16 | 1. Install latest Clojure in WSL 17 | 1. Install the Remote - WSL extension in VS Code 18 | 1. Launch remote window 19 | 1. Install Calva (gets installed into the WSL instance) 20 | 1. Work away 21 | 22 | See also [Remote Development](remote-development.md). -------------------------------------------------------------------------------- /src/calva-fmt/src/state.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as Immutable from 'immutable'; 3 | import * as ImmutableCursor from 'immutable-cursor'; 4 | 5 | const mode = { 6 | language: 'clojure', 7 | //scheme: 'file' 8 | }; 9 | 10 | var data; 11 | const initialData = { 12 | documents: {} 13 | }; 14 | 15 | reset(); 16 | 17 | const cursor = ImmutableCursor.from(data, [], (nextState) => { 18 | data = Immutable.fromJS(nextState); 19 | }); 20 | 21 | function deref() { 22 | return data; 23 | } 24 | 25 | function reset() { 26 | data = Immutable.fromJS(initialData); 27 | } 28 | 29 | function config() { 30 | let configOptions = vscode.workspace.getConfiguration('calva.fmt'); 31 | return { 32 | parinferOnSelectionChange: configOptions.get("inferParensOnCursorMove"), 33 | }; 34 | } 35 | 36 | export { 37 | cursor, 38 | mode, 39 | deref, 40 | reset, 41 | config 42 | }; 43 | -------------------------------------------------------------------------------- /docs/grammar/things-to-consider.md: -------------------------------------------------------------------------------- 1 | 2 | From @sogaiu: 3 | @pez in processing clj-ish files from github using tree-sitter or clj-kondo, here are a few things that come up that can lead to errors / warnings: 4 | * template files - people do things like: (ns {{namespace}}.rendering or :{{name}} (mustache?) -- small number of instances of other styles too 5 | * missing, misplaced, or incorrectly placed closing delimiters 6 | * non-clojure text in (comment or #_ blocks 7 | * questionable(?) symbols and keywords - things that the repl might accept, but for which i didn't find a statement of support (e.g. # in keywords, keywords with multiple slashes in them) (edited) 8 | 9 | ``` 10 | Error: Property failed after 58 tests 11 | { seed: -1913189269, path: "57:0:0:0:0:0:0:0:0:0:0:2:1:1:1:1:1:1:1:1:1:1:1:1:1:1:2:9:8:8:8:8:8:8:8:0:0:0:0:0:0", endOnFailure: true } 12 | Counterexample: ["##~"] 13 | Shrunk 40 time(s) 14 | Got error: AssertionError: expected 'junk' to equal 'id' 15 | ``` -------------------------------------------------------------------------------- /src/extension-test/integration/suite/foo-test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { after } from 'mocha'; 3 | import * as path from "path"; 4 | import * as vscode from "vscode"; 5 | 6 | const testFolderLocation = "./examples/"; 7 | 8 | suite("opening file", () => { 9 | test("", async () => { 10 | const testClj = await openFile("test.clj"); 11 | //Do stuff with the file 12 | assert.equal(1, 1); 13 | 14 | vscode.commands.executeCommand("workbench.action.closeActiveEditor"); 15 | }) 16 | 17 | }); 18 | 19 | async function openFile(fileName :string) { 20 | const uri = vscode.Uri.file(path.join(__dirname + testFolderLocation + fileName)); 21 | const document = await vscode.workspace.openTextDocument(uri); 22 | const editor = await vscode.window.showTextDocument(document); 23 | 24 | await sleep(500); 25 | 26 | return editor; 27 | } 28 | 29 | function sleep(ms: number): Promise { 30 | return new Promise((resolve) => { 31 | setTimeout(resolve, ms); 32 | }); 33 | } -------------------------------------------------------------------------------- /src/calva-fmt/atom-language-clojure/README.md: -------------------------------------------------------------------------------- 1 | # Calva Syntax Highlight Grammar 2 | 3 | Calva Format's `clojure.tmLanguage.json` file is built from here (the `/grammars/clojure.cson` file). When making changes, also update `spec/clojure-spec.coffee` and make sure all tests pass. 4 | 5 | To run the tests you need to open this directory in Atom and issue the **Run Package Specs** command. 6 | 7 | To test your changes on CLojure source files in Atom you need to link this directory to where Atom keeps its dev package files. This works on Mac and Linux: 8 | 9 | ```sh 10 | $ cd ~/.atom/dev/packages 11 | $ ln -s /calva-fmt/atom-language-clojure/ language-clojure 12 | ``` 13 | 14 | You also need to run Atom in dev mode: 15 | 16 | ```sh 17 | $ atom -d 18 | ``` 19 | 20 | When all old and new tests pass, update Calva Format's grammar from the root of the project: 21 | 22 | ```sh 23 | $ npm run update-grammar 24 | ``` 25 | 26 | Then make some sanity tests on Clojure source files in VS Code (this mainly tests that the update-grammar script works, but anyway). 27 | -------------------------------------------------------------------------------- /src/cljs-lib/src/calva/fmt/util.cljs: -------------------------------------------------------------------------------- 1 | (ns calva.fmt.util 2 | (:require [clojure.string] 3 | [calva.js-utils :refer [cljify jsify]])) 4 | 5 | 6 | (defn log 7 | "logs out the object `o` excluding any keywords in `exclude-kws`" 8 | [o & exlude-kws] 9 | (println (pr-str (if (map? o) (apply dissoc o exlude-kws) o))) 10 | o) 11 | 12 | 13 | (defn escape-regexp 14 | "Escapes regexp characters in `s`" 15 | [s] 16 | (clojure.string/replace s #"([.*+?^${}()|\[\]\\])" "\\$1")) 17 | 18 | 19 | (defn current-line 20 | "Finds the text of the current line in `text` from cursor position `index`" 21 | [text index] 22 | (let [head (subs text 0 index) 23 | tail (subs text index)] 24 | (str (second (re-find #"\n?(.*)$" head)) 25 | (second (re-find #"^(.*)\n?" tail))))) 26 | 27 | 28 | (defn re-pos-first 29 | "Find position of first match of `re` in `s`" 30 | [re s] 31 | (if-let [m (.match s re)] 32 | (.-index m) 33 | -1)) 34 | 35 | 36 | (defn split-into-lines 37 | [s] 38 | (clojure.string/split s #"\r?\n" -1)) 39 | 40 | -------------------------------------------------------------------------------- /src/select.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as util from './utilities' 3 | import * as docMirror from './doc-mirror' 4 | 5 | function getFormSelection(doc: vscode.TextDocument, pos: vscode.Position, topLevel): vscode.Selection { 6 | const idx = doc.offsetAt(pos), 7 | cursor = docMirror.getDocument(doc).getTokenCursor(topLevel ? 0 : idx), 8 | range = topLevel ? cursor.rangeForDefun(idx) : cursor.rangeForCurrentForm(idx); 9 | return new vscode.Selection(doc.positionAt(range[0]), doc.positionAt(range[1])); 10 | } 11 | 12 | function selectCurrentForm(document = {}) { 13 | let editor = vscode.window.activeTextEditor, 14 | doc = util.getDocument(document), 15 | selection = editor.selection; 16 | 17 | if (selection.isEmpty) { 18 | let codeSelection = getFormSelection(doc, selection.active, false); 19 | if (codeSelection) { 20 | editor.selection = codeSelection; 21 | } 22 | } 23 | } 24 | 25 | export default { 26 | getFormSelection: getFormSelection, 27 | selectCurrentForm: selectCurrentForm 28 | }; -------------------------------------------------------------------------------- /src/extension-test/unit/common/mock.ts: -------------------------------------------------------------------------------- 1 | import * as model from '../../../cursor-doc/model'; 2 | import { LispTokenCursor } from '../../../cursor-doc/token-cursor' 3 | 4 | model.initScanner(20000); 5 | 6 | export class MockDocument implements model.EditableDocument { 7 | selectionLeft: number; 8 | selectionRight: number; 9 | 10 | get selection() { 11 | return new model.ModelEditSelection(this.selectionLeft, this.selectionRight); 12 | } 13 | 14 | set selection(sel: model.ModelEditSelection) { 15 | this.selectionLeft = sel.anchor; 16 | this.selectionRight = sel.active; 17 | } 18 | 19 | model: model.LineInputModel = new model.LineInputModel(1, this); 20 | 21 | selectionStack: model.ModelEditSelection[] = []; 22 | 23 | getTokenCursor(offset?: number, previous?: boolean): LispTokenCursor { 24 | return this.model.getTokenCursor(offset); 25 | }; 26 | 27 | insertString(text: string) { 28 | this.model.insertString(0, text); 29 | }; 30 | 31 | getSelectionText: () => string; 32 | 33 | delete: () => void; 34 | 35 | backspace: () => void; 36 | } -------------------------------------------------------------------------------- /src/providers/hover.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as state from '../state'; 3 | import * as util from '../utilities'; 4 | import * as infoparser from './infoparser'; 5 | 6 | export default class HoverProvider implements vscode.HoverProvider { 7 | state: any; 8 | 9 | constructor() { 10 | this.state = state; 11 | } 12 | 13 | async provideHover(document, position, _) { 14 | 15 | if (util.getConnectedState()) { 16 | let text = util.getWordAtPosition(document, position); 17 | let ns = util.getNamespace(document); 18 | let client = util.getSession(util.getFileType(document)); 19 | if(client) { 20 | await util.createNamespaceFromDocumentIfNotExists(document); 21 | let res = await client.info(ns, text); 22 | return new vscode.Hover(infoparser.getHover(res)); 23 | } 24 | return new vscode.Hover(infoparser.getHoverNotAvailable(text)); 25 | } else { 26 | return new vscode.Hover("Please connect to a REPL to retrieve information."); 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/highlight/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Nikita Prokopov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/files-cache.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as fs from 'fs'; 3 | import * as state from "./state"; 4 | 5 | const filesCache: Map = new Map(); 6 | 7 | function writeToCache(uri: vscode.Uri) { 8 | try { 9 | const content: string = fs.readFileSync(uri.fsPath, "utf8"); 10 | filesCache.set(uri.fsPath, content) 11 | } catch { 12 | // if the file is not readable anymore then don't keep old content in cache 13 | filesCache.delete(uri.fsPath) 14 | } 15 | } 16 | 17 | /** 18 | * Tries to get content of cached file 19 | * @param path - absolute or relative to the project 20 | */ 21 | export const content = (path: string) => { 22 | const resolvedPath = state.resolvePath(path); 23 | if (!filesCache.has(resolvedPath)) { 24 | writeToCache(vscode.Uri.file(resolvedPath)); 25 | const filesWatcher = vscode.workspace.createFileSystemWatcher(resolvedPath); 26 | filesWatcher.onDidChange(writeToCache); 27 | filesWatcher.onDidCreate(writeToCache); 28 | filesWatcher.onDidDelete((uri) => filesCache.delete(uri.fsPath)); 29 | } 30 | return filesCache.get(resolvedPath); 31 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Parts of the software are Copyright (c) 2016-2018 Stian Sivertsen 4 | Other parts are Copyright (c) 2018 -> Better than Tomorrow 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/extension-test/integration/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | timeout: 60000, 10 | reporter: 'mocha-multi-reporters', 11 | reporterOptions: { 12 | "reporterEnabled": "mocha-junit-reporter, spec", 13 | "mochaJunitReporterReporterOptions": { 14 | "mochaFile": "junit/test-results.xml" 15 | } 16 | } 17 | }); 18 | mocha.useColors(true); 19 | 20 | const testsRoot = path.resolve(__dirname, '..'); 21 | 22 | return new Promise((c, e) => { 23 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 24 | if (err) { 25 | return e(err); 26 | } 27 | 28 | // Add files to the test suite 29 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 30 | 31 | try { 32 | // Run the mocha test 33 | mocha.run(failures => { 34 | if (failures > 0) { 35 | e(new Error(`${failures} tests failed.`)); 36 | } else { 37 | c(); 38 | } 39 | }); 40 | } catch (err) { 41 | e(err); 42 | } 43 | }); 44 | }); 45 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # visual code 2 | /*.vsix 3 | # .vscode/settings.json 4 | .vscode-test 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directories 35 | node_modules 36 | jspm_packages 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | 44 | #Macos 45 | .DS_Store 46 | 47 | ## CLJS 48 | .shadow-cljs/ 49 | lib/ 50 | cljs-out/ 51 | test-out/ 52 | 53 | # This and that 54 | .nrepl-port 55 | dist/ 56 | pom.xml 57 | .idea 58 | *.iml 59 | *.bak 60 | 61 | 62 | ## TS 63 | out/ 64 | html/main.js 65 | tsconfig.tsbuildinfo 66 | html/main.js.map 67 | 68 | ## Read The Docs 69 | docs/readthedocs/build 70 | 71 | # Test reporter 72 | junit/test-results.xml 73 | 74 | # Don't know what this is 75 | report.* 76 | 77 | # clojure-lsp 78 | .lsp 79 | -------------------------------------------------------------------------------- /docs/readthedocs/source/remote-development.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Using Calva with Remote Development 8 | 9 | [VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) is a new feature in version 1.35 of VS Code that allows a developer to use a container, remote machine, or the Windows Subsystem for Linux (WSL) as a full-featured development environment. 10 | 11 | I would recommend reading the [introductory blog post](https://code.visualstudio.com/blogs/2019/05/02/remote-development) and watching the videos. I find the feature extremely exciting and wish more IDEs would implement something like it. 12 | 13 | From a Clojure perspective it allows you to have VS Code installed on your Java-less, Clojure-less hardware and still use it to develop Clojure through it. 14 | 15 | ## A use-case 16 | 17 | - for some reason your physical computer has to be running Windows (organisational rules etc.) 18 | - your deployment environment is Linux 19 | - you want to edit files in an editor running on your physical computer 20 | - most Clojure tooling is made with *nix first in mind and there are incompatibilities with Windows 21 | 22 | ## WSL 23 | 24 | See [Using Calva with WSL](wsl.md) 25 | -------------------------------------------------------------------------------- /docs/readthedocs/source/try-first.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Something to Try First (After Connecting) 8 | 9 | You might want to start with evaluating some code. This preferably starts with **Loading Current File and Dependencies**, `ctrl+alt+c enter`. 10 | 11 | Then... Calva has this notion about the ”current” form. Issue the **Evaluate Current Form Inline** command, `ctrl+alt+c e` (`ctrl+alt+c v` on Windows) with the cursor placed in different locations to get a feeling for how the current form is determined. Dismiss the results display with `esc`. 12 | 13 | There is also a command for evaluating the current top level form. Good for evaluating various `def`s `defn`, `defthis`, `defthat`. With your cursor placed anywhere inside such a form, issue the **Evaluate Current Top Level Form (defun)** command (`ctrl+alt+c space`). 14 | 15 | The Top Level command also works inside `(comment ...)` forms, treating the `comment` as creating a new top level context. It is good for in-file code experimentation. To use it place the cursor inside a form contained inside a `(comment...)` and issue the command from there. It looks something like so: 16 | 17 | ![Comment top level form evaluation!](assets/howto/top-level-comment-eval.gif) 18 | 19 | See also 20 | * [Calva Top 10 Commands](commands-top10.md). 21 | * [Code Evaluation Tips](eval-tips.md) 22 | -------------------------------------------------------------------------------- /src/cljs-lib/src/calva/fmt/editor.cljs: -------------------------------------------------------------------------------- 1 | (ns calva.fmt.editor 2 | (:require [calva.fmt.util :as util])) 3 | 4 | 5 | (defn raplacement-edits-for-diffing-lines 6 | "Returns a list of replacement edits to apply to `old-text` to get `new-text`. 7 | Edits will be in the form `[:replace [range] text]`, 8 | where `range` is in the form `[[start-line start-char] [end-line end-char]]`. 9 | NB: The two versions need to have the same amount of lines." 10 | [old-text new-text] 11 | (let [old-lines (util/split-into-lines old-text) 12 | new-lines (util/split-into-lines new-text)] 13 | (->> (map vector (range) old-lines new-lines) 14 | (remove (fn [[line o n]] (= o n))) 15 | (mapv (fn [[line o n]] 16 | {:edit "replace" 17 | :start {:line line 18 | :character 0} 19 | :end {:line line 20 | :character (count o)} 21 | :text n}))))) 22 | 23 | 24 | (comment 25 | (raplacement-edits-for-diffing-lines "foo\nfooo\nbar\nbar" 26 | "foo\nbar\nbaz\nbar") 27 | (->> (map vector 28 | [:foo :foo :foo] 29 | [:foo :bar :foo] 30 | (range)) 31 | (remove (fn [[o n i]] 32 | (= o n)))) 33 | (filter some? 34 | (map (fn [o n line] (when-not (= o n) [o n line])) 35 | [:foo :foo :foo] 36 | [:foo :bar :foo] 37 | (range)))) -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:dependencies [[pez/cljfmt "0.0.4-SNAPSHOT"] 2 | [zprint "0.4.16"]] 3 | 4 | :source-paths ["src/cljs-lib/src" 5 | "src/cljs-lib/test"] 6 | 7 | :builds {:calva-lib 8 | {:target :node-library 9 | :exports {:formatText calva.fmt.formatter/format-text-bridge 10 | :formatTextAtRange calva.fmt.formatter/format-text-at-range-bridge 11 | :formatTextAtIdx calva.fmt.formatter/format-text-at-idx-bridge 12 | :formatTextAtIdxOnType calva.fmt.formatter/format-text-at-idx-on-type-bridge 13 | :cljfmtOptions calva.fmt.formatter/read-cljfmt-js-bridge 14 | :inferIndents calva.fmt.inferer/infer-indents-bridge 15 | :inferParens calva.fmt.inferer/infer-parens-bridge 16 | :jsify calva.js-utils/jsify 17 | :cljify calva.js-utils/cljify 18 | :prettyPrint calva.pprint.printer/pretty-print-js-bridge 19 | :parseEdn calva.parse/parse-edn-js-bridge 20 | :parseForms calva.parse/parse-forms-js-bridge} 21 | :output-to "out/cljs-lib/cljs-lib.js"} 22 | :test 23 | {:target :node-test 24 | :output-to "out/cljs-lib/test/cljs-lib-tests.js" 25 | :ns-regexp ".*" 26 | :autorun true}}} 27 | 28 | -------------------------------------------------------------------------------- /src/calva-fmt/src/config.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as filesCache from '../../files-cache'; 3 | import { cljfmtOptions } from '../../../out/cljs-lib/cljs-lib'; 4 | 5 | const defaultCljfmtContent = "\ 6 | {:remove-surrounding-whitespace? true\n\ 7 | :remove-trailing-whitespace? true\n\ 8 | :remove-consecutive-blank-lines? false\n\ 9 | :insert-missing-whitespace? true\n\ 10 | :align-associative? false}"; 11 | 12 | function configuration(workspaceConfig: vscode.WorkspaceConfiguration, cljfmtString: string) { 13 | return { 14 | "format-as-you-type": workspaceConfig.get("formatAsYouType") as boolean, 15 | "cljfmt-string": cljfmtString, 16 | "cljfmt-options": cljfmtOptions(cljfmtString) 17 | }; 18 | } 19 | 20 | function readConfiguration() { 21 | const workspaceConfig = vscode.workspace.getConfiguration("calva.fmt"); 22 | const configPath: string = workspaceConfig.get("configPath"); 23 | const cljfmtContent: string = filesCache.content(configPath); 24 | const config = configuration(workspaceConfig, cljfmtContent ? cljfmtContent : defaultCljfmtContent); 25 | if (!config["cljfmt-options"]["error"]) { 26 | return config; 27 | } else { 28 | vscode.window.showErrorMessage(`Error parsing ${configPath}: ${config["cljfmt-options"]["error"]}\n\nUsing default formatting configuration.`); 29 | return configuration(workspaceConfig, defaultCljfmtContent) 30 | } 31 | } 32 | 33 | 34 | export function getConfig() { 35 | const config = readConfiguration(); 36 | return config; 37 | } 38 | -------------------------------------------------------------------------------- /src/providers/content.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as state from '../state'; 3 | import * as os from 'os'; 4 | import * as fs from 'fs'; 5 | import * as JSZip from 'jszip'; 6 | 7 | export default class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { 8 | state: any; 9 | 10 | constructor() { 11 | this.state = state; 12 | } 13 | 14 | provideTextDocumentContent(uri, token) { 15 | let current = this.state.deref(); 16 | if (current.get('connected')) { 17 | return new Promise((resolve, reject) => { 18 | let rawPath = uri.path, 19 | pathToFileInJar = rawPath.slice(rawPath.search('!/') + 2), 20 | pathToJar = rawPath.slice('file:'.length); 21 | 22 | pathToJar = pathToJar.slice(0, pathToJar.search('!')); 23 | if (os.platform() === 'win32') { 24 | pathToJar = pathToJar.replace(/\//g, '\\').slice(1); 25 | } 26 | 27 | fs.readFile(pathToJar, (err, data) => { 28 | let zip = new JSZip(); 29 | zip.loadAsync(data).then((new_zip) => { 30 | new_zip.file(pathToFileInJar).async("string").then((value) => { 31 | resolve(value); 32 | }) 33 | }) 34 | }); 35 | }); 36 | } else { 37 | console.warn("Unable to provide textdocumentcontent, not connected to nREPL"); 38 | } 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /test-data/issue418.cljc: -------------------------------------------------------------------------------- 1 | (ns issue418) 2 | 3 | (comment 4 | '(1)| 5 | ;; => Execution error (ClassCastException) at nrepl-pprint-datomic.core/eval9432 (form-init7429449344366744362.clj:12). 6 | ;; class java.lang.Long cannot be cast to class clojure.lang.IFn (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app') 7 | 8 | '|(1) 9 | ;; => Execution error (ClassCastException) at nrepl-pprint-datomic.core/eval9440 (form-init7429449344366744362.clj:16). 10 | ;; class java.lang.Long cannot be cast to class clojure.lang.IFn (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app') 11 | 12 | |'(1) 13 | ;; => Syntax error reading source at (REPL:20:1). 14 | ;; EOF while reading 15 | 16 | @foo| 17 | ;; => Syntax error compiling at (core.cljc:1:8100). 18 | ;; Unable to resolve symbol: foo in this context 19 | 20 | |@foo 21 | ;; => Syntax error reading source at (REPL:28:1). 22 | ;; EOF while reading 23 | 24 | #?(:cljs :foo :clj :bar)| 25 | ;; => Execution error (IllegalArgumentException) at nrepl-pprint-datomic.core/eval7999 (form-init7514181033060422158.clj:32). 26 | ;; Wrong number of args passed to keyword: :cljs 27 | 28 | |#? (:cljs :foo :clj :bar) 29 | ;; => Syntax error reading source at (REPL:36:1). 30 | ;; EOF while reading character 31 | 32 | #{:foo :foo}| 33 | ;; => {:foo :foo} 34 | 35 | #{:foo :foo} 36 | ;; => Syntax error reading source at (REPL:43:1). 37 | ;; EOF while reading character 38 | ) 39 | 40 | -------------------------------------------------------------------------------- /docs/readthedocs/source/finding-commands.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Finding Calva Commands 8 | 9 | Calva relies a lot on that VS Code makes it really easy to find commands by opening the command palette: `ctrl+shift+p` (Windows/Linux), `cmd+shift+p` (Mac), and then start typing some words (or part of words) that you think might be in the command. 10 | 11 | To leverage this, all Calva commands are prefixed with `Calva`. As an example, say you want to evaluate a form and pretty print it. Then you can do this: 12 | 13 | 1. Open the command palette 14 | 2. Type `calevpr` 15 | 16 | VS Code will match `cal` to ”**Cal**va”, `ev` to ”**Ev**aluate”, and `pr` to ”**pr**etty” in ”**pr**etty print”. It looks like so: 17 | 18 | 19 | 20 | As you can see on the screenshot, VS Code will also reveal the keyboard shortcut for the command. My advice is to make it a habit to try to remember those shortcuts and use them for a more effective workflow. 21 | 22 | Now might be a good time to see [Calva Top 10 Commands](commands-top10.md) 23 | 24 | ## All the Settings and Commands 25 | 26 | Did you know? There is a complete list of Calva settings and commands in the *Contributions* tab of the Calva entry in the *Extensions* pane in VS Code. 27 | 28 | ![The Calva Contributions Tab](https://user-images.githubusercontent.com/30010/66733740-c754b800-ee60-11e9-877b-962f6b920cd7.png) 29 | -------------------------------------------------------------------------------- /test-data/projects/pirate-lang/src/pez/pirate_lang.clj: -------------------------------------------------------------------------------- 1 | (ns pez.pirate-lang 2 | (:require [clojure.string :as string])) 3 | 4 | (def english-o {:alphabet "abcdefghijklmnopqrstuvwxyz" 5 | :vowels "aeiou" 6 | :pirate-char "o"}) 7 | 8 | (defn- configure 9 | [{:keys [alphabet vowels pirate-char]}] 10 | (let [alphabet (set (seq (string/upper-case alphabet))) 11 | vowels (set (seq (string/upper-case vowels))) 12 | consonants (set (remove vowels alphabet)) 13 | pirates (if (vowels pirate-char) 14 | vowels 15 | consonants)] 16 | {:pirate-char pirate-char 17 | :pirates pirates})) 18 | 19 | (defn to-pirate-talk 20 | [text language] 21 | (let [{:keys [pirate-char pirates]} (configure language)] 22 | (apply str (mapcat (fn [c] 23 | (if (pirates (first (string/upper-case c))) 24 | (interpose pirate-char (repeat 2 c)) 25 | [c])) 26 | text)))) 27 | 28 | (defn from-pirate-talk 29 | [text language] 30 | (let [{:keys [pirate-char pirates]} (configure language) 31 | pattern (re-pattern (str "(?i)([" (apply str pirates) "])" pirate-char "\\1"))] 32 | (string/replace text pattern "$1"))) 33 | 34 | (comment 35 | (to-pirate-talk "Have you heard about Pirate talk?" english-o) 36 | ;; => "HoHavove yoyou hohearordod aboboutot PoPiroratote totalolkok?" 37 | 38 | (from-pirate-talk "HoHavove yoyou hohearordod aboboutot PoPiroratote totalolkok?" english-o) 39 | ;; => "Have you heard about Pirate talk?" 40 | ) -------------------------------------------------------------------------------- /src/cljs-lib/test/calva/fmt/util_test.cljs: -------------------------------------------------------------------------------- 1 | (ns calva.fmt.util-test 2 | (:require [cljs.test :include-macros true :refer [deftest is]] 3 | [calva.fmt.util :as sut])) 4 | 5 | 6 | #_(deftest log 7 | (is (= (with-out-str (sut/log {:range-text ""} :range-text)) 8 | {:range-text ""}))) 9 | 10 | 11 | (def all-text "(def a 1) 12 | 13 | 14 | (defn foo [x] (let [bar 1] 15 | 16 | bar))") 17 | 18 | 19 | (deftest current-line 20 | (is (= "(def a 1)" (sut/current-line all-text 0))) 21 | (is (= "(def a 1)" (sut/current-line all-text 4))) 22 | (is (= "(def a 1)" (sut/current-line all-text 9))) 23 | (is (= "" (sut/current-line all-text 10))) 24 | (is (= "" (sut/current-line all-text 11))) 25 | (is (= "(defn foo [x] (let [bar 1]" (sut/current-line all-text 12))) 26 | (is (= "(defn foo [x] (let [bar 1]" (sut/current-line all-text 27))) 27 | (is (= "(defn foo [x] (let [bar 1]" (sut/current-line all-text 38))) 28 | (is (= "" (sut/current-line all-text 39))) 29 | (is (= "bar))" (sut/current-line all-text (count all-text))))) 30 | 31 | 32 | (deftest re-pos-one 33 | (is (= 6 34 | (sut/re-pos-first "\\s*x\\s*t$" "foo te x t"))) 35 | (is (= 6 36 | (sut/re-pos-first "\\s*x\\s*t$" "foo te x t"))) 37 | (is (= 5 38 | (sut/re-pos-first "\\s*e\\s*xt\\s*$" "foo te xt"))) 39 | (is (= 173 40 | (sut/re-pos-first "\"\\s*#\\s*\"\\)$" "(create-state \"\" 41 | \"### \" 42 | \" ###\" 43 | \" ### \" 44 | \" # \")")))) 45 | 46 | 47 | (deftest escape-regexp 48 | (is (= "\\.\\*" 49 | (sut/escape-regexp ".*")))) 50 | -------------------------------------------------------------------------------- /src/calva-fmt/src/providers/ontype_formatter.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as formatter from '../format'; 3 | 4 | function continueComment(editor: vscode.TextEditor, document: vscode.TextDocument, position: vscode.Position): Thenable { 5 | const prevLineRange = new vscode.Range(position.with(position.line - 1, 0), position.with(position.line)), 6 | prevLineText: string = document.getText(prevLineRange), 7 | match = prevLineText.match(/^[ \t]*;+[ \t]*/); 8 | if (match) { 9 | const [commentStart] = match; 10 | return editor.edit(edits => edits.insert(position.with(position.line, 0), commentStart), { undoStopAfter: false, undoStopBefore: true }); 11 | } else { 12 | return new Promise((resolve, _reject) => { 13 | resolve(true); 14 | }); 15 | } 16 | } 17 | 18 | export class FormatOnTypeEditProvider { 19 | async provideOnTypeFormattingEdits(document: vscode.TextDocument, _position: vscode.Position, _ch, _options) { 20 | const editor = vscode.window.activeTextEditor; 21 | continueComment(editor, document, editor.selection.active).then(() => { 22 | const pos = editor.selection.active; 23 | if (vscode.workspace.getConfiguration("calva.fmt").get("formatAsYouType")) { 24 | if (vscode.workspace.getConfiguration("calva.fmt").get("newIndentEngine")) { 25 | return formatter.indentPosition(pos, document); 26 | } else { 27 | try { 28 | return formatter.formatPosition(editor, true); 29 | } catch (e) { 30 | return formatter.indentPosition(pos, document); 31 | } 32 | } 33 | } 34 | }); 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assets/images/calva-symbol.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | calva-symbol 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "tsBuildInfoFile": "tsconfig.tsbuildinfo", 5 | "strict": false, /* Enable all strict type-checking options. */ 6 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 7 | // "strictNullChecks": true, /* Enable strict null checks. */ 8 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 9 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 10 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 11 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 12 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ "module": "commonjs", 13 | /* Additional Checks */ 14 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 17 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 18 | "module": "commonjs", 19 | "target": "es6", 20 | "outDir": "out", 21 | "lib": [ 22 | "es7", 23 | "dom" 24 | ], 25 | "sourceMap": true, 26 | "rootDir": "src", 27 | }, 28 | "exclude": [ 29 | "node_modules", 30 | "src/webview", 31 | "src/webview.ts", 32 | ".vscode-test" 33 | ] 34 | } -------------------------------------------------------------------------------- /src/refresh.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as util from './utilities'; 3 | import * as state from './state'; 4 | import { NReplSession } from './nrepl'; 5 | 6 | 7 | function report(res, chan: vscode.OutputChannel) { 8 | if (res.status == "ok") { 9 | chan.appendLine("Reloaded: (" + res.reloaded.join(" ") + ")"); 10 | chan.appendLine(":ok"); 11 | } else { 12 | if (res.status == "error") { 13 | chan.appendLine("Error reloading: " + res.errorNs); 14 | //chan.appendLine(res.error); // TODO: Moar error reporting 15 | } 16 | if (res.err != undefined) { 17 | chan.appendLine(res.err); 18 | } 19 | chan.appendLine(":error 😿"); 20 | } 21 | } 22 | 23 | async function refresh(document = {}) { 24 | let doc = util.getDocument(document), 25 | client: NReplSession = util.getSession(util.getFileType(doc)), 26 | chan: vscode.OutputChannel = state.outputChannel(); 27 | 28 | if (client != undefined) { 29 | chan.appendLine("Reloading..."); 30 | client.refresh().then(res => { 31 | report(res, chan); 32 | }); 33 | } 34 | else { 35 | vscode.window.showErrorMessage("Not connected to a REPL."); 36 | } 37 | } 38 | 39 | async function refreshAll(document = {}) { 40 | let doc = util.getDocument(document), 41 | client: NReplSession = util.getSession(util.getFileType(doc)), 42 | chan: vscode.OutputChannel = state.outputChannel(); 43 | 44 | if (client != undefined) { 45 | chan.appendLine("Reloading all the things..."); 46 | client.refreshAll().then(res => { 47 | report(res, chan); 48 | }); 49 | } 50 | else { 51 | vscode.window.showErrorMessage("Not connected to a REPL."); 52 | } 53 | } 54 | 55 | export default { 56 | refresh, 57 | refreshAll 58 | } -------------------------------------------------------------------------------- /docs/things-to-do.md: -------------------------------------------------------------------------------- 1 | # Things to be done with Calva 2 | 3 | The major themes are: 4 | * Documentation 5 | * Squash Bugs 6 | * The Right Features 7 | * Development Workflow 8 | * Code Maintainability 9 | 10 | ## Documentation 11 | * Video, How to get started with Calva, to support the text on the wiki 12 | * The Calva settings (which ones are not documented on the wiki?) 13 | * Replace some outdated GIFs used in the README. 14 | 15 | ## Squash Bugs 16 | * The test runner sometimes just doesn't work 17 | * The REPL window can't handle large output. Ideas: 18 | * Fix the performance issues (might be a tricky job) 19 | * Truncate large output in the REPL window and print it in an untitled Clojure-enabled editor window instead. 20 | * Implement something like CIDER inspect: https://github.com/BetterThanTomorrow/calva/issues/228 21 | * Make it super easy to use Calva with REBL 22 | 23 | ## The Right Features 24 | * Enable using nrepl in streaming mode 25 | * Use this for the test runner 26 | * Add some basic refactoring support 27 | * Better connection life-cycle control fo shadow-cljs. Either: 28 | 1. Tap in to the shadow message bus (THeller said that there is such a thing that we can query about what shadow-cljs is doing.) 29 | 1. Run Jack-in in a Task proper and see if we can somehow catch the output. (This is needed for shadow-cljs Jack-in.) 30 | * Consider supporting REBL out-of-the-box. 31 | * Add more Calva extension context statuses and use it for more precise command and shortcut enablements. 32 | * A way to get output pasted in a Clojure-enabled editor window. 33 | 34 | ## Development Workflow 35 | * Write an issues template 36 | 37 | ## Code Maintainability 38 | * Organize Calva functionality in ”components”, more like we do with calva-fmt, ParEdit, and Clojure Warrior. 39 | * Get better control of Calva state. 40 | * Clean up extra messy parts of the code. Candidates: 41 | * The evaluations module 42 | * The annotations module (this is particularly brittle) 43 | -------------------------------------------------------------------------------- /docs/readthedocs/source/linting.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Linting 8 | 9 | Calva does no linting, yet with Calva you get excellent linting. That is because Calva bundles the [clj-kondo](https://marketplace.visualstudio.com/items?itemName=borkdude.clj-kondo) extension, which is powered by the [linter with the same name](https://github.com/borkdude/clj-kondo). 10 | 11 | You might want to read about [how to configure clj-kondo](https://github.com/borkdude/clj-kondo/blob/master/doc/config.md#configuration). These two sections might be of extra interest: 12 | * [Exclude unresolved symbols from being reported](https://github.com/borkdude/clj-kondo/blob/master/doc/config.md#exclude-unresolved-symbols-from-being-reported) 13 | * [Lint a custom macro like a built-in macro](https://github.com/borkdude/clj-kondo/blob/master/doc/config.md#lint-a-custom-macro-like-a-built-in-macro) 14 | 15 | If you see clj-kondo squiggle the first character of the file with an error you don't quite understand, it is probably something wrong with your clj-kondo configuration. 16 | 17 | The clj-kondo extension lints the current file as it is being edited. If you want to lint the whole project, use the clj-kondo cli command. See [https://github.com/borkdude/clj-kondo](https://github.com/borkdude/clj-kondo) for more info on that. Windows users might like to know that they too can get a clj-kondo cli command now, via [`npm install -g clj-kondo`](https://twitter.com/borkdude/status/1187622954236071936). It'll be a bit slower to start than the native build, but for sure it's better than not having a clj-kondo command! (Besides, the VS Code extension takes care of the cases where you really want speed.) See [https://github.com/borkdude/clj-kondo/blob/master/doc/install.md#npm-linux-macos-windows](https://github.com/borkdude/clj-kondo/blob/master/doc/install.md#npm-linux-macos-windows) for more on this. -------------------------------------------------------------------------------- /docs/readthedocs/source/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Home 8 | 9 | ![Calva Logo](https://raw.githubusercontent.com/BetterThanTomorrow/calva/dev/assets/calva-64h.png) 10 | 11 | [Calva](https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva) is an integrated REPL powered environment for enjoyable and productive [Clojure](https://clojure.org) and [ClojureScript](https://clojurescript.org) in [Visual Studio Code](https://code.visualstudio.com). It includes inline code evaluation, Paredit, code formatting, a test runner, syntax highlighting, [linting](linting.md), and more. [Calva is open source](https://github.com/BetterThanTomorrow/calva), and free to use. 12 | 13 | ## Getting Started 14 | 15 | First thing you need to do is to get [Calva connected to the REPL of your project](connect.md). Then we suggest you check out [Something to Try First](try-first.md). 16 | 17 | One thing to note about Calva's code formatter is that it sets the default keybinding of the **Format Current Form** command to `tab`. Meaning that most often when things look a bit untidy, you can press `tab` to make things look pretty. Good to know, right? For performance reasons it only formats the current enclosing form, so sometimes you want to move the cursor up/out a form (`ctrl+up`) first. See [The Paredit Guide](paredit.md) for more on moving the cursor structurally through your code. 18 | 19 | ## How to Contribute to Calva? 20 | 21 | I'm glad you asked! Please see the [Calva Development Wiki](https://github.com/BetterThanTomorrow/calva/wiki). (There, in a section labeled **The Tao of Calva**, we also try to make clear *why* you would want to contribute.) 22 | 23 | ## Have Questions and Feedback? Need Help? 24 | 25 | Easiest way is to chat with us and other Calva users. Please join the [#calva channel](https://clojurians.slack.com/messages/calva) on the Clojurians Slack. 26 | 27 | Happy coding! ❤️ 28 | -------------------------------------------------------------------------------- /docs/readthedocs/source/workspace-layouts.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Workspace layouts 8 | 9 | Project directory layouts can vary quite a lot. From the ”template” projects where the Clojure project files are at the root, to, well, let's just say that the project files are not always at the root. And sometimes there is more than one project. 10 | 11 | Calva only really supports working with one project at a time per VS Code window. Here's a short guide for some different setups: 12 | 13 | 1. **You have one project in the workspace, the project files are in there somewhere.** 14 | * Use a regular VS Code ”folder window” or a Workspace proper, both will totally work. 15 | 1. **You have more than one project in the repository, but only really work with one at a tine.** 16 | * Use a Workplace proper and add the different project directories as seperate Workplace Folders. 17 | * You can only jack-in/connect to one project at a time. 18 | 1. **You have more than one project in the repository, and need to work with them in parallell.** 19 | * Open each project you want to work with in a separate VS Code window. 20 | 21 | 22 | ## One Folder - Two Windows? 23 | 24 | As is mentioned in the [Calva Jack-In Guide](jack-in-guide.md), if you have a full stack project using a Clojure backend and a shadow-cljs frontend, you will need to open the same project in two separate VS Code windows, one for the backend and one for the frontend. This is how you can do that: 25 | 26 | 1. Open a new VS Code window. 27 | 2. Select *File->Add Folder to Workspace...*. Save the workspace as, say, `Server.code-workspace`. 28 | 3. Open a new VS Code window. 29 | 2. Select *File->Add Folder to Workspace...*. Save the workspace as, say, `Client.code-workspace`. 30 | 31 | Now, whenever you want to Jack-in to the backend and/or frontend, do it from the **Server** and/or **Client** workspace, respectively. -------------------------------------------------------------------------------- /test-data/lab/pirate_lang.clj: -------------------------------------------------------------------------------- 1 | (ns pez.pirate-lang 2 | (:require [clojure.string :as string])) 3 | 4 | (def english-o {:alphabet "abcdefghijklmnopqrstuvwxyz" 5 | :vowels "aeiou" 6 | :pirate-char "o"}) 7 | 8 | (def swedish-o {:alphabet "abcdefghijklmnopqrstuvwxyzåäö" 9 | :vowels "aeiouåäö" 10 | :pirate-char "o"}) 11 | (defn- configure 12 | [{:keys [alphabet vowels pirate-char]}] 13 | (let [alphabet (set (seq (string/upper-case alphabet))) 14 | vowels (set (seq (string/upper-case vowels))) 15 | consonants (set (remove vowels alphabet)) 16 | pirates (if (vowels pirate-char) 17 | vowels 18 | consonants)] 19 | {:pirate-char pirate-char 20 | :pirates pirates})) 21 | 22 | (defn to-pirate-talk 23 | [text language] 24 | (let [{:keys [pirate-char pirates]} (configure language)] 25 | (apply str (mapcat (fn [c] 26 | (if (pirates (first (string/upper-case c))) 27 | (interpose pirate-char (repeat 2 c)) 28 | [c])) 29 | text)))) 30 | 31 | (defn from-pirate-talk 32 | [text language] 33 | (let [{:keys [pirate-char pirates]} (configure language) 34 | pattern (re-pattern (str "(?i)([" (apply str pirates) "])" pirate-char "\\1"))] 35 | (string/replace text pattern "$1"))) 36 | 37 | (comment 38 | (to-pirate-talk "Have you heard about Pirate talk?" english-o) 39 | ;; => "HoHavove yoyou hohearordod aboboutot PoPiroratote totalolkok?" 40 | 41 | (from-pirate-talk "HoHavove yoyou hohearordod aboboutot PoPiroratote totalolkok?" english-o) 42 | ;; => "Have you heard about Pirate talk?" 43 | 44 | (to-pirate-talk "Har du hört talas om rövarspråket?" swedish-o) 45 | ;; => "HoHaror dodu hohörortot totalolasos omom rorövovarorsospoproråkoketot?" 46 | 47 | (from-pirate-talk "HoHaror dodu hohörortot totalolasos omom rorövovarorsospoproråkoketot?" swedish-o) 48 | ;; => "Har du hört talas om rövarspråket?" 49 | 50 | ) -------------------------------------------------------------------------------- /src/highlight/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.4 - July 12, 2019 2 | 3 | - Fixed trailing whitespace in comment, symbols with word comment in them (#15, #17) 4 | 5 | # 0.2.3 - July 10, 2019 6 | 7 | - Do not merge defaults for `commentFormStyle` and `ignoredFormStyle` 8 | 9 | # 0.2.2 - July 10, 2019 10 | 11 | - Comment decoration (#14 by @PEZ) 12 | 13 | # 0.2.1 - July 4, 2019 14 | 15 | - Removed `configurationDefault` as it was conflicting with Calva (#13) 16 | 17 | # 0.2.0 18 | 19 | - Option to disable rainbow brackets `clojureWarrior.enableBracketColors` (#12) 20 | - Handle setting `clojureWarrior.bracketColors` to empty array (#12) 21 | - Handle config changes when done from Setting GUI 22 | - Disable default `editor.matchBrackets` for Clojure files 23 | 24 | # 0.1.8 25 | 26 | - Highlight and match compound brackets: `#()`, `#{}`, `#?()`, `#?@()` (#10, thx @maratynsky) 27 | 28 | # 0.1.7 29 | 30 | - Show mismatched brackets on scrollbar (#7, #8) 31 | 32 | # 0.1.6 33 | 34 | - Avoid bracket styles bleeding into text typed next to them 35 | 36 | # 0.1.5 37 | 38 | - Don’t show matched brackets inside selection (#4) 39 | - Do not alter `editor.matchBrackets` in config dynamically (#3) 40 | - Showing matching bracket immediately since it’s fast (#5) 41 | - Jump to match should scroll if needed (#2) 42 | 43 | # 0.1.4 44 | 45 | - More distinct rainbow colors by default 46 | - Default settings support both dark and light themes 47 | 48 | # 0.1.3 49 | 50 | - New command: `clojureWarrior.jumpToMatchingBracket` 51 | - New command: `clojureWarrior.selectToMatchingBracket` 52 | - `editor.matchBrackets` is set to false only for Clojure editors 53 | - `matchPairs` is scheduled asynchronously not to slow down text editing 54 | 55 | # 0.1.2 56 | 57 | - Highlight bracket pairs 58 | - Added config param: `"clojureWarrior.matchedBracketStyle": {"backgroundColor": "#E0E0E0"}` 59 | 60 | # 0.1.1 61 | 62 | Added configuration parameters: 63 | - `"clojureWarrior.bracketColors": ["#000", "#999", ...]` 64 | - `"clojureWarrior.cycleBracketColors": true` 65 | - `"clojureWarrior.misplacedBracketStyle": { "border": "2px solid #c33" }` 66 | 67 | # 0.1.0 68 | 69 | - Initial release 70 | - Rainbow Brackets -------------------------------------------------------------------------------- /docs/readthedocs/source/custom-commands.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Running Custom REPL Commands 8 | 9 | Calva supports configuration of custom command snippets that you can execute in the REPL at will. If your workflow has you repeatedly evaluate a particular piece of code, you can use the setting `calva.customREPLCommandSnippets` to configure it and then use the command **Run Custom REPL Command** to access it. The command will give you a menu with the snippets you have configured. 10 | 11 | The `calva.customREPLCommandSnippets` is an object/dictionary with the following fields: 12 | 13 | * `name`: The name of the snippet as it will appear in the picker menu 14 | * `snippet`: The code that will be evaluated 15 | * `ns`: (optional) Namespace to evaluate the command in. If omitted the command will be executed in the namespace of the current editor. 16 | * `replType`: Which REPL window to use for the evaluation. Either `"clj"` or `"cljs"` 17 | 18 | E.g. with these settings: 19 | 20 | ``` 21 | "calva.customREPLCommandSnippets": [ 22 | { 23 | "name": "Foo", 24 | "snippet": "(println :foo)", 25 | "ns": "acme.test.foo-test", 26 | "repl": "cljs" 27 | }, 28 | { 29 | "name": "Bar", 30 | "snippet": "(println :bar)", 31 | "ns": "acme.test.bar-test", 32 | "repl": "clj" 33 | }, 34 | { 35 | "name": "Refresh", 36 | "snippet": "(refresh)", 37 | "repl": "clj" 38 | } 39 | ] 40 | ``` 41 | 42 | You will get this menu. 43 | 44 | image 45 | 46 | The items are numbered for you so that you can choose them in predictable way. The default keyboard shortcut for the command is ctrl+alt+c, .. Which means that to execute the **Refresh** command, `(refresh)`, in the `clj` REPL, you could do: 47 | 48 | ctrl+alt+c, ., 3, ENTER. -------------------------------------------------------------------------------- /assets/styles/_theme-dark.scss: -------------------------------------------------------------------------------- 1 | body.vscode-dark { 2 | .winnage { 3 | color: #0f0; 4 | } 5 | 6 | .match { 7 | border-color: #777; 8 | color: tomato; 9 | } 10 | 11 | .fail-match { 12 | background-color: #f00; 13 | } 14 | 15 | .documentation { 16 | box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.16); 17 | } 18 | 19 | .completion { 20 | box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.16); 21 | } 22 | 23 | .completion .icon { 24 | text-shadow: 0px 1px rgba(0, 0, 0, 0), 0px -1px rgba(0, 0, 0, 0), -1px 0px rgba(0, 0, 0, 0), 25 | 1px 0px rgba(0, 0, 0, 0); 26 | } 27 | 28 | .completion .icon.ic-macro::before { 29 | color: #f80; 30 | } 31 | 32 | .completion .icon.ic-function::before { 33 | color: #f0f; 34 | } 35 | .completion .icon.ic-namespace::before { 36 | color: #080; 37 | } 38 | .completion .icon.ic-special-form::before { 39 | color: #08a; 40 | } 41 | 42 | .caret { 43 | border: solid 1px #aaa; 44 | } 45 | 46 | .is-focused .caret { 47 | border: solid 1px rgba(0, 0, 0, 0); 48 | background-color: #fff; 49 | } 50 | 51 | .id { 52 | color: #d4d4d4; 53 | } 54 | 55 | .kw { 56 | color: #9cdcfe; 57 | } 58 | 59 | .comment { 60 | color: #6a9955; 61 | } 62 | 63 | .lit { 64 | color: #b5cea8; 65 | } 66 | 67 | .str, 68 | .str-start, 69 | .str-inside, 70 | .str-end { 71 | color: #ce9178; 72 | } 73 | 74 | .decl { 75 | color: #c586c0; 76 | } 77 | 78 | .macro { 79 | color: #569cd6; 80 | } 81 | 82 | .stacktrace .stack .name { 83 | color: #fff; 84 | } 85 | 86 | .toggle { 87 | color: #aaa; 88 | } 89 | 90 | .stacktrace.dup .toggle.dup { 91 | color: #666; 92 | } 93 | .stacktrace.java .toggle.java { 94 | color: #666; 95 | } 96 | .stacktrace.tooling .toggle.tooling { 97 | color: #666; 98 | } 99 | .stacktrace.clj .toggle.clj { 100 | color: #666; 101 | } 102 | 103 | .no-source { 104 | opacity: 0.6; 105 | } 106 | } -------------------------------------------------------------------------------- /src/calva-fmt/atom-language-clojure/snippets/language-clojure.cson: -------------------------------------------------------------------------------- 1 | '.source.clojure': 2 | 'ns': 3 | 'prefix': 'ns' 4 | 'body': """ 5 | (ns ${1:name} 6 | (:require [${2:libraries}])) 7 | $0 8 | """ 9 | 10 | 'def': 11 | 'prefix': 'def' 12 | 'body': '(def ${1:symbol} ${2:value})' 13 | 14 | 'defn': 15 | 'prefix': 'defn' 16 | 'body': """ 17 | (defn ${1:name} 18 | [${2:params}] 19 | ${3:body}) 20 | """ 21 | 22 | 'fn': 23 | 'prefix': 'fn' 24 | 'body': """ 25 | (fn [${1:params}] 26 | ${2:body})$0 27 | """ 28 | 29 | 'let': 30 | 'prefix': 'let' 31 | 'body': """ 32 | (let [${1:bindings}] 33 | ${2:body}) 34 | """ 35 | 36 | 'if': 37 | 'prefix': 'if' 38 | 'body': """ 39 | (if ${1:test} 40 | ${2:then} 41 | ${3:else}) 42 | """ 43 | 44 | 'if-let': 45 | 'prefix': 'ifl' 46 | 'body': """ 47 | (if-let [${1:bindings}] 48 | ${2:then} 49 | ${3:else}) 50 | """ 51 | 52 | 'if-not': 53 | 'prefix': 'ifn' 54 | 'body': """ 55 | (if-not ${1:test} 56 | ${2:then} 57 | ${3:else}) 58 | """ 59 | 60 | 'when': 61 | 'prefix': 'when' 62 | 'body': """ 63 | (when ${1:test} 64 | ${2:body}) 65 | """ 66 | 67 | 'when-let': 68 | 'prefix': 'whenl' 69 | 'body': """ 70 | (when-let [${1:bindings}] 71 | ${2:body}) 72 | """ 73 | 74 | 'when-not': 75 | 'prefix': 'whenn' 76 | 'body': """ 77 | (when-not ${1:test} 78 | ${2:body}) 79 | """ 80 | 81 | 'map': 82 | 'prefix': 'map' 83 | 'body': '(map $1 $2)' 84 | 85 | 'map lambda': 86 | 'prefix': 'mapl' 87 | 'body': '(map #($1) $2)' 88 | 89 | 'condp': 90 | 'prefix': 'condp' 91 | 'body': """ 92 | (condp ${1:pred} ${2:expr} 93 | $0) 94 | """ 95 | 96 | 'try': 97 | 'prefix': 'try' 98 | 'body': """ 99 | (try 100 | $1 101 | (catch ${2:exception} e 102 | $3)) 103 | """ 104 | 105 | 'prn': 106 | 'prefix': 'prn' 107 | 'body': '(prn $1)' 108 | 109 | 'println': 110 | 'prefix': 'prnl' 111 | 'body': '(println $1)' 112 | -------------------------------------------------------------------------------- /assets/styles/_theme-light.scss: -------------------------------------------------------------------------------- 1 | body.vscode-light { 2 | .winnage { 3 | color: #0c0; 4 | } 5 | 6 | .match { 7 | border-color: #777; 8 | color:deepskyblue; 9 | } 10 | 11 | .fail-match { 12 | background-color: #c00; 13 | } 14 | 15 | .documentation { 16 | box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.16); 17 | } 18 | 19 | .completion { 20 | box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.16); 21 | } 22 | 23 | .completion .icon { 24 | text-shadow: 0px 1px rgba(0, 0, 0, 0), 0px -1px rgba(0, 0, 0, 0), -1px 0px rgba(0, 0, 0, 0), 25 | 1px 0px rgba(0, 0, 0, 0); 26 | } 27 | 28 | .completion .icon.ic-macro::before { 29 | color: #f80; 30 | } 31 | 32 | .completion .icon.ic-function::before { 33 | color: #f0f; 34 | } 35 | .completion .icon.ic-namespace::before { 36 | color: #080; 37 | } 38 | .completion .icon.ic-special-form::before { 39 | color: #08a; 40 | } 41 | 42 | .caret { 43 | border: solid 1px #aaa; 44 | } 45 | 46 | .is-focused .caret { 47 | border: solid 1px rgba(0, 0, 0, 0); 48 | background-color: #3b3b3b; 49 | } 50 | 51 | .id { 52 | color: #000; 53 | } 54 | 55 | .kw { 56 | color: #b9008b; 57 | } 58 | 59 | .comment { 60 | color: #008000; 61 | } 62 | 63 | .lit { 64 | color: #008d2a; 65 | } 66 | 67 | .str, 68 | .str-start, 69 | .str-inside, 70 | .str-end { 71 | color: #a31515; 72 | } 73 | 74 | .decl { 75 | color: #c800fa; 76 | } 77 | 78 | .macro { 79 | color: #0026ff; 80 | } 81 | 82 | .stacktrace .stack .name { 83 | color: #000; 84 | } 85 | 86 | .toggle { 87 | color: #555; 88 | } 89 | 90 | .stacktrace.dup .toggle.dup { 91 | color: #999; 92 | } 93 | .stacktrace.java .toggle.java { 94 | color: #999; 95 | } 96 | .stacktrace.tooling .toggle.tooling { 97 | color: #999; 98 | } 99 | .stacktrace.clj .toggle.clj { 100 | color: #999; 101 | } 102 | 103 | .no-source { 104 | opacity: 0.5; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/extension-test/unit/debugger/util-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { moveTokenCursorToBreakpoint } from '../../../debugger/util'; 3 | import * as mock from '../common/mock'; 4 | import * as fs from "fs"; 5 | 6 | function getCoordinates(text: string): (string | number)[] { 7 | return text.split('\n')[0].split(',').map(s => { 8 | const coor = s.replace(/;/g, '').trim(); 9 | if (coor.startsWith('"')) { 10 | return coor.replace(/"/g, ''); 11 | } else { 12 | return parseInt(coor); 13 | } 14 | }); 15 | } 16 | 17 | function getTestFileText(fileName: string): string { 18 | return fs.readFileSync(__dirname + '/test-files/' + fileName, 'utf8'); 19 | } 20 | 21 | describe('Debugger Util', async () => { 22 | 23 | let doc: mock.MockDocument; 24 | let debugResponse: any; 25 | 26 | beforeEach(() => { 27 | doc = new mock.MockDocument(); 28 | 29 | debugResponse = { 30 | line: 0, 31 | column: 0 32 | }; 33 | }); 34 | 35 | describe('moveTokenCursorToBreakpoint', () => { 36 | 37 | function expectBreakpointToBeFound(fileName: string) { 38 | const docText = getTestFileText(fileName); 39 | debugResponse.coor = getCoordinates(docText); 40 | doc.insertString(docText); 41 | const tokenCursor = doc.getTokenCursor(0); 42 | moveTokenCursorToBreakpoint(tokenCursor, debugResponse); 43 | expect(tokenCursor.getPrevToken().raw.endsWith('|')).equals(true); 44 | } 45 | 46 | it('simple example', () => { 47 | expectBreakpointToBeFound('simple.clj'); 48 | }); 49 | 50 | it('function shorthand', () => { 51 | expectBreakpointToBeFound('fn-shorthand.clj'); 52 | }); 53 | 54 | it('map', () => { 55 | expectBreakpointToBeFound('map.clj'); 56 | }); 57 | 58 | it('metadata symbol', () => { 59 | expectBreakpointToBeFound('metadata-symbol.clj'); 60 | }); 61 | 62 | it('ignored forms', () => { 63 | expectBreakpointToBeFound('ignored-forms.clj'); 64 | }); 65 | 66 | it('syntax quote', () => { 67 | expectBreakpointToBeFound('syntax-quote.clj'); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test-data/test.clj: -------------------------------------------------------------------------------- 1 | ;; Clojure Warrior 2 | 3 | (()) 4 | (((((((((((((((((((()))))))))))))))))))) 5 | [[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]] 6 | {{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}} 7 | ((()))[[[]]] 8 | ([{}]) 9 | () 10 | [] 11 | {} 12 | 13 | ("--)--") 14 | ("--)-- 15 | --)--") 16 | ();;---) 17 | 18 | (\)\() 19 | ("--\"--)--") 20 | ;; ((())) 21 | ;; ())) "a[{(]})bc" 22 | ;; \ 23 | "()" 24 | ;; \ 25 | ;; 26 | (((#((()))))) 27 | ([ #{ }()[]]) 28 | 29 | 30 | ( 31 | #() 32 | #{} 33 | #?() 34 | #?@() 35 | #_()) 36 | 37 | (if comment :a :b) 38 | 39 | (comment 40 | (comment (foo bar))) 41 | (defn foo 42 | (let [x "y"] 43 | {:foo "bar" 44 | :bar (comment (fn [x] 45 | (let [foo :bar]) 46 | (str foo))) 47 | :baz x})) 48 | (comment 49 | (foo 2) 50 | (Math/abs -1) 51 | (range 10) 52 | (println "I ❤️Clojure") 53 | ([{} () []])) 54 | [comment] 55 | (foo) comment (bar) 56 | "(comment foo)" 57 | foo(comment)bar 58 | (def contains-comment (go-fish)) 59 | ( comment) 60 | ( 61 | comment "[foo]") 62 | (comment 63 | (Math/abs -1) 64 | (range 10) 65 | (println "I ❤️Clojure") 66 | ([{} () []])) 67 | ( comment 68 | foo) 69 | (comment foo (comment bar)) 70 | (foo (comment ({["(comment)"]})) ([{"(comment)"}])) 71 | 72 | #_foo bar 73 | #_ foo bar 74 | #_,foo,bar 75 | #_ 76 | foo bar 77 | #_ 78 | foo 79 | bar 80 | 81 | #_#_(1) (2 2) 82 | 83 | #_ 84 | #_ 85 | #_ 86 | 87 | 1 88 | 89 | 2 90 | 91 | 3 92 | 4 93 | 94 | #_ 95 | #_ 96 | 2 97 | (1 (1)) 98 | 3 99 | 100 | #_ 101 | #_ 102 | #_ 103 | 1 104 | (2 (2)) 105 | (3 3) 106 | 4 107 | 108 | #_#_#_#(1) 2 (3 4) (4) 5 109 | 110 | #_#_#_ (1) (2) (3 4) (4) 5 111 | 112 | #_ (1)2(3) (4) 113 | 114 | #_1(2) 115 | 116 | (let [#_#_x (get c :x "x") 117 | y (get c :y "y") 118 | #_ #_ z (get c :z "z") 119 | å {:ä :ö}] 120 | (str y å #_#_x z)) 121 | 122 | #_x y z 123 | 124 | #_(:bar [#{foo}]) 125 | ([{#_"foo"}]) 126 | [:a 127 | #_ 128 | [:b 129 | [:c {:d :e}]] 130 | [:b 131 | [:c {:d :e}]]] 132 | (comment 133 | (foo #_"bar" baz)) 134 | #_{:foo "foo" 135 | :bar (comment [["bar"]])} 136 | #_^{:foo foo} ^{:foo foo} 137 | #_@foo @foo 138 | #_@(foo bar) @(foo bar) 139 | #_'(foo bar) '(foo bar) 140 | #_`(foo bar) `(foo bar) 141 | #_~(foo bar) ~(foo bar) 142 | #_'foo 'foo 143 | #_#foo #foo 144 | #_@foo @foo 145 | #_~foo ~foo 146 | #_#"foo\sbar" #"foo\sbar" -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | * DONE Migrate testRunner to use nrepl, and clean up 2 | * DONE Migrate connect, and add cljs support 3 | * DONE send cljs stuff to the right repl 4 | * DONE add pretty printing back to evaluate 5 | * DONE Move paredit here 6 | * DONE Bind paredit keys to Repl console 7 | * DONE Jack-in 8 | * DONE clj 9 | * DONE boot 10 | * DONE shadow-cljs - select watch project(s) 11 | * DONE lein 12 | 13 | * DONE Save as workspace configuration. 14 | 15 | * DONE kill/restart jack-in process command 16 | 17 | * DONE Cljs repls 18 | * DONE figwheel 19 | * DONE figwheel-main 20 | * DONE shadow-cljs 21 | * DONE inject piggieback 22 | * DONE { :repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}} in lein 23 | * DONE clj 24 | 25 | * DONE prompt for clj aliases 26 | * DONE prompt for lein profiles 27 | 28 | * DONE fix repl window to show stack traces 29 | * DONE add interrupt support to repl window 30 | * DONE make it clear that a repl window session has been disconnected. 31 | * DONE replace eval to terminal stuff with new repl stuff. 32 | * DONE re-connect repl window session when reconnected. 33 | * DONE support session disconnection messages - route them to the right window. 34 | * DONE webviews do not receive postMessages when not in the foreground. buffer them and blast them when it focuses. 35 | * DONE repl focus needs to be made sane. 36 | * DONE hollow cursor when not focused. 37 | * DONE don't snatch focus away from selection 38 | * DONE update status.update() to show repl type when focused. 39 | * DONE Repl completions 40 | * DONE context! 41 | * DONE up/down navigate completion 42 | * DONE tab complete 43 | * DONE documentation 44 | * DONE PageUp/PageDown in completion pane 45 | * DONE Only show completions on type or Ctrl+Space (repl-interactor support) 46 | * DONE Use tonsky font in project, don't use cdn... 47 | * DONE Preserve Repl history. 48 | * DONE clj/cljs status notification bugged with webviews because vscode is buggy. 49 | * DONE Open new cljs repl 50 | * TODO Ship first stab at repl? 51 | 52 | * DONE Move calva-fmt here. 53 | * DONE Use model from repl-interactor in vscode 54 | * TODO Select toplevel forms etc, should use model <<<< 55 | * TODO Use our Paredit logic for calva proper. 56 | * TODO Complete Jack-in 57 | * TODO Argument-based completions for vscode 58 | * TODO Remove diagnostics in docmirror that ensure the model doesn't desync from the document. -------------------------------------------------------------------------------- /src/analytics.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as UA from 'universal-analytics'; 3 | import * as uuid from "uuid/v4"; 4 | import * as os from 'os'; 5 | 6 | // var debug = require('debug'); 7 | // debug.log = console.info.bind(console); 8 | 9 | 10 | function userAllowsTelemetry(): boolean { 11 | const config = vscode.workspace.getConfiguration('telemetry'); 12 | return config.get('enableTelemetry', false); 13 | } 14 | 15 | export default class Analytics { 16 | private visitor: UA.Visitor; 17 | private extension: vscode.Extension; 18 | private extensionVersion: string; 19 | private store: vscode.Memento; 20 | private GA_ID = (process.env.CALVA_DEV_GA ? process.env.CALVA_DEV_GA : 'FUBAR-69796730-3').replace(/^FUBAR/, "UA"); 21 | 22 | constructor(context: vscode.ExtensionContext) { 23 | this.extension = vscode.extensions.getExtension("betterthantomorrow.calva")!; 24 | this.extensionVersion = this.extension.packageJSON.version; 25 | this.store = context.globalState; 26 | 27 | this.visitor = UA(this.GA_ID, this.userID()); 28 | this.visitor.set("cd1", this.extensionVersion); 29 | this.visitor.set("cd2", vscode.version); 30 | this.visitor.set("cd3", this.extensionVersion); 31 | this.visitor.set("cd4", `${os.platform()}/${os.release()}`); 32 | this.visitor.set("cn", `calva-${this.extensionVersion}`); 33 | this.visitor.set("ua", `Calva/${this.extensionVersion} (${os.platform()}; ${os.release()}; ${os.type}) VSCode/${vscode.version}`); 34 | } 35 | 36 | private userID(): string { 37 | const KEY = 'userLogID'; 38 | if (this.store.get(KEY) == undefined) { 39 | const newID = uuid(); 40 | this.store.update(KEY, newID) 41 | return newID; 42 | } else { 43 | return this.store.get(KEY); 44 | } 45 | } 46 | 47 | logPath(path: string): Analytics { 48 | if (userAllowsTelemetry()) { 49 | this.visitor.pageview(path); 50 | } 51 | return this; 52 | } 53 | 54 | logEvent(category: string, action: string, label?: string, value?: string): Analytics { 55 | if (userAllowsTelemetry()) { 56 | this.visitor.event({ ec: category, ea: action, el: label, ev: value }); 57 | } 58 | return this; 59 | } 60 | 61 | send() { 62 | if (userAllowsTelemetry()) { 63 | this.visitor.send(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/calva-fmt/atom-language-clojure/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------- 23 | 24 | This package was derived from a TextMate bundle located at 25 | https://github.com/mmcgrana/textmate-clojure and distributed under the 26 | following license, located in `LICENSE.md`: 27 | 28 | The MIT License (MIT) 29 | 30 | Copyright (c) 2010- Mark McGranaghan 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in all 40 | copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 48 | SOFTWARE. 49 | -------------------------------------------------------------------------------- /src/calva-fmt/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as state from '../../state'; 3 | import { FormatOnTypeEditProvider } from './providers/ontype_formatter'; 4 | import { RangeEditProvider } from './providers/range_formatter'; 5 | import * as formatter from './format'; 6 | import * as inferer from './infer'; 7 | import * as docmirror from "../../doc-mirror" 8 | import * as config from './config' 9 | 10 | function getLanguageConfiguration(autoIndentOn: boolean): vscode.LanguageConfiguration { 11 | return { 12 | wordPattern: /[^\s,#()[\]{};"\\\@\']+/, 13 | onEnterRules: autoIndentOn ? [ 14 | // This is madness, but the only way to stop vscode from indenting new lines 15 | { 16 | beforeText: /.*/, 17 | action: { 18 | indentAction: vscode.IndentAction.Outdent, 19 | removeText: Number.MAX_VALUE 20 | } 21 | }, 22 | ] : [], 23 | comments: { 24 | lineComment: ';;', 25 | blockComment: ['(comment\n', ')'] 26 | } 27 | } 28 | } 29 | 30 | 31 | export function activate(context: vscode.ExtensionContext) { 32 | docmirror.activate(); 33 | vscode.languages.setLanguageConfiguration("clojure", getLanguageConfiguration(config.getConfig()["format-as-you-type"])); 34 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.formatCurrentForm', formatter.formatPositionCommand)); 35 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.alignCurrentForm', formatter.alignPositionCommand)); 36 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.inferParens', inferer.inferParensCommand)); 37 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.tabIndent', (e) => { inferer.indentCommand(e, " ", true) })); 38 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.tabDedent', (e) => { inferer.indentCommand(e, " ", false) })); 39 | context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider(state.documentSelector, new FormatOnTypeEditProvider, "\r", "\n")); 40 | context.subscriptions.push(vscode.languages.registerDocumentRangeFormattingEditProvider(state.documentSelector, new RangeEditProvider)); 41 | vscode.window.onDidChangeActiveTextEditor(inferer.updateState); 42 | vscode.workspace.onDidChangeConfiguration(e => { 43 | if (e.affectsConfiguration("calva.fmt.formatAsYouType")) { 44 | vscode.languages.setLanguageConfiguration("clojure", getLanguageConfiguration(config.getConfig()["format-as-you-type"])); 45 | } 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/paredit/statusbar.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { window, StatusBarAlignment, StatusBarItem } from 'vscode'; 3 | import statusbar from '../statusbar'; 4 | import * as paredit from './extension'; 5 | 6 | export class StatusBar { 7 | 8 | private _enabled: Boolean; 9 | private _visible: Boolean; 10 | private _keyMap: String; 11 | 12 | private _toggleBarItem: StatusBarItem; 13 | 14 | constructor(keymap: String) { 15 | this._toggleBarItem = window.createStatusBarItem(StatusBarAlignment.Right); 16 | this._toggleBarItem.text = "(λ)"; 17 | this._toggleBarItem.tooltip = ""; 18 | this._toggleBarItem.command = 'paredit.togglemode'; 19 | this._enabled = false; 20 | this._visible = false; 21 | this.keyMap = keymap; 22 | 23 | paredit.onPareditKeyMapChanged((keymap) => { 24 | this.keyMap = keymap; 25 | }) 26 | } 27 | 28 | get keyMap() { 29 | return this._keyMap; 30 | } 31 | 32 | set keyMap(keymap: String) { 33 | 34 | switch (keymap.trim().toLowerCase()) { 35 | case 'original': 36 | this._keyMap = 'original'; 37 | this.enabled = true; 38 | this.visible = true; 39 | this._toggleBarItem.text = "(λ)"; 40 | this._toggleBarItem.tooltip = "Toggle to strict Mode" 41 | break; 42 | case 'strict': 43 | this._keyMap = 'strict'; 44 | this.enabled = true; 45 | this.visible = true; 46 | this._toggleBarItem.text = "[λ]"; 47 | this._toggleBarItem.tooltip = "Toggle to original Mode" 48 | break; 49 | default: 50 | this._keyMap = 'none'; 51 | this.enabled = false; 52 | this.visible = true; 53 | this._toggleBarItem.text = "λ"; 54 | this._toggleBarItem.tooltip = "Calva Paredit Keymap is set to none, Toggle to Strict Mode is Disabled" 55 | } 56 | } 57 | 58 | get enabled() { 59 | return this._enabled; 60 | } 61 | 62 | set enabled(value: Boolean) { 63 | this._enabled = value; 64 | 65 | if (this._enabled) { 66 | this._toggleBarItem.color = undefined; 67 | } else { 68 | this._toggleBarItem.color = statusbar.color.inactive; 69 | } 70 | } 71 | 72 | get visible(): Boolean { 73 | return this._visible; 74 | } 75 | 76 | set visible(value: Boolean) { 77 | if (value) { 78 | this._toggleBarItem.show(); 79 | } else { 80 | this._toggleBarItem.hide(); 81 | } 82 | } 83 | 84 | dispose() { 85 | this._toggleBarItem.dispose(); 86 | } 87 | } -------------------------------------------------------------------------------- /docs/readthedocs/source/vim.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Calva and the VIM Extension 8 | 9 | First thing first. The [VIM Extension](https://github.com/VSCodeVim/Vim) and Calva has some friction between them. Expect it to be a bit painful. 10 | 11 | ## Selection commands 12 | 13 | Calva's various structural selection commands [do not put VIM into VISUAL mode](https://github.com/BetterThanTomorrow/calva/issues/297). This is true for many [VS Code selection scenarios](https://github.com/VSCodeVim/Vim/issues/2224) too, so it is not really Calva's fault, but it will be problematic for VIM Extension Calva users, regardless. 14 | 15 | ## Key bindings 16 | 17 | In general Calva's default key bindings are not very VI-ish. **This is a call for someone to share their VIM re-mappings**. 18 | 19 | ### Expand selection 20 | 21 | Calva binds **expand selection** to `ctrl+w`. This conflicts with the VIM Extension's default mapping of window splitting shortcuts. You'll need to remap it either with Calva or with the VIM Extension. 22 | 23 | ### The `esc` key 24 | 25 | Calva binds the `esc` key to dismiss the display of inline results. This gets into conflict with any `vi` coding since `esc` then is used to go back to command mode. You can either fix Calva's default keybinding or the VIM extension. 26 | 27 | Alternatively, you can use the native Vim command `Ctrl + [` to escape and get back to command mode. Rebinding your keyboard's CapsLock key to Control may make this even easier. 28 | 29 | #### Remap Calva's `clearInlineResults` 30 | 31 | - Open the Keyboard Shortcuts JSON file from the Command Palette 32 | - Disable `clearInlineResults` and remap the command e.g. 33 | 34 | ``` 35 | // Place your key bindings in this file to override the defaults 36 | [ 37 | { 38 | "key": "escape", 39 | "command": "-calva.clearInlineResults" 40 | }, 41 | { 42 | "key": "shift+escape", 43 | "command": "calva.clearInlineResults", 44 | "when": "editorTextFocus && !editorHasMultipleSelections && !editorReadOnly && !hasOtherSuggestions && !suggestWidgetVisible && editorLangId == 'clojure'" 45 | }, 46 | ] 47 | ``` 48 | 49 | If you run into issues, refer to the commands in the default Keyboard Shortcuts JSON file. 50 | 51 | #### Remap Vim's Insert Mode 52 | 53 | Remap vim's insert mode keybinding to go into command mode by adding the following to your user settings: 54 | 55 | ```json 56 | "vim.insertModeKeyBindings": [ 57 | { 58 | "before": ["j", "k"], 59 | "after": [""] 60 | } 61 | ] 62 | ``` 63 | 64 | (Change `before` to whatever keybinding you are comfortable with!) 65 | 66 | ## No Vim mode for the REPL Window 67 | 68 | This is not likely to be fixed anytime soon. (Because, a **lot** of work.) 69 | 70 | -------------------------------------------------------------------------------- /docs/readthedocs/source/quirks.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Quirks 8 | 9 | Here's a shocker for ya': Calva isn't perfect. 😄 10 | 11 | There are quirks and things that flat out do not work. We'll try to collect info about such things here, providing workarounds when available (or, rather, known to us). 12 | 13 | ## Test features not available with ClojureScript 14 | 15 | Currently [`cider-nrepl` does not provide its test functionality for ClojureScript](https://github.com/clojure-emacs/cider-nrepl/issues/555) code. Please consider contributing to fixing that. 16 | 17 | ## Using with Parinfer 18 | 19 | Calva defaults to formatting as you type. If you use Parinfer this creates a conflict, since it auto-indents your code. If you want to use Parinfer you'll have to tell Calva not to do auto-formatting by disabling `calva.fmt.formatAsYouType`. 20 | 21 | However, with VS Code and Calva it is probably better to learn to use [Paredit](paredit.md)'s **slurp** and **barf** and generally use Calva's automatic formatting. 22 | 23 | 24 | ## MacOS and the Slurp and Barf Keyboard Shortcuts 25 | 26 | To make slurping and barfing forward really easy to perform they are bound to `ctrl+right` and `ctrl+left`, respectively. However on MacOS those shortcuts are sometimes bound by Mission Control, causing the Calva shortcuts to not work. One way to solve it is to disable the shortcuts in *System Preferences -> Keyboard -> Shortcuts*: 27 | 28 | ![Disable Mission Control Shortcuts](assets/mission-control-shortcuts.gif) 29 | 30 | ## Calva and the VIM Extension 31 | 32 | See [Using Calva with the VIM Extension](vim.md). 33 | 34 | ## ”Command not found” errors on Jack-in 35 | 36 | [Jack-in](jack-in-guide.md) starts by spawning of a command in the shell. You will need the commands used installed on your computer: 37 | 38 | * `clojure` for tools.deps/Clojure CLI 39 | * `lein` for Leiningen 40 | * `npx` for shadow-cljs 41 | 42 | Also, in some circumstances VS Code is not spawned from a shell with the environment variables, expecially `$PATH`, which might mean that even though you have the tools installed, they are not found when VS Code/Calva tries to execute them. To fix this you will need to do one of these two things: 43 | 44 | 1. Figure out from where VS Code is spawned, and make sure the `$PATH` there includes the directoy with the needed binary. 45 | 1. Start VS Code from a terminal where the `$PATH` is correctly configured. (Using the `code` commmand.) 46 | 47 | See [this issue](https://github.com/BetterThanTomorrow/calva/issues/591) for more clues on this problem. 48 | 49 | ## Strange linting errors? 50 | 51 | This is not really a quirk, and most linting errors are not strange when you learn about why they are there. Calva does not do any linting, btw, see also [linting](linting.md). 52 | -------------------------------------------------------------------------------- /src/printer.ts: -------------------------------------------------------------------------------- 1 | import * as state from './state'; 2 | 3 | export type PrettyPrintingOptions = { 4 | enabled: boolean, 5 | printEngine?: 'calva' | 'pprint' | 'fipp' | 'puget' | 'zprint', 6 | width: number, 7 | maxLength?: number, 8 | maxDepth?: number 9 | }; 10 | 11 | export const disabledPrettyPrinter: PrettyPrintingOptions = { 12 | enabled: false, 13 | printEngine: undefined, 14 | width: undefined, 15 | maxLength: undefined, 16 | maxDepth: undefined 17 | }; 18 | 19 | function getPrinter(pprintOptions: PrettyPrintingOptions, printerFn: string, widthSlug: string, lengthSlug: string, depthsSlug: string, moreOptions = {}) { 20 | const PRINTER_FN = 'nrepl.middleware.print/print', 21 | OPTIONS = 'nrepl.middleware.print/options'; 22 | let printer = {}; 23 | printer[OPTIONS] = moreOptions; 24 | printer[PRINTER_FN] = printerFn; 25 | printer[OPTIONS][widthSlug] = pprintOptions.width; 26 | if (pprintOptions.maxLength && lengthSlug !== undefined) { 27 | printer[OPTIONS][lengthSlug] = pprintOptions.maxLength; 28 | } 29 | if (pprintOptions.maxDepth && depthsSlug !== undefined) { 30 | printer[OPTIONS][depthsSlug] = pprintOptions.maxDepth; 31 | } 32 | return printer; 33 | } 34 | 35 | const zprintExtraOptions = { 36 | // Can't do this, because `bencode` translates `false` to 0, and `zprint` does not approve (yet, Kim is looking into relaxing this) 37 | // "record": { 38 | // "to-string?": true 39 | // } 40 | } 41 | 42 | export function getServerSidePrinter(pprintOptions: PrettyPrintingOptions) { 43 | if (pprintOptions.enabled && pprintOptions.printEngine !== 'calva') { 44 | switch (pprintOptions.printEngine) { 45 | case "pprint": 46 | return getPrinter(pprintOptions, 'cider.nrepl.pprint/pprint', 'right-margin', 'length', 'level'); 47 | case "fipp": 48 | return getPrinter(pprintOptions, 'cider.nrepl.pprint/fipp-pprint', 'width', 'print-length', 'print-level'); 49 | case "puget": 50 | return getPrinter(pprintOptions, 'cider.nrepl.pprint/puget-pprint', 'width', 'seq-limit', undefined); 51 | case "zprint": 52 | return getPrinter(pprintOptions, 'cider.nrepl.pprint/zprint-pprint', 'width', 'max-length', 'print-depth', zprintExtraOptions); 53 | default: 54 | return undefined; 55 | } 56 | } 57 | return undefined; 58 | } 59 | 60 | export function prettyPrintingOptions(): PrettyPrintingOptions { 61 | return state.config().prettyPrintingOptions; 62 | } 63 | 64 | export const zprintDependencies = { 65 | "zprint": "0.4.16" 66 | } 67 | 68 | export function getServerSidePrinterDependencies() { 69 | if (prettyPrintingOptions().printEngine === 'zprint') { 70 | return zprintDependencies; 71 | } else { 72 | return {} 73 | } 74 | } -------------------------------------------------------------------------------- /src/calva-fmt/update-grammar.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /** 7 | * MIT License 8 | * 9 | * Copyright (c) 2015 - present Microsoft Corporation 10 | * 11 | * All rights reserved. 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in all 21 | * copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | * 31 | */ 32 | 33 | 'use strict'; 34 | 35 | var path = require('path'); 36 | var fs = require('fs'); 37 | var cson = require('cson-parser'); 38 | 39 | exports.update = function (contentPath, dest) { 40 | console.log('Reading from ' + contentPath); 41 | fs.readFile(contentPath, (_err, content) => { 42 | var grammar = cson.parse(content); 43 | let result = { 44 | information_for_contributors: [ 45 | 'This file is generated from ' + contentPath 46 | ] 47 | }; 48 | 49 | let keys = ['name', 'scopeName', 'comment', 'injections', 'patterns', 'repository']; 50 | for (let key of keys) { 51 | if (grammar.hasOwnProperty(key)) { 52 | result[key] = grammar[key]; 53 | } 54 | } 55 | 56 | try { 57 | fs.writeFileSync(dest, JSON.stringify(result, null, '\t').replace(/\n/g, '\r\n')); 58 | console.log('Updated ' + path.basename(dest)); 59 | } catch (e) { 60 | console.error(e); 61 | process.exit(1); 62 | } 63 | }); 64 | }; 65 | 66 | if (path.basename(process.argv[1]) === 'update-grammar.js') { 67 | exports.update(process.argv[2], process.argv[3]); 68 | } 69 | -------------------------------------------------------------------------------- /src/highlight/README.md: -------------------------------------------------------------------------------- 1 | # Clojure Warrior 2 | 3 | 4 | 5 | Visual Studio Code extension for Clojure development 6 | 7 | ## Features 8 | 9 | Rainbow brackets: 10 | 11 | - Chooses bracket color based on nesting level 12 | - Distinct bracket colors, plays well with [Alabaster theme](https://marketplace.visualstudio.com/items?itemName=tonsky.theme-alabaster) 13 | - Properly handles strings, comments and escaped characters 14 | - Highlights misplaced brackets 15 | 16 | Bracket pair matching: 17 | 18 | - Higlights corresponding bracket pair to the one under the cursor 19 | - Considers bracket directon and cursor position relative to it 20 | - Only highlights pair when cursor is standing _outside_ the expression (right after the closed bracket or right before opening one) 21 | 22 | Jump to matching bracket commands: 23 | 24 | - Jump to corresponding bracket pair (same rules as in bracket pair matching): `clojureWarrior.jumpToMatchingBracket` 25 | - Select a region between cursor and matching bracket (including brackets): `clojureWarrior.selectToMatchingBracket` 26 | 27 | ![Screenshot](https://raw.githubusercontent.com/tonsky/clojure-warrior/master/extras/screenshot.png) 28 | 29 | ## Configuration 30 | 31 | | Key | Meaning | Example | 32 | | --- | ------- | ------- | 33 | | `"clojureWarrior.enableBracketColors"` | Enable rainbow colors | `true` | 34 | | `"clojureWarrior.bracketColors"` | Which colors to use | `["#000", "#999"]` | 35 | | `"clojureWarrior.cycleBracketColors"` | Whether same colors should be reused for deeply nested brackets | `true` | 36 | | `"clojureWarrior.misplacedBracketStyle"` | Style of misplaced bracket | `{ "border": "2px solid #c33" }` | 37 | | `"clojureWarrior.matchedBracketStyle"` | Style of bracket pair highlight | `{"backgroundColor": "#E0E0E0"}` | 38 | | `"clojureWarrior.commentFormStyle"` | Style of `(comment ...)` form | `{"textDecoration": "none; opacity: 0.5"}` | 39 | | `"clojureWarrior.ignoredFormStyle"` | Style of `#_...` form | `{"textDecoration": "none; opacity: 0.5"}` | 40 | 41 | To disable VS Code default bracket matching for Clojure files, add this to `settings.json`: 42 | 43 | ``` 44 | "[clojure]": { 45 | "editor.matchBrackets": false 46 | } 47 | ``` 48 | 49 | ## Installation 50 | 51 | 1. Go to `Extensions` 52 | 2. Search for `Clojure Warrior` 53 | 3. Install 54 | 4. Restart Visual Studio Code (or click `Reload window`) 55 | 5. Open a Clojure/ClojureScript/EDN file 56 | 57 | ## Workign on Clojure Warrior 58 | 59 | Compiling: 60 | 61 | ``` 62 | cd clojure-warrior 63 | npm install 64 | npm run watch 65 | ``` 66 | 67 | Installing dev version locally: 68 | 69 | ``` 70 | ln -s `pwd` ~/.vscode/extensions/tonsky.clojure-warrior-0.2.0 71 | ``` 72 | 73 | Publishing: 74 | 75 | ``` 76 | vsce publish 77 | ``` 78 | 79 | ## License 80 | 81 | [MIT License](https://github.com/tonsky/clojure-warrior/blob/master/./LICENSE.txt) 82 | -------------------------------------------------------------------------------- /assets/webview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
11 |
12 | 17 |
18 |
19 | 20 |
21 | 22 |
23 |
24 |
25 | 26 |
27 | 28 |
29 |
30 |
31 |
32 |

The REPL prompt is a multiline, Paredit powered, Clojure editor. Some useful shortcuts are: 33 |

    34 |
  • Alt+Up/Down
  • Navigate the history
  • 35 |
  • Alt+Enter
  • Submit the current line
  • 36 |
  • Ctrl+Z
  • Undo an editor step
  • 37 |
  • Ctrl+Shift+Z
  • Redo an editor step
  • 38 |
  • Ctrl+D
  • Interrupt a running evaluation
  • 39 |
  • Ctrl+L
  • Clear the REPL window
  • 40 |
41 |

42 |

Make it a habit to start any Calva session by loading a file. 43 | Use Ctrl+Alt+C Enter in the code editor to load (evaluate) the active file. 44 | Many things do not work before that is done. 45 |

46 |

See also the Calva User Documentation.

47 |
48 |
49 |
50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /assets/images/cljs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | cljs 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/readthedocs/source/commands-top10.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # The Top 10 Calva Commands 8 | 9 | There are not all that many Calva commands, you can learn them all. But there are a few anyway and not all commands are created equal. Here are the most important ones to know about for effective Clojure/ClojureScript coding: 10 | 11 | * **Grow/expand selection**: `ctrl+w` 12 | * **Load current file**: `alt+ctrl+c enter`, evaluates the namespace code in the active editor tab. This also loads any required namespaces, and generally gives Calva what it needs to work. 13 | * **Evaluate current form**: `alt+ctrl+c e` (`alt+ctrl+c v` on Windows), finds the form from the cursor position, evaluates it and displays the result inline. Hit `esc` to dismiss the results display. 14 | * **Evaluate current top-level form**: `alt+ctrl+c space`: inline evaluate the current top-level form. This also works inside `(comment)` forms. Use it to (re)define vars and then inside comment forms you can verify that they do what you want them to do. 15 | * **Dismiss the display of results**: `escape`: (VIM Extension users should read [Using Calva with the VIM Extension](vim.md)). 16 | 17 | The evaluation commands often have an equivalent for when you want to use the REPL window for further exploration. (Basically you add the `ctrl+alt` modifier to the second chord in the shortcuts): 18 | 19 | * `alt+ctrl+c ctrl+alt+e`: to evaluate the current form in the REPL window. 20 | * `alt+ctrl+c ctrl+alt+space`: to evaluate the current top-level form in this window 21 | * **Load current namespace in the REPL window** `alt+ctrl+c ctrl+alt+n` 22 | 23 | * **Toggle pretty printing** of results on and off: `ctrl+alt+c p`. It's on by default. There is a status bar button showing the status and that also can be used to toggle the setting. 24 | 25 | 26 | ## Some More Commands to Try 27 | - Code evaluation 28 | - Evaluate code and add as comment: `ctrl+alt+c c` (current form), `ctrl+alt+c ctrl space` (current _top level_ form) 29 | - Evaluate code and replace it in the editor, inline: `ctrl+alt+c r` 30 | - Integrated REPLs 31 | - Load current namespace in the REPL window: `ctrl+alt+c ctrl+alt+n` 32 | - Evaluate current editor form in the REPL window: `ctrl+alt+c ctrl+alt+e` (`ctrl+alt+c ctrl+alt+v` on Windows) 33 | - Evaluate current editor top level form in the REPL window: `ctrl+alt+c ctrl+space` 34 | - Running tests and mark failures and errors in the Problems pane 35 | - Run namespace tests: `ctrl+alt+c t` 36 | - Run all tests: `ctrl+alt+c shift+t` 37 | - Run current test: `ctrl+alt+c ctrl+alt+t` 38 | - Rerun previously failing tests: `ctrl+alt+c ctrl+t` 39 | - **Caveat**: Right now the tests are reported only when all are run, making it painful to run all tests in larger projects. I'll fix it. Promise! 40 | - Select current form: `ctrl+alt+c s`. 41 | - Run custom commands, i.e. code snippets, at will: `ctrl+alt+c .` 42 | 43 | 44 | See also: 45 | 46 | * [Code Evaluation Tips](eval-tips.md) 47 | * [Finding Calva Commands and Shortcuts](finding-commands.md) 48 | -------------------------------------------------------------------------------- /src/webview/hotkeys.ts: -------------------------------------------------------------------------------- 1 | export const ALT = 1; 2 | export const CTRL = 2; 3 | export const SHIFT = 4; 4 | export const META = 8; 5 | 6 | const isMac = navigator.platform.match(/Mac(Intel|PPC|68k)/i); // somewhat optimistic this would run on MacOS8 but hey ;) 7 | 8 | let keyToId: {[id: string]: number} = {} 9 | let idToKey: {[id: string]: string} = {} 10 | 11 | interface CommandWidget { 12 | commands: {[id: string]: () => void}; 13 | } 14 | 15 | function key(name: string, id: number) { 16 | keyToId[name.toLowerCase()] = id; 17 | idToKey[id] = name; 18 | } 19 | 20 | key("Backspace", 8) 21 | key("Space", 0x20) 22 | key("Tab", 9) 23 | key("Return", 13) 24 | key("End", 35) 25 | key("/", 191) 26 | key("[", 219) 27 | 28 | key("Home", 36) 29 | key("LeftArrow", 37) 30 | key("UpArrow", 38) 31 | key("RightArrow", 39) 32 | key("DownArrow", 40) 33 | key("Delete", 46) 34 | 35 | export function parseHotKey(key: string, command: any) { 36 | let parts = key.split("+").map(x => x.trim().toLowerCase()); 37 | let i=0; 38 | let modifiers = 0; 39 | outer: for(; i { 84 | table: HotKey[] = []; 85 | constructor(keys: {[id: string]: keyof T["commands"]}) { 86 | for(let key in keys) 87 | this.table.push(parseHotKey(key, keys[key])); 88 | } 89 | 90 | execute(obj: T, e: KeyboardEvent) { 91 | for(let key of this.table) { 92 | if(key.match(e)) { 93 | obj.commands[key.command]() 94 | return true; 95 | } 96 | } 97 | return false; 98 | } 99 | } -------------------------------------------------------------------------------- /src/cljs-lib/src/calva/parse.cljs: -------------------------------------------------------------------------------- 1 | (ns calva.parse 2 | (:require [cljfmt.core :as cljfmt] 3 | [cljs.reader] 4 | [cljs.tools.reader :as tr] 5 | [cljs.tools.reader.reader-types :as rt] 6 | [cljs.test :refer [is]] 7 | [calva.js-utils :refer [jsify]])) 8 | 9 | (defn- parse-edn 10 | "Parses out the first form from `s`. 11 | `s` needs to be a string representation of valid EDN. 12 | Returns the parsed form." 13 | {:test (fn [] 14 | (is (= (parse-edn "#=(+ 1 2)") "#=(+ 1 2)")) 15 | (is (= (parse-edn "{:foo [1 2]}") {:foo [1 2]})) 16 | (is (= (parse-edn "{:foo/bar [1 2]}") {:foo/bar [1 2]})) 17 | (is (= (parse-edn ":a {:foo ['bar] :bar 'foo}") :a)))} 18 | [s] 19 | (cljs.reader/read-string {:default #(str "#" %1 %2)} s)) 20 | 21 | (defn parse-edn-js [s] 22 | (jsify (parse-edn s))) 23 | 24 | (defn parse-edn-js-bridge [s] 25 | (parse-edn-js s)) 26 | 27 | (defn- parse-forms 28 | "Parses out all top level forms from `s`. 29 | Returns a vector with the parsed forms." 30 | {:test (fn [] 31 | (is (= (parse-forms ":a {:foo [bar] :bar foo}") 32 | [:a {:foo ['bar] :bar 'foo}])) 33 | (is (thrown? js/Error (parse-forms ":a {:foo ['bar] :bar 'foo} #=(+ 1 2)") 34 | [:a {:foo ['bar] :bar 'foo}])))} 35 | [s] 36 | (let [pbr (rt/string-push-back-reader s)] 37 | (loop [parsed-forms []] 38 | (let [parsed-form (tr/read {:eof 'CALVA-EOF 39 | :read-cond :preserve} pbr)] 40 | (if (= parsed-form 'CALVA-EOF) 41 | parsed-forms 42 | (recur (conj parsed-forms parsed-form))))))) 43 | 44 | (defn parse-forms-js [s] 45 | (jsify (parse-forms s))) 46 | 47 | (defn parse-forms-js-bridge [s] 48 | (parse-forms-js s)) 49 | 50 | (defn parse-clj-edn 51 | "Reads edn (with regexp tags)" 52 | ; https://ask.clojure.org/index.php/8675/cljs-reader-read-string-fails-input-clojure-string-accepts 53 | {:test (fn [] 54 | (is (= (parse-clj-edn nil) nil)) 55 | (is (= (parse-clj-edn "{:foo [1 2]}") {:foo [1 2]})) 56 | (is (= (parse-clj-edn "{:foo/bar [1 2]}") {:foo/bar [1 2]})) 57 | (is (= :a (parse-clj-edn ":a {:foo ['bar] :bar 'foo}"))) 58 | (is (= js/RegExp (type (parse-clj-edn "#\"^foo.*bar$\"")))) 59 | (is (= "/^foo.*bar$/" (str (parse-clj-edn "#\"^foo.*bar$\"")))))} 60 | [s] (tr/read-string s)) 61 | 62 | ;[[ar gu ment] {:as extras, :keys [d e :s t r u c t u r e d]}] 63 | (comment 64 | (= [:a {:foo [(quote bar)], :bar (quote foo)}] 65 | [:a {:foo ['bar] :bar 'foo}]) 66 | (parse-forms "(ns calva.js-utils 67 | (:require [cljs.reader] 68 | [cljs.tools.reader :as tr] 69 | [cljs.tools.reader.reader-types :as rt] 70 | [cljs.test :refer [is]])) 71 | 72 | (defn jsify [o] 73 | (clj->js o)) 74 | 75 | (defn cljify [o] 76 | (js->clj o :keywordize-keys true))") 77 | (parse-forms "(ns ace2.legacy.bink 78 | (:gen-class) 79 | (:require [clojure.java.io :as io]) 80 | (:import (java.io RandomAccessFile))) 81 | (defn foo [] (println \"whee\"))")) 82 | -------------------------------------------------------------------------------- /docs/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## What has Changed? 5 | 6 | - 7 | - 8 | - 9 | 10 | 11 | Fixes # 12 | 13 | ## My Calva PR Checklist 14 | 15 | 16 | I have: 17 | 18 | - [ ] Read [How to Contribute](https://github.com/BetterThanTomorrow/calva/wiki/How-to-Contribute#before-sending-pull-requests). 19 | - [ ] Directed this pull request at the `dev` branch. (Or have specific reasons to target some other branch.) 20 | - [ ] Made sure I have changed the default PR base branch, so that it is not `master`. (Sorry for the nagging.) 21 | - [ ] Updated the `[Unreleased]` entry in `CHANGELOG.md`, linking the issue(s) that the PR is addressing. 22 | - [ ] Tested the VSIX built from the PR (so, after you've submitted the PR). You'll find the artifacts by clicking _Show all checks_ in the CI section of the PR page, and then _Details_ on the `ci/circleci: build` test. NB: *There is a CircleCI bug that makes the Artifacts hard to find. Please see [this issue](https://discuss.circleci.com/t/artifacts-tab-not-showing-unless-logged-in/32433) for workarounds.* 23 | - [ ] Tested the particular change 24 | - [ ] Figured if the change might have some side effects and tested those as well. 25 | - [ ] Smoke tested the extension as such. 26 | - [ ] Referenced the issue I am fixing/addressing _in a commit message for the pull request_. 27 | - [ ] If I am fixing the issue, I have used [GitHub's fixes/closes syntax](https://help.github.com/en/articles/closing-issues-using-keywords) 28 | - [ ] If I am fixing just part of the issue, I have just referenced it w/o any of the "fixes” keywords. 29 | - [ ] Created the issue I am fixing/addressing, if it was not present. 30 | 31 | ## The Calva Team PR Checklist: 32 | 33 | 34 | Before merging we (at least one of us) have: 35 | 36 | - [ ] Made sure the PR is directed at the `dev` branch (unless reasons). 37 | - [ ] Read the source changes. 38 | - [ ] Given feedback and guidance on source changes, if needed. (Please consider noting extra nice stuff as well.) 39 | - [ ] Tested the VSIX built from the PR (well, if this is a PR that changes the source code.) 40 | - [ ] Tested the particular change 41 | - [ ] Figured if the change might have some side effects and tested those as well. 42 | - [ ] Smoke tested the extension as such. 43 | - [ ] If need be, had a chat within the team about particular changes. 44 | 45 | Ping @pez, @kstehn, @cfehse, @bpringe 46 | 47 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "restructuredtext.confPath": "${workspaceFolder}\\docs\\readthedocs\\source", 3 | "mochaExplorer.files": "src/extension-test/unit/**/*-test.ts", 4 | "mochaExplorer.require": "ts-node/register", 5 | "cSpell.words": [ 6 | "Batsov", 7 | "Bozhidar", 8 | "Calva's", 9 | "Dedent", 10 | "Docstring", 11 | "Girardi", 12 | "Hmmm", 13 | "Insourcing", 14 | "Jackin", 15 | "Jackout", 16 | "OVSX", 17 | "REBL", 18 | "REPLs", 19 | "Sdeps", 20 | "Sexp", 21 | "Sexpr", 22 | "Sexps", 23 | "Strömberg's", 24 | "VSIX", 25 | "ahlbrecht", 26 | "alnum", 27 | "analysing", 28 | "arglist", 29 | "arglists", 30 | "autoindent", 31 | "bencode", 32 | "betterthantomorrow", 33 | "bhauman", 34 | "borkdude", 35 | "brear", 36 | "calva", 37 | "chmod", 38 | "cibuilds", 39 | "circleci", 40 | "classpath", 41 | "cljc", 42 | "cljfmt", 43 | "cljfx", 44 | "cljify", 45 | "cljslib", 46 | "clojurians", 47 | "cmdline", 48 | "codeblock", 49 | "cospaia", 50 | "debugable", 51 | "debugadapter", 52 | "defproject", 53 | "defun", 54 | "deref", 55 | "derefed", 56 | "derefs", 57 | "devtool", 58 | "devtools", 59 | "docmirror", 60 | "eckstein", 61 | "eldoc", 62 | "enablement", 63 | "enablements", 64 | "entrypoint", 65 | "errored", 66 | "eval", 67 | "falsesomething", 68 | "fehse", 69 | "feldman", 70 | "figwheel", 71 | "filipe", 72 | "fipp", 73 | "gifs", 74 | "gsub", 75 | "hacky", 76 | "highlightning", 77 | "hopperdietzel", 78 | "howto", 79 | "inferer", 80 | "infoparser", 81 | "isequal", 82 | "janne", 83 | "jsify", 84 | "jszip", 85 | "junit", 86 | "keywordize", 87 | "klepsch", 88 | "kondo", 89 | "lein", 90 | "leiningen", 91 | "lexing", 92 | "luminus", 93 | "missmatch", 94 | "mkdir", 95 | "mkdocs", 96 | "nashorn", 97 | "niclas", 98 | "nrepl", 99 | "ontype", 100 | "openjdk", 101 | "outdent", 102 | "paredit", 103 | "paren", 104 | "parens", 105 | "parinfer", 106 | "pidfile", 107 | "piggieback", 108 | "postrelease", 109 | "pprint", 110 | "preinstall", 111 | "prepending", 112 | "prewatch", 113 | "randr", 114 | "reponame", 115 | "ringe", 116 | "sauvala", 117 | "sbin", 118 | "scaturro", 119 | "schäfer", 120 | "sivertsen", 121 | "stian", 122 | "strömberg", 123 | "suitible", 124 | "terje", 125 | "togglemode", 126 | "tonsky", 127 | "tonsky's", 128 | "truesomething", 129 | "tsbuildinfo", 130 | "udris", 131 | "ullrich", 132 | "vsce", 133 | "vscodevim", 134 | "webpack'ed", 135 | "webpacked", 136 | "xvfb", 137 | "zprint", 138 | "être" 139 | ] 140 | } -------------------------------------------------------------------------------- /src/debugger/util.ts: -------------------------------------------------------------------------------- 1 | import { LispTokenCursor } from "../cursor-doc/token-cursor"; 2 | 3 | function moveCursorPastStringInList(tokenCursor: LispTokenCursor, s: string): void { 4 | 5 | const [listOffsetStart, listOffsetEnd] = tokenCursor.rangeForList(1); 6 | const text = tokenCursor.doc.getText(listOffsetStart, listOffsetEnd - 1); 7 | 8 | const stringIndexInList = text.indexOf(s); 9 | if (stringIndexInList !== -1) { 10 | const coorOffset = listOffsetStart + stringIndexInList; 11 | while (tokenCursor.offsetStart !== coorOffset) { 12 | tokenCursor.forwardSexp(); 13 | tokenCursor.forwardWhitespace(); 14 | } 15 | tokenCursor.forwardSexp(); 16 | } else { 17 | throw "Cannot find string in list"; 18 | } 19 | } 20 | 21 | function moveTokenCursorToBreakpoint(tokenCursor: LispTokenCursor, debugResponse: any): LispTokenCursor { 22 | 23 | const errorMessage = "Error finding position of breakpoint"; 24 | const [_, defunEnd] = tokenCursor.rangeForDefun(tokenCursor.offsetStart); 25 | let inSyntaxQuote = false; 26 | 27 | const coor = [...debugResponse.coor]; // Copy the array so we do not modify the one stored in state 28 | 29 | for (let i = 0; i < coor.length; i++) { 30 | while (!tokenCursor.downList(true)) { 31 | tokenCursor.next(); 32 | } 33 | const previousToken = tokenCursor.getPrevToken(); 34 | 35 | // Check if we just entered a syntax quote, since we have to account for how syntax quoted forms are read 36 | // `(. .) is read as (seq (concat (list .) (list .))). 37 | if (/.*\`(\[|\{|\()$/.test(previousToken.raw)) { 38 | inSyntaxQuote = true; 39 | } 40 | 41 | if (inSyntaxQuote) { 42 | i++; // Ignore this coor and move to the next 43 | 44 | // A syntax quote is ending - this happens if ~ or ~@ precedes a form 45 | if (previousToken.raw.match(/~@?/)) { 46 | inSyntaxQuote = false; 47 | } 48 | } 49 | 50 | if (inSyntaxQuote) { 51 | if (!previousToken.raw.endsWith('(')) { 52 | // Non-list seqs like `[] and `{} are read with an extra (apply vector ...) or (apply hash-map ...) 53 | // Ignore this coor too 54 | i++; 55 | } 56 | // Now we're inside the `concat` form, but we need to ignore the actual `concat` symbol 57 | coor[i]--; 58 | } 59 | 60 | // #() expands to (fn* ([] ...)) and this is what coor is calculated with, so ignore this coor and move to the next 61 | if (previousToken.raw.endsWith('#(')) { 62 | i++; 63 | } 64 | 65 | // If coor is a string it represents a map key 66 | if (typeof coor[i] === 'string') { 67 | moveCursorPastStringInList(tokenCursor, coor[i]); 68 | } else { 69 | for (let k = 0; k < coor[i]; k++) { 70 | if (!tokenCursor.forwardSexp(true, true, true)) { 71 | throw errorMessage; 72 | } 73 | } 74 | } 75 | } 76 | 77 | // Move past the target sexp 78 | if (!tokenCursor.forwardSexp(true, true, true)) { 79 | throw errorMessage; 80 | } 81 | 82 | // Make sure we're still inside the original instrumented form, otherwise something went wrong 83 | if (tokenCursor.offsetStart > defunEnd) { 84 | throw errorMessage; 85 | } 86 | 87 | return tokenCursor; 88 | } 89 | 90 | export { 91 | moveTokenCursorToBreakpoint 92 | }; -------------------------------------------------------------------------------- /docs/overview.plantuml: -------------------------------------------------------------------------------- 1 | @startuml "Overview" 2 | skinparam linetype ortho 3 | 4 | object "cljs-lib" as cljsLib 5 | object analytics 6 | object connector 7 | object evaluate 8 | object extension 9 | object greet 10 | object lint 11 | object refresh 12 | object "repl-window" as replWindow 13 | object select 14 | object state 15 | object status 16 | object statusbar 17 | object testRunner 18 | object "utilities" as ult 19 | 20 | package "calva-fmt" { 21 | object "state" as fmtState 22 | object config 23 | object format 24 | object infer 25 | object "extension" as fmtExtension 26 | package "docmirror" { 27 | object "index" as doc 28 | } 29 | package "providers" as fmtProviders { 30 | object ontype_formatter 31 | object range_formatter 32 | } 33 | 34 | format --> config 35 | fmtExtension --> format 36 | fmtExtension --> infer 37 | fmtExtension --> range_formatter 38 | fmtExtension --> ontype_formatter 39 | fmtExtension --> doc 40 | ontype_formatter --> format 41 | range_formatter --> format 42 | infer --> cljsLib 43 | doc --> ult 44 | } 45 | 46 | package "nrepl" { 47 | object "index" as nrepl 48 | object bencode 49 | object "jack-in" as jackIn 50 | 51 | nrepl --> bencode 52 | jackIn --> state 53 | jackIn --> statusbar 54 | jackIn --> connector 55 | jackIn --> cljsLib 56 | jackIn --> ult 57 | } 58 | 59 | package "paredit" { 60 | object "extension" as parExtension 61 | object "utils" as parUtils 62 | object "status_bar" as parStatusBar 63 | 64 | parExtension --> parUtils 65 | parExtension --> parStatusBar 66 | } 67 | 68 | package "providers" { 69 | object annotations 70 | object content 71 | object definition 72 | object completion 73 | object hover 74 | 75 | annotations --> state 76 | content --> state 77 | definition --> ult 78 | definition --> state 79 | completion --> ult 80 | completion --> state 81 | hover --> ult 82 | hover --> state 83 | } 84 | 85 | connector --> state 86 | connector --> ult 87 | connector --> status 88 | 89 | evaluate --> state 90 | evaluate --> annotations 91 | evaluate --> select 92 | evaluate --> ult 93 | evaluate --> replWindow 94 | evaluate --> nrepl 95 | 96 | extension --> parExtension 97 | extension --> fmtExtension 98 | extension --> state 99 | extension --> jackIn 100 | extension --> ult 101 | extension --> status 102 | extension --> connector 103 | extension --> completion 104 | extension --> content 105 | extension --> hover 106 | extension --> definition 107 | extension --> evaluate 108 | extension --> lint 109 | extension --> testRunner 110 | extension --> annotations 111 | extension --> select 112 | extension --> evaluate 113 | extension --> refresh 114 | extension --> replWindow 115 | extension --> greet 116 | extension --> analytics 117 | 118 | lint --> state 119 | lint --> ult 120 | 121 | refresh --> ult 122 | refresh --> state 123 | refresh --> nrepl 124 | 125 | replWindow --> connector 126 | replWindow --> state 127 | replWindow --> status 128 | replWindow --> nrepl 129 | replWindow --> annotations 130 | replWindow --> ult 131 | replWindow --> evaluate 132 | replWindow --> select 133 | 134 | select --> ult 135 | 136 | state --> analytics 137 | 138 | status --> ult 139 | status --> statusbar 140 | 141 | statusbar --> replWindow 142 | statusbar --> state 143 | statusbar --> ult 144 | 145 | testRunner --> state 146 | testRunner --> evaluate 147 | testRunner --> ult 148 | 149 | ult --> state 150 | ult --> nrepl 151 | ult --> replWindow 152 | ult --> cljsLib 153 | ult --> doc 154 | ult --> select 155 | 156 | @enduml -------------------------------------------------------------------------------- /docs/readthedocs/source/connect.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Connect Calva to Your Project 8 | 9 | The recommended way is to: 10 | 11 | ## Jack-in: Let Calva start the REPL for you 12 | 13 | This way Calva can make sure it is started with the dependencies needed for a working Clojure and/or ClojureScript session. This is often referred to as **Jack in** (because that is what it is called in CIDER). 14 | 15 | Jack-in supports both CLJ and for CLJS, and has built-in configurations for **Leiningen**, **Clojure CLI**, and **shadow-cljs** projects, as well as for the CLJS repl types: **Figwheel Main**, **lein-figwheel** (legacy Figwheel), **shadow-cljs**, and Nashorn. Using jack-in provides your development environment with all the dependencies you need for Calva to work. 16 | 17 | It works like so: 18 | 19 | 1. Open your project root directory in VS Code. 20 | 1. Issue the command **Start a Project REPL and Connect**: `ctrl+alt+c ctrl+alt+j`. 21 | 1. Answer the quick-pick prompts telling Calva about project types and what profiles to start. 22 | 23 | See also: [Workspace Layouts](workspace-layouts.md) 24 | 25 | ### Aliases, profiles, builds 26 | 27 | When Jack-in starts it will depend on the project type, and whether ClojureScript is involved or not, and if it is, what kind of ClojureScript project, what will happen next. Calva will analyze the project files and will then give you prompts with selections based on what is found there. 28 | 29 | You will need some basic knowledge about the project and the project type terminologies to answer the prompts. 30 | 31 | There are ways to tell Calva the answers to these prompts beforehand, so that Jack-in can be a zero-prompting command. Read on. 32 | 33 | ### Customizing Jack-in 34 | 35 | The main mechanism for customizing your Jack-in, including automating menu selections, and custom CLJS REPL types is [Custom Connect Sequences](connect-sequences.md). 36 | 37 | There are also these settings: 38 | * `calva.jackInEnv`: An object with environment variables that will be added to the environment of the Jack-in process. 39 | * `calva.myCljAliases`: An array of `deps.edn` aliases not found in the project file. Use this to launch your REPL using your user defined aliases. 40 | * `calva.myLeinProfiles`: An array of Leiningen profiles not found in `project.clj`. Use adding your user defined profiles to Jack-in launch of the REPL. 41 | * `calva.openBrowserWhenFigwheelStarted`: _For Legacy Figwheel only._ A boolean controlling if Calva should automatically launch your ClojureScript app, once it is compiled by Figwheel. Defaults to `true`. 42 | 43 | ### Troubleshooting 44 | 45 | I'm sure there are troubles we should mention here... 46 | 47 | ## Connecting w/o Jack-in 48 | 49 | If, for whatever reasons, you can't use Jack-in with your project (possibly because the REPL is started as part of some other job) all is not lost. Old fashioned **Connect to a running REPL** is still there for you. For all features to work in Calva while connecting to a running REPL, your environment needs to have REPL related dependencies set up. 50 | 51 | However, just as before it can be tricky to get the dependencies right. Consider using **Jack in** to inform yourself on how to start your REPL to Calva's satisfaction. When you use Jack in, Calva starts a VS Code task for it and the command line used is displayed in the terminal pane used to handle the task. Reading that command line tells you what dependencies are needed for your project. 52 | 53 | Even better: Copying that command line gives you the command to start the REPL with the correct dependencies. 54 | 55 | All this said, I still recommend you challenge the conclusion that you can't use Jack-in. 56 | -------------------------------------------------------------------------------- /src/cursor-doc/lexer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A Lexical analyser 3 | * @module lexer 4 | */ 5 | 6 | /** 7 | * The base Token class. Contains the token type, 8 | * the raw string of the token, and the offset into the input line. 9 | */ 10 | export interface Token { 11 | type: string; 12 | raw: string; 13 | offset: number; 14 | } 15 | 16 | /** 17 | * A Lexical rule for a terminal. Consists of a RegExp and an action. 18 | */ 19 | export interface Rule { 20 | r: RegExp; 21 | fn: (Lexer, RegExpExecArray) => any 22 | } 23 | 24 | /** 25 | * A Lexer instance, parsing a given file. Usually you should use a LexicalGrammar to 26 | * create one of these. 27 | * 28 | * @class 29 | * @param {string} source the source code to parse 30 | * @param rules the rules of this lexer. 31 | */ 32 | 33 | export class Lexer { 34 | position: number = 0; 35 | constructor(public source: string, public rules: Rule[], private maxLength) { 36 | } 37 | 38 | /** Returns the next token in this lexer, or null if at the end. If the match fails, throws an Error. */ 39 | scan(): Token { 40 | let token = null, 41 | length = 0; 42 | if (this.position < this.source.length) { 43 | if (this.source !== undefined && this.source.length < this.maxLength) { 44 | // TODO: Consider using vscode setting for tokenisation max length 45 | this.rules.forEach(rule => { 46 | rule.r.lastIndex = this.position; 47 | const x = rule.r.exec(this.source); 48 | if (x && x[0].length > length && this.position + x[0].length == rule.r.lastIndex) { 49 | token = rule.fn(this, x); 50 | token.offset = this.position; 51 | token.raw = x[0]; 52 | length = x[0].length; 53 | } 54 | }); 55 | } else { 56 | length = this.source.length; 57 | token = { 58 | type: "too-long-line", 59 | offset: this.position, 60 | raw: this.source 61 | }; 62 | } 63 | } 64 | this.position += length; 65 | if (token == null) { 66 | if (this.position == this.source.length) 67 | return null; 68 | throw new Error("Unexpected character at " + this.position + ": " + JSON.stringify(this.source)); 69 | } 70 | return token; 71 | } 72 | } 73 | 74 | /** 75 | * A lexical grammar- factory for lexer instances. 76 | * @class 77 | */ 78 | export class LexicalGrammar { 79 | rules: Rule[] = []; 80 | 81 | /** 82 | * Defines a terminal with the given pattern and constructor. 83 | * @param {string | RegExp} pattern the pattern this terminal must match. 84 | * @param {function(Array): Object} fn returns a lexical token representing 85 | * this terminal. An additional "offset" property containing the token source position 86 | * will also be added, as well as a "raw" property, containing the raw string match. 87 | */ 88 | terminal(pattern: string | RegExp, fn: (T, RegExpExecArray) => any): void { 89 | this.rules.push({ 90 | // This is b/c the RegExp constructor seems to not like our union type (unknown reasons why) 91 | r: pattern instanceof RegExp ? new RegExp(pattern, "g") : new RegExp(pattern, "g"), 92 | fn: fn 93 | }); 94 | } 95 | 96 | /** 97 | * Create a Lexer for the given input. 98 | */ 99 | lex(source: string, maxLength): Lexer { 100 | return new Lexer(source, this.rules, maxLength) 101 | } 102 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "Launch Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceRoot}", 15 | ], 16 | "stopOnEntry": false, 17 | "sourceMaps": true, 18 | "env": { 19 | "CALVA_DEV_GA": "FUBAR-69796730-4", 20 | "DEBUG": "no-debug-universal-analytics" 21 | }, 22 | "outFiles": [ 23 | "${workspaceFolder}/dist/**/*.js" 24 | ] 25 | }, 26 | { 27 | "name": "Launch w/ test folder", 28 | "type": "extensionHost", 29 | "request": "launch", 30 | "runtimeExecutable": "${execPath}", 31 | "args": [ 32 | "--extensionDevelopmentPath=${workspaceRoot}", 33 | "${workspaceFolder}/test-data" 34 | ], 35 | "stopOnEntry": false, 36 | "sourceMaps": true, 37 | "env": { 38 | "CALVA_DEV_GA": "FUBAR-69796730-4", 39 | "DEBUG": "no-debug-universal-analytics" 40 | }, 41 | "outFiles": [ 42 | "${workspaceFolder}/dist/**/*.js" 43 | ] 44 | }, 45 | { 46 | "name": "Launch w/ /tmp/foo.clj", 47 | "type": "extensionHost", 48 | "request": "launch", 49 | "runtimeExecutable": "${execPath}", 50 | "args": [ 51 | "--extensionDevelopmentPath=${workspaceRoot}", 52 | "--folder-uri=/tmp/foo.clj" 53 | ], 54 | "stopOnEntry": false, 55 | "sourceMaps": true, 56 | "env": { 57 | "CALVA_DEV_GA": "FUBAR-69796730-4", 58 | "DEBUG": "no-debug-universal-analytics" 59 | }, 60 | "outFiles": [ 61 | "${workspaceFolder}/dist/**/*.js" 62 | ] 63 | }, 64 | { 65 | "name": "Theia node launch", 66 | "type": "node", 67 | "request": "launch", 68 | "port": 9229, 69 | "runtimeExecutable": "${execPath}", 70 | "args": [ 71 | "${workspaceFolder}/test-data", 72 | "--port=3030", 73 | "--hostname", 74 | "0.0.0.0", 75 | "--hosted-plugin-inspect=9229" 76 | ], 77 | "stopOnEntry": false, 78 | "sourceMaps": true, 79 | "env": { 80 | "CALVA_DEV_GA": "FUBAR-69796730-4", 81 | "DEBUG": "no-debug-universal-analytics" 82 | }, 83 | "outFiles": [ 84 | "${workspaceFolder}/dist/**/*.js" 85 | ] 86 | }, 87 | { 88 | "name": "Extension Tests", 89 | "type": "extensionHost", 90 | "request": "launch", 91 | "runtimeExecutable": "${execPath}", 92 | "args": [ 93 | "--disable-extensions", 94 | "--extensionDevelopmentPath=${workspaceRoot}", 95 | "--extensionTestsPath=${workspaceRoot}/out/test/suite/index" 96 | ], 97 | "outFiles": ["${workspaceRoot}/out/test/**/*.js"], 98 | "preLaunchTask": "npm: test-compile" 99 | } 100 | ] 101 | } -------------------------------------------------------------------------------- /assets/images/clj.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | clj 5 | Created with Sketch. 6 | 7 | 17 | 18 | -------------------------------------------------------------------------------- /src/providers/completion.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument, Position, CancellationToken, CompletionContext, Hover, CompletionItemKind, window, CompletionList, CompletionItemProvider, CompletionItem } from 'vscode'; 2 | import * as state from '../state'; 3 | import * as util from '../utilities'; 4 | import select from '../select'; 5 | import * as docMirror from '../doc-mirror'; 6 | import * as infoparser from './infoparser'; 7 | 8 | export default class CalvaCompletionItemProvider implements CompletionItemProvider { 9 | state: any; 10 | mappings: any; 11 | constructor() { 12 | this.state = state; 13 | this.mappings = { 14 | 'nil': CompletionItemKind.Value, 15 | 'macro': CompletionItemKind.Value, 16 | 'class': CompletionItemKind.Class, 17 | 'keyword': CompletionItemKind.Keyword, 18 | 'namespace': CompletionItemKind.Module, 19 | 'function': CompletionItemKind.Function, 20 | 'special-form': CompletionItemKind.Keyword, 21 | 'var': CompletionItemKind.Variable, 22 | 'method': CompletionItemKind.Method 23 | }; 24 | } 25 | 26 | async provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: CompletionContext) { 27 | let text = util.getWordAtPosition(document, position); 28 | 29 | if (util.getConnectedState()) { 30 | const toplevelSelection = select.getFormSelection(document, position, true), 31 | toplevel = document.getText(toplevelSelection), 32 | toplevelStartOffset = document.offsetAt(toplevelSelection.start), 33 | toplevelStartCursor = docMirror.getDocument(document).getTokenCursor(toplevelStartOffset + 1), 34 | wordRange = document.getWordRangeAtPosition(position), 35 | wordStartLocalOffset = document.offsetAt(wordRange.start) - toplevelStartOffset, 36 | wordEndLocalOffset = document.offsetAt(wordRange.end) - toplevelStartOffset, 37 | contextStart = toplevel.substring(0, wordStartLocalOffset), 38 | contextEnd = toplevel.substring(wordEndLocalOffset), 39 | context = `${contextStart}__prefix__${contextEnd}`, 40 | toplevelIsValidForm = toplevelStartCursor.withinValidList() && context != '__prefix__', 41 | ns = util.getNamespace(document), 42 | client = util.getSession(util.getFileType(document)), 43 | res = await client.complete(util.getNamespace(document), text, toplevelIsValidForm ? context : null), 44 | results = res.completions || []; 45 | if(results) { 46 | results.forEach(element => { 47 | if(!element['ns']) { 48 | // make sure every entry has a namespace 49 | // for the 'info' call. 50 | element['ns'] = ns; 51 | } 52 | }); 53 | } 54 | return new CompletionList( 55 | results.map(item => ({ 56 | label: item.candidate, 57 | kind: this.mappings[item.type] || CompletionItemKind.Text, 58 | insertText: item[0] === '.' ? item.slice(1) : item 59 | })), true); 60 | } 61 | return []; 62 | } 63 | 64 | async resolveCompletionItem(item: CompletionItem, token: CancellationToken) { 65 | 66 | if (util.getConnectedState()) { 67 | let client = util.getSession(util.getFileType(window.activeTextEditor.document)); 68 | if (client) { 69 | await util.createNamespaceFromDocumentIfNotExists(window.activeTextEditor.document); 70 | let result = await client.info(item.insertText["ns"], item.label) 71 | let [doc, details] = infoparser.getCompletion(result); 72 | item.documentation = doc; 73 | item.detail = details; 74 | } 75 | } 76 | return item; 77 | } 78 | }; -------------------------------------------------------------------------------- /docs/readthedocs/source/eval-tips.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Code Evaluation Tips 8 | 9 | Calva tries to make it easy to evaluate code, supporting interactive development. 10 | 11 | NB: _The below assumes you have read about [Finding Calva Commands and Shortcuts](finding-commands.md)._ 12 | 13 | There are some different ”flavors” to the evaluation. And it depends on if you are evaluating in a file editor or a in a REPL window. 14 | 15 | ## Evaluation in a File Editor 16 | 17 | Calva has commands for evaluating the **current form** and the **current top-level form**. 18 | 19 | You can also choose what should happen with the results: 20 | 21 | 1. **Inline.** This will display the results (or some of it, if it is long) inline in the editor. _You find the full results in the hover of the eveluated form_, from where it is easy to copy it to the clipboard. (The results will also get printed to the **Calva says** output channel.) 22 | 1. **To comments.** This will add the results as comment lines below the current line. 23 | 1. **Replace the evaluated code.** This will do what it says, the evaluated code will be replaced with its results. 24 | 1. **Send to REPL window.** You can also send the current form, or current top-level form, to the REPL window for evaluation. This is currently the only way to get a readable and clickable stack trace in cases where evaluation results in such errors. 25 | 26 | ## Wait, Current Form? Top-level Form? 27 | 28 | These are important concepts in Calva in order for you to create your most effective workflow. 29 | 30 | ### Current Form 31 | 32 | The current form either means the current selection, or otherwise is based on the cursor position. Play some with the command **Calva: Select current form**, `ctrl+alt+c s`, to figure out what Calva thinks is the current form for some different situations. Try it inside a symbol, adjacent to a symbol (both sides) and adjacent to an opening or closing bracket (again, both sides). 33 | 34 | ### Current Top-level Form 35 | 36 | The current top-level form means top-level in a structural sense. It is _not_ the topmost form in the file. Typically in a Clojure file you will find `def` and `defn` (and `defwhatever`) forms at the top level, but it can be any form not enclosed in any other form. 37 | 38 | An exception is the `comment` form. It will create a new top level context, so that any forms immediatlly inside a `(commment ...)` form will be considered top-level by Calva. This is to support a workflow where you 39 | 40 | 1. Iterate on your functions. 41 | 2. Evaluate the function (top level). 42 | 3. Put them to test with expressions inside a `comment` form. 43 | 4. Repeat from *1.*, until the function does what you want it to do. 44 | 45 | Here's a demo of the last repetition of such a workflow, for a simple implementation of the `abs` function: 46 | 47 | ![top-level-eval](https://user-images.githubusercontent.com/30010/59426414-6ea0e000-8dd8-11e9-9db3-ae4ede2e0463.gif) 48 | 49 | ### Copying the inline results 50 | 51 | The easiest way is to use the **Copy** button in the result hover. There is also the **Copy last evaluation results** command, `ctrl+alt+c ctrl+c`. 52 | 53 | This works regardless if you have evaluated in a file editor or in a REPL window. 54 | 55 | ## Evaluating in a REPL window 56 | 57 | To evaluate code in the REPL window either send a form from the editor (as mentioned above) or type it at the prompt and submit it. There is also a repl history that you can access with `alt+up/down`. 58 | 59 | Note that the repl prompt is a _multiline_ mini Clojure editor. So if you press `enter` while the cursor is not at the end of the line, it will create a new line. To submit the code at the prompt you have three options: 60 | 61 | 1. Place the cursor at the end of the all the code, `end` (`fn+right` on Macs lacking an `end` key). 62 | 2. Press `alt+enter`. This submits and prints the results un-altered. 63 | 64 | See also above about sending forms from the file editors to the REPL window for evaluation. 65 | -------------------------------------------------------------------------------- /src/cljs-lib/src/calva/fmt/playground.cljs: -------------------------------------------------------------------------------- 1 | (ns calva.fmt.playground 2 | (:require [cljfmt.core :as cljfmt] 3 | #_[zprint.core :refer [zprint-str]] 4 | ["parinfer" :as parinfer] 5 | [calva.js-utils :refer [cljify jsify]] 6 | [calva.fmt.util :as util])) 7 | 8 | 9 | (comment 10 | (foo 11 | ;; 12 | bar 13 | baz)) 14 | 15 | (comment 16 | (cond) 17 | (cljfmt/reformat-string "(cond foo\n)\n\n(cond foo\nbar)" 18 | {:remove-surrounding-whitespace? false}) 19 | 20 | (cljfmt/reformat-string "(-> foo\nbar\n)\n(foo bar\nbaz\n)" 21 | {:remove-surrounding-whitespace? false 22 | :remove-trailing-whitespace? false 23 | :remove-consecutive-blank-lines? false}) 24 | 25 | (cljfmt/reformat-string "(let [x y\na b]\nbar\n)\n\n(-> foo\nbar\n)\n\n(foo bar\nbaz\n)" 26 | {:remove-surrounding-whitespace? false 27 | :remove-trailing-whitespace? false 28 | :remove-consecutive-blank-lines? false 29 | :indents ^:replace {#".*" [[:inner 0]]}}) 30 | 31 | (cljfmt/reformat-string " '([] 32 | [])" {:remove-surrounding-whitespace? false 33 | :remove-trailing-whitespace? false 34 | :remove-consecutive-blank-lines? false}) 35 | 36 | 37 | (def str "(defn \n\n)") 38 | 39 | (cljfmt/reformat-string str {:remove-surrounding-whitespace? false 40 | :remove-trailing-whitespace? false 41 | :remove-consecutive-blank-lines? false}) 42 | 43 | (cljfmt/reformat-string "(foo 44 | ;; 45 | bar 46 | baz)" 47 | {:remove-surrounding-whitespace? false 48 | :remove-trailing-whitespace? false 49 | :remove-consecutive-blank-lines? false}) 50 | 51 | (cljfmt/reformat-string 52 | "(foo 53 | 54 | )" 55 | {:remove-surrounding-whitespace? false 56 | :remove-trailing-whitespace? false 57 | :indentation? true}) 58 | 59 | (cljfmt/reformat-string "(bar\n \n)" 60 | {:remove-surrounding-whitespace? false 61 | :remove-trailing-whitespace? false}) 62 | 63 | (cljfmt/reformat-string "(ns ui-app.re-frame.db) 64 | 65 | (def default-db #::{:page :home})") 66 | 67 | (cljfmt/reformat-string 68 | "(defn bar\n [x]\n\n baz)") 69 | 70 | (zprint-str "(defn bar\n [x]\n\n baz)" 71 | {:style :community 72 | :parse-string-all? true 73 | :fn-force-nl #{:arg1-body}}) 74 | 75 | (cljfmt/reformat-string 76 | "(defn bar\n [x]\n\n baz)") 77 | 78 | "(defn bar\n [x]\n \n baz)" 79 | 80 | (cljfmt/reformat-string 81 | ";; foo 82 | (defn foo [x] 83 | (* x x)) 84 | 0") 85 | 86 | (div 87 | ;; foo 88 | [:div] 89 | ;; bar 90 | [:div])) 91 | 92 | 93 | (comment 94 | (parinfer/indentMode " (foo [] 95 | (bar) 96 | (baz)))" 97 | (jsify {:cursorLine 2 98 | :cursorX 13}))) 99 | 100 | (comment 101 | (cljfmt/reformat-string 102 | "{:foo false 103 | :bar false 104 | :baz #\"^[a-z]\"}")) 105 | 106 | (comment 107 | (def t "(when something 108 | body) 109 | 110 | (defn f [x] 111 | body) 112 | 113 | (defn f 114 | [x] 115 | body) 116 | 117 | (defn many-args [a b c 118 | d e f] 119 | body) 120 | 121 | (defn multi-arity 122 | ([x] 123 | body) 124 | ([x y] 125 | body)) 126 | 127 | (let [x 1 128 | y 2] 129 | body) 130 | 131 | [1 2 3 132 | 4 5 6] 133 | 134 | {:key-1 v1 135 | :key-2 v2} 136 | 137 | #{a b c 138 | d e f} 139 | 140 | (or (condition-a) 141 | (condition-b)) 142 | 143 | (filter even? 144 | (range 1 10)) 145 | 146 | (clojure.core/filter even? 147 | (range 1 10)) 148 | 149 | (filter 150 | even? 151 | (range 1 10))") 152 | 153 | (def f 154 | (cljfmt/reformat-string t {:indents {#"^\w" [[:inner 0]]}})) 155 | 156 | (= t f) 157 | (pr-str f) 158 | (println f)) -------------------------------------------------------------------------------- /src/cursor-doc/undo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A reversable operation to a document of type T. 3 | */ 4 | export abstract class UndoStep { 5 | /** The name of this undo operation. */ 6 | name: string; 7 | /** If true, the UndoManager will not attempt to coalesce events onto this step. */ 8 | undoStop: boolean; 9 | 10 | /** Given the document, undos the effect of this step */ 11 | abstract undo(c: T): void; 12 | 13 | /** Given the document, redoes the effect of this step */ 14 | abstract redo(c: T): void; 15 | 16 | /** 17 | * Given another UndoStep, attempts to modify this undo-step to include the subsequent one. 18 | * If successful, returns true, if unsuccessful, returns false, and the step must be added to the 19 | * UndoManager, too. 20 | */ 21 | coalesce(c: UndoStep): boolean { 22 | return false; 23 | } 24 | } 25 | 26 | export class UndoStepGroup extends UndoStep { 27 | steps: UndoStep[] = []; 28 | 29 | addUndoStep(step: UndoStep) { 30 | let prevStep = this.steps.length && this.steps[this.steps.length-1]; 31 | 32 | if(prevStep && !prevStep.undoStop && prevStep.coalesce(step)) 33 | return; 34 | this.steps.push(step); 35 | } 36 | 37 | undo(c: T): void { 38 | for(let i=this.steps.length-1; i>=0; i--) 39 | this.steps[i].undo(c); 40 | } 41 | 42 | redo(c: T): void { 43 | for(let i=0; i { 52 | private undos: UndoStep[] = []; 53 | private redos: UndoStep[] = []; 54 | 55 | private groupedUndo: UndoStepGroup | null; 56 | 57 | /** 58 | * Adds the step to the undo stack, and clears the redo stack. 59 | * If possible, coalesces it into the previous undo. 60 | * 61 | * @param step the UndoStep to add. 62 | */ 63 | addUndoStep(step: UndoStep) { 64 | if(this.groupedUndo) { 65 | this.groupedUndo.addUndoStep(step); 66 | } else if(this.undos.length) { 67 | let prevUndo = this.undos[this.undos.length-1]; 68 | if(prevUndo.undoStop) { 69 | this.undos.push(step); 70 | } else if(!prevUndo.coalesce(step)) { 71 | this.undos.push(step); 72 | } 73 | } else { 74 | this.undos.push(step); 75 | } 76 | this.redos = []; 77 | } 78 | 79 | withUndo(f: () => void) { 80 | if(!this.groupedUndo) { 81 | try { 82 | this.groupedUndo = new UndoStepGroup(); 83 | f(); 84 | let undo = this.groupedUndo; 85 | this.groupedUndo = null; 86 | switch(undo.steps.length) { 87 | case 0: break; 88 | case 1: this.addUndoStep(undo.steps[0]); break; 89 | default: 90 | this.addUndoStep(undo); 91 | } 92 | } finally { 93 | this.groupedUndo = null; 94 | } 95 | } else { 96 | f(); 97 | } 98 | } 99 | 100 | /** Prevents this undo from becoming coalesced with future undos */ 101 | insertUndoStop() { 102 | if(this.undos.length) 103 | this.undos[this.undos.length-1].undoStop = true; 104 | } 105 | 106 | /** Performs the top undo operation on the document (if it exists), moving it to the redo stack. */ 107 | undo(c: T) { 108 | if(this.undos.length) { 109 | const step = this.undos.pop(); 110 | step.undo(c); 111 | this.redos.push(step); 112 | } 113 | } 114 | 115 | /** Performs the top redo operation on the document (if it exists), moving it back onto the undo stack. */ 116 | redo(c: T) { 117 | if(this.redos.length) { 118 | const step = this.redos.pop(); 119 | step.redo(c); 120 | this.undos.push(step); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /docs/integration.md: -------------------------------------------------------------------------------- 1 | # The Calva development and release process 2 | 3 | _This is mainly for Calva maintainers. But is probably good for any [contributor](https://github.com/BetterThanTomorrow/calva/wiki/How-to-Contribute) to be familiar with. Also, feedback and tips on how to improve this process is very welcome._ 4 | 5 | ## 1. Introducing Changes 6 | 7 | Generally: 8 | 9 | * Don't forget about `README.md`. (It might not need to be updated, but anyway.) 10 | * If a change warrants updates to `CHANGELOG.md`, put these under `[Unreleased]`. 11 | * If a change warrants updates the the [Calva User Guide](https://github.com/BetterThanTomorrow/calva-docs), make those changes in a branch with the same name as your feature branch in the `calva` repo. 12 | 13 | Bigger changes: 14 | 15 | * These go into a `wip` branch and we send PR’s asking for feedback. 16 | * We keep `wip` branches updated by merging `dev` onto them often. 17 | * Circle-CI runs tests and builds a VSIX package for any commits pushed to a PR. Follow the links to the build to find the VSIX (the **Artifact** tab). 18 | * Consider asking for help testing in `#calva` and wherever. 19 | 20 | Smaller changes: 21 | 22 | * Small, low-risky, fixes, you can do right on `dev` 23 | * Before committing on `dev` one has to ask: _should this be branched out to a `wip` branch?_ 24 | 25 | ## 2. Prepare for Including the Change in the Next Published Calva 26 | 27 | 1. When a PR looks good 28 | 1. Make sure the PR is directed at `dev`` 29 | 1. Merge the PR. 30 | 1. Click the **Delete branch** button that Github offers (when it's not from a fork, that is). 31 | 1. Consider if `README.md` needs update. 32 | 1. Commit with a message like: ”Add feature: **Short Release Description Title**" 33 | 1. Consider if there should be a prerelease made of this. If so: 34 | 1. Tag the prerelease with `v-release-description-title`, _and make sure to make it a tag with a message_. 35 | 1. Push using `--follow-tags` 36 | 1. Regardless if prerelease or not. 37 | 1. Circle CI runs our tests, and builds a VSIX. 38 | 1. Download this VSIX post on #calva asking for help testing it. Attaching the `[Unreleased]` CHANGELOG entry is an easy way to let people know what is new. 39 | 40 | 41 | ## 3. Publishing a New Calva Version 42 | 43 | When a VSIX is good enough for release, and someone authorized to commit to the `master` branch has _at least half an hour of spare time_, the following will bring it to the Marketplace: 44 | 45 | 1. Checkout `dev` 46 | 1. Add the CHANGELOG entries from `[Unreleased]`, **NB** There should be no newline between the header and the entries. 47 | 1. Commit. 48 | 1. Tag with `v` (and you must provide a tag message, else the CI pipeline won't pick it up correctly) 49 | 1. Push `dev` (Using `--follow-tags`). 50 | * This will build the release VSIX, push a release to GitHub, and publish it on the extension Marketplace + `open-vsx.org`. **Note:** There is an approval step in the CircleCI pipeline. The release and VSIX will not be published until the Approve button is clicked in CircleCI. 51 | * You'll get an e-mail when it is published. (Or maybe only @pez gets that, not sure). 52 | 1. When the new version is live, immediately install it and see that it works. 53 | * If the Marketplace version works: 54 | 1. Merge `dev` onto `master` 55 | 1. Push 56 | 1. Checkout `dev` and `$ npm run bump-version` 57 | 1. Commit with this message: "`Bring on version: `v`! `[skip ci]`”. 58 | 1. Push. 59 | * If the Marketplace version does not work: 60 | 1. Install the artifact from the release build and test it. 61 | * If this works, then something in Microsoft's publish pipeline has broken the extension. This has happened (a few times) before. To retry again you need to build a new VSIX with a bumped version: 62 | 1. `$ npm run bump-version`. 63 | 1. Push. 64 | * If the artifact doesn't work (we should never be here). 65 | 1. ??? 66 | 67 | ## 4. Updating README.md (and other docs) after publishing 68 | 69 | Sometimes we need to update the documentation contained in the Calva repo, such as `README.md`, of the published extension, w/o publishing a new version. 70 | 71 | Make the changes on `master` and cherry pick back on `dev`. 72 | -------------------------------------------------------------------------------- /docs/readthedocs/source/debugger.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Calva Doccumentation has Moved 4 | 5 | [calva.io](https://calva.io) 6 | 7 | # Debugger 8 | 9 | Calva's debugger allows you to place breapoints via reader tags so that execution will pause at those locations. During paused execution, you can see the value of local variables in the side pane, as well as evaluate code within the local context - via the editor or the repl-window, just as you normally would evaluate code. 10 | 11 | The debugger is in an early release stage. Some functionality does not yet work. See the [features](#features) section for details. 12 | 13 | Note: The debugger currently does not support ClojureScript. Calva's debugger utilizes cider-nrepl for debugging. See [this Cider issue](https://github.com/clojure-emacs/cider/issues/1416) for more information. 14 | 15 | ## Using the Debugger 16 | 17 | ### Setting Breakpoints with `#break` 18 | 19 | You can insert a breakpoint manually into any code by placing a `#break` in front of the form where you want execution to pause, and then evaluating the top level form with `ctrl+alt+c space`. When you evaluate a call to this code the VS Code debugger will start, the cursor will move to right after the form that's preceded by `#break`, and the line will be highlighted so show execution is paused there. 20 | 21 | Note: Code will be executed up to and including the form preceding the cursor. 22 | 23 | ![Setting a breakpoint with #break](images/debugger/break.png) 24 | 25 | You can also set conditional breapoints by adding metadata before the form that the `#break` applies to. 26 | 27 | ![Setting a conditional breakpoint with #break](images/debugger/break-conditional.png) 28 | 29 | ### Instrumenting a Function with `#dbg` 30 | 31 | Adding `#dbg` before a function definition then evaluating the top-level form with `ctrl+alt+c space` will instrument the function, meaning breakpoints will be added in places where it makes sense. When you evaluate a call to this function, execution will pause wherever breakpoints were added. These breakpoints are not visible in the editor. If you notice execution does not pause, it's likely that no reasonable place was found to place a breakpoint. 32 | 33 | ![Setting a breakpoint with #dbg](images/debugger/dbg.png) 34 | 35 | ### Evaluating Code in the Paused Context 36 | 37 | When execution is paused at a breakpoint, you can evaluate code in that context. This can be done in the editor or in the REPL window, as usual. In the REPL window, the prompt changes to `<>=>` to show that evaluations will occur in the debug context. 38 | 39 | ![Evaluating code in the paused context via the editor](images/debugger/eval-editor.png) 40 | 41 | ![Evaluating code in the paused context via the repl window](images/debugger/eval-repl-window.png) 42 | 43 | ### Viewing Variable Values While Debugging 44 | 45 | While debugging, you can view the values of variables in VS Code's debugger side pane. You can also view values by hovering over the variables in the editor. Currently, values for collections and maps are shown as strings, but we plan to make them structured in the future. For now, if you want to see the value of a large structured variable, you can evaluate the variable, either from the editor or from the REPL window. 46 | 47 | ![Viewing variable values in the side pane](images/debugger/viewing-variable-values.png) 48 | 49 | ### Navigation 50 | 51 | You can use VS Code's debugger UI to navigate while debugging. Currently only the continue functionality is implemented, but in the future step over, step into, and step out will be implemented as well. Clicking the step buttons currently does nothing. Also, clicking restart does nothing, since this functionality does not make sense for our debugger. 52 | 53 | ![VS Code's debugger navigation buttons](images/debugger/navigation-buttons.png) 54 | 55 | ## Features 56 | 57 | ### Current 58 | 59 | - Set breakpoints with `#break` 60 | - Instrument functions with `#dbg` 61 | - Continue to next breakpoint 62 | - Evaluate code in the debug context 63 | - See variable values in the debugger side pane 64 | - See variable values on hover in the editor 65 | 66 | ### Upcoming 67 | 68 | - Step over form 69 | - Step into form 70 | - Step out of form 71 | - See structured variables in the debugger side pane (currently maps and collections are just shown as strings) 72 | -------------------------------------------------------------------------------- /src/providers/signature.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument, Position, Range, CancellationToken, SignatureHelp, SignatureHelpProvider, SignatureInformation } from 'vscode'; 2 | import * as util from '../utilities'; 3 | import * as infoparser from './infoparser'; 4 | import { LispTokenCursor } from '../cursor-doc/token-cursor'; 5 | import * as docMirror from '../doc-mirror'; 6 | 7 | export class CalvaSignatureHelpProvider implements SignatureHelpProvider { 8 | async provideSignatureHelp(document: TextDocument, position: Position, _token: CancellationToken): Promise { 9 | if (util.getConnectedState()) { 10 | const ns = util.getNamespace(document), 11 | idx = document.offsetAt(position), 12 | symbol = this.getSymbol(document, idx); 13 | if (symbol) { 14 | const client = util.getSession(util.getFileType(document)); 15 | if (client) { 16 | await util.createNamespaceFromDocumentIfNotExists(document); 17 | const res = await client.info(ns, symbol), 18 | signatures = infoparser.getSignatures(res, symbol); 19 | if (signatures) { 20 | const help = new SignatureHelp(), 21 | currentArgsRanges = this.getCurrentArgsRanges(document, idx); 22 | help.signatures = signatures; 23 | help.activeSignature = this.getActiveSignatureIdx(signatures, currentArgsRanges.length); 24 | if (signatures[help.activeSignature].parameters !== undefined) { 25 | const currentArgIdx = currentArgsRanges.findIndex(range => range.contains(position)), 26 | activeSignature = signatures[help.activeSignature]; 27 | help.activeParameter = activeSignature.label.match(/&/) !== null ? 28 | Math.min(currentArgIdx, activeSignature.parameters.length - 1) : 29 | currentArgIdx; 30 | } 31 | return (help); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | 38 | private getActiveSignatureIdx(signatures: SignatureInformation[], currentArgsCount): number { 39 | const activeSignatureIdx = signatures.findIndex(signature => signature.parameters && signature.parameters.length >= currentArgsCount); 40 | return activeSignatureIdx !== -1 ? activeSignatureIdx : signatures.length - 1; 41 | } 42 | 43 | private getSymbol(document: TextDocument, idx: number): string { 44 | const cursor: LispTokenCursor = docMirror.getDocument(document).getTokenCursor(idx); 45 | return cursor.getFunction(); 46 | } 47 | 48 | private coordsToRange(coords: [[number, number], [number, number]]): Range { 49 | return new Range(new Position(...coords[0]), new Position(...coords[1])); 50 | } 51 | 52 | private getPreviousRangeIndexAndFunction(document: TextDocument, idx: number) { 53 | const peekBehindCursor: LispTokenCursor = docMirror.getDocument(document).getTokenCursor(idx); 54 | peekBehindCursor.backwardFunction(1); 55 | const previousFunction = peekBehindCursor.getFunction(0), 56 | previousRanges = peekBehindCursor.rangesForSexpsInList('(').map(this.coordsToRange), 57 | previousRangeIndex = previousRanges.findIndex(range => range.contains(document.positionAt(idx))); 58 | return { previousRangeIndex, previousFunction }; 59 | } 60 | 61 | private getCurrentArgsRanges(document: TextDocument, idx: number): Range[] { 62 | const cursor: LispTokenCursor = docMirror.getDocument(document).getTokenCursor(idx), 63 | allRanges = cursor.rangesForSexpsInList('('); 64 | 65 | // Are we in a function that gets a threaded first parameter? 66 | const { previousRangeIndex, previousFunction } = this.getPreviousRangeIndexAndFunction(document, idx); 67 | const isInThreadFirst: boolean = 68 | previousRangeIndex > 1 && ['->', 'some->'].includes(previousFunction) || 69 | previousRangeIndex > 1 && previousRangeIndex % 2 && previousFunction === 'cond->'; 70 | 71 | if (allRanges !== undefined) { 72 | return allRanges 73 | .slice(1 - (isInThreadFirst ? 1 : 0)) 74 | .map(this.coordsToRange) 75 | } 76 | } 77 | } --------------------------------------------------------------------------------