From 5565e914052004f6ff8221d2e68f513a1b308613 Mon Sep 17 00:00:00 2001 From: Nicholas Riley Date: Sun, 7 Sep 2025 13:20:21 -0400 Subject: [PATCH 1/2] Properly position the cursor at the first stop in some cases. Applies when: - there is text on the line after the inserted snippet - the first stop in the snippet is on the last (or only) line Also documents an assumption in move_to_correct_column that caused the incorrect behavior. --- core/snippets/snippets_insert_raw_text.py | 54 +++++++++++++---------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/core/snippets/snippets_insert_raw_text.py b/core/snippets/snippets_insert_raw_text.py index 721f5cc821..32e1bc92aa 100644 --- a/core/snippets/snippets_insert_raw_text.py +++ b/core/snippets/snippets_insert_raw_text.py @@ -21,7 +21,7 @@ desc="""If true, inserting snippets as raw text will always be done through pasting""", ) -RE_STOP = re.compile(r"\$(\d+|\w+)|\$\{(\d+|\w+)\}|\$\{(\d+|\w+):(.+)\}") +RE_STOP = re.compile(r"\\?(?:\$(\d+|\w+)|\$\{(\d+|\w+)\}|\$\{(\d+|\w+):(.+)\})") LAST_SNIPPET_HOLE_KEY_VALUE = 1000 @@ -157,34 +157,40 @@ def parse_snippet(body: str): # Some IM services will send the message on a tab body = format_tabs(body) - # Replace variable with appropriate value/text - body = re.sub(r"\$TM_SELECTED_TEXT", lambda _: actions.edit.selected_text(), body) - body = re.sub(r"\$CLIPBOARD", lambda _: actions.clip.text(), body) - lines = body.splitlines() stops: list[Stop] = [] for i, line in enumerate(lines): - match = RE_STOP.search(line) - - while match: - stops.append( - Stop( - name=match.group(1) or match.group(2) or match.group(3), - rows_up=len(lines) - i - 1, - columns_left=0, - row=i, - col=match.start(), - ) - ) - - # Remove tab stops and variables. + start = 0 + while match := RE_STOP.search(line, start): stop_text = match.group(0) - default_value = match.group(4) or "" - line = line.replace(stop_text, default_value, 1) - - # Might have multiple stops on the same line - match = RE_STOP.search(line) + if stop_text[0] == "\\": + # Remove escape for $ + value = stop_text[1:] + # Don't match now-unescaped $ as a stop + start = match.end() - 1 + else: + name = match.group(1) or match.group(2) or match.group(3) + value = match.group(4) or "" + + match name: + case "TM_SELECTED_TEXT": + value = actions.edit.selected_text() or value + case "CLIPBOARD": + value = actions.clip.text() or value + case _: + stops.append( + Stop( + name=name, + rows_up=len(lines) - i - 1, + columns_left=0, + row=i, + col=match.start(), + ) + ) + + # Remove/replace escaped $, tab stops and variables. + line = line.replace(stop_text, value, 1) # Update existing line lines[i] = line From 54ad0ca155d23f4f5348956dfe53d94eee0ec232 Mon Sep 17 00:00:00 2001 From: Nicholas Riley Date: Sun, 14 Sep 2025 13:15:23 -0400 Subject: [PATCH 2/2] Comment the stop (or escaped $) regex and optimize it. Don't bother to match anything further after we've found \$. --- core/snippets/snippets_insert_raw_text.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/core/snippets/snippets_insert_raw_text.py b/core/snippets/snippets_insert_raw_text.py index 32e1bc92aa..b51484cf8a 100644 --- a/core/snippets/snippets_insert_raw_text.py +++ b/core/snippets/snippets_insert_raw_text.py @@ -21,7 +21,21 @@ desc="""If true, inserting snippets as raw text will always be done through pasting""", ) -RE_STOP = re.compile(r"\\?(?:\$(\d+|\w+)|\$\{(\d+|\w+)\}|\$\{(\d+|\w+):(.+)\})") +RE_STOP = re.compile( + r""" + (?:\\\$) (?# Escaped $ - not a stop; skip and don't capture anything) + | + (?: + \$(\d+|\w+) (?# $... where ... is a captured stop number or variable) + | + \$\{(\d+|\w+)\} (?# ${...} where ... is as above) + | + \$\{(\d+|\w+):(.+)\} (?# ${...:xxx} where ... is as above and xxx a default value) + ) + """, + re.VERBOSE, +) + LAST_SNIPPET_HOLE_KEY_VALUE = 1000