@compilr-dev/cli 0.4.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 +110 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.js +317 -0
- package/dist/agents/registry.d.ts +66 -0
- package/dist/agents/registry.js +238 -0
- package/dist/agents/types.d.ts +40 -0
- package/dist/agents/types.js +94 -0
- package/dist/commands/custom-registry.d.ts +69 -0
- package/dist/commands/custom-registry.js +246 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.js +7 -0
- package/dist/commands/types.d.ts +31 -0
- package/dist/commands/types.js +26 -0
- package/dist/commands.d.ts +63 -0
- package/dist/commands.js +324 -0
- package/dist/db/index.d.ts +42 -0
- package/dist/db/index.js +146 -0
- package/dist/db/repositories/document-repository.d.ts +63 -0
- package/dist/db/repositories/document-repository.js +184 -0
- package/dist/db/repositories/index.d.ts +9 -0
- package/dist/db/repositories/index.js +6 -0
- package/dist/db/repositories/project-repository.d.ts +132 -0
- package/dist/db/repositories/project-repository.js +337 -0
- package/dist/db/repositories/work-item-repository.d.ts +115 -0
- package/dist/db/repositories/work-item-repository.js +389 -0
- package/dist/db/schema.d.ts +83 -0
- package/dist/db/schema.js +143 -0
- package/dist/debug.d.ts +8 -0
- package/dist/debug.js +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +348 -0
- package/dist/index.old.d.ts +7 -0
- package/dist/index.old.js +1014 -0
- package/dist/repl.d.ts +121 -0
- package/dist/repl.js +1878 -0
- package/dist/settings/index.d.ts +80 -0
- package/dist/settings/index.js +195 -0
- package/dist/shared-handlers.d.ts +63 -0
- package/dist/shared-handlers.js +57 -0
- package/dist/slash-autocomplete.d.ts +41 -0
- package/dist/slash-autocomplete.js +638 -0
- package/dist/state.d.ts +75 -0
- package/dist/state.js +130 -0
- package/dist/tabbed-menu.d.ts +11 -0
- package/dist/tabbed-menu.js +328 -0
- package/dist/templates/backlog-md.d.ts +7 -0
- package/dist/templates/backlog-md.js +94 -0
- package/dist/templates/claude-md.d.ts +7 -0
- package/dist/templates/claude-md.js +189 -0
- package/dist/templates/coding-standards.d.ts +7 -0
- package/dist/templates/coding-standards.js +299 -0
- package/dist/templates/compilr-md.d.ts +7 -0
- package/dist/templates/compilr-md.js +189 -0
- package/dist/templates/config-json.d.ts +38 -0
- package/dist/templates/config-json.js +39 -0
- package/dist/templates/gitignore.d.ts +7 -0
- package/dist/templates/gitignore.js +85 -0
- package/dist/templates/index.d.ts +19 -0
- package/dist/templates/index.js +302 -0
- package/dist/templates/package-json.d.ts +7 -0
- package/dist/templates/package-json.js +111 -0
- package/dist/templates/readme-md.d.ts +7 -0
- package/dist/templates/readme-md.js +161 -0
- package/dist/templates/tsconfig.d.ts +7 -0
- package/dist/templates/tsconfig.js +61 -0
- package/dist/templates/types.d.ts +33 -0
- package/dist/templates/types.js +24 -0
- package/dist/test-autocomplete.d.ts +7 -0
- package/dist/test-autocomplete.js +85 -0
- package/dist/test-tabbed-menu.d.ts +7 -0
- package/dist/test-tabbed-menu.js +25 -0
- package/dist/themes/colors.d.ts +49 -0
- package/dist/themes/colors.js +135 -0
- package/dist/themes/index.d.ts +23 -0
- package/dist/themes/index.js +24 -0
- package/dist/themes/registry.d.ts +60 -0
- package/dist/themes/registry.js +195 -0
- package/dist/themes/types.d.ts +82 -0
- package/dist/themes/types.js +7 -0
- package/dist/tool-selector.d.ts +71 -0
- package/dist/tool-selector.js +184 -0
- package/dist/tools/ask-user-simple.d.ts +19 -0
- package/dist/tools/ask-user-simple.js +86 -0
- package/dist/tools/ask-user.d.ts +32 -0
- package/dist/tools/ask-user.js +113 -0
- package/dist/tools/backlog.d.ts +53 -0
- package/dist/tools/backlog.js +709 -0
- package/dist/tools.d.ts +15 -0
- package/dist/tools.js +121 -0
- package/dist/ui/agents-overlay.d.ts +12 -0
- package/dist/ui/agents-overlay.js +501 -0
- package/dist/ui/arch-type-overlay.d.ts +20 -0
- package/dist/ui/arch-type-overlay.js +229 -0
- package/dist/ui/ask-user-overlay.d.ts +26 -0
- package/dist/ui/ask-user-overlay.js +647 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay.js +242 -0
- package/dist/ui/backlog-overlay.d.ts +17 -0
- package/dist/ui/backlog-overlay.js +786 -0
- package/dist/ui/commands-overlay.d.ts +11 -0
- package/dist/ui/commands-overlay.js +410 -0
- package/dist/ui/config-overlay.d.ts +34 -0
- package/dist/ui/config-overlay.js +977 -0
- package/dist/ui/conversation.d.ts +82 -0
- package/dist/ui/conversation.js +508 -0
- package/dist/ui/diff.d.ts +38 -0
- package/dist/ui/diff.js +182 -0
- package/dist/ui/ephemeral.d.ts +111 -0
- package/dist/ui/ephemeral.js +413 -0
- package/dist/ui/file-autocomplete.d.ts +45 -0
- package/dist/ui/file-autocomplete.js +237 -0
- package/dist/ui/footer.d.ts +153 -0
- package/dist/ui/footer.js +422 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/init-overlay.d.ts +24 -0
- package/dist/ui/init-overlay.js +525 -0
- package/dist/ui/input-prompt-v2.d.ts +179 -0
- package/dist/ui/input-prompt-v2.js +991 -0
- package/dist/ui/input-prompt.d.ts +97 -0
- package/dist/ui/input-prompt.js +800 -0
- package/dist/ui/iteration-limit-overlay.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay.js +150 -0
- package/dist/ui/keys-overlay.d.ts +14 -0
- package/dist/ui/keys-overlay.js +181 -0
- package/dist/ui/model-warning-overlay.d.ts +30 -0
- package/dist/ui/model-warning-overlay.js +171 -0
- package/dist/ui/overlay-controller.d.ts +25 -0
- package/dist/ui/overlay-controller.js +35 -0
- package/dist/ui/overlays.d.ts +47 -0
- package/dist/ui/overlays.js +627 -0
- package/dist/ui/permission-overlay.d.ts +16 -0
- package/dist/ui/permission-overlay.js +494 -0
- package/dist/ui/terminal.d.ts +117 -0
- package/dist/ui/terminal.js +237 -0
- package/dist/ui/todo-zone.d.ts +112 -0
- package/dist/ui/todo-zone.js +353 -0
- package/dist/ui/tools-overlay.d.ts +26 -0
- package/dist/ui/tools-overlay.js +278 -0
- package/dist/ui/tutorial-overlay.d.ts +10 -0
- package/dist/ui/tutorial-overlay.js +936 -0
- package/dist/ui/types.d.ts +103 -0
- package/dist/ui/types.js +33 -0
- package/dist/utils/credentials.d.ts +55 -0
- package/dist/utils/credentials.js +268 -0
- package/dist/utils/model-tiers.d.ts +37 -0
- package/dist/utils/model-tiers.js +118 -0
- package/dist/utils/project-memory.d.ts +47 -0
- package/dist/utils/project-memory.js +117 -0
- package/dist/utils/project-status.d.ts +56 -0
- package/dist/utils/project-status.js +237 -0
- package/package.json +66 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Prompt
|
|
3
|
+
*
|
|
4
|
+
* @deprecated This module is superseded by input-prompt-v2.ts which uses
|
|
5
|
+
* event-driven architecture for non-blocking input with queue mode support.
|
|
6
|
+
* This file is kept for reference and may be removed in a future cleanup.
|
|
7
|
+
*
|
|
8
|
+
* Self-contained input handling with multiline support, autocomplete,
|
|
9
|
+
* history navigation, and proper visual line navigation.
|
|
10
|
+
*/
|
|
11
|
+
import pc from 'picocolors';
|
|
12
|
+
import * as terminal from './terminal.js';
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Constants
|
|
15
|
+
// =============================================================================
|
|
16
|
+
const MAX_VISIBLE_COMMANDS = 10;
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Default Commands
|
|
19
|
+
// =============================================================================
|
|
20
|
+
export const DEFAULT_COMMANDS = [
|
|
21
|
+
{ command: '/help', description: 'Show available commands' },
|
|
22
|
+
{ command: '/exit', description: 'Quit the demo' },
|
|
23
|
+
{ command: '/clear', description: 'Clear conversation history' },
|
|
24
|
+
{ command: '/compact', description: 'Summarize old messages' },
|
|
25
|
+
{ command: '/tools', description: 'List available tools' },
|
|
26
|
+
{ command: '/tokens', description: 'Show session token usage' },
|
|
27
|
+
{ command: '/context', description: 'Show context window usage' },
|
|
28
|
+
];
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Helper Functions
|
|
31
|
+
// =============================================================================
|
|
32
|
+
/**
|
|
33
|
+
* Strip ANSI codes from string
|
|
34
|
+
*/
|
|
35
|
+
export function stripAnsi(str) {
|
|
36
|
+
// eslint-disable-next-line no-control-regex
|
|
37
|
+
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Calculate physical lines for wrapped text
|
|
41
|
+
*/
|
|
42
|
+
function calcPhysicalLines(text, startCol, termWidth) {
|
|
43
|
+
if (text.length === 0)
|
|
44
|
+
return 1;
|
|
45
|
+
const totalLen = startCol + text.length;
|
|
46
|
+
return Math.ceil(totalLen / termWidth) || 1;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Filter commands matching input
|
|
50
|
+
*/
|
|
51
|
+
function filterCommands(input, commands) {
|
|
52
|
+
const lower = input.toLowerCase();
|
|
53
|
+
return commands.filter((cmd) => cmd.command.toLowerCase().startsWith(lower));
|
|
54
|
+
}
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Input Prompt Class
|
|
57
|
+
// =============================================================================
|
|
58
|
+
export class InputPrompt {
|
|
59
|
+
// Configuration
|
|
60
|
+
prompt;
|
|
61
|
+
promptLen;
|
|
62
|
+
showSeparators;
|
|
63
|
+
commands;
|
|
64
|
+
getTodos;
|
|
65
|
+
// Input state
|
|
66
|
+
state = {
|
|
67
|
+
lines: [''],
|
|
68
|
+
currentLine: 0,
|
|
69
|
+
cursorPos: 0,
|
|
70
|
+
};
|
|
71
|
+
// Autocomplete
|
|
72
|
+
autocomplete = {
|
|
73
|
+
active: false,
|
|
74
|
+
matches: [],
|
|
75
|
+
selectedIndex: 0,
|
|
76
|
+
};
|
|
77
|
+
// History
|
|
78
|
+
history = [];
|
|
79
|
+
historyIndex = -1;
|
|
80
|
+
savedInput = '';
|
|
81
|
+
// Rendering tracking
|
|
82
|
+
renderedLines = 0;
|
|
83
|
+
dropdownLines = 0;
|
|
84
|
+
linesAboveCursor = 0;
|
|
85
|
+
hasSeparators = false;
|
|
86
|
+
// Control
|
|
87
|
+
isRunning = false;
|
|
88
|
+
resolveInput = null;
|
|
89
|
+
constructor(options = {}) {
|
|
90
|
+
this.prompt = options.prompt ?? pc.cyan('❯ ');
|
|
91
|
+
this.promptLen = stripAnsi(this.prompt).length;
|
|
92
|
+
this.showSeparators = options.showSeparators ?? true;
|
|
93
|
+
this.commands = options.commands ?? DEFAULT_COMMANDS;
|
|
94
|
+
this.getTodos = options.getTodos;
|
|
95
|
+
}
|
|
96
|
+
// ===========================================================================
|
|
97
|
+
// Public API
|
|
98
|
+
// ===========================================================================
|
|
99
|
+
/**
|
|
100
|
+
* Get input from user (async)
|
|
101
|
+
* Returns when user submits or cancels
|
|
102
|
+
*/
|
|
103
|
+
async getInput() {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
this.resolveInput = resolve;
|
|
106
|
+
this.start();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Start input mode
|
|
111
|
+
*/
|
|
112
|
+
start() {
|
|
113
|
+
if (this.isRunning)
|
|
114
|
+
return;
|
|
115
|
+
this.isRunning = true;
|
|
116
|
+
terminal.enableRawMode();
|
|
117
|
+
this.resetState();
|
|
118
|
+
this.render();
|
|
119
|
+
process.stdin.on('data', this.handleData);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Stop input mode
|
|
123
|
+
*/
|
|
124
|
+
stop() {
|
|
125
|
+
this.isRunning = false;
|
|
126
|
+
terminal.disableRawMode();
|
|
127
|
+
process.stdin.removeListener('data', this.handleData);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get current buffer value
|
|
131
|
+
*/
|
|
132
|
+
getValue() {
|
|
133
|
+
return this.state.lines.join('\n');
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Set buffer value
|
|
137
|
+
*/
|
|
138
|
+
setValue(value) {
|
|
139
|
+
this.state.lines = value.split('\n');
|
|
140
|
+
this.state.currentLine = this.state.lines.length - 1;
|
|
141
|
+
this.state.cursorPos = this.state.lines[this.state.currentLine].length;
|
|
142
|
+
if (this.isRunning) {
|
|
143
|
+
this.updateAutocomplete();
|
|
144
|
+
this.render();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Clear the rendered area
|
|
149
|
+
*/
|
|
150
|
+
clear() {
|
|
151
|
+
this.clearDropdown();
|
|
152
|
+
if (this.linesAboveCursor > 0) {
|
|
153
|
+
terminal.moveCursorToLineStart();
|
|
154
|
+
terminal.moveCursorUp(this.linesAboveCursor);
|
|
155
|
+
}
|
|
156
|
+
terminal.clearToEndOfScreen();
|
|
157
|
+
this.renderedLines = 0;
|
|
158
|
+
this.linesAboveCursor = 0;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get number of rendered lines
|
|
162
|
+
*/
|
|
163
|
+
getRenderedLines() {
|
|
164
|
+
return this.renderedLines + this.dropdownLines + (this.showSeparators ? 2 : 0);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Add command to history
|
|
168
|
+
*/
|
|
169
|
+
addToHistory(input) {
|
|
170
|
+
if (input.trim() && (this.history.length === 0 || this.history[this.history.length - 1] !== input)) {
|
|
171
|
+
this.history.push(input);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ===========================================================================
|
|
175
|
+
// Private: State Management
|
|
176
|
+
// ===========================================================================
|
|
177
|
+
resetState() {
|
|
178
|
+
this.state = {
|
|
179
|
+
lines: [''],
|
|
180
|
+
currentLine: 0,
|
|
181
|
+
cursorPos: 0,
|
|
182
|
+
};
|
|
183
|
+
this.autocomplete = {
|
|
184
|
+
active: false,
|
|
185
|
+
matches: [],
|
|
186
|
+
selectedIndex: 0,
|
|
187
|
+
};
|
|
188
|
+
this.historyIndex = -1;
|
|
189
|
+
this.savedInput = '';
|
|
190
|
+
this.renderedLines = 0;
|
|
191
|
+
this.dropdownLines = 0;
|
|
192
|
+
this.linesAboveCursor = 0;
|
|
193
|
+
this.hasSeparators = false;
|
|
194
|
+
}
|
|
195
|
+
updateAutocomplete() {
|
|
196
|
+
const fullInput = this.getValue();
|
|
197
|
+
if (fullInput.startsWith('/') && this.state.lines.length === 1) {
|
|
198
|
+
this.autocomplete.active = true;
|
|
199
|
+
this.autocomplete.matches = filterCommands(fullInput, this.commands);
|
|
200
|
+
if (this.autocomplete.selectedIndex >= this.autocomplete.matches.length) {
|
|
201
|
+
this.autocomplete.selectedIndex = Math.max(0, this.autocomplete.matches.length - 1);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.autocomplete.active = false;
|
|
206
|
+
this.autocomplete.matches = [];
|
|
207
|
+
this.autocomplete.selectedIndex = 0;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// ===========================================================================
|
|
211
|
+
// Private: Layout Calculation
|
|
212
|
+
// ===========================================================================
|
|
213
|
+
/**
|
|
214
|
+
* Get physical layout of current buffer
|
|
215
|
+
*/
|
|
216
|
+
getPhysicalLayout() {
|
|
217
|
+
const termWidth = terminal.getTerminalWidth();
|
|
218
|
+
const lines = [];
|
|
219
|
+
let cursorRow = 0;
|
|
220
|
+
let cursorCol = 0;
|
|
221
|
+
let totalRows = 0;
|
|
222
|
+
for (let i = 0; i < this.state.lines.length; i++) {
|
|
223
|
+
const linePromptLen = i === 0 ? this.promptLen : 5; // " \ "
|
|
224
|
+
const lineText = this.state.lines[i];
|
|
225
|
+
const physicalLines = calcPhysicalLines(lineText, linePromptLen, termWidth);
|
|
226
|
+
for (let p = 0; p < physicalLines; p++) {
|
|
227
|
+
const start = p * termWidth - (p === 0 ? 0 : linePromptLen);
|
|
228
|
+
const end = start + termWidth;
|
|
229
|
+
lines.push(lineText.slice(Math.max(0, start), end));
|
|
230
|
+
}
|
|
231
|
+
if (i === this.state.currentLine) {
|
|
232
|
+
const cursorAbsPos = linePromptLen + this.state.cursorPos;
|
|
233
|
+
cursorRow = totalRows + Math.floor(cursorAbsPos / termWidth);
|
|
234
|
+
cursorCol = cursorAbsPos % termWidth;
|
|
235
|
+
}
|
|
236
|
+
totalRows += physicalLines;
|
|
237
|
+
}
|
|
238
|
+
return { lines, cursorRow, cursorCol, totalRows };
|
|
239
|
+
}
|
|
240
|
+
// ===========================================================================
|
|
241
|
+
// Private: Rendering
|
|
242
|
+
// ===========================================================================
|
|
243
|
+
getSeparatorLine() {
|
|
244
|
+
return pc.dim('─'.repeat(terminal.getTerminalWidth()));
|
|
245
|
+
}
|
|
246
|
+
renderTodos() {
|
|
247
|
+
if (!this.getTodos)
|
|
248
|
+
return 0;
|
|
249
|
+
const todos = this.getTodos();
|
|
250
|
+
if (todos.length === 0)
|
|
251
|
+
return 0;
|
|
252
|
+
let linesRendered = 0;
|
|
253
|
+
// Group todos by status for display
|
|
254
|
+
const inProgress = todos.filter(t => t.status === 'in_progress');
|
|
255
|
+
const pending = todos.filter(t => t.status === 'pending');
|
|
256
|
+
const completed = todos.filter(t => t.status === 'completed');
|
|
257
|
+
// Show in-progress task prominently
|
|
258
|
+
for (const todo of inProgress) {
|
|
259
|
+
const label = todo.activeForm || todo.content;
|
|
260
|
+
terminal.write(pc.cyan('⟳ ') + pc.bold(label) + '\n');
|
|
261
|
+
linesRendered++;
|
|
262
|
+
}
|
|
263
|
+
// Show pending tasks
|
|
264
|
+
for (const todo of pending) {
|
|
265
|
+
terminal.write(pc.dim('○ ') + pc.dim(todo.content) + '\n');
|
|
266
|
+
linesRendered++;
|
|
267
|
+
}
|
|
268
|
+
// Show completed tasks (dimmed)
|
|
269
|
+
for (const todo of completed) {
|
|
270
|
+
terminal.write(pc.green('✓ ') + pc.dim(pc.strikethrough(todo.content)) + '\n');
|
|
271
|
+
linesRendered++;
|
|
272
|
+
}
|
|
273
|
+
if (linesRendered > 0) {
|
|
274
|
+
terminal.write('\n');
|
|
275
|
+
linesRendered++;
|
|
276
|
+
}
|
|
277
|
+
return linesRendered;
|
|
278
|
+
}
|
|
279
|
+
render() {
|
|
280
|
+
const termWidth = terminal.getTerminalWidth();
|
|
281
|
+
// Clear previous render
|
|
282
|
+
this.clearDropdown();
|
|
283
|
+
if (this.linesAboveCursor > 0) {
|
|
284
|
+
terminal.moveCursorToLineStart();
|
|
285
|
+
terminal.moveCursorUp(this.linesAboveCursor);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
terminal.moveCursorToLineStart();
|
|
289
|
+
}
|
|
290
|
+
terminal.clearToEndOfScreen();
|
|
291
|
+
// Render todos (above input area)
|
|
292
|
+
let physicalLinesRendered = 0;
|
|
293
|
+
physicalLinesRendered += this.renderTodos();
|
|
294
|
+
// Render top separator
|
|
295
|
+
if (this.showSeparators) {
|
|
296
|
+
terminal.write(this.getSeparatorLine() + '\n');
|
|
297
|
+
physicalLinesRendered += 1;
|
|
298
|
+
}
|
|
299
|
+
// Render input lines
|
|
300
|
+
let physicalLinesBeforeCursor = 0;
|
|
301
|
+
for (let i = 0; i < this.state.lines.length; i++) {
|
|
302
|
+
const linePrompt = i === 0 ? this.prompt : pc.dim(' \\ ');
|
|
303
|
+
const linePromptLen = i === 0 ? this.promptLen : 5;
|
|
304
|
+
if (i < this.state.currentLine) {
|
|
305
|
+
physicalLinesBeforeCursor += calcPhysicalLines(this.state.lines[i], linePromptLen, termWidth);
|
|
306
|
+
}
|
|
307
|
+
terminal.write(linePrompt + this.state.lines[i]);
|
|
308
|
+
if (i < this.state.lines.length - 1) {
|
|
309
|
+
terminal.write('\n');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Render bottom separator
|
|
313
|
+
if (this.showSeparators) {
|
|
314
|
+
terminal.write('\n' + this.getSeparatorLine());
|
|
315
|
+
}
|
|
316
|
+
// Track state
|
|
317
|
+
this.renderedLines = this.state.lines.length;
|
|
318
|
+
this.hasSeparators = this.showSeparators;
|
|
319
|
+
// Position cursor
|
|
320
|
+
const currentLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
|
|
321
|
+
const cursorAbsPos = currentLinePromptLen + this.state.cursorPos;
|
|
322
|
+
const currentLinePhysical = calcPhysicalLines(this.state.lines[this.state.currentLine], currentLinePromptLen, termWidth);
|
|
323
|
+
const cursorPhysicalRow = Math.floor(cursorAbsPos / termWidth);
|
|
324
|
+
// Move up from bottom to cursor position
|
|
325
|
+
let linesToMoveUp = this.showSeparators ? 1 : 0;
|
|
326
|
+
for (let i = this.state.currentLine + 1; i < this.state.lines.length; i++) {
|
|
327
|
+
const lp = i === 0 ? this.promptLen : 5;
|
|
328
|
+
linesToMoveUp += calcPhysicalLines(this.state.lines[i], lp, termWidth);
|
|
329
|
+
}
|
|
330
|
+
linesToMoveUp += currentLinePhysical - 1 - cursorPhysicalRow;
|
|
331
|
+
if (linesToMoveUp > 0) {
|
|
332
|
+
terminal.moveCursorUp(linesToMoveUp);
|
|
333
|
+
}
|
|
334
|
+
const cursorCol = (cursorAbsPos % termWidth) + 1;
|
|
335
|
+
terminal.moveCursorToColumn(cursorCol);
|
|
336
|
+
// Track cursor position for next render
|
|
337
|
+
this.linesAboveCursor = physicalLinesRendered + physicalLinesBeforeCursor + cursorPhysicalRow;
|
|
338
|
+
// Render dropdown
|
|
339
|
+
this.renderDropdown();
|
|
340
|
+
}
|
|
341
|
+
renderDropdown() {
|
|
342
|
+
if (!this.autocomplete.active || this.autocomplete.matches.length === 0) {
|
|
343
|
+
this.dropdownLines = 0;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const termWidth = terminal.getTerminalWidth();
|
|
347
|
+
const currentLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
|
|
348
|
+
// Move down past remaining input lines and bottom separator
|
|
349
|
+
let linesToMoveDown = this.state.lines.length - 1 - this.state.currentLine;
|
|
350
|
+
if (this.hasSeparators)
|
|
351
|
+
linesToMoveDown += 1;
|
|
352
|
+
// Account for wrapped lines
|
|
353
|
+
for (let i = this.state.currentLine; i < this.state.lines.length; i++) {
|
|
354
|
+
const lp = i === 0 ? this.promptLen : 5;
|
|
355
|
+
const physical = calcPhysicalLines(this.state.lines[i], lp, termWidth);
|
|
356
|
+
if (i === this.state.currentLine) {
|
|
357
|
+
const cursorAbsPos = currentLinePromptLen + this.state.cursorPos;
|
|
358
|
+
const cursorRow = Math.floor(cursorAbsPos / termWidth);
|
|
359
|
+
linesToMoveDown += physical - 1 - cursorRow;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
linesToMoveDown += physical - 1;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (linesToMoveDown > 0) {
|
|
366
|
+
terminal.moveCursorDown(linesToMoveDown);
|
|
367
|
+
}
|
|
368
|
+
terminal.write('\n');
|
|
369
|
+
const visible = this.autocomplete.matches.slice(0, MAX_VISIBLE_COMMANDS);
|
|
370
|
+
for (let i = 0; i < visible.length; i++) {
|
|
371
|
+
const cmd = visible[i];
|
|
372
|
+
const isSelected = i === this.autocomplete.selectedIndex;
|
|
373
|
+
const prefix = isSelected ? pc.cyan('❯ ') : ' ';
|
|
374
|
+
const name = isSelected ? pc.cyan(pc.bold(cmd.command)) : cmd.command;
|
|
375
|
+
const desc = pc.dim(` - ${cmd.description}`);
|
|
376
|
+
terminal.write(`${prefix}${name}${desc}\n`);
|
|
377
|
+
}
|
|
378
|
+
// Move back up to cursor position
|
|
379
|
+
this.dropdownLines = visible.length;
|
|
380
|
+
const linesToMoveUp = this.dropdownLines + linesToMoveDown + 1;
|
|
381
|
+
terminal.moveCursorUp(linesToMoveUp);
|
|
382
|
+
const cursorAbsPos = currentLinePromptLen + this.state.cursorPos;
|
|
383
|
+
const cursorCol = (cursorAbsPos % termWidth) + 1;
|
|
384
|
+
terminal.moveCursorToColumn(cursorCol);
|
|
385
|
+
}
|
|
386
|
+
clearDropdown() {
|
|
387
|
+
if (this.dropdownLines === 0)
|
|
388
|
+
return;
|
|
389
|
+
const termWidth = terminal.getTerminalWidth();
|
|
390
|
+
const currentLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
|
|
391
|
+
// Move down past remaining lines and separator
|
|
392
|
+
let linesToMoveDown = this.state.lines.length - 1 - this.state.currentLine;
|
|
393
|
+
if (this.hasSeparators)
|
|
394
|
+
linesToMoveDown += 1;
|
|
395
|
+
for (let i = this.state.currentLine; i < this.state.lines.length; i++) {
|
|
396
|
+
const lp = i === 0 ? this.promptLen : 5;
|
|
397
|
+
const physical = calcPhysicalLines(this.state.lines[i], lp, termWidth);
|
|
398
|
+
if (i === this.state.currentLine) {
|
|
399
|
+
const cursorAbsPos = currentLinePromptLen + this.state.cursorPos;
|
|
400
|
+
const cursorRow = Math.floor(cursorAbsPos / termWidth);
|
|
401
|
+
linesToMoveDown += physical - 1 - cursorRow;
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
linesToMoveDown += physical - 1;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (linesToMoveDown > 0) {
|
|
408
|
+
terminal.moveCursorDown(linesToMoveDown);
|
|
409
|
+
}
|
|
410
|
+
terminal.write('\n');
|
|
411
|
+
// Clear dropdown lines
|
|
412
|
+
for (let i = 0; i < this.dropdownLines; i++) {
|
|
413
|
+
terminal.clearLine();
|
|
414
|
+
terminal.write('\n');
|
|
415
|
+
}
|
|
416
|
+
// Move back up
|
|
417
|
+
const linesToMoveUp = this.dropdownLines + linesToMoveDown + 1;
|
|
418
|
+
terminal.moveCursorUp(linesToMoveUp);
|
|
419
|
+
this.dropdownLines = 0;
|
|
420
|
+
}
|
|
421
|
+
// ===========================================================================
|
|
422
|
+
// Private: Input Handling
|
|
423
|
+
// ===========================================================================
|
|
424
|
+
handleData = (data) => {
|
|
425
|
+
if (!this.isRunning)
|
|
426
|
+
return;
|
|
427
|
+
const key = data.toString();
|
|
428
|
+
this.handleKey(key, data);
|
|
429
|
+
};
|
|
430
|
+
handleKey(key, data) {
|
|
431
|
+
// Detect special keys
|
|
432
|
+
const isEscape = data.length === 1 && data[0] === 0x1b;
|
|
433
|
+
const isEnter = key === '\r' || key === '\n';
|
|
434
|
+
const isBackspace = key === '\x7f' || key === '\b';
|
|
435
|
+
const isTab = key === '\t';
|
|
436
|
+
const isCtrlC = key === '\x03';
|
|
437
|
+
const isUpArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41;
|
|
438
|
+
const isDownArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42;
|
|
439
|
+
const isLeftArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x44;
|
|
440
|
+
const isRightArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x43;
|
|
441
|
+
// Mac Option + Arrow (word navigation)
|
|
442
|
+
const isOptionLeft = (data.length === 6 &&
|
|
443
|
+
data[0] === 0x1b &&
|
|
444
|
+
data[1] === 0x5b &&
|
|
445
|
+
data[2] === 0x31 &&
|
|
446
|
+
data[3] === 0x3b &&
|
|
447
|
+
data[4] === 0x33 &&
|
|
448
|
+
data[5] === 0x44) ||
|
|
449
|
+
(data.length === 2 && data[0] === 0x1b && data[1] === 0x62);
|
|
450
|
+
const isOptionRight = (data.length === 6 &&
|
|
451
|
+
data[0] === 0x1b &&
|
|
452
|
+
data[1] === 0x5b &&
|
|
453
|
+
data[2] === 0x31 &&
|
|
454
|
+
data[3] === 0x3b &&
|
|
455
|
+
data[4] === 0x33 &&
|
|
456
|
+
data[5] === 0x43) ||
|
|
457
|
+
(data.length === 2 && data[0] === 0x1b && data[1] === 0x66);
|
|
458
|
+
// Home/End (Cmd+Left/Right on Mac, or Home/End keys)
|
|
459
|
+
const isHome = key === '\x01' || (data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x48);
|
|
460
|
+
const isEnd = key === '\x05' || (data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x46);
|
|
461
|
+
// Ctrl+C - cancel
|
|
462
|
+
if (isCtrlC) {
|
|
463
|
+
this.finishInput({ action: 'cancel' });
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
// Escape - close autocomplete
|
|
467
|
+
if (isEscape) {
|
|
468
|
+
if (this.autocomplete.active) {
|
|
469
|
+
this.clearDropdown();
|
|
470
|
+
this.autocomplete.active = false;
|
|
471
|
+
this.autocomplete.matches = [];
|
|
472
|
+
this.render();
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
// Enter
|
|
477
|
+
if (isEnter) {
|
|
478
|
+
this.handleEnter();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
// Tab - accept autocomplete or insert spaces
|
|
482
|
+
if (isTab) {
|
|
483
|
+
this.handleTab();
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
// Arrow keys
|
|
487
|
+
if (isUpArrow) {
|
|
488
|
+
this.handleArrowUp();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (isDownArrow) {
|
|
492
|
+
this.handleArrowDown();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (isLeftArrow) {
|
|
496
|
+
this.handleArrowLeft();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (isRightArrow) {
|
|
500
|
+
this.handleArrowRight();
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
// Word navigation
|
|
504
|
+
if (isOptionLeft) {
|
|
505
|
+
this.handleWordLeft();
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (isOptionRight) {
|
|
509
|
+
this.handleWordRight();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
// Home/End
|
|
513
|
+
if (isHome) {
|
|
514
|
+
this.state.cursorPos = 0;
|
|
515
|
+
this.render();
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (isEnd) {
|
|
519
|
+
this.state.cursorPos = this.state.lines[this.state.currentLine].length;
|
|
520
|
+
this.render();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
// Backspace
|
|
524
|
+
if (isBackspace) {
|
|
525
|
+
this.handleBackspace();
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
// Regular character(s) - handles typing and paste
|
|
529
|
+
if (key.length >= 1) {
|
|
530
|
+
const printable = key
|
|
531
|
+
.split('')
|
|
532
|
+
.filter((c) => c.charCodeAt(0) >= 32)
|
|
533
|
+
.join('');
|
|
534
|
+
if (printable.length > 0) {
|
|
535
|
+
const line = this.state.lines[this.state.currentLine];
|
|
536
|
+
this.state.lines[this.state.currentLine] =
|
|
537
|
+
line.slice(0, this.state.cursorPos) + printable + line.slice(this.state.cursorPos);
|
|
538
|
+
this.state.cursorPos += printable.length;
|
|
539
|
+
this.historyIndex = -1;
|
|
540
|
+
this.updateAutocomplete();
|
|
541
|
+
this.render();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
handleEnter() {
|
|
546
|
+
const currentLine = this.state.lines[this.state.currentLine];
|
|
547
|
+
// Continuation with backslash
|
|
548
|
+
if (currentLine.endsWith('\\')) {
|
|
549
|
+
this.state.lines[this.state.currentLine] = currentLine.slice(0, -1);
|
|
550
|
+
this.state.lines.push('');
|
|
551
|
+
this.state.currentLine++;
|
|
552
|
+
this.state.cursorPos = 0;
|
|
553
|
+
this.autocomplete.active = false;
|
|
554
|
+
this.clearDropdown();
|
|
555
|
+
this.render();
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
// Autocomplete selection
|
|
559
|
+
if (this.autocomplete.active && this.autocomplete.matches.length > 0) {
|
|
560
|
+
this.state.lines[0] = this.autocomplete.matches[this.autocomplete.selectedIndex].command;
|
|
561
|
+
this.state.cursorPos = this.state.lines[0].length;
|
|
562
|
+
}
|
|
563
|
+
const input = this.getValue();
|
|
564
|
+
// Check if it's a command
|
|
565
|
+
if (input.startsWith('/')) {
|
|
566
|
+
const parts = input.slice(1).split(/\s+/);
|
|
567
|
+
const command = parts[0];
|
|
568
|
+
const args = parts.slice(1).join(' ');
|
|
569
|
+
this.finishInput({ action: 'command', command, args });
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
this.finishInput({ action: 'submit', value: input });
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
handleTab() {
|
|
576
|
+
if (this.autocomplete.active && this.autocomplete.matches.length > 0) {
|
|
577
|
+
this.state.lines[this.state.currentLine] =
|
|
578
|
+
this.autocomplete.matches[this.autocomplete.selectedIndex].command;
|
|
579
|
+
this.state.cursorPos = this.state.lines[this.state.currentLine].length;
|
|
580
|
+
this.autocomplete.active = false;
|
|
581
|
+
this.autocomplete.matches = [];
|
|
582
|
+
this.clearDropdown();
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
// Insert 2 spaces
|
|
586
|
+
const line = this.state.lines[this.state.currentLine];
|
|
587
|
+
this.state.lines[this.state.currentLine] =
|
|
588
|
+
line.slice(0, this.state.cursorPos) + ' ' + line.slice(this.state.cursorPos);
|
|
589
|
+
this.state.cursorPos += 2;
|
|
590
|
+
this.historyIndex = -1;
|
|
591
|
+
this.updateAutocomplete();
|
|
592
|
+
}
|
|
593
|
+
this.render();
|
|
594
|
+
}
|
|
595
|
+
handleArrowUp() {
|
|
596
|
+
// Autocomplete navigation
|
|
597
|
+
if (this.autocomplete.active && this.autocomplete.selectedIndex > 0) {
|
|
598
|
+
this.autocomplete.selectedIndex--;
|
|
599
|
+
this.render();
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const termWidth = terminal.getTerminalWidth();
|
|
603
|
+
const currentLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
|
|
604
|
+
const cursorAbsPos = currentLinePromptLen + this.state.cursorPos;
|
|
605
|
+
const currentPhysicalRow = Math.floor(cursorAbsPos / termWidth);
|
|
606
|
+
const cursorColInRow = cursorAbsPos % termWidth;
|
|
607
|
+
// Navigate within wrapped line
|
|
608
|
+
if (currentPhysicalRow > 0) {
|
|
609
|
+
const newAbsPos = (currentPhysicalRow - 1) * termWidth + cursorColInRow;
|
|
610
|
+
this.state.cursorPos = Math.max(0, newAbsPos - currentLinePromptLen);
|
|
611
|
+
this.state.cursorPos = Math.min(this.state.cursorPos, this.state.lines[this.state.currentLine].length);
|
|
612
|
+
this.render();
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
// Navigate to previous logical line
|
|
616
|
+
if (this.state.currentLine > 0) {
|
|
617
|
+
this.state.currentLine--;
|
|
618
|
+
const prevLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
|
|
619
|
+
const prevLineLen = this.state.lines[this.state.currentLine].length;
|
|
620
|
+
const prevLinePhysical = calcPhysicalLines(this.state.lines[this.state.currentLine], prevLinePromptLen, termWidth);
|
|
621
|
+
const lastRowStart = (prevLinePhysical - 1) * termWidth;
|
|
622
|
+
const targetAbsPos = lastRowStart + cursorColInRow;
|
|
623
|
+
this.state.cursorPos = Math.max(0, targetAbsPos - prevLinePromptLen);
|
|
624
|
+
this.state.cursorPos = Math.min(this.state.cursorPos, prevLineLen);
|
|
625
|
+
this.render();
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
// History navigation (at top of input)
|
|
629
|
+
if (!this.autocomplete.active && this.history.length > 0) {
|
|
630
|
+
if (this.historyIndex === -1) {
|
|
631
|
+
this.savedInput = this.getValue();
|
|
632
|
+
}
|
|
633
|
+
if (this.historyIndex < this.history.length - 1) {
|
|
634
|
+
this.historyIndex++;
|
|
635
|
+
const historyEntry = this.history[this.history.length - 1 - this.historyIndex];
|
|
636
|
+
this.state.lines = [historyEntry];
|
|
637
|
+
this.state.currentLine = 0;
|
|
638
|
+
this.state.cursorPos = historyEntry.length;
|
|
639
|
+
this.render();
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
handleArrowDown() {
|
|
644
|
+
// Autocomplete navigation
|
|
645
|
+
if (this.autocomplete.active &&
|
|
646
|
+
this.autocomplete.selectedIndex < this.autocomplete.matches.length - 1 &&
|
|
647
|
+
this.autocomplete.selectedIndex < MAX_VISIBLE_COMMANDS - 1) {
|
|
648
|
+
this.autocomplete.selectedIndex++;
|
|
649
|
+
this.render();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const termWidth = terminal.getTerminalWidth();
|
|
653
|
+
const currentLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
|
|
654
|
+
const cursorAbsPos = currentLinePromptLen + this.state.cursorPos;
|
|
655
|
+
const currentPhysicalRow = Math.floor(cursorAbsPos / termWidth);
|
|
656
|
+
const cursorColInRow = cursorAbsPos % termWidth;
|
|
657
|
+
const currentLinePhysical = calcPhysicalLines(this.state.lines[this.state.currentLine], currentLinePromptLen, termWidth);
|
|
658
|
+
// Navigate within wrapped line
|
|
659
|
+
if (currentPhysicalRow < currentLinePhysical - 1) {
|
|
660
|
+
const newAbsPos = (currentPhysicalRow + 1) * termWidth + cursorColInRow;
|
|
661
|
+
this.state.cursorPos = Math.max(0, newAbsPos - currentLinePromptLen);
|
|
662
|
+
this.state.cursorPos = Math.min(this.state.cursorPos, this.state.lines[this.state.currentLine].length);
|
|
663
|
+
this.render();
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
// Navigate to next logical line
|
|
667
|
+
if (this.state.currentLine < this.state.lines.length - 1) {
|
|
668
|
+
this.state.currentLine++;
|
|
669
|
+
const nextLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
|
|
670
|
+
const nextLineLen = this.state.lines[this.state.currentLine].length;
|
|
671
|
+
this.state.cursorPos = Math.max(0, cursorColInRow - nextLinePromptLen);
|
|
672
|
+
this.state.cursorPos = Math.min(this.state.cursorPos, nextLineLen);
|
|
673
|
+
this.render();
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
// History forward
|
|
677
|
+
if (this.historyIndex >= 0) {
|
|
678
|
+
this.historyIndex--;
|
|
679
|
+
if (this.historyIndex === -1) {
|
|
680
|
+
this.state.lines = this.savedInput.split('\n');
|
|
681
|
+
this.state.currentLine = this.state.lines.length - 1;
|
|
682
|
+
this.state.cursorPos = this.state.lines[this.state.currentLine].length;
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
const historyEntry = this.history[this.history.length - 1 - this.historyIndex];
|
|
686
|
+
this.state.lines = [historyEntry];
|
|
687
|
+
this.state.currentLine = 0;
|
|
688
|
+
this.state.cursorPos = historyEntry.length;
|
|
689
|
+
}
|
|
690
|
+
this.updateAutocomplete();
|
|
691
|
+
this.render();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
handleArrowLeft() {
|
|
695
|
+
if (this.state.cursorPos > 0) {
|
|
696
|
+
this.state.cursorPos--;
|
|
697
|
+
terminal.write('\x1B[D');
|
|
698
|
+
}
|
|
699
|
+
else if (this.state.currentLine > 0) {
|
|
700
|
+
this.state.currentLine--;
|
|
701
|
+
this.state.cursorPos = this.state.lines[this.state.currentLine].length;
|
|
702
|
+
this.render();
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
handleArrowRight() {
|
|
706
|
+
if (this.state.cursorPos < this.state.lines[this.state.currentLine].length) {
|
|
707
|
+
this.state.cursorPos++;
|
|
708
|
+
terminal.write('\x1B[C');
|
|
709
|
+
}
|
|
710
|
+
else if (this.state.currentLine < this.state.lines.length - 1) {
|
|
711
|
+
this.state.currentLine++;
|
|
712
|
+
this.state.cursorPos = 0;
|
|
713
|
+
this.render();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
handleWordLeft() {
|
|
717
|
+
const line = this.state.lines[this.state.currentLine];
|
|
718
|
+
if (this.state.cursorPos > 0) {
|
|
719
|
+
let pos = this.state.cursorPos;
|
|
720
|
+
while (pos > 0 && line[pos - 1] === ' ')
|
|
721
|
+
pos--;
|
|
722
|
+
while (pos > 0 && line[pos - 1] !== ' ')
|
|
723
|
+
pos--;
|
|
724
|
+
this.state.cursorPos = pos;
|
|
725
|
+
this.render();
|
|
726
|
+
}
|
|
727
|
+
else if (this.state.currentLine > 0) {
|
|
728
|
+
this.state.currentLine--;
|
|
729
|
+
this.state.cursorPos = this.state.lines[this.state.currentLine].length;
|
|
730
|
+
this.render();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
handleWordRight() {
|
|
734
|
+
const line = this.state.lines[this.state.currentLine];
|
|
735
|
+
if (this.state.cursorPos < line.length) {
|
|
736
|
+
let pos = this.state.cursorPos;
|
|
737
|
+
while (pos < line.length && line[pos] !== ' ')
|
|
738
|
+
pos++;
|
|
739
|
+
while (pos < line.length && line[pos] === ' ')
|
|
740
|
+
pos++;
|
|
741
|
+
this.state.cursorPos = pos;
|
|
742
|
+
this.render();
|
|
743
|
+
}
|
|
744
|
+
else if (this.state.currentLine < this.state.lines.length - 1) {
|
|
745
|
+
this.state.currentLine++;
|
|
746
|
+
this.state.cursorPos = 0;
|
|
747
|
+
this.render();
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
handleBackspace() {
|
|
751
|
+
this.historyIndex = -1;
|
|
752
|
+
if (this.state.cursorPos > 0) {
|
|
753
|
+
const line = this.state.lines[this.state.currentLine];
|
|
754
|
+
this.state.lines[this.state.currentLine] =
|
|
755
|
+
line.slice(0, this.state.cursorPos - 1) + line.slice(this.state.cursorPos);
|
|
756
|
+
this.state.cursorPos--;
|
|
757
|
+
this.updateAutocomplete();
|
|
758
|
+
this.render();
|
|
759
|
+
}
|
|
760
|
+
else if (this.state.currentLine > 0) {
|
|
761
|
+
const currentLine = this.state.lines[this.state.currentLine];
|
|
762
|
+
const prevLine = this.state.lines[this.state.currentLine - 1];
|
|
763
|
+
this.state.lines[this.state.currentLine - 1] = prevLine + currentLine;
|
|
764
|
+
this.state.lines.splice(this.state.currentLine, 1);
|
|
765
|
+
this.state.currentLine--;
|
|
766
|
+
this.state.cursorPos = prevLine.length;
|
|
767
|
+
this.updateAutocomplete();
|
|
768
|
+
this.render();
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
finishInput(result) {
|
|
772
|
+
// Clear display
|
|
773
|
+
this.clearDropdown();
|
|
774
|
+
if (this.linesAboveCursor > 0) {
|
|
775
|
+
terminal.moveCursorToLineStart();
|
|
776
|
+
terminal.moveCursorUp(this.linesAboveCursor);
|
|
777
|
+
}
|
|
778
|
+
terminal.clearToEndOfScreen();
|
|
779
|
+
// Print clean input (without separators)
|
|
780
|
+
for (let i = 0; i < this.state.lines.length; i++) {
|
|
781
|
+
const linePrompt = i === 0 ? this.prompt : pc.dim(' \\ ');
|
|
782
|
+
terminal.write(linePrompt + this.state.lines[i]);
|
|
783
|
+
if (i < this.state.lines.length - 1) {
|
|
784
|
+
terminal.write('\n');
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
terminal.writeLine('');
|
|
788
|
+
// Save to history if submitting
|
|
789
|
+
if (result.action === 'submit' || result.action === 'command') {
|
|
790
|
+
const input = this.getValue();
|
|
791
|
+
this.addToHistory(input);
|
|
792
|
+
}
|
|
793
|
+
// Stop and resolve
|
|
794
|
+
this.stop();
|
|
795
|
+
if (this.resolveInput) {
|
|
796
|
+
this.resolveInput(result);
|
|
797
|
+
this.resolveInput = null;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|