feat: add reliable trigger and worker message blocks
Some checks failed
tamq-ci / test (push) Failing after 6s
Some checks failed
tamq-ci / test (push) Failing after 6s
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
This commit is contained in:
parent
72f22a13f7
commit
17b6bd7ae7
25 changed files with 588 additions and 67 deletions
|
|
@ -60,6 +60,10 @@ class TerminalOutputObserver:
|
|||
self.text: list[str] = []
|
||||
self.state = "normal"
|
||||
self.string_kind = ""
|
||||
self.csi: list[str] = []
|
||||
self.cursor_row: int | None = None
|
||||
self.after_cr = False
|
||||
self.empty_cr = False
|
||||
self.operator_lines: deque[tuple[str, float]] = deque(maxlen=32)
|
||||
self.last_redraw: str | None = None
|
||||
|
||||
|
|
@ -72,10 +76,12 @@ class TerminalOutputObserver:
|
|||
self.operator_lines.popleft()
|
||||
return any(candidate == line for candidate, _ in self.operator_lines)
|
||||
|
||||
def _emit(self, *, redraw: bool = False) -> None:
|
||||
def _emit(self, *, redraw: bool = False, allow_empty: bool = False) -> None:
|
||||
line = "".join(self.text)
|
||||
self.text.clear()
|
||||
if not line:
|
||||
if allow_empty:
|
||||
self.on_line("")
|
||||
return
|
||||
if self._is_recent_operator_echo(line):
|
||||
return
|
||||
|
|
@ -84,21 +90,62 @@ class TerminalOutputObserver:
|
|||
self.last_redraw = line if redraw else None
|
||||
self.on_line(line)
|
||||
|
||||
def _move_cursor(self, final: str) -> None:
|
||||
parameters = "".join(self.csi)
|
||||
previous = self.cursor_row
|
||||
new_row: int | None = None
|
||||
try:
|
||||
first = int((parameters.split(";", 1)[0] or "1"))
|
||||
except ValueError:
|
||||
first = 1
|
||||
if final in "Hf":
|
||||
new_row = first
|
||||
elif final == "d":
|
||||
new_row = first
|
||||
elif final in "BE" and previous is not None:
|
||||
new_row = previous + first
|
||||
elif final in "AF" and previous is not None:
|
||||
new_row = max(1, previous - first)
|
||||
if (
|
||||
previous is not None
|
||||
and new_row is not None
|
||||
and new_row > previous + 1
|
||||
):
|
||||
self.on_line("")
|
||||
if new_row is not None:
|
||||
self.cursor_row = new_row
|
||||
|
||||
def feed(self, data: bytes) -> None:
|
||||
for character in self.decoder.decode(data):
|
||||
if self.state == "normal":
|
||||
if character == "\x1b":
|
||||
self.after_cr = False
|
||||
self.state = "esc"
|
||||
elif character in "\r\n":
|
||||
self._emit(redraw=character == "\r")
|
||||
elif character == "\r":
|
||||
self.empty_cr = not self.text
|
||||
self._emit(redraw=True)
|
||||
self.after_cr = True
|
||||
elif character == "\n":
|
||||
if self.after_cr:
|
||||
if self.empty_cr:
|
||||
self._emit(allow_empty=True)
|
||||
else:
|
||||
self._emit(allow_empty=True)
|
||||
self.after_cr = False
|
||||
self.empty_cr = False
|
||||
if self.cursor_row is not None:
|
||||
self.cursor_row += 1
|
||||
elif character in ("\b", "\x7f"):
|
||||
self.after_cr = False
|
||||
if self.text:
|
||||
self.text.pop()
|
||||
elif character == "\t" or character >= " ":
|
||||
self.after_cr = False
|
||||
self.text.append(character)
|
||||
elif self.state == "esc":
|
||||
if character == "[":
|
||||
self.state = "csi"
|
||||
self.csi.clear()
|
||||
elif character in "]P_^":
|
||||
self.state = "string"
|
||||
self.string_kind = character
|
||||
|
|
@ -108,7 +155,10 @@ class TerminalOutputObserver:
|
|||
if "@" <= character <= "~":
|
||||
if character in self._CURSOR_BOUNDARIES:
|
||||
self._emit(redraw=True)
|
||||
self._move_cursor(character)
|
||||
self.state = "normal"
|
||||
else:
|
||||
self.csi.append(character)
|
||||
elif self.state == "string":
|
||||
if character == "\x07" and self.string_kind == "]":
|
||||
self.state = "normal"
|
||||
|
|
@ -248,6 +298,9 @@ class PtyTap:
|
|||
if self.ready_file is not None:
|
||||
self.ready_file.unlink(missing_ok=True)
|
||||
self.output_observer.flush()
|
||||
flush_worker = getattr(self.broker, "flush_worker_message", None)
|
||||
if flush_worker is not None:
|
||||
flush_worker()
|
||||
for signum, handler in saved_handlers.items():
|
||||
signal.signal(signum, handler)
|
||||
if saved_terminal is not None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue