diff --git a/core/snippets/snippets/commentBlock.snippet b/core/snippets/snippets/commentBlock.snippet index 90e4625446..8b5ebf0e9b 100644 --- a/core/snippets/snippets/commentBlock.snippet +++ b/core/snippets/snippets/commentBlock.snippet @@ -1,5 +1,5 @@ name: commentBlock -phrase: block comment +phrase: inline comment insertionScope: statement $0.insertionFormatter: CAPITALIZE_FIRST_WORD @@ -22,7 +22,5 @@ language: xml | html language: lua - ---[[ -$0 ---]] +--[[ $0 --]] --- diff --git a/core/snippets/snippets/commentMultilineBlock.snippet b/core/snippets/snippets/commentMultilineBlock.snippet new file mode 100644 index 0000000000..afd384bc33 --- /dev/null +++ b/core/snippets/snippets/commentMultilineBlock.snippet @@ -0,0 +1,34 @@ +name: commentMultilineBlock +phrase: block comment +insertionScope: statement + +$0.insertionFormatter: CAPITALIZE_FIRST_WORD +--- + +language: c | cpp | csharp | java | javascript | typescript | javascriptreact | typescriptreact | php | scala | kotlin | go +- +/* + * $0 + */ +--- + +language: python +- +""" +$0 +""" +--- + +language: xml | html +- + +--- + +language: lua +- +--[[ +$0 +--]] +--- diff --git a/core/snippets/snippets_insert.py b/core/snippets/snippets_insert.py index 012a644c1b..6c4b832646 100644 --- a/core/snippets/snippets_insert.py +++ b/core/snippets/snippets_insert.py @@ -1,4 +1,5 @@ import re +from typing import Optional from talon import Module, actions, settings @@ -45,6 +46,11 @@ def insert_snippet_by_name_with_phrase_and_stop_at_end(name: str, phrase: str): body = get_snippet_body_by_name_with_phrase_substitutions(name, phrase) insert_snippet_with_stop_at_the_end(body) + def insert_snippet_with_substitutions(body: str, substitutions: dict[str, str]): + """Insert snippet with substitutions""" + body = compute_snippet_text_with_substitutions(body, substitutions) + actions.user.insert_snippet(body) + def get_snippet_body_by_name_with_phrase_substitutions(name: str, phrase: str): snippet: Snippet = actions.user.get_snippet(name) @@ -69,17 +75,29 @@ def insert_snippet_with_stop_at_the_end(body): def compute_snippet_body_with_substitutions( snippet: Snippet, substitutions: dict[str, str] ) -> str: - body = snippet.body + return compute_snippet_text_with_substitutions( + snippet.body, substitutions, snippet.name + ) + + +def compute_snippet_text_with_substitutions( + body: str, substitutions: dict[str, str], name: Optional[str] = None +) -> str: + result = body if substitutions: for k, v in substitutions.items(): v = v.replace("$", r"\$") reg = re.compile(rf"\${k}|\$\{{{k}\}}") - if not reg.search(body): + if not reg.search(result): + if name is None: + snippet_text = body + else: + snippet_text = name raise ValueError( - f"Can't substitute non existing variable '{k}' in snippet '{snippet.name}'" + f"Can't substitute non existing variable '{k}' in snippet '{snippet_text}'" ) - body = reg.sub(v, body) - return body + result = reg.sub(v, result) + return result def compute_phrase_substitutions(snippet: Snippet, phrase: str): diff --git a/lang/lua/lua.py b/lang/lua/lua.py index bcb52daa84..caf681f78c 100644 --- a/lang/lua/lua.py +++ b/lang/lua/lua.py @@ -1,3 +1,5 @@ +from typing import Optional + from talon import Context, Module, actions, settings from ...core.described_functions import ( @@ -201,4 +203,14 @@ def code_insert_library(text: str, selection: str): substitutions = {"1": selection, "0": selection} actions.user.insert_snippet_by_name("importStatement", substitutions) + ## + # comment_block + ## + def code_comment_block(text: Optional[str] = None): + if text is None: + substitutions = {} + else: + substitutions = {"0": text} + actions.user.insert_snippet_by_name("commentMultilineBlock", substitutions) + # non-tag related actions diff --git a/lang/tags/comment_block.py b/lang/tags/comment_block.py index 18821ab505..98226d3d6b 100644 --- a/lang/tags/comment_block.py +++ b/lang/tags/comment_block.py @@ -1,3 +1,5 @@ +from typing import Optional + from talon import Context, Module, actions c_like_ctx = Context() @@ -14,9 +16,9 @@ @mod.action_class class Actions: - def code_comment_block(): + def code_comment_block(text: Optional[str] = None): """Block comment""" - actions.user.insert_snippet_by_name("commentBlock") + actions.user.code_block_comment_inline(text) def code_comment_block_prefix(): """Block comment start syntax""" @@ -24,12 +26,45 @@ def code_comment_block_prefix(): def code_comment_block_suffix(): """Block comment end syntax""" + def code_comment_block_line(): + """Wraps current line in block comment markers""" + actions.edit.line_start() + actions.user.code_comment_block_prefix() + actions.key("space") + actions.edit.line_end() + actions.key("space") + actions.user.code_comment_block_suffix() + + def code_block_comment_inline(text: Optional[str]): + """Inserts an inline block comment""" + if text is None: + substitutions = None + else: + substitutions = {"0": text} + actions.user.insert_snippet_by_name("commentBlock", substitutions) + @c_like_ctx.action_class("user") class CActions: - def code_comment_block(): + def code_comment_block(text: Optional[str] = None): + # inserting this way instead of using a snippet + # has advantages by allowing editor specific behavior such as + # putting a star at the start of the lines inside the block comment for some languages + # illustration: + # /* + # * (cursor goes here) + # */ actions.insert("/*\n\n*/") actions.edit.up() + if text is not None: + actions.insert(text) + + def code_block_comment_inline(text: Optional[str]): + if text is None: + substitutions = None + else: + substitutions = {"0": text} + actions.user.insert_snippet_with_substitutions("/* $0 */", substitutions) def code_comment_block_prefix(): actions.insert("/*") diff --git a/lang/tags/comment_block.talon b/lang/tags/comment_block.talon index 3b57442d26..50051161fa 100644 --- a/lang/tags/comment_block.talon +++ b/lang/tags/comment_block.talon @@ -2,37 +2,18 @@ tag: user.code_comment_block - block comment: user.code_comment_block() -block comment line: - edit.line_start() - user.code_comment_block_prefix() - key(space) - edit.line_end() - key(space) - user.code_comment_block_suffix() +block comment line: user.code_comment_block_line() #adds comment to the start of the line block comment line over: edit.line_start() - user.code_comment_block() - insert(user.text) -block comment over: - user.code_comment_block() - insert(user.text) -block comment $: - user.code_comment_block() - insert(user.text) + user.code_block_comment_inline(text) +block comment over: user.code_comment_block(text) +block comment $: user.code_comment_block(text) (line | inline) block comment over: edit.line_end() - user.code_comment_block_prefix() - key(space) - insert(user.text) - key(space) - user.code_comment_block_suffix() + user.code_block_comment_inline(text) (line | inline) block comment $: edit.line_end() - user.code_comment_block_prefix() - key(space) - insert(user.text) - key(space) - user.code_comment_block_suffix() + user.code_block_comment_inline(text) open block comment: user.code_comment_block_prefix() close block comment: user.code_comment_block_suffix() diff --git a/test/test_snippet_substitution.py b/test/test_snippet_substitution.py new file mode 100644 index 0000000000..1113deb087 --- /dev/null +++ b/test/test_snippet_substitution.py @@ -0,0 +1,74 @@ +import talon + +if hasattr(talon, "test_mode"): # Only include this when we're running tests + import pytest + + from core.snippets.snippets_insert import compute_snippet_text_with_substitutions + + FUNCTION_DECLARATION_BODY = "def $1($2):\n\t$0" + + def assert_substituting_matches_expected( + body: str, substitutions: dict[str, str], expected: str + ): + actual = compute_snippet_text_with_substitutions(body, substitutions) + assert actual == expected + + def assert_substituting_raises_exception( + body: str, + substitutions: dict[str, str], + name: str | None = None, + expected_text: str | None = None, + ): + with pytest.raises(ValueError) as error_info: + compute_snippet_text_with_substitutions(body, substitutions, name) + if expected_text is not None: + assert expected_text in str(error_info.value) + + def test_substitution_only(): + body = "$0" + substitution = {"0": "test"} + expected = "test" + assert_substituting_matches_expected(body, substitution, expected) + + def test_substituting_first(): + substitution = {"1": "test"} + expected = "def test($2):\n\t$0" + assert_substituting_matches_expected( + FUNCTION_DECLARATION_BODY, substitution, expected + ) + + def test_substituting_second(): + substitution = {"2": "test"} + expected = "def $1(test):\n\t$0" + assert_substituting_matches_expected( + FUNCTION_DECLARATION_BODY, substitution, expected + ) + + def test_substituting_third(): + substitution = {"0": "test"} + expected = "def $1($2):\n\ttest" + assert_substituting_matches_expected( + FUNCTION_DECLARATION_BODY, substitution, expected + ) + + def test_substituting_all(): + substitution = {"0": "third", "1": "first", "2": "second"} + expected = "def first(second):\n\tthird" + assert_substituting_matches_expected( + FUNCTION_DECLARATION_BODY, substitution, expected + ) + + def test_error_message_contains_body(): + substitution = {"9": "test"} + assert_substituting_raises_exception( + FUNCTION_DECLARATION_BODY, + substitution, + expected_text=FUNCTION_DECLARATION_BODY, + ) + + def test_error_message_contains_name(): + substitution = {"9": "test"} + name = "function declaration" + assert_substituting_raises_exception( + FUNCTION_DECLARATION_BODY, substitution, name=name, expected_text=name + )