@openadapter/koda-tui 1.0.0-beta.3

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.
Files changed (60) hide show
  1. package/README.md +791 -0
  2. package/dist/autocomplete.d.ts +53 -0
  3. package/dist/autocomplete.js +2 -0
  4. package/dist/components/box.d.ts +21 -0
  5. package/dist/components/box.js +1 -0
  6. package/dist/components/cancellable-loader.d.ts +21 -0
  7. package/dist/components/cancellable-loader.js +1 -0
  8. package/dist/components/editor.d.ts +249 -0
  9. package/dist/components/editor.js +17 -0
  10. package/dist/components/image.d.ts +27 -0
  11. package/dist/components/image.js +1 -0
  12. package/dist/components/input.d.ts +36 -0
  13. package/dist/components/input.js +2 -0
  14. package/dist/components/loader.d.ts +30 -0
  15. package/dist/components/loader.js +1 -0
  16. package/dist/components/markdown.d.ts +95 -0
  17. package/dist/components/markdown.js +5 -0
  18. package/dist/components/select-list.d.ts +49 -0
  19. package/dist/components/select-list.js +1 -0
  20. package/dist/components/settings-list.d.ts +49 -0
  21. package/dist/components/settings-list.js +1 -0
  22. package/dist/components/spacer.d.ts +11 -0
  23. package/dist/components/spacer.js +1 -0
  24. package/dist/components/text.d.ts +18 -0
  25. package/dist/components/text.js +1 -0
  26. package/dist/components/truncated-text.d.ts +12 -0
  27. package/dist/components/truncated-text.js +2 -0
  28. package/dist/editor-component.d.ts +38 -0
  29. package/dist/editor-component.js +0 -0
  30. package/dist/fuzzy.d.ts +15 -0
  31. package/dist/fuzzy.js +1 -0
  32. package/dist/index.d.ts +22 -0
  33. package/dist/index.js +1 -0
  34. package/dist/keybindings.d.ts +192 -0
  35. package/dist/keybindings.js +1 -0
  36. package/dist/keys.d.ts +183 -0
  37. package/dist/keys.js +5 -0
  38. package/dist/kill-ring.d.ts +27 -0
  39. package/dist/kill-ring.js +1 -0
  40. package/dist/native-modifiers.d.ts +2 -0
  41. package/dist/native-modifiers.js +1 -0
  42. package/dist/stdin-buffer.d.ts +49 -0
  43. package/dist/stdin-buffer.js +1 -0
  44. package/dist/terminal-image.d.ts +89 -0
  45. package/dist/terminal-image.js +1 -0
  46. package/dist/terminal.d.ts +112 -0
  47. package/dist/terminal.js +1 -0
  48. package/dist/tui.d.ts +241 -0
  49. package/dist/tui.js +11 -0
  50. package/dist/undo-stack.d.ts +16 -0
  51. package/dist/undo-stack.js +1 -0
  52. package/dist/utils.d.ts +83 -0
  53. package/dist/utils.js +2 -0
  54. package/dist/word-navigation.d.ts +24 -0
  55. package/dist/word-navigation.js +1 -0
  56. package/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  57. package/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  58. package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  59. package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  60. package/package.json +55 -0
