Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ScopeRangeWatcher
Browse files Browse the repository at this point in the history
pokey committed Jul 17, 2023
1 parent 8e1c431 commit 0c2f322
Showing 3 changed files with 164 additions and 0 deletions.
121 changes: 121 additions & 0 deletions packages/cursorless-engine/src/ScopeVisualizer/ScopeRangeWatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Disposable } from "@cursorless/common";
import { pull } from "lodash";
import {
IterationScopeChangeEventCallback,
IterationScopeRangeConfig,
ScopeChangeEventCallback,
ScopeRangeConfig,
} from "..";
import { Debouncer } from "../core/Debouncer";
import { ide } from "../singletons/ide.singleton";
import { ScopeRangeProvider } from "./ScopeRangeProvider";

/**
* Watches for changes to the scope ranges of visible editors and notifies
* listeners when they change.
*/
export class ScopeRangeWatcher {
private disposables: Disposable[] = [];
private debouncer = new Debouncer(() => this.onChange());
private listeners: (() => void)[] = [];

constructor(private scopeRangeProvider: ScopeRangeProvider) {
this.disposables.push(
// An Event which fires when the array of visible editors has changed.
ide().onDidChangeVisibleTextEditors(this.debouncer.run),
// An event that fires when a text document opens
ide().onDidOpenTextDocument(this.debouncer.run),
// An Event that fires when a text document closes
ide().onDidCloseTextDocument(this.debouncer.run),
// An event that is emitted when a text document is changed. This usually
// happens when the contents changes but also when other things like the
// dirty-state changes.
ide().onDidChangeTextDocument(this.debouncer.run),
ide().onDidChangeTextEditorVisibleRanges(this.debouncer.run),
this.debouncer,
);

this.onDidChangeScopeRanges = this.onDidChangeScopeRanges.bind(this);
this.onDidChangeIterationScopeRanges =
this.onDidChangeIterationScopeRanges.bind(this);
}

/**
* Registers a callback to be run when the scope ranges change for any visible
* editor. The callback will be run immediately once for each visible editor
* with the current scope ranges.
* @param callback The callback to run when the scope ranges change
* @param config The configuration for the scope ranges
* @returns A {@link Disposable} which will stop the callback from running
*/
onDidChangeScopeRanges(
callback: ScopeChangeEventCallback,
config: ScopeRangeConfig,
): Disposable {
const fn = () => {
ide().visibleTextEditors.forEach((editor) => {
callback(
editor,
this.scopeRangeProvider.provideScopeRanges(editor, config),
);
});
};

this.listeners.push(fn);

fn();

return {
dispose: () => {
pull(this.listeners, fn);
},
};
}

/**
* Registers a callback to be run when the iteration scope ranges change for
* any visible editor. The callback will be run immediately once for each
* visible editor with the current iteration scope ranges.
* @param callback The callback to run when the scope ranges change
* @param config The configuration for the scope ranges
* @returns A {@link Disposable} which will stop the callback from running
*/
onDidChangeIterationScopeRanges(
callback: IterationScopeChangeEventCallback,
config: IterationScopeRangeConfig,
): Disposable {
const fn = () => {
ide().visibleTextEditors.forEach((editor) => {
callback(
editor,
this.scopeRangeProvider.provideIterationScopeRanges(editor, config),
);
});
};

this.listeners.push(fn);

fn();

return {
dispose: () => {
pull(this.listeners, fn);
},
};
}

private onChange() {
this.listeners.forEach((listener) => listener());
}

dispose(): void {
this.disposables.forEach(({ dispose }) => {
try {
dispose();
} catch (e) {
// do nothing; some of the VSCode disposables misbehave, and we don't
// want that to prevent us from disposing the rest of the disposables
}
});
}
}
37 changes: 37 additions & 0 deletions packages/cursorless-engine/src/api/ScopeProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Disposable,
GeneralizedRange,
Range,
ScopeType,
@@ -26,6 +27,32 @@ export interface ScopeProvider {
editor: TextEditor,
config: IterationScopeRangeConfig,
) => IterationScopeRanges[];

/**
* Registers a callback to be run when the scope ranges change for any visible
* editor. The callback will be run immediately once for each visible editor
* with the current scope ranges.
* @param callback The callback to run when the scope ranges change
* @param config The configuration for the scope ranges
* @returns A {@link Disposable} which will stop the callback from running
*/
onDidChangeScopeRanges: (
callback: ScopeChangeEventCallback,
config: ScopeRangeConfig,
) => Disposable;

/**
* Registers a callback to be run when the iteration scope ranges change for
* any visible editor. The callback will be run immediately once for each
* visible editor with the current iteration scope ranges.
* @param callback The callback to run when the scope ranges change
* @param config The configuration for the scope ranges
* @returns A {@link Disposable} which will stop the callback from running
*/
onDidChangeIterationScopeRanges: (
callback: IterationScopeChangeEventCallback,
config: IterationScopeRangeConfig,
) => Disposable;
}

interface ScopeRangeConfigBase {
@@ -49,6 +76,16 @@ export interface IterationScopeRangeConfig extends ScopeRangeConfigBase {
includeNestedTargets: boolean;
}

export type ScopeChangeEventCallback = (
editor: TextEditor,
scopeRanges: ScopeRanges[],
) => void;

export type IterationScopeChangeEventCallback = (
editor: TextEditor,
scopeRanges: IterationScopeRanges[],
) => void;

/**
* Contains the ranges that define a given scope, eg its {@link domain} and the
* ranges for its {@link targets}.
6 changes: 6 additions & 0 deletions packages/cursorless-engine/src/cursorlessEngine.ts
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandler
import { runCommand } from "./runCommand";
import { runIntegrationTests } from "./runIntegrationTests";
import { injectIde } from "./singletons/ide.singleton";
import { ScopeRangeWatcher } from "./ScopeVisualizer/ScopeRangeWatcher";

export function createCursorlessEngine(
treeSitter: TreeSitter,
@@ -99,8 +100,13 @@ function createScopeProvider(
),
);

const rangeWatcher = new ScopeRangeWatcher(rangeProvider);

return {
provideScopeRanges: rangeProvider.provideScopeRanges,
provideIterationScopeRanges: rangeProvider.provideIterationScopeRanges,
onDidChangeScopeRanges: rangeWatcher.onDidChangeScopeRanges,
onDidChangeIterationScopeRanges:
rangeWatcher.onDidChangeIterationScopeRanges,
};
}

0 comments on commit 0c2f322

Please sign in to comment.