|
1 |
| -# Parinfer |
| 1 | +# Parinfer.js |
2 | 2 |
|
3 |
| -_parentheses inference for Lisp_ |
| 3 | +> Pronounced "par-in-fur". "par" rhymes with car and star; "fur" rhymes with blur and stir |
4 | 4 |
|
5 |
| -<img src="http://zippy.gfycat.com/WeirdOddBluefintuna.gif" width="400"> |
6 |
| - |
7 |
| ---- |
8 |
| - |
9 |
| -<em>See the __[Parinfer Home Page]__ for a complete exploration.</em> |
| 5 | +<em>See the __[Parinfer Home Page]__ for the original animated demo page.</em> |
10 | 6 |
|
11 | 7 | __Parinfer__ is a proof-of-concept editor mode for Lisp programming languages.
|
12 | 8 | It simplifies the way we write Lisp by auto-adjusting parens when indentation
|
13 | 9 | changes and vice versa. The hope is to make basic Lisp-editing easier for
|
14 | 10 | newcomers and experts alike, while still allowing existing plugins like Paredit
|
15 | 11 | to satisfy the need for more advanced operations.
|
16 | 12 |
|
17 |
| -This project is split into two parts: |
| 13 | +## npm package |
| 14 | + |
| 15 | +This library is published on npm under the package name [`parinfer`](https://www.npmjs.com/package/parinfer) |
| 16 | + |
| 17 | +```sh |
| 18 | +## using npm |
| 19 | +npm install parinfer |
| 20 | + |
| 21 | +## using yarn |
| 22 | +yarn add parinfer |
| 23 | +``` |
| 24 | + |
| 25 | +## Usage |
| 26 | + |
| 27 | +Parinfer consists of a couple pure functions of your text, returning new text with |
| 28 | +corrected parens or indentation. |
| 29 | + |
| 30 | +```js |
| 31 | +// 'parinfer' is a global object if not used as Node module. |
| 32 | +var parinfer = require('parinfer'); |
| 33 | + |
| 34 | +// Run Indent Mode on the given text: |
| 35 | +var result = parinfer.indentMode("(def foo [a b"); |
| 36 | +console.log(result.text); |
| 37 | +// prints: |
| 38 | +// (def foo [a b]) |
| 39 | + |
| 40 | +// Run Paren Mode on the given text: |
| 41 | +var result = parinfer.parenMode("(def foo\n[a b\nc])"); |
| 42 | +console.log(result.text); |
| 43 | +// prints: |
| 44 | +// (def foo |
| 45 | +// [a b |
| 46 | +// c]) |
| 47 | +``` |
| 48 | + |
| 49 | +## Integrating with an Editor or REPL |
| 50 | + |
| 51 | +_See [integrating.md]_ |
| 52 | + |
| 53 | +<!-- file links need to be full path to make them work for the NPM readme --> |
| 54 | +[integrating.md]:https://github.com/parinfer/parinfer.js/blob/master/doc/integrating.md |
| 55 | + |
| 56 | +## API |
| 57 | + |
| 58 | +##### `smartMode(text[, options])` |
| 59 | +##### `indentMode(text[, options])` |
| 60 | +##### `parenMode(text[, options])` |
| 61 | + |
| 62 | +Runs |
| 63 | +[Indent Mode](http://shaunlebron.github.io/parinfer/#indent-mode) |
| 64 | +or |
| 65 | +[Paren Mode](http://shaunlebron.github.io/parinfer/#paren-mode) |
| 66 | +on the given text. Smart Mode is currently something in between. |
| 67 | + |
| 68 | +Arguments: |
| 69 | + |
| 70 | +- `text` is the full text input. |
| 71 | +- `options` is an object with the following properties: |
| 72 | + - `commentChars` - a character (ie: string of length 1) or array of characters that should be considered comments in the code (defaults to `[";"]`) |
| 73 | + - `cursorLine` - zero-based line number of the cursor |
| 74 | + - `cursorX` - zero-based x-position of the cursor |
| 75 | + - `prevCursorLine` and `prevCursorX` is required by Smart Mode (previous cursor position) |
| 76 | + - `selectionStartLine` - first line of the current selection |
| 77 | + - `changes` - ordered array of change objects with the following: |
| 78 | + - `lineNo` - starting line number of the change |
| 79 | + - `x` - starting x of the change |
| 80 | + - `oldText` - original text that was replaced |
| 81 | + - `newText` - new text that replaced the original text |
| 82 | + - `forceBalance` - employ the aggressive paren-balancing rules from v1 (defaults to false) |
| 83 | + - `partialResult` - return partially processed text/cursor if an error occurs (defaults to false) |
| 84 | + |
| 85 | +Returns an object with the following properties: |
| 86 | + |
| 87 | +- `success` is a boolean indicating if the input was properly formatted enough to create a valid result |
| 88 | +- `text` is the full text output (if `success` is false, returns original text unless `partialResult` is enabled) |
| 89 | +- `cursorX`/`cursorLine` is the new position of the cursor (since parinfer may shift it around) |
| 90 | +- `error` is an object populated if `success` is false: |
| 91 | + - `name` is the name of the error, which will be any of the following: |
| 92 | + - `"quote-danger"` |
| 93 | + - `"eol-backslash"` |
| 94 | + - `"unclosed-quote"` |
| 95 | + - `"unclosed-paren"` |
| 96 | + - `"unmatched-close-paren"` |
| 97 | + - `"unhandled"` |
| 98 | + - `message` is a message describing the error |
| 99 | + - `lineNo` is a zero-based line number where the error occurred |
| 100 | + - `x` is a zero-based column where the error occurred |
| 101 | + - `extra` has lineNo and x of open-paren for `unmatched-close-paren` |
| 102 | +- `tabStops` is an array of objects representing [Tab stops], which is |
| 103 | + populated if a cursor position or selection is supplied. We identify tab |
| 104 | + stops at relevant open-parens, and supply the following extra information so |
| 105 | + you may compute extra tab stops for one-space or two-space indentation |
| 106 | + conventions based on the type of open-paren. |
| 107 | + - `x` is a zero-based x-position of the tab stop |
| 108 | + - `argX` position of the first argument after `x` (e.g. position of bar in `(foo bar`) |
| 109 | + - `lineNo` is a zero-based line number of the open-paren responsible for the tab stop |
| 110 | + - `ch` is the character of the open-paren responsible for the tab stop (e.g. `(`,`[`,`{`) |
| 111 | +- `parenTrails` is an array of object representing the [Paren Trails] at the end |
| 112 | + of each line that Parinfer may move |
| 113 | + - `lineNo` is a zero-based line number |
| 114 | + - `startX` is a zero-based x-position of the first close-paren |
| 115 | + - `endX` is a zero-based x-position after the last close-paren |
| 116 | + |
| 117 | +[Tab stops]:https://en.wikipedia.org/wiki/Tab_stop |
| 118 | + |
| 119 | +## Test API |
| 120 | + |
| 121 | +You can use our testing API for a fast, visual way to specify options and verify |
| 122 | +results. This allows all metadata required by and returned from Parinfer to be |
| 123 | +specified inside the text using our annotation syntax. |
| 124 | + |
| 125 | +__[See here for Annotation Syntax details][annotation syntax]__ |
| 126 | + |
| 127 | +```js |
| 128 | +// Currently only supported in Node |
| 129 | +var parinferTest = require('parinfer/test'); |
| 130 | +``` |
| 131 | + |
| 132 | +### Test Example |
| 133 | + |
| 134 | +The following code is a quick way to verify behavior of Indent Mode. |
| 135 | +The `|` is parsed as the cursor and removed from the text before processing. |
| 136 | + |
| 137 | +```js |
| 138 | +parinterTest.indentMode(` |
| 139 | +(def foo |
| 140 | + "| |
| 141 | + "(a b) |
| 142 | + c") |
| 143 | +`); |
| 144 | +``` |
| 145 | + |
| 146 | +This returns the processed text below, with `|` reinserted to show cursor |
| 147 | +result, and an `^ error` annotation line since a string was not closed: |
| 148 | + |
| 149 | +``` |
| 150 | +(def foo |
| 151 | + "| |
| 152 | + "(a b) |
| 153 | + c") |
| 154 | + ^ error: unclosed-quote |
| 155 | +``` |
| 156 | + |
| 157 | +### Test Usage |
| 158 | + |
| 159 | +```js |
| 160 | +parinferTest.smartMode(inputText, extras); // returns string |
| 161 | +parinferTest.indentMode(inputText, extras); // returns string |
| 162 | +parinferTest.parenMode(inputText, extras); // returns string |
| 163 | +``` |
| 164 | + |
| 165 | +`extras` allows us to specify options for which there is no annotation syntax yet: |
| 166 | + |
| 167 | +- `forceBalance` |
| 168 | +- `partialResult` |
| 169 | +- `printTabStops` |
| 170 | + |
| 171 | +You can also use the input/output functions directly: |
| 172 | + |
| 173 | +```js |
| 174 | +parinferTest.parseInput(inputText, extras); // returns {text, options} |
| 175 | +parinferTest.parseOutput(inputText, extras); // returns result |
| 176 | + |
| 177 | +parinferTest.printOutput(result, extras); // returns string |
| 178 | + |
| 179 | +// `result` is returned by main indentMode or parenMode functions |
| 180 | +``` |
| 181 | + |
| 182 | +## Development |
| 183 | + |
| 184 | +__Code__: [`parinfer.js`] is implemented in ECMAScript 5 for easy speed and portability. Also: |
| 185 | + |
| 186 | +__Documentation__: Code is documented in [`code.md`]. |
| 187 | + |
| 188 | +__Performance__: To run a performance stress test: |
| 189 | + |
| 190 | +``` |
| 191 | +node test/perf.js |
| 192 | +``` |
| 193 | + |
| 194 | +__Testing__: See [`test/cases/`] directory for testing details. Or just run the following: |
| 195 | + |
| 196 | +``` |
| 197 | +npm install |
| 198 | +npm test |
| 199 | +``` |
| 200 | + |
| 201 | +<!-- file links need to be full path to make them work for the NPM readme --> |
| 202 | +[`parinfer.js`]:https://github.com/parinfer/parinfer.js/blob/master/parinfer.js |
| 203 | +[`code.md`]:https://github.com/parinfer/parinfer.js/blob/master/doc/code.md |
| 204 | +[`test/cases/`]:https://github.com/parinfer/parinfer.js/tree/master/test/cases |
| 205 | +[annotation syntax]:https://github.com/parinfer/parinfer.js/tree/master/test/cases#annotations |
| 206 | +[Paren Trails]:https://github.com/parinfer/parinfer.js/blob/master/doc/code.md#paren-trail |
18 | 207 |
|
19 |
| -- __[Parinfer Lib](https://github.com/shaunlebron/parinfer/tree/master/lib)__ - the _editor-agnostic_ library |
20 |
| -- __[Parinfer Site](https://github.com/shaunlebron/parinfer/tree/master/site)__ - the code for the website |
| 208 | +## A stable core for editor plugins |
21 | 209 |
|
22 |
| -## How to Use It! |
| 210 | +> __Want to use Parinfer on a team?__ Introduce [Parlinter] as your project's linter! |
23 | 211 |
|
24 |
| -<em>See the __[Parinfer Demo Editor]__ to try the latest version online.</em> |
| 212 | +[Parlinter]:https://github.com/shaunlebron/parlinter |
25 | 213 |
|
26 |
| -[Parinfer Demo Editor]:http://shaunlebron.github.io/parinfer/demo |
| 214 | +The behavior and implementation of the Parinfer library is stable and |
| 215 | +canonicalized. To allow different editors to use it, we have ported the |
| 216 | +implementation to the languages required by the plugin |
| 217 | +APIs of most major text editors. All language ports pass the same |
| 218 | +comprehensive test suite to help ensure consistent behavior. |
27 | 219 |
|
28 |
| -Parinfer is still in early development. Several people have started |
29 |
| -integrating it into code editors at various stages of development. |
| 220 | +| implemented in | link | relevant editor | |
| 221 | +|:---------------|:-------------------|:-------------------------| |
| 222 | +| JavaScript | parinfer.js (here) | Atom, VSCode, LightTable | |
| 223 | +| Rust | [parinfer-rust] | Vim | |
| 224 | +| Python | [parinfer.py] | Sublime Text | |
| 225 | +| Kotlin (JVM) | [parinfer-jvm] | Cursive IDE, Nightcode | |
| 226 | +| Emacs Lisp | [parinfer-elisp] | Emacs | |
| 227 | +| Vim Script | [parinfer-viml] | Vim | |
| 228 | +| Lua | [parinfer-lua] | TextAdept | |
30 | 229 |
|
31 |
| -- [atom-parinfer] for [Atom] |
32 |
| -- [parinfer-rust] for [Vim] and [Neovim] |
33 |
| -- [nvim-parinfer.js] for [Neovim] |
34 |
| -- [vim-parinfer] for [Vim] |
35 |
| -- [vscode-parinfer] for [Visual Studio Code] |
36 |
| -- [sublime-text-parinfer] for [Sublime Text] |
37 |
| -- [parinfer-mode] for [Emacs] |
38 |
| -- [Parinfer layer] for [Spacemacs] - _in progress_ |
39 |
| -- [parinfer-codemirror] for [CodeMirror] - _used on Parinfer's homepage_ |
40 |
| -- [lt_parinfer] for [Light Table] |
41 |
| -- native support in [Nightcode] |
| 230 | +[parinfer-rust]:https://github.com/eraserhd/parinfer-rust |
| 231 | +[parinfer.py]:https://github.com/oakmac/parinfer.py |
| 232 | +[parinfer-jvm]:https://github.com/oakmac/parinfer-jvm |
| 233 | +[parinfer-elisp]:https://github.com/oakmac/parinfer-elisp |
| 234 | +[parinfer-viml]:https://github.com/oakmac/parinfer-viml |
| 235 | +[parinfer-lua]:https://github.com/oakmac/parinfer-lua |
42 | 236 |
|
43 |
| -Parinfer will soon be available for some REPL environments as well: |
| 237 | +## Status Update 2019 (Smart Mode) |
44 | 238 |
|
45 |
| -- [Replete] for iOS |
46 |
| -- [Dirac DevTools] for Google Chrome |
47 |
| -- [Reepl] for the browser |
| 239 | +**Smart Mode** (available in [demo]) was an experiment to eliminate switching between Indent Mode and Paren Mode—by looking at a change and determining whether to run Indent Mode or Paren Mode. It is well tested and worked great in our sandboxes, but we found that the majority of editor APIs do not allow us to integrate Smart Mode's rules _safely_. |
48 | 240 |
|
49 |
| -<em>__[Let me know]__ if you're working on a plugin, or check the [lib readme] for extra guidance. Thanks!</em> |
| 241 | +For example, if we don't catch a search/replace change in multiple locations of your document, but we infer from the next typing operation that we should run Indent Mode, then Smart Mode will make its decision without knowing the previous search/replace operation took place—thereby breaking its promise of choosing the best mode, and unsafely modifying your code. |
50 | 242 |
|
51 |
| -[Let me know]:https://github.com/shaunlebron/parinfer/issues/new?title=new%20plugin |
52 |
| -[lib readme]:lib |
| 243 | +The larger problem is that Smart Mode requires the synchronous interception of _every type of change_ coming from the editor. It must decide the right thing to do for input changes at single/multiple cursors, search/replace, copy/paste, advanced macro operations, buffer refreshes from changes on disk, and maybe some others we haven't thought of yet. The interface for receiving these kinds of changes from the editor are not consistent—they either come in asynchronously or sychronously or _not at all_. This forces us to resort to computing diffs, a lossy mapping from _changes_ to _patches_. |
53 | 244 |
|
54 |
| -[parinfer-rust]:https://github.com/eraserhd/parinfer-rust |
55 |
| -[atom-parinfer]:https://github.com/oakmac/atom-parinfer |
56 |
| -[Atom]:https://atom.io/ |
57 |
| -[nvim-parinfer.js]:https://github.com/snoe/nvim-parinfer.js |
58 |
| -[Neovim]:https://neovim.io/ |
59 |
| -[vscode-parinfer]:https://github.com/narma/vscode-parinfer |
60 |
| -[Visual Studio Code]:https://code.visualstudio.com/ |
61 |
| -[sublime-text-parinfer]:https://github.com/oakmac/sublime-text-parinfer |
62 |
| -[Sublime Text]:http://www.sublimetext.com/ |
63 |
| -[parinfer-mode]:https://github.com/DogLooksGood/parinfer-mode |
64 |
| -[Emacs]:https://www.gnu.org/software/emacs/ |
65 |
| -[vim-parinfer]:https://github.com/bhurlow/vim-parinfer |
66 |
| -[Vim]:http://www.vim.org/ |
67 |
| -[parinfer-codemirror]:https://github.com/shaunlebron/parinfer-codemirror |
68 |
| -[CodeMirror]:https://codemirror.net/ |
69 |
| -[lt_parinfer]:https://github.com/mauricioszabo/lt_parinfer |
70 |
| -[Light Table]:http://lighttable.com/ |
71 |
| -[Nightcode]:https://github.com/oakes/Nightcode |
72 |
| -[Parinfer layer]:https://github.com/syl20bnr/spacemacs/issues/5574 |
73 |
| -[Spacemacs]:http://spacemacs.org/ |
74 |
| - |
75 |
| -[Replete]:https://github.com/mfikes/replete |
76 |
| -[Dirac DevTools]:https://github.com/binaryage/dirac |
77 |
| -[Reepl]:http://jaredforsyth.com/reepl/ |
78 |
| - |
79 |
| -[Paredit]:http://danmidwood.com/content/2014/11/21/animated-paredit.html |
80 |
| -[Parinfer Home Page]:http://shaunlebron.github.io/parinfer/ |
81 |
| - |
82 |
| ---- |
| 245 | +We have made separate attempts to implement Smart Mode in Cursive, Vim, Atom, and Emacs through some wrangling that made integration very difficult and delicate, and ultimately incomplete. _Editors simply are not yet designed to allow an ideal version of Parinfer to exist_—probably because nothing like Parinfer has demanded them before. The practicality of requesting these (likely non-trivial) changes on the editor is to be determined. |
| 246 | + |
| 247 | +## License |
83 | 248 |
|
84 | 249 | [MIT License](LICENSE.md)
|
0 commit comments