@@ -0,0 +1,53 @@
1
+ export interface AutocompleteItem {
2
+ value: string;
3
+ label: string;
4
+ description?: string;
5
+ }
6
+ type Awaitable<T> = T | Promise<T>;
7
+ export interface SlashCommand {
8
+ name: string;
9
+ description?: string;
10
+ argumentHint?: string;
11
+ getArgumentCompletions?(argumentPrefix: string): Awaitable<AutocompleteItem[] | null>;
12
+ }
13
+ export interface AutocompleteSuggestions {
14
+ items: AutocompleteItem[];
15
+ prefix: string;
16
+ }
17
+ export interface AutocompleteProvider {
18
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number, options: {
19
+ signal: AbortSignal;
20
+ force?: boolean;
21
+ }): Promise<AutocompleteSuggestions | null>;
22
+ applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
23
+ lines: string[];
24
+ cursorLine: number;
25
+ cursorCol: number;
26
+ };
27
+ shouldTriggerFileCompletion?(lines: string[], cursorLine: number, cursorCol: number): boolean;
28
+ }
29
+ export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
30
+ private commands;
31
+ private basePath;
32
+ private fdPath;
33
+ constructor(commands: (AutocompleteItem | SlashCommand)[] | undefined, basePath: string, fdPath?: string | null);
34
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number, options: {
35
+ signal: AbortSignal;
36
+ force?: boolean;
37
+ }): Promise<AutocompleteSuggestions | null>;
38
+ applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
39
+ lines: string[];
40
+ cursorLine: number;
41
+ cursorCol: number;
42
+ };
43
+ private extractAtPrefix;
44
+ private extractPathPrefix;
45
+ private expandHomePath;
46
+ private resolveScopedFuzzyQuery;
47
+ private scopedPathForDisplay;
48
+ private getFileSuggestions;
49
+ private scoreEntry;
50
+ private getFuzzyFileSuggestions;
51
+ shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
52
+ }
53
+ export {};
@@ -0,0 +1,2 @@
1
+ var k=Object.defineProperty;var p=(o,t)=>k(o,"name",{value:t,configurable:!0});import{spawn as N}from"child_process";import{readdirSync as O,statSync as L}from"fs";import{homedir as F}from"os";import{basename as A,dirname as S,join as v}from"path";import{fuzzyFilter as j}from"./fuzzy.js";const z=new Set([" "," ",'"',"'","="]);function Q(o){return o.replace(/\\/g,"/")}p(Q,"toDisplayPath");function q(o){return o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}p(q,"escapeRegex");function M(o){const t=Q(o);if(!t.includes("/"))return t;const s=t.endsWith("/"),n=t.replace(/^\/+|\/+$/g,"");if(!n)return t;const e="[\\\\/]",i=n.split("/").filter(Boolean).map(r=>q(r));if(i.length===0)return t;let a=i.join(e);return s&&(a+=e),a}p(M,"buildFdPathQuery");function I(o){for(let t=o.length-1;t>=0;t-=1)if(z.has(o[t]??""))return t;return-1}p(I,"findLastDelimiter");function V(o){let t=!1,s=-1;for(let n=0;n<o.length;n+=1)o[n]==='"'&&(t=!t,t&&(s=n));return t?s:null}p(V,"findUnclosedQuoteStart");function T(o,t){return t===0||z.has(o[t-1]??"")}p(T,"isTokenStart");function E(o){const t=V(o);return t===null?null:t>0&&o[t-1]==="@"?T(o,t-1)?o.slice(t-1):null:T(o,t)?o.slice(t):null}p(E,"extractQuotedPrefix");function B(o){return o.startsWith('@"')?{rawPrefix:o.slice(2),isAtPrefix:!0,isQuotedPrefix:!0}:o.startsWith('"')?{rawPrefix:o.slice(1),isAtPrefix:!1,isQuotedPrefix:!0}:o.startsWith("@")?{rawPrefix:o.slice(1),isAtPrefix:!0,isQuotedPrefix:!1}:{rawPrefix:o,isAtPrefix:!1,isQuotedPrefix:!1}}p(B,"parsePathPrefix");function H(o,t){const s=t.isQuotedPrefix||o.includes(" "),n=t.isAtPrefix?"@":"";return s?`${`${n}"`}${o}"`:`${n}${o}`}p(H,"buildCompletionValue");async function _(o,t,s,n,e){const i=["--base-directory",o,"--max-results",String(n),"--type","f","--type","d","--follow","--hidden","--exclude",".git","--exclude",".git/*","--exclude",".git/**"];return Q(s).includes("/")&&i.push("--full-path"),s&&i.push(M(s)),await new Promise(a=>{if(e.aborted){a([]);return}const r=N(t,i,{stdio:["ignore","pipe","pipe"]});let m="",y=!1;const h=p(l=>{y||(y=!0,e.removeEventListener("abort",u),a(l))},"finish"),u=p(()=>{r.exitCode===null&&r.kill("SIGKILL")},"onAbort");e.addEventListener("abort",u,{once:!0}),r.stdout.setEncoding("utf-8"),r.stdout.on("data",l=>{m+=l}),r.on("error",()=>{h([])}),r.on("close",l=>{if(e.aborted||l!==0||!m){h([]);return}const c=m.trim().split(`
2
+ `).filter(Boolean),d=[];for(const g of c){const P=Q(g),b=P.endsWith("/"),f=b?P.slice(0,-1):P;f===".git"||f.startsWith(".git/")||f.includes("/.git/")||d.push({path:P,isDirectory:b})}h(d)})})}p(_,"walkDirectoryWithFd");class Z{static{p(this,"CombinedAutocompleteProvider")}commands;basePath;fdPath;constructor(t=[],s,n=null){this.commands=t,this.basePath=s,this.fdPath=n}async getSuggestions(t,s,n,e){const a=(t[s]||"").slice(0,n),r=this.extractAtPrefix(a);if(r){const{rawPrefix:h,isQuotedPrefix:u}=B(r),l=await this.getFuzzyFileSuggestions(h,{isQuotedPrefix:u,signal:e.signal});return l.length===0?null:{items:l,prefix:r}}if(!e.force&&a.startsWith("/")){const h=a.indexOf(" ");if(h===-1){const g=a.slice(1),P=this.commands.map(f=>{const w="name"in f?f.name:f.value,x="argumentHint"in f&&f.argumentHint?f.argumentHint:void 0,W=f.description??"",$=x?W?`${x} \u2014 ${W}`:x:W;return{name:w,label:w,description:$||void 0}}),b=j(P,g,f=>f.name).map(f=>({value:f.name,label:f.label,...f.description&&{description:f.description}}));return b.length===0?null:{items:b,prefix:a}}const u=a.slice(1,h),l=a.slice(h+1),c=this.commands.find(g=>("name"in g?g.name:g.value)===u);if(!c||!("getArgumentCompletions"in c)||!c.getArgumentCompletions)return null;const d=await c.getArgumentCompletions(l);return!Array.isArray(d)||d.length===0?null:{items:d,prefix:l}}const m=this.extractPathPrefix(a,e.force??!1);if(m===null)return null;const y=this.getFileSuggestions(m);return y.length===0?null:{items:y,prefix:m}}applyCompletion(t,s,n,e,i){const a=t[s]||"",r=a.slice(0,n-i.length),m=a.slice(n),y=i.startsWith('"')||i.startsWith('@"'),h=m.startsWith('"'),u=e.value.endsWith('"'),l=y&&u&&h?m.slice(1):m;if(i.startsWith("/")&&r.trim()===""&&!i.slice(1).includes("/")){const x=`${r}/${e.value} ${l}`,W=[...t];return W[s]=x,{lines:W,cursorLine:s,cursorCol:r.length+e.value.length+2}}if(i.startsWith("@")){const x=e.label.endsWith("/"),W=x?"":" ",$=`${r+e.value}${W}${l}`,D=[...t];D[s]=$;const C=e.value.endsWith('"'),R=x&&C?e.value.length-1:e.value.length;return{lines:D,cursorLine:s,cursorCol:r.length+R+W.length}}const d=a.slice(0,n);if(d.includes("/")&&d.includes(" ")){const x=r+e.value+l,W=[...t];W[s]=x;const $=e.label.endsWith("/"),D=e.value.endsWith('"'),C=$&&D?e.value.length-1:e.value.length;return{lines:W,cursorLine:s,cursorCol:r.length+C}}const g=r+e.value+l,P=[...t];P[s]=g;const b=e.label.endsWith("/"),f=e.value.endsWith('"'),w=b&&f?e.value.length-1:e.value.length;return{lines:P,cursorLine:s,cursorCol:r.length+w}}extractAtPrefix(t){const s=E(t);if(s?.startsWith('@"'))return s;const n=I(t),e=n===-1?0:n+1;return t[e]==="@"?t.slice(e):null}extractPathPrefix(t,s=!1){const n=E(t);if(n)return n;const e=I(t),i=e===-1?t:t.slice(e+1);return s||i.includes("/")||i.startsWith(".")||i.startsWith("~/")||i===""&&t.endsWith(" ")?i:null}expandHomePath(t){if(t.startsWith("~/")){const s=v(F(),t.slice(2));return t.endsWith("/")&&!s.endsWith("/")?`${s}/`:s}else if(t==="~")return F();return t}resolveScopedFuzzyQuery(t){const s=Q(t),n=s.lastIndexOf("/");if(n===-1)return null;const e=s.slice(0,n+1),i=s.slice(n+1);let a;e.startsWith("~/")?a=this.expandHomePath(e):e.startsWith("/")?a=e:a=v(this.basePath,e);try{if(!L(a).isDirectory())return null}catch{return null}return{baseDir:a,query:i,displayBase:e}}scopedPathForDisplay(t,s){const n=Q(s);return t==="/"?`/${n}`:`${Q(t)}${n}`}getFileSuggestions(t){try{let s,n;const{rawPrefix:e,isAtPrefix:i,isQuotedPrefix:a}=B(t);let r=e;if(r.startsWith("~")&&(r=this.expandHomePath(r)),e===""||e==="./"||e==="../"||e==="~"||e==="~/"||e==="/"||i&&e==="")e.startsWith("~")||r.startsWith("/")?s=r:s=v(this.basePath,r),n="";else if(e.endsWith("/"))e.startsWith("~")||r.startsWith("/")?s=r:s=v(this.basePath,r),n="";else{const u=S(r),l=A(r);e.startsWith("~")||r.startsWith("/")?s=u:s=v(this.basePath,u),n=l}const y=O(s,{withFileTypes:!0}),h=[];for(const u of y){if(!u.name.toLowerCase().startsWith(n.toLowerCase()))continue;let l=u.isDirectory();if(!l&&u.isSymbolicLink())try{const f=v(s,u.name);l=L(f).isDirectory()}catch{}let c;const d=u.name,g=e;if(g.endsWith("/"))c=g+d;else if(g.includes("/")||g.includes("\\"))if(g.startsWith("~/")){const f=g.slice(2),w=S(f);c=`~/${w==="."?d:v(w,d)}`}else if(g.startsWith("/")){const f=S(g);f==="/"?c=`/${d}`:c=`${f}/${d}`}else c=v(S(g),d),g.startsWith("./")&&!c.startsWith("./")&&(c=`./${c}`);else g.startsWith("~")?c=`~/${d}`:c=d;c=Q(c);const P=l?`${c}/`:c,b=H(P,{isDirectory:l,isAtPrefix:i,isQuotedPrefix:a});h.push({value:b,label:d+(l?"/":"")})}return h.sort((u,l)=>{const c=u.value.endsWith("/"),d=l.value.endsWith("/");return c&&!d?-1:!c&&d?1:u.label.localeCompare(l.label)}),h}catch{return[]}}scoreEntry(t,s,n){const i=A(t).toLowerCase(),a=s.toLowerCase();let r=0;return i===a?r=100:i.startsWith(a)?r=80:i.includes(a)?r=50:t.toLowerCase().includes(a)&&(r=30),n&&r>0&&(r+=10),r}async getFuzzyFileSuggestions(t,s){if(!this.fdPath||s.signal.aborted)return[];try{const n=this.resolveScopedFuzzyQuery(t),e=n?.baseDir??this.basePath,i=n?.query??t,a=await _(e,this.fdPath,i,100,s.signal);if(s.signal.aborted)return[];const r=a.map(h=>({...h,score:i?this.scoreEntry(h.path,i,h.isDirectory):1})).filter(h=>h.score>0);r.sort((h,u)=>u.score-h.score);const m=r.slice(0,20),y=[];for(const{path:h,isDirectory:u}of m){const l=u?h.slice(0,-1):h,c=n?this.scopedPathForDisplay(n.displayBase,l):l,d=A(l),g=u?`${c}/`:c,P=H(g,{isDirectory:u,isAtPrefix:!0,isQuotedPrefix:s.isQuotedPrefix});y.push({value:P,label:d+(u?"/":""),description:c})}return y}catch{return[]}}shouldTriggerFileCompletion(t,s,n){const i=(t[s]||"").slice(0,n);return!(i.trim().startsWith("/")&&!i.trim().includes(" "))}}export{Z as CombinedAutocompleteProvider};
@@ -0,0 +1,21 @@
1
+ import type { Component } from "../tui.ts";
2
+ /**
3
+ * Box component - a container that applies padding and background to all children
4
+ */
5
+ export declare class Box implements Component {
6
+ children: Component[];
7
+ private paddingX;
8
+ private paddingY;
9
+ private bgFn?;
10
+ private cache?;
11
+ constructor(paddingX?: number, paddingY?: number, bgFn?: (text: string) => string);
12
+ addChild(component: Component): void;
13
+ removeChild(component: Component): void;
14
+ clear(): void;
15
+ setBgFn(bgFn?: (text: string) => string): void;
16
+ private invalidateCache;
17
+ private matchCache;
18
+ invalidate(): void;
19
+ render(width: number): string[];
20
+ private applyBg;
21
+ }
@@ -0,0 +1 @@
1
+ var o=Object.defineProperty;var d=(c,i)=>o(c,"name",{value:i,configurable:!0});import{applyBackgroundToLine as p,visibleWidth as g}from"../utils.js";class v{static{d(this,"Box")}children=[];paddingX;paddingY;bgFn;cache;constructor(i=1,t=1,h){this.paddingX=i,this.paddingY=t,this.bgFn=h}addChild(i){this.children.push(i),this.invalidateCache()}removeChild(i){const t=this.children.indexOf(i);t!==-1&&(this.children.splice(t,1),this.invalidateCache())}clear(){this.children=[],this.invalidateCache()}setBgFn(i){this.bgFn=i}invalidateCache(){this.cache=void 0}matchCache(i,t,h){const e=this.cache;return!!e&&e.width===i&&e.bgSample===h&&e.childLines.length===t.length&&e.childLines.every((s,a)=>s===t[a])}invalidate(){this.invalidateCache();for(const i of this.children)i.invalidate?.()}render(i){if(this.children.length===0)return[];const t=Math.max(1,i-this.paddingX*2),h=" ".repeat(this.paddingX),e=[];for(const n of this.children){const l=n.render(t);for(const r of l)e.push(h+r)}if(e.length===0)return[];const s=this.bgFn?this.bgFn("test"):void 0;if(this.matchCache(i,e,s))return this.cache.lines;const a=[];for(let n=0;n<this.paddingY;n++)a.push(this.applyBg("",i));for(const n of e)a.push(this.applyBg(n,i));for(let n=0;n<this.paddingY;n++)a.push(this.applyBg("",i));return this.cache={childLines:e,width:i,bgSample:s,lines:a},a}applyBg(i,t){const h=g(i),e=Math.max(0,t-h),s=i+" ".repeat(e);return this.bgFn?p(s,t,this.bgFn):s}}export{v as Box};
@@ -0,0 +1,21 @@
1
+ import { Loader } from "./loader.ts";
2
+ /**
3
+ * Loader that can be cancelled with Escape.
4
+ * Extends Loader with an AbortSignal for cancelling async operations.
5
+ *
6
+ * @example
7
+ * const loader = new CancellableLoader(tui, cyan, dim, "Working...");
8
+ * loader.onAbort = () => done(null);
9
+ * doWork(loader.signal).then(done);
10
+ */
11
+ export declare class CancellableLoader extends Loader {
12
+ private abortController;
13
+ /** Called when user presses Escape */
14
+ onAbort?: () => void;
15
+ /** AbortSignal that is aborted when user presses Escape */
16
+ get signal(): AbortSignal;
17
+ /** Whether the loader was aborted */
18
+ get aborted(): boolean;
19
+ handleInput(data: string): void;
20
+ dispose(): void;
21
+ }
@@ -0,0 +1 @@
1
+ var e=Object.defineProperty;var r=(o,t)=>e(o,"name",{value:t,configurable:!0});import{getKeybindings as n}from"../keybindings.js";import{Loader as l}from"./loader.js";class d extends l{static{r(this,"CancellableLoader")}abortController=new AbortController;onAbort;get signal(){return this.abortController.signal}get aborted(){return this.abortController.signal.aborted}handleInput(t){n().matches(t,"tui.select.cancel")&&(this.abortController.abort(),this.onAbort?.())}dispose(){this.stop()}}export{d as CancellableLoader};
@@ -0,0 +1,249 @@
1
+ import type { AutocompleteProvider } from "../autocomplete.ts";
2
+ import { type Component, type Focusable, type TUI } from "../tui.ts";
3
+ import { type SelectListTheme } from "./select-list.ts";
4
+ /**
5
+ * Represents a chunk of text for word-wrap layout.
6
+ * Tracks both the text content and its position in the original line.
7
+ */
8
+ export interface TextChunk {
9
+ text: string;
10
+ startIndex: number;
11
+ endIndex: number;
12
+ }
13
+ /**
14
+ * Split a line into word-wrapped chunks.
15
+ * Wraps at word boundaries when possible, falling back to character-level
16
+ * wrapping for words longer than the available width.
17
+ *
18
+ * @param line - The text line to wrap
19
+ * @param maxWidth - Maximum visible width per chunk
20
+ * @param preSegmented - Optional pre-segmented graphemes (e.g. with paste-marker awareness).
21
+ * When omitted the default Intl.Segmenter is used.
22
+ * @returns Array of chunks with text and position information
23
+ */
24
+ export declare function wordWrapLine(line: string, maxWidth: number, preSegmented?: Intl.SegmentData[]): TextChunk[];
25
+ export interface EditorTheme {
26
+ borderColor: (str: string) => string;
27
+ selectList: SelectListTheme;
28
+ }
29
+ export interface EditorOptions {
30
+ paddingX?: number;
31
+ autocompleteMaxVisible?: number;
32
+ }
33
+ export declare class Editor implements Component, Focusable {
34
+ private state;
35
+ /** Focusable interface - set by TUI when focus changes */
36
+ focused: boolean;
37
+ protected tui: TUI;
38
+ private theme;
39
+ private paddingX;
40
+ private lastWidth;
41
+ private scrollOffset;
42
+ borderColor: (str: string) => string;
43
+ title: string | undefined;
44
+ private autocompleteProvider?;
45
+ private autocompleteList?;
46
+ private autocompleteState;
47
+ private autocompletePrefix;
48
+ private autocompleteMaxVisible;
49
+ private autocompleteAbort?;
50
+ private autocompleteDebounceTimer?;
51
+ private autocompleteRequestTask;
52
+ private autocompleteStartToken;
53
+ private autocompleteRequestId;
54
+ private pastes;
55
+ private pasteCounter;
56
+ private pasteBuffer;
57
+ private isInPaste;
58
+ private history;
59
+ private historyIndex;
60
+ private killRing;
61
+ private lastAction;
62
+ private jumpMode;
63
+ private preferredVisualCol;
64
+ private snappedFromCursorCol;
65
+ private undoStack;
66
+ onSubmit?: (text: string) => void;
67
+ onChange?: (text: string) => void;
68
+ disableSubmit: boolean;
69
+ constructor(tui: TUI, theme: EditorTheme, options?: EditorOptions);
70
+ /** Set of currently valid paste IDs, for marker-aware segmentation. */
71
+ private validPasteIds;
72
+ /** Segment text with paste-marker awareness, only merging markers with valid IDs. */
73
+ private segment;
74
+ getPaddingX(): number;
75
+ setPaddingX(padding: number): void;
76
+ getAutocompleteMaxVisible(): number;
77
+ setAutocompleteMaxVisible(maxVisible: number): void;
78
+ setAutocompleteProvider(provider: AutocompleteProvider): void;
79
+ /**
80
+ * Add a prompt to history for up/down arrow navigation.
81
+ * Called after successful submission.
82
+ */
83
+ addToHistory(text: string): void;
84
+ private isEditorEmpty;
85
+ private isOnFirstVisualLine;
86
+ private isOnLastVisualLine;
87
+ private navigateHistory;
88
+ /** Internal setText that doesn't reset history state - used by navigateHistory */
89
+ private setTextInternal;
90
+ invalidate(): void;
91
+ render(width: number): string[];
92
+ handleInput(data: string): void;
93
+ private layoutText;
94
+ getText(): string;
95
+ private expandPasteMarkers;
96
+ /**
97
+ * Get text with paste markers expanded to their actual content.
98
+ * Use this when you need the full content (e.g., for external editor).
99
+ */
100
+ getExpandedText(): string;
101
+ getLines(): string[];
102
+ getCursor(): {
103
+ line: number;
104
+ col: number;
105
+ };
106
+ setText(text: string): void;
107
+ /**
108
+ * Insert text at the current cursor position.
109
+ * Used for programmatic insertion (e.g., clipboard image markers).
110
+ * This is atomic for undo - single undo restores entire pre-insert state.
111
+ */
112
+ insertTextAtCursor(text: string): void;
113
+ /**
114
+ * Normalize text for editor storage:
115
+ * - Normalize line endings (\r\n and \r -> \n)
116
+ * - Expand tabs to 4 spaces
117
+ */
118
+ private normalizeText;
119
+ /**
120
+ * Internal text insertion at cursor. Handles single and multi-line text.
121
+ * Does not push undo snapshots or trigger autocomplete - caller is responsible.
122
+ * Normalizes line endings and calls onChange once at the end.
123
+ */
124
+ private insertTextAtCursorInternal;
125
+ private insertCharacter;
126
+ private handlePaste;
127
+ private addNewLine;
128
+ private shouldSubmitOnBackslashEnter;
129
+ private submitValue;
130
+ private handleBackspace;
131
+ /**
132
+ * Set cursor column and clear preferredVisualCol.
133
+ * Use this for all non-vertical cursor movements to reset sticky column behavior.
134
+ */
135
+ private setCursorCol;
136
+ /**
137
+ * Move cursor to a target visual line, applying sticky column logic.
138
+ * Shared by moveCursor() and pageScroll().
139
+ */
140
+ private moveToVisualLine;
141
+ /**
142
+ * Compute the target visual column for vertical cursor movement.
143
+ * Implements the sticky column decision table:
144
+ *
145
+ * | P | S | T | U | Scenario | Set Preferred | Move To |
146
+ * |---|---|---|---| ---------------------------------------------------- |---------------|-------------|
147
+ * | 0 | * | 0 | - | Start nav, target fits | null | current |
148
+ * | 0 | * | 1 | - | Start nav, target shorter | current | target end |
149
+ * | 1 | 0 | 0 | 0 | Clamped, target fits preferred | null | preferred |
150
+ * | 1 | 0 | 0 | 1 | Clamped, target longer but still can't fit preferred | keep | target end |
151
+ * | 1 | 0 | 1 | - | Clamped, target even shorter | keep | target end |
152
+ * | 1 | 1 | 0 | - | Rewrapped, target fits current | null | current |
153
+ * | 1 | 1 | 1 | - | Rewrapped, target shorter than current | current | target end |
154
+ *
155
+ * Where:
156
+ * - P = preferred col is set
157
+ * - S = cursor in middle of source line (not clamped to end)
158
+ * - T = target line shorter than current visual col
159
+ * - U = target line shorter than preferred col
160
+ */
161
+ private computeVerticalMoveColumn;
162
+ private moveToLineStart;
163
+ private moveToLineEnd;
164
+ private deleteToStartOfLine;
165
+ private deleteToEndOfLine;
166
+ private deleteWordBackwards;
167
+ private deleteWordForward;
168
+ private handleForwardDelete;
169
+ /**
170
+ * Build a mapping from visual lines to logical positions.
171
+ * Returns an array where each element represents a visual line with:
172
+ * - logicalLine: index into this.state.lines
173
+ * - startCol: starting column in the logical line
174
+ * - length: length of this visual line segment
175
+ */
176
+ private buildVisualLineMap;
177
+ /**
178
+ * Find the visual line index that contains the given logical position.
179
+ */
180
+ private findVisualLineAt;
181
+ /**
182
+ * Find the visual line index for the current cursor position.
183
+ */
184
+ private findCurrentVisualLine;
185
+ private moveCursor;
186
+ /**
187
+ * Scroll by a page (direction: -1 for up, 1 for down).
188
+ * Moves cursor by the page size while keeping it in bounds.
189
+ */
190
+ private pageScroll;
191
+ private moveWordBackwards;
192
+ /**
193
+ * Yank (paste) the most recent kill ring entry at cursor position.
194
+ */
195
+ private yank;
196
+ /**
197
+ * Cycle through kill ring (only works immediately after yank or yank-pop).
198
+ * Replaces the last yanked text with the previous entry in the ring.
199
+ */
200
+ private yankPop;
201
+ /**
202
+ * Insert text at cursor position (used by yank operations).
203
+ */
204
+ private insertYankedText;
205
+ /**
206
+ * Delete the previously yanked text (used by yank-pop).
207
+ * The yanked text is derived from killRing[end] since it hasn't been rotated yet.
208
+ */
209
+ private deleteYankedText;
210
+ private pushUndoSnapshot;
211
+ private undo;
212
+ /**
213
+ * Jump to the first occurrence of a character in the specified direction.
214
+ * Multi-line search. Case-sensitive. Skips the current cursor position.
215
+ */
216
+ private jumpToChar;
217
+ private moveWordForwards;
218
+ private isSlashMenuAllowed;
219
+ private isAtStartOfMessage;
220
+ private isInSlashCommandContext;
221
+ /**
222
+ * Find the best autocomplete item index for the given prefix.
223
+ * Returns -1 if no match is found.
224
+ *
225
+ * Match priority:
226
+ * 1. Exact match (prefix === item.value) -> always selected
227
+ * 2. Prefix match -> first item whose value starts with prefix
228
+ * 3. No match -> -1 (keep default highlight)
229
+ *
230
+ * Matching is case-sensitive and checks item.value only.
231
+ */
232
+ private getBestAutocompleteMatchIndex;
233
+ private createAutocompleteList;
234
+ private tryTriggerAutocomplete;
235
+ private handleTabCompletion;
236
+ private handleSlashCommandCompletion;
237
+ private forceFileAutocomplete;
238
+ private requestAutocomplete;
239
+ private startAutocompleteRequest;
240
+ private getAutocompleteDebounceMs;
241
+ private runAutocompleteRequest;
242
+ private isAutocompleteRequestCurrent;
243
+ private applyAutocompleteSuggestions;
244
+ private cancelAutocompleteRequest;
245
+ private clearAutocompleteUi;
246
+ private cancelAutocomplete;
247
+ isShowingAutocomplete(): boolean;
248
+ private updateAutocomplete;
249
+ }
@@ -0,0 +1,17 @@
1
+ var q=Object.defineProperty;var S=(u,t)=>q(u,"name",{value:t,configurable:!0});import{getKeybindings as $}from"../keybindings.js";import{decodePrintableKey as v,matchesKey as k}from"../keys.js";import{KillRing as j}from"../kill-ring.js";import{CURSOR_MARKER as D}from"../tui.js";import{UndoStack as N}from"../undo-stack.js";import{getGraphemeSegmenter as _,getWordSegmenter as X,isWhitespaceChar as M,truncateToWidth as K,visibleWidth as x}from"../utils.js";import{findWordBackward as z,findWordForward as G}from"../word-navigation.js";import{SelectList as H}from"./select-list.js";const O=_(),Y=X(),Z=/\[paste #(\d+)( (\+\d+ lines|\d+ chars))?\]/g,J=/^\[paste #(\d+)( (\+\d+ lines|\d+ chars))?\]$/;function I(u){return u.length>=10&&J.test(u)}S(I,"isPasteMarker");function Q(u,t,e){if(e.size===0||!u.includes("[paste #"))return t.segment(u);const i=[];for(const n of u.matchAll(Z)){const h=Number.parseInt(n[1],10);e.has(h)&&i.push({start:n.index,end:n.index+n[0].length})}if(i.length===0)return t.segment(u);const s=t.segment(u),r=[];let o=0;for(const n of s){for(;o<i.length&&i[o].end<=n.index;)o++;const h=o<i.length?i[o]:null;if(h&&n.index>=h.start&&n.index<h.end){if(n.index===h.start){const l=u.slice(h.start,h.end);r.push({segment:l,index:h.start,input:u})}}else r.push(n)}return r}S(Q,"segmentWithMarkers");function P(u,t,e){if(!u||t<=0)return[{text:"",startIndex:0,endIndex:0}];if(x(u)<=t)return[{text:u,startIndex:0,endIndex:u.length}];const s=[],r=e??[...O.segment(u)];let o=0,n=0,h=-1,l=0;for(let d=0;d<r.length;d++){const C=r[d],f=C.segment,p=x(f),g=C.index,m=!I(f)&&M(f);if(o+p>t&&(h>=0&&o-l+p<=t?(s.push({text:u.slice(n,h),startIndex:n,endIndex:h}),n=h,o-=l):n<g&&(s.push({text:u.slice(n,g),startIndex:n,endIndex:g}),n=g,o=0),h=-1),p>t){const A=P(f,t);for(let a=0;a<A.length-1;a++){const L=A[a];s.push({text:L.text,startIndex:g+L.startIndex,endIndex:g+L.endIndex})}const c=A[A.length-1];n=g+c.startIndex,o=x(c.text),h=-1;continue}o+=p;const T=r[d+1];m&&T&&(I(T.segment)||!M(T.segment))&&(h=T.index,l=o)}return s.push({text:u.slice(n),startIndex:n,endIndex:u.length}),s}S(P,"wordWrapLine");const tt={minPrimaryColumnWidth:12,maxPrimaryColumnWidth:32},et=20;class ut{static{S(this,"Editor")}state={lines:[""],cursorLine:0,cursorCol:0};focused=!1;tui;theme;paddingX=0;lastWidth=80;scrollOffset=0;borderColor;title;autocompleteProvider;autocompleteList;autocompleteState=null;autocompletePrefix="";autocompleteMaxVisible=5;autocompleteAbort;autocompleteDebounceTimer;autocompleteRequestTask=Promise.resolve();autocompleteStartToken=0;autocompleteRequestId=0;pastes=new Map;pasteCounter=0;pasteBuffer="";isInPaste=!1;history=[];historyIndex=-1;killRing=new j;lastAction=null;jumpMode=null;preferredVisualCol=null;snappedFromCursorCol=null;undoStack=new N;onSubmit;onChange;disableSubmit=!1;constructor(t,e,i={}){this.tui=t,this.theme=e,this.borderColor=e.borderColor;const s=i.paddingX??0;this.paddingX=Number.isFinite(s)?Math.max(0,Math.floor(s)):0;const r=i.autocompleteMaxVisible??5;this.autocompleteMaxVisible=Number.isFinite(r)?Math.max(3,Math.min(20,Math.floor(r))):5}validPasteIds(){return new Set(this.pastes.keys())}segment(t,e){return Q(t,e==="word"?Y:O,this.validPasteIds())}getPaddingX(){return this.paddingX}setPaddingX(t){const e=Number.isFinite(t)?Math.max(0,Math.floor(t)):0;this.paddingX!==e&&(this.paddingX=e,this.tui.requestRender())}getAutocompleteMaxVisible(){return this.autocompleteMaxVisible}setAutocompleteMaxVisible(t){const e=Number.isFinite(t)?Math.max(3,Math.min(20,Math.floor(t))):5;this.autocompleteMaxVisible!==e&&(this.autocompleteMaxVisible=e,this.tui.requestRender())}setAutocompleteProvider(t){this.cancelAutocomplete(),this.autocompleteProvider=t}addToHistory(t){const e=t.trim();e&&(this.history.length>0&&this.history[0]===e||(this.history.unshift(e),this.history.length>100&&this.history.pop()))}isEditorEmpty(){return this.state.lines.length===1&&this.state.lines[0]===""}isOnFirstVisualLine(){const t=this.buildVisualLineMap(this.lastWidth);return this.findCurrentVisualLine(t)===0}isOnLastVisualLine(){const t=this.buildVisualLineMap(this.lastWidth);return this.findCurrentVisualLine(t)===t.length-1}navigateHistory(t){if(this.lastAction=null,this.history.length===0)return;const e=this.historyIndex-t;e<-1||e>=this.history.length||(this.historyIndex===-1&&e>=0&&this.pushUndoSnapshot(),this.historyIndex=e,this.historyIndex===-1?this.setTextInternal(""):this.setTextInternal(this.history[this.historyIndex]||""))}setTextInternal(t){const e=t.split(`
2
+ `);this.state.lines=e.length===0?[""]:e,this.state.cursorLine=this.state.lines.length-1,this.setCursorCol(this.state.lines[this.state.cursorLine]?.length||0),this.scrollOffset=0,this.onChange&&this.onChange(this.getText())}invalidate(){}render(t){const e=Math.max(0,Math.floor((t-1)/2)),i=Math.min(this.paddingX,e),s=Math.max(1,t-i*2),r=Math.max(1,s-(i?0:1));this.lastWidth=r;const o=this.borderColor("\u2500"),n=this.layoutText(r),h=this.tui.terminal.rows,l=Math.max(5,Math.floor(h*.3));let d=n.findIndex(c=>c.hasCursor);d===-1&&(d=0),d<this.scrollOffset?this.scrollOffset=d:d>=this.scrollOffset+l&&(this.scrollOffset=d-l+1);const C=Math.max(0,n.length-l);this.scrollOffset=Math.max(0,Math.min(this.scrollOffset,C));const f=n.slice(this.scrollOffset,this.scrollOffset+l),p=[],g=" ".repeat(i),m=g;if(this.scrollOffset>0){const c=`\u2500\u2500\u2500 \u2191 ${this.scrollOffset} more `,a=t-x(c);a>=0?p.push(this.borderColor(c+"\u2500".repeat(a))):p.push(this.borderColor(K(c,t)))}else if(this.title){const c=`${o} ${this.title} `,a=Math.max(0,t-x(c));p.push(c+this.borderColor("\u2500".repeat(a)))}else p.push(o.repeat(t));const T=this.focused&&!this.autocompleteState;for(const c of f){let a=c.text,L=x(c.text),b=!1;if(c.hasCursor&&c.cursorPos!==void 0){const w=a.slice(0,c.cursorPos),y=a.slice(c.cursorPos),R=T?D:"";if(y.length>0){const V=[...this.segment(y,"grapheme")][0]?.segment||"",F=y.slice(V.length),U=`\x1B[7m${V}\x1B[0m`;a=w+R+U+F}else a=w+R+"\x1B[7m \x1B[0m",L=L+1,L>s&&i>0&&(b=!0)}const W=" ".repeat(Math.max(0,s-L)),B=b?m.slice(1):m;p.push(`${g}${a}${W}${B}`)}const A=n.length-(this.scrollOffset+f.length);if(A>0){const c=`\u2500\u2500\u2500 \u2193 ${A} more `,a=t-x(c);p.push(this.borderColor(c+"\u2500".repeat(Math.max(0,a))))}else p.push(o.repeat(t));if(this.autocompleteState&&this.autocompleteList){const c=this.autocompleteList.render(s);for(const a of c){const L=x(a),b=" ".repeat(Math.max(0,s-L));p.push(`${g}${a}${b}${m}`)}}return p}handleInput(t){const e=$();if(this.jumpMode!==null){if(e.matches(t,"tui.editor.jumpForward")||e.matches(t,"tui.editor.jumpBackward")){this.jumpMode=null;return}const s=v(t)??(t.charCodeAt(0)>=32?t:void 0);if(s!==void 0){const r=this.jumpMode;this.jumpMode=null,this.jumpToChar(s,r);return}this.jumpMode=null}if(t.includes("\x1B[200~")&&(this.isInPaste=!0,this.pasteBuffer="",t=t.replace("\x1B[200~","")),this.isInPaste){this.pasteBuffer+=t;const s=this.pasteBuffer.indexOf("\x1B[201~");if(s!==-1){const r=this.pasteBuffer.substring(0,s);r.length>0&&this.handlePaste(r),this.isInPaste=!1;const o=this.pasteBuffer.substring(s+6);this.pasteBuffer="",o.length>0&&this.handleInput(o);return}return}if(e.matches(t,"tui.input.copy"))return;if(e.matches(t,"tui.editor.undo")){this.undo();return}if(this.autocompleteState&&this.autocompleteList){if(e.matches(t,"tui.select.cancel")){this.cancelAutocomplete();return}if(e.matches(t,"tui.select.up")||e.matches(t,"tui.select.down")){this.autocompleteList.handleInput(t);return}if(e.matches(t,"tui.input.tab")){const s=this.autocompleteList.getSelectedItem();if(s&&this.autocompleteProvider){this.pushUndoSnapshot(),this.lastAction=null;const r=this.autocompleteProvider.applyCompletion(this.state.lines,this.state.cursorLine,this.state.cursorCol,s,this.autocompletePrefix);this.state.lines=r.lines,this.state.cursorLine=r.cursorLine,this.setCursorCol(r.cursorCol),this.cancelAutocomplete(),this.onChange&&this.onChange(this.getText())}return}if(e.matches(t,"tui.select.confirm")){const s=this.autocompleteList.getSelectedItem();if(s&&this.autocompleteProvider){this.pushUndoSnapshot(),this.lastAction=null;const r=this.autocompleteProvider.applyCompletion(this.state.lines,this.state.cursorLine,this.state.cursorCol,s,this.autocompletePrefix);if(this.state.lines=r.lines,this.state.cursorLine=r.cursorLine,this.setCursorCol(r.cursorCol),this.autocompletePrefix.startsWith("/"))this.cancelAutocomplete();else{this.cancelAutocomplete(),this.onChange&&this.onChange(this.getText());return}}}}if(e.matches(t,"tui.input.tab")&&!this.autocompleteState){this.handleTabCompletion();return}if(e.matches(t,"tui.editor.deleteToLineEnd")){this.deleteToEndOfLine();return}if(e.matches(t,"tui.editor.deleteToLineStart")){this.deleteToStartOfLine();return}if(e.matches(t,"tui.editor.deleteWordBackward")){this.deleteWordBackwards();return}if(e.matches(t,"tui.editor.deleteWordForward")){this.deleteWordForward();return}if(e.matches(t,"tui.editor.deleteCharBackward")||k(t,"shift+backspace")){this.handleBackspace();return}if(e.matches(t,"tui.editor.deleteCharForward")||k(t,"shift+delete")){this.handleForwardDelete();return}if(e.matches(t,"tui.editor.yank")){this.yank();return}if(e.matches(t,"tui.editor.yankPop")){this.yankPop();return}if(e.matches(t,"tui.editor.cursorLineStart")){this.moveToLineStart();return}if(e.matches(t,"tui.editor.cursorLineEnd")){this.moveToLineEnd();return}if(e.matches(t,"tui.editor.cursorWordLeft")){this.moveWordBackwards();return}if(e.matches(t,"tui.editor.cursorWordRight")){this.moveWordForwards();return}if(e.matches(t,"tui.input.newLine")||t.charCodeAt(0)===10&&t.length>1||t==="\x1B\r"||t==="\x1B[13;2~"||t.length>1&&t.includes("\x1B")&&t.includes("\r")||t===`
3
+ `&&t.length===1){if(this.shouldSubmitOnBackslashEnter(t,e)){this.handleBackspace(),this.submitValue();return}this.addNewLine();return}if(e.matches(t,"tui.input.submit")){if(this.disableSubmit)return;const s=this.state.lines[this.state.cursorLine]||"";if(this.state.cursorCol>0&&s[this.state.cursorCol-1]==="\\"){this.handleBackspace(),this.addNewLine();return}this.submitValue();return}if(e.matches(t,"tui.editor.cursorUp")){this.isEditorEmpty()?this.navigateHistory(-1):this.historyIndex>-1&&this.isOnFirstVisualLine()?this.navigateHistory(-1):this.isOnFirstVisualLine()?this.moveToLineStart():this.moveCursor(-1,0);return}if(e.matches(t,"tui.editor.cursorDown")){this.historyIndex>-1&&this.isOnLastVisualLine()?this.navigateHistory(1):this.isOnLastVisualLine()?this.moveToLineEnd():this.moveCursor(1,0);return}if(e.matches(t,"tui.editor.cursorRight")){this.moveCursor(0,1);return}if(e.matches(t,"tui.editor.cursorLeft")){this.moveCursor(0,-1);return}if(e.matches(t,"tui.editor.pageUp")){this.pageScroll(-1);return}if(e.matches(t,"tui.editor.pageDown")){this.pageScroll(1);return}if(e.matches(t,"tui.editor.jumpForward")){this.jumpMode="forward";return}if(e.matches(t,"tui.editor.jumpBackward")){this.jumpMode="backward";return}if(k(t,"shift+space")){this.insertCharacter(" ");return}const i=v(t);if(i!==void 0){this.insertCharacter(i);return}t.charCodeAt(0)>=32&&this.insertCharacter(t)}layoutText(t){const e=[];if(this.state.lines.length===0||this.state.lines.length===1&&this.state.lines[0]==="")return e.push({text:"",hasCursor:!0,cursorPos:0}),e;for(let i=0;i<this.state.lines.length;i++){const s=this.state.lines[i]||"",r=i===this.state.cursorLine;if(x(s)<=t)r?e.push({text:s,hasCursor:!0,cursorPos:this.state.cursorCol}):e.push({text:s,hasCursor:!1});else{const n=P(s,t,[...this.segment(s,"grapheme")]);for(let h=0;h<n.length;h++){const l=n[h];if(!l)continue;const d=this.state.cursorCol,C=h===n.length-1;let f=!1,p=0;r&&(C?(f=d>=l.startIndex,p=d-l.startIndex):(f=d>=l.startIndex&&d<l.endIndex,f&&(p=d-l.startIndex,p>l.text.length&&(p=l.text.length)))),f?e.push({text:l.text,hasCursor:!0,cursorPos:p}):e.push({text:l.text,hasCursor:!1})}}}return e}getText(){return this.state.lines.join(`
4
+ `)}expandPasteMarkers(t){let e=t;for(const[i,s]of this.pastes){const r=new RegExp(`\\[paste #${i}( (\\+\\d+ lines|\\d+ chars))?\\]`,"g");e=e.replace(r,()=>s)}return e}getExpandedText(){return this.expandPasteMarkers(this.state.lines.join(`
5
+ `))}getLines(){return[...this.state.lines]}getCursor(){return{line:this.state.cursorLine,col:this.state.cursorCol}}setText(t){this.cancelAutocomplete(),this.lastAction=null,this.historyIndex=-1;const e=this.normalizeText(t);this.getText()!==e&&this.pushUndoSnapshot(),this.setTextInternal(e)}insertTextAtCursor(t){t&&(this.cancelAutocomplete(),this.pushUndoSnapshot(),this.lastAction=null,this.historyIndex=-1,this.insertTextAtCursorInternal(t))}normalizeText(t){return t.replace(/\r\n/g,`
6
+ `).replace(/\r/g,`
7
+ `).replace(/\t/g," ")}insertTextAtCursorInternal(t){if(!t)return;const e=this.normalizeText(t),i=e.split(`
8
+ `),s=this.state.lines[this.state.cursorLine]||"",r=s.slice(0,this.state.cursorCol),o=s.slice(this.state.cursorCol);i.length===1?(this.state.lines[this.state.cursorLine]=r+e+o,this.setCursorCol(this.state.cursorCol+e.length)):(this.state.lines=[...this.state.lines.slice(0,this.state.cursorLine),r+i[0],...i.slice(1,-1),i[i.length-1]+o,...this.state.lines.slice(this.state.cursorLine+1)],this.state.cursorLine+=i.length-1,this.setCursorCol((i[i.length-1]||"").length)),this.onChange&&this.onChange(this.getText())}insertCharacter(t,e){this.historyIndex=-1,e||((M(t)||this.lastAction!=="type-word")&&this.pushUndoSnapshot(),this.lastAction="type-word");const i=this.state.lines[this.state.cursorLine]||"",s=i.slice(0,this.state.cursorCol),r=i.slice(this.state.cursorCol);if(this.state.lines[this.state.cursorLine]=s+t+r,this.setCursorCol(this.state.cursorCol+t.length),this.onChange&&this.onChange(this.getText()),this.autocompleteState)this.updateAutocomplete();else if(t==="/"&&this.isAtStartOfMessage())this.tryTriggerAutocomplete();else if(t==="@"||t==="#"){const n=(this.state.lines[this.state.cursorLine]||"").slice(0,this.state.cursorCol),h=n[n.length-2];(n.length===1||h===" "||h===" ")&&this.tryTriggerAutocomplete()}else if(/[a-zA-Z0-9.\-_]/.test(t)){const n=(this.state.lines[this.state.cursorLine]||"").slice(0,this.state.cursorCol);this.isInSlashCommandContext(n)?this.tryTriggerAutocomplete():n.match(/(?:^|[\s])[@#][^\s]*$/)&&this.tryTriggerAutocomplete()}}handlePaste(t){this.cancelAutocomplete(),this.historyIndex=-1,this.lastAction=null,this.pushUndoSnapshot();const e=t.replace(/\x1b\[(\d+);5u/g,(n,h)=>{const l=Number(h);return l>=97&&l<=122?String.fromCharCode(l-96):l>=65&&l<=90?String.fromCharCode(l-64):n});let s=this.normalizeText(e).split("").filter(n=>n===`
9
+ `||n.charCodeAt(0)>=32).join("");if(/^[/~.]/.test(s)){const n=this.state.lines[this.state.cursorLine]||"",h=this.state.cursorCol>0?n[this.state.cursorCol-1]:"";h&&/\w/.test(h)&&(s=` ${s}`)}const r=s.split(`
10
+ `),o=s.length;if(r.length>10||o>1e3){this.pasteCounter++;const n=this.pasteCounter;this.pastes.set(n,s);const h=r.length>10?`[paste #${n} +${r.length} lines]`:`[paste #${n} ${o} chars]`;this.insertTextAtCursorInternal(h);return}if(r.length===1){this.insertTextAtCursorInternal(s);return}this.insertTextAtCursorInternal(s)}addNewLine(){this.cancelAutocomplete(),this.historyIndex=-1,this.lastAction=null,this.pushUndoSnapshot();const t=this.state.lines[this.state.cursorLine]||"",e=t.slice(0,this.state.cursorCol),i=t.slice(this.state.cursorCol);this.state.lines[this.state.cursorLine]=e,this.state.lines.splice(this.state.cursorLine+1,0,i),this.state.cursorLine++,this.setCursorCol(0),this.onChange&&this.onChange(this.getText())}shouldSubmitOnBackslashEnter(t,e){if(this.disableSubmit||!k(t,"enter"))return!1;const i=e.getKeys("tui.input.submit");if(!(i.includes("shift+enter")||i.includes("shift+return")))return!1;const r=this.state.lines[this.state.cursorLine]||"";return this.state.cursorCol>0&&r[this.state.cursorCol-1]==="\\"}submitValue(){this.cancelAutocomplete();const t=this.expandPasteMarkers(this.state.lines.join(`
11
+ `)).trim();this.state={lines:[""],cursorLine:0,cursorCol:0},this.pastes.clear(),this.pasteCounter=0,this.historyIndex=-1,this.scrollOffset=0,this.undoStack.clear(),this.lastAction=null,this.onChange&&this.onChange(""),this.onSubmit&&this.onSubmit(t)}handleBackspace(){if(this.historyIndex=-1,this.lastAction=null,this.state.cursorCol>0){this.pushUndoSnapshot();const t=this.state.lines[this.state.cursorLine]||"",e=t.slice(0,this.state.cursorCol),i=[...this.segment(e,"grapheme")],s=i[i.length-1],r=s?s.segment.length:1,o=t.slice(0,this.state.cursorCol-r),n=t.slice(this.state.cursorCol);this.state.lines[this.state.cursorLine]=o+n,this.setCursorCol(this.state.cursorCol-r)}else if(this.state.cursorLine>0){this.pushUndoSnapshot();const t=this.state.lines[this.state.cursorLine]||"",e=this.state.lines[this.state.cursorLine-1]||"";this.state.lines[this.state.cursorLine-1]=e+t,this.state.lines.splice(this.state.cursorLine,1),this.state.cursorLine--,this.setCursorCol(e.length)}if(this.onChange&&this.onChange(this.getText()),this.autocompleteState)this.updateAutocomplete();else{const e=(this.state.lines[this.state.cursorLine]||"").slice(0,this.state.cursorCol);this.isInSlashCommandContext(e)?this.tryTriggerAutocomplete():e.match(/(?:^|[\s])[@#][^\s]*$/)&&this.tryTriggerAutocomplete()}}setCursorCol(t){this.state.cursorCol=t,this.preferredVisualCol=null,this.snappedFromCursorCol=null}moveToVisualLine(t,e,i){const s=t[e],r=t[i];if(!(s&&r))return;let o;if(this.snappedFromCursorCol!==null){const m=this.findVisualLineAt(t,s.logicalLine,this.snappedFromCursorCol);o=this.snappedFromCursorCol-t[m].startCol}else o=this.state.cursorCol-s.startCol;const h=e===t.length-1||t[e+1]?.logicalLine!==s.logicalLine?s.length:Math.max(0,s.length-1),d=i===t.length-1||t[i+1]?.logicalLine!==r.logicalLine?r.length:Math.max(0,r.length-1),C=this.computeVerticalMoveColumn(o,h,d);this.state.cursorLine=r.logicalLine;const f=r.startCol+C,p=this.state.lines[r.logicalLine]||"";this.state.cursorCol=Math.min(f,p.length);const g=[...this.segment(p,"grapheme")];for(const m of g){if(m.index>this.state.cursorCol)break;if(!(m.segment.length<=1)&&this.state.cursorCol<m.index+m.segment.length){const T=m.index<r.startCol,A=i>e;if(T&&A){const c=m.index+m.segment.length;let a=i+1;for(;a<t.length&&t[a].logicalLine===r.logicalLine&&t[a].startCol<c;)a++;if(a<t.length){this.moveToVisualLine(t,e,a);return}}this.snappedFromCursorCol=this.state.cursorCol,this.state.cursorCol=m.index;return}}this.snappedFromCursorCol=null}computeVerticalMoveColumn(t,e,i){const s=this.preferredVisualCol!==null,r=t<e,o=i<t;if(!s||r)return o?(this.preferredVisualCol=t,i):(this.preferredVisualCol=null,t);const n=i<this.preferredVisualCol;if(o||n)return i;const h=this.preferredVisualCol;return this.preferredVisualCol=null,h}moveToLineStart(){this.lastAction=null,this.setCursorCol(0)}moveToLineEnd(){this.lastAction=null;const t=this.state.lines[this.state.cursorLine]||"";this.setCursorCol(t.length)}deleteToStartOfLine(){this.historyIndex=-1;const t=this.state.lines[this.state.cursorLine]||"";if(this.state.cursorCol>0){this.pushUndoSnapshot();const e=t.slice(0,this.state.cursorCol);this.killRing.push(e,{prepend:!0,accumulate:this.lastAction==="kill"}),this.lastAction="kill",this.state.lines[this.state.cursorLine]=t.slice(this.state.cursorCol),this.setCursorCol(0)}else if(this.state.cursorLine>0){this.pushUndoSnapshot(),this.killRing.push(`
12
+ `,{prepend:!0,accumulate:this.lastAction==="kill"}),this.lastAction="kill";const e=this.state.lines[this.state.cursorLine-1]||"";this.state.lines[this.state.cursorLine-1]=e+t,this.state.lines.splice(this.state.cursorLine,1),this.state.cursorLine--,this.setCursorCol(e.length)}this.onChange&&this.onChange(this.getText())}deleteToEndOfLine(){this.historyIndex=-1;const t=this.state.lines[this.state.cursorLine]||"";if(this.state.cursorCol<t.length){this.pushUndoSnapshot();const e=t.slice(this.state.cursorCol);this.killRing.push(e,{prepend:!1,accumulate:this.lastAction==="kill"}),this.lastAction="kill",this.state.lines[this.state.cursorLine]=t.slice(0,this.state.cursorCol)}else if(this.state.cursorLine<this.state.lines.length-1){this.pushUndoSnapshot(),this.killRing.push(`
13
+ `,{prepend:!1,accumulate:this.lastAction==="kill"}),this.lastAction="kill";const e=this.state.lines[this.state.cursorLine+1]||"";this.state.lines[this.state.cursorLine]=t+e,this.state.lines.splice(this.state.cursorLine+1,1)}this.onChange&&this.onChange(this.getText())}deleteWordBackwards(){this.historyIndex=-1;const t=this.state.lines[this.state.cursorLine]||"";if(this.state.cursorCol===0){if(this.state.cursorLine>0){this.pushUndoSnapshot(),this.killRing.push(`
14
+ `,{prepend:!0,accumulate:this.lastAction==="kill"}),this.lastAction="kill";const e=this.state.lines[this.state.cursorLine-1]||"";this.state.lines[this.state.cursorLine-1]=e+t,this.state.lines.splice(this.state.cursorLine,1),this.state.cursorLine--,this.setCursorCol(e.length)}}else{this.pushUndoSnapshot();const e=this.lastAction==="kill",i=this.state.cursorCol;this.moveWordBackwards();const s=this.state.cursorCol;this.setCursorCol(i);const r=t.slice(s,this.state.cursorCol);this.killRing.push(r,{prepend:!0,accumulate:e}),this.lastAction="kill",this.state.lines[this.state.cursorLine]=t.slice(0,s)+t.slice(this.state.cursorCol),this.setCursorCol(s)}this.onChange&&this.onChange(this.getText())}deleteWordForward(){this.historyIndex=-1;const t=this.state.lines[this.state.cursorLine]||"";if(this.state.cursorCol>=t.length){if(this.state.cursorLine<this.state.lines.length-1){this.pushUndoSnapshot(),this.killRing.push(`
15
+ `,{prepend:!1,accumulate:this.lastAction==="kill"}),this.lastAction="kill";const e=this.state.lines[this.state.cursorLine+1]||"";this.state.lines[this.state.cursorLine]=t+e,this.state.lines.splice(this.state.cursorLine+1,1)}}else{this.pushUndoSnapshot();const e=this.lastAction==="kill",i=this.state.cursorCol;this.moveWordForwards();const s=this.state.cursorCol;this.setCursorCol(i);const r=t.slice(this.state.cursorCol,s);this.killRing.push(r,{prepend:!1,accumulate:e}),this.lastAction="kill",this.state.lines[this.state.cursorLine]=t.slice(0,this.state.cursorCol)+t.slice(s)}this.onChange&&this.onChange(this.getText())}handleForwardDelete(){this.historyIndex=-1,this.lastAction=null;const t=this.state.lines[this.state.cursorLine]||"";if(this.state.cursorCol<t.length){this.pushUndoSnapshot();const e=t.slice(this.state.cursorCol),s=[...this.segment(e,"grapheme")][0],r=s?s.segment.length:1,o=t.slice(0,this.state.cursorCol),n=t.slice(this.state.cursorCol+r);this.state.lines[this.state.cursorLine]=o+n}else if(this.state.cursorLine<this.state.lines.length-1){this.pushUndoSnapshot();const e=this.state.lines[this.state.cursorLine+1]||"";this.state.lines[this.state.cursorLine]=t+e,this.state.lines.splice(this.state.cursorLine+1,1)}if(this.onChange&&this.onChange(this.getText()),this.autocompleteState)this.updateAutocomplete();else{const i=(this.state.lines[this.state.cursorLine]||"").slice(0,this.state.cursorCol);this.isInSlashCommandContext(i)?this.tryTriggerAutocomplete():i.match(/(?:^|[\s])[@#][^\s]*$/)&&this.tryTriggerAutocomplete()}}buildVisualLineMap(t){const e=[];for(let i=0;i<this.state.lines.length;i++){const s=this.state.lines[i]||"",r=x(s);if(s.length===0)e.push({logicalLine:i,startCol:0,length:0});else if(r<=t)e.push({logicalLine:i,startCol:0,length:s.length});else{const o=P(s,t,[...this.segment(s,"grapheme")]);for(const n of o)e.push({logicalLine:i,startCol:n.startIndex,length:n.endIndex-n.startIndex})}}return e}findVisualLineAt(t,e,i){for(let s=0;s<t.length;s++){const r=t[s];if(!r||r.logicalLine!==e)continue;const o=i-r.startCol,n=s===t.length-1||t[s+1]?.logicalLine!==r.logicalLine;if(o>=0&&(o<r.length||n&&o===r.length))return s}return t.length-1}findCurrentVisualLine(t){return this.findVisualLineAt(t,this.state.cursorLine,this.state.cursorCol)}moveCursor(t,e){this.lastAction=null;const i=this.buildVisualLineMap(this.lastWidth),s=this.findCurrentVisualLine(i);if(t!==0){const r=s+t;r>=0&&r<i.length&&this.moveToVisualLine(i,s,r)}if(e!==0){const r=this.state.lines[this.state.cursorLine]||"";if(e>0)if(this.state.cursorCol<r.length){const o=r.slice(this.state.cursorCol),h=[...this.segment(o,"grapheme")][0];this.setCursorCol(this.state.cursorCol+(h?h.segment.length:1))}else if(this.state.cursorLine<this.state.lines.length-1)this.state.cursorLine++,this.setCursorCol(0);else{const o=i[s];o&&(this.preferredVisualCol=this.state.cursorCol-o.startCol)}else if(this.state.cursorCol>0){const o=r.slice(0,this.state.cursorCol),n=[...this.segment(o,"grapheme")],h=n[n.length-1];this.setCursorCol(this.state.cursorCol-(h?h.segment.length:1))}else if(this.state.cursorLine>0){this.state.cursorLine--;const o=this.state.lines[this.state.cursorLine]||"";this.setCursorCol(o.length)}}}pageScroll(t){this.lastAction=null;const e=this.tui.terminal.rows,i=Math.max(5,Math.floor(e*.3)),s=this.buildVisualLineMap(this.lastWidth),r=this.findCurrentVisualLine(s),o=Math.max(0,Math.min(s.length-1,r+t*i));this.moveToVisualLine(s,r,o)}moveWordBackwards(){this.lastAction=null;const t=this.state.lines[this.state.cursorLine]||"";if(this.state.cursorCol===0){if(this.state.cursorLine>0){this.state.cursorLine--;const e=this.state.lines[this.state.cursorLine]||"";this.setCursorCol(e.length)}return}this.setCursorCol(z(t,this.state.cursorCol,{segment:S(e=>this.segment(e,"word"),"segment"),isAtomicSegment:I}))}yank(){if(this.killRing.length===0)return;this.pushUndoSnapshot();const t=this.killRing.peek();this.insertYankedText(t),this.lastAction="yank"}yankPop(){if(this.lastAction!=="yank"||this.killRing.length<=1)return;this.pushUndoSnapshot(),this.deleteYankedText(),this.killRing.rotate();const t=this.killRing.peek();this.insertYankedText(t),this.lastAction="yank"}insertYankedText(t){this.historyIndex=-1;const e=t.split(`
16
+ `);if(e.length===1){const i=this.state.lines[this.state.cursorLine]||"",s=i.slice(0,this.state.cursorCol),r=i.slice(this.state.cursorCol);this.state.lines[this.state.cursorLine]=s+t+r,this.setCursorCol(this.state.cursorCol+t.length)}else{const i=this.state.lines[this.state.cursorLine]||"",s=i.slice(0,this.state.cursorCol),r=i.slice(this.state.cursorCol);this.state.lines[this.state.cursorLine]=s+(e[0]||"");for(let n=1;n<e.length-1;n++)this.state.lines.splice(this.state.cursorLine+n,0,e[n]||"");const o=this.state.cursorLine+e.length-1;this.state.lines.splice(o,0,(e[e.length-1]||"")+r),this.state.cursorLine=o,this.setCursorCol((e[e.length-1]||"").length)}this.onChange&&this.onChange(this.getText())}deleteYankedText(){const t=this.killRing.peek();if(!t)return;const e=t.split(`
17
+ `);if(e.length===1){const i=this.state.lines[this.state.cursorLine]||"",s=t.length,r=i.slice(0,this.state.cursorCol-s),o=i.slice(this.state.cursorCol);this.state.lines[this.state.cursorLine]=r+o,this.setCursorCol(this.state.cursorCol-s)}else{const i=this.state.cursorLine-(e.length-1),s=(this.state.lines[i]||"").length-(e[0]||"").length,r=(this.state.lines[this.state.cursorLine]||"").slice(this.state.cursorCol),o=(this.state.lines[i]||"").slice(0,s);this.state.lines.splice(i,e.length,o+r),this.state.cursorLine=i,this.setCursorCol(s)}this.onChange&&this.onChange(this.getText())}pushUndoSnapshot(){this.undoStack.push(this.state)}undo(){this.historyIndex=-1;const t=this.undoStack.pop();t&&(Object.assign(this.state,t),this.lastAction=null,this.preferredVisualCol=null,this.onChange&&this.onChange(this.getText()))}jumpToChar(t,e){this.lastAction=null;const i=e==="forward",s=this.state.lines,r=i?s.length:-1,o=i?1:-1;for(let n=this.state.cursorLine;n!==r;n+=o){const h=s[n]||"",d=n===this.state.cursorLine?i?this.state.cursorCol+1:this.state.cursorCol-1:void 0,C=i?h.indexOf(t,d):h.lastIndexOf(t,d);if(C!==-1){this.state.cursorLine=n,this.setCursorCol(C);return}}}moveWordForwards(){this.lastAction=null;const t=this.state.lines[this.state.cursorLine]||"";if(this.state.cursorCol>=t.length){this.state.cursorLine<this.state.lines.length-1&&(this.state.cursorLine++,this.setCursorCol(0));return}this.setCursorCol(G(t,this.state.cursorCol,{segment:S(e=>this.segment(e,"word"),"segment"),isAtomicSegment:I}))}isSlashMenuAllowed(){return this.state.cursorLine===0}isAtStartOfMessage(){if(!this.isSlashMenuAllowed())return!1;const e=(this.state.lines[this.state.cursorLine]||"").slice(0,this.state.cursorCol);return e.trim()===""||e.trim()==="/"}isInSlashCommandContext(t){return this.isSlashMenuAllowed()&&t.trimStart().startsWith("/")}getBestAutocompleteMatchIndex(t,e){if(!e)return-1;let i=-1;for(let s=0;s<t.length;s++){const r=t[s].value;if(r===e)return s;i===-1&&r.startsWith(e)&&(i=s)}return i}createAutocompleteList(t,e){const i=t.startsWith("/")?tt:void 0;return new H(e,this.autocompleteMaxVisible,this.theme.selectList,i)}tryTriggerAutocomplete(t=!1){this.requestAutocomplete({force:!1,explicitTab:t})}handleTabCompletion(){if(!this.autocompleteProvider)return;const e=(this.state.lines[this.state.cursorLine]||"").slice(0,this.state.cursorCol);this.isInSlashCommandContext(e)&&!e.trimStart().includes(" ")?this.handleSlashCommandCompletion():this.forceFileAutocomplete(!0)}handleSlashCommandCompletion(){this.requestAutocomplete({force:!1,explicitTab:!0})}forceFileAutocomplete(t=!1){this.requestAutocomplete({force:!0,explicitTab:t})}requestAutocomplete(t){if(!this.autocompleteProvider||t.force&&!(!this.autocompleteProvider.shouldTriggerFileCompletion||this.autocompleteProvider.shouldTriggerFileCompletion(this.state.lines,this.state.cursorLine,this.state.cursorCol)))return;this.cancelAutocompleteRequest();const e=++this.autocompleteStartToken,i=this.getAutocompleteDebounceMs(t);if(i>0){this.autocompleteDebounceTimer=setTimeout(()=>{this.autocompleteDebounceTimer=void 0,this.startAutocompleteRequest(e,t)},i);return}this.startAutocompleteRequest(e,t)}async startAutocompleteRequest(t,e){const i=this.autocompleteRequestTask;this.autocompleteRequestTask=(async()=>{if(await i,t!==this.autocompleteStartToken||!this.autocompleteProvider)return;const s=new AbortController;this.autocompleteAbort=s;const r=++this.autocompleteRequestId,o=this.getText(),n=this.state.cursorLine,h=this.state.cursorCol;await this.runAutocompleteRequest(r,s,o,n,h,e)})(),await this.autocompleteRequestTask}getAutocompleteDebounceMs(t){if(t.explicitTab||t.force)return 0;const i=(this.state.lines[this.state.cursorLine]||"").slice(0,this.state.cursorCol);return/(?:^|[ \t])(?:@(?:"[^"]*|[^\s]*)|#[^\s]*)$/.test(i)?et:0}async runAutocompleteRequest(t,e,i,s,r,o){if(!this.autocompleteProvider)return;const n=await this.autocompleteProvider.getSuggestions(this.state.lines,this.state.cursorLine,this.state.cursorCol,{signal:e.signal,force:o.force});if(this.isAutocompleteRequestCurrent(t,e,i,s,r)){if(this.autocompleteAbort=void 0,!n||!Array.isArray(n.items)||n.items.length===0){this.cancelAutocomplete(),this.tui.requestRender();return}if(o.force&&o.explicitTab&&n.items.length===1){const h=n.items[0];this.pushUndoSnapshot(),this.lastAction=null;const l=this.autocompleteProvider.applyCompletion(this.state.lines,this.state.cursorLine,this.state.cursorCol,h,n.prefix);this.state.lines=l.lines,this.state.cursorLine=l.cursorLine,this.setCursorCol(l.cursorCol),this.onChange&&this.onChange(this.getText()),this.tui.requestRender();return}this.applyAutocompleteSuggestions(n,o.force?"force":"regular"),this.tui.requestRender()}}isAutocompleteRequestCurrent(t,e,i,s,r){return!e.signal.aborted&&t===this.autocompleteRequestId&&this.getText()===i&&this.state.cursorLine===s&&this.state.cursorCol===r}applyAutocompleteSuggestions(t,e){this.autocompletePrefix=t.prefix,this.autocompleteList=this.createAutocompleteList(t.prefix,t.items);const i=this.getBestAutocompleteMatchIndex(t.items,t.prefix);i>=0&&this.autocompleteList.setSelectedIndex(i),this.autocompleteState=e}cancelAutocompleteRequest(){this.autocompleteStartToken+=1,this.autocompleteDebounceTimer&&(clearTimeout(this.autocompleteDebounceTimer),this.autocompleteDebounceTimer=void 0),this.autocompleteAbort?.abort(),this.autocompleteAbort=void 0}clearAutocompleteUi(){this.autocompleteState=null,this.autocompleteList=void 0,this.autocompletePrefix=""}cancelAutocomplete(){this.cancelAutocompleteRequest(),this.clearAutocompleteUi()}isShowingAutocomplete(){return this.autocompleteState!==null}updateAutocomplete(){!this.autocompleteState||!this.autocompleteProvider||this.requestAutocomplete({force:this.autocompleteState==="force",explicitTab:!1})}}export{ut as Editor,P as wordWrapLine};
@@ -0,0 +1,27 @@
1
+ import { type ImageDimensions } from "../terminal-image.ts";
2
+ import type { Component } from "../tui.ts";
3
+ export interface ImageTheme {
4
+ fallbackColor: (str: string) => string;
5
+ }
6
+ export interface ImageOptions {
7
+ maxWidthCells?: number;
8
+ maxHeightCells?: number;
9
+ filename?: string;
10
+ /** Kitty image ID. If provided, reuses this ID (for animations/updates). */
11
+ imageId?: number;
12
+ }
13
+ export declare class Image implements Component {
14
+ private base64Data;
15
+ private mimeType;
16
+ private dimensions;
17
+ private theme;
18
+ private options;
19
+ private imageId?;
20
+ private cachedLines?;
21
+ private cachedWidth?;
22
+ constructor(base64Data: string, mimeType: string, theme: ImageTheme, options?: ImageOptions, dimensions?: ImageDimensions);
23
+ /** Get the Kitty image ID used by this image (if any). */
24
+ getImageId(): number | undefined;
25
+ invalidate(): void;
26
+ render(width: number): string[];
27
+ }
@@ -0,0 +1 @@
1
+ var f=Object.defineProperty;var l=(d,s)=>f(d,"name",{value:s,configurable:!0});import{allocateImageId as I,getCapabilities as p,getCellDimensions as u,getImageDimensions as x,imageFallback as g,renderImage as b}from"../terminal-image.js";class W{static{l(this,"Image")}base64Data;mimeType;dimensions;theme;options;imageId;cachedLines;cachedWidth;constructor(s,a,h,n={},m){this.base64Data=s,this.mimeType=a,this.theme=h,this.options=n,this.dimensions=m||x(s,a)||{widthPx:800,heightPx:600},this.imageId=n.imageId}getImageId(){return this.imageId}invalidate(){this.cachedLines=void 0,this.cachedWidth=void 0}render(s){if(this.cachedLines&&this.cachedWidth===s)return this.cachedLines;const a=Math.max(1,Math.min(s-2,this.options.maxWidthCells??60)),h=u(),n=Math.max(1,Math.ceil(a*h.widthPx/h.heightPx)),m=this.options.maxHeightCells??n,o=p();let i;if(o.images){o.images==="kitty"&&this.imageId===void 0&&(this.imageId=I());const e=b(this.base64Data,this.dimensions,{maxWidthCells:a,maxHeightCells:m,imageId:this.imageId,moveCursor:!1});if(e)if(e.imageId&&(this.imageId=e.imageId),o.images==="kitty"){i=[e.sequence];for(let t=0;t<e.rows-1;t++)i.push("")}else{i=[];for(let c=0;c<e.rows-1;c++)i.push("");const t=e.rows-1,r=t>0?`\x1B[${t}A`:"";i.push(r+e.sequence)}else{const t=g(this.mimeType,this.dimensions,this.options.filename);i=[this.theme.fallbackColor(t)]}}else{const e=g(this.mimeType,this.dimensions,this.options.filename);i=[this.theme.fallbackColor(e)]}return this.cachedLines=i,this.cachedWidth=s,i}}export{W as Image};
@@ -0,0 +1,36 @@
1
+ import { type Component, type Focusable } from "../tui.ts";
2
+ /**
3
+ * Input component - single-line text input with horizontal scrolling
4
+ */
5
+ export declare class Input implements Component, Focusable {
6
+ private value;
7
+ private cursor;
8
+ onSubmit?: (value: string) => void;
9
+ onEscape?: () => void;
10
+ /** Focusable interface - set by TUI when focus changes */
11
+ focused: boolean;
12
+ private pasteBuffer;
13
+ private isInPaste;
14
+ private killRing;
15
+ private lastAction;
16
+ private undoStack;
17
+ getValue(): string;
18
+ setValue(value: string): void;
19
+ handleInput(data: string): void;
20
+ private insertCharacter;
21
+ private handleBackspace;
22
+ private handleForwardDelete;
23
+ private deleteToLineStart;
24
+ private deleteToLineEnd;
25
+ private deleteWordBackwards;
26
+ private deleteWordForward;
27
+ private yank;
28
+ private yankPop;
29
+ private pushUndo;
30
+ private undo;
31
+ private moveWordBackwards;
32
+ private moveWordForwards;
33
+ private handlePaste;
34
+ invalidate(): void;
35
+ render(width: number): string[];
36
+ }
@@ -0,0 +1,2 @@
1
+ var B=Object.defineProperty;var g=(p,t)=>B(p,"name",{value:t,configurable:!0});import{getKeybindings as R}from"../keybindings.js";import{decodeKittyPrintable as y}from"../keys.js";import{KillRing as T}from"../kill-ring.js";import{CURSOR_MARKER as L}from"../tui.js";import{UndoStack as S}from"../undo-stack.js";import{getGraphemeSegmenter as U,isWhitespaceChar as F,sliceByColumn as v,visibleWidth as d}from"../utils.js";import{findWordBackward as P,findWordForward as E}from"../word-navigation.js";const n=U();class q{static{g(this,"Input")}value="";cursor=0;onSubmit;onEscape;focused=!1;pasteBuffer="";isInPaste=!1;killRing=new T;lastAction=null;undoStack=new S;getValue(){return this.value}setValue(t){this.value=t,this.cursor=Math.min(this.cursor,t.length)}handleInput(t){if(t.includes("\x1B[200~")&&(this.isInPaste=!0,this.pasteBuffer="",t=t.replace("\x1B[200~","")),this.isInPaste){this.pasteBuffer+=t;const r=this.pasteBuffer.indexOf("\x1B[201~");if(r!==-1){const h=this.pasteBuffer.substring(0,r);this.handlePaste(h),this.isInPaste=!1;const o=this.pasteBuffer.substring(r+6);this.pasteBuffer="",o&&this.handleInput(o)}return}const s=R();if(s.matches(t,"tui.select.cancel")){this.onEscape&&this.onEscape();return}if(s.matches(t,"tui.editor.undo")){this.undo();return}if(s.matches(t,"tui.input.submit")||t===`
2
+ `){this.onSubmit&&this.onSubmit(this.value);return}if(s.matches(t,"tui.editor.deleteCharBackward")){this.handleBackspace();return}if(s.matches(t,"tui.editor.deleteCharForward")){this.handleForwardDelete();return}if(s.matches(t,"tui.editor.deleteWordBackward")){this.deleteWordBackwards();return}if(s.matches(t,"tui.editor.deleteWordForward")){this.deleteWordForward();return}if(s.matches(t,"tui.editor.deleteToLineStart")){this.deleteToLineStart();return}if(s.matches(t,"tui.editor.deleteToLineEnd")){this.deleteToLineEnd();return}if(s.matches(t,"tui.editor.yank")){this.yank();return}if(s.matches(t,"tui.editor.yankPop")){this.yankPop();return}if(s.matches(t,"tui.editor.cursorLeft")){if(this.lastAction=null,this.cursor>0){const r=this.value.slice(0,this.cursor),h=[...n.segment(r)],o=h[h.length-1];this.cursor-=o?o.segment.length:1}return}if(s.matches(t,"tui.editor.cursorRight")){if(this.lastAction=null,this.cursor<this.value.length){const r=this.value.slice(this.cursor),o=[...n.segment(r)][0];this.cursor+=o?o.segment.length:1}return}if(s.matches(t,"tui.editor.cursorLineStart")){this.lastAction=null,this.cursor=0;return}if(s.matches(t,"tui.editor.cursorLineEnd")){this.lastAction=null,this.cursor=this.value.length;return}if(s.matches(t,"tui.editor.cursorWordLeft")){this.moveWordBackwards();return}if(s.matches(t,"tui.editor.cursorWordRight")){this.moveWordForwards();return}const e=y(t);if(e!==void 0){this.insertCharacter(e);return}[...t].some(r=>{const h=r.charCodeAt(0);return h<32||h===127||h>=128&&h<=159})||this.insertCharacter(t)}insertCharacter(t){(F(t)||this.lastAction!=="type-word")&&this.pushUndo(),this.lastAction="type-word",this.value=this.value.slice(0,this.cursor)+t+this.value.slice(this.cursor),this.cursor+=t.length}handleBackspace(){if(this.lastAction=null,this.cursor>0){this.pushUndo();const t=this.value.slice(0,this.cursor),s=[...n.segment(t)],e=s[s.length-1],i=e?e.segment.length:1;this.value=this.value.slice(0,this.cursor-i)+this.value.slice(this.cursor),this.cursor-=i}}handleForwardDelete(){if(this.lastAction=null,this.cursor<this.value.length){this.pushUndo();const t=this.value.slice(this.cursor),e=[...n.segment(t)][0],i=e?e.segment.length:1;this.value=this.value.slice(0,this.cursor)+this.value.slice(this.cursor+i)}}deleteToLineStart(){if(this.cursor===0)return;this.pushUndo();const t=this.value.slice(0,this.cursor);this.killRing.push(t,{prepend:!0,accumulate:this.lastAction==="kill"}),this.lastAction="kill",this.value=this.value.slice(this.cursor),this.cursor=0}deleteToLineEnd(){if(this.cursor>=this.value.length)return;this.pushUndo();const t=this.value.slice(this.cursor);this.killRing.push(t,{prepend:!1,accumulate:this.lastAction==="kill"}),this.lastAction="kill",this.value=this.value.slice(0,this.cursor)}deleteWordBackwards(){if(this.cursor===0)return;const t=this.lastAction==="kill";this.pushUndo();const s=this.cursor;this.moveWordBackwards();const e=this.cursor;this.cursor=s;const i=this.value.slice(e,this.cursor);this.killRing.push(i,{prepend:!0,accumulate:t}),this.lastAction="kill",this.value=this.value.slice(0,e)+this.value.slice(this.cursor),this.cursor=e}deleteWordForward(){if(this.cursor>=this.value.length)return;const t=this.lastAction==="kill";this.pushUndo();const s=this.cursor;this.moveWordForwards();const e=this.cursor;this.cursor=s;const i=this.value.slice(this.cursor,e);this.killRing.push(i,{prepend:!1,accumulate:t}),this.lastAction="kill",this.value=this.value.slice(0,this.cursor)+this.value.slice(e)}yank(){const t=this.killRing.peek();t&&(this.pushUndo(),this.value=this.value.slice(0,this.cursor)+t+this.value.slice(this.cursor),this.cursor+=t.length,this.lastAction="yank")}yankPop(){if(this.lastAction!=="yank"||this.killRing.length<=1)return;this.pushUndo();const t=this.killRing.peek()||"";this.value=this.value.slice(0,this.cursor-t.length)+this.value.slice(this.cursor),this.cursor-=t.length,this.killRing.rotate();const s=this.killRing.peek()||"";this.value=this.value.slice(0,this.cursor)+s+this.value.slice(this.cursor),this.cursor+=s.length,this.lastAction="yank"}pushUndo(){this.undoStack.push({value:this.value,cursor:this.cursor})}undo(){const t=this.undoStack.pop();t&&(this.value=t.value,this.cursor=t.cursor,this.lastAction=null)}moveWordBackwards(){this.cursor!==0&&(this.lastAction=null,this.cursor=P(this.value,this.cursor))}moveWordForwards(){this.cursor>=this.value.length||(this.lastAction=null,this.cursor=E(this.value,this.cursor))}handlePaste(t){this.lastAction=null,this.pushUndo();const s=t.replace(/\r\n/g,"").replace(/\r/g,"").replace(/\n/g,"").replace(/\t/g," ");this.value=this.value.slice(0,this.cursor)+s+this.value.slice(this.cursor),this.cursor+=s.length}invalidate(){}render(t){const e=t-2;if(e<=0)return["> "];let i="",r=this.cursor;const h=d(this.value);if(h<e)i=this.value;else{const u=this.cursor===this.value.length?e-1:e,c=d(this.value.slice(0,this.cursor));if(u>0){const a=Math.floor(u/2);let l=0;c<a?l=0:c>h-a?l=Math.max(0,h-u):l=Math.max(0,c-a),i=v(this.value,l,u,!0),r=v(this.value,l,Math.max(0,c-l),!0).length}else i="",r=0}const k=[...n.segment(i.slice(r))][0],C=i.slice(0,r),f=k?.segment??" ",A=i.slice(r+f.length),x=this.focused?L:"",w=`\x1B[7m${f}\x1B[27m`,m=C+x+w+A,b=d(m),W=" ".repeat(Math.max(0,e-b));return["> "+m+W]}}export{q as Input};
@@ -0,0 +1,30 @@
1
+ import type { TUI } from "../tui.ts";
2
+ import { Text } from "./text.ts";
3
+ export interface LoaderIndicatorOptions {
4
+ /** Animation frames. Use an empty array to hide the indicator. */
5
+ frames?: string[];
6
+ /** Frame interval in milliseconds for animated indicators. */
7
+ intervalMs?: number;
8
+ }
9
+ /**
10
+ * Loader component that updates with an optional spinning animation.
11
+ */
12
+ export declare class Loader extends Text {
13
+ private frames;
14
+ private intervalMs;
15
+ private currentFrame;
16
+ private intervalId;
17
+ private ui;
18
+ private renderIndicatorVerbatim;
19
+ private spinnerColorFn;
20
+ private messageColorFn;
21
+ private message;
22
+ constructor(ui: TUI, spinnerColorFn: (str: string) => string, messageColorFn: (str: string) => string, message?: string, indicator?: LoaderIndicatorOptions);
23
+ render(width: number): string[];
24
+ start(): void;
25
+ stop(): void;
26
+ setMessage(message: string): void;
27
+ setIndicator(indicator?: LoaderIndicatorOptions): void;
28
+ private restartAnimation;
29
+ private updateDisplay;
30
+ }
@@ -0,0 +1 @@
1
+ var o=Object.defineProperty;var i=(r,e)=>o(r,"name",{value:e,configurable:!0});import{Text as m}from"./text.js";const n=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],a=80;class p extends m{static{i(this,"Loader")}frames=[...n];intervalMs=a;currentFrame=0;intervalId=null;ui=null;renderIndicatorVerbatim=!1;spinnerColorFn;messageColorFn;message="Loading...";constructor(e,t,s,h="Loading...",l){super("",1,0),this.ui=e,this.spinnerColorFn=t,this.messageColorFn=s,this.message=h,this.setIndicator(l)}render(e){return["",...super.render(e)]}start(){this.updateDisplay(),this.restartAnimation()}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null)}setMessage(e){this.message=e,this.updateDisplay()}setIndicator(e){this.renderIndicatorVerbatim=e!==void 0,this.frames=e?.frames!==void 0?[...e.frames]:[...n],this.intervalMs=e?.intervalMs&&e.intervalMs>0?e.intervalMs:a,this.currentFrame=0,this.start()}restartAnimation(){this.stop(),!(this.frames.length<=1)&&(this.intervalId=setInterval(()=>{this.currentFrame=(this.currentFrame+1)%this.frames.length,this.updateDisplay()},this.intervalMs))}updateDisplay(){const e=this.frames[this.currentFrame]??"",t=this.renderIndicatorVerbatim?e:this.spinnerColorFn(e),s=e.length>0?`${t} `:"";this.setText(`${s}${this.messageColorFn(this.message)}`),this.ui&&this.ui.requestRender()}}export{p as Loader};