Skip to content

Commit

Permalink
Scope info provider (#1941)
Browse files Browse the repository at this point in the history
- Depends on #1940

## 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 Oct 30, 2023
1 parent cc155ba commit 64d01e4
Show file tree
Hide file tree
Showing 32 changed files with 1,113 additions and 112 deletions.
2 changes: 2 additions & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { getKey, splitKey } from "./util/splitKey";
export { hrtimeBigintToSeconds } from "./util/timeUtils";
export * from "./util/walkSync";
export * from "./util/walkAsync";
export * from "./util/disposableFrom";
export * from "./util/camelCaseToAllDown";
export { Notifier } from "./util/Notifier";
export type { Listener } from "./util/Notifier";
Expand Down Expand Up @@ -43,6 +44,7 @@ export * from "./types/TextEditorOptions";
export * from "./types/TextLine";
export * from "./types/Token";
export * from "./types/HatTokenMap";
export * from "./types/ScopeProvider";
export * from "./types/SpokenForm";
export * from "./util/textFormatters";
export * from "./types/snippet.types";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import {
GeneralizedRange,
Range,
ScopeType,
SpokenForm,
TextEditor,
} from "@cursorless/common";
} from "..";

export interface ScopeProvider {
/**
Expand All @@ -17,6 +18,7 @@ export interface ScopeProvider {
editor: TextEditor,
config: ScopeRangeConfig,
) => ScopeRanges[];

/**
* Get the iteration scope ranges for the given editor.
* @param editor The editor
Expand Down Expand Up @@ -75,6 +77,41 @@ export interface ScopeProvider {
editor: TextEditor,
scopeType: ScopeType,
) => ScopeSupport;

/**
* Registers a callback to be run when the scope support changes for the active
* editor. The callback will be run immediately once with the current support
* levels for the active editor.
*
* Note that this watcher could be expensive, because it runs all the scope
* handlers for the active editor every time the content of the active editor
* changes. If you only need info about the available scopes, including their
* spoken forms, you should use {@link onDidChangeScopeInfo} instead.
* @param callback The callback to run when the scope support changes
* @returns A {@link Disposable} which will stop the callback from running
*/
onDidChangeScopeSupport: (callback: ScopeSupportEventCallback) => Disposable;

/**
* Registers a callback to be run when the scope info changes. The callback
* will be run immediately once with the current scope info.
*
* Includes information about the available scopes, including their custom
* spoken forms, if available. Note that even custom regex scopes will be
* available, as reported to the engine by Talon.
* @param callback The callback to run when the scope support changes
* @returns A {@link Disposable} which will stop the callback from running
*/
onDidChangeScopeInfo(callback: ScopeTypeInfoEventCallback): Disposable;

/**
* Get info about {@link scopeType}, including its custom spoken form, if
* available.
* @param editor The editor to check
* @param scopeType The scope type to check
* @returns Info about {@link scopeType}
*/
getScopeInfo: (scopeType: ScopeType) => ScopeTypeInfo;
}

