├── .eslintrc.json ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── align-items.gif ├── calva-fmt.png ├── format-current-form.gif ├── infer-parens.gif └── parinfer.gif ├── atom-language-clojure ├── .coffeelintignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── appveyor.yml ├── coffeelint.json ├── grammars │ └── clojure.cson ├── package-lock.json ├── package.json ├── settings │ └── language-clojure.cson ├── snippets │ └── language-clojure.cson └── spec │ └── clojure-spec.coffee ├── calva-fmt ├── config.ts ├── docmirror │ ├── clojure-lexer.ts │ ├── index.ts │ └── lexer.ts ├── extension.ts ├── format.ts ├── infer.ts ├── providers │ ├── ontype_formatter.ts │ └── range_formatter.ts └── state.ts ├── clojure.tmLanguage.json ├── jsconfig.json ├── next-README.md ├── package-lock.json ├── package.json ├── test_js ├── extension.test.js └── index.js ├── tsconfig.json └── update-grammar.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "no-const-assign": "warn", 16 | "no-this-before-super": "warn", 17 | "no-undef": "warn", 18 | "no-unreachable": "warn", 19 | "no-unused-vars": "warn", 20 | "constructor-super": "warn", 21 | "valid-typeof": "warn" 22 | } 23 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode-test/ 3 | *.vsix 4 | .shadow-cljs/ 5 | .nrepl-port 6 | pom.xml 7 | *.idea/ 8 | *.DS_Store 9 | out/ 10 | lib/ 11 | test-out/ 12 | .vscode/settings.json 13 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ] 16 | }, 17 | { 18 | "name": "Extension Tests", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceFolder}", 24 | "--extensionTestsPath=${workspaceFolder}/test" 25 | ] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Watch TS", 8 | "type": "npm", 9 | "script": "watch-ts", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | }, 14 | "problemMatcher": [] 15 | }, 16 | { 17 | "label": "Compile", 18 | "type": "npm", 19 | "script": "compile", 20 | "problemMatcher": [] 21 | }, 22 | { 23 | "label": "Release", 24 | "type": "npm", 25 | "script": "release", 26 | "problemMatcher": [] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | jsconfig.json 3 | tsconfig.json 4 | vsc-extension-quickstart.md 5 | .eslintrc.json 6 | .nrepl-port 7 | 8 | .vscode/** 9 | .vscode-test/** 10 | test/** 11 | cljc/** 12 | lib/** 13 | test_js/** 14 | .shadow-cljs/** 15 | js/** 16 | 17 | atom-language-clojure/** 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "calva-fmt" extension will be documented in this file. (At least when I remember to update it.) 3 | 4 | ## Future 5 | 6 | ### Thinking About 7 | - Offer zprint as an option for formatting the whole file. 8 | 9 | ### Work in pogress 10 | - Support for `:cljfmt` settings maps. 11 | 12 | ## 0.0.28 - 2018-11-26 13 | - Add command for aligning map items, and bindings, in columns, `ctrl+alt+l`. (Exprimental) 14 | 15 | ## 0.0.26 - 2018-11-24 16 | - Add experimental option to align map items, and bindings, in columns, `calva.fmt.allgnMapItems`, defaults to `false`. 17 | 18 | ## 0.0.23 - 2018-11-24 19 | - Add **Indent/Dedent Line** commands, `ctrl+i` and `shift+ctrl+i`, respectively. 20 | 21 | ## 0.0.22 - 2018-11-22 22 | - Add **Infer Parens** command, `ctrl+alt+p`. 23 | 24 | ## 0.0.15 - 2018-11-04 25 | - Format code as new lines are entered, keeping things mostly formatted as you type. 26 | 27 | ## 0.0.14 - 2018-10-11 28 | - Reformat current form works so nicely that it gets back the default key binding to `tab tab`. 29 | 30 | ## 0.0.9 - 2018-10-05 31 | - Add user command for indenting current form 32 | 33 | ## 0.0.5 - 2018-07-18 34 | - Expose `formatRange` in extension API. 35 | - Expose `formatPosition` (formatting the current form based on cursor position) in extension API. 36 | 37 | ## 0.0.4 - 2018-06-23 38 | - Use `clj-fmt` to auto-adjust of cursor position on new lines. 39 | 40 | ## 0.0.3 - 2018-06-20 41 | - Calva Formatter is Calva's formatter. 42 | 43 | ## 0.0.2 - 2018-06-07 44 | - Adjust cursor position on new lines as you type (using custom code). 45 | 46 | ## 0.0.1 - 2018-05-27 47 | - Initial release. 48 | - Format Selection. 49 | - Fomrat on Save. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 -> Better than Tomorrow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This is OLD calva-fmt 2 | 3 | This used to be the [Calva](https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva) Formatter. 4 | 5 | Please check the [Calva repository](/BetterThanTomorrow/calva) instead. 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/align-items.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BetterThanTomorrow/calva-fmt/5309ad7c452f62eb9de9a614b23e8c8692e5ad97/assets/align-items.gif -------------------------------------------------------------------------------- /assets/calva-fmt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BetterThanTomorrow/calva-fmt/5309ad7c452f62eb9de9a614b23e8c8692e5ad97/assets/calva-fmt.png -------------------------------------------------------------------------------- /assets/format-current-form.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BetterThanTomorrow/calva-fmt/5309ad7c452f62eb9de9a614b23e8c8692e5ad97/assets/format-current-form.gif -------------------------------------------------------------------------------- /assets/infer-parens.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BetterThanTomorrow/calva-fmt/5309ad7c452f62eb9de9a614b23e8c8692e5ad97/assets/infer-parens.gif -------------------------------------------------------------------------------- /assets/parinfer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BetterThanTomorrow/calva-fmt/5309ad7c452f62eb9de9a614b23e8c8692e5ad97/assets/parinfer.gif -------------------------------------------------------------------------------- /atom-language-clojure/.coffeelintignore: -------------------------------------------------------------------------------- 1 | spec/fixtures 2 | -------------------------------------------------------------------------------- /atom-language-clojure/.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | notifications: 4 | email: 5 | on_success: never 6 | on_failure: change 7 | 8 | script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 9 | 10 | git: 11 | depth: 10 12 | 13 | branches: 14 | only: 15 | - master 16 | -------------------------------------------------------------------------------- /atom-language-clojure/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------- 23 | 24 | This package was derived from a TextMate bundle located at 25 | https://github.com/mmcgrana/textmate-clojure and distributed under the 26 | following license, located in `LICENSE.md`: 27 | 28 | The MIT License (MIT) 29 | 30 | Copyright (c) 2010- Mark McGranaghan 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in all 40 | copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 48 | SOFTWARE. 49 | -------------------------------------------------------------------------------- /atom-language-clojure/README.md: -------------------------------------------------------------------------------- 1 | # Calva Syntax Highlight Grammar 2 | 3 | Calva Format's `clojure.tmLanguage.json` file is built from here (the `/grammars/clojure.cson` file). When making changes, also update `spec/clojure-spec.coffee` and make sure all tests pass. 4 | 5 | To run the tests you need to open this directory in Atom and issue the **Run Package Specs** command. 6 | 7 | To test your changes on CLojure source files in Atom you need to link this directory to where Atom keeps its dev package files. This works on Mac and Linux: 8 | 9 | ```sh 10 | $ cd ~/.atom/dev/packages 11 | $ ln -s /calva-fmt/atom-language-clojure/ language-clojure 12 | ``` 13 | 14 | You also need to run Atom in dev mode: 15 | 16 | ```sh 17 | $ atom -d 18 | ``` 19 | 20 | When all old and new tests pass, update Calva Format's grammar from the root of the project: 21 | 22 | ```sh 23 | $ npm run update-grammar 24 | ``` 25 | 26 | Then make some sanity tests on Clojure source files in VS Code (this mainly tests that the update-grammar script works, but anyway). 27 | -------------------------------------------------------------------------------- /atom-language-clojure/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | platform: x64 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | clone_depth: 10 10 | 11 | skip_tags: true 12 | 13 | environment: 14 | APM_TEST_PACKAGES: 15 | 16 | matrix: 17 | - ATOM_CHANNEL: stable 18 | - ATOM_CHANNEL: beta 19 | 20 | install: 21 | - ps: Install-Product node 4 22 | 23 | build_script: 24 | - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) 25 | 26 | test: off 27 | deploy: off 28 | -------------------------------------------------------------------------------- /atom-language-clojure/coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_line_length": { 3 | "level": "ignore" 4 | }, 5 | "no_empty_param_list": { 6 | "level": "error" 7 | }, 8 | "arrow_spacing": { 9 | "level": "error" 10 | }, 11 | "no_interpolation_in_single_quotes": { 12 | "level": "error" 13 | }, 14 | "no_debugger": { 15 | "level": "error" 16 | }, 17 | "prefer_english_operator": { 18 | "level": "error" 19 | }, 20 | "colon_assignment_spacing": { 21 | "spacing": { 22 | "left": 0, 23 | "right": 1 24 | }, 25 | "level": "error" 26 | }, 27 | "braces_spacing": { 28 | "spaces": 0, 29 | "level": "error" 30 | }, 31 | "spacing_after_comma": { 32 | "level": "error" 33 | }, 34 | "no_stand_alone_at": { 35 | "level": "error" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /atom-language-clojure/grammars/clojure.cson: -------------------------------------------------------------------------------- 1 | 'scopeName': 'source.clojure' 2 | 'fileTypes': [ 3 | 'boot' 4 | 'clj' 5 | 'clj.hl' 6 | 'cljc' 7 | 'cljs' 8 | 'cljs.hl' 9 | 'cljx' 10 | 'clojure' 11 | 'edn' 12 | 'org' 13 | 'joke' 14 | 'joker' 15 | ] 16 | 'foldingStartMarker': '\\(\\s*$' 17 | 'foldingStopMarker': '^\\s*\\)' 18 | 'firstLineMatch': '''(?x) 19 | # Hashbang 20 | ^\\#!.*(?:\\s|\\/) 21 | boot 22 | (?:$|\\s) 23 | | 24 | # Modeline 25 | (?i: 26 | # Emacs 27 | -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*) 28 | clojure(script)? 29 | (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*= 33 | clojure 34 | (?=\\s|:|$) 35 | ) 36 | ''' 37 | 'name': 'Clojure' 38 | 'patterns': [ 39 | { 40 | 'include': '#comment' 41 | } 42 | { 43 | 'include': '#shebang-comment' 44 | } 45 | { 46 | 'include': '#quoted-sexp' 47 | } 48 | { 49 | 'include': '#sexp' 50 | } 51 | { 52 | 'include': '#keyfn' 53 | } 54 | { 55 | 'include': '#string' 56 | } 57 | { 58 | 'include': '#vector' 59 | } 60 | { 61 | 'include': '#set' 62 | } 63 | { 64 | 'include': '#map' 65 | } 66 | { 67 | 'include': '#regexp' 68 | } 69 | { 70 | 'include': '#var' 71 | } 72 | { 73 | 'include': '#constants' 74 | } 75 | { 76 | 'include': '#dynamic-variables' 77 | } 78 | { 79 | 'include': '#metadata' 80 | } 81 | { 82 | 'include': '#namespace-symbol' 83 | } 84 | { 85 | 'include': '#symbol' 86 | } 87 | ] 88 | 'repository': 89 | 'comment': 90 | # NOTE: This must be kept as a begin/end match for language-todo to work 91 | 'begin': '(?\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}|\\,))' 145 | 'name': 'constant.keyword.clojure' 146 | 'keyfn': 147 | 'patterns': [ 148 | { 149 | '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|\\)|\\]|\\}))' 150 | 'name': 'storage.control.clojure' 151 | } 152 | { 153 | 'match': '(?<=(\\s|\\(|\\[|\\{))(declare-?|(in-)?ns|import|use|require|load|compile|(def(?!ault)[\\p{Ll}\\-]*))(?=(\\s|\\)|\\]|\\}))' 154 | 'name': 'keyword.control.clojure' 155 | } 156 | ] 157 | 'dynamic-variables': 158 | 'match': '\\*[\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\d]+\\*' 159 | 'name': 'meta.symbol.dynamic.clojure' 160 | 'map': 161 | 'begin': '(\\{)' 162 | 'beginCaptures': 163 | '1': 164 | 'name': 'punctuation.section.map.begin.clojure' 165 | 'end': '(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})' 166 | 'endCaptures': 167 | '1': 168 | 'name': 'punctuation.section.map.end.trailing.clojure' 169 | '2': 170 | 'name': 'punctuation.section.map.end.clojure' 171 | 'name': 'meta.map.clojure' 172 | 'patterns': [ 173 | { 174 | 'include': '$self' 175 | } 176 | ] 177 | 'metadata': 178 | 'patterns': [ 179 | { 180 | 'begin': '(\\^\\{)' 181 | 'beginCaptures': 182 | '1': 183 | 'name': 'punctuation.section.metadata.map.begin.clojure' 184 | 'end': '(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})' 185 | 'endCaptures': 186 | '1': 187 | 'name': 'punctuation.section.metadata.map.end.trailing.clojure' 188 | '2': 189 | 'name': 'punctuation.section.metadata.map.end.clojure' 190 | 'name': 'meta.metadata.map.clojure' 191 | 'patterns': [ 192 | { 193 | 'include': '$self' 194 | } 195 | ] 196 | } 197 | { 198 | 'begin': '(\\^)' 199 | 'end': '(\\s)' 200 | 'name': 'meta.metadata.simple.clojure' 201 | 'patterns': [ 202 | { 203 | 'include': '#keyword' 204 | } 205 | { 206 | 'include': '$self' 207 | } 208 | ] 209 | } 210 | ] 211 | 'quoted-sexp': 212 | 'begin': '([\'``]\\()' 213 | 'beginCaptures': 214 | '1': 215 | 'name': 'punctuation.section.expression.begin.clojure' 216 | 'end': '(\\))$|(\\)(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\))' 217 | 'endCaptures': 218 | '1': 219 | 'name': 'punctuation.section.expression.end.trailing.clojure' 220 | '2': 221 | 'name': 'punctuation.section.expression.end.trailing.clojure' 222 | '3': 223 | 'name': 'punctuation.section.expression.end.clojure' 224 | 'name': 'meta.quoted-expression.clojure' 225 | 'patterns': [ 226 | { 227 | 'include': '$self' 228 | } 229 | ] 230 | 'regexp': 231 | 'begin': '#"' 232 | 'beginCaptures': 233 | '0': 234 | 'name': 'punctuation.definition.regexp.begin.clojure' 235 | 'end': '"' 236 | 'endCaptures': 237 | '0': 238 | 'name': 'punctuation.definition.regexp.end.clojure' 239 | 'name': 'string.regexp.clojure' 240 | 'patterns': [ 241 | { 242 | 'include': '#regexp_escaped_char' 243 | } 244 | ] 245 | 'regexp_escaped_char': 246 | 'match': '\\\\.' 247 | 'name': 'constant.character.escape.clojure' 248 | 'set': 249 | 'begin': '(\\#\\{)' 250 | 'beginCaptures': 251 | '1': 252 | 'name': 'punctuation.section.set.begin.clojure' 253 | 'end': '(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})' 254 | 'endCaptures': 255 | '1': 256 | 'name': 'punctuation.section.set.end.trailing.clojure' 257 | '2': 258 | 'name': 'punctuation.section.set.end.clojure' 259 | 'name': 'meta.set.clojure' 260 | 'patterns': [ 261 | { 262 | 'include': '$self' 263 | } 264 | ] 265 | 'sexp': 266 | 'begin': '(\\()' 267 | 'beginCaptures': 268 | '1': 269 | 'name': 'punctuation.section.expression.begin.clojure' 270 | 'end': '(\\))$|(\\)(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\))' 271 | 'endCaptures': 272 | '1': 273 | 'name': 'punctuation.section.expression.end.trailing.clojure' 274 | '2': 275 | 'name': 'punctuation.section.expression.end.trailing.clojure' 276 | '3': 277 | 'name': 'punctuation.section.expression.end.clojure' 278 | 'name': 'meta.expression.clojure' 279 | 'patterns': [ 280 | { 281 | # ns, declare and everything that starts with def* or namespace/def* 282 | 'begin': '(?<=\\()(ns|declare|def(?!ault)[\\w\\d._:+=>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)' 300 | 'name': 'entity.global.clojure' 301 | } 302 | { 303 | 'include': '$self' 304 | } 305 | ] 306 | } 307 | { 308 | 'include': '#keyfn' 309 | } 310 | { 311 | 'include': '#constants' 312 | } 313 | { 314 | 'include': '#vector' 315 | } 316 | { 317 | 'include': '#map' 318 | } 319 | { 320 | 'include': '#set' 321 | } 322 | { 323 | 'include': '#sexp' 324 | } 325 | { 326 | 'match': '(?<=\\()([^"]+?)(?=\\s|\\))' 327 | 'captures': 328 | '1': 329 | 'name': 'entity.name.function.clojure' 330 | 'patterns': [ 331 | { 332 | 'include': '$self' 333 | } 334 | ] 335 | } 336 | { 337 | 'include': '$self' 338 | } 339 | ] 340 | 'shebang-comment': 341 | # NOTE: This must be kept as a begin/end match for language-todo to work 342 | 'begin': '^(#!)' 343 | 'beginCaptures': 344 | '1': 345 | 'name': 'punctuation.definition.comment.shebang.clojure' 346 | 'end': '$' 347 | 'name': 'comment.line.shebang.clojure' 348 | 'string': 349 | 'begin': '(?\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)/' 369 | 'captures': 370 | '1': 371 | 'name': 'meta.symbol.namespace.clojure' 372 | } 373 | ] 374 | 'symbol': 375 | 'patterns': [ 376 | { 377 | 'match': '([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)' 378 | 'name': 'meta.symbol.clojure' 379 | } 380 | ] 381 | 'var': 382 | 'match': '(?<=(\\s|\\(|\\[|\\{)\\#)\'[\\w\\.\\-\\_\\:\\+\\=\\>\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}))' 383 | 'name': 'meta.var.clojure' 384 | 'vector': 385 | 'begin': '(\\[)' 386 | 'beginCaptures': 387 | '1': 388 | 'name': 'punctuation.section.vector.begin.clojure' 389 | 'end': '(\\](?=[\\}\\]\\)\\s]*(?:;|$)))|(\\])' 390 | 'endCaptures': 391 | '1': 392 | 'name': 'punctuation.section.vector.end.trailing.clojure' 393 | '2': 394 | 'name': 'punctuation.section.vector.end.clojure' 395 | 'name': 'meta.vector.clojure' 396 | 'patterns': [ 397 | { 398 | 'include': '$self' 399 | } 400 | ] 401 | -------------------------------------------------------------------------------- /atom-language-clojure/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "language-clojure", 3 | "version": "0.22.7", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 11 | "dev": true 12 | }, 13 | "brace-expansion": { 14 | "version": "1.1.11", 15 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 16 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 17 | "dev": true, 18 | "requires": { 19 | "balanced-match": "^1.0.0", 20 | "concat-map": "0.0.1" 21 | } 22 | }, 23 | "coffee-script": { 24 | "version": "1.11.1", 25 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.11.1.tgz", 26 | "integrity": "sha1-vxxHrWREOg2V0S3ysUfMCk2q1uk=", 27 | "dev": true 28 | }, 29 | "coffeelint": { 30 | "version": "1.16.2", 31 | "resolved": "https://registry.npmjs.org/coffeelint/-/coffeelint-1.16.2.tgz", 32 | "integrity": "sha512-6mzgOo4zb17WfdrSui/cSUEgQ0AQkW3gXDht+6lHkfkqGUtSYKwGdGcXsDfAyuScVzTlTtKdfwkAlJWfqul7zg==", 33 | "dev": true, 34 | "requires": { 35 | "coffee-script": "~1.11.0", 36 | "glob": "^7.0.6", 37 | "ignore": "^3.0.9", 38 | "optimist": "^0.6.1", 39 | "resolve": "^0.6.3", 40 | "strip-json-comments": "^1.0.2" 41 | } 42 | }, 43 | "concat-map": { 44 | "version": "0.0.1", 45 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 46 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 47 | "dev": true 48 | }, 49 | "fs.realpath": { 50 | "version": "1.0.0", 51 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 52 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 53 | "dev": true 54 | }, 55 | "glob": { 56 | "version": "7.1.3", 57 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 58 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 59 | "dev": true, 60 | "requires": { 61 | "fs.realpath": "^1.0.0", 62 | "inflight": "^1.0.4", 63 | "inherits": "2", 64 | "minimatch": "^3.0.4", 65 | "once": "^1.3.0", 66 | "path-is-absolute": "^1.0.0" 67 | } 68 | }, 69 | "ignore": { 70 | "version": "3.3.10", 71 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", 72 | "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", 73 | "dev": true 74 | }, 75 | "inflight": { 76 | "version": "1.0.6", 77 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 78 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 79 | "dev": true, 80 | "requires": { 81 | "once": "^1.3.0", 82 | "wrappy": "1" 83 | } 84 | }, 85 | "inherits": { 86 | "version": "2.0.3", 87 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 88 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 89 | "dev": true 90 | }, 91 | "minimatch": { 92 | "version": "3.0.4", 93 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 94 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 95 | "dev": true, 96 | "requires": { 97 | "brace-expansion": "^1.1.7" 98 | } 99 | }, 100 | "minimist": { 101 | "version": "0.0.10", 102 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", 103 | "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", 104 | "dev": true 105 | }, 106 | "once": { 107 | "version": "1.4.0", 108 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 109 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 110 | "dev": true, 111 | "requires": { 112 | "wrappy": "1" 113 | } 114 | }, 115 | "optimist": { 116 | "version": "0.6.1", 117 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 118 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 119 | "dev": true, 120 | "requires": { 121 | "minimist": "~0.0.1", 122 | "wordwrap": "~0.0.2" 123 | } 124 | }, 125 | "path-is-absolute": { 126 | "version": "1.0.1", 127 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 128 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 129 | "dev": true 130 | }, 131 | "resolve": { 132 | "version": "0.6.3", 133 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", 134 | "integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=", 135 | "dev": true 136 | }, 137 | "strip-json-comments": { 138 | "version": "1.0.4", 139 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", 140 | "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", 141 | "dev": true 142 | }, 143 | "wordwrap": { 144 | "version": "0.0.3", 145 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 146 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", 147 | "dev": true 148 | }, 149 | "wrappy": { 150 | "version": "1.0.2", 151 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 152 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 153 | "dev": true 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /atom-language-clojure/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "language-clojure", 3 | "version": "0.22.7", 4 | "description": "Clojure language support in Atom", 5 | "engines": { 6 | "atom": "*", 7 | "node": "*" 8 | }, 9 | "homepage": "http://atom.github.io/language-clojure", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/atom/language-clojure" 13 | }, 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/atom/language-clojure/issues" 17 | }, 18 | "devDependencies": { 19 | "coffeelint": "^1.10.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /atom-language-clojure/settings/language-clojure.cson: -------------------------------------------------------------------------------- 1 | '.source.clojure': 2 | 'editor': 3 | 'commentStart': '; ' 4 | 'autocomplete': 5 | 'extraWordCharacters': '-' 6 | -------------------------------------------------------------------------------- /atom-language-clojure/snippets/language-clojure.cson: -------------------------------------------------------------------------------- 1 | '.source.clojure': 2 | 'ns': 3 | 'prefix': 'ns' 4 | 'body': """ 5 | (ns ${1:name} 6 | (:require [${2:libraries}])) 7 | $0 8 | """ 9 | 10 | 'def': 11 | 'prefix': 'def' 12 | 'body': '(def ${1:symbol} ${2:value})' 13 | 14 | 'defn': 15 | 'prefix': 'defn' 16 | 'body': """ 17 | (defn ${1:name} 18 | [${2:params}] 19 | ${3:body}) 20 | """ 21 | 22 | 'fn': 23 | 'prefix': 'fn' 24 | 'body': """ 25 | (fn [${1:params}] 26 | ${2:body})$0 27 | """ 28 | 29 | 'let': 30 | 'prefix': 'let' 31 | 'body': """ 32 | (let [${1:bindings}] 33 | ${2:body}) 34 | """ 35 | 36 | 'if': 37 | 'prefix': 'if' 38 | 'body': """ 39 | (if ${1:test} 40 | ${2:then} 41 | ${3:else}) 42 | """ 43 | 44 | 'if-let': 45 | 'prefix': 'ifl' 46 | 'body': """ 47 | (if-let [${1:bindings}] 48 | ${2:then} 49 | ${3:else}) 50 | """ 51 | 52 | 'if-not': 53 | 'prefix': 'ifn' 54 | 'body': """ 55 | (if-not ${1:test} 56 | ${2:then} 57 | ${3:else}) 58 | """ 59 | 60 | 'when': 61 | 'prefix': 'when' 62 | 'body': """ 63 | (when ${1:test} 64 | ${2:body}) 65 | """ 66 | 67 | 'when-let': 68 | 'prefix': 'whenl' 69 | 'body': """ 70 | (when-let [${1:bindings}] 71 | ${2:body}) 72 | """ 73 | 74 | 'when-not': 75 | 'prefix': 'whenn' 76 | 'body': """ 77 | (when-not ${1:test} 78 | ${2:body}) 79 | """ 80 | 81 | 'map': 82 | 'prefix': 'map' 83 | 'body': '(map $1 $2)' 84 | 85 | 'map lambda': 86 | 'prefix': 'mapl' 87 | 'body': '(map #($1) $2)' 88 | 89 | 'condp': 90 | 'prefix': 'condp' 91 | 'body': """ 92 | (condp ${1:pred} ${2:expr} 93 | $0) 94 | """ 95 | 96 | 'try': 97 | 'prefix': 'try' 98 | 'body': """ 99 | (try 100 | $1 101 | (catch ${2:exception} e 102 | $3)) 103 | """ 104 | 105 | 'prn': 106 | 'prefix': 'prn' 107 | 'body': '(prn $1)' 108 | 109 | 'println': 110 | 'prefix': 'prnl' 111 | 'body': '(println $1)' 112 | -------------------------------------------------------------------------------- /atom-language-clojure/spec/clojure-spec.coffee: -------------------------------------------------------------------------------- 1 | describe "Clojure grammar", -> 2 | grammar = null 3 | 4 | beforeEach -> 5 | waitsForPromise -> 6 | atom.packages.activatePackage("language-clojure") 7 | 8 | runs -> 9 | grammar = atom.grammars.grammarForScopeName("source.clojure") 10 | 11 | it "parses the grammar", -> 12 | expect(grammar).toBeDefined() 13 | expect(grammar.scopeName).toBe "source.clojure" 14 | 15 | it "tokenizes semicolon comments", -> 16 | {tokens} = grammar.tokenizeLine "; clojure" 17 | expect(tokens[0]).toEqual value: ";", scopes: ["source.clojure", "comment.line.semicolon.clojure", "punctuation.definition.comment.clojure"] 18 | expect(tokens[1]).toEqual value: " clojure", scopes: ["source.clojure", "comment.line.semicolon.clojure"] 19 | 20 | it "does not tokenize escaped semicolons as comments", -> 21 | {tokens} = grammar.tokenizeLine "\\; clojure" 22 | expect(tokens[0]).toEqual value: "\\; ", scopes: ["source.clojure"] 23 | expect(tokens[1]).toEqual value: "clojure", scopes: ["source.clojure", "meta.symbol.clojure"] 24 | 25 | it "tokenizes shebang comments", -> 26 | {tokens} = grammar.tokenizeLine "#!/usr/bin/env clojure" 27 | expect(tokens[0]).toEqual value: "#!", scopes: ["source.clojure", "comment.line.shebang.clojure", "punctuation.definition.comment.shebang.clojure"] 28 | expect(tokens[1]).toEqual value: "/usr/bin/env clojure", scopes: ["source.clojure", "comment.line.shebang.clojure"] 29 | 30 | it "tokenizes strings", -> 31 | {tokens} = grammar.tokenizeLine '"foo bar"' 32 | expect(tokens[0]).toEqual value: '"', scopes: ["source.clojure", "string.quoted.double.clojure", "punctuation.definition.string.begin.clojure"] 33 | expect(tokens[1]).toEqual value: 'foo bar', scopes: ["source.clojure", "string.quoted.double.clojure"] 34 | expect(tokens[2]).toEqual value: '"', scopes: ["source.clojure", "string.quoted.double.clojure", "punctuation.definition.string.end.clojure"] 35 | 36 | it "tokenizes character escape sequences", -> 37 | {tokens} = grammar.tokenizeLine '"\\n"' 38 | expect(tokens[0]).toEqual value: '"', scopes: ["source.clojure", "string.quoted.double.clojure", "punctuation.definition.string.begin.clojure"] 39 | expect(tokens[1]).toEqual value: '\\n', scopes: ["source.clojure", "string.quoted.double.clojure", "constant.character.escape.clojure"] 40 | expect(tokens[2]).toEqual value: '"', scopes: ["source.clojure", "string.quoted.double.clojure", "punctuation.definition.string.end.clojure"] 41 | 42 | it "tokenizes regexes", -> 43 | {tokens} = grammar.tokenizeLine '#"foo"' 44 | expect(tokens[0]).toEqual value: '#"', scopes: ["source.clojure", "string.regexp.clojure", "punctuation.definition.regexp.begin.clojure"] 45 | expect(tokens[1]).toEqual value: 'foo', scopes: ["source.clojure", "string.regexp.clojure"] 46 | expect(tokens[2]).toEqual value: '"', scopes: ["source.clojure", "string.regexp.clojure", "punctuation.definition.regexp.end.clojure"] 47 | 48 | it "tokenizes backslash escape character in regexes", -> 49 | {tokens} = grammar.tokenizeLine '#"\\\\" "/"' 50 | expect(tokens[0]).toEqual value: '#"', scopes: ["source.clojure", "string.regexp.clojure", "punctuation.definition.regexp.begin.clojure"] 51 | expect(tokens[1]).toEqual value: "\\\\", scopes: ['source.clojure', 'string.regexp.clojure', 'constant.character.escape.clojure'] 52 | expect(tokens[2]).toEqual value: '"', scopes: ['source.clojure', 'string.regexp.clojure', "punctuation.definition.regexp.end.clojure"] 53 | expect(tokens[4]).toEqual value: '"', scopes: ['source.clojure', 'string.quoted.double.clojure', 'punctuation.definition.string.begin.clojure'] 54 | expect(tokens[5]).toEqual value: "/", scopes: ['source.clojure', 'string.quoted.double.clojure'] 55 | expect(tokens[6]).toEqual value: '"', scopes: ['source.clojure', 'string.quoted.double.clojure', 'punctuation.definition.string.end.clojure'] 56 | 57 | it "tokenizes escaped double quote in regexes", -> 58 | {tokens} = grammar.tokenizeLine '#"\\""' 59 | expect(tokens[0]).toEqual value: '#"', scopes: ["source.clojure", "string.regexp.clojure", "punctuation.definition.regexp.begin.clojure"] 60 | expect(tokens[1]).toEqual value: '\\"', scopes: ['source.clojure', 'string.regexp.clojure', 'constant.character.escape.clojure'] 61 | expect(tokens[2]).toEqual value: '"', scopes: ['source.clojure', 'string.regexp.clojure', "punctuation.definition.regexp.end.clojure"] 62 | 63 | it "tokenizes numerics", -> 64 | numbers = 65 | "constant.numeric.ratio.clojure": ["1/2", "123/456"] 66 | "constant.numeric.arbitrary-radix.clojure": ["2R1011", "16rDEADBEEF", "56råäöÅÄÖπ"] 67 | "constant.numeric.hexadecimal.clojure": ["0xDEADBEEF", "0XDEADBEEF"] 68 | "constant.numeric.octal.clojure": ["0123"] 69 | "constant.numeric.bigdecimal.clojure": ["123.456M"] 70 | "constant.numeric.double.clojure": ["123.45", "123.45e6", "123.45E6"] 71 | "constant.numeric.bigint.clojure": ["123N"] 72 | "constant.numeric.long.clojure": ["123", "12321"] 73 | 74 | for scope, nums of numbers 75 | for num in nums 76 | {tokens} = grammar.tokenizeLine num 77 | expect(tokens[0]).toEqual value: num, scopes: ["source.clojure", scope] 78 | 79 | it "tokenizes booleans", -> 80 | booleans = 81 | "constant.language.boolean.clojure": ["true", "false"] 82 | 83 | for scope, bools of booleans 84 | for bool in bools 85 | {tokens} = grammar.tokenizeLine bool 86 | expect(tokens[0]).toEqual value: bool, scopes: ["source.clojure", scope] 87 | 88 | it "tokenizes nil", -> 89 | {tokens} = grammar.tokenizeLine "nil" 90 | expect(tokens[0]).toEqual value: "nil", scopes: ["source.clojure", "constant.language.nil.clojure"] 91 | 92 | it "tokenizes keywords", -> 93 | tests = 94 | "meta.expression.clojure": ["(:foo)"] 95 | "meta.map.clojure": ["{:foo}"] 96 | "meta.vector.clojure": ["[:foo]"] 97 | "meta.quoted-expression.clojure": ["'(:foo)", "`(:foo)"] 98 | 99 | for metaScope, lines of tests 100 | for line in lines 101 | {tokens} = grammar.tokenizeLine line 102 | expect(tokens[1]).toEqual value: ":foo", scopes: ["source.clojure", metaScope, "constant.keyword.clojure"] 103 | 104 | {tokens} = grammar.tokenizeLine "(def foo :bar)" 105 | expect(tokens[5]).toEqual value: ":bar", scopes: ["source.clojure", "meta.expression.clojure", "meta.definition.global.clojure", "constant.keyword.clojure"] 106 | 107 | # keywords can start with an uppercase non-ASCII letter 108 | {tokens} = grammar.tokenizeLine "(def foo :Öπ)" 109 | expect(tokens[5]).toEqual value: ":Öπ", scopes: ["source.clojure", "meta.expression.clojure", "meta.definition.global.clojure", "constant.keyword.clojure"] 110 | 111 | it "tokenizes keyfns (keyword control)", -> 112 | keyfns = ["declare", "declare-", "ns", "in-ns", "import", "use", "require", "load", "compile", "def", "defn", "defn-", "defmacro", "defåπç"] 113 | 114 | for keyfn in keyfns 115 | {tokens} = grammar.tokenizeLine "(#{keyfn})" 116 | expect(tokens[1]).toEqual value: keyfn, scopes: ["source.clojure", "meta.expression.clojure", "keyword.control.clojure"] 117 | 118 | it "does not tokenize `default...`s as keyfn (keyword control)", -> 119 | { tokens } = grammar.tokenizeLine "(defnormal foo)" 120 | expect(tokens[1].scopes).toContain "keyword.control.clojure" 121 | { tokens } = grammar.tokenizeLine "(normaldef foo)" 122 | expect(tokens[1].scopes).not.toContain "keyword.control.clojure" 123 | { tokens } = grammar.tokenizeLine "(default foo)" 124 | expect(tokens[1].scopes).not.toContain "keyword.control.clojure" 125 | { tokens } = grammar.tokenizeLine "(defaultfoo ba)" 126 | expect(tokens[1].scopes).not.toContain "keyword.control.clojure" 127 | 128 | it "tokenizes keyfns (storage control)", -> 129 | keyfns = ["if", "when", "for", "cond", "do", "let", "binding", "loop", "recur", "fn", "throw", "try", "catch", "finally", "case"] 130 | 131 | for keyfn in keyfns 132 | {tokens} = grammar.tokenizeLine "(#{keyfn})" 133 | expect(tokens[1]).toEqual value: keyfn, scopes: ["source.clojure", "meta.expression.clojure", "storage.control.clojure"] 134 | 135 | it "tokenizes global definitions", -> 136 | 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åÄÖπç"] 137 | 138 | for macro in macros 139 | {tokens} = grammar.tokenizeLine "(#{macro} foo 'bar)" 140 | expect(tokens[1]).toEqual value: macro, scopes: ["source.clojure", "meta.expression.clojure", "meta.definition.global.clojure", "keyword.control.clojure"] 141 | expect(tokens[3]).toEqual value: "foo", scopes: ["source.clojure", "meta.expression.clojure", "meta.definition.global.clojure", "entity.global.clojure"] 142 | 143 | it "tokenizes dynamic variables", -> 144 | mutables = ["*ns*", "*foo-bar*", "*åÄÖπç*"] 145 | 146 | for mutable in mutables 147 | {tokens} = grammar.tokenizeLine mutable 148 | expect(tokens[0]).toEqual value: mutable, scopes: ["source.clojure", "meta.symbol.dynamic.clojure"] 149 | 150 | it "tokenizes metadata", -> 151 | {tokens} = grammar.tokenizeLine "^Foo" 152 | expect(tokens[0]).toEqual value: "^", scopes: ["source.clojure", "meta.metadata.simple.clojure"] 153 | expect(tokens[1]).toEqual value: "Foo", scopes: ["source.clojure", "meta.metadata.simple.clojure", "meta.symbol.clojure"] 154 | 155 | # non-ASCII letters 156 | {tokens} = grammar.tokenizeLine "^Öπ" 157 | expect(tokens[0]).toEqual value: "^", scopes: ["source.clojure", "meta.metadata.simple.clojure"] 158 | expect(tokens[1]).toEqual value: "Öπ", scopes: ["source.clojure", "meta.metadata.simple.clojure", "meta.symbol.clojure"] 159 | 160 | {tokens} = grammar.tokenizeLine "^{:foo true}" 161 | expect(tokens[0]).toEqual value: "^{", scopes: ["source.clojure", "meta.metadata.map.clojure", "punctuation.section.metadata.map.begin.clojure"] 162 | expect(tokens[1]).toEqual value: ":foo", scopes: ["source.clojure", "meta.metadata.map.clojure", "constant.keyword.clojure"] 163 | expect(tokens[2]).toEqual value: " ", scopes: ["source.clojure", "meta.metadata.map.clojure"] 164 | expect(tokens[3]).toEqual value: "true", scopes: ["source.clojure", "meta.metadata.map.clojure", "constant.language.boolean.clojure"] 165 | expect(tokens[4]).toEqual value: "}", scopes: ["source.clojure", "meta.metadata.map.clojure", "punctuation.section.metadata.map.end.trailing.clojure"] 166 | 167 | it "tokenizes functions", -> 168 | expressions = ["(foo)", "(foo 1 10)"] 169 | 170 | for expr in expressions 171 | {tokens} = grammar.tokenizeLine expr 172 | expect(tokens[1]).toEqual value: "foo", scopes: ["source.clojure", "meta.expression.clojure", "entity.name.function.clojure"] 173 | 174 | #non-ASCII letters 175 | {tokens} = grammar.tokenizeLine "(Öπ 2 20)" 176 | expect(tokens[1]).toEqual value: "Öπ", scopes: ["source.clojure", "meta.expression.clojure", "entity.name.function.clojure"] 177 | 178 | it "tokenizes vars", -> 179 | {tokens} = grammar.tokenizeLine "(func #'foo)" 180 | expect(tokens[2]).toEqual value: " #", scopes: ["source.clojure", "meta.expression.clojure"] 181 | expect(tokens[3]).toEqual value: "'foo", scopes: ["source.clojure", "meta.expression.clojure", "meta.var.clojure"] 182 | 183 | # non-ASCII letters 184 | {tokens} = grammar.tokenizeLine "(func #'Öπ)" 185 | expect(tokens[2]).toEqual value: " #", scopes: ["source.clojure", "meta.expression.clojure"] 186 | expect(tokens[3]).toEqual value: "'Öπ", scopes: ["source.clojure", "meta.expression.clojure", "meta.var.clojure"] 187 | 188 | it "tokenizes symbols", -> 189 | {tokens} = grammar.tokenizeLine "x" 190 | expect(tokens[0]).toEqual value: "x", scopes: ["source.clojure", "meta.symbol.clojure"] 191 | 192 | # non-ASCII letters 193 | {tokens} = grammar.tokenizeLine "Öπ" 194 | expect(tokens[0]).toEqual value: "Öπ", scopes: ["source.clojure", "meta.symbol.clojure"] 195 | 196 | # Should not be tokenized as a symbol 197 | {tokens} = grammar.tokenizeLine "1foobar" 198 | expect(tokens[0]).toEqual value: "1", scopes: ["source.clojure", "constant.numeric.long.clojure"] 199 | 200 | it "tokenizes namespaces", -> 201 | {tokens} = grammar.tokenizeLine "foo/bar" 202 | expect(tokens[0]).toEqual value: "foo", scopes: ["source.clojure", "meta.symbol.namespace.clojure"] 203 | expect(tokens[1]).toEqual value: "/", scopes: ["source.clojure"] 204 | expect(tokens[2]).toEqual value: "bar", scopes: ["source.clojure", "meta.symbol.clojure"] 205 | 206 | # non-ASCII letters 207 | {tokens} = grammar.tokenizeLine "Öπ/Åä" 208 | expect(tokens[0]).toEqual value: "Öπ", scopes: ["source.clojure", "meta.symbol.namespace.clojure"] 209 | expect(tokens[1]).toEqual value: "/", scopes: ["source.clojure"] 210 | expect(tokens[2]).toEqual value: "Åä", scopes: ["source.clojure", "meta.symbol.clojure"] 211 | 212 | testMetaSection = (metaScope, puncScope, startsWith, endsWith) -> 213 | # Entire expression on one line. 214 | {tokens} = grammar.tokenizeLine "#{startsWith}foo, bar#{endsWith}" 215 | 216 | [start, mid..., end] = tokens 217 | 218 | expect(start).toEqual value: startsWith, scopes: ["source.clojure", "meta.#{metaScope}.clojure", "punctuation.section.#{puncScope}.begin.clojure"] 219 | expect(end).toEqual value: endsWith, scopes: ["source.clojure", "meta.#{metaScope}.clojure", "punctuation.section.#{puncScope}.end.trailing.clojure"] 220 | 221 | for token in mid 222 | expect(token.scopes.slice(0, 2)).toEqual ["source.clojure", "meta.#{metaScope}.clojure"] 223 | 224 | # Expression broken over multiple lines. 225 | tokens = grammar.tokenizeLines("#{startsWith}foo\n bar#{endsWith}") 226 | 227 | [start, mid..., after] = tokens[0] 228 | 229 | expect(start).toEqual value: startsWith, scopes: ["source.clojure", "meta.#{metaScope}.clojure", "punctuation.section.#{puncScope}.begin.clojure"] 230 | 231 | for token in mid 232 | expect(token.scopes.slice(0, 2)).toEqual ["source.clojure", "meta.#{metaScope}.clojure"] 233 | 234 | [mid..., end] = tokens[1] 235 | 236 | expect(end).toEqual value: endsWith, scopes: ["source.clojure", "meta.#{metaScope}.clojure", "punctuation.section.#{puncScope}.end.trailing.clojure"] 237 | 238 | for token in mid 239 | expect(token.scopes.slice(0, 2)).toEqual ["source.clojure", "meta.#{metaScope}.clojure"] 240 | 241 | it "tokenizes expressions", -> 242 | testMetaSection "expression", "expression", "(", ")" 243 | 244 | it "tokenizes quoted expressions", -> 245 | testMetaSection "quoted-expression", "expression", "'(", ")" 246 | testMetaSection "quoted-expression", "expression", "`(", ")" 247 | 248 | it "tokenizes vectors", -> 249 | testMetaSection "vector", "vector", "[", "]" 250 | 251 | it "tokenizes maps", -> 252 | testMetaSection "map", "map", "{", "}" 253 | 254 | it "tokenizes sets", -> 255 | testMetaSection "set", "set", "\#{", "}" 256 | 257 | it "tokenizes functions in nested sexp", -> 258 | {tokens} = grammar.tokenizeLine "((foo bar) baz)" 259 | expect(tokens[0]).toEqual value: "(", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.begin.clojure"] 260 | expect(tokens[1]).toEqual value: "(", scopes: ["source.clojure", "meta.expression.clojure", "meta.expression.clojure", "punctuation.section.expression.begin.clojure"] 261 | expect(tokens[2]).toEqual value: "foo", scopes: ["source.clojure", "meta.expression.clojure", "meta.expression.clojure", "entity.name.function.clojure"] 262 | expect(tokens[3]).toEqual value: " ", scopes: ["source.clojure", "meta.expression.clojure", "meta.expression.clojure"] 263 | expect(tokens[4]).toEqual value: "bar", scopes: ["source.clojure", "meta.expression.clojure", "meta.expression.clojure", "meta.symbol.clojure"] 264 | expect(tokens[5]).toEqual value: ")", scopes: ["source.clojure", "meta.expression.clojure", "meta.expression.clojure", "punctuation.section.expression.end.clojure"] 265 | expect(tokens[6]).toEqual value: " ", scopes: ["source.clojure", "meta.expression.clojure"] 266 | expect(tokens[7]).toEqual value: "baz", scopes: ["source.clojure", "meta.expression.clojure", "meta.symbol.clojure"] 267 | expect(tokens[8]).toEqual value: ")", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.end.trailing.clojure"] 268 | 269 | it "tokenizes maps used as functions", -> 270 | {tokens} = grammar.tokenizeLine "({:foo bar} :foo)" 271 | expect(tokens[0]).toEqual value: "(", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.begin.clojure"] 272 | expect(tokens[1]).toEqual value: "{", scopes: ["source.clojure", "meta.expression.clojure", "meta.map.clojure", "punctuation.section.map.begin.clojure"] 273 | expect(tokens[2]).toEqual value: ":foo", scopes: ["source.clojure", "meta.expression.clojure", "meta.map.clojure", "constant.keyword.clojure"] 274 | expect(tokens[3]).toEqual value: " ", scopes: ["source.clojure", "meta.expression.clojure", "meta.map.clojure"] 275 | expect(tokens[4]).toEqual value: "bar", scopes: ["source.clojure", "meta.expression.clojure", "meta.map.clojure", "meta.symbol.clojure"] 276 | expect(tokens[5]).toEqual value: "}", scopes: ["source.clojure", "meta.expression.clojure", "meta.map.clojure", "punctuation.section.map.end.clojure"] 277 | expect(tokens[6]).toEqual value: " ", scopes: ["source.clojure", "meta.expression.clojure"] 278 | expect(tokens[7]).toEqual value: ":foo", scopes: ["source.clojure", "meta.expression.clojure", "constant.keyword.clojure"] 279 | expect(tokens[8]).toEqual value: ")", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.end.trailing.clojure"] 280 | 281 | it "tokenizes sets used in functions", -> 282 | {tokens} = grammar.tokenizeLine "(\#{:foo :bar})" 283 | expect(tokens[0]).toEqual value: "(", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.begin.clojure"] 284 | expect(tokens[1]).toEqual value: "\#{", scopes: ["source.clojure", "meta.expression.clojure", "meta.set.clojure", "punctuation.section.set.begin.clojure"] 285 | expect(tokens[2]).toEqual value: ":foo", scopes: ["source.clojure", "meta.expression.clojure", "meta.set.clojure", "constant.keyword.clojure"] 286 | expect(tokens[3]).toEqual value: " ", scopes: ["source.clojure", "meta.expression.clojure", "meta.set.clojure"] 287 | expect(tokens[4]).toEqual value: ":bar", scopes: ["source.clojure", "meta.expression.clojure", "meta.set.clojure", "constant.keyword.clojure"] 288 | expect(tokens[5]).toEqual value: "}", scopes: ["source.clojure", "meta.expression.clojure", "meta.set.clojure", "punctuation.section.set.end.trailing.clojure"] 289 | expect(tokens[6]).toEqual value: ")", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.end.trailing.clojure"] 290 | 291 | it "tokenize strings (wrongly) used as functions", -> 292 | {tokens} = grammar.tokenizeLine "(\"foo)\")" 293 | expect(tokens[0]).toEqual value: "(", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.begin.clojure"] 294 | expect(tokens[1]).toEqual value: "\"", scopes: ["source.clojure", "meta.expression.clojure", "string.quoted.double.clojure", "punctuation.definition.string.begin.clojure"] 295 | expect(tokens[2]).toEqual value: "foo)", scopes: ["source.clojure", "meta.expression.clojure", "string.quoted.double.clojure"] 296 | expect(tokens[3]).toEqual value: "\"", scopes: ["source.clojure", "meta.expression.clojure", "string.quoted.double.clojure", "punctuation.definition.string.end.clojure"] 297 | expect(tokens[4]).toEqual value: ")", scopes: ["source.clojure", "meta.expression.clojure", "punctuation.section.expression.end.trailing.clojure"] 298 | 299 | describe "firstLineMatch", -> 300 | it "recognises interpreter directives", -> 301 | valid = """ 302 | #!/usr/sbin/boot foo 303 | #!/usr/bin/boot foo=bar/ 304 | #!/usr/sbin/boot 305 | #!/usr/sbin/boot foo bar baz 306 | #!/usr/bin/boot perl 307 | #!/usr/bin/boot bin/perl 308 | #!/usr/bin/boot 309 | #!/bin/boot 310 | #!/usr/bin/boot --script=usr/bin 311 | #! /usr/bin/env A=003 B=149 C=150 D=xzd E=base64 F=tar G=gz H=head I=tail boot 312 | #!\t/usr/bin/env --foo=bar boot --quu=quux 313 | #! /usr/bin/boot 314 | #!/usr/bin/env boot 315 | """ 316 | for line in valid.split /\n/ 317 | expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull() 318 | 319 | invalid = """ 320 | \x20#!/usr/sbin/boot 321 | \t#!/usr/sbin/boot 322 | #!/usr/bin/env-boot/node-env/ 323 | #!/usr/bin/das-boot 324 | #! /usr/binboot 325 | #!\t/usr/bin/env --boot=bar 326 | """ 327 | for line in invalid.split /\n/ 328 | expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull() 329 | 330 | it "recognises Emacs modelines", -> 331 | valid = """ 332 | #-*- Clojure -*- 333 | #-*- mode: ClojureScript -*- 334 | /* -*-clojureScript-*- */ 335 | // -*- Clojure -*- 336 | /* -*- mode:Clojure -*- */ 337 | // -*- font:bar;mode:Clojure -*- 338 | // -*- font:bar;mode:Clojure;foo:bar; -*- 339 | // -*-font:mode;mode:Clojure-*- 340 | // -*- foo:bar mode: clojureSCRIPT bar:baz -*- 341 | " -*-foo:bar;mode:clojure;bar:foo-*- "; 342 | " -*-font-mode:foo;mode:clojure;foo-bar:quux-*-" 343 | "-*-font:x;foo:bar; mode : clojure; bar:foo;foooooo:baaaaar;fo:ba;-*-"; 344 | "-*- font:x;foo : bar ; mode : ClojureScript ; bar : foo ; foooooo:baaaaar;fo:ba-*-"; 345 | """ 346 | for line in valid.split /\n/ 347 | expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull() 348 | 349 | invalid = """ 350 | /* --*clojure-*- */ 351 | /* -*-- clojure -*- 352 | /* -*- -- Clojure -*- 353 | /* -*- Clojure -;- -*- 354 | // -*- iClojure -*- 355 | // -*- Clojure; -*- 356 | // -*- clojure-door -*- 357 | /* -*- model:clojure -*- 358 | /* -*- indent-mode:clojure -*- 359 | // -*- font:mode;Clojure -*- 360 | // -*- mode: -*- Clojure 361 | // -*- mode: das-clojure -*- 362 | // -*-font:mode;mode:clojure--*- 363 | """ 364 | for line in invalid.split /\n/ 365 | expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull() 366 | 367 | it "recognises Vim modelines", -> 368 | valid = """ 369 | vim: se filetype=clojure: 370 | # vim: se ft=clojure: 371 | # vim: set ft=Clojure: 372 | # vim: set filetype=Clojure: 373 | # vim: ft=Clojure 374 | # vim: syntax=Clojure 375 | # vim: se syntax=Clojure: 376 | # ex: syntax=Clojure 377 | # vim:ft=clojure 378 | # vim600: ft=clojure 379 | # vim>600: set ft=clojure: 380 | # vi:noai:sw=3 ts=6 ft=clojure 381 | # vi::::::::::noai:::::::::::: ft=clojure 382 | # vim:ts=4:sts=4:sw=4:noexpandtab:ft=clojure 383 | # vi:: noai : : : : sw =3 ts =6 ft =clojure 384 | # vim: ts=4: pi sts=4: ft=clojure: noexpandtab: sw=4: 385 | # vim: ts=4 sts=4: ft=clojure noexpandtab: 386 | # vim:noexpandtab sts=4 ft=clojure ts=4 387 | # vim:noexpandtab:ft=clojure 388 | # vim:ts=4:sts=4 ft=clojure:noexpandtab:\x20 389 | # vim:noexpandtab titlestring=hi\|there\\\\ ft=clojure ts=4 390 | """ 391 | for line in valid.split /\n/ 392 | expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull() 393 | 394 | invalid = """ 395 | ex: se filetype=clojure: 396 | _vi: se filetype=clojure: 397 | vi: se filetype=clojure 398 | # vim set ft=klojure 399 | # vim: soft=clojure 400 | # vim: clean-syntax=clojure: 401 | # vim set ft=clojure: 402 | # vim: setft=clojure: 403 | # vim: se ft=clojure backupdir=tmp 404 | # vim: set ft=clojure set cmdheight=1 405 | # vim:noexpandtab sts:4 ft:clojure ts:4 406 | # vim:noexpandtab titlestring=hi\\|there\\ ft=clojure ts=4 407 | # vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=clojure ts=4 408 | """ 409 | for line in invalid.split /\n/ 410 | expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull() 411 | -------------------------------------------------------------------------------- /calva-fmt/config.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | function readConfiguration() { 4 | let workspaceConfig = vscode.workspace.getConfiguration("calva.fmt"); 5 | return { 6 | "format-as-you-type": workspaceConfig.get("formatAsYouType"), 7 | "indentation?": workspaceConfig.get("indentation"), 8 | "remove-surrounding-whitespace?": workspaceConfig.get("removeSurroundingWhitespace"), 9 | "remove-trailing-whitespace?": workspaceConfig.get("removeTrailingWhitespace"), 10 | "insert-missing-whitespace?": workspaceConfig.get("insertMissingWhitespace"), 11 | "remove-consecutive-blank-lines?": workspaceConfig.get("removeConsecutiveBlankLines"), 12 | "align-associative?": workspaceConfig.get("allgnMapItems") 13 | }; 14 | } 15 | 16 | export function getConfig() { 17 | let config = readConfiguration(); 18 | return config; 19 | } 20 | -------------------------------------------------------------------------------- /calva-fmt/docmirror/clojure-lexer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A Scanner for Clojure. 3 | * 4 | * Based on a pared down version of my lexer without a lot of other needless cruft relating to source information etc. 5 | * Unlike flex, this lexer doesn't support states, so we toggle between toplevel and string states by swapping lexers. 6 | * 7 | * This is *not* a fully accurate lexer, since it needs to be robust in the face of junk. 8 | */ 9 | import { LexicalGrammar, Token as LexerToken } from "./lexer"; 10 | 11 | 12 | export interface Token extends LexerToken { 13 | state: ScannerState; 14 | } 15 | 16 | // The toplevel lexical grammar for Clojure. 17 | let toplevel = new LexicalGrammar(); 18 | 19 | // whitespace 20 | toplevel.terminal("[\\s,]+", (l, m) => ({ type: "ws" })) 21 | // comments 22 | toplevel.terminal(";.*", (l, m) => ({ type: "comment" })) 23 | // open parens 24 | toplevel.terminal("\\(|\\[|\\{|#\\(|#?\\(|#\\{|#?@\\(", (l, m) => ({ type: "open" })) 25 | // close parens 26 | toplevel.terminal("\\)|\\]|\\}", (l, m) => ({ type: "close" })) 27 | 28 | // punctuators 29 | toplevel.terminal("~@|~|'|#'|#:|#_|\\^|`|#|\\^:", (l, m) => ({ type: "punc" })) 30 | 31 | toplevel.terminal("true|false|nil", (l, m) => ({type: "lit"})) 32 | toplevel.terminal("[0-9]+[rR][0-9a-zA-Z]+", (l, m) => ({ type: "lit" })) 33 | toplevel.terminal("[-+]?[0-9]+(\\.[0-9]+)?([eE][-+]?[0-9]+)?", (l, m) => ({ type: "lit" })) 34 | 35 | toplevel.terminal(":[^()[\\]\\{\\}#,~@'`^\"\\s]+", (l, m) => ({ type: "lit" })) 36 | // this is a REALLY lose symbol definition, but similar to how clojure really collects it. numbers/true/nil are all 37 | toplevel.terminal("[^()[\\]\\{\\}#,~@'`^\"\\s:][^()[\\]\\{\\}#,~@'`^\"\\s]*", (l, m) => ({ type: "id" })) 38 | // complete string on a single line 39 | toplevel.terminal('"([^"\\\\]|\\\\.)*"', (l, m) => ({ type: "str"})) 40 | 41 | // begin a multiline string 42 | toplevel.terminal('"([^"\\\\]|\\\\.)*', (l, m) => ({ type: "str-start"})) 43 | toplevel.terminal('.', (l, m) => ({ type: "junk" })) 44 | 45 | 46 | // Inside a multi-line string lexical grammar 47 | let multstring = new LexicalGrammar() 48 | // end a multiline string 49 | multstring.terminal('([^"\\\\]|\\\\.)*"', (l, m) => ({ type: "str-end" })) 50 | // still within a multiline string 51 | multstring.terminal('([^"\\\\]|\\\\.)*', (l, m) => ({ type: "str-inside" })) 52 | 53 | /** The state of the scanner */ 54 | export interface ScannerState { 55 | /** Are we scanning inside a string? If so use multstring grammar, otherwise use toplevel. */ 56 | inString: boolean 57 | } 58 | 59 | export class Scanner { 60 | state: ScannerState = { inString: false }; 61 | processLine(line: string, lineNumber: number, state: ScannerState = this.state) { 62 | let tks: Token[] = []; 63 | this.state = state; 64 | let lex = (this.state.inString ? multstring : toplevel).lex(line); 65 | let tk: LexerToken; 66 | do { 67 | tk = lex.scan(); 68 | if(tk) { 69 | let oldpos = lex.position; 70 | switch(tk.type) { 71 | case "str-end": // multiline string ended, switch back to toplevel 72 | this.state = { ...this.state, inString: false}; 73 | lex = toplevel.lex(line); 74 | lex.position = oldpos; 75 | break; 76 | case "str-start": // multiline string started, switch to multstring. 77 | this.state = { ...this.state, inString: true}; 78 | lex = multstring.lex(line); 79 | lex.position = oldpos; 80 | break; 81 | } 82 | tks.push({ ...tk, state: this.state }); 83 | } 84 | } while(tk); 85 | // insert a sentinel EOL value, this allows us to simplify TokenCaret's implementation. 86 | tks.push({ type: "eol", raw: "\n", offset: line.length, state: this.state }) 87 | return tks; 88 | } 89 | } -------------------------------------------------------------------------------- /calva-fmt/docmirror/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Scanner, ScannerState, Token } from "./clojure-lexer"; 3 | 4 | const scanner = new Scanner(); 5 | 6 | class ClojureSourceLine { 7 | tokens: Token[]; 8 | endState: ScannerState; 9 | constructor(public text: string, public startState: ScannerState, lineNo: number) { 10 | this.tokens = scanner.processLine(text, lineNo, startState) 11 | this.endState = scanner.state; 12 | } 13 | 14 | get length() { 15 | return this.text.length; 16 | } 17 | } 18 | 19 | let debugValidation = false 20 | 21 | /** A mutable cursor into the token stream. */ 22 | class TokenCursor { 23 | constructor(public doc: DocumentMirror, public line: number, public token: number) { 24 | } 25 | 26 | /** Create a copy of this cursor. */ 27 | clone() { 28 | return new TokenCursor(this.doc, this.line, this.token); 29 | } 30 | 31 | set(cursor: TokenCursor) { 32 | this.doc = cursor.doc; 33 | this.line = cursor.line; 34 | this.token = cursor.token; 35 | } 36 | 37 | /** Return the position */ 38 | get position() { 39 | return new vscode.Position(this.line, this.getToken().offset); 40 | } 41 | 42 | /** True if we are at the start of the document */ 43 | atStart() { 44 | return this.token == 0 && this.line == 0; 45 | } 46 | 47 | /** True if we are at the end of the document */ 48 | atEnd() { 49 | return this.line == this.doc.lines.length-1 && this.token == this.doc.lines[this.line].tokens.length-1; 50 | } 51 | 52 | /** Move this cursor backwards one token */ 53 | previous() { 54 | if(this.token > 0) { 55 | this.token--; 56 | } else { 57 | if(this.line == 0) return; 58 | this.line--; 59 | this.token = this.doc.lines[this.line].tokens.length-1; 60 | } 61 | return this; 62 | } 63 | 64 | /** Move this cursor forwards one token */ 65 | next() { 66 | if(this.token < this.doc.lines[this.line].tokens.length-1) { 67 | this.token++; 68 | } else { 69 | if(this.line == this.doc.lines.length-1) return; 70 | this.line++; 71 | this.token = 0; 72 | } 73 | } 74 | 75 | fowardString() { 76 | while(!this.atEnd()) { 77 | switch(this.getToken().type) { 78 | case "eol": 79 | case "str-inside": 80 | case "str-start": 81 | this.next(); 82 | continue; 83 | default: 84 | return; 85 | } 86 | } 87 | } 88 | 89 | forwardWhitespace() { 90 | while(!this.atEnd()) { 91 | switch(this.getToken().type) { 92 | case "eol": 93 | case "ws": 94 | case "comment": 95 | this.next(); 96 | continue; 97 | default: 98 | return; 99 | } 100 | } 101 | } 102 | 103 | backwardWhitespace() { 104 | while(!this.atStart()) { 105 | switch(this.getPrevToken().type) { 106 | case "eol": 107 | case "ws": 108 | case "comment": 109 | this.previous(); 110 | continue; 111 | default: 112 | return; 113 | } 114 | } 115 | } 116 | 117 | forwardSexp(): boolean { 118 | let delta = 0; 119 | this.forwardWhitespace(); 120 | if(this.getToken().type == "close") { 121 | return false; 122 | } 123 | while(!this.atEnd()) { 124 | this.forwardWhitespace(); 125 | let tk = this.getToken(); 126 | switch(tk.type) { 127 | case 'id': 128 | case 'lit': 129 | case 'str': 130 | case 'str-end': 131 | this.next(); 132 | if(delta <= 0) 133 | return true; 134 | break; 135 | case 'str-inside': 136 | case 'str-start': 137 | do { 138 | this.next(); 139 | tk = this.getToken(); 140 | } while(!this.atEnd() && (tk.type == "str-inside" || tk.type == "eol")) 141 | continue; 142 | case 'close': 143 | delta--; 144 | this.next(); 145 | if(delta <= 0) 146 | return true; 147 | break; 148 | case 'open': 149 | delta++; 150 | this.next(); 151 | break; 152 | default: 153 | this.next(); 154 | break; 155 | } 156 | } 157 | } 158 | 159 | backwardSexp() { 160 | let delta = 0; 161 | this.backwardWhitespace(); 162 | switch(this.getPrevToken().type) { 163 | case "open": 164 | return false; 165 | } 166 | while(!this.atStart()) { 167 | this.backwardWhitespace(); 168 | let tk = this.getPrevToken(); 169 | switch(tk.type) { 170 | case 'id': 171 | case 'lit': 172 | case 'str': 173 | case 'str-start': 174 | this.previous(); 175 | if(delta <= 0) 176 | return true; 177 | break; 178 | case 'str-inside': 179 | case 'str-end': 180 | do { 181 | this.previous(); 182 | tk = this.getPrevToken(); 183 | } while(!this.atStart() && tk.type == "str-inside") 184 | continue; 185 | case 'close': 186 | delta++; 187 | this.previous(); 188 | break; 189 | case 'open': 190 | delta--; 191 | this.previous(); 192 | if(delta <= 0) 193 | return true; 194 | break; 195 | default: 196 | this.previous(); 197 | } 198 | } 199 | } 200 | 201 | forwardList(): boolean { 202 | let cursor = this.clone(); 203 | while(cursor.forwardSexp()) { 204 | if(cursor.getPrevToken().type == "close") { 205 | this.set(cursor); 206 | return true; 207 | } 208 | this.next() 209 | } 210 | return false; 211 | } 212 | 213 | backwardList(): boolean { 214 | let cursor = this.clone(); 215 | while(cursor.backwardSexp()) { 216 | if(cursor.getToken().type == "open") { 217 | this.set(cursor); 218 | return true; 219 | } 220 | } 221 | return false; 222 | } 223 | 224 | downList(): boolean { 225 | let cursor = this.clone(); 226 | do { 227 | cursor.forwardWhitespace(); 228 | if(cursor.getToken().type == "open") { 229 | cursor.next(); 230 | this.set(cursor); 231 | return true; 232 | } 233 | } while(cursor.forwardSexp()) 234 | return false; 235 | } 236 | 237 | upList(): boolean { 238 | let cursor = this.clone(); 239 | do { 240 | cursor.forwardWhitespace(); 241 | if(cursor.getToken().type == "close") { 242 | cursor.next(); 243 | this.set(cursor); 244 | return true; 245 | } 246 | } while(cursor.forwardSexp()) 247 | return false; 248 | } 249 | 250 | backwardUpList(): boolean { 251 | let cursor = this.clone(); 252 | do { 253 | cursor.backwardWhitespace(); 254 | if(cursor.getPrevToken().type == "open") { 255 | cursor.previous(); 256 | this.set(cursor); 257 | return true; 258 | } 259 | } while(cursor.backwardSexp()) 260 | return false; 261 | } 262 | 263 | getPrevToken(): Token { 264 | if(this.line == 0 && this.token == 0) 265 | return { type: "eol", raw: "\n", offset: 0, state: null }; 266 | let cursor = this.clone(); 267 | cursor.previous(); 268 | return cursor.getToken(); 269 | } 270 | 271 | getToken() { 272 | return this.doc.lines[this.line].tokens[this.token]; 273 | } 274 | } 275 | 276 | function equal(x: any, y: any): boolean { 277 | if(x==y) return true; 278 | if(x instanceof Array && y instanceof Array) { 279 | if(x.length == y.length) { 280 | for(let i = 0; i= 0 && idx < this.lines.length) 325 | if(this.dirtyLines.indexOf(idx) == -1) 326 | this.dirtyLines.push(idx); 327 | } 328 | 329 | private removeDirty(start: number, end: number, inserted: number) { 330 | let delta = end-start + inserted; 331 | this.dirtyLines = this.dirtyLines.filter(x => x < start || x > end) 332 | .map(x => x > start ? x - delta : x); 333 | } 334 | 335 | private getStateForLine(line: number): ScannerState { 336 | return line == 0 ? { inString: false, } : { ... this.lines[line-1].endState }; 337 | } 338 | 339 | public getTokenCursor(pos: vscode.Position, previous: boolean = false) { 340 | this.flushChanges(); 341 | let line = this.lines[pos.line] 342 | let lastIndex = 0; 343 | if(line) { 344 | for(let i=0; i pos.character : tk.offset > pos.character) 347 | return new TokenCursor(this, pos.line, previous ? Math.max(0, lastIndex-1) : lastIndex); 348 | lastIndex = i; 349 | } 350 | return new TokenCursor(this, pos.line, line.tokens.length-1); 351 | } 352 | } 353 | 354 | private changeRange(e: vscode.TextDocumentContentChangeEvent) { 355 | // extract the lines we will replace 356 | let replaceLines = e.text.split(this.doc.eol == vscode.EndOfLine.LF ? /\n/ : /\r\n/); 357 | 358 | // the left side of the line unaffected by the edit. 359 | let left = this.lines[e.range.start.line].text.substr(0, e.range.start.character); 360 | 361 | // the right side of the line unaffected by the edit. 362 | let right = this.lines[e.range.end.line].text.substr(e.range.end.character); 363 | 364 | // we've nuked these lines, so update the dirty line array to correct the indices and delete affected ranges. 365 | this.removeDirty(e.range.start.line, e.range.end.line, replaceLines.length-1); 366 | 367 | let items: ClojureSourceLine[] = []; 368 | 369 | // initialize the lexer state - the first line is definitely not in a string, otherwise copy the 370 | // end state of the previous line before the edit 371 | let state = this.getStateForLine(e.range.start.line) 372 | 373 | if(replaceLines.length == 1) { 374 | // trivial single line edit 375 | items.push(new ClojureSourceLine(left + replaceLines[0] + right, state, e.range.start.line)); 376 | } else { 377 | // multi line edit. 378 | items.push(new ClojureSourceLine(left + replaceLines[0], state, e.range.start.line)); 379 | for(let i=1; i(); 394 | this.dirtyLines.sort(); 395 | while(this.dirtyLines.length) { 396 | let nextIdx = this.dirtyLines.shift(); 397 | if(seen.has(nextIdx)) 398 | continue; // already processed. 399 | seen.add(nextIdx); 400 | let prevState = this.getStateForLine(nextIdx); 401 | let newLine: ClojureSourceLine; 402 | do { 403 | seen.add(nextIdx); 404 | newLine = new ClojureSourceLine(this.lines[nextIdx].text, prevState, nextIdx); 405 | prevState = newLine.endState; 406 | this.lines[nextIdx] = newLine; 407 | } while(this.lines[++nextIdx] && !(equal(this.lines[nextIdx].startState, prevState))) 408 | } 409 | } 410 | 411 | processChanges(e: vscode.TextDocumentContentChangeEvent[]) { 412 | for(let change of e) 413 | this.changeRange(change); 414 | this.flushChanges(); 415 | 416 | if(debugValidation && this.doc.getText() != this.text) 417 | vscode.window.showErrorMessage("DocumentMirror failed"); 418 | } 419 | 420 | get text() { 421 | return this.lines.map(x => x.text).join(this.doc.eol == vscode.EndOfLine.LF ? "\n" : "\r\n"); 422 | } 423 | } 424 | 425 | let documents = new Map(); 426 | 427 | let registered = false; 428 | export function activate() { 429 | // the last thing we want is to register twice and receive double events... 430 | if(registered) 431 | return; 432 | registered = true; 433 | 434 | vscode.workspace.onDidCloseTextDocument(e => { 435 | if(e.languageId == "clojure") { 436 | documents.delete(e); 437 | } 438 | }) 439 | 440 | vscode.workspace.onDidChangeTextDocument(e => { 441 | if(e.document.languageId == "clojure") { 442 | if(!documents.get(e.document)) 443 | documents.set(e.document, new DocumentMirror(e.document)); 444 | else 445 | documents.get(e.document).processChanges(e.contentChanges) 446 | } 447 | }) 448 | } 449 | 450 | export function getDocument(doc: vscode.TextDocument) { 451 | if(doc.languageId == "clojure") { 452 | if(!documents.get(doc)) 453 | documents.set(doc, new DocumentMirror(doc)); 454 | return documents.get(doc); 455 | } 456 | } 457 | 458 | /** 459 | * Temporary formatting commands 460 | * 461 | * These won't live here. 462 | */ 463 | export function forwardSexp() { 464 | let textEditor = vscode.window.activeTextEditor; 465 | let cursor = getDocument(textEditor.document).getTokenCursor(textEditor.selection.start); 466 | cursor.forwardSexp(); 467 | textEditor.selection = new vscode.Selection(cursor.position, cursor.position); 468 | } 469 | 470 | export function backwardSexp() { 471 | let textEditor = vscode.window.activeTextEditor; 472 | let cursor = getDocument(textEditor.document).getTokenCursor(textEditor.selection.start); 473 | cursor.backwardSexp(); 474 | textEditor.selection = new vscode.Selection(cursor.position, cursor.position); 475 | } 476 | 477 | export function forwardList() { 478 | let textEditor = vscode.window.activeTextEditor; 479 | let cursor = getDocument(textEditor.document).getTokenCursor(textEditor.selection.start); 480 | cursor.forwardList(); 481 | textEditor.selection = new vscode.Selection(cursor.position, cursor.position); 482 | } 483 | 484 | export function backwardList() { 485 | let textEditor = vscode.window.activeTextEditor; 486 | let cursor = getDocument(textEditor.document).getTokenCursor(textEditor.selection.start); 487 | cursor.backwardList(); 488 | textEditor.selection = new vscode.Selection(cursor.position, cursor.position); 489 | } 490 | 491 | export function downList() { 492 | let textEditor = vscode.window.activeTextEditor; 493 | let cursor = getDocument(textEditor.document).getTokenCursor(textEditor.selection.start); 494 | cursor.downList(); 495 | textEditor.selection = new vscode.Selection(cursor.position, cursor.position); 496 | } 497 | 498 | export function upList() { 499 | let textEditor = vscode.window.activeTextEditor; 500 | let cursor = getDocument(textEditor.document).getTokenCursor(textEditor.selection.start); 501 | cursor.upList(); 502 | textEditor.selection = new vscode.Selection(cursor.position, cursor.position); 503 | } 504 | 505 | export function backwardUpList() { 506 | let textEditor = vscode.window.activeTextEditor; 507 | let cursor = getDocument(textEditor.document).getTokenCursor(textEditor.selection.start); 508 | cursor.backwardUpList(); 509 | textEditor.selection = new vscode.Selection(cursor.position, cursor.position); 510 | } 511 | 512 | const whitespace = new Set(["ws", "comment", "eol"]) 513 | 514 | type IndentRule = ["block", number] | ["inner", number] | ["inner", number, number]; 515 | 516 | let indentRules: { [id: string]: IndentRule[]} = { 517 | "alt!": [["block", 0]], 518 | "alt!!": [["block", 0]], 519 | "are": [["block", 2]] , 520 | "as->": [["block", 2]], 521 | "binding": [["block", 1]], 522 | "bound-fn": [["inner", 1]], 523 | "case": [["block", 1]], 524 | "catch": [["block", 2]], 525 | "comment": [["block", 0]], 526 | "cond": [["block", 0]], 527 | "condp": [["block", 2]], 528 | "cond->": [["block", 1]], 529 | "cond->>": [["block", 1]], 530 | "def": [["inner", 0]], 531 | "defmacro": [["inner", 0]], 532 | "defmethod": [["inner", 0]], 533 | "defmulti": [["inner", 0]], 534 | "defn": [["inner", 0]], 535 | "defn-": [["inner", 0]], 536 | "defonce": [["inner", 0]], 537 | "defprotocol": [["block", 1], ["inner", 1]], 538 | "defrecord": [["block", 2], ["inner", 1]], 539 | "defstruct": [["block", 1]], 540 | "deftest": [["inner", 0]], 541 | "deftype": [["block", 2], ["inner", 1]], 542 | "do": [["block", 0]], 543 | "doseq": [["block", 1]], 544 | "dotimes": [["block", 1]], 545 | "doto": [["block", 1]], 546 | "extend": [["block", 1]], 547 | "extend-protocol": [["block", 1], ["inner", 1]], 548 | "extend-type": [["block", 1], ["inner", 1]], 549 | "fdef": [["inner", 0]], 550 | "finally": [["block", 0]], 551 | "fn": [["inner", 0]], 552 | "for": [["block", 1]], 553 | "future": [["block", 0]], 554 | "go": [["block", 0]], 555 | "go-loop": [["block", 1]], 556 | "if": [["block", 1]], 557 | "if-let": [["block", 1]], 558 | "if-not": [["block", 1]], 559 | "if-some": [["block", 1]], 560 | "let": [["block", 1]], 561 | "letfn": [["block", 1], ["inner", 2, 0]], 562 | "locking": [["block", 1]], 563 | "loop": [["block", 1]], 564 | "match": [["block", 1]], 565 | "ns": [["block", 1]], 566 | "proxy": [["block", 2], ["inner", 1]], 567 | "reify": [["inner", 0], ["inner", 1]], 568 | "struct-map": [["block", 1]], 569 | "testing": [["block", 1]], 570 | "thread": [["block", 0]], 571 | "try": [["block", 0]], 572 | "use-fixtures": [["inner", 0]], 573 | "when": [["block", 1]], 574 | "when-first": [["block", 1]], 575 | "when-let": [["block", 1]], 576 | "when-not": [["block", 1]], 577 | "when-some": [["block", 1]], 578 | "while": [["block", 1]], 579 | "with-local-vars": [["block", 1]], 580 | "with-open": [["block", 1]], 581 | "with-out-str": [["block", 0]], 582 | "with-precision": [["block", 1]], 583 | "with-redefs": [["block", 1]], 584 | } 585 | 586 | interface IndentState { 587 | first: string; 588 | startIndent: number; 589 | firstItemIdent: number; 590 | rules: IndentRule[]; 591 | argPos: number; 592 | exprsOnLine: number; 593 | } 594 | 595 | // If a token's raw string is in this set, then it counts as an 'open list'. An open list is something that could be 596 | // considered code, so special formatting rules apply. 597 | let OPEN_LIST = new Set(["#(", "#?(", "(", "#?@("]) 598 | 599 | export function collectIndentState(document: vscode.TextDocument, position: vscode.Position, maxDepth: number = 3, maxLines: number = 20): IndentState[] { 600 | let cursor = getDocument(document).getTokenCursor(position); 601 | let argPos = 0; 602 | let startLine = cursor.line; 603 | let exprsOnLine = 0; 604 | let lastLine = cursor.line; 605 | let lastIndent = -1; 606 | let indents: IndentState[] = []; 607 | do { 608 | if(!cursor.backwardSexp()) { 609 | // this needs some work.. 610 | let prevToken = cursor.getPrevToken(); 611 | if(prevToken.type == 'open' && prevToken.offset <= 1) { 612 | maxDepth = 0; // treat an sexpr starting on line 0 sensibly. 613 | } 614 | // skip past the first item and record the indent of the first item on the same line if there is one. 615 | let nextCursor = cursor.clone(); 616 | nextCursor.forwardSexp() 617 | nextCursor.forwardWhitespace(); 618 | 619 | // iff the first item of this list is a an identifier, and the second item is on the same line, indent to that second item. otherwise indent to the open paren. 620 | let firstItemIdent = cursor.getToken().type == "id" && nextCursor.line == cursor.line && OPEN_LIST.has(prevToken.raw) ? nextCursor.position.character : cursor.position.character; 621 | 622 | 623 | let token = cursor.getToken().raw; 624 | let startIndent = cursor.position.character; 625 | if(!cursor.backwardUpList()) 626 | break; 627 | let indentRule = indentRules[token] || []; 628 | indents.unshift({ first: token, rules: indentRule, argPos, exprsOnLine, startIndent, firstItemIdent }); 629 | argPos = 0; 630 | exprsOnLine = 1; 631 | } 632 | if(!whitespace.has(cursor.getPrevToken().raw)) { 633 | argPos++; 634 | exprsOnLine++; 635 | } 636 | 637 | if(cursor.line != lastLine) { 638 | let head = cursor.clone(); 639 | head.forwardSexp(); 640 | head.forwardWhitespace(); 641 | lastIndent = head.position.character; 642 | exprsOnLine = 0; 643 | lastLine = cursor.line; 644 | } 645 | } while(!cursor.atEnd() && Math.abs(startLine-cursor.line) < maxLines && indents.length < maxDepth); 646 | if(!indents.length) 647 | indents.push({argPos: 0, first: null, rules: [], exprsOnLine: 0, startIndent: lastIndent >= 0 ? lastIndent : 0, firstItemIdent: lastIndent >= 0 ? lastIndent : 0}) 648 | return indents; 649 | } 650 | 651 | /** Returns [argumentPosition, startOfList] */ 652 | export function getIndent(document: vscode.TextDocument, position: vscode.Position): number { 653 | let state = collectIndentState(document, position); 654 | // now find applicable indent rules 655 | let indent = -1; 656 | let thisBlock = state[state.length-1]; 657 | if(!state.length) 658 | return 0; 659 | 660 | for(let pos = state.length-1; pos >= 0; pos--) { 661 | for(let rule of state[pos].rules) { 662 | if(rule[0] == "inner") { 663 | if(pos + rule[1] == state.length-1) { 664 | if(rule.length == 3) { 665 | if(rule[2] > thisBlock.argPos) 666 | indent = thisBlock.startIndent + 1; 667 | } else 668 | indent = thisBlock.startIndent + 1; 669 | } 670 | } else if(rule[0] == "block" && pos == state.length-1) { 671 | if(thisBlock.exprsOnLine <= rule[1]) { 672 | if(thisBlock.argPos > rule[1]) 673 | indent = thisBlock.startIndent + 1 674 | } else { 675 | indent = thisBlock.firstItemIdent; 676 | } 677 | } 678 | } 679 | } 680 | 681 | if(indent == -1) { 682 | // no indentation styles applied, so use default style. 683 | if(thisBlock.exprsOnLine > 0) 684 | indent = thisBlock.firstItemIdent; 685 | else 686 | indent = thisBlock.startIndent 687 | } 688 | return indent; 689 | } -------------------------------------------------------------------------------- /calva-fmt/docmirror/lexer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A Lexical analyser 3 | * @module lexer 4 | */ 5 | 6 | export interface Token { 7 | type: string; 8 | raw: string; 9 | offset: number; 10 | } 11 | 12 | export interface Rule { 13 | r: RegExp; 14 | fn: (Lexer, RegExpExecArray) => any 15 | } 16 | 17 | /** 18 | * A Lexer instance, parsing a given file. Usually you should use a LexicalGrammar to 19 | * create one of these. 20 | * 21 | * @class 22 | * @param {string} source the source code to parse 23 | * @param rules the rules of this lexer. 24 | */ 25 | export class Lexer { 26 | position: number = 0; 27 | constructor(public source: string, public rules: Rule[]) { 28 | } 29 | 30 | peeked: any; 31 | 32 | peek() { 33 | return this.peeked = this.scan(); 34 | } 35 | 36 | match(type: string, raw?: string) { 37 | let p = this.peek(); 38 | if(p && p.type == type && (!raw || p.raw == raw)) { 39 | this.peeked = null; 40 | return true; 41 | } 42 | return false; 43 | } 44 | 45 | scan(): Token { 46 | if(this.peeked) { 47 | let res = this.peeked; 48 | this.peeked = null; 49 | return res; 50 | } 51 | var token = null; 52 | var length = 0; 53 | this.rules.forEach(rule => { 54 | rule.r.lastIndex = this.position; 55 | var x = rule.r.exec(this.source); 56 | if (x && x[0].length > length && this.position + x[0].length == rule.r.lastIndex) { 57 | token = rule.fn(this, x); 58 | token.offset = this.position; 59 | token.raw = x[0]; 60 | length = x[0].length; 61 | } 62 | }) 63 | this.position += length; 64 | if (token == null) { 65 | if(this.position == this.source.length) 66 | return null; 67 | throw new Error("Unexpected character at " + this.position + ": "+JSON.stringify(this.source)); 68 | } 69 | return token; 70 | } 71 | } 72 | 73 | /** 74 | * A lexical grammar- factory for lexer instances. 75 | * @class 76 | */ 77 | export class LexicalGrammar { 78 | rules: Rule[] = []; 79 | 80 | /** 81 | * Defines a terminal with the given pattern and constructor. 82 | * @param {string} pattern the pattern this nonterminal must match. 83 | * @param {function(Array): Object} fn returns a lexical token representing 84 | * this terminal. An additional "offset" property containing the token source position 85 | * will also be added, as well as a "raw" property, containing the raw string match. 86 | */ 87 | terminal(pattern: string, fn: (T, RegExpExecArray) => any): void { 88 | this.rules.push({ r: new RegExp(pattern, "g"), fn: fn }) 89 | } 90 | 91 | lex(source: string): Lexer { 92 | return new Lexer(source, this.rules) 93 | } 94 | } -------------------------------------------------------------------------------- /calva-fmt/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { FormatOnTypeEditProvider } from './providers/ontype_formatter'; 3 | import { RangeEditProvider } from './providers/range_formatter'; 4 | import * as formatter from './format'; 5 | import * as inferer from './infer'; 6 | import * as docmirror from "./docmirror" 7 | 8 | const ClojureLanguageConfiguration: vscode.LanguageConfiguration = { 9 | wordPattern: /[^\s,#()[\]{};"\\]+/, 10 | onEnterRules: [ 11 | // This is madness, but the only way to stop vscode from indenting new lines 12 | { 13 | beforeText: /.*/, 14 | action: { 15 | indentAction: vscode.IndentAction.Outdent, 16 | removeText: Number.MAX_VALUE 17 | } 18 | }, 19 | ] 20 | } 21 | 22 | 23 | 24 | function activate(context: vscode.ExtensionContext) { 25 | let calva = vscode.extensions.getExtension('cospaia.clojure4vscode'), 26 | calvaApi = calva.exports; 27 | if (calvaApi && calvaApi.hasFormatter) { 28 | console.log("calva-fmt: Not registering any commands since Calva w/ Formatter is installed."); 29 | return; 30 | } else { 31 | console.log("calva-fmt: Registering commands since Calva does not have Formatter yet."); 32 | } 33 | 34 | docmirror.activate(); 35 | vscode.languages.setLanguageConfiguration("clojure", ClojureLanguageConfiguration); 36 | // this doesn't actually grow anything yet, but just jumps to the start of the enclosing expression. 37 | // context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.forwardSexp', docmirror.forwardSexp)) 38 | // context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.backwardSexp', docmirror.backwardSexp)) 39 | // context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.forwardList', docmirror.forwardList)) 40 | // context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.backwardList', docmirror.backwardList)) 41 | // context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.downList', docmirror.downList)) 42 | // context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.upList', docmirror.upList)) 43 | // context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.backwardUpList', docmirror.backwardUpList)) 44 | 45 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.formatCurrentForm', formatter.formatPositionCommand)); 46 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.alignCurrentForm', formatter.alignPositionCommand)); 47 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.inferParens', inferer.inferParensCommand)); 48 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.tabIndent', (e) => { inferer.indentCommand(e, " ", true) })); 49 | context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.tabDedent', (e) => { inferer.indentCommand(e, " ", false) })); 50 | context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider("clojure", new FormatOnTypeEditProvider, "\r", "\n")); 51 | context.subscriptions.push(vscode.languages.registerDocumentRangeFormattingEditProvider("clojure", new RangeEditProvider)); 52 | vscode.window.onDidChangeActiveTextEditor(inferer.updateState); 53 | } 54 | 55 | module.exports = { 56 | activate 57 | } 58 | -------------------------------------------------------------------------------- /calva-fmt/format.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as config from './config'; 3 | const formatter = require('@cospaia/calva-lib/lib/calva.fmt.formatter'); 4 | const jsUtils = require('@cospaia/calva-lib/lib/calva.js_utils'); 5 | 6 | export function formatRangeEdits(document: vscode.TextDocument, range: vscode.Range): vscode.TextEdit[] { 7 | const text: string = document.getText(range), 8 | rangeTuple: number[] = [document.offsetAt(range.start), document.offsetAt(range.end)], 9 | newText: string = _formatRange(text, document.getText(), rangeTuple, document.eol == 2 ? "\r\n" : "\n"); 10 | return [vscode.TextEdit.replace(range, newText)]; 11 | } 12 | 13 | export function formatRange(document: vscode.TextDocument, range: vscode.Range) { 14 | let wsEdit: vscode.WorkspaceEdit = new vscode.WorkspaceEdit(); 15 | wsEdit.set(document.uri, formatRangeEdits(document, range)); 16 | return vscode.workspace.applyEdit(wsEdit); 17 | } 18 | 19 | export function formatPosition(editor: vscode.TextEditor, onType: boolean = false, extraConfig = {}): void { 20 | const doc: vscode.TextDocument = editor.document, 21 | pos: vscode.Position = editor.selection.active, 22 | index = doc.offsetAt(pos), 23 | formatted: { "range-text": string, "range": number[], "new-index": number } = _formatIndex(doc.getText(), index, doc.eol == 2 ? "\r\n" : "\n", onType, extraConfig), 24 | range: vscode.Range = new vscode.Range(doc.positionAt(formatted.range[0]), doc.positionAt(formatted.range[1])), 25 | newIndex: number = doc.offsetAt(range.start) + formatted["new-index"], 26 | previousText: string = doc.getText(range); 27 | if (previousText != formatted["range-text"]) { 28 | editor.edit(textEditorEdit => { 29 | textEditorEdit.replace(range, formatted["range-text"]); 30 | }, { undoStopAfter: false, undoStopBefore: false }).then((_onFulfilled: boolean) => { 31 | editor.selection = new vscode.Selection(doc.positionAt(newIndex), doc.positionAt(newIndex)); 32 | }); 33 | } else { 34 | if (newIndex != index) { 35 | editor.selection = new vscode.Selection(doc.positionAt(newIndex), doc.positionAt(newIndex)); 36 | } 37 | } 38 | } 39 | 40 | export function formatPositionCommand(editor: vscode.TextEditor) { 41 | formatPosition(editor); 42 | } 43 | 44 | export function alignPositionCommand(editor: vscode.TextEditor) { 45 | formatPosition(editor, true, { "align-associative?": true }); 46 | } 47 | 48 | function _formatIndex(allText: string, index: number, eol: string, onType: boolean = false, extraConfig = {}): { "range-text": string, "range": number[], "new-index": number } { 49 | const d = jsUtils.cljify({ 50 | "all-text": allText, 51 | "idx": index, 52 | "eol": eol, 53 | "config": { ...config.getConfig(), ...extraConfig } 54 | }), 55 | result = jsUtils.jsify(onType ? formatter.format_text_at_idx_on_type(d) : formatter.format_text_at_idx(d)); 56 | if (!result["error"]) { 57 | return result; 58 | } 59 | else { 60 | console.log(result["error"]); 61 | return null; 62 | } 63 | } 64 | 65 | 66 | function _formatRange(rangeText: string, allText: string, range: number[], eol: string): string { 67 | const d = { 68 | "range-text": rangeText, 69 | "all-text": allText, 70 | "range": range, 71 | "eol": eol, 72 | "config": config.getConfig() 73 | }, 74 | cljData = jsUtils.cljify(d), 75 | result = jsUtils.jsify(formatter.format_text_at_range(cljData)); 76 | if (!result["error"]) { 77 | return result["range-text"]; 78 | } 79 | else { 80 | console.log(result["error"]); 81 | return rangeText; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /calva-fmt/infer.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | const inferer = require('@cospaia/calva-lib/lib/calva.fmt.inferer'); 3 | 4 | interface CFEdit { 5 | edit: string, 6 | start: { line: number, character: number }, 7 | end: { line: number, character: number }, 8 | text?: string 9 | } 10 | 11 | interface CFError { 12 | message: string 13 | } 14 | 15 | interface ResultOptions { 16 | success: boolean, 17 | edits?: [CFEdit], 18 | line?: number, 19 | character?: number, 20 | error?: CFError, 21 | "error-msg"?: string 22 | } 23 | 24 | export function inferParensCommand(editor: vscode.TextEditor) { 25 | const position: vscode.Position = editor.selection.active, 26 | document = editor.document, 27 | currentText = document.getText(), 28 | r: ResultOptions = inferer.infer_parens({ 29 | "text": currentText, 30 | "line": position.line, 31 | "character": position.character, 32 | "previous-line": position.line, 33 | "previous-character": position.character 34 | }); 35 | applyResults(r, editor); 36 | } 37 | 38 | export function indentCommand(editor: vscode.TextEditor, spacing: string, forward: boolean = true) { 39 | const prevPosition: vscode.Position = editor.selection.active, 40 | document = editor.document; 41 | let deletedText = "", 42 | doEdit = true; 43 | 44 | editor.edit(editBuilder => { 45 | if (forward) { 46 | editBuilder.insert(new vscode.Position(prevPosition.line, prevPosition.character), spacing) 47 | } else { 48 | const startOfLine = new vscode.Position(prevPosition.line, 0), 49 | headRange = new vscode.Range(startOfLine, prevPosition), 50 | headText = document.getText(headRange), 51 | xOfFirstLeadingSpace = headText.search(/ *$/), 52 | leadingSpaces = xOfFirstLeadingSpace >= 0 ? prevPosition.character - xOfFirstLeadingSpace : 0; 53 | if (leadingSpaces > 0) { 54 | const spacingSize = Math.max(spacing.length, 1), 55 | deleteRange = new vscode.Range(prevPosition.translate(0, -spacingSize), prevPosition); 56 | deletedText = document.getText(deleteRange); 57 | editBuilder.delete(deleteRange); 58 | } else { 59 | doEdit = false; 60 | } 61 | } 62 | }, { undoStopAfter: false, undoStopBefore: false }).then((_onFulfilled: boolean) => { 63 | if (doEdit) { 64 | const position: vscode.Position = editor.selection.active, 65 | currentText = document.getText(), 66 | r: ResultOptions = inferer.infer_indents({ 67 | "text": currentText, 68 | "line": position.line, 69 | "character": position.character, 70 | "previous-line": prevPosition.line, 71 | "previous-character": prevPosition.character, 72 | "changes": [{ 73 | "line": forward ? prevPosition.line : position.line, 74 | "character": forward ? prevPosition.character : position.character, 75 | "old-text": forward ? "" : deletedText, 76 | "new-text": forward ? spacing : "" 77 | }] 78 | }); 79 | applyResults(r, editor); 80 | } 81 | }) 82 | } 83 | 84 | function applyResults(r: ResultOptions, editor: vscode.TextEditor) { 85 | if (r.success) { 86 | editor.edit(editBuilder => { 87 | r.edits.forEach((edit: CFEdit) => { 88 | const start = new vscode.Position(edit.start.line, edit.start.character), 89 | end = new vscode.Position(edit.end.line, edit.end.character); 90 | editBuilder.replace(new vscode.Range(start, end), edit.text); 91 | }); 92 | }, { undoStopAfter: true, undoStopBefore: false }).then((_onFulfilled: boolean) => { 93 | const newPosition = new vscode.Position(r.line, r.character); 94 | editor.selections = [new vscode.Selection(newPosition, newPosition)]; 95 | }); 96 | } 97 | else { 98 | vscode.window.showErrorMessage("Calva Formatter Error: " + (r.error ? r.error.message : r["error-msg"])); 99 | } 100 | } 101 | 102 | 103 | export function updateState(editor: vscode.TextEditor) { 104 | 105 | } -------------------------------------------------------------------------------- /calva-fmt/providers/ontype_formatter.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as formatter from '../format'; 3 | import { getDocument, getIndent } from "../docmirror"; 4 | export class FormatOnTypeEditProvider { 5 | async provideOnTypeFormattingEdits(document: vscode.TextDocument, position: vscode.Position, _ch, _options) { 6 | if (vscode.workspace.getConfiguration("calva.fmt").get("formatAsYouType")) { 7 | if (vscode.workspace.getConfiguration("calva.fmt").get("newIndentEngine")) { 8 | let editor = vscode.window.activeTextEditor; 9 | let pos = new vscode.Position(position.line, 0); 10 | let indent = getIndent(document, pos) 11 | 12 | let delta = document.lineAt(position.line).firstNonWhitespaceCharacterIndex - indent; 13 | if (delta > 0) { 14 | //return [vscode.TextEdit.delete(new vscode.Range(pos, new vscode.Position(pos.line, delta)))]; 15 | editor.edit(edits => edits.delete(new vscode.Range(pos, new vscode.Position(pos.line, delta))), { undoStopAfter: false, undoStopBefore: false }); 16 | } else if (delta < 0) { 17 | let str = ""; 18 | while (delta++ < 0) 19 | str += " " 20 | //return [vscode.TextEdit.insert(pos, str)]; 21 | editor.edit(edits => edits.insert(pos, str), { undoStopAfter: false, undoStopBefore: false }); 22 | } 23 | } else { 24 | formatter.formatPosition(vscode.window.activeTextEditor, true); 25 | } 26 | } 27 | return null; 28 | } 29 | } -------------------------------------------------------------------------------- /calva-fmt/providers/range_formatter.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as formatter from '../format'; 3 | 4 | 5 | export class RangeEditProvider implements vscode.DocumentRangeFormattingEditProvider { 6 | provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, _options, _token) { 7 | return formatter.formatRangeEdits(document, range); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /calva-fmt/state.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as Immutable from 'immutable'; 3 | import * as ImmutableCursor from 'immutable-cursor'; 4 | 5 | const mode = { 6 | language: 'clojure', 7 | //scheme: 'file' 8 | }; 9 | 10 | var data; 11 | const initialData = { 12 | documents: {} 13 | }; 14 | 15 | reset(); 16 | 17 | const cursor = ImmutableCursor.from(data, [], (nextState) => { 18 | data = Immutable.fromJS(nextState); 19 | }); 20 | 21 | function deref() { 22 | return data; 23 | } 24 | 25 | function reset() { 26 | data = Immutable.fromJS(initialData); 27 | } 28 | 29 | function config() { 30 | let configOptions = vscode.workspace.getConfiguration('calva.fmt'); 31 | return { 32 | parinferOnSelectionChange: configOptions.get("inferParensOnCursorMove"), 33 | }; 34 | } 35 | 36 | export { 37 | cursor, 38 | mode, 39 | deref, 40 | reset, 41 | config 42 | }; 43 | -------------------------------------------------------------------------------- /clojure.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "information_for_contributors": [ 3 | "This file is generated from ./atom-language-clojure/grammars/clojure.cson" 4 | ], 5 | "name": "Clojure", 6 | "scopeName": "source.clojure", 7 | "patterns": [ 8 | { 9 | "include": "#comment" 10 | }, 11 | { 12 | "include": "#shebang-comment" 13 | }, 14 | { 15 | "include": "#quoted-sexp" 16 | }, 17 | { 18 | "include": "#sexp" 19 | }, 20 | { 21 | "include": "#keyfn" 22 | }, 23 | { 24 | "include": "#string" 25 | }, 26 | { 27 | "include": "#vector" 28 | }, 29 | { 30 | "include": "#set" 31 | }, 32 | { 33 | "include": "#map" 34 | }, 35 | { 36 | "include": "#regexp" 37 | }, 38 | { 39 | "include": "#var" 40 | }, 41 | { 42 | "include": "#constants" 43 | }, 44 | { 45 | "include": "#dynamic-variables" 46 | }, 47 | { 48 | "include": "#metadata" 49 | }, 50 | { 51 | "include": "#namespace-symbol" 52 | }, 53 | { 54 | "include": "#symbol" 55 | } 56 | ], 57 | "repository": { 58 | "comment": { 59 | "begin": "(?\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}|\\,))", 117 | "name": "constant.keyword.clojure" 118 | }, 119 | "keyfn": { 120 | "patterns": [ 121 | { 122 | "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|\\)|\\]|\\}))", 123 | "name": "storage.control.clojure" 124 | }, 125 | { 126 | "match": "(?<=(\\s|\\(|\\[|\\{))(declare-?|(in-)?ns|import|use|require|load|compile|(def(?!ault)[\\p{Ll}\\-]*))(?=(\\s|\\)|\\]|\\}))", 127 | "name": "keyword.control.clojure" 128 | } 129 | ] 130 | }, 131 | "dynamic-variables": { 132 | "match": "\\*[\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\d]+\\*", 133 | "name": "meta.symbol.dynamic.clojure" 134 | }, 135 | "map": { 136 | "begin": "(\\{)", 137 | "beginCaptures": { 138 | "1": { 139 | "name": "punctuation.section.map.begin.clojure" 140 | } 141 | }, 142 | "end": "(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})", 143 | "endCaptures": { 144 | "1": { 145 | "name": "punctuation.section.map.end.trailing.clojure" 146 | }, 147 | "2": { 148 | "name": "punctuation.section.map.end.clojure" 149 | } 150 | }, 151 | "name": "meta.map.clojure", 152 | "patterns": [ 153 | { 154 | "include": "$self" 155 | } 156 | ] 157 | }, 158 | "metadata": { 159 | "patterns": [ 160 | { 161 | "begin": "(\\^\\{)", 162 | "beginCaptures": { 163 | "1": { 164 | "name": "punctuation.section.metadata.map.begin.clojure" 165 | } 166 | }, 167 | "end": "(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})", 168 | "endCaptures": { 169 | "1": { 170 | "name": "punctuation.section.metadata.map.end.trailing.clojure" 171 | }, 172 | "2": { 173 | "name": "punctuation.section.metadata.map.end.clojure" 174 | } 175 | }, 176 | "name": "meta.metadata.map.clojure", 177 | "patterns": [ 178 | { 179 | "include": "$self" 180 | } 181 | ] 182 | }, 183 | { 184 | "begin": "(\\^)", 185 | "end": "(\\s)", 186 | "name": "meta.metadata.simple.clojure", 187 | "patterns": [ 188 | { 189 | "include": "#keyword" 190 | }, 191 | { 192 | "include": "$self" 193 | } 194 | ] 195 | } 196 | ] 197 | }, 198 | "quoted-sexp": { 199 | "begin": "(['``]\\()", 200 | "beginCaptures": { 201 | "1": { 202 | "name": "punctuation.section.expression.begin.clojure" 203 | } 204 | }, 205 | "end": "(\\))$|(\\)(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\))", 206 | "endCaptures": { 207 | "1": { 208 | "name": "punctuation.section.expression.end.trailing.clojure" 209 | }, 210 | "2": { 211 | "name": "punctuation.section.expression.end.trailing.clojure" 212 | }, 213 | "3": { 214 | "name": "punctuation.section.expression.end.clojure" 215 | } 216 | }, 217 | "name": "meta.quoted-expression.clojure", 218 | "patterns": [ 219 | { 220 | "include": "$self" 221 | } 222 | ] 223 | }, 224 | "regexp": { 225 | "begin": "#\"", 226 | "beginCaptures": { 227 | "0": { 228 | "name": "punctuation.definition.regexp.begin.clojure" 229 | } 230 | }, 231 | "end": "\"", 232 | "endCaptures": { 233 | "0": { 234 | "name": "punctuation.definition.regexp.end.clojure" 235 | } 236 | }, 237 | "name": "string.regexp.clojure", 238 | "patterns": [ 239 | { 240 | "include": "#regexp_escaped_char" 241 | } 242 | ] 243 | }, 244 | "regexp_escaped_char": { 245 | "match": "\\\\.", 246 | "name": "constant.character.escape.clojure" 247 | }, 248 | "set": { 249 | "begin": "(\\#\\{)", 250 | "beginCaptures": { 251 | "1": { 252 | "name": "punctuation.section.set.begin.clojure" 253 | } 254 | }, 255 | "end": "(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})", 256 | "endCaptures": { 257 | "1": { 258 | "name": "punctuation.section.set.end.trailing.clojure" 259 | }, 260 | "2": { 261 | "name": "punctuation.section.set.end.clojure" 262 | } 263 | }, 264 | "name": "meta.set.clojure", 265 | "patterns": [ 266 | { 267 | "include": "$self" 268 | } 269 | ] 270 | }, 271 | "sexp": { 272 | "begin": "(\\()", 273 | "beginCaptures": { 274 | "1": { 275 | "name": "punctuation.section.expression.begin.clojure" 276 | } 277 | }, 278 | "end": "(\\))$|(\\)(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\))", 279 | "endCaptures": { 280 | "1": { 281 | "name": "punctuation.section.expression.end.trailing.clojure" 282 | }, 283 | "2": { 284 | "name": "punctuation.section.expression.end.trailing.clojure" 285 | }, 286 | "3": { 287 | "name": "punctuation.section.expression.end.clojure" 288 | } 289 | }, 290 | "name": "meta.expression.clojure", 291 | "patterns": [ 292 | { 293 | "begin": "(?<=\\()(ns|declare|def(?!ault)[\\w\\d._:+=>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)", 310 | "name": "entity.global.clojure" 311 | }, 312 | { 313 | "include": "$self" 314 | } 315 | ] 316 | }, 317 | { 318 | "include": "#keyfn" 319 | }, 320 | { 321 | "include": "#constants" 322 | }, 323 | { 324 | "include": "#vector" 325 | }, 326 | { 327 | "include": "#map" 328 | }, 329 | { 330 | "include": "#set" 331 | }, 332 | { 333 | "include": "#sexp" 334 | }, 335 | { 336 | "match": "(?<=\\()([^\"]+?)(?=\\s|\\))", 337 | "captures": { 338 | "1": { 339 | "name": "entity.name.function.clojure" 340 | } 341 | }, 342 | "patterns": [ 343 | { 344 | "include": "$self" 345 | } 346 | ] 347 | }, 348 | { 349 | "include": "$self" 350 | } 351 | ] 352 | }, 353 | "shebang-comment": { 354 | "begin": "^(#!)", 355 | "beginCaptures": { 356 | "1": { 357 | "name": "punctuation.definition.comment.shebang.clojure" 358 | } 359 | }, 360 | "end": "$", 361 | "name": "comment.line.shebang.clojure" 362 | }, 363 | "string": { 364 | "begin": "(?\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)/", 388 | "captures": { 389 | "1": { 390 | "name": "meta.symbol.namespace.clojure" 391 | } 392 | } 393 | } 394 | ] 395 | }, 396 | "symbol": { 397 | "patterns": [ 398 | { 399 | "match": "([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)", 400 | "name": "meta.symbol.clojure" 401 | } 402 | ] 403 | }, 404 | "var": { 405 | "match": "(?<=(\\s|\\(|\\[|\\{)\\#)'[\\w\\.\\-\\_\\:\\+\\=\\>\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}))", 406 | "name": "meta.var.clojure" 407 | }, 408 | "vector": { 409 | "begin": "(\\[)", 410 | "beginCaptures": { 411 | "1": { 412 | "name": "punctuation.section.vector.begin.clojure" 413 | } 414 | }, 415 | "end": "(\\](?=[\\}\\]\\)\\s]*(?:;|$)))|(\\])", 416 | "endCaptures": { 417 | "1": { 418 | "name": "punctuation.section.vector.end.trailing.clojure" 419 | }, 420 | "2": { 421 | "name": "punctuation.section.vector.end.clojure" 422 | } 423 | }, 424 | "name": "meta.vector.clojure", 425 | "patterns": [ 426 | { 427 | "include": "$self" 428 | } 429 | ] 430 | } 431 | } 432 | } -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "lib": [ 6 | "es6" 7 | ], 8 | "sourceMap": true 9 | }, 10 | "exclude": [ 11 | "node_modules", 12 | ".vscode-test" 13 | ] 14 | } -------------------------------------------------------------------------------- /next-README.md: -------------------------------------------------------------------------------- 1 | # calva-fmt 2 | 3 | **Calva Formatter** is now part of the [Calva](https://marketplace.visualstudio.com/items?itemName=cospaia.clojure4vscode) extension. If you are a Calva user you should not install this extension. 4 | 5 | Also: *This extension is not being maintained and you should uninstall it if you have it installed.* 6 | 7 | Chat us up in the [`#calva-dev` channel](https://clojurians.slack.com/messages/calva-dev/) of the Clojurians Slack if you have questions about what happened with Calva Formatter, and why. 8 | 9 | [![#calva-dev in Clojurians Slack](https://img.shields.io/badge/clojurians-calva--dev-blue.svg?logo=slack)](https://clojurians.slack.com/messages/calva-dev/) 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "calva-fmt", 3 | "displayName": "Calva Clojure Formatter", 4 | "description": "Clojure and ClojureScript formatter for Visual Studio Code", 5 | "version": "0.0.38", 6 | "publisher": "cospaia", 7 | "engines": { 8 | "vscode": "^1.20.0" 9 | }, 10 | "categories": [ 11 | "Programming Languages", 12 | "Formatters" 13 | ], 14 | "keywords": [ 15 | "Clojure", 16 | "ClojureScript", 17 | "EDN", 18 | "Formatting", 19 | "Pretty" 20 | ], 21 | "icon": "assets/calva-fmt.png", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/BetterThanTomorrow/calva-fmt.git" 25 | }, 26 | "galleryBanner": { 27 | "color": "#344D81", 28 | "theme": "dark" 29 | }, 30 | "badges": [ 31 | { 32 | "url": "https://img.shields.io/badge/clojurians-editors-blue.svg?logo=slack", 33 | "href": "https://clojurians.slack.com/messages/editors/", 34 | "description": "Also find us in the #editors channel" 35 | }, 36 | { 37 | "url": "https://img.shields.io/badge/clojurians-calva--dev-blue.svg?logo=slack", 38 | "href": "https://clojurians.slack.com/messages/calva-dev/", 39 | "description": "Let's talk in the #calva-dev channel on the Clojurians Slack" 40 | } 41 | ], 42 | "activationEvents": [ 43 | "onLanguage:clojure" 44 | ], 45 | "main": "./out/calva-fmt/extension", 46 | "contributes": { 47 | "grammars": [ 48 | { 49 | "language": "clojure", 50 | "scopeName": "source.clojure", 51 | "path": "./clojure.tmLanguage.json" 52 | } 53 | ], 54 | "commands": [ 55 | { 56 | "command": "calva-fmt.formatCurrentForm", 57 | "title": "Format Current Form", 58 | "category": "Calva Format" 59 | }, 60 | { 61 | "command": "calva-fmt.alignCurrentForm", 62 | "title": "Format and Align Current Form (recursively, experimental)", 63 | "category": "Calva Format" 64 | }, 65 | { 66 | "command": "calva-fmt.inferParens", 67 | "title": "Infer Parens (from the indentation)", 68 | "category": "Calva Format" 69 | }, 70 | { 71 | "command": "calva-fmt.tabIndent", 72 | "title": "Indent Line", 73 | "category": "Calva Format" 74 | }, 75 | { 76 | "command": "calva-fmt.tabDedent", 77 | "title": "Dedent Line", 78 | "category": "Calva Format" 79 | } 80 | ], 81 | "keybindings": [ 82 | { 83 | "command": "calva-fmt.formatCurrentForm", 84 | "key": "tab", 85 | "when": "editorLangId == clojure && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" 86 | }, 87 | { 88 | "command": "calva-fmt.alignCurrentForm", 89 | "key": "ctrl+alt+l", 90 | "when": "editorLangId == clojure && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" 91 | }, 92 | { 93 | "command": "calva-fmt.inferParens", 94 | "key": "ctrl+alt+p", 95 | "when": "editorLangId == clojure && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" 96 | }, 97 | { 98 | "command": "calva-fmt.tabIndent", 99 | "key": "ctrl+i", 100 | "when": "editorLangId == clojure && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" 101 | }, 102 | { 103 | "command": "calva-fmt.tabDedent", 104 | "key": "shift+ctrl+i", 105 | "when": "editorLangId == clojure && editorTextFocus && !editorReadOnly && !suggestWidgetVisible && !hasOtherSuggestions" 106 | } 107 | ], 108 | "configuration": [ 109 | { 110 | "title": "calva-fmt", 111 | "type": "object", 112 | "properties": { 113 | "calva.fmt.indentation": { 114 | "type": "boolean", 115 | "default": true, 116 | "description": "Correct the indentation of your code?" 117 | }, 118 | "calva.fmt.removeSurroundingWhitespace": { 119 | "type": "boolean", 120 | "default": true, 121 | "description": "Remove whitespace surrounding inner forms? This will convert ( foo ) to (foo)." 122 | }, 123 | "calva.fmt.removeTrailingWhitespace": { 124 | "type": "boolean", 125 | "default": true, 126 | "description": "Remove trailing whitespace in lines? This will convert (foo) \\n to (foo)\\n." 127 | }, 128 | "calva.fmt.insertMissingWhitespace": { 129 | "type": "boolean", 130 | "default": true, 131 | "description": "Insert whitespace missing from between elements? This will convert (foo(bar)) to (foo (bar))." 132 | }, 133 | "calva.fmt.removeConsecutiveBlankLines": { 134 | "type": "boolean", 135 | "default": false, 136 | "description": "Squeeze consecutive blank lines to one?" 137 | }, 138 | "calva.fmt.formatAsYouType": { 139 | "type": "boolean", 140 | "default": true, 141 | "description": "Auto-adjust indentation and format as you type (only on enter, currently)?" 142 | }, 143 | "calva.fmt.newIndentEngine": { 144 | "type": "boolean", 145 | "default": false, 146 | "description": "Help beta test the new indent engine (it is MUCH faster than the previous one)." 147 | }, 148 | "calva.fmt.allgnMapItems": { 149 | "type": "boolean", 150 | "default": false, 151 | "description": "Align map items in columns? (Experimental)" 152 | } 153 | } 154 | } 155 | ], 156 | "configurationDefaults": { 157 | "[clojure]": { 158 | "editor.wordSeparators": "\t ()\"':,;~@#$%^&{}[]`", 159 | "editor.autoClosingBrackets": "always", 160 | "editor.autoClosingQuotes": "always", 161 | "editor.formatOnType": true, 162 | "editor.autoIndent": true, 163 | "editor.formatOnPaste": true, 164 | "files.trimTrailingWhitespace": false 165 | } 166 | } 167 | }, 168 | "scripts": { 169 | "watch-ts": "tsc -watch -p ./", 170 | "release": "tsc -p ./", 171 | "compile": "tsc -p ./", 172 | "vscode:prepublish": "npm run release", 173 | "postinstall": "node ./node_modules/vscode/bin/install", 174 | "test": "node ./node_modules/vscode/bin/test", 175 | "update-grammar": "node ./update-grammar.js ./atom-language-clojure/grammars/clojure.cson ./clojure.tmLanguage.json" 176 | }, 177 | "devDependencies": { 178 | "@types/mocha": "^5.2.5", 179 | "@types/node": "^10.12.21", 180 | "ajv": "^6.8.1", 181 | "eslint": "^5.13.0", 182 | "mocha": "^5.2.0", 183 | "typescript": "^3.3.1", 184 | "vscode": "^1.1.21", 185 | "cson-parser": "^4.0.1", 186 | "event-stream": "^4.0.1" 187 | }, 188 | "dependencies": { 189 | "@cospaia/calva-lib": "0.0.21", 190 | "immutable": "^4.0.0-rc.12", 191 | "immutable-cursor": "^2.0.1", 192 | "npm": "^6.7.0" 193 | }, 194 | "extensionDependencies": [ 195 | "cospaia.clojure4vscode" 196 | ], 197 | "license": "MIT" 198 | } 199 | -------------------------------------------------------------------------------- /test_js/extension.test.js: -------------------------------------------------------------------------------- 1 | /* global suite, test */ 2 | 3 | // 4 | // Note: This example test is leveraging the Mocha test framework. 5 | // Please refer to their documentation on https://mochajs.org/ for help. 6 | // 7 | 8 | // The module 'assert' provides assertion methods from node 9 | const assert = require('assert'); 10 | 11 | // You can import and use all API from the 'vscode' module 12 | // as well as import your extension to test it 13 | // const vscode = require('vscode'); 14 | // const myExtension = require('../extension'); 15 | 16 | // Defines a Mocha test suite to group tests of similar kind together 17 | suite("Extension Tests", function() { 18 | 19 | // Defines a Mocha unit test 20 | test("Something 1", function() { 21 | assert.equal(-1, [1, 2, 3].indexOf(5)); 22 | assert.equal(-1, [1, 2, 3].indexOf(0)); 23 | }); 24 | }); -------------------------------------------------------------------------------- /test_js/index.js: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | const testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.js (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es7" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ] 16 | } -------------------------------------------------------------------------------- /update-grammar.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /** 7 | * MIT License 8 | * 9 | * Copyright (c) 2015 - present Microsoft Corporation 10 | * 11 | * All rights reserved. 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in all 21 | * copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | * 31 | */ 32 | 33 | 'use strict'; 34 | 35 | var path = require('path'); 36 | var fs = require('fs'); 37 | var cson = require('cson-parser'); 38 | 39 | exports.update = function (contentPath, dest) { 40 | console.log('Reading from ' + contentPath); 41 | fs.readFile(contentPath, (_err, content) => { 42 | var grammar = cson.parse(content); 43 | let result = { 44 | information_for_contributors: [ 45 | 'This file is generated from ' + contentPath 46 | ] 47 | }; 48 | 49 | let keys = ['name', 'scopeName', 'comment', 'injections', 'patterns', 'repository']; 50 | for (let key of keys) { 51 | if (grammar.hasOwnProperty(key)) { 52 | result[key] = grammar[key]; 53 | } 54 | } 55 | 56 | try { 57 | fs.writeFileSync(dest, JSON.stringify(result, null, '\t').replace(/\n/g, '\r\n')); 58 | console.log('Updated ' + path.basename(dest)); 59 | } catch (e) { 60 | console.error(e); 61 | process.exit(1); 62 | } 63 | }); 64 | }; 65 | 66 | if (path.basename(process.argv[1]) === 'update-grammar.js') { 67 | exports.update(process.argv[2], process.argv[3]); 68 | } 69 | --------------------------------------------------------------------------------