Skip to content

Commit

Permalink
Fix "from" / "instance" across splits (#1995)
Browse files Browse the repository at this point in the history
It was incorrectly using the editor of the instance, rather than the
"from" editor

## Checklist

- [x] I have added
[tests](https://www.cursorless.org/docs/contributing/test-case-recorder/)
- [-] I have updated the
[docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and
[cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet)
- [-] I have not broken the cheatsheet
  • Loading branch information
pokey authored Nov 3, 2023
1 parent b2ab244 commit e21a07f
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,8 @@ export default class InstanceStage implements ModifierStage {
}

private handleEveryScope(target: Target): Target[] {
const { editor } = target;

return Array.from(
flatmap(this.getEveryRanges(editor), (searchRange) =>
flatmap(this.getEveryRanges(target), ([editor, searchRange]) =>
this.getTargetIterable(target, editor, searchRange, "forward"),
),
);
Expand All @@ -59,9 +57,7 @@ export default class InstanceStage implements ModifierStage {
target: Target,
{ start, length }: OrdinalScopeModifier,
): Target[] {
const { editor } = target;

return this.getEveryRanges(editor).flatMap((searchRange) =>
return this.getEveryRanges(target).flatMap(([editor, searchRange]) =>
takeFromOffset(
this.getTargetIterable(
target,
Expand All @@ -79,13 +75,12 @@ export default class InstanceStage implements ModifierStage {
target: Target,
{ direction, offset, length }: RelativeScopeModifier,
): Target[] {
const { editor } = target;

const referenceTargets = this.storedTargets.get("instanceReference") ?? [
target,
];

return referenceTargets.flatMap((referenceTarget) => {
const { editor } = referenceTarget;
const iterationRange =
direction === "forward"
? new Range(
Expand All @@ -109,11 +104,14 @@ export default class InstanceStage implements ModifierStage {
});
}

private getEveryRanges(editor: TextEditor): Range[] {
private getEveryRanges({
editor: targetEditor,
}: Target): readonly (readonly [TextEditor, Range])[] {
return (
this.storedTargets
.get("instanceReference")
?.map(({ contentRange }) => contentRange) ?? [editor.document.range]
?.map(({ editor, contentRange }) => [editor, contentRange] as const) ??
([[targetEditor, targetEditor.document.range]] as const)
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import {
HatStability,
Modifier,
Range,
SpyIDE,
asyncSafety,
} from "@cursorless/common";
import {
getCursorlessApi,
openNewEditor,
runCursorlessCommand,
} from "@cursorless/vscode-common";
import * as assert from "assert";
import { Selection } from "vscode";
import { endToEndTestSetup } from "../endToEndTestSetup";
import { setupFake } from "./setupFake";

// Ensure that the "from" / "instance" work properly when "from"
// is run in a different editor from "instance"
suite("Instance across split", async function () {
const { getSpy } = endToEndTestSetup(this);

suiteSetup(async () => {
const { ide } = (await getCursorlessApi()).testHelpers!;
setupFake(ide, HatStability.stable);
});

test(
"Every instance",
asyncSafety(() =>
runTest(
getSpy()!,
{
type: "everyScope",
scopeType: { type: "instance" },
},
true,
" bbb ",
),
),
);
test(
"Next instance",
asyncSafety(() =>
runTest(
getSpy()!,
{
type: "relativeScope",
scopeType: { type: "instance" },
direction: "forward",
length: 1,
offset: 1,
},
false,
" bbb aaa aaa",
),
),
);
test(
"Two instances",
asyncSafety(() =>
runTest(
getSpy()!,
{
type: "relativeScope",
scopeType: { type: "instance" },
direction: "forward",
length: 2,
offset: 0,
},
false,
" bbb aaa",
),
),
);
test(
"Second instance",
asyncSafety(() =>
runTest(
getSpy()!,
{
type: "ordinalScope",
scopeType: { type: "instance" },
length: 1,
start: 1,
},
true,
" aaa bbb aaa",
),
),
);
});

async function runTest(
spyIde: SpyIDE,
modifier: Modifier,
useWholeFile: boolean,
expectedContents: string,
) {
const { hatTokenMap } = (await getCursorlessApi()).testHelpers!;

const { document: instanceDocument } = await openNewEditor("aaa");
/** The editor containing the "instance" */
const instanceEditor = spyIde.activeTextEditor!;
/** The editor in which "from" is run */
const fromEditor = await openNewEditor(" aaa bbb aaa aaa", {
openBeside: true,
});
const { document: fromDocument } = fromEditor;
fromEditor.selections = [new Selection(0, 0, 0, 0)];

await hatTokenMap.allocateHats([
{
grapheme: "a",
hatStyle: "default",
hatRange: new Range(0, 0, 0, 1),
token: {
editor: instanceEditor,
offsets: { start: 0, end: 3 },
range: new Range(0, 0, 0, 3),
text: "aaa",
},
},
]);

// "from this" / "from file this", depending on the value of `useWholeFile`
await runCursorlessCommand({
version: 6,
action: {
name: "experimental.setInstanceReference",
target: {
type: "primitive",
mark: {
type: "cursor",
},
modifiers: useWholeFile
? [{ type: "containingScope", scopeType: { type: "document" } }]
: [],
},
},
usePrePhraseSnapshot: false,
});

// "change <modifier> air", where <modifier> is some kind of "instance"
// modifier
await runCursorlessCommand({
version: 6,
action: {
name: "clearAndSetSelection",
target: {
type: "primitive",
mark: {
type: "decoratedSymbol",
symbolColor: "default",
character: "a",
},
modifiers: [modifier],
},
},
usePrePhraseSnapshot: false,
});

assert.deepStrictEqual(instanceDocument.getText(), "aaa");
assert.deepStrictEqual(fromDocument.getText(), expectedContents);
}

0 comments on commit e21a07f

Please sign in to comment.