diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index 54df310148..1a5039f12b 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -38,6 +38,7 @@ import { import { KeyboardCommands } from "./keyboard/KeyboardCommands"; import { registerCommands } from "./registerCommands"; import { ReleaseNotes } from "./ReleaseNotes"; +import { revisualizeOnCustomRegexChange } from "./revisualizeOnCustomRegexChange"; import { ScopeTreeProvider } from "./ScopeTreeProvider"; import { ScopeVisualizer, @@ -100,6 +101,9 @@ export async function activate( const statusBarItem = StatusBarItem.create("cursorless.showQuickPick"); const keyboardCommands = KeyboardCommands.create(context, statusBarItem); const scopeVisualizer = createScopeVisualizer(normalizedIde, scopeProvider); + context.subscriptions.push( + revisualizeOnCustomRegexChange(scopeVisualizer, scopeProvider), + ); new ScopeTreeProvider( vscodeApi, diff --git a/packages/cursorless-vscode/src/revisualizeOnCustomRegexChange.ts b/packages/cursorless-vscode/src/revisualizeOnCustomRegexChange.ts new file mode 100644 index 0000000000..ea757d894c --- /dev/null +++ b/packages/cursorless-vscode/src/revisualizeOnCustomRegexChange.ts @@ -0,0 +1,62 @@ +import { + Disposable, + ScopeProvider, + ScopeTypeInfo, + disposableFrom, +} from "@cursorless/common"; +import { isEqual } from "lodash"; +import { + ScopeVisualizer, + VisualizationType, +} from "./ScopeVisualizerCommandApi"; + +/** + * Attempts to ensure that the scope visualizer is still visualizing the same + * scope type after the user changes one of their custom regexes. Because custom + * regexes don't have a unique identifier, we have to do some guesswork to + * figure out which custom regex the user changed. This function look for a + * custom regex with the same spoken form as the one that was changed, and if it + * finds one, it starts visualizing that one instead. + * + * @param scopeVisualizer The scope visualizer to listen to + * @param scopeProvider Provides scope information + * @returns A {@link Disposable} which will stop the callback from running + */ +export function revisualizeOnCustomRegexChange( + scopeVisualizer: ScopeVisualizer, + scopeProvider: ScopeProvider, +): Disposable { + let currentRegexScopeInfo: ScopeTypeInfo | undefined; + let currentVisualizationType: VisualizationType | undefined; + + return disposableFrom( + scopeVisualizer.onDidChangeScopeType((scopeType, visualizationType) => { + currentRegexScopeInfo = + scopeType?.type === "customRegex" + ? scopeProvider.getScopeInfo(scopeType) + : undefined; + currentVisualizationType = visualizationType; + }), + + scopeProvider.onDidChangeScopeInfo((scopeInfos) => { + if ( + currentRegexScopeInfo != null && + !scopeInfos.some((scopeInfo) => + isEqual(scopeInfo.scopeType, currentRegexScopeInfo!.scopeType), + ) + ) { + const replacement = scopeInfos.find( + (scopeInfo) => + scopeInfo.scopeType.type === "customRegex" && + isEqual(scopeInfo.spokenForm, currentRegexScopeInfo!.spokenForm), + ); + if (replacement != null) { + scopeVisualizer.start( + replacement.scopeType, + currentVisualizationType!, + ); + } + } + }), + ); +}