Skip to content

Commit eb5dee6

Browse files
authored
Changes to prepare for tutorial (#2525)
## Checklist - [-] 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
1 parent 895ce10 commit eb5dee6

File tree

17 files changed

+157
-76
lines changed

17 files changed

+157
-76
lines changed

packages/cursorless-engine/src/core/Debouncer.ts renamed to packages/common/src/Debouncer.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { ide } from "../singletons/ide.singleton";
2-
31
/**
42
* Debounces a callback. Uses the `decorationDebounceDelayMs` configuration
53
* value to determine the debounce delay.
@@ -10,7 +8,7 @@ export class Debouncer {
108
constructor(
119
/** The callback to debounce */
1210
private callback: () => void,
13-
private debounceDelayMs?: number,
11+
private debounceDelayMs: number,
1412
) {
1513
this.run = this.run.bind(this);
1614
}
@@ -20,14 +18,10 @@ export class Debouncer {
2018
clearTimeout(this.timeoutHandle);
2119
}
2220

23-
const decorationDebounceDelayMs =
24-
this.debounceDelayMs ??
25-
ide().configuration.getOwnConfiguration("decorationDebounceDelayMs");
26-
2721
this.timeoutHandle = setTimeout(() => {
2822
this.callback();
2923
this.timeoutHandle = null;
30-
}, decorationDebounceDelayMs);
24+
}, this.debounceDelayMs);
3125
}
3226

