fix(injection): send newlines as Return on the xdotool backend (#962)

* fix(injection): send newlines as Return on the xdotool backend

xdotool type maps '\n' to the Linefeed keysym, which GTK targets
silently drop — "new line" produced nothing on X11 even though the
segment reached the injector.  Split each chunk on '\n' and press
Return between typed segments so a newline lands as a real keypress
in every app.

Verified against a live GtkTextView on Xvfb: the same "A\nB" input
that previously produced 'AB' now produces 'A\nB'.

Fixes #955

* fix(commands): consume trailing punctuation after mid-utterance commands

"New paragraph." inside a longer utterance substituted correctly but
left the engine's sentence-final period typed after the break
("\\n\\n. Does this work now?").  Text-command substitution now
consumes a run of trailing , . ! ? ; : … when it precedes whitespace
or the end of the segment — the reporter's comma-phrased case
("…, new paragraph, …") stops leaking the stray comma the same way.
A mark glued to following text ("newline.io") is untouched.

Fixes #955

* fix(injection,commands): resume mid-chunk on retry, don't eat produced marks

Greptile review follow-ups on #962:

- A failed Return mid-chunk left 'typed' at the chunk start, so the
  retry re-sent text xdotool had already delivered.  'typed' now
  advances per typed segment and per Return keypress, so the retry
  resumes exactly at the un-typed position — and the clipboard
  fallback reports the same progress if retries run out.
- _TRAILING_PUNCTUATION could consume a mark produced by an earlier
  command replacement ('hello new line exclamation mark' gave '\\n'
  instead of '\\n!').  The eater now only consumes marks before
  whitespace; the end-of-utterance case ('hello new line.') is a
  pre-pass on the raw segment before any substitution runs.

* style: black-format comment indent and test condition

* fix(commands): strip segment before the end-of-utterance cleanup

Trailing whitespace ('hello new line. ') hid the final mark from the
pre-pass: the raw segment ended in a space, and the stripped segment
left the dot at end-of-string where the mid-utterance eater cannot
touch it.  Strip first, then run the cleanup.

Also add the missing type hints on the new test helper.

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
This commit is contained in:
devin-ai-integration[bot]
2026-10-16 19:05:34 -07:00
committed by GitHub
co-authored by Jatin K Malik
parent e866cb85e4
commit d3ba4ee743
4 changed files with 194 additions and 22 deletions
+33
View File
@@ -592,3 +592,36 @@ class TestStandaloneCommandPunctuation(unittest.TestCase):
result, actions = self.processor.process_text("...")
self.assertEqual(result, "...")
self.assertEqual(actions, [])
def test_mid_utterance_trailing_punctuation_is_consumed(self) -> None:
"""Punctuation right after a mid-utterance command does not type (#955)."""
cases = [
(
"Testing 1, 2, 3. New paragraph. Does this work now?",
"Testing 1, 2, 3. \n\n Does this work now?",
),
(
"Testing 123, new paragraph, does this work now?",
"Testing 123, \n\n does this work now?",
),
("line one, new line, line two", "line one, \n line two"),
("hello new line.", "hello \n"),
("hello new line. ", "hello \n"),
]
for input_text, expected in cases:
with self.subTest(input_text=input_text):
result, actions = self.processor.process_text(input_text)
self.assertEqual(result, expected)
self.assertEqual(actions, [])
def test_trailing_punct_eaten_only_before_space_or_end(self) -> None:
"""A dot glued to following text is not sentence punctuation."""
result, actions = self.processor.process_text("use new paragraph.done")
self.assertEqual(result, "use \n\n.done")
self.assertEqual(actions, [])
def test_command_produced_punctuation_is_not_eaten(self) -> None:
"""A mark from an earlier command replacement survives (#955)."""
result, actions = self.processor.process_text("hello new line exclamation mark")
self.assertEqual(result, "hello \n!")
self.assertEqual(actions, [])