Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scope info provider #1941

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
});
},
};
}
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