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) image ## 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 image - [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)