@gajae-code/tui 0.1.1

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 (59) hide show
  1. package/CHANGELOG.md +818 -0
  2. package/README.md +704 -0
  3. package/dist/types/autocomplete.d.ts +76 -0
  4. package/dist/types/bracketed-paste.d.ts +26 -0
  5. package/dist/types/components/box.d.ts +15 -0
  6. package/dist/types/components/cancellable-loader.d.ts +21 -0
  7. package/dist/types/components/editor.d.ts +101 -0
  8. package/dist/types/components/image.d.ts +16 -0
  9. package/dist/types/components/input.d.ts +16 -0
  10. package/dist/types/components/loader.d.ts +13 -0
  11. package/dist/types/components/markdown.d.ts +61 -0
  12. package/dist/types/components/select-list.d.ts +46 -0
  13. package/dist/types/components/settings-list.d.ts +39 -0
  14. package/dist/types/components/spacer.d.ts +11 -0
  15. package/dist/types/components/tab-bar.d.ts +56 -0
  16. package/dist/types/components/text.d.ts +13 -0
  17. package/dist/types/components/truncated-text.d.ts +10 -0
  18. package/dist/types/editor-component.d.ts +36 -0
  19. package/dist/types/fuzzy.d.ts +15 -0
  20. package/dist/types/index.d.ts +25 -0
  21. package/dist/types/keybindings.d.ts +189 -0
  22. package/dist/types/keys.d.ts +208 -0
  23. package/dist/types/kill-ring.d.ts +27 -0
  24. package/dist/types/stdin-buffer.d.ts +43 -0
  25. package/dist/types/symbols.d.ts +23 -0
  26. package/dist/types/terminal-capabilities.d.ts +75 -0
  27. package/dist/types/terminal.d.ts +61 -0
  28. package/dist/types/ttyid.d.ts +9 -0
  29. package/dist/types/tui.d.ts +161 -0
  30. package/dist/types/utils.d.ts +74 -0
  31. package/package.json +73 -0
  32. package/src/autocomplete.ts +836 -0
  33. package/src/bracketed-paste.ts +47 -0
  34. package/src/components/box.ts +144 -0
  35. package/src/components/cancellable-loader.ts +40 -0
  36. package/src/components/editor.ts +2664 -0
  37. package/src/components/image.ts +90 -0
  38. package/src/components/input.ts +465 -0
  39. package/src/components/loader.ts +86 -0
  40. package/src/components/markdown.ts +1009 -0
  41. package/src/components/select-list.ts +249 -0
  42. package/src/components/settings-list.ts +211 -0
  43. package/src/components/spacer.ts +28 -0
  44. package/src/components/tab-bar.ts +175 -0
  45. package/src/components/text.ts +110 -0
  46. package/src/components/truncated-text.ts +61 -0
  47. package/src/editor-component.ts +71 -0
  48. package/src/fuzzy.ts +143 -0
  49. package/src/index.ts +39 -0
  50. package/src/keybindings.ts +279 -0
  51. package/src/keys.ts +537 -0
  52. package/src/kill-ring.ts +46 -0
  53. package/src/stdin-buffer.ts +410 -0
  54. package/src/symbols.ts +24 -0
  55. package/src/terminal-capabilities.ts +537 -0
  56. package/src/terminal.ts +716 -0
  57. package/src/ttyid.ts +66 -0
  58. package/src/tui.ts +1481 -0
  59. package/src/utils.ts +359 -0
