Skip to content

Commit b62cfee

Browse files
committed
Improved types
1 parent a2197f2 commit b62cfee

File tree

2 files changed

+83
-84
lines changed

2 files changed

+83
-84
lines changed

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

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import { Notifier, SimpleScopeTypeType } from "@cursorless/common";
2-
import { SpeakableSurroundingPairName } from "../spokenForms/SpokenFormType";
1+
import { Notifier } from "@cursorless/common";
2+
import {
3+
SpokenFormMapKeyTypes,
4+
SpokenFormType,
5+
} from "../spokenForms/SpokenFormType";
36

47
/**
58
* Interface representing a communication mechanism whereby Talon can provide
@@ -10,28 +13,27 @@ export interface TalonSpokenForms {
1013
onDidChange: Notifier["registerListener"];
1114
}
1215

13-
export interface CustomRegexSpokenFormEntry {
14-
type: "customRegex";
15-
id: string;
16-
spokenForms: string[];
17-
}
16+
/**
17+
* The types of entries for which we currently support getting custom spoken
18+
* forms from Talon.
19+
*/
20+
export const SUPPORTED_ENTRY_TYPES = [
21+
"simpleScopeTypeType",
22+
"customRegex",
23+
"pairedDelimiter",
24+
] as const;
1825

19-
export interface PairedDelimiterSpokenFormEntry {
20-
type: "pairedDelimiter";
21-
id: SpeakableSurroundingPairName;
22-
spokenForms: string[];
23-
}
26+
type SupportedEntryType = (typeof SUPPORTED_ENTRY_TYPES)[number];
2427

