From d7fe84b1f01fd28dca86552fdc51576256cb12df Mon Sep 17 00:00:00 2001
From: Pokey Rule <755842+pokey@users.noreply.github.com>
Date: Fri, 19 Jul 2024 07:51:34 -0700
Subject: [PATCH] Cursorless tutorial units 1 & 2 (#360)
## 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
- [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 https://github.com/pokey/cursorless-talon/pull/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>
---
src/cheatsheet/cheat_sheet.py | 7 +++
src/cheatsheet/sections/tutorial.py | 83 +++++++++++++++++++++++++++++
src/cursorless.py | 61 ++++++++++++++++++++-
src/cursorless.talon | 10 ++++
4 files changed, 160 insertions(+), 1 deletion(-)
create mode 100644 src/cheatsheet/sections/tutorial.py
diff --git a/src/cheatsheet/cheat_sheet.py b/src/cheatsheet/cheat_sheet.py
index 5d4b938576..7b82177e27 100644
--- a/src/cheatsheet/cheat_sheet.py
+++ b/src/cheatsheet/cheat_sheet.py
@@ -11,6 +11,7 @@
from .sections.modifiers import get_modifiers
from .sections.scopes import get_scopes
from .sections.special_marks import get_special_marks
+from .sections.tutorial import get_tutorial_entries
mod = Module()
ctx = Context()
@@ -37,6 +38,7 @@ def private_cursorless_cheat_sheet_update_json():
def private_cursorless_open_instructions():
"""Open web page with cursorless instructions"""
+ actions.user.private_cursorless_notify_docs_opened()
webbrowser.open(instructions_url)
@@ -150,5 +152,10 @@ def cursorless_cheat_sheet_get_json():
"id": "shapes",
"items": get_list("hat_shape", "hatShape"),
},
+ {
+ "name": "Tutorial",
+ "id": "tutorial",
+ "items": get_tutorial_entries(),
+ },
]
}
diff --git a/src/cheatsheet/sections/tutorial.py b/src/cheatsheet/sections/tutorial.py
new file mode 100644
index 0000000000..38189cfe11
--- /dev/null
+++ b/src/cheatsheet/sections/tutorial.py
@@ -0,0 +1,83 @@
+def get_tutorial_entries():
+ return [
+ {
+ "id": "start_tutorial",
+ "type": "command",
+ "variations": [
+ {
+ "spokenForm": "cursorless tutorial",
+ "description": "Start the introductory Cursorless tutorial",
+ },
+ ],
+ },
+ {
+ "id": "tutorial_next",
+ "type": "command",
+ "variations": [
+ {
+ "spokenForm": "tutorial next",
+ "description": "Advance to next step in tutorial",
+ },
+ ],
+ },
+ {
+ "id": "tutorial_previous",
+ "type": "command",
+ "variations": [
+ {
+ "spokenForm": "tutorial previous",
+ "description": "Go back to previous step in tutorial",
+ },
+ ],
+ },
+ {
+ "id": "tutorial_restart",
+ "type": "command",
+ "variations": [
+ {
+ "spokenForm": "tutorial restart",
+ "description": "Restart the tutorial",
+ },
+ ],
+ },
+ {
+ "id": "tutorial_resume",
+ "type": "command",
+ "variations": [
+ {
+ "spokenForm": "tutorial resume",
+ "description": "Resume the tutorial",
+ },
+ ],
+ },
+ {
+ "id": "tutorial_list",
+ "type": "command",
+ "variations": [
+ {
+ "spokenForm": "tutorial list",
+ "description": "List all available tutorials",
+ },
+ ],
+ },
+ {
+ "id": "tutorial_close",
+ "type": "command",
+ "variations": [
+ {
+ "spokenForm": "tutorial close",
+ "description": "Close the tutorial",
+ },
+ ],
+ },
+ {
+ "id": "tutorial_start_by_number",
+ "type": "command",
+ "variations": [
+ {
+ "spokenForm": "tutorial ",
+ "description": "Start a specific tutorial by number",
+ },
+ ],
+ },
+ ]
diff --git a/src/cursorless.py b/src/cursorless.py
index 9617f51593..06e58e8dbe 100644
--- a/src/cursorless.py
+++ b/src/cursorless.py
@@ -1,4 +1,4 @@
-from talon import Module, actions
+from talon import Context, Module, actions
mod = Module()
@@ -7,6 +7,11 @@
"Application supporting cursorless commands",
)
+ctx = Context()
+ctx.matches = r"""
+tag: user.cursorless
+"""
+
@mod.action_class
class Actions:
@@ -16,8 +21,62 @@ def private_cursorless_show_settings_in_ide():
def private_cursorless_show_sidebar():
"""Show Cursorless-specific settings in ide"""
+ def private_cursorless_notify_docs_opened():
+ """Notify the ide that the docs were opened in case the tutorial is waiting for that event"""
+ actions.skip()
+
def private_cursorless_show_command_statistics():
"""Show Cursorless command statistics"""
actions.user.private_cursorless_run_rpc_command_no_wait(
"cursorless.analyzeCommandHistory"
)
+
+ def private_cursorless_start_tutorial():
+ """Start the introductory Cursorless tutorial"""
+ actions.user.private_cursorless_run_rpc_command_no_wait(
+ "cursorless.tutorial.start", "tutorial-1-basics"
+ )
+
+ def private_cursorless_tutorial_next():
+ """Cursorless tutorial: next"""
+ actions.user.private_cursorless_run_rpc_command_no_wait(
+ "cursorless.tutorial.next"
+ )
+
+ def private_cursorless_tutorial_previous():
+ """Cursorless tutorial: previous"""
+ actions.user.private_cursorless_run_rpc_command_no_wait(
+ "cursorless.tutorial.previous"
+ )
+
+ def private_cursorless_tutorial_restart():
+ """Cursorless tutorial: restart"""
+ actions.user.private_cursorless_run_rpc_command_no_wait(
+ "cursorless.tutorial.restart"
+ )
+
+ def private_cursorless_tutorial_resume():
+ """Cursorless tutorial: resume"""
+ actions.user.private_cursorless_run_rpc_command_no_wait(
+ "cursorless.tutorial.resume"
+ )
+
+ def private_cursorless_tutorial_list():
+ """Cursorless tutorial: list all available tutorials"""
+ actions.user.private_cursorless_run_rpc_command_no_wait(
+ "cursorless.tutorial.list"
+ )
+
+ def private_cursorless_tutorial_start_by_number(number: int): # pyright: ignore [reportGeneralTypeIssues]
+ """Start Cursorless tutorial by number"""
+ actions.user.private_cursorless_run_rpc_command_no_wait(
+ "cursorless.tutorial.start", number - 1
+ )
+
+
+@ctx.action_class("user")
+class CursorlessActions:
+ def private_cursorless_notify_docs_opened():
+ actions.user.private_cursorless_run_rpc_command_no_wait(
+ "cursorless.documentationOpened"
+ )
diff --git a/src/cursorless.talon b/src/cursorless.talon
index a748282ab6..68047b9f03 100644
--- a/src/cursorless.talon
+++ b/src/cursorless.talon
@@ -37,3 +37,13 @@ bar {user.cursorless_homophone}:
{user.cursorless_homophone} stats:
user.private_cursorless_show_command_statistics()
+
+{user.cursorless_homophone} tutorial:
+ user.private_cursorless_start_tutorial()
+tutorial next: user.private_cursorless_tutorial_next()
+tutorial (previous | last): user.private_cursorless_tutorial_previous()
+tutorial restart: user.private_cursorless_tutorial_restart()
+tutorial resume: user.private_cursorless_tutorial_resume()
+tutorial (list | close): user.private_cursorless_tutorial_list()
+tutorial :
+ user.private_cursorless_tutorial_start_by_number(private_cursorless_number_small)