Skip to content

Commit fd37ef7

Browse files
committed
Merge branch dev into published
2 parents 453b4d6 + 28df841 commit fd37ef7

File tree

14 files changed

+116
-43
lines changed

14 files changed

+116
-43
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ Changes to Calva.
44

55
## [Unreleased]
66

7+
## [2.0.481] - 2024-10-29
8+
9+
- [Add extension when contexts for Calva states such as project root, session type, ns](https://github.com/BetterThanTomorrow/calva/issues/2652)
10+
- Fix: [Calva internals: The `backwardSexp` function can't handle skipping ignored forms, even though it says it can](https://github.com/BetterThanTomorrow/calva/issues/2657)
11+
- Fix: [Keep support for evaluating top level form in ignored forms when at top level](https://github.com/BetterThanTomorrow/calva/issues/2655)
12+
- [Enable separate styling for top level ignored forms](https://github.com/BetterThanTomorrow/calva/issues/2660)
13+
714
## [2.0.480] - 2024-10-21
815

916
- Fix: [Custom command snippets use the wrong ns when repl sessions types do not match](https://github.com/BetterThanTomorrow/calva/issues/2653)

docs/site/evaluation.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Some of the commands also let you choose what should happen with the results:
3535
* The `line` style is the default.
3636
* The `ignore` style will put an ignore marker (`#_`) before the result.
3737
* The `rcf` style will wrap the result in a rich comment form ( `(comment ...)`).
38-
38+
3939
Here are some example keybindings for using the different comment styles with the **Evaluate Top Level Form (defun) to Comment** command:
4040

4141
```jsonc
@@ -108,6 +108,8 @@ The **current top-level form** means top-level in a structural sense. It is _not
108108

109109
An ”exception” is introduced by the `comment` form. It will create a new top level context, so that any forms immediately inside a `(comment ...)` 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).
110110

111+
A special case is ignored forms (using the `#_` marker) at the top level. They will always be selected as top level forms separately from their ignore marker, enabling evaluating them as top level forms. Similar to Rich Comments.
112+
111113
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).
112114

113115
### Evaluate Enclosing Form

docs/site/syntax-highlighting.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ You are in charge of how brackets and comments are highlighted via the `calva.hi
4343
| `cycleBracketColors` | Whether same colors should be <br> reused for deeply nested brackets | `true` |
4444
| `misplacedBracketStyle` | Style of misplaced bracket | `{ "border": "2px solid #c33" }` |
4545
| `matchedBracketStyle` | Style of bracket pair highlight | `{"backgroundColor": "#E0E0E0"}` |
46-
| `ignoredFormStyle` | Style of `#_...` form | `{"textDecoration": "none; opacity: 0.5"}` |
46+
| `ignoredFormStyle` | Style of `#_...` forms | `{"textDecoration": "none; opacity: 0.5"}` |
47+
| `ignoredTopLevelFormStyle` | Style of `#_...` forms at the top level. (If not set, uses `ignoredFormStyle`) | `{ "textDecoration": "none; text-shadow: 2px 2px 5px rgba(255, 215, 0, 0.75)" }` |
4748
| `commentFormStyle` | Style of `(comment ...)` form | `{"fontStyle": "italic"}` |
4849

4950
!!! Note "Calva disables the VS Code built-in indent guides"

docs/site/when-clauses.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ description: Calva comes with batteries included and preconfigured, and if you d
1919
* `calva:cursorAfterComment`: `true` when the cursor is adjacent after a line comment
2020
* `calva:cursorAtStartOfLine`: `true` when the cursor is at the start of a line including any leading whitespace
2121
* `calva:cursorAtEndOfLine`: `true` when the cursor is at the end of a line including any trailing whitespace
22-
22+
* `calva:projectRoot`: A string with the absolute path to the repl project root, _without trailing slash_
23+
* `calva:ns`: A string with the current namespace
24+
* `calva:replSessionType`: `clj`, or `cljs` depending on the file type of the current file

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Calva: Clojure & ClojureScript Interactive Programming",
44
"description": "Integrated REPL, formatter, Paredit, and more. Powered by cider-nrepl and clojure-lsp.",
55
"icon": "assets/calva.png",
6-
"version": "2.0.480",
6+
"version": "2.0.481",
77
"publisher": "betterthantomorrow",
88
"author": {
99
"name": "Better Than Tomorrow",
@@ -1142,6 +1142,12 @@
11421142
"default": null,
11431143
"description": "Style of `#_` ignored forms",
11441144
"scope": "resource"
1145+
},
1146+
"calva.highlight.ignoredTopLevelFormStyle": {
1147+
"type": "object",
1148+
"default": null,
1149+
"markdownDescription": "Style of top level `#_` ignored forms. If not specified, it will be the same as what's set for `calva.highlight.ignoredFormStyle`",
1150+
"scope": "resource"
11451151
}
11461152
}
11471153
}

src/cursor-doc/token-cursor.ts

+5-10
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,7 @@ export class LispTokenCursor extends TokenCursor {
316316
*
317317
* @returns true if the cursor was moved, false otherwise.
318318
*/
319-
backwardSexp(
320-
skipComments = true,
321-
skipMetadata = false,
322-
skipIgnoredForms = false,
323-
skipReaders = true
324-
) {
319+
backwardSexp(skipComments = true, skipMetadata = false, skipReaders = true) {
325320
const stack = [];
326321
this.backwardWhitespace(skipComments);
327322
if (this.getPrevToken().type === 'open') {
@@ -345,9 +340,9 @@ export class LispTokenCursor extends TokenCursor {
345340
}
346341
if (skipMetadata) {
347342
const metaCursor = this.clone();
348-
metaCursor.backwardSexp(true, false, false, false);
343+
metaCursor.backwardSexp(true, false, false);
349344
if (metaCursor.tokenBeginsMetadata()) {
350-
this.backwardSexp(skipComments, skipMetadata, skipIgnoredForms);
345+
this.backwardSexp(skipComments, skipMetadata, skipReaders);
351346
}
352347
}
353348
if (skipReaders) {
@@ -376,9 +371,9 @@ export class LispTokenCursor extends TokenCursor {
376371
}
377372
if (skipMetadata) {
378373
const metaCursor = this.clone();
379-
metaCursor.backwardSexp(true, false, false, false);
374+
metaCursor.backwardSexp(true, false, false);
380375
if (metaCursor.tokenBeginsMetadata()) {
381-
this.backwardSexp(skipComments, skipMetadata, skipIgnoredForms);
376+
this.backwardSexp(skipComments, skipMetadata, skipReaders);
382377
}
383378
}
384379
if (skipReaders) {

src/cursor-doc/utilities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function isRightSexpStructural(cursor: LispTokenCursor): boolean {
4141
return false;
4242
}
4343
cursor.forwardSexp(true, true, false);
44-
cursor.backwardSexp(false, false, false, false);
44+
cursor.backwardSexp(false, false, false);
4545

4646
const token = cursor.getToken();
4747
if (token.type === 'open') {

src/extension-test/unit/cursor-doc/token-cursor-test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,20 @@ describe('Token Cursor', () => {
228228
cursor.backwardSexp(true, true);
229229
expect(cursor.offsetStart).toBe(b.selections[0].anchor);
230230
});
231+
it('Does not skip ignored forms if skipIgnoredForms', () => {
232+
const a = docFromTextNotation('(a #_1 #_2 |3)');
233+
const b = docFromTextNotation('(a #_a #_|2 3)');
234+
const cursor = a.getTokenCursor(a.selections[0].anchor);
235+
cursor.backwardSexp(true, true);
236+
expect(cursor.offsetStart).toBe(b.selections[0].anchor);
237+
});
238+
it('Does not skip stacked ignored forms', () => {
239+
const a = docFromTextNotation('(a #_ #_ 1 2 |3)');
240+
const b = docFromTextNotation('(a #_ #_ 1 |2 3)');
241+
const cursor = a.getTokenCursor(a.selections[0].anchor);
242+
cursor.backwardSexp(true, true);
243+
expect(cursor.offsetStart).toBe(b.selections[0].anchor);
244+
});
231245
});
232246

233247
describe('downList', () => {
@@ -790,6 +804,13 @@ describe('Token Cursor', () => {
790804
const cursor: LispTokenCursor = a.getTokenCursor(a.selections[0].active);
791805
expect(cursor.rangeForDefun(a.selections[0].active)).toEqual(textAndSelection(b)[1]);
792806
});
807+
// https://github.com/BetterThanTomorrow/calva/issues/2655
808+
it('Does not include ignore marker', () => {
809+
const a = docFromTextNotation('a #_ [b (c|)] [d]');
810+
const b = docFromTextNotation('a #_ |[b (c)]| [d]');
811+
const cursor: LispTokenCursor = a.getTokenCursor(a.selections[0].active);
812+
expect(cursor.rangeForDefun(a.selections[0].active)).toEqual(textAndSelection(b)[1]);
813+
});
793814
describe('Rich Comment Form top level context', () => {
794815
it('Finds range for a top level form inside a comment', () => {
795816
const a = docFromTextNotation('aaa (comment [bbb cc|c] ddd)');
@@ -940,6 +961,13 @@ describe('Token Cursor', () => {
940961
const cursor: LispTokenCursor = a.getTokenCursor(0);
941962
expect(cursor.rangeForDefun(a.selections[0].anchor)).toEqual(textAndSelection(b)[1]);
942963
});
964+
// https://github.com/BetterThanTomorrow/calva/issues/2655
965+
it('Does not include ignore marker', () => {
966+
const a = docFromTextNotation('aaa (comment #_ [bbb ccc|] ddd)');
967+
const b = docFromTextNotation('aaa (comment #_ |[bbb ccc]| ddd)');
968+
const cursor: LispTokenCursor = a.getTokenCursor(a.selections[0].active);
969+
expect(cursor.rangeForDefun(a.selections[0].active)).toEqual(textAndSelection(b)[1]);
970+
});
943971
});
944972
});
945973

src/highlight/src/extension.ts

+41-21
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ let lastHighlightedEditor,
3939
commentFormType: vscode.TextEditorDecorationType,
4040
ignoredFormStyle,
4141
ignoredFormType: vscode.TextEditorDecorationType,
42+
ignoredTopLevelFormStyle,
43+
ignoredTopLevelFormType: vscode.TextEditorDecorationType,
4244
enableBracketColors,
4345
useRainbowIndentGuides,
4446
highlightActiveIndent,
@@ -152,6 +154,13 @@ function reset_styles() {
152154
}
153155
ignoredFormType = decorationType(ignoredFormStyle || { textDecoration: 'none; opacity: 0.5' });
154156

157+
if (ignoredTopLevelFormType) {
158+
activeEditor.setDecorations(ignoredTopLevelFormType, []);
159+
}
160+
ignoredTopLevelFormType = decorationType(
161+
ignoredTopLevelFormStyle || ignoredFormStyle || { textDecoration: 'none; opacity: 0.5' }
162+
);
163+
155164
dirty = false;
156165
}
157166

@@ -206,6 +215,11 @@ function reloadConfig() {
206215
dirty = true;
207216
}
208217

218+
if (!isEqual(ignoredTopLevelFormStyle, configuration.get('ignoredTopLevelFormStyle'))) {
219+
ignoredTopLevelFormStyle = configuration.get('ignoredTopLevelFormStyle');
220+
dirty = true;
221+
}
222+
209223
if (dirty) {
210224
scheduleRainbowBrackets();
211225
}
@@ -231,18 +245,19 @@ function updateRainbowBrackets() {
231245
reset_styles();
232246
}
233247

234-
const doc = activeEditor.document,
235-
mirrorDoc = docMirror.getDocument(doc),
236-
rainbow = rainbowTypes.map(() => []),
237-
rainbowGuides = rainbowTypes.map(() => []),
238-
misplaced = [],
239-
comment_forms = [],
240-
ignores = [],
241-
len = rainbowTypes.length,
242-
colorsEnabled = enableBracketColors && len > 0,
243-
guideColorsEnabled = useRainbowIndentGuides && len > 0,
244-
activeGuideEnabled = highlightActiveIndent && len > 0,
245-
colorIndex = cycleBracketColors ? (i) => i % len : (i) => Math.min(i, len - 1);
248+
const doc = activeEditor.document;
249+
const mirrorDoc = docMirror.getDocument(doc);
250+
const rainbow = rainbowTypes.map(() => []);
251+
const rainbowGuides = rainbowTypes.map(() => []);
252+
const misplaced = [];
253+
const comment_forms = [];
254+
const ignores = [];
255+
const topLevelIgnores = [];
256+
const len = rainbowTypes.length;
257+
const colorsEnabled = enableBracketColors && len > 0;
258+
const guideColorsEnabled = useRainbowIndentGuides && len > 0;
259+
const activeGuideEnabled = highlightActiveIndent && len > 0;
260+
const colorIndex = cycleBracketColors ? (i) => i % len : (i) => Math.min(i, len - 1);
246261

247262
let in_comment_form = false;
248263
let stack_depth = 0;
@@ -252,14 +267,14 @@ function updateRainbowBrackets() {
252267
placedGuidesColor = new Map();
253268
activeEditor.visibleRanges.forEach((range) => {
254269
// Find the visible forms
255-
const startOffset = doc.offsetAt(range.start),
256-
endOffset = doc.offsetAt(range.end),
257-
startCursor: LispTokenCursor = mirrorDoc.getTokenCursor(0),
258-
startRange = startCursor.rangeForDefun(startOffset, false),
259-
endCursor: LispTokenCursor = mirrorDoc.getTokenCursor(endOffset),
260-
endRange = endCursor.rangeForDefun(endOffset, false),
261-
rangeStart = startRange ? startRange[0] : startOffset,
262-
rangeEnd = endRange ? endRange[1] : endOffset;
270+
const startOffset = doc.offsetAt(range.start);
271+
const endOffset = doc.offsetAt(range.end);
272+
const startCursor: LispTokenCursor = mirrorDoc.getTokenCursor(0);
273+
const startRange = startCursor.rangeForDefun(startOffset, false);
274+
const endCursor: LispTokenCursor = mirrorDoc.getTokenCursor(endOffset);
275+
const endRange = endCursor.rangeForDefun(endOffset, false);
276+
const rangeStart = startRange ? startRange[0] : startOffset;
277+
const rangeEnd = endRange ? endRange[1] : endOffset;
263278
// Look for top level ignores, and adjust starting point if found
264279
const topLevelSentinelCursor = mirrorDoc.getTokenCursor(rangeStart);
265280
let startPaintingFrom = rangeStart;
@@ -299,7 +314,11 @@ function updateRainbowBrackets() {
299314
ignoreCursor.forwardSexp(true, true, true);
300315
}
301316
const ignore_end = activeEditor.document.positionAt(ignoreCursor.offsetStart);
302-
ignores.push(new Range(ignore_start, ignore_end));
317+
if (cursor.atTopLevel()) {
318+
topLevelIgnores.push(new Range(ignore_start, ignore_end));
319+
} else {
320+
ignores.push(new Range(ignore_start, ignore_end));
321+
}
303322
}
304323
}
305324
const token = cursor.getToken(),
@@ -399,6 +418,7 @@ function updateRainbowBrackets() {
399418
activeEditor.setDecorations(misplacedType, misplaced);
400419
activeEditor.setDecorations(commentFormType, comment_forms);
401420
activeEditor.setDecorations(ignoredFormType, ignores);
421+
activeEditor.setDecorations(ignoredTopLevelFormType, topLevelIgnores);
402422
matchPairs();
403423
if (activeGuideEnabled) {
404424
decorateActiveGuides();

src/state.ts

+2
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ export async function initProjectDir(
202202
);
203203
}
204204
if (projectRootPath) {
205+
console.log('Setting project root to: ', projectRootPath.fsPath);
206+
void vscode.commands.executeCommand('setContext', 'calva:projectRoot', projectRootPath.fsPath);
205207
setStateValue(PROJECT_DIR_KEY, projectRootPath.fsPath);
206208
setStateValue(PROJECT_DIR_URI_KEY, projectRootPath);
207209
return projectRootPath;

src/when-contexts.ts

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { deepEqual } from './util/object';
33
import * as docMirror from './doc-mirror';
44
import * as context from './cursor-doc/cursor-context';
55
import * as util from './utilities';
6+
import * as namespace from './namespace';
7+
import * as session from './nrepl/repl-session';
8+
import { cljsLib } from './utilities';
69

710
export let lastContexts: context.CursorContext[] = [];
811
export let currentContexts: context.CursorContext[] = [];
@@ -18,6 +21,10 @@ export function setCursorContextIfChanged(editor: vscode.TextEditor) {
1821
}
1922
const contexts = determineCursorContexts(editor.document, editor.selections[0].active);
2023
setCursorContexts(contexts);
24+
const [ns, _form] = namespace.getDocumentNamespace(editor.document);
25+
void vscode.commands.executeCommand('setContext', 'calva:ns', ns);
26+
const sessionType = session.getReplSessionType(cljsLib.getStateValue('connected'));
27+
void vscode.commands.executeCommand('setContext', 'calva:replSessionType', sessionType);
2128
}
2229

2330
function determineCursorContexts(

test-data/.vscode/settings.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,8 @@
139139
"afterCL]ReplJackInCode": ["(println :hello)", "(println :world!)"],
140140
"cljsType": "none"
141141
}
142-
]
142+
],
143+
"calva.highlight.ignoredTopLevelFormStyle": {
144+
"textDecoration": "none; text-shadow: 2px 2px 5px rgba(255, 215, 0, 0.75)"
145+
}
143146
}

test-data/test-files/highlight_test.clj

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
;; \
2323
"()"
2424
;; \
25-
;;
25+
;;
2626
(((#((())))))
2727
([ #{ }()[]])
2828
@@ -54,10 +54,10 @@
5454
(println "I ❤️Clojure")
5555
([{} () []]))
5656
57-
57+
5858
(comment
5959
(+ (* 2 2)
60-
2)
60+
2)
6161
(Math/abs -1)
6262
(defn hello [s]
6363
(str "Hello " s))
@@ -143,7 +143,7 @@ bar
143143
[:c {:d :e}]]
144144
[:b
145145
[:c {:d :e}]]]
146-
(comment
146+
(comment
147147
(foo #_"bar" baz))
148148
#_{:foo "foo"
149149
:bar (comment [["bar"]])}

0 commit comments

Comments
 (0)