pi-lazy-panel 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/README.zh.md +104 -0
- package/docs/design.md +235 -0
- package/docs/keybindings.md +172 -0
- package/docs/keybindings.zh.md +172 -0
- package/package.json +67 -0
- package/src/actions/session-actions.ts +585 -0
- package/src/actions/tree-actions.ts +161 -0
- package/src/config/config.ts +155 -0
- package/src/config/keymap.ts +219 -0
- package/src/config/keys.ts +279 -0
- package/src/config/pi-settings.ts +54 -0
- package/src/constants.ts +48 -0
- package/src/data/changelog.ts +63 -0
- package/src/data/content.ts +178 -0
- package/src/data/search.ts +183 -0
- package/src/data/sessions.ts +148 -0
- package/src/data/tree-fold.ts +163 -0
- package/src/data/tree.ts +258 -0
- package/src/i18n/index.ts +103 -0
- package/src/i18n/locales/en.json +341 -0
- package/src/i18n/locales/zh.json +341 -0
- package/src/index.ts +155 -0
- package/src/types.ts +346 -0
- package/src/ui/app.ts +2892 -0
- package/src/ui/frame.ts +116 -0
- package/src/ui/mouse-input.ts +197 -0
- package/src/ui/mouse.ts +99 -0
- package/src/ui/panes/content-pane.ts +132 -0
- package/src/ui/panes/sessions-pane.ts +161 -0
- package/src/ui/panes/tree-pane.ts +152 -0
- package/src/ui/search-highlight.ts +108 -0
- package/src/ui/tree-lines.ts +111 -0
- package/src/ui/tree-outline.ts +80 -0
- package/src/ui/widgets/changelog-dialog.ts +196 -0
- package/src/ui/widgets/compact-dialog.ts +29 -0
- package/src/ui/widgets/confirm-dialog.ts +98 -0
- package/src/ui/widgets/export-dialog.ts +62 -0
- package/src/ui/widgets/footer.ts +117 -0
- package/src/ui/widgets/fork-dialog.ts +28 -0
- package/src/ui/widgets/help-overlay.ts +220 -0
- package/src/ui/widgets/import-dialog.ts +32 -0
- package/src/ui/widgets/input-dialog.ts +136 -0
- package/src/ui/widgets/label-dialog.ts +28 -0
- package/src/ui/widgets/new-session-dialog.ts +29 -0
- package/src/ui/widgets/prompt-bar.ts +92 -0
- package/src/ui/widgets/rename-dialog.ts +29 -0
- package/src/ui/widgets/restore-dialog.ts +60 -0
- package/src/ui/widgets/search-bar.ts +65 -0
- package/src/ui/widgets/select-dialog.ts +193 -0
- package/src/ui/widgets/session-info-dialog.ts +189 -0
- package/src/ui/widgets/tree-dialog.ts +293 -0
- package/src/utils/format.ts +83 -0
- package/src/utils/paths.ts +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cheems
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
**English** | [中文](./README.zh.md)
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
pi-lazy-panel is a panel for managing [pi](https://pi.dev) sessions and session trees, offering a lazygit-style workflow and TUI look. Once installed, run `/lazy-panel` inside pi to open a full-terminal three-pane interface:
|
|
6
|
+
|
|
7
|
+
- Top-left **SESSIONS**: the history of past sessions (mirrors `/resume`)
|
|
8
|
+
- Bottom-left **TREE**: the conversation tree of the selected session (mirrors `/tree`, switches automatically with the selection above)
|
|
9
|
+
- Right **CONTENT**: the conversation detail of the selected node, rendered with pi's native Markdown, distinguishing "me" from "AI"
|
|
10
|
+
|
|
11
|
+
The everyday session operations (new / resume / fork / clone / rename / delete / compact / export / import / share / copy) are all condensed into single-key shortcuts — no mouse required.
|
|
12
|
+
|
|
13
|
+
## Preview
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
1. Pure keyboard flow — most operations take a single key (hjkl to move, `d` to delete, `r` to rename, à la lazygit)
|
|
20
|
+
2. Three linked panes: SESSIONS → TREE → CONTENT cascade-refresh as the cursor moves
|
|
21
|
+
3. Full coverage of the session commands: resume / new / fork / clone / rename / delete / compact / export / import / share / copy
|
|
22
|
+
4. Session-tree navigation: restore to any node, choosing "No summary / Summarize / Summarize with custom prompt" for the abandoned branch
|
|
23
|
+
5. lazygit-style search: `/` jumps in place, `n` / `N` cycle, with `name:` `model:` `path:` `tag:` `role:` `after:` `before:` qualifiers
|
|
24
|
+
6. Theme follows pi automatically
|
|
25
|
+
7. Bilingual — English and Chinese (follows the system language, overridable in the config)
|
|
26
|
+
8. Fully customizable keybindings: rebind any action in `~/.pi/agent/lazy-panel.json`
|
|
27
|
+
9. Light mouse support (wheel to scroll, click to focus, double-click to enter / fold)
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
Requires [pi](https://pi.dev) to be installed. Run the install command inside pi:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# npm
|
|
35
|
+
pi install npm:pi-lazy-panel
|
|
36
|
+
|
|
37
|
+
# GitHub
|
|
38
|
+
pi install git:github.com/JessieChan0730/pi-lazy-panel
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
After installing, run `/lazy-panel` inside pi to open the panel; `?` shows all shortcuts for the current pane.
|
|
42
|
+
|
|
43
|
+
## Local development
|
|
44
|
+
|
|
45
|
+
The pi-related packages are `devDependencies`, so you must install dependencies after cloning:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install # install dependencies (pi packages included; not installed automatically by default)
|
|
49
|
+
npm run dev # load the plugin temporarily via pi -e ./src/index.ts, without writing config
|
|
50
|
+
npm run install:pi # pi install . registers this directory into pi (once only); after editing code, run /reload in pi to pick up the latest
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Run the full check suite before committing (same as the pre-push hook):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run lint # ESLint
|
|
57
|
+
npm run check # tsc --noEmit
|
|
58
|
+
npm test # unit tests
|
|
59
|
+
npm run i18n:check # English/Chinese copy alignment
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Compatibility
|
|
63
|
+
|
|
64
|
+
- **Runtime**: Node.js ≥ 20.
|
|
65
|
+
- **pi version**: runs as a pi plugin, depending on pi's session API (`@earendil-works/pi-coding-agent`).
|
|
66
|
+
- **Run mode**: available only in pi's TUI mode (`/lazy-panel` refuses to open outside TUI mode).
|
|
67
|
+
- **OS**: cross-platform — Windows / macOS / Linux all work (paths are handled uniformly per platform).
|
|
68
|
+
- **Terminal**: mouse support in regular mode needs a terminal that supports SGR mouse reporting (virtually all modern terminals do); without it, keyboard operation is unaffected.
|
|
69
|
+
- **Optional external commands**: `/share` needs a logged-in GitHub CLI (`gh`); when deleting a session, `trash` is used if available (moving it to the recycle bin), otherwise the file is deleted directly.
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
The config file lives at `~/.pi/agent/lazy-panel.json`. Every field is optional and falls back to a built-in default when omitted:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"locale": "zh",
|
|
78
|
+
"defaultScope": "all",
|
|
79
|
+
"defaultSort": "recent",
|
|
80
|
+
"leftColumnRatio": 0.25,
|
|
81
|
+
"keymap": {
|
|
82
|
+
"global": { "help": "F1", "scope-all": ["A", "ctrl+space"] },
|
|
83
|
+
"sessions": { "session-delete": "ctrl+d", "session-share": null }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Field | Description | Values / range | Default |
|
|
89
|
+
| ----------------- | ------------------------------- | --------------------------------------------------- | ---------------------- |
|
|
90
|
+
| `locale` | UI language | `"en"` / `"zh"` | follows system language |
|
|
91
|
+
| `defaultScope` | session scope on open | `"current-folder"` / `"all"` | `"current-folder"` |
|
|
92
|
+
| `defaultSort` | session list ordering | `"recent"` / `"created"` / `"title"` / `"threaded"` | `"recent"` |
|
|
93
|
+
| `leftColumnRatio` | left column width ratio | `0.15` ~ `0.6` | `0.25` |
|
|
94
|
+
| `keymap` | custom keybindings | see below | built-in keys |
|
|
95
|
+
|
|
96
|
+
`keymap` is grouped by `scope` (`global` / `sessions` / `tree` / `content` / `tree-dialog`); the key is an action id and the value is one or more chords (e.g. `"ctrl+d"`, `["A", "ctrl+space"]`). A user-provided binding **replaces the action's default entirely**, and `null` unbinds it. See [docs/keybindings.md](./docs/keybindings.md) for the full list of action ids and keybindings.
|
|
97
|
+
|
|
98
|
+
## Contributing
|
|
99
|
+
|
|
100
|
+
Contributions are welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md) for the local setup, coding conventions and commit/PR flow.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
Licensed under the [MIT License](./LICENSE).
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
[English](./README.md) | **中文**
|
|
2
|
+
|
|
3
|
+
## 简介
|
|
4
|
+
|
|
5
|
+
pi-lazy-panel 是 [pi](https://pi.dev) 会话和会话树管理的面板,提供类似于 lazygit 的操作方式和 TUI 风格。安装后在 pi 中执行 `/lazy-panel`,即可打开一个占满终端的三面板界面:
|
|
6
|
+
|
|
7
|
+
- 左上 **SESSIONS**:历史会话列表(对应 `/resume`)
|
|
8
|
+
- 左下 **TREE**:当前所选会话的对话树(对应 `/tree`,随上方选择自动切换)
|
|
9
|
+
- 右侧 **CONTENT**:所选节点的对话详情,用 pi 原生 Markdown 渲染,区分「我」和「AI」
|
|
10
|
+
|
|
11
|
+
围绕会话的常用操作(新建 / 恢复 / 分叉 / 克隆 / 改名 / 删除 / 压缩 / 导出 / 导入 / 分享 / 复制)都收敛成单键快捷键,全程无需鼠标。
|
|
12
|
+
|
|
13
|
+
## 界面预览
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## 特点
|
|
18
|
+
|
|
19
|
+
1. 纯粹的键盘流,大部分的操作使用一个键就能完成(hjkl 移动、d 删除、r 改名,参考 lazygit)
|
|
20
|
+
2. 三面板联动:SESSIONS → TREE → CONTENT 随光标自动级联刷新
|
|
21
|
+
3. 覆盖全套会话命令:resume / new / fork / clone / rename / delete / compact / export / import / share / copy
|
|
22
|
+
4. 会话树导航:可恢复到任意节点,并选择「不摘要 / 摘要 / 自定义摘要」处理被放弃的分支
|
|
23
|
+
5. lazygit 风格搜索:`/` 就地跳转、`n` / `N` 循环,支持 `name:` `model:` `path:` `tag:` `role:` `after:` `before:` 限定词
|
|
24
|
+
6. 主题跟随 pi 自动切换
|
|
25
|
+
7. 支持中英文(跟随系统语言,可在配置里覆盖)
|
|
26
|
+
8. 快捷键完全可自定义:在 `~/.pi/agent/lazy-panel.json` 里重绑任意动作
|
|
27
|
+
9. 鼠标轻量适配(滚轮滚动、单击聚焦、双击进入 / 折叠)
|
|
28
|
+
|
|
29
|
+
## 快速开始
|
|
30
|
+
|
|
31
|
+
需要已安装 [pi](https://pi.dev),在 pi 中执行安装命令:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# npm
|
|
35
|
+
pi install npm:pi-lazy-panel
|
|
36
|
+
|
|
37
|
+
# GitHub
|
|
38
|
+
pi install git:github.com/JessieChan0730/pi-lazy-panel
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
安装后在 pi 里执行 `/lazy-panel` 打开面板,`?` 可查看当前面板的全部快捷键。
|
|
42
|
+
|
|
43
|
+
## 本地开发
|
|
44
|
+
|
|
45
|
+
pi 相关的包是 `devDependencies`,克隆后必须先安装依赖:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install # 安装依赖(含 pi 相关包,默认不会自动装)
|
|
49
|
+
npm run dev # 用 pi -e ./src/index.ts 临时加载插件,不写配置
|
|
50
|
+
npm run install:pi # pi install . 把本目录注册进 pi,只需一次;改完代码在 pi 里 /reload 即最新
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
提交前请跑全部检查(与 pre-push 钩子一致):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run lint # ESLint
|
|
57
|
+
npm run check # tsc --noEmit
|
|
58
|
+
npm test # 单元测试
|
|
59
|
+
npm run i18n:check # 中英文案对齐
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 兼容性
|
|
63
|
+
|
|
64
|
+
- **运行环境**:Node.js ≥ 20。
|
|
65
|
+
- **pi 版本**:作为 pi 插件运行,依赖 pi 的会话 API(`@earendil-works/pi-coding-agent`)。
|
|
66
|
+
- **运行模式**:仅在 pi 的 TUI 模式下可用(非 TUI 模式下 `/lazy-panel` 会拒绝打开)。
|
|
67
|
+
- **操作系统**:跨平台,Windows / macOS / Linux 均可(路径按平台统一处理)。
|
|
68
|
+
- **终端**:regular 模式下的鼠标支持需要终端支持 SGR 鼠标上报(现代终端基本都支持);不支持也不影响键盘操作。
|
|
69
|
+
- **可选外部命令**:`/share` 需要已登录的 GitHub CLI(`gh`);删除会话时若存在 `trash` 则移入回收站,否则直接删除。
|
|
70
|
+
|
|
71
|
+
## 配置文件
|
|
72
|
+
|
|
73
|
+
配置文件位于 `~/.pi/agent/lazy-panel.json`,所有字段均为可选,缺省时使用内置默认值:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"locale": "zh",
|
|
78
|
+
"defaultScope": "all",
|
|
79
|
+
"defaultSort": "recent",
|
|
80
|
+
"leftColumnRatio": 0.25,
|
|
81
|
+
"keymap": {
|
|
82
|
+
"global": { "help": "F1", "scope-all": ["A", "ctrl+space"] },
|
|
83
|
+
"sessions": { "session-delete": "ctrl+d", "session-share": null }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| 字段 | 说明 | 可选值 / 范围 | 默认 |
|
|
89
|
+
| ----------------- | ---------------- | --------------------------------------------------- | ------------------ |
|
|
90
|
+
| `locale` | UI 语言 | `"en"` / `"zh"` | 跟随系统语言 |
|
|
91
|
+
| `defaultScope` | 打开时的会话范围 | `"current-folder"` / `"all"` | `"current-folder"` |
|
|
92
|
+
| `defaultSort` | 会话列表排序 | `"recent"` / `"created"` / `"title"` / `"threaded"` | `"recent"` |
|
|
93
|
+
| `leftColumnRatio` | 左侧列宽占比 | `0.15` ~ `0.6` | `0.25` |
|
|
94
|
+
| `keymap` | 自定义快捷键 | 见下 | 内置键位 |
|
|
95
|
+
|
|
96
|
+
`keymap` 按 `scope`(`global` / `sessions` / `tree` / `content` / `tree-dialog`)分组,键为动作 id、值为一个或多个 chord(如 `"ctrl+d"`、`["A", "ctrl+space"]`);用户提供的键位**整体替换**该动作的默认值,`null` 表示解绑。完整的动作 id 与键位说明见 [docs/keybindings.zh.md](./docs/keybindings.zh.md)。
|
|
97
|
+
|
|
98
|
+
## 贡献
|
|
99
|
+
|
|
100
|
+
欢迎贡献代码——本地环境、代码规范与提交 / PR 流程见 [CONTRIBUTING.zh.md](./CONTRIBUTING.zh.md)。
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
基于 [MIT 许可证](./LICENSE)授权。
|
package/docs/design.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
## 问题描述
|
|
2
|
+
|
|
3
|
+
目前 pi 启动的 resume 感觉操作很别扭,方向键控制上下移动,然后 ctrl + d 删, 能否帮我做一个插件 lazygit 那种(hjkl, d 是删除,r 是 rename)。操作 pi resume 的插件啊?
|
|
4
|
+
|
|
5
|
+
## 项目描述
|
|
6
|
+
|
|
7
|
+
一个类似 lazygit 的插件,用来管理 pi 的历史对话,主要命令有三个 /resume(恢复会话,从历史会话中选择并恢复) /tree (会话树导航,跳转到对话树的任意节点继续对话,某个对话详情里面的某一个界面) /new (新建一个对话),然后还有围绕着对话进行操作的命令:/name(命名会话,为当前会话设置一个易读的名称) /session(会话信息,显示会话文件路径、ID、消息数、token 用量和费用) /fork(分叉会话,从之前的用户消息分叉出一个新会话文件)/clone(克隆会话复制当前活动分支到新会话文件)/copy (复制 AI 最后一条回复到剪贴板)/export(导出会话导出为 HTML 或 JSONL 文件)/import(导入会话从 JSONL 文件导入并恢复会话)
|
|
8
|
+
/share(分享会话上传为私有 GitHub Gist,生成可分享的 HTML 链接), 根据这些命令能够拿到信息,我觉得产品可以设计成为这样:
|
|
9
|
+
|
|
10
|
+
- 左边第一个面板是显示历史对话, 也就是 /resume 里面的那些数据
|
|
11
|
+
- 左边第二个面板(在第一个面板底部)是显示对话节点 也就是 /tree 里面的那些数据(根据上面选择的不同的 /resume 来确自动变化这里面的内容)
|
|
12
|
+
- 右边第一个面板是显示对话的详细内容(需要区分是AI的消息,还是我的消息),这个面板占据全部高度
|
|
13
|
+
- 搜索,放在底部不需要一个输入框。当用户输入 / 时候,底部出现 搜索:两个字样,随后用户输入内容回车进行搜索(只搜索当前focus面板中的内容),这个完全参考 lazygit 就行
|
|
14
|
+
|
|
15
|
+
聚焦到得不同的面板,不同的快捷键有不同的含义。
|
|
16
|
+
|
|
17
|
+
```txt
|
|
18
|
+
关系其实是:
|
|
19
|
+
resume1
|
|
20
|
+
- tree1
|
|
21
|
+
- node1
|
|
22
|
+
- node2
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
也就是随着第一个面板,用户选择聚焦不同的resume,下面的第二个面板的tree也要不停变化显示不同的可还原的节点
|
|
26
|
+
|
|
27
|
+
暂定的指定(后续可能会适配):
|
|
28
|
+
~~/compact~~(2026-09-24 完成:SESSIONS 面板快捷键 `c`,见下面"左侧列表第一个面板"的 c),/settings,~~/changelog~~(2026-09-24 完成:全局快捷键 `@`,见下面"通用的快捷键")
|
|
29
|
+
|
|
30
|
+
确定不会用到的指令:
|
|
31
|
+
/login,/logout,/model,/thinking,/scoped-models
|
|
32
|
+
|
|
33
|
+
## 使用流程
|
|
34
|
+
|
|
35
|
+
用户安装插件之后,使用 /lazy-panel 命令可以打开一个 tui 对话框,整体布局是参考项目描述
|
|
36
|
+
|
|
37
|
+
- ~~打开时 SESSIONS 光标自动落到 pi 当前打开的会话上,TREE / CONTENT 随之显示当前对话;新对话(还没有会话文件或还没列出来)则选中第一行~~(2026-09-22 完成;C / A 切换范围后仍回到顶部)
|
|
38
|
+
|
|
39
|
+
## 快捷键操作
|
|
40
|
+
|
|
41
|
+
### 通用的快捷键
|
|
42
|
+
|
|
43
|
+
~~h/l: 参考 lazygit,h 切换到上一个面板,l 切换到下一个面板(tab 也保留为“下一个”),聚焦在不同的面板上,快捷键会有不同的表现~~
|
|
44
|
+
~~1/2/3: 每个面板标题前有数字标记([1] SESSIONS、[2] TREE、[3] CONTENT),按下对应数字直接跳到该面板~~
|
|
45
|
+
~~C:切换到 Current Folder 模式(只做单向切换,已经是 Current 时按下无效果)~~
|
|
46
|
+
~~A:切换到 All 模式(只做单向切换,已经是 All 时按下无效果)~~
|
|
47
|
+
|
|
48
|
+
- ~~SESSIONS 面板标题右侧始终显示 `1/15 · Current · recent`(位置 · 模式 · 排序),C/A 两种模式都要有;宽度不够时按 `1/15 · Current` → `1/15 · Cur` → `1/15` 逐级缩短,不能整段消失~~
|
|
49
|
+
- ~~底部 footer 只提示能切换过去的那个:当前是 Current 就显示 `A All`,当前是 All 就显示 `C Current`~~
|
|
50
|
+
~~?: 快捷键提示菜单, 不同的面板提示不同的快捷键。~~
|
|
51
|
+
~~exit/q: 退出插件~~
|
|
52
|
+
~~@: 查看 pi 的 changelog,和 /changelog 命令一样~~(2026-09-24 完成:居中大弹窗用 pi-tui `Markdown` 渲染 pi 安装目录的 `CHANGELOG.md`,j / k / 方向键滚动一行,ctrl+d / ctrl+u 半页,g / G 顶部 / 底部,Esc / q / @ 关闭;和 pi 的差异:最新版本在最上面(pi 倒序、最新的在底部))
|
|
53
|
+
~~/: 底部出现 搜索:两个字样,随后用户输入内容回车进行搜索,只搜索当前fouce的,文本需要高亮,使用 n/N 可以上一个和下一个,具体参考lazygit的形式~~(2026-09-23 完成:三个面板各记各的关键字,输入时实时跳到原位置之后的第一个匹配,Enter 保留、Esc 取消并回到原位置;n / N 回绕;标题右侧 `2/7 matches`,footer 显示关键字和 n / N / Esc 提示;命中文字高亮、当前匹配反色;列表不过滤只跳转)
|
|
54
|
+
|
|
55
|
+
### 鼠标
|
|
56
|
+
|
|
57
|
+
核心仍是键盘,鼠标只做轻量适配(2026-09-24 完成,`ui/mouse.ts` + `ui/mouse-input.ts` + `LazyPanel.handleMouse`):
|
|
58
|
+
|
|
59
|
+
- ~~滚轮 / 三指上下滚动指针所在的列表(只滚动视图、不移动选中项;CONTENT 按行滚),不改变焦点~~
|
|
60
|
+
- ~~单击列表项:焦点切到该面板并把光标移到该项;单击面板的空白 / 边框只切焦点~~
|
|
61
|
+
- ~~双击 SESSIONS:进入该会话(等价 Enter / resume);双击 TREE:折叠 / 展开分支(等价 z)~~
|
|
62
|
+
|
|
63
|
+
两种 TUI 模式都支持:fullscreen 由 pi 把事件派给面板;regular(默认)模式插件自己打开 SGR 鼠标上报并解析(需要终端支持 SGR,面板打开期间接管终端的选中 / 滚动,关闭后恢复),详见 `docs/issues.md`。
|
|
64
|
+
|
|
65
|
+
### 左侧列表第一个面板(resume)
|
|
66
|
+
|
|
67
|
+
~~/: 底部出现 搜索:两个字样,随后用户输入内容回车进行搜索,只搜索当前fouce的,文本需要高亮,使用 n/N 可以上一个和下一个,具体参考lazygit的形式~~(2026-09-23 完成:普通词只按 名称 / 首条消息预览 匹配,模型和路径只通过 `model:` / `path:` 匹配(`path:` 也认 `~/…` 写法),`name:` 只看会话名,`after:` / `before:` 看最后更新时间,`tag:` 忽略;搜索生效期间 n / N 是上下一个匹配,n 不再是新建会话)
|
|
68
|
+
~~j/k/方向上下键盘:上下移动~~
|
|
69
|
+
~~d: 删除对话~~(2026-09-23 完成:居中 Yes / No 确认框,默认停在 No,y / n 直接选;照 pi 内置 /resume 先试 `trash` 再 `unlink`,当前打开的会话拒绝删除;删完重新拉列表、光标夹回范围内)
|
|
70
|
+
~~r: 重命名对话(类似于进入 /resume 之后使用 ctrl + r) 修改过名字的 sessiion 面板顶部显示名字,并且看通过高亮还是什么手段,告诉用户这个对话修改过名称~~(2026-09-23 完成:居中输入框预填当前名字,空值清除;当前会话走 `pi.setSessionName`,其他会话追加 session_info 条目;改完重新拉列表、光标留在同一会话。有名字的行标题显示名字而不是首条消息,这就是"改过名"的标记;额外高亮暂不做)
|
|
71
|
+
~~o: Fork the session and open the fork~~(2026-09-23 完成:先弹一个 user 消息选择器让用户挑分叉点(默认停在最后一条,和 pi 内置 /fork 一致,消息多了按窗口滚动),选中后再弹 Yes / No 确认框(Esc / No 退回选择器);确认后 fork 在那条消息之前,pi 自己把那条 prompt 填回新会话的编辑器。光标会话不是当前会话时先切过去再 fork;没有 user 消息时 footer 报 `No messages to fork from`)
|
|
72
|
+
~~y: 克隆会话 复制当前活动分支到新会话文件~~(2026-09-23 完成:先弹 Yes / No 确认框(虽然无损但会把用户切走),确认后 `fork(leafId, { position: "at" })` 复制活动分支到新文件并打开)
|
|
73
|
+
~~J/K:上下滚动右侧的聊天内容~~
|
|
74
|
+
~~gg: 回到列表顶部~~
|
|
75
|
+
~~G: 跳转到列表底部~~
|
|
76
|
+
~~space: 多选聊天,主要是为了批量删除,fork 和 rname 不可用,如果选中多个按下了 f/r 提示用户:不能对多个对像进行操作~~(2026-09-24 完成:多选不画图标、只把选中行标题着 accent 色,标题显示 `3 selected`;d 在有选中时弹一次 `Delete N sessions?` 确认,跳过 pi 当前打开的会话,失败的留在列表和选中里、第一条错误进 footer;选中多个时 r / o / y / e / S 提示 `cannot act on multiple sessions`;Esc 先清空选中、再按才退出)
|
|
77
|
+
~~E: 导出对话,点击快捷键之后,显示一个输入框输入输出的目录~~(2026-09-24 完成,键位是小写 `e`:先选 HTML / JSONL,再弹输入框,预填 pi 的默认路径(完整路径),输入目录就在目录里用默认文件名,已有文件先确认覆盖;任意会话都能导出,面板不关)
|
|
78
|
+
~~I: 导入对话,点击快捷键之后,显示一个输入框输入导入的目录~~(2026-09-24 完成:输入的是 JSONL 文件路径而不是目录(pi 的 /import 一次导入一个文件);确认后复制进当前会话目录并切过去,面板关闭)
|
|
79
|
+
~~S: 分享会话 上传为私有 GitHub Gist,生成可分享的 HTML 链接,这里用大写的S, 避免和排序冲突~~(2026-09-24 完成:先确认,再用 `gh` 上传 secret gist,pi.dev 的查看链接自动复制到剪贴板并显示在 footer)
|
|
80
|
+
~~Y: 复制回复 复制 AI 最后一条回复到剪贴板~~(2026-09-23 完成:照搬 pi 的 `getLastAssistantText`——活动分支上最后一条 assistant 回复,只取 text 片段,跳过中止且没有内容的回复;面板不关,footer 报 `copied last reply` / `no assistant reply to copy`)
|
|
81
|
+
~~s: 对列表排序:(Threaded, Recent, Fuzzy)~~(2026-09-23 完成:循环 recent → created → title → threaded,标题右侧显示当前排序,光标跟着同一会话走。pi 的 fuzzy 只在有搜索关键字时有意义,换成了 created / title 两个没有 fork 会话也能看出效果的维度;title 按面板显示的标题排序,没名字的用首条消息预览参与排序,避免"没名字就一律沉底"的反直觉)
|
|
82
|
+
~~enter:点击恢复这个对话~~(对应 /resume:切过去并关闭面板,失败原因留在 footer)
|
|
83
|
+
~~n: 新建一个对话,显示输入框,必须输入新对话的名称(/name),输出完成后,回车关闭这个插件,并且进入到聊天界面~~(2026-09-23 完成:居中输入框输入名字,**名字可以留空**(对应 /name 的可选 `[name]` 参数,空则不设置名称,不再强制必须输入),回车新建并关闭面板进入新对话)
|
|
84
|
+
v: 暂定
|
|
85
|
+
~~i: 弹出对话框展示 Session Info 信息,就是 /seesion 中的内容(这个对话框弹出的时候使用y,可以复制对话框中全部的内容)~~(2026-09-23 完成:居中弹窗列 Name / Model / Messages / Tokens / Cost / Created / Updated / Path / ID,y 复制全部(路径用完整路径),Esc / q 关闭)
|
|
86
|
+
~~c: 压缩选中的对话(/compact),压缩完进对话~~(2026-09-24 完成:先弹居中输入框输入可选的聚焦指令(对应 /compact [instructions],可留空用 pi 默认),回车后压缩光标会话的活动分支并进入它——`ctx.compact` 只作用于当前会话,所以其他会话先 switchSession 切过去(这就是"进对话"),再在新 ctx 上压缩;压缩要跑模型、可能十几秒,期间面板隐藏、pi 自己的 footer 显示旋转 spinner(正方形),成功后关面板、失败留 footer;选中多个会话时拒绝。不弹确认框,和 pi 自带 /compact 一致(只追加一条 compaction 条目、不删数据))
|
|
87
|
+
|
|
88
|
+
### 左侧列表第二个面板(tree)
|
|
89
|
+
|
|
90
|
+
~~/: 底部出现 搜索:两个字样,随后用户输入内容回车进行搜索,只搜索当前fouce的,文本需要高亮,使用 n/N 可以上一个和下一个,具体参考lazygit的形式~~(2026-09-22 曾改为只在 a 打开的树对话框里搜;同日又恢复;2026-09-23 完成:和对话框共用 `matchTreeRow`——每个词都要出现在 label / 正文里,`tag:` 只看 label,`role:` 只看角色,`after:` / `before:` 看时间(role 不再参与普通词匹配,和 pi /tree 不同);折叠段里的行也算匹配,跳过去时展开它的祖先,右侧 CONTENT 跟着高亮;对话框里的搜索仍是过滤,两边互不影响)
|
|
91
|
+
~~j/k/方向上下键盘:上下移动~~
|
|
92
|
+
|
|
93
|
+
- ~~光标变化时右侧 CONTENT 跟着定位:对应消息框头部显示 `›` 箭头并加选中背景,同时把这条消息滚到面板顶部;节点在别的分支上时右侧改为显示“以该节点为叶子”的分支~~
|
|
94
|
+
- ~~树线:左侧画 pi `/tree` 一样的树形线条(`├⊟` / `└─` / `│`,⊟ 表示有子节点),活动分支上的节点文字前加 `• `,system 提示词 / compaction / branch summary 显示为 `[system]` / `[branch summary]: …` 这种标签行;层级最多画 3 层,更深的折叠成 `… `;完整的树在 a 打开的对话框里看~~(2026-09-22 起小面板改成折叠大纲:段头左侧画 `▸` / `▾` 三角、没有后代的旁支画 `─`,旁支默认折叠、活动分支展开,段内每层缩进 2 列、最多四层、更深的以 `… ` 代替,线性对话不缩进;pi 风格的树线只保留在 a 打开的对话框里)
|
|
95
|
+
- ~~z: 折叠 / 展开光标所在的分支段:光标在段头(`▸` / `▾` 行)上切换;在段内任意一行按下则折叠所在段并把光标移到段头(vim 的 zc);线性对话的主干上没有可折叠的段,footer 提示 nothing to fold here~~
|
|
96
|
+
~~y: 复制消息内容(类似于 /tree 里面 ctrl + x 快捷键的功能)~~
|
|
97
|
+
~~T: 给某个节点添加 label,在面板中央弹出一个输入框(类似 lazygit commit 的弹窗;和 /tree 里面 shift+t 快捷键一致;不用 l 是为了把 l 留给全局的“下一个面板”),打标签之后 / 搜索也要能按照标签搜索~~(2026-09-22 起标签在树对话框的搜索里能搜到:普通词就匹配 label,`tag:xxx` 只看 label)
|
|
98
|
+
~~d/t/u/L/a: 过滤(过滤 类似 /tree :filters ctrl+d/t/u/l/a 快捷键的功能,其中“只看有标签”用大写 L)~~(移到树对话框里,小面板只显示部分数据,过滤没意义;对话框里 labeled 改用小写 l)
|
|
99
|
+
~~a: 打开树对话框:顶部搜索框、中间完整的树形结构(不折叠层级)、底部快捷键提示,占满终端只留 2 列 / 1 行边距,光标停在面板当前节点上;Esc/q 关闭~~(对话框和面板共用折叠状态,折叠的段头在树线上画 `⊞`。2026-09-22 起对话框内的键按 `tree-dialog` → `tree` → `global` 的顺序解析:j/k/gg/G 移动自己的光标,y / T / Enter 和面板一样但作用于对话框选中的行(Label 弹窗、Summarize 菜单画在对话框上面),z 折叠,d/t/u/l/a 过滤(t/u/l/a 再按一次回 default;重新加载树、清空折叠,面板同步显示同一过滤并在标题里标出),`/` 聚焦顶部搜索框实时过滤(每个词都要出现在 label / 正文里,`tag:` 只看 label,`role:` 只看角色,`after:` / `before:` 看时间;2026-09-23 起 role 不再参与普通词匹配;Esc 退出搜索框、关键字和结果保留,再按 / 接着改、删光即清除,Enter 在搜索框里没有含义;搜索期间折叠全部打开、删光或关对话框后恢复),列表上 q / Esc 关闭并让面板光标跳到对话框选中的行——藏在折叠段里就展开它,右侧内容跟着高亮;对话框不做 ? 帮助,键都在底部一行)
|
|
100
|
+
~~enter: 确定要恢复到这个界面上,此时展示菜单,提供三个选项(No summary,Summarize,Summarize with custom prompt)~~(对应 /tree 的 restore:居中弹出三选菜单,j/k 移动、Enter 确认、Esc 退回 tree 面板;选 custom prompt 再弹一个单行输入框输入摘要指令,Esc 退回菜单;节点在其他会话里就先切会话再恢复;光标就在活动叶子上、或 pi 设置了 `branchSummary.skipPrompt` 时不弹菜单、直接进入)
|
|
101
|
+
|
|
102
|
+
三个选项只在一点上不同:跳到目标节点后,"被放弃的那段分支"(从当前叶子往上到它和目标节点的公共祖先之间的所有消息)怎么处理。跳转本身都一样:pi 把叶子指针移到目标节点,之后发给模型的上下文变成"根 → 目标节点"这条路径;被放弃的消息仍留在会话文件里,树里还能看到、以后还能跳回去。
|
|
103
|
+
|
|
104
|
+
- No summary:只移动叶子指针,不调模型、不花钱、瞬间完成,被放弃分支上发生过的事模型完全"忘记"。适合那段分支是死胡同,或只是想回到某句话重新问。
|
|
105
|
+
- Summarize:pi 把被放弃分支上的消息按时间顺序序列化成文本,用当前模型写一段结构化摘要(固定格式 Goal / Constraints & Preferences / Progress(Done、In Progress、Blocked)/ Key Decisions / Next Steps,要求保留精确的文件路径、函数名和报错信息),开头加一句"用户之前探索过另一条分支,以下是那次探索的总结",结尾附上那段分支读过和改过的文件列表。结果作为一条 `branch_summary` 条目挂在目标节点下面成为新叶子,模型继续对话时知道另一条路试过什么、有什么结论。代价是一次模型调用(输出最多 4096 token,用量计入会话总费用),要等几秒到几十秒,且必须有模型可用,否则报 `No model available for summarization`。适合那段分支做过有价值的探索或修改,想保留结论但不想把整段对话的 token 都带回来。
|
|
106
|
+
- Summarize with custom prompt:和 Summarize 一样,只是把输入的指令追加到默认提示词后面作为 `Additional focus: …`,默认结构仍然保留。用来指定摘要侧重点,比如"只记录和 API 改动相关的决定"或"完整保留报错堆栈"。
|
|
107
|
+
- 补充:目标是用户消息时,pi 把叶子移到它的父节点并把那句话填回输入框让你改了重发(摘要挂在父节点位置);目标在当前叶子的下游(没有任何消息被放弃)时,选 Summarize 也不会调模型,等同 No summary;分支太长放不进上下文时按 `branchSummary.reserveTokens`(默认 16384)预留空间、裁掉更早的消息再总结。摘要条目在 TREE 里显示为 `system: branch summary: …`,默认过滤下隐藏,按 `a` 可见。
|
|
108
|
+
|
|
109
|
+
### 右侧第一个面板(content 只读)
|
|
110
|
+
|
|
111
|
+
这个需要区分是“我”发送的内容,还是AI返回的内容
|
|
112
|
+
感觉这部分不需要这么重度的操作,用户大概率只需要粗略的查看这里面的内容,所以感觉快捷键只需要:
|
|
113
|
+
~~j/k/方向键:用来上下滚动查看内容~~
|
|
114
|
+
~~y: 复制对应对话的内容直接上tree面板复制就行了,不必要在这里~~
|
|
115
|
+
~~gg: 回到顶部~~
|
|
116
|
+
~~G:回到底部~~
|
|
117
|
+
~~/: 底部出现 搜索:两个字样,随后用户输入内容回车进行搜索,只搜索当前fouce的,文本需要高亮,使用 n/N 可以上一个和下一个,具体参考lazygit的形式~~(2026-09-23 完成:按渲染后的正文行匹配自由文本(不支持限定词,消息框的头部不参与),跳转把该行滚到面板顶部,命中文字在缓存的排版行上叠加高亮)
|
|
118
|
+
|
|
119
|
+
~~/: 底部出现 搜索:两个字样,随后用户输入内容回车进行搜索,只搜索当前fouce的,文本需要高亮,使用 n/N 可以上一个和下一个,具体参考lazygit的形式
|
|
120
|
+
h/j/k/l/键盘方向键: 光标移动
|
|
121
|
+
v: 预览模式
|
|
122
|
+
y: 复制内容
|
|
123
|
+
yy: 复制一整行
|
|
124
|
+
b/e: 跳过单词
|
|
125
|
+
gg: 回到顶部
|
|
126
|
+
G:回到底部
|
|
127
|
+
zz: 滚动到中心
|
|
128
|
+
其实就是 vim 这些快速跳转的快捷键, 把常规常用的全部加上就行~~
|
|
129
|
+
|
|
130
|
+
## 使用语言
|
|
131
|
+
|
|
132
|
+
- typescript
|
|
133
|
+
- node.js
|
|
134
|
+
|
|
135
|
+
## 注意点
|
|
136
|
+
|
|
137
|
+
1. 搜索可以强化一下语义,可以类似于github那样:auther:xxxx 这个代表找到作者是 xxxx 的仓库,这里的顶部搜索也可以参考这种设计,例如:model:xxx 当然这个根据实际情况考虑,如果做不到就算了(我建议第一版不要做真正的 semantic search。但是可以先设计成:
|
|
138
|
+
/name:FilmRecall
|
|
139
|
+
/model:opus
|
|
140
|
+
/path:FilmRecall
|
|
141
|
+
/tag:scan
|
|
142
|
+
/after:2026-09-01
|
|
143
|
+
/before:2026-09-20)(2026-09-23:已按这套语法实现,另加 `role:`——普通词只搜 名称 / 标题 / label,模型、路径、角色、时间都要加限定词:SESSIONS 认 name: / model: / path: / after: / before:,TREE 和树对话框认 tag: / role: / after: / before:,CONTENT 只搜自由文本;不认识的 key 和解析不了的日期当普通词搜,某个面板没有的字段对应的限定词被忽略;见 docs/keybindings.md 的 Search 一节)
|
|
144
|
+
2. 快捷键可配置,最好能够叫用户自定义快捷键,上面不是最优解, 可以通过配置文件或者在tui修改的形式来修改快捷键,并且要允许组合建,比如用户希望使用`ctrl + d` 来删除对话,这个也是可以的
|
|
145
|
+
3. 每次fork和delete操作的时候都需要,弹出一个对话框让用户来手动确定。(上下结构,确认和取消(y/n))
|
|
146
|
+
4. 右侧聊天内容要想办法区分“我”的内容和“AI”发送的内容,并且使用 markdown 渲染,最好直接用 直接用pi原生的渲染器
|
|
147
|
+
5. 确定左侧 resume 面板里面展示的内容,比如聊天的时间,模型,名称,运行路径,聊天内容的简介?等等不一定要全部展示,主要还是要UI美观,tree 面板就非常简单了,就按照
|
|
148
|
+
|
|
149
|
+
```txt
|
|
150
|
+
user: hi
|
|
151
|
+
› [我的] 22:18 assistant: Hi! 👋 I'm ready to help with your **FilmRecall** Android project — whether that's writing or refactoring code, de
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
前面 `user` 代表是用户 `assistant` 代表是AI,`:`后面是内容, `[]` 里面是标签, `22:18` 是时间。
|
|
155
|
+
|
|
156
|
+
## UI 图
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
25% 75%
|
|
161
|
+
┌───────────────┬─────────────────────────────┐
|
|
162
|
+
│ │ │
|
|
163
|
+
│ [1] SESSIONS │ │
|
|
164
|
+
│ │ │
|
|
165
|
+
│ │ [3] CONTENT │
|
|
166
|
+
│ │ │
|
|
167
|
+
│ │ │
|
|
168
|
+
├───────────────┤ │
|
|
169
|
+
│ │ │
|
|
170
|
+
│ [2] TREE │ │
|
|
171
|
+
│ │ │
|
|
172
|
+
│ │ │
|
|
173
|
+
└───────────────┴─────────────────────────────┘
|
|
174
|
+
|
|
175
|
+
┌───────────────────┬────────────────────────────────────────┐
|
|
176
|
+
│ │ │
|
|
177
|
+
│ SESSIONS │ │
|
|
178
|
+
│ │ │
|
|
179
|
+
│ 选对话 │ │
|
|
180
|
+
│ │ │
|
|
181
|
+
├───────────────────┤ CONTENT │
|
|
182
|
+
│ │ │
|
|
183
|
+
│ TREE │ 看内容 │
|
|
184
|
+
│ │ │
|
|
185
|
+
│ 选节点 │ │
|
|
186
|
+
│ │ │
|
|
187
|
+
└───────────────────┴────────────────────────────────────────┘
|
|
188
|
+
|
|
189
|
+
┌──────────────────────────────────────┐
|
|
190
|
+
│ SESSION INFO │
|
|
191
|
+
│ │
|
|
192
|
+
│ Name FilmRecall │
|
|
193
|
+
│ Model claude-opus-4 │
|
|
194
|
+
│ Messages 128 │
|
|
195
|
+
│ Tokens 84.2k │
|
|
196
|
+
│ Cost $1.42 │
|
|
197
|
+
│ Created Sep 20 22:18 │
|
|
198
|
+
│ Updated Sep 20 22:21 │
|
|
199
|
+
│ Path ~/.pi/sessions/... │
|
|
200
|
+
│ │
|
|
201
|
+
│ [Esc] Close │
|
|
202
|
+
└──────────────────────────────────────┘
|
|
203
|
+
|
|
204
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
205
|
+
│ CONTENT │
|
|
206
|
+
├──────────────────────────────────────────────────────────────────────────┤
|
|
207
|
+
│ │
|
|
208
|
+
│ ┌─ YOU · 22:18 ─────────────────────────────────────────────────────┐ │
|
|
209
|
+
│ │ │ │
|
|
210
|
+
│ │ 你帮我设计一下 FilmRecall 的 Android 项目目录结构。 │ │
|
|
211
|
+
│ │ │ │
|
|
212
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
213
|
+
│ │
|
|
214
|
+
│ ┌─ ASSISTANT · 22:18 ────────────────────────────────────────────────┐ │
|
|
215
|
+
│ │ │ │
|
|
216
|
+
│ │ 可以。 │ │
|
|
217
|
+
│ │ │ │
|
|
218
|
+
│ │ 我建议把扫描模块拆成几个部分: │ │
|
|
219
|
+
│ │ │ │
|
|
220
|
+
│ │ ### 1. Scanner │ │
|
|
221
|
+
│ │ │ │
|
|
222
|
+
│ │ 负责 CameraX 相机预览以及拍摄。 │ │
|
|
223
|
+
│ │ │ │
|
|
224
|
+
│ │ ### 2. Film Boundary Detection │ │
|
|
225
|
+
│ │ │ │
|
|
226
|
+
│ │ 使用 OpenCV 检测胶片边界…… │ │
|
|
227
|
+
│ │ │ │
|
|
228
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
229
|
+
│ │
|
|
230
|
+
│ │
|
|
231
|
+
├──────────────────────────────────────────────────────────────────────────┤
|
|
232
|
+
│ NORMAL │ / Search ? Help l Focus Enter Restore y Copy gg G │
|
|
233
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
234
|
+
|
|
235
|
+
```
|