@huseynsnmz/pi-suite 0.0.0-stage → 0.2.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/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +88 -2
- package/SECURITY.md +5 -0
- package/package.json +112 -4
- package/packages/board/CHANGELOG.md +340 -0
- package/packages/board/CONTRIBUTING.md +7 -0
- package/packages/board/LICENSE +21 -0
- package/packages/board/README.md +88 -0
- package/packages/board/SECURITY.md +19 -0
- package/packages/board/docs/how-it-works.md +31 -0
- package/packages/board/docs/reference.md +278 -0
- package/packages/board/package.json +81 -0
- package/packages/board/runner/job-runner-legacy.mjs +68 -0
- package/packages/board/runner/job-runner.mjs +834 -0
- package/packages/board/runner/pty-runner-legacy.mjs +50 -0
- package/packages/board/runner/pty-runner.mjs +2175 -0
- package/packages/board/runner/state-coordinator.mjs +673 -0
- package/packages/board/runner/state-runner.mjs +203 -0
- package/packages/board/runner/title-runner.mjs +80 -0
- package/packages/board/src/commands/agent-board.ts +394 -0
- package/packages/board/src/commands/attach-decision.mjs +66 -0
- package/packages/board/src/commands/attach-flow.ts +251 -0
- package/packages/board/src/commands/bg.ts +103 -0
- package/packages/board/src/commands/board-model.ts +21 -0
- package/packages/board/src/core/atomic.mjs +184 -0
- package/packages/board/src/core/attach-delete.mjs +24 -0
- package/packages/board/src/core/attach-frame-proof.mjs +870 -0
- package/packages/board/src/core/attach-frame-reporter.mjs +648 -0
- package/packages/board/src/core/attach-frame-stream.mjs +266 -0
- package/packages/board/src/core/attach-view-proof.mjs +709 -0
- package/packages/board/src/core/auto-state.mjs +377 -0
- package/packages/board/src/core/background-runs.mjs +273 -0
- package/packages/board/src/core/code-refs-store.mjs +315 -0
- package/packages/board/src/core/code-refs.mjs +913 -0
- package/packages/board/src/core/config.mjs +77 -0
- package/packages/board/src/core/control-protocol.mjs +482 -0
- package/packages/board/src/core/coordinator-client.mjs +633 -0
- package/packages/board/src/core/coordinator-journal.mjs +282 -0
- package/packages/board/src/core/coordinator-protocol.mjs +14 -0
- package/packages/board/src/core/cwd-stats.mjs +114 -0
- package/packages/board/src/core/dashboard-render.mjs +10 -0
- package/packages/board/src/core/derive.mjs +117 -0
- package/packages/board/src/core/diagnostics.mjs +133 -0
- package/packages/board/src/core/double-press.mjs +26 -0
- package/packages/board/src/core/editor-state-reporter.mjs +112 -0
- package/packages/board/src/core/events.mjs +286 -0
- package/packages/board/src/core/evidence.mjs +245 -0
- package/packages/board/src/core/follow-up-queue.mjs +211 -0
- package/packages/board/src/core/foreground-preview-cache.mjs +117 -0
- package/packages/board/src/core/foreground-projection-cache.mjs +99 -0
- package/packages/board/src/core/frame-witness-update.mjs +35 -0
- package/packages/board/src/core/heuristics.mjs +313 -0
- package/packages/board/src/core/hold-input.mjs +27 -0
- package/packages/board/src/core/host-coordination.mjs +300 -0
- package/packages/board/src/core/host-crash.mjs +79 -0
- package/packages/board/src/core/host-probe.mjs +207 -0
- package/packages/board/src/core/host-protocol.mjs +73 -0
- package/packages/board/src/core/ids.mjs +35 -0
- package/packages/board/src/core/ime-cursor-coalesce.mjs +193 -0
- package/packages/board/src/core/invocation.mjs +43 -0
- package/packages/board/src/core/launch-options.mjs +418 -0
- package/packages/board/src/core/launch.mjs +157 -0
- package/packages/board/src/core/load-config.mjs +4 -0
- package/packages/board/src/core/locks.mjs +530 -0
- package/packages/board/src/core/paths.mjs +183 -0
- package/packages/board/src/core/pid.mjs +75 -0
- package/packages/board/src/core/prewarm-schedule.mjs +41 -0
- package/packages/board/src/core/process-identity-probe.mjs +228 -0
- package/packages/board/src/core/prompt-transport.mjs +13 -0
- package/packages/board/src/core/pty-attach-jiggle-controller.mjs +356 -0
- package/packages/board/src/core/pty-attach-jiggle-retry.mjs +109 -0
- package/packages/board/src/core/pty-attach-reconnect.mjs +61 -0
- package/packages/board/src/core/pty-attach-render.mjs +229 -0
- package/packages/board/src/core/pty-links.mjs +81 -0
- package/packages/board/src/core/pty-scroll.mjs +522 -0
- package/packages/board/src/core/pty-support.mjs +327 -0
- package/packages/board/src/core/repo.mjs +103 -0
- package/packages/board/src/core/rows.mjs +414 -0
- package/packages/board/src/core/screen-log-gc.mjs +198 -0
- package/packages/board/src/core/screen-log.mjs +160 -0
- package/packages/board/src/core/session-view.mjs +174 -0
- package/packages/board/src/core/slash-commands.mjs +52 -0
- package/packages/board/src/core/state-commands.mjs +757 -0
- package/packages/board/src/core/status-consistency.mjs +98 -0
- package/packages/board/src/core/steering-prompts.mjs +34 -0
- package/packages/board/src/core/steering.mjs +133 -0
- package/packages/board/src/core/stop-latch.mjs +41 -0
- package/packages/board/src/core/store.mjs +523 -0
- package/packages/board/src/core/suspend.mjs +36 -0
- package/packages/board/src/core/terminal-attach-client.mjs +981 -0
- package/packages/board/src/core/terminal-attach-protocol.mjs +460 -0
- package/packages/board/src/core/terminal-model.mjs +222 -0
- package/packages/board/src/core/terminal-query-sequences.mjs +239 -0
- package/packages/board/src/core/terminal-reply-guard.mjs +222 -0
- package/packages/board/src/core/terminal-snapshot.mjs +440 -0
- package/packages/board/src/core/test-knobs.mjs +28 -0
- package/packages/board/src/core/title.mjs +43 -0
- package/packages/board/src/core/types.mjs +414 -0
- package/packages/board/src/core/warm-host-sweeper.mjs +168 -0
- package/packages/board/src/core/worktree.mjs +65 -0
- package/packages/board/src/index.ts +303 -0
- package/packages/board/src/runtime/service.mjs +3222 -0
- package/packages/board/src/ui/chrome.ts +20 -0
- package/packages/board/src/ui/dashboard-decisions.mjs +55 -0
- package/packages/board/src/ui/dashboard-evidence.mjs +85 -0
- package/packages/board/src/ui/dashboard.ts +2237 -0
- package/packages/board/src/ui/double-left.ts +30 -0
- package/packages/board/src/ui/external-editor.ts +25 -0
- package/packages/board/src/ui/focus.ts +32 -0
- package/packages/board/src/ui/pty-attach.ts +3184 -0
- package/packages/board/src/ui/waiting.ts +30 -0
- package/packages/commands/LICENSE +21 -0
- package/packages/commands/README.md +19 -0
- package/packages/commands/package.json +22 -0
- package/packages/commands/src/index.ts +43 -0
- package/packages/commands/src/security-review.ts +109 -0
- package/packages/frame/CHANGELOG.md +113 -0
- package/packages/frame/CONTRIBUTING.md +7 -0
- package/packages/frame/LICENSE +22 -0
- package/packages/frame/README.md +105 -0
- package/packages/frame/SECURITY.md +16 -0
- package/packages/frame/docs/how-it-works.md +53 -0
- package/packages/frame/docs/reference.md +78 -0
- package/packages/frame/package.json +72 -0
- package/packages/frame/src/data/git.ts +90 -0
- package/packages/frame/src/data/github.ts +140 -0
- package/packages/frame/src/data/gitlab.ts +63 -0
- package/packages/frame/src/data/mode.ts +49 -0
- package/packages/frame/src/data/past-prompts.ts +57 -0
- package/packages/frame/src/data/runner.ts +32 -0
- package/packages/frame/src/data/snapshot.ts +223 -0
- package/packages/frame/src/data/start-notes.ts +104 -0
- package/packages/frame/src/data/telemetry.ts +337 -0
- package/packages/frame/src/data/todos.ts +162 -0
- package/packages/frame/src/data/totals.ts +94 -0
- package/packages/frame/src/data/visits.ts +45 -0
- package/packages/frame/src/index.ts +696 -0
- package/packages/frame/src/kit/LICENSE +21 -0
- package/packages/frame/src/kit/app/attribution.ts +30 -0
- package/packages/frame/src/kit/app/builder.ts +186 -0
- package/packages/frame/src/kit/app/claim.ts +27 -0
- package/packages/frame/src/kit/app/feature.ts +91 -0
- package/packages/frame/src/kit/app/host.ts +214 -0
- package/packages/frame/src/kit/app/renderers.ts +62 -0
- package/packages/frame/src/kit/app/scope.ts +125 -0
- package/packages/frame/src/kit/board/model.ts +337 -0
- package/packages/frame/src/kit/contract.ts +79 -0
- package/packages/frame/src/kit/contracts/accounts.ts +33 -0
- package/packages/frame/src/kit/contracts/ask.ts +120 -0
- package/packages/frame/src/kit/contracts/board.ts +6 -0
- package/packages/frame/src/kit/contracts/by.ts +22 -0
- package/packages/frame/src/kit/contracts/calls.ts +46 -0
- package/packages/frame/src/kit/contracts/events.ts +89 -0
- package/packages/frame/src/kit/contracts/mode.ts +26 -0
- package/packages/frame/src/kit/contracts/names.ts +12 -0
- package/packages/frame/src/kit/contracts/peers.ts +77 -0
- package/packages/frame/src/kit/contracts/prompts.ts +8 -0
- package/packages/frame/src/kit/contracts/screen.ts +75 -0
- package/packages/frame/src/kit/contracts/skills.ts +81 -0
- package/packages/frame/src/kit/contracts/sources.ts +81 -0
- package/packages/frame/src/kit/contracts/views.ts +42 -0
- package/packages/frame/src/kit/control.ts +59 -0
- package/packages/frame/src/kit/dark-theme.ts +41 -0
- package/packages/frame/src/kit/decode.ts +210 -0
- package/packages/frame/src/kit/events/log.ts +313 -0
- package/packages/frame/src/kit/format.ts +25 -0
- package/packages/frame/src/kit/host.ts +4 -0
- package/packages/frame/src/kit/index.ts +25 -0
- package/packages/frame/src/kit/legacy-tasks.ts +39 -0
- package/packages/frame/src/kit/overlay-images.ts +36 -0
- package/packages/frame/src/kit/paths.ts +16 -0
- package/packages/frame/src/kit/screen/client.ts +74 -0
- package/packages/frame/src/kit/screen/feature.ts +64 -0
- package/packages/frame/src/kit/screen/model.ts +182 -0
- package/packages/frame/src/kit/screen/view.ts +838 -0
- package/packages/frame/src/kit/settings/files.ts +90 -0
- package/packages/frame/src/kit/settings/setting.ts +57 -0
- package/packages/frame/src/kit/settings/store.ts +192 -0
- package/packages/frame/src/kit/sources/file.ts +129 -0
- package/packages/frame/src/kit/sources/follow.ts +155 -0
- package/packages/frame/src/kit/testing.ts +260 -0
- package/packages/frame/src/kit/totals.ts +72 -0
- package/packages/frame/src/kit/ui/entry.ts +75 -0
- package/packages/frame/src/kit/ui/frame.ts +212 -0
- package/packages/frame/src/kit/ui/glyphs.ts +132 -0
- package/packages/frame/src/kit/ui/scroll.ts +14 -0
- package/packages/frame/src/render/diff.ts +157 -0
- package/packages/frame/src/render/fit.ts +86 -0
- package/packages/frame/src/render/format.ts +108 -0
- package/packages/frame/src/render/frame.ts +106 -0
- package/packages/frame/src/render/glyphs.ts +189 -0
- package/packages/frame/src/render/keys.ts +43 -0
- package/packages/frame/src/render/lead.ts +37 -0
- package/packages/frame/src/render/paint.ts +135 -0
- package/packages/frame/src/render/safe.ts +19 -0
- package/packages/frame/src/render/segments.ts +735 -0
- package/packages/frame/src/render/streaming.ts +223 -0
- package/packages/frame/src/render/timing.ts +106 -0
- package/packages/frame/src/render/tools.ts +167 -0
- package/packages/frame/src/settings.ts +105 -0
- package/packages/frame/src/ui/editor.ts +272 -0
- package/packages/frame/src/ui/effort.ts +36 -0
- package/packages/frame/src/ui/footer.ts +165 -0
- package/packages/frame/src/ui/header.ts +362 -0
- package/packages/frame/src/ui/history-search.ts +23 -0
- package/packages/frame/src/ui/interrupt.ts +21 -0
- package/packages/frame/src/ui/outputs.ts +170 -0
- package/packages/frame/src/ui/pi-entries.ts +298 -0
- package/packages/frame/src/ui/previews.ts +72 -0
- package/packages/frame/src/ui/queue.ts +75 -0
- package/packages/frame/src/ui/screen.ts +59 -0
- package/packages/frame/src/ui/timings.ts +48 -0
- package/packages/frame/src/ui/todos.ts +205 -0
- package/packages/frame/src/ui/tool-frame.ts +595 -0
- package/packages/frame/src/ui/working.ts +25 -0
- package/packages/frame/src/view/state.ts +97 -0
- package/packages/frame/src/view/transcript.ts +175 -0
- package/packages/frame/src/view/view.ts +162 -0
- package/packages/memory/CONTRIBUTING.md +7 -0
- package/packages/memory/LICENSE +21 -0
- package/packages/memory/README.md +44 -0
- package/packages/memory/SECURITY.md +20 -0
- package/packages/memory/package.json +55 -0
- package/packages/memory/skills/review-memory/SKILL.md +48 -0
- package/packages/memory/src/index.ts +163 -0
- package/packages/memory/src/prompt.ts +25 -0
- package/packages/memory/src/review.ts +150 -0
- package/packages/memory/src/secrets.ts +22 -0
- package/packages/memory/src/store.ts +173 -0
- package/packages/modes/CHANGELOG.md +104 -0
- package/packages/modes/CONTRIBUTING.md +7 -0
- package/packages/modes/LICENSE +21 -0
- package/packages/modes/README.md +174 -0
- package/packages/modes/SECURITY.md +17 -0
- package/packages/modes/package.json +55 -0
- package/packages/modes/skills/review-permissions/SKILL.md +54 -0
- package/packages/modes/src/bash.ts +218 -0
- package/packages/modes/src/deny-off.ts +62 -0
- package/packages/modes/src/git-safety.ts +72 -0
- package/packages/modes/src/index.ts +338 -0
- package/packages/modes/src/interview.ts +206 -0
- package/packages/modes/src/judge-gate.ts +113 -0
- package/packages/modes/src/modes.ts +121 -0
- package/packages/modes/src/review/apply.mjs +156 -0
- package/packages/modes/src/review/collect.mjs +208 -0
- package/packages/modes/src/review/tools.ts +71 -0
- package/packages/modes/src/sandbox.ts +37 -0
- package/packages/router/CHANGELOG.md +8 -0
- package/packages/router/LICENSE +21 -0
- package/packages/router/README.md +90 -0
- package/packages/router/package.json +51 -0
- package/packages/router/src/config.ts +65 -0
- package/packages/router/src/index.ts +172 -0
- package/packages/router/src/judge.ts +51 -0
- package/packages/router/src/route.ts +67 -0
- package/packages/web/CHANGELOG.md +36 -0
- package/packages/web/LICENSE +21 -0
- package/packages/web/README.md +87 -0
- package/packages/web/SECURITY.md +13 -0
- package/packages/web/package.json +59 -0
- package/packages/web/src/config.ts +109 -0
- package/packages/web/src/dnsfilter.ts +97 -0
- package/packages/web/src/extract.ts +65 -0
- package/packages/web/src/index.ts +217 -0
- package/packages/web/src/markdown.ts +122 -0
- package/packages/web/src/net.ts +109 -0
- package/packages/web/src/reader.ts +73 -0
- package/packages/web/src/sanitize.ts +50 -0
- package/packages/web/src/search.ts +76 -0
- package/packages/web/src/secrets.ts +22 -0
- package/packages/web/src/validate.ts +186 -0
- package/src/index.ts +156 -0
- package/src/settings.ts +79 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Every character the harness draws with, by name. Nerd Font marks live in look's icon sets.
|
|
2
|
+
|
|
3
|
+
/** Marks in running text. */
|
|
4
|
+
export const GLYPH = {
|
|
5
|
+
/** π, pi's own entries and the harness's name. */
|
|
6
|
+
pi: "\u03c0",
|
|
7
|
+
/** ✓ done, picked, passed. */
|
|
8
|
+
check: "\u2713",
|
|
9
|
+
/** ✗ failed. */
|
|
10
|
+
cross: "\u2717",
|
|
11
|
+
/** × close, remove. */
|
|
12
|
+
times: "\u00d7",
|
|
13
|
+
/** ↑ */
|
|
14
|
+
up: "\u2191",
|
|
15
|
+
/** ↓ */
|
|
16
|
+
down: "\u2193",
|
|
17
|
+
/** ← */
|
|
18
|
+
left: "\u2190",
|
|
19
|
+
/** → to whom, from what to what. */
|
|
20
|
+
right: "\u2192",
|
|
21
|
+
/** ▰ ▱ how well an agent fits a request, in the peers tool's bids. */
|
|
22
|
+
barFull: "\u25b0",
|
|
23
|
+
barEmpty: "\u25b1",
|
|
24
|
+
/** ↵ enter. */
|
|
25
|
+
enter: "\u21b5",
|
|
26
|
+
/** ↻ again. */
|
|
27
|
+
again: "\u21bb",
|
|
28
|
+
/** ❯ the selected row. */
|
|
29
|
+
pointer: "\u276f",
|
|
30
|
+
/** › a note on an option, a continuation. */
|
|
31
|
+
chevron: "\u203a",
|
|
32
|
+
/** ▸ a small pointer. */
|
|
33
|
+
triangle: "\u25b8",
|
|
34
|
+
/** ◆ an agent, a task in progress. */
|
|
35
|
+
diamond: "\u25c6",
|
|
36
|
+
/** ◇ an empty diamond: a task not started, a quiet mark. */
|
|
37
|
+
diamondEmpty: "\u25c7",
|
|
38
|
+
/** ◈ the judge. */
|
|
39
|
+
judge: "\u25c8",
|
|
40
|
+
/** ● on. */
|
|
41
|
+
dot: "\u25cf",
|
|
42
|
+
/** ○ off, not answered. */
|
|
43
|
+
ring: "\u25cb",
|
|
44
|
+
/** ◐ half: hearing a room without taking part in it. */
|
|
45
|
+
half: "\u25d0",
|
|
46
|
+
/** ◌ hidden, as tool output pruned from the context. */
|
|
47
|
+
ringDotted: "\u25cc",
|
|
48
|
+
/** ✎ words you typed. */
|
|
49
|
+
pencil: "\u270e",
|
|
50
|
+
/** ⚑ flagged. */
|
|
51
|
+
flag: "\u2691",
|
|
52
|
+
/** ⏵ running. */
|
|
53
|
+
play: "\u23f5",
|
|
54
|
+
/** ⌕ search. */
|
|
55
|
+
search: "\u2315",
|
|
56
|
+
/** ▍ where words arrive. */
|
|
57
|
+
cursor: "\u258d",
|
|
58
|
+
/** ▌ the bar on a selected row. */
|
|
59
|
+
bar: "\u258c",
|
|
60
|
+
/** ⋮ lines hidden above. */
|
|
61
|
+
ellipsisUp: "\u22ee",
|
|
62
|
+
/** ⋯ a gap between parts, as between two edits. */
|
|
63
|
+
ellipsisAcross: "\u22ef",
|
|
64
|
+
} as const;
|
|
65
|
+
|
|
66
|
+
/** Frames of the marks that move while something lasts, in order. */
|
|
67
|
+
export const SPIN = {
|
|
68
|
+
/** ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ a dot going round in one cell: someone writing. */
|
|
69
|
+
braille: [
|
|
70
|
+
"\u280b",
|
|
71
|
+
"\u2819",
|
|
72
|
+
"\u2839",
|
|
73
|
+
"\u2838",
|
|
74
|
+
"\u283c",
|
|
75
|
+
"\u2834",
|
|
76
|
+
"\u2826",
|
|
77
|
+
"\u2827",
|
|
78
|
+
"\u2807",
|
|
79
|
+
"\u280f",
|
|
80
|
+
],
|
|
81
|
+
/** ◴ ◷ ◶ ◵ a quarter of a circle going round: a call running. */
|
|
82
|
+
quadrants: ["\u25f4", "\u25f7", "\u25f6", "\u25f5"],
|
|
83
|
+
/** ⧖ ⧗ an hourglass and the same one flowing: a call waiting on you. */
|
|
84
|
+
hourglass: ["\u29d6", "\u29d7"],
|
|
85
|
+
/** | / - \ the same, in ASCII. */
|
|
86
|
+
ascii: ["|", "/", "-", "\\"],
|
|
87
|
+
/** ✎ ✏ ✐ ✏ a pencil that tilts as it writes, in one cell: the model writing a call. */
|
|
88
|
+
pencil: ["\u270e", "\u270f", "\u2710", "\u270f"],
|
|
89
|
+
} as const;
|
|
90
|
+
|
|
91
|
+
/** Three dots rather than `…`, which is one column in every font. */
|
|
92
|
+
export const CUT = "...";
|
|
93
|
+
|
|
94
|
+
/** The lines frames, rules and scroll bars are drawn with. */
|
|
95
|
+
export const LINE = {
|
|
96
|
+
/** ─ */
|
|
97
|
+
h: "\u2500",
|
|
98
|
+
/** │ */
|
|
99
|
+
v: "\u2502",
|
|
100
|
+
/** ┃ the thumb of a scroll bar, and the sides of what is chosen. */
|
|
101
|
+
vHeavy: "\u2503",
|
|
102
|
+
/** ━ */
|
|
103
|
+
hHeavy: "\u2501",
|
|
104
|
+
/** ┏ */
|
|
105
|
+
topLeftHeavy: "\u250f",
|
|
106
|
+
/** ┓ */
|
|
107
|
+
topRightHeavy: "\u2513",
|
|
108
|
+
/** ┗ */
|
|
109
|
+
bottomLeftHeavy: "\u2517",
|
|
110
|
+
/** ┛ */
|
|
111
|
+
bottomRightHeavy: "\u251b",
|
|
112
|
+
/** ┄ a frame around what has not happened yet. */
|
|
113
|
+
hDashed: "\u2504",
|
|
114
|
+
/** ┆ */
|
|
115
|
+
vDashed: "\u2506",
|
|
116
|
+
/** ╭ */
|
|
117
|
+
topLeft: "\u256d",
|
|
118
|
+
/** ╮ */
|
|
119
|
+
topRight: "\u256e",
|
|
120
|
+
/** ╰ */
|
|
121
|
+
bottomLeft: "\u2570",
|
|
122
|
+
/** ╯ */
|
|
123
|
+
bottomRight: "\u256f",
|
|
124
|
+
/** ├ a rule that meets the left side. */
|
|
125
|
+
teeLeft: "\u251c",
|
|
126
|
+
/** ┤ a rule that meets the right side. */
|
|
127
|
+
teeRight: "\u2524",
|
|
128
|
+
/** ┬ a column that meets the top edge. */
|
|
129
|
+
teeTop: "\u252c",
|
|
130
|
+
/** ┴ a column that meets the bottom edge. */
|
|
131
|
+
teeBottom: "\u2534",
|
|
132
|
+
} as const;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ThumbRange {
|
|
2
|
+
/** From the top. */
|
|
3
|
+
start: number;
|
|
4
|
+
/** At least one. */
|
|
5
|
+
size: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Undefined when everything fits. */
|
|
9
|
+
export function thumbRange(total: number, height: number, scroll: number): ThumbRange | undefined {
|
|
10
|
+
if (total <= height) return undefined;
|
|
11
|
+
const size = Math.max(1, Math.round((height * height) / total));
|
|
12
|
+
const start = Math.round((scroll / (total - height)) * (height - size));
|
|
13
|
+
return { start, size };
|
|
14
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// An edit's diff inside the frame, without pi's colored box behind it: the line numbers in a gutter,
|
|
2
|
+
// the sign and the text in the diff colors, and only the changed words on a tinted background.
|
|
3
|
+
import { GLYPH, isObject } from "../kit/index.ts";
|
|
4
|
+
import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { type Component, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
6
|
+
|
|
7
|
+
import { printable } from "./streaming.ts";
|
|
8
|
+
|
|
9
|
+
interface DiffLine {
|
|
10
|
+
sign: "-" | "+" | " ";
|
|
11
|
+
number: string;
|
|
12
|
+
text: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const COLORS: Record<DiffLine["sign"], ThemeColor> = {
|
|
16
|
+
"-": "toolDiffRemoved",
|
|
17
|
+
"+": "toolDiffAdded",
|
|
18
|
+
" ": "toolDiffContext",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Pi's diff text, one line per row, with the number gutter and the wrapped text hanging under itself. */
|
|
22
|
+
export function drawDiff(diff: string, theme: Theme, width: number): string[] {
|
|
23
|
+
const lines = diff.split("\n").map(parse);
|
|
24
|
+
const gutter = Math.max(...lines.map((line) => line?.number.length ?? 0), 1);
|
|
25
|
+
const texts = changedWords(lines, theme);
|
|
26
|
+
// The number, a space, the sign and a space; wrapped text hangs under its first character.
|
|
27
|
+
const hang = " ".repeat(gutter + 3);
|
|
28
|
+
return lines.flatMap((line, index) => {
|
|
29
|
+
if (line === undefined) return [];
|
|
30
|
+
if (line.number === "" && line.text === "...")
|
|
31
|
+
return [`${hang}${theme.fg("dim", GLYPH.ellipsisAcross)}`];
|
|
32
|
+
const lead = `${theme.fg("dim", line.number.padStart(gutter))} ${theme.fg(COLORS[line.sign], line.sign)} `;
|
|
33
|
+
const text = texts[index] ?? theme.fg(COLORS[line.sign], line.text);
|
|
34
|
+
return wrapTextWithAnsi(text, Math.max(1, width - hang.length)).map((part, at) =>
|
|
35
|
+
at === 0 ? `${lead}${part}` : `${hang}${part}`,
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Pi's edit body while it holds the file's diff, drawn here; before that, pi's own drawing.
|
|
42
|
+
* Pi keeps the diff on the component it returns, as `preview`.
|
|
43
|
+
*/
|
|
44
|
+
export function editBody(own: Component, header: string, theme: Theme): Component {
|
|
45
|
+
// The screen redraws on every keystroke and every piece the model writes; the diff is drawn once.
|
|
46
|
+
let drawn: { error?: string; diff?: string; width: number; lines: string[] } | undefined;
|
|
47
|
+
return {
|
|
48
|
+
render(width) {
|
|
49
|
+
const preview = isObject(own) && "preview" in own ? own.preview : undefined;
|
|
50
|
+
if (!isObject(preview)) return own.render(width);
|
|
51
|
+
const error = typeof preview.error === "string" ? preview.error : undefined;
|
|
52
|
+
const diff = typeof preview.diff === "string" ? preview.diff : undefined;
|
|
53
|
+
if (error === undefined && diff === undefined) return own.render(width);
|
|
54
|
+
if (drawn?.width !== width || drawn.error !== error || drawn.diff !== diff) {
|
|
55
|
+
const body =
|
|
56
|
+
error === undefined
|
|
57
|
+
? drawDiff(diff ?? "", theme, width)
|
|
58
|
+
: wrapTextWithAnsi(theme.fg("error", error), width);
|
|
59
|
+
drawn = { error, diff, width, lines: [header, "", ...body] };
|
|
60
|
+
}
|
|
61
|
+
return drawn.lines;
|
|
62
|
+
},
|
|
63
|
+
invalidate() {
|
|
64
|
+
drawn = undefined;
|
|
65
|
+
own.invalidate();
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parse(line: string): DiffLine | undefined {
|
|
71
|
+
if (line === "") return undefined;
|
|
72
|
+
const match = /^([+\- ])( *\d*) (.*)$/.exec(line);
|
|
73
|
+
if (match === null) return { sign: " ", number: "", text: printable(line) };
|
|
74
|
+
const sign = match[1] as DiffLine["sign"];
|
|
75
|
+
return { sign, number: (match[2] ?? "").trim(), text: printable(match[3] ?? "") };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One line taken out and one put in is a changed line: its changed words get the tint. */
|
|
79
|
+
function changedWords(
|
|
80
|
+
lines: readonly (DiffLine | undefined)[],
|
|
81
|
+
theme: Theme,
|
|
82
|
+
): (string | undefined)[] {
|
|
83
|
+
const texts: (string | undefined)[] = [];
|
|
84
|
+
for (let index = 0; index + 1 < lines.length; index++) {
|
|
85
|
+
const out = lines[index];
|
|
86
|
+
const into = lines[index + 1];
|
|
87
|
+
if (out?.sign !== "-" || into?.sign !== "+") continue;
|
|
88
|
+
if (lines[index - 1]?.sign === "-" || lines[index + 2]?.sign === "+") continue;
|
|
89
|
+
let removed = "";
|
|
90
|
+
let added = "";
|
|
91
|
+
for (const part of diffWords(out.text, into.text)) {
|
|
92
|
+
if (part.removed) removed += theme.bg("toolErrorBg", part.value);
|
|
93
|
+
else if (part.added) added += theme.bg("toolSuccessBg", part.value);
|
|
94
|
+
else {
|
|
95
|
+
removed += part.value;
|
|
96
|
+
added += part.value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
texts[index] = theme.fg("toolDiffRemoved", removed);
|
|
100
|
+
texts[index + 1] = theme.fg("toolDiffAdded", added);
|
|
101
|
+
}
|
|
102
|
+
return texts;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const TOKENS = /\s+|[\p{L}\p{N}_]+|./gsu;
|
|
106
|
+
/** A pair of very long lines costs this much to compare, and a minified file isn't worth it. */
|
|
107
|
+
const MOST_CELLS = 4_000_000;
|
|
108
|
+
|
|
109
|
+
export interface WordChange {
|
|
110
|
+
value: string;
|
|
111
|
+
added?: boolean;
|
|
112
|
+
removed?: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** What changed between two lines, word by word: the longest run of words they share stays. */
|
|
116
|
+
export function diffWords(before: string, after: string): WordChange[] {
|
|
117
|
+
const a = before.match(TOKENS) ?? [];
|
|
118
|
+
const b = after.match(TOKENS) ?? [];
|
|
119
|
+
if ((a.length + 1) * (b.length + 1) > MOST_CELLS) {
|
|
120
|
+
return [
|
|
121
|
+
...(before === "" ? [] : [{ value: before, removed: true }]),
|
|
122
|
+
...(after === "" ? [] : [{ value: after, added: true }]),
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
const width = b.length + 1;
|
|
126
|
+
const shared = new Uint32Array((a.length + 1) * width);
|
|
127
|
+
for (let i = a.length - 1; i >= 0; i--) {
|
|
128
|
+
for (let j = b.length - 1; j >= 0; j--) {
|
|
129
|
+
shared[i * width + j] =
|
|
130
|
+
a[i] === b[j]
|
|
131
|
+
? (shared[(i + 1) * width + j + 1] ?? 0) + 1
|
|
132
|
+
: Math.max(shared[(i + 1) * width + j] ?? 0, shared[i * width + j + 1] ?? 0);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const parts: WordChange[] = [];
|
|
136
|
+
const push = (value: string, change: { added?: boolean; removed?: boolean }) => {
|
|
137
|
+
const last = parts.at(-1);
|
|
138
|
+
if (last !== undefined && last.added === change.added && last.removed === change.removed) last.value += value;
|
|
139
|
+
else parts.push({ value, ...change });
|
|
140
|
+
};
|
|
141
|
+
let i = 0;
|
|
142
|
+
let j = 0;
|
|
143
|
+
while (i < a.length || j < b.length) {
|
|
144
|
+
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
145
|
+
push(a[i] as string, {});
|
|
146
|
+
i++;
|
|
147
|
+
j++;
|
|
148
|
+
} else if (j >= b.length || (i < a.length && (shared[(i + 1) * width + j] ?? 0) >= (shared[i * width + j + 1] ?? 0))) {
|
|
149
|
+
push(a[i] as string, { removed: true });
|
|
150
|
+
i++;
|
|
151
|
+
} else {
|
|
152
|
+
push(b[j] as string, { added: true });
|
|
153
|
+
j++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return parts;
|
|
157
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { CUT } from "../kit/index.ts";
|
|
2
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
export interface Piece {
|
|
5
|
+
text: string;
|
|
6
|
+
compact?: string | undefined;
|
|
7
|
+
priority: number;
|
|
8
|
+
/** When it is alone on the line. */
|
|
9
|
+
shrink?: (width: number) => string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FitOptions {
|
|
13
|
+
separator: string;
|
|
14
|
+
regionOverhead?: number;
|
|
15
|
+
ellipsis?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Slot {
|
|
19
|
+
region: number;
|
|
20
|
+
order: number;
|
|
21
|
+
piece: Piece;
|
|
22
|
+
text: string;
|
|
23
|
+
dropped: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function fitRegions(
|
|
27
|
+
regions: readonly (readonly Piece[])[],
|
|
28
|
+
width: number,
|
|
29
|
+
options: FitOptions,
|
|
30
|
+
): string[] {
|
|
31
|
+
const slots: Slot[] = regions.flatMap((pieces, region) =>
|
|
32
|
+
pieces.map((piece, order) => ({ region, order, piece, text: piece.text, dropped: false })),
|
|
33
|
+
);
|
|
34
|
+
const byPriority = slots.toSorted((a, b) => a.piece.priority - b.piece.priority);
|
|
35
|
+
const measure = (): number => measureRegions(regions.length, slots, options);
|
|
36
|
+
|
|
37
|
+
for (const slot of byPriority) {
|
|
38
|
+
if (measure() <= width) break;
|
|
39
|
+
if (slot.piece.compact !== undefined) slot.text = slot.piece.compact;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const slot of byPriority) {
|
|
43
|
+
if (measure() <= width) break;
|
|
44
|
+
if (slots.filter((candidate) => !candidate.dropped).length <= 1) break;
|
|
45
|
+
slot.dropped = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const joined = join(regions.length, slots, options.separator);
|
|
49
|
+
if (measure() <= width) return joined;
|
|
50
|
+
|
|
51
|
+
const kept = slots.filter((slot) => !slot.dropped);
|
|
52
|
+
const alone = regions.length === 1 ? kept[0]?.piece.shrink : undefined;
|
|
53
|
+
if (kept.length === 1 && alone !== undefined) return [alone(width)];
|
|
54
|
+
|
|
55
|
+
const overhead = options.regionOverhead ?? 0;
|
|
56
|
+
return joined.map((text) =>
|
|
57
|
+
text === ""
|
|
58
|
+
? ""
|
|
59
|
+
: truncateToWidth(text, Math.max(0, width - overhead), options.ellipsis ?? CUT),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function fitLine(pieces: readonly Piece[], width: number, options: FitOptions): string {
|
|
64
|
+
return fitRegions([pieces], width, options)[0] ?? "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function join(count: number, slots: readonly Slot[], separator: string): string[] {
|
|
68
|
+
const regions: string[][] = Array.from({ length: count }, () => []);
|
|
69
|
+
for (const slot of slots.toSorted((a, b) => a.order - b.order)) {
|
|
70
|
+
if (!slot.dropped) regions[slot.region]?.push(slot.text);
|
|
71
|
+
}
|
|
72
|
+
return regions.map((texts) => texts.join(separator));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function measureRegions(count: number, slots: readonly Slot[], options: FitOptions): number {
|
|
76
|
+
const separatorWidth = visibleWidth(options.separator);
|
|
77
|
+
let total = 0;
|
|
78
|
+
for (let region = 0; region < count; region++) {
|
|
79
|
+
const kept = slots.filter((slot) => slot.region === region && !slot.dropped);
|
|
80
|
+
if (kept.length === 0) continue;
|
|
81
|
+
total += options.regionOverhead ?? 0;
|
|
82
|
+
total += kept.reduce((sum, slot) => sum + visibleWidth(slot.text), 0);
|
|
83
|
+
total += separatorWidth * (kept.length - 1);
|
|
84
|
+
}
|
|
85
|
+
return total;
|
|
86
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { sep } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { formatCost, formatTokens, tildify } from "../kit/index.ts";
|
|
4
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
5
|
+
|
|
6
|
+
export const CELL = {
|
|
7
|
+
count: 4,
|
|
8
|
+
rate: 4,
|
|
9
|
+
latency: 5,
|
|
10
|
+
clock: 4,
|
|
11
|
+
percent: 4,
|
|
12
|
+
money: 6,
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export function cell(text: string, width: number): string {
|
|
16
|
+
return " ".repeat(Math.max(0, width - visibleWidth(text))) + text;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const UNKNOWN = "–";
|
|
20
|
+
|
|
21
|
+
export const count = formatTokens;
|
|
22
|
+
|
|
23
|
+
export function rate(perSecond: number): string {
|
|
24
|
+
if (perSecond < 10) return trim(perSecond.toFixed(1));
|
|
25
|
+
return count(perSecond);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function duration(milliseconds: number): string {
|
|
29
|
+
const total = Math.max(0, Math.floor(milliseconds / 1000));
|
|
30
|
+
const secs = total % 60;
|
|
31
|
+
const minutes = Math.floor(total / 60) % 60;
|
|
32
|
+
const hours = Math.floor(total / 3600);
|
|
33
|
+
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
34
|
+
if (minutes > 0) return `${minutes}m ${secs}s`;
|
|
35
|
+
return `${secs}s`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function stopwatch(milliseconds: number): string {
|
|
39
|
+
const total = Math.max(0, Math.floor(milliseconds / 1000));
|
|
40
|
+
const secs = String(total % 60).padStart(2, "0");
|
|
41
|
+
const minutes = Math.floor(total / 60) % 60;
|
|
42
|
+
const hours = Math.floor(total / 3600);
|
|
43
|
+
return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${secs}` : `${minutes}:${secs}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function latency(milliseconds: number): string {
|
|
47
|
+
if (milliseconds < 1000) return `${Math.round(milliseconds)}ms`;
|
|
48
|
+
if (milliseconds < 10_000) return `${(milliseconds / 1000).toFixed(1)}s`;
|
|
49
|
+
return duration(milliseconds);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const money = formatCost;
|
|
53
|
+
|
|
54
|
+
export function percent(value: number): string {
|
|
55
|
+
return value >= 10 || value === 0 ? `${Math.round(value)}%` : `${value.toFixed(1)}%`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function shortenPath(cwd: string, home: string | undefined, maxWidth: number): string {
|
|
59
|
+
const full = home === undefined ? cwd : tildify(cwd, home);
|
|
60
|
+
if (maxWidth <= 0 || visibleWidth(full) <= maxWidth) return full;
|
|
61
|
+
|
|
62
|
+
const parts = full.split(sep);
|
|
63
|
+
let kept = "";
|
|
64
|
+
for (let index = parts.length - 1; index > 0; index--) {
|
|
65
|
+
const candidate = `${sep}${parts[index] ?? ""}${kept}`;
|
|
66
|
+
if (visibleWidth(`...${candidate}`) > maxWidth) break;
|
|
67
|
+
kept = candidate;
|
|
68
|
+
}
|
|
69
|
+
if (kept === "") return truncateTail(parts.at(-1) ?? full, maxWidth);
|
|
70
|
+
return `...${kept}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function basename(cwd: string): string {
|
|
74
|
+
return cwd.split(/[\\/]/).findLast(Boolean) ?? cwd;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function gauge(value: number, cells: number): { filled: number; empty: number } {
|
|
78
|
+
const filled = Math.max(0, Math.min(cells, Math.round((value / 100) * cells)));
|
|
79
|
+
return { filled, empty: cells - filled };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function truncateTail(text: string, maxWidth: number): string {
|
|
83
|
+
if (maxWidth <= 0) return "";
|
|
84
|
+
if (visibleWidth(text) <= maxWidth) return text;
|
|
85
|
+
const chars = [...text];
|
|
86
|
+
let width = 3;
|
|
87
|
+
let start = chars.length;
|
|
88
|
+
while (start > 0) {
|
|
89
|
+
const next = visibleWidth(chars[start - 1] ?? "");
|
|
90
|
+
if (width + next > maxWidth) break;
|
|
91
|
+
width += next;
|
|
92
|
+
start--;
|
|
93
|
+
}
|
|
94
|
+
return `...${chars.slice(start).join("")}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function fitWidth(text: string, width: number, ellipsis = "..."): string {
|
|
98
|
+
return truncateToWidth(text, Math.max(0, width), ellipsis);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A tab is as wide as the terminal says, and these lines have a width. */
|
|
102
|
+
export function tabs(text: string): string {
|
|
103
|
+
return text.replace(/\t/g, " ");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function trim(fixed: string): string {
|
|
107
|
+
return fixed.endsWith(".0") ? fixed.slice(0, -2) : fixed;
|
|
108
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
|
|
3
|
+
import { type FitOptions, fitRegions, type Piece } from "./fit.ts";
|
|
4
|
+
|
|
5
|
+
export type FrameStyle = "rounded" | "square" | "heavy" | "line";
|
|
6
|
+
|
|
7
|
+
export interface BoxChars {
|
|
8
|
+
topLeft: string;
|
|
9
|
+
topRight: string;
|
|
10
|
+
bottomLeft: string;
|
|
11
|
+
bottomRight: string;
|
|
12
|
+
horizontal: string;
|
|
13
|
+
vertical: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const BOXES: Record<FrameStyle, BoxChars> = {
|
|
17
|
+
rounded: {
|
|
18
|
+
topLeft: "╭",
|
|
19
|
+
topRight: "╮",
|
|
20
|
+
bottomLeft: "╰",
|
|
21
|
+
bottomRight: "╯",
|
|
22
|
+
horizontal: "─",
|
|
23
|
+
vertical: "│",
|
|
24
|
+
},
|
|
25
|
+
square: {
|
|
26
|
+
topLeft: "┌",
|
|
27
|
+
topRight: "┐",
|
|
28
|
+
bottomLeft: "└",
|
|
29
|
+
bottomRight: "┘",
|
|
30
|
+
horizontal: "─",
|
|
31
|
+
vertical: "│",
|
|
32
|
+
},
|
|
33
|
+
heavy: {
|
|
34
|
+
topLeft: "┏",
|
|
35
|
+
topRight: "┓",
|
|
36
|
+
bottomLeft: "┗",
|
|
37
|
+
bottomRight: "┛",
|
|
38
|
+
horizontal: "━",
|
|
39
|
+
vertical: "┃",
|
|
40
|
+
},
|
|
41
|
+
line: {
|
|
42
|
+
topLeft: "─",
|
|
43
|
+
topRight: "─",
|
|
44
|
+
bottomLeft: "─",
|
|
45
|
+
bottomRight: "─",
|
|
46
|
+
horizontal: "─",
|
|
47
|
+
vertical: "",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export interface BorderOptions extends Pick<FitOptions, "separator" | "ellipsis"> {
|
|
52
|
+
box: BoxChars;
|
|
53
|
+
edge: "top" | "bottom";
|
|
54
|
+
paint: (text: string) => string;
|
|
55
|
+
lead?: ((budget: number) => string | undefined) | undefined;
|
|
56
|
+
leadMin?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const FIXED = 5;
|
|
60
|
+
const PADDING = 2;
|
|
61
|
+
|
|
62
|
+
export function border(
|
|
63
|
+
width: number,
|
|
64
|
+
left: readonly Piece[],
|
|
65
|
+
right: readonly Piece[],
|
|
66
|
+
options: BorderOptions,
|
|
67
|
+
): string {
|
|
68
|
+
const { box, paint } = options;
|
|
69
|
+
const [start, end] =
|
|
70
|
+
options.edge === "top" ? [box.topLeft, box.topRight] : [box.bottomLeft, box.bottomRight];
|
|
71
|
+
if (width < FIXED) return paint(box.horizontal.repeat(Math.max(0, width)));
|
|
72
|
+
|
|
73
|
+
const separatorWidth = visibleWidth(options.separator);
|
|
74
|
+
const reserve =
|
|
75
|
+
options.lead === undefined ? 0 : (options.leadMin ?? 1) + separatorWidth + PADDING;
|
|
76
|
+
const budget = width - FIXED - reserve;
|
|
77
|
+
|
|
78
|
+
const [fittedLeft = "", fittedRight = ""] =
|
|
79
|
+
budget > 0
|
|
80
|
+
? fitRegions([left, right], budget, {
|
|
81
|
+
separator: options.separator,
|
|
82
|
+
regionOverhead: PADDING,
|
|
83
|
+
ellipsis: options.ellipsis,
|
|
84
|
+
})
|
|
85
|
+
: ["", ""];
|
|
86
|
+
|
|
87
|
+
const rightCell = fittedRight === "" ? "" : ` ${fittedRight} `;
|
|
88
|
+
let leftText = fittedLeft;
|
|
89
|
+
if (options.lead !== undefined) {
|
|
90
|
+
const used =
|
|
91
|
+
visibleWidth(rightCell) + (fittedLeft === "" ? 0 : visibleWidth(fittedLeft) + separatorWidth);
|
|
92
|
+
const lead = options.lead(Math.max(1, width - FIXED - PADDING - used));
|
|
93
|
+
if (lead !== undefined && lead !== "")
|
|
94
|
+
leftText = fittedLeft === "" ? lead : `${fittedLeft}${options.separator}${lead}`;
|
|
95
|
+
}
|
|
96
|
+
const leftCell = leftText === "" ? "" : ` ${leftText} `;
|
|
97
|
+
const fill = Math.max(1, width - 4 - visibleWidth(leftCell) - visibleWidth(rightCell));
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
paint(`${start}${box.horizontal}`) +
|
|
101
|
+
leftCell +
|
|
102
|
+
paint(box.horizontal.repeat(fill)) +
|
|
103
|
+
rightCell +
|
|
104
|
+
paint(`${box.horizontal}${end}`)
|
|
105
|
+
);
|
|
106
|
+
}
|