@mariozechner/pi-tui 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +655 -0
- package/dist/autocomplete.d.ts +44 -0
- package/dist/autocomplete.d.ts.map +1 -0
- package/dist/autocomplete.js +454 -0
- package/dist/autocomplete.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +23 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +75 -0
- package/dist/logger.js.map +1 -0
- package/dist/markdown-component.d.ts +14 -0
- package/dist/markdown-component.d.ts.map +1 -0
- package/dist/markdown-component.js +225 -0
- package/dist/markdown-component.js.map +1 -0
- package/dist/select-list.d.ts +21 -0
- package/dist/select-list.d.ts.map +1 -0
- package/dist/select-list.js +130 -0
- package/dist/select-list.js.map +1 -0
- package/dist/text-component.d.ts +13 -0
- package/dist/text-component.d.ts.map +1 -0
- package/dist/text-component.js +90 -0
- package/dist/text-component.js.map +1 -0
- package/dist/text-editor.d.ts +40 -0
- package/dist/text-editor.d.ts.map +1 -0
- package/dist/text-editor.js +670 -0
- package/dist/text-editor.js.map +1 -0
- package/dist/tui.d.ts +58 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +391 -0
- package/dist/tui.js.map +1 -0
- package/dist/whitespace-component.d.ts +12 -0
- package/dist/whitespace-component.d.ts.map +1 -0
- package/dist/whitespace-component.js +21 -0
- package/dist/whitespace-component.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
# @mariozechner/pi-tui
|
|
2
|
+
|
|
3
|
+
Terminal UI framework with differential rendering for building interactive CLI applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Differential Rendering**: Only re-renders content that has changed for optimal performance
|
|
8
|
+
- **Interactive Components**: Text editor, autocomplete, selection lists, and markdown rendering
|
|
9
|
+
- **Composable Architecture**: Container-based component system with proper lifecycle management
|
|
10
|
+
- **Text Editor Autocomplete System**: File completion and slash commands with provider interface
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { TUI, Container, TextComponent, TextEditor } from "@mariozechner/pi-tui";
|
|
16
|
+
|
|
17
|
+
// Create TUI manager
|
|
18
|
+
const ui = new TUI();
|
|
19
|
+
|
|
20
|
+
// Create components
|
|
21
|
+
const header = new TextComponent("🚀 My TUI App");
|
|
22
|
+
const chatContainer = new Container();
|
|
23
|
+
const editor = new TextEditor();
|
|
24
|
+
|
|
25
|
+
// Add components to UI
|
|
26
|
+
ui.addChild(header);
|
|
27
|
+
ui.addChild(chatContainer);
|
|
28
|
+
ui.addChild(editor);
|
|
29
|
+
|
|
30
|
+
// Set focus to the editor
|
|
31
|
+
ui.setFocus(editor);
|
|
32
|
+
|
|
33
|
+
// Handle editor submissions
|
|
34
|
+
editor.onSubmit = (text: string) => {
|
|
35
|
+
if (text.trim()) {
|
|
36
|
+
const message = new TextComponent(`💬 ${text}`);
|
|
37
|
+
chatContainer.addChild(message);
|
|
38
|
+
ui.requestRender();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Start the UI
|
|
43
|
+
ui.start();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Core Components
|
|
47
|
+
|
|
48
|
+
### TUI
|
|
49
|
+
|
|
50
|
+
Main TUI manager that handles rendering, input, and component coordination.
|
|
51
|
+
|
|
52
|
+
**Methods:**
|
|
53
|
+
|
|
54
|
+
- `addChild(component)` - Add a component to the TUI
|
|
55
|
+
- `removeChild(component)` - Remove a component from the TUI
|
|
56
|
+
- `setFocus(component)` - Set which component receives keyboard input
|
|
57
|
+
- `start()` - Start the TUI (enables raw mode)
|
|
58
|
+
- `stop()` - Stop the TUI (disables raw mode)
|
|
59
|
+
- `requestRender()` - Request a re-render on next tick
|
|
60
|
+
- `configureLogging(config)` - Configure debug logging
|
|
61
|
+
- `cleanupSentinels()` - Remove placeholder components after removal operations
|
|
62
|
+
- `findComponent(component)` - Check if a component exists in the hierarchy (private)
|
|
63
|
+
- `findInContainer(container, component)` - Search for component in container (private)
|
|
64
|
+
|
|
65
|
+
### Container
|
|
66
|
+
|
|
67
|
+
Component that manages child components with differential rendering.
|
|
68
|
+
|
|
69
|
+
**Constructor:**
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
new Container(parentTui?: TUI | undefined)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Methods:**
|
|
76
|
+
|
|
77
|
+
- `addChild(component)` - Add a child component
|
|
78
|
+
- `removeChild(component)` - Remove a child component
|
|
79
|
+
- `getChild(index)` - Get a specific child component
|
|
80
|
+
- `getChildCount()` - Get the number of child components
|
|
81
|
+
- `clear()` - Remove all child components
|
|
82
|
+
- `setParentTui(tui)` - Set the parent TUI reference
|
|
83
|
+
- `cleanupSentinels()` - Clean up removed component placeholders
|
|
84
|
+
- `render(width)` - Render all child components (returns ContainerRenderResult)
|
|
85
|
+
|
|
86
|
+
### TextEditor
|
|
87
|
+
|
|
88
|
+
Interactive multiline text editor with cursor support and comprehensive keyboard shortcuts.
|
|
89
|
+
|
|
90
|
+
**Constructor:**
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
new TextEditor(config?: TextEditorConfig)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Configuration:**
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
interface TextEditorConfig {
|
|
100
|
+
// Configuration options for text editor
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
editor.configure(config: Partial<TextEditorConfig>)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Properties:**
|
|
107
|
+
|
|
108
|
+
- `onSubmit?: (text: string) => void` - Callback when user presses Enter
|
|
109
|
+
- `onChange?: (text: string) => void` - Callback when text content changes
|
|
110
|
+
|
|
111
|
+
**Methods:**
|
|
112
|
+
|
|
113
|
+
- `getText()` - Get current text content
|
|
114
|
+
- `setText(text)` - Set text content and move cursor to end
|
|
115
|
+
- `setAutocompleteProvider(provider)` - Set autocomplete provider for Tab completion
|
|
116
|
+
- `render(width)` - Render the editor with current state
|
|
117
|
+
- `handleInput(data)` - Process keyboard input
|
|
118
|
+
|
|
119
|
+
**Keyboard Shortcuts:**
|
|
120
|
+
|
|
121
|
+
**Navigation:**
|
|
122
|
+
|
|
123
|
+
- `Arrow Keys` - Move cursor
|
|
124
|
+
- `Home` / `Ctrl+A` - Move to start of line
|
|
125
|
+
- `End` / `Ctrl+E` - Move to end of line
|
|
126
|
+
|
|
127
|
+
**Editing:**
|
|
128
|
+
|
|
129
|
+
- `Backspace` - Delete character before cursor
|
|
130
|
+
- `Delete` / `Fn+Backspace` - Delete character at cursor
|
|
131
|
+
- `Ctrl+K` - Delete current line
|
|
132
|
+
- `Enter` - Submit text (calls onSubmit)
|
|
133
|
+
- `Shift+Enter` / `Option+Enter` - Add new line
|
|
134
|
+
- `Tab` - Trigger autocomplete
|
|
135
|
+
|
|
136
|
+
**Autocomplete (when active):**
|
|
137
|
+
|
|
138
|
+
- `Tab` - Apply selected completion
|
|
139
|
+
- `Arrow Up/Down` - Navigate suggestions
|
|
140
|
+
- `Escape` - Cancel autocomplete
|
|
141
|
+
- `Enter` - Cancel autocomplete and submit
|
|
142
|
+
|
|
143
|
+
**Paste Detection:**
|
|
144
|
+
|
|
145
|
+
- Automatically handles multi-line paste
|
|
146
|
+
- Converts tabs to 4 spaces
|
|
147
|
+
- Filters non-printable characters
|
|
148
|
+
|
|
149
|
+
### TextComponent
|
|
150
|
+
|
|
151
|
+
Simple text component with automatic text wrapping and differential rendering.
|
|
152
|
+
|
|
153
|
+
**Constructor:**
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
new TextComponent(text: string, padding?: Padding)
|
|
157
|
+
|
|
158
|
+
interface Padding {
|
|
159
|
+
top?: number;
|
|
160
|
+
bottom?: number;
|
|
161
|
+
left?: number;
|
|
162
|
+
right?: number;
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Methods:**
|
|
167
|
+
|
|
168
|
+
- `setText(text)` - Update the text content
|
|
169
|
+
- `getText()` - Get current text content
|
|
170
|
+
- `render(width)` - Render with word wrapping
|
|
171
|
+
|
|
172
|
+
**Features:**
|
|
173
|
+
|
|
174
|
+
- Automatic text wrapping to fit terminal width
|
|
175
|
+
- Configurable padding on all sides
|
|
176
|
+
- Preserves line breaks in source text
|
|
177
|
+
- Uses differential rendering to avoid unnecessary updates
|
|
178
|
+
|
|
179
|
+
### MarkdownComponent
|
|
180
|
+
|
|
181
|
+
Renders markdown content with syntax highlighting and proper formatting.
|
|
182
|
+
|
|
183
|
+
**Constructor:**
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
new MarkdownComponent(text?: string)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Methods:**
|
|
190
|
+
|
|
191
|
+
- `setText(text)` - Update markdown content
|
|
192
|
+
- `render(width)` - Render parsed markdown
|
|
193
|
+
|
|
194
|
+
**Features:**
|
|
195
|
+
|
|
196
|
+
- **Headings**: Styled with colors and formatting
|
|
197
|
+
- **Code blocks**: Syntax highlighting with gray background
|
|
198
|
+
- **Lists**: Bullet points (•) and numbered lists
|
|
199
|
+
- **Emphasis**: **Bold** and _italic_ text
|
|
200
|
+
- **Links**: Underlined with URL display
|
|
201
|
+
- **Blockquotes**: Styled with left border
|
|
202
|
+
- **Inline code**: Highlighted with background
|
|
203
|
+
- **Horizontal rules**: Terminal-width separator lines
|
|
204
|
+
- Differential rendering for performance
|
|
205
|
+
|
|
206
|
+
### SelectList
|
|
207
|
+
|
|
208
|
+
Interactive selection component for choosing from options.
|
|
209
|
+
|
|
210
|
+
**Constructor:**
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
new SelectList(items: SelectItem[], maxVisible?: number)
|
|
214
|
+
|
|
215
|
+
interface SelectItem {
|
|
216
|
+
value: string;
|
|
217
|
+
label: string;
|
|
218
|
+
description?: string;
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Properties:**
|
|
223
|
+
|
|
224
|
+
- `onSelect?: (item: SelectItem) => void` - Called when item is selected
|
|
225
|
+
- `onCancel?: () => void` - Called when selection is cancelled
|
|
226
|
+
|
|
227
|
+
**Methods:**
|
|
228
|
+
|
|
229
|
+
- `setFilter(filter)` - Filter items by value
|
|
230
|
+
- `getSelectedItem()` - Get currently selected item
|
|
231
|
+
- `handleInput(keyData)` - Handle keyboard navigation
|
|
232
|
+
- `render(width)` - Render the selection list
|
|
233
|
+
|
|
234
|
+
**Features:**
|
|
235
|
+
|
|
236
|
+
- Keyboard navigation (arrow keys, Enter)
|
|
237
|
+
- Search/filter functionality
|
|
238
|
+
- Scrolling for long lists
|
|
239
|
+
- Custom option rendering with descriptions
|
|
240
|
+
- Visual selection indicator (→)
|
|
241
|
+
- Scroll position indicator
|
|
242
|
+
|
|
243
|
+
### Autocomplete System
|
|
244
|
+
|
|
245
|
+
Comprehensive autocomplete system supporting slash commands and file paths.
|
|
246
|
+
|
|
247
|
+
#### AutocompleteProvider Interface
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
interface AutocompleteProvider {
|
|
251
|
+
getSuggestions(
|
|
252
|
+
lines: string[],
|
|
253
|
+
cursorLine: number,
|
|
254
|
+
cursorCol: number,
|
|
255
|
+
): {
|
|
256
|
+
items: AutocompleteItem[];
|
|
257
|
+
prefix: string;
|
|
258
|
+
} | null;
|
|
259
|
+
|
|
260
|
+
applyCompletion(
|
|
261
|
+
lines: string[],
|
|
262
|
+
cursorLine: number,
|
|
263
|
+
cursorCol: number,
|
|
264
|
+
item: AutocompleteItem,
|
|
265
|
+
prefix: string,
|
|
266
|
+
): {
|
|
267
|
+
lines: string[];
|
|
268
|
+
cursorLine: number;
|
|
269
|
+
cursorCol: number;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
interface AutocompleteItem {
|
|
274
|
+
value: string;
|
|
275
|
+
label: string;
|
|
276
|
+
description?: string;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### CombinedAutocompleteProvider
|
|
281
|
+
|
|
282
|
+
Built-in provider supporting slash commands and file completion.
|
|
283
|
+
|
|
284
|
+
**Constructor:**
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
new CombinedAutocompleteProvider(
|
|
288
|
+
commands: (SlashCommand | AutocompleteItem)[] = [],
|
|
289
|
+
basePath: string = process.cwd()
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
interface SlashCommand {
|
|
293
|
+
name: string;
|
|
294
|
+
description?: string;
|
|
295
|
+
getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Features:**
|
|
300
|
+
|
|
301
|
+
**Slash Commands:**
|
|
302
|
+
|
|
303
|
+
- Type `/` to trigger command completion
|
|
304
|
+
- Auto-completion for command names
|
|
305
|
+
- Argument completion for commands that support it
|
|
306
|
+
- Space after command name for argument input
|
|
307
|
+
|
|
308
|
+
**File Completion:**
|
|
309
|
+
|
|
310
|
+
- `Tab` key triggers file completion
|
|
311
|
+
- `@` prefix for file attachments
|
|
312
|
+
- Home directory expansion (`~/`)
|
|
313
|
+
- Relative and absolute path support
|
|
314
|
+
- Directory-first sorting
|
|
315
|
+
- Filters to attachable files for `@` prefix
|
|
316
|
+
|
|
317
|
+
**Path Patterns:**
|
|
318
|
+
|
|
319
|
+
- `./` and `../` - Relative paths
|
|
320
|
+
- `~/` - Home directory
|
|
321
|
+
- `@path` - File attachment syntax
|
|
322
|
+
- Tab completion from any context
|
|
323
|
+
|
|
324
|
+
**Methods:**
|
|
325
|
+
|
|
326
|
+
- `getSuggestions()` - Get completions for current context
|
|
327
|
+
- `getForceFileSuggestions()` - Force file completion (Tab key)
|
|
328
|
+
- `shouldTriggerFileCompletion()` - Check if file completion should trigger
|
|
329
|
+
- `applyCompletion()` - Apply selected completion
|
|
330
|
+
|
|
331
|
+
## Differential Rendering
|
|
332
|
+
|
|
333
|
+
The core concept: components return `{lines: string[], changed: boolean, keepLines?: number}`:
|
|
334
|
+
|
|
335
|
+
- `lines`: All lines the component should display
|
|
336
|
+
- `changed`: Whether the component has changed since last render
|
|
337
|
+
- `keepLines`: (Containers only) How many lines from the beginning are unchanged
|
|
338
|
+
|
|
339
|
+
**How it works:**
|
|
340
|
+
|
|
341
|
+
1. TUI calculates total unchanged lines from top (`keepLines`)
|
|
342
|
+
2. Moves cursor up by `(totalLines - keepLines)` positions
|
|
343
|
+
3. Clears from cursor position down with `\x1b[0J`
|
|
344
|
+
4. Prints only the changing lines: `result.lines.slice(keepLines)`
|
|
345
|
+
|
|
346
|
+
This approach minimizes screen updates and provides smooth performance even with large amounts of text.
|
|
347
|
+
|
|
348
|
+
**Important:** Don't add extra cursor positioning after printing - it interferes with terminal scrolling and causes rendering artifacts.
|
|
349
|
+
|
|
350
|
+
## Advanced Examples
|
|
351
|
+
|
|
352
|
+
### Chat Application with Autocomplete
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { TUI, Container, TextEditor, MarkdownComponent, CombinedAutocompleteProvider } from "@mariozechner/pi-tui";
|
|
356
|
+
|
|
357
|
+
const ui = new TUI();
|
|
358
|
+
const chatHistory = new Container();
|
|
359
|
+
const editor = new TextEditor();
|
|
360
|
+
|
|
361
|
+
// Set up autocomplete with slash commands
|
|
362
|
+
const autocompleteProvider = new CombinedAutocompleteProvider([
|
|
363
|
+
{ name: "clear", description: "Clear chat history" },
|
|
364
|
+
{ name: "help", description: "Show help information" },
|
|
365
|
+
{
|
|
366
|
+
name: "attach",
|
|
367
|
+
description: "Attach a file",
|
|
368
|
+
getArgumentCompletions: (prefix) => {
|
|
369
|
+
// Return file suggestions for attach command
|
|
370
|
+
return null; // Use default file completion
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
]);
|
|
374
|
+
|
|
375
|
+
editor.setAutocompleteProvider(autocompleteProvider);
|
|
376
|
+
|
|
377
|
+
editor.onSubmit = (text) => {
|
|
378
|
+
// Handle slash commands
|
|
379
|
+
if (text.startsWith("/")) {
|
|
380
|
+
const [command, ...args] = text.slice(1).split(" ");
|
|
381
|
+
if (command === "clear") {
|
|
382
|
+
chatHistory.clear();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (command === "help") {
|
|
386
|
+
const help = new MarkdownComponent(`
|
|
387
|
+
## Available Commands
|
|
388
|
+
- \`/clear\` - Clear chat history
|
|
389
|
+
- \`/help\` - Show this help
|
|
390
|
+
- \`/attach <file>\` - Attach a file
|
|
391
|
+
`);
|
|
392
|
+
chatHistory.addChild(help);
|
|
393
|
+
ui.requestRender();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Regular message
|
|
399
|
+
const message = new MarkdownComponent(`**You:** ${text}`);
|
|
400
|
+
chatHistory.addChild(message);
|
|
401
|
+
|
|
402
|
+
// Add AI response (simulated)
|
|
403
|
+
setTimeout(() => {
|
|
404
|
+
const response = new MarkdownComponent(`**AI:** Response to "${text}"`);
|
|
405
|
+
chatHistory.addChild(response);
|
|
406
|
+
ui.requestRender();
|
|
407
|
+
}, 1000);
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
ui.addChild(chatHistory);
|
|
411
|
+
ui.addChild(editor);
|
|
412
|
+
ui.setFocus(editor);
|
|
413
|
+
ui.start();
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### File Browser
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import { TUI, SelectList } from "@mariozechner/pi-tui";
|
|
420
|
+
import { readdirSync, statSync } from "fs";
|
|
421
|
+
import { join } from "path";
|
|
422
|
+
|
|
423
|
+
const ui = new TUI();
|
|
424
|
+
let currentPath = process.cwd();
|
|
425
|
+
|
|
426
|
+
function createFileList(path: string) {
|
|
427
|
+
const entries = readdirSync(path).map((entry) => {
|
|
428
|
+
const fullPath = join(path, entry);
|
|
429
|
+
const isDir = statSync(fullPath).isDirectory();
|
|
430
|
+
return {
|
|
431
|
+
value: entry,
|
|
432
|
+
label: entry,
|
|
433
|
+
description: isDir ? "directory" : "file",
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Add parent directory option
|
|
438
|
+
if (path !== "/") {
|
|
439
|
+
entries.unshift({
|
|
440
|
+
value: "..",
|
|
441
|
+
label: "..",
|
|
442
|
+
description: "parent directory",
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return entries;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function showDirectory(path: string) {
|
|
450
|
+
ui.clear();
|
|
451
|
+
|
|
452
|
+
const entries = createFileList(path);
|
|
453
|
+
const fileList = new SelectList(entries, 10);
|
|
454
|
+
|
|
455
|
+
fileList.onSelect = (item) => {
|
|
456
|
+
if (item.value === "..") {
|
|
457
|
+
currentPath = join(currentPath, "..");
|
|
458
|
+
showDirectory(currentPath);
|
|
459
|
+
} else if (item.description === "directory") {
|
|
460
|
+
currentPath = join(currentPath, item.value);
|
|
461
|
+
showDirectory(currentPath);
|
|
462
|
+
} else {
|
|
463
|
+
console.log(`Selected file: ${join(currentPath, item.value)}`);
|
|
464
|
+
ui.stop();
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
ui.addChild(fileList);
|
|
469
|
+
ui.setFocus(fileList);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
showDirectory(currentPath);
|
|
473
|
+
ui.start();
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Multi-Component Layout
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
import { TUI, Container, TextComponent, TextEditor, MarkdownComponent } from "@mariozechner/pi-tui";
|
|
480
|
+
|
|
481
|
+
const ui = new TUI();
|
|
482
|
+
|
|
483
|
+
// Create layout containers
|
|
484
|
+
const header = new TextComponent("📝 Advanced TUI Demo", { bottom: 1 });
|
|
485
|
+
const mainContent = new Container();
|
|
486
|
+
const sidebar = new Container();
|
|
487
|
+
const footer = new TextComponent("Press Ctrl+C to exit", { top: 1 });
|
|
488
|
+
|
|
489
|
+
// Sidebar content
|
|
490
|
+
sidebar.addChild(new TextComponent("📁 Files:", { bottom: 1 }));
|
|
491
|
+
sidebar.addChild(new TextComponent("- config.json"));
|
|
492
|
+
sidebar.addChild(new TextComponent("- README.md"));
|
|
493
|
+
sidebar.addChild(new TextComponent("- package.json"));
|
|
494
|
+
|
|
495
|
+
// Main content area
|
|
496
|
+
const chatArea = new Container();
|
|
497
|
+
const inputArea = new TextEditor();
|
|
498
|
+
|
|
499
|
+
// Add welcome message
|
|
500
|
+
chatArea.addChild(
|
|
501
|
+
new MarkdownComponent(`
|
|
502
|
+
# Welcome to the TUI Demo
|
|
503
|
+
|
|
504
|
+
This demonstrates multiple components working together:
|
|
505
|
+
|
|
506
|
+
- **Header**: Static title with padding
|
|
507
|
+
- **Sidebar**: File list (simulated)
|
|
508
|
+
- **Chat Area**: Scrollable message history
|
|
509
|
+
- **Input**: Interactive text editor
|
|
510
|
+
- **Footer**: Status information
|
|
511
|
+
|
|
512
|
+
Try typing a message and pressing Enter!
|
|
513
|
+
`),
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
inputArea.onSubmit = (text) => {
|
|
517
|
+
if (text.trim()) {
|
|
518
|
+
const message = new MarkdownComponent(`
|
|
519
|
+
**${new Date().toLocaleTimeString()}:** ${text}
|
|
520
|
+
`);
|
|
521
|
+
chatArea.addChild(message);
|
|
522
|
+
ui.requestRender();
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// Build layout
|
|
527
|
+
mainContent.addChild(chatArea);
|
|
528
|
+
mainContent.addChild(inputArea);
|
|
529
|
+
|
|
530
|
+
ui.addChild(header);
|
|
531
|
+
ui.addChild(mainContent);
|
|
532
|
+
ui.addChild(footer);
|
|
533
|
+
ui.setFocus(inputArea);
|
|
534
|
+
|
|
535
|
+
// Configure debug logging
|
|
536
|
+
ui.configureLogging({
|
|
537
|
+
enabled: true,
|
|
538
|
+
level: "info",
|
|
539
|
+
logFile: "tui-debug.log",
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
ui.start();
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
## Interfaces and Types
|
|
546
|
+
|
|
547
|
+
### Core Types
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
interface ComponentRenderResult {
|
|
551
|
+
lines: string[];
|
|
552
|
+
changed: boolean;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
interface ContainerRenderResult extends ComponentRenderResult {
|
|
556
|
+
keepLines: number;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
interface Component {
|
|
560
|
+
render(width: number): ComponentRenderResult;
|
|
561
|
+
handleInput?(keyData: string): void;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
interface Padding {
|
|
565
|
+
top?: number;
|
|
566
|
+
bottom?: number;
|
|
567
|
+
left?: number;
|
|
568
|
+
right?: number;
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### Autocomplete Types
|
|
573
|
+
|
|
574
|
+
```typescript
|
|
575
|
+
interface AutocompleteItem {
|
|
576
|
+
value: string;
|
|
577
|
+
label: string;
|
|
578
|
+
description?: string;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
interface SlashCommand {
|
|
582
|
+
name: string;
|
|
583
|
+
description?: string;
|
|
584
|
+
getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
interface AutocompleteProvider {
|
|
588
|
+
getSuggestions(
|
|
589
|
+
lines: string[],
|
|
590
|
+
cursorLine: number,
|
|
591
|
+
cursorCol: number,
|
|
592
|
+
): {
|
|
593
|
+
items: AutocompleteItem[];
|
|
594
|
+
prefix: string;
|
|
595
|
+
} | null;
|
|
596
|
+
|
|
597
|
+
applyCompletion(
|
|
598
|
+
lines: string[],
|
|
599
|
+
cursorLine: number,
|
|
600
|
+
cursorCol: number,
|
|
601
|
+
item: AutocompleteItem,
|
|
602
|
+
prefix: string,
|
|
603
|
+
): {
|
|
604
|
+
lines: string[];
|
|
605
|
+
cursorLine: number;
|
|
606
|
+
cursorCol: number;
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Selection Types
|
|
612
|
+
|
|
613
|
+
```typescript
|
|
614
|
+
interface SelectItem {
|
|
615
|
+
value: string;
|
|
616
|
+
label: string;
|
|
617
|
+
description?: string;
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## Development
|
|
622
|
+
|
|
623
|
+
```bash
|
|
624
|
+
# Install dependencies (from monorepo root)
|
|
625
|
+
npm install
|
|
626
|
+
|
|
627
|
+
# Build the package
|
|
628
|
+
npm run build
|
|
629
|
+
|
|
630
|
+
# Run type checking
|
|
631
|
+
npm run check
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**Testing:**
|
|
635
|
+
Create a test file and run it with tsx:
|
|
636
|
+
|
|
637
|
+
```bash
|
|
638
|
+
# From packages/tui directory
|
|
639
|
+
npx tsx test/demo.ts
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
Special input keywords for simulation: "TAB", "ENTER", "SPACE", "ESC"
|
|
643
|
+
|
|
644
|
+
**Debugging:**
|
|
645
|
+
Enable logging to see detailed component behavior:
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
ui.configureLogging({
|
|
649
|
+
enabled: true,
|
|
650
|
+
level: "debug", // "error" | "warn" | "info" | "debug"
|
|
651
|
+
logFile: "tui-debug.log",
|
|
652
|
+
});
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
Check the log file to debug rendering issues, input handling, and component lifecycle.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface AutocompleteItem {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SlashCommand {
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
|
|
10
|
+
}
|
|
11
|
+
export interface AutocompleteProvider {
|
|
12
|
+
getSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
|
|
13
|
+
items: AutocompleteItem[];
|
|
14
|
+
prefix: string;
|
|
15
|
+
} | null;
|
|
16
|
+
applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
|
|
17
|
+
lines: string[];
|
|
18
|
+
cursorLine: number;
|
|
19
|
+
cursorCol: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
23
|
+
private commands;
|
|
24
|
+
private basePath;
|
|
25
|
+
constructor(commands?: (SlashCommand | AutocompleteItem)[], basePath?: string);
|
|
26
|
+
getSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
|
|
27
|
+
items: AutocompleteItem[];
|
|
28
|
+
prefix: string;
|
|
29
|
+
} | null;
|
|
30
|
+
applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
|
|
31
|
+
lines: string[];
|
|
32
|
+
cursorLine: number;
|
|
33
|
+
cursorCol: number;
|
|
34
|
+
};
|
|
35
|
+
private extractPathPrefix;
|
|
36
|
+
private expandHomePath;
|
|
37
|
+
private getFileSuggestions;
|
|
38
|
+
getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
|
|
39
|
+
items: AutocompleteItem[];
|
|
40
|
+
prefix: string;
|
|
41
|
+
} | null;
|
|
42
|
+
shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=autocomplete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../src/autocomplete.ts"],"names":[],"mappings":"AAwFA,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,sBAAsB,CAAC,CAAC,cAAc,EAAE,MAAM,GAAG,gBAAgB,EAAE,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,oBAAoB;IAGpC,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QACF,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC;KACf,GAAG,IAAI,CAAC;IAIT,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QACF,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;CACF;AAGD,qBAAa,4BAA6B,YAAW,oBAAoB;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,GAAE,CAAC,YAAY,GAAG,gBAAgB,CAAC,EAAO,EAAE,QAAQ,GAAE,MAAsB;IAKhG,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IA+EvD,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IA6D7D,OAAO,CAAC,iBAAiB;IA0CzB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,kBAAkB;IA0J1B,uBAAuB,CACtB,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAoCvD,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;CAW5F"}
|