Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions frontends/tui_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -2542,6 +2542,27 @@ def _seg_at(self, segs: list[tuple[int, str]], pos: int | None = None) -> tuple[
return i, p - st
return len(segs) - 1, len(segs[-1][1])

def _line_region(self, pos: int | None = None) -> tuple[int, int, int, int]:
"""(行号, 总行数, 行起始偏移, 行结束偏移) 基于逻辑行(\\n分割)"""
p = self.pos if pos is None else pos
ls = self.buf.rfind('\n', 0, p) + 1
le = self.buf.find('\n', p)
if le == -1:
le = len(self.buf)
line_no = self.buf[:p].count('\n')
total = self.buf.count('\n') + 1
return line_no, total, ls, le

def _logical_visual_range(self, segs, line_no):
"""返回第 line_no 个逻辑行在 segs 中的 (first_vi, last_vi)"""
lines = self.buf.split('\n')
iw = max(1, _term()[0] - 6)
first = 0
for i in range(line_no):
first += max(1, len(_wrap_cells(lines[i], iw)))
cnt = max(1, len(_wrap_cells(lines[line_no], iw)))
return first, first + cnt - 1

def _cur_v(self, d: int) -> None:
"""↑/↓ roam by VISUAL row (a long single-line paste wraps to many rows
yet stays one logical line — must still roam). At the top/bottom visual
Expand Down Expand Up @@ -5157,15 +5178,47 @@ def _keys(self, data: bytes) -> None:
self._pending_drain_t = 0.0
self._render_live()
else:
self._sel = None; self._cur_v(-1)
self._sel = None
line_no, total, ls, le = self._line_region()
segs = self._segs(max(1, _term()[0] - 6))
vi, _ = self._seg_at(segs)
first_vi, _ = self._logical_visual_range(segs, line_no)
if vi == first_vi: # 在视觉首行
if line_no == 0:
if self.pos == ls:
self._nav_hist(-1)
else:
self.pos = ls
elif self.pos == ls:
self._cur_v(-1) # 已在行首 → 正常上移
else:
self.pos = ls # 跳到该行行首
else:
self._cur_v(-1)
elif o == 0x13: # Ctrl+S stash/restore draft
self._stash_draft()
elif o == 0x0e: # ↓ visual-row down (history at bottom)
if self._palette_visible():
n = len(self._cmd_matches(self.buf))
self._palette_sel = min(n - 1, self._palette_sel + 1) if n else 0
else:
self._sel = None; self._cur_v(1)
self._sel = None
line_no, total, ls, le = self._line_region()
segs = self._segs(max(1, _term()[0] - 6))
vi, _ = self._seg_at(segs)
first_vi, last_vi = self._logical_visual_range(segs, line_no)
if vi == last_vi: # 在视觉末行
if line_no == total - 1:
if self.pos == le:
self._nav_hist(1)
else:
self.pos = le
elif self.pos == le:
self._cur_v(1) # 已在行末 → 正常下移
else:
self.pos = le # 跳到该行行末
else:
self._cur_v(1)
elif o == 0x02: # ← caret left
self._sel = None; self.pos = max(0, self.pos - 1)
elif o == 0x06: # → caret right
Expand Down