Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Changes to Calva.

## [Unreleased]

- Fix: [Slurp backward with previous commented expr](https://github.com/BetterThanTomorrow/calva/issues/3025)
- Fix: [Pair selection and drag with threaded `cond->`](https://github.com/BetterThanTomorrow/calva/issues/3018)

## [2.0.547] - 2026-02-02

- Fix: [The 'calva.loadFile' command returns a Promise that is only completed on the first execution.](https://github.com/BetterThanTomorrow/calva/issues/2981)
Expand Down
5 changes: 5 additions & 0 deletions src/cursor-doc/paredit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,11 @@ function backwardSlurpSexpEdits(doc: EditableDocument, start: number): ModelEdit
// Navigate to previous sexp
cursor.previous();
cursor.backwardSexp(true, true);

// Check if there's an ignore marker (#_) before the sexp we just found
// This ensures that slurping includes the ignore marker as part of the slurped form
cursor.backwardThroughAnyIgnore();

const prevSexpStart = cursor.offsetStart;

// Skip whitespace to check if there's a previous sexp to slurp
Expand Down
27 changes: 24 additions & 3 deletions src/cursor-doc/token-cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,18 @@ export class LispTokenCursor extends TokenCursor {
this.next();
break;
case 'ignore':
// Always include the ignored form as part of this sexp
this.next();
this.forwardSexp(skipComments, skipMetadata, skipIgnoredForms);
if (skipIgnoredForms) {
this.next();
this.forwardSexp(skipComments, skipMetadata, skipIgnoredForms);
// Continue to next sexp when skipping ignored forms
break;
}
// eslint-disable-next-line no-fallthrough
// Stop here: the ignored form is the sexp we found
if (stack.length <= 0) {
return true;
}
break;
case 'id':
case 'lit':
case 'kw':
Expand Down Expand Up @@ -411,6 +417,21 @@ export class LispTokenCursor extends TokenCursor {
return hasReader;
}

/**
* Moves this cursor past the previous non-ws token, if it is an `ignore` token.
* Otherwise, this cursor is left unaffected.
*/
backwardThroughAnyIgnore() {
const cursor = this.clone();
cursor.backwardWhitespace();
if (cursor.getPrevToken().type === 'ignore') {
cursor.previous();
this.set(cursor);
return true;
}
return false;
}

/**
* Moves this cursor past the next non-ws token, if it is a `reader` token.
* Otherwise, this cursor is left unaffected.
Expand Down
12 changes: 12 additions & 0 deletions src/extension-test/unit/cursor-doc/paredit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2423,6 +2423,18 @@ describe('paredit', () => {
await paredit.backwardSlurpSexp(a);
expect(textAndSelection(a)).toEqual(textAndSelection(b));
});
it('slurps form before empty list with ignore marker preceding', async () => {
const a = docFromTextNotation('#_(dosomething) (|)');
const b = docFromTextNotation('(#_(dosomething)|)');
await paredit.backwardSlurpSexp(a);
expect(textAndSelection(a)).toEqual(textAndSelection(b));
});
it('slurps form before empty list with ignore marker preceding but does not slurp previous sexp', async () => {
const a = docFromTextNotation('something #_(dosomething) (|)');
const b = docFromTextNotation('something (#_(dosomething)|)');
await paredit.backwardSlurpSexp(a);
expect(textAndSelection(a)).toEqual(textAndSelection(b));
});
});
});

Expand Down
2 changes: 1 addition & 1 deletion src/extension-test/unit/cursor-doc/token-cursor-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ describe('Token Cursor', () => {
});
it('Does not skip ignored forms if skipIgnoredForms is false', () => {
const a = docFromTextNotation('(a| #_1 #_2 3)');
const b = docFromTextNotation('(a #_|a #_2 3)');
const b = docFromTextNotation('(a #_1| #_2 3)');
const cursor = a.getTokenCursor(a.selections[0].anchor);
cursor.forwardSexp(true, true);
expect(cursor.offsetStart).toBe(b.selections[0].anchor);
Expand Down