Skip to content

Commit 99d58b5

Browse files
committed
Almost working
1 parent c983aab commit 99d58b5

File tree

6 files changed

+74
-146
lines changed

6 files changed

+74
-146
lines changed

packages/cursorless-engine/src/CustomSpokenForms.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,16 @@ export class CustomSpokenForms implements SpokenFormMap {
4141

4242
private isInitialized_ = false;
4343

44+
/**
45+
* Whether the custom spoken forms have been initialized. If `false`, the
46+
* default spoken forms are currently being used while the custom spoken forms
47+
* are being loaded.
48+
*/
4449
get isInitialized() {
4550
return this.isInitialized_;
4651
}
4752

48-
private constructor(fileSystem: FileSystem) {
53+
constructor(fileSystem: FileSystem) {
4954
this.disposer.push(
5055
fileSystem.watch(spokenFormsPath, () => this.updateSpokenFormMaps()),
5156
);
@@ -61,7 +66,9 @@ export class CustomSpokenForms implements SpokenFormMap {
6166
onDidChangeCustomSpokenForms = this.notifier.registerListener;
6267

6368
private async updateSpokenFormMaps(): Promise<void> {
69+
console.log("updateSpokenFormMaps before getSpokenFormEntries");
6470
const entries = await getSpokenFormEntries();
71+
console.log("updateSpokenFormMaps after getSpokenFormEntries");
6572

6673
this.simpleScopeTypeType = Object.fromEntries(
6774
entries
@@ -88,6 +95,8 @@ export class CustomSpokenForms implements SpokenFormMap {
8895
.map(({ id, spokenForms }) => [id, spokenForms] as const),
8996
);
9097

98+
console.log("updateSpokenFormMaps at end");
99+
this.isInitialized_ = true;
91100
this.notifier.notifyListeners();
92101
}
93102

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ScopeType,
66
TextEditor,
77
} from "@cursorless/common";
8+
import { SpokenForm } from "../generateSpokenForm";
89

910
export interface ScopeProvider {
1011
/**
@@ -139,7 +140,7 @@ export type ScopeSupportEventCallback = (
139140

140141
export interface ScopeTypeInfo {
141142
scopeType: ScopeType;
142-
spokenForms: string[] | undefined;
143+
spokenForm: SpokenForm;
143144
humanReadableName: string;
144145
isLanguageSpecific: boolean;
145146
}

packages/cursorless-engine/src/cursorlessEngine.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ import {
66
IDE,
77
} from "@cursorless/common";
88
import { StoredTargetMap, TestCaseRecorder, TreeSitter } from ".";
9+
import { CustomSpokenForms } from "./CustomSpokenForms";
910
import { CursorlessEngine } from "./api/CursorlessEngineApi";
1011
import { ScopeProvider } from "./api/ScopeProvider";
11-
import { ScopeRangeProvider } from "./scopeProviders/ScopeRangeProvider";
12-
import { ScopeSupportChecker } from "./scopeProviders/ScopeSupportChecker";
1312
import { Debug } from "./core/Debug";
1413
import { HatTokenMapImpl } from "./core/HatTokenMapImpl";
1514
import { Snippets } from "./core/Snippets";
@@ -20,10 +19,13 @@ import { ModifierStageFactoryImpl } from "./processTargets/ModifierStageFactoryI
2019
import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandlers";
2120
import { runCommand } from "./runCommand";
2221
import { runIntegrationTests } from "./runIntegrationTests";
23-
import { injectIde } from "./singletons/ide.singleton";
22+
import { ScopeInfoProvider } from "./scopeProviders/ScopeInfoProvider";
23+
import { ScopeRangeProvider } from "./scopeProviders/ScopeRangeProvider";
2424
import { ScopeRangeWatcher } from "./scopeProviders/ScopeRangeWatcher";
25+
import { ScopeSupportChecker } from "./scopeProviders/ScopeSupportChecker";
2526
import { ScopeSupportWatcher } from "./scopeProviders/ScopeSupportWatcher";
26-
import { ScopeInfoProvider } from "./scopeProviders/ScopeInfoProvider";
27+
import { injectIde } from "./singletons/ide.singleton";
28+
import { SpokenFormGenerator } from "./generateSpokenForm";
2729

2830
export function createCursorlessEngine(
2931
treeSitter: TreeSitter,
@@ -55,8 +57,12 @@ export function createCursorlessEngine(
5557

5658
const languageDefinitions = new LanguageDefinitions(fileSystem, treeSitter);
5759

60+
const customSpokenForms = new CustomSpokenForms(fileSystem);
61+
5862
ide.disposeOnExit(rangeUpdater, languageDefinitions, hatTokenMap, debug);
5963

64+
console.log("createCursorlessEngine");
65+
6066
return {
6167
commandApi: {
6268
runCommand(command: Command) {
@@ -88,7 +94,7 @@ export function createCursorlessEngine(
8894
scopeProvider: createScopeProvider(
8995
languageDefinitions,
9096
storedTargets,
91-
fileSystem,
97+
customSpokenForms,
9298
),
9399
testCaseRecorder,
94100
storedTargets,
@@ -103,7 +109,7 @@ export function createCursorlessEngine(
103109
function createScopeProvider(
104110
languageDefinitions: LanguageDefinitions,
105111
storedTargets: StoredTargetMap,
106-
fileSystem: FileSystem,
112+
customSpokenForms: CustomSpokenForms,
107113
): ScopeProvider {
108114
const scopeHandlerFactory = new ScopeHandlerFactoryImpl(languageDefinitions);
109115

@@ -121,7 +127,10 @@ function createScopeProvider(
121127
rangeProvider,
122128
);
123129
const supportChecker = new ScopeSupportChecker(scopeHandlerFactory);
124-
const infoProvider = ScopeInfoProvider.create(fileSystem);
130+
const infoProvider = new ScopeInfoProvider(
131+
customSpokenForms,
132+
new SpokenFormGenerator(customSpokenForms),
133+
);
125134
const supportWatcher = new ScopeSupportWatcher(
126135
languageDefinitions,
127136
supportChecker,

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

Lines changed: 38 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,19 @@
11
import {
2-
CustomRegexScopeType,
32
Disposable,
4-
FileSystem,
3+
Disposer,
54
ScopeType,
6-
SimpleScopeTypeType,
7-
SurroundingPairName,
85
SurroundingPairScopeType,
9-
isSimpleScopeType,
106
simpleScopeTypeTypes,
117
surroundingPairNames,
128
} from "@cursorless/common";
139
import { pull } from "lodash";
14-
import { ScopeTypeInfo, ScopeTypeInfoEventCallback } from "..";
15-
import { Debouncer } from "../core/Debouncer";
1610
import { homedir } from "os";
1711
import * as path from "path";
12+
import { ScopeTypeInfo, ScopeTypeInfoEventCallback } from "..";
13+
import { CustomSpokenForms } from "../CustomSpokenForms";
14+
15+
import { SpokenFormGenerator } from "../generateSpokenForm";
1816
import { scopeTypeToString } from "./scopeTypeToString";
19-
import {
20-
CustomRegexSpokenFormEntry,
21-
PairedDelimiterSpokenFormEntry,
22-
SimpleScopeTypeTypeSpokenFormEntry,
23-
getSpokenFormEntries,
24-
} from "./getSpokenFormEntries";
2517

2618
export const spokenFormsPath = path.join(
2719
homedir(),
@@ -33,31 +25,20 @@ export const spokenFormsPath = path.join(
3325
* Maintains a list of all scope types and notifies listeners when it changes.
3426
*/
3527
export class ScopeInfoProvider {
36-
private disposables: Disposable[] = [];
37-
private debouncer = new Debouncer(() => this.onChange(), 250);
28+
private disposer = new Disposer();
3829
private listeners: ScopeTypeInfoEventCallback[] = [];
39-
private simpleScopeTypeSpokenFormMap?: Record<SimpleScopeTypeType, string[]>;
40-
private pairedDelimiterSpokenFormMap?: Record<SurroundingPairName, string[]>;
41-
private customRegexSpokenFormMap?: Record<string, string[]>;
4230
private scopeInfos!: ScopeTypeInfo[];
4331

44-
private constructor(fileSystem: FileSystem) {
45-
this.disposables.push(
46-
fileSystem.watch(spokenFormsPath, this.debouncer.run),
47-
this.debouncer,
32+
constructor(
33+
private customSpokenForms: CustomSpokenForms,
34+
private spokenFormGenerator: SpokenFormGenerator,
35+
) {
36+
this.disposer.push(
37+
customSpokenForms.onDidChangeCustomSpokenForms(() => this.onChange()),
4838
);
4939

5040
this.onDidChangeScopeInfo = this.onDidChangeScopeInfo.bind(this);
51-
}
52-
53-
static create(fileSystem: FileSystem) {
54-
const obj = new ScopeInfoProvider(fileSystem);
55-
obj.init();
56-
return obj;
57-
}
58-
59-
private async init() {
60-
await this.updateScopeTypeInfos();
41+
this.updateScopeTypeInfos();
6142
}
6243

6344
/**
@@ -69,7 +50,6 @@ export class ScopeInfoProvider {
6950
* @returns A {@link Disposable} which will stop the callback from running
7051
*/
7152
onDidChangeScopeInfo(callback: ScopeTypeInfoEventCallback): Disposable {
72-
this.updateScopeTypeInfos().then(() => callback(this.getScopeTypeInfos()));
7353
callback(this.getScopeTypeInfos());
7454

7555
this.listeners.push(callback);
@@ -82,76 +62,36 @@ export class ScopeInfoProvider {
8262
}
8363

8464
private async onChange() {
85-
await this.updateScopeTypeInfos();
65+
this.updateScopeTypeInfos();
8666

8767
this.listeners.forEach((listener) => listener(this.scopeInfos));
8868
}
8969

90-
private async updateScopeTypeInfos(): Promise<void> {
91-
const update = () => {
92-
const scopeTypes: ScopeType[] = [
93-
...simpleScopeTypeTypes
94-
// Ignore instance pseudo-scope for now
95-
// Skip "string" because we use surrounding pair for that
96-
.filter(
97-
(scopeTypeType) =>
98-
scopeTypeType !== "instance" && scopeTypeType !== "string",
99-
)
100-
.map((scopeTypeType) => ({
101-
type: scopeTypeType,
102-
})),
103-
104-
...surroundingPairNames.map(
105-
(surroundingPairName): SurroundingPairScopeType => ({
106-
type: "surroundingPair",
107-
delimiter: surroundingPairName,
108-
}),
109-
),
110-
111-
...(this.customRegexSpokenFormMap == null
112-
? []
113-
: Object.keys(this.customRegexSpokenFormMap)
114-
).map(
115-
(regex): CustomRegexScopeType => ({ type: "customRegex", regex }),
116-
),
117-
];
118-
119-
this.scopeInfos = scopeTypes.map((scopeType) =>
120-
this.getScopeTypeInfo(scopeType),
121-
);
122-
};
123-
124-
update();
125-
126-
return this.updateSpokenFormMaps().then(update);
127-
}
128-
129-
private async updateSpokenFormMaps() {
130-
const entries = await getSpokenFormEntries();
131-
132-
this.simpleScopeTypeSpokenFormMap = Object.fromEntries(
133-
entries
134-
.filter(
135-
(entry): entry is SimpleScopeTypeTypeSpokenFormEntry =>
136-
entry.type === "simpleScopeTypeType",
137-
)
138-
.map(({ id, spokenForms }) => [id, spokenForms] as const),
139-
);
140-
this.customRegexSpokenFormMap = Object.fromEntries(
141-
entries
142-
.filter(
143-
(entry): entry is CustomRegexSpokenFormEntry =>
144-
entry.type === "customRegex",
145-
)
146-
.map(({ id, spokenForms }) => [id, spokenForms] as const),
147-
);
148-
this.pairedDelimiterSpokenFormMap = Object.fromEntries(
149-
entries
70+
private updateScopeTypeInfos(): void {
71+
const scopeTypes: ScopeType[] = [
72+
...simpleScopeTypeTypes
73+
// Ignore instance pseudo-scope for now
74+
// Skip "string" because we use surrounding pair for that
15075
.filter(
151-
(entry): entry is PairedDelimiterSpokenFormEntry =>
152-
entry.type === "pairedDelimiter",
76+
(scopeTypeType) =>
77+
scopeTypeType !== "instance" && scopeTypeType !== "string",
15378
)
154-
.map(({ id, spokenForms }) => [id, spokenForms] as const),
79+
.map((scopeTypeType) => ({
80+
type: scopeTypeType,
81+
})),
82+
83+
...surroundingPairNames.map(
84+
(surroundingPairName): SurroundingPairScopeType => ({
85+
type: "surroundingPair",
86+
delimiter: surroundingPairName,
87+
}),
88+
),
89+
90+
...this.customSpokenForms.getCustomRegexScopeTypes(),
91+
];
92+
93+
this.scopeInfos = scopeTypes.map((scopeType) =>
94+
this.getScopeTypeInfo(scopeType),
15595
);
15696
}
15797

@@ -162,38 +102,11 @@ export class ScopeInfoProvider {
162102
getScopeTypeInfo(scopeType: ScopeType): ScopeTypeInfo {
163103
return {
164104
scopeType,
165-
spokenForms: this.getSpokenForms(scopeType),
105+
spokenForm: this.spokenFormGenerator.scopeType(scopeType),
166106
humanReadableName: scopeTypeToString(scopeType),
167107
isLanguageSpecific: isLanguageSpecific(scopeType),
168108
};
169109
}
170-
171-
getSpokenForms(scopeType: ScopeType): string[] | undefined {
172-
if (isSimpleScopeType(scopeType)) {
173-
return this.simpleScopeTypeSpokenFormMap?.[scopeType.type];
174-
}
175-
176-
if (scopeType.type === "surroundingPair") {
177-
return this.pairedDelimiterSpokenFormMap?.[scopeType.delimiter];
178-
}
179-
180-
if (scopeType.type === "customRegex") {
181-
return this.customRegexSpokenFormMap?.[scopeType.regex];
182-
}
183-
184-
return undefined;
185-
}
186-
187-
dispose(): void {
188-
this.disposables.forEach(({ dispose }) => {
189-
try {
190-
dispose();
191-
} catch (e) {
192-
// do nothing; some of the VSCode disposables misbehave, and we don't
193-
// want that to prevent us from disposing the rest of the disposables
194-
}
195-
});
196-
}
197110
}
198111

199112
/**

packages/cursorless-vscode/src/ScopeSupportTreeProvider.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,11 @@ function getSupportCategories(): SupportCategoryTreeItem[] {
129129

130130
class ScopeSupportTreeItem extends vscode.TreeItem {
131131
constructor(scopeTypeInfo: ScopeTypeInfo) {
132-
let label: string, description: string | undefined;
133-
if (scopeTypeInfo.spokenForms == null) {
134-
label = scopeTypeInfo.humanReadableName;
135-
} else {
136-
label =
137-
scopeTypeInfo.spokenForms.length === 0
138-
? "-"
139-
: `"${scopeTypeInfo.spokenForms[0]}"`;
140-
description = scopeTypeInfo.humanReadableName;
141-
}
132+
const label =
133+
scopeTypeInfo.spokenForm.type === "error"
134+
? "-"
135+
: `"${scopeTypeInfo.spokenForm.preferred}"`;
136+
const description = scopeTypeInfo.humanReadableName;
142137

143138
super(label, vscode.TreeItemCollapsibleState.None);
144139

packages/cursorless-vscode/src/ide/vscode/VscodeFileSystem.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Disposable, FileSystem, PathChangeListener } from "@cursorless/common";
2-
import { RelativePattern, workspace } from "vscode";
2+
import { RelativePattern, Uri, workspace } from "vscode";
33

44
export class VscodeFileSystem implements FileSystem {
55
watch(path: string, onDidChange: PathChangeListener): Disposable {
6+
console.log(`path: ${path}`);
67
// FIXME: Support globs?
78
const watcher = workspace.createFileSystemWatcher(
8-
new RelativePattern(path, "**"),
9+
new RelativePattern(Uri.file(path), "**"),
910
);
1011
watcher.onDidChange(onDidChange);
1112
watcher.onDidCreate(onDidChange);

0 commit comments

Comments
 (0)