From 32ba71ba016fde9528a3498892e314f5d2b64c73 Mon Sep 17 00:00:00 2001 From: Caleb Figgers Date: Thu, 24 Nov 2022 07:26:03 -0600 Subject: [PATCH 1/8] Recognize Greek alphabet --- syntaxes/hy.tmLanguage.json | 2 +- test/syntax-sample.hy | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/syntaxes/hy.tmLanguage.json b/syntaxes/hy.tmLanguage.json index 04fd3e6..ee731b3 100644 --- a/syntaxes/hy.tmLanguage.json +++ b/syntaxes/hy.tmLanguage.json @@ -73,7 +73,7 @@ }, "symbol": { "name": "variable.other.hy", - "match": "(?*#])[\\.a-zA-Z_\\-=!@\\$%^*#][\\.:\\w_\\-=!@\\$%^&?/<>*#]*" + "match": "(?*#])[\\.a-zA-ZΑ-Ωα-ω_\\-=!@\\$%^*#][\\.:\\w_\\-=!@\\$%^&?/<>*#]*" } }, "scopeName": "source.hy" diff --git a/test/syntax-sample.hy b/test/syntax-sample.hy index 3454e78..439307a 100644 --- a/test/syntax-sample.hy +++ b/test/syntax-sample.hy @@ -15,5 +15,3 @@ (setv dict {:a 1 :b 2 :c 3}) (get dict :a) - - From 5a1aabad31a4ca62178226ac2182370613eb1596 Mon Sep 17 00:00:00 2001 From: Caleb Figgers Date: Thu, 24 Nov 2022 08:52:20 -0600 Subject: [PATCH 2/8] Initial work adding ParEdit --- deps.edn | 7 + package.json | 856 ++++++++- shadow-cljs.edn | 29 + src/analytics.ts | 81 + src/calva-fmt/.vscodeignore | 17 + src/calva-fmt/README.md | 89 + src/calva-fmt/assets/align-items.gif | Bin 0 -> 100435 bytes src/calva-fmt/assets/calva-fmt.png | Bin 0 -> 85751 bytes src/calva-fmt/assets/format-current-form.gif | Bin 0 -> 199957 bytes src/calva-fmt/assets/infer-parens.gif | Bin 0 -> 77990 bytes src/calva-fmt/assets/parinfer.gif | Bin 0 -> 121901 bytes .../atom-language-clojure/.coffeelintignore | 1 + .../atom-language-clojure/.travis.yml | 15 + .../atom-language-clojure/LICENSE.md | 48 + src/calva-fmt/atom-language-clojure/README.md | 26 + .../atom-language-clojure/appveyor.yml | 27 + .../atom-language-clojure/coffeelint.json | 37 + .../grammars/clojure.cson | 424 +++++ .../atom-language-clojure/package.json | 28 + .../settings/language-clojure.cson | 5 + .../snippets/language-clojure.cson | 111 ++ .../spec/clojure-spec.coffee | 471 +++++ src/calva-fmt/src/config.ts | 68 + src/calva-fmt/src/extension.ts | 98 ++ src/calva-fmt/src/format.ts | 257 +++ src/calva-fmt/src/infer.ts | 137 ++ .../src/providers/ontype_formatter.ts | 51 + .../src/providers/range_formatter.ts | 13 + src/calva-fmt/src/state.ts | 36 + src/calva-fmt/update-grammar.js | 66 + src/cljs-lib/src/calva/dartclojure.cljs | 34 + src/cljs-lib/src/calva/fmt/editor.cljs | 37 + src/cljs-lib/src/calva/fmt/formatter.cljs | 339 ++++ src/cljs-lib/src/calva/fmt/inferer.cljs | 130 ++ src/cljs-lib/src/calva/fmt/playground.cljs | 158 ++ src/cljs-lib/src/calva/fmt/util.cljs | 38 + src/cljs-lib/src/calva/js2cljs/converter.cljs | 50 + src/cljs-lib/src/calva/js_utils.cljs | 11 + src/cljs-lib/src/calva/main.cljs | 4 + src/cljs-lib/src/calva/parse.cljs | 65 + src/cljs-lib/src/calva/pprint/printer.cljs | 66 + src/cljs-lib/src/calva/state.cljs | 20 + src/cljs-lib/src/js_cljs/core.cljs | 418 +++++ src/cljs-lib/src/pez_cljfmt/core.clj | 5 + src/cljs-lib/src/pez_cljfmt/core.cljs | 485 +++++ src/cljs-lib/src/pez_rewrite_clj/node.cljs | 197 +++ .../src/pez_rewrite_clj/node/coercer.cljs | 136 ++ .../src/pez_rewrite_clj/node/comment.cljs | 36 + src/cljs-lib/src/pez_rewrite_clj/node/fn.cljs | 97 + .../src/pez_rewrite_clj/node/forms.cljs | 43 + .../src/pez_rewrite_clj/node/keyword.cljs | 48 + .../src/pez_rewrite_clj/node/meta.cljs | 52 + .../src/pez_rewrite_clj/node/protocols.cljs | 105 ++ .../src/pez_rewrite_clj/node/quote.cljs | 75 + .../pez_rewrite_clj/node/reader_macro.cljs | 134 ++ .../src/pez_rewrite_clj/node/seq.cljs | 65 + .../src/pez_rewrite_clj/node/stringz.cljs | 48 + .../src/pez_rewrite_clj/node/token.cljs | 28 + .../src/pez_rewrite_clj/node/uneval.cljs | 39 + .../src/pez_rewrite_clj/node/whitespace.cljs | 131 ++ src/cljs-lib/src/pez_rewrite_clj/paredit.cljs | 551 ++++++ src/cljs-lib/src/pez_rewrite_clj/parser.cljs | 35 + .../src/pez_rewrite_clj/parser/core.cljs | 172 ++ .../src/pez_rewrite_clj/parser/keyword.cljs | 17 + .../src/pez_rewrite_clj/parser/string.cljs | 41 + .../src/pez_rewrite_clj/parser/token.cljs | 65 + .../pez_rewrite_clj/parser/whitespace.cljs | 13 + src/cljs-lib/src/pez_rewrite_clj/reader.cljs | 201 +++ src/cljs-lib/src/pez_rewrite_clj/zip.cljs | 205 +++ .../src/pez_rewrite_clj/zip/base.cljs | 90 + .../src/pez_rewrite_clj/zip/editz.cljs | 95 + .../src/pez_rewrite_clj/zip/findz.cljs | 147 ++ .../src/pez_rewrite_clj/zip/insert.cljs | 55 + .../src/pez_rewrite_clj/zip/move.cljs | 73 + .../src/pez_rewrite_clj/zip/removez.cljs | 62 + .../src/pez_rewrite_clj/zip/seqz.cljs | 110 ++ .../src/pez_rewrite_clj/zip/utils.cljs | 93 + .../src/pez_rewrite_clj/zip/whitespace.cljs | 76 + src/cljs-lib/test/calva/fmt/editor_test.cljs | 13 + .../test/calva/fmt/formatter_test.cljs | 344 ++++ src/cljs-lib/test/calva/fmt/util_test.cljs | 49 + .../test/calva/js2cljs/converter_test.cljs | 23 + src/cljs-lib/test/calva/js_utils_test.cljs | 11 + src/cljs-lib/test/calva/parse_test.cljs | 38 + .../test/calva/pprint/printer_test.cljs | 35 + src/cljs-lib/test/calva/state_test.cljs | 31 + .../test/pez_rewrite_clj/node_test.cljs | 55 + .../test/pez_rewrite_clj/paredit_test.cljs | 560 ++++++ src/cljs-lib/test/pez_rewrite_clj/runner.cljs | 15 + .../test/pez_rewrite_clj/zip/editz_test.cljs | 14 + .../test/pez_rewrite_clj/zip/findz_test.cljs | 46 + .../test/pez_rewrite_clj/zip/seqz_test.cljs | 33 + .../test/pez_rewrite_clj/zip_test.cljs | 37 + src/config.ts | 179 ++ src/cursor-doc/cdf-edits/hy-lexer.ts | 258 +++ src/cursor-doc/clojure-lexer.ts | 209 +++ src/cursor-doc/cursor-context.ts | 93 + src/cursor-doc/indent.ts | 202 +++ src/cursor-doc/lexer.ts | 106 ++ src/cursor-doc/model.ts | 604 +++++++ src/cursor-doc/paredit.ts | 1561 +++++++++++++++++ src/cursor-doc/token-cursor.ts | 938 ++++++++++ src/cursor-doc/undo.ts | 130 ++ src/doc-mirror/index.ts | 271 +++ src/edit.ts | 39 + src/evaluate.ts | 607 +++++++ src/files-cache.ts | 77 + src/hyMain.ts | 115 +- src/paredit/extension.ts | 464 +++++ src/paredit/statusbar.ts | 77 + src/providers/annotations.ts | 206 +++ src/state.ts | 174 ++ src/status.ts | 26 + src/utilities.ts | 586 +++++++ src/when-contexts.ts | 79 + tsconfig.json | 2 +- 116 files changed, 16495 insertions(+), 5 deletions(-) create mode 100644 deps.edn create mode 100644 shadow-cljs.edn create mode 100644 src/analytics.ts create mode 100644 src/calva-fmt/.vscodeignore create mode 100644 src/calva-fmt/README.md create mode 100644 src/calva-fmt/assets/align-items.gif create mode 100644 src/calva-fmt/assets/calva-fmt.png create mode 100644 src/calva-fmt/assets/format-current-form.gif create mode 100644 src/calva-fmt/assets/infer-parens.gif create mode 100644 src/calva-fmt/assets/parinfer.gif create mode 100644 src/calva-fmt/atom-language-clojure/.coffeelintignore create mode 100644 src/calva-fmt/atom-language-clojure/.travis.yml create mode 100644 src/calva-fmt/atom-language-clojure/LICENSE.md create mode 100644 src/calva-fmt/atom-language-clojure/README.md create mode 100644 src/calva-fmt/atom-language-clojure/appveyor.yml create mode 100644 src/calva-fmt/atom-language-clojure/coffeelint.json create mode 100644 src/calva-fmt/atom-language-clojure/grammars/clojure.cson create mode 100644 src/calva-fmt/atom-language-clojure/package.json create mode 100644 src/calva-fmt/atom-language-clojure/settings/language-clojure.cson create mode 100644 src/calva-fmt/atom-language-clojure/snippets/language-clojure.cson create mode 100644 src/calva-fmt/atom-language-clojure/spec/clojure-spec.coffee create mode 100644 src/calva-fmt/src/config.ts create mode 100644 src/calva-fmt/src/extension.ts create mode 100644 src/calva-fmt/src/format.ts create mode 100644 src/calva-fmt/src/infer.ts create mode 100644 src/calva-fmt/src/providers/ontype_formatter.ts create mode 100644 src/calva-fmt/src/providers/range_formatter.ts create mode 100644 src/calva-fmt/src/state.ts create mode 100644 src/calva-fmt/update-grammar.js create mode 100644 src/cljs-lib/src/calva/dartclojure.cljs create mode 100644 src/cljs-lib/src/calva/fmt/editor.cljs create mode 100644 src/cljs-lib/src/calva/fmt/formatter.cljs create mode 100644 src/cljs-lib/src/calva/fmt/inferer.cljs create mode 100644 src/cljs-lib/src/calva/fmt/playground.cljs create mode 100644 src/cljs-lib/src/calva/fmt/util.cljs create mode 100644 src/cljs-lib/src/calva/js2cljs/converter.cljs create mode 100644 src/cljs-lib/src/calva/js_utils.cljs create mode 100644 src/cljs-lib/src/calva/main.cljs create mode 100644 src/cljs-lib/src/calva/parse.cljs create mode 100644 src/cljs-lib/src/calva/pprint/printer.cljs create mode 100644 src/cljs-lib/src/calva/state.cljs create mode 100644 src/cljs-lib/src/js_cljs/core.cljs create mode 100644 src/cljs-lib/src/pez_cljfmt/core.clj create mode 100644 src/cljs-lib/src/pez_cljfmt/core.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/coercer.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/comment.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/fn.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/forms.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/keyword.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/meta.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/protocols.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/quote.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/reader_macro.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/seq.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/stringz.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/token.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/uneval.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/node/whitespace.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/paredit.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/parser.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/parser/core.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/parser/keyword.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/parser/string.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/parser/token.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/parser/whitespace.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/reader.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/zip.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/zip/base.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/zip/editz.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/zip/findz.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/zip/insert.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/zip/move.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/zip/removez.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/zip/seqz.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/zip/utils.cljs create mode 100644 src/cljs-lib/src/pez_rewrite_clj/zip/whitespace.cljs create mode 100644 src/cljs-lib/test/calva/fmt/editor_test.cljs create mode 100644 src/cljs-lib/test/calva/fmt/formatter_test.cljs create mode 100644 src/cljs-lib/test/calva/fmt/util_test.cljs create mode 100644 src/cljs-lib/test/calva/js2cljs/converter_test.cljs create mode 100644 src/cljs-lib/test/calva/js_utils_test.cljs create mode 100644 src/cljs-lib/test/calva/parse_test.cljs create mode 100644 src/cljs-lib/test/calva/pprint/printer_test.cljs create mode 100644 src/cljs-lib/test/calva/state_test.cljs create mode 100644 src/cljs-lib/test/pez_rewrite_clj/node_test.cljs create mode 100644 src/cljs-lib/test/pez_rewrite_clj/paredit_test.cljs create mode 100644 src/cljs-lib/test/pez_rewrite_clj/runner.cljs create mode 100644 src/cljs-lib/test/pez_rewrite_clj/zip/editz_test.cljs create mode 100644 src/cljs-lib/test/pez_rewrite_clj/zip/findz_test.cljs create mode 100644 src/cljs-lib/test/pez_rewrite_clj/zip/seqz_test.cljs create mode 100644 src/cljs-lib/test/pez_rewrite_clj/zip_test.cljs create mode 100644 src/config.ts create mode 100644 src/cursor-doc/cdf-edits/hy-lexer.ts create mode 100644 src/cursor-doc/clojure-lexer.ts create mode 100644 src/cursor-doc/cursor-context.ts create mode 100644 src/cursor-doc/indent.ts create mode 100644 src/cursor-doc/lexer.ts create mode 100644 src/cursor-doc/model.ts create mode 100644 src/cursor-doc/paredit.ts create mode 100644 src/cursor-doc/token-cursor.ts create mode 100644 src/cursor-doc/undo.ts create mode 100644 src/doc-mirror/index.ts create mode 100644 src/edit.ts create mode 100644 src/evaluate.ts create mode 100644 src/files-cache.ts create mode 100644 src/paredit/extension.ts create mode 100644 src/paredit/statusbar.ts create mode 100644 src/providers/annotations.ts create mode 100644 src/state.ts create mode 100644 src/status.ts create mode 100644 src/utilities.ts create mode 100644 src/when-contexts.ts diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..1ccc59b --- /dev/null +++ b/deps.edn @@ -0,0 +1,7 @@ +{:deps {zprint/zprint {:mvn/version "1.2.2"} + cljfmt/cljfmt {:mvn/version "0.8.0"} + thheller/shadow-cljs {:mvn/version "2.18.0"} + org.clojars.liverm0r/dartclojure {:mvn/version "0.1.10-SNAPSHOT"} + #_#_org.clojars.liverm0r/dartclojure {:local/root "../DartClojure"}} + :paths ["src/cljs-lib/src" + "src/cljs-lib/test"]} \ No newline at end of file diff --git a/package.json b/package.json index 6a499a4..5ce9d11 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,10 @@ "hy", "syntax" ], - "version": "0.0.2", + "version": "0.0.3", "publisher": "hylang", "icon": "images/hy-logo-small.png", - "main": "./out/src/hyMain", + "main": "./out/hyMain", "license": "MIT", "author": { "name": "Caleb Figgers" @@ -57,6 +57,98 @@ "path": "./syntaxes/hy.tmLanguage.json" } ], + "configurationDefaults": { + "[hy]": { + "editor.wordSeparators": "\t ()\"':,;~@#$%^&{}[]`", + "editor.autoClosingBrackets": "always", + "editor.autoClosingQuotes": "always", + "editor.formatOnType": true, + "editor.autoIndent": "full", + "editor.formatOnPaste": true, + "files.trimTrailingWhitespace": false, + "editor.matchBrackets": "never", + "editor.guides.indentation": false, + "editor.parameterHints.enabled": false, + "editor.unicodeHighlight.allowedCharacters": { + " ": true, + "꞉": true + } + } + }, + "configuration": [ + { + "type": "object", + "title": "Hy", + "properties": { + "Hy.keybindingsEnabled": { + "type": "boolean", + "description": "Activate keybindings.", + "default": true, + "scope": "window" + } + } + }, + { + "type": "object", + "title": "Paredit", + "properties": { + "hy.paredit.defaultKeyMap": { + "type": "string", + "description": "The default keymap to use for bindings when there is no custom binding.", + "default": "strict", + "enum": [ + "original", + "strict", + "none" + ], + "scope": "window" + }, + "hy.paredit.hijackVSCodeDefaults": { + "type": "boolean", + "markdownDescription": "When enabled, more VS Code built-in shortcuts are overridden with their ”corresponding” Paredit commands.", + "default": true, + "scope": "window" + }, + "hy.paredit.strictPreventUnmatchedClosingBracket": { + "type": "boolean", + "markdownDescription": "Experimental: Prevents you from entering unmatched closing brackets when in `strict` mode. (Does not work when there is an active selection.)", + "default": false, + "scope": "window" + }, + "hy.paredit.killAlsoCutsToClipboard": { + "type": "boolean", + "markdownDescription": "When enabled, replaces the clipboard content with the deleted code.", + "default": false, + "scope": "window" + } + } + }, + { + "title": "Calva-fmt", + "type": "object", + "properties": { + "calva.fmt.configPath": { + "type": "string", + "markdownDescription": "Path to [cljfmt](https://github.com/weavejester/cljfmt#configuration) configuration file. Absolute or relative to the project root directory. To provide the config via [clojure-lsp](https://clojure-lsp.io), set this to `CLOJURE-LSP` (case sensitive)." + }, + "calva.fmt.formatAsYouType": { + "type": "boolean", + "default": true, + "description": "Auto-adjust indentation and format as you enter new lines." + }, + "calva.fmt.newIndentEngine": { + "type": "boolean", + "default": true, + "markdownDescription": "Use the structural editor for indentation (instead of `cljfmt`)." + }, + "calva.fmt.keepCommentTrailParenOnOwnLine": { + "type": "boolean", + "default": true, + "markdownDescription": "Treat `(comment...)` forms special and keep its closing paren on a line of its own." + } + } + } + ], "commands": [ { "command": "hy.startREPL", @@ -69,6 +161,402 @@ { "command": "hy.evalFile", "title": "Hy: Evaluate file" + }, + { + "command": "hy.continueComment", + "title": "Continue Comment (add a commented line below).", + "category": "Hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.togglemode", + "title": "Toggle Paredit Mode", + "when": "editorLangId == hy && paredit:keyMap =~ /original|strict/", + "enablement": "editorLangId == hy && paredit:keyMap =~ /original|strict/" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.forwardSexp", + "title": "Move Cursor Forward Sexp/Form", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.backwardSexp", + "title": "Move Cursor Backward Sexp/Form", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.forwardSexpOrUp", + "title": "Move Cursor Forward or Up Sexp/Form", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.backwardSexpOrUp", + "title": "Move Cursor Backward or Up Sexp/Form", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.forwardDownSexp", + "title": "Move Cursor Forward Down Sexp/Form", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.backwardDownSexp", + "title": "Move Cursor Backward Down Sexp/Form", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.backwardUpSexp", + "title": "Move Cursor Backward Up Sexp/Form", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.forwardUpSexp", + "title": "Move Cursor Forward Up Sexp/Form", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.closeList", + "title": "Move Cursor Forward to List End/Close", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectForwardSexp", + "title": "Select Forward Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectRight", + "title": "Select Right", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectBackwardSexp", + "title": "Select Backward Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectForwardDownSexp", + "title": "Select Forward Down Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectBackwardDownSexp", + "title": "Select Backward Down Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectBackwardUpSexp", + "title": "Select Backward Up Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectForwardUpSexp", + "title": "Select Forward Up Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectBackwardSexpOrUp", + "title": "Select Backward Or Up Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectForwardSexpOrUp", + "title": "Select Forward Or Up Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectCloseList", + "title": "Select Forward to List End/Close", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.selectOpenList", + "title": "Select Backward to List Start/Open", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.rangeForDefun", + "title": "Select Current Top Level (aka defun) Form", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.sexpRangeExpansion", + "title": "Expand Selection", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.sexpRangeContraction", + "title": "Shrink Selection", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.slurpSexpForward", + "title": "Slurp Sexp Forward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.slurpSexpBackward", + "title": "Slurp Sexp Backward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.barfSexpForward", + "title": "Barf Sexp Forward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.barfSexpBackward", + "title": "Barf Sexp Backward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.spliceSexp", + "title": "Splice Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.splitSexp", + "title": "Split Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.joinSexp", + "title": "Join Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.raiseSexp", + "title": "Raise Sexp", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.transpose", + "title": "Transpose (Swap) the two Sexps Around the Cursor", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.dragSexprBackward", + "title": "Drag Sexp Backward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.dragSexprForward", + "title": "Drag Sexp Forward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.dragSexprBackwardUp", + "title": "Drag Sexp Backward Up", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.dragSexprForwardDown", + "title": "Drag Sexp Forward Down", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.dragSexprForwardUp", + "title": "Drag Sexp Forward Up", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.dragSexprBackwardDown", + "title": "Drag Sexp Backward Down", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.convolute", + "title": "Convolute Sexp ¯\\_(ツ)_/¯", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.killRight", + "title": "Kill/Delete Right", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.killSexpForward", + "title": "Kill/Delete Sexp Forward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.killSexpBackward", + "title": "Kill/Delete Sexp Backward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.killListForward", + "title": "Kill/Delete Forward to End of List", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.killListBackward", + "title": "Kill/Delete Backward to Start of List", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.spliceSexpKillForward", + "title": "Splice & Kill/Delete Forward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.spliceSexpKillBackward", + "title": "Splice & Kill/Delete Backward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.deleteForward", + "title": "Delete Forward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.deleteBackward", + "title": "Delete Backward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.forceDeleteForward", + "title": "Force Delete Forward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.forceDeleteBackward", + "title": "Force Delete Backward", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.wrapAroundParens", + "title": "Wrap Around ()", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.wrapAroundSquare", + "title": "Wrap Around []", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.wrapAroundCurly", + "title": "Wrap Around {}", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.wrapAroundQuote", + "title": "Wrap Around \"\"", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.rewrapParens", + "title": "Rewrap ()", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.rewrapSquare", + "title": "Rewrap []", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.rewrapCurly", + "title": "Rewrap {}", + "enablement": "editorLangId == hy" + }, + { + "category": "Hy Paredit", + "command": "hy.paredit.rewrapQuote", + "title": "Rewrap \"\"", + "enablement": "editorLangId == hy" + }, + { + "command": "calva-fmt.formatCurrentForm", + "title": "Format Current Form", + "category": "Hy Format", + "enablement": "editorLangId == hy" + }, + { + "command": "calva-fmt.alignCurrentForm", + "title": "Format and Align Current Form (recursively, experimental)", + "category": "Hy Format", + "enablement": "editorLangId == hy" + }, + { + "command": "calva-fmt.trimCurrentFormWhiteSpace", + "title": "Format Current Form and trim space between forms", + "category": "Hy Format", + "enablement": "editorLangId == hy" + }, + { + "command": "calva-fmt.inferParens", + "title": "Infer Parens (from the indentation)", + "category": "Hy Format", + "enablement": "editorLangId == hy" + }, + { + "command": "calva-fmt.tabIndent", + "title": "Indent Line", + "category": "Hy Format", + "enablement": "editorLangId == hy" + }, + { + "command": "calva-fmt.tabDedent", + "title": "Dedent Line", + "category": "Hy Format", + "enablement": "editorLangId == hy" } ], "keybindings": [ @@ -79,6 +567,358 @@ { "command": "hy.evalFile", "key": "ctrl+alt+enter" + }, + { + "command": "hy.paredit.togglemode", + "key": "ctrl+alt+p ctrl+alt+m", + "when": "editorLangId == hy && hy:keybindingsEnabled && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.backwardSexp", + "mac": "ctrl+left", + "win": "alt+left", + "linux": "alt+left", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/ && !config.hy.paredit.hijackVSCodeDefaults && !hy:cursorInComment || hy:cursorBeforeComment" + }, + { + "command": "hy.paredit.backwardSexp", + "mac": "alt+left", + "win": "ctrl+left", + "linux": "ctrl+left", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/ && config.hy.paredit.hijackVSCodeDefaults && !hy:cursorInComment || hy:cursorBeforeComment" + }, + { + "command": "hy.paredit.forwardSexp", + "mac": "ctrl+right", + "win": "alt+right", + "linux": "alt+right", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/ && !config.hy.paredit.hijackVSCodeDefaults && !hy:cursorInComment || hy:cursorAfterComment" + }, + { + "command": "hy.paredit.forwardSexp", + "mac": "alt+right", + "win": "ctrl+right", + "linux": "ctrl+right", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/ && config.hy.paredit.hijackVSCodeDefaults && !hy:cursorInComment || hy:cursorAfterComment" + }, + { + "command": "hy.paredit.forwardDownSexp", + "key": "ctrl+down", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.backwardDownSexp", + "key": "ctrl+alt+up", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.forwardUpSexp", + "key": "ctrl+alt+down", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.backwardUpSexp", + "key": "ctrl+up", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.closeList", + "key": "ctrl+end", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.openList", + "key": "ctrl+home", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.selectForwardSexp", + "mac": "shift+alt+right", + "win": "shift+ctrl+right", + "linux": "shift+ctrl+right", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.selectRight", + "mac": "ctrl+shift+k", + "win": "ctrl+k ctrl+shift+k", + "linux": "ctrl+k ctrl+shift+k", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !selectionAnchorSet && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.selectBackwardSexp", + "mac": "shift+alt+left", + "win": "shift+ctrl+left", + "linux": "shift+ctrl+left", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.selectForwardDownSexp", + "key": "ctrl+shift+down", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.selectBackwardDownSexp", + "key": "ctrl+shift+alt+up", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.selectForwardUpSexp", + "key": "ctrl+shift+alt+down", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.selectBackwardUpSexp", + "key": "ctrl+shift+up", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.selectCloseList", + "key": "ctrl+shift+end", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.selectOpenList", + "key": "ctrl+shift+home", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.rangeForDefun", + "key": "ctrl+alt+w space", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.sexpRangeExpansion", + "mac": "ctrl+w", + "win": "shift+alt+right", + "linux": "shift+alt+right", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/ && !hy:cursorInComment" + }, + { + "command": "hy.paredit.sexpRangeContraction", + "mac": "ctrl+shift+w", + "win": "shift+alt+left", + "linux": "shift+alt+left", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/ && !hy:cursorInComment" + }, + { + "command": "hy.paredit.slurpSexpForward", + "key": "ctrl+alt+win+right", + "linux": "ctrl+alt+.", + "when": "hy:keybindingsEnabled && editorLangId == hy" + }, + { + "command": "hy.paredit.slurpSexpBackward", + "key": "ctrl+alt+win+left", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.barfSexpForward", + "key": "ctrl+alt+shift+left", + "linux": "ctrl+alt+,", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.barfSexpBackward", + "key": "ctrl+alt+shift+right", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.spliceSexp", + "key": "ctrl+alt+s", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.splitSexp", + "key": "ctrl+shift+s", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.joinSexp", + "key": "ctrl+shift+j", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.raiseSexp", + "key": "ctrl+alt+p ctrl+alt+r", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.transpose", + "key": "ctrl+alt+t", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.dragSexprBackward", + "key": "ctrl+shift+alt+b", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/ && !hy:cursorInComment" + }, + { + "command": "hy.paredit.dragSexprForward", + "key": "ctrl+shift+alt+f", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/ && !hy:cursorInComment" + }, + { + "command": "hy.paredit.dragSexprBackward", + "key": "alt+up", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/ && config.hy.paredit.hijackVSCodeDefaults && !hy:cursorInComment" + }, + { + "command": "hy.paredit.dragSexprForward", + "key": "alt+down", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/ && config.hy.paredit.hijackVSCodeDefaults && !hy:cursorInComment" + }, + { + "command": "hy.paredit.dragSexprBackwardUp", + "key": "ctrl+shift+alt+u", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.dragSexprForwardDown", + "key": "ctrl+shift+alt+d", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.dragSexprForwardUp", + "key": "ctrl+shift+alt+k", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.dragSexprBackwardDown", + "key": "ctrl+shift+alt+j", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.convolute", + "key": "ctrl+shift+c", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.killRight", + "mac": "ctrl+k", + "win": "ctrl+k ctrl+k", + "linux": "ctrl+k ctrl+k", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !selectionAnchorSet && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.killSexpForward", + "key": "ctrl+shift+delete", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.killSexpBackward", + "key": "ctrl+alt+backspace", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.killListForward", + "key": "ctrl+delete", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.killListBackward", + "key": "ctrl+backspace", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.spliceSexpKillForward", + "key": "ctrl+alt+shift+delete", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.spliceSexpKillBackward", + "key": "ctrl+alt+shift+backspace", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.wrapAroundParens", + "key": "ctrl+alt+shift+p", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.wrapAroundSquare", + "key": "ctrl+alt+shift+s", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.wrapAroundCurly", + "key": "ctrl+alt+shift+c", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.wrapAroundQuote", + "key": "ctrl+alt+shift+q", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.rewrapParens", + "key": "ctrl+alt+r ctrl+alt+p", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.rewrapSquare", + "key": "ctrl+alt+r ctrl+alt+s", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.rewrapCurly", + "key": "ctrl+alt+r ctrl+alt+c", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.rewrapQuote", + "key": "ctrl+alt+r ctrl+alt+q", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap =~ /original|strict/" + }, + { + "command": "hy.paredit.deleteForward", + "key": "delete", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap == strict && !editorReadOnly && !editorHasMultipleSelections && !hy:cursorInComment" + }, + { + "command": "hy.paredit.deleteBackward", + "key": "backspace", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap == strict && !editorReadOnly && !editorHasMultipleSelections && !hy:cursorInComment" + }, + { + "command": "hy.paredit.forceDeleteForward", + "key": "alt+delete", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap == strict && !editorReadOnly && !editorHasMultipleSelections" + }, + { + "command": "hy.paredit.forceDeleteBackward", + "key": "alt+backspace", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && paredit:keyMap == strict && !editorReadOnly && !editorHasMultipleSelections" + }, + { + "command": "hy.continueComment", + "key": "enter", + "when": "hy:keybindingsEnabled && editorLangId = hy && editorTextfocus && !hy:cursorInComment" + }, + { + "command": "calva-fmt.formatCurrentForm", + "key": "tab", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !editorReadOnly && !inSnippetMode && !suggestWidgetVisible && !hasOtherSuggestions && !inSnippetMode && !inlineSuggestionVisible" + }, + { + "command": "calva-fmt.alignCurrentForm", + "key": "ctrl+alt+l", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" + }, + { + "command": "calva-fmt.inferParens", + "key": "ctrl+alt+p i", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" + }, + { + "command": "calva-fmt.tabIndent", + "key": "ctrl+i", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" + }, + { + "command": "calva-fmt.tabDedent", + "key": "shift+tab", + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" } ], "snippets": [ @@ -94,8 +934,18 @@ "postinstall": "node ./node_modules/vscode/bin/install", "test": "node ./node_modules/vscode/bin/test" }, + "dependencies": { + "@types/universal-analytics": "^0.4.2", + "acorn": "^6.4.1", + "immutable": "3.8.1", + "immutable-cursor": "2.0.1", + "lodash": "^4.17.19", + "lodash.isequal": "4.5.0", + "parinfer": "^3.12.0", + "universal-analytics": "^0.5.3", + "uuidv4": "6.2.12" + }, "devDependencies": { - "@types/mocha": "^2.2.32", "@types/node": "^12.12.0", "@types/vscode": "^1.34.0", "mocha": "^10.1.0", diff --git a/shadow-cljs.edn b/shadow-cljs.edn new file mode 100644 index 0000000..b6f5bc7 --- /dev/null +++ b/shadow-cljs.edn @@ -0,0 +1,29 @@ +{:deps true + + :builds {:calva-lib + {:target :node-library + :exports {:formatText calva.fmt.formatter/format-text-bridge + :formatTextAtRange calva.fmt.formatter/format-text-at-range-bridge + :formatTextAtIdx calva.fmt.formatter/format-text-at-idx-bridge + :formatTextAtIdxOnType calva.fmt.formatter/format-text-at-idx-on-type-bridge + :cljfmtOptionsFromString calva.fmt.formatter/merge-cljfmt-from-string-js-bridge + :inferIndents calva.fmt.inferer/infer-indents-bridge + :inferParens calva.fmt.inferer/infer-parens-bridge + :jsify calva.js-utils/jsify + :cljify calva.js-utils/cljify + :prettyPrint calva.pprint.printer/pretty-print-js-bridge + :parseEdn calva.parse/parse-edn-js-bridge + :parseForms calva.parse/parse-forms-js-bridge + :setStateValue calva.state/set-state-value! + :getStateValue calva.state/get-state-value + :getState calva.state/get-state + :removeStateValue calva.state/remove-state-value! + :js2cljs calva.js2cljs.converter/convert-bridge + :dart2clj calva.dartclojure/convert-bridge} + :output-to "out/cljs-lib/cljs-lib.js"} + :test + {:target :node-test + :output-to "out/cljs-lib/test/cljs-lib-tests.js" + :ns-regexp "-test$" + :autorun true}}} + diff --git a/src/analytics.ts b/src/analytics.ts new file mode 100644 index 0000000..3d2702b --- /dev/null +++ b/src/analytics.ts @@ -0,0 +1,81 @@ +import * as vscode from 'vscode'; +import * as UA from 'universal-analytics'; +import * as uuid from 'uuidv4'; +import * as os from 'os'; +import { isUndefined } from 'lodash'; + +// var debug = require('debug'); +// debug.log = console.info.bind(console); + +function userAllowsTelemetry(): boolean { + const config = vscode.workspace.getConfiguration('telemetry'); + return config.get('enableTelemetry', false); +} + +export default class Analytics { + private visitor: UA.Visitor; + private extension: vscode.Extension; + private extensionVersion: string; + private store: vscode.Memento; + private GA_ID = (process.env.CALVA_DEV_GA + ? process.env.CALVA_DEV_GA + : 'FUBAR-69796730-3' + ).replace(/^FUBAR/, 'UA'); + + constructor(context: vscode.ExtensionContext) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.extension = vscode.extensions.getExtension('betterthantomorrow.calva')!; + this.extensionVersion = this.extension.packageJSON.version; + this.store = context.globalState; + + this.visitor = UA(this.GA_ID, this.userID()); + this.visitor.set('cd1', this.extensionVersion); + this.visitor.set('cd2', vscode.version); + this.visitor.set('cd3', this.extensionVersion); + this.visitor.set('cd4', `${os.platform()}/${os.release()}`); + this.visitor.set('cn', `calva-${this.extensionVersion}`); + this.visitor.set( + 'ua', + `Calva/${this.extensionVersion} (${os.platform()}; ${os.release()}; ${os.type}) VSCode/${ + vscode.version + }` + ); + } + + private userID(): string { + const KEY = 'userLogID'; + const value = this.store.get(KEY); + if (isUndefined(value)) { + const newID = uuid.uuid(); + void this.store.update(KEY, newID); + return newID; + } else { + return value; + } + } + + logPath(path: string): Analytics { + if (userAllowsTelemetry()) { + this.visitor.pageview(path); + } + return this; + } + + logEvent(category: string, action: string, label?: string, value?: string): Analytics { + if (userAllowsTelemetry()) { + this.visitor.event({ + ec: category, + ea: action, + el: label, + ev: value, + }); + } + return this; + } + + send() { + if (userAllowsTelemetry()) { + this.visitor.send(); + } + } +} diff --git a/src/calva-fmt/.vscodeignore b/src/calva-fmt/.vscodeignore new file mode 100644 index 0000000..d75e9c4 --- /dev/null +++ b/src/calva-fmt/.vscodeignore @@ -0,0 +1,17 @@ +.gitignore +jsconfig.json +tsconfig.json +vsc-extension-quickstart.md +.eslintrc.json +.nrepl-port + +.vscode/** +.vscode-test/** +test/** +cljc/** +lib/** +test_js/** +js/** + +.shadow-cljs/** +atom-language-clojure/** diff --git a/src/calva-fmt/README.md b/src/calva-fmt/README.md new file mode 100644 index 0000000..984ad8f --- /dev/null +++ b/src/calva-fmt/README.md @@ -0,0 +1,89 @@ +# Calva Format + +A Clojure and ClojureScript formatter for Visual Studio Code. + +## Raison d´être + +1. To the extent possible, formatting should happen as you type. Code should very seldom be in a an unformatted state. +1. **Fewer dependencies, less headaches**. You should be able to edit a Clojure file, with full formatting help, without depending on a REPL running or anything else needed to be installed. +1. **Fewer conflicts, more predictability**. As VSCode gets to be a more serious editor for Clojurians there is a an editing war going on between the various plugins that help with editing Clojure code. Calva Formatter is aiming at being the major Clojure formatter, lifting this responsibility from the shoulders of extensions like Calva, Paredit and other Clojure related extensions. + +## Features + +* Formats according to the community [Clojure Style Guide](https://github.com/bbatsov/clojure-style-guide) (while giving you some options to tweak this style). +* Formats the code when new lines are entered, mostly keeping things formated as you type. +* Adds a command for formatting the enclosing form, default key binding is `tab`. +* Adds a command for aligning map items, and bindings in the current form, default key binding `ctrl+alt+l`. (This is a bit experimental and will not always produce the prettiest results. Also it is recursive.) You can also opt-in to have this behaviour be on for all formatting, via settings. +* Adds a command for infering parens/brackets from indents (using ParinferLib), default key binding `ctrl+alt+f ctrl+alt+p`. +* Adds a command for indenting and dedenting the current line (using ParinferLib), default key binding `ctrl+i` and `shift+ctrl+i`, respectively. +* Provides the formater for the VSCode *Format Selection* and *Format Document* commands as well as for *Format on Paste*. +* Is intended to be used alongside and by other Clojure extensions. + +### Demo GIF time + +Some examples of what it can be like to use Calva Formatter: + +### Format Current Form + +![Format Current Form](/src/calva-fmt/assets/format-current-form.gif) + +### Align Current Form + +![Align Current Form](/src/calva-fmt/assets/align-items.gif) + +### Parinfer + +![Infer parens](/src/calva-fmt/assets/parinfer.gif) + +## How to use + +Install it and edit away. It will keep the code formatted mostly as you type, in a somewhat ”relaxed” way, and will format it more strictly (collecting trailing brackets, for instance) when you hit `tab`. Search the settings for `calva-fmt` to see how you can tweak it. + + +## You might not need to install it + +*Calva Formatter* comes bundled with [Calva](https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva) + +## Written in ClojureScript + +Built with [Shadow CLJS](http://shadow-cljs.org/). + +## By the Calva team a.k.a. Better Than Tomorrow + +We are committed to make the Clojure experience in VS Code productive and pleasurable. + +* [Peter Strömberg](https://github.com/PEZ) +* [Matt Seddon](https://github.com/mseddon) +* You? + + +## Something is not working? + +File issues or send pull requests. You can also find us in the #editors and #calva channels of Clojurians Slack. + + +## Disable the Parinfer Extension + +Calva Formatter and the current Parinfer extension are not compatible. Some Parinfer functionality is is built in, though, in the form of explicit commands, see above feature list. + +## Calva Paredit recommended + +[Calva Paredit](https://marketplace.visualstudio.com/items?itemName=cospaia.paredit-revived) brings great structural editing support to VS Code. + +## How to contribute + +Calva Formater is written in TypeScript and ClojureScript. It is setup so that the formatting ”decisions” are made by a library written in ClojureScript and then TypeScript is used to integrate these decisions into VS Code. Division of labour. + +See [How to Contribute](https://github.com/BetterThanTomorrow/calva-fmt/wiki/How-to-Contribute) on the project wiki for instructions. + +## The Future of calva-fmt + +* Make it honor project settings. +* Offer more pretty printing options. + +## Happy Formatting ❤️ + + +PRs welcome, file an issue or chat us up in the [`#calva` channel](https://clojurians.slack.com/messages/calva/) of the Clojurians Slack. Tweeting [@pappapez](https://twitter.com/pappapez) works too. + +[![#calva in Clojurians Slack](https://img.shields.io/badge/clojurians-calva--dev-blue.svg?logo=slack)](https://clojurians.slack.com/messages/calva/) diff --git a/src/calva-fmt/assets/align-items.gif b/src/calva-fmt/assets/align-items.gif new file mode 100644 index 0000000000000000000000000000000000000000..ee111a222ca3d1b8a6371edf4740b37b4c1f7e23 GIT binary patch literal 100435 zcmXtf)zGU-FCx7oY67832~CQC^sa=CfEoxOpdwAB zDpf^L0UNeI*L!E~Z)WGroY{}(?Cja+SyOWp4NWfwNC7Ag0EAD^*cdz58yXOdmE;v5 zAmCr>ClnC;7iIq~;D5KJ3;sh0%O}PsgvDZo1hM}YME@ZnDJm-}E-fo9E+!==B_kyv zDJgkYPEkflMnX|rT2WO_RZCt&S4l=vNm@xmPF+n&R#`<^MN3IRQ%_G(M_XG-OH1pl zDo#?(P)fsCTE|jR2d`zIA!q0)Z|tgqGf^|N&@i#qG&`?geo@=fUf0G&kKm?g?Wc4; zP}%mfs&kZ@ON^RZyoSeB0~L8QO=Vj>bwd@rnYyW^riHEUd3!xO98S&1NDFVSZ(*V5 zY^ddBu4niUOT3K@&hj7jW;pu`jyQW41Lq4y9`^P|cJ9_LE~c(-<_-=H_U`s>Zf8NdZ@H zMqMNonvy!rl6%b41}!p%J(G*Q(#m`?EB$k711JrFl*Ztqwvf_$q2-<7mA#R72BK?+ zEwe|hC=)jM(}d#Zgj;jAWiRZ?mtq@f39aKv_ol9OKD*I9OT1{4e33x*JfG=fUmWaG z5$;hFb+I+xr}J7ssz>ro&-5IxEQ(KVp&z9@u(7kFa zBWEZvDJdzbS*e+snOT*j!otGaw{I7{I^CVd4DO zTQ5&amVTG5{zoGYjit~==|vNdOD85vpFF)iGg~z~cjwho{n*&plP6E+=jZ=50pb(@ zA^6Rx)gvhgHffJZY7IRdBWh7$)>%7#lUw=n+GJ6?ZtW9+{J->rXVHB|FX`ZVyE0n%4-P5wrM5wc9d40OK z?Nx^-Jxjo{uYI{YaG~|WlfJvF{o&iwm6rYY)`#N1d{}?d-|^-V5pqV*YJj>)&tQ|e z_;jFi`!Pk-vdZdy*Y1;2<>1#(?{~j@UTt_&(0Z_EZ=u<~?c%e+-u+ippC?tiD|FC` zhAu|90Q4?Yq(9=!B}=h#ND619!Z20LG`w*xm;WpFf_O~st4oPz-dUq)pVmp}4X>0rr{rT@Hkk;CF$Z*Z98D8pj(_ z2BrCA9=~vYqxGpBqW&I-N;L_aSPW8qj;v3S?Wi89!dX2%W`u(UHi+`R8Z;u#Kb$xr zi=?(ODpgvB^<0Hxwo#7pP8gAfVbd2>YjoT;ld3@AZS-${a zBWN1^B^zf%w3linjPk(r`!~5#`Tu@>OB2|LFEo9C@S9~8Fr7WR1Gk52QXWx~Ax*?=+pjOWfruM@f(`SVB3 zjr8sLK{hc`aq+lJfGACv`gOdj49L)tGdFJA=!D*X2cG@dKeuK=)mOQsF`ND<_JSk3 zEpxPqob0`3OJ?xYyeM#A!2lz_0G>Uw63-wxtMWQY6ILPk^;DUlzR9m}4&d|Vv0fB8 z6TMUJ0#RU^t@))4+j(xKOb}crCn+`ovQ?Ok7+Z{I3#fLGt-(NXu-e=SxD*WWlw)0bb& z(P7^)gKs>(hu+9bN=Om1y?G&Xa3qiMI3-|nBRv9Pcb`N(6UP>ejmIL0qAnDnQ3wu% zJbnbAXQQpUpTmF9aqt2v`4OKPlWF`tp3%%?&LSK3)6sl!^zt-GGnyxicGM)Gr*H*5 zDP(Xfo&HWEpFD@Xc%vCkZ0yD8oLjqe$}q1BBArhPG@4GZ_{!)~&kTGaB*Wgui;^b8 zumjb1{&H>sS|ghM)c0NZ&9mKEw<|P>pJFZZpnVS3>uE_s=P7DE}O_|&}%m)YUMmQ`B5p3qIjG)BNB74j>3D|UL=y}0p zQOi`q5rXQw%g&up5#SPe(`uVtipQLy_g_Syi3;M?{G@d~KUNEhG(5*mAiA_ma&}4r zKoOh%FI&sJ9}DXtN*m03UqhVHrG=7bB2S6e<7XneSBph?3P>M}L{sT}8T==TQ$e&K zUeJX}J;N8wNw@ZjzPkn*3h~a7SfRE3EX^j&8?)qrcJ=3z&(Fn)rz^Z%fVe?gP{ zZ4kwjbZ~@yb6~1*k+UMz9}~0iK3)HeQqC0-ri|AbXqJ=Byb!@1#O9=@2K1MH@A0;#?f-cu}yqiZ0XTO*)+Dg4bL-IT`tVW+)lge=^-lPGFo1AgNHp({h`eFwlP zVJD;9*Uj(nMgm$m6=qI}sWfG)E!Nv7W0E$VS5+x#ZfW;%06 zl6}&|o{OSSp1GoDx7+^2KQ?=5o-fdA03_j{A_STV1zFXqL5$)znKg02WL8^w-8V-^ zg<>PC(0e1y6+{C=5d|P^Bj!}XcE_v&?(Lbr28!Wr!Aq`G@J-H*P`uI6R=1Kca$H6% zmoFl~xFbOQosvZ9qFH^!9)|hpjI`fQQw#LjUI}IpE1Jw-d*(RV;Oiy?tY{#Fn@iUv z;)KJlmzI82@fK-swXsetXvVx~vDTNiYWyWJu~v`K(^NJ<1FWk5i5`sbVoGQdJv5XO zQQFEAd)ADMbHN};e}q}tmhck)VJT*%LvzSw)t1Buk`67G&nKl8=YLgE#3J|9`llUL zpsfU*xLg-Junb^~vcUQY+>(3=DccvAAvb9KfP1fd za?RPK4ds8E@LM>QEO`nw%?UNye8gqwXUR1Xk+__`E8l^hcC@(=N;(HSo*cuqQqiPv z691}1y10>7ra7XX(VYT^A#6;NIGFrIg1P7a`=;1nyb#23b7hg1`XI;eF4hw=uwt7m zE}|_9nX()*Fww=Bl;W`0DI^?cPN+cQh9&o-+as{8BLh-sxW<*&AyxeY>%IF0WA>AK zML!O>r?`%x5jy0x|7@@CAF{}CC(ePwzmhV%1>n_SE{0m5#s0@2oUQv=$%y<*Lx+a*vCajGk9M>X>u0}b8nf48%9rS`d7ho$E;*dz`l8<8+%h`- z#U@=OY3ua6uu2e{8zJ6<{l4M&If^-#;3KnZ3{Q>2qU+BXu_r5-?Kh!MmmtbywP^XC zotPJNsY_zG$HFE^yv9Sgq?d9MW{KsF2_{%24gm+MJi*LV5<=nHwGs@8ONIFTI#|(` zSYS17l#FaJ?EC7H6(zqO$+v(}`&44dYJ!QQ5|IYc-vSbMoG`!pF_R9M@g1vvXX2c_ z#fu$Nzk(pxrY8$`>e3PB4OB0c00A&S{5yCECm5m#R1v_yED4)&t(_Ge8N)aERe8WM znW#nzfmdgEfp`rbVyTj9ES%~7_@ts0&IW}i2ytt`5g|_o_c1q zze}XvvQiSaL&X3gllGH4_MFD?hK>S%qa7AuHg+yqT#BSeXEPvP;HqlmH7tgifT*ZO z-kgn7Iz-;~L{Rbb-S;Y}0s_w-N$vl{BlVZ$IC8sfEXT~UaUSIZ~ z*=%Aw<~h+1+zC3|h*rd-4+!Wy&l@EG^Ji={xCS_&p^s@m7M53kHG(iI3zL`d+#RyLbZA4Yj3m**(R0Fa!uoS@ueTe$hiy0sBkGCsH7M@ zE8%AsY;EK+6RYqgww#RsgBqYOVi4PXxyck{88s2if{?`4rcHv&aLCN+$~8?4D+amE z$^V8kRMg_EWkGP-me`1JwM#)5dOCF61o~J_Wx7!U{!47igy~72g#97v*pvy$D&cb+ zBw&Hjln&L`02eBRCUGLNkw9iR7$Crku*}66q@)67%LEx%j*L|3Q)0N6N{ z`7suyuYl3_L_P6@_aEIh@I*}#s}OUb(G3P}0_urE1;Zi369S6*0rCioF~GptL{X~* z=21^LgajI;q9SHXK5*6unAE&KzS8$LkM5L!bPMY_E~BO62956azpRTpZf~%w0U)*^ z=`~}wFcba_7K{x~8}B_=Gn#Y4L{?uj7Y;}Lo|`kQD6|qS)-CG*aX(_o*JiLeVtl&s$S(KX(aeWv z5(T^K0Xv&#x zr8B@q8rqSDE;)qn0st$(e1itQQQa?ZMpbjgoM2IpsLTW^69E9MsSMUs)EX8eP|fgr zgMkn(tVd*yn1zmRK<*4OPs~D>s3_zn5J6*F^@RUcsCYzWvc|v{ez(w&Kq!@o4FY>| zN@OBnpo*hRu^8wQ2Gwe*;ba=gdlk+fobq$Cvzoih`<AXnJ~)kB&_;PermKe5=S34HvN z*6|Lec#8_rtGR6a>1VZ)?6nF@v?gB?%VlrU-|S*71$Q7 zd=p7=!I+?cI_j8W?U+0Nq;#KhZ^IbO4go}zem8SD5}0dyKs$H<9;t%XFw7i4es9DP z)9E$MIyY*Q_4w&4+q$g>1}Tr}H`pFOT^Q}@e-{5I{v%hC^G&UgD+$H#uJ2n3)84U< zJ6~KVe!kZIoXzLj2^;?(59^=1fP{*mJ#{Ad$^@$eMkZPlfSNoe*)Dw*$2l0hn467{ zw9XY(T;orIpWsik%QCaa7;(s5%1hTI7@VnA_kYibvz${}B36a!!5m+KZm+Vn&Z&M?Q`yFtApeM|Cbh43M|5nEIy$ zvJY|#G!%~&&}z@nl<{?+FbFzE?&gei=G`G5q1H=ZsLnJaRbO@MZa9ELl*3LJs@ zn6NzZ=jpI4<`I#3nZlHJO?W=lfj38m|CzkdGdxQU(aezPPhCoS<+VUKO*}ro_nPs$ zf>mPjV$0CtL4PvYbPb*H{8tO+BmsAMO9`jE*x`$jI4vbFLJeQo(=T~jj)2k?l{za6 z*XDJD7L3d=EH5bvj3NF=nY;aFPhZ%h07o4R3CGBa*~r+>$j`>S2_X*_8LnAZj(;^T zC>rz;QGJa~@rv4D`=s$srn>Os|id3ABXdKt(W=;0EeD1u)H6fO5$^g z$Q*746yAKx>4(~2)X>29yZ5V5)7Pyrk_5(59HwD8zeECa5A|gHfB{W>3e19PB3y~4Jz^6jlsD7E zKczt<&f|&)n;jf#5y?|KO!1O}9b;)JTj@?;?0@!PNCfmXu9sO_X*-mc30KDir(p-x zz|Ex$*+Vpf7U24)kx>Dxw}g3f&5qS3L09|(IZ9n1rnl){x(vKG^{efxs!b*37x5m1 zGBN?qvM+?#g5*O#(+ctw7{LaND-+-{ZN)P8h!Rg^KT8?X0DVCM^m(JEHy7xyI&hbZ z2JXDP%CyrZ1@hxX+UZ~z&N$UK?_{m-*j;Q5rZRUi4FR(N*bDwGoL`^X>Js`EKDP_) zWb)Blxw8)KO9G}m-^Nmz7)}+!7d={;C{Ngbx;4oQ1ZQNn>1{&Z?RA7o+?l)=*lc;( zY(nqO$4=ACNXhl~PuVX%X{^e7;BN2{T~Zh2_B=lOute+x+ZmiIbpKVV@kK*0sMm?{ zg-f&H+*Q2CdM+{D> zE85_Tj81-@iG$X_!#7WMI}y2;1_CxP{?z4jj0T$OKmW}&Shq*ve`^JBWrwt$>*|^8 zy_xoBf#$!>!VXjO;v=Z_lcO(h0bEfyt^>R=^*q3yyfG|ZX+jX!2#=dj{`iya?h4tq zYDC;)>CkH|^UPJkQ;klaF$zb@Rf#e=douc|bZ85*>E-9S}Ze%yhP6-J#q2RfkZuLp30uyo78MlIY(h z#*nFgT$~+i)a1R*Ci3r07n`cL^@_Lkh@jnw*XfpLRXl~{85VviWA&52pJ7%VaOpN1 zS=ib~X+n|Ejr(KVS6Vgxp!vhj!tZBE;E{&xN1YQEVi7&4s{1}hobJ&_#@s$d`P41{ zo+FciL3$?@)tH~sEtR$!VJ31@rJ$SFD;qo}n%;9okxeu>(n7ker)ylNaWL}9DAb%} zsX%9sditkJ?8{Sfm6(yQ)o-Z~!Ult2(G#0Xtsd_)yqY3yks$U7G zMr@7_(`-C3i{pVJ#VX~-GQD5ztdw6wFK9r-C+z_othhT6s_??WLenwE(aAmLo1?Yy z-vz2Ihs-wzkX>qw^Tn&*zBzk_6!qHTe1;tXDax#>3m+2GX{cH07wdMp?M2j*X^`qm zhs(od-mU}a>e{S>t$L9M+#F{3PR7u{4Tb?SL0njPg76e@{&QQABVK!OlLAInwFOR0Ie9L>@0^<|m z#u@(QA{)EpF*9RDVYmDFL`O0%5an17N>Nsbwh8pVg9O*{I6CdcjWM!HPyn#H2`2AB@-Opo$pEl~ zWj!ZG5i49`I22*CFu}ej>$_Bs6)f|{YjK|+G@B|NjfRYZ4QhVM zAnb4yE2YiR+BYdeg2dvOck2oXVT8sOrve7rTL(K=nn<-Hk2j76JNrJ(!GQ;&j5_9` zFY_Tl9E}4c>7+D};s%c6lV}EJMIBmn$RvJjk@}FaG)wVM4tB5G>=fo9oW?jDcCDqE zhH;qd|F32129yWFKy=aoG-i3u^pL!yH}r#g4^t8MJsFtjdJLT6*S`c;OF!k(6qB%Z z;cR&Z!fkV(ef=)es%Xoy`91qW^A6fJgTc4fjAADa@Y|huNbHcnWM6NABP?9Aw3et} zl)_GmH@(7dkShk%=5Aa~7k9&CS+O@TmSK!JTs%wVis&$paMLTTvyb%|Sb==*^fO># z=DA%n0k`+bIuD}(9HHjSA#NGO;5uoT%q&3PBO*muMKppV z&;;yWW8iKf6aI{lCi82;%8}|#nsks*u0#@@Bz+M0a1$n`wyxQ+0{`!Z9oG5GvvCYi=p-?@%Y|{S>4_t4GBH$^w5SE$~|NwM8nzXa71_7)aZZ ztsowrBq^Yllz!bx6m%zQMAS;OpG?IGUI2*V6(_kdplWt{8(15$1&hQ&L(`?w-zyx! z4>dUef*hlc9r6Kx0ts7ZMz&Y7bxO7>##)ObxN0x66bm+TV2sQmVy#(DVrX%V0df4> zGK>%yfIq_m+;6{GqAH%>`VqwtXcf_YN;bm?C)x`G;GqGg`IxS9afD1!1R#u(qo%~% z@~tzc(D!;3H22M(9xnGUT>M@W-r08XsC6ahty4eihd_q3ZQ$kC@BJgX&%$j#@N7?{ zO3rv$t6+)H>y(hj2p?C%Hi#EFNsl$Igr2kxrS^kycY$z__R}Z11a@P=ZSHZ&B@ps6 zD|8s(Ch2r-_Z?TMKLBQJZI>GXi^+U}Jc{%x2E6!F-pmZ+$ncld&OO-y8%STns)IIc zigr3gGn8fZ!YE7JRe3e6m6E>j$Il=d+Z8{DmoF&>>%rN^I4WR(%t0!K2?9WdD`X68 z&JY(Rd^w(Cm6SJB8T{K%mv}z!D*`tATgz&&QB)=MSL{_kS$A1?xBuL{hdXQOYty_@ zC;T47EDYzcs4zl9)IiC=6Pg}343EcE3VBunG&*BbAu(nPrIkP+Vnge?D@PXIsNalZ zAL@jX7A|k}URNKk$iH&I+=E>~gRcLU?QPBtwGI!pK6-{@dq&u-3t1=jHZmv;BYpVc z$1Znw@oQNN7my)m@~=g!pyRxhYzhpjy<|1D(RQiQRp__XOWy3~MREl}*Zf>p=BxNi zq)NCSl%^tXE5G2@=gNqzmU(F47=<@8@I(%8Q*KC@VJ8%c=SFu~O<=T=3Eel4cAt1D zte@8;3lga3^E)jKm|$L}ll{#SxM-W_j0H0moX^d~yqey9J8_zL`I zA^6US$J-m-poMx=M5cYxiv9D*-d7Ha$2enY^3>8qv@tRs@r(9C9C2nzI4@=Z5Ehku zthIKen`T+f|0&*7;QtP0n8K4LrC+H#{wgUvYveK|tUJgl z`ymIf{wa+T64o@0vdTeQ!S!-Bbtt0?g{X z(>)(^xc~A)$mp*~&TLGlK??Lq7Mc`TSPGa`|4m)C2^_O7{gm`rYFW|x&%1NND9^ML z4d7N(R`Zd}`61?ul8lJl%rI0jo zYMxo7oMfnxO%Jl96Cw;G9I}P-4RzNH^&IfpUkg=->0^_0Jy|0|oiWcEL*p?+qbo+H zNk%rVV>qbM52BIfA{!Z**L2^=sAx=XXwYDXDMGkJQoL?6~b6Pn3pI8ne1 zY=KBMD+Q8WDqkj4uHvRXOJ%cT2XGm=ETv?tlazH&!FMDRr2& zOA}6}ma%HpAe62C)yT%Zqw)677Nf1nlk_DhpuA!Kwoi#O3Bx`aq^)=_^$!@0}?_!V}A%AF!_XhGOLJ~tCei#(~txdh(dL$MF^ZvSDvq1 zqyOL?r;MfqEr8w8c2t3=h`3u#Zwhqdjc<|gSJE56%{m1Vl4{!)R%X#moudQe_K;qe z@&=-gwf@)U60Lg!wNmFk&fnUyD{Pf2V6_KMSWcC!Gpc?`b;%6XNVnEmv^Gq)HWetF z$)H9d#I~-5yIHM}!&Za3nOa_0%_2JKN)|f>{uCo!TdRIFZ9iHKHHVQSuo+@Rcs!c} z5Y8tT-^M1XUV_gaAJid&0msq?e;SFYtHf@+ouq=AmacGqQ3k;d`if?|e=Iqx%8LrW zYhTJ0sfABIX}hDyJO82mIiuA}R?{&B;BO1nsd%2SPqF7KcLaI@Cju zvwplK!nB2#kJ6QYWARGy!YeW$A7gz_MeY?{qq}QJesxwpnM(fTJ#aLA?u$hS#HHhV zV#n2+t-l7(6@gs7iew-Mpz&Ck3@+pJD*I5c2;7bvo{)j&q`e}ay!vw41(1YyT4byX zXOOLz3}Aq1VET6vSOUNt?N^E&zT*$%WOm=ZE#(+yp<2{-TZG=o?yk66_p)le6Q*M( z5S3Q{jYrJB>&MXm{T9)8{Fg;iTMm`XgeMWkDu%%r!IX?yyqd6f4*XGdDSkulACCN*(Y~Ra;YH zx)gaU@P(5@Orza;Z;!6&$7^=092Jdxw20`e`>OOo@ki$E&qe|Dh}G4Hs_N19{VB$X zxRic$nR+9&U;jz}wSDzbGw`d)dk%~aMV+}!f!%T$%U%0xLX^}zRl|2 zFKQRKs#$P!nnupHMx`Zx)qZ=$7ccBWNn`gsSO*ARtG+GV)OUd}0(~R?;(p(S%~}G( znX3;O3=xs|`(MRhoql(u4~2{nDi*1A_o}sqdfbQlt`3=1| zk=2EnQZnsox>>0OZ~}?dT|v14$Y<#PyO zxW@C=+lKdjn_pyR^TQBz+WmM^Jn#3y3uGYrU^e%? zSt+US(eDBMQ?1~vHG(f+5P&i=pf9Z>P$j=N449!Ka~c4X*xndo@Ap97;L~;pck}o> zZe}BJ>`;7`vUL@##?~9KXtk}i6GYexsPw-T?+vT9_WIRp>n2H*AH zzjo2ovnY6YCiubY;D?`rAEmJj{)ca8$a*Ayg|0`ZIr;QYtkOfTJhmAb`?NPw_3H7x zD^FhEooqC$M&E*DB!3?`usAx9g?%*oV>oj0pzUpds27O1P#9VU7PSR9JwaEsi_FZ# z8~DArrp8Y@1dCvvuhXt_y(kBVEiul2H@u@3GX8L1q$Qa^4V2NG7~6o$NwKmIK<#Fw z4S9%!QWrV~)}Ea2B-5-j_dbf@(rfO|P>0NIlTTB*2hV2Muhr*2zs|J&HU|kXOV5Vd zfIf38Dsfd7&gq1)fWpt*OzsT~o1D6`+jwPQV3I5;XwmFZ;2=1g34Pj}T#y4ro0jM! zrniuvo3f`{imJ*`pN*=MEdxoW)#Ue-aK`ghN1Emv=2P_d{6)m@o2BMN6+oIQm!mIA0tZ3U`kXzOwz)mlYFNc?ygxpDqf7Ug3a z>(^2B8TGtBQOt=KW}Xb)ADbC1jXUy?8H)lXnJya?EH(n4LCs`a&#b=(=fPKFPUrH! z9R}9cQaJr-)kmCQPU~{ueD9@5Y~!qL%HUb;0G`AN@p=kHhl=~OipYsNW90pR(&=@d$MIE{MS9LdidyGi>U6vm^lFI2yp$X3BL-8s$#|_-A>>uLvb32R)jWNj!3VrQ~!fsP*DXrQ_nD z%D~|O;2`uSo$8>GI!k0M-}rf#MLzZ}z3f?bRE+ap-m7<8mF|f|=kN;Oi$@qga?6JVXB@2B`K$ z0z{T$F*AxfxTe7zC>Sn0?S{?aU?Q?)Bfl$oHq|1w2D38O((V78d`uJ}8<~sR6M{ioWvY)5qWHha02m zZ0AD%XdLfMmnes!U!{b)-RdlwKOzv1M^A?b4#pLm>!;l8_8jS7z>Z1Xo1yCq6BmR%l{ zYU=ZPwc0eC`XMNKR58#UyQ@<@Cw^<77;MjQE1iHX8xR!%4(>OxBnjFUZHd-li{4!S zJA%Mk-+jotAKz`KIRn5qF7s*F4i|rDMOfpfknAYpYzF|niIL*BI7GWJbt)tm0MtV? z_zgH5Z+u^GY_(GQ=+g*ahf3+ZQHr})sJj;9+>gSb_qR=^>HEmjaJ2w<(RAQF{n&jF{Sc|r2wEzB`U}gy;$*D2c zQo3!lWkHl5uYcqcX0$%}k}0Cw6v%V&5&`VAw!H9Is`iJTFUDa%(KAOHg&BBzj?dZM zXl<&X!j$%hVYbZSzRi2upC3Y)Wq@Sr7qrg7*kkt&IPOI5plczQz<9j2zUv`;J~lH7#om;0+b(P|Ae@r-b7_YC8?t$~{32AepzXvdV>xP|pY^7vc+*g@>~hKt@;) zh{~u2N=N(|;AK#59$|{a1Ya1MfnO`^?XQ?tgC^Vr>%_KT#m}aWAc;_$_k7_x5N#y~ zfCE#1OJZc)nAZ#BCtA>tYh!}4BF^-TilyL+NW)ir7#|d5hK~b z3IBw?C?ccPO@M=Vsa)hepUMNj>`AXWlQatM4Mv!ku#Xk|()?KWm_=Ch>!gq>tC@Zp zRbBi*6s~-jt^L&m^!db)wFj4LggMJn*NkUnoy)xvV-YfQRNv@<%{o$i$Iwm?5-IK` zjxSEAN5`8moZ3k}ah>nFHO?ajgtt7wAHSvat!*PkyfV$212o_#5OGNsM5_@KEV;Qz zS-Te|FBYL`2tasmNL4saTit&BM?$0bzU96AZmmYfvsw!+GcjG=m7kK%3O){+iA+|| zct}30_oe0ewd3wP|Ls)k>}x(J{r9%&R7Voe);gQ2v{(t^5I$$__dLz6r5fNWv~Au3p8%#NKS6@Af^m^Kh5U3nA8X@XyDZ)U_dz85N&^Gy2B~6zGwHNdRCJN z_uk7##cwFRdiKeEVE+P^6gRXkE*9I{#&T5KKfJGd4(KU--9NCwB4Y8@9XO(j(YD=p zs?9Bb*~3^9MqIyqtJ7aRQ-9s%4Dj01mt7tKXsxmEN8=Fsjf8ckr~&$i;ZTOy?puAQ z)#_tmUQgngJ-1kz;>URnA~KEtc(Xp{GLwI6tqHWVq@4H)-`i!~Sp4VX7>H+Nz1X2& zubdMIU>P@Y8T`^utHT4A=uQunZvBjReCA)=?9OB(WzxjL2%_nofVzk*k8f{neE?>S z5voc1zLJ(a!VWV^RbSczm}4~?uJr{OTur&lde_HMGTd@t;@h^jYedL{2tc1Zf%ra= z-p}(Me(@du8!gDj%+>!?nhoz($l1p>U;j_ZL*g+nwM@>iVtbW3yY^oA3xoQf_wd4~ zDUa(rAmQ>{B|J@WSNi7Q(u7{D{i&=2Nl9D#VZLy%#uVLR?quA~NUg8??ngWI)zc^I zdNI}ga-Sj!hw|!Y8tm_9e~9cJTL1KE@%}c`i@1fM*WU$W2Jhc}5w_ac@Z&#q8c06O z@9foekiN#~o^H53WMvt7Lu2g8gYfI;RySE5X-wqk9$kxCqd3s-DgH(rXBQ6dl&lQ& zYrlT7P4IGLo~#K+Q{diL>yb(mDD|X6&f5?g$eLCSsb#rP$oAmc8i67QVi>+Fk)Be?1>C_}1@x zZJ)OS-|_g~j|X+PH(dPU$8~2MJHSDD7XuSn)+KM<$0@s_?Cc& zCecsa-++MuTa0`}&P|V9rC0aEzGuw(b(|L5;5s-k{y}=+oOPcI|L4+aDp!&~b;5hIQpd!NrxJC1ErGW$XF*eXP!OJQk!>;ML-YnbR8S9VggHpbfK|iAT*?)= z@j5Pr=G>_;_eH-YcOAo&k3!6N{tS{}HfST~8-LEJxJU2-ciE0LmRzF1Zlu7!r`_Ew7y9V- z`Y&%&0xxCfj;f&Vke8^Gw1CG6Uk?bmEo@}8V#2^J=D)V4wC4SuB!~|e3v+NcwcGaC zB|Q?ZH^1y5DP3<`RNvrBL$9PkGJN5hz_ZGFYwz0@t$gG8v)141&x_dEeBfJA-8s+M zU>iyxWNq2p+p$$^u(zD)~=+IxSSM?EohoyQcS25Hhk3J!SI&wutCj#TQIavRxz{j zeU`c|T_rpVGEnVl)9CE)ccD+*W7OFB0n8=yw0N9QFqAzbWHbuBGU5uNF&HItnGwZ& zM~g}&`mtp9kz(=57a$|BX0xGKMp;ORbB z!hOpf!->a?c+DB8_!8?qO5sKj0f(J^10GeVQ%s{^f*?^K)lg+eAk4EV*3NJ-=4{f% z3D3Wr(N+d3C-efQ%6iIKzyAV0`Q7@641BX!^_eG-`0`@TU%|jkr?Pw})>5ash>>K*J2^6Tlb-B~2U2z2w zNYaTm;PAD~$ZgivVF`8p_kC()PxVxk3IbS&bd_j2pB7Dkn4-Qj5+1@E<`@(q;2a4u zXD1k{8-czpPZX%_6@E9&pC?GfmiNZfqNWVkGhB);NF9KhJ*2CFzLHaMY{Kpya4c0N zQqAVS0aqwfF&!a0A#UNnn5x$PmSQ;*$tifSr2`nl34OxeiRcPO`iq zxAH~ea0KW=3WmxnA$cp8f@xxJN3bMn~0G* zHQ??qh}^G~qC|-Pq$|IK6rk}oU`&K}R1AEcNfCga%6y02XB>CGtkI$gS+QAC)D!STx$ZT3e>1xnExXi0Ny0d)V&2 z(jXOSSHxZJJ^<)oX&{f}ZcNC`!_+PS_=+g&hJt(6Ub_RIjFOrM5N7}iSiBRE@iy80 zG`WFi4qH1l&^TaV85n1=+5!>i0_Jirz_|AhVV>4a9gbJ_c-wk1b{6p=#d`EkEtihe z&=dgxm9_LiT58Rv&2O~b&dsAWM%16WqGdJ-qCm8Z=%P<8*0Y(H&47q0fA#1Bb7A_D zB?ZSR`eevAKb(4CyL$ND{d*<FdZ?Y#4#4B^fMqhswF&}N--TibjK3^tQ>l*S7b@0FEPN)c-b(CzRVq` zsfauUuh{hNdYzNoOhQVVt#jBZhR+xj%8i64-V_#yVWbArTr8mMi0 zNk{p;&W&3E)@P3X&Zhdm&u)Bqx!38@chlTYfqA*<83lo1JB^&tBpn1ZmSDi;aBt|K zJ6n)uXc~PZ9;A(05)XgBM5DX^1(g^H6q?91eowB=7M+$~xi-PS8Xpbst8yc<>E4|n7}9Xl>V zk_Dg-)0?y|H`|OgGxTHuvaL?iGo#}-gvTU_|4bHO_c-agmyz!F1zaBZok1EhnvB0f z@@1*MyVcsGK}E%eP+KI%}>S*fyMDjz%T9?zdC4M3;cXVjJ&1o&L{$eVafKTr-3|XUIG2?gg!qA;2!{}eoawd6bn#qka(lW81=!maCz=w-@KAsJg$Cr>wB8)lw{IP z@jq(Ycs$8eI(_lmg|q#p)i^0nysU(pzk|rQw+JkcT|p_w-&E#ZWBU`Xr45 z_#gJ(E2^pX|Mp$!A%u`Xf^C%gU(gg$zy;(pJ zQ2_&@0-_>{qN41)@9)3&{_iu!8Dn3atCNddWvpb(^^B1>pk)$|zI^8zlsu8Frhx4*%!=x@KXJzujk+I^*J}vh*Drk1WAk#qU*OJS#?h4xw&p$7U$>6_?z{7y%j(DOxMlN*wgDQY zbM%xviE514gI#4J7sY@R1UGz$3_kDj1M zSdN5ik}!G(`X{LR%)hO~HZ9O4Xe+CB+ne6>4h?~d_s};lZ6Q%=sJ@dcJ7|@1%q1wM zIvX`$7|Tz($4}2s5$ipb6VPlDJnC8Od#G-5tI*^T5B64OKA8WuK(>??%H;jaDL_aYqh@vPuE(c530V_w0$!M0+Ou-o5khvkmjV0J8cj z&|u|02NED5MzlL3eo1Z_U0~Dgh*EBzy&R@{zawrXCa&r%jblUizTxpxL_>2ZI6t1) zN|owd_|i)yj_MZ=JZe6W(lZB4I;bY~hZ||#Qm!|v)ad+iE)>hYjDl8iSHmV+-sX@R%q^i;E%6K$_7&Ze@ zVm}eU_s8_x`%1SSDWEDrRtmvSh5V)at^e5e)Qp|uSBzF?^ROz@j{N@ZC#N^1Qa_bT zEeh{rTWemV9{LM^(Z12H@uVS2`B3U)h4aQh~P7H{XhnX zD{w>3#=?dIPLInmP(rKv(?MyvZ2I43kG4aaR~0xmHFix-e3_TCE-_M4rlO5eMjyr% zmA;H$xkd-G>0pPT1ea{`LZHf>Ze$0C+1)ZJS~56Ef%d-4g19y;5c=|G6;WozNs=>c zU5m;eHXUi8#bV%oq3ked!K1P?bkXL$*7kJJdx74F;<}r$$IqEyVm+&>E2Mt0B=g1^ zX#xD=H{0am{rBe@>O_Zgq)QLJHSG?-AL?*ERDI@>_fTDYY3t$bwQr5XWmx1zfCWz7 zpeC-#NIfpY@34x%qT{6imQSi%)YScE)0#Nm}&w=vH#Z*8|>SVRqx;*?V_UnSjHC?V3o&|}4!{=8+ zzDu6HQFi_1#g+lCSI!wQXKpNq)$Ue2iFjLfWF_)r zOXtd!&1cV7qJOs7XC7XK2|Bt3R2}1Xk0ZDit|ke@a<8R`mlv+3$q#bB$xvG1c*fWbhI?Isa7DTXa1=(KFLoQ`d0!g3QuMw&<}c4iMdGI-PYd(C z@9H&|Pe0@Xm4<2YhS!!CZ{5mEyz#2K;~haN(D*An?wBg|Jyvq;yd0v4jOXL9Tl8%v3jNB9GN8MYtGB5?N}?3>FK39t%cb3f`+;dO$9?ZU0WW=$N^@gyr=I z#RAqGcN=bgGnHhYb+z)mo)}W|l+6APgND!=Ed_$zvN3|A09KW zHGP^HXvel*&h;qrFK0!|r5A%pbxI_Usw)z<_{h_!1G?zzjVstNkao67cg0upJoD~P zJ4s2Dw$~MMSeywyub3dI%O8SSU*VYot}#Uwi{u z7iBGdC~aCPNN6+_zbJm~qC8wMw1fn6hozFB4&sDU>TH!K&vLT;#U~(*HHTGlf~h(d zG-2`vV9N`@ZiNt$7C|J=N1h4p24YC^{*Wz(&L(7U$Ze!~%8h3?zTbnSYBvelTxVPh zjDR$iIA64jnXZ6@Kw4mtaJ64Kn$=4*Q}hcsU$#d=3}uWt1BAkF2T4e|jR3HpAmwga zR&4{h>-Z+gKD7N+DL269FMlL3kri+wiVFF2vk74G_$H0IE%PidSU+B8d(1Hg`9YiX zHox++u_mmm#m|n@w*(IftnShFp&wuPmABU&uEy<6s_(9Jnd5ikb@h=8scv#O-29Y) znVY2Yi=E0w8^rU5b7OIJISiyoA?8gl1h<>b_`FQxb@h8Pg#LF!_;jC#_rXV&KT}wJ55ls{8uz9_YB0LdN~AeZ;=W zoxL9SWTKlWH;lH&+uE8n5JnP`e4UgB8OVb4FRYJAsc2Bj*+=!aBB}h*28f ztJaa@*n9B0dqbNr>2Y-m#tTsGWeH+vU6=HO`pQ)k4`l;>7F4#vzw1F~UpM`Hco{<_ z`a*aXHItqg&iDez_-zUiZm3m}6w?DjmG}(v7Pn>?8o&CI{n`Ri(dwC!F|e&m>zjX# zxj20~cH%cR4mWA__D*t{{!Wv@A4;N^z-{_w|#*;4k0Vk>{uiwz5Aj9`1VI&Y=RR+e}m8I1gPBW zt)L9y4?9EKJ*#ZN!<`Knk;7JyP#sPP`r*iqFkibnU*HZXxlM2h#e-yrg^Tb-vvV&1 ze8mjlyE|VyPDmoVE7(tH)wSi4l<*MaXkS$(kHq7%PztH=}5ahJ8UOR=l&16Ic!c zPo?uVDS!oXg*j~bdV`23tTFM}wjNesFh?j{1ea%R>uf6|Is5tL&J~>;t}AQE)&Pgj zqnv!}_qU~sgR67^110WiVt-|?@0v*pT}aA}3Rfwz=LxqCEuw~z+wLX247PVc z=+zleiXu`i7mZVb=&MLVRa}Xd{lgT+7*4hc2w#%_V}P#m)?9pjY@#7h{p?f4(A!5@ ztHwMpVF%_~QtTv|=YpkvS}_?aSHbd;9;iVM&iY-8TO2pj(sUSKb)GR=I@%^jU{7Ta^;A z#EC;IS+sVxWKG~5-iiT^r!7l>5HADpo?#2;0G1dZwcPl2D~Z{{9P(^i*bhe(*n#0$ zqdaBF=zI~m7LcQrxZKAdBEdoiw*3@N1H(=eM1q>-2Oz=H5JWXRcUip=iwV{fsI_l( zeA4ocmF>s$C-rjz&+|ojs!Z#*0XaX(irY$5a@Whw2R%4QT_;4&&@}yLUdz>K^)(Jl(#^8%d7Rv$N!`^rHq<%TAJ<) zckVO^<%^wARp6(CWKrKpY=P##XaUMqCN9r@d8nzjdz$wTo%lHTD;! zeFH5xQ??g#yaq1w^|%X(;2_ZNSmC&Hy5Ehyl3j2Q-H&p4Jht-btu!3VbMxZ@Ji>&| ztIsK1-$@GdK&T^s?VY{4h75a_e49O}=7~LL!tn{qvZN}T(Ft!1wOKXgZu3OS;nspA z<^hPEq2&*iBS74ic^cachWlU7*Fq`6q%QCiQ{rrzEp2 zBr(LxC+@P!{vhA4_YM~cF}%}-$qA7bK~6Sn+*b?E@wohDAuL`b^v;y``6JS*ux2P) zB(q|8KFp83#;@(YOo2G0bg#}NoDA$r#tQcoGk6h4PFK-?r!X+@+9P<@BksyX-aB$R zmVuSiz@$}jZ!<#1PL&V-xbosi?3?GI6t(EL&tpHIy0~)W>W>>&9gMI3K0<}`1pGZk zWnH9xz0o8ljw<{?v26KbKSQ>XbBkr7L(%~r}t4cm_O45H(dW*`PO=O*=K~W;Cot4Nf2Cvi_aJv#i zW$?N=Cf)3z9p2@091GhQ!zvGPbW=rcQ90HtIYfjVbEzD9!WT;dj)mvrRy z;CHD&iL6=~kyzI&S#QxbT-;b2h@3T$Jhh@c);RudKw(`E%vfc2WUy0;-h}D#{oMzs zi=Y%5_Z9#|MBKQ#n90sl_(S8s1Q|d1m|nMq`9%ith$J4uM>&j!GFS}-HwuMqiPPr#EOO z%qiRhte${xrV6+J%$TC#$H{_Ri4`EX5)j=1BO;6uPbF4C9;_xffKzH|+@ZY~tZXV3&pDv467 z0^{x+21Bge9<8kRh~0qT^bp4+Ss~ECIHE=p3lh-c6G*8HYFg!eKt^$Uq_8*ze{k-~ zN=v#qB-_lqpj!!+adpwd#w59$HIagPOUW3H6!$7Wb-D+%bybDI!?@g&ce*z{j(X!& z@6D3f%F})I+S0`(eRuf!mXF-Kt9`G!ITDKtJRoMNGGc`avJ5yc1Ae>qen15{R)mglQDt_6Va@3|V2FRk{W1^G-_uU} zukMS212nk@l{BO8+^Q^CQ)mX?Ippy`yO5E^AQ43(!BLnXRw^aqHW2CoLibB15D6ET z#!ISr66l0+hGcN`gz)fKj>veV9@4pz=P1z&#*cxC(a$O~>dxkuDQPF3+Zd=OW`qvvj5| z>phJJKP}OlHm!ad1BR5kgHR#_`yqh)3?ZL3hie*51fDy&NRAU9^DR&8xIFGnjazmZ zdEI?nxGJsd1?%DM9=TRgj<^dtqlFnCN?@6ixEcu1&4@S|1rZry6(eydyUSx3X&w7P z_1q!}B!a)&H0#;~{l;7;%DC*%tg z#+M}XS7$yStoBCJCm8VJgH;e?+JU^nqsQa#Mo=i1)cX78t1jXD!0W@Z*HhbY3z~?G ztGKQ3>XGA;A*I4ne^yLa*8lv0CjbPCRk;1?nz`<>MvNpX7A>VW&ALJ#Ie5T!Fg-+k z!#+6W;Eqg?f1@F|N+@3q^>}t#0B|i6rqc;e29~c@5d>X%0*SAm4{Ym$M^XnIIgVXF z;uL-Oz;F3UayECAKsPb(H5fEa#yXPln5jInla>@i;4hJdA=y4N1S5IM;~DJJG(33d zs#sC3eB`J56OWd+0i>Ib44ntl20Dm-V!1k5!2AVto&y1hqYyfQ{vnyp)C*bcPB)KU zK1C)hSu8(xeXjp^?Mpv#F=mC+b*5zXKD*mQ0F^*FYisYZqH*Hai4)J=&VFZ&`|c<7 z!=Z|hxw0B_mOwor`DpN+!_B=jLcdQ7p!r>SRAaZZ`k(xaem!Bc5gk-|N;{-5$E(4&RbVJH;w$B!gmfUuOP{x!c zEuV7QeQX9KG`U~hPSwa}BR-RkYc9$H5l2;%&Qmn>&rnOd{g$BhO}jBq@%v?(`HkmS zcVwS`W0HuN5@(eSThYaj9+`-Npz)|M*q({2KAo5&>a$mzqxb&xW65PkfQ`Gv{ zrwCG(bxsG&HR{-tM^e?~lt$ZduChc+*n7Xh zYeN<{TMRblv!0Lj*4JL0=8>$_Q47y2ivUy4T>0D*79U+(hbX9n_JU=tzOtm{)mHy` zj`k)rJh71Nwz(y1QN<2jsX?>0Q=ltV=hJQ zT<^Q`bpO7s+cSh-n_gBzKLzI_2GFua$b%ff4l`s6D1r+BiihcS019Lgerd4)tkk_4 z>OYxVf|~iUP?8GcmntVUo4Ms_#e0gRY(c|JSJ*>QWc)dxw+kNH<3V7=gO*+GZw zve>y#qp$50)QH0N2iPJ1rQpYw;mcvkY6bmPn=y9G$Za6!0I&|DpOpeC=t#hq_;~fS zDhqIATPlz)o_fZX2asM`SF|>@m!|2+kq%*2c*?cP2T5#8k@^OlMG~D=aPMXuS%;63 z-2z=To3%Dx{X0M1pq@8<`N1{I-yst6t?s8fMvUe^_Ffu&34Xn@{u@MB(YjdE{?r7MllXJ!BTJQ;VbL^Ve#C^PgmBtDej?{b#|beG z(f>@vE*|-JZ1%uhG%q4qYXz4I>S6P@RsxvAn5btY1D@N3mfI_)VtG~3vLmnO%7jwx;>fU>?fXPr;|!(bL1`U$<%kLIWuGOb+n|_MtJRV z0so26-1FZuq!A{DD!08M8#?k5LuQ7To!_C2aS1?@+&PZN4H>E{HQ>;tMJwk8!#6le zxQ%p{Y*@EyIVB#0^R1OV%VOT=kS*D}nMv{e3+SAgl zkb0DZG!VUISB?s8Fp$4xla)5l11hLB^}A(v6ZNdYEVSNy?3MkUv}es1Lz`T#**Ls@ z_N+B6wAoYsgZ+KpqaC+GTYUYRPP?Zr)I;}NE=4w-`JDE=UqbEnm1-2Lsv|)LcqD_E z9KpZiN@#a`iy$M1E%#6_Ce6ZHqW#P$Fk`~7(_2jQs2N1Cslcz5CN0n-2~cSn(LL_% z&3-MHx}2R*wy+MnIXMTNouX!BHMbki&ziS23uEHN*A&wdl$E|zIld5QRRk~#3^GsX zh;W|jnLkow$4CUCsJ-ckcU12dnJRFccO zhE3o1U)KVBETZ!`ZgNY@yfc;HJ<=2EO=GQk^ZehetWF&9ziJ)^B7~Jp-4I9Bz{MA$ z_a*Kx8gZ_*X0efn1ffAA>Z;&GFE-1=K9D8u9n|i{iyk=jc2P!_uje_w z-#s|>-79$#jB{)I^zmk6R%(<~$qS9hu+EZM(8b4ni@yh_@cXMD^P8gI#&pRz){AJG zyZQ7%$wO`uM0Wbc>#Wr#Ls}KV$s=1+51~$QMQH4Y68_RdV$dQSUjFEW?}Jm;n1((` z)#j^=fP>c#N!@w+t{>|SDuwF2dS5&)UwZrGiF7QV957kAiRi5EGmV%S6jVZ8YZk>8 z$0N?+0d_Mnu#9mE+`Bs{1id|i1iXijTa;$Ry>jP+liKbGEm6;#>&iGi*K9ueKt1c~ znRTWuGf^I6PZMN3^9~sJaJDJzv=%I$8goU0Ip1%#qm29HXnYKh0A%(RIn#Tm(%BIO z7x?pu0t_SF9ncquFGT1)U^JvezZSjyhQ;C0#Mk=FjF^p)HS5j(b`and?c(%}D5;D8 zPRml&7&AE7FjjTVIy^8||1KSiQYbL8m|P13FCKE9p7pWG0nO2)00vOu4pcnjz@&ia zUkM0+w&(6|rR4u>f+jF|^5Cwmauq;hU}O{tJgw1!4RNxQakAjJObJo;9VGpEoSJvM z#-hkk%~P6_@wyxF8&&Zqq!RR*ZMKv4!J!HAKZ4B*Pbxf5ut-TTluER7v>|UK*dPy;GloqQ=dIbkw6Wic_mA!*XXk%mmoS)?R;rzA>U$&X0AzIe5;CpB!+ zNrwhNq*AL*(uxXG3L?_#CR48Tq(v?srll>W^ceC+%_3^O)4Mg38y8dixZ{c!)BBO} zA>OWL9fYk2MLFUuVq7c$ zUt_M^F`wHDoSl=LA^_?r0K^gvu29n1cUV9h*(Vy~Hm^&NM8wvN@_ADH#WnJCPUgQ? z_t*2uw@A&mZp^na^|zclq}n_AAFnEKEXqF}$@!y)Q@NK@WQWBV4v5fMn4KXBKz=}P zTY2l_fG;kqGPU&8oEV`8}A|#|b%Z=p5tHF&o!isKO+e}OEO%LTM z(y)oBAw$x!0D^8Vf-Te`rK(-QI$BEO3tmgVU`MXROP{DIWOua zzsya+;tIS>RkK3{cT@$juR`i&1y@uRMyu-Ruan}ps(56o)uyWy)2bu9s!P4AbhWDG zqpD9#SA=$ncum+5f6^r1RULb2tG$)3hpM%Dd6WBV%GxN~aezx;*2`rTXip+wW*ylj z2nJX-o*p~FFiwL&sIgm}of32KoEK9;wfJRk+gBO(vU2lhs_gJ384s^B?w9U|(x6`; zlZr96!nQBF?GUqUdOwQiXW8F-5kg2dyvNwm%<2si>xKFng`>*adqm)Lw)gJ|SEmWI zX-@MWcn--!f@HDu{RDfp31s$iNheLtA z(6iVdtd>-?5r7^i;hUWW<^kU0Sj0COw8oQv+g9l(j7Y8D-`2k7z5SNJ#zRCjh2+eU z*utrJ14TAM2^ZcRVGx8c$VL#FB>9u-m?MXCShifPCLLVc-CtaDBm|2ITb=}>tX5K` zsma(O>qA85&SJ-IPsiu|j#cg~1MN;7?X2S^ouu?m6TVKye&?r#Ob^rZ=eZ68X}{`2 z!!AUAZ|>-ZYv7<>VdwipfwNra&vE(fwZa`qAp#Zbhka~4-S8!n@6T3YOvQyxE-%Y3 zvH$58b*%GpG5X)Fq|rU8I? zI?MJn@G1hpL#_f?6kdDWRApCj!;pq2+yEEWbU%|AcN0Gesf^! z%Ai!y;M(uO_1^=Z_;R&Dc{(`Xk#1+ld$F$1p-;IEgNC~~f13LOv-O|$F3p{E34;S+ zq}aMB%=C4%-QC`%MD%3H1=Aasb`ik-EXXp@Ym(w}&HTQ^l>unZ(6t{C+Fu~D+w@+z zAHdAuC=H3Dfn_WPLG%U`VT>KFKeHEq+~o{sutAl=ctDu4G4|v{zrtVW@q3(;CHD~G z?LZ*(Z!5mHppMed+1fZz>|9_KneTFJ+&r?t&T0H4OM!=6f#=fr`TlXIqH*8oaetSI zK>l%ib8KBW=fMZxzpP=R$@j6w*!$Umtj`1Il7h;U(LUen5v}a6Dgu-E&dQ>F@yYBN zM;b0s0&i^%5z&B%1y;;r;-uVYj833H>*3urzPqy95htiIKVeh3rEK{w(;B4+?Ht4b zgKc1&T{?GE2ggQP8U-rwP#h*XG}!XX=!Cf~g26ubf$db`+@C(|xK1+f07%W$p&3ZfrDe*g^(Ng*7Ik=BFqD1~`_K^yu$ zkcn}`WJmE1MIDXdT+W^`P-5%dyQuzf!Q2SD=v$-9W=sB9U8DO*|0CYD`PrIrEU!|` zv-v0U1BTRNi2_Ggw|ysZBKi#eEe z>VC<-YiaP555piSOrQ?|S-&w!kE_tOnNOClHI%{*fH9s~Hpw-%q=)Q(v6ynd;k}%J zDockGy0r}tS4l0a=Yv))zff&xZnj^jmzMFMkB@nl3EXZq3W9`SpdP)^k*dg-{wi9qBNJYtN34pVIKF282?s4?nOWAvw{ z2+W3K?K?lK4PXBaTIPnO+D5X~TmO}fRLrJH*GBl(d%E*RPUhya%jWCtjVmjgG3A?; zu{9+M^Edu(WPCc@{McEe#rrZuzoxsUhRcB01`Dg9)ig5ni(DQ-s z#MlA((_{pet&fjQQ^@9^kMl38RYR}&NtzNS8ht4nhZ=Xoz+1+m9+_SKv6}s3kJh1W z(~*0}ui$3?G96!dwWF~3>p7`TqkElILS2R*IxYEr)n4sv%=$%n^~=^g>rTM0W}#nq zOjn6?7G(r7TLYGD%Xwq3+1)npi~)N6MVZlRF#YtQ;DEAd;{$2DST$N zs9m4=TFWkH81>nuv6fT!PQKL&^&M$_R0F z_-126J^LiGLNMC}UtKA|dD&eZtx@ZNk7avHZCaEsH97`-inzpG`x>P6RC%ZRdZo}L zWPVo_YFzKGQ3UiC+(s&x>0b<&i)V&myB?1mIWD4Vd7 zbGdvKhti1sGh`S~Gi5kQYJO&zMQNL&4`}L83^?R>1NE`y(Se3|%aAWrym5xo*oEWZ zRNNFvuY%nOwF+%Z!b5yuRut#;#3>oOFUo-OD>?qhq*osYld2*gL#`@o4)f&=Zw8P0 z(7z-J6)~qSc`wTRYN>)82zNYK(KI-EpWR(cgg3XMwZ0PbeG>!tZUc=h(U>trNs=Pz z{7t`#hK+kWBwoC@7RcmIop)gK2J-#w^-f`HvR2$pQ3L+aItr_2m&4(yvx#aaolI>n z);ovxsnxp#z1*vF_50OT=VrrueW50^>sg()D#R4WYx1P}pD?n%36Rp<0JW}zNl%Gi zj#E~X2JOqKzLAMKZwaLAyay2bQ*SyJ6!+>Xd>%pld3E_Nc2vdIO%mO3s~u14&~?2T zkCS2->uE`?sqyLvi7@)A$?L_5(wCsAarSSp5SxK~H>ICwe8 zoK{+mywtXtacP&%df$e@SZ6pfD|Q%Ezu6b6Dn@DukD{)@w#7k1`o#6icjW%bjC%*BB^@H{Y4@^q5!E@Qk2A(2W>Z(QxPjU=>#l(anM@?*lbDe!EMAV#e zq{U3!bwlVc)H|-@QkB(eYDQE{cwn|k!FhKQyyTcLGiUhYFblk^cwWPd_-=b@J5<6d zHZaAF>uKSH!Mp8?sWYd4Dc`uUEv|Z1`r!pL<)Y`K=heGLM zkA;_(UGc(voyM4tRcN1G`S$E}^1lu3PcMRRRc zGFijeSlitE_-R8OCO6W`n#_boGLez?_J&SQrgnCAr%#{$?|{gE(U8Iapdo$!3k`X} zQqS8;FWBD1&CTlEIm#tphk!uG(6BQRk!P9wM<)9*I@W_pevFQe{)h7z&g49%F*%P+ zs^dQ-$3!N{vGhMAN1vSkMsh6u56SUf`2U19#{LU!EDdmBf*R|?J=$Y0BqsV?%M9qI z`czfNGSP}NnIUm;ap~#lxw*MzWo5UT;#=F2JG;_*dNY|I#j2{RmfOutisHb)z<*H` zd;g&*CexQwhF+(Qz4`C(#DZr`c;YW6JaK3!Z~Q;l#F>)$`O4=nYF;kYFD*CxhnzS+ zKhL~L|Nra%0|oxy@RS>^01A&+xd^2sS)!6)_RVgsZDLeC5pLe>uj5mkc?c6*-}oT+ zR2`TF%HoOGn_@v2Uo)`>FcN^H!9aF}0X$yZ)&!L;Tx$xWaV29@GxToq0f)J;}M?feT)NvlT?y>&<7+~oBfP4T2}uvU@BoNRVA(PxAcr_<1=vy(3X z2)ijUX>whx2Q+n_CcA}yK$nD5C1I#_Y2h8S7lbhAbc#y0r2a8|kO2-%1f}YUaVH55 zyjOu7KV_{VEXPY{eW`r~v*cFaS0;V-C%w^j%qo}WMO8TDw^Gy>jDxSB^5YVE>F>L0 zS>;+Q{>DK7@P<1r9~4XT#Tg{C0&#JQZ#SHh?fTpa6ME*~B`JUh^e9_7x|c)U3!f&%#BzJ|PcZxI(!ec?Yw3?=PQSUvvN>6u^-}rRTn?MG zPmrD!sz$yfI?knTQar|DcgnLkqg_wtP6lO0fe(85fnR$OjYHyL{~XE=Nl|_nD)CK{ zd_gUT3vlv#R&dkIw>u6EmKN>;Ls%*eAQ!&CNiYi&;e^KkHVA}swbnXYegh-M<8>W( zp4%gh;D%rYJAu^PkM1Z1-VM?@BpY@Ba@h!+IDJkK2%7(*#~RS}>tb^`TS5ux9&Wtp z&fCFJs-Qh<2z3Q&V`FTh2q-ddeL+R?v?;02eB!12kq(1F*-F zv8PzRl5P2qBJB~|n9T_x+x=Axm0yNJ{y`c{zTHJKk-DxebWHe;&R67scJ_VVxCQ=R z&o?yB?+}|dT|grz!GHixWvB0g;v2RzbwyIY@zN1L`75-dd-N|?R3z&}SQCxt`U30O z{C~cDwuEmkUf&o|u1I_k&pyX9Yha?nG5#PyL4S4HK1G?U{6V5veC6aX)lQmQ1&b{a zoY(VCtu^(Uud_LC^&#FCAm+QHhkmB9XKIR>Ra)wTd+7oR6tH-WVPJxa%U}G2W={sn zAKI8tnxw<=-$-aM3)oVT6ErcDrjo;H!nrP*{j}CUH0AO##Z8otr+4cb^ITjQ_({;8 zPhcaZEbFZY-lbs!E{*HE?0&yHvG+5_5n@UfMsd*e(JX)`H{^kfUG^tKTy!qK&>%+~ zSfAJkB%81pk1`a%YzfFvsaaUC?FnG%=+!)z6&YMERnMXkX!nHhin<{=z6Aup_OV%t zgdvubiiHKySs9q!5acJxY|wE_0q|(Bcg86NcBX>rokygo9Nm zte4D--nGd%-yw{j%`hfwVZjZwTBvm6Y~Z(oPgBI|P$>{Tf7U8~-^~KX7(NobV-r_O zV!Mcwv9V^1c9U>`8Fd7nLQmHHRLR0u0VWKmh$)E$5zmm~nVa&+LNLIl2{buQP0!!a z!DN<5Qw53orZdk_u2;p7Jf6pd$W#*1kdVUi3w!E5xVr^L_tcZYPW9!!*EBY7HUKdk z;w-3GtmZc9QFc7dMZE*a_R&XbDwc8uSi(TV`YIkvW9|Tz9U51AewNIAG>!8YS-IUpV?X$h!9$kqTD1&NX+k7!8=t)2D3f=nu7Tn*T%UgNaNSCe?Q=7 z)e{t&2ndmpI&;^zpmJ(6Nh5lj(79f1lBk$F{jjZ_6lYwz?mBZAHX?VVNJ!5N$71l1 zUUK&CBNgoXHrYo)Rw+Nl@@JObdP1U@-n4=!`tw@SyMXQDqy|+sRj+-Pkc7Dit>wNzHLTD>Z zce|Kyg*fntr$mf0Tam*O%Lh^-+m?1EgnqolQPqTd(jF(VGsI#>&%f;EGRPWo2lgKS zCaN6ec^`yHPt-1jCEJ3I}A`Lu17AE}7lw_T6H6GB9J}q21lZm~y_kthKiN5+A z_F9{<=juE7^p|l`vPlKbRv)0;^Hs5pd%kuNDm_U;`m|9~@9k9u@v)IL4fImH(T#i) ziOXd8l37|dNQPhG7N69xpw+GOF?pVsopE}*s98^YwrH&&myRJ21H9qL& zy&^1Dc4p}Ww3kk(YK?g&k@{lUqrXQ6V56DaV7vv5r}*O zU;A?92}iV|t#LzMAR$*4pj-g3ApERvTp@NjAKCLrkNIW%}tJs_9 z%#Hom46-u?lgk9&0$czmkjONHL?Izj5fM2_@&6b=B^gPk>C;h?Q&m-E+BzdmC8o2Z zoKUwXA7k1&Jv}`O3yc3)Ii^`-I<+V#3r|nafPjFAh=?S2n>0_m0$(Slv8uf6%Jf!^ z5$8H$FSN&ccTs)r#rrb7)p&Xk(_TH!31d2}l#~>v!zwK;{g0*U=;&a&p+}D%{olI) zcsV^TP$~qMWm2OQ3Dvx5btJ^B)~tymm0=oaQHzdS+bQjFv_@>QYU=6{q@dSb^=rk) zlfec3TWtKi5WrRxCBRNOBJU%~hZYn}1WtoNF-K1W7p+*OAg3I_vPwLqVNfY#l|4=o z2WzlGO2J#~j(A&PBs{r9@*RNKP@kWX6tLnq}enf`z%os^OYG z`F~4({r{9)m?$JJA}lK*rXc-~?N*bM);ubwrlzK?t<6;3LPyQ^gvKd-Ef*sl0|Nsy zGcyMV2NxF?4^xu2rQT%+6Aup$A0MA&cbjxiyOMy@SNeZ_5?)4%5A>$|D|sqYa)9u! ztr>f5i}mNwj-olo2o>TXTtRjztseAxL|iFR@qtk8HiK0zNa z1O%~@Jz~!)207(|lb44B7{ThI5(jAs6aW?VK-PK#@+k;0R)Ad$T4yiIVa+Qd$SXkT z4+ye`<^jbn5Kg)KE)!NPlGlUVD5mBXuk}_K@c$?-|IZ>X`#%*IWy<|O7rCO0q>8MR z=26-Iwa87iRQ?~!+slH?)H=}4IK;u^zpwC=bGG@H{#oJwgS_{QYNFx#ekYTjfJ{O_ znizV=P^D@@4L$TK2Be9oAfhO!3C$2dLY1naS2ZXq3Ti-55JW&!R8&Av>|n*p$#q@# zb3f0!&i$VA<^6KjS&PM&39#18&L;Dl-`;;wc{l%8d5cc^mCV1&dmtx%C@*o8_kX6m z|2gTXg1?G9;=hYr`tLIiari%q4Kdv7Mtxohbu6#v-fWAIXx85TMLM5%U$UTF!B`&U zrG!%uXGrUKz)569Db=*UN<3LF*j-G0zdR@^ajgw96>8;sq9ybtkc|w!k93rWn3^E^ zfCpY#U0Ez9WOqWxF3p3{10HKNu>A=hqPm{6zxS`Yw)*QVH;B$MOw>wPqE;d*WDx1f zYT1GOyYBzK3jaQS`phZO>p?_De)fMl{J;5eHsB7_!dm|qY^5l|Rw8PpqOK{zR@!Q+ z`kHE@zpSD6H@Pw}(K9g@kt>Rpx}oS2>uZbnm6d^>=tBPsVOd)lT8V~e9Zc<<%^cj! z=^mCYKGyC5n>>SP#v5!+{?+k3H*NIVY(}O2MYNoqt-YxmeQD-F_LiY^>oBKH|C4Tc zd;dkZe0+R@f`a}h-J&z&TqFOcTd~QExD@Y%BM~mN?d~=^J?#^`9YjU7Kfoy^$n`&x zuGrYve@NHCg9ra2UHdpe2Xp==UB%4w(y%NM>8ciyu9*CWXkJ4MuQ9HqeP`L3UF99| z)&GKB`~C}d9qJf4a&C+j>Yf(L$Y6S8idsgbPkxMFL9`z)#;-Uwpki0>Kkd39iP^kA zT+~DahoVFso5ha&8;k8KEJ~<8aq#a3zT+Q0*2~#7!2PGrlSFNt%jFgo6;)PNwzg&b z4a&IxfHG0LX=`u$7brVFp7s|ddwi|8=r2$ve3mmXpEvmOe`RIgk5B#liaENPL-~jEEASND@SMvps<9do3&y)^y9uywA{-_ z%e7Buz}xP#%}lp^2~d^5+oI5mfwXvz{88N{vvt&?145NOIOG8tJAK;uI3!AoboAJr zV}uIs+u^ZyeP^R@I$v#G7`}g6QAbkD(!36`R7J@~6ll~v90ix(CJmNFy<+dr%CL*q z>>IkjZu3!x+bJ_7iLHGB0E3y+;2PYjJ5PI&gJR1aMz43b!k4cPk@S1W+=6!2i5UF6 z3I+U1-op-K<3Bv%4JZ&;Vsjlj@$|}{n8CH>k~fjCG@V5n53_s3)6SdJWpERcED(b7 zu{<(5m?RdKrm%A!EyR7h#NfpuFTKoi2MZ`iCwk{%D-ExJ3L-%_oq)XK7^%a&yp1=ZYYEofo zvU>`f%IZ;46s?5g;X$5_r9?X_3?6bBB+J97eK`F|b)H;u)uS8rx_?&f1|hZ18gDQY zqJfIB0AxqjK`7sZz|#zlyy_I~riV_|%4{rniHFAOlR(i1LXwS+qV_JjHt&E8k*>HA zB!h~3CJ8r#yI$Yg5zFop_9;1NC($9Dm}Ca*fC9~C-4&4K*z-xQ{N05oa(iQ2FTjMY zZElC;UY9l}DUV5%iq$`~InFe_LCOgd18($Z*2e6^1b$4XMu*TCu!t_*u^Wro)3!I} z!x%K06qB+YX7TCmr(v4p^XamzLDMmv(?+%~;1GtqS@=gYf-d>~_L+pq<_-qz4|E z^%p8;6Cd2>-4%DNHX8`$A^{0AIMoOs?^Qmi5eaQ{R*SA0&z8=K9W@7Jb3gj-tH}?w z07{q044Kk(O9N^#dkZsfl(H8NzC`-)sWhC=(z%Z#maB%J5hkm@KH>|Y!6R;)*Rd6{ z`n7rM#z5*W+OGcPyGl^1I1sb#xE>GTVcGMQJ0=~EQQS90pvwci>yP5=tlx{P?nbtx zyP*oNe%bP77eG+}L^_d$*j!pVYlwgu-)yCzycsgC0@N--x3Kdmvvm?LrV_2@uH?cf z5keRMu^97wm|2xlwCQmJbnYAs9CC-PhHKig=iSK>GNK(OWB|wrOE%I`JQ&o~ca-;m ztgb+ZWyE8M+a>()J;MqJWw7&-#Ni0MwCuPkbly6LoW-OeUX-0g{cyJT@2`A{@Iq@~ z=P}!i*UH<<$l%r>U>v)8dk*=$xoIvB5UA|pq!0Wc9L-T}RgP`Ou(W+_fNC3#Odpd# zJ?~CFqa#!=zRtE$>n8>SR5*3KWJAUfP|B>PT49}Y-Yz9oL=3LgIiLp=3zZ?W438fH zJY^tUQ%Qiqwl7dFS9`{7*Ui{jJ9*>h@;C;V;wa25ttVjAA&d-5;g_MSwfb0oV{N0{ zqB^C~i3emcT^dm~t1#O#NtkNd{F#YMqwEYJ^!iPxK|Yohp{QO~I2!@iU&aQh*TbR~ z`_;8))wQ1jq;j(D&Cu*DH&$)3{9?bFt|#*LP+_g~@5q;AE@UB0q;A1C$>U%go6az0 zWlVnqZU$uwfsg|zB;XA6u`99aG1Ckslr$z$I|<67^pkI?-J`aPgGn-WigB^hnbZs( zUVZ&0{$e#B=GcAbXwQ8mq-LWXnbDd-7P5lcC%12Hd^Be9P1`Z`y|exUM)auCMI?>7 z+o1MF8^ixHiOovtz(4)S?Z-75pYc{@WYpI>ARHNI_C9!dGi=q*uSo~&8E0# z?kI-^l}Cgek^m#VAJ$jhv~Li0m{XvWt~x9}o)?4iO0!usQ{H8TEh3zKp>AZi%0?U5 ztdmxLG3Fd?y+Av*XKb?SB|x@#UE>aGPp`J}ZN)`k;1a3dGd)>hczK8;c9oH(XGSGU zPxVR2jUf$<$#-6QV$gAc$gwCPKBCM+iH6W8jLwcos!_*VJa&w2tSD2O#j#pZ)N`c{PrzTG$(3s`g15CW4Gu zh*2?W^}KZXlu8B};O$}g5oRpWKYSK#WSu#u54?M`@O-(yue%`gBvcrA($oJd-%KJcEi@6+VPD0zKNSxx1XS zWE<0&Y^^~i{!SR6*-B1#rY_Izf6ayIhan}(VBBIkPCv=$C{-qWP?`o1KC_N0GmPw2 z$)HJ|6q+~|mWzlfUFXN2z3Sc;^kDAt19(duku(cEG^Ia&cKXiY-);T~P6ze9&^MjI z^gDmq_w+s~L1F`b$FGlTg+v=bxtWal?kY|W01hO|;RA0OzMpJ!ccZudenT~UOj`=N zLB&P9s*#c%svYDNfHT{ax&vexcPN>ImLz(X@VU4y9Y;Iu0Rd zU>0A75RXa9GDC1=(dq*>0x4$P|Qo#KDdRC?7=C?Em4Lc59=Z zfw8U0UR(3|v@_3buWH!Ybp`_Qz-9{W@cneK7pMh5idn`3f9RwNm*Nw+T@VcqTL*T) zdYib00mvAZXj3d9#0&~uG5o9ytksvM-@t#r+=@>4h!&iMrDQ5kAPG3 z#JyB)2HH@4{b-{P+sDDJ*A?DNKUQp3WauGDK=THT6UTI(*ym#jd2#94q5%Z7Z%`Iv zYqW!RRliHkUZ~E;**nAe+(l<#%=w}tWJnGar{K6YDO(I|r75_A8*qP3NKuf3P%3zQ z&r7`nCanbDfCd6r<5B%J1q$Dz`OtPW4>mfO-AIgphoP^4uuMR*qDv(i(jN4Mp8L4h ztkr8U0N7(e%Fu!MZ5Y+Gk|5awzdw7FcdpOiZyQCpO{jW&j$kbrmr52pWtl(;6bB?r zv=fs8)EAdGyE%VhkHr)}m)sg!mK~LzhXgQl(t0c=mB7Rt*d9tmdapBwWEwmbGP!1- zJe3v>mdqhVp@G4ISdpGwGlq>JLt6n{$qX_|V5m5TtrK9oV#?SfEPFtE^YOdnV&hN3t%o~0wi!fyZ^k)*TNd=43>IdwdM(h<^ zX2Hj1P-iA#^DXF8Mi{+(OZ>~IkVg#&0y+t546{h9hS9v7i&vE!UlWP;6cYlJ|8%Zl z&#eZUXOk*9Y9_!uO`9@;nnja2oQh`d<>uVgN6p86H1p(I3N2cS zgIY=twv<)0VEth3{LG3UEl>^ME--@6!a5MGh-L5;3v=S2l>7`#kze~evenYBT!FR? zp$?|w!u1nma4gKJC6MWfGIKVtieh>QFguxQqoi1&bLF$bxrkK1aT4&H#EmzOEtGC2%GM zy?k!gKoRZI`RnzWyO(nm`N$IAb8F9~_lIqN^i8qz1sPISiX$LC7sSy`!OR+zw~BJP z5Ac(%`(+$u9Im2KtZGphH+(-1=XvJ7w9H#m0)2vXBwZ~e7%C>W=hPO`-@r;CR1|~? z1pteiiJPMBDur+hRv|Ek4UR|h`S9a2>$aa&-+5M3>ScWPN5d*ZpO$W|lG@9shA#^& zAyWz#2}>=QC^Vme#VSJqOspXlxl#6#HQGLcf|6$;FRDOBmQaml0%jRr&4O2xQMzk2 z)90MKu4^0yHN_#^H3Q`3)#pKJHkxG}8izh=EV8>iy9-XTWN%wuZqtBQQ!6B~K=Vr- zp|Wm22-pbS*@Alb_>l=ygdVeG(#Ea8cpq(vmDQxbyC8Xg^tEuueFuigp4$jePx67a zGRX}6ZebI; z1fvP?b|G%qHU|tt`3W-u#L|NZTON*M4PCEq=BG8+S+uKoTZULq)Y(rOLHCWUPgRWE z=ouMsRlKPSjfmYo!G&=kEIzDAeg-(sS5PvqI)(?_Vup8*Aa!11`R7l+i?z5mou(0x zzVp`&GIj9Gie)d{etf_ZoP}2`VS^caBB|*_M8 zd*U(cPx(-;995-a5D+4v0V?2OWcXurJP;+gVWy>VBZkm5Ep^%II>5NzFqmtB;9|al z?LsJ$g^FPyrNe+SJmAzhIUth?%)lyHsED!3Tiv6+hqzY*?3TrDe(tez`)r5Kv!kN{ zc^+yTi)a@?NPcIxqHr~G=cJX{)b`(dsszyqPk@qp@Mw`gJ0Qo%bW00E(~{`ZW?Cec z{!>lIy>=`hE+1s)AKRN1)So9;dkTRIZoP7B|3Xqq2Gq{%Vx>+xy(`#QUX-GDh;e1u z`--8|rGmBJ)4D%9eUWFHP`eQZo;wr@6?w4AOw@HFm+0`KAlNjpI)fZt2F$dFO|+q# z)uC~3Z|vJ?Z~*~y)w>8(U8L^;+_Ol(TCm!uZTVZ#U<`JYwF* zn`OT@Ran!qSjspUY|}XEvvJ9bJ!a=@6jHd4zP?wcGQ)YXmRAk%lFNi5uNE*WBTS5U0 zY$=2bXA)%Nv7*ttzLm;e^D22Js#JhIK^<6x;i?%{$7Sd5v_1V4XP|Z7OE1z+Ey+uF z`$9DzySPQPX5XvgQ@XmZ6`E{OM|qB2KD~R7di05M`l|8$)Pjc!;uBlyAyojwreIi1 z-)H82f(Gly<;QnRO+Hxh4?)5n4OM^EmPazti4?-`A>YW1ApG(vQbM2%El_;67U2hP zXTXaJN4H-|5rYo#EPd1t0vq`T-`o^eZ>20gf{p z17yQallzflxKH}n?fGxOWyF{@b_NHu3(k~Y3#%&598a_!I5Tqd*~pN`?cx0K(YRmz zV!sD}jf`&S4&G^en(~#)!vL!cxIA9;PyVO82UfWL7wLvmMa9Zy+wWipChfy-knjg2 z^ntWLFSdp)o@{Zi`if+B6#@_k@QIOvQ^lPDz^uGJzECo=I8L)ZV0_L#SJq_L{M7h! zN1l@XG1b6{c_(fq>7yWHV!@@%K<@@~pw(A7Q%2o0T+Lp|CR1#k4T9|X!n59~L_hM; zg8-gFa-_mp7 z%ln1L4?b)avXK(%AyZ3X{n_uNg}}$yz#CjOkJG`_Tm3mkqySyuRfhd-fHX4GN*>4G zYVkdj_ffS@OHR7L>hdZ^9?0|BqNCX=c=Tu_yB9vo z+fWWNErT}psx7@(Hoyxnl&g(txF{FoshUNXPT&wV;^L9#x$g3e%Qy=8m;p?SyT@6q z)o1aR?72?P@SC1?%m{p-)9aT0c?HaE%01Oj)EURx3 z?@5_n0Z8jrZLL0`lpa2&0hOux{S3;Fbg)(lP)`sC{3vgr2@SS4vsG4ynqTYLWPD!R z>rC+ZS|1l{- z8heDCf3s`0;cJVhk9?EEj)l|bJ3!~{AV6Yx9xG6bDlQAp*=-y7(E92y9p?8(hvnpi zP#)y7hfQRj$UvUFu!^+~2%pR-Y9@(R2q$NHi%phCpWIFsrH8nWd#hB$^GQ@tTH$otA`v*`njv;)BTj) zSATx}xmyTdn;PGT-}o!OU(!D{;fDPFUkQT`Ecmb{U3$u_d5N6K$E%$ywJmUm1Hzq1 znVmR-z1fBxIqT&X)5LDUX4ttKj2ZNFEvpq}G5piVeTA+Un#1d~F2pmxYLRXU@rI2M zwMN^Dv50$|uo6z9T$p0jKHPD{#ro^?V-F=Qx$EQtjjlBiOM}=V%(gf6-OO$K`sTxC zAdcQ&691uR@z-ay@Iy-l`8F%ts6}ew8riH-DuTZG-u6h`ajLDz*fduR0TI?Kh}DI7 zfJ;f`i$x?$tQlmY6zCo*iUK>C5eT&bV8Qs!X=2(1$jpE=H0~i{4Zvfs%k4v(F@?^? zgS}IF%{H<@b6NVL=vS|LhmE%iE^$FEq+J-Xkk^;LXoR)5Qx5r1oenMvgAJlfKPLVO$5Y!`HFXxEB0hjvrr^qeRK`jNIOGovv&#P(LM*|Civ8Rq{XTwdzO+e=%Q{mMfwrunte3 zIkD))3~7J`FBx<%1o*1<)G=)GzFIMoLAsRoS<8jIv^}*5s)Ur+tv4BoRSWaJ9RZPy z0OXE<^AqEY6;@ZKJ`f*MXlTwwvYEjNj4fM%+_4f}KwrMZvW#$&d;%Hm! z>sg+8eFc?H%Yddf@ozG1X)QFrJ4EDmexizzwc!U_AtTj%;`w zYbSolHOug%@Lt<;_fY&qnJ#;Fh4jjmFle@=ZI83_YI@SPY}7^@vwduxZi(aCG3 z4U%_H$%xVBALc50aH#b|^n0*CnHuI9+X{X~9Ni zQvEMaCb`_*MI`w%YzS7h5;`A$D%p3|N;%YWBm;Vj+;01IofbAbgwAYrPSuRHHXl=6 zlgpX$6ZVaV46*||TIP2x0FDcEK#mC%lK31cSpx?#UjLE86}>CrZB7|Um%K_McAdV( zu}M$}C!+%gZPoV__6;hdA5s=`L6DZ?{AQFWTtT8 z?g&C5hbFGr_?ov@7FcZ#srNDn&t9)NFIIkKjjYTXz$M&p6urkxw4wq9@L>qKAr@^5Ys?0Vu z;dCerxYO7@6O)fRLd%H-5K~Ec?Yz{J2-S?zx%U}w5@j?0OjxGKb4|W_o-yYrI8~oB#|Kz!QQoNwa;O9XWFFs8#()}%3Pt1H9 zP3%9n$*MI5-%W6dqdk0#WQjIr0xo5eEoVfV@NOU2_h|E3nD%s8{uoU50IDSRrVG=E>KLFxIOsE07_d3Da}K{Kq?tVLpI(_F%a6|W1675XygFfP<| zHz4y)LaTch!lgNF???TmT8_A8ucQxl#iiTvBg;jk@r@xSDss?2H`qVK;#BJJ!vb-* z6x>z-o3uK?EVPTsbfRa_Zvxj>aMP6vDE4_D(NP8t7~iG43^;LvO27( zUIQa5=$IL6wd+&Py1ONc-cky1Ztz0`g>%>Qx;i#Q;11(D5*}Xf7NjY-o_2uS*@}!Q zS3COmPpa&k`*Vf}XGdE4JD}G2ADV8ixU5gCSuLXv*iM>!HMmkeBRjw_ zH--_Rm7)9+{BcED9d)gfdI$KMU*&t(;eOAaJpH}@IK4@cSMP7D9J$R@BK`u=qJ~s^ zK|Rf3$6aW9`wfkX8!^SEaXA||gmdJVmD7^b>BNxAZF zW3hJIYAdrbS-~Mw^Ts!xMOwJ!#q@5zTyKU=b6Rb8q1{kXXLoVLP)UDxX~s}>x{Uvn zvJ(@@rf2L>E;}uvXWBitn^_}|t$cb1bM3MZAb_!9ZR!3i1tDcm>M*I6ZMUfi@c9sj zczV`IC*ac6?@Rr4so4w)3xwcKA2&@@Y|d=n%nfM2@v(1`bF28Hnz{j{EqNm_Q~P_{ zRX^6L?>ynvPj!!QF2#Zj;t1$exM9s?yIfDNLcjjIxzn)hK8Y5fzbNCjCW{_}bQTUH zCO2g1_DQ7V>%Hx}5-zA$aV>&aYjO>8nAdYC2BaO8a_J?Yu;9_B{dK$hn;!Q!{jP7P-QT};|M1IwNz7we z!Q-Q@$0sX~&#oRXC*8lqd3;OtSjoNf+4bzC>!?Hfo!0oYr=1?VL~Cg20FjR&a)3g* z#VUf~L(8~2e}}A-X2pcr@-bHNY!C^Emh9woS)s%Ylmu|137+H^vrKthPBvc&iND}N?!f~Da}@(y1hz#CwQ4za?n_yW!8N>lgpn3>AR`{jLbdr<&%*|=F*`ZxZ4 z$DQ}-4>4KA_vic4%>jf>JUl{*O&f$;X{J#b7;{R74L^PNJ&d^214suzl!>P3ZF6Vt z!8_O?eGjzYQ*1MnG`*>edj{V{+nMq_zNQ9bR|k-|rvh@W1mq@7ac&13dl!&ja)PSXklq# z@Q+CL_blY!BANGJA{pa9ie#pCcIM8`oBo4H=1%=*Ow!xiE+D`$DCj@9WLueTe-BG0 z{xvN5|Duz*MMQW;$9Qj#_1U#6Kr}v?m>9e-IW#%>UlWumsbQiC%9NCpf4s851AlpC zM_3Vm4N?Bz)UvJ@qbb+W2Z%BX2u-l#OCDg%+HU{=fxKlCmt_LDlgwx zRdwJb|KGCNA(2t}KV>tK%=&NHtf7(B*vxKg%Qz!AdPcwziD&0Jxfi?g{@Xok`^!D+ z>FN2`kY?9kLz-8Gh5zVh-2b4T{cBXS@A|*=v*D4-(XkU_<0nND+Wo1zhmRVjr~fsu z`Tt!B?O!4pkDQf;K`Xj);&IoHLMqNx^p0AQNM;l}{a+%Pg45ahp(0K1){vpXIh*Va zDi(20=NfNUnkBxT9y-@_=Oi_~H5Xg3c)i@FNPEkz&X&7Pj5_CPxp7U~Hf({HF7rC- z-Wld~u38JzIg=}c%iS{Ux9!mdWQTHbFyl?XOWiJE!>eAlBVJLY@z1cYKfN9%#lU?H zpUZK^xoVfOyUsczLkUm2YQIvpq?6t`Bkp&-3@*lvrr9lAe=gwznDP@oy?(iUSYB^$ z>A`G~+feGWtw&H9mKUre>DpCZvGWt}4)T%a0FNuxe^M&zDqZ`NJF+jMabMUvo$6P-Z-6G_V`Gmj3E3kpRpLXj-whTdXG>KlB@g6)PZ-&hQXj7%8>M3b zbAd=k!K}@O5T!Bv11SbptHCEcB1f%HrT}gZWjI4Y&FoAnI<6*5ZnIeZ04i{o77ra; zJZ^swX!mEp!(txFN{5-vq?bz6G%M#2O6m5wii!~`$1K)#hNnYG^dYtHT9oH%^jDW^ zcOMG{Tc`%pRIHi-h1;Tbobge%1Tnr{;{*^v%_;>k_4Z6)?`jPUFfaDZkqwKZj8#XD z{1_|S+S~y@9-3y(YuUf+;llASaE2xk#+s$cSyTmP$!#6m$k4Jh^yyU_KpLhiQ!r_q zAR~oBC#7Lgys6SPRT~TrUc=(3_%uuLU6a9#zW@i;(?q+)iuW2BZ88|P1 z0VwgOZ0J&OH!4SkL0Ws9qYr9DsM&4GuJ3m-{jLh=J8LcHmb%U+&^EBNBMTfu(X4vi zuQF$;M~P1MVx{C()gpKGAAPlXn=I~*YirPrYY}d<80v;(_ z7_cXVh#^f=S6Vi;e#D3Cj|-sP()33p-pAJmtSUPnBNpwk7pv_N2a3F>$*$XEG&g06 z7Z~__dvf4plKp_;F77^(?kTXxEftn`h2xux5fQVco8uAZ-R1xRd~0 z@2{^pyP1raHstp;R>Ea@x1<#X%LC=jRlPT4BgBkNfz+SkU0`oO7+fpsrry{z!D7vU zmpgZiLPZ;0JG%NGc@SI=-&@^+{kibQ9!W6;?pD5(LKqoKNjq1+EqiIT2xGy{j-`pY zcl1o3M**jry-?xk3Mdm#a%SZ-a z(GD7ubJ&6!DWs5gE?}QLQ2eP15cFx*ONyP3zgziUiT((n)_vL{zB|0T|Cals3srJX zbU>(zViA*%zcPvg8SEqs$mM~WJ6{X~WKe;hBRda=8W%e0g*NUFS?wsqD) zQoZt)7~6Cf3g{Bu#x}d5VrSFK2LKr%N5N^oimP%c6LkV$DOXS-(yv;hpWlyDAtiF! zBzY~^qGIR8uQ!u3Lb zJ1Z1fSTU;U-Hm*uPgO&Org4tP%|FrC))B}fr%OsVHlKD~D3y&r=t>&90#Y1$w}#oh zXjmfUYc6s9Cm!x6OF=hmfR~S7t+BiIYhC7%p8-!LHZf|@nzq_8cd2dKB=K=L%l_VV zMRqbwdS^_!d~5>17KGt3<{Qjwg0T<7sg)aUySo0=%} zr5&NFt1COHQn5)*3>=f{a8T;-U=G$R%Lo&3TJbZ*@1@F>pep5oGl1xG?Go4YTKKB? zsgxPnTVxsW#Hl)pB0X#4FYZFey;I~tAcZC524u{pxu7DI9$NBbM)Jv*R(3GaZ}b_- zeUe9|=;2r*PJ=YR83;&=HYmvPdI_Q=J`LaPc+~lD$x7kgc^q#6 zev#hmaAIpZa>4`iZKL5cT-G7!EAn0;JIU8gwf2fK?-W1z4~@TX;w?MRU%x#+=%g66 zxh}6I>;lq;czLiP;B|+>wp$kvgv?R1jK{dC?!hNwf&)^*-N?P_sHM`WA2B~J#BJNy z@a5`7`+hNi(plphy||RwHo3rEbEcn|WD7TaPg{FLO8KMuVC^wgvEviRyO&omwwOuI z!3$ZfM*M*_B+Lc5KkX8pgX?_b1KkmZwYLP@PB2FE|ryWSko!j~QF(f>oR@F7H|BK z!t(%Aglc7f<9_$*tYHAgf+6@>3bhRodTv=qhBG%3S}vMbBs$&s;?}NaFcBT`d{Ph7 zYpr`dh)*=zcr!(kV`{b5$AIFaYim(5w)0x}VFYb2LJ59$hPHJK13x zOgt(!OlCa^Wv+@RHKE?VJPb2~-aI%Q`!zbsUtS#038Q+#38`W=s+|ZRVdHi;*8b0D z5!VDet_bX~WXP9^FJOZ|KJGtu9Ln&AP6(1Eh4^Mucn{AcXC~Qq28$4w)Z1^s@&mA* zhu{f^2+fD|e1Rt$p=TFO=3b^w)JS8$W{5%v8z9*Yt9Ba|Ojb(oC<>vDbWI^?(@G40#PBcIO5ZNx=sV#LF+?T3EQC zFpB1JXAuRGnOioubG(44iHvl)r zVcreMd$1_)OcaAIIZ5=AdzW(ZW^+rOod1*+@SI$v1iZSXgH1=vCl}XAtndg<){=n* zxIV-3S5ou${hQ1PuTZJApxXc}QLcAK6$k2RB>Yh~y>w4`pr%SP3?O;}>n>KoJAkSw zU_c2F7aUo?I=g4~9`E+0-2}qM`RvF;2*S&j>gjttY*_V!1BXM>l8|8l^}tC&uN;zf zl2}yzUW*SU0AB-vjWceBy|RSmDx!*M=>x})uc`(_a;Y;U$*7T}0wi4ZQ^V@qtjHzV z6vfMg!SC`*Ehip-IdMalkWZ-I#@=FW4J|QL{MW?1!?Q+ z`smAzagQ2z{b-E0SnH-OnGn>p?_g7MMN@b|a?NQ6iK#e(hsD1{vIDC@ z&5mKj5L#vK4@tO?pi)69v1qBd+_G`DSuCMhp1t<_>WGbW^k08;2BUG4%0s5>qM52n4<1ML_s|I;8NQ<0&Mgk0ii?qB1d@p`t*Z}4zGe!;$cMB zK}cPuLlTCvHz&S$L{N2sUOgh&3DK%c7)&pCL_l~KBzbt1EFc+lT(CF?A4l6mL<|K> zpb{+}6ZC$bm8C)oQVqm&`Ho7Adj&W};#JlL6VqA>oU$E%~ z50JrJ7Ib**4CskSq+G&`IXd#t`)5D}3lR#!Ae6u)9@=qCmPAD#U?57IL0vLBo(d*Y z&X5=gbrw32aS0HBMQ=c4!Z~D3*Ir>8NCOX0(M}8je7Wlo0(OLffSBEd@-TfK+74)M z%()CEAV@4klMbXqMHj`R6IVr!KLM5)f4Q7{<B6*>@ixXU@^ znwf$1mg^=zEKQl)OsRJr0o{U=pNYSJcC=o%0Qjg=RD55ZG&owO)X-3INRcs5l;6&CsrE3{`P2sxMPP#&b!BO~(fsPPUjssK$pgy@1grN^J1`-(xgZOqH1EEGi z_f!A{OoZwTIvxOQ$1pl$m}CaxL^oJKMXL(YqI*XjN>6vrIdB#{LIw39ECc`q2pj$u zePcDH+iT>?5i&M!34Iq2D9@mi1?c_EGf}xWVs%AV2BR+oi^x}pEwHu%@Ip8F2oIqu zK<^L(PAc50iyP#GfLt+xZ|Z<89tav4sGjXt&bik2mN01tjT?s!PO`uo0(1LFGJIw9NkwKHXrhigL06oHCemAlJ2oMAI3(?)KklNTFSTKYPLm-2} zLeWG8qwNqCB#mJ%;wOBpu-!29S7)fl68fkOC5QozLNvB}5WNLDvdS8Rcc60_fDTz8 ztp~|$Y0-Lj10-}x+=f)Cf8*kRlb|Jr0~F=c9`6Z(@zAbw};`8b#a|71J2CM_is6b!^|e zsQ9eIiGI}o+8q~Ji#Rd>>fyfC6lpRb)nRmn*ezTNki`k z+KT{~=(B+xXrH6b zzm{KdAI?642m|1qNjZAZcU=R&{Yk*mUeAjOdKaeFjP1qQC;1&s@;jdt{xXSk=-HRw zq?6Uq>=Q>j3RdXyFf?xZPX!uO0#&OYwTVyc?wM7!hSZ{s$uOST5O`7jGnLi-j#l;W9HemeB z%wAYdrGL=yjJawe{*)xWX13|w-B=i`XsOM3H576bVuEGBSQ?lyGE8|lt0+EKKl?zg+|IR8n{oJxDOPp5$!S`#ypuukg=ayt4U;R-7!Fz zGQTtKb@L|C4H>qfdMtenpvG7{s=qL@OVUSC7+jN6FH4x#hSbV$$Nt8hAH$I;kSwF5 zgVH7b6lj6)7AuhD!=lYjT{6XrW@LODAN6JgY9<%k9oXymz1Sk~{mVZuU2D*%{#=rM z_i8VM&4sXuj27iFOpqRHZ< zpMV7D{4va7K_-%qhCI;7Rs0gxU>e@>uz+{-Ptfv__~oPi7+A=m8}eTwjr? zK!5`^cm?L@FSP66@H}`q`>b~|%bNuJX>aVf&zjwd>HL>;g z{Ug5_&c7*yA5*K(em`CP9eC&WN$o#Y;GY*q{w%I1KH4fZfAY`!)jvyjOdd-#_C5R~ z%JGwMwP>!=}kuw^c4aC$73ilHTU{}0;M%5X^}|QMc8%|qfF$P z1ql1On03CVRl7j8{c+vM(fUA1r5pzcgqNB~_qU?zXP#r`;D`u5IoT+aTI_Y0Pu2vr8WLhRxy1e8O)$=2`Y|EE7d@GqE1bDv{)-v0=9zje!HHcO_wksP2$4(t0+@OT!-NtW43X9@$gqBw@oW)JKNb=zH(z@ig}fPWp0>i{mX@ zKO_~NuMhIA`r^mdety0tWcxr8IXK$4t`v>*s*?(0&?4L60{VcsUPSgF-X~(PF9_Jf zTga8T&v<@CVUnqWmQeqwBn2Jjs44I6mGb9P++nnwphjDaSV|(atzYb5Gm@iV2m*-p z@eH^WySDm5kod6m#x2>VETkyHc`zRq*vXqM-mrLn?N&kiqdvH)4XTt3;$)~7tmF#j zPjx^P5ZZihphKbe-TGGcE%5-SmIZf%BvHI*KkIbCaa!9e1Xwr({sta`R8CzuY}dtuDS~y!=oumU@jvJ zDy9SNxwQtf%j1$9=H=_6$Y|O=eS9Y;Gt7nGdG3VP>{|ff3MR z-j6-W_I$F8G{%JMMbH!1z5H8|we+kqGaRaPiNSty zyW<$*(l*6cF^*QCM?UNGb4B#T&ABTt^GVrHKgTFlq9*68?yJ~dtVaXuIT8TOYD&lI zJJ+PANxCV1z<0fSV%%X_3D(QGA{0LVqnqSlGcA4Uv`%#L_&P%Z{gOrEEi z$qsvD5rGy=A8pE3+5f`F66P|f>%96ZD@cKApVK}=qm!`@?N~mbY^^ksc%FDaihSyT zou!sP%V~uzks8yl5WsQAxhlgVLg<^mVSy{EFyg1r@aE6b@+xlg5^?hMkWB-1Pa!)# z!er3~q8{D6G)wR$)x7{1?s#V=vDs#DW2CVIYi^(lpf*YBb@4g}JR6M~wr?{$r;uZI zRC%MCw4|Y6Qq7b<=ZrsS(PT15Qkt7Fu;z(-5MQnX=itr>NgF_O=&Y}63sMk30Jh0EFy?| zEuUeC6Z>%UBd#w63Cm1H$Fk%AlkR1gu3pY@z5rKr(^cvO(YCy05j@oF#v}5C>sjz+ z0{79E+s?K;S0*Ol?XdiO;=BTQZIu55I9EE^V2A5;o{xP%zan#ZVD<%ZJ zavhSYji*?=QIz)M3`Yin_K5C!&nrhYN^G4^7zv^Tphdt=t8+oo8W;=q;7SE4`6sA4 zktA=Pe3y3qWPJTQ?_-yQ)JL>7xx$!tn#Hc*Gz&cE3jOERs_W*XAfAFe0V}m=KH`XG zU@9_e%rD!7F{Uw3yaOt4n(sx}6hbMqfWcutusT)i5FT!1@EB4Uz1^yNHm*Q~IeYGq zuXw~91tuS~Xco*} zRAYevY5O-In(50e+=_Ox-T)v}OoG~swa*YA{h|XDoo_=rJBP=Os%ef=8X1(k^pP2i- z-r4Us8J_-7Qt?4G7B|Vojf0SM$|0i}iXr`uAemV^$X<2lu*^+dVeU34uC`re?j{a8 z3;3z2fPB;t&F&>PEN7AN+3g{*ZLcCt|L&4s@aii9^W)WUwxT^zJk z)-7Mu4g2%>Qtpo{r~Xdv?x|1L^8UQNpR;*7Z11(&so&G5abQIf+=CTMm}@bz4s271 zcH;?a9feMDH5B@l~({k<;mtioq5QmB0f$Di*d6-3SVdfxLx38t`8m*2EBoB zm(Lo(n0dr35WljOfGvW{6@fA+l<}u_zbNTtn`*G2Cis)4ZHHMgc#$sfJ(=h_NFrRSf$77ZX*$6qNpr6=Oh5&>#zTkwR=0 zctyBCf^0qu**C#eYpVBxO?oE;=?6blT5*e5kcHHnDhkk}U`Mc?X=QnWaH)+5HR>Q4 z14Da{zS$KG`F)^Kq~^o($*`^J5N@&aBF>TO8a@%?$H=wG+_kCdBtMj+@y@7>M7Pa% zz}p03WI}ablPCj8l5sTOO|mSe!w*5#VbEk9VVS|Tna69B0;Z0u0|ofn>+8);w)9&v zH8)4^GvC~&vOt_a(BFaa@CumhCXgwJNSG5#TAeEMkTi6p@@;|-504)lRc=YYPeV}r zHsuT$HNPgnG603s3G^n?U{N><1exk1(23cAGPqo)4bhXr9WcLi5vMbHw|c z=l}9s-JF(qsxkv3Y}<=1#;4#aK|ynFc0^Gey;)u+nz>!x@gE zb%yK`UliBbf696J2UjkF0y90cWfSi~s z1W%mXXfH#-07*wC%vl`HXdzG7BM!=Y+tSfE2#^9n&60k22pXjtP`mmOfscI3MMXzu?~>y zjIn_Vsfy}kf}$#=Qf;bfOltJt7IkK4erTfMxg+3&vdE)zhEDTE<{wT;D_bHtvstYQ z{XQ>-d;pv>fF*?fcoO1<5BF*DPb~7#xaif;*YX^gK@02-c>zSKHkuyee3_1;Eka4x za2}#CfU|xbWvJqYmZ%7MJ(b4?RW9%A*VXtDBojsH2heCVk>-zpPy-+V zo{u1H{>H4ut4mKyZa=mWe&+7^h^b$jFR^g=?9=Vzu);XM0<1Sc%*o&mMHE4Y9*6^SbWQY**JRvKJstY6C1e0z_6&1hr?cRJU z%160%QRB9cid%z+4)DItA3Xg{w6R1%=56D?dgE^X)9<^d+ zy-;=j*jTEFDWrJ$=|dGdUl!FMR$~9ERME35%P$NyYMxwtrTqY&0(0_O?hvBO$3pm7 zEhQ@f=lhuoD-gjf^?UJJl1;!JvesmC=P?__ytZC4{mLxTNA%fCC+?A=UA1k|E7N*X zWeo^o+vJV@JFE~J`^GLBTW2HUYEPDK^ph5F0ZdX|lZ*3QwD!N>9^uM*+N8Y=A z=WpWZKNnxnFnu9Fs5332XIGLBdkBB&bLTOf zlts9V^hu`cXt4b#-K2oF7V;*=3r7pUQF|sx4n=+B**8(wG>rfkf5M}U3Ys4pZ6|j) zzk7A@?>W9Knm3LH+jTaMg$1C|qX{%WA`Bc#kIsD;Ycw6#^)A0W%K4pr)3=&*(`b8N zu?vkafSrYT;jb z`atZ;nY?|u4#WZ8sK45821J zH?9TI+mjIXo6L*1$eut{QR^K%{M=0y_En=o_3k`AWx7r*Rfa*`P`z zSQ9FC=f#~xO5%0y*_S-<9Wejp0_&#t2h+$3b%!STt?tdNTjwS|?@D|TJ-C?v`Nhk` z{g+=h&fMSV;m5y{OIp+U@)VThm~HhsH0fPZ(lYJ~o+s)3xulO>N$)C?{u%q?(3kY( zOVZavNxiK)8$Vxt)k*&TGiiOsa?>*TXVTZzL&<-olYgB{{&PKc$M?&6D8fmZF2F4q zP8s)^C;C|k{$-E;gJXlcDK2e!N;zd}2|=782Ms#Nr5j4)=sGNlOE=}um(4%-U2ej* zBIfSjdpBAAVe*dc7S*KYalb_IoCJ{KdevaMTjoAjfq3hZi|nhdM&&KJ_o>2GsS5Jn z(EF)MR!8~Aom?bf(sf{G6kQVylkTPv>9W!YxC3l6qoYs#nViPK%OzQuhNJ#v0B z;Z^1dtGh|!U-s+~fRQfzz!KMMF;Sgg*CvP%Wlw%xQiBXl?JA&Dof&_gvY*8TBwRgK zXngmi@$icMn|z7uzL@KgpAM7Xf4}X9^V5D5>nEqnm9L4SAG?}KX1gE1npGcW`@1Ib z!^`!uBsyy*EOX;Y$GPLoOTUvJerbC3g=ux7rS?uy@<_J(iA$$W0Hd+X`6oKAo>;0m z@w+tP@~abFVYp7+6W_j^=#k&OxBI8HEVr%NGd`Q5pn!~O=< zf$Y$Ek%sbnYGq_NARZ8lhw&~*c7y@9JE!)uK?Mv|ih)Q)AC7N~k zlzrl-_`-E9eFJyDX)m#kzJGM($S<9jgC~A{+xYZLcZE&i64ue$zX9ZTNof*SFsPn2 zYO$=a>t5}k%6 zzW1+cbCTum2=#824uYV%I)8S)y_*mSJj&DSS4d{%@i&IM7-nu*A9*8Hw^j>YZK0*= zpdO5~J$whj(vCFm01XIvdB>6aPB;i@f+oYHvd|Q64HnTswB=GcSd`#PqRQKugws&Y zb&_~Vxpaj%S<9W({PP7EKX|{vid4cC-g%t16zR<>4&7_31Q^YjM3ks}iES!Vi_elN zHA6ZnmRr1&P^LLcJ!`Qyi%TEU6{a5}sJY45UaSTZ_AkwAL)Iw`c*DKy#YjUJ|*STD@@J#^IMHcMw(%oXgy8~O=-^x+CmDZ-r}?cx=qT9fEHFn zQ@uv4-MNT=jt*4=?3DYCk(7^bv%A&? zkmg!FWHYISo)m2}zusg+9+Fu5qCG(~5MU2!bx*DVKuC+H7K=1Sm~@0KDZ&8E=I~in z_w@9no1ZksimJ(ZLbYJ_13yyG1%Dqr)_hJQCLr_62?B4`l8DX-pZOkklxDd*H!7vA z={Lr8Flbz>fRrhYeJ^So4hG^ zu{2$MkQ8$KY1NTCrti<5{O5S~!RDS{Uo3t+2|9KA&FuX#^RY*f`v-Sc zi>|i+OijJ&@%#1VyOz5fTy_5hy}wmz!QvwBJ2PR$uMY*21aIzJ0k7U4EaFSa9R=G+ zBGXBZ+V1}v7gW&_WOIAY>TaGa*aPliSSC0{sa2fhJQLO($K<)zBL;M%;9NVL=K-9V znqgEPyco|{7auMe1qJwsLy*HJR2V`C?%k=#i^(fO2CNU0qe#3uB5YhAqgQ7F!*#fg zCOO>AAg>TkuV|1&b$Tw5{fD&uPzbmNo^~yyS<}1C7S@`={TF*lsJ0vIFqK6!Ki4ia zoKl%_;*z~l=0(68rF15gX#flo!>X|^oc_x_^N&|4HG`opDf%~E8}i&5WeUz(np$~) zbt<`^DFQ|SkJbd5*Qaw{V4t8T?cGS`i$OyGeiF@_h3Dz@FBk9xQRGJ>>`WPBl9t_Y zLzFUCJkf^V6D}?=`>2K$Mbhd+Q@ygo(QTVUE&8+6KtO@(G4dU@D6X=WQK4EtALaJc z>1@0AMdD3L8MqTy=n+iTELNte*siKl7;DIg9^~1Xikf~RTHC98=!IpAd+r}$&A=_S z$4Pc5>0oOSqZKa@^2Dr5g?IMj$?3f23uhiF-}9Zp$I%ZxKU38JH&jx$0UK68{HKnh z6JqT^DVx-13}GM!i7z=q>IDp z#%zUB*Lv!{Z?(0WzC;$&{%D38Fou(8=z%)edK7{QFu=G@7)~ia|M$%#`OXQakgtIZ zPS4Cz@~kk&d@H|OVc)HCTT8XbyZK(Mn9U)L@@S?(OI>D7pWqWm4a#QuS$^djuJ92t z%QaA7)#d*@uXauq?C?B@K!hqR`E{=L_Z*xMsBcpQ(nW!*k`v&A_NW(YzBe4>XJaa% z${VFW?Uk$_ySxcR-bC7{HZzogE}+<>+fU+yU~>4X8Onnd zkEcQk$YtDMO<)3f2r5mFqPa#X$+PaicEN%^sJ**k>yi){OG`8&qEHWp0T>SA{fxLu zCGIH2ntbe63~m>cx8)21v&{_^V@f7gYJnYVY4{pk_yjr#o!VFUH37w+rx~{UvGx&j z>2)n2nKR^H1YXRGqF{gaxtHn#dt;3x5HJNBfJU7TMp2<+^UdG&yQp06s9HM|naU)P z$c99HcX&C>n#Kv3jsYeymly#U$GG5fy`G$wcHZhNjXx>`2@@U&#XY4-G5 zS4N7*?FHD9+2V-KPjJTHPrP#G&mVXFT&rLHyUS-f$Y+8H^c^Q~t8t_7eLcKqIJfsZxY^6zZi z4^HndeRYhV(vum=rV?6PM z5dxR>u8JIcXX3n@z%T$8Xu)$`<6aeS+3hyynk%JQM5zc3OxO#(DZfZ>iTX^xSLj8) zP@To#10x+C@jluOEx#Ml#h%Mstwx{h8x_ft`@}~i_EoA)+NzCJs?vnOrK6znQOxA1 z`donFvJgP5G)k>BCRe68<(ZUMnx>Xx1uJLcpc>uQ0svl{5D1C_@dM-w_#oJtC(j+` z1=x`0C|v5Z5MaR8+D3_`Fu`}c%qJ@a=Nz#cErOn<~ccL#{#hR z?J>X4ll~UZY=#0$T)l;_KNp|j#@c#f_k2zfhB2cgJ_ZkHT!GwD9n(FZHYyU7ijeJ^ zcbOKEd+HsJkRou40>M7MiK1z#E5%Kr{>M=PLokgtF`$pk-IFKftq2fA6#|<%5||V~ zk|vSh6ZVBW0Qg-~K!4SVswWzM!Zj3&L;5EpK=f??fX90aQ6RC7xIDfV?%cRho~nh) zv50+HTdWimOC<3XgR4KB_LixUvA;m1Kh3B|82+TkB-YB+)EFLBim7i%jk7%(Fm-gY zj!SPXec3l@!#9P`FI8m?*N6wp?&BmkA8Dpny32~EKe-^iqy%Isa`qD86L0_kX^vV; z(5DS`xR3cB16yAd#~m%jmU6J5e7gA7fAyhQGx=fi2xKG=&Rra1E%Ai;7B(8chr+f8tna zD)|_yRLKp=9`tdxuZJvO#Hzm5H+p@VFl4u;9L`TalqukDUDSsN^c8zvh;C@s3*_y} zx3!fz=Mfmpw3gT=vOxPd1ZS;;Ih=Gw;;5ci?3WrpWJ`hpZYmFN-&PzDpVt9;aEKKM zA}L6g40q;?UwS)nE!)m7dntxpc=w=VLf5@O5j9{(#r5N9K8d_s$>8uqYA;k;DfP?a z=@0OX7uPa)ZnjI`8jv28%^RFMbNg@*w;WW1h1Z<0K9mUbHVxPFL66Bnvs)?IHyz_C z7r9!jH3z|(MAcFM;Gwt&;{i?g$c+LMk8r&{4~=m*)d!aFa6)|Y}emmKDoijcA;Q)9-W?1!R2>$*T0Wi(g@ z_b{CAvHEflcE4aLdz5Wb5I+HX{5!UywXN#QSa@^Uhrdo~Z>1i)s@Dp+R7;`W|D6&; zV7k;jYtOZJXSc&?>J8W1&p5lj2!DTOM*S>*VZ%Auw@=#7eNiVbsXy6LXTsw{cRo}j zJDTJ^9=eW!PpUT?bzIQUhmlS4>50bB-{-D7f`k$=3M9HbqR`jJSW!9)0I2 zn%_rYy9yq)7(c;oag?AHhz3+QyY4-ey`!l4vnjB@T0`neIx|93?#j^=`-n-!vj9S# z?^*R$UDyZ3Q-bw0jO65(57y5ojN>DXqZf=C5}#FUEY83Z;_}4V;G_Y(whYkH8RYS; zQ%e-cxbhk&p=@1PGHV^RsHpzjvc>j<_%kUTn?q5@B)go&NZ4*=tiPQKP-j0^FvLDh z%GqymeH3-6<>LZHqv?)DWPF|&i|pAo0ZyDtTh;bFTIcysqkqL)95iV|<~^>Zsr(|B z3TEkT#0EoBG6q8;_HwW1cU>&)YOV?RAU8U#GYVQv)ExD_-~>H0AlW$g{_q{gBX?B$ z_5zsg{9mp+?GM;ZYV#+3^SZ3(d*%1LN4QH^l6Sj8@Rw+3`2xBaYn`JRu{}lb86Q?!oP`61hCa9q7zJ_ojqwo(TJ`j-`mrK^oQSC-6ODEaAr^|PuI(;987ISbZcQt-qTqty%IObTxlxk(*uX{=?g&}sAOfQ@c~jne8+ zJQO6t28#3K4Zu9SWM!;ANP>(cZ6kT}5IZ2b&qK1aL%67I`>%_Xv9$I2&+^Tkk3No1 zoqICLzj6P0)ghlLoP;6}E6SXt%z)Wc(JOAkS2T?_9(~(>teWU$^CQry-|H?-zW4yJ zJ|sg-ms1|Xe+DUuu#qVlv%GvJkNVd6l8iDJp8H0|9*?xuNqRm*%i|k02}Kq!UV+JV ztyyv#!;Mx|lR|hhSLHIpjgvy|+NujcWmiBP!c}$;ByU0S7uX+_lOc!Dt#^7d&|C-F z`Fn%k{Ae1eBZ!ee+Xnz!6{mwnK%W7?2Fdf{5@L*i&v?N_7?yn?*1cWRis!prUYz;6 zCKV}6rWa&_V&KD&&&~kjD8{ewa;VX?vL8tcV_f^0weVxXE4ryXKX8pfH=iZKcVe`Z zHDSN;a#*nPEIF}=t+Yp$m%%IXLggH-mDlkho%+Yp+K=50UglMYO!WQ{0l?{;$hCT? z2%E2X;ncAku{Szo-D6+?G0%mCeI-kaVV&N0C!J!^y$JL&R)Lp$aBEg!B&qO5R@(rp z&>d9#(4=I6Rr1QD^gXNet4Y}}R@weeNf?$Puy0x}K2R=iTA?;jp=(-cJWy$6TIDcM zc#T!!b-d(0t14!oCfT%Bcz|)twC?mkU72Zp?Lhtc-*r6SzYsK#=UE0_8LE}3+b#SN zboVn{h2%2XeQ!3;Ah5Eb-Y9TD`lKGKcn&v+;~m8@U(uG+oso@&p~@UDeH;dqgkjjc z*om_XFkX@2(=9k8lN~04!&DWJP+;CZ{tkI4l60b$cY-f?$k}xWQ#IV`70-O7ey+&+ z(smN|L3)V)5ETqyu%IwDpo?%6CV`3bqz=r)m4|kGQD6`+6p5jzRhZ7;6Pd7&X9T$Rna6jzvqp-+-3MIs_-%XSPxf+6^cwG8Q*#`A zJ;obr9)=>#jM{;-ks(?ZEx#tA%Iv8u@U4fd*EAN{N*@Q4cR~g|WUhScFCFFa2dm7z zl6hOMxFzVASKf3tTqU^pwr1V6-pStQ^1+0mF%nn=K~YL^0H=_ZOsvN)@hF;Di%LLy z&u}R?S_HcS3Y~lojziquL(gTPye7j6Q0p6aM_~wxJrj5Vu!V@X+s$`*F<^zd=P;DD zuq#`^#JaZluR@&}-W#MU1D-{Zgoy{+iif#zAYL>@5od-J`8y{7RZzAp6WMbxIXEu> z6+sO{i*ptQZ26YjW=@QGh8 zXw-7{0qN5z=-8>er89q?-}w9Du9ReH5f<69BA0=2;a2T!5l!G1pulzATp3U9xU16jC7;& z&IAvS?297-@k~A#Y>FfI-9gUJdgG=P02k}6c|#D6FYI)5>?4t-dKJ)64d6|4t4}Nm$%x+A2<v| zX~^3@$b92hDTU;n@z)>mz;LYeuX3Ryf=8${f!8JXi@I8;fNY3((sQ-qF*wBd_@}+} zk55b;-1{y1Fq0vk(IjiF$4{Sh-sdwuehM^jlzOfq{qsK|x_9=Zv@X@?pTIO1==b9B z)K-bH0fb5;V;HGDh#KKFTw;vy8+tPVUi*)2U|!x)w3=X~#@48a!vneVx-a-p?7w**pIOnN^$z@LYlmVc!^RiJ?tn( zJFZQM@CSkxwzf`FMp037UooLN6LtlNYJi6%Tv74CwH0Vv(*YXT2RcJ^u*%{h{A4iT z(-4?9Cxtt(A^vjHS-}_(iJSDGqL)94gVWXy=l|`C zeq|qM#aMhOsjYcnFyS6e?iV}skJ+P|AlKa&^Gxi4VZeDXv`+uRKG#8+x)lnarNn9; z7rY1Ta=?>G5#xz9QKhT2vXqJrH!iLYDqi0zEbRiKt14)#Zj*Hy^%bwm9aj+~&&ENJ z#2^N=$kg~Q`mV);R8Mz-_zf>IYa_rkZYuAP5y*CD>g{bpBb{5_6>Ny84DOqO!2$5( z;Yv4)cJI~}Q1@FkJAW@x-9yovwhR@6`X(UElG|L|@CeJUGfbaHlNS5EUpo1AILhT5?4gDmHglBCsubO-xFFk|8j?S5k#~PDE3nJ9Aw7+DaH(pqIFUB z6-3noN($;)#zh4)yh6p&>kmEkfJb?qD=qrYzHstdhfdE=r3r%8d0_Y zgR|*6J3NH@nqXyWtH?WiJ^M?YI= z-8m(4Nq9#TGYOU8y*&D=DDDirouZvZrPQG9 z)Cb#yL$_P1p}ai#kB(UD2oV4~6cRQVdyd(X26A5?Mi;Vcea}H^alvY5cSjGoLZ1s! zRB;$n1H~@hcdjgS8 zrjwV$CaWqu8EZKs#r3dAfR_lhaRemlLqWHgL?F^x6h(`47;+&ZE{MdHpi~kFst$`0 z;-}YFJUP~2>bp;kq)_EQH{xE!r>!G-MwiZBm47QZ3>Af!Mw z%3N#to3G&Q#j(xeGe!G)y~o6_OI&pX0kE()Yr%P|V`}_O+%u9AL-18f*5TbTY~it! zR?9TCOqbb3{ly`;U_55~Sp~P&je)P;X+#{E{+ zV~!Bx62lWyBU3Y@)3RbRj>jI)iO(S?lBh{!S_-Y;XhCs$2_v(VkyT!Gyrw?8zA^XA zS<<;Ca`VN!*7p34&VsIb~g{L`mT&~rdVrDcrrcz#4KSf~`i zPl2H-k_wOUD;$CG^Yc=)dCJgd&Ue$gj__Q9=V*%M@*9+jX;OsrdX6+@3aRxetB;U` z*+&s2>5r0B>qeiQS~w!7Y;ZGs;MVP;yF=5XmGm*mU~AESCZrJBOCSvmA%u1$%vuo5@zaJI&;3VPa0nfu z<@xa3Jhkuz0NX3-j38$90Zals{BHxTM!2>1f=YD(zn>)k|3dkyetyf zt`RI1+PRpLoBjI_VBa#EBZNCGDiE6@$0Ib08-}RXG4qqzcB&xQc6WJoz@+wq48Yfk z10$8`%>9PI_-p_TRoJ}1h8hQ>@$MYeiOfCQShrT#=rk)N{bx(ufvnhli5RRi%tG-A zo6J%of7UNNmX=If49AP=-&jdLaXSj&ld|X@raiRd@+%eFt|gVMwYL%~ehjI}=k$g! z;US52vt%iytO>X-aukP>dDzy3Gu32q<9O>5R^)*9Y`jg3o)(JYphNVswjXYrWApjl zUS;#$*AFZh@H!gTUrmE$`o%-Ofw4Wpq$d3I3JJ9$}k*uoVRyE!2fUL ztj*Qc14zDwH_6fpL>?S?`XAw~`+9d*yXvFsmwP7w6Gm^^s>Sc2Cgl{TpJ(5XZD!%& z-bpwmsTSgK_Y;xds-y}!9>5V2wtqmp^sQ)9tA}g1mF<8VO()*&;RyRS(y}8eG1HdI zogd$ry@s&!;-l`EcGcyGj2H}u5__l2#fj9&ESuZc=KYsTci)v*++sg|?FoG2!GTrxP81G|x|DxA01e;6fSkk#kXw>4n5rWVIfVz`8oG$1uabCTJ*+4C zn`k_q&v-2q0ahE+)bA+p6|-`S%N~{K@f!9S>(Ai3qn?Z3bq(DKC%x!wf(v30*Ze_E zuAI>7;$sDY)o*Z#mjV(kYt17a>i`V3;-}LH*;{1W@^$PF(E-NFLQ;T$HX*gxEyH#-ZD zu=gKvS|r9Akn%x+VpLJ4gQYGVuOT+?3}Df4V^)JybR0J5kRn{430f6d0ZYshV{Nu& zRShR6Dd_KTeMbj?z?_^Y?lmjy#IyCUS=^ihD)PK+;0Xj5Lpr)f0XKB_>9a!sXGzDP z4bMBe#`&_B=FpQz)>-CuKi)9}(TpXN^POBGhsn^jVCm)=?wM>4VI5hM7@m(qcZYvh zru=&F-tu1aq`u&_nj1TZvE@->i+o;u=N~|DyZ^9tOro6qGf^a$u)-iJ&bvK(lKNo7a@`!6e=9wVvi|x|IQdKQ?6xz~J zY&V{7k8@c*(zYcu-&ba*W2T277zO|}A<5tT^W!S%uT3z-#jkV`1X1FA)uGrex-(aM z7&MB6qRX?Xz@NQ!)1A>Y?6c_;Kd&+StvL(x3csPif59W|L8m#DVmWy<=Oxb>9hUz; zA!1I7_}?w!e-o0*ntB)=JzafmRXuZ!e-(+%{#_*gS9DrO<9|bvhNcJpBTB4qW@e^t z=E&hB&G9DMwtoMr6aObFiFFJycllqgq@LU110F|=0*~PhbpA`J*vR8wrD6^+X=d$Y z^?x-c?Od%7{RcDYUARxswMlfK~|frq_= zWBtP7{r{&x$w_4Yx1e#BpYwk-jsF)y8Icqe{~r!zR7&Xoq$p$3h_RUw2`3IG<;4D* zrR3y}%_3i?NU zK2r7H-Ab3DA-D2LLiIyJ?c>bnm|Mxs{`2Wr$U(1dCZ-ynM>Ue&A4o9-2 zH=g=GnC033#4PhT<>dc6W?6Wa{O@=&qb0xQa#7utl5_o)|F$hTIpu%bmdxu_%{OZ< z-md=_a(V6l8@as1KGQyYmP1|kPPhI`yBvD;Kefx6CmikaU$N!?U)tq=;*rEN96U19 zu`Wg7{~M1yx;9k6uQL8W@W|$D^~yEZCddDRN9H&htj(SFQTHJi#vt(C2D}?Ro z*bJ?Cyk1fifmc6zz{3@uxUR~ePt5i_%g^Y;Foedo(_HnM4WWuf(UL3UH2y8ZX`BTi zhT}S>dp%%imaa$G3Ssdm6aQ_!Njb2}%01;ue?5Y{l7GQPoSN&Jqry6f8HyLg9DN&o z5~GM0^|OppEYtvRHYMpEWuZ!p)=UV6yw-gRdJ`-6aK#g-jF`e(+J&wcz<-%gz0o~J z?8SR%8&|4_kjVSS4Dcpi$&VjRkUs??&=CN3e-&X-hS49!rJI32*$@@=3xoqWWF+pw zqk@Yy!6&%c+}@aq3GNC5e5+C=TK^Sp`OO3YK7n|T6uh~!iPVV`Of5Rli{@er-dL}6 z(K6t*h3MRQP%f5gw9?FTiZ>NiBq_eM#&ZF`0B6>^0CT$_-MnFc2 z1J_Fc_x^yffD*ab$T4c?*a|i6_LlM{M}RO{_r}|O6!_jVD*DBd8~Eg70@{UceAa`S z#C4g@f>w7@tySp^Zhqx@+@lFx=oQ<$LZBEy$?lYwbHxjp0cFI7`FRt5+Td%^Hk)y@4yNvb2}XI%JJofHv~v``~bOn z3oI;yW@zLo!F-7hP&sgE8etW=i7p03IhemVOFTY(@+Bh1&m17)WnBpl%0E&3fGu10 zq{)z+#QJrXtw1ic$w6yBIJHcP_!7NR43+r$Y)Fm$81P z%E2>Q4G^5P$&KoVv_nTo!ZNG$o)URpf<~JnQ73_QIUskoj7soAKVKjBGjiL;2}E_;wbb4!nPFE zLlyxY=|AZR2colIN503P06|;c#A@NSPNxo&YiK0x82by+Kq~JY>*!&@&49DNY?-m;& zyy-~7>92C-F~l1mXv5RZiEcHa;}-)zIV@lADZ4&r$bP=Akhpq}6ghw89;E$iR>_0c z*MDqZ`kH8C{Bu=e-KkDNOO>vKRzR_mQbqzQ1h!YU_=GPj2?CB}v!|W>uAQa(WJU^< zsMEMway<(zHr{PkQTw#Nbp`TiVAyoA34v4{s9YbL79g3R?)(w$rWiIGc6#`eWoppR zOlocL@gW= zKmP=VqyE^HNZPt!Ss;fnZWCK}|K>Xho%=D8-WaCL$z;s&Z(34Cf3T6uZtLvrSe`Vjce8PJ*E_`yrsQtrS1oC7A_ zJ8dPtpp#4m8wjGx1`pWOq=4oj;Vm0uL2_ww%2+UMVvw7HQT!2kach71Sk~g{ulFQY zUi~9>)b)k^WS`9ITBXw)MhRb{{H*KI7K{0sx)lYCU1zis$sv%@DQovLKK$aC*k z)-wA@exL7+nM3-wS3^)WuXgno4rO(R@u$^G558;rd)PA4>cW$&uw@yqV|GO6DK|@3 z14B_(HW&ZQlV53iyeafM^49}DE82dCKC^$ZU8_J2;*%Ua*uMYd(YI_*qc(w6Y~Ry% zBoGf;0akF|{|LmxGL+dsx#MH*=bXwL2z}v31t8z;q3{c3TI(5&ce>y2cG)iHs7=tT z7HvfNL5*vHXTKSn+&Cc~Yjp6v7bgd-p=373P8_Vrg(_r1^q?Evcsi3C+{2B`0Bdh! zwW~790q!`wgiH&zl5?FwWxeQ)YI84@f3j;d+0Bs;Vn+YnaBqt88(aR@{s(w z$vKdw9MQ!bjl;l-F>39!;al;Xhvi~$RT{^nTHfE3uwaP2RHHw9cQPA6j#H58@uTZq zNO%!L?iwZMvC{lUPI^p84#$Wxo3yRQ1#sz>%fzUVAW6dk1eo=O}D%z;AL<4 zIL7lYDNF!gbX~$lmZ7}MR*rsMG|CR*+#GgiNr@{(yX*wsRm7JRAEFWXBZ_}zmTP@8 z8m^)O$8vun@s}`E;SBB#CO>2+{43oNtw_(4Uzj`R__ zrv`+$b2vx0pKrO_*|t|;iOpB)7<6-@sL!Na^mmlJgtyf!pI42xU{i@7t?Z8@ipJ(& zNAYj@!@i*S836xv2yk+R|6?~dcwZ1y&*Wd<p}4db+)O zsY6|6Zz_0|#Q!mc-wcB~4i+pSOM6TB11UVeQuuCc@={b#@eG0OO+Nuh%{EG4YdDT_ z4`qZC_>C2)g#j`QzAa9)={t52C-7zy$v6gjMiBT#gqHaToNR}W`4`=2LrqN-KU(B_ z=udp)Sn`x5?Eq$UEfxe;7TsY;h zIkl!U=Pm`k%S>v=h>MpWbSMvfJ;o3X4?P!P#AhrB+%vrY|M2uK{!G6C|Nna@W`=Ff z=WPx-8#$laj2xnzNyw>^5TfMNHp3iZq(TmJK9)n0l$t|1q(Tx>%^@L)j*{}-=f3aX z?{C=qvB!12uGj1Nv@7wrRsxLzFad!4zEWTeD3t*9=>TZXCHbxBcY7yFmi5cfTLufK zPk2MnAAS?i=ySrP(pL@1NAqL3-oyh(%LVs&{`^gNJzTcOhvp-e^RS?^BIt0IF+?*fuDEN|bmWXn>ROrLD?>>Sz{^c6Ar-(W!z~K+!u@wg`*`0%MD+(%rdHmRIc} zB^ryRy6&KoBd>aP*O+?9@^&mNn_8PbVvWO2ZQ|Rf;0CDo9>mUqq0ABIo#m#BMpR7q#Yp!2D6V&2pa@L zY$2qq5)t9uGX`xLCXEo_3Ptb>V0JnW(Cgq!YNc_xvK#_5k=5%kCdj0K3tMw}*w8CX zK8Gp=xK!*6Oc=9n)_KWBg%b5AA(C3tOo`G;z(`wNY>nt z&gKxR;<1lU2eA1#;DMy8L;Fs`sBD`9blkz>8=)rl*oPmZqcYEi=+gH@9lLZKrBmEA zp56)u3wb)7_joDTf&faecXbp|UQA_mY4lMg)G<-@Pi@cjrqvRUq5NPTr}R;uHBhU& z0qZKy@HL|_gE8=1){9Y86Z<}_4e&(*)l3B20i}Z-2G0+}#$mkDkUk>d9of_fKqSWa z?;V;rYKH6@=i9bNJv2pqS3{jg0)9*c{~rG^zb~qpJqDp79{2-B#8xRyl+5J@P4hvp zPKWR_)Q(znFJZ*w%;bLGmzeo6w#>NQjS8^4j56aKP($7L&@UPEbj z`u$-u_{Q6zQQ$8WX*3|TWkihdg{>#HpMG*O{3jv!x<7{c2HvCSRb^#soze<1amga??bKIwAUqPr!MI6eMun2US5t^qC+< zHpc8tf;!iS}6grnkxdm~Wu zQ;@bxFZsA{_tU%0uiY=is`13SypB?P@*cy9;r;R6<9e*?+o}LmE4Q2+8+e$PNx2n>V`o5==#` ztD^#dg`$#$#ha~D<|u^Il69GwFLsJYy$&xGwI-D1kk)@5s+GMl z{s#lt4K+lB^ctA|4L^bi)I_#2k{6%$EFR5XEhWCZGdecu3-md)3}=A)SZfIE14pgF zoB!4zY*6t5rHkh86RMCzzcWE8&OAGNQ*e_as7gS|6Rxg1 zb%o1;=GlT4+bEOaD;B?}fyj@c6N1YGzAt70B;x`HzMR8FS3dvSNP8$!NBmUrX1qoY z(aYxV1rUX6;GRhR3M`V%=3n9m0#L|nVW6nQCZ)3by~L3o)|%8Fe;*e4h%$_S&#&$X zs^8@A*=t4kh5&VuTNRP5yyd77b5x})G8x$_^8z7P{s~Na3$S5Vk;Cvdh`124aBj&U z_uEj@`e@l^p*?{7vWYtK+3y8rsw_e*uk7P*)nE6}f~*D*3iMT#^_MTTdDeY*SRb?# z6DA}kvB}S^0oOz}jgdiJ&In&1^$Ef}^#y8Q^7i`bh|RO@S8r!7o*g|=wJj6?WXhnS zM3C1m3PG9sbkrc}g+68PM|BG(Kt554T4iD)N!qFM6aYl9tF@eMSOt(Tgx*(%Y1tEN zd%=JE_zwF$rtU~pc5%&#P>>&UOK%<=;9oz~vfC^Fm#otesQn*2a$3HwU!Fblzwk)A z?!Q?(ZA(J`1CP|+oy^^toBg-Sg-6Qo$>IKmul+lszPDYzw{t}F(~;du#DDNenQ1PM zi_Ryi6+AsQPiCP1gGc`V7Gg9G34sW(H@&zP;sRCoTfq%&0#+$PK_(?Lt;=M{p+c*W zi%}UVW$Kp>H**3(b~K(~PCcpvF9;K=FsbR*wb4dE)W46+x2}vg{;!32@lX-rRqf?C zY{^|4rhzi&G)7i9Q`*v_I^pYvm3DJ9{Lo0KyNZjnsGj}|{~&qrq>^h42-#0U_Im3f z&EmeE8mq8wu{w0_=IKsUddHjlI(Z6@;*^vrdxpIWpQeTHR@1h!{ImUU>Yqo%>bCbU zR$b+{`k*u~?j~^Im!IF;o!Ir7@UnMhQWek{u&ftJ9B0X0^@{{#utP8}KwiqmJ6NZI zGT|I-IN(o28=Vtbt*TI47Vfp!{l3_8z)HDyK|2+>5`?jBglF*Ca4uTqp`lWJXa|uz zBKF7hIc@MBAkvD%$P`uQLZCaWJiDI~L@Y!q(Ww(I(%@`Fk0v~Z`SExY`J3CpY_L-& zm#FH}j`!zxwY-M}kX;mz4*w41gs2}eDhG$VDWdXJ({8o>5y64ZJ06 zf%vf& z!m!&{F{ZF2C!6FxMskWP|eyvYZI|S z|LdPyW@v@xA(%K5kq}JGI}mRP6Em$);pBUR0|HDP=yuG$-q2`11xZvdLe+Ih%|ZyVj`hm#eAZi3$FVm zh)V^Onx{D}5qW1sM`;KGg)-%^YAw-k>B7qL@Yuo^HurhK z(zKX7K)SyzbzWr86;J_qU0r%Xs;TGwVQI1@Y2KlYupJDKb{chm#qF2Lqku;r1$6ar z^RhkGUCtd2@$X`Q3Z9C=YxyNS8l(Yn4^^0`OJ>Tkh;DH;R+e-JisvxlTX2MRD_=d>jOf(!9TjR$UOPZ%la zYbaYqJsjW!r@R!|E41Ce%|ri z?6`YR{T4o;J{5jY=GstYPDJB8jp0c8crjws9_wsXMSP&#zksYZe8j0+K@zgvtMQ^e zfVJS43~GP|F^7*-ZwW#bak1U3rZ~|B8d)i=)$%Nal-*P?DMPc0sT6f&fGvA7-F5@B zBduhC6=O?&4SkbF&JC*^r4+(mRm;wcY0#3cEsU3vsn^)fYIpx_<*&dzy zMGxI^i`=|?YOSO&CxFg4$TFkRh?uo-wyBS_Y0A=m46u3t1qaCS%Lt3lgCDUqySk6o@y$1o{1Y+xq|D*`isR33nAv`|5uUO^>nLEeuBV&x&G+vH_%FLwB} zVOE3tW;4LA3R68lYWxgL0eV)z3tEOu7x>DaSjfS=DA z)|B%&XH_xV@RH|v)~&;F*S+7_B&6TiP~lvvY7~rPDYWP!c)0~$*0EyDRWzK}bT-PL zW`z}ABMutwz&O7&D`b}o2X^t3jH7qSXhY19++0PDBYXh;dHbM(Zrd|qfA5zKT0a`Z z0|A>|DcLDCsOcCw$7!T^s*{KPaI`H)!^x^0e37J`HqL)5fW`elpA?3fp%5KliC!;h zOBG7y@$Z46h$KUR($DWYi5lLsmm7Kn5v-HtHtX5bA!grxuRFoX^VbE)g!tY}eI;Xw zN@?3Z@rUDjLIbczglzGH5@UXGhp|q_thM7oOZ0+2b3QMr1aDHN`}dhL!)3g7861EC zKgL2&#*AOqlC&`tov^?Doy0nd<_-Rh1u@t!#q)t)-GQ4%d2A^mV5ZyBbq!q6{Ys(W z-7PTsGgy)cjVsNEWtU<_n5JWw%21HGo&&@OrXUrHN~Q!CV=ie%-W817P`0*9F!p`N z>++)S5VS)`C*lkewdmFGdF95(MECq0RGpmScc^4ihsmY*Nitfr>A3mpzUY^OcjI0( zA2%P@Ur0UX?(YEY-J{-e*-PM@TKZOb++-z7K5OB?DwA@+3y@!bR|Nw48!+ejQxRpsnjuHjti0y^!&7eoW7TK&J zib04X+KdGFQw)aWM63=71rxV!vkQzs)*PZ7hc}Qwx+XytHUhQ2k(CN#K#6y~YZ(%G8ja|-{^_uw40_!QpBmY15SF(R3ng@6Wz)PWM@a9W`i1P(E3xN?2&%GLWKI9z_p zN}9^(z{~#DQ<7q*b;MpOj0BU!LbeXn#E4Z^i=EM_Nu0T!9KeN2ibI^z)90Wb!C=0o z4q<@Tb_i-KQB!cV=o%UxNlaIZ1UI?xTC32{N75pPY!b)Clbywzp%Reo!g!KBHq^d% zps3f(VL)%RPXw;SNlP)pMrYTa51|PUiBnaspR>E(5nObm2x=!mC9`Xz-K-iXpUTU? zHJs9aWYEv93R!s3)CoEOkbaIuQ>XCmWdax2x#sn>Qwh)ZrV9jSI~?cST3W4!gz%vA z%*zitMs_cjPLKVPnbt@9xOX;+!`eXz+pGXX;B!)lQ#!^N=u?`NA={sEe55M76|(LTTB0040f&E)U?Xh}4}C zIwg-ELXiJ1$rOW8aIIKmyErdULnRDS$DH?4*)&^#;%Ey(v*l%q=$8J6JSOQTZnvx4lf=zjS6et1%PMh>pqKYg* zkBTxf?5l_?R$U&d)Lg4nSkSO@D{!CYcYlVwt(pTvr45l8^3&-_V_rMTZ4oS^2iIiX-LDbxa z0+FzNhQ83VN4dvvSFik|vRhtF|#Zb$?+whf-Z6(rO$g~F@w`q6ud}$7Gw!)tW9qO&A1Py@L%!JQGKh8SW?!ml&y)y@<_uLt-HURI8orrU8Z>rAhwFdz1gT1=p;p(FLO zBh;~r>&QO37p?D0lK>rNLN8xzVvhN~`|-A^@@TNG-*7f12Dme=uTE_RJ7TFWnea;e zS@Z=AYT9%< z>%p6)r*ABltklgI-|SodBKY=TU<9F^mBvh7g2;$~xLvJT6r;7xSS7mh1_% zYVWRTDMmV+be)6!p)@CG0}Sni1`a1M7n$NknoS3(@wh?zcpUzRS z`mPFn7SRw2m)aSb?kvS1Z}UV(rrW|ZgjPC^NHd(CboP(XJAHW7-CNE(rNiw%*d1ob zJ@Nf135tdDMvf6W=b$km>F`E}7^n1eUK~2>F3*EvT2 z?YrHF7&pcE{Jd~CU1{rZegJ^H}W& zzM&6{M+Y;YE0FUlpd2Sa8%SFbZoF4fnmR-YQW@*~cdH{J_#n^fupb{}8UkN~oTkIk zPEcDXXn#200o2FKMxU>8)rMX=mstggf{n6c;LXvnZ%tvN57#9+{)0z`zpq{A(lY1& zg)eADEIyZ=dl%k0^?5NO;$w0|oo&QJ-!p4z5uYALtZVVDzU%+6v9bOyVpH%eV>%-D zWW0*O4r6mV6$3=q~-K;MVnKpHWfq=t`Nd5}u<9N50~Z zYz;U>D_n|JIFJ@jq#0m&sP&KtBG`nJ{+v8{MrnZnpcr}3OFOn>g~$C=Y|PyQfZ>E;weC^uuMKMh|L=`00%v@L4qcZ}KCx#hJ_n~yU<@g&IUe;F zfGX@l)Y+6vycCLjTwb~5&eYku_~1N-fk&0W8HMGGxjyz!*glsh&OR#BeV=ZEVkFC7 zpkkdU2Y%%V?QE!TuX&uIp-x{B<9lG>6ndohmRNlKqtE$5o2xnTO^@QAE9|6b$2Wh8 zZ`r4I(=fhmFTPzn;VoanQ-iqM#}hgq33j{)zjHR>UiqKlxCGGq`0f`8Jwp8VTXx#s zCvYFyudMv}_T}ln*;FWFG=lAv=Z@uugc@kUr+-NqGhRnaSUhy%-@ip28n|m zCqS0v845!@d4x;fKPGX7aJ~$fuXqMuOQcNiX5sXv;9evZfSZ`DVXGh<4hb@(iw#@Z z#0@Yo+zWHa7XC@LlwtJx%9g?q6K|8Rae9IRN>@R5+7-6XkEF=@G$!)-D^m*Lu?v%S zuFFU0S2&l6^()6REj9}Q|F6%TGaZWjXOZ2vLk~9}J!A3x-c|Kix4e3id(_0jraMAG zs6X?BYx?+eUu92+gd;7xqF2F2`Cr8HPQ93tdy}?TEzp({+l8y>&&6Sq2a`j{=b6Xu z8rbh_MS3;g9v-W-xuO-mqqjEM=y^(Sj2h67l=bV35sF%Cubrg7l16#8(K^y}n5tS_Z+dN0`glO@J|dE5m4_&nKc=j-R; zM+eH^CH4oggcoi{Mf&;jqR~gr{r&6t8vR`+IK={4zt8o+dr7tshBrJ2h4~}1#AA5< zUeuU{y^)neYI9K>K2x=^3!kN#8BF96G#DWAbbJu0BI~W4ugZq6ZcFFvpMnSH^8SsJ z$+oum4l+Yq8OoaQS_8RoKG^Ca)71AgcT%agcd`1q@%iDZ!u4=IpfDJ5dii>g@=;}n zW1X*1ygNRTSj$(1RHxDlV8H+QSD6^9#eVulCPwPXl+fWp5eR^JD>Hd89f*910p2pE zopd7ZZL8K-WSXco)CAH`TN~BH=QWDlG0B5&I&22JI)mBOS8CTxFtRrU8-;k|z?>8_ z1GLn7IUIo?mZPno$erTFUGb{E;t{_}D(;IJy?8(Vk7n0=rnFY$t@ij^)-48;Hbyfa z=!fOGLyPAzpKSpmZa(||S?dELL03RuTS0)pFX*;|6oJp7wKwVQxjOY3+X1!(k0dZ_ zka2>Yd+ZgMxO$>6G;2&~Amp+3h-%Z`1Kp=}mG?utwGS*YOmE9|SeY(6U<3zj4W8 zXZ5{Y@;Ac$v$uA(o*p>x_tNdXOFlnKJ{~`?_xI1v)!H}mjE*?Sh$`&25(JV zc=Ns#jBBl~CX z5js2%aFwzI+c;DT#S4>g&BEIMVMj3aeTqK;ihv53AMHP&7@cZmcMe>U3L6RSr0U-ee>{SjPkhxQSA3ecD>QJP27+0t=bY3xao|Bo} zwkR=QP%Im#vA6h9wA4TJn!)O@SH$V7?Hr6|PuP&tI-G)T4iX|eL1n^Ez#_ePWtc-y zMJW&kx{i~%u|hU2aZ8P?W+T=kF~YMTJ`w*wjB!ApqHZspWxWJ_POsII9m}ym&Py|l z;6{zaCLt$~oToqSekb>}uNbCzU*jn${Gu3LWSNkmdaP4z05FLRl8%i-0&(oiK_UIl z#`6bdRlYfmhA4Z>PCH05nMU%?la&&_4nYKyPC-e4Z)l7MV|D^yX6jwD`0ze)A*hO# zcFJ&5RV|rzTcJ{Fu>RxG97h@FzBB@;V{W#inDQ)wtm!6W0}V`FHgzJ!b}!OHps$pbk5K5T4#?=54*Z%0@`jZaQ3D;r$yj ztm9XCIbyOy$-dr=cY7X(3b#*v8_x*BWnmCU;pF@subSxvOS+GTa`9i)uoD++6<(d} zJUV+h{Ce>*d@rBfsd!tXRt~dKF1QFjOqZ95rdnx)lNG+P z{tI2h)(o*1(~7{HC@^6>#20=|_@kv~w^yL+S)m{AjPCF~WVjRrT2D6NoRe}rSMfmDefd(>#_rb(;bX>B0M*#ec7y9rXV+%U~ zqeQhe4y2W^WCi#rro||#0l6*OR z>_s$#D*a~`>iISI%@A-cAc*r^dCC^K{dGm~Yl`YeMQ@-`toraFgI5N0LC@#W>5FrZ zgtDe7v3{QSQ4_o|!Ka|AcHDH=pJH3DFRXshHcJ+h(K_;UptG;7?2!)8t~W3yAZcj; zqQD(xf`v(DpdJinO*b>(LgNRop6$zhP5|~DI}AT9$>iZ<&^@5ZU={w(MK9pe<$bT|#8Ki2<(W zeAVLH_an1Y-BW4FF^dw}!z&gl8>)aziu`UX^3c!m%SlFGr!KAAP?Y*ovM&TJ%5Kl9 zT~Lb-tTuqmO?zj|Vx#j7?|)oP=Cd@a^KQeuHr1~ey zCgJnNJyQ-uKELPg-{;BVTzF(l90OIBh}GC#P|)U1C*Q^A?p@ej+TU`q%kv*KY;bp( zti5A&p)4)w#E6K~G(|9ZIn z?$6)P=6fg^1A4ZtkB-!;1O6lD3^SF!w2yo=+xgZ+fidZXVLc|I1GBnGk)C$b8G>UX3-DZuhpkNGk2SLRk=M76+lu1*vi!uxEt>kw_Y!&g%sNb z?TPdN(Q9#uHX%`SAyWH}#K|5N%vgvw_l$A$JRk0P zA!&g`wB%EwD0p2lnG^O%5|7thQr1%h#ws;y*}QJNfKRV+YYKkzvPpMUVpK*_MzCqV zVv;^RW&R*NYOcy@nuL<4@J$qFk3`jDr=p~4A(t4`W*TV)1y^ME&jeI{r-kyEZmD;Lm)fV30HeB3pXBA9< zK6`7`;ZTx|-A9i}I}fHfdoQPBN%q9)CsE4rfr6V#<*V~S&#+bx^Q0esfAa`7#}<{b zl=11)EV=&!Yf0%>W|2)DUcqCejn-#K1N$sRLX2cW3MQ2fdJOW>Atv?x#t)VsHkUln z6dF|;coy3qOB;NaH#N9kqVRa$B}XPi1acsftP|3o9uo2t2hHkM$)=q;k)trXNj8iW z;H}-q^q`-5D7Rt`opjuzPZ|$Qpk=2tSFM(=fZR$!OPMgo)0R$FV29FKhU_%SYUZfZ zokY8H4)kgFh8dV@ap(Y(M>gS>?M0m%gVhZsBfd9g%iKB{^8WQDc@rf+8|s@^=i3^} z1=T9j5G;Hc0(mH$)z8QB-p?~ZacGIF)0@-f`y3IvGAWI_lq$U^^3;Q} z96Iivi)Ao}$9oT>d^*21R81mzRBYiz&p#K^syf^t7s0hgSX|+kBN}b@@&(h#a@L6L zUQ?o7lO`QYbFX^_CChjaH8zq_ zBXRuqE3LrP7O=uN;uS&7{dMT)7d#5D#T0aS7IpkMk4uY4T}Ss@iUb{X(X*ev`oZ@4 z=jQ4(Mf(Nyca_88rHb#@#LS*onN0;*V?D_-eI~@$%4{-t0Kty>FsXY5)apr;gWAL^ zz7Gz6m$SF@zP8#dI-+HRy&d&xs;|4;G3Cb(+OytRw+{3CG!B9u+0m9ox6F z;Z195)$wa&HznzCOD~gQzM1ih)*GHryu3Vo{Wd<3LT-JF{73=m(%&p7?w6sZ`R+28}KrdF>99VI?Mg6jYIm0@w0%Ia6N z3OF2jP&4$dDQcS5e_N~-stMgdN2@?GZ-s+kK{iUqxugJIA671sBQ6+^L9&85sQ6u!v z#;sy#zRsKC%R16op^_KWWU{Pq@Rw!(Jf+`nk+E%K^E&c%WpZ~<%k#Elg>-R;-pL;b z#qHBwkZrF~Ygf?zDt$!_c0gCj)lYFy?RVuqNC(x}<10&7x210YL!e&Taf5ZS!EC^i zO@ze_sMfXTqWYUJMpx6Kl@HN- z{ky^+PEupDwc-19F3|N{Z8h*k-?!E#M~8TUOh&zc5@)H;Bw-;y%z>t0wzmQP zKvC`D!uR%z)2XUUY?2CKah)cHg^E)wVe1stuwK&8esfbR!!Q@a10Q;(FhgI@IN5&Z zix1%AX(l*=O&&muC}exKzq#=ej?$0XB-JKqhGIQ7lRKCWVjQwKkY4M_x3M#bCwUD zpDEG`xxREt+u-zth}9>rLlSOTA-|F6mZ|^+Ob4r1_i69`AhBrTK6Hci~o6-01GVcGhjjZezxj#l>j+^{+QM5`MV{_hRP|g6Y$We7j=)!H+Y>?OyfTB z-=wK-f(dkR?0vR&YoA&iNn6HB6;39U(5w}2C)cT7DsOvr)GGg{fSv*qqyjiNv9ETf zVPeQDF>%b~xGxbWc|x$+Uw$d2bVgBghpZD#Z9W!{jJy2K+Gnx(VmI?zKeKG7u~x}j+<;9t9*z-zM_B? zX&owm`5sOTGDk?bpR0bDq7FSsmu3qX)c2}yTT(PA*ln72wP`Phraelsr|-}PX!mRe zU;~!(51`VcY#9+jORg(%j;21gLylyt>p=x11i8fQ5y(!1FjZ@Sb}NdgQP>NcqiJtv zJbMpjeH@U{>(}&g1eNrTC_F{hQ-wG*tyW_J{|8Uxtu(@t@`CS^>AOSnbg6+p*tFR* z`2n=8gQa4URyN6ArdM-?tTg~GhXCj~+WsM0IlE`>We+9H+;81#Lc7Pxs^|H)2NX`v zySiOXvlRUSa_Q^cXGaF6ubYoR_JHXos-sWRlSfcqc#l-82~e;*T;2mh{(FT2aZI7z zrdH#S?iY}rv3>ELyMHbwBkL|9+ZWf!AJwgM{SRHAI2b!wwLUp&F*!0$#XW~YHdDTy z>$g4elZLZ;dcc3;!ll0d!Eqtqc_fJE865@<=-_fZ@nUWPBZGMs$c!IC+!E9Y&9Y9$1D5K(R<1GAjbB{PbT@z z-6*u&mT2d)<9=gTOS9;frQ4Oj@>CB$`$><2MVv*!Z=K-zo4$`io8$GHoU5%0!2bTZ z0^~k0pA<4%xaBxo=w?wyT+uR@W3(SaXcb#%3%8v`9m*Jp8!&jIXL#YCOnl7h%LdQ8 z$+8LOKfiI6T4CZ&qzSU$Z2yx>-t8A$?zIsSO6>Xd6ZPI^a_Sin1=x2reEhXp80b=& zV$96~PiaQX#f8W#0V}NX-+26TVL_*=5I%w#2bVOF+o>r3ngcrgkdZTwQwj@%u#3Fv zzdpX29$6#+?8$G;Q{;5Yg!^&8$*zw$QTfV`aA29p2keiZ9F-Fctw>Kh#%;});6c+T zMigpk4)PE6Zue>eF-Mz5(p#-;xe@ODbolHt>hvL{6V+Q4u(Hh1r|EHeh@tedj6on5 ziw8R~9=HetrWI=0JV_MSj#e*V5&LYVqadr2A?5pNC_QleLxpZj$;7<_kUQZ=E%;GUW9JIovzg1vfWwjd1skz-EZWQ=ln>ksUB<8)r?F~Ln zml&d$y{~uYJNBdF=2S<7{O+}59Jz_w9LC!8sT=42WE|=($TSwS-0bRYmFB%VU1yBY z92mR1m)v#wLirL=&u}^%hhI(?8IG$La}sCQD8<8{E@u~nh4Boh7gz8G$0q`8(Rdd1 zNwF4)z07Uub4lv0OJQ14_~wfzvQIGx*zz-nYX=`IEalmj*A(Bzz4TA`XS`yT;U}Vz zm-epSCR;xx#;K00$~p0{?Bf2KP>%xJ>j}+my5pt-{ZEPu?1yW|({3BzQ%&5kHWrb_+z&j>hXf{XUeK+FR^X z!t?0&k7dbf8bfHQ_kmR@PX6EydOW-7CZ>lxzRXB*0Lc#3puP21Yx7{>WM2FEynh!cVv%!ru)m# zmruI|1%&*PnR|81q&v%&cbkgmna&Y>%itH9ipFlDs))bhZ67+^77~&Kte+j?+v`q* zu+SiE9?)7Alk9|~2lQJb>qWINrvL|wV8Ja0zra+W0x-srGYNg3E7(r4e)T2#Bx?|i zWMwH;L`U1XAbFdeb2mVrutdHLb-8mR2}>B=BwZ20C%VXc=(m$V7u)>cNm3)#rJzpl zHP0Cl^{s}+t2)R4`?9>Wf^~=D-x5VM0#O8wq z_su!Ri1GqNFwRbVH1W9+u5ZLjQ>~!N=P85?=2z^UDBW55_$#EN>#TB&=I-}}a}xfx z@2w^bABRY^PffdAe<)HrbGqu>yOo1$|D{scvbFzGDUBgRqtB=DpZ3_f+-dlE8Wg}O z-OEkdBohHI&%>7xd63VGUXo*Ju6Nr8lfG9;t0xKS7(mfs+jzO3U8&n4#N04{g@WGh z$L1^O+>}C$ib3Ran}zvN)!7$@@>zqL5gEC=;Y#u(y&G0#q}+=GM-;bOd5l*oUZ;<- zTTyKw^RGI&20Bx1%$s>;D^WsYQCR6D*bV)`F$Kuzom)4dMHVuq-KWN4ofCgFX>A7N z4uq%5!4qzpF9gh<1rr5+vj&?0!s|103z9*HHlpS6jERY>Zi*V3D?6u!&V1J|e4e_I z2^3!ry7d8kIcfbS^3|L7Yb|m~O0UkWhI*C`7)rNvlw9pNGrPCu_qv5xVdFI+JRKU%UTW1js&jRdt~xhSWKdVD=RVa@cxqsS^3-^NmXof~_i#O!C86>|0F!c6 zlFOJh;U3zfN>lnb98Q5W(%=lo=@J?7W$@_jEk;0d5!S})iJn%rc7aj1!UY>g12L@x z>ReDiQoz|I&Ecd3Md+!r{opIRuj#`YrTc`9l@3_w^u;+L`Tnv5hZvEmt3IbpO+WP& z4-?Y9G(SNW+<8Im{Kotg=D5?=N_S@4( zE=f%mS5UFlL4f|;g4j7$bp6q{p`MS5FTb4Ws15KvbL{<|)a!dwKW<&@X?Ca!o?>tO zXwC2q3zCDBQ1oowKu>yh{Myfs2H)^A$KT9;?&HEE--e&PBt17T_N!~yH{x8m z^xIXBU)?irBQ89Wez*1R=j{)^XQR%%dG~wj*Tdg$&tBS-=77Yx@W_aW)m`ryq0^rK z3y+jpxEawA-F!u!_$huK{eSSt`wztvD1$6e2!d4`%5LmMUH>z;y8FFeyZti8AMCzE;InlL&~%7| zn#ol#&}PB#95x-VxQXf&ezz&K+RrEF0!N!IL30YB_>NnWAY!m&NE}1A5}+7mE5HFb zSl@vxaN!-gockR}iF884}8Ybo&Y=SYHM!RIp)@2bw0GbzI^VS2o>V#g9 zkR3_%8i6m;jQ;z1`ad$;IW?UX^aUL`D0jG(_e<`r!ATVDEc-cdY!ocWU3+Re97v63 zKm7Uo_1?eVXEg)H_nEVD%*XnDRw(jvs(2RwJqp9sVE7ygcj%Da`WCvCeInC)j6Ah^A0{EDKR4_ z29y2e2O5nPv6#64;bH)+v!`}fS;u;l6CCY4S3mWDLn{G`lx0)mi;dihyT__-p_!|$% zsk|_p3;2Qo00ZJSz(R09V#l^bSwNv2n}<2fKzAv;DvIg@wnF`s56=+=$ z|CqDvOrF?VoD3Eu^K%2*2a=tfm$79V!z*ME?J_5Oz4NJ(PjGx#Frq#{Dn9^JkCs-S zz-zl)?uat!qDq7!5H>bohZO)1fGbb|2QlEYXt3j!3H+JiHVv=iqI`<0U$a)IQGhT4 zvZFPX&rIyQgi$%O&M~{SFJ(d{H($Bs@;k)y_nVhh%Q0$IE^2l8YE2VrE%{v2i~8GV zs&^;;TN?MfXbk3SJk6h}UEFvAQ+=JU`DS8%LU(%3MeAd})=>eiPZ;fQF4}uKj#ClO z7HI9u&3T|%?8MJ#?Jvrh_PX6fQi=r{Q-4=7YG8d z_P0SG{v6#RYmnNvX~iqL7qTVRh6q8fz}a+@`bg7e&W?AwG_H@|3&%^x;R#p~C!B~6 z$3zWFC?|vTz8ObjMaqOtu3Jk)Q$!yCCbhRs6u`!}gN$z|@OsSSJOX(2f=tU5koGu< z4$9F;mUv*DX?Y6R5@ZxZ-6WR;Y2!#=2t(6`(s5t zCXKznTjTo3q)8L24&%iAW`p~cLfuR}KnL%omD9h9M1V5wQXgc8vf?I3*D!m~mAI}t+MgRn>d<|kl;IlCqH4XWIW zM&Jxjk01UP2@wJD<0)ums-ghv%RepSJJj>hY!M%dIF=^b$0E>QigZx`xjudZhhO;z zE)pw}Min7ez_6CiWSkxdfOG*omRyx8+mIL_8V3^D{$?DHwCC@dA&h+$|S$u z|10f2qnha2et}OC5=s&h6H2ItA_7u^AYD!9y`xAoROtrkMF_o0RXP%SQ|Y}Jib_$W zNml_CQ7j}8_$rLXpy8yv(K6={H@lLpSa362!8*LPwbl(0!UW9MXj zL4;HPRy&8!Gdt~$vz0EKf$KuyLa>S;bG|dey`-(tcCj$jAFU}R)yMWe%x=epe9zTZ$3Il2OA>*h89tUh9tx z*`F49lK6H2K?SIl;KXvPJqcVd0J~6*9d6?e#xo}hS;>)oI{@Y!4j`DyNhUMLZjg`s z@i;vY%w&kMk_n*DIV?cT_vTeRIE6a_^`{-&l3%R@i17F!W#qcK6Tmr@7v0_Y-Onh% z_^a=$Z$jG(etQc_7d9Ltgw8`kC~&0{7d9?l6!Kl{^gXYn-vIZWQDUBI`>l3SYy!l#3V|wsBHjy!#YS+?;TY{&FN(agK=+4WdQAdIb^E06l>k z%cFrhyplFez`_~4Sb<7D2JmwL!%m+8B4(kTyA%cBouqjwa-e452t^<`G46+2IF}ME ze#rkKN6_(MU7-rjwKE})&bV}t{XsOn;}zg`deVlXFuKd+90x#T{1$!{vH)4!-xng_ zakR?lQjx^t&WXn>5{-kd>T(DN9F`nvZQzm@ILQFyMOR3@Fap6%g`fdF zQksyn9tcRf{yqNqaQw42j)hnbiy@stTv}IX28ard!X;-~`7M4IYA1())|JdjIht)r z(V^*fLb&)1mvRWp_lFf3p9G7hL&0ur4# zErdA3V>EXP{8&|#Niij)jYuJl3=kecjVZn;biMeMZ4S1$qp)~@ zM_BclPLFa*oRzv-aC%C2_NicvxgT~LoMqbSoSjrVdBu`PL7XzGnNO{8=o+vA31|OZ zXshS;Tz(po1~uu0+E`~xkM65Olsk*LcIH56F6H>)3-WKR$eSqc_DrpF>!K z=0xL0i``ueZ=S>fT(&m^R8_*1oPCu8-`axW%TG(=E1jssJ{LHh3;503OuCuoK;^V8 z=F~axF5I`YQ?-4$sFJ~vf?HMzwyb-TQzsJ3VLVgE^J5pY4srJ9@;=6OmneXt1HwW| zB13T?B3|xlqY#)g#-FnsqS11bTO$DUa9;z}iM7}$eYWBNC*r7 zVsWwD`_>)8hu$_#ki|%@rUag`14Q(D(MYh`eDT2D9|PL*HdHGRb>rdyeGs``ywDTW zs4}qFmn~Y{%J`r`dd-oDB~JN3rJu^pRtMd^Rq^)F`$mB8n+xrh!mx!{##7`p^O`az z-si;cK@|6>#5#A>v+UKy&f*auegkHp)K>jzGrU+jnoqdDWj{SUc6o?@K?uIGC-BN& z5J|=?fCP9M=Z445g9HX+L`{hdxk%vew9;rc|C3{?p?xZBctpHVE~(sN1dj`k;r#Kn zC87u(U(U^y4>sH2W*eZs<`ovFaZ-iol{XE>U})gLe9+87;Y-5BHwKq{zr)Y z?99b->M)U0P=i@-G3)r*+5Mt0uz;|JfVv9*jh7d%MmS}hBH}Q=R6a0H_N*0;!}T~P zP7NVW(L?A_xDzg%bcPkr5gWrDOM0rAKX-Ou#$liqF4814aG&pI_VbIXuJor7F&sH_ z#L~5{U$ZLbzNoSg0zWu0ztm^w0-2Bf#xKoi~cA;_Izv zT$ryF6d{3)bBz-bu_8A8qP8Aw_x;pDB7)&9$Zpc6~gru)JiUkOYaN-dN zeqa#4t#S`sb^BeL<8q1PPTz~2w>dla9X~vZs#`Ao@Ko?)^yv?Cw?D3)V_$sn@s;DJ z@2F2lb7N{-f}i*QK6^jNt#(y4k9vZ2kbOAth7XNU8wc~D!!8(q=DWZ1^ERov65SZb z&OWcoMcunw8D$X*$=T-&EEjAa+tYoyBXsn;&gF|664V4qyXm&S$Le=GE)_*%ks@47!d&xj z!Ok(`R86Gw2KXU$yhKxbi~L)Alj}f;+j#@;Lqs?WaYd53r`^AOEoILlReN1N0_D0{ ze{%zYsf?i+Td=rtGi_LZS4W&FDI=9L^dz9{v3O-nNejw(-Ef9k5DVBaYR(mY)T7~e zwv<0+Hq)Gn!d{;o0E(6)Nq&h*aPRAfWMusE)>0^}k%si?z5S*aDlf^Hj~)Hjd@Sy| zdjG8zKDu#)Z(ND@~C+!H_Ss6%EYI-pc`RnixoYCqu6c6V+m#*Bh z`5>K7ElR3cGqH|GwcQsRcKY#zFN!yd{|vOE6QDauB703wBS2x32Vc=C)A@n1*43Vt8K)uN3)qO9 zjuTw@FmML4J^l3T>z@ZZtM@a{-~apP_pe_^SBbF8LyTt8d{$Ai6*TxLkr6BzW2sF_ z(__peXL0<~DC&wMkvceUjtc^H@^*2A9;P|y2Im3CH8p8wVgR8>1Enx+Q~8wol)n~z z?BKVasXWw4Q-`z{$_#{VezbZK#$R`lKZ50iO%~?Y?<>APikTCP@TDP7o8N#c>N5Fojr?2KzTFuCa~Rq4^f<>D$K8vz0m& zgwX^YtjWbn&whD#tSU@Py+_bdZ&$OZpoTHw+R$)s!mBYkyD+~n@awS*2&#UIu4)1h zwA2KID^iHt2Oxm;(^?@_ErTO=5%u7v7qXmt?G*&jT04@TmLW_z8G~5$lb(SM!GNcu z7H>)w=tt-kiLl$Thui+pr3dtLfh?Jhz-4eUzlQH$74bTAJ3JIz@2IGCunq*J640q%Ip( z=EsuAYlpY)siJ__Yt76vqa!BnrnUAmizJbq5fnZv+oWt<Jth&KdOuG^d~RG31cT$2ZV~v2s?kJ70|3HXwNc=XUirKKF|=F$)m@_vZK$~uiLH> z2#~cQbYeFI&IXQAyF&woPHRGVDM_92WT>XpQ6)5$5{*2ySUexk>vW0|GCbo& zP&?ZXTLc&6|lnk?#OS&(Krno1^};3ZHE;%WG3 z{41XGtOhTx>zo7DjYtG}g9#g*u==&flBgwvgaS<7VD_6+XDugVA7k>X*4*5!#%VV1 zzvSHybRz^@GH_qzp)H-xL$hS|{sQ zcbCq|zmpnMp5d7aX_dddBUP&!w`}6SfGuHLxpMA}r)kI`mLi@yA@77SPUaki+`>`W z{;oesHc$xB_Hl;f>uIL1OeZVP*OeVDTr*413Xs0JOFv$tGIO)xAdvG}9o8Y-{KUk~ z6hZ>os^r0lK8y~a`RYmgW+}~g1M9Rp>e~C@E~$YHSoF^7`ZbR3e(8JuZ%0-Z zdpkRCxqb4y1$THm{H=TbuHWUHQ%BF=2xfP#2KfY6u0G$g4e5Vf4G$jqwf4DzdtmOP zc*t|UrKeJ`d&v=QpFgB!A@f4Q-zGGL{wa3+=EY6_c*2kAWE~FT>H@u~mpWv~m||F}7qdqMVn%X@ zU&qV9xA`1AXmD?FY04vf^#X#1WCK%iW9vzvcbkb3zBOZ#T==wv$@#AjaS!E98rk2e z6@XnuJQBy{oPztgv^E~-hLZ?RGGvMCzOTle)gCSy*uMcB!@6yP_wqW{-oz5+B)yU! zKp(EB={=!y8aP1!AAF+C(daO9AAbMXltq@tan%`U4^ay9oJ;XBnY~0N85QXdFBE5* z=^|4uI6Ec6*M_;zUC=Y}p~4-K9|0`|22!D~*@U-;nVaa^c1!8`2z;fLyjfhciyU5f zZJ4LEt{COFhwvWN9KI7A^P}*cem$4Ss5h>MeG?Gk9M;iX`MiP6S7w%o@=see&rD)+ zeW3E!Kl2LzYZ30B5A8OcstFGy%t!$lBd7CP$-T-hW@8pbv%*k5sDx(41Qz@N6rs$= zH_4-^*ld|uOrRf0J#5i;$e{i^<9L_VEZzbgK03_#T^X#H74MTBUEoDf89wVL_qD;! z8=I{vr{ucI6Od}R#c$LPd-L@Q!M8YLa~aFIFQxj2X#W@He9Sl96CO1(3% z69*!fucp8^)M!ku{34P^iONcw!-hNfB?m`UfehY-k2O+9z zPGm3u)ub!^b@%ut$ZO9=J zz47RX{|$&yvqHbkk0z8OpOXQ95MV$@!iG@LnGkal^D6CX6&P4E^ZT`Xa@Wb#urrJ> z69(N2gPLCjb|HVLJZenXolY+EH{rb_rdy&BM+#A9;O0HmS-l^f}@gdM51?zYPsjPus)PN8I_^%WsPB$k; zj!}aEY)~_5m7MZtms1eO`M@kzcqztp78&h)B8Y@IQHMV10sJuwUu{OkFCN7ne1$aD zqJLh9+ibgX>6=+(eW>E|BJ4?ct8CrcB#WaMZ{Ww{vak+Xd3^IZ;XwHOufpF?^8?0x7Np2mub6wk4 z25iWE+YZ|amh@zKsK4|w`1%8u6Hl{XlS(O|2Dr0O=jcZu_MBX`6viRyzX5&( zSuAiGd3gxJBqy0mLh$}bfgVfUbd~*aO}NGey@F5i^+ym`l#cO`3@@QuSln^?=LCOV z=9h4Z=OHuc#6ipD)!Q(q=1!jsOUTqgsm~XjwUk#1kk}SRU&W!in)$hR5eEcRnnocb z92N5|aD3)QDVMx_K+=XsG|L{sNM0r|+o{T*=^M@Wfa26eK`l~!m-lWiQ&6gxZdG0? z>e%A?O-BK&{=cS?zs&q#6vS_Q>CbE7jAU4Mi!48Vl9|XESKX8;{ZckL$zNu%JT#!3_N3=D&Ng;CDiS!1$FvDQVFU9eomxxC3*!t82Ie<8B^+FT#nLN&ZUV zd$iCQn3zt2>W0gzI68An96?2)@%(5+u)_KFkK*S0#?SDHsqH@GRj@ zfENYcMPPnhU*+J%ZDdQ>F~S$ZHxDH{V+eFIoEzH8_1eI|T!-h4qzC6Xh)F<<9lProU|1IGAzRF0&L7om0gePPMSm(!S7+k=kVr1M~qh%PD%>#dz+k^;h=` z^WDLIn>v0FT|cG`n8Zb90XlGyc!X%)j4{fqLc;-2lA6KK&19C4`e&CtTNn^`atz85;}tUm-iPC86WJTa)7a zkLg}JS*s(U)FX7U=R`WP<{nS zTZAD~V#}9cf7YGQtFW?=flt{wsAR+nl}REg#Py2zmHc4Iq<~#+&K8QGt{I%EoYQ`T z^V8(De8a&l!?LHVM;E@0hl8Zj(wQ%U)zf^Rb=z4+w0@6xs1(`HG4!CFI(Eef9XZ5h zKZGe6;_n~gP&XQ09Kzp;BxEy(nsGyRnfvU}5I87*x`FLjJCwv4$QfZy>enMSy=+7y ze^3juWB5D(KQq@~6bh!N+odrDAcWQ`6C`j0-<1u`^zwz$Z?`66%`}*N^<)-et_RI< zoO5qkc>Cak+yha))TTox>wZ@+skq~lsA#?{pZ)^4y0TKJ>FfAMSq1T0OOL{SKZ@*+ zk5V6_+KtJIq2`6{%S8LPK>GhjACy@OVH zeAnSrtC7^vqY@AF-X4TPLhRSaASj6H#Z+-UvA0{Jr^-|FLMJ1Q9(eDWNYxE;+M;8& z#nr&rb70Z40ia$AY5}d_VkBVC^4Pdq6qOcZ1vE}L$Ax;yuHzypIF=J|3UoDtl8jc!vBANUV8MTh*6Ets28@#(L2eIN$hG(VqKH`o!4}nVu=r z(3T_AO0(Y|7T|*zdTKQ$z+BeAJ)fuOS@`lvR_Xpz8~N^UZ|Z2ydh4JsT#^R2$mnYb z_}X#vD5I;v1>fI288wqMD7=<_QgNk!@$`U-LTPKF*PNFZcaRSdMq#~0vqorZOA%Sw zDk)E))ZLcOVJsgT|yU!dPP^aK@OeUr&v z@lh&0p^NL~!1F|7)#QPtDb>m+k~Bv0%c>O`GToqlaKWP5?3;N-=rKG?OobcPw5vCO zXM3gNey@vt5qBiyVx$P}ttc95ye2)dE{F3&-<|;RY@FI#e|=YFYoL|y_KUX;1|!!= z>ftlw*s6`|RSmXf9}N&yu2pQYn>atKGfy^lN|rL;8(Xox!qsmGoO`A8{FMs(>%&X0 zCM92~`@JS*yl%px|K(5#FbqX03IGRj0smn?p4U>>)cMDL)HTpBG}1LQHL$WUy<~0a zc*)wy&c@Zj&f~I!my`2VR~LVGw?Gfi5HGKAA4)VOFg_qSIVdzGib+3KG(pTdM-Oj?Fv$#7l^aijC*s&z4k-TWEEb@B&DG zh^Vm12`b&nA&Po5A7Gu4ZF@p^w85$b?n+?;j zzRWPgtQ<`L4>L^D##z_i^M9wp3|;(9JVPyfue%wMJWMov&9(h4bpovQLTvQ^TL^PA zzv5x(;o3l4P*4ZZB*;^OV?{a-3r&gUN+EQ#_D50)07OKsMR>(GzyHcT0GNh)?t zz3qOZ!XvZ#N>;T;c8zcTJ-?z>|I*HDW!-@l{YL2{Mp+MyvnT!$!t!R#3+63~mLl#v zy52M%)jAd3HcfRjiMwnTPc}^;o2UI731bLh-Zn*kb|n=1@&L!`;QtiDBHjNbgt;Xo zdL|`%rKS63WKjN72=o6>AuPC{Fu3F&AuOVU>e(OfQ(o~OLfB|3W&B3KR8~-2TwE&S zRZ7jt$tf-_PNT*CgM{5lXCPt4Et&sz!fNYc{_TX(IvF=>UR8eq0|{$wOX=#q(cP0V zFpynURdwf1V_REWe}6x3P?dHyX z{l9IniO0oL)3>K*%I4gKvVY(^s^Qrv$tySZ>;+75SEVSy$e1gkFi9{RklVat7_iN*AwNJ~4aclwx?R7Ji zdbx}ytM>Z2TC+-nQiF~L>k^|TujdmTjSMy{ENXgxG98r9{;l2lORN5=bL@aC(y;5^ z^S;piL3_#_{Ynrvu7!mrbd7ZQIbd#rH znaM!-WPn*X`~?*xmWW&d;g0a%s!s@>gGXK36`L-G%AftQp5iw!_~%zpC6!O9F6Jr% zPHzWb8+&_(AnDQ}=188;q~JdFeY*G744jNTg8;adH0gQ1<$u7!CUojFPu8Y zVNphfNp92PxxB;;q6HhaE7BF;dl*FMNWQDOdBN*lb%A@sI|mrQ|7eX<0<)wwJbk7# zXviuz-jJi41|Wilk7!gl$GYgLyOx}06Hs8CZ5~P(3%3F{=PbW(l#!mER9Ck-Y1$^^ zU~a(P*B>|}R_?PoY|4?`YYFanadSlMb)NUUjLOYxP?PP3Ulv`Y7&S}ws&=gj_R~WV zxm>rmPd_kMUedVLuVmb;e?`^EiP-aIbNbZC&Xg9BljgzqvHjT~2ZCqe(tf?6RWcYN zBQr!x!qQ+wb8sEyPM414kJY>Jq4x9AVCelUf^ zN${D~hi-am7)TdsMdnJYbSr4@R4;}nz-=SJY5<2vYv?QKXPaR0uBWfnc5^RAg>6au zzUYDHmq0bSs>^Px8YvfS&dy!bV+Y>kxv(<|3$E8#5x13nWar1Z&*s8QBCkET1iFt` zefRPzI8|)p0*#fE!{cmB=SuVc0mI z{f6T;q(_i8`*PhV|5%I?iTsMm06z+f#0o+}Kb(LP0FbIC$T4v01lx21Cz+UPxHQS7 zHU^SCIvoNMG66B$K8)} z$ccOPG<}1NW;@QNqu!4-f^RJ42!Gj3*0*=YmWc~m^grcdAF1H#VZmCgWpEHVgpb`w z)&h(`S)IFDoOl*$?a(N+zj@QIJs`&3N(ff8&dDC!!Yb*7fmtRGog6LzP8AGiM)osF zkf{*k673td zGo<>l-*s27y%X5&uEOGiQs()?p5~ET#R$>g7;4wMRo*iGSg7*J- zX{dGaQTK&woVB#OgM)B!#BO5 z)oYzhu)b+_sCo-rv$Lu-U!X<@IUT#zNe$HZrOBPCO(PGjOK~epIfICRg+~maGM4bu zLj!r)hg|eJixpf&VL!GmikmG-0X>A}t}M@Y)kEKTr6tO+{owYFm4-S6wc6;W4nrjz zR!&BGB$ORqyuRlw4IKV@sjAb8m6el5_>`Ca-n!Kx3Z;8nO4?01n#LUXjFYj=a2soS z*UL$q5rPQi1Jj03QH44VW~qYt8t!Khi*IU*)m>*ty?pE>jc(+YYpuv&-?5ZJKGdwBiA-0q_-Dz=03#68D#33(AYi+Hxbs2m zVqHR>EpUI0tvhiQu83Jrs^(rjOEcIpUq_1MMw1?6fO+Tyeg1L=<{hV5RkuxEBV z2=9g`gpQv68f+xd3`e|lb?Vy=*;+9+a>#PVRvdMIg!>G^?Kska;%RN^z!qQ5@4sGt ziuw`i61yJyvasIdNldn*EH%BxFg%k~tuXJjdF5Sy*y~R+|E9HOXZPd^qZZLajV= zoUc7a-PPPvdzMSNk!j8h8PJ0yVkimrcT^s?n*X+b*+0BL|1u<8C? zB~Ce<>HGV&IdkB{71LiUtAD?BKOOiK^7Pk>xrfz*sKJk6>+t4aH;^XF1lWfR;bl4c z=?MB$A5{Tr-Wg?imz4sRqGEX;gL!wTgGWC)DiWmok)~hqMfKEKmamRGaA)OW1CvU*i+!mc&bJPncDwf$$_>KR6k2U(Q9kN?`OP{v#fyiAUB=V`itJ z(&g+rcu_kv%8|_EH4L32FgeoM*SY{Q0Xa>DeImxlIWc`k!!>D$F1(-)1wKJ$I${QP zXl%9Xj02G*7C>A)PId_LB@3oYc9g|MqTcYW%~%ZhGYtS(Cjcoe$MlK9b@oS`XL8j0 zx0to8M8=>-+6kg{!~(PtOmy(kj(DWZMwDy|J1>DLN*V6BoAl*-7@g7Ey$%mxxnA7C z2MFWap}=`^sw@cJHjUv$qt=v?0U#c9D{2sgnrTK%5)yB)pjKiKJCyXDbqJ*3I)j|Z zTf!7e%&q8bW#xQ40Wj1&Od- zcG8$)8#?9nHfFB?`+YlICI&W**1R&4`D_Vu4xM~W4C_fjf`%b7Hd({Ekt_aeAE@ke zN0emyUG_Bs++!O<22eZeNdOr%(aioHK(1YZ>;S0uS&<5R**`rPZ5!;L%$S_JQ~rwM zCf3;*&=j43`Z7&AeJRg{23yG%+aC)VBqe|zOMAwBv3#OMCZ zr}7lUvKCn8V-r%aNnzL&eQcTv_J#yDQ>9>tyO5FZBkNQs6H_Q%UI@?&OC$=r-xjI~ z6{(Vn5?PD(uM{P#6y3R0baxqBnOsy^Ta@~1?ns9S}z|O8|fb8XVhld&yH&ZmICvXku|;S@F%n-{Ts8B?RShwC_L5EHBc)1~kZf z%$Xk;lo>cD3yh}%(i@;CG;{DS>?%+eHI2d3SwLoGau!K%-GC7B)Kc?`lkYL2l7gpJ zFw$NXbIg_W+6s9#K<=dm_3@#^ETYKeO+E&>1o@j^GII7lMvaPY_t*M$xZ4$3qV8sT}!ITEZ;Vgpa zYUkmStoqW3!_w;wSQ@vWrfMxA1{^#MN~hL_P1MRAmDk>wsJk_eWiHH;kpr>-Fwm?a zZO$Bch+zg;uQtO{Q$;g)>!S|qYP}lNNV$Y^aP~Ay$TY?uV9g*h6G+V2)Z3L_qSfJr zS??R`+3qlY`K7h0lwugY8jZTMjiC3XQwAl|z1XmKSmtJM#x!VRqVfL9otc%==e?yC zF@Vo5u-tHWXyWe7&%4nL_ujk@f81O4maXY&(Y=qhP2X1XKc|-MH8kzNzk3kg6p_;e z?z{VC;vQgl64KYiy4u9-eG=~7Jj~Xz{l2N1CpYNINintv9=pO<2hB%p_b_(N$0M+P z^&IwuG(9<{{&!eKN~Jmp^7w6DxKgR}Y>nN~7s&Z_(zzC3kk$rdF?sad6G>}8oNDKQ zw`!ikJ}l&b5|Eyu9KCuR-7MH=N!U69`~&%hayD#DTuh&cEFFT>3!^kz+GR*ENPUCQ zWRv$!i>@J7p_oI5fHXMPp()p)+@7HfLfTFitSfWCD9Q8wOs=Omh-xnM<^sR8_N#{N zzLVIEpBP>s1``Tjdm9aiQ)}_Dq8_obLP$Ge+D)|>wlONZGNVHuf(uQC;WHf39b7cF zUyD3s8XH$LHC-7yyAJhej+Q1-VaZfX543^Cc8&-)o$OK}!73P&>_o_>O>!8cKtt<* zQDI{Ma*mMEqSlxG4(@tH1r4GRwWnc2S>1!#upw$zED5%w7Nkyuod*!xs~E|#*wPL7 zp?75bj=-zcSZNY$kP6yb?bsv@669d~0cFg+w8K;y;&&NLa)_0!3BAULyiGhp^2tyOxr}F2}^>-*mb?Scq3)%Cp`Qd74fQ$ZJNO30iZ}8 zz#J8(NJG?5C(jb#6L|RN+P-Z%)DA#R)q*r_;z66x9YRXmZoEu+KQ96pAl9oVv)iph zEu;jUXz}h#m@Xn*fxLO@%Ij{vYq!$78Tn;8E4~tyG(9Mv#1Xu(M;I%Hd zel#zji#9n=L=KVfU&SR^<}(k-%wD~p6Li&{K9^?Q}UJrGXS}H8)=iA?v1G82o7{V6tWB7h<$iQU6Dn`F~x>N-v zLqaZ2OFUVHXI!~YZ?5r7t+{un=5lz=9o+26v$Mj+)uO&N4n?z41GD#*rbmwI1cpj( z&Fek(s(bRtD1G2Z+E5+aWF0lnCXJ&mt+tt+&Vt;diA?~pY$@qG=tn2gGq!0N8|qV< zG|<*sm)J*)69U^kHp=+3)B+^zG{*q|^4@y<4uwm9VBT07=QQzjzzI0Wdh`sBiYQ<@ zhOA_;BUO=1i_@4WL=db*3iTXQw1)A*qn@Fc4`^cZG?@I*;IGKBW5o>jtJ`aG89Uj% z{;#+A8idpQ_6gaELYPWP7VGqLf2eW^a#e_q@0L_kh>Z7JH zhzPx3LW$oqhHBWX*F7PZ$@g_^r)sc4s0ZbK%KUEfCwdsW12K(mxJh@P9(qn7-M<2ohVLeBf~21{C*cSvzv>qN zWeJ$hkzMBde715;AdZhNRmGSKoPH_22N}r9V-{QiM{P0ved&IC3nGKPvyyE5^QKHo z?%AKY$lHiM( z@0rBkKF8%zg`t@;IC6WP-Pj1SLqN__VcXLf1sY;Di+wzFTd96qVI%Q?g1ngunqRku zAKP*~gKF*uEl`kO$je0AC3BM*7&^~oSxQ%i!4+E%l90|SK`w;6FZ3HuDyZy!zM9{d z{@=@9pI-X1uX}v@xFLugO~tYS`2_PRnSxJ2JXk0hbgCzK#W(BJudr3eXAxa|9HCre zAt~%C?qBXjhA*eufrGtR>pVSoVfC;lhLJLpolu#*mnM5}*gw5Ghn?fi&yEFUBz4J` z1D^nxCh^JV-(v8rwLbnmuChIElTR|4dp<{k`};{S)?}zarHn?_LC;+q;}JR{?Qkc<6jNWiy5Q7UXa~4&HQR<`ju(*i>~Zf+vu;3_rKaV zH9K7X>h!XXD*MwHWRiso{P}BxSkC&s&MlXg6%SEIcXo2E3})9{sVL zh6{Hr>nv0<#(QRf$${$vfB$&yu2^NnL;CCu<`|3nGx?snmV~ytPO?jAPWfQkUQXU3 zdH+>cR(LzZ9`?ie@F6=3hYup0cm#^gI39;G&@%vmV@T{7R#cBlImY-!@cp#@$7zu< zZQ;e%0Tl@|`S$=+UD-@OK% zR3j>kR_pSQD~Ef-W!K9=R4Baw#;dRRP*lY6{ypzh| zEfS((lzhpfx;i+jVH<=;zs7s>=_nQUf=#i)35xTIf4@vU|Nw#!#g*G7fVNbkr)5jO%XAn zsL$u(=ChJsWk3|@WJj!g0TI$i(BrZNHG^)CWnH@RHYk!X1`k}ir2XKM_k-m(Zg%eP zndOize76`048zS|lAZ{c7FiKv_!{Tc2Kyi)X z!$xgR%2~1E%XXJOTM0{g1~T-?$4iZPEY~=ullVNqoyU5!KmMY7^Pk+qtyFPkr!%2t@nohYrFdY*nY3+ XF5^9zDLymh-<#$C$L9ZexZ3{$7msKZ literal 0 HcmV?d00001 diff --git a/src/calva-fmt/assets/calva-fmt.png b/src/calva-fmt/assets/calva-fmt.png new file mode 100644 index 0000000000000000000000000000000000000000..179df101193b8805c80c5d17617ca6997ac3ba84 GIT binary patch literal 85751 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𚰹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

Ny6a>SHG;qzX!KZSP&J?Vy>9c+L7i4Jzbbwm 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 Ztd-E;0e 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�`=?kJ>g#Y9);CdB`-K@ z0U+o5LG zV8bz^2IvSH%h=(JoI3DqDWV7^lNYSq?)xg_-@MRt8{fGNsCrdOLRQRF=_8ZJlJi*u zYb`Kq6SS$r#lLX^nwMNA&gk4pmBiHvopv^z4^+)lTVLKvC(d}9LTETOV*`WFcERAe zTz`?w3ubzp5Fi}vB@q+hI&NFF;n!}bF8&W#s#tIG*js?qD9>-vLspFZp zg{v29-{`W+_^#zcOAq6borJ>y-U#dS9VpkL=hEt4_A6kkbdl5NpwrqV2pZ4+S5fCY zHYXRZegAra#MS1>{x(0LcC=I0AjF(^(0O>0(uF6#BjQCclSl4%sdXt4HQt&Qhid}o zZYTx1eRi2H)|6iU(z;j5^5OlC<(_BSsa_@FDf2sgSeCs6c_MP={&f8POA6$6b4K_S zUF!uR&z>NOAA+0P5u``0mBLegJX$-V!lSeLgpa?b&%295QZ)mr^C=madX5w|ognW?@W51*T-m$a93iw#x9;4Ml8sHt4rqfFI5K#I zXky?N*2JzrM1I|FH|#i1iji6YXGVg9U}f?3r`gPn9)~>f#iYm zY<>jyyY&qFdMv<4OfFxz!on3}xPv~5^_I0`PCor!eCwEacd@xQ*>Q#sZy|0D5%;13 zGfZ^4O;n9FCW9#F$5ut^CVt-zpvOKKFP)Au?Gx~)2|^5!jL|HY7cg@x29QyjVxc+| zgnQ|7w?n0ySnkQpt0(c+%bCcB|4uhAl;mv)r(7D)q1r@AqVqMw+g(~$fCIYsEpHhc z+2)gw_zG!6e=p<4>brn_Zug()BMi1%-dlbYx+1touX3%&eso*)VBdh>8#2_x-M(R%MQ;hkq&Bm2ZjtI_2O`KzOncw7c2p|4-zxb}#Y%BR49qcKD~ z3kbXhzMi?cgj|T5JMiOKS4ZE~k2xO`f43|UkE*=|Ol#l8UHh7y=Zr^qgcC(My%2}D zh6Oz)_%QVR3sPdQM2~;3$`f6t!3J*B<$lWcbAMUrNa7Lu8S&($%I0i$<%;CQ=UXgH zU)dA7(wylO9>?0)_B&TsQmIO%CXt(Y*_WpRY&Od8J@$`KgmNf8NSr)syVW;XU01|S zSc$O=G?I0leP;eO%kFJJztq#sW6!i(&F8+^^4QO-$un`hUSON;<9#D%557HnGH#@T zavyj3bVrPJrpJ!X;)wsmPFjxj8MdCJ2kj+bQRam&p|+3k_s(ql40CKR`CR*1B0JIl z!}SBn?dfCglCxU@Nt*{qwXCF_m9yMb(mPqwnrt{{{~3%H8P!DM4Lq9~L*geUajlXd zf@INHvf$!Dffaq}#WO5{uKeR}7oFTr;Ue*kZX~Y&Y;|dc1q(frT9ZY;s)F>$LO2S@#1JYq(1oJH~6% z+gHGg=ZF{Bo=fOF=EOpD^lM7lLaMkRP!tfe_qDfVO-#Zv5UdOLxV4aYrv%fxm1;;#Pfkz5)+7zwJONoc z!ITM6Q#-S~fOswIy(*AVrn|LFYDv*1hn7u8Nxwe@5 zp(*U0bEcgjX_%Nb0zdhwDLE3Jw<(wRm6-QtHSfh@o^XHO_t;pzv7@rFIgmJ-?pU(K zO0uIpAm_=Vj!mH$r2M{0vxQ@{1F3mvM0uxsIUzv)6TN;sm(!VYb;F&4W6$cOT#U{A z2zODTotD`76y>;uKB9G6bBLv~Gj-qPQ>%i>UR!7%t>``eX#pi^Cjv1rJAjV{k6cQN z2+VuRmeCzUjq8g^q+;|vLnsV4*9n?QT=9NHx_NQBb#MAUdjP!-&9W;>jw#ZRr(v8k zB-9elm1NW&h;Jyt$j%_T1TgU##X)f;Q2DGTxZtI}%)XUOIXa}u4k9;Hx)ED)X)zf4 zCWw)isDg&*F#)J5go;JXjFrj9<)H(!d(%!#EGFIBN}689@SqUOjd0DFGe!^St>47v z&SRbsd5{Fi=RUW`#bw4p#hm8334Xb^CMsivXspepqGjaLqC7SP?Rs+=tgK4pa@FIV zjMBRqrJtOuX5ZwlAgW)KRo^Y6C0wFqys3h*)7PZwry>A85?z)(ch04H!v!OExn$6p z2e|~>-tR?NJqHOc(O=MwqQSkI8QYOjlW!`xgi>)e1@vkZz3Opmq=!bn=wE(l~k52^|_PjoM%dVFI`l&h1nh$9oL6`Dz|58^UoD+v~L z()Pt%@wLd<(!`96n!XHkRHR7Rc|hr>11Y| zb75>7LiW!1@Y2e!A{t;Tf$$^-*wsa6!Mlvk^~y0eiZlX>=K zQ~eA}m0)@9+c(Y+GwNP~80uS9X?)!WA=e6>mU`Eg^YJYc()zWTEuzDw%~HfvMtN5t zyk}C%p3v%_dOp>hho8`z=GofcflY@+T|R;5l72d>`=B9MJ%Ou7NQ!CTI_ry z58L!!KxY)%XN(KhZ5GazSAy0mEZwilcX(>go!p zzlft>tX{SW!*@v-bh#IHO3^PyNcTt>^f3H-Afz65${9UNKK&DiA(sx1QV>nF)^!C8 zQH0QB*X#GgL@5j7`orAa((1TKm%N+4|B2q?FxJNPfTJ9I`Xbhfg~(UNHfDUiu(w7k zcKvCC7C{`=Oh@xGKl;dumx!$Z^+Z2*)*`K_&-VGn1K$EF+%QROe)Xf63=z*p!LHPA zmn)(!@>7tH79(G{^5}~IC7zA}%jPnc`p6krlRfaF9?-*>@+=K%J!&mkcal{bT;2!i zJpu1;9UQzjc)bEKG-wQ&hrKdBT3XTeO}M9+;CSK-nrCO`s?KSIUcMudmlss+G|vn? z>~^#)s+2}wd-~(lXWeVBS{?7X0&fPd$*93U3_7Y8U41!lSkc6opCIia-Q2@-1g>;s zpbjVvM_#bx@$kFS7SF?XrB&d{kZ7eYz(lq(ucUP%+h&Fm_Cv}T$hi~4uf~zWQ^Ru` zL#k6p)%8Z=okobxu>rUiZ=+v{;rya|l0t7OVtLx* zrp+`%ei>@B8I(d?E0!jb_i2!}}+ zuy9Aa2OF@Q^n~Ry*#k5MSW&FS;fUlJt3w)tRy(T$&C7jQwVO85m!!O4)mUVNXW7yt zc*6`_3!9SE>sn1>x3oh>ufuBA(I?ko8g?uNOm=N;WG-{8o-|==50sD)EBoM~ydG(G zH^Q>J?g`$^YrEk(&@WGiMyn!ExACLbVbv()Ny05@h^_T2gcbp4pg|FyNS`8T1Awc0 zcRUxx?l^1V!)F>i3o~H5nY)dqxnmM<71XKT`J#uELGjw!A(url8UV76gqat`R1;Vr zsz_No;Jn>&G>Sdf4pB{;fpo&E*Kg)}V)#}2{1ZY4d(4|=E?J}szk6qC#YFm7VAd(Z zuyClt)j9Ut1?EfeI#2c01uF00sk^-G_w$khAn;g)cuHo=8cx-^zCWzOJ7E=|<{>~o12b;TLo&>8qa2p=#D zA+Z=AL&4APk`XX!Z^wYS6q>K|ui| zUzg_Lk*)oHb-es zYmc2YR^8f3@b!mr?k67#mQzqt!Tre|dVyB}FM$5rkpAF$zlH?T1vfXawS^~0?!b#k z5Zi$qIx{m9G@xu~XlQS52RR)`1*WE^{+kzbtPbFi>fw%#`!pP#u1TS-(U50Db`P`? zf9sQ2mln;>G%ZkUZc{27_wA9j)4~*y?bk_y@FGJ3phxV{af9}U!{8|48i+5}PT~GW zaXQVM^Dn~~28V(8^U27_$be4SLJb#;d91f?bs0hXkPCbZpz_7uXQ&Lhu>re2i6`Xf4 zc+k0Xci#U;k+T|tB)4FcGg~7=5@LcH>K?)0k<7UGUBBbnQyVQQYm$~XIwMdE!!n0` z9xKY32}KO6a{BL22h3uLo_X#WU8E+%Kf-1OXF}-)7`Ettgbmh8fGY+J8(cA9sm5*v z1n3C+s{+E$FC-)cTsmjZp4}Y2cu#$cyfeW;P zpNrmG7A}f%I)aY$;1=oFf$*Z_MAtG+h13ylKQVM-iiTZC$4w|y9fv@rS(7JL%SUT7 ze6Ye=GWo{MoJQ=ZJR^c#29{SKQYtFI=M*1Ql7kmd!NWx2rO^Vs#e9MpAg$C8=GT|P zpjjIla{LYCF?^W6rpW`~IUo)u3S}2UfSC`nZ}3@xM8VMT7m5QHn!CHZkB`rZ6DPvM z!@*LI-G|oL*tkoyZ``=?cU1Ooj8j>2ehoT^4TwHCa>^?}i6>tg9eIXZuw$o=H#|KV zeRjA`rMXb8;ymw7c#cDBgREvc8Ve)efBF0_9|nGIy$c@8S2TRbo=%j@4F9Mq#P#A3 zQlE>L4XvqhhGeX#sJu5b(ip+Vf)ABEn@@mZxTFzuDGF~x1Gf}(56Tl?Q8d_{_nvg9{ccRRNdkZov*X-nO3JId|#m+8D~$FW;duPu0rXJQ>ys!ZhWq}3s~VWuHR z#LDejCRk?GwP5Wq-V8Ebk?gLBXBq6MiHp!5qOtpe@;43Yk7!2G|1NPn0v??NP9 zy?y^BL^Ar%5Xsm?$K(%-WhVozU4P~OQ-VYYvM~e+QutxhC@1q6XNzA1$;-yq*FGTd z@Gpq;cjM$p*S{MlgWLE2IZZldlj33bcc4TIIP&kA(w|nx|A3VW|ARJHRK}iTBwix@ zDsigW^*K)ePd>;0o-Eb>#o>6DEdBB}?#b9SHU{AmSU&PE;L=FZFSs;bIyqGV!ljv+ zbMyCV?=RLpc-ZiFzVts`qyKW@{{P}1L5Rc$7bX59MA}8nzaY|hm(LjRrvdXH5UJr- ziRvyyx?Q1lYVGj^j~_|`B3?;x-X)_O`Cn z1l`#>I2Qp#$p2G(2)6~O*NlP%>x0oWaOzz+~WY;bCXfHzHe_@ zE>d#N8JHy-8>8^`0J{q$o0g1aIZq?yCpXy*i`ZF&r{^eKDLRf+z;-(!VK7zaNCWKf zi#@4Zs>vsVs$m{TL*CEF3l+v|oQdwa1gOv*KEnC3n1*jmVTEkeD9)S}Ma3G{qo^V{ zxdaD9AOI9XaB#5xJdg@RFiIm_i$-ZwwBw6q)~rF%#o zR0ZF<)%ZuDhA^lD_UyIgv?fUdY@KLw6Wd%kuI%QEcH=dq*dSJ$a8o1_mYmLl%aL(F zrjMhkqLHG*k6F@~XjqqgPK7Hm^PyW{t+?;OcNm}4C<9+;RyKx`ozt6_5wX#T!?h52Qx27TC_d*6M-Ys z>byd*Ev7tdBt)IMVwyO%XB^E7f)1O}vx;t&1tEjTi2e;pd@&LL4{! z8$@#Wz_lmaGU|Ig(X2asQA6juY5+Kamj4=C0PBIEsaqK0I0;a1F{6gaRD^1d1J!Wn~V*cJ71fF;b+58Z3h^Uxex{m7JjyR;|=Y?1Adi2)ox7 z<(I7r-{l*R`evwkjoc24->6vPY}6VvpN=fws9d#b)Cqb)j~>_nO)VPrR?P3jKisJP z;@+sgIk9u+%=e9R09O-%-D0NMY6)St-pJ}YQ^3EBWkxY|WLI81GCftcY32@; zR9h=G1R~E)a+#_mZ+0hvDEJBC>^@(smCmYa22HV@AFWgHGv_84LaMVKFB>+l=&bXG6%%$6vV|oX1pbgjx%3N?i`| zU&wqgAXjurm8N3+e!%C4gI6yu2$pB$y$q9bnb@lzM$CV^oOHi<$Jjvz!RLwNs1Ei= zc9Be~L1!~-M;B!cqPtU{`1@6iK9DB=Myx*=>aSb&P;w}m*dqpn#XNeb{#$RqoBD^sz^!vD z{?V6RhTor-*u#I*Kbb4mfyCJlOcj&+%)i?~=nN9~?dXibJmvL5EAn-G7lk*v79je1 zW6Bsz3+^xs^vVlXNws8o=SX&0Skw|MAZBQTCsQ=`Fh~AQc&3y%dO82-_6v2$sF=Qf zB>mC$>sieM`^{$m0j(KI5XIYfmsDL^g35D>&>ItYqME6>E;R91uYGW7tN)yrb0 z-Z0B>Z`7eZB%YrnDVExiam_(NZJ#6z>uggL$%(nd8Pr; zo}W0jkx#%4@0Bx@cbbg{uk@(CAL+DZf!~58ir?7vB3$SIEpCYkU2*QYkotcMb7~CDP0|@5`uuj}y z%Q=8m%%8x1MDLhP@^Q`StQ?HOqW1G__K6+2;XN3hndjT9M~BT7>@qn=u^3i46E24r zztcm!UqecV?itiZfbjDx9(PKX4DAS)wy+~%>B!RpK=C>|-X4*N!a%in9&CnCV#qS~ zXQgYxB_r6|I@wLugOm&)6;y-}>g>is(ld5UTPF$Gc@{Er78e2SutQj3<0RF{I5Z^2 z6H(=fyxBil$&h#kZn zmFC^VB3lzjXRt;)A!PxN<0e1{Dw-8YQSXm&U?4h3XR$RY<+BKvb5i##&Qj3f1oCPr^ z;nGMZVtD~$Pebt70W=!BA_O@J$3)E_kaobC^-Rf6(BF>$4&g}2*_?$&po0;ECdHHF z(%Va6txKt_G>SP5nST`6yOheCo}FTr3W>wmc^<}*q3tusjwK`lfvH$RHZLKI30h^v z>j z%flJ~4xRb!K$6~@0wtkTx>jmFoMwVX8mD0pb`aqQ7{q#ESyQ1D8p7Lb$!C|NI9oWf zTqr-9u4$irPloo8ih+Kz4QLJ{29bRyikHs2dzXcpxTKzZ;1qE=^prO3bcSP$kZYVv zal$u;q;H|2hz#;X$%&v+vu`C-^HbT)rKXolbF@P;g0l01%3f<8-!2Rr)+`;1En{Vz z)ID%g#{dwrW7#}!U2BfnetmLC&Z|1E%qI@R+n23PM>Jp&Wi*74Y6RR2!%vOCsZr%; zBXV6zOXbTZwTomHtcmN`EEGa{p^}M6QK_N+&Ra3AU7@`hq_=X4ARA@8vyiSf#L{3F z;&7nSG2rxY+3Cd2(}y=tdmV`Oi-}gg933jldQu?fR38Z&5d*Tbq|KQ9HC4CFOGGka zGtJKAvd86{#T6foyO4o#1rQ3jcu9l!^VIm^=J+;Q%msmji$@dsN)iUga;~u_&X^_M zq$W;|B~G&^-8ztT&#Zc{7lU3_Csjl46#`x!z`UlOS!q0@U=OKh#(n8ahJ>FDmd(X| zIx9o2`k6*zCu$+#7_Kc+jUeFQTK2fPj)XYx*-&3XJR=TIc4)?Mk`VTt7`@44mDPHz zL5gO23MMcM!c5VJQw)g|^$ZHQGg^-$Y>9=BVmkCpdcjpe;QDy86gfQ$Uc4h$ksi;8H^;Q}lV<~k zDoW3ugJ<54%DF?q^a*3yw$4>6V7iG}z0QyULEy4r-nGTdD%F~n^46~MrZXAslsL@8 z^j4v@>=`%@>k^#q3E>Uz@VwuSo6UJ9m^(aiwv(7!9+CTI5%G30Hx8LMSlV)35cm?9 zx7CzaKAV?j(dy;e3Ll`exg;Pi)3`3zqvYvFCottPbpBg_z(A+(OFC9QA74h3Ez4JY zkdH+}_6ikX4GNk)3z}LAAgIFT^#a`o1!}s53p>G`Ve;p-6An5yW30>0>)Hbc2UB<| zBwR91x zTi+ACx(U5Ig}o=-dP4?n!$dB5iu8Tg=yM;u6rE)g`=jBs2&VFKSvevksr8bZNcktz za&512&`zDzGQoi({>AnR|e{;o^Hx+v~JJjG=a$c1_6_-j{`_d{5 zRaPozT~x`JH?#-fH=1%e-3MK z&Yv0`JQf|+7`<&C6HSdNrcsV-F-S2NTPg<5?o{+ik!xP|GbCpjAXG;y)F|242nf3f z$HzDK#diwUb}wZ1QWFNo66A#wM~)_rl_b7mue)=AsS%uHD{}2jYj2by=A;59cdc$# zu-?$3Uf!#o*pmDk_3U@qfl)+*^lZZ=vj(^zaCDNyt%X6wHX=CtxVC#ax0!wq$r6Ie z8eQi@TQGYbCkwu6j*3r_B3H$KPtkhQXx4NUhikHdr#@|Ew%%gCP{d>-hi2i;$F@=n zU7O`=fYO%c(5q3zDV%~@nNUM*XTJow0B48rJ$oyuMsxP0HZ z<8@|-0(k<$>{wcD{v4dU+TZcp_aj^1bCrLL|8&hOM|b`pc3Qt1KD|AxDvA+}qj92zs1>e7(uM9Ss9 z7xPR=}Ao+;*><+7eW_icvvwl(JWt3vOsMcSFCq&K-Oy@ z)a~v=WT>HKRVOZ@oXnoDT2&)*s#mek;^3_9mC!e3-B(4bjRdY}bg|SEuDJJAkGGx< zxP8UjEc#Buxw+PJ^NKO|2V)jnuO_a>qXG8)UDGSzN~=6E-btzbDkzJ-)7pWUA;eZy#mO#xNruFC=|SSI=%(-mH@} z7#6p!vW9 za~+Ic$WeXw6oOKV(}NV_$BoH5Ta83cjD=N`Rd$lg?b$QRH=6l}rUz1w>NOVzH!FBS z8$2`i|8^rhw#_$$>aW+*5a06fN{i>?mXmGijiHYRU$FViz31j0IuH2^SVN%74S3;)`RAoPHnTY1=*o%hf{!t*2OGCUF$f8t@V5BcmQ$n{X6{1yisVC zvi;*V-?wGg26Z>@;m}cDNlS9}M|td!OFu)7iKPTSsSXx9=ecktEcD!L#kn8m3qQZ5 z#wau5k1-af*J@Q34~AXK=|kj|Tmz|CmKbKk<6|28^@7_q(kC8tP#;JsyptZdF84j* z=U^5?nK^slp}IoSJe9e%Bf3^Ly z_{achSQ(>ONhO|Kdz15~QG8^gYU5Vu$i7b-I&Jm$eoxjD2M%$5Fr0dlIFMy33XobT zR{f*OR$ra2ua)WF;QZP2LAlwb681R><9;X2=-^U>;BUvBZ$4~4xYK^qSmi~iD5e8B zcG7kugZo>A1i(@KPJVeQ@!gor?B;Or^U;TyLN;GS^s_Rqv|*=TOQ~d6Dy?uF-)K{r zJS_p-UVPDdPJ(v{`q>O}z~jRE;K^58FCX_$3f`MEn!d0hcWX0!8G0+va(aOp_HN?c z$?Myldt6?b3}W_rPJLc@^?ql1N}S`iuYPx8*lqdN*SMz_o@~E1H1D3?`&A%xB7xgF zW!n1F@$QAQ2S0JH9{Uz#$?@^d9ux$|jhE{*F`&Tq$oTftY6z@IahrPQSxMszf&F2i z8no6Z3#*v9-A}JKE0lNY@O|J=V_qs?t6%wM?zMHLw$J2a?e0b!hC%A5Q;uuQradFqgT zSxDz5^BJ!cn`h6Uo5@ykb&Y!zgj0HkRaQu>4lvD;rcA$$P8uN~Bg1sx!O_aD7{ zEcY%y<>pQE%q-ztwSzT9)fc6b?;xrR*lL$dy?keu_DEhlu?{#FY=dMxg% z6U37pQ|#!OCwvl&p~43pv`JP)n- z&lstpk2%lt1wu(rQjdCkgf?wHO$2YUkEc5ges+p*k~~@`+AK$ea2P6KXWF!W6I?zI z`V{bIDw@7|k)rHj6=KKAD&*T;b7KuAPD67U0z?&_5yeZy*-@RTA+Zs(lb*?}B#_gZ_KrK1>Hx z?Z1UtmU+#0V~JRVAti9XQotJ9;*Xn=77NdF+`gYtCP;jgYWtFD<~7$BjL4)Iy>VRk z(h#-%3qyxP9ssg`ng;BC_P_t&T}%Tr`@=MV2X72BK*;3Y_Y}QJ=xC)ig=7ashR#Nd zFU8;9?2@3>n^V%1a1QnE9_7n2Jy$VFX# zy0QLiz71RI;jd*6qV(@~#{P;W(0q4d{-E>*klP@x-UZfRsmkB$IzW1oWp$bw z3eFR#xdvG+=q3PaJMZ7W|Mu;-i!W+t9O7`}hJS#tJ#f zPU4EYAvM=jC|zMAqhXg#thv)H*ZRuls-1*^2v$ivJK|!$Fy3RiKk*FbU+QeEA-6y) z2+ZFqBV=V|cS{UF6(1NsSY-efqU;vg^!E0Gbp+j04IpU#mqi5s9?G%mdt#yJFQQ0Z zzS=;oJw`BV5l(ZBN04qTx;otx1xu}y%BNUqnxm!jz7^WEYbr!Cj4=FN$1t^m^_{oD z7Qeqb{QluyVfRPKf6s&6s)*eJ0WjF&;$m>JcZ2&l@NL)>#E`s?smqs-o(PfrGlavC36RkL zeb#m#IT*qp`g|)8&x6%Tsi~>Ev49VLw+4gBWd1`$|9;BA|7A7DI>PX(<~bw80^+q% zDQ)AWYs+Ax=XCb;iMPKgfRz=J=3WFgWb*N$FRc!(d+&~BT=lkm9JZ+QlPmfeP=l_a zOJ@xw7j^g;{aIG>i_kkjO8*N&|I3a8lzFQBRpzMz7W({>d8+UGrIr4J(Ch5VJoOB< z|E0`xm(W}O^6vO0^E5Qo{qHwH{%55p*#3A|fBo;PWUo|W) zEGz_<{l9Ej{8yI$pIR0F4d;VaCx6$9{&Sz=k^ctN-<75Rn_8Rya-sh}`38{CAG`d| zGS5E={h}TAFD;U#SlOH>y8nsLU)*hx+|V4^G+Re3P&hW((|oty#ETz|St5%flz%Be zlh0FhJvp{u3GAbHt1z!c#XjAXM)L)v>X%Asc06gcKmL7)2QY5nQ!>ztpAW#~m%$xT z&5k9FNWMbi<0~VEL6AdWGdf0;m%%5Pzoh$7T-;^9#ZtGN!B*QYP{eSB8L zD%t!?C3+i`O*XyufST?AE1C*^9WsB)c6ri=ozO14BBnLxW_U+`&@c2liG_2g#_{T_ zOx>ideQ!{;Y?nz(J-T=>$*cvf!zHo{- zYbFzeQQ++w6Z_UR4;4?Ew|7nE7}&%FOD%Pcp_Fq-Peo0Hy~d<+1+@$zTYi4*FsIwN zb0?M5CYCG|d20;C;~7GSbNIdc4XGi$GS%VG5t6A>=ETgL`h0flAvNivVtJaJhQ!Hi5jT%TnCCcxeS7c zQQ;pwJRS3m%5l*!FOsL)W#%se$(Kxa>A$RNHT%#iHKyb542B~>4R-cfOr{UmzA1VE^ zr^>&!W1bqwYK8HDfjG1ZnnBZbrpY4JPbk6DU{_w(<&OD$cpYDx{D zNGWO?#zZI`TbL6qPQ#X=l3=Q4;$J_T#LiPco=}Q>Z&500Ns>|}p;|5-1cnrI?9AzE zw}-|o3&el+!pL=J^oAc>yzin^R?<@xE}cofu*?)m*m_Qs=I(^WoiEy& zrAnN!^M1xsux<}q(0G;f?$ch|qZ1O(eyB~L)!W1M(8}y6SIb!A&o_~h4#$BbB>cDc zuS3Q;xP94{Fnm#a&%7{t{O*V$^d^?7`s|kTa%aK$spsT~Q-PKelIXe@NjJ$)S&qZ# z%!`24aHy*NGbslY^i#t$q$uzx1bTo6Hn8j}ojoePlX!G+>N(dD9EpU=1+VfTDmu5_ z%%Mw1`c~1Nd!vk` z!{cS1FOBb);AyTq2e%G8&?Hp!zi4}}xTe;2Z+E1U1`ZOCuAz4b2ugFBARygQuwi9qPTw`xyY~03^;`R3pKy{K-1ivI zlR@%a_w`rsh|V{}Bqc@p%dOBH`;Mln#A(jpq@L)Szq(^~;la$IZATn|ktUzG@}z?I zKLR*=D=Z4MAb0r#=$qwPxa2I6aP+hrKEYL}50#ZGx-EDFf2r3jOT+J_EpH}W_J!9L zq16E?jn=*2Oahf+$^_?~P0ey~oGoFa+fcxBv1gW?5Z64oFi6e0B=y3u`--QGCt%1M z_D}1`8b@2V*woL88+@eb@2xW1O>imtSoI3x>#de@O^sqA0zhhbX-<+P~JX8Zml^I$^zZMEt0U)nd1(4$kc>xl@P&A0HPNl&W zW2~U-RJ8TxJAcKqRb~p?C~zgocfwgbSI7%;f+ZE|gx-KD7CtzD{@B;1=JsAbI$-*Z z$;BLPUHO3T#I22Xrg;Aw2^eWg0x$mlvD54{85M}fo_F^Z<>R9jf-_2lAGft}Pb-J3 zdkOf3#^3B4TMFvaeWaA?uw|&#{lb^qo@itWKD^2v!s8saHjpzC;tnXNIW#^^jJf9b z?fpghkHCs^NniGP+2c|uTGP$1`+f;NCu#bFb>8xl?7zvP9*br8TUx`=XZG+#u zSOS8=kZsfl;@iF+N}U*o<(|q@dE^6ZS=F|~(~jlSzNYD^0`Nj7x>nrR9&dY%{S~KX zFXqo|s-i=htt#K8D~ef-&Wzo-_GRMrA0vp#%R((I0lOtr0e~B8-%=uWXNesGS7fHP zlquW2v3Bp0yV~1QvFpwo`|xk~1^im7_Sr2v7JQqovTv<9bZ6PQTk-)R7b=Q54G~ir zLYVLvP?r+ewk6&Jj?e`g5n{4)0E3GN8EWv1WN6X7V1JCkHFUio**^WEePCPLk?Sxo zy%|Lv;;a4Xo+|~bJW*{|x>VOH18epjuf0HNYqvXlaKG16{Wm+QBK)+%XM>{dA!Epg zV5TkSiQ;;Fd;k7Rf93?p5`GDharL{CZG9FWt2c34tMA|uE~dnsTrwcSMBCv?MYnEl zezx9_Sp$5DIA7nr;{9AkRs?(I1P}!|<-Hp6JbZzC;^S7@TKXZ}(HZ_f%v(B$$sQv# zMJ*61=FJIC6X4AnvAV8fIZ>*(qu5U8P`-i|_eFySJ4_ou?&Q+58f68yKc-9eay~@0 zD=bhZyV;?nkE(oyc|%B;&Byt4=f|FeY!|c!^cOz+nGdF)o*;mFir*RRrMR8Ccm7iS zm%CeL@%*8v&JQwQv*EC<4VITkZcTU_)N}lS$LB}B|J=CO?X#}s6(!)@4B2^V+rpkN zH&?$ztQ3IP6#E}XBC^jyY6a*5IrJEk-20G%LDoC%zDq9+IpNP9mA&R{@7>(^G8d3z zOZlp0_{;h*V@GRjBipxF@f7be5VrZfTj0tMz*p?$BfLdiu*AjSl)$s@ zVz9`{lky{%D@G9BBlvvoWsSfqXDYA!_;TsO2tGuNP81jOd98q03g{!@mWHL=iMT#a z)aVb7KUBVyPZX zW+DKgBig`hU0LL{MtlqndytEFp1E=vof~PDOA5@~0RmfT*cPt1(pS7Y@r`UJ*W4Wx#O9^|%!-5A7{#+(VfrkD5(to3s8xyL+v0?MGnp|&q^%WU@eSUJi?w6I zYB{;WOn4NXso?shZ3l0HqJ0dN_r;UIps1NaH+66&2+!L5Sm5zltv46{rUmNKwY;y3ItqHo4 zKnx9K69zj3V0QGCAp$TGG~{6`##@6j^|XqUvlVA03A!{a2873vA#L1i>R-w-Tr#zr zLgqqiwGzs^FoZx1;mc!u6c-!MgxC8L>c2q?mn*Cc2|eTZo`5P$KqZ7(sS(JpfRCos zrdn5O2t%UCn7RNghm&K^sjORttM_5+-{8BM*xJ?lE;6RosK)c7&CSoV@J0QLC8-OQ2(W{swvM zNX-gQV7h^s*GOsmB(iqs&>>{t7Bvq+ygvOc-))nLxy88-S*_~m!z8*wE?C#saqx9q z7RfzNa{)`GLTue3BuPS=8nS|pd1Q$!;A9np7;greED4wzHC~8oR>NlLu4P^W;ns$^ zLQ-d+e#ZBc%eOu$3Nmgw_ZFWviSX8OzWAb25#~4g+Z37O-s@f_)VZ0(O7TC!mhXi zB%nF&ZG-?Bdx#9^(Xc)<`1)@!KI9XNt*L*4=c@Ni8xxL^(=h;s(AlQt)uT;8c(|2| z-R>K(xv5J*^d>=jdDvU4SjC(8npJGqDz=Pue3t_-;D;?Db!*A?U$Ele11y7++fM6O zd55h>BYGOKJuGa|%=I!ZmP425+ZKR&?@*-a1?lG{HURB`-}@3=ZfdjRcZVkG zrU9axw1Hh|Kr+s`kv=L649Gh|to;dzg#<+7DD7p-uBl|3$9QPKSgB5m(JH2&Ps%c4 z8n{8J$prWUK8F^(l#4#ziLW$CUbP&}P`P>M>uBiK+hrXQYF`NsDlrC(*sY}GvBKC3 zCw2QZMq}RHX4tmphTJh}x-En2Kc95x>D-+M@A@8^+$1W%R6kCM|8VG;37z9NlF z^r&4)g1fnYMofNAo|1P=4t$fek54xR!1~1pq<`W^a+8an_}6j{Rqdt3DTZf4l z!|U4+FjkqzRbYkumy8^C@z6bAFY3t=AG+0+DW3}#dcDdZc|D+1-1Z!a!VV(VnTz(6Dmx)a@Dof& zkWs~CLNOhEko<_+(XGyY^eQJCdgqb(%3X=mxd&8og8gzrs&n>t6Ua1dL`benQEpP` z10`NAQ#CKM6Q7fpSCBVYERpX<$}bPik29*Nv%pM) zqn-9pa}Cx?Cdhq(R%mAXgcfPDi$w3>Au^M1-_7nax%N_`;!WqZm9N*<(8a2|3Es5s zZxRtdzZOHn9>CmKnhX|ZoF#-|s$Dd1@e zVUPbq>>Qs;7%L*9cJ@6zFH`Gd7^$sO-XQqI`0%rv-F+ca6|`miQMU@8unK(#NGmrp z0Q2J9a^BP1_;5*r$#*xBYFd=AdaC91ltLTHB_-=! zp7rs|Ra0&HeCe6x{G-b&0pz>*P${6#sE%Vozy)9q(Fk|C@ROTeK@Pq6hl$r`yYL6M z0Z%dYi`Mn8mIs%=;a9%Z*Mbe+Kkz*SKNrk&tUcq({ z^n2WP5-!$ul-9h{gmk9cbY|DSoxaTljj0r~DL5a8Cj&8^S3rM`ZDGj9!v<)B4)YgYRp_p$fOv})3S!EmJU0y;)52M}q5 z@frFqn{3@lykzogxhvD5PiGA#@Cvv=RcSzdu3!5mVV~bf$dTtdQ=c8n229=!1ZWTL z_ZZw)IH)cRv6&lGhYWQQhXl?JB|RB(n;1Hb8;;#x8qfczqkn0mSMQjb4%stvqA1=B_TSsCeD4q*%bggz5ICx(Gp2KDY#?{cplR$t z$C#qlE0xh`a^QIN%{14?fg<3NuFmbM)3>$LZu_2n+q!x6+5D}b>FrWe(x>bW>k-UztZxr6Z%#qr#fkU-4<@M0DW=|KQi;aqbEq*lMxj7{6@=>)KGKe5H32Nf*50>+y(OXnWL~;28 zB%3We`o>$@I0;g^ptjR$|EfR3T~E+`(|FaE?OxZcC~jAa3)f4GhgK@D`G_iSP$xhz$h4p)(P`9(^**Rmuk(F{MvV8Njd=2>QP z9VnE$i&`^L(UJj=!NN$gR+L7CW1^PXI5Nf}s53^`!XK=PHFr`y(<%vNK++x4iB*wi zFPywt7!ikU2~UYM3aVP8;fAEY`7+nzkqaX&&@Iv^jQgDO&a**19A|DafV#HY#0wu#4fpbPO$$ zO*bWq1X0a1rGr#NKu!Nfil`+#C{oPg6zYYTYR|i4Ov!ZqK|a|ZD9Z^JoxRI4-ySKl z`;&N0CJ~CMVi8OS)j81z>Uq;dnb;XS7Olbne2}2t)1pKpdEaLu z-#wR}Z89Po5jW$uj4v)*B9DJN}}wngzpA)N*F}}EwAiNT!@|B zRwc$3jW?v{2^cNpIItSCh~)x_G|-Ws*(I)8)R>p03}1XX8F^vsnCRW}vO`=zAgD{W zN-@h#wg#a_j1>DrHG~b5%Y}%>2z;AmiPtMUdDj|~iC%dc>hWMDGRP91bC`-^MJnZA z>=k%u^2Uv=8Y>o0KQf`g`;!BCAeVK-W2?%VnCnb4G*|w_F`{$D&{NyU2u2C?n|jqQ z*$T8|5&ND%e(>(Ce1%;bs>t*V4f}Ymo@fGEJdBoYYN=ki*!w)8XV@7`Rjm@Stc7f< znru+Y6w81j6*xpX{T49i?RNY5wL6%5(MP~&;l-m~T|q$Swd?zdD^Fw_Q0q1EEGqH4 zyX7W)ea{s{BT=H!oGoq&bC>2^D3wHsk&oqI?U|8(s2*I!tz*={vqURi9@KfQZSYh- zVJnL)b>s68NeB+s1x(AiS%Ttv6@VKDL3!n(jrg&+U)Uh~+vE0rrB%~V1jmCA>{Scp zkV9>5d&5;|D0?c4&MpDayN-5=s=gP%fgJcwM&d3^8R;yWu6$YFyb%E;BiWI|3NC$G zi}(;YyI-{WCHBaVMKO*@yxL`U(h+9l31J7$gF-*-U5&CFTe%_akWJxut$DfJfsaGQ zEN+tLM^mvgj?$`OkmPX$fEk%KCj>0PWsvkZ2$8;Z@J-Ui2Oq9Ihs#im)+X#!;f8m8 zoB*alD`3wPx%<)V9Ns&UF&jx>AK;NSh{;{(7ouP?ZC_8-kb8~L))QWqP&MXMzD9gm zqI*S%8j0bwb+8N+cCkQQ*n{mgO^}`HQ;!bpijH_CD&qhksEN#y;~()2EN?)!ubc5v zufhg>Z);4t%`fQ(Y8bA+kBTF`{?t7=%#!t?y$`agt$m=rtu_xkx@LB^TGl9tT0oRk^xePHl8w&O zG2}-l-zdhP$`6z3UACxCEjs&H{3P?WxI&ad=>CXT&kGl%6^!mnSbo_WStNLR9f9|Z z7d-_@T=$XnXSd9Y`gkP`sQN&?s}OovM+b&2Kt|3?9V-ZWb}#?4xo8(U#oLUHL~oo0 zWw$#YW*|tjg({4 zF%c_k@AM2-^nnDAachY?_p-Y_mYYQ<7#&s1Aal!RgYH3#-bE|3(b4-aLo-<@ z#XJopfIXx#kV-h3Bt=hmw4RuNlO~uG8V31|gALjOay9dZJ3Ept)m4Q0^)Om6@lB3H zb^hnoJy-l?AySXuir_UOakVZZaskcR=HLm6joWnd*0vT!@XX)A$vK@|MUYTe>-nek zg`2MsuNIE$z$#V9Y$-=R?M2yo-dJV%f+Vm{*;O|ESHJ-nx3SKAV({R5z3%p^UAW#m zM-k|mQ8Q=V7#G2lYByez-9A?s3zZ$;L{&E(z5MFNF{B;QY=;{PMqTw4@QqGryKtml zDT#>9>xZM;_m2L2Q4Ti(j=5iSs*CXM>MtOEk13d2a!z~DdiMd^!SLD5tpN;NeZXS< z+M?u_j!`?UqMRM^uMFBTlb;R{Z66PUf;}M8iH57{IMN>VZ6$Tri|3hImhY+7>vr-N z=j#N?pLWJtJeS=cI&tps2i7AdBr%J4eEaBkr?_*wuV_camwG`a;-DXUc3d-Q38%a` zwCB6(ZlH=3Ek4MS*2&ExvZ{)P#bj#sotz)@P)5OIMyEvLezkgQ*MnlI77tFZ9@`FQ z@6BB@i)K+{!iJ40G?l%Q&OjWbW0Ng=vBT8a;5Ib^Sbz2dPdPlwd`Zk&t=}~UUQHW7C)xCA3g6no|cMelxG}!)o;=pfAz8tf{2g+ zOfkQhP^%YmQeQ{K(iDd77gz3$CyxFV=c?3*kO=8jkc)@1=!k9zCbtI8qRTYKh*gpA zO2vPCUViy`?+RhJ{f;_6Nt6hn5jM zIS3z*A}I&s_j?3DYx-9w)KRmUd#qV zdx8XF=}D&5DJ8lYC)+pmZR^wgQ5oqb4@os~)auB1diHWsj$=b!%|M1)e1Q?$i(B2$ zpFF=*U5rnmiPxmN@SR_3v7v}h_j-l(>PN!VyZg;%q3T$saUzlbcfjVTdb*1`HYE3e z9?A}9ykEZ0y2WfZ(cIMuEpl$iS-Qt{!rW7&&pq1Q*{stOXW{k2oK~i5Ep6eGIdoKG z_(*WOPsFgNhs6nnVZUgLlVzHIrv{Es51)KzF+FZk$ue6^Ob#^DY$jp(-}O_MuBUBW z=b2Uq4GtaF=zI4z>6VLDp8@)#V}n@d$eYNX5lfAC&A8MLW>Fhu4Dkk8g)aJ%`idCI?Xr0BOiH1Mb(TK*ziKD2i8u5@)Au&f3yc8p{F0=}6Sto6| zJktT7#;_T^TaplNt8iBUk?5}`{G<&3#KKRZ8K+;v{-nn&0d`2NEp3kSQfuO2N zG*Ot4w=K%eGD@XcUdpO$9fJBAE%k=X5FcmcHiwxQrbb-XP>_Jk?+*RvHo7_)yWBH&hVqYwGj?^}Fqz5tLl!aNx@}KXk1CF7R&82qg4%}$ zS3jC4)#Wk z8g2SPTLGs2QOee1fozYYY|)zRll}sMtkH+u?wnVGWVq3LbR!bU2Y{m-?&FrB z4WHq~=E?i=@f~?m4z?&>-6V(u3Tmxx-ZvajjlXtOdL&eteWP$x8ddtXa8mV;2+02E z4r=}!muq6_y1@uaQfyF1O(*LfDcir(#6gvC_q$p5bE|7F{fyhA^n^4~UES9<-*p;) zffZ)f0X?O%>ucQ~tcs;)Z}le?f34o`J5XX*!(E*oU-KDVk8q5P2b>zA2laMM-f%n$ z^rW1+tzqsUs))KTCAp{$Ja|idD36HCnA*`gt&zWbtN#JmjUI8yc!}AJ`L{jBo~EJe zcO1e|_hHkDqD}-Elpvy=`bm-+&}*Jj{uX9-lw@gDG55&%P?lB%%rgdG9jvrt*4(vVVB0Rg-pv-o6OZ6a3&r|_zR_I#kW>!T*?So5M z-ozcn9$x)wI`hKCV_9M>4-0jA}oveh^15j=57J?S6LRNdwtV|E7D} zv^!@vrdRMu)4&tX?zs|i50>bYTjm}k*U|WcPezJ7#(&;9L`1qU=f>wfM$GScbKwXs z{Lu@Kdz$l!RC@F1>=~1NCHnINr*myr|A=n{?5gJHd6Ba*PT^A?Zr43U?K_38b0BEGB z!op_S5;8e5)EEy|S_e}B&5rbvHuo?zD-Dh6Et|H|ZKtKH^d4Hf7mY%sL(hLVGP6~1 zc+a3?_eHXP+Dm@ikcWeMUfN20l^Q*X{%zmtx(ybBD|2uB@#5ckmkGB#nq9$5LR?$Dne_ zgTb!xD9xiJpZ!^IfpHrr3|YD)!P_aAY-WF42^7>&x<{6mvvz4lShbr@IO1Vg!W-IEm~aIf>T6^(cc~Tg^ppGrXLTy{VYus(vsv zu{TZL)lW&N!=a=p-dPT+bkFURpqi(IscG7rMWz7cAo@24{WruNz^@whU#>SFfBjuG z|4Q>Ce)!8MBqW4iIDd2eUPiwQ=U;5ToUgC1?|)mhF(2ZVqdMH-QnOqv511t*9c6uJ zTAec=v$l_Y1WZNZ9FM)BCEMcrNT^`@(9*pn-b7KkraW}HcMR}1!;9y$k{YM^>mSP> z2KdX8_S=uh-_pMcWWFD9|Ni~IEouK)1%I!S-&dT!N`7wve&Kv+;_of+-<6MWA+nBf zfn#p08hDIra#MX&n}Kr)rl!hND61p~V?f+fvRqZ70uxMFUe~ad#MqF_tOO;~;`mu) zP9ay7chISoFq3s{gIXQA?15xbpOXk&T~0wsMg8|;SNN-$zkp;Yf3$$k|8U}8b|@+t zR(*TrlIidJBmUQ0w(#GQ0(}j{!QpTe3Wdkx`R|odklLp5UovVGG!#@7<<O7?mO0};vu=u6;eW;5 zM4kLA?k4s>;%-v^N?+v1-9#t+CB95%CMR8E@YiZ~UN+xw_wP`15vwS#F}3JMdT~eQ zzjMtEjq(2lZN8Dv-kI`SR#s9{QeO4fg6Bi{?VauXHUEDy&bRt*4GjLvU3U9l?y_t5 zr}^19|KTnh9QdsieDY*=adDA9yz$>XFaG}qnfc7}F`W4SXca5{7qh%x!bw;8uU4^F zeHlCdvWoqSS(ZsS_YbqII$0u=B`rau?2m~=S>Gh~HTD-bpfoQ3+~Iv9X19#poKEiG z5EUKXZ*-~ig?e(NQg6w1U(@|A)#qT?Yv@+CEE#4I97YKkEVqf-sqfmsF;BM-Qtb-p zdowt&=i(kYQg9X#V=r^nLs!K&VPLBKi1ShlaZyHbhu3X|g%Yk$q_BtHn2!T`1rh^p zIegdTm{)8ZL3>*~@%~{xoR`i?Zu&4_YDL!CTylD-=bu`mo~o_=-C%G}$npA0iKUBu zVuR|E4MjIQH#fKMZl?w(>g)vYh118m*efD<%MirroEfs>Mp~h}z%aRJJ6`043`NOB z1w{y&Ul>t}*;o(=-v?iGKd#)NGU|lUFIRL>rr`=+{n)rA zVcCoDz=zT3eeXWSH6Oaryc`G9XAh#az>-3=mIt&Pi}MK|!>Zs)JP?KA-y<)fWO@DA zqL!Ea5Lh7+l~<&Y7_GGL#X(drrgNzupHDL6G{tsUFuRlr1NOzO-#e+@i~q3}iM9^t z7Y)kMm?|K`=hLh>Hsr^Hoo}8da!Z|pAjIoevi^A9>a^m=wg#+>8$Q3fkVs@G zh37H{m9{q|*LS@#?Ho}eNIC`f6v(Vb_QN2$Gx&l*Ucc-Wl0u)-<-%3y7>qAYL))E- zRH|L|&woZn#$(@agDYK`ds4R{~ zP@Xl%D)&&3<3p^|UcF+!heA&ACsC)oiO?LgQ0}SnwXysGSfDqHQYk;J7L;^O>6mgZ z;f3K6Hol&F3aRBaM$YC5ZKJG;w!}3ngj_I_g}oED9E+DZ(z&kw-1zB;e)Jpz-HeNHbo=KlD|#t1seo`mBXlz>52ZpE*Dh@m8!`;@?cTJ75g_?oDG#YeYF73%qfl);vg(%n@k1P}*PO#-9 zo>OO67&tK89y(I+e9F?G$v{GDO;6tg`_qvpundqF?+~XuFlM0&je~6F=X=U#i(TqLRtZi1X3jU69$&^0FH|`ksQS5jThk){cddv!_%h?`?v4(LI--Umu5Th z_Acr7M;7wpN($}ph7|d?D=`-kQ)ZdR&)J3zH9I-9Jfn2EILZq)6FKi@-H-;VvE2O> z^^>nCAh zFHEw~nW?73o0=08G{{=&-56+o`wsX!3cKzb4CcRsJZ#1SPZxO*(|s#uH#K-DFImW; z*+VU{wz@kgG63=0>o`j0$(G%o%L>)&;^jiL7OympqMjM^86LhRrb-ZRE6G=2Ic_T}|o!Aq37lWHR8 z_Y;uc28dt)eYbREBF3=V9S7#i`*^o2snmQ>XarCP%r6#;@_O(YMOVFdjQwEOT#R=z~_aHv}~IwHE3^n z%WWAkIgPR>(MtFypqNV}VGjL1K>f9V{kzAYk$yZ{myLc)8ry{H#IB|1`MsinxxQ9qp%?es7=13e!F}38dEg5PtAb`#bu@4uerUek_1j zI|d;yRukNREQ(KV9MHSFmKy%!xo)+==sx@P^n#lU!kdto@k90N8Qni#lJQ7H8M zC(-}+L1 z(fz7SaW<1^Xhh)os8e*@iB-C}1ti(QhfXYAiqUJa|I!sTy|gFS0)hfRM^CfF#3jBz z*$#ytksgFxAV#6|8b9}Lfuqi~cfU~HZ%|60Na|nwL5Uyg+~%!6zLATWwRAQ8$>^E|$K zsah=b|yQWdD-0^S2~z0{a{jab+M`aXbhA)V(4gzd_W zLN-MqDFU)2a2r`*uP?Y57tLpu)x3h)T=3isuB|o z7}qxvnH`K)gOuqF(Bd_0nguioB(7Wpj}ir^X(5xM$ooXWmo682$b!5M?BR_N4q0%T zn@UCD4&F#i+DN`S3Z{uCWw@mL+PG>_3Ebmh*{smCj73~5LZOajq$S z$EII~QD??popT!PK_(KJre6ujLz%*9r;z)i_I74!`elL(nL=rqwuV_M=q!iO%>5F= z)>c^_hAGZpgI)Ym-1v!lON3)zw?k3z>MG$?0eF;Hmcs~B+yNH&CLt&^+fyR&$QT%2 z4LYhGxLkFJhLb}_flXIotHlr_7Ipzuvg%RCMNaM$T#+dLkbvNj zi?=+=x$IXQVVHec;wsVskp`l7Q$ZXP)gFLNBSQ#*K>upd79wt#Ru)%UdIycowt z?A5rNJ@+u)V5chD?+m9o@u(BN%+yV#R(kC{9GT zlhG+w*@D)f0}%%&0f%WgwHnZmiGfX_hBq4#hy^xF;(}#OneB2JYK^sv1W3?ugG`Bc zp`aHKg7kuDsFh;(m7*Cn{JEqLuAm$DsvZyCwORmAtKg&BG!_&_hNW|Bdog+FHJ0cs z%8$&(t<@kG;CG3L-AwF3YlwX#YSIccF#X@jP614LEHD`jJ@DRBQ^l1igk*^M}>aFmYj# z3)NS>lL)j9yS!kTbF zMnEGOEwF}3BZKz0!OgC!RA!Sg7g5&;N+tm<9LSk~5;&x~G53b?lN);qz@0RlAPs&s z05Z&~g{@U1WnowR!5s$83gHFHNzDn};B~cdjgw%nE11N?F?sNyH(-P@?Bv@U!ZpoT zje)xXt%3n9FlGz93Cdl?9OShhwrPP=AV~nWi;kP3l^x);bdeFTDcnVWaDs*F1aI;U z1ecfNQdUtGJQR%9=39U~!YUg1UbUqg{GQuc;NFf3Y=_xEB5p&DJT8gbRLO}s+2MYo zG_kv*Iw>MePP4HG5+N2RySvC>7wbCwT{&V6caRPtCPRMagI-c4H>lWxPGkkOq?U{E zlEnxb0|DySu7nr;N@8OI+0`^SezxqH)aCnBtNX;}7sdfKT{ZFHy@FF6-3A>@sT=m9 zSkD4jBX!7mt`)<$<`oDF!M53(0HdtypOQGXCqYL*5J5o;B|}<`!QI^90|9-ow7zq= zI)qLw-l6}YD;&md!uA#IT=lS0>)Aeg(ai+#i0GW&bih@SKno)7+H4a-2U+38 zWG*Za%WcGQ0qlCksbqg#6>IdPG#3Y8xm;WVwS1HxHuDpA(G78zgS*>^JN~V!k%yaN zb;GC#Y!8k6DeZ$lDhTOp&MBES+$D06+$+#gRj$u2RmMAK` zdkWVFRM2q4t0)4rAwM}BhsL~nbgh(B z9P62X_!8NB_rt=%-fK5O<_dwGk{ymJ7GpE9XkmpsNbdDnML)34GwMvy%-g!4cKD(d*|1dnP-7&W!z>Q)-{>Iwo_}Z;bT6eB z1(|y^KQFnNGfy*|$1F@f^?ZsGc>2S3enhZs-h&V_BkdRZP<$$TrDy*9m~gORvU*6) zr_)dC56unBJe|E>fMWoOou$z;!U7zKaX{|)1)e}(ZmQqNUfI0c2K>j^EIJTUUk`j( zg?I%MbIU!0-)D+dgb^Ncm_#=oG7%gfD-?nB={WRDlW|9g7~6Vq7ZK+{$LPFkjM;p5 zXSxqn%K>YN7&W$a_CQ;M>4k6LBf`F zafG*WUUD z`f*1`j1tDj>+UAL=4YBA9Iijgzy4(Q`ZLLTm`?r6u=+P$^?bZqSH0n*f5TdS!}r8x zfxt0X-#f6c(LrkkNm&t~KC0rNgxD)bezj-*0X}xIl2w2Ii}Jo*llOWv6{M(->tJsn zb#Ca}+}QO56sO`uF)hW$EhnsBz?xdn?AJ(0tD{XTjMfTEYjsZ=+VYO0@MB2g**k3C z8ZU1N;RScn#N?h{&v;rAi7kHqcKtTw!?mYx;Ps&Xv*y3x>TuyLWUMC-4BP{Wo3_E#1vF-EB$T8h5${*1G!&umj)kBiUedJMTN?$pa8p1vu=3VDTFO@D%8^^xISzvT8b$2W zAouB}OziaJ2rTr;&OLe%0cQRQX79gy_zFmb9KYFGsUkS zT>4b8d)!5K$o%BcA&o=zCTD(q8}c%F*RvlyQ8;{ZZWy*YJc8p-3og~jQN`pKXn{zF z$6PiCH?3iCN`864{J?4H?DgQa1r@58q4)q#X90I~i{wsLcHctoVIg^Yy}y}d!}IT7 z%-nyypKlctQp~5u-YqrESoH9}QGJJ`f5|TtmE>irj6>>;o|zw-{3kAc|?h)9t6b7S*ErjgKcy`wq+ga>iVt`4v*4ZM?w zQS}H2UWVFT-Kxd%52hS5O*Nz_m62XRAlM53yic2>y#6gmcW7~+&fb%qjdhCwl_-11XNVR=TG!h29|XNlvm?`S0Tq^=!%Q}TV?{R#)Vg!w+W^W(l)_Mv>)CEWoP zMQ${?Q_`B&pZay>(up?1g+$m+)nw0_^+XlIq3ZIj672EsY}l%erB>nA&XTDuh~@>6 zqf|XKL&j0ZqBPdgUD%%CD~!x?i*{uz9rdS}i@Rx2RYoc+6T;}8_hP~?gukp$J(Itp zBq>gePVU!$n^`H^KGjx}uD_<{8XcH`U!>=1o2AW_N<}X~MJGh>&Dq}-pw8=9IH$Q0 z;r4+(=UWJg^RgOr_RpUkfs;jESEf=@^X+Qm71FnfcoWmy=j8CGzF7$;FNplme}Qh~ zz_GZ>$e*gFjK)!f(zji7b*ibvC=0%+$p6A1ZM(SDnQbS8r@(9n`_Y&_vVsLF4?-j3 z+gb2?r28gcH>%`zdff(eo6}nJqRoXQOm%|pA@H9RFF7XeH`Qn~+b|FGY&($t=wtZtMH=DClqB31CSyv zKn%KxCD0yVz9o#VXGWJ5FzPUmB~Ih4mx#h#S_Y=DDPG#3FD={5ObA@SLBq-d(KE94 zmvcLbVLtJac!HX^uoAqAzSWa_WDLFt^DncOB&U08SNaaAAGh(M>WqoUmhOtd9X$)9 zLgWg+PN>eoVMDnLyX4Iorn|Q|Yd40k} zv_`)KGXOdsv`%+@`l19Kh%}bs+<}A~Aga@c)oTJuL;W-Kljt#8%r%y3^Z_xnBzXrd z7d);aFSgecsbNw^b+m6#`f8iz-}PD?GeHw=s7rvV1Vp)U9TVE5$%6RDVC)}Gb@Pc; z8A|>S6aRo?-3NV5!@vX|gHj!Frm5$OWOTkBQ52~^<|Y~h_O9~K9i7_d~i1Rd%rlDyLm>1V}dexBI;#w<}%n-vN^X3<5xTlTv zvgBNgnu@;LO|Q0=oG`B?y{+tyi*_bDhoYd~vXjyr)6Uyj%2}4@fWl9wqvlP3T_r-(Q|z)&es)ZTIbsNjeWMFihn*AGv21PdwQEUB=lp zrQcn}YeJsmhYD;e5v|YNW$Ptj<9gRLE4nI1^9<$it_F9p5uUj={3DhICp;Zji`7YG zB>SjD0k;be?I>KcJt-k2h?Ck8UfXEK z`v+;l*)P;8T05Nk9rHT|-)JwG+4?N&OLe7QQV!r;==5=x~huSjaVt9D46vY=`xMn-y(!5rRA$So`dgR~Oo4 zVpdMwZ#ZM~`9kNNOJ|Bbr0$D6*x5Dz^GrxwcTvo8ne%1QQ)XSkLIs{ErD4Fw6QrOWQ)Qy2<7=scUK1| zWVBtYt&)6k2S@$=+1zJ>CydUaCKKD=)X?Bjef2?B8Z#CE<; zT<`N&;pxE!Up@!tVSL&yKr#Xli3?b zUlndxKfSNoUb41|D@PZtXzaF9AxlY#4{+aI>e}o42IcgFP_fN5Mx~9m@&B-Q-%m}3 zjl1B}2|b}nC-jc=CM7_C(0eb2UZqJBDG3A!Rk}2h-g^f@=^X_GMMMNCqF6vdQBjuf zdC%@SvvYQLW@q-d&7bgOCb{Q+uFrMZOO)As_PZR~9sTPEw0Yk|zRY!bL52p#76@u% zwriQzN62)7;u(i>tJ3W&>SR>^<{d;Rzu z=Q1G-B*o$?Woa3eK?${Qyl6VO1`nT>CZDz|pKdV-o5QD12R9hwGm6*nI;=J^S1Uq` z(3C?VIQ5@?`0EImA-SxTzWARK)!d~gCsrqT1W>mONW|V>>GhH&d8k>|d{Ns(>56Xb3xLamkJucI_Q zi3-?H;(B>1rw$Y0=90Ubt(W_*5~a*3-zg=kizUYCbdzUVKYwf`tI!EIOed6&T;E8U z`VfD2V%mO5x70RKHcOrLE~~4d=H`tlB0hzEZz@r?FX^H!_QnV>p&j(PqNWSPxTgnvpP9u3Y=|t$>cR+3_ zO7;gSy9BpDT$lbMS$ch!R_ZD{E+Zqm+W@sof+^`KHgzdgAzrW>ndQv%48aw=Xl23uYlhc13cHIbCKa?jx}@k{T0@;6_&qd9FykK z;ubQh7P3Ybm&{h?P;U!`7<0J{3#A7Z$}P`@waq3~>C3?#KH?G`GZ?+qj$4^#^?RopH9X9;JaC)M6~Mw}^b zF@rl()3+g6n{Ti=Y$ywS7+lcMm!C-z10?#O;xRE+{V7)13`=nIs#?BEuLazIA^*wz zY=^_kC!KTs4yF3qjngTmk`1NvKpxG_)Clb{8*-Q{7V3h7>S(D<`oX8FSbfSfBo!?| zLeNW7xJ_^dONgpX=o<@UzNMUS8H%9#>b82;{*dhH(4>Ce)Sa?Jz3a*KWk)+r8mo&y zgk7W)IX9rpN|CFKft2)F=Vve+w_QWhUNuEQ(}rzRW^FS++h&B>J6Pqov*X{5Hv`Wf z0$9t-3P}Y2$K>&9dxUaqR(XFpijod~w2PvpgMa4b?mbgubmczbeZtbneGH+7Epgv> zBJZ+o!(EWR>}-^r!LJu4VkQPJ@A9;Q63w&dQlQZ4TUIssMKOH(x-DxN9!rx8BX1nh ziSQ~Z5nl3A^eEfr_Vy-qHShR9m5MnDaZ!b4t=3fLBVenwefbEuQEYLw{$TZ`;YKy< zDW7TMWc3{R6Dw;10{c7$ZstuK7teDHAvuM>#o-&3`~Z`~#Oeo*wk?jo^Q`8@ZJMhv zU9UA04>hktv@Gnl%J>%iT^*X-$CHD`1q=j3^=Xy;1#eiwRT$vE|LUc#zfM-%io1pU zVFhOsO8U-bEMzy~VF*W+!=|+@X^s<3P#~LqXM&5p{p9At*|x|jN!tD8@aRM)D7CdE z1xYj6(mrP%-H?RVwi;>}^VTLrP9~AGE2TwL{7B38B1L~4Ub5*BhDnr&ByL;HZG zbw?{-j_Ws1Do=+x>b6sL%!iO7KZ&uY_iA6boKd*$aRBzkU6af4_S^9`1Ri22>2;^c zQU1E5A`F`4C?3u%@x%4@tqr@xnRWk`aQ=t8po%oh)&u*S7}%ca$qaMlOCn;HxGljX zqIW#E8*ZqVY^(PGA))aD4yv^SJMTZnB^LX;w|>*J4j&fd#2eTmWB{;pwx5`)Xxs z@^8!KDA!PAmwbx>`PRL2vHITA)6Q%fBWckru+*ZQg^}_jgl=k=7v5+| zQ$*E`R_zf9^ZX@C(>xLT;fJkHGII*6&!Mi2alz<|BdbJ{k;C*C*L)v)`qR9vqu!kb z_7u}qN|I~cHQ0 zQFGjK1zKu|L#-q&tru~tp7+Opd~7@LoqT$*9?rz?v3B|L>8+N3W|GtvT+{(f`~gfH z7B`Fn_MHO`1kB~bp35gbKPU_M*cx!eWWMXX{HfP$n=?RaIN+Ej@MOxIKWs&SBkWH@|;jg|T$x?~>`A&)^Eakc8K_?aPZ<9AnxUdv$8!BEu zf;bYVLXuQXY+0a}d^V!u??V(M60g&KXSR8$$prU7 zF>us{+z@O6J^4mFhWISSv%ZeCsT5%3O`>BsC`e`zO7CcNNVl9 zUnAl!Bk;Omes9U`zE1fBk>LLr62Im~>YhH%0$!5to!4T27LUXyJZvd2jr5QS4UUUU zNsdg2$c%pZGl)DgSN-a(?=S23De-~Z^?E|r(n#jy-WtEc?%PZhKHMm3A>W`l783jw zW_G87E2`4uPW-d)!&^|U4#4#fiRQ=IuqDtI8gKcT+mH-K+5S6Ckx^b)Apn` z?srSp?*~o4J4d5hp7r=Fff8x+v&o_Ygrf%A?mkKm4@%godj(~Cm^9Y~czhq`dzdH< z1mV1cABiRUFo)m61^Y%`lv8Jn{QSd2eldFeV*I8J#58&;4sIG5z4W!L|NdQ{fxFMu zV^+D&R2Jo|*ajbG^*(rguFaCKb;2;6X?!yab}20Q*URU}>SfG!-5)~R=Xbvu$Rn6% z5B?6&#vTO5R*~J@*S+;NBKBk3Ki}kkhu8g&-o}233;*=&U;3BW6Rt}Ou79nt%k;9? zZ#OT`6yl6^E}K2$e%+5dNsRL;i@V^u{M{EP{xt5Aj06CY(TRBm-WXfz6 z4=hRAccJ#dgL5TrDIdRCZM5kMaFPt1b67MMyV;u!cVA2>C@*Jl?jwsMkjdw5$5ShxqO6cf zc|bWXC5XQ8T0=ii8vU)GJn2$fYY(t$rdrP0#;EZH9%e;%g6LBJ3OJK7o3sg-F*JwD zX5%{ywMh8v1e!kFiYepMk+HWjYEzd)^!3Mi&^cW=z#`S-NU7d|gblFdc5}(JJ1Fvr zQUs7_I27m*i>iCJX#@(92kD8dQ9{PDNyJH6o(@Ec%y*y;kax@Vm$O!HS(uyIQ!!>?vkj~8DuHjBn7gC`qf}<@S_0t154U7 zC84gx!9=0%Wz8lbY`WS6UgnkWks(Dg0l3&iyI-Cz!*)wVR9cB@JDmcKN#KO0jZ{)f ze{D=n*ZSs}H!A!0_l=BZ#GTH0OpW0#<=DN9g7-;0nJ0lys#I2+bimi5gT@rz*^7nH zYKaAF!SgyPGtsgi#pZPV3B4 zd%&Y*!+J_Q^ByjcaZr&sBhJE&0>7}EH{DAS*6cPAW+92ocOZDY2rq0O$^j->Dio3H zZn?u9uheVB$L*@l=_JcW>Ms5@eG^R}JKsd9O0#b3a%6&1ZePXL#PEQc`B~63nA>bi3H1z)MTI8WN_u z7Lx#G#pelz)P|QbFav2Z9oJBIg0(An2AmEK{><%~n8tgxD*SiGg?F8 z>3Pd_I1ds!@+p8TXo_!|LYhaTSlBCX&NBo^^@CYk6yE~*57~EM9D}JO;3)4`P7i5_PLGGQ=k%Ib}sU@Uek&jd`YaYwBK)nC_eiTfyEY9 z5->EuQmzKbGLj7yz;Nhln&$+3n0&BRua|csb>nvRE+<+1G_bopH)fOcRdWU3(*p%I zGw3m1(hbb+`gj!dh8767eb+M`G|gL{hY4#H86=#W<=%M2FP+<9xJs2P_h^q_L&t8; zle4;Ten>$6>RnB-sfFA#VlBE_cp(5bQv6y)Q2F^CgOJ*Tl5GaB>#yvLEgQSaehj@A z*w-`kJMMb;??MpW5}a|9j-mK?wa)ZVILFeHSiu-9q{b_u|MoUV{?#EcSWG zB`almWd=fu0^TC>GR0M{*=e}R`ib3Y%Zg>0r_-UGFi*mh%_Pj&Je(;&?&aiGS8Mp> zk<-Hgo5wt1#d20c_F%U04eWILhoYnuW>Nc4Wv0&BH~a2AtoX+gj9sWZzF43G@@#Wf z^*Eb028QFhIIL6q`^y9`n*)TNff!u5&8T&z7?J@l*B%RL3q!r>otUphX*T`+bkhgMpWj~95Vq{ zJ5i=wpr<#k%5=!^^q79NNcQ|Z?$G?@uTP9^ch)FXtwwCLPCa@g6>U;KacA55=c)UC z(xiMWBb8cpp~KkLKS`c+67ONS$m$2_RcIkWQwzqIxbZ$>L9B>6Yq>rSZU3@?HruBj z*;1$Rtj}Xelhw`d5$1OU6CX)yAZ);Ev8$s=UphUx#|BO^?R=`-@;^kU4<^+c^R9R! z0@V^WlWZw@QV7`V@PYScP0i1407Eqf?O$jq$S#w@*?y_rccZKhjQRZiKi=%$udl+ZkKjvQg(+ zTx7u{JI8mjW9{B_cPeiV2W2v4MouizpeYC9kF0)sCvekj$|6?PT77km zD{xNAMB;W>s!(H=x6emzcf`Ie<%DLI$VP)uQa9H&iSbFUMKGNa<0S~ zF+BPM;v{6UqV;Ub*y*EYgz|EqM0j5KnvKf^dxFyO@v<9vXbsNHDE2b2e(p@$u=B+VG+Um%{)pzV{^vEEaIp z*Ie00mexPX!#vA%34rsolKthg+|FVriVju1g~mh>7^%)&8JK%#ij(3!G)9=JX@7^Nk3Dvthv<1Lv@+m@+ttM)5 zqY|cI1T!3>VM9$EmBm3?P$Lpq&j5KKvawf~^Z5FXhh+l~=SI0+Xcz8|YVycpB>FYA zZ~$5C!=xVlGwPA=N*G$hVyee4ndK;$&NG;zowcy{wfs?EWG^R<80Ozlk4WH<MBN$?Oi#r z@*03Afx$id?Fcs$bJ8r1uzsJ+*2Vpu+(UsrTwdTNu-z5qL3W%KBl@ZT`n_K;UWaL z@dnD_mIEP6Fy(w=1@wawk!~PgL7GW1$xZxA*@)F1=GdUY6fZ$6`FOR(EU6_^Ps#5Z zk9dRos-Z8t_X41(A&cNrrvV7DCPA^;vgw+_>#(>Y5)?ZgLX8)UF8^Eo&PIjLjLBmI zxFv4Qfng}sVh6y)N-3?cPWL0;e49Ke>{&ICmWCYJp&Y^HN^RLPvpJqhDzshMeg&yY zyY}Xc287ReVupYL?~O=fTpd(4G1$_w?tNQLK6}Kq0+N;yW6`%CG;*%?YWGEE+A3k}7Mz8oJ3dP0C%ky7qQux32 zUc@EEBormCn54AK|1gqQI`V&(k5`*O+J>6{_teqc+}zU9{C}p7&hF0tp^h#t#_sNB zUfvcsfBXNXYV`2)_`gz(ckkZ)KMdo40OS8yQ2BpShgaS(K0f|R7iQ*W{&!ti{D0Pk z4=W!2ztDxX4Yke9|D_B6m$dNz!4;1GcUQQ+zJBEjKYsjpH463b-@mKx?EnA#fAIva z_@wxM_#_ZSE5cqFv@@DSL;jm|o>e)S#w_EzIn(jK^U2QY+1&reCs+6|Y1f6P(iLy- zOr*;>{7-yRzuSAZlp@=DqUc0Gr$(nr|ABr_<64t#S0m9rOH`wcs^n_#4XF8|(_V`e zrNNUh3-e!8^C%f{hYiBCSLI0ZEpM-8Sgyw?_e$#2UCU<`Vz7Y0^I$xVlK9}@ML}8$ zYayOU08z_6mcg>T0yqU@Wnfsq*C6uKZ4YtTH&Ea9(qIyXtJ)3k<&qE9lUGfRYICh#Ro!PkPQsY z8lgwvOnaf%mL>5toQ#^fs z=T+X+q#dd4$<(8pMH!Be9HEIqUw%L!oT;jboQez>KAnF^S+55X2Mve}w#ZsC&S`P@ zCR%MK*c5CYApE)8)|Xk}K2Ec5i8_&;I7$)dsEerK2wfV7fN{qYgP%fr5gE!NKujh> zu-_5|VF(e=Mv!g?(H$aJHBjP~OYu}-D+Gwmjfem>YM(9<$ovhZt^%i+9Rf08Pe}uX z4Q-E~RDhXd$h09m&038#5J)PrzJi!TDFv*7;rzLe(2st2?E#KyP}hekPuB53r-d7C z-H?=^nP3Q`Ik?Bt=0R!ChU*`v`_R9V)0!_BLzDXY}_}B6WfCnZR!D5VHP}CNu9EPlL9U^qD{_?YQ zAQ)La(`QU)%81ti0VEIo#G(Bb%1?0ig33>LB9|9y|WYCPf7y z)x-WSC$pXZG1F=6e?3Q}Fkr?XD3U=qmvqSuml)&pf65%{1%^@hI4v-v9>Eb9JF?l4 zGOEfOZ|@u@m;pHrNp)Folpw-#hc8a3=s<$U+z3YI5wJ6QoV8$tTlO0$#(S@dRNan( zGGi()soICXWHgfbF=KwgBnJr0+thJU=&g;Pu=pXMi-w`42826-Eb;^>>VVz9Vli^{ zI#)fLbj%azBGFI&SjuYB2Rze5jvq7JD&OKM0*G~#8vy`5NysY9mDE5u&LVwEfe+{P z>^L2v@6K0pC=ombg(e0i{(m{!__(vH*uQ2{QYmP`hKYP(Mjp%%R63$wRrahQ9+6K4I!Q*KbDJNCYN6oB zC%_q8V{9Z){FV~Hk)|A=8q|cs!fZz$=f`Od;yJ!$k>#52fg@)UI8sv#o&Pq=+wY5L zBif;=hXiudahwJU2lsk9nuRuM@zp)Y>QG$g(cxIoMDH=BU81&uPHS*bNE2d%l?b#k zS_Wr^XTSEH0QAejL&j%1wH9_dAt;FeqAUQpFp3Zj!}=Wnvfp^}0iQOFvHK?{(%400 zH5s)J#g?7xkkXJn)^S3Vh^3lz|Vem`>xXfq(ZYquw`df!baU-_$q;xEip7 zZ=8=JGLQAw9iF~WCXPB~w?Cvl1kMc=Wq}?^Vs+TJgRDT+jMH|1{&i-rGH1nHrPN^HyYhr!O zfPO(uPeJbxzl7qbf!-HQ(EAV29NzrSVh2qRK?l3b8G}}8H=1{axeO=}S3h;YK{H~( zTjxIhm!UUn=*?tBv>e3czXgy6nDHR4C45}+8KC~xh{8PGQ?2x_1LLFW#iuE^%_hmq zLJKg|QGf*2Rop0B@_W>-Fs(*97I`*-#jb=-_%KGuruzt-Jp~}j{i4AQ7cKv-GJQ=4W;U� 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 literal 0 HcmV?d00001 diff --git a/src/calva-fmt/assets/infer-parens.gif b/src/calva-fmt/assets/infer-parens.gif new file mode 100644 index 0000000000000000000000000000000000000000..64007dfb57d1cc8001e52cdcf937e23f27edeaf3 GIT binary patch literal 77990 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 literal 0 HcmV?d00001 diff --git a/src/calva-fmt/assets/parinfer.gif b/src/calva-fmt/assets/parinfer.gif new file mode 100644 index 0000000000000000000000000000000000000000..06458dd1200961a490aef1f7c3267267b5cff029 GIT binary patch literal 121901 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

SCZ$)NB7k3#@FL_tFMTDFtU}K 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

ZLZwJLxUWXKqg={Sx=w>OkvH|Gl@?fz+0_= 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%0A~KWcMrwUTWGM7;K!pSbb))kc}q3BjV)~ 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[n)t$ui6c{?%~n*JS|@7z`8kWC$?v{ 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/calva-fmt/atom-language-clojure/ language-clojure +``` + +You also need to run Atom in dev mode: + +```sh +$ atom -d +``` + +When all old and new tests pass, update Calva Format's grammar from the root of the project: + +```sh +$ npm run update-grammar +``` + +Then make some sanity tests on Clojure source files in VS Code (this mainly tests that the update-grammar script works, but anyway). diff --git a/src/calva-fmt/atom-language-clojure/appveyor.yml b/src/calva-fmt/atom-language-clojure/appveyor.yml new file mode 100644 index 0000000..2b0fde4 --- /dev/null +++ b/src/calva-fmt/atom-language-clojure/appveyor.yml @@ -0,0 +1,27 @@ +version: "{build}" + +platform: x64 + +branches: + only: + - master + +clone_depth: 10 + +skip_tags: true + +environment: + APM_TEST_PACKAGES: + + matrix: + - ATOM_CHANNEL: stable + - ATOM_CHANNEL: beta + +install: + - ps: Install-Product node 4 + +build_script: + - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) + +test: off +deploy: off diff --git a/src/calva-fmt/atom-language-clojure/coffeelint.json b/src/calva-fmt/atom-language-clojure/coffeelint.json new file mode 100644 index 0000000..a5dd715 --- /dev/null +++ b/src/calva-fmt/atom-language-clojure/coffeelint.json @@ -0,0 +1,37 @@ +{ + "max_line_length": { + "level": "ignore" + }, + "no_empty_param_list": { + "level": "error" + }, + "arrow_spacing": { + "level": "error" + }, + "no_interpolation_in_single_quotes": { + "level": "error" + }, + "no_debugger": { + "level": "error" + }, + "prefer_english_operator": { + "level": "error" + }, + "colon_assignment_spacing": { + "spacing": { + "left": 0, + "right": 1 + }, + "level": "error" + }, + "braces_spacing": { + "spaces": 0, + "level": "error" + }, + "spacing_after_comma": { + "level": "error" + }, + "no_stand_alone_at": { + "level": "error" + } +} diff --git a/src/calva-fmt/atom-language-clojure/grammars/clojure.cson b/src/calva-fmt/atom-language-clojure/grammars/clojure.cson new file mode 100644 index 0000000..0a23326 --- /dev/null +++ b/src/calva-fmt/atom-language-clojure/grammars/clojure.cson @@ -0,0 +1,424 @@ +'scopeName': 'source.clojure' +'fileTypes': [ + 'boot' + 'clj' + 'clj.hl' + 'cljc' + 'cljs' + 'cljs.hl' + 'cljx' + 'clojure' + 'edn' + 'org' + 'joke' + 'joker' +] +'foldingStartMarker': '\\(\\s*$' +'foldingStopMarker': '^\\s*\\)' +'firstLineMatch': '''(?x) + # Hashbang + ^\\#!.*(?:\\s|\\/) + boot + (?:$|\\s) + | + # Modeline + (?i: + # Emacs + -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*) + clojure(script)? + (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*= + clojure + (?=\\s|:|$) + ) +''' +'name': 'Clojure' +'patterns': [ + { + 'include': '#comment' + } + { + 'include': '#shebang-comment' + } + { + 'include': '#prompt' + } + { + 'include': '#quoted-sexp' + } + { + 'include': '#sexp' + } + { + 'include': '#keyfn' + } + { + 'include': '#string' + } + { + 'include': '#vector' + } + { + 'include': '#set' + } + { + 'include': '#map' + } + { + 'include': '#regexp' + } + { + 'include': '#var' + } + { + 'include': '#constants' + } + { + 'include': '#dynamic-variables' + } + { + 'include': '#metadata' + } + { + 'include': '#namespace-symbol' + } + { + 'include': '#symbol' + } +] +'repository': + 'comment': + # NOTE: This must be kept as a begin/end match for language-todo to work + 'begin': '(?\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}|\\,))' + 'name': 'constant.keyword.clojure' + 'keyfn': + 'patterns': [ + { + 'match': '(?<=^|\\s|\\(|\\[|\\{)(if(-[-\\p{Ll}\\?]*)?|when(-[-\\p{Ll}]*)?|for(-[-\\p{Ll}]*)?|cond|do|let(-[-\\p{Ll}\\?]*)?|binding|loop|recur|fn|throw[\\p{Ll}\\-]*|try|catch|finally|([\\p{Ll}]*case))(?=(\\s|\\)|\\]|\\}))' + 'name': 'storage.control.clojure' + } + { + 'match': '(?<=^|\\s|\\(|\\[|\\{)(declare-?|(in-)?ns|import|use|require|load|compile|(def(?!ault)[\\p{Ll}\\-]*))(?=(\\s|\\)|\\]|\\}))' + 'name': 'storage.control.clojure' + } + ] + 'dynamic-variables': + 'match': '\\*[\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\d]+\\*' + 'name': 'entity.name.variable.dynamic.clojure' + 'map': + 'begin': '(\\{)' + 'beginCaptures': + '1': + 'name': 'punctuation.section.map.begin.clojure' + 'end': '(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})' + 'endCaptures': + '1': + 'name': 'punctuation.section.map.end.trailing.clojure' + '2': + 'name': 'punctuation.section.map.end.clojure' + 'name': 'meta.map.clojure' + 'patterns': [ + { + 'include': '$self' + } + ] + 'metadata': + 'patterns': [ + { + 'begin': '(\\^\\{)' + 'beginCaptures': + '1': + 'name': 'punctuation.section.metadata.map.begin.clojure' + 'end': '(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})' + 'endCaptures': + '1': + 'name': 'punctuation.section.metadata.map.end.trailing.clojure' + '2': + 'name': 'punctuation.section.metadata.map.end.clojure' + 'name': 'meta.metadata.map.clojure' + 'patterns': [ + { + 'include': '$self' + } + ] + } + { + 'begin': '(\\^)' + 'end': '(\\s)' + 'name': 'meta.metadata.simple.clojure' + 'patterns': [ + { + 'include': '#keyword' + } + { + 'include': '$self' + } + ] + } + ] + 'quoted-sexp': + 'begin': '([\'``]\\()' + 'beginCaptures': + '1': + 'name': 'punctuation.section.expression.begin.clojure' + 'end': '(\\))$|(\\)(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\))' + 'endCaptures': + '1': + 'name': 'punctuation.section.expression.end.trailing.clojure' + '2': + 'name': 'punctuation.section.expression.end.trailing.clojure' + '3': + 'name': 'punctuation.section.expression.end.clojure' + 'name': 'meta.quoted-expression.clojure' + 'patterns': [ + { + 'include': '$self' + } + ] + 'regexp': + 'begin': '#"' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.regexp.begin.clojure' + 'end': '"' + 'endCaptures': + '0': + 'name': 'punctuation.definition.regexp.end.clojure' + 'name': 'string.regexp.clojure' + 'patterns': [ + { + 'include': '#regexp_escaped_char' + } + ] + 'regexp_escaped_char': + 'match': '\\\\.' + 'name': 'constant.character.escape.clojure' + 'set': + 'begin': '(\\#\\{)' + 'beginCaptures': + '1': + 'name': 'punctuation.section.set.begin.clojure' + 'end': '(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})' + 'endCaptures': + '1': + 'name': 'punctuation.section.set.end.trailing.clojure' + '2': + 'name': 'punctuation.section.set.end.clojure' + 'name': 'meta.set.clojure' + 'patterns': [ + { + 'include': '$self' + } + ] + 'sexp': + 'begin': '(\\()' + 'beginCaptures': + '1': + 'name': 'punctuation.section.expression.begin.clojure' + 'end': '(\\))$|(\\)(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\))' + 'endCaptures': + '1': + 'name': 'punctuation.section.expression.end.trailing.clojure' + '2': + 'name': 'punctuation.section.expression.end.trailing.clojure' + '3': + 'name': 'punctuation.section.expression.end.clojure' + 'name': 'meta.expression.clojure' + 'patterns': [ + { + # ns, declare and everything that starts with def* or namespace/def* + 'begin': '(?<=\\()(ns|declare|def(?!ault)[\\w\\d._:+=>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)' + 'name': 'entity.global.clojure' + } + { + 'include': '$self' + } + ] + } + { + 'include': '#keyfn' + } + { + 'include': '#constants' + } + { + 'include': '#vector' + } + { + 'include': '#map' + } + { + 'include': '#set' + } + { + 'include': '#sexp' + } + { + 'match': '(?<=\\()([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)/([^"]+?)(?=\\s|\\))' + 'captures': + '1': + 'name': 'entity.name.namespace.clojure' + '2': + 'name': 'entity.name.function.clojure' + } + { + 'match': '(?<=\\()([^"]+?)(?=\\s|\\))' + 'captures': + '1': + 'name': 'entity.name.function.clojure' + 'patterns': [ + { + 'include': '$self' + } + ] + } + { + 'include': '$self' + } + ] + 'shebang-comment': + # NOTE: This must be kept as a begin/end match for language-todo to work + 'begin': '^(#!)' + 'beginCaptures': + '1': + 'name': 'punctuation.definition.comment.shebang.clojure' + 'end': '$' + 'name': 'comment.line.shebang.clojure' + 'string': + 'begin': '(?\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)/' + 'captures': + '1': + 'name': 'entity.name.namespace.clojure' + } + ] + 'symbol': + 'patterns': [ + { + 'match': '([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)' + 'name': 'entity.name.variable.clojure' + } + ] + 'var': + 'match': '(?<=^\\#|[\\s\\(\\[\\{]\\#)\'[\\w\\.\\-\\_\\:\\+\\=\\>\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}))' + 'name': 'meta.var.clojure' + 'vector': + 'begin': '(\\[)' + 'beginCaptures': + '1': + 'name': 'punctuation.section.vector.begin.clojure' + 'end': '(\\](?=[\\}\\]\\)\\s]*(?:;|$)))|(\\])' + 'endCaptures': + '1': + 'name': 'punctuation.section.vector.end.trailing.clojure' + '2': + 'name': 'punctuation.section.vector.end.clojure' + 'name': 'meta.vector.clojure' + 'patterns': [ + { + 'include': '$self' + } + ] + 'prompt': + 'patterns': [ + { + 'match': '^([\\p{L}0-9]+)(꞉)([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*0-9]+)(꞉>)([ ])' + 'captures': + '1': + 'name': 'keyword.control.prompt.clojure' + '2': + 'name': 'keyword.control.prompt.clojure' + '3': + 'name': 'entity.name.namespace.prompt.clojure' + '4': + 'name': 'keyword.control.prompt.clojure' + } + ] diff --git a/src/calva-fmt/atom-language-clojure/package.json b/src/calva-fmt/atom-language-clojure/package.json new file mode 100644 index 0000000..e89adb1 --- /dev/null +++ b/src/calva-fmt/atom-language-clojure/package.json @@ -0,0 +1,28 @@ +{ + "name": "language-clojure", + "version": "0.22.7", + "description": "Clojure language support in Atom", + "engines": { + "atom": "*", + "node": "*" + }, + "homepage": "http://atom.github.io/language-clojure", + "repository": { + "type": "git", + "url": "https://github.com/atom/language-clojure" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/atom/language-clojure/issues" + }, + "scripts": { + "preinstall": "npx npm-force-resolutions" + }, + "devDependencies": { + "coffeelint": "^1.10.1", + "minimist": "^1.2.6" + }, + "resolutions": { + "minimist": "^1.2.5" + } +} diff --git a/src/calva-fmt/atom-language-clojure/settings/language-clojure.cson b/src/calva-fmt/atom-language-clojure/settings/language-clojure.cson new file mode 100644 index 0000000..d0dd718 --- /dev/null +++ b/src/calva-fmt/atom-language-clojure/settings/language-clojure.cson @@ -0,0 +1,5 @@ +'.source.clojure': + 'editor': + 'commentStart': '; ' + 'autocomplete': + 'extraWordCharacters': '-' diff --git a/src/calva-fmt/atom-language-clojure/snippets/language-clojure.cson b/src/calva-fmt/atom-language-clojure/snippets/language-clojure.cson new file mode 100644 index 0000000..4b56104 --- /dev/null +++ b/src/calva-fmt/atom-language-clojure/snippets/language-clojure.cson @@ -0,0 +1,111 @@ +'.source.clojure': + 'ns': + 'prefix': 'ns' + 'body': """ + (ns ${1:name} + (:require [${2:libraries}])) + $0 + """ + + 'def': + 'prefix': 'def' + 'body': '(def ${1:symbol} ${2:value})' + + 'defn': + 'prefix': 'defn' + 'body': """ + (defn ${1:name} + [${2:params}] + ${3:body}) + """ + + 'fn': + 'prefix': 'fn' + 'body': """ + (fn [${1:params}] + ${2:body})$0 + """ + + 'let': + 'prefix': 'let' + 'body': """ + (let [${1:bindings}] + ${2:body}) + """ + + 'if': + 'prefix': 'if' + 'body': """ + (if ${1:test} + ${2:then} + ${3:else}) + """ + + 'if-let': + 'prefix': 'ifl' + 'body': """ + (if-let [${1:bindings}] + ${2:then} + ${3:else}) + """ + + 'if-not': + 'prefix': 'ifn' + 'body': """ + (if-not ${1:test} + ${2:then} + ${3:else}) + """ + + 'when': + 'prefix': 'when' + 'body': """ + (when ${1:test} + ${2:body}) + """ + + 'when-let': + 'prefix': 'whenl' + 'body': """ + (when-let [${1:bindings}] + ${2:body}) + """ + + 'when-not': + 'prefix': 'whenn' + 'body': """ + (when-not ${1:test} + ${2:body}) + """ + + 'map': + 'prefix': 'map' + 'body': '(map $1 $2)' + + 'map lambda': + 'prefix': 'mapl' + 'body': '(map #($1) $2)' + + 'condp': + 'prefix': 'condp' + 'body': """ + (condp ${1:pred} ${2:expr} + $0) + """ + + 'try': + 'prefix': 'try' + 'body': """ + (try + $1 + (catch ${2:exception} e + $3)) + """ + + 'prn': + 'prefix': 'prn' + 'body': '(prn $1)' + + 'println': + 'prefix': 'prnl' + 'body': '(println $1)' diff --git a/src/calva-fmt/atom-language-clojure/spec/clojure-spec.coffee b/src/calva-fmt/atom-language-clojure/spec/clojure-spec.coffee new file mode 100644 index 0000000..84f385c --- /dev/null +++ b/src/calva-fmt/atom-language-clojure/spec/clojure-spec.coffee @@ -0,0 +1,471 @@ +describe "Clojure grammar", -> + grammar = null + + beforeEach -> + waitsForPromise -> + atom.packages.activatePackage("language-clojure") + + runs -> + grammar = atom.grammars.grammarForScopeName("source.clojure") + + it "parses the grammar", -> + expect(grammar).toBeDefined() + expect(grammar.scopeName).toBe "source.clojure" + + it "tokenizes semicolon comments", -> + {tokens} = grammar.tokenizeLine "; clojure" + expect(tokens[0]).toEqual value: ";", scopes: ["source.clojure", "comment.line.semicolon.clojure", "punctuation.definition.comment.clojure"] + expect(tokens[1]).toEqual value: " clojure", scopes: ["source.clojure", "comment.line.semicolon.clojure"] + + it "does not tokenize escaped semicolons as comments", -> + {tokens} = grammar.tokenizeLine "\\; clojure" + expect(tokens[0]).toEqual value: "\\; ", scopes: ["source.clojure"] + expect(tokens[1]).toEqual value: "clojure", scopes: ["source.clojure", "entity.name.variable.clojure"] + + it "tokenizes shebang comments", -> + {tokens} = grammar.tokenizeLine "#!/usr/bin/env clojure" + expect(tokens[0]).toEqual value: "#!", scopes: ["source.clojure", "comment.line.shebang.clojure", "punctuation.definition.comment.shebang.clojure"] + expect(tokens[1]).toEqual value: "/usr/bin/env clojure", scopes: ["source.clojure", "comment.line.shebang.clojure"] + + it "tokenizes strings", -> + {tokens} = grammar.tokenizeLine '"foo bar"' + expect(tokens[0]).toEqual value: '"', scopes: ["source.clojure", "string.quoted.double.clojure", "punctuation.definition.string.begin.clojure"] + expect(tokens[1]).toEqual value: 'foo bar', scopes: ["source.clojure", "string.quoted.double.clojure"] + expect(tokens[2]).toEqual value: '"', scopes: ["source.clojure", "string.quoted.double.clojure", "punctuation.definition.string.end.clojure"] + + it "tokenizes character escape sequences", -> + {tokens} = grammar.tokenizeLine '"\\n"' + expect(tokens[0]).toEqual value: '"', scopes: ["source.clojure", "string.quoted.double.clojure", "punctuation.definition.string.begin.clojure"] + expect(tokens[1]).toEqual value: '\\n', scopes: ["source.clojure", "string.quoted.double.clojure", "constant.character.escape.clojure"] + expect(tokens[2]).toEqual value: '"', scopes: ["source.clojure", "string.quoted.double.clojure", "punctuation.definition.string.end.clojure"] + + it "tokenizes regexes", -> + {tokens} = grammar.tokenizeLine '#"foo"' + expect(tokens[0]).toEqual value: '#"', scopes: ["source.clojure", "string.regexp.clojure", "punctuation.definition.regexp.begin.clojure"] + expect(tokens[1]).toEqual value: 'foo', scopes: ["source.clojure", "string.regexp.clojure"] + expect(tokens[2]).toEqual value: '"', scopes: ["source.clojure", "string.regexp.clojure", "punctuation.definition.regexp.end.clojure"] + + it "tokenizes backslash escape character in regexes", -> + {tokens} = grammar.tokenizeLine '#"\\\\" "/"' + expect(tokens[0]).toEqual value: '#"', scopes: ["source.clojure", "string.regexp.clojure", "punctuation.definition.regexp.begin.clojure"] + expect(tokens[1]).toEqual value: "\\\\", scopes: ['source.clojure', 'string.regexp.clojure', 'constant.character.escape.clojure'] + expect(tokens[2]).toEqual value: '"', scopes: ['source.clojure', 'string.regexp.clojure', "punctuation.definition.regexp.end.clojure"] + expect(tokens[4]).toEqual value: '"', scopes: ['source.clojure', 'string.quoted.double.clojure', 'punctuation.definition.string.begin.clojure'] + expect(tokens[5]).toEqual value: "/", scopes: ['source.clojure', 'string.quoted.double.clojure'] + expect(tokens[6]).toEqual value: '"', scopes: ['source.clojure', 'string.quoted.double.clojure', 'punctuation.definition.string.end.clojure'] + + it "tokenizes escaped double quote in regexes", -> + {tokens} = grammar.tokenizeLine '#"\\""' + expect(tokens[0]).toEqual value: '#"', scopes: ["source.clojure", "string.regexp.clojure", "punctuation.definition.regexp.begin.clojure"] + expect(tokens[1]).toEqual value: '\\"', scopes: ['source.clojure', 'string.regexp.clojure', 'constant.character.escape.clojure'] + expect(tokens[2]).toEqual value: '"', scopes: ['source.clojure', 'string.regexp.clojure', "punctuation.definition.regexp.end.clojure"] + + it "tokenizes numerics", -> + numbers = + "constant.numeric.ratio.clojure": ["1/2", "123/456", "+0/2", "-23/1"] + "constant.numeric.arbitrary-radix.clojure": ["2R1011", "16rDEADBEEF", "16rDEADBEEFN", "36rZebra"] + "constant.numeric.hexadecimal.clojure": ["0xDEADBEEF", "0XDEADBEEF", "0xDEADBEEFN", "0x0"] + "constant.numeric.octal.clojure": ["0123", "0123N", "00"] + "constant.numeric.double.clojure": ["123.45", "123.45e6", "123.45E6", "123.456M", "42.", "42.M", "42E+9M", "42E-0", "0M", "+0M", "42.E-23M"] + "constant.numeric.long.clojure": ["123", "12321", "123N", "+123N", "-123", "0"] + "constant.numeric.symbol.clojure": ["##Inf", "##-Inf", "##NaN"] + + for scope, nums of numbers + for num in nums + {tokens} = grammar.tokenizeLine num + expect(tokens[0]).toEqual value: num, scopes: ["source.clojure", scope] + + it "tokenizes booleans", -> + booleans = + "constant.language.boolean.clojure": ["true", "false"] + + for scope, bools of booleans + for bool in bools + {tokens} = grammar.tokenizeLine bool + expect(tokens[0]).toEqual value: bool, scopes: ["source.clojure", scope] + {tokens} = grammar.tokenizeLine " " + bool + expect(tokens[1]).toEqual value: bool, scopes: ["source.clojure", scope] + {tokens} = grammar.tokenizeLine bool + " " + expect(tokens[0]).toEqual value: bool, scopes: ["source.clojure", scope] + {tokens} = grammar.tokenizeLine "," + bool + expect(tokens[1]).toEqual value: bool, scopes: ["source.clojure", scope] + {tokens} = grammar.tokenizeLine bool + "," + expect(tokens[0]).toEqual value: bool, scopes: ["source.clojure", scope] + {tokens} = grammar.tokenizeLine "(not " + bool + ")" + expect(tokens[3]).toEqual value: bool, scopes: ["source.clojure", "meta.expression.clojure", scope] + {tokens} = grammar.tokenizeLine "[" + bool + "]" + expect(tokens[1]).toEqual value: bool, scopes: ["source.clojure", "meta.vector.clojure", scope] + {tokens} = grammar.tokenizeLine "{:a " + bool + "}" + expect(tokens[3]).toEqual value: bool, scopes: ["source.clojure", "meta.map.clojure", scope] + {tokens} = grammar.tokenizeLine bool + "^{:hi 1}[]" + expect(tokens[0]).toEqual value: bool, scopes: ["source.clojure", scope] + + + it "tokenizes nil", -> + {tokens} = grammar.tokenizeLine "nil" + expect(tokens[0]).toEqual value: "nil", scopes: ["source.clojure", "constant.language.nil.clojure"] + {tokens} = grammar.tokenizeLine " nil" + expect(tokens[1]).toEqual value: "nil", scopes: ["source.clojure", "constant.language.nil.clojure"] + {tokens} = grammar.tokenizeLine "nil " + expect(tokens[0]).toEqual value: "nil", scopes: ["source.clojure", "constant.language.nil.clojure"] + {tokens} = grammar.tokenizeLine ",nil" + expect(tokens[1]).toEqual value: "nil", scopes: ["source.clojure", "constant.language.nil.clojure"] + {tokens} = grammar.tokenizeLine "nil," + expect(tokens[0]).toEqual value: "nil", scopes: ["source.clojure", "constant.language.nil.clojure"] + {tokens} = grammar.tokenizeLine "(conj nil)" + expect(tokens[3]).toEqual value: "nil", scopes: ["source.clojure", "meta.expression.clojure", "constant.language.nil.clojure"] + {tokens} = grammar.tokenizeLine "[nil]" + expect(tokens[1]).toEqual value: "nil", scopes: ["source.clojure", "meta.vector.clojure", "constant.language.nil.clojure"] + {tokens} = grammar.tokenizeLine "{:a nil}" + expect(tokens[3]).toEqual value: "nil", scopes: ["source.clojure", "meta.map.clojure", "constant.language.nil.clojure"] + {tokens} = grammar.tokenizeLine "nil^{:hi 1}[]" + expect(tokens[0]).toEqual value: "nil", scopes: ["source.clojure", "constant.language.nil.clojure"] + + it "tokenizes keywords", -> + tests = + "meta.expression.clojure": ["(:foo)"] + "meta.map.clojure": ["{:foo}"] + "meta.vector.clojure": ["[:foo]"] + "meta.quoted-expression.clojure": ["'(:foo)", "`(:foo)"] + + for metaScope, lines of tests + for line in lines + {tokens} = grammar.tokenizeLine line + expect(tokens[1]).toEqual value: ":foo", scopes: ["source.clojure", metaScope, "constant.keyword.clojure"] + + {tokens} = grammar.tokenizeLine "(def foo :bar)" + expect(tokens[5]).toEqual value: ":bar", scopes: ["source.clojure", "meta.expression.clojure", "meta.definition.global.clojure", "constant.keyword.clojure"] + + # keywords can start with an uppercase non-ASCII letter + {tokens} = grammar.tokenizeLine "(def foo :Öπ)" + expect(tokens[5]).toEqual value: ":Öπ", scopes: ["source.clojure", "meta.expression.clojure", "meta.definition.global.clojure", "constant.keyword.clojure"] + + it "tokenizes keyfns (keyword control)", -> + keyfns = ["declare", "declare-", "ns", "in-ns", "import", "use", "require", "load", "compile", "def", "defn", "defn-", "defmacro", "defåπç"] + + for keyfn in keyfns + {tokens} = grammar.tokenizeLine "(#{keyfn})" + expect(tokens[1]).toEqual value: keyfn, scopes: ["source.clojure", "meta.expression.clojure", "storage.control.clojure"] + + it "does not tokenize `default...`s as keyfn (keyword control)", -> + {tokens} = grammar.tokenizeLine "(defnormal foo)" + expect(tokens[1].scopes).toContain "storage.control.clojure" + {tokens} = grammar.tokenizeLine "(foo/defnormal foo)" + expect(tokens[1].scopes).toContain "storage.control.clojure" + {tokens} = grammar.tokenizeLine "(normaldef foo)" + expect(tokens[1].scopes).not.toContain "storage.control.clojure" + {tokens} = grammar.tokenizeLine "(default foo)" + expect(tokens[1].scopes).not.toContain "storage.control.clojure" + {tokens} = grammar.tokenizeLine "(defaultfoo ba)" + expect(tokens[1].scopes).not.toContain "storage.control.clojure" + {tokens} = grammar.tokenizeLine "(foo/default foo)" + expect(tokens[1].scopes).not.toContain "storage.control.clojure" + {tokens} = grammar.tokenizeLine "(foo/defaultfoo ba)" + expect(tokens[1].scopes).not.toContain "storage.control.clojure" + + it "tokenizes keyfns (storage control)", -> + keyfns = ["if", "when", "for", "cond", "do", "let", "binding", "loop", "recur", "fn", "throw", "try", "catch", "finally", "case"] + + for keyfn in keyfns + {tokens} = grammar.tokenizeLine "(#{keyfn})" + expect(tokens[1]).toEqual value: keyfn, scopes: ["source.clojure", "meta.expression.clojure", "storage.control.clojure"] + + it "tokenizes global definitions", -> + macros = ["ns", "declare", "def", "defn", "defn-", "defroutes", "compojure/defroutes", "rum.core/defc123-", "some.nested-ns/def-nested->symbol!?*", "def+!.?abc8:<>", "ns/def+!.?abc8:<>", "ns/defåÄÖπç"] + + for macro in macros + {tokens} = grammar.tokenizeLine "(#{macro} foo 'bar)" + expect(tokens[1]).toEqual value: macro, scopes: ["source.clojure", "meta.expression.clojure", "meta.definition.global.clojure", "storage.control.clojure"] + expect(tokens[3]).toEqual value: "foo", scopes: ["source.clojure", "meta.expression.clojure", "meta.definition.global.clojure", "entity.global.clojure"] + + it "tokenizes dynamic variables", -> + mutables = ["*ns*", "*foo-bar*", "*åÄÖπç*"] + + for mutable in mutables + {tokens} = grammar.tokenizeLine mutable + expect(tokens[0]).toEqual value: mutable, scopes: ["source.clojure", "entity.name.variable.dynamic.clojure"] + + it "tokenizes metadata", -> + {tokens} = grammar.tokenizeLine "^Foo" + expect(tokens[0]).toEqual value: "^", scopes: ["source.clojure", "meta.metadata.simple.clojure"] + expect(tokens[1]).toEqual value: "Foo", scopes: ["source.clojure", "meta.metadata.simple.clojure", "entity.name.variable.clojure"] + + # non-ASCII letters + {tokens} = grammar.tokenizeLine "^Öπ" + expect(tokens[0]).toEqual value: "^", scopes: ["source.clojure", "meta.metadata.simple.clojure"] + expect(tokens[1]).toEqual value: "Öπ", scopes: ["source.clojure", "meta.metadata.simple.clojure", "entity.name.variable.clojure"] + + {tokens} = grammar.tokenizeLine "^{:foo true}" + expect(tokens[0]).toEqual value: "^{", scopes: ["source.clojure", "meta.metadata.map.clojure", "punctuation.section.metadata.map.begin.clojure"] + expect(tokens[1]).toEqual value: ":foo", scopes: ["source.clojure", "meta.metadata.map.clojure", "constant.keyword.clojure"] + expect(tokens[2]).toEqual value: " ", scopes: ["source.clojure", "meta.metadata.map.clojure"] + expect(tokens[3]).toEqual value: "true", scopes: ["source.clojure", "meta.metadata.map.clojure", "constant.language.boolean.clojure"] + expect(tokens[4]).toEqual value: "}", scopes: ["source.clojure", "meta.metadata.map.clojure", "punctuation.section.metadata.map.end.trailing.clojure"] + + it "tokenizes functions", -> + expressions = ["(foo)", "(foo 1 10)"] + + for expr in expressions + {tokens} = grammar.tokenizeLine expr + expect(tokens[1]).toEqual value: "foo", scopes: ["source.clojure", "meta.expression.clojure", "entity.name.function.clojure"] + + namespaced_expressions = ["(bar/foo)", "(bar/foo 1 10)"] + + for expr in namespaced_expressions + {tokens} = grammar.tokenizeLine expr + expect(tokens[1]).toEqual value: "bar", scopes: ["source.clojure", "meta.expression.clojure", "entity.name.namespace.clojure"] + expect(tokens[2]).toEqual value: "/", scopes: ["source.clojure", "meta.expression.clojure"] + expect(tokens[3]).toEqual value: "foo", scopes: ["source.clojure", "meta.expression.clojure", "entity.name.function.clojure"] + + #non-ASCII letters + {tokens} = grammar.tokenizeLine "(Öπ 2 20)" + expect(tokens[1]).toEqual value: "Öπ", scopes: ["source.clojure", "meta.expression.clojure", "entity.name.function.clojure"] + + it "tokenizes vars", -> + {tokens} = grammar.tokenizeLine "(func #'foo)" + expect(tokens[2]).toEqual value: " #", scopes: ["source.clojure", "meta.expression.clojure"] + expect(tokens[3]).toEqual value: "'foo", scopes: ["source.clojure", "meta.expression.clojure", "meta.var.clojure"] + + # non-ASCII letters + {tokens} = grammar.tokenizeLine "(func #'Öπ)" + expect(tokens[2]).toEqual value: " #", scopes: ["source.clojure", "meta.expression.clojure"] + expect(tokens[3]).toEqual value: "'Öπ", scopes: ["source.clojure", "meta.expression.clojure", "meta.var.clojure"] + + it "tokenizes symbols", -> + {tokens} = grammar.tokenizeLine "x" + expect(tokens[0]).toEqual value: "x", scopes: ["source.clojure", "entity.name.variable.clojure"] + + # non-ASCII letters + {tokens} = grammar.tokenizeLine "Öπ" + expect(tokens[0]).toEqual value: "Öπ", scopes: ["source.clojure", "entity.name.variable.clojure"] + + # Should not be tokenized as a symbol + {tokens} = grammar.tokenizeLine "1foobar" + expect(tokens[0]).toEqual value: "1", scopes: ["source.clojure", "constant.numeric.long.clojure"] + + it "tokenizes namespaces", -> + {tokens} = grammar.tokenizeLine "foo/bar" + expect(tokens[0]).toEqual value: "foo", scopes: ["source.clojure", "entity.name.namespace.clojure"] + expect(tokens[1]).toEqual value: "/", scopes: ["source.clojure"] + expect(tokens[2]).toEqual value: "bar", scopes: ["source.clojure", "entity.name.variable.clojure"] + + # non-ASCII letters + {tokens} = grammar.tokenizeLine "Öπ/Åä" + expect(tokens[0]).toEqual value: "Öπ", scopes: ["source.clojure", "entity.name.namespace.clojure"] + expect(tokens[1]).toEqual value: "/", scopes: ["source.clojure"] + expect(tokens[2]).toEqual value: "Åä", scopes: ["source.clojure", "entity.name.variable.clojure"] + + testMetaSection = (metaScope, puncScope, startsWith, endsWith) -> + # Entire expression on one line. + {tokens} = grammar.tokenizeLine "#{startsWith}foo, bar#{endsWith}" + + [start, mid..., end] = tokens + + expect(start).toEqual value: startsWith, scopes: ["source.clojure", "meta.#{metaScope}.clojure", "punctuation.section.#{puncScope}.begin.clojure"] + expect(end).toEqual value: endsWith, scopes: ["source.clojure", "meta.#{metaScope}.clojure", "punctuation.section.#{puncScope}.end.trailing.clojure"] + + for token in mid + expect(token.scopes.slice(0, 2)).toEqual ["source.clojure", "meta.#{metaScope}.clojure"] + + # Expression broken over multiple lines. + tokens = grammar.tokenizeLines("#{startsWith}foo\n bar#{endsWith}") + + [start, mid..., after] = tokens[0] + + expect(start).toEqual value: startsWith, scopes: ["source.clojure", "meta.#{metaScope}.clojure", "punctuation.section.#{puncScope}.begin.clojure"] + + for token in mid + expect(token.scopes.slice(0, 2)).toEqual ["source.clojure", "meta.#{metaScope}.clojure"] + + [mid..., end] = tokens[1] + + expect(end).toEqual value: endsWith, scopes: ["source.clojure", "meta.#{metaScope}.clojure", "punctuation.section.#{puncScope}.end.trailing.clojure"] + + for token in mid + expect(token.scopes.slice(0, 2)).toEqual ["source.clojure", "meta.#{metaScope}.clojure"] + + it "tokenizes expressions", -> + testMetaSection "expression", "expression", "(", ")" + + it "tokenizes quoted expressions", -> + testMetaSection "quoted-expression", "expression", "'(", ")" + testMetaSection "quoted-expression", "expression", "`(", ")" + + it "tokenizes vectors", -> + testMetaSection "vector", "vector", "[", "]" + + it "tokenizes maps", -> + testMetaSection "map", "map", "{", "}" + + it "tokenizes sets", -> + testMetaSection "set", "set", "\#{", "}" + + it "tokenizes functions in nested sexp", -> + {tokens} = grammar.tokenizeLine "((foo bar) baz)" + expect(tokens[0]).toEqual value: "(", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.begin.clojure"] + expect(tokens[1]).toEqual value: "(", scopes: ["source.clojure", "meta.expression.clojure", "meta.expression.clojure", "punctuation.section.expression.begin.clojure"] + expect(tokens[2]).toEqual value: "foo", scopes: ["source.clojure", "meta.expression.clojure", "meta.expression.clojure", "entity.name.function.clojure"] + expect(tokens[3]).toEqual value: " ", scopes: ["source.clojure", "meta.expression.clojure", "meta.expression.clojure"] + expect(tokens[4]).toEqual value: "bar", scopes: ["source.clojure", "meta.expression.clojure", "meta.expression.clojure", "entity.name.variable.clojure"] + expect(tokens[5]).toEqual value: ")", scopes: ["source.clojure", "meta.expression.clojure", "meta.expression.clojure", "punctuation.section.expression.end.clojure"] + expect(tokens[6]).toEqual value: " ", scopes: ["source.clojure", "meta.expression.clojure"] + expect(tokens[7]).toEqual value: "baz", scopes: ["source.clojure", "meta.expression.clojure", "entity.name.variable.clojure"] + expect(tokens[8]).toEqual value: ")", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.end.trailing.clojure"] + + it "tokenizes maps used as functions", -> + {tokens} = grammar.tokenizeLine "({:foo bar} :foo)" + expect(tokens[0]).toEqual value: "(", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.begin.clojure"] + expect(tokens[1]).toEqual value: "{", scopes: ["source.clojure", "meta.expression.clojure", "meta.map.clojure", "punctuation.section.map.begin.clojure"] + expect(tokens[2]).toEqual value: ":foo", scopes: ["source.clojure", "meta.expression.clojure", "meta.map.clojure", "constant.keyword.clojure"] + expect(tokens[3]).toEqual value: " ", scopes: ["source.clojure", "meta.expression.clojure", "meta.map.clojure"] + expect(tokens[4]).toEqual value: "bar", scopes: ["source.clojure", "meta.expression.clojure", "meta.map.clojure", "entity.name.variable.clojure"] + expect(tokens[5]).toEqual value: "}", scopes: ["source.clojure", "meta.expression.clojure", "meta.map.clojure", "punctuation.section.map.end.clojure"] + expect(tokens[6]).toEqual value: " ", scopes: ["source.clojure", "meta.expression.clojure"] + expect(tokens[7]).toEqual value: ":foo", scopes: ["source.clojure", "meta.expression.clojure", "constant.keyword.clojure"] + expect(tokens[8]).toEqual value: ")", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.end.trailing.clojure"] + + it "tokenizes sets used in functions", -> + {tokens} = grammar.tokenizeLine "(\#{:foo :bar})" + expect(tokens[0]).toEqual value: "(", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.begin.clojure"] + expect(tokens[1]).toEqual value: "\#{", scopes: ["source.clojure", "meta.expression.clojure", "meta.set.clojure", "punctuation.section.set.begin.clojure"] + expect(tokens[2]).toEqual value: ":foo", scopes: ["source.clojure", "meta.expression.clojure", "meta.set.clojure", "constant.keyword.clojure"] + expect(tokens[3]).toEqual value: " ", scopes: ["source.clojure", "meta.expression.clojure", "meta.set.clojure"] + expect(tokens[4]).toEqual value: ":bar", scopes: ["source.clojure", "meta.expression.clojure", "meta.set.clojure", "constant.keyword.clojure"] + expect(tokens[5]).toEqual value: "}", scopes: ["source.clojure", "meta.expression.clojure", "meta.set.clojure", "punctuation.section.set.end.trailing.clojure"] + expect(tokens[6]).toEqual value: ")", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.end.trailing.clojure"] + + it "tokenize strings (wrongly) used as functions", -> + {tokens} = grammar.tokenizeLine "(\"foo)\")" + expect(tokens[0]).toEqual value: "(", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.begin.clojure"] + expect(tokens[1]).toEqual value: "\"", scopes: ["source.clojure", "meta.expression.clojure", "string.quoted.double.clojure", "punctuation.definition.string.begin.clojure"] + expect(tokens[2]).toEqual value: "foo)", scopes: ["source.clojure", "meta.expression.clojure", "string.quoted.double.clojure"] + expect(tokens[3]).toEqual value: "\"", scopes: ["source.clojure", "meta.expression.clojure", "string.quoted.double.clojure", "punctuation.definition.string.end.clojure"] + expect(tokens[4]).toEqual value: ")", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.end.trailing.clojure"] + + describe "replPrompt", -> + it "tokenizes repl prompt", -> + {tokens} = grammar.tokenizeLine "foo꞉bar.baz-2꞉> " + expect(tokens[0]).toEqual value: "foo", scopes: ["source.clojure", "keyword.control.prompt.clojure"] + expect(tokens[1]).toEqual value: "꞉", scopes: ["source.clojure", "keyword.control.prompt.clojure"] + expect(tokens[2]).toEqual value: "bar.baz-2", scopes: ["source.clojure", "entity.name.namespace.prompt.clojure"] + expect(tokens[3]).toEqual value: "꞉>", scopes: ["source.clojure", "keyword.control.prompt.clojure"] + it "does not tokenize repl prompt when prepended with anything", -> + {tokens} = grammar.tokenizeLine " foo꞉bar.baz-2꞉> " + expect(tokens[0]).toEqual value: " ", scopes: ["source.clojure"] + expect(tokens[1]).toEqual value: "foo", scopes: ["source.clojure", "entity.name.variable.clojure"] + it "does not tokenize repl prompt when not followed by hard space", -> + {tokens} = grammar.tokenizeLine "foo꞉bar.baz-2꞉>" + expect(tokens[0]).toEqual value: "foo", scopes: ["source.clojure", "entity.name.variable.clojure"] + + describe "firstLineMatch", -> + it "recognises interpreter directives", -> + valid = """ + #!/usr/sbin/boot foo + #!/usr/bin/boot foo=bar/ + #!/usr/sbin/boot + #!/usr/sbin/boot foo bar baz + #!/usr/bin/boot perl + #!/usr/bin/boot bin/perl + #!/usr/bin/boot + #!/bin/boot + #!/usr/bin/boot --script=usr/bin + #! /usr/bin/env A=003 B=149 C=150 D=xzd E=base64 F=tar G=gz H=head I=tail boot + #!\t/usr/bin/env --foo=bar boot --quu=quux + #! /usr/bin/boot + #!/usr/bin/env boot + """ + for line in valid.split /\n/ + expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull() + + invalid = """ + \x20#!/usr/sbin/boot + \t#!/usr/sbin/boot + #!/usr/bin/env-boot/node-env/ + #!/usr/bin/das-boot + #! /usr/binboot + #!\t/usr/bin/env --boot=bar + """ + for line in invalid.split /\n/ + expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull() + + it "recognises Emacs modelines", -> + valid = """ + #-*- Clojure -*- + #-*- mode: ClojureScript -*- + /* -*-clojureScript-*- */ + // -*- Clojure -*- + /* -*- mode:Clojure -*- */ + // -*- font:bar;mode:Clojure -*- + // -*- font:bar;mode:Clojure;foo:bar; -*- + // -*-font:mode;mode:Clojure-*- + // -*- foo:bar mode: clojureSCRIPT bar:baz -*- + " -*-foo:bar;mode:clojure;bar:foo-*- "; + " -*-font-mode:foo;mode:clojure;foo-bar:quux-*-" + "-*-font:x;foo:bar; mode : clojure; bar:foo;foooooo:baaaaar;fo:ba;-*-"; + "-*- font:x;foo : bar ; mode : ClojureScript ; bar : foo ; foooooo:baaaaar;fo:ba-*-"; + """ + for line in valid.split /\n/ + expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull() + + invalid = """ + /* --*clojure-*- */ + /* -*-- clojure -*- + /* -*- -- Clojure -*- + /* -*- Clojure -;- -*- + // -*- iClojure -*- + // -*- Clojure; -*- + // -*- clojure-door -*- + /* -*- model:clojure -*- + /* -*- indent-mode:clojure -*- + // -*- font:mode;Clojure -*- + // -*- mode: -*- Clojure + // -*- mode: das-clojure -*- + // -*-font:mode;mode:clojure--*- + """ + for line in invalid.split /\n/ + expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull() + + it "recognises Vim modelines", -> + valid = """ + vim: se filetype=clojure: + # vim: se ft=clojure: + # vim: set ft=Clojure: + # vim: set filetype=Clojure: + # vim: ft=Clojure + # vim: syntax=Clojure + # vim: se syntax=Clojure: + # ex: syntax=Clojure + # vim:ft=clojure + # vim600: ft=clojure + # vim>600: set ft=clojure: + # vi:noai:sw=3 ts=6 ft=clojure + # vi::::::::::noai:::::::::::: ft=clojure + # vim:ts=4:sts=4:sw=4:noexpandtab:ft=clojure + # vi:: noai : : : : sw =3 ts =6 ft =clojure + # vim: ts=4: pi sts=4: ft=clojure: noexpandtab: sw=4: + # vim: ts=4 sts=4: ft=clojure noexpandtab: + # vim:noexpandtab sts=4 ft=clojure ts=4 + # vim:noexpandtab:ft=clojure + # vim:ts=4:sts=4 ft=clojure:noexpandtab:\x20 + # vim:noexpandtab titlestring=hi\|there\\\\ ft=clojure ts=4 + """ + for line in valid.split /\n/ + expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull() + + invalid = """ + ex: se filetype=clojure: + _vi: se filetype=clojure: + vi: se filetype=clojure + # vim set ft=klojure + # vim: soft=clojure + # vim: clean-syntax=clojure: + # vim set ft=clojure: + # vim: setft=clojure: + # vim: se ft=clojure backupdir=tmp + # vim: set ft=clojure set cmdheight=1 + # vim:noexpandtab sts:4 ft:clojure ts:4 + # vim:noexpandtab titlestring=hi\\|there\\ ft=clojure ts=4 + # vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=clojure ts=4 + """ + for line in invalid.split /\n/ + expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull() diff --git a/src/calva-fmt/src/config.ts b/src/calva-fmt/src/config.ts new file mode 100644 index 0000000..5706f55 --- /dev/null +++ b/src/calva-fmt/src/config.ts @@ -0,0 +1,68 @@ +import * as vscode from 'vscode'; +import * as filesCache from '../../files-cache'; +import * as cljsLib from '../../../out/cljs-lib/cljs-lib.js'; +// import * as lsp from '../../lsp/main'; +const defaultCljfmtContent = + '\ +{:remove-surrounding-whitespace? true\n\ + :remove-trailing-whitespace? true\n\ + :remove-consecutive-blank-lines? false\n\ + :insert-missing-whitespace? true\n\ + :align-associative? false}'; + +const LSP_CONFIG_KEY = 'CLOJURE-LSP'; +let lspFormatConfig: string | undefined; + +function configuration(workspaceConfig: vscode.WorkspaceConfiguration, cljfmt: string) { + return { + 'format-as-you-type': !!workspaceConfig.get('formatAsYouType'), + 'keep-comment-forms-trail-paren-on-own-line?': !!workspaceConfig.get( + 'keepCommentTrailParenOnOwnLine' + ), + 'cljfmt-options-string': cljfmt, + 'cljfmt-options': cljsLib.cljfmtOptionsFromString(cljfmt), + }; +} + +async function readConfiguration(): Promise<{ + 'format-as-you-type': boolean; + 'keep-comment-forms-trail-paren-on-own-line?': boolean; + 'cljfmt-options-string': string; + 'cljfmt-options': object; +}> { + const workspaceConfig = vscode.workspace.getConfiguration('calva.fmt'); + const configPath: string | undefined = workspaceConfig.get('configPath'); + + // if (configPath === LSP_CONFIG_KEY) { + // lspFormatConfig = await lsp.getCljFmtConfig(); + // } + if (configPath === LSP_CONFIG_KEY && !lspFormatConfig) { + void vscode.window.showErrorMessage( + 'Fetching formatting settings from clojure-lsp failed. Check that you are running a version of clojure-lsp that provides "cljfmt-raw" in serverInfo.', + 'Roger that' + ); + } + const cljfmtContent: string | undefined = + configPath === LSP_CONFIG_KEY + ? lspFormatConfig + ? lspFormatConfig + : defaultCljfmtContent + : filesCache.content(configPath); + const config = configuration( + workspaceConfig, + cljfmtContent ? cljfmtContent : defaultCljfmtContent + ); + if (!config['cljfmt-options']['error']) { + return config; + } else { + void vscode.window.showErrorMessage( + `Error parsing ${configPath}: ${config['cljfmt-options']['error']}\n\nUsing default formatting configuration.` + ); + return configuration(workspaceConfig, defaultCljfmtContent); + } +} + +export async function getConfig() { + const config = await readConfiguration(); + return config; +} diff --git a/src/calva-fmt/src/extension.ts b/src/calva-fmt/src/extension.ts new file mode 100644 index 0000000..a538f67 --- /dev/null +++ b/src/calva-fmt/src/extension.ts @@ -0,0 +1,98 @@ +import * as vscode from 'vscode'; +import { FormatOnTypeEditProvider } from './providers/ontype_formatter'; +import { RangeEditProvider } from './providers/range_formatter'; +import * as formatter from './format'; +import * as inferer from './infer'; +import * as docmirror from '../../doc-mirror/index'; +import * as config from './config'; +import * as hyConfig from '../../config'; + +function getLanguageConfiguration(autoIndentOn: boolean): vscode.LanguageConfiguration { + return { + onEnterRules: + autoIndentOn && hyConfig.getConfig().format + ? [ + // When Calva is the formatter disable all vscode default indentation + // (By outdenting a lot, which is the only way I have found that works) + // TODO: Make it actually consider whether Calva is the formatter or not + { + beforeText: /.*/, + action: { + indentAction: vscode.IndentAction.Outdent, + removeText: Number.MAX_VALUE, + }, + }, + ] + : [], + }; +} + +export async function activate(context: vscode.ExtensionContext) { + docmirror.activate(); + vscode.languages.setLanguageConfiguration( + 'hy', + getLanguageConfiguration(await config.getConfig()['format-as-you-type']) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + 'calva-fmt.formatCurrentForm', + formatter.formatPositionCommand + ) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + 'calva-fmt.alignCurrentForm', + formatter.alignPositionCommand + ) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + 'calva-fmt.trimCurrentFormWhiteSpace', + formatter.trimWhiteSpacePositionCommand + ) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + 'calva-fmt.inferParens', + inferer.inferParensCommand) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand( + 'calva-fmt.tabIndent', + (e) => { + inferer.indentCommand(e, ' ', true); + } + ) + ); + context.subscriptions.push( + vscode.commands.registerTextEditorCommand('calva-fmt.tabDedent', (e) => { + inferer.indentCommand(e, ' ', false); + }) + ); + context.subscriptions.push( + vscode.languages.registerOnTypeFormattingEditProvider( + hyConfig.documentSelector, + new FormatOnTypeEditProvider(), + '\r', + '\n', + ')', + ']', + '}' + ) + ); + context.subscriptions.push( + vscode.languages.registerDocumentRangeFormattingEditProvider( + hyConfig.documentSelector, + new RangeEditProvider() + ) + ); + vscode.window.onDidChangeActiveTextEditor(inferer.updateState); + vscode.workspace.onDidChangeConfiguration(async (e) => { + if (e.affectsConfiguration('calva.fmt.formatAsYouType')) { + vscode.languages.setLanguageConfiguration( + 'hy', + getLanguageConfiguration(await config.getConfig()['format-as-you-type']) + ); + } + }); +} diff --git a/src/calva-fmt/src/format.ts b/src/calva-fmt/src/format.ts new file mode 100644 index 0000000..b6b6df0 --- /dev/null +++ b/src/calva-fmt/src/format.ts @@ -0,0 +1,257 @@ +import * as vscode from 'vscode'; +import * as config from './config'; +import * as path from 'path'; +// import * as outputWindow from '../../results-output/results-doc'; +import { + getIndent, + getDocumentOffset, + MirroredDocument, + getDocument, +} from '../../doc-mirror/index'; +import { + formatTextAtRange, + formatTextAtIdx, + formatTextAtIdxOnType, + formatText, + cljify, + jsify, +} from '../../../out/cljs-lib/cljs-lib'; +import * as util from '../../utilities'; +import { isUndefined, cloneDeep } from 'lodash'; + +export async function indentPosition(position: vscode.Position, document: vscode.TextDocument) { + const editor = util.getActiveTextEditor(); + const pos = new vscode.Position(position.line, 0); + const indent = getIndent( + getDocument(document).model.lineInputModel, + getDocumentOffset(document, position), + await config.getConfig() + ); + let delta = document.lineAt(position.line).firstNonWhitespaceCharacterIndex - indent; + if (delta > 0) { + return editor.edit( + (edits) => edits.delete(new vscode.Range(pos, new vscode.Position(pos.line, delta))), + { + undoStopAfter: false, + undoStopBefore: false, + } + ); + } else if (delta < 0) { + let str = ''; + while (delta++ < 0) { + str += ' '; + } + return editor.edit((edits) => edits.insert(pos, str), { + undoStopAfter: false, + undoStopBefore: false, + }); + } +} + +export async function formatRangeEdits( + document: vscode.TextDocument, + range: vscode.Range +): Promise { + const text: string = document.getText(range); + const mirroredDoc: MirroredDocument = getDocument(document); + const startIndex = document.offsetAt(range.start); + const endIndex = document.offsetAt(range.end); + const cursor = mirroredDoc.getTokenCursor(startIndex); + if (!cursor.withinString()) { + const rangeTuple: number[] = [startIndex, endIndex]; + const newText: string | undefined = await _formatRange( + text, + document.getText(), + rangeTuple, + document.eol == 2 ? '\r\n' : '\n' + ); + if (newText) { + return [vscode.TextEdit.replace(range, newText)]; + } + } +} + +export async function formatRange(document: vscode.TextDocument, range: vscode.Range) { + const wsEdit: vscode.WorkspaceEdit = new vscode.WorkspaceEdit(); + const edits = await formatRangeEdits(document, range); + + if (isUndefined(edits)) { + console.error('formatRangeEdits returned undefined!', cloneDeep({ document, range })); + return false; + } + + wsEdit.set(document.uri, edits); + return vscode.workspace.applyEdit(wsEdit); +} + +export async function formatPositionInfo( + editor: vscode.TextEditor, + onType: boolean = false, + extraConfig = {} +) { + const doc: vscode.TextDocument = editor.document; + const pos: vscode.Position = editor.selection.active; + const index = doc.offsetAt(pos); + const mirroredDoc: MirroredDocument = getDocument(doc); + const cursor = mirroredDoc.getTokenCursor(index); + const formatDepth = extraConfig['format-depth'] ? extraConfig['format-depth'] : 1; + const isComment = cursor.getFunctionName() === 'comment'; + const config = { ...extraConfig, 'comment-form?': isComment }; + let formatRange = cursor.rangeForList(formatDepth); + if (!formatRange) { + formatRange = cursor.rangeForCurrentForm(index); + if (!formatRange || !formatRange.includes(index)) { + return; + } + } + const formatted: { + 'range-text': string; + range: number[]; + 'new-index': number; + } = await _formatIndex( + doc.getText(), + formatRange, + index, + doc.eol == 2 ? '\r\n' : '\n', + onType, + config + ); + const range: vscode.Range = new vscode.Range( + doc.positionAt(formatted.range[0]), + doc.positionAt(formatted.range[1]) + ); + const newIndex: number = doc.offsetAt(range.start) + formatted['new-index']; + const previousText: string = doc.getText(range); + return { + formattedText: formatted['range-text'], + range: range, + previousText: previousText, + previousIndex: index, + newIndex: newIndex, + }; +} + +// From results-doc.ts +const RESULTS_DOC_NAME = "thing"; + +export function isResultsDoc(doc?: vscode.TextDocument): boolean { + return !!doc && path.basename(doc.fileName) === RESULTS_DOC_NAME; +} + +// End from results-doc.ts + +export async function formatPosition( + editor: vscode.TextEditor, + onType: boolean = false, + extraConfig = {} +): Promise { + // console.log("calva-fmt/src/format.ts/formatPosition called") + const doc: vscode.TextDocument = editor.document, + formattedInfo = await formatPositionInfo(editor, onType, extraConfig); + if (formattedInfo && formattedInfo.previousText != formattedInfo.formattedText) { + return editor + .edit( + (textEditorEdit) => { + textEditorEdit.replace(formattedInfo.range, formattedInfo.formattedText); + }, + { undoStopAfter: false, undoStopBefore: false } + ) + .then((onFulfilled: boolean) => { + editor.selection = new vscode.Selection( + doc.positionAt(formattedInfo.newIndex), + doc.positionAt(formattedInfo.newIndex) + ); + return onFulfilled; + }); + } + if (formattedInfo) { + return new Promise((resolve, _reject) => { + if (formattedInfo.newIndex != formattedInfo.previousIndex) { + editor.selection = new vscode.Selection( + doc.positionAt(formattedInfo.newIndex), + doc.positionAt(formattedInfo.newIndex) + ); + } + resolve(true); + }); + } + if (!onType && !isResultsDoc(doc)) { + return formatRange( + doc, + new vscode.Range(doc.positionAt(0), doc.positionAt(doc.getText().length)) + ); + } + return new Promise((resolve, _reject) => { + resolve(true); + }); +} + +export function formatPositionCommand(editor: vscode.TextEditor) { + void formatPosition(editor); +} + +export function alignPositionCommand(editor: vscode.TextEditor) { + void formatPosition(editor, true, { 'align-associative?': true }); +} + +export function trimWhiteSpacePositionCommand(editor: vscode.TextEditor) { + void formatPosition(editor, false, { 'remove-multiple-non-indenting-spaces?': true }); +} + +export async function formatCode(code: string, eol: number) { + const d = { + 'range-text': code, + eol: eol == 2 ? '\r\n' : '\n', + config: await config.getConfig(), + }; + const result = jsify(formatText(d)); + if (!result['error']) { + return result['range-text']; + } else { + console.error('Error in `formatCode`:', result['error']); + return code; + } +} + +async function _formatIndex( + allText: string, + range: [number, number], + index: number, + eol: string, + onType: boolean = false, + extraConfig = {} +): Promise<{ 'range-text': string; range: number[]; 'new-index': number }> { + const d = { + 'all-text': allText, + idx: index, + eol: eol, + range: range, + config: { ...(await config.getConfig()), ...extraConfig }, + }; + const result = jsify(onType ? formatTextAtIdxOnType(d) : formatTextAtIdx(d)); + if (!result['error']) { + return result; + } else { + console.error('Error in `_formatIndex`:', result['error']); + throw result['error']; + } +} + +async function _formatRange( + rangeText: string, + allText: string, + range: number[], + eol: string +): Promise { + const d = { + 'range-text': rangeText, + 'all-text': allText, + range: range, + eol: eol, + config: await config.getConfig(), + }; + const result = jsify(formatTextAtRange(d)); + if (!result['error']) { + return result['range-text']; + } +} diff --git a/src/calva-fmt/src/infer.ts b/src/calva-fmt/src/infer.ts new file mode 100644 index 0000000..aa70bdc --- /dev/null +++ b/src/calva-fmt/src/infer.ts @@ -0,0 +1,137 @@ +import * as vscode from 'vscode'; +import { inferParens, inferIndents } from '../../../out/cljs-lib/cljs-lib'; +import { isUndefined, cloneDeep } from 'lodash'; + +interface CFEdit { + edit: string; + start: { line: number; character: number }; + end: { line: number; character: number }; + text?: string; +} + +interface CFError { + message: string; +} + +interface ResultOptions { + success: boolean; + edits?: [CFEdit]; + line?: number; + character?: number; + error?: CFError; + 'error-msg'?: string; +} + +export function inferParensCommand(editor: vscode.TextEditor) { + // console.log("calva-fmt/src/infer.ts/inferParensCommand called"); + const position: vscode.Position = editor.selection.active, + document = editor.document, + currentText = document.getText(), + r: ResultOptions = inferParens({ + text: currentText, + line: position.line, + character: position.character, + 'previous-line': position.line, + 'previous-character': position.character, + }); + applyResults(r, editor); +} + +export function indentCommand(editor: vscode.TextEditor, spacing: string, forward: boolean = true) { + const prevPosition: vscode.Position = editor.selection.active, + document = editor.document; + let deletedText = '', + doEdit = true; + + void editor + .edit( + (editBuilder) => { + if (forward) { + editBuilder.insert( + new vscode.Position(prevPosition.line, prevPosition.character), + spacing + ); + } else { + const startOfLine = new vscode.Position(prevPosition.line, 0), + headRange = new vscode.Range(startOfLine, prevPosition), + headText = document.getText(headRange), + xOfFirstLeadingSpace = headText.search(/ *$/), + leadingSpaces = + xOfFirstLeadingSpace >= 0 ? prevPosition.character - xOfFirstLeadingSpace : 0; + if (leadingSpaces > 0) { + const spacingSize = Math.max(spacing.length, 1), + deleteRange = new vscode.Range(prevPosition.translate(0, -spacingSize), prevPosition); + deletedText = document.getText(deleteRange); + editBuilder.delete(deleteRange); + } else { + doEdit = false; + } + } + }, + { undoStopAfter: false, undoStopBefore: false } + ) + .then((_onFulfilled: boolean) => { + if (doEdit) { + const position: vscode.Position = editor.selection.active, + currentText = document.getText(), + r: ResultOptions = inferIndents({ + text: currentText, + line: position.line, + character: position.character, + 'previous-line': prevPosition.line, + 'previous-character': prevPosition.character, + changes: [ + { + line: forward ? prevPosition.line : position.line, + character: forward ? prevPosition.character : position.character, + 'old-text': forward ? '' : deletedText, + 'new-text': forward ? spacing : '', + }, + ], + }); + applyResults(r, editor); + } + }); +} + +function applyResults(r: ResultOptions, editor: vscode.TextEditor) { + if (r.success) { + void editor + .edit( + (editBuilder) => { + if (isUndefined(r.edits)) { + console.error('Edits were undefined!', cloneDeep({ editBuilder, r, editor })); + return; + } + r.edits.forEach((edit: CFEdit) => { + const start = new vscode.Position(edit.start.line, edit.start.character), + end = new vscode.Position(edit.end.line, edit.end.character); + if (isUndefined(edit.text)) { + console.error( + 'edit.text was undefined!', + cloneDeep({ edit, editBuilder, r, editor }) + ); + return; + } + editBuilder.replace(new vscode.Range(start, end), edit.text); + }); + }, + { undoStopAfter: true, undoStopBefore: false } + ) + .then((_onFulfilled: boolean) => { + // these will never be undefined in practice: + // https://github.com/BetterThanTomorrow/calva/blob/5d23da5704989e000b1f860fc09f5935d7bac3f5/src/cljs-lib/src/calva/fmt/editor.cljs#L5-L21 + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion + const newPosition = new vscode.Position(r.line!, r.character!); + editor.selections = [new vscode.Selection(newPosition, newPosition)]; + }); + } else { + void vscode.window.showErrorMessage( + 'Calva Formatter Error: ' + (r.error ? r.error.message : r['error-msg']) + ); + } +} + +export function updateState(editor: vscode.TextEditor) { + // do nothing +} diff --git a/src/calva-fmt/src/providers/ontype_formatter.ts b/src/calva-fmt/src/providers/ontype_formatter.ts new file mode 100644 index 0000000..664951a --- /dev/null +++ b/src/calva-fmt/src/providers/ontype_formatter.ts @@ -0,0 +1,51 @@ +import * as vscode from 'vscode'; +import * as formatter from '../format'; +import * as docMirror from '../../../doc-mirror/index'; +import { EditableDocument } from '../../../cursor-doc/model'; +import * as paredit from '../../../cursor-doc/paredit'; +import { getConfig } from '../../../config'; +import * as util from '../../../utilities'; + +export class FormatOnTypeEditProvider implements vscode.OnTypeFormattingEditProvider { + async provideOnTypeFormattingEdits( + document: vscode.TextDocument, + _position: vscode.Position, + ch: string, + _options + ): Promise { + let keyMap = vscode.workspace.getConfiguration().get('hy.paredit.defaultKeyMap'); + keyMap = String(keyMap).trim().toLowerCase(); + if ([')', ']', '}'].includes(ch)) { + if (keyMap === 'strict' && getConfig().strictPreventUnmatchedClosingBracket) { + const mDoc: EditableDocument = docMirror.getDocument(document); + const tokenCursor = mDoc.getTokenCursor(); + if (tokenCursor.withinComment()) { + return undefined; + } + // TODO: We should make a function in/for the MirrorDoc that can return + // edits instead of performing them. It is not awesome to perform edits + // here, since we are expected to return them. + await paredit.backspace(mDoc); + await paredit.close(mDoc, ch); + } else { + return undefined; + } + } + const editor = util.getActiveTextEditor(); + + const pos = editor.selection.active; + if (vscode.workspace.getConfiguration('calva.fmt').get('formatAsYouType')) { + if (vscode.workspace.getConfiguration('calva.fmt').get('newIndentEngine')) { + void formatter.indentPosition(pos, document); + } else { + try { + void formatter.formatPosition(editor, true); + } catch (e) { + void formatter.indentPosition(pos, document); + } + } + } + + return undefined; + } +} diff --git a/src/calva-fmt/src/providers/range_formatter.ts b/src/calva-fmt/src/providers/range_formatter.ts new file mode 100644 index 0000000..d8ffec7 --- /dev/null +++ b/src/calva-fmt/src/providers/range_formatter.ts @@ -0,0 +1,13 @@ +import * as vscode from 'vscode'; +import * as formatter from '../format'; + +export class RangeEditProvider implements vscode.DocumentRangeFormattingEditProvider { + provideDocumentRangeFormattingEdits( + document: vscode.TextDocument, + range: vscode.Range, + _options, + _token + ) { + return formatter.formatRangeEdits(document, range); + } +} diff --git a/src/calva-fmt/src/state.ts b/src/calva-fmt/src/state.ts new file mode 100644 index 0000000..5175557 --- /dev/null +++ b/src/calva-fmt/src/state.ts @@ -0,0 +1,36 @@ +import * as vscode from 'vscode'; +import * as Immutable from 'immutable'; +import * as ImmutableCursor from 'immutable-cursor'; + +const mode = { + language: 'clojure', + //scheme: 'file' +}; + +let data; +const initialData = { + documents: {}, +}; + +reset(); + +const cursor = ImmutableCursor.from(data, [], (nextState) => { + data = Immutable.fromJS(nextState); +}); + +function deref() { + return data; +} + +function reset() { + data = Immutable.fromJS(initialData); +} + +function config() { + const configOptions = vscode.workspace.getConfiguration('calva.fmt'); + return { + parinferOnSelectionChange: configOptions.get('inferParensOnCursorMove'), + }; +} + +export { cursor, mode, deref, reset, config }; diff --git a/src/calva-fmt/update-grammar.js b/src/calva-fmt/update-grammar.js new file mode 100644 index 0000000..ca4976a --- /dev/null +++ b/src/calva-fmt/update-grammar.js @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + *--------------------------------------------------------------------------------------------*/ + +/** + * MIT License + * + * Copyright (c) 2015 - present Microsoft Corporation + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +'use strict'; + +var path = require('path'); +var fs = require('fs'); +var cson = require('cson-parser'); + +exports.update = function (contentPath, dest) { + console.log('Reading from ' + contentPath); + fs.readFile(contentPath, (_err, content) => { + var grammar = cson.parse(content); + const result = { + information_for_contributors: ['This file is generated from ' + contentPath], + }; + + const keys = ['name', 'scopeName', 'comment', 'injections', 'patterns', 'repository']; + for (const key of keys) { + if (Object.prototype.hasOwnProperty.call(grammar, key)) { + result[key] = grammar[key]; + } + } + + try { + fs.writeFileSync(dest, JSON.stringify(result, null, '\t').replace(/\n/g, '\r\n')); + console.log('Updated ' + path.basename(dest)); + } catch (e) { + console.error(e); + process.exit(1); + } + }); +}; + +if (path.basename(process.argv[1]) === 'update-grammar.js') { + exports.update(process.argv[2], process.argv[3]); +} diff --git a/src/cljs-lib/src/calva/dartclojure.cljs b/src/cljs-lib/src/calva/dartclojure.cljs new file mode 100644 index 0000000..a55269a --- /dev/null +++ b/src/cljs-lib/src/calva/dartclojure.cljs @@ -0,0 +1,34 @@ +(ns calva.dartclojure + (:require [dumch.convert :as dart->clj])) + +(defn convert [dart-string] + (try + {:result + (dart->clj/convert dart-string :string :sexpr)} + (catch :default e + {:error {:message "Error parsing Dart code" + :exception {:name (.-name e) + :message (.-message e)}}}))) + +(defn convert-bridge [dart-string] + (convert dart-string)) + +(comment + (convert + " + (context, index) { + if (index == 0) { + return const Padding( + padding: EdgeInsets.only(left: 15, top: 16, bottom: 8), + child: Text( + 'You might also like:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ); + } + return const SongPlaceholderTile(); + }; + ")) \ No newline at end of file diff --git a/src/cljs-lib/src/calva/fmt/editor.cljs b/src/cljs-lib/src/calva/fmt/editor.cljs new file mode 100644 index 0000000..2342c4f --- /dev/null +++ b/src/cljs-lib/src/calva/fmt/editor.cljs @@ -0,0 +1,37 @@ +(ns calva.fmt.editor + (:require [calva.fmt.util :as util])) + + +(defn raplacement-edits-for-diffing-lines + "Returns a list of replacement edits to apply to `old-text` to get `new-text`. + Edits will be in the form `[:replace [range] text]`, + where `range` is in the form `[[start-line start-char] [end-line end-char]]`. + NB: The two versions need to have the same amount of lines." + [old-text new-text] + (let [old-lines (util/split-into-lines old-text) + new-lines (util/split-into-lines new-text)] + (->> (map vector (range) old-lines new-lines) + (remove (fn [[line o n]] (= o n))) + (mapv (fn [[line o n]] + {:edit "replace" + :start {:line line + :character 0} + :end {:line line + :character (count o)} + :text n}))))) + + +(comment + (raplacement-edits-for-diffing-lines "foo\nfooo\nbar\nbar" + "foo\nbar\nbaz\nbar") + (->> (map vector + [:foo :foo :foo] + [:foo :bar :foo] + (range)) + (remove (fn [[o n i]] + (= o n)))) + (filter some? + (map (fn [o n line] (when-not (= o n) [o n line])) + [:foo :foo :foo] + [:foo :bar :foo] + (range)))) \ No newline at end of file diff --git a/src/cljs-lib/src/calva/fmt/formatter.cljs b/src/cljs-lib/src/calva/fmt/formatter.cljs new file mode 100644 index 0000000..56aa015 --- /dev/null +++ b/src/cljs-lib/src/calva/fmt/formatter.cljs @@ -0,0 +1,339 @@ +(ns calva.fmt.formatter + (:require [pez-cljfmt.core :as pez-cljfmt] + [cljfmt.core :as cljfmt] + #_[zprint.core :refer [zprint-str]] + [calva.js-utils :refer [jsify cljify]] + [calva.fmt.util :as util] + [calva.parse :refer [parse-clj-edn]] + [clojure.string])) + +(defn- merge-default-indents + "Merges onto default-indents. + The :replace metadata hint allows to replace defaults." + [indents] + (if (:replace (meta indents)) + indents + (merge cljfmt/default-indents indents))) + +(def ^:private default-fmt + {:remove-surrounding-whitespace? true + :remove-trailing-whitespace? true + :remove-consecutive-blank-lines? false + :insert-missing-whitespace? true + :align-associative? false}) + +(defn merge-cljfmt + [fmt] + (as-> fmt $ + (update $ :indents merge-default-indents) + (merge default-fmt $))) + +(defn- read-cljfmt + [s] + (try + (as-> s $ + (parse-clj-edn $) + (merge-cljfmt $)) + (catch js/Error e + {:error (.-message e)}))) + +(defn- reformat-string [range-text {:keys [align-associative? + remove-multiple-non-indenting-spaces?] :as config}] + (let [cljfmt-options (:cljfmt-options config) + trim-space-between? (or remove-multiple-non-indenting-spaces? + (:remove-multiple-non-indenting-spaces? cljfmt-options))] + (if (or align-associative? + (:align-associative? cljfmt-options)) + (pez-cljfmt/reformat-string range-text (-> cljfmt-options + (assoc :align-associative? true) + (dissoc :remove-multiple-non-indenting-spaces?))) + (cljfmt/reformat-string range-text (-> cljfmt-options + (assoc :remove-multiple-non-indenting-spaces? + trim-space-between?)))))) + +(defn format-text + [{:keys [range-text eol config] :as m}] + (try + (let [formatted-text (-> range-text + (reformat-string config) + (clojure.string/replace #"\r?\n" eol))] + (assoc m :range-text formatted-text)) + (catch js/Error e + (assoc m :error (.-message e))))) + +(comment + {:eol "\n" :all-text "[:foo\n\n(foo)(bar)]" :idx 6} + (def s "[:foo\n\n(foo\n(bar))]") + #_(def s "(defn\n0\n#_)") + (format-text #_s + {:range-text s + :eol "\n" + :config {:cljfmt-options + {:remove-surrounding-whitespace? false + :indents {"foo" [["inner" 0]]} + :remove-trailing-whitespace? false + :remove-consecutive-blank-lines? false + :align-associative? true}}})) + +(defn extract-range-text + [{:keys [all-text range]}] + (subs all-text (first range) (last range))) + +(defn current-line-empty? + "Figure out if `:current-line` is empty" + [{:keys [current-line]}] + (some? (re-find #"^[\s,]*$" current-line))) + + +(defn indent-before-range + "Figures out how much extra indentation to add based on the length of the line before the range" + [{:keys [all-text range]}] + (let [start (first range) + end (last range)] + (if (= start end) + 0 + (-> (subs all-text 0 (first range)) + (util/split-into-lines) + (last) + (count))))) + + +(defn add-head-and-tail + "Splits `:all-text` at `:idx` in `:head` and `:tail`" + [{:keys [all-text idx] :as m}] + (-> m + (assoc :head (subs all-text 0 idx) + :tail (subs all-text idx)))) + + +(defn add-current-line + "Finds the text of the current line in `text` from cursor position `index`" + [{:keys [head tail] :as m}] + (-> m + (assoc :current-line + (str (second (re-find #"\n?(.*)$" head)) + (second (re-find #"^(.*)\n?" tail)))))) + + +(defn- normalize-indents + "Normalizes indents based on where the text starts on the first line" + [{:keys [range-text eol] :as m}] + (let [indent-before (apply str (repeat (indent-before-range m) " ")) + lines (clojure.string/split range-text #"\r?\n(?!\s*;)" -1)] + (assoc m :range-text (clojure.string/join (str eol indent-before) lines)))) + + +(defn index-for-tail-in-range + "Find index for the `tail` in `text` disregarding whitespace" + [{:keys [range-text range-tail on-type] :as m}] + (let [leading-space-length (count (re-find #"^[ \t,]*" range-tail)) + space-sym (str "@" (gensym "ESPACEIALLY") "@") + tail-pattern (-> range-tail + (clojure.string/replace #"[\]\)\}\"]" (str "$&" space-sym)) + (util/escape-regexp) + (clojure.string/replace #"^[ \t,]+" "") + (clojure.string/replace #"[\s,]+" "[\\s,]*") + (clojure.string/replace space-sym " ?")) + tail-pattern (if (and on-type (re-find #"^\r?\n" range-tail)) + (str "(\\r?\\n)+" tail-pattern) + tail-pattern) + pos (util/re-pos-first (str "[ \\t]{0," leading-space-length "}" tail-pattern "$") range-text)] + (assoc m :new-index pos))) + +(defn format-text-at-range + "Formats text from all-text at the range" + [{:keys [range idx] :as m}] + (let [indent-before (indent-before-range m) + padding (apply str (repeat indent-before " ")) + range-text (extract-range-text m) + padded-text (str padding range-text) + range-index (- idx (first range)) + tail (subs range-text range-index) + formatted-m (format-text (assoc m :range-text padded-text)) + formatted-text (subs (:range-text formatted-m) indent-before)] + (-> (assoc formatted-m + :range-text formatted-text + :range-tail tail)))) + +(comment + (format-text-at-range {:all-text " '([]\n[])" + :idx 7 + :on-type true + :head " '([]\n" + :tail "[])" + :current-line "[])" + :range [4 9]}) + (format-text-at-range {:eol "\n" + :all-text "[:foo\n\n(foo)(bar)]" + :idx 6 + :range [0 18]})) + + +(defn add-indent-token-if-empty-current-line + "If `:current-line` is empty add an indent token at `:idx`" + [{:keys [head tail range] :as m}] + (let [indent-token "0" + new-range [(first range) (inc (last range))]] + (if (current-line-empty? m) + (let [m1 (assoc m + :all-text (str head indent-token tail) + :range new-range)] + (assoc m1 :range-text (extract-range-text m1))) + m))) + + +(defn remove-indent-token-if-empty-current-line + "If an indent token was added, lets remove it. Not forgetting to shrink `:range`" + [{:keys [range-text range new-index] :as m}] + (if (current-line-empty? m) + (assoc m :range-text (str (subs range-text 0 new-index) (subs range-text (inc new-index))) + :range [(first range) (dec (second range))]) + m)) + +(def trailing-bracket_symbol "_calva-fmt-trail-symbol_") +(def trailing-bracket_pattern (re-pattern (str "_calva-fmt-trail-symbol_\\)$"))) + +(defn add-trail-symbol-if-comment + "If the `range-text` is a comment, add a symbol at the end, preventing the last paren from folding" + [{:keys [range all-text config idx] :as m}] + (let [keep-trailing-bracket-on-own-line? + (and (:keep-comment-forms-trail-paren-on-own-line? config) + (:comment-form? config))] + (if keep-trailing-bracket-on-own-line? + (let [range-text (extract-range-text m) + new-range-text (clojure.string/replace + range-text + #"\n{0,1}[ \t,]*\)$" + (str "\n" trailing-bracket_symbol ")")) + added-text-length (- (count new-range-text) + (count range-text)) + new-range-end (+ (second range) added-text-length) + new-all-text (str (subs all-text 0 (first range)) + new-range-text + (subs all-text (second range))) + new-idx (if (>= idx (- (second range) 1)) + (+ idx added-text-length) + idx)] + (-> m + (assoc :all-text new-all-text + :range-text new-range-text + :idx new-idx) + (assoc-in [:range 1] new-range-end))) + m))) + +(defn remove-trail-symbol-if-comment + "If the `range-text` is a comment, remove the symbol at the end" + [{:keys [range range-text new-index idx config] :as m} original-range] + (let [keep-trailing-bracket-on-own-line? + (and (:keep-comment-forms-trail-paren-on-own-line? config) + (:comment-form? config))] + (if keep-trailing-bracket-on-own-line? + (let [new-range-text (clojure.string/replace + range-text + trailing-bracket_pattern + ")")] + (-> m + (assoc :range-text new-range-text + :new-index (if (>= idx (- (second range) 1)) + (- (count new-range-text) + (- (second range) idx)) + new-index) + :range original-range))) + m))) + +(defn format-text-at-idx + "Formats the enclosing range of text surrounding idx" + [{:keys [range] :as m}] + (-> m + (update-in [:config :cljfmt-options] merge-cljfmt) + (add-trail-symbol-if-comment) + (add-head-and-tail) + (add-current-line) + (add-indent-token-if-empty-current-line) + (format-text-at-range) + (index-for-tail-in-range) + (remove-indent-token-if-empty-current-line) + (remove-trail-symbol-if-comment range))) + +(defn format-text-at-idx-on-type + "Relax formating some when used as an on-type handler" + [m] + (-> m + (assoc :on-type true) + (assoc-in [:config :cljfmt-options :remove-surrounding-whitespace?] false) + (assoc-in [:config :cljfmt-options :remove-trailing-whitespace?] false) + (assoc-in [:config :cljfmt-options :remove-consecutive-blank-lines?] false) + (format-text-at-idx))) + +(defn- js-cljfmt-options->clj [^js opts] + (let [indents (.-indents opts)] + (-> opts + (cljify) + (assoc :indents (->> indents + js->clj + (reduce-kv (fn [m k v] + (let [new-v (reduce (fn [acc x] + (conj acc [(keyword (first x)) (second x)])) + [] + v)] + (if (.startsWith k "#") + (let [regex-string (subs k 2 (- (count k) 1))] + (assoc m (re-pattern regex-string) new-v)) + (assoc m (symbol k) new-v)))) + {})))))) + +(defn- parse-cljfmt-options-string [^js m] + (let [conf (.-config m) + edn (aget conf "cljfmt-options-string")] + (-> m + (cljify) + (assoc-in [:config :cljfmt-options] (parse-clj-edn edn))))) + +(defn format-text-bridge + [^js m] + (-> m + (parse-cljfmt-options-string) + (update-in [:config :cljfmt-options] merge-cljfmt) + (format-text))) + +(defn format-text-at-range-bridge + [^js m] + (-> m + (parse-cljfmt-options-string) + (update-in [:config :cljfmt-options] merge-cljfmt) + (format-text-at-range))) + +(defn format-text-at-idx-bridge + [^js m] + (-> m + (parse-cljfmt-options-string) + (format-text-at-idx))) + +(defn format-text-at-idx-on-type-bridge + [^js m] + (-> m + (parse-cljfmt-options-string) + (format-text-at-idx-on-type))) + +(defn merge-cljfmt-from-string-js-bridge + [^js s] + (-> s + read-cljfmt + jsify)) + +(defn merge-cljfmt-js-bridge + [^js fmt] + (-> fmt + js-cljfmt-options->clj + merge-cljfmt + jsify)) + +(comment + (:range-text (format-text-at-idx-on-type {:all-text " '([]\n[])" :idx 7}))) + +(comment + {:remove-surrounding-whitespace? false + :remove-trailing-whitespace? false + :remove-consecutive-blank-lines? false + :insert-missing-whitespace? true + :align-associative? true}) diff --git a/src/cljs-lib/src/calva/fmt/inferer.cljs b/src/cljs-lib/src/calva/fmt/inferer.cljs new file mode 100644 index 0000000..69015f3 --- /dev/null +++ b/src/cljs-lib/src/calva/fmt/inferer.cljs @@ -0,0 +1,130 @@ +(ns calva.fmt.inferer + (:require ["parinfer" :as parinfer] + [calva.js-utils :refer [cljify jsify]] + [calva.fmt.editor :as editor])) + +(defn infer-parens + "Calculate the edits needed for infering parens in `text`, + and where the cursor should be placed to 'stay' in the right place." + [{:keys [text line character previous-line previous-character]}] + (let [options {:cursorLine line + :cursorX character + :prevCursorLine previous-line + :prevCursorX previous-character} + result (cljify (parinfer/indentMode text (jsify options)))] + (jsify + (if (:success result) + {:success true + :line (:cursorLine result) + :character (:cursorX result) + :edits (editor/raplacement-edits-for-diffing-lines text (:text result))} + {:success false + :error-msg (get-in result [:error :message])})))) + +(defn infer-parens-bridge + [^js m] + (infer-parens (cljify m))) + + +(comment + (let [o (jsify {:cursorLine 1 :cursorX 13}) + result (parinfer/indentMode " (foo []\n (bar)\n (baz)))" o)] + (cljify result)) + + (infer-parens {:text " (foo []\n (bar)\n (baz)))" + :line 2 + :character 13}) + (infer-parens {:text "(f)))" + :line 0 + :character 2})) + +(defn infer-indents + "Calculate the edits needed for infering indents in `text`, + and where the cursor should be placed to 'stay' in the right place." + [{:keys [text line character previous-line previous-character changes]}] + (let [options {:cursorLine line :cursorX character + :prevCursorLine previous-line + :prevCursorX previous-character + :changes (mapv (fn [change] + {:lineNo (:line change) + :x (:character change) + :oldText (:old-text change) + :newText (:new-text change)}) + changes)} + result (cljify (parinfer/smartMode text (jsify options)))] + (jsify + (if (:success result) + {:success true + :line (:cursorLine result) + :character (:cursorX result) + :edits (editor/raplacement-edits-for-diffing-lines text (:text result))} + {:success false + :error-msg (get-in result [:error :message])})))) + +(defn infer-indents-bridge + [^js m] + (infer-indents (cljify m))) + + +(comment ;; SCRAP + (let [o (jsify {:cursorLine 1 :cursorX 4 + :changes {:lineNo 1 :x 0 :oldText "" :newText " "}}) + result (parinfer/parenMode " (defn a []\n (foo []\n (bar)\n (baz)))" o)] + (cljify result)) + + (let [o (jsify {:cursorLine 1 + :cursorX 3 + :prevCursorLine 1 + :prevCursorX 2 + :changes [{:lineNo 1 + :x 2 + :oldText "" + :newText " "}]}) + result (parinfer/parenMode "(comment\n (foo bar\n baz))" o)] + (cljify result)) + + (let [o (jsify {:cursorLine 1 + :cursorX 0 + :prevCursorLine 0 + :prevCursorX 8 + :changes [{:lineNo 0 + :x 8 + :oldText ")" + :newText "\n)"}]}) + result (parinfer/smartMode "(--> foo\n)" o)] + (cljify result)) + + (let [o (jsify {:cursorLine 1 + :cursorX 3 + :prevCursorLine 1 + :prevCursorX 2}) + result (parinfer/parenMode "(comment\n (foo bar\n baz))" o)] + (cljify result)) + + (infer-indents {:text "(comment \n (foo bar \n baz))" + :line 1 + :character 3 + :previous-line 1 + :previous-character 2 + :changes [{:line 1 + :character 2 + :old-text "" + :new-text " "}]}) + + (infer-indents {:text "(comment\n\n (foo bar \n baz))" + :line 1 + :character 0 + :previous-line 1 + :previous-character 0 + :changes [{:line 0 + :character 8 + :old-text "\n (foo bar \n baz))" + :new-text "\n\n (foo bar \n baz))"}]}) + + (infer-indents {:text " (defn a []\n (foo []\n (bar)\n (baz)))" + :line 1 + :character 4 + :changes [{:line 4 + :character 0 + :old-text "" + :new-text " "}]})) diff --git a/src/cljs-lib/src/calva/fmt/playground.cljs b/src/cljs-lib/src/calva/fmt/playground.cljs new file mode 100644 index 0000000..ea48e7f --- /dev/null +++ b/src/cljs-lib/src/calva/fmt/playground.cljs @@ -0,0 +1,158 @@ +(ns calva.fmt.playground + (:require [cljfmt.core :as cljfmt] + #_[zprint.core :refer [zprint-str]] + ["parinfer" :as parinfer] + [calva.js-utils :refer [cljify jsify]] + [calva.fmt.util :as util])) + + +(comment + (foo + ;; + bar + baz)) + +(comment + (cond) + (cljfmt/reformat-string "(cond foo\n)\n\n(cond foo\nbar)" + {:remove-surrounding-whitespace? false}) + + (cljfmt/reformat-string "(-> foo\nbar\n)\n(foo bar\nbaz\n)" + {:remove-surrounding-whitespace? false + :remove-trailing-whitespace? false + :remove-consecutive-blank-lines? false}) + + (cljfmt/reformat-string "(let [x y\na b]\nbar\n)\n\n(-> foo\nbar\n)\n\n(foo bar\nbaz\n)" + {:remove-surrounding-whitespace? false + :remove-trailing-whitespace? false + :remove-consecutive-blank-lines? false + :indents ^:replace {#".*" [[:inner 0]]}}) + + (cljfmt/reformat-string " '([] +[])" {:remove-surrounding-whitespace? false + :remove-trailing-whitespace? false + :remove-consecutive-blank-lines? false}) + + + (def str "(defn \n\n)") + + (cljfmt/reformat-string str {:remove-surrounding-whitespace? false + :remove-trailing-whitespace? false + :remove-consecutive-blank-lines? false}) + + (cljfmt/reformat-string "(foo + ;; + bar + baz)" + {:remove-surrounding-whitespace? false + :remove-trailing-whitespace? false + :remove-consecutive-blank-lines? false}) + + (cljfmt/reformat-string + "(foo + +)" + {:remove-surrounding-whitespace? false + :remove-trailing-whitespace? false + :indentation? true}) + + (cljfmt/reformat-string "(bar\n \n)" + {:remove-surrounding-whitespace? false + :remove-trailing-whitespace? false}) + + (cljfmt/reformat-string "(ns ui-app.re-frame.db) + +(def default-db #::{:page :home})") + + (cljfmt/reformat-string + "(defn bar\n [x]\n\n baz)") + + (zprint-str "(defn bar\n [x]\n\n baz)" + {:style :community + :parse-string-all? true + :fn-force-nl #{:arg1-body}}) + + (cljfmt/reformat-string + "(defn bar\n [x]\n\n baz)") + + "(defn bar\n [x]\n \n baz)" + + (cljfmt/reformat-string + ";; foo +(defn foo [x] + (* x x)) + 0") + + (div + ;; foo + [:div] + ;; bar + [:div])) + + +(comment + (parinfer/indentMode " (foo [] + (bar) + (baz)))" + (jsify {:cursorLine 2 + :cursorX 13}))) + +(comment + (cljfmt/reformat-string + "{:foo false + :bar false +:baz #\"^[a-z]\"}")) + +(comment + (def t "(when something + body) + +(defn f [x] + body) + +(defn f + [x] + body) + +(defn many-args [a b c + d e f] + body) + +(defn multi-arity + ([x] + body) + ([x y] + body)) + +(let [x 1 + y 2] + body) + +[1 2 3 + 4 5 6] + +{:key-1 v1 + :key-2 v2} + +#{a b c + d e f} + +(or (condition-a) + (condition-b)) + +(filter even? + (range 1 10)) + +(clojure.core/filter even? + (range 1 10)) + +(filter + even? + (range 1 10))") + + (def f + (cljfmt/reformat-string t {:indents {#"^\w" [[:inner 0]]}})) + + (= t f) + (pr-str f) + (println f)) \ No newline at end of file diff --git a/src/cljs-lib/src/calva/fmt/util.cljs b/src/cljs-lib/src/calva/fmt/util.cljs new file mode 100644 index 0000000..60c2f87 --- /dev/null +++ b/src/cljs-lib/src/calva/fmt/util.cljs @@ -0,0 +1,38 @@ +(ns calva.fmt.util + (:require [clojure.string])) + + +(defn log + "logs out the object `o` excluding any keywords in `exclude-kws`" + [o & exlude-kws] + (println (pr-str (if (map? o) (apply dissoc o exlude-kws) o))) + o) + + +(defn escape-regexp + "Escapes regexp characters in `s`" + [s] + (clojure.string/replace s #"([.*+?^${}()|\[\]\\])" "\\$1")) + + +(defn current-line + "Finds the text of the current line in `text` from cursor position `index`" + [text index] + (let [head (subs text 0 index) + tail (subs text index)] + (str (second (re-find #"\n?(.*)$" head)) + (second (re-find #"^(.*)\n?" tail))))) + + +(defn re-pos-first + "Find position of first match of `re` in `s`" + [re s] + (if-let [m (.match s re)] + (.-index m) + -1)) + + +(defn split-into-lines + [s] + (clojure.string/split s #"\r?\n" -1)) + diff --git a/src/cljs-lib/src/calva/js2cljs/converter.cljs b/src/cljs-lib/src/calva/js2cljs/converter.cljs new file mode 100644 index 0000000..db41f65 --- /dev/null +++ b/src/cljs-lib/src/calva/js2cljs/converter.cljs @@ -0,0 +1,50 @@ +(ns calva.js2cljs.converter + (:require [js-cljs.core :as jsc])) + +(defn convert [js-string] + (let [debug (atom nil)] + (try + {:result + (jsc/parse-str js-string {:zprint-opts {:style [:community] + :parse {:interpose "\n\n"} + :width 60 + :pair {:nl-separator? true}} + :format-opts {:debug debug}})} + (catch :default e + {:error {:message "Error parsing JS file" + :number-of-parsed-lines (count (.split (subs js-string 0 (:start @debug)) "\n")) + :exception {:name (.-name e) + :message (.-message e)}}})))) + +(defn convert-bridge [js-string] + (convert js-string)) + +(comment + (convert "var MongoClient = require('mongodb').MongoClient; +var url = \"mongodb://localhost:27017/mydb\"; + +MongoClient.connect(url, function(err, db) { + if (err) throw err; + console.log(\"Database created!\"); + db.close(); +});") + + (convert "foo; + bar; + import * as foo from 'foo'") + + (jsc/parse-str "a++") + + (println (jsc/parse-str " + const sdk = new ChartsEmbedSDK({ + baseUrl: 'https://charts.mongodb.com/charts-mongodb-gtywi' + }); + + const chart = sdk.createChart({ chartId: '7f535ee7-2074-4350-9f94-237277b94391' }); + chart.render(document.getElementById('chart')); +" {:zprint-opts {:style [:community] + :parse {:interpose "\n\n"} + :width 60 + :pair {:nl-separator? true}} + :format-opts {:debug (atom nil)}})) + ) \ No newline at end of file diff --git a/src/cljs-lib/src/calva/js_utils.cljs b/src/cljs-lib/src/calva/js_utils.cljs new file mode 100644 index 0000000..79df04c --- /dev/null +++ b/src/cljs-lib/src/calva/js_utils.cljs @@ -0,0 +1,11 @@ +(ns calva.js-utils + (:require [cljs.reader])) + +(defn jsify + "Converts clojure data to js data" + [o] + (clj->js o :keyword-fn (fn [kw] (str (symbol kw))))) + +(defn cljify [o] + (js->clj o :keywordize-keys true)) + diff --git a/src/cljs-lib/src/calva/main.cljs b/src/cljs-lib/src/calva/main.cljs new file mode 100644 index 0000000..a3748e6 --- /dev/null +++ b/src/cljs-lib/src/calva/main.cljs @@ -0,0 +1,4 @@ +(ns calva.main) + +(defn main [& args] + (js/console.log "Hello from calva-lib")) diff --git a/src/cljs-lib/src/calva/parse.cljs b/src/cljs-lib/src/calva/parse.cljs new file mode 100644 index 0000000..ca53373 --- /dev/null +++ b/src/cljs-lib/src/calva/parse.cljs @@ -0,0 +1,65 @@ +(ns calva.parse + (:require [cljs.reader] + [cljs.tools.reader :as tr] + [cljs.tools.reader.reader-types :as rt] + [clojure.string :as str] + [calva.js-utils :refer [jsify]])) + +(defn parse-edn + "Parses out the first form from `s`. + `s` needs to be a string representation of valid EDN. + Returns the parsed form." + [s] + (cljs.reader/read-string {:default #(str "#" %1 %2)} s)) + +(defn parse-edn-js [s] + (jsify (parse-edn s))) + +(defn parse-edn-js-bridge [s] + (parse-edn-js s)) + +(defn parse-forms + "Parses out all top level forms from `s`. + Returns a vector with the parsed forms." + [s] + (let [pbr (rt/string-push-back-reader (str/replace s #"#=\(" "nil #_("))] + (loop [parsed-forms []] + (let [parsed-form (tr/read {:eof 'CALVA-EOF + :read-cond :preserve} pbr)] + (if (= parsed-form 'CALVA-EOF) + parsed-forms + (recur (conj parsed-forms parsed-form))))))) + +(defn parse-forms-js [s] + (jsify (parse-forms s))) + +(defn parse-forms-js-bridge [s] + (parse-forms-js s)) + +(defn parse-clj-edn + "Reads edn (with regexp tags)" + ; https://ask.clojure.org/index.php/8675/cljs-reader-read-string-fails-input-clojure-string-accepts + [s] (tr/read-string s)) + +;[[ar gu ment] {:as extras, :keys [d e :s t r u c t u r e d]}] +(comment + (meta (:indents (parse-clj-edn "{:indents ^:replace {}}"))) + (parse-forms-js-bridge "(deftest fact-rec-test\n (testing \"returns 1 when passed 1\"\n (is (= 1 (do (println \"hello\") #break (core/fact-rec 1))))))") + (= [:a {:foo [(quote bar)], :bar (quote foo)}] + [:a {:foo ['bar] :bar 'foo}]) + (parse-forms "(ns calva.js-utils + (:require [cljs.reader] + [cljs.tools.reader :as tr] + [cljs.tools.reader.reader-types :as rt] + [cljs.test :refer [is]])) + +(defn jsify [o] + (clj->js o)) + +(defn cljify [o] + (js->clj o :keywordize-keys true))") + (parse-forms "(ns ace2.legacy.bink + (:gen-class) + (:require [clojure.java.io :as io]) + (:import (java.io RandomAccessFile))) +(defn foo [] (println \"whee\"))")) diff --git a/src/cljs-lib/src/calva/pprint/printer.cljs b/src/cljs-lib/src/calva/pprint/printer.cljs new file mode 100644 index 0000000..6a0e5ee --- /dev/null +++ b/src/cljs-lib/src/calva/pprint/printer.cljs @@ -0,0 +1,66 @@ +(ns calva.pprint.printer + (:require [zprint.core :refer [zprint-str]] + [calva.js-utils :refer [jsify cljify]] + [clojure.string])) + +(defn pretty-print + "Parses the string `s` as EDN and returns it pretty printed as a string. + Accepts that s is an EDN form already, and skips the parsing, if so. + Formats the result to fit the width `w`." + [s opts] + (let [result (try + {:value + (zprint-str s (assoc opts :parse-string? (string? s)))} + (catch js/Error e + {:value s + :error (str "Plain printing, b/c pprint failed. (" (.-message e) ")")}))] + result)) + + +(defn pretty-print-js [s {:keys [width, maxLength, maxDepth]}] + (let [opts (into {} (remove (comp nil? val) {:width width + :max-length maxLength + :max-depth maxDepth}))] + (jsify (pretty-print s opts)))) + +(defn pretty-print-js-bridge [s ^js opts] + (pretty-print-js s (cljify opts))) + + +;; SCRAP +(comment + (pretty-print "[ [ [:foo + ]] ]" nil) + ;; => {:value "[[[:foo]]]"} + (pretty-print [[:shallow]] {:max-depth 1}) + ;; => {:value "[##]"} + (pretty-print [[[[[[[[:deeper]]]]]]]] {:max-depth 4}) + ;; => {:value "[[[[##]]]]"} + + (def ignores [#_#_#_#_#_:span "This" "is" "How" "it" "Works"]) + (:value (pretty-print ignores nil)) + ;; => "[(quote \"Works\")]" + + (def str-ignores "[ #_ #_ #_#_#_:span \"This\" \"is\" \"How\" \"it\" \"Works\" ]") + (:value (pretty-print str-ignores nil)) + ;; => "[#_#_#_#_#_:span \"This\" \"is\" \"How\" \"it\" \"Works\"]" + + (def struct '(let [r :r + this-page :this-page] + [:div.grid-x.grid-margin-x.grid-margin-y + [:div.cell.align-center.margin-top.show-for-medium + [:a#foo.button + {:href "#how-it-works"} + [#_#_#_#_#_:span "This" "is" "How" "it" "Works"]] + [:a#bar.button + {:on-click #(citrus/broadcast! r :submit this-page)} + "Send"]]])) + (:value (pretty-print struct nil)) + ;; => "(let [r :r\n this-page :this-page]\n [:div.grid-x.grid-margin-x.grid-margin-y\n [:div.cell.align-center.margin-top.show-for-medium\n [:a#foo.button {:href \"#how-it-works\"} [\"Works\"]]\n [:a#bar.button {:on-click (fn* [] (citrus/broadcast! r :submit this-page))}\n \"Send\"]]])" + + (:value (pretty-print struct {:max-length 2})) + ;; => "(let [r :r ...] ...)" + + (:value (pretty-print struct {:max-length 3})) + ;; => "(let [r :r\n this-page :this-page]\n [:div.grid-x.grid-margin-x.grid-margin-y\n [:div.cell.align-center.margin-top.show-for-medium\n [:a#foo.button {:href \"#how-it-works\"} [\"Works\"]]\n [:a#bar.button {:on-click (fn* [] (citrus/broadcast! r :submit ...))}\n \"Send\"]]])" + ) \ No newline at end of file diff --git a/src/cljs-lib/src/calva/state.cljs b/src/cljs-lib/src/calva/state.cljs new file mode 100644 index 0000000..cee0a2c --- /dev/null +++ b/src/cljs-lib/src/calva/state.cljs @@ -0,0 +1,20 @@ +(ns calva.state) + +(defonce ^:private state (atom {})) + +(defn set-state-value! [key value] + (swap! state assoc key value)) + +(defn remove-state-value! [key] + (swap! state dissoc key)) + +(defn get-state-value [key] + (get @state key)) + +(defn get-state [] + @state) + +(comment + (set-state-value! "hello" "world") + (get-state) + (remove-state-value! "hello")) \ No newline at end of file diff --git a/src/cljs-lib/src/js_cljs/core.cljs b/src/cljs-lib/src/js_cljs/core.cljs new file mode 100644 index 0000000..2f99c2f --- /dev/null +++ b/src/cljs-lib/src/js_cljs/core.cljs @@ -0,0 +1,418 @@ +(ns js-cljs.core + (:require ["acorn" :refer [parse]] + [zprint.core :as zprint] + [clojure.string :as str])) + +(defmulti parse-frag (fn [step state] + (when (and step (:debug state)) (reset! (:debug state) step)) + (:type step))) + +(defn- block [bodies state sep] + (let [ops (->> bodies + (map #(parse-frag % state)) + (remove nil?)) + body (str/join sep ops) + locals (:locals state)] + (cond + (and locals (seq @locals)) (str "(let [" (str/join " " (mapcat identity @locals)) "] " body ")") + (-> state :single? not (or (-> ops count (= 1)))) body + :else (str "(do " body ")")))) + +(defmethod parse-frag "Program" [step state] + (block (:body step) (assoc state :root? true) "\n")) + +(defmethod parse-frag "BlockStatement" [step state] + (block (:body step) (assoc state :root? false :locals (atom [])) " ")) + +(defmethod parse-frag "ExpressionStatement" [step state] + (parse-frag (:expression step) state)) + +(defmethod parse-frag "ForStatement" [{:keys [init test update body]} state] + (let [[id val] (when init (parse-frag (-> init :declarations first) (assoc state :root? false))) + test (if test + (parse-frag test (assoc state :single? true)) + "true") + add-let? (and init (not (:locals state)))] + (str (when add-let? + (str "(let [" id " " val "] ")) + "(while " test + " " (block (:body body) + (assoc state :root? false :single? false :locals (atom [])) + " ") + (when update (str " " (parse-frag update (assoc state :single? false)))) + ")" + (when add-let? ")")))) + +(defn- get-operator [operator] + (case operator + "&&" "and" + "||" "or" + "==" "=" + "===" "=" + "!=" "not=" + "!==" "not=" + "!" "not" + operator)) + +(defn- binary-exp [{:keys [left right operator]} state] + (let [state (assoc state :single? true) + left (parse-frag left state) + right (parse-frag right state)] + (if (= operator "??") + (str "(if (some? " left ") " left " " right ")") + (str "(" (get-operator operator) " " left " " right ")")))) + +(defmethod parse-frag "UnaryExpression" [{:keys [operator argument]} state] + (let [operator (get-operator operator)] + (str "(" operator " " (parse-frag argument (assoc state :single? true)) ")"))) + +(defmethod parse-frag "BinaryExpression" [step state] (binary-exp step state)) +(defmethod parse-frag "LogicalExpression" [step state] (binary-exp step state)) + +(defmethod parse-frag "Literal" [{:keys [value regex] :as p} _] + (if regex + (if-let [flags (-> regex :flags not-empty)] + (str "#" (pr-str (str "(?" flags ")" (:pattern regex)))) + (str "#" (pr-str (:pattern regex)))) + (pr-str value))) + +(defmethod parse-frag "Identifier" [{:keys [name]} _] name) + +(defn- call-expr [{:keys [callee arguments]} state] + (let [callee (parse-frag callee (assoc state :single? true :special-js? true)) + args (mapv #(parse-frag % (assoc state :single? true)) arguments) + [non-rest [[fst] & rst]] (split-with (complement vector?) args) + rest (cond + (seq rst) [(str "(concat " fst " [" (str/join " " rst) "])")] + fst [fst])] + (if (string? callee) + (if rest + (str "(apply " (str/join " " (concat [callee] non-rest rest)) ")") + (str "(" (->> args (cons callee) (str/join " ")) ")")) + (str "(." (second callee) " " (first callee) " " (str/join " " args) + ")")))) +(defmethod parse-frag "CallExpression" [prop state] (call-expr prop state)) +(defmethod parse-frag "NewExpression" [props state] + (call-expr (update-in props [:callee :name] str ".") state)) + +(defn- if-then-else [{:keys [test consequent alternate]} state] + (if alternate + (str "(if " + (parse-frag test (assoc state :single? true)) + " " (parse-frag consequent (assoc state :single? true)) + " " (parse-frag alternate (assoc state :single? true)) + ")") + (str "(when " + (parse-frag test (assoc state :single? true)) + " " (parse-frag consequent state) + ")"))) + +(defmethod parse-frag "IfStatement" [element state] (if-then-else element state)) +(defmethod parse-frag "ConditionalExpression" [element state] (if-then-else element state)) + +(defn- random-identifier [] (gensym "-temp-")) +(defn- to-obj-params [fun param] + (map (fn [[k v]] (str k " (.-" v " " fun ")")) param)) + +(defn- to-default-param [[fun default]] + [fun (str "(if (undefined? " fun ") " default " " fun ")")]) + +(defn- normalize-params [params state] + (let [params (map #(parse-frag % state) params) + params-detailed (for [param params] + (if (vector? param) + (if (-> param first vector?) + (let [id (random-identifier)] + {:fun id :extracts-to (to-obj-params id param)}) + {:fun (first param) :extracts-to (to-default-param param)}) + {:fun param})) + let-params (->> params-detailed (mapcat :extracts-to) (filter identity))] + {:params (->> params-detailed (map :fun) (str/join " ")) + :lets (when (seq let-params) (str/join " " let-params))})) + +(defmethod parse-frag "FunctionDeclaration" [{:keys [id params body]} state] + (let [body (parse-frag body (assoc state :single? false)) + {:keys [params lets]} (normalize-params params state) + norm-body (if lets + (str "(let [" lets "] " body ")") + body)] + (str "(defn " (parse-frag id state) " [" params "] " norm-body ")"))) + +(defn- parse-fun [{:keys [id params body]} state] + (let [params (->> params (map #(parse-frag % state)) (str/join " ")) + body (parse-frag body (assoc state :single? false))] + (str "(fn" + (when-let [name (some-> id (parse-frag state))] + (str " " name)) + " [" params "] " body ")"))) + +(defmethod parse-frag "FunctionExpression" [step state] (parse-fun step state)) +(defmethod parse-frag "ArrowFunctionExpression" [step state] (parse-fun step state)) + +(defmethod parse-frag "ReturnStatement" [{:keys [argument]} state] + (if argument + (parse-frag argument state) + "(js* \"return\")")) + +(defmethod parse-frag "ForOfStatement" [{:keys [left right body]} state] + (str "(doseq [" (-> left :declarations first :id :name) + " " (parse-frag right (assoc state :single? true)) + "] " (parse-frag body (assoc state :single? false)) ")")) + +(defmethod parse-frag "ForInStatement" [{:keys [left right body]} state] + (str "(doseq [" (-> left :declarations first :id :name) + " (js/Object.keys " (parse-frag right (assoc state :single? true)) + ")] " (parse-frag body (assoc state :single? false)) ")")) + +(defn- template-lit [tag {:keys [expressions quasis]} state] + (when tag + (swap! (:cljs-requires state) conj '[shadow.cljs.modern :as modern])) + (let [state (assoc state :single? true) + elems (interleave quasis expressions) + parsed (mapv #(parse-frag % state) elems) + last (-> quasis peek (parse-frag state)) + parsed (cond-> parsed (not= last "\"\"") (conj last))] + (cond + tag (str "(modern/js-template " (parse-frag tag state) " " + (str/join " " parsed) ")") + (seq parsed) (str "(str " (str/join " " parsed) ")") + :else "\"\""))) + +(defmethod parse-frag "TaggedTemplateExpression" [{:keys [tag quasi]} state] + (template-lit tag quasi state)) +(defmethod parse-frag "TemplateLiteral" [prop state] + (template-lit nil prop state)) + +(defmethod parse-frag "TemplateElement" [{:keys [value]} _] (pr-str (:cooked value))) + +(defmethod parse-frag "ThrowStatement" [{:keys [argument]} state] + (str "(throw " (parse-frag argument state) ")")) + +(defmethod parse-frag "AssignmentExpression" [{:keys [operator left right] :as a} state] + (let [vars (parse-frag left (assoc state :single? true :special-js? true)) + val (parse-frag right (assoc state :single? true)) + attr (-> vars second delay) + obj (delay (let [f (first vars)] + (if (vector? f) (str "(.-" (second f) " " (first f) ")") f)))] + + (cond + (string? vars) + (str "(js* " (pr-str (str "~{} " operator " ~{}")) " " vars " " val ")") + + (:computed left) + (str "(aset " @obj " " @attr " " val ")") + + :else + (str "(aset " @obj " " (pr-str @attr) " " val ")")))) + +(defn- make-destr-def [[k v] val] + (str "(def " k " (.-" v " " val "))")) + +(defmethod parse-frag "VariableDeclaration" [{:keys [declarations]} state] + (let [declarations (mapv #(parse-frag % state) declarations)] + (when (:root? state) + (let [defs (for [[k v] declarations] + (if (vector? k) + (if (-> k count (= 1)) + (make-destr-def (first k) v) + (let [sym (random-identifier) + inner (map #(make-destr-def % sym) k)] + (str "(let [" sym " " v "] " (str/join " " inner) ")"))) + (if (and (string? v) (str/starts-with? v "(fn ")) + (str "(defn " k " " (subs v 4)) + (str "(def " k " " v ")"))))] + (str/join " " defs))))) + +(defmethod parse-frag "ContinueStatement" [_ _] "(js* \"continue\")") + +(defmethod parse-frag "VariableDeclarator" [{:keys [id init]} state] + (let [vars (:locals state) + init (if init + (parse-frag init (assoc state :single? true)) + "nil") + body [(parse-frag id (assoc state :single? true)) init]] + (if vars + (swap! vars conj body) + body))) + +(defmethod parse-frag "ObjectExpression" [{:keys [properties]} state] + (let [kvs (->> properties + (map #(parse-frag % (assoc state :single? true))) + (map (fn [[k v]] (str ":" k " " v))))] + (str "#js {" (str/join " " kvs) "}"))) + +(defmethod parse-frag "ArrayExpression" [{:keys [elements]} state] + (let [vals (map #(parse-frag % (assoc state :single? true)) elements)] + (str "#js [" (str/join " " vals) "]"))) + +(defmethod parse-frag "Property" [{:keys [key value]} state] + [(parse-frag key (assoc state :single? true)) + (parse-frag value (assoc state :single? true))]) + +(defmethod parse-frag "MemberExpression" [{:keys [object property computed] :as m} state] + (let [obj (parse-frag object state) + prop (parse-frag property state)] + (if (:special-js? state) + [obj prop] + (cond + (not computed) (str "(.-" prop " " obj ")") + (re-matches #"\"?\d+\"?" prop) (str "(nth " obj " " (js/parseInt prop) ")") + :else (str "(aget " obj " " prop ")"))))) + +(defmethod parse-frag "ObjectPattern" [{:keys [properties]} state] + (mapv #(parse-frag % (assoc state :single? true)) + properties)) + +(defmethod parse-frag "AssignmentPattern" [{:keys [left right]} state] + [(parse-frag left (assoc state :single? true)) + (parse-frag right (assoc state :single? true))]) + +(defmethod parse-frag "SpreadElement" [{:keys [argument]} state] + [(parse-frag argument state)]) + +(defn- gen-properties [class [property {:keys [get set]}]] + (str "(.defineProperty js/Object (.-prototype " class + ") " (pr-str property) " #js {" + (when get + (str ":get (fn [] " (str/replace-first get #".*this\]" "(this-as this"))) + (when set + (let [[_ params] (re-find #"\[this (.*)\]" set)] + (str ":set (fn [" params "] " + (str/replace-first set #".*this.*\]" "(this-as this")))) + ")})")) + +(defn- class-declaration [{:keys [id, superClass, body]} state] + (swap! (:cljs-requires state) conj '[shadow.cljs.modern :as modern]) + (let [class-name (parse-frag id state) + {:keys [constructor methods properties]} (parse-frag body state) + super (some-> superClass (parse-frag state)) + defclass (str "(modern/defclass " class-name + (when super (str " (extends " super ")")) + " " + (if constructor constructor "(constructor [this])") + (when (seq methods) + (->> methods (cons " Object") (str/join " "))) + ")")] + (cond-> defclass + properties (str (->> properties + (map #(gen-properties class-name %)) + (cons "") + (str/join " ")))))) + +(defmethod parse-frag "ClassDeclaration" [props state] (class-declaration props state)) +(defmethod parse-frag "ClassExpression" [props state] (class-declaration props state)) + +(defmethod parse-frag "ClassBody" [{:keys [body]} state] + (let [state (assoc state :js-class? true)] + (reduce (fn [acc b] + (case (:kind b) + "constructor" (assoc acc :constructor (parse-frag b state)) + "get" (assoc-in acc [:properties (-> b :key :name) :get] (parse-frag b state)) + "set" (assoc-in acc [:properties (-> b :key :name) :set] (parse-frag b state)) + (update acc :methods conj (parse-frag b state)))) + {:methods []} + body))) + +(defmethod parse-frag "MethodDefinition" [{:keys [key value]} state] + (let [{:keys [params body]} value + {:keys [lets params]} (normalize-params params state) + body (some->> (parse-frag body state) not-empty (str " ")) + norm-body (if lets + (str " (let [" lets "]" body ")") + body)] + (str "(" (parse-frag key state) + " [this" (cond->> params (seq params) (str " ")) + "]" + norm-body + ")"))) + +(defmethod parse-frag "ThisExpression" [_ state] + (if (:js-class? state) + "this" + "(js* \"this\")")) + +(defmethod parse-frag "TryStatement" [{:keys [block handler finalizer]} state] + (str "(try " (parse-frag block state) + (when handler + (str " (catch :default " (parse-frag (:param handler) state) + " " (parse-frag (:body handler) state) ")")) + (when finalizer + (str " (finally " (parse-frag finalizer state) ")")) + ")")) + +(defmethod parse-frag "SwitchStatement" [{:keys [discriminant cases]} state] + (let [state (assoc state :single? true) + test (parse-frag discriminant state) + cases (map #(parse-frag % state) cases)] + (str "(case " test + " " (str/join " " cases) + ")"))) + +(defmethod parse-frag "SwitchCase" [{:keys [test consequent]} state] + (let [body (block consequent state " ")] + (if test + (str (parse-frag test state) " " body) + body))) + +(defmethod parse-frag "ArrayPattern" [{:keys [elements]} state] + (str "[" + (->> elements + (map #(parse-frag % (assoc state :single? true))) + (str/join " ")) + "]")) + +(defmethod parse-frag "WhileStatement" [{:keys [test body]} state] + (str "(while " (parse-frag test (assoc state :single? true)) + " " (parse-frag body (assoc state :single? false)) + ")")) + +(defmethod parse-frag "BreakStatement" [_ _] nil) + +(defmethod parse-frag "RestElement" [{:keys [argument]} state] + (str "& " (parse-frag argument (assoc state :single? true)))) + +(defmethod parse-frag "UpdateExpression" [{:keys [operator prefix argument]} state] + (let [macro (if prefix + (str operator "~{}") + (str "~{}" operator))] + (str "(js* " (pr-str macro) " " (parse-frag argument (assoc state :single? true)) ")"))) + +(defmethod parse-frag :default [dbg state] + (tap> dbg) + (def t (:type dbg)) + (throw (ex-info (str "Not implemented: " (:type dbg)) + {:element (:type dbg)}))) + +#_(parse-str "a++") + +#_(from-js "a.b = 1") +#_(from-js "a[b] = 1") + +(defn- from-js [code] + (-> code + (parse #js {:ecmaVersion 2020}) + js/JSON.stringify + js/JSON.parse + (js->clj :keywordize-keys true))) + +(defn- add-requires [code requires] + (cond->> code + (seq requires) + (str "(ns your.ns (:require " (str/join " " requires) ")) "))) + +(defn parse-str + ([code] + (let [reqs (atom #{})] + (-> code + from-js + (parse-frag {:cljs-requires reqs :debug (atom nil)}) + (add-requires @reqs)))) + ([code opts] + (let [reqs (atom #{})] + (-> code + from-js + (parse-frag (assoc (:format-opts opts) :cljs-requires reqs)) + (add-requires @reqs) + (cond-> + (-> opts :zprint-opts :disable not) + (zprint/zprint-file-str "file: example.cljs" (:zprint-opts opts))))))) diff --git a/src/cljs-lib/src/pez_cljfmt/core.clj b/src/cljs-lib/src/pez_cljfmt/core.clj new file mode 100644 index 0000000..dd5b6d4 --- /dev/null +++ b/src/cljs-lib/src/pez_cljfmt/core.clj @@ -0,0 +1,5 @@ +(ns pez-cljfmt.core + (:require [clojure.java.io :as io])) + +(def read-resource* (comp read-string slurp io/resource)) +(defmacro read-resource [path] `'~(read-resource* path)) \ No newline at end of file diff --git a/src/cljs-lib/src/pez_cljfmt/core.cljs b/src/cljs-lib/src/pez_cljfmt/core.cljs new file mode 100644 index 0000000..75f0625 --- /dev/null +++ b/src/cljs-lib/src/pez_cljfmt/core.cljs @@ -0,0 +1,485 @@ +(ns pez-cljfmt.core + (:require [cljs.reader :as reader] + [clojure.zip :as zip] + [clojure.string :as str] + [pez-rewrite-clj.node :as n] + [pez-rewrite-clj.parser :as p] + [pez-rewrite-clj.zip :as z] + [pez-rewrite-clj.zip.base :as zb :refer [edn]] + [pez-rewrite-clj.zip.whitespace :as zw + :refer [append-space skip whitespace-or-comment?]]) + (:require-macros [pez-cljfmt.core :refer [read-resource]])) + +(def zwhitespace? + zw/whitespace?) + +(def zlinebreak? + zw/linebreak?) + +(def includes? + str/includes?) + +(defn- find-all [zloc p?] + (loop [matches [] + zloc zloc] + (if-let [zloc (z/find-next zloc zip/next p?)] + (recur (conj matches zloc) + (zip/next zloc)) + matches))) + +(defn- edit-all [zloc p? f] + (loop [zloc (if (p? zloc) (f zloc) zloc)] + (if-let [zloc (z/find-next zloc zip/next p?)] + (recur (f zloc)) + zloc))) + +(defn- transform [form zf & args] + (z/root (apply zf (edn form) args))) + +(defn- surrounding? [zloc p?] + (and (p? zloc) (or (nil? (zip/left zloc)) + (nil? (skip zip/right p? zloc))))) + +(defn- top? [zloc] + (and zloc (not= (z/node zloc) (z/root zloc)))) + +(defn- surrounding-whitespace? [zloc] + (and (top? (z/up zloc)) + (surrounding? zloc zwhitespace?))) + +(defn remove-surrounding-whitespace [form] + (transform form edit-all surrounding-whitespace? zip/remove)) + +(defn- element? [zloc] + (and zloc (not (whitespace-or-comment? zloc)))) + +(defn- reader-macro? [zloc] + (and zloc (= (n/tag (z/node zloc)) :reader-macro))) + +(defn- missing-whitespace? [zloc] + (and (element? zloc) + (not (reader-macro? (zip/up zloc))) + (element? (zip/right zloc)))) + +(defn insert-missing-whitespace [form] + (transform form edit-all missing-whitespace? append-space)) + +(defn- whitespace? [zloc] + (= (z/tag zloc) :whitespace)) + +(defn- comma? [zloc] + (= (z/tag zloc) :comma)) + +(defn- comment? [zloc] + (some-> zloc z/node n/comment?)) + +(defn- line-break? [zloc] + (or (zlinebreak? zloc) (comment? zloc))) + +(defn- skip-whitespace [zloc] + (skip zip/next whitespace? zloc)) + +(defn- skip-comma [zloc] + (skip zip/next comma? zloc)) + +(defn- count-newlines [zloc] + (loop [zloc zloc, newlines 0] + (if (zlinebreak? zloc) + (recur (-> zloc zip/right skip-whitespace) + (-> zloc z/string count (+ newlines))) + newlines))) + +(defn- final-transform-element? [zloc] + (= (z/next zloc) zloc)) + +(defn- consecutive-blank-line? [zloc] + (and (> (count-newlines zloc) 2) + (not (final-transform-element? zloc)))) + +(defn- remove-whitespace-and-newlines [zloc] + (if (zwhitespace? zloc) + (recur (zip/remove zloc)) + zloc)) + +(defn- replace-consecutive-blank-lines [zloc] + (-> zloc + z/next + zip/prev + remove-whitespace-and-newlines + z/next + (zip/insert-left (n/newlines 2)))) + +(defn remove-consecutive-blank-lines [form] + (transform form edit-all consecutive-blank-line? replace-consecutive-blank-lines)) + +(defn- indentation? [zloc] + (and (line-break? (zip/prev zloc)) (whitespace? zloc))) + +(defn- comment-next? [zloc] + (-> zloc zip/next skip-whitespace comment?)) + +(defn- should-indent? [zloc] + (and (line-break? zloc) (not (comment-next? zloc)))) + +(defn- should-unindent? [zloc] + (and (indentation? zloc) (not (comment-next? zloc)))) + +(defn unindent [form] + (transform form edit-all should-unindent? zip/remove)) + +(def ^:private start-element + {:meta "^", :meta* "#^", :vector "[", :map "{" + :list "(", :eval "#=", :uneval "#_", :fn "#(" + :set "#{", :deref "@", :reader-macro "#", :unquote "~" + :var "#'", :quote "'", :syntax-quote "`", :unquote-splicing "~@" + ;; hy-specific options + :j-table "@{" :j-array "@[" :j-buffer "@\"" + }) + +(defn- prior-line-string [zloc] + (loop [zloc zloc + worklist '()] + (if-let [p (zip/left zloc)] + (let [s (str (n/string (z/node p))) + new-worklist (cons s worklist)] + (if-not (includes? s "\n") + (recur p new-worklist) + (apply str new-worklist))) + (if-let [p (zip/up zloc)] + ;; newline cannot be introduced by start-element + (recur p (cons (start-element (n/tag (z/node p))) worklist)) + (apply str worklist))))) + +(defn- last-line-in-string [^String s] + (subs s (inc (.lastIndexOf s "\n")))) + +(defn- margin [zloc] + (-> zloc prior-line-string last-line-in-string count)) + +(defn- whitespace [width] + (n/whitespace-node (apply str (repeat width " ")))) + +(defn- coll-indent [zloc] + (-> zloc zip/leftmost margin)) + +(defn- index-of [zloc] + (->> (iterate z/left zloc) + (take-while identity) + (count) + (dec))) + +(defn- list-indent [zloc] + (if (> (index-of zloc) 1) + (-> zloc zip/leftmost z/right margin) + (coll-indent zloc))) + +(def indent-size 2) + +(defn- indent-width [zloc] + (case (z/tag zloc) + :list indent-size + :fn (inc indent-size))) + +(defn- remove-namespace [x] + (if (symbol? x) (symbol (name x)) x)) + +(defn pattern? [v] + (instance? js/RegExp v)) + +(defn- indent-matches? [key sym] + (cond + (symbol? key) (= key sym) + (pattern? key) (re-find key (str sym)))) + +(defn- token? [zloc] + (= (z/tag zloc) :token)) + +(defn- token-value [zloc] + (and (token? zloc) (z/sexpr zloc))) + +(defn- reader-conditional? [zloc] + (and (reader-macro? zloc) (#{"?" "?@"} (-> zloc z/down token-value str)))) + +(defn- form-symbol [zloc] + (-> zloc z/leftmost token-value)) + +(defn- index-matches-top-argument? [zloc depth idx] + (and (> depth 0) + (= (inc idx) (index-of (nth (iterate z/up zloc) depth))))) + +(defn- fully-qualify-symbol [possible-sym alias-map] + (if-let [ns-string (and (symbol? possible-sym) + (namespace possible-sym))] + (symbol (get alias-map ns-string ns-string) + (name possible-sym)) + possible-sym)) + +(defn- inner-indent [zloc key depth idx alias-map] + (let [top (nth (iterate z/up zloc) depth)] + (if (and (or (indent-matches? key (fully-qualify-symbol (form-symbol top) alias-map)) + (indent-matches? key (remove-namespace (form-symbol top)))) + (or (nil? idx) (index-matches-top-argument? zloc depth idx))) + (let [zup (z/up zloc)] + (+ (margin zup) (indent-width zup)))))) + +(defn- nth-form [zloc n] + (reduce (fn [z f] (if z (f z))) + (z/leftmost zloc) + (repeat n z/right))) + +(defn- first-form-in-line? [zloc] + (and (some? zloc) + (if-let [zloc (zip/left zloc)] + (if (whitespace? zloc) + (recur zloc) + (or (zlinebreak? zloc) (comment? zloc))) + true))) + +(defn- block-indent [zloc key idx alias-map] + (if (or (indent-matches? key (fully-qualify-symbol (form-symbol zloc) alias-map)) + (indent-matches? key (remove-namespace (form-symbol zloc)))) + (let [zloc-after-idx (some-> zloc (nth-form (inc idx)))] + (if (and (or (nil? zloc-after-idx) (first-form-in-line? zloc-after-idx)) + (> (index-of zloc) idx)) + (inner-indent zloc key 0 nil alias-map) + (list-indent zloc))))) + +(def default-indents + (merge (read-resource "cljfmt/indents/clojure.clj") + (read-resource "cljfmt/indents/compojure.clj") + (read-resource "cljfmt/indents/fuzzy.clj"))) + +(defmulti ^:private indenter-fn + (fn [sym alias-map [type & args]] type)) + +(defmethod indenter-fn :inner [sym alias-map [_ depth idx]] + (fn [zloc] (inner-indent zloc sym depth idx alias-map))) + +(defmethod indenter-fn :block [sym alias-map [_ idx]] + (fn [zloc] (block-indent zloc sym idx alias-map))) + +(defn- make-indenter [[key opts] alias-map] + (apply some-fn (map (partial indenter-fn key alias-map) opts))) + +(defn- indent-order [[key _]] + (cond + (and (symbol? key) (namespace key)) (str 0 key) + (symbol? key) (str 1 key) + (pattern? key) (str 2 key))) + +(defn- custom-indent [zloc indents alias-map] + (if (empty? indents) + (list-indent zloc) + (let [indenter (->> (sort-by indent-order indents) + (map #(make-indenter % alias-map)) + (apply some-fn))] + (or (indenter zloc) + (list-indent zloc))))) + +(defn- indent-amount [zloc indents alias-map] + (let [tag (-> zloc z/up z/tag) + gp (-> zloc z/up z/up)] + (cond + (reader-conditional? gp) (coll-indent zloc) + (#{:list :fn} tag) (custom-indent zloc indents alias-map) + (= :meta tag) (indent-amount (z/up zloc) indents alias-map) + :else (coll-indent zloc)))) + +(defn- indent-line [zloc indents alias-map] + (let [width (indent-amount zloc indents alias-map)] + (if (> width 0) + (zip/insert-right zloc (whitespace width)) + zloc))) + +(defn indent + ([form] + (indent form default-indents)) + ([form indents] + (transform form edit-all should-indent? #(indent-line % indents {}))) + ([form indents alias-map] + (transform form edit-all should-indent? #(indent-line % indents alias-map)))) + +(defn reindent + ([form] + (indent (unindent form))) + ([form indents] + (indent (unindent form) indents)) + ([form indents alias-map] + (indent (unindent form) indents alias-map))) + +(defn root? [zloc] + (nil? (zip/up zloc))) + +(defn final? [zloc] + (and (nil? (zip/right zloc)) (root? (zip/up zloc)))) + +(defn- trailing-whitespace? [zloc] + (and (whitespace? zloc) + (or (zlinebreak? (zip/right zloc)) (final? zloc)))) + +(defn remove-trailing-whitespace [form] + (transform form edit-all trailing-whitespace? zip/remove)) + +(defn- top-level-form [zloc] + (->> zloc + (iterate z/up) + (take-while (complement root?)) + last)) + +(def default-line-separator + \newline) + +(defn normalize-newlines [s] + (str/replace s #"\r\n" "\n")) + +(defn replace-newlines [s sep] + (str/replace s #"\n" sep)) + +(defn find-line-separator [s] + (or (re-find #"\r?\n" s) default-line-separator)) + +(defn wrap-normalize-newlines [f] + (fn [s] + (let [sep (find-line-separator s)] + (-> s normalize-newlines f (replace-newlines sep))))) +(defn- append-newline-if-absent [zloc] + (if (or (-> zloc zip/right skip-whitespace skip-comma line-break?) + (z/rightmost? zloc)) + zloc + (zip/insert-right zloc (n/newlines 1)))) + +(defn- map-odd-seq + "Applies f to all oddly-indexed nodes." + [f zloc] + (loop [loc (z/down zloc) + parent zloc] + (if-not (and loc (z/node loc)) + parent + (if-let [v (f loc)] + (recur (z/right (z/right v)) (z/up v)) + (recur (z/right (z/right loc)) parent))))) + +(defn- map-even-seq + "Applies f to all evenly-indexed nodes." + [f zloc] + (loop [loc (z/right (z/down zloc)) + parent zloc] + (if-not (and loc (z/node loc)) + parent + (if-let [v (f loc)] + (recur (z/right (z/right v)) (z/up v)) + (recur (z/right (z/right loc)) parent))))) + +(defn- add-map-newlines [zloc] + (map-even-seq #(cond-> % (complement z/rightmost?) + append-newline-if-absent) zloc)) + +(defn- add-binding-newlines [zloc] + (map-even-seq append-newline-if-absent zloc)) + +(defn- update-in-path [[node path :as loc] k f] + (let [v (get path k)] + (if (seq v) + (with-meta + [node (assoc path k (f v) :changed? true)] + (meta loc)) + loc))) + +(defn- remove-right + [loc] + (update-in-path loc :r next)) + +(defn- *remove-right-while + [zloc p?] + (loop [zloc zloc] + (if-let [rloc (zip/right zloc)] + (if (p? rloc) + (recur (remove-right zloc)) + zloc) + zloc))) + +(defn- align-seq-value [zloc max-length] + (let [key-length (-> zloc z/sexpr str count) + width (- max-length key-length) + zloc (*remove-right-while zloc zwhitespace?)] + (zip/insert-right zloc (whitespace (inc width))))) + +(defn- align-map [zloc] + (let [key-list (-> zloc z/sexpr keys) + max-key-length (apply max (map #(-> % str count) key-list))] + (map-odd-seq #(align-seq-value % max-key-length) zloc))) + +(defn- align-binding [zloc] + (let [vec-sexpr (z/sexpr zloc) + odd-elements (take-nth 2 vec-sexpr) + max-length (apply max (map #(-> % str count) odd-elements))] + (map-odd-seq #(align-seq-value % max-length) zloc))) + +(defn- align-elements [zloc] + (if (z/map? zloc) + (-> zloc align-map add-map-newlines) + (-> zloc align-binding add-binding-newlines))) + +(def ^:private binding-keywords + #{"doseq" "let" "loop" "binding" "with-open" "go-loop" "if-let" "when-some" + "if-some" "for" "with-local-vars" "with-redefs" "when-let"}) + +(defn- binding? [zloc] + (and (z/vector? zloc) + (-> zloc z/sexpr count even?) + (->> zloc + z/left + z/string + (contains? binding-keywords)))) + +(defn- align-binding? [zloc] + (and (binding? zloc) + (-> zloc z/sexpr count (> 2)))) + +(defn- empty-seq? [zloc] + (if (z/map? zloc) + (-> zloc z/sexpr empty?) + false)) + +(defn- align-map? [zloc] + (and (z/map? zloc) + (not (empty-seq? zloc)))) + +(defn- align-elements? [zloc] + (or (align-binding? zloc) + (align-map? zloc))) + +(defn align-collection-elements [form] + (transform form edit-all align-elements? align-elements)) + + +(defn reformat-form + ([form] + (reformat-form form {})) + ([form opts] + (-> form + (cond-> (:remove-consecutive-blank-lines? opts true) + remove-consecutive-blank-lines) + (cond-> (:remove-surrounding-whitespace? opts true) + remove-surrounding-whitespace) + (cond-> (:insert-missing-whitespace? opts true) + insert-missing-whitespace) + (cond-> (:align-associative? opts true) + align-collection-elements) + (cond-> (:indentation? opts true) + (reindent (:indents opts default-indents))) + (cond-> (:remove-trailing-whitespace? opts true) + remove-trailing-whitespace)))) + + +(defn reformat-string + ([form-string] + (reformat-string form-string {})) + ([form-string options] + (let [parsed-form (p/parse-string-all form-string) + alias-map (:alias-map options)] + (-> parsed-form + (reformat-form (cond-> options + alias-map (assoc :alias-map alias-map))) + (n/string))))) + diff --git a/src/cljs-lib/src/pez_rewrite_clj/node.cljs b/src/cljs-lib/src/pez_rewrite_clj/node.cljs new file mode 100644 index 0000000..c6729e3 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node.cljs @@ -0,0 +1,197 @@ +(ns pez-rewrite-clj.node + "Facade for node related namespaces." + (:require [pez-rewrite-clj.node.coercer] + [pez-rewrite-clj.node.protocols :as prot] + [pez-rewrite-clj.node.keyword :as kw-node] + [pez-rewrite-clj.node.seq :as seq-node] + [pez-rewrite-clj.node.whitespace :as ws-node] + [pez-rewrite-clj.node.token :as tok-node] + [pez-rewrite-clj.node.comment :as cmt-node] + [pez-rewrite-clj.node.forms :as fm-node] + [pez-rewrite-clj.node.meta :as mt-node] + [pez-rewrite-clj.node.stringz :as s-node] + [pez-rewrite-clj.node.reader-macro :as rm-node] + [pez-rewrite-clj.node.quote :as q-node] + [pez-rewrite-clj.node.uneval :as ue-node] + [pez-rewrite-clj.node.fn :as f-node])) + + + + + +; ******************************* +; see pez-rewrite-clj.node.protocols +; ******************************* +(def tag + "See [[protocols/tag]]" + prot/tag) +(def sexpr + "See [[protocols/sexpr]]" + prot/sexpr) +(def string + "See [[protocols/string]]" + prot/string) +(def children + "See [[protocols/children]]" + prot/children) +(def child-sexprs + "See [[protocols/sexprs]]" + prot/child-sexprs) +(def replace-children + "See [[protocols/replace-children]]" + prot/replace-children) +(def inner? + "See [[protocols/inner?]]" + prot/inner?) +(def printable-only? + "See [[protocols/printable-only?]]" + prot/printable-only?) +(def coerce + "See [[protocols/coerce]]" + prot/coerce) +(def length + "See [[protocols/length]]" + prot/length) + + +; ******************************* +; see pez-rewrite-clj.node.forms +; ******************************* +(def forms-node + "see [[forms/forms-node]]" + fm-node/forms-node) +(def keyword-node + "see [[keyword/keyword-node]]" + kw-node/keyword-node) + + +; ******************************* +; see pez-rewrite-clj.node.seq +; ******************************* +(def list-node + "See [[seq/list-node]]" + seq-node/list-node) +(def vector-node + "See [[seq/vector-node]]" + seq-node/vector-node) +(def set-node + "See [[seq/set-node]]" + seq-node/set-node) +(def map-node + "See [[seq/map-node]]" + seq-node/map-node) + + +; ******************************* +; see pez-rewrite-clj.node.string +; ******************************* +(def string-node + "See [[stringz/string-node]]" + s-node/string-node) + + + +; ******************************* +; see pez-rewrite-clj.node.comment +; ******************************* +(def comment-node + "See [[comment/comment-node]]" + cmt-node/comment-node) +(def comment? + "See [[comment/comment?]]" + cmt-node/comment?) + + + +; ******************************* +; see pez-rewrite-clj.node.whitespace +; ******************************* +(def whitespace-node + "See [[whitespace/whitespace-node]]" + ws-node/whitespace-node) +(def newline-node + "See [[whitespace/newline-node]]" + ws-node/newline-node) +(def spaces + "See [[whitespace/spaces]]" + ws-node/spaces) +(def newlines + "See [[whitespace/newlines]]" + ws-node/newlines) +(def whitespace? + "See [[whitespace/whitespace?]]" + ws-node/whitespace?) +(def linebreak? + "See [[whitespace/linebreak?]]" + ws-node/linebreak?) + +(defn whitespace-or-comment? + "Check whether the given node represents whitespace or comment." + [node] + (or (whitespace? node) + (comment? node))) + + +; ******************************* +; see pez-rewrite-clj.node.token +; ******************************* +(def token-node + "See [[token/token-node]]" + tok-node/token-node) + + +; ******************************* +; see pez-rewrite-clj.node.reader-macro +; ******************************* +(def var-node + "See [[reader-macro/var-node]]" + rm-node/var-node) +(def eval-node + "See [[reader-macro/eval-node]]" + rm-node/eval-node) +(def reader-macro-node + "See [[reader-macro/reader-macro-node]]" + rm-node/reader-macro-node) +(def deref-node + "See [[reader-macro/deref-node]]" + rm-node/deref-node) + + +; ******************************* +; see pez-rewrite-clj.node.quote +; ******************************* +(def quote-node + "See [[quote/quote-node]]" + q-node/quote-node) +(def syntax-quote-node + "See [[quote/syntax-quote-node]]" + q-node/syntax-quote-node) +(def unquote-node + "See [[quote/unquote-node]]" + q-node/unquote-node) +(def unquote-splicing-node + "See [[quote/unquote-splicing-node]]" + q-node/unquote-splicing-node) + + +; ******************************* +; see pez-rewrite-clj.node.uneval +; ******************************* +(def uneval-node + "See [[uneval/uneval-node]]" + ue-node/uneval-node) + + +; ******************************* +; see pez-rewrite-clj.node.meta +; ******************************* +(def meta-node + "See [[meta/meta-node]]" + mt-node/meta-node) + +; ******************************* +; see pez-rewrite-clj.node.fn +; ******************************* +(def fn-node + "See [[fn/fn-node]]" + f-node/fn-node) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/coercer.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/coercer.cljs new file mode 100644 index 0000000..9f208ad --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/coercer.cljs @@ -0,0 +1,136 @@ +(ns pez-rewrite-clj.node.coercer + (:require [pez-rewrite-clj.node.comment :refer [CommentNode]] + [pez-rewrite-clj.node.forms :refer [FormsNode]] + [pez-rewrite-clj.node.keyword :refer [KeywordNode]] + [pez-rewrite-clj.node.quote :refer [QuoteNode]] + [pez-rewrite-clj.node.stringz :refer [StringNode string-node]] + [pez-rewrite-clj.node.uneval :refer [UnevalNode]] + [pez-rewrite-clj.node.meta :refer [MetaNode meta-node]] + [pez-rewrite-clj.node.fn :refer [FnNode]] + [pez-rewrite-clj.node.protocols :refer [NodeCoerceable coerce]] + [pez-rewrite-clj.node.reader-macro :refer [ReaderNode ReaderMacroNode DerefNode]] + [pez-rewrite-clj.node.seq :refer [SeqNode vector-node list-node set-node map-node]] + [pez-rewrite-clj.node.token :refer [TokenNode token-node]] + [pez-rewrite-clj.node.whitespace :refer [WhitespaceNode NewlineNode whitespace-node space-separated]])) + +;; ## Helpers + +(defn node-with-meta + [n value] + (if (implements? IWithMeta value) + (let [mta (meta value)] + (if (empty? mta) + n + (meta-node (coerce mta) n))) + n)) + + +;; ## Tokens + +(extend-protocol NodeCoerceable + object + (coerce [v] + (node-with-meta + (token-node v) + v))) + +;; Number +(extend-protocol NodeCoerceable + number + (coerce [n] + (node-with-meta + (token-node n) + n))) + +;; Number +(extend-protocol NodeCoerceable + string + (coerce [n] + (node-with-meta + (string-node n) + n))) + + + +;; ## Seqs + +(defn seq-node + [f sq] + (node-with-meta + (->> (map coerce sq) + (space-separated) + (vec) + (f)) + sq)) + +(extend-protocol NodeCoerceable + PersistentVector + (coerce [sq] + (seq-node vector-node sq)) + List + (coerce [sq] + (seq-node list-node sq)) + PersistentHashSet + (coerce [sq] + (seq-node set-node sq))) + + + + +;; ## Maps + +(let [comma (whitespace-node ", ") + space (whitespace-node " ")] + (defn- map->children + [m] + (->> (mapcat + (fn [[k v]] + [(coerce k) space (coerce v) comma]) + m) + (butlast) + (vec)))) + + +(extend-protocol NodeCoerceable + PersistentHashMap + (coerce [m] + (node-with-meta + (map-node (map->children m)) + m))) + + + + +;(seq-node vector-node [1]) + +;; ## Vars + +;; (extend-protocol NodeCoerceable +;; Var +;; (coerce [v] +;; (-> (str v) +;; (subs 2) +;; (symbol) +;; (token-node) +;; (vector) +;; (var-node)))) + +;; ## Existing Nodes + +(extend-protocol NodeCoerceable + CommentNode (coerce [v] v) + FormsNode (coerce [v] v) + FnNode (coerce [v] v) + ;IntNode (coerce [v] v) + KeywordNode (coerce [v] v) + MetaNode (coerce [v] v) + QuoteNode (coerce [v] v) + ReaderNode (coerce [v] v) + ReaderMacroNode (coerce [v] v) + DerefNode (coerce [v] v) + StringNode (coerce [v] v) + ;UnevalNode (coerce [v] v) + NewlineNode (coerce [v] v) + SeqNode (coerce [v] v) + TokenNode (coerce [v] v) + WhitespaceNode (coerce [v] v)) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/comment.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/comment.cljs new file mode 100644 index 0000000..607b80d --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/comment.cljs @@ -0,0 +1,36 @@ +(ns pez-rewrite-clj.node.comment + (:require [pez-rewrite-clj.node.protocols :as node])) + +;; ## Node + +(defrecord CommentNode [s] + node/Node + (tag [_] :comment) + (printable-only? [_] true) + (sexpr [_] + (throw (js/Error. "Unsupported operation"))) + (length [_] + (+ 1 (count s))) + (string [_] + (str ";" s)) + + Object + (toString [this] + (node/string this))) + +;;(node/make-printable! CommentNode) + +;; ## Constructor + +(defn comment-node + "Create node representing an EDN comment." + [s] + (->CommentNode s)) + +(defn comment? + "Check whether a node represents a comment." + [node] + (= (node/tag node) :comment)) + + + diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/fn.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/fn.cljs new file mode 100644 index 0000000..6aadc54 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/fn.cljs @@ -0,0 +1,97 @@ +(ns ^:no-doc pez-rewrite-clj.node.fn + (:require [pez-rewrite-clj.node.protocols :as node] + [clojure.walk :as w])) + +;; ## Conversion + +(defn- construct-fn + "Construct function form." + [syms vararg body] + (list + 'fn* + (vec + (concat + syms + (if vararg + (list '& vararg)))) + body)) + +(defn- sym-index + "Get index based on the substring following the parameter's `%`. + Zero means vararg." + [n] + (cond (= n "&") 0 + (= n "") 1 + (re-matches #"\d+" n) (js/parseInt n) + :else (throw (js/Error. "arg literal must be %, %& or %integer.")))) + +;; TODO: No promises available +(defn- symbol->gensym + "If symbol starting with `%`, convert to respective gensym." + [sym-seq vararg? max-n sym] + (if (symbol? sym) + (let [nm (name sym)] + (if (= (.indexOf nm "%") 0) + (let [i (sym-index (subs nm 1))] +;; (if (and (= i 0) (not (realized? vararg?))) +;; (deliver vararg? true)) + (swap! max-n max i) + (nth sym-seq i)))))) + +;; TODO: No promises available +(defn- fn-walk + "Walk the form and create an expand function form." + [form] + (let [syms (for [i (range) + :let [base (if (= i 0) + "rest__" + (str "p" i "__")) + s (name (gensym base))]] + (symbol (str s "#"))) + vararg? false ;(promise) + max-n (atom 0) + body (w/prewalk + #(or (symbol->gensym syms vararg? max-n %) %) + form)] + (construct-fn + (take @max-n (rest syms)) + nil +;; (if (deref vararg? 0 nil) +;; (first syms)) + body))) + +;; ## Node + +(defrecord FnNode [children] + node/Node + (tag [_] :fn) + (printable-only? [_] + false) + (sexpr [_] + (fn-walk (node/sexprs children))) + (length [_] + (+ 3 (node/sum-lengths children))) + (string [_] + (str "#(" (node/concat-strings children) ")")) + + node/InnerNode + (inner? [_] + true) + (children [_] + children) + (replace-children [this children'] + (assoc this :children children')) + + Object + (toString [this] + (node/string this))) + +;; TODO +;(node/make-printable! FnNode) + +;; ## Constructor + +(defn fn-node + "Create node representing an anonymous function." + [children] + (->FnNode children)) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/forms.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/forms.cljs new file mode 100644 index 0000000..deda783 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/forms.cljs @@ -0,0 +1,43 @@ +(ns pez-rewrite-clj.node.forms + (:require [pez-rewrite-clj.node.protocols :as node])) + +;; ## Node + +(defrecord FormsNode [children] + node/Node + (tag [_] + :forms) + (printable-only? [_] + false) + (sexpr [_] + (let [es (node/sexprs children)] + (if (next es) + (list* 'do es) + (first es)))) + (length [_] + (node/sum-lengths children)) + (string [_] + (node/concat-strings children)) + + node/InnerNode + (inner? [_] + true) + (children [_] + children) + (replace-children [this children'] + (assoc this :children children')) + + Object + (toString [this] + (node/string this))) + +;; TODO: Macro fun ! +;(node/make-printable! FormsNode) + +;; ## Constructor + +(defn forms-node + "Create top-level node wrapping multiple children + (equals an implicit `do` on the top-level)." + [children] + (->FormsNode children)) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/keyword.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/keyword.cljs new file mode 100644 index 0000000..3dc047e --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/keyword.cljs @@ -0,0 +1,48 @@ +(ns pez-rewrite-clj.node.keyword + (:require [pez-rewrite-clj.node.protocols :as node])) + +;; ## Node + +(defrecord KeywordNode [k namespaced?] + node/Node + (tag [_] :token) + (printable-only? [_] false) + (sexpr [_] + (if (and namespaced? + (not (namespace k))) +;; (keyword +;; (name (ns-name *ns*)) +;; (name k)) + (throw (js/Error. "Namespaced keywords not supported !")) + k)) + (length [this] + (let [c (inc (count (name k)))] + (if namespaced? + (inc c) + (if-let [nspace (namespace k)] + (+ 1 c (count nspace)) + c)))) + (string [_] + (let [v (pr-str k)] + (if namespaced? + (str ":" v) + v))) + + Object + (toString [this] + (node/string this))) + + + + +;; TODO +;;(node/make-printable! KeywordNode) + +;; ## Constructor + +(defn keyword-node + "Create node representing a keyword. If `namespaced?` is given as `true` + a keyword à la `::x` or `::ns/x` (i.e. namespaced/aliased) is generated." + [k & [namespaced?]] + {:pre [(keyword? k)]} + (->KeywordNode k namespaced?)) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/meta.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/meta.cljs new file mode 100644 index 0000000..d3a47ae --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/meta.cljs @@ -0,0 +1,52 @@ +(ns pez-rewrite-clj.node.meta + (:require [pez-rewrite-clj.node.protocols :as node] + [pez-rewrite-clj.node.whitespace :as ws])) + +;; ## Node + +(defrecord MetaNode [tag prefix children] + node/Node + (tag [_] tag) + (printable-only? [_] false) + (sexpr [_] + (let [[mta data] (node/sexprs children)] + (assert (implements? IWithMeta data) + (str "cannot attach metadata to: " (pr-str data))) + (with-meta data (if (map? mta) mta {mta true})))) + (length [_] + (+ (count prefix) (node/sum-lengths children))) + (string [_] + (str prefix (node/concat-strings children))) + + node/InnerNode + (inner? [_] true) + (children [_] children) + (replace-children [this children'] + (node/assert-sexpr-count children' 2) + (assoc this :children children')) + + Object + (toString [this] + (node/string this))) + +;; TODO +;(node/make-printable! MetaNode) + +;; ## Constructor + +(defn meta-node + "Create node representing a form and its metadata." + ([children] + (node/assert-sexpr-count children 2) + (->MetaNode :meta "^" children)) + ([metadata data] + (meta-node [metadata (ws/spaces 1) data]))) + +(defn raw-meta-node + "Create node representing a form and its metadata using the + `#^` prefix." + ([children] + (node/assert-sexpr-count children 2) + (->MetaNode :meta* "#^" children)) + ([metadata data] + (raw-meta-node [metadata (ws/spaces 1) data]))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/protocols.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/protocols.cljs new file mode 100644 index 0000000..8cbb221 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/protocols.cljs @@ -0,0 +1,105 @@ +(ns pez-rewrite-clj.node.protocols + (:require [clojure.string :as s])) + + + +(defprotocol Node + "Protocol for EDN/Clojure nodes." + (tag [_] + "Keyword representing the type of the node.") + (printable-only? [_] + "Return true if the node cannot be converted to an s-expression + element.") + (sexpr [_] + "Convert node to s-expression.") + (length [_] + "Get number of characters for the string version of this node.") + (string [_] + "Convert node to printable string.")) + + +(extend-protocol Node + object + (tag [_] :unknown) + (printable-only? [_] false) + (sexpr [this] this) + (length [this] (count (string this))) + (string [this] (pr-str this))) + +(defn sexprs + "Given a seq of nodes, convert those that represent s-expressions + to the respective forms." + [nodes] + (->> nodes + (remove printable-only?) + (map sexpr))) + +(defn sum-lengths + "Sum up lengths of the given nodes." + [nodes] + (reduce + (map length nodes))) + +(defn concat-strings + "Convert nodes to strings and concatenate them." + [nodes] + (reduce str (map string nodes))) + + +(defprotocol InnerNode + "Protocol for non-leaf EDN/Clojure nodes." + (inner? [_] + "Check whether the node can contain children.") + (children [_] + "Get child nodes.") + (replace-children [_ children] + "Replace the node's children.")) + +(extend-protocol InnerNode + object + (inner? [_] false) + (children [_] + (throw (js/Error. "UnsupportedOperationException"))) + (replace-children [_ _] + (throw (js/Error. "UnsupportedOperationException")))) + +(defn child-sexprs + "Get all child s-expressions for the given node." + [node] + (if (inner? node) + (sexprs (children node)))) + + +(defprotocol NodeCoerceable + "Protocol for values that can be coerced to nodes." + (coerce [_])) + + +;; TODO: Need to handle format !!!! +;; (defn- node->string +;; [node] +;; (let [n (str (if (printable-only? node) +;; (pr-str (string node)) +;; (string node))) +;; n' (if (re-find #"\n" n) +;; (->> (s/replace n #"\r?\n" "\n ") +;; (format "%n %s%n")) +;; (str " " n))] +;; (format "<%s:%s>" (name (tag node)) n'))) + + +;; (defn write-node +;; [writer node] +;; (str writer (node->string node))) + + +;; ## Helpers + +(defn assert-sexpr-count + [nodes c] + (assert + (= (count (remove printable-only? nodes)) c) + (str "can only contain" c " non-whitespace form(s)."))) + +(defn assert-single-sexpr + [nodes] + (assert-sexpr-count nodes 1)) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/quote.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/quote.cljs new file mode 100644 index 0000000..a70c602 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/quote.cljs @@ -0,0 +1,75 @@ +(ns ^:no-doc pez-rewrite-clj.node.quote + (:require [pez-rewrite-clj.node.protocols :as node])) + +;; ## Node + +(defrecord QuoteNode [tag prefix sym children] + node/Node + (tag [_] tag) + (printable-only? [_] false) + (sexpr [_] + (list sym (first (node/sexprs children)))) + (length [_] + (+ (count prefix) (node/sum-lengths children))) + (string [_] + (str prefix (node/concat-strings children))) + + node/InnerNode + (inner? [_] true) + (children [_] children) + (replace-children [this children'] + (node/assert-single-sexpr children') + (assoc this :children children')) + + Object + (toString [this] + (node/string this))) + +;(node/make-printable! QuoteNode) + +;; ## Constructors + +(defn- ->node + [t prefix sym children] + (node/assert-single-sexpr children) + (->QuoteNode t prefix sym children)) + +(defn quote-node + "Create node representing a quoted form. + Takes either a seq of nodes or a single one." + [children] + (if (sequential? children) + (->node + :quote "'" 'quote + children) + (recur [children]))) + +(defn syntax-quote-node + "Create node representing a syntax-quoted form. + Takes either a seq of nodes or a single one." + [children] + (if (sequential? children) + (->node + :syntax-quote "`" 'quote + children) + (recur [children]))) + +(defn unquote-node + "Create node representing an unquoted form. (`~...`) + Takes either a seq of nodes or a single one." + [children] + (if (sequential? children) + (->node + :unquote "~" 'unquote + children) + (recur [children]))) + +(defn unquote-splicing-node + "Create node representing an unquote-spliced form. (`~@...`) + Takes either a seq of nodes or a single one." + [children] + (if (sequential? children) + (->node + :unquote-splicing "~@" 'unquote-splicing + children) + (recur [children]))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/reader_macro.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/reader_macro.cljs new file mode 100644 index 0000000..882b6f8 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/reader_macro.cljs @@ -0,0 +1,134 @@ +(ns ^:no-doc pez-rewrite-clj.node.reader-macro + (:require [pez-rewrite-clj.node.protocols :as node] + [pez-rewrite-clj.node.whitespace :as ws])) + +;; ## Node + +(defrecord ReaderNode [tag prefix suffix + sexpr-fn sexpr-count + children] + node/Node + (tag [_] tag) + (printable-only? [_] + (not sexpr-fn)) + (sexpr [_] + (if sexpr-fn + (sexpr-fn (node/sexprs children)) + (throw (js/Error. "Unsupported operation")))) + (length [_] + (-> (node/sum-lengths children) + (+ 1 (count prefix) (count suffix)))) + (string [_] + (str "#" prefix (node/concat-strings children) suffix)) + + node/InnerNode + (inner? [_] + true) + (children [_] + children) + (replace-children [this children'] + (when sexpr-count + (node/assert-sexpr-count children' sexpr-count)) + (assoc this :children children')) + + Object + (toString [this] + (node/string this))) + +(defrecord ReaderMacroNode [children] + node/Node + (tag [_] :reader-macro) + (printable-only?[_] false) + (sexpr [this] + (list 'read-string (node/string this))) + (length [_] + (inc (node/sum-lengths children))) + (string [_] + (str "#" (node/concat-strings children))) + + node/InnerNode + (inner? [_] + true) + (children [_] + children) + (replace-children [this children'] + (node/assert-sexpr-count children' 2) + (assoc this :children children')) + + Object + (toString [this] + (node/string this))) + +(defrecord DerefNode [children] + node/Node + (tag [_] :deref) + (printable-only?[_] false) + (sexpr [this] + (list* 'deref (node/sexprs children))) + (length [_] + (inc (node/sum-lengths children))) + (string [_] + (str "@" (node/concat-strings children))) + + node/InnerNode + (inner? [_] + true) + (children [_] + children) + (replace-children [this children'] + (node/assert-sexpr-count children' 1) + (assoc this :children children')) + + Object + (toString [this] + (node/string this))) + +;; TODO: +;; (node/make-printable! ReaderNode) +;; (node/make-printable! ReaderMacroNode) +;; (node/make-printable! DerefNode) + +;; ## Constructors + +(defn- ->node + [tag prefix suffix sexpr-fn sexpr-count children] + (when sexpr-count + (node/assert-sexpr-count children sexpr-count)) + (->ReaderNode + tag prefix suffix + sexpr-fn sexpr-count + children)) + +(defn var-node + "Create node representing a var. + Takes either a seq of nodes or a single one." + [children] + (if (sequential? children) + (->node :var "'" "" #(list* 'var %) 1 children) + (recur [children]))) + +(defn eval-node + "Create node representing an inline evaluation. (`#=...`) + Takes either a seq of nodes or a single one." + [children] + (if (sequential? children) + (->node + :eval "=" "" + #(list 'eval (list* 'quote %)) + 1 children) + (recur [children]))) + +(defn reader-macro-node + "Create node representing a reader macro. (`#... ...`)" + ([children] + (->ReaderMacroNode children)) + ([macro-node form-node] + (->ReaderMacroNode [macro-node (ws/spaces 1) form-node]))) + +(defn deref-node + "Create node representing the dereferencing of a form. (`@...`) + Takes either a seq of nodes or a single one." + [children] + (if (sequential? children) + (->DerefNode children) + (->DerefNode [children]))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/seq.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/seq.cljs new file mode 100644 index 0000000..fa46d9c --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/seq.cljs @@ -0,0 +1,65 @@ +(ns pez-rewrite-clj.node.seq + (:require [pez-rewrite-clj.node.protocols :as node])) + +;; ## Node + +(defn wrap-vec [s] (str "[" s "]")) +(defn wrap-list [s] (str "(" s ")")) +(defn wrap-set [s] (str "#{" s "}")) +(defn wrap-map [s] (str "{" s "}")) + + + +(defrecord SeqNode [tag + wrap-fn + wrap-length + seq-fn + children] + node/Node + (tag [this] + tag) + (printable-only? [_] false) + (sexpr [this] + (seq-fn (node/sexprs children))) + (length [_] + (+ wrap-length (node/sum-lengths children))) + (string [this] + (->> (node/concat-strings children) + wrap-fn)) + + node/InnerNode + (inner? [_] + true) + (children [_] + children) + (replace-children [this children'] + (assoc this :children children')) + + Object + (toString [this] + (node/string this))) + +;; TODO +;(node/make-printable! SeqNode) + +;; ## Constructors + +(defn list-node + "Create a node representing an EDN list." + [children] + (->SeqNode :list wrap-list 2 #(apply list %) children)) + +(defn vector-node + "Create a node representing an EDN vector." + [children] + (->SeqNode :vector wrap-vec 2 vec children)) + +(defn set-node + "Create a node representing an EDN set." + [children] + (->SeqNode :set wrap-set 3 set children)) + +(defn map-node + "Create a node representing an EDN map." + [children] + (->SeqNode :map wrap-map 2 #(apply hash-map %) children)) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/stringz.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/stringz.cljs new file mode 100644 index 0000000..6e4929a --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/stringz.cljs @@ -0,0 +1,48 @@ +(ns pez-rewrite-clj.node.stringz + (:require [pez-rewrite-clj.node.protocols :as node] + [cljs.tools.reader :as r] + [clojure.string :as s])) + +;; ## Node + +(defn- wrap-string + [v] + (str "\"" v "\"")) + +(defn- join-lines + [lines] + (s/join "\n" lines)) + +(defrecord StringNode [lines] + node/Node + (tag [_] + (if (next lines) + :multi-line + :token)) + (printable-only? [_] + false) + (sexpr [_] + (join-lines + (map + (comp r/read-string wrap-string) + lines))) + (length [_] + (+ 2 (reduce + (map count lines)))) + (string [_] + (wrap-string (join-lines lines))) + + Object + (toString [this] + (node/string this))) + +;(node/make-printable! StringNode) + +;; ## Constructors + +(defn string-node + "Create node representing a string value. + Takes either a seq of strings or a single one." + [lines] + (if (string? lines) + (->StringNode [lines]) + (->StringNode lines))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/token.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/token.cljs new file mode 100644 index 0000000..723089b --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/token.cljs @@ -0,0 +1,28 @@ +(ns pez-rewrite-clj.node.token + (:require [pez-rewrite-clj.node.protocols :as node])) + +;; ## Node + +(defrecord TokenNode [value string-value] + node/Node + (tag [_] :token) + (printable-only? [_] false) + (sexpr [_] value) + (length [_] (.-length string-value)) + (string [_] string-value) + + Object + (toString [this] + (node/string this))) + +; TODO +;(node/make-printable! TokenNode) + +;; ## Constructor + +(defn token-node + "Create node for an unspecified EDN token." + ([value] + (token-node value (pr-str value))) + ([value string-value] + (->TokenNode value string-value))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/uneval.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/uneval.cljs new file mode 100644 index 0000000..d5ae51f --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/uneval.cljs @@ -0,0 +1,39 @@ +(ns ^:no-doc pez-rewrite-clj.node.uneval + (:require [pez-rewrite-clj.node.protocols :as node])) + +;; ## Node + +(defrecord UnevalNode [children] + node/Node + (tag [_] :uneval) + (printable-only? [_] true) + (sexpr [_] + (throw (js/Error. "Unsupported operation for unevalnode"))) + (length [_] + (+ 2 (node/sum-lengths children))) + (string [_] + (str "#_" (node/concat-strings children))) + + node/InnerNode + (inner? [_] true) + (children [_] children) + (replace-children [this children'] + (node/assert-single-sexpr children') + (assoc this :children children')) + + Object + (toString [this] + (node/string this))) + +;(node/make-printable! UnevalNode) + +;; ## Constructor + +(defn uneval-node + "Create node representing an EDN uneval `#_` form." + [children] + (if (sequential? children) + (do + (node/assert-single-sexpr children) + (->UnevalNode children)) + (recur [children]))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/node/whitespace.cljs b/src/cljs-lib/src/pez_rewrite_clj/node/whitespace.cljs new file mode 100644 index 0000000..020c55d --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/node/whitespace.cljs @@ -0,0 +1,131 @@ +(ns pez-rewrite-clj.node.whitespace + (:require [pez-rewrite-clj.node.protocols :as node])) + +;; ## Newline Modifiers + +(def ^:dynamic *newline-fn* + "This function is applied to every newline string." + identity) + +(def ^:dynamic *count-fn* + "This function is applied to every newline string and should produce + the eventual character count." + count) + + +;; TODO +;; (defmacro with-newline-fn +;; [f & body] +;; `(binding [*newline-fn* (comp *newline-fn* ~f)] +;; ~@body)) + +;; (defmacro with-count-fn +;; [f & body] +;; `(binding [*count-fn* (comp *count-fn* ~f)] +;; ~@body)) + +;; ## Nodes + +(defrecord WhitespaceNode [whitespace] + node/Node + (tag [_] :whitespace) + (printable-only? [_] true) + (sexpr [_] (throw (js/Error. "Unsupported operation"))) + (length [_] (count whitespace)) + (string [_] whitespace) + + Object + (toString [this] + (node/string this))) + +(defrecord NewlineNode [newlines] + node/Node + (tag [_] :newline) + (printable-only? [_] true) + (sexpr [_] (throw (js/Error. "Unsupported operation"))) + (length [_] (*count-fn* newlines)) + (string [_] (*newline-fn* newlines)) + + Object + (toString [this] + (node/string this))) + + +;; TODO +;; (node/make-printable! WhitespaceNode) +;; (node/make-printable! NewlineNode) + +;; ## Constructors + +(defn whitespace-node + "Create whitespace node." + [s] + (->WhitespaceNode s)) + +(defn newline-node + "Create newline node." + [s] + (->NewlineNode s)) + +(defn- newline? + "Check whether a character represents a linebreak." + [c] + (contains? #{\return \newline} c)) + +(defn whitespace-nodes + "Convert a string of whitespace to whitespace/newline nodes." + [s] + (->> (partition-by newline? s) + (map + (fn [char-seq] + (let [s (apply str char-seq)] + (if (newline? (first char-seq)) + (newline-node s) + (whitespace-node s))))))) + +;; ## Utilities + +(defn spaces + "Create node representing the given number of spaces." + [n] + (whitespace-node (apply str (repeat n \space)))) + +(defn newlines + "Create node representing the given number of newline characters." + [n] + (newline-node (apply str (repeat n \newline)))) + + + +(let [comma (whitespace-node ", ")] + (defn comma-separated + "Interleave the given seq of nodes with `\", \"` nodes." + [nodes] + (butlast (interleave nodes (repeat comma))))) + +(let [nl (newline-node "\n")] + (defn line-separated + "Interleave the given seq of nodes with newline nodes." + [nodes] + (butlast (interleave nodes (repeat nl))))) + +(let [space (whitespace-node " ")] + (defn space-separated + "Interleave the given seq of nodes with `\" \"` nodes." + [nodes] + (butlast (interleave nodes (repeat space))))) + +;; ## Predicates + +(defn whitespace? + "Check whether a node represents whitespace." + [node] + (contains? + #{:whitespace + :newline} + (node/tag node))) + +(defn linebreak? + "Check whether a ndoe represents linebreaks." + [node] + (= (node/tag node) :newline)) diff --git a/src/cljs-lib/src/pez_rewrite_clj/paredit.cljs b/src/cljs-lib/src/pez_rewrite_clj/paredit.cljs new file mode 100644 index 0000000..f5c9b36 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/paredit.cljs @@ -0,0 +1,551 @@ +(ns pez-rewrite-clj.paredit + "This namespace provides zipper operations for performing paredit type of + operations on clojure/clojurescript forms. + + You might find inspirational examples here: http://pub.gajendra.net/src/paredit-refcard.pdf" + (:require [pez-rewrite-clj.zip :as z] + [clojure.zip :as zz] + [pez-rewrite-clj.zip.whitespace :as ws] + [pez-rewrite-clj.zip.utils :as u] + [pez-rewrite-clj.node :as nd] + [pez-rewrite-clj.node.stringz :as sn :refer [StringNode] ] + [clojure.string :as cstring])) + + + + +;;***************************** +;; Helpers +;;***************************** + +(defn- ^{:no-doc true} empty-seq? [zloc] + (and (z/seq? zloc) (not (seq (z/sexpr zloc))))) + +;; helper +(defn ^{:no-doc true} move-n [loc f n] + (if (= 0 n) + loc + (->> loc (iterate f) (take (inc n)) last))) + +(defn- ^{:no-doc true} top + [zloc] + (->> zloc + (iterate z/up) + (take-while identity) + last)) + +;; TODO : not very efficent ... +(defn- ^{:no-doc true} global-find-by-node + [zloc n] + (-> zloc + top + (z/find zz/next #(= (meta (z/node %)) (meta n))))) + + + +(defn- ^{:no-doc true} nodes-by-dir + ([zloc f] (nodes-by-dir zloc f constantly)) + ([zloc f p?] + (->> zloc + (iterate f) + (take-while identity) + (take-while p?) + (map z/node)))) + +(defn- ^{:no-doc true} remove-first-if-ws [nodes] + (when (seq nodes) + (if (nd/whitespace? (first nodes)) + (rest nodes) + nodes))) + + +(defn- ^{:no-doc true} remove-ws-or-comment [zloc] + (if-not (ws/whitespace-or-comment? zloc) + zloc + (recur (zz/remove zloc)))) + + +(defn- ^{:no-doc true} create-seq-node + "Creates a sequence node of given type `t` with node values of `v`" + [t v] + (case t + :list (nd/list-node v) + :vector (nd/vector-node v) + :map (nd/map-node v) + :set (nd/set-node v) + (throw (js/Error. (str "Unsupported wrap type: " t))))) + +(defn- ^{:no-doc true} string-node? [zloc] + (= (some-> zloc z/node type) (type (nd/string-node " ")))) + +;;***************************** +;; Paredit functions +;;***************************** + + + + +(defn kill + "Kill all sibling nodes to the right of the current node + + - [1 2| 3 4] => [1 2|]" + [zloc] + (let [left (zz/left zloc)] + (-> zloc + (u/remove-right-while (constantly true)) + zz/remove + (#(if left + (global-find-by-node % (z/node left)) + %))))) + + + +(defn- ^{:no-doc true} kill-in-string-node [zloc pos] + (if (= (z/string zloc) "\"\"") + (z/remove zloc) + (let [bounds (-> zloc z/node meta) + row-idx (- (:row pos) (:row bounds)) + sub-length (if-not (= (:row pos) (:row bounds)) + (dec (:col pos)) + (- (:col pos) (inc (:col bounds))))] + + (-> (take (inc row-idx) (-> zloc z/node :lines)) + vec + (update-in [row-idx] #(.substring % 0 sub-length)) + (#(z/replace zloc (nd/string-node %))))))) + +(defn- ^{:no-doc true} kill-in-comment-node [zloc pos] + (let [col-bounds (-> zloc z/node meta :col)] + (if (= (:col pos) col-bounds) + (z/remove zloc) + (-> zloc + (z/replace (-> zloc + z/node + :s + (.substring 0 (- (:col pos) col-bounds 1)) + nd/comment-node)) + (#(if (zz/right %) + (zz/insert-right % (nd/newlines 1)) + %)))))) + + + +(defn kill-at-pos + "In string and comment aware kill + + Perform kill for given position `pos` Like [[kill]], but: + + - if inside string kills to end of string and stops there + - If inside comment kills to end of line (not including linebreak!) + + `pos` should provide `{:row :col }` which are relative to the start of the given form the zipper represents + `zloc` must be positioned at a node previous (given depth first) to the node at given pos" + [zloc pos] + (if-let [candidate (z/find-last-by-pos zloc pos)] + (cond + (string-node? candidate) (kill-in-string-node candidate pos) + (ws/comment? candidate) (kill-in-comment-node candidate pos) + (and (empty-seq? candidate) + (> (:col pos) (-> candidate z/node meta :col))) (z/remove candidate) + :else (kill candidate)) + zloc)) + + + +(defn- ^{:no-doc true} find-word-bounds + [v col] + (when (<= col (count v)) + [(->> (seq v) + (take col) + reverse + (take-while #(not (= % \space))) count (- col)) + (->> (seq v) + (drop col) + (take-while #(not (or (= % \space) (= % \newline)))) + count + (+ col))])) + + +(defn- ^{:no-doc true} remove-word-at + [v col] + (when-let [[start end] (find-word-bounds v col)] + (str (.substring v 0 start) + (.substring v end)))) + + + +(defn- ^{:no-doc true} kill-word-in-comment-node [zloc pos] + (let [col-bounds (-> zloc z/node meta :col)] + (-> zloc + (z/replace (-> zloc + z/node + :s + (remove-word-at (- (:col pos) col-bounds)) + nd/comment-node))))) + +(defn- ^{:no-doc true} kill-word-in-string-node [zloc pos] + (let [bounds (-> zloc z/node meta) + row-idx (- (:row pos) (:row bounds)) + col (if (= 0 row-idx) + (- (:col pos) (:col bounds)) + (:col pos))] + (-> zloc + (z/replace (-> zloc + z/node + :lines + (update-in [row-idx] + #(remove-word-at % col)) + nd/string-node))))) + + + +(defn kill-one-at-pos + "In string and comment aware kill for one node/word at given pos + + - `(+ |100 100) => (+ |100)` + - `(for |(bar do)) => (foo)` + - `\"|hello world\" => \"| world\"` + - ` ; |hello world => ; |world`" + [zloc pos] + (if-let [candidate (->> (z/find-last-by-pos zloc pos) + (ws/skip zz/right ws/whitespace?))] + (let [bounds (-> candidate z/node meta) + kill-in-node? (not (and (= (:row pos) (:row bounds)) + (<= (:col pos) (:col bounds))))] + (cond + (and kill-in-node? (string-node? candidate)) (kill-word-in-string-node candidate pos) + (and kill-in-node? (ws/comment? candidate)) (kill-word-in-comment-node candidate pos) + (not (z/leftmost? candidate)) (-> (z/remove candidate) + (global-find-by-node (-> candidate z/left z/node))) + :else (z/remove candidate))) + zloc)) + + +(defn- ^{:no-doc true} find-slurpee-up [zloc f] + (loop [l (z/up zloc) + n 1] + (cond + (nil? l) nil + (not (nil? (f l))) [n (f l)] + (nil? (z/up l)) nil + :else (recur (z/up l) (inc n))))) + +(defn- ^{:no-doc true} find-slurpee [zloc f] + (if (empty-seq? zloc) + [(f zloc) 0] + (some-> zloc (find-slurpee-up f) reverse))) + + + + +(defn slurp-forward + "Pull in next right outer node (if none at first level, tries next etc) into + current S-expression + + - `[1 2 [|3] 4 5] => [1 2 [|3 4] 5]`" + [zloc] + (let [[slurpee-loc n-ups] (find-slurpee zloc z/right)] + (if-not slurpee-loc + zloc + (let [slurper-loc (move-n zloc z/up n-ups) + preserves (->> (-> slurper-loc + zz/right + (nodes-by-dir zz/right #(not (= (z/node slurpee-loc) (z/node %))))) + (filter #(or (nd/linebreak? %) (nd/comment? %))))] + (-> slurper-loc + (u/remove-right-while ws/whitespace-or-comment?) + u/remove-right + ((partial reduce z/append-child) preserves) + (z/append-child (z/node slurpee-loc)) + (#(if (empty-seq? zloc) + (-> % z/down (u/remove-left-while ws/whitespace?)) + (global-find-by-node % (z/node zloc))))))))) + +(defn slurp-forward-fully + "Pull in all right outer-nodes into current S-expression, but only the ones at the same level + as the the first one. + + - `[1 2 [|3] 4 5] => [1 2 [|3 4 5]]`" + [zloc] + (let [curr-slurpee (some-> zloc (find-slurpee z/right) first) + num-slurps (some-> curr-slurpee (nodes-by-dir z/right) count inc)] + + (->> zloc + (iterate slurp-forward) + (take num-slurps) + last))) + + +(defn slurp-backward + "Pull in prev left outer node (if none at first level, tries next etc) into + current S-expression + + - `[1 2 [|3] 4 5] => [1 [2 |3] 4 5]`" + [zloc] + (if-let [[slurpee-loc _] (find-slurpee zloc z/left)] + (let [preserves (->> (-> slurpee-loc + zz/right + (nodes-by-dir zz/right ws/whitespace-or-comment?)) + (filter #(or (nd/linebreak? %) (nd/comment? %))))] + (-> slurpee-loc + (u/remove-left-while ws/whitespace-not-linebreak?) + (#(if (and (z/left slurpee-loc) + (not (ws/linebreak? (zz/left %)))) + (ws/prepend-space %) + %)) + (u/remove-right-while ws/whitespace-or-comment?) + zz/remove + z/next + ((partial reduce z/insert-child) preserves) + (z/insert-child (z/node slurpee-loc)) + (#(if (empty-seq? zloc) + (-> % z/down (u/remove-right-while ws/linebreak?)) + (global-find-by-node % (z/node zloc)))))) + zloc)) + +(defn slurp-backward-fully + "Pull in all left outer-nodes into current S-expression, but only the ones at the same level + as the the first one. + + - `[1 2 [|3] 4 5] => [[1 2 |3] 4 5]`" + [zloc] + (let [curr-slurpee (some-> zloc (find-slurpee z/left) first) + num-slurps (some-> curr-slurpee (nodes-by-dir z/left) count inc)] + + (->> zloc + (iterate slurp-backward) + (take num-slurps) + last))) + + +(defn barf-forward + "Push out the rightmost node of the current S-expression into outer right form + + - `[1 2 [|3 4] 5] => [1 2 [|3] 4 5]`" + [zloc] + (let [barfee-loc (z/rightmost zloc)] + + (if-not (z/up zloc) + zloc + (let [preserves (->> (-> barfee-loc + zz/left + (nodes-by-dir zz/left ws/whitespace-or-comment?)) + (filter #(or (nd/linebreak? %) (nd/comment? %))) + reverse)] + (-> barfee-loc + (u/remove-left-while ws/whitespace-or-comment?) + (u/remove-right-while ws/whitespace?) + u/remove-and-move-up + (z/insert-right (z/node barfee-loc)) + ((partial reduce z/insert-right) preserves) + (#(or (global-find-by-node % (z/node zloc)) + (global-find-by-node % (z/node barfee-loc))))))))) + + +(defn barf-backward + "Push out the leftmost node of the current S-expression into outer left form + + - `[1 2 [3 |4] 5] => [1 2 3 [|4] 5]`" + [zloc] + (let [barfee-loc (z/leftmost zloc)] + (if-not (z/up zloc) + zloc + (let [preserves (->> (-> barfee-loc + zz/right + (nodes-by-dir zz/right ws/whitespace-or-comment?)) + (filter #(or (nd/linebreak? %) (nd/comment? %))))] + (-> barfee-loc + (u/remove-left-while ws/whitespace?) + (u/remove-right-while ws/whitespace-or-comment?) ;; probably insert space when on same line ! + zz/remove + (z/insert-left (z/node barfee-loc)) + ((partial reduce z/insert-left) preserves) + (#(or (global-find-by-node % (z/node zloc)) + (global-find-by-node % (z/node barfee-loc))))))))) + + +(defn wrap-around + "Wrap current node with a given type `t` (:vector, :list, :set, :map :fn) + + - `|123 => [|123] ; given :vector` + - `|[1 [2]] => [|[1 [2]]]`" + [zloc t] + (-> zloc + (z/insert-left (create-seq-node t nil)) + z/left + (u/remove-right-while ws/whitespace?) + u/remove-right + (zz/append-child (z/node zloc)) + z/down)) + +(defn wrap-fully-forward-slurp + "Create a new seq node of type `t` left of `zloc` then slurp fully into the new node + + - `[1 |2 3 4] => [1 [|2 3 4]]`" + [zloc t] + (-> zloc + (z/insert-left (create-seq-node t nil)) + z/left + slurp-forward-fully)) + +(def splice + "See pez-rewrite-clj.zip/splice" + z/splice) + + +(defn- ^{:no-doc true} splice-killing + [zloc f] + (if-not (z/up zloc) + zloc + (-> zloc + (f (constantly true)) + z/up + splice + (global-find-by-node (z/node zloc))))) + +(defn splice-killing-backward + "Remove left siblings of current given node in S-Expression and unwrap remaining into enclosing S-expression + + - `(foo (let ((x 5)) |(sqrt n)) bar) => (foo (sqrt n) bar)`" + [zloc] + (splice-killing zloc u/remove-left-while)) + +(defn splice-killing-forward + "Remove current given node and its right siblings in S-Expression and unwrap remaining into enclosing S-expression + + - `(a (b c |d e) f) => (a b |c f)`" + [zloc] + (if (and (z/up zloc) (not (z/leftmost? zloc))) + (splice-killing (z/left zloc) u/remove-right-while) + (if (z/up zloc) + (-> zloc z/up z/remove) + zloc))) + + +(defn split + "Split current s-sexpression in two at given node `zloc` + + - `[1 2 |3 4 5] => [1 2 3] [4 5]`" + [zloc] + (let [parent-loc (z/up zloc)] + (if-not parent-loc + zloc + (let [t (z/tag parent-loc) + lefts (reverse (remove-first-if-ws (rest (nodes-by-dir (z/right zloc) zz/left)))) + rights (remove-first-if-ws (nodes-by-dir (z/right zloc) zz/right))] + + (if-not (and (seq lefts) (seq rights)) + zloc + (-> parent-loc + (z/insert-left (create-seq-node t lefts)) + (z/insert-left (create-seq-node t rights)) + z/remove + (#(or (global-find-by-node % (z/node zloc)) + (global-find-by-node % (last lefts)))))))))) + + +(defn- ^{:no-doc true} split-string [zloc pos] + (let [bounds (-> zloc z/node meta) + row-idx (- (:row pos) (:row bounds)) + lines (-> zloc z/node :lines) + split-col (if-not (= (:row pos) (:row bounds)) + (dec (:col pos)) + (- (:col pos) (inc (:col bounds))))] + (-> zloc + (z/replace (nd/string-node + (-> (take (inc row-idx) lines) + vec + (update-in [row-idx] #(.substring % 0 split-col))))) + (z/insert-right (nd/string-node + (-> (drop row-idx lines) + vec + (update-in [0] #(.substring % split-col)))))))) + + +(defn split-at-pos + "In string aware split + + Perform split at given position `pos` Like split, but: + + - if inside string splits string into two strings + + `pos` should provide `{:row :col }` which are relative to the start of the given form the zipper represents + `zloc` must be positioned at a node previous (given depth first) to the node at given pos" + [zloc pos] + (if-let [candidate (z/find-last-by-pos zloc pos)] + (if (string-node? candidate) + (split-string candidate pos) + (split candidate)) + zloc)) + +(defn- ^{:no-doc true} join-seqs [left right] + (let [lefts (-> left z/node nd/children) + ws-nodes (-> (zz/right left) (nodes-by-dir zz/right ws/whitespace-or-comment?)) + rights (-> right z/node nd/children)] + + (-> right + zz/remove + remove-ws-or-comment + z/up + (z/insert-left (create-seq-node :vector + (concat lefts + ws-nodes + rights))) + z/remove + (global-find-by-node (first rights))))) + + +(defn- ^{:no-doc true} join-strings [left right] + (-> right + zz/remove + remove-ws-or-comment + (z/replace (nd/string-node (str (-> left z/node nd/sexpr) + (-> right z/node nd/sexpr)))))) + +(defn join + "Join S-expression to the left and right of current loc. Also works for strings. + + - `[[1 2] |[3 4]] => [[1 2 3 4]]` + - `[\"Hello \" | \"World\"] => [\"Hello World\"]" + [zloc] + (let [left (some-> zloc z/left) + right (if (some-> zloc z/node nd/whitespace?) (z/right zloc) zloc)] + + + (if-not (and left right) + zloc + (cond + (and (z/seq? left) (z/seq? right)) (join-seqs left right) + (and (string-node? left) (string-node? right)) (join-strings left right) + :else zloc)))) + + +(defn raise + "Delete siblings and raise node at zloc one level up + + - `[1 [2 |3 4]] => [1 |3]`" + [zloc] + (if-let [containing (z/up zloc)] + (-> containing + (z/replace (z/node zloc))) + zloc)) + + +(defn move-to-prev + "Move node at current location to the position of previous location given a depth first traversal + + - `(+ 1 (+ 2 |3) 4) => (+ 1 (+ |3 2) 4)` + - `(+ 1 (+ 2 3) |4) => (+ 1 (+ 2 3 |4))` + + returns zloc after move or given zloc if a move isn't possible" + [zloc] + (let [n (z/node zloc) + p (some-> zloc z/left z/node) + ins-fn (if (or (nil? p) (= (-> zloc z/remove z/node) p)) + #(-> % (z/insert-left n) z/left) + #(-> % (z/insert-right n) z/right))] + (if-not (-> zloc z/remove z/prev) + zloc + (-> zloc + z/remove + ins-fn)))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/parser.cljs b/src/cljs-lib/src/pez_rewrite_clj/parser.cljs new file mode 100644 index 0000000..1767c65 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/parser.cljs @@ -0,0 +1,35 @@ +(ns pez-rewrite-clj.parser + (:require [pez-rewrite-clj.parser.core :as p] + [pez-rewrite-clj.node :as node] + [pez-rewrite-clj.reader :as r])) + +;; ## Parser Core + +(defn parse + "Parse next form from the given reader." + [^not-native reader] + (p/parse-next reader)) + +(defn parse-all + "Parse all forms from the given reader." + [^not-native reader] + (let [nodes (->> (repeatedly #(parse reader)) + (take-while identity) + (doall))] + (with-meta + (node/forms-node nodes) + (meta (first nodes))))) + +;; ## Specialized Parsers + +(defn parse-string + "Parse first form in the given string." + [s] + (parse (r/indexing-push-back-reader s))) + +(defn parse-string-all + "Parse all forms in the given string." + [s] + (parse-all (r/indexing-push-back-reader s))) + + diff --git a/src/cljs-lib/src/pez_rewrite_clj/parser/core.cljs b/src/cljs-lib/src/pez_rewrite_clj/parser/core.cljs new file mode 100644 index 0000000..67db058 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/parser/core.cljs @@ -0,0 +1,172 @@ +(ns pez-rewrite-clj.parser.core + (:require [pez-rewrite-clj.node :as node] + [pez-rewrite-clj.reader :as reader] + [pez-rewrite-clj.parser.keyword :refer [parse-keyword]] + [pez-rewrite-clj.parser.string :refer [parse-string parse-regex]] + [pez-rewrite-clj.parser.token :refer [parse-token]] + [pez-rewrite-clj.parser.whitespace :refer [parse-whitespace]] + [cljs.tools.reader.reader-types :refer [peek-char]])) + +;; ## Base Parser + +(def ^:dynamic ^:private *delimiter* + nil) + + +(declare parse-next) + + +(defn- parse-delim + [^not-native reader delimiter] + (reader/ignore reader) + (->> #(binding [*delimiter* delimiter] + (parse-next %)) + (reader/read-repeatedly reader))) + +(defn- parse-printables + [^not-native reader node-tag n & [ignore?]] + (when ignore? + (reader/ignore reader)) + (reader/read-n + reader + node-tag + parse-next + (complement node/printable-only?) + n)) + + +(defn- parse-meta + [^not-native reader] + (reader/ignore reader) + (node/meta-node (parse-printables reader :meta 2))) + + +(defn- parse-eof + [^not-native reader] + (when *delimiter* + (reader/throw-reader reader "Unexpected EOF."))) + +;; ### Seqs + +(defn- parse-list + [^not-native reader] + (node/list-node (parse-delim reader \)))) + +(defn- parse-vector + [^not-native reader] + (node/vector-node (parse-delim reader \]))) + +(defn- parse-map + [^not-native reader] + (node/map-node (parse-delim reader \}))) + + +;; ### Reader Specialities + + +(defn- parse-conditional [reader] + ;; we need to examine the next character, so consume one (known \?) + (reader/next reader) + ;; we will always have a reader-macro-node as the result + (node/reader-macro-node + (let [read1 (fn [] (parse-printables reader :reader-macro 1))] + (cons (case (reader/peek reader) + ;; the easy case, just emit a token + \( (node/token-node (symbol "?")) + + ;; the harder case, match \@, consume it and emit the token + \@ (do (reader/next reader) + (node/token-node (symbol "?@"))) + + ;; otherwise no idea what we're reading but its \? prefixed + (do (reader/unread reader \?) + (first (read1)))) + (read1))))) + + + +(defn- parse-sharp + [^not-native reader] + (reader/ignore reader) + (if (reader/whitespace? (peek-char reader)) + (node/comment-node (reader/read-include-linebreak reader)) + (case (peek-char reader) + nil (reader/throw-reader reader "Unexpected EOF.") + \{ (node/set-node (parse-delim reader \})) + \( (node/fn-node (parse-delim reader \))) + \" (parse-regex reader) + \^ (node/meta-node (parse-printables reader :meta 2 true)) + \' (node/var-node (parse-printables reader :var 1 true)) + \= (node/eval-node (parse-printables reader :eval 1 true)) + \_ (node/uneval-node (parse-printables reader :uneval 1 true)) + \? (parse-conditional reader) + (node/reader-macro-node (parse-printables reader :reader-macro 2))))) + + + + +(defn- parse-unmatched + [^not-native reader] + (reader/throw-reader + reader + "Unmatched delimiter: %s" + (peek-char reader))) + + +(defn- parse-deref + [^not-native reader] + (node/deref-node (parse-printables reader :deref 1 true))) + +;; ## Quotes + +(defn- parse-quote + [^not-native reader] + (node/quote-node (parse-printables reader :quote 1 true))) + +(defn- parse-syntax-quote + [^not-native reader] + (node/syntax-quote-node (parse-printables reader :syntax-quote 1 true))) + +(defn- parse-unquote + [^not-native reader] + (reader/ignore reader) + (let [c (peek-char reader)] + (if (= c \@) + (node/unquote-splicing-node + (parse-printables reader :unquote 1 true)) + (node/unquote-node + (parse-printables reader :unquote 1))))) + +(defn- parse-comment + [^not-native reader] + (reader/ignore reader) + (node/comment-node (reader/read-include-linebreak reader))) + + + +(defn- dispatch + [c] + (cond (nil? c) parse-eof + (identical? c *delimiter*) reader/ignore + (reader/whitespace? c) parse-whitespace + (identical? c \^) parse-meta + (identical? c \#) parse-sharp + (identical? c \() parse-list + (identical? c \[) parse-vector + (identical? c \{) parse-map + (identical? c \}) parse-unmatched + (identical? c \]) parse-unmatched + (identical? c \)) parse-unmatched + (identical? c \~) parse-unquote + (identical? c \') parse-quote + (identical? c \`) parse-syntax-quote + ;; (identical? c \;) parse-comment + (identical? c \@) parse-deref + (identical? c \") parse-string + (identical? c \:) parse-keyword + :else parse-token)) + + +(defn parse-next + [^not-native rdr] + (reader/read-with-meta rdr (dispatch (peek-char rdr)))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/parser/keyword.cljs b/src/cljs-lib/src/pez_rewrite_clj/parser/keyword.cljs new file mode 100644 index 0000000..76c56ac --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/parser/keyword.cljs @@ -0,0 +1,17 @@ +(ns pez-rewrite-clj.parser.keyword + (:require [pez-rewrite-clj.node :as node] + [cljs.tools.reader.reader-types] + [pez-rewrite-clj.reader :as r])) + +(defn parse-keyword + [^not-native reader] + (r/read-char reader) + (if-let [c (r/peek-char reader)] + (if (identical? c \:) + (node/keyword-node + (r/read-keyword reader ":") + true) + (do + (r/unread reader \:) + (node/keyword-node (r/read-keyword reader ":")))) + (r/throw-reader reader "unexpected EOF while reading keyword."))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/parser/string.cljs b/src/cljs-lib/src/pez_rewrite_clj/parser/string.cljs new file mode 100644 index 0000000..417fcd2 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/parser/string.cljs @@ -0,0 +1,41 @@ +(ns pez-rewrite-clj.parser.string + (:require [pez-rewrite-clj.node :as node] + [pez-rewrite-clj.reader :as r] + [goog.string :as gstring] + [clojure.string :as string])) + +(defn- flush-into + "Flush buffer and add string to the given vector." + [lines buf] + (let [s (.toString buf)] + (.set buf "") + (conj lines s))) + +(defn- read-string-data + [^not-native reader] + (r/ignore reader) + (let [buf (gstring/StringBuffer.)] + (loop [escape? false + lines []] + (if-let [c (r/read-char reader)] + (cond (and (not escape?) (identical? c \")) + (flush-into lines buf) + + (identical? c \newline) + (recur escape? (flush-into lines buf)) + + :else + (do + (.append buf c) + (recur (and (not escape?) (identical? c \\)) lines))) + (r/throw-reader reader "Unexpected EOF while reading string."))))) + +(defn parse-string + [^not-native reader] + (node/string-node (read-string-data reader))) + +(defn parse-regex + [^not-native reader] + (let [lines (read-string-data reader) + regex (string/join "\n" lines)] + (node/token-node (re-pattern regex) (str "#\"" regex "\"")))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/parser/token.cljs b/src/cljs-lib/src/pez_rewrite_clj/parser/token.cljs new file mode 100644 index 0000000..1ea35c5 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/parser/token.cljs @@ -0,0 +1,65 @@ +(ns pez-rewrite-clj.parser.token + (:require [pez-rewrite-clj.node :as node] + [pez-rewrite-clj.reader :as r] + [goog.string :as gstring])) + + +(defn- join-2 [a b] + (-> a gstring/StringBuffer. (.append b) .toString)) + +(defn- ^boolean allowed-default? [c] + false) + +(defn- ^boolean allowed-suffix? [c] + (or (identical? c \') + (identical? c \:))) + + + +(defn- read-to-boundary + [^not-native reader allowed?] + (r/read-until + reader + #(and (not (allowed? %)) + (r/whitespace-or-boundary? %)))) + + + + +(defn- read-to-char-boundary + [^not-native reader] + (let [c (r/read-char reader)] + (join-2 c (if (not (identical? c \\)) + (read-to-boundary reader allowed-default?) + "")))) + + + +(defn- symbol-node + "Symbols allow for certain boundary characters that have + to be handled explicitly." + [^not-native reader value value-string] + (let [suffix (read-to-boundary + reader + allowed-suffix?)] + (if (empty? suffix) + (node/token-node value value-string) + (let [s (join-2 value-string suffix)] + (node/token-node + (r/read-string s) + s))))) + + + + +(defn parse-token + "Parse a single token." + [^not-native reader] + (let [first-char (r/read-char reader) + s (join-2 first-char (if (identical? first-char \\) + (read-to-char-boundary reader) + (read-to-boundary reader allowed-default?))) + v (r/read-string s)] + (if (symbol? v) + (symbol-node reader v s) + (node/token-node v s)))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/parser/whitespace.cljs b/src/cljs-lib/src/pez_rewrite_clj/parser/whitespace.cljs new file mode 100644 index 0000000..fb4abe2 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/parser/whitespace.cljs @@ -0,0 +1,13 @@ +(ns pez-rewrite-clj.parser.whitespace + (:require [pez-rewrite-clj.node :as node] + [pez-rewrite-clj.reader :as r])) + +(defn parse-whitespace + "Parse as much whitespace as possible. The created node can either contain + only linebreaks or only space/tabs." + [^not-native reader] + (if (r/linebreak? (r/peek-char reader)) + (node/newline-node + (r/read-while reader r/linebreak?)) + (node/whitespace-node + (r/read-while reader r/space?)))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/reader.cljs b/src/cljs-lib/src/pez_rewrite_clj/reader.cljs new file mode 100644 index 0000000..cb4dde4 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/reader.cljs @@ -0,0 +1,201 @@ +(ns pez-rewrite-clj.reader + (:refer-clojure :exclude [peek next]) + (:require [cljs.tools.reader :as r] + [cljs.tools.reader.reader-types :as reader-types] + [cljs.tools.reader.impl.commons :refer [parse-symbol]] + [goog.string :as gstring] + [pez-rewrite-clj.node.protocols :as nd])) + +(def read-char reader-types/read-char) +(def get-column-number reader-types/get-column-number) +(def get-line-number reader-types/get-line-number) +(def peek-char reader-types/peek-char) +(def indexing-push-back-reader reader-types/indexing-push-back-reader) +(def unread reader-types/unread) +(def read-string r/read-string) + +;; TODO: try to get goog.string.format up and running ! +(defn throw-reader + "Throw reader exception, including line/column." + [^not-native reader fmt & data] + (let [c (get-column-number reader) + l (get-line-number reader)] + (throw + (js/Error. + (str data fmt + " [at line " l ", column " c "]"))))) + + +(defn boundary? + "Check whether a given char is a token boundary." + [c] + (< -1 (.indexOf #js [\" \: \; \' \@ \^ \` \~ + \( \) \[ \] \{ \} \\ nil] c))) + +(defn ^boolean whitespace? + "Checks whether a given character is whitespace" + [ch] + ;(or (gstring/isBreakingWhitespace ch) (identical? \, ch)) + (< -1 (.indexOf #js [\return \newline \tab \space ","] ch))) + +(defn ^boolean linebreak? + "Checks whether the character is a newline" + [c] + (< -1 (.indexOf #js [\return \newline] c))) + +(defn ^boolean space? + "Checks whether the character is a space" + [c] + (< -1 (.indexOf #js [\tab \space ","] c))) + +(defn ^boolean whitespace-or-boundary? + [c] + (or (whitespace? c) (boundary? c))) + +(def buf (gstring/StringBuffer. "")) + +(defn read-while + "Read while the chars fulfill the given condition. Ignores + the unmatching char." + ([^not-native reader p?] + (read-while reader p? (not (p? nil)))) + + ([^not-native reader p? eof?] + (.clear buf) + (loop [] + (if-let [c (read-char reader)] + (if (p? c) + (do + (.append buf c) + (recur)) + (do + (unread reader c) + (.toString buf))) + (if eof? + (.toString buf) + (throw-reader reader "Unexpected EOF.")))))) + +(defn read-until + "Read until a char fulfills the given condition. Ignores the + matching char." + [^not-native reader p?] + (read-while + reader + (complement p?) + (p? nil))) + +(defn read-include-linebreak + "Read until linebreak and include it." + [^not-native reader] + (str + (read-until + reader + #(or (nil? %) (linebreak? %))) + (read-char reader))) + +(defn string->edn + "Convert string to EDN value." + [s] + (read-string s)) + +(defn ignore + "Ignore the next character." + [^not-native reader] + (read-char reader) + nil) + + +(defn next + "Read next char." + [^not-native reader] + (read-char reader)) + +(defn peek + "Peek next char." + [^not-native reader] + (peek-char reader)) + + + +(defn read-with-meta + "Use the given function to read value, then attach row/col metadata." + [^not-native reader read-fn] + (let [row (get-line-number reader) + col (get-column-number reader) + ^not-native entry (read-fn reader)] + (when entry + (let [end-row (get-line-number reader) + end-col (get-column-number reader) + end-col (if (= 0 end-col) + (+ col (.-length (nd/string entry))) + end-col)] ; TODO: Figure out why numbers are sometimes whacky + (if (= 0 col) ; why oh why + entry + (-with-meta + entry + {:row row + :col col + :end-row end-row + :end-col end-col})))))) + +(defn read-repeatedly + "Call the given function on the given reader until it returns + a non-truthy value." + [^not-native reader read-fn] + (->> (repeatedly #(read-fn reader)) + (take-while identity) + (doall))) + + +(defn read-n + "Call the given function on the given reader until `n` values matching `p?` have been + collected." + [^not-native reader node-tag read-fn p? n] + {:pre [(pos? n)]} + (loop [c 0 + vs []] + (if (< c n) + (if-let [v (read-fn reader)] + (recur + (if (p? v) (inc c) c) + (conj vs v)) + (throw-reader + reader + "%s node expects %d value%s." + node-tag + n + (if (= n 1) "" "s"))) + vs))) + +(defn- re-matches* + [re s] + (let [matches (.exec re s)] + (when (and (not (nil? matches)) + (identical? (aget matches 0) s)) + (if (== (alength matches) 1) + (aget matches 0) + matches)))) + +(defn read-keyword + [^not-native reader initch] + (let [tok (#'cljs.tools.reader/read-token reader :keyword (read-char reader)) + a (re-matches* (re-pattern "^[:]?([^0-9/].*/)?([^0-9/][^/]*)$") tok) + token (aget a 0) + ns (aget a 1) + name (aget a 2)] + (if (or (and (not (undefined? ns)) + (identical? (. ns (substring (- (.-length ns) 2) (.-length ns))) ":/")) + (identical? (aget name (dec (.-length name))) ":") + (not (== (.indexOf token "::" 1) -1))) + (cljs.tools.reader.impl.errors/reader-error reader + "Invalid token: " + token) + (if (and (not (nil? ns)) (> (.-length ns) 0)) + (keyword (.substring ns 0 (.indexOf ns "/")) name) + (keyword (.substring token 1)))))) + +;; (let [form-rdr (r/indexing-push-back-reader "(+ 1 1)")] +;; (read-include-linebreak form-rdr)) + + +;(re-matches* (re-pattern "^[:]?([^0-9/].*/)?([^0-9/][^/]*)$") ":%dill.*") diff --git a/src/cljs-lib/src/pez_rewrite_clj/zip.cljs b/src/cljs-lib/src/pez_rewrite_clj/zip.cljs new file mode 100644 index 0000000..d4aabc0 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/zip.cljs @@ -0,0 +1,205 @@ +(ns pez-rewrite-clj.zip + "Client facing facade for zipper functions" + (:refer-clojure :exclude [next find replace remove + seq? map? vector? list? set? + print map get assoc]) + (:require [pez-rewrite-clj.zip.base :as base] + [pez-rewrite-clj.parser :as p] + [pez-rewrite-clj.zip.move :as m] + [pez-rewrite-clj.zip.findz :as f] + [pez-rewrite-clj.zip.editz :as ed] + [pez-rewrite-clj.zip.insert :as ins] + [pez-rewrite-clj.zip.removez :as rm] + [pez-rewrite-clj.zip.seqz :as sz] + [clojure.zip :as z])) + + + +(def node + "Function reference to clojure.zip/node" + z/node) +(def root + "Function reference to clojure.zip/root" + z/root) + + +(def of-string + "See [[base/of-string]]" + base/of-string) +(def root-string + "See [[base/root-string]]" + base/root-string) +(def string + "See [[base/string]]" + base/string) +(def tag + "See [[base/tag]]" + base/tag) +(def sexpr + "See [[base/sexpr]]" + base/sexpr) + + + + +;; ********************************** +;; Originally in pez-rewrite-clj.zip.move +;; ********************************** +(def right + "See [[move/right]]" + m/right) +(def left + "See [[move/left]]" + m/left) +(def down + "See [[move/down]]" + m/down) +(def up + "See [[move/up]]" + m/up) +(def next + "See [[move/next]]" + m/next) +(def end? + "See [[move/end?]]" + m/end?) +(def rightmost? + "See [[move/rightmost?]]" + m/rightmost?) +(def leftmost? + "See [[move/leftmost?]]" + m/leftmost?) +(def prev + "See [[move/prev]]" + m/prev) +(def leftmost + "See [[move/leftmost]]" + m/leftmost) +(def rightmost + "See [[move/rightmost]]" + m/rightmost) + + + +;; ********************************** +;; Originally in pez-rewrite-clj.zip.findz +;; ********************************** +(def find + "See [[findz/find]]" + f/find) +(def find-last-by-pos + "See [[findz/find-last-by-pos]]" + f/find-last-by-pos) +(def find-depth-first + "See [[findz/find-depth-first]]" + f/find-depth-first) +(def find-next + "See [[findz/find-next]]" + f/find-next) +(def find-next-depth-first + "See [[findz/find-next-depth-first]]" + f/find-next-depth-first) +(def find-tag + "See [[findz/find-tag]]" + f/find-tag) +(def find-next-tag + "See [[findz/find-next-tag]]" + f/find-next-tag) +(def find-tag-by-pos + "See [[findz/tag-by-pos]]" + f/find-tag-by-pos) +(def find-token + "See [[findz/find-token]]" + f/find-token) +(def find-next-token + "See [[findz/find-next-token]]" + f/find-next-token) +(def find-value + "See [[findz/find-value]]" + f/find-value) +(def find-next-value + "See [[findz/find-next-value]]" + f/find-next-value) + + + +;; ********************************** +;; Originally in pez-rewrite-clj.zip.editz +;; ********************************** +(def replace + "See [[editz/replace]]" + ed/replace) +(def edit + "See [[editz/edit]]" + ed/edit) +(def splice + "See [[editz/splice]]" + ed/splice) +(def prefix + "See [[editz/prefix]]" + ed/prefix) +(def suffix + "See [[editz/suffix]]" + ed/suffix) + +;; ********************************** +;; Originally in pez-rewrite-clj.zip.removez +;; ********************************** +(def remove + "See [[removez/remove]]" + rm/remove) +(def remove-preserve-newline + "See [[removez/remove-preserve-newline]]" + rm/remove-preserve-newline) + + +;; ********************************** +;; Originally in pez-rewrite-clj.zip.insert +;; ********************************** +(def insert-right + "See [[insert/insert-right]]" + ins/insert-right) +(def insert-left + "See [[insert/insert-left]]" + ins/insert-left) +(def insert-child + "See [[insert/insert-child]]" + ins/insert-child) +(def append-child + "See [[insert/append-child]]" + ins/append-child) + + +;; ********************************** +;; Originally in pez-rewrite-clj.zip.seqz +;; ********************************** +(def seq? + "See [[seqz/seq?]]" + sz/seq?) +(def list? + "See [[seqz/list?]]" + sz/list?) +(def vector? + "See [[seqz/vector?]]" + sz/vector?) +(def set? + "See [[seqz/set?]]" + sz/set?) +(def map? + "See [[seqz/map?]]" + sz/map?) +(def map-vals + "See [[seqz/map-vals]]" + sz/map-vals) +(def map-keys + "See [[seqz/map-keys]]" + sz/map-keys) +(def map + "See [[seqz/map]]" + sz/map) +(def get + "See [[seqz/get]]" + sz/get) +(def assoc + "See [[seqz/assoc]]" + sz/assoc) diff --git a/src/cljs-lib/src/pez_rewrite_clj/zip/base.cljs b/src/cljs-lib/src/pez_rewrite_clj/zip/base.cljs new file mode 100644 index 0000000..a2208f5 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/zip/base.cljs @@ -0,0 +1,90 @@ +(ns pez-rewrite-clj.zip.base + (:refer-clojure :exclude [print]) + (:require [pez-rewrite-clj.node :as node] + [pez-rewrite-clj.parser :as p] + [pez-rewrite-clj.zip.whitespace :as ws] + [clojure.zip :as z])) + +;; ## Zipper + +(defn edn* + "Create zipper over the given Clojure/EDN node." + [node] + (z/zipper + node/inner? + (comp seq node/children) + node/replace-children + node)) + +(defn edn + "Create zipper over the given Clojure/EDN node and move + to the first non-whitespace/non-comment child." + [node] + (if (= (node/tag node) :forms) + (let [top (edn* node)] + (or (-> top z/down ws/skip-whitespace) + top)) + (recur (node/forms-node [node])))) + +;; ## Inspection + +(defn tag + "Get tag of node at the current zipper location." + [zloc] + (some-> zloc z/node node/tag)) + +(defn sexpr + "Get sexpr represented by the given node." + [zloc] + (some-> zloc z/node node/sexpr)) + +(defn child-sexprs + "Get children as s-expressions." + [zloc] + (some-> zloc z/node node/child-sexprs)) + +(defn length + "Get length of printable string for the given zipper location." + [zloc] + (or (some-> zloc z/node node/length) 0)) + + +;; ## Read + +(defn of-string + "Create zipper from String." + [s] + (some-> s p/parse-string-all edn)) + + +;; ## Write + +(defn string + "Create string representing the current zipper location." + [zloc] + (some-> zloc z/node node/string)) + +(defn root-string + "Create string representing the zipped-up zipper." + [zloc] + (some-> zloc z/root node/string)) + +;; (defn- print! +;; [s writer] +;; (if writer +;; (.write ^java.io.Writer writer s) +;; (recur s *out*))) + +;; (defn print +;; "Print current zipper location." +;; [zloc & [writer]] +;; (some-> zloc +;; string +;; (print! writer))) + +;; (defn print-root +;; "Zip up and print root node." +;; [zloc & [writer]] +;; (some-> zloc +;; root-string +;; (print! writer))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/zip/editz.cljs b/src/cljs-lib/src/pez_rewrite_clj/zip/editz.cljs new file mode 100644 index 0000000..08775dc --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/zip/editz.cljs @@ -0,0 +1,95 @@ +(ns pez-rewrite-clj.zip.editz + (:refer-clojure :exclude [replace]) + (:require [pez-rewrite-clj.zip.base :as base] + [pez-rewrite-clj.zip.move :as m] + [pez-rewrite-clj.zip.removez :as r] + [pez-rewrite-clj.zip.utils :as u] + [pez-rewrite-clj.zip.whitespace :as ws] + [pez-rewrite-clj.node :as n] + [clojure.zip :as z])) + +;; ## In-Place Modification + +(defn replace + "Replace the node at the given location with one representing + the given value. (The value will be coerced to a node if + possible.)" + [zloc value] + (z/replace zloc (n/coerce value))) + +(defn- edit-node + "Create s-expression from node, apply the function and create + node from the result." + [node f] + (-> (n/sexpr node) + (f) + (n/coerce))) + +(defn edit + "Apply the given function to the s-expression at the given + location, using its result to replace the node there. (The + result will be coerced to a node if possible.)" + [zloc f & args] + (z/edit zloc edit-node #(apply f % args))) + +;; ## Splice + + + +(defn splice + "Splice the given node, i.e. merge its children into the current one + (akin to Clojure's `unquote-splicing` macro: `~@...`). + - if the node is not one that can have children, no modification will + be performed. + - if the node has no or only whitespace children, it will be removed. + - otherwise, splicing will be performed, moving the zipper to the first + non-whitespace child afterwards. + " + [zloc] + (if (z/branch? zloc) + (if-let [children (->> (z/children zloc) + (drop-while n/whitespace?) + (reverse) + (drop-while n/whitespace?) + (seq))] + (let [loc (->> (reduce z/insert-right zloc children) + (u/remove-and-move-right))] + (or (ws/skip-whitespace loc) loc)) + (r/remove zloc)) + zloc)) + +;; ## Prefix/Suffix + +(defn- edit-token + [zloc str-fn] + (let [e (base/sexpr zloc) + e' (cond (string? e) (str-fn e) + (keyword? e) (keyword (namespace e) (str-fn (name e))) + (symbol? e) (symbol (namespace e) (str-fn (name e))))] + (z/replace zloc (n/token-node e')))) + +(defn- edit-multi-line + [zloc line-fn] + (let [n (-> (z/node zloc) + (update-in [:lines] (comp line-fn vec)))] + (z/replace zloc n))) + +(defn prefix + [zloc s] + (case (base/tag zloc) + :token (edit-token zloc #(str s %)) + :multi-line (->> (fn [lines] + (if (empty? lines) + [s] + (update-in lines [0] #(str s %)))) + (edit-multi-line zloc )))) + +(defn suffix + [zloc s] + (case (base/tag zloc) + :token (edit-token zloc #(str % s)) + :multi-line (->> (fn [lines] + (if (empty? lines) + [s] + (concat (butlast lines) (str (last lines) s)))) + (edit-multi-line zloc)))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/zip/findz.cljs b/src/cljs-lib/src/pez_rewrite_clj/zip/findz.cljs new file mode 100644 index 0000000..913865a --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/zip/findz.cljs @@ -0,0 +1,147 @@ +(ns pez-rewrite-clj.zip.findz + (:refer-clojure :exclude [find]) + (:require [pez-rewrite-clj.zip.base :as base] + [pez-rewrite-clj.zip.move :as m] + [pez-rewrite-clj.node :as node] + [pez-rewrite-clj.zip.whitespace :as ws] + [clojure.zip :as z])) + +;; ## Helpers + +(defn- tag-predicate + [t & [additional]] + (if additional + (fn [node] + (and (= (base/tag node) t) + (additional node))) + #(= (base/tag %) t))) + + +(defn in-range? [{:keys [row col end-row end-col]} {r :row c :col}] + (and (>= r row) + (<= r end-row) + (if (= r row) (>= c col) true) + (if (= r end-row) (<= c end-col) true))) + + +;; ## Find Operations + +(defn find + "Find node satisfying the given predicate by repeatedly + applying the given movement function to the initial zipper + location." + ([zloc p?] + (find zloc m/right p?)) + ([zloc f p?] + (->> zloc + (iterate f) + (take-while identity) + (take-while (complement m/end?)) + (drop-while (complement p?)) + (first)))) + + + +(defn find-last-by-pos + "Find last node (if more than one node) that is in range of pos and + satisfying the given predicate depth first from initial zipper + location." + ([zloc pos] (find-last-by-pos zloc pos (constantly true))) + ([zloc pos p?] + (->> zloc + (iterate z/next) + (take-while identity) + (take-while (complement m/end?)) + (filter #(and (p? %) + (in-range? (-> % z/node meta) pos))) + last))) + + +(defn find-depth-first + "Find node satisfying the given predicate by traversing + the zipper in a depth-first way." + [zloc p?] + (find zloc m/next p?)) + + +(defn find-next + "Find node other than the current zipper location matching + the given predicate by applying the given movement function + to the initial zipper location." + ([zloc p?] + (find-next zloc m/right p?)) + ([zloc f p?] + (some-> zloc f (find f p?)))) + +(defn find-next-depth-first + "Find node other than the current zipper location matching + the given predicate by traversing the zipper in a + depth-first way." + [zloc p?] + (find-next zloc m/next p?)) + +(defn find-tag + "Find node with the given tag by repeatedly applying the given + movement function to the initial zipper location." + ([zloc t] + (find-tag zloc m/right t)) + ([zloc f t] + (find zloc f #(= (base/tag %) t)))) + +(defn find-next-tag + "Find node other than the current zipper location with the + given tag by repeatedly applying the given movement function to + the initial zipper location." + ([zloc t] + (find-next-tag zloc m/right t)) + ([zloc f t] + (->> (tag-predicate t) + (find-next zloc f)))) + + +(defn find-tag-by-pos + "Find node with the given tag and pos depth-first from initial zipper location." + ([zloc pos t] + (find-last-by-pos zloc pos #(= (base/tag %) t)))) + + + +(defn find-token + "Find token node matching the given predicate by applying the + given movement function to the initial zipper location, defaulting + to `right`." + ([zloc p?] + (find-token zloc m/right p?)) + ([zloc f p?] + (->> (tag-predicate :token p?) + (find zloc f)))) + +(defn find-next-token + "Find next token node matching the given predicate by applying the + given movement function to the initial zipper location, defaulting + to `right`." + ([zloc p?] + (find-next-token zloc m/right p?)) + ([zloc f p?] + (find-token (f zloc) f p?))) + +(defn find-value + "Find token node whose value matches the given one by applying the + given movement function to the initial zipper location, defaulting + to `right`." + ([zloc v] + (find-value zloc m/right v)) + ([zloc f v] + (let [p? (if (set? v) + (comp v base/sexpr) + #(= (base/sexpr %) v))] + (find-token zloc f p?)))) + +(defn find-next-value + "Find next token node whose value matches the given one by applying the + given movement function to the initial zipper location, defaulting + to `right`." + ([zloc v] + (find-next-value zloc m/right v)) + ([zloc f v] + (find-value (f zloc) f v))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/zip/insert.cljs b/src/cljs-lib/src/pez_rewrite_clj/zip/insert.cljs new file mode 100644 index 0000000..cea1678 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/zip/insert.cljs @@ -0,0 +1,55 @@ +(ns ^:no-doc pez-rewrite-clj.zip.insert + (:require [pez-rewrite-clj.zip.base :as base] + [pez-rewrite-clj.zip.whitespace :as ws] + [pez-rewrite-clj.node :as node] + [clojure.zip :as z])) + +(def ^:private space + (node/spaces 1)) + +(defn- insert + "Generic insertion helper. If the node reached by `move-fn` + is a whitespace, insert an additional space." + [move-fn insert-fn prefix zloc item] + (let [item-node (node/coerce item) + next-node (move-fn zloc)] + (->> (if (or (not next-node) (ws/whitespace? next-node)) + (concat [item-node] prefix) + (concat [space item-node] prefix)) + (reduce insert-fn zloc)))) + +(defn insert-right + "Insert item to the right of the current location. Will insert a space if necessary." + [zloc item] + (insert + z/right + z/insert-right + [space] + zloc item)) + +(defn insert-left + "Insert item to the right of the left location. Will insert a space if necessary." + [zloc item] + (insert + z/left + z/insert-left + [space] + zloc item)) + +(defn insert-child + "Insert item as first child of the current node. Will insert a space if necessary." + [zloc item] + (insert + z/down + z/insert-child + [] + zloc item)) + +(defn append-child + "Insert item as last child of the current node. Will insert a space if necessary." + [zloc item] + (insert + #(some-> % z/down z/rightmost) + z/append-child + [] + zloc item)) diff --git a/src/cljs-lib/src/pez_rewrite_clj/zip/move.cljs b/src/cljs-lib/src/pez_rewrite_clj/zip/move.cljs new file mode 100644 index 0000000..67d285e --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/zip/move.cljs @@ -0,0 +1,73 @@ +(ns pez-rewrite-clj.zip.move + (:refer-clojure :exclude [next]) + (:require [pez-rewrite-clj.zip.whitespace :as ws] + [clojure.zip :as z])) + +(defn right + "Move right to next non-whitespace/non-comment location." + [zloc] + (some-> zloc z/right ws/skip-whitespace)) + +(defn left + "Move left to next non-whitespace/non-comment location." + [zloc] + (some-> zloc z/left ws/skip-whitespace-left)) + +(defn down + "Move down to next non-whitespace/non-comment location." + [zloc] + (some-> zloc z/down ws/skip-whitespace)) + +(defn up + "Move up to next non-whitespace/non-comment location." + [zloc] + (some-> zloc z/up ws/skip-whitespace-left)) + +(defn next + "Move to the next non-whitespace/non-comment location in a depth-first manner." + [zloc] + (when zloc + (or (some->> zloc + z/next + (ws/skip-whitespace z/next)) + (vary-meta zloc assoc ::end? true)))) + +(defn end? + "Check whether the given node is at the end of the depth-first traversal." + [zloc] + (or (not zloc) + (z/end? zloc) + (::end? (meta zloc)))) + +(defn rightmost? + "Check if the given location represents the leftmost non-whitespace/ + non-comment one." + [zloc] + (nil? (ws/skip-whitespace (z/right zloc)))) + +(defn leftmost? + "Check if the given location represents the leftmost non-whitespace/ + non-comment one." + [zloc] + (nil? (ws/skip-whitespace-left (z/left zloc)))) + +(defn prev + "Move to the next non-whitespace/non-comment location in a depth-first manner." + [zloc] + (some->> zloc + z/prev + (ws/skip-whitespace z/prev))) + +(defn leftmost + "Move to the leftmost non-whitespace/non-comment location." + [zloc] + (some-> zloc + z/leftmost + ws/skip-whitespace)) + +(defn rightmost + "Move to the rightmost non-whitespace/non-comment location." + [zloc] + (some-> zloc + z/rightmost + ws/skip-whitespace-left)) diff --git a/src/cljs-lib/src/pez_rewrite_clj/zip/removez.cljs b/src/cljs-lib/src/pez_rewrite_clj/zip/removez.cljs new file mode 100644 index 0000000..6d82aa9 --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/zip/removez.cljs @@ -0,0 +1,62 @@ +(ns pez-rewrite-clj.zip.removez + (:refer-clojure :exclude [remove]) + (:require [pez-rewrite-clj.zip.move :as m] + [pez-rewrite-clj.zip.utils :as u] + [pez-rewrite-clj.zip.whitespace :as ws] + [clojure.zip :as z])) + + +(defn- remove-trailing-space + "Remove all whitespace following a given node." + [zloc p?] + (u/remove-right-while zloc p?)) + +(defn- remove-preceding-space + "Remove all whitespace preceding a given node." + [zloc p?] + (u/remove-left-while zloc p?)) + +(defn remove + "Remove value at the given zipper location. Returns the first non-whitespace + node that would have preceded it in a depth-first walk. Will remove whitespace + appropriately. + + - `[1 2 3] => [1 3]` + - `[1 2] => [1]` + - `[1 2] => [2]` + - `[1] => []` + - `[ 1 ] => []` + - `[1 [2 3] 4] => [1 [2 3]]` + - `[1 [2 3] 4] => [[2 3] 4]` + + If a node is located rightmost, both preceding and trailing spaces are removed, + otherwise only trailing spaces are touched. This means that a following element + (no matter whether on the same line or not) will end up in the same position + (line/column) as the removed one, _unless_ a comment lies between the original + node and the neighbour." + [zloc] + {:pre [zloc] + :post [%]} + (->> (-> (if (or (m/rightmost? zloc) + (m/leftmost? zloc)) + (remove-preceding-space zloc ws/whitespace?) + zloc) + (remove-trailing-space ws/whitespace?) + z/remove) + (ws/skip-whitespace z/prev))) + +(defn remove-preserve-newline + "Same as remove but preserves newlines" + [zloc] + {:pre [zloc] + :post [%]} + (->> (-> (if (or (m/rightmost? zloc) + (m/leftmost? zloc)) + (remove-preceding-space zloc #(and (ws/whitespace? %) + (not (ws/linebreak? %)))) + zloc) + (remove-trailing-space #(and (ws/whitespace? %) + (not (ws/linebreak? %)))) + z/remove) + (ws/skip-whitespace z/prev))) + diff --git a/src/cljs-lib/src/pez_rewrite_clj/zip/seqz.cljs b/src/cljs-lib/src/pez_rewrite_clj/zip/seqz.cljs new file mode 100644 index 0000000..9dbdf7e --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/zip/seqz.cljs @@ -0,0 +1,110 @@ +(ns pez-rewrite-clj.zip.seqz + (:refer-clojure :exclude [map get assoc seq? vector? list? map? set?]) + (:require [pez-rewrite-clj.zip.base :as base] + [pez-rewrite-clj.zip.editz :as e] + [pez-rewrite-clj.zip.findz :as f] + [pez-rewrite-clj.zip.insert :as i] + [pez-rewrite-clj.zip.move :as m] + [clojure.zip :as z])) + +;; ## Predicates + +(defn seq? + [zloc] + (contains? + #{:forms :list :vector :set :map} + (base/tag zloc))) + +(defn list? + [zloc] + (= (base/tag zloc) :list)) + +(defn vector? + [zloc] + (= (base/tag zloc) :vector)) + +(defn set? + [zloc] + (= (base/tag zloc) :set)) + +(defn map? + [zloc] + (= (base/tag zloc) :map)) + +;; ## Map Operations + +(defn- map-seq + [f zloc] + {:pre [(seq? zloc)]} + (if-let [n0 (m/down zloc)] + (some->> (f n0) + (iterate + (fn [loc] + (if-let [n (m/right loc)] + (f n)))) + (take-while identity) + (last) + (m/up)) + zloc)) + +(defn map-vals + "Apply function to all value nodes of the given map node." + [f zloc] + {:pre [(map? zloc)]} + (loop [loc (m/down zloc) + parent zloc] + (if-not (and loc (z/node loc)) + parent + (if-let [v0 (m/right loc)] + (if-let [v (f v0)] + (recur (m/right v) (m/up v)) + (recur (m/right v0) parent)) + parent)))) + +(defn map-keys + "Apply function to all key nodes of the given map node." + [f zloc] + {:pre [(map? zloc)]} + (loop [loc (m/down zloc) + parent zloc] + (if-not (and loc (z/node loc)) + parent + (if-let [v (f loc)] + (recur (m/right (m/right v)) (m/up v)) + (recur (m/right (m/right loc)) parent))))) + +(defn map + "Apply function to all value nodes in the given seq node. Iterates over + value nodes of maps but over each element of a seq." + [f zloc] + {:pre [(seq? zloc)]} + (if (map? zloc) + (map-vals f zloc) + (map-seq f zloc))) + +;; ## Get/Assoc + +(defn get + "If a map is given, get element with the given key; if a seq is given, get nth element." + [zloc k] + {:pre [(or (map? zloc) (and (seq? zloc) (integer? k)))]} + (if (map? zloc) + (some-> zloc m/down (f/find-value k) m/right) + (nth + (some->> (m/down zloc) + (iterate m/right) + (take-while identity)) + k))) + +(defn assoc + "Set map/seq element to the given value." + [zloc k v] + (if-let [vloc (get zloc k)] + (-> vloc (e/replace v) m/up) + (if (map? zloc) + (-> zloc + (i/append-child k) + (i/append-child v)) + (throw + (js/Error. + (str "index out of bounds: " k)))))) diff --git a/src/cljs-lib/src/pez_rewrite_clj/zip/utils.cljs b/src/cljs-lib/src/pez_rewrite_clj/zip/utils.cljs new file mode 100644 index 0000000..c2ea66e --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/zip/utils.cljs @@ -0,0 +1,93 @@ +(ns ^:no-doc pez-rewrite-clj.zip.utils + (:require [clojure.zip :as z])) + +;; ## Remove + +(defn- update-in-path + [[node path :as loc] k f] + (let [v (get path k)] + (if (seq v) + (with-meta + [node (assoc path k (f v) :changed? true)] + (meta loc)) + loc))) + +(defn remove-right + "Remove right sibling of the current node (if there is one)." + [loc] + (update-in-path loc :r next)) + +(defn remove-left + "Remove left sibling of the current node (if there is one)." + [loc] + (update-in-path loc :l pop)) + + +(defn remove-while + [zloc p?] + "Remove nodes while predicate true. (depth first in reverse!) " + (loop [zloc zloc] + (let [ploc (z/prev zloc)] + (if-not (and ploc (p? ploc)) + zloc + (recur (z/remove zloc)))))) + +(defn remove-right-while + "Remove elements to the right of the current zipper location as long as + the given predicate matches." + [zloc p?] + (loop [zloc zloc] + (if-let [rloc (z/right zloc)] + (if (p? rloc) + (recur (remove-right zloc)) + zloc) + zloc))) + +(defn remove-left-while + "Remove elements to the left of the current zipper location as long as + the given predicate matches." + [zloc p?] + (loop [zloc zloc] + (if-let [lloc (z/left zloc)] + (if (p? lloc) + (recur (remove-left zloc)) + zloc) + zloc))) + +;; ## Remove and Move + +(defn remove-and-move-left + "Remove current node and move left. If current node is at the leftmost + location, returns `nil`." + [[_ {:keys [l] :as path} :as loc]] + (if (seq l) + (with-meta + [(peek l) (-> path + (update-in [:l] pop) + (assoc :changed? true))] + (meta loc)))) + +(defn remove-and-move-right + "Remove current node and move right. If current node is at the rightmost + location, returns `nil`." + [[_ {:keys [r] :as path} :as loc]] + (if (seq r) + (with-meta + [(first r) (-> path + (update-in [:r] next) + (assoc :changed? true))] + (meta loc)))) + + +(defn remove-and-move-up [loc] + (let [[node {l :l, ppath :ppath, pnodes :pnodes, rs :r, :as path}] loc] + (if (nil? path) + (throw (js/Error. "Remove at top")) + (if (pos? (count l)) + (z/up (with-meta [(peek l) + (assoc path :l (pop l) :changed? true)] + (meta loc))) + (with-meta [(z/make-node loc (peek pnodes) rs) + (and ppath (assoc ppath :changed? true))] + (meta loc)))))) + diff --git a/src/cljs-lib/src/pez_rewrite_clj/zip/whitespace.cljs b/src/cljs-lib/src/pez_rewrite_clj/zip/whitespace.cljs new file mode 100644 index 0000000..4a2707e --- /dev/null +++ b/src/cljs-lib/src/pez_rewrite_clj/zip/whitespace.cljs @@ -0,0 +1,76 @@ +(ns pez-rewrite-clj.zip.whitespace + (:require [pez-rewrite-clj.node :as node] + [clojure.zip :as z])) + +;; ## Predicates + +(defn whitespace? + [zloc] + (some-> zloc z/node node/whitespace?)) + +(defn linebreak? + [zloc] + (some-> zloc z/node node/linebreak?)) + +(defn comment? + [zloc] + (some-> zloc z/node node/comment?)) + +(defn whitespace-not-linebreak? + [zloc] + (and + (whitespace? zloc) + (not (linebreak? zloc)))) + +(defn whitespace-or-comment? + [zloc] + (some-> zloc z/node node/whitespace-or-comment?)) + + +;; ## Movement + +(defn skip + "Perform the given movement while the given predicate returns true." + [f p? zloc] + (->> (iterate f zloc) + (take-while identity) + (take-while (complement z/end?)) + (drop-while p?) + (first))) + +(defn skip-whitespace + "Perform the given movement (default: `z/right`) until a non-whitespace/ + non-comment node is encountered." + ([zloc] (skip-whitespace z/right zloc)) + ([f zloc] (skip f whitespace-or-comment? zloc))) + +(defn skip-whitespace-left + "Move left until a non-whitespace/non-comment node is encountered." + [zloc] + (skip-whitespace z/left zloc)) + +;; ## Insertion + +(defn prepend-space + "Prepend a whitespace node representing the given number of spaces (default: 1)." + ([zloc] (prepend-space zloc 1)) + ([zloc n] + (z/insert-left zloc (node/spaces n)))) + +(defn append-space + "Append a whitespace node representing the given number of spaces (default: 1)." + ([zloc] (append-space zloc 1)) + ([zloc n] + (z/insert-right zloc (node/spaces n)))) + +(defn prepend-newline + "Prepend a newlines node representing the given number of newlines (default: 1)." + ([zloc] (prepend-newline zloc 1)) + ([zloc n] + (z/insert-left zloc (node/newlines n)))) + +(defn append-newline + "Append a newline node representing the given number of newlines (default: 1)." + ([zloc] (append-newline zloc 1)) + ([zloc n] + (z/insert-right zloc (node/newlines n)))) diff --git a/src/cljs-lib/test/calva/fmt/editor_test.cljs b/src/cljs-lib/test/calva/fmt/editor_test.cljs new file mode 100644 index 0000000..21d094e --- /dev/null +++ b/src/cljs-lib/test/calva/fmt/editor_test.cljs @@ -0,0 +1,13 @@ +(ns calva.fmt.editor-test + (:require [cljs.test :include-macros true :refer [deftest is]] + [calva.fmt.editor :as sut])) + + +(deftest raplacement-edits-for-diffing-lines + (is (= [] + (sut/raplacement-edits-for-diffing-lines "foo\nfoo\nbar\nbar" + "foo\nfoo\nbar\nbar"))) + (is (= [{:edit "replace", :start {:line 1, :character 0}, :end {:line 1, :character 6}, :text "bar"} + {:edit "replace", :start {:line 2, :character 0}, :end {:line 2, :character 3}, :text "baz"}] + (sut/raplacement-edits-for-diffing-lines "foo\nfooooo\nbar\nbar" + "foo\nbar\nbaz\nbar")))) diff --git a/src/cljs-lib/test/calva/fmt/formatter_test.cljs b/src/cljs-lib/test/calva/fmt/formatter_test.cljs new file mode 100644 index 0000000..e409b20 --- /dev/null +++ b/src/cljs-lib/test/calva/fmt/formatter_test.cljs @@ -0,0 +1,344 @@ +(ns calva.fmt.formatter-test + (:require [cljs.test :include-macros true :refer [deftest is testing]] + [cljfmt.core :as cljfmt] + [calva.fmt.formatter :as sut])) + +(deftest format-text-at-range + (is (= "(foo)\n(defn bar\n [x]\n baz)" + (:range-text (sut/format-text-at-range {:eol "\n" :all-text " (foo)\n(defn bar\n[x]\nbaz)" :range [2 26]})))) + (is (not (contains? (sut/format-text-at-range {:eol "\n" :all-text " (foo)\n(defn bar\n[x]\nbaz)" :range [2 26]}) :new-index)))) + +(def all-text " (foo) + (defn bar + [x] + +baz)") + +(deftest format-text-at-idx + (is (= "(defn bar + [x] + + baz)" + (:range-text (sut/format-text-at-idx {:eol "\n" :all-text all-text :range [10 38] :idx 11})))) + (is (= 1 + (:new-index (sut/format-text-at-idx {:eol "\n" :all-text all-text :range [10 38] :idx 11})))) + (is (= [10 38] + (:range (sut/format-text-at-idx {:eol "\n" :all-text all-text :range [10 38] :idx 11})))) + (is (= [0 5] + (:range (sut/format-text-at-idx {:eol "\n" :all-text "(\n\n,)" :range [0 5] :idx 2})))) + (is (= "()" + (:range-text (sut/format-text-at-idx {:eol "\n" :all-text "(\n\n,)" :range [0 5] :idx 2}))))) + +(def misaligned-text "(def foo +(let[a b +aa bb +ccc {:a b :aa bb :ccc ccc}] +))") + +(deftest format-aligned-text-at-idx + (testing "Aligns associative structures when `:align-associative` is `true`" + (is (= "(def foo + (let [a b + aa bb + ccc {:a b + :aa bb + :ccc ccc}]))" + (:range-text (sut/format-text-at-idx {:eol "\n" + :all-text misaligned-text + :config {:align-associative? true} + :range [0 56] + :idx 0}))))) + + (testing "Does not align associative structures when `:align-associative` is not `true`" + (is (= "(def foo + (let [a b + aa bb + ccc {:a b :aa bb :ccc ccc}]))" + (:range-text (sut/format-text-at-idx {:eol "\n" + :all-text misaligned-text + :range [0 56] + :idx 1})))))) + +(deftest format-trim-text-at-idx + (testing "Trims space between forms when `:remove-multiple-non-indenting-spaces?` is `true`" + (is (= "(def foo + (let [a b + aa bb + ccc {:a b :aa bb :ccc ccc}]))" + (:range-text (sut/format-text-at-idx {:eol "\n" + :all-text misaligned-text + :config {:remove-multiple-non-indenting-spaces? true} + :range [0 56] + :idx 0}))))) + + (testing "Does not trim space between forms when `:remove-multiple-non-indenting-spaces?` is missing" + (is (= "(def foo + (let [a b + aa bb + ccc {:a b :aa bb :ccc ccc}]))" + (:range-text (sut/format-text-at-idx {:eol "\n" + :all-text misaligned-text + :range [0 56] + :idx 1})))))) + +(def a-comment + {:eol "\n" + :all-text " (foo) +(comment + (defn bar + [x] + +baz))" + :range [8 48] + :idx 47 + :config {:keep-comment-forms-trail-paren-on-own-line? true + :comment-form? true}}) + +(deftest format-text-w-comments-at-idx + (is (= {:new-index 38 + :range-text "(comment + (defn bar + [x] + + baz))"} + (select-keys (sut/format-text-at-idx + (assoc-in a-comment [:config :comment-form?] false)) + [:range-text :new-index]))) + + (is (= {:new-index 41 + :range-text "(comment + (defn bar + [x] + + baz) + )"} + (select-keys (sut/format-text-at-idx + (assoc a-comment :idx 47)) + [:range-text :new-index])))) + +(deftest new-index + (is (= 1 + (:new-index (sut/format-text-at-idx {:eol "\n" :all-text all-text :range [10 38] :idx 11})))) + (is (= 13 + (:new-index (sut/format-text-at-idx {:eol "\n" :all-text all-text :range [10 38] :idx 28})))) + (is (= 10 + (:new-index (sut/format-text-at-idx {:eol "\n" :all-text all-text :range [10 38] :idx 22})))) + (is (= 12 + (:new-index (sut/format-text-at-idx {:eol "\n" :all-text all-text :range [10 38] :idx 27})))) + (is (= 22 + (:new-index (sut/format-text-at-idx {:eol "\n" :all-text all-text :range [10 38] :idx 33})))) + (is (= 5 + (:new-index (sut/format-text-at-idx {:eol "\n" :all-text "(defn \n \nfoo)" :range [0 14] :idx 6})))) + (is (= 11 + (:new-index (sut/format-text-at-idx {:eol "\n" :all-text "(foo\n (bar)\n )" :range [0 14] :idx 11})))) + (is (= 1 + (:new-index (sut/format-text-at-idx {:eol "\n" :all-text "(\n\n,)" :range [0 14] :idx 2}))))) + + +(def head-and-tail-text "(def a 1) + + +(defn foo [x] (let [bar 1] + +bar))") + + +(deftest add-head-and-tail + (is (= {:head "" :tail head-and-tail-text + :all-text head-and-tail-text + :idx 0} + (sut/add-head-and-tail {:all-text head-and-tail-text :idx 0}))) + (is (= {:head head-and-tail-text :tail "" + :all-text head-and-tail-text + :idx (count head-and-tail-text)} + (sut/add-head-and-tail {:all-text head-and-tail-text :idx (count head-and-tail-text)}))) + (is (= {:head "(def a 1)\n\n\n(defn foo " + :tail "[x] (let [bar 1]\n\nbar))" + :all-text head-and-tail-text + :idx 22} + (sut/add-head-and-tail {:all-text head-and-tail-text :idx 22}))) + (is (= {:head head-and-tail-text :tail "" + :all-text head-and-tail-text + :idx (inc (count head-and-tail-text))} + (sut/add-head-and-tail {:all-text head-and-tail-text :idx (inc (count head-and-tail-text))})))) + + +(deftest normalize-indents + (is (= "(foo)\n (defn bar\n [x]\n baz)" + (:range-text (sut/normalize-indents {:eol "\n" + :all-text " (foo)\n(defn bar\n[x]\nbaz)" + :range [2 26] + :range-text "(foo)\n(defn bar\n [x]\n baz)"}))))) + + +(def first-top-level-text " +;; foo +(defn foo [x] + (* x x)) + ") + +(def mid-top-level-text ";; foo +(defn foo [x] + (* x x)) + +(bar)") + +(def last-top-level-text ";; foo +(defn foo [x] + (* x x)) + ") + + +(deftest format-text-at-idx-on-type + (is (= "(bar \n\n )" + (:range-text (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(bar \n\n)" :range [0 8] :idx 7})))) + (is (= "(bar \n \n )" + (:range-text (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(bar \n \n)" :range [0 9] :idx 8})))) + (is (= "(bar \n \n )" + (:range-text (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(bar \n\n)" :range [0 8] :idx 6})))) + (is (= "\"bar \n \n \"" + (:range-text (sut/format-text-at-idx-on-type {:eol "\n" :all-text "\"bar \n \n \"" :range [0 10] :idx 7})))) + (is (= "\"bar \n \n \"" + (:range-text (sut/format-text-at-idx-on-type {:eol "\n" :all-text "\"bar \n \n \"" :range [0 10] :idx 7})))) + (is (= "'([]\n [])" + (:range-text (sut/format-text-at-idx-on-type {:eol "\n" :all-text " '([]\n[])" :range [2 10] :idx 7})))) + (is (= "[:foo\n \n (foo) (bar)]" + (:range-text (sut/format-text-at-idx-on-type {:eol "\n" :all-text "[:foo\n\n(foo)(bar)]" :range [0 18] :idx 6}))))) + + +(deftest new-index-on-type + (is (= 6 + (:new-index (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(defn \n)" :range [0 8] :idx 6})))) + (is (= 8 + (:new-index (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(defn\n\n)" :range [0 8] :idx 6})))) + #_(is (= 8 ;; Fails due to a bug in rewrite-cljs + (:new-index (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(defn\n\n#_)" :range [0 10] :idx 6})))) + (is (= 9 + (:new-index (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(defn \n)" :range [0 8] :idx 7})))) + (is (= 7 + (:new-index (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(defn \n )" :range [0 10] :idx 7})))) + (is (= 9 + (:new-index (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(defn \n \n )" :range [0 13] :idx 9})))) + (is (= 9 + (:new-index (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(defn \n\n)" :range [0 9] :idx 7})))) + (is (= 10 + (:new-index (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(defn \n\n)" :range [0 9] :idx 8})))) + (is (= 13 + (:new-index (sut/format-text-at-idx-on-type {:eol "\n" :all-text "(foo\n (bar)\n)" :range [0 13] :idx 12})))) + (is (= 7 + (:new-index (sut/format-text-at-idx-on-type {:eol "\n" :all-text "[:foo\n\n(foo)(bar)]" :range [0 18] :idx 6}))))) + + +(deftest new-index-on-type-crlf + (is (= 6 + (:new-index (sut/format-text-at-idx-on-type {:eol "\r\n" :all-text "(defn \r\n)" :range [0 9] :idx 6})))) + (is (= 10 + (:new-index (sut/format-text-at-idx-on-type {:eol "\r\n" :all-text "(defn \r\n)" :range [0 9] :idx 8})))) + (is (= 8 + (:new-index (sut/format-text-at-idx-on-type {:eol "\r\n" :all-text "(defn \r\n )" :range [0 11] :idx 8})))) + (is (= 10 + (:new-index (sut/format-text-at-idx-on-type {:eol "\r\n" :all-text "(defn \r\n \r\n )" :range [0 15] :idx 10})))) + (is (= 10 + (:new-index (sut/format-text-at-idx-on-type {:eol "\r\n" :all-text "(defn \r\n\r\n)" :range [0 11] :idx 8})))) + (is (= 12 + (:new-index (sut/format-text-at-idx-on-type {:eol "\r\n" :all-text "(defn \r\n\r\n)" :range [0 11] :idx 10})))) + (is (= 15 + (:new-index (sut/format-text-at-idx-on-type {:eol "\r\n" :all-text "(foo\r\n (bar)\r\n)" :range [0 15] :idx 14}))))) + + +(deftest index-for-tail-in-range + (is (= 7 + (:new-index (sut/index-for-tail-in-range + {:range-text "foo te x t" + :range-tail " x t"})))) + (is (= 169 + (:new-index (sut/index-for-tail-in-range + {:range-text "(create-state \"\" + \"### \" + \" ###\" + \" ### \" + \" # \")" + :range-tail "\" # \")"}))))) + + +(deftest remove-indent-token-if-empty-current-line + (is (= {:range-text "foo\n\nbar" + :range [4 4] + :current-line "" + :new-index 4} + (sut/remove-indent-token-if-empty-current-line {:range-text "foo\n0\nbar" + :range [4 5] + :new-index 4 + :current-line ""}))) + (is (= {:range-text "foo\n0\nbar" + :range [4 5] + :current-line "0" + :new-index 4} + (sut/remove-indent-token-if-empty-current-line {:range-text "foo\n0\nbar" + :range [4 5] + :new-index 4 + :current-line "0"})))) + + +(deftest current-line-empty? + (is (= true (sut/current-line-empty? {:current-line " "}))) + (is (= false (sut/current-line-empty? {:current-line " foo "})))) + + +(deftest indent-before-range + (is (= 10 + (sut/indent-before-range {:all-text "(def a 1) + + +(defn foo [x] (let [bar 1] + +bar))" :range [22 25]}))) + (is (= 4 + (sut/indent-before-range {:all-text " '([] +[])" :range [4 9]})))) + + +(deftest read-cljfmt + (is (= (count cljfmt/default-indents) + (count (:indents (sut/read-cljfmt "{}")))) + "by default uses cljfmt indent rules") + (is (= (+ 2 (count cljfmt/default-indents)) + (count (:indents (sut/read-cljfmt "{:indents {foo [[:inner 0]] bar [[:block 1]]}}")))) + "merges indents on top of cljfmt indent rules") + (is (= {'a [[:inner 0]]} + (:indents (sut/read-cljfmt "{:indents ^:replace {a [[:inner 0]]}}"))) + "with :replace metadata hint overrides default indents") + (is (= false + (:align-associative? (sut/read-cljfmt "{}"))) + ":align-associative? is false by default.") + (is (= true + (:align-associative? (sut/read-cljfmt "{:align-associative? true}"))) + "including keys in cljfmt such as :align-associative? will override defaults.") + (is (= true + (:remove-surrounding-whitespace? (sut/read-cljfmt "{}"))) + ":remove-surrounding-whitespace? is true by default.") + (is (= false + (:remove-surrounding-whitespace? (sut/read-cljfmt "{:remove-surrounding-whitespace? false}"))) + "including keys in cljfmt such as :remove-surrounding-whitespace? will override defaults.") + (is (nil? (:foo (sut/read-cljfmt "{:bar false}"))) + "most keys don't have any defaults.")) + +(deftest cljfmt-options + (is (= (count cljfmt/default-indents) + (count (:indents (sut/merge-cljfmt {})))) + "by default uses cljfmt indent rules") + (is (= (+ 2 (count cljfmt/default-indents)) + (count (:indents (sut/merge-cljfmt '{:indents {foo [[:inner 0]] bar [[:block 1]]}})))) + "merges indents on top of cljfmt indent rules") + (is (= {'a [[:inner 0]]} + (:indents (sut/merge-cljfmt '{:indents ^:replace {a [[:inner 0]]}}))) + "with :replace metadata hint overrides default indents") + (is (= true + (:align-associative? (sut/merge-cljfmt {:align-associative? true + :cljfmt-string "{:align-associative? false}"}))) + "cljfmt :align-associative? has lower priority than config's option") + (is (= false + (:align-associative? (sut/merge-cljfmt {:cljfmt-string "{}"}))) + ":align-associative? is false by default") + (is (nil? (:foo (sut/merge-cljfmt {:cljfmt-string "{:bar false}"}))) + "most keys don't have any defaults.")) diff --git a/src/cljs-lib/test/calva/fmt/util_test.cljs b/src/cljs-lib/test/calva/fmt/util_test.cljs new file mode 100644 index 0000000..7a822f5 --- /dev/null +++ b/src/cljs-lib/test/calva/fmt/util_test.cljs @@ -0,0 +1,49 @@ +(ns calva.fmt.util-test + (:require [cljs.test :include-macros true :refer [deftest is]] + [calva.fmt.util :as sut])) + + +#_(deftest log + (is (= (with-out-str (sut/log {:range-text ""} :range-text)) + {:range-text ""}))) + + +(def all-text "(def a 1) + + +(defn foo [x] (let [bar 1] + +bar))") + + +(deftest current-line + (is (= "(def a 1)" (sut/current-line all-text 0))) + (is (= "(def a 1)" (sut/current-line all-text 4))) + (is (= "(def a 1)" (sut/current-line all-text 9))) + (is (= "" (sut/current-line all-text 10))) + (is (= "" (sut/current-line all-text 11))) + (is (= "(defn foo [x] (let [bar 1]" (sut/current-line all-text 12))) + (is (= "(defn foo [x] (let [bar 1]" (sut/current-line all-text 27))) + (is (= "(defn foo [x] (let [bar 1]" (sut/current-line all-text 38))) + (is (= "" (sut/current-line all-text 39))) + (is (= "bar))" (sut/current-line all-text (count all-text))))) + + +(deftest re-pos-one + (is (= 6 + (sut/re-pos-first "\\s*x\\s*t$" "foo te x t"))) + (is (= 6 + (sut/re-pos-first "\\s*x\\s*t$" "foo te x t"))) + (is (= 5 + (sut/re-pos-first "\\s*e\\s*xt\\s*$" "foo te xt"))) + (is (= 173 + (sut/re-pos-first "\"\\s*#\\s*\"\\)$" "(create-state \"\" + \"### \" + \" ###\" + \" ### \" + \" # \")")))) + + +(deftest escape-regexp + (is (= "\\.\\*" + (sut/escape-regexp ".*")))) diff --git a/src/cljs-lib/test/calva/js2cljs/converter_test.cljs b/src/cljs-lib/test/calva/js2cljs/converter_test.cljs new file mode 100644 index 0000000..7dcafe5 --- /dev/null +++ b/src/cljs-lib/test/calva/js2cljs/converter_test.cljs @@ -0,0 +1,23 @@ +(ns calva.js2cljs.converter-test + (:require [calva.js2cljs.converter :as sut] + [clojure.spec.alpha :as s] + [cljs.test :refer [testing deftest is]])) + +;; valid result +(s/def ::result string?) + +;; invalid result +(s/def ::message string?) +(s/def ::number-of-parsed-lines pos-int?) +(s/def ::name string?) +(s/def ::exception (s/keys :req-un [::name ::message])) +(s/def ::error (s/keys :req-un [::message ::number-of-parsed-lines ::exception])) +(s/def ::invalid-result (s/keys :req-un [::error])) + +(deftest valid-results-test + (testing "Returns a map with a `:result` string entry when conversion succeeds" + (is (s/valid? ::result (:result (sut/convert "foo")))))) + +(deftest invalid-results-test + (testing "Returns a map with an `:error` entry when conversion fails" + (is (s/valid? ::invalid-result (sut/convert "import * as foo from 'foo';"))))) \ No newline at end of file diff --git a/src/cljs-lib/test/calva/js_utils_test.cljs b/src/cljs-lib/test/calva/js_utils_test.cljs new file mode 100644 index 0000000..aba400f --- /dev/null +++ b/src/cljs-lib/test/calva/js_utils_test.cljs @@ -0,0 +1,11 @@ +(ns calva.js-utils-test + (:require [cljs.test :refer [deftest is testing]] + [calva.js-utils :refer [jsify]])) + +(deftest jsify-test + (testing "Converts map with vector containing map" + (is (= (pr-str (jsify {:foo [1 {:bar :baz}]})) + (pr-str #js {:foo #js [1 #js {:bar "baz"}]})))) + (testing "Converts map with namespaced keywords" + (is (= (pr-str (jsify {:foo/bar :foo/bar})) + (pr-str #js {"foo/bar" "foo/bar"}))))) \ No newline at end of file diff --git a/src/cljs-lib/test/calva/parse_test.cljs b/src/cljs-lib/test/calva/parse_test.cljs new file mode 100644 index 0000000..81fbc22 --- /dev/null +++ b/src/cljs-lib/test/calva/parse_test.cljs @@ -0,0 +1,38 @@ +(ns calva.parse-test + (:require [cljs.test :refer [testing is deftest]] + [calva.parse :refer [parse-edn parse-forms parse-clj-edn]])) + +(deftest parse-edn-test + (testing "Should parse form preceded by #= as is" + (is (= (parse-edn "#=(+ 1 2)") "#=(+ 1 2)"))) + (testing "Should parse map with keyword key and vector value" + (is (= (parse-edn "{:foo [1 2]}") {:foo [1 2]}))) + (testing "Should parse map with namespaced keyword key and vector value" + (is (= (parse-edn "{:foo/bar [1 2]}") {:foo/bar [1 2]}))) + (testing "Should return first form if input is multiple top level forms" + (is (= (parse-edn ":a {:foo ['bar] :bar 'foo}") :a)))) + +(deftest parse-forms-test + (testing "Should parse keyword, map, vector, symbol, and should add ' before symbols" + (is (= (parse-forms ":a {:foo [bar] :bar foo}") + [:a {:foo ['bar] :bar 'foo}]))) + (testing "Should parse quote symbol into `quote` and should parse forms preceded by #= as nil" + (is (= (parse-forms ":a {:foo ['bar] :bar 'foo} #=(+ 1 2)") + [:a {:foo ['(quote bar)] :bar '(quote foo)} nil]))) + (testing "Should parse forms preceded by #= as nil (and not evaluate them)" + (is (= (parse-forms "{:a #=(1 + 2)}") + [{:a nil}])))) + +(deftest parse-clj-edn-test + (testing "Should parse nil as nil" + (is (= (parse-clj-edn nil) nil))) + (testing "Should parse map with keyword key and vector" + (is (= (parse-clj-edn "{:foo [1 2]}") {:foo [1 2]}))) + (testing "Should parse map with namespaced keyword key and vector" + (is (= (parse-clj-edn "{:foo/bar [1 2]}") {:foo/bar [1 2]}))) + (testing "Should return first form if input is multiple top level forms" + (is (= :a (parse-clj-edn ":a {:foo ['bar] :bar 'foo}")))) + (testing "Should parse edn regexp as js regexp" + (is (= js/RegExp (type (parse-clj-edn "#\"^foo.*bar$\""))))) + (testing "Should return string representation of regexp as string" + (is (= "/^foo.*bar$/" (str (parse-clj-edn "#\"^foo.*bar$\"")))))) \ No newline at end of file diff --git a/src/cljs-lib/test/calva/pprint/printer_test.cljs b/src/cljs-lib/test/calva/pprint/printer_test.cljs new file mode 100644 index 0000000..c261991 --- /dev/null +++ b/src/cljs-lib/test/calva/pprint/printer_test.cljs @@ -0,0 +1,35 @@ +(ns calva.pprint.printer-test + (:require [cljs.test :refer [testing deftest is]] + [calva.pprint.printer :refer [pretty-print]] + [clojure.string :as str])) + +(deftest pretty-print-test + (letfn [(pretty-line-of [n s opts] + (as-> `[~@(repeat n s)] x + (pretty-print x opts) + (:value x) + (str/split x #"\n") + (take 1 x)))] + (let [deep [[[[[[[[[[[[[[[[[{:foo [:bar]}]]]]]]]]]]]]]]]]] + shallow [[:foo]]] + (testing "String input" + (is (= "[[[:foo]]]" + (:value (pretty-print "[ [ [:foo + ]] ]" nil))))) + (testing "Valid and invalid EDN" + (is (= "[1]" + (:value (pretty-print "[ 1]" nil)))) + (is (= "[ 1" + (:value (pretty-print "[ 1" nil))))) + (testing "Default printing options" ; zprint default width 80 + (let [width (apply count (pretty-line-of 25 "foo" nil))] + (is (> width 70)) + (is (<= width 80))) + (is (not (re-find #"#" (:value (pretty-print deep nil)))))) + (testing "Settings" + (let [width (apply count (pretty-line-of 25 "foo" {:width 40}))] + (is (> width 30)) + (is (<= width 40))) + (let [width (apply count (pretty-line-of 25 "foo" {:max-length 2}))] + (is (< width 20))) + (is (re-find #"#" (:value (pretty-print shallow {:max-depth 1})))))))) \ No newline at end of file diff --git a/src/cljs-lib/test/calva/state_test.cljs b/src/cljs-lib/test/calva/state_test.cljs new file mode 100644 index 0000000..db14acd --- /dev/null +++ b/src/cljs-lib/test/calva/state_test.cljs @@ -0,0 +1,31 @@ +(ns calva.state-test + (:require [cljs.test :refer [testing deftest is use-fixtures]] + [calva.state :as state])) + +(use-fixtures :each + {:before (fn [] (reset! state/state {}))}) + +(deftest set-state-value!-test + (testing "Should write value to state, given key" + (state/set-state-value! "hello" "world") + (is (= {"hello" "world"} @state/state)))) + +(deftest remove-state-value!-test + (testing "Should remove value from state, given key" + (reset! state/state {"hello" "world"}) + (state/remove-state-value! "hello") + (is (= {} @state/state)))) + +(deftest get-state-value-test + (testing "Should get value from state, given key" + (reset! state/state {"hello" "world"}) + (is (= "world" (state/get-state-value "hello"))))) + +(deftest get-state-test + (testing "Should return all state" + (let [all-state {"hello" "world" "fizz" "buzz"}] + (reset! state/state all-state) + (is (= all-state (state/get-state)))))) + +(comment + (state/get-state)) \ No newline at end of file diff --git a/src/cljs-lib/test/pez_rewrite_clj/node_test.cljs b/src/cljs-lib/test/pez_rewrite_clj/node_test.cljs new file mode 100644 index 0000000..d131c7c --- /dev/null +++ b/src/cljs-lib/test/pez_rewrite_clj/node_test.cljs @@ -0,0 +1,55 @@ +(ns pez-rewrite-clj.node-test + (:require [cljs.test :refer-macros [deftest is testing run-tests]] + [pez-rewrite-clj.node :as n] + [pez-rewrite-clj.parser :as p])) + + +(deftest namespaced-keyword + (is (= ":dill/dall" + (n/string (n/keyword-node :dill/dall))))) + +(deftest funky-keywords + (is (= ":%dummy.*" + (n/string (n/keyword-node :%dummy.*))))) + +(deftest regex-node + (let [sample "(re-find #\"(?i)RUN\" s)" + sample2 "(re-find #\"(?m)^rss\\s+(\\d+)$\")" + sample3 "(->> (str/split container-name #\"/\"))"] + (is (= sample (-> sample p/parse-string n/string))) + (is (= sample2 (-> sample2 p/parse-string n/string))) + (is (= sample3 (-> sample3 p/parse-string n/string))))) + + +(deftest regex-with-newlines + (let [sample "(re-find #\"Hello + \\nJalla\")"] + (is (= sample (-> sample p/parse-string n/string))))) + + + +(deftest reader-conditionals + (testing "Simple reader conditional" + (let [sample "#?(:clj bar)" + res (p/parse-string sample)] + (is (= sample (n/string res))) + (is (= :reader-macro (n/tag res))) + (is (= [:token :list] (map n/tag (n/children res)))))) + + (testing "Reader conditional with space before list" + (let [sample "#? (:clj bar)" + sample2 "#?@ (:clj bar)"] + (is (= sample (-> sample p/parse-string n/string))) + (is (= sample2 (-> sample2 p/parse-string n/string))))) + + + (testing "Reader conditional with splice" + (let [sample +"(:require [clojure.string :as s] + #?@(:clj [[clj-time.format :as tf] + [clj-time.coerce :as tc]] + :cljs [[cljs-time.coerce :as tc] + [cljs-time.format :as tf]]))" + res (p/parse-string sample)] + (is (= sample (n/string res)))))) + diff --git a/src/cljs-lib/test/pez_rewrite_clj/paredit_test.cljs b/src/cljs-lib/test/pez_rewrite_clj/paredit_test.cljs new file mode 100644 index 0000000..0ea22f7 --- /dev/null +++ b/src/cljs-lib/test/pez_rewrite_clj/paredit_test.cljs @@ -0,0 +1,560 @@ +(ns pez-rewrite-clj.paredit-test + (:require [cljs.test :refer-macros [deftest is testing run-tests]] + [pez-rewrite-clj.zip :as z] + [clojure.zip :as zz] + [pez-rewrite-clj.paredit :as pe])) + + + +;; helper +(defn move-n [loc f n] + (->> loc (iterate f) (take n) last)) + + +(deftest kill-to-end-of-sexpr + (let [res (-> "[1 2 3 4]" + z/of-string + z/down zz/right + pe/kill)] + (is (= "[1]" (-> res z/root-string))) + (is (= "1" (-> res z/string))))) + +(deftest kill-to-end-of-line + (let [res (-> "[1 2] ; useless comment" + z/of-string + zz/right + pe/kill)] + (is (= "[1 2]" (-> res z/root-string))) + (is (= "[1 2]" (-> res z/string))))) + +(deftest kill-to-wipe-all-sexpr-contents + (let [res (-> "[1 2 3 4]" + z/of-string + z/down + pe/kill)] + (is (= "[]" (-> res z/root-string))) + (is (= "[]" (-> res z/string))))) + +(deftest kill-to-wipe-all-sexpr-contents-in-nested-seq + (let [res (-> "[[1 2 3 4]]" + z/of-string + z/down + pe/kill)] + (is (= "[]" (-> res z/root-string))) + (is (= "[]" (-> res z/string))))) + +(deftest kill-when-left-is-sexpr + (let [res (-> "[1 2 3 4] 2" + z/of-string + zz/right + pe/kill)] + (is (= "[1 2 3 4]" (-> res z/root-string))) + (is (= "[1 2 3 4]" (-> res z/string))))) + +(deftest kill-it-all + (let [res (-> "[1 2 3 4] 5" + z/of-string + pe/kill)] + (is (= "" (-> res z/root-string))) + (is (= "" (-> res z/string))))) + + + +(deftest kill-at-pos-when-in-empty-seq + (let [res (-> "[] 5" + z/of-string + (pe/kill-at-pos {:row 1 :col 2}))] + (is (= "5" (-> res z/root-string))) + (is (= "5" (-> res z/string))))) + + +(deftest kill-inside-comment + (is (= "; dill" (-> "; dilldall" + z/of-string + (pe/kill-at-pos {:row 1 :col 7}) + z/root-string)))) + +(deftest kill-at-pos-when-string + (let [res (-> "(str \"Hello \" \"World!\")" + z/of-string + z/down + (pe/kill-at-pos {:row 1 :col 9}))] + (is (= "(str \"He\" \"World!\")" (-> res z/root-string))))) + + +(deftest kill-at-pos-when-string-multiline + (let [sample "(str \" +First line + Second Line + Third Line + \")" + expected "(str \" +First line + Second\")" + + res (-> sample + z/of-string + z/down + (pe/kill-at-pos {:row 3 :col 9}))] + (is (= expected (-> res z/root-string))))) + + + + +(deftest kill-at-pos-multiline-aligned + (let [sample " +(println \"Hello + There + World\")"] + (is (= "\n(println \"Hello\")" (-> sample + z/of-string + (pe/kill-at-pos {:row 2 :col 16}) + (z/root-string)))))) + + + +(deftest kill-at-pos-when-empty-string + (is (= "" (-> (z/of-string "\"\"") (pe/kill-at-pos {:row 1 :col 1}) z/root-string)))) + + + +(deftest kill-one-at-pos + (let [sample "[10 20 30]"] + (is (= "[10 30]" + (-> (z/of-string sample) + (pe/kill-one-at-pos {:row 1 :col 4}) ; at whitespace + z/root-string))) + (is (= "[10 30]" + (-> (z/of-string sample) + (pe/kill-one-at-pos {:row 1 :col 5}) + z/root-string))))) + +(deftest kill-one-at-pos-new-zloc-is-left-node + (let [sample "[[10] 20 30]"] + (is (= "[10]" + (-> (z/of-string sample) + (pe/kill-one-at-pos {:row 1 :col 6}) + z/string))) + (is (= "[10]" + (-> (z/of-string sample) + (pe/kill-one-at-pos {:row 1 :col 7}) + z/string))))) + +(deftest kill-one-at-pos-keep-linebreaks + (let [sample (z/of-string "[10\n 20\n 30]")] + (is (= "[20\n 30]" + (-> sample (pe/kill-one-at-pos {:row 1 :col 2}) z/root-string))) + (is (= "[10\n 30]" + (-> sample (pe/kill-one-at-pos {:row 2 :col 1}) z/root-string))) + (is (= "[10\n 20]" + (-> sample (pe/kill-one-at-pos {:row 3 :col 1}) z/root-string))))) + +(deftest kill-one-at-pos-in-comment + (let [sample (z/of-string "; hello world")] + (is (= "; hello " + (-> (pe/kill-one-at-pos sample {:row 1 :col 8}) z/root-string))) + (is (= "; hello " + (-> (pe/kill-one-at-pos sample {:row 1 :col 9}) z/root-string))) + (is (= "; hello " + (-> (pe/kill-one-at-pos sample {:row 1 :col 13}) z/root-string))) + (is (= "; world" + (-> (pe/kill-one-at-pos sample {:row 1 :col 2}) z/root-string))))) + +(deftest kill-one-at-pos-in-string + (let [sample (z/of-string "\"hello world\"")] + (is (= "\"hello \"" + (-> (pe/kill-one-at-pos sample {:row 1 :col 7}) z/root-string))) + (is (= "\"hello \"" + (-> (pe/kill-one-at-pos sample {:row 1 :col 8}) z/root-string))) + (is (= "\"hello \"" + (-> (pe/kill-one-at-pos sample {:row 1 :col 12}) z/root-string))) + (is (= "\" world\"" + (-> (pe/kill-one-at-pos sample {:row 1 :col 2}) z/root-string))))) + + +(deftest kill-one-at-pos-in-multiline-string + (let [sample (z/of-string "\"foo bar do\n lorem\"")] + (is (= "\" bar do\n lorem\"" + (-> (pe/kill-one-at-pos sample {:row 1 :col 2}) z/root-string))) + (is (= "\"foo bar do\n \"" + (-> (pe/kill-one-at-pos sample {:row 2 :col 1}) z/root-string))) + (is (= "\"foo bar \n lorem\"" + (-> (pe/kill-one-at-pos sample {:row 1 :col 10}) z/root-string))))) + + + +(deftest slurp-forward-and-keep-loc-rightmost + (let [res (-> "[[1 2] 3 4]" + z/of-string + z/down z/down z/right + pe/slurp-forward)] + (is (= "[[1 2 3] 4]" (-> res z/root-string))) + (is (= "2" (-> res z/string))))) + +(deftest slurp-forward-and-keep-loc-leftmost + (let [res (-> "[[1 2] 3 4]" + z/of-string + z/down z/down + pe/slurp-forward)] + (is (= "[[1 2 3] 4]" (-> res z/root-string))) + (is (= "1" (-> res z/string))))) + +(deftest slurp-forward-from-empty-sexpr + (let [res (-> "[[] 1 2 3]" + z/of-string + z/down + pe/slurp-forward)] + (is (= "[[1] 2 3]" (-> res z/root-string))) + (is (= "1" (-> res z/string))))) + +(deftest slurp-forward-from-whitespace-node + (let [res (-> "[[1 2] 3 4]" + z/of-string + z/down z/down zz/right + pe/slurp-forward)] + (is (= "[[1 2 3] 4]" (-> res z/root-string))) + (is (= " " (-> res z/string))))) + +(deftest slurp-forward-nested + (let [res (-> "[[[1 2]] 3 4]" + z/of-string + z/down z/down z/down + pe/slurp-forward)] + (is (= "[[[1 2] 3] 4]" (-> res z/root-string))) + (is (= "1" (-> res z/string))))) + +(deftest slurp-forward-nested-silly + (let [res (-> "[[[[[1 2]]]] 3 4]" + z/of-string + z/down z/down z/down z/down z/down + pe/slurp-forward)] + (is (= "[[[[[1 2]]] 3] 4]" (-> res z/root-string))) + (is (= "1" (-> res z/string))))) + +(deftest slurp-forward-when-last-is-sexpr + (let [res (-> "[1 [2 [3 4]] 5]" + z/of-string + z/down z/right z/down ;at 2 + pe/slurp-forward)] + (is (= "[1 [2 [3 4] 5]]" (-> res z/root-string)) + (= "2" (-> res z/string))))) + +(deftest slurp-forward-keep-linebreak + (let [sample " +(let [dill] + {:a 1} + {:b 2})" + expected "\n(let [dill \n{:a 1}]\n {:b 2})"] + (is (= expected (-> sample + z/of-string + z/down z/right z/down + pe/slurp-forward + z/root-string))))) + +(deftest slurp-forward-fully + (is (= "[1 [2 3 4]]" (-> (z/of-string "[1 [2] 3 4]") + z/down z/right z/down + pe/slurp-forward-fully + z/root-string)))) + + + +(deftest slurp-backward-and-keep-loc-leftmost + (let [res (-> "[1 2 [3 4]]" + z/of-string + z/down z/rightmost z/down + pe/slurp-backward)] + (is (= "[1 [2 3 4]]" (-> res z/root-string))) + (is (= "3" (-> res z/string))))) + +(deftest slurp-backward-and-keep-loc-rightmost + (let [res (-> "[1 2 [3 4]]" + z/of-string + z/down z/rightmost z/down z/rightmost + pe/slurp-backward)] + (is (= "[1 [2 3 4]]" (-> res z/root-string))) + (is (= "4" (-> res z/string))))) + +(deftest slurp-backward-from-empty-sexpr + (let [res (-> "[1 2 3 4 []]" + z/of-string + z/down z/rightmost + pe/slurp-backward)] + (is (= "[1 2 3 [4]]" (-> res z/root-string))) + (is (= "4" (-> res z/string))))) + +(deftest slurp-backward-nested + (let [res (-> "[1 2 [[3 4]]]" + z/of-string + z/down z/rightmost z/down z/down z/rightmost + pe/slurp-backward)] + (is (= "[1 [2 [3 4]]]" (-> res z/root-string))) + (is (= "4" (-> res z/string))))) + +(deftest slurp-backward-nested-silly + (let [res (-> "[1 2 [[[3 4]]]]" + z/of-string + z/down z/rightmost z/down z/down z/down z/rightmost + pe/slurp-backward)] + (is (= "[1 [2 [[3 4]]]]" (-> res z/root-string))) + (is (= "4" (-> res z/string))))) + +(deftest slurp-backward-keep-linebreaks-and-comments + (let [res (-> "[1 2 ;dill\n [3 4]]" + z/of-string + z/down z/rightmost z/down + pe/slurp-backward)] + (is (= "[1 [2 ;dill\n 3 4]]" (-> res z/root-string))))) + + +(deftest slurp-backward-fully + (is (= "[[1 2 3 4] 5]" (-> (z/of-string "[1 2 3 [4] 5]") + z/down z/rightmost z/left z/down + pe/slurp-backward-fully + z/root-string)))) + + +(deftest barf-forward-and-keep-loc + (let [res (-> "[[1 2 3] 4]" + z/of-string + z/down z/down z/right; position at 2 + pe/barf-forward)] + (is (= "[[1 2] 3 4]" (-> res z/root-string))) + (is (= "2" (-> res z/string))))) + +(deftest barf-forward-at-leftmost + (let [res (-> "[[1 2 3] 4]" + z/of-string + z/down z/down + pe/barf-forward)] + (is (= "[[1 2] 3 4]" (-> res z/root-string))) + (is (= "1" (-> res z/string))))) + + +(deftest barf-forward-at-rightmost-moves-out-of-sexrp + (let [res (-> "[[1 2 3] 4]" + z/of-string + z/down z/down z/rightmost; position at 3 + pe/barf-forward)] + + (is (= "[[1 2] 3 4]" (-> res z/root-string))) + (is (= "3" (-> res z/string))))) + +(deftest barf-forward-at-rightmost-which-is-a-whitespace-haha + (let [res (-> "[[1 2 3 ] 4]" + z/of-string + z/down z/down zz/rightmost; position at space at the end + pe/barf-forward)] + + (is (= "[[1 2] 3 4]" (-> res z/root-string))) + (is (= "3" (-> res z/string))))) + + +(deftest barf-forward-at-when-only-one + (let [res (-> "[[1] 2]" + z/of-string + z/down z/down + pe/barf-forward)] + + (is (= "[[] 1 2]" (-> res z/root-string))) + (is (= "1" (-> res z/string))))) + + + + +(deftest barf-backward-and-keep-current-loc + (let [res (-> "[1 [2 3 4]]" + z/of-string + z/down z/rightmost z/down z/rightmost ; position at 4 + pe/barf-backward)] + (is (= "[1 2 [3 4]]" (-> res z/root-string))) + (is (= "4" (-> res z/string))))) + +(deftest barf-backward-at-leftmost-moves-out-of-sexpr + (let [res (-> "[1 [2 3 4]]" + z/of-string + z/down z/rightmost z/down ; position at 2 + pe/barf-backward)] + (is (= "[1 2 [3 4]]" (-> res z/root-string))) + (is (= "2" (-> res z/string))))) + + +(deftest wrap-around + (is (= "(1)" (-> (z/of-string "1") (pe/wrap-around :list) z/root-string))) + (is (= "[1]" (-> (z/of-string "1") (pe/wrap-around :vector) z/root-string))) + (is (= "{1}" (-> (z/of-string "1") (pe/wrap-around :map) z/root-string))) + (is (= "#{1}" (-> (z/of-string "1") (pe/wrap-around :set) z/root-string)))) + +(deftest wrap-around-keeps-loc + (let [res (-> "1" + z/of-string + (pe/wrap-around :list))] + (is (= "1" (-> res z/string))))) + +(deftest wrap-around-keeps-newlines + (is (= "[[1]\n 2]" (-> (z/of-string "[1\n 2]") z/down (pe/wrap-around :vector) z/root-string)))) + + + +(deftest wrap-around-fn + (is (= "(-> (#(+ 1 1)))" (-> (z/of-string "(-> #(+ 1 1))") + z/down z/right + (pe/wrap-around :list) + z/root-string)))) + + +(deftest wrap-fully-forward-slurp + (is (= "[1 [2 3 4]]" + (-> (z/of-string "[1 2 3 4]") + z/down z/right + (pe/wrap-fully-forward-slurp :vector) + z/root-string)))) + +(deftest splice-killing-backward [] + (let [res (-> (z/of-string "(foo (let ((x 5)) (sqrt n)) bar)") + z/down z/right z/down z/right z/right + pe/splice-killing-backward)] + (is (= "(foo (sqrt n) bar)" (z/root-string res))) + (is (= "(sqrt n)" (z/string res))))) + + +(deftest splice-killing-forward [] + (let [res (-> (z/of-string "(a (b c d e) f)") + z/down z/right z/down z/right z/right + pe/splice-killing-forward)] + (is (= "(a b c f)" (z/root-string res))) + (is (= "c" (z/string res))))) + +(deftest splice-killing-forward-at-leftmost [] + (let [res (-> (z/of-string "(a (b c d e) f)") + z/down z/right z/down + pe/splice-killing-forward)] + (is (= "(a f)" (z/root-string res))) + (is (= "a" (z/string res))))) + + +(deftest split + (let [res (-> "[1 2]" + z/of-string + z/down + pe/split)] + (is (= "[1] [2]" (-> res z/root-string))) + (is (= "1" (-> res z/string))))) + +(deftest split-includes-node-at-loc-as-left + (let [res (-> "[1 2 3 4]" + z/of-string + z/down z/right + pe/split)] + (is (= "[1 2] [3 4]" (-> res z/root-string))) + (is (= "2" (-> res z/string))))) + + +(deftest split-at-whitespace + (let [res (-> "[1 2 3 4]" + z/of-string + z/down z/right zz/right + pe/split)] + (is (= "[1 2] [3 4]" (-> res z/root-string))) + (is (= "2" (-> res z/string))))) + + + + +(deftest split-includes-comments-and-newlines + (let [sexpr " +[1 ;dill + 2 ;dall + 3 ;jalla +]" + expected " +[1 ;dill + 2 ;dall +] [3 ;jalla +]" + res (-> sexpr + z/of-string + z/down z/right + pe/split)] + (is (= expected (-> res z/root-string))) + (is (= "2" (-> res z/string))))) + +(deftest split-when-only-one-returns-self + (is (= "[1]" (-> (z/of-string "[1]") + z/down + pe/split + z/root-string))) + (is (= "[1 ;dill\n]" (-> (z/of-string "[1 ;dill\n]") + z/down + pe/split + z/root-string)))) + + +(deftest split-at-pos-when-string + (is (= "(\"Hello \" \"World\")" (-> (z/of-string "(\"Hello World\")") + (pe/split-at-pos {:row 1 :col 9}) + z/root-string)))) + + +(deftest join-simple + (let [res (-> "[1 2] [3 4]" + z/of-string + ;z/down + zz/right + pe/join)] + (is (= "[1 2 3 4]" (-> res z/root-string))) + (is (= "3" (-> res z/string))))) + +(deftest join-with-comments + (let [sexpr " +[[1 2] ; the first stuff + [3 4] ; the second stuff +]" expected " +[[1 2 ; the first stuff + 3 4]; the second stuff +]" + res (-> sexpr + z/of-string + z/down zz/right + pe/join)] + (is (= expected (-> res z/root-string))))) + + +(deftest join-strings + (is (= "(\"Hello World\")" (-> (z/of-string "(\"Hello \" \"World\")") + z/down z/rightmost + pe/join + z/root-string)))) + + +(deftest raise + (is (= "[1 3]" + (-> (z/of-string "[1 [2 3 4]]") + z/down z/right z/down z/right + pe/raise + z/root-string)))) + + +(deftest move-to-prev-flat + (is (= "(+ 2 1)" (-> "(+ 1 2)" + z/of-string + z/down + z/rightmost + pe/move-to-prev + z/root-string)))) + +(deftest move-to-prev-when-prev-is-seq + (is (= "(+ 1 (+ 2 3 4))" (-> "(+ 1 (+ 2 3) 4)" + z/of-string + z/down + z/rightmost + pe/move-to-prev + z/root-string)))) + +(deftest move-to-prev-out-of-seq + (is (= "(+ 1 4 (+ 2 3))" (-> "(+ 1 (+ 2 3) 4)" + z/of-string + z/down + z/rightmost + (move-n pe/move-to-prev 6) + z/root-string)))) diff --git a/src/cljs-lib/test/pez_rewrite_clj/runner.cljs b/src/cljs-lib/test/pez_rewrite_clj/runner.cljs new file mode 100644 index 0000000..fca6ab6 --- /dev/null +++ b/src/cljs-lib/test/pez_rewrite_clj/runner.cljs @@ -0,0 +1,15 @@ +(ns pez-rewrite-clj.runner + (:require [doo.runner :refer-macros [doo-tests]] + [pez-rewrite-clj.zip-test] + [pez-rewrite-clj.paredit-test] + [pez-rewrite-clj.node-test] + [pez-rewrite-clj.zip.seqz-test] + [pez-rewrite-clj.zip.findz-test] + [pez-rewrite-clj.zip.editz-test])) + +(doo-tests 'pez-rewrite-clj.zip-test + 'pez-rewrite-clj.paredit-test + 'pez-rewrite-clj.node-test + 'pez-rewrite-clj.zip.seqz-test + 'pez-rewrite-clj.zip.findz-test + 'pez-rewrite-clj.zip.editz-test) diff --git a/src/cljs-lib/test/pez_rewrite_clj/zip/editz_test.cljs b/src/cljs-lib/test/pez_rewrite_clj/zip/editz_test.cljs new file mode 100644 index 0000000..2343319 --- /dev/null +++ b/src/cljs-lib/test/pez_rewrite_clj/zip/editz_test.cljs @@ -0,0 +1,14 @@ +(ns pez-rewrite-clj.zip.editz-test + (:require [cljs.test :refer-macros [deftest is testing run-tests]] + [pez-rewrite-clj.zip :as z] + [pez-rewrite-clj.node :as n] + [pez-rewrite-clj.zip.editz :as e])) + + + +(deftest splice + (is (= "[1 2 [3 4]]" (-> "[[1 2] [3 4]]" + z/of-string + z/down + e/splice + z/root-string)))) diff --git a/src/cljs-lib/test/pez_rewrite_clj/zip/findz_test.cljs b/src/cljs-lib/test/pez_rewrite_clj/zip/findz_test.cljs new file mode 100644 index 0000000..6c1ed52 --- /dev/null +++ b/src/cljs-lib/test/pez_rewrite_clj/zip/findz_test.cljs @@ -0,0 +1,46 @@ +(ns pez-rewrite-clj.zip.findz-test + (:require [cljs.test :refer-macros [deftest is testing run-tests]] + [pez-rewrite-clj.zip :as z] + [pez-rewrite-clj.node :as n] + [pez-rewrite-clj.zip.findz :as f])) + + + +(deftest find-last-by-pos + (is (= "2" (-> "[1 2 3]" + z/of-string + (f/find-last-by-pos {:row 1 :col 4} (constantly true)) + z/string)))) + +(deftest find-last-by-pos-when-whitespace + (is (= " " (-> "[1 2 3]" + z/of-string + (f/find-last-by-pos {:row 1 :col 3} (constantly true)) + z/string)))) + + +(deftest find-last-by-pos-multiline + (let [sample " +{:a 1 + :b 2}" ] + (is (= ":a" (-> sample + z/of-string + (f/find-last-by-pos {:row 2 :col 2}) + z/string))) + (is (= "1" (-> sample + z/of-string + (f/find-last-by-pos {:row 2 :col 5}) + z/string))))) + +(deftest find-tag-by-pos + (is (= "[4 5 6]" (-> "[1 2 3 [4 5 6]]" + z/of-string + (f/find-tag-by-pos {:row 1 :col 8} :vector) + z/string)))) + + +(deftest find-tag-by-pos-set + (is (= "#{4 5 6}" (-> "[1 2 3 #{4 5 6}]" + z/of-string + (f/find-tag-by-pos {:row 1 :col 10} :set) + z/string)))) diff --git a/src/cljs-lib/test/pez_rewrite_clj/zip/seqz_test.cljs b/src/cljs-lib/test/pez_rewrite_clj/zip/seqz_test.cljs new file mode 100644 index 0000000..8e594ae --- /dev/null +++ b/src/cljs-lib/test/pez_rewrite_clj/zip/seqz_test.cljs @@ -0,0 +1,33 @@ +(ns pez-rewrite-clj.zip.seqz-test + (:require [cljs.test :refer-macros [deftest is testing run-tests]] + [pez-rewrite-clj.zip :as z] + [pez-rewrite-clj.node :as n] + [pez-rewrite-clj.zip.seqz :as seqz])) + + + +(deftest check-predicates + (is (-> "[1 2 3]" z/of-string z/vector?)) + (is (-> "{:a 1}" z/of-string z/map?)) + (is (-> "#{1 2}" z/of-string z/set?)) + (is (-> "(+ 2 3)" z/of-string z/list?)) + (is (-> "[1 2]" z/of-string z/seq?))) + +(deftest get-from-map + (is (= 1 (-> "{:a 1}" z/of-string (z/get :a) z/node :value)))) + +(deftest get-from-vector + (is (= 10 (-> "[5 10 15]" z/of-string (z/get 1) z/node :value)))) + +(deftest get-from-vector-index-out-of-bounds + (is (thrown-with-msg? js/Error #"Index out of bounds" + (-> "[5 10 15]" z/of-string (z/get 5) z/node :value)))) + +(deftest map-on-vector + (let [sexpr "[1\n2\n3]" + expected "[5\n6\n7]"] + (is (= expected (->> sexpr z/of-string (z/map #(z/edit % + 4)) z/root-string))))) + + +(deftest assoc-on-map + (is (contains? (-> "{:a 1}" z/of-string (z/assoc :b 2) z/node n/sexpr) :b))) diff --git a/src/cljs-lib/test/pez_rewrite_clj/zip_test.cljs b/src/cljs-lib/test/pez_rewrite_clj/zip_test.cljs new file mode 100644 index 0000000..7f79a74 --- /dev/null +++ b/src/cljs-lib/test/pez_rewrite_clj/zip_test.cljs @@ -0,0 +1,37 @@ +(ns pez-rewrite-clj.zip-test + (:require [cljs.test :refer-macros [deftest is testing run-tests]] + [pez-rewrite-clj.zip :as z] + [pez-rewrite-clj.node :as n])) + + +(deftest of-string-simple-sexpr + (let [sexpr "(+ 1 2)"] + (is (= sexpr (-> sexpr z/of-string z/root-string))))) + + + +(deftest manipulate-sexpr + (let [sexpr " + ^{:dynamic true} (+ 1 1 + (+ 2 2) + (reduce + [1 3 4]))" + expected " + ^{:dynamic true} (+ 1 1 + (+ 2 2) + (reduce + [6 7 [1 2]]))"] + (is (= expected (-> sexpr + z/of-string + (z/find-tag-by-pos {:row 4 :col 19} :vector) + (z/replace [5 6 7]) + (z/append-child [1 2]) + z/down + z/remove + z/root-string))))) + + +(deftest namespaced-keywords + (is (= ":dill" (-> ":dill" z/of-string z/root-string))) + (is (= "::dill" (-> "::dill" z/of-string z/root-string))) + (is (= ":dill/dall" (-> ":dill/dall" z/of-string z/root-string))) + (is (= "::dill/dall" (-> "::dill/dall" z/of-string z/root-string))) + (is (= ":%dill.*" (-> ":%dill.*" z/of-string z/root-string)))) diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..ad8b5e0 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,179 @@ +import * as vscode from 'vscode'; +import { customREPLCommandSnippet } from './evaluate'; +// import { ReplConnectSequence } from './nrepl/connectSequence'; +// import { PrettyPrintingOptions } from './printer'; +// import { parseEdn } from '../out/cljs-lib/cljs-lib'; +import { getProjectConfig } from './state'; +import _ = require('lodash'); +// import { isDefined } from './utilities'; + +// const REPL_FILE_EXT = 'calva-repl'; +const KEYBINDINGS_ENABLED_CONFIG_KEY = 'hy.keybindingsEnabled'; +const KEYBINDINGS_ENABLED_CONTEXT_KEY = 'hy:keybindingsEnabled'; + +// type ReplSessionType = 'clj' | 'cljs'; + +// include the 'file' and 'untitled' to the +// document selector. All other schemes are +// not known and therefore not supported. +const documentSelector = [ + { scheme: 'file', language: 'hy' }, + { scheme: 'jar', language: 'hy' }, + { scheme: 'untitled', language: 'hy' }, +]; + +/** + * Trims EDN alias and profile names from any surrounding whitespace or `:` characters. + * This in order to free the user from having to figure out how the name should be entered. + * @param {string} name + * @return {string} The trimmed name + */ +function _trimAliasName(name: string): string { + return name.replace(/^[\s,:]*/, '').replace(/[\s,:]*$/, ''); +} + +// async function readEdnWorkspaceConfig(uri?: vscode.Uri) { +// try { +// let resolvedUri: vscode.Uri; +// const configPath = state.resolvePath('.calva/config.edn'); + +// if (isDefined(uri)) { +// resolvedUri = uri; +// } else if (isDefined(configPath)) { +// resolvedUri = vscode.Uri.file(configPath); +// } else { +// throw new Error('Expected a uri to be passed in or a config to exist at .calva/config.edn'); +// } +// const data = await vscode.workspace.fs.readFile(resolvedUri); +// return addEdnConfig(new TextDecoder('utf-8').decode(data)); +// } catch (error) { +// return error; +// } +// } + +// function mergeSnippets( +// oldSnippets: customREPLCommandSnippet[], +// newSnippets: customREPLCommandSnippet[] +// ): customREPLCommandSnippet[] { +// return newSnippets.concat( +// _.reject( +// oldSnippets, +// (item) => _.findIndex(newSnippets, (newItem) => item.name === newItem.name) !== -1 +// ) +// ); +// } + +/** + * Saves the EDN config in the state to be merged into the actual vsconfig. + * Currently only `:customREPLCommandSnippets` is supported and the `:snippet` has to be a string. + * @param {string} data a string representation of a clojure map + * @returns an error of one was thrown + */ +// function addEdnConfig(data: string) { +// try { +// const parsed = parseEdn(data); +// const old = getProjectConfig(); + +// state.setProjectConfig({ +// customREPLCommandSnippets: mergeSnippets( +// old?.customREPLCommandSnippets ?? [], +// parsed?.customREPLCommandSnippets ?? [] +// ), +// customREPLHoverSnippets: mergeSnippets( +// old?.customREPLHoverSnippets ?? [], +// parsed?.customREPLHoverSnippets ?? [] +// ), +// }); +// } catch (error) { +// return error; +// } +// } +// const watcher = vscode.workspace.createFileSystemWatcher( +// '**/.calva/**/config.edn', +// false, +// false, +// false +// ); + +// watcher.onDidChange((uri: vscode.Uri) => { +// void readEdnWorkspaceConfig(uri); +// }); + +// TODO find a way to validate the configs +function getConfig() { + const configOptions = vscode.workspace.getConfiguration('hy'); + const pareditOptions = vscode.workspace.getConfiguration('hy.paredit'); + + const w = + configOptions.inspect('customREPLCommandSnippets') + ?.workspaceValue ?? []; + const commands = w.concat( + (getProjectConfig()?.customREPLCommandSnippets as customREPLCommandSnippet[]) ?? [] + ); + const hoverSnippets = ( + configOptions.inspect('customREPLHoverSnippets')?.workspaceValue ?? + [] + ).concat((getProjectConfig()?.customREPLHoverSnippets as customREPLCommandSnippet[]) ?? []); + + return { + format: configOptions.get('formatOnSave'), + evaluate: configOptions.get('evalOnSave'), + test: configOptions.get('testOnSave'), + showDocstringInParameterHelp: configOptions.get('showDocstringInParameterHelp'), + jackInEnv: configOptions.get('jackInEnv'), + jackInDependencyVersions: configOptions.get<{ + JackInDependency: string; + }>('jackInDependencyVersions'), + clojureLspVersion: configOptions.get('clojureLspVersion'), + clojureLspPath: configOptions.get('clojureLspPath'), + openBrowserWhenFigwheelStarted: configOptions.get('openBrowserWhenFigwheelStarted'), + customCljsRepl: configOptions.get('customCljsRepl', null), + // replConnectSequences: configOptions.get('replConnectSequences'), + myLeinProfiles: configOptions.get('myLeinProfiles', []).map(_trimAliasName), + myCljAliases: configOptions.get('myCljAliases', []).map(_trimAliasName), + asyncOutputDestination: configOptions.get('sendAsyncOutputTo'), + customREPLCommandSnippets: configOptions.get( + 'customREPLCommandSnippets', + [] + ), + customREPLCommandSnippetsGlobal: + configOptions.inspect('customREPLCommandSnippets')?.globalValue ?? + [], + customREPLCommandSnippetsWorkspace: commands, + customREPLCommandSnippetsWorkspaceFolder: + configOptions.inspect('customREPLCommandSnippets') + ?.workspaceFolderValue ?? [], + customREPLHoverSnippets: hoverSnippets, + // prettyPrintingOptions: configOptions.get('prettyPrintingOptions'), + evaluationSendCodeToOutputWindow: configOptions.get( + 'evaluationSendCodeToOutputWindow' + ), + enableJSCompletions: configOptions.get('enableJSCompletions'), + autoOpenREPLWindow: configOptions.get('autoOpenREPLWindow'), + autoOpenJackInTerminal: configOptions.get('autoOpenJackInTerminal'), + referencesCodeLensEnabled: configOptions.get('referencesCodeLens.enabled'), + hideReplUi: configOptions.get('hideReplUi'), + strictPreventUnmatchedClosingBracket: pareditOptions.get( + 'strictPreventUnmatchedClosingBracket' + ), + showCalvaSaysOnStart: configOptions.get('showCalvaSaysOnStart'), + jackIn: { + useDeprecatedAliasFlag: configOptions.get('jackIn.useDeprecatedAliasFlag'), + }, + enableClojureLspOnStart: configOptions.get('enableClojureLspOnStart'), + projectRootsSearchExclude: configOptions.get('projectRootsSearchExclude', []), + useLiveShare: configOptions.get('useLiveShare'), + definitionProviderPriority: configOptions.get('definitionProviderPriority'), + }; +} + +export { + // readEdnWorkspaceConfig, + // addEdnConfig, + // REPL_FILE_EXT, + KEYBINDINGS_ENABLED_CONFIG_KEY, + KEYBINDINGS_ENABLED_CONTEXT_KEY, + documentSelector, + // ReplSessionType, + getConfig, +}; diff --git a/src/cursor-doc/cdf-edits/hy-lexer.ts b/src/cursor-doc/cdf-edits/hy-lexer.ts new file mode 100644 index 0000000..0b927ad --- /dev/null +++ b/src/cursor-doc/cdf-edits/hy-lexer.ts @@ -0,0 +1,258 @@ +/* eslint-disable no-control-regex */ +/** + * Calva-inspired hy Lexer + * + * 2022-07-06: Borrowed from Calva, original stored in directory above + * + * NB: The lexer tokenizes any combination of clojure quotes, `~`, and `@` prepending a list, symbol, or a literal + * as one token, together with said list, symbol, or literal, even if there is whitespace between the quoting characters. + * All such combos won't actually be accepted by the Clojure Reader, but, hey, we're not writing a Clojure Reader here. 😀 + * See below for the regex used for this. + */ + +// prefixing patterns - TODO: revisit these and see if we can always use the same +// opens ((? ({ type: 'ws' })); +// newlines, we want each one as a token of its own +toplevel.terminal('ws-nl', /(\r|\n|\r\n)/, (l, m) => ({ type: 'ws' })); +// lots of other things are considered whitespace +// https://github.com/sogaiu/tree-sitter-clojure/blob/f8006afc91296b0cdb09bfa04e08a6b3347e5962/grammar.js#L6-L32 +toplevel.terminal( + 'ws-other', + /[\f\u000B\u001C\u001D\u001E\u001F\u2028\u2029\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2008\u2009\u200a\u205f\u3000]+/, + (l, m) => ({ type: 'ws' }) +); +// comments -- 2022-07-06: Updated for hy comment syntax rather than Clojure (`#` instead of `;`) +toplevel.terminal('comment', /#.*$/, (l, m) => ({ type: 'comment' })); +// Calva repl prompt, it contains special colon symbols and a hard space +toplevel.terminal( + 'comment', + // eslint-disable-next-line no-irregular-whitespace + /^[^()[\]{},~@`^"\s;]+꞉[^()[\]{},~@`^"\s;]+꞉> /, + (l, m) => ({ type: 'prompt' }) +); + +// current idea for prefixing data reader +// (#[^\(\)\[\]\{\}"_@~\s,]+[\s,]*)* + +// open parens +toplevel.terminal('open', /(```|((?<=(^|[()[\]{}\s,]))['`~#@?^]\s*)*['`~#@?^]*[([{"])/, (l, m) => ({ + type: 'open', +})); + +// close parens +toplevel.terminal('close', /\)|\]|\}/, (l, m) => ({ type: 'close' })); + +// ignores +toplevel.terminal('ignore', /#_/, (l, m) => ({ type: 'ignore' })); + +// literals +toplevel.terminal('lit-quoted-ws', /\\[\n\r\t ]/, (l, m) => ({ type: 'lit' })); +toplevel.terminal('lit-quoted-chars', /\\.?/, (l, m) => ({ type: 'lit' })); +toplevel.terminal('lit-quoted', /\\[^()[\]{}\s;,\\][^()[\]{}\s;,\\]+/, (l, m) => ({ type: 'lit' })); +toplevel.terminal('lit-quoted-brackets', /\\[()[\]{}]/, (l, m) => ({ + type: 'lit', +})); +toplevel.terminal('lit-symbolic-values', /##[\s,]*(NaN|-?Inf)/, (l, m) => ({ + type: 'lit', +})); +toplevel.terminal('lit-reserved', /(['`~#]\s*)*(true|false|nil)/, (l, m) => ({ + type: 'lit', +})); +toplevel.terminal( + 'lit-integer', + /(['`~#]\s*)*[-+]?(0+|[1-9]+[0-9]*)([rR][0-9a-zA-Z]+|[N])*/, + (l, m) => ({ + type: 'lit', + }) +); +toplevel.terminal( + 'lit-number-sci', + /(['`~#]\s*)*([-+]?(0+[0-9]*|[1-9]+[0-9]*)(\.[0-9]+)?([eE][-+]?[0-9]+)?)M?/, + (l, m) => ({ type: 'lit' }) +); +toplevel.terminal('lit-hex-integer', /(['`~#]\s*)*[-+]?0[xX][0-9a-zA-Z]+/, (l, m) => ({ + type: 'lit', +})); +toplevel.terminal('lit-octal-integer', /(['`~#]\s*)*[-+]?0[0-9]+[nN]?/, (l, m) => ({ + type: 'lit', +})); +toplevel.terminal('lit-ratio', /(['`~#]\s*)*[-+]?\d+\/\d+/, (l, m) => ({ + type: 'lit', +})); + +toplevel.terminal('kw', /(['`~^]\s*)*(:[^()[\]{},~@`^"\s;]*)/, (l, m) => ({ + type: 'kw', +})); + +// data readers +toplevel.terminal('reader', /#[^()[\]{}'"_@~\s,;\\]+/, (_l, _m) => ({ + type: 'reader', +})); + +// symbols, allows quite a lot, but can't start with `#_`, anything numeric, or a selection of chars +// 2022-07-07: Removed ` from first capture group to try and support Long Strings +toplevel.terminal( + 'id', + /(['~#^@]\s*)*(((? ({ type: 'id' }) +); + +// Lexer croaks without this catch-all safe +toplevel.terminal('junk', /[\u0000-\uffff]/, (l, m) => ({ type: 'junk' })); + +/** This is inside-string string grammar. It spits out 'close' once it is time to switch back to the 'toplevel' grammar, + * and 'str-inside' for the words in the string. */ +const inString = new LexicalGrammar(); +// end a string +inString.terminal('close', /"/, (l, m) => ({ type: 'close' })); +// still within a string +// 2022-07-07: Trying to make long strings work too +inString.terminal('str-inside', /(?<="| |```)(\\.|[^"\s`]|`(?!`))+/, (l, m) => ({ + type: 'str-inside', +})); +// whitespace, excluding newlines +inString.terminal('ws', /[\t ]+/, (l, m) => ({ type: 'ws' })); +// newlines, we want each one as a token of its own +inString.terminal('ws-nl', /(\r?\n)/, (l, m) => ({ type: 'ws' })); + +// Lexer can croak on funny data without this catch-all safe: see https://github.com/BetterThanTomorrow/calva/issues/659 +inString.terminal('junk', /[\u0000-\uffff]/, (l, m) => ({ type: 'junk' })); + +/** this is inside-long-string string grammar. It spits out 'close' once it is time to switch back to the 'toplevel grammar, + * and 'long-str-inside' for the words in the string. */ +const inLongString = new LexicalGrammar(); + +inLongString.terminal('comment', /#.*$/, (l, m) => ({ type: 'comment' })); + +inLongString.terminal('close', /```/, (l, m) => ({ type : 'close' })); + +inLongString.terminal('long-str-inside', /(?<="| |```)(\\.|[^"\s`]|`(?!`))+/, (l, m) => ({ + type: 'long-str-inside', + })); + +inLongString.terminal('open', /(```|((?<=(^|[()[\]{}\s,]))['`~#@?^]\s*)*['`~#@?^]*[([{"])/, (l, m) => ({ + type: 'open', +})); + +// whitespace, excluding newlines +inLongString.terminal('ws', /[\t ]+/, (l, m) => ({ type: 'ws' })); +// newlines, we want each one as a token of its own +inLongString.terminal('ws-nl', /(\r?\n)/, (l, m) => ({ type: 'ws' })); + +// Lexer can croak on funny data without this catch-all safe: see https://github.com/BetterThanTomorrow/calva/issues/659 +inLongString.terminal('junk', /[\u0000-\uffff]/, (l, m) => ({ type: 'junk' })); + +/** + * The state of the scanner. + * We only really need to know if we're inside a string or not. + */ +export interface ScannerState { + /** Are we scanning inside a string? If so use inString grammar, otherwise use toplevel. */ + inLongString: boolean; + inString: boolean; +} + +/** + * A Clojure(Script) lexical analyser. + * Takes a line of text and a start state, and returns an array of Token, updating its internal state. + */ +export class Scanner { + state: ScannerState = { inLongString: false, + inString: false }; + + constructor(private maxLength: number) {} + + processLine(line: string, state: ScannerState = this.state) { + const tks: Token[] = []; + this.state = state; + let lex = (this.state.inString ? inString : (this.state.inLongString ? inLongString : toplevel)).lex(line, this.maxLength); + let tk: LexerToken; + do { + tk = lex.scan(); + if (tk) { + const oldpos = lex.position; + if (tk.raw.match(/(```|[~`'@#]*")$/)) { + switch (tk.type) { + case 'open': // string started, switch to inString. + this.state = (tk.raw.match(/```$/) ? { ...this.state, inLongString: true } : { ...this.state, inString: true }); + lex = (tk.raw.match(/```$/) ? inLongString : inString).lex(line, this.maxLength); + lex.position = oldpos; + break; + case 'close': + // string ended, switch back to toplevel + if (tk.raw.match(/```$/)) { + this.state = { ...this.state, inLongString: false } + lex = toplevel.lex(line, this.maxLength); + lex.position = oldpos; + break; + } else if (this.state.inLongString) { + this.state = { ...this.state, inString: false }; + lex = inLongString.lex(line, this.maxLength); + lex.position = oldpos; + break; + } else if (this.state.inString) { + this.state = { ...this.state, inString: false }; + lex = toplevel.lex(line, this.maxLength); + lex.position = oldpos; + break; + } else { + break; + } + } + } + tks.push({ ...tk, state: this.state }); + } + } while (tk); + // Uncomment to observe the lexer's output + // console.log("cursor-doc/cdf-edits/hy-lexer.ts/Scanner/processLine ", tks); + + // insert a sentinel EOL value, this allows us to simplify TokenCaret's implementation. + tks.push({ + type: 'eol', + raw: '\n', + offset: line.length, + state: this.state, + }); + return tks; + } +} diff --git a/src/cursor-doc/clojure-lexer.ts b/src/cursor-doc/clojure-lexer.ts new file mode 100644 index 0000000..6ed535e --- /dev/null +++ b/src/cursor-doc/clojure-lexer.ts @@ -0,0 +1,209 @@ +/* eslint-disable no-control-regex */ +/** + * Calva Clojure Lexer + * + * NB: The lexer tokenizes any combination of clojure quotes, `~`, and `@` prepending a list, symbol, or a literal + * as one token, together with said list, symbol, or literal, even if there is whitespace between the quoting characters. + * All such combos won't actually be accepted by the Clojure Reader, but, hey, we're not writing a Clojure Reader here. 😀 + * See below for the regex used for this. + */ + +// prefixing patterns - TODO: revisit these and see if we can always use the same +// opens ((? ({ type: 'ws' })); +// newlines, we want each one as a token of its own +toplevel.terminal('ws-nl', /(\r|\n|\r\n)/, (l, m) => ({ type: 'ws' })); +// lots of other things are considered whitespace +// https://github.com/sogaiu/tree-sitter-clojure/blob/f8006afc91296b0cdb09bfa04e08a6b3347e5962/grammar.js#L6-L32 +toplevel.terminal( + 'ws-other', + /[\f\u000B\u001C\u001D\u001E\u001F\u2028\u2029\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2008\u2009\u200a\u205f\u3000]+/, + (l, m) => ({ type: 'ws' }) +); +// comments +toplevel.terminal('comment', /;.*/, (l, m) => ({ type: 'comment' })); +// Calva repl prompt, it contains special colon symbols and a hard space +toplevel.terminal( + 'comment', + // eslint-disable-next-line no-irregular-whitespace + /^[^()[\]{},~@`^"\s;]+꞉[^()[\]{},~@`^"\s;]+꞉> /, + (l, m) => ({ type: 'prompt' }) +); + +// current idea for prefixing data reader +// (#[^\(\)\[\]\{\}"_@~\s,]+[\s,]*)* + +// open parens +toplevel.terminal('open', /((?<=(^|[()[\]{}\s,]))['`~#@?^]\s*)*['`~#@?^]*[([{"]/, (l, m) => ({ + type: 'open', +})); + +// close parens +toplevel.terminal('close', /\)|\]|\}/, (l, m) => ({ type: 'close' })); + +// ignores +toplevel.terminal('ignore', /#_/, (l, m) => ({ type: 'ignore' })); + +// literals +toplevel.terminal('lit-quoted-ws', /\\[\n\r\t ]/, (l, m) => ({ type: 'lit' })); +toplevel.terminal('lit-quoted-chars', /\\.?/, (l, m) => ({ type: 'lit' })); +toplevel.terminal('lit-quoted', /\\[^()[\]{}\s;,\\][^()[\]{}\s;,\\]+/, (l, m) => ({ type: 'lit' })); +toplevel.terminal('lit-quoted-brackets', /\\[()[\]{}]/, (l, m) => ({ + type: 'lit', +})); +toplevel.terminal('lit-symbolic-values', /##[\s,]*(NaN|-?Inf)/, (l, m) => ({ + type: 'lit', +})); +toplevel.terminal('lit-reserved', /(['`~#]\s*)*(true|false|nil)/, (l, m) => ({ + type: 'lit', +})); +toplevel.terminal( + 'lit-integer', + /(['`~#]\s*)*[-+]?(0+|[1-9]+[0-9]*)([rR][0-9a-zA-Z]+|[N])*/, + (l, m) => ({ + type: 'lit', + }) +); +toplevel.terminal( + 'lit-number-sci', + /(['`~#]\s*)*([-+]?(0+[0-9]*|[1-9]+[0-9]*)(\.[0-9]+)?([eE][-+]?[0-9]+)?)M?/, + (l, m) => ({ type: 'lit' }) +); +toplevel.terminal('lit-hex-integer', /(['`~#]\s*)*[-+]?0[xX][0-9a-zA-Z]+/, (l, m) => ({ + type: 'lit', +})); +toplevel.terminal('lit-octal-integer', /(['`~#]\s*)*[-+]?0[0-9]+[nN]?/, (l, m) => ({ + type: 'lit', +})); +toplevel.terminal('lit-ratio', /(['`~#]\s*)*[-+]?\d+\/\d+/, (l, m) => ({ + type: 'lit', +})); + +toplevel.terminal('kw', /(['`~^]\s*)*(:[^()[\]{},~@`^"\s;]*)/, (l, m) => ({ + type: 'kw', +})); + +// data readers +toplevel.terminal('reader', /#[^()[\]{}'"_@~\s,;\\]+/, (_l, _m) => ({ + type: 'reader', +})); + +// symbols, allows quite a lot, but can't start with `#_`, anything numeric, or a selection of chars +toplevel.terminal( + 'id', + /(['`~#^@]\s*)*(((? ({ type: 'id' }) +); + +// Lexer croaks without this catch-all safe +toplevel.terminal('junk', /[\u0000-\uffff]/, (l, m) => ({ type: 'junk' })); + +/** This is inside-string string grammar. It spits out 'close' once it is time to switch back to the 'toplevel' grammar, + * and 'str-inside' for the words in the string. */ +const inString = new LexicalGrammar(); +// end a string +inString.terminal('close', /"/, (l, m) => ({ type: 'close' })); +// still within a string +inString.terminal('str-inside', /(\\.|[^"\s])+/, (l, m) => ({ + type: 'str-inside', +})); +// whitespace, excluding newlines +inString.terminal('ws', /[\t ]+/, (l, m) => ({ type: 'ws' })); +// newlines, we want each one as a token of its own +inString.terminal('ws-nl', /(\r?\n)/, (l, m) => ({ type: 'ws' })); + +// Lexer can croak on funny data without this catch-all safe: see https://github.com/BetterThanTomorrow/calva/issues/659 +inString.terminal('junk', /[\u0000-\uffff]/, (l, m) => ({ type: 'junk' })); + +/** + * The state of the scanner. + * We only really need to know if we're inside a string or not. + */ +export interface ScannerState { + /** Are we scanning inside a string? If so use inString grammar, otherwise use toplevel. */ + inString: boolean; +} + +/** + * A Clojure(Script) lexical analyser. + * Takes a line of text and a start state, and returns an array of Token, updating its internal state. + */ +export class Scanner { + state: ScannerState = { inString: false }; + + constructor(private maxLength: number) {} + + processLine(line: string, state: ScannerState = this.state) { + const tks: Token[] = []; + this.state = state; + let lex = (this.state.inString ? inString : toplevel).lex(line, this.maxLength); + let tk: LexerToken; + do { + tk = lex.scan(); + if (tk) { + const oldpos = lex.position; + if (tk.raw.match(/[~`'@#]*"$/)) { + switch (tk.type) { + case 'open': // string started, switch to inString. + this.state = { ...this.state, inString: true }; + lex = inString.lex(line, this.maxLength); + lex.position = oldpos; + break; + case 'close': + // string ended, switch back to toplevel + this.state = { ...this.state, inString: false }; + lex = toplevel.lex(line, this.maxLength); + lex.position = oldpos; + break; + } + } + tks.push({ ...tk, state: this.state }); + } + } while (tk); + // insert a sentinel EOL value, this allows us to simplify TokenCaret's implementation. + tks.push({ + type: 'eol', + raw: '\n', + offset: line.length, + state: this.state, + }); + return tks; + } +} diff --git a/src/cursor-doc/cursor-context.ts b/src/cursor-doc/cursor-context.ts new file mode 100644 index 0000000..1f8771d --- /dev/null +++ b/src/cursor-doc/cursor-context.ts @@ -0,0 +1,93 @@ +import { EditableDocument } from './model'; + +export const allCursorContexts = [ + 'hy:cursorInString', + 'hy:cursorInComment', + 'hy:cursorAtStartOfLine', + 'hy:cursorAtEndOfLine', + 'hy:cursorBeforeComment', + 'hy:cursorAfterComment', +] as const; + +export type CursorContext = typeof allCursorContexts[number]; + +/** + * Returns true if documentOffset is either at the first char of the token under the cursor, or + * in the whitespace between the token and the first preceding EOL, otherwise false + */ +export function isAtLineStartInclWS(doc: EditableDocument, offset = doc.selection.active) { + const tokenCursor = doc.getTokenCursor(offset); + let startOfLine = false; + // only at start if we're in ws, or at the 1st char of a non-ws sexp + if (tokenCursor.getToken().type === 'ws' || tokenCursor.offsetStart >= offset) { + while (tokenCursor.getPrevToken().type === 'ws') { + tokenCursor.previous(); + } + startOfLine = tokenCursor.getPrevToken().type === 'eol'; + } + + return startOfLine; +} + +/** + * Returns true if position is after the last char of the last lisp token on the line, including + * any trailing whitespace or EOL, otherwise false + */ +export function isAtLineEndInclWS(doc: EditableDocument, offset = doc.selection.active) { + const tokenCursor = doc.getTokenCursor(offset); + if (tokenCursor.getToken().type === 'eol') { + return true; + } + if (tokenCursor.getPrevToken().type === 'eol' && tokenCursor.getToken().type !== 'ws') { + return false; + } + if (tokenCursor.getToken().type === 'ws') { + tokenCursor.next(); + if (tokenCursor.getToken().type !== 'eol') { + return false; + } + tokenCursor.previous(); + } + tokenCursor.forwardWhitespace(); + const textFromOffset = doc.model.getText(offset, tokenCursor.offsetStart); + if (textFromOffset.match(/^\s+/)) { + return true; + } + return false; +} + +export function determineContexts( + doc: EditableDocument, + offset = doc.selection.active +): CursorContext[] { + const tokenCursor = doc.getTokenCursor(offset); + const contexts: CursorContext[] = []; + + if (isAtLineStartInclWS(doc)) { + contexts.push('hy:cursorAtStartOfLine'); + } else if (isAtLineEndInclWS(doc)) { + contexts.push('hy:cursorAtEndOfLine'); + } + + if (tokenCursor.withinString()) { + contexts.push('hy:cursorInString'); + } else if (tokenCursor.withinComment()) { + contexts.push('hy:cursorInComment'); + } + + // Compound contexts + if (contexts.includes('hy:cursorInComment')) { + if (contexts.includes('hy:cursorAtEndOfLine')) { + tokenCursor.forwardWhitespace(false); + if (tokenCursor.getToken().type != 'comment') { + contexts.push('hy:cursorAfterComment'); + } + } else if (contexts.includes('hy:cursorAtStartOfLine')) { + tokenCursor.backwardWhitespace(false); + if (tokenCursor.getPrevToken().type != 'comment') { + contexts.push('hy:cursorBeforeComment'); + } + } + } + return contexts; +} diff --git a/src/cursor-doc/indent.ts b/src/cursor-doc/indent.ts new file mode 100644 index 0000000..a9f0d81 --- /dev/null +++ b/src/cursor-doc/indent.ts @@ -0,0 +1,202 @@ +import { EditableModel } from './model'; +import * as _ from 'lodash'; +import { InlineCompletionTriggerKind } from 'vscode'; + +const whitespace = new Set(['ws', 'comment', 'eol']); + +export type IndentRule = ['block', number] | ['inner', number] | ['inner', number, number]; + +export type IndentRules = { + [id: string]: IndentRule[]; +}; + +const indentRules: IndentRules = { + '#"^\\w"': [['inner', 0]], +}; + +/** + * The information about an enclosing s-expr, returned by collectIndents + */ +export interface IndentInformation { + /** The first token in the expression (after the open paren/bracket etc.), as a raw string */ + first: string; + + /** The indent immediately after the open paren/bracket etc */ + startIndent: number; + + /** If there is a second token on the same line as the first token, the indent for that token */ + firstItemIdent: number; + + /** The applicable indent rules for this IndentInformation, local only. */ + rules: IndentRule[]; + + /** The index at which the cursor (or the sexpr containing the cursor at this level) is in the expression. */ + argPos: number; + + /** The number of expressions on the first line of this expression. */ + exprsOnLine: number; +} + +/** + * Analyses the text before position in the document, and returns a list of enclosing expression information with + * various indent information, for use with getIndent() + * + * @param document The document to analyse + * @param position The position (as [row, col] into the document to analyse from) + * @param maxDepth The maximum depth upwards from the expression to search. + * @param maxLines The maximum number of lines above the position to search until we bail with an imprecise answer. + */ +export function collectIndents( + document: EditableModel, + offset: number, + config: any, + maxDepth: number = 3, + maxLines: number = 20 +): IndentInformation[] { + const cursor = document.getTokenCursor(offset); + cursor.backwardWhitespace(); + let argPos = 0; + const startLine = cursor.line; + let exprsOnLine = 0; + let lastLine = cursor.line; + let lastIndent = 0; + const indents: IndentInformation[] = []; + const rules = config['cljfmt-options']['indents']; + do { + // console.log("If not cursor.backwardSexp, go into If block ", !cursor.backwardSexp()) + if (!cursor.backwardSexp()) { + // this needs some work.. + // console.log("Set prevToken to ", cursor.getPrevToken()) + const prevToken = cursor.getPrevToken(); + if (prevToken.type == 'open' && prevToken.offset <= 1) { + maxDepth = 0; // treat an sexpr starting on line 0 sensibly. + } + // skip past the first item and record the indent of the first item on the same line if there is one. + const nextCursor = cursor.clone(); + nextCursor.forwardSexp(); + nextCursor.forwardWhitespace(); + + // if the first item of this list is a a function, and the second item is on the same line, indent to that second item. otherwise indent to the open paren. + const isList = prevToken.type === 'open' && prevToken.raw.endsWith('('); + const firstItemIdent = + ['id', 'kw'].includes(cursor.getToken().type) && + nextCursor.line == cursor.line && + !nextCursor.atEnd() && + isList + ? nextCursor.rowCol[1] + : cursor.rowCol[1]; + + // console.log("firstItemIdent ", firstItemIdent); + + const token = cursor.getToken().raw; + const startIndent = cursor.rowCol[1]; + if (!cursor.backwardUpList()) { + break; + } + + const pattern = + isList && + _.find(_.keys(rules), (pattern) => pattern === token || testCljRe(pattern, token)); + const indentRule = pattern ? rules[pattern] : []; + indents.unshift({ + first: token, + rules: indentRule, + argPos, + exprsOnLine, + startIndent, + firstItemIdent, + }); + argPos = 0; + exprsOnLine = 1; + } + + if (cursor.line != lastLine) { + const head = cursor.clone(); + head.forwardSexp(); + head.forwardWhitespace(); + if (!head.atEnd()) { + lastIndent = head.rowCol[1]; + exprsOnLine = 0; + lastLine = cursor.line; + } + } + + if (whitespace.has(cursor.getPrevToken().type)) { + argPos++; + exprsOnLine++; + } + } while ( + !cursor.atStart() && + Math.abs(startLine - cursor.line) < maxLines && + indents.length < maxDepth + ); + if (!indents.length) { + indents.push({ + argPos: 0, + first: null, + rules: [], + exprsOnLine: 0, + startIndent: lastIndent >= 0 ? lastIndent : 0, + firstItemIdent: lastIndent >= 0 ? lastIndent : 0, + }); + } + // console.log("src/cursor-doc/indent.ts/collectIndents ", indents); + return indents; +} + +const testCljRe = (re, str) => { + const matches = re.match(/^#"(.*)"$/); + return matches && RegExp(matches[1]).test(str); +}; + +/** Returns the expected newline indent for the given position, in characters. */ +export function getIndent(document: EditableModel, offset: number, config?: any): number { + if (!config) { + config = { + 'cljfmt-options': { + indents: indentRules, + }, + }; + } + const state = collectIndents(document, offset, config); + // now find applicable indent rules + let indent = -1; + const thisBlock = state[state.length - 1]; + if (!state.length) { + return 0; + } + + for (let pos = state.length - 1; pos >= 0; pos--) { + for (const rule of state[pos].rules) { + if (rule[0] == 'inner') { + if (pos + rule[1] == state.length - 1) { + if (rule.length == 3) { + if (rule[2] > thisBlock.argPos) { + indent = thisBlock.startIndent + 1; + } + } else { + indent = thisBlock.startIndent + 1; + } + } + } else if (rule[0] == 'block' && pos == state.length - 1) { + if (thisBlock.exprsOnLine <= rule[1]) { + if (thisBlock.argPos >= rule[1]) { + indent = thisBlock.startIndent + 1; + } + } else { + indent = thisBlock.firstItemIdent; + } + } + } + } + + if (indent == -1) { + // no indentation styles applied, so use default style. + if (thisBlock.exprsOnLine > 0) { + indent = thisBlock.firstItemIdent; + } else { + indent = thisBlock.startIndent; + } + } + return indent; +} diff --git a/src/cursor-doc/lexer.ts b/src/cursor-doc/lexer.ts new file mode 100644 index 0000000..4da61f1 --- /dev/null +++ b/src/cursor-doc/lexer.ts @@ -0,0 +1,106 @@ +/** + * A Lexical analyser + * @module lexer + */ + +/** + * The base Token class. Contains the token type, + * the raw string of the token, and the offset into the input line. + */ +export interface Token { + type: string; + raw: string; + offset: number; +} + +/** + * A Lexical rule for a terminal. Consists of a RegExp and an action. + */ +export interface Rule { + name: string; + r: RegExp; + fn: (Lexer, RegExpExecArray) => any; +} + +/** + * A Lexer instance, parsing a given file. Usually you should use a LexicalGrammar to + * create one of these. + * + * @class + * @param {string} source the source code to parse + * @param rules the rules of this lexer. + */ + +export class Lexer { + position: number = 0; + constructor(public source: string, public rules: Rule[], private maxLength) {} + + /** Returns the next token in this lexer, or null if at the end. If the match fails, throws an Error. */ + scan(): Token { + let token = null, + length = 0; + if (this.position < this.source.length) { + if (this.source !== undefined && this.source.length < this.maxLength) { + // TODO: Consider using vscode setting for tokenisation max length + this.rules.forEach((rule) => { + rule.r.lastIndex = this.position; + const x = rule.r.exec(this.source); + if (x && x[0].length > length && this.position + x[0].length == rule.r.lastIndex) { + token = rule.fn(this, x); + token.offset = this.position; + token.raw = x[0]; + length = x[0].length; + } + }); + } else { + length = this.source.length; + token = { + type: 'too-long-line', + offset: this.position, + raw: this.source, + }; + } + } + this.position += length; + if (token == null) { + if (this.position == this.source.length) { + return null; + } + throw new Error( + 'Unexpected character at ' + this.position + ': ' + JSON.stringify(this.source) + ); + } + return token; + } +} + +/** + * A lexical grammar- factory for lexer instances. + * @class + */ +export class LexicalGrammar { + rules: Rule[] = []; + + /** + * Defines a terminal with the given pattern and constructor. + * @param {string | RegExp} pattern the pattern this terminal must match. + * @param {function(Array): Object} fn returns a lexical token representing + * this terminal. An additional "offset" property containing the token source position + * will also be added, as well as a "raw" property, containing the raw string match. + */ + terminal(name: string, pattern: string | RegExp, fn: (T, RegExpExecArray) => any): void { + this.rules.push({ + name, + // This is b/c the RegExp constructor seems to not like our union type (unknown reasons why) + r: pattern instanceof RegExp ? new RegExp(pattern, 'g') : new RegExp(pattern, 'g'), + fn: fn, + }); + } + + /** + * Create a Lexer for the given input. + */ + lex(source: string, maxLength): Lexer { + return new Lexer(source, this.rules, maxLength); + } +} diff --git a/src/cursor-doc/model.ts b/src/cursor-doc/model.ts new file mode 100644 index 0000000..3f57b55 --- /dev/null +++ b/src/cursor-doc/model.ts @@ -0,0 +1,604 @@ +import { Scanner, Token, ScannerState } from './cdf-edits/hy-lexer'; +import { LispTokenCursor } from './token-cursor'; +// import { deepEqual as equal } from '../util/object'; +import { isUndefined } from 'lodash'; + +let scanner: Scanner; + +function equal(x: any, y: any): boolean { + if (x == y) { + return true; + } + if (x instanceof Array && y instanceof Array) { + if (x.length == y.length) { + for (let i = 0; i < x.length; i++) { + if (!equal(x[i], y[i])) { + return false; + } + } + return true; + } else { + return false; + } + } else if ( + !(x instanceof Array) && + !(y instanceof Array) && + x instanceof Object && + y instanceof Object + ) { + for (const f in x) { + if (!equal(x[f], y[f])) { + return false; + } + } + for (const f in y) { + if (!Object.prototype.hasOwnProperty.call(x, f)) { + return false; + } + } + return true; + } + return false; +} + +export function initScanner(maxLength: number) { + scanner = new Scanner(maxLength); +} + +export class TextLine { + tokens: Token[] = []; + text: string; + endState: ScannerState; + constructor(text: string, public startState: ScannerState) { + this.text = text; + this.tokens = scanner.processLine(text); + this.endState = { ...scanner.state }; + } + + processLine(oldState: any) { + this.startState = { ...oldState }; + this.tokens = scanner.processLine(this.text, oldState); + this.endState = { ...scanner.state }; + } +} + +export type ModelEditFunction = 'insertString' | 'changeRange' | 'deleteRange'; + +export class ModelEdit { + constructor(public editFn: ModelEditFunction, public args: any[]) {} +} + +/** + * Naming notes for Model Selections: + * `anchor`, the start of a selection, can be left or right of, or the same as the end of the selection (active) + * `active`, the end of a selection, where the caret is, can be left or right of, or the same as the start of the selection (anchor) + * `left`, the smallest of `anchor` and `active` + * `right`, the largest of `anchor` and `active` + * `backward`, movement towards the left + * `forward`, movement towards the right + * `up`, movement out of lists + * `down`, movement into lists + * + * This will be in line with vscode when it comes to anchor/active, but introduce our own terminology for the span of the selection. It will also keep the tradition of paredit with backward/forward and up/down. + */ + +export class ModelEditSelection { + private _anchor: number; + private _active: number; + + constructor(anchor: number, active?: number) { + this._anchor = anchor; + if (active !== undefined) { + this._active = active; + } else { + this._active = anchor; + } + } + + get anchor() { + return this._anchor; + } + + set anchor(v: number) { + this._anchor = v; + } + + get active() { + return this._active; + } + + set active(v: number) { + this._active = v; + } + + clone() { + return new ModelEditSelection(this._anchor, this._active); + } +} + +export type ModelEditOptions = { + undoStopBefore?: boolean; + formatDepth?: number; + skipFormat?: boolean; + selection?: ModelEditSelection; +}; + +export interface EditableModel { + readonly lineEndingLength: number; + + /** + * Performs a model edit batch. + * For some EditableModel's these are performed as one atomic set of edits. + * @param edits + */ + edit: (edits: ModelEdit[], options: ModelEditOptions) => Thenable; + + getText: (start: number, end: number, mustBeWithin?: boolean) => string; + getLineText: (line: number) => string; + getOffsetForLine: (line: number) => number; + getTokenCursor: (offset: number, previous?: boolean) => LispTokenCursor; +} + +export interface EditableDocument { + selection: ModelEditSelection; + model: EditableModel; + selectionStack: ModelEditSelection[]; + getTokenCursor: (offset?: number, previous?: boolean) => LispTokenCursor; + insertString: (text: string) => void; + getSelectionText: () => string; + delete: () => Thenable; + backspace: () => Thenable; +} + +/** The underlying model for the REPL readline. */ +export class LineInputModel implements EditableModel { + /** How many characters in the line endings of the text of this model? */ + constructor(readonly lineEndingLength: number = 1, private document?: EditableDocument) {} + + /** The input lines. */ + lines: TextLine[] = [new TextLine('', this.getStateForLine(0))]; + + /** Lines whose text has changed. */ + changedLines: Set = new Set(); + + /** Lines which must be inserted. */ + insertedLines: Set<[number, number]> = new Set(); + + /** Lines which must be deleted. */ + deletedLines: Set<[number, number]> = new Set(); + + /** When set, insertString and deleteRange will be added to the undo history. */ + recordingUndo: boolean = false; + + /** Lines which must be re-lexed. */ + dirtyLines: number[] = []; + + private updateLines(start: number, deleted: number, inserted: number) { + const delta = inserted - deleted; + + this.dirtyLines = this.dirtyLines + .filter((x) => x < start || x >= start + deleted) + .map((x) => (x >= start ? x + delta : x)); + + this.changedLines = new Set( + Array.from(this.changedLines) + .map((x) => { + if (x > start && x < start + deleted) { + return null; + } + if (x >= start) { + return x + delta; + } + return x; + }) + .filter((x) => x !== null) + ); + + this.insertedLines = new Set( + Array.from(this.insertedLines) + .map((x): [number, number] => { + const [a, b] = x; + if (a > start && a < start + deleted) { + return null; + } + if (a >= start) { + return [a + delta, b]; + } + return [a, b]; + }) + .filter((x) => x !== null) + ); + + this.deletedLines = new Set( + Array.from(this.deletedLines) + .map((x): [number, number] => { + const [a, b] = x; + if (a > start && a < start + deleted) { + return null; + } + if (a >= start) { + return [a + delta, b]; + } + return [a, b]; + }) + .filter((x) => x !== null) + ); + } + + private deleteLines(start: number, count: number) { + if (count == 0) { + return; + } + this.updateLines(start, count, 0); + this.deletedLines.add([start, count]); + } + + private insertLines(start: number, count: number) { + this.updateLines(start, 0, count); + this.insertedLines.add([start, count]); + } + + /** + * Mark a line as needing to be re-lexed. + * + * @param idx the index of the line which needs re-lexing (0-based) + */ + private markDirty(idx: number) { + if (idx >= 0 && idx < this.lines.length && this.dirtyLines.indexOf(idx) == -1) { + this.dirtyLines.push(idx); + } + } + + /** + * Re-lexes all lines marked dirty, cascading onto the lines below if the end state for this line has + * changed. + */ + flushChanges() { + if (!this.dirtyLines.length) { + return; + } + const seen = new Set(); + this.dirtyLines.sort(); + while (this.dirtyLines.length) { + let nextIdx = this.dirtyLines.shift(); + if (seen.has(nextIdx)) { + continue; + } // already processed. + let prevState = this.getStateForLine(nextIdx); + do { + seen.add(nextIdx); + this.changedLines.add(nextIdx); + this.lines[nextIdx].processLine(prevState); + prevState = this.lines[nextIdx].endState; + } while (this.lines[++nextIdx] && !equal(this.lines[nextIdx].startState, prevState)); + } + } + + /** + * Returns the character offset in the model to the start of a given line. + * + * @param line the line who's offset will be returned. + */ + getOffsetForLine(line: number) { + let max = 0; + for (let i = 0; i < line; i++) { + max += this.lines[i].text.length + this.lineEndingLength; + } + return max; + } + + /** + * Returns the text of the given line + * + * @param line the line to get the text of + */ + getLineText(line: number): string { + return this.lines[line].text; + } + + /** + * Returns the text between start and end as a string. These may be in any order. + * + * @param start the start offset in the text range + * @param end the end offset in the text range + * @param mustBeWithin if the start or end are outside the document, returns "" + */ + getText(start: number, end: number, mustBeWithin = false): string { + if (start == end) { + return ''; + } + if (mustBeWithin && (Math.min(start, end) < 0 || Math.max(start, end) > this.maxOffset)) { + return ''; + } + const st = this.getRowCol(Math.min(start, end)); + const en = this.getRowCol(Math.max(start, end)); + + const lines: string[] = []; + if (st[0] == en[0]) { + lines[0] = this.lines[st[0]].text.substring(st[1], en[1]); + } else { + lines[0] = this.lines[st[0]].text.substring(st[1]); + } + for (let i = st[0] + 1; i < en[0]; i++) { + lines.push(this.lines[i].text); + } + if (st[0] != en[0]) { + lines.push(this.lines[en[0]].text.substring(0, en[1])); + } + return lines.join('\n'); + } + + /** + * Returns the row and column for a given text offset in this model. + */ + getRowCol(offset: number): [number, number] { + for (let i = 0; i < this.lines.length; i++) { + if (offset > this.lines[i].text.length) { + offset -= this.lines[i].text.length + this.lineEndingLength; + } else { + return [i, offset]; + } + } + return [this.lines.length - 1, this.lines[this.lines.length - 1].text.length]; + } + + /** + * Returns the start and end offset of the word found for the given offset in + * the model. + * + * @param offset The offset in the line model. + * @returns [number, number] The start and the index of the word in the model. + */ + getWordSelection(offset: number): [number, number] { + const stopChars = [' ', '"', ';', '.', '(', ')', '[', ']', '{', '}', '\t', '\n', '\r'], + [row, column] = this.getRowCol(offset), + text = this.lines[row].text; + + if (text && text.length > 1 && column < text.length && column >= 0) { + if (stopChars.includes(text[column])) { + return [offset, offset]; + } + let stopIdx = column; + let startIdx = column; + for (let i = column; i >= 0; i--) { + if (stopChars.includes(text[i])) { + break; + } + startIdx = i; + } + for (let j = column; j < text.length; j++) { + if (stopChars.includes(text[j])) { + break; + } + stopIdx = j; + } + return [offset - (column - startIdx), offset + (stopIdx - column) + 1]; + } + return [offset, offset]; + } + + /** + * Returns the initial lexer state for a given line. + * Line 0 is always { inString: false }, all lines below are equivalent to their previous line's startState. + * + * @param line the line to retrieve the lexer state. + */ + private getStateForLine(line: number): ScannerState { + return line == 0 ? { inString: false, inLongString: false } : { ...this.lines[line - 1].endState }; + } + + /** + * Performs a model edit batch. + * Doesn't need to be atomic in the LineInputModel. + * @param edits + */ + edit(edits: ModelEdit[], options: ModelEditOptions): Thenable { + return new Promise((resolve, reject) => { + for (const edit of edits) { + switch (edit.editFn) { + case 'insertString': { + const fn = this.insertString; + this.insertString(...(edit.args.slice(0, 4) as Parameters)); + break; + } + case 'changeRange': { + const fn = this.changeRange; + this.changeRange(...(edit.args.slice(0, 5) as Parameters)); + break; + } + case 'deleteRange': { + const fn = this.deleteRange; + this.deleteRange(...(edit.args.slice(0, 5) as Parameters)); + break; + } + default: + break; + } + } + if (this.document && options.selection) { + this.document.selection = options.selection; + } + resolve(true); + }); + } + + /** + * Changes the model. Deletes any text between `start` and `end`, and the inserts `text`. + * + * If provided, `oldSelection` and `newSelection` are used to manage the cursor positioning for undo support. + * + * @param start the start offset in the range to delete + * @param end the end offset in the range to delete + * @param text the new text to insert + * @param oldSelection the old selection + * @param newSelection the new selection + */ + private changeRange( + start: number, + end: number, + text: string, + oldSelection?: [number, number], + newSelection?: [number, number] + ) { + const t1 = new Date(); + + const startPos = Math.min(start, end); + const endPos = Math.max(start, end); + const deletedText = this.recordingUndo ? this.getText(startPos, endPos) : ''; + const [startLine, startCol] = this.getRowCol(startPos); + const [endLine, endCol] = this.getRowCol(endPos); + // extract the lines we will replace + const replaceLines = text.split(/\r\n|\n/); + + // the left side of the line unaffected by the edit. + const left = this.lines[startLine].text.substr(0, startCol); + + // the right side of the line unaffected by the edit. + const right = this.lines[endLine].text.substr(endCol); + + const items: TextLine[] = []; + + // initialize the lexer state - the first line is definitely not in a string, otherwise copy the + // end state of the previous line before the edit + const state = this.getStateForLine(startLine); + const currentLength = endLine - startLine + 1; + + if (replaceLines.length == 1) { + // trivial single line edit + items.push(new TextLine(left + replaceLines[0] + right, state)); + } else { + // multi line edit. + items.push(new TextLine(left + replaceLines[0], state)); + for (let i = 1; i < replaceLines.length - 1; i++) { + items.push(new TextLine(replaceLines[i], scanner.state)); + } + items.push(new TextLine(replaceLines[replaceLines.length - 1] + right, scanner.state)); + } + + if (currentLength > replaceLines.length) { + // shrink the lines + this.deleteLines(startLine + replaceLines.length, currentLength - replaceLines.length); + } else if (currentLength < replaceLines.length) { + // extend the lines + this.insertLines(endLine, replaceLines.length - currentLength); + } + + // now splice in our edited lines + this.lines.splice(startLine, endLine - startLine + 1, ...items); + + // set the changed and dirty marker + for (let i = 0; i < items.length; i++) { + this.changedLines.add(startLine + i); + this.markDirty(startLine + i); + } + + // console.log("Parsing took: ", new Date().valueOf() - t1.valueOf()); + } + + /** + * Inserts a string at the given position in the document. + * + * If recordingUndo is set, an UndoStep is inserted into the undoManager, which will record the original + * cursor position. + * + * @param offset the offset to insert at + * @param text the text to insert + * @param oldCursor the [row,col] of the cursor at the start of the operation + */ + insertString( + offset: number, + text: string, + oldSelection?: [number, number], + newSelection?: [number, number] + ): number { + this.changeRange(offset, offset, text, oldSelection, newSelection); + return text.length; + } + + /** + * Deletes count characters starting at offset from the document. + * If recordingUndo is set, adds an undoStep, using oldCursor and newCursor. + * + * @param offset the offset to delete from + * @param count the number of characters to delete + * @param oldCursor the cursor at the start of the operation + * @param newCursor the cursor at the end of the operation + */ + deleteRange( + offset: number, + count: number, + oldSelection?: [number, number], + newSelection?: [number, number] + ) { + this.changeRange(offset, offset + count, '', oldSelection, newSelection); + } + + /** Return the offset of the last character in this model. */ + get maxOffset() { + let max = 0; + for (let i = 0; i < this.lines.length; i++) { + max += this.lines[i].text.length + this.lineEndingLength; + } + return max - 1; + } + + public getTokenCursor(offset: number, previous: boolean = false) { + const [row, col] = this.getRowCol(offset); + const line = this.lines[row]; + let lastIndex = 0; + if (line) { + for (let i = 0; i < line.tokens.length; i++) { + const tk = line.tokens[i]; + if (previous ? tk.offset > col : tk.offset > col) { + return new LispTokenCursor(this, row, previous ? Math.max(0, lastIndex - 1) : lastIndex); + } + lastIndex = i; + } + return new LispTokenCursor(this, row, line.tokens.length - 1); + } else { + throw new Error('Unable to get token cursor for LineInputModel!'); + } + } +} + +export class StringDocument implements EditableDocument { + constructor(contents?: string) { + if (contents) { + this.insertString(contents); + } + } + + selection: ModelEditSelection; + + model: LineInputModel = new LineInputModel(1, this); + + selectionStack: ModelEditSelection[] = []; + + getTokenCursor(offset?: number, previous?: boolean): LispTokenCursor { + if (isUndefined(offset)) { + throw new Error('Expected a cursor for StringDocument!'); + } + + return this.model.getTokenCursor(offset); + } + + insertString(text: string) { + this.model.insertString(0, text); + } + + getSelectionText: () => string; + + delete() { + const p = this.selection.anchor; + return this.model.edit([new ModelEdit('deleteRange', [p, 1])], { + selection: new ModelEditSelection(p), + }); + } + + backspace() { + const p = this.selection.anchor; + return this.model.edit([new ModelEdit('deleteRange', [p - 1, 1])], { + selection: new ModelEditSelection(p - 1), + }); + } +} diff --git a/src/cursor-doc/paredit.ts b/src/cursor-doc/paredit.ts new file mode 100644 index 0000000..e471d6d --- /dev/null +++ b/src/cursor-doc/paredit.ts @@ -0,0 +1,1561 @@ +import { validPair } from './cdf-edits/hy-lexer'; +import { getIndent } from './indent'; +import { ModelEdit, EditableDocument, ModelEditSelection } from './model'; +import { LispTokenCursor } from './token-cursor'; + +// NB: doc.model.edit returns a Thenable, so that the vscode Editor can compose commands. +// But don't put such chains in this module because that won't work in the repl-console. +// In the repl-console, compose commands just by performing them in succession, making sure +// you provide selections, old and new. + +// TODO: Implement all movement and selection commands here, instead of composing them +// exactly the same way in the editor and in the repl-window. +// Example: paredit.moveToRangeRight(this.readline, paredit.forwardSexpRange(this.readline)) +// => paredit.moveForwardSexp(this.readline) + +export async function killRange( + doc: EditableDocument, + range: [number, number], + start = doc.selection.anchor, + end = doc.selection.active +) { + const [left, right] = [Math.min(...range), Math.max(...range)]; + return doc.model.edit([new ModelEdit('deleteRange', [left, right - left, [start, end]])], { + selection: new ModelEditSelection(left), + }); +} + +export function moveToRangeLeft(doc: EditableDocument, range: [number, number]) { + doc.selection = new ModelEditSelection(Math.min(range[0], range[1])); +} + +export function moveToRangeRight(doc: EditableDocument, range: [number, number]) { + doc.selection = new ModelEditSelection(Math.max(range[0], range[1])); +} + +export function selectRange(doc: EditableDocument, range: [number, number]) { + growSelectionStack(doc, range); +} + +export function selectRangeForward(doc: EditableDocument, range: [number, number]) { + const selectionLeft = doc.selection.anchor; + const rangeRight = Math.max(range[0], range[1]); + growSelectionStack(doc, [selectionLeft, rangeRight]); +} + +export function selectRangeBackward(doc: EditableDocument, range: [number, number]) { + const selectionRight = doc.selection.anchor; + const rangeLeft = Math.min(range[0], range[1]); + growSelectionStack(doc, [selectionRight, rangeLeft]); +} + +export function selectForwardSexp(doc: EditableDocument) { + const rangeFn = + doc.selection.active >= doc.selection.anchor + ? forwardSexpRange + : (doc: EditableDocument) => forwardSexpRange(doc, doc.selection.active, true); + selectRangeForward(doc, rangeFn(doc)); +} + +export function selectRight(doc: EditableDocument) { + const rangeFn = + doc.selection.active >= doc.selection.anchor + ? forwardHybridSexpRange + : (doc: EditableDocument) => forwardHybridSexpRange(doc, doc.selection.active, true); + selectRangeForward(doc, rangeFn(doc)); +} + +export function selectForwardSexpOrUp(doc: EditableDocument) { + const rangeFn = + doc.selection.active >= doc.selection.anchor + ? forwardSexpOrUpRange + : (doc: EditableDocument) => forwardSexpOrUpRange(doc, doc.selection.active, true); + + selectRangeForward(doc, rangeFn(doc)); +} + +export function selectBackwardSexp(doc: EditableDocument) { + const rangeFn = + doc.selection.active <= doc.selection.anchor + ? backwardSexpRange + : (doc: EditableDocument) => backwardSexpRange(doc, doc.selection.active, false); + selectRangeBackward(doc, rangeFn(doc)); +} + +export function selectForwardDownSexp(doc: EditableDocument) { + const rangeFn = + doc.selection.active >= doc.selection.anchor + ? (doc: EditableDocument) => rangeToForwardDownList(doc, doc.selection.active, true) + : (doc: EditableDocument) => rangeToForwardDownList(doc, doc.selection.active, true); + selectRangeForward(doc, rangeFn(doc)); +} + +export function selectBackwardDownSexp(doc: EditableDocument) { + selectRangeBackward(doc, rangeToBackwardDownList(doc)); +} + +export function selectForwardUpSexp(doc: EditableDocument) { + selectRangeForward(doc, rangeToForwardUpList(doc, doc.selection.active)); +} + +export function selectBackwardUpSexp(doc: EditableDocument) { + const rangeFn = + doc.selection.active <= doc.selection.anchor + ? (doc: EditableDocument) => rangeToBackwardUpList(doc, doc.selection.active, false) + : (doc: EditableDocument) => rangeToBackwardUpList(doc, doc.selection.active, false); + selectRangeBackward(doc, rangeFn(doc)); +} + +export function selectBackwardSexpOrUp(doc: EditableDocument) { + const rangeFn = + doc.selection.active <= doc.selection.anchor + ? (doc: EditableDocument) => backwardSexpOrUpRange(doc, doc.selection.active, false) + : (doc: EditableDocument) => backwardSexpOrUpRange(doc, doc.selection.active, false); + selectRangeBackward(doc, rangeFn(doc)); +} + +export function selectCloseList(doc: EditableDocument) { + selectRangeForward(doc, rangeToForwardList(doc, doc.selection.active)); +} + +export function selectOpenList(doc: EditableDocument) { + selectRangeBackward(doc, rangeToBackwardList(doc)); +} + +/** + * Gets the range for the ”current” top level form + * @see ListTokenCursor.rangeForDefun + */ +export function rangeForDefun( + doc: EditableDocument, + offset: number = doc.selection.active, + commentCreatesTopLevel = true +): [number, number] { + const cursor = doc.getTokenCursor(offset); + return cursor.rangeForDefun(offset, commentCreatesTopLevel); +} + +/** + * Required : If the cursor can move up and out of an sexp, it must + * Never : If the cursor is at the inner limit of an sexp, it may not escape + * WhenAtLimit : If the cursor is at the inner limit of an sexp, it may move up and out + */ +enum GoUpSexpOption { + Required, + Never, + WhenAtLimit, +} + +/** + * Return a modified selection range on doc. Moves the right limit around sexps, potentially moving up. + */ +function _forwardSexpRange( + doc: EditableDocument, + offset = Math.max(doc.selection.anchor, doc.selection.active), + goUpSexp: GoUpSexpOption, + goPastWhitespace = false +): [number, number] { + const cursor = doc.getTokenCursor(offset); + + if (goUpSexp == GoUpSexpOption.Never || goUpSexp == GoUpSexpOption.WhenAtLimit) { + // Normalize our position by scooting to the beginning of the closest sexp + cursor.forwardWhitespace(); + + if (cursor.forwardSexp(true, true)) { + if (goPastWhitespace) { + cursor.forwardWhitespace(); + } + return [offset, cursor.offsetStart]; + } + } + + if (goUpSexp == GoUpSexpOption.Required || goUpSexp == GoUpSexpOption.WhenAtLimit) { + cursor.forwardList(); + if (cursor.upList()) { + if (goPastWhitespace) { + cursor.forwardWhitespace(); + } + return [offset, cursor.offsetStart]; + } + } + return [offset, offset]; +} + +/** + * Return a modified selection range on doc. Moves the left limit around sexps, potentially moving up. + */ +function _backwardSexpRange( + doc: EditableDocument, + offset: number = Math.min(doc.selection.anchor, doc.selection.active), + goUpSexp: GoUpSexpOption, + goPastWhitespace = false +): [number, number] { + const cursor = doc.getTokenCursor(offset); + + if (goUpSexp == GoUpSexpOption.Never || goUpSexp == GoUpSexpOption.WhenAtLimit) { + if (!cursor.isWhiteSpace() && cursor.offsetStart < offset) { + // This is because cursor.backwardSexp() can't move backwards when "on" the first sexp inside a list + // TODO: Try to fix this in LispTokenCursor instead. + cursor.forwardSexp(); + } + cursor.backwardWhitespace(); + + if (cursor.backwardSexp(true, true)) { + if (goPastWhitespace) { + cursor.backwardWhitespace(); + } + return [cursor.offsetStart, offset]; + } + } + + if (goUpSexp == GoUpSexpOption.Required || goUpSexp == GoUpSexpOption.WhenAtLimit) { + cursor.backwardList(); + if (cursor.backwardUpList()) { + cursor.forwardSexp(true, true); + cursor.backwardSexp(true, true); + if (goPastWhitespace) { + cursor.backwardWhitespace(); + } + return [cursor.offsetStart, offset]; + } + } + + return [offset, offset]; +} + +export function forwardSexpRange( + doc: EditableDocument, + offset = Math.max(doc.selection.anchor, doc.selection.active), + goPastWhitespace = false +): [number, number] { + return _forwardSexpRange(doc, offset, GoUpSexpOption.Never, goPastWhitespace); +} + +export function backwardSexpRange( + doc: EditableDocument, + offset: number = Math.min(doc.selection.anchor, doc.selection.active), + goPastWhitespace = false +): [number, number] { + return _backwardSexpRange(doc, offset, GoUpSexpOption.Never, goPastWhitespace); +} + +export function forwardListRange( + doc: EditableDocument, + start: number = doc.selection.active +): [number, number] { + const cursor = doc.getTokenCursor(start); + cursor.forwardList(); + return [start, cursor.offsetStart]; +} + +export function backwardListRange( + doc: EditableDocument, + start: number = doc.selection.active +): [number, number] { + const cursor = doc.getTokenCursor(start); + cursor.backwardList(); + return [cursor.offsetStart, start]; +} + +/** + * Aims to find the end of the current form (list|vector|map|set|string etc) + * When there is a newline before the end of the current form either: + * - Return the end of the nearest form to the right of the cursor location if one exists + * - Returns the newline's offset if no form exists + * + * This function's output range is needed to implement features similar to paredit's + * killRight or smartparens' sp-kill-hybrid-sexp. + * + * @param doc + * @param offset + * @param goPastWhitespace + * @returns [number, number] + */ +export function forwardHybridSexpRange( + doc: EditableDocument, + offset = Math.max(doc.selection.anchor, doc.selection.active), + goPastWhitespace = false +): [number, number] { + let cursor = doc.getTokenCursor(offset); + if (cursor.getToken().type === 'open') { + return forwardSexpRange(doc); + } else if (cursor.getToken().type === 'close') { + return [offset, offset]; + } + + const currentLineText = doc.model.getLineText(cursor.line); + const lineStart = doc.model.getOffsetForLine(cursor.line); + const currentLineNewlineOffset = lineStart + currentLineText.length; + const remainderLineText = doc.model.getText(offset, currentLineNewlineOffset + 1); + + cursor.forwardList(); // move to the end of the current form + const currentFormEndToken = cursor.getToken(); + // when we've advanced the cursor but start is behind us then go to the end + // happens when in a clojure comment i.e: ;; ---- + const cursorOffsetEnd = cursor.offsetStart <= offset ? cursor.offsetEnd : cursor.offsetStart; + const text = doc.model.getText(offset, cursorOffsetEnd); + let hasNewline = text.indexOf('\n') > -1; + let end = cursorOffsetEnd; + + // Want the min of closing token or newline + // After moving forward, the cursor is not yet at the end of the current line, + // and it is not a close token. So we include the newline + // because what forms are here extend beyond the end of the current line + if (currentLineNewlineOffset > cursor.offsetEnd && currentFormEndToken.type != 'close') { + hasNewline = true; + end = currentLineNewlineOffset; + } + + if (remainderLineText === '' || remainderLineText === '\n') { + end = currentLineNewlineOffset + doc.model.lineEndingLength; + } else if (hasNewline) { + // Try to find the first open token to the right of the document's cursor location if any + let nearestOpenTokenOffset = -1; + + // Start at the newline. + // Work backwards to find the smallest open token offset + // greater than the document's cursor location if any + cursor = doc.getTokenCursor(currentLineNewlineOffset); + while (cursor.offsetStart > offset) { + while (cursor.backwardSexp()) { + // move backward until the cursor cannot move backward anymore + } + if (cursor.offsetStart > offset) { + nearestOpenTokenOffset = cursor.offsetStart; + cursor = doc.getTokenCursor(cursor.offsetStart - 1); + } + } + + if (nearestOpenTokenOffset > 0) { + cursor = doc.getTokenCursor(nearestOpenTokenOffset); + cursor.forwardList(); + end = cursor.offsetEnd; // include the closing token + } else { + // no open tokens found so the end is the newline + end = currentLineNewlineOffset; + } + } + return [offset, end]; +} + +export function rangeToForwardUpList( + doc: EditableDocument, + offset: number = Math.max(doc.selection.anchor, doc.selection.active), + goPastWhitespace = false +): [number, number] { + return _forwardSexpRange(doc, offset, GoUpSexpOption.Required, goPastWhitespace); +} + +export function rangeToBackwardUpList( + doc: EditableDocument, + offset: number = Math.min(doc.selection.anchor, doc.selection.active), + goPastWhitespace = false +): [number, number] { + return _backwardSexpRange(doc, offset, GoUpSexpOption.Required, goPastWhitespace); +} + +export function forwardSexpOrUpRange( + doc: EditableDocument, + offset = Math.max(doc.selection.anchor, doc.selection.active), + goPastWhitespace = false +): [number, number] { + return _forwardSexpRange(doc, offset, GoUpSexpOption.WhenAtLimit, goPastWhitespace); +} + +export function backwardSexpOrUpRange( + doc: EditableDocument, + offset: number = Math.min(doc.selection.anchor, doc.selection.active), + goPastWhitespace = false +): [number, number] { + return _backwardSexpRange(doc, offset, GoUpSexpOption.WhenAtLimit, goPastWhitespace); +} + +export function rangeToForwardDownList( + doc: EditableDocument, + offset: number = Math.max(doc.selection.anchor, doc.selection.active), + goPastWhitespace = false +): [number, number] { + const cursor = doc.getTokenCursor(offset); + if (cursor.downListSkippingMeta()) { + if (goPastWhitespace) { + cursor.forwardWhitespace(); + } + return [offset, cursor.offsetStart]; + } else { + return [offset, offset]; + } +} + +export function rangeToBackwardDownList( + doc: EditableDocument, + offset: number = Math.min(doc.selection.anchor, doc.selection.active), + goPastWhitespace = false +): [number, number] { + const cursor = doc.getTokenCursor(offset); + do { + cursor.backwardWhitespace(); + if (cursor.getPrevToken().type === 'close') { + break; + } + } while (cursor.backwardSexp()); + if (cursor.backwardDownList()) { + if (goPastWhitespace) { + cursor.backwardWhitespace(); + } + return [cursor.offsetStart, offset]; + } else { + return [offset, offset]; + } +} + +export function rangeToForwardList( + doc: EditableDocument, + offset: number = Math.max(doc.selection.anchor, doc.selection.active) +): [number, number] { + const cursor = doc.getTokenCursor(offset); + if (cursor.forwardList()) { + return [offset, cursor.offsetStart]; + } else { + return [offset, offset]; + } +} + +export function rangeToBackwardList( + doc: EditableDocument, + offset: number = Math.min(doc.selection.anchor, doc.selection.active) +): [number, number] { + const cursor = doc.getTokenCursor(offset); + if (cursor.backwardList()) { + return [cursor.offsetStart, offset]; + } else { + return [offset, offset]; + } +} + +export async function wrapSexpr( + doc: EditableDocument, + open: string, + close: string, + start: number = doc.selection.anchor, + end: number = doc.selection.active, + options = { skipFormat: false } +) { + const cursor = doc.getTokenCursor(end); + if (cursor.withinString() && open == '"') { + open = close = '\\"'; + } + if (start == end) { + // No selection + const currentFormRange = cursor.rangeForCurrentForm(start); + if (currentFormRange) { + const range = currentFormRange; + return doc.model.edit( + [ + new ModelEdit('insertString', [range[1], close]), + new ModelEdit('insertString', [ + range[0], + open, + [end, end], + [start + open.length, start + open.length], + ]), + ], + { + selection: new ModelEditSelection(start + open.length), + skipFormat: options.skipFormat, + } + ); + } + } else { + // there is a selection + const range = [Math.min(start, end), Math.max(start, end)]; + return doc.model.edit( + [ + new ModelEdit('insertString', [range[1], close]), + new ModelEdit('insertString', [range[0], open]), + ], + { + selection: new ModelEditSelection(start + open.length), + skipFormat: options.skipFormat, + } + ); + } +} + +export async function rewrapSexpr( + doc: EditableDocument, + open: string, + close: string, + start: number = doc.selection.anchor, + end: number = doc.selection.active +): Promise> { + const cursor = doc.getTokenCursor(end); + if (cursor.backwardList()) { + const openStart = cursor.offsetStart - 1, + openEnd = cursor.offsetStart; + if (cursor.forwardList()) { + const closeStart = cursor.offsetStart, + closeEnd = cursor.offsetEnd; + return doc.model.edit( + [ + new ModelEdit('changeRange', [closeStart, closeEnd, close]), + new ModelEdit('changeRange', [openStart, openEnd, open]), + ], + { selection: new ModelEditSelection(end) } + ); + } + } +} + +export async function splitSexp(doc: EditableDocument, start: number = doc.selection.active) { + const cursor = doc.getTokenCursor(start); + if (!cursor.withinString() && !(cursor.isWhiteSpace() || cursor.previousIsWhiteSpace())) { + cursor.forwardWhitespace(); + } + const splitPos = cursor.withinString() ? start : cursor.offsetStart; + if (cursor.backwardList()) { + const open = cursor.getPrevToken().raw; + if (cursor.forwardList()) { + const close = cursor.getToken().raw; + return doc.model.edit( + [new ModelEdit('changeRange', [splitPos, splitPos, `${close}${open}`])], + { + selection: new ModelEditSelection(splitPos + 1), + } + ); + } + } +} + +/** + * If `start` is between two strings or two lists of the same type: join them. Otherwise do nothing. + * @param doc + * @param start + */ +export async function joinSexp( + doc: EditableDocument, + start: number = doc.selection.active +): Promise> { + const cursor = doc.getTokenCursor(start); + cursor.backwardWhitespace(); + const prevToken = cursor.getPrevToken(), + prevEnd = cursor.offsetStart; + if (['close', 'str-end', 'str'].includes(prevToken.type)) { + cursor.forwardWhitespace(); + const nextToken = cursor.getToken(), + nextStart = cursor.offsetStart; + if (validPair(nextToken.raw[0], prevToken.raw[prevToken.raw.length - 1])) { + return doc.model.edit( + [ + new ModelEdit('changeRange', [ + prevEnd - 1, + nextStart + 1, + prevToken.type === 'close' ? ' ' : '', + [start, start], + [prevEnd, prevEnd], + ]), + ], + { selection: new ModelEditSelection(prevEnd), formatDepth: 2 } + ); + } + } +} + +export async function spliceSexp( + doc: EditableDocument, + start: number = doc.selection.active, + undoStopBefore = true +): Promise> { + const cursor = doc.getTokenCursor(start); + // TODO: this should unwrap the string, not the enclosing list. + + cursor.backwardList(); + const open = cursor.getPrevToken(); + const beginning = cursor.offsetStart; + if (open.type == 'open') { + cursor.forwardList(); + const close = cursor.getToken(); + const end = cursor.offsetStart; + if (close.type == 'close' && validPair(open.raw, close.raw)) { + return doc.model.edit( + [ + new ModelEdit('changeRange', [end, end + close.raw.length, '']), + new ModelEdit('changeRange', [beginning - open.raw.length, beginning, '']), + ], + { undoStopBefore, selection: new ModelEditSelection(start - 1) } + ); + } + } +} + +export async function killBackwardList(doc: EditableDocument, [start, end]: [number, number]) { + return doc.model.edit( + [new ModelEdit('changeRange', [start, end, '', [end, end], [start, start]])], + { + selection: new ModelEditSelection(start), + } + ); +} + +export async function killForwardList(doc: EditableDocument, [start, end]: [number, number]) { + const cursor = doc.getTokenCursor(start); + const inComment = + (cursor.getToken().type == 'comment' && start > cursor.offsetStart) || + cursor.getPrevToken().type == 'comment'; + return doc.model.edit( + [ + new ModelEdit('changeRange', [ + start, + end, + inComment ? '\n' : '', + [start, start], + [start, start], + ]), + ], + { selection: new ModelEditSelection(start) } + ); +} + +export async function forwardSlurpSexp( + doc: EditableDocument, + start: number = doc.selection.active, + extraOpts = { formatDepth: 1 } +) { + const cursor = doc.getTokenCursor(start); + cursor.forwardList(); + if (cursor.getToken().type == 'close') { + const currentCloseOffset = cursor.offsetStart; + const close = cursor.getToken().raw; + const wsInsideCursor = cursor.clone(); + wsInsideCursor.backwardWhitespace(false); + const wsStartOffset = wsInsideCursor.offsetStart; + cursor.upList(); + const wsOutSideCursor = cursor.clone(); + if (cursor.forwardSexp(true, true)) { + wsOutSideCursor.forwardWhitespace(false); + const wsEndOffset = wsOutSideCursor.offsetStart; + const newCloseOffset = cursor.offsetStart; + const replacedText = doc.model.getText(wsStartOffset, wsEndOffset); + const changeArgs = + replacedText.indexOf('\n') >= 0 + ? [currentCloseOffset, currentCloseOffset + close.length, ''] + : [wsStartOffset, wsEndOffset, ' ']; + return doc.model.edit( + [ + new ModelEdit('insertString', [newCloseOffset, close]), + new ModelEdit('changeRange', changeArgs), + ], + { + ...{ + undoStopBefore: true, + }, + ...extraOpts, + } + ); + } else { + const formatDepth = extraOpts['formatDepth'] ? extraOpts['formatDepth'] : 1; + return forwardSlurpSexp(doc, cursor.offsetStart, { + formatDepth: formatDepth + 1, + }); + } + } +} + +export async function backwardSlurpSexp( + doc: EditableDocument, + start: number = doc.selection.active, + extraOpts = {} +) { + const cursor = doc.getTokenCursor(start); + cursor.backwardList(); + const tk = cursor.getPrevToken(); + if (tk.type == 'open') { + const offset = cursor.clone().previous().offsetStart; + const open = cursor.getPrevToken().raw; + cursor.previous(); + cursor.backwardSexp(true, true); + cursor.forwardWhitespace(false); + if (offset !== cursor.offsetStart) { + return doc.model.edit( + [ + new ModelEdit('deleteRange', [offset, tk.raw.length]), + new ModelEdit('changeRange', [cursor.offsetStart, cursor.offsetStart, open]), + ], + { + ...{ + undoStopBefore: true, + }, + ...extraOpts, + } + ); + } else { + const formatDepth = extraOpts['formatDepth'] ? extraOpts['formatDepth'] : 1; + return backwardSlurpSexp(doc, cursor.offsetStart, { + formatDepth: formatDepth + 1, + }); + } + } +} + +export async function forwardBarfSexp(doc: EditableDocument, start: number = doc.selection.active) { + const cursor = doc.getTokenCursor(start); + cursor.forwardList(); + if (cursor.getToken().type == 'close') { + const offset = cursor.offsetStart, + close = cursor.getToken().raw; + cursor.backwardSexp(true, true); + cursor.backwardWhitespace(); + return doc.model.edit( + [ + new ModelEdit('deleteRange', [offset, close.length]), + new ModelEdit('insertString', [cursor.offsetStart, close]), + ], + start >= cursor.offsetStart + ? { + selection: new ModelEditSelection(cursor.offsetStart), + formatDepth: 2, + } + : { formatDepth: 2 } + ); + } +} + +export async function backwardBarfSexp( + doc: EditableDocument, + start: number = doc.selection.active +) { + const cursor = doc.getTokenCursor(start); + cursor.backwardList(); + const tk = cursor.getPrevToken(); + if (tk.type == 'open') { + cursor.previous(); + const offset = cursor.offsetStart; + const close = cursor.getToken().raw; + cursor.next(); + cursor.forwardSexp(true, true); + cursor.forwardWhitespace(false); + return doc.model.edit( + [ + new ModelEdit('changeRange', [cursor.offsetStart, cursor.offsetStart, close]), + new ModelEdit('deleteRange', [offset, tk.raw.length]), + ], + start <= cursor.offsetStart + ? { + selection: new ModelEditSelection(cursor.offsetStart), + formatDepth: 2, + } + : { formatDepth: 2 } + ); + } +} + +export function open( + doc: EditableDocument, + open: string, + close: string, + start: number = doc.selection.active +) { + const [cs, ce] = [doc.selection.anchor, doc.selection.active]; + doc.insertString(open + doc.getSelectionText() + close); + if (cs != ce) { + doc.selection = new ModelEditSelection(cs + open.length, ce + open.length); + } else { + doc.selection = new ModelEditSelection(start + open.length); + } +} + +function docIsBalanced(doc: EditableDocument, start: number = doc.selection.active): boolean { + const cursor = doc.getTokenCursor(0); + while (cursor.forwardSexp(true, true, true)) { + // move forward until the cursor cannot move forward anymore + } + cursor.forwardWhitespace(true); + return cursor.atEnd(); +} + +export async function close( + doc: EditableDocument, + close: string, + start: number = doc.selection.active +) { + console.log("cursor-doc/paredit.ts/close triggered") + const cursor = doc.getTokenCursor(start); + const inString = cursor.withinString(); + cursor.forwardWhitespace(false); + if (cursor.getToken().raw === close) { + doc.selection = new ModelEditSelection(cursor.offsetEnd); + } else { + if (!inString && docIsBalanced(doc)) { + // Do nothing when there is balance + } else { + return doc.model.edit([new ModelEdit('insertString', [start, close])], { + selection: new ModelEditSelection(start + close.length), + }); + } + } +} + +function onlyWhitespaceLeftOfCursor(doc: EditableDocument, cursor: LispTokenCursor) { + const token = cursor.getToken(); + if (token.type === 'ws') { + return token.offset === 0; + } else if (doc.selection.anchor > cursor.offsetStart) { + return false; + } + const prevToken = cursor.getPrevToken(); + + return prevToken.type === 'ws' && prevToken.offset === 0; +} + +function backspaceOnWhitespaceEdit(doc: EditableDocument, cursor: LispTokenCursor) { + const origIndent = getIndent(doc.model, cursor.offsetStart); + const onCloseToken = cursor.getToken().type === 'close'; + let start = doc.selection.anchor; + let token = cursor.getToken(); + if (token.type === 'ws') { + start = cursor.offsetEnd; + } + cursor.previous(); + const prevToken = cursor.getToken(); + if (prevToken.type === 'ws' && start === cursor.offsetEnd) { + token = prevToken; + } + + let end = start; + if (token.type === 'ws') { + end = cursor.offsetStart; + cursor.previous(); + if (cursor.getToken().type === 'eol') { + end = cursor.offsetStart; + cursor.previous(); + if (cursor.getToken().type === 'ws') { + end = cursor.offsetStart; + cursor.previous(); + } + } + } + + const destTokenType = cursor.getToken().type; + let indent = destTokenType === 'eol' ? origIndent : 1; + if (destTokenType === 'open' || onCloseToken) { + indent = 0; + } + const changeArgs = [start, end, ' '.repeat(indent)]; + return doc.model.edit([new ModelEdit('changeRange', changeArgs)], { + selection: new ModelEditSelection(end + indent), + skipFormat: true, + }); +} + +export async function backspace( + doc: EditableDocument, + start: number = doc.selection.anchor, + end: number = doc.selection.active +): Promise { + if (start != end) { + return doc.backspace(); + } else { + const cursor = doc.getTokenCursor(start); + const nextToken = cursor.getToken(); + const p = start; + const prevToken = + p > cursor.offsetStart && !['open', 'close'].includes(nextToken.type) + ? nextToken + : cursor.getPrevToken(); + if (prevToken.type == 'prompt') { + return new Promise((resolve) => resolve(true)); + } else if (nextToken.type == 'prompt') { + return new Promise((resolve) => resolve(true)); + } else if (doc.model.getText(p - 2, p, true) == '\\"') { + return doc.model.edit([new ModelEdit('deleteRange', [p - 2, 2])], { + selection: new ModelEditSelection(p - 2), + }); + } else if (prevToken.type === 'open' && nextToken.type === 'close') { + return doc.model.edit( + [new ModelEdit('deleteRange', [p - prevToken.raw.length, prevToken.raw.length + 1])], + { + selection: new ModelEditSelection(p - prevToken.raw.length), + } + ); + } else if (!cursor.withinString() && onlyWhitespaceLeftOfCursor(doc, cursor)) { + return backspaceOnWhitespaceEdit(doc, cursor); + } else { + if (['open', 'close'].includes(prevToken.type) && docIsBalanced(doc)) { + doc.selection = new ModelEditSelection(p - prevToken.raw.length); + return new Promise((resolve) => resolve(true)); + } else { + return doc.backspace(); + } + } + } +} + +export async function deleteForward( + doc: EditableDocument, + start: number = doc.selection.anchor, + end: number = doc.selection.active +) { + if (start != end) { + await doc.delete(); + } else { + const cursor = doc.getTokenCursor(start); + const prevToken = cursor.getPrevToken(); + const nextToken = cursor.getToken(); + const p = start; + if (doc.model.getText(p, p + 2, true) == '\\"') { + return doc.model.edit([new ModelEdit('deleteRange', [p, 2])], { + selection: new ModelEditSelection(p), + }); + } else if (prevToken.type === 'open' && nextToken.type === 'close') { + return doc.model.edit( + [new ModelEdit('deleteRange', [p - prevToken.raw.length, prevToken.raw.length + 1])], + { + selection: new ModelEditSelection(p - prevToken.raw.length), + } + ); + } else { + if (['open', 'close'].includes(nextToken.type) && docIsBalanced(doc)) { + doc.selection = new ModelEditSelection(p + 1); + return new Promise((resolve) => resolve(true)); + } else { + return doc.delete(); + } + } + } +} + +export async function stringQuote( + doc: EditableDocument, + start: number = doc.selection.anchor, + end: number = doc.selection.active +) { + if (start != end) { + doc.insertString('"'); + } else { + const cursor = doc.getTokenCursor(start); + if (cursor.withinString()) { + // inside a string, let's be clever + if (cursor.getToken().type == 'close') { + if (doc.model.getText(0, start).endsWith('\\')) { + return doc.model.edit([new ModelEdit('changeRange', [start, start, '"'])], { + selection: new ModelEditSelection(start + 1), + }); + } else { + return close(doc, '"', start); + } + } else { + if (doc.model.getText(0, start).endsWith('\\')) { + return doc.model.edit([new ModelEdit('changeRange', [start, start, '"'])], { + selection: new ModelEditSelection(start + 1), + }); + } else { + return doc.model.edit([new ModelEdit('changeRange', [start, start, '\\"'])], { + selection: new ModelEditSelection(start + 2), + }); + } + } + } else { + return doc.model.edit([new ModelEdit('changeRange', [start, start, '""'])], { + selection: new ModelEditSelection(start + 1), + }); + } + } +} + +export function growSelection( + doc: EditableDocument, + start: number = doc.selection.anchor, + end: number = doc.selection.active +) { + const startC = doc.getTokenCursor(start), + endC = doc.getTokenCursor(end), + emptySelection = startC.equals(endC); + + if (emptySelection) { + const currentFormRange = startC.rangeForCurrentForm(start); + if (currentFormRange) { + growSelectionStack(doc, currentFormRange); + } + } else { + if (startC.getPrevToken().type == 'open' && endC.getToken().type == 'close') { + startC.backwardList(); + startC.backwardUpList(); + endC.forwardList(); + growSelectionStack(doc, [startC.offsetStart, endC.offsetEnd]); + } else { + if (startC.backwardList()) { + // we are in an sexpr. + endC.forwardList(); + endC.previous(); + } else { + if (startC.backwardDownList()) { + startC.backwardList(); + if (emptySelection) { + endC.set(startC); + endC.forwardList(); + endC.next(); + } + startC.previous(); + } else if (startC.downList()) { + if (emptySelection) { + endC.set(startC); + endC.forwardList(); + endC.next(); + } + startC.previous(); + } + } + growSelectionStack(doc, [startC.offsetStart, endC.offsetEnd]); + } + } +} + +export function growSelectionStack(doc: EditableDocument, range: [number, number]) { + const [start, end] = range; + if (doc.selectionStack.length > 0) { + const prev = doc.selectionStack[doc.selectionStack.length - 1]; + if (!(doc.selection.anchor == prev.anchor && doc.selection.active == prev.active)) { + setSelectionStack(doc); + } else if (prev.anchor === range[0] && prev.active === range[1]) { + return; + } + } else { + doc.selectionStack = [doc.selection]; + } + doc.selection = new ModelEditSelection(start, end); + doc.selectionStack.push(doc.selection); +} + +export function shrinkSelection(doc: EditableDocument) { + if (doc.selectionStack.length) { + const latest = doc.selectionStack.pop(); + if ( + doc.selectionStack.length && + latest.anchor == doc.selection.anchor && + latest.active == doc.selection.active + ) { + doc.selection = doc.selectionStack[doc.selectionStack.length - 1]; + } + } +} + +export function setSelectionStack(doc: EditableDocument, selection = doc.selection) { + doc.selectionStack = [selection]; +} + +export async function raiseSexp( + doc: EditableDocument, + start = doc.selection.anchor, + end = doc.selection.active +) { + const cursor = doc.getTokenCursor(end); + const [formStart, formEnd] = cursor.rangeForCurrentForm(start); + const isCaretTrailing = formEnd - start < start - formStart; + const startCursor = doc.getTokenCursor(formStart); + const endCursor = startCursor.clone(); + if (endCursor.forwardSexp()) { + const raised = doc.model.getText(startCursor.offsetStart, endCursor.offsetStart); + startCursor.backwardList(); + endCursor.forwardList(); + if (startCursor.getPrevToken().type == 'open') { + startCursor.previous(); + if (endCursor.getToken().type == 'close') { + return doc.model.edit( + [new ModelEdit('changeRange', [startCursor.offsetStart, endCursor.offsetEnd, raised])], + { + selection: new ModelEditSelection( + isCaretTrailing ? startCursor.offsetStart + raised.length : startCursor.offsetStart + ), + } + ); + } + } + } +} + +export async function convolute( + doc: EditableDocument, + start = doc.selection.anchor, + end = doc.selection.active +) { + if (start == end) { + const cursorStart = doc.getTokenCursor(end); + const cursorEnd = cursorStart.clone(); + + if (cursorStart.backwardList()) { + if (cursorEnd.forwardList()) { + const head = doc.model.getText(cursorStart.offsetStart, end); + if (cursorStart.getPrevToken().type == 'open') { + cursorStart.previous(); + const headStart = cursorStart.clone(); + + if (headStart.backwardList() && headStart.backwardUpList()) { + const headEnd = cursorStart.clone(); + if (headEnd.forwardList() && cursorEnd.getToken().type == 'close') { + return doc.model.edit( + [ + new ModelEdit('changeRange', [headEnd.offsetEnd, headEnd.offsetEnd, ')']), + new ModelEdit('changeRange', [cursorEnd.offsetStart, cursorEnd.offsetEnd, '']), + new ModelEdit('changeRange', [cursorStart.offsetStart, end, '']), + new ModelEdit('changeRange', [ + headStart.offsetStart, + headStart.offsetStart, + '(' + head, + ]), + ], + {} + ); + } + } + } + } + } + } +} + +export async function transpose( + doc: EditableDocument, + left = doc.selection.anchor, + right = doc.selection.active, + newPosOffset: { fromLeft?: number; fromRight?: number } = {} +) { + const cursor = doc.getTokenCursor(right); + cursor.backwardWhitespace(); + if (cursor.getPrevToken().type == 'open') { + cursor.forwardSexp(); + } + cursor.forwardWhitespace(); + if (cursor.getToken().type == 'close') { + cursor.backwardSexp(); + } + if (cursor.getToken().type != 'close') { + const rightStart = cursor.offsetStart; + if (cursor.forwardSexp()) { + const rightEnd = cursor.offsetStart; + cursor.backwardSexp(); + cursor.backwardWhitespace(); + const leftEnd = cursor.offsetStart; + if (cursor.backwardSexp()) { + const leftStart = cursor.offsetStart, + leftText = doc.model.getText(leftStart, leftEnd), + rightText = doc.model.getText(rightStart, rightEnd); + let newCursorPos = leftStart + rightText.length; + if (newPosOffset.fromLeft != undefined) { + newCursorPos = leftStart + newPosOffset.fromLeft; + } else if (newPosOffset.fromRight != undefined) { + newCursorPos = rightEnd - newPosOffset.fromRight; + } + return doc.model.edit( + [ + new ModelEdit('changeRange', [rightStart, rightEnd, leftText]), + new ModelEdit('changeRange', [ + leftStart, + leftEnd, + rightText, + [left, left], + [newCursorPos, newCursorPos], + ]), + ], + { selection: new ModelEditSelection(newCursorPos) } + ); + } + } + } +} + +export const bindingForms = [ + 'let', + 'for', + 'loop', + 'binding', + 'with-local-vars', + 'doseq', + 'with-redefs', + 'when-let', +]; + +function isInPairsList(cursor: LispTokenCursor, pairForms: string[]): boolean { + const probeCursor = cursor.clone(); + if (probeCursor.backwardList()) { + const opening = probeCursor.getPrevToken().raw; + if (opening.endsWith('{') && !opening.endsWith('#{')) { + return true; + } + if (opening.endsWith('[')) { + probeCursor.backwardUpList(); + probeCursor.backwardList(); + if (probeCursor.getPrevToken().raw.endsWith('{')) { + return false; + } + const fn = probeCursor.getFunctionName(); + if (fn && pairForms.includes(fn)) { + return true; + } + } + return false; + } + return false; +} + +/** + * Returns the range of the current form + * or the current form pair, if usePairs is true + */ +function currentSexpsRange( + doc: EditableDocument, + cursor: LispTokenCursor, + offset: number, + usePairs = false +): [number, number] { + const currentSingleRange = cursor.rangeForCurrentForm(offset); + if (usePairs) { + const ranges = cursor.rangesForSexpsInList(); + if (ranges.length > 1) { + const indexOfCurrentSingle = ranges.findIndex( + (r) => r[0] === currentSingleRange[0] && r[1] === currentSingleRange[1] + ); + if (indexOfCurrentSingle % 2 == 0) { + const pairCursor = doc.getTokenCursor(currentSingleRange[1]); + pairCursor.forwardSexp(); + return [currentSingleRange[0], pairCursor.offsetStart]; + } else { + const pairCursor = doc.getTokenCursor(currentSingleRange[0]); + pairCursor.backwardSexp(); + return [pairCursor.offsetStart, currentSingleRange[1]]; + } + } + } + return currentSingleRange; +} + +export async function dragSexprBackward( + doc: EditableDocument, + pairForms = bindingForms, + left = doc.selection.anchor, + right = doc.selection.active +) { + const cursor = doc.getTokenCursor(right); + const usePairs = isInPairsList(cursor, pairForms); + const currentRange = currentSexpsRange(doc, cursor, right, usePairs); + const newPosOffset = right - currentRange[0]; + const backCursor = doc.getTokenCursor(currentRange[0]); + backCursor.backwardSexp(); + const backRange = currentSexpsRange(doc, backCursor, backCursor.offsetStart, usePairs); + if (backRange[0] !== currentRange[0]) { + // there is a sexp to the left + const leftText = doc.model.getText(backRange[0], backRange[1]); + const currentText = doc.model.getText(currentRange[0], currentRange[1]); + return doc.model.edit( + [ + new ModelEdit('changeRange', [currentRange[0], currentRange[1], leftText]), + new ModelEdit('changeRange', [backRange[0], backRange[1], currentText]), + ], + { selection: new ModelEditSelection(backRange[0] + newPosOffset) } + ); + } +} + +export async function dragSexprForward( + doc: EditableDocument, + pairForms = bindingForms, + left = doc.selection.anchor, + right = doc.selection.active +) { + const cursor = doc.getTokenCursor(right); + const usePairs = isInPairsList(cursor, pairForms); + const currentRange = currentSexpsRange(doc, cursor, right, usePairs); + const newPosOffset = currentRange[1] - right; + const forwardCursor = doc.getTokenCursor(currentRange[1]); + forwardCursor.forwardSexp(); + const forwardRange = currentSexpsRange(doc, forwardCursor, forwardCursor.offsetStart, usePairs); + if (forwardRange[0] !== currentRange[0]) { + // there is a sexp to the right + const rightText = doc.model.getText(forwardRange[0], forwardRange[1]); + const currentText = doc.model.getText(currentRange[0], currentRange[1]); + return doc.model.edit( + [ + new ModelEdit('changeRange', [forwardRange[0], forwardRange[1], currentText]), + new ModelEdit('changeRange', [currentRange[0], currentRange[1], rightText]), + ], + { + selection: new ModelEditSelection( + currentRange[1] + (forwardRange[1] - currentRange[1]) - newPosOffset + ), + } + ); + } +} + +export type WhitespaceInfo = { + hasLeftWs: boolean; + leftWsRange: [number, number]; + leftWs: string; + leftWsHasNewline: boolean; + hasRightWs: boolean; + rightWsRange: [number, number]; + rightWs: string; + rightWsHasNewline: boolean; +}; + +/** + * Collect and return information about the current form regarding its surrounding whitespace + * @param doc + * @param p the position in `doc` from where to determine the current form + */ +export function collectWhitespaceInfo( + doc: EditableDocument, + p = doc.selection.active +): WhitespaceInfo { + const cursor = doc.getTokenCursor(p); + const currentRange = cursor.rangeForCurrentForm(p); + const leftWsRight = currentRange[0]; + const leftWsCursor = doc.getTokenCursor(leftWsRight); + const rightWsLeft = currentRange[1]; + const rightWsCursor = doc.getTokenCursor(rightWsLeft); + leftWsCursor.backwardWhitespace(false); + rightWsCursor.forwardWhitespace(false); + const leftWsLeft = leftWsCursor.offsetStart; + const leftWs = doc.model.getText(leftWsLeft, leftWsRight); + const leftWsHasNewline = leftWs.indexOf('\n') !== -1; + const rightWsRight = rightWsCursor.offsetStart; + const rightWs = doc.model.getText(rightWsLeft, rightWsRight); + const rightWsHasNewline = rightWs.indexOf('\n') !== -1; + return { + hasLeftWs: leftWs !== '', + leftWsRange: [leftWsLeft, leftWsRight], + leftWs, + leftWsHasNewline, + hasRightWs: rightWs !== '', + rightWsRange: [rightWsLeft, rightWsRight], + rightWs, + rightWsHasNewline, + }; +} + +export async function dragSexprBackwardUp(doc: EditableDocument, p = doc.selection.active) { + const wsInfo = collectWhitespaceInfo(doc, p); + const cursor = doc.getTokenCursor(p); + const currentRange = cursor.rangeForCurrentForm(p); + if (cursor.backwardList() && cursor.backwardUpList()) { + const listStart = cursor.offsetStart; + const newPosOffset = p - currentRange[0]; + const newCursorPos = listStart + newPosOffset; + const listIndent = cursor.getToken().offset; + let dragText: string, deleteEdit: ModelEdit; + if (wsInfo.hasLeftWs) { + dragText = + doc.model.getText(...currentRange) + + (wsInfo.leftWsHasNewline ? '\n' + ' '.repeat(listIndent) : ' '); + const lineCommentCursor = doc.getTokenCursor(wsInfo.leftWsRange[0]); + const havePrecedingLineComment = lineCommentCursor.getPrevToken().type === 'comment'; + const wsLeftStart = wsInfo.leftWsRange[0] + (havePrecedingLineComment ? 1 : 0); + deleteEdit = new ModelEdit('deleteRange', [wsLeftStart, currentRange[1] - wsLeftStart]); + } else { + dragText = + doc.model.getText(...currentRange) + + (wsInfo.rightWsHasNewline ? '\n' + ' '.repeat(listIndent) : ' '); + deleteEdit = new ModelEdit('deleteRange', [ + currentRange[0], + wsInfo.rightWsRange[1] - currentRange[0], + ]); + } + return doc.model.edit( + [ + deleteEdit, + new ModelEdit('insertString', [listStart, dragText, [p, p], [newCursorPos, newCursorPos]]), + ], + { + selection: new ModelEditSelection(newCursorPos), + skipFormat: false, + undoStopBefore: true, + } + ); + } +} + +export async function dragSexprForwardDown(doc: EditableDocument, p = doc.selection.active) { + const wsInfo = collectWhitespaceInfo(doc, p); + const currentRange = doc.getTokenCursor(p).rangeForCurrentForm(p); + const newPosOffset = p - currentRange[0]; + const cursor = doc.getTokenCursor(currentRange[0]); + while (cursor.forwardSexp()) { + cursor.forwardWhitespace(); + const token = cursor.getToken(); + if (token.type === 'open') { + const listStart = cursor.offsetStart; + const deleteLength = wsInfo.rightWsRange[1] - currentRange[0]; + const insertStart = listStart + token.raw.length; + const newCursorPos = insertStart - deleteLength + newPosOffset; + const insertText = + doc.model.getText(...currentRange) + (wsInfo.rightWsHasNewline ? '\n' : ' '); + return doc.model.edit( + [ + new ModelEdit('insertString', [ + insertStart, + insertText, + [p, p], + [newCursorPos, newCursorPos], + ]), + new ModelEdit('deleteRange', [currentRange[0], deleteLength]), + ], + { + selection: new ModelEditSelection(newCursorPos), + skipFormat: false, + undoStopBefore: true, + } + ); + } + } +} + +export async function dragSexprForwardUp(doc: EditableDocument, p = doc.selection.active) { + const wsInfo = collectWhitespaceInfo(doc, p); + const cursor = doc.getTokenCursor(p); + const currentRange = cursor.rangeForCurrentForm(p); + if (cursor.forwardList() && cursor.upList()) { + const listEnd = cursor.offsetStart; + const newPosOffset = p - currentRange[0]; + const listWsInfo = collectWhitespaceInfo(doc, listEnd); + const dragText = + (listWsInfo.rightWsHasNewline ? '\n' : ' ') + doc.model.getText(...currentRange); + let deleteStart = wsInfo.leftWsRange[0]; + let deleteLength = currentRange[1] - deleteStart; + if (wsInfo.hasRightWs) { + deleteStart = currentRange[0]; + deleteLength = wsInfo.rightWsRange[1] - deleteStart; + } + const newCursorPos = listEnd + newPosOffset + 1 - deleteLength; + return doc.model.edit( + [ + new ModelEdit('insertString', [listEnd, dragText, [p, p], [newCursorPos, newCursorPos]]), + new ModelEdit('deleteRange', [deleteStart, deleteLength]), + ], + { + selection: new ModelEditSelection(newCursorPos), + skipFormat: false, + undoStopBefore: true, + } + ); + } +} + +export async function dragSexprBackwardDown(doc: EditableDocument, p = doc.selection.active) { + const wsInfo = collectWhitespaceInfo(doc, p); + const currentRange = doc.getTokenCursor(p).rangeForCurrentForm(p); + const newPosOffset = p - currentRange[0]; + const cursor = doc.getTokenCursor(currentRange[1]); + while (cursor.backwardSexp()) { + cursor.backwardWhitespace(); + const token = cursor.getPrevToken(); + if (token.type === 'close') { + cursor.previous(); + const listEnd = cursor.offsetStart; + cursor.backwardWhitespace(); + const siblingWsInfo = collectWhitespaceInfo(doc, cursor.offsetStart); + const deleteLength = currentRange[1] - wsInfo.leftWsRange[0]; + const insertStart = listEnd; + const newCursorPos = insertStart + newPosOffset + 1; + let insertText = doc.model.getText(...currentRange); + insertText = (siblingWsInfo.leftWsHasNewline ? '\n' : ' ') + insertText; + return doc.model.edit( + [ + new ModelEdit('deleteRange', [wsInfo.leftWsRange[0], deleteLength]), + new ModelEdit('insertString', [ + insertStart, + insertText, + [p, p], + [newCursorPos, newCursorPos], + ]), + ], + { + selection: new ModelEditSelection(newCursorPos), + skipFormat: false, + undoStopBefore: true, + } + ); + break; + } + } +} + +function adaptContentsToRichComment(contents: string): string { + return contents + .split(/\n/) + .map((line) => ` ${line}`) + .join('\n') + .trim(); +} + +export async function addRichComment( + doc: EditableDocument, + p = doc.selection.active, + contents?: string +) { + const richComment = `(comment\n ${contents ? adaptContentsToRichComment(contents) : ''}\n )`; + let cursor = doc.getTokenCursor(p); + const topLevelRange = rangeForDefun(doc, p, false); + const isInsideForm = !(p <= topLevelRange[0] || p >= topLevelRange[1]); + const checkIfAtStartCursor = doc.getTokenCursor(p); + checkIfAtStartCursor.backwardWhitespace(true); + const isAtStart = checkIfAtStartCursor.atStart(); + if (isInsideForm || isAtStart) { + cursor = doc.getTokenCursor(topLevelRange[1]); + } + const inLineComment = + cursor.getPrevToken().type === 'comment' || cursor.getToken().type === 'comment'; + if (inLineComment) { + cursor.forwardWhitespace(true); + cursor.backwardWhitespace(false); + } + const insertStart = cursor.offsetStart; + const insideNextTopLevelFormPos = rangeToForwardDownList(doc, insertStart)[1]; + if (!contents && insideNextTopLevelFormPos !== insertStart) { + const checkIfRichCommentExistsCursor = doc.getTokenCursor(insideNextTopLevelFormPos); + checkIfRichCommentExistsCursor.forwardWhitespace(true); + if (checkIfRichCommentExistsCursor.getToken().raw == 'comment') { + checkIfRichCommentExistsCursor.forwardSexp(); + checkIfRichCommentExistsCursor.forwardWhitespace(false); + // insert nothing, just place cursor + const newCursorPos = checkIfRichCommentExistsCursor.offsetStart; + return doc.model.edit( + [ + new ModelEdit('insertString', [ + newCursorPos, + '', + [newCursorPos, newCursorPos], + [newCursorPos, newCursorPos], + ]), + ], + { + selection: new ModelEditSelection(newCursorPos), + skipFormat: true, + undoStopBefore: false, + } + ); + } + } + cursor.backwardWhitespace(false); + const leftWs = doc.model.getText(cursor.offsetStart, insertStart); + cursor.forwardWhitespace(false); + const rightWs = doc.model.getText(insertStart, cursor.offsetStart); + const numPrependNls = leftWs.match('\n\n') ? 0 : leftWs.match('\n') ? 1 : 2; + const numAppendNls = rightWs.match('\n\n') ? 0 : rightWs.match('^\n') ? 1 : 2; + const prepend = '\n'.repeat(numPrependNls); + const append = '\n'.repeat(numAppendNls); + const insertText = `${prepend}${richComment}${append}`; + const newCursorPos = insertStart + 11 + numPrependNls * doc.model.lineEndingLength; + return doc.model.edit( + [ + new ModelEdit('insertString', [ + insertStart, + insertText, + [insertStart, insertStart], + [newCursorPos, newCursorPos], + ]), + ], + { + selection: new ModelEditSelection(newCursorPos), + skipFormat: false, + undoStopBefore: true, + } + ); +} diff --git a/src/cursor-doc/token-cursor.ts b/src/cursor-doc/token-cursor.ts new file mode 100644 index 0000000..521f2c8 --- /dev/null +++ b/src/cursor-doc/token-cursor.ts @@ -0,0 +1,938 @@ +import { LineInputModel } from './model'; +import { Token, validPair } from './cdf-edits/hy-lexer'; + +function tokenIsWhiteSpace(token: Token) { + return token.type === 'eol' || token.type == 'ws'; +} + +/** + * A mutable cursor into the token stream. + */ +export class TokenCursor { + constructor(public doc: LineInputModel, public line: number, public token: number) {} + + /** Create a copy of this cursor. */ + clone() { + return new TokenCursor(this.doc, this.line, this.token); + } + + /** + * Sets this TokenCursor state to the same as another. + * @param cursor the cursor to copy state from. + */ + set(cursor: TokenCursor) { + this.doc = cursor.doc; + this.line = cursor.line; + this.token = cursor.token; + } + + /** Return the position */ + get rowCol() { + return [this.line, this.getToken().offset]; + } + + /** Return the offset at the start of the token */ + get offsetStart() { + return this.doc.getOffsetForLine(this.line) + this.getToken().offset; + } + + /** Return the offset at the end of the token */ + get offsetEnd() { + return Math.min( + this.doc.maxOffset, + this.doc.getOffsetForLine(this.line) + this.getToken().offset + this.getToken().raw.length + ); + } + + /** True if we are at the start of the document */ + atStart() { + return this.token == 0 && this.line == 0; + } + + /** True if we are at the end of the document */ + atEnd() { + return ( + this.line == this.doc.lines.length - 1 && + this.token == this.doc.lines[this.line].tokens.length - 1 + ); + } + + /** Move this cursor backwards one token */ + previous() { + if (this.token > 0) { + this.token--; + } else { + if (this.line == 0) { + return; + } + this.line--; + this.token = this.doc.lines[this.line].tokens.length - 1; + } + return this; + } + + /** Move this cursor forwards one token */ + next() { + if (this.token < this.doc.lines[this.line].tokens.length - 1) { + this.token++; + } else { + if (this.line == this.doc.lines.length - 1) { + return; + } + this.line++; + this.token = 0; + } + return this; + } + + /** + * Return the token immediately preceding this cursor. At the start of the file, a token of type "eol" is returned. + */ + getPrevToken(): Token { + if (this.line == 0 && this.token == 0) { + return { type: 'eol', raw: '\n', offset: 0, state: null }; + } + const cursor = this.clone(); + cursor.previous(); + return cursor.getToken(); + } + + /** + * Returns the token at this cursor position. + */ + getToken() { + return this.doc.lines[this.line].tokens[this.token]; + } + + equals(cursor: TokenCursor) { + return this.line == cursor.line && this.token == cursor.token && this.doc == cursor.doc; + } +} + +/** + * Implementation for cursor.rangesForSexpsInList and + * cursor.rowColRangesForSexpsInList + * Returns the ranges for all forms in the current list. + * Returns undefined if the current cursor is not within a list. + * If you are particular about which list type that should be considered, supply an `openingBracket`. + */ + +function _rangesForSexpsInList( + cursor: LispTokenCursor, + useRowCol = false, + openingBracket?: string +): [number, number][] | [[number, number], [number, number]][] { + if (openingBracket !== undefined) { + if (!cursor.backwardListOfType(openingBracket)) { + return undefined; + } + } else { + if (!cursor.backwardList()) { + return undefined; + } + } + const ranges = []; + // TODO: Figure out how to do this ignore skipping more generally in forward/backward this or that. + let ignoreCounter = 0; + while (true) { + cursor.forwardWhitespace(); + const start = useRowCol ? cursor.rowCol : cursor.offsetStart; + if (cursor.getToken().type === 'ignore') { + ignoreCounter++; + cursor.forwardSexp(); + continue; + } + if (cursor.forwardSexp()) { + if (ignoreCounter === 0) { + const end = useRowCol ? cursor.rowCol : cursor.offsetStart; + ranges.push([start, end]); + } else { + ignoreCounter--; + } + } else { + break; + } + } + return ranges; +} + +export class LispTokenCursor extends TokenCursor { + constructor(public doc: LineInputModel, public line: number, public token: number) { + super(doc, line, token); + } + + /** Create a copy of this cursor. */ + clone() { + return new LispTokenCursor(this.doc, this.line, this.token); + } + + tokenBeginsMetadata(): boolean { + return this.getToken().raw.startsWith('^'); + } + + prevTokenBeginsMetadata(): boolean { + return this.getPrevToken().raw.startsWith('^'); + } + /** + * Moves this token past any whitespace or comment. + */ + forwardWhitespace(includeComments = true) { + while (!this.atEnd()) { + switch (this.getToken().type) { + case 'comment': + case 'prompt': + if (!includeComments) { + return; + } + // eslint-disable-next-line no-fallthrough + case 'eol': + case 'ws': + this.next(); + if (['comment', 'prompt'].includes(this.getToken().type) && !includeComments) { + return; + } + continue; + default: + return; + } + } + } + + /** + * Moves this token back past any whitespace or comment. + */ + backwardWhitespace(includeComments = true) { + while (!this.atStart()) { + switch (this.getPrevToken().type) { + case 'comment': + case 'prompt': + if (!includeComments) { + return; + } + // eslint-disable-next-line no-fallthrough + case 'eol': + case 'ws': + this.previous(); + if (['comment', 'prompt'].includes(this.getPrevToken().type) && !includeComments) { + return; + } + continue; + default: + return; + } + } + } + + // Lisp navigation commands begin here. + + // TODO: When f/b sexp, use the stack knowledge to ”flag” unbalance + + /** + * Moves this token forward one s-expression at this level. + * If the next non whitespace token is an open paren, skips past it's matching + * close paren. + * + * If the next token is a form of closing paren, does not move. + * + * @returns true if the cursor was moved, false otherwise. + */ + forwardSexp(skipComments = true, skipMetadata = false, skipIgnoredForms = false): boolean { + // TODO: Consider using a proper bracket stack + const stack = []; + let isMetadata = false; + this.forwardWhitespace(skipComments); + if (this.getToken().type === 'close') { + return false; + } + isMetadata = this.tokenBeginsMetadata(); + while (!this.atEnd()) { + this.forwardWhitespace(skipComments); + const token = this.getToken(); + switch (token.type) { + case 'comment': + this.next(); + this.next(); + break; + case 'prompt': + this.next(); + this.next(); + break; + case 'ignore': + if (skipIgnoredForms) { + this.next(); + this.forwardSexp(skipComments, skipMetadata, skipIgnoredForms); + break; + } + // eslint-disable-next-line no-fallthrough + case 'id': + case 'lit': + case 'kw': + case 'junk': + case 'str-inside': + if (skipMetadata && this.getToken().raw.startsWith('^')) { + this.next(); + } else { + this.next(); + if (stack.length <= 0) { + return true; + } + } + break; + case 'close': { + const close = token.raw; + let open: string; + while ((open = stack.pop())) { + if (validPair(open, close)) { + this.next(); + break; + } + } + if (skipMetadata && isMetadata) { + this.forwardSexp(skipComments, skipMetadata); + } + if (stack.length <= 0) { + return true; + } + break; + } + case 'open': + stack.push(token.raw); + isMetadata = this.tokenBeginsMetadata(); + this.next(); + break; + default: + this.next(); + break; + } + } + } + + /** + * Moves this token backward one s-expression at this level. + * If the previous non whitespace token is a close paren, skips past it's matching + * open paren. + * + * If the previous token is a form of open paren, does not move. + * + * @returns true if the cursor was moved, false otherwise. + */ + backwardSexp( + skipComments = true, + skipMetadata = false, + skipIgnoredForms = false, + skipReaders = true + ) { + const stack = []; + this.backwardWhitespace(skipComments); + if (this.getPrevToken().type === 'open') { + return false; + } + while (!this.atStart()) { + this.backwardWhitespace(skipComments); + const tk = this.getPrevToken(); + switch (tk.type) { + case 'id': + case 'lit': + case 'kw': + case 'ignore': + case 'junk': + case 'comment': + case 'prompt': + case 'str-inside': { + this.previous(); + if (skipReaders) { + this.backwardThroughAnyReader(); + } + if (skipMetadata) { + const metaCursor = this.clone(); + metaCursor.backwardSexp(true, false, false, false); + if (metaCursor.tokenBeginsMetadata()) { + this.backwardSexp(skipComments, skipMetadata, skipIgnoredForms); + } + } + if (skipReaders) { + this.backwardThroughAnyReader(); + } + if (stack.length <= 0) { + return true; + } + break; + } + case 'close': + stack.push(tk.raw); + this.previous(); + break; + case 'open': { + const open = tk.raw; + let close: string; + while ((close = stack.pop())) { + if (validPair(open, close)) { + break; + } + } + this.previous(); + if (skipReaders) { + this.backwardThroughAnyReader(); + } + if (skipMetadata) { + const metaCursor = this.clone(); + metaCursor.backwardSexp(true, false, false, false); + if (metaCursor.tokenBeginsMetadata()) { + this.backwardSexp(skipComments, skipMetadata, skipIgnoredForms); + } + } + if (skipReaders) { + this.backwardThroughAnyReader(); + } + if (stack.length <= 0) { + return true; + } + break; + } + default: + this.previous(); + break; + } + } + } + + /** + * Moves this cursor past the previous non-ws token, if it is a `reader` token. + * Otherwise, this cursor is left unaffected. + */ + backwardThroughAnyReader() { + const cursor = this.clone(); + let hasReader = false; + while (true) { + cursor.backwardWhitespace(); + if (cursor.getPrevToken().type === 'reader') { + cursor.previous(); + this.set(cursor); + hasReader = true; + } else { + break; + } + } + return hasReader; + } + + /** + * Moves this cursor past the next non-ws token, if it is a `reader` token. + * Otherwise, this cursor is left unaffected. + */ + forwardThroughAnyReader() { + const cursor = this.clone(); + let hasReader = false; + while (true) { + cursor.forwardWhitespace(); + if (cursor.getToken().type === 'reader') { + cursor.next(); + this.set(cursor); + hasReader = true; + } else { + break; + } + } + return hasReader; + } + + /** + * Moves this cursor to the close paren of the containing sexpr, or until the end of the document. + */ + forwardList(): boolean { + const cursor = this.clone(); + while (cursor.forwardSexp()) { + // move forward until the cursor cannot move forward anymore + } + if (cursor.getToken().type === 'close') { + const backCursor = cursor.clone(); + if (backCursor.backwardList()) { + this.set(cursor); + return true; + } + } + return false; + } + + /** + * Moves this cursor forwards to the `closingBracket` of the containing sexpr, or until the end of the document. + */ + forwardListOfType(closingBracket: string): boolean { + const cursor = this.clone(); + while (cursor.forwardList()) { + if (cursor.getToken().raw === closingBracket) { + this.set(cursor); + return true; + } + if (!cursor.upList()) { + return false; + } + } + return false; + } + + /** + * Moves this cursor backwards to the open paren of the containing sexpr, or until the start of the document. + */ + backwardList(): boolean { + const cursor = this.clone(); + while (cursor.backwardSexp()) { + // move backward until the cursor cannot move backward anymore + } + if (cursor.getPrevToken().type === 'open') { + const checkCursor = cursor.clone(); + if (checkCursor.backwardUpList() && checkCursor.forwardSexp()) { + this.set(cursor); + return true; + } + } + return false; + } + + /** + * Moves this cursor backwards to the `openingBracket` of the containing sexpr, or until the start of the document. + */ + backwardListOfType(openingBracket: string): boolean { + const cursor = this.clone(); + while (cursor.backwardList()) { + if (cursor.getPrevToken().raw.endsWith(openingBracket)) { + this.set(cursor); + return true; + } + if (!cursor.backwardUpList()) { + return false; + } + } + return false; + } + + /** + * Finds the range of the current list up to `depth`. + * If you are particular about which type of list, supply the `openingBracket`. + * @param openingBracket + */ + rangeForList(depth: number, openingBracket?: string): [number, number] { + const cursor = this.clone(); + let range: [number, number] = undefined; + for (let i = 0; i < depth; i++) { + if (openingBracket === undefined) { + if (!(cursor.backwardList() && cursor.backwardUpList())) { + return range; + } + } else { + if (!(cursor.backwardListOfType(openingBracket) && cursor.backwardUpList())) { + return range; + } + } + const start = cursor.offsetStart; + if (!cursor.forwardSexp()) { + return range; + } + const end = cursor.offsetStart; + range = [start, end]; + } + return range; + } + + /** + * If possible, moves this cursor forwards past any readers and whitespace, + * and then past the immediately following open-paren and returns true. + * If the source does not match this, returns false and does not move the cursor. + */ + downList(): boolean { + const cursor = this.clone(); + cursor.forwardThroughAnyReader(); + cursor.forwardWhitespace(); + if (cursor.getToken().type === 'open') { + cursor.next(); + this.set(cursor); + return true; + } + return false; + } + + /** + * If possible, moves this cursor forwards past any readers, whitespace, and metadata, + * and then past the immediately following open-paren and returns true. + * If the source does not match this, returns false and does not move the cursor. + */ + downListSkippingMeta(): boolean { + const cursor = this.clone(); + do { + cursor.forwardThroughAnyReader(); + cursor.forwardWhitespace(); + if (cursor.getToken().type === 'open' && !cursor.tokenBeginsMetadata()) { + break; + } + } while (cursor.forwardSexp()); + if (cursor.downList()) { + this.set(cursor); + return true; + } + return false; + } + + /** + * If possible, moves this cursor forwards past any whitespace, and then past the immediately following close-paren and returns true. + * If the source does not match this, returns false and does not move the cursor. + */ + upList(): boolean { + const cursor = this.clone(); + cursor.forwardWhitespace(); + if (cursor.getToken().type == 'close') { + cursor.next(); + this.set(cursor); + return true; + } + return false; + } + + /** + * If possible, moves this cursor backwards past any whitespace, and then backwards past the immediately following open-paren and returns true. + * If the source does not match this, returns false and does not move the cursor. + */ + backwardUpList(): boolean { + const cursor = this.clone(); + cursor.backwardThroughAnyReader(); + cursor.backwardWhitespace(); + if (cursor.getPrevToken().type == 'open') { + cursor.previous(); + this.set(cursor); + return true; + } + return false; + } + + /** + * If possible, moves this cursor backwards past any whitespace, and then backwards past the immediately following close-paren and returns true. + * If the source does not match this, returns false and does not move the cursor. + */ + backwardDownList(): boolean { + const cursor = this.clone(); + cursor.backwardWhitespace(); + if (cursor.getPrevToken().type == 'close') { + cursor.previous(); + this.set(cursor); + return true; + } + return false; + } + + /** + * Figures out the `range` for the current form according to this priority: + * 0. If `offset` is within a symbol, literal or keyword + * 1. Else, if `offset` is adjacent after form + * 2. Else, if `offset` is adjacent before a form + * 3. Else, if the previous form is on the same line + * 4. Else, if the next form is on the same line + * 5. Else, the previous form, if any + * 6. Else, the next form, if any + * 7. Else, the current enclosing form, if any + * 8. Else, return `undefined`. + * @param offset the current cursor (caret) offset in the document + */ + rangeForCurrentForm(offset: number): [number, number] { + let afterCurrentFormOffset: number; + // console.log(-1, offset); + + // 0. If `offset` is within or before, a symbol, literal or keyword + if ( + ['id', 'kw', 'lit', 'str-inside'].includes(this.getToken().type) && + !this.tokenBeginsMetadata() + ) { + afterCurrentFormOffset = this.offsetEnd; + } + // console.log(0, afterCurrentFormOffset); + + // 1. Else, if `offset` is adjacent after form + if (afterCurrentFormOffset === undefined) { + const cursor = this.clone(); + cursor.backwardWhitespace(true); + if ( + cursor.offsetStart == offset && + cursor.getToken().type !== 'reader' && + !cursor.tokenBeginsMetadata() && + cursor.getPrevToken().type !== 'reader' && + !cursor.prevTokenBeginsMetadata() + ) { + if (cursor.backwardSexp() && !cursor.tokenBeginsMetadata()) { + afterCurrentFormOffset = offset; + } + } + } + // console.log(1, afterCurrentFormOffset); + + // 2. Else, if `offset` is adjacent before a form + if (afterCurrentFormOffset === undefined) { + const tk = this.getToken(); + const pTk = this.getPrevToken(); + let isAdjacentBefore = + tk.type === 'reader' || + this.tokenBeginsMetadata() || + pTk.type === 'reader' || + this.prevTokenBeginsMetadata() || + tk.type === 'open'; + // console.log(2.1, isAdjacentBefore); + if (!isAdjacentBefore) { + const cursor = this.clone(); + cursor.backwardWhitespace(); + isAdjacentBefore = + cursor.prevTokenBeginsMetadata() || cursor.getPrevToken().type === 'reader'; + } + // console.log(2.2, isAdjacentBefore); + if (!isAdjacentBefore) { + const cursor = this.clone(); + cursor.forwardWhitespace(); + if (cursor.rowCol[0] === this.rowCol[0]) { + isAdjacentBefore = cursor.tokenBeginsMetadata() || cursor.getToken().type === 'reader'; + } + } + // console.log(2.3, isAdjacentBefore); + if (isAdjacentBefore) { + const cursor = this.clone(); + cursor.forwardWhitespace(); + if (cursor.forwardSexp(true, true)) { + afterCurrentFormOffset = cursor.offsetStart; + } + } + } + // console.log(2, afterCurrentFormOffset); + + // 3. Else, if the previous form is on the same line + if (afterCurrentFormOffset === undefined) { + const cursor = this.clone(); + cursor.backwardWhitespace(true); + const afterOffset = cursor.offsetStart; + if (cursor.rowCol[0] === this.rowCol[0]) { + if (cursor.backwardSexp()) { + afterCurrentFormOffset = afterOffset; + } + } + } + // console.log(3, afterCurrentFormOffset); + + // 4. Else, if the next form is on the same line + if (afterCurrentFormOffset === undefined) { + const cursor = this.clone(); + cursor.forwardWhitespace(true); + if (cursor.rowCol[0] === this.rowCol[0]) { + if (cursor.forwardSexp()) { + afterCurrentFormOffset = cursor.offsetStart; + } + } + } + // console.log(4, afterCurrentFormOffset); + + // 5. Else, the previous form, if any + if (afterCurrentFormOffset === undefined) { + const cursor = this.clone(); + cursor.backwardWhitespace(true); + const afterOffset = cursor.offsetStart; + if (cursor.backwardSexp()) { + afterCurrentFormOffset = afterOffset; + } + } + // console.log(5, afterCurrentFormOffset); + + // 6. Else, the next form, if any + if (afterCurrentFormOffset === undefined) { + const cursor = this.clone(); + cursor.forwardWhitespace(); + if (cursor.forwardSexp()) { + afterCurrentFormOffset = cursor.offsetStart; + } + } + // console.log(6, afterCurrentFormOffset); + + // 7. Else, the current enclosing form, if any + if (afterCurrentFormOffset === undefined) { + const cursor = this.clone(); + if (cursor.backwardUpList()) { + if (cursor.forwardSexp()) { + afterCurrentFormOffset = cursor.offsetStart; + } + } + } + // console.log(7, afterCurrentFormOffset); + + // 8. Else, ¯\_(ツ)_/¯ + if (afterCurrentFormOffset === undefined) { + return undefined; // 8. + } + + const currentFormCursor = this.doc.getTokenCursor(afterCurrentFormOffset); + currentFormCursor.backwardSexp(true, true); + return [currentFormCursor.offsetStart, afterCurrentFormOffset]; + } + + rangeForDefun(offset: number, commentCreatesTopLevel = true): [number, number] { + const cursor = this.doc.getTokenCursor(offset); + let lastCandidateRange: [number, number] = cursor.rangeForCurrentForm(offset); + while (cursor.forwardList() && cursor.upList()) { + const commentCursor = cursor.clone(); + commentCursor.backwardDownList(); + if ( + !commentCreatesTopLevel || + commentCursor.getToken().raw !== ')' || + commentCursor.getFunctionName() !== 'comment' + ) { + lastCandidateRange = cursor.rangeForCurrentForm(cursor.offsetStart); + } + } + return lastCandidateRange; + } + + rangesForTopLevelForms(): [number, number][] { + const cursor = new LispTokenCursor(this.doc, 0, 0); + const ranges: [number, number][] = []; + while (cursor.forwardSexp()) { + const end = cursor.offsetStart; + cursor.backwardSexp(); + ranges.push([cursor.offsetStart, end]); + cursor.forwardSexp(); + } + return ranges; + } + + isWhiteSpace(): boolean { + return tokenIsWhiteSpace(this.getToken()); + } + + previousIsWhiteSpace(): boolean { + return tokenIsWhiteSpace(this.getPrevToken()); + } + + withinWhiteSpace(): boolean { + return this.isWhiteSpace() && this.previousIsWhiteSpace(); + } + + /** + * Indicates if the current token is inside a string + */ + withinString() { + const cursor = this.clone(); + cursor.backwardList(); + if (cursor.getPrevToken().type === 'open' && cursor.getPrevToken().raw.endsWith('"') || + cursor.getPrevToken().type === 'open' && cursor.getPrevToken().raw == '```') { + return true; + } + return false; + } + + /** + * Indicates if the current token is in a comment line + */ + withinComment() { + const cursor = this.clone(); + let isComment = + cursor.getToken().type === 'comment' || cursor.getPrevToken().type === 'comment'; + if (!isComment && this.withinWhiteSpace()) { + cursor.forwardWhitespace(false); + isComment = cursor.getToken().type === 'comment'; + if (!isComment) { + cursor.backwardWhitespace(false); + isComment = cursor.getPrevToken().type === 'comment' && cursor.getToken().type !== 'eol'; + } + } + return isComment; + } + + /** + * Tells if the cursor is inside a properly closed list. + */ + withinValidList(): boolean { + const cursor = this.clone(); + while (cursor.forwardSexp()) { + // move forward until the cursor cannot move forward anymore + } + return cursor.getToken().type == 'close'; + } + + /** + * Returns the rowCol ranges for all forms in the current list. + * Returns undefined if the current cursor is not within a list. + * If you are particular about which list type that should be considered, supply an `openingBracket`. + */ + rowColRangesForSexpsInList(openingBracket?: string): [[number, number], [number, number]][] { + const cursor = this.clone(); + return _rangesForSexpsInList(cursor, true, openingBracket) as [ + [number, number], + [number, number] + ][]; + } + + /** + * Returns the rowCol ranges for all forms in the current list. + * Returns undefined if the current cursor is not within a list. + * If you are particular about which list type that should be considered, supply an `openingBracket`. + */ + rangesForSexpsInList(openingBracket?: string): [number, number][] { + const cursor = this.clone(); + return _rangesForSexpsInList(cursor, false, openingBracket) as [number, number][]; + } + + /** + * Tries to move this cursor backwards to the open paren of the function, `level` functions up. + * If there aren't that many functions behind the cursor, the cursor is not moved at all. + * @param levels how many functions up to go before placing the cursor at the start of it. + * @returns `true` if the cursor was moved, otherwise `false` + */ + backwardFunction(levels: number = 0): boolean { + const cursor = this.clone(); + if (!cursor.backwardListOfType('(')) { + return false; + } + for (let i = 0; i < levels; i++) { + if (!cursor.backwardUpList()) { + return false; + } + if (!cursor.backwardListOfType('(')) { + return false; + } + } + this.set(cursor); + return true; + } + + /** + * Get the name of the current function, optionally digging `levels` functions up. + * @param levels how many levels of functions to dig up. + * @returns the function name, or undefined if there is no function there. + */ + getFunctionName(levels: number = 0): string { + const cursor = this.clone(); + if (cursor.backwardFunction(levels)) { + cursor.forwardWhitespace(); + const symbol = cursor.getToken(); + if (symbol.type === 'id') { + return symbol.raw; + } + } + } + + /** + * Get the range of the sexp that is in function position of the current list, optionally digging `levels` functions up. + * @param levels how many levels of functions to dig up. + * @returns the range of the function sexp/form, or undefined if there is no function there. + */ + getFunctionSexpRange(levels: number = 0): [number, number] { + const cursor = this.clone(); + if (cursor.backwardFunction(levels)) { + cursor.forwardWhitespace(); + const start = cursor.offsetStart; + cursor.forwardSexp(true, true, true); + const end = cursor.offsetStart; + return [start, end]; + } + return [undefined, undefined]; + } +} + +/** + * Creates a `LispTokenCursor` for walking and manipulating the string `s`. + */ +export function createStringCursor(s: string): LispTokenCursor { + const model = new LineInputModel(); + model.insertString(0, s); + return model.getTokenCursor(0); +} diff --git a/src/cursor-doc/undo.ts b/src/cursor-doc/undo.ts new file mode 100644 index 0000000..cf5f124 --- /dev/null +++ b/src/cursor-doc/undo.ts @@ -0,0 +1,130 @@ +/** + * A reversable operation to a document of type T. + */ +export abstract class UndoStep { + /** The name of this undo operation. */ + name: string; + /** If true, the UndoManager will not attempt to coalesce events onto this step. */ + undoStop: boolean; + + /** Given the document, undos the effect of this step */ + abstract undo(c: T): void; + + /** Given the document, redoes the effect of this step */ + abstract redo(c: T): void; + + /** + * Given another UndoStep, attempts to modify this undo-step to include the subsequent one. + * If successful, returns true, if unsuccessful, returns false, and the step must be added to the + * UndoManager, too. + */ + coalesce(c: UndoStep): boolean { + return false; + } +} + +export class UndoStepGroup extends UndoStep { + steps: UndoStep[] = []; + + addUndoStep(step: UndoStep) { + const prevStep = this.steps.length && this.steps[this.steps.length - 1]; + + if (prevStep && !prevStep.undoStop && prevStep.coalesce(step)) { + return; + } + this.steps.push(step); + } + + undo(c: T): void { + for (let i = this.steps.length - 1; i >= 0; i--) { + this.steps[i].undo(c); + } + } + + redo(c: T): void { + for (let i = 0; i < this.steps.length; i++) { + this.steps[i].redo(c); + } + } +} + +/** + * Handles the undo/redo stacks. + */ +export class UndoManager { + private undos: UndoStep[] = []; + private redos: UndoStep[] = []; + + private groupedUndo: UndoStepGroup | null; + + /** + * Adds the step to the undo stack, and clears the redo stack. + * If possible, coalesces it into the previous undo. + * + * @param step the UndoStep to add. + */ + addUndoStep(step: UndoStep) { + if (this.groupedUndo) { + this.groupedUndo.addUndoStep(step); + } else if (this.undos.length) { + const prevUndo = this.undos[this.undos.length - 1]; + if (prevUndo.undoStop) { + this.undos.push(step); + } else if (!prevUndo.coalesce(step)) { + this.undos.push(step); + } + } else { + this.undos.push(step); + } + this.redos = []; + } + + withUndo(f: () => void) { + if (!this.groupedUndo) { + try { + this.groupedUndo = new UndoStepGroup(); + f(); + const undo = this.groupedUndo; + this.groupedUndo = null; + switch (undo.steps.length) { + case 0: + break; + case 1: + this.addUndoStep(undo.steps[0]); + break; + default: + this.addUndoStep(undo); + } + } finally { + this.groupedUndo = null; + } + } else { + f(); + } + } + + /** Prevents this undo from becoming coalesced with future undos */ + insertUndoStop() { + if (this.undos.length) { + this.undos[this.undos.length - 1].undoStop = true; + } + } + + /** Performs the top undo operation on the document (if it exists), moving it to the redo stack. */ + undo(c: T) { + if (this.undos.length) { + const step = this.undos.pop(); + step.undo(c); + this.redos.push(step); + } + } + + /** Performs the top redo operation on the document (if it exists), moving it back onto the undo stack. */ + redo(c: T) { + if (this.redos.length) { + const step = this.redos.pop(); + step.redo(c); + this.undos.push(step); + } + } +} diff --git a/src/doc-mirror/index.ts b/src/doc-mirror/index.ts new file mode 100644 index 0000000..0e40b21 --- /dev/null +++ b/src/doc-mirror/index.ts @@ -0,0 +1,271 @@ +export { getIndent } from '../cursor-doc/indent'; +import * as vscode from 'vscode'; +import * as utilities from '../utilities'; +import * as formatter from '../calva-fmt/src/format'; +import { LispTokenCursor } from '../cursor-doc/token-cursor'; +import { + ModelEdit, + EditableDocument, + EditableModel, + ModelEditOptions, + LineInputModel, + ModelEditSelection, +} from '../cursor-doc/model'; +import { isUndefined } from 'lodash'; + +const documents = new Map(); + +export class DocumentModel implements EditableModel { + readonly lineEndingLength: number; + lineInputModel: LineInputModel; + + constructor(private document: MirroredDocument) { + this.lineEndingLength = document.document.eol == vscode.EndOfLine.CRLF ? 2 : 1; + this.lineInputModel = new LineInputModel(this.lineEndingLength); + } + + edit(modelEdits: ModelEdit[], options: ModelEditOptions): Thenable { + const editor = utilities.getActiveTextEditor(), + undoStopBefore = !!options.undoStopBefore; + return editor + .edit( + (builder) => { + for (const modelEdit of modelEdits) { + switch (modelEdit.editFn) { + case 'insertString': + this.insertEdit.apply(this, [builder, ...modelEdit.args]); + break; + case 'changeRange': + this.replaceEdit.apply(this, [builder, ...modelEdit.args]); + break; + case 'deleteRange': + this.deleteEdit.apply(this, [builder, ...modelEdit.args]); + break; + default: + break; + } + } + }, + { undoStopBefore, undoStopAfter: false } + ) + .then((isFulfilled) => { + if (isFulfilled) { + if (options.selection) { + this.document.selection = options.selection; + } + if (!options.skipFormat) { + return formatter.formatPosition(editor, false, { + 'format-depth': options.formatDepth ? options.formatDepth : 1, + }); + } + } + return isFulfilled; + }); + } + + private insertEdit( + builder: vscode.TextEditorEdit, + offset: number, + text: string, + oldSelection?: [number, number], + newSelection?: [number, number] + ) { + const editor = utilities.getActiveTextEditor(), + document = editor.document; + builder.insert(document.positionAt(offset), text); + } + + private replaceEdit( + builder: vscode.TextEditorEdit, + start: number, + end: number, + text: string, + oldSelection?: [number, number], + newSelection?: [number, number] + ) { + const editor = utilities.getActiveTextEditor(), + document = editor.document, + range = new vscode.Range(document.positionAt(start), document.positionAt(end)); + builder.replace(range, text); + } + + private deleteEdit( + builder: vscode.TextEditorEdit, + offset: number, + count: number, + oldSelection?: [number, number], + newSelection?: [number, number] + ) { + const editor = utilities.getActiveTextEditor(), + document = editor.document, + range = new vscode.Range(document.positionAt(offset), document.positionAt(offset + count)); + builder.delete(range); + } + + public getText(start: number, end: number, mustBeWithin = false) { + return this.lineInputModel.getText(start, end, mustBeWithin); + } + + public getLineText(line: number) { + return this.lineInputModel.getLineText(line); + } + + getOffsetForLine(line: number) { + return this.lineInputModel.getOffsetForLine(line); + } + + public getTokenCursor(offset: number, previous?: boolean) { + return this.lineInputModel.getTokenCursor(offset, previous); + } +} + +export class MirroredDocument implements EditableDocument { + constructor(public document: vscode.TextDocument) {} + + model = new DocumentModel(this); + + selectionStack: ModelEditSelection[] = []; + + public getTokenCursor( + offset: number = this.selection.active, + previous: boolean = false + ): LispTokenCursor { + return this.model.getTokenCursor(offset, previous); + } + + public insertString(text: string) { + const editor = utilities.getActiveTextEditor(), + selection = editor.selection, + wsEdit = new vscode.WorkspaceEdit(), + // TODO: prob prefer selection.active or .start + edit = vscode.TextEdit.insert(this.document.positionAt(this.selection.anchor), text); + wsEdit.set(this.document.uri, [edit]); + void vscode.workspace.applyEdit(wsEdit).then((_v) => { + editor.selection = selection; + }); + } + + set selection(selection: ModelEditSelection) { + const editor = utilities.getActiveTextEditor(), + document = editor.document, + anchor = document.positionAt(selection.anchor), + active = document.positionAt(selection.active); + editor.selection = new vscode.Selection(anchor, active); + editor.revealRange(new vscode.Range(active, active)); + } + + get selection(): ModelEditSelection { + const editor = utilities.getActiveTextEditor(), + document = editor.document, + anchor = document.offsetAt(editor.selection.anchor), + active = document.offsetAt(editor.selection.active); + return new ModelEditSelection(anchor, active); + } + + public getSelectionText() { + const editor = utilities.getActiveTextEditor(), + selection = editor.selection; + return this.document.getText(selection); + } + + public delete(): Thenable { + return vscode.commands.executeCommand('deleteRight'); + } + + public backspace(): Thenable { + return vscode.commands.executeCommand('deleteLeft'); + } +} + +let registered = false; + +function processChanges(event: vscode.TextDocumentChangeEvent) { + const model = documents.get(event.document).model; + for (const change of event.contentChanges) { + // vscode may have a \r\n marker, so it's line offsets are all wrong. + const myStartOffset = + model.getOffsetForLine(change.range.start.line) + change.range.start.character, + myEndOffset = model.getOffsetForLine(change.range.end.line) + change.range.end.character; + void model.lineInputModel.edit( + [ + new ModelEdit('changeRange', [ + myStartOffset, + myEndOffset, + change.text.replace(/\r\n/g, '\n'), + ]), + ], + {} + ); + } + model.lineInputModel.flushChanges(); + + // we must clear out the repaint cache data, since we don't use it. + model.lineInputModel.dirtyLines = []; + model.lineInputModel.insertedLines.clear(); + model.lineInputModel.deletedLines.clear(); +} + +export function tryToGetDocument(doc: vscode.TextDocument) { + return documents.get(doc); +} + +export function getDocument(doc: vscode.TextDocument) { + const mirrorDoc = tryToGetDocument(doc); + + if (isUndefined(mirrorDoc)) { + throw new Error('Missing mirror document!'); + } + + return mirrorDoc; +} + +export function getDocumentOffset(doc: vscode.TextDocument, position: vscode.Position) { + const model = getDocument(doc).model; + return model.getOffsetForLine(position.line) + position.character; +} + +function addDocument(doc?: vscode.TextDocument): boolean { + if (doc && doc.languageId == 'hy') { + if (!documents.has(doc)) { + const document = new MirroredDocument(doc); + document.model.lineInputModel.insertString(0, doc.getText()); + documents.set(doc, document); + return false; + } else { + return true; + } + } + return false; +} + +export function activate() { + // the last thing we want is to register twice and receive double events... + if (registered) { + return; + } + registered = true; + + addDocument(utilities.tryToGetDocument({})); + + vscode.workspace.onDidCloseTextDocument((e) => { + if (e.languageId == 'hy') { + documents.delete(e); + } + }); + + vscode.window.onDidChangeActiveTextEditor((e) => { + if (e && e.document && e.document.languageId == 'hy') { + addDocument(e.document); + } + }); + + vscode.workspace.onDidOpenTextDocument((doc) => { + addDocument(doc); + }); + + vscode.workspace.onDidChangeTextDocument((e) => { + if (addDocument(e.document)) { + processChanges(e); + } + }); +} diff --git a/src/edit.ts b/src/edit.ts new file mode 100644 index 0000000..d1a2741 --- /dev/null +++ b/src/edit.ts @@ -0,0 +1,39 @@ +import * as vscode from 'vscode'; +import * as util from './utilities'; +import * as docMirror from './doc-mirror/index'; + +// Relies on that `when` claus guards this from being called +// when the cursor is before the comment marker +export function continueCommentCommand() { + const document = util.tryToGetDocument({}); + if (document && document.languageId === 'hy') { + const editor = util.getActiveTextEditor(); + const position = editor.selection.active; + const cursor = docMirror.getDocument(document).getTokenCursor(); + if (cursor.getToken().type !== 'comment') { + if (cursor.getPrevToken().type === 'comment') { + cursor.previous(); + } else { + return; + } + } + const commentOffset = cursor.rowCol[1]; + const commentText = cursor.getToken().raw; + const [_1, startText, bullet, num] = commentText.match(/^([#\s]+)([*-] +|(\d+)\. +)?/) ?? []; + const newNum = num ? parseInt(num) + 1 : undefined; + const bulletText = newNum ? bullet.replace(/\d+/, '' + newNum) : bullet; + const pad = ' '.repeat(commentOffset); + const newText = `${pad}${startText}${bullet ? bulletText : ''}`; + void editor + .edit((edits) => edits.insert(position, `\n${newText}`), { + undoStopAfter: false, + undoStopBefore: true, + }) + .then((fulfilled) => { + if (fulfilled) { + const newPosition = position.with(position.line + 1, newText.length); + editor.selection = new vscode.Selection(newPosition, newPosition); + } + }); + } +} diff --git a/src/evaluate.ts b/src/evaluate.ts new file mode 100644 index 0000000..9604214 --- /dev/null +++ b/src/evaluate.ts @@ -0,0 +1,607 @@ +// import * as vscode from 'vscode'; +// import * as state from './state'; +// import annotations from './providers/annotations'; +// import * as path from 'path'; +// import * as util from './utilities'; +// import { NReplSession, NReplEvaluation } from './nrepl'; +// import statusbar from './statusbar'; +// import { PrettyPrintingOptions } from './printer'; +// import * as outputWindow from './results-output/results-doc'; +// import { DEBUG_ANALYTICS } from './debugger/calva-debug'; +// import * as namespace from './namespace'; +// import * as replHistory from './results-output/repl-history'; +// import { formatAsLineComments } from './results-output/util'; +// import { getStateValue } from '../out/cljs-lib/cljs-lib'; +// import { getConfig } from './config'; +// import * as replSession from './nrepl/repl-session'; +// import * as getText from './util/get-text'; + +// function interruptAllEvaluations() { +// if (util.getConnectedState()) { +// const msgs: string[] = []; +// const nums = NReplEvaluation.interruptAll((msg) => { +// msgs.push(msg); +// }); +// if (msgs.length) { +// outputWindow.append(normalizeNewLinesAndJoin(msgs)); +// } +// NReplSession.getInstances().forEach((session, _index) => { +// session.interruptAll(); +// }); +// if (nums > 0) { +// void vscode.window.showInformationMessage(`Interrupted ${nums} running evaluation(s).`); +// } else { +// void vscode.window.showInformationMessage('Interruption command finished (unknown results)'); +// } +// outputWindow.discardPendingPrints(); +// return; +// } +// void vscode.window.showInformationMessage('Not connected to a REPL server'); +// } + +// async function addAsComment( +// c: number, +// result: string, +// codeSelection: vscode.Selection, +// editor: vscode.TextEditor, +// selection: vscode.Selection +// ) { +// const indent = `${' '.repeat(c)}`, +// output = result +// .replace(/\n\r?$/, '') +// .split(/\n\r?/) +// .join(`\n${indent};; `), +// edit = vscode.TextEdit.insert(codeSelection.end, `\n${indent};; => ${output}\n`), +// wsEdit = new vscode.WorkspaceEdit(); +// wsEdit.set(editor.document.uri, [edit]); +// await vscode.workspace.applyEdit(wsEdit); +// editor.selection = selection; +// } + +// async function evaluateCodeUpdatingUI( +// code: string, +// options, +// selection?: vscode.Selection +// ): Promise { +// const pprintOptions = options.pprintOptions || getConfig().prettyPrintingOptions; +// // passed options overwrite config options +// const evaluationSendCodeToOutputWindow = +// (options.evaluationSendCodeToOutputWindow === undefined || +// options.evaluationSendCodeToOutputWindow === true) && +// getConfig().evaluationSendCodeToOutputWindow; +// const addToHistory = +// (options.addToHistory === undefined || options.addToHistory === true) && +// (evaluationSendCodeToOutputWindow || +// state.extensionContext.workspaceState.get('outputWindowActive')); +// const showErrorMessage = +// options.showErrorMessage === undefined || options.showErrorMessage === true; +// const showResult = options.showResult === undefined || options.showResult === true; +// const line = options.line; +// const column = options.column; +// const filePath = options.filePath; +// const session: NReplSession = options.session; +// const ns = options.ns; +// const editor = util.getActiveTextEditor(); +// let result = null; + +// if (code.length > 0) { +// if (addToHistory) { +// replHistory.addToReplHistory(session.replType, code); +// replHistory.resetState(); +// } + +// const err: string[] = []; + +// if (outputWindow.getNs() !== ns) { +// await session.switchNS(ns); +// } + +// const context: NReplEvaluation = session.eval(code, ns, { +// file: filePath, +// line: line + 1, +// column: column + 1, +// stdout: (m) => { +// outputWindow.append(normalizeNewLines(m)); +// }, +// stderr: (m) => err.push(m), +// pprintOptions: pprintOptions, +// }); + +// try { +// if (evaluationSendCodeToOutputWindow) { +// outputWindow.append(code); +// } + +// let value = await context.value; +// value = util.stripAnsi(context.pprintOut || value); + +// result = value; + +// if (showResult) { +// outputWindow.append(value, async (resultLocation) => { +// if (selection) { +// const c = selection.start.character; +// if (options.replace) { +// const indent = `${' '.repeat(c)}`, +// edit = vscode.TextEdit.replace(selection, value.replace(/\n/gm, '\n' + indent)), +// wsEdit = new vscode.WorkspaceEdit(); +// wsEdit.set(editor.document.uri, [edit]); +// void vscode.workspace.applyEdit(wsEdit); +// } else { +// if (options.comment) { +// await addAsComment(c, value, selection, editor, editor.selection); +// } +// if (!outputWindow.isResultsDoc(editor.document)) { +// annotations.decorateSelection( +// value, +// selection, +// editor, +// editor.selection.active, +// resultLocation, +// annotations.AnnotationStatus.SUCCESS +// ); +// if (!options.comment) { +// annotations.decorateResults(value, false, selection, editor); +// } +// } +// } +// } +// }); +// // May need to move this inside of onResultsAppended callback above, depending on desired ordering of appended results +// if (err.length > 0) { +// const errMsg = `; ${normalizeNewLinesAndJoin(err, true)}`; +// if (context.stacktrace) { +// outputWindow.saveStacktrace(context.stacktrace); +// outputWindow.append(errMsg, (_, afterResultLocation) => { +// outputWindow.markLastStacktraceRange(afterResultLocation); +// }); +// } else { +// outputWindow.append(errMsg); +// } +// } +// } +// } catch (e) { +// if (showErrorMessage) { +// const outputWindowError = err.length +// ? `; ${normalizeNewLinesAndJoin(err, true)}` +// : formatAsLineComments(e); +// outputWindow.append(outputWindowError, async (resultLocation, afterResultLocation) => { +// if (selection) { +// const editorError = util.stripAnsi(err.length ? err.join('\n') : e); +// const currentCursorPos = editor.selection.active; +// if (options.comment) { +// await addAsComment( +// selection.start.character, +// editorError, +// selection, +// editor, +// editor.selection +// ); +// } +// if (!outputWindow.isResultsDoc(editor.document)) { +// annotations.decorateSelection( +// editorError, +// selection, +// editor, +// currentCursorPos, +// resultLocation, +// annotations.AnnotationStatus.ERROR +// ); +// if (!options.comment) { +// annotations.decorateResults(editorError, true, selection, editor); +// } +// } +// } +// if (context.stacktrace && context.stacktrace.stacktrace) { +// outputWindow.markLastStacktraceRange(afterResultLocation); +// } +// }); +// if (context.stacktrace && context.stacktrace.stacktrace) { +// outputWindow.saveStacktrace(context.stacktrace.stacktrace); +// } +// } +// } +// outputWindow.setSession(session, context.ns || ns); +// replSession.updateReplSessionType(); +// } + +// return result; +// } + +// async function evaluateSelection(document = {}, options) { +// const selectionFn: (editor: vscode.TextEditor) => [vscode.Selection, string] = +// options.selectionFn; + +// if (getStateValue('connected')) { +// const editor = util.getActiveTextEditor(); +// state.analytics().logEvent('Evaluation', 'selectionFn').send(); +// const selection = selectionFn(editor); +// const codeSelection: vscode.Selection = selection[0]; +// let code = selection[1]; +// [codeSelection, code]; + +// const doc = util.getDocument(document); +// const ns = namespace.getNamespace(doc); +// const line = codeSelection.start.line; +// const column = codeSelection.start.character; +// const filePath = doc.fileName; +// const session = replSession.getSession(util.getFileType(doc)); + +// if (code.length > 0) { +// if (options.debug) { +// code = '#dbg\n' + code; +// } +// annotations.decorateSelection( +// '', +// codeSelection, +// editor, +// undefined, +// undefined, +// annotations.AnnotationStatus.PENDING +// ); +// await evaluateCodeUpdatingUI( +// code, +// { ...options, ns, line, column, filePath, session }, +// codeSelection +// ); +// outputWindow.appendPrompt(); +// } +// } else { +// void vscode.window.showErrorMessage('Not connected to a REPL'); +// } +// } + +// function printWarningForError(e: any) { +// console.warn(`Unhandled error: ${e.message}`); +// } + +// function normalizeNewLines(str: string, asLineComment = false): string { +// const s = str.replace(/\n\r?$/, ''); +// return asLineComment ? s.replace(/\n\r?/, '\n; ') : s; +// } + +// function normalizeNewLinesAndJoin(strings: string[], asLineComment = false): string { +// return strings +// .map((s) => normalizeNewLines(s, asLineComment), asLineComment) +// .join(`\n${asLineComment ? '; ' : ''}`); +// } + +// function _currentSelectionElseCurrentForm(editor: vscode.TextEditor): getText.SelectionAndText { +// if (editor.selection.isEmpty) { +// return getText.currentFormText(editor?.document, editor.selection.active); +// } else { +// return [editor.selection, editor.document.getText(editor.selection)]; +// } +// } + +// function _currentTopLevelFormText(editor: vscode.TextEditor): getText.SelectionAndText { +// return getText.currentTopLevelFormText(editor?.document, editor?.selection.active); +// } + +// function _currentEnclosingFormText(editor: vscode.TextEditor): getText.SelectionAndText { +// return getText.currentEnclosingFormText(editor?.document, editor?.selection.active); +// } + +// function evaluateSelectionReplace(document = {}, options = {}) { +// evaluateSelection( +// document, +// Object.assign({}, options, { +// replace: true, +// pprintOptions: getConfig().prettyPrintingOptions, +// selectionFn: _currentSelectionElseCurrentForm, +// }) +// ).catch(printWarningForError); +// } + +// function evaluateSelectionAsComment(document = {}, options = {}) { +// evaluateSelection( +// document, +// Object.assign({}, options, { +// comment: true, +// pprintOptions: getConfig().prettyPrintingOptions, +// selectionFn: _currentSelectionElseCurrentForm, +// }) +// ).catch(printWarningForError); +// } + +// function evaluateTopLevelFormAsComment(document = {}, options = {}) { +// evaluateSelection( +// document, +// Object.assign({}, options, { +// comment: true, +// pprintOptions: getConfig().prettyPrintingOptions, +// selectionFn: _currentTopLevelFormText, +// }) +// ).catch(printWarningForError); +// } + +// function evaluateTopLevelForm(document = {}, options = {}) { +// evaluateSelection( +// document, +// Object.assign({}, options, { +// pprintOptions: getConfig().prettyPrintingOptions, +// selectionFn: _currentTopLevelFormText, +// }) +// ).catch(printWarningForError); +// } + +// function evaluateOutputWindowForm(document = {}, options = {}) { +// evaluateSelection( +// document, +// Object.assign({}, options, { +// pprintOptions: getConfig().prettyPrintingOptions, +// selectionFn: _currentTopLevelFormText, +// evaluationSendCodeToOutputWindow: false, +// addToHistory: true, +// }) +// ).catch(printWarningForError); +// } + +// function evaluateCurrentForm(document = {}, options = {}) { +// evaluateSelection( +// document, +// Object.assign({}, options, { +// pprintOptions: getConfig().prettyPrintingOptions, +// selectionFn: _currentSelectionElseCurrentForm, +// }) +// ).catch(printWarningForError); +// } + +// function evaluateEnclosingForm(document = {}, options = {}) { +// evaluateSelection( +// document, +// Object.assign({}, options, { +// pprintOptions: getConfig().prettyPrintingOptions, +// selectionFn: _currentEnclosingFormText, +// }) +// ).catch(printWarningForError); +// } + +// function evaluateUsingTextAndSelectionGetter( +// getter: (doc: vscode.TextDocument, pos: vscode.Position) => getText.SelectionAndText, +// formatter: (s: string) => string, +// document = {}, +// options = {} +// ) { +// evaluateSelection( +// document, +// Object.assign({}, options, { +// pprintOptions: getConfig().prettyPrintingOptions, +// selectionFn: (editor: vscode.TextEditor) => { +// const [selection, code] = getter(editor?.document, editor?.selection.active); +// return [selection, formatter(code)]; +// }, +// }) +// ).catch(printWarningForError); +// } + +// function evaluateToCursor(document = {}, options = {}) { +// evaluateUsingTextAndSelectionGetter( +// getText.currentEnclosingFormToCursor, +// (code) => `${code}`, +// document, +// options +// ); +// } + +// function evaluateTopLevelFormToCursor(document = {}, options = {}) { +// evaluateUsingTextAndSelectionGetter( +// getText.currentTopLevelFormToCursor, +// (code) => `${code}`, +// document, +// options +// ); +// } + +// function evaluateStartOfFileToCursor(document = {}, options = {}) { +// evaluateUsingTextAndSelectionGetter( +// getText.startOFileToCursor, +// (code) => `${code}`, +// document, +// options +// ); +// } + +// async function loadFile( +// document: vscode.TextDocument | Record | undefined, +// pprintOptions: PrettyPrintingOptions +// ) { +// const doc = util.tryToGetDocument(document); +// const fileType = util.getFileType(doc); +// const ns = namespace.getNamespace(doc); +// const session = replSession.getSession(util.getFileType(doc)); + +// if (doc && doc.languageId == 'clojure' && fileType != 'edn' && getStateValue('connected')) { +// state.analytics().logEvent('Evaluation', 'LoadFile').send(); +// const docUri = outputWindow.isResultsDoc(doc) +// ? await namespace.getUriForNamespace(session, ns) +// : doc.uri; +// const fileName = path.basename(docUri.path); +// const fileContents = await util.getFileContents(docUri.path); + +// outputWindow.append('; Evaluating file: ' + fileName); + +// await session.switchNS(ns); + +// const res = session.loadFile(fileContents, { +// fileName, +// filePath: docUri.path, +// stdout: (m) => outputWindow.append(normalizeNewLines(m)), +// stderr: (m) => outputWindow.append('; ' + normalizeNewLines(m, true)), +// pprintOptions: pprintOptions, +// }); +// try { +// const value = await res.value; +// if (value) { +// outputWindow.append(value); +// } else { +// outputWindow.append('; No results from file evaluation.'); +// } +// } catch (e) { +// outputWindow.append( +// `; Evaluation of file ${fileName} failed: ${e}`, +// (_location, nextLocation) => { +// if (res.stacktrace) { +// outputWindow.saveStacktrace(res.stacktrace.stacktrace); +// outputWindow.markLastStacktraceRange(nextLocation); +// } +// } +// ); +// } +// outputWindow.setSession(session, res.ns || ns); +// replSession.updateReplSessionType(); +// } +// } + +// async function evaluateUser(code: string) { +// const fileType = util.getFileType(util.tryToGetDocument({})), +// session = replSession.getSession(fileType); +// if (session) { +// try { +// await session.eval(code, session.client.ns).value; +// } catch (e) { +// const chan = state.outputChannel(); +// chan.appendLine(`Eval failure: ${e}`); +// } +// } else { +// void vscode.window.showInformationMessage('Not connected to a REPL server'); +// } +// } + +// async function requireREPLUtilitiesCommand() { +// if (util.getConnectedState()) { +// const chan = state.outputChannel(), +// ns = namespace.getDocumentNamespace(util.tryToGetDocument({})), +// CLJS_FORM = "(use '[cljs.repl :only [apropos dir doc find-doc print-doc pst source]])", +// CLJ_FORM = '(clojure.core/apply clojure.core/require clojure.main/repl-requires)', +// sessionType = replSession.getReplSessionTypeFromState(), +// form = sessionType == 'cljs' ? CLJS_FORM : CLJ_FORM, +// fileType = util.getFileType(util.tryToGetDocument({})), +// session = replSession.getSession(fileType); + +// if (session) { +// try { +// await namespace.createNamespaceFromDocumentIfNotExists(util.tryToGetDocument({})); +// await session.switchNS(ns); +// await session.eval(form, ns).value; +// chan.appendLine(`REPL utilities are now available in namespace ${ns}.`); +// } catch (e) { +// chan.appendLine(`REPL utilities could not be acquired for namespace ${ns}: ${e}`); +// } +// } +// } else { +// void vscode.window.showInformationMessage('Not connected to a REPL server'); +// } +// } + +// async function copyLastResultCommand() { +// const chan = state.outputChannel(); +// const session = replSession.getSession(util.getFileType(util.tryToGetDocument({}))); + +// const value = await session.eval('*1', session.client.ns).value; +// if (value !== null) { +// void vscode.env.clipboard.writeText(value); +// void vscode.window.showInformationMessage('Results copied to the clipboard.'); +// } else { +// chan.appendLine('Nothing to copy'); +// } +// } + +// async function togglePrettyPrint() { +// const config = vscode.workspace.getConfiguration('calva'), +// pprintConfigKey = 'prettyPrintingOptions', +// pprintOptions = config.get(pprintConfigKey); +// pprintOptions.enabled = !pprintOptions.enabled; +// if (pprintOptions.enabled && !(pprintOptions.printEngine || pprintOptions.printFn)) { +// pprintOptions.printEngine = 'pprint'; +// } +// await config.update(pprintConfigKey, pprintOptions, vscode.ConfigurationTarget.Global); +// statusbar.update(); +// } + +// async function toggleEvaluationSendCodeToOutputWindow() { +// const config = vscode.workspace.getConfiguration('calva'); +// await config.update( +// 'evaluationSendCodeToOutputWindow', +// !config.get('evaluationSendCodeToOutputWindow'), +// vscode.ConfigurationTarget.Global +// ); +// statusbar.update(); +// } + +// function instrumentTopLevelForm() { +// evaluateSelection( +// {}, +// { +// pprintOptions: getConfig().prettyPrintingOptions, +// debug: true, +// selectionFn: _currentTopLevelFormText, +// } +// ).catch(printWarningForError); +// state +// .analytics() +// .logEvent(DEBUG_ANALYTICS.CATEGORY, DEBUG_ANALYTICS.EVENT_ACTIONS.INSTRUMENT_FORM) +// .send(); +// } + +// export async function evaluateInOutputWindow( +// code: string, +// sessionType: string, +// ns: string, +// options +// ) { +// const outputDocument = await outputWindow.openResultsDoc(); +// const evalPos = outputDocument.positionAt(outputDocument.getText().length); +// try { +// const session = replSession.getSession(sessionType); +// replSession.updateReplSessionType(); +// if (outputWindow.getNs() !== ns) { +// await session.switchNS(ns); +// outputWindow.setSession(session, ns); +// if (options.evaluationSendCodeToOutputWindow !== false) { +// outputWindow.appendPrompt(); +// } +// } + +// return await evaluateCodeUpdatingUI(code, { +// ...options, +// filePath: outputDocument.fileName, +// session, +// ns, +// line: evalPos.line, +// column: evalPos.character, +// }); +// } catch (e) { +// outputWindow.append('; Evaluation failed.'); +// } +// } + +export type customREPLCommandSnippet = { + name: string; + key?: string; + snippet: string; + repl?: string; + ns?: string; +}; + +export default { + // interruptAllEvaluations, + // loadFile, + // evaluateCurrentForm, + // evaluateEnclosingForm, + // evaluateTopLevelForm, + // evaluateSelectionReplace, + // evaluateSelectionAsComment, + // evaluateTopLevelFormAsComment, + // evaluateToCursor, + // evaluateTopLevelFormToCursor, + // evaluateStartOfFileToCursor, + // evaluateUser, + // copyLastResultCommand, + // requireREPLUtilitiesCommand, + // togglePrettyPrint, + // toggleEvaluationSendCodeToOutputWindow, + // instrumentTopLevelForm, + // evaluateInOutputWindow, + // evaluateOutputWindowForm, +}; diff --git a/src/files-cache.ts b/src/files-cache.ts new file mode 100644 index 0000000..ba77eb0 --- /dev/null +++ b/src/files-cache.ts @@ -0,0 +1,77 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +// import * as state from './state'; +import * as path from 'path'; + +const filesCache: Map = new Map(); + +function writeToCache(uri: vscode.Uri) { + try { + const content: string = fs.readFileSync(uri.fsPath, 'utf8'); + filesCache.set(uri.fsPath, content); + } catch { + // if the file is not readable anymore then don't keep old content in cache + filesCache.delete(uri.fsPath); + } +} + +// From '../utilities' +function tryToGetActiveTextEditor(): vscode.TextEditor | undefined { + return vscode.window.activeTextEditor; +} + +function util_tryToGetDocument( + document: vscode.TextDocument | Record | undefined +): vscode.TextDocument | undefined { + const activeTextEditor = tryToGetActiveTextEditor(); + if (document && Object.prototype.hasOwnProperty.call(document, 'fileName')) { + return document as vscode.TextDocument; + } else if (activeTextEditor?.document && activeTextEditor.document.languageId !== 'Log') { + return activeTextEditor.document; + } else if (vscode.window.visibleTextEditors.length > 0) { + const editor = vscode.window.visibleTextEditors.find( + (editor) => editor.document && editor.document.languageId !== 'Log' + ); + return editor?.document; + } +} + +function getProjectWsFolder(): vscode.WorkspaceFolder | undefined { + const doc = util_tryToGetDocument({}); + if (doc) { + const folder = vscode.workspace.getWorkspaceFolder(doc.uri); + if (folder) { + return folder; + } + } + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { + return vscode.workspace.workspaceFolders[0]; + } + return undefined; +} + +export function resolvePath(filePath?: string) { + const root = getProjectWsFolder(); + if (filePath && path.isAbsolute(filePath)) { + return filePath; + } + return filePath && root && path.resolve(root.uri.fsPath, filePath); +} + +/** + * Tries to get content of cached file + * @param path - absolute or relative to the project + */ +export const content = (path: string | undefined) => { + const resolvedPath = resolvePath(path); + if (resolvedPath) { + if (!filesCache.has(resolvedPath)) { + writeToCache(vscode.Uri.file(resolvedPath)); + const filesWatcher = vscode.workspace.createFileSystemWatcher(resolvedPath); + filesWatcher.onDidChange(writeToCache); + filesWatcher.onDidCreate(writeToCache); + filesWatcher.onDidDelete((uri) => filesCache.delete(uri.fsPath)); + } + return filesCache.get(resolvedPath); + } +}; diff --git a/src/hyMain.ts b/src/hyMain.ts index e23abe9..0c640ec 100644 --- a/src/hyMain.ts +++ b/src/hyMain.ts @@ -3,6 +3,16 @@ import * as os from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; import {configuration} from './schemeConfiguration'; +import * as paredit from './paredit/extension'; +import * as fmt from './calva-fmt/src/extension'; +import * as model from './cursor-doc/model'; +import * as config from './config'; +import * as whenContexts from './when-contexts'; +import * as edit from './edit'; +import annotations from './providers/annotations'; +import * as util from './utilities'; +import * as state from './state'; +import status from './status'; const windows: boolean = os.platform() == 'win32'; @@ -51,6 +61,47 @@ function thenFocusTextEditor() { setTimeout(() => vscode.commands.executeCommand('workbench.action.focusActiveEditorGroup'), 250); } +async function onDidSave(testController: vscode.TestController, document: vscode.TextDocument) { + const { evaluate, test } = config.getConfig(); + + if (document.languageId !== 'hy') { + return; + } + + // if (test && util.getConnectedState()) { + // // void testRunner.runNamespaceTests(testController, document); + // state.analytics().logEvent('Calva', 'OnSaveTest').send(); + // } else if (evaluate) { + // if (!outputWindow.isResultsDoc(document)) { + // await eval.loadFile(document, config.getConfig().prettyPrintingOptions); + // outputWindow.appendPrompt(); + // state.analytics().logEvent('Calva', 'OnSaveLoad').send(); + // } + // } +} + +function onDidOpen(document) { + if (document.languageId !== 'hy') { + return; + } +} + +function onDidChangeEditorOrSelection(editor: vscode.TextEditor) { + // replHistory.setReplHistoryCommandsActiveContext(editor); + whenContexts.setCursorContextIfChanged(editor); +} + +function setKeybindingsEnabledContext() { + const keybindingsEnabled = vscode.workspace + .getConfiguration() + .get(config.KEYBINDINGS_ENABLED_CONFIG_KEY); + void vscode.commands.executeCommand( + 'setContext', + config.KEYBINDINGS_ENABLED_CONTEXT_KEY, + keybindingsEnabled + ); +} + export function activate(context: vscode.ExtensionContext) { console.log('Extension "vscode-hy" is now active!'); @@ -97,8 +148,70 @@ export function activate(context: vscode.ExtensionContext) { } )); - context.subscriptions.push(vscode.languages.setLanguageConfiguration('scheme', configuration)); + // Inspired by Calva + + context.subscriptions.push( + vscode.commands.registerCommand('hy.continueComment', edit.continueCommentCommand) + ); + + //EVENTS + context.subscriptions.push( + vscode.workspace.onDidOpenTextDocument((document) => { + onDidOpen(document); + }) + ); + context.subscriptions.push( + vscode.workspace.onDidSaveTextDocument((document) => { + // void onDidSave(controller, document); + }) + ); + context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor((editor) => { + status.update(); + onDidChangeEditorOrSelection(editor); + }) + ); + context.subscriptions.push( + vscode.workspace.onDidChangeTextDocument(annotations.onDidChangeTextDocument) + ); + + + model.initScanner(vscode.workspace.getConfiguration('editor').get('maxTokenizationLineLength')); + + // Initial set of the provided contexts + setKeybindingsEnabledContext(); + + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration((e: vscode.ConfigurationChangeEvent) => { + if (e.affectsConfiguration(config.KEYBINDINGS_ENABLED_CONFIG_KEY)) { + setKeybindingsEnabledContext(); + } + }) + ); + + try { + void fmt.activate(context); + } catch (e) { + console.error('Failed activating Formatter: ' + e.message); + } + + try { + paredit.activate(context); + } catch (e) { + console.error('Failed activating Paredit: ' + e.message); + } + + try { + void fmt.activate(context); + } catch (e) { + console.error('Failed activating Formatter: ' + e.message); + } + } export function deactivate() { + state.analytics().logEvent('LifeCycle', 'Deactivated').send(); + // jackIn.calvaJackout(); + return paredit.deactivate(); + // return lsp.deactivate(); } \ No newline at end of file diff --git a/src/paredit/extension.ts b/src/paredit/extension.ts new file mode 100644 index 0000000..def26dc --- /dev/null +++ b/src/paredit/extension.ts @@ -0,0 +1,464 @@ +'use strict'; +import { StatusBar } from './statusbar'; +import * as vscode from 'vscode'; +import { + commands, + window, + Event, + EventEmitter, + ExtensionContext, + workspace, + ConfigurationChangeEvent, +} from 'vscode'; +import * as paredit from '../cursor-doc/paredit'; +import * as docMirror from '../doc-mirror/index'; +import { EditableDocument } from '../cursor-doc/model'; +// import { assertIsDefined } from '../utilities'; + +const onPareditKeyMapChangedEmitter = new EventEmitter(); + +const languages = new Set(['clojure', 'lisp', 'scheme', 'hy']); +const enabled = true; + +/** + * Copies the text represented by the range from doc to the clipboard. + * @param doc + * @param range + */ +async function copyRangeToClipboard(doc: EditableDocument, [start, end]) { + const text = doc.model.getText(start, end); + await vscode.env.clipboard.writeText(text); +} + +/** + * Answers true when `calva.paredit.killAlsoCutsToClipboard` is enabled. + * @returns boolean + */ +function shouldKillAlsoCutToClipboard() { + return workspace.getConfiguration().get('hy.paredit.killAlsoCutsToClipboard'); +} + +function assertIsDefined( + value: T | undefined | null, + message: string | (() => string) + ): asserts value is T { + if (value === null || value === undefined) { + throw new Error(typeof message === 'string' ? message : message()); + } + } + +type PareditCommand = { + command: string; + handler: (doc: EditableDocument) => void | Promise; +}; +const pareditCommands: PareditCommand[] = [ + // NAVIGATING + { + command: 'hy.paredit.forwardSexp', + handler: (doc: EditableDocument) => { + paredit.moveToRangeRight(doc, paredit.forwardSexpRange(doc)); + }, + }, + { + command: 'hy.paredit.backwardSexp', + handler: (doc: EditableDocument) => { + paredit.moveToRangeLeft(doc, paredit.backwardSexpRange(doc)); + }, + }, + { + command: 'hy.paredit.forwardDownSexp', + handler: (doc: EditableDocument) => { + paredit.moveToRangeRight(doc, paredit.rangeToForwardDownList(doc)); + }, + }, + { + command: 'hy.paredit.backwardDownSexp', + handler: (doc: EditableDocument) => { + paredit.moveToRangeLeft(doc, paredit.rangeToBackwardDownList(doc)); + }, + }, + { + command: 'hy.paredit.forwardUpSexp', + handler: (doc: EditableDocument) => { + paredit.moveToRangeRight(doc, paredit.rangeToForwardUpList(doc)); + }, + }, + { + command: 'hy.paredit.backwardUpSexp', + handler: (doc: EditableDocument) => { + paredit.moveToRangeLeft(doc, paredit.rangeToBackwardUpList(doc)); + }, + }, + { + command: 'hy.paredit.forwardSexpOrUp', + handler: (doc: EditableDocument) => { + paredit.moveToRangeRight(doc, paredit.forwardSexpOrUpRange(doc)); + }, + }, + { + command: 'hy.paredit.backwardSexpOrUp', + handler: (doc: EditableDocument) => { + paredit.moveToRangeLeft(doc, paredit.backwardSexpOrUpRange(doc)); + }, + }, + { + command: 'hy.paredit.closeList', + handler: (doc: EditableDocument) => { + paredit.moveToRangeRight(doc, paredit.rangeToForwardList(doc)); + }, + }, + { + command: 'hy.paredit.openList', + handler: (doc: EditableDocument) => { + paredit.moveToRangeLeft(doc, paredit.rangeToBackwardList(doc)); + }, + }, + + // SELECTING + { + command: 'hy.paredit.rangeForDefun', + handler: (doc: EditableDocument) => { + paredit.selectRange(doc, paredit.rangeForDefun(doc)); + }, + }, + { + command: 'hy.paredit.sexpRangeExpansion', + handler: paredit.growSelection, + }, // TODO: Inside string should first select contents + { + command: 'hy.paredit.sexpRangeContraction', + handler: paredit.shrinkSelection, + }, + + { + command: 'hy.paredit.selectForwardSexp', + handler: paredit.selectForwardSexp, + }, + { + command: 'hy.paredit.selectRight', + handler: paredit.selectRight, + }, + { + command: 'hy.paredit.selectBackwardSexp', + handler: paredit.selectBackwardSexp, + }, + { + command: 'hy.paredit.selectForwardDownSexp', + handler: paredit.selectForwardDownSexp, + }, + { + command: 'hy.paredit.selectBackwardDownSexp', + handler: paredit.selectBackwardDownSexp, + }, + { + command: 'hy.paredit.selectForwardUpSexp', + handler: paredit.selectForwardUpSexp, + }, + { + command: 'hy.paredit.selectForwardSexpOrUp', + handler: paredit.selectForwardSexpOrUp, + }, + { + command: 'hy.paredit.selectBackwardSexpOrUp', + handler: paredit.selectBackwardSexpOrUp, + }, + { + command: 'hy.paredit.selectBackwardUpSexp', + handler: paredit.selectBackwardUpSexp, + }, + { + command: 'hy.paredit.selectCloseList', + handler: paredit.selectCloseList, + }, + { + command: 'hy.paredit.selectOpenList', + handler: paredit.selectOpenList, + }, + + // EDITING + { + command: 'hy.paredit.slurpSexpForward', + handler: paredit.forwardSlurpSexp, + }, + { + command: 'hy.paredit.barfSexpForward', + handler: paredit.forwardBarfSexp, + }, + { + command: 'hy.paredit.slurpSexpBackward', + handler: paredit.backwardSlurpSexp, + }, + { + command: 'hy.paredit.barfSexpBackward', + handler: paredit.backwardBarfSexp, + }, + { + command: 'hy.paredit.splitSexp', + handler: paredit.splitSexp, + }, + { + command: 'hy.paredit.joinSexp', + handler: paredit.joinSexp, + }, + { + command: 'hy.paredit.spliceSexp', + handler: paredit.spliceSexp, + }, + // ['paredit.transpose', ], // TODO: Not yet implemented + { + command: 'hy.paredit.raiseSexp', + handler: paredit.raiseSexp, + }, + { + command: 'hy.paredit.transpose', + handler: paredit.transpose, + }, + { + command: 'hy.paredit.dragSexprBackward', + handler: paredit.dragSexprBackward, + }, + { + command: 'hy.paredit.dragSexprForward', + handler: paredit.dragSexprForward, + }, + { + command: 'hy.paredit.dragSexprBackwardUp', + handler: paredit.dragSexprBackwardUp, + }, + { + command: 'hy.paredit.dragSexprForwardDown', + handler: paredit.dragSexprForwardDown, + }, + { + command: 'hy.paredit.dragSexprForwardUp', + handler: paredit.dragSexprForwardUp, + }, + { + command: 'hy.paredit.dragSexprBackwardDown', + handler: paredit.dragSexprBackwardDown, + }, + { + command: 'hy.paredit.convolute', + handler: paredit.convolute, + }, + { + command: 'hy.paredit.killRight', + handler: async (doc: EditableDocument) => { + const range = paredit.forwardHybridSexpRange(doc); + if (shouldKillAlsoCutToClipboard()) { + await copyRangeToClipboard(doc, range); + } + return paredit.killRange(doc, range); + }, + }, + { + command: 'hy.paredit.killSexpForward', + handler: async (doc: EditableDocument) => { + const range = paredit.forwardSexpRange(doc); + if (shouldKillAlsoCutToClipboard()) { + await copyRangeToClipboard(doc, range); + } + return paredit.killRange(doc, range); + }, + }, + { + command: 'hy.paredit.killSexpBackward', + handler: async (doc: EditableDocument) => { + const range = paredit.backwardSexpRange(doc); + if (shouldKillAlsoCutToClipboard()) { + await copyRangeToClipboard(doc, range); + } + return paredit.killRange(doc, range); + }, + }, + { + command: 'hy.paredit.killListForward', + handler: async (doc: EditableDocument) => { + const range = paredit.forwardListRange(doc); + if (shouldKillAlsoCutToClipboard()) { + await copyRangeToClipboard(doc, range); + } + return await paredit.killForwardList(doc, range); + }, + }, // TODO: Implement with killRange + { + command: 'hy.paredit.killListBackward', + handler: async (doc: EditableDocument) => { + const range = paredit.backwardListRange(doc); + if (shouldKillAlsoCutToClipboard()) { + await copyRangeToClipboard(doc, range); + } + return await paredit.killBackwardList(doc, range); + }, + }, // TODO: Implement with killRange + { + command: 'hy.paredit.spliceSexpKillForward', + handler: async (doc: EditableDocument) => { + const range = paredit.forwardListRange(doc); + if (shouldKillAlsoCutToClipboard()) { + await copyRangeToClipboard(doc, range); + } + await paredit.killForwardList(doc, range).then((isFulfilled) => { + return paredit.spliceSexp(doc, doc.selection.active, false); + }); + }, + }, + { + command: 'hy.paredit.spliceSexpKillBackward', + handler: async (doc: EditableDocument) => { + const range = paredit.backwardListRange(doc); + if (shouldKillAlsoCutToClipboard()) { + await copyRangeToClipboard(doc, range); + } + await paredit.killBackwardList(doc, range).then((isFulfilled) => { + return paredit.spliceSexp(doc, doc.selection.active, false); + }); + }, + }, + { + command: 'hy.paredit.wrapAroundParens', + handler: (doc: EditableDocument) => { + return paredit.wrapSexpr(doc, '(', ')'); + }, + }, + { + command: 'hy.paredit.wrapAroundSquare', + handler: (doc: EditableDocument) => { + return paredit.wrapSexpr(doc, '[', ']'); + }, + }, + { + command: 'hy.paredit.wrapAroundCurly', + handler: (doc: EditableDocument) => { + return paredit.wrapSexpr(doc, '{', '}'); + }, + }, + { + command: 'hy.paredit.wrapAroundQuote', + handler: (doc: EditableDocument) => { + return paredit.wrapSexpr(doc, '"', '"'); + }, + }, + { + command: 'hy.paredit.rewrapParens', + handler: (doc: EditableDocument) => { + return paredit.rewrapSexpr(doc, '(', ')'); + }, + }, + { + command: 'hy.paredit.rewrapSquare', + handler: (doc: EditableDocument) => { + return paredit.rewrapSexpr(doc, '[', ']'); + }, + }, + { + command: 'hy.paredit.rewrapCurly', + handler: (doc: EditableDocument) => { + return paredit.rewrapSexpr(doc, '{', '}'); + }, + }, + { + command: 'hy.paredit.rewrapQuote', + handler: (doc: EditableDocument) => { + return paredit.rewrapSexpr(doc, '"', '"'); + }, + }, + { + command: 'hy.paredit.deleteForward', + handler: async (doc: EditableDocument) => { + await paredit.deleteForward(doc); + }, + }, + { + command: 'hy.paredit.deleteBackward', + handler: async (doc: EditableDocument) => { + await paredit.backspace(doc); + }, + }, + { + command: 'hy.paredit.forceDeleteForward', + handler: () => { + return vscode.commands.executeCommand('deleteRight'); + }, + }, + { + command: 'hy.paredit.forceDeleteBackward', + handler: () => { + return vscode.commands.executeCommand('deleteLeft'); + }, + }, + { + command: 'hy.paredit.addRichComment', + handler: async (doc: EditableDocument) => { + await paredit.addRichComment(doc); + }, + }, +]; + +function wrapPareditCommand(command: PareditCommand) { + return async () => { + try { + const textEditor = window.activeTextEditor; + + assertIsDefined(textEditor, 'Expected window to have an activeTextEditor!'); + + const mDoc: EditableDocument = docMirror.getDocument(textEditor.document); + if (!enabled || !languages.has(textEditor.document.languageId)) { + return; + } + return command.handler(mDoc); + } catch (e) { + console.error(e.message); + } + }; +} + +export function getKeyMapConf(): string { + const keyMap = workspace.getConfiguration().get('hy.paredit.defaultKeyMap'); + return String(keyMap); +} + +function setKeyMapConf() { + const keyMap = workspace.getConfiguration().get('hy.paredit.defaultKeyMap'); + void commands.executeCommand('setContext', 'paredit:keyMap', keyMap); + onPareditKeyMapChangedEmitter.fire(String(keyMap)); +} +setKeyMapConf(); + +export function activate(context: ExtensionContext) { + const statusBar = new StatusBar(getKeyMapConf()); + + context.subscriptions.push( + statusBar, + commands.registerCommand('paredit.togglemode', () => { + let keyMap = workspace.getConfiguration().get('hy.paredit.defaultKeyMap'); + keyMap = String(keyMap).trim().toLowerCase(); + if (keyMap == 'original') { + void workspace + .getConfiguration() + .update('hy.paredit.defaultKeyMap', 'strict', vscode.ConfigurationTarget.Global); + } else if (keyMap == 'strict') { + void workspace + .getConfiguration() + .update('hy.paredit.defaultKeyMap', 'original', vscode.ConfigurationTarget.Global); + } + }), + window.onDidChangeActiveTextEditor( + (e) => e && e.document && languages.has(e.document.languageId) + ), + workspace.onDidChangeConfiguration((e: ConfigurationChangeEvent) => { + if (e.affectsConfiguration('hy.paredit.defaultKeyMap')) { + setKeyMapConf(); + } + }), + ...pareditCommands.map((command) => + commands.registerCommand(command.command, wrapPareditCommand(command)) + ) + ); +} + +export function deactivate() { + // do nothing +} + +export const onPareditKeyMapChanged: Event = onPareditKeyMapChangedEmitter.event; diff --git a/src/paredit/statusbar.ts b/src/paredit/statusbar.ts new file mode 100644 index 0000000..b6a08f2 --- /dev/null +++ b/src/paredit/statusbar.ts @@ -0,0 +1,77 @@ +'use strict'; +import { window, StatusBarAlignment, StatusBarItem } from 'vscode'; +// import statusbar from '../statusbar'; +import * as paredit from './extension'; + +const color = { + active: 'white', + inactive: '#b3b3b3', + }; + +export class StatusBar { + private _visible: boolean; + private _keyMap: string; + + private _toggleBarItem: StatusBarItem; + + constructor(keymap: string) { + this._toggleBarItem = window.createStatusBarItem(StatusBarAlignment.Right); + this._toggleBarItem.text = '(λ)'; + this._toggleBarItem.tooltip = ''; + this._toggleBarItem.command = 'paredit.togglemode'; + this._visible = false; + this.keyMap = keymap; + + paredit.onPareditKeyMapChanged((keymap) => { + this.keyMap = keymap; + }); + } + + get keyMap() { + return this._keyMap; + } + + set keyMap(keymap: string) { + this._keyMap = keymap; + this.updateUIState(); + } + + updateUIState() { + switch (this.keyMap.trim().toLowerCase()) { + case 'original': + this.visible = true; + this._toggleBarItem.text = '(λ)'; + this._toggleBarItem.tooltip = 'Toggle to Strict Mode'; + this._toggleBarItem.color = undefined; + break; + case 'strict': + this.visible = true; + this._toggleBarItem.text = '[λ]'; + this._toggleBarItem.tooltip = 'Toggle to Original Mode'; + this._toggleBarItem.color = undefined; + break; + default: + this.visible = true; + this._toggleBarItem.text = 'λ'; + this._toggleBarItem.tooltip = + 'hy Paredit Keymap is set to none, Toggle to Strict Mode is Disabled'; + this._toggleBarItem.color = color.inactive; + } + } + + get visible(): boolean { + return this._visible; + } + + set visible(value: boolean) { + if (value) { + this._toggleBarItem.show(); + } else { + this._toggleBarItem.hide(); + } + } + + dispose() { + this._toggleBarItem.dispose(); + } +} diff --git a/src/providers/annotations.ts b/src/providers/annotations.ts new file mode 100644 index 0000000..173779e --- /dev/null +++ b/src/providers/annotations.ts @@ -0,0 +1,206 @@ +import * as vscode from 'vscode'; +import * as _ from 'lodash'; +import * as util from '../utilities'; + +enum AnnotationStatus { + PENDING = 0, + SUCCESS, + ERROR, + REPL_WINDOW, +} + +const selectionBackgrounds = [ + 'rgba(197, 197, 197, 0.07)', + 'rgba(63, 255, 63, 0.05)', + 'rgba(255, 63, 63, 0.06)', + 'rgba(63, 63, 255, 0.1)', +]; + +const selectionRulerColors = ['gray', 'green', 'red', 'blue']; + +const evalResultsDecorationType = vscode.window.createTextEditorDecorationType({ + after: { + textDecoration: 'none', + fontWeight: 'normal', + fontStyle: 'normal', + }, + rangeBehavior: vscode.DecorationRangeBehavior.ClosedOpen, +}); + +let resultsLocations: [vscode.Range, vscode.Position, vscode.Location][] = []; + +function getResultsLocation(pos: vscode.Position): vscode.Location | undefined { + for (const [range, _evaluatePosition, location] of resultsLocations) { + if (range.contains(pos)) { + return location; + } + } +} + +function getEvaluationPosition(pos: vscode.Position): vscode.Position | undefined { + for (const [range, evaluatePosition, _location] of resultsLocations) { + if (range.contains(pos)) { + return evaluatePosition; + } + } +} + +function evaluated(contentText, hoverText, hasError) { + return { + renderOptions: { + after: { + contentText: contentText.replace(/ /g, '\u00a0'), + overflow: 'hidden', + }, + light: { + after: { + color: hasError ? 'rgb(255, 127, 127)' : 'black', + }, + }, + dark: { + after: { + color: hasError ? 'rgb(255, 175, 175)' : 'white', + }, + }, + }, + }; +} + +function createEvalSelectionDecorationType(status: AnnotationStatus) { + return vscode.window.createTextEditorDecorationType({ + backgroundColor: selectionBackgrounds[status], + overviewRulerColor: selectionRulerColors[status], + overviewRulerLane: vscode.OverviewRulerLane.Right, + rangeBehavior: vscode.DecorationRangeBehavior.OpenOpen, + }); +} + +const evalSelectionDecorationTypes = [ + createEvalSelectionDecorationType(AnnotationStatus.PENDING), + createEvalSelectionDecorationType(AnnotationStatus.SUCCESS), + createEvalSelectionDecorationType(AnnotationStatus.ERROR), + createEvalSelectionDecorationType(AnnotationStatus.REPL_WINDOW), +]; + +function setResultDecorations(editor: vscode.TextEditor, ranges) { + const key = editor.document.uri + ':resultDecorationRanges'; + util.cljsLib.setStateValue(key, ranges); + editor.setDecorations(evalResultsDecorationType, ranges); +} + +function setSelectionDecorations(editor, ranges, status) { + const key = editor.document.uri + ':selectionDecorationRanges:' + status; + util.cljsLib.setStateValue(key, ranges); + editor.setDecorations(evalSelectionDecorationTypes[status], ranges); +} + +function clearEvaluationDecorations(editor?: vscode.TextEditor) { + editor = editor || util.tryToGetActiveTextEditor(); + if (editor) { + util.cljsLib.removeStateValue(editor.document.uri + ':resultDecorationRanges'); + setResultDecorations(editor, []); + for (const status of [ + AnnotationStatus.PENDING, + AnnotationStatus.SUCCESS, + AnnotationStatus.ERROR, + AnnotationStatus.REPL_WINDOW, + ]) { + util.cljsLib.removeStateValue(editor.document.uri + ':selectionDecorationRanges:' + status); + setSelectionDecorations(editor, [], status); + } + } + resultsLocations = []; +} + +function clearAllEvaluationDecorations() { + vscode.window.visibleTextEditors.forEach((editor) => { + clearEvaluationDecorations(editor); + }); +} + +function decorateResults( + resultString, + hasError, + codeSelection: vscode.Range, + editor: vscode.TextEditor +) { + const uri = editor.document.uri; + const key = uri + ':resultDecorationRanges'; + let decorationRanges = util.cljsLib.getStateValue(key) || []; + const decoration = evaluated(` => ${resultString} `, resultString, hasError); + decorationRanges = _.filter(decorationRanges, (o) => { + return !o.codeRange.intersection(codeSelection); + }); + decoration['codeRange'] = codeSelection; + decoration['range'] = new vscode.Selection(codeSelection.end, codeSelection.end); + decorationRanges.push(decoration); + setResultDecorations(editor, decorationRanges); +} + +function decorateSelection( + resultString: string, + codeSelection: vscode.Selection, + editor: vscode.TextEditor, + evaluatePosition: vscode.Position, + resultsLocation, + status: AnnotationStatus +) { + const uri = editor.document.uri; + const key = uri + ':selectionDecorationRanges:' + status; + const decoration = {}; + let decorationRanges = util.cljsLib.getStateValue(key) || []; + decorationRanges = _.filter(decorationRanges, (o) => { + return !o.range.intersection(codeSelection); + }); + decoration['range'] = codeSelection; + if (status != AnnotationStatus.PENDING && status != AnnotationStatus.REPL_WINDOW) { + const copyCommandUri = `command:hy.copyAnnotationHoverText?${encodeURIComponent( + JSON.stringify([{ text: resultString }]) + )}`, + copyCommandMd = `[Copy](${copyCommandUri} "Copy results to the clipboard")`; + const openWindowCommandUri = `command:hy.showOutputWindow`, + openWindowCommandMd = `[Open Output Window](${openWindowCommandUri} "Open the output window")`; + const hoverMessage = new vscode.MarkdownString( + `${copyCommandMd} | ${openWindowCommandMd}\n` + '```clojure\n' + resultString + '\n```' + ); + hoverMessage.isTrusted = true; + decoration['hoverMessage'] = status == AnnotationStatus.ERROR ? resultString : hoverMessage; + } + // for (let s = 0; s < evalSelectionDecorationTypes.length; s++) { + // setSelectionDecorations(editor, [], s);. + // } + setSelectionDecorations(editor, [], status); + decorationRanges.push(decoration); + setSelectionDecorations(editor, decorationRanges, status); + if (status == AnnotationStatus.SUCCESS || status == AnnotationStatus.ERROR) { + resultsLocations.push([codeSelection, evaluatePosition, resultsLocation]); + } +} + +function onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { + if (event.contentChanges.length) { + const activeTextEditor: vscode.TextEditor | undefined = util.tryToGetActiveTextEditor(); + if (activeTextEditor) { + const activeDocument = activeTextEditor.document, + changeDocument = event.document; + if (activeDocument.uri == changeDocument.uri) { + clearEvaluationDecorations(activeTextEditor); + } + } + } +} + +function copyHoverTextCommand(args: { [x: string]: string }) { + void vscode.env.clipboard.writeText(args['text']); +} +export default { + AnnotationStatus, + clearEvaluationDecorations, + clearAllEvaluationDecorations, + copyHoverTextCommand, + decorateResults, + decorateSelection, + onDidChangeTextDocument, + getResultsLocation, + getEvaluationPosition, +}; diff --git a/src/state.ts b/src/state.ts new file mode 100644 index 0000000..86c3a02 --- /dev/null +++ b/src/state.ts @@ -0,0 +1,174 @@ +import * as vscode from 'vscode'; +import Analytics from './analytics'; +// import * as util from './utilities'; +// import * as path from 'path'; +// import * as os from 'os'; +import { getStateValue, setStateValue } from '../out/cljs-lib/cljs-lib'; +// import * as projectRoot from './project-root'; + +let extensionContext: vscode.ExtensionContext; +// export function setExtensionContext(context: vscode.ExtensionContext) { +// extensionContext = context; +// if (context.workspaceState.get('selectedCljTypeName') == undefined) { +// void context.workspaceState.update('selectedCljTypeName', 'unknown'); +// } +// } + +// Super-quick fix for: https://github.com/BetterThanTomorrow/calva/issues/144 +// TODO: Revisit the whole state management business. +// function _outputChannel(name: string): vscode.OutputChannel { +// const channel = getStateValue(name); +// if (channel.toJS !== undefined) { +// return channel.toJS(); +// } else { +// return channel; +// } +// } + +// function outputChannel(): vscode.OutputChannel { +// return _outputChannel('outputChannel'); +// } + +// function connectionLogChannel(): vscode.OutputChannel { +// return _outputChannel('connectionLogChannel'); +// } + +function analytics(): Analytics { + const analytics = getStateValue('analytics'); + if (analytics.toJS !== undefined) { + return analytics.toJS(); + } else { + return analytics; + } +} + +// const PROJECT_DIR_KEY = 'connect.projectDir'; +// const PROJECT_DIR_URI_KEY = 'connect.projectDirNew'; +const PROJECT_CONFIG_MAP = 'config'; + +// export function getProjectRootLocal(useCache = true): string | undefined { +// if (useCache) { +// return getStateValue(PROJECT_DIR_KEY); +// } +// } + +export function getProjectConfig(useCache = true) { + if (useCache) { + return getStateValue(PROJECT_CONFIG_MAP); + } +} + +// export function setProjectConfig(config) { +// return setStateValue(PROJECT_CONFIG_MAP, config); +// } + +// export function getProjectRootUri(useCache = true): vscode.Uri | undefined { +// if (useCache) { +// return getStateValue(PROJECT_DIR_URI_KEY); +// } +// } + +// const NON_PROJECT_DIR_KEY = 'hy.connect.nonProjectDir'; + +// export async function getNonProjectRootDir( +// context: vscode.ExtensionContext +// ): Promise { +// let root: vscode.Uri | undefined = undefined; +// if (!process.env['NEW_DRAMS']) { +// root = await context.globalState.get>(NON_PROJECT_DIR_KEY); +// } +// if (root) { +// const createNewOption = 'Create new temp directory, download new files'; +// const useExistingOption = 'Use existing temp directory, reuse any existing files'; +// root = await vscode.window +// .showQuickPick([useExistingOption, createNewOption], { +// placeHolder: 'Reuse the existing REPL temp dir and its files?', +// }) +// .then((option) => { +// return option === useExistingOption ? root : undefined; +// }); +// } +// if (typeof root === 'object') { +// root = vscode.Uri.file(root.path); +// } +// return root; +// } + +// export async function setNonProjectRootDir(context: vscode.ExtensionContext, root: vscode.Uri) { +// await context.globalState.update(NON_PROJECT_DIR_KEY, root); +// } + +// export async function setOrCreateNonProjectRoot( +// context: vscode.ExtensionContext, +// preferProjectDir = false +// ): Promise { +// let root: vscode.Uri | undefined = undefined; +// if (preferProjectDir) { +// root = getProjectRootUri(); +// } +// if (!root) { +// root = await getNonProjectRootDir(context); +// } +// if (!root) { +// const subDir = util.randomSlug(); +// root = vscode.Uri.file(path.join(util.calvaTmpDir(), subDir)); +// await setNonProjectRootDir(context, root); +// } +// await setStateValue(PROJECT_DIR_KEY, path.resolve(root.fsPath ? root.fsPath : root.path)); +// await setStateValue(PROJECT_DIR_URI_KEY, root); +// return root; +// } + +// function getProjectWsFolder(): vscode.WorkspaceFolder | undefined { +// const doc = util.tryToGetDocument({}); +// if (doc) { +// const folder = vscode.workspace.getWorkspaceFolder(doc.uri); +// if (folder) { +// return folder; +// } +// } +// if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { +// return vscode.workspace.workspaceFolders[0]; +// } +// return undefined; +// } + +/** + * Figures out the current clojure project root, and stores it in Calva state + */ +// export async function initProjectDir(uri?: vscode.Uri): Promise { +// if (uri) { +// setStateValue(PROJECT_DIR_KEY, path.resolve(uri.fsPath)); +// setStateValue(PROJECT_DIR_URI_KEY, uri); +// } else { +// const candidatePaths = await projectRoot.findProjectRootPaths(); +// const closestRootPath = await projectRoot.findClosestProjectRootPath(candidatePaths); +// const projectRootPath = await projectRoot.pickProjectRootPath(candidatePaths, closestRootPath); +// if (projectRootPath !== undefined) { +// setStateValue(PROJECT_DIR_KEY, projectRootPath); +// setStateValue(PROJECT_DIR_URI_KEY, vscode.Uri.file(projectRootPath)); +// } else { +// await setOrCreateNonProjectRoot(extensionContext, true); +// } +// } +// } + +/** + * + * Tries to resolve absolute path in relation to project root + * @param filePath - absolute or relative to the project + */ +// export function resolvePath(filePath?: string) { +// const root = getProjectWsFolder(); +// if (filePath && path.isAbsolute(filePath)) { +// return filePath; +// } +// return filePath && root && path.resolve(root.uri.fsPath, filePath); +// } + +export { + extensionContext, + // outputChannel, + // connectionLogChannel, + analytics +}; diff --git a/src/status.ts b/src/status.ts new file mode 100644 index 0000000..0bc1985 --- /dev/null +++ b/src/status.ts @@ -0,0 +1,26 @@ +import * as vscode from 'vscode'; +// import statusbar from './statusbar'; +import * as state from './state'; +import { getConfig } from './config'; +// import { updateReplSessionType } from './nrepl/repl-session'; + +// function updateNeedReplUi(isNeeded: boolean, context = state.extensionContext) { +// void context.workspaceState.update('needReplUi', isNeeded); +// update(context); +// } + +// function shouldshowReplUi(context = state.extensionContext): boolean { +// return context.workspaceState.get('needReplUi') || !getConfig().hideReplUi; +// } + +function update(context = state.extensionContext) { + // void vscode.commands.executeCommand('setContext', 'calva:showReplUi', shouldshowReplUi(context)); + // updateReplSessionType(); + // statusbar.update(context); +} + +export default { + update, + // updateNeedReplUi, + // shouldshowReplUi, +}; diff --git a/src/utilities.ts b/src/utilities.ts new file mode 100644 index 0000000..9cb49b7 --- /dev/null +++ b/src/utilities.ts @@ -0,0 +1,586 @@ +import * as vscode from 'vscode'; +// import { https } from 'follow-redirects'; +import * as _ from 'lodash'; +// import * as state from './state'; +// import * as path from 'path'; +// import * as os from 'os'; +// import * as fs from 'fs'; +// import * as JSZip from 'jszip'; +// import * as outputWindow from './results-output/results-doc'; +import * as cljsLib from '../out/cljs-lib/cljs-lib'; +// import * as url from 'url'; +import { isUndefined } from 'lodash'; +// import { isNullOrUndefined } from 'util'; + +// const specialWords = ['-', '+', '/', '*']; //TODO: Add more here +// const syntaxQuoteSymbol = '`'; + +// export function stripAnsi(str: string) { +// return str.replace( +// // eslint-disable-next-line no-control-regex +// /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~]))/g, +// '' +// ); +// } + +// export const isDefined = (value: T | undefined | null): value is T => { +// return !isNullOrUndefined(value); +// }; + +// This needs to be a function and not an arrow function +// because assertion types are special. +// export function assertIsDefined( +// value: T | undefined | null, +// message: string | (() => string) +// ): asserts value is T { +// if (isNullOrUndefined(value)) { +// throw new Error(typeof message === 'string' ? message : message()); +// } +// } + +// export function escapeStringRegexp(s: string): string { +// return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +// } + +// export function isNonEmptyString(value: any): boolean { +// return typeof value == 'string' && value.length > 0; +// } + +// async function quickPickSingle(opts: { +// title?: string; +// values: string[]; +// saveAs: string; +// default?: string; +// placeHolder: string; +// autoSelect?: boolean; +// }) { +// if (opts.values.length == 0) { +// return; +// } +// const saveAs = `qps-${opts.saveAs}`; +// const selected = opts.default ?? state.extensionContext.workspaceState.get(saveAs); + +// let result; +// if (opts.autoSelect && opts.values.length == 1) { +// result = opts.values[0]; +// } else { +// result = await quickPick(opts.values, selected ? [selected] : [], [], { +// title: opts.title, +// placeHolder: opts.placeHolder, +// ignoreFocusOut: true, +// }); +// } +// void state.extensionContext.workspaceState.update(saveAs, result); +// return result; +// } + +// async function quickPickMulti(opts: { values: string[]; saveAs: string; placeHolder: string }) { +// const saveAs = `qps-${opts.saveAs}`; +// const selected = state.extensionContext.workspaceState.get(saveAs) || []; +// const result = await quickPick(opts.values, [], selected, { +// placeHolder: opts.placeHolder, +// canPickMany: true, +// ignoreFocusOut: true, +// }); +// void state.extensionContext.workspaceState.update(saveAs, result); +// return result; +// } + +// Testing facility. +// Recreated every time we create a new quickPick +// let quickPickActive: Promise; + +// function quickPick( +// itemsToPick: string[], +// active: string[], +// selected: string[], +// options: vscode.QuickPickOptions & { canPickMany: true } +// ): Promise; +// function quickPick( +// itemsToPick: string[], +// active: string[], +// selected: string[], +// options: vscode.QuickPickOptions +// ): Promise; + +// async function quickPick( +// itemsToPick: string[], +// active: string[], +// selected: string[], +// options: vscode.QuickPickOptions +// ): Promise { +// const items = itemsToPick.map((x) => ({ label: x })); + +// const qp = vscode.window.createQuickPick(); +// quickPickActive = new Promise((resolve) => qp.onDidChangeActive((e) => resolve())); +// qp.canSelectMany = !!options.canPickMany; +// qp.title = options.title; +// qp.placeholder = options.placeHolder; +// qp.ignoreFocusOut = !!options.ignoreFocusOut; +// qp.matchOnDescription = !!options.matchOnDescription; +// qp.matchOnDetail = !!options.matchOnDetail; +// qp.items = items; +// qp.activeItems = items.filter((x) => active.indexOf(x.label) != -1); +// qp.selectedItems = items.filter((x) => selected.indexOf(x.label) != -1); +// return new Promise((resolve, reject) => { +// qp.show(); +// qp.onDidAccept(() => { +// if (qp.canSelectMany) { +// resolve(qp.selectedItems.map((x) => x.label)); +// } else if (qp.selectedItems.length) { +// resolve(qp.selectedItems[0].label); +// } else { +// resolve(undefined); +// } +// qp.hide(); +// quickPickActive = undefined; +// }); +// qp.onDidHide(() => { +// resolve([]); +// qp.hide(); +// quickPickActive = undefined; +// }); +// }); +// } + +// function getCljsReplStartCode() { +// return vscode.workspace.getConfiguration('hy').startCLJSREPLCommand; +// } + +// function getShadowCljsReplStartCode(build) { +// return '(shadow.cljs.devtools.api/nrepl-select ' + build + ')'; +// } + +// function getActualWord(document, position, selected, word) { +// if (selected === undefined) { +// const selectedChar = document +// .lineAt(position.line) +// .text.slice(position.character, position.character + 1), +// isFn = +// document.lineAt(position.line).text.slice(position.character - 1, position.character) === +// '('; +// if (selectedChar !== undefined && specialWords.indexOf(selectedChar) !== -1 && isFn) { +// return selectedChar; +// } else { +// return ''; +// } +// } else { +// return word && word.startsWith(syntaxQuoteSymbol) ? word.substr(1) : word; +// } +// } + +// function getWordAtPosition(document, position) { +// const selected = document.getWordRangeAtPosition(position), +// selectedText = +// selected !== undefined +// ? document.getText(new vscode.Range(selected.start, selected.end)) +// : '', +// text = getActualWord(document, position, selected, selectedText); +// return text; +// } + +function tryToGetDocument( + document: vscode.TextDocument | Record | undefined +): vscode.TextDocument | undefined { + const activeTextEditor = tryToGetActiveTextEditor(); + if (document && Object.prototype.hasOwnProperty.call(document, 'fileName')) { + return document as vscode.TextDocument; + } else if (activeTextEditor?.document && activeTextEditor.document.languageId !== 'Log') { + return activeTextEditor.document; + } else if (vscode.window.visibleTextEditors.length > 0) { + const editor = vscode.window.visibleTextEditors.find( + (editor) => editor.document && editor.document.languageId !== 'Log' + ); + return editor?.document; + } +} + +// function getDocument(document: vscode.TextDocument | Record): vscode.TextDocument { +// const doc = tryToGetDocument(document); + +// if (isUndefined(doc)) { +// throw new Error('Expected an activeTextEditor with a document!'); +// } + +// return doc; +// } + +// function getFileType(document: vscode.TextDocument | Record | undefined) { +// const doc = tryToGetDocument(document); + +// if (doc) { +// return path.extname(doc.fileName).replace(/^\./, ''); +// } else { +// return 'clj'; +// } +// } + +// function getLaunchingState() { +// return cljsLib.getStateValue('launching'); +// } + +// function setLaunchingState(value: any) { +// void vscode.commands.executeCommand('setContext', 'calva:launching', Boolean(value)); +// cljsLib.setStateValue('launching', value); +// } + +function getConnectedState() { + return cljsLib.getStateValue('connected'); +} + +// function setConnectedState(value: boolean) { +// void vscode.commands.executeCommand('setContext', 'calva:connected', value); +// cljsLib.setStateValue('connected', value); +// } + +// function getConnectingState() { +// return cljsLib.getStateValue('connecting'); +// } + +// function setConnectingState(value: boolean) { +// if (value) { +// void vscode.commands.executeCommand('setContext', 'calva:connecting', true); +// cljsLib.setStateValue('connecting', true); +// } else { +// void vscode.commands.executeCommand('setContext', 'calva:connecting', false); +// cljsLib.setStateValue('connecting', false); +// } +// } + +// ERROR HELPERS +// const ERROR_TYPE = { +// WARNING: 'warning', +// ERROR: 'error', +// }; + +// function logSuccess(results) { +// const chan = state.outputChannel(); +// chan.appendLine('Evaluation completed successfully'); +// _.each(results, (r) => { +// const value = Object.prototype.hasOwnProperty.call(r, 'value') ? r.value : null; +// const out = Object.prototype.hasOwnProperty.call(r, 'out') ? r.out : null; +// if (value !== null) { +// chan.appendLine('=>\n' + value); +// } +// if (out !== null) { +// chan.appendLine('out:\n' + out); +// } +// }); +// } + +// function logError(error) { +// outputWindow.append('; ' + error.reason); +// if ( +// error.line !== undefined && +// error.line !== null && +// error.column !== undefined && +// error.column !== null +// ) { +// outputWindow.append('; at line: ' + error.line + ' and column: ' + error.column); +// } +// } + +// function markError(error) { +// if (error.line === null) { +// error.line = 0; +// } +// if (error.column === null) { +// error.column = 0; +// } + +// const diagnostic = cljsLib.getStateValue('diagnosticCollection'), +// editor = getActiveTextEditor(); + +// //editor.selection = new vscode.Selection(position, position); +// const line = error.line - 1, +// column = error.column, +// lineLength = editor.document.lineAt(line).text.length, +// lineText = editor.document.lineAt(line).text.substring(column, lineLength), +// firstWordStart = column + lineText.indexOf(' '), +// existing = diagnostic.get(editor.document.uri), +// err = new vscode.Diagnostic( +// new vscode.Range(line, column, line, firstWordStart), +// error.reason, +// vscode.DiagnosticSeverity.Error +// ); + +// const errors = existing !== undefined && existing.length > 0 ? [...existing, err] : [err]; +// diagnostic.set(editor.document.uri, errors); +// } + +// function logWarning(warning) { +// outputWindow.append('; ' + warning.reason); +// if (warning.line !== null) { +// if (warning.column !== null) { +// outputWindow.append('; at line: ' + warning.line + ' and column: ' + warning.column); +// } else { +// outputWindow.append('; at line: ' + warning.line); +// } +// } +// } + +// function markWarning(warning) { +// if (warning.line === null) { +// warning.line = 0; +// } +// if (warning.column === null) { +// warning.column = 0; +// } + +// const diagnostic = cljsLib.getStateValue('diagnosticCollection'), +// editor = getActiveTextEditor(); + +// //editor.selection = new vscode.Selection(position, position); +// const line = Math.max(0, warning.line - 1), +// column = warning.column, +// lineLength = editor.document.lineAt(line).text.length, +// existing = diagnostic.get(editor.document.uri), +// warn = new vscode.Diagnostic( +// new vscode.Range(line, column, line, lineLength), +// warning.reason, +// vscode.DiagnosticSeverity.Warning +// ); + +// const warnings = existing !== undefined && existing.length > 0 ? [...existing, warn] : [warn]; +// diagnostic.set(editor.document.uri, warnings); +// } + +// async function promptForUserInputString(prompt: string): Promise { +// return vscode.window.showInputBox({ +// prompt: prompt, +// ignoreFocusOut: true, +// }); +// } + +// function filterVisibleRanges( +// editor: vscode.TextEditor, +// ranges: vscode.Range[], +// combine = true +// ): vscode.Range[] { +// let filtered: vscode.Range[] = []; +// editor.visibleRanges.forEach((visibleRange) => { +// const visibles = ranges.filter((r) => { +// return ( +// visibleRange.contains(r.start) || visibleRange.contains(r.end) || r.contains(visibleRange) +// ); +// }); +// filtered = filtered.concat( +// combine ? [new vscode.Range(visibles[0].start, visibles[visibles.length - 1].end)] : visibles +// ); +// }); +// return filtered; +// } + +// function scrollToBottom(editor: vscode.TextEditor) { +// const lastPos = editor.document.positionAt(Infinity); +// editor.selection = new vscode.Selection(lastPos, lastPos); +// editor.revealRange(new vscode.Range(lastPos, lastPos)); +// } + +// async function getFileContents(path: string) { +// const doc = vscode.workspace.textDocuments.find((document) => document.uri.path === path); +// if (doc) { +// return doc.getText(); +// } +// if (path.match(/jar!\//)) { +// return await getJarContents(path); +// } +// return fs.readFileSync(path).toString(); +// } + +// function jarFilePathComponents(uri: vscode.Uri | string) { +// const rawPath = typeof uri === 'string' ? uri : uri.path; +// const replaceRegex = os.platform() === 'win32' ? /file:\/*/ : /file:/; +// return rawPath.replace(replaceRegex, '').split('!/'); +// } + +/** + * Gets the contents of a file in a zip + * @param uri url to jar file, followed by "!/" and than the url inside the jar + * @returns contents of the file or an empty string + */ +// async function getJarContents(uri: vscode.Uri | string) { +// return new Promise((resolve, _reject) => { +// const [pathToJar, pathToFileInJar] = jarFilePathComponents(uri); + +// fs.readFile(pathToJar, (err, data) => { +// const zip = new JSZip(); +// zip +// .loadAsync(data) +// .then((new_zip) => { +// const fileInJar = new_zip.file(pathToFileInJar); + +// if (fileInJar) { +// return fileInJar.async('string').then((value) => { +// resolve(value); +// }); +// } + +// return resolve(''); +// }) +// .catch((_) => { +// return resolve(''); +// }); +// }); +// }); +// } + +// function sortByPresetOrder(arr: any[], presetOrder: any[]) { +// const result: any[] = []; +// presetOrder.forEach((preset) => { +// if (arr.indexOf(preset) != -1) { +// result.push(preset); +// } +// }); +// return [...result, ...arr.filter((e) => !presetOrder.includes(e))]; +// } + +// function writeTextToFile(uri: vscode.Uri, text: string): Thenable { +// const ab = new ArrayBuffer(text.length); +// const ui8a = new Uint8Array(ab); +// for (let i = 0, strLen = text.length; i < strLen; i++) { +// ui8a[i] = text.charCodeAt(i); +// } +// return vscode.workspace.fs.writeFile(uri, ui8a); +// } + +// async function downloadFromUrl(url: string, savePath: string) { +// return new Promise((resolve, reject) => { +// const saveFile = fs.createWriteStream(savePath); +// https.get(url, (res) => { +// if (res.statusCode === 200) { +// res.pipe(saveFile); +// } else { +// saveFile.close(); +// reject(new Error(`Server responded with ${res.statusCode}: ${res.statusMessage}`)); +// } +// res.on('end', () => { +// saveFile.close(); +// resolve(true); +// }); +// res.on('error', (err: any) => { +// console.error(`Error downloading file from ${url}: ${err.message}`); +// reject(err); +// }); +// }); +// }); +// } + +// async function fetchFromUrl(fullUrl: string): Promise { +// const q = url.parse(fullUrl); +// return new Promise((resolve, reject) => { +// https +// .get( +// { +// host: q.hostname, +// path: q.pathname, +// port: q.port, +// headers: { 'user-agent': 'node.js' }, +// }, +// (res) => { +// let data = ''; +// res.on('data', (chunk: any) => { +// data += chunk; +// }); +// res.on('end', () => { +// resolve(data); +// }); +// } +// ) +// .on('error', (err: any) => { +// console.error(`Error downloading file from ${url}: ${err.message}`); +// reject(err); +// }); +// }); +// } + +// function randomSlug(length = 7) { +// return Math.random().toString(36).substring(7); +// } + +// function hyTmpDir() { +// return path.join(os.tmpdir(), 'hy-lang.hy'); +// } + +// const isWindows = process.platform === 'win32'; + +// export async function isDocumentWritable(document: vscode.TextDocument): Promise { +// if (!vscode.workspace.fs.isWritableFileSystem(document.uri.scheme)) { +// return false; +// } +// const fileStat = await vscode.workspace.fs.stat(document.uri); + +// // I'm not sure in which cases fileStat permissions can be missing +// // and so it's not clear what to do if it is. For the moment we can +// // ignore this to maintain current behavior. +// // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion +// return (fileStat.permissions! & vscode.FilePermission.Readonly) !== 1; +// } + +// Returns the elements of coll with duplicates removed +// (See clojure.core/distinct). +function distinct(coll: T[]): T[] { + return [...new Set(coll)]; +} + +function tryToGetActiveTextEditor(): vscode.TextEditor | undefined { + return vscode.window.activeTextEditor; +} + +function getActiveTextEditor(): vscode.TextEditor { + const editor = tryToGetActiveTextEditor(); + + if (isUndefined(editor)) { + throw new Error('Expected active text editor!'); + } + + return editor; +} + +// function pathExists(path: string): boolean { +// return fs.existsSync(path); +// } + +export { + // distinct, + // getWordAtPosition, + tryToGetDocument, + // getDocument, + // getFileType, + // getLaunchingState, + // setLaunchingState, + getConnectedState, + // setConnectedState, + // getConnectingState, + // setConnectingState, + // specialWords, + // ERROR_TYPE, + // logError, + // markError, + // logWarning, + // markWarning, + // logSuccess, + // getCljsReplStartCode, + // getShadowCljsReplStartCode, + // quickPickActive, + // quickPick, + // quickPickSingle, + // quickPickMulti, + // promptForUserInputString, + // filterVisibleRanges, + // scrollToBottom, + // getFileContents, + // jarFilePathComponents, + // getJarContents, + // sortByPresetOrder, + // writeTextToFile, + // downloadFromUrl, + // fetchFromUrl, + cljsLib, + // randomSlug, + // isWindows, + tryToGetActiveTextEditor, + getActiveTextEditor, + // pathExists, + // hyTmpDir, +}; diff --git a/src/when-contexts.ts b/src/when-contexts.ts new file mode 100644 index 0000000..30810cb --- /dev/null +++ b/src/when-contexts.ts @@ -0,0 +1,79 @@ +import * as vscode from 'vscode'; +import * as docMirror from './doc-mirror'; +import * as context from './cursor-doc/cursor-context'; +import * as util from './utilities'; + +let lastContexts: context.CursorContext[] = []; + +function deepEqual(x: any, y: any): boolean { + if (x == y) { + return true; + } + if (x instanceof Array && y instanceof Array) { + if (x.length == y.length) { + for (let i = 0; i < x.length; i++) { + if (!deepEqual(x[i], y[i])) { + return false; + } + } + return true; + } else { + return false; + } + } else if ( + !(x instanceof Array) && + !(y instanceof Array) && + x instanceof Object && + y instanceof Object + ) { + for (const f in x) { + if (!deepEqual(x[f], y[f])) { + return false; + } + } + for (const f in y) { + if (!Object.prototype.hasOwnProperty.call(x, f)) { + return false; + } + } + return true; + } + return false; +} + +export function setCursorContextIfChanged(editor: vscode.TextEditor) { + if ( + !editor || + !editor.document || + editor.document.languageId !== 'hy' || + editor !== util.tryToGetActiveTextEditor() + ) { + return; + } + const currentContexts = determineCursorContexts(editor.document, editor.selection.active); + if (editor.selection.active.line == 0 && editor.selection.active.character == 0) { + delete currentContexts[currentContexts.indexOf("hy:cursorInComment")]; + } + if (!deepEqual(lastContexts, currentContexts)) { + setCursorContexts(currentContexts); + } +} + +function determineCursorContexts( + document: vscode.TextDocument, + position: vscode.Position +): context.CursorContext[] { + const mirrorDoc = docMirror.getDocument(document); + return context.determineContexts(mirrorDoc, document.offsetAt(position)); +} + +function setCursorContexts(currentContexts: context.CursorContext[]) { + lastContexts = currentContexts; + context.allCursorContexts.forEach((context) => { + void vscode.commands.executeCommand( + 'setContext', + context, + currentContexts.indexOf(context) > -1 + ); + }); +} diff --git a/tsconfig.json b/tsconfig.json index ad2a9d4..7257383 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "es2019" ], "sourceMap": true, - "rootDir": ".", + "rootDir": "src", "skipLibCheck": true }, "exclude": [ From 27a3250d9057564e55040733723ab5d1dc720e17 Mon Sep 17 00:00:00 2001 From: Caleb Figgers Date: Fri, 9 Dec 2022 11:57:41 -0600 Subject: [PATCH 3/8] Fix bug preventing keyboard shortcuts from enabling --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ce9d11..2a6e70d 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "type": "object", "title": "Hy", "properties": { - "Hy.keybindingsEnabled": { + "hy.keybindingsEnabled": { "type": "boolean", "description": "Activate keybindings.", "default": true, From aa749c628f82642a603eed2525424e110ea39d58 Mon Sep 17 00:00:00 2001 From: Caleb Figgers Date: Fri, 9 Dec 2022 11:57:57 -0600 Subject: [PATCH 4/8] Avoiding conflicts with Calva --- package.json | 34 ++++++++++++++++------------------ src/calva-fmt/src/extension.ts | 12 ++++++------ src/hyMain.ts | 6 ++++++ 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 2a6e70d..f41bfc4 100644 --- a/package.json +++ b/package.json @@ -127,21 +127,21 @@ "title": "Calva-fmt", "type": "object", "properties": { - "calva.fmt.configPath": { + "hy.calva.fmt.configPath": { "type": "string", "markdownDescription": "Path to [cljfmt](https://github.com/weavejester/cljfmt#configuration) configuration file. Absolute or relative to the project root directory. To provide the config via [clojure-lsp](https://clojure-lsp.io), set this to `CLOJURE-LSP` (case sensitive)." }, - "calva.fmt.formatAsYouType": { + "hy.calva.fmt.formatAsYouType": { "type": "boolean", "default": true, "description": "Auto-adjust indentation and format as you enter new lines." }, - "calva.fmt.newIndentEngine": { + "hy.calva.fmt.newIndentEngine": { "type": "boolean", "default": true, "markdownDescription": "Use the structural editor for indentation (instead of `cljfmt`)." }, - "calva.fmt.keepCommentTrailParenOnOwnLine": { + "hy.calva.fmt.keepCommentTrailParenOnOwnLine": { "type": "boolean", "default": true, "markdownDescription": "Treat `(comment...)` forms special and keep its closing paren on a line of its own." @@ -523,37 +523,37 @@ "enablement": "editorLangId == hy" }, { - "command": "calva-fmt.formatCurrentForm", + "command": "hy.calva-fmt.formatCurrentForm", "title": "Format Current Form", "category": "Hy Format", "enablement": "editorLangId == hy" }, { - "command": "calva-fmt.alignCurrentForm", + "command": "hy.calva-fmt.alignCurrentForm", "title": "Format and Align Current Form (recursively, experimental)", "category": "Hy Format", "enablement": "editorLangId == hy" }, { - "command": "calva-fmt.trimCurrentFormWhiteSpace", + "command": "hy.calva-fmt.trimCurrentFormWhiteSpace", "title": "Format Current Form and trim space between forms", "category": "Hy Format", "enablement": "editorLangId == hy" }, { - "command": "calva-fmt.inferParens", + "command": "hy.calva-fmt.inferParens", "title": "Infer Parens (from the indentation)", "category": "Hy Format", "enablement": "editorLangId == hy" }, { - "command": "calva-fmt.tabIndent", + "command": "hy.calva-fmt.tabIndent", "title": "Indent Line", "category": "Hy Format", "enablement": "editorLangId == hy" }, { - "command": "calva-fmt.tabDedent", + "command": "hy.calva-fmt.tabDedent", "title": "Dedent Line", "category": "Hy Format", "enablement": "editorLangId == hy" @@ -896,27 +896,27 @@ "when": "hy:keybindingsEnabled && editorLangId = hy && editorTextfocus && !hy:cursorInComment" }, { - "command": "calva-fmt.formatCurrentForm", + "command": "hy.calva-fmt.formatCurrentForm", "key": "tab", "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !editorReadOnly && !inSnippetMode && !suggestWidgetVisible && !hasOtherSuggestions && !inSnippetMode && !inlineSuggestionVisible" }, { - "command": "calva-fmt.alignCurrentForm", + "command": "hy.calva-fmt.alignCurrentForm", "key": "ctrl+alt+l", "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" }, { - "command": "calva-fmt.inferParens", + "command": "hy.calva-fmt.inferParens", "key": "ctrl+alt+p i", "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" }, { - "command": "calva-fmt.tabIndent", + "command": "hy.calva-fmt.tabIndent", "key": "ctrl+i", "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" }, { - "command": "calva-fmt.tabDedent", + "command": "hy.calva-fmt.tabDedent", "key": "shift+tab", "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" } @@ -931,7 +931,6 @@ "scripts": { "vscode:prepublish": "tsc -p ./", "compile": "tsc -watch -p ./", - "postinstall": "node ./node_modules/vscode/bin/install", "test": "node ./node_modules/vscode/bin/test" }, "dependencies": { @@ -949,7 +948,6 @@ "@types/node": "^12.12.0", "@types/vscode": "^1.34.0", "mocha": "^10.1.0", - "typescript": "^4.0.0", - "vscode": "^1.0.0" + "typescript": "^4.7.4" } } diff --git a/src/calva-fmt/src/extension.ts b/src/calva-fmt/src/extension.ts index a538f67..232c4ca 100644 --- a/src/calva-fmt/src/extension.ts +++ b/src/calva-fmt/src/extension.ts @@ -41,31 +41,31 @@ export async function activate(context: vscode.ExtensionContext) { ); context.subscriptions.push( vscode.commands.registerTextEditorCommand( - 'calva-fmt.alignCurrentForm', + 'hy.calva-fmt.alignCurrentForm', formatter.alignPositionCommand ) ); context.subscriptions.push( vscode.commands.registerTextEditorCommand( - 'calva-fmt.trimCurrentFormWhiteSpace', + 'hy.calva-fmt.trimCurrentFormWhiteSpace', formatter.trimWhiteSpacePositionCommand ) ); context.subscriptions.push( vscode.commands.registerTextEditorCommand( - 'calva-fmt.inferParens', + 'hy.calva-fmt.inferParens', inferer.inferParensCommand) ); context.subscriptions.push( vscode.commands.registerTextEditorCommand( - 'calva-fmt.tabIndent', + 'hy.calva-fmt.tabIndent', (e) => { inferer.indentCommand(e, ' ', true); } ) ); context.subscriptions.push( - vscode.commands.registerTextEditorCommand('calva-fmt.tabDedent', (e) => { + vscode.commands.registerTextEditorCommand('hy.calva-fmt.tabDedent', (e) => { inferer.indentCommand(e, ' ', false); }) ); @@ -88,7 +88,7 @@ export async function activate(context: vscode.ExtensionContext) { ); vscode.window.onDidChangeActiveTextEditor(inferer.updateState); vscode.workspace.onDidChangeConfiguration(async (e) => { - if (e.affectsConfiguration('calva.fmt.formatAsYouType')) { + if (e.affectsConfiguration('hy.calva.fmt.formatAsYouType')) { vscode.languages.setLanguageConfiguration( 'hy', getLanguageConfiguration(await config.getConfig()['format-as-you-type']) diff --git a/src/hyMain.ts b/src/hyMain.ts index 0c640ec..ac0f400 100644 --- a/src/hyMain.ts +++ b/src/hyMain.ts @@ -95,6 +95,12 @@ function setKeybindingsEnabledContext() { const keybindingsEnabled = vscode.workspace .getConfiguration() .get(config.KEYBINDINGS_ENABLED_CONFIG_KEY); + console.log("DEBUG: vscode = ", vscode); + console.log("DEBUG: vscode.workspace = ", vscode.workspace); + console.log("DEBUG: vscode.workspace.getConfiguration() = ", vscode.workspace.getConfiguration()); + console.log("DEBUG: config.KEYBINDINGS_ENABLED_CONFIG_KEY = ", config.KEYBINDINGS_ENABLED_CONFIG_KEY); + console.log("DEBUG: vscode.workspace.getConfiguration().get(config.KEYBINDINGS_ENABLED_CONFIG_KEY) = ", vscode.workspace.getConfiguration().get(config.KEYBINDINGS_ENABLED_CONFIG_KEY)); + console.log("DEBUG: keybindingsEnabled = ", keybindingsEnabled); void vscode.commands.executeCommand( 'setContext', config.KEYBINDINGS_ENABLED_CONTEXT_KEY, From b68c99e8eb84fac3b21863cf3e296fa5e6ec2185 Mon Sep 17 00:00:00 2001 From: Caleb Figgers Date: Sat, 10 Dec 2022 11:22:10 -0600 Subject: [PATCH 5/8] Fix bug with ontype formatting --- src/calva-fmt/src/config.ts | 2 +- src/calva-fmt/src/extension.ts | 2 +- src/calva-fmt/src/providers/ontype_formatter.ts | 4 ++-- src/calva-fmt/src/state.ts | 2 +- src/hyMain.ts | 6 ------ 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/calva-fmt/src/config.ts b/src/calva-fmt/src/config.ts index 5706f55..e6846d3 100644 --- a/src/calva-fmt/src/config.ts +++ b/src/calva-fmt/src/config.ts @@ -30,7 +30,7 @@ async function readConfiguration(): Promise<{ 'cljfmt-options-string': string; 'cljfmt-options': object; }> { - const workspaceConfig = vscode.workspace.getConfiguration('calva.fmt'); + const workspaceConfig = vscode.workspace.getConfiguration('hy.calva.fmt'); const configPath: string | undefined = workspaceConfig.get('configPath'); // if (configPath === LSP_CONFIG_KEY) { diff --git a/src/calva-fmt/src/extension.ts b/src/calva-fmt/src/extension.ts index 232c4ca..4c87e06 100644 --- a/src/calva-fmt/src/extension.ts +++ b/src/calva-fmt/src/extension.ts @@ -35,7 +35,7 @@ export async function activate(context: vscode.ExtensionContext) { ); context.subscriptions.push( vscode.commands.registerTextEditorCommand( - 'calva-fmt.formatCurrentForm', + 'hy.calva-fmt.formatCurrentForm', formatter.formatPositionCommand ) ); diff --git a/src/calva-fmt/src/providers/ontype_formatter.ts b/src/calva-fmt/src/providers/ontype_formatter.ts index 664951a..547e5f5 100644 --- a/src/calva-fmt/src/providers/ontype_formatter.ts +++ b/src/calva-fmt/src/providers/ontype_formatter.ts @@ -34,8 +34,8 @@ export class FormatOnTypeEditProvider implements vscode.OnTypeFormattingEditProv const editor = util.getActiveTextEditor(); const pos = editor.selection.active; - if (vscode.workspace.getConfiguration('calva.fmt').get('formatAsYouType')) { - if (vscode.workspace.getConfiguration('calva.fmt').get('newIndentEngine')) { + if (vscode.workspace.getConfiguration('hy.calva.fmt').get('formatAsYouType')) { + if (vscode.workspace.getConfiguration('hy.calva.fmt').get('newIndentEngine')) { void formatter.indentPosition(pos, document); } else { try { diff --git a/src/calva-fmt/src/state.ts b/src/calva-fmt/src/state.ts index 5175557..8ea29d8 100644 --- a/src/calva-fmt/src/state.ts +++ b/src/calva-fmt/src/state.ts @@ -27,7 +27,7 @@ function reset() { } function config() { - const configOptions = vscode.workspace.getConfiguration('calva.fmt'); + const configOptions = vscode.workspace.getConfiguration('hy.calva.fmt'); return { parinferOnSelectionChange: configOptions.get('inferParensOnCursorMove'), }; diff --git a/src/hyMain.ts b/src/hyMain.ts index ac0f400..004b053 100644 --- a/src/hyMain.ts +++ b/src/hyMain.ts @@ -206,12 +206,6 @@ export function activate(context: vscode.ExtensionContext) { } catch (e) { console.error('Failed activating Paredit: ' + e.message); } - - try { - void fmt.activate(context); - } catch (e) { - console.error('Failed activating Formatter: ' + e.message); - } } From 23f8adca4ef0d6e4cb797946a3a1ce48db3d7922 Mon Sep 17 00:00:00 2001 From: Caleb Figgers Date: Sat, 10 Dec 2022 11:46:56 -0600 Subject: [PATCH 6/8] Removed DEBUG messages --- package.json | 2 +- src/hyMain.ts | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/package.json b/package.json index f41bfc4..9535f3c 100644 --- a/package.json +++ b/package.json @@ -893,7 +893,7 @@ { "command": "hy.continueComment", "key": "enter", - "when": "hy:keybindingsEnabled && editorLangId = hy && editorTextfocus && !hy:cursorInComment" + "when": "hy:keybindingsEnabled && editorLangId = hy && editorTextfocus && hy:cursorInComment" }, { "command": "hy.calva-fmt.formatCurrentForm", diff --git a/src/hyMain.ts b/src/hyMain.ts index 004b053..6f3a5cc 100644 --- a/src/hyMain.ts +++ b/src/hyMain.ts @@ -95,12 +95,6 @@ function setKeybindingsEnabledContext() { const keybindingsEnabled = vscode.workspace .getConfiguration() .get(config.KEYBINDINGS_ENABLED_CONFIG_KEY); - console.log("DEBUG: vscode = ", vscode); - console.log("DEBUG: vscode.workspace = ", vscode.workspace); - console.log("DEBUG: vscode.workspace.getConfiguration() = ", vscode.workspace.getConfiguration()); - console.log("DEBUG: config.KEYBINDINGS_ENABLED_CONFIG_KEY = ", config.KEYBINDINGS_ENABLED_CONFIG_KEY); - console.log("DEBUG: vscode.workspace.getConfiguration().get(config.KEYBINDINGS_ENABLED_CONFIG_KEY) = ", vscode.workspace.getConfiguration().get(config.KEYBINDINGS_ENABLED_CONFIG_KEY)); - console.log("DEBUG: keybindingsEnabled = ", keybindingsEnabled); void vscode.commands.executeCommand( 'setContext', config.KEYBINDINGS_ENABLED_CONTEXT_KEY, From 9156e09920fc914f4af574daa9595cdc461c8877 Mon Sep 17 00:00:00 2001 From: Caleb Figgers Date: Tue, 20 Dec 2022 21:17:10 -0600 Subject: [PATCH 7/8] Hy: Continue comment cmd doesn't seem to be working Fixes hylang/vscode-hy#11 --- package.json | 2 +- src/cursor-doc/cdf-edits/hy-lexer.ts | 4 ++-- src/edit.ts | 2 +- src/hyMain.ts | 6 ++++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9535f3c..45c6a79 100644 --- a/package.json +++ b/package.json @@ -893,7 +893,7 @@ { "command": "hy.continueComment", "key": "enter", - "when": "hy:keybindingsEnabled && editorLangId = hy && editorTextfocus && hy:cursorInComment" + "when": "hy:keybindingsEnabled && editorLangId == hy && editorTextFocus && hy:cursorInComment" }, { "command": "hy.calva-fmt.formatCurrentForm", diff --git a/src/cursor-doc/cdf-edits/hy-lexer.ts b/src/cursor-doc/cdf-edits/hy-lexer.ts index 0b927ad..09a970e 100644 --- a/src/cursor-doc/cdf-edits/hy-lexer.ts +++ b/src/cursor-doc/cdf-edits/hy-lexer.ts @@ -61,8 +61,8 @@ toplevel.terminal( /[\f\u000B\u001C\u001D\u001E\u001F\u2028\u2029\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2008\u2009\u200a\u205f\u3000]+/, (l, m) => ({ type: 'ws' }) ); -// comments -- 2022-07-06: Updated for hy comment syntax rather than Clojure (`#` instead of `;`) -toplevel.terminal('comment', /#.*$/, (l, m) => ({ type: 'comment' })); +// comments +toplevel.terminal('comment', /;.*/, (l, m) => ({ type: 'comment' })); // Calva repl prompt, it contains special colon symbols and a hard space toplevel.terminal( 'comment', diff --git a/src/edit.ts b/src/edit.ts index d1a2741..93e27c2 100644 --- a/src/edit.ts +++ b/src/edit.ts @@ -19,7 +19,7 @@ export function continueCommentCommand() { } const commentOffset = cursor.rowCol[1]; const commentText = cursor.getToken().raw; - const [_1, startText, bullet, num] = commentText.match(/^([#\s]+)([*-] +|(\d+)\. +)?/) ?? []; + const [_1, startText, bullet, num] = commentText.match(/^([;\s]+)([*-] +|(\d+)\. +)?/) ?? []; const newNum = num ? parseInt(num) + 1 : undefined; const bulletText = newNum ? bullet.replace(/\d+/, '' + newNum) : bullet; const pad = ' '.repeat(commentOffset); diff --git a/src/hyMain.ts b/src/hyMain.ts index 6f3a5cc..9b978ae 100644 --- a/src/hyMain.ts +++ b/src/hyMain.ts @@ -171,6 +171,12 @@ export function activate(context: vscode.ExtensionContext) { onDidChangeEditorOrSelection(editor); }) ); + context.subscriptions.push( + vscode.window.onDidChangeTextEditorSelection((editor) => { + status.update(); + onDidChangeEditorOrSelection(editor.textEditor); + }) + ); context.subscriptions.push( vscode.workspace.onDidChangeTextDocument(annotations.onDidChangeTextDocument) ); From 7356f4cec9ecea41d44b1cb666beadd479accc32 Mon Sep 17 00:00:00 2001 From: Caleb Figgers Date: Tue, 20 Dec 2022 21:24:51 -0600 Subject: [PATCH 8/8] Updated README and CHANGELOG --- CHANGELOG.md | 5 +++++ README.md | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90b3567..799bda1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 0.0.3 (Dec 20, 2022) + - Added ParEdit-style structural editing + - Added format-on-type (incl. auto-indentation) + - Bug fixes + ## 0.0.2 (Nov 24, 2022) - Improved syntax highlighting. diff --git a/README.md b/README.md index c4028b8..85e1160 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,18 @@ Features: - [x] Basic Hy code snippets - [x] Code evaluation shortcuts - [x] Improved syntax highlighting +- [x] [Paredit](https://www.emacswiki.org/emacs/ParEdit)-style structural editing based on S-expressions (slurping, barfing, dragging, killing, rewrapping, splicing, raising, navigation, auto-balancing for parens and other wrappers `[({""})]`) +- [x] Auto-formatting on edit (esp. auto-indentation) Planned features: -- [ ] Auto-formatting on edit (esp. auto-indentation) -- [ ] [Paredit](https://www.emacswiki.org/emacs/ParEdit)-style structural editing based on S-expressions (slurping, barfing, dragging, killing, rewrapping, splicing, raising, navigation, auto-balancing for parens and other wrappers `[({""})]`) - [ ] Intellisense code completion for built-in Hy functions and macros -## Installation +## How to Install + +### VS Code Extension Marketplace + +1. Navigate to the VS Code Extension marketplace within VS Code. +2. Search for "vscode-hy (hylang official)" and install as usual. ### Local Install @@ -26,7 +31,6 @@ Planned features: 2. Clone this repo within that directory (e.g. `git clone https://www.github.com/hylang/vscode-hy`) 3. Reload or relaunch any open VS Code/VS Codium windows - ## Contribution Issues and pull requests welcome.