From 693c165c512a58c9e1d233c307074dbc251bf833 Mon Sep 17 00:00:00 2001 From: Edern Deneuville Date: Fri, 11 Sep 2026 14:05:34 +0200 Subject: [PATCH] Insert Word equation objects for math output --- src/ai_typewriter/key_stepper.py | 47 +++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/ai_typewriter/key_stepper.py b/src/ai_typewriter/key_stepper.py index 3555a6d..4ad69b5 100644 --- a/src/ai_typewriter/key_stepper.py +++ b/src/ai_typewriter/key_stepper.py @@ -2,50 +2,63 @@ from __future__ import annotations from dataclasses import dataclass, field from threading import Lock -from typing import Optional +from typing import Optional, Sequence import keyboard +@dataclass(frozen=True) +class TypeAction: + kind: str + value: str + + @dataclass class KeyStepper: - text: str + text: str | Sequence[TypeAction] delay: float = 0.0 _index: int = 0 _hook: Optional[object] = None _lock: Lock = field(default_factory=Lock) _injecting: bool = False + _actions: list[TypeAction] = field(init=False) + + def __post_init__(self) -> None: + if isinstance(self.text, str): + self._actions = [TypeAction("text", char) for char in self.text] + else: + self._actions = list(self.text) def start(self) -> None: - if not self.text: + if not self._actions: return if self._hook is not None: return self.type_next_character() - if self._index >= len(self.text): + if self._index >= len(self._actions): return - # suppress=True blocks the physical key while the callback types the next AI character. + # suppress=True blocks the physical key while the callback types the next AI character/action. self._hook = keyboard.hook(self._on_event, suppress=True) @property def remaining_characters(self) -> int: - return max(len(self.text) - self._index, 0) + return max(len(self._actions) - self._index, 0) def type_next_character(self) -> None: with self._lock: - if self._index >= len(self.text): + if self._index >= len(self._actions): self.stop() return - char = self.text[self._index] + action = self._actions[self._index] self._index += 1 self._injecting = True try: - self._type_char(char) + self._type_action(action) finally: self._injecting = False - if self._index >= len(self.text): + if self._index >= len(self._actions): self.stop() def stop(self) -> None: @@ -59,6 +72,20 @@ class KeyStepper: return self.type_next_character() + def _type_action(self, action: TypeAction) -> None: + if action.kind == "equation": + self._type_word_equation(action.value) + return + for char in action.value: + self._type_char(char) + + def _type_word_equation(self, expression: str) -> None: + keyboard.send("alt+=") + keyboard.write(expression, delay=self.delay, exact=True) + # Word converts much of UnicodeMath after Space; Right exits the equation object. + keyboard.send("space") + keyboard.send("right") + def _type_char(self, char: str) -> None: if char == "\n": keyboard.send("enter")