Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Special formatting for comment form trailing bracket #1227

Merged
merged 32 commits into from
Jul 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a2266c6
Fix doc typo
bpringe May 12, 2021
a1afb57
Merge branch dev into published
PEZ May 12, 2021
dc4709e
Add missing `npm i` step
PEZ May 20, 2021
210e816
Merge branch dev into published
PEZ May 26, 2021
dedfd2c
Merge branch dev into published
PEZ Jun 4, 2021
35f1490
Merge branch dev into published
PEZ Jun 6, 2021
cd54a37
Add merch page to calva.io
PEZ Jun 22, 2021
31cf82b
Merch for German Amazon store available
PEZ Jun 23, 2021
157a8cf
Merge branch dev into published
PEZ Jun 24, 2021
b4cbac3
Merge branch dev into published
PEZ Jun 29, 2021
43f223b
Workaround for paredit's inability to support multiline
Jun 30, 2021
48e9e25
Revert Changes to plugin defaults
Jun 30, 2021
3b6c9e8
Add documentation.
Jun 30, 2021
fc32136
Merge branch 'dev' into 610-workaround
JBlaz Jul 1, 2021
ff5912c
Update merch [skip ci]
PEZ Jul 3, 2021
4c316d7
Merch Rich Comments 2
PEZ Jul 3, 2021
8915691
Merch: Symbol + rich comments in German store
PEZ Jul 4, 2021
5b0c797
Update Luminus instruictions
PEZ Jul 4, 2021
4313d41
Merge branch dev into published
PEZ Jul 4, 2021
24a11b8
Format comment w/ a symbol at closing paren
PEZ Jul 8, 2021
7b1bcd7
Merge branch 'dev' into 610-workaround
PEZ Jul 8, 2021
812d88b
Merge pull request #1220 from JBlaz/610-workaround
PEZ Jul 8, 2021
cec1d77
Issue-1228: merge cljfmt.edn file contents into calva formatting defa…
mchughs Jul 8, 2021
b560006
Merge branch 'dev' into dev
mchughs Jul 8, 2021
42f390c
Merge pull request #1229 from mchughs/dev
PEZ Jul 10, 2021
2062ad0
Format comment w/ a symbol at closing paren
PEZ Jul 8, 2021
bfff0cb
Merge branch '1224-comment-trail-bracket' of github.com:BetterThanTom…
PEZ Jul 10, 2021
01b50b9
Make special comment form formatting optional
PEZ Jul 10, 2021
20042d8
Add two tests
PEZ Jul 11, 2021
3b5ccdd
Add docs about comment formatting
PEZ Jul 11, 2021
5dcf36b
Rename variable
PEZ Jul 11, 2021
8c2753e
Crate Rich Comments doc page [skip ci]
PEZ Jul 11, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
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)
Expand Down
69 changes: 31 additions & 38 deletions docs/site/evaluation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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

Expand Down
9 changes: 5 additions & 4 deletions docs/site/formatting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -34,7 +35,7 @@ You configure Calva's formatting using [cljfmt's configuration EDN](https://gith
!!! Note
The `cljfmt` docs mention the `:cljfmt` config key of Leiningen projects. Calva does not yet read the config from there, so if your Leiningen project has such a configuration, you will need to copy it out into a file.

To start changing the defaults, paste the following map into a file and save it. It could be somewhere in the project workspace, or some other place, dependig on your requirements:
To start changing the Calva formatting defaults, paste the following map into a file and save it. It could be somewhere in the project workspace, or some other place, depending on your requirements:

```clojure
{:remove-surrounding-whitespace? true
Expand Down Expand Up @@ -89,6 +90,6 @@ 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. See [Rich Comments](rich-comments.md).
2 changes: 2 additions & 0 deletions docs/site/paredit.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,7 @@ There are some context keys you can utilize to configure keyboard shortcuts with

*The Nuclear Option*: You can choose to disable all default key bindings by configuring `calva.paredit.defaultKeyMap` to `none`. (Then you probably also want to register your own shortcuts for the commands you often use.)

In some instances built-in command defaults are the same as Paredit's defaults, and Paredit's functionality in a particular case is less than what the default is. This is true of *Expand Selection* and *Shrink Selection* for Windows/Linux when multiple lines are selected. In this particular case adding `!editorHasMultipleSelections` to the `when` clause of the binding makes for a better workflow. The point is that when the bindings overlap and default functionality is desired peaceful integration can be achieved with the right `when` clause. This is left out of Paredit's defaults to respect user preference, and ease of maintenance.


Happy Editing! ❤️
129 changes: 129 additions & 0 deletions docs/site/rich-comments.md
Original file line number Diff line number Diff line change
@@ -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.

<iframe width="560" height="315" src="https://www.youtube.com/embed/d0K1oaFGvuQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## 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)|)
```

<kbd>tab</kbd>

```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)

|

)
```

<kbd>tab</kbd>

```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))|
```

<kbd>tab</kbd>

```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.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ nav:
- Features:
- commands-top10.md
- evaluation.md
- rich-comments.md
- output.md
- formatting.md
- paredit.md
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
}
},
Expand Down
1 change: 1 addition & 0 deletions src/calva-fmt/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
};
Expand Down
4 changes: 3 additions & 1 deletion src/calva-fmt/src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +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 isComment = cursor.getFunctionName() === 'comment';
const config = {...extraConfig, "comment-form?": isComment};
let formatRange = cursor.rangeForList(formatDepth);
if (!formatRange) {
formatRange = cursor.rangeForCurrentForm(index);
Expand All @@ -60,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, 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);
Expand Down
Loading