Error
+
{state.errorMessage}
{state.requiresTalonUpdate && (
Please{" "}
diff --git a/packages/test-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts b/packages/test-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts
index f540d9d820..b863859549 100644
--- a/packages/test-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts
+++ b/packages/test-vscode-e2e/src/suite/tutorial/tutorial.vscode.test.ts
@@ -30,7 +30,7 @@ suite("tutorial", function () {
);
});
-const BASICS_TUTORIAL_ID = "tutorial-1-basics";
+const INTRODUCTION_TUTORIAL_ID = "1-introduction";
async function runBasicTutorialTest(spyIde: SpyIDE) {
const { hatTokenMap, takeSnapshot, getTutorialWebviewEventLog, vscodeApi } =
@@ -47,7 +47,7 @@ async function runBasicTutorialTest(spyIde: SpyIDE) {
const tutorialDirectory = path.join(
getRecordedTestsDirPath(),
"tutorial",
- BASICS_TUTORIAL_ID,
+ INTRODUCTION_TUTORIAL_ID,
);
const fixtures = await Promise.all(
@@ -79,7 +79,7 @@ async function runBasicTutorialTest(spyIde: SpyIDE) {
// Test starting tutorial
await commands.executeCommand(
"cursorless.tutorial.start",
- BASICS_TUTORIAL_ID,
+ INTRODUCTION_TUTORIAL_ID,
);
await checkStepSetup(fixtures[0]);
@@ -100,7 +100,7 @@ async function runBasicTutorialTest(spyIde: SpyIDE) {
data: {
type: "doingTutorial",
hasErrors: false,
- id: "tutorial-1-basics",
+ id: "1-introduction",
stepNumber: 0,
stepContent: [
[
@@ -184,17 +184,21 @@ async function runBasicTutorialTest(spyIde: SpyIDE) {
type: "pickingTutorial",
tutorials: [
{
- id: "tutorial-1-basics",
+ id: "1-introduction",
title: "Introduction",
- version: 0,
stepCount: 11,
currentStep: 1,
},
{
- id: "tutorial-2-basic-coding",
+ id: "2-basic-coding",
title: "Basic coding",
- version: 0,
- stepCount: 11,
+ stepCount: 9,
+ currentStep: 0,
+ },
+ {
+ id: "3-visualization",
+ title: "Visualization",
+ stepCount: 6,
currentStep: 0,
},
],
diff --git a/packages/tool-meta-updater/src/metaUpdater.ts b/packages/tool-meta-updater/src/metaUpdater.ts
index 481de86683..14aa90484b 100644
--- a/packages/tool-meta-updater/src/metaUpdater.ts
+++ b/packages/tool-meta-updater/src/metaUpdater.ts
@@ -9,6 +9,7 @@ import type { ScopeTypeType } from "@cursorless/lib-common";
import {
actionReferenceGroups,
actionReferences,
+ getTutorialsForContext,
graphemeDefaultSpokenForms,
languageReferences,
modifierReferenceGroups,
@@ -43,7 +44,6 @@ import { updateSpokenForms } from "./updateSpokenForms";
import { updateTSConfig } from "./updateTSConfig";
import { updateTSConfigBase } from "./updateTSConfigBase";
import {
- parseTutorialId,
updateTutorialFixtureData,
updateTutorialMdx,
updateTutorialReadmeMdx,
@@ -76,7 +76,7 @@ export const updater = async (workspaceDir: string) => {
const tutorialContentProvider = new FileSystemTutorialContentProvider(
getRecordedTestsDirPath(),
);
- const tutorials = await tutorialContentProvider.loadRawTutorials();
+ const tutorials = getTutorialsForContext("documentation");
return createUpdateOptions({
files: {
@@ -101,15 +101,19 @@ export const updater = async (workspaceDir: string) => {
tutorials,
),
...Object.fromEntries(
- tutorials.flatMap((tutorial) => {
- const { shortId } = parseTutorialId(tutorial.id);
+ tutorials.flatMap((tutorial, index) => {
return [
[
- `${userDir}/tutorial/${shortId}.mdx`,
- updateTutorialMdx.bind(null, tutorial, tutorialContentProvider),
+ `${userDir}/tutorial/${tutorial.id}.mdx`,
+ updateTutorialMdx.bind(
+ null,
+ tutorial,
+ tutorials[index + 1],
+ tutorialContentProvider,
+ ),
],
[
- `${userDir}/tutorial/fixtures/${shortId}.json`,
+ `${userDir}/tutorial/fixtures/${tutorial.id}.json`,
updateTutorialFixtureData.bind(
null,
tutorial,
diff --git a/packages/tool-meta-updater/src/renderRecordedTestVisualizer.ts b/packages/tool-meta-updater/src/renderRecordedTestVisualizer.ts
index ae814ff413..589dac7ea1 100644
--- a/packages/tool-meta-updater/src/renderRecordedTestVisualizer.ts
+++ b/packages/tool-meta-updater/src/renderRecordedTestVisualizer.ts
@@ -6,6 +6,7 @@ import {
getRecordedTestsDirPath,
loadFixture,
} from "@cursorless/lib-node-common";
+import { isAppWebDocs } from "./util/isManifest";
export const recordedTestVisualizerImport = `import { RecordedTestVisualizer, RecordedTestVisualizerOptions, RecordedTestVisualizerProvider } from "@site/src/docs/components/RecordedTestVisualizer";`;
@@ -20,7 +21,7 @@ export function updateRecordedTestFixtureData(
_actual: unknown,
options: FormatPluginFnOptions,
): Promise {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return Promise.resolve(null);
}
if (recordedTestPaths.length === 0) {
diff --git a/packages/tool-meta-updater/src/updateGraphemeDefaultSpokenFormsMd.ts b/packages/tool-meta-updater/src/updateGraphemeDefaultSpokenFormsMd.ts
index e5b6193696..2e992343c3 100644
--- a/packages/tool-meta-updater/src/updateGraphemeDefaultSpokenFormsMd.ts
+++ b/packages/tool-meta-updater/src/updateGraphemeDefaultSpokenFormsMd.ts
@@ -1,4 +1,5 @@
import type { FormatPluginFnOptions } from "@pnpm/meta-updater";
+import { isAppWebDocs } from "./util/isManifest";
const HEADERS = ["Character", "Default spoken form"] as const;
@@ -7,7 +8,7 @@ export function updateGraphemeDefaultSpokenFormsMd(
actual: string | null,
options: FormatPluginFnOptions,
): string | null {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return null;
}
diff --git a/packages/tool-meta-updater/src/updateLanguages.ts b/packages/tool-meta-updater/src/updateLanguages.ts
index b7d3f47faf..4e852f3526 100644
--- a/packages/tool-meta-updater/src/updateLanguages.ts
+++ b/packages/tool-meta-updater/src/updateLanguages.ts
@@ -6,13 +6,14 @@ import {
scopeVisualizerImport,
} from "./renderScopeVisualizerMdx";
import type { ScopeFixtureGroup } from "./scopeFixtureGroups";
+import { isAppWebDocs } from "./util/isManifest";
export function updateLanguagesReadmeMd(
languageIds: LanguageId[],
_actual: string | null,
options: FormatPluginFnOptions,
): string | null {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return null;
}
@@ -37,7 +38,7 @@ export function updateLanguageMdx(
actual: string | null,
options: FormatPluginFnOptions,
): string | null {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return null;
}
diff --git a/packages/tool-meta-updater/src/updatePackageJson.ts b/packages/tool-meta-updater/src/updatePackageJson.ts
index 0968cd62ee..e009a5841e 100644
--- a/packages/tool-meta-updater/src/updatePackageJson.ts
+++ b/packages/tool-meta-updater/src/updatePackageJson.ts
@@ -4,6 +4,12 @@ import type { PackageJson } from "type-fest";
import { omitByDeep } from "@cursorless/lib-common";
import type { Context } from "./Context";
import { getCursorlessVscodeFields } from "./util/getCursorlessVscodeFields";
+import {
+ isAppNeovim,
+ isAppVscode,
+ isAppWebDocs,
+ isTestRunner,
+} from "./util/isManifest";
const LIB_ENTRY_POINT = "./src/index.ts";
@@ -30,19 +36,19 @@ export function updatePackageJson(
const isRoot = packageDir === workspaceDir;
const isLib = !isRoot && !input.private;
- const isCursorlessVscode = input.name === "@cursorless/app-vscode";
- const isCursorlessNeovim = input.name === "@cursorless/app-neovim";
- const isCursorlessOrgDocs = input.name === "@cursorless/app-web-docs";
- const isCursorlessTestRunner = input.name === "@cursorless/test-runner";
+ const isCursorlessVscode = isAppVscode(options);
+ const isCursorlessNeovim = isAppNeovim(options);
+ const isCursorlessOrgDocs = isAppWebDocs(options);
+ const isCursorlessTestRunner = isTestRunner(options);
if (input.description == null || input.description === "") {
throw new Error(`No description found in ${packageDir}/package.json`);
}
const name =
- isRoot || input.name?.startsWith("@cursorless/")
- ? input.name
- : `@cursorless/${input.name}`;
+ isRoot || options.manifest.name?.startsWith("@cursorless/")
+ ? options.manifest.name
+ : `@cursorless/${options.manifest.name}`;
if (isLib) {
const exports =
diff --git a/packages/tool-meta-updater/src/updatePairedDelimitersMd.ts b/packages/tool-meta-updater/src/updatePairedDelimitersMd.ts
index 6dba4ea2d4..519d2f4199 100644
--- a/packages/tool-meta-updater/src/updatePairedDelimitersMd.ts
+++ b/packages/tool-meta-updater/src/updatePairedDelimitersMd.ts
@@ -1,6 +1,7 @@
import type { FormatPluginFnOptions } from "@pnpm/meta-updater";
import type { PairedDelimiterReference } from "@cursorless/lib-common";
import { DISABLED_BY_DEFAULT } from "./util/constants";
+import { isAppWebDocs } from "./util/isManifest";
const HEADERS = [
"Default spoken form",
@@ -16,7 +17,7 @@ export function updatePairedDelimitersMd(
actual: string | null,
options: FormatPluginFnOptions,
): string | null {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return null;
}
diff --git a/packages/tool-meta-updater/src/updateReferences.ts b/packages/tool-meta-updater/src/updateReferences.ts
index b0553a0a53..b6f7351759 100644
--- a/packages/tool-meta-updater/src/updateReferences.ts
+++ b/packages/tool-meta-updater/src/updateReferences.ts
@@ -15,6 +15,7 @@ import { cleanId } from "./util/cleanId";
import { DISABLED_BY_DEFAULT } from "./util/constants";
import { formatVariables } from "./util/formatVariables";
import { injectSpokenForm } from "./util/injectSpokenForm";
+import { isAppWebDocs } from "./util/isManifest";
export function updateReferenceReadmeMd(
title: string,
@@ -23,7 +24,7 @@ export function updateReferenceReadmeMd(
actual: string | null,
options: FormatPluginFnOptions,
): string | null {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return null;
}
@@ -79,7 +80,7 @@ export async function updateReferenceMdx(
actual: string | null,
options: FormatPluginFnOptions,
): Promise {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return null;
}
diff --git a/packages/tool-meta-updater/src/updateScopeFixtureData.ts b/packages/tool-meta-updater/src/updateScopeFixtureData.ts
index 828ad3d83a..d3505c4d58 100644
--- a/packages/tool-meta-updater/src/updateScopeFixtureData.ts
+++ b/packages/tool-meta-updater/src/updateScopeFixtureData.ts
@@ -6,6 +6,7 @@ import type {
} from "@cursorless/lib-common";
import type { ScopeTestPath } from "@cursorless/lib-node-common";
import type { ScopeFixtureGroup } from "./scopeFixtureGroups";
+import { isAppWebDocs } from "./util/isManifest";
type FacetValue = ScopeSupportFacet | PlaintextScopeSupportFacet;
@@ -33,7 +34,7 @@ export function updateScopeFixtureData(
_actual: unknown,
options: FormatPluginFnOptions,
): ScopeFixtureData[] | null {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return null;
}
diff --git a/packages/tool-meta-updater/src/updateTutorialMdx.ts b/packages/tool-meta-updater/src/updateTutorialMdx.ts
index ff797a1be7..eedcc43cc6 100644
--- a/packages/tool-meta-updater/src/updateTutorialMdx.ts
+++ b/packages/tool-meta-updater/src/updateTutorialMdx.ts
@@ -1,12 +1,13 @@
import type { FormatPluginFnOptions } from "@pnpm/meta-updater";
import type {
- RawTutorialContent,
+ ResolvedTutorialContent,
TestCaseFixtureLegacy,
TutorialContentProvider,
TutorialId,
} from "@cursorless/lib-common";
import { graphemeDefaultSpokenForms } from "@cursorless/lib-common";
import { recordedTestVisualizerImport } from "./renderRecordedTestVisualizer";
+import { isAppWebDocs } from "./util/isManifest";
const componentRegex = /\{(\w+):([^}]+)\}/gu;
@@ -28,19 +29,14 @@ interface TutorialRecordedTest {
}
export function updateTutorialReadmeMdx(
- tutorials: RawTutorialContent[],
+ tutorials: ResolvedTutorialContent[],
_actual: string | null,
options: FormatPluginFnOptions,
): string | null {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return null;
}
- const enrichedTutorials = tutorials.map((tutorial) => ({
- ...tutorial,
- ...parseTutorialId(tutorial.id),
- }));
-
return [
"# Tutorial",
"",
@@ -48,12 +44,12 @@ export function updateTutorialReadmeMdx(
"",
'To follow along interactively, focus VS Code and say `"cursorless tutorial"`.',
"",
- ...enrichedTutorials
+ ...tutorials
.toSorted((a, b) => a.position - b.position)
.flatMap((tutorial) => [
`## ${tutorial.position}. ${tutorial.title}`,
"",
- `[Start ${tutorial.title}](./${tutorial.shortId}.mdx)`,
+ `[Start ${tutorial.title}](./${tutorial.id}.mdx) - ${tutorial.description}`,
"",
]),
"## Use the interactive tutorial",
@@ -83,22 +79,23 @@ export function updateTutorialReadmeMdx(
}
export async function updateTutorialMdx(
- tutorial: RawTutorialContent,
+ tutorial: ResolvedTutorialContent,
+ nextTutorial: ResolvedTutorialContent | undefined,
contentProvider: TutorialContentProvider,
_actual: string | null,
options: FormatPluginFnOptions,
): Promise {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return null;
}
- const { position, shortId } = parseTutorialId(tutorial.id);
-
const lines = [
recordedTestVisualizerImport,
- `import recordedTests from "./fixtures/${shortId}.json";`,
+ `import recordedTests from "./fixtures/${tutorial.id}.json";`,
"",
- `# ${position}. ${tutorial.title}`,
+ `# ${tutorial.position}. ${tutorial.title}`,
+ "",
+ tutorial.description,
"",
"",
"",
@@ -113,28 +110,29 @@ export async function updateTutorialMdx(
for (const fixtureName of step.fixtureNames) {
lines.push(``, "");
}
- if (step.fixtureNames.length === 0) {
- lines.push(
- ":::note",
-
- 'This step has no visualisation. To get the full experience try the interactive tutorial in VS Code by saying `"cursorless tutorial"`.',
- ":::",
- "",
- );
- }
}
lines.push("", "");
+
+ if (nextTutorial != null) {
+ lines.push(
+ `## Next tutorial: ${nextTutorial.position}. ${nextTutorial.title}`,
+ "",
+ `[Continue to ${nextTutorial.title}](./${nextTutorial.id}.mdx) - ${nextTutorial.description}`,
+ "",
+ );
+ }
+
return lines.join("\n");
}
export async function updateTutorialFixtureData(
- tutorial: RawTutorialContent,
+ tutorial: ResolvedTutorialContent,
contentProvider: TutorialContentProvider,
_actual: unknown,
options: FormatPluginFnOptions,
): Promise {
- if (options.manifest.name !== "@cursorless/app-web-docs") {
+ if (!isAppWebDocs(options)) {
return null;
}
@@ -162,7 +160,7 @@ export async function updateTutorialFixtureData(
async function renderStep(
contentProvider: TutorialContentProvider,
- tutorialId: RawTutorialContent["id"],
+ tutorialId: TutorialId,
rawStep: string,
): Promise {
const fixtureNames: string[] = [];
@@ -205,7 +203,7 @@ async function renderStep(
}
function getTutorialFixtureName(
- tutorialId: RawTutorialContent["id"],
+ tutorialId: TutorialId,
argument: string,
): string {
return `tutorial/${tutorialId}/${argument.replace(/\.ya?ml$/u, "")}`;
@@ -235,18 +233,3 @@ function formatSpokenForm(spokenForm: string): string {
function formatTerm(term: string): string {
return `"${term}"`;
}
-
-export function parseTutorialId(id: TutorialId) {
- const match = id.match(/^tutorial-(?(?\d+)-.+)$/u);
- const position = match?.groups?.number;
- const shortId = match?.groups?.identifier;
-
- if (position == null || shortId == null) {
- throw new Error(`Invalid tutorial fixture id: ${id}`);
- }
-
- return {
- position: Number(position),
- shortId,
- };
-}
diff --git a/packages/tool-meta-updater/src/util/isManifest.ts b/packages/tool-meta-updater/src/util/isManifest.ts
new file mode 100644
index 0000000000..830715d408
--- /dev/null
+++ b/packages/tool-meta-updater/src/util/isManifest.ts
@@ -0,0 +1,17 @@
+import type { FormatPluginFnOptions } from "@pnpm/meta-updater";
+
+export function isAppWebDocs(options: FormatPluginFnOptions): boolean {
+ return options.manifest.name === "@cursorless/app-web-docs";
+}
+
+export function isAppVscode(options: FormatPluginFnOptions): boolean {
+ return options.manifest.name === "@cursorless/app-vscode";
+}
+
+export function isAppNeovim(options: FormatPluginFnOptions): boolean {
+ return options.manifest.name === "@cursorless/app-neovim";
+}
+
+export function isTestRunner(options: FormatPluginFnOptions): boolean {
+ return options.manifest.name === "@cursorless/test-runner";
+}
diff --git a/resources/fixtures/recorded/tutorial/tutorial-1-basics/changeSit.yml b/resources/fixtures/recorded/tutorial/1-introduction/changeSit.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-1-basics/changeSit.yml
rename to resources/fixtures/recorded/tutorial/1-introduction/changeSit.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-1-basics/chuckLineOdd.yml b/resources/fixtures/recorded/tutorial/1-introduction/chuckLineOdd.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-1-basics/chuckLineOdd.yml
rename to resources/fixtures/recorded/tutorial/1-introduction/chuckLineOdd.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-1-basics/chuckTrap.yml b/resources/fixtures/recorded/tutorial/1-introduction/chuckTrap.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-1-basics/chuckTrap.yml
rename to resources/fixtures/recorded/tutorial/1-introduction/chuckTrap.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-1-basics/postAir.yml b/resources/fixtures/recorded/tutorial/1-introduction/postAir.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-1-basics/postAir.yml
rename to resources/fixtures/recorded/tutorial/1-introduction/postAir.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-1-basics/preUrge.yml b/resources/fixtures/recorded/tutorial/1-introduction/preUrge.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-1-basics/preUrge.yml
rename to resources/fixtures/recorded/tutorial/1-introduction/preUrge.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-1-basics/takeBlueSun.yml b/resources/fixtures/recorded/tutorial/1-introduction/takeBlueSun.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-1-basics/takeBlueSun.yml
rename to resources/fixtures/recorded/tutorial/1-introduction/takeBlueSun.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-1-basics/takeCap.yml b/resources/fixtures/recorded/tutorial/1-introduction/takeCap.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-1-basics/takeCap.yml
rename to resources/fixtures/recorded/tutorial/1-introduction/takeCap.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-1-basics/takeHarpPastDrum.yml b/resources/fixtures/recorded/tutorial/1-introduction/takeHarpPastDrum.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-1-basics/takeHarpPastDrum.yml
rename to resources/fixtures/recorded/tutorial/1-introduction/takeHarpPastDrum.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-1-basics/takeLine.yml b/resources/fixtures/recorded/tutorial/1-introduction/takeLine.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-1-basics/takeLine.yml
rename to resources/fixtures/recorded/tutorial/1-introduction/takeLine.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-1-basics/takeNearAndSun.yml b/resources/fixtures/recorded/tutorial/1-introduction/takeNearAndSun.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-1-basics/takeNearAndSun.yml
rename to resources/fixtures/recorded/tutorial/1-introduction/takeNearAndSun.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/bringBlueCapToValueRisk.yml b/resources/fixtures/recorded/tutorial/2-basic-coding/bringBlueCapToValueRisk.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/bringBlueCapToValueRisk.yml
rename to resources/fixtures/recorded/tutorial/2-basic-coding/bringBlueCapToValueRisk.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/bringStateUrge.yml b/resources/fixtures/recorded/tutorial/2-basic-coding/bringStateUrge.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/bringStateUrge.yml
rename to resources/fixtures/recorded/tutorial/2-basic-coding/bringStateUrge.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/chuckArgueBlueVest.yml b/resources/fixtures/recorded/tutorial/2-basic-coding/chuckArgueBlueVest.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/chuckArgueBlueVest.yml
rename to resources/fixtures/recorded/tutorial/2-basic-coding/chuckArgueBlueVest.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/cloneStateInk.yml b/resources/fixtures/recorded/tutorial/2-basic-coding/cloneStateInk.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/cloneStateInk.yml
rename to resources/fixtures/recorded/tutorial/2-basic-coding/cloneStateInk.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/dedentThis.yml b/resources/fixtures/recorded/tutorial/2-basic-coding/dedentThis.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/dedentThis.yml
rename to resources/fixtures/recorded/tutorial/2-basic-coding/dedentThis.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/pourUrge.yml b/resources/fixtures/recorded/tutorial/2-basic-coding/pourUrge.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/pourUrge.yml
rename to resources/fixtures/recorded/tutorial/2-basic-coding/pourUrge.yml
diff --git a/resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/swapStringAirWithWhale.yml b/resources/fixtures/recorded/tutorial/2-basic-coding/swapStringAirWithWhale.yml
similarity index 100%
rename from resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/swapStringAirWithWhale.yml
rename to resources/fixtures/recorded/tutorial/2-basic-coding/swapStringAirWithWhale.yml
diff --git a/resources/fixtures/recorded/tutorial/3-visualization/flashState.yml b/resources/fixtures/recorded/tutorial/3-visualization/flashState.yml
new file mode 100644
index 0000000000..caffafeb04
--- /dev/null
+++ b/resources/fixtures/recorded/tutorial/3-visualization/flashState.yml
@@ -0,0 +1,30 @@
+languageId: typescript
+command:
+ version: 7
+ spokenForm: flash state
+ action:
+ name: flashTargets
+ target:
+ type: primitive
+ modifiers:
+ - type: containingScope
+ scopeType: {type: statement}
+ usePrePhraseSnapshot: false
+initialState:
+ documentContents: const value = 123;
+ selections:
+ - anchor: {line: 0, character: 0}
+ active: {line: 0, character: 0}
+ marks: {}
+finalState:
+ documentContents: const value = 123;
+ selections:
+ - anchor: {line: 0, character: 0}
+ active: {line: 0, character: 0}
+ thatMark:
+ - type: ScopeTypeTarget
+ contentRange:
+ start: {line: 0, character: 0}
+ end: {line: 0, character: 18}
+ isReversed: false
+ hasExplicitRange: true
diff --git a/resources/fixtures/recorded/tutorial/3-visualization/highlightNothing.yml b/resources/fixtures/recorded/tutorial/3-visualization/highlightNothing.yml
new file mode 100644
index 0000000000..eb605690fd
--- /dev/null
+++ b/resources/fixtures/recorded/tutorial/3-visualization/highlightNothing.yml
@@ -0,0 +1,30 @@
+languageId: typescript
+command:
+ version: 7
+ spokenForm: highlight nothing
+ action:
+ name: highlight
+ target:
+ type: primitive
+ mark: {type: nothing}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: const value = 123;
+ selections:
+ - anchor: {line: 0, character: 18}
+ active: {line: 0, character: 18}
+ marks: {}
+ highlights:
+ - ranges:
+ - type: character
+ start: {line: 0, character: 0}
+ end: {line: 0, character: 18}
+finalState:
+ documentContents: const value = 123;
+ selections:
+ - anchor: {line: 0, character: 18}
+ active: {line: 0, character: 18}
+ thatMark: []
+ide:
+ highlights:
+ - ranges: []
diff --git a/resources/fixtures/recorded/tutorial/3-visualization/highlightState.yml b/resources/fixtures/recorded/tutorial/3-visualization/highlightState.yml
new file mode 100644
index 0000000000..c5e4ef8321
--- /dev/null
+++ b/resources/fixtures/recorded/tutorial/3-visualization/highlightState.yml
@@ -0,0 +1,36 @@
+languageId: typescript
+command:
+ version: 7
+ spokenForm: highlight state
+ action:
+ name: highlight
+ target:
+ type: primitive
+ modifiers:
+ - type: containingScope
+ scopeType: {type: statement}
+ usePrePhraseSnapshot: false
+initialState:
+ documentContents: const value = 123;
+ selections:
+ - anchor: {line: 0, character: 0}
+ active: {line: 0, character: 0}
+ marks: {}
+finalState:
+ documentContents: const value = 123;
+ selections:
+ - anchor: {line: 0, character: 0}
+ active: {line: 0, character: 0}
+ thatMark:
+ - type: ScopeTypeTarget
+ contentRange:
+ start: {line: 0, character: 0}
+ end: {line: 0, character: 18}
+ isReversed: false
+ hasExplicitRange: true
+ide:
+ highlights:
+ - ranges:
+ - type: character
+ start: {line: 0, character: 0}
+ end: {line: 0, character: 18}
diff --git a/resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/script.json b/resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/script.json
deleted file mode 100644
index f74eeb45eb..0000000000
--- a/resources/fixtures/recorded/tutorial/tutorial-2-basic-coding/script.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "title": "Basic coding",
- "version": 0,
- "steps": [
- "When editing code, we often think in terms of statements, functions, etc. Let's clone a statement: {command:cloneStateInk.yml}",
- "{scopeType:state} is one of many scopes supported by Cursorless. To see all available scopes, have a look at the Scopes section below, and use the {term:visualize} command to see them live: {visualize:funk}",
- "Say {special:visualizeNothing} to hide the visualization.",
- "Cursorless tries its best to keep your commands short.\nIn the following command, we just say {scopeType:string} once, but Cursorless infers that both targets are strings: {command:swapStringAirWithWhale.yml}",
- "Great. Let's learn a new action. The {action:pour} action lets you start editing a new line below any line on your screen: {command:pourUrge.yml}",
- "Now let's try applying a Cursorless action to the current line: {command:dedentThis.yml}",
- "Code reuse is a fact of life as a programmer. Cursorless makes this easy with the {action:bring} command: {command:bringStateUrge.yml}",
- "{action:bring} also works with two targets just like {action:swap}: {command:bringBlueCapToValueRisk.yml}",
- "Cursorless tries its best to use its knowledge of programming languages to leave you with syntactically valid code.\nNote how it cleans up the comma here: {command:chuckArgueBlueVest.yml}",
- "We introduced a lot of different scopes today. If you're anything like us, you've already forgotten them all.\nThe important thing to remember is that you can always say {special:help} to see a list.",
- "As always, feel free to stick around and play with this file to practice what you've just learned. Happy coding 😊. Say {special:next} to get back home."
- ]
-}