Skip to content

Cursorless tutorial ced #2131

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

Closed
Closed
Show file tree
Hide file tree
Changes from 9 commits
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
30 changes: 30 additions & 0 deletions cursorless-talon/src/get_action_spoken_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from talon import registry

from .actions.actions import ACTION_LIST_NAMES
from .conventions import get_cursorless_list_name


def make_cursorless_list_reverse_look_up(*raw_list_names: str):
return make_list_reverse_look_up(
*[get_cursorless_list_name(raw_list_name) for raw_list_name in raw_list_names]
)


def make_list_reverse_look_up(*list_names: str):
"""
Given a list of talon list names, returns a function that does a reverse
look-up in all lists to find the spoken form for its input.
"""

def return_func(argument: str):
for list_name in list_names:
for spoken_form, value in registry.lists[list_name][-1].items():
if value == argument:
return list_name, spoken_form

raise LookupError(f"Unknown identifier `{argument}`")

return return_func


lookup_action = make_cursorless_list_reverse_look_up(*ACTION_LIST_NAMES)
98 changes: 98 additions & 0 deletions cursorless-talon/src/tutorial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import json
import re
from pathlib import Path
from typing import Callable

import yaml
from talon import actions, app

from .get_action_spoken_form import lookup_action

regex = re.compile(r"\{(\w+):([^}]+)\}")
tutorial_dir = Path(
r"C:\work\tools\voicecoding\cursorless_fork\packages\cursorless-vscode-e2e\src\suite\fixtures\recorded\tutorial\unit-2-basic-coding"
)


def process_literal_step(argument: str):
return f"<cmd@{argument}/>"


def process_action(argument: str):
_, spoken_form = lookup_action(argument)
return f'<*"{spoken_form}"/>'


def process_scope_type(argument: str):
# TODO not sure what we are trying to achieve here
_, spoken_form = lookup_scope_type(argument)
return f'<*"{spoken_form}"/>'


def process_cursorless_command_step(argument: str):
print(f"{argument=}")
step_fixture = yaml.safe_load((tutorial_dir / argument).read_text())
print(f"{step_fixture['command']=}")
result = actions.user.private_cursorless_run_rpc_command_get(
"cursorless.tutorial.create",
{
"version": 0,
"stepFixture": step_fixture,
"yamlFilename": argument,
},
)
print(f"{result=}")
return f"<cmd@{cursorless_command_to_spoken_form(step_fixture['command'])}/>"
# return f"<cmd@{result}/>"


# TODO get this information from the extension
def cursorless_command_to_spoken_form(command: dict[str, str]):
return command["spokenForm"]


interpolation_processor_map: dict[str, Callable[[str], str]] = {
"literalStep": process_literal_step,
"action": process_action,
"scopeType": process_scope_type,
"step": process_cursorless_command_step,
}


def process_tutorial_step(raw: str):
print(f"{raw=}")
current_index = 0
content = ""
for match in regex.finditer(raw):
content += raw[current_index : match.start()]
content += interpolation_processor_map[match.group(1)](match.group(2))
current_index = match.end()
content += raw[current_index : len(raw)]
print(f"{content=}")

return {
"content": content,
"restore_callback": print,
"modes": ["command"],
"app": "Code",
"context_hint": "Please open VSCode and enter command mode",
}


def get_basic_coding_walkthrough():
with open(tutorial_dir / "script.json") as f:
script = json.load(f)

return [
actions.user.hud_create_walkthrough_step(**process_tutorial_step(step))
for step in script
]


def on_ready():
actions.user.hud_add_lazy_walkthrough(
"Cursorless basic coding", get_basic_coding_walkthrough
)


app.register("ready", on_ready)
9 changes: 9 additions & 0 deletions data/playground/tutorial/extra-cloning-a-talon-list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from talon import Context, Module

mod = Module()
ctx = Context()

mod.list("cursorless_walkthrough_list", desc="My tutorial list")
ctx.list["user.cursorless_walkthrough_list"] = {
"spoken form": "whatever",
}
11 changes: 11 additions & 0 deletions data/playground/tutorial/unit-1-basics.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
==================================================
========== ==========
========== Welcome to Cursorless! ==========
========== ==========
========== Let's start using marks ==========
========== ==========
========== so we can navigate around ==========
========== ==========
========== without lifting a finger! ==========
========== ==========
==================================================
13 changes: 13 additions & 0 deletions data/playground/tutorial/unit-2-basic-coding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
def print_color(color, invert=False):
if invert:
print(invert_color(color))
else:
print(color)


def invert_color(color):
if color == "black":
return "white"


