feat(terminal): 输入行跟随输出并显示当前路径

- Text 不再把输入行固定在控件底部:输入行紧跟在最后一条输出之后,
  输出增加时随之下移;超出可视区时 scroll_offset 保持输入行可见
- Text 增加 prompt 字段 / set_prompt;点击定位、光标、_exec_input、
  滚动条计算都按实际 prompt 长度和总行数(输出行 + 输入行)计算
- Terminal 的提示符改为“归一化路径 + /> ”:
  根目录是 "/> ",/apps 是 "/apps/> "
- cd 成功后更新提示符;裸 cd 仍打印当前路径
This commit is contained in:
TermiNexus committed 2026-10-27 14:41:35 +08:00
1 parent ab18afd26e
commit 5e2598a992
2 files changed
+73 -25

No files matched your search

+26 -2
View File
@@ -56,9 +56,9 @@ export def _start() -> int:
elif act == 5:
aval: int = sbar.action_value
if term.line_count > 0:
term.scroll_offset = aval * term.line_count / 1000
term.scroll_offset = aval * (term.line_count + 1) / 1000
vis3: int = term._visible_lines()
max_off: int = term.line_count - vis3
max_off: int = term.line_count + 1 - vis3
if max_off < 0: max_off = 0
if term.scroll_offset < 0: term.scroll_offset = 0
if term.scroll_offset > max_off: term.scroll_offset = max_off
@@ -144,6 +144,28 @@ export def _start() -> int:
j += 1
*(dst + out_len) = char(0)
# 提示符 = 归一化路径 + "/> ":根目录显示 "/> ",/apps 显示 "/apps/> "。
def _update_prompt():
p: [80]char
i: int = 0
while i < 63:
ch: char = _cwd[i]
p[i] = ch
if ch == 0: break
i += 1
if i == 0:
p[0] = char(47)
i = 1
if p[i - 1] != char(47):
p[i] = char(47)
i += 1
p[i] = char(62) # '>'
i += 1
p[i] = char(32) # ' '
i += 1
p[i] = char(0)
term.set_prompt(&p[0])
def _cmd_cd(path: str):
# 裸 cd:只显示当前路径,不切换目录(切换请用 cd <path>)。
if path is None or *path == 0:
@@ -158,6 +180,7 @@ export def _start() -> int:
fs.closedir(dd)
# 存入归一化后的路径:根目录的 ".." 保持为 "/",pwd 也保持干净。
_normalize_path(&_cwd[0], 64, &full[0])
_update_prompt()
def _cmd_ls():
dd: int = fs.opendir(&_cwd[0])
@@ -359,6 +382,7 @@ export def _start() -> int:
term.insert("Unknown command. Type 'help'.")
return 0
_update_prompt()
term.on_command = _on_command
root.mainloop()
+47 -23
View File
@@ -1197,6 +1197,7 @@ virtual class Text(Widget):
input_buf: [80]char
input_len: int
cursor_pos: int
prompt: [80]char
bg_color: uint32
text_color: uint32
prompt_color: uint32
@@ -1223,6 +1224,9 @@ virtual class Text(Widget):
self.line_count = 0
self.input_len = 0
self.cursor_pos = 0
self.prompt[0] = '>'
self.prompt[1] = ' '
self.prompt[2] = 0
self.bg_color = uint32(0x0C0C0C)
self.text_color = uint32(0xCCCCCC)
self.prompt_color = uint32(0x00FF00)
@@ -1250,13 +1254,19 @@ virtual class Text(Widget):
line_idx: int = self.scroll_offset + i
if line_idx < self.line_count:
_draw_text(fb, fw, fh, self.x + 4, self.y + i * CHAR_H, &self.lines[line_idx][0], self.text_color)
prompt_y: int = self.y + self.h - CHAR_H - 4
_draw_text(fb, fw, fh, self.x + 4, prompt_y, "> ", self.prompt_color)
if self.input_len > 0:
_draw_text(fb, fw, fh, self.x + 4 + 2 * CHAR_W, prompt_y, &self.input_buf[0], uint32(0xFFFFFF))
cx_pos: int = self.x + 4 + (2 + self.cursor_pos) * CHAR_W
if cx_pos < self.x + self.w - 4:
_fill_rect(fb, fw, fh, cx_pos, prompt_y, CHAR_W // 2, CHAR_H, self.cursor_color)
# 输入行不是一个固定在底部的控件,而是紧跟在最后一条输出之后的一行;
# 输出增多时它随之下移,超出可视区时由 scroll_offset 把它保持在最后一行。
prompt_row: int = self.line_count - self.scroll_offset
if prompt_row >= 0 and prompt_row < visible:
prompt_y: int = self.y + prompt_row * CHAR_H
plen: int = int(string.strlen(&self.prompt[0]))
_draw_text(fb, fw, fh, self.x + 4, prompt_y, &self.prompt[0], self.prompt_color)
if self.input_len > 0:
_draw_text(fb, fw, fh, self.x + 4 + plen * CHAR_W, prompt_y,
&self.input_buf[0], uint32(0xFFFFFF))
cx_pos: int = self.x + 4 + (plen + self.cursor_pos) * CHAR_W
if cx_pos < self.x + self.w - 4:
_fill_rect(fb, fw, fh, cx_pos, prompt_y, CHAR_W // 2, CHAR_H, self.cursor_color)
def handle_event(self, evt: window.InputEvent*) -> int:
etype: uint32 = evt.type
@@ -1268,9 +1278,11 @@ virtual class Text(Widget):
self.focused = 1
tk: Tk* = self.tk
tk.focused_widget = Widget*(self)
prompt_y: int = self.y + self.h - CHAR_H - 4
if ey >= prompt_y and ey < prompt_y + CHAR_H:
rel_x: int = ex - self.x - 4 - 2 * CHAR_W
prompt_row: int = self.line_count - self.scroll_offset
prompt_y: int = self.y + prompt_row * CHAR_H
if prompt_row >= 0 and prompt_row < self._visible_lines() and ey >= prompt_y and ey < prompt_y + CHAR_H:
plen: int = int(string.strlen(&self.prompt[0]))
rel_x: int = ex - self.x - 4 - plen * CHAR_W
if rel_x < 0: rel_x = 0
new_pos: int = (rel_x + CHAR_W / 2) / CHAR_W
if new_pos > self.input_len: new_pos = self.input_len
@@ -1368,7 +1380,7 @@ virtual class Text(Widget):
return 1
elif ch == window.VK_NEXT:
vis2: int = self._visible_lines()
max_off: int = self.line_count - vis2
max_off: int = self.line_count + 1 - vis2
if max_off < 0: max_off = 0
if self.scroll_offset < max_off:
self.scroll_offset += vis2
@@ -1389,14 +1401,15 @@ virtual class Text(Widget):
return 0
def _exec_input(self):
self.insert("> ")
self.insert(&self.prompt[0])
plen: int = int(string.strlen(&self.prompt[0]))
if self.input_len > 0:
last: int = self.line_count - 1
j: int = 0
while j < self.input_len and j < 77:
self.lines[last][2 + j] = self.input_buf[j]
while j < self.input_len and plen + j < 79:
self.lines[last][plen + j] = self.input_buf[j]
j += 1
self.lines[last][2 + j] = 0
self.lines[last][plen + j] = 0
if self.history_count < 16:
for k in range(80):
self.history[self.history_count][k] = self.input_buf[k]
@@ -1487,8 +1500,9 @@ pop r12""",
self.lines[idx][j] = 0
self.line_count += 1
vis: int = self._visible_lines()
if self.line_count > vis:
self.scroll_offset = self.line_count - vis
total: int = self.line_count + 1 # 输出行 + 输入行
if total > vis:
self.scroll_offset = total - vis
else:
self.scroll_offset = 0
self._update_scrollbar()
@@ -1501,6 +1515,15 @@ pop r12""",
def get(self) -> const str:
return &self.input_buf[0]
def set_prompt(self, text: const str):
i: int = 0
while i < 79:
ch: char = *(text + i)
self.prompt[i] = ch
if ch == 0: break
i += 1
self.prompt[i] = 0
def config(self, text_color: uint32 = None, bg_color: uint32 = None, prompt_color: uint32 = None, cursor_color: uint32 = None, border_color: uint32 = None):
if text_color is not None:
self.text_color = text_color
@@ -1514,23 +1537,24 @@ pop r12""",
self.border_color = border_color
def _visible_lines(self) -> int:
return (self.h - CHAR_H - 4) / CHAR_H
return (self.h - 2) / CHAR_H
def _update_scrollbar(self):
vis: int = self._visible_lines()
if self.line_count <= vis:
total: int = self.line_count + 1 # 输出行 + 输入行
if total <= vis:
self.scroll_offset = 0
else:
max_off: int = self.line_count - vis
max_off: int = total - vis
if self.scroll_offset > max_off:
self.scroll_offset = max_off
if self.yscrollbar is not None:
sb: Scrollbar* = Scrollbar*(self.yscrollbar)
if self.line_count <= vis:
if total <= vis:
sb.set(0, 1000)
else:
first: int = self.scroll_offset * 1000 / self.line_count
last: int = (self.scroll_offset + vis) * 1000 / self.line_count
first: int = self.scroll_offset * 1000 / total
last: int = (self.scroll_offset + vis) * 1000 / total
sb.set(first, last)
# ---- Tk:主窗口类 = Wm(窗口管理器协议,无字段)+ Misc(公共状态/方法)----