@gavin-hu/my-pi-agent 0.18.0 → 0.20.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/README.md +66 -52
- package/README.zh-CN.md +56 -46
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +507 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +774 -574
- package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
- package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
- package/docs/extensions/file-browse.md +561 -0
- package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
- package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
- package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
- package/{extensions/job/README.md → docs/extensions/job.md} +62 -52
- package/{extensions/memory/README.md → docs/extensions/memory.md} +71 -60
- package/{extensions/plan/README.md → docs/extensions/plan.md} +440 -322
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -31
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +298 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +165 -118
- package/docs/extensions/team.md +254 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +54 -36
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +260 -163
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +137 -79
- package/extensions/board/commands.ts +105 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +170 -0
- package/extensions/board/index.ts +40 -0
- package/extensions/board/input.ts +54 -0
- package/extensions/board/join.ts +42 -0
- package/extensions/board/page.ts +388 -0
- package/extensions/board/result.ts +108 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +90 -0
- package/extensions/board/seat.ts +28 -0
- package/extensions/board/snapshot.ts +42 -0
- package/extensions/board/tools.ts +444 -0
- package/extensions/board/transport/router.ts +91 -0
- package/extensions/board/transport/server.ts +138 -0
- package/extensions/board/types.ts +121 -0
- package/extensions/board/view/client.ts +51 -0
- package/extensions/board/view/dialog.ts +43 -0
- package/extensions/board/view/keys.ts +125 -0
- package/extensions/board/view/live.ts +231 -0
- package/extensions/board/view/names.ts +91 -0
- package/extensions/board/view/page.ts +118 -0
- package/extensions/board/view/render.ts +519 -0
- package/extensions/board/view/styles.ts +228 -0
- package/extensions/board/view/team.ts +327 -0
- package/extensions/bytefly/footer/budget.ts +6 -5
- package/extensions/bytefly/footer/collect/entries.ts +12 -4
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
- package/extensions/bytefly/footer/config.ts +19 -8
- package/extensions/bytefly/footer/price/price.ts +30 -8
- package/extensions/bytefly/footer/register.ts +5 -5
- package/extensions/bytefly/footer/render/format.ts +18 -0
- package/extensions/bytefly/footer/render/lines.ts +23 -7
- package/extensions/bytefly/footer/render/segments.ts +117 -31
- package/extensions/bytefly/footer/types.ts +29 -5
- package/extensions/bytefly/footer/unpriced.ts +42 -17
- package/extensions/bytefly/panel/commands.ts +67 -7
- package/extensions/bytefly/panel/filter.ts +68 -0
- package/extensions/bytefly/panel/format.ts +16 -15
- package/extensions/bytefly/panel/join.ts +56 -13
- package/extensions/bytefly/panel/prices.ts +43 -0
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +75 -43
- package/extensions/bytefly/panel/tui/entries.ts +26 -8
- package/extensions/bytefly/panel/tui/frame.ts +130 -93
- package/extensions/bytefly/panel/tui/keys.ts +352 -0
- package/extensions/bytefly/panel/tui/list-view.ts +25 -46
- package/extensions/bytefly/panel/tui/panes.ts +76 -0
- package/extensions/bytefly/panel/tui/section-view.ts +126 -0
- package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
- package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
- package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
- package/extensions/bytefly/panel/tui.ts +306 -259
- package/extensions/bytefly/picker/discovery.ts +0 -2
- package/extensions/bytefly/picker/types.ts +0 -2
- package/extensions/bytefly/update/check.ts +39 -0
- package/extensions/bytefly/update/format.ts +15 -0
- package/extensions/bytefly/update/register.ts +73 -34
- package/extensions/bytefly/update/state.ts +6 -3
- package/extensions/check/commands.ts +2 -1
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +1 -1
- package/extensions/file-browse/files/classify.ts +136 -0
- package/extensions/file-browse/files/files.ts +12 -0
- package/extensions/file-browse/files/index.ts +90 -0
- package/extensions/file-browse/files/list.ts +261 -0
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/files/tree.ts +101 -0
- package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
- package/extensions/file-browse/index.ts +4 -4
- package/extensions/file-browse/{live.ts → live/live.ts} +20 -69
- package/extensions/file-browse/{router.ts → transport/router.ts} +21 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/{chrome.ts → view/chrome.ts} +4 -4
- package/extensions/file-browse/{client.ts → view/client.ts} +1 -1
- package/extensions/file-browse/{diff-html.ts → view/diff-html.ts} +3 -3
- package/extensions/file-browse/view/emit.ts +51 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +19 -18
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +18 -20
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +19 -14
- package/extensions/file-browse/{styles.ts → view/styles.ts} +29 -5
- package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
- package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
- package/extensions/file-read/schema.ts +2 -1
- package/extensions/job/runtime.ts +4 -4
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +21 -16
- package/extensions/plan/feedback.ts +185 -52
- package/extensions/plan/format.ts +10 -3
- package/extensions/plan/index.ts +23 -5
- package/extensions/plan/plan-files.ts +88 -0
- package/extensions/plan/plan-index.ts +43 -9
- package/extensions/plan/plan-text.ts +33 -23
- package/extensions/plan/plans.ts +27 -34
- package/extensions/plan/policy.ts +30 -17
- package/extensions/plan/review.ts +3 -27
- package/extensions/plan/runtime.ts +52 -11
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +3 -22
- package/extensions/plan/todos.ts +51 -3
- package/extensions/plan/tools/exit.ts +20 -95
- package/extensions/plan/tools/todo-seed.ts +4 -2
- package/extensions/plan/tools/write.ts +12 -1
- package/extensions/plan/tui.ts +5 -34
- package/extensions/plan/types.ts +15 -8
- package/extensions/plan/web-blocks.ts +89 -0
- package/extensions/plan/web-chrome.ts +3 -5
- package/extensions/plan/web-client.ts +179 -330
- package/extensions/plan/web-collab.ts +3 -3
- package/extensions/plan/web-comments.ts +18 -8
- package/extensions/plan/web-composer.ts +80 -0
- package/extensions/plan/web-draft.ts +40 -0
- package/extensions/plan/web-keys.ts +134 -0
- package/extensions/plan/web-outline.ts +192 -0
- package/extensions/plan/web-page.ts +4 -8
- package/extensions/plan/web-plan.ts +150 -0
- package/extensions/plan/web-router.ts +3 -58
- package/extensions/plan/web-settle.ts +194 -0
- package/extensions/plan/web-share-session.ts +24 -30
- package/extensions/plan/web-streams.ts +14 -14
- package/extensions/plan/web-tokens.ts +2 -2
- package/extensions/plan/web.ts +66 -120
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +15 -14
- package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
- package/extensions/rewind/{config.ts → config/config.ts} +1 -1
- package/extensions/rewind/index.ts +3 -3
- package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
- package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
- package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
- package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
- package/extensions/rewind/{format.ts → text/format.ts} +3 -3
- package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
- package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
- package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
- package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
- package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
- package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
- package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
- package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
- package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
- package/extensions/rewind/{status.ts → view/status.ts} +4 -4
- package/extensions/session-bus/commands.ts +17 -69
- package/extensions/session-bus/format.ts +22 -45
- package/extensions/session-bus/index.ts +13 -7
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +32 -4
- package/{lib/claims → extensions/session-bus}/overlap.ts +11 -18
- package/extensions/session-bus/protocol.ts +19 -49
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +16 -51
- package/extensions/session-bus/rows.ts +11 -5
- package/extensions/session-bus/runtime.ts +220 -267
- package/extensions/session-bus/store.ts +55 -7
- package/extensions/session-bus/tools.ts +31 -66
- package/extensions/session-bus/tui.ts +5 -5
- package/extensions/session-bus/types.ts +23 -14
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/commands.ts +2 -1
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +145 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +119 -0
- package/extensions/team/index.ts +34 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +267 -0
- package/extensions/team/types.ts +65 -0
- package/extensions/todo/index.ts +4 -1
- package/extensions/todo/nudge.ts +8 -2
- package/extensions/web-access/fetch/page.ts +4 -1
- package/extensions/web-access/fetch/structured.ts +447 -0
- package/extensions/web-access/fetch/tool.ts +2 -1
- package/extensions/web-access/search/tool.ts +2 -1
- package/extensions/wechat/bridge.ts +106 -251
- package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
- package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
- package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
- package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
- package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
- package/extensions/wechat/dialog/channels.ts +136 -0
- package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +37 -37
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +2 -2
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/intake.ts +245 -0
- package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
- package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
- package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +100 -22
- package/extensions/wechat/{presence.ts → peer/presence.ts} +8 -7
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +1 -1
- package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
- package/extensions/wechat/{state.ts → state/state.ts} +14 -2
- package/extensions/wechat/{format.ts → text/format.ts} +3 -3
- package/extensions/wechat/{notices.ts → text/notices.ts} +8 -0
- package/extensions/wechat/tools.ts +29 -12
- package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
- package/extensions/wechat/{deliver.ts → transport/deliver.ts} +72 -28
- package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
- package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
- package/extensions/wechat/transport/outbound.ts +142 -0
- package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
- package/extensions/wechat/types.ts +10 -1
- package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
- package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
- package/extensions/worktree/commands.ts +24 -17
- package/extensions/worktree/exit.ts +12 -6
- package/extensions/worktree/git.ts +22 -14
- package/extensions/worktree/guard.ts +18 -290
- package/extensions/worktree/handoff.ts +10 -2
- package/extensions/worktree/index.ts +2 -1
- package/extensions/worktree/lifecycle.ts +2 -0
- package/extensions/worktree/registry.ts +20 -11
- package/extensions/worktree/root-tools.ts +13 -3
- package/extensions/worktree/runtime.ts +8 -2
- package/extensions/worktree/screen.ts +15 -8
- package/extensions/worktree/shell.ts +303 -0
- package/extensions/worktree/state.ts +1 -1
- package/extensions/worktree/status.ts +42 -9
- package/extensions/worktree/tools.ts +19 -13
- package/lib/README.md +11 -8
- package/lib/board/card.ts +479 -0
- package/lib/board/links.ts +141 -0
- package/lib/board/page-pointer.ts +310 -0
- package/lib/board/round-text.ts +81 -0
- package/lib/board/round.ts +94 -0
- package/lib/board/rules.ts +437 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +497 -0
- package/lib/board/view.ts +234 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +35 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/serve.ts +237 -0
- package/lib/http/server.ts +12 -10
- package/lib/http/sink.ts +135 -0
- package/lib/http/token.ts +38 -0
- package/lib/plan-entry.ts +11 -0
- package/lib/policy.ts +25 -2
- package/lib/requirements.ts +6 -7
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +201 -0
- package/lib/session-tool.ts +184 -0
- package/lib/spawn.ts +37 -3
- package/lib/stray-fields.ts +45 -0
- package/lib/tool-names.ts +38 -2
- package/lib/tui/glyphs.ts +3 -2
- package/lib/web/README.md +57 -4
- package/lib/web/client-runtime.ts +123 -0
- package/lib/web/components.ts +5 -0
- package/lib/web/index.ts +4 -2
- package/lib/web/markup.ts +88 -1
- package/lib/web/names.ts +42 -0
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +26 -14
- package/skills/next/SKILL.md +4 -3
- package/skills/project-docs/SKILL.md +133 -0
- package/skills/project-docs/references/audit.md +131 -0
- package/skills/project-docs/references/changelog.md +105 -0
- package/skills/project-docs/references/discovery.md +127 -0
- package/skills/project-docs/references/readme.md +80 -0
- package/skills/project-docs/references/release.md +84 -0
- package/skills/project-docs/references/roadmap.md +93 -0
- package/skills/project-docs/scripts/state.ts +316 -0
- package/skills/refactor/SKILL.md +100 -125
- package/skills/refactor/references/handoff.md +35 -0
- package/skills/refactor/references/signals.md +54 -0
- package/skills/refactor/scripts/inventory.ts +587 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +0 -40
- package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
- package/extensions/file-browse/README.md +0 -506
- package/extensions/file-browse/files.ts +0 -564
- package/extensions/file-browse/server.ts +0 -141
- package/extensions/job/process.ts +0 -35
- package/extensions/plan/split.ts +0 -87
- package/extensions/session-bus/README.md +0 -247
- package/extensions/session-bus/team.ts +0 -102
- package/extensions/wechat/outbound.ts +0 -100
- package/lib/plans/sections.ts +0 -172
- package/lib/plans/setting.ts +0 -52
- package/lib/plans/split.ts +0 -215
- package/lib/plans/store.ts +0 -19
- package/lib/plans/team.ts +0 -151
- package/skills/changelog/SKILL.md +0 -148
- package/skills/parallel-dev/SKILL.md +0 -150
- /package/extensions/file-browse/{rev.ts → live/rev.ts} +0 -0
- /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
- /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
- /package/extensions/file-browse/{live-client.ts → view/live-client.ts} +0 -0
- /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
- /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
- /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
- /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
- /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
- /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
- /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
|
@@ -1,45 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The control center: three sections around one frame.
|
|
3
3
|
*
|
|
4
4
|
* Extensions lists every resolved extension with its enable checkbox and its
|
|
5
|
-
* health
|
|
6
|
-
*
|
|
5
|
+
* health, Skills the same with what its `SKILL.md` declares, and Settings the
|
|
6
|
+
* curated preferences with their values. All three are drawn by one frame —
|
|
7
|
+
* header, tabs, a shared body height, one focused-row block — so the screen does
|
|
8
|
+
* not change shape when the section does.
|
|
9
|
+
*
|
|
7
10
|
* The component owns the section switch, the cursors, the focused row key per
|
|
8
|
-
* section, the
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* surface, and keeps the focused row's key across rebuilds. The screen is drawn
|
|
13
|
-
* from the design system — a label-first `titleRule`, one `listRow` grammar, one
|
|
14
|
-
* `checkbox` — and its scroll math from `lib/tui/cursor.ts`.
|
|
11
|
+
* section, the filter, the help block, the pending toggle chain and the notice.
|
|
12
|
+
* It is derived: every render rebuilds from the picker runtime, the reports, the
|
|
13
|
+
* skill index, or the settings surface, and keeps the focused row's key across
|
|
14
|
+
* rebuilds.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import {
|
|
19
|
-
import type {
|
|
20
|
-
import {
|
|
21
|
-
import { titleRule } from "../../../lib/tui/rules.ts";
|
|
18
|
+
import type { Component, TuiMouseEvent, TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
19
|
+
import type { ViewportRowsSource } from "../../../lib/tui/chrome.ts";
|
|
20
|
+
import { ListCursor, wheelDelta } from "../../../lib/tui/cursor.ts";
|
|
22
21
|
import type { ExtensionReport } from "../../../lib/requirements.ts";
|
|
23
22
|
import type { PickerRuntime } from "../picker/runtime.ts";
|
|
24
23
|
import type { ResourceKind } from "../picker/types.ts";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
24
|
+
import { NO_FILTER, type PanelFilter } from "./filter.ts";
|
|
25
|
+
import { SECTION_ORDER } from "./format.ts";
|
|
27
26
|
import { UNTRUSTED_SETTINGS_NOTE } from "./settings.ts";
|
|
28
27
|
import type { SettingsSurface } from "./settings-runtime.ts";
|
|
29
|
-
import type { PanelModel,
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import { BODY_START, detailRows, groupLine,
|
|
33
|
-
import {
|
|
28
|
+
import type { PanelModel, PanelSection, SkillInfo } from "./types.ts";
|
|
29
|
+
import { applyEditKey, type EditingState } from "./tui/editing.ts";
|
|
30
|
+
import { entriesOf, entryKey, isSelectable, type PanelEntry } from "./tui/entries.ts";
|
|
31
|
+
import { BODY_START, detailRows, groupLine, headerLine, panelFrame, tabsLine } from "./tui/frame.ts";
|
|
32
|
+
import { hintRow, keyFor } from "./tui/keys.ts";
|
|
33
|
+
import { resourceRow, verdictColumn } from "./tui/list-view.ts";
|
|
34
34
|
import { clampSelection, moveRows, navigateEntries, retainFocus } from "./tui/nav.ts";
|
|
35
|
-
import {
|
|
35
|
+
import { clipRows, isWide, sideWidth } from "./tui/panes.ts";
|
|
36
|
+
import {
|
|
37
|
+
attentionFor,
|
|
38
|
+
chipsFor,
|
|
39
|
+
emptyLabelFor,
|
|
40
|
+
groupLabelFor,
|
|
41
|
+
modelFor,
|
|
42
|
+
sectionCounts,
|
|
43
|
+
tallestBody,
|
|
44
|
+
type ViewSource,
|
|
45
|
+
} from "./tui/section-view.ts";
|
|
46
|
+
import { sideRows } from "./tui/side-pane.ts";
|
|
36
47
|
import { SettingsSection } from "./tui/settings-section.ts";
|
|
37
|
-
import {
|
|
48
|
+
import { projectNote, settingLabelColumn, settingRow } from "./tui/settings-view.ts";
|
|
38
49
|
|
|
39
50
|
/** A section that draws a resource list rather than the settings table. */
|
|
40
51
|
type ListSection = ResourceKind;
|
|
41
52
|
|
|
42
|
-
/**
|
|
53
|
+
/** The filter editor's identity, so it can share the settings editor's key map. */
|
|
54
|
+
const FILTER_EDIT_ID = "filter";
|
|
55
|
+
|
|
56
|
+
/** The longest filter query the editor accepts. */
|
|
57
|
+
const FILTER_MAX = 60;
|
|
58
|
+
|
|
59
|
+
/** Host seams for the control center, injected by the command runner. */
|
|
43
60
|
export interface PanelComponentOptions {
|
|
44
61
|
/** The session's picker runtime: items, scope, toggles, dirty flag. */
|
|
45
62
|
runtime: PickerRuntime;
|
|
@@ -53,7 +70,7 @@ export interface PanelComponentOptions {
|
|
|
53
70
|
section?: PanelSection;
|
|
54
71
|
theme: Theme;
|
|
55
72
|
agentDir: string;
|
|
56
|
-
/** The session working directory, for the config-file path in the
|
|
73
|
+
/** The session working directory, for the config-file path in the block. */
|
|
57
74
|
cwd: string;
|
|
58
75
|
/** Live terminal height, for the row budget. */
|
|
59
76
|
viewportRows?: ViewportRowsSource;
|
|
@@ -66,58 +83,12 @@ export interface PanelComponentOptions {
|
|
|
66
83
|
initialRefresh?: boolean;
|
|
67
84
|
/** Re-probe; `force` ignores the session cache. Awaited before the busy state clears. */
|
|
68
85
|
refresh: (force?: boolean) => void | Promise<void>;
|
|
86
|
+
/** Confirm and reload, so the changes on screen take effect. */
|
|
87
|
+
reload: () => void | Promise<void>;
|
|
69
88
|
onClose: () => void;
|
|
70
89
|
}
|
|
71
90
|
|
|
72
|
-
/**
|
|
73
|
-
interface DetailInput {
|
|
74
|
-
theme: Theme;
|
|
75
|
-
width: number;
|
|
76
|
-
row: PanelRow | undefined;
|
|
77
|
-
budget: number;
|
|
78
|
-
expanded: boolean;
|
|
79
|
-
dirty: boolean;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** What a list section's hint row receives. */
|
|
83
|
-
interface HintInput {
|
|
84
|
-
expanded: boolean;
|
|
85
|
-
refreshing: boolean;
|
|
86
|
-
projectScope: boolean;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** One list section's own vocabulary: what it calls empty, its pane, and its hints. */
|
|
90
|
-
interface ListView {
|
|
91
|
-
emptyLabel: string;
|
|
92
|
-
detail(input: DetailInput): string[];
|
|
93
|
-
hints(input: HintInput): ScreenHint[];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* The two list sections. Everything else — the frame, the rows, the cursor, the
|
|
98
|
-
* keys, the scope — is shared, so a section only supplies what it alone can say.
|
|
99
|
-
*/
|
|
100
|
-
const LIST_VIEWS: Record<ListSection, ListView> = {
|
|
101
|
-
extensions: {
|
|
102
|
-
emptyLabel: "No extensions found.",
|
|
103
|
-
detail: (input) => extensionDetail(input.theme, input.width, input.row, input.budget, input.expanded, input.dirty),
|
|
104
|
-
hints: (input) =>
|
|
105
|
-
sectionHints({ expanded: input.expanded, projectScope: input.projectScope, refreshing: input.refreshing }),
|
|
106
|
-
},
|
|
107
|
-
skills: {
|
|
108
|
-
emptyLabel: "No skills found.",
|
|
109
|
-
detail: (input) => skillDetail(input),
|
|
110
|
-
// A skill has no probe to re-run, so the section never offers the refresh key.
|
|
111
|
-
hints: (input) => sectionHints({ expanded: input.expanded, projectScope: input.projectScope }),
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/** The resource kind a list section toggles, or `undefined` for Settings. */
|
|
116
|
-
function kindOf(section: PanelSection): ResourceKind | undefined {
|
|
117
|
-
return section === "settings" ? undefined : section;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** The docked bytefly panel. */
|
|
91
|
+
/** The control center's screen. */
|
|
121
92
|
export class PanelComponent implements Component {
|
|
122
93
|
private readonly cursors: Record<PanelSection, ListCursor>;
|
|
123
94
|
/** Serialises toggles so a rapid double press applies twice, not once. */
|
|
@@ -138,8 +109,16 @@ export class PanelComponent implements Component {
|
|
|
138
109
|
private skillIndex: ReadonlyMap<string, SkillInfo> | undefined;
|
|
139
110
|
/** Transient feedback (a refusal or a failed write), shown in the hint row. */
|
|
140
111
|
private notice: string | undefined;
|
|
141
|
-
/**
|
|
112
|
+
/** What the reader narrowed the rows to. */
|
|
113
|
+
private filter: PanelFilter = NO_FILTER;
|
|
114
|
+
/** The filter editor, while `/` is open. */
|
|
115
|
+
private filterEditing: EditingState | undefined;
|
|
116
|
+
/** Whether the key table has taken the focused-row block. */
|
|
117
|
+
private helpOpen = false;
|
|
118
|
+
/** Whether the last render kept the project note; the click mapping needs it. */
|
|
142
119
|
private noteShown = false;
|
|
120
|
+
/** The label column the last Settings render used, in columns. */
|
|
121
|
+
private labelColumn = 0;
|
|
143
122
|
/** The Settings section's editor and JSON-pane state. */
|
|
144
123
|
private readonly settingsSection: SettingsSection;
|
|
145
124
|
/** The visible section. */
|
|
@@ -155,21 +134,14 @@ export class PanelComponent implements Component {
|
|
|
155
134
|
this.section = options.section ?? "extensions";
|
|
156
135
|
this.settingsSection = new SettingsSection({
|
|
157
136
|
surface: options.settings,
|
|
158
|
-
theme: options.theme,
|
|
159
137
|
scope: () => this.options.runtime.scope(),
|
|
138
|
+
filter: () => this.filter,
|
|
160
139
|
cursor: this.cursors.settings,
|
|
161
|
-
viewportRows: options.viewportRows,
|
|
162
|
-
cwd: options.cwd,
|
|
163
|
-
agentDir: options.agentDir,
|
|
164
140
|
retain: (entries) => this.retain(entries),
|
|
165
141
|
navigate: (data) => this.navigate(data),
|
|
166
142
|
switchScope: () => this.switchScope(),
|
|
167
143
|
notify: (message) => this.notify(message),
|
|
168
144
|
requestRender,
|
|
169
|
-
setNoteShown: (shown) => {
|
|
170
|
-
this.noteShown = shown;
|
|
171
|
-
},
|
|
172
|
-
hint: (width) => this.hintRow(width, { jsonOpen: this.settingsSection.jsonOpen }),
|
|
173
145
|
});
|
|
174
146
|
}
|
|
175
147
|
|
|
@@ -192,21 +164,23 @@ export class PanelComponent implements Component {
|
|
|
192
164
|
// Everything is rebuilt from live state on each render.
|
|
193
165
|
}
|
|
194
166
|
|
|
195
|
-
/** The
|
|
196
|
-
private
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
return buildPanelModel({
|
|
205
|
-
items: this.options.runtime.items("extensions"),
|
|
206
|
-
reports: this.options.reports(),
|
|
167
|
+
/** The sources the screen's content is derived from. */
|
|
168
|
+
private source(): ViewSource {
|
|
169
|
+
return {
|
|
170
|
+
scope: this.options.runtime.scope(),
|
|
171
|
+
items: (kind) => this.options.runtime.items(kind),
|
|
172
|
+
reports: () => this.options.reports(),
|
|
173
|
+
settings: this.options.settings,
|
|
207
174
|
agentDir: this.options.agentDir,
|
|
208
|
-
filter: this.
|
|
209
|
-
|
|
175
|
+
filter: this.filter,
|
|
176
|
+
commandFilter: this.options.filter,
|
|
177
|
+
dirty: this.dirty,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The merged model for a list section, filtered unless `filtered` is off. */
|
|
182
|
+
private model(kind: ListSection, filtered = true): PanelModel {
|
|
183
|
+
return modelFor(this.source(), kind, () => this.skills(), filtered);
|
|
210
184
|
}
|
|
211
185
|
|
|
212
186
|
/** Skill metadata, loaded on first use and kept for the screen's lifetime. */
|
|
@@ -217,25 +191,8 @@ export class PanelComponent implements Component {
|
|
|
217
191
|
|
|
218
192
|
/** The entries of the visible section. */
|
|
219
193
|
private activeEntries(): PanelEntry[] {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
return settingEntries(this.options.settings.views(this.options.runtime.scope()));
|
|
223
|
-
}
|
|
224
|
-
return entriesOf(this.model(section));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** Keep the focus on the same row key when the list shape changes. */
|
|
228
|
-
private retainFocus(entries: PanelEntry[]): void {
|
|
229
|
-
const before = entryKey(entries[this.cursor.selected]);
|
|
230
|
-
retainFocus(entries, this.cursor, this.focusedKeys[this.section]);
|
|
231
|
-
const after = entryKey(entries[this.cursor.selected]);
|
|
232
|
-
if (after !== undefined) this.focusedKeys[this.section] = after;
|
|
233
|
-
else if (before !== undefined) this.focusedKeys[this.section] = before;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/** Move onto the nearest selectable row when the focus is not already on one. */
|
|
237
|
-
private clampSelection(entries: PanelEntry[]): void {
|
|
238
|
-
clampSelection(entries, this.cursor);
|
|
194
|
+
if (this.section === "settings") return this.settingsSection.entries();
|
|
195
|
+
return entriesOf(this.model(this.section));
|
|
239
196
|
}
|
|
240
197
|
|
|
241
198
|
private switchScope(): void {
|
|
@@ -252,9 +209,9 @@ export class PanelComponent implements Component {
|
|
|
252
209
|
this.settingsSection.reset();
|
|
253
210
|
const focused = entryKey(this.activeEntries()[this.cursor.selected]);
|
|
254
211
|
this.options.runtime.setScope(next);
|
|
255
|
-
// Keep the
|
|
256
|
-
//
|
|
257
|
-
//
|
|
212
|
+
// Keep the row under the cursor across the layer switch; the two scopes do
|
|
213
|
+
// not share entry indices because Project drops the app rows, so the render's
|
|
214
|
+
// focus retention and clamp land on the nearest row when it cannot.
|
|
258
215
|
if (focused !== undefined) {
|
|
259
216
|
const index = this.activeEntries().findIndex((entry) => entryKey(entry) === focused);
|
|
260
217
|
if (index >= 0) this.cursor.selected = index;
|
|
@@ -263,9 +220,7 @@ export class PanelComponent implements Component {
|
|
|
263
220
|
}
|
|
264
221
|
|
|
265
222
|
private toggle(): void {
|
|
266
|
-
const
|
|
267
|
-
if (kind === undefined) return;
|
|
268
|
-
const entry = this.activeEntries()[this.cursor.selected];
|
|
223
|
+
const entry = this.focusedEntry();
|
|
269
224
|
if (entry?.kind !== "row") return;
|
|
270
225
|
const item = entry.row.item;
|
|
271
226
|
if (!item) {
|
|
@@ -274,6 +229,7 @@ export class PanelComponent implements Component {
|
|
|
274
229
|
this.notify(`${entry.row.label} is not resolved; manage it with pi config.`);
|
|
275
230
|
return;
|
|
276
231
|
}
|
|
232
|
+
const kind = this.section === "skills" ? "skills" : "extensions";
|
|
277
233
|
this.chain = this.chain
|
|
278
234
|
.then(() => this.options.runtime.toggle(kind, item))
|
|
279
235
|
.catch(() => undefined)
|
|
@@ -282,15 +238,14 @@ export class PanelComponent implements Component {
|
|
|
282
238
|
|
|
283
239
|
/** Drop the focused resource's project override, restoring the inherited row. */
|
|
284
240
|
private clearOverride(): void {
|
|
285
|
-
const
|
|
286
|
-
if (kind === undefined) return;
|
|
287
|
-
const entry = this.activeEntries()[this.cursor.selected];
|
|
241
|
+
const entry = this.focusedEntry();
|
|
288
242
|
if (entry?.kind !== "row") return;
|
|
289
243
|
const item = entry.row.item;
|
|
290
244
|
if (!item) {
|
|
291
245
|
this.notify(`${entry.row.label} is not resolved; there is no override to clear.`);
|
|
292
246
|
return;
|
|
293
247
|
}
|
|
248
|
+
const kind = this.section === "skills" ? "skills" : "extensions";
|
|
294
249
|
this.chain = this.chain
|
|
295
250
|
.then(() => this.options.runtime.clearOverride(kind, item))
|
|
296
251
|
.catch(() => undefined)
|
|
@@ -301,7 +256,7 @@ export class PanelComponent implements Component {
|
|
|
301
256
|
* Ask for the work the newly visible section needs. The Extensions view probes
|
|
302
257
|
* when the session had no cache — doing it at mount would delay `/bytefly
|
|
303
258
|
* settings`, which never reads the reports — and the Skills view reads its
|
|
304
|
-
* metadata in the same one-shot way
|
|
259
|
+
* metadata in the same one-shot way.
|
|
305
260
|
*/
|
|
306
261
|
prime(): void {
|
|
307
262
|
const section = this.section;
|
|
@@ -339,14 +294,25 @@ export class PanelComponent implements Component {
|
|
|
339
294
|
}
|
|
340
295
|
}
|
|
341
296
|
|
|
297
|
+
/** Hand the reload to the command, which confirms before it takes the session. */
|
|
298
|
+
private async reload(): Promise<void> {
|
|
299
|
+
if (!this.dirty) {
|
|
300
|
+
this.notify("Nothing to apply; change something first.");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
await this.options.reload();
|
|
304
|
+
}
|
|
305
|
+
|
|
342
306
|
/**
|
|
343
|
-
* Step one section left or right. The
|
|
344
|
-
*
|
|
307
|
+
* Step one section left or right, wrapping. The tabs read as a ring, so the last
|
|
308
|
+
* one continues to the first instead of stopping with no explanation.
|
|
345
309
|
*/
|
|
346
310
|
private stepSection(direction: -1 | 1): void {
|
|
347
|
-
const
|
|
311
|
+
const index = SECTION_ORDER.indexOf(this.section);
|
|
312
|
+
const next = SECTION_ORDER[(index + direction + SECTION_ORDER.length) % SECTION_ORDER.length];
|
|
348
313
|
if (next === undefined || next === this.section) return;
|
|
349
314
|
this.section = next;
|
|
315
|
+
this.helpOpen = false;
|
|
350
316
|
this.settingsSection.reset();
|
|
351
317
|
this.prime();
|
|
352
318
|
this.options.requestRender();
|
|
@@ -354,82 +320,167 @@ export class PanelComponent implements Component {
|
|
|
354
320
|
|
|
355
321
|
/** Read one navigation key against the visible section. */
|
|
356
322
|
private navigate(data: string): void {
|
|
357
|
-
navigateEntries(this.activeEntries(), this.cursor, data, { grouped:
|
|
323
|
+
navigateEntries(this.activeEntries(), this.cursor, data, { grouped: true });
|
|
358
324
|
}
|
|
359
325
|
|
|
360
|
-
/**
|
|
361
|
-
private
|
|
362
|
-
|
|
363
|
-
this.
|
|
326
|
+
/** Keep the focus on the same row key when the list shape changes. */
|
|
327
|
+
private retainFocus(entries: PanelEntry[]): void {
|
|
328
|
+
const before = entryKey(entries[this.cursor.selected]);
|
|
329
|
+
retainFocus(entries, this.cursor, this.focusedKeys[this.section]);
|
|
330
|
+
const after = entryKey(entries[this.cursor.selected]);
|
|
331
|
+
if (after !== undefined) this.focusedKeys[this.section] = after;
|
|
332
|
+
else if (before !== undefined) this.focusedKeys[this.section] = before;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Move onto the nearest selectable row when the focus is not already on one. */
|
|
336
|
+
private clampSelection(entries: PanelEntry[]): void {
|
|
337
|
+
clampSelection(entries, this.cursor);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* The focused entry, with the cursor clamped onto a row first: a key can arrive
|
|
342
|
+
* before the first render, when the cursor still sits on a group heading.
|
|
343
|
+
*/
|
|
344
|
+
private focusedEntry(): PanelEntry | undefined {
|
|
345
|
+
const entries = this.activeEntries();
|
|
346
|
+
this.clampSelection(entries);
|
|
347
|
+
return entries[this.cursor.selected];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Retain the focus across a rebuild and return the entry it landed on. */
|
|
351
|
+
private retain(entries: PanelEntry[]): PanelEntry | undefined {
|
|
352
|
+
this.retainFocus(entries);
|
|
353
|
+
this.clampSelection(entries);
|
|
354
|
+
return entries[this.cursor.selected];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Expand or collapse the focused row's findings. */
|
|
358
|
+
private toggleDetail(): void {
|
|
359
|
+
const key = entryKey(this.focusedEntry());
|
|
360
|
+
if (key === undefined) return;
|
|
361
|
+
if (this.expanded.has(key)) this.expanded.delete(key);
|
|
362
|
+
else this.expanded.add(key);
|
|
364
363
|
this.options.requestRender();
|
|
365
364
|
}
|
|
366
365
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (matchesKey(data, Key.right)) {
|
|
386
|
-
this.stepSection(1);
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
this.settingsSection.handleInput(data);
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
if (isCloseKey(data)) {
|
|
393
|
-
this.options.onClose();
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
this.dismissNotice();
|
|
397
|
-
if (matchesKey(data, Key.left)) {
|
|
398
|
-
this.stepSection(-1);
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
if (matchesKey(data, Key.right)) {
|
|
402
|
-
this.stepSection(1);
|
|
366
|
+
/** Open the filter editor, seeded with what is already applied. */
|
|
367
|
+
private openFilter(): void {
|
|
368
|
+
this.filterEditing = { id: FILTER_EDIT_ID, buffer: this.filter.query, caret: this.filter.query.length };
|
|
369
|
+
this.options.requestRender();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* The filter editor's keys. The query applies as it is typed — a filter that
|
|
374
|
+
* only takes effect on `Enter` would make the reader wait to see it work — and
|
|
375
|
+
* `Esc` clears it rather than leaving a hidden narrowing behind.
|
|
376
|
+
*/
|
|
377
|
+
private handleFilterInput(data: string): void {
|
|
378
|
+
const editing = this.filterEditing;
|
|
379
|
+
if (editing === undefined) return;
|
|
380
|
+
const action = applyEditKey(data, editing, FILTER_MAX);
|
|
381
|
+
if (action.kind === "commit") {
|
|
382
|
+
this.filterEditing = undefined;
|
|
383
|
+
this.options.requestRender();
|
|
403
384
|
return;
|
|
404
385
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
this.
|
|
386
|
+
if (action.kind === "cancel") {
|
|
387
|
+
this.filterEditing = undefined;
|
|
388
|
+
this.filter = { ...this.filter, query: "" };
|
|
389
|
+
this.options.requestRender();
|
|
409
390
|
return;
|
|
410
391
|
}
|
|
411
|
-
if (
|
|
412
|
-
|
|
392
|
+
if (!action.changed) return;
|
|
393
|
+
this.filterEditing = action.state;
|
|
394
|
+
this.filter = { ...this.filter, query: action.state.buffer };
|
|
395
|
+
this.options.requestRender();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** One key, read against the section's table, with navigation as the fallback. */
|
|
399
|
+
handleInput(data: string): void {
|
|
400
|
+
const section = this.section;
|
|
401
|
+
// The editor takes every key, `Esc` included: it cancels the edit rather than
|
|
402
|
+
// closing the screen. The filter editor does the same with the query.
|
|
403
|
+
if (section === "settings" && this.settingsSection.isEditing) {
|
|
404
|
+
this.settingsSection.handleEditingInput(data);
|
|
413
405
|
return;
|
|
414
406
|
}
|
|
415
|
-
if (
|
|
416
|
-
this.
|
|
407
|
+
if (this.filterEditing !== undefined) {
|
|
408
|
+
this.handleFilterInput(data);
|
|
417
409
|
return;
|
|
418
410
|
}
|
|
419
|
-
//
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
411
|
+
// Any key dismisses the last notice and still does its own work.
|
|
412
|
+
if (this.notice !== undefined) {
|
|
413
|
+
this.notice = undefined;
|
|
414
|
+
this.options.requestRender();
|
|
423
415
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
this.clearOverride();
|
|
416
|
+
const key = keyFor(data, section);
|
|
417
|
+
if (key !== undefined) {
|
|
418
|
+
this.act(key);
|
|
428
419
|
return;
|
|
429
420
|
}
|
|
421
|
+
// An input can arrive before the first render; put the focus on a row.
|
|
422
|
+
this.clampSelection(this.activeEntries());
|
|
430
423
|
this.navigate(data);
|
|
431
424
|
}
|
|
432
425
|
|
|
426
|
+
/** Run the action a key table row names. */
|
|
427
|
+
private act(key: ReturnType<typeof keyFor>): void {
|
|
428
|
+
switch (key) {
|
|
429
|
+
case "close":
|
|
430
|
+
this.options.onClose();
|
|
431
|
+
return;
|
|
432
|
+
case "previousSection":
|
|
433
|
+
this.stepSection(-1);
|
|
434
|
+
return;
|
|
435
|
+
case "nextSection":
|
|
436
|
+
this.stepSection(1);
|
|
437
|
+
return;
|
|
438
|
+
case "scope":
|
|
439
|
+
this.switchScope();
|
|
440
|
+
return;
|
|
441
|
+
case "filter":
|
|
442
|
+
this.openFilter();
|
|
443
|
+
return;
|
|
444
|
+
case "attention":
|
|
445
|
+
this.filter = { ...this.filter, attentionOnly: !this.filter.attentionOnly };
|
|
446
|
+
this.options.requestRender();
|
|
447
|
+
return;
|
|
448
|
+
case "help":
|
|
449
|
+
this.helpOpen = !this.helpOpen;
|
|
450
|
+
this.options.requestRender();
|
|
451
|
+
return;
|
|
452
|
+
case "reload":
|
|
453
|
+
void this.reload();
|
|
454
|
+
return;
|
|
455
|
+
case "toggle":
|
|
456
|
+
if (this.section === "settings") this.settingsSection.changeSetting();
|
|
457
|
+
else this.toggle();
|
|
458
|
+
return;
|
|
459
|
+
case "change":
|
|
460
|
+
this.settingsSection.changeSetting();
|
|
461
|
+
return;
|
|
462
|
+
case "reset":
|
|
463
|
+
this.settingsSection.resetSetting();
|
|
464
|
+
return;
|
|
465
|
+
case "json":
|
|
466
|
+
this.settingsSection.toggleJson();
|
|
467
|
+
return;
|
|
468
|
+
case "detail":
|
|
469
|
+
this.toggleDetail();
|
|
470
|
+
return;
|
|
471
|
+
case "refresh":
|
|
472
|
+
void this.refresh(true);
|
|
473
|
+
return;
|
|
474
|
+
case "inherit":
|
|
475
|
+
// Only Project scope can hold an override, so the key does nothing
|
|
476
|
+
// elsewhere and the hint row does not offer it there.
|
|
477
|
+
if (this.options.runtime.scope() === "project") this.clearOverride();
|
|
478
|
+
return;
|
|
479
|
+
default:
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
433
484
|
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
434
485
|
// A click while editing leaves the editor the way `Esc` does: nothing is
|
|
435
486
|
// written behind the screen.
|
|
@@ -445,10 +496,7 @@ export class PanelComponent implements Component {
|
|
|
445
496
|
if ((event.type !== "click" && event.type !== "press") || event.button !== "left") return undefined;
|
|
446
497
|
const entries = this.activeEntries();
|
|
447
498
|
if (entries.length === 0) return undefined;
|
|
448
|
-
|
|
449
|
-
// so the rows start one line lower there.
|
|
450
|
-
const note = this.section === "settings" && this.noteShown ? 1 : 0;
|
|
451
|
-
const line = event.y - (BODY_START + note);
|
|
499
|
+
const line = event.y - (BODY_START + (this.noteShown ? 1 : 0));
|
|
452
500
|
const window = this.cursor.window(entries.length);
|
|
453
501
|
if (line < 0 || line >= window.end - window.start) return undefined;
|
|
454
502
|
const index = window.start + line;
|
|
@@ -457,92 +505,91 @@ export class PanelComponent implements Component {
|
|
|
457
505
|
return { handled: true };
|
|
458
506
|
}
|
|
459
507
|
|
|
460
|
-
/** Key hints for the active section; whole hints drop as the width runs out. */
|
|
461
|
-
private hint(width: number, options: { expanded?: boolean; jsonOpen?: boolean } = {}): string {
|
|
462
|
-
if (this.settingsSection.isEditing) return hintLine(this.theme, width, editHints());
|
|
463
|
-
if (this.section === "settings") {
|
|
464
|
-
return hintLine(this.theme, width, settingsHints(options.jsonOpen === true));
|
|
465
|
-
}
|
|
466
|
-
const hints = LIST_VIEWS[this.section].hints({
|
|
467
|
-
expanded: options.expanded === true,
|
|
468
|
-
refreshing: this.refreshing,
|
|
469
|
-
projectScope: this.options.runtime.scope() === "project",
|
|
470
|
-
});
|
|
471
|
-
return hintLine(this.theme, width, hints);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/** The key-hint row, or the notice that temporarily replaces it. */
|
|
475
|
-
private hintRow(width: number, options: { expanded?: boolean; jsonOpen?: boolean } = {}): string {
|
|
476
|
-
return this.notice !== undefined ? noticeLine(this.theme, width, this.notice) : this.hint(width, options);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/** Expand or collapse the focused row's findings. */
|
|
480
|
-
private toggleDetail(): void {
|
|
481
|
-
const key = entryKey(this.activeEntries()[this.cursor.selected]);
|
|
482
|
-
if (key === undefined) return;
|
|
483
|
-
if (this.expanded.has(key)) this.expanded.delete(key);
|
|
484
|
-
else this.expanded.add(key);
|
|
485
|
-
this.options.requestRender();
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/** Retain the focus across a rebuild and return the entry it landed on. */
|
|
489
|
-
private retain(entries: PanelEntry[]): PanelEntry | undefined {
|
|
490
|
-
this.retainFocus(entries);
|
|
491
|
-
this.clampSelection(entries);
|
|
492
|
-
return entries[this.cursor.selected];
|
|
493
|
-
}
|
|
494
|
-
|
|
495
508
|
render(width: number): string[] {
|
|
496
509
|
const w = Math.max(1, width);
|
|
497
|
-
const header = titleRule(this.theme, w, "Bytefly");
|
|
498
510
|
const section = this.section;
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const model = this.model(section);
|
|
502
|
-
const entries = entriesOf(model);
|
|
503
|
-
const summary = sectionBar(
|
|
504
|
-
this.theme,
|
|
505
|
-
w,
|
|
506
|
-
section,
|
|
507
|
-
resourceSummary(
|
|
508
|
-
this.theme,
|
|
509
|
-
w,
|
|
510
|
-
model,
|
|
511
|
-
this.options.runtime.scope(),
|
|
512
|
-
section === "skills" ? { noun: SKILL_NOUN } : { filter: this.options.filter },
|
|
513
|
-
),
|
|
514
|
-
);
|
|
515
|
-
|
|
511
|
+
const scope = this.options.runtime.scope();
|
|
512
|
+
const source = this.source();
|
|
513
|
+
const model = section === "settings" ? undefined : this.model(section);
|
|
514
|
+
const entries = model === undefined ? this.settingsSection.entries() : entriesOf(model);
|
|
516
515
|
const focusedEntry = this.retain(entries);
|
|
517
|
-
const
|
|
518
|
-
const
|
|
519
|
-
|
|
516
|
+
const row = focusedEntry?.kind === "row" ? focusedEntry.row : undefined;
|
|
517
|
+
const setting = focusedEntry?.kind === "setting" ? focusedEntry.setting : undefined;
|
|
518
|
+
// The help block takes the focused row's place, and the block gets the whole
|
|
519
|
+
// free height while it is open: a key table clipped to three rows is no help.
|
|
520
|
+
const expanded = this.helpOpen || (row !== undefined && this.expanded.has(row.key));
|
|
521
|
+
const wide = isWide(w);
|
|
522
|
+
const blockWidth = wide ? sideWidth(w) : w;
|
|
523
|
+
const block = sideRows({
|
|
520
524
|
theme: this.theme,
|
|
521
|
-
width:
|
|
522
|
-
|
|
523
|
-
|
|
525
|
+
width: blockWidth,
|
|
526
|
+
section,
|
|
527
|
+
row,
|
|
528
|
+
groupLabel: groupLabelFor(model, row),
|
|
529
|
+
setting,
|
|
530
|
+
help: this.helpOpen,
|
|
524
531
|
expanded,
|
|
532
|
+
// Below the pane breakpoint the block shares the rows with a table, so it
|
|
533
|
+
// leaves out what the row above already says.
|
|
534
|
+
compact: !wide,
|
|
525
535
|
dirty: this.dirty,
|
|
536
|
+
paths: { scope, cwd: this.options.cwd, agentDir: this.options.agentDir },
|
|
537
|
+
json: this.settingsSection.jsonFor(setting),
|
|
526
538
|
});
|
|
527
539
|
|
|
540
|
+
if (section === "settings") this.labelColumn = settingLabelColumn(this.settingsSection.views(), w);
|
|
528
541
|
const { lines, noteShown } = panelFrame({
|
|
529
542
|
theme: this.theme,
|
|
530
543
|
viewportRows: this.options.viewportRows,
|
|
531
544
|
cursor: this.cursor,
|
|
532
545
|
width: w,
|
|
533
|
-
header,
|
|
534
|
-
|
|
546
|
+
header: headerLine(this.theme, w, "Bytefly", attentionFor(source)),
|
|
547
|
+
tabs: tabsLine(this.theme, w, section, sectionCounts(source), {
|
|
548
|
+
chips: chipsFor(source, section, this.filterEditing),
|
|
549
|
+
scope: scope === "global" ? "Global" : "Project",
|
|
550
|
+
}),
|
|
535
551
|
entries,
|
|
536
|
-
|
|
537
|
-
detail,
|
|
538
|
-
hint:
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
552
|
+
note: section === "settings" && scope === "project" ? projectNote(this.theme, w) : undefined,
|
|
553
|
+
detail: wide ? [] : clipRows(block, detailRows(expanded, this.options.viewportRows), w),
|
|
554
|
+
hint: hintRow({
|
|
555
|
+
theme: this.theme,
|
|
556
|
+
width: w,
|
|
557
|
+
section,
|
|
558
|
+
editing: this.settingsSection.isEditing || this.filterEditing !== undefined,
|
|
559
|
+
notice: this.notice,
|
|
560
|
+
state: {
|
|
561
|
+
expanded,
|
|
562
|
+
refreshing: this.refreshing,
|
|
563
|
+
projectScope: scope === "project",
|
|
564
|
+
dirty: this.dirty,
|
|
565
|
+
help: this.helpOpen,
|
|
566
|
+
},
|
|
567
|
+
}),
|
|
568
|
+
emptyLabel: emptyLabelFor(this.filter, section),
|
|
569
|
+
tallest: tallestBody(source),
|
|
570
|
+
side: wide ? (rows) => clipRows(block, rows, blockWidth) : undefined,
|
|
571
|
+
draw: (entry, index) => this.drawRow(entry, index, w, section, model),
|
|
544
572
|
});
|
|
545
573
|
this.noteShown = noteShown;
|
|
546
574
|
return lines;
|
|
547
575
|
}
|
|
576
|
+
|
|
577
|
+
/** One body line, in the section's own row grammar. */
|
|
578
|
+
private drawRow(
|
|
579
|
+
entry: PanelEntry,
|
|
580
|
+
index: number,
|
|
581
|
+
width: number,
|
|
582
|
+
section: PanelSection,
|
|
583
|
+
model: PanelModel | undefined,
|
|
584
|
+
): string | undefined {
|
|
585
|
+
const selected = index === this.cursor.selected;
|
|
586
|
+
if (entry.kind === "group") return groupLine(this.theme, width, entry);
|
|
587
|
+
if (entry.kind === "row") {
|
|
588
|
+
return resourceRow(this.theme, width, entry.row, selected, model === undefined ? 0 : verdictColumn(model.rows));
|
|
589
|
+
}
|
|
590
|
+
if (section !== "settings") return undefined;
|
|
591
|
+
const editing =
|
|
592
|
+
this.settingsSection.editingState?.id === entry.setting.id ? this.settingsSection.editingState : undefined;
|
|
593
|
+
return settingRow(this.theme, width, entry.setting, selected, editing, this.labelColumn);
|
|
594
|
+
}
|
|
548
595
|
}
|