@agimon-ai/doompi-ui 0.0.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/_virtual/_rolldown/runtime.mjs +1 -0
- package/dist/components/configOverlay.cjs +0 -0
- package/dist/components/configOverlay.cjs.map +1 -0
- package/dist/components/configOverlay.d.cts +0 -0
- package/dist/components/configOverlay.d.cts.map +1 -0
- package/dist/components/configOverlay.d.mts +0 -0
- package/dist/components/configOverlay.d.mts.map +1 -0
- package/dist/components/configOverlay.mjs +0 -0
- package/dist/components/configOverlay.mjs.map +1 -0
- package/dist/components/doomEditor.cjs +2 -0
- package/dist/components/doomEditor.cjs.map +1 -0
- package/dist/components/doomEditor.d.cts +59 -0
- package/dist/components/doomEditor.d.cts.map +1 -0
- package/dist/components/doomEditor.d.mts +59 -0
- package/dist/components/doomEditor.d.mts.map +1 -0
- package/dist/components/doomEditor.mjs +2 -0
- package/dist/components/doomEditor.mjs.map +1 -0
- package/dist/components/doomFooter.cjs +2 -0
- package/dist/components/doomFooter.cjs.map +1 -0
- package/dist/components/doomFooter.d.cts +32 -0
- package/dist/components/doomFooter.d.cts.map +1 -0
- package/dist/components/doomFooter.d.mts +32 -0
- package/dist/components/doomFooter.d.mts.map +1 -0
- package/dist/components/doomFooter.mjs +2 -0
- package/dist/components/doomFooter.mjs.map +1 -0
- package/dist/components/doomHeader.cjs +2 -0
- package/dist/components/doomHeader.cjs.map +1 -0
- package/dist/components/doomHeader.d.cts +34 -0
- package/dist/components/doomHeader.d.cts.map +1 -0
- package/dist/components/doomHeader.d.mts +34 -0
- package/dist/components/doomHeader.d.mts.map +1 -0
- package/dist/components/doomHeader.mjs +2 -0
- package/dist/components/doomHeader.mjs.map +1 -0
- package/dist/components/doomOverlay.cjs +2 -0
- package/dist/components/doomOverlay.cjs.map +1 -0
- package/dist/components/doomOverlay.d.cts +79 -0
- package/dist/components/doomOverlay.d.cts.map +1 -0
- package/dist/components/doomOverlay.d.mts +79 -0
- package/dist/components/doomOverlay.d.mts.map +1 -0
- package/dist/components/doomOverlay.mjs +2 -0
- package/dist/components/doomOverlay.mjs.map +1 -0
- package/dist/components/leaderHints.cjs +2 -0
- package/dist/components/leaderHints.cjs.map +1 -0
- package/dist/components/leaderHints.d.cts +37 -0
- package/dist/components/leaderHints.d.cts.map +1 -0
- package/dist/components/leaderHints.d.mts +37 -0
- package/dist/components/leaderHints.d.mts.map +1 -0
- package/dist/components/leaderHints.mjs +2 -0
- package/dist/components/leaderHints.mjs.map +1 -0
- package/dist/components/toolsOverlay.cjs +2 -0
- package/dist/components/toolsOverlay.cjs.map +1 -0
- package/dist/components/toolsOverlay.d.cts +29 -0
- package/dist/components/toolsOverlay.d.cts.map +1 -0
- package/dist/components/toolsOverlay.d.mts +29 -0
- package/dist/components/toolsOverlay.d.mts.map +1 -0
- package/dist/components/toolsOverlay.mjs +2 -0
- package/dist/components/toolsOverlay.mjs.map +1 -0
- package/dist/config.cjs +2 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +5 -0
- package/dist/config.d.cts.map +1 -0
- package/dist/config.d.mts +5 -0
- package/dist/config.d.mts.map +1 -0
- package/dist/config.mjs +2 -0
- package/dist/config.mjs.map +1 -0
- package/dist/extension.cjs +2 -0
- package/dist/extension.cjs.map +1 -0
- package/dist/extension.d.cts +7 -0
- package/dist/extension.d.cts.map +1 -0
- package/dist/extension.d.mts +7 -0
- package/dist/extension.d.mts.map +1 -0
- package/dist/extension.mjs +2 -0
- package/dist/extension.mjs.map +1 -0
- package/dist/extensionName.cjs +2 -0
- package/dist/extensionName.cjs.map +1 -0
- package/dist/extensionName.d.cts +5 -0
- package/dist/extensionName.d.cts.map +1 -0
- package/dist/extensionName.d.mts +5 -0
- package/dist/extensionName.d.mts.map +1 -0
- package/dist/extensionName.mjs +2 -0
- package/dist/extensionName.mjs.map +1 -0
- package/dist/extensions/pi.cjs +2 -0
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.cts +2 -0
- package/dist/extensions/pi.d.mts +2 -0
- package/dist/extensions/pi.mjs +2 -0
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/footer.cjs +2 -0
- package/dist/footer.cjs.map +1 -0
- package/dist/footer.d.cts +5 -0
- package/dist/footer.d.cts.map +1 -0
- package/dist/footer.d.mts +5 -0
- package/dist/footer.d.mts.map +1 -0
- package/dist/footer.mjs +2 -0
- package/dist/footer.mjs.map +1 -0
- package/dist/harnessMetadata.cjs +2 -0
- package/dist/harnessMetadata.cjs.map +1 -0
- package/dist/harnessMetadata.d.cts +21 -0
- package/dist/harnessMetadata.d.cts.map +1 -0
- package/dist/harnessMetadata.d.mts +21 -0
- package/dist/harnessMetadata.d.mts.map +1 -0
- package/dist/harnessMetadata.mjs +2 -0
- package/dist/harnessMetadata.mjs.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +25 -0
- package/dist/index.d.mts +25 -0
- package/dist/index.mjs +1 -0
- package/dist/leader.cjs +1 -0
- package/dist/leader.d.cts +2 -0
- package/dist/leader.d.mts +2 -0
- package/dist/leader.mjs +1 -0
- package/dist/leaderRegistry.cjs +2 -0
- package/dist/leaderRegistry.cjs.map +1 -0
- package/dist/leaderRegistry.d.cts +52 -0
- package/dist/leaderRegistry.d.cts.map +1 -0
- package/dist/leaderRegistry.d.mts +52 -0
- package/dist/leaderRegistry.d.mts.map +1 -0
- package/dist/leaderRegistry.mjs +2 -0
- package/dist/leaderRegistry.mjs.map +1 -0
- package/dist/logSinkTelemetry.cjs +2 -0
- package/dist/logSinkTelemetry.cjs.map +1 -0
- package/dist/logSinkTelemetry.d.cts +29 -0
- package/dist/logSinkTelemetry.d.cts.map +1 -0
- package/dist/logSinkTelemetry.d.mts +29 -0
- package/dist/logSinkTelemetry.d.mts.map +1 -0
- package/dist/logSinkTelemetry.mjs +2 -0
- package/dist/logSinkTelemetry.mjs.map +1 -0
- package/dist/mode.cjs +2 -0
- package/dist/mode.cjs.map +1 -0
- package/dist/mode.d.cts +5 -0
- package/dist/mode.d.cts.map +1 -0
- package/dist/mode.d.mts +5 -0
- package/dist/mode.d.mts.map +1 -0
- package/dist/mode.mjs +2 -0
- package/dist/mode.mjs.map +1 -0
- package/dist/rendering.cjs +2 -0
- package/dist/rendering.cjs.map +1 -0
- package/dist/rendering.d.cts +11 -0
- package/dist/rendering.d.cts.map +1 -0
- package/dist/rendering.d.mts +11 -0
- package/dist/rendering.d.mts.map +1 -0
- package/dist/rendering.mjs +2 -0
- package/dist/rendering.mjs.map +1 -0
- package/dist/skills.cjs +1 -0
- package/dist/skills.d.cts +1 -0
- package/dist/skills.d.mts +1 -0
- package/dist/skills.mjs +1 -0
- package/dist/theme.cjs +2 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.d.cts +95 -0
- package/dist/theme.d.cts.map +1 -0
- package/dist/theme.d.mts +95 -0
- package/dist/theme.d.mts.map +1 -0
- package/dist/theme.mjs +2 -0
- package/dist/theme.mjs.map +1 -0
- package/dist/toolInventory.cjs +2 -0
- package/dist/toolInventory.cjs.map +1 -0
- package/dist/toolInventory.d.cts +40 -0
- package/dist/toolInventory.d.cts.map +1 -0
- package/dist/toolInventory.d.mts +40 -0
- package/dist/toolInventory.d.mts.map +1 -0
- package/dist/toolInventory.mjs +2 -0
- package/dist/toolInventory.mjs.map +1 -0
- package/dist/uiState.cjs +2 -0
- package/dist/uiState.cjs.map +1 -0
- package/dist/uiState.d.cts +37 -0
- package/dist/uiState.d.cts.map +1 -0
- package/dist/uiState.d.mts +37 -0
- package/dist/uiState.d.mts.map +1 -0
- package/dist/uiState.mjs +2 -0
- package/dist/uiState.mjs.map +1 -0
- package/package.json +171 -0
- package/themes/doom-pi-dark.json +86 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vuong Ngo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @agimon-ai/doompi-ui
|
|
2
|
+
|
|
3
|
+
Doom-inspired interactive UI extension for Pi.
|
|
4
|
+
|
|
5
|
+
Owns the leader state machine, rendering, conflict handling, and the core key bindings.
|
|
6
|
+
Space opens the leader only when the draft is empty, so a half-written prompt is never lost
|
|
7
|
+
to a keystroke.
|
|
8
|
+
|
|
9
|
+
Part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi), a coding agent that loads
|
|
10
|
+
only the skills and tools you name. This package is composed by Doompi and is not usually
|
|
11
|
+
installed on its own.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @agimon-ai/doompi-ui
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## How it loads
|
|
20
|
+
|
|
21
|
+
Doompi loads this unconditionally. It is core, not a layer, so it does not appear in
|
|
22
|
+
`.doom/modes.yaml`: making it optional would only create broken configurations.
|
|
23
|
+
|
|
24
|
+
Extension entry: `@agimon-ai/doompi-ui/extensions/pi`
|
|
25
|
+
|
|
26
|
+
## Leader
|
|
27
|
+
|
|
28
|
+
`SPC e c` opens the config panel, once the extension is loaded.
|
|
29
|
+
|
|
30
|
+
## Notes
|
|
31
|
+
|
|
32
|
+
Other extensions contribute bindings through the public `@agimon-ai/doompi-ui/leader` API
|
|
33
|
+
rather than hardcoding chords.
|
|
34
|
+
|
|
35
|
+
## Entry points
|
|
36
|
+
|
|
37
|
+
24 subpaths are declared in the `exports` field of `package.json`.
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(e,t,n)=>(s(e,t,`default`),n&&s(n,t,`default`)),l=(n,r,o)=>(o=n==null?{}:e(i(n)),s(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));exports.__exportAll=o,exports.__reExport=c,exports.__toESM=l;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.defineProperty,t=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,r=Object.prototype.hasOwnProperty,i=(t,n)=>{let r={};for(var i in t)e(r,i,{get:t[i],enumerable:!0});return n||e(r,Symbol.toStringTag,{value:`Module`}),r},a=(i,a,o,s)=>{if(a&&typeof a==`object`||typeof a==`function`)for(var c=n(a),l=0,u=c.length,d;l<u;l++)d=c[l],!r.call(i,d)&&d!==o&&e(i,d,{get:(e=>a[e]).bind(null,d),enumerable:!(s=t(a,d))||s.enumerable});return i},o=(e,t,n)=>(a(e,t,`default`),n&&a(n,t,`default`));export{i as __exportAll,o as __reExport};
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configOverlay.cjs","names":["truncateToWidth","visibleWidth","DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/components/configOverlay.ts"],"sourcesContent":["/**\n * Configuration panel: the `SPC e c` overlay.\n *\n * Sections on the left, one per contributing extension, and that section's\n * fields on the right. The panel owns navigation and text entry; every value it\n * shows was published by an extension, and every change it makes is a request\n * back to whichever extension owns the field. Nothing here knows what a voice\n * model or an editor command is.\n *\n * It subscribes to the registry rather than snapshotting at open time, unlike\n * the tools browser next door, because a contributor can be doing long work\n * (fetching a model, running an installer) and the panel is where that progress\n * is meant to appear.\n *\n * There is no local echo of an edit. A typed value is sent and the field stays\n * pending until its owner republishes, so a rejected value comes back with the\n * reason attached instead of the panel showing a value that was never written.\n */\n\nimport type { ConfigChoice, ConfigField } from '@agimon-ai/doompi-extension-contracts/config';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { DoomConfigRegistry, DoomConfigSectionView } from '../config.ts';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from './doomOverlay.ts';\n\ntype Pane = 'sections' | 'fields';\ntype Mode = 'browse' | 'edit' | 'choices';\n\nconst TITLE = 'CONFIG';\nconst BREADCRUMB = 'SPC › e / c · config';\nconst ACTION_SET = 'set';\nconst ACTION_CLEAR = 'clear';\nconst ACTION_ABORT = 'abort';\nconst DEFAULT_CHOICE_ACTION = 'select';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_TAB = '\\t';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\nconst MIN_WIDTH = 48;\nconst SECTION_PANE_RATIO = 1 / 3;\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst ELLIPSIS = '…';\nconst LABEL_COLUMN = 18;\nconst INDENT = ' ';\nconst EMPTY_MESSAGE = 'No extension has contributed settings to this session.';\nconst NO_FIELDS_MESSAGE = 'This section has no fields.';\nconst UNSET_PLACEHOLDER = 'not set';\nconst SECTION_HEADING = 'SECTIONS';\nconst PROGRESS_WIDTH = 24;\nconst ACTION_INPUT = 'input';\ntype ThemeColor = Parameters<Theme['fg']>[0];\nconst STEP_MARKERS: Readonly<Record<string, readonly [string, ThemeColor]>> = {\n pending: ['·', 'dim'],\n satisfied: ['✓', 'dim'],\n running: ['▸', 'accent'],\n done: ['✓', 'success'],\n failed: ['✗', 'error'],\n};\nconst PROGRESS_FULL = '█';\nconst PROGRESS_EMPTY = '░';\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\nfunction fit(text: string, width: number): string {\n const clipped = truncateToWidth(text, Math.max(0, width), ELLIPSIS);\n return clipped + ' '.repeat(Math.max(0, width - visibleWidth(clipped)));\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fit(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fit(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\nfunction isSelectable(field: ConfigField): boolean {\n return field.kind !== 'info';\n}\n\nexport class ConfigOverlayComponent extends DoomOverlay {\n private readonly registry: DoomConfigRegistry;\n private readonly done: (result: undefined) => void;\n private readonly unsubscribe: () => void;\n private sections: readonly DoomConfigSectionView[];\n private pane: Pane = 'sections';\n private mode: Mode = 'browse';\n private sectionIndex = 0;\n private fieldIndex = 0;\n private choiceIndex = 0;\n private draft = '';\n /** Fields awaiting a reply, keyed `source\u0000section\u0000field`. */\n private readonly pending = new Set<string>();\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, registry: DoomConfigRegistry, done: (result: undefined) => void) {\n super(tui, theme);\n this.registry = registry;\n this.done = done;\n this.sections = registry.getSections();\n this.fieldIndex = this.firstSelectableField();\n this.unsubscribe = registry.subscribe(() => this.onSectionsChanged());\n }\n\n /** Info fields are labels, so the cursor never starts on one. */\n private firstSelectableField(): number {\n const index = this.currentFields().findIndex(isSelectable);\n return index < 0 ? 0 : index;\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n private onSectionsChanged(): void {\n this.sections = this.registry.getSections();\n // Any republish from an owner is its answer, so nothing stays pending past it.\n this.pending.clear();\n this.sectionIndex = Math.min(this.sectionIndex, Math.max(0, this.sections.length - 1));\n const fields = this.currentFields();\n this.fieldIndex = Math.min(this.fieldIndex, Math.max(0, fields.length - 1));\n if (!isSelectable(fields[this.fieldIndex] ?? { kind: 'info' })) this.fieldIndex = this.firstSelectableField();\n const field = this.currentField();\n // A field that grew an error keeps the draft so the typed value is not lost,\n // and an answer being typed survives the republish each output chunk causes.\n if (this.mode === 'edit' && !field?.statusText && !field?.awaitingInput) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private currentSection(): DoomConfigSectionView | undefined {\n return this.sections[this.sectionIndex];\n }\n\n private currentFields(): readonly ConfigField[] {\n return this.currentSection()?.fields ?? [];\n }\n\n private currentField(): ConfigField | undefined {\n return this.currentFields()[this.fieldIndex];\n }\n\n private currentChoices(): readonly ConfigChoice[] {\n return this.currentField()?.choices ?? [];\n }\n\n private pendingKey(field: ConfigField): string {\n return `${this.currentSection()?.source ?? ''}\u0000${this.currentSection()?.id ?? ''}\u0000${field.id}`;\n }\n\n private invoke(action: string, value?: string): void {\n const section = this.currentSection();\n const field = this.currentField();\n if (!section || !field) return;\n this.pending.add(this.pendingKey(field));\n this.registry.invoke({\n source: section.source,\n sectionId: section.id,\n fieldId: field.id,\n action,\n ...(value === undefined ? {} : { value }),\n });\n this.tui.requestRender();\n }\n\n private moveSection(delta: number): void {\n if (this.sections.length === 0) return;\n const next = Math.max(0, Math.min(this.sectionIndex + delta, this.sections.length - 1));\n if (next === this.sectionIndex) return;\n this.sectionIndex = next;\n this.fieldIndex = this.firstSelectableField();\n this.tui.requestRender();\n }\n\n /** Info fields are labels, so the cursor steps over them to the next control. */\n private moveField(delta: number): void {\n const fields = this.currentFields();\n const step = delta < 0 ? -1 : 1;\n let next = this.fieldIndex + step;\n while (next >= 0 && next < fields.length && !isSelectable(fields[next] as ConfigField)) next += step;\n if (next < 0 || next >= fields.length) return;\n this.fieldIndex = next;\n this.tui.requestRender();\n }\n\n private moveChoice(delta: number): void {\n const choices = this.currentChoices();\n if (choices.length === 0) return;\n const next = Math.max(0, Math.min(this.choiceIndex + delta, choices.length - 1));\n if (next === this.choiceIndex) return;\n this.choiceIndex = next;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'edit') return this.handleEditInput(data);\n if (this.mode === 'choices') return this.handleChoiceInput(data);\n this.handleBrowseInput(data);\n }\n\n private handleBrowseInput(data: string): void {\n const field = this.currentField();\n if (matchesKey(data, 'ctrl+c')) {\n if (field?.busy) return this.invoke(ACTION_ABORT);\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'escape') || data === 'q') {\n // Escape backs out of the fields pane before it closes the panel.\n if (this.pane === 'fields') {\n this.pane = 'sections';\n this.tui.requestRender();\n return;\n }\n this.done(undefined);\n return;\n }\n if (data === KEY_TAB) {\n this.pane = this.pane === 'sections' ? 'fields' : 'sections';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n return this.pane === 'sections' ? this.moveSection(-1) : this.moveField(-1);\n }\n if (matchesKey(data, 'down') || data === 'j') {\n return this.pane === 'sections' ? this.moveSection(1) : this.moveField(1);\n }\n // While a field offers its own keys, those win: a pending confirmation is\n // answered before anything else can be typed.\n const offered = field?.actions?.find((action) => action.key === data);\n if (this.pane === 'fields' && offered) return this.invoke(offered.action);\n // Answering a waiting command beats every other reading of `i`, and it is\n // the only thing that will make progress while one is blocked on a prompt.\n if (this.pane === 'fields' && field?.awaitingInput && data === 'i') {\n this.draft = '';\n this.mode = 'edit';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) return this.activate();\n if (this.pane === 'fields' && data === 'd' && field && isSelectable(field)) return this.invoke(ACTION_CLEAR);\n }\n\n private activate(): void {\n if (this.pane === 'sections') {\n this.pane = 'fields';\n this.tui.requestRender();\n return;\n }\n const field = this.currentField();\n if (!field || field.busy || !isSelectable(field)) return;\n // `enum` and `choice` differ only in how many entries they carry, so both\n // open the same list with the cursor on whatever is currently set.\n if (field.kind === 'choice' || field.kind === 'enum') {\n this.choiceIndex = Math.max(\n 0,\n this.currentChoices().findIndex((choice) => choice.id === field.value),\n );\n this.mode = 'choices';\n this.tui.requestRender();\n return;\n }\n this.draft = field.value ?? '';\n this.mode = 'edit';\n this.tui.requestRender();\n }\n\n private handleEditInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.draft = '';\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n const value = this.draft.trim();\n // Answering a prompt is a different write from setting a field, and an\n // empty answer is meaningful there: it accepts the command's default.\n if (this.currentField()?.awaitingInput) {\n this.mode = 'browse';\n this.invoke(ACTION_INPUT, value.length === 0 ? undefined : value);\n this.draft = '';\n return;\n }\n // The parser rejects blank values, so clearing a key means removing it.\n this.invoke(value.length === 0 ? ACTION_CLEAR : ACTION_SET, value.length === 0 ? undefined : value);\n return;\n }\n if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n private handleChoiceInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') return this.moveChoice(-1);\n if (matchesKey(data, 'down') || data === 'j') return this.moveChoice(1);\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n const choice = this.currentChoices()[this.choiceIndex];\n if (!choice) return;\n this.mode = 'browse';\n // The entry names what enter does to it, which is what lets one list hold\n // both an installed thing to select and an absent one to fetch.\n this.invoke(choice.action ?? DEFAULT_CHOICE_ACTION, choice.id);\n }\n }\n\n private sectionLines(width: number): string[] {\n const lines = [this.theme.bold(SECTION_HEADING), ''];\n if (this.sections.length === 0) {\n lines.push(...wrap(EMPTY_MESSAGE, width).map((line) => this.theme.fg('dim', line)));\n return lines;\n }\n for (const [index, section] of this.sections.entries()) {\n const active = index === this.sectionIndex;\n const title = rightAligned(\n active ? this.theme.bold(section.title) : this.theme.fg('text', section.title),\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', section.detail ?? ''),\n width,\n );\n lines.push(active && this.pane === 'sections' ? this.theme.bg('selectedBg', fit(title, width)) : title);\n lines.push(this.theme.fg('dim', fit(`${INDENT}${section.source}`, width)));\n }\n return lines;\n }\n\n /**\n * The whole plan, not just the running step.\n *\n * Seeing that a package manager already finished and a download is now\n * underway is what makes a long install legible; a lone \"step 3 of 6\" is not.\n */\n private stepLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.steps ?? []).map((step) => {\n const [glyph, color] = STEP_MARKERS[step.state];\n const label = step.state === 'pending' ? this.theme.fg('dim', step.label) : step.label;\n const row = rightAligned(\n `${this.theme.fg(color, glyph)} ${label}`,\n this.theme.fg('dim', step.detail ?? ''),\n inner,\n );\n return `${INDENT}${row}`;\n });\n }\n\n /** The tail of the running command, so a prompt it printed is visible. */\n private outputLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.output ?? []).map((line) => `${INDENT}${this.theme.fg('dim', fit(line, inner))}`);\n }\n\n private progressBar(ratio: number | undefined, width: number): string {\n if (ratio === undefined) return '';\n const filled = Math.max(0, Math.min(PROGRESS_WIDTH, Math.round(ratio * PROGRESS_WIDTH)));\n const bar = PROGRESS_FULL.repeat(filled) + PROGRESS_EMPTY.repeat(PROGRESS_WIDTH - filled);\n return fit(`${bar} ${Math.round(ratio * 100)}%`, width);\n }\n\n private fieldLines(width: number): string[] {\n const section = this.currentSection();\n if (!section) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n const lines = [this.theme.bold(section.title), ''];\n if (section.notice) {\n lines.push(\n ...wrap(section.notice, width).map((line) =>\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', line),\n ),\n );\n lines.push('');\n }\n if (section.fields.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n for (const [index, field] of section.fields.entries()) {\n const active = index === this.fieldIndex && this.pane === 'fields';\n const editing = active && this.mode === 'edit';\n const isPending = this.pending.has(this.pendingKey(field));\n const shown = editing ? `${this.draft}█` : (field.value ?? field.placeholder ?? UNSET_PLACEHOLDER);\n const tone = editing || field.value ? 'text' : 'dim';\n const value = this.theme.fg(isPending || field.busy ? 'dim' : tone, shown);\n const status = field.statusText ?? (isPending ? '' : '');\n const row = `${this.theme.fg('dim', fit(field.label, LABEL_COLUMN))}${\n status\n ? rightAligned(value, this.theme.fg(field.status === 'blocked' ? 'error' : 'dim', status), valueWidth)\n : fit(value, valueWidth)\n }`;\n lines.push(active ? this.theme.bg('selectedBg', fit(row, width)) : row);\n lines.push(...this.stepLines(field, width));\n if (field.busy && field.progress) {\n lines.push(this.theme.fg('dim', fit(`${INDENT}${field.progress.label}`, width)));\n const bar = this.progressBar(field.progress.ratio, valueWidth);\n if (bar) lines.push(`${INDENT}${this.theme.fg('accent', bar)}`);\n }\n lines.push(...this.outputLines(field, width));\n if (active && field.awaitingInput) {\n // The command is asking something. Answering is the only useful action,\n // so the line is offered without having to enter a mode first.\n const prompt = this.mode === 'edit' ? `${this.draft}█` : 'press i to answer';\n lines.push(`${INDENT}${this.theme.fg('warning', fit(`waiting for input ${prompt}`, width - INDENT.length))}`);\n }\n if (!active) continue;\n if (field.detail) {\n lines.push(\n ...wrap(field.detail, Math.max(1, width - LABEL_COLUMN)).map(\n (line) => `${' '.repeat(LABEL_COLUMN)}${this.theme.fg('dim', line)}`,\n ),\n );\n }\n if (field.keyPath) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.theme.fg('accent', field.keyPath)}`);\n if (field.actions && field.actions.length > 0) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.hintLine()}`);\n }\n return lines;\n }\n\n private choiceLines(width: number): string[] {\n const field = this.currentField();\n const choices = this.currentChoices();\n const lines = [this.theme.bold(field?.label ?? ''), ''];\n if (choices.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n const budget = Math.max(1, this.bodyHeight - lines.length);\n const selected = Math.max(0, Math.min(this.choiceIndex, choices.length - 1));\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, choices.length - budget)));\n let lastGroup: string | undefined;\n for (const [offset, choice] of choices.slice(start, start + budget).entries()) {\n if (choice.group && choice.group !== lastGroup) {\n lastGroup = choice.group;\n lines.push(this.theme.fg('accent', fit(choice.group, width)));\n }\n const status = choice.statusText ?? choice.status ?? '';\n const row = rightAligned(\n `${INDENT}${choice.label}${choice.detail ? this.theme.fg('dim', ` ${choice.detail}`) : ''}`,\n this.theme.fg(choice.status === 'blocked' ? 'error' : 'dim', status),\n width,\n );\n lines.push(start + offset === selected ? this.theme.bg('selectedBg', fit(row, width)) : row);\n }\n return lines;\n }\n\n private hints(): readonly (readonly [string, string])[] {\n if (this.mode === 'edit') {\n return [\n ['⌫', 'delete'],\n ['enter', this.currentField()?.awaitingInput ? 'send' : 'save'],\n ['esc', 'cancel'],\n ];\n }\n if (this.mode === 'choices') {\n const choice = this.currentChoices()[this.choiceIndex];\n // The hint names what enter does to the entry under the cursor, because on\n // this list that differs row by row.\n return [\n ['↑↓', 'move'],\n ['enter', choice?.action ?? DEFAULT_CHOICE_ACTION],\n ['esc', 'back'],\n ];\n }\n const field = this.currentField();\n if (field?.awaitingInput) {\n return [\n ['i', 'answer'],\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n if (field?.busy) {\n return [\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n const offered = (field?.actions ?? []).map((action) => [action.key, action.label] as const);\n return [\n ['↑↓', 'move'],\n ['tab', 'pane'],\n ['enter', this.pane === 'sections' ? 'fields' : 'edit'],\n ...offered,\n ...(this.pane === 'fields' && offered.length === 0 ? ([['d', 'clear']] as const) : []),\n ['esc', this.pane === 'fields' ? 'back' : 'close'],\n ];\n }\n\n private hintLine(): string {\n return this.hints()\n .map(\n ([key, label]) =>\n `${this.theme.bg('selectedBg', this.theme.fg('text', ` ${key} `))}${this.theme.fg('dim', ` ${label}`)}`,\n )\n .join(this.theme.fg('dim', ' '));\n }\n\n protected getChrome(): DoomOverlayChrome {\n const section = this.currentSection();\n const field = this.currentField();\n const breadcrumb =\n this.mode === 'choices' && section && field ? `${BREADCRUMB} › ${section.title} / ${field.label}` : BREADCRUMB;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb,\n headerRight: `${this.sections.length} sections · ${this.sections.length === 0 ? 'none' : (section?.source ?? '')}`,\n footer: this.hintLine(),\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n const right = this.mode === 'choices' ? this.choiceLines.bind(this) : this.fieldLines.bind(this);\n if (width < MIN_WIDTH) return this.renderStacked(width, height, right);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * SECTION_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.sectionLines(leftContent);\n const rightLines = right(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fit(left[index] ?? '', leftContent)} ${divider} ${fit(rightLines[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number, right: (width: number) => string[]): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.sectionLines(width);\n const bottom = right(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n}\n\nexport async function openConfigOverlay(ctx: ExtensionContext, registry: DoomConfigRegistry): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new ConfigOverlayComponent(tui, theme, registry, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configOverlay.d.cts","names":[],"sources":["../../src/components/configOverlay.ts"],"mappings":";;;;cAoHa,+BAA+B;mBACzB;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;UACA;;mBAES;UACT;EAEI,YAAA,KAAK,gBAAgB,OAAO,OAAO,UAAU,iBAAA,oBAAoB,OAAO;;UAU5E;EAKR;UAIQ;UAeA;UAIA;UAIA;UAIA;UAIA;UAIA;UAeA;;UAUA;UAUA;EASR,YAAY;UAMJ;UA4CA;UAwBA;UA0BA;UAkBA;;;;;;;UAyBA;;UAeA;UAKA;UAOA;UA2DA;UA4BA;UA2CA;YASE,aAAa;YAcb,WAAW,eAAe;UAmB5B;;iBAeY,kBAAkB,KAAK,kBAAkB,UAAU,iBAAA,qBAAqB"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configOverlay.d.mts","names":[],"sources":["../../src/components/configOverlay.ts"],"mappings":";;;;cAoHa,+BAA+B;mBACzB;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;UACA;;mBAES;UACT;EAEI,YAAA,KAAK,gBAAgB,OAAO,OAAO,UAAU,iBAAA,oBAAoB,OAAO;;UAU5E;EAKR;UAIQ;UAeA;UAIA;UAIA;UAIA;UAIA;UAIA;UAeA;;UAUA;UAUA;EASR,YAAY;UAMJ;UA4CA;UAwBA;UA0BA;UAkBA;;;;;;;UAyBA;;UAeA;UAKA;UAOA;UA2DA;UA4BA;UA2CA;YASE,aAAa;YAcb,WAAW,eAAe;UAmB5B;;iBAeY,kBAAkB,KAAK,kBAAkB,UAAU,iBAAA,qBAAqB"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configOverlay.mjs","names":[],"sources":["../../src/components/configOverlay.ts"],"sourcesContent":["/**\n * Configuration panel: the `SPC e c` overlay.\n *\n * Sections on the left, one per contributing extension, and that section's\n * fields on the right. The panel owns navigation and text entry; every value it\n * shows was published by an extension, and every change it makes is a request\n * back to whichever extension owns the field. Nothing here knows what a voice\n * model or an editor command is.\n *\n * It subscribes to the registry rather than snapshotting at open time, unlike\n * the tools browser next door, because a contributor can be doing long work\n * (fetching a model, running an installer) and the panel is where that progress\n * is meant to appear.\n *\n * There is no local echo of an edit. A typed value is sent and the field stays\n * pending until its owner republishes, so a rejected value comes back with the\n * reason attached instead of the panel showing a value that was never written.\n */\n\nimport type { ConfigChoice, ConfigField } from '@agimon-ai/doompi-extension-contracts/config';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { DoomConfigRegistry, DoomConfigSectionView } from '../config.ts';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from './doomOverlay.ts';\n\ntype Pane = 'sections' | 'fields';\ntype Mode = 'browse' | 'edit' | 'choices';\n\nconst TITLE = 'CONFIG';\nconst BREADCRUMB = 'SPC › e / c · config';\nconst ACTION_SET = 'set';\nconst ACTION_CLEAR = 'clear';\nconst ACTION_ABORT = 'abort';\nconst DEFAULT_CHOICE_ACTION = 'select';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_TAB = '\\t';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\nconst MIN_WIDTH = 48;\nconst SECTION_PANE_RATIO = 1 / 3;\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst ELLIPSIS = '…';\nconst LABEL_COLUMN = 18;\nconst INDENT = ' ';\nconst EMPTY_MESSAGE = 'No extension has contributed settings to this session.';\nconst NO_FIELDS_MESSAGE = 'This section has no fields.';\nconst UNSET_PLACEHOLDER = 'not set';\nconst SECTION_HEADING = 'SECTIONS';\nconst PROGRESS_WIDTH = 24;\nconst ACTION_INPUT = 'input';\ntype ThemeColor = Parameters<Theme['fg']>[0];\nconst STEP_MARKERS: Readonly<Record<string, readonly [string, ThemeColor]>> = {\n pending: ['·', 'dim'],\n satisfied: ['✓', 'dim'],\n running: ['▸', 'accent'],\n done: ['✓', 'success'],\n failed: ['✗', 'error'],\n};\nconst PROGRESS_FULL = '█';\nconst PROGRESS_EMPTY = '░';\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\nfunction fit(text: string, width: number): string {\n const clipped = truncateToWidth(text, Math.max(0, width), ELLIPSIS);\n return clipped + ' '.repeat(Math.max(0, width - visibleWidth(clipped)));\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fit(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fit(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\nfunction isSelectable(field: ConfigField): boolean {\n return field.kind !== 'info';\n}\n\nexport class ConfigOverlayComponent extends DoomOverlay {\n private readonly registry: DoomConfigRegistry;\n private readonly done: (result: undefined) => void;\n private readonly unsubscribe: () => void;\n private sections: readonly DoomConfigSectionView[];\n private pane: Pane = 'sections';\n private mode: Mode = 'browse';\n private sectionIndex = 0;\n private fieldIndex = 0;\n private choiceIndex = 0;\n private draft = '';\n /** Fields awaiting a reply, keyed `source\u0000section\u0000field`. */\n private readonly pending = new Set<string>();\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, registry: DoomConfigRegistry, done: (result: undefined) => void) {\n super(tui, theme);\n this.registry = registry;\n this.done = done;\n this.sections = registry.getSections();\n this.fieldIndex = this.firstSelectableField();\n this.unsubscribe = registry.subscribe(() => this.onSectionsChanged());\n }\n\n /** Info fields are labels, so the cursor never starts on one. */\n private firstSelectableField(): number {\n const index = this.currentFields().findIndex(isSelectable);\n return index < 0 ? 0 : index;\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n private onSectionsChanged(): void {\n this.sections = this.registry.getSections();\n // Any republish from an owner is its answer, so nothing stays pending past it.\n this.pending.clear();\n this.sectionIndex = Math.min(this.sectionIndex, Math.max(0, this.sections.length - 1));\n const fields = this.currentFields();\n this.fieldIndex = Math.min(this.fieldIndex, Math.max(0, fields.length - 1));\n if (!isSelectable(fields[this.fieldIndex] ?? { kind: 'info' })) this.fieldIndex = this.firstSelectableField();\n const field = this.currentField();\n // A field that grew an error keeps the draft so the typed value is not lost,\n // and an answer being typed survives the republish each output chunk causes.\n if (this.mode === 'edit' && !field?.statusText && !field?.awaitingInput) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private currentSection(): DoomConfigSectionView | undefined {\n return this.sections[this.sectionIndex];\n }\n\n private currentFields(): readonly ConfigField[] {\n return this.currentSection()?.fields ?? [];\n }\n\n private currentField(): ConfigField | undefined {\n return this.currentFields()[this.fieldIndex];\n }\n\n private currentChoices(): readonly ConfigChoice[] {\n return this.currentField()?.choices ?? [];\n }\n\n private pendingKey(field: ConfigField): string {\n return `${this.currentSection()?.source ?? ''}\u0000${this.currentSection()?.id ?? ''}\u0000${field.id}`;\n }\n\n private invoke(action: string, value?: string): void {\n const section = this.currentSection();\n const field = this.currentField();\n if (!section || !field) return;\n this.pending.add(this.pendingKey(field));\n this.registry.invoke({\n source: section.source,\n sectionId: section.id,\n fieldId: field.id,\n action,\n ...(value === undefined ? {} : { value }),\n });\n this.tui.requestRender();\n }\n\n private moveSection(delta: number): void {\n if (this.sections.length === 0) return;\n const next = Math.max(0, Math.min(this.sectionIndex + delta, this.sections.length - 1));\n if (next === this.sectionIndex) return;\n this.sectionIndex = next;\n this.fieldIndex = this.firstSelectableField();\n this.tui.requestRender();\n }\n\n /** Info fields are labels, so the cursor steps over them to the next control. */\n private moveField(delta: number): void {\n const fields = this.currentFields();\n const step = delta < 0 ? -1 : 1;\n let next = this.fieldIndex + step;\n while (next >= 0 && next < fields.length && !isSelectable(fields[next] as ConfigField)) next += step;\n if (next < 0 || next >= fields.length) return;\n this.fieldIndex = next;\n this.tui.requestRender();\n }\n\n private moveChoice(delta: number): void {\n const choices = this.currentChoices();\n if (choices.length === 0) return;\n const next = Math.max(0, Math.min(this.choiceIndex + delta, choices.length - 1));\n if (next === this.choiceIndex) return;\n this.choiceIndex = next;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'edit') return this.handleEditInput(data);\n if (this.mode === 'choices') return this.handleChoiceInput(data);\n this.handleBrowseInput(data);\n }\n\n private handleBrowseInput(data: string): void {\n const field = this.currentField();\n if (matchesKey(data, 'ctrl+c')) {\n if (field?.busy) return this.invoke(ACTION_ABORT);\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'escape') || data === 'q') {\n // Escape backs out of the fields pane before it closes the panel.\n if (this.pane === 'fields') {\n this.pane = 'sections';\n this.tui.requestRender();\n return;\n }\n this.done(undefined);\n return;\n }\n if (data === KEY_TAB) {\n this.pane = this.pane === 'sections' ? 'fields' : 'sections';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n return this.pane === 'sections' ? this.moveSection(-1) : this.moveField(-1);\n }\n if (matchesKey(data, 'down') || data === 'j') {\n return this.pane === 'sections' ? this.moveSection(1) : this.moveField(1);\n }\n // While a field offers its own keys, those win: a pending confirmation is\n // answered before anything else can be typed.\n const offered = field?.actions?.find((action) => action.key === data);\n if (this.pane === 'fields' && offered) return this.invoke(offered.action);\n // Answering a waiting command beats every other reading of `i`, and it is\n // the only thing that will make progress while one is blocked on a prompt.\n if (this.pane === 'fields' && field?.awaitingInput && data === 'i') {\n this.draft = '';\n this.mode = 'edit';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) return this.activate();\n if (this.pane === 'fields' && data === 'd' && field && isSelectable(field)) return this.invoke(ACTION_CLEAR);\n }\n\n private activate(): void {\n if (this.pane === 'sections') {\n this.pane = 'fields';\n this.tui.requestRender();\n return;\n }\n const field = this.currentField();\n if (!field || field.busy || !isSelectable(field)) return;\n // `enum` and `choice` differ only in how many entries they carry, so both\n // open the same list with the cursor on whatever is currently set.\n if (field.kind === 'choice' || field.kind === 'enum') {\n this.choiceIndex = Math.max(\n 0,\n this.currentChoices().findIndex((choice) => choice.id === field.value),\n );\n this.mode = 'choices';\n this.tui.requestRender();\n return;\n }\n this.draft = field.value ?? '';\n this.mode = 'edit';\n this.tui.requestRender();\n }\n\n private handleEditInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.draft = '';\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n const value = this.draft.trim();\n // Answering a prompt is a different write from setting a field, and an\n // empty answer is meaningful there: it accepts the command's default.\n if (this.currentField()?.awaitingInput) {\n this.mode = 'browse';\n this.invoke(ACTION_INPUT, value.length === 0 ? undefined : value);\n this.draft = '';\n return;\n }\n // The parser rejects blank values, so clearing a key means removing it.\n this.invoke(value.length === 0 ? ACTION_CLEAR : ACTION_SET, value.length === 0 ? undefined : value);\n return;\n }\n if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n private handleChoiceInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') return this.moveChoice(-1);\n if (matchesKey(data, 'down') || data === 'j') return this.moveChoice(1);\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n const choice = this.currentChoices()[this.choiceIndex];\n if (!choice) return;\n this.mode = 'browse';\n // The entry names what enter does to it, which is what lets one list hold\n // both an installed thing to select and an absent one to fetch.\n this.invoke(choice.action ?? DEFAULT_CHOICE_ACTION, choice.id);\n }\n }\n\n private sectionLines(width: number): string[] {\n const lines = [this.theme.bold(SECTION_HEADING), ''];\n if (this.sections.length === 0) {\n lines.push(...wrap(EMPTY_MESSAGE, width).map((line) => this.theme.fg('dim', line)));\n return lines;\n }\n for (const [index, section] of this.sections.entries()) {\n const active = index === this.sectionIndex;\n const title = rightAligned(\n active ? this.theme.bold(section.title) : this.theme.fg('text', section.title),\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', section.detail ?? ''),\n width,\n );\n lines.push(active && this.pane === 'sections' ? this.theme.bg('selectedBg', fit(title, width)) : title);\n lines.push(this.theme.fg('dim', fit(`${INDENT}${section.source}`, width)));\n }\n return lines;\n }\n\n /**\n * The whole plan, not just the running step.\n *\n * Seeing that a package manager already finished and a download is now\n * underway is what makes a long install legible; a lone \"step 3 of 6\" is not.\n */\n private stepLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.steps ?? []).map((step) => {\n const [glyph, color] = STEP_MARKERS[step.state];\n const label = step.state === 'pending' ? this.theme.fg('dim', step.label) : step.label;\n const row = rightAligned(\n `${this.theme.fg(color, glyph)} ${label}`,\n this.theme.fg('dim', step.detail ?? ''),\n inner,\n );\n return `${INDENT}${row}`;\n });\n }\n\n /** The tail of the running command, so a prompt it printed is visible. */\n private outputLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.output ?? []).map((line) => `${INDENT}${this.theme.fg('dim', fit(line, inner))}`);\n }\n\n private progressBar(ratio: number | undefined, width: number): string {\n if (ratio === undefined) return '';\n const filled = Math.max(0, Math.min(PROGRESS_WIDTH, Math.round(ratio * PROGRESS_WIDTH)));\n const bar = PROGRESS_FULL.repeat(filled) + PROGRESS_EMPTY.repeat(PROGRESS_WIDTH - filled);\n return fit(`${bar} ${Math.round(ratio * 100)}%`, width);\n }\n\n private fieldLines(width: number): string[] {\n const section = this.currentSection();\n if (!section) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n const lines = [this.theme.bold(section.title), ''];\n if (section.notice) {\n lines.push(\n ...wrap(section.notice, width).map((line) =>\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', line),\n ),\n );\n lines.push('');\n }\n if (section.fields.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n for (const [index, field] of section.fields.entries()) {\n const active = index === this.fieldIndex && this.pane === 'fields';\n const editing = active && this.mode === 'edit';\n const isPending = this.pending.has(this.pendingKey(field));\n const shown = editing ? `${this.draft}█` : (field.value ?? field.placeholder ?? UNSET_PLACEHOLDER);\n const tone = editing || field.value ? 'text' : 'dim';\n const value = this.theme.fg(isPending || field.busy ? 'dim' : tone, shown);\n const status = field.statusText ?? (isPending ? '' : '');\n const row = `${this.theme.fg('dim', fit(field.label, LABEL_COLUMN))}${\n status\n ? rightAligned(value, this.theme.fg(field.status === 'blocked' ? 'error' : 'dim', status), valueWidth)\n : fit(value, valueWidth)\n }`;\n lines.push(active ? this.theme.bg('selectedBg', fit(row, width)) : row);\n lines.push(...this.stepLines(field, width));\n if (field.busy && field.progress) {\n lines.push(this.theme.fg('dim', fit(`${INDENT}${field.progress.label}`, width)));\n const bar = this.progressBar(field.progress.ratio, valueWidth);\n if (bar) lines.push(`${INDENT}${this.theme.fg('accent', bar)}`);\n }\n lines.push(...this.outputLines(field, width));\n if (active && field.awaitingInput) {\n // The command is asking something. Answering is the only useful action,\n // so the line is offered without having to enter a mode first.\n const prompt = this.mode === 'edit' ? `${this.draft}█` : 'press i to answer';\n lines.push(`${INDENT}${this.theme.fg('warning', fit(`waiting for input ${prompt}`, width - INDENT.length))}`);\n }\n if (!active) continue;\n if (field.detail) {\n lines.push(\n ...wrap(field.detail, Math.max(1, width - LABEL_COLUMN)).map(\n (line) => `${' '.repeat(LABEL_COLUMN)}${this.theme.fg('dim', line)}`,\n ),\n );\n }\n if (field.keyPath) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.theme.fg('accent', field.keyPath)}`);\n if (field.actions && field.actions.length > 0) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.hintLine()}`);\n }\n return lines;\n }\n\n private choiceLines(width: number): string[] {\n const field = this.currentField();\n const choices = this.currentChoices();\n const lines = [this.theme.bold(field?.label ?? ''), ''];\n if (choices.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n const budget = Math.max(1, this.bodyHeight - lines.length);\n const selected = Math.max(0, Math.min(this.choiceIndex, choices.length - 1));\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, choices.length - budget)));\n let lastGroup: string | undefined;\n for (const [offset, choice] of choices.slice(start, start + budget).entries()) {\n if (choice.group && choice.group !== lastGroup) {\n lastGroup = choice.group;\n lines.push(this.theme.fg('accent', fit(choice.group, width)));\n }\n const status = choice.statusText ?? choice.status ?? '';\n const row = rightAligned(\n `${INDENT}${choice.label}${choice.detail ? this.theme.fg('dim', ` ${choice.detail}`) : ''}`,\n this.theme.fg(choice.status === 'blocked' ? 'error' : 'dim', status),\n width,\n );\n lines.push(start + offset === selected ? this.theme.bg('selectedBg', fit(row, width)) : row);\n }\n return lines;\n }\n\n private hints(): readonly (readonly [string, string])[] {\n if (this.mode === 'edit') {\n return [\n ['⌫', 'delete'],\n ['enter', this.currentField()?.awaitingInput ? 'send' : 'save'],\n ['esc', 'cancel'],\n ];\n }\n if (this.mode === 'choices') {\n const choice = this.currentChoices()[this.choiceIndex];\n // The hint names what enter does to the entry under the cursor, because on\n // this list that differs row by row.\n return [\n ['↑↓', 'move'],\n ['enter', choice?.action ?? DEFAULT_CHOICE_ACTION],\n ['esc', 'back'],\n ];\n }\n const field = this.currentField();\n if (field?.awaitingInput) {\n return [\n ['i', 'answer'],\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n if (field?.busy) {\n return [\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n const offered = (field?.actions ?? []).map((action) => [action.key, action.label] as const);\n return [\n ['↑↓', 'move'],\n ['tab', 'pane'],\n ['enter', this.pane === 'sections' ? 'fields' : 'edit'],\n ...offered,\n ...(this.pane === 'fields' && offered.length === 0 ? ([['d', 'clear']] as const) : []),\n ['esc', this.pane === 'fields' ? 'back' : 'close'],\n ];\n }\n\n private hintLine(): string {\n return this.hints()\n .map(\n ([key, label]) =>\n `${this.theme.bg('selectedBg', this.theme.fg('text', ` ${key} `))}${this.theme.fg('dim', ` ${label}`)}`,\n )\n .join(this.theme.fg('dim', ' '));\n }\n\n protected getChrome(): DoomOverlayChrome {\n const section = this.currentSection();\n const field = this.currentField();\n const breadcrumb =\n this.mode === 'choices' && section && field ? `${BREADCRUMB} › ${section.title} / ${field.label}` : BREADCRUMB;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb,\n headerRight: `${this.sections.length} sections · ${this.sections.length === 0 ? 'none' : (section?.source ?? '')}`,\n footer: this.hintLine(),\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n const right = this.mode === 'choices' ? this.choiceLines.bind(this) : this.fieldLines.bind(this);\n if (width < MIN_WIDTH) return this.renderStacked(width, height, right);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * SECTION_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.sectionLines(leftContent);\n const rightLines = right(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fit(left[index] ?? '', leftContent)} ${divider} ${fit(rightLines[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number, right: (width: number) => string[]): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.sectionLines(width);\n const bottom = right(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n}\n\nexport async function openConfigOverlay(ctx: ExtensionContext, registry: DoomConfigRegistry): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new ConfigOverlayComponent(tui, theme, registry, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../leaderRegistry.cjs"),t=require("../rendering.cjs");let n=require("@earendil-works/pi-coding-agent"),r=require("@earendil-works/pi-tui");const i=`borderAccent`,a=`accent`,o={bg:(e,t)=>t,bold:e=>e,fg:(e,t)=>t,inverse:e=>e},s={active:!1,prefix:[],label:``,options:[]};var c=class extends n.CustomEditor{uiState;onSnapshot;chromeTheme;leaderRegistry;isCommandAvailable;onUnavailableCommand;onUnavailableAction;onLeaderAction;unsubscribeLeaderRegistry;unsubscribeUiState;leaderActive=!1;leaderPath=[];constructor(t,n,r,i,a,s,c=o,l={}){super(t,n,r,s),this.uiState=i,this.onSnapshot=a,this.chromeTheme=c,this.leaderRegistry=l.registry??new e.DoomLeaderRegistry,this.isCommandAvailable=l.isCommandAvailable??(()=>!0),this.onUnavailableCommand=l.onUnavailableCommand??(()=>void 0),this.onUnavailableAction=l.onUnavailableAction??(()=>void 0),this.onLeaderAction=l.onLeaderAction,this.unsubscribeLeaderRegistry=this.leaderRegistry.subscribe(()=>{this.leaderActive&&this.publishLeader()}),this.unsubscribeUiState=this.uiState.subscribe(()=>t.requestRender())}handleInput(e){let t=(0,r.parseKey)(e);if(this.leaderActive){this.handleLeaderInput(t);return}if(t===`space`&&this.getText().length===0||t===`ctrl+space`){this.startLeader();return}super.handleInput(e)}render(e){if(e<12)return super.render(e);let n=e-4,r=super.render(n);if(r.length<3)return r.map(n=>t.fitLine(n,e));let o=this.findBottomBorder(r,n),s=r.slice(1,o),c=r.slice(o+1),l=this.chromeTheme.fg(i,`│`),u=this.chromeTheme.fg(i,`╭${`─`.repeat(e-2)}╮`),d=this.chromeTheme.fg(i,`╰${`─`.repeat(e-2)}╯`),f=this.editorLine(t.alignLine(` ${this.modeBadge()}${this.modeNames()}`,this.chromeTheme.fg(`dim`,`${this.editorStatus()} `),e-2),e,l),p=s.map((t,n)=>{let r=n===0?this.chromeTheme.bold(this.chromeTheme.fg(a,`❯ `)):` `;return this.editorLine(`${r}${t}`,e,l)});return[u,f,`${this.chromeTheme.fg(i,`├`)}${this.chromeTheme.fg(`borderMuted`,`─`.repeat(e-2))}${this.chromeTheme.fg(i,`┤`)}`,...p,d,...c.map(n=>t.fitLine(` ${n}`,e))]}dispose(){this.unsubscribeLeaderRegistry(),this.unsubscribeUiState(),this.cancelLeader()}startLeader(){this.leaderActive=!0,this.leaderPath=[],this.publishLeader()}cancelLeader(){this.leaderActive=!1,this.leaderPath=[],this.publish(s)}handleLeaderInput(e){if(e===`escape`){this.cancelLeader();return}if(e===`backspace`){this.leaderPath.length===0?this.cancelLeader():(this.leaderPath.pop(),this.publishLeader());return}if(!e||e.length!==1){this.cancelLeader();return}let t=e.toLowerCase(),n=this.currentGroup()?.options.find(e=>e.key===t);if(!n){this.cancelLeader();return}if(n.hasChildren){this.leaderPath.push(t),this.publishLeader();return}if(!n.action){this.cancelLeader();return}this.cancelLeader(),this.execute(n.action)}currentGroup(){return this.leaderRegistry.getGroup(this.leaderPath)}publishLeader(){let e=this.currentGroup();if(!e){this.cancelLeader();return}let t=this.leaderRegistry.getGroup([]);this.publish({active:!0,prefix:[`SPC`,...this.leaderPath],label:e.label,options:e.options.map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{}})),rootOptions:t?.options.map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{}}))??[]})}publish(e){this.uiState.setLeader(e),this.onSnapshot(e),this.tui.requestRender()}execute(e){if(e.type===`app`){let t=this.actionHandlers.get(e.action);if(t){t();return}if(e.action===`app.exit`){this.onCtrlD?.();return}this.onUnavailableAction(e.action);return}if(e.type===`command`){this.submitCommand(e.command.name,e.command.args);return}this.onLeaderAction?this.onLeaderAction(e.source,e.action.name):this.onUnavailableAction(`${e.source}:${e.action.name}`)}submitCommand(e,t){if(!this.isCommandAvailable(e)){this.onUnavailableCommand(e);return}let n=this.getText(),r=`/${e}${t?` ${t}`:``}`;this.setText(``);try{this.onSubmit?.(r)}finally{this.setText(n)}}modeLabel(){return this.leaderActive?`LEADER ${[`SPC`,...this.leaderPath].join(` `)}`:`INSERT`}modeBadge(){let e=` ${this.modeLabel()} `,t=this.leaderActive?`mdHeading`:a;return this.chromeTheme.inverse(this.chromeTheme.bold(this.chromeTheme.fg(t,e)))}modeNames(){let e=this.uiState.getModes();if(e.length===0)return``;let t=e.map(e=>e.label.toLowerCase()).join(` · `);return this.chromeTheme.fg(`muted`,` ${t}`)}editorStatus(){if(this.leaderActive)return`draft preserved · esc cancel`;let e=this.getText().length;return e===0?`draft 0 · ^SPC leader · SPC shortcut`:`draft ${e} · ^SPC leader`}editorLine(e,n,r){return t.frameLine(t.padLine(e,n-2),n,r,r)}findBottomBorder(e,t){let n=Math.max(1,t-10),r=e.findIndex((e,t)=>t!==0&&e.split(`─`).length-1>=n);return r>0?r:e.length-1}};exports.DoomEditor=c;
|
|
2
|
+
//# sourceMappingURL=doomEditor.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomEditor.cjs","names":["CustomEditor","DoomLeaderRegistry","parseKey","fitLine","alignLine","frameLine","padLine"],"sources":["../../src/components/doomEditor.ts"],"sourcesContent":["import { CustomEditor, type KeybindingsManager, type Theme } from '@earendil-works/pi-coding-agent';\nimport { parseKey, type EditorOptions, type EditorTheme, type TUI } from '@earendil-works/pi-tui';\nimport { DoomLeaderRegistry, type DoomLeaderAction, type DoomLeaderGroup } from '../leaderRegistry.ts';\nimport { alignLine, fitLine, frameLine, padLine } from '../rendering.ts';\nimport { type DoomUiState, type LeaderSnapshot } from '../uiState.ts';\n\nexport interface DoomEditorLeaderOptions {\n registry?: DoomLeaderRegistry;\n isCommandAvailable?: (commandName: string) => boolean;\n onUnavailableCommand?: (commandName: string) => void;\n onUnavailableAction?: (action: string) => void;\n onLeaderAction?: (source: string, actionName: string) => void;\n}\n\ntype SnapshotHandler = (snapshot: LeaderSnapshot) => void;\ntype ChromeTheme = Pick<Theme, 'bg' | 'bold' | 'fg' | 'inverse'>;\n\nconst MIN_CHROME_WIDTH = 12;\nconst EDITOR_CHROME_INSET = 4;\nconst FRAME_OVERHEAD = 2;\nconst BORDER_THRESHOLD_INSET = 10;\nconst MIN_BORDER_SEGMENTS = 1;\nconst APP_EXIT_ACTION = 'app.exit';\nconst ESCAPE_KEY = 'escape';\nconst BACKSPACE_KEY = 'backspace';\nconst SPACE_KEY = 'space';\nconst GLOBAL_LEADER_KEY = 'ctrl+space';\nconst LEADER_PREFIX = 'SPC';\nconst BORDER_COLOR = 'borderAccent';\nconst DIM_COLOR = 'dim';\nconst ACCENT_COLOR = 'accent';\nconst HEADING_COLOR = 'mdHeading';\nconst MUTED_BORDER_COLOR = 'borderMuted';\n/** Between mode names in the badge row. */\nconst MODE_SEPARATOR = ' · ';\n/** Quieter than the badge beside it: the badge is the editor's own state. */\nconst MODE_COLOR = 'muted';\nconst VERTICAL_BORDER = '│';\nconst HORIZONTAL_BORDER = '─';\nconst PLAIN_CHROME_THEME: ChromeTheme = {\n bg: (_color, text) => text,\n bold: (text) => text,\n fg: (_color, text) => text,\n inverse: (text) => text,\n};\nconst INACTIVE_SNAPSHOT: LeaderSnapshot = {\n active: false,\n prefix: [],\n label: '',\n options: [],\n};\n\nexport class DoomEditor extends CustomEditor {\n private readonly chromeTheme: ChromeTheme;\n private readonly leaderRegistry: DoomLeaderRegistry;\n private readonly isCommandAvailable: (commandName: string) => boolean;\n private readonly onUnavailableCommand: (commandName: string) => void;\n private readonly onUnavailableAction: (action: string) => void;\n private readonly onLeaderAction: ((source: string, actionName: string) => void) | undefined;\n private readonly unsubscribeLeaderRegistry: () => void;\n private readonly unsubscribeUiState: () => void;\n private leaderActive = false;\n private leaderPath: string[] = [];\n\n constructor(\n tui: TUI,\n editorTheme: EditorTheme,\n keybindings: KeybindingsManager,\n private readonly uiState: DoomUiState,\n private readonly onSnapshot: SnapshotHandler,\n options?: EditorOptions,\n chromeTheme: ChromeTheme = PLAIN_CHROME_THEME,\n leaderOptions: DoomEditorLeaderOptions = {},\n ) {\n super(tui, editorTheme, keybindings, options);\n this.chromeTheme = chromeTheme;\n this.leaderRegistry = leaderOptions.registry ?? new DoomLeaderRegistry();\n this.isCommandAvailable = leaderOptions.isCommandAvailable ?? (() => true);\n this.onUnavailableCommand = leaderOptions.onUnavailableCommand ?? (() => undefined);\n this.onUnavailableAction = leaderOptions.onUnavailableAction ?? (() => undefined);\n this.onLeaderAction = leaderOptions.onLeaderAction;\n this.unsubscribeLeaderRegistry = this.leaderRegistry.subscribe(() => {\n if (this.leaderActive) this.publishLeader();\n });\n // A mode toggling changes the badge row and nothing else about the editor,\n // so without this the new name waits for the next keystroke to appear.\n this.unsubscribeUiState = this.uiState.subscribe(() => tui.requestRender());\n }\n\n handleInput(data: string): void {\n const key = parseKey(data);\n\n if (this.leaderActive) {\n this.handleLeaderInput(key);\n return;\n }\n\n if ((key === SPACE_KEY && this.getText().length === 0) || key === GLOBAL_LEADER_KEY) {\n this.startLeader();\n return;\n }\n\n super.handleInput(data);\n }\n\n render(width: number): string[] {\n if (width < MIN_CHROME_WIDTH) return super.render(width);\n\n const editorWidth = width - EDITOR_CHROME_INSET;\n const base = super.render(editorWidth);\n if (base.length < 3) return base.map((line) => fitLine(line, width));\n\n const bottomIndex = this.findBottomBorder(base, editorWidth);\n const contentLines = base.slice(1, bottomIndex);\n const autocompleteLines = base.slice(bottomIndex + 1);\n const borderSide = this.chromeTheme.fg(BORDER_COLOR, VERTICAL_BORDER);\n const top = this.chromeTheme.fg(BORDER_COLOR, `╭${HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD)}╮`);\n const bottom = this.chromeTheme.fg(BORDER_COLOR, `╰${HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD)}╯`);\n const metadata = this.editorLine(\n alignLine(\n ` ${this.modeBadge()}${this.modeNames()}`,\n this.chromeTheme.fg(DIM_COLOR, `${this.editorStatus()} `),\n width - FRAME_OVERHEAD,\n ),\n width,\n borderSide,\n );\n const promptLines = contentLines.map((line, index) => {\n const prompt = index === 0 ? this.chromeTheme.bold(this.chromeTheme.fg(ACCENT_COLOR, '❯ ')) : ' ';\n return this.editorLine(`${prompt}${line}`, width, borderSide);\n });\n // The badge and the prompt are separate registers; a rule separates them\n // without the mode label reading as a caption on the input. Junctions take\n // the frame colour so the border stays unbroken through the row.\n const divider = `${this.chromeTheme.fg(BORDER_COLOR, '├')}${this.chromeTheme.fg(\n MUTED_BORDER_COLOR,\n HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD),\n )}${this.chromeTheme.fg(BORDER_COLOR, '┤')}`;\n\n return [\n top,\n metadata,\n divider,\n ...promptLines,\n bottom,\n ...autocompleteLines.map((line) => fitLine(` ${line}`, width)),\n ];\n }\n\n dispose(): void {\n this.unsubscribeLeaderRegistry();\n this.unsubscribeUiState();\n this.cancelLeader();\n }\n\n private startLeader(): void {\n this.leaderActive = true;\n this.leaderPath = [];\n this.publishLeader();\n }\n\n private cancelLeader(): void {\n this.leaderActive = false;\n this.leaderPath = [];\n this.publish(INACTIVE_SNAPSHOT);\n }\n\n private handleLeaderInput(key: string | undefined): void {\n if (key === ESCAPE_KEY) {\n this.cancelLeader();\n return;\n }\n if (key === BACKSPACE_KEY) {\n if (this.leaderPath.length === 0) this.cancelLeader();\n else {\n this.leaderPath.pop();\n this.publishLeader();\n }\n return;\n }\n if (!key || key.length !== 1) {\n this.cancelLeader();\n return;\n }\n\n const lowerKey = key.toLowerCase();\n const option = this.currentGroup()?.options.find((candidate) => candidate.key === lowerKey);\n if (!option) {\n this.cancelLeader();\n return;\n }\n if (option.hasChildren) {\n this.leaderPath.push(lowerKey);\n this.publishLeader();\n return;\n }\n if (!option.action) {\n this.cancelLeader();\n return;\n }\n\n this.cancelLeader();\n this.execute(option.action);\n }\n\n private currentGroup(): DoomLeaderGroup | undefined {\n return this.leaderRegistry.getGroup(this.leaderPath);\n }\n\n private publishLeader(): void {\n const group = this.currentGroup();\n if (!group) {\n this.cancelLeader();\n return;\n }\n const root = this.leaderRegistry.getGroup([]);\n this.publish({\n active: true,\n prefix: [LEADER_PREFIX, ...this.leaderPath],\n label: group.label,\n options: group.options.map((option) => ({\n key: option.key,\n label: option.label,\n ...(option.detail ? { detail: option.detail } : {}),\n })),\n rootOptions:\n root?.options.map((option) => ({\n key: option.key,\n label: option.label,\n ...(option.detail ? { detail: option.detail } : {}),\n })) ?? [],\n });\n }\n\n private publish(snapshot: LeaderSnapshot): void {\n this.uiState.setLeader(snapshot);\n this.onSnapshot(snapshot);\n this.tui.requestRender();\n }\n\n private execute(action: DoomLeaderAction): void {\n if (action.type === 'app') {\n const handler = this.actionHandlers.get(action.action);\n if (handler) {\n handler();\n return;\n }\n if (action.action === APP_EXIT_ACTION) {\n this.onCtrlD?.();\n return;\n }\n this.onUnavailableAction(action.action);\n return;\n }\n if (action.type === 'command') {\n this.submitCommand(action.command.name, action.command.args);\n return;\n }\n if (this.onLeaderAction) this.onLeaderAction(action.source, action.action.name);\n else this.onUnavailableAction(`${action.source}:${action.action.name}`);\n }\n\n private submitCommand(commandName: string, args?: string): void {\n if (!this.isCommandAvailable(commandName)) {\n this.onUnavailableCommand(commandName);\n return;\n }\n const draft = this.getText();\n const command = `/${commandName}${args ? ` ${args}` : ''}`;\n this.setText('');\n try {\n this.onSubmit?.(command);\n } finally {\n this.setText(draft);\n }\n }\n\n private modeLabel(): string {\n if (this.leaderActive) return `LEADER ${[LEADER_PREFIX, ...this.leaderPath].join(' ')}`;\n return 'INSERT';\n }\n\n private modeBadge(): string {\n const label = ` ${this.modeLabel()} `;\n const color = this.leaderActive ? HEADING_COLOR : ACCENT_COLOR;\n return this.chromeTheme.inverse(this.chromeTheme.bold(this.chromeTheme.fg(color, label)));\n }\n\n /**\n * The enabled modes, names only, beside the editor's own mode badge.\n *\n * Names only because this row is glanced at, not read: which modes are on is\n * the whole question here, and their sub-state belongs where there is room to\n * explain it. The leader panel carries that longer form.\n *\n * Empty when nothing is on, so an ordinary session pays no columns for a\n * feature it is not using.\n */\n private modeNames(): string {\n const modes = this.uiState.getModes();\n if (modes.length === 0) return '';\n const names = modes.map((mode) => mode.label.toLowerCase()).join(MODE_SEPARATOR);\n return this.chromeTheme.fg(MODE_COLOR, ` ${names}`);\n }\n\n private editorStatus(): string {\n if (this.leaderActive) return 'draft preserved · esc cancel';\n const draftLength = this.getText().length;\n return draftLength === 0 ? 'draft 0 · ^SPC leader · SPC shortcut' : `draft ${draftLength} · ^SPC leader`;\n }\n\n private editorLine(content: string, width: number, side: string): string {\n // No background fill: the frame alone delimits the editor, and a painted\n // interior reads as a shadow behind the prompt.\n return frameLine(padLine(content, width - FRAME_OVERHEAD), width, side, side);\n }\n\n private findBottomBorder(lines: string[], editorWidth: number): number {\n const threshold = Math.max(MIN_BORDER_SEGMENTS, editorWidth - BORDER_THRESHOLD_INSET);\n const index = lines.findIndex((line, lineIndex) => {\n if (lineIndex === 0) return false;\n return line.split(HORIZONTAL_BORDER).length - MIN_BORDER_SEGMENTS >= threshold;\n });\n return index > 0 ? index : lines.length - 1;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DoomLeaderRegistry } from "../leaderRegistry.cjs";
|
|
2
|
+
import { DoomUiState, LeaderSnapshot } from "../uiState.cjs";
|
|
3
|
+
import { CustomEditor, KeybindingsManager, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { EditorOptions, EditorTheme, TUI } from "@earendil-works/pi-tui";
|
|
5
|
+
//#region src/components/doomEditor.d.ts
|
|
6
|
+
interface DoomEditorLeaderOptions {
|
|
7
|
+
registry?: DoomLeaderRegistry;
|
|
8
|
+
isCommandAvailable?: (commandName: string) => boolean;
|
|
9
|
+
onUnavailableCommand?: (commandName: string) => void;
|
|
10
|
+
onUnavailableAction?: (action: string) => void;
|
|
11
|
+
onLeaderAction?: (source: string, actionName: string) => void;
|
|
12
|
+
}
|
|
13
|
+
type SnapshotHandler = (snapshot: LeaderSnapshot) => void;
|
|
14
|
+
type ChromeTheme = Pick<Theme, 'bg' | 'bold' | 'fg' | 'inverse'>;
|
|
15
|
+
declare class DoomEditor extends CustomEditor {
|
|
16
|
+
private readonly uiState;
|
|
17
|
+
private readonly onSnapshot;
|
|
18
|
+
private readonly chromeTheme;
|
|
19
|
+
private readonly leaderRegistry;
|
|
20
|
+
private readonly isCommandAvailable;
|
|
21
|
+
private readonly onUnavailableCommand;
|
|
22
|
+
private readonly onUnavailableAction;
|
|
23
|
+
private readonly onLeaderAction;
|
|
24
|
+
private readonly unsubscribeLeaderRegistry;
|
|
25
|
+
private readonly unsubscribeUiState;
|
|
26
|
+
private leaderActive;
|
|
27
|
+
private leaderPath;
|
|
28
|
+
constructor(tui: TUI, editorTheme: EditorTheme, keybindings: KeybindingsManager, uiState: DoomUiState, onSnapshot: SnapshotHandler, options?: EditorOptions, chromeTheme?: ChromeTheme, leaderOptions?: DoomEditorLeaderOptions);
|
|
29
|
+
handleInput(data: string): void;
|
|
30
|
+
render(width: number): string[];
|
|
31
|
+
dispose(): void;
|
|
32
|
+
private startLeader;
|
|
33
|
+
private cancelLeader;
|
|
34
|
+
private handleLeaderInput;
|
|
35
|
+
private currentGroup;
|
|
36
|
+
private publishLeader;
|
|
37
|
+
private publish;
|
|
38
|
+
private execute;
|
|
39
|
+
private submitCommand;
|
|
40
|
+
private modeLabel;
|
|
41
|
+
private modeBadge;
|
|
42
|
+
/**
|
|
43
|
+
* The enabled modes, names only, beside the editor's own mode badge.
|
|
44
|
+
*
|
|
45
|
+
* Names only because this row is glanced at, not read: which modes are on is
|
|
46
|
+
* the whole question here, and their sub-state belongs where there is room to
|
|
47
|
+
* explain it. The leader panel carries that longer form.
|
|
48
|
+
*
|
|
49
|
+
* Empty when nothing is on, so an ordinary session pays no columns for a
|
|
50
|
+
* feature it is not using.
|
|
51
|
+
*/
|
|
52
|
+
private modeNames;
|
|
53
|
+
private editorStatus;
|
|
54
|
+
private editorLine;
|
|
55
|
+
private findBottomBorder;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { DoomEditor, DoomEditorLeaderOptions };
|
|
59
|
+
//# sourceMappingURL=doomEditor.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomEditor.d.cts","names":[],"sources":["../../src/components/doomEditor.ts"],"mappings":";;;;;UAMiB;EACf,WAAW;EACX,sBAAsB;EACtB,wBAAwB;EACxB,uBAAuB;EACvB,kBAAkB,gBAAgB;;KAG/B,mBAAmB,UAAU;KAC7B,cAAc,KAAK;cAqCX,mBAAmB;mBAgBX;mBACA;mBAhBF;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;UACA;EAGN,YAAA,KAAK,KACL,aAAa,aACb,aAAa,oBACI,SAAS,aACT,YAAY,iBAC7B,UAAU,eACV,cAAa,aACb,gBAAe;EAiBjB,YAAY;EAgBZ,OAAO;EA4CP;UAMQ;UAMA;UAMA;UAsCA;UAIA;UAyBA;UAMA;UAsBA;UAeA;UAKA;;;;;;;;;;;UAgBA;UAOA;UAMA;UAMA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DoomLeaderRegistry } from "../leaderRegistry.mjs";
|
|
2
|
+
import { DoomUiState, LeaderSnapshot } from "../uiState.mjs";
|
|
3
|
+
import { CustomEditor, KeybindingsManager, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { EditorOptions, EditorTheme, TUI } from "@earendil-works/pi-tui";
|
|
5
|
+
//#region src/components/doomEditor.d.ts
|
|
6
|
+
interface DoomEditorLeaderOptions {
|
|
7
|
+
registry?: DoomLeaderRegistry;
|
|
8
|
+
isCommandAvailable?: (commandName: string) => boolean;
|
|
9
|
+
onUnavailableCommand?: (commandName: string) => void;
|
|
10
|
+
onUnavailableAction?: (action: string) => void;
|
|
11
|
+
onLeaderAction?: (source: string, actionName: string) => void;
|
|
12
|
+
}
|
|
13
|
+
type SnapshotHandler = (snapshot: LeaderSnapshot) => void;
|
|
14
|
+
type ChromeTheme = Pick<Theme, 'bg' | 'bold' | 'fg' | 'inverse'>;
|
|
15
|
+
declare class DoomEditor extends CustomEditor {
|
|
16
|
+
private readonly uiState;
|
|
17
|
+
private readonly onSnapshot;
|
|
18
|
+
private readonly chromeTheme;
|
|
19
|
+
private readonly leaderRegistry;
|
|
20
|
+
private readonly isCommandAvailable;
|
|
21
|
+
private readonly onUnavailableCommand;
|
|
22
|
+
private readonly onUnavailableAction;
|
|
23
|
+
private readonly onLeaderAction;
|
|
24
|
+
private readonly unsubscribeLeaderRegistry;
|
|
25
|
+
private readonly unsubscribeUiState;
|
|
26
|
+
private leaderActive;
|
|
27
|
+
private leaderPath;
|
|
28
|
+
constructor(tui: TUI, editorTheme: EditorTheme, keybindings: KeybindingsManager, uiState: DoomUiState, onSnapshot: SnapshotHandler, options?: EditorOptions, chromeTheme?: ChromeTheme, leaderOptions?: DoomEditorLeaderOptions);
|
|
29
|
+
handleInput(data: string): void;
|
|
30
|
+
render(width: number): string[];
|
|
31
|
+
dispose(): void;
|
|
32
|
+
private startLeader;
|
|
33
|
+
private cancelLeader;
|
|
34
|
+
private handleLeaderInput;
|
|
35
|
+
private currentGroup;
|
|
36
|
+
private publishLeader;
|
|
37
|
+
private publish;
|
|
38
|
+
private execute;
|
|
39
|
+
private submitCommand;
|
|
40
|
+
private modeLabel;
|
|
41
|
+
private modeBadge;
|
|
42
|
+
/**
|
|
43
|
+
* The enabled modes, names only, beside the editor's own mode badge.
|
|
44
|
+
*
|
|
45
|
+
* Names only because this row is glanced at, not read: which modes are on is
|
|
46
|
+
* the whole question here, and their sub-state belongs where there is room to
|
|
47
|
+
* explain it. The leader panel carries that longer form.
|
|
48
|
+
*
|
|
49
|
+
* Empty when nothing is on, so an ordinary session pays no columns for a
|
|
50
|
+
* feature it is not using.
|
|
51
|
+
*/
|
|
52
|
+
private modeNames;
|
|
53
|
+
private editorStatus;
|
|
54
|
+
private editorLine;
|
|
55
|
+
private findBottomBorder;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { DoomEditor, DoomEditorLeaderOptions };
|
|
59
|
+
//# sourceMappingURL=doomEditor.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomEditor.d.mts","names":[],"sources":["../../src/components/doomEditor.ts"],"mappings":";;;;;UAMiB;EACf,WAAW;EACX,sBAAsB;EACtB,wBAAwB;EACxB,uBAAuB;EACvB,kBAAkB,gBAAgB;;KAG/B,mBAAmB,UAAU;KAC7B,cAAc,KAAK;cAqCX,mBAAmB;mBAgBX;mBACA;mBAhBF;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;UACA;EAGN,YAAA,KAAK,KACL,aAAa,aACb,aAAa,oBACI,SAAS,aACT,YAAY,iBAC7B,UAAU,eACV,cAAa,aACb,gBAAe;EAiBjB,YAAY;EAgBZ,OAAO;EA4CP;UAMQ;UAMA;UAMA;UAsCA;UAIA;UAyBA;UAMA;UAsBA;UAeA;UAKA;;;;;;;;;;;UAgBA;UAOA;UAMA;UAMA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{DoomLeaderRegistry as e}from"../leaderRegistry.mjs";import{alignLine as t,fitLine as n,frameLine as r,padLine as i}from"../rendering.mjs";import{CustomEditor as a}from"@earendil-works/pi-coding-agent";import{parseKey as o}from"@earendil-works/pi-tui";const s=`borderAccent`,c=`accent`,l={bg:(e,t)=>t,bold:e=>e,fg:(e,t)=>t,inverse:e=>e},u={active:!1,prefix:[],label:``,options:[]};var d=class extends a{uiState;onSnapshot;chromeTheme;leaderRegistry;isCommandAvailable;onUnavailableCommand;onUnavailableAction;onLeaderAction;unsubscribeLeaderRegistry;unsubscribeUiState;leaderActive=!1;leaderPath=[];constructor(t,n,r,i,a,o,s=l,c={}){super(t,n,r,o),this.uiState=i,this.onSnapshot=a,this.chromeTheme=s,this.leaderRegistry=c.registry??new e,this.isCommandAvailable=c.isCommandAvailable??(()=>!0),this.onUnavailableCommand=c.onUnavailableCommand??(()=>void 0),this.onUnavailableAction=c.onUnavailableAction??(()=>void 0),this.onLeaderAction=c.onLeaderAction,this.unsubscribeLeaderRegistry=this.leaderRegistry.subscribe(()=>{this.leaderActive&&this.publishLeader()}),this.unsubscribeUiState=this.uiState.subscribe(()=>t.requestRender())}handleInput(e){let t=o(e);if(this.leaderActive){this.handleLeaderInput(t);return}if(t===`space`&&this.getText().length===0||t===`ctrl+space`){this.startLeader();return}super.handleInput(e)}render(e){if(e<12)return super.render(e);let r=e-4,i=super.render(r);if(i.length<3)return i.map(t=>n(t,e));let a=this.findBottomBorder(i,r),o=i.slice(1,a),l=i.slice(a+1),u=this.chromeTheme.fg(s,`│`),d=this.chromeTheme.fg(s,`╭${`─`.repeat(e-2)}╮`),f=this.chromeTheme.fg(s,`╰${`─`.repeat(e-2)}╯`),p=this.editorLine(t(` ${this.modeBadge()}${this.modeNames()}`,this.chromeTheme.fg(`dim`,`${this.editorStatus()} `),e-2),e,u),m=o.map((t,n)=>{let r=n===0?this.chromeTheme.bold(this.chromeTheme.fg(c,`❯ `)):` `;return this.editorLine(`${r}${t}`,e,u)});return[d,p,`${this.chromeTheme.fg(s,`├`)}${this.chromeTheme.fg(`borderMuted`,`─`.repeat(e-2))}${this.chromeTheme.fg(s,`┤`)}`,...m,f,...l.map(t=>n(` ${t}`,e))]}dispose(){this.unsubscribeLeaderRegistry(),this.unsubscribeUiState(),this.cancelLeader()}startLeader(){this.leaderActive=!0,this.leaderPath=[],this.publishLeader()}cancelLeader(){this.leaderActive=!1,this.leaderPath=[],this.publish(u)}handleLeaderInput(e){if(e===`escape`){this.cancelLeader();return}if(e===`backspace`){this.leaderPath.length===0?this.cancelLeader():(this.leaderPath.pop(),this.publishLeader());return}if(!e||e.length!==1){this.cancelLeader();return}let t=e.toLowerCase(),n=this.currentGroup()?.options.find(e=>e.key===t);if(!n){this.cancelLeader();return}if(n.hasChildren){this.leaderPath.push(t),this.publishLeader();return}if(!n.action){this.cancelLeader();return}this.cancelLeader(),this.execute(n.action)}currentGroup(){return this.leaderRegistry.getGroup(this.leaderPath)}publishLeader(){let e=this.currentGroup();if(!e){this.cancelLeader();return}let t=this.leaderRegistry.getGroup([]);this.publish({active:!0,prefix:[`SPC`,...this.leaderPath],label:e.label,options:e.options.map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{}})),rootOptions:t?.options.map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{}}))??[]})}publish(e){this.uiState.setLeader(e),this.onSnapshot(e),this.tui.requestRender()}execute(e){if(e.type===`app`){let t=this.actionHandlers.get(e.action);if(t){t();return}if(e.action===`app.exit`){this.onCtrlD?.();return}this.onUnavailableAction(e.action);return}if(e.type===`command`){this.submitCommand(e.command.name,e.command.args);return}this.onLeaderAction?this.onLeaderAction(e.source,e.action.name):this.onUnavailableAction(`${e.source}:${e.action.name}`)}submitCommand(e,t){if(!this.isCommandAvailable(e)){this.onUnavailableCommand(e);return}let n=this.getText(),r=`/${e}${t?` ${t}`:``}`;this.setText(``);try{this.onSubmit?.(r)}finally{this.setText(n)}}modeLabel(){return this.leaderActive?`LEADER ${[`SPC`,...this.leaderPath].join(` `)}`:`INSERT`}modeBadge(){let e=` ${this.modeLabel()} `,t=this.leaderActive?`mdHeading`:c;return this.chromeTheme.inverse(this.chromeTheme.bold(this.chromeTheme.fg(t,e)))}modeNames(){let e=this.uiState.getModes();if(e.length===0)return``;let t=e.map(e=>e.label.toLowerCase()).join(` · `);return this.chromeTheme.fg(`muted`,` ${t}`)}editorStatus(){if(this.leaderActive)return`draft preserved · esc cancel`;let e=this.getText().length;return e===0?`draft 0 · ^SPC leader · SPC shortcut`:`draft ${e} · ^SPC leader`}editorLine(e,t,n){return r(i(e,t-2),t,n,n)}findBottomBorder(e,t){let n=Math.max(1,t-10),r=e.findIndex((e,t)=>t!==0&&e.split(`─`).length-1>=n);return r>0?r:e.length-1}};export{d as DoomEditor};
|
|
2
|
+
//# sourceMappingURL=doomEditor.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomEditor.mjs","names":[],"sources":["../../src/components/doomEditor.ts"],"sourcesContent":["import { CustomEditor, type KeybindingsManager, type Theme } from '@earendil-works/pi-coding-agent';\nimport { parseKey, type EditorOptions, type EditorTheme, type TUI } from '@earendil-works/pi-tui';\nimport { DoomLeaderRegistry, type DoomLeaderAction, type DoomLeaderGroup } from '../leaderRegistry.ts';\nimport { alignLine, fitLine, frameLine, padLine } from '../rendering.ts';\nimport { type DoomUiState, type LeaderSnapshot } from '../uiState.ts';\n\nexport interface DoomEditorLeaderOptions {\n registry?: DoomLeaderRegistry;\n isCommandAvailable?: (commandName: string) => boolean;\n onUnavailableCommand?: (commandName: string) => void;\n onUnavailableAction?: (action: string) => void;\n onLeaderAction?: (source: string, actionName: string) => void;\n}\n\ntype SnapshotHandler = (snapshot: LeaderSnapshot) => void;\ntype ChromeTheme = Pick<Theme, 'bg' | 'bold' | 'fg' | 'inverse'>;\n\nconst MIN_CHROME_WIDTH = 12;\nconst EDITOR_CHROME_INSET = 4;\nconst FRAME_OVERHEAD = 2;\nconst BORDER_THRESHOLD_INSET = 10;\nconst MIN_BORDER_SEGMENTS = 1;\nconst APP_EXIT_ACTION = 'app.exit';\nconst ESCAPE_KEY = 'escape';\nconst BACKSPACE_KEY = 'backspace';\nconst SPACE_KEY = 'space';\nconst GLOBAL_LEADER_KEY = 'ctrl+space';\nconst LEADER_PREFIX = 'SPC';\nconst BORDER_COLOR = 'borderAccent';\nconst DIM_COLOR = 'dim';\nconst ACCENT_COLOR = 'accent';\nconst HEADING_COLOR = 'mdHeading';\nconst MUTED_BORDER_COLOR = 'borderMuted';\n/** Between mode names in the badge row. */\nconst MODE_SEPARATOR = ' · ';\n/** Quieter than the badge beside it: the badge is the editor's own state. */\nconst MODE_COLOR = 'muted';\nconst VERTICAL_BORDER = '│';\nconst HORIZONTAL_BORDER = '─';\nconst PLAIN_CHROME_THEME: ChromeTheme = {\n bg: (_color, text) => text,\n bold: (text) => text,\n fg: (_color, text) => text,\n inverse: (text) => text,\n};\nconst INACTIVE_SNAPSHOT: LeaderSnapshot = {\n active: false,\n prefix: [],\n label: '',\n options: [],\n};\n\nexport class DoomEditor extends CustomEditor {\n private readonly chromeTheme: ChromeTheme;\n private readonly leaderRegistry: DoomLeaderRegistry;\n private readonly isCommandAvailable: (commandName: string) => boolean;\n private readonly onUnavailableCommand: (commandName: string) => void;\n private readonly onUnavailableAction: (action: string) => void;\n private readonly onLeaderAction: ((source: string, actionName: string) => void) | undefined;\n private readonly unsubscribeLeaderRegistry: () => void;\n private readonly unsubscribeUiState: () => void;\n private leaderActive = false;\n private leaderPath: string[] = [];\n\n constructor(\n tui: TUI,\n editorTheme: EditorTheme,\n keybindings: KeybindingsManager,\n private readonly uiState: DoomUiState,\n private readonly onSnapshot: SnapshotHandler,\n options?: EditorOptions,\n chromeTheme: ChromeTheme = PLAIN_CHROME_THEME,\n leaderOptions: DoomEditorLeaderOptions = {},\n ) {\n super(tui, editorTheme, keybindings, options);\n this.chromeTheme = chromeTheme;\n this.leaderRegistry = leaderOptions.registry ?? new DoomLeaderRegistry();\n this.isCommandAvailable = leaderOptions.isCommandAvailable ?? (() => true);\n this.onUnavailableCommand = leaderOptions.onUnavailableCommand ?? (() => undefined);\n this.onUnavailableAction = leaderOptions.onUnavailableAction ?? (() => undefined);\n this.onLeaderAction = leaderOptions.onLeaderAction;\n this.unsubscribeLeaderRegistry = this.leaderRegistry.subscribe(() => {\n if (this.leaderActive) this.publishLeader();\n });\n // A mode toggling changes the badge row and nothing else about the editor,\n // so without this the new name waits for the next keystroke to appear.\n this.unsubscribeUiState = this.uiState.subscribe(() => tui.requestRender());\n }\n\n handleInput(data: string): void {\n const key = parseKey(data);\n\n if (this.leaderActive) {\n this.handleLeaderInput(key);\n return;\n }\n\n if ((key === SPACE_KEY && this.getText().length === 0) || key === GLOBAL_LEADER_KEY) {\n this.startLeader();\n return;\n }\n\n super.handleInput(data);\n }\n\n render(width: number): string[] {\n if (width < MIN_CHROME_WIDTH) return super.render(width);\n\n const editorWidth = width - EDITOR_CHROME_INSET;\n const base = super.render(editorWidth);\n if (base.length < 3) return base.map((line) => fitLine(line, width));\n\n const bottomIndex = this.findBottomBorder(base, editorWidth);\n const contentLines = base.slice(1, bottomIndex);\n const autocompleteLines = base.slice(bottomIndex + 1);\n const borderSide = this.chromeTheme.fg(BORDER_COLOR, VERTICAL_BORDER);\n const top = this.chromeTheme.fg(BORDER_COLOR, `╭${HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD)}╮`);\n const bottom = this.chromeTheme.fg(BORDER_COLOR, `╰${HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD)}╯`);\n const metadata = this.editorLine(\n alignLine(\n ` ${this.modeBadge()}${this.modeNames()}`,\n this.chromeTheme.fg(DIM_COLOR, `${this.editorStatus()} `),\n width - FRAME_OVERHEAD,\n ),\n width,\n borderSide,\n );\n const promptLines = contentLines.map((line, index) => {\n const prompt = index === 0 ? this.chromeTheme.bold(this.chromeTheme.fg(ACCENT_COLOR, '❯ ')) : ' ';\n return this.editorLine(`${prompt}${line}`, width, borderSide);\n });\n // The badge and the prompt are separate registers; a rule separates them\n // without the mode label reading as a caption on the input. Junctions take\n // the frame colour so the border stays unbroken through the row.\n const divider = `${this.chromeTheme.fg(BORDER_COLOR, '├')}${this.chromeTheme.fg(\n MUTED_BORDER_COLOR,\n HORIZONTAL_BORDER.repeat(width - FRAME_OVERHEAD),\n )}${this.chromeTheme.fg(BORDER_COLOR, '┤')}`;\n\n return [\n top,\n metadata,\n divider,\n ...promptLines,\n bottom,\n ...autocompleteLines.map((line) => fitLine(` ${line}`, width)),\n ];\n }\n\n dispose(): void {\n this.unsubscribeLeaderRegistry();\n this.unsubscribeUiState();\n this.cancelLeader();\n }\n\n private startLeader(): void {\n this.leaderActive = true;\n this.leaderPath = [];\n this.publishLeader();\n }\n\n private cancelLeader(): void {\n this.leaderActive = false;\n this.leaderPath = [];\n this.publish(INACTIVE_SNAPSHOT);\n }\n\n private handleLeaderInput(key: string | undefined): void {\n if (key === ESCAPE_KEY) {\n this.cancelLeader();\n return;\n }\n if (key === BACKSPACE_KEY) {\n if (this.leaderPath.length === 0) this.cancelLeader();\n else {\n this.leaderPath.pop();\n this.publishLeader();\n }\n return;\n }\n if (!key || key.length !== 1) {\n this.cancelLeader();\n return;\n }\n\n const lowerKey = key.toLowerCase();\n const option = this.currentGroup()?.options.find((candidate) => candidate.key === lowerKey);\n if (!option) {\n this.cancelLeader();\n return;\n }\n if (option.hasChildren) {\n this.leaderPath.push(lowerKey);\n this.publishLeader();\n return;\n }\n if (!option.action) {\n this.cancelLeader();\n return;\n }\n\n this.cancelLeader();\n this.execute(option.action);\n }\n\n private currentGroup(): DoomLeaderGroup | undefined {\n return this.leaderRegistry.getGroup(this.leaderPath);\n }\n\n private publishLeader(): void {\n const group = this.currentGroup();\n if (!group) {\n this.cancelLeader();\n return;\n }\n const root = this.leaderRegistry.getGroup([]);\n this.publish({\n active: true,\n prefix: [LEADER_PREFIX, ...this.leaderPath],\n label: group.label,\n options: group.options.map((option) => ({\n key: option.key,\n label: option.label,\n ...(option.detail ? { detail: option.detail } : {}),\n })),\n rootOptions:\n root?.options.map((option) => ({\n key: option.key,\n label: option.label,\n ...(option.detail ? { detail: option.detail } : {}),\n })) ?? [],\n });\n }\n\n private publish(snapshot: LeaderSnapshot): void {\n this.uiState.setLeader(snapshot);\n this.onSnapshot(snapshot);\n this.tui.requestRender();\n }\n\n private execute(action: DoomLeaderAction): void {\n if (action.type === 'app') {\n const handler = this.actionHandlers.get(action.action);\n if (handler) {\n handler();\n return;\n }\n if (action.action === APP_EXIT_ACTION) {\n this.onCtrlD?.();\n return;\n }\n this.onUnavailableAction(action.action);\n return;\n }\n if (action.type === 'command') {\n this.submitCommand(action.command.name, action.command.args);\n return;\n }\n if (this.onLeaderAction) this.onLeaderAction(action.source, action.action.name);\n else this.onUnavailableAction(`${action.source}:${action.action.name}`);\n }\n\n private submitCommand(commandName: string, args?: string): void {\n if (!this.isCommandAvailable(commandName)) {\n this.onUnavailableCommand(commandName);\n return;\n }\n const draft = this.getText();\n const command = `/${commandName}${args ? ` ${args}` : ''}`;\n this.setText('');\n try {\n this.onSubmit?.(command);\n } finally {\n this.setText(draft);\n }\n }\n\n private modeLabel(): string {\n if (this.leaderActive) return `LEADER ${[LEADER_PREFIX, ...this.leaderPath].join(' ')}`;\n return 'INSERT';\n }\n\n private modeBadge(): string {\n const label = ` ${this.modeLabel()} `;\n const color = this.leaderActive ? HEADING_COLOR : ACCENT_COLOR;\n return this.chromeTheme.inverse(this.chromeTheme.bold(this.chromeTheme.fg(color, label)));\n }\n\n /**\n * The enabled modes, names only, beside the editor's own mode badge.\n *\n * Names only because this row is glanced at, not read: which modes are on is\n * the whole question here, and their sub-state belongs where there is room to\n * explain it. The leader panel carries that longer form.\n *\n * Empty when nothing is on, so an ordinary session pays no columns for a\n * feature it is not using.\n */\n private modeNames(): string {\n const modes = this.uiState.getModes();\n if (modes.length === 0) return '';\n const names = modes.map((mode) => mode.label.toLowerCase()).join(MODE_SEPARATOR);\n return this.chromeTheme.fg(MODE_COLOR, ` ${names}`);\n }\n\n private editorStatus(): string {\n if (this.leaderActive) return 'draft preserved · esc cancel';\n const draftLength = this.getText().length;\n return draftLength === 0 ? 'draft 0 · ^SPC leader · SPC shortcut' : `draft ${draftLength} · ^SPC leader`;\n }\n\n private editorLine(content: string, width: number, side: string): string {\n // No background fill: the frame alone delimits the editor, and a painted\n // interior reads as a shadow behind the prompt.\n return frameLine(padLine(content, width - FRAME_OVERHEAD), width, side, side);\n }\n\n private findBottomBorder(lines: string[], editorWidth: number): number {\n const threshold = Math.max(MIN_BORDER_SEGMENTS, editorWidth - BORDER_THRESHOLD_INSET);\n const index = lines.findIndex((line, lineIndex) => {\n if (lineIndex === 0) return false;\n return line.split(HORIZONTAL_BORDER).length - MIN_BORDER_SEGMENTS >= threshold;\n });\n return index > 0 ? index : lines.length - 1;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../rendering.cjs");let n=require("@earendil-works/pi-tui"),r=require("node:path");r=e.__toESM(r,1);const i=` · `,a=`muted`,o=`toolPendingBg`,s=`userMessageBg`,c=`modeline`,l={off:`thinkingOff`,minimal:`thinkingMinimal`,low:`thinkingLow`,medium:`thinkingMedium`,high:`thinkingHigh`,xhigh:`thinkingXhigh`,max:`thinkingMax`};function u(e){return e.replace(/[\r\n\t]+/gu,` `).replace(/ {2,}/gu,` `).trim()}var d=class{theme;context;footerData;footerStatuses;uiState;unsubscribeBranch;unsubscribeFooterStatuses;unsubscribeState;constructor(e,t,n,r,i,a){this.theme=t,this.context=n,this.footerData=r,this.footerStatuses=i,this.uiState=a,this.unsubscribeBranch=r.onBranchChange(()=>e.requestRender()),this.unsubscribeFooterStatuses=i.subscribe(()=>e.requestRender()),this.unsubscribeState=a.subscribe(()=>e.requestRender())}dispose(){this.unsubscribeBranch(),this.unsubscribeFooterStatuses(),this.unsubscribeState()}invalidate(){}render(e){if(e<=0)return[];let i=e<90,a=this.uiState.getSnapshot(),s=a.active?a.prefix.join(` `):i?`I`:`INSERT`,c=a.active?this.theme.inverse(this.theme.bold(this.theme.fg(`mdHeading`,` ${s} `))):this.theme.inverse(this.theme.bold(this.theme.fg(`accent`,` ${s} `))),l=r.default.basename(this.context.cwd)||this.context.cwd,u=this.footerData.getGitBranch(),d=`${c}${this.locationSegment(l,u,i)}`,f=this.footerStatusSummary(`beforeModel`,i),p=this.runtimeSegment(i),m=this.runtimeSegment(i,f),h=f&&(0,n.visibleWidth)(c)+(0,n.visibleWidth)(m)<=e?m:p;if((0,n.visibleWidth)(d)+(0,n.visibleWidth)(h)>e)return[t.fitLine(`${c}${h}`,e)];let g=e-(0,n.visibleWidth)(d)-(0,n.visibleWidth)(h),_=this.modelineSummary(Math.max(0,g-1),i),v=this.theme.bg(o,t.padLine(` ${_}`,g));return[t.fitLine(`${d}${v}${h}`,e)]}locationSegment(e,t,n){let r=this.theme.bg(s,this.theme.fg(`text`,` ${e}`));if(!t)return`${r}${this.theme.bg(s,` `)}`;let i=n?` `:` `;return`${r}${this.theme.bg(s,this.theme.fg(`success`,`${i}${t} `))}`}runtimeSegment(e,n=``){let r=this.context.getContextUsage()?.percent,a=r==null?e?`?`:`ctx ?`:e?`${Math.round(r)}%`:`ctx ${Math.round(r)}%`,s=e?18:30,c=t.fitLine(this.compactModel(this.context.model?.id??`no-model`),s),l=this.context.thinkingLevel??`off`,u=[...n?[n]:[],this.theme.fg(`warning`,c),this.theme.fg(this.thinkingColor(l),l),this.theme.fg(this.contextColor(r),a)].join(i);return this.theme.bg(o,` ${u} `)}thinkingColor(e){return l[e]??`thinkingText`}contextColor(e){return e==null?a:e>=85?`error`:e>=60?`warning`:a}modelineSummary(e,n){return e<=0?``:t.fitLine(this.footerStatusSummary(c,n),e)}footerStatusSummary(e,t){return this.footerStatuses.getStatuses().filter(t=>(t.placement??c)===e).map(e=>{let n=t?e.compactSegments:e.fullSegments;return n?.length?this.renderFooterSegments(n):this.theme.fg(`dim`,u(t?e.compactText:e.fullText))}).filter(Boolean).join(this.theme.fg(`dim`,i))}renderFooterSegments(e){return e.map(e=>{let t=e.text.replace(/[\r\n\t]+/gu,` `);return this.theme.fg(e.color??`dim`,t)}).join(``)}compactModel(e){return(e.split(`/`).at(-1)??e).replace(/^claude-/,``)}};exports.DoomFooter=d;
|
|
2
|
+
//# sourceMappingURL=doomFooter.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomFooter.cjs","names":["path","visibleWidth","fitLine","padLine"],"sources":["../../src/components/doomFooter.ts"],"sourcesContent":["import path from 'node:path';\nimport type { FooterStatusPlacement, FooterTextSegment } from '@agimon-ai/doompi-extension-contracts/footer';\nimport type { ExtensionContext, ReadonlyFooterDataProvider, Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\nimport { visibleWidth, type Component, type TUI } from '@earendil-works/pi-tui';\nimport type { DoomFooterStatusRegistry } from '../footer.ts';\nimport { fitLine, padLine } from '../rendering.ts';\nimport type { DoomUiState } from '../uiState.ts';\n\nconst NARROW_FOOTER_WIDTH = 90;\nconst SEGMENT_SEPARATOR = ' · ';\nconst DIM_COLOR = 'dim';\nconst MUTED_COLOR = 'muted';\nconst TOOL_PENDING_BACKGROUND = 'toolPendingBg';\nconst USER_MESSAGE_BACKGROUND = 'userMessageBg';\nconst DEFAULT_THINKING_LEVEL = 'off';\nconst MODELINE_PLACEMENT = 'modeline' satisfies FooterStatusPlacement;\nconst BEFORE_MODEL_PLACEMENT = 'beforeModel' satisfies FooterStatusPlacement;\n// The theme ships a rising ramp for effort, ending at red for max.\nconst THINKING_COLORS: Record<string, ThemeColor> = {\n off: 'thinkingOff',\n minimal: 'thinkingMinimal',\n low: 'thinkingLow',\n medium: 'thinkingMedium',\n high: 'thinkingHigh',\n xhigh: 'thinkingXhigh',\n max: 'thinkingMax',\n};\nconst CONTEXT_WARN_PERCENT = 60;\nconst CONTEXT_CRITICAL_PERCENT = 85;\n\nfunction normalizeFooterText(text: string): string {\n return text\n .replace(/[\\r\\n\\t]+/gu, ' ')\n .replace(/ {2,}/gu, ' ')\n .trim();\n}\n\nexport class DoomFooter implements Component {\n private readonly unsubscribeBranch: () => void;\n private readonly unsubscribeFooterStatuses: () => void;\n private readonly unsubscribeState: () => void;\n\n constructor(\n tui: TUI,\n private readonly theme: Theme,\n private readonly context: ExtensionContext,\n private readonly footerData: ReadonlyFooterDataProvider,\n private readonly footerStatuses: DoomFooterStatusRegistry,\n private readonly uiState: DoomUiState,\n ) {\n this.unsubscribeBranch = footerData.onBranchChange(() => tui.requestRender());\n this.unsubscribeFooterStatuses = footerStatuses.subscribe(() => tui.requestRender());\n this.unsubscribeState = uiState.subscribe(() => tui.requestRender());\n }\n\n dispose(): void {\n this.unsubscribeBranch();\n this.unsubscribeFooterStatuses();\n this.unsubscribeState();\n }\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0) return [];\n\n const narrow = width < NARROW_FOOTER_WIDTH;\n const snapshot = this.uiState.getSnapshot();\n const modeText = snapshot.active ? snapshot.prefix.join(' ') : narrow ? 'I' : 'INSERT';\n const mode = snapshot.active\n ? this.theme.inverse(this.theme.bold(this.theme.fg('mdHeading', ` ${modeText} `)))\n : this.theme.inverse(this.theme.bold(this.theme.fg('accent', ` ${modeText} `)));\n const repository = path.basename(this.context.cwd) || this.context.cwd;\n const branch = this.footerData.getGitBranch();\n const location = this.locationSegment(repository, branch, narrow);\n const left = `${mode}${location}`;\n const beforeModel = this.footerStatusSummary(BEFORE_MODEL_PLACEMENT, narrow);\n const baseRuntime = this.runtimeSegment(narrow);\n const runtimeWithBeforeModel = this.runtimeSegment(narrow, beforeModel);\n const runtime =\n beforeModel && visibleWidth(mode) + visibleWidth(runtimeWithBeforeModel) <= width\n ? runtimeWithBeforeModel\n : baseRuntime;\n\n if (visibleWidth(left) + visibleWidth(runtime) > width) {\n return [fitLine(`${mode}${runtime}`, width)];\n }\n\n const middleWidth = width - visibleWidth(left) - visibleWidth(runtime);\n const middleText = this.modelineSummary(Math.max(0, middleWidth - 1), narrow);\n const middle = this.theme.bg(TOOL_PENDING_BACKGROUND, padLine(` ${middleText}`, middleWidth));\n return [fitLine(`${left}${middle}${runtime}`, width)];\n }\n\n private locationSegment(repository: string, branch: string | null, narrow: boolean): string {\n const repositoryPart = this.theme.bg(USER_MESSAGE_BACKGROUND, this.theme.fg('text', ` ${repository}`));\n if (!branch) return `${repositoryPart}${this.theme.bg(USER_MESSAGE_BACKGROUND, ' ')}`;\n const branchPrefix = narrow ? ' ' : ' ';\n return `${repositoryPart}${this.theme.bg(\n USER_MESSAGE_BACKGROUND,\n this.theme.fg('success', `${branchPrefix}${branch} `),\n )}`;\n }\n\n private runtimeSegment(narrow: boolean, beforeModel = ''): string {\n const contextPercent = this.context.getContextUsage()?.percent;\n const contextUsage =\n contextPercent === null || contextPercent === undefined\n ? narrow\n ? '?'\n : 'ctx ?'\n : narrow\n ? `${Math.round(contextPercent)}%`\n : `ctx ${Math.round(contextPercent)}%`;\n const modelLimit = narrow ? 18 : 30;\n const model = fitLine(this.compactModel(this.context.model?.id ?? 'no-model'), modelLimit);\n const thinking = this.context.thinkingLevel ?? DEFAULT_THINKING_LEVEL;\n const content = [\n ...(beforeModel ? [beforeModel] : []),\n this.theme.fg('warning', model),\n this.theme.fg(this.thinkingColor(thinking), thinking),\n this.theme.fg(this.contextColor(contextPercent), contextUsage),\n ].join(SEGMENT_SEPARATOR);\n return this.theme.bg(TOOL_PENDING_BACKGROUND, ` ${content} `);\n }\n\n /** Effort rides the theme's rising ramp, from grey at off to red at max. */\n private thinkingColor(level: string): ThemeColor {\n return THINKING_COLORS[level] ?? 'thinkingText';\n }\n\n /** Context usage warms as the window fills, so pressure is visible at a glance. */\n private contextColor(percent: number | null | undefined): ThemeColor {\n if (percent === null || percent === undefined) return MUTED_COLOR;\n if (percent >= CONTEXT_CRITICAL_PERCENT) return 'error';\n if (percent >= CONTEXT_WARN_PERCENT) return 'warning';\n return MUTED_COLOR;\n }\n\n private modelineSummary(availableWidth: number, compact: boolean): string {\n if (availableWidth <= 0) return '';\n return fitLine(this.footerStatusSummary(MODELINE_PLACEMENT, compact), availableWidth);\n }\n\n private footerStatusSummary(placement: FooterStatusPlacement, compact: boolean): string {\n return this.footerStatuses\n .getStatuses()\n .filter((status) => (status.placement ?? MODELINE_PLACEMENT) === placement)\n .map((status) => {\n const segments = compact ? status.compactSegments : status.fullSegments;\n if (segments?.length) return this.renderFooterSegments(segments);\n return this.theme.fg(DIM_COLOR, normalizeFooterText(compact ? status.compactText : status.fullText));\n })\n .filter(Boolean)\n .join(this.theme.fg(DIM_COLOR, SEGMENT_SEPARATOR));\n }\n\n private renderFooterSegments(segments: readonly FooterTextSegment[]): string {\n return segments\n .map((segment) => {\n const text = segment.text.replace(/[\\r\\n\\t]+/gu, ' ');\n return this.theme.fg((segment.color ?? DIM_COLOR) as ThemeColor, text);\n })\n .join('');\n }\n\n private compactModel(model: string): string {\n const modelId = model.split('/').at(-1) ?? model;\n return modelId.replace(/^claude-/, '');\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DoomUiState } from "../uiState.cjs";
|
|
2
|
+
import { footer_d_exports } from "../footer.cjs";
|
|
3
|
+
import { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Component, TUI } from "@earendil-works/pi-tui";
|
|
5
|
+
//#region src/components/doomFooter.d.ts
|
|
6
|
+
declare class DoomFooter implements Component {
|
|
7
|
+
private readonly theme;
|
|
8
|
+
private readonly context;
|
|
9
|
+
private readonly footerData;
|
|
10
|
+
private readonly footerStatuses;
|
|
11
|
+
private readonly uiState;
|
|
12
|
+
private readonly unsubscribeBranch;
|
|
13
|
+
private readonly unsubscribeFooterStatuses;
|
|
14
|
+
private readonly unsubscribeState;
|
|
15
|
+
constructor(tui: TUI, theme: Theme, context: ExtensionContext, footerData: ReadonlyFooterDataProvider, footerStatuses: footer_d_exports.DoomFooterStatusRegistry, uiState: DoomUiState);
|
|
16
|
+
dispose(): void;
|
|
17
|
+
invalidate(): void;
|
|
18
|
+
render(width: number): string[];
|
|
19
|
+
private locationSegment;
|
|
20
|
+
private runtimeSegment;
|
|
21
|
+
/** Effort rides the theme's rising ramp, from grey at off to red at max. */
|
|
22
|
+
private thinkingColor;
|
|
23
|
+
/** Context usage warms as the window fills, so pressure is visible at a glance. */
|
|
24
|
+
private contextColor;
|
|
25
|
+
private modelineSummary;
|
|
26
|
+
private footerStatusSummary;
|
|
27
|
+
private renderFooterSegments;
|
|
28
|
+
private compactModel;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { DoomFooter };
|
|
32
|
+
//# sourceMappingURL=doomFooter.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doomFooter.d.cts","names":[],"sources":["../../src/components/doomFooter.ts"],"mappings":";;;;;cAqCa,sBAAsB;mBAOd;mBACA;mBACA;mBACA;mBACA;mBAVF;mBACA;mBACA;EAGf,YAAA,KAAK,KACY,OAAO,OACP,SAAS,kBACT,YAAY,4BACZ,gBAAgB,iBAAA,0BAChB,SAAS;EAO5B;EAMA;EAEA,OAAO;UA+BC;UAUA;;UAuBA;;UAKA;UAOA;UAKA;UAaA;UASA"}
|