Skip to content

feat: add -v/--replace to overwrite selection with translation - #3

Open
Penguin-SAMA wants to merge 5 commits into
mainfrom
feat/replace-selection
Open

feat: add -v/--replace to overwrite selection with translation#3
Penguin-SAMA wants to merge 5 commits into
mainfrom
feat/replace-selection

Conversation

@Penguin-SAMA

Copy link
Copy Markdown
Owner

Summary

  • -s/--selection 之外新增 -v/--replace:读取当前选中文本,翻译后通过模拟键入(wtypeydotoolxdotool type)直接覆盖选区,不写入剪贴板。
  • -s 互斥;通过 notify-send / osascript 推送 "替换完成" / "替换失败" 通知。
  • README 补充说明和依赖提示(Wayland 下的 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 返回用法错误。

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.
Copilot AI review requested due to automatic review settings April 20, 2026 08:06

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/localtrans_cli/cli.py Outdated


def _run_replace() -> int:
text = _read_from_selection()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/--replace argument 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.

Comment thread src/localtrans_cli/cli.py Outdated
Comment on lines +80 to +88
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])

Copilot AI Apr 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_type_text() 把待注入的译文作为命令行参数传给 wtype/ydotool/xdotool,这会让文本出现在进程列表(如 /proc/<pid>/cmdline / ps)中,可能泄露敏感内容;同时超长译文也可能触发 OS 的 argv 长度限制。建议优先使用 stdin/临时文件方式喂给注入工具(若工具支持),或至少在 README 中明确这一隐私与长度限制。

Copilot uses AI. Check for mistakes.
Comment thread README.md Outdated

触发前先用鼠标选中任意一段文本即可。需安装 `wl-clipboard` / `xclip` / `xsel` 其一用于读写剪贴板,以及 `libnotify`(提供 `notify-send`)用于通知。

`-v` / `--replace` 参数会读取当前选中的文本,翻译后通过模拟键入的方式直接覆盖选区,不写入剪贴板,适合在编辑器 / 浏览器输入框等场景中"原地替换"。需要安装 `wtype`(Wayland)、`ydotool` 或 `xdotool`(X11)其一用于键盘事件注入,并同样依赖上面列出的剪贴板读取工具和 `notify-send`。

Copilot AI Apr 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里描述 -v/--replace “读取当前选中的文本…覆盖选区”,但当前实现复用了 selection 读取逻辑,会在 primary selection 为空时回退到剪贴板;这会导致无选区时仍可能把剪贴板内容翻译后注入到焦点窗口。建议在文档中明确 --replace 的数据来源与“无选区时的行为”,并与代码实现保持一致(更安全的做法是仅在有选区时才执行替换)。

Suggested change
`-v` / `--replace` 参数会读取当前选中的文本,翻译后通过模拟键入的方式直接覆盖选区,不写入剪贴板适合在编辑器 / 浏览器输入框等场景中"原地替换"。需要安装 `wtype`(Wayland)、`ydotool``xdotool`(X11)其一用于键盘事件注入,并同样依赖上面列出的剪贴板读取工具和 `notify-send`
`-v` / `--replace` 参数复用与 `-s` / `--selection` 相同的读取逻辑:优先读取系统主选区(primary selection),若主选区为空则回退到剪贴板。翻译后会通过模拟键入的方式把结果发送到当前焦点窗口,不写入剪贴板;当存在活动选区时,通常会表现为“覆盖选区”,但如果没有选区,则可能把剪贴板内容翻译后作为普通输入注入到当前光标位置。适合在编辑器 / 浏览器输入框等场景中使用,但触发前请确认焦点窗口和选区状态。需要安装 `wtype`(Wayland)、`ydotool``xdotool`(X11)其一用于键盘事件注入,并同样依赖上面列出的剪贴板读取工具和 `notify-send`

Copilot uses AI. Check for mistakes.
Comment thread src/localtrans_cli/cli.py Outdated
Comment on lines +89 to +92
if not candidates:
raise TranslationError(
"未找到按键注入工具,请安装 wtype / ydotool / xdotool 后重试。"
)

Copilot AI Apr 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当在 macOS/Windows 上使用 --replace 时,这里的报错会提示安装 wtype/ydotool/xdotool(主要是 Linux 工具),对非 Linux 平台可能具有误导性。建议根据 sys.platform 给出更准确的提示(例如标明 --replace 仅支持 Linux,或列出该平台可用的等价方案)。

Copilot uses AI. Check for mistakes.
Comment thread src/localtrans_cli/cli.py Outdated
Comment on lines +177 to +178
def _run_replace() -> int:
text = _read_from_selection()

Copilot AI Apr 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_run_replace() 复用了 _read_from_selection(),该函数会在 primary selection 为空时回退读取 clipboard(见其实现遍历 primary + clipboard)。这会导致“无选区但剪贴板有内容”时仍会翻译并注入键入,从而把剪贴板内容意外输入到当前焦点窗口,且与 PR 描述/测试计划里“无选区应提示未读取到选中文本”不一致。建议为 --replace 仅读取 primary selection(或给 _read_from_selection 增加开关禁用回退),确保没有选区时必然失败并提示。

Suggested change
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()

Copilot uses AI. Check for mistakes.
Comment thread src/localtrans_cli/cli.py Outdated
"-v",
"--replace",
action="store_true",
help="读取系统主选区/剪贴板中的文本,翻译后通过模拟键入直接替换选中内容。",

Copilot AI Apr 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--replace 的 help 文案写了“主选区/剪贴板”,但该参数的语义是“替换当前选中内容”。如果按预期改成仅依赖 primary selection(避免无选区时误把剪贴板内容注入到焦点窗口),这里的描述也需要同步更新为“读取主选区/选中文本”。

Suggested change
help="读取系统主选区/剪贴板中的文本,翻译后通过模拟键入直接替换选中内容。",
help="读取系统主选区/选中文本,翻译后通过模拟键入直接替换选中内容。",

Copilot uses AI. Check for mistakes.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants