Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
This commit is contained in:
parent
d7d92502b4
commit
8186550c9a
10 changed files with 379 additions and 33 deletions
131
src/tamq/composer.py
Normal file
131
src/tamq/composer.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import termios
|
||||
import tty
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
class ComposerError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def recipient_order(
|
||||
current_repo: str,
|
||||
latest_counterparty: str | None,
|
||||
session_repos: Sequence[str],
|
||||
) -> tuple[str, ...]:
|
||||
"""Put the latest sender first, then the remaining peer repositories."""
|
||||
ordered: list[str] = []
|
||||
for repo in ([latest_counterparty] if latest_counterparty else []):
|
||||
if (
|
||||
repo != current_repo
|
||||
and repo not in ordered
|
||||
and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", repo) is not None
|
||||
):
|
||||
ordered.append(repo)
|
||||
for repo in session_repos:
|
||||
if (
|
||||
repo != current_repo
|
||||
and repo not in ordered
|
||||
and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", repo) is not None
|
||||
):
|
||||
ordered.append(repo)
|
||||
return tuple(ordered)
|
||||
|
||||
|
||||
def _remove_last_character(value: bytearray) -> None:
|
||||
if not value:
|
||||
return
|
||||
removed = value.pop()
|
||||
if removed & 0xC0 == 0x80:
|
||||
while value and value[-1] & 0xC0 == 0x80:
|
||||
value.pop()
|
||||
if value and value[-1] & 0xC0 == 0xC0:
|
||||
value.pop()
|
||||
|
||||
|
||||
def compose_message(
|
||||
recipients: Sequence[str],
|
||||
*,
|
||||
input_fd: int | None = None,
|
||||
output_fd: int | None = None,
|
||||
) -> tuple[str, str] | None:
|
||||
"""Read one literal terminal line while Tab cycles the visible recipient."""
|
||||
if not recipients:
|
||||
raise ComposerError("no recipient is available")
|
||||
try:
|
||||
source = sys.stdin.fileno() if input_fd is None else input_fd
|
||||
destination = sys.stderr.fileno() if output_fd is None else output_fd
|
||||
except (AttributeError, OSError) as exc:
|
||||
raise ComposerError("interactive composition requires a terminal") from exc
|
||||
if not os.isatty(source) or not os.isatty(destination):
|
||||
raise ComposerError("interactive composition requires a terminal")
|
||||
|
||||
saved = termios.tcgetattr(source)
|
||||
draft = bytearray()
|
||||
recipient_index = 0
|
||||
escape_state = 0
|
||||
|
||||
def write(value: bytes) -> None:
|
||||
offset = 0
|
||||
while offset < len(value):
|
||||
offset += os.write(destination, value[offset:])
|
||||
|
||||
def render() -> None:
|
||||
prompt = f"@{recipients[recipient_index]}: ".encode("utf-8")
|
||||
write(b"\r\x1b[2K" + prompt + draft)
|
||||
|
||||
try:
|
||||
tty.setraw(source)
|
||||
render()
|
||||
while True:
|
||||
value = os.read(source, 1)
|
||||
if not value:
|
||||
write(b"\r\n")
|
||||
return None
|
||||
byte = value[0]
|
||||
if escape_state == 1:
|
||||
escape_state = 2 if byte in (ord("["), ord("O")) else 0
|
||||
continue
|
||||
if escape_state == 2:
|
||||
if 0x40 <= byte <= 0x7E:
|
||||
escape_state = 0
|
||||
continue
|
||||
if byte == 0x1B:
|
||||
escape_state = 1
|
||||
elif byte == 0x09:
|
||||
recipient_index = (recipient_index + 1) % len(recipients)
|
||||
render()
|
||||
elif byte in (0x0A, 0x0D):
|
||||
try:
|
||||
body = draft.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
write(b"\a")
|
||||
continue
|
||||
if not body.strip():
|
||||
write(b"\a")
|
||||
continue
|
||||
write(b"\r\n")
|
||||
return recipients[recipient_index], body
|
||||
elif byte == 0x03:
|
||||
write(b"^C\r\n")
|
||||
return None
|
||||
elif byte == 0x04 and not draft:
|
||||
write(b"\r\n")
|
||||
return None
|
||||
elif byte in (0x08, 0x7F):
|
||||
_remove_last_character(draft)
|
||||
render()
|
||||
elif byte == 0x15:
|
||||
draft.clear()
|
||||
render()
|
||||
elif byte >= 0x20:
|
||||
draft.extend(value)
|
||||
write(value)
|
||||
else:
|
||||
write(b"\a")
|
||||
finally:
|
||||
termios.tcsetattr(source, termios.TCSADRAIN, saved)
|
||||
Loading…
Add table
Add a link
Reference in a new issue