Skip to content

Commit d72aec3

Browse files
Cursorless tutorial units 1 & 2 (#360)
<img width="773" alt="image" src="https://github.com/user-attachments/assets/ae0d024b-a810-4eab-add5-732f1171d9ef"> ## Todo - [x] Install locally and try everything out - [x] Try "cursorless docs" both inside and outside of VSCode - [x] I have not broken the cheatsheet - [x] Make it more obvious that the tutorial list is a list of all tutorials - [x] Change what we show when tutorial prereqs no longer met, eg "prereqs not met, feel free to keep playing, and say resume to resume" - [x] Add note at end of unit 1 suggesting they continue to play with the document - [x] Tune tutorial content. Move / add docs tip to end of first tutorial. More stuff maybe better for follow-up PR - [x] Point out that we're using highlights for marks in first tutorial step - [x] Have "tutorial exit" command and corresponding button - [x] Add paragraph break between sentences in step - [x] Workshop the actual text in the editor itself so that it indicates they should look at the side bar - [x] Make color of commands stand out more - [x] Properly support custom actions and other necessary custom spoken forms - [x] Store current progress in local storage (and sync it) - [x] Properly support custom symbol spoken forms (eg alphabet) - [x] Add "tutorial restart" - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [x] Fix scope tree view - [x] How to handle disabled hat color? - [x] Figure out unicode for emoji - [x] Figure out what "cursorless tutorial" should actually do. Currently starts second tutorial, which is obv not what we want - [x] Figure out how to advance from last step of initial tutorial, as it has no action to perform - [x] Add "tutorial next" / "tutorial previous" commands - [x] Add tutorial step saying to use "cursorless help" cheatsheat - [x] Fix broken references to css eg reset - [x] Invalidate state if custom spoken forms change - [x] Change view when we detect that prereqs are no longer met (eg cursor in wrong place, etc). Indicate they should say "tutorial continue" / "tutorial restore" / "cursorless tutorial" to reset the prereqs - [x] Merge HUD changes - [x] Figure out how to properly detect VSCode focused - [x] Highlight marks that need to be targeted, so user doesn't have to search for them - [x] Remove print statements and commented out code from `tutorial.py` - [x] Figure out how to only run step preparation when you have the right window focused. Some of the time @pokey gets error message after "cursorles help" step <img width="385" alt="image" src="https://github.com/cursorless-dev/cursorless/assets/755842/fc9bbcbd-111c-48e0-80b5-beeed8dafaea"> - [x] Open new window on start? - [x] Ensure you're in the right window / editor before restoring state, so we don't accidentally clobber something - [x] Get it to work with mainline Talon HUD - [x] Finish constructing spoken forms from commands - [x] Use [app name](https://github.com/pokey/cursorless-vscode/blob/a81dd0ce5f6359482fe9afc55a47ceb142cac17d/cursorless-talon/src/tutorial.py#L17) for VSCode that works cross-platform - [x] Figure out what todo with the fact that the cheatsheet clobbers the entire screen and you can't even see the close button (#619) See also cursorless-dev/cursorless-talon#143 ### Unit 2 - Python - Have an action class? To cover: - [x] New scope types - [x] New actions, including - [x] single argument "bring" - [x] multiple argument "bring" - [x] "move" - [x] "swap" - [x] Simple inference in the context of multiple target actions such as swap or bring ### Extra unit - [x] Probably want to move this into a new pull request - [x] Create a json script for it - [x] Don't forget to add the intermediate steps where it was not a cursorless command and so isn't recorded ## Links ### Helpful places in our code base - https://github.com/cursorless-dev/cursorless/blob/main/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts sets up file to initial state of tutorial step - to generate a spoken form from the command payload in the yaml: https://github.com/cursorless-dev/cursorless/blob/a9cc79f287a0278faf1198f9775cef6932630800/packages/cursorless-engine/src/scopeProviders/ScopeInfoProvider.ts#L95 - this is where you'll send a command to cursorless requesting it to set up initial tutorial step state and have it return generated spoken form: https://github.com/cursorless-dev/cursorless/pull/360/files#diff-5c1cd9d422f7c8df0325fd0958446c90873fb338c462287ef5f57606a363658bR35-R37 - registering a new command extension-side https://github.com/cursorless-dev/cursorless/blob/a9cc79f287a0278faf1198f9775cef6932630800/packages/cursorless-vscode/src/extension.ts#L125 ### In case we decided to look into VSCode walkthroughs rather than Talon hud - https://code.visualstudio.com/api/references/contribution-points#contributes.walkthroughs --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 99969ac commit d72aec3

File tree

82 files changed

+3705
-7
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+3705
-7
lines changed

.vscode/tasks.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,33 @@
115115
"type": "npm",
116116
"script": "populate-dist",
117117
"path": "packages/cursorless-vscode",
118+
"dependsOn": ["VSCode: Build tutorial webview"],
118119
"presentation": {
119120
"reveal": "silent"
120121
},
121122
"group": "build"
122123
},
124+
{
125+
"label": "VSCode: Build tutorial webview",
126+
"type": "npm",
127+
"script": "build:dev",
128+
"path": "packages/cursorless-vscode-tutorial-webview",
129+
"presentation": {
130+
"reveal": "silent"
131+
},
132+
"group": "build"
133+
},
134+
{
135+
"label": "VSCode: Watch tutorial",
136+
"type": "npm",
137+
"script": "watch:tailwind",
138+
"isBackground": true,
139+
"presentation": {
140+
"reveal": "never"
141+
},
142+
"path": "packages/cursorless-vscode-tutorial-webview",
143+
"group": "build"
144+
},
123145
{
124146
"label": "VSCode: Prepare test subset",
125147
"dependsOn": [

cursorless-talon/src/cheatsheet/cheat_sheet.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .sections.modifiers import get_modifiers
1212
from .sections.scopes import get_scopes
1313
from .sections.special_marks import get_special_marks
14+
from .sections.tutorial import get_tutorial_entries
1415

1516
mod = Module()
1617
ctx = Context()
@@ -37,6 +38,7 @@ def private_cursorless_cheat_sheet_update_json():
3738

3839
def private_cursorless_open_instructions():
3940
"""Open web page with cursorless instructions"""
41+
actions.user.private_cursorless_notify_docs_opened()
4042
webbrowser.open(instructions_url)
4143

4244

@@ -150,5 +152,10 @@ def cursorless_cheat_sheet_get_json():
150152
"id": "shapes",
151153
"items": get_list("hat_shape", "hatShape"),
152154
},
155+
{
156+
"name": "Tutorial",
157+
"id": "tutorial",
158+
"items": get_tutorial_entries(),
159+
},
153160
]
154161
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
def get_tutorial_entries():
2+
return [
3+
{
4+
"id": "start_tutorial",
5+
"type": "command",
6+
"variations": [
7+
{
8+
"spokenForm": "cursorless tutorial",
9+
"description": "Start the introductory Cursorless tutorial",
10+
},
11+
],
12+
},
13+
{
14+
"id": "tutorial_next",
15+
"type": "command",
16+
"variations": [
17+
{
18+
"spokenForm": "tutorial next",
19+
"description": "Advance to next step in tutorial",
20+
},
21+
],
22+
},
23+
{
24+
"id": "tutorial_previous",
25+
"type": "command",
26+
"variations": [
27+
{
28+
"spokenForm": "tutorial previous",
29+
"description": "Go back to previous step in tutorial",
30+
},
31+
],
32+
},
33+
{
34+
"id": "tutorial_restart",
35+
"type": "command",
36+
"variations": [
37+
{
38+
"spokenForm": "tutorial restart",
39+
"description": "Restart the tutorial",
40+
},
41+
],
42+
},
43+
{
44+
"id": "tutorial_resume",
45+
"type": "command",
46+
"variations": [
47+
{
48+
"spokenForm": "tutorial resume",
49+
"description": "Resume the tutorial",
50+
},
51+
],
52+
},
53+
{
54+
"id": "tutorial_list",
55+
"type": "command",
56+
"variations": [
57+
{
58+
"spokenForm": "tutorial list",
59+
"description": "List all available tutorials",
60+
},
61+
],
62+
},
63+
{
64+
"id": "tutorial_close",
65+
"type": "command",
66+
"variations": [
67+
{
68+
"spokenForm": "tutorial close",
69+
"description": "Close the tutorial",
70+
},
71+
],
72+
},
73+
{
74+
"id": "tutorial_start_by_number",
75+
"type": "command",
76+
"variations": [
77+
{
78+
"spokenForm": "tutorial <number>",
79+
"description": "Start a specific tutorial by number",
80+
},
81+
],
82+
},
83+
]

