feat: add -v/--replace to overwrite selection with translation - #3
feat: add -v/--replace to overwrite selection with translation#3Penguin-SAMA wants to merge 5 commits into
Conversation
Reads the user's primary selection, translates it, then simulates typing (wtype / ydotool / xdotool) so the translation overwrites the selected text in place — no clipboard detour.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 68cb6a1898
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
|
|
||
| def _run_replace() -> int: | ||
| text = _read_from_selection() |
There was a problem hiding this comment.
Read only active selection before --replace typing
_run_replace pulls input from _read_from_selection(), which falls back to clipboard data when no primary selection is available. In -v/--replace mode, that means invoking the command with no active selection but a non-empty clipboard still translates and injects text into the focused app, instead of reporting "未读取到选中文本"; this can unintentionally overwrite user content. Replace mode should require an actual current selection (no clipboard fallback) before calling _type_text.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Adds a new -v/--replace CLI mode that translates the current selection and replaces it in-place by simulating keyboard input (instead of writing to the clipboard), plus documentation updates.
Changes:
- Implement
_type_text()and a new_run_replace()flow for in-place replacement with notifications. - Add
-v/--replaceargument handling and enforce mutual exclusion with-s/--selection. - Update README usage and document dependencies for replacement mode.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/localtrans_cli/cli.py |
Adds --replace flag, typing-injection execution path, and related notifications/CLI validation. |
README.md |
Documents the new -v/--replace mode, example usage, and required tools. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _type_text(text: str) -> None: | ||
| candidates: list[list[str]] = [] | ||
| if shutil.which("wtype"): | ||
| candidates.append(["wtype", "--", text]) | ||
| if shutil.which("ydotool"): | ||
| candidates.append(["ydotool", "type", "--", text]) | ||
| if shutil.which("xdotool"): | ||
| candidates.append(["xdotool", "type", "--clearmodifiers", "--", text]) | ||
|
|
There was a problem hiding this comment.
_type_text() 把待注入的译文作为命令行参数传给 wtype/ydotool/xdotool,这会让文本出现在进程列表(如 /proc/<pid>/cmdline / ps)中,可能泄露敏感内容;同时超长译文也可能触发 OS 的 argv 长度限制。建议优先使用 stdin/临时文件方式喂给注入工具(若工具支持),或至少在 README 中明确这一隐私与长度限制。
|
|
||
| 触发前先用鼠标选中任意一段文本即可。需安装 `wl-clipboard` / `xclip` / `xsel` 其一用于读写剪贴板,以及 `libnotify`(提供 `notify-send`)用于通知。 | ||
|
|
||
| `-v` / `--replace` 参数会读取当前选中的文本,翻译后通过模拟键入的方式直接覆盖选区,不写入剪贴板,适合在编辑器 / 浏览器输入框等场景中"原地替换"。需要安装 `wtype`(Wayland)、`ydotool` 或 `xdotool`(X11)其一用于键盘事件注入,并同样依赖上面列出的剪贴板读取工具和 `notify-send`。 |
There was a problem hiding this comment.
这里描述 -v/--replace “读取当前选中的文本…覆盖选区”,但当前实现复用了 selection 读取逻辑,会在 primary selection 为空时回退到剪贴板;这会导致无选区时仍可能把剪贴板内容翻译后注入到焦点窗口。建议在文档中明确 --replace 的数据来源与“无选区时的行为”,并与代码实现保持一致(更安全的做法是仅在有选区时才执行替换)。
| `-v` / `--replace` 参数会读取当前选中的文本,翻译后通过模拟键入的方式直接覆盖选区,不写入剪贴板,适合在编辑器 / 浏览器输入框等场景中"原地替换"。需要安装 `wtype`(Wayland)、`ydotool` 或 `xdotool`(X11)其一用于键盘事件注入,并同样依赖上面列出的剪贴板读取工具和 `notify-send`。 | |
| `-v` / `--replace` 参数复用与 `-s` / `--selection` 相同的读取逻辑:优先读取系统主选区(primary selection),若主选区为空则回退到剪贴板。翻译后会通过模拟键入的方式把结果发送到当前焦点窗口,不写入剪贴板;当存在活动选区时,通常会表现为“覆盖选区”,但如果没有选区,则可能把剪贴板内容翻译后作为普通输入注入到当前光标位置。适合在编辑器 / 浏览器输入框等场景中使用,但触发前请确认焦点窗口和选区状态。需要安装 `wtype`(Wayland)、`ydotool` 或 `xdotool`(X11)其一用于键盘事件注入,并同样依赖上面列出的剪贴板读取工具和 `notify-send`。 |
| if not candidates: | ||
| raise TranslationError( | ||
| "未找到按键注入工具,请安装 wtype / ydotool / xdotool 后重试。" | ||
| ) |
There was a problem hiding this comment.
当在 macOS/Windows 上使用 --replace 时,这里的报错会提示安装 wtype/ydotool/xdotool(主要是 Linux 工具),对非 Linux 平台可能具有误导性。建议根据 sys.platform 给出更准确的提示(例如标明 --replace 仅支持 Linux,或列出该平台可用的等价方案)。
| def _run_replace() -> int: | ||
| text = _read_from_selection() |
There was a problem hiding this comment.
_run_replace() 复用了 _read_from_selection(),该函数会在 primary selection 为空时回退读取 clipboard(见其实现遍历 primary + clipboard)。这会导致“无选区但剪贴板有内容”时仍会翻译并注入键入,从而把剪贴板内容意外输入到当前焦点窗口,且与 PR 描述/测试计划里“无选区应提示未读取到选中文本”不一致。建议为 --replace 仅读取 primary selection(或给 _read_from_selection 增加开关禁用回退),确保没有选区时必然失败并提示。
| def _run_replace() -> int: | |
| text = _read_from_selection() | |
| def _read_primary_selection() -> str: | |
| commands: list[list[str]] = [] | |
| if sys.platform != "darwin": | |
| if shutil.which("wl-paste"): | |
| commands.append(["wl-paste", "--no-newline", "--primary"]) | |
| if shutil.which("xclip"): | |
| commands.append(["xclip", "-selection", "primary", "-out"]) | |
| if shutil.which("xsel"): | |
| commands.append(["xsel", "--primary", "--output"]) | |
| for cmd in commands: | |
| text = _run_capture(cmd) | |
| if text: | |
| return text.strip() | |
| return "" | |
| def _run_replace() -> int: | |
| text = _read_primary_selection() |
| "-v", | ||
| "--replace", | ||
| action="store_true", | ||
| help="读取系统主选区/剪贴板中的文本,翻译后通过模拟键入直接替换选中内容。", |
There was a problem hiding this comment.
--replace 的 help 文案写了“主选区/剪贴板”,但该参数的语义是“替换当前选中内容”。如果按预期改成仅依赖 primary selection(避免无选区时误把剪贴板内容注入到焦点窗口),这里的描述也需要同步更新为“读取主选区/选中文本”。
| help="读取系统主选区/剪贴板中的文本,翻译后通过模拟键入直接替换选中内容。", | |
| help="读取系统主选区/选中文本,翻译后通过模拟键入直接替换选中内容。", |
Typing via wtype/ydotool/xdotool couldn't grab selections from Chromium-family apps (no primary-selection support on Wayland) and left the user's keymap in a broken state after injection. Replace the whole pipeline with: simulate Ctrl+C -> read clipboard -> translate -> write clipboard -> simulate Ctrl+V. The translated text stays in the clipboard; the original clipboard is not restored.
Running `lt -v` directly from a terminal lets the simulated Ctrl+C land on that very terminal, which SIGINTs the running process and prints a traceback. `-v` is only meaningful when bound to a compositor shortcut, so: - Ignore SIGINT for the duration of _run_replace so accidental/injected Ctrl+C no longer kills the process mid-flight. - Print a stderr warning when stdout/stdin is a TTY pointing the user at a compositor keybinding. - Document the constraint and show sample bindings in README.
… wait Two issues caused -v to leave the keyboard state broken and never actually copy/paste: 1. wtype was invoked as \`wtype -M ctrl c -m ctrl\`. Without -k, wtype types 'c' as literal text through the text_input protocol, which does not combine with the Ctrl modifier state. The result was that Ctrl+C / Ctrl+V never triggered, and the interleaved modifier press/release around a text_input character event left the compositor's keyboard state confused. Use -k c / -k v so the key is dispatched as a real key event. 2. The initial 120ms wait was too short: when invoked from a compositor bind like Super+Shift+T, the user is usually still holding Super/Shift when we inject Ctrl+C, producing Ctrl+Super+Shift+C instead of Ctrl+C. Bump to 250ms and widen the inter-step sleeps slightly.
Under niri (and some other Wayland compositors), wtype's virtual-keyboard disconnect does not trigger a keymap resend to the previously-focused surface. The result is that after `-v` completes, the target application keeps wtype's synthetic keymap until the user refocuses the window, so keys show up as the wrong characters (classic symptom: Esc behaves as V). ydotool sidesteps this entirely by writing to /dev/uinput, so events reach the compositor through the real kernel input path and are dispatched with the real xkb keymap. Flip the preference order to ydotool -> wtype -> xdotool, and add a LOCALTRANS_INJECT env override for explicit backend selection / debugging.
Summary
-s/--selection之外新增-v/--replace:读取当前选中文本,翻译后通过模拟键入(wtype→ydotool→xdotool type)直接覆盖选区,不写入剪贴板。-s互斥;通过notify-send/osascript推送 "替换完成" / "替换失败" 通知。wtype、X11 下的xdotool/ydotool)。Test plan
python -m unittest discover -s tests -v— 35 passed,包含 5 个新增-v用例(正常替换、空选区、注入失败、拒绝位置参数、与-s互斥)。localtrans -v,确认选区被译文替换并弹出"替换完成"通知。localtrans -v,确认 critical 通知"未读取到选中文本"。localtrans -s -v返回用法错误。