├── dev-resources └── .gitignore ├── figwheel-main.edn ├── docs ├── fonts │ ├── FiraCode-Bold.otf │ ├── FiraCode-Light.otf │ ├── FiraCode-Medium.otf │ ├── FiraCode-Retina.otf │ ├── FiraCode-Regular.otf │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── glyphicons-halflings-regular.woff2 │ └── glyphicons-halflings-regular.svg ├── style.css ├── paren-soup-dark.css └── paren-soup-light.css ├── .gitignore ├── dev.clj ├── dev.cljs.edn ├── project.clj ├── deps.edn ├── src └── parinferish │ ├── examples.cljs │ └── core.cljc ├── UNLICENSE ├── README.md ├── prod.clj ├── test.clj └── test-resources ├── smart-mode.md ├── paren-mode.md └── indent-mode.md /dev-resources/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /figwheel-main.edn: -------------------------------------------------------------------------------- 1 | {:open-url "http://localhost:5000/cljs/parinferish.core" 2 | :clean-outputs true} 3 | -------------------------------------------------------------------------------- /docs/fonts/FiraCode-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oakes/parinferish/HEAD/docs/fonts/FiraCode-Bold.otf -------------------------------------------------------------------------------- /docs/fonts/FiraCode-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oakes/parinferish/HEAD/docs/fonts/FiraCode-Light.otf -------------------------------------------------------------------------------- /docs/fonts/FiraCode-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oakes/parinferish/HEAD/docs/fonts/FiraCode-Medium.otf -------------------------------------------------------------------------------- /docs/fonts/FiraCode-Retina.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oakes/parinferish/HEAD/docs/fonts/FiraCode-Retina.otf -------------------------------------------------------------------------------- /docs/fonts/FiraCode-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oakes/parinferish/HEAD/docs/fonts/FiraCode-Regular.otf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data_readers.clj 2 | hs_err_pid*.log 3 | pom.xml 4 | pom.xml.asc 5 | **/gen 6 | **/target 7 | .* 8 | !.gitignore 9 | -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oakes/parinferish/HEAD/docs/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oakes/parinferish/HEAD/docs/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oakes/parinferish/HEAD/docs/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oakes/parinferish/HEAD/docs/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /dev.clj: -------------------------------------------------------------------------------- 1 | (require 2 | '[figwheel.main :as figwheel] 3 | '[dynadoc.core :as dynadoc]) 4 | 5 | (dynadoc/-main "--port" "5000") 6 | (figwheel/-main "--build" "dev") 7 | 8 | -------------------------------------------------------------------------------- /dev.cljs.edn: -------------------------------------------------------------------------------- 1 | {:main parinferish.examples 2 | :optimizations :simple 3 | :output-to "dev-resources/dynadoc-extend/main.js" 4 | :output-dir "dev-resources/dynadoc-extend/main.out" 5 | :asset-path "/main.out"} 6 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject parinferish "0.9.0-SNAPSHOT" 2 | :description "A library for parsing and optionally applying parinfer(ish) to Clojure code" 3 | :url "https://github.com/oakes/parinferish" 4 | :license {:name "Public Domain" 5 | :url "http://unlicense.org/UNLICENSE"} 6 | :repositories [["clojars" {:url "https://clojars.org/repo" 7 | :sign-releases false}]]) 8 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :aliases {:dev {:extra-deps {org.clojure/clojurescript {:mvn/version "1.10.764"} 3 | dynadoc {:mvn/version "RELEASE"} 4 | com.bhauman/figwheel-main {:mvn/version "0.2.11"}} 5 | :extra-paths ["dev-resources"] 6 | :main-opts ["dev.clj"]} 7 | :prod {:extra-deps {leiningen {:mvn/version "2.9.0"}} 8 | :main-opts ["prod.clj"]} 9 | :test {:extra-paths ["test-resources"] 10 | :main-opts ["test.clj"]}}} 11 | -------------------------------------------------------------------------------- /src/parinferish/examples.cljs: -------------------------------------------------------------------------------- 1 | (ns parinferish.examples 2 | (:require parinferish.core 3 | dynadoc.core) 4 | (:require-macros [dynadoc.example :refer [defexample defexamples]])) 5 | 6 | (defexample parinferish.core/parse 7 | (parse "(+ 1 2)")) 8 | 9 | (defexamples parinferish.core/flatten 10 | ["Run indent mode" 11 | (parinferish.core/flatten (parinferish.core/parse "(foo a" {:mode :indent}))] 12 | ["Run paren mode" 13 | (parinferish.core/flatten (parinferish.core/parse "(foo\na)" {:mode :paren}))] 14 | ["Run smart mode" 15 | (parinferish.core/flatten (parinferish.core/parse "([1\n 2\n 3]" {:mode :smart :cursor-line 0 :cursor-column 1}))]) 16 | 17 | (defexample parinferish.core/diff 18 | (parinferish.core/diff (parinferish.core/parse "([1\n 2\n 3]" {:mode :smart :cursor-line 0 :cursor-column 1}))) 19 | 20 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Clojars Project](https://img.shields.io/clojars/v/parinferish.svg)](https://clojars.org/parinferish) 2 | 3 | ## Introduction 4 | 5 | A Clojure and ClojureScript library that parses code and optionally applies parinfer(ish) to it. It works pretty much like parinfer and implements the three modes (indent, paren, and smart) but I'm not necessarily trying to make it behave the exact same way. 6 | 7 | ### [Try the interactive docs!](https://oakes.github.io/parinferish/) 8 | 9 | Compared to the real parinfer, parinferish... 10 | 11 | 1. Acts as a general purpose clojure parser, because its `parse` function returns the code a hiccup-style data structure, not merely a string. In fact, adjusting parens/indentation is just an optional step. 12 | 2. Doesn't ever change code when only the cursor position changes (and only smart mode requires the cursor position at all) 13 | 3. Has no basis in math, logic, or sanity...the only basis for any behavior is my own preferences and bugs. 14 | 15 | See [html-soup](https://github.com/oakes/html-soup) for an example of a library that uses parinferish as a parser. 16 | 17 | ## Licensing 18 | 19 | All files that originate from this project are dedicated to the public domain. I would love pull requests, and will assume that they are also dedicated to the public domain. 20 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Fira Code'; 3 | src: url('fonts/FiraCode-Regular.otf') format('opentype'); 4 | } 5 | html, body, .paren-soup { 6 | -webkit-font-feature-settings: "liga" on, "calt" on; 7 | -webkit-font-smoothing: antialiased; 8 | text-rendering: optimizeLegibility; 9 | font-family: 'Fira Code'; 10 | font-size: 18px; 11 | height: 100%; 12 | margin: 0; 13 | } 14 | .nses { 15 | position: fixed; 16 | width: 300px; 17 | left: 0px; 18 | top: 50px; 19 | bottom: 0px; 20 | margin: 5px; 21 | overflow: auto; 22 | white-space: nowrap; 23 | } 24 | .vars { 25 | position: fixed; 26 | top: 0px; 27 | left: 300px; 28 | right: 0px; 29 | bottom: 0px; 30 | margin: 10px; 31 | padding: 5px; 32 | overflow: auto; 33 | } 34 | .var-info { 35 | margin-top: 30px; 36 | margin-bottom: 30px; 37 | } 38 | .doc { 39 | white-space: pre; 40 | } 41 | .section { 42 | margin-top: 10px; 43 | margin-bottom: 10px; 44 | } 45 | .tag { 46 | float: left; 47 | margin-right: 5px; 48 | border-radius: 5px; 49 | background-color: lightgreen; 50 | } 51 | .search { 52 | position: fixed; 53 | left: 0px; 54 | top: 0px; 55 | width: 300px; 56 | height: 40px; 57 | margin: 5px; 58 | font-size: 30px; 59 | } 60 | .var { 61 | margin-left: 30px; 62 | } 63 | .footer { 64 | text-align: center; 65 | margin-top: 30px; 66 | font-size: 14px; 67 | } 68 | .export { 69 | text-align: right; 70 | float: right; 71 | font-size: 20px; 72 | } 73 | .card { 74 | width: 100%; 75 | height: 200px; 76 | border: 1px solid black; 77 | margin-top: 10px; 78 | margin-bottom: 10px; 79 | } 80 | canvas { 81 | width: 100%; 82 | height: 100%; 83 | } 84 | -------------------------------------------------------------------------------- /prod.clj: -------------------------------------------------------------------------------- 1 | (defmulti task first) 2 | 3 | (defmethod task :default 4 | [_] 5 | (let [all-tasks (-> task methods (dissoc :default) keys sort) 6 | interposed (->> all-tasks (interpose ", ") (apply str))] 7 | (println "Unknown or missing task. Choose one of:" interposed) 8 | (System/exit 1))) 9 | 10 | (require 11 | '[clojure.string :as str] 12 | '[leiningen.core.project :as p :refer [defproject]] 13 | '[leiningen.install :refer [install]] 14 | '[leiningen.deploy :refer [deploy]]) 15 | 16 | (defn read-project-clj [] 17 | (p/ensure-dynamic-classloader) 18 | (-> "project.clj" load-file var-get)) 19 | 20 | (defn read-deps-edn [aliases-to-include] 21 | (let [{:keys [paths deps aliases]} (-> "deps.edn" slurp clojure.edn/read-string) 22 | deps (->> (select-keys aliases aliases-to-include) 23 | vals 24 | (mapcat :extra-deps) 25 | (into deps) 26 | (map (fn parse-coord [coord] 27 | (let [[artifact info] coord 28 | s (str artifact)] 29 | (if-let [i (str/index-of s "$")] 30 | [(symbol (subs s 0 i)) 31 | (assoc info :classifier (subs s (inc i)))] 32 | coord)))) 33 | (reduce 34 | (fn [deps [artifact info]] 35 | (if-let [version (:mvn/version info)] 36 | (conj deps 37 | (transduce cat conj [artifact version] 38 | (select-keys info [:exclusions :classifier]))) 39 | deps)) 40 | [])) 41 | paths (->> (select-keys aliases aliases-to-include) 42 | vals 43 | (mapcat :extra-paths) 44 | (into paths))] 45 | {:dependencies deps 46 | :source-paths [] 47 | :resource-paths paths})) 48 | 49 | (defmethod task "install" 50 | [_] 51 | (-> (read-project-clj) 52 | (merge (read-deps-edn [])) 53 | p/init-project 54 | install) 55 | (System/exit 0)) 56 | 57 | (defmethod task "deploy" 58 | [_] 59 | (-> (read-project-clj) 60 | (merge (read-deps-edn [])) 61 | p/init-project 62 | (deploy "clojars")) 63 | (System/exit 0)) 64 | 65 | ;; entry point 66 | 67 | (task *command-line-args*) 68 | 69 | -------------------------------------------------------------------------------- /docs/paren-soup-dark.css: -------------------------------------------------------------------------------- 1 | html, body, .paren-soup { 2 | background-color: #272b30; 3 | } 4 | 5 | button { 6 | color: white; 7 | } 8 | 9 | .paren-soup { 10 | font-family: monospace; 11 | color: lightgray; 12 | } 13 | 14 | .paren-soup .error-text { 15 | position: fixed; 16 | background-color: #293134; 17 | padding-left: 10px; 18 | } 19 | 20 | .paren-soup .instarepl { 21 | float: left; 22 | position: relative; 23 | display: list-item; 24 | padding-right: 5px; 25 | min-width: 200px; 26 | max-width: 200px; 27 | } 28 | 29 | .paren-soup .instarepl .result { 30 | position: absolute; 31 | overflow: hidden; 32 | background-color: darkgreen; 33 | outline: 1px solid; 34 | max-width: inherit; 35 | word-wrap: break-word; 36 | right: 0px; 37 | opacity: 0.7; 38 | white-space: break-spaces; 39 | } 40 | 41 | .paren-soup .instarepl .result:hover { 42 | cursor: pointer; 43 | height: auto !important; 44 | z-index: 1; 45 | opacity: 1; 46 | } 47 | 48 | .paren-soup .instarepl .error { 49 | background-color: darkred; 50 | } 51 | 52 | .paren-soup .numbers { 53 | float: left; 54 | padding: 0px 5px; 55 | text-align: right; 56 | opacity: 0.7; 57 | border-right: 1px solid #ddd; 58 | } 59 | 60 | .paren-soup .content { 61 | margin: 0px; 62 | padding: 0px 5px; 63 | outline: 0px solid transparent; 64 | white-space: pre; 65 | word-wrap: normal; 66 | overflow: scroll; 67 | } 68 | 69 | .paren-soup .content .symbol { 70 | color: white; 71 | } 72 | 73 | .paren-soup .content .number { 74 | color: gold; 75 | } 76 | 77 | .paren-soup .content .string { 78 | color: darksalmon; 79 | } 80 | 81 | .paren-soup .content .keyword { 82 | color: dodgerblue; 83 | } 84 | 85 | .paren-soup .content .nil { 86 | color: lightblue; 87 | } 88 | 89 | .paren-soup .content .boolean { 90 | color: lightblue; 91 | } 92 | 93 | .paren-soup .content .error { 94 | display: none; 95 | width: 0.9em; 96 | height: 0.9em; 97 | background-color: red; 98 | border-radius: 0.3em; 99 | } 100 | 101 | .paren-soup .content .rainbow-0 { 102 | color: aqua; 103 | } 104 | 105 | .paren-soup .content .rainbow-1 { 106 | color: orange; 107 | } 108 | 109 | .paren-soup .content .rainbow-2 { 110 | color: cornflowerblue; 111 | } 112 | 113 | .paren-soup .content .rainbow-3 { 114 | color: fuchsia; 115 | } 116 | 117 | .paren-soup .content .rainbow-4 { 118 | color: lime; 119 | } 120 | -------------------------------------------------------------------------------- /docs/paren-soup-light.css: -------------------------------------------------------------------------------- 1 | html, body, .paren-soup { 2 | background-color: #f7f7f7; 3 | } 4 | 5 | .paren-soup { 6 | font-size: 18px; 7 | font-family: monospace; 8 | color: gray; 9 | caret-color: black; 10 | } 11 | 12 | .paren-soup .error-text { 13 | position: fixed; 14 | background-color: white; 15 | padding-left: 10px; 16 | } 17 | 18 | .paren-soup .instarepl { 19 | float: left; 20 | position: relative; 21 | display: list-item; 22 | padding-right: 5px; 23 | min-width: 200px; 24 | max-width: 200px; 25 | } 26 | 27 | .paren-soup .instarepl .result { 28 | position: absolute; 29 | overflow: hidden; 30 | background-color: lightgreen; 31 | outline: 1px solid; 32 | max-width: inherit; 33 | word-wrap: break-word; 34 | right: 0px; 35 | opacity: 0.7; 36 | white-space: break-spaces; 37 | } 38 | 39 | .paren-soup .instarepl .result:hover { 40 | cursor: pointer; 41 | height: auto !important; 42 | z-index: 1; 43 | opacity: 1; 44 | } 45 | 46 | .paren-soup .instarepl .error { 47 | background-color: pink; 48 | } 49 | 50 | .paren-soup .numbers { 51 | float: left; 52 | padding: 0px 5px; 53 | text-align: right; 54 | opacity: 0.7; 55 | border-right: 1px solid #ddd; 56 | } 57 | 58 | .paren-soup .content { 59 | margin: 0px; 60 | padding: 0px 5px; 61 | outline: 0px solid transparent; 62 | white-space: pre; 63 | word-wrap: normal; 64 | overflow: scroll; 65 | background-color: white; 66 | } 67 | 68 | .paren-soup .content .symbol { 69 | color: black; 70 | } 71 | 72 | .paren-soup .content .number { 73 | color: gold; 74 | } 75 | 76 | .paren-soup .content .string { 77 | color: red; 78 | } 79 | 80 | .paren-soup .content .keyword { 81 | color: blue; 82 | } 83 | 84 | .paren-soup .content .nil { 85 | color: lightblue; 86 | } 87 | 88 | .paren-soup .content .boolean { 89 | color: lightblue; 90 | } 91 | 92 | .paren-soup .content .error { 93 | display: none; 94 | width: 0.9em; 95 | height: 0.9em; 96 | background-color: red; 97 | border-radius: 0.3em; 98 | } 99 | 100 | .paren-soup .content .rainbow-0 { 101 | color: aqua; 102 | } 103 | 104 | .paren-soup .content .rainbow-1 { 105 | color: orange; 106 | } 107 | 108 | .paren-soup .content .rainbow-2 { 109 | color: cornflowerblue; 110 | } 111 | 112 | .paren-soup .content .rainbow-3 { 113 | color: fuchsia; 114 | } 115 | 116 | .paren-soup .content .rainbow-4 { 117 | color: lime; 118 | } 119 | -------------------------------------------------------------------------------- /test.clj: -------------------------------------------------------------------------------- 1 | (require 2 | '[clojure.string :as str] 3 | '[clojure.java.io :as io] 4 | '[parinferish.core :as ps]) 5 | 6 | (defn parse-cases [mode] 7 | (-> (str (name mode) "-mode.md") 8 | io/resource 9 | slurp 10 | (str/replace #"\n.*\^ error:.*" "") 11 | (str/split #"```") 12 | (->> (filter #(or (str/starts-with? % "in\n") 13 | (str/starts-with? % "out\n"))) 14 | (map str/trim) 15 | (partition 2) 16 | (map (fn [[in out :as pair]] 17 | (when-not (str/starts-with? in "in") 18 | (throw (ex-info (str "Expected \"in\":" \newline in) {}))) 19 | (when-not (str/starts-with? out "out") 20 | (throw (ex-info (str "Expected \"out\":" \newline out) {}))) 21 | (let [[new-in new-out] (mapv (fn [s] 22 | (subs s (inc (str/index-of s "\n")))) 23 | pair) 24 | ret {:in new-in :out new-out :opts {:mode mode}}] 25 | (if (= :smart mode) 26 | (let [pipe-pos (or (str/index-of new-in "|") 27 | (throw (ex-info (str "No pipe found:" \newline new-in) {}))) 28 | until-pipe (subs new-in 0 pipe-pos)] 29 | (update ret :opts assoc 30 | :cursor-line (->> until-pipe 31 | (re-seq #"\n") 32 | count) 33 | :cursor-column (-> until-pipe 34 | (subs (or (some-> (str/last-index-of until-pipe "\n") inc) 35 | 0)) 36 | count))) 37 | ret))))))) 38 | 39 | (doseq [mode [:indent :paren :smart]] 40 | (println "Testing" (name mode) "mode") 41 | (let [*success-count (atom 0)] 42 | (doseq [{:keys [in out opts]} (parse-cases mode)] 43 | (let [in (str/replace in #"\|" "") 44 | out (str/replace out #"\|" "") 45 | res (ps/flatten (ps/parse in opts))] 46 | (if (= res out) 47 | (swap! *success-count inc) 48 | (do 49 | (println "Test failed") 50 | (println "Options: " opts) 51 | (println "Input: " (pr-str in)) 52 | (println "Output: " (pr-str res)) 53 | (println "Expected:" (pr-str out)) 54 | (println))))) 55 | (println @*success-count "tests passed"))) 56 | 57 | -------------------------------------------------------------------------------- /test-resources/smart-mode.md: -------------------------------------------------------------------------------- 1 | # Smart Mode 2 | 3 | ## Leading Close-Parens 4 | 5 | Leading close-parens can cause many problems that can be fixed by paren mode, 6 | so we exit to paren mode when they are detected. 7 | 8 | For example, it is convenient to keep trailing parens in front of the cursor 9 | after pressing enter or after deleting everything behind them: 10 | 11 | ```in 12 | (let [a 1 13 | |]) 14 | ``` 15 | 16 | ```out 17 | (let [a 1 18 | |]) 19 | ``` 20 | 21 | Moving the cursor away: 22 | 23 | ```in-disable 24 | (let [a 1 25 | ]); <-- spaces 26 | ``` 27 | 28 | ```out-disable 29 | (let [a 1]) 30 | ; <-- spaces 31 | ``` 32 | 33 | **NOTE: Parinferish behaves differently.** It does not change any code when the cursor moves: 34 | 35 | But we also need safety from inadvertent AST breakage. For example, 36 | Indent Mode should allow this intermediate state: 37 | 38 | ```in 39 | (let [a 1 40 | |] (+ a 2)) 41 | ``` 42 | 43 | ```out 44 | (let [a 1 45 | |] (+ a 2)) 46 | ``` 47 | 48 | Moving the cursor away will cause Indent Mode to still detect the leading 49 | close-paren, exit to Paren Mode, then fix the spacing to prevent inadvertent 50 | breakage. 51 | 52 | ```in-disable 53 | (let [a 1 54 | ] (+ a 2)) 55 | ``` 56 | 57 | ```out-disable 58 | (let [a 1] 59 | (+ a 2)) 60 | ``` 61 | 62 | **NOTE: Parinferish behaves differently.** It does not change any code when the cursor moves: 63 | 64 | To prevent weird things, indentation needs to be locked to respect 65 | the leading close-paren. Exiting to Paren Mode allows this and prevents further 66 | AST breakage. 67 | 68 | ```in 69 | (let [a 1 70 | |] (+ a 2)) 71 | ``` 72 | 73 | ```out-disable 74 | (let [a 1 75 | |] (+ a 2)) 76 | ``` 77 | 78 | **NOTE: Parinferish behaves differently.** It will not allow the end bracket to be there: 79 | 80 | ```out 81 | (let [a 1] 82 | |(+ a 2)) 83 | ``` 84 | 85 | Moving cursor to the right progressively moves leading close-parens behind it 86 | to their normal positions: 87 | 88 | ```in 89 | (let [a 1 90 | ]|) 91 | ``` 92 | 93 | ```out-disable 94 | (let [a 1] 95 | |) 96 | ``` 97 | 98 | **NOTE: Parinferish behaves differently.** It does not move the end bracket: 99 | 100 | ```out 101 | (let [a 1 102 | ]|) 103 | ``` 104 | 105 | When in Paren Mode we must abide by its rules to stay balanced. 106 | 107 | As a courtesy, unmatched close-parens in a paren trail at the beginning of a 108 | line are auto-removed (only when paren mode is triggered from smart mode). 109 | 110 | ```in 111 | |) 112 | ``` 113 | 114 | ```out 115 | | 116 | ``` 117 | 118 | ```in 119 | (foo 120 | |)) 121 | ``` 122 | 123 | ```out 124 | (foo 125 | |) 126 | ``` 127 | 128 | ```in 129 | (foo 130 | }|) 131 | ``` 132 | 133 | ```out 134 | (foo 135 | |) 136 | ``` 137 | 138 | Likewise: 139 | 140 | ```in 141 | (foo 142 | ) foo} bar| 143 | ``` 144 | 145 | ```out-disable 146 | (foo 147 | ) foo} bar| 148 | ^ error: unmatched-close-paren 149 | ``` 150 | 151 | **NOTE: Parinferish behaves differently.** It removes the invalid end bracket: 152 | 153 | ```out 154 | (foo 155 | ) foo bar| 156 | ``` 157 | 158 | ```in 159 | (foo 160 | ) (bar| 161 | ``` 162 | 163 | ```out-disable 164 | (foo 165 | ) (bar| 166 | ^ error: unclosed-paren 167 | ``` 168 | 169 | **NOTE: Parinferish behaves differently.** It adds the necessary paren: 170 | 171 | ```out 172 | (foo 173 | ) (bar|) 174 | ``` 175 | 176 | 177 | ## Changes 178 | 179 | Indent a single-line expression to enter a sibling: 180 | 181 | ```in 182 | (foo (bar) 183 | |baz) 184 | ``` 185 | 186 | ```out 187 | (foo (bar 188 | |baz)) 189 | ``` 190 | 191 | Dedent multi-line expression to leave its parent: 192 | 193 | ```in 194 | (foo 195 | |{:a 1 196 | :b 2}) 197 | ``` 198 | 199 | ```out-disable 200 | (foo) 201 | {:a 1 202 | :b 2} 203 | ``` 204 | 205 | **NOTE: Parinferish behaves differently.** It does not maintain relative indentation: 206 | 207 | ```out 208 | (foo) 209 | {:a 1 210 | :b 2} 211 | ``` 212 | 213 | Indent multi-line expression to enter new parent: 214 | 215 | ```in 216 | (foo) 217 | |{:a 1 218 | :b 2} 219 | ``` 220 | 221 | ```out 222 | (foo 223 | {:a 1 224 | :b 2}) 225 | ``` 226 | 227 | Dedenting an inner line makes it leave parent: 228 | 229 | ```in 230 | (foo 231 | {:a 1 232 | |:b 2}) 233 | ``` 234 | 235 | ```out 236 | (foo 237 | {:a 1}) 238 | :b 2 239 | ``` 240 | 241 | Dedenting a collection will adopt a former sibling line below it: 242 | 243 | ```in 244 | (defn foo 245 | |[a b] 246 | bar) 247 | ``` 248 | 249 | ```out-disable 250 | (defn foo) 251 | [a b 252 | bar] 253 | ``` 254 | 255 | **NOTE: Parinferish behaves differently.** It doesn't perform indent mode on collections starting after the cursor: 256 | 257 | ```out 258 | (defn foo) 259 | [a b] 260 | bar 261 | ``` 262 | 263 | But dedenting a top-level form should not cause a child to adopt a sibling: 264 | 265 | ```in 266 | |(defn foo 267 | [a b] 268 | bar) 269 | ``` 270 | 271 | ```out-disable 272 | (defn foo 273 | [a b] 274 | bar) 275 | ``` 276 | 277 | **NOTE: Parinferish behaves differently.** It does not maintain relative indentation: 278 | 279 | ```out 280 | (defn foo 281 | [a b] 282 | bar) 283 | ``` 284 | 285 | Indented comments move with expressions: 286 | 287 | ```in 288 | |(defn foo 289 | [a b] 290 | ; comment 1 291 | bar) 292 | ; comment 2 293 | ``` 294 | 295 | ```out-disable 296 | (defn foo 297 | [a b] 298 | ; comment 1 299 | bar) 300 | ; comment 2 301 | ``` 302 | 303 | **NOTE: Parinferish behaves differently.** It does not maintain relative indentation: 304 | 305 | ```out 306 | (defn foo 307 | [a b] 308 | ; comment 1 309 | bar) 310 | ; comment 2 311 | ``` 312 | 313 | ## Cursor temporarily preventing sibling adoption 314 | 315 | To prevent undesirable sibling adoption when dedenting, we temporarily keep 316 | a close-paren from moving when the cursor is to the left of its open-paren. 317 | 318 | ```in 319 | (defn foo 320 | |[a b 321 | c d] 322 | bar 323 | baz) 324 | ``` 325 | 326 | ```out-disable 327 | (defn foo) 328 | |[a b 329 | c d] 330 | bar 331 | baz 332 | ``` 333 | 334 | **NOTE: Parinferish behaves differently.** It does not maintain relative indentation: 335 | 336 | ```out 337 | (defn foo) 338 | |[a b 339 | c d] 340 | bar 341 | baz 342 | ``` 343 | 344 | ```in 345 | (defn foo) 346 | |[a b 347 | c d] 348 | bar 349 | baz 350 | ``` 351 | 352 | ```out 353 | (defn foo) 354 | |[a b 355 | c d] 356 | bar 357 | baz 358 | ``` 359 | 360 | ## Multiple Changes 361 | 362 | ```in-disable 363 | (my-fnfoo (if some-condition 364 | -----+++ 365 | println) my-funfoo {:foo 1 366 | ------+++ 367 | :bar 2}) 368 | ``` 369 | 370 | ```out-disable 371 | (foo (if some-condition 372 | println) foo {:foo 1 373 | :bar 2}) 374 | ``` 375 | 376 | ## Resolving Precarious Paren After Dedent 377 | 378 | In the example below, we expect `4` to not be adopted 379 | by any collection inside `(((1 2 3)))`. 380 | 381 | ```in 382 | (|(((1 383 | 2 384 | 3))) 385 | 4) 386 | ``` 387 | 388 | ```out-disable 389 | (|(((1 390 | 2 391 | 3))) 392 | 4) 393 | ``` 394 | 395 | **NOTE: Parinferish behaves differently.** It does not maintain relative indentation: 396 | 397 | ```out 398 | (|(((1 399 | 2 400 | 3))) 401 | 4) 402 | ``` 403 | 404 | When cursor is removed, the precarious parens are resolved by preserving structure 405 | and correcting indentation. 406 | 407 | ```in 408 | (|(((1 409 | 2 410 | 3))) 411 | 4) 412 | ``` 413 | 414 | ```out-disable 415 | ((((1 416 | 2 417 | 3))) 418 | 4) 419 | ``` 420 | 421 | **NOTE: Parinferish behaves differently.** It doesn't perform paren mode on collections starting before the cursor: 422 | 423 | ```out 424 | ((((1 425 | 2 426 | 3))) 427 | 4) 428 | ``` 429 | 430 | ```in 431 | ((|((1 432 | 2 433 | 3))) 434 | 4) 435 | ``` 436 | 437 | ```out-disable 438 | ((|((1 439 | 2 440 | 3))) 441 | 4) 442 | ``` 443 | 444 | **NOTE: Parinferish behaves differently.** It doesn't perform paren mode on collections starting before the cursor: 445 | 446 | ```out 447 | ((|((1 448 | 2 449 | 3)) 450 | 4)) 451 | ``` 452 | 453 | ## Indenting Selected Lines 454 | 455 | Indent only the first line: 456 | 457 | ```in 458 | |(foo 459 | (bar 460 | baz)) 461 | ``` 462 | 463 | ```out-disable 464 | (foo 465 | (bar 466 | baz)) 467 | ``` 468 | 469 | **NOTE: Parinferish behaves differently.** It only corrects indentation with the minimum number of spaces: 470 | 471 | ```out 472 | (foo 473 | (bar 474 | baz)) 475 | ``` 476 | 477 | **NOTE: The rest of the tests below are currently disabled for Parinferish.** 478 | 479 | Indent first two lines: 480 | 481 | ```in-disable 482 | (foo 483 | ++ 484 | (bar 485 | ++ 486 | baz)) 487 | ``` 488 | 489 | ```out-disable 490 | (foo 491 | (bar 492 | baz)) 493 | ``` 494 | 495 | Indent last two lines: 496 | 497 | ```in-disable 498 | (foo 499 | (bar 500 | ++ 501 | baz)) 502 | ++ 503 | ``` 504 | 505 | ```out-disable 506 | (foo 507 | (bar 508 | baz)) 509 | ``` 510 | 511 | 512 | Indent only the first line: 513 | 514 | ```in-disable 515 | |(foo 516 | bar 517 | baz) 518 | ``` 519 | 520 | ```out-disable 521 | (foo 522 | bar 523 | baz) 524 | ``` 525 | 526 | Indent first two lines: 527 | 528 | ```in-disable 529 | (foo 530 | ++ 531 | bar 532 | ++ 533 | baz) 534 | ``` 535 | 536 | ```out-disable 537 | (foo 538 | bar 539 | baz) 540 | ``` 541 | 542 | Indent last two lines: 543 | 544 | ```in-disable 545 | (foo 546 | bar 547 | ++ 548 | baz) 549 | ++ 550 | ``` 551 | 552 | ```out-disable 553 | (foo 554 | bar 555 | baz) 556 | ``` 557 | 558 | ## Multi-change Bug 559 | 560 | [Issue #173](https://github.com/shaunlebron/parinfer/issues/173) 561 | 562 | ```in-disable 563 | ((reduce-kv (fn [m k v] 564 | + 565 | {} 566 | + 567 | {})) 568 | + 569 | ``` 570 | 571 | ```in-disable 572 | ((reduce-kv (fn [m k v] 573 | {} 574 | {}))) 575 | + 576 | ``` 577 | 578 | ```out-disable 579 | ((reduce-kv (fn [m k v]) 580 | {} 581 | {})) 582 | ``` 583 | 584 | [Issue #176](https://github.com/shaunlebron/parinfer/issues/176) 585 | 586 | ```in-disable 587 | (let [a 1] 588 | ( 589 | + 590 | (foo)) 591 | ++ 592 | ``` 593 | 594 | ```in-disable 595 | (let [a 1] 596 | ( 597 | (foo))) 598 | + 599 | ``` 600 | 601 | ```out-disable 602 | (let [a 1] 603 | ( 604 | (foo))) 605 | ``` 606 | 607 | [Issue #177](https://github.com/shaunlebron/parinfer/issues/177) 608 | 609 | ```in-disable 610 | (let [a 1] 611 | 612 | (foo)) 613 | ``` 614 | 615 | ```in-disable 616 | (let [a 1] 617 | (let [a 1] 618 | +++++++++++ 619 | (foo)) 620 | ++++++++ 621 | (foo)) 622 | ``` 623 | 624 | ```in-disable 625 | (let [a 1] 626 | (let [a 1] 627 | (foo)) 628 | ++ 629 | (foo)) 630 | ``` 631 | 632 | ```out-disable 633 | (let [a 1] 634 | (let [a 1] 635 | (foo)) 636 | (foo)) 637 | ``` 638 | 639 | [Issue #179](https://github.com/shaunlebron/parinfer/issues/179) 640 | 641 | ```in-disable 642 | {:a {:b (Integer/valueOf (-> "" 643 | ---------------- 644 | (.length)))}} 645 | ``` 646 | 647 | ```in-disable 648 | {:a {:b (Integer/valueOf (-> "" 649 | ------------- 650 | (.length)))}} 651 | ----------------------------- 652 | ``` 653 | 654 | ```out-disable 655 | {:a {:b (Integer/valueOf (-> "" 656 | (.length)))}} 657 | ``` 658 | -------------------------------------------------------------------------------- /test-resources/paren-mode.md: -------------------------------------------------------------------------------- 1 | # Paren Mode 2 | 3 | ## Indentation Threshold Clamping 4 | 5 | clamp left 6 | 7 | ```in 8 | (let [foo 1] 9 | foo) 10 | ``` 11 | 12 | ```out 13 | (let [foo 1] 14 | foo) 15 | ``` 16 | 17 | clamp right 18 | 19 | ```in 20 | (let [foo 1] 21 | foo) 22 | ``` 23 | 24 | ```out 25 | (let [foo 1] 26 | foo) 27 | ``` 28 | 29 | ```in 30 | (let [foo {:a 1}] 31 | foo) 32 | ``` 33 | 34 | ```out 35 | (let [foo {:a 1}] 36 | foo) 37 | ``` 38 | 39 | sanity check to apply rules to multiple expressions 40 | 41 | ```in 42 | (let [foo 1] 43 | foo) 44 | 45 | (let [foo 1] 46 | foo) 47 | ``` 48 | 49 | ```out 50 | (let [foo 1] 51 | foo) 52 | 53 | (let [foo 1] 54 | foo) 55 | ``` 56 | 57 | ## Relative Indentation 58 | 59 | keep relative indentation of child expressions: 60 | 61 | ```in 62 | (let [foo [1 2 3]] 63 | (-> foo 64 | (map inc))) 65 | ``` 66 | 67 | ```out-disable 68 | (let [foo [1 2 3]] 69 | (-> foo 70 | (map inc))) 71 | ``` 72 | 73 | **NOTE: Parinferish behaves differently.** It does not maintain relative indentation: 74 | 75 | ```out 76 | (let [foo [1 2 3]] 77 | (-> foo 78 | (map inc))) 79 | ``` 80 | 81 | ## Leading Close-Paren 82 | 83 | 84 | Close-parens at start of line are moved to end of previous line. Note that the 85 | spaces before the close-paren are not removed. 86 | 87 | ```in 88 | (let [foo 1 89 | ]; <-- spaces 90 | foo) 91 | ``` 92 | 93 | ```out-disable 94 | (let [foo 1] 95 | ; <-- spaces 96 | foo) 97 | ``` 98 | 99 | **NOTE: Parinferish behaves differently.** It does not move the paren: 100 | 101 | ```out 102 | (let [foo 1 103 | ]; <-- spaces 104 | foo) 105 | ``` 106 | 107 | ```in 108 | (let [foo 1 109 | bar 2 110 | 111 | ] (+ foo bar 112 | ); <-- spaces 113 | ) 114 | ``` 115 | 116 | ```out-disable 117 | (let [foo 1 118 | bar 2] 119 | 120 | (+ foo bar)) 121 | ; <-- spaces 122 | 123 | ``` 124 | 125 | **NOTE: Parinferish behaves differently.** It only changes indentation: 126 | 127 | ```out 128 | (let [foo 1 129 | bar 2 130 | 131 | ] (+ foo bar 132 | ); <-- spaces 133 | ) 134 | ``` 135 | 136 | ## Inside the Indentation Treshold 137 | 138 | extra indent is fine (won't cause parinfer to restructure it) 139 | 140 | ```in 141 | (def x [1 2 3 4 142 | 5 6 7 8]) 143 | ``` 144 | 145 | ```out 146 | (def x [1 2 3 4 147 | 5 6 7 8]) 148 | ``` 149 | 150 | doesn't try to align siblings. 151 | 152 | ```in 153 | (assoc x 154 | :foo 1 155 | :bar 2) 156 | ``` 157 | 158 | ```out 159 | (assoc x 160 | :foo 1 161 | :bar 2) 162 | ``` 163 | 164 | ## Unclosed Parens 165 | 166 | ```in 167 | (foo 168 | ``` 169 | 170 | ```out 171 | (foo 172 | ^ error: unclosed-paren 173 | ``` 174 | 175 | ```in 176 | (defn foo 177 | [arg arg2 178 | bar 179 | ``` 180 | 181 | ```out 182 | (defn foo 183 | [arg arg2 184 | ^ error: unclosed-paren 185 | bar 186 | ``` 187 | 188 | ## Unmatched close-parens 189 | 190 | ```in 191 | (foo}) 192 | ``` 193 | 194 | ```out 195 | (foo}) 196 | ^ error: unmatched-close-paren 197 | ``` 198 | 199 | ```in 200 | (foo 201 | }) 202 | ``` 203 | 204 | ```out 205 | (foo 206 | }) 207 | ^ error: unmatched-close-paren 208 | ``` 209 | 210 | ```in 211 | (defn foo 212 | [arg 213 | bar) 214 | ``` 215 | 216 | ```out 217 | (defn foo 218 | [arg 219 | bar) 220 | ^ error: unmatched-close-paren 221 | ``` 222 | 223 | ## Backslash cases 224 | 225 | escape character in comment untouched: 226 | 227 | ```in 228 | ; hello \n world 229 | ``` 230 | 231 | ```out 232 | ; hello \n world 233 | ``` 234 | 235 | escaped whitespace 236 | 237 | ```in 238 | (def foo \,) 239 | (def bar \ ) 240 | ``` 241 | 242 | ```out 243 | (def foo \,) 244 | (def bar \ ) 245 | ``` 246 | 247 | Hanging backslash at end of line is invalid and causes processing to be abandoned. 248 | 249 | ```in 250 | (foo [a b]\ 251 | c) 252 | ``` 253 | 254 | ```out 255 | (foo [a b]\ 256 | ^ error: eol-backslash 257 | c) 258 | ``` 259 | 260 | ## Unclosed Quotes 261 | 262 | ```in 263 | (def foo 264 | "hello 265 | bar) 266 | ``` 267 | 268 | ```out 269 | (def foo 270 | "hello 271 | ^ error: unclosed-quote 272 | bar) 273 | ``` 274 | 275 | ## Dangerous Quotes 276 | 277 | odd number of quotes not allowed in a comment, so it remains unprocessed: 278 | 279 | ```in 280 | (def foo [a b] 281 | ; "my string 282 | ret) 283 | ``` 284 | 285 | ```out-disable 286 | (def foo [a b] 287 | ; "my string 288 | ^ error: quote-danger 289 | ret) 290 | ``` 291 | 292 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 293 | 294 | ```out 295 | (def foo [a b] 296 | ; "my string 297 | ret) 298 | ``` 299 | 300 | balanced quotes allowed across contiguous comments: 301 | 302 | ```in 303 | (def foo [a b] 304 | ; "my multiline 305 | ; docstring." 306 | ret) 307 | ``` 308 | 309 | ```out 310 | (def foo [a b] 311 | ; "my multiline 312 | ; docstring." 313 | ret) 314 | ``` 315 | 316 | ## Multiline Strings 317 | 318 | A line ending inside a string will not have a definable Paren Trail. This 319 | minimal test case will fail if the close-paren is treated as a Paren Trail. 320 | 321 | ```in 322 | ( )" 323 | " 324 | ``` 325 | 326 | ```out 327 | ( )" 328 | " 329 | ``` 330 | 331 | ## Spaces in Paren Trail 332 | 333 | Preserve spaces in paren trail for the cursor line. This allows the user 334 | to insert things between close-parens more easily. 335 | 336 | ```in 337 | (foo |) 338 | ``` 339 | 340 | ```out 341 | (foo |) 342 | ``` 343 | 344 | ```in 345 | (foo [1 2 3 |] ) 346 | ``` 347 | 348 | ```out 349 | (foo [1 2 3 |] ) 350 | ``` 351 | 352 | But get rid of spaces in paren trail if no cursor is present on the line: 353 | 354 | ```in 355 | (foo ) 356 | ``` 357 | 358 | ```out-disable 359 | (foo) 360 | ``` 361 | 362 | **NOTE: Parinferish behaves differently.** It preserves the whitespace: 363 | 364 | ```out 365 | (foo ) 366 | ``` 367 | 368 | ```in 369 | (foo [1 2 3 ] ) 370 | ``` 371 | 372 | ```out-disable 373 | (foo [1 2 3]) 374 | ``` 375 | 376 | **NOTE: Parinferish behaves differently.** It preserves the whitespace: 377 | 378 | ```out 379 | (foo [1 2 3 ] ) 380 | ``` 381 | 382 | ## Cursor Behavior 383 | 384 | Pressing enter before a close-paren. 385 | 386 | ```in 387 | (foo [a b 388 | |]) 389 | ``` 390 | 391 | ```out 392 | (foo [a b 393 | |]) 394 | ``` 395 | 396 | Cursor pushed forward when a form is balanced and indented. 397 | 398 | ```in 399 | (foo [1 2 3 400 | 4 5 6 401 | 7 8 9])| 402 | ``` 403 | 404 | ```out 405 | (foo [1 2 3 406 | 4 5 6 407 | 7 8 9])| 408 | ``` 409 | 410 | ## Changes 411 | 412 | When backspacing, preserve the indentation of the child lines. 413 | 414 | ```in-disable 415 | (let [foo 1 416 | ---- 417 | ; comment 1 418 | bar 2 419 | baz 3]) 420 | ; comment 2 421 | ``` 422 | 423 | ```out-disable 424 | (let [foo 1 425 | ; comment 1 426 | bar 2 427 | baz 3]) 428 | ; comment 2 429 | ``` 430 | 431 | **NOTE: Parinferish behaves differently.** It does not preserve the relative indentation: 432 | 433 | ```in 434 | (let [foo 1 435 | ; comment 1 436 | bar 2 437 | baz 3]) 438 | ; comment 2 439 | ``` 440 | 441 | ```out 442 | (let [foo 1 443 | ; comment 1 444 | bar 2 445 | baz 3]) 446 | ; comment 2 447 | ``` 448 | 449 | ```in-disable 450 | (def foo 451 | --- 452 | ; comment 1 453 | bar) 454 | ; comment 2 455 | ``` 456 | 457 | ```out-disable 458 | (def foo 459 | ; comment 1 460 | bar) 461 | ; comment 2 462 | ``` 463 | 464 | **NOTE: Parinferish behaves differently.** It does not preserve the relative indentation: 465 | 466 | ```in 467 | (def foo 468 | ; comment 1 469 | bar) 470 | ; comment 2 471 | ``` 472 | 473 | ```out 474 | (def foo 475 | ; comment 1 476 | bar) 477 | ; comment 2 478 | ``` 479 | 480 | When typing before an open-paren, preserve the indentation of the child lines. 481 | 482 | ```in-disable 483 | (def foo (bar 484 | ++++ 485 | 4 5 6 486 | ; comment 1 487 | 7 8 9)) 488 | ; comment 2 489 | ``` 490 | 491 | ```out-disable 492 | (def foo (bar 493 | 4 5 6 494 | ; comment 1 495 | 7 8 9)) 496 | ; comment 2 497 | ``` 498 | 499 | **NOTE: Parinferish behaves differently.** It does not preserve the relative indentation: 500 | 501 | ```in 502 | (def foo (bar 503 | 4 5 6 504 | ; comment 1 505 | 7 8 9)) 506 | ; comment 2 507 | ``` 508 | 509 | ```out 510 | (def foo (bar 511 | 4 5 6 512 | ; comment 1 513 | 7 8 9)) 514 | ; comment 2 515 | ``` 516 | 517 | __Multiple changes__: 518 | 519 | ```in-disable 520 | (my-fnfoo (if some-condition 521 | -----+++ 522 | println) my-funfoo {:foo 1 523 | ------+++ 524 | :bar 2}) 525 | ``` 526 | 527 | ```out-disable 528 | (foo (if some-condition 529 | println) foo {:foo 1 530 | :bar 2}) 531 | ``` 532 | 533 | **NOTE: Parinferish behaves differently.** It does not preserve the relative indentation: 534 | 535 | ```in 536 | (foo (if some-condition 537 | println) foo {:foo 1 538 | :bar 2}) 539 | ``` 540 | 541 | ```out 542 | (foo (if some-condition 543 | println) foo {:foo 1 544 | :bar 2}) 545 | ``` 546 | 547 | ## Extending indentation constraints 548 | 549 | Child lines should never fall to the right of a sibling list somewhere above it. 550 | This promotes more structural stability in Indent Mode—allowing child lines 551 | to be deleted without affecting structure of subsequent lines. 552 | 553 | In the example below, the original Paren Mode would dedent `1 2 3`, but would 554 | leave `4 5 6` unchanged. Subsequently, if the user deletes `1 2 3` in Indent 555 | Mode, `4 5 6` would immediately be adopted into the `[bar baz]` collection, 556 | which is likely an unintended side effect. 557 | 558 | This places a harder constraint on indentation in Paren Mode than Indent Mode, 559 | making the invariant no longer equivalent between them. 560 | 561 | ```in 562 | (foo [bar baz] 563 | 1 2 3 564 | 4 5 6) 565 | ``` 566 | 567 | ```out 568 | (foo [bar baz] 569 | 1 2 3 570 | 4 5 6) 571 | ``` 572 | 573 | ```in 574 | (foo [bar baz 575 | ]; <-- spaces 576 | 1 2 3 577 | 4 5 6) 578 | ``` 579 | 580 | ```out-disable 581 | (foo [bar baz] 582 | ; <-- spaces 583 | 1 2 3 584 | 4 5 6) 585 | ``` 586 | 587 | **NOTE: Parinferish behaves differently.** It does not move the bracket: 588 | 589 | ```out 590 | (foo [bar baz 591 | ]; <-- spaces 592 | 1 2 3 593 | 4 5 6) 594 | ``` 595 | 596 | ## Paren Trails 597 | 598 | We return non-empty Paren Trails so plugins can dim them with markers: 599 | 600 | 601 | ```in-disable 602 | (defn foo 603 | "hello, this is a docstring" 604 | [a b] 605 | (let [sum (+ a b) 606 | prod (* a b)] 607 | {:sum sum 608 | :prod prod})) 609 | ``` 610 | 611 | ```out-disable 612 | (defn foo 613 | "hello, this is a docstring" 614 | [a b] 615 | ^ parenTrail 616 | (let [sum (+ a b) 617 | ^ parenTrail 618 | prod (* a b)] 619 | ^^ parenTrail 620 | {:sum sum 621 | :prod prod})) 622 | ^^^ parenTrail 623 | ``` 624 | 625 | ## Indenting Selected Lines 626 | 627 | Indent only the first line: 628 | 629 | ```in-disable 630 | (foo 631 | ++ 632 | (bar 633 | baz)) 634 | ``` 635 | 636 | ```out-disable 637 | (foo 638 | (bar 639 | baz)) 640 | ``` 641 | 642 | Indent first two lines: 643 | 644 | ```in-disable 645 | (foo 646 | ++ 647 | (bar 648 | ++ 649 | baz)) 650 | ``` 651 | 652 | ```out-disable 653 | (foo 654 | (bar 655 | baz)) 656 | ``` 657 | 658 | Indent last two lines: 659 | 660 | ```in-disable 661 | (foo 662 | (bar 663 | ++ 664 | baz)) 665 | ++ 666 | ``` 667 | 668 | ```out-disable 669 | (foo 670 | (bar 671 | baz)) 672 | ``` 673 | 674 | 675 | Indent only the first line: 676 | 677 | ```in-disable 678 | (foo 679 | ++ 680 | bar 681 | baz) 682 | ``` 683 | 684 | ```out-disable 685 | (foo 686 | bar 687 | baz) 688 | ``` 689 | 690 | Indent first two lines: 691 | 692 | ```in-disable 693 | (foo 694 | ++ 695 | bar 696 | ++ 697 | baz) 698 | ``` 699 | 700 | ```out-disable 701 | (foo 702 | bar 703 | baz) 704 | ``` 705 | 706 | Indent last two lines: 707 | 708 | ```in-disable 709 | (foo 710 | bar 711 | ++ 712 | baz) 713 | ++ 714 | ``` 715 | 716 | ```out-disable 717 | (foo 718 | bar 719 | baz) 720 | ``` 721 | 722 | ## Tab Stops 723 | 724 | > __NOTE__: should be copied from same section in Indent Mode 725 | 726 | We can return the positions of the open-parens whose structure would be 727 | affected by the indentation of the current cursor line. This allows editors to 728 | use them to create tab stops for smart indentation snapping. 729 | 730 | ```in-disable 731 | (def x [1 2 3]) 732 | (def y 2) 733 | | 734 | ``` 735 | 736 | ```out-disable 737 | (def x [1 2 3]) 738 | (def y 2) 739 | ^ > tabStops 740 | | 741 | ``` 742 | 743 | The `>` means the position of the first arg after an open-paren, because some styles 744 | use it for alignment. 745 | 746 | ```in-disable 747 | (foo bar 748 | (baz boo)) 749 | | 750 | ``` 751 | 752 | ```out-disable 753 | (foo bar 754 | (baz boo)) 755 | ^ ^ > tabStops 756 | | 757 | ``` 758 | 759 | ```in-disable 760 | (let [a {:foo 1} 761 | | 762 | bar [1 2 3]] 763 | bar) 764 | ``` 765 | 766 | ```out-disable 767 | (let [a {:foo 1} 768 | ^ ^ ^ > tabStops 769 | | 770 | bar [1 2 3]] 771 | bar) 772 | ``` 773 | 774 | ```in-disable 775 | (let [a {:foo 1} 776 | bar (func 1 2 3)] 777 | | 778 | bar) 779 | ``` 780 | 781 | ```out-disable 782 | (let [a {:foo 1} 783 | bar (func 1 2 3)] 784 | ^ ^ ^ > tabStops 785 | | 786 | bar) 787 | ``` 788 | -------------------------------------------------------------------------------- /src/parinferish/core.cljc: -------------------------------------------------------------------------------- 1 | (ns parinferish.core 2 | (:require [clojure.string :as str]) 3 | (:refer-clojure :exclude [flatten])) 4 | 5 | (def ^:private regexes 6 | [[:newline-and-indent #"^\n[ ]*"] 7 | [:whitespace #"^[ \t\r,]+"] 8 | [:special-char #"^['`~^@]"] 9 | [:delimiter #"^[\[\]{}()]"] 10 | [:delimiter #"^#\{"] 11 | ;; the next regex only matches the beginning of a string. 12 | ;; the rest is read by the parse-string function below. 13 | ;; parsing entire strings via regex was causing a stack 14 | ;; overflow error, so i'm doing it manually instead 15 | [:string #"^\""] 16 | [:character #"^\\\S"] 17 | [:backslash #"^\\"] 18 | [:comment #"^;.*"] 19 | [:number #"^[+-]?\d+[/\.]?[a-zA-Z\d]*"] 20 | [:symbol #"^[^\s\[\]{}('\"`,;)\\]+"]]) 21 | 22 | (def ^:private whitespace? 23 | #{:newline-and-indent :whitespace :comment 24 | ;; a lone backslash is not actually whitespace, 25 | ;; but it's here to prevent it from being immediately 26 | ;; followed by an end paren, since that would cause it 27 | ;; to be escaped and turned into a character 28 | :backslash}) 29 | 30 | (def ^:private open-delims #{"#{" "(" "[" "{"}) 31 | (def ^:private close-delims #{"}" ")" "]"}) 32 | (def ^:private delims {"#{" "}" 33 | "(" ")" 34 | "[" "]" 35 | "{" "}"}) 36 | 37 | (defn- count-newlines [s] 38 | (let [*counter (volatile! 0)] 39 | (doseq [ch s] 40 | (when (= ch \newline) 41 | (vswap! *counter inc))) 42 | @*counter)) 43 | 44 | (defn- count-last-line [s] 45 | (when-let [index (str/last-index-of s \newline)] 46 | (count (subs s (inc index))))) 47 | 48 | (defn- read-token [group-name token *error *line *column *indent last-token] 49 | (let [token-data [(if (and (= group-name :symbol) 50 | (str/starts-with? token ":")) 51 | :keyword 52 | group-name) 53 | token] 54 | line (cond 55 | (= group-name :newline-and-indent) (vswap! *line inc) 56 | (= group-name :string) (vswap! *line + (count-newlines token)) 57 | :else @*line) 58 | start-column (if (= group-name :newline-and-indent) 59 | -1 60 | @*column) 61 | end-column (cond 62 | (= group-name :newline-and-indent) 63 | (vreset! *column (dec (count token))) 64 | (= group-name :string) 65 | ;; multiline strings need to reset the column number correctly 66 | (if-let [n (count-last-line token)] 67 | (vreset! *column n) 68 | (vswap! *column + (count token))) 69 | :else 70 | (vswap! *column + (count token))) 71 | indent (cond 72 | (and (= :delimiter group-name) 73 | (open-delims token)) 74 | (vreset! *indent end-column) 75 | (= group-name :newline-and-indent) 76 | (vreset! *indent (dec (count token))) 77 | :else 78 | @*indent) 79 | token-data (vary-meta token-data assoc 80 | :line line 81 | :column start-column 82 | :indent indent)] 83 | (cond-> token-data 84 | (whitespace? group-name) 85 | (vary-meta assoc :whitespace? true) 86 | (and (= group-name :string) 87 | (not (str/ends-with? token "\""))) 88 | (vary-meta assoc :error-message (vreset! *error "Unbalanced quote")) 89 | (and (= group-name :newline-and-indent) 90 | (= (first last-token) :backslash)) 91 | (vary-meta assoc :error-message (vreset! *error "Backslash at end of line"))))) 92 | 93 | (declare read-structured-token) 94 | 95 | (defn- wrap-coll [data] 96 | (let [first-meta (-> data first meta)] 97 | (vary-meta (into [:collection] data) 98 | assoc :indent (:indent first-meta)))) 99 | 100 | (defn- insert-token [data token-data] 101 | (conj data (vary-meta token-data assoc :action :insert))) 102 | 103 | (defn- remove-token [data token-data] 104 | (conj data (vary-meta token-data assoc :action :remove))) 105 | 106 | (defn- insert-delim [data end-delim] 107 | (let [first-meta (-> data first meta) 108 | token-data (vary-meta [:delimiter end-delim] assoc 109 | :indent (:indent first-meta) 110 | :action :insert) 111 | [last-data first-data] 112 | (->> data 113 | reverse 114 | (split-with #(-> % first whitespace?)) 115 | (mapv reverse) 116 | (mapv vec))] 117 | (into (conj first-data token-data) 118 | last-data))) 119 | 120 | (defn- read-next-tokens-with-indent [flat-tokens {:keys [*index] :as opts}] 121 | (loop [data [] 122 | ignore-data [] 123 | last-index @*index 124 | new-line? false] 125 | (if-let [[group _ :as token-data] (read-structured-token flat-tokens opts)] 126 | (cond 127 | (= :delimiter group) 128 | (recur 129 | data 130 | (conj ignore-data (vary-meta token-data assoc :action :remove)) 131 | last-index 132 | new-line?) 133 | (whitespace? group) 134 | (recur 135 | data 136 | (conj ignore-data token-data) 137 | last-index 138 | (or new-line? (= group :newline-and-indent))) 139 | :else 140 | (if new-line? 141 | (recur 142 | (-> data 143 | (into ignore-data) 144 | (conj token-data)) 145 | [] 146 | @*index 147 | new-line?) 148 | (recur 149 | data 150 | (conj ignore-data token-data) 151 | last-index 152 | new-line?))) 153 | (do 154 | (vreset! *index last-index) 155 | data)))) 156 | 157 | (defn- read-coll-indent-mode [flat-tokens [_ delim :as token-data] {:keys [*index] :as opts}] 158 | (let [end-delim (delims delim) 159 | indent (-> token-data meta :indent) 160 | opts (assoc opts :min-indent indent)] 161 | (loop [data [token-data] 162 | whitespace-data [] 163 | last-index @*index] 164 | (if-let [[group token :as token-data] (read-structured-token flat-tokens opts)] 165 | (cond 166 | (whitespace? group) 167 | (recur data (conj whitespace-data token-data) last-index) 168 | (< (-> token-data meta :indent) indent) 169 | (do 170 | (vreset! *index last-index) 171 | (-> data 172 | (insert-delim end-delim) 173 | wrap-coll)) 174 | (= :delimiter group) 175 | (if (= token end-delim) 176 | (let [tokens-to-move (read-next-tokens-with-indent flat-tokens opts)] 177 | (if (seq tokens-to-move) 178 | (-> data 179 | (into whitespace-data) 180 | (remove-token token-data) 181 | (into tokens-to-move) 182 | (insert-token token-data) 183 | wrap-coll) 184 | (-> data 185 | (into whitespace-data) 186 | (conj token-data) 187 | wrap-coll))) 188 | (recur 189 | (-> data 190 | (into whitespace-data) 191 | (remove-token token-data)) 192 | [] 193 | @*index)) 194 | :else 195 | (recur 196 | (-> data 197 | (into whitespace-data) 198 | (conj token-data)) 199 | [] 200 | @*index)) 201 | (do 202 | (vreset! *index last-index) 203 | (-> data 204 | (insert-delim end-delim) 205 | wrap-coll)))))) 206 | 207 | (defn- read-coll-paren-mode [flat-tokens [_ delim :as token-data] {:keys [indent-change *error] :as opts}] 208 | (let [end-delim (delims delim) 209 | min-indent (cond-> (-> token-data meta :indent) 210 | indent-change 211 | (+ indent-change)) 212 | opts (dissoc opts :min-indent)] 213 | (loop [data [token-data] 214 | max-indent nil 215 | indent-change (or indent-change 0)] 216 | (if-let [[group token :as token-data] (read-structured-token flat-tokens 217 | (assoc opts :indent-change indent-change))] 218 | (cond 219 | (= :newline-and-indent group) 220 | (let [current-indent (-> token-data meta :indent) 221 | indent-change (cond 222 | (< current-indent min-indent) 223 | (- min-indent current-indent) 224 | (and max-indent (> current-indent max-indent)) 225 | (- max-indent current-indent) 226 | :else 227 | 0)] 228 | (recur 229 | (cond 230 | (pos? indent-change) 231 | (-> data 232 | (conj token-data) 233 | (conj (vary-meta [:whitespace (str/join (repeat indent-change " "))] 234 | assoc :action :insert :whitespace? true))) 235 | (neg? indent-change) 236 | (-> data 237 | (conj (update token-data 1 subs 0 (+ (count token) indent-change))) 238 | (conj (vary-meta [:whitespace (str/join (repeat (* -1 indent-change) " "))] 239 | assoc :action :remove :whitespace? true))) 240 | :else 241 | (conj data token-data)) 242 | max-indent 243 | indent-change)) 244 | (= :collection group) 245 | (recur 246 | (conj data token-data) 247 | (cond-> (-> token-data meta :indent dec) 248 | max-indent 249 | (min max-indent)) 250 | indent-change) 251 | (= :delimiter group) 252 | (cond-> (wrap-coll (conj data token-data)) 253 | (not= token end-delim) 254 | (vary-meta assoc :error-message (vreset! *error "Unmatched delimiter"))) 255 | :else 256 | (recur (conj data token-data) max-indent indent-change)) 257 | (vary-meta (wrap-coll data) 258 | assoc :error-message (vreset! *error "EOF while reading")))))) 259 | 260 | (defn- read-coll [flat-tokens [_ delim :as token-data] {:keys [*index *error] :as opts}] 261 | (let [end-delim (delims delim)] 262 | (loop [data [token-data]] 263 | (if-let [[group token :as token-data] (read-structured-token flat-tokens opts)] 264 | (if (= :delimiter group) 265 | (cond-> (wrap-coll (conj data token-data)) 266 | (not= token end-delim) 267 | (vary-meta assoc :error-message (vreset! *error "Unmatched delimiter"))) 268 | (recur (conj data token-data))) 269 | (vary-meta (wrap-coll data) 270 | assoc :error-message (vreset! *error "EOF while reading")))))) 271 | 272 | (defn- read-structured-token [flat-tokens {:keys [*index mode min-indent] :as opts}] 273 | (when-let [[group token :as token-data] (get flat-tokens (vswap! *index inc))] 274 | (when (or (nil? min-indent) 275 | (whitespace? group) 276 | (-> token-data meta :column (>= min-indent))) 277 | (if (and (= :delimiter group) 278 | (open-delims token)) 279 | (case mode 280 | :indent (read-coll-indent-mode flat-tokens token-data opts) 281 | :paren (read-coll-paren-mode flat-tokens token-data opts) 282 | :smart (let [{:keys [cursor-line cursor-column]} opts 283 | {:keys [line column]} (meta token-data)] 284 | (if (or (< line cursor-line) 285 | (and (= line cursor-line) 286 | (< column cursor-column))) 287 | (read-coll-indent-mode flat-tokens token-data opts) 288 | (read-coll-paren-mode flat-tokens token-data opts))) 289 | nil (read-coll flat-tokens token-data opts)) 290 | token-data)))) 291 | 292 | (defn- read-useful-token [flat-tokens {:keys [mode *error] :as opts}] 293 | (when-let [[_ token :as token-data] (read-structured-token flat-tokens opts)] 294 | (cond 295 | (close-delims token) 296 | (if (#{:indent :smart} mode) 297 | (vary-meta token-data assoc :action :remove) 298 | (vary-meta token-data assoc :error-message (vreset! *error "Unmatched delimiter"))) 299 | :else 300 | token-data))) 301 | 302 | (defn- parse-string [token input-str] 303 | (loop [token token 304 | input-str (subs input-str (count token))] 305 | (if-let [ch (first input-str)] 306 | (case ch 307 | \\ 308 | (if (> (count input-str) 1) 309 | (recur (str token (subs input-str 0 2)) (subs input-str 2)) 310 | (str token ch)) 311 | \" 312 | (str token ch) 313 | (recur (str token ch) (subs input-str 1))) 314 | token))) 315 | 316 | (defn parse 317 | "Returns a hierarcichal (I can never spell that goddamn word) vector of tuples 318 | representing each distinct Clojure token from the input string. Takes an options 319 | map that allows you to apply parinferish via the :mode option (:indent, :paren, :smart). 320 | Note that if you use parinfer, the return value will include both the inserted and removed 321 | tokens. The tokens parinfer wants to remove will be removed by the `flatten` function." 322 | ([input] 323 | (parse input {})) 324 | ([input opts] 325 | (when (and (= :smart (:mode opts)) 326 | (or (nil? (:cursor-line opts)) 327 | (nil? (:cursor-column opts)))) 328 | (throw (ex-info "Smart mode requires :cursor-line and :cursor-column" {}))) 329 | (let [*error (volatile! nil) 330 | *line (volatile! 0) 331 | *column (volatile! 0) 332 | *indent (volatile! 0) 333 | tokens (loop [input-str input 334 | tokens (transient []) 335 | last-token nil] 336 | (if-let [[group-name token] 337 | (some (fn [[group-name regex]] 338 | (when-let [match (re-find regex input-str)] 339 | [group-name match])) 340 | regexes)] 341 | (let [token (if (= group-name :string) 342 | (parse-string token input-str) 343 | token) 344 | token-data (read-token group-name token *error *line *column *indent last-token)] 345 | (recur (subs input-str (count token)) (conj! tokens token-data) token-data)) 346 | (persistent! tokens))) 347 | opts (assoc opts 348 | :*error *error 349 | :*index (volatile! -1)) 350 | opts (cond-> opts @*error (dissoc :mode))] 351 | (loop [structured-tokens []] 352 | (if-let [token-data (read-useful-token tokens opts)] 353 | (recur (conj structured-tokens token-data)) 354 | (let [{:keys [mode]} opts] 355 | (vary-meta structured-tokens 356 | assoc :mode mode :error? (some? @*error)))))))) 357 | 358 | (defn- node-iter [node-fn nodes node disable-parinfer?] 359 | (if (= (first node) :collection) 360 | (->> (reduce 361 | (fn [v child] 362 | (node-iter node-fn v child disable-parinfer?)) 363 | [] 364 | (rest node)) 365 | (into (with-meta [:collection] (meta node))) 366 | node-fn 367 | (conj nodes)) 368 | (if (if disable-parinfer? 369 | (-> node meta :action (= :insert)) 370 | (-> node meta :action (= :remove))) 371 | nodes 372 | (conj nodes (node-fn node))))) 373 | 374 | (defn flatten 375 | "Takes the result of `parse` and flattens it into a string. Optionally takes a 376 | `node-fn` which will receive each token and return whatever it wants, so you can 377 | have more control of what format it outputs to." 378 | ([parsed-code] 379 | (->> parsed-code 380 | (flatten #(-> % rest str/join)) 381 | str/join)) 382 | ([node-fn parsed-code] 383 | (let [m (meta parsed-code) 384 | disable-parinfer? (and (= :paren (:mode m)) 385 | (:error? m))] 386 | (reduce 387 | (fn [v code] 388 | (into v (node-iter node-fn [] code disable-parinfer?))) 389 | [] 390 | parsed-code)))) 391 | 392 | (defn- diff-node [*line *column *diff parent-node node] 393 | (if (vector? node) 394 | (let [[type & children] node] 395 | (cond 396 | (= type :newline-and-indent) 397 | (do 398 | (vswap! *line inc) 399 | (vreset! *column -1)) 400 | (= type :string) 401 | (vswap! *line + (count-newlines (first children)))) 402 | (run! (partial diff-node *line *column *diff node) children)) 403 | (let [line @*line 404 | column @*column 405 | parent-meta (meta parent-node)] 406 | (if (= :string (first parent-node)) 407 | ;; multiline strings need to reset the column number correctly 408 | (if-let [n (count-last-line node)] 409 | (vreset! *column n) 410 | (vswap! *column + (count node))) 411 | (vswap! *column + (count node))) 412 | (when-let [action (:action parent-meta)] 413 | (let [last-diff (last @*diff)] 414 | ;; if we are removing the thing we just inserted, 415 | ;; the actions cancel each other out 416 | ;; so just remove both from the diff 417 | (if (and last-diff 418 | (= action :remove) 419 | (= (:action last-diff) :insert) 420 | (= line (:line last-diff)) 421 | (= 1 (- column (:column last-diff))) 422 | (= node (:content last-diff))) 423 | (vswap! *diff pop) 424 | (vswap! *diff conj {:line line 425 | :column column 426 | :content node 427 | :action action 428 | :type (first parent-node)}))) 429 | (when (= action :remove) 430 | (vswap! *column - (count node))))))) 431 | 432 | (defn diff 433 | "Takes the result of `parse` and returns a vector of maps describing 434 | each insertion or removal made by parinferish." 435 | [parsed-code] 436 | (let [*line (volatile! 0) 437 | *column (volatile! 0) 438 | *diff (volatile! []) 439 | m (meta parsed-code) 440 | disable-parinfer? (and (= :paren (:mode m)) 441 | (:error? m))] 442 | (when-not disable-parinfer? 443 | (run! (partial diff-node *line *column *diff nil) parsed-code)) 444 | @*diff)) 445 | 446 | -------------------------------------------------------------------------------- /test-resources/indent-mode.md: -------------------------------------------------------------------------------- 1 | # Indent Mode 2 | 3 | ## No Closers 4 | 5 | Most basic behavior can be described by leaving out close-parens. 6 | 7 | ```in 8 | (defn foo 9 | [arg 10 | ret 11 | ``` 12 | 13 | ```out 14 | (defn foo 15 | [arg] 16 | ret) 17 | ``` 18 | 19 | indenting line 3: 20 | 21 | ```in 22 | (defn foo 23 | [arg 24 | ret 25 | ``` 26 | 27 | ```out 28 | (defn foo 29 | [arg 30 | ret]) 31 | ``` 32 | 33 | dedenting line 2: 34 | 35 | ```in 36 | (defn foo 37 | [arg 38 | ret 39 | ``` 40 | 41 | ```out 42 | (defn foo) 43 | [arg 44 | ret] 45 | ``` 46 | 47 | dedenting line 2 and 3: 48 | 49 | ```in 50 | (defn foo 51 | [arg 52 | ret 53 | ``` 54 | 55 | ```out 56 | (defn foo) 57 | [arg] 58 | ret 59 | ``` 60 | 61 | multiple functions: 62 | 63 | ```in 64 | (defn foo 65 | [arg 66 | ret 67 | 68 | (defn foo 69 | [arg 70 | ret 71 | ``` 72 | 73 | ```out 74 | (defn foo 75 | [arg] 76 | ret) 77 | 78 | (defn foo 79 | [arg] 80 | ret) 81 | ``` 82 | 83 | ## Bad Closers 84 | 85 | close-paren at end-of-line with no open-paren: 86 | 87 | ```in 88 | bar) 89 | ``` 90 | 91 | ```out 92 | bar 93 | ``` 94 | 95 | close-paren at end-of-line is wrong type: 96 | 97 | ```in 98 | (def foo [a b]] 99 | ``` 100 | 101 | ```out 102 | (def foo [a b]) 103 | ``` 104 | 105 | insert missing close-paren inside another when at end-of-line: 106 | 107 | ```in 108 | (let [x {:foo 1 :bar 2] 109 | x) 110 | ``` 111 | 112 | ```out 113 | (let [x {:foo 1 :bar 2}] 114 | x) 115 | ``` 116 | 117 | unmatched close-parens _inside_ a line are removed: 118 | 119 | ```in 120 | (foo [a (|b] c) 121 | ``` 122 | ```out-disable 123 | (foo [a (|b] c) 124 | ^ error: unmatched-close-paren 125 | ``` 126 | 127 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 128 | 129 | ```out 130 | (foo [a (|b c)]) 131 | ``` 132 | 133 | ## Strings 134 | 135 | No close-parens are inserted when a string is unclosed. 136 | 137 | ```in 138 | (def foo "as 139 | ``` 140 | 141 | ```out 142 | (def foo "as 143 | ^ error: unclosed-quote 144 | ``` 145 | 146 | even if close-parens are quoted out, do not do anything. 147 | 148 | ```in 149 | (defn foo [a "]) 150 | ``` 151 | 152 | ```out 153 | (defn foo [a "]) 154 | ^ error: unclosed-quote 155 | ``` 156 | 157 | Multiline strings are supported: 158 | 159 | ```in 160 | (defn foo 161 | "This is docstring. 162 | Line 2 here." 163 | ret 164 | ``` 165 | 166 | ```out 167 | (defn foo 168 | "This is docstring. 169 | Line 2 here." 170 | ret) 171 | ``` 172 | 173 | Indentation inside multiline strings does not trigger Parinfer's indentation rules. 174 | 175 | ```in 176 | (let [a "Hello 177 | World" 178 | b 2 179 | ret 180 | ``` 181 | 182 | ```out 183 | (let [a "Hello 184 | World" 185 | b 2] 186 | ret) 187 | ``` 188 | 189 | Close-parens are ignored when inside strings. 190 | 191 | ```in 192 | (let [a "])" 193 | b 2 194 | ``` 195 | 196 | ```out 197 | (let [a "])" 198 | b 2]) 199 | ``` 200 | 201 | Escaped quotes are handled correctly. 202 | 203 | ```in 204 | (def foo "\"" 205 | ``` 206 | 207 | ```out 208 | (def foo "\"") 209 | ``` 210 | 211 | A line ending inside a string will not have a definable Paren Trail. This 212 | minimal test case will fail if the close-paren is treated as a Paren Trail. 213 | 214 | ```in 215 | ()" 216 | " 217 | ``` 218 | 219 | ```out 220 | ()" 221 | " 222 | ``` 223 | 224 | ## Unbalanced Quotes 225 | 226 | __NOTE:__ The pipe `|` represents the cursor, and its character is removed from 227 | input. We use it here to suggest that the user has just typed the character to 228 | the left of the cursor. 229 | 230 | Typing a quote before another string does not corrupt it (i.e. turn it inside 231 | out, causing Parinfer to treat its contents as code). 232 | 233 | ```in 234 | "|"foo" 235 | ``` 236 | 237 | ```out 238 | "|"foo" 239 | ^ error: unclosed-quote 240 | ``` 241 | 242 | Another case: 243 | 244 | ```in 245 | (def foo 246 | "| 247 | "(a b) 248 | c") 249 | ``` 250 | 251 | ```out 252 | (def foo 253 | "| 254 | "(a b) 255 | c") 256 | ^ error: unclosed-quote 257 | ``` 258 | 259 | ## Unbalanced Quotes in Comments 260 | 261 | Unbalanced quotes can be accidentally rebalanced by comments containing an odd number of quotes, 262 | so we do not want to process if any comments meet this critera. 263 | 264 | Notice that the following code is correctly balanced, quite accidentally, but 265 | Parinfer does not process it because the last line contains a comment with an 266 | odd number of quotes (one): 267 | 268 | ```in 269 | (for [col columns] 270 | "| 271 | [:div.td {:style "max-width: 500px;"}]) 272 | ``` 273 | 274 | ```out-disable 275 | (for [col columns] 276 | "| 277 | [:div.td {:style "max-width: 500px;"}]) 278 | ^ error: quote-danger 279 | ``` 280 | 281 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 282 | 283 | ```out 284 | (for [col columns] 285 | "| 286 | [:div.td {:style "max-width: 500px);"}]) 287 | ``` 288 | 289 | But a comment can contain an odd number of quotes if it is in a contiguous group of comments 290 | which contain an even number of them. This allows commenting out a multiline string without 291 | any problems: 292 | 293 | ```in 294 | (def foo [a b] 295 | ; "my multiline 296 | ; docstring." 297 | ret) 298 | ``` 299 | 300 | ```out 301 | (def foo [a b]) 302 | ; "my multiline 303 | ; docstring." 304 | ret 305 | ``` 306 | 307 | Escaped strings are not counted when determining odd number of quotes in a comment. 308 | 309 | ```in 310 | (def foo [a b] 311 | ; ""\" 312 | ret) 313 | ``` 314 | 315 | ```out 316 | (def foo [a b]) 317 | ; ""\" 318 | ret 319 | ``` 320 | 321 | ## Character syntax 322 | 323 | Correctly handle escaped parens as literal characters. 324 | 325 | ```in 326 | (defn foo [a b 327 | \[ 328 | ret 329 | ``` 330 | 331 | ```out 332 | (defn foo [a b] 333 | \[ 334 | ret) 335 | ``` 336 | 337 | ```in 338 | (defn foo [a b] 339 | ret\) 340 | ``` 341 | 342 | ```out 343 | (defn foo [a b] 344 | ret\)) 345 | ``` 346 | 347 | ```in 348 | {:tag-open \[ :tag-close \]} 349 | {:tag-open \[ :tag-close \]} 350 | ``` 351 | 352 | ```out 353 | {:tag-open \[ :tag-close \]} 354 | {:tag-open \[ :tag-close \]} 355 | ``` 356 | 357 | Correctly handle escaped semicolons as characters instead of comments. 358 | Otherwise, the inferred close-parens would be inserted before them. 359 | 360 | ```in 361 | (def foo \; 362 | ``` 363 | 364 | ```out 365 | (def foo \;) 366 | ``` 367 | 368 | Inferred close-parens are inserted after escaped whitespace. 369 | 370 | ```in 371 | (def foo \, 372 | (def bar \ ; <-- space 373 | ``` 374 | 375 | ```out-disable 376 | (def foo \,) 377 | (def bar \ ); <-- space 378 | ``` 379 | 380 | **NOTE: Parinferish behaves differently.** It inserts the end paren before the backslash: 381 | 382 | ```out 383 | (def foo \,) 384 | (def bar) \ ; <-- space 385 | ``` 386 | 387 | Hanging backslash at end of line is invalid and causes processing to be abandoned. 388 | 389 | ```in 390 | (foo [a b\ 391 | c) 392 | ``` 393 | 394 | ```out 395 | (foo [a b\ 396 | ^ error: eol-backslash 397 | c) 398 | ``` 399 | 400 | ## Comments 401 | 402 | When commenting-out an inferred close-paren, a new one should be inserted 403 | before it. 404 | 405 | ```in 406 | (def foo ;) 407 | ``` 408 | 409 | ```out 410 | (def foo) ;) 411 | ``` 412 | 413 | Commenting-out a line containing inferred close-parens should cause new ones to 414 | be inserted at the previous non-empty line. 415 | 416 | ```in 417 | (let [a 1 418 | b 2 419 | c {:foo 1 420 | ;; :bar 2}] 421 | ret) 422 | ``` 423 | 424 | ```out 425 | (let [a 1 426 | b 2 427 | c {:foo 1}] 428 | ;; :bar 2}] 429 | ret) 430 | ``` 431 | 432 | Inferred close-parens are inserted before comments. 433 | 434 | ```in 435 | (let [a 1 ;; a comment 436 | ret) 437 | ``` 438 | 439 | ```out 440 | (let [a 1] ;; a comment 441 | ret) 442 | ``` 443 | 444 | escape character in comment untouched: 445 | 446 | ```in 447 | ; hello \n world 448 | ``` 449 | 450 | ```out 451 | ; hello \n world 452 | ``` 453 | 454 | --- 455 | 456 | ## Cursor Padding in Paren Trail 457 | 458 | __NOTE__: the pipe `|` represents the cursor, but the character is removed from the input. 459 | 460 | Inferred close-parens can only be inserted to the right of the cursor (if it is present). 461 | This allows us to insert a space before typing a new token. 462 | 463 | ```in 464 | (def b |) 465 | ``` 466 | 467 | ```out 468 | (def b |) 469 | ``` 470 | 471 | Once the cursor leaves the line, the space is removed. 472 | 473 | ```in 474 | (def b ) 475 | ``` 476 | 477 | ```out-disable 478 | (def b) 479 | ``` 480 | 481 | **NOTE: Parinferish behaves differently.** The space is preserved: 482 | 483 | ```out 484 | (def b ) 485 | ``` 486 | 487 | Another example with more close-parens: 488 | 489 | ```in 490 | (def b [[c d] |]) 491 | ``` 492 | 493 | ```out 494 | (def b [[c d] |]) 495 | ``` 496 | 497 | Once the cursor leaves the line, the space is removed. 498 | 499 | ```in 500 | (def b [[c d] ]) 501 | ``` 502 | 503 | ```out-disable 504 | (def b [[c d]]) 505 | ``` 506 | 507 | **NOTE: Parinferish behaves differently.** The space is preserved: 508 | 509 | ```out 510 | (def b [[c d] ]) 511 | ``` 512 | 513 | ## Cursor Blocking displacement of Paren Trail 514 | 515 | Inferred close-parens before the cursor are never removed, which may 516 | cause indented lines below to be ignored. This is to allow inserting a token 517 | after such a close-paren. 518 | 519 | For example, without the cursor on the first line, this is expected: 520 | 521 | ```in 522 | (let [a 1]) 523 | ret) 524 | ``` 525 | 526 | ```out 527 | (let [a 1] 528 | ret) 529 | ``` 530 | 531 | With the cursor at the end of the first line, the indented line below does not affect it. 532 | 533 | ```in 534 | (let [a 1])| 535 | ret) 536 | ``` 537 | 538 | ```out-disable 539 | (let [a 1])| 540 | ret 541 | ``` 542 | 543 | **NOTE: Parinferish behaves differently.** It does not take your cursor into account in indent mode: 544 | 545 | ```out 546 | (let [a 1]| 547 | ret) 548 | ``` 549 | 550 | If this was not allowed, we would not be able to reach this valid state from 551 | the previous state: 552 | 553 | ```in 554 | (let [a 1]) 2 555 | ret 556 | ``` 557 | 558 | ```out-disable 559 | (let [a 1]) 2 560 | ret 561 | ``` 562 | 563 | **NOTE: Parinferish behaves differently.** It moves the paren if possible: 564 | 565 | ```out 566 | (let [a 1] 2 567 | ret) 568 | ``` 569 | 570 | But if the cursor is before such a close-paren, we are not in a position to 571 | insert a token after it, thus indentation can affect it again: 572 | 573 | ```in 574 | (let [a 1]|) 575 | ret) 576 | ``` 577 | 578 | ```out 579 | (let [a 1]| 580 | ret) 581 | ``` 582 | 583 | If the cursor is in a comment after such a close-paren, we can safely move it: 584 | 585 | ```in 586 | (let [a 1]) ;| 587 | ret 588 | ``` 589 | 590 | ```out 591 | (let [a 1] ;| 592 | ret) 593 | ``` 594 | 595 | The cursor should not keep unmatched close-parens in the trail: 596 | 597 | ```in 598 | (foo)}}}}| 599 | ``` 600 | 601 | ```out 602 | (foo)| 603 | ``` 604 | 605 | ```in 606 | (foo}}}}|) 607 | ``` 608 | 609 | ```out 610 | (foo|) 611 | ``` 612 | 613 | ## Leading close-parens 614 | 615 | Leading close-parens can cause many problems that we can ignore by simply removing 616 | them, which is what we do when `forceBalance` is enabled. (currently can't enable 617 | these in tests). 618 | 619 | If `forceBalance` is not on, we suspend Indent Mode. 620 | 621 | ```in 622 | (foo 623 | ) bar 624 | ``` 625 | 626 | ```out 627 | (foo 628 | ) bar 629 | ^ error: leading-close-paren 630 | ``` 631 | 632 | It is allowed if the leading parens are also in paren trail: 633 | 634 | ```in 635 | (foo 636 | ); comment 637 | ``` 638 | 639 | ```out-disable 640 | (foo) 641 | ; comment 642 | ``` 643 | 644 | **NOTE: Parinferish behaves differently.** It preserves the whitespace: 645 | 646 | ```out 647 | (foo 648 | ); comment 649 | ``` 650 | 651 | If there's more than one, point to the first one. 652 | 653 | ```in 654 | [(foo 655 | )] bar 656 | ``` 657 | 658 | ```out 659 | [(foo 660 | )] bar 661 | ^ error: leading-close-paren 662 | ``` 663 | 664 | ## Unmatched close-parens 665 | 666 | I've tried some inference algorithms to resolve unmatched close-parens (see 667 | [#131]), but they didn't work out due to reasons stated in the issue. The 668 | following cases demonstrate many edge cases that would have to be resolved if 669 | ever approached again. 670 | 671 | [#131]:https://github.com/shaunlebron/parinfer/issues/131 672 | 673 | Inserting a `(` inside a nested vector: 674 | 675 | ```in 676 | (foo [bar (|...] baz) 677 | ``` 678 | 679 | ```out-disable 680 | (foo [bar (|...] baz) 681 | ^ error: unmatched-close-paren 682 | ``` 683 | 684 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 685 | 686 | ```out 687 | (foo [bar (|... baz)]) 688 | ``` 689 | 690 | Inserting a `]` inside a nested list: 691 | 692 | ```in 693 | (foo [bar (]| baz)]) 694 | ``` 695 | 696 | ```out-disable 697 | (foo [bar (]| baz)]) 698 | ^ error: unmatched-close-paren 699 | ``` 700 | 701 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 702 | 703 | ```out 704 | (foo [bar (| baz)]) 705 | ``` 706 | 707 | Inserting a `]` ahead of another inside a list (maybe to "barf" the end of the 708 | vector). 709 | 710 | ```in 711 | [... (foo [bar ]| baz] ...)] 712 | ``` 713 | 714 | ```out-disable 715 | [... (foo [bar ]| baz] ...)] 716 | ^ error: unmatched-close-paren 717 | ``` 718 | 719 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 720 | 721 | ```out 722 | [... (foo [bar ]| baz ...)] 723 | ``` 724 | 725 | Suppose you just backspaced a `[` below: 726 | 727 | ```in 728 | (let [{:keys |foo bar]} my-map]) 729 | ``` 730 | 731 | ```out-disable 732 | (let [{:keys |foo bar]} my-map]) 733 | ^ error: unmatched-close-paren 734 | ``` 735 | 736 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 737 | 738 | ```out 739 | (let [{:keys |foo bar} my-map]) 740 | ``` 741 | 742 | Inserting a matched `)` inside nested expressions sometimes works out: 743 | 744 | ```in 745 | (a (b (c))| d) e) 746 | ``` 747 | 748 | ```out 749 | (a (b (c))| d) e 750 | ``` 751 | 752 | Inserting a matched `(` inside nested expressions sometimes works out too: 753 | 754 | ```in 755 | (a (b (c(|) d) e) 756 | ``` 757 | 758 | ```out 759 | (a (b (c(|) d) e)) 760 | ``` 761 | 762 | But all it takes is one different kind of a paren to keep it from working: 763 | 764 | ```in 765 | (f [x (a (b c(|) d) y] g) 766 | ``` 767 | 768 | ```out-disable 769 | (f [x (a (b c(|) d) y] g) 770 | ^ error: unmatched-close-paren 771 | ``` 772 | 773 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 774 | 775 | ```out 776 | (f [x (a (b c(|) d) y g)]) 777 | ``` 778 | 779 | Unmatched close-parens on indented lines present similar issues. 780 | For example, inserting a `)` below: 781 | 782 | ```in 783 | (foo 784 | bar)| baz) qux 785 | ``` 786 | 787 | ```out-disable 788 | (foo 789 | bar)| baz) qux 790 | ^ error: unmatched-close-paren 791 | ``` 792 | 793 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 794 | 795 | ```out 796 | (foo 797 | bar)| baz qux 798 | ``` 799 | 800 | ```in 801 | (foo 802 | [bar 803 | bar)| baz 804 | bar]) 805 | ``` 806 | 807 | ```out-disable 808 | (foo 809 | [bar 810 | bar)| baz 811 | ^ error: unmatched-close-paren 812 | bar]) 813 | ``` 814 | 815 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 816 | 817 | ```out 818 | (foo 819 | [bar 820 | bar| baz 821 | bar]) 822 | ``` 823 | 824 | Or when dedenting a line makes an inner close-paren unmatched: 825 | 826 | ```in 827 | (foo 828 | [bar] 829 | |bar) baz 830 | ``` 831 | 832 | ```out-disable 833 | (foo 834 | [bar] 835 | |bar) baz 836 | ^ error: unmatched-close-paren 837 | ``` 838 | 839 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 840 | 841 | ```out 842 | (foo 843 | [bar]) 844 | |bar baz 845 | ``` 846 | 847 | In the same example, a different similar problem emerges when indenting a line 848 | makes the same inner close-paren unmatched: 849 | 850 | ```in 851 | (foo 852 | [bar] 853 | |bar) baz 854 | ``` 855 | 856 | ```out-disable 857 | (foo 858 | [bar] 859 | |bar) baz 860 | ^ error: unmatched-close-paren 861 | ``` 862 | 863 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 864 | 865 | ```out 866 | (foo 867 | [bar 868 | |bar baz]) 869 | ``` 870 | 871 | The same problem demonstrated for another dedenting example: 872 | 873 | ```in 874 | (foo 875 | [bar 876 | bar]) baz 877 | ``` 878 | 879 | ```out-disable 880 | (foo 881 | [bar 882 | bar]) baz 883 | ^ error: unmatched-close-paren 884 | ``` 885 | 886 | **NOTE: Parinferish behaves differently.** It does not turn itself off: 887 | 888 | ```out 889 | (foo 890 | [bar] 891 | bar) baz 892 | ``` 893 | 894 | ## Cursor Shifting 895 | 896 | Commenting an inferred close-paren 897 | 898 | ```in 899 | (foo bar ;|) 900 | ``` 901 | 902 | ```out 903 | (foo bar) ;|) 904 | ``` 905 | 906 | Commenting multiple inferred close-parens 907 | 908 | ```in 909 | (let [x 1 910 | y 2;|]) 911 | ``` 912 | 913 | ```out 914 | (let [x 1 915 | y 2]);|]) 916 | ``` 917 | 918 | When typing an open-paren, a close-paren should come after the cursor: 919 | 920 | ```in 921 | (| 922 | ``` 923 | 924 | ```out 925 | (|) 926 | ``` 927 | 928 | ## Tab Stops 929 | 930 | We can return the positions of the open-parens whose structure would be 931 | affected by the indentation of the current cursor line. This allows editors to 932 | use them to create tab stops for smart indentation snapping. 933 | 934 | ```in-disable 935 | (def x [1 2 3]) 936 | (def y 2) 937 | | 938 | ``` 939 | 940 | ```out-disable 941 | (def x [1 2 3]) 942 | (def y 2) 943 | ^ > tabStops 944 | | 945 | ``` 946 | 947 | The `>` means the position of the first arg after an open-paren, because some styles 948 | use it for alignment. 949 | 950 | ```in-disable 951 | (foo bar 952 | (baz boo)) 953 | | 954 | ``` 955 | 956 | ```out-disable 957 | (foo bar 958 | (baz boo)) 959 | ^ ^ > tabStops 960 | | 961 | ``` 962 | 963 | ```in-disable 964 | (let [a {:foo 1} 965 | | 966 | bar [1 2 3]] 967 | bar) 968 | ``` 969 | 970 | ```out-disable 971 | (let [a {:foo 1} 972 | ^ ^ ^ > tabStops 973 | | 974 | bar [1 2 3]] 975 | bar) 976 | ``` 977 | 978 | 979 | ```in-disable 980 | (let [a {:foo 1} 981 | bar (func 1 2 3)] 982 | | 983 | bar) 984 | ``` 985 | 986 | ```out-disable 987 | (let [a {:foo 1} 988 | bar (func 1 2 3)] 989 | ^ ^ ^ > tabStops 990 | | 991 | bar) 992 | ``` 993 | 994 | ## Paren Trails 995 | 996 | We return non-empty Paren Trails so plugins can dim them with markers: 997 | 998 | ```in-disable 999 | (defn foo 1000 | "hello, this is a docstring" 1001 | [a b] 1002 | (let [sum (+ a b) 1003 | prod (* a b)] 1004 | {:sum sum 1005 | :prod prod})) 1006 | ``` 1007 | 1008 | ```out-disable 1009 | (defn foo 1010 | "hello, this is a docstring" 1011 | [a b] 1012 | ^ parenTrail 1013 | (let [sum (+ a b) 1014 | ^ parenTrail 1015 | prod (* a b)] 1016 | ^^ parenTrail 1017 | {:sum sum 1018 | :prod prod})) 1019 | ^^^ parenTrail 1020 | ``` 1021 | 1022 | **NOTE: This is a fix added by Parinferish.** 1023 | 1024 | Mismatched parens following multiline strings are corrected regardless of indentation. 1025 | 1026 | ```in 1027 | (def shader 1028 | '{:inputs {a_position vec2} 1029 | :outputs {v_color vec4} 1030 | :functions " 1031 | void main() 1032 | { 1033 | gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0); 1034 | v_color = gl_Position * 0.5 + 0.5; 1035 | }") 1036 | ``` 1037 | 1038 | ```out 1039 | (def shader 1040 | '{:inputs {a_position vec2} 1041 | :outputs {v_color vec4} 1042 | :functions " 1043 | void main() 1044 | { 1045 | gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0); 1046 | v_color = gl_Position * 0.5 + 0.5; 1047 | }"}) 1048 | ``` 1049 | -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | --------------------------------------------------------------------------------