Skip to content

Commit 2ae73a7

Browse files
authored
Highlight instanceReference (#2154)
## Checklist - [-] 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
1 parent f2ed4ad commit 2ae73a7

File tree

8 files changed

+140
-4
lines changed

8 files changed

+140
-4
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const storedTargetKeys = [
2+
"that",
3+
"source",
4+
"instanceReference",
5+
] as const;
6+
export type StoredTargetKey = (typeof storedTargetKeys)[number];

packages/common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,4 @@ export * from "./scopeSupportFacets/scopeSupportFacets.types";
9999
export * from "./scopeSupportFacets/scopeSupportFacetInfos";
100100
export * from "./scopeSupportFacets/textualScopeSupportFacetInfos";
101101
export * from "./scopeSupportFacets/getLanguageScopeSupport";
102+
export * from "./StoredTargetKey";
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
1+
import { Notifier } from "@cursorless/common";
12
import { Target } from "../typings/target.types";
2-
3-
export type StoredTargetKey = "that" | "source" | "instanceReference";
3+
import { StoredTargetKey, storedTargetKeys } from "@cursorless/common";
44

55
/**
66
* Used to store targets between commands. This is used by marks like `that`
77
* and `source`.
88
*/
99
export class StoredTargetMap {
1010
private targetMap: Map<StoredTargetKey, Target[] | undefined> = new Map();
11+
private notifier = new Notifier<[StoredTargetKey, Target[] | undefined]>();
1112

1213
set(key: StoredTargetKey, targets: Target[] | undefined) {
1314
this.targetMap.set(key, targets);
15+
this.notifier.notifyListeners(key, targets);
1416
}
1517

1618
get(key: StoredTargetKey) {
1719
return this.targetMap.get(key);
1820
}
21+
22+
onStoredTargets(
23+
callback: (key: StoredTargetKey, targets: Target[] | undefined) => void,
24+
) {
25+
for (const key of storedTargetKeys) {
26+
callback(key, this.get(key));
27+
}
28+
29+
return this.notifier.registerListener(callback);
30+
}
1931
}

packages/cursorless-engine/src/processTargets/marks/StoredTargetStage.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { StoredTargetKey, StoredTargetMap } from "../../core/StoredTargets";
1+
import { StoredTargetKey } from "@cursorless/common";
2+
import { StoredTargetMap } from "../../core/StoredTargets";
23
import { Target } from "../../typings/target.types";
34
import { MarkStage } from "../PipelineStages.types";
45

packages/cursorless-vscode/src/constructTestHelpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import {
77
NormalizedIDE,
88
ScopeProvider,
99
SerializedMarks,
10+
StoredTargetKey,
1011
TargetPlainObject,
1112
TestCaseSnapshot,
1213
TextEditor,
1314
} from "@cursorless/common";
1415
import {
15-
StoredTargetKey,
1616
StoredTargetMap,
1717
plainObjectToTarget,
1818
takeSnapshot,

packages/cursorless-vscode/src/extension.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
} from "./ScopeVisualizerCommandApi";
5151
import { StatusBarItem } from "./StatusBarItem";
5252
import { vscodeApi } from "./vscodeApi";
53+
import { storedTargetHighlighter } from "./storedTargetHighlighter";
5354

5455
/**
5556
* Extension entrypoint called by VSCode on Cursorless startup.
@@ -127,6 +128,8 @@ export async function activate(
127128
commandServerApi != null,
128129
);
129130

131+
context.subscriptions.push(storedTargetHighlighter(vscodeIDE, storedTargets));
132+
130133
registerCommands(
131134
context,
132135
vscodeIDE,
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { StoredTargetKey, groupBy, toCharacterRange } from "@cursorless/common";
2+
import { StoredTargetMap } from "@cursorless/cursorless-engine";
3+
import {
4+
ScopeRangeType,
5+
ScopeVisualizerColorConfig,
6+
} from "@cursorless/vscode-common";
7+
import { VscodeIDE } from "./ide/vscode/VscodeIDE";
8+
import { VscodeFancyRangeHighlighter } from "./ide/vscode/VSCodeScopeVisualizer/VscodeFancyRangeHighlighter";
9+
import { getColorsFromConfig } from "./ide/vscode/VSCodeScopeVisualizer/getColorsFromConfig";
10+
import { mapValues } from "lodash";
11+
import { usingSetting } from "./usingSetting";
12+
13+
const targetColorMap: Partial<Record<StoredTargetKey, ScopeRangeType>> = {
14+
instanceReference: "domain",
15+
};
16+
17+
/**
18+
* Constructs the stored target highlighter and listens for changes to stored
19+
* targets, highlighting them in the editor.
20+
* @param ide The ide object
21+
* @param storedTargets Keeps track of stored targets
22+
* @returns A disposable that disposes of the stored target highlighter
23+
*/
24+
export function storedTargetHighlighter(
25+
ide: VscodeIDE,
26+
storedTargets: StoredTargetMap,
27+
) {
28+
return usingSetting<ScopeVisualizerColorConfig>(
29+
"cursorless.scopeVisualizer",
30+
"colors",
31+
(colorConfig) => {
32+
const highlighters = mapValues(targetColorMap, (type) =>
33+
type == null
34+
? undefined
35+
: new VscodeFancyRangeHighlighter(
36+
getColorsFromConfig(colorConfig, type),
37+
),
38+
);
39+
40+
const storedTargetsDisposable = storedTargets.onStoredTargets(
41+
(key, targets) => {
42+
const highlighter = highlighters[key];
43+
44+
if (highlighter == null) {
45+
return;
46+
}
47+
48+
const editorRangeMap = groupBy(
49+
targets ?? [],
50+
({ editor }) => editor.id,
51+
);
52+
53+
ide.visibleTextEditors.forEach((editor) => {
54+
highlighter.setRanges(
55+
editor,
56+
(editorRangeMap.get(editor.id) ?? []).map(({ contentRange }) =>
57+
toCharacterRange(contentRange),
58+
),
59+
);
60+
});
61+
},
62+
);
63+
64+
return {
65+
dispose: () => {
66+
for (const highlighter of Object.values(highlighters)) {
67+
highlighter?.dispose();
68+
}
69+
70+
storedTargetsDisposable.dispose();
71+
},
72+
};
73+
},
74+
);
75+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { vscodeApi } from "./vscodeApi";
2+
import { Disposable } from "@cursorless/common";
3+
4+
/**
5+
* Watches for changes to a setting and calls a factory function whenever the
6+
* setting changes, disposing of any disposables created by the factory. On the
7+
* initial call, the factory function is called immediately.
8+
*
9+
* @param section The section of the setting
10+
* @param setting The setting
11+
* @param factory A function that takes the setting value and returns a disposable
12+
* @returns A disposable that disposes of the setting listener and any disposables created by the factory
13+
*/
14+
export function usingSetting<T>(
15+
section: string,
16+
setting: string,
17+
factory: (value: T) => Disposable,
18+
): Disposable {
19+
const runFactoryWithLatestConfig = () =>
20+
factory(vscodeApi.workspace.getConfiguration(section).get<T>(setting)!);
21+
22+
let disposable = runFactoryWithLatestConfig();
23+
const configurationDisposable = vscodeApi.workspace.onDidChangeConfiguration(
24+
({ affectsConfiguration }) => {
25+
if (affectsConfiguration(`${section}.${setting}`)) {
26+
disposable.dispose();
27+
disposable = runFactoryWithLatestConfig();
28+
}
29+
},
30+
);
31+
32+
return {
33+
dispose: () => {
34+
disposable.dispose();
35+
configurationDisposable.dispose();
36+
},
37+
};
38+
}

0 commit comments

Comments
 (0)