berry-agent 0.1.0-alpha.1
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 +96 -0
- package/dist/.api-emit.stamp +1 -0
- package/dist/.build-meta.json +6 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/loop.js +139 -0
- package/dist/agent/queue.js +119 -0
- package/dist/agent/stream.js +62 -0
- package/dist/agent/tools-batch.js +221 -0
- package/dist/agent/types.js +8 -0
- package/dist/api/berry-agent-llm.d.ts +18 -0
- package/dist/api/berry-agent.d.ts +9 -0
- package/dist/api/surface.json +1632 -0
- package/dist/api/tsconfig.paths.json +12 -0
- package/dist/api/typebox-compile.d.ts +2 -0
- package/dist/api/typebox-value.d.ts +2 -0
- package/dist/api/typebox.d.ts +2 -0
- package/dist/browser/cdp.js +256 -0
- package/dist/browser/codes.js +31 -0
- package/dist/browser/discover.js +107 -0
- package/dist/browser/engine.js +169 -0
- package/dist/browser/index.js +17 -0
- package/dist/browser/install.js +342 -0
- package/dist/browser/page.js +321 -0
- package/dist/browser/service.js +158 -0
- package/dist/browser/tools.js +198 -0
- package/dist/browser/types.js +81 -0
- package/dist/channels/ask-queue.js +82 -0
- package/dist/channels/commands.js +129 -0
- package/dist/channels/engine/cell.js +143 -0
- package/dist/channels/engine/diff.js +143 -0
- package/dist/channels/engine/engine.js +416 -0
- package/dist/channels/engine/index.js +8 -0
- package/dist/channels/engine/input-keys.js +86 -0
- package/dist/channels/engine/input.js +596 -0
- package/dist/channels/engine/memory-io.js +71 -0
- package/dist/channels/engine/process-io.js +38 -0
- package/dist/channels/engine/types.js +17 -0
- package/dist/channels/engine/width.js +122 -0
- package/dist/channels/index.js +15 -0
- package/dist/channels/registry.js +56 -0
- package/dist/channels/sdk/admit.js +29 -0
- package/dist/channels/sdk/backend.js +102 -0
- package/dist/channels/sdk/cursor.js +56 -0
- package/dist/channels/sdk/index.js +20 -0
- package/dist/channels/sdk/jsonl.js +208 -0
- package/dist/channels/sdk/protocol.js +24 -0
- package/dist/channels/sdk/schema.js +91 -0
- package/dist/channels/sdk/wire-core.js +446 -0
- package/dist/channels/service.js +169 -0
- package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
- package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
- package/dist/channels/tui/autocomplete/popup.js +148 -0
- package/dist/channels/tui/autocomplete/provider.js +7 -0
- package/dist/channels/tui/autocomplete/token.js +68 -0
- package/dist/channels/tui/backend/ansi-rows.js +211 -0
- package/dist/channels/tui/backend/main-screen.js +207 -0
- package/dist/channels/tui/backend/osc.js +91 -0
- package/dist/channels/tui/backend/transcript.js +230 -0
- package/dist/channels/tui/backend/tui-backend.js +915 -0
- package/dist/channels/tui/editor/editor-model.js +543 -0
- package/dist/channels/tui/editor/editor-view.js +140 -0
- package/dist/channels/tui/editor/editor.js +275 -0
- package/dist/channels/tui/editor/undo-stack.js +35 -0
- package/dist/channels/tui/editor/visual-lines.js +126 -0
- package/dist/channels/tui/editor/word-nav.js +106 -0
- package/dist/channels/tui/history/history-viewer.js +403 -0
- package/dist/channels/tui/index.js +3 -0
- package/dist/channels/tui/layout.js +167 -0
- package/dist/channels/tui/markdown/blocks.js +111 -0
- package/dist/channels/tui/markdown/inline.js +75 -0
- package/dist/channels/tui/markdown/markdown.js +182 -0
- package/dist/channels/tui/memory/memory-viewer.js +540 -0
- package/dist/channels/tui/overlay/alt-screen.js +105 -0
- package/dist/channels/tui/overlay/overlay.js +57 -0
- package/dist/channels/tui/overlay/select-confirm.js +156 -0
- package/dist/channels/tui/panels/todo-panel.js +54 -0
- package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
- package/dist/channels/tui/scroll/scroll-view.js +254 -0
- package/dist/channels/tui/status/status-line.js +76 -0
- package/dist/channels/tui/text.js +38 -0
- package/dist/channels/tui/theme.js +41 -0
- package/dist/channels/types.js +11 -0
- package/dist/channels/ui-core.js +209 -0
- package/dist/checkpoint/capture.js +45 -0
- package/dist/checkpoint/codes.js +33 -0
- package/dist/checkpoint/command.js +82 -0
- package/dist/checkpoint/gate.js +48 -0
- package/dist/checkpoint/index.js +20 -0
- package/dist/checkpoint/restore.js +172 -0
- package/dist/checkpoint/store.js +247 -0
- package/dist/checkpoint/types.js +4 -0
- package/dist/checkpoint/walk.js +146 -0
- package/dist/compaction/ccr-tools.js +112 -0
- package/dist/compaction/ccr.js +84 -0
- package/dist/compaction/codes.js +21 -0
- package/dist/compaction/index.js +18 -0
- package/dist/compaction/policy.js +200 -0
- package/dist/compaction/service.js +422 -0
- package/dist/compaction/slots.js +121 -0
- package/dist/compaction/types.js +10 -0
- package/dist/context/codes.js +40 -0
- package/dist/context/events.js +160 -0
- package/dist/context/index.js +13 -0
- package/dist/context/logger.js +136 -0
- package/dist/context/scope.js +182 -0
- package/dist/context/workspace.js +104 -0
- package/dist/contracts/agent-events.d.ts +73 -0
- package/dist/contracts/agent-events.js +16 -0
- package/dist/contracts/api.d.ts +249 -0
- package/dist/contracts/api.js +299 -0
- package/dist/contracts/approval.d.ts +37 -0
- package/dist/contracts/approval.js +9 -0
- package/dist/contracts/env-ref.d.ts +41 -0
- package/dist/contracts/env-ref.js +32 -0
- package/dist/contracts/errors.d.ts +56 -0
- package/dist/contracts/errors.js +266 -0
- package/dist/contracts/events.d.ts +70 -0
- package/dist/contracts/events.js +329 -0
- package/dist/contracts/index.d.ts +32 -0
- package/dist/contracts/index.js +26 -0
- package/dist/contracts/llm.d.ts +275 -0
- package/dist/contracts/llm.js +12 -0
- package/dist/contracts/messages.d.ts +63 -0
- package/dist/contracts/messages.js +65 -0
- package/dist/contracts/redact.d.ts +46 -0
- package/dist/contracts/redact.js +223 -0
- package/dist/contracts/tools.d.ts +197 -0
- package/dist/contracts/tools.js +33 -0
- package/dist/contracts/types.d.ts +302 -0
- package/dist/contracts/types.js +50 -0
- package/dist/contracts/ui.d.ts +122 -0
- package/dist/contracts/ui.js +15 -0
- package/dist/conversation/agent-service.js +61 -0
- package/dist/conversation/approval-wiring.js +82 -0
- package/dist/conversation/backoff.js +38 -0
- package/dist/conversation/codes.js +47 -0
- package/dist/conversation/control-tools.js +102 -0
- package/dist/conversation/control.js +210 -0
- package/dist/conversation/driver.js +922 -0
- package/dist/conversation/index.js +12 -0
- package/dist/conversation/model-visible.js +169 -0
- package/dist/conversation/open-tools.js +126 -0
- package/dist/conversation/reseed.js +132 -0
- package/dist/conversation/sessions.js +215 -0
- package/dist/conversation/todo.js +203 -0
- package/dist/conversation/types.js +35 -0
- package/dist/conversation/wiring.js +204 -0
- package/dist/credentials/codes.js +59 -0
- package/dist/credentials/commands.js +186 -0
- package/dist/credentials/env-ref.js +34 -0
- package/dist/credentials/index.js +28 -0
- package/dist/credentials/migration.js +18 -0
- package/dist/credentials/oauth.js +266 -0
- package/dist/credentials/refresh.js +152 -0
- package/dist/credentials/secrets.js +118 -0
- package/dist/credentials/types.js +30 -0
- package/dist/exec/bash.js +279 -0
- package/dist/exec/codes.js +37 -0
- package/dist/exec/env.js +99 -0
- package/dist/exec/environment.js +39 -0
- package/dist/exec/git-guard.js +623 -0
- package/dist/exec/index.js +24 -0
- package/dist/exec/registry.js +120 -0
- package/dist/exec/spawn.js +281 -0
- package/dist/exec/tail.js +77 -0
- package/dist/exec/types.js +5 -0
- package/dist/goal/codes.js +33 -0
- package/dist/goal/command.js +175 -0
- package/dist/goal/fold.js +121 -0
- package/dist/goal/gates.js +122 -0
- package/dist/goal/index.js +22 -0
- package/dist/goal/migration.js +48 -0
- package/dist/goal/service.js +501 -0
- package/dist/goal/todo-tool.js +174 -0
- package/dist/goal/types.js +1 -0
- package/dist/goal/update-tool.js +46 -0
- package/dist/host/approval-cmd.js +276 -0
- package/dist/host/assembly.js +1146 -0
- package/dist/host/boot-failures.js +104 -0
- package/dist/host/budget-advisory.js +74 -0
- package/dist/host/budget-broadcast.js +50 -0
- package/dist/host/builtins.js +54 -0
- package/dist/host/cli.js +544 -0
- package/dist/host/codes.js +120 -0
- package/dist/host/config-schema.js +195 -0
- package/dist/host/conversation-stack.js +654 -0
- package/dist/host/core-plugins.js +1685 -0
- package/dist/host/credentials-cmd.js +42 -0
- package/dist/host/disclosure.js +30 -0
- package/dist/host/dispatch.js +105 -0
- package/dist/host/doors-cmd.js +154 -0
- package/dist/host/dump-config.js +44 -0
- package/dist/host/hook-dispatch-guard.js +27 -0
- package/dist/host/import-gate.js +153 -0
- package/dist/host/index.js +54 -0
- package/dist/host/issue-session.js +241 -0
- package/dist/host/loader.js +372 -0
- package/dist/host/main.js +161 -0
- package/dist/host/manifest.js +381 -0
- package/dist/host/mcp-entry.js +53 -0
- package/dist/host/plugin-boot.js +952 -0
- package/dist/host/plugin-context.js +672 -0
- package/dist/host/plugin-install.js +480 -0
- package/dist/host/plugin-reload.js +124 -0
- package/dist/host/plugin-store.js +462 -0
- package/dist/host/plugin-tools.js +359 -0
- package/dist/host/plugin-uninstall.js +257 -0
- package/dist/host/plugins-cmd.js +302 -0
- package/dist/host/plugins-command.js +130 -0
- package/dist/host/plugins-config.js +195 -0
- package/dist/host/prompt-sections.js +133 -0
- package/dist/host/run-entry.js +544 -0
- package/dist/host/runtime.js +209 -0
- package/dist/host/scheduler-tick.js +294 -0
- package/dist/host/serve-daemon.js +501 -0
- package/dist/host/serve-entry.js +279 -0
- package/dist/host/session-anchor.js +41 -0
- package/dist/host/sessions-cmd.js +219 -0
- package/dist/host/sessions-face.js +47 -0
- package/dist/host/settings-store.js +112 -0
- package/dist/host/signals.js +69 -0
- package/dist/host/single-instance.js +105 -0
- package/dist/host/subagent-factory.js +291 -0
- package/dist/host/testkit/harness.js +283 -0
- package/dist/host/testkit/index.js +14 -0
- package/dist/host/testkit/matrix.js +302 -0
- package/dist/host/tool-policy-store.js +177 -0
- package/dist/host/triggers.js +212 -0
- package/dist/host/tui-entry.js +216 -0
- package/dist/host/webui-bridge.js +165 -0
- package/dist/issue/codes.js +38 -0
- package/dist/issue/filter.js +146 -0
- package/dist/issue/github.js +116 -0
- package/dist/issue/index.js +17 -0
- package/dist/issue/mount.js +70 -0
- package/dist/issue/poll.js +58 -0
- package/dist/issue/service.js +341 -0
- package/dist/issue/tools.js +70 -0
- package/dist/issue/types.js +34 -0
- package/dist/issue/webhook.js +126 -0
- package/dist/llm/codes.js +77 -0
- package/dist/llm/complete.js +196 -0
- package/dist/llm/events.js +15 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/inflight.js +65 -0
- package/dist/llm/model-id.js +58 -0
- package/dist/llm/provider-face.d.ts +32 -0
- package/dist/llm/provider-face.js +34 -0
- package/dist/llm/recovery.js +146 -0
- package/dist/llm/runtime.js +46 -0
- package/dist/llm/stream-fn.js +147 -0
- package/dist/lsp/codes.js +31 -0
- package/dist/lsp/connection.js +147 -0
- package/dist/lsp/frame.js +110 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/inject.js +58 -0
- package/dist/lsp/instance.js +264 -0
- package/dist/lsp/service.js +522 -0
- package/dist/lsp/tools.js +53 -0
- package/dist/lsp/types.js +201 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/codes.js +21 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/jsonrpc.js +230 -0
- package/dist/mcp/service.js +125 -0
- package/dist/mcp/tools.js +145 -0
- package/dist/mcp/types.js +108 -0
- package/dist/memory/cite.js +131 -0
- package/dist/memory/codes.js +50 -0
- package/dist/memory/command.js +71 -0
- package/dist/memory/consolidate.js +219 -0
- package/dist/memory/cycle.js +109 -0
- package/dist/memory/dao.js +878 -0
- package/dist/memory/diff.js +238 -0
- package/dist/memory/extract.js +181 -0
- package/dist/memory/fts.js +58 -0
- package/dist/memory/index.js +42 -0
- package/dist/memory/inject.js +354 -0
- package/dist/memory/merge.js +178 -0
- package/dist/memory/migration.js +132 -0
- package/dist/memory/pollution.js +59 -0
- package/dist/memory/port.js +385 -0
- package/dist/memory/review.js +201 -0
- package/dist/memory/scan.js +89 -0
- package/dist/memory/tools.js +508 -0
- package/dist/memory/types.js +140 -0
- package/dist/obs/codes.js +38 -0
- package/dist/obs/db.js +115 -0
- package/dist/obs/index.js +20 -0
- package/dist/obs/rollup.js +61 -0
- package/dist/obs/service.js +363 -0
- package/dist/obs/session-tools.js +282 -0
- package/dist/obs/session-view.js +225 -0
- package/dist/obs/tool.js +97 -0
- package/dist/obs/types.js +1 -0
- package/dist/persist/audit.js +63 -0
- package/dist/persist/aux.js +39 -0
- package/dist/persist/codes.js +35 -0
- package/dist/persist/index.js +30 -0
- package/dist/persist/load-history.js +50 -0
- package/dist/persist/migrations.js +37 -0
- package/dist/persist/paths.js +108 -0
- package/dist/persist/persistence.js +229 -0
- package/dist/persist/schema.js +99 -0
- package/dist/persist/secret-box.js +95 -0
- package/dist/persist/store.js +706 -0
- package/dist/persist/write-behind.js +238 -0
- package/dist/safety/approval.js +171 -0
- package/dist/safety/bwrap.js +123 -0
- package/dist/safety/codes.js +57 -0
- package/dist/safety/danger.js +505 -0
- package/dist/safety/gate.js +226 -0
- package/dist/safety/index.js +24 -0
- package/dist/safety/presets.js +73 -0
- package/dist/safety/roots.js +139 -0
- package/dist/safety/sandbox.js +214 -0
- package/dist/safety/seatbelt.js +88 -0
- package/dist/safety/sensitive.js +34 -0
- package/dist/safety/tool-policy.js +203 -0
- package/dist/safety/types.js +13 -0
- package/dist/scheduler/codes.js +54 -0
- package/dist/scheduler/cron-backend.js +135 -0
- package/dist/scheduler/engine.js +280 -0
- package/dist/scheduler/gates.js +60 -0
- package/dist/scheduler/index.js +25 -0
- package/dist/scheduler/migration.js +22 -0
- package/dist/scheduler/runner.js +171 -0
- package/dist/scheduler/schedule.js +193 -0
- package/dist/scheduler/service.js +283 -0
- package/dist/scheduler/tick.js +139 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/sdk/http.js +865 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/mcp.js +289 -0
- package/dist/sdk/plugin-route-registry.js +126 -0
- package/dist/sdk/plugin-routes.js +149 -0
- package/dist/sdk/security.js +116 -0
- package/dist/sdk/types.js +32 -0
- package/dist/session/budget.js +159 -0
- package/dist/session/codes.js +40 -0
- package/dist/session/derive.js +180 -0
- package/dist/session/event-data.js +1 -0
- package/dist/session/fork.js +72 -0
- package/dist/session/import-gates.js +148 -0
- package/dist/session/index.js +18 -0
- package/dist/session/recover.js +103 -0
- package/dist/session/session.js +279 -0
- package/dist/session/snapshot.js +107 -0
- package/dist/skills/agents.js +242 -0
- package/dist/skills/codes.js +51 -0
- package/dist/skills/discovery.js +266 -0
- package/dist/skills/frontmatter.js +215 -0
- package/dist/skills/index.js +28 -0
- package/dist/skills/load.js +118 -0
- package/dist/skills/manage.js +201 -0
- package/dist/skills/registry.js +138 -0
- package/dist/skills/render.js +124 -0
- package/dist/skills/sections.js +171 -0
- package/dist/skills/types.js +21 -0
- package/dist/subagent/codes.js +50 -0
- package/dist/subagent/declarative.js +55 -0
- package/dist/subagent/index.js +19 -0
- package/dist/subagent/notify.js +51 -0
- package/dist/subagent/provide.js +7 -0
- package/dist/subagent/registry.js +149 -0
- package/dist/subagent/service.js +338 -0
- package/dist/subagent/surface.js +44 -0
- package/dist/subagent/tool.js +153 -0
- package/dist/subagent/types.js +25 -0
- package/dist/tools/apply-patch.js +164 -0
- package/dist/tools/codes.js +109 -0
- package/dist/tools/fs.js +501 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/observed.js +99 -0
- package/dist/tools/pipeline.js +176 -0
- package/dist/tools/protected-read.js +54 -0
- package/dist/tools/registry.js +250 -0
- package/dist/tools/search.js +451 -0
- package/dist/tools/worktree.js +327 -0
- package/dist/web/codes.js +37 -0
- package/dist/web/gate.js +43 -0
- package/dist/web/hygiene.js +195 -0
- package/dist/web/index.js +16 -0
- package/dist/web/service.js +207 -0
- package/dist/web/ssrf-guard.js +44 -0
- package/dist/web/tool.js +76 -0
- package/dist/web/types.js +10 -0
- package/dist/webui/assets/index-B-t2O8gh.js +22 -0
- package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
- package/dist/webui/index.html +14 -0
- package/dist/webui/index.js +14 -0
- package/dist/webui/server.js +549 -0
- package/dist/webui/types.js +38 -0
- package/examples/README.md +26 -0
- package/examples/minimal-code-plugin/entry.js +43 -0
- package/examples/minimal-code-plugin/package.json +12 -0
- package/examples/pure-skill-pack/package.json +14 -0
- package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
- package/package.json +86 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浮层栈:主屏帧的「后画段」与输入路由的「先吃段」。
|
|
3
|
+
*
|
|
4
|
+
* 装配序(每帧):主树 render → renderAll(浮层覆盖其上);
|
|
5
|
+
* 路由序(每键):routeEvent(栈非空短路三序)→ UiBackend 命令 / 队列 / 输入框。
|
|
6
|
+
*/
|
|
7
|
+
export class OverlayStack {
|
|
8
|
+
entries = [];
|
|
9
|
+
/** 栈变更通知(开 / 关——装配层接重绘请求) */
|
|
10
|
+
onChange;
|
|
11
|
+
/** 在场浮层数 */
|
|
12
|
+
get size() {
|
|
13
|
+
return this.entries.length;
|
|
14
|
+
}
|
|
15
|
+
/** 在场浮层只读快照(栈底→栈顶——装配层量高遍历/测试观测面) */
|
|
16
|
+
get contents() {
|
|
17
|
+
return this.entries.map((entry) => entry.content);
|
|
18
|
+
}
|
|
19
|
+
/** 开层:压栈 + 返回句柄(幂等关闭归句柄) */
|
|
20
|
+
open(content, anchor) {
|
|
21
|
+
const entry = { content, anchor, closed: false };
|
|
22
|
+
this.entries.push(entry);
|
|
23
|
+
this.onChange?.();
|
|
24
|
+
return {
|
|
25
|
+
close: () => {
|
|
26
|
+
if (entry.closed)
|
|
27
|
+
return;
|
|
28
|
+
entry.closed = true;
|
|
29
|
+
const idx = this.entries.indexOf(entry);
|
|
30
|
+
if (idx >= 0)
|
|
31
|
+
this.entries.splice(idx, 1);
|
|
32
|
+
this.onChange?.();
|
|
33
|
+
},
|
|
34
|
+
get closed() {
|
|
35
|
+
return entry.closed;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** 浮层渲染(主树渲染后调用——栈底先画、栈顶覆盖) */
|
|
40
|
+
renderAll(buffer) {
|
|
41
|
+
for (const entry of this.entries) {
|
|
42
|
+
const region = entry.anchor({ width: buffer.columns, height: buffer.rows });
|
|
43
|
+
entry.content.render(buffer, region);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 事件路由(模态独占):栈顶先吃;**栈非空恒返 true**——未消费键不穿透
|
|
48
|
+
* 至三序(07 §4.3 overlay 占焦条款)。栈空返 false(无浮层归常态路由)。
|
|
49
|
+
*/
|
|
50
|
+
routeEvent(event) {
|
|
51
|
+
const top = this.entries[this.entries.length - 1];
|
|
52
|
+
if (top === undefined)
|
|
53
|
+
return false;
|
|
54
|
+
top.content.handleEvent(event); // 消费与否都在层内终局(不穿透)
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { ACCENT_INDEX } from '../theme.js';
|
|
2
|
+
import { ansiColor } from '../../engine/index.js';
|
|
3
|
+
import { prefixDisplayWidth } from '../editor/visual-lines.js';
|
|
4
|
+
/** 保守取消值(select——空串与撤销面同语义) */
|
|
5
|
+
export const SELECT_CANCELLED = '';
|
|
6
|
+
/** 高亮行样式(整行反色——面板内最强存在感) */
|
|
7
|
+
const ACTIVE_STYLE = Object.freeze({ inverse: true });
|
|
8
|
+
/** 标题样式(accent 定值——theme 单源) */
|
|
9
|
+
const TITLE_STYLE = Object.freeze({ fg: ansiColor(ACCENT_INDEX) });
|
|
10
|
+
/** 说明段样式(dim) */
|
|
11
|
+
const HINT_STYLE = Object.freeze({ dim: true });
|
|
12
|
+
/** 无修饰单字符 / 命名键匹配 */
|
|
13
|
+
function isPlainKey(e, key) {
|
|
14
|
+
return !e.ctrl && !e.alt && !e.shift && !e.meta && e.key === key;
|
|
15
|
+
}
|
|
16
|
+
/** 键事件窄化(其他事件形面板不消费) */
|
|
17
|
+
function asKey(event) {
|
|
18
|
+
return event.kind === 'key' ? event : null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 选择面板:单选浮层。onFinish 后赋(装配在 open 句柄后接线——闭包里关层)。
|
|
22
|
+
*/
|
|
23
|
+
export class SelectPanel {
|
|
24
|
+
/** 完成回调(Enter 选定 value / Esc 取消收 '')——装配接线 */
|
|
25
|
+
onFinish;
|
|
26
|
+
activeIndex = 0;
|
|
27
|
+
done = false;
|
|
28
|
+
title;
|
|
29
|
+
options;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.title = options.title;
|
|
32
|
+
this.options = options.options;
|
|
33
|
+
}
|
|
34
|
+
/** 量高:标题(有则 1)+ 选项行数(单行制——不折行,超宽截断归渲染) */
|
|
35
|
+
measure(width) {
|
|
36
|
+
void width; // 高与宽无关(截断不折行)
|
|
37
|
+
return (this.title !== undefined ? 1 : 0) + this.options.length;
|
|
38
|
+
}
|
|
39
|
+
/** 落位:铺底空格 → 标题 → 选项行(高亮反色 + 说明右对齐) */
|
|
40
|
+
render(buffer, region) {
|
|
41
|
+
// 铺底空格(写格覆盖——未写格会透出主树文字)
|
|
42
|
+
for (let r = 0; r < region.height; r++) {
|
|
43
|
+
for (let c = 0; c < region.width; c++) {
|
|
44
|
+
buffer.setCell(region.row + r, region.col + c, ' ');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let row = region.row;
|
|
48
|
+
if (this.title !== undefined) {
|
|
49
|
+
buffer.writeText(row, region.col, this.title, TITLE_STYLE);
|
|
50
|
+
row += 1;
|
|
51
|
+
}
|
|
52
|
+
for (let i = 0; i < this.options.length; i++, row++) {
|
|
53
|
+
const active = i === this.activeIndex;
|
|
54
|
+
const option = this.options[i];
|
|
55
|
+
// 前缀 + label 段(高亮行整段反色)
|
|
56
|
+
const prefix = active ? '❯ ' : ' ';
|
|
57
|
+
buffer.writeText(row, region.col, `${prefix}${option.label}`, active ? ACTIVE_STYLE : undefined);
|
|
58
|
+
// 说明段右对齐(dim 恒态——不随高亮变脸;按显示宽——CJK 段宽 ≠ 码位数)
|
|
59
|
+
if (option.hint !== undefined && option.hint.length > 0) {
|
|
60
|
+
const hintCols = prefixDisplayWidth(option.hint, option.hint.length);
|
|
61
|
+
buffer.writeText(row, region.col + region.width - hintCols, option.hint, HINT_STYLE);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** 事件分发:完成态静默;↑/↓ 循环移动、Enter 选定、Esc 取消 */
|
|
66
|
+
handleEvent(event) {
|
|
67
|
+
const e = asKey(event);
|
|
68
|
+
if (e === null)
|
|
69
|
+
return true; // 面板占焦——非键事件(文本 / IME / 粘贴)层内终局
|
|
70
|
+
if (e.phase === 'release' || this.done)
|
|
71
|
+
return true;
|
|
72
|
+
if (e.ctrl || e.alt || e.shift || e.meta)
|
|
73
|
+
return true;
|
|
74
|
+
if (e.key === 'up') {
|
|
75
|
+
this.activeIndex = this.activeIndex === 0 ? this.options.length - 1 : this.activeIndex - 1;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (e.key === 'down') {
|
|
79
|
+
this.activeIndex = this.activeIndex === this.options.length - 1 ? 0 : this.activeIndex + 1;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (e.key === 'enter') {
|
|
83
|
+
this.finish(this.options[this.activeIndex]?.value ?? SELECT_CANCELLED);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
if (e.key === 'escape') {
|
|
87
|
+
this.finish(SELECT_CANCELLED);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return true; // 其余键层内终局(模态独占——不穿透)
|
|
91
|
+
}
|
|
92
|
+
/** 单次完成路(触发后锁完成态) */
|
|
93
|
+
finish(value) {
|
|
94
|
+
if (this.done)
|
|
95
|
+
return;
|
|
96
|
+
this.done = true;
|
|
97
|
+
this.onFinish?.(value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 确认面板:两键问答浮层(Enter/y → true、Esc/n → false——保守值 = 不动原状态)。
|
|
102
|
+
*/
|
|
103
|
+
export class ConfirmPanel {
|
|
104
|
+
/** 完成回调——装配接线(同 SelectPanel 形) */
|
|
105
|
+
onFinish;
|
|
106
|
+
done = false;
|
|
107
|
+
message;
|
|
108
|
+
confirmHint;
|
|
109
|
+
cancelHint;
|
|
110
|
+
constructor(options) {
|
|
111
|
+
this.message = options.message;
|
|
112
|
+
this.confirmHint = options.confirmHint ?? 'enter 确认';
|
|
113
|
+
this.cancelHint = options.cancelHint ?? 'esc 取消';
|
|
114
|
+
}
|
|
115
|
+
/** 量高:消息 1 + 键提示 1 */
|
|
116
|
+
measure(width) {
|
|
117
|
+
void width;
|
|
118
|
+
return 2;
|
|
119
|
+
}
|
|
120
|
+
/** 落位:铺底空格 → 消息 → 键提示(dim) */
|
|
121
|
+
render(buffer, region) {
|
|
122
|
+
for (let r = 0; r < region.height; r++) {
|
|
123
|
+
for (let c = 0; c < region.width; c++) {
|
|
124
|
+
buffer.setCell(region.row + r, region.col + c, ' ');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
buffer.writeText(region.row, region.col, this.message);
|
|
128
|
+
buffer.writeText(region.row + 1, region.col, `${this.confirmHint} · ${this.cancelHint}`, HINT_STYLE);
|
|
129
|
+
}
|
|
130
|
+
/** 事件分发:Enter/y 确认、Esc/n 取消(保守值) */
|
|
131
|
+
handleEvent(event) {
|
|
132
|
+
const e = asKey(event);
|
|
133
|
+
if (e === null)
|
|
134
|
+
return true;
|
|
135
|
+
if (e.phase === 'release' || this.done)
|
|
136
|
+
return true;
|
|
137
|
+
if (e.ctrl || e.alt || e.shift || e.meta)
|
|
138
|
+
return true;
|
|
139
|
+
if (isPlainKey(e, 'enter') || isPlainKey(e, 'y')) {
|
|
140
|
+
this.finish(true);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (isPlainKey(e, 'escape') || isPlainKey(e, 'n')) {
|
|
144
|
+
this.finish(false);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return true; // 其余键层内终局
|
|
148
|
+
}
|
|
149
|
+
/** 单次完成路 */
|
|
150
|
+
finish(confirmed) {
|
|
151
|
+
if (this.done)
|
|
152
|
+
return;
|
|
153
|
+
this.done = true;
|
|
154
|
+
this.onFinish?.(confirmed);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* todo 面板(07 §4.1 呈现面件 4)——输入框上方紧凑面板。
|
|
3
|
+
*
|
|
4
|
+
* - 数据面纯呈现投影:items 全量快照经 `update()` 整体替换(真源 = 装配
|
|
5
|
+
* 注入的 todoFor——05 §1.1 todo/write 事件载荷的会话侧映射,webui SPA
|
|
6
|
+
* 同源折叠产物);空表与 null/undefined 同为清板;
|
|
7
|
+
* - 条目四态记号:☐ 待办 / ◐ 进行中(activeForm 优先于 content——CC 式
|
|
8
|
+
* 文案)/ ☑ 已完成·暗淡 / ⊙ 缓办·暗淡;
|
|
9
|
+
* - 帽 6 条 + 溢出行「+ N 更多」(暗淡);
|
|
10
|
+
* - 刷新时机归装配(TuiBackend 三时点 repaint / tool_execution_end /
|
|
11
|
+
* agent_end——批 10e-2 已接线),件内零时钟零事件面。
|
|
12
|
+
*/
|
|
13
|
+
import { truncateToWidth } from '../../engine/index.js';
|
|
14
|
+
/** 帽内条数(溢出行另计——spec 定形不预收数字者已实测定形 6) */
|
|
15
|
+
const MAX_ITEMS = 6;
|
|
16
|
+
/** 四态记号(呈现投影——07 §4.1 件 4 定形) */
|
|
17
|
+
const STATUS_MARKS = Object.freeze({
|
|
18
|
+
pending: '☐',
|
|
19
|
+
'in-progress': '◐',
|
|
20
|
+
completed: '☑',
|
|
21
|
+
deferred: '⊙',
|
|
22
|
+
});
|
|
23
|
+
/** 暗淡两态(已完成 / 缓办——退场视觉语义) */
|
|
24
|
+
const DIM_STATUSES = new Set(['completed', 'deferred']);
|
|
25
|
+
/** todo 面板:条目快照 → 固定区行段(清板即零行——布局自洽) */
|
|
26
|
+
export class TodoPanel {
|
|
27
|
+
items = [];
|
|
28
|
+
/** 快照整体替换(null / undefined / 空表同义——清板) */
|
|
29
|
+
update(items) {
|
|
30
|
+
this.items = items ?? [];
|
|
31
|
+
}
|
|
32
|
+
/** 量高:清板 0;帽内条数 + 溢出行 */
|
|
33
|
+
measure(width) {
|
|
34
|
+
void width; // 条目单行制——宽度不敏感(内容截断吸收)
|
|
35
|
+
if (this.items.length === 0)
|
|
36
|
+
return 0;
|
|
37
|
+
return Math.min(this.items.length, MAX_ITEMS) + (this.items.length > MAX_ITEMS ? 1 : 0);
|
|
38
|
+
}
|
|
39
|
+
/** 落位:每条一行(记号 + 空格 + 内容截断);溢出行收尾 */
|
|
40
|
+
render(buffer, region) {
|
|
41
|
+
const visible = this.items.slice(0, MAX_ITEMS);
|
|
42
|
+
visible.forEach((item, i) => {
|
|
43
|
+
const dimmed = DIM_STATUSES.has(item.status);
|
|
44
|
+
// 进行中 activeForm 优先(缺席回落 content)
|
|
45
|
+
const content = item.status === 'in-progress' && item.activeForm !== undefined ? item.activeForm : item.content;
|
|
46
|
+
const row = `${STATUS_MARKS[item.status]} ${truncateToWidth(content, region.width - 2)}`;
|
|
47
|
+
buffer.writeText(region.row + i, region.col, row, dimmed ? { dim: true } : undefined);
|
|
48
|
+
});
|
|
49
|
+
const overflow = this.items.length - MAX_ITEMS;
|
|
50
|
+
if (overflow > 0) {
|
|
51
|
+
buffer.writeText(region.row + MAX_ITEMS, region.col, `+ ${overflow} 更多`, { dim: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具实时进度面板(07 §4.1 呈现面件 5)——状态行上方的瞬时活动面板。
|
|
3
|
+
*
|
|
4
|
+
* - 行生命周期:`tool_execution_start` 建档(toolCallId→名——**不建行**,
|
|
5
|
+
* 首个 `tool_execution_update` 才建行)/ 后续 update 原位换行 /
|
|
6
|
+
* `tool_execution_end` 即摘行 / `clear()` 清板(agent_start·end·repaint
|
|
7
|
+
* 三时点——瞬时面律:不入正文、不跨 repaint 保存);
|
|
8
|
+
* - 宽容解码(update 载荷契约面 `unknown`——03 §2.3 自由载荷,呈现侧零
|
|
9
|
+
* 契约收紧):string 直显 / AgentToolResult 形〔content 块数组〕取文本块
|
|
10
|
+
* 倒扫末条非空行 / 其余视为文本缺席退化为 ` ▸ 名 …`;
|
|
11
|
+
* - 帽 4 行 + 溢出行「+ N 更多」;
|
|
12
|
+
* - 与件 3 分职互补:状态行示「执行哪个工具」、本件行示「输出到哪了」。
|
|
13
|
+
*/
|
|
14
|
+
import { truncateToWidth } from '../../engine/index.js';
|
|
15
|
+
/** 帽内行数(溢出行另计——spec 定形) */
|
|
16
|
+
const MAX_ROWS = 4;
|
|
17
|
+
/**
|
|
18
|
+
* update 载荷宽容解码 → 末行输出文本(null = 文本缺席)。
|
|
19
|
+
* 网格行是单行物理面——string 形内部换行折叠空格(直显语义的物理收口)。
|
|
20
|
+
*/
|
|
21
|
+
export function decodeUpdateText(update) {
|
|
22
|
+
if (typeof update === 'string') {
|
|
23
|
+
const collapsed = update.replaceAll('\n', ' ');
|
|
24
|
+
return collapsed.trim() === '' ? null : collapsed;
|
|
25
|
+
}
|
|
26
|
+
// AgentToolResult 形:content 块数组——取文本块的行序列倒扫末条非空行
|
|
27
|
+
if (typeof update === 'object' && update !== null && Array.isArray(update.content)) {
|
|
28
|
+
const lines = [];
|
|
29
|
+
for (const block of update.content) {
|
|
30
|
+
if (typeof block === 'object' &&
|
|
31
|
+
block !== null &&
|
|
32
|
+
block.type === 'text' &&
|
|
33
|
+
typeof block.text === 'string') {
|
|
34
|
+
lines.push(...block.text.split('\n'));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
38
|
+
const line = lines[i].trim();
|
|
39
|
+
if (line !== '')
|
|
40
|
+
return line;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null; // 数字/布尔/无文本块对象——文本缺席
|
|
44
|
+
}
|
|
45
|
+
/** 工具进度面板:正在流 partial 的工具各占一行(清板即零行) */
|
|
46
|
+
export class ToolProgressPanel {
|
|
47
|
+
/** 建档表(toolCallId→名——start 建档反查;end/clear 同摘) */
|
|
48
|
+
names = new Map();
|
|
49
|
+
rows = [];
|
|
50
|
+
/** 建档不建行(工具名供 update 反查——无档退 toolCallId 防御路径) */
|
|
51
|
+
begin(toolCallId, name) {
|
|
52
|
+
this.names.set(toolCallId, name);
|
|
53
|
+
}
|
|
54
|
+
/** update 消费:已有行原位换文本;无行建行(首 update 建行条款) */
|
|
55
|
+
applyUpdate(toolCallId, update) {
|
|
56
|
+
const text = decodeUpdateText(update);
|
|
57
|
+
const existing = this.rows.find((row) => row.toolCallId === toolCallId);
|
|
58
|
+
if (existing !== undefined) {
|
|
59
|
+
existing.text = text;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.rows.push({ toolCallId, name: this.names.get(toolCallId) ?? toolCallId, text });
|
|
63
|
+
}
|
|
64
|
+
/** end 即摘行(建档同摘——迟到 update 无档退 id 名) */
|
|
65
|
+
end(toolCallId) {
|
|
66
|
+
this.rows = this.rows.filter((row) => row.toolCallId !== toolCallId);
|
|
67
|
+
this.names.delete(toolCallId);
|
|
68
|
+
}
|
|
69
|
+
/** 清板(行与建档同清——agent_start·end·repaint 瞬时面律) */
|
|
70
|
+
clear() {
|
|
71
|
+
this.rows = [];
|
|
72
|
+
this.names.clear();
|
|
73
|
+
}
|
|
74
|
+
/** 量高:清板 0;帽内行数 + 溢出行 */
|
|
75
|
+
measure(width) {
|
|
76
|
+
void width; // 行单行制——宽度不敏感(截断吸收)
|
|
77
|
+
if (this.rows.length === 0)
|
|
78
|
+
return 0;
|
|
79
|
+
return Math.min(this.rows.length, MAX_ROWS) + (this.rows.length > MAX_ROWS ? 1 : 0);
|
|
80
|
+
}
|
|
81
|
+
/** 落位:每工具一行 ` ▸ 名 · 末行`(缺席 ` ▸ 名 …`);溢出行收尾 */
|
|
82
|
+
render(buffer, region) {
|
|
83
|
+
const visible = this.rows.slice(0, MAX_ROWS);
|
|
84
|
+
visible.forEach((row, i) => {
|
|
85
|
+
const tail = row.text !== null ? ` · ${row.text}` : ' …';
|
|
86
|
+
buffer.writeText(region.row + i, region.col, truncateToWidth(` ▸ ${row.name}${tail}`, region.width));
|
|
87
|
+
});
|
|
88
|
+
const overflow = this.rows.length - MAX_ROWS;
|
|
89
|
+
if (overflow > 0) {
|
|
90
|
+
buffer.writeText(region.row + MAX_ROWS, region.col, `+ ${overflow} 更多`, { dim: true });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { buildVisualLineMap, findVisualLineAt } from '../editor/visual-lines.js';
|
|
2
|
+
/** 滚动条 thumb 样式(dim——存在感弱于正文) */
|
|
3
|
+
const SCROLLBAR_STYLE = Object.freeze({ dim: true });
|
|
4
|
+
/** 滚动条 thumb 字符 */
|
|
5
|
+
const THUMB = '┃';
|
|
6
|
+
/** 未渲染过时的防御缺省(render 后由实测回写) */
|
|
7
|
+
const DEFAULT_WIDTH = 80;
|
|
8
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
9
|
+
/** 单步滚动键位表(无修饰——ctrl/meta/shift 组合归上层) */
|
|
10
|
+
const SCROLL_ACTIONS = Object.freeze({
|
|
11
|
+
up: 'line-up',
|
|
12
|
+
down: 'line-down',
|
|
13
|
+
pageup: 'page-up',
|
|
14
|
+
pagedown: 'page-down',
|
|
15
|
+
home: 'to-top',
|
|
16
|
+
end: 'to-bottom',
|
|
17
|
+
});
|
|
18
|
+
/** 滚轮单步视觉行数(vim mousescroll ver 缺省档三行——tmux copy-mode 五行不取;07 引擎节件 6 条款码面缺省参数) */
|
|
19
|
+
const WHEEL_LINES = 3;
|
|
20
|
+
/**
|
|
21
|
+
* 滚动视口:逻辑行集为数据源,字素硬折视觉行(复用 editor 基件折行算术),
|
|
22
|
+
* 自持视口偏移与尾随态。
|
|
23
|
+
*/
|
|
24
|
+
export class ScrollView {
|
|
25
|
+
lines = [];
|
|
26
|
+
/** 折叠缓存(行集引用失效 + 折宽分槽——同一行集多折宽并存零抖动) */
|
|
27
|
+
cacheLines = null;
|
|
28
|
+
cacheMaps = new Map();
|
|
29
|
+
/** 视口首行(视觉行下标) */
|
|
30
|
+
offset = 0;
|
|
31
|
+
/** 尾随模式(true = 内容更替贴尾) */
|
|
32
|
+
follow = true;
|
|
33
|
+
/** 折宽与视口高实测(render / measure 回写——显式滚动的即时夹取依据) */
|
|
34
|
+
lastWidth = DEFAULT_WIDTH;
|
|
35
|
+
viewportHeight = DEFAULT_PAGE_SIZE;
|
|
36
|
+
/** 量高帽(嵌入 Flex 布局时限制分配;缺省全量——副屏 root 直收全屏 region) */
|
|
37
|
+
maxHeight;
|
|
38
|
+
/** 滚动通知(装配层接重绘请求) */
|
|
39
|
+
onScroll;
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.maxHeight = options.maxHeight;
|
|
42
|
+
}
|
|
43
|
+
/* ---------------- 数据面 ---------------- */
|
|
44
|
+
/** 行集整体替换(回看器快照档——新事件不追加、整档重设) */
|
|
45
|
+
setLines(lines) {
|
|
46
|
+
this.lines = lines;
|
|
47
|
+
this.cacheLines = null; // 缓存失效(引用比对落空即整缓存弃)
|
|
48
|
+
if (this.follow) {
|
|
49
|
+
this.offset = Number.MAX_SAFE_INTEGER; // 尾随贴尾
|
|
50
|
+
this.clampNow(); // 即时夹到视觉底
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/* ---------------- 滚动控制面 ---------------- */
|
|
54
|
+
/** 绝对滚动(视觉行下标——越界即时夹取) */
|
|
55
|
+
scrollTo(viewportRow) {
|
|
56
|
+
this.applyOffset(viewportRow);
|
|
57
|
+
}
|
|
58
|
+
/** 相对滚动(负向上 / 正向下;0 无动作) */
|
|
59
|
+
scrollBy(delta) {
|
|
60
|
+
if (delta !== 0)
|
|
61
|
+
this.applyOffset(this.offset + delta);
|
|
62
|
+
}
|
|
63
|
+
/** 滚到顶部(破随) */
|
|
64
|
+
scrollToTop() {
|
|
65
|
+
this.applyOffset(0);
|
|
66
|
+
}
|
|
67
|
+
/** 滚到底部(复随) */
|
|
68
|
+
scrollToEnd() {
|
|
69
|
+
this.applyOffset(Number.MAX_SAFE_INTEGER);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 滚到逻辑行定位处(件 8 回看器搜索跳转消费):定位处所在视觉行对齐视口顶
|
|
73
|
+
* (col 缺省 = 行首);两遍折叠同 render 判溢出(溢出档折宽与呈现一致——
|
|
74
|
+
* 跳转落点不因滚动条让列错行)。显式滚动路——破随(applyOffset 同律)。
|
|
75
|
+
*/
|
|
76
|
+
scrollToLine(line, col = 0) {
|
|
77
|
+
let map = this.visualMap(this.lastWidth, false);
|
|
78
|
+
if (map.length > this.viewportHeight)
|
|
79
|
+
map = this.visualMap(this.lastWidth, true);
|
|
80
|
+
// 定位处所在视觉行(col 落段判定——与 findVisualLineAt 同规则的本体内联)
|
|
81
|
+
const vi = findVisualLineAt(map, line, col);
|
|
82
|
+
this.applyOffset(vi);
|
|
83
|
+
}
|
|
84
|
+
/** 视口首行(夹取后的观测值) */
|
|
85
|
+
get scrollOffset() {
|
|
86
|
+
return this.offset;
|
|
87
|
+
}
|
|
88
|
+
/** 尾随态(回看器判断是否在追新) */
|
|
89
|
+
get isFollowing() {
|
|
90
|
+
return this.follow;
|
|
91
|
+
}
|
|
92
|
+
/* ---------------- 渲染协商 ---------------- */
|
|
93
|
+
/** 量高:视觉行数夹 maxHeight(缺省全量——副屏 root 场景不经本路) */
|
|
94
|
+
measure(width) {
|
|
95
|
+
this.lastWidth = width;
|
|
96
|
+
const map = this.visualMap(width, false);
|
|
97
|
+
const cap = this.maxHeight ?? map.length;
|
|
98
|
+
if (map.length <= cap)
|
|
99
|
+
return map.length;
|
|
100
|
+
// 溢出预判:滚动条让列重折后行数只多不少——以折后行数夹帽(量高即承诺,不超卖)
|
|
101
|
+
return Math.min(this.visualMap(width, true).length, cap);
|
|
102
|
+
}
|
|
103
|
+
/** 落位渲染:折行判溢出 → 夹取自愈 → 正文 → 滚动条(溢出才显) */
|
|
104
|
+
render(buffer, region) {
|
|
105
|
+
if (region.width <= 1 || region.height <= 0)
|
|
106
|
+
return; // 一列给内容都不够——防御位
|
|
107
|
+
this.lastWidth = region.width;
|
|
108
|
+
this.viewportHeight = region.height;
|
|
109
|
+
// 两遍折叠:全宽判溢出 → 溢出让一列重折(分槽缓存免抖动)
|
|
110
|
+
let map = this.visualMap(region.width, false);
|
|
111
|
+
const reservesBar = map.length > region.height;
|
|
112
|
+
if (reservesBar)
|
|
113
|
+
map = this.visualMap(region.width, true);
|
|
114
|
+
// follow 物化:尾随态贴真尾后再夹取(防御 setLines 时几何缺省值的贴尾失真
|
|
115
|
+
// ——批 10f-4 回看器开屏场景:构造期 clampNow 用缺省页高,首帧真几何才对齐)
|
|
116
|
+
if (this.follow)
|
|
117
|
+
this.offset = Number.MAX_SAFE_INTEGER;
|
|
118
|
+
this.clampOffset(map, region.height);
|
|
119
|
+
// 正文(视口内视觉行——切片写出经 writeSlice 单点,子类覆写带样式)
|
|
120
|
+
const end = Math.min(map.length, this.offset + region.height);
|
|
121
|
+
for (let vi = this.offset; vi < end; vi++) {
|
|
122
|
+
const seg = map[vi];
|
|
123
|
+
this.writeSlice(buffer, region, vi - this.offset, seg);
|
|
124
|
+
}
|
|
125
|
+
// 滚动条(溢出才显——右列 thumb 段按比例)
|
|
126
|
+
if (reservesBar)
|
|
127
|
+
this.drawScrollbar(buffer, region, map.length);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 视口切片写出(批 10f-4 件 8 回看器接缝):基类裸文本直写;子类覆写按
|
|
131
|
+
* 带样式行渲染(样式段 + 匹配高亮)——折叠与偏移算术恒归本件,样式呈现
|
|
132
|
+
* 归子类(零第二滚动引擎)。
|
|
133
|
+
*/
|
|
134
|
+
writeSlice(buffer, region, displayRow, seg) {
|
|
135
|
+
const line = this.lines[seg.line] ?? '';
|
|
136
|
+
buffer.writeText(region.row + displayRow, region.col, line.slice(seg.startCol, seg.startCol + seg.length));
|
|
137
|
+
}
|
|
138
|
+
/* ---------------- 输入事件 ---------------- */
|
|
139
|
+
/** 输入消费:key = 无修饰滚动键位(press/repeat 相);mouse = 滚轮(其余归上层) */
|
|
140
|
+
handleEvent(event) {
|
|
141
|
+
if (event.kind === 'mouse') {
|
|
142
|
+
// 滚轮消费路(mu-2):wheel 无 release 相(终端不报——press 一相到达);
|
|
143
|
+
// 修饰位照常滚动——shift/ctrl+轮在终端侧多已截留改道(水平滚/缩放),
|
|
144
|
+
// SGR 报文到达即按垂直滚消费。走 scrollBy 显式滚动路(复随判据同键盘)
|
|
145
|
+
if (event.phase !== 'press')
|
|
146
|
+
return false;
|
|
147
|
+
if (event.button === 'wheel-up') {
|
|
148
|
+
this.scrollBy(-WHEEL_LINES);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (event.button === 'wheel-down') {
|
|
152
|
+
this.scrollBy(WHEEL_LINES);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
return false; // 非滚轮鼠标相归上层(子类选区路)
|
|
156
|
+
}
|
|
157
|
+
if (event.kind !== 'key')
|
|
158
|
+
return false;
|
|
159
|
+
if (event.phase === 'release')
|
|
160
|
+
return false;
|
|
161
|
+
if (event.ctrl || event.alt || event.shift || event.meta)
|
|
162
|
+
return false;
|
|
163
|
+
const action = SCROLL_ACTIONS[event.key];
|
|
164
|
+
if (action === undefined)
|
|
165
|
+
return false;
|
|
166
|
+
switch (action) {
|
|
167
|
+
case 'line-up':
|
|
168
|
+
this.scrollBy(-1);
|
|
169
|
+
break;
|
|
170
|
+
case 'line-down':
|
|
171
|
+
this.scrollBy(1);
|
|
172
|
+
break;
|
|
173
|
+
case 'page-up':
|
|
174
|
+
this.scrollBy(-this.viewportHeight);
|
|
175
|
+
break;
|
|
176
|
+
case 'page-down':
|
|
177
|
+
this.scrollBy(this.viewportHeight);
|
|
178
|
+
break;
|
|
179
|
+
case 'to-top':
|
|
180
|
+
this.scrollToTop();
|
|
181
|
+
break;
|
|
182
|
+
case 'to-bottom':
|
|
183
|
+
this.scrollToEnd();
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
/* ---------------- 内部算术 ---------------- */
|
|
189
|
+
/** 视口几何观测(子类屏幕坐标反查用——render/measure 实测回写值) */
|
|
190
|
+
get viewportGeometry() {
|
|
191
|
+
return { width: this.lastWidth, height: this.viewportHeight };
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 视口内显示行 → 视觉段反查(mu-2 子类接缝:屏幕坐标 → 逻辑位的折叠映射
|
|
195
|
+
* 半场——与 render 两遍折叠同判溢出同分槽缓存,零第二映射;界外返 null)。
|
|
196
|
+
*/
|
|
197
|
+
segmentAt(displayRow) {
|
|
198
|
+
let map = this.visualMap(this.lastWidth, false);
|
|
199
|
+
if (map.length > this.viewportHeight)
|
|
200
|
+
map = this.visualMap(this.lastWidth, true);
|
|
201
|
+
return map[this.offset + displayRow] ?? null;
|
|
202
|
+
}
|
|
203
|
+
/** 滚动条列命中判(溢出让列时末列——07 件 8「视口外命中零动作」的判据位) */
|
|
204
|
+
hitScrollbar(col) {
|
|
205
|
+
const map = this.visualMap(this.lastWidth, false);
|
|
206
|
+
return map.length > this.viewportHeight && col === this.lastWidth - 1;
|
|
207
|
+
}
|
|
208
|
+
/** 折叠缓存取(行集引用 + 折宽双键——分槽并存) */
|
|
209
|
+
visualMap(width, reserveBar) {
|
|
210
|
+
const foldWidth = Math.max(1, width - (reserveBar ? 1 : 0));
|
|
211
|
+
if (this.cacheLines !== this.lines) {
|
|
212
|
+
this.cacheLines = this.lines;
|
|
213
|
+
this.cacheMaps.clear();
|
|
214
|
+
}
|
|
215
|
+
let map = this.cacheMaps.get(foldWidth);
|
|
216
|
+
if (map === undefined) {
|
|
217
|
+
map = buildVisualLineMap(this.lines, foldWidth);
|
|
218
|
+
this.cacheMaps.set(foldWidth, map);
|
|
219
|
+
}
|
|
220
|
+
return map;
|
|
221
|
+
}
|
|
222
|
+
/** 以已知折宽即时夹取(显式滚动 / 尾随贴尾的收口路——渲染后缓存命中零成本) */
|
|
223
|
+
clampNow() {
|
|
224
|
+
const map = this.visualMap(this.lastWidth, false);
|
|
225
|
+
this.clampOffset(map, this.viewportHeight);
|
|
226
|
+
}
|
|
227
|
+
/** 偏移夹取(内容短于视口归零)+ 复随判据(到底即复随;破随只在显式滚动) */
|
|
228
|
+
clampOffset(map, viewportHeight) {
|
|
229
|
+
const maxOffset = Math.max(0, map.length - viewportHeight);
|
|
230
|
+
this.offset = Math.min(Math.max(0, this.offset), maxOffset);
|
|
231
|
+
if (this.offset >= maxOffset)
|
|
232
|
+
this.follow = true;
|
|
233
|
+
}
|
|
234
|
+
/** 显式滚动统一路(先破随后即时夹取——夹到底自然复随) */
|
|
235
|
+
applyOffset(next) {
|
|
236
|
+
this.follow = false;
|
|
237
|
+
this.offset = Math.max(0, next);
|
|
238
|
+
this.clampNow();
|
|
239
|
+
this.onScroll?.();
|
|
240
|
+
}
|
|
241
|
+
/** 滚动条:右列 thumb 段(size 按比例、start 按偏移比例) */
|
|
242
|
+
drawScrollbar(buffer, region, totalLines) {
|
|
243
|
+
const h = region.height;
|
|
244
|
+
const maxOffset = Math.max(0, totalLines - h);
|
|
245
|
+
if (maxOffset === 0)
|
|
246
|
+
return; // 防御位(溢出判据已排除——双保险)
|
|
247
|
+
const thumbSize = Math.max(1, Math.floor((h * h) / totalLines));
|
|
248
|
+
const thumbStart = Math.round((this.offset / maxOffset) * (h - thumbSize));
|
|
249
|
+
const col = region.col + region.width - 1;
|
|
250
|
+
for (let r = 0; r < thumbSize; r++) {
|
|
251
|
+
buffer.setCell(region.row + thumbStart + r, col, THUMB, SCROLLBAR_STYLE);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|