cursorless-talon/src/cursorless.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from talon import Module, actions
1+
from talon import Context, Module, actions
22

33
mod = Module()
44

@@ -7,6 +7,11 @@
77
"Application supporting cursorless commands",
88
)
99

10+
ctx = Context()
11+
ctx.matches = r"""
12+
tag: user.cursorless
13+
"""
14+
1015

1116
@mod.action_class
1217
class Actions:
@@ -16,8 +21,62 @@ def private_cursorless_show_settings_in_ide():
1621
def private_cursorless_show_sidebar():
1722
"""Show Cursorless-specific settings in ide"""
1823

24+
def private_cursorless_notify_docs_opened():
25+
"""Notify the ide that the docs were opened in case the tutorial is waiting for that event"""
26+
actions.skip()
27+
1928
def private_cursorless_show_command_statistics():
2029
"""Show Cursorless command statistics"""
2130
actions.user.private_cursorless_run_rpc_command_no_wait(
2231
"cursorless.analyzeCommandHistory"
2332
)
33+
34+
def private_cursorless_start_tutorial():
35+
"""Start the introductory Cursorless tutorial"""
36+
actions.user.private_cursorless_run_rpc_command_no_wait(
37+
"cursorless.tutorial.start", "tutorial-1-basics"
38+
)
39+
40+
def private_cursorless_tutorial_next():
41+
"""Cursorless tutorial: next"""
42+
actions.user.private_cursorless_run_rpc_command_no_wait(
43+
"cursorless.tutorial.next"
44+
)
45+
46+
def private_cursorless_tutorial_previous():
47+
"""Cursorless tutorial: previous"""
48+
actions.user.private_cursorless_run_rpc_command_no_wait(
49+
"cursorless.tutorial.previous"
50+
)
51+
52+
def private_cursorless_tutorial_restart():
53+
"""Cursorless tutorial: restart"""
54+
actions.user.private_cursorless_run_rpc_command_no_wait(
55+
"cursorless.tutorial.restart"
56+
)
57+
58+
def private_cursorless_tutorial_resume():
59+
"""Cursorless tutorial: resume"""
60+
actions.user.private_cursorless_run_rpc_command_no_wait(
61+
"cursorless.tutorial.resume"
62+
)
63+
64+
def private_cursorless_tutorial_list():
65+
"""Cursorless tutorial: list all available tutorials"""
66+
actions.user.private_cursorless_run_rpc_command_no_wait(
67+
"cursorless.tutorial.list"
68+
)
69+
70+
def private_cursorless_tutorial_start_by_number(number: int): # pyright: ignore [reportGeneralTypeIssues]
71+
"""Start Cursorless tutorial by number"""
72+
actions.user.private_cursorless_run_rpc_command_no_wait(
73+
"cursorless.tutorial.start", number - 1
74+
)
75+
76+
77+
@ctx.action_class("user")
78+
class CursorlessActions:
79+
def private_cursorless_notify_docs_opened():
80+
actions.user.private_cursorless_run_rpc_command_no_wait(
81+
"cursorless.documentationOpened"
82+
)

