letmecook 0.0.13 → 0.0.15
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/package.json +1 -1
- package/src/flows/add-repos.ts +52 -16
- package/src/flows/edit-session.ts +104 -46
- package/src/flows/new-session.ts +81 -76
- package/src/ui/add-repos.ts +292 -173
- package/src/ui/common/command-runner.ts +249 -0
- package/src/ui/common/footer.ts +105 -0
- package/src/ui/common/keyboard.ts +95 -0
- package/src/ui/confirm-delete.ts +10 -9
- package/src/ui/conflict.ts +10 -1
- package/src/ui/exit.ts +79 -47
- package/src/ui/list.ts +23 -14
- package/src/ui/main-menu.ts +25 -46
- package/src/ui/reclone-prompt.ts +11 -9
- package/src/ui/renderer.ts +16 -4
- package/src/ui/session-details.ts +43 -18
- package/src/ui/session-settings.ts +91 -71
- package/src/ui/skills.ts +44 -100
package/src/ui/add-repos.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { type CliRenderer, TextRenderable, InputRenderable, type KeyEvent } from
|
|
|
2
2
|
import { createBaseLayout, clearLayout } from "./renderer";
|
|
3
3
|
import { parseRepoSpec, type RepoSpec } from "../types";
|
|
4
4
|
import { listRepoHistory } from "../repo-history";
|
|
5
|
+
import { showFooter, hideFooter } from "./common/footer";
|
|
6
|
+
import { isEnter, isEscape, isArrowUp, isArrowDown } from "./common/keyboard";
|
|
5
7
|
|
|
6
8
|
export interface AddReposResult {
|
|
7
9
|
repos: RepoSpec[];
|
|
@@ -11,7 +13,6 @@ export interface AddReposResult {
|
|
|
11
13
|
export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddReposResult> {
|
|
12
14
|
const history = await listRepoHistory();
|
|
13
15
|
const historySpecs = history.map((item) => item.spec);
|
|
14
|
-
let historyIndex = historySpecs.length;
|
|
15
16
|
const maxMatches = 6;
|
|
16
17
|
|
|
17
18
|
return new Promise((resolve) => {
|
|
@@ -21,12 +22,14 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
21
22
|
|
|
22
23
|
const repos: RepoSpec[] = [];
|
|
23
24
|
let currentInput = "";
|
|
24
|
-
let
|
|
25
|
-
let
|
|
26
|
-
let
|
|
27
|
-
let
|
|
28
|
-
let
|
|
29
|
-
let
|
|
25
|
+
let currentReadOnly = false;
|
|
26
|
+
let currentLatest = false;
|
|
27
|
+
let currentValidRepo: RepoSpec | null = null;
|
|
28
|
+
let selectedMatchIndex = -1; // -1 means no match selected, user is typing freely
|
|
29
|
+
let lastQuery = ""; // Track the query that generated current matches
|
|
30
|
+
let isNavigating = false; // Flag to prevent input handler from resetting when navigating
|
|
31
|
+
let isConfirming = false; // Flag for confirmation mode (showing checkboxes)
|
|
32
|
+
let confirmOptionIndex = 0; // 0 = read-only, 1 = latest, 2 = confirm button
|
|
30
33
|
|
|
31
34
|
// Repository input
|
|
32
35
|
const repoLabel = new TextRenderable(renderer, {
|
|
@@ -67,10 +70,10 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
67
70
|
});
|
|
68
71
|
content.add(matchesList);
|
|
69
72
|
|
|
70
|
-
//
|
|
73
|
+
// Inline toggles (always visible when repo is valid)
|
|
71
74
|
const detailsLabel = new TextRenderable(renderer, {
|
|
72
75
|
id: "details-label",
|
|
73
|
-
content: "
|
|
76
|
+
content: "",
|
|
74
77
|
fg: "#e2e8f0",
|
|
75
78
|
marginTop: 1,
|
|
76
79
|
});
|
|
@@ -78,7 +81,7 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
78
81
|
|
|
79
82
|
const detailsReadOnly = new TextRenderable(renderer, {
|
|
80
83
|
id: "details-readonly",
|
|
81
|
-
content: "
|
|
84
|
+
content: "",
|
|
82
85
|
fg: "#94a3b8",
|
|
83
86
|
marginTop: 0,
|
|
84
87
|
});
|
|
@@ -86,22 +89,40 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
86
89
|
|
|
87
90
|
const detailsLatest = new TextRenderable(renderer, {
|
|
88
91
|
id: "details-latest",
|
|
89
|
-
content: "
|
|
92
|
+
content: "",
|
|
90
93
|
fg: "#94a3b8",
|
|
91
94
|
marginTop: 0,
|
|
92
95
|
});
|
|
93
96
|
content.add(detailsLatest);
|
|
94
97
|
|
|
98
|
+
const confirmButton = new TextRenderable(renderer, {
|
|
99
|
+
id: "confirm-button",
|
|
100
|
+
content: "",
|
|
101
|
+
fg: "#10b981",
|
|
102
|
+
marginTop: 1,
|
|
103
|
+
});
|
|
104
|
+
content.add(confirmButton);
|
|
105
|
+
|
|
95
106
|
repoInput.onPaste = (event) => {
|
|
96
107
|
const text = event.text.replace(/[\r\n]+/g, "");
|
|
97
108
|
if (!text) return;
|
|
98
109
|
repoInput.insertText(text);
|
|
99
110
|
currentInput = repoInput.value;
|
|
100
111
|
if (currentInput.trim()) {
|
|
101
|
-
|
|
112
|
+
const repo = validateRepo(currentInput.trim());
|
|
113
|
+
currentValidRepo = repo;
|
|
114
|
+
if (repo) {
|
|
115
|
+
currentReadOnly = false;
|
|
116
|
+
currentLatest = false;
|
|
117
|
+
}
|
|
102
118
|
} else {
|
|
103
119
|
statusText.content = "";
|
|
120
|
+
currentValidRepo = null;
|
|
104
121
|
}
|
|
122
|
+
selectedMatchIndex = -1;
|
|
123
|
+
lastQuery = currentInput;
|
|
124
|
+
updateMatches();
|
|
125
|
+
updateDetails();
|
|
105
126
|
event.preventDefault();
|
|
106
127
|
};
|
|
107
128
|
|
|
@@ -114,7 +135,7 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
114
135
|
});
|
|
115
136
|
content.add(statusText);
|
|
116
137
|
|
|
117
|
-
// Repos list
|
|
138
|
+
// Repos list with counter
|
|
118
139
|
const reposLabel = new TextRenderable(renderer, {
|
|
119
140
|
id: "repos-label",
|
|
120
141
|
content: "\nAdded repositories:",
|
|
@@ -131,15 +152,6 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
131
152
|
});
|
|
132
153
|
content.add(reposList);
|
|
133
154
|
|
|
134
|
-
// Instructions
|
|
135
|
-
const instructions = new TextRenderable(renderer, {
|
|
136
|
-
id: "instructions",
|
|
137
|
-
content: "\n[Enter] Next [Ctrl+D] Continue [↑/↓] History [Tab] Complete [Esc] Exit",
|
|
138
|
-
fg: "#64748b",
|
|
139
|
-
marginTop: 2,
|
|
140
|
-
});
|
|
141
|
-
content.add(instructions);
|
|
142
|
-
|
|
143
155
|
repoInput.focus();
|
|
144
156
|
|
|
145
157
|
function updateReposList() {
|
|
@@ -159,43 +171,55 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
159
171
|
}
|
|
160
172
|
|
|
161
173
|
function updateDetails() {
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
detailsLabel.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
174
|
+
if (isConfirming && currentValidRepo) {
|
|
175
|
+
// Confirmation mode with interactive checkboxes
|
|
176
|
+
detailsLabel.content = `\nConfigure options for: ${currentInput.trim()}`;
|
|
177
|
+
detailsLabel.fg = "#38bdf8";
|
|
178
|
+
|
|
179
|
+
const roCheckbox = currentReadOnly ? "[✓]" : "[ ]";
|
|
180
|
+
const roSelected = confirmOptionIndex === 0;
|
|
181
|
+
detailsReadOnly.content = ` ${roSelected ? "▶" : " "} ${roCheckbox} Read-only [r]`;
|
|
182
|
+
detailsReadOnly.fg = roSelected ? "#f8fafc" : currentReadOnly ? "#f59e0b" : "#94a3b8";
|
|
183
|
+
|
|
184
|
+
const latestCheckbox = currentLatest ? "[✓]" : "[ ]";
|
|
185
|
+
const latestSelected = confirmOptionIndex === 1;
|
|
186
|
+
detailsLatest.content = ` ${latestSelected ? "▶" : " "} ${latestCheckbox} Latest [l]`;
|
|
187
|
+
detailsLatest.fg = latestSelected ? "#f8fafc" : currentLatest ? "#22d3ee" : "#94a3b8";
|
|
188
|
+
|
|
189
|
+
const confirmSelected = confirmOptionIndex === 2;
|
|
190
|
+
confirmButton.content = ` ${confirmSelected ? "▶" : " "} [Add repository]`;
|
|
191
|
+
confirmButton.fg = confirmSelected ? "#10b981" : "#64748b";
|
|
192
|
+
} else if (currentValidRepo) {
|
|
193
|
+
detailsLabel.content = "\nPress Enter to configure options";
|
|
194
|
+
detailsLabel.fg = "#64748b";
|
|
195
|
+
detailsReadOnly.content = "";
|
|
196
|
+
detailsLatest.content = "";
|
|
197
|
+
confirmButton.content = "";
|
|
171
198
|
} else {
|
|
172
|
-
detailsLabel.content = "
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
detailsLatest.content = " Latest: No";
|
|
177
|
-
detailsLatest.fg = "#475569";
|
|
178
|
-
instructions.content =
|
|
179
|
-
"\n[Enter] Next [Ctrl+D] Continue [↑/↓] History [Tab] Complete [Esc] Cancel";
|
|
199
|
+
detailsLabel.content = "";
|
|
200
|
+
detailsReadOnly.content = "";
|
|
201
|
+
detailsLatest.content = "";
|
|
202
|
+
confirmButton.content = "";
|
|
180
203
|
}
|
|
181
204
|
}
|
|
182
205
|
|
|
183
|
-
function
|
|
184
|
-
const
|
|
185
|
-
if (!
|
|
186
|
-
matchesList.content = "(no matches)";
|
|
187
|
-
matchesList.fg = "#64748b";
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
206
|
+
function getMatchesForQuery(query: string): string[] {
|
|
207
|
+
const trimmed = query.trim();
|
|
208
|
+
if (!trimmed) return [];
|
|
190
209
|
|
|
191
|
-
const lowerQuery =
|
|
192
|
-
|
|
210
|
+
const lowerQuery = trimmed.toLowerCase();
|
|
211
|
+
return historySpecs
|
|
193
212
|
.filter((spec) => spec.toLowerCase().startsWith(lowerQuery))
|
|
194
213
|
.toSorted((a, b) => {
|
|
195
214
|
if (a.length !== b.length) return a.length - b.length;
|
|
196
215
|
return a.localeCompare(b);
|
|
197
216
|
})
|
|
198
217
|
.slice(0, maxMatches);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function updateMatches() {
|
|
221
|
+
const query = lastQuery; // Use the last query, not currentInput (which may be a selected match)
|
|
222
|
+
const matches = getMatchesForQuery(query);
|
|
199
223
|
|
|
200
224
|
if (matches.length === 0) {
|
|
201
225
|
matchesList.content = "(no matches)";
|
|
@@ -205,192 +229,287 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
205
229
|
|
|
206
230
|
matchesList.content = matches
|
|
207
231
|
.map((spec, index) => {
|
|
208
|
-
const isSelected =
|
|
232
|
+
const isSelected = selectedMatchIndex === index;
|
|
209
233
|
return `${isSelected ? "▶" : " "} ${spec}`;
|
|
210
234
|
})
|
|
211
235
|
.join("\n");
|
|
212
236
|
matchesList.fg = "#94a3b8";
|
|
213
237
|
}
|
|
214
238
|
|
|
215
|
-
function
|
|
216
|
-
const query = value.trim();
|
|
217
|
-
if (!query) return [];
|
|
218
|
-
const lowerQuery = query.toLowerCase();
|
|
219
|
-
return historySpecs
|
|
220
|
-
.filter((spec) => spec.toLowerCase().startsWith(lowerQuery))
|
|
221
|
-
.toSorted((a, b) => {
|
|
222
|
-
if (a.length !== b.length) return a.length - b.length;
|
|
223
|
-
return a.localeCompare(b);
|
|
224
|
-
})
|
|
225
|
-
.slice(0, maxMatches);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function validateAndAddRepo(spec: string): boolean {
|
|
239
|
+
function validateRepo(spec: string): RepoSpec | null {
|
|
229
240
|
try {
|
|
230
|
-
parseRepoSpec(spec);
|
|
241
|
+
const repo = parseRepoSpec(spec);
|
|
231
242
|
statusText.content = "✓ Valid format";
|
|
232
243
|
statusText.fg = "#10b981";
|
|
233
|
-
return
|
|
244
|
+
return repo;
|
|
234
245
|
} catch (error) {
|
|
235
246
|
statusText.content = `✗ ${error instanceof Error ? error.message : "Invalid format"}`;
|
|
236
247
|
statusText.fg = "#ef4444";
|
|
237
|
-
return
|
|
248
|
+
return null;
|
|
238
249
|
}
|
|
239
250
|
}
|
|
240
251
|
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (spec.trim()) {
|
|
245
|
-
validateAndAddRepo(spec.trim());
|
|
246
|
-
} else {
|
|
247
|
-
statusText.content = "";
|
|
248
|
-
}
|
|
249
|
-
updateMatches(currentInput);
|
|
250
|
-
}
|
|
252
|
+
function selectMatch(matchIndex: number) {
|
|
253
|
+
const matches = getMatchesForQuery(lastQuery);
|
|
254
|
+
if (matchIndex < 0 || matchIndex >= matches.length) return;
|
|
251
255
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (!spec) return;
|
|
256
|
+
const selectedMatch = matches[matchIndex];
|
|
257
|
+
if (!selectedMatch) return;
|
|
255
258
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
selectedMatchIndex = matchIndex;
|
|
260
|
+
isNavigating = true; // Set flag to prevent input handler from resetting
|
|
261
|
+
|
|
262
|
+
// Update input with selected match
|
|
263
|
+
repoInput.value = selectedMatch;
|
|
264
|
+
repoInput.cursorPosition = selectedMatch.length; // Set cursor to end
|
|
265
|
+
currentInput = selectedMatch;
|
|
263
266
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
267
|
+
// Validate and update details
|
|
268
|
+
const repo = validateRepo(selectedMatch.trim());
|
|
269
|
+
currentValidRepo = repo;
|
|
270
|
+
if (repo) {
|
|
271
|
+
currentReadOnly = false;
|
|
272
|
+
currentLatest = false;
|
|
270
273
|
}
|
|
274
|
+
|
|
275
|
+
updateMatches(); // Refresh display with new selection (matches stay the same, just highlight changes)
|
|
276
|
+
updateDetails();
|
|
277
|
+
|
|
278
|
+
isNavigating = false; // Reset flag
|
|
271
279
|
}
|
|
272
280
|
|
|
273
|
-
function
|
|
274
|
-
if (!
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
281
|
+
function addCurrentRepo() {
|
|
282
|
+
if (!currentValidRepo) return;
|
|
283
|
+
|
|
284
|
+
const spec = currentInput.trim();
|
|
285
|
+
// Check if already added
|
|
286
|
+
if (repos.some((r) => r.spec === spec)) {
|
|
287
|
+
statusText.content = "⚠️ Repository already added";
|
|
288
|
+
statusText.fg = "#f59e0b";
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const repoToAdd = { ...currentValidRepo };
|
|
293
|
+
repoToAdd.readOnly = currentReadOnly;
|
|
294
|
+
repoToAdd.latest = currentLatest;
|
|
295
|
+
repos.push(repoToAdd);
|
|
278
296
|
|
|
279
|
-
pendingRepo = null;
|
|
280
|
-
pendingReadOnly = false;
|
|
281
|
-
pendingLatest = false;
|
|
282
|
-
mode = "spec";
|
|
283
297
|
currentInput = "";
|
|
284
298
|
repoInput.value = "";
|
|
285
|
-
|
|
299
|
+
currentValidRepo = null;
|
|
300
|
+
currentReadOnly = false;
|
|
301
|
+
currentLatest = false;
|
|
286
302
|
updateReposList();
|
|
287
|
-
|
|
303
|
+
lastQuery = "";
|
|
304
|
+
selectedMatchIndex = -1;
|
|
305
|
+
updateMatches();
|
|
288
306
|
updateDetails();
|
|
289
|
-
lastMatchIndex = -1;
|
|
290
|
-
lastMatchQuery = "";
|
|
291
307
|
|
|
292
|
-
statusText.content = "
|
|
293
|
-
|
|
308
|
+
statusText.content = "";
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function enterConfirmMode() {
|
|
312
|
+
isConfirming = true;
|
|
313
|
+
confirmOptionIndex = 2; // Start on confirm button for quick add
|
|
314
|
+
repoInput.blur();
|
|
315
|
+
updateDetails();
|
|
316
|
+
updateFooter();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function exitConfirmMode() {
|
|
320
|
+
isConfirming = false;
|
|
321
|
+
confirmOptionIndex = 0;
|
|
322
|
+
repoInput.focus();
|
|
323
|
+
updateDetails();
|
|
324
|
+
updateFooter();
|
|
325
|
+
}
|
|
294
326
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
327
|
+
function toggleCurrentOption() {
|
|
328
|
+
if (confirmOptionIndex === 0) {
|
|
329
|
+
// Toggle read-only
|
|
330
|
+
currentReadOnly = !currentReadOnly;
|
|
331
|
+
if (!currentReadOnly) {
|
|
332
|
+
currentLatest = false;
|
|
298
333
|
}
|
|
299
|
-
}
|
|
334
|
+
} else if (confirmOptionIndex === 1) {
|
|
335
|
+
// Toggle latest
|
|
336
|
+
currentLatest = !currentLatest;
|
|
337
|
+
if (currentLatest) {
|
|
338
|
+
currentReadOnly = true;
|
|
339
|
+
}
|
|
340
|
+
} else if (confirmOptionIndex === 2) {
|
|
341
|
+
// Confirm button - add the repo
|
|
342
|
+
addCurrentRepo();
|
|
343
|
+
exitConfirmMode();
|
|
344
|
+
}
|
|
345
|
+
updateDetails();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function updateFooter() {
|
|
349
|
+
if (isConfirming) {
|
|
350
|
+
showFooter(renderer, content, {
|
|
351
|
+
navigate: true,
|
|
352
|
+
select: false,
|
|
353
|
+
back: true,
|
|
354
|
+
custom: ["r Read-only", "l Latest", "space Toggle", "enter Add"],
|
|
355
|
+
});
|
|
356
|
+
} else {
|
|
357
|
+
showFooter(renderer, content, {
|
|
358
|
+
navigate: true,
|
|
359
|
+
select: true,
|
|
360
|
+
back: true,
|
|
361
|
+
custom: repos.length > 0 ? ["enter (empty) Continue"] : [],
|
|
362
|
+
});
|
|
363
|
+
}
|
|
300
364
|
}
|
|
301
365
|
|
|
302
366
|
const handleKeypress = (key: KeyEvent) => {
|
|
303
|
-
|
|
367
|
+
// Escape behavior differs based on mode
|
|
368
|
+
if (isEscape(key)) {
|
|
369
|
+
if (isConfirming) {
|
|
370
|
+
// Exit confirmation mode, go back to input
|
|
371
|
+
exitConfirmMode();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
cleanup();
|
|
375
|
+
resolve({ repos, cancelled: true });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Confirmation mode handling
|
|
380
|
+
if (isConfirming) {
|
|
381
|
+
// Toggle read-only with 'r' hotkey
|
|
304
382
|
if (key.name === "r") {
|
|
305
|
-
|
|
306
|
-
if (!
|
|
307
|
-
|
|
383
|
+
currentReadOnly = !currentReadOnly;
|
|
384
|
+
if (!currentReadOnly) {
|
|
385
|
+
currentLatest = false;
|
|
308
386
|
}
|
|
309
387
|
updateDetails();
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Toggle latest with 'l' hotkey
|
|
392
|
+
if (key.name === "l") {
|
|
393
|
+
currentLatest = !currentLatest;
|
|
394
|
+
if (currentLatest) {
|
|
395
|
+
currentReadOnly = true;
|
|
314
396
|
}
|
|
315
397
|
updateDetails();
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Space to toggle current option
|
|
402
|
+
if (key.name === "space") {
|
|
403
|
+
toggleCurrentOption();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Enter to confirm/add
|
|
408
|
+
if (isEnter(key)) {
|
|
409
|
+
addCurrentRepo();
|
|
410
|
+
exitConfirmMode();
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Arrow keys to navigate options
|
|
415
|
+
if (isArrowUp(key)) {
|
|
416
|
+
confirmOptionIndex = Math.max(0, confirmOptionIndex - 1);
|
|
322
417
|
updateDetails();
|
|
323
|
-
|
|
324
|
-
confirmAddRepo();
|
|
418
|
+
return;
|
|
325
419
|
}
|
|
326
|
-
} else {
|
|
327
|
-
// Spec input mode
|
|
328
|
-
if (key.name === "escape") {
|
|
329
|
-
cleanup();
|
|
330
|
-
resolve({ repos, cancelled: true });
|
|
331
|
-
} else if (key.name === "return" || key.name === "enter") {
|
|
332
|
-
startRepoDetails();
|
|
333
|
-
} else if (key.name === "tab") {
|
|
334
|
-
const query = currentInput.trim();
|
|
335
|
-
const matches = getMatches(query);
|
|
336
|
-
if (matches.length === 0) return;
|
|
337
|
-
|
|
338
|
-
const currentIndex = matches.findIndex((spec) => spec === currentInput);
|
|
339
|
-
let nextIndex = 0;
|
|
340
|
-
|
|
341
|
-
if (currentIndex !== -1) {
|
|
342
|
-
nextIndex = (currentIndex + 1) % matches.length;
|
|
343
|
-
} else if (lastMatchQuery === query.toLowerCase() && lastMatchIndex >= 0) {
|
|
344
|
-
nextIndex = (lastMatchIndex + 1) % matches.length;
|
|
345
|
-
}
|
|
346
420
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const spec = historySpecs[historyIndex];
|
|
365
|
-
if (spec) applyHistorySpec(spec);
|
|
366
|
-
} else if (key.name === "d" && key.ctrl) {
|
|
367
|
-
// Ctrl+D to finish
|
|
421
|
+
if (isArrowDown(key)) {
|
|
422
|
+
confirmOptionIndex = Math.min(2, confirmOptionIndex + 1);
|
|
423
|
+
updateDetails();
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Normal input mode handling
|
|
431
|
+
|
|
432
|
+
// Enter to enter confirmation mode or continue
|
|
433
|
+
if (isEnter(key)) {
|
|
434
|
+
if (currentInput.trim() && currentValidRepo) {
|
|
435
|
+
enterConfirmMode();
|
|
436
|
+
} else if (!currentInput.trim() && repos.length > 0) {
|
|
437
|
+
// Empty input + repos added = continue
|
|
368
438
|
cleanup();
|
|
369
439
|
resolve({ repos, cancelled: false });
|
|
370
440
|
}
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Arrow keys for navigating matches
|
|
445
|
+
if (isArrowUp(key)) {
|
|
446
|
+
const matches = getMatchesForQuery(lastQuery);
|
|
447
|
+
if (matches.length === 0) return;
|
|
448
|
+
|
|
449
|
+
if (selectedMatchIndex < 0) {
|
|
450
|
+
// Start from last match
|
|
451
|
+
selectedMatchIndex = matches.length - 1;
|
|
452
|
+
} else {
|
|
453
|
+
// Move up
|
|
454
|
+
selectedMatchIndex = Math.max(0, selectedMatchIndex - 1);
|
|
455
|
+
}
|
|
456
|
+
selectMatch(selectedMatchIndex);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (isArrowDown(key)) {
|
|
461
|
+
const matches = getMatchesForQuery(lastQuery);
|
|
462
|
+
if (matches.length === 0) return;
|
|
463
|
+
|
|
464
|
+
if (selectedMatchIndex < 0) {
|
|
465
|
+
// Start from first match
|
|
466
|
+
selectedMatchIndex = 0;
|
|
467
|
+
} else {
|
|
468
|
+
// Move down
|
|
469
|
+
selectedMatchIndex = Math.min(matches.length - 1, selectedMatchIndex + 1);
|
|
470
|
+
}
|
|
471
|
+
selectMatch(selectedMatchIndex);
|
|
472
|
+
return;
|
|
371
473
|
}
|
|
372
474
|
};
|
|
373
475
|
|
|
374
476
|
repoInput.on("input", (value: string) => {
|
|
375
477
|
currentInput = value;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
478
|
+
|
|
479
|
+
// If we're navigating (programmatically setting value), don't reset selection
|
|
480
|
+
if (isNavigating) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// User is typing - reset selected match index and update query
|
|
485
|
+
selectedMatchIndex = -1;
|
|
486
|
+
lastQuery = value; // Update the query that generates matches
|
|
487
|
+
|
|
379
488
|
if (value.trim()) {
|
|
380
|
-
|
|
489
|
+
const repo = validateRepo(value.trim());
|
|
490
|
+
currentValidRepo = repo;
|
|
491
|
+
if (repo) {
|
|
492
|
+
currentReadOnly = false;
|
|
493
|
+
currentLatest = false;
|
|
494
|
+
}
|
|
381
495
|
} else {
|
|
382
496
|
statusText.content = "";
|
|
497
|
+
currentValidRepo = null;
|
|
383
498
|
}
|
|
384
|
-
updateMatches(
|
|
499
|
+
updateMatches(); // Update matches based on new lastQuery
|
|
500
|
+
updateDetails();
|
|
385
501
|
});
|
|
386
502
|
|
|
387
503
|
const cleanup = () => {
|
|
388
504
|
renderer.keyInput.off("keypress", handleKeypress);
|
|
389
505
|
repoInput.blur();
|
|
506
|
+
hideFooter(renderer);
|
|
390
507
|
clearLayout(renderer);
|
|
391
508
|
};
|
|
392
509
|
|
|
393
510
|
renderer.keyInput.on("keypress", handleKeypress);
|
|
394
511
|
updateDetails();
|
|
512
|
+
updateReposList();
|
|
513
|
+
updateFooter();
|
|
395
514
|
});
|
|
396
515
|
}
|