minercon 3.0.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +439 -0
  2. package/LICENSE +22 -0
  3. package/README.md +401 -0
  4. package/images/icon.png +0 -0
  5. package/out/ansi.js +123 -0
  6. package/out/argumentHint.js +43 -0
  7. package/out/bukkitHelpParsing.js +62 -0
  8. package/out/cli.js +253 -0
  9. package/out/cliConfig.js +141 -0
  10. package/out/commandLine.js +28 -0
  11. package/out/commandSuggestions.js +202 -0
  12. package/out/commandTree.js +46 -0
  13. package/out/commandTreeCache.js +171 -0
  14. package/out/commandTreeCrawler.js +583 -0
  15. package/out/commandTreeParsingBrigadier.js +426 -0
  16. package/out/commandTreeParsingBukkit.js +116 -0
  17. package/out/commandTreeSuggestions.js +142 -0
  18. package/out/completionBackend.js +94 -0
  19. package/out/completionEngine.js +376 -0
  20. package/out/completionQueries.js +86 -0
  21. package/out/completionsBackend.js +97 -0
  22. package/out/connectionManager.js +209 -0
  23. package/out/displayArgumentHint.js +43 -0
  24. package/out/displayCommandTree.js +115 -0
  25. package/out/displaySuggestion.js +282 -0
  26. package/out/extension.js +190 -0
  27. package/out/helpTextParsing.js +445 -0
  28. package/out/historySearch.js +46 -0
  29. package/out/historyStore.js +126 -0
  30. package/out/lineEditor.js +525 -0
  31. package/out/localCommandTree.js +541 -0
  32. package/out/logger.js +14 -0
  33. package/out/minercon +253 -0
  34. package/out/pager.js +168 -0
  35. package/out/pagination.js +142 -0
  36. package/out/rconClient.js +97 -0
  37. package/out/rconConnectionManager.js +238 -0
  38. package/out/rconProtocol.js +421 -0
  39. package/out/rconSession.js +920 -0
  40. package/out/rconTerminal.js +80 -0
  41. package/out/suggestionDisplay.js +286 -0
  42. package/out/terminalOutput.js +110 -0
  43. package/out/unpaginate.js +30 -0
  44. package/package.json +138 -0
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ // src/historyStore.ts
3
+ //
4
+ // Command history: on-disk persistence (server-scoped, like
5
+ // commandTreeCache.ts) — loaded once at session start to seed LineEditor's
6
+ // in-memory history and rewritten each time a command is run — plus the pure
7
+ // state/matching logic for Ctrl+R reverse history search, which operates on
8
+ // that same in-memory list.
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.HistoryStore = void 0;
44
+ exports.searchHistory = searchHistory;
45
+ exports.startHistorySearch = startHistorySearch;
46
+ exports.setHistorySearchQuery = setHistorySearchQuery;
47
+ exports.cycleHistorySearch = cycleHistorySearch;
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ class HistoryStore {
51
+ logger;
52
+ maxEntries;
53
+ file;
54
+ /** `maxEntries` mirrors LineEditor's in-memory cap — no point persisting more than it'll ever hold. */
55
+ constructor(cacheDir, serverHost, serverPort, logger, maxEntries = 100) {
56
+ this.logger = logger;
57
+ this.maxEntries = maxEntries;
58
+ this.file = path.join(cacheDir, `${serverHost}_${serverPort}_history.txt`);
59
+ }
60
+ /** Returns persisted entries (oldest-first), or `[]` if there's nothing usable to load. */
61
+ load() {
62
+ try {
63
+ if (!fs.existsSync(this.file)) {
64
+ return [];
65
+ }
66
+ const lines = fs.readFileSync(this.file, 'utf-8').split('\n');
67
+ if (lines.length > 0 && lines[lines.length - 1] === '') {
68
+ lines.pop();
69
+ }
70
+ return lines.slice(-this.maxEntries);
71
+ }
72
+ catch (error) {
73
+ this.logger.error(`Error loading command history: ${error}`);
74
+ return [];
75
+ }
76
+ }
77
+ /** Persists `entries` (oldest-first, one per line), overwriting whatever was there before. Newlines within an entry are stripped. */
78
+ save(entries) {
79
+ try {
80
+ const dir = path.dirname(this.file);
81
+ if (!fs.existsSync(dir)) {
82
+ fs.mkdirSync(dir, { recursive: true });
83
+ }
84
+ const lines = entries.slice(-this.maxEntries).map((entry) => entry.replace(/[\r\n]/g, ''));
85
+ fs.writeFileSync(this.file, lines.map((line) => `${line}\n`).join(''));
86
+ }
87
+ catch (error) {
88
+ this.logger.error(`Error saving command history: ${error}`);
89
+ }
90
+ }
91
+ }
92
+ exports.HistoryStore = HistoryStore;
93
+ /** Entries from `history` (oldest-first) containing `query` (case-insensitive), most-recently-used first, deduplicated. An empty query matches everything, so the list starts as "recent history, newest first". */
94
+ function searchHistory(history, query) {
95
+ const lowerQuery = query.toLowerCase();
96
+ const seen = new Set();
97
+ const results = [];
98
+ for (let i = history.length - 1; i >= 0; i--) {
99
+ const entry = history[i];
100
+ if (seen.has(entry)) {
101
+ continue;
102
+ }
103
+ seen.add(entry);
104
+ if (entry.toLowerCase().includes(lowerQuery)) {
105
+ results.push(entry);
106
+ }
107
+ }
108
+ return results;
109
+ }
110
+ /** Enters search mode: empty query, recent history shown newest-first. */
111
+ function startHistorySearch(history, originalLine) {
112
+ return { query: '', items: searchHistory(history, ''), selectedIndex: 0, originalLine };
113
+ }
114
+ /** Re-filters as the query changes (typing or backspacing), resetting to the best (first) match. */
115
+ function setHistorySearchQuery(history, state, query) {
116
+ return { ...state, query, items: searchHistory(history, query), selectedIndex: 0 };
117
+ }
118
+ /** Moves the selection by `delta`, wrapping; a no-op when there's nothing to select. */
119
+ function cycleHistorySearch(state, delta) {
120
+ if (state.items.length === 0) {
121
+ return state;
122
+ }
123
+ const selectedIndex = ((state.selectedIndex + delta) % state.items.length + state.items.length) % state.items.length;
124
+ return { ...state, selectedIndex };
125
+ }
126
+ //# sourceMappingURL=historyStore.js.map
@@ -0,0 +1,525 @@
1
+ "use strict";
2
+ // src/lineEditor.ts
3
+ //
4
+ // Owns the input line itself: the text, cursor position, selection range,
5
+ // and command history, plus every operation that mutates them — character
6
+ // insertion/deletion, cursor and selection movement, kill/yank-style word and
7
+ // line edits, and history navigation. It also owns redrawing the line (with
8
+ // or without selection highlighting) to the terminal.
9
+ //
10
+ // This is the "what does typing do to the buffer, and how does that get
11
+ // painted" layer — it knows nothing about RCON, tab completion, or connection
12
+ // state. Where it needs the host terminal to do something on its behalf (emit
13
+ // ANSI, know the current prompt text, react to the line changing), it goes
14
+ // through the small `LineEditorHost` interface below, so it stays testable
15
+ // and reusable independent of `vscode.Pseudoterminal` plumbing.
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.LineEditor = void 0;
51
+ const ansi = __importStar(require("./ansi"));
52
+ class LineEditor {
53
+ host;
54
+ maxHistorySize;
55
+ currentLine = '';
56
+ cursorPosition = 0;
57
+ // The active selection, or null when there's none. `anchor` is the fixed end
58
+ // (where the gesture started); `head` is the moving end, kept in step with the
59
+ // cursor. A collapsed selection (anchor === head) counts as none — see
60
+ // hasSelection.
61
+ selection = null;
62
+ history = [];
63
+ // Non-null while the user is navigating history; null when at the live line.
64
+ // Bundles the navigation index and the line saved on first Up-arrow so they
65
+ // stay in sync and can be reset atomically.
66
+ historyCursor = null;
67
+ constructor(host, maxHistorySize = 100) {
68
+ this.host = host;
69
+ this.maxHistorySize = maxHistorySize;
70
+ }
71
+ get line() {
72
+ return this.currentLine;
73
+ }
74
+ get cursor() {
75
+ return this.cursorPosition;
76
+ }
77
+ // ── selection ──
78
+ hasSelection() {
79
+ return this.selection !== null && this.selection.anchor !== this.selection.head;
80
+ }
81
+ clearSelection() {
82
+ this.selection = null;
83
+ }
84
+ /** The selection's bounds as `[start, end)`, low-to-high — only valid when `hasSelection()`. */
85
+ selectionRange() {
86
+ const { anchor, head } = this.selection;
87
+ return { start: Math.min(anchor, head), end: Math.max(anchor, head) };
88
+ }
89
+ getSelectedText() {
90
+ if (!this.hasSelection()) {
91
+ return '';
92
+ }
93
+ const { start, end } = this.selectionRange();
94
+ return this.currentLine.slice(start, end);
95
+ }
96
+ deleteSelection() {
97
+ if (!this.hasSelection()) {
98
+ return;
99
+ }
100
+ this.deleteSelectionInternal();
101
+ this.host.onLineChanged(this.currentLine);
102
+ }
103
+ /** `deleteSelection` without the host notification — for compound edits (insertText) that notify once themselves, with the final line. */
104
+ deleteSelectionInternal() {
105
+ if (!this.hasSelection()) {
106
+ return;
107
+ }
108
+ const { start, end } = this.selectionRange();
109
+ this.currentLine = this.currentLine.slice(0, start) + this.currentLine.slice(end);
110
+ this.cursorPosition = start;
111
+ this.clearSelection();
112
+ }
113
+ /**
114
+ * Extend (or begin) the selection so its moving end lands at `newPos`: anchor
115
+ * at the current cursor if there isn't a selection yet, then move both the
116
+ * cursor and the selection's moving end to `newPos` and repaint. Every
117
+ * select* operation is this — they differ only in how they pick `newPos`.
118
+ */
119
+ extendSelectionTo(newPos) {
120
+ if (!this.hasSelection()) {
121
+ this.selection = { anchor: this.cursorPosition, head: this.cursorPosition };
122
+ }
123
+ this.cursorPosition = newPos;
124
+ this.selection.head = newPos;
125
+ this.redraw();
126
+ }
127
+ selectLeft() {
128
+ if (this.cursorPosition > 0) {
129
+ this.extendSelectionTo(this.cursorPosition - 1);
130
+ }
131
+ }
132
+ selectRight() {
133
+ if (this.cursorPosition < this.currentLine.length) {
134
+ this.extendSelectionTo(this.cursorPosition + 1);
135
+ }
136
+ }
137
+ selectWordLeft() {
138
+ this.extendSelectionTo(this.findWordLeft());
139
+ }
140
+ selectWordRight() {
141
+ this.extendSelectionTo(this.findWordRight());
142
+ }
143
+ selectToStart() {
144
+ if (this.cursorPosition > 0) {
145
+ this.extendSelectionTo(0);
146
+ }
147
+ }
148
+ selectToEnd() {
149
+ if (this.cursorPosition < this.currentLine.length) {
150
+ this.extendSelectionTo(this.currentLine.length);
151
+ }
152
+ }
153
+ // ── cursor movement ──
154
+ moveLeft() {
155
+ const hadSelection = this.hasSelection();
156
+ if (hadSelection) {
157
+ this.clearSelection();
158
+ }
159
+ if (this.cursorPosition > 0) {
160
+ this.cursorPosition--;
161
+ if (hadSelection) {
162
+ this.redraw();
163
+ }
164
+ else {
165
+ this.host.write('\x1b[D');
166
+ }
167
+ }
168
+ else if (hadSelection) {
169
+ this.redraw();
170
+ }
171
+ }
172
+ moveRight() {
173
+ const hadSelection = this.hasSelection();
174
+ if (hadSelection) {
175
+ this.clearSelection();
176
+ }
177
+ if (this.cursorPosition < this.currentLine.length) {
178
+ this.cursorPosition++;
179
+ if (hadSelection) {
180
+ this.redraw();
181
+ }
182
+ else {
183
+ this.host.write('\x1b[C');
184
+ }
185
+ }
186
+ else if (hadSelection) {
187
+ this.redraw();
188
+ }
189
+ }
190
+ moveWordLeft() {
191
+ const newPos = this.findWordLeft();
192
+ if (newPos !== this.cursorPosition) {
193
+ this.clearSelection();
194
+ this.cursorPosition = newPos;
195
+ this.redraw();
196
+ }
197
+ }
198
+ moveWordRight() {
199
+ const newPos = this.findWordRight();
200
+ if (newPos !== this.cursorPosition) {
201
+ this.clearSelection();
202
+ this.cursorPosition = newPos;
203
+ this.redraw();
204
+ }
205
+ }
206
+ moveToStart() {
207
+ const hadSelection = this.hasSelection();
208
+ this.clearSelection();
209
+ if (this.cursorPosition > 0) {
210
+ this.cursorPosition = 0;
211
+ if (hadSelection) {
212
+ this.redraw();
213
+ }
214
+ else {
215
+ this.host.write('\r');
216
+ this.host.write(this.host.promptText());
217
+ this.host.write(this.currentLine);
218
+ const moveBack = this.currentLine.length;
219
+ if (moveBack > 0) {
220
+ this.host.write('\x1b[' + moveBack + 'D');
221
+ }
222
+ }
223
+ }
224
+ else if (hadSelection) {
225
+ this.redraw();
226
+ }
227
+ }
228
+ moveToEnd() {
229
+ const hadSelection = this.hasSelection();
230
+ this.clearSelection();
231
+ const moveForward = this.currentLine.length - this.cursorPosition;
232
+ if (moveForward > 0) {
233
+ this.cursorPosition = this.currentLine.length;
234
+ if (hadSelection) {
235
+ this.redraw();
236
+ }
237
+ else {
238
+ this.host.write('\x1b[' + moveForward + 'C');
239
+ }
240
+ }
241
+ else if (hadSelection) {
242
+ this.redraw();
243
+ }
244
+ }
245
+ // ── editing ──
246
+ insertText(text) {
247
+ if (this.host.consumeOutputArtifacts()) {
248
+ this.host.write('\x1b[2K');
249
+ this.host.write('\r');
250
+ this.host.write(this.host.promptText());
251
+ this.host.write(this.currentLine.substring(0, this.cursorPosition));
252
+ }
253
+ if (this.hasSelection()) {
254
+ this.deleteSelectionInternal();
255
+ }
256
+ const filteredText = text.replace(/[\x00-\x08\x0a-\x1f\x7f]/g, '');
257
+ if (filteredText.length === 0) {
258
+ return;
259
+ }
260
+ this.currentLine = this.currentLine.slice(0, this.cursorPosition) +
261
+ filteredText +
262
+ this.currentLine.slice(this.cursorPosition);
263
+ const restOfLine = this.currentLine.slice(this.cursorPosition + filteredText.length);
264
+ this.host.write(filteredText + restOfLine);
265
+ this.cursorPosition += filteredText.length;
266
+ if (restOfLine.length > 0) {
267
+ this.host.write('\x1b[' + restOfLine.length + 'D');
268
+ }
269
+ this.clearSelection();
270
+ this.host.onLineChanged(this.currentLine);
271
+ }
272
+ handleBackspace() {
273
+ if (this.hasSelection()) {
274
+ this.deleteSelection();
275
+ this.redraw();
276
+ }
277
+ else if (this.cursorPosition > 0) {
278
+ this.currentLine = this.currentLine.slice(0, this.cursorPosition - 1) +
279
+ this.currentLine.slice(this.cursorPosition);
280
+ this.cursorPosition--;
281
+ this.host.write('\b');
282
+ const restOfLine = this.currentLine.slice(this.cursorPosition);
283
+ this.host.write(restOfLine + ' ');
284
+ this.host.write('\x1b[' + (restOfLine.length + 1) + 'D');
285
+ this.host.onLineChanged(this.currentLine);
286
+ }
287
+ }
288
+ deleteForward() {
289
+ if (this.hasSelection()) {
290
+ this.deleteSelection();
291
+ this.redraw();
292
+ }
293
+ else if (this.cursorPosition < this.currentLine.length) {
294
+ this.currentLine = this.currentLine.slice(0, this.cursorPosition) +
295
+ this.currentLine.slice(this.cursorPosition + 1);
296
+ const restOfLine = this.currentLine.slice(this.cursorPosition);
297
+ this.host.write(restOfLine + ' ');
298
+ this.host.write('\x1b[' + (restOfLine.length + 1) + 'D');
299
+ this.host.onLineChanged(this.currentLine);
300
+ }
301
+ }
302
+ /**
303
+ * Repaint the line after a kill: step the cursor `cursorShiftLeft` columns
304
+ * left to the new insertion point (for backward kills), clear to end of line,
305
+ * then write the surviving `tail` and step back onto its start. Shared by all
306
+ * four kill operations — they differ only in `tail` and the shift.
307
+ */
308
+ repaintAfterKill(tail, cursorShiftLeft = 0) {
309
+ if (cursorShiftLeft > 0) {
310
+ this.host.write('\x1b[' + cursorShiftLeft + 'D');
311
+ }
312
+ this.host.write('\x1b[K');
313
+ this.host.write(tail);
314
+ if (tail.length > 0) {
315
+ this.host.write('\x1b[' + tail.length + 'D');
316
+ }
317
+ }
318
+ // emacs: unix-line-discard — kill from cursor to start of line
319
+ killToStart() {
320
+ if (this.cursorPosition > 0) {
321
+ const killed = this.currentLine.slice(0, this.cursorPosition);
322
+ const afterCursor = this.currentLine.slice(this.cursorPosition);
323
+ this.currentLine = afterCursor;
324
+ this.cursorPosition = 0;
325
+ this.clearSelection();
326
+ this.repaintAfterKill(afterCursor, killed.length);
327
+ this.host.onLineChanged(this.currentLine);
328
+ return killed;
329
+ }
330
+ return '';
331
+ }
332
+ // emacs: kill-line — kill from cursor to end of line
333
+ killToEnd() {
334
+ if (this.cursorPosition < this.currentLine.length) {
335
+ const killed = this.currentLine.slice(this.cursorPosition);
336
+ this.currentLine = this.currentLine.slice(0, this.cursorPosition);
337
+ this.clearSelection();
338
+ this.repaintAfterKill('');
339
+ this.host.onLineChanged(this.currentLine);
340
+ return killed;
341
+ }
342
+ return '';
343
+ }
344
+ // emacs: backward-kill-word (Ctrl+W / Alt+Backspace)
345
+ killWordBack() {
346
+ if (this.cursorPosition > 0) {
347
+ const beforeCursor = this.currentLine.slice(0, this.cursorPosition);
348
+ const afterCursor = this.currentLine.slice(this.cursorPosition);
349
+ const newPos = this.findWordLeft();
350
+ const killed = beforeCursor.slice(newPos);
351
+ this.currentLine = beforeCursor.slice(0, newPos) + afterCursor;
352
+ this.cursorPosition = newPos;
353
+ this.clearSelection();
354
+ this.repaintAfterKill(afterCursor, killed.length);
355
+ this.host.onLineChanged(this.currentLine);
356
+ return killed;
357
+ }
358
+ return '';
359
+ }
360
+ // emacs: kill-word (Alt+D) — delete from cursor to end of the word forward
361
+ killWordForward() {
362
+ const newPos = this.findWordRight();
363
+ if (newPos > this.cursorPosition) {
364
+ const beforeCursor = this.currentLine.slice(0, this.cursorPosition);
365
+ const killed = this.currentLine.slice(this.cursorPosition, newPos);
366
+ const afterDeleted = this.currentLine.slice(newPos);
367
+ this.currentLine = beforeCursor + afterDeleted;
368
+ this.clearSelection();
369
+ this.repaintAfterKill(afterDeleted);
370
+ this.host.onLineChanged(this.currentLine);
371
+ return killed;
372
+ }
373
+ return '';
374
+ }
375
+ // emacs: transpose-chars — swap the two characters around the cursor
376
+ transposeChars() {
377
+ if (this.currentLine.length >= 2 && this.cursorPosition > 0) {
378
+ // At end of line, transpose the last two characters; otherwise transpose
379
+ // the character before the cursor with the one at the cursor.
380
+ const pos = Math.min(this.cursorPosition, this.currentLine.length - 1);
381
+ const chars = [...this.currentLine];
382
+ [chars[pos - 1], chars[pos]] = [chars[pos], chars[pos - 1]];
383
+ this.currentLine = chars.join('');
384
+ this.cursorPosition = Math.min(pos + 1, this.currentLine.length);
385
+ this.clearSelection();
386
+ this.redraw();
387
+ this.host.onLineChanged(this.currentLine);
388
+ }
389
+ }
390
+ // ── whole-line operations ──
391
+ redraw() {
392
+ // Move cursor to start of line, clear it, redraw the prompt
393
+ this.host.write('\r');
394
+ this.host.write('\x1b[K');
395
+ this.host.write(this.host.promptText());
396
+ if (!this.hasSelection()) {
397
+ this.host.write(this.currentLine);
398
+ }
399
+ else {
400
+ const { start, end } = this.selectionRange();
401
+ if (start > 0) {
402
+ this.host.write(this.currentLine.slice(0, start));
403
+ }
404
+ this.host.write(ansi.REVERSE + this.currentLine.slice(start, end) + ansi.REVERSE_OFF);
405
+ if (end < this.currentLine.length) {
406
+ this.host.write(this.currentLine.slice(end));
407
+ }
408
+ }
409
+ if (this.currentLine.length > this.cursorPosition) {
410
+ const moveBack = this.currentLine.length - this.cursorPosition;
411
+ this.host.write('\x1b[' + moveBack + 'D');
412
+ }
413
+ }
414
+ /** Resets the line/cursor/selection to empty without writing anything — for callers (like Enter) that print their own newline and redraw the prompt themselves. */
415
+ resetLine() {
416
+ this.currentLine = '';
417
+ this.cursorPosition = 0;
418
+ this.clearSelection();
419
+ }
420
+ clearAndReset() {
421
+ this.host.beforeLineCleared();
422
+ this.host.write('\r');
423
+ this.host.write('\x1b[K');
424
+ this.currentLine = '';
425
+ this.cursorPosition = 0;
426
+ this.clearSelection();
427
+ this.host.onLineChanged('');
428
+ }
429
+ /** Replaces the line wholesale (tab-completion splices, history recall, restore-on-escape) — sets the cursor to the end and redraws from scratch. */
430
+ replaceLine(newLine) {
431
+ this.host.write('\r');
432
+ this.host.write('\x1b[K');
433
+ this.host.write(this.host.promptText());
434
+ this.host.write(newLine);
435
+ this.currentLine = newLine;
436
+ this.cursorPosition = newLine.length;
437
+ this.clearSelection();
438
+ }
439
+ // ── history ──
440
+ /** Command history, oldest-first — e.g. for Ctrl+R search or persisting to disk. */
441
+ get historyEntries() {
442
+ return this.history;
443
+ }
444
+ /** Seeds history (oldest-first) from persisted entries, e.g. loaded from disk at session start. Replaces any existing in-memory history. */
445
+ loadHistory(entries) {
446
+ this.history = entries.slice(-this.maxHistorySize);
447
+ }
448
+ pushHistory(command) {
449
+ if (this.history.length === 0 || this.history[this.history.length - 1] !== command) {
450
+ this.history.push(command);
451
+ if (this.history.length > this.maxHistorySize) {
452
+ this.history.shift();
453
+ }
454
+ }
455
+ }
456
+ navigateHistory(direction) {
457
+ if (this.history.length === 0) {
458
+ return;
459
+ }
460
+ if (direction === 'up') {
461
+ if (this.historyCursor === null) {
462
+ this.historyCursor = { index: 0, savedLine: this.currentLine };
463
+ this.replaceLine(this.history[this.history.length - 1]);
464
+ }
465
+ else if (this.historyCursor.index < this.history.length - 1) {
466
+ this.historyCursor.index++;
467
+ this.replaceLine(this.history[this.history.length - 1 - this.historyCursor.index]);
468
+ }
469
+ }
470
+ else {
471
+ if (this.historyCursor === null) {
472
+ return;
473
+ }
474
+ if (this.historyCursor.index > 0) {
475
+ this.historyCursor.index--;
476
+ this.replaceLine(this.history[this.history.length - 1 - this.historyCursor.index]);
477
+ }
478
+ else {
479
+ const { savedLine } = this.historyCursor;
480
+ this.historyCursor = null;
481
+ this.replaceLine(savedLine);
482
+ }
483
+ }
484
+ }
485
+ resetHistoryCursor() {
486
+ this.historyCursor = null;
487
+ }
488
+ // ── word-boundary helpers ──
489
+ findWordLeft() {
490
+ if (this.cursorPosition === 0) {
491
+ return 0;
492
+ }
493
+ let pos = this.cursorPosition - 1;
494
+ // Skip whitespace
495
+ while (pos > 0 && this.currentLine[pos] === ' ') {
496
+ pos--;
497
+ }
498
+ // Skip word characters
499
+ while (pos > 0 && this.currentLine[pos - 1] !== ' ') {
500
+ pos--;
501
+ }
502
+ return pos;
503
+ }
504
+ findWordRight() {
505
+ if (this.cursorPosition >= this.currentLine.length) {
506
+ return this.currentLine.length;
507
+ }
508
+ let pos = this.cursorPosition;
509
+ // If we're in whitespace, skip to next word
510
+ if (this.currentLine[pos] === ' ') {
511
+ while (pos < this.currentLine.length && this.currentLine[pos] === ' ') {
512
+ pos++;
513
+ }
514
+ }
515
+ else {
516
+ // Skip current word
517
+ while (pos < this.currentLine.length && this.currentLine[pos] !== ' ') {
518
+ pos++;
519
+ }
520
+ }
521
+ return pos;
522
+ }
523
+ }
524
+ exports.LineEditor = LineEditor;
525
+ //# sourceMappingURL=lineEditor.js.map