cursorless-talon/src/cursorless.talon

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,13 @@ bar {user.cursorless_homophone}:
3737

3838
{user.cursorless_homophone} stats:
3939
user.private_cursorless_show_command_statistics()
40+
41+
{user.cursorless_homophone} tutorial:
42+
user.private_cursorless_start_tutorial()
43+
tutorial next: user.private_cursorless_tutorial_next()
44+
tutorial (previous | last): user.private_cursorless_tutorial_previous()
45+
tutorial restart: user.private_cursorless_tutorial_restart()
46+
tutorial resume: user.private_cursorless_tutorial_resume()
47+
tutorial (list | close): user.private_cursorless_tutorial_list()
48+
tutorial <user.private_cursorless_number_small>:
49+
user.private_cursorless_tutorial_start_by_number(private_cursorless_number_small)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: change sit
5+
action:
6+
name: clearAndSetSelection
7+
target:
8+
type: primitive
9+
mark: {type: decoratedSymbol, symbolColor: default, character: i}
10+
usePrePhraseSnapshot: true
11+
initialState:
12+
documentContents: |
13+
Welcome Cursorless!
14+
15+
Notice the hats above each word in this sentence.
16+
selections:
17+
- anchor: {line: 2, character: 15}
18+
active: {line: 2, character: 15}
19+
marks:
20+
default.i:
21+
start: {line: 2, character: 32}
22+
end: {line: 2, character: 34}
23+
finalState:
24+
documentContents: |
25+
Welcome Cursorless!
26+
27+
Notice the hats above each word this sentence.
28+
selections:
29+
- anchor: {line: 2, character: 32}
30+
active: {line: 2, character: 32}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: chuck line odd
5+
action:
6+
name: remove
7+
target:
8+
type: primitive
9+
mark: {type: decoratedSymbol, symbolColor: default, character: o}
10+
modifiers:
11+
- type: containingScope
12+
scopeType: {type: line}
13+
usePrePhraseSnapshot: true
14+
initialState:
15+
documentContents: |-
16+
Welcome Cursorless!
17+
18+
Notice the hats above each word in this sentence.
19+
20+
Now, see the sidebar.
21+
selections:
22+
- anchor: {line: 2, character: 0}
23+
active: {line: 2, character: 6}
24+
- anchor: {line: 2, character: 35}
25+
active: {line: 2, character: 39}
26+
marks:
27+
default.o:
28+
start: {line: 4, character: 0}
29+
end: {line: 4, character: 3}
30+
finalState:
31+
documentContents: |
32+
Welcome Cursorless!
33+
34+
Notice the hats above each word in this sentence.
35+
selections:
36+
- anchor: {line: 2, character: 0}
37+
active: {line: 2, character: 6}
38+
- anchor: {line: 2, character: 35}
39+
active: {line: 2, character: 39}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: chuck trap
5+
action:
6+
name: remove
7+
target:
8+
type: primitive
9+
mark: {type: decoratedSymbol, symbolColor: default, character: t}
10+
usePrePhraseSnapshot: true
11+
initialState:
12+
documentContents: |-
13+
Welcome to Cursorless!
14+
15+
Notice the hats above each word in this sentence.
16+
17+
Now, see the sidebar.
18+
selections:
19+
- anchor: {line: 2, character: 0}
20+
active: {line: 2, character: 6}
21+
- anchor: {line: 2, character: 35}
22+
active: {line: 2, character: 39}
23+
marks:
24+
default.t:
25+
start: {line: 0, character: 8}
26+
end: {line: 0, character: 10}
27+
finalState:
28+
documentContents: |-
29+
Welcome Cursorless!
30+
31+
Notice the hats above each word in this sentence.
32+
33+
Now, see the sidebar.
34+
selections:
35+
- anchor: {line: 2, character: 0}
36+
active: {line: 2, character: 6}
37+
- anchor: {line: 2, character: 35}
38+
active: {line: 2, character: 39}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: post air
5+
action:
6+
name: setSelectionAfter
7+
target:
8+
type: primitive
9+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
10+
usePrePhraseSnapshot: true
11+
initialState:
12+
documentContents: |
13+
Welcome Cursorless!
14+
15+
Notice the hats above each word in this sentence.
16+
selections:
17+
- anchor: {line: 0, character: 8}
18+
active: {line: 0, character: 8}
19+
marks:
20+
default.a:
21+
start: {line: 2, character: 11}
22+
end: {line: 2, character: 15}
23+
finalState:
24+
documentContents: |
25+
Welcome Cursorless!
26+
27+
Notice the hats above each word in this sentence.
28+
selections:
29+
- anchor: {line: 2, character: 15}
30+
active: {line: 2, character: 15}

0 commit comments

Comments
 (0)