Skip to content

Commit 1e19863

Browse files
committed
Merge branch dev into published
2 parents d4d66ed + 6a3311e commit 1e19863

11 files changed

Lines changed: 126 additions & 46 deletions

File tree

.circleci/config.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,6 @@ jobs:
225225
- run:
226226
name: Apt install missing dependencies
227227
command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E88979FB9B30ACF2; sudo apt update && sudo apt install -y libnss3 imagemagick
228-
- run:
229-
name: Pip install Basilisp (Python)
230-
command: command -v python3 >/dev/null || (sudo apt-get update && sudo apt-get install -y python3); python3 -m pip --version >/dev/null 2>&1 || (sudo apt-get update && sudo apt-get install -y python3-pip); python3 -m pip install --upgrade pip setuptools wheel; python3 -m pip install basilisp==0.1.0b2;
231228
- run:
232229
name: Run Extension Tests
233230
command: npm run integration-test

.circleci/jobs/test-integration.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,6 @@ steps:
2222
--recv-keys E88979FB9B30ACF2;
2323
sudo apt update && sudo apt install -y libnss3 imagemagick
2424

25-
- !:cmd
26-
- Pip install Basilisp (Python)
27-
- command -v python3 >/dev/null || (sudo apt-get update && sudo apt-get install -y python3);
28-
python3 -m pip --version >/dev/null 2>&1 || (sudo apt-get update && sudo apt-get install -y python3-pip);
29-
python3 -m pip install --upgrade pip setuptools wheel;
30-
python3 -m pip install basilisp==0.1.0b2;
31-
3225
- !:cmd
3326
- Run Extension Tests
3427
- npm run integration-test

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Changes to Calva.
44

55
## [Unreleased]
66