@@ -0,0 +1,161 @@
1
+ import type { Terminal } from "./terminal";
2
+ import { visibleWidth } from "./utils";
3
+ type InputListenerResult = {
4
+ consume?: boolean;
5
+ data?: string;
6
+ } | undefined;
7
+ type InputListener = (data: string) => InputListenerResult;
8
+ /**
9
+ * Component interface - all components must implement this
10
+ */
11
+ export interface Component {
12
+ /**
13
+ * Render the component to lines for the given viewport width
14
+ * @param width - Current viewport width
15
+ * @returns Array of strings, each representing a line
16
+ */
17
+ render(width: number): string[];
18
+ /**
19
+ * Optional handler for keyboard input when component has focus
20
+ */
21
+ handleInput?(data: string): void;
22
+ /**
23
+ * If true, component receives key release events (Kitty protocol).
24
+ * Default is false - release events are filtered out.
25
+ */
26
+ wantsKeyRelease?: boolean;
27
+ /**
28
+ * Invalidate any cached rendering state.
29
+ * Called when theme changes or when component needs to re-render from scratch.
30
+ */
31
+ invalidate(): void;
32
+ }
33
+ /**
34
+ * Interface for components that can receive focus and display a hardware cursor.
35
+ * When focused, the component should emit CURSOR_MARKER at the cursor position
36
+ * in its render output. TUI will find this marker and position the hardware
37
+ * cursor there for proper IME candidate window positioning.
38
+ */
39
+ export interface Focusable {
40
+ /** Set by TUI when focus changes. Component should emit CURSOR_MARKER when true. */
41
+ focused: boolean;
42
+ }
43
+ /** Type guard to check if a component implements Focusable */
44
+ export declare function isFocusable(component: Component | null): component is Component & Focusable;
45
+ /**
46
+ * Cursor position marker - APC (Application Program Command) sequence.
47
+ * This is a zero-width escape sequence that terminals ignore.
48
+ * Components emit this at the cursor position when focused.
49
+ * TUI finds and strips this marker, then positions the hardware cursor there.
50
+ */
51
+ export declare const CURSOR_MARKER = "\u001B_pi:c\u0007";
52
+ export { visibleWidth };
53
+ /**
54
+ * Anchor position for overlays
55
+ */
56
+ export type OverlayAnchor = "center" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center" | "left-center" | "right-center";
57
+ /**
58
+ * Margin configuration for overlays
59
+ */
60
+ export interface OverlayMargin {
61
+ top?: number;
62
+ right?: number;
63
+ bottom?: number;
64
+ left?: number;
65
+ }
66
+ /** Value that can be absolute (number) or percentage (string like "50%") */
67
+ export type SizeValue = number | `${number}%`;
68
+ /**
69
+ * Options for overlay positioning and sizing.
70
+ * Values can be absolute numbers or percentage strings (e.g., "50%").
71
+ */
72
+ export interface OverlayOptions {
73
+ /** Width in columns, or percentage of terminal width (e.g., "50%") */
74
+ width?: SizeValue;
75
+ /** Minimum width in columns */
76
+ minWidth?: number;
77
+ /** Maximum height in rows, or percentage of terminal height (e.g., "50%") */
78
+ maxHeight?: SizeValue;
79
+ /** Anchor point for positioning (default: 'center') */
80
+ anchor?: OverlayAnchor;
81
+ /** Horizontal offset from anchor position (positive = right) */
82
+ offsetX?: number;
83
+ /** Vertical offset from anchor position (positive = down) */
84
+ offsetY?: number;
85
+ /** Row position: absolute number, or percentage (e.g., "25%" = 25% from top) */
86
+ row?: SizeValue;
87
+ /** Column position: absolute number, or percentage (e.g., "50%" = centered horizontally) */
88
+ col?: SizeValue;
89
+ /** Margin from terminal edges. Number applies to all sides. */
90
+ margin?: OverlayMargin | number;
91
+ /**
92
+ * Control overlay visibility based on terminal dimensions.
93
+ * If provided, overlay is only rendered when this returns true.
94
+ * Called each render cycle with current terminal dimensions.
95
+ */
96
+ visible?: (termWidth: number, termHeight: number) => boolean;
97
+ }
98
+ /**
99
+ * Handle returned by showOverlay for controlling the overlay
100
+ */
101
+ export interface OverlayHandle {
102
+ /** Permanently remove the overlay (cannot be shown again) */
103
+ hide(): void;
104
+ /** Temporarily hide or show the overlay */
105
+ setHidden(hidden: boolean): void;
106
+ /** Check if overlay is temporarily hidden */
107
+ isHidden(): boolean;
108
+ }
109
+ /**
110
+ * Container - a component that contains other components
111
+ */
112
+ export declare class Container implements Component {
113
+ children: Component[];
114
+ addChild(component: Component): void;
115
+ removeChild(component: Component): void;
116
+ clear(): void;
117
+ invalidate(): void;
118
+ render(width: number): string[];
119
+ }
120
+ /**
121
+ * TUI - Main class for managing terminal UI with differential rendering
122
+ */
123
+ export declare class TUI extends Container {
124
+ #private;
125
+ terminal: Terminal;
126
+ /** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
127
+ onDebug?: () => void;
128
+ overlayStack: {
129
+ component: Component;
130
+ options?: OverlayOptions;
131
+ preFocus: Component | null;
132
+ hidden: boolean;
133
+ }[];
134
+ constructor(terminal: Terminal, showHardwareCursor?: boolean);
135
+ get fullRedraws(): number;
136
+ getShowHardwareCursor(): boolean;
137
+ setShowHardwareCursor(enabled: boolean): void;
138
+ getClearOnShrink(): boolean;
139
+ /**
140
+ * Set whether to trigger full re-render when content shrinks.
141
+ * When true (default), empty rows are cleared when content shrinks.
142
+ * When false, empty rows remain (reduces redraws on slower terminals).
143
+ */
144
+ setClearOnShrink(enabled: boolean): void;
145
+ setFocus(component: Component | null): void;
146
+ /**
147
+ * Show an overlay component with configurable positioning and sizing.
148
+ * Returns a handle to control the overlay's visibility.
149
+ */
150
+ showOverlay(component: Component, options?: OverlayOptions): OverlayHandle;
151
+ /** Hide the topmost overlay and restore previous focus. */
152
+ hideOverlay(): void;
153
+ /** Check if there are any visible overlays */
154
+ hasOverlay(): boolean;
155
+ invalidate(): void;
156
+ start(): void;
157
+ addInputListener(listener: InputListener): () => void;
158
+ removeInputListener(listener: InputListener): void;
159
+ stop(): void;
160
+ requestRender(force?: boolean): void;
161
+ }
@@ -0,0 +1,74 @@
1
+ import { Ellipsis, type ExtractSegmentsResult, type SliceResult } from "@gajae-code/natives";
2
+ export { Ellipsis } from "@gajae-code/natives";
3
+ export { getDefaultTabWidth, getIndentation } from "@gajae-code/utils";
4
+ export declare function sliceWithWidth(line: string, startCol: number, length: number, strict?: boolean | null): SliceResult;
5
+ export declare function truncateToWidth(text: string, maxWidth: number, ellipsisKind?: Ellipsis | null, pad?: boolean | null): string;
6
+ export declare function wrapTextWithAnsi(text: string, width: number): string[];
7
+ export declare function extractSegments(line: string, beforeEnd: number, afterStart: number, afterLen: number, strictAfter: boolean): ExtractSegmentsResult;
8
+ /**
9
+ * Tab width in columns for `file`, using `process.cwd()` as the project root for relative paths.
10
+ */
11
+ export declare function getIndentationNoescape(file?: string): number;
12
+ export declare function replaceTabs(text: string, file?: string): string;
13
+ /**
14
+ * Returns a string of n spaces. Uses a pre-allocated buffer for efficiency.
15
+ */
16
+ export declare function padding(n: number): string;
17
+ /**
18
+ * Get the shared grapheme segmenter instance.
19
+ */
20
+ export declare function getSegmenter(): Intl.Segmenter;
21
+ export declare function visibleWidthRaw(str: string): number;
22
+ /**
23
+ * Calculate the visible width of a string in terminal columns.
24
+ */
25
+ export declare function visibleWidth(str: string): number;
26
+ /**
27
+ * Normalize text for terminal output without changing logical editor content.
28
+ * Some terminals render precomposed Thai/Lao AM vowels inconsistently during
29
+ * differential repaint. Their compatibility decompositions have the same cell
30
+ * width but avoid stale-cell artifacts in terminal renderers.
31
+ */
32
+ export declare function normalizeTerminalOutput(str: string): string;
33
+ /**
34
+ * Check if a character is whitespace.
35
+ */
36
+ export declare function isWhitespaceChar(char: string): boolean;
37
+ /**
38
+ * Check if a character is punctuation.
39
+ */
40
+ export declare function isPunctuationChar(char: string): boolean;
41
+ export type WordNavKind = "whitespace" | "delimiter" | "cjk" | "word" | "other";
42
+ /**
43
+ * Coarse Unicode-aware character classification for word navigation (Option/Alt + Left/Right).
44
+ * This intentionally avoids language-specific word segmentation for predictability across scripts.
45
+ */
46
+ export declare function getWordNavKind(grapheme: string): WordNavKind;
47
+ export declare function isWordNavJoiner(grapheme: string): boolean;
48
+ /**
49
+ * Move the cursor one "word" to the left using Unicode-aware coarse navigation.
50
+ *
51
+ * Returns a new cursor index in the range [0, text.length].
52
+ */
53
+ export declare function moveWordLeft(text: string, cursor: number): number;
54
+ /**
55
+ * Move the cursor one "word" to the right using Unicode-aware coarse navigation.
56
+ *
57
+ * Returns a new cursor index in the range [0, text.length].
58
+ */
59
+ export declare function moveWordRight(text: string, cursor: number): number;
60
+ /**
61
+ * Apply background color to a line, padding to full width.
62
+ *
63
+ * @param line - Line of text (may contain ANSI codes)
64
+ * @param width - Total width to pad to
65
+ * @param bgFn - Background color function
66
+ * @returns Line with background applied and padded to width
67
+ */
68
+ export declare function applyBackgroundToLine(line: string, width: number, bgFn: (text: string) => string): string;
69
+ /**
70
+ * Extract a range of visible columns from a line. Handles ANSI codes and wide chars.
71
+ *
72
+ * @param strict - If true, exclude wide chars at boundary that would extend past the range
73
+ */
74
+ export declare function sliceByColumn(line: string, startCol: number, length: number, strict?: boolean): string;
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "type": "module",
3
+ "name": "@gajae-code/tui",
4
+ "version": "0.1.1",
5
+ "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
+ "homepage": "https://gajae-code.dev",
7
+ "author": "Can Boluk",
8
+ "contributors": [
9
+ "Mario Zechner"
10
+ ],
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/gajae-ai/gajae-code.git",
15
+ "directory": "packages/tui"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/gajae-ai/gajae-code/issues"
19
+ },
20
+ "keywords": [
21
+ "tui",
22
+ "terminal",
23
+ "ui",
24
+ "text-editor",
25
+ "differential-rendering",
26
+ "typescript",
27
+ "cli"
28
+ ],
29
+ "main": "./src/index.ts",
30
+ "types": "./dist/types/index.d.ts",
31
+ "scripts": {
32
+ "check": "biome check . && bun run check:types",
33
+ "check:types": "tsgo -p tsconfig.json --noEmit",
34
+ "lint": "biome lint .",
35
+ "test": "bun test test/*.test.ts",
36
+ "fix": "biome check --write --unsafe .",
37
+ "fmt": "biome format --write ."
38
+ },
39
+ "dependencies": {
40
+ "@gajae-code/natives": "0.1.1",
41
+ "@gajae-code/utils": "0.1.1",
42
+ "lru-cache": "11.3.6",
43
+ "marked": "^18.0.3"
44
+ },
45
+ "devDependencies": {
46
+ "chalk": "^5.6.2",
47
+ "@xterm/headless": "^6.0.0"
48
+ },
49
+ "engines": {
50
+ "bun": ">=1.3.14"
51
+ },
52
+ "files": [
53
+ "src",
54
+ "README.md",
55
+ "CHANGELOG.md",
56
+ "dist/types"
57
+ ],
58
+ "exports": {
59
+ ".": {
60
+ "types": "./dist/types/index.d.ts",
61
+ "import": "./src/index.ts"
62
+ },
63
+ "./*": {
64
+ "types": "./dist/types/*.d.ts",
65
+ "import": "./src/*.ts"
66
+ },
67
+ "./components/*": {
68
+ "types": "./dist/types/components/*.d.ts",
69
+ "import": "./src/components/*.ts"
70
+ },
71
+ "./*.js": "./src/*.ts"
72
+ }
73
+ }