From 24a11b86e58f0c79a9484141d460063caa8df3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 8 Jul 2021 14:04:59 +0200 Subject: [PATCH 1/7] Format comment w/ a symbol at closing paren Fixes #1224 WIP --- CHANGELOG.md | 1 + src/calva-fmt/src/format.ts | 6 +- src/cljs-lib/src/calva/fmt/formatter.cljs | 68 ++++++++++++++++++++--- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 357b3f217..963144e9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Changes to Calva. ## [Unreleased] +- [Put closing paren of rich comments on a separate line](https://github.com/BetterThanTomorrow/calva/issues/1224) ## [2.0.203] - 2021-07-04 - Fix: [Custom repl commands show error if run from non-clojure file](https://github.com/BetterThanTomorrow/calva/issues/1203) diff --git a/src/calva-fmt/src/format.ts b/src/calva-fmt/src/format.ts index e2a51fd11..90a37c09b 100644 --- a/src/calva-fmt/src/format.ts +++ b/src/calva-fmt/src/format.ts @@ -49,6 +49,7 @@ export function formatPositionInfo(editor: vscode.TextEditor, onType: boolean = const mirroredDoc: MirroredDocument = getDocument(doc); const cursor = mirroredDoc.getTokenCursor(index); const formatDepth = extraConfig["format-depth"] ? extraConfig["format-depth"] : 1; + const isFormattingComment = cursor.getFunctionName() === 'comment'; let formatRange = cursor.rangeForList(formatDepth); if (!formatRange) { formatRange = cursor.rangeForCurrentForm(index); @@ -60,7 +61,7 @@ export function formatPositionInfo(editor: vscode.TextEditor, onType: boolean = "range-text": string, "range": number[], "new-index": number - } = _formatIndex(doc.getText(), formatRange, index, doc.eol == 2 ? "\r\n" : "\n", onType, extraConfig); + } = _formatIndex(doc.getText(), formatRange, index, doc.eol == 2 ? "\r\n" : "\n", onType, isFormattingComment, extraConfig); const range: vscode.Range = new vscode.Range(doc.positionAt(formatted.range[0]), doc.positionAt(formatted.range[1])); const newIndex: number = doc.offsetAt(range.start) + formatted["new-index"]; const previousText: string = doc.getText(range); @@ -124,12 +125,13 @@ export function formatCode(code: string, eol: number) { } } -function _formatIndex(allText: string, range: [number, number], index: number, eol: string, onType: boolean = false, extraConfig = {}): { "range-text": string, "range": number[], "new-index": number } { +function _formatIndex(allText: string, range: [number, number], index: number, eol: string, onType: boolean = false, isFormattingComment: boolean, extraConfig = {}): { "range-text": string, "range": number[], "new-index": number } { const d = cljify({ "all-text": allText, "idx": index, "eol": eol, "range": range, + "formatting-comment?": isFormattingComment, "config": { ...config.getConfig(), ...extraConfig } }), result = jsify(onType ? formatTextAtIdxOnType(d) : formatTextAtIdx(d)); diff --git a/src/cljs-lib/src/calva/fmt/formatter.cljs b/src/cljs-lib/src/calva/fmt/formatter.cljs index 0072f244b..63a2ce55a 100644 --- a/src/cljs-lib/src/calva/fmt/formatter.cljs +++ b/src/cljs-lib/src/calva/fmt/formatter.cljs @@ -59,6 +59,9 @@ :remove-trailing-whitespace? false :remove-consecutive-blank-lines? false :align-associative? true}})) +(defn extract-range-text + [{:keys [all-text range]}] + (subs all-text (first range) (last range))) (defn current-line-empty? "Figure out if `:current-line` is empty" @@ -123,10 +126,10 @@ (defn format-text-at-range "Formats text from all-text at the range" - [{:keys [all-text range idx] :as m}] + [{:keys [range idx] :as m}] (let [indent-before (indent-before-range m) padding (apply str (repeat indent-before " ")) - range-text (subs all-text (first range) (last range)) + range-text (extract-range-text m) padded-text (str padding range-text) range-index (- idx (first range)) tail (subs range-text range-index) @@ -155,11 +158,13 @@ (defn add-indent-token-if-empty-current-line "If `:current-line` is empty add an indent token at `:idx`" [{:keys [head tail range] :as m}] - (let [indent-token "0"] + (let [indent-token "0" + new-range [(first range) (inc (last range))]] (if (current-line-empty? m) - (assoc m - :all-text (str head indent-token tail) - :range [(first range) (inc (last range))]) + (let [m1 (assoc m + :all-text (str head indent-token tail) + :range new-range)] + (assoc m1 :range-text (extract-range-text m1))) m))) @@ -171,18 +176,65 @@ :range [(first range) (dec (second range))]) m)) +(def commment_trail_token "_ctt_") +(def comment_trail_pattern (re-pattern (str "_ctt_\\)$"))) + +(defn add-trail-token-if-comment + "If the `range-text` is a comment, add a symbol at the end, preventing the last paren from folding" + [{:keys [range all-text formatting-comment? idx] :as m}] + (if formatting-comment? + (let [range-text (extract-range-text m) + new-range-text (clojure.string/replace + range-text + #"\n{0,1}[ \t]*\)$" + (str "\n" commment_trail_token ")")) + added-text-length (- (count new-range-text) + (count range-text)) + new-range-end (+ (second range) added-text-length) + new-all-text (str (subs all-text 0 (first range)) + new-range-text + (subs all-text (second range))) + new-idx (if (>= idx (- (second range) 1)) + (+ idx added-text-length) + idx)] + (-> m + (assoc :all-text new-all-text + :range-text new-range-text + :idx new-idx) + (assoc-in [:range 1] new-range-end))) + m)) + +(defn remove-trail-token-if-commment + "If the `range-text` is a comment, remove the symbol at the end" + [{:keys [range range-text new-index idx formatting-comment?] :as m} original-range] + (if formatting-comment? + (let [new-range-text (clojure.string/replace + range-text + comment_trail_pattern + ")")] + (-> m + (assoc :range-text new-range-text + :new-index (if (>= idx (- (second range) 1)) + (- (count new-range-text) + (- (second range) idx)) + new-index) + :range original-range))) + m)) (defn format-text-at-idx "Formats the enclosing range of text surrounding idx" - [m] + [{:keys [range] :as m}] + (def m m) (-> m + (add-trail-token-if-comment) (add-head-and-tail) (add-current-line) (add-indent-token-if-empty-current-line) #_(enclosing-range) (format-text-at-range) (index-for-tail-in-range) - (remove-indent-token-if-empty-current-line))) + (remove-indent-token-if-empty-current-line) + (remove-trail-token-if-commment range))) (defn format-text-at-idx-bridge [m] From 2062ad07192fe56f48b5f4246dcd2537400913f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 8 Jul 2021 14:04:59 +0200 Subject: [PATCH 2/7] Format comment w/ a symbol at closing paren Fixes #1224 WIP --- CHANGELOG.md | 1 + src/calva-fmt/src/format.ts | 6 +- src/cljs-lib/src/calva/fmt/formatter.cljs | 69 ++++++++++++++++++++--- 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9db75d026..d80dcb15b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changes to Calva. ## [Unreleased] - Fix: [Calva formatting defaults do not get applied when including any kind of .cljfmt.edn config](https://github.com/BetterThanTomorrow/calva/issues/1228) - Workaround: [Paredit commands don't propagate to multiple cursors](https://github.com/BetterThanTomorrow/calva/issues/610) +- [Put closing paren of rich comments on a separate line](https://github.com/BetterThanTomorrow/calva/issues/1224) ## [2.0.203] - 2021-07-04 - Fix: [Custom repl commands show error if run from non-clojure file](https://github.com/BetterThanTomorrow/calva/issues/1203) diff --git a/src/calva-fmt/src/format.ts b/src/calva-fmt/src/format.ts index e2a51fd11..90a37c09b 100644 --- a/src/calva-fmt/src/format.ts +++ b/src/calva-fmt/src/format.ts @@ -49,6 +49,7 @@ export function formatPositionInfo(editor: vscode.TextEditor, onType: boolean = const mirroredDoc: MirroredDocument = getDocument(doc); const cursor = mirroredDoc.getTokenCursor(index); const formatDepth = extraConfig["format-depth"] ? extraConfig["format-depth"] : 1; + const isFormattingComment = cursor.getFunctionName() === 'comment'; let formatRange = cursor.rangeForList(formatDepth); if (!formatRange) { formatRange = cursor.rangeForCurrentForm(index); @@ -60,7 +61,7 @@ export function formatPositionInfo(editor: vscode.TextEditor, onType: boolean = "range-text": string, "range": number[], "new-index": number - } = _formatIndex(doc.getText(), formatRange, index, doc.eol == 2 ? "\r\n" : "\n", onType, extraConfig); + } = _formatIndex(doc.getText(), formatRange, index, doc.eol == 2 ? "\r\n" : "\n", onType, isFormattingComment, extraConfig); const range: vscode.Range = new vscode.Range(doc.positionAt(formatted.range[0]), doc.positionAt(formatted.range[1])); const newIndex: number = doc.offsetAt(range.start) + formatted["new-index"]; const previousText: string = doc.getText(range); @@ -124,12 +125,13 @@ export function formatCode(code: string, eol: number) { } } -function _formatIndex(allText: string, range: [number, number], index: number, eol: string, onType: boolean = false, extraConfig = {}): { "range-text": string, "range": number[], "new-index": number } { +function _formatIndex(allText: string, range: [number, number], index: number, eol: string, onType: boolean = false, isFormattingComment: boolean, extraConfig = {}): { "range-text": string, "range": number[], "new-index": number } { const d = cljify({ "all-text": allText, "idx": index, "eol": eol, "range": range, + "formatting-comment?": isFormattingComment, "config": { ...config.getConfig(), ...extraConfig } }), result = jsify(onType ? formatTextAtIdxOnType(d) : formatTextAtIdx(d)); diff --git a/src/cljs-lib/src/calva/fmt/formatter.cljs b/src/cljs-lib/src/calva/fmt/formatter.cljs index c6fc2e4c2..f90dc8e47 100644 --- a/src/cljs-lib/src/calva/fmt/formatter.cljs +++ b/src/cljs-lib/src/calva/fmt/formatter.cljs @@ -67,6 +67,9 @@ :remove-trailing-whitespace? false :remove-consecutive-blank-lines? false :align-associative? true}})) +(defn extract-range-text + [{:keys [all-text range]}] + (subs all-text (first range) (last range))) (defn current-line-empty? "Figure out if `:current-line` is empty" @@ -131,10 +134,10 @@ (defn format-text-at-range "Formats text from all-text at the range" - [{:keys [all-text range idx] :as m}] + [{:keys [range idx] :as m}] (let [indent-before (indent-before-range m) padding (apply str (repeat indent-before " ")) - range-text (subs all-text (first range) (last range)) + range-text (extract-range-text m) padded-text (str padding range-text) range-index (- idx (first range)) tail (subs range-text range-index) @@ -163,11 +166,13 @@ (defn add-indent-token-if-empty-current-line "If `:current-line` is empty add an indent token at `:idx`" [{:keys [head tail range] :as m}] - (let [indent-token "0"] + (let [indent-token "0" + new-range [(first range) (inc (last range))]] (if (current-line-empty? m) - (assoc m - :all-text (str head indent-token tail) - :range [(first range) (inc (last range))]) + (let [m1 (assoc m + :all-text (str head indent-token tail) + :range new-range)] + (assoc m1 :range-text (extract-range-text m1))) m))) @@ -179,18 +184,64 @@ :range [(first range) (dec (second range))]) m)) +(def commment_trail_token "_ctt_") +(def comment_trail_pattern (re-pattern (str "_ctt_\\)$"))) + +(defn add-trail-token-if-comment + "If the `range-text` is a comment, add a symbol at the end, preventing the last paren from folding" + [{:keys [range all-text formatting-comment? idx] :as m}] + (if formatting-comment? + (let [range-text (extract-range-text m) + new-range-text (clojure.string/replace + range-text + #"\n{0,1}[ \t]*\)$" + (str "\n" commment_trail_token ")")) + added-text-length (- (count new-range-text) + (count range-text)) + new-range-end (+ (second range) added-text-length) + new-all-text (str (subs all-text 0 (first range)) + new-range-text + (subs all-text (second range))) + new-idx (if (>= idx (- (second range) 1)) + (+ idx added-text-length) + idx)] + (-> m + (assoc :all-text new-all-text + :range-text new-range-text + :idx new-idx) + (assoc-in [:range 1] new-range-end))) + m)) + +(defn remove-trail-token-if-commment + "If the `range-text` is a comment, remove the symbol at the end" + [{:keys [range range-text new-index idx formatting-comment?] :as m} original-range] + (if formatting-comment? + (let [new-range-text (clojure.string/replace + range-text + comment_trail_pattern + ")")] + (-> m + (assoc :range-text new-range-text + :new-index (if (>= idx (- (second range) 1)) + (- (count new-range-text) + (- (second range) idx)) + new-index) + :range original-range))) + m)) (defn format-text-at-idx "Formats the enclosing range of text surrounding idx" - [m] + [{:keys [range] :as m}] + (def m m) (-> m + (add-trail-token-if-comment) (add-head-and-tail) (add-current-line) (add-indent-token-if-empty-current-line) - #_(enclosing-range) (format-text-at-range) (index-for-tail-in-range) - (remove-indent-token-if-empty-current-line))) + (remove-indent-token-if-empty-current-line) + (remove-trail-token-if-commment range))) (defn format-text-at-idx-bridge [m] From 01b50b95214bd55809285a1fe9e099db8b9ac944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 10 Jul 2021 16:53:11 +0200 Subject: [PATCH 3/7] Make special comment form formatting optional --- package.json | 5 ++ src/calva-fmt/src/config.ts | 1 + src/calva-fmt/src/format.ts | 8 +-- src/cljs-lib/src/calva/fmt/formatter.cljs | 83 ++++++++++++----------- 4 files changed, 54 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index 49cb1bc34..a9254d604 100644 --- a/package.json +++ b/package.json @@ -590,6 +590,11 @@ "type": "boolean", "default": true, "markdownDescription": "Use the structural editor for indentation (instead of `cljfmt`)." + }, + "calva.fmt.keepCommentTrailParenOnOwnLine": { + "type": "boolean", + "default": true, + "markdownDescription": "Treat `(comment...)` forms special and keep its closing paren on a line of its own." } } }, diff --git a/src/calva-fmt/src/config.ts b/src/calva-fmt/src/config.ts index a592fcff0..ca66cbcec 100644 --- a/src/calva-fmt/src/config.ts +++ b/src/calva-fmt/src/config.ts @@ -12,6 +12,7 @@ const defaultCljfmtContent = "\ function configuration(workspaceConfig: vscode.WorkspaceConfiguration, cljfmtString: string) { return { "format-as-you-type": workspaceConfig.get("formatAsYouType") as boolean, + "keep-comment-forms-trail-paren-on-own-line?": workspaceConfig.get("keepCommentTrailParenOnOwnLine") as boolean, "cljfmt-string": cljfmtString, "cljfmt-options": cljfmtOptions(cljfmtString) }; diff --git a/src/calva-fmt/src/format.ts b/src/calva-fmt/src/format.ts index 90a37c09b..25ed72302 100644 --- a/src/calva-fmt/src/format.ts +++ b/src/calva-fmt/src/format.ts @@ -49,7 +49,8 @@ export function formatPositionInfo(editor: vscode.TextEditor, onType: boolean = const mirroredDoc: MirroredDocument = getDocument(doc); const cursor = mirroredDoc.getTokenCursor(index); const formatDepth = extraConfig["format-depth"] ? extraConfig["format-depth"] : 1; - const isFormattingComment = cursor.getFunctionName() === 'comment'; + const isComment = cursor.getFunctionName() === 'comment'; + const config = {...extraConfig, "comment-form?": isComment}; let formatRange = cursor.rangeForList(formatDepth); if (!formatRange) { formatRange = cursor.rangeForCurrentForm(index); @@ -61,7 +62,7 @@ export function formatPositionInfo(editor: vscode.TextEditor, onType: boolean = "range-text": string, "range": number[], "new-index": number - } = _formatIndex(doc.getText(), formatRange, index, doc.eol == 2 ? "\r\n" : "\n", onType, isFormattingComment, extraConfig); + } = _formatIndex(doc.getText(), formatRange, index, doc.eol == 2 ? "\r\n" : "\n", onType, config); const range: vscode.Range = new vscode.Range(doc.positionAt(formatted.range[0]), doc.positionAt(formatted.range[1])); const newIndex: number = doc.offsetAt(range.start) + formatted["new-index"]; const previousText: string = doc.getText(range); @@ -125,13 +126,12 @@ export function formatCode(code: string, eol: number) { } } -function _formatIndex(allText: string, range: [number, number], index: number, eol: string, onType: boolean = false, isFormattingComment: boolean, extraConfig = {}): { "range-text": string, "range": number[], "new-index": number } { +function _formatIndex(allText: string, range: [number, number], index: number, eol: string, onType: boolean = false, extraConfig = {}): { "range-text": string, "range": number[], "new-index": number } { const d = cljify({ "all-text": allText, "idx": index, "eol": eol, "range": range, - "formatting-comment?": isFormattingComment, "config": { ...config.getConfig(), ...extraConfig } }), result = jsify(onType ? formatTextAtIdxOnType(d) : formatTextAtIdx(d)); diff --git a/src/cljs-lib/src/calva/fmt/formatter.cljs b/src/cljs-lib/src/calva/fmt/formatter.cljs index f90dc8e47..8e0f05be3 100644 --- a/src/cljs-lib/src/calva/fmt/formatter.cljs +++ b/src/cljs-lib/src/calva/fmt/formatter.cljs @@ -184,55 +184,60 @@ :range [(first range) (dec (second range))]) m)) -(def commment_trail_token "_ctt_") -(def comment_trail_pattern (re-pattern (str "_ctt_\\)$"))) +(def trailing-bracket_symbol "_calva-fmt-trail-symbol_") +(def trailing-bracket_pattern (re-pattern (str "_calva-fmt-trail-symbol_\\)$"))) (defn add-trail-token-if-comment "If the `range-text` is a comment, add a symbol at the end, preventing the last paren from folding" - [{:keys [range all-text formatting-comment? idx] :as m}] - (if formatting-comment? - (let [range-text (extract-range-text m) - new-range-text (clojure.string/replace - range-text - #"\n{0,1}[ \t]*\)$" - (str "\n" commment_trail_token ")")) - added-text-length (- (count new-range-text) - (count range-text)) - new-range-end (+ (second range) added-text-length) - new-all-text (str (subs all-text 0 (first range)) - new-range-text - (subs all-text (second range))) - new-idx (if (>= idx (- (second range) 1)) - (+ idx added-text-length) - idx)] - (-> m - (assoc :all-text new-all-text - :range-text new-range-text - :idx new-idx) - (assoc-in [:range 1] new-range-end))) - m)) + [{:keys [range all-text config idx] :as m}] + (let [keep-trailing-bracket-on-own-line? + (and (:keep-comment-forms-trail-paren-on-own-line? config) + (:comment-form? config))] + (if keep-trailing-bracket-on-own-line? + (let [range-text (extract-range-text m) + new-range-text (clojure.string/replace + range-text + #"\n{0,1}[ \t]*\)$" + (str "\n" trailing-bracket_symbol ")")) + added-text-length (- (count new-range-text) + (count range-text)) + new-range-end (+ (second range) added-text-length) + new-all-text (str (subs all-text 0 (first range)) + new-range-text + (subs all-text (second range))) + new-idx (if (>= idx (- (second range) 1)) + (+ idx added-text-length) + idx)] + (-> m + (assoc :all-text new-all-text + :range-text new-range-text + :idx new-idx) + (assoc-in [:range 1] new-range-end))) + m))) (defn remove-trail-token-if-commment "If the `range-text` is a comment, remove the symbol at the end" - [{:keys [range range-text new-index idx formatting-comment?] :as m} original-range] - (if formatting-comment? - (let [new-range-text (clojure.string/replace - range-text - comment_trail_pattern - ")")] - (-> m - (assoc :range-text new-range-text - :new-index (if (>= idx (- (second range) 1)) - (- (count new-range-text) - (- (second range) idx)) - new-index) - :range original-range))) - m)) + [{:keys [range range-text new-index idx config] :as m} original-range] + (let [keep-trailing-bracket-on-own-line? + (and (:keep-comment-forms-trail-paren-on-own-line? config) + (:comment-form? config))] + (if keep-trailing-bracket-on-own-line? + (let [new-range-text (clojure.string/replace + range-text + trailing-bracket_pattern + ")")] + (-> m + (assoc :range-text new-range-text + :new-index (if (>= idx (- (second range) 1)) + (- (count new-range-text) + (- (second range) idx)) + new-index) + :range original-range))) + m))) (defn format-text-at-idx "Formats the enclosing range of text surrounding idx" [{:keys [range] :as m}] - (def m m) (-> m (add-trail-token-if-comment) (add-head-and-tail) From 20042d8453c3ce22b7e3472c506f808d75c1fa36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 11 Jul 2021 08:28:19 +0200 Subject: [PATCH 4/7] Add two tests --- .../test/calva/fmt/formatter_test.cljs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/cljs-lib/test/calva/fmt/formatter_test.cljs b/src/cljs-lib/test/calva/fmt/formatter_test.cljs index 17a59bb93..69cbaae29 100644 --- a/src/cljs-lib/test/calva/fmt/formatter_test.cljs +++ b/src/cljs-lib/test/calva/fmt/formatter_test.cljs @@ -27,6 +27,40 @@ baz)") (is (= [10 38] (:range (sut/format-text-at-idx {:eol "\n" :all-text all-text :range [10 38] :idx 11}))))) +(def a-comment + {:eol "\n" + :all-text " (foo) +(comment + (defn bar + [x] + +baz))" + :range [8 48] + :idx 47 + :config {:keep-comment-forms-trail-paren-on-own-line? true + :comment-form? true}}) + +(deftest format-text-w-comments-at-idx + (is (= {:new-index 38 + :range-text "(comment + (defn bar + [x] + + baz))"} + (select-keys (sut/format-text-at-idx + (assoc-in a-comment [:config :comment-form?] false)) + [:range-text :new-index]))) + + (is (= {:new-index 41 + :range-text "(comment + (defn bar + [x] + + baz) + )"} + (select-keys (sut/format-text-at-idx + (assoc a-comment :idx 47)) + [:range-text :new-index])))) (deftest new-index (is (= 1 From 3b5ccdd2664f26f4879ad1fce36f557106b3d90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 11 Jul 2021 08:56:46 +0200 Subject: [PATCH 5/7] Add docs about comment formatting --- docs/site/formatting.md | 79 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/docs/site/formatting.md b/docs/site/formatting.md index ac5c98e17..fa47eaf46 100644 --- a/docs/site/formatting.md +++ b/docs/site/formatting.md @@ -11,7 +11,8 @@ With the default settings, Calva's formatting behaves like so: * formats the current enclosing form when you hit `tab` * formats pasted code * formats according to community standards (see above link) -* formats the current form, _aligning map keys and values_, when you press `ctrl+alt+l`. +* formats the current form, _aligning map keys and values_, when you press `ctrl+alt+l` +* formats `(comment ..)` forms special, see [rich comments](#rich-comments) !!! Tips Calva has a command that will ”heal” the bracket structure if it is correctly indented. Yes, it is Parinfer behind the scenes. This command is default bound to `shift+tab` to form a nicely balanced pair with the `tab` formatting. @@ -89,6 +90,78 @@ Save, then hit `tab`, and the code should get formatted like so: That's somewhat similar to Nikita Prokopov's [Better Clojure Formatting](https://tonsky.me/blog/clojurefmt/) suggestion. (Please be aware that this setting might not be sufficient to get complete **Tonsky Formatting**, please share any settings you use to get full compliance.) -## Under Construction +## Rich Comments -Much of this formatting configurability is recent work. There might be dragons. And also, we probably should make Calva pick the `:cljfmt` config up from Leiningen project files. If you agree, and there isn't an issue about that already, please file one. +To encourage use of `(comment ...)` forms for development, these forms get a special treatment when formatting. The closing bracket is kept on a line of its own. + +```clojure +(comment + ) +``` + +With the cursor somewhere directly inside the comment form (denoted with a `|`): + +```clojure +(comment + (def foo +:foo)|) +``` + +tab + +```clojure +(comment + (def foo + :foo) + |) +``` + +### Thinking space is kept + +The formatter will not remove newlines between the cursor and the closing bracket. So if you have entered a few lines to get ”thinking” room: + +```clojure +(comment + (def foo +:foo) + +| + +) +``` + +tab + +```clojure +(comment + (def foo + :foo) + + | + + ) +``` + +### Fold when done + +To fold the trailing paren automatically, place the cursor immediately outside (before or after) the form: + +```clojure +(comment + (def foo +:foo))| +``` + +tab + +```clojure +(comment + (def foo + :foo))| +``` + +### Enabled by default + +You can disable this behaviour with the setting: `calva.fmt.keepCommentTrailParenOnOwnLine`. + +But why would you? It is awesome! 😄 \ No newline at end of file From 5dcf36be318346360ee1bf82d3ae9c9f81d02d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 11 Jul 2021 12:51:34 +0200 Subject: [PATCH 6/7] Rename variable --- src/cljs-lib/src/calva/fmt/formatter.cljs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cljs-lib/src/calva/fmt/formatter.cljs b/src/cljs-lib/src/calva/fmt/formatter.cljs index 8e0f05be3..63acee806 100644 --- a/src/cljs-lib/src/calva/fmt/formatter.cljs +++ b/src/cljs-lib/src/calva/fmt/formatter.cljs @@ -187,7 +187,7 @@ (def trailing-bracket_symbol "_calva-fmt-trail-symbol_") (def trailing-bracket_pattern (re-pattern (str "_calva-fmt-trail-symbol_\\)$"))) -(defn add-trail-token-if-comment +(defn add-trail-symbol-if-comment "If the `range-text` is a comment, add a symbol at the end, preventing the last paren from folding" [{:keys [range all-text config idx] :as m}] (let [keep-trailing-bracket-on-own-line? @@ -215,7 +215,7 @@ (assoc-in [:range 1] new-range-end))) m))) -(defn remove-trail-token-if-commment +(defn remove-trail-symbol-if-commment "If the `range-text` is a comment, remove the symbol at the end" [{:keys [range range-text new-index idx config] :as m} original-range] (let [keep-trailing-bracket-on-own-line? @@ -239,14 +239,14 @@ "Formats the enclosing range of text surrounding idx" [{:keys [range] :as m}] (-> m - (add-trail-token-if-comment) + (add-trail-symbol-if-comment) (add-head-and-tail) (add-current-line) (add-indent-token-if-empty-current-line) (format-text-at-range) (index-for-tail-in-range) (remove-indent-token-if-empty-current-line) - (remove-trail-token-if-commment range))) + (remove-trail-symbol-if-commment range))) (defn format-text-at-idx-bridge [m] From 8c2753eb32e4cb4fd5c1c59cb1a9450f88ed1fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 11 Jul 2021 15:09:10 +0200 Subject: [PATCH 7/7] Crate Rich Comments doc page [skip ci] --- docs/site/evaluation.md | 69 +++++++++----------- docs/site/formatting.md | 74 +-------------------- docs/site/rich-comments.md | 129 +++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 4 files changed, 162 insertions(+), 111 deletions(-) create mode 100644 docs/site/rich-comments.md diff --git a/docs/site/evaluation.md b/docs/site/evaluation.md index b4fc4eaf0..eec5dff1c 100644 --- a/docs/site/evaluation.md +++ b/docs/site/evaluation.md @@ -6,12 +6,16 @@ NB: _The below assumes you have read about [Finding Calva Commands and Shortcuts ## Evaluation in a File Editor -Calva has commands for evaluating the **current form** and the **current top-level form**. +Calva has many commands for evaluating forms, including the **current form** and the **current top-level form**. -You can also choose what should happen with the results: +Some of the commands also let you choose what should happen with the results: -1. **Inline.** This will display the results (or some of it, if it is long) inline in the editor. _You find the full results in the [output window](output.md)_, from where it is easy to copy it to the clipboard. -1. **To comments.** This will add the results as comment lines below the current line. +1. **Inline.** This will display the results (or some of it, if it is long) inline in the editor. + * This also creates a hover pane including the full results and a button which will copy the results to the clipboard. + * There is also a command for copying the last result to the clipboard. + * The full results are always available in the [output window](output.md). + * There is a command for showing the output window, allowing for a workflow where you either generally have it closed, or have it as one of the tabs in the same editor group as the files you are working with. +1. **To comments.** This will add the results as line comments below the current line. 1. **Replace the evaluated code.** This will do what it says, the evaluated code will be replaced with its results. ## Wait, Current Form? Top-level Form? @@ -22,47 +26,36 @@ These are important concepts in Calva in order for you to create your most effec Default shortcut for evaluating the current form: `ctrl+enter`. -The current form either means the current selection, or otherwise is based on the cursor position. Play some with the command **Calva: Select current form**, `ctrl+alt+c s`, to figure out what Calva thinks is the current form for some different situations. Try it inside a symbol, adjacent to a symbol (both sides) and adjacent to an opening or closing bracket (again, both sides). Generally the current form is determined like so: - -If text is selected, then that text - -If the cursor is ”in” a symbol, then that symbol -```clojure -foob|ar ; foobar -``` - -If the cursor is adjacent to a form (a symbol or a list of some kind), then that form -```clojure -(foo bar |(baz)) ; (baz) -``` - -If the cursor is between to forms, then the left side form -```clojure -(foo bar | (baz)) ; bar -``` - -If the cursor is before the first form of a line, then that form -```clojure -(foo - | bar (baz)) ; bar -``` +The **current form** either means the current selection, or otherwise is based on the cursor position. Play some with the command **Calva: Select current form**, `ctrl+alt+c s`, to figure out what Calva thinks is the current form for some different situations. Try it inside a symbol, adjacent to a symbol (both sides) and adjacent to an opening or closing bracket (again, both sides). Generally the current form is determined like so: + +1. If text is selected, then that text +1. If the cursor is ”in” a symbol, then that symbol + ```clojure + foob|ar ; foobar + ``` +1. If the cursor is adjacent to a form (a symbol or a list of some kind), then that form + ```clojure + (foo bar |(baz)) ; (baz) + ``` +1. If the cursor is between to forms, then the left side form + ```clojure + (foo bar | (baz)) ; bar + ``` +1. If the cursor is before the first form of a line, then that form + ```clojure + (foo + | bar (baz)) ; bar + ``` ### Current Top-level Form Default shortcut for evaluating the current top level form: `alt+enter`. -The current top-level form means top-level in a structural sense. It is _not_ the topmost form in the file. Typically in a Clojure file you will find `def` and `defn` (and `defwhatever`) forms at the top level, but it can be any form not enclosed in any other form. - -An exception is the `comment` form. It will create a new top level context, so that any forms immediately inside a `(commment ...)` form will be considered top-level by Calva. This is to support a workflow where you - -1. Iterate on your functions. -2. Evaluate the function (top level). -3. Put them to test with expressions inside a `comment` form. -4. Repeat from *1.*, until the function does what you want it to do. +The **current top-level form** means top-level in a structural sense. It is _not_ the topmost form in the file. Typically in a Clojure file you will find `def` and `defn` (and `defwhatever`) forms at the top level, which also is one major intended use for evaluating top level form: _to define and redefine variables_. However, Calva does not check the contents of the form in order to determine it as a top-level forms: _all forms not enclosed in any other form are top level forms_. -Here's a demo of the last repetition of such a workflow, for a simple implementation of the `abs` function: +An ”exception” is introduced by the `comment` form. It will create a new top level context, so that any forms immediately inside a `(commment ...)` form will be considered top-level by Calva. This is to support a workflow with what is often referred to the [Rich Comments](rich-comments.md). -![top-level-eval](images/howto/top-level-eval.gif) +At the top level the selection of which form is the current top level form follows the same rules as those for [the current form](#current-form). ### Evaluate to Cursor diff --git a/docs/site/formatting.md b/docs/site/formatting.md index fa47eaf46..ef8737d5e 100644 --- a/docs/site/formatting.md +++ b/docs/site/formatting.md @@ -92,76 +92,4 @@ That's somewhat similar to Nikita Prokopov's [Better Clojure Formatting](https:/ ## Rich Comments -To encourage use of `(comment ...)` forms for development, these forms get a special treatment when formatting. The closing bracket is kept on a line of its own. - -```clojure -(comment - ) -``` - -With the cursor somewhere directly inside the comment form (denoted with a `|`): - -```clojure -(comment - (def foo -:foo)|) -``` - -tab - -```clojure -(comment - (def foo - :foo) - |) -``` - -### Thinking space is kept - -The formatter will not remove newlines between the cursor and the closing bracket. So if you have entered a few lines to get ”thinking” room: - -```clojure -(comment - (def foo -:foo) - -| - -) -``` - -tab - -```clojure -(comment - (def foo - :foo) - - | - - ) -``` - -### Fold when done - -To fold the trailing paren automatically, place the cursor immediately outside (before or after) the form: - -```clojure -(comment - (def foo -:foo))| -``` - -tab - -```clojure -(comment - (def foo - :foo))| -``` - -### Enabled by default - -You can disable this behaviour with the setting: `calva.fmt.keepCommentTrailParenOnOwnLine`. - -But why would you? It is awesome! 😄 \ No newline at end of file +To encourage use of `(comment ...)` forms for development, these forms get a special treatment when formatting. See [Rich Comments](rich-comments.md). diff --git a/docs/site/rich-comments.md b/docs/site/rich-comments.md new file mode 100644 index 000000000..8da74751b --- /dev/null +++ b/docs/site/rich-comments.md @@ -0,0 +1,129 @@ +# Rich Comments Support + +Why bother with **Rich comments**? Read on. Consider watching [this Youtube video](https://www.youtube.com/watch?v=d0K1oaFGvuQ) for a demo of the workflow using the (in?)famous FizzBuzz problem as an example. + + + +## Things in `comment` is not evaluated + +The Clojure `comment` macro is defined like so: + +```clojure +(defmacro comment + "Ignores body, yields nil" + {:added "1.0"} + [& body]) +``` + +It has no forms in its body and will therefore always (as long as the Clojure Reader can read it) evaluate to `nil`. That is: _nothing in the `(comment ...)` form will get evaluated when the file is loaded_. + +This makes it a very good ”place” where you can develop code, experiment with code, and keep example code. Since you will be able to load/evaluate the current file without worrying about that the code in the `comment` form will get evaluated. This also holds true for when using tools that hot-reloads the code on save, such as [Figwheel](https://figwheel.org), [shadow-cljs](https://github.com/thheller/shadow-cljs) and [Krell](https://calva.io/krell/). + +To develop or refine a function you might: + +1. Open up a `(comment ...)` form +1. Inside this form, type a first, super simple, version (or refinement) of your function and evaluate it +1. Inside the same `comment` form, type some code to test your function and evaluate that + * Or type and evaluate some code you might need for your function +1. Repeat from *2.*, until the function does what you want it to do +1. Move the function definition out of the `comment` form +1. Clean up the `comment` form to keep some of the test code as example use, or ”design decision log” for the function. + +!!! Note + Using `(comment ...)` forms for developing code is very common among Clojure coders. Rich Hickey is known for using it, which is why they are called **Rich comments** to begin with (even if it also is a very rich experience). + +## Calva encourages Rich comments + +Calva has several features to facilitate the Rich comments workflow, e.g. + +1. Secial [Syntax highlight](customizing.md#calva-highlight). By default `comment` forms are rendered in _italics_ +1. Special [top-level form](evaluation.md#current-top-level-form) context +1. Special formatting + +### `comment` is top-level + +To make it easy to evaluate forms in `(commment ...)` forms, they create a new top-level context. Instead of you having to place the cursor with precision before evaluating the **current form**, you can have the cursor anywhere within a `comment` enclosed form and [**Evaluate Top-Level Form**](evaluation.md#current-top-level-form). + +This carries over to all commands in Calva which deal with the top level form. Including [custom command snippets](custom-commands.md). + +### Special formatting + +To invite a **Rich comments** workflow, the Calva command **Format Current Form** will not fold the closing bracket of the `(comment ...)` form. Instead it will place this bracket on a line of its own (or keep it there). + +```clojure +(comment + ) +``` + +With the cursor somewhere directly inside the comment form (denoted with a `|`): + +```clojure +(comment + (def foo +:foo)|) +``` + +tab + +```clojure +(comment + (def foo + :foo) + |) +``` + +#### Thinking space is kept + +The formatter will not remove newlines between the cursor and the closing bracket. So if you have entered a few lines to get ”thinking” room: + +```clojure +(comment + (def foo +:foo) + +| + +) +``` + +tab + +```clojure +(comment + (def foo + :foo) + + | + + ) +``` + +#### Fold when done + +To fold the trailing paren automatically, place the cursor immediately outside (before or after) the form: + +```clojure +(comment + (def foo +:foo))| +``` + +tab + +```clojure +(comment + (def foo + :foo))| +``` + +#### Enabled by default + +You can disable this behavior with the setting: `calva.fmt.keepCommentTrailParenOnOwnLine`. + +But why would you? It is awesome! 😄 + + +#### Only for the Current Form + +!!! Note + This treatment only applies to formatting of [the current form](evaluation.md#current-form). With [fold when done](#fold-when-done) as an exception. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index a1cc04fbc..8024363e4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -32,6 +32,7 @@ nav: - Features: - commands-top10.md - evaluation.md + - rich-comments.md - output.md - formatting.md - paredit.md