7+
## [2.0.587] - 2026-05-10
8+
9+
- Fix: [The structural editor fails in some API usage scenarios](https://github.com/BetterThanTomorrow/calva/issues/3218)
10+
711
## [2.0.586] - 2026-05-09
812

913
- Fix: [Prevent calva from breaking other debugging session](https://github.com/BetterThanTomorrow/calva/pull/2936)

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
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.586",
6+
"version": "2.0.587",
77
"publisher": "betterthantomorrow",
88
"author": {
99
"name": "Better Than Tomorrow",

src/cursor-doc/model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ export type ModelEditOptions = {
250250
skipFormat?: boolean;
251251
selections?: ModelEditSelection[];
252252
builder?: TextEditorEdit;
253+
editor?: unknown;
253254
};
254255

255256
export interface EditableModel {

src/doc-mirror/index.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export class DocumentModel implements EditableModel {
171171
this.document.selections = options.selections;
172172
}
173173
if (!options.skipFormat) {
174-
const editor = utilities.getActiveTextEditor();
174+
const editor = (options.editor as vscode.TextEditor) ?? utilities.getActiveTextEditor();
175175
void formatter.scheduleFormatAsType(editor, {});
176176
}
177177
}
@@ -279,7 +279,7 @@ export class DocumentModel implements EditableModel {
279279
: undefined;
280280
// Do the edits (with undoStopAfter=false if we will reformat,
281281
// to include the reformatting in the same undo-unit as the edit).
282-
const editor = utilities.getActiveTextEditor();
282+
const editor = (options.editor as vscode.TextEditor) ?? utilities.getActiveTextEditor();
283283
const editCompletion = editor.edit(
284284
(builder) => {
285285
this.editNowTextOnly(modelEdits, { builder: builder, ...options });
@@ -327,8 +327,7 @@ export class DocumentModel implements EditableModel {
327327
oldSelection?: [number, number],
328328
newSelection?: [number, number]
329329
) {
330-
const editor = utilities.getActiveTextEditor(),
331-
document = editor.document;
330+
const document = this.document.document;
332331
builder.insert(document.positionAt(offset), text);
333332
}
334333

@@ -340,8 +339,7 @@ export class DocumentModel implements EditableModel {
340339
oldSelection?: [number, number],
341340
newSelection?: [number, number]
342341
) {
343-
const editor = utilities.getActiveTextEditor(),
344-
document = editor.document,
342+
const document = this.document.document,
345343
range = new vscode.Range(document.positionAt(start), document.positionAt(end));
346344
builder.replace(range, text);
347345
}
@@ -353,8 +351,7 @@ export class DocumentModel implements EditableModel {
353351
oldSelection?: [number, number],
354352
newSelection?: [number, number]
355353
) {
356-
const editor = utilities.getActiveTextEditor(),
357-
document = editor.document,
354+
const document = this.document.document,
358355
range = new vscode.Range(document.positionAt(offset), document.positionAt(offset + count));
359356
builder.delete(range);
360357
}

src/edit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@ export function replace(
633633
undoStopBefore: true,
634634
},
635635
...options,
636+
editor,
636637
}
637638
);
638639
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import * as assert from 'assert';
2+
import * as path from 'path';
3+
import * as testUtil from './util';
4+
import * as vscode from 'vscode';
5+
import * as edit from '../../../edit';
6+
import * as docMirror from '../../../doc-mirror';
7+
8+
const suiteName = 'Edit Replace Suite';
9+
10+
const targetFilePath = path.join(testUtil.testDataDir, 'edit-replace-target.clj');
11+
const otherFilePath = path.join(testUtil.testDataDir, 'reformattable.clj');
12+
13+
suite(suiteName, function () {
14+
teardown(async function () {
15+
// Revert target file to original content
16+
const targetDoc = await vscode.workspace.openTextDocument(vscode.Uri.file(targetFilePath));
17+
const fullRange = new vscode.Range(
18+
new vscode.Position(0, 0),
19+
targetDoc.positionAt(targetDoc.getText().length)
20+
);
21+
const editor = await vscode.window.showTextDocument(targetDoc);
22+
await editor.edit((builder) => {
23+
builder.replace(fullRange, '(ns edit-replace-target)\n\n(def a 1)\n\n(def b 2)\n');
24+
});
25+
await targetDoc.save();
26+
});
27+
28+
test('should apply edit to the passed editor, not activeTextEditor', async function () {
29+
// Open the target file in Column Two — capture a fresh editor reference
30+
const targetDoc = await vscode.workspace.openTextDocument(vscode.Uri.file(targetFilePath));
31+
const targetEditor = await vscode.window.showTextDocument(targetDoc, {
32+
viewColumn: vscode.ViewColumn.Two,
33+
preview: false,
34+
});
35+
36+
// Wait for the mirror doc to be available for the target file
37+
await testUtil.waitForCondition(
38+
() => {
39+
try {
40+
docMirror.getDocument(targetDoc);
41+
return true;
42+
} catch {
43+
return false;
44+
}
45+
},
46+
4000,
47+
50,
48+
'Timed out waiting for mirror document for target file'
49+
);
50+
51+
// Show the other file in Column One — explicitly setting viewColumn
52+
// ensures it opens in a different group and becomes activeTextEditor
53+
const otherDoc = await vscode.workspace.openTextDocument(vscode.Uri.file(otherFilePath));
54+
await vscode.window.showTextDocument(otherDoc, {
55+
viewColumn: vscode.ViewColumn.One,
56+
preview: false,
57+
});
58+
await testUtil.waitForCondition(
59+
() => vscode.window.activeTextEditor?.document.uri.fsPath === otherFilePath,
60+
4000,
61+
50,
62+
'Timed out waiting for other file to become active'
63+
);
64+
65+
// Confirm precondition: active editor is NOT the target file
66+
const activeEditor = vscode.window.activeTextEditor;
67+
assert.ok(activeEditor, 'There should be an active editor');
68+
assert.strictEqual(
69+
activeEditor.document.uri.fsPath,
70+
otherFilePath,
71+
'Precondition: active editor should be the other file'
72+
);
73+
74+
const targetContentBefore = targetDoc.getText();
75+
const otherContentBefore = activeEditor.document.getText();
76+
77+
// Call edit.replace with the target editor (which is NOT activeTextEditor)
78+
const range = new vscode.Range(
79+
new vscode.Position(2, 0),
80+
new vscode.Position(2, '(def a 1)'.length)
81+
);
82+
const result = await edit.replace(targetEditor, range, '(def a 42)', {
83+
skipFormat: true,
84+
});
85+
86+
assert.strictEqual(result, true, 'edit.replace should return true');
87+
88+
// The edit should have gone to the target file
89+
const targetContentAfter = targetDoc.getText();
90+
assert.ok(
91+
targetContentAfter.includes('(def a 42)'),
92+
`Target file should contain the replacement text. Got: ${targetContentAfter}`
93+
);
94+
assert.ok(
95+
!targetContentAfter.includes('(def a 1)'),
96+
`Target file should no longer contain the original text. Got: ${targetContentAfter}`
97+
);
98+
99+
// The active editor's file should be unchanged
100+
const otherContentAfter = activeEditor.document.getText();
101+
assert.strictEqual(
102+
otherContentAfter,
103+
otherContentBefore,
104+
'Active editor content should be unchanged'
105+
);
106+
});
107+
});

src/extension-test/integration/suite/jack-in-and-connect-test.ts

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -67,31 +67,6 @@ suite('Jack-in and Connect suite', () => {
6767
testUtil.log(suite, 'test.clj closed');
6868
});
6969

70-
test('start repl and connect (jack-in) to Basilisp', async function () {
71-
testUtil.log(suite, 'start repl and connect (jack-in) to Basilisp');
72-
const basilispPath = config.getConfig().basilispPath;
73-
const executablePath = testUtil.getExecutablePath(basilispPath);
74-
75-
if (executablePath === null && !testUtil.isCircleCI) {
76-
testUtil.log(suite, `Basilisp executable '${basilispPath}' not found, skipping test...`);
77-
this.skip();
78-
} else {
79-
testUtil.log(suite, `Basilisp executable found at ${executablePath}`);
80-
81-
const testFilePath = await startJackInProcedure(
82-
suite,
83-
'calva.jackIn',
84-
'basilisp',
85-
'../projects/minimal-basilisp/src/test.lpy'
86-
);
87-
88-
await loadAndAssert(suite, testFilePath, ['; bar', 'nil', 'basilisp꞉test꞉> ']);
89-
90-
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
91-
testUtil.log(suite, 'test.lpy closed for Basilisp');
92-
}
93-
});
94-
9570
test('Jack-in afterPrimaryReplConnectedCode can be a string', async () => {
9671
testUtil.log(suite, 'Reconnect: afterPrimaryReplConnectedCode (string)');
9772
const connectSequence: connectSequenceTypes.ReplConnectSequence = {

0 commit comments

Comments
 (0)