Skip to content

Commit

Permalink
Highlight instanceReference (#2154)
Browse files Browse the repository at this point in the history
  • Loading branch information
pokey authored Jan 11, 2024
1 parent f2ed4ad commit 2ae73a7
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 4 deletions.
6 changes: 6 additions & 0 deletions packages/common/src/StoredTargetKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const storedTargetKeys = [
"that",
"source",
"instanceReference",
] as const;
export type StoredTargetKey = (typeof storedTargetKeys)[number];
1 change: 1 addition & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,4 @@ export * from "./scopeSupportFacets/scopeSupportFacets.types";
export * from "./scopeSupportFacets/scopeSupportFacetInfos";
export * from "./scopeSupportFacets/textualScopeSupportFacetInfos";
export * from "./scopeSupportFacets/getLanguageScopeSupport";
export * from "./StoredTargetKey";
16 changes: 14 additions & 2 deletions packages/cursorless-engine/src/core/StoredTargets.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import { Notifier } from "@cursorless/common";
import { Target } from "../typings/target.types";

export type StoredTargetKey = "that" | "source" | "instanceReference";
import { StoredTargetKey, storedTargetKeys } from "@cursorless/common";

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

set(key: StoredTargetKey, targets: Target[] | undefined) {
this.targetMap.set(key, targets);
this.notifier.notifyListeners(key, targets);
}

get(key: StoredTargetKey) {
return this.targetMap.get(key);
}

onStoredTargets(
callback: (key: StoredTargetKey, targets: Target[] | undefined) => void,
) {
for (const key of storedTargetKeys) {
callback(key, this.get(key));
}

return this.notifier.registerListener(callback);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { StoredTargetKey, StoredTargetMap } from "../../core/StoredTargets";
import { StoredTargetKey } from "@cursorless/common";
import { StoredTargetMap } from "../../core/StoredTargets";
import { Target } from "../../typings/target.types";
import { MarkStage } from "../PipelineStages.types";

Expand Down
2 changes: 1 addition & 1 deletion packages/cursorless-vscode/src/constructTestHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import {
NormalizedIDE,
ScopeProvider,
SerializedMarks,
StoredTargetKey,
TargetPlainObject,
TestCaseSnapshot,
TextEditor,
} from "@cursorless/common";
import {
StoredTargetKey,
StoredTargetMap,
plainObjectToTarget,
takeSnapshot,
Expand Down
3 changes: 3 additions & 0 deletions packages/cursorless-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
} from "./ScopeVisualizerCommandApi";
import { StatusBarItem } from "./StatusBarItem";
import { vscodeApi } from "./vscodeApi";
import { storedTargetHighlighter } from "./storedTargetHighlighter";

/**
* Extension entrypoint called by VSCode on Cursorless startup.
Expand Down Expand Up @@ -127,6 +128,8 @@ export async function activate(
commandServerApi != null,
);

context.subscriptions.push(storedTargetHighlighter(vscodeIDE, storedTargets));

registerCommands(
context,
vscodeIDE,
Expand Down
75 changes: 75 additions & 0 deletions packages/cursorless-vscode/src/storedTargetHighlighter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { StoredTargetKey, groupBy, toCharacterRange } from "@cursorless/common";
import { StoredTargetMap } from "@cursorless/cursorless-engine";
import {
ScopeRangeType,
ScopeVisualizerColorConfig,
} from "@cursorless/vscode-common";
import { VscodeIDE } from "./ide/vscode/VscodeIDE";
import { VscodeFancyRangeHighlighter } from "./ide/vscode/VSCodeScopeVisualizer/VscodeFancyRangeHighlighter";
import { getColorsFromConfig } from "./ide/vscode/VSCodeScopeVisualizer/getColorsFromConfig";
import { mapValues } from "lodash";
import { usingSetting } from "./usingSetting";

const targetColorMap: Partial<Record<StoredTargetKey, ScopeRangeType>> = {
instanceReference: "domain",
};

/**
* Constructs the stored target highlighter and listens for changes to stored
* targets, highlighting them in the editor.
* @param ide The ide object
* @param storedTargets Keeps track of stored targets
* @returns A disposable that disposes of the stored target highlighter
*/
export function storedTargetHighlighter(
ide: VscodeIDE,
storedTargets: StoredTargetMap,
) {
return usingSetting<ScopeVisualizerColorConfig>(
"cursorless.scopeVisualizer",
"colors",
(colorConfig) => {
const highlighters = mapValues(targetColorMap, (type) =>
type == null
? undefined
: new VscodeFancyRangeHighlighter(
getColorsFromConfig(colorConfig, type),
),
);

const storedTargetsDisposable = storedTargets.onStoredTargets(
(key, targets) => {
const highlighter = highlighters[key];

if (highlighter == null) {
return;
}

const editorRangeMap = groupBy(
targets ?? [],
({ editor }) => editor.id,
);

ide.visibleTextEditors.forEach((editor) => {
highlighter.setRanges(
editor,
(editorRangeMap.get(editor.id) ?? []).map(({ contentRange }) =>
toCharacterRange(contentRange),
),
);
});
},
);

return {
dispose: () => {
for (const highlighter of Object.values(highlighters)) {
highlighter?.dispose();
}

storedTargetsDisposable.dispose();
},
};
},
);
}
38 changes: 38 additions & 0 deletions packages/cursorless-vscode/src/usingSetting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { vscodeApi } from "./vscodeApi";
import { Disposable } from "@cursorless/common";

/**
* Watches for changes to a setting and calls a factory function whenever the
* setting changes, disposing of any disposables created by the factory. On the
* initial call, the factory function is called immediately.
*
* @param section The section of the setting
* @param setting The setting
* @param factory A function that takes the setting value and returns a disposable
* @returns A disposable that disposes of the setting listener and any disposables created by the factory
*/
export function usingSetting<T>(
section: string,
setting: string,
factory: (value: T) => Disposable,
): Disposable {
const runFactoryWithLatestConfig = () =>
factory(vscodeApi.workspace.getConfiguration(section).get<T>(setting)!);

let disposable = runFactoryWithLatestConfig();
const configurationDisposable = vscodeApi.workspace.onDidChangeConfiguration(
({ affectsConfiguration }) => {
if (affectsConfiguration(`${section}.${setting}`)) {
disposable.dispose();
disposable = runFactoryWithLatestConfig();
}
},
);

return {
dispose: () => {
disposable.dispose();
configurationDisposable.dispose();
},
};
}

0 comments on commit 2ae73a7

Please sign in to comment.