25-
export interface SimpleScopeTypeTypeSpokenFormEntry {
26-
type: "simpleScopeTypeType";
27-
id: SimpleScopeTypeType;
28+
export interface SpokenFormEntryForType<T extends SpokenFormType> {
29+
type: T;
30+
id: SpokenFormMapKeyTypes[T];
2831
spokenForms: string[];
2932
}
3033

31-
export type SpokenFormEntry =
32-
| CustomRegexSpokenFormEntry
33-
| PairedDelimiterSpokenFormEntry
34-
| SimpleScopeTypeTypeSpokenFormEntry;
34+
export type SpokenFormEntry = {
35+
[K in SpokenFormType]: SpokenFormEntryForType<K>;
36+
}[SupportedEntryType];
3537

3638
export class NeedsInitialTalonUpdateError extends Error {
3739
constructor(message: string) {

packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts

Lines changed: 62 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,20 @@ import {
66
} from "@cursorless/common";
77
import { isEqual } from "lodash";
88
import {
9+
SUPPORTED_ENTRY_TYPES,
910
NeedsInitialTalonUpdateError,
1011
SpokenFormEntry,
1112
TalonSpokenForms,
1213
} from "../scopeProviders/SpokenFormEntry";
1314
import { ide } from "../singletons/ide.singleton";
1415
import { SpokenFormMap, SpokenFormMapEntry } from "./SpokenFormMap";
15-
import { SpokenFormType } from "./SpokenFormType";
16+
import { SpokenFormMapKeyTypes, SpokenFormType } from "./SpokenFormType";
1617
import {
1718
defaultSpokenFormInfoMap,
1819
defaultSpokenFormMap,
1920
} from "./defaultSpokenFormMap";
2021
import { DefaultSpokenFormMapEntry } from "./defaultSpokenFormMap.types";
2122

22-
/**
23-
* The types of entries for which we currently support getting custom spoken
24-
* forms from Talon.
25-
*/
26-
const ENTRY_TYPES = [
27-
"simpleScopeTypeType",
28-
"customRegex",
29-
"pairedDelimiter",
30-
] as const;
31-
3223
type Writable<T> = {
3324
-readonly [K in keyof T]: T[K];
3425
};
@@ -110,61 +101,17 @@ export class CustomSpokenForms {
110101
return;
111102
}
112103

113-
for (const entryType of ENTRY_TYPES) {
114-
const customEntries = Object.fromEntries(
115-
allCustomEntries
116-
.filter((entry) => entry.type === entryType)
117-
.map(({ id, spokenForms }) => [id, spokenForms]),
104+
for (const entryType of SUPPORTED_ENTRY_TYPES) {
105+
updateEntriesForType(
106+
this.spokenFormMap_,
107+
entryType,
108+
defaultSpokenFormInfoMap[entryType],
109+
Object.fromEntries(
110+
allCustomEntries
111+
.filter((entry) => entry.type === entryType)
112+
.map(({ id, spokenForms }) => [id, spokenForms]),
113+
),
118114
);
119-
120-
const defaultEntries: Partial<Record<string, DefaultSpokenFormMapEntry>> =
121-
defaultSpokenFormInfoMap[entryType];
122-
123-
/**
124-
* The ids of the entries to include in the spoken form map. We need a
125-
* union of the ids from the default entry and the custom entry. The custom
126-
* entry could be missing private entries, or it could be missing entries
127-
* because the Talon side is old. The default entry could be missing entries
128-
* like custom regexes, where the user can create arbitrary ids.
129-
*/
130-
const ids = Array.from(
131-
new Set([
132-
...Object.keys(defaultEntries),
133-
...Object.keys(customEntries),
134-
]),
135-
);
136-
// FIXME: How to avoid the type assertions here?
137-
this.spokenFormMap_[entryType] = Object.fromEntries(
138-
ids.map((id): [SpokenFormType, SpokenFormMapEntry] => {
139-
const { defaultSpokenForms = [], isPrivate = false } =
140-
defaultEntries[id] ?? {};
141-
const customSpokenForms = customEntries[id];
142-
if (customSpokenForms != null) {
143-
return [
144-
id as SpokenFormType,
145-
{
146-
defaultSpokenForms,
147-
spokenForms: customSpokenForms,
148-
requiresTalonUpdate: false,
149-
isCustom: isEqual(defaultSpokenForms, customSpokenForms),
150-
isPrivate,
151-
},
152-
];
153-
} else {
154-
return [
155-
id as SpokenFormType,
156-
{
157-
defaultSpokenForms,
158-
spokenForms: [],
159-
// If it's not a private spoken form, then it's a new scope type
160-
requiresTalonUpdate: !isPrivate,
161-
isCustom: false,
162-
isPrivate,
163-
},
164-
];
165-
}
166-
}),
167-
) as any;
168115
}
169116

170117
this.customSpokenFormsInitialized_ = true;
@@ -180,3 +127,53 @@ export class CustomSpokenForms {
180127

181128
dispose = this.disposer.dispose;
182129
}
130+
131+
function updateEntriesForType<T extends SpokenFormType>(
132+
spokenFormMapToUpdate: Writable<SpokenFormMap>,
133+
key: T,
134+
defaultEntries: Partial<
135+
Record<SpokenFormMapKeyTypes[T], DefaultSpokenFormMapEntry>
136+
>,
137+
customEntries: Partial<Record<SpokenFormMapKeyTypes[T], string[]>>,
138+
) {
139+
/**
140+
* The ids of the entries to include in the spoken form map. We need a
141+
* union of the ids from the default entry and the custom entry. The custom
142+
* entry could be missing private entries, or it could be missing entries
143+
* because the Talon side is old. The default entry could be missing entries
144+
* like custom regexes, where the user can create arbitrary ids.
145+
*/
146+
const ids = Array.from(
147+
new Set([...Object.keys(defaultEntries), ...Object.keys(customEntries)]),
148+
) as SpokenFormMapKeyTypes[T][];
149+
150+
const obj: Partial<Record<SpokenFormMapKeyTypes[T], SpokenFormMapEntry>> = {};
151+
for (const id of ids) {
152+
const { defaultSpokenForms = [], isPrivate = false } =
153+
defaultEntries[id] ?? {};
154+
const customSpokenForms = customEntries[id];
155+
156+
obj[id] =
157+
customSpokenForms == null
158+
? // No entry for the given id. This either means that the user needs to
159+
// update Talon, or it's a private spoken form.
160+
{
161+
defaultSpokenForms,
162+
spokenForms: [],
163+
// If it's not a private spoken form, then it's a new scope type
164+
requiresTalonUpdate: !isPrivate,
165+
isCustom: false,
166+
isPrivate,
167+
}
168+
: // We have an entry for the given id
169+
{
170+
defaultSpokenForms,
171+
spokenForms: customSpokenForms,
172+
requiresTalonUpdate: false,
173+
isCustom: isEqual(defaultSpokenForms, customSpokenForms),
174+
isPrivate,
175+
};
176+
}
177+
178+
spokenFormMapToUpdate[key] = obj as SpokenFormMap[T];
179+
}

0 commit comments

Comments
 (0)