diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index f42a47ee7e..4f61638126 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -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/camelCaseToAllDown"; export { Notifier } from "./util/Notifier"; export type { Listener } from "./util/Notifier"; export type { TokenHatSplittingMode } from "./ide/types/Configuration"; @@ -42,6 +43,7 @@ export * from "./types/TextEditorOptions"; export * from "./types/TextLine"; export * from "./types/Token"; export * from "./types/HatTokenMap"; +export * from "./types/SpokenForm"; export * from "./util/textFormatters"; export * from "./types/snippet.types"; export * from "./testUtil/fromPlainObject"; diff --git a/packages/common/src/types/SpokenForm.ts b/packages/common/src/types/SpokenForm.ts new file mode 100644 index 0000000000..f79e5d4a7a --- /dev/null +++ b/packages/common/src/types/SpokenForm.ts @@ -0,0 +1,47 @@ +/** + * The spoken form of a command, scope type, etc, that can be spoken + * using a given set of custom or default spoken forms. + */ +export interface SpokenFormSuccess { + type: "success"; + + /** + * The spoken forms for this entity. These could either be a user's custom + * spoken forms, if we have access to them, or the default spoken forms, if we + * don't, or if we're testing. There will often only be a single entry in this + * array, but there can be multiple if the user has used the `|` syntax in their + * spoken form csv's to define aliases for a single spoken form. + */ + spokenForms: string[]; +} + +/** + * An error spoken form, which indicates that the given entity (command, scope + * type, etc) cannot be spoken, and the reason why. + */ +export interface SpokenFormError { + type: "error"; + + /** + * The reason why the entity cannot be spoken. + */ + reason: string; + + /** + * If `true`, indicates that the entity wasn't found in the user's Talon spoken + * forms json, and so they need to update their cursorless-talon to get the + * given entity. + */ + requiresTalonUpdate: boolean; + + /** + * If `true`, indicates that the entity is only for internal experimentation, + * and should not be exposed to users except within a targeted working group. + */ + isPrivate: boolean; +} + +/** + * A spoken form, which can either be a success or an error. + */ +export type SpokenForm = SpokenFormSuccess | SpokenFormError; diff --git a/packages/common/src/util/camelCaseToAllDown.ts b/packages/common/src/util/camelCaseToAllDown.ts new file mode 100644 index 0000000000..0417ff2e79 --- /dev/null +++ b/packages/common/src/util/camelCaseToAllDown.ts @@ -0,0 +1,17 @@ +/** + * Converts a camelCase string to a string with spaces between each word, and + * all words in lowercase. + * + * Example: `camelCaseToAllDown("fooBarBaz")` returns `"foo bar baz"`. + * + * @param input A camelCase string + * @returns The same string, but with spaces between each word, and all words + * in lowercase + */ +export function camelCaseToAllDown(input: string): string { + return input + .replace(/([A-Z])/g, " $1") + .split(" ") + .map((word) => word.toLowerCase()) + .join(" "); +} diff --git a/packages/cursorless-engine/src/generateSpokenForm/NoSpokenFormError.ts b/packages/cursorless-engine/src/generateSpokenForm/NoSpokenFormError.ts index d7d00eaad8..0f2b86f332 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/NoSpokenFormError.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/NoSpokenFormError.ts @@ -1,5 +1,9 @@ export class NoSpokenFormError extends Error { - constructor(public reason: string) { + constructor( + public reason: string, + public requiresTalonUpdate: boolean = false, + public isPrivate: boolean = false, + ) { super(`No spoken form for: ${reason}`); } } diff --git a/packages/cursorless-engine/src/generateSpokenForm/SpokenFormComponent.ts b/packages/cursorless-engine/src/generateSpokenForm/SpokenFormComponent.ts new file mode 100644 index 0000000000..526385a5fa --- /dev/null +++ b/packages/cursorless-engine/src/generateSpokenForm/SpokenFormComponent.ts @@ -0,0 +1,40 @@ +import { SpokenFormMapEntry } from "../spokenForms/SpokenFormMap"; +import { + SpokenFormMapKeyTypes, + SpokenFormType, +} from "../spokenForms/SpokenFormType"; + +/** + * A component of a spoken form used internally during spoken form generation. + * This is a recursive type, so it can contain other spoken form components. + * During the final step of spoken form generation, it is flattened. + * + * FIXME: In the future, we want to replace `string` with something like + * `LiteralSpokenFormComponent` and `SpokenFormComponent[]` with something like + * `SequenceSpokenFormComponent`. We'd also like to avoid throwing + * `NoSpokenFormError` and instead return a `SpokenFormComponent` that + * represents an error. This would allow us to localize errors and still render + * the remainder of the spoken form component. + */ +export type SpokenFormComponent = + | CustomizableSpokenFormComponent + | string + | SpokenFormComponent[]; + +export interface CustomizableSpokenFormComponentForType< + T extends SpokenFormType, +> { + type: "customizable"; + spokenForms: SpokenFormMapEntry; + spokenFormType: T; + id: SpokenFormMapKeyTypes[T]; +} + +/** + * A customizable spoken form component. This is a spoken form component that + * can be customized by the user. It is used internally during spoken form + * generation. + */ +export type CustomizableSpokenFormComponent = { + [K in SpokenFormType]: CustomizableSpokenFormComponentForType; +}[SpokenFormType]; diff --git a/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/modifiers.ts b/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/modifiers.ts index 06f10b91d1..1652c9e02b 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/modifiers.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/modifiers.ts @@ -1,136 +1,10 @@ -import { - ModifierType, - SimpleScopeTypeType, - SurroundingPairName, - CompositeKeyMap, -} from "@cursorless/common"; - -export const modifiers = { - excludeInterior: "bounds", - toRawSelection: "just", - leading: "leading", - trailing: "trailing", - keepContentFilter: "content", - keepEmptyFilter: "empty", - inferPreviousMark: "its", - startOf: "start of", - endOf: "end of", - interiorOnly: "inside", - extendThroughStartOf: "head", - extendThroughEndOf: "tail", - everyScope: "every", - - containingScope: null, - ordinalScope: null, - relativeScope: null, - modifyIfUntyped: null, - cascading: null, - range: null, -} as const satisfies Record; - -export const modifiersExtra = { - first: "first", - last: "last", - previous: "previous", - next: "next", - forward: "forward", - backward: "backward", -}; - -export const scopeSpokenForms = { - argumentOrParameter: "arg", - attribute: "attribute", - functionCall: "call", - functionCallee: "callee", - className: "class name", - class: "class", - comment: "comment", - functionName: "funk name", - namedFunction: "funk", - ifStatement: "if state", - instance: "instance", - collectionItem: "item", - collectionKey: "key", - anonymousFunction: "lambda", - list: "list", - map: "map", - name: "name", - regularExpression: "regex", - section: "section", - sectionLevelOne: "one section", - sectionLevelTwo: "two section", - sectionLevelThree: "three section", - sectionLevelFour: "four section", - sectionLevelFive: "five section", - sectionLevelSix: "six section", - selector: "selector", - statement: "state", - string: "string", - branch: "branch", - type: "type", - value: "value", - condition: "condition", - unit: "unit", - // XML, JSX - xmlElement: "element", - xmlBothTags: "tags", - xmlStartTag: "start tag", - xmlEndTag: "end tag", - // LaTeX - part: "part", - chapter: "chapter", - subSection: "subsection", - subSubSection: "subsubsection", - namedParagraph: "paragraph", - subParagraph: "subparagraph", - environment: "environment", - // Talon - command: "command", - // Text-based scope types - character: "char", - word: "word", - token: "token", - identifier: "identifier", - line: "line", - sentence: "sentence", - paragraph: "block", - document: "file", - nonWhitespaceSequence: "paint", - boundedNonWhitespaceSequence: "short paint", - url: "link", - notebookCell: "cell", - - switchStatementSubject: null, - ["private.fieldAccess"]: null, -} as const satisfies Record; - -type ExtendedSurroundingPairName = SurroundingPairName | "whitespace"; - -const surroundingPairsSpoken: Record< - ExtendedSurroundingPairName, - string | null -> = { - curlyBrackets: "curly", - angleBrackets: "diamond", - escapedDoubleQuotes: "escaped quad", - escapedSingleQuotes: "escaped twin", - escapedParentheses: "escaped round", - escapedSquareBrackets: "escaped box", - doubleQuotes: "quad", - parentheses: "round", - backtickQuotes: "skis", - squareBrackets: "box", - singleQuotes: "twin", - any: "pair", - string: "string", - whitespace: "void", - - // Used internally by the "item" scope type - collectionBoundary: null, -}; +import { CompositeKeyMap } from "@cursorless/common"; +import { SpeakableSurroundingPairName } from "../../spokenForms/SpokenFormType"; +import { SpokenFormComponentMap } from "../getSpokenFormComponentMap"; +import { CustomizableSpokenFormComponentForType } from "../SpokenFormComponent"; const surroundingPairsDelimiters: Record< - ExtendedSurroundingPairName, + SpeakableSurroundingPairName, [string, string] | null > = { curlyBrackets: ["{", "}"], @@ -150,36 +24,19 @@ const surroundingPairsDelimiters: Record< string: null, collectionBoundary: null, }; + const surroundingPairDelimiterToName = new CompositeKeyMap< [string, string], - SurroundingPairName + SpeakableSurroundingPairName >((pair) => pair); for (const [name, pair] of Object.entries(surroundingPairsDelimiters)) { if (pair != null) { - surroundingPairDelimiterToName.set(pair, name as SurroundingPairName); - } -} - -export const surroundingPairForceDirections = { - left: "left", - right: "right", -}; - -/** - * Given a pair name (eg `parentheses`), returns the spoken form of the - * surrounding pair. - * @param surroundingPair The name of the surrounding pair - * @returns The spoken form of the surrounding pair - */ -export function surroundingPairNameToSpokenForm( - surroundingPair: SurroundingPairName, -): string { - const result = surroundingPairsSpoken[surroundingPair]; - if (result == null) { - throw Error(`Unknown surrounding pair '${surroundingPair}'`); + surroundingPairDelimiterToName.set( + pair, + name as SpeakableSurroundingPairName, + ); } - return result; } /** @@ -191,12 +48,13 @@ export function surroundingPairNameToSpokenForm( * @returns The spoken form of the surrounding pair */ export function surroundingPairDelimitersToSpokenForm( + spokenFormMap: SpokenFormComponentMap, left: string, right: string, -): string { +): CustomizableSpokenFormComponentForType<"pairedDelimiter"> { const pairName = surroundingPairDelimiterToName.get([left, right]); if (pairName == null) { throw Error(`Unknown surrounding pair delimiters '${left} ${right}'`); } - return surroundingPairNameToSpokenForm(pairName); + return spokenFormMap.pairedDelimiter[pairName]; } diff --git a/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.test.ts b/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.test.ts index 6e8e2d31f4..ce14b1e441 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.test.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.test.ts @@ -8,36 +8,83 @@ import * as yaml from "js-yaml"; import * as assert from "node:assert"; import { promises as fsp } from "node:fs"; import { canonicalizeAndValidateCommand } from "../core/commandVersionUpgrades/canonicalizeAndValidateCommand"; -import { generateSpokenForm } from "./generateSpokenForm"; import { getHatMapCommand } from "./getHatMapCommand"; +import { SpokenFormGenerator } from "."; +import { defaultSpokenFormInfoMap } from "../spokenForms/defaultSpokenFormMap"; +import { SpokenFormMap, mapSpokenForms } from "../spokenForms/SpokenFormMap"; + +/** + * A spoken form map to use for testing. Just uses default spoken forms, but + * enables spoken forms that are disabled by default. + */ +const spokenFormMap: SpokenFormMap = mapSpokenForms( + defaultSpokenFormInfoMap, + ({ defaultSpokenForms, isPrivate }) => ({ + spokenForms: isPrivate ? [] : defaultSpokenForms, + isCustom: false, + defaultSpokenForms, + requiresTalonUpdate: false, + isPrivate, + }), +); suite("Generate spoken forms", () => { getRecordedTestPaths().forEach(({ name, path }) => test(name, () => runTest(path)), ); + + test("generate spoken form for custom regex", () => { + const generator = new SpokenFormGenerator({ + ...spokenFormMap, + customRegex: { + foo: { + spokenForms: ["bar"], + isCustom: true, + defaultSpokenForms: [], + requiresTalonUpdate: false, + isPrivate: false, + }, + }, + }); + + const spokenForm = generator.processScopeType({ + type: "customRegex", + regex: "foo", + }); + + assert(spokenForm.type === "success"); + assert.equal(spokenForm.spokenForms, "bar"); + }); }); async function runTest(file: string) { const buffer = await fsp.readFile(file); const fixture = yaml.load(buffer.toString()) as TestCaseFixtureLegacy; - const generatedSpokenForm = generateSpokenForm( + const generator = new SpokenFormGenerator(spokenFormMap); + + const generatedSpokenForm = generator.processCommand( canonicalizeAndValidateCommand(fixture.command), ); + if (generatedSpokenForm.type === "success") { + assert(generatedSpokenForm.spokenForms.length === 1); + } + if (fixture.marksToCheck != null && generatedSpokenForm.type === "success") { // If the test has marks to check (eg a hat token map test), it will end in // "take " as a way to indicate which mark to check - const hatMapSpokenForm = generateSpokenForm( + const hatMapSpokenForm = generator.processCommand( getHatMapCommand(fixture.marksToCheck), ); assert(hatMapSpokenForm.type === "success"); - generatedSpokenForm.value += " " + hatMapSpokenForm.value; + assert(hatMapSpokenForm.spokenForms.length === 1); + generatedSpokenForm.spokenForms[0] += " " + hatMapSpokenForm.spokenForms[0]; } if (shouldUpdateFixtures()) { if (generatedSpokenForm.type === "success") { - fixture.command.spokenForm = generatedSpokenForm.value; + fixture.command.spokenForm = generatedSpokenForm.spokenForms[0]; fixture.spokenFormError = undefined; } else { fixture.spokenFormError = generatedSpokenForm.reason; @@ -47,7 +94,10 @@ async function runTest(file: string) { await fsp.writeFile(file, serializeTestFixture(fixture)); } else { if (generatedSpokenForm.type === "success") { - assert.equal(fixture.command.spokenForm, generatedSpokenForm.value); + assert.equal( + fixture.command.spokenForm, + generatedSpokenForm.spokenForms[0], + ); assert.equal(fixture.spokenFormError, undefined); } else { assert.equal(fixture.spokenFormError, generatedSpokenForm.reason); diff --git a/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts index 34f5dda028..ad3a09f9ab 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts @@ -4,8 +4,9 @@ import { DestinationDescriptor, InsertionMode, PartialTargetDescriptor, + ScopeType, + camelCaseToAllDown, } from "@cursorless/common"; -import { RecursiveArray, flattenDeep } from "lodash"; import { NoSpokenFormError } from "./NoSpokenFormError"; import { actions } from "./defaultSpokenForms/actions"; import { connectives } from "./defaultSpokenForms/connectives"; @@ -15,191 +16,297 @@ import { wrapperSnippetToSpokenForm, } from "./defaultSpokenForms/snippets"; import { getRangeConnective } from "./getRangeConnective"; -import { primitiveTargetToSpokenForm } from "./primitiveTargetToSpokenForm"; +import { SpokenFormMap } from "../spokenForms/SpokenFormMap"; +import { PrimitiveTargetSpokenFormGenerator } from "./primitiveTargetToSpokenForm"; +import { + SpokenFormComponentMap, + getSpokenFormComponentMap, +} from "./getSpokenFormComponentMap"; +import { SpokenFormComponent } from "./SpokenFormComponent"; +import { SpokenForm } from "@cursorless/common"; -export interface SpokenFormSuccess { - type: "success"; - value: string; -} +export class SpokenFormGenerator { + private primitiveGenerator: PrimitiveTargetSpokenFormGenerator; + private spokenFormMap: SpokenFormComponentMap; -export interface SpokenFormError { - type: "error"; - reason: string; -} + constructor(spokenFormMap: SpokenFormMap) { + this.spokenFormMap = getSpokenFormComponentMap(spokenFormMap); -export type SpokenForm = SpokenFormSuccess | SpokenFormError; + this.primitiveGenerator = new PrimitiveTargetSpokenFormGenerator( + this.spokenFormMap, + ); + } -/** - * Given a command, generates its spoken form. - * @param command The command to generate a spoken form for - * @returns The spoken form of the command, or null if the command has no spoken - * form - */ -export function generateSpokenForm(command: CommandComplete): SpokenForm { - try { - const components = generateSpokenFormComponents(command.action); - return { type: "success", value: flattenDeep(components).join(" ") }; - } catch (e) { - if (e instanceof NoSpokenFormError) { - return { type: "error", reason: e.reason }; - } + /** + * Given a command, generates its spoken form. + * @param command The command to generate a spoken form for + * @returns The spoken form of the command + */ + processCommand(command: CommandComplete): SpokenForm { + return this.componentsToSpokenForm(() => this.handleAction(command.action)); + } - throw e; + /** + * Given a scope type, generates its spoken form. + * @param scopeType The scope type to generate a spoken form for + * @returns The spoken form of the scope type + */ + processScopeType(scopeType: ScopeType): SpokenForm { + return this.componentsToSpokenForm(() => [ + this.primitiveGenerator.handleScopeType(scopeType), + ]); } -} -function generateSpokenFormComponents( - action: ActionDescriptor, -): RecursiveArray { - switch (action.name) { - case "editNew": - case "getText": - case "replace": - case "executeCommand": - case "private.getTargets": - throw new NoSpokenFormError(`Action '${action.name}'`); - - case "replaceWithTarget": - case "moveToTarget": - return [ - actions[action.name], - targetToSpokenForm(action.source), - destinationToSpokenForm(action.destination), - ]; - - case "swapTargets": - return [ - actions[action.name], - targetToSpokenForm(action.target1), - connectives.swapConnective, - targetToSpokenForm(action.target2), - ]; - - case "callAsFunction": - if (action.argument.type === "implicit") { - return [actions[action.name], targetToSpokenForm(action.callee)]; + /** + * Given a function that returns a spoken form component, generates a spoken + * form for that component by flattening the component and performing a + * cartesian product over any elements that have multiple ways to be spoken. + * Note that this spoken form object can correspond to multiple actual spoken + * forms, consisting of a preferred spoken form and a list of alternative + * spoken forms. + * + * Note that today, we arbitrarily choose the first spoken form as the + * preferred spoken form, and the rest as alternative spoken forms. + * + * If the function throws a {@link NoSpokenFormError}, returns an error spoken + * form object instead. + * + * @param getComponents A function that returns the components to generate a + * spoken form for + * @returns A spoken form for the given components + */ + private componentsToSpokenForm( + getComponents: () => SpokenFormComponent, + ): SpokenForm { + try { + return { + type: "success", + spokenForms: constructSpokenForms(getComponents()), + }; + } catch (e) { + if (e instanceof NoSpokenFormError) { + return { + type: "error", + reason: e.reason, + requiresTalonUpdate: e.requiresTalonUpdate, + isPrivate: e.isPrivate, + }; } - return [ - actions[action.name], - targetToSpokenForm(action.callee), - "on", - targetToSpokenForm(action.argument), - ]; - - case "wrapWithPairedDelimiter": - case "rewrapWithPairedDelimiter": - return [ - surroundingPairDelimitersToSpokenForm(action.left, action.right), - actions[action.name], - targetToSpokenForm(action.target), - ]; - - case "pasteFromClipboard": - return [ - actions[action.name], - destinationToSpokenForm(action.destination), - ]; - - case "insertSnippet": - return [ - actions[action.name], - insertionSnippetToSpokenForm(action.snippetDescription), - destinationToSpokenForm(action.destination), - ]; - - case "generateSnippet": - if (action.snippetName != null) { - throw new NoSpokenFormError(`${action.name}.snippetName`); - } - return [actions[action.name], targetToSpokenForm(action.target)]; - - case "wrapWithSnippet": - return [ - wrapperSnippetToSpokenForm(action.snippetDescription), - actions[action.name], - targetToSpokenForm(action.target), - ]; - - case "highlight": { - if (action.highlightId != null) { - throw new NoSpokenFormError(`${action.name}.highlightId`); - } - return [actions[action.name], targetToSpokenForm(action.target)]; + + throw e; } + } + + private handleAction(action: ActionDescriptor): SpokenFormComponent { + switch (action.name) { + case "editNew": + case "getText": + case "replace": + case "executeCommand": + case "private.getTargets": + throw new NoSpokenFormError(`Action '${action.name}'`); + + case "replaceWithTarget": + case "moveToTarget": + return [ + actions[action.name], + this.handleTarget(action.source), + this.handleDestination(action.destination), + ]; + + case "swapTargets": + return [ + actions[action.name], + this.handleTarget(action.target1), + connectives.swapConnective, + this.handleTarget(action.target2), + ]; + + case "callAsFunction": + if (action.argument.type === "implicit") { + return [actions[action.name], this.handleTarget(action.callee)]; + } + return [ + actions[action.name], + this.handleTarget(action.callee), + "on", + this.handleTarget(action.argument), + ]; + + case "wrapWithPairedDelimiter": + case "rewrapWithPairedDelimiter": + return [ + surroundingPairDelimitersToSpokenForm( + this.spokenFormMap, + action.left, + action.right, + ), + actions[action.name], + this.handleTarget(action.target), + ]; + + case "pasteFromClipboard": + return [ + actions[action.name], + this.handleDestination(action.destination), + ]; + + case "insertSnippet": + return [ + actions[action.name], + insertionSnippetToSpokenForm(action.snippetDescription), + this.handleDestination(action.destination), + ]; + + case "generateSnippet": + if (action.snippetName != null) { + throw new NoSpokenFormError(`${action.name}.snippetName`); + } + return [actions[action.name], this.handleTarget(action.target)]; + + case "wrapWithSnippet": + return [ + wrapperSnippetToSpokenForm(action.snippetDescription), + actions[action.name], + this.handleTarget(action.target), + ]; + + case "highlight": { + if (action.highlightId != null) { + throw new NoSpokenFormError(`${action.name}.highlightId`); + } + return [actions[action.name], this.handleTarget(action.target)]; + } - default: { - return [actions[action.name], targetToSpokenForm(action.target)]; + default: { + return [actions[action.name], this.handleTarget(action.target)]; + } } } -} -function targetToSpokenForm( - target: PartialTargetDescriptor, -): RecursiveArray { - switch (target.type) { - case "list": - if (target.elements.length < 2) { - throw new NoSpokenFormError("List target with < 2 elements"); + private handleTarget(target: PartialTargetDescriptor): SpokenFormComponent { + switch (target.type) { + case "list": + if (target.elements.length < 2) { + throw new NoSpokenFormError("List target with < 2 elements"); + } + + return target.elements.map((element, i) => + i === 0 + ? this.handleTarget(element) + : [connectives.listConnective, this.handleTarget(element)], + ); + + case "range": { + const anchor = this.handleTarget(target.anchor); + const active = this.handleTarget(target.active); + const connective = getRangeConnective( + target.excludeAnchor, + target.excludeActive, + target.rangeType, + ); + return [anchor, connective, active]; } - return target.elements.map((element, i) => - i === 0 - ? targetToSpokenForm(element) - : [connectives.listConnective, targetToSpokenForm(element)], - ); - - case "range": { - const anchor = targetToSpokenForm(target.anchor); - const active = targetToSpokenForm(target.active); - const connective = getRangeConnective( - target.excludeAnchor, - target.excludeActive, - target.rangeType, - ); - return [anchor, connective, active]; + case "primitive": + return this.primitiveGenerator.handlePrimitiveTarget(target); + + case "implicit": + return []; } + } - case "primitive": - return primitiveTargetToSpokenForm(target); + private handleDestination( + destination: DestinationDescriptor, + ): SpokenFormComponent { + switch (destination.type) { + case "list": + if (destination.destinations.length < 2) { + throw new NoSpokenFormError("List destination with < 2 elements"); + } - case "implicit": - return []; + return destination.destinations.map((destination, i) => + i === 0 + ? this.handleDestination(destination) + : [connectives.listConnective, this.handleDestination(destination)], + ); + + case "primitive": + return [ + this.handleInsertionMode(destination.insertionMode), + this.handleTarget(destination.target), + ]; + + case "implicit": + return []; + } + } + + private handleInsertionMode(insertionMode: InsertionMode): string { + switch (insertionMode) { + case "to": + return connectives.sourceDestinationConnective; + case "before": + return connectives.before; + case "after": + return connectives.after; + } } } -function destinationToSpokenForm( - destination: DestinationDescriptor, -): RecursiveArray { - switch (destination.type) { - case "list": - if (destination.destinations.length < 2) { - throw new NoSpokenFormError("List destination with < 2 elements"); - } +function constructSpokenForms(component: SpokenFormComponent): string[] { + if (typeof component === "string") { + return [component]; + } + + if (Array.isArray(component)) { + if (component.length === 0) { + return [""]; + } + + return cartesianProduct(component.map(constructSpokenForms)).map((words) => + words.filter((word) => word.length !== 0).join(" "), + ); + } - return destination.destinations.map((destination, i) => - i === 0 - ? destinationToSpokenForm(destination) - : [connectives.listConnective, destinationToSpokenForm(destination)], - ); + if (component.spokenForms.spokenForms.length === 0) { + const componentInfo = `${camelCaseToAllDown( + component.spokenFormType, + )} with id ${component.id}`; - case "primitive": - return [ - insertionModeToSpokenForm(destination.insertionMode), - targetToSpokenForm(destination.target), - ]; + const helpInfo = component.spokenForms.isPrivate + ? "this is a private spoken form currently only for internal experimentation" + : "please see https://www.cursorless.org/docs/user/customization/ for more information"; - case "implicit": - return []; + throw new NoSpokenFormError( + `${componentInfo}; ${helpInfo}`, + component.spokenForms.requiresTalonUpdate, + component.spokenForms.isPrivate, + ); } + + return component.spokenForms.spokenForms; } -function insertionModeToSpokenForm(insertionMode: InsertionMode): string { - switch (insertionMode) { - case "to": - return connectives.sourceDestinationConnective; - case "before": - return connectives.before; - case "after": - return connectives.after; +/** + * Given an array of arrays, constructs all possible combinations of the + * elements of the arrays. For example, given [[1, 2], [3, 4]], returns [[1, 3], + * [1, 4], [2, 3], [2, 4]]. If any of the arrays are empty, returns an empty + * array. + * @param arrays The arrays to take the cartesian product of + */ +function cartesianProduct(arrays: T[][]): T[][] { + if (arrays.length === 0) { + return []; + } + + if (arrays.length === 1) { + return arrays[0].map((element) => [element]); } + + const [first, ...rest] = arrays; + const restCartesianProduct = cartesianProduct(rest); + return first.flatMap((element) => + restCartesianProduct.map((restElement) => [element, ...restElement]), + ); } diff --git a/packages/cursorless-engine/src/generateSpokenForm/getSpokenFormComponentMap.ts b/packages/cursorless-engine/src/generateSpokenForm/getSpokenFormComponentMap.ts new file mode 100644 index 0000000000..7fd6e7b4f9 --- /dev/null +++ b/packages/cursorless-engine/src/generateSpokenForm/getSpokenFormComponentMap.ts @@ -0,0 +1,63 @@ +import { SpokenFormMap } from "../spokenForms/SpokenFormMap"; +import { + PartialSpokenFormTypes, + SpokenFormMapKeyTypes, + SpokenFormType, +} from "../spokenForms/SpokenFormType"; +import { CustomizableSpokenFormComponentForType } from "./SpokenFormComponent"; + +/** + * A spoken form component map is a map of spoken form types to a map of IDs to + * spoken form components. It is used to generate spoken forms. It mirrors the + * structure of a {@link SpokenFormMap}, but instead of containing spoken form + * map entries, it contains spoken form components for use in spoken form + * generation. + */ +export type SpokenFormComponentMap = { + readonly [K in SpokenFormType]: K extends PartialSpokenFormTypes + ? Readonly< + Partial< + Record< + SpokenFormMapKeyTypes[K], + CustomizableSpokenFormComponentForType + > + > + > + : Readonly< + Record< + SpokenFormMapKeyTypes[K], + CustomizableSpokenFormComponentForType + > + >; +}; + +/** + * Converts a spoken form map to a spoken form component map for use in spoken + * form generation. + * @param spokenFormMap The spoken form map to convert to a spoken form + * component map + * @returns A spoken form component map that can be used to generate spoken + * forms + */ +export function getSpokenFormComponentMap( + spokenFormMap: SpokenFormMap, +): SpokenFormComponentMap { + return Object.fromEntries( + Object.entries(spokenFormMap).map(([spokenFormType, map]) => [ + spokenFormType, + Object.fromEntries( + Object.entries(map).map(([id, spokenForms]) => [ + id, + { + type: "customizable", + spokenForms, + spokenFormType, + id, + }, + ]), + ), + ]), + // FIXME: Don't cast here; need to make our own mapValues with stronger typing + // using tricks from our object.d.ts + ) as SpokenFormComponentMap; +} diff --git a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts index 9cda195b59..05b22e0e5f 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts @@ -6,9 +6,7 @@ import { RelativeScopeModifier, ScopeType, } from "@cursorless/common"; -import { RecursiveArray } from "lodash"; import { NoSpokenFormError } from "./NoSpokenFormError"; -import { characterToSpokenForm } from "./defaultSpokenForms/characters"; import { connectives } from "./defaultSpokenForms/connectives"; import { hatColorToSpokenForm, @@ -16,276 +14,334 @@ import { lineDirections, marks, } from "./defaultSpokenForms/marks"; -import { - modifiers, - modifiersExtra, - scopeSpokenForms, - surroundingPairForceDirections, - surroundingPairNameToSpokenForm, -} from "./defaultSpokenForms/modifiers"; + +import { getRangeConnective } from "./getRangeConnective"; import { numberToSpokenForm, ordinalToSpokenForm, } from "./defaultSpokenForms/numbers"; -import { getRangeConnective } from "./getRangeConnective"; +import { characterToSpokenForm } from "./defaultSpokenForms/characters"; +import { SpokenFormComponentMap } from "./getSpokenFormComponentMap"; +import { SpokenFormComponent } from "./SpokenFormComponent"; -export function primitiveTargetToSpokenForm( - target: PartialPrimitiveTargetDescriptor, -): RecursiveArray { - const components: RecursiveArray = []; - if (target.modifiers != null) { - components.push(target.modifiers.map(modifierToSpokenForm)); - } - if (target.mark != null) { - components.push(markToSpokenForm(target.mark)); +export class PrimitiveTargetSpokenFormGenerator { + constructor(private spokenFormMap: SpokenFormComponentMap) { + this.handleModifier = this.handleModifier.bind(this); } - return components; -} - -function modifierToSpokenForm(modifier: Modifier): RecursiveArray { - switch (modifier.type) { - case "cascading": - case "modifyIfUntyped": - throw new NoSpokenFormError(`Modifier '${modifier.type}'`); - - case "containingScope": - return [scopeTypeToSpokenForm(modifier.scopeType)]; - case "everyScope": - return [modifiers.everyScope, scopeTypeToSpokenForm(modifier.scopeType)]; - - case "extendThroughStartOf": - case "extendThroughEndOf": { - const type = modifiers[modifier.type]; - return modifier.modifiers != null - ? [type, modifier.modifiers.map(modifierToSpokenForm)] - : [type]; + handlePrimitiveTarget( + target: PartialPrimitiveTargetDescriptor, + ): SpokenFormComponent { + const components: SpokenFormComponent[] = []; + if (target.modifiers != null) { + components.push(target.modifiers.map(this.handleModifier)); } + if (target.mark != null) { + components.push(this.handleMark(target.mark)); + } + return components; + } - case "relativeScope": - return modifier.offset === 0 - ? relativeScopeInclusiveToSpokenForm(modifier) - : relativeScopeExclusiveToSpokenForm(modifier); - - case "ordinalScope": { - const scope = scopeTypeToSpokenForm(modifier.scopeType); + private handleModifier(modifier: Modifier): SpokenFormComponent { + switch (modifier.type) { + case "cascading": + case "modifyIfUntyped": + throw new NoSpokenFormError(`Modifier '${modifier.type}'`); + + case "containingScope": + return [this.handleScopeType(modifier.scopeType)]; + + case "everyScope": + return [ + this.spokenFormMap.simpleModifier.everyScope, + this.handleScopeType(modifier.scopeType), + ]; + + case "extendThroughStartOf": + case "extendThroughEndOf": { + const type = this.spokenFormMap.simpleModifier[modifier.type]; + return modifier.modifiers != null + ? [type, modifier.modifiers.map(this.handleModifier)] + : [type]; + } - if (modifier.length === 1) { - if (modifier.start === -1) { - return [modifiersExtra.last, scope]; + case "relativeScope": + return modifier.offset === 0 + ? this.handleRelativeScopeInclusive(modifier) + : this.handleRelativeScopeExclusive(modifier); + + case "ordinalScope": { + const scope = this.handleScopeType(modifier.scopeType); + + if (modifier.length === 1) { + if (modifier.start === -1) { + return [this.spokenFormMap.modifierExtra.last, scope]; + } + if (modifier.start === 0) { + return [this.spokenFormMap.modifierExtra.first, scope]; + } + if (modifier.start < 0) { + return [ + ordinalToSpokenForm(Math.abs(modifier.start)), + this.spokenFormMap.modifierExtra.last, + scope, + ]; + } + return [ordinalToSpokenForm(modifier.start + 1), scope]; } + + const number = numberToSpokenForm(modifier.length); + if (modifier.start === 0) { - return [modifiersExtra.first, scope]; + return [ + this.spokenFormMap.modifierExtra.first, + number, + pluralize(scope), + ]; } - if (modifier.start < 0) { + if (modifier.start === -modifier.length) { return [ - ordinalToSpokenForm(Math.abs(modifier.start)), - modifiersExtra.last, - scope, + this.spokenFormMap.modifierExtra.last, + number, + pluralize(scope), ]; } - return [ordinalToSpokenForm(modifier.start + 1), scope]; + + throw new NoSpokenFormError( + `'${modifier.type}' with count > 1 and offset away from start / end`, + ); } - const number = numberToSpokenForm(modifier.length); + case "range": { + if ( + modifier.anchor.type === "ordinalScope" && + modifier.active.type === "ordinalScope" && + modifier.anchor.length === 1 && + modifier.active.length === 1 && + modifier.anchor.scopeType.type === modifier.active.scopeType.type + ) { + const anchor = + modifier.anchor.start === -1 + ? this.spokenFormMap.modifierExtra.last + : ordinalToSpokenForm(modifier.anchor.start + 1); + const active = this.handleModifier(modifier.active); + const connective = getRangeConnective( + modifier.excludeAnchor, + modifier.excludeActive, + ); + return [anchor, connective, active]; + } - if (modifier.start === 0) { - return [modifiersExtra.first, number, pluralize(scope)]; - } - if (modifier.start === -modifier.length) { - return [modifiersExtra.last, number, pluralize(scope)]; + // Throw actual Error here because we're not sure we ever want to support + // a spoken form for these; we may deprecate this construct entirely + throw Error(`Modifier '${modifier.type}' is not fully implemented`); } - throw new NoSpokenFormError( - `'${modifier.type}' with count > 1 and offset away from start / end`, - ); + default: + return [this.spokenFormMap.simpleModifier[modifier.type]]; } + } - case "range": { - if ( - modifier.anchor.type === "ordinalScope" && - modifier.active.type === "ordinalScope" && - modifier.anchor.length === 1 && - modifier.active.length === 1 && - modifier.anchor.scopeType.type === modifier.active.scopeType.type - ) { - const anchor = - modifier.anchor.start === -1 - ? modifiersExtra.last - : ordinalToSpokenForm(modifier.anchor.start + 1); - const active = modifierToSpokenForm(modifier.active); - const connective = getRangeConnective( - modifier.excludeAnchor, - modifier.excludeActive, - ); - return [anchor, connective, active]; - } + private handleRelativeScopeInclusive( + modifier: RelativeScopeModifier, + ): SpokenFormComponent { + const scope = this.handleScopeType(modifier.scopeType); - // Throw actual Error here because we're not sure we ever want to support - // a spoken form for these; we may deprecate this construct entirely - throw Error(`Modifier '${modifier.type}' is not fully implemented`); - } + if (modifier.length === 1) { + const direction = + modifier.direction === "forward" + ? connectives.forward + : connectives.backward; - default: - return [modifiers[modifier.type]]; - } -} + // token forward/backward + return [scope, direction]; + } -function relativeScopeInclusiveToSpokenForm( - modifier: RelativeScopeModifier, -): RecursiveArray { - const scope = scopeTypeToSpokenForm(modifier.scopeType); + const length = numberToSpokenForm(modifier.length); + const scopePlural = pluralize(scope); - if (modifier.length === 1) { - const direction = - modifier.direction === "forward" - ? connectives.forward - : connectives.backward; + // two tokens + // This could also have been "two tokens forward"; there is no way to disambiguate. + if (modifier.direction === "forward") { + return [length, scopePlural]; + } - // token forward/backward - return [scope, direction]; + // two tokens backward + return [length, scopePlural, connectives.backward]; } - const length = numberToSpokenForm(modifier.length); - const scopePlural = pluralize(scope); + private handleRelativeScopeExclusive( + modifier: RelativeScopeModifier, + ): SpokenFormComponent { + const scope = this.handleScopeType(modifier.scopeType); + const direction = + modifier.direction === "forward" + ? connectives.next + : connectives.previous; - // two tokens - // This could also have been "two tokens forward"; there is no way to disambiguate. - if (modifier.direction === "forward") { - return [length, scopePlural]; - } + if (modifier.offset === 1) { + const number = numberToSpokenForm(modifier.length); - // two tokens backward - return [length, scopePlural, connectives.backward]; -} + if (modifier.length === 1) { + // next/previous token + return [direction, scope]; + } -function relativeScopeExclusiveToSpokenForm( - modifier: RelativeScopeModifier, -): RecursiveArray { - const scope = scopeTypeToSpokenForm(modifier.scopeType); - const direction = - modifier.direction === "forward" ? connectives.next : connectives.previous; + const scopePlural = pluralize(scope); - if (modifier.offset === 1) { - const number = numberToSpokenForm(modifier.length); + // next/previous two tokens + return [direction, number, scopePlural]; + } if (modifier.length === 1) { - // next/previous token - return [direction, scope]; + const ordinal = ordinalToSpokenForm(modifier.offset); + // second next/previous token + return [ordinal, direction, scope]; } - const scopePlural = pluralize(scope); - - // next/previous two tokens - return [direction, number, scopePlural]; + throw new NoSpokenFormError( + `${modifier.type} modifier with offset > 1 and length > 1`, + ); } - if (modifier.length === 1) { - const ordinal = ordinalToSpokenForm(modifier.offset); - // second next/previous token - return [ordinal, direction, scope]; - } + handleScopeType(scopeType: ScopeType): SpokenFormComponent { + switch (scopeType.type) { + case "oneOf": + throw new NoSpokenFormError(`Scope type '${scopeType.type}'`); + case "surroundingPair": { + const pair = this.spokenFormMap.pairedDelimiter[scopeType.delimiter]; + if (scopeType.forceDirection != null) { + return [ + this.spokenFormMap.surroundingPairForceDirection[ + scopeType.forceDirection + ], + pair, + ]; + } + return pair; + } - throw new NoSpokenFormError( - `${modifier.type} modifier with offset > 1 and length > 1`, - ); -} + case "customRegex": + return ( + this.spokenFormMap.customRegex[scopeType.regex] ?? { + type: "customizable", + spokenForms: { + spokenForms: [], + isCustom: true, + defaultSpokenForms: [], + requiresTalonUpdate: false, + isPrivate: false, + }, + spokenFormType: "customRegex", + id: scopeType.regex, + } + ); -function scopeTypeToSpokenForm(scopeType: ScopeType): string { - switch (scopeType.type) { - case "oneOf": - case "customRegex": - case "switchStatementSubject": - case "private.fieldAccess": - case "string": - throw new NoSpokenFormError(`Scope type '${scopeType.type}'`); - case "surroundingPair": { - const pair = surroundingPairNameToSpokenForm(scopeType.delimiter); - if (scopeType.forceDirection != null) { - const direction = - scopeType.forceDirection === "left" - ? surroundingPairForceDirections.left - : surroundingPairForceDirections.right; - return `${direction} ${pair}`; - } - return pair; + default: + return this.spokenFormMap.simpleScopeTypeType[scopeType.type]; } - - default: - return scopeSpokenForms[scopeType.type]; } -} -function markToSpokenForm(mark: PartialMark): RecursiveArray { - switch (mark.type) { - case "decoratedSymbol": { - const [color, shape] = mark.symbolColor.split("-"); - const components: string[] = []; - if (color !== "default") { - components.push(hatColorToSpokenForm(color)); - } - if (shape != null) { - components.push(hatShapeToSpokenForm(shape)); + private handleMark(mark: PartialMark): SpokenFormComponent { + switch (mark.type) { + case "decoratedSymbol": { + const [color, shape] = mark.symbolColor.split("-"); + const components: string[] = []; + if (color !== "default") { + components.push(hatColorToSpokenForm(color)); + } + if (shape != null) { + components.push(hatShapeToSpokenForm(shape)); + } + components.push(characterToSpokenForm(mark.character)); + return components; } - components.push(characterToSpokenForm(mark.character)); - return components; - } - case "lineNumber": { - return lineNumberToParts(mark); - } + case "lineNumber": { + return this.handleLineNumberMark(mark); + } - case "range": { - if ( - mark.anchor.type === "lineNumber" && - mark.active.type === "lineNumber" - ) { - const [typeAnchor, numberAnchor] = lineNumberToParts(mark.anchor); - const [typeActive, numberActive] = lineNumberToParts(mark.active); - if (typeAnchor === typeActive) { - const connective = getRangeConnective( - mark.excludeAnchor, - mark.excludeActive, + case "range": { + if ( + mark.anchor.type === "lineNumber" && + mark.active.type === "lineNumber" + ) { + const [typeAnchor, numberAnchor] = this.handleLineNumberMark( + mark.anchor, + ); + const [typeActive, numberActive] = this.handleLineNumberMark( + mark.active, ); - // Row five past seven - return [typeAnchor, numberAnchor, connective, numberActive]; + if (typeAnchor === typeActive) { + const connective = getRangeConnective( + mark.excludeAnchor, + mark.excludeActive, + ); + // Row five past seven + return [typeAnchor, numberAnchor, connective, numberActive]; + } } + // Throw actual Error here because we're not sure we ever want to support + // a spoken form for these; we may deprecate this construct entirely + throw Error(`Mark '${mark.type}' is not fully implemented`); } - // Throw actual Error here because we're not sure we ever want to support - // a spoken form for these; we may deprecate this construct entirely - throw Error(`Mark '${mark.type}' is not fully implemented`); + case "explicit": + throw new NoSpokenFormError(`Mark '${mark.type}'`); + + default: + return [marks[mark.type]]; } - case "explicit": - throw new NoSpokenFormError(`Mark '${mark.type}'`); + } - default: - return [marks[mark.type]]; + private handleLineNumberMark(mark: LineNumberMark): [string, string] { + switch (mark.lineNumberType) { + case "absolute": + throw new NoSpokenFormError("Absolute line numbers"); + case "modulo100": { + // row/ five + return [ + lineDirections.modulo100, + numberToSpokenForm(mark.lineNumber + 1), + ]; + } + case "relative": { + // up/down five + return [ + mark.lineNumber < 0 + ? lineDirections.relativeUp + : lineDirections.relativeDown, + numberToSpokenForm(Math.abs(mark.lineNumber)), + ]; + } + } } } -function lineNumberToParts(mark: LineNumberMark): [string, string] { - switch (mark.lineNumberType) { - case "absolute": - throw new NoSpokenFormError("Absolute line numbers"); - case "modulo100": { - // row/ five - return [ - lineDirections.modulo100, - numberToSpokenForm(mark.lineNumber + 1), - ]; - } - case "relative": { - // up/down five - return [ - mark.lineNumber < 0 - ? lineDirections.relativeUp - : lineDirections.relativeDown, - numberToSpokenForm(Math.abs(mark.lineNumber)), - ]; +function pluralize(name: SpokenFormComponent): SpokenFormComponent { + if (typeof name === "string") { + return pluralizeString(name); + } + + if (Array.isArray(name)) { + if (name.length === 0) { + return name; } + + const last = name[name.length - 1]; + + return [...name.slice(0, -1), pluralize(last)]; } + + return { + ...name, + spokenForms: { + ...name.spokenForms, + spokenForms: name.spokenForms.spokenForms.map(pluralizeString), + }, + }; } -function pluralize(name: string): string { +// FIXME: Properly pluralize +function pluralizeString(name: string): string { return `${name}s`; } diff --git a/packages/cursorless-engine/src/spokenForms/SpokenFormMap.ts b/packages/cursorless-engine/src/spokenForms/SpokenFormMap.ts new file mode 100644 index 0000000000..37002e14db --- /dev/null +++ b/packages/cursorless-engine/src/spokenForms/SpokenFormMap.ts @@ -0,0 +1,92 @@ +import { + SpokenFormType, + PartialSpokenFormTypes, + SpokenFormMapKeyTypes, +} from "./SpokenFormType"; + +export interface SpokenFormMapEntry { + /** + * The spoken forms for this entry. These could either be a user's custom + * spoken forms, if we have access to them, or the default spoken forms, if we + * don't, or if we're testing. + */ + spokenForms: string[]; + + /** + * If `true`, indicates that the user is not using the default spoken forms + * for this entry. + */ + isCustom: boolean; + + /** + * The default spoken forms for this entry. + */ + defaultSpokenForms: string[]; + + /** + * If `true`, indicates that the entry wasn't found in the user's Talon spoken + * forms json, and so they need to update their cursorless-talon to get the + * given entity. + */ + requiresTalonUpdate: boolean; + + /** + * If `true`, indicates that the entry is only for internal experimentation, + * and should not be exposed to users except within a targeted working group. + */ + isPrivate: boolean; +} + +/** + * A type that contains all the keys of {@link SpokenFormMapKeyTypes}, each of + * whose values are a map from the allowed identifiers for that key to a particular + * value type {@link T}. + */ +export type SpokenFormMappingType = { + readonly [K in SpokenFormType]: K extends PartialSpokenFormTypes + ? Readonly>> + : Readonly>; +}; + +/** + * A spoken form map contains information about the spoken forms for all our + * speakable entities, including scope types, paired delimiters, etc. It can + * either contain the user's custom spoken forms, or the default spoken forms, + * if we don't have access to the user's custom spoken forms, or if we're + * testing. + * + * Each key of this map is a type of spoken form, eg `simpleScopeTypeType`, and + * the value is a map of identifiers to {@link SpokenFormMapEntry}s. + */ +export type SpokenFormMap = SpokenFormMappingType; + +/** + * Converts a spoken form map to a spoken form component map for use in spoken + * form generation. + * @param spokenFormMap The spoken form map to convert to a spoken form + * component map + * @returns A spoken form component map that can be used to generate spoken + * forms + */ +export function mapSpokenForms( + input: SpokenFormMappingType, + mapper: ( + input: I, + spokenFormType: T, + id: SpokenFormMapKeyTypes[T], + ) => O, +): SpokenFormMappingType { + return Object.fromEntries( + Object.entries(input).map(([spokenFormType, map]) => [ + spokenFormType, + Object.fromEntries( + Object.entries(map).map(([id, inputValue]) => [ + id, + mapper(inputValue!, spokenFormType as SpokenFormType, id), + ]), + ), + ]), + // FIXME: Don't cast here; need to make our own mapValues with stronger typing + // using tricks from our object.d.ts + ) as SpokenFormMappingType; +} diff --git a/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts b/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts new file mode 100644 index 0000000000..06a52eae60 --- /dev/null +++ b/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts @@ -0,0 +1,64 @@ +import { + ModifierType, + SimpleScopeTypeType, + SurroundingPairName, +} from "@cursorless/common"; + +/** + * This interface is the source of truth for the types used in our spoken form + * map. The keys of this interface are the types of spoken forms that we + * support, eg `simpleScopeTypeType`, `simpleModifier`, etc. The type of each + * key is a disjunction of all identifiers that are allowed for the given type of + * spoken form. + */ +export interface SpokenFormMapKeyTypes { + pairedDelimiter: SpeakableSurroundingPairName; + simpleScopeTypeType: SimpleScopeTypeType; + surroundingPairForceDirection: "left" | "right"; + + /** + * These modifier types are spoken by directly saying the spoken form for the + * modifier type, unlike the more complex spoken forms such as + * `relativeScope`, which can use various different custom spoken forms such + * as `next`, `previous`, etc. + */ + simpleModifier: SimpleModifierType; + + /** + * These are customizable spoken forms used in speaking modifiers, but that + * don't directly correspond to a modifier type. For example, `next` is a + * customizable spoken form that can be used when speaking `relativeScope` + * modifiers, but `next` itself isn't a modifier type. + */ + modifierExtra: ModifierExtra; + customRegex: string; +} + +/** + * These are the types of spoken forms that are not total mappings, eg if you + * look up a string in `spokenFormMap.customRegex`, you might get `undefined`, + * even though technically the identifier type is `string`. + */ +export type PartialSpokenFormTypes = "customRegex"; + +export type SpeakableSurroundingPairName = SurroundingPairName | "whitespace"; + +type SimpleModifierType = Exclude< + ModifierType, + | "containingScope" + | "ordinalScope" + | "relativeScope" + | "modifyIfUntyped" + | "cascading" + | "range" +>; + +type ModifierExtra = + | "first" + | "last" + | "previous" + | "next" + | "forward" + | "backward"; + +export type SpokenFormType = keyof SpokenFormMapKeyTypes; diff --git a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMap.ts b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMap.ts new file mode 100644 index 0000000000..446d661082 --- /dev/null +++ b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMap.ts @@ -0,0 +1,35 @@ +import { mapSpokenForms } from "./SpokenFormMap"; +import { defaultSpokenFormMapCore } from "./defaultSpokenFormMapCore"; +import { DefaultSpokenFormInfoMap } from "./defaultSpokenFormMap.types"; + +/** + * This map contains information about the default spoken forms for all our + * speakable entities, including scope types, paired delimiters, etc. Note that + * this map can't be used as a spoken form map. If you want something that can + * be used as a spoken form map, see {@link defaultSpokenFormMap}. + */ +export const defaultSpokenFormInfoMap: DefaultSpokenFormInfoMap = + mapSpokenForms(defaultSpokenFormMapCore, (value) => + typeof value === "string" + ? { + defaultSpokenForms: [value], + isDisabledByDefault: false, + isPrivate: false, + } + : value, + ); + +/** + * A spoken form map constructed from the default spoken forms. It is designed to + * be used as a fallback when the Talon spoken form map is not available. + */ +export const defaultSpokenFormMap = mapSpokenForms( + defaultSpokenFormInfoMap, + ({ defaultSpokenForms, isDisabledByDefault, isPrivate }) => ({ + spokenForms: isDisabledByDefault ? [] : defaultSpokenForms, + isCustom: false, + defaultSpokenForms, + requiresTalonUpdate: false, + isPrivate, + }), +); diff --git a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMap.types.ts b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMap.types.ts new file mode 100644 index 0000000000..5b7fa567c5 --- /dev/null +++ b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMap.types.ts @@ -0,0 +1,28 @@ +import { SpokenFormMappingType } from "./SpokenFormMap"; +import { SpokenFormMapKeyTypes } from "./SpokenFormType"; + +export type DefaultSpokenFormMapDefinition = { + readonly [K in keyof SpokenFormMapKeyTypes]: Readonly< + Record + >; +}; + +export interface DefaultSpokenFormMapEntry { + defaultSpokenForms: string[]; + + /** + * If `true`, indicates that the entry may have a default spoken form, but + * it should not be enabled by default. These will show up in user csv's with + * a `-` at the beginning. + */ + isDisabledByDefault: boolean; + + /** + * If `true`, indicates that the entry is only for internal experimentation, + * and should not be exposed to users except within a targeted working group. + */ + isPrivate: boolean; +} + +export type DefaultSpokenFormInfoMap = + SpokenFormMappingType; diff --git a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts new file mode 100644 index 0000000000..5b825407bb --- /dev/null +++ b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts @@ -0,0 +1,135 @@ +import { DefaultSpokenFormMapDefinition } from "./defaultSpokenFormMap.types"; +import { isDisabledByDefault, isPrivate } from "./spokenFormMapUtil"; + +/** + * This map contains the default spoken forms for all our speakable entities, + * including scope types, paired delimiters, etc. We would like this map to + * become the sole source of truth for our default spoken forms, including the + * Talon side. Today it is only used on the extension side for testing, and as a + * fallback when we can't get the custom spoken forms from Talon. + * + * In this map, for regular entities, ie ones that are speakable by default, not + * private, and have only one spoken form, we allow a shorthand of just providing + * the spoken form as a string. For more complex cases, we can use the + * {@link isPrivate} or {@link isDisabledByDefault} helper functions to construct + * {@link DefaultSpokenFormMapEntry} objects, or just construct them manually. + */ +export const defaultSpokenFormMapCore: DefaultSpokenFormMapDefinition = { + pairedDelimiter: { + curlyBrackets: "curly", + angleBrackets: "diamond", + escapedDoubleQuotes: "escaped quad", + escapedSingleQuotes: "escaped twin", + escapedParentheses: "escaped round", + escapedSquareBrackets: "escaped box", + doubleQuotes: "quad", + parentheses: "round", + backtickQuotes: "skis", + squareBrackets: "box", + singleQuotes: "twin", + any: "pair", + string: "string", + whitespace: "void", + + collectionBoundary: isPrivate("collection boundary"), + }, + + simpleScopeTypeType: { + argumentOrParameter: "arg", + attribute: "attribute", + functionCall: "call", + functionCallee: "callee", + className: "class name", + class: "class", + comment: "comment", + functionName: "funk name", + namedFunction: "funk", + ifStatement: "if state", + instance: "instance", + collectionItem: "item", + collectionKey: "key", + anonymousFunction: "lambda", + list: "list", + map: "map", + name: "name", + regularExpression: "regex", + section: "section", + sectionLevelOne: isDisabledByDefault("one section"), + sectionLevelTwo: isDisabledByDefault("two section"), + sectionLevelThree: isDisabledByDefault("three section"), + sectionLevelFour: isDisabledByDefault("four section"), + sectionLevelFive: isDisabledByDefault("five section"), + sectionLevelSix: isDisabledByDefault("six section"), + selector: "selector", + statement: "state", + branch: "branch", + type: "type", + value: "value", + condition: "condition", + unit: "unit", + // XML, JSX + xmlElement: "element", + xmlBothTags: "tags", + xmlStartTag: "start tag", + xmlEndTag: "end tag", + // LaTeX + part: "part", + chapter: "chapter", + subSection: "subsection", + subSubSection: "subsubsection", + namedParagraph: "paragraph", + subParagraph: "subparagraph", + environment: "environment", + // Talon + command: "command", + // Text-based scope types + character: "char", + word: "word", + token: "token", + identifier: "identifier", + line: "line", + sentence: "sentence", + paragraph: "block", + document: "file", + nonWhitespaceSequence: "paint", + boundedNonWhitespaceSequence: "short paint", + url: "link", + notebookCell: "cell", + + ["private.fieldAccess"]: isPrivate("access"), + string: isPrivate("parse tree string"), + switchStatementSubject: isPrivate("subject"), + }, + + surroundingPairForceDirection: { + left: "left", + right: "right", + }, + + simpleModifier: { + excludeInterior: "bounds", + toRawSelection: "just", + leading: "leading", + trailing: "trailing", + keepContentFilter: "content", + keepEmptyFilter: "empty", + inferPreviousMark: "its", + startOf: "start of", + endOf: "end of", + interiorOnly: "inside", + extendThroughStartOf: "head", + extendThroughEndOf: "tail", + everyScope: "every", + }, + + modifierExtra: { + first: "first", + last: "last", + previous: "previous", + next: "next", + forward: "forward", + backward: "backward", + }, + + customRegex: {}, +}; diff --git a/packages/cursorless-engine/src/spokenForms/spokenFormMapUtil.ts b/packages/cursorless-engine/src/spokenForms/spokenFormMapUtil.ts new file mode 100644 index 0000000000..602f8df49a --- /dev/null +++ b/packages/cursorless-engine/src/spokenForms/spokenFormMapUtil.ts @@ -0,0 +1,35 @@ +import { DefaultSpokenFormMapEntry } from "./defaultSpokenFormMap.types"; + +/** + * Used to construct entities that should not be speakable by default. + * + * @param spokenForms The default spoken forms for this entity + * @returns A DefaultSpokenFormMapEntry with the given spoken forms, and + * {@link DefaultSpokenFormMapEntry.isDisabledByDefault|isDisabledByDefault} set + * to true + */ +export function isDisabledByDefault( + ...spokenForms: string[] +): DefaultSpokenFormMapEntry { + return { + defaultSpokenForms: spokenForms, + isDisabledByDefault: true, + isPrivate: false, + }; +} + +/** + * Used to construct entities that are only for internal experimentation. + * + * @param spokenForms The default spoken forms for this entity + * @returns A DefaultSpokenFormMapEntry with the given spoken forms, and + * {@link DefaultSpokenFormMapEntry.isDisabledByDefault|isDisabledByDefault} and + * {@link DefaultSpokenFormMapEntry.isPrivate|isPrivate} set to true + */ +export function isPrivate(...spokenForms: string[]): DefaultSpokenFormMapEntry { + return { + defaultSpokenForms: spokenForms, + isDisabledByDefault: true, + isPrivate: true, + }; +} diff --git a/packages/cursorless-engine/src/testCaseRecorder/TestCaseRecorder.ts b/packages/cursorless-engine/src/testCaseRecorder/TestCaseRecorder.ts index 6cf7d22ecc..6d497ab95d 100644 --- a/packages/cursorless-engine/src/testCaseRecorder/TestCaseRecorder.ts +++ b/packages/cursorless-engine/src/testCaseRecorder/TestCaseRecorder.ts @@ -31,8 +31,9 @@ import { takeSnapshot } from "../testUtil/takeSnapshot"; import { TestCase } from "./TestCase"; import { StoredTargetMap } from "../core/StoredTargets"; import { CommandRunner } from "../CommandRunner"; -import { generateSpokenForm } from "../generateSpokenForm"; import { RecordTestCaseCommandOptions } from "./RecordTestCaseCommandOptions"; +import { SpokenFormGenerator } from "../generateSpokenForm"; +import { defaultSpokenFormMap } from "../spokenForms/defaultSpokenFormMap"; const CALIBRATION_DISPLAY_DURATION_MS = 50; @@ -59,6 +60,7 @@ export class TestCaseRecorder { private captureFinalThatMark: boolean = false; private spyIde: SpyIDE | undefined; private originalIde: IDE | undefined; + private spokenFormGenerator = new SpokenFormGenerator(defaultSpokenFormMap); constructor( private hatTokenMap: HatTokenMap, @@ -275,14 +277,20 @@ export class TestCaseRecorder { this.spyIde = new SpyIDE(this.originalIde); injectIde(this.spyIde!); - const spokenForm = generateSpokenForm(command); + const spokenForm = this.spokenFormGenerator.processCommand(command); this.testCase = new TestCase( { ...command, + + // If spoken form is an error, we just use the spoken form that they + // actually used. If it is a success, we use the first spoken form + // that from our generator, which will almost always be the only spoken + // form. If there are multiple, there will be a test failure so we can + // cross that bridge if it happens. spokenForm: spokenForm.type === "success" - ? spokenForm.value + ? spokenForm.spokenForms[0] : command.spokenForm, }, hatTokenMap, diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/customRegex/clearWhite.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/customRegex/clearWhite.yml index e383561f60..14cb2f4936 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/customRegex/clearWhite.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/customRegex/clearWhite.yml @@ -9,7 +9,9 @@ command: scopeType: {type: customRegex, regex: '\p{Zs}+'} usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'customRegex' +spokenFormError: >- + custom regex with id \p{Zs}+; please see + https://www.cursorless.org/docs/user/customization/ for more information initialState: documentContents: "\" \"" selections: diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeHarpAndStringEach.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeHarpAndStringEach.yml index 5889176cf6..e31761c62a 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeHarpAndStringEach.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeHarpAndStringEach.yml @@ -1,7 +1,7 @@ languageId: typescript command: version: 1 - spokenForm: take harp and string each + spokenForm: take harp and parse tree string each action: setSelection targets: - type: list @@ -11,7 +11,9 @@ command: - type: primitive modifier: {type: containingScope, scopeType: string} mark: {type: decoratedSymbol, symbolColor: default, character: e} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeHarpPastStringEach.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeHarpPastStringEach.yml index 09045e44a1..254e53f347 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeHarpPastStringEach.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeHarpPastStringEach.yml @@ -1,7 +1,7 @@ languageId: typescript command: version: 1 - spokenForm: take harp past string each + spokenForm: take harp past parse tree string each action: setSelection targets: - type: range @@ -14,7 +14,9 @@ command: mark: {type: decoratedSymbol, symbolColor: default, character: e} excludeStart: false excludeEnd: false -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeStringHarpAndEach.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeStringHarpAndEach.yml index cddc264ee1..772fd0e5ff 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeStringHarpAndEach.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeStringHarpAndEach.yml @@ -1,7 +1,7 @@ languageId: typescript command: version: 1 - spokenForm: take string harp and each + spokenForm: take parse tree string harp and each action: setSelection targets: - type: list @@ -11,7 +11,9 @@ command: mark: {type: decoratedSymbol, symbolColor: default, character: h} - type: primitive mark: {type: decoratedSymbol, symbolColor: default, character: e} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeStringHarpPastEach.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeStringHarpPastEach.yml index cd736de89a..6fd0480dca 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeStringHarpPastEach.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/inference/takeStringHarpPastEach.yml @@ -1,7 +1,7 @@ languageId: typescript command: version: 1 - spokenForm: take string harp past each + spokenForm: take parse tree string harp past each action: setSelection targets: - type: range @@ -14,7 +14,9 @@ command: mark: {type: decoratedSymbol, symbolColor: default, character: e} excludeStart: false excludeEnd: false -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/clearSubject.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/clearSubject.yml index dd7876f978..2527a0329a 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/clearSubject.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/clearSubject.yml @@ -9,7 +9,9 @@ command: - type: containingScope scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: true -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: | int main() { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/clearSubject2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/clearSubject2.yml index b01328d27f..7cb19907d1 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/clearSubject2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/clearSubject2.yml @@ -9,7 +9,9 @@ command: - type: containingScope scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: false -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: | int main() { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/takeString.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/takeString.yml index bcd97676e5..ca13958b19 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/takeString.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/cpp/takeString.yml @@ -1,12 +1,14 @@ languageId: cpp command: version: 1 - spokenForm: take string + spokenForm: take parse tree string action: setSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | char* a = "hello world"; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/csharp/clearSubject.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/csharp/clearSubject.yml index d2da4cdfd0..8b01c6f5ff 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/csharp/clearSubject.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/csharp/clearSubject.yml @@ -9,7 +9,9 @@ command: - type: containingScope scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: true -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: |- switch (aaa) { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/csharp/clearSubject2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/csharp/clearSubject2.yml index c998355f1f..6003ddaffa 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/csharp/clearSubject2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/csharp/clearSubject2.yml @@ -9,7 +9,9 @@ command: - type: containingScope scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: false -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: |- switch (aaa + 1) { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/go/takeString.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/go/takeString.yml index ef6a3e77c2..7780ee15b3 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/go/takeString.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/go/takeString.yml @@ -1,12 +1,14 @@ languageId: go command: version: 1 - spokenForm: take string + spokenForm: take parse tree string action: setSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string, includeSiblings: false} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: x := "hello world" selections: diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/clearSubject.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/clearSubject.yml index a19ee22d0f..a069d783ad 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/clearSubject.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/clearSubject.yml @@ -9,7 +9,9 @@ command: scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: | class Aaa { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/clearSubject2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/clearSubject2.yml index 43ecc96dfd..23cc14a25d 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/clearSubject2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/clearSubject2.yml @@ -9,7 +9,9 @@ command: scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: | class Aaa { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/takeString.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/takeString.yml index bc5e8814b9..2781e22f6e 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/takeString.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/takeString.yml @@ -1,12 +1,14 @@ languageId: java command: version: 1 - spokenForm: take string + spokenForm: take parse tree string action: setSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/json/takeString.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/json/takeString.yml index b8fcf009f2..6cff10b7af 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/json/takeString.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/json/takeString.yml @@ -1,12 +1,14 @@ languageId: json command: version: 1 - spokenForm: take string + spokenForm: take parse tree string action: setSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessAir.yml index 4b98fd0b05..ebd6ae7171 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessAir.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessAir.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: a} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc().ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat.yml index 8d788acd33..edf2a91d41 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc().ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat3.yml index 49bafcd209..7dc22b89ce 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat3.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[c.d] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat5.yml index 0632a701c2..d634680ffb 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat5.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessBat5.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa.bbb( diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap.yml index becce34a70..0c29d78513 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: c} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc().ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap2.yml index b35b7656a4..762ccabe28 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: c} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc(eee.fff).ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap3.yml index 5ecea40d2b..c15a0890ff 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessCap3.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: c} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[c.d()] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessDrum.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessDrum.yml index 1b4121234a..3b113d3db1 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessDrum.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessDrum.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: d} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc().ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessDrum2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessDrum2.yml index 8099913a3a..44bb58435d 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessDrum2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessDrum2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: d} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[c.d()] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessEach.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessEach.yml index acf32cdebc..d95cb087a4 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessEach.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessEach.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: e} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc(eee).ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessEach2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessEach2.yml index 15bfd18e0f..389adaaf5d 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessEach2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessEach2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: e} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc(eee.fff).ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessFine.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessFine.yml index b98a509d43..8eb3d315da 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessFine.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearAccessFine.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: f} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc(eee.fff).ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearEveryAccessAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearEveryAccessAir.yml index 00c75f2f27..1ef5a7498d 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearEveryAccessAir.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearEveryAccessAir.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: a} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | ( diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearSubject.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearSubject.yml index 91e63c279c..c72dd3f221 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearSubject.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/clearSubject.yml @@ -9,7 +9,9 @@ command: scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: | match 0: diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccess.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccess.yml index 42bb5aedc9..f01ca44d73 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccess.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccess.yml @@ -9,7 +9,9 @@ command: - type: everyScope scopeType: {type: private.fieldAccess} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: aaa.bbb() + ccc() selections: diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessAir.yml index 6918a185fb..9028b40f70 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessAir.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessAir.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: a} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c().d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessAir2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessAir2.yml index a2410c06fb..89c8a2b234 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessAir2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessAir2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: a} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[1] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessBat.yml index d23f897cac..ef10a03acb 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessBat.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessBat.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c().d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessBat2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessBat2.yml index cf8581063e..7c17575b92 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessBat2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessBat2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[1] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessCap.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessCap.yml index d82f52ab12..f720bd7b5a 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessCap.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessCap.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: c} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c().d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessCap2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessCap2.yml index 627f54fe33..bf1495045a 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessCap2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessCap2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: c} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[c.d] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessDrum.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessDrum.yml index a4f0a028f2..460f01c1d3 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessDrum.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessDrum.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: d} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c().d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessDrum2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessDrum2.yml index 911286cbdf..e3942e2e08 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessDrum2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessDrum2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: d} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[c.d] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessEach.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessEach.yml index 3a95625e03..a62a669551 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessEach.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessEach.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: e} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c(e).d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessEach2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessEach2.yml index c7b408ba6c..b6d1649fde 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessEach2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeEveryAccessEach2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: e} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c(e.f).d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString.yml index cb6a793fe7..ec0732ee80 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString.yml @@ -1,12 +1,14 @@ languageId: python command: version: 1 - spokenForm: take string + spokenForm: take parse tree string action: setSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString2.yml index 5255d44a5d..d826dffe54 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString2.yml @@ -1,12 +1,14 @@ languageId: python command: version: 1 - spokenForm: take string + spokenForm: take parse tree string action: setSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString3.yml index 2e95571eea..76b8c15d97 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString3.yml @@ -1,12 +1,14 @@ languageId: python command: version: 1 - spokenForm: take string + spokenForm: take parse tree string action: setSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString4.yml index d562f3523a..0b48a66e51 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString4.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString4.yml @@ -1,12 +1,14 @@ languageId: python command: version: 1 - spokenForm: take string + spokenForm: take parse tree string action: setSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/rust/changeSubject.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/rust/changeSubject.yml index 53696501f8..30657de978 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/rust/changeSubject.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/rust/changeSubject.yml @@ -9,7 +9,9 @@ command: scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: | match user { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearString.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearString.yml index 357ab4fd71..04d9d5c3bc 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearString.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearString.yml @@ -1,12 +1,14 @@ languageId: scala command: version: 0 - spokenForm: change string + spokenForm: change parse tree string action: clearAndSetSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string, includeSiblings: false} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | class ExampleClass() { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearString2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearString2.yml index 9f8082f9ec..db10ab5c6d 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearString2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearString2.yml @@ -1,12 +1,14 @@ languageId: scala command: version: 0 - spokenForm: change string + spokenForm: change parse tree string action: clearAndSetSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string, includeSiblings: false} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | val string = """ diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearStringOdd.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearStringOdd.yml index acdc1a250f..5724ddc567 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearStringOdd.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearStringOdd.yml @@ -1,13 +1,15 @@ languageId: scala command: version: 0 - spokenForm: change string odd + spokenForm: change parse tree string odd action: clearAndSetSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string, includeSiblings: false} mark: {type: decoratedSymbol, symbolColor: default, character: o} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | class ExampleClass() { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearSubject.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearSubject.yml index f50e3eee5a..6a62af3073 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearSubject.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scala/clearSubject.yml @@ -9,7 +9,9 @@ command: scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: | def matchTest(x: Int): String = x match { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir.yml index a9deceb536..fd79d24529 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: a} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc().ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir2.yml index 99a14bfa7d..646b1562e2 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: a} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa?.bbb() diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir3.yml index 4f7124d3cc..fa659192c4 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessAir3.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: a} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a?.[0].b?.() diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat.yml index ca8858e530..e2f730f1da 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc().ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat2.yml index 272a33731d..155706af8c 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa?.bbb() diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat3.yml index 7455ffcb2f..ed2ea3cb34 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat3.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[c.d] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat4.yml index 960653badb..ea3e7a5bb7 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat4.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat4.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a?.[0].b?.() diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat5.yml index 8b51354984..4692b5770f 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat5.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessBat5.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa.bbb( diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap.yml index ae4fe1ab38..98eb43dadc 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: c} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc().ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap2.yml index ec3d5b8b56..91b305e5d2 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: c} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc(eee.fff).ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap3.yml index 6806548f42..235df9f09b 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessCap3.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: c} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[c.d()] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessDrum.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessDrum.yml index 311fe3475e..f6c5d5bbb9 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessDrum.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessDrum.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: d} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc().ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessDrum2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessDrum2.yml index 0b4df7cba3..98e809ef0b 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessDrum2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessDrum2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: d} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[c.d()] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessEach.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessEach.yml index dbc36d4a5d..733b3767af 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessEach.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessEach.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: e} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc(eee).ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessEach2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessEach2.yml index 905de811cc..479bf37921 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessEach2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessEach2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: e} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc(eee.fff).ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessFine.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessFine.yml index c83d3de18a..308d5412bd 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessFine.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearAccessFine.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: f} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa().bbb.ccc(eee.fff).ddd diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryAccessAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryAccessAir.yml index 0fb01ad164..526711dc1f 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryAccessAir.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryAccessAir.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: a} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | aaa diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearSubject.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearSubject.yml index ae271fbc7f..856ef72574 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearSubject.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearSubject.yml @@ -9,7 +9,9 @@ command: - type: containingScope scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: true -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: |- switch(aaa) { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearSubject2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearSubject2.yml index 0441888bd4..2ccfd2dda8 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearSubject2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearSubject2.yml @@ -9,7 +9,9 @@ command: - type: containingScope scopeType: {type: switchStatementSubject} usePrePhraseSnapshot: true -spokenFormError: Scope type 'switchStatementSubject' +spokenFormError: >- + simple scope type type with id switchStatementSubject; this is a private + spoken form currently only for internal experimentation initialState: documentContents: |- switch(aaa + 1) { diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccess.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccess.yml index ee2c321c9c..090d160999 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccess.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccess.yml @@ -9,7 +9,9 @@ command: - type: everyScope scopeType: {type: private.fieldAccess} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: aaa.bbb() + ccc() selections: diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessAir.yml index 345af57396..52059e1cbd 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessAir.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessAir.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: a} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c().d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessAir2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessAir2.yml index 9e2716cd6d..f1e4131b38 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessAir2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessAir2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: a} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[1] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat.yml index 5c11e3c3c0..916c0de009 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c().d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat2.yml index 08b8531457..6507f59f3e 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[1] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat3.yml index d9dab15eb0..674f02b387 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessBat3.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: b} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a.b`c d` diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessCap.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessCap.yml index fdf7fe9f8b..c3ea28db89 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessCap.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessCap.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: c} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c().d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessCap2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessCap2.yml index f6c29dd04f..c83e733ae4 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessCap2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessCap2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: c} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[c.d] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessDrum.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessDrum.yml index a86fe8773b..486be7502b 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessDrum.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessDrum.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: d} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c().d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessDrum2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessDrum2.yml index 16d60e6b7b..064e370ce2 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessDrum2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessDrum2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: d} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a[0].b[c.d] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessEach.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessEach.yml index 436bea50e8..35c275d30f 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessEach.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessEach.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: e} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c(e).d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessEach2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessEach2.yml index 79fff2d6d8..85738c3186 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessEach2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeEveryAccessEach2.yml @@ -10,7 +10,9 @@ command: scopeType: {type: private.fieldAccess} mark: {type: decoratedSymbol, symbolColor: default, character: e} usePrePhraseSnapshot: true -spokenFormError: Scope type 'private.fieldAccess' +spokenFormError: >- + simple scope type type with id private.fieldAccess; this is a private spoken + form currently only for internal experimentation initialState: documentContents: | a().b.c(e.f).d; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeString.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeString.yml index 77fb08e29d..37aef03639 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeString.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/takeString.yml @@ -1,12 +1,14 @@ languageId: typescript command: version: 1 - spokenForm: take string + spokenForm: take parse tree string action: setSelection targets: - type: primitive modifier: {type: containingScope, scopeType: string} -spokenFormError: Scope type 'string' +spokenFormError: >- + simple scope type type with id string; this is a private spoken form currently + only for internal experimentation initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstPaint.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstPaint.yml index 0b74d1458c..d8a196cda6 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstPaint.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstPaint.yml @@ -11,7 +11,9 @@ command: length: 1 usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'customRegex' +spokenFormError: >- + custom regex with id [^\s"'`]+; please see + https://www.cursorless.org/docs/user/customization/ for more information initialState: documentContents: aaa-bbb ccc-ddd eee-fff ggg-hhh selections: diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstPaint2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstPaint2.yml index bfb63aa1e6..a60149376b 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstPaint2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearFirstPaint2.yml @@ -11,7 +11,9 @@ command: length: 1 usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'customRegex' +spokenFormError: >- + custom regex with id [^\s"'`]+; please see + https://www.cursorless.org/docs/user/customization/ for more information initialState: documentContents: aaa-bbb ccc-ddd eee-fff ggg-hhh selections: diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastPaint.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastPaint.yml index 879c9da17f..d8c3cbe502 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastPaint.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastPaint.yml @@ -11,7 +11,9 @@ command: length: 1 usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'customRegex' +spokenFormError: >- + custom regex with id [^\s"'`]+; please see + https://www.cursorless.org/docs/user/customization/ for more information initialState: documentContents: aaa-bbb ccc-ddd eee-fff ggg-hhh selections: diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastPaint2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastPaint2.yml index f46e7fc1e2..cd4a8df92b 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastPaint2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/ordinalScopes/clearLastPaint2.yml @@ -11,7 +11,9 @@ command: length: 1 usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'customRegex' +spokenFormError: >- + custom regex with id [^\s"'`]+; please see + https://www.cursorless.org/docs/user/customization/ for more information initialState: documentContents: aaa-bbb ccc-ddd eee-fff ggg-hhh selections: diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/selectionTypes/clearCustomRegex.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/selectionTypes/clearCustomRegex.yml index 24d99bc00c..a192da5d1a 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/selectionTypes/clearCustomRegex.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/selectionTypes/clearCustomRegex.yml @@ -9,7 +9,9 @@ command: scopeType: {type: customRegex, regex: '[\w/_.]+'} usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'customRegex' +spokenFormError: >- + custom regex with id [\w/_.]+; please see + https://www.cursorless.org/docs/user/customization/ for more information initialState: documentContents: aa.bb/cc_dd123( ) selections: diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/selectionTypes/clearEveryCustomRegex.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/selectionTypes/clearEveryCustomRegex.yml index abe8c17b05..4ef31c9dbd 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/selectionTypes/clearEveryCustomRegex.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/selectionTypes/clearEveryCustomRegex.yml @@ -9,7 +9,9 @@ command: scopeType: {type: customRegex, regex: '[\w/_.]+'} usePrePhraseSnapshot: true action: {name: clearAndSetSelection} -spokenFormError: Scope type 'customRegex' +spokenFormError: >- + custom regex with id [\w/_.]+; please see + https://www.cursorless.org/docs/user/customization/ for more information initialState: documentContents: aa.bb/cc_dd123 aa.bb/cc_dd123( ) selections: