diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 33f266adbb..79ab9f5f65 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @pokey +* @pokey @AndreasArvidsson diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index df1b46c289..a1c0e1c20f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -96,5 +96,5 @@ jobs: # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - - name: Checkout repository - uses: actions/checkout@v3 + - name: Do nothing + run: "true" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 40c5107e5c..e143c8cd4a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,3 +72,5 @@ jobs: name: dumps path: ${{ env.VSCODE_CRASH_DIR }} if: failure() + - name: Forbid TODOs + run: ./scripts/forbid-todo.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ed1c7e0628..9abdc72b13 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,6 +32,7 @@ repos: exclude_types: [svg] exclude: patches/.*\.patch - id: fix-byte-order-marker + - id: forbid-submodules - id: mixed-line-ending - id: trailing-whitespace # Trailing whitespace breaks yaml files if you use a multiline string @@ -83,8 +84,3 @@ repos: rev: 23.3.0 hooks: - id: black - - repo: https://github.com/wenkokke/talonfmt - rev: 1.9.5 - hooks: - - id: talonfmt - args: ["--in-place"] diff --git a/.vscode/extensions.json b/.vscode/extensions.json index f39b8b1e4c..8606829da6 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,6 +3,7 @@ // for the documentation about the extensions.json format "recommendations": [ "AndreasArvidsson.andreas-talon", + "charliermarsh.ruff", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "jrieken.vscode-tree-sitter-query", diff --git a/CHANGELOG.md b/CHANGELOG.md index c47102693f..8a1817e535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,7 +139,7 @@ There's too much new stuff in this release to fit in a highlights reel, so we'll - Improve and unify selection updating behaviour to handle overlapping ranges [\#138](https://github.com/cursorless-dev/cursorless/issues/138) - Duplicate symbols after VS Code update [\#111](https://github.com/cursorless-dev/cursorless/issues/111) - Fold action not working properly with multiple list elements [\#39](https://github.com/cursorless-dev/cursorless/issues/39) -- The `clear` command clashes with Knausj commands [\#68](https://github.com/pokey/cursorless-talon/issues/68) +- The `clear` command clashes with community commands [\#68](https://github.com/pokey/cursorless-talon/issues/68) **Closed issues:** diff --git a/changelog/2023-09-addedArgumentTargetToCallAction.md b/changelog/2023-09-addedArgumentTargetToCallAction.md new file mode 100644 index 0000000000..bc5ff89dce --- /dev/null +++ b/changelog/2023-09-addedArgumentTargetToCallAction.md @@ -0,0 +1,6 @@ +--- +tags: [enhancement, talon] +pullRequest: 1900 +--- + +- Added optional second target to action `call` to specify argument. eg: `"call air on bat"`. diff --git a/changelog/2023-09-addedInsertPythonAction.md b/changelog/2023-09-addedInsertPythonAction.md new file mode 100644 index 0000000000..417b0d0c08 --- /dev/null +++ b/changelog/2023-09-addedInsertPythonAction.md @@ -0,0 +1,7 @@ +--- +tags: [enhancement, talon] +pullRequest: 1875 +mergeDate: 2023-09-10 +--- + +- Added `cursorless_insert` action to the public Talon api. This api enables you to define custom grammars for Cursorless text insertion. See the [talon-side api docs](https://www.cursorless.org/docs/user/customization/#public-talon-actions) for more diff --git a/cursorless-talon-dev/src/cursorless_dev.talon b/cursorless-talon-dev/src/cursorless_dev.talon index 54582af86d..e777549d88 100644 --- a/cursorless-talon-dev/src/cursorless_dev.talon +++ b/cursorless-talon-dev/src/cursorless_dev.talon @@ -1,3 +1,5 @@ +mode: command +mode: user.cursorless_spoken_form_test tag: user.cursorless - @@ -28,3 +30,6 @@ tag: user.cursorless test snippet make : user.private_cursorless_make_snippet_test(cursorless_target) + +parse tree : + user.cursorless_command("private.showParseTree", cursorless_target) diff --git a/cursorless-talon-dev/src/cursorless_test.talon b/cursorless-talon-dev/src/cursorless_test.talon index 561abe24b6..03250a3251 100644 --- a/cursorless-talon-dev/src/cursorless_test.talon +++ b/cursorless-talon-dev/src/cursorless_test.talon @@ -7,6 +7,10 @@ test api command : user.cursorless_command("setSelection", cursorless_target) test api command bring : user.cursorless_command("replaceWithTarget", cursorless_target) +test api insert : + user.cursorless_insert(cursorless_destination, word) +test api insert and : + user.cursorless_insert(cursorless_destination, word_list) test api insert snippet: user.cursorless_insert_snippet("Hello, $foo! My name is $bar!") test api insert snippet by name: diff --git a/cursorless-talon-dev/src/default_vocabulary.py b/cursorless-talon-dev/src/default_vocabulary.py index c2347135a6..6fada1a773 100644 --- a/cursorless-talon-dev/src/default_vocabulary.py +++ b/cursorless-talon-dev/src/default_vocabulary.py @@ -13,10 +13,6 @@ # https://github.com/talonhub/community/blob/9acb6c9659bb0c9b794a7b7126d025603b4ed726/core/keys/keys.py#L139C1-L171C2 punctuation_words = { - # TODO: I'm not sure why we need these, I think it has something to do with - # Dragon. Possibly it has been fixed by later improvements to talon? -rntz - # "`": "`", - # ",": ",", # <== these things "back tick": "`", "comma": ",", # Workaround for issue with conformer b-series; see #946 diff --git a/cursorless-talon/src/actions/actions.py b/cursorless-talon/src/actions/actions.py index b157ba3e0b..93decf1e4f 100644 --- a/cursorless-talon/src/actions/actions.py +++ b/cursorless-talon/src/actions/actions.py @@ -1,14 +1,19 @@ +from typing import Callable, Union + from talon import Module, actions -from ..targets.target_types import CursorlessTarget, ImplicitDestination +from ..targets.target_types import ( + CursorlessDestination, + CursorlessTarget, + ImplicitDestination, +) from .bring_move import BringMoveTargets -from .call import cursorless_call_action from .execute_command import cursorless_execute_command_action from .homophones import cursorless_homophones_action +from .replace import cursorless_replace_action mod = Module() - mod.list( "cursorless_simple_action", desc="Cursorless internal: simple actions", @@ -38,11 +43,11 @@ "wrap_action", "insert_snippet_action", "reformat_action", + "call_action", "experimental_action", ] -callback_actions = { - "callAsFunction": cursorless_call_action, +callback_actions: dict[str, Callable[[CursorlessTarget], None]] = { "findInDocument": actions.user.private_cursorless_find, "nextHomophone": cursorless_homophones_action, } @@ -64,10 +69,11 @@ "{user.cursorless_simple_action} |" "{user.cursorless_experimental_action} |" "{user.cursorless_callback_action} |" + "{user.cursorless_call_action} |" "{user.cursorless_custom_action}" ) ) -def cursorless_action_or_ide_command(m) -> dict: +def cursorless_action_or_ide_command(m) -> dict[str, str]: try: value = m.cursorless_custom_action type = "ide_command" @@ -90,6 +96,8 @@ def cursorless_command(action_name: str, target: CursorlessTarget): actions.user.private_cursorless_bring_move( action_name, BringMoveTargets(target, ImplicitDestination()) ) + elif action_name == "callAsFunction": + actions.user.private_cursorless_call(target) elif action_name in no_wait_actions: action = {"name": action_name, "target": target} actions.user.private_cursorless_command_no_wait(action) @@ -111,8 +119,16 @@ def cursorless_ide_command(command_id: str, target: CursorlessTarget): """Perform ide command on cursorless target""" return cursorless_execute_command_action(command_id, target) + def cursorless_insert( + destination: CursorlessDestination, text: Union[str, list[str]] + ): + """Perform text insertion on Cursorless destination""" + if isinstance(text, str): + text = [text] + cursorless_replace_action(destination, text) + def private_cursorless_action_or_ide_command( - instruction: dict, target: CursorlessTarget + instruction: dict[str, str], target: CursorlessTarget ): """Perform cursorless action or ide command on target (internal use only)""" type = instruction["type"] diff --git a/cursorless-talon/src/actions/call.py b/cursorless-talon/src/actions/call.py index 083adf26b8..6601d7ba5f 100644 --- a/cursorless-talon/src/actions/call.py +++ b/cursorless-talon/src/actions/call.py @@ -1,13 +1,22 @@ -from talon import actions +from talon import Module, actions from ..targets.target_types import CursorlessTarget, ImplicitTarget +mod = Module() +mod.list("cursorless_call_action", desc="Cursorless call action") -def cursorless_call_action(target: CursorlessTarget): - actions.user.private_cursorless_command_and_wait( - { - "name": "callAsFunction", - "callee": target, - "argument": ImplicitTarget(), - } - ) + +@mod.action_class +class Actions: + def private_cursorless_call( + callee: CursorlessTarget, + argument: CursorlessTarget = ImplicitTarget(), + ): + """Execute Cursorless call action""" + actions.user.private_cursorless_command_and_wait( + { + "name": "callAsFunction", + "callee": callee, + "argument": argument, + } + ) diff --git a/cursorless-talon/src/actions/get_text.py b/cursorless-talon/src/actions/get_text.py index 1b38f49a1a..8ee8849da8 100644 --- a/cursorless-talon/src/actions/get_text.py +++ b/cursorless-talon/src/actions/get_text.py @@ -7,11 +7,12 @@ def cursorless_get_text_action( target: CursorlessTarget, + *, show_decorations: Optional[bool] = None, ensure_single_target: Optional[bool] = None, ) -> list[str]: """Get target texts""" - options = {} + options: dict[str, bool] = {} if show_decorations is not None: options["showDecorations"] = show_decorations diff --git a/cursorless-talon/src/actions/homophones.py b/cursorless-talon/src/actions/homophones.py index a462038071..df571f9469 100644 --- a/cursorless-talon/src/actions/homophones.py +++ b/cursorless-talon/src/actions/homophones.py @@ -1,10 +1,13 @@ +from typing import Optional + from talon import actions, app +from ..targets.target_types import CursorlessTarget, PrimitiveDestination from .get_text import cursorless_get_text_action from .replace import cursorless_replace_action -def cursorless_homophones_action(target: dict): +def cursorless_homophones_action(target: CursorlessTarget): """Replaced target with next homophone""" texts = cursorless_get_text_action(target, show_decorations=False) try: @@ -12,11 +15,12 @@ def cursorless_homophones_action(target: dict): except LookupError as e: app.notify(str(e)) return - cursorless_replace_action(target, updated_texts) + destination = PrimitiveDestination("to", target) + cursorless_replace_action(destination, updated_texts) -def get_next_homophone(word: str): - homophones = actions.user.homophones_get(word) +def get_next_homophone(word: str) -> str: + homophones: Optional[list[str]] = actions.user.homophones_get(word) if not homophones: raise LookupError(f"Found no homophones for '{word}'") index = (homophones.index(word.lower()) + 1) % len(homophones) @@ -24,7 +28,7 @@ def get_next_homophone(word: str): return format_homophone(word, homophone) -def format_homophone(word: str, homophone: str): +def format_homophone(word: str, homophone: str) -> str: if word.isupper(): return homophone.upper() if word == word.capitalize(): diff --git a/cursorless-talon/src/actions/reformat.py b/cursorless-talon/src/actions/reformat.py index 12a142a35f..ce2b89d427 100644 --- a/cursorless-talon/src/actions/reformat.py +++ b/cursorless-talon/src/actions/reformat.py @@ -1,6 +1,6 @@ from talon import Module, actions -from ..targets.target_types import CursorlessTarget +from ..targets.target_types import CursorlessTarget, PrimitiveDestination from .get_text import cursorless_get_text_action from .replace import cursorless_replace_action @@ -15,4 +15,5 @@ def private_cursorless_reformat(target: CursorlessTarget, formatters: str): """Execute Cursorless reformat action. Reformat target with formatter""" texts = cursorless_get_text_action(target, show_decorations=False) updated_texts = [actions.user.reformat_text(text, formatters) for text in texts] - cursorless_replace_action(target, updated_texts) + destination = PrimitiveDestination("to", target) + cursorless_replace_action(destination, updated_texts) diff --git a/cursorless-talon/src/actions/replace.py b/cursorless-talon/src/actions/replace.py index 10e92dd626..4067eb1f2b 100644 --- a/cursorless-talon/src/actions/replace.py +++ b/cursorless-talon/src/actions/replace.py @@ -1,14 +1,16 @@ from talon import actions -from ..targets.target_types import CursorlessTarget, PrimitiveDestination +from ..targets.target_types import CursorlessDestination -def cursorless_replace_action(target: CursorlessTarget, replace_with: list[str]): +def cursorless_replace_action( + destination: CursorlessDestination, replace_with: list[str] +): """Execute Cursorless replace action. Replace targets with texts""" actions.user.private_cursorless_command_and_wait( { "name": "replace", "replaceWith": replace_with, - "destination": PrimitiveDestination("to", target), + "destination": destination, } ) diff --git a/cursorless-talon/src/cheatsheet/get_list.py b/cursorless-talon/src/cheatsheet/get_list.py index 9bf49586a0..001feaa1cc 100644 --- a/cursorless-talon/src/cheatsheet/get_list.py +++ b/cursorless-talon/src/cheatsheet/get_list.py @@ -1,33 +1,48 @@ import re +from collections.abc import Mapping, Sequence +from typing import Optional, TypedDict from talon import registry from ..conventions import get_cursorless_list_name -def get_list(name, type, descriptions=None): +class Variation(TypedDict): + spokenForm: str + description: str + + +class ListItemDescriptor(TypedDict): + id: str + type: str + variations: list[Variation] + + +def get_list( + name: str, type: str, descriptions: Optional[Mapping[str, str]] = None +) -> list[ListItemDescriptor]: if descriptions is None: descriptions = {} items = get_raw_list(name) - item_dict = items if isinstance(items, dict) else {item: item for item in items} - return make_dict_readable(type, item_dict, descriptions) + return make_dict_readable(type, items, descriptions) -def get_lists(names: list[str], type: str, descriptions=None): +def get_lists( + names: Sequence[str], type: str, descriptions: Optional[Mapping[str, str]] = None +) -> list[ListItemDescriptor]: return [item for name in names for item in get_list(name, type, descriptions)] -def get_raw_list(name): +def get_raw_list(name: str) -> Mapping[str, str]: cursorless_list_name = get_cursorless_list_name(name) return registry.lists[cursorless_list_name][0].copy() -def make_dict_readable(type: str, dict, descriptions=None): - if descriptions is None: - descriptions = {} - +def make_dict_readable( + type: str, dict: Mapping[str, str], descriptions: Mapping[str, str] +) -> list[ListItemDescriptor]: return [ { "id": value, @@ -43,7 +58,7 @@ def make_dict_readable(type: str, dict, descriptions=None): ] -def make_readable(text): +def make_readable(text: str) -> str: text = text.replace(".", " ") return de_camel(text).lower().capitalize() diff --git a/cursorless-talon/src/cheatsheet/sections/actions.py b/cursorless-talon/src/cheatsheet/sections/actions.py index 7bbeeeeaca..b83c3e5ec8 100644 --- a/cursorless-talon/src/cheatsheet/sections/actions.py +++ b/cursorless-talon/src/cheatsheet/sections/actions.py @@ -12,6 +12,7 @@ def get_actions(): "moveToTarget", "swapTargets", "applyFormatter", + "callAsFunction", "wrapWithPairedDelimiter", "rewrap", "pasteFromClipboard", @@ -34,7 +35,8 @@ def get_actions(): "action", simple_actions, { - "callAsFunction": "Call on selection", + "editNewLineAfter": "Edit new line/scope after", + "editNewLineBefore": "Edit new line/scope before", }, ), { @@ -99,6 +101,20 @@ def get_actions(): } ], }, + { + "id": "callAsFunction", + "type": "action", + "variations": [ + { + "spokenForm": f"{complex_actions['callAsFunction']} ", + "description": "Call on selection", + }, + { + "spokenForm": f"{complex_actions['callAsFunction']} on ", + "description": "Call on ", + }, + ], + }, { "id": "wrapWithPairedDelimiter", "type": "action", diff --git a/cursorless-talon/src/cheatsheet/sections/scopes.py b/cursorless-talon/src/cheatsheet/sections/scopes.py index 49722a814a..b7133c9a72 100644 --- a/cursorless-talon/src/cheatsheet/sections/scopes.py +++ b/cursorless-talon/src/cheatsheet/sections/scopes.py @@ -5,5 +5,8 @@ def get_scopes(): return get_lists( ["scope_type"], "scopeType", - {"argumentOrParameter": "Argument"}, + { + "argumentOrParameter": "Argument", + "boundedNonWhitespaceSequence": "Non whitespace sequence stopped by surrounding pair delimeters", + }, ) diff --git a/cursorless-talon/src/cursorless.talon b/cursorless-talon/src/cursorless.talon index 0f2f331dff..9b784f684c 100644 --- a/cursorless-talon/src/cursorless.talon +++ b/cursorless-talon/src/cursorless.talon @@ -18,6 +18,9 @@ tag: user.cursorless {user.cursorless_reformat_action} at : user.private_cursorless_reformat(cursorless_target, formatters) +{user.cursorless_call_action} on : + user.private_cursorless_call(cursorless_target_1, cursorless_target_2) + {user.cursorless_wrap_action} : user.private_cursorless_wrap_with_paired_delimiter(cursorless_wrap_action, cursorless_target, cursorless_wrapper_paired_delimiter) diff --git a/cursorless-talon/src/marks/decorated_mark.py b/cursorless-talon/src/marks/decorated_mark.py index 2a92f77c9a..75675ee895 100644 --- a/cursorless-talon/src/marks/decorated_mark.py +++ b/cursorless-talon/src/marks/decorated_mark.py @@ -4,6 +4,7 @@ from talon import Module, actions, cron, fs from ..csv_overrides import init_csv_and_watch_changes +from .mark_types import DecoratedSymbol mod = Module() @@ -28,9 +29,9 @@ def cursorless_grapheme(m) -> str: @mod.capture( rule="[{user.cursorless_hat_color}] [{user.cursorless_hat_shape}] " ) -def cursorless_decorated_symbol(m) -> dict[str, Any]: +def cursorless_decorated_symbol(m) -> DecoratedSymbol: """A decorated symbol""" - hat_color = getattr(m, "cursorless_hat_color", "default") + hat_color: str = getattr(m, "cursorless_hat_color", "default") try: hat_style_name = f"{hat_color}-{m.cursorless_hat_shape}" except AttributeError: @@ -82,10 +83,10 @@ def cursorless_decorated_symbol(m) -> dict[str, Any]: } FALLBACK_COLOR_ENABLEMENT = DEFAULT_COLOR_ENABLEMENT -unsubscribe_hat_styles = None +unsubscribe_hat_styles: Any = None -def setup_hat_styles_csv(hat_colors: dict, hat_shapes: dict): +def setup_hat_styles_csv(hat_colors: dict[str, str], hat_shapes: dict[str, str]): global unsubscribe_hat_styles ( @@ -149,7 +150,7 @@ def setup_hat_styles_csv(hat_colors: dict, hat_shapes: dict): slow_reload_job = None -def init_hats(hat_colors: dict, hat_shapes: dict): +def init_hats(hat_colors: dict[str, str], hat_shapes: dict[str, str]): setup_hat_styles_csv(hat_colors, hat_shapes) vscode_settings_path: Path = actions.user.vscode_settings_path().resolve() diff --git a/cursorless-talon/src/marks/lines_number.py b/cursorless-talon/src/marks/lines_number.py index 4f892cd12a..a1ff66fe62 100644 --- a/cursorless-talon/src/marks/lines_number.py +++ b/cursorless-talon/src/marks/lines_number.py @@ -1,10 +1,10 @@ from collections.abc import Callable from dataclasses import dataclass -from typing import Any from talon import Module from ..targets.range_target import RangeConnective +from .mark_types import LineNumber, LineNumberMark, LineNumberType mod = Module() @@ -14,8 +14,8 @@ @dataclass class CustomizableTerm: cursorlessIdentifier: str - type: str - formatter: Callable + type: LineNumberType + formatter: Callable[[int], int] # NOTE: Please do not change these dicts. Use the CSVs for customization. @@ -35,15 +35,13 @@ class CustomizableTerm: "[ ]" ) ) -def cursorless_line_number(m) -> dict[str, Any]: +def cursorless_line_number(m) -> LineNumber: direction = directions_map[m.cursorless_line_direction] - anchor = create_line_number_mark( - direction.type, direction.formatter(m.private_cursorless_number_small_list[0]) - ) - if len(m.private_cursorless_number_small_list) > 1: + numbers: list[int] = m.private_cursorless_number_small_list + anchor = create_line_number_mark(direction.type, direction.formatter(numbers[0])) + if len(numbers) > 1: active = create_line_number_mark( - direction.type, - direction.formatter(m.private_cursorless_number_small_list[1]), + direction.type, direction.formatter(numbers[1]) ) range_connective: RangeConnective = m.cursorless_range_connective return { @@ -56,9 +54,9 @@ def cursorless_line_number(m) -> dict[str, Any]: return anchor -def create_line_number_mark(line_number_type: str, line_number: int) -> dict[str, Any]: +def create_line_number_mark(type: LineNumberType, line_number: int) -> LineNumberMark: return { "type": "lineNumber", - "lineNumberType": line_number_type, + "lineNumberType": type, "lineNumber": line_number, } diff --git a/cursorless-talon/src/marks/mark.py b/cursorless-talon/src/marks/mark.py index 0231ebb936..18e21abb75 100644 --- a/cursorless-talon/src/marks/mark.py +++ b/cursorless-talon/src/marks/mark.py @@ -1,7 +1,7 @@ -from typing import Any - from talon import Module +from .mark_types import Mark + mod = Module() @@ -12,5 +12,5 @@ "" # row (ie absolute mod 100), up, down ) ) -def cursorless_mark(m) -> dict[str, Any]: +def cursorless_mark(m) -> Mark: return m[0] diff --git a/cursorless-talon/src/marks/mark_types.py b/cursorless-talon/src/marks/mark_types.py new file mode 100644 index 0000000000..3985b7d4f5 --- /dev/null +++ b/cursorless-talon/src/marks/mark_types.py @@ -0,0 +1,31 @@ +from typing import Literal, TypedDict, Union + + +class DecoratedSymbol(TypedDict): + type: Literal["decoratedSymbol"] + symbolColor: str + character: str + + +SimpleMark = dict[Literal["type"], str] + +LineNumberType = Literal["modulo100", "relative"] + + +class LineNumberMark(TypedDict): + type: Literal["lineNumber"] + lineNumberType: LineNumberType + lineNumber: int + + +class LineNumberRange(TypedDict): + type: Literal["range"] + anchor: LineNumberMark + active: LineNumberMark + excludeAnchor: bool + excludeActive: bool + + +LineNumber = Union[LineNumberMark, LineNumberRange] + +Mark = Union[DecoratedSymbol, SimpleMark, LineNumber] diff --git a/cursorless-talon/src/marks/simple_mark.py b/cursorless-talon/src/marks/simple_mark.py index 4359d05dba..a98d3d84c9 100644 --- a/cursorless-talon/src/marks/simple_mark.py +++ b/cursorless-talon/src/marks/simple_mark.py @@ -1,5 +1,7 @@ from talon import Module +from .mark_types import SimpleMark + mod = Module() mod.list("cursorless_simple_mark", desc="Cursorless simple marks") @@ -15,7 +17,7 @@ @mod.capture(rule="{user.cursorless_simple_mark}") -def cursorless_simple_mark(m) -> dict[str, str]: +def cursorless_simple_mark(m) -> SimpleMark: return { "type": simple_marks[m.cursorless_simple_mark], } diff --git a/cursorless-talon/src/spoken_forms.json b/cursorless-talon/src/spoken_forms.json index db5acfab04..40a0fd16b1 100644 --- a/cursorless-talon/src/spoken_forms.json +++ b/cursorless-talon/src/spoken_forms.json @@ -43,7 +43,6 @@ "unfold": "unfoldRegion" }, "callback_action": { - "call": "callAsFunction", "scout": "findInDocument", "phones": "nextHomophone" }, @@ -55,7 +54,8 @@ "swap_action": { "swap": "swapTargets" }, "wrap_action": { "wrap": "wrapWithPairedDelimiter", "repack": "rewrap" }, "insert_snippet_action": { "snippet": "insertSnippet" }, - "reformat_action": { "format": "applyFormatter" } + "reformat_action": { "format": "applyFormatter" }, + "call_action": { "call": "callAsFunction" } }, "target_connectives.csv": { "range_connective": { diff --git a/cursorless-talon/src/spoken_forms.py b/cursorless-talon/src/spoken_forms.py index a0c804a992..16d53205f5 100644 --- a/cursorless-talon/src/spoken_forms.py +++ b/cursorless-talon/src/spoken_forms.py @@ -54,7 +54,7 @@ def update(): for disposable in disposables: disposable() - with open(JSON_FILE) as file: + with open(JSON_FILE, encoding="utf-8") as file: spoken_forms = json.load(file) handle_csv = auto_construct_defaults(spoken_forms, init_csv_and_watch_changes) diff --git a/cursorless-talon/src/targets/target_types.py b/cursorless-talon/src/targets/target_types.py index 6e130ce9f8..49e028322b 100644 --- a/cursorless-talon/src/targets/target_types.py +++ b/cursorless-talon/src/targets/target_types.py @@ -1,5 +1,7 @@ from dataclasses import dataclass -from typing import Literal, Optional, Union +from typing import Any, Literal, Optional, Union + +from ..marks.mark_types import Mark RangeTargetType = Literal["vertical"] @@ -7,8 +9,8 @@ @dataclass class PrimitiveTarget: type = "primitive" - mark: Optional[dict] - modifiers: Optional[list[dict]] + mark: Optional[Mark] + modifiers: Optional[list[dict[str, Any]]] @dataclass diff --git a/data/playground/python/maps_and_lists.py b/data/playground/python/maps_and_lists.py new file mode 100644 index 0000000000..3e1ccdd83c --- /dev/null +++ b/data/playground/python/maps_and_lists.py @@ -0,0 +1,34 @@ +import sys + +one = 1 + + +def mapsAndLists(): + # immutable can serve as dictionary keys + key1 = "a" + key2 = False + key3 = 1.5 + + # Python "tuple" is "pair"/"round" scope + _ = (1, 2, 3) + + # Python "list" is "list" scope + _ = [1, 2, 3] + _ = [sys.path[0], sys.path[1]] + fruits = ["apple", "banana", "cherry", "kiwi", "mango"] + _ = [x for x in fruits if "a" in x] + + # Python "set" is "list" scope + _ = {1, 2, 3} + + # Python "dictionary" is "map" scope + d1 = {"a": 1, "b": 2, "c": 3} + _ = {key1: 1, key2: 2, key3: 3} + _ = {value: key for (key, value) in d1.items()} + + # complex ones + _ = [[1, 2, 3], 4, 5, 6] + _ = [[1, 2, 3], {1, 2, 3}, {"a": 1, "b": 2, "c": 3}] + _ = {{"a": 1, "b": 2, "c": 3}: 1, d1: 2} + _ = {{1, 2, 3}: 1, {2, 3, 4}: 2} + _ = ({1, 2, 3}, {1, 2, 3}, {1, 2, 3}) diff --git a/data/playground/python/statements.py b/data/playground/python/statements.py new file mode 100644 index 0000000000..d98c0b04af --- /dev/null +++ b/data/playground/python/statements.py @@ -0,0 +1,36 @@ +import sys + + +def statements(): + print(sys.path[0]) # comment 1 + print("hello") # comment 2 + + # the below statement has additional spaces after it + val = 1 == 2 + if val is True: + return + + # also the below has non-empty indentation + + c = range(10) + c.append(100) + x = 1 + x /= 2 + for i in range(10): + if i == 0: + continue + print(i) + break + + age = 120 + if age > 90: + print("You are too old to party, granny.") + elif age < 0: + print("You're yet to be born") + elif age >= 18: + print("You are allowed to party") + else: + print("You're too young to party") + + +statements() diff --git a/data/playground/python/strings.py b/data/playground/python/strings.py new file mode 100644 index 0000000000..98e07fd2b7 --- /dev/null +++ b/data/playground/python/strings.py @@ -0,0 +1,8 @@ +value = 3 + +a = "single quote string" +b = "double quote string" +c = """triple single quote string""" +d = """triple double quote string""" +e = r"literal string" +f = f"format string {value}" diff --git a/data/playground/python/values.py b/data/playground/python/values.py new file mode 100644 index 0000000000..e20b579375 --- /dev/null +++ b/data/playground/python/values.py @@ -0,0 +1,64 @@ +# Useful commands: +# "visualize value" +# "visualize value iteration" +# "take value" +# "chuck value" +# "chuck every value" + +# the argument value "True" that is part of "b=True" +# is "value" scope +def func(b=True, c=True): + # the returned value "1" that is part of "return 1" + # is "value" scope + if b is True: + return 1 + else: + return 0 + +# the argument value "False" that is part of "b=False" +# is "value" scope +func(b=False) +# but here, there is no "value" scope for "False" since no argument +func(False) + +# both "1.5" and "25" are "value" scope +val = 1.5 +val /= 25 + +val1, val2 = 0, 5 +val1 = 0, val2 = 5 +val1 = val2 + +def my_funk(value: str) -> str: + print(value) + +def my_funk(value: str = "hello", other: bool=False) -> str: + print(value) + +# we can say "change every value" to allow modifying all the values in one go +def foo(): + a = 0 + b = 1 + c = 2 + +# But we don't support outside of a function yet +a = 0 +b = 1 +c = 2 + +# values of a Python "dictionary" are "value" scope +# we can say "chuck every value" to convert the dict into a set +d1 = {"a": 1, "b": 2, "c": 3} + +_ = {value: key for (key, value) in d1.items()} + +# complex ones +_ = {{"a": 1, "b": 2, "c": 3}: 1, d1: 2} +_ = {{1, 2, 3}: 1, {2, 3, 4}: 2} + +# we don't want the access to a a Python "dictionary" +# value to be of "value" scope so we have it here +# to be sure we ignore it +d1["a"] + +value = "hello world" diff --git a/docs/contributing/CONTRIBUTING.md b/docs/contributing/CONTRIBUTING.md index 8ad61eac25..64b6f4e878 100644 --- a/docs/contributing/CONTRIBUTING.md +++ b/docs/contributing/CONTRIBUTING.md @@ -43,7 +43,9 @@ extension](#running--testing-extension-locally). You may also find the [VSCode A code --profile=cursorlessDevelopment --install-extension some.extension ``` - where `some.extension` is the id of the extension you'd like to install into the sandbox + where `some.extension` is the id of the extension you'd like to install into the sandbox. + + Note that if you are adding support for a new language that isn't in the default list of [language identifiers](https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers) supported by VSCode, you may need to add an extension dependency. See [Adding a new language](./adding-a-new-language.md#2-ensure-file-type-is-supported-by-vscode) for more details. 6. Copy / symlink `cursorless-talon-dev` into your Talon user directory for some useful voice commands for developing Cursorless. diff --git a/docs/contributing/adding-a-new-language.md b/docs/contributing/adding-a-new-language.md index 860b817f29..b692992cb3 100644 --- a/docs/contributing/adding-a-new-language.md +++ b/docs/contributing/adding-a-new-language.md @@ -9,7 +9,11 @@ of a document. See the [docs](https://github.com/pokey/vscode-parse-tree/#adding-a-new-language) there for how to add support for a new parser -## 2. Define parse tree patterns in Cursorless +## 2. Ensure file type is supported by VSCode + +If you are adding support for a new language that isn't natively detected by VSCode, you will need to add the appropriate extension to the list of dependencies. The list of languages officially supported by VSCode is listed [in the VSCode docs](https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers). If your language is in that list, you can skip this step and proceed to step 3. If your language is not in that list, you need to find a VSCode extension that adds support for your language, and add the id of the given extension to [`packages/common/src/extensionDependencies.ts`](../../packages/common/src/extensionDependencies.ts) and then re-run `pnpm init-vscode-sandbox` to ensure it is installed. If you do not do this you will encounter errors when attempting to execute cursorless commands in the next step. See [#1895](https://github.com/cursorless-dev/cursorless/issues/1895) for more info. + +## 3. Define parse tree patterns in Cursorless First a few notes / tips: diff --git a/docs/contributing/architecture/hat-snapshots.md b/docs/contributing/architecture/hat-snapshots.md index 1ead256699..de4c33c4f3 100644 --- a/docs/contributing/architecture/hat-snapshots.md +++ b/docs/contributing/architecture/hat-snapshots.md @@ -2,7 +2,7 @@ In order to allow long chained command phrases, we take a snapshot of the hat token map at the start of a phrase and continue to use this map during the course of the entire phrase. This way you can be sure that any commands issued during the course of a single phrase that refer to a decorated token will continue to refer to the same logical token no matter what happens in the document during phrase execution. Note that the ranges of tokens will be kept current as the document changes so that they refer to the same logical range, but the same logical token will keep the same key in the hat token map over the course of a phrase. -To make this work, first the voice engine [touches](https://github.com/knausj85/knausj_talon/blob/e373780af16256ab8fd5638af32d97fa23c4c0fc/apps/vscode/command_client/command_client.py#L398) a file within the signals subdirectory of the command server communication directory after the phrase has been parsed but right before execution begins. Then cursorless will check the version of the signal file before it [reads](https://github.com/cursorless-dev/cursorless/blob/2a624888369d41b0531e472d001d63d09912c8aa/src/core/HatTokenMap.ts#L88) or [updates](https://github.com/cursorless-dev/cursorless/blob/0d1004bafc6764734bee62afbfbb02500630a264/src/core/HatTokenMap.ts#L70) the hat token map via the command server [signal API](https://github.com/pokey/command-server/blob/2b9f9ea2a38b6e95aa60ff9553a804165e527308/src/extension.ts#L29). If the signal has been emitted since the last time cursorless took a snapshot of the hat token map, it will take a new snapshot and continue to use that snapshot of the hats until the next time the signal is emitted. Note that the signal transmission is asynchronous so cursorless just needs to make sure to check the version of the signal before it either updates or reads the map. +To make this work, first the voice engine [touches](https://github.com/talonhub/community/blob/e373780af16256ab8fd5638af32d97fa23c4c0fc/apps/vscode/command_client/command_client.py#L398) a file within the signals subdirectory of the command server communication directory after the phrase has been parsed but right before execution begins. Then cursorless will check the version of the signal file before it [reads](https://github.com/cursorless-dev/cursorless/blob/2a624888369d41b0531e472d001d63d09912c8aa/src/core/HatTokenMap.ts#L88) or [updates](https://github.com/cursorless-dev/cursorless/blob/0d1004bafc6764734bee62afbfbb02500630a264/src/core/HatTokenMap.ts#L70) the hat token map via the command server [signal API](https://github.com/pokey/command-server/blob/2b9f9ea2a38b6e95aa60ff9553a804165e527308/src/extension.ts#L29). If the signal has been emitted since the last time cursorless took a snapshot of the hat token map, it will take a new snapshot and continue to use that snapshot of the hats until the next time the signal is emitted. Note that the signal transmission is asynchronous so cursorless just needs to make sure to check the version of the signal before it either updates or reads the map. In the diagram below, we document the flow of a couple different cases. Dotted lines represent asynchronous / fire-and-forget communication: diff --git a/docs/user/README.md b/docs/user/README.md index 8b5fec551d..2c3906a233 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -351,6 +351,15 @@ If your cursor is touching a token, you can say `"take every instance"` to selec Pro tip: if you say eg `"take five instances air"`, and it turns out you need more, you can say eg `"take that and next two instances that"` to select the next two instances after the last instance you selected. +###### Experimental: `"from"` + +We have experimental support for prefixing a command with `"from "` to narrow the range within which `"every instance"` searches, or to set the start point from which `"next instance"` searches. For example: + +- `"from funk take every instance air"`: selects all instances of the token with a hat over the letter `a` in the current function +- `"from air take next instance bat"`: selects the next instance of the token with a hat over the letter `b` starting from the token with a hat over the letter `a` + +Note that the `"from"` modifier is not enabled by default; you must remove the `-` at the start of the line starting with `-from` in your `experimental/experimental_actions.csv` [settings csv](./customization.md). Note also that this feature is considered experimental and may change in the future. + ##### `"just"` The `"just"` modifier strips the target of any semantic information, treating it as just a raw range, with the following effects: @@ -367,7 +376,7 @@ The `"just"` modifier strips the target of any semantic information, treating it - `"chuck just line"` will delete only the content of the current line, without removing the line ending, resulting in a blank line, unlike the default behaviour of `"chuck line"` that removes the line entirely, leaving no blank line. -- A raw range does not have its own insertion delimitiers. +- A raw range does not have its own insertion delimiters. - For example, `"paste after just air"` will paste directly after the air token, without inserting a space, as opposed to the way `"paste after air"` would insert a space before the pasted content. - If you use `"just"` on the destination of a `"bring"` command, it will inherit its insertion delimiters from the source of the `"bring"` action. For example, in the command `"bring arg air and bat after just paren"`, the `"air"` and `"bat"` arguments will be joined by commas. In contrast, `"bring arg air and bat after token paren"` would join the arguments with spaces. - In the case of [`"instance"`](#instance), by default `"every instance air"` will only consider instances of the air token that are themselves full tokens, but `"every instance just air"` doesn't have such a restriction, because we've stripped air of its semantic "token-ness". @@ -378,15 +387,6 @@ Some examples: - `"chuck just line"`: deletes just the content of the line, leaving a blank line. - `"bring bat after just air"`: results in something like `aaabbb` where the bat token was copied after the air token with no delimeter between them. -###### Experimental: `"from"` - -We have experimental support for prefixing a command with `"from "` to narrow the range within which `"every instance"` searches, or to set the start point from which `"next instance"` searches. For example: - -- `"from funk take every instance air"`: selects all instances of the token with a hat over the letter `a` in the current function -- `"from air take next instance bat"`: selects the next instance of the token with a hat over the letter `b` starting from the token with a hat over the letter `a` - -Note that the `"from"` modifier is not enabled by default; you must remove the `-` at the start of the line starting with `-from` in your `experimental/experimental_actions.csv` [settings csv](./customization.md). Note also that this feature is considered experimental and may change in the future. - ##### Surrounding pair Cursorless has support for expanding the selection to the nearest containing paired delimiter, eg the surrounding parentheses, curly brackets, etc. @@ -579,9 +579,13 @@ For example: - `"drink "`: Inserts a new line above the target line, and moves the cursor to the newly created line - `"pour "`: Inserts a new line below the target line, and moves the cursor to the newly created line -eg: -`pour blue air` -Insert empty line below the token containing letter 'a' with a blue hat. +eg: `pour blue air` will insert empty line below the token containing letter 'a' with a blue hat and moves the cursor to the new line. + +Note: `"drink"` and `"pour"` are actually a bit more versatile than just lines. +If you use a [syntactic scope](#syntactic-scopes) modifier on the target, then`"drink"` and `"pour"` will do the +appropriate insertions to prepare the text for a new instance of that scope. + +eg: `pour item air` will insert a comma and space after the air item, and place the cursor after the inserted characters. ### Homophones diff --git a/docs/user/customization.md b/docs/user/customization.md index d78e321b65..5fda1a7720 100644 --- a/docs/user/customization.md +++ b/docs/user/customization.md @@ -118,6 +118,9 @@ Cursorless exposes a couple talon actions and captures that you can use to defin - `user.cursorless_ide_command(command_id: str, target: cursorless_target)`: Performs a built-in IDE command on the given target eg: `user.cursorless_ide_command("editor.action.addCommentLine", cursorless_target)` +- `user.cursorless_insert(destination: CursorlessDestination, text: Union[str, List[str]])`: + Insert text at destination. + eg: `user.cursorless_insert(cursorless_destination, "hello")` #### Snippet actions diff --git a/docs/user/experimental/keyboard/modal.md b/docs/user/experimental/keyboard/modal.md index 375dd0bff8..d46d2c9d36 100644 --- a/docs/user/experimental/keyboard/modal.md +++ b/docs/user/experimental/keyboard/modal.md @@ -99,7 +99,7 @@ To bind keys that do not have modifiers (eg just pressing `a`), add entries like }, ``` -Any supported scopes, actions, or colors can be added to these sections, using the same identifierss that appear in the second column of your customisation csvs. Feel free to add / tweak / remove the keyboard shortcuts above as you see fit. +Any supported scopes, actions, or colors can be added to these sections, using the same identifiers that appear in the second column of your customisation csvs. Feel free to add / tweak / remove the keyboard shortcuts above as you see fit. The above allows you to press `d` followed by any letter to highlight the given token, `i` to expand to its containing line, and `t` to select the given target. diff --git a/docs/user/experimental/snippets.md b/docs/user/experimental/snippets.md index 86f7c8535d..a53aa03f80 100644 --- a/docs/user/experimental/snippets.md +++ b/docs/user/experimental/snippets.md @@ -92,7 +92,7 @@ Note that each snippet can use `insertionScopeTypes` to indicate that it will au As usual, the spoken forms for these snippets can be [customized by csv](../customization.md). The csvs are in the files in `cursorless-settings/experimental` with `snippet` in their name. -In addition, you can change the term `"snippet"` (for snippet insertion) using actions.csv. Keep in mind that if you change it to `"snip"`, you may want to turn off the built-in knausj `"snip"` commands to avoid conflicts. +In addition, you can change the term `"snippet"` (for snippet insertion) using actions.csv. Keep in mind that if you change it to `"snip"`, you may want to turn off the built-in community `"snip"` commands to avoid conflicts. ## Adding your own snippets diff --git a/docs/user/glossary.md b/docs/user/glossary.md index d2ec049fe6..ca7a2d12e7 100644 --- a/docs/user/glossary.md +++ b/docs/user/glossary.md @@ -16,7 +16,7 @@ The [range](#range) within which a [scope](#scope) is valid/active. For example, ## Iteration scope -A canonical [range](#range) for a given [scope type](#scope-type) that defines a list of sibling [scopes](#scope). For example, the iteration scope for `"arg"` is an argument list. The iteration scope is used for things like `"every"`, to select all sibling scopes. This allows us to say things like `"take every arg"`, `"pre every line"`, etc. +The canonical [scope](#scope-type) for a given [scope type](#scope-type) that defines the iteration range when `"every "` or eg `"first "` are issued without an explicit input range. For example, the iteration scope for `"arg"` is an argument list. Thus, when you say `"take every arg"`, you'll get every argument in the argument list containing your cursor, and `"take every arg air"` will select every argument in the argument list containing the `"air"` token. Note that the iteration scope can be ignored by giving an explicit range, eg `"take every arg air file"`, `"take every arg air past bat"`, etc. ## Insertion delimiter diff --git a/docs/user/installation.md b/docs/user/installation.md index 88ce69c42c..ce9adcbeb2 100644 --- a/docs/user/installation.md +++ b/docs/user/installation.md @@ -1,8 +1,8 @@ # Installation 1. Install [Talon](https://talonvoice.com/) -2. Install [knausj_talon](https://github.com/knausj85/knausj_talon). - _(Or see [here](https://github.com/cursorless-dev/cursorless/wiki/Talon-home-requirements) if you prefer not to use knausj.)_ +2. Install the [community Talon commands](https://github.com/talonhub/community). + _(Or see [here](https://github.com/cursorless-dev/cursorless/wiki/Talon-home-requirements) if you prefer not to use community.)_ 3. Install [VSCode](https://code.visualstudio.com/) 4. Install the [VSCode talon extension pack](https://marketplace.visualstudio.com/items?itemName=pokey.talon) 5. Install the [Cursorless VSCode extension](https://marketplace.visualstudio.com/items?itemName=pokey.cursorless) @@ -25,9 +25,9 @@ Alternatively, access the directory by right clicking the Talon icon in taskbar, The folder structure should look something like the below: ```insert code: -~/.talon/user/knausj_talon -~/.talon/user/knausj_talon/apps -~/.talon/user/knausj_talon/code +~/.talon/user/community +~/.talon/user/community/apps +~/.talon/user/community/code ... ~/.talon/user/cursorless-talon ~/.talon/user/cursorless-talon/src @@ -50,9 +50,9 @@ Alternatively, access the directory by right clicking the Talon icon in taskbar, The folder structure should look something like the below: ```insert code: -%AppData%\Talon\user\knausj_talon -%AppData%\Talon\user\knausj_talon\apps -%AppData%\Talon\user\knausj_talon\code +%AppData%\Talon\user\community +%AppData%\Talon\user\community\apps +%AppData%\Talon\user\community\code ... %AppData%\Talon\user\cursorless-talon %AppData%\Talon\user\cursorless-talon\src diff --git a/docs/user/release-notes/0.27.0.md b/docs/user/release-notes/0.28.0.md similarity index 90% rename from docs/user/release-notes/0.27.0.md rename to docs/user/release-notes/0.28.0.md index 0ed5ef1e6b..f832190ae5 100644 --- a/docs/user/release-notes/0.27.0.md +++ b/docs/user/release-notes/0.28.0.md @@ -1,9 +1,9 @@ --- -version: "0.27.0" -releaseDate: "2023-08-16" +version: "0.28.0" +releaseDate: "2023-09-01" --- -# Release Notes for 0.27.0 +# Release Notes for 0.28.0 ## Preamble @@ -13,7 +13,7 @@ Given we don't actually have "releases", we're arbitrarily considering everythin ## The subject at hand -Since the start of 2023, we've merged [289 PRs from 9 authors](https://github.com/cursorless-dev/cursorless/pulls?q=is%3Apr+is%3Amerged+merged%3A%3E%3D2023-01-01+sort%3Aupdated-asc) (including a whopping [59](https://github.com/cursorless-dev/cursorless/pulls?q=is%3Apr+is%3Amerged+merged%3A%3E%3D2023-01-01+sort%3Aupdated-asc+author%3AAndreasArvidsson) from [@AndreasArvidsson](https://github.com/sponsors/AndreasArvidsson)!), so there's a lot of ground to cover πŸ˜… +Since the start of 2023, we've merged [306 PRs from 9 authors](https://github.com/cursorless-dev/cursorless/pulls?q=is%3Apr+is%3Amerged+merged%3A2023-01-01..2023-09-01+sort%3Aupdated-asc) (including a whopping [62](https://github.com/cursorless-dev/cursorless/pulls?q=is%3Apr+is%3Amerged+merged%3A2023-01-01..2023-09-01+sort%3Aupdated-asc+author%3AAndreasArvidsson+) from [@AndreasArvidsson](https://github.com/sponsors/AndreasArvidsson)!), so there's a lot of ground to cover πŸ˜… But here are some highlights: @@ -24,10 +24,10 @@ But here are some highlights: - `"branch"` ([#1149](https://github.com/cursorless-dev/cursorless/pull/1149)) - `"instance"` ([video 🎬](https://youtu.be/rqWmwcfZ_sw)) ([#1497](https://github.com/cursorless-dev/cursorless/pull/1497)) - `"sentence"` ([video 🎬](https://youtu.be/rdLH2GKJirE)) ([#1595](https://github.com/cursorless-dev/cursorless/pull/1595)) -- Added [the scope visualizer](../scope-visualizer.md) ([#1653](https://github.com/cursorless-dev/cursorless/pull/1653)) to let you visualize scopes live in your editor. Video to follow; stay tuned 😎. But here's a screenshot ![Scope visualizer](../images/visualize-funk.png) +- Added [the scope visualizer](../scope-visualizer.md) ([#1653](https://github.com/cursorless-dev/cursorless/pull/1653)) to let you visualize scopes live in your editor ([video 🎬](https://youtu.be/BbXEzUrf5lU)). And here's a screenshot ![Scope visualizer](../images/visualize-funk.png) - A next-gen inference engine that enables a much more powerful grammar, allowing you to do things like individually targeting a sequence of scopes, eg `"every line air past bat"`. To learn more, see the initial [#1462](https://github.com/cursorless-dev/cursorless/pull/1462), and then the follow-up [#1463](https://github.com/cursorless-dev/cursorless/pull/1463) if you're addicted to 🀯. Note that this grammar only works for our next-gen scopes, which include most generic scopes (`"line"`, `"token"`, `"word"`, `"character"`, `"block"`), as well as some language-specific scopes. We still have a lot of language-specific scopes to upgrade, as the infrastructure to do so is still quite new. Stay tuned - [Stable hats](../hatAssignment.md) ([#1252](https://github.com/cursorless-dev/cursorless/pull/1252)) keep hats from moving around so much while you edit. This change is something you may or may not have consciously noticed, but if you spontaneously experienced a deep feeling of oneness and calm at some point in the past six months, the newly meditative hats might have been the cause 🎩🧘 -- Improved support for very large hats. A trend among users has been to slightly increase their line height (eg 1.6) and then use a very large hat size (eg 70) to make the hats more visible. This works great, but adjacent hats would overlap. Now, we automatically detect clashing hats and stop them from growing horizontally if they'd overlap ([#1687](https://github.com/cursorless-dev/cursorless/pull/1687)). @pokey is now running with a line height of 1.6 and a hat size of 70, after getting serious hat-size envy during some user screenshares on discord πŸ‘’.![Big hats](big-hats.png) If you want to join the big hat revolution, say `"show settings phrase line height"` to mess with your line height, then `"cursorless settings"` to mess with your hat size +- Improved support for very large hats. A trend among users has been to slightly increase their line height (eg 1.6) and then use a very large hat size (eg 70) to make the hats more visible. This works great, but adjacent hats would overlap. Now, we automatically detect clashing hats and stop them from growing horizontally if they'd overlap ([#1687](https://github.com/cursorless-dev/cursorless/pull/1687)). @pokey is now running with a line height of 1.6 and a hat size of 70, after getting serious hat-size envy during some user screenshares on discord πŸ‘’.![Big hats](big-hats.png) If you want to join the big hat revolution, say `"show settings say line height"` to mess with your line height, then `"cursorless settings"` to mess with your hat size - Lots of improvements to our Go language support (thanks [@josharian](https://github.com/josharian)!) - Expanded our [Talon-side custom API](../customization.md#public-talon-actions) to - allow [snippet wrapping / insertion](../customization.md#snippet-actions) ([#1329](https://github.com/cursorless-dev/cursorless/pull/1329)), used eg for [Cursorless mathfly 🎬](https://youtu.be/v0j2_W97_s0), and @@ -36,13 +36,13 @@ But here are some highlights: ## πŸ› Bug fixes -We fixed [dozens of bugs](https://github.com/cursorless-dev/cursorless/issues?q=is%3Aissue+is%3Aclosed+closed%3A%3E%3D2023-01-01+reason%3Acompleted+sort%3Aupdated-asc+label%3Abug); probably easiest to look at the list. +We fixed [dozens of bugs](https://github.com/cursorless-dev/cursorless/issues?q=is%3Aissue+is%3Aclosed+closed%3A2023-01-01..2023-09-01+reason%3Acompleted+sort%3Aupdated-asc+label%3Abug); probably easiest to look at the list. ## πŸ“œ Documentation - Added a version of the cheatsheet hosted [on our website](https://www.cursorless.org/cheatsheet) so you can show your friends how cool Cursorless is without having to send them a PDF. Note that this version uses the default spoken forms, rather than the ones you've customized. - [@josharian](https://www.youtube.com/@josharian) has made a fantastic series of YouTube videos called [Cursorless by example](https://www.youtube.com/watch?v=2hPwfBCtXws&list=PLbN8ceamGu2c6JrNf83EWyP6K5K77MzVZ). Even Cursorless pros will learn something! -- [Lots of other docs improvements](https://github.com/cursorless-dev/cursorless/pulls?q=is%3Apr+is%3Amerged+merged%3A%3E%3D2023-01-01+sort%3Aupdated-asc+label%3Adocumentation+) +- [Lots of other docs improvements](https://github.com/cursorless-dev/cursorless/pulls?q=is%3Apr+is%3Amerged+merged%3A2023-01-01..2023-09-01+sort%3Aupdated-asc+label%3Adocumentation) (special shout-out to [@jmegner](https://github.com/jmegner)!) ## πŸ€“ Internal improvements diff --git a/docs/user/scope-visualizer.md b/docs/user/scope-visualizer.md index 8535d38840..d4c9561d0b 100644 --- a/docs/user/scope-visualizer.md +++ b/docs/user/scope-visualizer.md @@ -1,6 +1,6 @@ # Scope visualizer -The scope visualizer allows you to visualize Cursorless scopes on your code in real time. It is useful to understand how Cursorless scopes work, and is also useful for Cursorless contributors as they develop new scopes. +The scope visualizer allows you to visualize Cursorless scopes on your code in real time. It is useful to understand how Cursorless scopes work, and is also useful for Cursorless contributors as they develop new scopes. You may find the [scope visualizer intro video](https://youtu.be/BbXEzUrf5lU) helpful. ## Usage diff --git a/docs/user/unicode.md b/docs/user/unicode.md index 16195191a1..a8680e88f4 100644 --- a/docs/user/unicode.md +++ b/docs/user/unicode.md @@ -7,11 +7,11 @@ Cursorless has first-class support for Unicode. By default, when constructing ha - Africa - África -For Unicode symbols that are not letters, and that are not speakable in knausj, for example emoji, Chinese characters, etc, we have a special "character" called `"special"` that can be used. So for example, if there were a blue hat over a 'πŸ˜„' character, you could say `"take blue special"` to select it. As always, the spoken form `"special"` can be [customized](customization.md). +For Unicode symbols that are not letters, and that are not speakable by default, for example emoji, Chinese characters, etc, we have a special "character" called `"special"` that can be used. So for example, if there were a blue hat over a 'πŸ˜„' character, you could say `"take blue special"` to select it. As always, the spoken form `"special"` can be [customized](customization.md). ## Advanced customization -The above setup will allow you to refer to any Unicode token. However, if you have overridden your `` capture to contain characters other than lowercase letters and the default knausj symbols, you can tell Cursorless to be less aggressive with its normalization, so that it can allocate hats more efficiently. +The above setup will allow you to refer to any Unicode token, and is sufficient for most users. However, if you have overridden your `` capture to contain characters other than lowercase letters and the default symbols, you can tell Cursorless to be less aggressive with its normalization, so that it can allocate hats more efficiently. Note that this is not necessary in order to refer to these tokens; it just makes hat allocation slightly more efficient. ### Preserving case diff --git a/packages/cheatsheet-local/package.json b/packages/cheatsheet-local/package.json index 86095ff96a..e2bb90109d 100644 --- a/packages/cheatsheet-local/package.json +++ b/packages/cheatsheet-local/package.json @@ -41,7 +41,7 @@ "css-loader": "6.7.3", "html-webpack-plugin": "5.5.0", "jest": "29.5.0", - "postcss": "8.4.21", + "postcss": "8.4.31", "postcss-loader": "7.0.2", "style-loader": "3.3.1", "tailwindcss": "3.2.7", diff --git a/packages/cheatsheet/src/lib/sampleSpokenFormInfos/defaults.json b/packages/cheatsheet/src/lib/sampleSpokenFormInfos/defaults.json index 1d9c4f853d..1c1e45f74a 100644 --- a/packages/cheatsheet/src/lib/sampleSpokenFormInfos/defaults.json +++ b/packages/cheatsheet/src/lib/sampleSpokenFormInfos/defaults.json @@ -21,6 +21,10 @@ { "spokenForm": "call ", "description": "Call on selection" + }, + { + "spokenForm": "call on ", + "description": "Call on " } ] }, @@ -70,7 +74,7 @@ "variations": [ { "spokenForm": "pour ", - "description": "Edit new line after" + "description": "Edit new line/scope after" } ] }, @@ -80,7 +84,7 @@ "variations": [ { "spokenForm": "drink ", - "description": "Edit new line before" + "description": "Edit new line/scope before" } ] }, @@ -1188,6 +1192,16 @@ } ] }, + { + "id": "command", + "type": "scopeType", + "variations": [ + { + "spokenForm": "command", + "description": "Command" + } + ] + }, { "id": "comment", "type": "scopeType", diff --git a/packages/common/src/cursorlessCommandIds.ts b/packages/common/src/cursorlessCommandIds.ts index cf0edd10aa..f1c69de9f5 100644 --- a/packages/common/src/cursorlessCommandIds.ts +++ b/packages/common/src/cursorlessCommandIds.ts @@ -78,7 +78,7 @@ export const cursorlessCommandDescriptions: Record< "Display the cursorless cheatsheet", ), ["cursorless.internal.updateCheatsheetDefaults"]: new HiddenCommand( - "Update the default values of the cheatsheet payload used on the website and for local development. Be sure to run this on stock knausj and cursorless.", + "Update the default values of the cheatsheet payload used on the website and for local development. Be sure to run this on stock community and cursorless.", ), ["cursorless.takeSnapshot"]: new HiddenCommand( "Take a snapshot of the current editor state", diff --git a/packages/common/src/extensionDependencies.ts b/packages/common/src/extensionDependencies.ts index 08a37aa65c..5139c3b3a7 100644 --- a/packages/common/src/extensionDependencies.ts +++ b/packages/common/src/extensionDependencies.ts @@ -1,8 +1,12 @@ export const extensionDependencies = [ + // Cursorless access to Tree sitter "pokey.parse-tree", + + // Register necessary language-IDs for tests + "scala-lang.scala", // scala + "mrob95.vscode-talonscript", // talon + "jrieken.vscode-tree-sitter-query", // scm + + // Necessary for the `drink cell` and `pour cell` tests "ms-toolsai.jupyter", - "scalameta.metals", - "ms-python.python", - "mrob95.vscode-talonscript", - "jrieken.vscode-tree-sitter-query", ]; diff --git a/packages/common/src/ide/PassthroughIDEBase.ts b/packages/common/src/ide/PassthroughIDEBase.ts index 23dc1e034a..44d57f4128 100644 --- a/packages/common/src/ide/PassthroughIDEBase.ts +++ b/packages/common/src/ide/PassthroughIDEBase.ts @@ -10,7 +10,13 @@ import { TextEditorVisibleRangesChangeEvent, } from "./types/events.types"; import { FlashDescriptor } from "./types/FlashDescriptor"; -import { Disposable, IDE, RunMode, WorkspaceFolder } from "./types/ide.types"; +import { + Disposable, + IDE, + OpenUntitledTextDocumentOptions, + RunMode, + WorkspaceFolder, +} from "./types/ide.types"; import { Messages } from "./types/Messages"; import { QuickPickOptions } from "./types/QuickPickOptions"; import { State } from "./types/State"; @@ -137,6 +143,12 @@ export default class PassthroughIDEBase implements IDE { return this.original.openTextDocument(path); } + public openUntitledTextDocument( + options?: OpenUntitledTextDocumentOptions, + ): Promise { + return this.original.openUntitledTextDocument(options); + } + public showQuickPick( items: readonly string[], options?: QuickPickOptions, diff --git a/packages/common/src/ide/fake/FakeIDE.ts b/packages/common/src/ide/fake/FakeIDE.ts index 7bb1069fbe..4dddee295b 100644 --- a/packages/common/src/ide/fake/FakeIDE.ts +++ b/packages/common/src/ide/fake/FakeIDE.ts @@ -1,21 +1,22 @@ -import type { EditableTextEditor, TextEditor } from "../.."; import { pull } from "lodash"; +import type { EditableTextEditor, TextEditor } from "../.."; import { GeneralizedRange } from "../../types/GeneralizedRange"; import { TextDocument } from "../../types/TextDocument"; import type { TextDocumentChangeEvent } from "../types/Events"; +import { FlashDescriptor } from "../types/FlashDescriptor"; +import { QuickPickOptions } from "../types/QuickPickOptions"; import { Event, TextEditorSelectionChangeEvent, TextEditorVisibleRangesChangeEvent, } from "../types/events.types"; -import { FlashDescriptor } from "../types/FlashDescriptor"; import type { Disposable, IDE, + OpenUntitledTextDocumentOptions, RunMode, WorkspaceFolder, } from "../types/ide.types"; -import { QuickPickOptions } from "../types/QuickPickOptions"; import { FakeCapabilities } from "./FakeCapabilities"; import FakeClipboard from "./FakeClipboard"; import FakeConfiguration from "./FakeConfiguration"; @@ -92,6 +93,12 @@ export default class FakeIDE implements IDE { throw Error("Not implemented"); } + public openUntitledTextDocument( + _options: OpenUntitledTextDocumentOptions, + ): Promise { + throw Error("Not implemented"); + } + public setQuickPickReturnValue(value: string | undefined) { this.quickPickReturnValue = value; } diff --git a/packages/common/src/ide/types/ide.types.ts b/packages/common/src/ide/types/ide.types.ts index c99e1e711d..3cb67325e8 100644 --- a/packages/common/src/ide/types/ide.types.ts +++ b/packages/common/src/ide/types/ide.types.ts @@ -22,6 +22,10 @@ import { State } from "./State"; export type RunMode = "production" | "development" | "test"; export type HighlightId = string; +export interface OpenUntitledTextDocumentOptions { + language?: string; + content?: string; +} export interface IDE { readonly configuration: Configuration; @@ -105,6 +109,17 @@ export interface IDE { */ openTextDocument(path: string): Promise; + /** + * Opens an untitled document. + * + * @see {@link openTextDocument} + * @param options optional language and documents content + * @return An editor + */ + openUntitledTextDocument( + options?: OpenUntitledTextDocumentOptions, + ): Promise; + /** * An event that is emitted when a {@link TextDocument text document} is opened or when the language id * of a text document {@link languages.setTextDocumentLanguage has been changed}. diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 6f087a3b22..f42a47ee7e 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,8 +1,8 @@ export * from "./cursorlessCommandIds"; export * from "./testUtil/extractTargetedMarks"; export { default as FakeIDE } from "./ide/fake/FakeIDE"; -export { Message } from "./ide/spy/SpyMessages"; -export { SpyIDERecordedValues } from "./ide/spy/SpyIDE"; +export type { Message } from "./ide/spy/SpyMessages"; +export type { SpyIDERecordedValues } from "./ide/spy/SpyIDE"; export { default as SpyIDE } from "./ide/spy/SpyIDE"; export { HatStability } from "./ide/types/HatStability"; export * from "./util"; @@ -11,8 +11,9 @@ export { getKey, splitKey } from "./util/splitKey"; export { hrtimeBigintToSeconds } from "./util/timeUtils"; export * from "./util/walkSync"; export * from "./util/walkAsync"; -export { Listener, Notifier } from "./util/Notifier"; -export { TokenHatSplittingMode } from "./ide/types/Configuration"; +export { Notifier } from "./util/Notifier"; +export type { Listener } from "./util/Notifier"; +export type { TokenHatSplittingMode } from "./ide/types/Configuration"; export * from "./ide/types/ide.types"; export * from "./ide/types/Capabilities"; export * from "./ide/types/CommandId"; diff --git a/packages/common/src/testUtil/TestCaseSnapshot.ts b/packages/common/src/testUtil/TestCaseSnapshot.ts index 3c7649bc6b..dc300e1b8c 100644 --- a/packages/common/src/testUtil/TestCaseSnapshot.ts +++ b/packages/common/src/testUtil/TestCaseSnapshot.ts @@ -9,7 +9,7 @@ export type TestCaseSnapshot = { documentContents: string; selections: SelectionPlainObject[]; clipboard?: string; - // TODO Visible ranges are not asserted during testing, see: + // FIXME Visible ranges are not asserted during testing, see: // https://github.com/cursorless-dev/cursorless/issues/160 visibleRanges?: RangePlainObject[]; marks?: SerializedMarks; diff --git a/packages/common/src/testUtil/getFixturePaths.ts b/packages/common/src/testUtil/getFixturePaths.ts index bfe8765da0..034d3ceeff 100644 --- a/packages/common/src/testUtil/getFixturePaths.ts +++ b/packages/common/src/testUtil/getFixturePaths.ts @@ -28,7 +28,7 @@ export function getRecordedTestPaths() { return walkFilesSync(directory) .filter((p) => p.endsWith(".yml") || p.endsWith(".yaml")) .map((p) => ({ - name: path.relative(relativeDir, p.split(".")[0]), + name: path.relative(relativeDir, p.substring(0, p.lastIndexOf("."))), path: p, })); } diff --git a/packages/common/src/types/Position.ts b/packages/common/src/types/Position.ts index 79f4d9063b..e4b423218b 100644 --- a/packages/common/src/types/Position.ts +++ b/packages/common/src/types/Position.ts @@ -138,4 +138,16 @@ export class Position { public toEmptyRange(): Range { return new Range(this, this); } + + /** + * Return a concise string representation of the position. + * @returns concise representation + **/ + public concise(): string { + return `${this.line}:${this.character}`; + } + + public toString(): string { + return this.concise(); + } } diff --git a/packages/common/src/types/Range.ts b/packages/common/src/types/Range.ts index 55ea3b4029..1230bbc048 100644 --- a/packages/common/src/types/Range.ts +++ b/packages/common/src/types/Range.ts @@ -12,32 +12,27 @@ export class Range { readonly end: Position; /** - * Create a new range from two positions. If `start` is not - * before or equal to `end`, the values will be swapped. + * Create a new range from two positions. + * The earlier of `p1` and `p2` will be used as the start position. * - * @param start A position. - * @param end A position. + * @param p1 A position. + * @param p2 A position. */ - constructor(start: Position, end: Position); + constructor(p1: Position, p2: Position); /** - * Create a new range from number coordinates. It is a shorter equivalent of - * using `new Range(new Position(startLine, startCharacter), new Position(endLine, endCharacter))` + * Create a new range from number coordinates. + * It is equivalent to `new Range(new Position(line1, char1), new Position(line2, char2))` * - * @param startLine A zero-based line value. - * @param startCharacter A zero-based character value. - * @param endLine A zero-based line value. - * @param endCharacter A zero-based character value. + * @param line1 A zero-based line value. + * @param char1 A zero-based character value. + * @param line2 A zero-based line value. + * @param char2 A zero-based character value. */ - constructor( - startLine: number, - startCharacter: number, - endLine: number, - endCharacter: number, - ); + constructor(line1: number, char1: number, line2: number, char2: number); constructor(...args: any[]) { - const [start, end]: [Position, Position] = (() => { + const [p1, p2]: [Position, Position] = (() => { // Arguments are two positions if (args.length === 2) { return args as [Position, Position]; @@ -48,12 +43,12 @@ export class Range { })(); // Ranges are always non-reversed - if (start.isBefore(end)) { - this.start = start; - this.end = end; + if (p1.isBefore(p2)) { + this.start = p1; + this.end = p2; } else { - this.start = end; - this.end = start; + this.start = p2; + this.end = p1; } } @@ -98,8 +93,9 @@ export class Range { } /** - * Intersect `range` with this range and returns a new range or `undefined` - * if the ranges have no overlap. + * Intersect `range` with this range and returns a new range. + * If the ranges are adjacent but non-overlapping, the resulting range is empty. + * If the ranges have no overlap and are not adjacent, it returns `undefined`. * * @param other A range. * @return A range of the greater start and smaller end positions. Will @@ -125,12 +121,12 @@ export class Range { } /** - * Derived a new range from this range. + * Derive a new range from this range. + * If the resulting range has end before start, they are swapped. * * @param start A position that should be used as start. The default value is the {@link Range.start current start}. * @param end A position that should be used as end. The default value is the {@link Range.end current end}. * @return A range derived from this range with the given start and end position. - * If start and end are not different `this` range will be returned. */ public with(start?: Position, end?: Position): Range { return new Range(start ?? this.start, end ?? this.end); @@ -146,4 +142,16 @@ export class Range { ? new Selection(this.end, this.start) : new Selection(this.start, this.end); } + + /** + * Return a concise string representation of the range + * @returns concise representation + **/ + public concise(): string { + return `${this.start.concise()}-${this.end.concise()}`; + } + + public toString(): string { + return this.concise(); + } } diff --git a/packages/common/src/types/Selection.ts b/packages/common/src/types/Selection.ts index f54ac06787..a202c47251 100644 --- a/packages/common/src/types/Selection.ts +++ b/packages/common/src/types/Selection.ts @@ -72,4 +72,16 @@ export class Selection extends Range { this.anchor.isEqual(other.anchor) && this.active.isEqual(other.active) ); } + + /** + * Return a concise string representation of the selection + * @returns concise representation + **/ + public concise(): string { + return `${this.anchor.concise()}->${this.active.concise()}`; + } + + public toString(): string { + return this.concise(); + } } diff --git a/packages/common/src/types/command/ActionDescriptor.ts b/packages/common/src/types/command/ActionDescriptor.ts index b465d81e67..a0c43cf73a 100644 --- a/packages/common/src/types/command/ActionDescriptor.ts +++ b/packages/common/src/types/command/ActionDescriptor.ts @@ -46,6 +46,7 @@ const simpleActionNames = [ "toggleLineBreakpoint", "toggleLineComment", "unfoldRegion", + "private.showParseTree", "private.getTargets", ] as const; diff --git a/packages/common/src/util/walkAsync.ts b/packages/common/src/util/walkAsync.ts index 2094141523..a42600fd7f 100644 --- a/packages/common/src/util/walkAsync.ts +++ b/packages/common/src/util/walkAsync.ts @@ -6,13 +6,16 @@ import { flatten } from "lodash"; * Note: Returns full paths * Based on https://gist.github.com/kethinov/6658166#gistcomment-1941504 * @param dir - * @param filelist + * @param fileEnding If defined, only return files ending with this string. Eg `.txt` * @returns */ -export const walkFiles = async (dir: string): Promise => { +export const walkFiles = async ( + dir: string, + fileEnding?: string, +): Promise => { const dirEntries = await readdir(dir, { withFileTypes: true }); - return flatten( + const files = flatten( await Promise.all( dirEntries.map(async (dirent) => { const filePath = path.join(dir, dirent.name); @@ -20,4 +23,10 @@ export const walkFiles = async (dir: string): Promise => { }), ), ); + + if (fileEnding != null) { + return files.filter((file) => file.endsWith(fileEnding)); + } + + return files; }; diff --git a/packages/cursorless-engine/package.json b/packages/cursorless-engine/package.json index 717d13ad20..34054c00af 100644 --- a/packages/cursorless-engine/package.json +++ b/packages/cursorless-engine/package.json @@ -19,7 +19,7 @@ "lodash": "^4.17.21", "node-html-parser": "^5.3.3", "sbd": "^1.0.19", - "zod": "3.21.4" + "zod": "3.22.3" }, "devDependencies": { "@types/js-yaml": "^4.0.2", @@ -27,6 +27,7 @@ "@types/mocha": "^8.0.4", "@types/sbd": "^1.0.3", "@types/sinon": "^10.0.2", + "fast-check": "3.12.0", "js-yaml": "^4.1.0", "mocha": "^10.2.0", "sinon": "^11.1.1" diff --git a/packages/cursorless-engine/src/actions/Actions.ts b/packages/cursorless-engine/src/actions/Actions.ts index 5b70327b0e..c338a82d6e 100644 --- a/packages/cursorless-engine/src/actions/Actions.ts +++ b/packages/cursorless-engine/src/actions/Actions.ts @@ -1,3 +1,4 @@ +import { TreeSitter } from ".."; import { Snippets } from "../core/Snippets"; import { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { ModifierStageFactory } from "../processTargets/ModifierStageFactory"; @@ -26,6 +27,7 @@ import { } from "./InsertEmptyLines"; import InsertSnippet from "./InsertSnippet"; import { PasteFromClipboard } from "./PasteFromClipboard"; +import ShowParseTree from "./ShowParseTree"; import Remove from "./Remove"; import Replace from "./Replace"; import Rewrap from "./Rewrap"; @@ -63,6 +65,7 @@ import { ActionRecord } from "./actions.types"; */ export class Actions implements ActionRecord { constructor( + private treeSitter: TreeSitter, private snippets: Snippets, private rangeUpdater: RangeUpdater, private modifierStageFactory: ModifierStageFactory, @@ -145,5 +148,6 @@ export class Actions implements ActionRecord { this.snippets, this.modifierStageFactory, ); + ["private.showParseTree"] = new ShowParseTree(this.treeSitter); ["private.getTargets"] = new GetTargets(); } diff --git a/packages/cursorless-engine/src/actions/GenerateSnippet/GenerateSnippet.ts b/packages/cursorless-engine/src/actions/GenerateSnippet/GenerateSnippet.ts index b9e78aa141..e41a35de4b 100644 --- a/packages/cursorless-engine/src/actions/GenerateSnippet/GenerateSnippet.ts +++ b/packages/cursorless-engine/src/actions/GenerateSnippet/GenerateSnippet.ts @@ -175,7 +175,7 @@ export default class GenerateSnippet { // as wrapperScopeType. Ie the output will look like `{|}` (with the `|` // representing a tabstop in the meta-snippet) // - // NB: We use the subsituter here, with `isQuoted=true` because in order + // NB: We use the substituter here, with `isQuoted=true` because in order // to make this work for the meta-snippet, we want to end up with // something like `{$3}`, which is not valid json. So we instead arrange // to end up with json like `"hgidfsivhs"`, and then replace the whole diff --git a/packages/cursorless-engine/src/actions/Replace.ts b/packages/cursorless-engine/src/actions/Replace.ts index a4b7a9b192..9b0fe5b6b1 100644 --- a/packages/cursorless-engine/src/actions/Replace.ts +++ b/packages/cursorless-engine/src/actions/Replace.ts @@ -1,9 +1,14 @@ -import { FlashStyle, ReplaceWith } from "@cursorless/common"; -import { flatten, zip } from "lodash"; +import { + FlashStyle, + RangeExpansionBehavior, + ReplaceWith, +} from "@cursorless/common"; +import { zip } from "lodash"; import { RangeUpdater } from "../core/updateSelections/RangeUpdater"; -import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; +import { performEditsAndUpdateSelectionsWithBehavior } from "../core/updateSelections/updateSelections"; import { ide } from "../singletons/ide.singleton"; -import { Destination } from "../typings/target.types"; +import { SelectionWithEditor } from "../typings/Types"; +import { Destination, Target } from "../typings/target.types"; import { flashTargets, runForEachEditor } from "../util/targetUtils"; import { ActionReturnValue } from "./actions.types"; @@ -47,30 +52,49 @@ export default class Replace { } const edits = zip(destinations, texts).map(([destination, text]) => ({ - edit: destination!.constructChangeEdit(text!), editor: destination!.editor, + target: destination!.target, + edit: destination!.constructChangeEdit(text!), })); - const thatMark = flatten( - await runForEachEditor( - edits, - (edit) => edit.editor, - async (editor, edits) => { - const [updatedSelections] = await performEditsAndUpdateSelections( + const sourceTargets: Target[] = []; + const thatSelections: SelectionWithEditor[] = []; + + await runForEachEditor( + edits, + (edit) => edit.editor, + async (editor, edits) => { + const contentSelections = { + selections: edits.map(({ target }) => target.contentSelection), + }; + const editSelections = { + selections: edits.map(({ edit }) => edit.range.toSelection(false)), + rangeBehavior: RangeExpansionBehavior.openOpen, + }; + + const [updatedContentSelections, updatedEditSelections] = + await performEditsAndUpdateSelectionsWithBehavior( this.rangeUpdater, ide().getEditableTextEditor(editor), edits.map(({ edit }) => edit), - [destinations.map((destination) => destination.contentSelection)], + [contentSelections, editSelections], ); - return updatedSelections.map((selection) => ({ + for (const [edit, selection] of zip(edits, updatedContentSelections)) { + sourceTargets.push(edit!.target.withContentRange(selection!)); + } + + for (const [edit, selection] of zip(edits, updatedEditSelections)) { + thatSelections.push({ editor, - selection, - })); - }, - ), + selection: edit!.edit + .updateRange(selection!) + .toSelection(selection!.isReversed), + }); + } + }, ); - return { thatSelections: thatMark }; + return { sourceTargets, thatSelections }; } } diff --git a/packages/cursorless-engine/src/actions/ShowParseTree.ts b/packages/cursorless-engine/src/actions/ShowParseTree.ts new file mode 100644 index 0000000000..0c7db86232 --- /dev/null +++ b/packages/cursorless-engine/src/actions/ShowParseTree.ts @@ -0,0 +1,121 @@ +import { FlashStyle, Range, TextDocument } from "@cursorless/common"; +import * as path from "node:path"; +import type { Tree, TreeCursor } from "web-tree-sitter"; +import type { TreeSitter } from ".."; +import { ide } from "../singletons/ide.singleton"; +import type { Target } from "../typings/target.types"; +import { flashTargets } from "../util/targetUtils"; +import type { ActionReturnValue } from "./actions.types"; + +export default class ShowParseTree { + constructor(private treeSitter: TreeSitter) { + this.run = this.run.bind(this); + } + + async run(targets: Target[]): Promise { + await flashTargets(ide(), targets, FlashStyle.referenced); + + const results: string[] = ["# Cursorless parse tree"]; + + for (const target of targets) { + const { editor, contentRange } = target; + const tree = this.treeSitter.getTree(editor.document); + results.push(parseTree(editor.document, tree, contentRange)); + } + + ide().openUntitledTextDocument({ + language: "markdown", + content: results.join("\n\n"), + }); + + return { thatTargets: targets }; + } +} + +function parseTree( + document: TextDocument, + tree: Tree, + contentRange: Range, +): string { + const resultPlayground: string[] = []; + const resultQuery: string[] = []; + + parseCursor(resultPlayground, resultQuery, contentRange, tree.walk(), 0); + + return [ + `## ${path.basename(document.uri.path)} [${contentRange}]\n`, + `\`\`\`${document.languageId}`, + document.getText(contentRange), + "```", + "", + "```scm", + ...resultQuery, + "```", + "", + "```js", + ...resultPlayground, + "```", + "", + ].join("\n"); +} + +function parseCursor( + resultPlayground: string[], + resultQuery: string[], + contentRange: Range, + cursor: TreeCursor, + numIndents: number, +): void { + while (true) { + const nodeRange = new Range( + cursor.startPosition.row, + cursor.startPosition.column, + cursor.endPosition.row, + cursor.endPosition.column, + ); + + if (contentRange.intersection(nodeRange) != null) { + const indentation = " ".repeat(numIndents); + const fieldName = getFieldName(cursor); + const prefix = indentation + fieldName; + + // Named node + if (cursor.nodeIsNamed) { + resultPlayground.push(`${prefix}${cursor.nodeType} [${nodeRange}]`); + resultQuery.push(`${prefix}(${cursor.nodeType}`); + + // Named node with children + if (cursor.gotoFirstChild()) { + parseCursor( + resultPlayground, + resultQuery, + contentRange, + cursor, + numIndents + 1, + ); + cursor.gotoParent(); + resultQuery.push(`${indentation})`); + } + // Named node without children + else { + resultQuery[resultQuery.length - 1] += ")"; + } + } + // Anonymous node + else { + const type = `"${cursor.nodeType}"`; + resultPlayground.push(`${prefix}${type} [${nodeRange}]`); + resultQuery.push(`${prefix}${type}`); + } + } + + if (!cursor.gotoNextSibling()) { + return; + } + } +} + +function getFieldName(cursor: TreeCursor): string { + const field = cursor.currentFieldName(); + return field != null ? `${field}: ` : ""; +} diff --git a/packages/cursorless-engine/src/actions/Sort.ts b/packages/cursorless-engine/src/actions/Sort.ts index b5c1423a00..e7c823a59e 100644 --- a/packages/cursorless-engine/src/actions/Sort.ts +++ b/packages/cursorless-engine/src/actions/Sort.ts @@ -35,10 +35,12 @@ abstract class SortBase implements SimpleAction { const sortedTexts = this.sortTexts(unsortedTexts); - return this.actions.replace.run( + const { thatSelections } = await this.actions.replace.run( sortedTargets.map((target) => target.toDestination("to")), sortedTexts, ); + + return { thatSelections }; } } diff --git a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts index 559375d93b..2df45a8859 100644 --- a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts +++ b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts @@ -23,7 +23,7 @@ const PRE_PHRASE_SNAPSHOT_MAX_AGE_NS = BigInt(6e10); // 60 seconds */ export class HatTokenMapImpl implements HatTokenMap { /** - * This is the active map the changes every time we reallocate hats. It is + * This is the active map that changes every time we reallocate hats. It is * liable to change in the middle of a phrase. */ private activeMap: IndividualHatMap; diff --git a/packages/cursorless-engine/src/core/Snippets.ts b/packages/cursorless-engine/src/core/Snippets.ts index d93e61e83f..373bdccf2f 100644 --- a/packages/cursorless-engine/src/core/Snippets.ts +++ b/packages/cursorless-engine/src/core/Snippets.ts @@ -236,8 +236,6 @@ export class Snippets { } } -async function getSnippetPaths(snippetsDir: string) { - return (await walkFiles(snippetsDir)).filter((path) => - path.endsWith(CURSORLESS_SNIPPETS_SUFFIX), - ); +function getSnippetPaths(snippetsDir: string) { + return walkFiles(snippetsDir, CURSORLESS_SNIPPETS_SUFFIX); } diff --git a/packages/cursorless-engine/src/core/compareSnippetDefinitions.ts b/packages/cursorless-engine/src/core/compareSnippetDefinitions.ts index 794282db2f..cff4816ece 100644 --- a/packages/cursorless-engine/src/core/compareSnippetDefinitions.ts +++ b/packages/cursorless-engine/src/core/compareSnippetDefinitions.ts @@ -21,7 +21,7 @@ export function compareSnippetDefinitions( b.definition.scope, ); - // Prefer the more specific snippet definitino, no matter the origin + // Prefer the more specific snippet definition, no matter the origin if (scopeComparision !== 0) { return scopeComparision; } diff --git a/packages/cursorless-engine/src/core/updateSelections/updateSelections.ts b/packages/cursorless-engine/src/core/updateSelections/updateSelections.ts index fe7e1bd9b8..c2dded43e2 100644 --- a/packages/cursorless-engine/src/core/updateSelections/updateSelections.ts +++ b/packages/cursorless-engine/src/core/updateSelections/updateSelections.ts @@ -330,7 +330,7 @@ async function performEditsAndUpdateInternal( return selectionInfosToSelections(selectionInfoMatrix); } -// TODO: Remove this function if we don't end up using it for the next couple use cases, eg `that` mark and cursor history +// FIXME: Remove this function if we don't end up using it for the next couple use cases, eg `that` mark and cursor history export async function performEditsAndUpdateSelectionInfos( rangeUpdater: RangeUpdater, editor: EditableTextEditor, @@ -363,7 +363,7 @@ export async function performEditsAndUpdateFullSelectionInfos( originalSelectionInfos: FullSelectionInfo[][], ): Promise { // NB: We do everything using VSCode listeners. We can associate changes - // with our changes just by looking at their offets / text in order to + // with our changes just by looking at their offsets / text in order to // recover isReplace. We need to do this because VSCode does some fancy // stuff, and returns the changes in a nice order // Note that some additional weird edits like whitespace things can be diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index ab0c96b495..b45fbc993e 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -59,6 +59,7 @@ export function createCursorlessEngine( commandApi: { runCommand(command: Command) { return runCommand( + treeSitter, debug, hatTokenMap, testCaseRecorder, @@ -72,6 +73,7 @@ export function createCursorlessEngine( runCommandSafe(...args: unknown[]) { return runCommand( + treeSitter, debug, hatTokenMap, testCaseRecorder, diff --git a/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/actions.ts b/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/actions.ts index e1009625d0..c15e47d38f 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/actions.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/actions.ts @@ -54,6 +54,7 @@ export const actions = { insertSnippet: "snippet", pasteFromClipboard: "paste", + ["private.showParseTree"]: "parse tree", ["experimental.setInstanceReference"]: "from", editNew: null, diff --git a/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts index 532485981e..34f5dda028 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/generateSpokenForm.ts @@ -76,13 +76,14 @@ function generateSpokenFormComponents( ]; case "callAsFunction": - if (action.argument != null) { - throw new NoSpokenFormError(`Action '${action.name}' with argument`); + if (action.argument.type === "implicit") { + return [actions[action.name], targetToSpokenForm(action.callee)]; } return [ actions[action.name], targetToSpokenForm(action.callee), - // targetToSpokenForm(action.argument), + "on", + targetToSpokenForm(action.argument), ]; case "wrapWithPairedDelimiter": diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/PredicateOperatorSchemaTypes.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/PredicateOperatorSchemaTypes.ts index 5d76b6bea7..9805367121 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/PredicateOperatorSchemaTypes.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/PredicateOperatorSchemaTypes.ts @@ -1,4 +1,4 @@ -import z from "zod"; +import { z } from "zod"; import { SchemaInputType, SchemaOutputType, diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/QueryPredicateOperator.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/QueryPredicateOperator.ts index c7601ad259..aa2436a170 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/QueryPredicateOperator.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/QueryPredicateOperator.ts @@ -1,5 +1,5 @@ import { PredicateOperand } from "web-tree-sitter"; -import z from "zod"; +import { z } from "zod"; import { AcceptFunctionArgs, HasSchema, @@ -47,6 +47,21 @@ export abstract class QueryPredicateOperator { ...args: AcceptFunctionArgs>> ): boolean; + /** + * Whether it is ok for a node argument to be missing. If true, then the + * operator will just accept the pattern if the given node is missing. If + * false, then the operator will throw an error if the node is missing. + * + * This is useful if we want to set some flag on a node, but only if it's + * present. + * + * @returns A boolean indicating whether it is ok for a node argument to be + * missing. + */ + protected allowMissingNode(): boolean { + return false; + } + /** * Given a list of operands, return a predicate function that can be used to * test whether a given match satisfies the predicate. @@ -62,8 +77,21 @@ export abstract class QueryPredicateOperator { return result.success ? { success: true, - predicate: (match: MutableQueryMatch) => - this.run(...this.constructAcceptArgs(result.data, match)), + predicate: (match: MutableQueryMatch) => { + try { + const acceptArgs = this.constructAcceptArgs(result.data, match); + return this.run(...acceptArgs); + } catch (err) { + if ( + err instanceof CaptureNotFoundError && + this.allowMissingNode() + ) { + return true; + } + + throw err; + } + }, } : { success: false, @@ -89,13 +117,7 @@ export abstract class QueryPredicateOperator { ); if (capture == null) { - // FIXME: We could allow some predicates to be forgiving, - // because it's possible to have a capture on an optional nodeInfo. - // In that case we'd prob just return `true` if any capture was - // `null`, but we should check that the given capture name - // appears statically in the given pattern. But we don't yet - // have a use case so let's leave it for now. - throw new Error(`Could not find capture ${operand.name}`); + throw new CaptureNotFoundError(operand.name); } return capture; @@ -117,3 +139,9 @@ interface FailedPredicateResult { } type PredicateResult = SuccessfulPredicateResult | FailedPredicateResult; + +class CaptureNotFoundError extends Error { + constructor(operandName: string) { + super(`Could not find capture ${operandName}`); + } +} diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.ts index e9b13d22b2..aa80c6bb66 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.ts @@ -60,7 +60,9 @@ export function checkCaptureStartEnd( showError( messages, "TreeSitterQuery.checkCaptures.mixRegularStartEnd", - `Please do not mix regular captures and start/end captures: ${captures}`, + `Please do not mix regular captures and start/end captures: ${captures.map( + ({ name, range }) => name + " " + range.toString(), + )}`, ); shownError = true; } @@ -71,7 +73,7 @@ export function checkCaptureStartEnd( messages, "TreeSitterQuery.checkCaptures.duplicate", `A capture with the same name may only appear once in a single pattern: ${captures.map( - ({ name }) => name, + ({ name, range }) => name + " " + range.toString(), )}`, ); shownError = true; diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/constructZodErrorMessages.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/constructZodErrorMessages.ts index 41768b74ce..f2889b3d13 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/constructZodErrorMessages.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/constructZodErrorMessages.ts @@ -1,5 +1,5 @@ import { PredicateOperand } from "web-tree-sitter"; -import z from "zod"; +import { z } from "zod"; import { operandToString } from "./predicateToString"; export function constructZodErrorMessages( diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/operatorArgumentSchemaTypes.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/operatorArgumentSchemaTypes.ts index db6a6eadd4..bd068d453e 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/operatorArgumentSchemaTypes.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/operatorArgumentSchemaTypes.ts @@ -1,4 +1,4 @@ -import z from "zod"; +import { z } from "zod"; import { assertTypesEqual } from "./assertTypesEqual"; import { PredicateOperand } from "web-tree-sitter"; diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts index e1647f5f1e..2218c937df 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts @@ -1,5 +1,5 @@ import { Range } from "@cursorless/common"; -import z from "zod"; +import { z } from "zod"; import { makeRangeFromPositions } from "../../util/nodeSelectors"; import { MutableQueryCapture } from "./QueryCapture"; import { QueryPredicateOperator } from "./QueryPredicateOperator"; @@ -150,10 +150,25 @@ class ShrinkToMatch extends QueryPredicateOperator { } } +/** + * Indicates that it is ok for multiple captures to have the same domain but + * different targets. For example, if we have the query `(#allow-multiple! + * @foo)`, then if we define the query so that `@foo` appears multiple times + * with the same domain but different targets, then the given domain will end up + * with multiple targets. The canonical example is `tags` in HTML / jsx. + * + * This operator is allowed to be applied to a capture that doesn't actually + * appear; ie we can make it so that we allow multiple if the capture appears in + * the pattern. + */ class AllowMultiple extends QueryPredicateOperator { name = "allow-multiple!" as const; schema = z.tuple([q.node]); + protected allowMissingNode(): boolean { + return true; + } + run(nodeInfo: MutableQueryCapture) { nodeInfo.allowMultiple = true; @@ -161,6 +176,19 @@ class AllowMultiple extends QueryPredicateOperator { } } +/** + * A predicate operator that logs a node, for debugging. + */ +class Log extends QueryPredicateOperator { + name = "log!" as const; + schema = z.tuple([q.node]); + + run(nodeInfo: MutableQueryCapture) { + console.log(`#log!: ${nodeInfo.name}@${nodeInfo.range}`); + return true; + } +} + /** * A predicate operator that sets the insertion delimiter of the match. For * example, `(#insertion-delimiter! @foo ", ")` will set the insertion delimiter @@ -178,6 +206,7 @@ class InsertionDelimiter extends QueryPredicateOperator { } export const queryPredicateOperators = [ + new Log(), new NotType(), new NotEmpty(), new NotParentType(), diff --git a/packages/cursorless-engine/src/languages/branchMatcher.ts b/packages/cursorless-engine/src/languages/branchMatcher.ts index 7293bdd577..63b93d874f 100644 --- a/packages/cursorless-engine/src/languages/branchMatcher.ts +++ b/packages/cursorless-engine/src/languages/branchMatcher.ts @@ -15,7 +15,7 @@ import { childRangeSelector } from "../util/nodeSelectors"; * "if_statement" or "try_statement" * @param optionalBranchTypes The optional branch type names that can be * children of the top-level statement, eg "else_clause" or "except_clause" - * @returns A node matcher capabale of matching this type of branch + * @returns A node matcher capable of matching this type of branch */ export function branchMatcher( statementType: string, diff --git a/packages/cursorless-engine/src/languages/clojure.ts b/packages/cursorless-engine/src/languages/clojure.ts index bd00fb1d3b..9f1cb7338b 100644 --- a/packages/cursorless-engine/src/languages/clojure.ts +++ b/packages/cursorless-engine/src/languages/clojure.ts @@ -58,7 +58,7 @@ function indexNodeFinder( const nodeIndex = valueNodes.findIndex(({ id }) => id === node.id); if (nodeIndex === -1) { - // TODO: In the future we might conceivably try to handle saying "take + // FIXME: In the future we might conceivably try to handle saying "take // item" when the selection is inside a comment between the key and value return null; } @@ -154,7 +154,7 @@ const nodeMatchers: Partial< ), value: matcher(mapParityNodeFinder(1)), - // TODO: Handle formal parameters + // FIXME: Handle formal parameters argumentOrParameter: matcher( indexNodeFinder(patternFinder(functionCallPattern), (nodeIndex: number) => nodeIndex !== 0 ? nodeIndex : -1, @@ -176,7 +176,7 @@ const nodeMatchers: Partial< functionName: functionNameMatcher, - // TODO: Handle `let` declarations, defs, etc + // FIXME: Handle `let` declarations, defs, etc name: functionNameMatcher, anonymousFunction: cascadingMatcher( diff --git a/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts b/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts index 3ec3d17be7..07d9035a1b 100644 --- a/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts +++ b/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts @@ -1,4 +1,4 @@ -import { Range, UnsupportedLanguageError } from "@cursorless/common"; +import { Range } from "@cursorless/common"; import type { SyntaxNode } from "web-tree-sitter"; import { SelectionWithEditor } from "../typings/Types"; import { notSupported } from "../util/nodeMatchers"; @@ -104,14 +104,8 @@ function constructHackedStringTextFragmentExtractor( */ export default function getTextFragmentExtractor( languageId: string, -): TextFragmentExtractor { - const extractor = textFragmentExtractors[languageId as LegacyLanguageId]; - - if (extractor == null) { - throw new UnsupportedLanguageError(languageId); - } - - return extractor; +): TextFragmentExtractor | null { + return textFragmentExtractors[languageId as LegacyLanguageId]; } // NB: For now when we want use the entire file as a text fragment we just @@ -165,7 +159,6 @@ const textFragmentExtractors: Record< "php", phpStringTextFragmentExtractor, ), - python: constructDefaultTextFragmentExtractor("python"), ruby: constructDefaultTextFragmentExtractor( "ruby", rubyStringTextFragmentExtractor, diff --git a/packages/cursorless-engine/src/languages/java.ts b/packages/cursorless-engine/src/languages/java.ts index 240dd0d3ce..1be9e7909e 100644 --- a/packages/cursorless-engine/src/languages/java.ts +++ b/packages/cursorless-engine/src/languages/java.ts @@ -93,6 +93,7 @@ const nodeMatchers: Partial< type: trailingMatcher([ "generic_type.type_arguments.type_identifier", "generic_type.type_identifier", + "generic_type.scoped_type_identifier.type_identifier", "type_identifier", "local_variable_declaration[type]", "array_creation_expression[type]", diff --git a/packages/cursorless-engine/src/languages/python.ts b/packages/cursorless-engine/src/languages/python.ts index b29611b705..81346da0f0 100644 --- a/packages/cursorless-engine/src/languages/python.ts +++ b/packages/cursorless-engine/src/languages/python.ts @@ -23,9 +23,6 @@ import { ternaryBranchMatcher } from "./ternaryBranchMatcher"; export const getTypeNode = (node: SyntaxNode) => node.children.find((child) => child.type === "type") ?? null; -const dictionaryTypes = ["dictionary", "dictionary_comprehension"]; -const listTypes = ["list", "list_comprehension", "set"]; - function itemNodeFinder( parentType: string, childType: string, @@ -48,9 +45,6 @@ function itemNodeFinder( const nodeMatchers: Partial< Record > = { - map: dictionaryTypes, - list: listTypes, - string: "string", collectionItem: cascadingMatcher( matcher( itemNodeFinder("import_from_statement", "dotted_name", true), @@ -66,7 +60,6 @@ const nodeMatchers: Partial< anonymousFunction: "lambda?.lambda", functionCall: "call", functionCallee: "call[function]", - comment: "comment", condition: cascadingMatcher( conditionMatcher("*[condition]"), @@ -76,39 +69,6 @@ const nodeMatchers: Partial< // Ternaries patternMatcher("conditional_expression[1]"), ), - type: leadingMatcher( - ["function_definition[return_type]", "*[type]"], - [":", "->"], - ), - name: [ - "assignment[left]", - "augmented_assignment[left]", - "typed_parameter.identifier!", - "parameters.identifier!", - "*[name]", - ], - value: cascadingMatcher( - leadingMatcher( - ["assignment[right]", "augmented_assignment[right]", "~subscript[value]"], - [ - ":", - "=", - "+=", - "-=", - "*=", - "/=", - "%=", - "//=", - "**=", - "&=", - "|=", - "^=", - "<<=", - ">>=", - ], - ), - patternMatcher("return_statement.~return!"), - ), argumentOrParameter: cascadingMatcher( argumentMatcher("parameters", "argument_list"), matcher(patternFinder("call.generator_expression!"), childRangeSelector()), diff --git a/packages/cursorless-engine/src/languages/typescript.ts b/packages/cursorless-engine/src/languages/typescript.ts index 377f81d3ea..300cf8f393 100644 --- a/packages/cursorless-engine/src/languages/typescript.ts +++ b/packages/cursorless-engine/src/languages/typescript.ts @@ -1,10 +1,6 @@ import { SimpleScopeTypeType } from "@cursorless/common"; import type { SyntaxNode } from "web-tree-sitter"; -import { - NodeMatcher, - NodeMatcherAlternative, - SelectionWithEditor, -} from "../typings/Types"; +import { NodeMatcherAlternative, SelectionWithEditor } from "../typings/Types"; import { patternFinder } from "../util/nodeFinders"; import { argumentMatcher, @@ -20,9 +16,6 @@ import { extendForwardPastOptional, getNodeInternalRange, getNodeRange, - pairSelectionExtractor, - selectWithLeadingDelimiter, - simpleSelectionExtractor, unwrapSelectionExtractor, } from "../util/nodeSelectors"; import { branchMatcher } from "./branchMatcher"; @@ -69,89 +62,6 @@ const STATEMENT_TYPES = [ "with_statement", ]; -function typeMatcher(): NodeMatcher { - const delimiterSelector = selectWithLeadingDelimiter(":"); - return function (selection: SelectionWithEditor, node: SyntaxNode) { - if ( - node.parent?.type === "new_expression" && - node.type !== "new" && - node.type !== "arguments" - ) { - const identifierNode = node.parent.children.find( - (n) => n.type === "identifier", - ); - const argsNode = node.parent.children.find( - (n) => n.type === "type_arguments", - ); - if (identifierNode && argsNode) { - return [ - { - node, - selection: pairSelectionExtractor( - selection.editor, - identifierNode, - argsNode, - ), - }, - ]; - } else if (identifierNode) { - return [ - { - node: identifierNode, - selection: simpleSelectionExtractor( - selection.editor, - identifierNode, - ), - }, - ]; - } - } - - const typeAnnotationNode = node.children.find((child) => - ["type_annotation", "opting_type_annotation"].includes(child.type), - ); - const targetNode = typeAnnotationNode?.lastChild; - - if (targetNode) { - return [ - { - node: targetNode, - selection: delimiterSelector(selection.editor, targetNode), - }, - ]; - } - return null; - }; -} - -function valueMatcher() { - const pFinder = patternFinder( - "assignment_expression[right]", - "augmented_assignment_expression[right]", - "*[value]", - "shorthand_property_identifier", - ); - return matcher( - (node: SyntaxNode) => - node.type === "jsx_attribute" ? node.lastChild : pFinder(node), - selectWithLeadingDelimiter( - ":", - "=", - "+=", - "-=", - "*=", - "/=", - "%=", - "**=", - "&=", - "|=", - "^=", - "<<=", - ">>=", - ), - ); -} - const mapTypes = ["object", "object_pattern"]; const listTypes = ["array", "array_pattern"]; @@ -166,15 +76,11 @@ const nodeMatchers: Partial< [ "pair[key]", "jsx_attribute.property_identifier!", + "object_type.property_signature[name]!", "shorthand_property_identifier", ], [":"], ), - value: cascadingMatcher( - valueMatcher(), - patternMatcher("return_statement.~return!"), - patternMatcher("yield_expression.~yield!"), - ), ifStatement: "if_statement", comment: "comment", regularExpression: "regex", @@ -226,16 +132,6 @@ const nodeMatchers: Partial< "export_statement?.abstract_class_declaration", // export abstract class | abstract class "export_statement.class", // export default class ], - type: cascadingMatcher( - // Typed parameters, properties, and functions - typeMatcher(), - // matcher(findTypeNode, selectWithLeadingDelimiter(":")), - // Type alias/interface declarations - patternMatcher( - "export_statement?.type_alias_declaration", - "export_statement?.interface_declaration", - ), - ), argumentOrParameter: argumentMatcher("formal_parameters", "arguments"), // XML, JSX attribute: ["jsx_attribute"], diff --git a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts index c5f147d8b0..407d6b93a0 100644 --- a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts +++ b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts @@ -5,7 +5,7 @@ import { Range, ScopeType, } from "@cursorless/common"; -import { uniqWith, zip } from "lodash"; +import { zip } from "lodash"; import { PrimitiveTargetDescriptor, RangeTargetDescriptor, @@ -18,6 +18,7 @@ import { MarkStage, ModifierStage } from "./PipelineStages.types"; import ImplicitStage from "./marks/ImplicitStage"; import { ContainingTokenIfUntypedEmptyStage } from "./modifiers/ConditionalModifierStages"; import { PlainTarget } from "./targets"; +import { uniqWithHash } from "../util/uniqWithHash"; export class TargetPipelineRunner { constructor( @@ -287,7 +288,11 @@ function calcIsReversed(anchor: Target, active: Target) { } function uniqTargets(array: Target[]): Target[] { - return uniqWith(array, (a, b) => a.isEqual(b)); + return uniqWithHash( + array, + (a, b) => a.isEqual(b), + (a) => a.contentRange.concise(), + ); } function ensureSingleEditor(anchorTarget: Target, activeTarget: Target) { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts index cb4c77d9f8..74785bb372 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts @@ -65,7 +65,7 @@ export class EveryScopeStage implements ModifierStage { ) { // If the only scope that came back completely contains the input target // range, we treat the input as if it had no explicit range, expanding - // to default iteration socpe below + // to default iteration scope below scopes = undefined; } } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/BaseTreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/BaseTreeSitterScopeHandler.ts index 3011807a4e..d59a06d501 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/BaseTreeSitterScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/BaseTreeSitterScopeHandler.ts @@ -1,20 +1,11 @@ -import { - Direction, - Position, - TextDocument, - TextEditor, - showError, -} from "@cursorless/common"; +import { Direction, Position, TextEditor, showError } from "@cursorless/common"; import { uniqWith } from "lodash"; import { TreeSitterQuery } from "../../../../languages/TreeSitterQuery"; import { QueryMatch } from "../../../../languages/TreeSitterQuery/QueryCapture"; import BaseScopeHandler from "../BaseScopeHandler"; import { compareTargetScopes } from "../compareTargetScopes"; import { TargetScope } from "../scope.types"; -import { - ContainmentPolicy, - ScopeIteratorRequirements, -} from "../scopeHandler.types"; +import { ScopeIteratorRequirements } from "../scopeHandler.types"; import { mergeAdjacentBy } from "./mergeAdjacentBy"; import { ide } from "../../../../singletons/ide.singleton"; @@ -105,87 +96,3 @@ export abstract class BaseTreeSitterScopeHandler extends BaseScopeHandler { export interface ExtendedTargetScope extends TargetScope { allowMultiple: boolean; } - -/** - * Constructs a range to pass to {@link Query.matches} to find scopes. Note - * that {@link Query.matches} will only return scopes that have non-empty - * intersection with this range. Also note that the base - * {@link BaseScopeHandler.generateScopes} will filter out any extra scopes - * that we yield, so we don't need to be totally precise. - * - * @returns Range to pass to {@link Query.matches} - */ -function getQuerySearchRange( - document: TextDocument, - position: Position, - direction: Direction, - { - containment, - distalPosition, - allowAdjacentScopes, - }: ScopeIteratorRequirements, -) { - const { start, end } = getQuerySearchRangeCore( - document.offsetAt(position), - document.offsetAt(distalPosition), - direction, - containment, - allowAdjacentScopes, - ); - - return { - start: document.positionAt(start), - end: document.positionAt(end), - }; -} - -/** Helper function for {@link getQuerySearchRange} */ -function getQuerySearchRangeCore( - offset: number, - distalOffset: number, - direction: Direction, - containment: ContainmentPolicy | null, - allowAdjacentScopes: boolean, -) { - /** - * If we allow adjacent scopes, we need to shift some of our offsets by one - * character - */ - const adjacentShift = allowAdjacentScopes ? 1 : 0; - - if (containment === "required") { - // If containment is required, we smear the position left and right by one - // character so that we have a non-empty intersection with any scope that - // touches position. Note that we can skip shifting the initial position - // if we disallow adjacent scopes. - return direction === "forward" - ? { - start: offset - adjacentShift, - end: offset + 1, - } - : { - start: offset - 1, - end: offset + adjacentShift, - }; - } - - // If containment is disallowed, we can shift the position forward by a - // character to avoid matching scopes that touch position. Otherwise, we - // shift the position backward by a character to ensure we get scopes that - // touch position, if we allow adjacent scopes. - const proximalShift = containment === "disallowed" ? 1 : -adjacentShift; - - // FIXME: Don't go all the way to end of document when there is no - // distalPosition? Seems wasteful to query all the way to end of document for - // something like "next funk" Might be better to start smaller and - // exponentially grow - return direction === "forward" - ? { - start: offset + proximalShift, - end: distalOffset + adjacentShift, - } - : { - start: distalOffset - adjacentShift, - end: offset - proximalShift, - }; -} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts b/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts index 9b9337fbb3..52898d6e88 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts @@ -5,9 +5,7 @@ import { } from "@cursorless/common"; import type { SyntaxNode } from "web-tree-sitter"; import { LanguageDefinitions } from "../../../languages/LanguageDefinitions"; -import getTextFragmentExtractor, { - TextFragmentExtractor, -} from "../../../languages/getTextFragmentExtractor"; +import getTextFragmentExtractor from "../../../languages/getTextFragmentExtractor"; import { Target } from "../../../typings/target.types"; import { SurroundingPairTarget } from "../../targets"; import { getContainingScopeTarget } from "../getContainingScopeTarget"; @@ -73,40 +71,6 @@ function processSurroundingPairCore( ] ?? [scopeType.delimiter]; let node: SyntaxNode | null; - let textFragmentExtractor: TextFragmentExtractor; - - const textFragmentScopeHandler = - languageDefinition?.getTextFragmentScopeHandler(); - - if (textFragmentScopeHandler != null) { - const containingScope = getContainingScopeTarget( - target, - textFragmentScopeHandler, - 0, - ); - - if (containingScope != null) { - const surroundingRange = findSurroundingPairTextBased( - editor, - range, - containingScope[0].contentRange, - delimiters, - scopeType, - ); - if (surroundingRange != null) { - // Found the pair within this text fragment or comment, e.g. "(abc)" - return surroundingRange; - } - // Search in the rest of the file, to find e.g. ("abc") - return findSurroundingPairTextBased( - editor, - range, - null, - delimiters, - scopeType, - ); - } - } try { node = languageDefinitions.getNodeAtLocation(document, range); @@ -122,8 +86,6 @@ function processSurroundingPairCore( scopeType, ); } - - textFragmentExtractor = getTextFragmentExtractor(document.languageId); } catch (err) { if ((err as Error).name === "UnsupportedLanguageError") { // If we're in a language where we don't have a parse tree we use the text @@ -140,14 +102,41 @@ function processSurroundingPairCore( } } - // If we have a parse tree but we are in a string node or in a comment node, - // then we use the text-based algorithm - const selectionWithEditor = { - editor, - selection: new Selection(range.start, range.end), - }; - const textFragmentRange = textFragmentExtractor(node, selectionWithEditor); + const textFragmentRange = (() => { + // First try to use the text fragment scope handler if it exists + const textFragmentScopeHandler = + languageDefinition?.getTextFragmentScopeHandler(); + + if (textFragmentScopeHandler != null) { + const containingScope = getContainingScopeTarget( + target, + textFragmentScopeHandler, + 0, + ); + + return containingScope?.[0].contentRange; + } + + // Then try to use the legacy text fragment extractor if it exists + const textFragmentExtractor = getTextFragmentExtractor(document.languageId); + + if (textFragmentExtractor == null) { + // If the text fragment extractor doesn't exist, or if it explicitly is + // set to `null`, then we just use text-based algorithm on entire document + return document.range; + } + + const selectionWithEditor = { + editor, + selection: new Selection(range.start, range.end), + }; + + return textFragmentExtractor(node, selectionWithEditor); + })(); + if (textFragmentRange != null) { + // If we have a parse tree but we are in a string node or in a comment node, + // then we use the text-based algorithm const surroundingRange = findSurroundingPairTextBased( editor, range, diff --git a/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts b/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts index 9d7f59da49..fddfadf176 100644 --- a/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/ParagraphTarget.ts @@ -34,7 +34,7 @@ export default class ParagraphTarget extends BaseTarget } getRemovalRange(): Range { - // TODO: In the future we could get rid of this function if {@link + // FIXME: In the future we could get rid of this function if {@link // getDelimitedSequenceRemovalRange} made a continuous range from the target // past its delimiter target and then used the removal range of that. const delimiterTarget = diff --git a/packages/cursorless-engine/src/runCommand.ts b/packages/cursorless-engine/src/runCommand.ts index d6d0a02a14..619d71350d 100644 --- a/packages/cursorless-engine/src/runCommand.ts +++ b/packages/cursorless-engine/src/runCommand.ts @@ -6,7 +6,7 @@ import { Snippets } from "./core/Snippets"; import { CommandRunnerImpl } from "./core/commandRunner/CommandRunnerImpl"; import { canonicalizeAndValidateCommand } from "./core/commandVersionUpgrades/canonicalizeAndValidateCommand"; import { RangeUpdater } from "./core/updateSelections/RangeUpdater"; -import { StoredTargetMap, TestCaseRecorder } from "./index"; +import { StoredTargetMap, TestCaseRecorder, TreeSitter } from "./index"; import { LanguageDefinitions } from "./languages/LanguageDefinitions"; import { TargetPipelineRunner } from "./processTargets"; import { MarkStageFactoryImpl } from "./processTargets/MarkStageFactoryImpl"; @@ -26,6 +26,7 @@ import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandler * 5. Call {@link CommandRunnerImpl.run} to run the actual command. */ export async function runCommand( + treeSitter: TreeSitter, debug: Debug, hatTokenMap: HatTokenMap, testCaseRecorder: TestCaseRecorder, @@ -47,6 +48,7 @@ export async function runCommand( ); let commandRunner = createCommandRunner( + treeSitter, languageDefinitions, debug, storedTargets, @@ -66,6 +68,7 @@ export async function runCommand( } function createCommandRunner( + treeSitter: TreeSitter, languageDefinitions: LanguageDefinitions, debug: Debug, storedTargets: StoredTargetMap, @@ -92,6 +95,6 @@ function createCommandRunner( debug, storedTargets, targetPipelineRunner, - new Actions(snippets, rangeUpdater, modifierStageFactory), + new Actions(treeSitter, snippets, rangeUpdater, modifierStageFactory), ); } diff --git a/packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts b/packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts index 7e8f093fbb..959a5b2f84 100644 --- a/packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts +++ b/packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts @@ -1,4 +1,7 @@ -import { ActionDescriptor } from "@cursorless/common"; +import { + ActionDescriptor, + PartialPrimitiveTargetDescriptor, +} from "@cursorless/common"; import { spokenFormTest } from "./spokenFormTest"; // See cursorless-talon-dev/src/cursorless_test.talon @@ -11,11 +14,29 @@ const setSelectionAction: ActionDescriptor = { }; const replaceWithTargetAction: ActionDescriptor = { name: "replaceWithTarget", - source: { + source: decoratedPrimitiveTarget("a"), + destination: { type: "implicit" }, +}; +const insertSingleWordTargetAction: ActionDescriptor = { + name: "replace", + destination: { type: "primitive", - mark: { type: "decoratedSymbol", symbolColor: "default", character: "a" }, + insertionMode: "to", + target: decoratedPrimitiveTarget("a"), }, - destination: { type: "implicit" }, + replaceWith: ["hello"], +}; +const insertMultipleWordsTargetAction: ActionDescriptor = { + name: "replace", + destination: { + type: "primitive", + insertionMode: "after", + target: { + type: "list", + elements: [decoratedPrimitiveTarget("a"), decoratedPrimitiveTarget("b")], + }, + }, + replaceWith: ["hello", "world"], }; const insertSnippetAction: ActionDescriptor = { name: "insertSnippet", @@ -58,6 +79,10 @@ const wrapWithSnippetByNameAction: ActionDescriptor = { variableName: "body", }, }; +const parseTreeAction: ActionDescriptor = { + name: "private.showParseTree", + target: decoratedPrimitiveTarget("a"), +}; /** * These test our Talon api using dummy spoken forms defined in @@ -66,6 +91,11 @@ const wrapWithSnippetByNameAction: ActionDescriptor = { export const talonApiFixture = [ spokenFormTest("test api command this", setSelectionAction), spokenFormTest("test api command bring air", replaceWithTargetAction), + spokenFormTest("test api insert hello to air", insertSingleWordTargetAction), + spokenFormTest( + "test api insert hello and world after air and bat", + insertMultipleWordsTargetAction, + ), spokenFormTest("test api insert snippet", insertSnippetAction), spokenFormTest("test api insert snippet by name", insertSnippetByNameAction), spokenFormTest("test api wrap with snippet this", wrapWithSnippetAction), @@ -73,4 +103,14 @@ export const talonApiFixture = [ "test api wrap with snippet by name this", wrapWithSnippetByNameAction, ), + spokenFormTest("parse tree air", parseTreeAction), ]; + +function decoratedPrimitiveTarget( + character: string, +): PartialPrimitiveTargetDescriptor { + return { + type: "primitive", + mark: { type: "decoratedSymbol", symbolColor: "default", character }, + }; +} diff --git a/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.ts b/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.ts index 116bcd75e5..456f6f968a 100644 --- a/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.ts +++ b/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.ts @@ -8,7 +8,7 @@ import { import { matchAll } from "../util/regex"; /** - * A list of all symbols that are speakable by default in knausj. + * A list of all symbols that are speakable by default in community. */ const KNOWN_SYMBOLS = [ "!", diff --git a/packages/cursorless-engine/src/tokenizer/tokenizer.ts b/packages/cursorless-engine/src/tokenizer/tokenizer.ts index 2f9b25287a..75b7145cb7 100644 --- a/packages/cursorless-engine/src/tokenizer/tokenizer.ts +++ b/packages/cursorless-engine/src/tokenizer/tokenizer.ts @@ -43,7 +43,7 @@ const FIXED_TOKENS = [ export const IDENTIFIER_WORD_REGEXES = ["\\p{L}", "\\p{M}", "\\p{N}"]; const SINGLE_SYMBOLS_REGEX = "[^\\s\\w]"; // Accepts digits dot digits if not preceded or followed by a digit or dot. The -// negative lookahed / lookbehind are to prevent matching numbers in semantic +// negative lookahead / lookbehind are to prevent matching numbers in semantic // versions (eg 1.2.3) const NUMBERS_REGEX = "(? - min(graphemeTokenRanks[text].filter((r) => r > tokenRank)) ?? Infinity; + const coreMetric = memoize((graphemeText: string): number => { + return ( + min(graphemeTokenRanks[graphemeText].filter((r) => r > tokenRank)) ?? + Infinity + ); + }); + return ({ grapheme: { text } }) => coreMetric(text); } /** diff --git a/packages/cursorless-engine/src/util/allocateHats/allocateHats.ts b/packages/cursorless-engine/src/util/allocateHats/allocateHats.ts index 5c22ffab46..09bb49fc00 100644 --- a/packages/cursorless-engine/src/util/allocateHats/allocateHats.ts +++ b/packages/cursorless-engine/src/util/allocateHats/allocateHats.ts @@ -9,7 +9,6 @@ import { Token, TokenHat, } from "@cursorless/common"; -import { clone } from "lodash"; import { Grapheme, TokenGraphemeSplitter } from "../../tokenGraphemeSplitter"; import { chooseTokenHat } from "./chooseTokenHat"; import { getHatRankingContext } from "./getHatRankingContext"; @@ -69,13 +68,19 @@ export function allocateHats( tokenGraphemeSplitter, ); + /* All initially enabled hat styles. */ + const enabledHatStyleNames = Object.keys(enabledHatStyles); + /** * A map from graphemes to the remaining hat styles that have not yet been * used for that grapheme. As we assign hats to tokens, we remove them from - * these lists so that they don't get used again in this pass. + * these arrays so that they don't get used again in this pass. + * We use an array rather than a map to make iteration cheaper; + * we only remove elements once, but we iterate many times, + * and this is a hot path. */ - const graphemeRemainingHatCandidates = new DefaultMap( - () => clone(enabledHatStyles), + const graphemeRemainingHatCandidates = new DefaultMap( + () => [...enabledHatStyleNames], ); // Iterate through tokens in order of decreasing rank, assigning each one a @@ -90,6 +95,7 @@ export function allocateHats( tokenGraphemeSplitter, token, graphemeRemainingHatCandidates, + enabledHatStyles, ); const chosenHat = chooseTokenHat( @@ -107,10 +113,12 @@ export function allocateHats( } // Remove the hat we chose from consideration for lower ranked tokens - delete graphemeRemainingHatCandidates.get(chosenHat.grapheme.text)[ - chosenHat.style - ]; - + graphemeRemainingHatCandidates.set( + chosenHat.grapheme.text, + graphemeRemainingHatCandidates + .get(chosenHat.grapheme.text) + .filter((style) => style !== chosenHat.style), + ); return constructHatRangeDescriptor(token, chosenHat); }) .filter((value): value is TokenHat => value != null); @@ -135,23 +143,31 @@ function getTokenOldHatMap(oldTokenHats: readonly TokenHat[]) { function getTokenRemainingHatCandidates( tokenGraphemeSplitter: TokenGraphemeSplitter, token: Token, - availableGraphemeStyles: DefaultMap, + graphemeRemainingHatCandidates: DefaultMap, + enabledHatStyles: HatStyleMap, ): HatCandidate[] { - return tokenGraphemeSplitter - .getTokenGraphemes(token.text) - .flatMap((grapheme) => - Object.entries(availableGraphemeStyles.get(grapheme.text)).map( - ([style, { penalty }]) => ({ - grapheme, - style, - penalty, - }), - ), - ); + // Use iteration here instead of functional constructs, + // because this is a hot path and we want to avoid allocating arrays + // and calling tiny functions lots of times. + const candidates: HatCandidate[] = []; + const graphemes = tokenGraphemeSplitter.getTokenGraphemes(token.text); + for (const grapheme of graphemes) { + for (const style of graphemeRemainingHatCandidates.get(grapheme.text)) { + // Allocating and pushing all of these objects is + // the single most expensive thing in hat allocation. + // Please pay attention to performance when modifying this code. + candidates.push({ + grapheme, + style, + penalty: enabledHatStyles[style].penalty, + }); + } + } + return candidates; } /** - * @param token The token that recevied the hat + * @param token The token that received the hat * @param chosenHat The hat we chose for the token * @returns An object indicating the hat assigned to the token, along with the * range of the grapheme upon which it sits diff --git a/packages/cursorless-engine/src/util/allocateHats/maxByFirstDiffering.test.ts b/packages/cursorless-engine/src/util/allocateHats/maxByFirstDiffering.test.ts new file mode 100644 index 0000000000..296b199edd --- /dev/null +++ b/packages/cursorless-engine/src/util/allocateHats/maxByFirstDiffering.test.ts @@ -0,0 +1,40 @@ +import * as assert from "assert"; +import { maxByAllowingTies } from "./maxByFirstDiffering"; + +// known good but slow +function goldenMaxByAllowingTies(arr: T[], fn: (item: T) => number): T[] { + const max = Math.max(...arr.map(fn)); + return arr.filter((item) => fn(item) === max); +} + +suite("maxByFirstDiffering", () => { + test("maxByAllowingTies", () => { + const testCases: number[][] = [ + [], + [0], + [1], + [-Infinity], + [+Infinity], + [0, 0], + [0, 1], + [1, 0], + [-Infinity, -Infinity], + [-Infinity, +Infinity], + [+Infinity, -Infinity], + [+Infinity, 0], + [0, +Infinity], + [-Infinity, 0], + [0, -Infinity], + [0, 1, 0, 1], + [1, 0, 1, 0], + [0, 1, 1, 0], + [1, 0, 0, 1], + ]; + + testCases.forEach((testCase) => { + const actual = maxByAllowingTies(testCase, (x) => x); + const expected = goldenMaxByAllowingTies(testCase, (x) => x); + assert.deepStrictEqual(actual, expected); + }); + }); +}); diff --git a/packages/cursorless-engine/src/util/allocateHats/maxByFirstDiffering.ts b/packages/cursorless-engine/src/util/allocateHats/maxByFirstDiffering.ts index e8634c14af..c5bcc2989d 100644 --- a/packages/cursorless-engine/src/util/allocateHats/maxByFirstDiffering.ts +++ b/packages/cursorless-engine/src/util/allocateHats/maxByFirstDiffering.ts @@ -24,15 +24,45 @@ export function maxByFirstDiffering( return undefined; } let remainingValues = arr; - for (const fn of fns) { if (remainingValues.length === 1) { return remainingValues[0]; } - - const max = Math.max(...remainingValues.map(fn)); - remainingValues = remainingValues.filter((item) => fn(item) === max); + remainingValues = maxByAllowingTies(remainingValues, fn); } - return remainingValues[0]; } + +/** + * Given an array of items and a function that returns a number for each item, + * return all items that share the maximum value according to that function. + * @param arr The array to find the max values of + * @param fn A function that returns a number for each item in the array + * @returns All items in the array that share the maximum value + **/ +export function maxByAllowingTies(arr: T[], fn: (item: T) => number): T[] { + // This is equivalent to, but faster than: + // + // const max = Math.max(...arr.map(fn)); + // return arr.filter((item) => fn(item) === max); + // + // It does only a single pass through the array, and allocates no + // intermediate arrays (in the common case). + + // Accumulate all items with the single highest value, + // resetting whenever we find a new highest value. + let best: number = -Infinity; + const keep: T[] = []; + for (const item of arr) { + const value = fn(item); + if (value < best) { + continue; + } + if (value > best) { + best = value; + keep.length = 0; + } + keep.push(item); + } + return keep; +} diff --git a/packages/cursorless-engine/src/util/setSelectionsAndFocusEditor.ts b/packages/cursorless-engine/src/util/setSelectionsAndFocusEditor.ts index 2b8b318a22..404daefc54 100644 --- a/packages/cursorless-engine/src/util/setSelectionsAndFocusEditor.ts +++ b/packages/cursorless-engine/src/util/setSelectionsAndFocusEditor.ts @@ -1,6 +1,6 @@ import { EditableTextEditor, Selection } from "@cursorless/common"; -import uniqDeep from "./uniqDeep"; +import { uniqWithHash } from "./uniqWithHash"; export async function setSelectionsAndFocusEditor( editor: EditableTextEditor, @@ -22,5 +22,9 @@ export function setSelectionsWithoutFocusingEditor( editor: EditableTextEditor, selections: Selection[], ) { - editor.selections = uniqDeep(selections); + editor.selections = uniqWithHash( + selections, + (a, b) => a.isEqual(b), + (s) => s.concise(), + ); } diff --git a/packages/cursorless-engine/src/util/unifyRanges.ts b/packages/cursorless-engine/src/util/unifyRanges.ts index c4f3eef627..851d575d6e 100644 --- a/packages/cursorless-engine/src/util/unifyRanges.ts +++ b/packages/cursorless-engine/src/util/unifyRanges.ts @@ -16,7 +16,7 @@ export function unifyRemovalTargets(targets: Target[]): Target[] { a.contentRange.start.compareTo(b.contentRange.start), ); let run = true; - // Merge targets untill there are no overlaps/intersections + // Merge targets until there are no overlaps/intersections while (run) { [results, run] = unifyTargetsOnePass(results); } diff --git a/packages/cursorless-engine/src/util/uniqDeep.ts b/packages/cursorless-engine/src/util/uniqDeep.ts deleted file mode 100644 index 182226a01e..0000000000 --- a/packages/cursorless-engine/src/util/uniqDeep.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { uniqWith, isEqual } from "lodash"; - -export default (array: T[]): T[] => { - return uniqWith(array, isEqual); -}; diff --git a/packages/cursorless-engine/src/util/uniqWithHash.test.ts b/packages/cursorless-engine/src/util/uniqWithHash.test.ts new file mode 100644 index 0000000000..5e6be110ed --- /dev/null +++ b/packages/cursorless-engine/src/util/uniqWithHash.test.ts @@ -0,0 +1,65 @@ +import * as assert from "assert"; +import * as fc from "fast-check"; +import { uniqWith } from "lodash"; +import { uniqWithHash } from "./uniqWithHash"; + +// known good but slow (quadratic!) +function knownGoodUniqWithHash( + array: T[], + fn: (a: T, b: T) => boolean, + _: (t: T) => string, +): T[] { + return uniqWith(array, fn); +} + +suite("uniqWithHash", () => { + test("uniqWithHash", () => { + // believe it or not, all these test cases are important + const testCases: number[][] = [ + [], + [0], + [1], + [0, 1], + [1, 0], + [0, 0], + [1, 1], + [0, 1, 0, 1], + [1, 0, 1, 0], + [0, 1, 1, 0], + [1, 0, 0, 1], + [0, 1, 2, 3], + [0, 1, 2, 3, 0, 1, 2, 3], + [0, 0, 1, 1, 2, 2, 3, 3], + [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3], + [0, 0, 1, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2, 3, 3], + [0, 1, 2, 3, 4, 5, 0, 1, 2, 3], + [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 0, 1, 2, 3], + [0, 0, 1, 1, 2, 2, 3, 3, 4, 4], + [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4], + ]; + + const hashFunctions = [ + (x: number) => x.toString(), + (x: number) => (x % 2).toString(), + (_: number) => "0", + ]; + + hashFunctions.forEach((hash) => { + const check = (testCase: number[]) => { + const actual = uniqWithHash(testCase, (a, b) => a === b, hash); + const expected = knownGoodUniqWithHash( + testCase, + (a, b) => a === b, + hash, + ); + assert.deepStrictEqual(actual, expected); + }; + + testCases.forEach(check); + + // max length 50 because the known good implementation is quadratic + const randomNumbers = fc.array(fc.integer(), { maxLength: 50 }); + fc.assert(fc.property(randomNumbers, check)); + }); + }); +}); diff --git a/packages/cursorless-engine/src/util/uniqWithHash.ts b/packages/cursorless-engine/src/util/uniqWithHash.ts new file mode 100644 index 0000000000..16221ce7b0 --- /dev/null +++ b/packages/cursorless-engine/src/util/uniqWithHash.ts @@ -0,0 +1,81 @@ +import { uniqWith } from "lodash"; + +/** + * Like lodash.uniqWith, but uses a hash function to (mostly) avoid quadratic runtime. + * @param array The array to uniq + * @param isEqual The equality function + * @param hash The hash function. It must be a valid hash function, insofar as it must return the same value for equal items. A hash function that returns a constant value is equivalent to lodash.uniqWith. + * @returns The uniq array + */ +export function uniqWithHash( + array: T[], + isEqual: (a: T, b: T) => boolean, + hash: (t: T) => string, +): T[] { + // Handle the common, tiny cases without allocating anything extra. + if (array.length < 2) { + return [...array]; + } + if (array.length === 2) { + if (isEqual(array[0], array[1])) { + return [array[0]]; + } + return [...array]; + } + // First, split up the array using the hash function. + // This keeps the sets of items passed to uniqWith small, + // so that the quadratic runtime of uniqWith less of a problem. + + /* Keep track of which sets have multiple items, so that we can uniq them. */ + const needsUniq: string[] = []; + const hashToItems: Map = array.reduce((acc, item) => { + const key = hash(item); + const items = acc.get(key); + if (items == null) { + acc.set(key, [item]); + return acc; + } + + acc.get(key)!.push(item); + if (items.length === 2) { + needsUniq.push(key); + } + return acc; + }, new Map()); + + // Another common case: Everything is unique. + if (needsUniq.length === 0) { + return [...array]; + } + + // For hash collisions, uniq the items, + // letting uniqWith provide correct semantics. + needsUniq.forEach((key) => { + hashToItems.set(key, uniqWith(hashToItems.get(key)!, isEqual)); + }); + + // To preserve order, step through the original items + // one at a time, returning it as appropriate. + // We need to do this because uniqWith preserves order, + // and we are mimicking its semantics. + // Note that items were added in order, + // and uniqWith preserved that order. + return array.flatMap((item) => { + const key = hash(item); + const items = hashToItems.get(key)!; + if (items == null || items.length === 0) { + // Removed by uniqWith. + return []; + } + const first = items[0]!; + if (!isEqual(first, item)) { + // Removed by uniqWith. + // Note that it is sufficient to check the first item, + // because uniqWith preserves order. + return []; + } + // Emit item. + items.shift(); + return first; + }); +} diff --git a/packages/cursorless-org/package.json b/packages/cursorless-org/package.json index e5a7c6a56f..e432962465 100644 --- a/packages/cursorless-org/package.json +++ b/packages/cursorless-org/package.json @@ -34,7 +34,7 @@ "@types/react-dom": "18.0.11", "autoprefixer": "10.4.13", "http-server": "14.1.1", - "postcss": "8.4.21", + "postcss": "8.4.31", "tailwindcss": "3.2.7" }, "license": "MIT", diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callFine.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callFine.yml index 1dd484ddee..ce28c2d8d3 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callFine.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callFine.yml @@ -7,7 +7,6 @@ command: - type: primitive mark: {type: decoratedSymbol, symbolColor: default, character: f} - {type: primitive, isImplicit: true} -spokenFormError: Action 'callAsFunction' with argument initialState: documentContents: |- foo; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callFineOnBatt.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callFineOnBatt.yml new file mode 100644 index 0000000000..990596ee6a --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callFineOnBatt.yml @@ -0,0 +1,41 @@ +languageId: plaintext +command: + version: 6 + spokenForm: call fine on bat + action: + name: callAsFunction + callee: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: f} + argument: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: b} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + foo + bar + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: + default.f: + start: {line: 0, character: 0} + end: {line: 0, character: 3} + default.b: + start: {line: 1, character: 0} + end: {line: 1, character: 3} +finalState: + documentContents: |- + foo + foo(bar) + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - type: UntypedTarget + contentRange: + start: {line: 1, character: 0} + end: {line: 1, character: 8} + isReversed: false + hasExplicitRange: true diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callVest.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callVest.yml index dd90002c66..0796f6d295 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callVest.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callVest.yml @@ -7,7 +7,6 @@ command: - type: primitive mark: {type: decoratedSymbol, symbolColor: default, character: v} - {type: primitive, isImplicit: true} -spokenFormError: Action 'callAsFunction' with argument initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callVestOnCap.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callVestOnCap.yml index f7f9d4688a..cb557fe47c 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callVestOnCap.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/callVestOnCap.yml @@ -8,7 +8,6 @@ command: mark: {type: decoratedSymbol, symbolColor: default, character: v} - type: primitive mark: {type: decoratedSymbol, symbolColor: default, character: c} -spokenFormError: Action 'callAsFunction' with argument initialState: documentContents: | diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/parseTreeFile.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/parseTreeFile.yml new file mode 100644 index 0000000000..95de60ec68 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/parseTreeFile.yml @@ -0,0 +1,30 @@ +languageId: typescript +command: + version: 6 + spokenForm: parse tree file + action: + name: private.showParseTree + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: document} + usePrePhraseSnapshot: true +initialState: + documentContents: const value = 2; + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: const value = 2; + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - type: DocumentTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 16} + isReversed: false + hasExplicitRange: true diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/placeHelloAfterAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/placeHelloAfterAir.yml new file mode 100644 index 0000000000..2b54fc0599 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/placeHelloAfterAir.yml @@ -0,0 +1,43 @@ +languageId: plaintext +command: + version: 6 + spokenForm: place hello after air + action: + name: replace + replaceWith: [hello] + destination: + type: primitive + insertionMode: after + target: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +spokenFormError: Action 'replace' +initialState: + documentContents: aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: aaa hello + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 4} + end: {line: 0, character: 9} + isReversed: false + hasExplicitRange: true + sourceMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 3} + isReversed: false + hasExplicitRange: false diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/placeHelloToFine.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/placeHelloToFine.yml new file mode 100644 index 0000000000..62674c6483 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/placeHelloToFine.yml @@ -0,0 +1,43 @@ +languageId: plaintext +command: + version: 6 + spokenForm: place hello to fine + action: + name: replace + replaceWith: [hello] + destination: + type: primitive + insertionMode: to + target: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: f} + usePrePhraseSnapshot: true +spokenFormError: Action 'replace' +initialState: + documentContents: foo + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: + default.f: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: hello + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 5} + isReversed: false + hasExplicitRange: true + sourceMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 5} + isReversed: false + hasExplicitRange: false diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/changeType.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/changeType.yml new file mode 100644 index 0000000000..16d17eb2b3 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/java/changeType.yml @@ -0,0 +1,37 @@ +languageId: java +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: | + import java.util.Map; + + public class MyClass { + public void myFunk() { + Map.Entry e = null; + } + } + selections: + - anchor: {line: 4, character: 4} + active: {line: 4, character: 4} + marks: {} +finalState: + documentContents: | + import java.util.Map; + + public class MyClass { + public void myFunk() { + e = null; + } + } + selections: + - anchor: {line: 4, character: 4} + active: {line: 4, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeEveryValue.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeEveryValue.yml new file mode 100644 index 0000000000..b51f67cdf8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeEveryValue.yml @@ -0,0 +1,33 @@ +languageId: javascript +command: + version: 6 + spokenForm: change every value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + function aaa() { + const bbb = 0; + const ccc = 0; + } + selections: + - anchor: {line: 2, character: 18} + active: {line: 2, character: 18} + marks: {} +finalState: + documentContents: |- + function aaa() { + const bbb = ; + const ccc = ; + } + selections: + - anchor: {line: 1, character: 16} + active: {line: 1, character: 16} + - anchor: {line: 2, character: 16} + active: {line: 2, character: 16} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeEveryValue2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeEveryValue2.yml new file mode 100644 index 0000000000..4eddf36914 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeEveryValue2.yml @@ -0,0 +1,29 @@ +languageId: javascript +command: + version: 6 + spokenForm: change every value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const bbb = 0; + const ccc = 0; + selections: + - anchor: {line: 1, character: 14} + active: {line: 1, character: 14} + marks: {} +finalState: + documentContents: |- + const bbb = ; + const ccc = ; + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName.yml new file mode 100644 index 0000000000..b381255cd1 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName.yml @@ -0,0 +1,23 @@ +languageId: javascript +command: + version: 6 + spokenForm: change name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: const aaa = "bbb", ccc = "ddd"; + selections: + - anchor: {line: 0, character: 30} + active: {line: 0, character: 30} + marks: {} +finalState: + documentContents: const aaa = "bbb", = "ddd"; + selections: + - anchor: {line: 0, character: 19} + active: {line: 0, character: 19} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName2.yml new file mode 100644 index 0000000000..da2076527f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName2.yml @@ -0,0 +1,105 @@ +languageId: javascript +command: + version: 6 + spokenForm: change name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa = 0; + let bbb = 0; + var hhh = 0; + ccc = 0; + kkk += 1; + const ddd = 0, eee = 0; + let fff = 0, ggg = 0; + var iii = 0; + export const jjj = 0; + export let kkk = 0; + export var lll = 0; + export const mmm = 0, nnn = 0; + export let ooo = 0, ppp = 0; + selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} + - anchor: {line: 2, character: 12} + active: {line: 2, character: 12} + - anchor: {line: 3, character: 8} + active: {line: 3, character: 8} + - anchor: {line: 4, character: 9} + active: {line: 4, character: 9} + - anchor: {line: 5, character: 23} + active: {line: 5, character: 23} + - anchor: {line: 6, character: 21} + active: {line: 6, character: 21} + - anchor: {line: 7, character: 12} + active: {line: 7, character: 12} + - anchor: {line: 8, character: 21} + active: {line: 8, character: 21} + - anchor: {line: 9, character: 19} + active: {line: 9, character: 19} + - anchor: {line: 10, character: 19} + active: {line: 10, character: 19} + - anchor: {line: 11, character: 30} + active: {line: 11, character: 30} + - anchor: {line: 12, character: 28} + active: {line: 12, character: 28} + marks: {} +finalState: + documentContents: |- + const = 0; + let = 0; + var = 0; + = 0; + += 1; + const = 0, = 0; + let = 0, = 0; + var = 0; + export const = 0; + export let = 0; + export var = 0; + export const = 0, = 0; + export let = 0, = 0; + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + - anchor: {line: 3, character: 0} + active: {line: 3, character: 0} + - anchor: {line: 4, character: 0} + active: {line: 4, character: 0} + - anchor: {line: 5, character: 6} + active: {line: 5, character: 6} + - anchor: {line: 5, character: 12} + active: {line: 5, character: 12} + - anchor: {line: 6, character: 4} + active: {line: 6, character: 4} + - anchor: {line: 6, character: 10} + active: {line: 6, character: 10} + - anchor: {line: 7, character: 4} + active: {line: 7, character: 4} + - anchor: {line: 8, character: 13} + active: {line: 8, character: 13} + - anchor: {line: 9, character: 11} + active: {line: 9, character: 11} + - anchor: {line: 10, character: 11} + active: {line: 10, character: 11} + - anchor: {line: 11, character: 13} + active: {line: 11, character: 13} + - anchor: {line: 11, character: 19} + active: {line: 11, character: 19} + - anchor: {line: 12, character: 11} + active: {line: 12, character: 11} + - anchor: {line: 12, character: 17} + active: {line: 12, character: 17} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName3.yml new file mode 100644 index 0000000000..1789821b20 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName3.yml @@ -0,0 +1,47 @@ +languageId: javascript +command: + version: 6 + spokenForm: change name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + hhh = 0, iii = 0, jjj=0; + lll += 1, mmm += 1, nnn += 1; + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} + - anchor: {line: 1, character: 20} + active: {line: 1, character: 20} + marks: {} +finalState: + documentContents: |2- + = 0, = 0, =0; + += 1, += 1, += 1; + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + - anchor: {line: 1, character: 7} + active: {line: 1, character: 7} + - anchor: {line: 1, character: 14} + active: {line: 1, character: 14} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName4.yml new file mode 100644 index 0000000000..4335acfe1a --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName4.yml @@ -0,0 +1,23 @@ +languageId: javascript +command: + version: 6 + spokenForm: change name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: for (const aaa of bbb) {} + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: for (const of bbb) {} + selections: + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName5.yml new file mode 100644 index 0000000000..33b8d2db3c --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeName5.yml @@ -0,0 +1,19 @@ +languageId: javascript +command: + version: 6 + spokenForm: change name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: for (const aaa of bbb) {} + selections: + - anchor: {line: 0, character: 24} + active: {line: 0, character: 24} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue.yml new file mode 100644 index 0000000000..4cb1bdd9b8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue.yml @@ -0,0 +1,47 @@ +languageId: javascript +command: + version: 6 + spokenForm: change value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + hhh = 0, iii = 0, jjj=0; + lll += 1, mmm += 1, nnn += 1; + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} + - anchor: {line: 1, character: 20} + active: {line: 1, character: 20} + marks: {} +finalState: + documentContents: |- + hhh = , iii = , jjj=; + lll += , mmm += , nnn += ; + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} + - anchor: {line: 1, character: 7} + active: {line: 1, character: 7} + - anchor: {line: 1, character: 16} + active: {line: 1, character: 16} + - anchor: {line: 1, character: 25} + active: {line: 1, character: 25} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue2.yml new file mode 100644 index 0000000000..3898f69f5a --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue2.yml @@ -0,0 +1,109 @@ +languageId: javascript +command: + version: 6 + spokenForm: change value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa = 0; + let bbb = 0; + var hhh = 0; + ccc = 0; + kkk += 1; + const ddd = 0, eee = 0; + let fff = 0, ggg = 0; + var iii = 0; + let qqq; + var rrr; + export const jjj = 0; + export let kkk = 0; + export var lll = 0; + export const mmm = 0, nnn = 0; + export let ooo = 0, ppp = 0; + selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} + - anchor: {line: 2, character: 12} + active: {line: 2, character: 12} + - anchor: {line: 3, character: 8} + active: {line: 3, character: 8} + - anchor: {line: 4, character: 9} + active: {line: 4, character: 9} + - anchor: {line: 5, character: 23} + active: {line: 5, character: 23} + - anchor: {line: 6, character: 21} + active: {line: 6, character: 21} + - anchor: {line: 7, character: 12} + active: {line: 7, character: 12} + - anchor: {line: 10, character: 21} + active: {line: 10, character: 21} + - anchor: {line: 11, character: 19} + active: {line: 11, character: 19} + - anchor: {line: 12, character: 19} + active: {line: 12, character: 19} + - anchor: {line: 13, character: 30} + active: {line: 13, character: 30} + - anchor: {line: 14, character: 28} + active: {line: 14, character: 28} + marks: {} +finalState: + documentContents: |- + const aaa = ; + let bbb = ; + var hhh = ; + ccc = ; + kkk += ; + const ddd = , eee = ; + let fff = , ggg = ; + var iii = ; + let qqq; + var rrr; + export const jjj = ; + export let kkk = ; + export var lll = ; + export const mmm = , nnn = ; + export let ooo = , ppp = ; + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} + - anchor: {line: 2, character: 10} + active: {line: 2, character: 10} + - anchor: {line: 3, character: 6} + active: {line: 3, character: 6} + - anchor: {line: 4, character: 7} + active: {line: 4, character: 7} + - anchor: {line: 5, character: 12} + active: {line: 5, character: 12} + - anchor: {line: 5, character: 20} + active: {line: 5, character: 20} + - anchor: {line: 6, character: 10} + active: {line: 6, character: 10} + - anchor: {line: 6, character: 18} + active: {line: 6, character: 18} + - anchor: {line: 7, character: 10} + active: {line: 7, character: 10} + - anchor: {line: 10, character: 19} + active: {line: 10, character: 19} + - anchor: {line: 11, character: 17} + active: {line: 11, character: 17} + - anchor: {line: 12, character: 17} + active: {line: 12, character: 17} + - anchor: {line: 13, character: 19} + active: {line: 13, character: 19} + - anchor: {line: 13, character: 27} + active: {line: 13, character: 27} + - anchor: {line: 14, character: 17} + active: {line: 14, character: 17} + - anchor: {line: 14, character: 25} + active: {line: 14, character: 25} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue3.yml new file mode 100644 index 0000000000..ce24f8114e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue3.yml @@ -0,0 +1,43 @@ +languageId: javascript +command: + version: 6 + spokenForm: change value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const ddd = 0, eee = 0; + let fff = 0, ggg = 0; + export const mmm = 0, nnn = 0; + export let ooo = 0, ppp = 0; + selections: + - anchor: {line: 0, character: 15} + active: {line: 0, character: 15} + - anchor: {line: 1, character: 13} + active: {line: 1, character: 13} + - anchor: {line: 2, character: 22} + active: {line: 2, character: 22} + - anchor: {line: 3, character: 20} + active: {line: 3, character: 20} + marks: {} +finalState: + documentContents: |- + const ddd = 0, eee = ; + let fff = 0, ggg = ; + export const mmm = 0, nnn = ; + export let ooo = 0, ppp = ; + selections: + - anchor: {line: 0, character: 21} + active: {line: 0, character: 21} + - anchor: {line: 1, character: 19} + active: {line: 1, character: 19} + - anchor: {line: 2, character: 28} + active: {line: 2, character: 28} + - anchor: {line: 3, character: 26} + active: {line: 3, character: 26} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue4.yml new file mode 100644 index 0000000000..c5a2f03666 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue4.yml @@ -0,0 +1,23 @@ +languageId: javascript +command: + version: 6 + spokenForm: change value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: for (const aaa of bbb) {} + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: for (const aaa of ) {} + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue5.yml new file mode 100644 index 0000000000..0e6fdfd58e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/changeValue5.yml @@ -0,0 +1,19 @@ +languageId: javascript +command: + version: 6 + spokenForm: change value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: for (const aaa of bbb) {} + selections: + - anchor: {line: 0, character: 24} + active: {line: 0, character: 24} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckName.yml new file mode 100644 index 0000000000..f73bed4682 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckName.yml @@ -0,0 +1,67 @@ +languageId: javascript +command: + version: 6 + spokenForm: chuck name + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa = 0; + let bbb = 0; + var hhh = 0; + ccc = 0; + kkk += 1; + const ddd = 0, eee = 0; + let fff = 0, ggg = 0; + var iii = 0; + selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} + - anchor: {line: 2, character: 12} + active: {line: 2, character: 12} + - anchor: {line: 3, character: 8} + active: {line: 3, character: 8} + - anchor: {line: 4, character: 9} + active: {line: 4, character: 9} + - anchor: {line: 5, character: 23} + active: {line: 5, character: 23} + - anchor: {line: 6, character: 21} + active: {line: 6, character: 21} + - anchor: {line: 7, character: 12} + active: {line: 7, character: 12} + marks: {} +finalState: + documentContents: |- + 0; + 0; + 0; + 0; + 1; + 0, 0; + 0, 0; + 0; + selections: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + - anchor: {line: 2, character: 2} + active: {line: 2, character: 2} + - anchor: {line: 3, character: 2} + active: {line: 3, character: 2} + - anchor: {line: 4, character: 2} + active: {line: 4, character: 2} + - anchor: {line: 5, character: 5} + active: {line: 5, character: 5} + - anchor: {line: 6, character: 5} + active: {line: 6, character: 5} + - anchor: {line: 7, character: 2} + active: {line: 7, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckName2.yml new file mode 100644 index 0000000000..483074fd7b --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckName2.yml @@ -0,0 +1,23 @@ +languageId: javascript +command: + version: 6 + spokenForm: chuck name + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: const aaa = "bbb", ccc = "ddd"; + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + marks: {} +finalState: + documentContents: "\"bbb\", ccc = \"ddd\";" + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckName3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckName3.yml new file mode 100644 index 0000000000..04d8c2a893 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckName3.yml @@ -0,0 +1,47 @@ +languageId: javascript +command: + version: 6 + spokenForm: chuck name + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + hhh = 0, iii = 0, jjj=0; + lll += 1, mmm += 1, nnn += 1; + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} + - anchor: {line: 1, character: 20} + active: {line: 1, character: 20} + marks: {} +finalState: + documentContents: |- + 0, 0, 0; + 1, 1, 1; + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + - anchor: {line: 1, character: 3} + active: {line: 1, character: 3} + - anchor: {line: 1, character: 6} + active: {line: 1, character: 6} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckValue.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckValue.yml new file mode 100644 index 0000000000..4dcfcd849e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckValue.yml @@ -0,0 +1,47 @@ +languageId: javascript +command: + version: 6 + spokenForm: chuck value + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + hhh = 0, iii = 0, jjj=0; + lll += 1, mmm += 1, nnn += 1; + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} + - anchor: {line: 1, character: 20} + active: {line: 1, character: 20} + marks: {} +finalState: + documentContents: |- + hhh, iii, jjj; + lll, mmm, nnn; + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + - anchor: {line: 1, character: 5} + active: {line: 1, character: 5} + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckValue2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckValue2.yml new file mode 100644 index 0000000000..71da6def1c --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/javascript/chuckValue2.yml @@ -0,0 +1,101 @@ +languageId: javascript +command: + version: 6 + spokenForm: chuck value + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa = 0; + let bbb = 0; + var hhh = 0; + ccc = 0; + kkk += 1; + const ddd = 0, eee = 0; + let fff = 0, ggg = 0; + var iii = 0; + let qqq; + var rrr; + export const jjj = 0; + export let kkk = 0; + export var lll = 0; + export const mmm = 0, nnn = 0; + export let ooo = 0, ppp = 0; + selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} + - anchor: {line: 2, character: 12} + active: {line: 2, character: 12} + - anchor: {line: 3, character: 8} + active: {line: 3, character: 8} + - anchor: {line: 4, character: 9} + active: {line: 4, character: 9} + - anchor: {line: 5, character: 23} + active: {line: 5, character: 23} + - anchor: {line: 6, character: 21} + active: {line: 6, character: 21} + - anchor: {line: 7, character: 12} + active: {line: 7, character: 12} + - anchor: {line: 10, character: 21} + active: {line: 10, character: 21} + - anchor: {line: 11, character: 19} + active: {line: 11, character: 19} + - anchor: {line: 12, character: 19} + active: {line: 12, character: 19} + - anchor: {line: 13, character: 30} + active: {line: 13, character: 30} + - anchor: {line: 14, character: 28} + active: {line: 14, character: 28} + marks: {} +finalState: + documentContents: |- + const aaa; + let bbb; + var hhh; + ccc; + kkk; + const ddd, eee; + let fff, ggg; + var iii; + let qqq; + var rrr; + export const jjj; + export let kkk; + export var lll; + export const mmm, nnn; + export let ooo, ppp; + selections: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} + - anchor: {line: 1, character: 8} + active: {line: 1, character: 8} + - anchor: {line: 2, character: 8} + active: {line: 2, character: 8} + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} + - anchor: {line: 4, character: 4} + active: {line: 4, character: 4} + - anchor: {line: 5, character: 15} + active: {line: 5, character: 15} + - anchor: {line: 6, character: 13} + active: {line: 6, character: 13} + - anchor: {line: 7, character: 8} + active: {line: 7, character: 8} + - anchor: {line: 10, character: 17} + active: {line: 10, character: 17} + - anchor: {line: 11, character: 15} + active: {line: 11, character: 15} + - anchor: {line: 12, character: 15} + active: {line: 12, character: 15} + - anchor: {line: 13, character: 22} + active: {line: 13, character: 22} + - anchor: {line: 14, character: 20} + active: {line: 14, character: 20} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/jsx/changeEveryValue.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/jsx/changeEveryValue.yml new file mode 100644 index 0000000000..78deddbcac --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/jsx/changeEveryValue.yml @@ -0,0 +1,25 @@ +languageId: javascriptreact +command: + version: 6 + spokenForm: change every value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: + selections: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + marks: {} +finalState: + documentContents: + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/jsx/changeEveryValue2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/jsx/changeEveryValue2.yml new file mode 100644 index 0000000000..184f04dfb8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/jsx/changeEveryValue2.yml @@ -0,0 +1,25 @@ +languageId: javascriptreact +command: + version: 6 + spokenForm: change every value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + marks: {} +finalState: + documentContents: + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/jsx/chuckValue.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/jsx/chuckValue.yml new file mode 100644 index 0000000000..cf42f543fc --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/jsx/chuckValue.yml @@ -0,0 +1,23 @@ +languageId: javascriptreact +command: + version: 6 + spokenForm: chuck value + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + marks: {} +finalState: + documentContents: + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryName.yml new file mode 100644 index 0000000000..ac3806e7a6 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryName.yml @@ -0,0 +1,33 @@ +languageId: python +command: + version: 6 + spokenForm: change every name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + aaa = bbb + aaa: str = bbb + aaa: str + selections: + - anchor: {line: 2, character: 8} + active: {line: 2, character: 8} + marks: {} +finalState: + documentContents: |2- + = bbb + : str = bbb + : str + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + - anchor: {line: 2, character: 0} + active: {line: 2, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryName2.yml new file mode 100644 index 0000000000..0e49865560 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryName2.yml @@ -0,0 +1,31 @@ +languageId: python +command: + version: 6 + spokenForm: change every name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def aaa(): + bbb = ccc + ddd = eee + selections: + - anchor: {line: 2, character: 13} + active: {line: 2, character: 13} + marks: {} +finalState: + documentContents: |- + def aaa(): + = ccc + = eee + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryName3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryName3.yml new file mode 100644 index 0000000000..7080bda266 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryName3.yml @@ -0,0 +1,29 @@ +languageId: python +command: + version: 6 + spokenForm: change every name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def aaa(bbb, ccc: str): + pass + selections: + - anchor: {line: 0, character: 21} + active: {line: 0, character: 21} + marks: {} +finalState: + documentContents: |- + def aaa(, : str): + pass + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryType.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryType.yml new file mode 100644 index 0000000000..392c5070d0 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryType.yml @@ -0,0 +1,29 @@ +languageId: python +command: + version: 6 + spokenForm: change every type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def aaa(bbb, ccc: str, ddd: str): + pass + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + marks: {} +finalState: + documentContents: |- + def aaa(bbb, ccc: , ddd: ): + pass + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + - anchor: {line: 0, character: 25} + active: {line: 0, character: 25} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryType2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryType2.yml new file mode 100644 index 0000000000..bd66001275 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeEveryType2.yml @@ -0,0 +1,31 @@ +languageId: python +command: + version: 6 + spokenForm: change every type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def aaa(): + bbb: str + ccc: str = "hello" + selections: + - anchor: {line: 2, character: 22} + active: {line: 2, character: 22} + marks: {} +finalState: + documentContents: |- + def aaa(): + bbb: + ccc: = "hello" + selections: + - anchor: {line: 1, character: 9} + active: {line: 1, character: 9} + - anchor: {line: 2, character: 9} + active: {line: 2, character: 9} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeName.yml new file mode 100644 index 0000000000..2fea916c8c --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeName.yml @@ -0,0 +1,27 @@ +languageId: python +command: + version: 6 + spokenForm: change name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + for aaa in bbb: + pass + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: |- + for in bbb: + pass + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeName2.yml new file mode 100644 index 0000000000..8b7ec1fde0 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeName2.yml @@ -0,0 +1,21 @@ +languageId: python +command: + version: 6 + spokenForm: change name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + for aaa in bbb: + pass + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeState.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeState.yml new file mode 100644 index 0000000000..37b9aff44f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeState.yml @@ -0,0 +1,26 @@ +languageId: python +command: + version: 6 + spokenForm: change state + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: statement} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + match value: + case 0: + pass + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeType.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeType.yml new file mode 100644 index 0000000000..2dc0e88dde --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeType.yml @@ -0,0 +1,27 @@ +languageId: python +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def aaa(bbb, ccc: int): + pass + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + marks: {} +finalState: + documentContents: |- + def aaa(bbb, ccc: ): + pass + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeType2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeType2.yml new file mode 100644 index 0000000000..1508491688 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeType2.yml @@ -0,0 +1,21 @@ +languageId: python +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def aaa(bbb, ccc: int): + pass + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue.yml new file mode 100644 index 0000000000..927da9ad05 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue.yml @@ -0,0 +1,27 @@ +languageId: python +command: + version: 6 + spokenForm: change value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + if (x := 0) < 1: + pass + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: |- + if (x := ) < 1: + pass + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue2.yml new file mode 100644 index 0000000000..8c766129bc --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue2.yml @@ -0,0 +1,29 @@ +languageId: python +command: + version: 6 + spokenForm: change value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + match aaa: + case {"bbb": ccc}: + pass + selections: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} + marks: {} +finalState: + documentContents: |- + match aaa: + case {"bbb": }: + pass + selections: + - anchor: {line: 1, character: 17} + active: {line: 1, character: 17} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue3.yml new file mode 100644 index 0000000000..abebf29e89 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue3.yml @@ -0,0 +1,27 @@ +languageId: python +command: + version: 6 + spokenForm: change value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + for aaa in bbb: + pass + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: |- + for aaa in : + pass + selections: + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue4.yml new file mode 100644 index 0000000000..d2888bfc7f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/changeValue4.yml @@ -0,0 +1,21 @@ +languageId: python +command: + version: 6 + spokenForm: change value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + for aaa in bbb: + pass + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/chuckName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/chuckName.yml new file mode 100644 index 0000000000..06b090aa96 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/chuckName.yml @@ -0,0 +1,31 @@ +languageId: python +command: + version: 6 + spokenForm: chuck name + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + aaa = bbb + aaa: str = bbb + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + - anchor: {line: 1, character: 9} + active: {line: 1, character: 14} + marks: {} +finalState: + documentContents: |- + bbb + bbb + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 3} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/chuckValue4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/chuckValue4.yml new file mode 100644 index 0000000000..4841de5b97 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/chuckValue4.yml @@ -0,0 +1,27 @@ +languageId: python +command: + version: 6 + spokenForm: chuck value + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + if (x := 0) < 1: + pass + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: |- + if (x) < 1: + pass + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/chuckValue5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/chuckValue5.yml new file mode 100644 index 0000000000..6bd67dc98e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/chuckValue5.yml @@ -0,0 +1,29 @@ +languageId: python +command: + version: 6 + spokenForm: chuck value + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + match aaa: + case {"bbb": ccc}: + pass + selections: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} + marks: {} +finalState: + documentContents: |- + match aaa: + case {"bbb"}: + pass + selections: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString2.yml new file mode 100644 index 0000000000..5255d44a5d --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString2.yml @@ -0,0 +1,24 @@ +languageId: python +command: + version: 1 + spokenForm: take string + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: string} +spokenFormError: Scope type 'string' +initialState: + documentContents: | + + value = """hello world""" + selections: + - anchor: {line: 1, character: 17} + active: {line: 1, character: 17} + marks: {} +finalState: + documentContents: | + + value = """hello world""" + selections: + - anchor: {line: 1, character: 8} + active: {line: 1, character: 25} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString3.yml new file mode 100644 index 0000000000..2e95571eea --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString3.yml @@ -0,0 +1,24 @@ +languageId: python +command: + version: 1 + spokenForm: take string + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: string} +spokenFormError: Scope type 'string' +initialState: + documentContents: | + + value = r"hello world" + selections: + - anchor: {line: 1, character: 16} + active: {line: 1, character: 16} + marks: {} +finalState: + documentContents: | + + value = r"hello world" + selections: + - anchor: {line: 1, character: 8} + active: {line: 1, character: 22} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString4.yml new file mode 100644 index 0000000000..d562f3523a --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/python/takeString4.yml @@ -0,0 +1,26 @@ +languageId: python +command: + version: 1 + spokenForm: take string + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: string} +spokenFormError: Scope type 'string' +initialState: + documentContents: | + + w = "world" + value = f"hello {w}" + selections: + - anchor: {line: 2, character: 16} + active: {line: 2, character: 16} + marks: {} +finalState: + documentContents: | + + w = "world" + value = f"hello {w}" + selections: + - anchor: {line: 2, character: 8} + active: {line: 2, character: 20} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeEveryName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeEveryName.yml index b30a3a3753..b6c78bcf71 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeEveryName.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeEveryName.yml @@ -17,11 +17,11 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: (aaa) @ @ @ + documentContents: "(aaa) " selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} - anchor: {line: 0, character: 7} active: {line: 0, character: 7} - - anchor: {line: 0, character: 9} - active: {line: 0, character: 9} - - anchor: {line: 0, character: 11} - active: {line: 0, character: 11} + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName.yml index 13e3bcd9df..007b4a4c97 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName.yml @@ -17,7 +17,7 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: (aaa) @ + documentContents: "(aaa) " selections: - - anchor: {line: 0, character: 7} - active: {line: 0, character: 7} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName2.yml index 8d70cd725a..3cbfcd6846 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName2.yml @@ -17,7 +17,7 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: "eee: (aaa) @" + documentContents: "eee: (aaa) " selections: - - anchor: {line: 0, character: 12} - active: {line: 0, character: 12} + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName3.yml index 8406b97e0f..0c1497b6d4 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/changeName3.yml @@ -17,7 +17,7 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: "eee: _ @" + documentContents: "eee: _ " selections: - - anchor: {line: 0, character: 8} - active: {line: 0, character: 8} + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/chuckName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/chuckName.yml index 3a07b9fc75..110e686430 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/chuckName.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/chuckName.yml @@ -17,7 +17,7 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: "(aaa) " + documentContents: (aaa) selections: - anchor: {line: 0, character: 0} active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/chuckName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/chuckName2.yml index 48eca2ec19..3fc43eed23 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/chuckName2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/chuckName2.yml @@ -17,7 +17,7 @@ initialState: active: {line: 0, character: 20} marks: {} finalState: - documentContents: "(aaa) @bbb @ccc " + documentContents: (aaa) @bbb @ccc selections: - - anchor: {line: 0, character: 16} - active: {line: 0, character: 16} + - anchor: {line: 0, character: 15} + active: {line: 0, character: 15} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName.yml index 3d7ca3e4da..aec931039c 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName.yml @@ -16,9 +16,9 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: (aaa) @ @ + documentContents: "(aaa) " selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} - anchor: {line: 0, character: 7} active: {line: 0, character: 7} - - anchor: {line: 0, character: 9} - active: {line: 0, character: 9} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName2.yml index 05f69f2028..c5357a054f 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName2.yml @@ -16,9 +16,9 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: "[(aaa) (bbb)] @ @" + documentContents: "[(aaa) (bbb)] " selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} - anchor: {line: 0, character: 15} active: {line: 0, character: 15} - - anchor: {line: 0, character: 17} - active: {line: 0, character: 17} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName3.yml index 4929f597fa..9e7b369b8f 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearEveryName3.yml @@ -21,10 +21,10 @@ initialState: finalState: documentContents: |- (anonymous_node - name: (_) @ @ + name: (_) ) selections: + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} - anchor: {line: 1, character: 13} active: {line: 1, character: 13} - - anchor: {line: 1, character: 15} - active: {line: 1, character: 15} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName.yml index b9fb035b80..c256a34303 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName.yml @@ -16,7 +16,7 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: (aaa) @ + documentContents: "(aaa) " selections: - - anchor: {line: 0, character: 7} - active: {line: 0, character: 7} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName2.yml index 93d278995a..0d162e078d 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName2.yml @@ -16,7 +16,7 @@ initialState: active: {line: 0, character: 11} marks: {} finalState: - documentContents: (aaa) @bbb @ + documentContents: "(aaa) @bbb " selections: - - anchor: {line: 0, character: 12} - active: {line: 0, character: 12} + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName3.yml index 6483c5f1a4..b9d903fb6d 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName3.yml @@ -16,7 +16,7 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: (aaa) @ + documentContents: "(aaa) " selections: - - anchor: {line: 0, character: 7} - active: {line: 0, character: 7} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName4.yml index 5965b2f56f..027eced85e 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName4.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName4.yml @@ -16,7 +16,7 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: "\"aaa\" @" + documentContents: "\"aaa\" " selections: - - anchor: {line: 0, character: 7} - active: {line: 0, character: 7} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName5.yml index 2f239de12b..61cf415eb4 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName5.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName5.yml @@ -16,7 +16,7 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: "[(aaa) (bbb)] @" + documentContents: "[(aaa) (bbb)] " selections: - - anchor: {line: 0, character: 15} - active: {line: 0, character: 15} + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName6.yml index 47fc49c9ad..4c0da03687 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName6.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName6.yml @@ -23,8 +23,8 @@ finalState: documentContents: |- (anonymous_node - name: (_) @ + name: (_) ) selections: - - anchor: {line: 2, character: 13} - active: {line: 2, character: 13} + - anchor: {line: 2, character: 12} + active: {line: 2, character: 12} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName7.yml index 7f29ed6af7..e0b700e0db 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName7.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/clearName7.yml @@ -21,8 +21,8 @@ initialState: finalState: documentContents: |- (anonymous_node - name: (_) @ + name: (_) ) selections: - - anchor: {line: 1, character: 13} - active: {line: 1, character: 13} + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/drinkName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/drinkName.yml index f4501138fc..7116c6e725 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/drinkName.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/drinkName.yml @@ -17,7 +17,7 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: "eee: _ @ @bbb @ccc" + documentContents: "eee: _ @bbb @ccc" selections: - - anchor: {line: 0, character: 8} - active: {line: 0, character: 8} + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/pourName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/pourName.yml index 61df78276b..13901f9110 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/pourName.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/pourName.yml @@ -17,7 +17,7 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: "eee: _ @bbb @ccc @" + documentContents: "eee: _ @bbb @ccc " selections: - - anchor: {line: 0, character: 18} - active: {line: 0, character: 18} + - anchor: {line: 0, character: 17} + active: {line: 0, character: 17} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/pourName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/pourName2.yml index dccd953997..607c1ecb5e 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/pourName2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/pourName2.yml @@ -17,7 +17,7 @@ initialState: active: {line: 0, character: 7} marks: {} finalState: - documentContents: "eee: _ @bbb @ @ccc" + documentContents: "eee: _ @bbb @ccc" selections: - - anchor: {line: 0, character: 13} - active: {line: 0, character: 13} + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/takeName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/takeName.yml index 6c7c8a477d..6b089e2645 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/takeName.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/scm/takeName.yml @@ -19,5 +19,5 @@ initialState: finalState: documentContents: (aaa) @bbb @ccc @ddd selections: - - anchor: {line: 0, character: 7} + - anchor: {line: 0, character: 6} active: {line: 0, character: 20} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryType.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryType.yml new file mode 100644 index 0000000000..22dce551ce --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryType.yml @@ -0,0 +1,29 @@ +languageId: typescript +command: + version: 6 + spokenForm: change every type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa: number = 0; + const bbb: number = 0; + selections: + - anchor: {line: 1, character: 22} + active: {line: 1, character: 22} + marks: {} +finalState: + documentContents: |- + const aaa: = 0; + const bbb: = 0; + selections: + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} + - anchor: {line: 1, character: 11} + active: {line: 1, character: 11} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryType2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryType2.yml new file mode 100644 index 0000000000..ea58ee0508 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryType2.yml @@ -0,0 +1,33 @@ +languageId: typescript +command: + version: 6 + spokenForm: change every type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + function ccc() { + const aaa: number = 0; + const bbb: number = 0; + } + selections: + - anchor: {line: 2, character: 26} + active: {line: 2, character: 26} + marks: {} +finalState: + documentContents: |- + function ccc() { + const aaa: = 0; + const bbb: = 0; + } + selections: + - anchor: {line: 1, character: 15} + active: {line: 1, character: 15} + - anchor: {line: 2, character: 15} + active: {line: 2, character: 15} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryType3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryType3.yml new file mode 100644 index 0000000000..3aa8f6544f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryType3.yml @@ -0,0 +1,25 @@ +languageId: typescript +command: + version: 6 + spokenForm: change every type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: "function ccc(aaa: string, bbb: string) {}" + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + marks: {} +finalState: + documentContents: "function ccc(aaa: , bbb: ) {}" + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + - anchor: {line: 0, character: 25} + active: {line: 0, character: 25} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeKey.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeKey.yml new file mode 100644 index 0000000000..cd395f2c49 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeKey.yml @@ -0,0 +1,29 @@ +languageId: typescript +command: + version: 6 + spokenForm: change key + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionKey} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + interface Hello { + value: number; + } + selections: + - anchor: {line: 1, character: 17} + active: {line: 1, character: 17} + marks: {} +finalState: + documentContents: |- + interface Hello { + : number; + } + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeKey2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeKey2.yml new file mode 100644 index 0000000000..fd692b5be8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeKey2.yml @@ -0,0 +1,29 @@ +languageId: typescript +command: + version: 6 + spokenForm: change key + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionKey} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + type Hello = { + value: number; + } + selections: + - anchor: {line: 1, character: 17} + active: {line: 1, character: 17} + marks: {} +finalState: + documentContents: |- + type Hello = { + : number; + } + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeKey3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeKey3.yml new file mode 100644 index 0000000000..18ac6d37e8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeKey3.yml @@ -0,0 +1,33 @@ +languageId: typescript +command: + version: 6 + spokenForm: change key + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionKey} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + function funk(): { + value: number; + } { + return { value: 2 } + } + selections: + - anchor: {line: 1, character: 17} + active: {line: 1, character: 17} + marks: {} +finalState: + documentContents: |- + function funk(): { + : number; + } { + return { value: 2 } + } + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeNextValue.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeNextValue.yml new file mode 100644 index 0000000000..3abb6a3554 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeNextValue.yml @@ -0,0 +1,30 @@ +languageId: typescript +command: + version: 6 + spokenForm: change next value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: value} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa = 0; + const bbb = 0; + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: |- + const aaa = 0; + const bbb = ; + selections: + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType.yml new file mode 100644 index 0000000000..40df97954d --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType.yml @@ -0,0 +1,23 @@ +languageId: typescript +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: const value = 2 as number; + selections: + - anchor: {line: 0, character: 19} + active: {line: 0, character: 19} + marks: {} +finalState: + documentContents: const value = 2 as ; + selections: + - anchor: {line: 0, character: 19} + active: {line: 0, character: 19} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType2.yml new file mode 100644 index 0000000000..2b97d0a6f6 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType2.yml @@ -0,0 +1,23 @@ +languageId: typescript +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: const map = {} as Record; + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + marks: {} +finalState: + documentContents: const map = {} as ; + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType3.yml new file mode 100644 index 0000000000..43b9687d48 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType3.yml @@ -0,0 +1,23 @@ +languageId: typescript +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: const value = 2 as const satisfies number; + selections: + - anchor: {line: 0, character: 35} + active: {line: 0, character: 35} + marks: {} +finalState: + documentContents: const value = 2 as const satisfies ; + selections: + - anchor: {line: 0, character: 35} + active: {line: 0, character: 35} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType4.yml new file mode 100644 index 0000000000..b08e288628 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType4.yml @@ -0,0 +1,23 @@ +languageId: typescript +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: const meta = {} as const satisfies Record; + selections: + - anchor: {line: 0, character: 35} + active: {line: 0, character: 35} + marks: {} +finalState: + documentContents: const meta = {} as const satisfies ; + selections: + - anchor: {line: 0, character: 35} + active: {line: 0, character: 35} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType5.yml new file mode 100644 index 0000000000..da503d66e9 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType5.yml @@ -0,0 +1,23 @@ +languageId: typescript +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: const value = 2; + selections: + - anchor: {line: 0, character: 15} + active: {line: 0, character: 15} + marks: {} +finalState: + documentContents: const value = <>2; + selections: + - anchor: {line: 0, character: 15} + active: {line: 0, character: 15} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType6.yml new file mode 100644 index 0000000000..0c204f6ca3 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType6.yml @@ -0,0 +1,23 @@ +languageId: typescript +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: const map = >{} + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + marks: {} +finalState: + documentContents: const map = <>{} + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType7.yml new file mode 100644 index 0000000000..4209ffa5d4 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType7.yml @@ -0,0 +1,113 @@ +languageId: typescript +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa: number = 0; + let bbb: number = 0; + var hhh: number = 0; + const ddd: number = 0, eee: number = 0; + let fff: number = 0, ggg: number = 0; + var iii: number = 0; + export const jjj: number = 0; + export let kkk: number = 0; + export var lll: number = 0; + export const mmm: number = 0, nnn: number = 0; + export let ooo: number = 0, ppp: number = 0; + let qqq: number; + var rrr: number; + let sss: number, ttt: number; + selections: + - anchor: {line: 0, character: 22} + active: {line: 0, character: 22} + - anchor: {line: 1, character: 20} + active: {line: 1, character: 20} + - anchor: {line: 2, character: 20} + active: {line: 2, character: 20} + - anchor: {line: 3, character: 39} + active: {line: 3, character: 39} + - anchor: {line: 4, character: 37} + active: {line: 4, character: 37} + - anchor: {line: 5, character: 20} + active: {line: 5, character: 20} + - anchor: {line: 6, character: 29} + active: {line: 6, character: 29} + - anchor: {line: 7, character: 27} + active: {line: 7, character: 27} + - anchor: {line: 8, character: 27} + active: {line: 8, character: 27} + - anchor: {line: 9, character: 46} + active: {line: 9, character: 46} + - anchor: {line: 10, character: 44} + active: {line: 10, character: 44} + - anchor: {line: 11, character: 16} + active: {line: 11, character: 16} + - anchor: {line: 12, character: 16} + active: {line: 12, character: 16} + - anchor: {line: 13, character: 29} + active: {line: 13, character: 29} + marks: {} +finalState: + documentContents: |- + const aaa: = 0; + let bbb: = 0; + var hhh: = 0; + const ddd: = 0, eee: = 0; + let fff: = 0, ggg: = 0; + var iii: = 0; + export const jjj: = 0; + export let kkk: = 0; + export var lll: = 0; + export const mmm: = 0, nnn: = 0; + export let ooo: = 0, ppp: = 0; + let qqq: ; + var rrr: ; + let sss: , ttt: ; + selections: + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} + - anchor: {line: 1, character: 9} + active: {line: 1, character: 9} + - anchor: {line: 2, character: 9} + active: {line: 2, character: 9} + - anchor: {line: 3, character: 11} + active: {line: 3, character: 11} + - anchor: {line: 3, character: 22} + active: {line: 3, character: 22} + - anchor: {line: 4, character: 9} + active: {line: 4, character: 9} + - anchor: {line: 4, character: 20} + active: {line: 4, character: 20} + - anchor: {line: 5, character: 9} + active: {line: 5, character: 9} + - anchor: {line: 6, character: 18} + active: {line: 6, character: 18} + - anchor: {line: 7, character: 16} + active: {line: 7, character: 16} + - anchor: {line: 8, character: 16} + active: {line: 8, character: 16} + - anchor: {line: 9, character: 18} + active: {line: 9, character: 18} + - anchor: {line: 9, character: 29} + active: {line: 9, character: 29} + - anchor: {line: 10, character: 16} + active: {line: 10, character: 16} + - anchor: {line: 10, character: 27} + active: {line: 10, character: 27} + - anchor: {line: 11, character: 9} + active: {line: 11, character: 9} + - anchor: {line: 12, character: 9} + active: {line: 12, character: 9} + - anchor: {line: 13, character: 9} + active: {line: 13, character: 9} + - anchor: {line: 13, character: 16} + active: {line: 13, character: 16} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType8.yml new file mode 100644 index 0000000000..bceb2cd1ff --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeType8.yml @@ -0,0 +1,43 @@ +languageId: typescript +command: + version: 6 + spokenForm: change type + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const ddd: number = 0, eee: number = 0; + let fff: number = 0, ggg: number = 0; + export const mmm: number = 0, nnn: number = 0; + export let ooo: number = 0, ppp: number = 0; + selections: + - anchor: {line: 0, character: 38} + active: {line: 0, character: 38} + - anchor: {line: 1, character: 36} + active: {line: 1, character: 36} + - anchor: {line: 2, character: 45} + active: {line: 2, character: 45} + - anchor: {line: 3, character: 43} + active: {line: 3, character: 43} + marks: {} +finalState: + documentContents: |- + const ddd: number = 0, eee: = 0; + let fff: number = 0, ggg: = 0; + export const mmm: number = 0, nnn: = 0; + export let ooo: number = 0, ppp: = 0; + selections: + - anchor: {line: 0, character: 28} + active: {line: 0, character: 28} + - anchor: {line: 1, character: 26} + active: {line: 1, character: 26} + - anchor: {line: 2, character: 35} + active: {line: 2, character: 35} + - anchor: {line: 3, character: 33} + active: {line: 3, character: 33} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckName.yml new file mode 100644 index 0000000000..98d3b3a125 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckName.yml @@ -0,0 +1,55 @@ +languageId: typescript +command: + version: 6 + spokenForm: chuck name + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: name} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa: number = 0; + let bbb: number = 0; + var hhh: number = 0; + const ddd: number = 0, eee: number = 0; + let fff: number = 0, ggg: number = 0; + var iii: number = 0; + selections: + - anchor: {line: 0, character: 22} + active: {line: 0, character: 22} + - anchor: {line: 1, character: 20} + active: {line: 1, character: 20} + - anchor: {line: 2, character: 20} + active: {line: 2, character: 20} + - anchor: {line: 3, character: 39} + active: {line: 3, character: 39} + - anchor: {line: 4, character: 37} + active: {line: 4, character: 37} + - anchor: {line: 5, character: 20} + active: {line: 5, character: 20} + marks: {} +finalState: + documentContents: |- + 0; + 0; + 0; + 0, 0; + 0, 0; + 0; + selections: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + - anchor: {line: 2, character: 2} + active: {line: 2, character: 2} + - anchor: {line: 3, character: 5} + active: {line: 3, character: 5} + - anchor: {line: 4, character: 5} + active: {line: 4, character: 5} + - anchor: {line: 5, character: 2} + active: {line: 5, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType.yml new file mode 100644 index 0000000000..b5384c9514 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType.yml @@ -0,0 +1,103 @@ +languageId: typescript +command: + version: 6 + spokenForm: chuck type + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa: number = 0; + let bbb: number = 0; + var hhh: number = 0; + const ddd: number = 0, eee: number = 0; + let fff: number = 0, ggg: number = 0; + var iii: number = 0; + export const jjj: number = 0; + export let kkk: number = 0; + export var lll: number = 0; + export const mmm: number = 0, nnn: number = 0; + export let ooo: number = 0, ppp: number = 0; + let qqq: number; + var rrr: number; + let sss: number, ttt: number; + selections: + - anchor: {line: 0, character: 22} + active: {line: 0, character: 22} + - anchor: {line: 1, character: 20} + active: {line: 1, character: 20} + - anchor: {line: 2, character: 20} + active: {line: 2, character: 20} + - anchor: {line: 3, character: 39} + active: {line: 3, character: 39} + - anchor: {line: 4, character: 37} + active: {line: 4, character: 37} + - anchor: {line: 5, character: 20} + active: {line: 5, character: 20} + - anchor: {line: 6, character: 29} + active: {line: 6, character: 29} + - anchor: {line: 7, character: 27} + active: {line: 7, character: 27} + - anchor: {line: 8, character: 27} + active: {line: 8, character: 27} + - anchor: {line: 9, character: 46} + active: {line: 9, character: 46} + - anchor: {line: 10, character: 44} + active: {line: 10, character: 44} + - anchor: {line: 11, character: 16} + active: {line: 11, character: 16} + - anchor: {line: 12, character: 16} + active: {line: 12, character: 16} + - anchor: {line: 13, character: 29} + active: {line: 13, character: 29} + marks: {} +finalState: + documentContents: |- + const aaa = 0; + let bbb = 0; + var hhh = 0; + const ddd = 0, eee = 0; + let fff = 0, ggg = 0; + var iii = 0; + export const jjj = 0; + export let kkk = 0; + export var lll = 0; + export const mmm = 0, nnn = 0; + export let ooo = 0, ppp = 0; + let qqq; + var rrr; + let sss, ttt; + selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} + - anchor: {line: 2, character: 12} + active: {line: 2, character: 12} + - anchor: {line: 3, character: 23} + active: {line: 3, character: 23} + - anchor: {line: 4, character: 21} + active: {line: 4, character: 21} + - anchor: {line: 5, character: 12} + active: {line: 5, character: 12} + - anchor: {line: 6, character: 21} + active: {line: 6, character: 21} + - anchor: {line: 7, character: 19} + active: {line: 7, character: 19} + - anchor: {line: 8, character: 19} + active: {line: 8, character: 19} + - anchor: {line: 9, character: 30} + active: {line: 9, character: 30} + - anchor: {line: 10, character: 28} + active: {line: 10, character: 28} + - anchor: {line: 11, character: 8} + active: {line: 11, character: 8} + - anchor: {line: 12, character: 8} + active: {line: 12, character: 8} + - anchor: {line: 13, character: 13} + active: {line: 13, character: 13} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType2.yml new file mode 100644 index 0000000000..bf4a234195 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType2.yml @@ -0,0 +1,43 @@ +languageId: typescript +command: + version: 6 + spokenForm: chuck type + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const ddd: number = 0, eee: number = 0; + let fff: number = 0, ggg: number = 0; + export const mmm: number = 0, nnn: number = 0; + export let ooo: number = 0, ppp: number = 0; + selections: + - anchor: {line: 0, character: 38} + active: {line: 0, character: 38} + - anchor: {line: 1, character: 36} + active: {line: 1, character: 36} + - anchor: {line: 2, character: 45} + active: {line: 2, character: 45} + - anchor: {line: 3, character: 43} + active: {line: 3, character: 43} + marks: {} +finalState: + documentContents: |- + const ddd: number = 0, eee = 0; + let fff: number = 0, ggg = 0; + export const mmm: number = 0, nnn = 0; + export let ooo: number = 0, ppp = 0; + selections: + - anchor: {line: 0, character: 30} + active: {line: 0, character: 30} + - anchor: {line: 1, character: 28} + active: {line: 1, character: 28} + - anchor: {line: 2, character: 37} + active: {line: 2, character: 37} + - anchor: {line: 3, character: 35} + active: {line: 3, character: 35} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType3.yml new file mode 100644 index 0000000000..98c66b1bdf --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType3.yml @@ -0,0 +1,23 @@ +languageId: typescript +command: + version: 6 + spokenForm: chuck type + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: const aaa = bbb; + selections: + - anchor: {line: 0, character: 23} + active: {line: 0, character: 23} + marks: {} +finalState: + documentContents: const aaa = bbb; + selections: + - anchor: {line: 0, character: 15} + active: {line: 0, character: 15} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType4.yml new file mode 100644 index 0000000000..353fae2e9f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckType4.yml @@ -0,0 +1,31 @@ +languageId: typescript +command: + version: 6 + spokenForm: chuck type + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: type} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa = bbb as string; + const ccc = ddd satisfies string; + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} + marks: {} +finalState: + documentContents: |- + const aaa = bbb; + const ccc = ddd; + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckValue2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckValue2.yml new file mode 100644 index 0000000000..d48d3e78e4 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckValue2.yml @@ -0,0 +1,85 @@ +languageId: typescript +command: + version: 6 + spokenForm: chuck value + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const aaa: number = 0; + let bbb: number = 0; + var hhh: number = 0; + const ddd: number = 0, eee: number = 0; + let fff: number = 0, ggg: number = 0; + var iii: number = 0; + export const jjj: number = 0; + export let kkk: number = 0; + export var lll: number = 0; + export const mmm: number = 0, nnn: number = 0; + export let ooo: number = 0, ppp: number = 0; + selections: + - anchor: {line: 0, character: 22} + active: {line: 0, character: 22} + - anchor: {line: 1, character: 20} + active: {line: 1, character: 20} + - anchor: {line: 2, character: 20} + active: {line: 2, character: 20} + - anchor: {line: 3, character: 39} + active: {line: 3, character: 39} + - anchor: {line: 4, character: 37} + active: {line: 4, character: 37} + - anchor: {line: 5, character: 20} + active: {line: 5, character: 20} + - anchor: {line: 6, character: 29} + active: {line: 6, character: 29} + - anchor: {line: 7, character: 27} + active: {line: 7, character: 27} + - anchor: {line: 8, character: 27} + active: {line: 8, character: 27} + - anchor: {line: 9, character: 46} + active: {line: 9, character: 46} + - anchor: {line: 10, character: 44} + active: {line: 10, character: 44} + marks: {} +finalState: + documentContents: |- + const aaa: number; + let bbb: number; + var hhh: number; + const ddd: number, eee: number; + let fff: number, ggg: number; + var iii: number; + export const jjj: number; + export let kkk: number; + export var lll: number; + export const mmm: number, nnn: number; + export let ooo: number, ppp: number; + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + - anchor: {line: 1, character: 16} + active: {line: 1, character: 16} + - anchor: {line: 2, character: 16} + active: {line: 2, character: 16} + - anchor: {line: 3, character: 31} + active: {line: 3, character: 31} + - anchor: {line: 4, character: 29} + active: {line: 4, character: 29} + - anchor: {line: 5, character: 16} + active: {line: 5, character: 16} + - anchor: {line: 6, character: 25} + active: {line: 6, character: 25} + - anchor: {line: 7, character: 23} + active: {line: 7, character: 23} + - anchor: {line: 8, character: 23} + active: {line: 8, character: 23} + - anchor: {line: 9, character: 38} + active: {line: 9, character: 38} + - anchor: {line: 10, character: 36} + active: {line: 10, character: 36} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckValue3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckValue3.yml new file mode 100644 index 0000000000..a282ba6508 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/chuckValue3.yml @@ -0,0 +1,43 @@ +languageId: typescript +command: + version: 6 + spokenForm: chuck value + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + const ddd: number = 0, eee: number = 0; + let fff: number = 0, ggg: number = 0; + export const mmm: number = 0, nnn: number = 0; + export let ooo: number = 0, ppp: number = 0; + selections: + - anchor: {line: 0, character: 38} + active: {line: 0, character: 38} + - anchor: {line: 1, character: 36} + active: {line: 1, character: 36} + - anchor: {line: 2, character: 45} + active: {line: 2, character: 45} + - anchor: {line: 3, character: 43} + active: {line: 3, character: 43} + marks: {} +finalState: + documentContents: |- + const ddd: number = 0, eee: number; + let fff: number = 0, ggg: number; + export const mmm: number = 0, nnn: number; + export let ooo: number = 0, ppp: number; + selections: + - anchor: {line: 0, character: 34} + active: {line: 0, character: 34} + - anchor: {line: 1, character: 32} + active: {line: 1, character: 32} + - anchor: {line: 2, character: 41} + active: {line: 2, character: 41} + - anchor: {line: 3, character: 39} + active: {line: 3, character: 39} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString.yml new file mode 100644 index 0000000000..0d5e90c5a8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString.yml @@ -0,0 +1,23 @@ +languageId: python +command: + version: 6 + spokenForm: change string + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: surroundingPair, delimiter: string} + usePrePhraseSnapshot: true +initialState: + documentContents: "\"\"\"hello\"\"\"" + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString3.yml new file mode 100644 index 0000000000..174464de8f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString3.yml @@ -0,0 +1,23 @@ +languageId: python +command: + version: 6 + spokenForm: change string + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: surroundingPair, delimiter: string} + usePrePhraseSnapshot: true +initialState: + documentContents: "\"\"\"aaa\"\"\"" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString4.yml new file mode 100644 index 0000000000..8a9fa441fb --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString4.yml @@ -0,0 +1,23 @@ +languageId: python +command: + version: 6 + spokenForm: change string + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: surroundingPair, delimiter: string} + usePrePhraseSnapshot: false +initialState: + documentContents: "\"\"\"aaa\"\"\"" + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString5.yml new file mode 100644 index 0000000000..f39b27b5dd --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString5.yml @@ -0,0 +1,23 @@ +languageId: python +command: + version: 6 + spokenForm: change string + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: surroundingPair, delimiter: string} + usePrePhraseSnapshot: false +initialState: + documentContents: f"""aaa""" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString6.yml new file mode 100644 index 0000000000..413c7e322c --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString6.yml @@ -0,0 +1,23 @@ +languageId: python +command: + version: 6 + spokenForm: change string + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: surroundingPair, delimiter: string} + usePrePhraseSnapshot: false +initialState: + documentContents: "'''aaa'''" + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString7.yml new file mode 100644 index 0000000000..71d0220713 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString7.yml @@ -0,0 +1,23 @@ +languageId: python +command: + version: 6 + spokenForm: change string + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: surroundingPair, delimiter: string} + usePrePhraseSnapshot: false +initialState: + documentContents: "'aaa'" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString8.yml new file mode 100644 index 0000000000..ccadce753b --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/surroundingPair/parseTree/python/changeString8.yml @@ -0,0 +1,23 @@ +languageId: python +command: + version: 6 + spokenForm: change string + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: surroundingPair, delimiter: string} + usePrePhraseSnapshot: false +initialState: + documentContents: "'aaa'" + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts index db2f0658e3..62ad7e84c1 100644 --- a/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts @@ -68,7 +68,7 @@ async function runTest(file: string, spyIde: SpyIDE) { const fixture = yaml.load(buffer.toString()) as TestCaseFixtureLegacy; const excludeFields: ExcludableSnapshotField[] = []; - // TODO The snapshot gets messed up with timing issues when running the recorded tests + // FIXME The snapshot gets messed up with timing issues when running the recorded tests // "Couldn't find token default.a" const usePrePhraseSnapshot = false; @@ -180,7 +180,7 @@ async function runTest(file: string, spyIde: SpyIDE) { excludeFields.push("instanceReferenceMark"); } - // TODO Visible ranges are not asserted, see: + // FIXME Visible ranges are not asserted, see: // https://github.com/cursorless-dev/cursorless/issues/160 const { visibleRanges, ...resultState } = await takeSnapshot( excludeFields, diff --git a/packages/cursorless-vscode/package.json b/packages/cursorless-vscode/package.json index b011aff760..6257845faf 100644 --- a/packages/cursorless-vscode/package.json +++ b/packages/cursorless-vscode/package.json @@ -20,7 +20,7 @@ "description": "Tests" } ], - "version": "0.27.0", + "version": "0.28.0", "publisher": "pokey", "license": "MIT", "repository": { @@ -123,7 +123,7 @@ }, { "command": "cursorless.internal.updateCheatsheetDefaults", - "title": "Cursorless: Update the default values of the cheatsheet payload used on the website and for local development. Be sure to run this on stock knausj and cursorless.", + "title": "Cursorless: Update the default values of the cheatsheet payload used on the website and for local development. Be sure to run this on stock community and cursorless.", "enablement": "false" }, { @@ -838,7 +838,7 @@ } }, "cursorless.experimental.snippetsDir": { - "description": "Directory containing snippets for use in cursorless", + "description": "Directory containing snippets for use in Cursorless", "type": "string" }, "cursorless.experimental.keyboard.modal.keybindings.actions": { diff --git a/packages/cursorless-vscode/src/ide/vscode/VscodeFocusEditor.ts b/packages/cursorless-vscode/src/ide/vscode/VscodeFocusEditor.ts index e336a0fbde..fdc847274a 100644 --- a/packages/cursorless-vscode/src/ide/vscode/VscodeFocusEditor.ts +++ b/packages/cursorless-vscode/src/ide/vscode/VscodeFocusEditor.ts @@ -53,7 +53,7 @@ function getViewColumn(editor: TextEditor): ViewColumn | undefined { if (editor.viewColumn != null) { return editor.viewColumn; } - // TODO: tabGroups is not available on older versions of vscode we still support. + // FIXME: tabGroups is not available on older versions of vscode we still support. // Remove any cast as soon as version is updated. if (semver.lt(version, "1.67.0")) { return undefined; diff --git a/packages/cursorless-vscode/src/ide/vscode/VscodeIDE.ts b/packages/cursorless-vscode/src/ide/vscode/VscodeIDE.ts index 761bfb5fd9..5993911c96 100644 --- a/packages/cursorless-vscode/src/ide/vscode/VscodeIDE.ts +++ b/packages/cursorless-vscode/src/ide/vscode/VscodeIDE.ts @@ -6,6 +6,7 @@ import { HighlightId, IDE, InputBoxOptions, + OpenUntitledTextDocumentOptions, OutdatedExtensionError, QuickPickOptions, RunMode, @@ -19,7 +20,7 @@ import { import { pull } from "lodash"; import { v4 as uuid } from "uuid"; import * as vscode from "vscode"; -import { ExtensionContext, window, workspace, WorkspaceFolder } from "vscode"; +import { ExtensionContext, WorkspaceFolder, window, workspace } from "vscode"; import { VscodeCapabilities } from "./VscodeCapabilities"; import VscodeClipboard from "./VscodeClipboard"; import VscodeConfiguration from "./VscodeConfiguration"; @@ -29,9 +30,9 @@ import VscodeGlobalState from "./VscodeGlobalState"; import VscodeHighlights, { HighlightStyle } from "./VscodeHighlights"; import VscodeMessages from "./VscodeMessages"; import { vscodeRunMode } from "./VscodeRunMode"; -import { vscodeShowQuickPick } from "./vscodeShowQuickPick"; import { VscodeTextDocumentImpl } from "./VscodeTextDocumentImpl"; import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; +import { vscodeShowQuickPick } from "./vscodeShowQuickPick"; export class VscodeIDE implements IDE { readonly configuration: VscodeConfiguration; @@ -126,6 +127,13 @@ export class VscodeIDE implements IDE { return this.fromVscodeEditor(await window.showTextDocument(textDocument)); } + public async openUntitledTextDocument( + options?: OpenUntitledTextDocumentOptions, + ): Promise { + const textDocument = await workspace.openTextDocument(options); + return this.fromVscodeEditor(await window.showTextDocument(textDocument)); + } + public async showInputBox( options?: InputBoxOptions, ): Promise { diff --git a/packages/cursorless-vscode/src/ide/vscode/hats/VscodeHatRenderer.ts b/packages/cursorless-vscode/src/ide/vscode/hats/VscodeHatRenderer.ts index 315f5ee6c9..9159998e64 100644 --- a/packages/cursorless-vscode/src/ide/vscode/hats/VscodeHatRenderer.ts +++ b/packages/cursorless-vscode/src/ide/vscode/hats/VscodeHatRenderer.ts @@ -1,20 +1,27 @@ -import { readFileSync } from "fs"; +import { + Listener, + Notifier, + PathChangeListener, + walkFiles, +} from "@cursorless/common"; import { cloneDeep, isEqual } from "lodash"; -import { join } from "path"; +import * as fs from "node:fs"; +import * as path from "node:path"; import * as vscode from "vscode"; +import VscodeEnabledHatStyleManager, { + ExtendedHatStyleMap, +} from "../VscodeEnabledHatStyleManager"; +import { HAT_SHAPES, HatShape, VscodeHatStyleName } from "../hatStyles.types"; +import { FontMeasurements } from "./FontMeasurements"; import getHatThemeColors from "./getHatThemeColors"; import { - defaultShapeAdjustments, DEFAULT_HAT_HEIGHT_EM, DEFAULT_VERTICAL_OFFSET_EM, IndividualHatAdjustmentMap, + defaultShapeAdjustments, } from "./shapeAdjustments"; -import { Listener, Notifier } from "@cursorless/common"; -import { FontMeasurements } from "./FontMeasurements"; -import { HatShape, HAT_SHAPES, VscodeHatStyleName } from "../hatStyles.types"; -import VscodeEnabledHatStyleManager, { - ExtendedHatStyleMap, -} from "../VscodeEnabledHatStyleManager"; + +const CURSORLESS_HAT_SHAPES_SUFFIX = ".svg"; type HatDecorationMap = Partial< Record @@ -39,11 +46,24 @@ const hatConfigSections = [ * hats. The decision about which hat styles should be available is up to * {@link VscodeEnabledHatStyles} */ + +const SETTING_SECTION_HAT_SHAPES_DIR = "cursorless.private"; +const SETTING_NAME_HAT_SHAPES_DIR = "hatShapesDir"; +const hatShapesDirSettingId = `${SETTING_SECTION_HAT_SHAPES_DIR}.${SETTING_NAME_HAT_SHAPES_DIR}`; + +interface SvgInfo { + svg: string; + svgHeightPx: number; + svgWidthPx: number; +} + export default class VscodeHatRenderer { private decorationMap!: HatDecorationMap; private disposables: vscode.Disposable[] = []; private notifier: Notifier<[]> = new Notifier(); private lastSeenEnabledHatStyles: ExtendedHatStyleMap = {}; + private hatsDirWatcherDisposable?: vscode.Disposable; + private hatShapeOverrides: Record = {}; constructor( private extensionContext: vscode.ExtensionContext, @@ -57,7 +77,9 @@ export default class VscodeHatRenderer { this.disposables.push( vscode.workspace.onDidChangeConfiguration( async ({ affectsConfiguration }) => { - if ( + if (affectsConfiguration(hatShapesDirSettingId)) { + await this.updateHatsDirWatcher(); + } else if ( hatConfigSections.some((section) => affectsConfiguration(section)) ) { await this.recomputeDecorations(); @@ -88,6 +110,7 @@ export default class VscodeHatRenderer { async init() { await this.constructDecorations(); + await this.updateHatsDirWatcher(); } /** @@ -99,6 +122,52 @@ export default class VscodeHatRenderer { return this.decorationMap[hatStyle]; } + private async updateHatsDirWatcher() { + this.hatsDirWatcherDisposable?.dispose(); + + const hatsDir = vscode.workspace + .getConfiguration(SETTING_SECTION_HAT_SHAPES_DIR) + .get(SETTING_NAME_HAT_SHAPES_DIR)!; + + if (hatsDir) { + await this.updateShapeOverrides(hatsDir); + + if (fs.existsSync(hatsDir)) { + this.hatsDirWatcherDisposable = watchDir(hatsDir, () => + this.updateShapeOverrides(hatsDir), + ); + } + } else { + this.hatShapeOverrides = {}; + await this.recomputeDecorations(); + } + } + + private async updateShapeOverrides(hatShapesDir: string) { + this.hatShapeOverrides = {}; + const files = await this.getHatShapePaths(hatShapesDir); + + for (const file of files) { + const name = path.basename(file, CURSORLESS_HAT_SHAPES_SUFFIX); + this.hatShapeOverrides[name] = file; + } + + await this.recomputeDecorations(); + } + + private async getHatShapePaths(hatShapesDir: string) { + try { + return await walkFiles(hatShapesDir, CURSORLESS_HAT_SHAPES_SUFFIX); + } catch (error) { + void vscode.window.showErrorMessage( + `Error with cursorless hat shapes dir "${hatShapesDir}": ${ + (error as Error).message + }`, + ); + return []; + } + } + private destroyDecorations() { Object.values(this.decorationMap).forEach((decoration) => { decoration.dispose(); @@ -160,7 +229,16 @@ export default class VscodeHatRenderer { this.decorationMap = Object.fromEntries( Object.entries(this.enabledHatStyles.hatStyleMap).map( ([styleName, { color, shape }]) => { - const { svg, svgWidthPx, svgHeightPx } = hatSvgMap[shape]; + const svgInfo = hatSvgMap[shape]; + + if (svgInfo == null) { + return [ + styleName, + vscode.window.createTextEditorDecorationType({}), + ]; + } + + const { svg, svgWidthPx, svgHeightPx } = svgInfo; const { light, dark } = getHatThemeColors(color); @@ -194,17 +272,36 @@ export default class VscodeHatRenderer { ); } - private constructColoredSvgDataUri(originalSvg: string, color: string) { + private checkSvg(shape: HatShape, svg: string) { + let isOk = true; + if ( - originalSvg.match(/fill="[^"]+"/) == null && - originalSvg.match(/fill:[^;]+;/) == null + svg.match(/fill="(?!none)[^"]+"/) == null && + svg.match(/fill:(?!none)[^;]+;/) == null ) { - throw Error("Raw svg doesn't have fill"); + vscode.window.showErrorMessage( + `Raw svg '${shape}' is missing 'fill' property`, + ); + isOk = false; } + const viewBoxMatch = svg.match(/viewBox="([^"]+)"/); + + if (viewBoxMatch == null) { + vscode.window.showErrorMessage( + `Raw svg '${shape}' is missing 'viewBox' property`, + ); + isOk = false; + } + + return isOk; + } + + private constructColoredSvgDataUri(originalSvg: string, color: string) { const svg = originalSvg - .replace(/fill="[^"]+"/, `fill="${color}"`) - .replace(/fill:[^;]+;/, `fill:${color};`); + .replace(/fill="(?!none)[^"]+"/g, `fill="${color}"`) + .replace(/fill:(?!none)[^;]+;/g, `fill:${color};`) + .replace(/\r?\n/g, " "); const encoded = encodeURIComponent(svg); @@ -227,16 +324,22 @@ export default class VscodeHatRenderer { shape: HatShape, scaleFactor: number, hatVerticalOffsetEm: number, - ) { - const iconPath = join( - this.extensionContext.extensionPath, - "images", - "hats", - `${shape}.svg`, - ); - const rawSvg = readFileSync(iconPath, "utf8"); + ): SvgInfo | null { + const iconPath = + this.hatShapeOverrides[shape] ?? + path.join( + this.extensionContext.extensionPath, + "images", + "hats", + `${shape}.svg`, + ); + const rawSvg = fs.readFileSync(iconPath, "utf8"); const { characterWidth, characterHeight, fontSize } = fontMeasurements; + if (!this.checkSvg(shape, rawSvg)) { + return null; + } + const { originalViewBoxHeight, originalViewBoxWidth } = this.getViewBoxDimensions(rawSvg); @@ -289,10 +392,7 @@ export default class VscodeHatRenderer { } private getViewBoxDimensions(rawSvg: string) { - const viewBoxMatch = rawSvg.match(/viewBox="([^"]+)"/); - if (viewBoxMatch == null) { - throw Error("View box not found in svg"); - } + const viewBoxMatch = rawSvg.match(/viewBox="([^"]+)"/)!; const originalViewBoxString = viewBoxMatch[1]; const [_0, _1, originalViewBoxWidthStr, originalViewBoxHeightStr] = @@ -306,6 +406,23 @@ export default class VscodeHatRenderer { dispose() { this.destroyDecorations(); + this.hatsDirWatcherDisposable?.dispose(); this.disposables.forEach(({ dispose }) => dispose()); } } + +function watchDir( + path: string, + onDidChange: PathChangeListener, +): vscode.Disposable { + const hatsDirWatcher = vscode.workspace.createFileSystemWatcher( + new vscode.RelativePattern(path, `**/*${CURSORLESS_HAT_SHAPES_SUFFIX}`), + ); + + return vscode.Disposable.from( + hatsDirWatcher, + hatsDirWatcher.onDidChange(onDidChange), + hatsDirWatcher.onDidCreate(onDidChange), + hatsDirWatcher.onDidDelete(onDidChange), + ); +} diff --git a/packages/cursorless-vscode/src/ide/vscode/hats/VscodeHats.ts b/packages/cursorless-vscode/src/ide/vscode/hats/VscodeHats.ts index df1a0b136b..255a7ab3c7 100644 --- a/packages/cursorless-vscode/src/ide/vscode/hats/VscodeHats.ts +++ b/packages/cursorless-vscode/src/ide/vscode/hats/VscodeHats.ts @@ -15,8 +15,8 @@ import { VscodeHatStyleName } from "../hatStyles.types"; import VscodeEnabledHatStyleManager from "../VscodeEnabledHatStyleManager"; import type { VscodeIDE } from "../VscodeIDE"; import { VscodeTextEditorImpl } from "../VscodeTextEditorImpl"; -import VscodeHatRenderer from "./VscodeHatRenderer"; import { FontMeasurements } from "./FontMeasurements"; +import VscodeHatRenderer from "./VscodeHatRenderer"; export class VscodeHats implements Hats { private enabledHatStyleManager: VscodeEnabledHatStyleManager; diff --git a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts index 820a19c111..e08f12f716 100644 --- a/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts +++ b/packages/cursorless-vscode/src/keyboard/KeyboardCommandsModal.ts @@ -173,7 +173,7 @@ export default class KeyboardCommandsModal { let keyHandler: KeyHandler | undefined = this.mergedKeymap[sequence]; // We handle multi-key sequences by repeatedly awaiting a single keypress - // until they've pressed somethign in the map. + // until they've pressed something in the map. while (keyHandler == null) { if (!this.isPrefixOfKey(sequence)) { const errorMessage = `Unknown key sequence "${sequence}"`; diff --git a/packages/cursorless-vscode/src/scripts/initLaunchSandbox.ts b/packages/cursorless-vscode/src/scripts/initLaunchSandbox.ts index 5d7343aac3..db5f7591d3 100644 --- a/packages/cursorless-vscode/src/scripts/initLaunchSandbox.ts +++ b/packages/cursorless-vscode/src/scripts/initLaunchSandbox.ts @@ -6,7 +6,7 @@ import { extensionDependencies } from "@cursorless/common"; import * as cp from "child_process"; -const extraExtensions = ["pokey.command-server", "pokey.talon"]; +const extraExtensions = ["pokey.command-server"]; async function main() { try { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 872860bb0d..129ab708b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -143,7 +143,7 @@ importers: version: 3.0.7(prettier@3.0.0)(webpack-cli@5.1.4)(webpack@5.88.2) autoprefixer: specifier: 10.4.13 - version: 10.4.13(postcss@8.4.21) + version: 10.4.13(postcss@8.4.31) css-loader: specifier: 6.7.3 version: 6.7.3(webpack@5.88.2) @@ -154,17 +154,17 @@ importers: specifier: 29.5.0 version: 29.5.0(@types/node@16.18.13)(ts-node@10.9.1) postcss: - specifier: 8.4.21 - version: 8.4.21 + specifier: 8.4.31 + version: 8.4.31 postcss-loader: specifier: 7.0.2 - version: 7.0.2(postcss@8.4.21)(webpack@5.88.2) + version: 7.0.2(postcss@8.4.31)(webpack@5.88.2) style-loader: specifier: 3.3.1 version: 3.3.1(webpack@5.88.2) tailwindcss: specifier: 3.2.7 - version: 3.2.7(postcss@8.4.21)(ts-node@10.9.1) + version: 3.2.7(postcss@8.4.31)(ts-node@10.9.1) ts-loader: specifier: 9.4.2 version: 9.4.2(typescript@5.1.6)(webpack@5.88.2) @@ -239,8 +239,8 @@ importers: specifier: ^1.0.19 version: 1.0.19 zod: - specifier: 3.21.4 - version: 3.21.4 + specifier: 3.22.3 + version: 3.22.3 devDependencies: '@types/js-yaml': specifier: ^4.0.2 @@ -257,6 +257,9 @@ importers: '@types/sinon': specifier: ^10.0.2 version: 10.0.13 + fast-check: + specifier: 3.12.0 + version: 3.12.0 js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -323,16 +326,16 @@ importers: version: 18.0.11 autoprefixer: specifier: 10.4.13 - version: 10.4.13(postcss@8.4.21) + version: 10.4.13(postcss@8.4.31) http-server: specifier: 14.1.1 version: 14.1.1 postcss: - specifier: 8.4.21 - version: 8.4.21 + specifier: 8.4.31 + version: 8.4.31 tailwindcss: specifier: 3.2.7 - version: 3.2.7(postcss@8.4.21)(ts-node@10.9.1) + version: 3.2.7(postcss@8.4.31)(ts-node@10.9.1) packages/cursorless-org-docs: dependencies: @@ -1975,7 +1978,7 @@ packages: '@docusaurus/utils-validation': 3.0.0-alpha.0(@docusaurus/types@3.0.0-alpha.0) '@slorber/static-site-generator-webpack-plugin': 4.0.7 '@svgr/webpack': 6.5.1 - autoprefixer: 10.4.13(postcss@8.4.21) + autoprefixer: 10.4.13(postcss@8.4.31) babel-loader: 9.1.3(@babel/core@7.22.10)(webpack@5.88.2) babel-plugin-dynamic-import-node: 2.3.3 boxen: 6.2.1 @@ -1989,7 +1992,7 @@ packages: core-js: 3.29.0 css-loader: 6.7.3(webpack@5.88.2) css-minimizer-webpack-plugin: 4.2.2(clean-css@5.3.2)(webpack@5.88.2) - cssnano: 5.1.15(postcss@8.4.21) + cssnano: 5.1.15(postcss@8.4.31) del: 6.1.1 detect-port: 1.5.1 escape-html: 1.0.3 @@ -2003,8 +2006,8 @@ packages: leven: 3.1.0 lodash: 4.17.21 mini-css-extract-plugin: 2.7.3(webpack@5.88.2) - postcss: 8.4.21 - postcss-loader: 7.0.2(postcss@8.4.21)(webpack@5.88.2) + postcss: 8.4.31 + postcss-loader: 7.0.2(postcss@8.4.31)(webpack@5.88.2) prompts: 2.4.2 react: 18.2.0 react-dev-utils: 12.0.1(eslint@8.38.0)(typescript@5.1.6)(webpack@5.88.2) @@ -2052,9 +2055,9 @@ packages: resolution: {integrity: sha512-W+rCRAAv/AIePyWk62tYUxZBRJojBWwIW0/olhhjoEyI4cyLrfUXE1be8gilu0SoDw6S1WukDCoEf+31+9+ZJg==} engines: {node: '>=16.14'} dependencies: - cssnano-preset-advanced: 5.3.10(postcss@8.4.21) - postcss: 8.4.21 - postcss-sort-media-queries: 4.3.0(postcss@8.4.21) + cssnano-preset-advanced: 5.3.10(postcss@8.4.31) + postcss: 8.4.31 + postcss-sort-media-queries: 4.3.0(postcss@8.4.31) tslib: 2.6.1 dev: false @@ -2494,7 +2497,7 @@ packages: infima: 0.2.0-alpha.43 lodash: 4.17.21 nprogress: 0.2.0 - postcss: 8.4.21 + postcss: 8.4.31 prism-react-renderer: 1.3.5(react@18.2.0) prismjs: 1.29.0 react: 18.2.0 @@ -5734,7 +5737,7 @@ packages: engines: {node: '>= 4.0.0'} dev: false - /autoprefixer@10.4.13(postcss@8.4.21): + /autoprefixer@10.4.13(postcss@8.4.31): resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} engines: {node: ^10 || ^12 || >=14} hasBin: true @@ -5746,7 +5749,7 @@ packages: fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 /available-typed-arrays@1.0.5: @@ -6819,13 +6822,13 @@ packages: type-fest: 1.4.0 dev: false - /css-declaration-sorter@6.3.1(postcss@8.4.21): + /css-declaration-sorter@6.3.1(postcss@8.4.31): resolution: {integrity: sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==} engines: {node: ^10 || ^12 || >=14} peerDependencies: postcss: ^8.0.9 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 dev: false /css-in-js-utils@3.1.0: @@ -6840,12 +6843,12 @@ packages: peerDependencies: webpack: ^5.0.0 dependencies: - icss-utils: 5.1.0(postcss@8.4.21) - postcss: 8.4.21 - postcss-modules-extract-imports: 3.0.0(postcss@8.4.21) - postcss-modules-local-by-default: 4.0.0(postcss@8.4.21) - postcss-modules-scope: 3.0.0(postcss@8.4.21) - postcss-modules-values: 4.0.0(postcss@8.4.21) + icss-utils: 5.1.0(postcss@8.4.31) + postcss: 8.4.31 + postcss-modules-extract-imports: 3.0.0(postcss@8.4.31) + postcss-modules-local-by-default: 4.0.0(postcss@8.4.31) + postcss-modules-scope: 3.0.0(postcss@8.4.31) + postcss-modules-values: 4.0.0(postcss@8.4.31) postcss-value-parser: 4.2.0 semver: 7.5.4 webpack: 5.88.2(webpack-cli@5.1.4) @@ -6876,9 +6879,9 @@ packages: optional: true dependencies: clean-css: 5.3.2 - cssnano: 5.1.15(postcss@8.4.21) + cssnano: 5.1.15(postcss@8.4.31) jest-worker: 29.5.0 - postcss: 8.4.21 + postcss: 8.4.31 schema-utils: 4.0.0 serialize-javascript: 6.0.1 source-map: 0.6.1 @@ -6920,77 +6923,77 @@ packages: engines: {node: '>=4'} hasBin: true - /cssnano-preset-advanced@5.3.10(postcss@8.4.21): + /cssnano-preset-advanced@5.3.10(postcss@8.4.31): resolution: {integrity: sha512-fnYJyCS9jgMU+cmHO1rPSPf9axbQyD7iUhLO5Df6O4G+fKIOMps+ZbU0PdGFejFBBZ3Pftf18fn1eG7MAPUSWQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - autoprefixer: 10.4.13(postcss@8.4.21) - cssnano-preset-default: 5.2.14(postcss@8.4.21) - postcss: 8.4.21 - postcss-discard-unused: 5.1.0(postcss@8.4.21) - postcss-merge-idents: 5.1.1(postcss@8.4.21) - postcss-reduce-idents: 5.2.0(postcss@8.4.21) - postcss-zindex: 5.1.0(postcss@8.4.21) + autoprefixer: 10.4.13(postcss@8.4.31) + cssnano-preset-default: 5.2.14(postcss@8.4.31) + postcss: 8.4.31 + postcss-discard-unused: 5.1.0(postcss@8.4.31) + postcss-merge-idents: 5.1.1(postcss@8.4.31) + postcss-reduce-idents: 5.2.0(postcss@8.4.31) + postcss-zindex: 5.1.0(postcss@8.4.31) dev: false - /cssnano-preset-default@5.2.14(postcss@8.4.21): + /cssnano-preset-default@5.2.14(postcss@8.4.31): resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - css-declaration-sorter: 6.3.1(postcss@8.4.21) - cssnano-utils: 3.1.0(postcss@8.4.21) - postcss: 8.4.21 - postcss-calc: 8.2.4(postcss@8.4.21) - postcss-colormin: 5.3.1(postcss@8.4.21) - postcss-convert-values: 5.1.3(postcss@8.4.21) - postcss-discard-comments: 5.1.2(postcss@8.4.21) - postcss-discard-duplicates: 5.1.0(postcss@8.4.21) - postcss-discard-empty: 5.1.1(postcss@8.4.21) - postcss-discard-overridden: 5.1.0(postcss@8.4.21) - postcss-merge-longhand: 5.1.7(postcss@8.4.21) - postcss-merge-rules: 5.1.4(postcss@8.4.21) - postcss-minify-font-values: 5.1.0(postcss@8.4.21) - postcss-minify-gradients: 5.1.1(postcss@8.4.21) - postcss-minify-params: 5.1.4(postcss@8.4.21) - postcss-minify-selectors: 5.2.1(postcss@8.4.21) - postcss-normalize-charset: 5.1.0(postcss@8.4.21) - postcss-normalize-display-values: 5.1.0(postcss@8.4.21) - postcss-normalize-positions: 5.1.1(postcss@8.4.21) - postcss-normalize-repeat-style: 5.1.1(postcss@8.4.21) - postcss-normalize-string: 5.1.0(postcss@8.4.21) - postcss-normalize-timing-functions: 5.1.0(postcss@8.4.21) - postcss-normalize-unicode: 5.1.1(postcss@8.4.21) - postcss-normalize-url: 5.1.0(postcss@8.4.21) - postcss-normalize-whitespace: 5.1.1(postcss@8.4.21) - postcss-ordered-values: 5.1.3(postcss@8.4.21) - postcss-reduce-initial: 5.1.2(postcss@8.4.21) - postcss-reduce-transforms: 5.1.0(postcss@8.4.21) - postcss-svgo: 5.1.0(postcss@8.4.21) - postcss-unique-selectors: 5.1.1(postcss@8.4.21) - dev: false - - /cssnano-utils@3.1.0(postcss@8.4.21): + css-declaration-sorter: 6.3.1(postcss@8.4.31) + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 + postcss-calc: 8.2.4(postcss@8.4.31) + postcss-colormin: 5.3.1(postcss@8.4.31) + postcss-convert-values: 5.1.3(postcss@8.4.31) + postcss-discard-comments: 5.1.2(postcss@8.4.31) + postcss-discard-duplicates: 5.1.0(postcss@8.4.31) + postcss-discard-empty: 5.1.1(postcss@8.4.31) + postcss-discard-overridden: 5.1.0(postcss@8.4.31) + postcss-merge-longhand: 5.1.7(postcss@8.4.31) + postcss-merge-rules: 5.1.4(postcss@8.4.31) + postcss-minify-font-values: 5.1.0(postcss@8.4.31) + postcss-minify-gradients: 5.1.1(postcss@8.4.31) + postcss-minify-params: 5.1.4(postcss@8.4.31) + postcss-minify-selectors: 5.2.1(postcss@8.4.31) + postcss-normalize-charset: 5.1.0(postcss@8.4.31) + postcss-normalize-display-values: 5.1.0(postcss@8.4.31) + postcss-normalize-positions: 5.1.1(postcss@8.4.31) + postcss-normalize-repeat-style: 5.1.1(postcss@8.4.31) + postcss-normalize-string: 5.1.0(postcss@8.4.31) + postcss-normalize-timing-functions: 5.1.0(postcss@8.4.31) + postcss-normalize-unicode: 5.1.1(postcss@8.4.31) + postcss-normalize-url: 5.1.0(postcss@8.4.31) + postcss-normalize-whitespace: 5.1.1(postcss@8.4.31) + postcss-ordered-values: 5.1.3(postcss@8.4.31) + postcss-reduce-initial: 5.1.2(postcss@8.4.31) + postcss-reduce-transforms: 5.1.0(postcss@8.4.31) + postcss-svgo: 5.1.0(postcss@8.4.31) + postcss-unique-selectors: 5.1.1(postcss@8.4.31) + dev: false + + /cssnano-utils@3.1.0(postcss@8.4.31): resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 dev: false - /cssnano@5.1.15(postcss@8.4.21): + /cssnano@5.1.15(postcss@8.4.31): resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - cssnano-preset-default: 5.2.14(postcss@8.4.21) + cssnano-preset-default: 5.2.14(postcss@8.4.31) lilconfig: 2.1.0 - postcss: 8.4.21 + postcss: 8.4.31 yaml: 1.10.2 dev: false @@ -8138,6 +8141,13 @@ packages: iconv-lite: 0.4.24 tmp: 0.0.33 + /fast-check@3.12.0: + resolution: {integrity: sha512-SqahE9mlL3+lhjJ39joMLwcj6F+24hfZdf/tchlNO8sHcTdrUUdA5P/ZbSFZM9Xpzs36XaneGwE0FWepm/zyOA==} + engines: {node: '>=8.0.0'} + dependencies: + pure-rand: 6.0.0 + dev: true + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -9182,13 +9192,13 @@ packages: dependencies: safer-buffer: 2.1.2 - /icss-utils@5.1.0(postcss@8.4.21): + /icss-utils@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -12522,17 +12532,17 @@ packages: - supports-color dev: true - /postcss-calc@8.2.4(postcss@8.4.21): + /postcss-calc@8.2.4(postcss@8.4.31): resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} peerDependencies: postcss: ^8.2.2 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 postcss-value-parser: 4.2.0 dev: false - /postcss-colormin@5.3.1(postcss@8.4.21): + /postcss-colormin@5.3.1(postcss@8.4.31): resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: @@ -12541,90 +12551,90 @@ packages: browserslist: 4.21.10 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-convert-values@5.1.3(postcss@8.4.21): + /postcss-convert-values@5.1.3(postcss@8.4.31): resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-discard-comments@5.1.2(postcss@8.4.21): + /postcss-discard-comments@5.1.2(postcss@8.4.31): resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 dev: false - /postcss-discard-duplicates@5.1.0(postcss@8.4.21): + /postcss-discard-duplicates@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 dev: false - /postcss-discard-empty@5.1.1(postcss@8.4.21): + /postcss-discard-empty@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 dev: false - /postcss-discard-overridden@5.1.0(postcss@8.4.21): + /postcss-discard-overridden@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 dev: false - /postcss-discard-unused@5.1.0(postcss@8.4.21): + /postcss-discard-unused@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: false - /postcss-import@14.1.0(postcss@8.4.21): + /postcss-import@14.1.0(postcss@8.4.31): resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} peerDependencies: postcss: ^8.0.0 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.4 dev: true - /postcss-js@4.0.1(postcss@8.4.21): + /postcss-js@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.4.21 dependencies: camelcase-css: 2.0.1 - postcss: 8.4.21 + postcss: 8.4.31 dev: true - /postcss-load-config@3.1.4(postcss@8.4.21)(ts-node@10.9.1): + /postcss-load-config@3.1.4(postcss@8.4.31)(ts-node@10.9.1): resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} peerDependencies: @@ -12637,12 +12647,12 @@ packages: optional: true dependencies: lilconfig: 2.1.0 - postcss: 8.4.21 + postcss: 8.4.31 ts-node: 10.9.1(@types/node@16.18.13)(typescript@5.1.6) yaml: 1.10.2 dev: true - /postcss-loader@7.0.2(postcss@8.4.21)(webpack@5.88.2): + /postcss-loader@7.0.2(postcss@8.4.31)(webpack@5.88.2): resolution: {integrity: sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -12651,33 +12661,33 @@ packages: dependencies: cosmiconfig: 7.1.0 klona: 2.0.6 - postcss: 8.4.21 + postcss: 8.4.31 semver: 7.5.4 webpack: 5.88.2(webpack-cli@5.1.4) - /postcss-merge-idents@5.1.1(postcss@8.4.21): + /postcss-merge-idents@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - cssnano-utils: 3.1.0(postcss@8.4.21) - postcss: 8.4.21 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-merge-longhand@5.1.7(postcss@8.4.21): + /postcss-merge-longhand@5.1.7(postcss@8.4.31): resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.4.21) + stylehacks: 5.1.1(postcss@8.4.31) dev: false - /postcss-merge-rules@5.1.4(postcss@8.4.21): + /postcss-merge-rules@5.1.4(postcss@8.4.31): resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: @@ -12685,215 +12695,215 @@ packages: dependencies: browserslist: 4.21.10 caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.4.21) - postcss: 8.4.21 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: false - /postcss-minify-font-values@5.1.0(postcss@8.4.21): + /postcss-minify-font-values@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-minify-gradients@5.1.1(postcss@8.4.21): + /postcss-minify-gradients@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.4.21) - postcss: 8.4.21 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-minify-params@5.1.4(postcss@8.4.21): + /postcss-minify-params@5.1.4(postcss@8.4.31): resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - cssnano-utils: 3.1.0(postcss@8.4.21) - postcss: 8.4.21 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-minify-selectors@5.2.1(postcss@8.4.21): + /postcss-minify-selectors@5.2.1(postcss@8.4.31): resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: false - /postcss-modules-extract-imports@3.0.0(postcss@8.4.21): + /postcss-modules-extract-imports@3.0.0(postcss@8.4.31): resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 - /postcss-modules-local-by-default@4.0.0(postcss@8.4.21): + /postcss-modules-local-by-default@4.0.0(postcss@8.4.31): resolution: {integrity: sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - icss-utils: 5.1.0(postcss@8.4.21) - postcss: 8.4.21 + icss-utils: 5.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-selector-parser: 6.0.11 postcss-value-parser: 4.2.0 - /postcss-modules-scope@3.0.0(postcss@8.4.21): + /postcss-modules-scope@3.0.0(postcss@8.4.31): resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 - /postcss-modules-values@4.0.0(postcss@8.4.21): + /postcss-modules-values@4.0.0(postcss@8.4.31): resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - icss-utils: 5.1.0(postcss@8.4.21) - postcss: 8.4.21 + icss-utils: 5.1.0(postcss@8.4.31) + postcss: 8.4.31 - /postcss-nested@6.0.0(postcss@8.4.21): + /postcss-nested@6.0.0(postcss@8.4.31): resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: true - /postcss-normalize-charset@5.1.0(postcss@8.4.21): + /postcss-normalize-charset@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 dev: false - /postcss-normalize-display-values@5.1.0(postcss@8.4.21): + /postcss-normalize-display-values@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-normalize-positions@5.1.1(postcss@8.4.21): + /postcss-normalize-positions@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-normalize-repeat-style@5.1.1(postcss@8.4.21): + /postcss-normalize-repeat-style@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-normalize-string@5.1.0(postcss@8.4.21): + /postcss-normalize-string@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-normalize-timing-functions@5.1.0(postcss@8.4.21): + /postcss-normalize-timing-functions@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-normalize-unicode@5.1.1(postcss@8.4.21): + /postcss-normalize-unicode@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-normalize-url@5.1.0(postcss@8.4.21): + /postcss-normalize-url@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: normalize-url: 6.1.0 - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-normalize-whitespace@5.1.1(postcss@8.4.21): + /postcss-normalize-whitespace@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-ordered-values@5.1.3(postcss@8.4.21): + /postcss-ordered-values@5.1.3(postcss@8.4.31): resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - cssnano-utils: 3.1.0(postcss@8.4.21) - postcss: 8.4.21 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-reduce-idents@5.2.0(postcss@8.4.21): + /postcss-reduce-idents@5.2.0(postcss@8.4.31): resolution: {integrity: sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false - /postcss-reduce-initial@5.1.2(postcss@8.4.21): + /postcss-reduce-initial@5.1.2(postcss@8.4.31): resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: @@ -12901,16 +12911,16 @@ packages: dependencies: browserslist: 4.21.10 caniuse-api: 3.0.0 - postcss: 8.4.21 + postcss: 8.4.31 dev: false - /postcss-reduce-transforms@5.1.0(postcss@8.4.21): + /postcss-reduce-transforms@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: false @@ -12921,47 +12931,47 @@ packages: cssesc: 3.0.0 util-deprecate: 1.0.2 - /postcss-sort-media-queries@4.3.0(postcss@8.4.21): + /postcss-sort-media-queries@4.3.0(postcss@8.4.31): resolution: {integrity: sha512-jAl8gJM2DvuIJiI9sL1CuiHtKM4s5aEIomkU8G3LFvbP+p8i7Sz8VV63uieTgoewGqKbi+hxBTiOKJlB35upCg==} engines: {node: '>=10.0.0'} peerDependencies: postcss: ^8.4.16 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 sort-css-media-queries: 2.1.0 dev: false - /postcss-svgo@5.1.0(postcss@8.4.21): + /postcss-svgo@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-value-parser: 4.2.0 svgo: 2.8.0 dev: false - /postcss-unique-selectors@5.1.1(postcss@8.4.21): + /postcss-unique-selectors@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: false /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - /postcss-zindex@5.1.0(postcss@8.4.21): + /postcss-zindex@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.21 + postcss: 8.4.31 dev: false /postcss@8.4.14: @@ -12973,8 +12983,8 @@ packages: source-map-js: 1.0.2 dev: false - /postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.6 @@ -13903,7 +13913,7 @@ packages: dependencies: escalade: 3.1.1 picocolors: 1.0.0 - postcss: 8.4.21 + postcss: 8.4.31 strip-json-comments: 3.1.1 dev: false @@ -13970,7 +13980,7 @@ packages: htmlparser2: 8.0.1 is-plain-object: 5.0.0 parse-srcset: 1.0.2 - postcss: 8.4.21 + postcss: 8.4.31 dev: false /sax@1.2.4: @@ -14692,14 +14702,14 @@ packages: react: 18.2.0 dev: false - /stylehacks@5.1.1(postcss@8.4.21): + /stylehacks@5.1.1(postcss@8.4.31): resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: browserslist: 4.21.10 - postcss: 8.4.21 + postcss: 8.4.31 postcss-selector-parser: 6.0.11 dev: false @@ -14774,7 +14784,7 @@ packages: zod: 3.20.6 dev: true - /tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.1): + /tailwindcss@3.2.7(postcss@8.4.31)(ts-node@10.9.1): resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==} engines: {node: '>=12.13.0'} hasBin: true @@ -14795,11 +14805,11 @@ packages: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.0 - postcss: 8.4.21 - postcss-import: 14.1.0(postcss@8.4.21) - postcss-js: 4.0.1(postcss@8.4.21) - postcss-load-config: 3.1.4(postcss@8.4.21)(ts-node@10.9.1) - postcss-nested: 6.0.0(postcss@8.4.21) + postcss: 8.4.31 + postcss-import: 14.1.0(postcss@8.4.31) + postcss-js: 4.0.1(postcss@8.4.31) + postcss-load-config: 3.1.4(postcss@8.4.31)(ts-node@10.9.1) + postcss-nested: 6.0.0(postcss@8.4.31) postcss-selector-parser: 6.0.11 postcss-value-parser: 4.2.0 quick-lru: 5.1.1 @@ -16233,8 +16243,8 @@ packages: resolution: {integrity: sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==} dev: true - /zod@3.21.4: - resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} + /zod@3.22.3: + resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} dev: false /zwitch@2.0.4: diff --git a/pyproject.toml b/pyproject.toml index a345db38d9..31dab584d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,14 @@ [tool.black] target-version = ["py39"] +force-exclude = ''' +( + ^/vendor/ + | ^/data/playground/ +) +''' [tool.ruff] select = ["E", "F", "C4", "I001", "UP", "SIM"] ignore = ["E501", "SIM105", "UP035"] target-version = "py39" -extend-exclude = ["vendor"] +extend-exclude = ["vendor", "data/playground/**/*.py"] diff --git a/queries/javascript.core.scm b/queries/javascript.core.scm index c7978670de..920524256d 100644 --- a/queries/javascript.core.scm +++ b/queries/javascript.core.scm @@ -34,21 +34,93 @@ ;; this full grandparent statement. ( [ + ;; name: ;;!! (const | let) foo = ...; - ;;! --------------^^^------- + ;;! ^^^ + ;;! xxxxxxxxxxxxxxxxxxxx + ;;! ------------------------ + ;; value: + ;;!! (const | let) foo = ...; + ;;! ^^^ + ;;! xxxxxx + ;;! ------------------------ (lexical_declaration + . (variable_declarator - name: (_) @name + name: (_) @name @name.removal.end.endOf + value: (_)? @name.removal.end.startOf ) ) + ;; name: + ;;!! var foo = ...; + ;;! ^^^ + ;;! xxxxxxxxxx + ;;! -------------- + ;; value: ;;!! var foo = ...; - ;;! ----^^^------- + ;;! ^^^ + ;;! xxxxxx + ;;! -------------- + ;; Note that we can't merge this with the variable declaration above because + ;; of https://github.com/tree-sitter/tree-sitter/issues/1442#issuecomment-1584628651 + (variable_declaration + . + (variable_declarator + name: (_) @name @name.removal.end.endOf + value: (_)? @name.removal.end.startOf + ) + ) + ] @_.domain @name.removal.start.startOf + (#not-parent-type? @_.domain export_statement) + + ;; Handle multiple variable declarators in one statement, eg + ;;!! (let | const | var) aaa = ..., ccc = ...; + ;;! --------------------^^^--------^^^------- + (#allow-multiple! @name) +) + +;; Note that the same domains below will also have the first variable +;; as a target, but that is matched above +( + [ + ;; name: + ;;!! (const | let) aaa = 0, bbb = 0; + ;;! ^^^ + ;;! xxxxxx + ;;! ------------------------------- + ;; value: + ;;!! (const | let) aaa = 0, bbb = 0; + ;;! ^ + ;;! xxxx + ;;! ------------------------------- + (lexical_declaration + (variable_declarator) + . + (variable_declarator + name: (_) @name @name.trailing.start.endOf + value: (_)? @name.trailing.end.startOf + ) + ) + + ;; name: + ;;!! var aaa = 0, bbb = 0; + ;;! ^^^ + ;;! xxxxxx + ;;! --------------------- + ;; value: + ;;!! var aaa = 0, bbb = 0; + ;;! ^ + ;;! xxxx + ;;! --------------------- ;; Note that we can't merge this with the variable declaration above because ;; of https://github.com/tree-sitter/tree-sitter/issues/1442#issuecomment-1584628651 (variable_declaration + (variable_declarator) + . (variable_declarator - name: (_) @name + name: (_) @name @name.trailing.start.endOf + value: (_)? @name.trailing.end.startOf ) ) ] @_.domain @@ -63,10 +135,19 @@ ( (export_statement (_ - ;;!! export [default] (let | const | var) foo = ...; - ;;! -------------------------------------^^^------- + ;; name: + ;;!! export (const | let | var) foo = ...; + ;;! ^^^ + ;;! xxxxxx + ;;! ------------------------------------- + ;; value: + ;;!! export (const | let | var) foo = 0; + ;;! ^ + ;;! xxxx + ;;! ----------------------------------- (variable_declarator - name: (_) @name + name: (_) @name @name.trailing.start.endOf + value: (_)? @name.trailing.end.startOf ) ) ) @_.domain @@ -77,22 +158,246 @@ (#allow-multiple! @name) ) -;;!! foo += ...; -;;! ^^^-------- -(augmented_assignment_expression - left: (_) @name +;; name: +;;!! (const | let | var) aaa = 0, bbb = 0; +;;! ^^^ +;;! xxxxxxxxxxxxxxxxxxxxxxxxxx +;;! ------- +;; value: +;;!! (const | let | var) aaa = 0, bbb = 0; +;;! ^ +;;! xxxx +;;! ------- +(_ + . + (variable_declarator + name: (_) @name @name.removal.end.endOf + value: (_)? @name.removal.end.startOf + ) @_.domain + . + (variable_declarator) +) @name.removal.start.startOf + +;;!! (const | let | var) aaa = 0, bbb = 0; +;;! ^^^ +;;! xxxxxx +;;! ------- +(_ + (variable_declarator) + . + (variable_declarator + name: (_) @name @name.trailing.start.endOf + value: (_)? @name.trailing.end.startOf + ) @_.domain +) + +;; Special cases for `(let | const | var) foo = ...;` because the full statement +;; is actually a grandparent of the `name` node, so we want the domain to include +;; this full grandparent statement. +( + [ + ;;!! (const | let) aaa: Bbb = 0; + ;;! ^ + ;;! xxxx + ;;! --------------------------- + (lexical_declaration + (variable_declarator + (_) @value.leading.start.endOf + . + value: (_)? @value @value.leading.end.startOf + ) + ) + + ;;!! var aaa: Bbb = 0; + ;;! ^ + ;;! xxxx + ;;! ----------------- + ;; Note that we can't merge this with the variable declaration above because + ;; of https://github.com/tree-sitter/tree-sitter/issues/1442#issuecomment-1584628651 + (variable_declaration + (variable_declarator + (_) @value.leading.start.endOf + . + value: (_)? @value @value.leading.end.startOf + ) + ) + ] @_.domain + (#not-parent-type? @_.domain export_statement) + + ;; Handle multiple variable declarators in one statement, eg + ;;!! (let | const | var) aaa: Bbb = ..., ccc: Ddd = ...; + ;;! -------------------------------^^^-------------^^^- + (#allow-multiple! @value) +) + +( + (export_statement + (_ + ;;!! export (const | let | var) aaa: Bbb = 0; + ;;! ^ + ;;! xxxx + ;;! ---------------------------------------- + (variable_declarator + (_) @value.leading.start.endOf + . + value: (_)? @value @value.leading.end.startOf + ) + ) + ) @_.domain + + ;; Handle multiple variable declarators in one statement, eg + ;;!! export (let | const | var) aaa: Bbb = ..., ccc: Ddd = ...; + ;;! --------------------------------------^^^-------------^^^- + (#allow-multiple! @value) +) + +;;!! (const | let | var) aaa: Ccc = 0, bbb: Ddd = 0; +;;!1 ^ +;;!1 xxxx +;;!1 ------------ +;;!2 ^ +;;!2 xxxx +;;!2 ------------ +( + (_ + (variable_declarator + (_) @value.leading.start.endOf + . + value: (_)? @value @value.leading.end.startOf + ) @_.domain + ) @dummy + (#has-multiple-children-of-type? @dummy variable_declarator) +) + +(expression_statement + [ + ;; name: + ;;!! foo = 0; + ;;! ^^^ + ;;! xxxxxx + ;;! -------- + ;; value: + ;;!! foo = 0; + ;;! ^ + ;;! xxxx + ;;! -------- + (assignment_expression + left: (_) @name @value.leading.start.endOf @name.trailing.start.endOf + right: (_) @value @name.trailing.end.startOf @value.leading.end.startOf + ) + + ;; name: + ;;!! foo += 1; + ;;! ^^^ + ;;! xxxxxxx + ;;! --------- + ;; value: + ;;!! foo += 1; + ;;! ^ + ;;! xxxxx + ;;! --------- + (augmented_assignment_expression + left: (_) @name @value.leading.start.endOf @name.trailing.start.endOf + right: (_) @value @name.trailing.end.startOf @value.leading.end.startOf + ) + ] ) @_.domain -;;!! foo = ...; -;;! ^^^------- -(assignment_expression - left: (_) @name +( + [ + ;; name: + ;;!! aaa = 0, bbb = 0; + ;;!1 ^^^ + ;;!1 xxxxxx + ;;!1 ------- + ;;!2 ^^^ + ;;!2 xxxxxx + ;;!2 ------- + ;; value: + ;;!! aaa = 0, bbb = 0; + ;;!1 ^ + ;;!1 xxxx + ;;!1 ------- + ;;!2 ^ + ;;!2 xxxx + ;;!2 ------- + (assignment_expression + left: (_) @name @value.leading.start.endOf @name.trailing.start.endOf + right: (_) @value @name.trailing.end.startOf @value.leading.end.startOf + ) + + ;; name: + ;;!! aaa += 0, bbb += 0; + ;;!1 ^^^ + ;;!1 xxxxxxx + ;;!1 -------- + ;;!2 ^^^ + ;;!2 xxxxxxx + ;;!2 -------- + ;; value: + ;;!! aaa += 0, bbb += 0; + ;;!1 ^ + ;;!1 xxxxx + ;;!1 -------- + ;;!2 ^ + ;;!2 xxxxx + ;;!2 -------- + (augmented_assignment_expression + left: (_) @name @value.leading.start.endOf @name.trailing.start.endOf + right: (_) @value @name.trailing.end.startOf @value.leading.end.startOf + ) + ] @_.domain + + (#not-parent-type? @_.domain expression_statement) +) + +;; Match nodes at field `value` of their parent node, setting leading delimiter +;; to be the range until the previous named node +( + (_ + (_)? @value.leading.start.endOf + . + value: (_) @value @value.leading.end.startOf + ) @_.domain + (#not-type? @_.domain variable_declarator) +) + +;;!! const aaa = {bbb}; +;;! ^^^ +(shorthand_property_identifier) @value + +;;!! return 0; +;;! ^ +;;! --------- +(return_statement + (_) @value ) @_.domain +;;!! yield 0; +;;! ^ +;;! -------- +(yield_expression + (_) @value +) @_.domain + +;; name: +;;!! for (const aaa of bbb) {} +;;! ^^^ +;;! ---------------------- +;; value: +;;!! for (const aaa of bbb) {} +;;! ^^^ +;;! ---------------------- +(for_in_statement + left: (_) @name + right: (_) @value + ")" @_.domain.end.endOf +) @_.domain.start.startOf + [ (program) (formal_parameters) -] @name.iteration +] @name.iteration @value.iteration @type.iteration ;; Treat interior of all bodies as iteration scopes for `name`, eg ;;!! function foo() { } @@ -100,8 +405,15 @@ (_ body: (_ . - "{" @name.iteration.start.endOf - "}" @name.iteration.end.startOf + "{" @name.iteration.start.endOf @value.iteration.start.endOf @type.iteration.start.endOf + "}" @name.iteration.end.startOf @value.iteration.end.startOf @type.iteration.end.startOf . ) ) + +;;!! const aaa = {bbb: 0, ccc: 0}; +;;! ************** +(object + "{" @value.iteration.start.endOf + "}" @value.iteration.end.startOf +) diff --git a/queries/javascript.jsx.scm b/queries/javascript.jsx.scm index f9b2cb44f9..129f9da20f 100644 --- a/queries/javascript.jsx.scm +++ b/queries/javascript.jsx.scm @@ -81,3 +81,22 @@ "<" @_.domain.start ">" @name.startOf @_.domain.end ) + +;;!! +;;! ^^^^^ +;;! xxxxxx +;;! --------- +(jsx_attribute + (_) @value.leading.start.endOf + (_) @value @value.leading.end.startOf +) @_.domain + +(jsx_self_closing_element + "<" @value.iteration.start.endOf + "/" @value.iteration.end.startOf +) + +(jsx_opening_element + "<" @value.iteration.start.endOf + ">" @value.iteration.end.startOf +) diff --git a/queries/python.scm b/queries/python.scm index 08c4b98bb9..180c675a59 100644 --- a/queries/python.scm +++ b/queries/python.scm @@ -17,6 +17,7 @@ (if_statement) (import_from_statement) (import_statement) + (match_statement) (nonlocal_statement) (pass_statement) (print_statement) @@ -27,6 +28,150 @@ (with_statement) ] @statement +;;!! a = 25 +;;! ^^ +;;! xxxxx +;;! ------ +(assignment + (_) @_.leading.start.endOf + . + right: (_) @value @_.leading.end.startOf +) @_.domain + +;; value: +;;!! a /= 25 +;;! ^^ +;;! xxxxxx +;;! ------- +;; name: +;;!! a /= 25 +;;! ^ +;;! xxxxx +;;! ------- +(augmented_assignment + left: (_) @name @name.trailing.start.endOf @value.leading.start.endOf + right: (_) @value @value.leading.end.startOf @name.trailing.end.startOf +) @_.domain + +;;!! a = 25 +;;! ^ +;;! xxxx +;;! ------ +;;!! a: int = 25 +;;! ^ +;;! xxxxxxxxx +;;! ----------- +(assignment + left: (_) @name @name.trailing.start.endOf + right: (_)? @name.trailing.end.startOf +) @_.domain + +(_ + name: (_) @name +) @_.domain + +;;!! def aaa(bbb): +;;! ^^^ +(parameters + (identifier) @name +) + +;;!! def aaa(bbb: str): +;;! ^^^ +;;! -------- +(typed_parameter + . + (_) @name +) @_.domain + +;; Matches any node at field `type` of its parent, with leading delimiter until +;; previous named node. For example: +;;!! aaa: str = "bbb"; +;;! ^^^ +;;! ----------------- +;;! xxxxx +(_ + (_) @_.leading.start.endOf + . + type: (_) @type @_.leading.end.startOf +) @_.domain + +;;!! def aaa() -> str: +;;! ^^^ +;;! xxxxxxx +;;! [----------------- +;;!! pass +;;! --------] +(function_definition + (_) @_.leading.start.endOf + . + return_type: (_) @type @_.leading.end.startOf +) @_.domain + +;;!! d = {"a": 1234} +;;! ^^^^ +;;! xxxxxx +;;! --------- +;;!! {value: key for (key, value) in d1.items()} +;;! ^^^ +;;! xxxxx +;;! ---------- +;;!! def func(value: str = ""): +;;! ^^ +;;! xxxxx +;;! --------------- +( + (_ + (_) @_.leading.start.endOf + . + value: (_) @value @_.leading.end.startOf + ) @_.domain + (#not-type? @_.domain subscript) +) + +;;!! return 1 +;;! ^ +;;! xx +;;! -------- +;; +;; NOTE: in tree-sitter, both "return" and the "1" are children of `return_statement` +;; but "return" is anonymous whereas "1" is named node, so no need to exclude explicitly +(return_statement + (_) @value +) @_.domain + +;; value: +;;!! for aaa in bbb: +;;! ^^^ +;;! --------------- +;; name: +;;!! for aaa in bbb: +;;! ^^^ +;;! --------------- +(for_statement + left: (_) @name + right: (_) @value + ":" @_.domain.end +) @_.domain.start.startOf + +(comment) @comment @textFragment + +(string + _ @textFragment.start.endOf + _ @textFragment.end.startOf +) @string + +[ + (dictionary) + (dictionary_comprehension) +] @map + +[ + (list) + (list_comprehension) + (set) +] @list + ( (function_definition name: (_) @functionName @@ -56,9 +201,30 @@ ) @class @className.domain (module) @className.iteration @class.iteration -(module) @statement.iteration +(module) @statement.iteration @value.iteration @name.iteration (module) @namedFunction.iteration @functionName.iteration (class_definition) @namedFunction.iteration @functionName.iteration -(_ - body: (_) @statement.iteration + +;;!! def foo(): +;;!! a = 0 +;;! <***** +;;!! b = 1 +;;! ***** +;;!! c = 2 +;;! *****> +(block) @statement.iteration @value.iteration @name.iteration +(block) @type.iteration + +;;!! {"a": 1, "b": 2, "c": 3} +;;! ********************** +(dictionary + "{" @value.iteration.start.endOf + "}" @value.iteration.end.startOf +) + +;;!! def func(a=0, b=1): +;;! ******** +(parameters + "(" @value.iteration.start.endOf @name.iteration.start.endOf @type.iteration.start.endOf + ")" @value.iteration.end.startOf @name.iteration.end.startOf @type.iteration.end.startOf ) diff --git a/queries/scm.name.scm b/queries/scm.name.scm index 01bd7cc390..1926a67a76 100644 --- a/queries/scm.name.scm +++ b/queries/scm.name.scm @@ -1,59 +1,43 @@ ;;!! (aaa) @bbb @ccc -;;! ^^^^^^^^ -;;! xxxxxxxxx +;;! ^^^^^^^^^ ;;! --------------- ( (_ _ @dummy . - (capture - "@" @_.leading - name: (identifier) @name.start - ) + (capture) @name.start (capture)? @name.end . ) @_.domain (#not-type? @_.domain parameters) (#not-type? @dummy capture) (#not-parent-type? @_.domain field_definition) - (#insertion-delimiter! @name.start " @") ) ;;!! eee: (aaa) @bbb @ccc -;;! ^^^^^^^^ -;;! xxxxxxxxx +;;! ^^^^^^^^^ ;;! -------------------- ( (field_definition (_ _ @dummy . - (capture - "@" @_.leading - name: (identifier) @name.start - ) + (capture) @name.start (capture)? @name.end . ) ) @_.domain (#not-type? @dummy capture) - (#insertion-delimiter! @name.start " @") ) ;;!! (aaa) @bbb @ccc -;;! ^^^ ^^^ -;;! xxxx xxxx -;;! ---- ---- +;;! ^^^^ ^^^^ ( (_ - (capture - "@" @_.leading - name: (identifier) @name - ) @_.domain + (capture) @name ) @dummy (#not-type? @dummy parameters) (#has-multiple-children-of-type? @dummy capture) - (#insertion-delimiter! @name " @") ) ;;!! (aaa) @bbb @ccc diff --git a/queries/typescript.core.scm b/queries/typescript.core.scm new file mode 100644 index 0000000000..12ec9443e8 --- /dev/null +++ b/queries/typescript.core.scm @@ -0,0 +1,217 @@ +;; Note that we don't just import `javascript.scm` because that includes +;; `javascript.jsx.scm`, and tree-sitter would complain because those node +;; types are not defined in the typescript grammar. + +;; import javascript.core.scm + +;;!! function aaa(bbb?: Ccc = "ddd") {} +;;! ^^^-------------- +(optional_parameter + (identifier) @name +) @_.domain + +;;!! function aaa(bbb: Ccc = "ddd") {} +;;! ^^^------------- +(required_parameter + (identifier) @name +) @_.domain + +;; Define these here because these node types don't exist in javascript. +( + [ + ;;!! class Foo { foo() {} } + ;;! ^^^^^^^^ + ;;!! interface Foo { foo(): void; } + ;;! ^^^^^^^^^^^^ + (method_signature + name: (_) @functionName @name + ) + + ;;!! class Foo { abstract foo(): void; } + ;;! ^^^^^^^^^^^^^^^^^^^^^ + (abstract_method_signature + name: (_) @functionName @name + ) + + ;;!! class Foo { + ;;!! (public | private | protected) foo = () => {}; + ;;! ^^^^^^^^^^^^^^^ + ;;!! (public | private | protected) foo = function() {}; + ;;! ^^^^^^^^^^^^^^^^^^^^ + ;;!! (public | private | protected) foo = function *() {}; + ;;! ^^^^^^^^^^^^^^^^^^^^^^ + ;;!! } + (public_field_definition + name: (_) @functionName + value: [ + (function + !name + ) + (generator_function + !name + ) + (arrow_function) + ] + ) + ] @namedFunction.start @functionName.domain.start @name.domain.start + . + ";"? @namedFunction.end @functionName.domain.end @name.domain.end +) + +( + ;;!! (public | private | protected) foo = ...; + ;;! -------------------------------^^^------- + (public_field_definition + name: (_) @name + ) @name.domain.start + . + ";"? @name.domain.end +) + +[ + (interface_declaration) + (object_type) +] @namedFunction.iteration @functionName.iteration + +;; Special cases for `(let | const | var) foo = ...;` because the full statement +;; is actually a grandparent of the `name` node, so we want the domain to include +;; this full grandparent statement. +( + [ + ;;!! (const | let) aaa: Bbb = 0; + ;;! ^^^ + ;;! xxxxx + ;;! --------------------------- + (lexical_declaration + (variable_declarator + type: (type_annotation + (_) @type + ) @type.removal + ) + ) + + ;;!! var aaa: Bbb = 0; + ;;! ^^^ + ;;! xxxxx + ;;! ----------------- + ;; Note that we can't merge this with the variable declaration above because + ;; of https://github.com/tree-sitter/tree-sitter/issues/1442#issuecomment-1584628651 + (variable_declaration + (variable_declarator + type: (type_annotation + (_) @type + ) @type.removal + ) + ) + ] @_.domain + (#not-parent-type? @_.domain export_statement) + + ;; Handle multiple variable declarators in one statement, eg + ;;!! (let | const | var) aaa: Bbb = ..., ccc: Ddd = ...; + ;;! -------------------------^^^-------------^^^------- + (#allow-multiple! @type) +) + +( + (export_statement + (_ + ;;!! export (const | let | var) aaa: Bbb = 0; + ;;! ^^^ + ;;! xxxxx + ;;! ---------------------------------------- + (variable_declarator + type: (type_annotation + (_) @type + ) @type.removal + ) + ) + ) @_.domain + + ;; Handle multiple variable declarators in one statement, eg + ;;!! export (let | const | var) aaa: Bbb = ..., ccc: Ddd = ...; + ;;! --------------------------------^^^-------------^^^------- + (#allow-multiple! @type) +) + +;;!! (const | let | var) aaa: Ccc = 0, bbb: Ddd = 0; +;;!1 ^^^ +;;!1 xxxxx +;;!1 ------------ +;;!2 ^^^ +;;!2 xxxxx +;;!2 ------------ +( + (_ + (variable_declarator + type: (type_annotation + (_) @type + ) @type.removal + ) @_.domain + ) @dummy + (#has-multiple-children-of-type? @dummy variable_declarator) +) + +;; Generic type matcher +( + (_ + [ + type: (_ + (_) @type + ) + return_type: (_ + (_) @type + ) + ] @type.removal + ) @_.domain + (#not-type? @_.domain variable_declarator) +) + +;;!! new Aaa() +;;! ^^^^^^^^ +(new_expression + constructor: (_) @type.start + type_arguments: (_)? @type.end +) + +;;!! interface Aaa {} +;;!! type Aaa = Bbb; +( + [ + (type_alias_declaration) + (interface_declaration) + ] @type + (#not-parent-type? @type export_statement) +) + +;;!! export interface Aaa {} +;;!! export type Aaa = Bbb; +(export_statement + [ + (type_alias_declaration) + (interface_declaration) + ] @type +) @_.domain + +;;!! aaa as Bbb +;;! ^^^ +;;! xxxxxxx +;;! ---------- +(as_expression + (_) @_.leading.start.endOf + [ + (generic_type) + (predefined_type) + ] @type @_.leading.end.startOf +) @_.domain + +;;!! aaa satisfies Bbb +;;! ^^^ +;;! xxxxxxxxxxxxxx +;;! ----------------- +(satisfies_expression + (_) @_.leading.start.endOf + [ + (generic_type) + (predefined_type) + ] @type @_.leading.end.startOf +) @_.domain diff --git a/queries/typescript.scm b/queries/typescript.scm index 9e724f2380..f885360bde 100644 --- a/queries/typescript.scm +++ b/queries/typescript.scm @@ -1,74 +1,16 @@ -;; Note that we don't just import `javascript.scm` because that includes -;; `javascript.jsx.scm`, and tree-sitter would complain because those node -;; types are not defined in the typescript grammar. - -;; import javascript.core.scm - -;;!! function aaa(bbb?: Ccc = "ddd") {} -;;! ^^^-------------- -(optional_parameter - (identifier) @name +;; import typescript.core.scm + +;; Define this here because type assertions don't exist in TSX because +;; they are not valid JSX. +;;!! bbb +;;! ^^^ +;;! xxxxx +;;! -------- +(type_assertion + (type_arguments + [ + (generic_type) + (predefined_type) + ] @type + ) @_.removal ) @_.domain - -;;!! function aaa(bbb: Ccc = "ddd") {} -;;! ^^^------------- -(required_parameter - (identifier) @name -) @_.domain - -;; Define these here because these node types don't exist in javascript. -( - [ - ;;!! class Foo { foo() {} } - ;;! ^^^^^^^^ - ;;!! interface Foo { foo(): void; } - ;;! ^^^^^^^^^^^^ - (method_signature - name: (_) @functionName @name - ) - - ;;!! class Foo { abstract foo(): void; } - ;;! ^^^^^^^^^^^^^^^^^^^^^ - (abstract_method_signature - name: (_) @functionName @name - ) - - ;;!! class Foo { - ;;!! (public | private | protected) foo = () => {}; - ;;! ^^^^^^^^^^^^^^^ - ;;!! (public | private | protected) foo = function() {}; - ;;! ^^^^^^^^^^^^^^^^^^^^ - ;;!! (public | private | protected) foo = function *() {}; - ;;! ^^^^^^^^^^^^^^^^^^^^^^ - ;;!! } - (public_field_definition - name: (_) @functionName - value: [ - (function - !name - ) - (generator_function - !name - ) - (arrow_function) - ] - ) - ] @namedFunction.start @functionName.domain.start @name.domain.start - . - ";"? @namedFunction.end @functionName.domain.end @name.domain.end -) - -( - ;;!! (public | private | protected) foo = ...; - ;;! -------------------------------^^^------- - (public_field_definition - name: (_) @name - ) @name.domain.start - . - ";"? @name.domain.end -) - -[ - (interface_declaration) - (object_type) -] @namedFunction.iteration @functionName.iteration diff --git a/queries/typescriptreact.scm b/queries/typescriptreact.scm index 934357070c..b01d30a0f9 100644 --- a/queries/typescriptreact.scm +++ b/queries/typescriptreact.scm @@ -1,2 +1,2 @@ ;; import javascript.jsx.scm -;; import typescript.scm +;; import typescript.core.scm diff --git a/scripts/forbid-todo.sh b/scripts/forbid-todo.sh new file mode 100755 index 0000000000..7a2220ef0b --- /dev/null +++ b/scripts/forbid-todo.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail +# Fail if there are any TODOs in the codebase + +# Find the string 'TODO' in all files tracked by git, excluding +# this file +TODOS_FOUND=$(git grep --color=always -nw TODO -- ':!scripts/forbid-todo.sh' || true) + +if [ -n "$TODOS_FOUND" ]; then + printf "\e[1;31mERROR: \e[0mTODOs found in codebase:\n" + printf '%s\n' "$TODOS_FOUND" + exit 1 +fi diff --git a/tsconfig.base.json b/tsconfig.base.json index 059d1cb50e..80f180cc27 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -10,6 +10,7 @@ "resolveJsonModule": true, "composite": true, "forceConsistentCasingInFileNames": true, + "isolatedModules": true, "strict": true } }