├── .python-version ├── CHANGELOG.md ├── Clojure (Sublimed).sublime-syntax ├── Clojure Sublimed Dark.sublime-color-scheme ├── Clojure Sublimed Light.sublime-color-scheme ├── Clojure Sublimed.sublime-settings ├── ClojureSymbols.tmPreferences ├── Comment.tmPreferences ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── Default.sublime-commands ├── LICENSE.txt ├── Main.sublime-menu ├── README.md ├── cljfmt.edn ├── cs_bencode.py ├── cs_cljfmt.py ├── cs_colors.py ├── cs_comment.py ├── cs_common.py ├── cs_conn.py ├── cs_conn_nrepl_jvm.py ├── cs_conn_nrepl_raw.py ├── cs_conn_shadow_cljs.py ├── cs_conn_socket_repl.py ├── cs_eval.py ├── cs_eval_status.py ├── cs_indent.py ├── cs_parser.py ├── cs_printer.py ├── cs_progress.py ├── cs_warn.py ├── cs_watch.py ├── docs └── protocol_socket.md ├── messages.json ├── messages ├── 2.4.0.txt ├── 2.8.0.txt ├── 2.9.0.txt ├── 3.0.0.txt ├── 3.4.0.txt ├── 3.6.0.txt ├── 3.7.0.txt ├── 4.0.0.txt └── install.txt ├── src_clojure └── clojure_sublimed │ ├── core.clj │ ├── middleware.clj │ └── socket_repl.clj ├── test_comment ├── comment.txt └── comment_reversible.txt ├── test_indent └── indent.txt └── test_scheme ├── color_scheme.clj └── demo.clj /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 4.4.3 - May 9, 2025 2 | 3 | - Fixed `Align Cursors` not appearing in Command Palette #136 4 | 5 | ### 4.4.2 - Apr 28, 2025 6 | 7 | - Get settings from view, which merges in project and syntax settings on top of `Preferences.sublime-settings` 8 | 9 | ### 4.4.1 - Apr 5, 2025 10 | 11 | - Autoscroll on Enter #135 12 | 13 | ### 4.4.0 - Dec 30, 2024 14 | 15 | - New `Align Cursors` command 16 | - Color scheme adjustments 17 | 18 | ### 4.3.2 - Dec 9, 2024 19 | 20 | - Clarified some symbol/keyword edge cases in syntax 21 | - Add `clojure_sublimed_select_topmost_form` command 22 | - Add `|` to the allowed symbols chars #132 23 | 24 | ### 4.3.1 - Nov 4, 2024 25 | 26 | - Fixed evaluation of `()` #131 27 | 28 | ### 4.3.0 - Oct 31, 2024 29 | 30 | - Pretty print selection #123 31 | - Execute code from inside top-level `; ...` and `#_...` #124 32 | - `Toggle Comment` command that uses `#_` instead of `;;` 33 | - Remove background color on quoted strings inside metadata 34 | - Better handle eval of `#_` forms in nREPL JVM 35 | - Made line numbers much more transparent 36 | 37 | ### 4.2.2 - Sep 27, 2024 38 | 39 | - cljfmt correctly indents forms with custom rules - again 40 | 41 | ### 4.2.1 - Sep 27, 2024 42 | 43 | - cljfmt correctly indents forms with custom rules 44 | 45 | ### 4.2.0 - Sep 27, 2024 46 | 47 | - Simplified formatting rules: if list's first form is a symbol, indent next line by +2 spaces, in all other cases, indent to opening paren (1 space) 48 | - We now provide `cljfmt.edn` that tries to match our default formatting 49 | - Better handle selection after formatting with cljfmt 50 | - Highlight namespace name as `entity.name`, same as defs 51 | - No exceptions on disconnect 52 | - Removed background on unused symbols inside quotes 53 | 54 | ### 4.1.1 - Sep 6, 2024 55 | 56 | - Support Clojure 1.12 array type annotations 57 | 58 | ### 4.1.0 - Aug 30, 2024 59 | 60 | - Eval previous form at current level #118 61 | - Auto-detect UNIX sockets, support relative paths 62 | - Correctly parse escaped comma #120 via @oakmac 63 | 64 | ### 4.0.0 - Aug 23, 2024 65 | 66 | Syntax has been significantly reworked. 67 | 68 | - New syntax that can highlight reader comments `comment.reader` together with the following form 69 | - Highlight `(comment ...)` blocks as `comment.form` 70 | - Highlight namespaces in symbols as `meta.namespace.symbol` 71 | - Highlight unused symbols as `source.symbol.unused` 72 | - Properly highlight `entity.name` in `def*` forms only at second position, skipping all meta/comments 73 | - Quote & syntax quote highlight following form as `meta.quoted` and `meta.quoted.syntax` 74 | - Metadata highlights following form as `meta.metadata` 75 | - Octal & arbitrary radix integers #71 76 | - Better keyword detection 77 | 78 | Other changes: 79 | 80 | - Built-in color scheme to utilize REPL and new syntax features. 81 | - Allow using `cljfmt` for formatting (requires `cljfmt` binary on `$PATH`) 82 | - Removed separate EDN syntax, merged with main Clojure (Sublimed) 83 | - Settings can now be specified in main `Preferences.sublime-settings` as well. Just prepend `clojure_sublimed_` to each setting’s name. 84 | - REPL can detect namespaces with meta on ns form #116 85 | - Detect `.shadow-cljs/nrepl.port` and `.shadow-cljs/socket-repl.port` #114 86 | - Connect commands now accept `timeout` argument for automation scenarios like “start clojure, start trying to connect to REPL until port is available” 87 | 88 | ### 3.8.0 - Aug 8, 2024 89 | 90 | - `clojure_sublimed_reindent` command that reindents entire buffer if selection is empty and only selected lines if not 91 | 92 | ### 3.7.3 - June 16, 2024 93 | 94 | - Fixed Socket REPL not working on Windows #95 95 | - Fixed Exception in settings on first install #109 96 | 97 | ### 3.7.2 - May 5, 2024 98 | 99 | - Some defensive coding around default settings fallback #109 100 | 101 | ### 3.7.1 - Mar 15, 2024 102 | 103 | - Added `expand` argument to `clojure_sublimed_eval` command 104 | 105 | ### 3.7.0 - Mar 14, 2024 106 | 107 | - New feature: Watches! Added `Add Watch` command 108 | - Added `output.repl` panel for raw nREPL output #104 109 | - Added `Toggle Output Panel` command for raw nREPL connections #104 110 | - Fixed `Reconnect` command 111 | - Added optional `on_finish` argument to `cs_conn.eval` 112 | - Added `print_quota` as a setting and as an argument to `cs_conn.eval` 113 | 114 | ### 3.6.0 - Mar 5, 2024 115 | 116 | - Added optional `transform` argument to `clojure_sublimed_eval` #101 #102 117 | - Display failed test reports as red 118 | - Socket REPL: fixed escaping in `clojure_sublimed_eval_code` #103 via @KGOH 119 | 120 | ### 3.5.0 - Jan 22, 2023 121 | 122 | - Detect namespace from in-ns forms 123 | 124 | ### 3.4.1 - Dec 7, 2023 125 | 126 | - Fixed status eval not clearing on disconnect 127 | 128 | ### 3.4.0 - Nov 30, 2023 129 | 130 | - Support multiple windows, one connection per widnow 131 | - Support .repl-port files for Socket REPL 132 | 133 | ### 3.3.0 - Oct 26, 2023 134 | 135 | - Eval inside already evaled region re-evals same region instead of going to top form 136 | - Printer can display newlines 137 | 138 | ### 3.2.1 - Sep 10, 2023 139 | 140 | - Socket: Report number of reflection warnings in status bar 141 | 142 | ### 3.2.0 - Sep 10, 2023 143 | 144 | - Socket REPL: handle exceptions in lookup 145 | - Do not silence exception during lazy seq printing 146 | 147 | ### 3.1.3 - Aug 19, 2023 148 | 149 | - Show file/line/column information when `clojure_sublimed_eval_code` fails 150 | 151 | ### 3.1.2 - June 1, 2023 152 | 153 | - Fixed indenting of reader conditionals 154 | 155 | ### 3.1.1 - Apr 3, 2023 156 | 157 | - Fixed perf degradation on reindent #96 158 | 159 | ### 3.1.0 - Mar 13, 2023 160 | 161 | - Socket: Fixed status_eval, bind *1/*2/*3/*e for e.g. tools.namespace 162 | - Do not fail because of styles #91 163 | 164 | ### 3.0.0 - Mar 9, 2023 165 | 166 | - Huge refactoring, easier to add new REPLs 167 | - REPLs do not depend on syntax highlighting anymore, will work with any syntax 168 | - new REPL: Raw nREPL 169 | - new REPL: Socket REPL (no external dependencies, faster to start than nREPL) 170 | - Report results of different forms in the same selection separately (Socket REPL only) 171 | - Removed `clojure_sublimed_require_namespace` command 172 | - Implemented pretty-printing of expanded results in Python, removing clojure.pprint dependency 173 | - Added `wrap_width` setting 174 | 175 | ### 2.11.0 - Jan 5, 2023 176 | 177 | - Connect command accepts 'auto' (will look for `.nrepl-port` file) #82 178 | 179 | ### 2.10.0 - Jan 4, 2023 180 | 181 | - Do not move cursor if error region visible on screen #73 182 | - Fixed: Wrong highlight of the source of error #79 183 | - Render sequential spaces #78 184 | - Fixed: Exceptions from evaluating buffer without a file name are not processed #18 185 | - Special case when CompilerError has wrong location info 186 | - Select whole line on error 187 | 188 | ### 2.9.1 - Dec 3, 2022 189 | 190 | - Added Main.sublime-menu #85 via @wundervaflja 191 | 192 | ### 2.9.0 - Nov 21, 2022 193 | 194 | - Allow connection through UNIX domain socket #80 via @tribals 195 | 196 | ### 2.8.1 - Nov 2, 2022 197 | 198 | - Allow specifying `ns` in `clojure-sublimed-eval-code` 199 | 200 | ### 2.8.0 - Oct 17, 2022 201 | 202 | - Shadow-cljs support #43 #77 via @sainadh-d 203 | 204 | ### 2.7.0 - Sep 27, 2022 205 | 206 | - Added `eval_shared` 207 | 208 | ### 2.6.0 - Sep 27, 2022 209 | 210 | - Added `format_on_save` option #76 thx @sainadh-d 211 | 212 | ### 2.5.2 - May 24, 2022 213 | 214 | - Fixed clojure_sublimed_eval_code #75 215 | - Fixed clojure_sublimed_insert_newline with multicursor #72 216 | 217 | ### 2.5.1 - January 19, 2022 218 | 219 | - Fixed indent on Enter 220 | 221 | ### 2.5.0 - January 18, 2022 222 | 223 | - Pretty print returned values. Toggle with Toggle Info (Ctrl + I) 224 | - Do not reindent blank lines 225 | 226 | ### 2.4.1 - January 13, 2022 227 | 228 | - Proper `messages.json` and install message. 229 | 230 | ### 2.4.0 - January 12, 2022 231 | 232 | New commands: 233 | 234 | - Reindent Buffer 235 | - Reindent Lines 236 | - Insert Newline 237 | 238 | ### 2.3.0 - January 4, 2022 239 | 240 | - A command to require namespace of symbol #12 #59 via @jaihindhreddy 241 | - Fixed regions returning on undo #22 #60 via @jaihindhreddy 242 | - Fixed AttributeError: 'Connection' object has no attribute 'eval_in_session' 243 | 244 | ### 2.2.0 - December 29, 2021 245 | 246 | - Do clone, eval and close in a single middleware #20 via @jaihindhreddy and @tonsky 247 | - Auto-close sessions cloned by eval #48 #49 #50 via @tonsky 248 | - Interrupt pending eval when region is erased #16 #58 via @jaihindhreddy and @tonsky 249 | 250 | ### 2.1.0 - December 28, 2021 251 | 252 | - An option to use nREPL session for eval #9 #57 via @jaihindhreddy 253 | - Optimize region invalidation #19 #54 via @jaihindhreddy 254 | - Optimise iterating through evals by maintaining evals by view #23 #51 #55 #56 via @jaihindhreddy 255 | - Use ephemeral sessions instead of cloning for each eval #20 #48 #50 via @jaihindhreddy 256 | 257 | ### 2.0.0 - December 22, 2021 258 | 259 | - Renamed to Clojure Sublimed due to Package Control policy. Thanks @YurySolovyov for the name 260 | 261 | ### 1.0.7 - December 15, 2021 262 | 263 | - Toggle symbol info works on def/defn #44 #45 264 | 265 | ### 1.0.6 - December 14, 2021 266 | 267 | - Escape HTML in evaluation results #35 #38 thx @jaihindhreddy 268 | - Measure time-taken in nREPL middleware #13 #39 thx @jaihindhreddy 269 | - Eval form on the left if between forms #10 #42 thx @jaihindhreddy 270 | - Fixed Ctrl+I at the last position in the file #17 271 | - Copy evaluation result #37 272 | 273 | ### 1.0.5 - November 10, 2021 274 | 275 | - Fixed runtime error on startup #32 276 | 277 | ### 1.0.4 - November 10, 2021 278 | 279 | - Clear Eval Code status in non-active views #32 280 | - When evaluating buffer fails, scroll to error line #28 281 | 282 | ### 1.0.3 - November 9, 2021 283 | 284 | - Fixed exception in eval code command handling excetpion response 285 | 286 | ### 1.0.2 - November 4, 2021 287 | 288 | - Automatically detect port from .nrepl-port #5 289 | 290 | ### 1.0.1 - November 1, 2021 291 | 292 | - Do not bundle any key bindings by default 293 | - Bind keys to eval arbitrary code #25 294 | - Remove phantom if region is fully removed #21 295 | 296 | ### 1.0.0 - October 25, 2021 297 | 298 | - Initial release 299 | 300 | ### October 22, 2021 301 | 302 | - Renamed syntaxes to `Clojure (Sublime Clojure)` and `EDN (Sublime Clojure)` 303 | - Added nREPL client 304 | 305 | ### Jan 3, 2019 306 | 307 | - Clojure syntax: quotes, reader conditionals, operators, better whitespace handling. 308 | 309 | ### Jan 1, 2019 310 | 311 | - Clojure syntax: regexps. 312 | 313 | ### Dec 30, 2018 314 | 315 | - Supported namespaced `/` symbol. 316 | - Tokens can end directly with `;`, without any whitespace in between. 317 | - Content of `()[]{}` is marked with `meta.parens`/`.brackets`/`.braces`, making possible nested brackets highlighting. 318 | - Bracket/paren/braces classes replaced with `punctuation.section.(parens|brackets|braces).begin`/`...end`. 319 | - Keywords use `constant.other.keyword` instead of `constant.keyword`. 320 | - Beginning of Clojure syntax, highlighting `entity.name` in all `def*`/`ns` forms. 321 | 322 | ### Dec 24, 2018 323 | 324 | - Instants, uuids, custom reader tags; 325 | - punctuation class for colons and slashes in symbols/keywords, comma, backslash in strings/chars; 326 | - allow single quote in keywords; 327 | - more [conventional](https://macromates.com/manual/en/language_grammars) class names; 328 | - automated tests. 329 | 330 | ### Dec 23, 2018 331 | 332 | - Inital version. 333 | -------------------------------------------------------------------------------- /Clojure Sublimed Dark.sublime-color-scheme: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Clojure Sublimed Dark", 3 | "author": "Nikita Prokopov", 4 | "variables": 5 | { 6 | "active": "#00BFFF", 7 | "fg": "#CECECE", 8 | "bg": "#0E1415", 9 | "blue": "#71ADE7", 10 | "green": "#95CB82", 11 | "green-bg": "#203028", 12 | "red": "#FF6060", 13 | "red-bg": "#2B1D1E", 14 | "magenta": "#CC8BC9", 15 | "yellow": "#FFF080", 16 | // "orange": "#FFBC5D", 17 | "gray": "#606060", 18 | }, 19 | "globals": 20 | { 21 | "foreground": "var(fg)", 22 | "background": "var(bg)", 23 | "caret": "var(active)", 24 | "line_highlight": "#ffffff10", 25 | "misspelling": "#ff0000", 26 | "selection": "#293334", 27 | "inactive_selection": "#ffffff10", 28 | "selection_border_width": "0", 29 | "selection_corner_radius": "2", 30 | "highlight": "var(active)", 31 | "find_highlight_foreground": "#000", 32 | "find_highlight": "var(active)", 33 | "brackets_options": "underline", 34 | "brackets_foreground": "var(active)", 35 | "bracket_contents_options": "underline", 36 | "bracket_contents_foreground": "var(active)", 37 | "tags_options": "underline", 38 | "tags_foreground": "var(active)", 39 | "gutter": "#121819", 40 | "gutter_foreground": "#282828", 41 | "gutter_foreground_active": "#282828", 42 | }, 43 | "rules": 44 | [ 45 | {"name": "Strings", 46 | "scope": "string - meta.metadata, meta.quoted string - meta.metadata - comment", 47 | "foreground": "var(green)"}, 48 | 49 | {"name": "Escapes", 50 | "scope": "constant.character.escape - meta.metadata, constant.other.placeholder - meta.metadata", 51 | "background": "var(green-bg)"}, 52 | 53 | {"name": "Constants", 54 | "scope": "constant - constant.character.escape, punctuation.definition.constant, support.type", 55 | "foreground": "var(magenta)"}, 56 | 57 | {"name": "Definitions", 58 | "scope": "entity.name - entity.name.tag - meta.metadata", 59 | "foreground": "var(blue)"}, 60 | 61 | {"name": "Symbol namespaces", 62 | "scope": "meta.namespace.symbol, source.symbol punctuation.definition.namespace", 63 | "foreground": "var(gray)"}, 64 | 65 | {"name": "Unused symbol", 66 | "scope": "source.symbol.unused", 67 | "foreground": "var(gray)"}, 68 | 69 | {"name": "Punctuation", 70 | "scope": "punctuation - punctuation.section - punctuation.definition", 71 | "foreground": "var(gray)"}, 72 | 73 | {"name": "Comma", 74 | "scope": "punctuation.definition.comma", 75 | "foreground": "var(gray)"}, 76 | 77 | {"name": "Line Comments", 78 | "scope": "comment - comment.reader - comment.form, invalid comment, meta.quoted comment.line, meta.quoted comment.line punctuation.definition.comment, meta.metadata comment.line punctuation.definition.comment", 79 | "foreground": "var(yellow)" 80 | }, 81 | 82 | {"name": "Reader comments", 83 | "scope": "comment.reader, comment.reader keyword.operator, comment.reader string, comment.reader constant, comment.reader punctuation.definition.constant, comment.reader punctuation, comment.reader constant.character.escape, comment.reader invalid.illegal.escape, comment.reader string invalid, comment.reader string invalid punctuation, comment.reader entity.name, comment.reader meta.quoted", 84 | "foreground": "var(gray)", 85 | "background": "var(bg)"}, 86 | 87 | {"name": "Form comments", 88 | "scope": "comment.form, comment.form keyword.operator, comment.form string, comment.form constant, comment.form punctuation.definition.constant, comment.form punctuation, comment.form constant.character.escape, comment.form invalid.illegal.escape, comment.form string invalid, comment.form string invalid punctuation, comment.form entity.name, comment.form meta.quoted", 89 | "foreground": "var(gray)", 90 | "background": "var(bg)"}, 91 | 92 | {"name": "Metadata", 93 | "scope": "meta.metadata, meta.metadata keyword.operator, meta.metadata string, meta.metadata constant, meta.metadata punctuation.definition.constant, meta.metadata punctuation", 94 | "foreground": "var(gray)"}, 95 | 96 | {"scope": "meta.metadata meta.quoted", 97 | "background": "var(bg)"}, 98 | 99 | {"name": "Quoted", 100 | "scope": "meta.quoted - meta.quoted meta.unquoted, meta.quoted meta.unquoted meta.quoted - meta.quoted meta.unquoted meta.quoted meta.unquoted, meta.quoted meta.unquoted meta.quoted meta.unquoted meta.quoted - meta.quoted meta.unquoted meta.quoted meta.unquoted meta.quoted meta.unquoted, meta.quoted comment.reader, meta.quoted punctuation.definition.comment, meta.quoted comment.form, meta.quoted comment.form punctuation", 101 | "background": "#FFFFFF10"}, 102 | 103 | {"name": "Inner brackets", 104 | "scope": "meta.parens meta.parens punctuation.section, meta.parens meta.brackets punctuation.section, meta.parens meta.braces punctuation.section, meta.brackets meta.parens punctuation.section, meta.brackets meta.brackets punctuation.section, meta.brackets meta.braces punctuation.section, meta.braces meta.parens punctuation.section, meta.braces meta.brackets punctuation.section, meta.braces meta.braces punctuation.section", 105 | "foreground": "var(gray)"}, 106 | 107 | {"name": "Mistakes", 108 | "scope": "invalid, invalid string, invalid constant, invalid entity.name, invalid punctuation, invalid source.symbol", 109 | "foreground": "var(red)", 110 | "background": "var(red-bg)"}, 111 | 112 | // MARKUP 113 | 114 | {"scope": "markup.inserted", 115 | "foreground": "var(green)"}, 116 | 117 | {"scope": "markup.deleted", 118 | "foreground": "hsl(2, 65%, 50%)"}, 119 | 120 | {"scope": "markup.changed", 121 | "foreground": "hsl(30, 85%, 50%)"}, 122 | 123 | {"scope": "markup.ignored", 124 | "foreground": "#aaa"}, 125 | 126 | {"scope": "markup.untracked", 127 | "foreground": "#aaa"}, 128 | 129 | // REGIONS 130 | 131 | {"scope": "region.eval.success", 132 | "foreground": "var(green)"}, 133 | 134 | {"scope": "region.eval.exception", 135 | "foreground": "var(red)"}, 136 | 137 | {"scope": "region.eval.pending", 138 | "foreground": "var(gray)"}, 139 | 140 | {"scope": "region.watch", 141 | "foreground": "var(magenta)"}, 142 | 143 | {"scope": "region.redish", 144 | "background": "#F04F5080"}, 145 | 146 | {"scope": "region.orangish", 147 | "background": "#FF935680"}, 148 | 149 | {"scope": "region.yellowish", 150 | "background": "#FFBC5D80"}, 151 | 152 | {"scope": "region.greenish", 153 | "background": "#60CB0080"}, 154 | 155 | {"scope": "region.cyanish", 156 | "background": "#00AACB80"}, 157 | 158 | {"scope": "region.bluish", 159 | "background": "#017ACC80"}, 160 | 161 | {"scope": "region.purplish", 162 | "background": "#C171FF80"}, 163 | 164 | {"scope": "region.pinkish", 165 | "background": "#E64CE680"}, 166 | 167 | {"scope": "region.greyish", 168 | "background": "#FFFFFF10"}, 169 | 170 | // {"scope": "region.eval.lookup", 171 | // "foreground": "hsl(208, 100%, 50%)"}, 172 | ] 173 | } 174 | -------------------------------------------------------------------------------- /Clojure Sublimed Light.sublime-color-scheme: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Clojure Sublimed Light", 3 | "author": "Nikita Prokopov", 4 | "variables": 5 | { 6 | "active": "#43bef4", 7 | "fg": "#000", 8 | "bg": "#fff", 9 | "gray": "#A0A0A0", 10 | }, 11 | "globals": 12 | { 13 | "foreground": "var(fg)", 14 | "background": "var(bg)", 15 | "caret": "var(active)", 16 | "line_highlight": "#00000008", 17 | "misspelling": "#f00", 18 | "selection": "#B4D8FD", 19 | "inactive_selection": "#E0E0E0", 20 | "selection_border_width": "0", 21 | "selection_corner_radius": "2", 22 | "highlight": "#FFBC5D", 23 | "find_highlight": "#FFBC5D", 24 | "find_highlight_foreground": "#000", 25 | "brackets_options": "underline", 26 | "brackets_foreground": "var(active)", 27 | "bracket_contents_options": "underline", 28 | "bracket_contents_foreground": "var(active)", 29 | "tags_options": "underline", 30 | "tags_foreground": "var(active)", 31 | // "gutter": "#E6E6E6", 32 | "gutter_foreground": "#CCC", 33 | "gutter_foreground_highlight": "#CCC", 34 | }, 35 | "rules": 36 | [ {"name": "Strings", 37 | "scope": "string - meta.metadata, meta.quoted string - meta.metadata - comment", 38 | "background": "#eefbd9", 39 | "foreground": "#3c5c00"}, 40 | 41 | {"name": "Escapes", 42 | "scope": "constant.character.escape - meta.metadata, constant.other.placeholder - meta.metadata", 43 | "background": "#DBECB6"}, 44 | 45 | {"name": "Constants", 46 | "scope": "constant - constant.character.escape, punctuation.definition.constant, support.type", 47 | "foreground": "#8a3eb5"}, 48 | 49 | {"name": "Definitions", 50 | "scope": "entity.name - entity.name.tag - meta.metadata", 51 | "background": "#DBF1FF", 52 | "foreground": "#195b7c"}, 53 | 54 | {"name": "Symbol namespaces", 55 | "scope": "meta.namespace.symbol, source.symbol punctuation.definition.namespace", 56 | "foreground": "var(gray)"}, 57 | 58 | {"name": "Unused symbol", 59 | "scope": "source.symbol.unused", 60 | "foreground": "var(gray)"}, 61 | 62 | {"name": "Punctuation", 63 | "scope": "punctuation - punctuation.section - punctuation.definition", 64 | "foreground": "var(gray)"}, 65 | 66 | {"name": "Comma", 67 | "scope": "punctuation.definition.comma", 68 | "foreground": "var(gray)"}, 69 | 70 | {"name": "Line Comments", 71 | "scope": "comment - comment.reader - comment.form, invalid comment, meta.quoted comment.line, meta.quoted comment.line punctuation.definition.comment, meta.metadata comment.line punctuation.definition.comment", 72 | "foreground": "#6d6607", 73 | "background": "#FFFABC"}, 74 | 75 | {"name": "Reader comments", 76 | "scope": "comment.reader, comment.reader keyword.operator, comment.reader string, comment.reader constant, comment.reader punctuation.definition.constant, comment.reader punctuation, comment.reader constant.character.escape, comment.reader invalid.illegal.escape, comment.reader string invalid, comment.reader string invalid punctuation, comment.reader entity.name, comment.reader meta.quoted", 77 | "foreground": "var(gray)", 78 | "background": "var(bg)"}, 79 | 80 | {"name": "Form comments", 81 | "scope": "comment.form, comment.form keyword.operator, comment.form string, comment.form constant, comment.form punctuation.definition.constant, comment.form punctuation, comment.form constant.character.escape, comment.form invalid.illegal.escape, comment.form string invalid, comment.form string invalid punctuation, comment.form entity.name, comment.form meta.quoted", 82 | "foreground": "var(gray)", 83 | "background": "var(bg)"}, 84 | 85 | {"name": "Metadata", 86 | "scope": "meta.metadata, meta.metadata keyword.operator, meta.metadata string, meta.metadata constant, meta.metadata punctuation.definition.constant, meta.metadata punctuation", 87 | "foreground": "var(gray)"}, 88 | 89 | {"scope": "meta.metadata meta.quoted", 90 | "background": "var(bg)"}, 91 | 92 | {"name": "Quoted", 93 | "scope": "meta.quoted - meta.quoted meta.unquoted, meta.quoted meta.unquoted meta.quoted - meta.quoted meta.unquoted meta.quoted meta.unquoted, meta.quoted meta.unquoted meta.quoted meta.unquoted meta.quoted - meta.quoted meta.unquoted meta.quoted meta.unquoted meta.quoted meta.unquoted, meta.quoted comment.reader, meta.quoted punctuation.definition.comment, meta.quoted comment.form, meta.quoted comment.form punctuation", 94 | "background": "#00000010"}, 95 | 96 | {"name": "JSX", 97 | "scope": "meta.jsx - meta.jsx source.js.embedded, meta.jsx source.js.embedded meta.jsx - meta.jsx source.js.embedded meta.jsx source.js.embedded, meta.jsx source.js.embedded meta.jsx source.js.embedded meta.jsx - meta.jsx source.js.embedded meta.jsx source.js.embedded meta.jsx source.js.embedded", 98 | "background": "#00000010"}, 99 | 100 | {"name": "Inner brackets", 101 | "scope": "meta.parens meta.parens punctuation.section, meta.parens meta.brackets punctuation.section, meta.parens meta.braces punctuation.section, meta.brackets meta.parens punctuation.section, meta.brackets meta.brackets punctuation.section, meta.brackets meta.braces punctuation.section, meta.braces meta.parens punctuation.section, meta.braces meta.brackets punctuation.section, meta.braces meta.braces punctuation.section", 102 | "foreground": "var(gray)"}, 103 | 104 | // {"name": "Parens level 0", 105 | // "scope": "meta.parens punctuation.section", 106 | // "foreground": "hsl(0, 50%, 50%)"}, 107 | 108 | // {"name": "Parens level 1", 109 | // "scope": "meta.parens meta.parens punctuation.section", 110 | // "foreground": "hsl(60, 50%, 50%)"}, 111 | 112 | // {"name": "Parens level 2", 113 | // "scope": "meta.parens meta.parens meta.parens punctuation.section", 114 | // "foreground": "hsl(120, 50%, 50%)"}, 115 | 116 | // {"name": "Parens level 3", 117 | // "scope": "meta.parens meta.parens meta.parens meta.parens punctuation.section", 118 | // "foreground": "hsl(180, 50%, 50%)"}, 119 | 120 | // {"name": "Parens level 4", 121 | // "scope": "meta.parens meta.parens meta.parens meta.parens meta.parens punctuation.section", 122 | // "foreground": "hsl(240, 50%, 50%)"}, 123 | 124 | // {"name": "Parens level 5", 125 | // "scope": "meta.parens meta.parens meta.parens meta.parens meta.parens meta.parens punctuation.section", 126 | // "foreground": "hsl(300, 50%, 50%)"}, 127 | 128 | {"name": "Mistakes", 129 | "scope": "invalid, invalid string, invalid constant, invalid entity.name, invalid punctuation, invalid source.symbol", 130 | "foreground": "#c33", 131 | "background": "#FFE0E0"}, 132 | 133 | // MARKUP 134 | 135 | {"scope": "markup.inserted", 136 | "foreground": "hsl(100, 50%, 50%)"}, 137 | 138 | {"scope": "markup.deleted", 139 | "foreground": "hsl(2, 65%, 50%)"}, 140 | 141 | {"scope": "markup.changed", 142 | "foreground": "hsl(30, 85%, 50%)"}, 143 | 144 | {"scope": "markup.ignored", 145 | "foreground": "#aaa"}, 146 | 147 | {"scope": "markup.untracked", 148 | "foreground": "#aaa"}, 149 | 150 | // REGION 151 | 152 | {"scope": "region.eval.success", 153 | "foreground": "hsl(100, 50%, 50%)"}, 154 | 155 | {"scope": "region.eval.exception", 156 | "foreground": "hsl(2, 65%, 50%)"}, 157 | 158 | {"scope": "region.eval.pending", 159 | "foreground": "#CCCCCC"}, 160 | 161 | {"scope": "region.watch", 162 | "foreground": "hsl(285, 50%, 50%)"}, 163 | 164 | {"scope": "region.redish", 165 | "background": "#F04F5080"}, 166 | 167 | {"scope": "region.orangish", 168 | "background": "#FF935680"}, 169 | 170 | {"scope": "region.yellowish", 171 | "background": "#FFBC5D80"}, 172 | 173 | {"scope": "region.greenish", 174 | "background": "#60CB0080"}, 175 | 176 | {"scope": "region.cyanish", 177 | "background": "#00AACB80"}, 178 | 179 | {"scope": "region.bluish", 180 | "background": "#017ACC80"}, 181 | 182 | {"scope": "region.purplish", 183 | "background": "#C171FF80"}, 184 | 185 | {"scope": "region.pinkish", 186 | "background": "#E64CE680"}, 187 | 188 | {"scope": "region.greyish", 189 | "background": "#00000010"}, 190 | 191 | // {"scope": "region.eval.lookup", 192 | // "foreground": "hsl(208, 100%, 50%)"}, 193 | ] 194 | } -------------------------------------------------------------------------------- /Clojure Sublimed.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // Enable debug logging in Sublime Text console 3 | "debug": false, 4 | 5 | // If evaluation takes longer than this, print elapsed time 6 | // Set to null to disable 7 | "elapsed_threshold_ms": 100, 8 | 9 | // Animation to display while waiting for evaluation to finish. 10 | // 11 | // Some ideas: 12 | // 13 | // ["\\", "|", "/", "-"] 14 | // 15 | // ["[=----]", "[-=---]", "[--=--]", "[---=-]", 16 | // "[----=]", "[---=-]", "[--=--]", "[-=---]"] 17 | // 18 | // ["▓░░░░", "░▓░░░", "░░▓░░", "░░░▓░", 19 | // "░░░░▓", "░░░▓░", "░░▓░░", "░▓░░░"] 20 | // 21 | // ["⠏", "⠛", "⠹", "⢸", "⣰", "⣤", "⣆", "⡇"] 22 | // 23 | // ["▁▂▃▄▅", "▂▁▂▃▄", "▃▂▁▂▃", "▄▃▂▁▂", "▅▄▃▂▁", 24 | // "▆▅▄▃▂", "▇▆▅▄▃", "█▇▆▅▄", "▇█▇▆▅", "▆▇█▇▆", 25 | // "▅▆▇█▇", "▄▅▆▇█", "▃▄▅▆▇", "▂▃▄▅▆"] 26 | // 27 | // Set to 1-element array to disable animation: 28 | // 29 | // ["..."] 30 | // 31 | "progress_phases": ["🕐", "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘", "🕙", "🕚", "🕛"], 32 | 33 | // how often to update animation 34 | "progress_interval_ms": 100, 35 | 36 | // values larger than this will be truncated. Set to 0 to disable truncation 37 | "print_quota": 4096, 38 | 39 | // When true, all evals will happen in a single session. This makes 40 | // dynamic vars like `*e` or `*warn-on-reflection*` persistent, but also 41 | // makes all evaluations strictly sequential (new eval will not start until 42 | // all the previous ones have finished). 43 | // 44 | // False by default (enables parallel evals). 45 | "eval_in_session": false, 46 | 47 | // A form to be evaluated in shared session and inherited by all evals 48 | // E.g. (set! *warn-on-reflection* true) 49 | "eval_shared": "", 50 | 51 | // formatter, "sublimed" or "cljfmt". Latter requires `cljfmt` to be on $PATH 52 | "formatter": "sublimed", 53 | 54 | // reformat file on save, false by default 55 | "format_on_save": false 56 | } -------------------------------------------------------------------------------- /ClojureSymbols.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | name 5 | Clojure Sublimed Symbols 6 | scope 7 | entity.name.clojure 8 | settings 9 | 10 | showInIndexedSymbolList 11 | 1 12 | showInSymbolList 13 | 1 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Comment.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | name 5 | Comment 6 | scope 7 | source.clojure 8 | settings 9 | 10 | shellVariables 11 | 12 | 13 | name 14 | TM_COMMENT_START 15 | value 16 | ; 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | // // Evaluate 3 | // {"keys": ["ctrl+alt+enter"], 4 | // "command": "clojure_sublimed_eval", 5 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 6 | 7 | // // Evaluate Buffer 8 | // {"keys": ["ctrl+alt+b"], 9 | // "command": "clojure_sublimed_eval_buffer", 10 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 11 | 12 | // // Interrupt Pending Evaluations 13 | // {"keys": ["ctrl+alt+c"], 14 | // "command": "clojure_sublimed_interrupt_eval", 15 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 16 | 17 | // // Toggle Info 18 | // {"keys": ["ctrl+alt+i"], 19 | // "command": "clojure_sublimed_toggle_info", 20 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 21 | 22 | // // Clear Evaluation Results 23 | // {"keys": ["ctrl+alt+l"], 24 | // "command": "clojure_sublimed_clear_evals", 25 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 26 | 27 | // // Extras 28 | 29 | // // Look for .nrepl-port file and try connection to port in it 30 | // {"keys": ["ctrl+alt+j"], 31 | // "command": "clojure_sublimed_connect_nrepl_jvm", 32 | // "args": {"address": "auto"}}, 33 | 34 | // // Toggle Stacktrace 35 | // {"keys": ["ctrl+alt+e"], 36 | // "command": "clojure_sublimed_toggle_trace", 37 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 38 | 39 | // // Toggle Symbol Info 40 | // {"keys": ["ctrl+alt+d"], 41 | // "command": "clojure_sublimed_toggle_symbol", 42 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 43 | 44 | // // Copy Evaluation Result 45 | // {"keys": ["ctrl+c"], 46 | // "command": "clojure_sublimed_copy", 47 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 48 | 49 | // // Reindent 50 | // {"keys": ["ctrl+alt+f"], 51 | // "command": "clojure_sublimed_reindent", 52 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 53 | 54 | // // Insert New Line 55 | // {"keys": ["enter"], 56 | // "command": "clojure_sublimed_insert_newline", 57 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}, 58 | // {"key": "auto_complete_visible", "operator": "equal", "operand": false}, 59 | // {"key": "panel_has_focus", "operator": "equal", "operand": false}]}, 60 | 61 | // // Toggle Comment 62 | // {"keys": ["ctrl+/"], 63 | // "command": "clojure_sublimed_toggle_comment", 64 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 65 | ] -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | // // Evaluate 3 | // {"keys": ["ctrl+enter"], 4 | // "command": "clojure_sublimed_eval", 5 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 6 | 7 | // // Evaluate Buffer 8 | // {"keys": ["ctrl+b"], 9 | // "command": "clojure_sublimed_eval_buffer", 10 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 11 | 12 | // // Interrupt Pending Evaluations 13 | // {"keys": ["ctrl+c"], 14 | // "command": "clojure_sublimed_interrupt_eval", 15 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 16 | 17 | // // Toggle Info 18 | // {"keys": ["ctrl+i"], 19 | // "command": "clojure_sublimed_toggle_info", 20 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 21 | 22 | // // Clear Evaluation Results 23 | // {"keys": ["ctrl+l"], 24 | // "command": "clojure_sublimed_clear_evals", 25 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 26 | 27 | // // Extras 28 | 29 | // // Look for .nrepl-port file and try connection to port in it 30 | // {"keys": ["ctrl+j"], 31 | // "command": "clojure_sublimed_connect_nrepl_jvm", 32 | // "args": {"address": "auto"}}, 33 | 34 | // // Toggle Stacktrace 35 | // {"keys": ["ctrl+e"], 36 | // "command": "clojure_sublimed_toggle_trace", 37 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 38 | 39 | // // Toggle Symbol Info 40 | // {"keys": ["ctrl+d"], 41 | // "command": "clojure_sublimed_toggle_symbol", 42 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 43 | 44 | // // Copy Evaluation Result 45 | // {"keys": ["super+c"], 46 | // "command": "clojure_sublimed_copy", 47 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 48 | 49 | // // Reindent 50 | // {"keys": ["ctrl+f"], 51 | // "command": "clojure_sublimed_reindent", 52 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 53 | 54 | // // Insert New Line 55 | // {"keys": ["enter"], 56 | // "command": "clojure_sublimed_insert_newline", 57 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}, 58 | // {"key": "auto_complete_visible", "operator": "equal", "operand": false}, 59 | // {"key": "panel_has_focus", "operator": "equal", "operand": false}]}, 60 | 61 | // // Toggle Comment 62 | // {"keys": ["super+forward_slash"], 63 | // "command": "clojure_sublimed_toggle_comment", 64 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 65 | ] -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | // // Evaluate 3 | // {"keys": ["ctrl+alt+enter"], 4 | // "command": "clojure_sublimed_eval", 5 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 6 | 7 | // // Evaluate Buffer 8 | // {"keys": ["ctrl+alt+b"], 9 | // "command": "clojure_sublimed_eval_buffer", 10 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 11 | 12 | // // Interrupt Pending Evaluations 13 | // {"keys": ["ctrl+alt+c"], 14 | // "command": "clojure_sublimed_interrupt_eval", 15 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 16 | 17 | // // Toggle Info 18 | // {"keys": ["ctrl+alt+i"], 19 | // "command": "clojure_sublimed_toggle_info", 20 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 21 | 22 | // // Clear Evaluation Results 23 | // {"keys": ["ctrl+alt+l"], 24 | // "command": "clojure_sublimed_clear_evals", 25 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 26 | 27 | // // Extras 28 | 29 | // // Look for .nrepl-port file and try connection to port in it 30 | // {"keys": ["ctrl+alt+j"], 31 | // "command": "clojure_sublimed_connect_nrepl_jvm", 32 | // "args": {"address": "auto"}}, 33 | 34 | // // Toggle Stacktrace 35 | // {"keys": ["ctrl+alt+e"], 36 | // "command": "clojure_sublimed_toggle_trace", 37 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 38 | 39 | // // Toggle Symbol Info 40 | // {"keys": ["ctrl+alt+d"], 41 | // "command": "clojure_sublimed_toggle_symbol", 42 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 43 | 44 | // // Copy Evaluation Result 45 | // {"keys": ["ctrl+c"], 46 | // "command": "clojure_sublimed_copy", 47 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 48 | 49 | // // Reindent 50 | // {"keys": ["ctrl+alt+f"], 51 | // "command": "clojure_sublimed_reindent", 52 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 53 | 54 | // // Insert New Line 55 | // {"keys": ["enter"], 56 | // "command": "clojure_sublimed_insert_newline", 57 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}, 58 | // {"key": "auto_complete_visible", "operator": "equal", "operand": false}, 59 | // {"key": "panel_has_focus", "operator": "equal", "operand": false}]}, 60 | 61 | // // Toggle Comment 62 | // {"keys": ["ctrl+/"], 63 | // "command": "clojure_sublimed_toggle_comment", 64 | // "context": [{"key": "selector", "operator": "equal", "operand": "source.clojure"}]}, 65 | ] -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Clojure Sublimed: Connect to nREPL JVM", 4 | "command": "clojure_sublimed_connect_nrepl_jvm" 5 | }, 6 | { 7 | "caption": "Clojure Sublimed: Connect to raw nREPL", 8 | "command": "clojure_sublimed_connect_nrepl_raw" 9 | }, 10 | { 11 | "caption": "Clojure Sublimed: Toggle output panel", 12 | "command": "clojure_sublimed_toggle_output_panel" 13 | }, 14 | 15 | { 16 | "caption": "Clojure Sublimed: Connect to shadow-cljs", 17 | "command": "clojure_sublimed_connect_shadow_cljs" 18 | }, 19 | { 20 | "caption": "Clojure Sublimed: Connect to Socket REPL", 21 | "command": "clojure_sublimed_connect_socket_repl" 22 | }, 23 | { 24 | "caption": "Clojure Sublimed: Disconnect", 25 | "command": "clojure_sublimed_disconnect" 26 | }, 27 | { 28 | "caption": "Clojure Sublimed: Reconnect", 29 | "command": "clojure_sublimed_reconnect" 30 | }, 31 | { 32 | "caption": "Clojure Sublimed: Evaluate", 33 | "command": "clojure_sublimed_eval" 34 | }, 35 | { 36 | "caption": "Clojure Sublimed: Evaluate Buffer", 37 | "command": "clojure_sublimed_eval_buffer" 38 | }, 39 | { 40 | "caption": "Clojure Sublimed: Evaluate Previous Form at Current Level", 41 | "command": "clojure_sublimed_eval_previous_form" 42 | }, 43 | { 44 | "caption": "Clojure Sublimed: Interrupt Pending Evaluations", 45 | "command": "clojure_sublimed_interrupt_eval" 46 | }, 47 | { 48 | "caption": "Clojure Sublimed: Copy Evaluation Result", 49 | "command": "clojure_sublimed_copy" 50 | }, 51 | { 52 | "caption": "Clojure Sublimed: Toggle Stacktrace", 53 | "command": "clojure_sublimed_toggle_trace" 54 | }, 55 | { 56 | "caption": "Clojure Sublimed: Toggle Symbol Info", 57 | "command": "clojure_sublimed_toggle_symbol" 58 | }, 59 | { 60 | "caption": "Clojure Sublimed: Toggle Info", 61 | "command": "clojure_sublimed_toggle_info" 62 | }, 63 | { 64 | "caption": "Clojure Sublimed: Clear Evaluation Results", 65 | "command": "clojure_sublimed_clear_evals" 66 | }, 67 | { 68 | "caption": "Clojure Sublimed: Reindent", 69 | "command": "clojure_sublimed_reindent" 70 | }, 71 | { 72 | "caption": "Clojure Sublimed: Reindent Lines", 73 | "command": "clojure_sublimed_reindent_lines" 74 | }, 75 | { 76 | "caption": "Clojure Sublimed: Reindent Buffer", 77 | "command": "clojure_sublimed_reindent_buffer" 78 | }, 79 | { 80 | "caption": "Clojure Sublimed: Pretty-print selection", 81 | "command": "clojure_sublimed_pretty_print" 82 | }, 83 | { 84 | "caption": "Clojure Sublimed: Select topmost form", 85 | "command": "clojure_sublimed_select_topmost_form" 86 | }, 87 | { 88 | "caption": "Clojure Sublimed: Toggle comment", 89 | "command": "clojure_sublimed_toggle_comment" 90 | }, 91 | { 92 | "caption": "Clojure Sublimed: Insert Newline", 93 | "command": "clojure_sublimed_insert_newline" 94 | }, 95 | { 96 | "caption": "Clojure Sublimed: Add Watch", 97 | "command": "clojure_sublimed_add_watch" 98 | }, 99 | { 100 | "caption": "Clojure Sublimed: Align Cursors", 101 | "command": "clojure_sublimed_align_cursors" 102 | }, 103 | { 104 | "caption": "Preferences: Clojure Sublimed Settings", 105 | "command": "edit_settings", 106 | "args": { 107 | "base_file": "${packages}/Clojure Sublimed/Clojure Sublimed.sublime-settings", 108 | "default": "// Clojure Sublimed Settings - User\n{\n\t$0\n}\n" 109 | } 110 | }, 111 | { 112 | "caption": "Preferences: Clojure Sublimed Key Bindings", 113 | "command": "edit_settings", 114 | "args": { 115 | "base_file": "${packages}/Clojure Sublimed/Default (${platform}).sublime-keymap", 116 | "user_file": "${packages}/User/Default (${platform}).sublime-keymap" 117 | } 118 | }, 119 | ] -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-2021 Nikita Prokopov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "id": "preferences", 3 | "children": [ 4 | { "caption": "Package Settings", 5 | "mnemonic": "P", 6 | "id": "package-settings", 7 | "children": [ 8 | { "caption": "Clojure Sublimed", 9 | "children": [ 10 | { "caption": "README", 11 | "command": "open_file", 12 | "args": { 13 | "file": "${packages}/Clojure Sublimed/README.md" 14 | } 15 | }, 16 | { "caption": "-" }, 17 | { "caption": "Settings", 18 | "command": "edit_settings", 19 | "args": { 20 | "base_file": "${packages}/Clojure Sublimed/Clojure Sublimed.sublime-settings", 21 | "default": "{\n\t$0\n}\n" 22 | } 23 | }, 24 | { "caption": "Key Bindings", 25 | "command": "edit_settings", 26 | "args": { 27 | "base_file": "${packages}/Clojure Sublimed/Default (${platform}).sublime-keymap", 28 | "default": "[\n\t$0\n]\n" 29 | } 30 | }, 31 | ] 32 | } 33 | ] 34 | } 35 | ] 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /cljfmt.edn: -------------------------------------------------------------------------------- 1 | {:indents {#re ".*" [[:inner 0]]} 2 | :remove-surrounding-whitespace? false 3 | :remove-trailing-whitespace? false 4 | :remove-consecutive-blank-lines? false} -------------------------------------------------------------------------------- /cs_bencode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | nrepl.bencode 5 | ------------- 6 | This module provides BEncode-protocol support. 7 | :copyright: (c) 2013 by Chas Emerick. 8 | :license: MIT, see LICENSE for more details. 9 | ''' 10 | 11 | 12 | from io import StringIO, BytesIO 13 | import array, numbers, sys 14 | from io import BytesIO 15 | 16 | def _read_byte(s): 17 | return s.read(1) 18 | 19 | def _read_int(s, terminator=None, init_data=None): 20 | int_chrs = init_data or [] 21 | while True: 22 | c = _read_byte(s) 23 | if not (c.isdigit() or c == b'-') or c == terminator or not c: 24 | break 25 | else: 26 | int_chrs.append(c) 27 | return int(b''.join(int_chrs)) 28 | 29 | 30 | def _read_bytes(s, n): 31 | data = BytesIO() 32 | cnt = 0 33 | while cnt < n: 34 | m = s.read(n - cnt) 35 | if not m: 36 | raise Exception("Invalid bytestring, unexpected end of input.") 37 | data.write(m) 38 | cnt += len(m) 39 | data.flush() 40 | # Taking into account that Python3 can't decode strings 41 | try: 42 | ret = data.getvalue().decode("UTF-8") 43 | except AttributeError: 44 | ret = data.getvalue() 45 | return ret 46 | 47 | 48 | def _read_delimiter(s): 49 | d = _read_byte(s) 50 | if d.isdigit(): 51 | d = _read_int(s, ":", [d]) 52 | return d 53 | 54 | 55 | def _read_list(s): 56 | data = [] 57 | while True: 58 | datum = _read_datum(s) 59 | if datum is None: 60 | break 61 | data.append(datum) 62 | return data 63 | 64 | 65 | def _read_map(s): 66 | i = iter(_read_list(s)) 67 | return dict(zip(i, i)) 68 | 69 | 70 | _read_fns = {b"i": _read_int, 71 | b"l": _read_list, 72 | b"d": _read_map, 73 | b"e": lambda _: None, 74 | # EOF 75 | None: lambda _: None} 76 | 77 | 78 | def _read_datum(s): 79 | delim = _read_delimiter(s) 80 | if delim != b'': 81 | return _read_fns.get(delim, lambda s: _read_bytes(s, delim))(s) 82 | 83 | 84 | def _write_datum(x, out): 85 | if isinstance(x, (str, bytes)): 86 | # x = x.encode("UTF-8") 87 | # TODO revisit encodings, this is surely not right. Python 88 | # (2.x, anyway) conflates bytes and strings, but 3.x does not... 89 | out.write(str(len(x.encode('utf-8'))).encode('utf-8')) 90 | out.write(b":") 91 | out.write(x.encode('utf-8')) 92 | elif isinstance(x, numbers.Integral): 93 | out.write(b"i") 94 | out.write(str(x).encode('utf-8')) 95 | out.write(b"e") 96 | elif isinstance(x, (list, tuple)): 97 | out.write(b"l") 98 | for v in x: 99 | _write_datum(v, out) 100 | out.write(b"e") 101 | elif isinstance(x, dict): 102 | out.write(b"d") 103 | for k, v in x.items(): 104 | _write_datum(k, out) 105 | _write_datum(v, out) 106 | out.write(b"e") 107 | out.flush() 108 | 109 | 110 | def encode(v): 111 | "bencodes the given value, may be a string, integer, list, or dict." 112 | s = BytesIO() 113 | _write_datum(v, s) 114 | return s.getvalue().decode('utf-8') 115 | 116 | 117 | def decode_file(file): 118 | while True: 119 | x = _read_datum(file) 120 | if x is None: 121 | break 122 | yield x 123 | 124 | 125 | def decode(string): 126 | "Generator that yields decoded values from the input string." 127 | return decode_file(BytesIO(string.encode('utf-8'))) 128 | 129 | 130 | class BencodeIO(object): 131 | def __init__(self, file, on_close=None): 132 | self._file = file 133 | self._on_close = on_close 134 | 135 | def read(self): 136 | return _read_datum(self._file) 137 | 138 | def __iter__(self): 139 | return self 140 | 141 | def next(self): 142 | v = self.read() 143 | if not v: 144 | raise StopIteration 145 | return v 146 | 147 | def __next__(self): 148 | # In Python3, __next__ it is an own special class. 149 | v = self.read() 150 | if not v: 151 | raise StopIteration 152 | return v 153 | 154 | def write(self, v): 155 | return _write_datum(v, self._file) 156 | 157 | def flush(self): 158 | if self._file.flush: 159 | self._file.flush() 160 | 161 | def close(self): 162 | # Run the on_close handler if one exists, which can do something 163 | # useful like cleanly close a socket. (Note that .close() on a 164 | # socket.makefile('rw') does some kind of unclean close.) 165 | if self._on_close is not None: 166 | self._on_close() 167 | else: 168 | self._file.close() 169 | 170 | if __name__ == '__main__': 171 | import json, socket, threading 172 | conn = socket.create_connection(("localhost", 5555)) 173 | 174 | def read_loop(conn): 175 | while msg := conn.recv(4096): 176 | payload = msg.decode() 177 | parsed = next(decode(payload)) 178 | cs_common.debug("RCV", json.dumps(parsed)) 179 | threading.Thread(daemon=True, target=read_loop, args=(conn,)).start() 180 | 181 | for line in sys.stdin: 182 | try: 183 | parsed = json.loads(line) 184 | encoded = encode(parsed) 185 | # print("SND RAW", encoded.encode()) 186 | conn.sendall(encoded.encode()) 187 | except json.JSONDecodeError: 188 | print("Not a valid JSON") 189 | -------------------------------------------------------------------------------- /cs_cljfmt.py: -------------------------------------------------------------------------------- 1 | import difflib, os, re, sublime, subprocess 2 | from . import cs_common, cs_parser 3 | 4 | def format_string(text, view = None, cwd = None): 5 | try: 6 | cmd = 'cljfmt.exe' if 'windows' == sublime.platform() else 'cljfmt' 7 | if not cwd: 8 | if file := view.file_name(): 9 | cwd = os.path.dirname(file) 10 | elif folders := view.window().folders(): 11 | cwd = folders[0] 12 | 13 | proc = subprocess.run([cmd, 'fix', '-'], 14 | input = text, 15 | text = True, 16 | capture_output = True, 17 | check = True, 18 | cwd = cwd) 19 | except FileNotFoundError: 20 | sublime.error_message(f'`{cmd}` is not on $PATH') 21 | raise 22 | if 'Failed' not in proc.stderr: 23 | return proc.stdout 24 | 25 | def indent_lines(view, regions, edit): 26 | regions = [region for region in regions if not region.empty()] 27 | if not regions: 28 | regions = [sublime.Region(0, view.size())] 29 | replacements = [] 30 | for region in regions: 31 | text = view.substr(region) 32 | if text_formatted := format_string(text, view = view): 33 | pos = region.begin() 34 | diff = difflib.ndiff(text.splitlines(keepends=True), text_formatted.splitlines(keepends=True)) 35 | for line in diff: 36 | if line[:2] == '- ': 37 | replacements.append((sublime.Region(pos, pos + len(line) - 2), '')) 38 | pos = pos + len(line) - 2 39 | elif line[:2] == '+ ': 40 | replacements.append((sublime.Region(pos, pos), line[2:])) 41 | elif line[:2] == ' ': 42 | pos = pos + len(line) - 2 43 | elif line[:2] == '? ': 44 | pass 45 | if replacements: 46 | selections_before = [(view.rowcol(r.a), view.rowcol(r.b)) for r in view.sel()] 47 | delta = 0 48 | for region, string in replacements: 49 | transformed_region = sublime.Region(region.a + delta, region.b + delta) 50 | view.replace(edit, transformed_region, string) 51 | delta = delta - region.size() + len(string) 52 | 53 | view.sel().clear() 54 | for ((rowa, cola), (rowb, colb)) in selections_before: 55 | a = view.text_point(rowa, cola) 56 | b = view.text_point(rowb, colb) 57 | view.sel().add(sublime.Region(a, b)) 58 | 59 | def newline_indent(view, point): 60 | text = view.substr(sublime.Region(0, point)) 61 | parsed = cs_parser.parse(text) 62 | to_close = [] 63 | node = parsed 64 | start = node.children[-1].start if node.children else 0 65 | while node: 66 | if 'string' == node.name and node.open and not node.close: 67 | to_close.insert(0, '"') 68 | elif 'parens' == node.name and node.open and not node.close: 69 | to_close.insert(0, ')') 70 | elif 'braces' == node.name and node.open and not node.close: 71 | to_close.insert(0, '}') 72 | elif 'brackets' == node.name and node.open and not node.close: 73 | to_close.insert(0, ']') 74 | node = node.children[-1] if node.children else None 75 | if to_close and '"' == to_close[0]: 76 | return None 77 | 78 | ns = None 79 | for child in parsed.children: 80 | if child.end >= start: 81 | break 82 | if child.name == 'meta': 83 | child = child.body.children[0] 84 | if child.name == 'parens': 85 | body = child.body 86 | if body and len(body.children) >= 2: 87 | first_form = body.children[0] 88 | if first_form.name == 'token' and first_form.text == 'ns': 89 | ns = child 90 | 91 | excerpt = '' 92 | if ns: 93 | excerpt = text[ns.start:ns.end] + '\n' 94 | 95 | excerpt = excerpt + text[start:] + "\nCLOJURE_SUBLIMED_SYM" + "".join(to_close) 96 | formatted = format_string(excerpt, view = view) 97 | last_line = formatted.splitlines()[-1] 98 | indent = re.match(r"^\s*", last_line)[0] 99 | return len(indent) 100 | -------------------------------------------------------------------------------- /cs_colors.py: -------------------------------------------------------------------------------- 1 | import re, sublime 2 | 3 | RE_REPLACE_GLOB = re.compile(r"\*\*|[\*\?\.\(\)\[\]\{\}\$\^\+\|]") 4 | 5 | region_id = 0 6 | 7 | # Colors 8 | FG_ANSI = { 9 | 30: 'black', 10 | 31: 'red', 11 | 32: 'green', 12 | 33: 'brown', 13 | 34: 'blue', 14 | 35: 'magenta', 15 | 36: 'cyan', 16 | 37: 'white', 17 | 39: 'default', 18 | 90: 'light_black', 19 | 91: 'light_red', 20 | 92: 'light_green', 21 | 93: 'light_brown', 22 | 94: 'light_blue', 23 | 95: 'light_magenta', 24 | 96: 'light_cyan', 25 | 97: 'light_white' 26 | } 27 | 28 | BG_ANSI = { 29 | 40: 'black', 30 | 41: 'red', 31 | 42: 'green', 32 | 43: 'brown', 33 | 44: 'blue', 34 | 45: 'magenta', 35 | 46: 'cyan', 36 | 47: 'white', 37 | 49: 'default', 38 | 100: 'light_black', 39 | 101: 'light_red', 40 | 102: 'light_green', 41 | 103: 'light_brown', 42 | 104: 'light_blue', 43 | 105: 'light_magenta', 44 | 106: 'light_cyan', 45 | 107: 'light_white' 46 | } 47 | 48 | SCOPES = { 49 | 'red': 'redish', 50 | 'green': 'greenish', 51 | 'brown': 'orangish', 52 | 'blue': 'bluish', 53 | 'magenta': 'pinkish', # purplish 54 | 'cyan': 'cyanish', 55 | 'light_red': 'redish', 56 | 'light_green': 'greenish', 57 | 'light_brown': 'orangish', 58 | 'light_blue': 'bluish', 59 | 'light_magenta': 'pinkish', 60 | 'light_cyan': 'cyanish' 61 | } 62 | 63 | RE_UNKNOWN_ESCAPES = re.compile(r"\x1b[^a-zA-Z]*[a-zA-Z]") 64 | RE_COLOR_ESCAPES = re.compile(r"\x1b\[((?:;?\d+)*)m") 65 | RE_NOTSPACE = re.compile(r"[^\s]+") 66 | 67 | def write(view, characters): 68 | decolorized = "" 69 | original_pos = 0 70 | decolorized_pos = 0 71 | fg = "default" 72 | bg = "default" 73 | regions = [] 74 | def iteration(start, end, group): 75 | nonlocal decolorized, original_pos, decolorized_pos, fg, bg, regions 76 | text = characters[original_pos:start] 77 | text = RE_UNKNOWN_ESCAPES.sub("", text) 78 | decolorized += text 79 | if len(text) > 0 and (fg != "default" or bg != "default"): 80 | regions.append({"text": text, 81 | "start": decolorized_pos, 82 | "end": decolorized_pos + len(text), 83 | "fg": fg, 84 | "bg": bg}) 85 | digits = re.findall(r"\d+", group) or ["0"] 86 | for digit in digits: 87 | digit = int(digit) 88 | if digit in FG_ANSI: 89 | fg = FG_ANSI[digit] 90 | if digit in BG_ANSI: 91 | bg = BG_ANSI[digit] 92 | if digit == 0: 93 | fg = 'default' 94 | bg = 'default' 95 | original_pos = end 96 | decolorized_pos += len(text) 97 | 98 | for m in RE_COLOR_ESCAPES.finditer(characters): 99 | iteration(m.start(), m.end(), m.group(1)) 100 | iteration(len(characters), len(characters), "") 101 | 102 | insertion_point = view.size() 103 | view.run_command('append', {'characters': decolorized, 'force': True, 'scroll_to_end': True}) 104 | 105 | global region_id 106 | for region in regions: 107 | if scope := SCOPES.get(region['bg'], None) or SCOPES.get(region['fg'], None): 108 | for m in RE_NOTSPACE.finditer(region['text']): 109 | start = insertion_point + region['start'] + m.start() 110 | end = start + len(m.group(0)) 111 | region_id += 1 112 | view.add_regions( 113 | "executor#{}".format(region_id), 114 | [sublime.Region(start, end)], 115 | 'region.' + scope) 116 | -------------------------------------------------------------------------------- /cs_comment.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sublime, sublime_plugin 3 | from . import cs_parser 4 | 5 | def search_point(node, pos): 6 | # no children 7 | if node.nested() is None: 8 | if node.start <= pos and pos <= node.end: 9 | return node 10 | else: 11 | return None 12 | 13 | # uncomment 14 | if node.is_terminal() and node.start <= pos and pos <= node.end: 15 | return node 16 | 17 | prev_child = None 18 | res = None 19 | for child in node.nested(): 20 | # has children: between two (() () | ()) 21 | # has children: before first ( | () () ()) 22 | if child.start > pos: 23 | res = prev_child 24 | break 25 | 26 | # has children: at the start (() |() ()) 27 | # has children: inside one (() (|) ()) 28 | # has children: at the end (() ()| ()) 29 | if child.start <= pos and pos <= child.end: 30 | res = search_point(child, pos) 31 | break 32 | 33 | prev_child = child 34 | 35 | if res: 36 | return res 37 | 38 | if node.start <= pos and pos <= node.end: 39 | return node 40 | 41 | def search_range(node, start, end): 42 | if node.nested() is not None and node.start <= start and end <= node.end: 43 | if start <= node.start and node.end <= end: 44 | return [node] 45 | res = [] 46 | for child in node.nested(): 47 | if child.nested() is not None and child.start <= start and end <= child.end: 48 | return search_range(child, start, end) 49 | # (...)...[...] - no 50 | # (...[...)...] 51 | # [...(...)...] 52 | # [...(...]...) 53 | # [...]...(...) - no 54 | # (...[...]...) 55 | elif not child.end <= start and not child.start >= end: 56 | res.append(child) 57 | return res or [node] 58 | 59 | class ClojureSublimedToggleCommentCommand(sublime_plugin.TextCommand): 60 | def run(self, edit): 61 | view = self.view 62 | parsed = cs_parser.parse_tree(view) 63 | sel = [] 64 | offset = 0 65 | regions = [r for r in view.sel()] 66 | regions.sort(key = lambda r: r.begin()) 67 | for region in regions: 68 | if region.empty(): 69 | nodes = [search_point(parsed, region.begin())] 70 | else: 71 | nodes = search_range(parsed, region.begin(), region.end()) 72 | a = region.a + offset 73 | b = region.b + offset 74 | if all(node.name == "comment" for node in nodes): 75 | # uncomment line comments 76 | for node in nodes: 77 | m = re.match(r'^;+\s*', node.text) 78 | r = sublime.Region(node.start + offset, node.start + offset + len(m[0])) 79 | view.replace(edit, r, "") 80 | a = a if a < r.begin() else r.begin() if a < r.end() else a - r.size() 81 | b = b if b < r.begin() else r.begin() if b < r.end() else b - r.size() 82 | offset -= r.size() 83 | elif all(node.name in ["discard", "comment"] for node in nodes): 84 | # uncomment discards only 85 | for node in nodes: 86 | if node.name == "discard": 87 | r = sublime.Region(node.start + offset, node.body.start + offset) 88 | view.replace(edit, r, "") 89 | a = a if a < r.begin() else r.begin() if a < r.end() else a - r.size() 90 | b = b if b < r.begin() else r.begin() if b < r.end() else b - r.size() 91 | offset -= r.size() 92 | else: 93 | for node in nodes: 94 | if node.name not in ["discard", "comment"]: 95 | # comment 96 | r = sublime.Region(node.start + offset, node.start + offset) 97 | view.replace(edit, r, "#_") 98 | a = a if a < r.begin() else a + 2 99 | b = b if b < r.begin() else b + 2 100 | offset += 2 101 | sel.append(sublime.Region(a, b)) 102 | view.sel().clear() 103 | view.sel().add_all(sel) 104 | -------------------------------------------------------------------------------- /cs_common.py: -------------------------------------------------------------------------------- 1 | import collections, html, math, os, re, socket, sublime, sublime_plugin, time, traceback 2 | from typing import Any, Dict, Tuple 3 | 4 | ns = 'clojure-sublimed' 5 | 6 | package = None 7 | 8 | class State: 9 | def __init__(self): 10 | self.statuses = {} 11 | self.last_view = None 12 | self.conn = None 13 | self.last_conn = None 14 | self.warnings = 0 15 | self.status_eval = None 16 | self.watches = {} 17 | 18 | states = collections.defaultdict(lambda: State()) 19 | 20 | def get_state(window = None): 21 | if window is None: 22 | window = sublime.active_window() 23 | return states[window.id()] 24 | 25 | class Form: 26 | def __init__(self, id = None, code = None, ns = 'user', line = None, column = None, file = None, print_quota = None): 27 | self.id = id 28 | self.code = code 29 | self.ns = ns 30 | self.line = line 31 | self.column = column 32 | self.file = file 33 | self.print_quota = print_quota 34 | 35 | def main_settings(view = None): 36 | if view := view or sublime.active_window().active_view(): 37 | return view.settings() 38 | return sublime.load_settings("Preferences.sublime-settings") 39 | 40 | def settings(): 41 | """ 42 | Plugin settings 43 | """ 44 | return sublime.load_settings("Clojure Sublimed.sublime-settings") 45 | 46 | def setting(key, default = None, view = None): 47 | """ 48 | Shortcut to get value of a particular plugin setting 49 | """ 50 | s = main_settings(view = view) 51 | if s and (res := s.get("clojure_sublimed_" + key)) is not None: 52 | return res 53 | s = settings() 54 | if s and (res := s.get(key)) is not None: 55 | return res 56 | return default 57 | 58 | def on_settings_change(tag, callback): 59 | """ 60 | Subscribe to settings change 61 | """ 62 | main_settings().add_on_change(tag, callback) 63 | settings().add_on_change(tag, callback) 64 | callback() 65 | 66 | def clear_settings_change(tag): 67 | """ 68 | Unsubscribe from settings change 69 | """ 70 | main_settings().clear_on_change(tag) 71 | settings().clear_on_change(tag) 72 | 73 | def wrap_width(view): 74 | if (w := setting('wrap_width')): 75 | return w 76 | if not view: 77 | return 80 78 | return math.floor(view.viewport_extent()[0] / view.em_width()) - 3 79 | 80 | def debug(format, *args): 81 | """ 82 | Print to console if 'debug' is set to True. Format as in `str.format` 83 | """ 84 | if setting('debug'): 85 | print('[ Clojure Sublimed ]', format.format(*args)) 86 | 87 | def error(format, *args): 88 | """ 89 | Print error and stacktrace to console. Format as in `str.format` 90 | """ 91 | print('[ Clojure Sublimed ] ERROR:', format.format(*args)) 92 | traceback.print_exc() 93 | 94 | class Measure: 95 | """ 96 | Measure and print (if debug) execution time of with block. Format as in `str.format` 97 | """ 98 | def __init__(self, format, *args): 99 | self.format = "{:.2f} ms " + format 100 | self.args = args 101 | 102 | def __enter__(self): 103 | self.time = time.time() 104 | 105 | def __exit__(self, exc_type, exc_value, exc_tb): 106 | debug(self.format, (time.time() - self.time) * 1000, *self.args) 107 | 108 | def format_time_taken(time_taken): 109 | """ 110 | Human-readable time taken (ms or sec) 111 | """ 112 | threshold = setting("elapsed_threshold_ms" ,100) 113 | if threshold != None and time_taken != None: 114 | elapsed = time_taken / 1000 115 | if elapsed * 1000 >= threshold: 116 | if elapsed >= 10: 117 | return f"({'{:,.0f}'.format(elapsed)} sec)" 118 | elif elapsed >= 1: 119 | return f"({'{:.1f}'.format(elapsed)} sec)" 120 | elif elapsed >= 0.005: 121 | return f"({'{:.0f}'.format(elapsed * 1000)} ms)" 122 | else: 123 | return f"({'{:.2f}'.format(elapsed * 1000)} ms)" 124 | 125 | def regions_touch(r1, r2): 126 | """ 127 | True iff regions intersect or touch 128 | """ 129 | return r1 != None and r2 != None and not r1.end() < r2.begin() and not r1.begin() > r2.end() 130 | 131 | def basic_styles(view): 132 | """ 133 | Used to format phantoms, to achieve ~line height as in the main editor 134 | """ 135 | settings = view.settings() 136 | top = settings.get('line_padding_top', 0) 137 | bottom = settings.get('line_padding_bottom', 0) 138 | return f"""""" 88 | limit = cs_common.wrap_width(self.view) 89 | for line in text.splitlines(): 90 | line = cs_printer.wrap_string(line, limit = limit) 91 | line = cs_common.escape(line) 92 | body += "

