pi-llama-cpp 0.11.0 → 0.12.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 +174 -58
- package/package.json +4 -4
- package/src/interfaces/server.ts +7 -0
- package/src/interfaces/settings.ts +65 -0
- package/src/managers/command.ts +108 -31
- package/src/managers/events.ts +2 -2
- package/src/managers/server.ts +12 -9
- package/src/managers/settings.ts +120 -45
- package/src/models/baseModel.ts +37 -7
- package/src/models/routerModel.ts +2 -1
- package/src/server.ts +38 -4
- package/src/sse/manager.ts +9 -8
- package/src/ui/dialog.ts +287 -0
- package/src/ui/overrideEntryEditor.ts +119 -0
- package/src/ui/overrideSettingsList.ts +682 -0
- package/src/ui/serverListEditor.ts +0 -449
- package/src/ui/serverSettingsList.ts +466 -0
- package/src/ui/strings.ts +127 -0
- package/src/utils/settingsStore.ts +2 -6
- package/tests/commandManager.test.ts +106 -16
- package/tests/dialog.test.ts +186 -0
- package/tests/events.test.ts +12 -9
- package/tests/legacyModel.test.ts +4 -19
- package/tests/mocks.ts +12 -8
- package/tests/overrides.test.ts +352 -0
- package/tests/server.test.ts +12 -12
- package/tests/serverManager.test.ts +24 -58
- package/tests/settings.test.ts +489 -43
- package/tests/settingsStore.test.ts +0 -19
- package/tests/singleModel.test.ts +32 -0
- package/tests/sseManager.test.ts +4 -4
- package/tests/serverListEditor.test.ts +0 -637
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
Input,
|
|
4
|
-
truncateToWidth,
|
|
5
|
-
type Component,
|
|
6
|
-
type Focusable,
|
|
7
|
-
type KeybindingsManager,
|
|
8
|
-
type TUI,
|
|
9
|
-
} from "@earendil-works/pi-tui";
|
|
10
1
|
import { PROVIDER_PREFIX } from "../constants";
|
|
11
2
|
import type { LlamaServer } from "../interfaces/settings";
|
|
12
|
-
import { errorMessage } from "../utils/errors";
|
|
13
3
|
import { normalizeUrl } from "../utils/urls";
|
|
14
4
|
|
|
15
5
|
/**
|
|
@@ -29,54 +19,6 @@ export const normalizeServerUrl = (raw: string): string | null => {
|
|
|
29
19
|
return url;
|
|
30
20
|
};
|
|
31
21
|
|
|
32
|
-
/**
|
|
33
|
-
* Returns a new list with a `{ url }` entry appended. Immutable.
|
|
34
|
-
*/
|
|
35
|
-
export const addServer = (
|
|
36
|
-
servers: LlamaServer[],
|
|
37
|
-
url: string,
|
|
38
|
-
): LlamaServer[] => [...servers, { url }];
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Fields of a `LlamaServer` editable through the inline Input.
|
|
42
|
-
*/
|
|
43
|
-
type EditableField = "url" | "id" | "name";
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Returns a new list with the URL at `index` replaced, preserving any
|
|
47
|
-
* `id`/`name` overrides. Immutable.
|
|
48
|
-
*/
|
|
49
|
-
export const updateServerUrl = (
|
|
50
|
-
servers: LlamaServer[],
|
|
51
|
-
index: number,
|
|
52
|
-
url: string,
|
|
53
|
-
): LlamaServer[] => updateServerField(servers, index, "url", url);
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Returns a new list with `field` at `index` set to `value` (trimmed).
|
|
57
|
-
* An empty `value` removes the `id`/`name` key instead of storing an empty
|
|
58
|
-
* string, so the settings JSON stays clean. Only the edited field is
|
|
59
|
-
* touched — the other override is preserved. Immutable.
|
|
60
|
-
*/
|
|
61
|
-
export const updateServerField = (
|
|
62
|
-
servers: LlamaServer[],
|
|
63
|
-
index: number,
|
|
64
|
-
field: EditableField,
|
|
65
|
-
value: string,
|
|
66
|
-
): LlamaServer[] => {
|
|
67
|
-
const v = value.trim();
|
|
68
|
-
return servers.map((server, i) => {
|
|
69
|
-
if (i !== index) return server;
|
|
70
|
-
if (field === "url") return { ...server, url: v }; // validated upstream
|
|
71
|
-
if (field === "id") {
|
|
72
|
-
const { id, ...rest } = server; // drop the old id override…
|
|
73
|
-
return v.length === 0 ? rest : { ...rest, id: v }; // …clear or replace
|
|
74
|
-
}
|
|
75
|
-
const { name, ...rest } = server; // drop the old name override…
|
|
76
|
-
return v.length === 0 ? rest : { ...rest, name: v }; // …clear or replace
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
|
|
80
22
|
/**
|
|
81
23
|
* Formats the dim `(...)` suffix shown after a server's URL:
|
|
82
24
|
* `(<id> - <name>)`, with the auto-detected URL-based id used when no
|
|
@@ -88,394 +30,3 @@ export const formatServerSuffix = (server: LlamaServer): string => {
|
|
|
88
30
|
const id = server.id ?? `${PROVIDER_PREFIX}=${server.url}`;
|
|
89
31
|
return server.name ? `(${id} - ${server.name})` : `(${server.id})`;
|
|
90
32
|
};
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Returns a new list without the entry at `index`. Immutable.
|
|
94
|
-
*/
|
|
95
|
-
export const removeServer = (
|
|
96
|
-
servers: LlamaServer[],
|
|
97
|
-
index: number,
|
|
98
|
-
): LlamaServer[] => servers.filter((_, i) => i !== index);
|
|
99
|
-
|
|
100
|
-
export interface ServerListEditorOptions {
|
|
101
|
-
/** TUI instance, used to request re-renders */
|
|
102
|
-
tui: TUI;
|
|
103
|
-
/** Theme for styling */
|
|
104
|
-
theme: Theme;
|
|
105
|
-
/** App keybindings manager (injected by ctx.ui.custom) */
|
|
106
|
-
keybindings: KeybindingsManager;
|
|
107
|
-
/** Snapshot of the merged `llamaSettings.servers` to edit */
|
|
108
|
-
servers: LlamaServer[];
|
|
109
|
-
/** Persists a new server list; a rejection keeps the current list */
|
|
110
|
-
persist: (next: LlamaServer[]) => Promise<void>;
|
|
111
|
-
/** Closes the editor (called on Esc in list mode) */
|
|
112
|
-
done: () => void;
|
|
113
|
-
/** Notifies about persistence errors */
|
|
114
|
-
onError: (message: string) => void;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Editor for `llamaSettings.servers`, shown by `/models servers`.
|
|
119
|
-
*
|
|
120
|
-
* List mode: up/down move the cursor, Enter/e edits the selected entry's
|
|
121
|
-
* URL, i edits its id, n its name, a adds a new entry, d asks for
|
|
122
|
-
* confirmation before deleting, Esc closes.
|
|
123
|
-
* Confirm mode: y deletes the selected entry, Esc/n aborts (Enter is
|
|
124
|
-
* deliberately ignored).
|
|
125
|
-
* Edit mode: all other input goes to an inline `Input`; Enter saves,
|
|
126
|
-
* Esc reverts. The edited field (url/id/name) is tracked in `field`.
|
|
127
|
-
*
|
|
128
|
-
* Each mutation is persisted immediately through `persist()` (which maps to
|
|
129
|
-
* `LlamaSettingsManager.setLlamaSetting()`); the in-memory list only updates
|
|
130
|
-
* after the write succeeds, so a failed write leaves everything unchanged
|
|
131
|
-
* and the editor open.
|
|
132
|
-
*/
|
|
133
|
-
export class ServerListEditor implements Component, Focusable {
|
|
134
|
-
private servers: LlamaServer[];
|
|
135
|
-
private selectedIndex = 0;
|
|
136
|
-
private mode: "list" | "edit" | "add" | "confirm" = "list";
|
|
137
|
-
/** Which `LlamaServer` field the inline Input is editing (edit/add modes) */
|
|
138
|
-
private field: EditableField = "url";
|
|
139
|
-
private editingIndex = -1;
|
|
140
|
-
private error: string | undefined;
|
|
141
|
-
private readonly input = new Input();
|
|
142
|
-
private readonly envOverride: boolean;
|
|
143
|
-
private isFocused = false;
|
|
144
|
-
|
|
145
|
-
constructor(private readonly options: ServerListEditorOptions) {
|
|
146
|
-
this.servers = options.servers;
|
|
147
|
-
this.envOverride = Boolean(process.env.LLAMA_SERVER_URL);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Focusable: delegates to the inline Input while it is rendered */
|
|
151
|
-
get focused(): boolean {
|
|
152
|
-
return this.isFocused;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
set focused(value: boolean) {
|
|
156
|
-
this.isFocused = value;
|
|
157
|
-
this.input.focused = value;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
invalidate(): void {
|
|
161
|
-
// No cached state to invalidate
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
handleInput(data: string): void {
|
|
165
|
-
if (this.mode === "list") {
|
|
166
|
-
this.handleListInput(data);
|
|
167
|
-
} else if (this.mode === "confirm") {
|
|
168
|
-
this.handleConfirmInput(data);
|
|
169
|
-
} else {
|
|
170
|
-
this.handleEditInput(data);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
render(width: number): string[] {
|
|
175
|
-
const { theme } = this.options;
|
|
176
|
-
const truncate = (line: string) => truncateToWidth(line, width);
|
|
177
|
-
const lines: string[] = [];
|
|
178
|
-
|
|
179
|
-
lines.push(
|
|
180
|
-
truncate(theme.fg("accent", theme.bold("Manage llama.cpp servers"))),
|
|
181
|
-
);
|
|
182
|
-
lines.push("");
|
|
183
|
-
|
|
184
|
-
if (this.servers.length === 0) {
|
|
185
|
-
lines.push(
|
|
186
|
-
truncate(
|
|
187
|
-
theme.fg("dim", "No servers configured — press a to add one."),
|
|
188
|
-
),
|
|
189
|
-
);
|
|
190
|
-
lines.push(
|
|
191
|
-
truncate(
|
|
192
|
-
theme.fg(
|
|
193
|
-
"dim",
|
|
194
|
-
"With an empty list the default http://127.0.0.1:8080 is used.",
|
|
195
|
-
),
|
|
196
|
-
),
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
this.servers.forEach((server, index) => {
|
|
201
|
-
const selected = index === this.selectedIndex;
|
|
202
|
-
const prefix = selected ? theme.fg("accent", "→ ") : " ";
|
|
203
|
-
const suffix = formatServerSuffix(server);
|
|
204
|
-
const dim = suffix ? theme.fg("dim", ` ${suffix}`) : "";
|
|
205
|
-
lines.push(truncate(`${prefix}${server.url}${dim}`));
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
if (this.mode === "confirm") {
|
|
209
|
-
lines.push(
|
|
210
|
-
truncate(
|
|
211
|
-
theme.fg(
|
|
212
|
-
"error",
|
|
213
|
-
`About to delete "${this.servers[this.selectedIndex]?.url}"`,
|
|
214
|
-
),
|
|
215
|
-
),
|
|
216
|
-
);
|
|
217
|
-
lines.push(truncate(theme.fg("error", "Are you sure?")));
|
|
218
|
-
} else if (this.mode !== "list") {
|
|
219
|
-
const label =
|
|
220
|
-
this.field === "url" ? "URL:" : this.field === "id" ? "ID:" : "Name:";
|
|
221
|
-
lines.push("");
|
|
222
|
-
lines.push(truncate(theme.fg("dim", label)));
|
|
223
|
-
lines.push(truncate(this.input.render(width)[0] ?? ""));
|
|
224
|
-
if (this.error) {
|
|
225
|
-
lines.push(truncate(theme.fg("error", this.error)));
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
lines.push("");
|
|
230
|
-
if (this.envOverride) {
|
|
231
|
-
lines.push(
|
|
232
|
-
truncate(
|
|
233
|
-
theme.fg("dim", "LLAMA_SERVER_URL env var overrides these servers."),
|
|
234
|
-
),
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
lines.push(
|
|
238
|
-
truncate(
|
|
239
|
-
theme.fg(
|
|
240
|
-
"dim",
|
|
241
|
-
this.mode === "list"
|
|
242
|
-
? "Enter/e url · i id · n name · a add · d delete · Esc done"
|
|
243
|
-
: this.mode === "confirm"
|
|
244
|
-
? "y delete · Esc/n cancel"
|
|
245
|
-
: "Enter save · Esc cancel",
|
|
246
|
-
),
|
|
247
|
-
),
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
return lines;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* List mode: navigation, edit/add/delete shortcuts and close.
|
|
255
|
-
*/
|
|
256
|
-
private handleListInput(data: string): void {
|
|
257
|
-
const kb = this.options.keybindings;
|
|
258
|
-
|
|
259
|
-
if (kb.matches(data, "tui.select.cancel")) {
|
|
260
|
-
this.options.done();
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
if (kb.matches(data, "tui.select.up")) {
|
|
264
|
-
if (this.servers.length > 0) {
|
|
265
|
-
this.selectedIndex =
|
|
266
|
-
this.selectedIndex === 0
|
|
267
|
-
? this.servers.length - 1
|
|
268
|
-
: this.selectedIndex - 1;
|
|
269
|
-
this.requestRender();
|
|
270
|
-
}
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
if (kb.matches(data, "tui.select.down")) {
|
|
274
|
-
if (this.servers.length > 0) {
|
|
275
|
-
this.selectedIndex =
|
|
276
|
-
this.selectedIndex === this.servers.length - 1
|
|
277
|
-
? 0
|
|
278
|
-
: this.selectedIndex + 1;
|
|
279
|
-
this.requestRender();
|
|
280
|
-
}
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
if (kb.matches(data, "tui.select.confirm") || data === "e") {
|
|
284
|
-
this.beginEdit();
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
if (data === "i") {
|
|
288
|
-
this.beginEditField("id");
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
if (data === "n") {
|
|
292
|
-
this.beginEditField("name");
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
if (data === "a") {
|
|
296
|
-
this.beginAdd();
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
if (data === "d") {
|
|
300
|
-
this.beginConfirm();
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Confirm mode: only `y` deletes the selected entry, Esc/n returns to the
|
|
307
|
-
* list without changing anything; every other key — Enter included, so a
|
|
308
|
-
* stray keypress can't confirm — is ignored.
|
|
309
|
-
*/
|
|
310
|
-
private handleConfirmInput(data: string): void {
|
|
311
|
-
const kb = this.options.keybindings;
|
|
312
|
-
|
|
313
|
-
if (data === "y") {
|
|
314
|
-
this.deleteSelected();
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
if (kb.matches(data, "tui.select.cancel") || data === "n") {
|
|
318
|
-
this.mode = "list";
|
|
319
|
-
this.requestRender();
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Edit mode: Enter saves, Esc reverts, everything else goes to the Input.
|
|
325
|
-
*/
|
|
326
|
-
private handleEditInput(data: string): void {
|
|
327
|
-
const kb = this.options.keybindings;
|
|
328
|
-
|
|
329
|
-
if (kb.matches(data, "tui.select.confirm")) {
|
|
330
|
-
this.saveEdit();
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
if (kb.matches(data, "tui.select.cancel")) {
|
|
334
|
-
this.mode = "list";
|
|
335
|
-
this.editingIndex = -1;
|
|
336
|
-
this.error = undefined;
|
|
337
|
-
this.requestRender();
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
this.input.handleInput(data);
|
|
341
|
-
this.requestRender();
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Opens the selected entry for editing, prefilling the Input with its URL
|
|
346
|
-
* and placing the cursor at the end (the common edit: appending/changing
|
|
347
|
-
* the port).
|
|
348
|
-
*/
|
|
349
|
-
private beginEdit(): void {
|
|
350
|
-
if (this.servers.length === 0) return;
|
|
351
|
-
this.beginEditField("url");
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Opens the selected entry for editing `field`, prefilling the Input with
|
|
356
|
-
* its current value (empty when unset) and placing the cursor at the end.
|
|
357
|
-
*/
|
|
358
|
-
private beginEditField(field: EditableField): void {
|
|
359
|
-
if (this.servers.length === 0) return;
|
|
360
|
-
this.mode = "edit";
|
|
361
|
-
this.field = field;
|
|
362
|
-
this.editingIndex = this.selectedIndex;
|
|
363
|
-
this.error = undefined;
|
|
364
|
-
this.input.setValue(this.servers[this.selectedIndex][field] ?? "");
|
|
365
|
-
this.moveInputCursorToEnd();
|
|
366
|
-
this.requestRender();
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Enters confirm mode for the selected entry: deletion only proceeds
|
|
371
|
-
* after an explicit Enter/y, guarding against accidental presses of d.
|
|
372
|
-
*/
|
|
373
|
-
private beginConfirm(): void {
|
|
374
|
-
if (this.servers.length === 0) return;
|
|
375
|
-
this.mode = "confirm";
|
|
376
|
-
this.requestRender();
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Starts adding a new entry with an empty Input.
|
|
381
|
-
*/
|
|
382
|
-
private beginAdd(): void {
|
|
383
|
-
this.mode = "add";
|
|
384
|
-
this.field = "url";
|
|
385
|
-
this.editingIndex = -1;
|
|
386
|
-
this.error = undefined;
|
|
387
|
-
this.input.setValue("");
|
|
388
|
-
this.requestRender();
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Saves the edited/added value: validates it per field, persists the new
|
|
393
|
-
* list and returns to list mode. Invalid input shows an inline error and
|
|
394
|
-
* keeps editing; a failed write notifies via `onError` and keeps editing
|
|
395
|
-
* too.
|
|
396
|
-
*/
|
|
397
|
-
private async saveEdit(): Promise<void> {
|
|
398
|
-
const raw = this.input.getValue();
|
|
399
|
-
if (this.field === "url") {
|
|
400
|
-
const url = normalizeServerUrl(raw);
|
|
401
|
-
if (!url) {
|
|
402
|
-
this.error = "Invalid URL — use http://host:port (one URL per entry)";
|
|
403
|
-
this.requestRender();
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
await this.applySave((servers) =>
|
|
407
|
-
this.mode === "add"
|
|
408
|
-
? addServer(servers, url)
|
|
409
|
-
: updateServerUrl(servers, this.editingIndex, url),
|
|
410
|
-
);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
// id/name: free-form; empty/whitespace clears the override
|
|
414
|
-
await this.applySave((servers) =>
|
|
415
|
-
updateServerField(servers, this.editingIndex, this.field, raw),
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Persists `build(this.servers)`. On success, adopts the new list and
|
|
421
|
-
* returns to list mode; on failure, notifies via `onError` and stays in
|
|
422
|
-
* edit mode with the pre-mutation list.
|
|
423
|
-
*/
|
|
424
|
-
private async applySave(
|
|
425
|
-
build: (servers: LlamaServer[]) => LlamaServer[],
|
|
426
|
-
): Promise<void> {
|
|
427
|
-
const isAdd = this.mode === "add";
|
|
428
|
-
const next = build(this.servers);
|
|
429
|
-
|
|
430
|
-
try {
|
|
431
|
-
await this.options.persist(next);
|
|
432
|
-
this.servers = next;
|
|
433
|
-
if (isAdd) this.selectedIndex = next.length - 1;
|
|
434
|
-
this.mode = "list";
|
|
435
|
-
this.editingIndex = -1;
|
|
436
|
-
this.error = undefined;
|
|
437
|
-
} catch (err) {
|
|
438
|
-
// Write failed: keep the pre-mutation list and stay in edit mode
|
|
439
|
-
this.options.onError(errorMessage(err));
|
|
440
|
-
}
|
|
441
|
-
this.requestRender();
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Deletes the selected entry and persists immediately. Leaves confirm
|
|
446
|
-
* mode synchronously so a second Enter while the write is pending cannot
|
|
447
|
-
* queue a duplicate deletion.
|
|
448
|
-
*/
|
|
449
|
-
private async deleteSelected(): Promise<void> {
|
|
450
|
-
if (this.servers.length === 0) return;
|
|
451
|
-
this.mode = "list";
|
|
452
|
-
const next = removeServer(this.servers, this.selectedIndex);
|
|
453
|
-
|
|
454
|
-
try {
|
|
455
|
-
await this.options.persist(next);
|
|
456
|
-
this.servers = next;
|
|
457
|
-
if (this.selectedIndex >= next.length) {
|
|
458
|
-
this.selectedIndex = Math.max(0, next.length - 1);
|
|
459
|
-
}
|
|
460
|
-
} catch (err) {
|
|
461
|
-
// Write failed: keep the pre-mutation list
|
|
462
|
-
this.options.onError(errorMessage(err));
|
|
463
|
-
}
|
|
464
|
-
this.requestRender();
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* The Input has no "move to end" API and `setValue()` clamps the cursor;
|
|
469
|
-
* walk it right one grapheme at a time using the standard arrow sequence
|
|
470
|
-
* (resolved by the Input against pi's global keybindings).
|
|
471
|
-
*/
|
|
472
|
-
private moveInputCursorToEnd(): void {
|
|
473
|
-
for (let i = 0; i < [...this.input.getValue()].length; i++) {
|
|
474
|
-
this.input.handleInput("\x1b[C");
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
private requestRender(): void {
|
|
479
|
-
this.options.tui.requestRender();
|
|
480
|
-
}
|
|
481
|
-
}
|