Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cursorless tutorial ced #2131

Closed
Closed
Changes from 1 commit
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
466fe64
Initial tutorial work
pokey Dec 7, 2023
617ea7c
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Dec 7, 2023
ef66688
Update spoken forms
pokey Dec 7, 2023
ae958d3
Upgrade commands
pokey Dec 7, 2023
1b4cae3
Fix extension tests
pokey Dec 7, 2023
bb303aa
whoops
pokey Dec 7, 2023
6ecaf87
Merge branch 'main' into cursorless-tutorial
pokey Dec 12, 2023
016e4d8
basic communication between talon and the extension for the tutorial
Dec 13, 2023
f761306
initial way to populate a window even if not really the right way
Dec 13, 2023
d4b8aa6
use the injected ide
Dec 19, 2023
9673f52
remove dependencies unneeded now
Dec 19, 2023
8b95e02
moved tutorial to a class
Dec 20, 2023
b0e2f8c
Merge remote-tracking branch 'upstream/main' into cursorless-tutorial…
Dec 21, 2023
25be0ca
start parsing things from the extension side
Jan 2, 2024
a7c7fa2
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jan 2, 2024
203bdd2
refactor and parse literalStep
Jan 4, 2024
1bbfe44
finished converting all the spoken forms
Jan 4, 2024
f3a3bf3
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jan 4, 2024
1436353
document functions and use a tutorial directory
Jan 5, 2024
43734e7
initial working version of the tutorial
Jan 5, 2024
171abdd
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jan 5, 2024
cd5bfc6
tweaks
pokey Jan 17, 2024
e022e69
change function
pokey Jan 17, 2024
e0f3c02
more cleanup
pokey Jan 17, 2024
142ec58
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jan 17, 2024
7ed3aac
fix
pokey Jan 17, 2024
a5ed0ff
More cleanup
pokey Jan 17, 2024
f789d24
More cleanup
pokey Jan 17, 2024
f607f16
Tweak imports
pokey Jan 17, 2024
01fc505
More tweaks# Please enter the commit message for your changes. Lines …
pokey Jan 17, 2024
af491e9
More cleanup
pokey Jan 17, 2024
06025d4
more tweaks
pokey Jan 17, 2024
55b596d
Remove comment
pokey Jan 17, 2024
6e101de
Bugfixes
pokey Jan 17, 2024
0e1cf6b
More fixes
pokey Jan 17, 2024
179098b
Merge branch 'main' into pr/saidelike/2131
pokey Feb 23, 2024
46be1bd
run meta-updater
pokey Feb 23, 2024
20bd5ed
Initial cursorless-vscode-tutorial scaffolding
pokey Feb 23, 2024
ea0481c
cursorless-vscode-tutorial => cursorless-vscode-tutorial-webview
pokey Feb 23, 2024
47fd057
more tutorial hacking
pokey Feb 28, 2024
69bd66b
Tweak package.json
pokey Mar 10, 2024
e9d8dbe
Update adding-a-new-package.md (#2247)
pokey Feb 23, 2024
5cbc8e1
bump pnpm => 8.15.3 (#2248)
pokey Feb 24, 2024
6dc5a2d
Update adding-a-new-package.md (#2249)
pokey Feb 24, 2024
5cd2c12
{grand, every} rephrasings for clarity and consistency (#2250)
jmegner Feb 25, 2024
ed7ae67
Update adding-a-new-package.md (#2255)
pokey Feb 27, 2024
401cb10
Get actual js from our webview package
pokey Mar 10, 2024
d5dddff
Initial React scaffolding
pokey Mar 10, 2024
ff3f0cd
Fix tsconfig
pokey Mar 10, 2024
419d830
more cleanup
pokey Mar 10, 2024
571a979
fix:meta
pokey Mar 10, 2024
2f1d786
more cleanup
pokey Mar 10, 2024
dce7d96
Merge branch 'main' into pr/saidelike/2131
pokey Mar 15, 2024
db75743
Add tailwind
pokey Mar 15, 2024
4d63d67
Let VscodeTutorial own tutorial state
pokey Mar 15, 2024
04c5653
Clean up some TutorialImpl stuff
pokey Mar 15, 2024
b59cccb
More PR feedback
pokey Mar 15, 2024
ac51e1f
Initial step content and step init code connected to webview
pokey Mar 15, 2024
26a2cd9
tweaks
pokey Mar 16, 2024
1f07077
tweaks
pokey Mar 26, 2024
0dbbfcd
some more tweaks
pokey Mar 27, 2024
32ab372
Merge branch 'main' into pr/saidelike/2131
pokey Mar 27, 2024
c9f56b3
more tweaks
pokey Mar 28, 2024
e8a66f8
Merge branch 'main' into pr/saidelike/2131
pokey Mar 28, 2024
ad8a152
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Mar 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] authored Jan 2, 2024

Verified

This commit was signed with the committer’s verified signature.
commit a7c7fa2a605ea4e79a20414a8cb3e71941a63a98
65 changes: 35 additions & 30 deletions cursorless-talon/src/tutorial.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import json
import re
from pathlib import Path
from typing import Callable

from talon import actions, app

from .get_action_spoken_form import lookup_action


# {literalStep:...}
# "To see all available scopes, use the command {literalStep:cursorless help}, and look at the Scopes section."
def process_literal_step(argument: str):
return f"<cmd@{argument}/>"


# {action:...}
# "Say {action:setSelectionAfter} to place the cursor after a target: {step:postLook.yml}",
# "Say {action:clearAndSetSelection} to delete a word and move your cursor to where it used to be: {step:clearTrap.yml}",
@@ -20,16 +19,18 @@ def process_action(argument: str):
_, spoken_form = lookup_action(argument)
return f'<*"{spoken_form}"/>'


# {scopeType:...}
# "We can also use {scopeType:line} to refer to the line containing our cursor: {step:takeLine.yml}"
# "Cursorless tries its best to keep your commands short. In the following command, we just say {scopeType:string} once, but cursorless infers that both targets are strings: {step:swapStringAirWithWhale.yml}"
def process_scope_type(argument: str):
#_, spoken_form = lookup_scope_type(argument)
#return f'<*"{spoken_form}"/>'
# _, spoken_form = lookup_scope_type(argument)
# return f'<*"{spoken_form}"/>'
return f'<*"SCOPETYPE_{argument}"/>'


# "When editing code, we often think in terms of statements, functions, etc. Let's clone a statement: {step:cloneStateInk.yml}",
#
#
# this builds a dictionary which has keys and values (each one is a spoken form)
# each value is built by calling the function with the argument being passed
# eg interpolation_processor_map["step"]()
@@ -41,50 +42,54 @@ def process_scope_type(argument: str):
# hardcoded list of default spoken form for an action (not yet the customized one)
"action": process_action,
# scopeTypeType == "line", etc.
# generator.processScopeType({type: scopeTypeType})
# generator.processScopeType({type: scopeTypeType})
"scopeType": process_scope_type,
}

# TODO all the above will be deleted


def step_callback(x):
print(f"step_callback8: {x}")
yamlFilename = tutorial_content['yamlFilenames'][x]
yamlFilename = tutorial_content["yamlFilenames"][x]
if yamlFilename:
response = actions.user.private_cursorless_run_rpc_command_get(
"cursorless.tutorial.setupStep",
{
"version": 0,
"tutorialName": "unit-2-basic-coding",
"yamlFilename": yamlFilename
},
)
actions.user.private_cursorless_run_rpc_command_get(
"cursorless.tutorial.setupStep",
{
"version": 0,
"tutorialName": "unit-2-basic-coding",
"yamlFilename": yamlFilename,
},
)


tutorial_content = None
def get_basic_coding_walkthrough():


def get_basic_coding_walkthrough():
global tutorial_content
print("get_basic_coding_walkthrough start")
tutorial_content = actions.user.private_cursorless_run_rpc_command_get(
"cursorless.tutorial.getContent",
{
"version": 0,
"tutorialName": "unit-2-basic-coding"
},
{"version": 0, "tutorialName": "unit-2-basic-coding"},
)
print(f"{tutorial_content=}")
walkthrough_steps = []
for content in tutorial_content['content']:
walkthrough_steps.append(actions.user.hud_create_walkthrough_step(
content=content,
restore_callback=step_callback,
modes=["command"],
app="Visual Studio Code", # Windows
# app="Code", # OS X?
context_hint="Please open VSCode and enter command mode"
))
for content in tutorial_content["content"]:
walkthrough_steps.append(
actions.user.hud_create_walkthrough_step(
content=content,
restore_callback=step_callback,
modes=["command"],
app="Visual Studio Code", # Windows
# app="Code", # OS X?
context_hint="Please open VSCode and enter command mode",
)
)
print("get_basic_coding_walkthrough end")
return walkthrough_steps


# this is adding the menu to the hud
# by adding a list of HudWalkThroughStep
def on_ready():
81 changes: 48 additions & 33 deletions packages/cursorless-engine/src/core/Tutorial.ts
Original file line number Diff line number Diff line change
@@ -9,14 +9,13 @@ import path from "path";
import * as yaml from "js-yaml";
import { promises as fsp } from "node:fs";

import { SpokenForm, SpokenFormSuccess, TestCaseFixture } from "@cursorless/common";
import { Dictionary } from "lodash";
import { SpokenFormSuccess, TestCaseFixture } from "@cursorless/common";
import { ide } from "../singletons/ide.singleton";
import { HatTokenMapImpl } from "./HatTokenMapImpl";
import { CustomSpokenFormGeneratorImpl } from "../generateSpokenForm/CustomSpokenFormGeneratorImpl";
import { canonicalizeAndValidateCommand } from "./commandVersionUpgrades/canonicalizeAndValidateCommand";

const fs = require('node:fs');
const fs = require("node:fs");

// TODO use a relative path but I'm not sure where these source files are at running time
// and CWD is C:\Users\User\AppData\Local\Programs\Microsoft VS Code\
@@ -83,94 +82,110 @@ export class Tutorial {
}

async getContent({ version, tutorialName }: TutorialGetContentArg) {
console.log("getContent(){ version, tutorialName, yamlFilename }: TutorialSetupStepArg", tutorialName);
console.log(
"getContent(){ version, tutorialName, yamlFilename }: TutorialSetupStepArg",
tutorialName,
);
if (version !== 0) {
throw new Error(`Unsupported tutorial api version: ${version}`);
}

const tutorialDir = path.join(tutorialRootDir, tutorialName);
if (!fs.existsSync(tutorialDir)) {
throw new Error(`Invalid tutorial name: ${tutorialName}`);
}
throw new Error(`Invalid tutorial name: ${tutorialName}`);
}

const scriptFile = path.join(tutorialDir, "script.json");
if (!fs.existsSync(scriptFile)) {
throw new Error(`Can't file script file: ${scriptFile} in tutorial name: ${tutorialName}`);
throw new Error(
`Can't file script file: ${scriptFile} in tutorial name: ${tutorialName}`,
);
}
const buffer = await fsp.readFile(scriptFile);
const contentList = JSON.parse(buffer.toString());
console.log(contentList);

// this is trying to catch occurrences of things like "{step:cloneStateInk.yml}"
const re = /\{(\w+):([^}]+)\}/g;
var m;
var response : TutorialGetContentResponse = {

let m;
const response: TutorialGetContentResponse = {
version: 0,
content: [],
yamlFilenames: [],
};
// we need to replace the {...} with the right content
for (var content of contentList) {
var yamlFilename = "";
for (let content of contentList) {
let yamlFilename = "";
do {
m = re.exec(content);
if (m) {
const name = m[1];
const arg = m[2];
console.log(name, arg);
if (name === "step")
{
if (name === "step") {
const tutorialDir = path.join(tutorialRootDir, tutorialName);
if (!fs.existsSync(tutorialDir)) {
throw new Error(`Invalid tutorial name: ${tutorialName}`);
}
const yamlFile = path.join(tutorialDir, arg)
throw new Error(`Invalid tutorial name: ${tutorialName}`);
}

const yamlFile = path.join(tutorialDir, arg);
if (!fs.existsSync(yamlFile)) {
throw new Error(`Can't file yaml file: ${yamlFile} in tutorial name: ${tutorialName}`);
throw new Error(
`Can't file yaml file: ${yamlFile} in tutorial name: ${tutorialName}`,
);
}
yamlFilename = arg;

const buffer = await fsp.readFile(yamlFile);
const fixture = yaml.load(buffer.toString()) as TestCaseFixture;

// command to be said for moving to the next step
const spoken_form = this.customSpokenFormGenerator.commandToSpokenForm(
canonicalizeAndValidateCommand(fixture.command)
) as SpokenFormSuccess;
const spoken_form =
this.customSpokenFormGenerator.commandToSpokenForm(
canonicalizeAndValidateCommand(fixture.command),
) as SpokenFormSuccess;
console.log("\t", spoken_form.spokenForms[0]);
content = content.replace(m[0], `<cmd@${spoken_form.spokenForms[0]}/>`)
content = content.replace(
m[0],
`<cmd@${spoken_form.spokenForms[0]}/>`,
);
}
}
} while (m);
response.yamlFilenames.push(yamlFilename);
response.content.push(content);
}

// return to the talon side
return response;
}

async setupStep({ version, tutorialName, yamlFilename }: TutorialSetupStepArg) {
async setupStep({
version,
tutorialName,
yamlFilename,
}: TutorialSetupStepArg) {
console.log("setupStep()", tutorialName, yamlFilename);
if (version !== 0) {
throw new Error(`Unsupported tutorial api version: ${version}`);
}

const tutorialDir = path.join(tutorialRootDir, tutorialName);
if (!fs.existsSync(tutorialDir)) {
throw new Error(`Invalid tutorial name: ${tutorialName}`);
throw new Error(`Invalid tutorial name: ${tutorialName}`);
}

// TODO check for directory traversal?
const yamlFile = path.join(tutorialDir, yamlFilename);
if (!fs.existsSync(yamlFile)) {
throw new Error(`Can't file yaml file: ${yamlFile} in tutorial name: ${tutorialName}`);
throw new Error(
`Can't file yaml file: ${yamlFile} in tutorial name: ${tutorialName}`,
);
}
const buffer = await fsp.readFile(yamlFile);
const fixture = yaml.load(buffer.toString()) as TestCaseFixture;

const editor = ide().openUntitledTextDocument({
content: fixture.initialState.documentContents,
language: fixture.languageId,
@@ -181,4 +196,4 @@ export class Tutorial {
// return to the talon side
return true;
}
}
}