@agimon-ai/doompi-ui 0.0.1-alpha.32 → 0.0.1-alpha.34
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/dist/components/leaderHints.cjs +1 -1
- package/dist/components/leaderHints.d.cts +2 -2
- package/dist/components/leaderHints.d.mts +2 -2
- package/dist/components/leaderHints.mjs +1 -1
- package/dist/services/leader/leaderRegistry.cjs +1 -1
- package/dist/services/leader/leaderRegistry.cjs.map +1 -1
- package/dist/services/leader/leaderRegistry.d.cts +2 -1
- package/dist/services/leader/leaderRegistry.d.cts.map +1 -1
- package/dist/services/leader/leaderRegistry.d.mts +2 -1
- package/dist/services/leader/leaderRegistry.d.mts.map +1 -1
- package/dist/services/leader/leaderRegistry.mjs +1 -1
- package/dist/services/leader/leaderRegistry.mjs.map +1 -1
- package/dist/services/state/uiState.cjs.map +1 -1
- package/dist/services/state/uiState.d.cts +2 -0
- package/dist/services/state/uiState.d.cts.map +1 -1
- package/dist/services/state/uiState.d.mts +2 -0
- package/dist/services/state/uiState.d.mts.map +1 -1
- package/dist/services/state/uiState.mjs.map +1 -1
- package/dist/tui/doomEditor.cjs +1 -1
- package/dist/tui/doomEditor.cjs.map +1 -1
- package/dist/tui/doomEditor.d.cts.map +1 -1
- package/dist/tui/doomEditor.d.mts.map +1 -1
- package/dist/tui/doomEditor.mjs +1 -1
- package/dist/tui/doomEditor.mjs.map +1 -1
- package/dist/tui/leaderHints.cjs +1 -1
- package/dist/tui/leaderHints.cjs.map +1 -1
- package/dist/tui/leaderHints.d.cts +15 -1
- package/dist/tui/leaderHints.d.cts.map +1 -1
- package/dist/tui/leaderHints.d.mts +15 -1
- package/dist/tui/leaderHints.d.mts.map +1 -1
- package/dist/tui/leaderHints.mjs +1 -1
- package/dist/tui/leaderHints.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../tui/leaderHints.cjs");exports.LeaderHints=e.LeaderHints;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../tui/leaderHints.cjs");exports.LeaderHints=e.LeaderHints,exports.MAX_WIDGET_LINES=e.MAX_WIDGET_LINES;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { LeaderHints } from "../tui/leaderHints.cjs";
|
|
2
|
-
export { LeaderHints };
|
|
1
|
+
import { LeaderHints, MAX_WIDGET_LINES } from "../tui/leaderHints.cjs";
|
|
2
|
+
export { LeaderHints, MAX_WIDGET_LINES };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { LeaderHints } from "../tui/leaderHints.mjs";
|
|
2
|
-
export { LeaderHints };
|
|
1
|
+
import { LeaderHints, MAX_WIDGET_LINES } from "../tui/leaderHints.mjs";
|
|
2
|
+
export { LeaderHints, MAX_WIDGET_LINES };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LeaderHints as e}from"../tui/leaderHints.mjs";export{e as LeaderHints};
|
|
1
|
+
import{LeaderHints as e,MAX_WIDGET_LINES as t}from"../tui/leaderHints.mjs";export{e as LeaderHints,t as MAX_WIDGET_LINES};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=`@agimon-ai/doompi-ui`,t=`unknown`,n=1e3,r=/^[a-z0-9]$/,i=/^[a-z0-9][a-z0-9._:-]*$/,a=/^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/,o=/^[a-z0-9][a-z0-9:_-]*$/,s={key:`m`,label:`models`,order:10},c={key:`s`,label:`sessions`,order:30},l={key:`e`,label:`extension`,order:50},u={key:`h`,label:`help`,order:70},d={key:`q`,label:`quit`,order:90};function f(t,n,r){return{id:t,source:e,path:n,action:r}}const p=[f(`model.select`,[s,{key:`m`,label:`select`,detail:`choose model`,order:10}],{type:`app`,action:`app.model.select`}),f(`model.next`,[s,{key:`n`,label:`next`,detail:`cycle model`,order:20}],{type:`app`,action:`app.model.cycleForward`}),f(`thinking.cycle`,[s,{key:`t`,label:`thinking`,detail:`cycle level`,order:30}],{type:`app`,action:`app.thinking.cycle`}),f(`tools.open`,[l,{key:`t`,label:`tools`,detail:`browse tools`,order:20}],{type:`command`,command:{name:`tools`}}),f(`config.open`,[l,{key:`c`,label:`config`,detail:`settings`,order:30}],{type:`command`,command:{name:`config`}}),f(`session.new`,[c,{key:`n`,label:`new`,detail:`fresh session`,order:10}],{type:`app`,action:`app.session.new`}),f(`session.resume`,[c,{key:`r`,label:`resume`,detail:`open history`,order:20}],{type:`app`,action:`app.session.resume`}),f(`session.tree`,[c,{key:`t`,label:`tree`,detail:`branch view`,order:30}],{type:`app`,action:`app.session.tree`}),f(`session.fork`,[c,{key:`f`,label:`fork`,detail:`from selection`,order:40}],{type:`app`,action:`app.session.fork`}),f(`editor.external`,[l,{key:`e`,label:`external`,detail:`open editor`,order:10}],{type:`app`,action:`app.editor.external`}),f(`help.hotkeys`,[u,{key:`h`,label:`hotkeys`,detail:`show bindings`}],{type:`command`,command:{name:`hotkeys`}}),f(`app.exit`,[d,{key:`q`,label:`quit`,detail:`exit Pi`}],{type:`app`,action:`app.exit`})];function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e,t,n){return{source:e,message:t,...n?{bindingId:n}:{}}}function g(e){for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t);if(n<=31||n===127)return!0}return!1}function _(e,t,n,r,i,a){if(typeof e!=`string`||!e.trim()){i.push(h(r,`${t} must be a non-empty string.`,a));return}let o=e.trim();if(o.length>n||g(o)){i.push(h(r,`${t} must be at most ${n} characters and contain no control characters.`,a));return}return o}function v(e,t,n,r,i,a){if(e!==void 0)return _(e,t,n,r,i,a)}function y(e,t,i,a,o){if(!m(e)){o.push(h(t,`bindings[].path[${a}] must be an object.`,i));return}let s=_(e.key,`bindings[].path[${a}].key`,1,t,o,i),c=_(e.label,`bindings[].path[${a}].label`,32,t,o,i),l=v(e.detail,`bindings[].path[${a}].detail`,80,t,o,i),u;if(e.order!==void 0&&(!Number.isInteger(e.order)||e.order<0||e.order>n?o.push(h(t,`bindings[].path[${a}].order must be an integer from 0 to ${n}.`,i)):
|
|
1
|
+
const e=`@agimon-ai/doompi-ui`,t=`unknown`,n=1e3,r=/^[a-z0-9]$/,i=/^[a-z0-9][a-z0-9._:-]*$/,a=/^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/,o=/^[a-z0-9][a-z0-9:_-]*$/,s={key:`m`,label:`models`,detail:`model and thinking level`,order:10},c={key:`s`,label:`sessions`,detail:`history, forks and branches`,order:30},l={key:`e`,label:`extension`,detail:`tools, skills and config`,order:50},u={key:`h`,label:`help`,detail:`package docs and logs`,order:70},d={key:`q`,label:`quit`,detail:`leave the session`,order:90};function f(t,n,r){return{id:t,source:e,path:n,action:r}}const p=[f(`model.select`,[s,{key:`m`,label:`select`,detail:`choose model`,order:10}],{type:`app`,action:`app.model.select`}),f(`model.next`,[s,{key:`n`,label:`next`,detail:`cycle model`,order:20}],{type:`app`,action:`app.model.cycleForward`}),f(`thinking.cycle`,[s,{key:`t`,label:`thinking`,detail:`cycle level`,order:30}],{type:`app`,action:`app.thinking.cycle`}),f(`tools.open`,[l,{key:`t`,label:`tools`,detail:`browse tools`,order:20}],{type:`command`,command:{name:`tools`}}),f(`config.open`,[l,{key:`c`,label:`config`,detail:`settings`,order:30}],{type:`command`,command:{name:`config`}}),f(`session.new`,[c,{key:`n`,label:`new`,detail:`fresh session`,order:10}],{type:`app`,action:`app.session.new`}),f(`session.resume`,[c,{key:`r`,label:`resume`,detail:`open history`,order:20}],{type:`app`,action:`app.session.resume`}),f(`session.tree`,[c,{key:`t`,label:`tree`,detail:`branch view`,order:30}],{type:`app`,action:`app.session.tree`}),f(`session.fork`,[c,{key:`f`,label:`fork`,detail:`from selection`,order:40}],{type:`app`,action:`app.session.fork`}),f(`editor.external`,[l,{key:`e`,label:`external`,detail:`open editor`,order:10}],{type:`app`,action:`app.editor.external`}),f(`help.hotkeys`,[u,{key:`h`,label:`hotkeys`,detail:`show bindings`}],{type:`command`,command:{name:`hotkeys`}}),f(`app.exit`,[d,{key:`q`,label:`quit`,detail:`exit Pi`}],{type:`app`,action:`app.exit`})];function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e,t,n){return{source:e,message:t,...n?{bindingId:n}:{}}}function g(e){for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t);if(n<=31||n===127)return!0}return!1}function _(e,t,n,r,i,a){if(typeof e!=`string`||!e.trim()){i.push(h(r,`${t} must be a non-empty string.`,a));return}let o=e.trim();if(o.length>n||g(o)){i.push(h(r,`${t} must be at most ${n} characters and contain no control characters.`,a));return}return o}function v(e,t,n,r,i,a){if(e!==void 0)return _(e,t,n,r,i,a)}function y(e,t,i,a,o){if(!m(e)){o.push(h(t,`bindings[].path[${a}] must be an object.`,i));return}let s=_(e.key,`bindings[].path[${a}].key`,1,t,o,i),c=_(e.label,`bindings[].path[${a}].label`,32,t,o,i),l=v(e.detail,`bindings[].path[${a}].detail`,80,t,o,i),u=e.tone==="default"||e.tone===`exit`?e.tone:void 0,d;if(e.order!==void 0&&(!Number.isInteger(e.order)||e.order<0||e.order>n?o.push(h(t,`bindings[].path[${a}].order must be an integer from 0 to ${n}.`,i)):d=e.order),!s||!c||!r.test(s)){s&&!r.test(s)&&o.push(h(t,`bindings[].path[${a}].key must match ${r}.`,i));return}return{key:s,label:c,...l?{detail:l}:{},...u?{tone:u}:{},...d===void 0?{}:{order:d}}}function b(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].command must be an object.`,n));return}let i=_(e.name,`bindings[].command.name`,64,t,r,n),a=v(e.args,`bindings[].command.args`,256,t,r,n);if(!i||!o.test(i)){i&&!o.test(i)&&r.push(h(t,`bindings[].command.name must match ${o}.`,n));return}return{name:i,...a?{args:a}:{}}}function x(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].action must be an object.`,n));return}let a=_(e.name,`bindings[].action.name`,128,t,r,n);if(!a||!i.test(a)){a&&!i.test(a)&&r.push(h(t,`bindings[].action.name must match ${i}.`,n));return}return{name:a}}function S(e,t,n){if(!m(e)){n.push(h(t,`bindings[] must be an object.`));return}let r=_(e.id,`bindings[].id`,128,t,n);if(!r)return;if(!i.test(r)){n.push(h(t,`bindings[].id must match ${i}.`,r));return}if(!Array.isArray(e.path)||e.path.length===0||e.path.length>4){n.push(h(t,`bindings[].path must contain between 1 and 4 segments.`,r));return}let a=e.path.map((e,i)=>y(e,t,r,i,n)),o=Object.hasOwn(e,`command`);if(o===Object.hasOwn(e,`action`)){n.push(h(t,`bindings[] must contain exactly one of command or action.`,r));return}let s=o?{command:b(e.command,t,r,n)}:{action:x(e.action,t,r,n)};if(!a.some(e=>e===void 0)){if(`command`in s)return s.command?{id:r,path:a,command:s.command}:void 0;if(s.action)return{id:r,path:a,action:s.action}}}function C(e){let n=[];if(!m(e))return{diagnostics:[h(t,`Leader contribution must be an object.`)]};let r=_(e.source,`source`,128,t,n);if(!r)return{diagnostics:n};if(!a.test(r))return n.push(h(r,`source must match ${a}.`)),{diagnostics:n};if(!Array.isArray(e.bindings))return{diagnostics:[h(r,`bindings must be an array.`)]};let i=e.bindings.map(e=>S(e,r,n)),o=i.flatMap(e=>e?[e.id]:[]).filter((e,t,n)=>n.indexOf(e)!==t);for(let e of new Set(o))n.push(h(r,`Binding id '${e}' is duplicated.`,e));return n.length>0||i.some(e=>e===void 0)?{diagnostics:n}:{contribution:{source:r,bindings:i},diagnostics:n}}function w(e){return e.order??500}function T(e){return e.type===`command`?{type:`command`,command:{...e.command}}:e.type===`app`?{type:`app`,action:e.action}:{type:`extension`,source:e.source,action:{...e.action}}}function E(){return{key:``,label:`leader`,order:0,owner:e,children:new Map}}function D(e){return`SPC ${e.map(e=>e.key).join(` `)}`}function O(e,t){return e.label===t.label&&e.order===w(t)}function k(e,t){let n=e,r=-1;for(let[e,i]of t.path.entries()){let a=n.children.get(i.key);if(!a){r=e;break}if(!O(a,i))return h(t.source,`${D(t.path.slice(0,e+1))} conflicts with metadata from ${a.owner}.`,t.id);!a.detail&&i.detail&&(a.detail=i.detail);let o=e===t.path.length-1;if(!o&&a.action||o&&(a.action||a.children.size>0))return h(t.source,`${D(t.path)} conflicts with an existing binding from ${a.owner}.`,t.id);n=a}let i=r===-1?t.path.length:r;for(let e=i;e<t.path.length;e+=1){let r=t.path[e],i={key:r.key,label:r.label,...r.detail?{detail:r.detail}:{},...r.tone?{tone:r.tone}:{},order:w(r),owner:t.source,children:new Map};n.children.set(r.key,i),n=i}n.action=T(t.action),n.bindingId=t.id}var A=class{root=E();contributions=new Map;listeners=new Set;diagnostics=[];dirty=!1;constructor(){this.rebuild()}register(e){let t=this.apply(e);return t.accepted?(this.rebuild(),{accepted:!0,diagnostics:this.getDiagnostics()}):t}registerDeferred(e){return this.apply(e)}apply(e){let t=C(e);return t.contribution?(t.contribution.bindings.length===0?this.contributions.delete(t.contribution.source):this.contributions.set(t.contribution.source,t.contribution.bindings),this.dirty=!0,{accepted:!0,diagnostics:t.diagnostics}):{accepted:!1,diagnostics:t.diagnostics}}flush(){return this.dirty&&this.rebuild(),this.diagnostics.map(e=>({...e}))}getGroup(e){this.flush();let t=this.root;for(let n of e){let e=t.children.get(n);if(!e||e.action)return;t=e}let n=[...t.children.values()].sort((e,t)=>e.key.localeCompare(t.key)).map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{},...e.tone?{tone:e.tone}:{},hasChildren:e.children.size>0,...e.action?{action:T(e.action)}:{}}));return{label:t.label,options:n}}getDiagnostics(){return this.flush()}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}rebuild(){let e=E(),t=[];for(let t of p){let n=k(e,t);if(n)throw Error(`Invalid core leader binding: ${n.message}`)}for(let[n,r]of[...this.contributions.entries()].sort(([e],[t])=>e.localeCompare(t)))for(let i of[...r].sort((e,t)=>e.id.localeCompare(t.id))){let r=k(e,{id:i.id,source:n,path:i.path,action:`command`in i?{type:`command`,command:i.command}:{type:`extension`,source:n,action:i.action}});r&&t.push(r)}this.root=e,this.diagnostics=t,this.dirty=!1;for(let e of this.listeners)e()}};exports.DoomLeaderRegistry=A;
|
|
2
2
|
//# sourceMappingURL=leaderRegistry.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderRegistry.cjs","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"sourcesContent":["type AppKeybinding =\n | 'app.model.select'\n | 'app.model.cycleForward'\n | 'app.thinking.cycle'\n | 'app.session.new'\n | 'app.session.resume'\n | 'app.session.tree'\n | 'app.session.fork'\n | 'app.editor.external'\n | 'app.exit';\n\nimport type {\n LeaderBinding as DoomLeaderBinding,\n LeaderCommand as DoomLeaderCommand,\n LeaderContribution as DoomLeaderContribution,\n LeaderAction as DoomLeaderExtensionAction,\n LeaderSegment as DoomLeaderSegment,\n} from '@agimon-ai/doompi-extension-contracts/leader';\n\ninterface AppLeaderAction {\n type: 'app';\n action: AppKeybinding;\n}\n\ninterface CommandLeaderAction {\n type: 'command';\n command: DoomLeaderCommand;\n}\n\ninterface ExtensionLeaderAction {\n type: 'extension';\n source: string;\n action: DoomLeaderExtensionAction;\n}\n\nexport type DoomLeaderAction = AppLeaderAction | CommandLeaderAction | ExtensionLeaderAction;\n\nexport interface DoomLeaderResolvedOption {\n key: string;\n label: string;\n detail?: string;\n hasChildren: boolean;\n action?: DoomLeaderAction;\n}\n\nexport interface DoomLeaderGroup {\n label: string;\n options: readonly DoomLeaderResolvedOption[];\n}\n\nexport interface DoomLeaderDiagnostic {\n source: string;\n bindingId?: string;\n message: string;\n}\n\nexport interface DoomLeaderRegistrationResult {\n accepted: boolean;\n diagnostics: readonly DoomLeaderDiagnostic[];\n}\n\ninterface InternalLeaderBinding {\n id: string;\n source: string;\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]];\n action: DoomLeaderAction;\n}\n\ninterface LeaderNode {\n key: string;\n label: string;\n detail?: string;\n order: number;\n owner: string;\n bindingId?: string;\n children: Map<string, LeaderNode>;\n action?: DoomLeaderAction;\n}\n\ninterface ParsedContribution {\n contribution?: DoomLeaderContribution;\n diagnostics: DoomLeaderDiagnostic[];\n}\n\nconst CORE_SOURCE = '@agimon-ai/doompi-ui';\nconst UNKNOWN_SOURCE = 'unknown';\nconst ROOT_LABEL = 'leader';\nconst DEFAULT_OPTION_ORDER = 500;\nconst MAX_OPTION_ORDER = 1000;\nconst MAX_PATH_LENGTH = 4;\nconst MAX_SOURCE_LENGTH = 128;\nconst MAX_BINDING_ID_LENGTH = 128;\nconst MAX_LABEL_LENGTH = 32;\nconst MAX_DETAIL_LENGTH = 80;\nconst MAX_COMMAND_NAME_LENGTH = 64;\nconst MAX_COMMAND_ARGUMENT_LENGTH = 256;\nconst MAX_ACTION_NAME_LENGTH = 128;\nconst SAFE_KEY = /^[a-z0-9]$/;\nconst SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;\nconst SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;\nconst SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;\nconst C0_CONTROL_MAX = 31;\nconst DELETE_CHARACTER = 127;\nconst MODEL_GROUP = { key: 'm', label: 'models', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', order: 90 } as const;\n\nfunction coreBinding(\n id: string,\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: DoomLeaderAction,\n): InternalLeaderBinding {\n return { id, source: CORE_SOURCE, path, action };\n}\n\nconst CORE_BINDINGS: readonly InternalLeaderBinding[] = [\n coreBinding('model.select', [MODEL_GROUP, { key: 'm', label: 'select', detail: 'choose model', order: 10 }], {\n type: 'app',\n action: 'app.model.select',\n }),\n coreBinding('model.next', [MODEL_GROUP, { key: 'n', label: 'next', detail: 'cycle model', order: 20 }], {\n type: 'app',\n action: 'app.model.cycleForward',\n }),\n coreBinding('thinking.cycle', [MODEL_GROUP, { key: 't', label: 'thinking', detail: 'cycle level', order: 30 }], {\n type: 'app',\n action: 'app.thinking.cycle',\n }),\n coreBinding('tools.open', [EXTENSION_GROUP, { key: 't', label: 'tools', detail: 'browse tools', order: 20 }], {\n type: 'command',\n command: { name: 'tools' },\n }),\n // Core rather than contributed, so no extension owns the key and the panel is\n // reachable even when nothing has contributed a section to it yet.\n coreBinding('config.open', [EXTENSION_GROUP, { key: 'c', label: 'config', detail: 'settings', order: 30 }], {\n type: 'command',\n command: { name: 'config' },\n }),\n coreBinding('session.new', [SESSION_GROUP, { key: 'n', label: 'new', detail: 'fresh session', order: 10 }], {\n type: 'app',\n action: 'app.session.new',\n }),\n coreBinding('session.resume', [SESSION_GROUP, { key: 'r', label: 'resume', detail: 'open history', order: 20 }], {\n type: 'app',\n action: 'app.session.resume',\n }),\n coreBinding('session.tree', [SESSION_GROUP, { key: 't', label: 'tree', detail: 'branch view', order: 30 }], {\n type: 'app',\n action: 'app.session.tree',\n }),\n coreBinding('session.fork', [SESSION_GROUP, { key: 'f', label: 'fork', detail: 'from selection', order: 40 }], {\n type: 'app',\n action: 'app.session.fork',\n }),\n coreBinding('editor.external', [EXTENSION_GROUP, { key: 'e', label: 'external', detail: 'open editor', order: 10 }], {\n type: 'app',\n action: 'app.editor.external',\n }),\n coreBinding('help.hotkeys', [HELP_GROUP, { key: 'h', label: 'hotkeys', detail: 'show bindings' }], {\n type: 'command',\n command: { name: 'hotkeys' },\n }),\n coreBinding('app.exit', [QUIT_GROUP, { key: 'q', label: 'quit', detail: 'exit Pi' }], {\n type: 'app',\n action: 'app.exit',\n }),\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction diagnostic(source: string, message: string, bindingId?: string): DoomLeaderDiagnostic {\n return { source, message, ...(bindingId ? { bindingId } : {}) };\n}\n\nfunction containsControlCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;\n }\n return false;\n}\n\nfunction readRequiredText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId?: string,\n): string | undefined {\n if (typeof value !== 'string' || !value.trim()) {\n diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));\n return undefined;\n }\n const text = value.trim();\n if (text.length > maximumLength || containsControlCharacter(text)) {\n diagnostics.push(\n diagnostic(\n source,\n `${field} must be at most ${maximumLength} characters and contain no control characters.`,\n bindingId,\n ),\n );\n return undefined;\n }\n return text;\n}\n\nfunction readOptionalText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);\n}\n\nfunction parseSegment(\n value: unknown,\n source: string,\n bindingId: string,\n index: number,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderSegment | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));\n return undefined;\n }\n\n const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);\n const label = readRequiredText(\n value.label,\n `bindings[].path[${index}].label`,\n MAX_LABEL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const detail = readOptionalText(\n value.detail,\n `bindings[].path[${index}].detail`,\n MAX_DETAIL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n let order: number | undefined;\n if (value.order !== undefined) {\n if (!Number.isInteger(value.order) || (value.order as number) < 0 || (value.order as number) > MAX_OPTION_ORDER) {\n diagnostics.push(\n diagnostic(\n source,\n `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`,\n bindingId,\n ),\n );\n } else {\n order = value.order as number;\n }\n }\n if (!key || !label || !SAFE_KEY.test(key)) {\n if (key && !SAFE_KEY.test(key)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));\n }\n return undefined;\n }\n return { key, label, ...(detail ? { detail } : {}), ...(order === undefined ? {} : { order }) };\n}\n\nfunction parseCommand(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderCommand | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].command must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].command.name',\n MAX_COMMAND_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const args = readOptionalText(\n value.args,\n 'bindings[].command.args',\n MAX_COMMAND_ARGUMENT_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_COMMAND_NAME.test(name)) {\n if (name && !SAFE_COMMAND_NAME.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));\n }\n return undefined;\n }\n return { name, ...(args ? { args } : {}) };\n}\n\nfunction parseExtensionAction(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderExtensionAction | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].action must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].action.name',\n MAX_ACTION_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_IDENTIFIER.test(name)) {\n if (name && !SAFE_IDENTIFIER.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));\n }\n return undefined;\n }\n return { name };\n}\n\nfunction parseBinding(\n value: unknown,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderBinding | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[] must be an object.'));\n return undefined;\n }\n const bindingId = readRequiredText(value.id, 'bindings[].id', MAX_BINDING_ID_LENGTH, source, diagnostics);\n if (!bindingId) return undefined;\n if (!SAFE_IDENTIFIER.test(bindingId)) {\n diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));\n return undefined;\n }\n if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {\n diagnostics.push(\n diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId),\n );\n return undefined;\n }\n\n const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));\n const hasCommand = Object.hasOwn(value, 'command');\n const hasAction = Object.hasOwn(value, 'action');\n if (hasCommand === hasAction) {\n diagnostics.push(diagnostic(source, 'bindings[] must contain exactly one of command or action.', bindingId));\n return undefined;\n }\n const target = hasCommand\n ? { command: parseCommand(value.command, source, bindingId, diagnostics) }\n : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };\n if (path.some((segment) => segment === undefined)) return undefined;\n if ('command' in target) {\n if (!target.command) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n command: target.command,\n };\n }\n if (!target.action) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: target.action,\n };\n}\n\nfunction parseContribution(value: unknown): ParsedContribution {\n const diagnostics: DoomLeaderDiagnostic[] = [];\n if (!isRecord(value)) {\n return { diagnostics: [diagnostic(UNKNOWN_SOURCE, 'Leader contribution must be an object.')] };\n }\n const source = readRequiredText(value.source, 'source', MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);\n if (!source) return { diagnostics };\n if (!SAFE_SOURCE.test(source)) {\n diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));\n return { diagnostics };\n }\n if (!Array.isArray(value.bindings)) {\n return { diagnostics: [diagnostic(source, 'bindings must be an array.')] };\n }\n\n const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));\n const duplicateIds = bindings\n .flatMap((binding) => (binding ? [binding.id] : []))\n .filter((id, index, ids) => ids.indexOf(id) !== index);\n for (const id of new Set(duplicateIds)) {\n diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));\n }\n if (diagnostics.length > 0 || bindings.some((binding) => binding === undefined)) return { diagnostics };\n\n return {\n contribution: {\n source,\n bindings: bindings as DoomLeaderBinding[],\n },\n diagnostics,\n };\n}\n\nfunction optionOrder(segment: DoomLeaderSegment): number {\n return segment.order ?? DEFAULT_OPTION_ORDER;\n}\n\nfunction cloneAction(action: DoomLeaderAction): DoomLeaderAction {\n if (action.type === 'command') return { type: 'command', command: { ...action.command } };\n if (action.type === 'app') return { type: 'app', action: action.action };\n return { type: 'extension', source: action.source, action: { ...action.action } };\n}\n\nfunction createRootNode(): LeaderNode {\n return {\n key: '',\n label: ROOT_LABEL,\n order: 0,\n owner: CORE_SOURCE,\n children: new Map(),\n };\n}\n\nfunction pathLabel(path: readonly DoomLeaderSegment[]): string {\n return `SPC ${path.map((segment) => segment.key).join(' ')}`;\n}\n\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.detail === segment.detail && node.order === optionOrder(segment);\n}\n\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic | undefined {\n let current = root;\n let firstMissing = -1;\n\n for (const [index, segment] of binding.path.entries()) {\n const existing = current.children.get(segment.key);\n if (!existing) {\n firstMissing = index;\n break;\n }\n if (!metadataMatches(existing, segment)) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} conflicts with metadata from ${existing.owner}.`,\n binding.id,\n );\n }\n const isLeaf = index === binding.path.length - 1;\n if ((!isLeaf && existing.action) || (isLeaf && (existing.action || existing.children.size > 0))) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path)} conflicts with an existing binding from ${existing.owner}.`,\n binding.id,\n );\n }\n current = existing;\n }\n\n const start = firstMissing === -1 ? binding.path.length : firstMissing;\n for (let index = start; index < binding.path.length; index += 1) {\n const segment = binding.path[index] as DoomLeaderSegment;\n const node: LeaderNode = {\n key: segment.key,\n label: segment.label,\n ...(segment.detail ? { detail: segment.detail } : {}),\n order: optionOrder(segment),\n owner: binding.source,\n children: new Map(),\n };\n current.children.set(segment.key, node);\n current = node;\n }\n\n current.action = cloneAction(binding.action);\n current.bindingId = binding.id;\n return undefined;\n}\n\nexport class DoomLeaderRegistry {\n private root = createRootNode();\n private readonly contributions = new Map<string, readonly DoomLeaderBinding[]>();\n private readonly listeners = new Set<() => void>();\n private diagnostics: DoomLeaderDiagnostic[] = [];\n private dirty = false;\n\n constructor() {\n this.rebuild();\n }\n\n register(value: unknown): DoomLeaderRegistrationResult {\n const result = this.apply(value);\n if (!result.accepted) return result;\n this.rebuild();\n return { accepted: true, diagnostics: this.getDiagnostics() };\n }\n\n /** Records a contribution without rebuilding the whole tree immediately. */\n registerDeferred(value: unknown): DoomLeaderRegistrationResult {\n return this.apply(value);\n }\n\n private apply(value: unknown): DoomLeaderRegistrationResult {\n const parsed = parseContribution(value);\n if (!parsed.contribution) return { accepted: false, diagnostics: parsed.diagnostics };\n\n if (parsed.contribution.bindings.length === 0) this.contributions.delete(parsed.contribution.source);\n else this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n this.dirty = true;\n return { accepted: true, diagnostics: parsed.diagnostics };\n }\n\n /** Rebuilds one batch and returns its deterministic conflict diagnostics. */\n flush(): DoomLeaderDiagnostic[] {\n if (this.dirty) this.rebuild();\n return this.diagnostics.map((item) => ({ ...item }));\n }\n\n getGroup(path: readonly string[]): DoomLeaderGroup | undefined {\n this.flush();\n let current = this.root;\n for (const key of path) {\n const next = current.children.get(key);\n if (!next || next.action) return undefined;\n current = next;\n }\n\n // The key is the sort, at every level. A reader looking for a chord knows the\n // key, not the number some contributor picked for it, and those numbers live\n // across nine packages so no one of them can see the sequence they produce.\n // `order` survives only as group identity: co-contributors to a shared prefix\n // must still agree on it, which is what `metadataMatches` checks.\n const options = [...current.children.values()]\n .sort((left, right) => left.key.localeCompare(right.key))\n .map((node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\n }));\n return { label: current.label, options };\n }\n\n getDiagnostics(): DoomLeaderDiagnostic[] {\n return this.flush();\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private rebuild(): void {\n const nextRoot = createRootNode();\n const diagnostics: DoomLeaderDiagnostic[] = [];\n for (const binding of CORE_BINDINGS) {\n const issue = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n for (const [source, bindings] of [...this.contributions.entries()].sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n const issue = insertBinding(nextRoot, {\n id: binding.id,\n source,\n path: binding.path,\n action:\n 'command' in binding\n ? { type: 'command', command: binding.command }\n : { type: 'extension', source, action: binding.action },\n });\n if (issue) diagnostics.push(issue);\n }\n }\n\n this.root = nextRoot;\n this.diagnostics = diagnostics;\n this.dirty = false;\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"leaderRegistry.cjs","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"sourcesContent":["type AppKeybinding =\n | 'app.model.select'\n | 'app.model.cycleForward'\n | 'app.thinking.cycle'\n | 'app.session.new'\n | 'app.session.resume'\n | 'app.session.tree'\n | 'app.session.fork'\n | 'app.editor.external'\n | 'app.exit';\n\nimport type {\n LeaderBinding as DoomLeaderBinding,\n LeaderCommand as DoomLeaderCommand,\n LeaderContribution as DoomLeaderContribution,\n LeaderAction as DoomLeaderExtensionAction,\n LeaderSegment as DoomLeaderSegment,\n LeaderTone as DoomLeaderTone,\n} from '@agimon-ai/doompi-extension-contracts/leader';\n\ninterface AppLeaderAction {\n type: 'app';\n action: AppKeybinding;\n}\n\ninterface CommandLeaderAction {\n type: 'command';\n command: DoomLeaderCommand;\n}\n\ninterface ExtensionLeaderAction {\n type: 'extension';\n source: string;\n action: DoomLeaderExtensionAction;\n}\n\nexport type DoomLeaderAction = AppLeaderAction | CommandLeaderAction | ExtensionLeaderAction;\n\nexport interface DoomLeaderResolvedOption {\n key: string;\n label: string;\n detail?: string;\n tone?: DoomLeaderTone;\n hasChildren: boolean;\n action?: DoomLeaderAction;\n}\n\nexport interface DoomLeaderGroup {\n label: string;\n options: readonly DoomLeaderResolvedOption[];\n}\n\nexport interface DoomLeaderDiagnostic {\n source: string;\n bindingId?: string;\n message: string;\n}\n\nexport interface DoomLeaderRegistrationResult {\n accepted: boolean;\n diagnostics: readonly DoomLeaderDiagnostic[];\n}\n\ninterface InternalLeaderBinding {\n id: string;\n source: string;\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]];\n action: DoomLeaderAction;\n}\n\ninterface LeaderNode {\n key: string;\n label: string;\n detail?: string;\n tone?: DoomLeaderTone;\n order: number;\n owner: string;\n bindingId?: string;\n children: Map<string, LeaderNode>;\n action?: DoomLeaderAction;\n}\n\ninterface ParsedContribution {\n contribution?: DoomLeaderContribution;\n diagnostics: DoomLeaderDiagnostic[];\n}\n\nconst CORE_SOURCE = '@agimon-ai/doompi-ui';\nconst UNKNOWN_SOURCE = 'unknown';\nconst ROOT_LABEL = 'leader';\nconst DEFAULT_OPTION_ORDER = 500;\nconst MAX_OPTION_ORDER = 1000;\nconst MAX_PATH_LENGTH = 4;\nconst MAX_SOURCE_LENGTH = 128;\nconst MAX_BINDING_ID_LENGTH = 128;\nconst MAX_LABEL_LENGTH = 32;\nconst MAX_DETAIL_LENGTH = 80;\nconst MAX_COMMAND_NAME_LENGTH = 64;\nconst MAX_COMMAND_ARGUMENT_LENGTH = 256;\nconst MAX_ACTION_NAME_LENGTH = 128;\nconst SAFE_KEY = /^[a-z0-9]$/;\nconst SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;\nconst SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;\nconst SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;\nconst C0_CONTROL_MAX = 31;\nconst DELETE_CHARACTER = 127;\nconst MODEL_GROUP = { key: 'm', label: 'models', detail: 'model and thinking level', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', detail: 'history, forks and branches', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', detail: 'tools, skills and config', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', detail: 'package docs and logs', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', detail: 'leave the session', order: 90 } as const;\n\nfunction coreBinding(\n id: string,\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: DoomLeaderAction,\n): InternalLeaderBinding {\n return { id, source: CORE_SOURCE, path, action };\n}\n\nconst CORE_BINDINGS: readonly InternalLeaderBinding[] = [\n coreBinding('model.select', [MODEL_GROUP, { key: 'm', label: 'select', detail: 'choose model', order: 10 }], {\n type: 'app',\n action: 'app.model.select',\n }),\n coreBinding('model.next', [MODEL_GROUP, { key: 'n', label: 'next', detail: 'cycle model', order: 20 }], {\n type: 'app',\n action: 'app.model.cycleForward',\n }),\n coreBinding('thinking.cycle', [MODEL_GROUP, { key: 't', label: 'thinking', detail: 'cycle level', order: 30 }], {\n type: 'app',\n action: 'app.thinking.cycle',\n }),\n coreBinding('tools.open', [EXTENSION_GROUP, { key: 't', label: 'tools', detail: 'browse tools', order: 20 }], {\n type: 'command',\n command: { name: 'tools' },\n }),\n // Core rather than contributed, so no extension owns the key and the panel is\n // reachable even when nothing has contributed a section to it yet.\n coreBinding('config.open', [EXTENSION_GROUP, { key: 'c', label: 'config', detail: 'settings', order: 30 }], {\n type: 'command',\n command: { name: 'config' },\n }),\n coreBinding('session.new', [SESSION_GROUP, { key: 'n', label: 'new', detail: 'fresh session', order: 10 }], {\n type: 'app',\n action: 'app.session.new',\n }),\n coreBinding('session.resume', [SESSION_GROUP, { key: 'r', label: 'resume', detail: 'open history', order: 20 }], {\n type: 'app',\n action: 'app.session.resume',\n }),\n coreBinding('session.tree', [SESSION_GROUP, { key: 't', label: 'tree', detail: 'branch view', order: 30 }], {\n type: 'app',\n action: 'app.session.tree',\n }),\n coreBinding('session.fork', [SESSION_GROUP, { key: 'f', label: 'fork', detail: 'from selection', order: 40 }], {\n type: 'app',\n action: 'app.session.fork',\n }),\n coreBinding('editor.external', [EXTENSION_GROUP, { key: 'e', label: 'external', detail: 'open editor', order: 10 }], {\n type: 'app',\n action: 'app.editor.external',\n }),\n coreBinding('help.hotkeys', [HELP_GROUP, { key: 'h', label: 'hotkeys', detail: 'show bindings' }], {\n type: 'command',\n command: { name: 'hotkeys' },\n }),\n coreBinding('app.exit', [QUIT_GROUP, { key: 'q', label: 'quit', detail: 'exit Pi' }], {\n type: 'app',\n action: 'app.exit',\n }),\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction diagnostic(source: string, message: string, bindingId?: string): DoomLeaderDiagnostic {\n return { source, message, ...(bindingId ? { bindingId } : {}) };\n}\n\nfunction containsControlCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;\n }\n return false;\n}\n\nfunction readRequiredText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId?: string,\n): string | undefined {\n if (typeof value !== 'string' || !value.trim()) {\n diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));\n return undefined;\n }\n const text = value.trim();\n if (text.length > maximumLength || containsControlCharacter(text)) {\n diagnostics.push(\n diagnostic(\n source,\n `${field} must be at most ${maximumLength} characters and contain no control characters.`,\n bindingId,\n ),\n );\n return undefined;\n }\n return text;\n}\n\nfunction readOptionalText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);\n}\n\nfunction parseSegment(\n value: unknown,\n source: string,\n bindingId: string,\n index: number,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderSegment | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));\n return undefined;\n }\n\n const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);\n const label = readRequiredText(\n value.label,\n `bindings[].path[${index}].label`,\n MAX_LABEL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const detail = readOptionalText(\n value.detail,\n `bindings[].path[${index}].detail`,\n MAX_DETAIL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n // An unrecognised tone is ignored, not diagnosed. Every other malformed field\n // here rejects the whole contribution, which is right for a key or a label but\n // not for a badge colour: it would mean a package compiled against a later\n // contract loses its entire menu on an older host over a cosmetic value. The\n // union type already catches a typo at every call site built against this\n // contract, which is the only place a typo can come from.\n const tone: DoomLeaderTone | undefined = value.tone === 'default' || value.tone === 'exit' ? value.tone : undefined;\n let order: number | undefined;\n if (value.order !== undefined) {\n if (!Number.isInteger(value.order) || (value.order as number) < 0 || (value.order as number) > MAX_OPTION_ORDER) {\n diagnostics.push(\n diagnostic(\n source,\n `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`,\n bindingId,\n ),\n );\n } else {\n order = value.order as number;\n }\n }\n if (!key || !label || !SAFE_KEY.test(key)) {\n if (key && !SAFE_KEY.test(key)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));\n }\n return undefined;\n }\n return {\n key,\n label,\n ...(detail ? { detail } : {}),\n ...(tone ? { tone } : {}),\n ...(order === undefined ? {} : { order }),\n };\n}\n\nfunction parseCommand(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderCommand | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].command must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].command.name',\n MAX_COMMAND_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const args = readOptionalText(\n value.args,\n 'bindings[].command.args',\n MAX_COMMAND_ARGUMENT_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_COMMAND_NAME.test(name)) {\n if (name && !SAFE_COMMAND_NAME.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));\n }\n return undefined;\n }\n return { name, ...(args ? { args } : {}) };\n}\n\nfunction parseExtensionAction(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderExtensionAction | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].action must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].action.name',\n MAX_ACTION_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_IDENTIFIER.test(name)) {\n if (name && !SAFE_IDENTIFIER.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));\n }\n return undefined;\n }\n return { name };\n}\n\nfunction parseBinding(\n value: unknown,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderBinding | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[] must be an object.'));\n return undefined;\n }\n const bindingId = readRequiredText(value.id, 'bindings[].id', MAX_BINDING_ID_LENGTH, source, diagnostics);\n if (!bindingId) return undefined;\n if (!SAFE_IDENTIFIER.test(bindingId)) {\n diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));\n return undefined;\n }\n if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {\n diagnostics.push(\n diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId),\n );\n return undefined;\n }\n\n const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));\n const hasCommand = Object.hasOwn(value, 'command');\n const hasAction = Object.hasOwn(value, 'action');\n if (hasCommand === hasAction) {\n diagnostics.push(diagnostic(source, 'bindings[] must contain exactly one of command or action.', bindingId));\n return undefined;\n }\n const target = hasCommand\n ? { command: parseCommand(value.command, source, bindingId, diagnostics) }\n : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };\n if (path.some((segment) => segment === undefined)) return undefined;\n if ('command' in target) {\n if (!target.command) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n command: target.command,\n };\n }\n if (!target.action) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: target.action,\n };\n}\n\nfunction parseContribution(value: unknown): ParsedContribution {\n const diagnostics: DoomLeaderDiagnostic[] = [];\n if (!isRecord(value)) {\n return { diagnostics: [diagnostic(UNKNOWN_SOURCE, 'Leader contribution must be an object.')] };\n }\n const source = readRequiredText(value.source, 'source', MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);\n if (!source) return { diagnostics };\n if (!SAFE_SOURCE.test(source)) {\n diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));\n return { diagnostics };\n }\n if (!Array.isArray(value.bindings)) {\n return { diagnostics: [diagnostic(source, 'bindings must be an array.')] };\n }\n\n const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));\n const duplicateIds = bindings\n .flatMap((binding) => (binding ? [binding.id] : []))\n .filter((id, index, ids) => ids.indexOf(id) !== index);\n for (const id of new Set(duplicateIds)) {\n diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));\n }\n if (diagnostics.length > 0 || bindings.some((binding) => binding === undefined)) return { diagnostics };\n\n return {\n contribution: {\n source,\n bindings: bindings as DoomLeaderBinding[],\n },\n diagnostics,\n };\n}\n\nfunction optionOrder(segment: DoomLeaderSegment): number {\n return segment.order ?? DEFAULT_OPTION_ORDER;\n}\n\nfunction cloneAction(action: DoomLeaderAction): DoomLeaderAction {\n if (action.type === 'command') return { type: 'command', command: { ...action.command } };\n if (action.type === 'app') return { type: 'app', action: action.action };\n return { type: 'extension', source: action.source, action: { ...action.action } };\n}\n\nfunction createRootNode(): LeaderNode {\n return {\n key: '',\n label: ROOT_LABEL,\n order: 0,\n owner: CORE_SOURCE,\n children: new Map(),\n };\n}\n\nfunction pathLabel(path: readonly DoomLeaderSegment[]): string {\n return `SPC ${path.map((segment) => segment.key).join(' ')}`;\n}\n\n/**\n * Group identity is the key, the label and the sort order. `detail` is left out\n * on purpose: it is a subtitle, and these packages ship on independent npm\n * versions, so requiring every co-contributor to a shared prefix to carry the\n * same wording means one upgraded package silently drops the menus of every\n * package that has not caught up. A subtitle disagreement is not a conflict\n * worth losing `SPC e` over.\n */\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.order === optionOrder(segment);\n}\n\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic | undefined {\n let current = root;\n let firstMissing = -1;\n\n for (const [index, segment] of binding.path.entries()) {\n const existing = current.children.get(segment.key);\n if (!existing) {\n firstMissing = index;\n break;\n }\n if (!metadataMatches(existing, segment)) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} conflicts with metadata from ${existing.owner}.`,\n binding.id,\n );\n }\n // First writer wins for the subtitle, but a group created without one still\n // adopts the first that is offered, so the wording does not depend on which\n // package happened to register first.\n if (!existing.detail && segment.detail) existing.detail = segment.detail;\n const isLeaf = index === binding.path.length - 1;\n if ((!isLeaf && existing.action) || (isLeaf && (existing.action || existing.children.size > 0))) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path)} conflicts with an existing binding from ${existing.owner}.`,\n binding.id,\n );\n }\n current = existing;\n }\n\n const start = firstMissing === -1 ? binding.path.length : firstMissing;\n for (let index = start; index < binding.path.length; index += 1) {\n const segment = binding.path[index] as DoomLeaderSegment;\n const node: LeaderNode = {\n key: segment.key,\n label: segment.label,\n ...(segment.detail ? { detail: segment.detail } : {}),\n ...(segment.tone ? { tone: segment.tone } : {}),\n order: optionOrder(segment),\n owner: binding.source,\n children: new Map(),\n };\n current.children.set(segment.key, node);\n current = node;\n }\n\n current.action = cloneAction(binding.action);\n current.bindingId = binding.id;\n return undefined;\n}\n\nexport class DoomLeaderRegistry {\n private root = createRootNode();\n private readonly contributions = new Map<string, readonly DoomLeaderBinding[]>();\n private readonly listeners = new Set<() => void>();\n private diagnostics: DoomLeaderDiagnostic[] = [];\n private dirty = false;\n\n constructor() {\n this.rebuild();\n }\n\n register(value: unknown): DoomLeaderRegistrationResult {\n const result = this.apply(value);\n if (!result.accepted) return result;\n this.rebuild();\n return { accepted: true, diagnostics: this.getDiagnostics() };\n }\n\n /** Records a contribution without rebuilding the whole tree immediately. */\n registerDeferred(value: unknown): DoomLeaderRegistrationResult {\n return this.apply(value);\n }\n\n private apply(value: unknown): DoomLeaderRegistrationResult {\n const parsed = parseContribution(value);\n if (!parsed.contribution) return { accepted: false, diagnostics: parsed.diagnostics };\n\n if (parsed.contribution.bindings.length === 0) this.contributions.delete(parsed.contribution.source);\n else this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n this.dirty = true;\n return { accepted: true, diagnostics: parsed.diagnostics };\n }\n\n /** Rebuilds one batch and returns its deterministic conflict diagnostics. */\n flush(): DoomLeaderDiagnostic[] {\n if (this.dirty) this.rebuild();\n return this.diagnostics.map((item) => ({ ...item }));\n }\n\n getGroup(path: readonly string[]): DoomLeaderGroup | undefined {\n this.flush();\n let current = this.root;\n for (const key of path) {\n const next = current.children.get(key);\n if (!next || next.action) return undefined;\n current = next;\n }\n\n // The key is the sort, at every level. A reader looking for a chord knows the\n // key, not the number some contributor picked for it, and those numbers live\n // across nine packages so no one of them can see the sequence they produce.\n // `order` survives only as group identity: co-contributors to a shared prefix\n // must still agree on it, which is what `metadataMatches` checks.\n const options = [...current.children.values()]\n .sort((left, right) => left.key.localeCompare(right.key))\n .map((node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n ...(node.tone ? { tone: node.tone } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\n }));\n return { label: current.label, options };\n }\n\n getDiagnostics(): DoomLeaderDiagnostic[] {\n return this.flush();\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private rebuild(): void {\n const nextRoot = createRootNode();\n const diagnostics: DoomLeaderDiagnostic[] = [];\n for (const binding of CORE_BINDINGS) {\n const issue = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n for (const [source, bindings] of [...this.contributions.entries()].sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n const issue = insertBinding(nextRoot, {\n id: binding.id,\n source,\n path: binding.path,\n action:\n 'command' in binding\n ? { type: 'command', command: binding.command }\n : { type: 'extension', source, action: binding.action },\n });\n if (issue) diagnostics.push(issue);\n }\n }\n\n this.root = nextRoot;\n this.diagnostics = diagnostics;\n this.dirty = false;\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LeaderAction, LeaderCommand } from "@agimon-ai/doompi-extension-contracts/leader";
|
|
1
|
+
import { LeaderAction, LeaderCommand, LeaderTone } from "@agimon-ai/doompi-extension-contracts/leader";
|
|
2
2
|
//#region src/services/leader/leaderRegistry.d.ts
|
|
3
3
|
type AppKeybinding = 'app.model.select' | 'app.model.cycleForward' | 'app.thinking.cycle' | 'app.session.new' | 'app.session.resume' | 'app.session.tree' | 'app.session.fork' | 'app.editor.external' | 'app.exit';
|
|
4
4
|
interface AppLeaderAction {
|
|
@@ -19,6 +19,7 @@ interface DoomLeaderResolvedOption {
|
|
|
19
19
|
key: string;
|
|
20
20
|
label: string;
|
|
21
21
|
detail?: string;
|
|
22
|
+
tone?: LeaderTone;
|
|
22
23
|
hasChildren: boolean;
|
|
23
24
|
action?: DoomLeaderAction;
|
|
24
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderRegistry.d.cts","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"mappings":";;KAAK;
|
|
1
|
+
{"version":3,"file":"leaderRegistry.d.cts","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"mappings":";;KAAK;UAoBK;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA,OAAO;EACP;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;cAidX;UACH;mBACS;mBACA;UACT;UACA;EAER;EAIA,SAAS,iBAAiB;;EAQ1B,iBAAiB,iBAAiB;UAI1B;;EAWR,SAAS;EAKT,SAAS,0BAA0B;EA2BnC,kBAAkB;EAIlB,UAAU;UAKF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LeaderAction, LeaderCommand } from "@agimon-ai/doompi-extension-contracts/leader";
|
|
1
|
+
import { LeaderAction, LeaderCommand, LeaderTone } from "@agimon-ai/doompi-extension-contracts/leader";
|
|
2
2
|
//#region src/services/leader/leaderRegistry.d.ts
|
|
3
3
|
type AppKeybinding = 'app.model.select' | 'app.model.cycleForward' | 'app.thinking.cycle' | 'app.session.new' | 'app.session.resume' | 'app.session.tree' | 'app.session.fork' | 'app.editor.external' | 'app.exit';
|
|
4
4
|
interface AppLeaderAction {
|
|
@@ -19,6 +19,7 @@ interface DoomLeaderResolvedOption {
|
|
|
19
19
|
key: string;
|
|
20
20
|
label: string;
|
|
21
21
|
detail?: string;
|
|
22
|
+
tone?: LeaderTone;
|
|
22
23
|
hasChildren: boolean;
|
|
23
24
|
action?: DoomLeaderAction;
|
|
24
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderRegistry.d.mts","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"mappings":";;KAAK;
|
|
1
|
+
{"version":3,"file":"leaderRegistry.d.mts","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"mappings":";;KAAK;UAoBK;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA,OAAO;EACP;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;cAidX;UACH;mBACS;mBACA;UACT;UACA;EAER;EAIA,SAAS,iBAAiB;;EAQ1B,iBAAiB,iBAAiB;UAI1B;;EAWR,SAAS;EAKT,SAAS,0BAA0B;EA2BnC,kBAAkB;EAIlB,UAAU;UAKF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=`@agimon-ai/doompi-ui`,t=`unknown`,n=1e3,r=/^[a-z0-9]$/,i=/^[a-z0-9][a-z0-9._:-]*$/,a=/^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/,o=/^[a-z0-9][a-z0-9:_-]*$/,s={key:`m`,label:`models`,order:10},c={key:`s`,label:`sessions`,order:30},l={key:`e`,label:`extension`,order:50},u={key:`h`,label:`help`,order:70},d={key:`q`,label:`quit`,order:90};function f(t,n,r){return{id:t,source:e,path:n,action:r}}const p=[f(`model.select`,[s,{key:`m`,label:`select`,detail:`choose model`,order:10}],{type:`app`,action:`app.model.select`}),f(`model.next`,[s,{key:`n`,label:`next`,detail:`cycle model`,order:20}],{type:`app`,action:`app.model.cycleForward`}),f(`thinking.cycle`,[s,{key:`t`,label:`thinking`,detail:`cycle level`,order:30}],{type:`app`,action:`app.thinking.cycle`}),f(`tools.open`,[l,{key:`t`,label:`tools`,detail:`browse tools`,order:20}],{type:`command`,command:{name:`tools`}}),f(`config.open`,[l,{key:`c`,label:`config`,detail:`settings`,order:30}],{type:`command`,command:{name:`config`}}),f(`session.new`,[c,{key:`n`,label:`new`,detail:`fresh session`,order:10}],{type:`app`,action:`app.session.new`}),f(`session.resume`,[c,{key:`r`,label:`resume`,detail:`open history`,order:20}],{type:`app`,action:`app.session.resume`}),f(`session.tree`,[c,{key:`t`,label:`tree`,detail:`branch view`,order:30}],{type:`app`,action:`app.session.tree`}),f(`session.fork`,[c,{key:`f`,label:`fork`,detail:`from selection`,order:40}],{type:`app`,action:`app.session.fork`}),f(`editor.external`,[l,{key:`e`,label:`external`,detail:`open editor`,order:10}],{type:`app`,action:`app.editor.external`}),f(`help.hotkeys`,[u,{key:`h`,label:`hotkeys`,detail:`show bindings`}],{type:`command`,command:{name:`hotkeys`}}),f(`app.exit`,[d,{key:`q`,label:`quit`,detail:`exit Pi`}],{type:`app`,action:`app.exit`})];function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e,t,n){return{source:e,message:t,...n?{bindingId:n}:{}}}function g(e){for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t);if(n<=31||n===127)return!0}return!1}function _(e,t,n,r,i,a){if(typeof e!=`string`||!e.trim()){i.push(h(r,`${t} must be a non-empty string.`,a));return}let o=e.trim();if(o.length>n||g(o)){i.push(h(r,`${t} must be at most ${n} characters and contain no control characters.`,a));return}return o}function v(e,t,n,r,i,a){if(e!==void 0)return _(e,t,n,r,i,a)}function y(e,t,i,a,o){if(!m(e)){o.push(h(t,`bindings[].path[${a}] must be an object.`,i));return}let s=_(e.key,`bindings[].path[${a}].key`,1,t,o,i),c=_(e.label,`bindings[].path[${a}].label`,32,t,o,i),l=v(e.detail,`bindings[].path[${a}].detail`,80,t,o,i),u;if(e.order!==void 0&&(!Number.isInteger(e.order)||e.order<0||e.order>n?o.push(h(t,`bindings[].path[${a}].order must be an integer from 0 to ${n}.`,i)):
|
|
1
|
+
const e=`@agimon-ai/doompi-ui`,t=`unknown`,n=1e3,r=/^[a-z0-9]$/,i=/^[a-z0-9][a-z0-9._:-]*$/,a=/^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/,o=/^[a-z0-9][a-z0-9:_-]*$/,s={key:`m`,label:`models`,detail:`model and thinking level`,order:10},c={key:`s`,label:`sessions`,detail:`history, forks and branches`,order:30},l={key:`e`,label:`extension`,detail:`tools, skills and config`,order:50},u={key:`h`,label:`help`,detail:`package docs and logs`,order:70},d={key:`q`,label:`quit`,detail:`leave the session`,order:90};function f(t,n,r){return{id:t,source:e,path:n,action:r}}const p=[f(`model.select`,[s,{key:`m`,label:`select`,detail:`choose model`,order:10}],{type:`app`,action:`app.model.select`}),f(`model.next`,[s,{key:`n`,label:`next`,detail:`cycle model`,order:20}],{type:`app`,action:`app.model.cycleForward`}),f(`thinking.cycle`,[s,{key:`t`,label:`thinking`,detail:`cycle level`,order:30}],{type:`app`,action:`app.thinking.cycle`}),f(`tools.open`,[l,{key:`t`,label:`tools`,detail:`browse tools`,order:20}],{type:`command`,command:{name:`tools`}}),f(`config.open`,[l,{key:`c`,label:`config`,detail:`settings`,order:30}],{type:`command`,command:{name:`config`}}),f(`session.new`,[c,{key:`n`,label:`new`,detail:`fresh session`,order:10}],{type:`app`,action:`app.session.new`}),f(`session.resume`,[c,{key:`r`,label:`resume`,detail:`open history`,order:20}],{type:`app`,action:`app.session.resume`}),f(`session.tree`,[c,{key:`t`,label:`tree`,detail:`branch view`,order:30}],{type:`app`,action:`app.session.tree`}),f(`session.fork`,[c,{key:`f`,label:`fork`,detail:`from selection`,order:40}],{type:`app`,action:`app.session.fork`}),f(`editor.external`,[l,{key:`e`,label:`external`,detail:`open editor`,order:10}],{type:`app`,action:`app.editor.external`}),f(`help.hotkeys`,[u,{key:`h`,label:`hotkeys`,detail:`show bindings`}],{type:`command`,command:{name:`hotkeys`}}),f(`app.exit`,[d,{key:`q`,label:`quit`,detail:`exit Pi`}],{type:`app`,action:`app.exit`})];function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e,t,n){return{source:e,message:t,...n?{bindingId:n}:{}}}function g(e){for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t);if(n<=31||n===127)return!0}return!1}function _(e,t,n,r,i,a){if(typeof e!=`string`||!e.trim()){i.push(h(r,`${t} must be a non-empty string.`,a));return}let o=e.trim();if(o.length>n||g(o)){i.push(h(r,`${t} must be at most ${n} characters and contain no control characters.`,a));return}return o}function v(e,t,n,r,i,a){if(e!==void 0)return _(e,t,n,r,i,a)}function y(e,t,i,a,o){if(!m(e)){o.push(h(t,`bindings[].path[${a}] must be an object.`,i));return}let s=_(e.key,`bindings[].path[${a}].key`,1,t,o,i),c=_(e.label,`bindings[].path[${a}].label`,32,t,o,i),l=v(e.detail,`bindings[].path[${a}].detail`,80,t,o,i),u=e.tone==="default"||e.tone===`exit`?e.tone:void 0,d;if(e.order!==void 0&&(!Number.isInteger(e.order)||e.order<0||e.order>n?o.push(h(t,`bindings[].path[${a}].order must be an integer from 0 to ${n}.`,i)):d=e.order),!s||!c||!r.test(s)){s&&!r.test(s)&&o.push(h(t,`bindings[].path[${a}].key must match ${r}.`,i));return}return{key:s,label:c,...l?{detail:l}:{},...u?{tone:u}:{},...d===void 0?{}:{order:d}}}function b(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].command must be an object.`,n));return}let i=_(e.name,`bindings[].command.name`,64,t,r,n),a=v(e.args,`bindings[].command.args`,256,t,r,n);if(!i||!o.test(i)){i&&!o.test(i)&&r.push(h(t,`bindings[].command.name must match ${o}.`,n));return}return{name:i,...a?{args:a}:{}}}function x(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].action must be an object.`,n));return}let a=_(e.name,`bindings[].action.name`,128,t,r,n);if(!a||!i.test(a)){a&&!i.test(a)&&r.push(h(t,`bindings[].action.name must match ${i}.`,n));return}return{name:a}}function S(e,t,n){if(!m(e)){n.push(h(t,`bindings[] must be an object.`));return}let r=_(e.id,`bindings[].id`,128,t,n);if(!r)return;if(!i.test(r)){n.push(h(t,`bindings[].id must match ${i}.`,r));return}if(!Array.isArray(e.path)||e.path.length===0||e.path.length>4){n.push(h(t,`bindings[].path must contain between 1 and 4 segments.`,r));return}let a=e.path.map((e,i)=>y(e,t,r,i,n)),o=Object.hasOwn(e,`command`);if(o===Object.hasOwn(e,`action`)){n.push(h(t,`bindings[] must contain exactly one of command or action.`,r));return}let s=o?{command:b(e.command,t,r,n)}:{action:x(e.action,t,r,n)};if(!a.some(e=>e===void 0)){if(`command`in s)return s.command?{id:r,path:a,command:s.command}:void 0;if(s.action)return{id:r,path:a,action:s.action}}}function C(e){let n=[];if(!m(e))return{diagnostics:[h(t,`Leader contribution must be an object.`)]};let r=_(e.source,`source`,128,t,n);if(!r)return{diagnostics:n};if(!a.test(r))return n.push(h(r,`source must match ${a}.`)),{diagnostics:n};if(!Array.isArray(e.bindings))return{diagnostics:[h(r,`bindings must be an array.`)]};let i=e.bindings.map(e=>S(e,r,n)),o=i.flatMap(e=>e?[e.id]:[]).filter((e,t,n)=>n.indexOf(e)!==t);for(let e of new Set(o))n.push(h(r,`Binding id '${e}' is duplicated.`,e));return n.length>0||i.some(e=>e===void 0)?{diagnostics:n}:{contribution:{source:r,bindings:i},diagnostics:n}}function w(e){return e.order??500}function T(e){return e.type===`command`?{type:`command`,command:{...e.command}}:e.type===`app`?{type:`app`,action:e.action}:{type:`extension`,source:e.source,action:{...e.action}}}function E(){return{key:``,label:`leader`,order:0,owner:e,children:new Map}}function D(e){return`SPC ${e.map(e=>e.key).join(` `)}`}function O(e,t){return e.label===t.label&&e.order===w(t)}function k(e,t){let n=e,r=-1;for(let[e,i]of t.path.entries()){let a=n.children.get(i.key);if(!a){r=e;break}if(!O(a,i))return h(t.source,`${D(t.path.slice(0,e+1))} conflicts with metadata from ${a.owner}.`,t.id);!a.detail&&i.detail&&(a.detail=i.detail);let o=e===t.path.length-1;if(!o&&a.action||o&&(a.action||a.children.size>0))return h(t.source,`${D(t.path)} conflicts with an existing binding from ${a.owner}.`,t.id);n=a}let i=r===-1?t.path.length:r;for(let e=i;e<t.path.length;e+=1){let r=t.path[e],i={key:r.key,label:r.label,...r.detail?{detail:r.detail}:{},...r.tone?{tone:r.tone}:{},order:w(r),owner:t.source,children:new Map};n.children.set(r.key,i),n=i}n.action=T(t.action),n.bindingId=t.id}var A=class{root=E();contributions=new Map;listeners=new Set;diagnostics=[];dirty=!1;constructor(){this.rebuild()}register(e){let t=this.apply(e);return t.accepted?(this.rebuild(),{accepted:!0,diagnostics:this.getDiagnostics()}):t}registerDeferred(e){return this.apply(e)}apply(e){let t=C(e);return t.contribution?(t.contribution.bindings.length===0?this.contributions.delete(t.contribution.source):this.contributions.set(t.contribution.source,t.contribution.bindings),this.dirty=!0,{accepted:!0,diagnostics:t.diagnostics}):{accepted:!1,diagnostics:t.diagnostics}}flush(){return this.dirty&&this.rebuild(),this.diagnostics.map(e=>({...e}))}getGroup(e){this.flush();let t=this.root;for(let n of e){let e=t.children.get(n);if(!e||e.action)return;t=e}let n=[...t.children.values()].sort((e,t)=>e.key.localeCompare(t.key)).map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{},...e.tone?{tone:e.tone}:{},hasChildren:e.children.size>0,...e.action?{action:T(e.action)}:{}}));return{label:t.label,options:n}}getDiagnostics(){return this.flush()}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}rebuild(){let e=E(),t=[];for(let t of p){let n=k(e,t);if(n)throw Error(`Invalid core leader binding: ${n.message}`)}for(let[n,r]of[...this.contributions.entries()].sort(([e],[t])=>e.localeCompare(t)))for(let i of[...r].sort((e,t)=>e.id.localeCompare(t.id))){let r=k(e,{id:i.id,source:n,path:i.path,action:`command`in i?{type:`command`,command:i.command}:{type:`extension`,source:n,action:i.action}});r&&t.push(r)}this.root=e,this.diagnostics=t,this.dirty=!1;for(let e of this.listeners)e()}};export{A as DoomLeaderRegistry};
|
|
2
2
|
//# sourceMappingURL=leaderRegistry.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderRegistry.mjs","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"sourcesContent":["type AppKeybinding =\n | 'app.model.select'\n | 'app.model.cycleForward'\n | 'app.thinking.cycle'\n | 'app.session.new'\n | 'app.session.resume'\n | 'app.session.tree'\n | 'app.session.fork'\n | 'app.editor.external'\n | 'app.exit';\n\nimport type {\n LeaderBinding as DoomLeaderBinding,\n LeaderCommand as DoomLeaderCommand,\n LeaderContribution as DoomLeaderContribution,\n LeaderAction as DoomLeaderExtensionAction,\n LeaderSegment as DoomLeaderSegment,\n} from '@agimon-ai/doompi-extension-contracts/leader';\n\ninterface AppLeaderAction {\n type: 'app';\n action: AppKeybinding;\n}\n\ninterface CommandLeaderAction {\n type: 'command';\n command: DoomLeaderCommand;\n}\n\ninterface ExtensionLeaderAction {\n type: 'extension';\n source: string;\n action: DoomLeaderExtensionAction;\n}\n\nexport type DoomLeaderAction = AppLeaderAction | CommandLeaderAction | ExtensionLeaderAction;\n\nexport interface DoomLeaderResolvedOption {\n key: string;\n label: string;\n detail?: string;\n hasChildren: boolean;\n action?: DoomLeaderAction;\n}\n\nexport interface DoomLeaderGroup {\n label: string;\n options: readonly DoomLeaderResolvedOption[];\n}\n\nexport interface DoomLeaderDiagnostic {\n source: string;\n bindingId?: string;\n message: string;\n}\n\nexport interface DoomLeaderRegistrationResult {\n accepted: boolean;\n diagnostics: readonly DoomLeaderDiagnostic[];\n}\n\ninterface InternalLeaderBinding {\n id: string;\n source: string;\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]];\n action: DoomLeaderAction;\n}\n\ninterface LeaderNode {\n key: string;\n label: string;\n detail?: string;\n order: number;\n owner: string;\n bindingId?: string;\n children: Map<string, LeaderNode>;\n action?: DoomLeaderAction;\n}\n\ninterface ParsedContribution {\n contribution?: DoomLeaderContribution;\n diagnostics: DoomLeaderDiagnostic[];\n}\n\nconst CORE_SOURCE = '@agimon-ai/doompi-ui';\nconst UNKNOWN_SOURCE = 'unknown';\nconst ROOT_LABEL = 'leader';\nconst DEFAULT_OPTION_ORDER = 500;\nconst MAX_OPTION_ORDER = 1000;\nconst MAX_PATH_LENGTH = 4;\nconst MAX_SOURCE_LENGTH = 128;\nconst MAX_BINDING_ID_LENGTH = 128;\nconst MAX_LABEL_LENGTH = 32;\nconst MAX_DETAIL_LENGTH = 80;\nconst MAX_COMMAND_NAME_LENGTH = 64;\nconst MAX_COMMAND_ARGUMENT_LENGTH = 256;\nconst MAX_ACTION_NAME_LENGTH = 128;\nconst SAFE_KEY = /^[a-z0-9]$/;\nconst SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;\nconst SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;\nconst SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;\nconst C0_CONTROL_MAX = 31;\nconst DELETE_CHARACTER = 127;\nconst MODEL_GROUP = { key: 'm', label: 'models', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', order: 90 } as const;\n\nfunction coreBinding(\n id: string,\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: DoomLeaderAction,\n): InternalLeaderBinding {\n return { id, source: CORE_SOURCE, path, action };\n}\n\nconst CORE_BINDINGS: readonly InternalLeaderBinding[] = [\n coreBinding('model.select', [MODEL_GROUP, { key: 'm', label: 'select', detail: 'choose model', order: 10 }], {\n type: 'app',\n action: 'app.model.select',\n }),\n coreBinding('model.next', [MODEL_GROUP, { key: 'n', label: 'next', detail: 'cycle model', order: 20 }], {\n type: 'app',\n action: 'app.model.cycleForward',\n }),\n coreBinding('thinking.cycle', [MODEL_GROUP, { key: 't', label: 'thinking', detail: 'cycle level', order: 30 }], {\n type: 'app',\n action: 'app.thinking.cycle',\n }),\n coreBinding('tools.open', [EXTENSION_GROUP, { key: 't', label: 'tools', detail: 'browse tools', order: 20 }], {\n type: 'command',\n command: { name: 'tools' },\n }),\n // Core rather than contributed, so no extension owns the key and the panel is\n // reachable even when nothing has contributed a section to it yet.\n coreBinding('config.open', [EXTENSION_GROUP, { key: 'c', label: 'config', detail: 'settings', order: 30 }], {\n type: 'command',\n command: { name: 'config' },\n }),\n coreBinding('session.new', [SESSION_GROUP, { key: 'n', label: 'new', detail: 'fresh session', order: 10 }], {\n type: 'app',\n action: 'app.session.new',\n }),\n coreBinding('session.resume', [SESSION_GROUP, { key: 'r', label: 'resume', detail: 'open history', order: 20 }], {\n type: 'app',\n action: 'app.session.resume',\n }),\n coreBinding('session.tree', [SESSION_GROUP, { key: 't', label: 'tree', detail: 'branch view', order: 30 }], {\n type: 'app',\n action: 'app.session.tree',\n }),\n coreBinding('session.fork', [SESSION_GROUP, { key: 'f', label: 'fork', detail: 'from selection', order: 40 }], {\n type: 'app',\n action: 'app.session.fork',\n }),\n coreBinding('editor.external', [EXTENSION_GROUP, { key: 'e', label: 'external', detail: 'open editor', order: 10 }], {\n type: 'app',\n action: 'app.editor.external',\n }),\n coreBinding('help.hotkeys', [HELP_GROUP, { key: 'h', label: 'hotkeys', detail: 'show bindings' }], {\n type: 'command',\n command: { name: 'hotkeys' },\n }),\n coreBinding('app.exit', [QUIT_GROUP, { key: 'q', label: 'quit', detail: 'exit Pi' }], {\n type: 'app',\n action: 'app.exit',\n }),\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction diagnostic(source: string, message: string, bindingId?: string): DoomLeaderDiagnostic {\n return { source, message, ...(bindingId ? { bindingId } : {}) };\n}\n\nfunction containsControlCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;\n }\n return false;\n}\n\nfunction readRequiredText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId?: string,\n): string | undefined {\n if (typeof value !== 'string' || !value.trim()) {\n diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));\n return undefined;\n }\n const text = value.trim();\n if (text.length > maximumLength || containsControlCharacter(text)) {\n diagnostics.push(\n diagnostic(\n source,\n `${field} must be at most ${maximumLength} characters and contain no control characters.`,\n bindingId,\n ),\n );\n return undefined;\n }\n return text;\n}\n\nfunction readOptionalText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);\n}\n\nfunction parseSegment(\n value: unknown,\n source: string,\n bindingId: string,\n index: number,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderSegment | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));\n return undefined;\n }\n\n const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);\n const label = readRequiredText(\n value.label,\n `bindings[].path[${index}].label`,\n MAX_LABEL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const detail = readOptionalText(\n value.detail,\n `bindings[].path[${index}].detail`,\n MAX_DETAIL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n let order: number | undefined;\n if (value.order !== undefined) {\n if (!Number.isInteger(value.order) || (value.order as number) < 0 || (value.order as number) > MAX_OPTION_ORDER) {\n diagnostics.push(\n diagnostic(\n source,\n `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`,\n bindingId,\n ),\n );\n } else {\n order = value.order as number;\n }\n }\n if (!key || !label || !SAFE_KEY.test(key)) {\n if (key && !SAFE_KEY.test(key)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));\n }\n return undefined;\n }\n return { key, label, ...(detail ? { detail } : {}), ...(order === undefined ? {} : { order }) };\n}\n\nfunction parseCommand(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderCommand | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].command must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].command.name',\n MAX_COMMAND_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const args = readOptionalText(\n value.args,\n 'bindings[].command.args',\n MAX_COMMAND_ARGUMENT_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_COMMAND_NAME.test(name)) {\n if (name && !SAFE_COMMAND_NAME.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));\n }\n return undefined;\n }\n return { name, ...(args ? { args } : {}) };\n}\n\nfunction parseExtensionAction(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderExtensionAction | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].action must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].action.name',\n MAX_ACTION_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_IDENTIFIER.test(name)) {\n if (name && !SAFE_IDENTIFIER.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));\n }\n return undefined;\n }\n return { name };\n}\n\nfunction parseBinding(\n value: unknown,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderBinding | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[] must be an object.'));\n return undefined;\n }\n const bindingId = readRequiredText(value.id, 'bindings[].id', MAX_BINDING_ID_LENGTH, source, diagnostics);\n if (!bindingId) return undefined;\n if (!SAFE_IDENTIFIER.test(bindingId)) {\n diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));\n return undefined;\n }\n if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {\n diagnostics.push(\n diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId),\n );\n return undefined;\n }\n\n const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));\n const hasCommand = Object.hasOwn(value, 'command');\n const hasAction = Object.hasOwn(value, 'action');\n if (hasCommand === hasAction) {\n diagnostics.push(diagnostic(source, 'bindings[] must contain exactly one of command or action.', bindingId));\n return undefined;\n }\n const target = hasCommand\n ? { command: parseCommand(value.command, source, bindingId, diagnostics) }\n : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };\n if (path.some((segment) => segment === undefined)) return undefined;\n if ('command' in target) {\n if (!target.command) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n command: target.command,\n };\n }\n if (!target.action) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: target.action,\n };\n}\n\nfunction parseContribution(value: unknown): ParsedContribution {\n const diagnostics: DoomLeaderDiagnostic[] = [];\n if (!isRecord(value)) {\n return { diagnostics: [diagnostic(UNKNOWN_SOURCE, 'Leader contribution must be an object.')] };\n }\n const source = readRequiredText(value.source, 'source', MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);\n if (!source) return { diagnostics };\n if (!SAFE_SOURCE.test(source)) {\n diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));\n return { diagnostics };\n }\n if (!Array.isArray(value.bindings)) {\n return { diagnostics: [diagnostic(source, 'bindings must be an array.')] };\n }\n\n const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));\n const duplicateIds = bindings\n .flatMap((binding) => (binding ? [binding.id] : []))\n .filter((id, index, ids) => ids.indexOf(id) !== index);\n for (const id of new Set(duplicateIds)) {\n diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));\n }\n if (diagnostics.length > 0 || bindings.some((binding) => binding === undefined)) return { diagnostics };\n\n return {\n contribution: {\n source,\n bindings: bindings as DoomLeaderBinding[],\n },\n diagnostics,\n };\n}\n\nfunction optionOrder(segment: DoomLeaderSegment): number {\n return segment.order ?? DEFAULT_OPTION_ORDER;\n}\n\nfunction cloneAction(action: DoomLeaderAction): DoomLeaderAction {\n if (action.type === 'command') return { type: 'command', command: { ...action.command } };\n if (action.type === 'app') return { type: 'app', action: action.action };\n return { type: 'extension', source: action.source, action: { ...action.action } };\n}\n\nfunction createRootNode(): LeaderNode {\n return {\n key: '',\n label: ROOT_LABEL,\n order: 0,\n owner: CORE_SOURCE,\n children: new Map(),\n };\n}\n\nfunction pathLabel(path: readonly DoomLeaderSegment[]): string {\n return `SPC ${path.map((segment) => segment.key).join(' ')}`;\n}\n\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.detail === segment.detail && node.order === optionOrder(segment);\n}\n\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic | undefined {\n let current = root;\n let firstMissing = -1;\n\n for (const [index, segment] of binding.path.entries()) {\n const existing = current.children.get(segment.key);\n if (!existing) {\n firstMissing = index;\n break;\n }\n if (!metadataMatches(existing, segment)) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} conflicts with metadata from ${existing.owner}.`,\n binding.id,\n );\n }\n const isLeaf = index === binding.path.length - 1;\n if ((!isLeaf && existing.action) || (isLeaf && (existing.action || existing.children.size > 0))) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path)} conflicts with an existing binding from ${existing.owner}.`,\n binding.id,\n );\n }\n current = existing;\n }\n\n const start = firstMissing === -1 ? binding.path.length : firstMissing;\n for (let index = start; index < binding.path.length; index += 1) {\n const segment = binding.path[index] as DoomLeaderSegment;\n const node: LeaderNode = {\n key: segment.key,\n label: segment.label,\n ...(segment.detail ? { detail: segment.detail } : {}),\n order: optionOrder(segment),\n owner: binding.source,\n children: new Map(),\n };\n current.children.set(segment.key, node);\n current = node;\n }\n\n current.action = cloneAction(binding.action);\n current.bindingId = binding.id;\n return undefined;\n}\n\nexport class DoomLeaderRegistry {\n private root = createRootNode();\n private readonly contributions = new Map<string, readonly DoomLeaderBinding[]>();\n private readonly listeners = new Set<() => void>();\n private diagnostics: DoomLeaderDiagnostic[] = [];\n private dirty = false;\n\n constructor() {\n this.rebuild();\n }\n\n register(value: unknown): DoomLeaderRegistrationResult {\n const result = this.apply(value);\n if (!result.accepted) return result;\n this.rebuild();\n return { accepted: true, diagnostics: this.getDiagnostics() };\n }\n\n /** Records a contribution without rebuilding the whole tree immediately. */\n registerDeferred(value: unknown): DoomLeaderRegistrationResult {\n return this.apply(value);\n }\n\n private apply(value: unknown): DoomLeaderRegistrationResult {\n const parsed = parseContribution(value);\n if (!parsed.contribution) return { accepted: false, diagnostics: parsed.diagnostics };\n\n if (parsed.contribution.bindings.length === 0) this.contributions.delete(parsed.contribution.source);\n else this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n this.dirty = true;\n return { accepted: true, diagnostics: parsed.diagnostics };\n }\n\n /** Rebuilds one batch and returns its deterministic conflict diagnostics. */\n flush(): DoomLeaderDiagnostic[] {\n if (this.dirty) this.rebuild();\n return this.diagnostics.map((item) => ({ ...item }));\n }\n\n getGroup(path: readonly string[]): DoomLeaderGroup | undefined {\n this.flush();\n let current = this.root;\n for (const key of path) {\n const next = current.children.get(key);\n if (!next || next.action) return undefined;\n current = next;\n }\n\n // The key is the sort, at every level. A reader looking for a chord knows the\n // key, not the number some contributor picked for it, and those numbers live\n // across nine packages so no one of them can see the sequence they produce.\n // `order` survives only as group identity: co-contributors to a shared prefix\n // must still agree on it, which is what `metadataMatches` checks.\n const options = [...current.children.values()]\n .sort((left, right) => left.key.localeCompare(right.key))\n .map((node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\n }));\n return { label: current.label, options };\n }\n\n getDiagnostics(): DoomLeaderDiagnostic[] {\n return this.flush();\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private rebuild(): void {\n const nextRoot = createRootNode();\n const diagnostics: DoomLeaderDiagnostic[] = [];\n for (const binding of CORE_BINDINGS) {\n const issue = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n for (const [source, bindings] of [...this.contributions.entries()].sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n const issue = insertBinding(nextRoot, {\n id: binding.id,\n source,\n path: binding.path,\n action:\n 'command' in binding\n ? { type: 'command', command: binding.command }\n : { type: 'extension', source, action: binding.action },\n });\n if (issue) diagnostics.push(issue);\n }\n }\n\n this.root = nextRoot;\n this.diagnostics = diagnostics;\n this.dirty = false;\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"leaderRegistry.mjs","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"sourcesContent":["type AppKeybinding =\n | 'app.model.select'\n | 'app.model.cycleForward'\n | 'app.thinking.cycle'\n | 'app.session.new'\n | 'app.session.resume'\n | 'app.session.tree'\n | 'app.session.fork'\n | 'app.editor.external'\n | 'app.exit';\n\nimport type {\n LeaderBinding as DoomLeaderBinding,\n LeaderCommand as DoomLeaderCommand,\n LeaderContribution as DoomLeaderContribution,\n LeaderAction as DoomLeaderExtensionAction,\n LeaderSegment as DoomLeaderSegment,\n LeaderTone as DoomLeaderTone,\n} from '@agimon-ai/doompi-extension-contracts/leader';\n\ninterface AppLeaderAction {\n type: 'app';\n action: AppKeybinding;\n}\n\ninterface CommandLeaderAction {\n type: 'command';\n command: DoomLeaderCommand;\n}\n\ninterface ExtensionLeaderAction {\n type: 'extension';\n source: string;\n action: DoomLeaderExtensionAction;\n}\n\nexport type DoomLeaderAction = AppLeaderAction | CommandLeaderAction | ExtensionLeaderAction;\n\nexport interface DoomLeaderResolvedOption {\n key: string;\n label: string;\n detail?: string;\n tone?: DoomLeaderTone;\n hasChildren: boolean;\n action?: DoomLeaderAction;\n}\n\nexport interface DoomLeaderGroup {\n label: string;\n options: readonly DoomLeaderResolvedOption[];\n}\n\nexport interface DoomLeaderDiagnostic {\n source: string;\n bindingId?: string;\n message: string;\n}\n\nexport interface DoomLeaderRegistrationResult {\n accepted: boolean;\n diagnostics: readonly DoomLeaderDiagnostic[];\n}\n\ninterface InternalLeaderBinding {\n id: string;\n source: string;\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]];\n action: DoomLeaderAction;\n}\n\ninterface LeaderNode {\n key: string;\n label: string;\n detail?: string;\n tone?: DoomLeaderTone;\n order: number;\n owner: string;\n bindingId?: string;\n children: Map<string, LeaderNode>;\n action?: DoomLeaderAction;\n}\n\ninterface ParsedContribution {\n contribution?: DoomLeaderContribution;\n diagnostics: DoomLeaderDiagnostic[];\n}\n\nconst CORE_SOURCE = '@agimon-ai/doompi-ui';\nconst UNKNOWN_SOURCE = 'unknown';\nconst ROOT_LABEL = 'leader';\nconst DEFAULT_OPTION_ORDER = 500;\nconst MAX_OPTION_ORDER = 1000;\nconst MAX_PATH_LENGTH = 4;\nconst MAX_SOURCE_LENGTH = 128;\nconst MAX_BINDING_ID_LENGTH = 128;\nconst MAX_LABEL_LENGTH = 32;\nconst MAX_DETAIL_LENGTH = 80;\nconst MAX_COMMAND_NAME_LENGTH = 64;\nconst MAX_COMMAND_ARGUMENT_LENGTH = 256;\nconst MAX_ACTION_NAME_LENGTH = 128;\nconst SAFE_KEY = /^[a-z0-9]$/;\nconst SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;\nconst SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;\nconst SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;\nconst C0_CONTROL_MAX = 31;\nconst DELETE_CHARACTER = 127;\nconst MODEL_GROUP = { key: 'm', label: 'models', detail: 'model and thinking level', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', detail: 'history, forks and branches', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', detail: 'tools, skills and config', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', detail: 'package docs and logs', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', detail: 'leave the session', order: 90 } as const;\n\nfunction coreBinding(\n id: string,\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: DoomLeaderAction,\n): InternalLeaderBinding {\n return { id, source: CORE_SOURCE, path, action };\n}\n\nconst CORE_BINDINGS: readonly InternalLeaderBinding[] = [\n coreBinding('model.select', [MODEL_GROUP, { key: 'm', label: 'select', detail: 'choose model', order: 10 }], {\n type: 'app',\n action: 'app.model.select',\n }),\n coreBinding('model.next', [MODEL_GROUP, { key: 'n', label: 'next', detail: 'cycle model', order: 20 }], {\n type: 'app',\n action: 'app.model.cycleForward',\n }),\n coreBinding('thinking.cycle', [MODEL_GROUP, { key: 't', label: 'thinking', detail: 'cycle level', order: 30 }], {\n type: 'app',\n action: 'app.thinking.cycle',\n }),\n coreBinding('tools.open', [EXTENSION_GROUP, { key: 't', label: 'tools', detail: 'browse tools', order: 20 }], {\n type: 'command',\n command: { name: 'tools' },\n }),\n // Core rather than contributed, so no extension owns the key and the panel is\n // reachable even when nothing has contributed a section to it yet.\n coreBinding('config.open', [EXTENSION_GROUP, { key: 'c', label: 'config', detail: 'settings', order: 30 }], {\n type: 'command',\n command: { name: 'config' },\n }),\n coreBinding('session.new', [SESSION_GROUP, { key: 'n', label: 'new', detail: 'fresh session', order: 10 }], {\n type: 'app',\n action: 'app.session.new',\n }),\n coreBinding('session.resume', [SESSION_GROUP, { key: 'r', label: 'resume', detail: 'open history', order: 20 }], {\n type: 'app',\n action: 'app.session.resume',\n }),\n coreBinding('session.tree', [SESSION_GROUP, { key: 't', label: 'tree', detail: 'branch view', order: 30 }], {\n type: 'app',\n action: 'app.session.tree',\n }),\n coreBinding('session.fork', [SESSION_GROUP, { key: 'f', label: 'fork', detail: 'from selection', order: 40 }], {\n type: 'app',\n action: 'app.session.fork',\n }),\n coreBinding('editor.external', [EXTENSION_GROUP, { key: 'e', label: 'external', detail: 'open editor', order: 10 }], {\n type: 'app',\n action: 'app.editor.external',\n }),\n coreBinding('help.hotkeys', [HELP_GROUP, { key: 'h', label: 'hotkeys', detail: 'show bindings' }], {\n type: 'command',\n command: { name: 'hotkeys' },\n }),\n coreBinding('app.exit', [QUIT_GROUP, { key: 'q', label: 'quit', detail: 'exit Pi' }], {\n type: 'app',\n action: 'app.exit',\n }),\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction diagnostic(source: string, message: string, bindingId?: string): DoomLeaderDiagnostic {\n return { source, message, ...(bindingId ? { bindingId } : {}) };\n}\n\nfunction containsControlCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;\n }\n return false;\n}\n\nfunction readRequiredText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId?: string,\n): string | undefined {\n if (typeof value !== 'string' || !value.trim()) {\n diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));\n return undefined;\n }\n const text = value.trim();\n if (text.length > maximumLength || containsControlCharacter(text)) {\n diagnostics.push(\n diagnostic(\n source,\n `${field} must be at most ${maximumLength} characters and contain no control characters.`,\n bindingId,\n ),\n );\n return undefined;\n }\n return text;\n}\n\nfunction readOptionalText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);\n}\n\nfunction parseSegment(\n value: unknown,\n source: string,\n bindingId: string,\n index: number,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderSegment | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));\n return undefined;\n }\n\n const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);\n const label = readRequiredText(\n value.label,\n `bindings[].path[${index}].label`,\n MAX_LABEL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const detail = readOptionalText(\n value.detail,\n `bindings[].path[${index}].detail`,\n MAX_DETAIL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n // An unrecognised tone is ignored, not diagnosed. Every other malformed field\n // here rejects the whole contribution, which is right for a key or a label but\n // not for a badge colour: it would mean a package compiled against a later\n // contract loses its entire menu on an older host over a cosmetic value. The\n // union type already catches a typo at every call site built against this\n // contract, which is the only place a typo can come from.\n const tone: DoomLeaderTone | undefined = value.tone === 'default' || value.tone === 'exit' ? value.tone : undefined;\n let order: number | undefined;\n if (value.order !== undefined) {\n if (!Number.isInteger(value.order) || (value.order as number) < 0 || (value.order as number) > MAX_OPTION_ORDER) {\n diagnostics.push(\n diagnostic(\n source,\n `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`,\n bindingId,\n ),\n );\n } else {\n order = value.order as number;\n }\n }\n if (!key || !label || !SAFE_KEY.test(key)) {\n if (key && !SAFE_KEY.test(key)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));\n }\n return undefined;\n }\n return {\n key,\n label,\n ...(detail ? { detail } : {}),\n ...(tone ? { tone } : {}),\n ...(order === undefined ? {} : { order }),\n };\n}\n\nfunction parseCommand(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderCommand | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].command must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].command.name',\n MAX_COMMAND_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const args = readOptionalText(\n value.args,\n 'bindings[].command.args',\n MAX_COMMAND_ARGUMENT_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_COMMAND_NAME.test(name)) {\n if (name && !SAFE_COMMAND_NAME.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));\n }\n return undefined;\n }\n return { name, ...(args ? { args } : {}) };\n}\n\nfunction parseExtensionAction(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderExtensionAction | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].action must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].action.name',\n MAX_ACTION_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_IDENTIFIER.test(name)) {\n if (name && !SAFE_IDENTIFIER.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));\n }\n return undefined;\n }\n return { name };\n}\n\nfunction parseBinding(\n value: unknown,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderBinding | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[] must be an object.'));\n return undefined;\n }\n const bindingId = readRequiredText(value.id, 'bindings[].id', MAX_BINDING_ID_LENGTH, source, diagnostics);\n if (!bindingId) return undefined;\n if (!SAFE_IDENTIFIER.test(bindingId)) {\n diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));\n return undefined;\n }\n if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {\n diagnostics.push(\n diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId),\n );\n return undefined;\n }\n\n const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));\n const hasCommand = Object.hasOwn(value, 'command');\n const hasAction = Object.hasOwn(value, 'action');\n if (hasCommand === hasAction) {\n diagnostics.push(diagnostic(source, 'bindings[] must contain exactly one of command or action.', bindingId));\n return undefined;\n }\n const target = hasCommand\n ? { command: parseCommand(value.command, source, bindingId, diagnostics) }\n : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };\n if (path.some((segment) => segment === undefined)) return undefined;\n if ('command' in target) {\n if (!target.command) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n command: target.command,\n };\n }\n if (!target.action) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: target.action,\n };\n}\n\nfunction parseContribution(value: unknown): ParsedContribution {\n const diagnostics: DoomLeaderDiagnostic[] = [];\n if (!isRecord(value)) {\n return { diagnostics: [diagnostic(UNKNOWN_SOURCE, 'Leader contribution must be an object.')] };\n }\n const source = readRequiredText(value.source, 'source', MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);\n if (!source) return { diagnostics };\n if (!SAFE_SOURCE.test(source)) {\n diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));\n return { diagnostics };\n }\n if (!Array.isArray(value.bindings)) {\n return { diagnostics: [diagnostic(source, 'bindings must be an array.')] };\n }\n\n const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));\n const duplicateIds = bindings\n .flatMap((binding) => (binding ? [binding.id] : []))\n .filter((id, index, ids) => ids.indexOf(id) !== index);\n for (const id of new Set(duplicateIds)) {\n diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));\n }\n if (diagnostics.length > 0 || bindings.some((binding) => binding === undefined)) return { diagnostics };\n\n return {\n contribution: {\n source,\n bindings: bindings as DoomLeaderBinding[],\n },\n diagnostics,\n };\n}\n\nfunction optionOrder(segment: DoomLeaderSegment): number {\n return segment.order ?? DEFAULT_OPTION_ORDER;\n}\n\nfunction cloneAction(action: DoomLeaderAction): DoomLeaderAction {\n if (action.type === 'command') return { type: 'command', command: { ...action.command } };\n if (action.type === 'app') return { type: 'app', action: action.action };\n return { type: 'extension', source: action.source, action: { ...action.action } };\n}\n\nfunction createRootNode(): LeaderNode {\n return {\n key: '',\n label: ROOT_LABEL,\n order: 0,\n owner: CORE_SOURCE,\n children: new Map(),\n };\n}\n\nfunction pathLabel(path: readonly DoomLeaderSegment[]): string {\n return `SPC ${path.map((segment) => segment.key).join(' ')}`;\n}\n\n/**\n * Group identity is the key, the label and the sort order. `detail` is left out\n * on purpose: it is a subtitle, and these packages ship on independent npm\n * versions, so requiring every co-contributor to a shared prefix to carry the\n * same wording means one upgraded package silently drops the menus of every\n * package that has not caught up. A subtitle disagreement is not a conflict\n * worth losing `SPC e` over.\n */\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.order === optionOrder(segment);\n}\n\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic | undefined {\n let current = root;\n let firstMissing = -1;\n\n for (const [index, segment] of binding.path.entries()) {\n const existing = current.children.get(segment.key);\n if (!existing) {\n firstMissing = index;\n break;\n }\n if (!metadataMatches(existing, segment)) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} conflicts with metadata from ${existing.owner}.`,\n binding.id,\n );\n }\n // First writer wins for the subtitle, but a group created without one still\n // adopts the first that is offered, so the wording does not depend on which\n // package happened to register first.\n if (!existing.detail && segment.detail) existing.detail = segment.detail;\n const isLeaf = index === binding.path.length - 1;\n if ((!isLeaf && existing.action) || (isLeaf && (existing.action || existing.children.size > 0))) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path)} conflicts with an existing binding from ${existing.owner}.`,\n binding.id,\n );\n }\n current = existing;\n }\n\n const start = firstMissing === -1 ? binding.path.length : firstMissing;\n for (let index = start; index < binding.path.length; index += 1) {\n const segment = binding.path[index] as DoomLeaderSegment;\n const node: LeaderNode = {\n key: segment.key,\n label: segment.label,\n ...(segment.detail ? { detail: segment.detail } : {}),\n ...(segment.tone ? { tone: segment.tone } : {}),\n order: optionOrder(segment),\n owner: binding.source,\n children: new Map(),\n };\n current.children.set(segment.key, node);\n current = node;\n }\n\n current.action = cloneAction(binding.action);\n current.bindingId = binding.id;\n return undefined;\n}\n\nexport class DoomLeaderRegistry {\n private root = createRootNode();\n private readonly contributions = new Map<string, readonly DoomLeaderBinding[]>();\n private readonly listeners = new Set<() => void>();\n private diagnostics: DoomLeaderDiagnostic[] = [];\n private dirty = false;\n\n constructor() {\n this.rebuild();\n }\n\n register(value: unknown): DoomLeaderRegistrationResult {\n const result = this.apply(value);\n if (!result.accepted) return result;\n this.rebuild();\n return { accepted: true, diagnostics: this.getDiagnostics() };\n }\n\n /** Records a contribution without rebuilding the whole tree immediately. */\n registerDeferred(value: unknown): DoomLeaderRegistrationResult {\n return this.apply(value);\n }\n\n private apply(value: unknown): DoomLeaderRegistrationResult {\n const parsed = parseContribution(value);\n if (!parsed.contribution) return { accepted: false, diagnostics: parsed.diagnostics };\n\n if (parsed.contribution.bindings.length === 0) this.contributions.delete(parsed.contribution.source);\n else this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n this.dirty = true;\n return { accepted: true, diagnostics: parsed.diagnostics };\n }\n\n /** Rebuilds one batch and returns its deterministic conflict diagnostics. */\n flush(): DoomLeaderDiagnostic[] {\n if (this.dirty) this.rebuild();\n return this.diagnostics.map((item) => ({ ...item }));\n }\n\n getGroup(path: readonly string[]): DoomLeaderGroup | undefined {\n this.flush();\n let current = this.root;\n for (const key of path) {\n const next = current.children.get(key);\n if (!next || next.action) return undefined;\n current = next;\n }\n\n // The key is the sort, at every level. A reader looking for a chord knows the\n // key, not the number some contributor picked for it, and those numbers live\n // across nine packages so no one of them can see the sequence they produce.\n // `order` survives only as group identity: co-contributors to a shared prefix\n // must still agree on it, which is what `metadataMatches` checks.\n const options = [...current.children.values()]\n .sort((left, right) => left.key.localeCompare(right.key))\n .map((node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n ...(node.tone ? { tone: node.tone } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\n }));\n return { label: current.label, options };\n }\n\n getDiagnostics(): DoomLeaderDiagnostic[] {\n return this.flush();\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private rebuild(): void {\n const nextRoot = createRootNode();\n const diagnostics: DoomLeaderDiagnostic[] = [];\n for (const binding of CORE_BINDINGS) {\n const issue = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n for (const [source, bindings] of [...this.contributions.entries()].sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n const issue = insertBinding(nextRoot, {\n id: binding.id,\n source,\n path: binding.path,\n action:\n 'command' in binding\n ? { type: 'command', command: binding.command }\n : { type: 'extension', source, action: binding.action },\n });\n if (issue) diagnostics.push(issue);\n }\n }\n\n this.root = nextRoot;\n this.diagnostics = diagnostics;\n this.dirty = false;\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uiState.cjs","names":[],"sources":["../../../src/services/state/uiState.ts"],"sourcesContent":["import type { MinorModeRecord, ModeTextColor } from '@agimon-ai/doompi-extension-contracts/mode';\n\nexport interface LeaderOption {\n key: string;\n label: string;\n detail?: string;\n}\n\nexport interface LeaderSnapshot {\n active: boolean;\n prefix: string[];\n label: string;\n options: LeaderOption[];\n rootOptions?: LeaderOption[];\n}\n\nexport interface UiMinorModeStatus {\n source: string;\n id: string;\n label: string;\n detail?: string;\n color?: ModeTextColor;\n order: number;\n}\n\ntype StateListener = (snapshot: LeaderSnapshot) => void;\n\nconst INACTIVE_SNAPSHOT: LeaderSnapshot = {\n active: false,\n prefix: [],\n label: '',\n options: [],\n};\n\nfunction cloneSnapshot(snapshot: LeaderSnapshot): LeaderSnapshot {\n return {\n active: snapshot.active,\n prefix: [...snapshot.prefix],\n label: snapshot.label,\n options: snapshot.options.map((option) => ({ ...option })),\n ...(snapshot.rootOptions ? { rootOptions: snapshot.rootOptions.map((option) => ({ ...option })) } : {}),\n };\n}\n\nexport function projectMinorModeRecords(records: readonly MinorModeRecord[]): UiMinorModeStatus[] {\n return records\n .filter(({ state }) => state.activation !== 'inactive')\n .map(({ descriptor, state }) => ({\n source: descriptor.source,\n id: descriptor.id,\n label: descriptor.label,\n order: descriptor.order,\n ...(state.detail ? { detail: state.detail } : {}),\n ...(state.color ? { color: state.color } : {}),\n }))\n .sort(\n (left, right) =>\n left.order - right.order || left.source.localeCompare(right.source) || left.id.localeCompare(right.id),\n );\n}\n\nexport class DoomUiState {\n private snapshot = INACTIVE_SNAPSHOT;\n private modes: readonly UiMinorModeStatus[] = [];\n private readonly listeners = new Set<StateListener>();\n\n getSnapshot(): LeaderSnapshot {\n return cloneSnapshot(this.snapshot);\n }\n\n getModes(): readonly UiMinorModeStatus[] {\n return this.modes;\n }\n\n setModes(modes: readonly UiMinorModeStatus[]): void {\n this.modes = modes.map((mode) => ({ ...mode }));\n for (const listener of this.listeners) listener(this.getSnapshot());\n }\n\n setLeader(snapshot: LeaderSnapshot): void {\n this.snapshot = cloneSnapshot(snapshot);\n for (const listener of this.listeners) listener(this.getSnapshot());\n }\n\n reset(): void {\n this.modes = [];\n this.setLeader(INACTIVE_SNAPSHOT);\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"uiState.cjs","names":[],"sources":["../../../src/services/state/uiState.ts"],"sourcesContent":["import type { LeaderTone } from '@agimon-ai/doompi-extension-contracts/leader';\nimport type { MinorModeRecord, ModeTextColor } from '@agimon-ai/doompi-extension-contracts/mode';\n\nexport interface LeaderOption {\n key: string;\n label: string;\n detail?: string;\n tone?: LeaderTone;\n}\n\nexport interface LeaderSnapshot {\n active: boolean;\n prefix: string[];\n label: string;\n options: LeaderOption[];\n rootOptions?: LeaderOption[];\n}\n\nexport interface UiMinorModeStatus {\n source: string;\n id: string;\n label: string;\n detail?: string;\n color?: ModeTextColor;\n order: number;\n}\n\ntype StateListener = (snapshot: LeaderSnapshot) => void;\n\nconst INACTIVE_SNAPSHOT: LeaderSnapshot = {\n active: false,\n prefix: [],\n label: '',\n options: [],\n};\n\nfunction cloneSnapshot(snapshot: LeaderSnapshot): LeaderSnapshot {\n return {\n active: snapshot.active,\n prefix: [...snapshot.prefix],\n label: snapshot.label,\n options: snapshot.options.map((option) => ({ ...option })),\n ...(snapshot.rootOptions ? { rootOptions: snapshot.rootOptions.map((option) => ({ ...option })) } : {}),\n };\n}\n\nexport function projectMinorModeRecords(records: readonly MinorModeRecord[]): UiMinorModeStatus[] {\n return records\n .filter(({ state }) => state.activation !== 'inactive')\n .map(({ descriptor, state }) => ({\n source: descriptor.source,\n id: descriptor.id,\n label: descriptor.label,\n order: descriptor.order,\n ...(state.detail ? { detail: state.detail } : {}),\n ...(state.color ? { color: state.color } : {}),\n }))\n .sort(\n (left, right) =>\n left.order - right.order || left.source.localeCompare(right.source) || left.id.localeCompare(right.id),\n );\n}\n\nexport class DoomUiState {\n private snapshot = INACTIVE_SNAPSHOT;\n private modes: readonly UiMinorModeStatus[] = [];\n private readonly listeners = new Set<StateListener>();\n\n getSnapshot(): LeaderSnapshot {\n return cloneSnapshot(this.snapshot);\n }\n\n getModes(): readonly UiMinorModeStatus[] {\n return this.modes;\n }\n\n setModes(modes: readonly UiMinorModeStatus[]): void {\n this.modes = modes.map((mode) => ({ ...mode }));\n for (const listener of this.listeners) listener(this.getSnapshot());\n }\n\n setLeader(snapshot: LeaderSnapshot): void {\n this.snapshot = cloneSnapshot(snapshot);\n for (const listener of this.listeners) listener(this.getSnapshot());\n }\n\n reset(): void {\n this.modes = [];\n this.setLeader(INACTIVE_SNAPSHOT);\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n"],"mappings":"AA6BA,MAAM,EAAoC,CACxC,OAAQ,GACR,OAAQ,CAAC,EACT,MAAO,GACP,QAAS,CAAC,CACZ,EAEA,SAAS,EAAc,EAA0C,CAC/D,MAAO,CACL,OAAQ,EAAS,OACjB,OAAQ,CAAC,GAAG,EAAS,MAAM,EAC3B,MAAO,EAAS,MAChB,QAAS,EAAS,QAAQ,IAAK,IAAY,CAAE,GAAG,CAAO,EAAE,EACzD,GAAI,EAAS,YAAc,CAAE,YAAa,EAAS,YAAY,IAAK,IAAY,CAAE,GAAG,CAAO,EAAE,CAAE,EAAI,CAAC,CACvG,CACF,CAEA,SAAgB,EAAwB,EAA0D,CAChG,OAAO,EACJ,QAAQ,CAAE,WAAY,EAAM,aAAe,UAAU,CAAC,CACtD,KAAK,CAAE,aAAY,YAAa,CAC/B,OAAQ,EAAW,OACnB,GAAI,EAAW,GACf,MAAO,EAAW,MAClB,MAAO,EAAW,MAClB,GAAI,EAAM,OAAS,CAAE,OAAQ,EAAM,MAAO,EAAI,CAAC,EAC/C,GAAI,EAAM,MAAQ,CAAE,MAAO,EAAM,KAAM,EAAI,CAAC,CAC9C,EAAE,CAAC,CACF,MACE,EAAM,IACL,EAAK,MAAQ,EAAM,OAAS,EAAK,OAAO,cAAc,EAAM,MAAM,GAAK,EAAK,GAAG,cAAc,EAAM,EAAE,CACzG,CACJ,CAEA,IAAa,EAAb,KAAyB,CACvB,SAAmB,EACnB,MAA8C,CAAC,EAC/C,UAA6B,IAAI,IAEjC,aAA8B,CAC5B,OAAO,EAAc,KAAK,QAAQ,CACpC,CAEA,UAAyC,CACvC,OAAO,KAAK,KACd,CAEA,SAAS,EAA2C,CAClD,KAAK,MAAQ,EAAM,IAAK,IAAU,CAAE,GAAG,CAAK,EAAE,EAC9C,IAAK,IAAM,KAAY,KAAK,UAAW,EAAS,KAAK,YAAY,CAAC,CACpE,CAEA,UAAU,EAAgC,CACxC,KAAK,SAAW,EAAc,CAAQ,EACtC,IAAK,IAAM,KAAY,KAAK,UAAW,EAAS,KAAK,YAAY,CAAC,CACpE,CAEA,OAAc,CACZ,KAAK,MAAQ,CAAC,EACd,KAAK,UAAU,CAAiB,CAClC,CAEA,UAAU,EAAqC,CAE7C,OADA,KAAK,UAAU,IAAI,CAAQ,MACd,KAAK,UAAU,OAAO,CAAQ,CAC7C,CACF"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { LeaderTone } from "@agimon-ai/doompi-extension-contracts/leader";
|
|
1
2
|
import { MinorModeRecord, ModeTextColor } from "@agimon-ai/doompi-extension-contracts/mode";
|
|
2
3
|
//#region src/services/state/uiState.d.ts
|
|
3
4
|
interface LeaderOption {
|
|
4
5
|
key: string;
|
|
5
6
|
label: string;
|
|
6
7
|
detail?: string;
|
|
8
|
+
tone?: LeaderTone;
|
|
7
9
|
}
|
|
8
10
|
interface LeaderSnapshot {
|
|
9
11
|
active: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uiState.d.cts","names":[],"sources":["../../../src/services/state/uiState.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"uiState.d.cts","names":[],"sources":["../../../src/services/state/uiState.ts"],"mappings":";;;UAGiB;EACf;EACA;EACA;EACA,OAAO;;UAGQ;EACf;EACA;EACA;EACA,SAAS;EACT,cAAc;;UAGC;EACf;EACA;EACA;EACA;EACA,QAAQ;EACR;;KAGG,iBAAiB,UAAU;iBAmBhB,wBAAwB,kBAAkB,oBAAoB;cAiBjE;UACH;UACA;mBACS;EAEjB,eAAe;EAIf,qBAAqB;EAIrB,SAAS,gBAAgB;EAKzB,UAAU,UAAU;EAKpB;EAKA,UAAU,UAAU"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { MinorModeRecord, ModeTextColor } from "@agimon-ai/doompi-extension-contracts/mode";
|
|
2
|
+
import { LeaderTone } from "@agimon-ai/doompi-extension-contracts/leader";
|
|
2
3
|
//#region src/services/state/uiState.d.ts
|
|
3
4
|
interface LeaderOption {
|
|
4
5
|
key: string;
|
|
5
6
|
label: string;
|
|
6
7
|
detail?: string;
|
|
8
|
+
tone?: LeaderTone;
|
|
7
9
|
}
|
|
8
10
|
interface LeaderSnapshot {
|
|
9
11
|
active: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uiState.d.mts","names":[],"sources":["../../../src/services/state/uiState.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"uiState.d.mts","names":[],"sources":["../../../src/services/state/uiState.ts"],"mappings":";;;UAGiB;EACf;EACA;EACA;EACA,OAAO;;UAGQ;EACf;EACA;EACA;EACA,SAAS;EACT,cAAc;;UAGC;EACf;EACA;EACA;EACA;EACA,QAAQ;EACR;;KAGG,iBAAiB,UAAU;iBAmBhB,wBAAwB,kBAAkB,oBAAoB;cAiBjE;UACH;UACA;mBACS;EAEjB,eAAe;EAIf,qBAAqB;EAIrB,SAAS,gBAAgB;EAKzB,UAAU,UAAU;EAKpB;EAKA,UAAU,UAAU"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uiState.mjs","names":[],"sources":["../../../src/services/state/uiState.ts"],"sourcesContent":["import type { MinorModeRecord, ModeTextColor } from '@agimon-ai/doompi-extension-contracts/mode';\n\nexport interface LeaderOption {\n key: string;\n label: string;\n detail?: string;\n}\n\nexport interface LeaderSnapshot {\n active: boolean;\n prefix: string[];\n label: string;\n options: LeaderOption[];\n rootOptions?: LeaderOption[];\n}\n\nexport interface UiMinorModeStatus {\n source: string;\n id: string;\n label: string;\n detail?: string;\n color?: ModeTextColor;\n order: number;\n}\n\ntype StateListener = (snapshot: LeaderSnapshot) => void;\n\nconst INACTIVE_SNAPSHOT: LeaderSnapshot = {\n active: false,\n prefix: [],\n label: '',\n options: [],\n};\n\nfunction cloneSnapshot(snapshot: LeaderSnapshot): LeaderSnapshot {\n return {\n active: snapshot.active,\n prefix: [...snapshot.prefix],\n label: snapshot.label,\n options: snapshot.options.map((option) => ({ ...option })),\n ...(snapshot.rootOptions ? { rootOptions: snapshot.rootOptions.map((option) => ({ ...option })) } : {}),\n };\n}\n\nexport function projectMinorModeRecords(records: readonly MinorModeRecord[]): UiMinorModeStatus[] {\n return records\n .filter(({ state }) => state.activation !== 'inactive')\n .map(({ descriptor, state }) => ({\n source: descriptor.source,\n id: descriptor.id,\n label: descriptor.label,\n order: descriptor.order,\n ...(state.detail ? { detail: state.detail } : {}),\n ...(state.color ? { color: state.color } : {}),\n }))\n .sort(\n (left, right) =>\n left.order - right.order || left.source.localeCompare(right.source) || left.id.localeCompare(right.id),\n );\n}\n\nexport class DoomUiState {\n private snapshot = INACTIVE_SNAPSHOT;\n private modes: readonly UiMinorModeStatus[] = [];\n private readonly listeners = new Set<StateListener>();\n\n getSnapshot(): LeaderSnapshot {\n return cloneSnapshot(this.snapshot);\n }\n\n getModes(): readonly UiMinorModeStatus[] {\n return this.modes;\n }\n\n setModes(modes: readonly UiMinorModeStatus[]): void {\n this.modes = modes.map((mode) => ({ ...mode }));\n for (const listener of this.listeners) listener(this.getSnapshot());\n }\n\n setLeader(snapshot: LeaderSnapshot): void {\n this.snapshot = cloneSnapshot(snapshot);\n for (const listener of this.listeners) listener(this.getSnapshot());\n }\n\n reset(): void {\n this.modes = [];\n this.setLeader(INACTIVE_SNAPSHOT);\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"uiState.mjs","names":[],"sources":["../../../src/services/state/uiState.ts"],"sourcesContent":["import type { LeaderTone } from '@agimon-ai/doompi-extension-contracts/leader';\nimport type { MinorModeRecord, ModeTextColor } from '@agimon-ai/doompi-extension-contracts/mode';\n\nexport interface LeaderOption {\n key: string;\n label: string;\n detail?: string;\n tone?: LeaderTone;\n}\n\nexport interface LeaderSnapshot {\n active: boolean;\n prefix: string[];\n label: string;\n options: LeaderOption[];\n rootOptions?: LeaderOption[];\n}\n\nexport interface UiMinorModeStatus {\n source: string;\n id: string;\n label: string;\n detail?: string;\n color?: ModeTextColor;\n order: number;\n}\n\ntype StateListener = (snapshot: LeaderSnapshot) => void;\n\nconst INACTIVE_SNAPSHOT: LeaderSnapshot = {\n active: false,\n prefix: [],\n label: '',\n options: [],\n};\n\nfunction cloneSnapshot(snapshot: LeaderSnapshot): LeaderSnapshot {\n return {\n active: snapshot.active,\n prefix: [...snapshot.prefix],\n label: snapshot.label,\n options: snapshot.options.map((option) => ({ ...option })),\n ...(snapshot.rootOptions ? { rootOptions: snapshot.rootOptions.map((option) => ({ ...option })) } : {}),\n };\n}\n\nexport function projectMinorModeRecords(records: readonly MinorModeRecord[]): UiMinorModeStatus[] {\n return records\n .filter(({ state }) => state.activation !== 'inactive')\n .map(({ descriptor, state }) => ({\n source: descriptor.source,\n id: descriptor.id,\n label: descriptor.label,\n order: descriptor.order,\n ...(state.detail ? { detail: state.detail } : {}),\n ...(state.color ? { color: state.color } : {}),\n }))\n .sort(\n (left, right) =>\n left.order - right.order || left.source.localeCompare(right.source) || left.id.localeCompare(right.id),\n );\n}\n\nexport class DoomUiState {\n private snapshot = INACTIVE_SNAPSHOT;\n private modes: readonly UiMinorModeStatus[] = [];\n private readonly listeners = new Set<StateListener>();\n\n getSnapshot(): LeaderSnapshot {\n return cloneSnapshot(this.snapshot);\n }\n\n getModes(): readonly UiMinorModeStatus[] {\n return this.modes;\n }\n\n setModes(modes: readonly UiMinorModeStatus[]): void {\n this.modes = modes.map((mode) => ({ ...mode }));\n for (const listener of this.listeners) listener(this.getSnapshot());\n }\n\n setLeader(snapshot: LeaderSnapshot): void {\n this.snapshot = cloneSnapshot(snapshot);\n for (const listener of this.listeners) listener(this.getSnapshot());\n }\n\n reset(): void {\n this.modes = [];\n this.setLeader(INACTIVE_SNAPSHOT);\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n"],"mappings":"AA6BA,MAAM,EAAoC,CACxC,OAAQ,GACR,OAAQ,CAAC,EACT,MAAO,GACP,QAAS,CAAC,CACZ,EAEA,SAAS,EAAc,EAA0C,CAC/D,MAAO,CACL,OAAQ,EAAS,OACjB,OAAQ,CAAC,GAAG,EAAS,MAAM,EAC3B,MAAO,EAAS,MAChB,QAAS,EAAS,QAAQ,IAAK,IAAY,CAAE,GAAG,CAAO,EAAE,EACzD,GAAI,EAAS,YAAc,CAAE,YAAa,EAAS,YAAY,IAAK,IAAY,CAAE,GAAG,CAAO,EAAE,CAAE,EAAI,CAAC,CACvG,CACF,CAEA,SAAgB,EAAwB,EAA0D,CAChG,OAAO,EACJ,QAAQ,CAAE,WAAY,EAAM,aAAe,UAAU,CAAC,CACtD,KAAK,CAAE,aAAY,YAAa,CAC/B,OAAQ,EAAW,OACnB,GAAI,EAAW,GACf,MAAO,EAAW,MAClB,MAAO,EAAW,MAClB,GAAI,EAAM,OAAS,CAAE,OAAQ,EAAM,MAAO,EAAI,CAAC,EAC/C,GAAI,EAAM,MAAQ,CAAE,MAAO,EAAM,KAAM,EAAI,CAAC,CAC9C,EAAE,CAAC,CACF,MACE,EAAM,IACL,EAAK,MAAQ,EAAM,OAAS,EAAK,OAAO,cAAc,EAAM,MAAM,GAAK,EAAK,GAAG,cAAc,EAAM,EAAE,CACzG,CACJ,CAEA,IAAa,EAAb,KAAyB,CACvB,SAAmB,EACnB,MAA8C,CAAC,EAC/C,UAA6B,IAAI,IAEjC,aAA8B,CAC5B,OAAO,EAAc,KAAK,QAAQ,CACpC,CAEA,UAAyC,CACvC,OAAO,KAAK,KACd,CAEA,SAAS,EAA2C,CAClD,KAAK,MAAQ,EAAM,IAAK,IAAU,CAAE,GAAG,CAAK,EAAE,EAC9C,IAAK,IAAM,KAAY,KAAK,UAAW,EAAS,KAAK,YAAY,CAAC,CACpE,CAEA,UAAU,EAAgC,CACxC,KAAK,SAAW,EAAc,CAAQ,EACtC,IAAK,IAAM,KAAY,KAAK,UAAW,EAAS,KAAK,YAAY,CAAC,CACpE,CAEA,OAAc,CACZ,KAAK,MAAQ,CAAC,EACd,KAAK,UAAU,CAAiB,CAClC,CAEA,UAAU,EAAqC,CAE7C,OADA,KAAK,UAAU,IAAI,CAAQ,MACd,KAAK,UAAU,OAAO,CAAQ,CAC7C,CACF"}
|
package/dist/tui/doomEditor.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../services/leader/leaderRegistry.cjs"),t=require("./rendering.cjs"),n=require("../types/harnessMetadata.cjs");let r=require("@earendil-works/pi-tui"),i=require("@earendil-works/pi-coding-agent");const a=`borderAccent`,o=`accent`,s={bg:(e,t)=>t,bold:e=>e,fg:(e,t)=>t,inverse:e=>e},c={active:!1,prefix:[],label:``,options:[]};var l=class extends i.CustomEditor{uiState;onSnapshot;chromeTheme;leaderRegistry;majorMode;isCommandAvailable;onUnavailableCommand;onUnavailableAction;onLeaderAction;unsubscribeLeaderRegistry;unsubscribeUiState;leaderActive=!1;leaderPath=[];constructor(t,r,i,a,o,c,l=s,u={}){super(t,r,i,c),this.uiState=a,this.onSnapshot=o,this.chromeTheme=l,this.leaderRegistry=u.registry??new e.DoomLeaderRegistry,this.majorMode=u.majorMode?.trim()||n.readDoomHarnessMetadata().majorMode,this.isCommandAvailable=u.isCommandAvailable??(()=>!0),this.onUnavailableCommand=u.onUnavailableCommand??(()=>void 0),this.onUnavailableAction=u.onUnavailableAction??(()=>void 0),this.onLeaderAction=u.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 i=this.findBottomBorder(r,n),s=r.slice(1,i),c=r.slice(i+1),l=this.chromeTheme.fg(a,`│`),u=this.chromeTheme.fg(a,`╭${`─`.repeat(e-2)}╮`),d=this.chromeTheme.fg(a,`╰${`─`.repeat(e-2)}╯`),f=this.editorLine(t.alignLine(` ${this.modeBadge()}${this.modeNames()}${this.domainNames()}`,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(o,`❯ `)):` `;return this.editorLine(`${r}${t}`,e,l)});return[u,f,`${this.chromeTheme.fg(a,`├`)}${this.chromeTheme.fg(`borderMuted`,`─`.repeat(e-2))}${this.chromeTheme.fg(a,`┤`)}`,...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(c)}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(` `)}`:this.majorMode.toUpperCase()}modeBadge(){let e=` ${this.modeLabel()} `,t=this.leaderActive?`mdHeading`:o;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=this.chromeTheme.fg(`dim`,` · `);return` ${e.map(e=>this.chromeTheme.fg(o,e.label.toLowerCase())).join(t)}`}domainNames(){let e=n.readDoomHarnessMetadata().domains;return e.length===0?``:this.chromeTheme.fg(`dim`,` ${e.join(`, `)}`)}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=l;
|
|
1
|
+
const e=require("../services/leader/leaderRegistry.cjs"),t=require("./rendering.cjs"),n=require("../types/harnessMetadata.cjs");let r=require("@earendil-works/pi-tui"),i=require("@earendil-works/pi-coding-agent");const a=`borderAccent`,o=`accent`,s={bg:(e,t)=>t,bold:e=>e,fg:(e,t)=>t,inverse:e=>e},c={active:!1,prefix:[],label:``,options:[]};var l=class extends i.CustomEditor{uiState;onSnapshot;chromeTheme;leaderRegistry;majorMode;isCommandAvailable;onUnavailableCommand;onUnavailableAction;onLeaderAction;unsubscribeLeaderRegistry;unsubscribeUiState;leaderActive=!1;leaderPath=[];constructor(t,r,i,a,o,c,l=s,u={}){super(t,r,i,c),this.uiState=a,this.onSnapshot=o,this.chromeTheme=l,this.leaderRegistry=u.registry??new e.DoomLeaderRegistry,this.majorMode=u.majorMode?.trim()||n.readDoomHarnessMetadata().majorMode,this.isCommandAvailable=u.isCommandAvailable??(()=>!0),this.onUnavailableCommand=u.onUnavailableCommand??(()=>void 0),this.onUnavailableAction=u.onUnavailableAction??(()=>void 0),this.onLeaderAction=u.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 i=this.findBottomBorder(r,n),s=r.slice(1,i),c=r.slice(i+1),l=this.chromeTheme.fg(a,`│`),u=this.chromeTheme.fg(a,`╭${`─`.repeat(e-2)}╮`),d=this.chromeTheme.fg(a,`╰${`─`.repeat(e-2)}╯`),f=this.editorLine(t.alignLine(` ${this.modeBadge()}${this.modeNames()}${this.domainNames()}`,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(o,`❯ `)):` `;return this.editorLine(`${r}${t}`,e,l)});return[u,f,`${this.chromeTheme.fg(a,`├`)}${this.chromeTheme.fg(`borderMuted`,`─`.repeat(e-2))}${this.chromeTheme.fg(a,`┤`)}`,...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(c)}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}:{},...e.tone?{tone:e.tone}:{}})),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(` `)}`:this.majorMode.toUpperCase()}modeBadge(){let e=` ${this.modeLabel()} `,t=this.leaderActive?`mdHeading`:o;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=this.chromeTheme.fg(`dim`,` · `);return` ${e.map(e=>this.chromeTheme.fg(o,e.label.toLowerCase())).join(t)}`}domainNames(){let e=n.readDoomHarnessMetadata().domains;return e.length===0?``:this.chromeTheme.fg(`dim`,` ${e.join(`, `)}`)}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=l;
|
|
2
2
|
//# sourceMappingURL=doomEditor.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doomEditor.cjs","names":["CustomEditor","DoomLeaderRegistry","readDoomHarnessMetadata","parseKey","fitLine","alignLine","frameLine","padLine"],"sources":["../../src/tui/doomEditor.ts"],"sourcesContent":["import { type AppKeybinding, CustomEditor, type KeybindingsManager, type Theme } from '@earendil-works/pi-coding-agent';\nimport { type EditorOptions, type EditorTheme, parseKey, type TUI } from '@earendil-works/pi-tui';\nimport { type DoomLeaderAction, type DoomLeaderGroup, DoomLeaderRegistry } from '../services/leader/leaderRegistry.ts';\nimport { type DoomUiState, type LeaderSnapshot } from '../services/state/uiState.ts';\nimport { readDoomHarnessMetadata } from '../types/harnessMetadata.ts';\nimport { alignLine, fitLine, frameLine, padLine } from './rendering.ts';\n\nexport interface DoomEditorLeaderOptions {\n /** Overrides the harness major mode shown while the leader is inactive. */\n majorMode?: string;\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 = ' · ';\nconst DOMAIN_SEPARATOR = ', ';\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 majorMode: string;\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.majorMode = leaderOptions.majorMode?.trim() || readDoomHarnessMetadata().majorMode;\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()}${this.domainNames()}`,\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 as AppKeybinding);\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 this.majorMode.toUpperCase();\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 separator = this.chromeTheme.fg(DIM_COLOR, MODE_SEPARATOR);\n const names = modes.map((mode) => this.chromeTheme.fg(ACCENT_COLOR, mode.label.toLowerCase())).join(separator);\n return ` ${names}`;\n }\n\n private domainNames(): string {\n const domains = readDoomHarnessMetadata().domains;\n if (domains.length === 0) return '';\n return this.chromeTheme.fg(DIM_COLOR, ` ${domains.join(DOMAIN_SEPARATOR)}`);\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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"}
|
|
1
|
+
{"version":3,"file":"doomEditor.cjs","names":["CustomEditor","DoomLeaderRegistry","readDoomHarnessMetadata","parseKey","fitLine","alignLine","frameLine","padLine"],"sources":["../../src/tui/doomEditor.ts"],"sourcesContent":["import { type AppKeybinding, CustomEditor, type KeybindingsManager, type Theme } from '@earendil-works/pi-coding-agent';\nimport { type EditorOptions, type EditorTheme, parseKey, type TUI } from '@earendil-works/pi-tui';\nimport { type DoomLeaderAction, type DoomLeaderGroup, DoomLeaderRegistry } from '../services/leader/leaderRegistry.ts';\nimport { type DoomUiState, type LeaderSnapshot } from '../services/state/uiState.ts';\nimport { readDoomHarnessMetadata } from '../types/harnessMetadata.ts';\nimport { alignLine, fitLine, frameLine, padLine } from './rendering.ts';\n\nexport interface DoomEditorLeaderOptions {\n /** Overrides the harness major mode shown while the leader is inactive. */\n majorMode?: string;\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 = ' · ';\nconst DOMAIN_SEPARATOR = ', ';\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 majorMode: string;\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.majorMode = leaderOptions.majorMode?.trim() || readDoomHarnessMetadata().majorMode;\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()}${this.domainNames()}`,\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 ...(option.tone ? { tone: option.tone } : {}),\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 as AppKeybinding);\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 this.majorMode.toUpperCase();\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 separator = this.chromeTheme.fg(DIM_COLOR, MODE_SEPARATOR);\n const names = modes.map((mode) => this.chromeTheme.fg(ACCENT_COLOR, mode.label.toLowerCase())).join(separator);\n return ` ${names}`;\n }\n\n private domainNames(): string {\n const domains = readDoomHarnessMetadata().domains;\n if (domains.length === 0) return '';\n return this.chromeTheme.fg(DIM_COLOR, ` ${domains.join(DOMAIN_SEPARATOR)}`);\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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doomEditor.d.cts","names":[],"sources":["../../src/tui/doomEditor.ts"],"mappings":";;;;;UAOiB;;EAEf;EACA,WAAW;EACX,sBAAsB;EACtB,wBAAwB;EACxB,uBAAuB;EACvB,kBAAkB,gBAAgB;;KAG/B,mBAAmB,UAAU;KAC7B,cAAc,KAAK;cAoCX,mBAAmB;mBAiBX;mBACA;mBAjBF;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;UACA;EAER,YACE,KAAK,KACL,aAAa,aACb,aAAa,oBACI,SAAS,aACT,YAAY,iBAC7B,UAAU,eACV,cAAa,aACb,gBAAe;EAkBjB,YAAY;EAgBZ,OAAO;EA4CP;UAMQ;UAMA;UAMA;UAsCA;UAIA;
|
|
1
|
+
{"version":3,"file":"doomEditor.d.cts","names":[],"sources":["../../src/tui/doomEditor.ts"],"mappings":";;;;;UAOiB;;EAEf;EACA,WAAW;EACX,sBAAsB;EACtB,wBAAwB;EACxB,uBAAuB;EACvB,kBAAkB,gBAAgB;;KAG/B,mBAAmB,UAAU;KAC7B,cAAc,KAAK;cAoCX,mBAAmB;mBAiBX;mBACA;mBAjBF;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;UACA;EAER,YACE,KAAK,KACL,aAAa,aACb,aAAa,oBACI,SAAS,aACT,YAAY,iBAC7B,UAAU,eACV,cAAa,aACb,gBAAe;EAkBjB,YAAY;EAgBZ,OAAO;EA4CP;UAMQ;UAMA;UAMA;UAsCA;UAIA;UA0BA;UAMA;UAsBA;UAeA;UAKA;;;;;;;;;;;UAgBA;UAQA;UAMA;UAMA;UAMA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doomEditor.d.mts","names":[],"sources":["../../src/tui/doomEditor.ts"],"mappings":";;;;;UAOiB;;EAEf;EACA,WAAW;EACX,sBAAsB;EACtB,wBAAwB;EACxB,uBAAuB;EACvB,kBAAkB,gBAAgB;;KAG/B,mBAAmB,UAAU;KAC7B,cAAc,KAAK;cAoCX,mBAAmB;mBAiBX;mBACA;mBAjBF;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;UACA;EAER,YACE,KAAK,KACL,aAAa,aACb,aAAa,sBACI,SAAS,aACT,YAAY,iBAC7B,UAAU,eACV,cAAa,aACb,gBAAe;EAkBjB,YAAY;EAgBZ,OAAO;EA4CP;UAMQ;UAMA;UAMA;UAsCA;UAIA;
|
|
1
|
+
{"version":3,"file":"doomEditor.d.mts","names":[],"sources":["../../src/tui/doomEditor.ts"],"mappings":";;;;;UAOiB;;EAEf;EACA,WAAW;EACX,sBAAsB;EACtB,wBAAwB;EACxB,uBAAuB;EACvB,kBAAkB,gBAAgB;;KAG/B,mBAAmB,UAAU;KAC7B,cAAc,KAAK;cAoCX,mBAAmB;mBAiBX;mBACA;mBAjBF;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UACT;UACA;EAER,YACE,KAAK,KACL,aAAa,aACb,aAAa,sBACI,SAAS,aACT,YAAY,iBAC7B,UAAU,eACV,cAAa,aACb,gBAAe;EAkBjB,YAAY;EAgBZ,OAAO;EA4CP;UAMQ;UAMA;UAMA;UAsCA;UAIA;UA0BA;UAMA;UAsBA;UAeA;UAKA;;;;;;;;;;;UAgBA;UAQA;UAMA;UAMA;UAMA"}
|
package/dist/tui/doomEditor.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{DoomLeaderRegistry as e}from"../services/leader/leaderRegistry.mjs";import{alignLine as t,fitLine as n,frameLine as r,padLine as i}from"./rendering.mjs";import{readDoomHarnessMetadata as a}from"../types/harnessMetadata.mjs";import{parseKey as o}from"@earendil-works/pi-tui";import{CustomEditor as s}from"@earendil-works/pi-coding-agent";const c=`borderAccent`,l=`accent`,u={bg:(e,t)=>t,bold:e=>e,fg:(e,t)=>t,inverse:e=>e},d={active:!1,prefix:[],label:``,options:[]};var f=class extends s{uiState;onSnapshot;chromeTheme;leaderRegistry;majorMode;isCommandAvailable;onUnavailableCommand;onUnavailableAction;onLeaderAction;unsubscribeLeaderRegistry;unsubscribeUiState;leaderActive=!1;leaderPath=[];constructor(t,n,r,i,o,s,c=u,l={}){super(t,n,r,s),this.uiState=i,this.onSnapshot=o,this.chromeTheme=c,this.leaderRegistry=l.registry??new e,this.majorMode=l.majorMode?.trim()||a().majorMode,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=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),s=i.slice(a+1),u=this.chromeTheme.fg(c,`│`),d=this.chromeTheme.fg(c,`╭${`─`.repeat(e-2)}╮`),f=this.chromeTheme.fg(c,`╰${`─`.repeat(e-2)}╯`),p=this.editorLine(t(` ${this.modeBadge()}${this.modeNames()}${this.domainNames()}`,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(l,`❯ `)):` `;return this.editorLine(`${r}${t}`,e,u)});return[d,p,`${this.chromeTheme.fg(c,`├`)}${this.chromeTheme.fg(`borderMuted`,`─`.repeat(e-2))}${this.chromeTheme.fg(c,`┤`)}`,...m,f,...s.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(d)}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(` `)}`:this.majorMode.toUpperCase()}modeBadge(){let e=` ${this.modeLabel()} `,t=this.leaderActive?`mdHeading`:l;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=this.chromeTheme.fg(`dim`,` · `);return` ${e.map(e=>this.chromeTheme.fg(l,e.label.toLowerCase())).join(t)}`}domainNames(){let e=a().domains;return e.length===0?``:this.chromeTheme.fg(`dim`,` ${e.join(`, `)}`)}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{f as DoomEditor};
|
|
1
|
+
import{DoomLeaderRegistry as e}from"../services/leader/leaderRegistry.mjs";import{alignLine as t,fitLine as n,frameLine as r,padLine as i}from"./rendering.mjs";import{readDoomHarnessMetadata as a}from"../types/harnessMetadata.mjs";import{parseKey as o}from"@earendil-works/pi-tui";import{CustomEditor as s}from"@earendil-works/pi-coding-agent";const c=`borderAccent`,l=`accent`,u={bg:(e,t)=>t,bold:e=>e,fg:(e,t)=>t,inverse:e=>e},d={active:!1,prefix:[],label:``,options:[]};var f=class extends s{uiState;onSnapshot;chromeTheme;leaderRegistry;majorMode;isCommandAvailable;onUnavailableCommand;onUnavailableAction;onLeaderAction;unsubscribeLeaderRegistry;unsubscribeUiState;leaderActive=!1;leaderPath=[];constructor(t,n,r,i,o,s,c=u,l={}){super(t,n,r,s),this.uiState=i,this.onSnapshot=o,this.chromeTheme=c,this.leaderRegistry=l.registry??new e,this.majorMode=l.majorMode?.trim()||a().majorMode,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=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),s=i.slice(a+1),u=this.chromeTheme.fg(c,`│`),d=this.chromeTheme.fg(c,`╭${`─`.repeat(e-2)}╮`),f=this.chromeTheme.fg(c,`╰${`─`.repeat(e-2)}╯`),p=this.editorLine(t(` ${this.modeBadge()}${this.modeNames()}${this.domainNames()}`,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(l,`❯ `)):` `;return this.editorLine(`${r}${t}`,e,u)});return[d,p,`${this.chromeTheme.fg(c,`├`)}${this.chromeTheme.fg(`borderMuted`,`─`.repeat(e-2))}${this.chromeTheme.fg(c,`┤`)}`,...m,f,...s.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(d)}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}:{},...e.tone?{tone:e.tone}:{}})),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(` `)}`:this.majorMode.toUpperCase()}modeBadge(){let e=` ${this.modeLabel()} `,t=this.leaderActive?`mdHeading`:l;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=this.chromeTheme.fg(`dim`,` · `);return` ${e.map(e=>this.chromeTheme.fg(l,e.label.toLowerCase())).join(t)}`}domainNames(){let e=a().domains;return e.length===0?``:this.chromeTheme.fg(`dim`,` ${e.join(`, `)}`)}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{f as DoomEditor};
|
|
2
2
|
//# sourceMappingURL=doomEditor.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doomEditor.mjs","names":[],"sources":["../../src/tui/doomEditor.ts"],"sourcesContent":["import { type AppKeybinding, CustomEditor, type KeybindingsManager, type Theme } from '@earendil-works/pi-coding-agent';\nimport { type EditorOptions, type EditorTheme, parseKey, type TUI } from '@earendil-works/pi-tui';\nimport { type DoomLeaderAction, type DoomLeaderGroup, DoomLeaderRegistry } from '../services/leader/leaderRegistry.ts';\nimport { type DoomUiState, type LeaderSnapshot } from '../services/state/uiState.ts';\nimport { readDoomHarnessMetadata } from '../types/harnessMetadata.ts';\nimport { alignLine, fitLine, frameLine, padLine } from './rendering.ts';\n\nexport interface DoomEditorLeaderOptions {\n /** Overrides the harness major mode shown while the leader is inactive. */\n majorMode?: string;\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 = ' · ';\nconst DOMAIN_SEPARATOR = ', ';\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 majorMode: string;\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.majorMode = leaderOptions.majorMode?.trim() || readDoomHarnessMetadata().majorMode;\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()}${this.domainNames()}`,\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 as AppKeybinding);\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 this.majorMode.toUpperCase();\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 separator = this.chromeTheme.fg(DIM_COLOR, MODE_SEPARATOR);\n const names = modes.map((mode) => this.chromeTheme.fg(ACCENT_COLOR, mode.label.toLowerCase())).join(separator);\n return ` ${names}`;\n }\n\n private domainNames(): string {\n const domains = readDoomHarnessMetadata().domains;\n if (domains.length === 0) return '';\n return this.chromeTheme.fg(DIM_COLOR, ` ${domains.join(DOMAIN_SEPARATOR)}`);\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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"}
|
|
1
|
+
{"version":3,"file":"doomEditor.mjs","names":[],"sources":["../../src/tui/doomEditor.ts"],"sourcesContent":["import { type AppKeybinding, CustomEditor, type KeybindingsManager, type Theme } from '@earendil-works/pi-coding-agent';\nimport { type EditorOptions, type EditorTheme, parseKey, type TUI } from '@earendil-works/pi-tui';\nimport { type DoomLeaderAction, type DoomLeaderGroup, DoomLeaderRegistry } from '../services/leader/leaderRegistry.ts';\nimport { type DoomUiState, type LeaderSnapshot } from '../services/state/uiState.ts';\nimport { readDoomHarnessMetadata } from '../types/harnessMetadata.ts';\nimport { alignLine, fitLine, frameLine, padLine } from './rendering.ts';\n\nexport interface DoomEditorLeaderOptions {\n /** Overrides the harness major mode shown while the leader is inactive. */\n majorMode?: string;\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 = ' · ';\nconst DOMAIN_SEPARATOR = ', ';\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 majorMode: string;\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.majorMode = leaderOptions.majorMode?.trim() || readDoomHarnessMetadata().majorMode;\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()}${this.domainNames()}`,\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 ...(option.tone ? { tone: option.tone } : {}),\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 as AppKeybinding);\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 this.majorMode.toUpperCase();\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 separator = this.chromeTheme.fg(DIM_COLOR, MODE_SEPARATOR);\n const names = modes.map((mode) => this.chromeTheme.fg(ACCENT_COLOR, mode.label.toLowerCase())).join(separator);\n return ` ${names}`;\n }\n\n private domainNames(): string {\n const domains = readDoomHarnessMetadata().domains;\n if (domains.length === 0) return '';\n return this.chromeTheme.fg(DIM_COLOR, ` ${domains.join(DOMAIN_SEPARATOR)}`);\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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"}
|
package/dist/tui/leaderHints.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./rendering.cjs")
|
|
1
|
+
const e=require("./rendering.cjs"),t=` · `,n=[{minWidth:130,columns:4},{minWidth:90,columns:3},{minWidth:56,columns:2}],r=`text`,i=`accent`,a=`warning`,o=`mdHeading`,s=[`mdCode`,`toolDiffAdded`,`warning`,`muted`];var c=class{theme;snapshot;context;footerData;modes;constructor(e,t,n,r,i=[]){this.theme=e,this.snapshot=t,this.context=n,this.footerData=r,this.modes=i}invalidate(){}render(t){if(t<=0||!this.snapshot.active)return[];if(t<4)return[this.theme.fg(`borderAccent`,`─`.repeat(t))];let i=Math.max(1,t-4),a=n=>this.theme.bg(`toolPendingBg`,e.padLine(` ${n}`,t)),s=this.theme.inverse(this.theme.bold(this.theme.fg(o,` LEADER `))),c=`${this.snapshot.prefix.join(` › `)} / ${this.snapshot.label}`,l=a(e.alignLine(`${s} ${this.theme.bold(this.theme.fg(r,c))}`,this.theme.fg(`dim`,`esc cancel · backspace up`),i)),u=this.snapshot.rootOptions?.map(e=>`${e.key} ${e.label}`).join(` · `),d=t>=100&&this.snapshot.prefix.length>1&&u?[a(this.theme.fg(`dim`,`root ${u}`))]:[],f=Math.max(1,19-d.length),p=n.find(e=>t>=e.minWidth)?.columns??1,m=this.snapshot.prefix.length===1?4:0,h=Math.max(1,17-d.length-m),g=this.renderOptionGrid(i,p,h).slice(0,f).map(a),_=Math.max(0,17-d.length-g.length),v=this.snapshot.prefix.length===1?this.renderDiagnostics(i).slice(0,_).map(a):[];return[...[a(``),l,a(``),...g,...d,...v].slice(0,20),a(``)]}renderOptionGrid(t,n,o){if(this.snapshot.options.length===0)return[];let s=Math.max(1,Math.floor((t-2*(n-1))/n)),c=Math.ceil(this.snapshot.options.length/n)*2<=o,l=this.snapshot.options.map(e=>({heading:`${this.theme.inverse(this.theme.bold(this.theme.fg(e.tone===`exit`?a:i,` ${e.key} `)))} ${this.theme.bold(this.theme.fg(r,e.label))}`,detail:e.detail?` ${this.theme.fg(`dim`,e.detail)}`:``})),u=` `.repeat(2),d=[];for(let t=0;t<l.length;t+=n){let r=l.slice(t,t+n);d.push(r.map(t=>e.fitStyledLine(t.heading,s)).join(u)),c&&d.push(r.map(t=>e.fitStyledLine(t.detail,s)).join(u))}return d}renderDiagnostics(e){let t=this.sessionDiagnostics(),n=this.extensionDiagnostics(e),r=this.modeDiagnostics();if(!t&&!r&&n.length===0)return[];let s=[this.theme.fg(`borderMuted`,`─`.repeat(e))];r&&s.push(this.diagnosticLine(`MODES`,r,a)),t&&s.push(this.diagnosticLine(`SESSION`,t,o));for(let[e,t]of n.entries()){let n=e===0;s.push(this.diagnosticLine(n?`EXTENSIONS`:`↳`,t,n?i:`dim`))}return s}diagnosticLine(e,t,n){return`${this.theme.bold(this.theme.fg(n,e.padEnd(11)))}${t}`}modeDiagnostics(){if(this.modes.length!==0)return this.modes.map(e=>{let t=this.theme.fg(a,e.label.toLowerCase());return e.detail?`${t}${this.theme.fg(`dim`,` (${e.detail})`)}`:t}).join(this.theme.fg(`dim`,t))}sessionDiagnostics(){if(!this.context)return;let n=0,r=0,i=0,o=0,s=0;for(let e of this.context.sessionManager.getBranch())e.type===`message`&&e.message.role===`assistant`&&(n+=e.message.usage.input,r+=e.message.usage.output,i+=e.message.usage.cacheRead,o+=e.message.usage.cacheWrite,s+=e.message.usage.cost.total);let c=n+i+o,l=c===0?0:i/c*100,u=this.context.getContextUsage(),d=u?.percent===null||u?.percent===void 0?`ctx ?`:`ctx ${u.percent.toFixed(1)}% / ${e.formatTokens(u.contextWindow)}`;return`${this.theme.fg(`muted`,`↑${e.formatTokens(n)} ↓${e.formatTokens(r)} R${e.formatTokens(i)} W${e.formatTokens(o)} CH${l.toFixed(1)}%`)} ${this.theme.fg(a,`$${s.toFixed(3)}`)}${this.theme.fg(`dim`,`${t}${d}`)}`}extensionDiagnostics(n){if(!this.footerData)return[];let r=[...this.footerData.getExtensionStatuses().values()],i=this.footerData.getAvailableProviderCount(),a=[...r];i>0&&a.push(`${i} provider${i===1?``:`s`}`);let o=a.map((e,t)=>this.theme.fg(s[t%s.length],e)),c=Math.max(1,n-15);return e.packSegments(o,c,this.theme.fg(`dim`,t)).slice(0,2)}};exports.LeaderHints=c,exports.MAX_WIDGET_LINES=21;
|
|
2
2
|
//# sourceMappingURL=leaderHints.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderHints.cjs","names":["padLine","alignLine","visibleWidth","fitLine","formatTokens","packSegments"],"sources":["../../src/tui/leaderHints.ts"],"sourcesContent":["import type { ExtensionContext, ReadonlyFooterDataProvider, Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\n\nimport { type Component, visibleWidth } from '@earendil-works/pi-tui';\nimport type { LeaderSnapshot, UiMinorModeStatus } from '../services/state/uiState.ts';\nimport { alignLine, fitLine, formatTokens, packSegments, padLine } from './rendering.ts';\n\n/** The background palette is not exported by name, so take it from the method. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\nconst WIDE_HINT_WIDTH = 100;\n/**\n * No frame rows to pay for, so the whole budget is content. Two-line option\n * cards need roughly double the grid rows a single-line list did, plus a blank\n * row of breathing space at the top and bottom.\n */\nconst MAX_WIDGET_LINES = 21;\n/** Leading pad inside the shaded block, mirrored by padLine on the right. */\nconst PANEL_INSET = ' ';\nconst MIN_RENDER_WIDTH = 4;\nconst CONTENT_INSET = 4;\nconst GRID_GAP = 2;\nconst DIAGNOSTIC_LABEL_WIDTH = 11;\nconst DIAGNOSTIC_VALUE_INSET = 15;\nconst MAX_EXTENSION_LINES = 2;\nconst STATUS_SEPARATOR = ' · ';\nconst SEGMENT_GAP = ' ';\n/** The inverse key badge is ` k `, three columns wide. */\nconst KEY_BADGE_WIDTH = 3;\n/** Puts the description under the label rather than under the badge. */\nconst DETAIL_INDENT = ' ';\nconst HORIZONTAL_BORDER = '─';\nconst CONTINUATION_MARKER = '↳';\nconst DIM_COLOR: ThemeColor = 'dim';\nconst TEXT_COLOR: ThemeColor = 'text';\nconst MUTED_COLOR: ThemeColor = 'muted';\nconst ACCENT_COLOR: ThemeColor = 'accent';\nconst WARNING_COLOR: ThemeColor = 'warning';\nconst HEADING_COLOR: ThemeColor = 'mdHeading';\nconst BORDER_COLOR: ThemeColor = 'borderAccent';\nconst MUTED_BORDER_COLOR: ThemeColor = 'borderMuted';\n/** Subtle panel shade; avoid the brighter active/selection surface used by messages. */\nconst PANEL_BACKGROUND: ThemeBg = 'toolPendingBg';\n// The mockup colors each extension status differently so adjacent values stay\n// separable at a glance; cycling this palette reproduces that without hardcoding\n// a color per status key.\nconst STATUS_COLORS: ThemeColor[] = ['mdCode', 'toolDiffAdded', 'warning', 'muted'];\n\nexport class LeaderHints implements Component {\n constructor(\n private readonly theme: Theme,\n private readonly snapshot: LeaderSnapshot,\n private readonly context?: ExtensionContext,\n private readonly footerData?: ReadonlyFooterDataProvider,\n private readonly modes: readonly UiMinorModeStatus[] = [],\n ) {}\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0 || !this.snapshot.active) return [];\n if (width < MIN_RENDER_WIDTH) return [this.theme.fg(BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n\n // A filled block rather than a framed one: the shade alone separates the\n // panel from the transcript, which buys back the two rows a frame costs.\n const contentWidth = Math.max(1, width - CONTENT_INSET);\n const shade = (content: string): string =>\n this.theme.bg(PANEL_BACKGROUND, padLine(`${PANEL_INSET}${content}`, width));\n const badge = this.theme.inverse(this.theme.bold(this.theme.fg(HEADING_COLOR, ' LEADER ')));\n const breadcrumb = `${this.snapshot.prefix.join(' › ')} / ${this.snapshot.label}`;\n const header = shade(\n alignLine(\n `${badge} ${this.theme.bold(this.theme.fg(TEXT_COLOR, breadcrumb))}`,\n this.theme.fg(DIM_COLOR, 'esc cancel · backspace up'),\n contentWidth,\n ),\n );\n const rootTrail = this.snapshot.rootOptions?.map((option) => `${option.key} ${option.label}`).join(' · ');\n const trail =\n width >= WIDE_HINT_WIDTH && this.snapshot.prefix.length > 1 && rootTrail\n ? [shade(this.theme.fg(DIM_COLOR, `root ${rootTrail}`))]\n : [];\n // Options are the interactive surface, so they claim space before the\n // diagnostics do; truncating them would hide a reachable key.\n const optionBudget = Math.max(1, MAX_WIDGET_LINES - 2 - trail.length);\n const optionLines = this.renderOptionGrid(contentWidth).slice(0, optionBudget).map(shade);\n // Four rows are spoken for before diagnostics get any: the blank, header and\n // blank above, and the closing blank below. Counting only two of them let\n // this budget overrun, and the final `slice` did the real cutting silently\n // from the end, which is how the EXTENSIONS row went missing when MODES\n // arrived. Take all four off here so what is dropped is decided in one place.\n const diagnosticsBudget = Math.max(0, MAX_WIDGET_LINES - 4 - trail.length - optionLines.length);\n // Session and extension diagnostics belong to the board opened directly by\n // SPC/^SPC. Sub-boards stay focused on their own actions and navigation.\n const diagnostics =\n this.snapshot.prefix.length === 1\n ? this.renderDiagnostics(contentWidth).slice(0, diagnosticsBudget).map(shade)\n : [];\n // Blank shaded rows top and bottom: the panel butts against the editor above\n // and the modeline below, so the shade needs room before its first glyph.\n const body = [shade(''), header, shade(''), ...optionLines, ...trail, ...diagnostics];\n\n return [...body.slice(0, MAX_WIDGET_LINES - 1), shade('')];\n }\n\n /**\n * Lays options out on a fixed cell grid rather than a packed run of segments,\n * so keys and labels stay column-aligned when the list wraps.\n */\n private renderOptionGrid(width: number): string[] {\n if (this.snapshot.options.length === 0) return [];\n\n // Two lines per option, as the mockup lays them out: the key badge and a\n // bold label, then the description indented under the label.\n const cells = this.snapshot.options.map((option) => {\n const key = this.theme.inverse(this.theme.bold(this.theme.fg(ACCENT_COLOR, ` ${option.key} `)));\n return {\n heading: `${key} ${this.theme.bold(this.theme.fg(TEXT_COLOR, option.label))}`,\n headingWidth: KEY_BADGE_WIDTH + 1 + visibleWidth(option.label),\n detail: option.detail ? `${DETAIL_INDENT}${this.theme.fg(DIM_COLOR, option.detail)}` : '',\n detailWidth: option.detail ? visibleWidth(DETAIL_INDENT) + visibleWidth(option.detail) : 0,\n };\n });\n const widest = Math.max(...cells.map((cell) => Math.max(cell.headingWidth, cell.detailWidth)));\n const cellWidth = Math.min(width, widest);\n const columns = Math.max(1, Math.floor((width + GRID_GAP) / (cellWidth + GRID_GAP)));\n const gap = ' '.repeat(GRID_GAP);\n const rows: string[] = [];\n for (let index = 0; index < cells.length; index += columns) {\n const group = cells.slice(index, index + columns);\n rows.push(fitLine(group.map((cell) => padLine(cell.heading, cellWidth)).join(gap), width));\n // Keep the second row even when every detail is empty. Each option card\n // then retains the same two-line rhythm instead of collapsing terse items\n // into a denser list than items with supporting text.\n rows.push(fitLine(group.map((cell) => padLine(cell.detail, cellWidth)).join(gap), width));\n }\n return rows;\n }\n\n /** Content lines only: the caller paints the shade and pads to full width. */\n private renderDiagnostics(width: number): string[] {\n const session = this.sessionDiagnostics();\n const extensions = this.extensionDiagnostics(width);\n const modes = this.modeDiagnostics();\n if (!session && !modes && extensions.length === 0) return [];\n\n const lines = [this.theme.fg(MUTED_BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n // Above SESSION because it is the more consequential fact: session counters\n // describe what this session has spent, a mode describes what it will do.\n //\n // No blank rows between these three. The diagnostics budget is what is left\n // after the option grid takes its share, which at a full grid is exactly one\n // row per label: a spacer here does not cost breathing room, it costs the\n // EXTENSIONS row entirely. Distinct labels in distinct colours separate them\n // well enough without one.\n if (modes) lines.push(this.diagnosticLine('MODES', modes, WARNING_COLOR));\n if (session) lines.push(this.diagnosticLine('SESSION', session, HEADING_COLOR));\n for (const [index, extension] of extensions.entries()) {\n const isFirst = index === 0;\n lines.push(\n this.diagnosticLine(\n isFirst ? 'EXTENSIONS' : CONTINUATION_MARKER,\n extension,\n isFirst ? ACCENT_COLOR : DIM_COLOR,\n ),\n );\n }\n return lines;\n }\n\n private diagnosticLine(label: string, value: string, color: ThemeColor): string {\n return `${this.theme.bold(this.theme.fg(color, label.padEnd(DIAGNOSTIC_LABEL_WIDTH)))}${value}`;\n }\n\n /**\n * The enabled modes with their detail, which is the long form of what the\n * editor's badge row shows as bare names.\n *\n * This panel is opened deliberately and has room, so it is the right place\n * for `plan (normal - read only)`: the badge row is glanced at mid-typing and\n * only has to answer which modes are on.\n */\n private modeDiagnostics(): string | undefined {\n if (this.modes.length === 0) return undefined;\n return this.modes\n .map((mode) => {\n const label = this.theme.fg(WARNING_COLOR, mode.label.toLowerCase());\n return mode.detail ? `${label}${this.theme.fg(DIM_COLOR, ` (${mode.detail})`)}` : label;\n })\n .join(this.theme.fg(DIM_COLOR, STATUS_SEPARATOR));\n }\n\n private sessionDiagnostics(): string | undefined {\n if (!this.context) return undefined;\n\n let input = 0;\n let output = 0;\n let cacheRead = 0;\n let cacheWrite = 0;\n let cost = 0;\n for (const entry of this.context.sessionManager.getBranch()) {\n if (entry.type !== 'message' || entry.message.role !== 'assistant') continue;\n input += entry.message.usage.input;\n output += entry.message.usage.output;\n cacheRead += entry.message.usage.cacheRead;\n cacheWrite += entry.message.usage.cacheWrite;\n cost += entry.message.usage.cost.total;\n }\n\n const cachedTotal = input + cacheRead + cacheWrite;\n const cacheHit = cachedTotal === 0 ? 0 : (cacheRead / cachedTotal) * 100;\n const usage = this.context.getContextUsage();\n const context =\n usage?.percent === null || usage?.percent === undefined\n ? 'ctx ?'\n : `ctx ${usage.percent.toFixed(1)}% / ${formatTokens(usage.contextWindow)}`;\n // Built as sibling segments rather than nested theme calls so each color\n // closes cleanly instead of resetting the one around it.\n const metrics = this.theme.fg(\n MUTED_COLOR,\n `↑${formatTokens(input)} ↓${formatTokens(output)} R${formatTokens(cacheRead)} W${formatTokens(\n cacheWrite,\n )} CH${cacheHit.toFixed(1)}%`,\n );\n const costText = this.theme.fg(WARNING_COLOR, `$${cost.toFixed(3)}`);\n return `${metrics}${SEGMENT_GAP}${costText}${this.theme.fg(DIM_COLOR, `${STATUS_SEPARATOR}${context}`)}`;\n }\n\n private extensionDiagnostics(width: number): string[] {\n if (!this.footerData) return [];\n\n const statuses = [...this.footerData.getExtensionStatuses().values()];\n const providers = this.footerData.getAvailableProviderCount();\n const values = [...statuses];\n if (providers > 0) values.push(`${providers} provider${providers === 1 ? '' : 's'}`);\n const colored = values.map((value, index) =>\n this.theme.fg(STATUS_COLORS[index % STATUS_COLORS.length] as ThemeColor, value),\n );\n const valueWidth = Math.max(1, width - DIAGNOSTIC_VALUE_INSET);\n return packSegments(colored, valueWidth, this.theme.fg(DIM_COLOR, STATUS_SEPARATOR)).slice(0, MAX_EXTENSION_LINES);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"leaderHints.cjs","names":["padLine","alignLine","fitStyledLine","formatTokens","packSegments"],"sources":["../../src/tui/leaderHints.ts"],"sourcesContent":["import type { ExtensionContext, ReadonlyFooterDataProvider, Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\n\nimport type { Component } from '@earendil-works/pi-tui';\nimport type { LeaderSnapshot, UiMinorModeStatus } from '../services/state/uiState.ts';\nimport { alignLine, fitStyledLine, formatTokens, packSegments, padLine } from './rendering.ts';\n\n/** The background palette is not exported by name, so take it from the method. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\nconst WIDE_HINT_WIDTH = 100;\n/**\n * No frame rows to pay for, so the whole budget is content. Two-line option\n * cards need roughly double the grid rows a single-line list did, plus a blank\n * row of breathing space at the top and bottom.\n *\n * Exported because tests assert the panel stays inside it; a second copy of the\n * number in the test file drifted from this one and stopped catching anything.\n */\nexport const MAX_WIDGET_LINES = 21;\n/** Blank, header, blank above the grid, and the closing blank below it. */\nconst CHROME_LINES = 4;\n/** Rule, MODES, SESSION, EXTENSIONS: what the root board's diagnostics want. */\nconst DIAGNOSTIC_RESERVE = 4;\n/** Leading pad inside the shaded block, mirrored by padLine on the right. */\nconst PANEL_INSET = ' ';\nconst MIN_RENDER_WIDTH = 4;\nconst CONTENT_INSET = 4;\nconst GRID_GAP = 2;\nconst DIAGNOSTIC_LABEL_WIDTH = 11;\nconst DIAGNOSTIC_VALUE_INSET = 15;\nconst MAX_EXTENSION_LINES = 2;\nconst STATUS_SEPARATOR = ' · ';\nconst SEGMENT_GAP = ' ';\n/**\n * Column count comes from width alone, never from the widest cell, so the root\n * board and every sub-board land on the same grid at the same terminal size.\n * Sizing to content made one long description narrow every other space's grid.\n */\nconst COLUMN_BREAKPOINTS: readonly { minWidth: number; columns: number }[] = [\n { minWidth: 130, columns: 4 },\n { minWidth: 90, columns: 3 },\n { minWidth: 56, columns: 2 },\n];\nconst SINGLE_COLUMN = 1;\n/** Marks the row that leaves an active minor mode; painted apart from the rest. */\nconst EXIT_TONE = 'exit';\n/** Puts the description under the label rather than under the badge. */\nconst DETAIL_INDENT = ' ';\nconst HORIZONTAL_BORDER = '─';\nconst CONTINUATION_MARKER = '↳';\nconst DIM_COLOR: ThemeColor = 'dim';\nconst TEXT_COLOR: ThemeColor = 'text';\nconst MUTED_COLOR: ThemeColor = 'muted';\nconst ACCENT_COLOR: ThemeColor = 'accent';\nconst WARNING_COLOR: ThemeColor = 'warning';\nconst HEADING_COLOR: ThemeColor = 'mdHeading';\nconst BORDER_COLOR: ThemeColor = 'borderAccent';\nconst MUTED_BORDER_COLOR: ThemeColor = 'borderMuted';\n/** Subtle panel shade; avoid the brighter active/selection surface used by messages. */\nconst PANEL_BACKGROUND: ThemeBg = 'toolPendingBg';\n// The mockup colors each extension status differently so adjacent values stay\n// separable at a glance; cycling this palette reproduces that without hardcoding\n// a color per status key.\nconst STATUS_COLORS: ThemeColor[] = ['mdCode', 'toolDiffAdded', 'warning', 'muted'];\n\nexport class LeaderHints implements Component {\n constructor(\n private readonly theme: Theme,\n private readonly snapshot: LeaderSnapshot,\n private readonly context?: ExtensionContext,\n private readonly footerData?: ReadonlyFooterDataProvider,\n private readonly modes: readonly UiMinorModeStatus[] = [],\n ) {}\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0 || !this.snapshot.active) return [];\n if (width < MIN_RENDER_WIDTH) return [this.theme.fg(BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n\n // A filled block rather than a framed one: the shade alone separates the\n // panel from the transcript, which buys back the two rows a frame costs.\n const contentWidth = Math.max(1, width - CONTENT_INSET);\n const shade = (content: string): string =>\n this.theme.bg(PANEL_BACKGROUND, padLine(`${PANEL_INSET}${content}`, width));\n const badge = this.theme.inverse(this.theme.bold(this.theme.fg(HEADING_COLOR, ' LEADER ')));\n const breadcrumb = `${this.snapshot.prefix.join(' › ')} / ${this.snapshot.label}`;\n const header = shade(\n alignLine(\n `${badge} ${this.theme.bold(this.theme.fg(TEXT_COLOR, breadcrumb))}`,\n this.theme.fg(DIM_COLOR, 'esc cancel · backspace up'),\n contentWidth,\n ),\n );\n const rootTrail = this.snapshot.rootOptions?.map((option) => `${option.key} ${option.label}`).join(' · ');\n const trail =\n width >= WIDE_HINT_WIDTH && this.snapshot.prefix.length > 1 && rootTrail\n ? [shade(this.theme.fg(DIM_COLOR, `root ${rootTrail}`))]\n : [];\n // Options are the interactive surface, so they claim space before the\n // diagnostics do; truncating them would hide a reachable key.\n const optionBudget = Math.max(1, MAX_WIDGET_LINES - 2 - trail.length);\n const columns = COLUMN_BREAKPOINTS.find((breakpoint) => width >= breakpoint.minWidth)?.columns ?? SINGLE_COLUMN;\n // Two-line cards are the intended rhythm, but they are not worth the rows\n // they cost once they start pushing diagnostics off the bottom. Sub-boards\n // draw no diagnostics, so they reserve nothing and keep their descriptions.\n const reserve = this.snapshot.prefix.length === 1 ? DIAGNOSTIC_RESERVE : 0;\n const detailBudget = Math.max(1, MAX_WIDGET_LINES - CHROME_LINES - trail.length - reserve);\n const optionLines = this.renderOptionGrid(contentWidth, columns, detailBudget).slice(0, optionBudget).map(shade);\n // Four rows are spoken for before diagnostics get any: the blank, header and\n // blank above, and the closing blank below. Counting only two of them let\n // this budget overrun, and the final `slice` did the real cutting silently\n // from the end, which is how the EXTENSIONS row went missing when MODES\n // arrived. Take all four off here so what is dropped is decided in one place.\n const diagnosticsBudget = Math.max(0, MAX_WIDGET_LINES - 4 - trail.length - optionLines.length);\n // Session and extension diagnostics belong to the board opened directly by\n // SPC/^SPC. Sub-boards stay focused on their own actions and navigation.\n const diagnostics =\n this.snapshot.prefix.length === 1\n ? this.renderDiagnostics(contentWidth).slice(0, diagnosticsBudget).map(shade)\n : [];\n // Blank shaded rows top and bottom: the panel butts against the editor above\n // and the modeline below, so the shade needs room before its first glyph.\n const body = [shade(''), header, shade(''), ...optionLines, ...trail, ...diagnostics];\n\n return [...body.slice(0, MAX_WIDGET_LINES - 1), shade('')];\n }\n\n /**\n * Lays options out on a fixed cell grid rather than a packed run of segments,\n * so keys and labels stay column-aligned when the list wraps.\n *\n * `budget` is the rows the grid may take while still leaving the panel its\n * chrome and diagnostics. It decides whether descriptions are drawn, not\n * whether options are kept: a grid that overruns is cut from the end by the\n * caller, which would take whole options away with it.\n */\n private renderOptionGrid(width: number, columns: number, budget: number): string[] {\n if (this.snapshot.options.length === 0) return [];\n\n // Equal cells, sized from the grid rather than from their contents, so a\n // long description is ellipsised instead of widening every other column.\n const cellWidth = Math.max(1, Math.floor((width - GRID_GAP * (columns - 1)) / columns));\n const withDetail = Math.ceil(this.snapshot.options.length / columns) * 2 <= budget;\n\n // Two lines per option, as the mockup lays them out: the key badge and a\n // bold label, then the description indented under the label.\n const cells = this.snapshot.options.map((option) => {\n const badge = this.theme.inverse(\n this.theme.bold(this.theme.fg(option.tone === EXIT_TONE ? WARNING_COLOR : ACCENT_COLOR, ` ${option.key} `)),\n );\n return {\n heading: `${badge} ${this.theme.bold(this.theme.fg(TEXT_COLOR, option.label))}`,\n detail: option.detail ? `${DETAIL_INDENT}${this.theme.fg(DIM_COLOR, option.detail)}` : '',\n };\n });\n\n const gap = ' '.repeat(GRID_GAP);\n const rows: string[] = [];\n for (let index = 0; index < cells.length; index += columns) {\n const group = cells.slice(index, index + columns);\n // fitStyledLine, not padLine: fixed cells clip descriptions routinely now,\n // and it is the one that ellipsises and drops the hard reset pi-tui injects,\n // which would otherwise cancel the panel shade partway along the row.\n rows.push(group.map((cell) => fitStyledLine(cell.heading, cellWidth)).join(gap));\n // Keep the second row even when every detail is empty. Each option card\n // then retains the same two-line rhythm instead of collapsing terse items\n // into a denser list than items with supporting text.\n if (withDetail) rows.push(group.map((cell) => fitStyledLine(cell.detail, cellWidth)).join(gap));\n }\n return rows;\n }\n\n /** Content lines only: the caller paints the shade and pads to full width. */\n private renderDiagnostics(width: number): string[] {\n const session = this.sessionDiagnostics();\n const extensions = this.extensionDiagnostics(width);\n const modes = this.modeDiagnostics();\n if (!session && !modes && extensions.length === 0) return [];\n\n const lines = [this.theme.fg(MUTED_BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n // Above SESSION because it is the more consequential fact: session counters\n // describe what this session has spent, a mode describes what it will do.\n //\n // No blank rows between these three. The diagnostics budget is what is left\n // after the option grid takes its share, which at a full grid is exactly one\n // row per label: a spacer here does not cost breathing room, it costs the\n // EXTENSIONS row entirely. Distinct labels in distinct colours separate them\n // well enough without one.\n if (modes) lines.push(this.diagnosticLine('MODES', modes, WARNING_COLOR));\n if (session) lines.push(this.diagnosticLine('SESSION', session, HEADING_COLOR));\n for (const [index, extension] of extensions.entries()) {\n const isFirst = index === 0;\n lines.push(\n this.diagnosticLine(\n isFirst ? 'EXTENSIONS' : CONTINUATION_MARKER,\n extension,\n isFirst ? ACCENT_COLOR : DIM_COLOR,\n ),\n );\n }\n return lines;\n }\n\n private diagnosticLine(label: string, value: string, color: ThemeColor): string {\n return `${this.theme.bold(this.theme.fg(color, label.padEnd(DIAGNOSTIC_LABEL_WIDTH)))}${value}`;\n }\n\n /**\n * The enabled modes with their detail, which is the long form of what the\n * editor's badge row shows as bare names.\n *\n * This panel is opened deliberately and has room, so it is the right place\n * for `plan (normal - read only)`: the badge row is glanced at mid-typing and\n * only has to answer which modes are on.\n */\n private modeDiagnostics(): string | undefined {\n if (this.modes.length === 0) return undefined;\n return this.modes\n .map((mode) => {\n const label = this.theme.fg(WARNING_COLOR, mode.label.toLowerCase());\n return mode.detail ? `${label}${this.theme.fg(DIM_COLOR, ` (${mode.detail})`)}` : label;\n })\n .join(this.theme.fg(DIM_COLOR, STATUS_SEPARATOR));\n }\n\n private sessionDiagnostics(): string | undefined {\n if (!this.context) return undefined;\n\n let input = 0;\n let output = 0;\n let cacheRead = 0;\n let cacheWrite = 0;\n let cost = 0;\n for (const entry of this.context.sessionManager.getBranch()) {\n if (entry.type !== 'message' || entry.message.role !== 'assistant') continue;\n input += entry.message.usage.input;\n output += entry.message.usage.output;\n cacheRead += entry.message.usage.cacheRead;\n cacheWrite += entry.message.usage.cacheWrite;\n cost += entry.message.usage.cost.total;\n }\n\n const cachedTotal = input + cacheRead + cacheWrite;\n const cacheHit = cachedTotal === 0 ? 0 : (cacheRead / cachedTotal) * 100;\n const usage = this.context.getContextUsage();\n const context =\n usage?.percent === null || usage?.percent === undefined\n ? 'ctx ?'\n : `ctx ${usage.percent.toFixed(1)}% / ${formatTokens(usage.contextWindow)}`;\n // Built as sibling segments rather than nested theme calls so each color\n // closes cleanly instead of resetting the one around it.\n const metrics = this.theme.fg(\n MUTED_COLOR,\n `↑${formatTokens(input)} ↓${formatTokens(output)} R${formatTokens(cacheRead)} W${formatTokens(\n cacheWrite,\n )} CH${cacheHit.toFixed(1)}%`,\n );\n const costText = this.theme.fg(WARNING_COLOR, `$${cost.toFixed(3)}`);\n return `${metrics}${SEGMENT_GAP}${costText}${this.theme.fg(DIM_COLOR, `${STATUS_SEPARATOR}${context}`)}`;\n }\n\n private extensionDiagnostics(width: number): string[] {\n if (!this.footerData) return [];\n\n const statuses = [...this.footerData.getExtensionStatuses().values()];\n const providers = this.footerData.getAvailableProviderCount();\n const values = [...statuses];\n if (providers > 0) values.push(`${providers} provider${providers === 1 ? '' : 's'}`);\n const colored = values.map((value, index) =>\n this.theme.fg(STATUS_COLORS[index % STATUS_COLORS.length] as ThemeColor, value),\n );\n const valueWidth = Math.max(1, width - DIAGNOSTIC_VALUE_INSET);\n return packSegments(colored, valueWidth, this.theme.fg(DIM_COLOR, STATUS_SEPARATOR)).slice(0, MAX_EXTENSION_LINES);\n }\n}\n"],"mappings":"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"}
|
|
@@ -2,6 +2,15 @@ import { LeaderSnapshot, UiMinorModeStatus } from "../services/state/uiState.cjs
|
|
|
2
2
|
import { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { Component } from "@earendil-works/pi-tui";
|
|
4
4
|
//#region src/tui/leaderHints.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* No frame rows to pay for, so the whole budget is content. Two-line option
|
|
7
|
+
* cards need roughly double the grid rows a single-line list did, plus a blank
|
|
8
|
+
* row of breathing space at the top and bottom.
|
|
9
|
+
*
|
|
10
|
+
* Exported because tests assert the panel stays inside it; a second copy of the
|
|
11
|
+
* number in the test file drifted from this one and stopped catching anything.
|
|
12
|
+
*/
|
|
13
|
+
declare const MAX_WIDGET_LINES = 21;
|
|
5
14
|
declare class LeaderHints implements Component {
|
|
6
15
|
private readonly theme;
|
|
7
16
|
private readonly snapshot;
|
|
@@ -14,6 +23,11 @@ declare class LeaderHints implements Component {
|
|
|
14
23
|
/**
|
|
15
24
|
* Lays options out on a fixed cell grid rather than a packed run of segments,
|
|
16
25
|
* so keys and labels stay column-aligned when the list wraps.
|
|
26
|
+
*
|
|
27
|
+
* `budget` is the rows the grid may take while still leaving the panel its
|
|
28
|
+
* chrome and diagnostics. It decides whether descriptions are drawn, not
|
|
29
|
+
* whether options are kept: a grid that overruns is cut from the end by the
|
|
30
|
+
* caller, which would take whole options away with it.
|
|
17
31
|
*/
|
|
18
32
|
private renderOptionGrid;
|
|
19
33
|
/** Content lines only: the caller paints the shade and pads to full width. */
|
|
@@ -32,5 +46,5 @@ declare class LeaderHints implements Component {
|
|
|
32
46
|
private extensionDiagnostics;
|
|
33
47
|
}
|
|
34
48
|
//#endregion
|
|
35
|
-
export { LeaderHints };
|
|
49
|
+
export { LeaderHints, MAX_WIDGET_LINES };
|
|
36
50
|
//# sourceMappingURL=leaderHints.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderHints.d.cts","names":[],"sources":["../../src/tui/leaderHints.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"leaderHints.d.cts","names":[],"sources":["../../src/tui/leaderHints.ts"],"mappings":";;;;;;;;;;;;cAkBa;cA+CA,uBAAuB;mBAEf;mBACA;mBACA;mBACA;mBACA;EALnB,YACmB,OAAO,OACP,UAAU,gBACV,UAAU,8BACV,aAAa,wCACb,iBAAgB;EAGnC;EAEA,OAAO;;;;;;;;;;UA6DC;;UAqCA;UA8BA;;;;;;;;;UAYA;UAUA;UAoCA"}
|
|
@@ -2,6 +2,15 @@ import { LeaderSnapshot, UiMinorModeStatus } from "../services/state/uiState.mjs
|
|
|
2
2
|
import { Component } from "@earendil-works/pi-tui";
|
|
3
3
|
import { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
//#region src/tui/leaderHints.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* No frame rows to pay for, so the whole budget is content. Two-line option
|
|
7
|
+
* cards need roughly double the grid rows a single-line list did, plus a blank
|
|
8
|
+
* row of breathing space at the top and bottom.
|
|
9
|
+
*
|
|
10
|
+
* Exported because tests assert the panel stays inside it; a second copy of the
|
|
11
|
+
* number in the test file drifted from this one and stopped catching anything.
|
|
12
|
+
*/
|
|
13
|
+
declare const MAX_WIDGET_LINES = 21;
|
|
5
14
|
declare class LeaderHints implements Component {
|
|
6
15
|
private readonly theme;
|
|
7
16
|
private readonly snapshot;
|
|
@@ -14,6 +23,11 @@ declare class LeaderHints implements Component {
|
|
|
14
23
|
/**
|
|
15
24
|
* Lays options out on a fixed cell grid rather than a packed run of segments,
|
|
16
25
|
* so keys and labels stay column-aligned when the list wraps.
|
|
26
|
+
*
|
|
27
|
+
* `budget` is the rows the grid may take while still leaving the panel its
|
|
28
|
+
* chrome and diagnostics. It decides whether descriptions are drawn, not
|
|
29
|
+
* whether options are kept: a grid that overruns is cut from the end by the
|
|
30
|
+
* caller, which would take whole options away with it.
|
|
17
31
|
*/
|
|
18
32
|
private renderOptionGrid;
|
|
19
33
|
/** Content lines only: the caller paints the shade and pads to full width. */
|
|
@@ -32,5 +46,5 @@ declare class LeaderHints implements Component {
|
|
|
32
46
|
private extensionDiagnostics;
|
|
33
47
|
}
|
|
34
48
|
//#endregion
|
|
35
|
-
export { LeaderHints };
|
|
49
|
+
export { LeaderHints, MAX_WIDGET_LINES };
|
|
36
50
|
//# sourceMappingURL=leaderHints.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderHints.d.mts","names":[],"sources":["../../src/tui/leaderHints.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"leaderHints.d.mts","names":[],"sources":["../../src/tui/leaderHints.ts"],"mappings":";;;;;;;;;;;;cAkBa;cA+CA,uBAAuB;mBAEf;mBACA;mBACA;mBACA;mBACA;EALnB,YACmB,OAAO,OACP,UAAU,gBACV,UAAU,8BACV,aAAa,wCACb,iBAAgB;EAGnC;EAEA,OAAO;;;;;;;;;;UA6DC;;UAqCA;UA8BA;;;;;;;;;UAYA;UAUA;UAoCA"}
|
package/dist/tui/leaderHints.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{alignLine as e,
|
|
1
|
+
import{alignLine as e,fitStyledLine as t,formatTokens as n,packSegments as r,padLine as i}from"./rendering.mjs";const a=21,o=` · `,s=[{minWidth:130,columns:4},{minWidth:90,columns:3},{minWidth:56,columns:2}],c=`text`,l=`accent`,u=`warning`,d=`mdHeading`,f=[`mdCode`,`toolDiffAdded`,`warning`,`muted`];var p=class{theme;snapshot;context;footerData;modes;constructor(e,t,n,r,i=[]){this.theme=e,this.snapshot=t,this.context=n,this.footerData=r,this.modes=i}invalidate(){}render(t){if(t<=0||!this.snapshot.active)return[];if(t<4)return[this.theme.fg(`borderAccent`,`─`.repeat(t))];let n=Math.max(1,t-4),r=e=>this.theme.bg(`toolPendingBg`,i(` ${e}`,t)),a=this.theme.inverse(this.theme.bold(this.theme.fg(d,` LEADER `))),o=`${this.snapshot.prefix.join(` › `)} / ${this.snapshot.label}`,l=r(e(`${a} ${this.theme.bold(this.theme.fg(c,o))}`,this.theme.fg(`dim`,`esc cancel · backspace up`),n)),u=this.snapshot.rootOptions?.map(e=>`${e.key} ${e.label}`).join(` · `),f=t>=100&&this.snapshot.prefix.length>1&&u?[r(this.theme.fg(`dim`,`root ${u}`))]:[],p=Math.max(1,19-f.length),m=s.find(e=>t>=e.minWidth)?.columns??1,h=this.snapshot.prefix.length===1?4:0,g=Math.max(1,17-f.length-h),_=this.renderOptionGrid(n,m,g).slice(0,p).map(r),v=Math.max(0,17-f.length-_.length),y=this.snapshot.prefix.length===1?this.renderDiagnostics(n).slice(0,v).map(r):[];return[...[r(``),l,r(``),..._,...f,...y].slice(0,20),r(``)]}renderOptionGrid(e,n,r){if(this.snapshot.options.length===0)return[];let i=Math.max(1,Math.floor((e-2*(n-1))/n)),a=Math.ceil(this.snapshot.options.length/n)*2<=r,o=this.snapshot.options.map(e=>({heading:`${this.theme.inverse(this.theme.bold(this.theme.fg(e.tone===`exit`?u:l,` ${e.key} `)))} ${this.theme.bold(this.theme.fg(c,e.label))}`,detail:e.detail?` ${this.theme.fg(`dim`,e.detail)}`:``})),s=` `.repeat(2),d=[];for(let e=0;e<o.length;e+=n){let r=o.slice(e,e+n);d.push(r.map(e=>t(e.heading,i)).join(s)),a&&d.push(r.map(e=>t(e.detail,i)).join(s))}return d}renderDiagnostics(e){let t=this.sessionDiagnostics(),n=this.extensionDiagnostics(e),r=this.modeDiagnostics();if(!t&&!r&&n.length===0)return[];let i=[this.theme.fg(`borderMuted`,`─`.repeat(e))];r&&i.push(this.diagnosticLine(`MODES`,r,u)),t&&i.push(this.diagnosticLine(`SESSION`,t,d));for(let[e,t]of n.entries()){let n=e===0;i.push(this.diagnosticLine(n?`EXTENSIONS`:`↳`,t,n?l:`dim`))}return i}diagnosticLine(e,t,n){return`${this.theme.bold(this.theme.fg(n,e.padEnd(11)))}${t}`}modeDiagnostics(){if(this.modes.length!==0)return this.modes.map(e=>{let t=this.theme.fg(u,e.label.toLowerCase());return e.detail?`${t}${this.theme.fg(`dim`,` (${e.detail})`)}`:t}).join(this.theme.fg(`dim`,o))}sessionDiagnostics(){if(!this.context)return;let e=0,t=0,r=0,i=0,a=0;for(let n of this.context.sessionManager.getBranch())n.type===`message`&&n.message.role===`assistant`&&(e+=n.message.usage.input,t+=n.message.usage.output,r+=n.message.usage.cacheRead,i+=n.message.usage.cacheWrite,a+=n.message.usage.cost.total);let s=e+r+i,c=s===0?0:r/s*100,l=this.context.getContextUsage(),d=l?.percent===null||l?.percent===void 0?`ctx ?`:`ctx ${l.percent.toFixed(1)}% / ${n(l.contextWindow)}`;return`${this.theme.fg(`muted`,`↑${n(e)} ↓${n(t)} R${n(r)} W${n(i)} CH${c.toFixed(1)}%`)} ${this.theme.fg(u,`$${a.toFixed(3)}`)}${this.theme.fg(`dim`,`${o}${d}`)}`}extensionDiagnostics(e){if(!this.footerData)return[];let t=[...this.footerData.getExtensionStatuses().values()],n=this.footerData.getAvailableProviderCount(),i=[...t];n>0&&i.push(`${n} provider${n===1?``:`s`}`);let a=i.map((e,t)=>this.theme.fg(f[t%f.length],e)),s=Math.max(1,e-15);return r(a,s,this.theme.fg(`dim`,o)).slice(0,2)}};export{p as LeaderHints,a as MAX_WIDGET_LINES};
|
|
2
2
|
//# sourceMappingURL=leaderHints.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderHints.mjs","names":[],"sources":["../../src/tui/leaderHints.ts"],"sourcesContent":["import type { ExtensionContext, ReadonlyFooterDataProvider, Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\n\nimport { type Component, visibleWidth } from '@earendil-works/pi-tui';\nimport type { LeaderSnapshot, UiMinorModeStatus } from '../services/state/uiState.ts';\nimport { alignLine, fitLine, formatTokens, packSegments, padLine } from './rendering.ts';\n\n/** The background palette is not exported by name, so take it from the method. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\nconst WIDE_HINT_WIDTH = 100;\n/**\n * No frame rows to pay for, so the whole budget is content. Two-line option\n * cards need roughly double the grid rows a single-line list did, plus a blank\n * row of breathing space at the top and bottom.\n */\nconst MAX_WIDGET_LINES = 21;\n/** Leading pad inside the shaded block, mirrored by padLine on the right. */\nconst PANEL_INSET = ' ';\nconst MIN_RENDER_WIDTH = 4;\nconst CONTENT_INSET = 4;\nconst GRID_GAP = 2;\nconst DIAGNOSTIC_LABEL_WIDTH = 11;\nconst DIAGNOSTIC_VALUE_INSET = 15;\nconst MAX_EXTENSION_LINES = 2;\nconst STATUS_SEPARATOR = ' · ';\nconst SEGMENT_GAP = ' ';\n/** The inverse key badge is ` k `, three columns wide. */\nconst KEY_BADGE_WIDTH = 3;\n/** Puts the description under the label rather than under the badge. */\nconst DETAIL_INDENT = ' ';\nconst HORIZONTAL_BORDER = '─';\nconst CONTINUATION_MARKER = '↳';\nconst DIM_COLOR: ThemeColor = 'dim';\nconst TEXT_COLOR: ThemeColor = 'text';\nconst MUTED_COLOR: ThemeColor = 'muted';\nconst ACCENT_COLOR: ThemeColor = 'accent';\nconst WARNING_COLOR: ThemeColor = 'warning';\nconst HEADING_COLOR: ThemeColor = 'mdHeading';\nconst BORDER_COLOR: ThemeColor = 'borderAccent';\nconst MUTED_BORDER_COLOR: ThemeColor = 'borderMuted';\n/** Subtle panel shade; avoid the brighter active/selection surface used by messages. */\nconst PANEL_BACKGROUND: ThemeBg = 'toolPendingBg';\n// The mockup colors each extension status differently so adjacent values stay\n// separable at a glance; cycling this palette reproduces that without hardcoding\n// a color per status key.\nconst STATUS_COLORS: ThemeColor[] = ['mdCode', 'toolDiffAdded', 'warning', 'muted'];\n\nexport class LeaderHints implements Component {\n constructor(\n private readonly theme: Theme,\n private readonly snapshot: LeaderSnapshot,\n private readonly context?: ExtensionContext,\n private readonly footerData?: ReadonlyFooterDataProvider,\n private readonly modes: readonly UiMinorModeStatus[] = [],\n ) {}\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0 || !this.snapshot.active) return [];\n if (width < MIN_RENDER_WIDTH) return [this.theme.fg(BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n\n // A filled block rather than a framed one: the shade alone separates the\n // panel from the transcript, which buys back the two rows a frame costs.\n const contentWidth = Math.max(1, width - CONTENT_INSET);\n const shade = (content: string): string =>\n this.theme.bg(PANEL_BACKGROUND, padLine(`${PANEL_INSET}${content}`, width));\n const badge = this.theme.inverse(this.theme.bold(this.theme.fg(HEADING_COLOR, ' LEADER ')));\n const breadcrumb = `${this.snapshot.prefix.join(' › ')} / ${this.snapshot.label}`;\n const header = shade(\n alignLine(\n `${badge} ${this.theme.bold(this.theme.fg(TEXT_COLOR, breadcrumb))}`,\n this.theme.fg(DIM_COLOR, 'esc cancel · backspace up'),\n contentWidth,\n ),\n );\n const rootTrail = this.snapshot.rootOptions?.map((option) => `${option.key} ${option.label}`).join(' · ');\n const trail =\n width >= WIDE_HINT_WIDTH && this.snapshot.prefix.length > 1 && rootTrail\n ? [shade(this.theme.fg(DIM_COLOR, `root ${rootTrail}`))]\n : [];\n // Options are the interactive surface, so they claim space before the\n // diagnostics do; truncating them would hide a reachable key.\n const optionBudget = Math.max(1, MAX_WIDGET_LINES - 2 - trail.length);\n const optionLines = this.renderOptionGrid(contentWidth).slice(0, optionBudget).map(shade);\n // Four rows are spoken for before diagnostics get any: the blank, header and\n // blank above, and the closing blank below. Counting only two of them let\n // this budget overrun, and the final `slice` did the real cutting silently\n // from the end, which is how the EXTENSIONS row went missing when MODES\n // arrived. Take all four off here so what is dropped is decided in one place.\n const diagnosticsBudget = Math.max(0, MAX_WIDGET_LINES - 4 - trail.length - optionLines.length);\n // Session and extension diagnostics belong to the board opened directly by\n // SPC/^SPC. Sub-boards stay focused on their own actions and navigation.\n const diagnostics =\n this.snapshot.prefix.length === 1\n ? this.renderDiagnostics(contentWidth).slice(0, diagnosticsBudget).map(shade)\n : [];\n // Blank shaded rows top and bottom: the panel butts against the editor above\n // and the modeline below, so the shade needs room before its first glyph.\n const body = [shade(''), header, shade(''), ...optionLines, ...trail, ...diagnostics];\n\n return [...body.slice(0, MAX_WIDGET_LINES - 1), shade('')];\n }\n\n /**\n * Lays options out on a fixed cell grid rather than a packed run of segments,\n * so keys and labels stay column-aligned when the list wraps.\n */\n private renderOptionGrid(width: number): string[] {\n if (this.snapshot.options.length === 0) return [];\n\n // Two lines per option, as the mockup lays them out: the key badge and a\n // bold label, then the description indented under the label.\n const cells = this.snapshot.options.map((option) => {\n const key = this.theme.inverse(this.theme.bold(this.theme.fg(ACCENT_COLOR, ` ${option.key} `)));\n return {\n heading: `${key} ${this.theme.bold(this.theme.fg(TEXT_COLOR, option.label))}`,\n headingWidth: KEY_BADGE_WIDTH + 1 + visibleWidth(option.label),\n detail: option.detail ? `${DETAIL_INDENT}${this.theme.fg(DIM_COLOR, option.detail)}` : '',\n detailWidth: option.detail ? visibleWidth(DETAIL_INDENT) + visibleWidth(option.detail) : 0,\n };\n });\n const widest = Math.max(...cells.map((cell) => Math.max(cell.headingWidth, cell.detailWidth)));\n const cellWidth = Math.min(width, widest);\n const columns = Math.max(1, Math.floor((width + GRID_GAP) / (cellWidth + GRID_GAP)));\n const gap = ' '.repeat(GRID_GAP);\n const rows: string[] = [];\n for (let index = 0; index < cells.length; index += columns) {\n const group = cells.slice(index, index + columns);\n rows.push(fitLine(group.map((cell) => padLine(cell.heading, cellWidth)).join(gap), width));\n // Keep the second row even when every detail is empty. Each option card\n // then retains the same two-line rhythm instead of collapsing terse items\n // into a denser list than items with supporting text.\n rows.push(fitLine(group.map((cell) => padLine(cell.detail, cellWidth)).join(gap), width));\n }\n return rows;\n }\n\n /** Content lines only: the caller paints the shade and pads to full width. */\n private renderDiagnostics(width: number): string[] {\n const session = this.sessionDiagnostics();\n const extensions = this.extensionDiagnostics(width);\n const modes = this.modeDiagnostics();\n if (!session && !modes && extensions.length === 0) return [];\n\n const lines = [this.theme.fg(MUTED_BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n // Above SESSION because it is the more consequential fact: session counters\n // describe what this session has spent, a mode describes what it will do.\n //\n // No blank rows between these three. The diagnostics budget is what is left\n // after the option grid takes its share, which at a full grid is exactly one\n // row per label: a spacer here does not cost breathing room, it costs the\n // EXTENSIONS row entirely. Distinct labels in distinct colours separate them\n // well enough without one.\n if (modes) lines.push(this.diagnosticLine('MODES', modes, WARNING_COLOR));\n if (session) lines.push(this.diagnosticLine('SESSION', session, HEADING_COLOR));\n for (const [index, extension] of extensions.entries()) {\n const isFirst = index === 0;\n lines.push(\n this.diagnosticLine(\n isFirst ? 'EXTENSIONS' : CONTINUATION_MARKER,\n extension,\n isFirst ? ACCENT_COLOR : DIM_COLOR,\n ),\n );\n }\n return lines;\n }\n\n private diagnosticLine(label: string, value: string, color: ThemeColor): string {\n return `${this.theme.bold(this.theme.fg(color, label.padEnd(DIAGNOSTIC_LABEL_WIDTH)))}${value}`;\n }\n\n /**\n * The enabled modes with their detail, which is the long form of what the\n * editor's badge row shows as bare names.\n *\n * This panel is opened deliberately and has room, so it is the right place\n * for `plan (normal - read only)`: the badge row is glanced at mid-typing and\n * only has to answer which modes are on.\n */\n private modeDiagnostics(): string | undefined {\n if (this.modes.length === 0) return undefined;\n return this.modes\n .map((mode) => {\n const label = this.theme.fg(WARNING_COLOR, mode.label.toLowerCase());\n return mode.detail ? `${label}${this.theme.fg(DIM_COLOR, ` (${mode.detail})`)}` : label;\n })\n .join(this.theme.fg(DIM_COLOR, STATUS_SEPARATOR));\n }\n\n private sessionDiagnostics(): string | undefined {\n if (!this.context) return undefined;\n\n let input = 0;\n let output = 0;\n let cacheRead = 0;\n let cacheWrite = 0;\n let cost = 0;\n for (const entry of this.context.sessionManager.getBranch()) {\n if (entry.type !== 'message' || entry.message.role !== 'assistant') continue;\n input += entry.message.usage.input;\n output += entry.message.usage.output;\n cacheRead += entry.message.usage.cacheRead;\n cacheWrite += entry.message.usage.cacheWrite;\n cost += entry.message.usage.cost.total;\n }\n\n const cachedTotal = input + cacheRead + cacheWrite;\n const cacheHit = cachedTotal === 0 ? 0 : (cacheRead / cachedTotal) * 100;\n const usage = this.context.getContextUsage();\n const context =\n usage?.percent === null || usage?.percent === undefined\n ? 'ctx ?'\n : `ctx ${usage.percent.toFixed(1)}% / ${formatTokens(usage.contextWindow)}`;\n // Built as sibling segments rather than nested theme calls so each color\n // closes cleanly instead of resetting the one around it.\n const metrics = this.theme.fg(\n MUTED_COLOR,\n `↑${formatTokens(input)} ↓${formatTokens(output)} R${formatTokens(cacheRead)} W${formatTokens(\n cacheWrite,\n )} CH${cacheHit.toFixed(1)}%`,\n );\n const costText = this.theme.fg(WARNING_COLOR, `$${cost.toFixed(3)}`);\n return `${metrics}${SEGMENT_GAP}${costText}${this.theme.fg(DIM_COLOR, `${STATUS_SEPARATOR}${context}`)}`;\n }\n\n private extensionDiagnostics(width: number): string[] {\n if (!this.footerData) return [];\n\n const statuses = [...this.footerData.getExtensionStatuses().values()];\n const providers = this.footerData.getAvailableProviderCount();\n const values = [...statuses];\n if (providers > 0) values.push(`${providers} provider${providers === 1 ? '' : 's'}`);\n const colored = values.map((value, index) =>\n this.theme.fg(STATUS_COLORS[index % STATUS_COLORS.length] as ThemeColor, value),\n );\n const valueWidth = Math.max(1, width - DIAGNOSTIC_VALUE_INSET);\n return packSegments(colored, valueWidth, this.theme.fg(DIM_COLOR, STATUS_SEPARATOR)).slice(0, MAX_EXTENSION_LINES);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"leaderHints.mjs","names":[],"sources":["../../src/tui/leaderHints.ts"],"sourcesContent":["import type { ExtensionContext, ReadonlyFooterDataProvider, Theme, ThemeColor } from '@earendil-works/pi-coding-agent';\n\nimport type { Component } from '@earendil-works/pi-tui';\nimport type { LeaderSnapshot, UiMinorModeStatus } from '../services/state/uiState.ts';\nimport { alignLine, fitStyledLine, formatTokens, packSegments, padLine } from './rendering.ts';\n\n/** The background palette is not exported by name, so take it from the method. */\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\nconst WIDE_HINT_WIDTH = 100;\n/**\n * No frame rows to pay for, so the whole budget is content. Two-line option\n * cards need roughly double the grid rows a single-line list did, plus a blank\n * row of breathing space at the top and bottom.\n *\n * Exported because tests assert the panel stays inside it; a second copy of the\n * number in the test file drifted from this one and stopped catching anything.\n */\nexport const MAX_WIDGET_LINES = 21;\n/** Blank, header, blank above the grid, and the closing blank below it. */\nconst CHROME_LINES = 4;\n/** Rule, MODES, SESSION, EXTENSIONS: what the root board's diagnostics want. */\nconst DIAGNOSTIC_RESERVE = 4;\n/** Leading pad inside the shaded block, mirrored by padLine on the right. */\nconst PANEL_INSET = ' ';\nconst MIN_RENDER_WIDTH = 4;\nconst CONTENT_INSET = 4;\nconst GRID_GAP = 2;\nconst DIAGNOSTIC_LABEL_WIDTH = 11;\nconst DIAGNOSTIC_VALUE_INSET = 15;\nconst MAX_EXTENSION_LINES = 2;\nconst STATUS_SEPARATOR = ' · ';\nconst SEGMENT_GAP = ' ';\n/**\n * Column count comes from width alone, never from the widest cell, so the root\n * board and every sub-board land on the same grid at the same terminal size.\n * Sizing to content made one long description narrow every other space's grid.\n */\nconst COLUMN_BREAKPOINTS: readonly { minWidth: number; columns: number }[] = [\n { minWidth: 130, columns: 4 },\n { minWidth: 90, columns: 3 },\n { minWidth: 56, columns: 2 },\n];\nconst SINGLE_COLUMN = 1;\n/** Marks the row that leaves an active minor mode; painted apart from the rest. */\nconst EXIT_TONE = 'exit';\n/** Puts the description under the label rather than under the badge. */\nconst DETAIL_INDENT = ' ';\nconst HORIZONTAL_BORDER = '─';\nconst CONTINUATION_MARKER = '↳';\nconst DIM_COLOR: ThemeColor = 'dim';\nconst TEXT_COLOR: ThemeColor = 'text';\nconst MUTED_COLOR: ThemeColor = 'muted';\nconst ACCENT_COLOR: ThemeColor = 'accent';\nconst WARNING_COLOR: ThemeColor = 'warning';\nconst HEADING_COLOR: ThemeColor = 'mdHeading';\nconst BORDER_COLOR: ThemeColor = 'borderAccent';\nconst MUTED_BORDER_COLOR: ThemeColor = 'borderMuted';\n/** Subtle panel shade; avoid the brighter active/selection surface used by messages. */\nconst PANEL_BACKGROUND: ThemeBg = 'toolPendingBg';\n// The mockup colors each extension status differently so adjacent values stay\n// separable at a glance; cycling this palette reproduces that without hardcoding\n// a color per status key.\nconst STATUS_COLORS: ThemeColor[] = ['mdCode', 'toolDiffAdded', 'warning', 'muted'];\n\nexport class LeaderHints implements Component {\n constructor(\n private readonly theme: Theme,\n private readonly snapshot: LeaderSnapshot,\n private readonly context?: ExtensionContext,\n private readonly footerData?: ReadonlyFooterDataProvider,\n private readonly modes: readonly UiMinorModeStatus[] = [],\n ) {}\n\n invalidate(): void {}\n\n render(width: number): string[] {\n if (width <= 0 || !this.snapshot.active) return [];\n if (width < MIN_RENDER_WIDTH) return [this.theme.fg(BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n\n // A filled block rather than a framed one: the shade alone separates the\n // panel from the transcript, which buys back the two rows a frame costs.\n const contentWidth = Math.max(1, width - CONTENT_INSET);\n const shade = (content: string): string =>\n this.theme.bg(PANEL_BACKGROUND, padLine(`${PANEL_INSET}${content}`, width));\n const badge = this.theme.inverse(this.theme.bold(this.theme.fg(HEADING_COLOR, ' LEADER ')));\n const breadcrumb = `${this.snapshot.prefix.join(' › ')} / ${this.snapshot.label}`;\n const header = shade(\n alignLine(\n `${badge} ${this.theme.bold(this.theme.fg(TEXT_COLOR, breadcrumb))}`,\n this.theme.fg(DIM_COLOR, 'esc cancel · backspace up'),\n contentWidth,\n ),\n );\n const rootTrail = this.snapshot.rootOptions?.map((option) => `${option.key} ${option.label}`).join(' · ');\n const trail =\n width >= WIDE_HINT_WIDTH && this.snapshot.prefix.length > 1 && rootTrail\n ? [shade(this.theme.fg(DIM_COLOR, `root ${rootTrail}`))]\n : [];\n // Options are the interactive surface, so they claim space before the\n // diagnostics do; truncating them would hide a reachable key.\n const optionBudget = Math.max(1, MAX_WIDGET_LINES - 2 - trail.length);\n const columns = COLUMN_BREAKPOINTS.find((breakpoint) => width >= breakpoint.minWidth)?.columns ?? SINGLE_COLUMN;\n // Two-line cards are the intended rhythm, but they are not worth the rows\n // they cost once they start pushing diagnostics off the bottom. Sub-boards\n // draw no diagnostics, so they reserve nothing and keep their descriptions.\n const reserve = this.snapshot.prefix.length === 1 ? DIAGNOSTIC_RESERVE : 0;\n const detailBudget = Math.max(1, MAX_WIDGET_LINES - CHROME_LINES - trail.length - reserve);\n const optionLines = this.renderOptionGrid(contentWidth, columns, detailBudget).slice(0, optionBudget).map(shade);\n // Four rows are spoken for before diagnostics get any: the blank, header and\n // blank above, and the closing blank below. Counting only two of them let\n // this budget overrun, and the final `slice` did the real cutting silently\n // from the end, which is how the EXTENSIONS row went missing when MODES\n // arrived. Take all four off here so what is dropped is decided in one place.\n const diagnosticsBudget = Math.max(0, MAX_WIDGET_LINES - 4 - trail.length - optionLines.length);\n // Session and extension diagnostics belong to the board opened directly by\n // SPC/^SPC. Sub-boards stay focused on their own actions and navigation.\n const diagnostics =\n this.snapshot.prefix.length === 1\n ? this.renderDiagnostics(contentWidth).slice(0, diagnosticsBudget).map(shade)\n : [];\n // Blank shaded rows top and bottom: the panel butts against the editor above\n // and the modeline below, so the shade needs room before its first glyph.\n const body = [shade(''), header, shade(''), ...optionLines, ...trail, ...diagnostics];\n\n return [...body.slice(0, MAX_WIDGET_LINES - 1), shade('')];\n }\n\n /**\n * Lays options out on a fixed cell grid rather than a packed run of segments,\n * so keys and labels stay column-aligned when the list wraps.\n *\n * `budget` is the rows the grid may take while still leaving the panel its\n * chrome and diagnostics. It decides whether descriptions are drawn, not\n * whether options are kept: a grid that overruns is cut from the end by the\n * caller, which would take whole options away with it.\n */\n private renderOptionGrid(width: number, columns: number, budget: number): string[] {\n if (this.snapshot.options.length === 0) return [];\n\n // Equal cells, sized from the grid rather than from their contents, so a\n // long description is ellipsised instead of widening every other column.\n const cellWidth = Math.max(1, Math.floor((width - GRID_GAP * (columns - 1)) / columns));\n const withDetail = Math.ceil(this.snapshot.options.length / columns) * 2 <= budget;\n\n // Two lines per option, as the mockup lays them out: the key badge and a\n // bold label, then the description indented under the label.\n const cells = this.snapshot.options.map((option) => {\n const badge = this.theme.inverse(\n this.theme.bold(this.theme.fg(option.tone === EXIT_TONE ? WARNING_COLOR : ACCENT_COLOR, ` ${option.key} `)),\n );\n return {\n heading: `${badge} ${this.theme.bold(this.theme.fg(TEXT_COLOR, option.label))}`,\n detail: option.detail ? `${DETAIL_INDENT}${this.theme.fg(DIM_COLOR, option.detail)}` : '',\n };\n });\n\n const gap = ' '.repeat(GRID_GAP);\n const rows: string[] = [];\n for (let index = 0; index < cells.length; index += columns) {\n const group = cells.slice(index, index + columns);\n // fitStyledLine, not padLine: fixed cells clip descriptions routinely now,\n // and it is the one that ellipsises and drops the hard reset pi-tui injects,\n // which would otherwise cancel the panel shade partway along the row.\n rows.push(group.map((cell) => fitStyledLine(cell.heading, cellWidth)).join(gap));\n // Keep the second row even when every detail is empty. Each option card\n // then retains the same two-line rhythm instead of collapsing terse items\n // into a denser list than items with supporting text.\n if (withDetail) rows.push(group.map((cell) => fitStyledLine(cell.detail, cellWidth)).join(gap));\n }\n return rows;\n }\n\n /** Content lines only: the caller paints the shade and pads to full width. */\n private renderDiagnostics(width: number): string[] {\n const session = this.sessionDiagnostics();\n const extensions = this.extensionDiagnostics(width);\n const modes = this.modeDiagnostics();\n if (!session && !modes && extensions.length === 0) return [];\n\n const lines = [this.theme.fg(MUTED_BORDER_COLOR, HORIZONTAL_BORDER.repeat(width))];\n // Above SESSION because it is the more consequential fact: session counters\n // describe what this session has spent, a mode describes what it will do.\n //\n // No blank rows between these three. The diagnostics budget is what is left\n // after the option grid takes its share, which at a full grid is exactly one\n // row per label: a spacer here does not cost breathing room, it costs the\n // EXTENSIONS row entirely. Distinct labels in distinct colours separate them\n // well enough without one.\n if (modes) lines.push(this.diagnosticLine('MODES', modes, WARNING_COLOR));\n if (session) lines.push(this.diagnosticLine('SESSION', session, HEADING_COLOR));\n for (const [index, extension] of extensions.entries()) {\n const isFirst = index === 0;\n lines.push(\n this.diagnosticLine(\n isFirst ? 'EXTENSIONS' : CONTINUATION_MARKER,\n extension,\n isFirst ? ACCENT_COLOR : DIM_COLOR,\n ),\n );\n }\n return lines;\n }\n\n private diagnosticLine(label: string, value: string, color: ThemeColor): string {\n return `${this.theme.bold(this.theme.fg(color, label.padEnd(DIAGNOSTIC_LABEL_WIDTH)))}${value}`;\n }\n\n /**\n * The enabled modes with their detail, which is the long form of what the\n * editor's badge row shows as bare names.\n *\n * This panel is opened deliberately and has room, so it is the right place\n * for `plan (normal - read only)`: the badge row is glanced at mid-typing and\n * only has to answer which modes are on.\n */\n private modeDiagnostics(): string | undefined {\n if (this.modes.length === 0) return undefined;\n return this.modes\n .map((mode) => {\n const label = this.theme.fg(WARNING_COLOR, mode.label.toLowerCase());\n return mode.detail ? `${label}${this.theme.fg(DIM_COLOR, ` (${mode.detail})`)}` : label;\n })\n .join(this.theme.fg(DIM_COLOR, STATUS_SEPARATOR));\n }\n\n private sessionDiagnostics(): string | undefined {\n if (!this.context) return undefined;\n\n let input = 0;\n let output = 0;\n let cacheRead = 0;\n let cacheWrite = 0;\n let cost = 0;\n for (const entry of this.context.sessionManager.getBranch()) {\n if (entry.type !== 'message' || entry.message.role !== 'assistant') continue;\n input += entry.message.usage.input;\n output += entry.message.usage.output;\n cacheRead += entry.message.usage.cacheRead;\n cacheWrite += entry.message.usage.cacheWrite;\n cost += entry.message.usage.cost.total;\n }\n\n const cachedTotal = input + cacheRead + cacheWrite;\n const cacheHit = cachedTotal === 0 ? 0 : (cacheRead / cachedTotal) * 100;\n const usage = this.context.getContextUsage();\n const context =\n usage?.percent === null || usage?.percent === undefined\n ? 'ctx ?'\n : `ctx ${usage.percent.toFixed(1)}% / ${formatTokens(usage.contextWindow)}`;\n // Built as sibling segments rather than nested theme calls so each color\n // closes cleanly instead of resetting the one around it.\n const metrics = this.theme.fg(\n MUTED_COLOR,\n `↑${formatTokens(input)} ↓${formatTokens(output)} R${formatTokens(cacheRead)} W${formatTokens(\n cacheWrite,\n )} CH${cacheHit.toFixed(1)}%`,\n );\n const costText = this.theme.fg(WARNING_COLOR, `$${cost.toFixed(3)}`);\n return `${metrics}${SEGMENT_GAP}${costText}${this.theme.fg(DIM_COLOR, `${STATUS_SEPARATOR}${context}`)}`;\n }\n\n private extensionDiagnostics(width: number): string[] {\n if (!this.footerData) return [];\n\n const statuses = [...this.footerData.getExtensionStatuses().values()];\n const providers = this.footerData.getAvailableProviderCount();\n const values = [...statuses];\n if (providers > 0) values.push(`${providers} provider${providers === 1 ? '' : 's'}`);\n const colored = values.map((value, index) =>\n this.theme.fg(STATUS_COLORS[index % STATUS_COLORS.length] as ThemeColor, value),\n );\n const valueWidth = Math.max(1, width - DIAGNOSTIC_VALUE_INSET);\n return packSegments(colored, valueWidth, this.theme.fg(DIM_COLOR, STATUS_SEPARATOR)).slice(0, MAX_EXTENSION_LINES);\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-ui",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.34",
|
|
4
4
|
"description": "Leader-key menus, themes, and TUI components for DoomPi and Pi extensions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -160,9 +160,9 @@
|
|
|
160
160
|
"dependencies": {
|
|
161
161
|
"@deepseek-ai/cordis": "4.0.1",
|
|
162
162
|
"yaml": "2.9.0",
|
|
163
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
164
|
-
"@agimon-ai/doompi-
|
|
165
|
-
"@agimon-ai/doompi-
|
|
163
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.34",
|
|
164
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.34",
|
|
165
|
+
"@agimon-ai/doompi-hashline": "0.0.1-alpha.6"
|
|
166
166
|
},
|
|
167
167
|
"devDependencies": {
|
|
168
168
|
"@earendil-works/pi-coding-agent": "0.84.2",
|