" + line + "

" 93 | body += "" 94 | region = self.region() 95 | if region: 96 | point = self.view.line(region.end()).begin() 97 | self.phantom_id = self.view.add_phantom(self.region_key(), sublime.Region(point, point), body, sublime.LAYOUT_BLOCK) 98 | 99 | def toggle_pprint(self): 100 | node = cs_parser.parse(self.value) 101 | string = cs_printer.format(self.value, node, limit = cs_common.wrap_width(self.view)) 102 | styles = """ 103 | .light body { background-color: hsl(100, 100%, 90%); } 104 | .dark body { background-color: hsl(100, 100%, 10%); } 105 | """ 106 | if phantom_styles := cs_common.phantom_styles(self.view, "phantom_success"): 107 | styles += f".light body, .dark body {{ { phantom_styles }; border: 4px solid #33CC33; }}" 108 | self.toggle_phantom(string, styles) 109 | 110 | def toggle_failure(self): 111 | node = cs_parser.parse(self.value) 112 | string = cs_printer.format(self.value, node, limit = cs_common.wrap_width(self.view)) 113 | styles = """ 114 | .light body { background-color: hsl(0, 100%, 90%); } 115 | .dark body { background-color: hsl(0, 100%, 10%); } 116 | """ 117 | if phantom_styles := cs_common.phantom_styles(self.view, "phantom_failure"): 118 | styles += f".light body, .dark body {{ { phantom_styles }; border: 4px solid #CC3333; }}" 119 | self.toggle_phantom(string, styles) 120 | 121 | def toggle_trace(self): 122 | styles = """ 123 | .light body { background-color: hsl(0, 100%, 90%); } 124 | .dark body { background-color: hsl(0, 100%, 10%); } 125 | """ 126 | if phantom_styles := cs_common.phantom_styles(self.view, "phantom_exception"): 127 | styles += f".light body, .dark body {{ {phantom_styles}; border: 4px solid #CC3333; }}" 128 | self.toggle_phantom(self.trace, styles) 129 | 130 | def erase(self, interrupt = True): 131 | self.view.erase_regions(self.region_key()) 132 | if self.phantom_id: 133 | self.view.erase_phantom_by_id(self.phantom_id) 134 | 135 | del evals[self.id] 136 | del evals_by_view[self.view.id()][self.id] 137 | if interrupt and self.status == "pending" and self.session: 138 | state = cs_common.get_state() 139 | state.conn.send({"op": "interrupt", "interrupt-id": self.id, "session": self.session}) 140 | 141 | def by_id(id): 142 | """ 143 | Find an eval by id. Might return status_eval 144 | """ 145 | state = cs_common.get_state() 146 | if (eval := state.status_eval) and id == eval.id: 147 | return eval 148 | return evals.get(id, None) 149 | 150 | def by_region(view, region): 151 | """ 152 | Find an eval touching region 153 | """ 154 | for eval in list(evals_by_view[view.id()].values()): 155 | if cs_common.regions_touch(eval.region(), region): 156 | return eval 157 | 158 | def by_status(view, status): 159 | """ 160 | Find evals by status 161 | """ 162 | return (eval for eval in list(evals_by_view[view.id()].values()) if eval.status == status) 163 | 164 | def erase_evals(predicate = lambda x: True, view = None): 165 | """ 166 | Kill evals based on predicate 167 | """ 168 | if view: 169 | es = list(evals_by_view[view.id()].items()) 170 | else: 171 | es = list(evals.items()) 172 | state = cs_common.get_state(view.window() if view else None) 173 | if eval := state.status_eval: 174 | es += [(eval.id, eval)] 175 | for id, eval in es: 176 | if predicate(eval): 177 | eval.erase() 178 | 179 | def on_success(id, value, time = None): 180 | """ 181 | Callback to be called after conn.eval or conn.load_file 182 | """ 183 | if (eval := by_id(id)): 184 | failure = re.search(r":fail\s+[1-9]\d*", value) \ 185 | or re.search(r":error\s+[1-9]\d*", value) 186 | eval.update("failure" if failure else "success", value, time_taken = time) 187 | if eval.on_finish: 188 | eval.on_finish(eval) 189 | 190 | def on_exception(id, value, source = None, line = None, column = None, trace = None): 191 | """ 192 | Callback to be called after conn.eval, conn.load_file or conn.interrupt 193 | """ 194 | if (eval := by_id(id)): 195 | eval.ex_source = source 196 | eval.ex_line = line 197 | eval.ex_column = column 198 | eval.trace = trace 199 | eval.update('exception', value) 200 | if eval.on_finish: 201 | eval.on_finish(eval) 202 | 203 | def on_done(id): 204 | if (eval := by_id(id)): 205 | es = [eval] 206 | else: 207 | es = [eval for eval in evals.values() if eval.batch_id == id] 208 | for eval in es: 209 | if eval.status not in {"success", "failure", "exception"}: 210 | eval.erase() 211 | 212 | def format_lookup(view, info): 213 | settings = view.settings() 214 | top = settings.get('line_padding_top', 0) 215 | bottom = settings.get('line_padding_bottom', 0) 216 | body = f""" 217 | {cs_common.basic_styles(view)} 218 | .dark body {{ background-color: color(var(--background) blend(#FFF 90%)); }} 219 | .light body {{ background-color: color(var(--background) blend(#000 95%)); }} 220 | a {{ text-decoration: none; }} 221 | .arglists {{ color: color(var(--foreground) alpha(0.5)); }} 222 | """ 223 | 224 | if not info: 225 | body += "

Not found

" 226 | else: 227 | ns = info.get('ns') 228 | name = info['name'] 229 | file = info.get('file') 230 | arglists = info.get('arglists') 231 | forms = info.get('forms') 232 | doc = info.get('doc') 233 | 234 | body += "

" 235 | if file: 236 | body += f"" 237 | if ns: 238 | body += html.escape(ns) + "/" 239 | body += html.escape(name) 240 | if file: 241 | body += f"" 242 | body += "

" 243 | 244 | if arglists: 245 | body += f'

{html.escape(arglists.strip("()"))}

' 246 | 247 | if forms and isinstance(forms, str): 248 | body += f'

{html.escape(forms.strip("[]"))}

' 249 | elif forms: 250 | def format_form(form): 251 | if isinstance(form, str): 252 | return form 253 | else: 254 | return "(" + " ".join([format_form(x) for x in form]) + ")" 255 | body += '

' 256 | body += html.escape(" ".join([format_form(form) for form in forms])) 257 | body += "

" 258 | 259 | if doc: 260 | body += "

" + "

".join(html.escape(doc).split("\n")) + "

" 261 | body += "" 262 | return body 263 | 264 | def on_lookup(id, value): 265 | """ 266 | Callback to be called after conn.lookup 267 | """ 268 | if (eval := by_id(id)): 269 | eval.update('lookup', None) 270 | view = eval.view 271 | body = format_lookup(view, value) 272 | point = view.line(eval.region().end()).begin() 273 | eval.phantom_id = view.add_phantom(eval.region_key(), sublime.Region(point, point), body, sublime.LAYOUT_BLOCK) 274 | 275 | def format_code_fn(s): 276 | def transform_fn(code, **kwargs): 277 | return s \ 278 | .replace(r"%code", code) \ 279 | .replace(r"%symbol", cs_common.get_default(kwargs, 'symbol', 'nil')) \ 280 | .replace(r"%ns", cs_common.get_default(kwargs, 'ns', 'user')) 281 | return transform_fn 282 | 283 | class ClojureSublimedEvalCommand(sublime_plugin.WindowCommand): 284 | """ 285 | Eval selected code or topmost form is selection is collapsed 286 | """ 287 | def run(self, regions = None, print_quota = None, transform = None, expand = False): 288 | view = self.window.active_view() 289 | state = cs_common.get_state(self.window) 290 | 291 | transform_fn = None 292 | if transform: 293 | transform_fn = format_code_fn(transform) 294 | 295 | on_finish = None 296 | if expand: 297 | on_finish = lambda _: view.run_command("clojure_sublimed_toggle_info", {}) 298 | 299 | regions = [sublime.Region(a, b) for (a, b) in regions or []] 300 | state.conn.eval(view, regions or view.sel(), transform_fn = transform_fn, print_quota = print_quota, on_finish = on_finish) 301 | 302 | def is_enabled(self): 303 | return self.window.active_view() and cs_conn.ready(self.window) 304 | 305 | class ClojureSublimedEvalPreviousFormCommand(sublime_plugin.WindowCommand): 306 | def run(self, print_quota = None, transform = None, expand = False): 307 | view = self.window.active_view() 308 | regions = [] 309 | 310 | for sel in view.sel(): 311 | if sel.empty(): 312 | if form := cs_parser.previous_form_at_level(view, sel.begin()): 313 | regions.append((form.start, form.end)) 314 | 315 | if regions: 316 | args = {"regions": regions, "print_quota": print_quota, "transform": transform, "expand": expand} 317 | self.window.run_command("clojure_sublimed_eval", args) 318 | 319 | def is_enabled(self): 320 | return self.window.active_view() and cs_conn.ready(self.window) 321 | 322 | class ClojureSublimedEvalBufferCommand(sublime_plugin.TextCommand): 323 | """ 324 | Eval whole buffer 325 | """ 326 | def run(self, edit): 327 | state = cs_common.get_state(self.view.window()) 328 | state.conn.load_file(self.view) 329 | 330 | def is_enabled(self): 331 | return cs_conn.ready(self.view.window()) 332 | 333 | class ClojureSublimedCopyCommand(sublime_plugin.TextCommand): 334 | """ 335 | Copy .value of eval under cursor to clipboard 336 | """ 337 | def eval(self): 338 | view = self.view 339 | return by_region(view, view.sel()[0]) 340 | 341 | def run(self, edir): 342 | if cs_conn.ready(self.view.window()) and len(self.view.sel()) == 1 and self.view.sel()[0].empty() and (eval := self.eval()) and eval.value: 343 | sublime.set_clipboard(eval.value) 344 | else: 345 | self.view.run_command("copy", {}) 346 | 347 | class ClojureSublimedToggleTraceCommand(sublime_plugin.TextCommand): 348 | """ 349 | Show/hide extended stacktrace 350 | """ 351 | def run(self, edit): 352 | view = self.view 353 | sel = view.sel()[0] 354 | if eval := by_region(view, sel): 355 | eval.toggle_trace() 356 | 357 | def is_enabled(self): 358 | return cs_conn.ready(self.view.window()) and len(self.view.sel()) == 1 359 | 360 | class ClojureSublimedToggleSymbolCommand(sublime_plugin.TextCommand): 361 | """ 362 | Show/hide symbol info 363 | """ 364 | def run(self, edit): 365 | view = self.view 366 | sel = view.sel()[0] 367 | eval = by_region(view, sel) 368 | if eval and eval.phantom_id: 369 | eval.erase() 370 | else: 371 | if region := cs_parser.symbol_at_point(view, sel.begin()) if sel.empty() else sel: 372 | state = cs_common.get_state(self.view.window()) 373 | state.conn.lookup(view, region) 374 | 375 | def is_enabled(self): 376 | return cs_conn.ready(self.view.window()) and len(self.view.sel()) == 1 377 | 378 | class ClojureSublimedToggleInfoCommand(sublime_plugin.TextCommand): 379 | """ 380 | Universal show/hide, depends on where it was called. Can expand stacktrace, 381 | successfull eval or look up symbol 382 | """ 383 | def run(self, edit): 384 | view = self.view 385 | sel = view.sel()[0] 386 | if watch := cs_watch.by_region(view, sel): 387 | watch.toggle() 388 | elif eval := by_region(view, sel): 389 | if eval.status == "exception": 390 | eval.toggle_trace() 391 | elif eval.status == "failure": 392 | eval.toggle_failure() 393 | elif eval.status == "success": 394 | eval.toggle_pprint() 395 | elif eval.status == 'lookup': 396 | eval.erase() 397 | else: 398 | view.run_command("clojure_sublimed_toggle_symbol", {}) 399 | 400 | def is_enabled(self): 401 | return cs_conn.ready(self.view.window()) and len(self.view.sel()) == 1 402 | 403 | class ClojureSublimedClearEvalsCommand(sublime_plugin.TextCommand): 404 | """ 405 | Clear all completed evals in current view 406 | """ 407 | def run(self, edit): 408 | erase_evals(lambda eval: eval.status not in {"pending", "interrupt"}, self.view) 409 | state = cs_common.get_state(self.view.window()) 410 | if (eval := state.status_eval) and eval.status not in {"pending", "interrupt"}: 411 | eval.erase() 412 | cs_watch.erase_watches(view = self.view) 413 | 414 | class ClojureSublimedInterruptEvalCommand(sublime_plugin.TextCommand): 415 | """ 416 | Interrupt first pending eval in current view 417 | """ 418 | def run(self, edit): 419 | es = list(by_status(self.view, 'pending')) 420 | state = cs_common.get_state(self.view.window()) 421 | if (eval := state.status_eval) and eval.status not in {"pending", "interrupt"}: 422 | es += [eval] 423 | if len(es) > 0: 424 | eval = min(es, key = lambda e: e.batch_id) 425 | state = cs_common.get_state(self.view.window()) 426 | state.conn.interrupt(eval.batch_id, eval.id) 427 | for e in es: 428 | if e.batch_id == eval.batch_id: 429 | e.update('interrupt', "Interrupting...") 430 | 431 | def is_enabled(self): 432 | return cs_conn.ready(self.view.window()) 433 | 434 | class EventListener(sublime_plugin.EventListener): 435 | def on_close(self, view): 436 | erase_evals(view = view) 437 | 438 | class TextChangeListener(sublime_plugin.TextChangeListener): 439 | def on_text_changed_async(self, changes): 440 | view = self.buffer.primary_view() 441 | changed = [sublime.Region(x.a.pt, x.b.pt) for x in changes] 442 | def should_erase(eval): 443 | return not (reg := eval.region()) or any(reg.intersects(r) for r in changed) and view.substr(reg) != eval.code 444 | erase_evals(should_erase, view) 445 | 446 | def plugin_unloaded(): 447 | erase_evals() 448 | -------------------------------------------------------------------------------- /cs_eval_status.py: -------------------------------------------------------------------------------- 1 | import sublime, sublime_plugin 2 | from . import cs_common, cs_conn, cs_eval, cs_parser, cs_progress 3 | 4 | status_key = 'clojure-sublimed-eval-status' 5 | 6 | class StatusEval: 7 | """ 8 | Displays 'eval_code' command results in status bar 9 | """ 10 | def __init__(self, code, id = None, batch_id = None): 11 | self.window = sublime.active_window() 12 | state = cs_common.get_state(self.window) 13 | 14 | if state.status_eval: 15 | state.status_eval.erase() 16 | 17 | self.id = id or cs_eval.Eval.next_id() 18 | self.batch_id = batch_id or self.id 19 | self.code = code 20 | self.session = None 21 | self.ex_source = None 22 | self.ex_line = None 23 | self.ex_column = None 24 | self.trace = None 25 | self.on_finish = None 26 | 27 | state.status_eval = self 28 | 29 | self.update('pending', cs_progress.phase()) 30 | cs_progress.wake() 31 | 32 | def update(self, status, value, time_taken = None): 33 | self.status = status 34 | self.value = value 35 | if status in {"pending", "interrupt"}: 36 | cs_common.set_status(self.window, status_key, "⏳ " + self.code) 37 | elif "success" == status: 38 | if time := cs_common.format_time_taken(time_taken): 39 | value = time + ' ' + value 40 | cs_common.set_status(self.window, status_key, "✅ " + value) 41 | elif "failure" == status: 42 | if time := cs_common.format_time_taken(time_taken): 43 | value = time + ' ' + value 44 | cs_common.set_status(self.window, status_key, "❌ " + value) 45 | elif "exception" == status: 46 | if time := cs_common.format_time_taken(time_taken): 47 | value = time + ' ' + value 48 | msg = "❌ " + value 49 | if self.ex_source: 50 | msg += ", at " + self.ex_source 51 | if self.ex_line: 52 | msg += ":" + str(self.ex_line) 53 | if self.ex_column: 54 | msg += ":" + str(self.ex_column) 55 | cs_common.set_status(self.window, status_key, msg) 56 | 57 | def erase(self, interrupt = True): 58 | state = cs_common.get_state(self.window) 59 | cs_common.set_status(self.window, status_key, None) 60 | state.status_eval = None 61 | if interrupt and self.status == "pending" and self.session: 62 | state.conn.interrupt(self.id) 63 | 64 | class ClojureSublimedEvalCodeCommand(sublime_plugin.WindowCommand): 65 | def run(self, code, ns = None): 66 | if (not ns) and (view := cs_common.active_view()): 67 | ns = cs_parser.namespace(view, view.size()) 68 | state = cs_common.get_state(self.window) 69 | state.conn.eval_status(code, ns or 'user') 70 | 71 | def is_enabled(self): 72 | if not cs_conn.ready(self.window): 73 | return False 74 | state = cs_common.get_state(self.window) 75 | if state.status_eval and state.status_eval.status in {'pending', 'interrupt'}: 76 | return False 77 | return True 78 | -------------------------------------------------------------------------------- /cs_indent.py: -------------------------------------------------------------------------------- 1 | import collections, re 2 | import sublime, sublime_plugin 3 | from . import cs_cljfmt, cs_common, cs_parser, cs_printer 4 | 5 | def search_path(node, pos): 6 | """ 7 | Looks for the deepest node that wraps pos (start < pos < end). 8 | Returns full path to that node from the top 9 | """ 10 | res = [node] 11 | for child in node.children: 12 | if child.start < pos < child.end: 13 | res += search_path(child, pos) 14 | elif pos < child.start: 15 | break 16 | return res 17 | 18 | def indent(view, point, parsed = None): 19 | """ 20 | Given point, returns (tag, row, indent) for that line, where indent 21 | is a correct indent based on the last unclosed paren before point. 22 | 23 | Tag could be 'string' (don't change anything, we're inside string), 24 | 'top-level' (set to 0, we are at top level) or 'indent' (normal behaviour) 25 | 26 | Row is row number of the token for which this indent is based on (row of open paren) 27 | """ 28 | parsed = parsed or cs_parser.parse(view.substr(sublime.Region(0, point)) + ' ') 29 | if path := search_path(parsed, point): 30 | node = None 31 | first_form = None 32 | 33 | # try finding unmatched open paren 34 | for child in path[-1].children: 35 | if child.start >= point: 36 | break 37 | if child.name == 'error' and child.text in ['(', '[', '{', '"']: 38 | node = child 39 | first_form = None 40 | elif first_form is None: 41 | first_form = child 42 | 43 | # try indent relative to wrapping paren 44 | if not node: 45 | for n in reversed(path): 46 | if n.name in ['string', 'parens', 'braces', 'brackets']: 47 | node = n 48 | first_form = node.body.children[0] if node.body and node.body.children else None 49 | break 50 | 51 | # top level 52 | if not node: 53 | row, _ = view.rowcol(point) 54 | return ('top-level', row, 0) 55 | 56 | row, col = view.rowcol(node.open.end if node.open else node.end) 57 | offset = 0 58 | if node.name == 'string': 59 | return ('string', row, col) 60 | elif node.name == 'parens' or (node.name == 'error' and node.text == '('): 61 | if first_form and cs_parser.is_symbol(first_form): 62 | offset = 1 63 | else: 64 | offset = 0 65 | return ('indent', row, col + offset) 66 | 67 | def newline_indent(view, point): 68 | return indent(view, point)[2] 69 | 70 | def skip_spaces(view, point): 71 | """ 72 | Starting from point, skips as much spaces as it can without going to the new line, 73 | and returns new point 74 | """ 75 | def is_space(point): 76 | s = view.substr(sublime.Region(point, point + 1)) 77 | return s.isspace() and s not in ['\n', '\r'] 78 | while point < view.size() and is_space(point): 79 | point = point + 1 80 | return point 81 | 82 | def indent_lines(view, selections, edit): 83 | """ 84 | Given set of sorted ranges (`selections`), indents all lines touched by those selections 85 | """ 86 | # Calculate all replacements first 87 | parsed = cs_parser.parse(view.substr(sublime.Region(0, view.size())) + ' ') 88 | replacements = {} # row -> (begin, delta_i) 89 | for sel in selections: 90 | for line in view.lines(sel): 91 | begin = line.begin() 92 | end = skip_spaces(view, begin) 93 | # do not touch empty lines 94 | if end == line.end(): 95 | continue 96 | row, _ = view.rowcol(begin) 97 | type, base_row, i = indent(view, begin, parsed) 98 | # do not re-indent multiline strings 99 | if type == 'string': 100 | continue 101 | # if we moved line before and depend on it, take that into account 102 | _, base_delta_i = replacements.get(base_row, (0, 0)) 103 | delta_i = i - (end - begin) + base_delta_i 104 | if delta_i != 0: 105 | replacements[row] = (begin, delta_i) 106 | 107 | # Now apply all replacements, recalculating begins as we go 108 | delta_total = 0 109 | for row in replacements: 110 | begin, delta_i = replacements[row] 111 | begin = begin + delta_total 112 | delta_total += delta_i 113 | if delta_i < 0: 114 | view.replace(edit, sublime.Region(begin, begin - delta_i), "") 115 | else: 116 | view.replace(edit, sublime.Region(begin, begin), " " * delta_i) 117 | 118 | class ClojureSublimedReindentBufferOnSave(sublime_plugin.EventListener): 119 | def on_pre_save(self, view): 120 | if cs_common.setting("format_on_save", False) and ('Clojure' in view.syntax().name or 'EDN' in view.syntax().name): 121 | view.run_command('clojure_sublimed_reindent_buffer') 122 | 123 | class ClojureSublimedReindentBufferCommand(sublime_plugin.TextCommand): 124 | def run(self, edit): 125 | view = self.view 126 | with cs_common.Measure("Reindent Buffer {} chars", view.size()): 127 | if 'cljfmt' == cs_common.setting('formatter', view = view): 128 | cs_cljfmt.indent_lines(view, [sublime.Region(0, view.size())], edit) 129 | else: 130 | indent_lines(view, [sublime.Region(0, view.size())], edit) 131 | 132 | class ClojureSublimedReindentLinesCommand(sublime_plugin.TextCommand): 133 | def run(self, edit): 134 | view = self.view 135 | with cs_common.Measure("Reindent Lines {} chars", sum([r.size() for r in view.sel()])): 136 | if 'cljfmt' == cs_common.setting('formatter', view = view): 137 | cs_cljfmt.indent_lines(view, view.sel(), edit) 138 | else: 139 | indent_lines(view, view.sel(), edit) 140 | 141 | class ClojureSublimedReindentCommand(sublime_plugin.TextCommand): 142 | def run(self, edit): 143 | view = self.view 144 | if all(r.empty() for r in view.sel()): 145 | view.run_command('clojure_sublimed_reindent_buffer') 146 | else: 147 | view.run_command('clojure_sublimed_reindent_lines') 148 | 149 | class ClojureSublimedPrettyPrintCommand(sublime_plugin.TextCommand): 150 | def run(self, edit): 151 | view = self.view 152 | change_id = view.change_id() 153 | for region in [r for r in view.sel()]: 154 | region = view.transform_region_from(region, change_id) 155 | if region.empty(): 156 | region = cs_parser.topmost_form(view, region.begin()) 157 | form = view.substr(region) 158 | node = cs_parser.parse(form) 159 | formatted = cs_printer.format(form, node, limit = cs_common.wrap_width(view)) 160 | view.replace(edit, region, formatted) 161 | 162 | class ClojureSublimedSelectTopmostFormCommand(sublime_plugin.TextCommand): 163 | def run(self, edit): 164 | view = self.view 165 | sel = view.sel() 166 | for region in [r for r in sel]: 167 | sel.add(cs_parser.topmost_form(view, region.begin())) 168 | 169 | def cljfmt_indent(view, point): 170 | i = None 171 | try: 172 | i = cs_cljfmt.newline_indent(view, point) 173 | except: 174 | pass 175 | return newline_indent(view, point) if i is None else i 176 | 177 | class ClojureSublimedInsertNewlineCommand(sublime_plugin.TextCommand): 178 | def run(self, edit): 179 | view = self.view 180 | newline_indent_fn = cljfmt_indent if 'cljfmt' == cs_common.setting('formatter', view = view) else newline_indent 181 | 182 | # Calculate all replacements first 183 | replacements = [] 184 | for sel in view.sel(): 185 | end = skip_spaces(view, sel.end()) 186 | i = newline_indent_fn(view, sel.begin()) 187 | replacements.append((sublime.Region(sel.begin(), end), "\n" + " " * i)) 188 | 189 | # Now apply them all at once 190 | change_id_sel = view.change_id() 191 | view.sel().clear() 192 | for region, string in replacements: 193 | region = view.transform_region_from(region, change_id_sel) 194 | point = region.begin() + len(string) 195 | view.replace(edit, region, string) 196 | # Add selection at the end of newly inserted region 197 | view.sel().add(sublime.Region(point, point)) 198 | 199 | view.show(view.sel(), show_surrounds = False) 200 | 201 | class ClojureSublimedAlignCursorsCommand(sublime_plugin.TextCommand): 202 | def run(self, edit): 203 | view = self.view 204 | by_row = collections.defaultdict(list) 205 | for region in view.sel(): 206 | row, col = view.rowcol(region.a) 207 | by_row[row].append(region) 208 | # print('by_row', by_row) 209 | cols = max(len(line_regions) for line_regions in by_row.values()) 210 | # print('cols', cols) 211 | change_id = view.change_id() 212 | # upd = lambda r: view.transform_region_from(r, change_id) 213 | for col in range(0, cols): 214 | col_regions = [line_regions[col] for line_regions in by_row.values() if len(line_regions) > col] 215 | col_regions = [view.transform_region_from(r, change_id) for r in col_regions] 216 | col_regions.sort(key = lambda r: view.rowcol(r.a)[0]) 217 | # print('col', col, col_regions) 218 | max_col = max(view.rowcol(r.a)[1] for r in col_regions) 219 | max_len = max(r.size() for r in col_regions) 220 | # print("max_col", max_col, "max_len", max_len) 221 | change_id_2 = view.change_id() 222 | for r in col_regions: 223 | r = view.transform_region_from(r, change_id_2) 224 | _, col = view.rowcol(r.begin()) 225 | length = r.size() 226 | prepend = max_col - col 227 | append = max_len - length 228 | # print("r", r, "col", col, "len", length, "left", max_col - col, "right", max_len - length) 229 | view.replace(edit, sublime.Region(r.begin()), ' ' * prepend) 230 | # r = view.transform_region_from(r, change_id) 231 | # print("new r", r) 232 | view.replace(edit, sublime.Region(r.end() + prepend), ' ' * append) 233 | 234 | 235 | # change_id = view.change_id() 236 | # for region in list(view.sel()): 237 | # region = view.transform_region_from(region, change_id) 238 | # _, col = view.rowcol(region.a) 239 | # view.replace(edit, region, ' ' * (max_col - col)) 240 | -------------------------------------------------------------------------------- /cs_printer.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | def safe_get(l, i, default = None): 4 | """ 5 | Like dict.get(), but for lists 6 | """ 7 | if i < len(l): 8 | return l[i] 9 | else: 10 | return default 11 | 12 | def format_map(text, node, indent, limit): 13 | """ 14 | Puts key-value pairs on separate line each. Aligns keys by longest one: 15 | {:a 1 :bbb 2 :cc 3} => {:a 1 16 | :bbb 2 17 | :cc 3} 18 | """ 19 | res = node.open.text 20 | indent_keys = indent + len(node.open.text) * ' ' 21 | keys = [] 22 | vals = [] 23 | if node.body: 24 | idxs = range(0, len(node.body.children), 2) 25 | keys = [node.body.children[i] for i in idxs] 26 | vals = [safe_get(node.body.children, i + 1) for i in idxs] 27 | key_strings = [format(text, k, indent_keys, limit) for k in keys] 28 | longest_key = max(len(ks) for ks in key_strings) if key_strings else 0 29 | indent_vals = indent_keys + longest_key * ' ' + ' ' 30 | for i, ks, v in zip(range(0, len(keys)), key_strings, vals): 31 | if i > 0: 32 | res += '\n' + indent_keys 33 | res += ks 34 | if v is not None: 35 | vs = format(text, v, indent_keys, limit) 36 | if '\n' in vs: 37 | res += '\n' + indent_keys + vs 38 | elif len(indent_keys) + longest_key + 1 + len(vs) <= limit: 39 | res += (longest_key - len(ks)) * ' ' + ' ' + vs 40 | elif len(indent_keys) + len(ks) + 1 + len(vs) <= limit: 41 | res += ' ' + vs 42 | else: 43 | res += '\n' + indent_keys + vs 44 | if node.close: 45 | res += node.close.text 46 | return res 47 | 48 | def format_list(text, node, indent, limit): 49 | """ 50 | Everythin list-like: (...), [...], #{...} 51 | Puts as many children as it can on a line, then starts new one. 52 | """ 53 | indent_children = indent + (len(node.open.text) * ' ') 54 | res = node.open.text 55 | force_newline = False 56 | is_first = True 57 | if node.body: 58 | for i, child in enumerate(node.body.children): 59 | if force_newline: 60 | res += '\n' + indent_children 61 | is_first = True 62 | 63 | child_str = format(text, child, indent_children, limit) 64 | if '\n' in child_str or child.name in {'brackets', 'parens', 'braces'} or len(child_str) > limit / 3: 65 | if not is_first: 66 | res += '\n' + indent_children 67 | res += child_str 68 | force_newline = True 69 | is_first = True 70 | continue 71 | last_line = res[res.rfind('\n') + 1:] 72 | separator = '' if is_first else ' ' 73 | if len(last_line) + len(separator) + len(child_str) > limit: 74 | res += '\n' + indent_children + child_str 75 | else: 76 | res += separator + child_str 77 | force_newline = False 78 | is_first = False 79 | close = node.close.text if node.close else '' 80 | res += close 81 | return res 82 | 83 | def format_tagged(text, node, indent, limit): 84 | """ 85 | #tag 86 | """ 87 | tag_string = format(text, node.tag, indent, limit) 88 | res = '#' + tag_string 89 | if node.body: 90 | value_indent = indent + ' ' + len(tag_string) * ' ' + ' ' 91 | res += ' ' + format(text, node.body.children[0], value_indent, limit) 92 | return res 93 | 94 | def wrap_string(s, limit = 80, indent = ''): 95 | space = limit - len(indent) 96 | length = len(s) 97 | if length <= space: 98 | return s 99 | if space < 10: 100 | return s 101 | res = "" 102 | for start in range(0, length, space): 103 | end = min(start + space, length) 104 | if start > 0: 105 | res += '\n' + indent 106 | res += s[start:end] 107 | return res 108 | 109 | def format(text, node, indent = '', limit = 80): 110 | """ 111 | Given text and its parsed AST as node, returns formatted (pretty-printed) string of that node 112 | """ 113 | if node.name == 'source': 114 | return '\n'.join(format(text, n, '', limit) for n in node.children) 115 | elif node.name == 'braces' and node.open.text != '#{': 116 | return format_map(text, node, indent, limit) 117 | elif node.name in {'parens', 'brackets', 'braces'}: 118 | return format_list(text, node, indent, limit) 119 | elif node.name == 'tagged': 120 | return format_tagged(text, node, indent, limit) 121 | else: 122 | str = text[node.start:node.end] 123 | str = re.sub("(? 1: 23 | self.start() 24 | else: 25 | self.stop() 26 | 27 | def phase(self): 28 | return self.phases[self.phase_idx] 29 | 30 | def run_loop(self): 31 | thread.update_phases(cs_common.setting("progress_phases"), cs_common.setting("progress_interval_ms")) 32 | while True: 33 | if not self.running: 34 | break 35 | time.sleep(self.interval / 1000.0) 36 | updated = False 37 | if (window := sublime.active_window()) and (view := window.active_view()): 38 | for eval in cs_eval.by_status(view, 'pending'): 39 | eval.update(eval.status, self.phase()) 40 | updated = True 41 | if updated: 42 | self.phase_idx = (self.phase_idx + 1) % len(self.phases) 43 | else: 44 | with self.condition: 45 | self.condition.wait() 46 | 47 | def start(self): 48 | if not self.running: 49 | self.running = True 50 | threading.Thread(daemon=True, target=self.run_loop).start() 51 | 52 | def wake(self): 53 | if self.running: 54 | with self.condition: 55 | self.condition.notify_all() 56 | 57 | def stop(self): 58 | self.running = False 59 | with self.condition: 60 | self.condition.notify_all() 61 | 62 | thread = ProgressThread() 63 | 64 | def phase(): 65 | return thread.phase() 66 | 67 | def wake(): 68 | thread.wake() 69 | 70 | class EventListener(sublime_plugin.EventListener): 71 | def on_activated_async(self, view): 72 | """ 73 | On active view change 74 | """ 75 | thread.wake() 76 | 77 | def on_settings_change(): 78 | thread.update_phases(cs_common.setting("progress_phases"), cs_common.setting("progress_interval_ms")) 79 | 80 | def plugin_loaded(): 81 | cs_common.on_settings_change(__name__, on_settings_change) 82 | 83 | def plugin_unloaded(): 84 | thread.stop() 85 | cs_common.clear_settings_change(__name__) 86 | -------------------------------------------------------------------------------- /cs_warn.py: -------------------------------------------------------------------------------- 1 | import sublime, sublime_plugin 2 | from . import cs_common 3 | 4 | status_key = 'clojure-sublimed-warn-status' 5 | 6 | def add_warning(window): 7 | state = cs_common.get_state(window) 8 | state.warnings += 1 9 | suffix = 's' if state.warnings > 0 else '' 10 | cs_common.set_status(window, status_key, f'⚠️ {state.warnings} warning{suffix}') 11 | 12 | def reset_warnings(window): 13 | state = cs_common.get_state(window) 14 | state.warnings = 0 15 | cs_common.set_status(window, status_key, None) -------------------------------------------------------------------------------- /cs_watch.py: -------------------------------------------------------------------------------- 1 | import collections, re, sublime, sublime_plugin 2 | from . import cs_common, cs_conn, cs_parser, cs_printer 3 | 4 | watches = {} # Dict[int, Watch] 5 | watches_by_view = collections.defaultdict(dict) # Dict[int, Dict[int, Watch]] 6 | 7 | class Watch: 8 | last_id: int = 0 9 | 10 | def next_id(): 11 | Watch.last_id += 1 12 | return Watch.last_id 13 | 14 | def region_key(self): 15 | return f"{cs_common.ns}.watch-{self.id}" 16 | 17 | def value(self): 18 | return self.values[-1] if len(self.values) > 0 else None 19 | 20 | def update_region(self): 21 | regions = self.view.get_regions(self.region_key()) 22 | if regions and len(regions) >= 1: 23 | self.region = regions[0] 24 | 25 | def __init__(self, view, region): 26 | self.id = Watch.next_id() 27 | self.view = view 28 | self.region = region 29 | self.values = collections.deque(maxlen = 10) 30 | self.phantom_id = None 31 | watches[self.id] = self 32 | watches_by_view[view.id()][self.id] = self 33 | self.update(recursive = False) 34 | 35 | def __lt__(self, other): 36 | return self.region.begin() < other.region.begin() 37 | 38 | def update(self, value = None, recursive = True): 39 | view = self.view 40 | if value is not None: 41 | self.values.append(value) 42 | scope, color = cs_common.scope_color(self.view, 'watch') 43 | view.erase_regions(self.region_key()) 44 | 45 | line = view.line(self.region) 46 | same_line_watches = list(w for w in watches_by_view[view.id()].values() if view.line(w.region) == line) 47 | same_line_watches.sort() 48 | if same_line_watches[0] == self: 49 | display = " · ".join(cs_common.escape(w.value()) for w in same_line_watches if w.value()) 50 | self.view.add_regions( 51 | key = self.region_key(), 52 | regions = [self.region], 53 | scope = scope, 54 | flags = sublime.DRAW_NO_FILL + sublime.NO_UNDO, 55 | annotations = [display] if display else [], 56 | annotation_color = color if display else '' 57 | ) 58 | else: 59 | self.view.add_regions( 60 | key = self.region_key(), 61 | regions = [self.region], 62 | scope = scope, 63 | flags = sublime.DRAW_NO_FILL + sublime.NO_UNDO 64 | ) 65 | if recursive: 66 | same_line_watches[0].update() 67 | 68 | def erase(self): 69 | self.view.erase_regions(self.region_key()) 70 | if self.phantom_id: 71 | self.view.erase_phantom_by_id(self.phantom_id) 72 | del watches[self.id] 73 | del watches_by_view[self.view.id()][self.id] 74 | 75 | def toggle(self): 76 | if self.value() is None: 77 | return 78 | 79 | if self.phantom_id: 80 | self.view.erase_phantom_by_id(self.phantom_id) 81 | self.phantom_id = None 82 | return 83 | 84 | limit = cs_common.wrap_width(self.view) 85 | 86 | string = "" 87 | for index, value in enumerate(reversed(self.values)): 88 | node = cs_parser.parse(value) 89 | prefix = f" i-{index}" if index > 0 else "last" 90 | string += f"{prefix}: {cs_printer.format(value, node, limit = limit)}\n" 91 | 92 | styles = """ 93 | .light body { background-color: hsl(285, 100%, 90%); } 94 | .dark body { background-color: hsl(285, 100%, 10%); } 95 | """ 96 | if phantom_styles := cs_common.phantom_styles(self.view, "phantom_success"): 97 | styles += f".light body, .dark body {{ { phantom_styles }; border: 4px solid #CC33CC; }}" 98 | 99 | body = f""" 100 | { cs_common.basic_styles(self.view) } 101 | { styles } 102 | """ 103 | 104 | for line in string.splitlines(): 105 | line = cs_printer.wrap_string(line, limit = limit) 106 | line = cs_common.escape(line) 107 | body += "

" + line + "

" 108 | body += "" 109 | point = self.view.line(self.region.end()).begin() 110 | self.phantom_id = self.view.add_phantom( 111 | key = self.region_key(), 112 | region = sublime.Region(point, point), 113 | content = body, 114 | layout = sublime.LAYOUT_BLOCK 115 | ) 116 | 117 | def on_watch(id, value): 118 | if w := watches.get(id): 119 | w.update(value) 120 | 121 | def erase_watches(predicate = lambda x: True, view = None): 122 | if view: 123 | to_erase = list(w for w in watches_by_view[view.id()].values() if predicate(w)) 124 | else: 125 | to_erase = list(w for w in watches.values() if predicate(w)) 126 | for w in to_erase: 127 | w.erase() 128 | 129 | def by_region(view, region): 130 | for w in watches_by_view[view.id()].values(): 131 | if cs_common.regions_touch(w.region, region): 132 | return w 133 | 134 | def transform(view): 135 | def transform_impl(code, **kwargs): 136 | region = kwargs['eval_region'] 137 | watches = list(w for w in watches_by_view[view.id()].values() if region.contains(w.region)) 138 | watches.sort() 139 | pos = region.begin() 140 | res = '' 141 | for w in watches: 142 | w_region = w.region 143 | res += view.substr(sublime.Region(pos, w_region.begin())) 144 | res += "(clojure-sublimed.socket-repl/watch " + str(w.id) + " " 145 | res += view.substr(w_region) 146 | res += ")" 147 | pos = w_region.end() 148 | res += view.substr(sublime.Region(pos, region.end())) 149 | return res 150 | return transform_impl 151 | 152 | class ClojureSublimedAddWatchCommand(sublime_plugin.TextCommand): 153 | def run(self, edit): 154 | view = self.view 155 | window = view.window() 156 | sel = view.sel()[0] 157 | if ws := list(w for w in watches_by_view[view.id()].values() if w.region == sel): 158 | ws[0].erase() 159 | else: 160 | erase_watches(lambda w: w.region.intersects(sel), view) 161 | top = cs_parser.topmost_form(view, sel.begin()) 162 | state = cs_common.get_state(window) 163 | watch = Watch(view, sel) 164 | state.conn.eval(view, [top]) 165 | 166 | def is_enabled(self): 167 | view = self.view 168 | window = view.window() 169 | sel = view.sel()[0] 170 | state = cs_common.get_state(window) 171 | return bool(state.conn \ 172 | and state.conn.ready() \ 173 | and type(state.conn).__name__ == 'ConnectionSocketRepl' \ 174 | and len(view.sel()) == 1 \ 175 | and not sel.empty()) 176 | 177 | class EventListener(sublime_plugin.EventListener): 178 | def on_pre_close(self, view): 179 | erase_watches(view = view) 180 | 181 | class TextChangeListener(sublime_plugin.TextChangeListener): 182 | def on_text_changed_async(self, changes): 183 | view = self.buffer.primary_view() 184 | changed = [sublime.Region(x.a.pt, x.b.pt) for x in changes] 185 | 186 | def should_erase(w): 187 | return any(w.region.intersects(r) for r in changed) 188 | erase_watches(should_erase, view) 189 | 190 | lines = list(view.line(r) for r in changed) 191 | def should_update(w): 192 | return any(r.contains(w.region.begin()) for r in lines) 193 | need_update = list(w for w in watches_by_view[view.id()].values() if should_update(w)) 194 | 195 | for w in watches_by_view[view.id()].values(): 196 | w.update_region() 197 | 198 | for w in need_update: 199 | w.update(recursive = False) 200 | 201 | def plugin_unloaded(): 202 | erase_watches() 203 | -------------------------------------------------------------------------------- /docs/protocol_socket.md: -------------------------------------------------------------------------------- 1 | # Upgraded Socket REPL protocol 2 | 3 | All messages are: 4 | 5 | - EDN-formatted, 6 | - No keywords or symbols, only strings and ints, 7 | - '\n' inside strings escaped, 8 | - no newlines inside messages, 9 | - newline after each message. 10 | 11 | --- 12 | 13 | ``` 14 | RCV {"tag" "started"} 15 | ``` 16 | 17 | When client receives this message, REPL has finished upgrading and is ready to accept commands. 18 | 19 | --- 20 | 21 | ``` 22 | SND {"op" => "eval" 23 | "id" => any, id 24 | "code" => string. Code to evaluate 25 | "ns" => string, optional. Namespace name. Defaults to user 26 | "file" => string, optional. File name where this code comes from. Defaults to NO_SOURCE_FILE 27 | "line" => int, optional. Line in file 28 | "column" => int, optional. Column position of first code character, 0-based} 29 | ``` 30 | 31 | Evaluate form. If `code` contains multiple top-level forms, they are evaluated sequentially. After each successful evaluation, you get this: 32 | 33 | ``` 34 | RCV {"tag" => "ret" 35 | "id" => any, id 36 | "idx" => int, sequential number of form in original `code` 37 | "val" => string, pr-str value 38 | "time" => int, execution time, ms 39 | "form" => string, form that being evaluated 40 | "from_line" => int, line at the beginning of the form 41 | "from_column" => int, column at the beginning of the form 42 | "to_line" => int, line at the end of the form 43 | "to_column" => int, column at the end of the form} 44 | ``` 45 | 46 | Value string will be truncated after 1024 characters, so don’t rely on it be valid readable Clojure. 47 | 48 | If some form fails, you get this and batch execution stops: 49 | 50 | ``` 51 | RCV {"tag" => "ex" 52 | "id" => any. Id 53 | "val" => string. Error messasge 54 | "trace" => multiline string. Stacktrace 55 | "source" => string, if known. File name 56 | "line" => int, if known 57 | "column" => int, 0-based, if known 58 | "form", "from_"/"to_" "_line"/"_column" => same as in "ret"} 59 | ``` 60 | 61 | Finally, success of failure, you’ll always recieve this: 62 | 63 | ``` 64 | {"tag" => "done" 65 | "id" => any. Id} 66 | ``` 67 | 68 | --- 69 | 70 | To interrupt evaluation, send this: 71 | 72 | ``` 73 | SND {"op" => "interrupt" 74 | "id" => any} 75 | ``` 76 | 77 | Whole batch will be stopped by throwing an exception. You’ll receive :ex and :done after this 78 | 79 | --- 80 | 81 | To look up a symbol, send this: 82 | 83 | ``` 84 | SND {"op" => "lookup" 85 | "id" => any 86 | "symbol" => string 87 | "ns" => string, optional, defaults to user} 88 | ``` 89 | 90 | To which you’ll get either 91 | 92 | ``` 93 | RCV {"tag" => "lookup" 94 | "id" => any 95 | "val" => map, description} 96 | ``` 97 | 98 | Val map looks like this for functions: 99 | 100 | ``` 101 | {"ns" "clojure.core" 102 | "name" "str" 103 | "arglists" "([] [x] [x & ys])" 104 | "doc" "With no args, returns the empty string. With one arg x, returns\n x.toString(). (str nil) returns the empty string. With more than\n one arg, returns the concatenation of the str values of the args." 105 | "file" "clojure/core.clj" 106 | "line" "546" 107 | "column" "1" 108 | "added" "1.0"} 109 | ``` 110 | 111 | for vars: 112 | 113 | ``` 114 | {"ns" "clojure.core" 115 | "name" "*warn-on-reflection*" 116 | "doc" "When set to true, the compiler will emit warnings when reflection is\n needed to resolve Java method calls or field accesses.\n\n Defaults to false." 117 | "added" "1.0"} 118 | ``` 119 | 120 | and for special forms: 121 | 122 | ``` 123 | {"ns" "clojure.core" 124 | "name" "do" 125 | "forms" "[(do exprs*)]" 126 | "doc" "Evaluates the expressions in order and returns the value of\n the last. If no expressions are supplied, returns nil." 127 | "file" "clojure/core.clj" 128 | "special-form" "true"} 129 | ``` 130 | 131 | If symbol can’t be found, you’ll get: 132 | 133 | ``` 134 | RCV {"tag" => "ex" 135 | "id" => any, id 136 | "val" => string, message} 137 | ``` 138 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install.txt", 3 | "2.4.0": "messages/2.4.0.txt" 4 | } -------------------------------------------------------------------------------- /messages/2.4.0.txt: -------------------------------------------------------------------------------- 1 | Clojure Sublimed now includes optional support for [Simple Clojure Formatting rules](https://tonsky.me/blog/clojurefmt/). It doesn’t require nREPL connection but does require `Clojure (Sublimed)` syntax to be selected for buffer. 2 | 3 | To reformat whole file, run `Clojure Sublimed: Reindent Buffer`. 4 | 5 | To reindent only current line(s), run `Clojure Sublimed: Reindent Lines`. 6 | 7 | To enable correct indentations as you type code, rebind `Enter` to `Clojure Sublimed: Insert Newline`: 8 | 9 | ``` 10 | {"keys": ["enter"], 11 | "command": "clojure_sublimed_insert_newline", 12 | "context": [{"key": "selector", "operator": "equal", "operand": "source.edn | source.clojure"}, 13 | {"key": "auto_complete_visible", "operator": "equal", "operand": false}, 14 | {"key": "panel_has_focus", "operator": "equal", "operand": false}]} 15 | ``` 16 | 17 | Best way to do it is through running `Preferences: Clojure Sublimed Key Bindings`. 18 | 19 | For full changelog, see https://github.com/tonsky/Clojure-Sublimed/blob/master/CHANGELOG.md -------------------------------------------------------------------------------- /messages/2.8.0.txt: -------------------------------------------------------------------------------- 1 | Clojure Sublimed now includes support for Shadow CLJS thanks to @sainadh-d. To use it, select `Clojure Sublimed: Connect shadow-cljs` command -------------------------------------------------------------------------------- /messages/2.9.0.txt: -------------------------------------------------------------------------------- 1 | Clojure Sublimed now includes support for UNIX domain sockets for nREPL, thanks to @tribals. To use it, select `Clojure Sublimed: Connect` command and specify absolute filesystem path to the socket file instead of host:port pair -------------------------------------------------------------------------------- /messages/3.0.0.txt: -------------------------------------------------------------------------------- 1 | Clojure Sublimed just updated to 3.0.0! It’s a huge rewrite with many exciting features: 2 | 3 | - REPL doesn’t depend on syntax highlighting anymore. 4 | - On top of JVM nREPL and ShadowCLJS nREPL, we now support Raw nREPL (no extra middlewares, so less quality, but should work anywhere) and JVM Socket REPL (works on core Clojure with 0 dependencies). 5 | - It is now much easier to add new REPLs. Contributions welcome :) 6 | - Pretty-printer now works client-side, same on every REPL. 7 | - Indenter and formatter work much faster now and do not require setting `Clojure (Sublimed)` syntax. 8 | 9 | Let me know if anything breaks. If you were using JVM nREPL, I recommend switching to Socket REPL as it has better support in Clojure Sublimed, faster startup and brighter future. 10 | 11 | Happy Clojure-ing! 12 | 13 | Best, 14 | Nikita. -------------------------------------------------------------------------------- /messages/3.4.0.txt: -------------------------------------------------------------------------------- 1 | Clojure Sublimed now supports: 2 | 3 | - a separate connection per window (was one global connection) 4 | - `.repl-port` files for Socket REPL, similar to `.nrepl-port` for nREPL -------------------------------------------------------------------------------- /messages/3.6.0.txt: -------------------------------------------------------------------------------- 1 | Clojure Sublimed now allows you to create custom commands that transform code before sending it to eval. 2 | 3 | For example, this will pretty-print result of your evaluation to stdout: 4 | 5 | ``` 6 | {"keys": ["ctrl+p"], 7 | "command": "clojure_sublimed_eval", 8 | "args": {"transform": "(doto %code clojure.pprint/pprint)"}} 9 | ``` 10 | 11 | `transform` is a format string that takes selected form, formats it according to described rules and then sends resulting code to evaluation. 12 | 13 | If you now press `ctrl+p` on a form like `(+ 1 2)`, the actual eval sent to REPL will be: 14 | 15 | ``` 16 | (doto (+ 1 2) clojure.pprint/pprint) 17 | ``` 18 | 19 | Which will pretty-print evaluation result to stdout. This pattern might be useful for large results that don’t fit inline. 20 | 21 | Another use-case might be “eval to buffer”: 22 | 23 | ``` 24 | {"keys": ["ctrl+b"], 25 | "command": "chain", 26 | "args": { 27 | "commands": [ 28 | ["clojure_sublimed_eval", {"transform": "(with-open [w (clojure.java.io/writer \"/tmp/sublimed_output.edn\")] (doto %code (clojure.pprint/pprint w)))"}], 29 | ["open_file", {"file": "/tmp/sublimed_output.edn"}] 30 | ] 31 | } 32 | } 33 | ``` 34 | 35 | Inside `transform` you can also use `%ns` (current ns) and `%symbol` (if selected form is `def`-something, it will be replaced with defined name, otherwise `nil`). 36 | 37 | This allows us to implement “run test under cursor”: 38 | 39 | ``` 40 | {"keys": ["super+shift+t"], 41 | "command": "clojure_sublimed_eval", 42 | "args": {"transform": "(clojure.test/run-test-var #'%symbol)"}} 43 | ``` 44 | 45 | Enjoy! 46 | -------------------------------------------------------------------------------- /messages/3.7.0.txt: -------------------------------------------------------------------------------- 1 | New feature in Clojure Sublimed: Watches! 2 | 3 | Watches are great alternative to debug prints: they allow you to monitor intermediate values during function execution right in the editor. 4 | 5 | This is how they work: 6 | 7 | - Select a right-hand expression 8 | - Run `Clojure Sublimed: Add Watch` command 9 | - Now every time function is executed, for any reason, watched expressions will display values they evaluate to, in real time. 10 | 11 | For now watches are only supported in Socket REPL. 12 | 13 | Enjoy! 14 | -------------------------------------------------------------------------------- /messages/4.0.0.txt: -------------------------------------------------------------------------------- 1 | Clojure Sublimed 4.0.0 2 | ---------------------- 3 | 4 | Two major new features: 5 | 6 | # Code formatting 7 | 8 | By default, Clojure Sublimed uses [Better Clojure Formatting style](https://tonsky.me/blog/clojurefmt/). 9 | 10 | Starting with 4.0.0, you can switch to `cljftm` instead: 11 | 12 | - Download `cljfmt` binary from `https://github.com/weavejester/cljfmt/releases/latest` 13 | - Add `cljfmt` to `$PATH` 14 | - In Sublime Text, open `Preferences: Settings` 15 | - Add `"clojure_sublimed_formatter": "cljfmt"` 16 | 17 | # Color scheme 18 | 19 | 4.0.0 ships major improvement in syntax definitons for Clojure. 20 | 21 | To make best use of them, we now offer color scheme that makes use of many of these features. 22 | 23 | - Cmd/Ctrl + Shift + P (Command Palette) 24 | - `UI: Select Color Scheme` 25 | - Select `Auto` -> `Clojure Sublimed Light` -> `Clojure Sublimed Dark` 26 | 27 | These color schemes will work for other languages, too. -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | Hi there! Thank you for installing Clojure Sublimed. 2 | 3 | How to get started: 4 | 5 | 6 | Associate syntax with *.clj* files (only needs to be done once) 7 | --------------------------------------------------------------- 8 | 9 | For each file type (.clj, .cljs, .cljc, .edn) do: 10 | 11 | - Open any file with that extension 12 | - Go to menu -> `View` → `Syntax` → `Open all with current extension as...` 13 | - Select `Clojure (Sublimed)` 14 | 15 | 16 | Key bindings 17 | ------------ 18 | 19 | Sublime has no good way to ship optional key bindings with plugin. So 20 | 21 | - Cmd/Ctrl + Shift + P (Command Palette) 22 | - `Preferences: Clojure Sublimed Key Bindings` 23 | - Copy examples from the left to your config on the right 24 | 25 | I recomment at least: 26 | 27 | - Evaluate 28 | - Evaluate Buffer 29 | - Interrupt Pending Evaluations 30 | - Clear Evaluation Results 31 | - Reindent 32 | - Insert New Line 33 | 34 | 35 | Code formatting 36 | --------------- 37 | 38 | By default, Clojure Sublimed uses [Better Clojure Formatting style](https://tonsky.me/blog/clojurefmt/). 39 | 40 | If you want to use `cljftm`: 41 | 42 | - Download `cljfmt` binary from `https://github.com/weavejester/cljfmt/releases/latest` 43 | - Add `cljfmt` to `$PATH` 44 | - In Sublime Text, open `Preferences: Settings` 45 | - Add `"clojure_sublimed_formatter": "cljfmt"` 46 | 47 | Color scheme 48 | ------------ 49 | 50 | If you want to try our color scheme: 51 | 52 | - Cmd/Ctrl + Shift + P (Command Palette) 53 | - `UI: Select Color Scheme` 54 | - Select `Auto` -> `Clojure Sublimed Light` -> `Clojure Sublimed Dark` 55 | 56 | These color schemes will work for other languages, too. 57 | 58 | 59 | Running your app 60 | ---------------- 61 | 62 | Clojure Sublimed will not run your app for you. A few alternatives instead: 63 | 64 | - Use separate terminal app 65 | - Terminus plugin 66 | - Sublime Executor plugin 67 | 68 | 69 | Connecting to REPLs 70 | ------------------- 71 | 72 | Depending on what type of REPL your run, use following commands: 73 | 74 | - JVM nREPL → `Clojure Sublimed: Connect to nREPL JVM` 75 | - Shadow CLJS client-side → `Clojure Sublimed: Connect to shadow-cljs` 76 | - Shadow CLJS server-side → `Clojure Sublimed: Connect to raw nREPL` 77 | - Socket REPL on JVM → `Clojure Sublimed: Connect to Socket REPL` 78 | - Any other nREPL (babashka, sci, ...) → `Clojure Sublimed: Connect to raw nREPL` 79 | 80 | Clojure Sublimed can only run one REPL connection per window. 81 | 82 | Minimal supported nREPL version is 0.9. 83 | 84 | Read more at https://github.com/tonsky/Clojure-Sublimed/blob/master/README.md -------------------------------------------------------------------------------- /src_clojure/clojure_sublimed/core.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-sublimed.core 2 | (:require 3 | [clojure.spec.alpha :as spec] 4 | [clojure.string :as str]) 5 | (:import 6 | [clojure.lang Compiler Compiler$CompilerException ExceptionInfo LispReader$ReaderException] 7 | [java.io BufferedWriter OutputStream OutputStreamWriter PrintWriter Writer])) 8 | 9 | (def ^:dynamic *print-quota* 10 | 4096) 11 | 12 | (def quota-marker 13 | {}) 14 | 15 | (defn- to-char-array ^chars [x] 16 | (cond 17 | (string? x) (.toCharArray ^String x) 18 | (integer? x) (char-array [(char x)]) 19 | :else x)) 20 | 21 | ;; modified from nrepl.middleware.print/with-quota-writer 22 | (defn bounded-writer 23 | "java.io.Writer that wraps throws once it has written more than `quota` bytes" 24 | ^Writer [^Writer writer quota] 25 | (let [total (volatile! 0)] 26 | (proxy [Writer] [] 27 | (toString [] 28 | (.toString writer)) 29 | (write 30 | ([x] 31 | (let [cbuf (to-char-array x)] 32 | (.write ^Writer this cbuf (int 0) (count cbuf)))) 33 | ([x off len] 34 | (locking total 35 | (let [cbuf (to-char-array x) 36 | rem (- quota @total)] 37 | (vswap! total + len) 38 | (.write writer cbuf ^int off ^int (min len rem)) 39 | (when (neg? (- rem len)) 40 | (throw (ex-info "Quota Exceeded" quota-marker))))))) 41 | (flush [] 42 | (.flush writer)) 43 | (close [] 44 | (.close writer))))) 45 | 46 | (defn bounded-pr-str [x] 47 | (let [writer (if (> *print-quota* 0) 48 | (bounded-writer (java.io.StringWriter.) *print-quota*) 49 | (java.io.StringWriter.))] 50 | (try 51 | (binding [*out* writer] 52 | (pr x)) 53 | (str writer) 54 | (catch clojure.lang.ExceptionInfo e 55 | (if (identical? quota-marker (ex-data e)) 56 | (str writer "...") 57 | (throw e)))))) 58 | 59 | (defn duplicate-writer ^Writer [^Writer writer tag out-fn] 60 | (let [sb (StringBuffer.) 61 | proxy (proxy [Writer] [] 62 | (flush [] 63 | (.flush writer) 64 | (let [len (.length sb)] 65 | (when (pos? len) 66 | (out-fn {"tag" tag, "val" (str sb)}) 67 | (.delete sb 0 len)))) 68 | (close [] 69 | (.close writer)) 70 | (write 71 | ([x] 72 | (let [cbuf (to-char-array x)] 73 | (.write writer cbuf) 74 | (.append sb cbuf))) 75 | ([x off len] 76 | (let [cbuf (to-char-array x)] 77 | (.write writer cbuf ^int off ^int len) 78 | (.append sb cbuf ^int off ^int len)))))] 79 | (PrintWriter. proxy true))) 80 | 81 | ;; CompilerException has location info, but its cause RuntimeException has the message ¯\_(ツ)_/¯ 82 | (defn root-cause [^Throwable t] 83 | (loop [t t 84 | data nil] 85 | (if (and 86 | (nil? data) 87 | (or 88 | (instance? Compiler$CompilerException t) 89 | (instance? LispReader$ReaderException t)) 90 | (not= [0 0] ((juxt :clojure.error/line :clojure.error/column) (ex-data t)))) 91 | (recur t (ex-data t)) 92 | (if-some [cause (some-> t .getCause)] 93 | (recur cause data) 94 | (if data 95 | (ExceptionInfo. "Wrapper to pass CompilerException ex-data" data t) 96 | t))))) 97 | 98 | (defn duplicate? [^StackTraceElement prev-el ^StackTraceElement el] 99 | (and 100 | (= (.getClassName prev-el) (.getClassName el)) 101 | (= (.getFileName prev-el) (.getFileName el)) 102 | (= "invokeStatic" (.getMethodName prev-el)) 103 | (#{"invoke" "doInvoke"} (.getMethodName el)))) 104 | 105 | (defn clear-duplicates [els] 106 | (for [[prev-el el] (map vector (cons nil els) els) 107 | :when (or (nil? prev-el) (not (duplicate? prev-el el)))] 108 | el)) 109 | 110 | (defn trace-element [^StackTraceElement el] 111 | (let [file (.getFileName el) 112 | clojure? (or (nil? file) 113 | (= file "NO_SOURCE_FILE") 114 | (.endsWith file ".clj") 115 | (.endsWith file ".cljc"))] 116 | {:method (if clojure? 117 | (Compiler/demunge (.getClassName el)) 118 | (str (.getClassName el) "." (.getMethodName el))) 119 | :file (.getFileName el) 120 | :line (.getLineNumber el)})) 121 | 122 | (defn as-table [table] 123 | (let [[method file] (for [col [:method :file]] 124 | (->> table 125 | (map #(get % col)) 126 | (map str) 127 | (map count) 128 | (reduce max (count "null")))) 129 | format-str (str "\t%-" method "s\t%-" file "s\t:%d")] 130 | (->> table 131 | (map #(format format-str (:method %) (:file %) (:line %))) 132 | (str/join "\n")))) 133 | 134 | (defn trace-str 135 | ([t] 136 | (trace-str t nil)) 137 | ([^Throwable t opts] 138 | (let [{:clojure.error/keys [source line column]} (ex-data t) 139 | cause (or (.getCause t) t)] 140 | (str 141 | (->> (.getStackTrace cause) 142 | (take-while #(not (#{"clojure.lang.Compiler" "clojure.lang.LispReader"} 143 | (.getClassName ^StackTraceElement %)))) 144 | (remove #(#{"clojure.lang.RestFn" "clojure.lang.AFn"} (.getClassName ^StackTraceElement %))) 145 | (clear-duplicates) 146 | (map trace-element) 147 | (reverse) 148 | (as-table)) 149 | "\n>>> " 150 | (.getSimpleName (class cause)) 151 | ": " 152 | (.getMessage cause) 153 | (when (:location? opts true) 154 | (when (or source line column) 155 | (str " (" source ":" line ":" column ")"))) 156 | (when-some [data (ex-data cause)] 157 | (str " " (bounded-pr-str data))))))) 158 | 159 | ;; Allow dynamic vars to be set in root thread when changed in spawned threads 160 | 161 | (def settable-vars 162 | [#'*ns* 163 | #'*warn-on-reflection* 164 | #'*math-context* 165 | #'*print-meta* 166 | #'*print-length* 167 | #'*print-level* 168 | #'*print-namespace-maps* 169 | #'*data-readers* 170 | #'*default-data-reader-fn* 171 | #'*compile-path* 172 | #'*command-line-args* 173 | #'*unchecked-math* 174 | #'*assert* 175 | #'spec/*explain-out*]) 176 | 177 | (def ^:dynamic *changed-vars) 178 | 179 | (defn track-vars* [vars on-change body] 180 | (let [before (persistent! 181 | (reduce #(assoc! %1 %2 @%2) 182 | (transient {}) 183 | vars))] 184 | (push-thread-bindings before) 185 | (try 186 | (body) 187 | (finally 188 | (doseq [var vars 189 | :let [val @var] 190 | :when (not= val (before var))] 191 | (on-change var val)) 192 | (pop-thread-bindings))))) 193 | 194 | (defmacro track-vars [& body] 195 | `(track-vars* 196 | settable-vars 197 | (fn [var# val#] 198 | (swap! *changed-vars assoc var# val#)) 199 | (fn [] ~@body))) 200 | 201 | (defn set-changed-vars! [] 202 | (let [[vars _] (reset-vals! *changed-vars {})] 203 | (doseq [[var val] vars] 204 | (.set ^clojure.lang.Var var val)))) 205 | -------------------------------------------------------------------------------- /src_clojure/clojure_sublimed/middleware.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-sublimed.middleware 2 | (:require 3 | [clojure.string :as str] 4 | [clojure-sublimed.core :as core] 5 | [nrepl.middleware :as middleware] 6 | [nrepl.middleware.print :as print] 7 | [nrepl.middleware.caught :as caught] 8 | [nrepl.middleware.session :as session] 9 | [nrepl.transport :as transport]) 10 | (:import 11 | [nrepl.transport Transport])) 12 | 13 | (defn on-send [{:keys [transport] :as msg} on-send] 14 | (assoc msg :transport 15 | (reify Transport 16 | (recv [this] 17 | (transport/recv transport)) 18 | (recv [this timeout] 19 | (transport/recv transport timeout)) 20 | (send [this resp] 21 | (when-some [resp' (on-send resp)] 22 | (transport/send transport resp')) 23 | this)))) 24 | 25 | (defn after-send [{:keys [transport] :as msg} after-send] 26 | (assoc msg :transport 27 | (reify Transport 28 | (recv [this] 29 | (transport/recv transport)) 30 | (recv [this timeout] 31 | (transport/recv transport timeout)) 32 | (send [this resp] 33 | (transport/send transport resp) 34 | (after-send resp) 35 | this)))) 36 | 37 | (defn print-root-trace [^Throwable t] 38 | (println (core/trace-str t))) 39 | 40 | (defn- populate-caught [{t ::caught/throwable :as resp}] 41 | (let [root ^Throwable (core/root-cause t) 42 | {:clojure.error/keys [source line column]} (ex-data root) 43 | cause ^Throwable (or (some-> root .getCause) root) 44 | data (ex-data cause) 45 | resp' (cond-> resp 46 | cause (assoc 47 | ::caught/throwable root 48 | ::root-ex-msg (.getMessage cause) 49 | ::root-ex-class (.getSimpleName (class cause)) 50 | ::trace (core/trace-str root)) 51 | source (assoc ::source source) 52 | line (assoc ::line line) 53 | column (assoc ::column column) 54 | data (update ::print/keys (fnil conj []) ::root-ex-data) 55 | data (assoc ::root-ex-data data))] 56 | resp')) 57 | 58 | (defn wrap-errors [handler] 59 | (fn [msg] 60 | (handler (-> msg (on-send populate-caught))))) 61 | 62 | (middleware/set-descriptor! 63 | #'wrap-errors 64 | {:requires #{#'caught/wrap-caught} ;; run inside wrap-caught 65 | :expects #{"eval"} ;; but outside of "eval" 66 | :handles {}}) 67 | 68 | 69 | (defn- redirect-output [resp] 70 | (when-some [out (:out resp)] 71 | (.print System/out out) 72 | (.flush System/out)) 73 | (when-some [err (:err resp)] 74 | (.print System/err err) 75 | (.flush System/err)) 76 | resp) 77 | 78 | (defn wrap-output [handler] 79 | (fn [msg] 80 | (handler (-> msg (on-send redirect-output))))) 81 | 82 | (middleware/set-descriptor! 83 | #'wrap-output 84 | {:requires #{} 85 | :expects #{"eval"} ;; run outside of "eval" 86 | :handles {}}) 87 | 88 | 89 | (defn time-eval [handler] 90 | (fn [{:keys [op] :as msg}] 91 | (if (= "eval" op) 92 | (let [start (System/nanoTime)] 93 | (-> msg 94 | (on-send #(cond-> % (contains? % :value) (assoc ::time-taken (- (System/nanoTime) start)))) 95 | (handler))) 96 | (handler msg)))) 97 | 98 | (middleware/set-descriptor! 99 | #'time-eval 100 | {:requires #{#'wrap-output #'wrap-errors #'print/wrap-print} 101 | :expects #{"eval"} 102 | :handles {}}) 103 | 104 | 105 | (defn clone-and-eval [handler] 106 | (fn [{:keys [id op session transport] :as msg}] 107 | (if (= op "clone-eval-close") 108 | (let [*new-session (promise)] 109 | (-> {:id id :op "clone" :session session :transport transport} 110 | (on-send #(do (deliver *new-session (:new-session %)) %)) 111 | (handler)) 112 | (-> msg 113 | (assoc :session @*new-session :op "eval") 114 | (after-send (fn [resp] 115 | (when (and 116 | (= (:id resp) id) 117 | (= (:session resp) @*new-session) 118 | (contains? (:status resp) :done)) 119 | (future ((session/session handler) {:id id :op "close" :session @*new-session :transport transport}))))) 120 | (handler))) 121 | (handler msg)))) 122 | 123 | (middleware/set-descriptor! 124 | #'clone-and-eval 125 | {:requires #{} 126 | :expects #{"eval" "clone"} 127 | :handles {"clone-and-eval" 128 | {:doc "Clones current session, evals given code in the cloned session"}}}) 129 | -------------------------------------------------------------------------------- /src_clojure/clojure_sublimed/socket_repl.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-sublimed.socket-repl 2 | (:require 3 | [clojure.string :as str] 4 | [clojure.walk :as walk] 5 | [clojure-sublimed.core :as core]) 6 | (:import 7 | [java.io FilterWriter Reader StringReader Writer] 8 | [java.lang.reflect Field] 9 | [clojure.lang Compiler Compiler$CompilerException LineNumberingPushbackReader LispReader LispReader$ReaderException RT TaggedLiteral])) 10 | 11 | (defonce ^:dynamic *out-fn* 12 | prn) 13 | 14 | (defonce *out-fns 15 | (atom #{})) 16 | 17 | (defonce ^:dynamic *context* 18 | nil) 19 | 20 | (defonce *evals 21 | (atom {})) 22 | 23 | (defn stop! [] 24 | (throw (ex-info "Stop" {::stop true}))) 25 | 26 | (defn read-command [in] 27 | (let [[form s] (read+string {:eof ::eof, :read-cond :allow} in)] 28 | (when (= ::eof form) 29 | (stop!)) 30 | 31 | ; (vswap! *context* assoc :form s) 32 | 33 | (when-not (map? form) 34 | (throw (Exception. (str "Unexpected form: " (pr-str form))))) 35 | 36 | form)) 37 | 38 | (defn report-throwable [^Throwable t] 39 | (let [root ^Throwable (core/root-cause t) 40 | {:clojure.error/keys [source line column]} (ex-data root) 41 | cause ^Throwable (or (some-> root .getCause) root) 42 | data (ex-data cause) 43 | class (.getSimpleName (class cause)) 44 | msg (.getMessage cause) 45 | val (cond-> (str class ": " msg) 46 | data 47 | (str " " (core/bounded-pr-str data))) 48 | trace (core/trace-str root {:location? false})] 49 | (*out-fn* 50 | {"tag" "ex" 51 | "val" val 52 | "trace" trace 53 | "source" source 54 | "line" line 55 | "column" column}))) 56 | 57 | (defn reader ^LineNumberingPushbackReader [code line column] 58 | (let [reader (LineNumberingPushbackReader. (StringReader. code))] 59 | (when line 60 | (.setLineNumber reader (int line))) 61 | (when column 62 | (when-some [field (.getDeclaredField LineNumberingPushbackReader "_columnNumber")] 63 | (doto ^Field field 64 | (.setAccessible true) 65 | (.set reader (int column))))) 66 | reader)) 67 | 68 | (defn consume-ws [^LineNumberingPushbackReader reader] 69 | (loop [ch (.read reader)] 70 | (if (or (Character/isWhitespace ch) (= (int \,) ch)) 71 | (recur (.read reader)) 72 | (when-not (neg? ch) 73 | (.unread reader ch))))) 74 | 75 | (defn eval-code [form] 76 | (let [{:strs [code ns line column file]} form 77 | name (or (some-> file (str/split #"[/\\]") last) "NO_SOURCE_FILE") 78 | ns (symbol (or ns "user")) 79 | ns-obj (or 80 | (find-ns ns) 81 | (do 82 | (require ns) 83 | (find-ns ns))) 84 | ;; Adapted from clojure.lang.Compiler/load 85 | ;; Does not bind *uncheked-math*, *warn-on-reflection* and *data-readers* 86 | eof (Object.) 87 | opts {:eof eof 88 | :read-cond :allow} 89 | reader (reader code line column) 90 | _ (consume-ws reader) 91 | _ (push-thread-bindings 92 | {Compiler/LOADER (RT/makeClassLoader) 93 | #'*file* file 94 | #'*source-path* name 95 | Compiler/METHOD nil 96 | Compiler/LOCAL_ENV nil 97 | Compiler/LOOP_LOCALS nil 98 | Compiler/NEXT_LOCAL_NUM 0 99 | #'*read-eval* true 100 | #'*ns* ns-obj 101 | Compiler/LINE_BEFORE (.getLineNumber reader) 102 | Compiler/COLUMN_BEFORE (.getColumnNumber reader) 103 | Compiler/LINE_AFTER (.getLineNumber reader) 104 | Compiler/COLUMN_AFTER (.getColumnNumber reader) 105 | #'*e nil 106 | #'*1 nil 107 | #'*2 nil 108 | #'*3 nil}) 109 | ret (try 110 | (loop [idx 0] 111 | (vswap! *context* assoc "idx" idx) 112 | (let [[obj obj-str] (read+string opts reader)] 113 | (when-not (identical? obj eof) 114 | (.set Compiler/LINE_AFTER (.getLineNumber reader)) 115 | (.set Compiler/COLUMN_AFTER (.getColumnNumber reader)) 116 | (vswap! *context* assoc 117 | "from_line" (.get Compiler/LINE_BEFORE) 118 | "from_column" (.get Compiler/COLUMN_BEFORE) 119 | "to_line" (.get Compiler/LINE_AFTER) 120 | "to_column" (.get Compiler/COLUMN_AFTER) 121 | "form" obj-str) 122 | (let [start (System/nanoTime) 123 | ret (Compiler/eval obj false)] 124 | (*out-fn* 125 | {"tag" "ret" 126 | "val" (core/bounded-pr-str ret) 127 | "time" (-> (System/nanoTime) (- start) (quot 1000000))}) 128 | (consume-ws reader) 129 | (.set Compiler/LINE_BEFORE (.getLineNumber reader)) 130 | (.set Compiler/COLUMN_BEFORE (.getColumnNumber reader)) 131 | (recur (inc idx)))))) 132 | (catch LispReader$ReaderException e 133 | (throw (Compiler$CompilerException. 134 | file 135 | (.-line e) 136 | (.-column e) 137 | nil 138 | Compiler$CompilerException/PHASE_READ 139 | (.getCause e)))) 140 | (catch Throwable e 141 | (if (instance? Compiler$CompilerException e) 142 | (throw e) 143 | (throw (Compiler$CompilerException. 144 | file 145 | (.deref Compiler/LINE_BEFORE) 146 | (.deref Compiler/COLUMN_BEFORE) 147 | nil 148 | Compiler$CompilerException/PHASE_EXECUTION 149 | e)))) 150 | (finally 151 | (pop-thread-bindings)))])) 152 | 153 | (defn fork-eval [{:strs [id print_quota] :as form}] 154 | (swap! *evals assoc id 155 | (future 156 | (binding [core/*print-quota* (or print_quota core/*print-quota*)] 157 | (try 158 | (core/track-vars 159 | (eval-code form)) 160 | (catch Throwable t 161 | (try 162 | (report-throwable t) 163 | (catch Throwable t 164 | :ignore))) 165 | (finally 166 | (swap! *evals dissoc id) 167 | (vswap! *context* dissoc "idx" "from_line" "from_column" "to_line" "to_column" "form") 168 | (*out-fn* 169 | {"tag" "done"}))))))) 170 | 171 | (defn interrupt [{:strs [id]}] 172 | (when-some [f (@*evals id)] 173 | (future-cancel f))) 174 | 175 | (def safe-meta? 176 | #{:ns :name :doc :file :arglists :forms :macro :special-form :protocol :line :column :added :deprecated :resource}) 177 | 178 | (defn lookup-symbol [form] 179 | (let [{:strs [id op symbol ns]} form 180 | ns (clojure.core/symbol (or ns "user")) 181 | symbol (clojure.core/symbol symbol) 182 | meta (if (special-symbol? symbol) 183 | (assoc ((requiring-resolve 'clojure.repl/special-doc) symbol) 184 | :ns 'clojure.core 185 | :file "clojure/core.clj" 186 | :special-form true) 187 | (meta (ns-resolve ns symbol)))] 188 | (*out-fn* 189 | (if meta 190 | (let [meta' (reduce-kv 191 | (fn [m k v] 192 | (if (safe-meta? k) 193 | (assoc m (name k) (str v)) ; stringify to match nREPL 194 | m)) 195 | nil 196 | meta)] 197 | {"tag" "lookup" 198 | "val" meta'}) 199 | {"tag" "ex" 200 | "val" (str "Symbol '" symbol " not found in ns '" ns)})))) 201 | 202 | (defmacro watch [id form] 203 | `(let [res# ~form 204 | msg# {"tag" "watch" 205 | "val" (core/bounded-pr-str res#) 206 | "watch_id" ~id}] 207 | (doseq [out-fn# @*out-fns] 208 | (out-fn# msg#)) 209 | res#)) 210 | 211 | (defn out-fn [out] 212 | (let [lock (Object.)] 213 | #(locking lock 214 | (binding [*out* out 215 | *print-readably* true] 216 | (prn (merge (sorted-map) (some-> *context* deref) %)))))) 217 | 218 | (defn repl [] 219 | (let [out-fn (out-fn *out*)] 220 | (try 221 | (swap! *out-fns conj out-fn) 222 | (binding [*out-fn* out-fn 223 | *out* (core/duplicate-writer (.getRawRoot #'*out*) "out" out-fn) 224 | *err* (core/duplicate-writer (.getRawRoot #'*err*) "err" out-fn) 225 | core/*changed-vars (atom {})] 226 | (out-fn {"tag" "started"}) 227 | (loop [] 228 | (when 229 | (binding [*context* (volatile! {})] 230 | (try 231 | (let [form (read-command *in*)] 232 | (core/set-changed-vars!) 233 | (when-some [id (form "id")] 234 | (vswap! *context* assoc "id" id)) 235 | (case (get form "op") 236 | "eval" (fork-eval form) 237 | "interrupt" (interrupt form) 238 | "lookup" (lookup-symbol form) 239 | (throw (Exception. (str "Unknown op: " (get form "op"))))) 240 | true) 241 | (catch Throwable t 242 | (when-not (-> t ex-data ::stop) 243 | (report-throwable t) 244 | true)))) 245 | (recur))) 246 | (doseq [[id f] @*evals] 247 | (future-cancel f))) 248 | (finally 249 | (swap! *out-fns disj out-fn))))) 250 | -------------------------------------------------------------------------------- /test_comment/comment.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Partial comment 3 | ================================================================================ 4 | 5 | (def m 6 | {→:x #_1 7 | #_:y 2←}) 8 | 9 | -------------------------------------------------------------------------------- 10 | 11 | (def m 12 | {#_→:x #_1 13 | #_:y #_2←}) 14 | 15 | ================================================================================ 16 | Partial comment (reverse) 17 | ================================================================================ 18 | 19 | (def m 20 | {#_→:x #_1 21 | #_:y #_2←}) 22 | 23 | -------------------------------------------------------------------------------- 24 | 25 | (def m 26 | {→:x 1 27 | :y 2←}) 28 | 29 | ================================================================================ 30 | [ BROKEN ] Uncomment line comments 1 31 | ================================================================================ 32 | 33 | (def m 34 | {:x 1 35 | | ;; :y 2 36 | :z 3}) 37 | 38 | -------------------------------------------------------------------------------- 39 | 40 | (def m 41 | {:x 1 42 | | :y 2 43 | :z 3}) 44 | 45 | ================================================================================ 46 | Uncomment line comments 2 47 | ================================================================================ 48 | 49 | (def m 50 | {:x 1 51 | |;; :y 2 52 | :z 3}) 53 | 54 | -------------------------------------------------------------------------------- 55 | 56 | (def m 57 | {:x 1 58 | |:y 2 59 | :z 3}) 60 | 61 | ================================================================================ 62 | Uncomment line comments 3 63 | ================================================================================ 64 | 65 | (def m 66 | {:x 1 67 | ;|; :y 2 68 | :z 3}) 69 | 70 | -------------------------------------------------------------------------------- 71 | 72 | (def m 73 | {:x 1 74 | |:y 2 75 | :z 3}) 76 | 77 | ================================================================================ 78 | Uncomment line comments 4 79 | ================================================================================ 80 | 81 | (def m 82 | {:x 1 83 | ;;| :y 2 84 | :z 3}) 85 | 86 | -------------------------------------------------------------------------------- 87 | 88 | (def m 89 | {:x 1 90 | |:y 2 91 | :z 3}) 92 | 93 | ================================================================================ 94 | Uncomment line comments 5 95 | ================================================================================ 96 | 97 | (def m 98 | {:x 1 99 | ;; |:y 2 100 | :z 3}) 101 | 102 | -------------------------------------------------------------------------------- 103 | 104 | (def m 105 | {:x 1 106 | |:y 2 107 | :z 3}) 108 | 109 | ================================================================================ 110 | Uncomment line comments 6 111 | ================================================================================ 112 | 113 | (def m 114 | {:x 1 115 | ;; :y |2 116 | :z 3}) 117 | 118 | -------------------------------------------------------------------------------- 119 | 120 | (def m 121 | {:x 1 122 | :y |2 123 | :z 3}) 124 | 125 | ================================================================================ 126 | Uncomment line comments 7 127 | ================================================================================ 128 | 129 | (def m 130 | {:x 1 131 | ;; :y 2| 132 | :z 3}) 133 | 134 | -------------------------------------------------------------------------------- 135 | 136 | (def m 137 | {:x 1 138 | :y 2| 139 | :z 3}) 140 | 141 | ================================================================================ 142 | Uncomment with space 1 143 | ================================================================================ 144 | 145 | (defn |#_ abc [] 146 | ...) 147 | 148 | -------------------------------------------------------------------------------- 149 | 150 | (defn |abc [] 151 | ...) 152 | 153 | ================================================================================ 154 | Uncomment with space 2 155 | ================================================================================ 156 | 157 | (defn #|_ abc [] 158 | ...) 159 | 160 | -------------------------------------------------------------------------------- 161 | 162 | (defn |abc [] 163 | ...) 164 | 165 | ================================================================================ 166 | Uncomment with space 3 167 | ================================================================================ 168 | 169 | (defn #_| abc [] 170 | ...) 171 | 172 | -------------------------------------------------------------------------------- 173 | 174 | (defn |abc [] 175 | ...) 176 | 177 | ================================================================================ 178 | Uncomment with space 4 179 | ================================================================================ 180 | 181 | (defn #_ | abc [] 182 | ...) 183 | 184 | -------------------------------------------------------------------------------- 185 | 186 | (defn |abc [] 187 | ...) 188 | 189 | ================================================================================ 190 | Uncomment with space 5 191 | ================================================================================ 192 | 193 | (defn #_ |abc [] 194 | ...) 195 | 196 | -------------------------------------------------------------------------------- 197 | 198 | (defn |abc [] 199 | ...) 200 | 201 | ================================================================================ 202 | Uncomment with space 6 203 | ================================================================================ 204 | 205 | (defn #_ a|bc [] 206 | ...) 207 | 208 | -------------------------------------------------------------------------------- 209 | 210 | (defn a|bc [] 211 | ...) 212 | 213 | ================================================================================ 214 | Uncomment with space 7 215 | ================================================================================ 216 | 217 | (defn #_ abc| [] 218 | ...) 219 | 220 | -------------------------------------------------------------------------------- 221 | 222 | (defn abc| [] 223 | ...) 224 | 225 | ================================================================================ 226 | Uncomment with space 8 227 | ================================================================================ 228 | 229 | (defn #_ abc | [] 230 | ...) 231 | 232 | -------------------------------------------------------------------------------- 233 | 234 | (defn abc | [] 235 | ...) 236 | 237 | ================================================================================ 238 | Uncomment deep 1 239 | ================================================================================ 240 | 241 | (defn |#_ (let [x 1 242 | y {:a [1 2 3]}]) 243 | (println)) 244 | 245 | -------------------------------------------------------------------------------- 246 | 247 | (defn |(let [x 1 248 | y {:a [1 2 3]}]) 249 | (println)) 250 | 251 | ================================================================================ 252 | Uncomment deep 2 253 | ================================================================================ 254 | 255 | (defn #|_ (let [x 1 256 | y {:a [1 2 3]}]) 257 | (println)) 258 | 259 | -------------------------------------------------------------------------------- 260 | 261 | (defn |(let [x 1 262 | y {:a [1 2 3]}]) 263 | (println)) 264 | 265 | ================================================================================ 266 | Uncomment deep 3 267 | ================================================================================ 268 | 269 | (defn #_| (let [x 1 270 | y {:a [1 2 3]}]) 271 | (println)) 272 | 273 | -------------------------------------------------------------------------------- 274 | 275 | (defn |(let [x 1 276 | y {:a [1 2 3]}]) 277 | (println)) 278 | 279 | ================================================================================ 280 | Uncomment deep 4 281 | ================================================================================ 282 | 283 | (defn #_ |(let [x 1 284 | y {:a [1 2 3]}]) 285 | (println)) 286 | 287 | -------------------------------------------------------------------------------- 288 | 289 | (defn |(let [x 1 290 | y {:a [1 2 3]}]) 291 | (println)) 292 | 293 | ================================================================================ 294 | Uncomment deep 5 295 | ================================================================================ 296 | 297 | (defn #_ (let [x 1 298 | y {:a [1| 2 3]}]) 299 | (println)) 300 | 301 | -------------------------------------------------------------------------------- 302 | 303 | (defn (let [x 1 304 | y {:a [1| 2 3]}]) 305 | (println)) 306 | 307 | ================================================================================ 308 | Uncomment deep 6 309 | ================================================================================ 310 | 311 | (defn #_ (let [x 1 312 | y {:a [1 2 3]}]|) 313 | (println)) 314 | 315 | -------------------------------------------------------------------------------- 316 | 317 | (defn (let [x 1 318 | y {:a [1 2 3]}]|) 319 | (println)) 320 | 321 | ================================================================================ 322 | Uncomment deep 7 323 | ================================================================================ 324 | 325 | (defn #_ (let [x 1 326 | y {:a [1 2 3]}])| 327 | (println)) 328 | 329 | -------------------------------------------------------------------------------- 330 | 331 | (defn (let [x 1 332 | y {:a [1 2 3]}])| 333 | (println)) 334 | 335 | -------------------------------------------------------------------------------- /test_comment/comment_reversible.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Comment form under cursor 3 | ================================================================================ 4 | 5 | (defn a|bc [] 6 | ...) 7 | 8 | -------------------------------------------------------------------------------- 9 | 10 | (defn #_a|bc [] 11 | ...) 12 | 13 | ================================================================================ 14 | Comment form under cursor - left 15 | ================================================================================ 16 | 17 | (defn |abc [] 18 | ...) 19 | 20 | -------------------------------------------------------------------------------- 21 | 22 | (defn #_|abc [] 23 | ...) 24 | 25 | ================================================================================ 26 | Comment form under cursor - right 27 | ================================================================================ 28 | 29 | (defn abc| [] 30 | ...) 31 | 32 | -------------------------------------------------------------------------------- 33 | 34 | (defn #_abc| [] 35 | ...) 36 | 37 | ================================================================================ 38 | Comment form under cursor - space 39 | ================================================================================ 40 | 41 | (defn abc [] 42 | (let [x | 1] 43 | ...)) 44 | 45 | -------------------------------------------------------------------------------- 46 | 47 | (defn abc [] 48 | (let [#_x | 1] 49 | ...)) 50 | 51 | ================================================================================ 52 | Comment form under cursor - nested left 53 | ================================================================================ 54 | 55 | (defn abc [] 56 | |(let [x 1] 57 | ...)) 58 | 59 | -------------------------------------------------------------------------------- 60 | 61 | (defn abc [] 62 | #_|(let [x 1] 63 | ...)) 64 | 65 | ================================================================================ 66 | Comment form under cursor - nested right 67 | ================================================================================ 68 | 69 | (defn abc [] 70 | (let [x 1] 71 | ...)|) 72 | 73 | -------------------------------------------------------------------------------- 74 | 75 | (defn abc [] 76 | #_(let [x 1] 77 | ...)|) 78 | 79 | ================================================================================ 80 | Comment form under cursor - touching left 81 | ================================================================================ 82 | 83 | (defn abc [] 84 | (|let [x 1] 85 | ...)) 86 | 87 | -------------------------------------------------------------------------------- 88 | 89 | (defn abc [] 90 | (#_|let [x 1] 91 | ...)) 92 | 93 | ================================================================================ 94 | Comment form under cursor - touching middle 95 | ================================================================================ 96 | 97 | (defn abc [] 98 | (l|et [x 1] 99 | ...)) 100 | 101 | -------------------------------------------------------------------------------- 102 | 103 | (defn abc [] 104 | (#_l|et [x 1] 105 | ...)) 106 | 107 | ================================================================================ 108 | Comment form under cursor - touching right 109 | ================================================================================ 110 | 111 | (defn abc [] 112 | (let| [x 1] 113 | ...)) 114 | 115 | -------------------------------------------------------------------------------- 116 | 117 | (defn abc [] 118 | (#_let| [x 1] 119 | ...)) 120 | 121 | ================================================================================ 122 | Multi cursors 123 | ================================================================================ 124 | 125 | (defn abc [] 126 | (let| [x |1 127 | |y 2] 128 | ...)) 129 | 130 | -------------------------------------------------------------------------------- 131 | 132 | (defn abc [] 133 | (#_let| [x #_|1 134 | #_|y 2] 135 | ...)) 136 | 137 | ================================================================================ 138 | Meta 139 | ================================================================================ 140 | 141 | (defn ^:smth a|bc [] 142 | ...) 143 | 144 | -------------------------------------------------------------------------------- 145 | 146 | (defn #_^:smth a|bc [] 147 | ...) 148 | 149 | ================================================================================ 150 | Tagged 151 | ================================================================================ 152 | 153 | (def uuid 154 | #uuid "a|bc") 155 | 156 | -------------------------------------------------------------------------------- 157 | 158 | (def uuid 159 | #_#uuid "a|bc") 160 | 161 | ================================================================================ 162 | Wrapped 163 | ================================================================================ 164 | 165 | (def wrap 166 | @*wra|p) 167 | 168 | -------------------------------------------------------------------------------- 169 | 170 | (def wrap 171 | #_@*wra|p) 172 | 173 | ================================================================================ 174 | [ BROKEN ] Unbalanced 175 | ================================================================================ 176 | 177 | (defn abc [] 178 | ...))| 179 | 180 | -------------------------------------------------------------------------------- 181 | 182 | (defn abc [] 183 | ...)#_)| 184 | 185 | ================================================================================ 186 | Empty Vector 187 | ================================================================================ 188 | 189 | []| 190 | 191 | -------------------------------------------------------------------------------- 192 | 193 | #_[]| 194 | 195 | ================================================================================ 196 | Reader conditional 197 | ================================================================================ 198 | 199 | (defn fun #?(:clj abc)| [] 200 | ...) 201 | 202 | -------------------------------------------------------------------------------- 203 | 204 | (defn fun #_#?(:clj abc)| [] 205 | ...) 206 | 207 | ================================================================================ 208 | Selection 209 | ================================================================================ 210 | 211 | (def m 212 | {→:x 1← 213 | :y 2}) 214 | 215 | -------------------------------------------------------------------------------- 216 | 217 | (def m 218 | {#_→:x #_1← 219 | :y 2}) 220 | 221 | ================================================================================ 222 | Selection 2 223 | ================================================================================ 224 | 225 | →(def m 226 | {:x 1 227 | :y 2})← 228 | 229 | -------------------------------------------------------------------------------- 230 | 231 | #_→(def m 232 | {:x 1 233 | :y 2})← 234 | 235 | 236 | ================================================================================ 237 | Skip line comments 238 | ================================================================================ 239 | 240 | (def m 241 | {→:x 1 242 | ;; some comment 243 | :y 2 ;; another 244 | :z 3←}) 245 | 246 | -------------------------------------------------------------------------------- 247 | 248 | (def m 249 | {#_→:x #_1 250 | ;; some comment 251 | #_:y #_2 ;; another 252 | #_:z #_3←}) 253 | -------------------------------------------------------------------------------- /test_indent/indent.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Empty list 3 | ================================================================================ 4 | 5 | ( 6 | ) 7 | ( 8 | ) 9 | 10 | -------------------------------------------------------------------------------- 11 | 12 | ( 13 | ) 14 | ( 15 | ) 16 | 17 | ================================================================================ 18 | List starting with symbol 19 | ================================================================================ 20 | 21 | (abc 22 | ) 23 | (abc 24 | def) 25 | (abc def 26 | ghi) 27 | 28 | -------------------------------------------------------------------------------- 29 | 30 | (abc 31 | ) 32 | (abc 33 | def) 34 | (abc def 35 | ghi) 36 | 37 | ================================================================================ 38 | List starting with number 39 | ================================================================================ 40 | 41 | (123 42 | ) 43 | (123 44 | 456) 45 | '(123 456 46 | 789) 47 | '(1 2 3 4 48 | 5 6 7 8) 49 | 50 | -------------------------------------------------------------------------------- 51 | 52 | (123 53 | ) 54 | (123 55 | 456) 56 | '(123 456 57 | 789) 58 | '(1 2 3 4 59 | 5 6 7 8) 60 | 61 | ================================================================================ 62 | List starting with keyword 63 | ================================================================================ 64 | 65 | (:abc 66 | ) 67 | (:abc 68 | :def) 69 | (:abc :def 70 | :ghi) 71 | 72 | -------------------------------------------------------------------------------- 73 | 74 | (:abc 75 | ) 76 | (:abc 77 | :def) 78 | (:abc :def 79 | :ghi) 80 | 81 | ================================================================================ 82 | Many args 83 | ================================================================================ 84 | 85 | (defn many-args [a b c 86 | d e f]) 87 | 88 | -------------------------------------------------------------------------------- 89 | 90 | (defn many-args [a b c 91 | d e f]) 92 | 93 | ================================================================================ 94 | Multi-arity 95 | ================================================================================ 96 | 97 | (defn multi-arity 98 | ([x] 99 | body) 100 | ([x y] 101 | body)) 102 | 103 | -------------------------------------------------------------------------------- 104 | 105 | (defn multi-arity 106 | ([x] 107 | body) 108 | ([x y] 109 | body)) 110 | 111 | ================================================================================ 112 | Vectors 113 | ================================================================================ 114 | 115 | [] 116 | [ 117 | ] 118 | [a] 119 | [a 120 | ] 121 | [ 122 | a] 123 | [a b] 124 | [a b 125 | ] 126 | [a 127 | b 128 | ] 129 | [ 130 | a 131 | b 132 | ] 133 | [a b c] 134 | [a b c 135 | ] 136 | [a b 137 | c 138 | ] 139 | [a 140 | b 141 | c 142 | ] 143 | [ 144 | a 145 | b 146 | c 147 | ] 148 | 149 | -------------------------------------------------------------------------------- 150 | 151 | [] 152 | [ 153 | ] 154 | [a] 155 | [a 156 | ] 157 | [ 158 | a] 159 | [a b] 160 | [a b 161 | ] 162 | [a 163 | b 164 | ] 165 | [ 166 | a 167 | b 168 | ] 169 | [a b c] 170 | [a b c 171 | ] 172 | [a b 173 | c 174 | ] 175 | [a 176 | b 177 | c 178 | ] 179 | [ 180 | a 181 | b 182 | c 183 | ] 184 | 185 | ================================================================================ 186 | Maps 187 | ================================================================================ 188 | 189 | {} 190 | { 191 | } 192 | {a b} 193 | {a b 194 | } 195 | {a 196 | b 197 | } 198 | { 199 | a 200 | b 201 | } 202 | {a b c d} 203 | {a b c d 204 | } 205 | {a b c 206 | d} 207 | {a b 208 | c d 209 | } 210 | {a 211 | b c d} 212 | {a 213 | b c 214 | d} 215 | 216 | -------------------------------------------------------------------------------- 217 | 218 | {} 219 | { 220 | } 221 | {a b} 222 | {a b 223 | } 224 | {a 225 | b 226 | } 227 | { 228 | a 229 | b 230 | } 231 | {a b c d} 232 | {a b c d 233 | } 234 | {a b c 235 | d} 236 | {a b 237 | c d 238 | } 239 | {a 240 | b c d} 241 | {a 242 | b c 243 | d} 244 | 245 | ================================================================================ 246 | Sets 247 | ================================================================================ 248 | 249 | #{} 250 | #{ 251 | } 252 | #{a} 253 | #{a 254 | } 255 | #{ 256 | a} 257 | #{a b} 258 | #{a b 259 | } 260 | #{a 261 | b 262 | } 263 | #{ 264 | a 265 | b 266 | } 267 | #{a b c} 268 | #{a b c 269 | } 270 | #{a b 271 | c 272 | } 273 | #{a 274 | b 275 | c 276 | } 277 | #{ 278 | a 279 | b 280 | c 281 | } 282 | 283 | -------------------------------------------------------------------------------- 284 | 285 | #{} 286 | #{ 287 | } 288 | #{a} 289 | #{a 290 | } 291 | #{ 292 | a} 293 | #{a b} 294 | #{a b 295 | } 296 | #{a 297 | b 298 | } 299 | #{ 300 | a 301 | b 302 | } 303 | #{a b c} 304 | #{a b c 305 | } 306 | #{a b 307 | c 308 | } 309 | #{a 310 | b 311 | c 312 | } 313 | #{ 314 | a 315 | b 316 | c 317 | } 318 | 319 | ================================================================================ 320 | Reader conditional 321 | ================================================================================ 322 | 323 | #?(:clj 1 324 | :cljs 2) 325 | 326 | #?@(:clj [1] 327 | :cljs [2]) 328 | 329 | -------------------------------------------------------------------------------- 330 | 331 | #?(:clj 1 332 | :cljs 2) 333 | 334 | #?@(:clj [1] 335 | :cljs [2]) 336 | 337 | ================================================================================ 338 | Multiline strings 339 | ================================================================================ 340 | 341 | "asdasd 342 | asdas 343 | aksjdlkj 344 | lkjdlk" 345 | (def x 346 | "asdasd 347 | asdas 348 | aksjdlkj 349 | lkjdlk") 350 | (def y [ 351 | "asdas" 352 | "adasd" 353 | "adasd" 354 | ]) 355 | 356 | -------------------------------------------------------------------------------- 357 | 358 | "asdasd 359 | asdas 360 | aksjdlkj 361 | lkjdlk" 362 | (def x 363 | "asdasd 364 | asdas 365 | aksjdlkj 366 | lkjdlk") 367 | (def y [ 368 | "asdas" 369 | "adasd" 370 | "adasd" 371 | ]) 372 | 373 | ================================================================================ 374 | Requires 375 | ================================================================================ 376 | 377 | (ns abc 378 | (:requires 379 | [a.b.c :as c] 380 | [d.e.f :as f])) 381 | (ns abc 382 | (:requires [a.b.c :as c] 383 | [d.e.f :as f])) 384 | 385 | -------------------------------------------------------------------------------- 386 | 387 | (ns abc 388 | (:requires 389 | [a.b.c :as c] 390 | [d.e.f :as f])) 391 | (ns abc 392 | (:requires [a.b.c :as c] 393 | [d.e.f :as f])) 394 | 395 | ================================================================================ 396 | Or 397 | ================================================================================ 398 | 399 | (or cond-1 cond-2) 400 | (or cond-1 401 | cond-2) 402 | (or 403 | cond-1 404 | cond-2) 405 | 406 | -------------------------------------------------------------------------------- 407 | 408 | (or cond-1 cond-2) 409 | (or cond-1 410 | cond-2) 411 | (or 412 | cond-1 413 | cond-2) 414 | 415 | ================================================================================ 416 | Threading 417 | ================================================================================ 418 | 419 | (-> a b c) 420 | (-> a b 421 | c) 422 | (-> a 423 | b 424 | c) 425 | (-> 426 | a 427 | b 428 | c) 429 | 430 | -------------------------------------------------------------------------------- 431 | 432 | (-> a b c) 433 | (-> a b 434 | c) 435 | (-> a 436 | b 437 | c) 438 | (-> 439 | a 440 | b 441 | c) 442 | 443 | ================================================================================ 444 | Threading 445 | ================================================================================ 446 | 447 | (do a b c) 448 | (do a b 449 | c) 450 | (do a 451 | b 452 | c) 453 | (do 454 | a 455 | b 456 | c) 457 | 458 | -------------------------------------------------------------------------------- 459 | 460 | (do a b c) 461 | (do a b 462 | c) 463 | (do a 464 | b 465 | c) 466 | (do 467 | a 468 | b 469 | c) 470 | 471 | ================================================================================ 472 | Everything 473 | ================================================================================ 474 | 475 | (letfn [(square [x] 476 | (* x x)) 477 | (sum [x y] 478 | (+ x y))] 479 | (let [x 3 480 | y 4] 481 | (sum (square x) 482 | (square y)))) 483 | 484 | -------------------------------------------------------------------------------- 485 | 486 | (letfn [(square [x] 487 | (* x x)) 488 | (sum [x y] 489 | (+ x y))] 490 | (let [x 3 491 | y 4] 492 | (sum (square x) 493 | (square y)))) 494 | -------------------------------------------------------------------------------- /test_scheme/color_scheme.clj: -------------------------------------------------------------------------------- 1 | 2 | ; Constants 3 | nil true false \c \tab 1 1.0 1/2 #inst "1985-01-25" 4 | 5 | ; Symbols 6 | abc ab/cd _abc 7 | 8 | ; Keywords 9 | :a :a.b/c.d ::ab ::a.b/c.d 10 | 11 | ; Strings 12 | "" "abc" "\" \u221e \x" 13 | 14 | ; Regexps 15 | #"re \n \uFFFF \p{L} \Qabc\E) \y" 16 | 17 | ; Top-level parens 18 | () [] {} #() #{} #?() #?@() 19 | 20 | ; Nested parens 21 | (() [] {} #() #{} #?() #?@()) 22 | [() [] {} #() #{} #?() #?@()] 23 | {() [] {} #() #{} #?() #?@()} 24 | #{() [] {} #() #{} #?() #?@()} 25 | #(() [] {} #() #{} #?() #?@()) 26 | ([{}]) 27 | (((((((((()))))))))) [[[[[[[[[[]]]]]]]]]] {{{{{{{{{{}}}}}}}}}} 28 | 29 | ; Definitions 30 | (def xyz) 31 | (def xyz xyz) 32 | (def xyz 123) 33 | (def 123 xyz) 34 | (def 35 | xyz) 36 | (do (def xyz)) 37 | 38 | ; Punctuation 39 | , 40 | 41 | ; Meta 42 | ^{:s sym 43 | :v "str\n" 44 | :n 123 45 | :re #"\p{L}) \y" 46 | :l ([{}]) 47 | :q '(abc) 48 | :sq `(abc ~def) 49 | :m ^int i 50 | :d @ref 51 | :v #'var 52 | :c #?(:clj) 53 | :df (def x 1) 54 | ; linecomment 55 | #_#_reader comment 56 | (comment form))))} sym 57 | 58 | ; Quotes 59 | '{:symbols [name/space _ _abc] 60 | :strings ["str\n\x" '"str"] 61 | :regexp #"\p{L}) \y" 62 | :number 123.456 63 | :keyword :key/word 64 | :parens (() #() #?(:clj :cljs) [] {} #{}) 65 | :quoted ('abc `(x ~y)) 66 | :meta ^int i 67 | :vars [@ref #'var] 68 | :defs (def x 1) 69 | ; linecomment 70 | #_#_reader comment 71 | (comment form))))} 72 | 73 | ; Syntax quotes 74 | `{:symbols [name/space _ _abc] 75 | :strings ["str\n\x" '"str"] 76 | :regexp #"\p{L}) \y" 77 | :number 123.456 78 | :keyword :key/word 79 | :parens (() #() #?(:clj :cljs) [] {} #{}) 80 | :quoted ('abc `(x ~y)) 81 | :meta ^int i 82 | :vars [@ref #'var] 83 | :defs (def x 1) 84 | ; linecomment 85 | #_#_reader comment 86 | (comment form))))} 87 | 88 | `{:symbols [name/space _ _abc] 89 | :strings ["str\n\x" '"str"] 90 | :regexp #"\p{L}) \y" 91 | :number 123.456 92 | :keyword :key/word 93 | :parens (() #() #?(:clj :cljs) [] {} #{}) 94 | :quoted ('abc `(x ~y)) 95 | :meta ^int i 96 | :vars [@ref #'var] 97 | :defs (def x 1) 98 | ; linecomment 99 | #_#_reader comment 100 | (comment form))))} 101 | 102 | `~{:symbols [name/space _ _abc] 103 | :strings ["str\n\x" '"str"] 104 | :regexp #"\p{L}) \y" 105 | :number 123.456 106 | :keyword :key/word 107 | :parens (() #() #?(:clj :cljs) [] {} #{}) 108 | :quoted ('abc `(x ~y)) 109 | :meta ^int i 110 | :vars [@ref #'var] 111 | :defs (def x 1) 112 | ; linecomment 113 | #_#_reader comment 114 | (comment form))))} 115 | 116 | ; Line comments 117 | ; {:symbols [name/space _ _abc] 118 | ; :strings ["str\n\x" '"str"] 119 | ; :regexp #"\p{L}) \y" 120 | ; :number 123.456 121 | ; :keyword :key/word 122 | ; :parens (() #() #?(:clj :cljs) [] {} #{}) 123 | ; :quoted ('abc `(x ~y)) 124 | ; :meta ^int i 125 | ; :vars [@ref #'var] 126 | ; :defs (def x 1) 127 | ; ; linecomment 128 | ; #_#_reader comment 129 | ; (comment form))))} 130 | 131 | ; Reader comments 132 | #_{:symbols [name/space _ _abc] 133 | :strings ["str\n\x" '"str"] 134 | :regexp #"\p{L}) \y" 135 | :number 123.456 136 | :keyword :key/word 137 | :parens (() #() #?(:clj :cljs) [] {} #{}) 138 | :quoted ('abc `(x ~y)) 139 | :meta ^int i 140 | :vars [@ref #'var] 141 | :defs (def x 1) 142 | ; linecomment 143 | #_#_reader comment 144 | (comment form))))} 145 | 146 | ; Form comments 147 | (comment 148 | {:symbols [name/space _ _abc] 149 | :strings ["str\n\x" '"str"] 150 | :regexp #"\p{L}) \y" 151 | :number 123.456 152 | :keyword :key/word 153 | :parens (() #() #?(:clj :cljs) [] {} #{}) 154 | :quoted ('abc `(x ~y)) 155 | :meta ^int i 156 | :vars [@ref #'var] 157 | :defs (def x 1) 158 | ; linecomment 159 | #_#_reader comment 160 | (comment form))))}) 161 | -------------------------------------------------------------------------------- /test_scheme/demo.clj: -------------------------------------------------------------------------------- 1 | ;; Clojure Sublimed 2 | ;; ---------------- 3 | 4 | ;; Nested parentheses 5 | ((())) ([{}]) [#{} #?(:clj) #()] 6 | 7 | ;; Reader comments 8 | (let [x 1 #_(throw (ex-info "" {})) 9 | y 2 10 | ;; Including stacking 11 | #_#_ z (/ 1 0)]) 12 | 13 | ;; Block comments 14 | (comment 15 | (/ 1 0)) 16 | 17 | ;; Unused symbols 18 | (defn fn [a _unused & _]) 19 | 20 | ;; Namespaces 21 | clojure.string/index-of 22 | 23 | ;; Metadata 24 | ^{:doc "abc"} x 25 | 26 | ;; Quoting 27 | ... '[a b c] ... 28 | 29 | ;; And unquoting 30 | `(let [x# ~(gensym x)] 31 | ...) 32 | 33 | ;; Trickiy edge cases 34 | (defn #_c ^int fn "doc" {:attr :map} []) 35 | '^int sym 36 | ^'tag sym 37 | #_#_()()() 38 | 39 | ;; Error detection 40 | "\u221 \x" #".* \E) \y" 41 | #inst "1985-" \aa 1/0 09 #123 nnil truee 42 | :kv: :kv/ :/kv :/ :kv/ab: :kv/ab/ 43 | 44 | 45 | 46 | 47 | (defn reverse 48 | "Returns a seq of \"items\" in reverse order." 49 | {:added "1.0" 50 | :static true} 51 | [coll] 52 | (reduce1 conj () coll)) 53 | 54 | ;; math stuff 55 | (defn ^:private nary-inline 56 | ([op] 57 | (nary-inline op op)) 58 | ([op unchecked-op] 59 | (fn [x y & more] 60 | (let [op (if *unchecked-math* 61 | unchecked-op op)] 62 | (reduce1 63 | (fn [a b] 64 | `(. clojure.lang.Numbers (~op ~a ~b))) 65 | `(. clojure.lang.Numbers (~op ~x ~y)) 66 | more)))))) 67 | 68 | (defn ^:private >1? [n] 69 | (clojure.lang.Numbers/gt n 1)) 70 | 71 | (defn ^:private >0? [n] 72 | (clojure.lang.Numbers/gt n 0)) 73 | 74 | 75 | --------------------------------------------------------------------------------