interface ScopeRangeConfigBase {
Expand Down Expand Up @@ -108,6 +145,24 @@ export type IterationScopeChangeEventCallback = (
scopeRanges: IterationScopeRanges[],
) => void;

export interface ScopeSupportInfo extends ScopeTypeInfo {
support: ScopeSupport;
iterationScopeSupport: ScopeSupport;
}

export type ScopeSupportEventCallback = (
scopeSupportInfos: ScopeSupportInfo[],
) => void;

export interface ScopeTypeInfo {
scopeType: ScopeType;
spokenForm: SpokenForm;
humanReadableName: string;
isLanguageSpecific: boolean;
}

export type ScopeTypeInfoEventCallback = (scopeInfos: ScopeTypeInfo[]) => void;

/**
* Contains the ranges that define a given scope, eg its {@link domain} and the
* ranges for its {@link targets}.
Expand Down
167 changes: 93 additions & 74 deletions packages/common/src/types/command/PartialTargetDescriptor.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,89 +75,108 @@ export type PartialMark =
| RangeMark
| ExplicitMark;

export const simpleSurroundingPairNames = [
"angleBrackets",
"backtickQuotes",
"curlyBrackets",
"doubleQuotes",
"escapedDoubleQuotes",
"escapedParentheses",
"escapedSquareBrackets",
"escapedSingleQuotes",
"parentheses",
"singleQuotes",
"squareBrackets",
] as const;
export const complexSurroundingPairNames = [
"string",
"any",
"collectionBoundary",
] as const;
export const surroundingPairNames = [
...simpleSurroundingPairNames,
...complexSurroundingPairNames,
];
export type SimpleSurroundingPairName =
| "angleBrackets"
| "backtickQuotes"
| "curlyBrackets"
| "doubleQuotes"
| "escapedDoubleQuotes"
| "escapedParentheses"
| "escapedSquareBrackets"
| "escapedSingleQuotes"
| "parentheses"
| "singleQuotes"
| "squareBrackets";
(typeof simpleSurroundingPairNames)[number];
export type ComplexSurroundingPairName =
| "string"
| "any"
| "collectionBoundary";
(typeof complexSurroundingPairNames)[number];
export type SurroundingPairName =
| SimpleSurroundingPairName
| ComplexSurroundingPairName;

export type SimpleScopeTypeType =
| "argumentOrParameter"
| "anonymousFunction"
| "attribute"
| "branch"
| "class"
| "className"
| "collectionItem"
| "collectionKey"
| "comment"
| "private.fieldAccess"
| "functionCall"
| "functionCallee"
| "functionName"
| "ifStatement"
| "instance"
| "list"
| "map"
| "name"
| "namedFunction"
| "regularExpression"
| "statement"
| "string"
| "type"
| "value"
| "condition"
| "section"
| "sectionLevelOne"
| "sectionLevelTwo"
| "sectionLevelThree"
| "sectionLevelFour"
| "sectionLevelFive"
| "sectionLevelSix"
| "selector"
| "switchStatementSubject"
| "unit"
| "xmlBothTags"
| "xmlElement"
| "xmlEndTag"
| "xmlStartTag"
| "notebookCell"
export const simpleScopeTypeTypes = [
"argumentOrParameter",
"anonymousFunction",
"attribute",
"branch",
"class",
"className",
"collectionItem",
"collectionKey",
"comment",
"private.fieldAccess",
"functionCall",
"functionCallee",
"functionName",
"ifStatement",
"instance",
"list",
"map",
"name",
"namedFunction",
"regularExpression",
"statement",
"string",
"type",
"value",
"condition",
"section",
"sectionLevelOne",
"sectionLevelTwo",
"sectionLevelThree",
"sectionLevelFour",
"sectionLevelFive",
"sectionLevelSix",
"selector",
"switchStatementSubject",
"unit",
"xmlBothTags",
"xmlElement",
"xmlEndTag",
"xmlStartTag",
// Latex scope types
| "part"
| "chapter"
| "subSection"
| "subSubSection"
| "namedParagraph"
| "subParagraph"
| "environment"
"part",
"chapter",
"subSection",
"subSubSection",
"namedParagraph",
"subParagraph",
"environment",
// Text based scopes
| "character"
| "word"
| "token"
| "identifier"
| "line"
| "sentence"
| "paragraph"
| "document"
| "nonWhitespaceSequence"
| "boundedNonWhitespaceSequence"
| "url"
"character",
"word",
"token",
"identifier",
"line",
"sentence",
"paragraph",
"document",
"nonWhitespaceSequence",
"boundedNonWhitespaceSequence",
"url",
"notebookCell",
// Talon
| "command";
"command",
] as const;

export function isSimpleScopeType(
scopeType: ScopeType,
): scopeType is SimpleScopeType {
return (simpleScopeTypeTypes as readonly string[]).includes(scopeType.type);
}

export type SimpleScopeTypeType = (typeof simpleScopeTypeTypes)[number];

export interface SimpleScopeType {
type: SimpleScopeTypeType;
Expand Down
24 changes: 24 additions & 0 deletions packages/common/src/util/disposableFrom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Disposable } from "../ide/types/ide.types";

/**
* Construct a disposable that disposes multiple disposables at once. This is
* useful for managing the lifetime of multiple disposables that are created
* together. It ensures that if one of the disposables throws an error during
* disposal, the rest of the disposables will still be disposed.
*/
export function disposableFrom(...disposables: Disposable[]): Disposable {
return {
dispose(): void {
disposables.forEach(({ dispose }) => {
try {
dispose();
} catch (e) {
// just log, but don't throw; some of the VSCode disposables misbehave,
// and we don't want that to prevent us from disposing the rest of the
// disposables
console.error(e);
}
});
},
};
}
2 changes: 1 addition & 1 deletion packages/cursorless-engine/src/api/CursorlessEngineApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command, HatTokenMap, IDE } from "@cursorless/common";
import { Snippets } from "../core/Snippets";
import { StoredTargetMap } from "../core/StoredTargets";
import { TestCaseRecorder } from "../testCaseRecorder/TestCaseRecorder";
import { ScopeProvider } from "./ScopeProvider";
import { ScopeProvider } from "@cursorless/common";

export interface CursorlessEngine {
commandApi: CommandApi;
Expand Down
7 changes: 4 additions & 3 deletions packages/cursorless-engine/src/core/Debouncer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class Debouncer {
constructor(
/** The callback to debounce */
private callback: () => void,
private debounceDelayMs?: number,
) {
this.run = this.run.bind(this);
}
Expand All @@ -19,9 +20,9 @@ export class Debouncer {
clearTimeout(this.timeoutHandle);
}

const decorationDebounceDelayMs = ide().configuration.getOwnConfiguration(
"decorationDebounceDelayMs",
);
const decorationDebounceDelayMs =
this.debounceDelayMs ??
ide().configuration.getOwnConfiguration("decorationDebounceDelayMs");

this.timeoutHandle = setTimeout(() => {
this.callback();
Expand Down
Loading

0 comments on commit 64d01e4

Please sign in to comment.