print_color("black")
1 change: 1 addition & 0 deletions packages/cursorless-engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"license": "MIT",
"dependencies": {
"@cursorless/common": "workspace:*",
"@cursorless/vscode-common": "workspace:*",
"immer": "^9.0.15",
"immutability-helper": "^3.1.1",
"itertools": "^2.1.1",
Expand Down
65 changes: 65 additions & 0 deletions packages/cursorless-engine/src/core/Tutorial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// import { readFile, writeFile } from "fs/promises";
// import { parse } from "node-html-parser";
// import produce from "immer";
// import { sortBy } from "lodash";
// import { ide } from "../singletons/ide.singleton";
import path from "path";
// import { getCursorlessRepoRoot } from "@cursorless/common";

// TODO the engine is editor agnostic so we shouldn't really import that
// TODO Editor specific features are accessed via the injected ide instance.
// TODO packages\cursorless-engine\src\singletons\ide.singleton.ts
import { openNewEditor } from "@cursorless/vscode-common";

import * as yaml from "js-yaml";
import { promises as fsp } from "node:fs";

import { TestCaseFixture } from "@cursorless/common";
import { Dictionary } from "lodash";

const tutorial_dir =
"C:\\work\\tools\\voicecoding\\cursorless_fork\\packages\\cursorless-vscode-e2e\\src\\suite\\fixtures\\recorded\\tutorial\\unit-2-basic-coding";

/**
* The argument expected by the tutorial command.
*/
interface TutorialCommandArg {
/**
* The version of the tutorial command.
*/
version: 0;

/**
* A representation of the yaml file
*/
stepFixture: Dictionary<string>;

/**
* The yaml file for the current step
*/
yamlFilename: string;
}

export async function tutorialCreate({
version,
stepFixture,
yamlFilename,
}: TutorialCommandArg) {
if (version !== 0) {
throw new Error(`Unsupported tutorial api version: ${version}`);
}

// const fixture = stepFixture as TestCaseFixture;
createEnvironment(yamlFilename);
// TODO need to answer to the talon side only what is necessary
return stepFixture;
}

async function createEnvironment(yamlFilename: string) {
const buffer = await fsp.readFile(path.join(tutorial_dir, yamlFilename));
const fixture = yaml.load(buffer.toString()) as TestCaseFixture;

const editor = await openNewEditor(fixture.initialState.documentContents, {
languageId: fixture.languageId,
});
}
1 change: 1 addition & 0 deletions packages/cursorless-engine/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./testUtil/plainObjectToTarget";
export * from "./core/Cheatsheet";
export * from "./core/Tutorial";
export * from "./testUtil/takeSnapshot";
export * from "./testCaseRecorder/TestCaseRecorder";
export * from "./core/StoredTargets";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
languageId: python
command:
version: 6
spokenForm: bring block made
action:
name: replaceWithTarget
source:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: paragraph}
mark: {type: decoratedSymbol, symbolColor: default, character: m}
destination: {type: implicit}
usePrePhraseSnapshot: false
initialState:
documentContents: |+
from talon import Context, Module

mod = Module()
ctx = Context()

mod.list("cursorless_walkthrough_list", desc="My tutorial list")
ctx.list['user.cursorless_walkthrough_list'] = {
"spoken form": "whatever",
}

selections:
- anchor: {line: 10, character: 0}
active: {line: 10, character: 0}
marks:
default.m:
start: {line: 5, character: 0}
end: {line: 5, character: 3}
finalState:
documentContents: |-
from talon import Context, Module

mod = Module()
ctx = Context()

mod.list("cursorless_walkthrough_list", desc="My tutorial list")
ctx.list['user.cursorless_walkthrough_list'] = {
"spoken form": "whatever",
}

mod.list("cursorless_walkthrough_list", desc="My tutorial list")
ctx.list['user.cursorless_walkthrough_list'] = {
"spoken form": "whatever",
}
selections:
- anchor: {line: 13, character: 1}
active: {line: 13, character: 1}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
languageId: python
command:
version: 6
spokenForm: change inside pair sun
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- {type: interiorOnly}
- type: containingScope
scopeType: {type: surroundingPair, delimiter: any}
mark: {type: decoratedSymbol, symbolColor: default, character: s}
usePrePhraseSnapshot: false
initialState:
documentContents: |-
from talon import Context, Module

mod = Module()
ctx = Context()

mod.list("cursorless_walkthrough_list", desc="My tutorial list")
ctx.list['user.cursorless_walkthrough_list'] = {
"spoken form": "whatever",
}

mod.list("emoji", desc="Emojis")
ctx.list['user.emoji'] = {
"spoken form": "whatever",
}
selections:
- anchor: {line: 10, character: 30}
active: {line: 10, character: 30}
marks:
default.s:
start: {line: 12, character: 5}
end: {line: 12, character: 11}
finalState:
documentContents: |-
from talon import Context, Module

mod = Module()
ctx = Context()

mod.list("cursorless_walkthrough_list", desc="My tutorial list")
ctx.list['user.cursorless_walkthrough_list'] = {
"spoken form": "whatever",
}

mod.list("emoji", desc="Emojis")
ctx.list['user.emoji'] = {
"": "whatever",
}
selections:
- anchor: {line: 12, character: 5}
active: {line: 12, character: 5}
Loading