3327
dispose() {

packages/common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from "./cursorlessCommandIds";
22
export * from "./cursorlessSideBarIds";
3+
export * from "./Debouncer";
34
export * from "./errors";
45
export * from "./extensionDependencies";
56
export * from "./FakeCommandServerApi";

packages/cursorless-engine/src/KeyboardTargetUpdater.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1-
import { Disposable } from "@cursorless/common";
2-
import { Debouncer } from "./core/Debouncer";
1+
import { Disposable, IDE } from "@cursorless/common";
32
import { StoredTargetMap } from "./core/StoredTargets";
43
import { CursorStage } from "./processTargets/marks/CursorStage";
5-
import { ide } from "./singletons/ide.singleton";
4+
import { DecorationDebouncer } from "./util/DecorationDebouncer";
65

76
export class KeyboardTargetUpdater {
87
private disposables: Disposable[] = [];
98
private selectionWatcherDisposable: Disposable | undefined;
10-
private debouncer: Debouncer;
11-
12-
constructor(private storedTargets: StoredTargetMap) {
13-
this.debouncer = new Debouncer(() => this.updateKeyboardTarget());
9+
private debouncer: DecorationDebouncer;
10+
11+
constructor(
12+
private ide: IDE,
13+
private storedTargets: StoredTargetMap,
14+
) {
15+
this.debouncer = new DecorationDebouncer(ide.configuration, () =>
16+
this.updateKeyboardTarget(),
17+
);
1418

1519
this.disposables.push(
16-
ide().configuration.onDidChangeConfiguration(() => this.maybeActivate()),
20+
ide.configuration.onDidChangeConfiguration(() => this.maybeActivate()),
1721

1822
this.debouncer,
1923
);
@@ -22,15 +26,14 @@ export class KeyboardTargetUpdater {
2226
}
2327

2428
maybeActivate(): void {
25-
const isActive = ide().configuration.getOwnConfiguration(
29+
const isActive = this.ide.configuration.getOwnConfiguration(
2630
"experimental.keyboardTargetFollowsSelection",
2731
);
2832

2933
if (isActive) {
3034
if (this.selectionWatcherDisposable == null) {
31-
this.selectionWatcherDisposable = ide().onDidChangeTextEditorSelection(
32-
this.debouncer.run,
33-
);
35+
this.selectionWatcherDisposable =
36+
this.ide.onDidChangeTextEditorSelection(this.debouncer.run);
3437
}
3538

3639
return;
@@ -43,7 +46,7 @@ export class KeyboardTargetUpdater {
4346
}
4447

4548
private updateKeyboardTarget() {
46-
const activeEditor = ide().activeTextEditor;
49+
const activeEditor = this.ide.activeTextEditor;
4750

4851
if (activeEditor == null || this.storedTargets.get("keyboard") == null) {
4952
return;

packages/cursorless-engine/src/api/CursorlessEngineApi.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import type {
2+
ActionType,
23
Command,
4+
CommandComplete,
35
CommandResponse,
6+
Disposable,
47
HatTokenMap,
58
IDE,
69
ReadOnlyHatMap,
710
ScopeProvider,
11+
ScopeType,
12+
SpokenForm,
813
} from "@cursorless/common";
914
import type { CommandRunner } from "../CommandRunner";
1015
import type { StoredTargetMap } from "../core/StoredTargets";
@@ -29,7 +34,12 @@ export interface CustomSpokenFormGenerator {
2934
*/
3035
readonly needsInitialTalonUpdate: boolean | undefined;
3136

32-
onDidChangeCustomSpokenForms: (listener: () => void) => void;
37+
onDidChangeCustomSpokenForms(listener: () => void): Disposable;
38+
39+
commandToSpokenForm(command: CommandComplete): SpokenForm;
40+
scopeTypeToSpokenForm(scopeType: ScopeType): SpokenForm;
41+
actionIdToSpokenForm(actionId: ActionType): SpokenForm;
42+
graphemeToSpokenForm(grapheme: string): SpokenForm;
3343
}
3444

3545
export interface CommandApi {

packages/cursorless-engine/src/core/HatAllocator.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,49 @@ import type { Disposable, Hats, TokenHat } from "@cursorless/common";
22
import { ide } from "../singletons/ide.singleton";
33
import tokenGraphemeSplitter from "../singletons/tokenGraphemeSplitter.singleton";
44
import { allocateHats } from "../util/allocateHats";
5-
import { Debouncer } from "./Debouncer";
65
import { IndividualHatMap } from "./IndividualHatMap";
6+
import { DecorationDebouncer } from "../util/DecorationDebouncer";
77

88
interface Context {
99
getActiveMap(): Promise<IndividualHatMap>;
1010
}
1111

1212
export class HatAllocator {
1313
private disposables: Disposable[] = [];
14-
private debouncer = new Debouncer(() => this.allocateHats());
1514

1615
constructor(
1716
private hats: Hats,
1817
private context: Context,
1918
) {
2019
ide().disposeOnExit(this);
2120

21+
const debouncer = new DecorationDebouncer(ide().configuration, () =>
22+
this.allocateHats(),
23+
);
24+
2225
this.disposables.push(
23-
this.hats.onDidChangeEnabledHatStyles(this.debouncer.run),
24-
this.hats.onDidChangeIsEnabled(this.debouncer.run),
26+
this.hats.onDidChangeEnabledHatStyles(debouncer.run),
27+
this.hats.onDidChangeIsEnabled(debouncer.run),
2528

2629
// An event that fires when a text document opens
27-
ide().onDidOpenTextDocument(this.debouncer.run),
30+
ide().onDidOpenTextDocument(debouncer.run),
2831
// An event that fires when a text document closes
29-
ide().onDidCloseTextDocument(this.debouncer.run),
32+
ide().onDidCloseTextDocument(debouncer.run),
3033
// An Event which fires when the active editor has changed. Note that the event also fires when the active editor changes to undefined.
31-
ide().onDidChangeActiveTextEditor(this.debouncer.run),
34+
ide().onDidChangeActiveTextEditor(debouncer.run),
3235
// An Event which fires when the array of visible editors has changed.
33-
ide().onDidChangeVisibleTextEditors(this.debouncer.run),
36+
ide().onDidChangeVisibleTextEditors(debouncer.run),
3437
// 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.
35-
ide().onDidChangeTextDocument(this.debouncer.run),
38+
ide().onDidChangeTextDocument(debouncer.run),
3639
// An Event which fires when the selection in an editor has changed.
37-
ide().onDidChangeTextEditorSelection(this.debouncer.run),
40+
ide().onDidChangeTextEditorSelection(debouncer.run),
3841
// An Event which fires when the visible ranges of an editor has changed.
39-
ide().onDidChangeTextEditorVisibleRanges(this.debouncer.run),
42+
ide().onDidChangeTextEditorVisibleRanges(debouncer.run),
4043
// Re-draw hats on grapheme splitting algorithm change in case they
4144
// changed their token hat splitting setting.
42-
tokenGraphemeSplitter().registerAlgorithmChangeListener(
43-
this.debouncer.run,
44-
),
45+
tokenGraphemeSplitter().registerAlgorithmChangeListener(debouncer.run),
4546

46-
this.debouncer,
47+
debouncer,
4748
);
4849
}
4950

packages/cursorless-engine/src/cursorlessEngine.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { ScopeSupportChecker } from "./scopeProviders/ScopeSupportChecker";
4141
import { ScopeSupportWatcher } from "./scopeProviders/ScopeSupportWatcher";
4242
import { injectIde } from "./singletons/ide.singleton";
4343

44-
interface Props {
44+
export interface EngineProps {
4545
ide: IDE;
4646
hats?: Hats;
4747
treeSitterQueryProvider?: RawTreeSitterQueryProvider;
@@ -59,13 +59,13 @@ export async function createCursorlessEngine({
5959
commandServerApi = new DisabledCommandServerApi(),
6060
talonSpokenForms = new DisabledTalonSpokenForms(),
6161
snippets = new DisabledSnippets(),
62-
}: Props): Promise<CursorlessEngine> {
62+
}: EngineProps): Promise<CursorlessEngine> {
6363
injectIde(ide);
6464

6565
const debug = new Debug(ide);
6666
const rangeUpdater = new RangeUpdater();
6767
const storedTargets = new StoredTargetMap();
68-
const keyboardTargetUpdater = new KeyboardTargetUpdater(storedTargets);
68+
const keyboardTargetUpdater = new KeyboardTargetUpdater(ide, storedTargets);
6969
const customSpokenFormGenerator = new CustomSpokenFormGeneratorImpl(
7070
talonSpokenForms,
7171
);

packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,17 @@ export class CustomSpokenFormGeneratorImpl
5656
}
5757

5858
actionIdToSpokenForm(actionId: ActionType) {
59-
return this.customSpokenForms.spokenFormMap.action[actionId];
59+
return this.spokenFormGenerator.getSpokenFormForSingleTerm(
60+
"action",
61+
actionId,
62+
);
63+
}
64+
65+
graphemeToSpokenForm(grapheme: string) {
66+
return this.spokenFormGenerator.getSpokenFormForSingleTerm(
67+
"grapheme",
68+
grapheme,
69+
);
6070
}
6171

6272
getCustomRegexScopeTypes() {

packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,26 @@ import {
55
InsertionMode,
66
PartialTargetDescriptor,
77
ScopeType,
8+
SpokenForm,
9+
SpokenFormMapKeyTypes,
10+
SpokenFormType,
811
camelCaseToAllDown,
912
} from "@cursorless/common";
13+
import { SpokenFormMap } from "../spokenForms/SpokenFormMap";
1014
import { NoSpokenFormError } from "./NoSpokenFormError";
15+
import { SpokenFormComponent } from "./SpokenFormComponent";
1116
import { connectives } from "./defaultSpokenForms/connectives";
1217
import { surroundingPairDelimitersToSpokenForm } from "./defaultSpokenForms/modifiers";
1318
import {
1419
insertionSnippetToSpokenForm,
1520
wrapperSnippetToSpokenForm,
1621
} from "./defaultSpokenForms/snippets";
1722
import { getRangeConnective } from "./getRangeConnective";
18-
import { SpokenFormMap } from "../spokenForms/SpokenFormMap";
19-
import { PrimitiveTargetSpokenFormGenerator } from "./primitiveTargetToSpokenForm";
2023
import {
2124
SpokenFormComponentMap,
2225
getSpokenFormComponentMap,
2326
} from "./getSpokenFormComponentMap";
24-
import { SpokenFormComponent } from "./SpokenFormComponent";
25-
import { SpokenForm } from "@cursorless/common";
27+
import { PrimitiveTargetSpokenFormGenerator } from "./primitiveTargetToSpokenForm";
2628

2729
export class SpokenFormGenerator {
2830
private primitiveGenerator: PrimitiveTargetSpokenFormGenerator;
@@ -36,6 +38,23 @@ export class SpokenFormGenerator {
3638
);
3739
}
3840

41+
getSpokenFormForSingleTerm<T extends SpokenFormType>(
42+
type: T,
43+
id: SpokenFormMapKeyTypes[T],
44+
): SpokenForm {
45+
return this.componentsToSpokenForm(() => {
46+
const value = this.spokenFormMap[type][
47+
id as unknown as keyof SpokenFormComponentMap[T]
48+
] as SpokenFormComponent;
49+
50+
if (value == null) {
51+
throw new NoSpokenFormError(`${type} with id ${id}`);
52+
}
53+
54+
return value;
55+
});
56+
}
57+
3958
/**
4059
* Given a command, generates its spoken form.
4160
* @param command The command to generate a spoken form for

packages/cursorless-engine/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ export * from "./api/CursorlessEngineApi";
22
export * from "./CommandHistory";
33
export * from "./CommandHistoryAnalyzer";
44
export * from "./CommandRunner";
5+
export * from "./core/commandVersionUpgrades/canonicalizeAndValidateCommand";
56
export * from "./core/mergeSnippets";
67
export * from "./core/Snippets";
78
export * from "./core/StoredTargets";
8-
export * from "./core/StoredTargets";
99
export * from "./cursorlessEngine";
10+
export * from "./customCommandGrammar/parseCommand";
1011
export * from "./generateSpokenForm/defaultSpokenForms/surroundingPairsDelimiters";
1112
export * from "./generateSpokenForm/generateSpokenForm";
1213
export * from "./singletons/ide.singleton";

packages/cursorless-engine/src/scopeProviders/ScopeRangeWatcher.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,17 @@ import {
99
} from "@cursorless/common";
1010
import { pull } from "lodash-es";
1111

12-
import { Debouncer } from "../core/Debouncer";
1312
import { LanguageDefinitions } from "../languages/LanguageDefinitions";
1413
import { ide } from "../singletons/ide.singleton";
1514
import { ScopeRangeProvider } from "./ScopeRangeProvider";
15+
import { DecorationDebouncer } from "../util/DecorationDebouncer";
1616

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

2625
constructor(
@@ -32,20 +31,24 @@ export class ScopeRangeWatcher {
3231
this.onDidChangeIterationScopeRanges =
3332
this.onDidChangeIterationScopeRanges.bind(this);
3433

34+
const debouncer = new DecorationDebouncer(ide().configuration, () =>
35+
this.onChange(),
36+
);
37+
3538
this.disposables.push(
3639
// An Event which fires when the array of visible editors has changed.
37-
ide().onDidChangeVisibleTextEditors(this.debouncer.run),
40+
ide().onDidChangeVisibleTextEditors(debouncer.run),
3841
// An event that fires when a text document opens
39-
ide().onDidOpenTextDocument(this.debouncer.run),
42+
ide().onDidOpenTextDocument(debouncer.run),
4043
// An Event that fires when a text document closes
41-
ide().onDidCloseTextDocument(this.debouncer.run),
44+
ide().onDidCloseTextDocument(debouncer.run),
4245
// An event that is emitted when a text document is changed. This usually
4346
// happens when the contents changes but also when other things like the
4447
// dirty-state changes.
45-
ide().onDidChangeTextDocument(this.debouncer.run),
46-
ide().onDidChangeTextEditorVisibleRanges(this.debouncer.run),
48+
ide().onDidChangeTextDocument(debouncer.run),
49+
ide().onDidChangeTextEditorVisibleRanges(debouncer.run),
4750
languageDefinitions.onDidChangeDefinition(this.onChange),
48-
this.debouncer,
51+
debouncer,
4952
);
5053
}
5154

0 commit comments

Comments
 (0)