diff --git a/packages/cursorless-engine/src/index.ts b/packages/cursorless-engine/src/index.ts index 85ec9c86fb..0fde5045ea 100644 --- a/packages/cursorless-engine/src/index.ts +++ b/packages/cursorless-engine/src/index.ts @@ -15,4 +15,5 @@ export * from "./spokenForms/defaultSpokenFormMap"; export * from "./testUtil/extractTargetKeys"; export * from "./testUtil/plainObjectToTarget"; export * from "./util/getPartialTargetDescriptors"; +export * from "./util/getPrimitiveTargets"; export * from "./util/grammarHelpers"; diff --git a/packages/cursorless-tutorial/src/TutorialImpl.ts b/packages/cursorless-tutorial/src/TutorialImpl.ts index bb6ddd8a3a..65d9cc5bd0 100644 --- a/packages/cursorless-tutorial/src/TutorialImpl.ts +++ b/packages/cursorless-tutorial/src/TutorialImpl.ts @@ -2,6 +2,7 @@ import { CharacterRange, Debouncer, Disposable, + Hats, HatTokenMap, IDE, Notifier, @@ -68,6 +69,7 @@ export class TutorialImpl implements Tutorial, CommandRunnerDecorator { private hatTokenMap: HatTokenMap, private customSpokenFormGenerator: CustomSpokenFormGenerator, private contentProvider: TutorialContentProvider, + private hats: Hats, ) { this.setupStep = this.setupStep.bind(this); this.reparseCurrentTutorial = this.reparseCurrentTutorial.bind(this); @@ -170,6 +172,7 @@ export class TutorialImpl implements Tutorial, CommandRunnerDecorator { this.customSpokenFormGenerator, this.getRawTutorial(tutorialId), this.ide.keyValueStore, + this.hats, ); this.currentTutorial = tutorialContent; @@ -204,6 +207,7 @@ export class TutorialImpl implements Tutorial, CommandRunnerDecorator { this.customSpokenFormGenerator, this.getRawTutorial(tutorialId), this.ide.keyValueStore, + this.hats, ); this.currentTutorial = tutorialContent; diff --git a/packages/cursorless-tutorial/src/TutorialStepParser.ts b/packages/cursorless-tutorial/src/TutorialStepParser.ts index a2690a99c1..39019f51be 100644 --- a/packages/cursorless-tutorial/src/TutorialStepParser.ts +++ b/packages/cursorless-tutorial/src/TutorialStepParser.ts @@ -1,4 +1,5 @@ import { + Hats, TutorialContentProvider, TutorialId, TutorialStepFragment, @@ -41,6 +42,7 @@ export class TutorialStepParser { contentProvider: TutorialContentProvider, tutorialId: TutorialId, customSpokenFormGenerator: CustomSpokenFormGenerator, + hats: Hats, ) { this.parseTutorialStep = this.parseTutorialStep.bind(this); @@ -48,6 +50,7 @@ export class TutorialStepParser { contentProvider, tutorialId, customSpokenFormGenerator, + hats, ); const actionParser = new ActionComponentParser(customSpokenFormGenerator); diff --git a/packages/cursorless-tutorial/src/loadTutorial.ts b/packages/cursorless-tutorial/src/loadTutorial.ts index 5652947207..601d7d5410 100644 --- a/packages/cursorless-tutorial/src/loadTutorial.ts +++ b/packages/cursorless-tutorial/src/loadTutorial.ts @@ -1,4 +1,5 @@ import { + Hats, RawTutorialContent, TutorialContentProvider, TutorialId, @@ -16,11 +17,13 @@ export async function loadTutorial( customSpokenFormGenerator: CustomSpokenFormGenerator, rawContent: RawTutorialContent, keyValueStore: KeyValueStore, + hats: Hats, ) { const parser = new TutorialStepParser( contentProvider, tutorialId, customSpokenFormGenerator, + hats, ); let tutorialContent: TutorialContent; diff --git a/packages/cursorless-tutorial/src/stepComponentParsers/CursorlessCommandComponentParser.ts b/packages/cursorless-tutorial/src/stepComponentParsers/CursorlessCommandComponentParser.ts index 6040107581..e89a878268 100644 --- a/packages/cursorless-tutorial/src/stepComponentParsers/CursorlessCommandComponentParser.ts +++ b/packages/cursorless-tutorial/src/stepComponentParsers/CursorlessCommandComponentParser.ts @@ -1,14 +1,20 @@ import { CommandComplete, + getKey, + Hats, + splitKey, TutorialContentProvider, TutorialId, } from "@cursorless/common"; import { CustomSpokenFormGenerator, canonicalizeAndValidateCommand, + getPartialTargetDescriptors, + transformPartialPrimitiveTargets, } from "@cursorless/cursorless-engine"; import { TutorialError } from "../TutorialError"; import { StepComponent, StepComponentParser } from "../types/StepComponent"; +import { cloneDeep, mapKeys } from "lodash-es"; /** * Parses components of the form `{command:takeNear.yml}`. The argument @@ -19,6 +25,7 @@ export class CursorlessCommandComponentParser implements StepComponentParser { private contentProvider: TutorialContentProvider, private tutorialId: TutorialId, private customSpokenFormGenerator: CustomSpokenFormGenerator, + private hats: Hats, ) {} async parse(arg: string): Promise { @@ -26,7 +33,40 @@ export class CursorlessCommandComponentParser implements StepComponentParser { this.tutorialId, arg, ); - const command = canonicalizeAndValidateCommand(fixture.command); + const command = cloneDeep(canonicalizeAndValidateCommand(fixture.command)); + + transformPartialPrimitiveTargets( + getPartialTargetDescriptors(command.action), + (target) => { + if (target.mark?.type !== "decoratedSymbol") { + return target; + } + + const color = target.mark.symbolColor; + + if (this.hats.enabledHatStyles[color] === undefined) { + target.mark.symbolColor = Object.keys(this.hats.enabledHatStyles)[0]; + } + + return target; + }, + ); + + if (fixture.initialState.marks != null) { + fixture.initialState.marks = mapKeys( + fixture.initialState.marks, + (_value, key) => { + const { hatStyle, character } = splitKey(key); + if (this.hats.enabledHatStyles[hatStyle] === undefined) { + return getKey( + Object.keys(this.hats.enabledHatStyles)[0], + character, + ); + } + return key; + }, + ); + } return { initialState: fixture.initialState, diff --git a/packages/cursorless-vscode/src/createTutorial.ts b/packages/cursorless-vscode/src/createTutorial.ts index 1ce5f1d954..14de06751e 100644 --- a/packages/cursorless-vscode/src/createTutorial.ts +++ b/packages/cursorless-vscode/src/createTutorial.ts @@ -1,4 +1,4 @@ -import { HatTokenMap, IDE } from "@cursorless/common"; +import { Hats, HatTokenMap, IDE } from "@cursorless/common"; import { CommandRunnerDecorator, CustomSpokenFormGenerator, @@ -21,6 +21,7 @@ export function createTutorial( ) => void, hatTokenMap: HatTokenMap, customSpokenFormGenerator: CustomSpokenFormGenerator, + hats: Hats, ) { const contentProvider = new FileSystemTutorialContentProvider(ide.assetsRoot); @@ -29,6 +30,7 @@ export function createTutorial( hatTokenMap, customSpokenFormGenerator, contentProvider, + hats, ); ide.disposeOnExit(tutorial); addCommandRunnerDecorator(tutorial); diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index ffefeed17a..fa2f01a11a 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -176,6 +176,7 @@ export async function activate( addCommandRunnerDecorator, hatTokenMap, customSpokenFormGenerator, + hats, ); registerCommands(