ring-cli 0.6.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ring_cli-0.6.0 → ring_cli-0.7.0}/PKG-INFO +3 -2
- {ring_cli-0.6.0 → ring_cli-0.7.0}/README.en.md +2 -1
- {ring_cli-0.6.0 → ring_cli-0.7.0}/pyproject.toml +1 -1
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/doctor.py +4 -1
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/config.py +1 -1
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/__init__.py +16 -4
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/base.py +4 -1
- ring_cli-0.7.0/src/ring/focus/neovim.py +184 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/locale/en/LC_MESSAGES/ring.mo +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/locale/en/LC_MESSAGES/ring.po +3 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/locale/ring.pot +23 -19
- {ring_cli-0.6.0 → ring_cli-0.7.0}/LICENSE +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/__init__.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/__main__.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/cli.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/__init__.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/_args.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/completion.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/focus.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/gc.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/hook.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/commands/stats.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/applescript.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/iterm2.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/linux_wm.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/terminal.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/focus/tmux.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/gc.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/hook.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/hook_protocol.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/i18n.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/ipc.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/labels.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/__init__.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/base.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/command.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/notify_send.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/ntfy.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/osascript_notifier.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/terminal_notifier.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/notify/webhook.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/osascript.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/plugins.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/registry.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/sources/__init__.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/sources/base.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/sources/claude_code.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/sources/codex.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/sources/hook_registry.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/stats.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/transcript.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/tui.py +0 -0
- {ring_cli-0.6.0 → ring_cli-0.7.0}/src/ring/watcher.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ring-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: RiNG — Realtime Instance Notification Grid for active agent CLI sessions.
|
|
5
5
|
Keywords: claude-code,codex,tui,dashboard,session,monitor,rich
|
|
6
6
|
Author: Wei Lee
|
|
@@ -162,6 +162,7 @@ Completes subcommands, flags, and `config set` keys.
|
|
|
162
162
|
Select a session and press `Enter`. RiNG focuses the terminal where that session is running.
|
|
163
163
|
|
|
164
164
|
- **tmux**: switches directly to the pane via `switch-client`.
|
|
165
|
+
- **Neovim `:terminal`**: uses the terminal job's inherited `$NVIM` server socket to switch to the exact buffer, then lets the outer tmux or terminal focuser raise its pane/window.
|
|
165
166
|
- **iTerm2 / Terminal.app** on macOS: uses the session `tty` and AppleScript to focus the matching tab. The first run may ask for macOS Automation permission.
|
|
166
167
|
- **Linux X11 window** (`wmctrl`, best-effort fallback): for Linux without tmux — walks from the `tty` up to the terminal window that owns it and raises it via `wmctrl`. **Limits**: X11 only (usually a no-op on Wayland), raises the whole window but cannot pick the tab, and gnome-terminal's client/server model may not match. Requires `apt install wmctrl`.
|
|
167
168
|
|
|
@@ -371,7 +372,7 @@ notify_repeat_max = 3
|
|
|
371
372
|
notify_ntfy_url = "" # full ntfy topic URL enables phone push (e.g. https://ntfy.sh/my-topic)
|
|
372
373
|
notify_webhook_url = "" # URL enables the generic webhook backend (JSON POST)
|
|
373
374
|
notify_also = [] # extra backends fired besides the primary, e.g. ["ntfy"]
|
|
374
|
-
focusers = ["tmux", "iTerm2", "Terminal", "linux-wm"]
|
|
375
|
+
focusers = ["Neovim", "tmux", "iTerm2", "Terminal", "linux-wm"]
|
|
375
376
|
plugins = [] # external plugin modules imported at startup (see Extending)
|
|
376
377
|
|
|
377
378
|
[colors]
|
|
@@ -137,6 +137,7 @@ Completes subcommands, flags, and `config set` keys.
|
|
|
137
137
|
Select a session and press `Enter`. RiNG focuses the terminal where that session is running.
|
|
138
138
|
|
|
139
139
|
- **tmux**: switches directly to the pane via `switch-client`.
|
|
140
|
+
- **Neovim `:terminal`**: uses the terminal job's inherited `$NVIM` server socket to switch to the exact buffer, then lets the outer tmux or terminal focuser raise its pane/window.
|
|
140
141
|
- **iTerm2 / Terminal.app** on macOS: uses the session `tty` and AppleScript to focus the matching tab. The first run may ask for macOS Automation permission.
|
|
141
142
|
- **Linux X11 window** (`wmctrl`, best-effort fallback): for Linux without tmux — walks from the `tty` up to the terminal window that owns it and raises it via `wmctrl`. **Limits**: X11 only (usually a no-op on Wayland), raises the whole window but cannot pick the tab, and gnome-terminal's client/server model may not match. Requires `apt install wmctrl`.
|
|
142
143
|
|
|
@@ -346,7 +347,7 @@ notify_repeat_max = 3
|
|
|
346
347
|
notify_ntfy_url = "" # full ntfy topic URL enables phone push (e.g. https://ntfy.sh/my-topic)
|
|
347
348
|
notify_webhook_url = "" # URL enables the generic webhook backend (JSON POST)
|
|
348
349
|
notify_also = [] # extra backends fired besides the primary, e.g. ["ntfy"]
|
|
349
|
-
focusers = ["tmux", "iTerm2", "Terminal", "linux-wm"]
|
|
350
|
+
focusers = ["Neovim", "tmux", "iTerm2", "Terminal", "linux-wm"]
|
|
350
351
|
plugins = [] # external plugin modules imported at startup (see Extending)
|
|
351
352
|
|
|
352
353
|
[colors]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# import 的 module(見 [tool.uv.build-backend] module-name)與 CLI 指令仍是 `ring`。
|
|
3
3
|
[project]
|
|
4
4
|
name = "ring-cli"
|
|
5
|
-
version = "0.
|
|
5
|
+
version = "0.7.0"
|
|
6
6
|
description = "RiNG — Realtime Instance Notification Grid for active agent CLI sessions."
|
|
7
7
|
readme = "README.en.md"
|
|
8
8
|
requires-python = ">=3.13"
|
|
@@ -87,7 +87,10 @@ def run_doctor(args: list[str]) -> int:
|
|
|
87
87
|
width_f = max(len(f.name) for f in focuser_list) if focuser_list else 10
|
|
88
88
|
for f in focuser_list:
|
|
89
89
|
name_lower = f.name.lower()
|
|
90
|
-
if name_lower == "
|
|
90
|
+
if name_lower == "neovim":
|
|
91
|
+
avail = shutil.which("nvim") is not None
|
|
92
|
+
avail_str = _("可用") if avail else _("不可用(nvim 不在 PATH)")
|
|
93
|
+
elif name_lower == "tmux":
|
|
91
94
|
avail = shutil.which("tmux") is not None
|
|
92
95
|
avail_str = _("可用") if avail else _("不可用(tmux 不在 PATH)")
|
|
93
96
|
elif shutil.which("osascript") is None:
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
notify_ntfy_url = "https://ntfy.sh/my-topic" # 設了才啟用 ntfy 後端(推到手機)
|
|
24
24
|
notify_webhook_url = "https://example.com/hook" # 設了才啟用 webhook 後端(JSON POST)
|
|
25
25
|
notify_also = ["ntfy"] # 主後端之外「加發」的後端(例如桌面通知+手機各一份)
|
|
26
|
-
focusers = ["tmux", "iTerm2", "Terminal", "linux-wm"] # 跳轉嘗試順序;省略=內建預設
|
|
26
|
+
focusers = ["Neovim", "tmux", "iTerm2", "Terminal", "linux-wm"] # 跳轉嘗試順序;省略=內建預設
|
|
27
27
|
plugins = ["my_ring_plugin"] # 啟動時 import 的外部 plugin 模組(自行 register_*)
|
|
28
28
|
"""
|
|
29
29
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
每個終端是一個 ``Focuser``(見 ``base.Focuser``):core 不認識任何具體終端,
|
|
4
4
|
只依序問每個 focuser「這個 session 歸不歸你管」。要支援新終端=寫一個模組、
|
|
5
|
-
``register_focuser()`` 註冊,core 零改動。內建:
|
|
6
|
-
Linux X11 視窗(wmctrl,best-effort fallback)。
|
|
5
|
+
``register_focuser()`` 註冊,core 零改動。內建:Neovim terminal / tmux / iTerm2 /
|
|
6
|
+
Terminal.app / Linux X11 視窗(wmctrl,best-effort fallback)。
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
@@ -12,14 +12,16 @@ from ring.config import get_config
|
|
|
12
12
|
from ring.focus.base import Focuser
|
|
13
13
|
from ring.focus.iterm2 import focuser as _iterm2
|
|
14
14
|
from ring.focus.linux_wm import focuser as _linux_wm
|
|
15
|
+
from ring.focus.neovim import focuser as _neovim
|
|
15
16
|
from ring.focus.terminal import focuser as _terminal
|
|
16
17
|
from ring.focus.tmux import focuser as _tmux
|
|
17
18
|
from ring.i18n import gettext as _
|
|
18
19
|
from ring.registry import Session, Status
|
|
19
20
|
|
|
20
|
-
# 內建 focuser。順序可由 config 的 `focusers` 覆寫。
|
|
21
|
-
#
|
|
21
|
+
# 內建 focuser。順序可由 config 的 `focusers` 覆寫。Neovim 先切內層 terminal buffer,
|
|
22
|
+
# 再由 tmux / macOS app / linux-wm 把外層帶到前景。
|
|
22
23
|
_BUILTIN: dict[str, Focuser] = {
|
|
24
|
+
"Neovim": _neovim,
|
|
23
25
|
"tmux": _tmux,
|
|
24
26
|
"iTerm2": _iterm2,
|
|
25
27
|
"Terminal": _terminal,
|
|
@@ -55,14 +57,24 @@ def jump(session: Session) -> tuple[bool, str]:
|
|
|
55
57
|
if session.status is Status.ENDED:
|
|
56
58
|
return False, _("已離場的 session 無法跳轉")
|
|
57
59
|
failures: list[str] = []
|
|
60
|
+
preparations: list[str] = []
|
|
58
61
|
for focuser in _FOCUSERS:
|
|
59
62
|
result = focuser.try_focus(session)
|
|
60
63
|
if result is None:
|
|
61
64
|
continue
|
|
62
65
|
ok, msg = result
|
|
63
66
|
if ok:
|
|
67
|
+
if getattr(focuser, "continue_after_success", False):
|
|
68
|
+
preparations.append(msg)
|
|
69
|
+
continue
|
|
70
|
+
if preparations:
|
|
71
|
+
return True, "; ".join([*preparations, msg])
|
|
64
72
|
return True, msg
|
|
65
73
|
failures.append(f"{focuser.name}: {msg}")
|
|
74
|
+
if preparations and failures:
|
|
75
|
+
return False, "; ".join([*preparations, *failures])
|
|
76
|
+
if preparations:
|
|
77
|
+
return True, "; ".join(preparations)
|
|
66
78
|
if failures:
|
|
67
79
|
return False, "; ".join(failures)
|
|
68
80
|
if session.tty:
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
``try_focus`` 的回傳語意:
|
|
4
4
|
None → 不歸我管(dispatcher 換下一個)
|
|
5
5
|
(True, msg) → 我接手且成功
|
|
6
|
-
(False, err) →
|
|
6
|
+
(False, err) → 我接手但失敗(記下原因,再試其他 focuser)
|
|
7
|
+
|
|
8
|
+
一般 focuser 成功後 dispatcher 就停止;需要先處理內層、再交給外層 focuser 的實作可設
|
|
9
|
+
``continue_after_success = True``(例如 Neovim 先切 terminal buffer,再讓 tmux 聚焦 pane)。
|
|
7
10
|
"""
|
|
8
11
|
|
|
9
12
|
from __future__ import annotations
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Neovim terminal focuser:切回承載 session 的 ``:terminal`` buffer。
|
|
2
|
+
|
|
3
|
+
Neovim 的 terminal job 會繼承 ``NVIM``(server socket)環境變數。從 session 的 tty
|
|
4
|
+
找到該 job,再沿 parent process chain 找到 owning nvim,就能透過 remote API 精準切換
|
|
5
|
+
buffer。這個 focuser 是「前置聚焦」:切完內層 buffer 後,dispatcher 仍會繼續讓 tmux /
|
|
6
|
+
iTerm2 / Terminal.app 把外層 pane 或視窗帶到前景。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from shutil import which
|
|
18
|
+
|
|
19
|
+
from ring.registry import Session
|
|
20
|
+
|
|
21
|
+
_NVIM_NAMES = {"nvim", "nvim-bin"}
|
|
22
|
+
_NVIM_ENV_RE = re.compile(r"(?:^|\s)NVIM=([^\s]+)")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _run(cmd: list[str]) -> subprocess.CompletedProcess[str] | None:
|
|
26
|
+
try:
|
|
27
|
+
return subprocess.run(cmd, capture_output=True, text=True, timeout=3)
|
|
28
|
+
except (OSError, subprocess.SubprocessError):
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _tty_pids(tty: str) -> list[int]:
|
|
33
|
+
result = _run(["ps", "-o", "pid=", "-t", tty.removeprefix("/dev/")])
|
|
34
|
+
if result is None or result.returncode != 0:
|
|
35
|
+
return []
|
|
36
|
+
pids: list[int] = []
|
|
37
|
+
for value in result.stdout.split():
|
|
38
|
+
try:
|
|
39
|
+
pids.append(int(value))
|
|
40
|
+
except ValueError:
|
|
41
|
+
continue
|
|
42
|
+
return pids
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _process_table() -> tuple[dict[int, int], dict[int, str]]:
|
|
46
|
+
result = _run(["ps", "-eo", "pid=,ppid=,comm="])
|
|
47
|
+
if result is None or result.returncode != 0:
|
|
48
|
+
return {}, {}
|
|
49
|
+
parents: dict[int, int] = {}
|
|
50
|
+
commands: dict[int, str] = {}
|
|
51
|
+
for line in result.stdout.splitlines():
|
|
52
|
+
parts = line.split(None, 2)
|
|
53
|
+
if len(parts) != 3:
|
|
54
|
+
continue
|
|
55
|
+
try:
|
|
56
|
+
pid, ppid = int(parts[0]), int(parts[1])
|
|
57
|
+
except ValueError:
|
|
58
|
+
continue
|
|
59
|
+
parents[pid] = ppid
|
|
60
|
+
commands[pid] = os.path.basename(parts[2])
|
|
61
|
+
return parents, commands
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _ancestors(pid: int, parents: dict[int, int]) -> list[int]:
|
|
65
|
+
chain: list[int] = []
|
|
66
|
+
seen: set[int] = set()
|
|
67
|
+
current = pid
|
|
68
|
+
while current > 1 and current not in seen:
|
|
69
|
+
seen.add(current)
|
|
70
|
+
chain.append(current)
|
|
71
|
+
current = parents.get(current, 0)
|
|
72
|
+
return chain
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _nvim_address(pid: int) -> str:
|
|
76
|
+
"""讀 process 繼承到的 ``NVIM``;Linux 優先用不會混入 argv 的 procfs。"""
|
|
77
|
+
if sys.platform.startswith("linux"):
|
|
78
|
+
try:
|
|
79
|
+
entries = Path(f"/proc/{pid}/environ").read_bytes().split(b"\0")
|
|
80
|
+
except OSError:
|
|
81
|
+
entries = []
|
|
82
|
+
for entry in entries:
|
|
83
|
+
if entry.startswith(b"NVIM="):
|
|
84
|
+
return entry.removeprefix(b"NVIM=").decode(errors="surrogateescape")
|
|
85
|
+
|
|
86
|
+
result = _run(["ps", "eww", "-p", str(pid), "-o", "command="])
|
|
87
|
+
if result is None or result.returncode != 0:
|
|
88
|
+
return ""
|
|
89
|
+
match = _NVIM_ENV_RE.search(result.stdout)
|
|
90
|
+
return match.group(1) if match else ""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _pid_tty(pid: int) -> str:
|
|
94
|
+
result = _run(["ps", "-o", "tty=", "-p", str(pid)])
|
|
95
|
+
if result is None or result.returncode != 0:
|
|
96
|
+
return ""
|
|
97
|
+
tty = result.stdout.strip()
|
|
98
|
+
if not tty or tty in {"?", "??"}:
|
|
99
|
+
return ""
|
|
100
|
+
return tty if tty.startswith("/dev/") else f"/dev/{tty}"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _find_neovim(tty: str) -> tuple[str, int] | None:
|
|
104
|
+
parents, commands = _process_table()
|
|
105
|
+
for pid in _tty_pids(tty):
|
|
106
|
+
address = _nvim_address(pid)
|
|
107
|
+
if not address:
|
|
108
|
+
continue
|
|
109
|
+
for ancestor in _ancestors(pid, parents):
|
|
110
|
+
if commands.get(ancestor) in _NVIM_NAMES:
|
|
111
|
+
return address, ancestor
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _remote_expr(tty: str) -> str:
|
|
116
|
+
"""建立在 server 端找 terminal job tty、切 tab/window/buffer 的 Lua expression。"""
|
|
117
|
+
target = json.dumps(tty)
|
|
118
|
+
lua = f"""(function()
|
|
119
|
+
local target = {target}
|
|
120
|
+
local function job_tty(pid)
|
|
121
|
+
local value = vim.trim(vim.fn.system({{'ps', '-o', 'tty=', '-p', tostring(pid)}}))
|
|
122
|
+
if value == '' or value == '?' or value == '??' then return '' end
|
|
123
|
+
if vim.startswith(value, '/dev/') then return value end
|
|
124
|
+
return '/dev/' .. value
|
|
125
|
+
end
|
|
126
|
+
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
|
|
127
|
+
local ok, job = pcall(vim.api.nvim_buf_get_var, buf, 'terminal_job_id')
|
|
128
|
+
-- 已死的 :terminal buffer([Process exited])還留著 terminal_job_id,但 channel
|
|
129
|
+
-- 已關,jobpid 會丟 E900——pcall 跳過它,才輪得到後面活著的 buffer。
|
|
130
|
+
local ok_pid, pid = pcall(vim.fn.jobpid, job)
|
|
131
|
+
if ok and ok_pid and job_tty(pid) == target then
|
|
132
|
+
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
|
|
133
|
+
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
|
|
134
|
+
if vim.api.nvim_win_get_buf(win) == buf then
|
|
135
|
+
vim.api.nvim_set_current_tabpage(tab)
|
|
136
|
+
vim.api.nvim_set_current_win(win)
|
|
137
|
+
vim.cmd('startinsert')
|
|
138
|
+
return buf
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
vim.api.nvim_set_current_buf(buf)
|
|
143
|
+
vim.cmd('startinsert')
|
|
144
|
+
return buf
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
return 0
|
|
148
|
+
end)()"""
|
|
149
|
+
return f"luaeval({json.dumps(lua)})"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class NeovimFocuser:
|
|
153
|
+
name = "Neovim"
|
|
154
|
+
continue_after_success = True
|
|
155
|
+
|
|
156
|
+
def try_focus(self, session: Session) -> tuple[bool, str] | None:
|
|
157
|
+
tty = session.tty
|
|
158
|
+
nvim = which("nvim")
|
|
159
|
+
if not tty or not nvim:
|
|
160
|
+
return None
|
|
161
|
+
found = _find_neovim(tty)
|
|
162
|
+
if found is None:
|
|
163
|
+
return None
|
|
164
|
+
address, nvim_pid = found
|
|
165
|
+
result = _run([nvim, "--server", address, "--remote-expr", _remote_expr(tty)])
|
|
166
|
+
if result is None:
|
|
167
|
+
return False, "nvim remote request failed"
|
|
168
|
+
if result.returncode != 0:
|
|
169
|
+
return False, result.stderr.strip() or "nvim remote request failed"
|
|
170
|
+
try:
|
|
171
|
+
buffer = int(result.stdout.strip())
|
|
172
|
+
except ValueError:
|
|
173
|
+
buffer = 0
|
|
174
|
+
if buffer <= 0:
|
|
175
|
+
return False, f"terminal buffer for {tty} not found"
|
|
176
|
+
|
|
177
|
+
# 後續外層 focuser 要比對 nvim 自己所在的 tty,而不是內層 :terminal job 的 tty。
|
|
178
|
+
outer_tty = _pid_tty(nvim_pid)
|
|
179
|
+
if outer_tty:
|
|
180
|
+
session.tty = outer_tty
|
|
181
|
+
return True, f"Neovim buffer {buffer}"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
focuser = NeovimFocuser()
|
|
Binary file
|
|
@@ -8,7 +8,7 @@ msgid ""
|
|
|
8
8
|
msgstr ""
|
|
9
9
|
"Project-Id-Version: PROJECT VERSION\n"
|
|
10
10
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
|
11
|
-
"POT-Creation-Date: 2026-07-03
|
|
11
|
+
"POT-Creation-Date: 2026-07-03 08:44+0800\n"
|
|
12
12
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
13
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
14
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
@@ -61,7 +61,7 @@ msgid "(場館目前沒人上台)"
|
|
|
61
61
|
msgstr ""
|
|
62
62
|
|
|
63
63
|
#: src/ring/cli.py:178 src/ring/cli.py:326 src/ring/commands/doctor.py:31
|
|
64
|
-
#: src/ring/commands/doctor.py:
|
|
64
|
+
#: src/ring/commands/doctor.py:122 src/ring/tui.py:163
|
|
65
65
|
msgid "狀態"
|
|
66
66
|
msgstr ""
|
|
67
67
|
|
|
@@ -98,15 +98,15 @@ msgstr ""
|
|
|
98
98
|
msgid "RiNG 設定檔"
|
|
99
99
|
msgstr ""
|
|
100
100
|
|
|
101
|
-
#: src/ring/cli.py:325 src/ring/commands/doctor.py:
|
|
101
|
+
#: src/ring/cli.py:325 src/ring/commands/doctor.py:121
|
|
102
102
|
msgid "路徑"
|
|
103
103
|
msgstr ""
|
|
104
104
|
|
|
105
|
-
#: src/ring/cli.py:326 src/ring/commands/doctor.py:
|
|
105
|
+
#: src/ring/cli.py:326 src/ring/commands/doctor.py:122
|
|
106
106
|
msgid "已存在"
|
|
107
107
|
msgstr ""
|
|
108
108
|
|
|
109
|
-
#: src/ring/cli.py:326 src/ring/commands/doctor.py:
|
|
109
|
+
#: src/ring/cli.py:326 src/ring/commands/doctor.py:122
|
|
110
110
|
msgid "不存在(全部用內建預設)"
|
|
111
111
|
msgstr ""
|
|
112
112
|
|
|
@@ -434,7 +434,7 @@ msgstr ""
|
|
|
434
434
|
msgid "偵測到 {n} 個 session"
|
|
435
435
|
msgstr ""
|
|
436
436
|
|
|
437
|
-
#: src/ring/commands/doctor.py:45 src/ring/commands/doctor.py:
|
|
437
|
+
#: src/ring/commands/doctor.py:45 src/ring/commands/doctor.py:116
|
|
438
438
|
msgid "偵測失敗"
|
|
439
439
|
msgstr ""
|
|
440
440
|
|
|
@@ -463,7 +463,7 @@ msgid "目前設定"
|
|
|
463
463
|
msgstr ""
|
|
464
464
|
|
|
465
465
|
#: src/ring/commands/doctor.py:68 src/ring/commands/doctor.py:92
|
|
466
|
-
#: src/ring/commands/doctor.py:
|
|
466
|
+
#: src/ring/commands/doctor.py:95 src/ring/commands/doctor.py:102
|
|
467
467
|
msgid "可用"
|
|
468
468
|
msgstr ""
|
|
469
469
|
|
|
@@ -500,40 +500,44 @@ msgid "聚焦終端(focuser)"
|
|
|
500
500
|
msgstr ""
|
|
501
501
|
|
|
502
502
|
#: src/ring/commands/doctor.py:92
|
|
503
|
+
msgid "不可用(nvim 不在 PATH)"
|
|
504
|
+
msgstr ""
|
|
505
|
+
|
|
506
|
+
#: src/ring/commands/doctor.py:95
|
|
503
507
|
msgid "不可用(tmux 不在 PATH)"
|
|
504
508
|
msgstr ""
|
|
505
509
|
|
|
506
|
-
#: src/ring/commands/doctor.py:
|
|
510
|
+
#: src/ring/commands/doctor.py:97
|
|
507
511
|
msgid "不可用(osascript 不在 PATH)"
|
|
508
512
|
msgstr ""
|
|
509
513
|
|
|
510
|
-
#: src/ring/commands/doctor.py:
|
|
514
|
+
#: src/ring/commands/doctor.py:102 src/ring/commands/doctor.py:104
|
|
511
515
|
msgid "不可用(app 沒在跑)"
|
|
512
516
|
msgstr ""
|
|
513
517
|
|
|
514
|
-
#: src/ring/commands/doctor.py:
|
|
518
|
+
#: src/ring/commands/doctor.py:108
|
|
515
519
|
msgid "維護"
|
|
516
520
|
msgstr ""
|
|
517
521
|
|
|
518
|
-
#: src/ring/commands/doctor.py:
|
|
519
|
-
#: src/ring/commands/doctor.py:
|
|
522
|
+
#: src/ring/commands/doctor.py:112 src/ring/commands/doctor.py:114
|
|
523
|
+
#: src/ring/commands/doctor.py:116
|
|
520
524
|
msgid "可清理"
|
|
521
525
|
msgstr ""
|
|
522
526
|
|
|
523
|
-
#: src/ring/commands/doctor.py:
|
|
527
|
+
#: src/ring/commands/doctor.py:112
|
|
524
528
|
#, python-brace-format
|
|
525
529
|
msgid "{n} 個 RiNG stale 狀態檔(執行 ring gc --dry-run 預覽)"
|
|
526
530
|
msgstr ""
|
|
527
531
|
|
|
528
|
-
#: src/ring/commands/doctor.py:
|
|
532
|
+
#: src/ring/commands/doctor.py:114
|
|
529
533
|
msgid "沒有 RiNG stale 狀態檔"
|
|
530
534
|
msgstr ""
|
|
531
535
|
|
|
532
|
-
#: src/ring/commands/doctor.py:
|
|
536
|
+
#: src/ring/commands/doctor.py:119
|
|
533
537
|
msgid "設定檔"
|
|
534
538
|
msgstr ""
|
|
535
539
|
|
|
536
|
-
#: src/ring/commands/doctor.py:
|
|
540
|
+
#: src/ring/commands/doctor.py:123
|
|
537
541
|
msgid "完整生效值請看 `ring config`。"
|
|
538
542
|
msgstr ""
|
|
539
543
|
|
|
@@ -618,15 +622,15 @@ msgstr ""
|
|
|
618
622
|
msgid "⚠️ 其中 {n} 段還在 🔴 等你(計到現在為止)。"
|
|
619
623
|
msgstr ""
|
|
620
624
|
|
|
621
|
-
#: src/ring/focus/__init__.py:
|
|
625
|
+
#: src/ring/focus/__init__.py:58
|
|
622
626
|
msgid "已離場的 session 無法跳轉"
|
|
623
627
|
msgstr ""
|
|
624
628
|
|
|
625
|
-
#: src/ring/focus/__init__.py:
|
|
629
|
+
#: src/ring/focus/__init__.py:81
|
|
626
630
|
msgid "找不到這個終端分頁(可能已關閉;刷新後若仍存在,請裝 hook 取得更精準狀態)"
|
|
627
631
|
msgstr ""
|
|
628
632
|
|
|
629
|
-
#: src/ring/focus/__init__.py:
|
|
633
|
+
#: src/ring/focus/__init__.py:82
|
|
630
634
|
msgid "沒有 focuser 接得住(裝 hook,或一個專案只開一個 session 才測得到 tty)"
|
|
631
635
|
msgstr ""
|
|
632
636
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|