diff --git a/cursorless-talon/src/cheatsheet/sections/scopes.py b/cursorless-talon/src/cheatsheet/sections/scopes.py index b7133c9a72..9c610891fe 100644 --- a/cursorless-talon/src/cheatsheet/sections/scopes.py +++ b/cursorless-talon/src/cheatsheet/sections/scopes.py @@ -1,12 +1,25 @@ -from ..get_list import get_lists +from ..get_list import get_lists, get_raw_list def get_scopes(): - return get_lists( - ["scope_type"], - "scopeType", + complex_scopes = get_raw_list("glyph_scope_type") + return [ + *get_lists( + ["scope_type"], + "scopeType", + { + "argumentOrParameter": "Argument", + "boundedNonWhitespaceSequence": "Non whitespace sequence stopped by surrounding pair delimeters", + }, + ), { - "argumentOrParameter": "Argument", - "boundedNonWhitespaceSequence": "Non whitespace sequence stopped by surrounding pair delimeters", + "id": "glyph", + "type": "scopeType", + "variations": [ + { + "spokenForm": f"{complex_scopes['glyph']} ", + "description": "Instance of single character ", + }, + ], }, - ) + ] diff --git a/cursorless-talon/src/modifiers/glyph_scope.py b/cursorless-talon/src/modifiers/glyph_scope.py new file mode 100644 index 0000000000..beff4c35ed --- /dev/null +++ b/cursorless-talon/src/modifiers/glyph_scope.py @@ -0,0 +1,30 @@ +from talon import Module + +mod = Module() + +mod.list( + "cursorless_glyph_scope_type", + desc="Cursorless glyph scope type", +) +mod.list( + "cursorless_glyph_scope_type_plural", + desc="Plural version of Cursorless glyph scope type", +) + + +@mod.capture(rule="{user.cursorless_glyph_scope_type} ") +def cursorless_glyph_scope_type(m) -> dict[str, str]: + return { + "type": "glyph", + "character": m.any_alphanumeric_key, + } + + +@mod.capture( + rule="{user.cursorless_glyph_scope_type_plural} " +) +def cursorless_glyph_scope_type_plural(m) -> dict[str, str]: + return { + "type": "glyph", + "character": m.any_alphanumeric_key, + } diff --git a/cursorless-talon/src/modifiers/scopes.py b/cursorless-talon/src/modifiers/scopes.py index 02b98da19e..ee1efb9b2c 100644 --- a/cursorless-talon/src/modifiers/scopes.py +++ b/cursorless-talon/src/modifiers/scopes.py @@ -15,25 +15,39 @@ @mod.capture( - rule="{user.cursorless_scope_type} | {user.cursorless_custom_regex_scope_type}" + rule="{user.cursorless_scope_type} | | {user.cursorless_custom_regex_scope_type}" ) def cursorless_scope_type(m) -> dict[str, str]: """Cursorless scope type singular""" try: return {"type": m.cursorless_scope_type} except AttributeError: - return {"type": "customRegex", "regex": m.cursorless_custom_regex_scope_type} + pass + + try: + return m.cursorless_glyph_scope_type + except AttributeError: + pass + + return {"type": "customRegex", "regex": m.cursorless_custom_regex_scope_type} @mod.capture( - rule="{user.cursorless_scope_type_plural} | {user.cursorless_custom_regex_scope_type_plural}" + rule="{user.cursorless_scope_type_plural} | | {user.cursorless_custom_regex_scope_type_plural}" ) def cursorless_scope_type_plural(m) -> dict[str, str]: """Cursorless scope type plural""" try: return {"type": m.cursorless_scope_type_plural} except AttributeError: - return { - "type": "customRegex", - "regex": m.cursorless_custom_regex_scope_type_plural, - } + pass + + try: + return m.cursorless_glyph_scope_type_plural + except AttributeError: + pass + + return { + "type": "customRegex", + "regex": m.cursorless_custom_regex_scope_type_plural, + } diff --git a/cursorless-talon/src/spoken_forms.json b/cursorless-talon/src/spoken_forms.json index 062f4b3291..eec6aac6e2 100644 --- a/cursorless-talon/src/spoken_forms.json +++ b/cursorless-talon/src/spoken_forms.json @@ -171,6 +171,9 @@ }, "surrounding_pair_scope_type": { "string": "string" + }, + "glyph_scope_type": { + "glyph": "glyph" } }, "paired_delimiters.csv": { diff --git a/cursorless-talon/src/spoken_forms.py b/cursorless-talon/src/spoken_forms.py index aeed1593f0..30ad16498d 100644 --- a/cursorless-talon/src/spoken_forms.py +++ b/cursorless-talon/src/spoken_forms.py @@ -68,6 +68,7 @@ def ret(filename: str, *args: P.args, **kwargs: P.kwargs) -> R: "wrapper_only_paired_delimiter": "pairedDelimiter", "surrounding_pair_scope_type": "pairedDelimiter", "scope_type": "simpleScopeTypeType", + "glyph_scope_type": "complexScopeTypeType", "custom_regex_scope_type": "customRegex", } @@ -125,7 +126,7 @@ def handle_new_values(csv_name: str, values: list[SpokenFormEntry]): handle_csv("experimental/miscellaneous.csv"), handle_csv( "modifier_scope_types.csv", - pluralize_lists=["scope_type"], + pluralize_lists=["scope_type", "glyph_scope_type"], extra_allowed_values=[ "private.fieldAccess", "private.switchStatementSubject", diff --git a/packages/cheatsheet/src/lib/sampleSpokenFormInfos/defaults.json b/packages/cheatsheet/src/lib/sampleSpokenFormInfos/defaults.json index 1c1e45f74a..f70df30696 100644 --- a/packages/cheatsheet/src/lib/sampleSpokenFormInfos/defaults.json +++ b/packages/cheatsheet/src/lib/sampleSpokenFormInfos/defaults.json @@ -14,6 +14,16 @@ } ] }, + { + "id": "breakLine", + "type": "action", + "variations": [ + { + "spokenForm": "break ", + "description": "Break line" + } + ] + }, { "id": "callAsFunction", "type": "action", @@ -228,6 +238,16 @@ } ] }, + { + "id": "joinLines", + "type": "action", + "variations": [ + { + "spokenForm": "join ", + "description": "Join lines" + } + ] + }, { "id": "moveToTarget", "type": "action", @@ -905,6 +925,16 @@ "description": "Trailing delimiter range" } ] + }, + { + "id": "visible", + "type": "modifier", + "variations": [ + { + "spokenForm": "visible", + "description": "Visible" + } + ] } ] }, @@ -1058,6 +1088,16 @@ } ] }, + { + "id": "show_scope_sidebar", + "type": "command", + "variations": [ + { + "spokenForm": "bar cursorless", + "description": "Show cursorless sidebar" + } + ] + }, { "id": "show_scope_visualizer", "type": "command", @@ -1272,6 +1312,16 @@ } ] }, + { + "id": "glyph", + "type": "scopeType", + "variations": [ + { + "spokenForm": "glyph ", + "description": "Instance of single character " + } + ] + }, { "id": "identifier", "type": "scopeType", @@ -1537,7 +1587,7 @@ "type": "scopeType", "variations": [ { - "spokenForm": "word", + "spokenForm": "sub", "description": "Word" } ] diff --git a/packages/common/src/types/command/PartialTargetDescriptor.types.ts b/packages/common/src/types/command/PartialTargetDescriptor.types.ts index 0a5293046e..1f0b02bb9e 100644 --- a/packages/common/src/types/command/PartialTargetDescriptor.types.ts +++ b/packages/common/src/types/command/PartialTargetDescriptor.types.ts @@ -205,11 +205,17 @@ export interface OneOfScopeType { scopeTypes: ScopeType[]; } +export interface GlyphScopeType { + type: "glyph"; + character: string; +} + export type ScopeType = | SimpleScopeType | SurroundingPairScopeType | CustomRegexScopeType - | OneOfScopeType; + | OneOfScopeType + | GlyphScopeType; export interface ContainingSurroundingPairModifier extends ContainingScopeModifier { diff --git a/packages/cursorless-engine/src/core/Cheatsheet.ts b/packages/cursorless-engine/src/core/Cheatsheet.ts index 2c8089fac0..5cfd18fa48 100644 --- a/packages/cursorless-engine/src/core/Cheatsheet.ts +++ b/packages/cursorless-engine/src/core/Cheatsheet.ts @@ -4,6 +4,7 @@ import produce from "immer"; import { sortBy } from "lodash"; import { ide } from "../singletons/ide.singleton"; import path from "path"; +import { getCursorlessRepoRoot } from "@cursorless/common"; /** * The argument expected by the cheatsheet command. @@ -56,27 +57,9 @@ export async function showCheatsheet({ * @param spokenFormInfo The new value to use for default spoken forms. */ export async function updateDefaults(spokenFormInfo: CheatsheetInfo) { - const { runMode, assetsRoot, workspaceFolders } = ide(); - - const workspacePath = - runMode === "development" - ? assetsRoot - : workspaceFolders?.[0].uri.path ?? null; - - if (workspacePath == null) { - throw new Error( - "Please update defaults from Cursorless workspace or running in debug", - ); - } - const defaultsPath = path.join( - workspacePath, - "packages", - "cheatsheet", - "src", - "lib", - "sampleSpokenFormInfos", - "defaults.json", + getCursorlessRepoRoot(), + "packages/cheatsheet/src/lib/sampleSpokenFormInfos/defaults.json", ); const outputObject = produce(spokenFormInfo, (draft) => { @@ -86,7 +69,7 @@ export async function updateDefaults(spokenFormInfo: CheatsheetInfo) { }); }); - await writeFile(defaultsPath, JSON.stringify(outputObject, null, "\t")); + await writeFile(defaultsPath, JSON.stringify(outputObject, null, 2) + "\n"); } // FIXME: Stop duplicating these types once we have #945 diff --git a/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.test.ts b/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.test.ts new file mode 100644 index 0000000000..bdfd6b0954 --- /dev/null +++ b/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.test.ts @@ -0,0 +1,35 @@ +import assert from "node:assert"; +import { CustomSpokenFormGeneratorImpl } from "./CustomSpokenFormGeneratorImpl"; +import { asyncSafety } from "@cursorless/common"; + +suite("CustomSpokenFormGeneratorImpl", async function () { + test( + "glyph", + asyncSafety(async () => { + const generator = new CustomSpokenFormGeneratorImpl({ + async getSpokenFormEntries() { + return [ + { + type: "complexScopeTypeType", + id: "glyph", + spokenForms: ["foo"], + }, + ]; + }, + onDidChange: () => ({ dispose() {} }), + }); + + await generator.customSpokenFormsInitialized; + + const spokenForm = generator.scopeTypeToSpokenForm({ + type: "glyph", + character: "a", + }); + + assert.deepStrictEqual(spokenForm, { + type: "success", + spokenForms: ["foo air"], + }); + }), + ); +}); diff --git a/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.ts b/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.ts index 481a69818d..a572d12b83 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.ts @@ -21,8 +21,15 @@ export class CustomSpokenFormGeneratorImpl private spokenFormGenerator: SpokenFormGenerator; private disposable: Disposable; + /** + * A promise that resolves when the custom spoken forms have been loaded. + */ + public readonly customSpokenFormsInitialized: Promise; + constructor(talonSpokenForms: TalonSpokenForms) { this.customSpokenForms = new CustomSpokenForms(talonSpokenForms); + this.customSpokenFormsInitialized = + this.customSpokenForms.customSpokenFormsInitialized; this.spokenFormGenerator = new SpokenFormGenerator( this.customSpokenForms.spokenFormMap, ); diff --git a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts index 05b22e0e5f..6dba6a8f65 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts @@ -208,6 +208,11 @@ export class PrimitiveTargetSpokenFormGenerator { switch (scopeType.type) { case "oneOf": throw new NoSpokenFormError(`Scope type '${scopeType.type}'`); + case "glyph": + return [ + this.spokenFormMap.complexScopeTypeType.glyph, + characterToSpokenForm(scopeType.character), + ]; case "surroundingPair": { const pair = this.spokenFormMap.pairedDelimiter[scopeType.delimiter]; if (scopeType.forceDirection != null) { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/RegexScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/RegexScopeHandler.ts index 6b17995529..a8bb130540 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/RegexScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/RegexScopeHandler.ts @@ -1,5 +1,11 @@ -import { CustomRegexScopeType, Direction, ScopeType } from "@cursorless/common"; +import { + CustomRegexScopeType, + Direction, + GlyphScopeType, + ScopeType, +} from "@cursorless/common"; import { imap } from "itertools"; +import { escapeRegExp } from "lodash"; import { NestedScopeHandler } from "./NestedScopeHandler"; import { ScopeHandlerFactory } from "./ScopeHandlerFactory"; import { generateMatchesInRange } from "../../../util/getMatchesInRange"; @@ -62,3 +68,17 @@ export class CustomRegexScopeHandler extends RegexStageBase { super(scopeHandlerFactory, scopeType, languageId); } } + +export class GlyphScopeHandler extends RegexStageBase { + get regex() { + return new RegExp(escapeRegExp(this.scopeType.character), "gui"); + } + + constructor( + scopeHandlerFactory: ScopeHandlerFactory, + readonly scopeType: GlyphScopeType, + languageId: string, + ) { + super(scopeHandlerFactory, scopeType, languageId); + } +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts index b9a87fd865..25c3575553 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -1,18 +1,21 @@ import type { ScopeType } from "@cursorless/common"; +import { LanguageDefinitions } from "../../../languages/LanguageDefinitions"; import { CharacterScopeHandler } from "./CharacterScopeHandler"; -import { CustomRegexScopeHandler } from "./RegexScopeHandler"; import { DocumentScopeHandler } from "./DocumentScopeHandler"; import { IdentifierScopeHandler } from "./IdentifierScopeHandler"; import { LineScopeHandler } from "./LineScopeHandler"; -import { NonWhitespaceSequenceScopeHandler } from "./RegexScopeHandler"; import { OneOfScopeHandler } from "./OneOfScopeHandler"; import { ParagraphScopeHandler } from "./ParagraphScopeHandler"; +import { + CustomRegexScopeHandler, + GlyphScopeHandler, + NonWhitespaceSequenceScopeHandler, + UrlScopeHandler, +} from "./RegexScopeHandler"; import { ScopeHandlerFactory } from "./ScopeHandlerFactory"; import { SentenceScopeHandler } from "./SentenceScopeHandler/SentenceScopeHandler"; import { TokenScopeHandler } from "./TokenScopeHandler"; -import { UrlScopeHandler } from "./RegexScopeHandler"; import { WordScopeHandler } from "./WordScopeHandler/WordScopeHandler"; -import { LanguageDefinitions } from "../../../languages/LanguageDefinitions"; import type { CustomScopeType, ScopeHandler } from "./scopeHandler.types"; /** @@ -70,6 +73,8 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { return new UrlScopeHandler(this, scopeType, languageId); case "customRegex": return new CustomRegexScopeHandler(this, scopeType, languageId); + case "glyph": + return new GlyphScopeHandler(this, scopeType, languageId); case "custom": return scopeType.scopeHandler; case "instance": diff --git a/packages/cursorless-engine/src/scopeProviders/ScopeInfoProvider.ts b/packages/cursorless-engine/src/scopeProviders/ScopeInfoProvider.ts index 9a697a48aa..f183ee1f3d 100644 --- a/packages/cursorless-engine/src/scopeProviders/ScopeInfoProvider.ts +++ b/packages/cursorless-engine/src/scopeProviders/ScopeInfoProvider.ts @@ -173,6 +173,7 @@ function isLanguageSpecific(scopeType: ScopeType): boolean { case "notebookCell": case "surroundingPair": case "customRegex": + case "glyph": return false; case "oneOf": diff --git a/packages/cursorless-engine/src/scopeProviders/TalonSpokenForms.ts b/packages/cursorless-engine/src/scopeProviders/TalonSpokenForms.ts index a6331be5d2..c3c10dfd4a 100644 --- a/packages/cursorless-engine/src/scopeProviders/TalonSpokenForms.ts +++ b/packages/cursorless-engine/src/scopeProviders/TalonSpokenForms.ts @@ -19,6 +19,7 @@ export interface TalonSpokenForms { */ export const SUPPORTED_ENTRY_TYPES = [ "simpleScopeTypeType", + "complexScopeTypeType", "customRegex", "pairedDelimiter", ] as const; diff --git a/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts b/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts index 28ff5e3c8f..4e650bdee2 100644 --- a/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts +++ b/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts @@ -34,13 +34,17 @@ export class CustomSpokenForms { private disposable: Disposable; private notifier = new Notifier(); + /** + * A promise that resolves when the custom spoken forms have been loaded. + */ + public readonly customSpokenFormsInitialized: Promise; + private spokenFormMap_: Writable = { ...defaultSpokenFormMap }; get spokenFormMap(): SpokenFormMap { return this.spokenFormMap_; } - private customSpokenFormsInitialized_ = false; private needsInitialTalonUpdate_: boolean | undefined; /** @@ -51,21 +55,12 @@ export class CustomSpokenForms { return this.needsInitialTalonUpdate_; } - /** - * Whether the custom spoken forms have been initialized. If `false`, the - * default spoken forms are currently being used while the custom spoken forms - * are being loaded. - */ - get customSpokenFormsInitialized() { - return this.customSpokenFormsInitialized_; - } - constructor(private talonSpokenForms: TalonSpokenForms) { this.disposable = talonSpokenForms.onDidChange(() => this.updateSpokenFormMaps(), ); - this.updateSpokenFormMaps(); + this.customSpokenFormsInitialized = this.updateSpokenFormMaps(); } /** @@ -98,10 +93,9 @@ export class CustomSpokenForms { } this.spokenFormMap_ = { ...defaultSpokenFormMap }; - this.customSpokenFormsInitialized_ = false; this.notifier.notifyListeners(); - return; + throw err; } for (const entryType of SUPPORTED_ENTRY_TYPES) { @@ -117,7 +111,6 @@ export class CustomSpokenForms { ); } - this.customSpokenFormsInitialized_ = true; this.notifier.notifyListeners(); } @@ -175,7 +168,7 @@ function updateEntriesForType( defaultSpokenForms, spokenForms: customSpokenForms, requiresTalonUpdate: false, - isCustom: isEqual(defaultSpokenForms, customSpokenForms), + isCustom: !isEqual(defaultSpokenForms, customSpokenForms), isPrivate, }; } diff --git a/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts b/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts index 06a52eae60..e1d604fce3 100644 --- a/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts +++ b/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts @@ -14,6 +14,7 @@ import { export interface SpokenFormMapKeyTypes { pairedDelimiter: SpeakableSurroundingPairName; simpleScopeTypeType: SimpleScopeTypeType; + complexScopeTypeType: "glyph"; surroundingPairForceDirection: "left" | "right"; /** diff --git a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts index 15b4e5ee2b..6b94ec67ba 100644 --- a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts +++ b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts @@ -100,6 +100,9 @@ export const defaultSpokenFormMapCore: DefaultSpokenFormMapDefinition = { string: isPrivate("parse tree string"), ["private.switchStatementSubject"]: isPrivate("subject"), }, + complexScopeTypeType: { + glyph: "glyph", + }, surroundingPairForceDirection: { left: "left", diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearEveryGlyphAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearEveryGlyphAir.yml new file mode 100644 index 0000000000..c79ff8bef0 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearEveryGlyphAir.yml @@ -0,0 +1,25 @@ +languageId: plaintext +command: + version: 6 + spokenForm: change every glyph air + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: glyph, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: abc$!4123a + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: bc$!4123 + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearFinalGlyphAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearFinalGlyphAir.yml new file mode 100644 index 0000000000..782322b77f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearFinalGlyphAir.yml @@ -0,0 +1,25 @@ +languageId: plaintext +command: + version: 6 + spokenForm: change last glyph air + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: ordinalScope + scopeType: {type: glyph, character: a} + start: -1 + length: 1 + usePrePhraseSnapshot: true +initialState: + documentContents: abc$!4123a + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: abc$!4123 + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearGlyphAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearGlyphAir.yml new file mode 100644 index 0000000000..151edb4d22 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearGlyphAir.yml @@ -0,0 +1,23 @@ +languageId: plaintext +command: + version: 6 + spokenForm: change glyph air + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: glyph, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: abc$!4123a + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: bc$!4123a + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearNextGlyphBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearNextGlyphBat.yml new file mode 100644 index 0000000000..4185b97b45 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearNextGlyphBat.yml @@ -0,0 +1,26 @@ +languageId: plaintext +command: + version: 6 + spokenForm: change next glyph bat + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: glyph, character: b} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: aB + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: a + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearNextGlyphDollar.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearNextGlyphDollar.yml new file mode 100644 index 0000000000..b6fdc326d9 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearNextGlyphDollar.yml @@ -0,0 +1,26 @@ +languageId: plaintext +command: + version: 6 + spokenForm: change next glyph dollar + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: glyph, character: $} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: abc$!4123a + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: abc!4123a + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearNextGlyphOnyx.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearNextGlyphOnyx.yml new file mode 100644 index 0000000000..84e35bc89d --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/scope/glyph/clearNextGlyphOnyx.yml @@ -0,0 +1,27 @@ +languageId: plaintext +command: + version: 6 + spokenForm: clear next glyph onyx + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: glyph, character: å} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +spokenFormError: Unknown character 'å' +initialState: + documentContents: abå + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: ab + selections: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2}