From 3c247a8360fc222960350c6bf6796dc3f72db347 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 13 Jul 2023 19:58:32 +0200 Subject: [PATCH 01/17] Talon destination --- cursorless-talon/src/actions/actions.py | 49 ++- .../src/actions/actions_callback.py | 12 +- .../src/actions/actions_simple.py | 13 +- cursorless-talon/src/actions/bring_move.py | 47 +++ cursorless-talon/src/actions/call.py | 22 +- .../src/actions/execute_command.py | 16 + cursorless-talon/src/actions/find.py | 6 +- cursorless-talon/src/actions/get_text.py | 21 +- cursorless-talon/src/actions/homophones.py | 5 +- cursorless-talon/src/actions/move_bring.py | 20 -- cursorless-talon/src/actions/paste.py | 20 ++ cursorless-talon/src/actions/reformat.py | 12 +- cursorless-talon/src/actions/replace.py | 18 +- cursorless-talon/src/actions/swap.py | 26 +- cursorless-talon/src/actions/wrap.py | 95 +++--- .../src/apps/cursorless_vscode.py | 5 +- .../src/cheatsheet/cheat_sheet.py | 296 +++++++++--------- .../cheatsheet/sections/compound_targets.py | 104 +++--- cursorless-talon/src/command.py | 169 ++++------ cursorless-talon/src/compound_targets.py | 87 ----- cursorless-talon/src/connective.py | 10 +- cursorless-talon/src/cursorless.py | 2 +- cursorless-talon/src/cursorless.talon | 32 +- .../src/cursorless_snippets.talon | 11 - cursorless-talon/src/marks/lines_number.py | 14 +- cursorless-talon/src/modifiers/modifiers.py | 3 +- .../src/modifiers/ordinal_scope.py | 9 +- cursorless-talon/src/modifiers/position.py | 17 +- cursorless-talon/src/paired_delimiter.py | 7 +- cursorless-talon/src/positional_target.py | 44 --- cursorless-talon/src/primitive_target.py | 38 --- cursorless-talon/src/targets/destination.py | 41 +++ .../src/targets/primitive_target.py | 17 + cursorless-talon/src/targets/range_target.py | 67 ++++ .../src/{modifiers => targets}/range_type.py | 0 cursorless-talon/src/targets/target.py | 35 +++ cursorless-talon/src/targets/target_types.py | 62 ++++ .../src/actions/InsertCopy.ts | 11 +- .../src/core/inferFullTargetDescriptor.ts | 8 +- .../ModifierStageFactoryImpl.ts | 4 +- .../processTargets/TargetPipelineRunner.ts | 14 +- .../processTargets/modifiers/PositionStage.ts | 24 +- .../src/processTargets/targets/BaseTarget.ts | 2 +- .../processTargets/targets/DestinationImpl.ts | 23 +- .../targets/NotebookCellDestination.ts | 4 +- .../targets/NotebookCellTarget.ts | 2 +- .../src/processTargets/targets/PlainTarget.ts | 8 +- .../targets/VerticalRangeTarget.ts | 31 ++ .../src/processTargets/targets/index.ts | 5 +- .../transformations/upgrade.ts | 2 +- .../src/testCaseRecorder/TestCase.ts | 2 +- .../src/util/getPartialTargetDescriptors.ts | 33 +- .../fixtures/recorded/actions/getTextAir.yml | 36 --- .../recorded/actions/giveEndOfDot.yml | 39 --- .../recorded/actions/giveStartOfDot.yml | 39 --- .../bringFineAfterJustThis.yml | 32 -- .../bringFineAfterThis.yml | 0 .../bringWhaleBeforeJustThis.yml | 32 -- .../bringWhaleBeforeThis.yml | 0 .../src/suite/getText.vscode.test.ts | 37 +++ packages/cursorless-vscode/package.json | 2 +- .../src/keyboard/KeyboardCommandsTargeted.ts | 49 +-- .../meta-updater/src/updatePackageJson.ts | 46 +-- 63 files changed, 953 insertions(+), 984 deletions(-) create mode 100644 cursorless-talon/src/actions/bring_move.py create mode 100644 cursorless-talon/src/actions/execute_command.py delete mode 100644 cursorless-talon/src/actions/move_bring.py create mode 100644 cursorless-talon/src/actions/paste.py delete mode 100644 cursorless-talon/src/compound_targets.py delete mode 100644 cursorless-talon/src/cursorless_snippets.talon delete mode 100644 cursorless-talon/src/positional_target.py delete mode 100644 cursorless-talon/src/primitive_target.py create mode 100644 cursorless-talon/src/targets/destination.py create mode 100644 cursorless-talon/src/targets/primitive_target.py create mode 100644 cursorless-talon/src/targets/range_target.py rename cursorless-talon/src/{modifiers => targets}/range_type.py (100%) create mode 100644 cursorless-talon/src/targets/target.py create mode 100644 cursorless-talon/src/targets/target_types.py create mode 100644 packages/cursorless-engine/src/processTargets/targets/VerticalRangeTarget.ts delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/getTextAir.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveEndOfDot.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveStartOfDot.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterJustThis.yml rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{implicitExpansion => updateSelections}/bringFineAfterThis.yml (100%) delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeJustThis.yml rename packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/{implicitExpansion => updateSelections}/bringWhaleBeforeThis.yml (100%) create mode 100644 packages/cursorless-vscode-e2e/src/suite/getText.vscode.test.ts diff --git a/cursorless-talon/src/actions/actions.py b/cursorless-talon/src/actions/actions.py index e2bb45e29f..d58413c174 100644 --- a/cursorless-talon/src/actions/actions.py +++ b/cursorless-talon/src/actions/actions.py @@ -1,14 +1,15 @@ from talon import Module, actions, app from ..csv_overrides import init_csv_and_watch_changes -from ..primitive_target import create_implicit_target +from ..targets.target_types import CursorlessTarget, ImplicitTarget from .actions_callback import callback_action_defaults, callback_action_map from .actions_simple import ( no_wait_actions, no_wait_actions_post_sleep, - positional_action_defaults, simple_action_defaults, ) +from .bring_move import BringMoveTargets +from .execute_command import cursorless_execute_command_action mod = Module() @@ -39,22 +40,24 @@ def cursorless_action_or_ide_command(m) -> dict: @mod.action_class class Actions: - def cursorless_command(action_id: str, target: dict): + def cursorless_command(action_name: str, target: CursorlessTarget): """Perform cursorless command on target""" - if action_id in callback_action_map: - return callback_action_map[action_id](target) - elif action_id in no_wait_actions: - actions.user.cursorless_single_target_command_no_wait(action_id, target) - if action_id in no_wait_actions_post_sleep: - actions.sleep(no_wait_actions_post_sleep[action_id]) - elif action_id in ["replaceWithTarget", "moveToTarget"]: - actions.user.cursorless_multiple_target_command( - action_id, [target, create_implicit_target()] + if action_name in callback_action_map: + callback_action_map[action_name](target) + elif action_name in ["replaceWithTarget", "moveToTarget"]: + actions.user.cursorless_bring_move( + action_name, BringMoveTargets(target, ImplicitTarget()) ) + elif action_name in no_wait_actions: + action = {"name": action_name, "target": target} + actions.user.cursorless_command_no_wait(action) + if action_name in no_wait_actions_post_sleep: + actions.sleep(no_wait_actions_post_sleep[action_name]) else: - return actions.user.cursorless_single_target_command(action_id, target) + action = {"name": action_name, "target": target} + actions.user.cursorless_command_and_wait(action) - def cursorless_vscode_command(command_id: str, target: dict): + def cursorless_vscode_command(command_id: str, target: CursorlessTarget): """ Perform vscode command on cursorless target @@ -62,11 +65,13 @@ def cursorless_vscode_command(command_id: str, target: dict): """ return actions.user.cursorless_ide_command(command_id, target) - def cursorless_ide_command(command_id: str, target: dict): + def cursorless_ide_command(command_id: str, target: CursorlessTarget): """Perform ide command on cursorless target""" - return ide_command(command_id, target) + return cursorless_execute_command_action(command_id, target) - def cursorless_action_or_ide_command(instruction: dict, target: dict): + def private_cursorless_action_or_ide_command( + instruction: dict, target: CursorlessTarget + ): """Perform cursorless action or ide command on target (internal use only)""" type = instruction["type"] value = instruction["value"] @@ -76,18 +81,12 @@ def cursorless_action_or_ide_command(instruction: dict, target: dict): return actions.user.cursorless_ide_command(value, target) -def ide_command(command_id: str, target: dict, command_options: dict = {}): - return actions.user.cursorless_single_target_command( - "executeCommand", target, command_id, command_options - ) - - default_values = { "simple_action": simple_action_defaults, - "positional_action": positional_action_defaults, "callback_action": callback_action_defaults, + "paste_action": {"paste": "pasteFromClipboard"}, + "bring_move_action": {"bring": "replaceWithTarget", "move": "moveToTarget"}, "swap_action": {"swap": "swapTargets"}, - "move_bring_action": {"bring": "replaceWithTarget", "move": "moveToTarget"}, "wrap_action": {"wrap": "wrapWithPairedDelimiter", "repack": "rewrap"}, "insert_snippet_action": {"snippet": "insertSnippet"}, "reformat_action": {"format": "applyFormatter"}, diff --git a/cursorless-talon/src/actions/actions_callback.py b/cursorless-talon/src/actions/actions_callback.py index c06899ce29..c4c1121022 100644 --- a/cursorless-talon/src/actions/actions_callback.py +++ b/cursorless-talon/src/actions/actions_callback.py @@ -4,8 +4,8 @@ from talon import Module, actions -from .call import run_call_action -from .homophones import run_homophones_action +from .call import cursorless_call_action +from .homophones import cursorless_homophones_action @dataclass @@ -18,11 +18,9 @@ class CallbackAction: # NOTE: Please do not change these dicts. Use the CSVs for customization. # See https://www.cursorless.org/docs/user/customization/ callbacks = [ - CallbackAction("call", "callAsFunction", run_call_action), - CallbackAction( - "scout", "findInDocument", actions.user.cursorless_private_run_find_action - ), - CallbackAction("phones", "nextHomophone", run_homophones_action), + CallbackAction("call", "callAsFunction", cursorless_call_action), + CallbackAction("scout", "findInDocument", actions.user.private_cursorless_find), + CallbackAction("phones", "nextHomophone", cursorless_homophones_action), ] callback_action_defaults = { diff --git a/cursorless-talon/src/actions/actions_simple.py b/cursorless-talon/src/actions/actions_simple.py index c6f70b08e6..cef2959bdd 100644 --- a/cursorless-talon/src/actions/actions_simple.py +++ b/cursorless-talon/src/actions/actions_simple.py @@ -5,6 +5,7 @@ simple_action_defaults = { "bottom": "scrollToBottom", "break point": "toggleLineBreakpoint", + "call": "callAsFunction", "carve": "cutToClipboard", "center": "scrollToCenter", "change": "clearAndSetSelection", @@ -27,6 +28,7 @@ "hover": "showHover", "indent": "indentLine", "inspect": "showDebugHover", + "phones": "nextHomophone", "post": "setSelectionAfter", "pour": "editNewLineAfter", "pre": "setSelectionBefore", @@ -36,6 +38,7 @@ "rename": "rename", "reverse": "reverseTargets", "scout all": "findInWorkspace", + "scout": "findInDocument", "shuffle": "randomizeTargets", "snippet make": "generateSnippet", "sort": "sortTargets", @@ -44,12 +47,6 @@ "unfold": "unfoldRegion", } -# NOTE: Please do not change these dicts. Use the CSVs for customization. -# See https://www.cursorless.org/docs/user/customization/ -positional_action_defaults = { - "paste": "pasteFromClipboard", -} - # Don't wait for these actions to finish, usually because they hang on some kind of user interaction no_wait_actions = [ "generateSnippet", @@ -66,7 +63,3 @@ "cursorless_simple_action", desc="Supported simple actions for cursorless navigation", ) -mod.list( - "cursorless_positional_action", - desc="Supported actions for cursorless that expect a positional target", -) diff --git a/cursorless-talon/src/actions/bring_move.py b/cursorless-talon/src/actions/bring_move.py new file mode 100644 index 0000000000..6dadac0ad9 --- /dev/null +++ b/cursorless-talon/src/actions/bring_move.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass + +from talon import Module + +from ..command import cursorless_command_and_wait +from ..targets.target_types import ( + CursorlessDestination, + CursorlessTarget, + ImplicitDestination, +) + + +@dataclass +class BringMoveTargets: + source: CursorlessTarget + destination: CursorlessDestination + + +mod = Module() + + +mod.list("cursorless_bring_move_action", desc="Cursorless bring or move actions") + + +@mod.capture(rule=" []") +def cursorless_bring_move_targets(m) -> BringMoveTargets: + source = m.cursorless_target + + try: + destination = m.cursorless_destination + except AttributeError: + destination = ImplicitDestination() + + return BringMoveTargets(source, destination) + + +@mod.action_class +class Actions: + def private_cursorless_bring_move(action_name: str, targets: BringMoveTargets): + """Execute Cursorless move/bring action""" + cursorless_command_and_wait( + { + "name": action_name, + "source": targets.source, + "destination": targets.destination, + } + ) diff --git a/cursorless-talon/src/actions/call.py b/cursorless-talon/src/actions/call.py index 4de83cc084..aa1c97074d 100644 --- a/cursorless-talon/src/actions/call.py +++ b/cursorless-talon/src/actions/call.py @@ -1,10 +1,12 @@ -from talon import Module, actions - -from ..primitive_target import create_implicit_target - -mod = Module() - - -def run_call_action(target: dict): - targets = [target, create_implicit_target()] - actions.user.cursorless_multiple_target_command("callAsFunction", targets) +from ..command import cursorless_command_and_wait +from ..targets.target_types import CursorlessTarget, ImplicitTarget + + +def cursorless_call_action(target: CursorlessTarget): + cursorless_command_and_wait( + { + "name": "callAsFunction", + "callee": target, + "argument": ImplicitTarget(), + } + ) diff --git a/cursorless-talon/src/actions/execute_command.py b/cursorless-talon/src/actions/execute_command.py new file mode 100644 index 0000000000..aa5d9b1169 --- /dev/null +++ b/cursorless-talon/src/actions/execute_command.py @@ -0,0 +1,16 @@ +from ..command import cursorless_command_and_wait +from ..targets.target_types import CursorlessTarget + + +def cursorless_execute_command_action( + command_id: str, target: CursorlessTarget, command_options: dict = {} +): + """Execute Cursorless execute command action""" + cursorless_command_and_wait( + { + "name": "executeCommand", + "commandId": command_id, + "options": command_options, + "target": target, + } + ) diff --git a/cursorless-talon/src/actions/find.py b/cursorless-talon/src/actions/find.py index 34bb02fdee..8023e00050 100644 --- a/cursorless-talon/src/actions/find.py +++ b/cursorless-talon/src/actions/find.py @@ -1,9 +1,11 @@ from talon import Module +from ..targets.target_types import CursorlessTarget + mod = Module() @mod.action_class class Actions: - def cursorless_private_run_find_action(target: dict): - """Find text of target in editor""" + def private_cursorless_find(target: CursorlessTarget): + """Execute Cursorless find action. Find text of target in editor""" diff --git a/cursorless-talon/src/actions/get_text.py b/cursorless-talon/src/actions/get_text.py index 5144636ae8..0606512be6 100644 --- a/cursorless-talon/src/actions/get_text.py +++ b/cursorless-talon/src/actions/get_text.py @@ -1,19 +1,22 @@ from typing import Optional -from talon import actions +from ..command import cursorless_command_and_wait +from ..targets.target_types import CursorlessTarget -def get_text( - target: dict, +def cursorless_get_text( + target: CursorlessTarget, show_decorations: Optional[bool] = None, ensure_single_target: Optional[bool] = None, ): """Get target texts""" - return actions.user.cursorless_single_target_command_get( - "getText", - target, + cursorless_command_and_wait( { - "showDecorations": show_decorations, - "ensureSingleTarget": ensure_single_target, - }, + "name": "getText", + "options": { + "showDecorations": show_decorations, + "ensureSingleTarget": ensure_single_target, + }, + "target": target, + } ) diff --git a/cursorless-talon/src/actions/homophones.py b/cursorless-talon/src/actions/homophones.py index 58476e2386..3760a8d7c5 100644 --- a/cursorless-talon/src/actions/homophones.py +++ b/cursorless-talon/src/actions/homophones.py @@ -1,9 +1,10 @@ from talon import actions, app from .get_text import get_text +from .replace import cursorless_replace_action -def run_homophones_action(target: dict): +def cursorless_homophones_action(target: dict): """Replaced target with next homophone""" texts = get_text(target, show_decorations=False) try: @@ -11,7 +12,7 @@ def run_homophones_action(target: dict): except LookupError as e: app.notify(str(e)) return - actions.user.cursorless_replace(target, updated_texts) + cursorless_replace_action(target, updated_texts) def get_next_homophone(word: str): diff --git a/cursorless-talon/src/actions/move_bring.py b/cursorless-talon/src/actions/move_bring.py deleted file mode 100644 index 99e9232c24..0000000000 --- a/cursorless-talon/src/actions/move_bring.py +++ /dev/null @@ -1,20 +0,0 @@ -from talon import Module - -from ..primitive_target import create_implicit_target - -mod = Module() - - -mod.list("cursorless_move_bring_action", desc="Cursorless move or bring actions") - - -@mod.capture(rule=" []") -def cursorless_move_bring_targets(m) -> list[dict]: - target_list = m.cursorless_target_list - - try: - target_list += [m.cursorless_positional_target] - except AttributeError: - target_list += [create_implicit_target()] - - return target_list diff --git a/cursorless-talon/src/actions/paste.py b/cursorless-talon/src/actions/paste.py new file mode 100644 index 0000000000..f34aa4fd87 --- /dev/null +++ b/cursorless-talon/src/actions/paste.py @@ -0,0 +1,20 @@ +from talon import Module + +from ..command import cursorless_command_and_wait +from ..targets.target_types import CursorlessDestination + +mod = Module() + +mod.list("cursorless_paste_action", desc="Cursorless paste action") + + +@mod.action_class +class Actions: + def private_cursorless_paste(destination: CursorlessDestination): + """Execute Cursorless paste action""" + cursorless_command_and_wait( + { + "name": "pasteFromClipboard", + "destination": destination, + } + ) diff --git a/cursorless-talon/src/actions/reformat.py b/cursorless-talon/src/actions/reformat.py index edfcf3b42b..53e38f9f4f 100644 --- a/cursorless-talon/src/actions/reformat.py +++ b/cursorless-talon/src/actions/reformat.py @@ -1,6 +1,8 @@ from talon import Module, actions -from .get_text import get_text +from ..targets.target_types import CursorlessTarget +from .get_text import cursorless_get_text +from .replace import cursorless_replace_action mod = Module() @@ -9,8 +11,8 @@ @mod.action_class class Actions: - def cursorless_reformat(target: dict, formatters: str): - """Reformat target with formatter""" - texts = get_text(target, show_decorations=False) + def private_cursorless_reformat(target: CursorlessTarget, formatters: str): + """Execute Cursorless reformat action. Reformat target with formatter""" + texts = cursorless_get_text(target, show_decorations=False) updated_texts = [actions.user.reformat_text(text, formatters) for text in texts] - actions.user.cursorless_replace(target, updated_texts) + cursorless_replace_action(target, updated_texts) diff --git a/cursorless-talon/src/actions/replace.py b/cursorless-talon/src/actions/replace.py index f25c01c072..68fa9727ea 100644 --- a/cursorless-talon/src/actions/replace.py +++ b/cursorless-talon/src/actions/replace.py @@ -1,10 +1,12 @@ -from talon import Module, actions +from ..targets.target_types import CursorlessTarget, PrimitiveDestination -mod = Module() - -@mod.action_class -class Actions: - def cursorless_replace(target: dict, texts: list[str]): - """Replace targets with texts""" - actions.user.cursorless_single_target_command("replace", target, texts) +def cursorless_replace_action(target: CursorlessTarget, replace_with: list[str]): + """Execute Cursorless replace action. Replace targets with texts""" + cursorless_command_and_wait( + { + "name": "replace", + "replaceWith": replace_with, + "destination": PrimitiveDestination("to", target), + } + ) diff --git a/cursorless-talon/src/actions/swap.py b/cursorless-talon/src/actions/swap.py index bc2b1c2b7f..197bd697f0 100644 --- a/cursorless-talon/src/actions/swap.py +++ b/cursorless-talon/src/actions/swap.py @@ -1,6 +1,7 @@ from talon import Module -from ..primitive_target import create_base_target +from ..command import cursorless_command_and_wait +from ..targets.target_types import CursorlessTarget, PrimitiveTarget mod = Module() @@ -16,10 +17,23 @@ "[] {user.cursorless_swap_connective} " ) ) -def cursorless_swap_targets(m) -> list[dict]: - target_list = m.cursorless_target_list +def cursorless_swap_targets(m) -> list[CursorlessTarget]: + targets = m.cursorless_target_list - if len(target_list) == 1: - target_list = [create_base_target()] + target_list + if len(targets) == 1: + [PrimitiveTarget(), targets[0]] - return target_list + return targets + + +@mod.action_class +class Actions: + def private_cursorles_swap(targets: list[CursorlessTarget]): + """Execute Cursorless swap action""" + cursorless_command_and_wait( + { + "name": "swapTargets", + "target1": targets[0], + "target2": targets[1], + } + ) diff --git a/cursorless-talon/src/actions/wrap.py b/cursorless-talon/src/actions/wrap.py index 34edddf6eb..7bde2f750f 100644 --- a/cursorless-talon/src/actions/wrap.py +++ b/cursorless-talon/src/actions/wrap.py @@ -1,47 +1,53 @@ -from dataclasses import dataclass -from typing import Literal, Union - -from talon import Module, actions - -from ..paired_delimiter import paired_delimiters_map - - -@dataclass -class Wrapper: - type: Literal["pairedDelimiter", "snippet"] - extra_args: list[Union[str, dict]] +from talon import Module +from ..command import cursorless_command_and_wait +from ..paired_delimiter import PairedDelimiter +from ..targets.target_types import CursorlessTarget mod = Module() mod.list("cursorless_wrap_action", desc="Cursorless wrap action") -@mod.capture( - rule=" | {user.cursorless_wrapper_snippet}" -) -def cursorless_wrapper(m) -> Wrapper: - try: - paired_delimiter_info = paired_delimiters_map[ - m.cursorless_wrapper_paired_delimiter - ] - return Wrapper( - type="pairedDelimiter", - extra_args=[paired_delimiter_info.left, paired_delimiter_info.right], - ) - except AttributeError: - snippet_name, variable_name = parse_snippet_location( - m.cursorless_wrapper_snippet +@mod.action_class +class Actions: + def private_cursorless_wrap_paired_delimiter( + action_name: str, target: CursorlessTarget, paired_delimiter: PairedDelimiter + ): + """Execute Cursorless wrap/rewrap with paired delimiter action""" + if action_name == "rewrap": + action_name = "rewrapWithPairedDelimiter" + + cursorless_command_and_wait( + { + "name": action_name, + "left": paired_delimiter.left, + "right": paired_delimiter.right, + "target": target, + } ) - return Wrapper( - type="snippet", - extra_args=[ - { + + def private_cursorless_wrap_snippet( + action_name: str, target: CursorlessTarget, snippet_location: str + ): + """Execute Cursorless wrap with snippet action""" + if action_name == "wrapWithPairedDelimiter": + action_name = "wrapWithSnippet" + elif action_name == "rewrap": + raise Exception("Rewrapping with snippet not supported") + + snippet_name, variable_name = parse_snippet_location(snippet_location) + + cursorless_command_and_wait( + { + "name": action_name, + "snippetDescription": { "type": "named", "name": snippet_name, "variableName": variable_name, - } - ], + }, + "target": target, + } ) @@ -50,26 +56,3 @@ def parse_snippet_location(snippet_location: str) -> tuple[str, str]: if snippet_name is None or variable_name is None: raise Exception("Snippet location missing '.'") return (snippet_name, variable_name) - - -# Maps from (action_type, wrapper_type) to action name -action_map: dict[tuple[str, Literal["pairedDelimiter", "snippet"]], str] = { - ("wrapWithPairedDelimiter", "pairedDelimiter"): "wrapWithPairedDelimiter", - # This is awkward because we used an action name which was to verbose previously - ("wrapWithPairedDelimiter", "snippet"): "wrapWithSnippet", - ("rewrap", "pairedDelimiter"): "rewrapWithPairedDelimiter", - # Not yet supported - ("rewrap", "snippet"): "rewrapWithSnippet", -} - - -@mod.action_class -class Actions: - def cursorless_wrap(action_type: str, target: dict, cursorless_wrapper: Wrapper): - """Perform cursorless wrap action""" - wrapper_type = cursorless_wrapper.type - action = action_map[(action_type, wrapper_type)] - - actions.user.cursorless_single_target_command_with_arg_list( - action, target, cursorless_wrapper.extra_args - ) diff --git a/cursorless-talon/src/apps/cursorless_vscode.py b/cursorless-talon/src/apps/cursorless_vscode.py index 64d1df4119..f3a022f586 100644 --- a/cursorless-talon/src/apps/cursorless_vscode.py +++ b/cursorless-talon/src/apps/cursorless_vscode.py @@ -2,6 +2,7 @@ from ..actions.get_text import get_text from ..cursorless_command_server import run_rpc_command_no_wait +from ..targets.target_types import CursorlessTarget ctx = Context() @@ -14,7 +15,7 @@ @ctx.action_class("user") class Actions: - def cursorless_private_run_find_action(target: dict): + def private_cursorless_find(target: CursorlessTarget): """Find text of target in editor""" texts = get_text(target, ensure_single_target=True) search_text = texts[0] @@ -25,7 +26,7 @@ def cursorless_private_run_find_action(target: dict): actions.sleep("50ms") actions.insert(search_text) - def cursorless_show_settings_in_ide(): + def private_cursorless_show_settings_in_ide(): """Show Cursorless-specific settings in ide""" run_rpc_command_no_wait("workbench.action.openGlobalSettings") actions.sleep("250ms") diff --git a/cursorless-talon/src/cheatsheet/cheat_sheet.py b/cursorless-talon/src/cheatsheet/cheat_sheet.py index 6b7d1182f8..49160da38f 100644 --- a/cursorless-talon/src/cheatsheet/cheat_sheet.py +++ b/cursorless-talon/src/cheatsheet/cheat_sheet.py @@ -1,148 +1,148 @@ -import webbrowser -from pathlib import Path - -from talon import Context, Module, app - -from ..cursorless_command_server import run_rpc_command_and_wait -from .get_list import get_list, get_lists -from .sections.actions import get_actions -from .sections.compound_targets import get_compound_targets -from .sections.modifiers import get_modifiers -from .sections.scopes import get_scopes -from .sections.special_marks import get_special_marks - -mod = Module() -ctx = Context() -ctx.matches = r""" -tag: user.cursorless -""" - -instructions_url = "https://www.cursorless.org/docs/" - - -@mod.action_class -class Actions: - def cursorless_cheat_sheet_show_html(): - """Show new cursorless html cheat sheet""" - app.notify( - 'Please first focus an app that supports cursorless, eg say "focus code"' - ) - - def cursorless_cheat_sheet_update_json(): - """Update default cursorless cheatsheet json (for developer use only)""" - app.notify( - 'Please first focus an app that supports cursorless, eg say "focus code"' - ) - - def cursorless_open_instructions(): - """Open web page with cursorless instructions""" - webbrowser.open(instructions_url) - - -@ctx.action_class("user") -class CursorlessActions: - def cursorless_cheat_sheet_show_html(): - """Show cursorless html cheat sheet""" - # On Linux browsers installed using snap can't open files in a hidden directory - if app.platform == "linux": - cheatsheet_out_dir = cheatsheet_dir_linux() - cheatsheet_filename = "cursorless-cheatsheet.html" - else: - cheatsheet_out_dir = Path.home() / ".cursorless" - cheatsheet_filename = "cheatsheet.html" - - cheatsheet_out_dir.mkdir(parents=True, exist_ok=True) - cheatsheet_out_path = cheatsheet_out_dir / cheatsheet_filename - run_rpc_command_and_wait( - "cursorless.showCheatsheet", - { - "version": 0, - "spokenFormInfo": cursorless_cheat_sheet_get_json(), - "outputPath": str(cheatsheet_out_path), - }, - ) - webbrowser.open(cheatsheet_out_path.as_uri()) - - def cursorless_cheat_sheet_update_json(): - """Update default cursorless cheatsheet json (for developer use only)""" - run_rpc_command_and_wait( - "cursorless.internal.updateCheatsheetDefaults", - cursorless_cheat_sheet_get_json(), - ) - - -def cheatsheet_dir_linux() -> Path: - """Get cheatsheet directory for Linux""" - try: - # 1. Get users actual document directory - import platformdirs - - return Path(platformdirs.user_documents_dir()) - except Exception: - # 2. Look for a documents directory in user home - user_documents_dir = Path.home() / "Documents" - if user_documents_dir.is_dir(): - return user_documents_dir - - # 3. Fall back to user home - return Path.home() - - -def cursorless_cheat_sheet_get_json(): - """Get cursorless cheat sheet json""" - return { - "sections": [ - { - "name": "Actions", - "id": "actions", - "items": get_actions(), - }, - { - "name": "Scopes", - "id": "scopes", - "items": get_scopes(), - }, - { - "name": "Modifiers", - "id": "modifiers", - "items": get_modifiers(), - }, - { - "name": "Paired delimiters", - "id": "pairedDelimiters", - "items": get_lists( - [ - "wrapper_only_paired_delimiter", - "wrapper_selectable_paired_delimiter", - "selectable_only_paired_delimiter", - ], - "pairedDelimiter", - ), - }, - { - "name": "Special marks", - "id": "specialMarks", - "items": get_special_marks(), - }, - { - "name": "Positions", - "id": "positions", - "items": get_list("position", "position"), - }, - { - "name": "Compound targets", - "id": "compoundTargets", - "items": get_compound_targets(), - }, - { - "name": "Colors", - "id": "colors", - "items": get_list("hat_color", "hatColor"), - }, - { - "name": "Shapes", - "id": "shapes", - "items": get_list("hat_shape", "hatShape"), - }, - ] - } +# import webbrowser +# from pathlib import Path + +# from talon import Context, Module, app + +# from ..cursorless_command_server import run_rpc_command_and_wait +# from .get_list import get_list, get_lists +# from .sections.actions import get_actions +# from .sections.compound_targets import get_compound_targets +# from .sections.modifiers import get_modifiers +# from .sections.scopes import get_scopes +# from .sections.special_marks import get_special_marks + +# mod = Module() +# ctx = Context() +# ctx.matches = r""" +# tag: user.cursorless +# """ + +# instructions_url = "https://www.cursorless.org/docs/" + + +# @mod.action_class +# class Actions: +# def cursorless_cheat_sheet_show_html(): +# """Show new cursorless html cheat sheet""" +# app.notify( +# 'Please first focus an app that supports cursorless, eg say "focus code"' +# ) + +# def cursorless_cheat_sheet_update_json(): +# """Update default cursorless cheatsheet json (for developer use only)""" +# app.notify( +# 'Please first focus an app that supports cursorless, eg say "focus code"' +# ) + +# def cursorless_open_instructions(): +# """Open web page with cursorless instructions""" +# webbrowser.open(instructions_url) + + +# @ctx.action_class("user") +# class CursorlessActions: +# def cursorless_cheat_sheet_show_html(): +# """Show cursorless html cheat sheet""" +# # On Linux browsers installed using snap can't open files in a hidden directory +# if app.platform == "linux": +# cheatsheet_out_dir = cheatsheet_dir_linux() +# cheatsheet_filename = "cursorless-cheatsheet.html" +# else: +# cheatsheet_out_dir = Path.home() / ".cursorless" +# cheatsheet_filename = "cheatsheet.html" + +# cheatsheet_out_dir.mkdir(parents=True, exist_ok=True) +# cheatsheet_out_path = cheatsheet_out_dir / cheatsheet_filename +# run_rpc_command_and_wait( +# "cursorless.showCheatsheet", +# { +# "version": 0, +# "spokenFormInfo": cursorless_cheat_sheet_get_json(), +# "outputPath": str(cheatsheet_out_path), +# }, +# ) +# webbrowser.open(cheatsheet_out_path.as_uri()) + +# def cursorless_cheat_sheet_update_json(): +# """Update default cursorless cheatsheet json (for developer use only)""" +# run_rpc_command_and_wait( +# "cursorless.internal.updateCheatsheetDefaults", +# cursorless_cheat_sheet_get_json(), +# ) + + +# def cheatsheet_dir_linux() -> Path: +# """Get cheatsheet directory for Linux""" +# try: +# # 1. Get users actual document directory +# import platformdirs + +# return Path(platformdirs.user_documents_dir()) +# except Exception: +# # 2. Look for a documents directory in user home +# user_documents_dir = Path.home() / "Documents" +# if user_documents_dir.is_dir(): +# return user_documents_dir + +# # 3. Fall back to user home +# return Path.home() + + +# def cursorless_cheat_sheet_get_json(): +# """Get cursorless cheat sheet json""" +# return { +# "sections": [ +# { +# "name": "Actions", +# "id": "actions", +# "items": get_actions(), +# }, +# { +# "name": "Scopes", +# "id": "scopes", +# "items": get_scopes(), +# }, +# { +# "name": "Modifiers", +# "id": "modifiers", +# "items": get_modifiers(), +# }, +# { +# "name": "Paired delimiters", +# "id": "pairedDelimiters", +# "items": get_lists( +# [ +# "wrapper_only_paired_delimiter", +# "wrapper_selectable_paired_delimiter", +# "selectable_only_paired_delimiter", +# ], +# "pairedDelimiter", +# ), +# }, +# { +# "name": "Special marks", +# "id": "specialMarks", +# "items": get_special_marks(), +# }, +# { +# "name": "Positions", +# "id": "positions", +# "items": get_list("position", "position"), +# }, +# { +# "name": "Compound targets", +# "id": "compoundTargets", +# "items": get_compound_targets(), +# }, +# { +# "name": "Colors", +# "id": "colors", +# "items": get_list("hat_color", "hatColor"), +# }, +# { +# "name": "Shapes", +# "id": "shapes", +# "items": get_list("hat_shape", "hatShape"), +# }, +# ] +# } diff --git a/cursorless-talon/src/cheatsheet/sections/compound_targets.py b/cursorless-talon/src/cheatsheet/sections/compound_targets.py index faf7383d88..ab475dc702 100644 --- a/cursorless-talon/src/cheatsheet/sections/compound_targets.py +++ b/cursorless-talon/src/cheatsheet/sections/compound_targets.py @@ -1,59 +1,59 @@ -from ..get_list import get_raw_list +# from ..get_list import get_raw_list -FORMATTERS = { - "rangeExclusive": lambda start, end: f"between {start} and {end}", - "rangeInclusive": lambda start, end: f"{start} through {end}", - "rangeExcludingStart": lambda start, end: f"end of {start} through {end}", - "rangeExcludingEnd": lambda start, end: f"{start} until start of {end}", - "verticalRange": lambda start, end: f"{start} vertically through {end}", -} +# FORMATTERS = { +# "rangeExclusive": lambda start, end: f"between {start} and {end}", +# "rangeInclusive": lambda start, end: f"{start} through {end}", +# "rangeExcludingStart": lambda start, end: f"end of {start} through {end}", +# "rangeExcludingEnd": lambda start, end: f"{start} until start of {end}", +# "verticalRange": lambda start, end: f"{start} vertically through {end}", +# } -def get_compound_targets(): - list_connective_term = next( - spoken_form - for spoken_form, value in get_raw_list("list_connective").items() - if value == "listConnective" - ) - vertical_range_term = next( - spoken_form - for spoken_form, value in get_raw_list("range_type").items() - if value == "verticalRange" - ) +# def get_compound_targets(): +# list_connective_term = next( +# spoken_form +# for spoken_form, value in get_raw_list("list_connective").items() +# if value == "listConnective" +# ) +# vertical_range_term = next( +# spoken_form +# for spoken_form, value in get_raw_list("range_type").items() +# if value == "verticalRange" +# ) - return [ - { - "id": "listConnective", - "type": "compoundTargetConnective", - "variations": [ - { - "spokenForm": f" {list_connective_term} ", - "description": " and ", - }, - ], - }, - *[ - get_entry(spoken_form, id) - for spoken_form, id in get_raw_list("range_connective").items() - ], - get_entry(vertical_range_term, "verticalRange"), - ] +# return [ +# { +# "id": "listConnective", +# "type": "compoundTargetConnective", +# "variations": [ +# { +# "spokenForm": f" {list_connective_term} ", +# "description": " and ", +# }, +# ], +# }, +# *[ +# get_entry(spoken_form, id) +# for spoken_form, id in get_raw_list("range_connective").items() +# ], +# get_entry(vertical_range_term, "verticalRange"), +# ] -def get_entry(spoken_form, id): - formatter = FORMATTERS[id] +# def get_entry(spoken_form, id): +# formatter = FORMATTERS[id] - return { - "id": id, - "type": "compoundTargetConnective", - "variations": [ - { - "spokenForm": f" {spoken_form} ", - "description": formatter("", ""), - }, - { - "spokenForm": f"{spoken_form} ", - "description": formatter("selection", ""), - }, - ], - } +# return { +# "id": id, +# "type": "compoundTargetConnective", +# "variations": [ +# { +# "spokenForm": f" {spoken_form} ", +# "description": formatter("", ""), +# }, +# { +# "spokenForm": f"{spoken_form} ", +# "description": formatter("selection", ""), +# }, +# ], +# } diff --git a/cursorless-talon/src/command.py b/cursorless-talon/src/command.py index 3e7060e852..bca99dc0a2 100644 --- a/cursorless-talon/src/command.py +++ b/cursorless-talon/src/command.py @@ -1,18 +1,23 @@ -from typing import Any +from dataclasses import dataclass -from talon import Module, actions, speech_system +from talon import actions, speech_system from .cursorless_command_server import ( run_rpc_command_and_wait, run_rpc_command_get, run_rpc_command_no_wait, ) -from .primitive_target import create_implicit_target -mod = Module() + +@dataclass +class CursorlessCommand: + version = 6 + spokenForm: str + usePrePhraseSnapshot: bool + action: dict + CURSORLESS_COMMAND_ID = "cursorless.command" -CURSORLESS_COMMAND_VERSION = 5 last_phrase = None @@ -24,129 +29,61 @@ def on_phrase(d): speech_system.register("pre:phrase", on_phrase) -class NotSet: - def __repr__(self): - return "" +def cursorless_command_and_wait(action: dict): + """Execute cursorless command and wait for it to finish""" + return run_rpc_command_and_wait( + CURSORLESS_COMMAND_ID, + construct_cursorless_command(action), + ) -@mod.action_class -class Actions: - def cursorless_single_target_command( - action: str, - target: dict, - arg1: Any = NotSet, - arg2: Any = NotSet, - arg3: Any = NotSet, - ): - """Execute single-target cursorless command""" - actions.user.cursorless_multiple_target_command( - action, [target], arg1, arg2, arg3 - ) - - def cursorless_single_target_command_no_wait( - action: str, - target: dict, - arg1: Any = NotSet, - arg2: Any = NotSet, - arg3: Any = NotSet, - ): - """Execute single-target cursorless command""" - actions.user.cursorless_multiple_target_command_no_wait( - action, [target], arg1, arg2, arg3 - ) +def cursorless_command_get(action: dict): + """Execute cursorless command and return result""" + return run_rpc_command_get( + CURSORLESS_COMMAND_ID, + construct_cursorless_command(action), + ) - def cursorless_single_target_command_with_arg_list( - action: str, target: dict, args: list[Any] - ): - """Execute single-target cursorless command with argument list""" - actions.user.cursorless_single_target_command( - action, - target, - *args, - ) - def cursorless_single_target_command_get( - action: str, - target: dict, - arg1: Any = NotSet, - arg2: Any = NotSet, - arg3: Any = NotSet, - ): - """Execute single-target cursorless command and return result""" - return run_rpc_command_get( - CURSORLESS_COMMAND_ID, - construct_cursorless_command_argument( - action=action, - targets=[target], - args=[x for x in [arg1, arg2, arg3] if x is not NotSet], - ), - ) +def cursorless_command_no_wait(action: dict): + """Execute cursorless command without waiting""" + run_rpc_command_no_wait( + CURSORLESS_COMMAND_ID, + construct_cursorless_command(action), + ) - def cursorless_implicit_target_command( - action: str, - arg1: Any = NotSet, - arg2: Any = NotSet, - arg3: Any = NotSet, - ): - """Execute cursorless command with implicit target""" - actions.user.cursorless_single_target_command( - action, create_implicit_target(), arg1, arg2, arg3 - ) - def cursorless_multiple_target_command( - action: str, - targets: list[dict], - arg1: Any = NotSet, - arg2: Any = NotSet, - arg3: Any = NotSet, - ): - """Execute multi-target cursorless command""" - run_rpc_command_and_wait( - CURSORLESS_COMMAND_ID, - construct_cursorless_command_argument( - action=action, - targets=targets, - args=[x for x in [arg1, arg2, arg3] if x is not NotSet], - ), - ) - - def cursorless_multiple_target_command_no_wait( - action: str, - targets: list[dict], - arg1: Any = NotSet, - arg2: Any = NotSet, - arg3: Any = NotSet, - ): - """Execute multi-target cursorless command""" - run_rpc_command_no_wait( - CURSORLESS_COMMAND_ID, - construct_cursorless_command_argument( - action=action, - targets=targets, - args=[x for x in [arg1, arg2, arg3] if x is not NotSet], - ), - ) - - -def construct_cursorless_command_argument( - action: str, targets: list[dict], args: list[Any] -): +def construct_cursorless_command(action: dict) -> dict: try: use_pre_phrase_snapshot = actions.user.did_emit_pre_phrase_signal() except KeyError: use_pre_phrase_snapshot = False - return { - "version": CURSORLESS_COMMAND_VERSION, - "spokenForm": get_spoken_form(), - "action": { - "name": action, - "args": args, - }, - "targets": targets, - "usePrePhraseSnapshot": use_pre_phrase_snapshot, - } + return makes_serializable( + CursorlessCommand( + get_spoken_form(), + use_pre_phrase_snapshot, + action, + ) + ) def get_spoken_form(): return " ".join(last_phrase["phrase"]) + + +def makes_serializable(value: any): + if isinstance(value, dict): + return {k: makes_serializable(v) for k, v in value.items()} + if isinstance(value, list): + return [makes_serializable(v) for v in value] + try: + items = value.__dict__ + class_items = {k: v for k, v in value.__class__.__dict__.items() if k[0] != "_"} + all_items = {**class_items, **items} + return {k: makes_serializable(v) for k, v in all_items.items()} + except AttributeError: + return value + + if dataclasses.is_dataclass(o): + return dataclasses.asdict(o) diff --git a/cursorless-talon/src/compound_targets.py b/cursorless-talon/src/compound_targets.py deleted file mode 100644 index bd2bb2715a..0000000000 --- a/cursorless-talon/src/compound_targets.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import Any - -from talon import Module - -from .connective import default_range_connective -from .primitive_target import create_implicit_target - -mod = Module() - -mod.list( - "cursorless_range_connective", - desc="A range joiner that indicates whether to include or exclude anchor and active", -) -mod.list( - "cursorless_list_connective", - desc="A list joiner", -) - - -@mod.capture( - rule="[] {user.cursorless_range_connective} | " -) -def cursorless_range_connective_with_type(m) -> dict[str, Any]: - return { - "connective": getattr( - m, "cursorless_range_connective", default_range_connective - ), - "type": getattr(m, "cursorless_range_type", None), - } - - -@mod.capture( - rule=( - " | " - " | " - " " - ) -) -def cursorless_range(m) -> dict[str, Any]: - primitive_targets = m.cursorless_primitive_target_list - range_connective_with_type = getattr( - m, "cursorless_range_connective_with_type", None - ) - - if range_connective_with_type is None: - return primitive_targets[0] - - if len(primitive_targets) == 1: - anchor = create_implicit_target() - else: - anchor = primitive_targets[0] - - range_connective = range_connective_with_type["connective"] - range_type = range_connective_with_type["type"] - - range = { - "type": "range", - "anchor": anchor, - "active": primitive_targets[-1], - "excludeAnchor": not is_anchor_included(range_connective), - "excludeActive": not is_active_included(range_connective), - } - - if range_type: - range["rangeType"] = range_type - - return range - - -def is_anchor_included(range_connective: str): - return range_connective not in ["rangeExclusive", "rangeExcludingStart"] - - -def is_active_included(range_connective: str): - return range_connective not in ["rangeExclusive", "rangeExcludingEnd"] - - -@mod.capture( - rule=" ({user.cursorless_list_connective} )*" -) -def cursorless_target(m) -> dict: - if len(m.cursorless_range_list) == 1: - return m.cursorless_range - return { - "type": "list", - "elements": m.cursorless_range_list, - } diff --git a/cursorless-talon/src/connective.py b/cursorless-talon/src/connective.py index 05723e9cd6..2e14b4986b 100644 --- a/cursorless-talon/src/connective.py +++ b/cursorless-talon/src/connective.py @@ -4,11 +4,6 @@ mod = Module() -mod.list( - "cursorless_source_destination_connective", - desc="The connective used to separate source and destination targets", -) - # NOTE: Please do not change these dicts. Use the CSVs for customization. # See https://www.cursorless.org/docs/user/customization/ @@ -20,9 +15,6 @@ } -default_range_connective = "rangeInclusive" - - def on_ready(): init_csv_and_watch_changes( "target_connectives", @@ -30,7 +22,7 @@ def on_ready(): "range_connective": range_connectives, "list_connective": {"and": "listConnective"}, "swap_connective": {"with": "swapConnective"}, - "source_destination_connective": {"to": "sourceDestinationConnective"}, + "insertion_mode_to": {"to": "sourceDestinationConnective"}, }, ) diff --git a/cursorless-talon/src/cursorless.py b/cursorless-talon/src/cursorless.py index 6b551c96af..86147fb1eb 100644 --- a/cursorless-talon/src/cursorless.py +++ b/cursorless-talon/src/cursorless.py @@ -10,5 +10,5 @@ @mod.action_class class Actions: - def cursorless_show_settings_in_ide(): + def private_cursorless_show_settings_in_ide(): """Show Cursorless-specific settings in ide""" diff --git a/cursorless-talon/src/cursorless.talon b/cursorless-talon/src/cursorless.talon index a9a60a0cb5..4742cd1cd5 100644 --- a/cursorless-talon/src/cursorless.talon +++ b/cursorless-talon/src/cursorless.talon @@ -2,21 +2,33 @@ tag: user.cursorless - : - user.cursorless_action_or_ide_command(cursorless_action_or_ide_command, cursorless_target) + user.private_cursorless_action_or_ide_command(cursorless_action_or_ide_command, cursorless_target) -{user.cursorless_positional_action} : - user.cursorless_single_target_command(cursorless_positional_action, cursorless_positional_target) +{user.cursorless_bring_move_action} : + user.private_cursorless_bring_move(cursorless_bring_move_action, cursorless_bring_move_targets) {user.cursorless_swap_action} : - user.cursorless_multiple_target_command(cursorless_swap_action, cursorless_swap_targets) + user.private_cursorles_swap(cursorless_swap_targets) -{user.cursorless_move_bring_action} : - user.cursorless_multiple_target_command(cursorless_move_bring_action, cursorless_move_bring_targets) +{user.cursorless_paste_action} : + user.private_cursorless_paste(cursorless_destination) {user.cursorless_reformat_action} at : - user.cursorless_reformat(cursorless_target, formatters) + user.private_cursorless_reformat(cursorless_target, formatters) - {user.cursorless_wrap_action} : - user.cursorless_wrap(cursorless_wrap_action, cursorless_target, cursorless_wrapper) + {user.cursorless_wrap_action} : + user.private_cursorless_wrap_paired_delimiter(cursorless_wrap_action, cursorless_wrapper_paired_delimiter) -{user.cursorless_homophone} settings: user.cursorless_show_settings_in_ide() +{user.cursorless_wrapper_snippet} {user.cursorless_wrap_action} : + user.private_cursorless_wrap_snippet(cursorless_wrap_action, cursorless_target, cursorless_wrapper_snippet) + +# {user.cursorless_insert_snippet_action} : +# user.cursorless_implicit_target_command(cursorless_insert_snippet_action, cursorless_insertion_snippet) + +# {user.cursorless_insert_snippet_action} : +# user.cursorless_single_target_command(cursorless_insert_snippet_action, cursorless_positional_target, cursorless_insertion_snippet) + +# {user.cursorless_insert_snippet_action} {user.cursorless_insertion_snippet_single_phrase} [{user.cursorless_phrase_terminator}]: +# user.private_cursorless_insert_snippet_with_phrase(cursorless_insert_snippet_action, cursorless_insertion_snippet_single_phrase, text) + +{user.cursorless_homophone} settings: user.private_cursorless_show_settings_in_ide() diff --git a/cursorless-talon/src/cursorless_snippets.talon b/cursorless-talon/src/cursorless_snippets.talon deleted file mode 100644 index c9e428680d..0000000000 --- a/cursorless-talon/src/cursorless_snippets.talon +++ /dev/null @@ -1,11 +0,0 @@ -tag: user.cursorless -- - -{user.cursorless_insert_snippet_action} : - user.cursorless_implicit_target_command(cursorless_insert_snippet_action, cursorless_insertion_snippet) - -{user.cursorless_insert_snippet_action} : - user.cursorless_single_target_command(cursorless_insert_snippet_action, cursorless_positional_target, cursorless_insertion_snippet) - -{user.cursorless_insert_snippet_action} {user.cursorless_insertion_snippet_single_phrase} [{user.cursorless_phrase_terminator}]: - user.private_cursorless_insert_snippet_with_phrase(cursorless_insert_snippet_action, cursorless_insertion_snippet_single_phrase, text) diff --git a/cursorless-talon/src/marks/lines_number.py b/cursorless-talon/src/marks/lines_number.py index 446cef60db..8261ed04ee 100644 --- a/cursorless-talon/src/marks/lines_number.py +++ b/cursorless-talon/src/marks/lines_number.py @@ -4,7 +4,7 @@ from talon import Context, Module -from ..compound_targets import is_active_included, is_anchor_included +from ..targets.range_target import RangeConnective mod = Module() ctx = Context() @@ -37,7 +37,10 @@ class CustomizableTerm: @mod.capture( - rule="{user.cursorless_line_direction} [{user.cursorless_range_connective} ]" + rule=( + "{user.cursorless_line_direction} " + "[{user.cursorless_range_connective} ]" + ) ) def cursorless_line_number(m) -> dict[str, Any]: direction = directions_map[m.cursorless_line_direction] @@ -49,14 +52,13 @@ def cursorless_line_number(m) -> dict[str, Any]: direction.type, direction.formatter(m.private_cursorless_number_small_list[1]), ) - include_anchor = is_anchor_included(m.cursorless_range_connective) - include_active = is_active_included(m.cursorless_range_connective) + range_connective: RangeConnective = m.cursorless_range_connective return { "type": "range", "anchor": anchor, "active": active, - "excludeAnchor": not include_anchor, - "excludeActive": not include_active, + "excludeAnchor": range_connective.excludeAnchor, + "excludeActive": range_connective.excludeActive, } return anchor diff --git a/cursorless-talon/src/modifiers/modifiers.py b/cursorless-talon/src/modifiers/modifiers.py index b4a7e25375..5152bdfd7a 100644 --- a/cursorless-talon/src/modifiers/modifiers.py +++ b/cursorless-talon/src/modifiers/modifiers.py @@ -1,10 +1,10 @@ from talon import Module, app from ..csv_overrides import init_csv_and_watch_changes +from ..targets.range_type import range_types from .head_tail import head_tail_modifiers from .interior import interior_modifiers from .ordinal_scope import first_modifiers, last_modifiers -from .range_type import range_types from .relative_scope import forward_backward_modifiers, previous_next_modifiers from .simple_scope_modifier import simple_scope_modifiers @@ -53,6 +53,7 @@ def cursorless_simple_modifier(m) -> dict[str, str]: modifiers = [ "", # inside "", # head, tail + "", # start of, end of *head_tail_swallowed_modifiers, ] diff --git a/cursorless-talon/src/modifiers/ordinal_scope.py b/cursorless-talon/src/modifiers/ordinal_scope.py index 103f28c5fd..78dc375a3a 100644 --- a/cursorless-talon/src/modifiers/ordinal_scope.py +++ b/cursorless-talon/src/modifiers/ordinal_scope.py @@ -2,7 +2,7 @@ from talon import Module -from ..compound_targets import is_active_included, is_anchor_included +from ..targets.range_target import RangeConnective first_modifiers = {"first": "first"} last_modifiers = {"last": "last"} @@ -35,14 +35,13 @@ def cursorless_ordinal_range(m) -> dict[str, Any]: active = create_ordinal_scope_modifier( m.cursorless_scope_type, m.ordinal_or_last_list[1] ) - include_anchor = is_anchor_included(m.cursorless_range_connective) - include_active = is_active_included(m.cursorless_range_connective) + range_connective: RangeConnective = m.cursorless_range_connective return { "type": "range", "anchor": anchor, "active": active, - "excludeAnchor": not include_anchor, - "excludeActive": not include_active, + "excludeAnchor": range_connective.excludeAnchor, + "excludeActive": range_connective.excludeActive, } return anchor diff --git a/cursorless-talon/src/modifiers/position.py b/cursorless-talon/src/modifiers/position.py index 20c144dd0e..4c57964244 100644 --- a/cursorless-talon/src/modifiers/position.py +++ b/cursorless-talon/src/modifiers/position.py @@ -14,26 +14,25 @@ positions = { "start of": "start", "end of": "end", +} +destinations = { "before": "before", "after": "after", } -def construct_positional_modifier(position: str) -> dict[str, Any]: - return {"type": "position", "position": position} - - -# Note that we allow positional connectives such as "before" and "after" to appear -# as modifiers. We may disallow this in the future. @mod.capture(rule="{user.cursorless_position}") -def cursorless_position(m) -> dict[str, Any]: - return construct_positional_modifier(m.cursorless_position) +def cursorless_position_modifier(m) -> dict[str, Any]: + return {"type": "startOf" if m.cursorless_position == "start" else "endOf"} def on_ready(): init_csv_and_watch_changes( "positions", - {"position": positions}, + { + "position": positions, + "insertion_mode_pos": destinations, + }, ) diff --git a/cursorless-talon/src/paired_delimiter.py b/cursorless-talon/src/paired_delimiter.py index 18daeb9472..45f87a0b18 100644 --- a/cursorless-talon/src/paired_delimiter.py +++ b/cursorless-talon/src/paired_delimiter.py @@ -82,11 +82,12 @@ class PairedDelimiter: "{user.cursorless_wrapper_selectable_paired_delimiter}" ) ) -def cursorless_wrapper_paired_delimiter(m) -> str: +def cursorless_wrapper_paired_delimiter(m) -> PairedDelimiter: try: - return m.cursorless_wrapper_only_paired_delimiter + id = m.cursorless_wrapper_only_paired_delimiter except AttributeError: - return m.cursorless_wrapper_selectable_paired_delimiter + id = m.cursorless_wrapper_selectable_paired_delimiter + return paired_delimiters_map[id] @mod.capture( diff --git a/cursorless-talon/src/positional_target.py b/cursorless-talon/src/positional_target.py deleted file mode 100644 index ab66284659..0000000000 --- a/cursorless-talon/src/positional_target.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import Any - -from talon import Module - -from .modifiers.position import construct_positional_modifier - -mod = Module() - - -@mod.capture( - rule=( - "({user.cursorless_position} | {user.cursorless_source_destination_connective}) " - "" - ) -) -def cursorless_positional_target(m) -> dict[str, Any]: - target: dict[str, Any] = m.cursorless_target - try: - modifier = construct_positional_modifier(m.cursorless_position) - return update_first_primitive_target(target, modifier) - except AttributeError: - return target - - -def update_first_primitive_target(target: dict[str, Any], modifier: dict[str, Any]): - if target["type"] == "primitive": - if "modifiers" not in target: - target["modifiers"] = [] - target["modifiers"].insert(0, modifier) - return target - elif target["type"] == "range": - return { - **target, - "anchor": update_first_primitive_target(target["anchor"], modifier), - } - else: - elements = target["elements"] - return { - **target, - "elements": [ - update_first_primitive_target(elements[0], modifier), - *elements[1:], - ], - } diff --git a/cursorless-talon/src/primitive_target.py b/cursorless-talon/src/primitive_target.py deleted file mode 100644 index 4c0c5fe585..0000000000 --- a/cursorless-talon/src/primitive_target.py +++ /dev/null @@ -1,38 +0,0 @@ -from contextlib import suppress -from typing import Any - -from talon import Module - -mod = Module() - - -def create_base_target() -> dict[str, Any]: - return {"type": "primitive"} - - -def create_implicit_target() -> dict[str, Any]: - return {"type": "implicit"} - - -@mod.capture( - rule=( - "[] " - "(+ [] | )" - ) -) -def cursorless_primitive_target(m) -> dict[str, Any]: - """Supported extents for cursorless navigation""" - result = create_base_target() - - modifiers = [ - *getattr(m, "cursorless_position_list", []), - *getattr(m, "cursorless_modifier_list", []), - ] - - if modifiers: - result["modifiers"] = modifiers - - with suppress(AttributeError): - result["mark"] = m.cursorless_mark - - return result diff --git a/cursorless-talon/src/targets/destination.py b/cursorless-talon/src/targets/destination.py new file mode 100644 index 0000000000..924d2c2215 --- /dev/null +++ b/cursorless-talon/src/targets/destination.py @@ -0,0 +1,41 @@ +from typing import Union + +from talon import Module + +from .target_types import ListDestination, PrimitiveDestination + +mod = Module() + + +mod.list("cursorless_insertion_mode_pos", desc="Cursorless insertion mode before/after") +mod.list("cursorless_insertion_mode_to", desc="Cursorless insertion mode to") + + +@mod.capture( + rule="{user.cursorless_insertion_mode_pos} | {user.cursorless_insertion_mode_to}" +) +def cursorless_insertion_mode(m) -> str: + try: + return m.cursorless_insertion_mode_pos + except AttributeError: + return "to" + + +@mod.capture( + rule=( + " " + "({user.cursorless_list_connective} )*" + ) +) +def cursorless_destination(m) -> Union[ListDestination, PrimitiveDestination]: + destinations = [ + PrimitiveDestination(insertion_mode, target) + for insertion_mode, target in zip( + m.cursorless_insertion_mode_list, m.cursorless_target_list + ) + ] + + if len(destinations) == 1: + return destinations[0] + + return ListDestination(destinations) diff --git a/cursorless-talon/src/targets/primitive_target.py b/cursorless-talon/src/targets/primitive_target.py new file mode 100644 index 0000000000..0051a69e48 --- /dev/null +++ b/cursorless-talon/src/targets/primitive_target.py @@ -0,0 +1,17 @@ +from talon import Module + +from .target_types import PrimitiveTarget + +mod = Module() + + +@mod.capture( + rule=( + "+ [] | " + ) +) +def cursorless_primitive_target(m) -> PrimitiveTarget: + return PrimitiveTarget( + getattr(m, "cursorless_mark", None), + getattr(m, "cursorless_modifier_list", None), + ) diff --git a/cursorless-talon/src/targets/range_target.py b/cursorless-talon/src/targets/range_target.py new file mode 100644 index 0000000000..167a89889f --- /dev/null +++ b/cursorless-talon/src/targets/range_target.py @@ -0,0 +1,67 @@ +from dataclasses import dataclass +from typing import Optional + +from talon import Module + +from .target_types import ImplicitTarget, PrimitiveTarget + +mod = Module() + +mod.list( + "cursorless_range_connective", + desc="A range joiner that indicates whether to include or exclude anchor and active", +) + + +@dataclass +class RangeConnective: + excludeAnchor: bool + excludeActive: bool + + +@dataclass +class RangeConnectiveWithType: + connective: RangeConnective + type: Optional[str] + + +@mod.capture(rule="{user.cursorless_range_connective}") +def cursorless_range_connective(m) -> RangeConnective: + return RangeConnective( + m.cursorless_range_connective in ["rangeExclusive", "rangeExcludingStart"], + m.cursorless_range_connective in ["rangeExclusive", "rangeExcludingEnd"], + ) + + +@mod.capture( + rule="[] | " +) +def cursorless_range_connective_with_type(m) -> RangeConnectiveWithType: + return RangeConnectiveWithType( + getattr(m, "cursorless_range_connective", RangeConnective(False, False)), + getattr(m, "cursorless_range_type", None), + ) + + +@mod.capture( + rule=( + " | " + " " + ) +) +def cursorless_range_target(m) -> RangeTarget: + primitive_targets: list[PrimitiveTarget] = m.cursorless_primitive_target_list + range_connective_with_type: RangeConnectiveWithType = ( + m.cursorless_range_connective_with_type + ) + range_connective = range_connective_with_type.connective + + anchor = ImplicitTarget() if len(primitive_targets) == 1 else primitive_targets[0] + + return RangeTarget( + anchor, + primitive_targets[-1], + range_connective.excludeAnchor, + range_connective.excludeActive, + range_connective_with_type.type, + ) diff --git a/cursorless-talon/src/modifiers/range_type.py b/cursorless-talon/src/targets/range_type.py similarity index 100% rename from cursorless-talon/src/modifiers/range_type.py rename to cursorless-talon/src/targets/range_type.py diff --git a/cursorless-talon/src/targets/target.py b/cursorless-talon/src/targets/target.py new file mode 100644 index 0000000000..9922f0e9dc --- /dev/null +++ b/cursorless-talon/src/targets/target.py @@ -0,0 +1,35 @@ +from typing import Union + +from talon import Module + +from .primitive_target import ListTarget, PrimitiveTarget, RangeTarget + +mod = Module() + + +mod.list( + "cursorless_list_connective", + desc="A list joiner", +) + + +@mod.capture( + rule=(" | ") +) +def cursorless_primitive_or_range_target(m) -> Union[RangeTarget, PrimitiveTarget]: + return m[0] + + +@mod.capture( + rule=( + " " + "({user.cursorless_list_connective} )*" + ) +) +def cursorless_target(m) -> Union[ListTarget, RangeTarget, PrimitiveTarget]: + targets = m.cursorless_primitive_or_range_target_list + + if len(targets) == 1: + return targets[0] + + return ListTarget(targets) diff --git a/cursorless-talon/src/targets/target_types.py b/cursorless-talon/src/targets/target_types.py new file mode 100644 index 0000000000..88fae6ec55 --- /dev/null +++ b/cursorless-talon/src/targets/target_types.py @@ -0,0 +1,62 @@ +from typing import Literal, Union, Optional + + +@dataclass +class PrimitiveTarget: + type = "primitive" + mark: Optional[dict] + modifiers: Optional[list[dict]] + + +@dataclass +class ImplicitTarget: + type = "implicit" + + +@dataclass +class RangeTarget: + type = "range" + anchor: Union[PrimitiveTarget, ImplicitTarget] + active: PrimitiveTarget + excludeAnchor: bool + excludeActive: bool + rangeType: Optional[str] + + +@dataclass +class ListTarget: + type = "list" + elements: list[Union[PrimitiveTarget, RangeTarget]] + + +CursorlessTarget = Union[ + ListTarget, + RangeTarget, + PrimitiveTarget, + ImplicitTarget, +] + + +@dataclass +class PrimitiveDestination: + type = "primitive" + insertionMode: Literal["to", "before", "after"] + target: Union[ListTarget, RangeTarget, PrimitiveTarget] + + +@dataclass +class ImplicitDestination: + type = "implicit" + + +@dataclass +class ListDestination: + type = "list" + destinations: list[PrimitiveDestination] + + +CursorlessDestination = Union[ + ListDestination, + PrimitiveDestination, + ImplicitDestination, +] diff --git a/packages/cursorless-engine/src/actions/InsertCopy.ts b/packages/cursorless-engine/src/actions/InsertCopy.ts index d9caad1366..a4310e7aa0 100644 --- a/packages/cursorless-engine/src/actions/InsertCopy.ts +++ b/packages/cursorless-engine/src/actions/InsertCopy.ts @@ -87,16 +87,7 @@ class InsertCopy implements SimpleAction { ); setSelectionsWithoutFocusingEditor(editableEditor, updatedEditorSelections); - const primarySelection = editor.selections[0]; - - if ( - updatedContentSelections.some( - (selection) => selection.intersection(primarySelection) != null, - ) - ) { - // If the original target contained the user's cursor, reveal it in case it got pushed off screen - await editableEditor.revealRange(primarySelection); - } + await editableEditor.revealRange(editor.selections[0]); return { sourceMark: createThatMark(targets, insertionRanges), diff --git a/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts b/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts index 81e613bdaa..2f593a4027 100644 --- a/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts +++ b/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts @@ -209,9 +209,11 @@ function getPreviousTargetAttribute( const target = previousTargets[i]; switch (target.type) { case "primitive": { - const attributeValue = getAttribute(target); - if (attributeValue != null) { - return attributeValue; + if (getAttribute != null) { + const attributeValue = getAttribute(target); + if (attributeValue != null) { + return attributeValue; + } } break; } diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts index a656688473..e172a5d9e9 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts @@ -50,9 +50,9 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { create(modifier: Modifier): ModifierStage { switch (modifier.type) { case "startOf": - return new StartOfStage(); + return new StartOfStage(modifier); case "endOf": - return new EndOfStage(); + return new EndOfStage(modifier); case "extendThroughStartOf": return new HeadStage(this, modifier); case "extendThroughEndOf": diff --git a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts index c5f147d8b0..272e33ea10 100644 --- a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts +++ b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts @@ -17,7 +17,7 @@ import { ModifierStageFactory } from "./ModifierStageFactory"; import { MarkStage, ModifierStage } from "./PipelineStages.types"; import ImplicitStage from "./marks/ImplicitStage"; import { ContainingTokenIfUntypedEmptyStage } from "./modifiers/ConditionalModifierStages"; -import { PlainTarget } from "./targets"; +import { VerticalRangeTarget } from "./targets"; export class TargetPipelineRunner { constructor( @@ -151,7 +151,7 @@ class TargetPipeline { return [ targetsToContinuousTarget( excludeAnchor - ? getExcludedScope( + ? excludeScope( this.modifierStageFactory, anchorTarget, exclusionScopeType, @@ -159,7 +159,7 @@ class TargetPipeline { ) : anchorTarget, excludeActive - ? getExcludedScope( + ? excludeScope( this.modifierStageFactory, activeTarget, exclusionScopeType, @@ -254,17 +254,17 @@ export function processModifierStages( return targets; } -function getExcludedScope( +function excludeScope( modifierStageFactory: ModifierStageFactory, target: Target, - scopeType: ScopeType, + exclusionScopeType: ScopeType, direction: Direction, ): Target { return ( modifierStageFactory .create({ type: "relativeScope", - scopeType, + scopeType: exclusionScopeType, direction, length: 1, offset: 1, @@ -348,7 +348,7 @@ function targetsToVerticalTarget( ); results.push( - new PlainTarget({ + new VerticalRangeTarget({ editor: anchorTarget.editor, isReversed: anchorTarget.isReversed, contentRange, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/PositionStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/PositionStage.ts index a76dc163f8..46bb6929b8 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/PositionStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/PositionStage.ts @@ -1,4 +1,4 @@ -import { Range } from "@cursorless/common"; +import { EndOfModifier, Range, StartOfModifier } from "@cursorless/common"; import { Target } from "../../typings/target.types"; import { ModifierStage } from "../PipelineStages.types"; import { @@ -7,12 +7,12 @@ import { RawSelectionTarget, } from "../targets"; -abstract class PositionStage implements ModifierStage { +abstract class StartEndOfStage implements ModifierStage { run(target: Target): Target[] { const parameters: CommonTargetParameters = { editor: target.editor, isReversed: target.isReversed, - contentRange: this.getContentRange(target.contentRange), + contentRange: this.constructContentRange(target.contentRange), }; return [ @@ -22,17 +22,25 @@ abstract class PositionStage implements ModifierStage { ]; } - protected abstract getContentRange(contentRange: Range): Range; + protected abstract constructContentRange(contentRange: Range): Range; } -export class StartOfStage extends PositionStage { - protected getContentRange(contentRange: Range): Range { +export class StartOfStage extends StartEndOfStage { + constructor(_modifier: StartOfModifier) { + super(); + } + + protected constructContentRange(contentRange: Range): Range { return contentRange.start.toEmptyRange(); } } -export class EndOfStage extends PositionStage { - protected getContentRange(contentRange: Range): Range { +export class EndOfStage extends StartEndOfStage { + constructor(_modifier: EndOfModifier) { + super(); + } + + protected constructContentRange(contentRange: Range): Range { return contentRange.end.toEmptyRange(); } } diff --git a/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts b/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts index da49eb96b2..ddced4a443 100644 --- a/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts @@ -14,7 +14,7 @@ import { createContinuousRange, createContinuousRangeUntypedTarget, } from "../targetUtil/createContinuousRange"; -import { DestinationImpl } from "./DestinationImpl"; +import DestinationImpl from "./DestinationImpl"; /** Parameters supported by all target classes */ export interface MinimumTargetParameters { diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index 1b5d787f03..3c39595bdd 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -3,6 +3,7 @@ import { Range, Selection, TextEditor, + UnsupportedError, } from "@cursorless/common"; import { EditWithRangeUpdater } from "../../typings/Types"; import { @@ -11,15 +12,15 @@ import { Target, } from "../../typings/target.types"; -export class DestinationImpl implements Destination { +export default class DestinationImpl implements Destination { public readonly contentRange: Range; private readonly isLineDelimiter: boolean; private readonly isBefore: boolean; private readonly indentationString: string; constructor( - public readonly target: Target, - public readonly insertionMode: InsertionMode, + public target: Target, + public insertionMode: InsertionMode, indentationString?: string, ) { this.contentRange = getContentRange(target.contentRange, insertionMode); @@ -160,6 +161,22 @@ export class DestinationImpl implements Destination { } } +export function removalUnsupportedForPosition( + insertionMode: InsertionMode, +): Range { + if (insertionMode === "to") { + throw new UnsupportedError( + `Removal is not supported for "${insertionMode}"`, + ); + } + + const preferredModifier = insertionMode === "after" ? "trailing" : "leading"; + + throw new UnsupportedError( + `Please use "${preferredModifier}" modifier; removal is not supported for "${insertionMode}"`, + ); +} + /** Calculate the minimum indentation/padding for a range */ function getIndentationString(editor: TextEditor, range: Range) { let length = Number.MAX_SAFE_INTEGER; diff --git a/packages/cursorless-engine/src/processTargets/targets/NotebookCellDestination.ts b/packages/cursorless-engine/src/processTargets/targets/NotebookCellDestination.ts index 9f28d148bb..fe909f44e0 100644 --- a/packages/cursorless-engine/src/processTargets/targets/NotebookCellDestination.ts +++ b/packages/cursorless-engine/src/processTargets/targets/NotebookCellDestination.ts @@ -8,7 +8,7 @@ import { EditWithRangeUpdater } from "../../typings/Types"; import { Destination, EditNewActionType } from "../../typings/target.types"; import NotebookCellTarget from "./NotebookCellTarget"; -export class NotebookCellDestination implements Destination { +export default class NotebookCellDestination implements Destination { constructor( public target: NotebookCellTarget, public insertionMode: InsertionMode, @@ -34,7 +34,7 @@ export class NotebookCellDestination implements Destination { return this.target.isRaw; } - withTarget(target: NotebookCellTarget): NotebookCellDestination { + withTarget(target: NotebookCellTarget): Destination { return new NotebookCellDestination(target, this.insertionMode); } diff --git a/packages/cursorless-engine/src/processTargets/targets/NotebookCellTarget.ts b/packages/cursorless-engine/src/processTargets/targets/NotebookCellTarget.ts index cd71744ee8..e96cbfab60 100644 --- a/packages/cursorless-engine/src/processTargets/targets/NotebookCellTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/NotebookCellTarget.ts @@ -1,7 +1,7 @@ import { InsertionMode } from "@cursorless/common"; import { BaseTarget, CommonTargetParameters } from "."; import { Destination } from "../../typings/target.types"; -import { NotebookCellDestination } from "./NotebookCellDestination"; +import NotebookCellDestination from "./NotebookCellDestination"; export default class NotebookCellTarget extends BaseTarget { type = "NotebookCellTarget"; diff --git a/packages/cursorless-engine/src/processTargets/targets/PlainTarget.ts b/packages/cursorless-engine/src/processTargets/targets/PlainTarget.ts index 06d993270b..4d826524e0 100644 --- a/packages/cursorless-engine/src/processTargets/targets/PlainTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/PlainTarget.ts @@ -2,22 +2,19 @@ import { BaseTarget, CommonTargetParameters } from "."; interface PlainTargetParameters extends CommonTargetParameters { readonly isToken?: boolean; - readonly insertionDelimiter?: string; } /** * A target that has no leading or trailing delimiters so it's removal range - * just consists of the content itself. Its insertion delimiter is empty string, - * unless specified. + * just consists of the content itself. Its insertion delimiter is empty string. */ export default class PlainTarget extends BaseTarget { type = "PlainTarget"; - insertionDelimiter: string; + insertionDelimiter = ""; constructor(parameters: PlainTargetParameters) { super(parameters); this.isToken = parameters.isToken ?? true; - this.insertionDelimiter = parameters.insertionDelimiter ?? ""; } getLeadingDelimiterTarget = () => undefined; @@ -28,7 +25,6 @@ export default class PlainTarget extends BaseTarget { return { ...this.state, isToken: this.isToken, - insertionDelimiter: this.insertionDelimiter, }; } } diff --git a/packages/cursorless-engine/src/processTargets/targets/VerticalRangeTarget.ts b/packages/cursorless-engine/src/processTargets/targets/VerticalRangeTarget.ts new file mode 100644 index 0000000000..98e0eccc5c --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/targets/VerticalRangeTarget.ts @@ -0,0 +1,31 @@ +import { BaseTarget, CommonTargetParameters } from "."; + +interface VerticalRangeTargetParameters extends CommonTargetParameters { + readonly insertionDelimiter: string; +} + +/** + * A target that has no leading or trailing delimiters so it's removal range + * just consists of the content itself. Its insertion delimiter is given by argument. + */ +export default class VerticalRangeTarget extends BaseTarget { + type = "VerticalRangeTarget"; + isToken = false; + insertionDelimiter: string; + + constructor(parameters: VerticalRangeTargetParameters) { + super(parameters); + this.insertionDelimiter = parameters.insertionDelimiter; + } + + getLeadingDelimiterTarget = () => undefined; + getTrailingDelimiterTarget = () => undefined; + getRemovalRange = () => this.contentRange; + + protected getCloneParameters() { + return { + ...this.state, + insertionDelimiter: this.insertionDelimiter, + }; + } +} diff --git a/packages/cursorless-engine/src/processTargets/targets/index.ts b/packages/cursorless-engine/src/processTargets/targets/index.ts index 0154f82e78..13b4244bc2 100644 --- a/packages/cursorless-engine/src/processTargets/targets/index.ts +++ b/packages/cursorless-engine/src/processTargets/targets/index.ts @@ -6,12 +6,15 @@ export * from "./LineTarget"; export { default as LineTarget } from "./LineTarget"; export * from "./NotebookCellTarget"; export { default as NotebookCellTarget } from "./NotebookCellTarget"; -export * from "./NotebookCellDestination"; +export { default as NotebookCellDestination } from "./NotebookCellDestination"; export * from "./ParagraphTarget"; export { default as ParagraphTarget } from "./ParagraphTarget"; export * from "./PlainTarget"; export { default as PlainTarget } from "./PlainTarget"; +export * from "./VerticalRangeTarget"; +export { default as VerticalRangeTarget } from "./VerticalRangeTarget"; export * from "./DestinationImpl"; +export { default as DestinationImpl } from "./DestinationImpl"; export * from "./RawSelectionTarget"; export { default as RawSelectionTarget } from "./RawSelectionTarget"; export * from "./ScopeTypeTarget"; diff --git a/packages/cursorless-engine/src/scripts/transformRecordedTests/transformations/upgrade.ts b/packages/cursorless-engine/src/scripts/transformRecordedTests/transformations/upgrade.ts index 1926bedc9c..ec303ddbc1 100644 --- a/packages/cursorless-engine/src/scripts/transformRecordedTests/transformations/upgrade.ts +++ b/packages/cursorless-engine/src/scripts/transformRecordedTests/transformations/upgrade.ts @@ -8,6 +8,6 @@ export const upgrade = flow(upgradeCommand, reorderFields); function upgradeCommand(fixture: TestCaseFixtureLegacy): TestCaseFixture { return { ...fixture, - command: canonicalizeAndValidateCommand(fixture.command), + command: flow(canonicalizeAndValidateCommand)(fixture.command), }; } diff --git a/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts b/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts index 00ab9e6cb7..e84bd90573 100644 --- a/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts +++ b/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts @@ -21,7 +21,7 @@ import { StoredTargetMap } from ".."; import { ide } from "../singletons/ide.singleton"; import { extractTargetKeys } from "../testUtil/extractTargetKeys"; import { takeSnapshot } from "../testUtil/takeSnapshot"; -import { getPartialTargetDescriptors } from "../util/getPartialTargetDescriptors"; +import { getPartialTargetDescriptors as getPartialTargetDescriptors } from "../util/getPartialTargetDescriptors"; export class TestCase { private languageId: string; diff --git a/packages/cursorless-engine/src/util/getPartialTargetDescriptors.ts b/packages/cursorless-engine/src/util/getPartialTargetDescriptors.ts index 0b088c3aa5..4f7f3f64b0 100644 --- a/packages/cursorless-engine/src/util/getPartialTargetDescriptors.ts +++ b/packages/cursorless-engine/src/util/getPartialTargetDescriptors.ts @@ -1,6 +1,8 @@ import { ActionDescriptor, DestinationDescriptor, + PartialPrimitiveTargetDescriptor, + PartialRangeTargetDescriptor, PartialTargetDescriptor, } from "@cursorless/common"; @@ -14,7 +16,7 @@ export function getPartialTargetDescriptors( case "moveToTarget": return [ action.source, - ...getPartialTargetDescriptorsFromDestination(action.destination), + getPartialTargetDescriptorFromDestination(action.destination), ]; case "swapTargets": return [action.target1, action.target2]; @@ -22,21 +24,36 @@ export function getPartialTargetDescriptors( case "insertSnippet": case "replace": case "editNew": - return getPartialTargetDescriptorsFromDestination(action.destination); + return [getPartialTargetDescriptorFromDestination(action.destination)]; default: return [action.target]; } } -function getPartialTargetDescriptorsFromDestination( +export function getPartialTargetDescriptorFromDestination( destination: DestinationDescriptor, -): PartialTargetDescriptor[] { +): PartialTargetDescriptor { switch (destination.type) { - case "list": - return destination.destinations.map(({ target }) => target); + case "list": { + const elements: ( + | PartialPrimitiveTargetDescriptor + | PartialRangeTargetDescriptor + )[] = []; + destination.destinations.forEach((destination) => { + if (destination.target.type === "list") { + elements.push(...destination.target.elements); + } else { + elements.push(destination.target); + } + }); + return { + type: "list", + elements, + }; + } case "primitive": - return [destination.target]; + return destination.target; case "implicit": - return []; + return destination; } } diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/getTextAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/getTextAir.yml deleted file mode 100644 index bb13cd6740..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/getTextAir.yml +++ /dev/null @@ -1,36 +0,0 @@ -languageId: plaintext -command: - version: 6 - action: - name: getText - options: {showDecorations: false, ensureSingleTarget: null} - target: - type: primitive - mark: {type: decoratedSymbol, symbolColor: default, character: a} - usePrePhraseSnapshot: true -initialState: - documentContents: | - foo bar baz - selections: - - anchor: {line: 1, character: 0} - active: {line: 1, character: 0} - marks: - default.a: - start: {line: 0, character: 4} - end: {line: 0, character: 7} -finalState: - documentContents: | - foo bar baz - selections: - - anchor: {line: 1, character: 0} - active: {line: 1, character: 0} - thatMark: - - type: UntypedTarget - contentRange: - start: {line: 0, character: 4} - end: {line: 0, character: 7} - isReversed: false - hasExplicitRange: false -returnValue: [bar] -ide: - flashes: [] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveEndOfDot.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveEndOfDot.yml deleted file mode 100644 index 910716c16e..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveEndOfDot.yml +++ /dev/null @@ -1,39 +0,0 @@ -languageId: plaintext -command: - version: 6 - spokenForm: give end of dot - action: - name: deselect - target: - type: primitive - modifiers: - - {type: endOf} - mark: {type: decoratedSymbol, symbolColor: default, character: .} - usePrePhraseSnapshot: true -initialState: - documentContents: a b.c - selections: - - anchor: {line: 0, character: 1} - active: {line: 0, character: 1} - - anchor: {line: 0, character: 3} - active: {line: 0, character: 3} - - anchor: {line: 0, character: 4} - active: {line: 0, character: 4} - marks: - default..: - start: {line: 0, character: 3} - end: {line: 0, character: 4} -finalState: - documentContents: a b.c - selections: - - anchor: {line: 0, character: 1} - active: {line: 0, character: 1} - - anchor: {line: 0, character: 3} - active: {line: 0, character: 3} - thatMark: - - type: PlainTarget - contentRange: - start: {line: 0, character: 4} - end: {line: 0, character: 4} - isReversed: false - hasExplicitRange: true diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveStartOfDot.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveStartOfDot.yml deleted file mode 100644 index 6f88d94bf3..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveStartOfDot.yml +++ /dev/null @@ -1,39 +0,0 @@ -languageId: plaintext -command: - version: 6 - spokenForm: give start of dot - action: - name: deselect - target: - type: primitive - modifiers: - - {type: startOf} - mark: {type: decoratedSymbol, symbolColor: default, character: .} - usePrePhraseSnapshot: true -initialState: - documentContents: a b.c - selections: - - anchor: {line: 0, character: 1} - active: {line: 0, character: 1} - - anchor: {line: 0, character: 3} - active: {line: 0, character: 3} - - anchor: {line: 0, character: 4} - active: {line: 0, character: 4} - marks: - default..: - start: {line: 0, character: 3} - end: {line: 0, character: 4} -finalState: - documentContents: a b.c - selections: - - anchor: {line: 0, character: 1} - active: {line: 0, character: 1} - - anchor: {line: 0, character: 4} - active: {line: 0, character: 4} - thatMark: - - type: PlainTarget - contentRange: - start: {line: 0, character: 3} - end: {line: 0, character: 3} - isReversed: false - hasExplicitRange: true diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterJustThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterJustThis.yml deleted file mode 100644 index 4c02f80f8d..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterJustThis.yml +++ /dev/null @@ -1,32 +0,0 @@ -languageId: plaintext -command: - version: 6 - spokenForm: bring fine after just this - action: - name: replaceWithTarget - source: - type: primitive - mark: {type: decoratedSymbol, symbolColor: default, character: f} - destination: - type: primitive - insertionMode: after - target: - type: primitive - modifiers: - - {type: toRawSelection} - mark: {type: cursor} - usePrePhraseSnapshot: true -initialState: - documentContents: foo world - selections: - - anchor: {line: 0, character: 4} - active: {line: 0, character: 4} - marks: - default.f: - start: {line: 0, character: 0} - end: {line: 0, character: 3} -finalState: - documentContents: foo fooworld - selections: - - anchor: {line: 0, character: 4} - active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringFineAfterThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterThis.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringFineAfterThis.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterThis.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeJustThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeJustThis.yml deleted file mode 100644 index 40ca5dc850..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeJustThis.yml +++ /dev/null @@ -1,32 +0,0 @@ -languageId: plaintext -command: - version: 6 - spokenForm: bring whale before just this - action: - name: replaceWithTarget - source: - type: primitive - mark: {type: decoratedSymbol, symbolColor: default, character: w} - destination: - type: primitive - insertionMode: before - target: - type: primitive - modifiers: - - {type: toRawSelection} - mark: {type: cursor} - usePrePhraseSnapshot: true -initialState: - documentContents: foo world - selections: - - anchor: {line: 0, character: 3} - active: {line: 0, character: 3} - marks: - default.w: - start: {line: 0, character: 4} - end: {line: 0, character: 9} -finalState: - documentContents: fooworld world - selections: - - anchor: {line: 0, character: 8} - active: {line: 0, character: 8} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringWhaleBeforeThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeThis.yml similarity index 100% rename from packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringWhaleBeforeThis.yml rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeThis.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/getText.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/getText.vscode.test.ts new file mode 100644 index 0000000000..ad1dd415d0 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/getText.vscode.test.ts @@ -0,0 +1,37 @@ +import { + getCursorlessApi, + openNewEditor, + runCursorlessCommand, +} from "@cursorless/vscode-common"; +import * as assert from "assert"; +import { endToEndTestSetup } from "../endToEndTestSetup"; + +suite("getText", async function () { + endToEndTestSetup(this); + + test("getText", getText); +}); + +async function getText() { + const { hatTokenMap } = (await getCursorlessApi()).testHelpers!; + await openNewEditor("foo bar baz"); + await hatTokenMap.allocateHats(); + + const result = await runCursorlessCommand({ + version: 6, + usePrePhraseSnapshot: false, + action: { + name: "getText", + target: { + type: "primitive", + mark: { + type: "decoratedSymbol", + symbolColor: "default", + character: "b", + }, + }, + }, + }); + + assert.equal(result, "bar"); +} diff --git a/packages/cursorless-vscode/package.json b/packages/cursorless-vscode/package.json index 1aba06ab31..0a126d88ea 100644 --- a/packages/cursorless-vscode/package.json +++ b/packages/cursorless-vscode/package.json @@ -846,7 +846,7 @@ "hat-adjustment-average": "tsx --conditions=cursorless:bundler src/scripts/hatAssignments/add.ts", "compile": "tsc --build", "watch": "tsc --build --watch", - "clean": "rm -rf ./out tsconfig.tsbuildinfo ./dist" + "clean": "rm -rf ./out tsconfig.tsbuildinfo" }, "devDependencies": { "@types/chai": "^4.3.3", diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts index 80d44036c2..a666f8a60b 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts @@ -1,6 +1,8 @@ import { ActionDescriptor, ActionType, + DestinationDescriptor, + ImplicitTargetDescriptor, LATEST_VERSION, PartialPrimitiveTargetDescriptor, PartialTargetDescriptor, @@ -163,11 +165,11 @@ export default class KeyboardCommandsTargeted { }); /** - * Performs action {@link name} on the current target - * @param name The action to run + * Performs action {@link action} on the current target + * @param action The action to run * @returns A promise that resolves to the result of the cursorless command */ - performActionOnTarget = async (name: ActionType) => { + performActionOnTarget = async (action: ActionType) => { const target: PartialPrimitiveTargetDescriptor = { type: "primitive", mark: { @@ -177,7 +179,7 @@ export default class KeyboardCommandsTargeted { let returnValue: unknown; - switch (name) { + switch (action) { case "wrapWithPairedDelimiter": case "rewrapWithPairedDelimiter": case "insertSnippet": @@ -186,56 +188,52 @@ export default class KeyboardCommandsTargeted { case "replace": case "editNew": case "getText": - throw Error(`Unsupported keyboard action: ${name}`); + throw Error(`Unsupported keyboard action: ${action}`); case "replaceWithTarget": case "moveToTarget": returnValue = await executeCursorlessCommand({ - name, + name: action, source: target, - destination: { type: "implicit" }, + destination: toDestination({ type: "implicit" }), }); break; case "swapTargets": returnValue = await executeCursorlessCommand({ - name, + name: action, target1: target, target2: { type: "implicit" }, }); break; case "callAsFunction": returnValue = await executeCursorlessCommand({ - name, + name: action, callee: target, argument: { type: "implicit" }, }); break; case "pasteFromClipboard": returnValue = await executeCursorlessCommand({ - name, - destination: { - type: "primitive", - insertionMode: "to", - target, - }, + name: action, + destination: toDestination(target), }); break; case "generateSnippet": case "highlight": returnValue = await executeCursorlessCommand({ - name, + name: action, target, }); break; default: returnValue = await executeCursorlessCommand({ - name, + name: action, target, }); } await this.highlightTarget(); - if (EXIT_CURSORLESS_MODE_ACTIONS.includes(name)) { + if (EXIT_CURSORLESS_MODE_ACTIONS.includes(action)) { // For some Cursorless actions, it is more convenient if we automatically // exit modal mode await this.modal.modeOff(); @@ -284,6 +282,21 @@ function executeCursorlessCommand(action: ActionDescriptor) { }); } +function toDestination( + target: PartialPrimitiveTargetDescriptor | ImplicitTargetDescriptor, +): DestinationDescriptor { + switch (target.type) { + case "primitive": + return { + type: "primitive", + insertionMode: "to", + target, + }; + case "implicit": + return target; + } +} + const EXIT_CURSORLESS_MODE_ACTIONS: ActionType[] = [ "setSelectionBefore", "setSelectionAfter", diff --git a/packages/meta-updater/src/updatePackageJson.ts b/packages/meta-updater/src/updatePackageJson.ts index 31545fd715..6385fcced1 100644 --- a/packages/meta-updater/src/updatePackageJson.ts +++ b/packages/meta-updater/src/updatePackageJson.ts @@ -56,44 +56,28 @@ export async function updatePackageJson( }, }; - const isCursorlessVscode = input.name === "@cursorless/cursorless-vscode"; + const extraFields = + input.name === "@cursorless/cursorless-vscode" + ? getCursorlessVscodeFields(input) + : {}; - const extraFields = isCursorlessVscode - ? getCursorlessVscodeFields(input) - : {}; + const extraScripts = isRoot + ? {} + : { + clean: "rm -rf ./out tsconfig.tsbuildinfo", + }; return { ...input, name, license: "MIT", - scripts: getScripts(input.scripts, isRoot, isCursorlessVscode), + scripts: { + ...(input.scripts ?? {}), + compile: "tsc --build", + watch: "tsc --build --watch", + ...extraScripts, + }, ...exportFields, ...extraFields, } as PackageJson; } - -function getScripts( - inputScripts: PackageJson.Scripts | undefined, - isRoot: boolean, - isCursorlessVscode: boolean, -) { - const scripts: PackageJson.Scripts = { - ...(inputScripts ?? {}), - compile: "tsc --build", - watch: "tsc --build --watch", - }; - - if (isRoot) { - return scripts; - } - - const cleanDirs = ["./out", "tsconfig.tsbuildinfo"]; - - if (isCursorlessVscode) { - cleanDirs.push("./dist"); - } - - scripts.clean = `rm -rf ${cleanDirs.join(" ")}`; - - return scripts; -} From 97b8bf18186ade8ac961f87067f3a4593976fc7a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:01:18 +0000 Subject: [PATCH 02/17] [pre-commit.ci lite] apply automatic fixes --- cursorless-talon/src/cursorless.talon | 3 ++- cursorless-talon/src/targets/target_types.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cursorless-talon/src/cursorless.talon b/cursorless-talon/src/cursorless.talon index 4742cd1cd5..430378b0d7 100644 --- a/cursorless-talon/src/cursorless.talon +++ b/cursorless-talon/src/cursorless.talon @@ -31,4 +31,5 @@ tag: user.cursorless # {user.cursorless_insert_snippet_action} {user.cursorless_insertion_snippet_single_phrase} [{user.cursorless_phrase_terminator}]: # user.private_cursorless_insert_snippet_with_phrase(cursorless_insert_snippet_action, cursorless_insertion_snippet_single_phrase, text) -{user.cursorless_homophone} settings: user.private_cursorless_show_settings_in_ide() +{user.cursorless_homophone} settings: + user.private_cursorless_show_settings_in_ide() diff --git a/cursorless-talon/src/targets/target_types.py b/cursorless-talon/src/targets/target_types.py index 88fae6ec55..17ee7b2dd9 100644 --- a/cursorless-talon/src/targets/target_types.py +++ b/cursorless-talon/src/targets/target_types.py @@ -1,4 +1,4 @@ -from typing import Literal, Union, Optional +from typing import Literal, Optional, Union @dataclass From cd392490864a4dc12047bff6e9719bbc7cb68743 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 13 Jul 2023 20:01:41 +0200 Subject: [PATCH 03/17] revert --- .../src/actions/InsertCopy.ts | 11 ++++- .../src/core/inferFullTargetDescriptor.ts | 8 ++- .../ModifierStageFactoryImpl.ts | 4 +- .../processTargets/TargetPipelineRunner.ts | 14 +++--- .../processTargets/modifiers/PositionStage.ts | 24 +++------ .../src/processTargets/targets/BaseTarget.ts | 2 +- .../processTargets/targets/DestinationImpl.ts | 23 ++------- .../targets/NotebookCellDestination.ts | 4 +- .../targets/NotebookCellTarget.ts | 2 +- .../src/processTargets/targets/PlainTarget.ts | 8 ++- .../src/processTargets/targets/index.ts | 5 +- .../transformations/upgrade.ts | 2 +- .../src/testCaseRecorder/TestCase.ts | 2 +- .../src/util/getPartialTargetDescriptors.ts | 33 +++---------- .../fixtures/recorded/actions/getTextAir.yml | 36 ++++++++++++++ .../recorded/actions/giveEndOfDot.yml | 39 +++++++++++++++ .../recorded/actions/giveStartOfDot.yml | 39 +++++++++++++++ .../implicitExpansion/bringFineAfterThis.yml | 26 ++++++++++ .../bringWhaleBeforeThis.yml | 26 ++++++++++ .../bringFineAfterJustThis.yml | 32 ++++++++++++ .../bringWhaleBeforeJustThis.yml | 32 ++++++++++++ packages/cursorless-vscode/package.json | 2 +- .../src/keyboard/KeyboardCommandsTargeted.ts | 49 +++++++------------ .../meta-updater/src/updatePackageJson.ts | 46 +++++++++++------ 24 files changed, 334 insertions(+), 135 deletions(-) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/getTextAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveEndOfDot.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveStartOfDot.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringFineAfterThis.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringWhaleBeforeThis.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterJustThis.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeJustThis.yml diff --git a/packages/cursorless-engine/src/actions/InsertCopy.ts b/packages/cursorless-engine/src/actions/InsertCopy.ts index a4310e7aa0..d9caad1366 100644 --- a/packages/cursorless-engine/src/actions/InsertCopy.ts +++ b/packages/cursorless-engine/src/actions/InsertCopy.ts @@ -87,7 +87,16 @@ class InsertCopy implements SimpleAction { ); setSelectionsWithoutFocusingEditor(editableEditor, updatedEditorSelections); - await editableEditor.revealRange(editor.selections[0]); + const primarySelection = editor.selections[0]; + + if ( + updatedContentSelections.some( + (selection) => selection.intersection(primarySelection) != null, + ) + ) { + // If the original target contained the user's cursor, reveal it in case it got pushed off screen + await editableEditor.revealRange(primarySelection); + } return { sourceMark: createThatMark(targets, insertionRanges), diff --git a/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts b/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts index 2f593a4027..81e613bdaa 100644 --- a/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts +++ b/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts @@ -209,11 +209,9 @@ function getPreviousTargetAttribute( const target = previousTargets[i]; switch (target.type) { case "primitive": { - if (getAttribute != null) { - const attributeValue = getAttribute(target); - if (attributeValue != null) { - return attributeValue; - } + const attributeValue = getAttribute(target); + if (attributeValue != null) { + return attributeValue; } break; } diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts index e172a5d9e9..a656688473 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts @@ -50,9 +50,9 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { create(modifier: Modifier): ModifierStage { switch (modifier.type) { case "startOf": - return new StartOfStage(modifier); + return new StartOfStage(); case "endOf": - return new EndOfStage(modifier); + return new EndOfStage(); case "extendThroughStartOf": return new HeadStage(this, modifier); case "extendThroughEndOf": diff --git a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts index 272e33ea10..c5f147d8b0 100644 --- a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts +++ b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts @@ -17,7 +17,7 @@ import { ModifierStageFactory } from "./ModifierStageFactory"; import { MarkStage, ModifierStage } from "./PipelineStages.types"; import ImplicitStage from "./marks/ImplicitStage"; import { ContainingTokenIfUntypedEmptyStage } from "./modifiers/ConditionalModifierStages"; -import { VerticalRangeTarget } from "./targets"; +import { PlainTarget } from "./targets"; export class TargetPipelineRunner { constructor( @@ -151,7 +151,7 @@ class TargetPipeline { return [ targetsToContinuousTarget( excludeAnchor - ? excludeScope( + ? getExcludedScope( this.modifierStageFactory, anchorTarget, exclusionScopeType, @@ -159,7 +159,7 @@ class TargetPipeline { ) : anchorTarget, excludeActive - ? excludeScope( + ? getExcludedScope( this.modifierStageFactory, activeTarget, exclusionScopeType, @@ -254,17 +254,17 @@ export function processModifierStages( return targets; } -function excludeScope( +function getExcludedScope( modifierStageFactory: ModifierStageFactory, target: Target, - exclusionScopeType: ScopeType, + scopeType: ScopeType, direction: Direction, ): Target { return ( modifierStageFactory .create({ type: "relativeScope", - scopeType: exclusionScopeType, + scopeType, direction, length: 1, offset: 1, @@ -348,7 +348,7 @@ function targetsToVerticalTarget( ); results.push( - new VerticalRangeTarget({ + new PlainTarget({ editor: anchorTarget.editor, isReversed: anchorTarget.isReversed, contentRange, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/PositionStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/PositionStage.ts index 46bb6929b8..a76dc163f8 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/PositionStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/PositionStage.ts @@ -1,4 +1,4 @@ -import { EndOfModifier, Range, StartOfModifier } from "@cursorless/common"; +import { Range } from "@cursorless/common"; import { Target } from "../../typings/target.types"; import { ModifierStage } from "../PipelineStages.types"; import { @@ -7,12 +7,12 @@ import { RawSelectionTarget, } from "../targets"; -abstract class StartEndOfStage implements ModifierStage { +abstract class PositionStage implements ModifierStage { run(target: Target): Target[] { const parameters: CommonTargetParameters = { editor: target.editor, isReversed: target.isReversed, - contentRange: this.constructContentRange(target.contentRange), + contentRange: this.getContentRange(target.contentRange), }; return [ @@ -22,25 +22,17 @@ abstract class StartEndOfStage implements ModifierStage { ]; } - protected abstract constructContentRange(contentRange: Range): Range; + protected abstract getContentRange(contentRange: Range): Range; } -export class StartOfStage extends StartEndOfStage { - constructor(_modifier: StartOfModifier) { - super(); - } - - protected constructContentRange(contentRange: Range): Range { +export class StartOfStage extends PositionStage { + protected getContentRange(contentRange: Range): Range { return contentRange.start.toEmptyRange(); } } -export class EndOfStage extends StartEndOfStage { - constructor(_modifier: EndOfModifier) { - super(); - } - - protected constructContentRange(contentRange: Range): Range { +export class EndOfStage extends PositionStage { + protected getContentRange(contentRange: Range): Range { return contentRange.end.toEmptyRange(); } } diff --git a/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts b/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts index ddced4a443..da49eb96b2 100644 --- a/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts @@ -14,7 +14,7 @@ import { createContinuousRange, createContinuousRangeUntypedTarget, } from "../targetUtil/createContinuousRange"; -import DestinationImpl from "./DestinationImpl"; +import { DestinationImpl } from "./DestinationImpl"; /** Parameters supported by all target classes */ export interface MinimumTargetParameters { diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index 3c39595bdd..1b5d787f03 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -3,7 +3,6 @@ import { Range, Selection, TextEditor, - UnsupportedError, } from "@cursorless/common"; import { EditWithRangeUpdater } from "../../typings/Types"; import { @@ -12,15 +11,15 @@ import { Target, } from "../../typings/target.types"; -export default class DestinationImpl implements Destination { +export class DestinationImpl implements Destination { public readonly contentRange: Range; private readonly isLineDelimiter: boolean; private readonly isBefore: boolean; private readonly indentationString: string; constructor( - public target: Target, - public insertionMode: InsertionMode, + public readonly target: Target, + public readonly insertionMode: InsertionMode, indentationString?: string, ) { this.contentRange = getContentRange(target.contentRange, insertionMode); @@ -161,22 +160,6 @@ export default class DestinationImpl implements Destination { } } -export function removalUnsupportedForPosition( - insertionMode: InsertionMode, -): Range { - if (insertionMode === "to") { - throw new UnsupportedError( - `Removal is not supported for "${insertionMode}"`, - ); - } - - const preferredModifier = insertionMode === "after" ? "trailing" : "leading"; - - throw new UnsupportedError( - `Please use "${preferredModifier}" modifier; removal is not supported for "${insertionMode}"`, - ); -} - /** Calculate the minimum indentation/padding for a range */ function getIndentationString(editor: TextEditor, range: Range) { let length = Number.MAX_SAFE_INTEGER; diff --git a/packages/cursorless-engine/src/processTargets/targets/NotebookCellDestination.ts b/packages/cursorless-engine/src/processTargets/targets/NotebookCellDestination.ts index fe909f44e0..9f28d148bb 100644 --- a/packages/cursorless-engine/src/processTargets/targets/NotebookCellDestination.ts +++ b/packages/cursorless-engine/src/processTargets/targets/NotebookCellDestination.ts @@ -8,7 +8,7 @@ import { EditWithRangeUpdater } from "../../typings/Types"; import { Destination, EditNewActionType } from "../../typings/target.types"; import NotebookCellTarget from "./NotebookCellTarget"; -export default class NotebookCellDestination implements Destination { +export class NotebookCellDestination implements Destination { constructor( public target: NotebookCellTarget, public insertionMode: InsertionMode, @@ -34,7 +34,7 @@ export default class NotebookCellDestination implements Destination { return this.target.isRaw; } - withTarget(target: NotebookCellTarget): Destination { + withTarget(target: NotebookCellTarget): NotebookCellDestination { return new NotebookCellDestination(target, this.insertionMode); } diff --git a/packages/cursorless-engine/src/processTargets/targets/NotebookCellTarget.ts b/packages/cursorless-engine/src/processTargets/targets/NotebookCellTarget.ts index e96cbfab60..cd71744ee8 100644 --- a/packages/cursorless-engine/src/processTargets/targets/NotebookCellTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/NotebookCellTarget.ts @@ -1,7 +1,7 @@ import { InsertionMode } from "@cursorless/common"; import { BaseTarget, CommonTargetParameters } from "."; import { Destination } from "../../typings/target.types"; -import NotebookCellDestination from "./NotebookCellDestination"; +import { NotebookCellDestination } from "./NotebookCellDestination"; export default class NotebookCellTarget extends BaseTarget { type = "NotebookCellTarget"; diff --git a/packages/cursorless-engine/src/processTargets/targets/PlainTarget.ts b/packages/cursorless-engine/src/processTargets/targets/PlainTarget.ts index 4d826524e0..06d993270b 100644 --- a/packages/cursorless-engine/src/processTargets/targets/PlainTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/PlainTarget.ts @@ -2,19 +2,22 @@ import { BaseTarget, CommonTargetParameters } from "."; interface PlainTargetParameters extends CommonTargetParameters { readonly isToken?: boolean; + readonly insertionDelimiter?: string; } /** * A target that has no leading or trailing delimiters so it's removal range - * just consists of the content itself. Its insertion delimiter is empty string. + * just consists of the content itself. Its insertion delimiter is empty string, + * unless specified. */ export default class PlainTarget extends BaseTarget { type = "PlainTarget"; - insertionDelimiter = ""; + insertionDelimiter: string; constructor(parameters: PlainTargetParameters) { super(parameters); this.isToken = parameters.isToken ?? true; + this.insertionDelimiter = parameters.insertionDelimiter ?? ""; } getLeadingDelimiterTarget = () => undefined; @@ -25,6 +28,7 @@ export default class PlainTarget extends BaseTarget { return { ...this.state, isToken: this.isToken, + insertionDelimiter: this.insertionDelimiter, }; } } diff --git a/packages/cursorless-engine/src/processTargets/targets/index.ts b/packages/cursorless-engine/src/processTargets/targets/index.ts index 13b4244bc2..0154f82e78 100644 --- a/packages/cursorless-engine/src/processTargets/targets/index.ts +++ b/packages/cursorless-engine/src/processTargets/targets/index.ts @@ -6,15 +6,12 @@ export * from "./LineTarget"; export { default as LineTarget } from "./LineTarget"; export * from "./NotebookCellTarget"; export { default as NotebookCellTarget } from "./NotebookCellTarget"; -export { default as NotebookCellDestination } from "./NotebookCellDestination"; +export * from "./NotebookCellDestination"; export * from "./ParagraphTarget"; export { default as ParagraphTarget } from "./ParagraphTarget"; export * from "./PlainTarget"; export { default as PlainTarget } from "./PlainTarget"; -export * from "./VerticalRangeTarget"; -export { default as VerticalRangeTarget } from "./VerticalRangeTarget"; export * from "./DestinationImpl"; -export { default as DestinationImpl } from "./DestinationImpl"; export * from "./RawSelectionTarget"; export { default as RawSelectionTarget } from "./RawSelectionTarget"; export * from "./ScopeTypeTarget"; diff --git a/packages/cursorless-engine/src/scripts/transformRecordedTests/transformations/upgrade.ts b/packages/cursorless-engine/src/scripts/transformRecordedTests/transformations/upgrade.ts index ec303ddbc1..1926bedc9c 100644 --- a/packages/cursorless-engine/src/scripts/transformRecordedTests/transformations/upgrade.ts +++ b/packages/cursorless-engine/src/scripts/transformRecordedTests/transformations/upgrade.ts @@ -8,6 +8,6 @@ export const upgrade = flow(upgradeCommand, reorderFields); function upgradeCommand(fixture: TestCaseFixtureLegacy): TestCaseFixture { return { ...fixture, - command: flow(canonicalizeAndValidateCommand)(fixture.command), + command: canonicalizeAndValidateCommand(fixture.command), }; } diff --git a/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts b/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts index e84bd90573..00ab9e6cb7 100644 --- a/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts +++ b/packages/cursorless-engine/src/testCaseRecorder/TestCase.ts @@ -21,7 +21,7 @@ import { StoredTargetMap } from ".."; import { ide } from "../singletons/ide.singleton"; import { extractTargetKeys } from "../testUtil/extractTargetKeys"; import { takeSnapshot } from "../testUtil/takeSnapshot"; -import { getPartialTargetDescriptors as getPartialTargetDescriptors } from "../util/getPartialTargetDescriptors"; +import { getPartialTargetDescriptors } from "../util/getPartialTargetDescriptors"; export class TestCase { private languageId: string; diff --git a/packages/cursorless-engine/src/util/getPartialTargetDescriptors.ts b/packages/cursorless-engine/src/util/getPartialTargetDescriptors.ts index 4f7f3f64b0..0b088c3aa5 100644 --- a/packages/cursorless-engine/src/util/getPartialTargetDescriptors.ts +++ b/packages/cursorless-engine/src/util/getPartialTargetDescriptors.ts @@ -1,8 +1,6 @@ import { ActionDescriptor, DestinationDescriptor, - PartialPrimitiveTargetDescriptor, - PartialRangeTargetDescriptor, PartialTargetDescriptor, } from "@cursorless/common"; @@ -16,7 +14,7 @@ export function getPartialTargetDescriptors( case "moveToTarget": return [ action.source, - getPartialTargetDescriptorFromDestination(action.destination), + ...getPartialTargetDescriptorsFromDestination(action.destination), ]; case "swapTargets": return [action.target1, action.target2]; @@ -24,36 +22,21 @@ export function getPartialTargetDescriptors( case "insertSnippet": case "replace": case "editNew": - return [getPartialTargetDescriptorFromDestination(action.destination)]; + return getPartialTargetDescriptorsFromDestination(action.destination); default: return [action.target]; } } -export function getPartialTargetDescriptorFromDestination( +function getPartialTargetDescriptorsFromDestination( destination: DestinationDescriptor, -): PartialTargetDescriptor { +): PartialTargetDescriptor[] { switch (destination.type) { - case "list": { - const elements: ( - | PartialPrimitiveTargetDescriptor - | PartialRangeTargetDescriptor - )[] = []; - destination.destinations.forEach((destination) => { - if (destination.target.type === "list") { - elements.push(...destination.target.elements); - } else { - elements.push(destination.target); - } - }); - return { - type: "list", - elements, - }; - } + case "list": + return destination.destinations.map(({ target }) => target); case "primitive": - return destination.target; + return [destination.target]; case "implicit": - return destination; + return []; } } diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/getTextAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/getTextAir.yml new file mode 100644 index 0000000000..bb13cd6740 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/getTextAir.yml @@ -0,0 +1,36 @@ +languageId: plaintext +command: + version: 6 + action: + name: getText + options: {showDecorations: false, ensureSingleTarget: null} + target: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + foo bar baz + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: | + foo bar baz + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + thatMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 4} + end: {line: 0, character: 7} + isReversed: false + hasExplicitRange: false +returnValue: [bar] +ide: + flashes: [] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveEndOfDot.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveEndOfDot.yml new file mode 100644 index 0000000000..910716c16e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveEndOfDot.yml @@ -0,0 +1,39 @@ +languageId: plaintext +command: + version: 6 + spokenForm: give end of dot + action: + name: deselect + target: + type: primitive + modifiers: + - {type: endOf} + mark: {type: decoratedSymbol, symbolColor: default, character: .} + usePrePhraseSnapshot: true +initialState: + documentContents: a b.c + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: + default..: + start: {line: 0, character: 3} + end: {line: 0, character: 4} +finalState: + documentContents: a b.c + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + thatMark: + - type: PlainTarget + contentRange: + start: {line: 0, character: 4} + end: {line: 0, character: 4} + isReversed: false + hasExplicitRange: true diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveStartOfDot.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveStartOfDot.yml new file mode 100644 index 0000000000..6f88d94bf3 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/giveStartOfDot.yml @@ -0,0 +1,39 @@ +languageId: plaintext +command: + version: 6 + spokenForm: give start of dot + action: + name: deselect + target: + type: primitive + modifiers: + - {type: startOf} + mark: {type: decoratedSymbol, symbolColor: default, character: .} + usePrePhraseSnapshot: true +initialState: + documentContents: a b.c + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: + default..: + start: {line: 0, character: 3} + end: {line: 0, character: 4} +finalState: + documentContents: a b.c + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + thatMark: + - type: PlainTarget + contentRange: + start: {line: 0, character: 3} + end: {line: 0, character: 3} + isReversed: false + hasExplicitRange: true diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringFineAfterThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringFineAfterThis.yml new file mode 100644 index 0000000000..886dd623b0 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringFineAfterThis.yml @@ -0,0 +1,26 @@ +languageId: plaintext +command: + version: 1 + spokenForm: bring fine after this + action: replaceWithTarget + targets: + - type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: f} + - type: primitive + mark: {type: cursor} + position: after +initialState: + documentContents: foo world + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: + default.f: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: foo world foo + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: f}, selectionType: token, position: contents, insideOutsideType: null, modifier: {type: identity}}, {type: primitive, mark: {type: cursor}, selectionType: token, position: after, insideOutsideType: null, modifier: {type: identity}}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringWhaleBeforeThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringWhaleBeforeThis.yml new file mode 100644 index 0000000000..f72d034522 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/implicitExpansion/bringWhaleBeforeThis.yml @@ -0,0 +1,26 @@ +languageId: plaintext +command: + version: 1 + spokenForm: bring whale before this + action: replaceWithTarget + targets: + - type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: w} + - type: primitive + mark: {type: cursor} + position: before +initialState: + documentContents: foo world + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: + default.w: + start: {line: 0, character: 4} + end: {line: 0, character: 9} +finalState: + documentContents: world foo world + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: w}, selectionType: token, position: contents, insideOutsideType: null, modifier: {type: identity}}, {type: primitive, mark: {type: cursor}, selectionType: token, position: before, insideOutsideType: null, modifier: {type: identity}}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterJustThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterJustThis.yml new file mode 100644 index 0000000000..4c02f80f8d --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterJustThis.yml @@ -0,0 +1,32 @@ +languageId: plaintext +command: + version: 6 + spokenForm: bring fine after just this + action: + name: replaceWithTarget + source: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: f} + destination: + type: primitive + insertionMode: after + target: + type: primitive + modifiers: + - {type: toRawSelection} + mark: {type: cursor} + usePrePhraseSnapshot: true +initialState: + documentContents: foo world + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: + default.f: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: foo fooworld + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeJustThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeJustThis.yml new file mode 100644 index 0000000000..40ca5dc850 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeJustThis.yml @@ -0,0 +1,32 @@ +languageId: plaintext +command: + version: 6 + spokenForm: bring whale before just this + action: + name: replaceWithTarget + source: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: w} + destination: + type: primitive + insertionMode: before + target: + type: primitive + modifiers: + - {type: toRawSelection} + mark: {type: cursor} + usePrePhraseSnapshot: true +initialState: + documentContents: foo world + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: + default.w: + start: {line: 0, character: 4} + end: {line: 0, character: 9} +finalState: + documentContents: fooworld world + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} diff --git a/packages/cursorless-vscode/package.json b/packages/cursorless-vscode/package.json index 0a126d88ea..1aba06ab31 100644 --- a/packages/cursorless-vscode/package.json +++ b/packages/cursorless-vscode/package.json @@ -846,7 +846,7 @@ "hat-adjustment-average": "tsx --conditions=cursorless:bundler src/scripts/hatAssignments/add.ts", "compile": "tsc --build", "watch": "tsc --build --watch", - "clean": "rm -rf ./out tsconfig.tsbuildinfo" + "clean": "rm -rf ./out tsconfig.tsbuildinfo ./dist" }, "devDependencies": { "@types/chai": "^4.3.3", diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts index a666f8a60b..80d44036c2 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts @@ -1,8 +1,6 @@ import { ActionDescriptor, ActionType, - DestinationDescriptor, - ImplicitTargetDescriptor, LATEST_VERSION, PartialPrimitiveTargetDescriptor, PartialTargetDescriptor, @@ -165,11 +163,11 @@ export default class KeyboardCommandsTargeted { }); /** - * Performs action {@link action} on the current target - * @param action The action to run + * Performs action {@link name} on the current target + * @param name The action to run * @returns A promise that resolves to the result of the cursorless command */ - performActionOnTarget = async (action: ActionType) => { + performActionOnTarget = async (name: ActionType) => { const target: PartialPrimitiveTargetDescriptor = { type: "primitive", mark: { @@ -179,7 +177,7 @@ export default class KeyboardCommandsTargeted { let returnValue: unknown; - switch (action) { + switch (name) { case "wrapWithPairedDelimiter": case "rewrapWithPairedDelimiter": case "insertSnippet": @@ -188,52 +186,56 @@ export default class KeyboardCommandsTargeted { case "replace": case "editNew": case "getText": - throw Error(`Unsupported keyboard action: ${action}`); + throw Error(`Unsupported keyboard action: ${name}`); case "replaceWithTarget": case "moveToTarget": returnValue = await executeCursorlessCommand({ - name: action, + name, source: target, - destination: toDestination({ type: "implicit" }), + destination: { type: "implicit" }, }); break; case "swapTargets": returnValue = await executeCursorlessCommand({ - name: action, + name, target1: target, target2: { type: "implicit" }, }); break; case "callAsFunction": returnValue = await executeCursorlessCommand({ - name: action, + name, callee: target, argument: { type: "implicit" }, }); break; case "pasteFromClipboard": returnValue = await executeCursorlessCommand({ - name: action, - destination: toDestination(target), + name, + destination: { + type: "primitive", + insertionMode: "to", + target, + }, }); break; case "generateSnippet": case "highlight": returnValue = await executeCursorlessCommand({ - name: action, + name, target, }); break; default: returnValue = await executeCursorlessCommand({ - name: action, + name, target, }); } await this.highlightTarget(); - if (EXIT_CURSORLESS_MODE_ACTIONS.includes(action)) { + if (EXIT_CURSORLESS_MODE_ACTIONS.includes(name)) { // For some Cursorless actions, it is more convenient if we automatically // exit modal mode await this.modal.modeOff(); @@ -282,21 +284,6 @@ function executeCursorlessCommand(action: ActionDescriptor) { }); } -function toDestination( - target: PartialPrimitiveTargetDescriptor | ImplicitTargetDescriptor, -): DestinationDescriptor { - switch (target.type) { - case "primitive": - return { - type: "primitive", - insertionMode: "to", - target, - }; - case "implicit": - return target; - } -} - const EXIT_CURSORLESS_MODE_ACTIONS: ActionType[] = [ "setSelectionBefore", "setSelectionAfter", diff --git a/packages/meta-updater/src/updatePackageJson.ts b/packages/meta-updater/src/updatePackageJson.ts index 6385fcced1..31545fd715 100644 --- a/packages/meta-updater/src/updatePackageJson.ts +++ b/packages/meta-updater/src/updatePackageJson.ts @@ -56,28 +56,44 @@ export async function updatePackageJson( }, }; - const extraFields = - input.name === "@cursorless/cursorless-vscode" - ? getCursorlessVscodeFields(input) - : {}; + const isCursorlessVscode = input.name === "@cursorless/cursorless-vscode"; - const extraScripts = isRoot - ? {} - : { - clean: "rm -rf ./out tsconfig.tsbuildinfo", - }; + const extraFields = isCursorlessVscode + ? getCursorlessVscodeFields(input) + : {}; return { ...input, name, license: "MIT", - scripts: { - ...(input.scripts ?? {}), - compile: "tsc --build", - watch: "tsc --build --watch", - ...extraScripts, - }, + scripts: getScripts(input.scripts, isRoot, isCursorlessVscode), ...exportFields, ...extraFields, } as PackageJson; } + +function getScripts( + inputScripts: PackageJson.Scripts | undefined, + isRoot: boolean, + isCursorlessVscode: boolean, +) { + const scripts: PackageJson.Scripts = { + ...(inputScripts ?? {}), + compile: "tsc --build", + watch: "tsc --build --watch", + }; + + if (isRoot) { + return scripts; + } + + const cleanDirs = ["./out", "tsconfig.tsbuildinfo"]; + + if (isCursorlessVscode) { + cleanDirs.push("./dist"); + } + + scripts.clean = `rm -rf ${cleanDirs.join(" ")}`; + + return scripts; +} From b49fd30c86b76cd522bb1ac56ba035ed0de65619 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 13 Jul 2023 20:05:24 +0200 Subject: [PATCH 04/17] cleanup --- .../targets/VerticalRangeTarget.ts | 31 ---------------- .../updateSelections/bringFineAfterThis.yml | 26 ------------- .../updateSelections/bringWhaleBeforeThis.yml | 26 ------------- .../src/suite/getText.vscode.test.ts | 37 ------------------- 4 files changed, 120 deletions(-) delete mode 100644 packages/cursorless-engine/src/processTargets/targets/VerticalRangeTarget.ts delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterThis.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeThis.yml delete mode 100644 packages/cursorless-vscode-e2e/src/suite/getText.vscode.test.ts diff --git a/packages/cursorless-engine/src/processTargets/targets/VerticalRangeTarget.ts b/packages/cursorless-engine/src/processTargets/targets/VerticalRangeTarget.ts deleted file mode 100644 index 98e0eccc5c..0000000000 --- a/packages/cursorless-engine/src/processTargets/targets/VerticalRangeTarget.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { BaseTarget, CommonTargetParameters } from "."; - -interface VerticalRangeTargetParameters extends CommonTargetParameters { - readonly insertionDelimiter: string; -} - -/** - * A target that has no leading or trailing delimiters so it's removal range - * just consists of the content itself. Its insertion delimiter is given by argument. - */ -export default class VerticalRangeTarget extends BaseTarget { - type = "VerticalRangeTarget"; - isToken = false; - insertionDelimiter: string; - - constructor(parameters: VerticalRangeTargetParameters) { - super(parameters); - this.insertionDelimiter = parameters.insertionDelimiter; - } - - getLeadingDelimiterTarget = () => undefined; - getTrailingDelimiterTarget = () => undefined; - getRemovalRange = () => this.contentRange; - - protected getCloneParameters() { - return { - ...this.state, - insertionDelimiter: this.insertionDelimiter, - }; - } -} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterThis.yml deleted file mode 100644 index 886dd623b0..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringFineAfterThis.yml +++ /dev/null @@ -1,26 +0,0 @@ -languageId: plaintext -command: - version: 1 - spokenForm: bring fine after this - action: replaceWithTarget - targets: - - type: primitive - mark: {type: decoratedSymbol, symbolColor: default, character: f} - - type: primitive - mark: {type: cursor} - position: after -initialState: - documentContents: foo world - selections: - - anchor: {line: 0, character: 4} - active: {line: 0, character: 4} - marks: - default.f: - start: {line: 0, character: 0} - end: {line: 0, character: 3} -finalState: - documentContents: foo world foo - selections: - - anchor: {line: 0, character: 4} - active: {line: 0, character: 4} -fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: f}, selectionType: token, position: contents, insideOutsideType: null, modifier: {type: identity}}, {type: primitive, mark: {type: cursor}, selectionType: token, position: after, insideOutsideType: null, modifier: {type: identity}}] diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeThis.yml deleted file mode 100644 index f72d034522..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/updateSelections/bringWhaleBeforeThis.yml +++ /dev/null @@ -1,26 +0,0 @@ -languageId: plaintext -command: - version: 1 - spokenForm: bring whale before this - action: replaceWithTarget - targets: - - type: primitive - mark: {type: decoratedSymbol, symbolColor: default, character: w} - - type: primitive - mark: {type: cursor} - position: before -initialState: - documentContents: foo world - selections: - - anchor: {line: 0, character: 3} - active: {line: 0, character: 3} - marks: - default.w: - start: {line: 0, character: 4} - end: {line: 0, character: 9} -finalState: - documentContents: world foo world - selections: - - anchor: {line: 0, character: 9} - active: {line: 0, character: 9} -fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: w}, selectionType: token, position: contents, insideOutsideType: null, modifier: {type: identity}}, {type: primitive, mark: {type: cursor}, selectionType: token, position: before, insideOutsideType: null, modifier: {type: identity}}] diff --git a/packages/cursorless-vscode-e2e/src/suite/getText.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/getText.vscode.test.ts deleted file mode 100644 index ad1dd415d0..0000000000 --- a/packages/cursorless-vscode-e2e/src/suite/getText.vscode.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - getCursorlessApi, - openNewEditor, - runCursorlessCommand, -} from "@cursorless/vscode-common"; -import * as assert from "assert"; -import { endToEndTestSetup } from "../endToEndTestSetup"; - -suite("getText", async function () { - endToEndTestSetup(this); - - test("getText", getText); -}); - -async function getText() { - const { hatTokenMap } = (await getCursorlessApi()).testHelpers!; - await openNewEditor("foo bar baz"); - await hatTokenMap.allocateHats(); - - const result = await runCursorlessCommand({ - version: 6, - usePrePhraseSnapshot: false, - action: { - name: "getText", - target: { - type: "primitive", - mark: { - type: "decoratedSymbol", - symbolColor: "default", - character: "b", - }, - }, - }, - }); - - assert.equal(result, "bar"); -} From f06eb6ae8352c37e7b5c0a0c0b3c4402db73f7bf Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 13 Jul 2023 20:15:21 +0200 Subject: [PATCH 05/17] updates --- cursorless-talon/src/actions/get_text.py | 4 ++-- cursorless-talon/src/actions/replace.py | 1 + cursorless-talon/src/command.py | 5 +++-- cursorless-talon/src/targets/range_target.py | 2 +- cursorless-talon/src/targets/target_types.py | 1 + 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cursorless-talon/src/actions/get_text.py b/cursorless-talon/src/actions/get_text.py index 0606512be6..6f2ece34c6 100644 --- a/cursorless-talon/src/actions/get_text.py +++ b/cursorless-talon/src/actions/get_text.py @@ -1,6 +1,6 @@ from typing import Optional -from ..command import cursorless_command_and_wait +from ..command import cursorless_command_get from ..targets.target_types import CursorlessTarget @@ -10,7 +10,7 @@ def cursorless_get_text( ensure_single_target: Optional[bool] = None, ): """Get target texts""" - cursorless_command_and_wait( + cursorless_command_get( { "name": "getText", "options": { diff --git a/cursorless-talon/src/actions/replace.py b/cursorless-talon/src/actions/replace.py index 68fa9727ea..ec21455d35 100644 --- a/cursorless-talon/src/actions/replace.py +++ b/cursorless-talon/src/actions/replace.py @@ -1,3 +1,4 @@ +from ..command import cursorless_command_and_wait from ..targets.target_types import CursorlessTarget, PrimitiveDestination diff --git a/cursorless-talon/src/command.py b/cursorless-talon/src/command.py index bca99dc0a2..c7f3d54b89 100644 --- a/cursorless-talon/src/command.py +++ b/cursorless-talon/src/command.py @@ -85,5 +85,6 @@ def makes_serializable(value: any): except AttributeError: return value - if dataclasses.is_dataclass(o): - return dataclasses.asdict(o) + # TODO: Try to utilize this + # if dataclasses.is_dataclass(o): + # return dataclasses.asdict(o) diff --git a/cursorless-talon/src/targets/range_target.py b/cursorless-talon/src/targets/range_target.py index 167a89889f..a9f1681f05 100644 --- a/cursorless-talon/src/targets/range_target.py +++ b/cursorless-talon/src/targets/range_target.py @@ -3,7 +3,7 @@ from talon import Module -from .target_types import ImplicitTarget, PrimitiveTarget +from .target_types import ImplicitTarget, PrimitiveTarget, RangeTarget mod = Module() diff --git a/cursorless-talon/src/targets/target_types.py b/cursorless-talon/src/targets/target_types.py index 17ee7b2dd9..f35a6d314b 100644 --- a/cursorless-talon/src/targets/target_types.py +++ b/cursorless-talon/src/targets/target_types.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from typing import Literal, Optional, Union From 8f68d642433d7851231ed44183a1f29f8440c5b4 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 13 Jul 2023 21:27:28 +0200 Subject: [PATCH 06/17] Use is data class --- cursorless-talon/src/command.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/cursorless-talon/src/command.py b/cursorless-talon/src/command.py index c7f3d54b89..983874b685 100644 --- a/cursorless-talon/src/command.py +++ b/cursorless-talon/src/command.py @@ -1,5 +1,4 @@ -from dataclasses import dataclass - +import dataclasses from talon import actions, speech_system from .cursorless_command_server import ( @@ -9,7 +8,7 @@ ) -@dataclass +@dataclasses.dataclass class CursorlessCommand: version = 6 spokenForm: str @@ -77,14 +76,10 @@ def makes_serializable(value: any): return {k: makes_serializable(v) for k, v in value.items()} if isinstance(value, list): return [makes_serializable(v) for v in value] - try: - items = value.__dict__ - class_items = {k: v for k, v in value.__class__.__dict__.items() if k[0] != "_"} - all_items = {**class_items, **items} - return {k: makes_serializable(v) for k, v in all_items.items()} - except AttributeError: - return value - - # TODO: Try to utilize this - # if dataclasses.is_dataclass(o): - # return dataclasses.asdict(o) + if dataclasses.is_dataclass(value): + items = { + **{k: v for k, v in value.__class__.__dict__.items() if k[0] != "_"}, + **value.__dict__, + } + return {k: makes_serializable(v) for k, v in items.items()} + return value From a6c2b5ec4cf46eb1dd29872f7f13e8b16642a910 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 19:29:24 +0000 Subject: [PATCH 07/17] [pre-commit.ci lite] apply automatic fixes --- cursorless-talon/src/command.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cursorless-talon/src/command.py b/cursorless-talon/src/command.py index 983874b685..e4c9cd0bed 100644 --- a/cursorless-talon/src/command.py +++ b/cursorless-talon/src/command.py @@ -1,4 +1,5 @@ import dataclasses + from talon import actions, speech_system from .cursorless_command_server import ( From 5a4641a970572eb380ce485eb788b0df4f25b593 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 14 Jul 2023 16:40:36 +0200 Subject: [PATCH 08/17] Added snippets --- cursorless-talon/src/command.py | 14 +-- cursorless-talon/src/cursorless.talon | 11 +- cursorless-talon/src/snippets.py | 144 ++++++++++++++++++-------- 3 files changed, 110 insertions(+), 59 deletions(-) diff --git a/cursorless-talon/src/command.py b/cursorless-talon/src/command.py index 983874b685..fc44efed5e 100644 --- a/cursorless-talon/src/command.py +++ b/cursorless-talon/src/command.py @@ -30,23 +30,23 @@ def on_phrase(d): def cursorless_command_and_wait(action: dict): """Execute cursorless command and wait for it to finish""" - return run_rpc_command_and_wait( + run_rpc_command_and_wait( CURSORLESS_COMMAND_ID, construct_cursorless_command(action), ) -def cursorless_command_get(action: dict): - """Execute cursorless command and return result""" - return run_rpc_command_get( +def cursorless_command_no_wait(action: dict): + """Execute cursorless command without waiting""" + run_rpc_command_no_wait( CURSORLESS_COMMAND_ID, construct_cursorless_command(action), ) -def cursorless_command_no_wait(action: dict): - """Execute cursorless command without waiting""" - run_rpc_command_no_wait( +def cursorless_command_get(action: dict): + """Execute cursorless command and return result""" + return run_rpc_command_get( CURSORLESS_COMMAND_ID, construct_cursorless_command(action), ) diff --git a/cursorless-talon/src/cursorless.talon b/cursorless-talon/src/cursorless.talon index 430378b0d7..22a15a123e 100644 --- a/cursorless-talon/src/cursorless.talon +++ b/cursorless-talon/src/cursorless.talon @@ -22,14 +22,11 @@ tag: user.cursorless {user.cursorless_wrapper_snippet} {user.cursorless_wrap_action} : user.private_cursorless_wrap_snippet(cursorless_wrap_action, cursorless_target, cursorless_wrapper_snippet) -# {user.cursorless_insert_snippet_action} : -# user.cursorless_implicit_target_command(cursorless_insert_snippet_action, cursorless_insertion_snippet) +{user.cursorless_insert_snippet_action} : + user.private_cursorless_insert_snippet(cursorless_insertion_snippet) -# {user.cursorless_insert_snippet_action} : -# user.cursorless_single_target_command(cursorless_insert_snippet_action, cursorless_positional_target, cursorless_insertion_snippet) - -# {user.cursorless_insert_snippet_action} {user.cursorless_insertion_snippet_single_phrase} [{user.cursorless_phrase_terminator}]: -# user.private_cursorless_insert_snippet_with_phrase(cursorless_insert_snippet_action, cursorless_insertion_snippet_single_phrase, text) +{user.cursorless_insert_snippet_action} {user.cursorless_insertion_snippet_single_phrase} [{user.cursorless_phrase_terminator}]: + user.private_cursorless_insert_snippet_with_phrase(cursorless_insertion_snippet_single_phrase, text) {user.cursorless_homophone} settings: user.private_cursorless_show_settings_in_ide() diff --git a/cursorless-talon/src/snippets.py b/cursorless-talon/src/snippets.py index a9f100f58d..836dfdf63e 100644 --- a/cursorless-talon/src/snippets.py +++ b/cursorless-talon/src/snippets.py @@ -1,10 +1,23 @@ from typing import Any, Optional - +from dataclasses import dataclass +from .targets.target_types import ( + CursorlessTarget, + CursorlessDestination, + ImplicitDestination, +) from talon import Module, actions, app - +from .command import cursorless_command_and_wait from .csv_overrides import init_csv_and_watch_changes + +@dataclass +class InsertionSnippet: + name: str + destination: CursorlessDestination + + mod = Module() + mod.list("cursorless_insert_snippet_action", desc="Cursorless insert snippet action") # Deprecated tag; we should probably remove this and notify users that they @@ -27,15 +40,20 @@ @mod.capture( - rule="{user.cursorless_insertion_snippet_no_phrase} | {user.cursorless_insertion_snippet_single_phrase}" + rule="({user.cursorless_insertion_snippet_no_phrase} | {user.cursorless_insertion_snippet_single_phrase}) []" ) -def cursorless_insertion_snippet(m) -> dict: +def cursorless_insertion_snippet(m) -> InsertionSnippet: try: name = m.cursorless_insertion_snippet_no_phrase except AttributeError: name = m.cursorless_insertion_snippet_single_phrase.split(".")[0] - return {"type": "named", "name": name} + try: + destination = m.cursorless_destination + except AttributeError: + destination = ImplicitDestination() + + return InsertionSnippet(name, destination) # NOTE: Please do not change these dicts. Use the CSVs for customization. @@ -65,65 +83,103 @@ def cursorless_insertion_snippet(m) -> dict: } +def wrap_with_snippet(snippet_description: dict, target: CursorlessTarget): + cursorless_command_and_wait( + { + "name": "wrapWithSnippet", + "snippetDescription": snippet_description, + "target": target, + }, + ) + + +def insert_snippet(snippet_description: dict, destination: CursorlessDestination): + cursorless_command_and_wait( + { + "name": "insertSnippet", + "snippetDescription": snippet_description, + "destination": destination, + }, + ) + + +def insert_named_snippet( + name: str, destination: CursorlessDestination, substitutions: Optional[dict] +): + insert_snippet( + { + "type": "named", + "name": name, + "substitutions": substitutions, + }, + destination, + ) + + +def insert_custom_snippet(body: str, destination: CursorlessDestination): + insert_snippet( + { + "type": "custom", + "body": body, + }, + destination, + ) + + @mod.action_class class Actions: + def private_cursorless_insert_snippet(insertion_snippet: InsertionSnippet): + """Execute Cursorless insert snippet action""" + insert_named_snippet( + insertion_snippet.name, + insertion_snippet.destination, + ) + def private_cursorless_insert_snippet_with_phrase( - action: str, snippet_description: str, text: str + snippet_description: str, text: str ): - """Perform cursorless wrap action""" + """Cursorless: Insert snippet with phrase """ snippet_name, snippet_variable = snippet_description.split(".") - actions.user.cursorless_implicit_target_command( - action, - { - "type": "named", - "name": snippet_name, - "substitutions": {snippet_variable: text}, - }, + insert_named_snippet( + snippet_name, + ImplicitDestination(), + {snippet_variable: text}, ) def cursorless_insert_snippet_by_name(name: str): - """Inserts a named snippet""" - actions.user.cursorless_implicit_target_command( - "insertSnippet", - { - "type": "named", - "name": name, - }, + """Cursorless: Insert named snippet """ + insert_named_snippet( + snippet_name, + ImplicitDestination(), ) def cursorless_insert_snippet(body: str): - """Inserts a custom snippet""" - actions.user.cursorless_implicit_target_command( - "insertSnippet", - { - "type": "custom", - "body": body, - }, + """Cursorless: Insert custom snippet """ + insert_custom_snippet( + body, + ImplicitDestination(), ) def cursorless_wrap_with_snippet_by_name( - name: str, variable_name: str, target: dict + name: str, variable_name: str, target: CursorlessTarget ): - """Wrap target with a named snippet""" - actions.user.cursorless_single_target_command_with_arg_list( - "wrapWithSnippet", + """Cursorless: Wrap target with a named snippet """ + wrap_with_snippet( + { + "type": "named", + "name": name, + "variableName": variable_name, + }, target, - [ - { - "type": "named", - "name": name, - "variableName": variable_name, - } - ], ) def cursorless_wrap_with_snippet( body: str, - target: dict, + target: CursorlessTarget, variable_name: Optional[str] = None, scope: Optional[str] = None, ): - """Wrap target with a custom snippet""" + """Cursorless: Wrap target with custom snippet """ snippet_arg: dict[str, Any] = { "type": "custom", "body": body, @@ -132,11 +188,9 @@ def cursorless_wrap_with_snippet( snippet_arg["scopeType"] = {"type": scope} if variable_name is not None: snippet_arg["variableName"] = variable_name - - actions.user.cursorless_single_target_command_with_arg_list( - "wrapWithSnippet", + wrap_with_snippet( + snippet_arg, target, - [snippet_arg], ) From 40eb6fdf09fc64a7e3169222010d291aefe28b2d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:42:24 +0000 Subject: [PATCH 09/17] [pre-commit.ci lite] apply automatic fixes --- cursorless-talon/src/snippets.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cursorless-talon/src/snippets.py b/cursorless-talon/src/snippets.py index 836dfdf63e..0e150751de 100644 --- a/cursorless-talon/src/snippets.py +++ b/cursorless-talon/src/snippets.py @@ -1,13 +1,15 @@ -from typing import Any, Optional from dataclasses import dataclass +from typing import Any, Optional + +from talon import Module, app + +from .command import cursorless_command_and_wait +from .csv_overrides import init_csv_and_watch_changes from .targets.target_types import ( - CursorlessTarget, CursorlessDestination, + CursorlessTarget, ImplicitDestination, ) -from talon import Module, actions, app -from .command import cursorless_command_and_wait -from .csv_overrides import init_csv_and_watch_changes @dataclass From ff3a7f478d768faebe8eca07bc8e3f406b44c6b6 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 14 Jul 2023 17:06:46 +0200 Subject: [PATCH 10/17] make Cursorless commands Talon actions once again --- cursorless-talon-dev/src/cursorless_dev.py | 14 ++++-- cursorless-talon-dev/src/cursorless_dev.talon | 13 +++--- cursorless-talon/src/actions/actions.py | 8 ++-- cursorless-talon/src/actions/bring_move.py | 5 +-- cursorless-talon/src/actions/call.py | 5 ++- .../src/actions/execute_command.py | 4 +- cursorless-talon/src/actions/get_text.py | 5 ++- cursorless-talon/src/actions/paste.py | 5 +-- cursorless-talon/src/actions/replace.py | 4 +- cursorless-talon/src/actions/swap.py | 5 +-- cursorless-talon/src/actions/wrap.py | 7 ++- .../src/cheatsheet/cheat_sheet.py | 6 +-- cursorless-talon/src/command.py | 44 ++++++++++--------- cursorless-talon/src/cursorless_global.talon | 4 +- cursorless-talon/src/snippets.py | 5 +-- 15 files changed, 70 insertions(+), 64 deletions(-) diff --git a/cursorless-talon-dev/src/cursorless_dev.py b/cursorless-talon-dev/src/cursorless_dev.py index a72629b443..bea122ad5c 100644 --- a/cursorless-talon-dev/src/cursorless_dev.py +++ b/cursorless-talon-dev/src/cursorless_dev.py @@ -5,26 +5,32 @@ @mod.action_class class Actions: - def cursorless_record_navigation_test(): + def private_cursorless_record_navigation_test(): """Start / stop recording Cursorless navigation tests""" actions.user.run_rpc_command( "cursorless.recordTestCase", {"isHatTokenMapTest": True} ) - def cursorless_record_error_test(): + def private_cursorless_record_error_test(): """Start recording Cursorless error tests""" actions.user.run_rpc_command( "cursorless.recordTestCase", {"recordErrors": True} ) - def cursorless_record_highlights_test(): + def private_cursorless_record_highlights_test(): """Start recording Cursorless decoration tests""" actions.user.run_rpc_command( "cursorless.recordTestCase", {"isDecorationsTest": True} ) - def cursorless_record_that_mark_test(): + def private_cursorless_record_that_mark_test(): """Start recording Cursorless that mark tests""" actions.user.run_rpc_command( "cursorless.recordTestCase", {"captureFinalThatMark": True} ) + + def private_cursorless_makes_snippet_test(target): + """Test generating a snippet""" + actions.user.private_cursorless_command_no_wait( + {"name": "generateSnippet", "snippetName": "testSnippet", "target": target} + ) diff --git a/cursorless-talon-dev/src/cursorless_dev.talon b/cursorless-talon-dev/src/cursorless_dev.talon index afa2ae6e81..68e6571b1b 100644 --- a/cursorless-talon-dev/src/cursorless_dev.talon +++ b/cursorless-talon-dev/src/cursorless_dev.talon @@ -9,15 +9,16 @@ tag: user.cursorless user.run_rpc_command("cursorless.resumeRecording") {user.cursorless_homophone} record navigation: - user.cursorless_record_navigation_test() -{user.cursorless_homophone} record error: user.cursorless_record_error_test() + user.private_cursorless_record_navigation_test() +{user.cursorless_homophone} record error: + user.private_cursorless_record_error_test() {user.cursorless_homophone} record highlights: - user.cursorless_record_highlights_test() + user.private_cursorless_record_highlights_test() {user.cursorless_homophone} record that mark: - user.cursorless_record_that_mark_test() + user.private_cursorless_record_that_mark_test() {user.cursorless_homophone} update cheatsheet: - user.cursorless_cheat_sheet_update_json() + user.private_cursorless_cheat_sheet_update_json() test snippet make : - user.cursorless_single_target_command_no_wait("generateSnippet", cursorless_target, "testSnippet") + user.private_cursorless_makes_snippet_test(cursorless_target) diff --git a/cursorless-talon/src/actions/actions.py b/cursorless-talon/src/actions/actions.py index d58413c174..f0f6bd571a 100644 --- a/cursorless-talon/src/actions/actions.py +++ b/cursorless-talon/src/actions/actions.py @@ -50,12 +50,12 @@ def cursorless_command(action_name: str, target: CursorlessTarget): ) elif action_name in no_wait_actions: action = {"name": action_name, "target": target} - actions.user.cursorless_command_no_wait(action) + actions.user.private_cursorless_command_no_wait(action) if action_name in no_wait_actions_post_sleep: actions.sleep(no_wait_actions_post_sleep[action_name]) else: action = {"name": action_name, "target": target} - actions.user.cursorless_command_and_wait(action) + actions.user.private_cursorless_command_and_wait(action) def cursorless_vscode_command(command_id: str, target: CursorlessTarget): """ @@ -76,9 +76,9 @@ def private_cursorless_action_or_ide_command( type = instruction["type"] value = instruction["value"] if type == "cursorless_action": - return actions.user.cursorless_command(value, target) + actions.user.cursorless_command(value, target) elif type == "ide_command": - return actions.user.cursorless_ide_command(value, target) + actions.user.cursorless_ide_command(value, target) default_values = { diff --git a/cursorless-talon/src/actions/bring_move.py b/cursorless-talon/src/actions/bring_move.py index 6dadac0ad9..7861f76fd4 100644 --- a/cursorless-talon/src/actions/bring_move.py +++ b/cursorless-talon/src/actions/bring_move.py @@ -1,8 +1,7 @@ from dataclasses import dataclass -from talon import Module +from talon import Module, actions -from ..command import cursorless_command_and_wait from ..targets.target_types import ( CursorlessDestination, CursorlessTarget, @@ -38,7 +37,7 @@ def cursorless_bring_move_targets(m) -> BringMoveTargets: class Actions: def private_cursorless_bring_move(action_name: str, targets: BringMoveTargets): """Execute Cursorless move/bring action""" - cursorless_command_and_wait( + actions.user.private_cursorless_command_and_wait( { "name": action_name, "source": targets.source, diff --git a/cursorless-talon/src/actions/call.py b/cursorless-talon/src/actions/call.py index aa1c97074d..083adf26b8 100644 --- a/cursorless-talon/src/actions/call.py +++ b/cursorless-talon/src/actions/call.py @@ -1,9 +1,10 @@ -from ..command import cursorless_command_and_wait +from talon import actions + from ..targets.target_types import CursorlessTarget, ImplicitTarget def cursorless_call_action(target: CursorlessTarget): - cursorless_command_and_wait( + actions.user.private_cursorless_command_and_wait( { "name": "callAsFunction", "callee": target, diff --git a/cursorless-talon/src/actions/execute_command.py b/cursorless-talon/src/actions/execute_command.py index aa5d9b1169..217e2cae7e 100644 --- a/cursorless-talon/src/actions/execute_command.py +++ b/cursorless-talon/src/actions/execute_command.py @@ -1,4 +1,4 @@ -from ..command import cursorless_command_and_wait +from talon import actions from ..targets.target_types import CursorlessTarget @@ -6,7 +6,7 @@ def cursorless_execute_command_action( command_id: str, target: CursorlessTarget, command_options: dict = {} ): """Execute Cursorless execute command action""" - cursorless_command_and_wait( + actions.user.private_cursorless_command_and_wait( { "name": "executeCommand", "commandId": command_id, diff --git a/cursorless-talon/src/actions/get_text.py b/cursorless-talon/src/actions/get_text.py index 6f2ece34c6..c53c3bec5d 100644 --- a/cursorless-talon/src/actions/get_text.py +++ b/cursorless-talon/src/actions/get_text.py @@ -1,6 +1,7 @@ from typing import Optional -from ..command import cursorless_command_get +from talon import actions + from ..targets.target_types import CursorlessTarget @@ -10,7 +11,7 @@ def cursorless_get_text( ensure_single_target: Optional[bool] = None, ): """Get target texts""" - cursorless_command_get( + actions.user.private_cursorless_command_get( { "name": "getText", "options": { diff --git a/cursorless-talon/src/actions/paste.py b/cursorless-talon/src/actions/paste.py index f34aa4fd87..bcef75d9b5 100644 --- a/cursorless-talon/src/actions/paste.py +++ b/cursorless-talon/src/actions/paste.py @@ -1,6 +1,5 @@ -from talon import Module +from talon import Module, actions -from ..command import cursorless_command_and_wait from ..targets.target_types import CursorlessDestination mod = Module() @@ -12,7 +11,7 @@ class Actions: def private_cursorless_paste(destination: CursorlessDestination): """Execute Cursorless paste action""" - cursorless_command_and_wait( + actions.user.private_cursorless_command_and_wait( { "name": "pasteFromClipboard", "destination": destination, diff --git a/cursorless-talon/src/actions/replace.py b/cursorless-talon/src/actions/replace.py index ec21455d35..c05d12a8d4 100644 --- a/cursorless-talon/src/actions/replace.py +++ b/cursorless-talon/src/actions/replace.py @@ -1,10 +1,10 @@ -from ..command import cursorless_command_and_wait +from talon import actions from ..targets.target_types import CursorlessTarget, PrimitiveDestination def cursorless_replace_action(target: CursorlessTarget, replace_with: list[str]): """Execute Cursorless replace action. Replace targets with texts""" - cursorless_command_and_wait( + actions.user.private_cursorless_command_and_wait( { "name": "replace", "replaceWith": replace_with, diff --git a/cursorless-talon/src/actions/swap.py b/cursorless-talon/src/actions/swap.py index 197bd697f0..3b1b4c2269 100644 --- a/cursorless-talon/src/actions/swap.py +++ b/cursorless-talon/src/actions/swap.py @@ -1,6 +1,5 @@ -from talon import Module +from talon import Module, actions -from ..command import cursorless_command_and_wait from ..targets.target_types import CursorlessTarget, PrimitiveTarget mod = Module() @@ -30,7 +29,7 @@ def cursorless_swap_targets(m) -> list[CursorlessTarget]: class Actions: def private_cursorles_swap(targets: list[CursorlessTarget]): """Execute Cursorless swap action""" - cursorless_command_and_wait( + actions.user.private_cursorless_command_and_wait( { "name": "swapTargets", "target1": targets[0], diff --git a/cursorless-talon/src/actions/wrap.py b/cursorless-talon/src/actions/wrap.py index 7bde2f750f..b2a0470ff9 100644 --- a/cursorless-talon/src/actions/wrap.py +++ b/cursorless-talon/src/actions/wrap.py @@ -1,6 +1,5 @@ -from talon import Module +from talon import Module, actions -from ..command import cursorless_command_and_wait from ..paired_delimiter import PairedDelimiter from ..targets.target_types import CursorlessTarget @@ -18,7 +17,7 @@ def private_cursorless_wrap_paired_delimiter( if action_name == "rewrap": action_name = "rewrapWithPairedDelimiter" - cursorless_command_and_wait( + actions.user.private_cursorless_command_and_wait( { "name": action_name, "left": paired_delimiter.left, @@ -38,7 +37,7 @@ def private_cursorless_wrap_snippet( snippet_name, variable_name = parse_snippet_location(snippet_location) - cursorless_command_and_wait( + actions.user.private_cursorless_command_and_wait( { "name": action_name, "snippetDescription": { diff --git a/cursorless-talon/src/cheatsheet/cheat_sheet.py b/cursorless-talon/src/cheatsheet/cheat_sheet.py index 49160da38f..751b0cb20c 100644 --- a/cursorless-talon/src/cheatsheet/cheat_sheet.py +++ b/cursorless-talon/src/cheatsheet/cheat_sheet.py @@ -22,19 +22,19 @@ # @mod.action_class # class Actions: -# def cursorless_cheat_sheet_show_html(): +# def private_cursorless_cheat_sheet_show_html(): # """Show new cursorless html cheat sheet""" # app.notify( # 'Please first focus an app that supports cursorless, eg say "focus code"' # ) -# def cursorless_cheat_sheet_update_json(): +# def private_cursorless_cheat_sheet_update_json(): # """Update default cursorless cheatsheet json (for developer use only)""" # app.notify( # 'Please first focus an app that supports cursorless, eg say "focus code"' # ) -# def cursorless_open_instructions(): +# def private_cursorless_open_instructions(): # """Open web page with cursorless instructions""" # webbrowser.open(instructions_url) diff --git a/cursorless-talon/src/command.py b/cursorless-talon/src/command.py index 102f4ec111..519035f025 100644 --- a/cursorless-talon/src/command.py +++ b/cursorless-talon/src/command.py @@ -1,6 +1,6 @@ import dataclasses -from talon import actions, speech_system +from talon import Module, actions, speech_system from .cursorless_command_server import ( run_rpc_command_and_wait, @@ -20,6 +20,8 @@ class CursorlessCommand: CURSORLESS_COMMAND_ID = "cursorless.command" last_phrase = None +mod = Module() + def on_phrase(d): global last_phrase @@ -29,28 +31,28 @@ def on_phrase(d): speech_system.register("pre:phrase", on_phrase) -def cursorless_command_and_wait(action: dict): - """Execute cursorless command and wait for it to finish""" - run_rpc_command_and_wait( - CURSORLESS_COMMAND_ID, - construct_cursorless_command(action), - ) - - -def cursorless_command_no_wait(action: dict): - """Execute cursorless command without waiting""" - run_rpc_command_no_wait( - CURSORLESS_COMMAND_ID, - construct_cursorless_command(action), - ) +@mod.action_class +class Actions: + def private_cursorless_command_and_wait(action: dict): + """Execute cursorless command and wait for it to finish""" + run_rpc_command_and_wait( + CURSORLESS_COMMAND_ID, + construct_cursorless_command(action), + ) + def private_cursorless_command_no_wait(action: dict): + """Execute cursorless command without waiting""" + run_rpc_command_no_wait( + CURSORLESS_COMMAND_ID, + construct_cursorless_command(action), + ) -def cursorless_command_get(action: dict): - """Execute cursorless command and return result""" - return run_rpc_command_get( - CURSORLESS_COMMAND_ID, - construct_cursorless_command(action), - ) + def private_cursorless_command_get(action: dict): + """Execute cursorless command and return result""" + return run_rpc_command_get( + CURSORLESS_COMMAND_ID, + construct_cursorless_command(action), + ) def construct_cursorless_command(action: dict) -> dict: diff --git a/cursorless-talon/src/cursorless_global.talon b/cursorless-talon/src/cursorless_global.talon index 79637fc32d..97675e61ea 100644 --- a/cursorless-talon/src/cursorless_global.talon +++ b/cursorless-talon/src/cursorless_global.talon @@ -1,4 +1,4 @@ {user.cursorless_homophone} (reference | ref | cheatsheet | cheat sheet): - user.cursorless_cheat_sheet_show_html() + user.private_cursorless_cheat_sheet_show_html() {user.cursorless_homophone} (instructions | docks | help) | help {user.cursorless_homophone}: - user.cursorless_open_instructions() + user.private_cursorless_open_instructions() diff --git a/cursorless-talon/src/snippets.py b/cursorless-talon/src/snippets.py index 0e150751de..afa25ce4e5 100644 --- a/cursorless-talon/src/snippets.py +++ b/cursorless-talon/src/snippets.py @@ -3,7 +3,6 @@ from talon import Module, app -from .command import cursorless_command_and_wait from .csv_overrides import init_csv_and_watch_changes from .targets.target_types import ( CursorlessDestination, @@ -86,7 +85,7 @@ def cursorless_insertion_snippet(m) -> InsertionSnippet: def wrap_with_snippet(snippet_description: dict, target: CursorlessTarget): - cursorless_command_and_wait( + actions.user.private_cursorless_command_and_wait( { "name": "wrapWithSnippet", "snippetDescription": snippet_description, @@ -96,7 +95,7 @@ def wrap_with_snippet(snippet_description: dict, target: CursorlessTarget): def insert_snippet(snippet_description: dict, destination: CursorlessDestination): - cursorless_command_and_wait( + actions.user.private_cursorless_command_and_wait( { "name": "insertSnippet", "snippetDescription": snippet_description, From 512bdd8bc6e73a144904691cd4799b328663f0e3 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 14 Jul 2023 17:09:04 +0200 Subject: [PATCH 11/17] reactivate sheet cheat --- .../src/cheatsheet/cheat_sheet.py | 296 +++++++++--------- 1 file changed, 148 insertions(+), 148 deletions(-) diff --git a/cursorless-talon/src/cheatsheet/cheat_sheet.py b/cursorless-talon/src/cheatsheet/cheat_sheet.py index 751b0cb20c..dcd3c9c107 100644 --- a/cursorless-talon/src/cheatsheet/cheat_sheet.py +++ b/cursorless-talon/src/cheatsheet/cheat_sheet.py @@ -1,148 +1,148 @@ -# import webbrowser -# from pathlib import Path - -# from talon import Context, Module, app - -# from ..cursorless_command_server import run_rpc_command_and_wait -# from .get_list import get_list, get_lists -# from .sections.actions import get_actions -# from .sections.compound_targets import get_compound_targets -# from .sections.modifiers import get_modifiers -# from .sections.scopes import get_scopes -# from .sections.special_marks import get_special_marks - -# mod = Module() -# ctx = Context() -# ctx.matches = r""" -# tag: user.cursorless -# """ - -# instructions_url = "https://www.cursorless.org/docs/" - - -# @mod.action_class -# class Actions: -# def private_cursorless_cheat_sheet_show_html(): -# """Show new cursorless html cheat sheet""" -# app.notify( -# 'Please first focus an app that supports cursorless, eg say "focus code"' -# ) - -# def private_cursorless_cheat_sheet_update_json(): -# """Update default cursorless cheatsheet json (for developer use only)""" -# app.notify( -# 'Please first focus an app that supports cursorless, eg say "focus code"' -# ) - -# def private_cursorless_open_instructions(): -# """Open web page with cursorless instructions""" -# webbrowser.open(instructions_url) - - -# @ctx.action_class("user") -# class CursorlessActions: -# def cursorless_cheat_sheet_show_html(): -# """Show cursorless html cheat sheet""" -# # On Linux browsers installed using snap can't open files in a hidden directory -# if app.platform == "linux": -# cheatsheet_out_dir = cheatsheet_dir_linux() -# cheatsheet_filename = "cursorless-cheatsheet.html" -# else: -# cheatsheet_out_dir = Path.home() / ".cursorless" -# cheatsheet_filename = "cheatsheet.html" - -# cheatsheet_out_dir.mkdir(parents=True, exist_ok=True) -# cheatsheet_out_path = cheatsheet_out_dir / cheatsheet_filename -# run_rpc_command_and_wait( -# "cursorless.showCheatsheet", -# { -# "version": 0, -# "spokenFormInfo": cursorless_cheat_sheet_get_json(), -# "outputPath": str(cheatsheet_out_path), -# }, -# ) -# webbrowser.open(cheatsheet_out_path.as_uri()) - -# def cursorless_cheat_sheet_update_json(): -# """Update default cursorless cheatsheet json (for developer use only)""" -# run_rpc_command_and_wait( -# "cursorless.internal.updateCheatsheetDefaults", -# cursorless_cheat_sheet_get_json(), -# ) - - -# def cheatsheet_dir_linux() -> Path: -# """Get cheatsheet directory for Linux""" -# try: -# # 1. Get users actual document directory -# import platformdirs - -# return Path(platformdirs.user_documents_dir()) -# except Exception: -# # 2. Look for a documents directory in user home -# user_documents_dir = Path.home() / "Documents" -# if user_documents_dir.is_dir(): -# return user_documents_dir - -# # 3. Fall back to user home -# return Path.home() - - -# def cursorless_cheat_sheet_get_json(): -# """Get cursorless cheat sheet json""" -# return { -# "sections": [ -# { -# "name": "Actions", -# "id": "actions", -# "items": get_actions(), -# }, -# { -# "name": "Scopes", -# "id": "scopes", -# "items": get_scopes(), -# }, -# { -# "name": "Modifiers", -# "id": "modifiers", -# "items": get_modifiers(), -# }, -# { -# "name": "Paired delimiters", -# "id": "pairedDelimiters", -# "items": get_lists( -# [ -# "wrapper_only_paired_delimiter", -# "wrapper_selectable_paired_delimiter", -# "selectable_only_paired_delimiter", -# ], -# "pairedDelimiter", -# ), -# }, -# { -# "name": "Special marks", -# "id": "specialMarks", -# "items": get_special_marks(), -# }, -# { -# "name": "Positions", -# "id": "positions", -# "items": get_list("position", "position"), -# }, -# { -# "name": "Compound targets", -# "id": "compoundTargets", -# "items": get_compound_targets(), -# }, -# { -# "name": "Colors", -# "id": "colors", -# "items": get_list("hat_color", "hatColor"), -# }, -# { -# "name": "Shapes", -# "id": "shapes", -# "items": get_list("hat_shape", "hatShape"), -# }, -# ] -# } +import webbrowser +from pathlib import Path + +from talon import Context, Module, app + +from ..cursorless_command_server import run_rpc_command_and_wait +from .get_list import get_list, get_lists +from .sections.actions import get_actions +from .sections.compound_targets import get_compound_targets +from .sections.modifiers import get_modifiers +from .sections.scopes import get_scopes +from .sections.special_marks import get_special_marks + +mod = Module() +ctx = Context() +ctx.matches = r""" +tag: user.cursorless +""" + +instructions_url = "https://www.cursorless.org/docs/" + + +@mod.action_class +class Actions: + def private_cursorless_cheat_sheet_show_html(): + """Show new cursorless html cheat sheet""" + app.notify( + 'Please first focus an app that supports cursorless, eg say "focus code"' + ) + + def private_cursorless_cheat_sheet_update_json(): + """Update default cursorless cheatsheet json (for developer use only)""" + app.notify( + 'Please first focus an app that supports cursorless, eg say "focus code"' + ) + + def private_cursorless_open_instructions(): + """Open web page with cursorless instructions""" + webbrowser.open(instructions_url) + + +@ctx.action_class("user") +class CursorlessActions: + def cursorless_cheat_sheet_show_html(): + """Show cursorless html cheat sheet""" + # On Linux browsers installed using snap can't open files in a hidden directory + if app.platform == "linux": + cheatsheet_out_dir = cheatsheet_dir_linux() + cheatsheet_filename = "cursorless-cheatsheet.html" + else: + cheatsheet_out_dir = Path.home() / ".cursorless" + cheatsheet_filename = "cheatsheet.html" + + cheatsheet_out_dir.mkdir(parents=True, exist_ok=True) + cheatsheet_out_path = cheatsheet_out_dir / cheatsheet_filename + run_rpc_command_and_wait( + "cursorless.showCheatsheet", + { + "version": 0, + "spokenFormInfo": cursorless_cheat_sheet_get_json(), + "outputPath": str(cheatsheet_out_path), + }, + ) + webbrowser.open(cheatsheet_out_path.as_uri()) + + def cursorless_cheat_sheet_update_json(): + """Update default cursorless cheatsheet json (for developer use only)""" + run_rpc_command_and_wait( + "cursorless.internal.updateCheatsheetDefaults", + cursorless_cheat_sheet_get_json(), + ) + + +def cheatsheet_dir_linux() -> Path: + """Get cheatsheet directory for Linux""" + try: + # 1. Get users actual document directory + import platformdirs + + return Path(platformdirs.user_documents_dir()) + except Exception: + # 2. Look for a documents directory in user home + user_documents_dir = Path.home() / "Documents" + if user_documents_dir.is_dir(): + return user_documents_dir + + # 3. Fall back to user home + return Path.home() + + +def cursorless_cheat_sheet_get_json(): + """Get cursorless cheat sheet json""" + return { + "sections": [ + { + "name": "Actions", + "id": "actions", + "items": get_actions(), + }, + { + "name": "Scopes", + "id": "scopes", + "items": get_scopes(), + }, + { + "name": "Modifiers", + "id": "modifiers", + "items": get_modifiers(), + }, + { + "name": "Paired delimiters", + "id": "pairedDelimiters", + "items": get_lists( + [ + "wrapper_only_paired_delimiter", + "wrapper_selectable_paired_delimiter", + "selectable_only_paired_delimiter", + ], + "pairedDelimiter", + ), + }, + { + "name": "Special marks", + "id": "specialMarks", + "items": get_special_marks(), + }, + { + "name": "Positions", + "id": "positions", + "items": get_list("position", "position"), + }, + { + "name": "Compound targets", + "id": "compoundTargets", + "items": get_compound_targets(), + }, + { + "name": "Colors", + "id": "colors", + "items": get_list("hat_color", "hatColor"), + }, + { + "name": "Shapes", + "id": "shapes", + "items": get_list("hat_shape", "hatShape"), + }, + ] + } From 265e2c38694d2f88c592393bf842939dac72e88c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Fri, 14 Jul 2023 15:12:46 +0000 Subject: [PATCH 12/17] [pre-commit.ci lite] apply automatic fixes --- cursorless-talon-dev/src/cursorless_dev.talon | 2 +- cursorless-talon/src/actions/execute_command.py | 1 + cursorless-talon/src/actions/replace.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cursorless-talon-dev/src/cursorless_dev.talon b/cursorless-talon-dev/src/cursorless_dev.talon index 68e6571b1b..b7277dd2bc 100644 --- a/cursorless-talon-dev/src/cursorless_dev.talon +++ b/cursorless-talon-dev/src/cursorless_dev.talon @@ -10,7 +10,7 @@ tag: user.cursorless {user.cursorless_homophone} record navigation: user.private_cursorless_record_navigation_test() -{user.cursorless_homophone} record error: +{user.cursorless_homophone} record error: user.private_cursorless_record_error_test() {user.cursorless_homophone} record highlights: user.private_cursorless_record_highlights_test() diff --git a/cursorless-talon/src/actions/execute_command.py b/cursorless-talon/src/actions/execute_command.py index 217e2cae7e..067fc14488 100644 --- a/cursorless-talon/src/actions/execute_command.py +++ b/cursorless-talon/src/actions/execute_command.py @@ -1,4 +1,5 @@ from talon import actions + from ..targets.target_types import CursorlessTarget diff --git a/cursorless-talon/src/actions/replace.py b/cursorless-talon/src/actions/replace.py index c05d12a8d4..10e92dd626 100644 --- a/cursorless-talon/src/actions/replace.py +++ b/cursorless-talon/src/actions/replace.py @@ -1,4 +1,5 @@ from talon import actions + from ..targets.target_types import CursorlessTarget, PrimitiveDestination From 302a5649c355e8095dfa1a0671cd339d4efae2b9 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 14 Jul 2023 17:27:08 +0200 Subject: [PATCH 13/17] fix errors --- cursorless-talon/src/actions/homophones.py | 4 +- .../src/apps/cursorless_vscode.py | 4 +- .../src/cheatsheet/cheat_sheet.py | 4 +- .../cheatsheet/sections/compound_targets.py | 104 +++++++++--------- cursorless-talon/src/targets/target.py | 2 +- 5 files changed, 59 insertions(+), 59 deletions(-) diff --git a/cursorless-talon/src/actions/homophones.py b/cursorless-talon/src/actions/homophones.py index 3760a8d7c5..283c7b4546 100644 --- a/cursorless-talon/src/actions/homophones.py +++ b/cursorless-talon/src/actions/homophones.py @@ -1,12 +1,12 @@ from talon import actions, app -from .get_text import get_text +from .get_text import cursorless_get_text from .replace import cursorless_replace_action def cursorless_homophones_action(target: dict): """Replaced target with next homophone""" - texts = get_text(target, show_decorations=False) + texts = cursorless_get_text(target, show_decorations=False) try: updated_texts = list(map(get_next_homophone, texts)) except LookupError as e: diff --git a/cursorless-talon/src/apps/cursorless_vscode.py b/cursorless-talon/src/apps/cursorless_vscode.py index f3a022f586..962002397e 100644 --- a/cursorless-talon/src/apps/cursorless_vscode.py +++ b/cursorless-talon/src/apps/cursorless_vscode.py @@ -1,6 +1,6 @@ from talon import Context, actions, app -from ..actions.get_text import get_text +from ..actions.get_text import cursorless_get_text from ..cursorless_command_server import run_rpc_command_no_wait from ..targets.target_types import CursorlessTarget @@ -17,7 +17,7 @@ class Actions: def private_cursorless_find(target: CursorlessTarget): """Find text of target in editor""" - texts = get_text(target, ensure_single_target=True) + texts = cursorless_get_text(target, ensure_single_target=True) search_text = texts[0] if len(search_text) > 200: search_text = search_text[:200] diff --git a/cursorless-talon/src/cheatsheet/cheat_sheet.py b/cursorless-talon/src/cheatsheet/cheat_sheet.py index dcd3c9c107..bc53ced9a5 100644 --- a/cursorless-talon/src/cheatsheet/cheat_sheet.py +++ b/cursorless-talon/src/cheatsheet/cheat_sheet.py @@ -41,7 +41,7 @@ def private_cursorless_open_instructions(): @ctx.action_class("user") class CursorlessActions: - def cursorless_cheat_sheet_show_html(): + def private_cursorless_cheat_sheet_show_html(): """Show cursorless html cheat sheet""" # On Linux browsers installed using snap can't open files in a hidden directory if app.platform == "linux": @@ -63,7 +63,7 @@ def cursorless_cheat_sheet_show_html(): ) webbrowser.open(cheatsheet_out_path.as_uri()) - def cursorless_cheat_sheet_update_json(): + def private_cursorless_cheat_sheet_update_json(): """Update default cursorless cheatsheet json (for developer use only)""" run_rpc_command_and_wait( "cursorless.internal.updateCheatsheetDefaults", diff --git a/cursorless-talon/src/cheatsheet/sections/compound_targets.py b/cursorless-talon/src/cheatsheet/sections/compound_targets.py index ab475dc702..faf7383d88 100644 --- a/cursorless-talon/src/cheatsheet/sections/compound_targets.py +++ b/cursorless-talon/src/cheatsheet/sections/compound_targets.py @@ -1,59 +1,59 @@ -# from ..get_list import get_raw_list +from ..get_list import get_raw_list -# FORMATTERS = { -# "rangeExclusive": lambda start, end: f"between {start} and {end}", -# "rangeInclusive": lambda start, end: f"{start} through {end}", -# "rangeExcludingStart": lambda start, end: f"end of {start} through {end}", -# "rangeExcludingEnd": lambda start, end: f"{start} until start of {end}", -# "verticalRange": lambda start, end: f"{start} vertically through {end}", -# } +FORMATTERS = { + "rangeExclusive": lambda start, end: f"between {start} and {end}", + "rangeInclusive": lambda start, end: f"{start} through {end}", + "rangeExcludingStart": lambda start, end: f"end of {start} through {end}", + "rangeExcludingEnd": lambda start, end: f"{start} until start of {end}", + "verticalRange": lambda start, end: f"{start} vertically through {end}", +} -# def get_compound_targets(): -# list_connective_term = next( -# spoken_form -# for spoken_form, value in get_raw_list("list_connective").items() -# if value == "listConnective" -# ) -# vertical_range_term = next( -# spoken_form -# for spoken_form, value in get_raw_list("range_type").items() -# if value == "verticalRange" -# ) +def get_compound_targets(): + list_connective_term = next( + spoken_form + for spoken_form, value in get_raw_list("list_connective").items() + if value == "listConnective" + ) + vertical_range_term = next( + spoken_form + for spoken_form, value in get_raw_list("range_type").items() + if value == "verticalRange" + ) -# return [ -# { -# "id": "listConnective", -# "type": "compoundTargetConnective", -# "variations": [ -# { -# "spokenForm": f" {list_connective_term} ", -# "description": " and ", -# }, -# ], -# }, -# *[ -# get_entry(spoken_form, id) -# for spoken_form, id in get_raw_list("range_connective").items() -# ], -# get_entry(vertical_range_term, "verticalRange"), -# ] + return [ + { + "id": "listConnective", + "type": "compoundTargetConnective", + "variations": [ + { + "spokenForm": f" {list_connective_term} ", + "description": " and ", + }, + ], + }, + *[ + get_entry(spoken_form, id) + for spoken_form, id in get_raw_list("range_connective").items() + ], + get_entry(vertical_range_term, "verticalRange"), + ] -# def get_entry(spoken_form, id): -# formatter = FORMATTERS[id] +def get_entry(spoken_form, id): + formatter = FORMATTERS[id] -# return { -# "id": id, -# "type": "compoundTargetConnective", -# "variations": [ -# { -# "spokenForm": f" {spoken_form} ", -# "description": formatter("", ""), -# }, -# { -# "spokenForm": f"{spoken_form} ", -# "description": formatter("selection", ""), -# }, -# ], -# } + return { + "id": id, + "type": "compoundTargetConnective", + "variations": [ + { + "spokenForm": f" {spoken_form} ", + "description": formatter("", ""), + }, + { + "spokenForm": f"{spoken_form} ", + "description": formatter("selection", ""), + }, + ], + } diff --git a/cursorless-talon/src/targets/target.py b/cursorless-talon/src/targets/target.py index 9922f0e9dc..417d8c1c4b 100644 --- a/cursorless-talon/src/targets/target.py +++ b/cursorless-talon/src/targets/target.py @@ -2,7 +2,7 @@ from talon import Module -from .primitive_target import ListTarget, PrimitiveTarget, RangeTarget +from .target_types import ListTarget, PrimitiveTarget, RangeTarget mod = Module() From b57303f77e070bddf4b8067fd8d67c6b00893d04 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 14 Jul 2023 17:30:35 +0200 Subject: [PATCH 14/17] fix broken sheet cheats --- cursorless-talon/src/cheatsheet/sections/actions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cursorless-talon/src/cheatsheet/sections/actions.py b/cursorless-talon/src/cheatsheet/sections/actions.py index a5ebf1a91b..e0a7b355d9 100644 --- a/cursorless-talon/src/cheatsheet/sections/actions.py +++ b/cursorless-talon/src/cheatsheet/sections/actions.py @@ -27,9 +27,7 @@ def get_actions(): } swap_connective = list(get_raw_list("swap_connective").keys())[0] - source_destination_connective = list( - get_raw_list("source_destination_connective").keys() - )[0] + source_destination_connective = list(get_raw_list("insertion_mode_to").keys())[0] return [ *make_dict_readable( From 5126681dbc451b403aa8d3037ae94d5578f1e3ef Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 14 Jul 2023 17:53:54 +0200 Subject: [PATCH 15/17] bug fixes --- cursorless-talon/src/actions/get_text.py | 4 ++-- cursorless-talon/src/cursorless.talon | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cursorless-talon/src/actions/get_text.py b/cursorless-talon/src/actions/get_text.py index c53c3bec5d..0b5a486aeb 100644 --- a/cursorless-talon/src/actions/get_text.py +++ b/cursorless-talon/src/actions/get_text.py @@ -9,9 +9,9 @@ def cursorless_get_text( target: CursorlessTarget, show_decorations: Optional[bool] = None, ensure_single_target: Optional[bool] = None, -): +) -> list[str]: """Get target texts""" - actions.user.private_cursorless_command_get( + return actions.user.private_cursorless_command_get( { "name": "getText", "options": { diff --git a/cursorless-talon/src/cursorless.talon b/cursorless-talon/src/cursorless.talon index 22a15a123e..1745036552 100644 --- a/cursorless-talon/src/cursorless.talon +++ b/cursorless-talon/src/cursorless.talon @@ -17,7 +17,7 @@ tag: user.cursorless user.private_cursorless_reformat(cursorless_target, formatters) {user.cursorless_wrap_action} : - user.private_cursorless_wrap_paired_delimiter(cursorless_wrap_action, cursorless_wrapper_paired_delimiter) + user.private_cursorless_wrap_paired_delimiter(cursorless_wrap_action, cursorless_target, cursorless_wrapper_paired_delimiter) {user.cursorless_wrapper_snippet} {user.cursorless_wrap_action} : user.private_cursorless_wrap_snippet(cursorless_wrap_action, cursorless_target, cursorless_wrapper_snippet) From 1a1dacc0a3ccb4a59f5fe84988600a2a48f5f7b9 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 14 Jul 2023 17:57:52 +0200 Subject: [PATCH 16/17] fixes --- cursorless-talon/src/snippets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cursorless-talon/src/snippets.py b/cursorless-talon/src/snippets.py index afa25ce4e5..34de785d24 100644 --- a/cursorless-talon/src/snippets.py +++ b/cursorless-talon/src/snippets.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Any, Optional -from talon import Module, app +from talon import Module, app, actions from .csv_overrides import init_csv_and_watch_changes from .targets.target_types import ( @@ -150,7 +150,7 @@ def private_cursorless_insert_snippet_with_phrase( def cursorless_insert_snippet_by_name(name: str): """Cursorless: Insert named snippet """ insert_named_snippet( - snippet_name, + name, ImplicitDestination(), ) From a2baf62e2055fc3c41872b55530629c507258cc0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Fri, 14 Jul 2023 15:59:35 +0000 Subject: [PATCH 17/17] [pre-commit.ci lite] apply automatic fixes --- cursorless-talon/src/snippets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cursorless-talon/src/snippets.py b/cursorless-talon/src/snippets.py index 34de785d24..d508eac35f 100644 --- a/cursorless-talon/src/snippets.py +++ b/cursorless-talon/src/snippets.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Any, Optional -from talon import Module, app, actions +from talon import Module, actions, app from .csv_overrides import init_csv_and_watch_changes from .targets.target_types import (