github-search-cli 9.4.2 → 9.4.3
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/CHANGELOG.md +26 -4
- package/README.md +8 -3
- package/bin/ghfind.js +98 -89
- package/completions/ghfind.bash +1 -1
- package/completions/ghfind.fish +3 -1
- package/completions/ghfind.zsh +6 -0
- package/dist/{chunk-IVUU2KGR.js → chunk-GYIL4S5X.js} +4 -1
- package/dist/cli.js +761 -342
- package/dist/{config-GNZOQRTH.js → config-OCIPF2IF.js} +1 -1
- package/package.json +6 -3
- package/release-notes/9.3.0.md +32 -29
- package/scripts/build.ts +34 -19
- package/scripts/generate-svg-demo.js +218 -168
- package/scripts/postinstall.js +176 -176
- package/src/bookmarks.ts +69 -69
- package/src/cli.ts +17 -1
- package/src/compare.ts +69 -47
- package/src/config.ts +70 -65
- package/src/deepdive.ts +149 -118
- package/src/doctor.ts +287 -287
- package/src/errors.ts +62 -59
- package/src/explore.ts +34 -29
- package/src/export.ts +73 -63
- package/src/help.ts +157 -157
- package/src/history.ts +69 -55
- package/src/init.ts +60 -55
- package/src/landing.ts +106 -104
- package/src/login.ts +186 -0
- package/src/markdown-render.ts +1 -6
- package/src/notifications.ts +75 -59
- package/src/open-url.ts +16 -16
- package/src/org.ts +43 -15
- package/src/output.ts +153 -137
- package/src/package-browser.ts +317 -305
- package/src/package.ts +149 -137
- package/src/pipe.ts +60 -54
- package/src/releases.ts +182 -144
- package/src/saved-searches.ts +72 -50
- package/src/search-state.ts +283 -229
- package/src/search.ts +678 -653
- package/src/session.ts +26 -24
- package/src/share.ts +61 -54
- package/src/status.ts +68 -61
- package/src/storage.ts +136 -106
- package/src/themes.ts +259 -116
- package/src/tips.ts +42 -42
- package/src/toast.ts +70 -69
- package/src/trending-parser.ts +104 -0
- package/src/trending.ts +481 -462
- package/src/tui.ts +149 -153
- package/src/types.ts +160 -158
- package/src/watch.ts +95 -74
package/src/package-browser.ts
CHANGED
|
@@ -1,305 +1,317 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/** package-browser.ts — "Packages" browse/list view for the ghfind CLI. */
|
|
3
|
-
import {
|
|
4
|
-
createCliRenderer,
|
|
5
|
-
BoxRenderable,
|
|
6
|
-
TextRenderable,
|
|
7
|
-
ScrollBoxRenderable,
|
|
8
|
-
StyledText,
|
|
9
|
-
bold,
|
|
10
|
-
fg,
|
|
11
|
-
dim,
|
|
12
|
-
t,
|
|
13
|
-
} from "@opentui/core";
|
|
14
|
-
import type { TextChunk, CliRenderer } from "@opentui/core";
|
|
15
|
-
import type { Package } from "./types";
|
|
16
|
-
import { openUrl } from "./open-url";
|
|
17
|
-
import { createPackageSearch } from "./package";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
backgroundColor: C.bg,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
outerBox.add(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** package-browser.ts — "Packages" browse/list view for the ghfind CLI. */
|
|
3
|
+
import {
|
|
4
|
+
createCliRenderer,
|
|
5
|
+
BoxRenderable,
|
|
6
|
+
TextRenderable,
|
|
7
|
+
ScrollBoxRenderable,
|
|
8
|
+
StyledText,
|
|
9
|
+
bold,
|
|
10
|
+
fg,
|
|
11
|
+
dim,
|
|
12
|
+
t,
|
|
13
|
+
} from "@opentui/core";
|
|
14
|
+
import type { TextChunk, CliRenderer } from "@opentui/core";
|
|
15
|
+
import type { Package } from "./types";
|
|
16
|
+
import { openUrl } from "./open-url";
|
|
17
|
+
import { createPackageSearch } from "./package";
|
|
18
|
+
import { detectTerminalBackground, deriveSurfaceLayers } from "./themes";
|
|
19
|
+
const C = {
|
|
20
|
+
bg: "#1a1b26",
|
|
21
|
+
surface: "#16161e",
|
|
22
|
+
text: "#a9b1d6",
|
|
23
|
+
muted: "#565f89",
|
|
24
|
+
cyan: "#7dcfff",
|
|
25
|
+
purple: "#bb9af7",
|
|
26
|
+
green: "#9ece6a",
|
|
27
|
+
gold: "#e0af68",
|
|
28
|
+
orange: "#ff9e64",
|
|
29
|
+
red: "#f7768e",
|
|
30
|
+
blue: "#7aa2f7",
|
|
31
|
+
lightBlue: "#b4f9f8",
|
|
32
|
+
selectionBg: "#3b6ce8",
|
|
33
|
+
selectionText: "#c0caf5",
|
|
34
|
+
border: "#363b54",
|
|
35
|
+
descText: "#6c7086", // lighter muted for descriptions
|
|
36
|
+
nameText: "#c0caf5", // bright for package names
|
|
37
|
+
};
|
|
38
|
+
// ─── Number formatting ────────────────────────────────────────────────
|
|
39
|
+
const INT_FMT = new Intl.NumberFormat("en-US");
|
|
40
|
+
/** 523219736 → "523,219,736" (full downloads tally, no compaction) */
|
|
41
|
+
function fmtCount(n: number): string {
|
|
42
|
+
return INT_FMT.format(Math.trunc(n));
|
|
43
|
+
}
|
|
44
|
+
// ─── Row formatting ───────────────────────────────────────────────────
|
|
45
|
+
/** Format one package as a 2-line StyledText (name line + description line). */
|
|
46
|
+
function formatPackageLine(pack: Package): StyledText {
|
|
47
|
+
const nameStr = `${pack.name}@${pack.version}`.padEnd(28).slice(0, 28);
|
|
48
|
+
const dlStr = `↓ ${fmtCount(pack.downloads)}`.padEnd(20).slice(0, 20);
|
|
49
|
+
const scoreStr = `score ${pack.score.toFixed(2)}`.padEnd(12).slice(0, 12);
|
|
50
|
+
const desc =
|
|
51
|
+
(pack.description ?? "").length > 60
|
|
52
|
+
? (pack.description ?? "").slice(0, 57) + "..."
|
|
53
|
+
: (pack.description ?? "");
|
|
54
|
+
const line1 = t`${bold(fg(C.nameText)(nameStr))}${fg(C.cyan)(dlStr)}${fg(C.gold)(scoreStr)}`;
|
|
55
|
+
const line2 = t`${dim(fg(C.descText)(` ${desc}`))}`;
|
|
56
|
+
const newline: TextChunk = { text: "\n", __isChunk: true as const };
|
|
57
|
+
const chunks: TextChunk[] = [...line1.chunks, newline, ...line2.chunks];
|
|
58
|
+
return new StyledText(chunks);
|
|
59
|
+
}
|
|
60
|
+
// ─── Main TUI entry ───────────────────────────────────────────────────
|
|
61
|
+
export async function launchPackageBrowser(): Promise<void> {
|
|
62
|
+
// Match the app backdrop to the terminal's own background (OSC 11), before
|
|
63
|
+
// the renderer takes over stdin. Falls back to the local palette default.
|
|
64
|
+
// Surface and border layers are blended toward that background as well.
|
|
65
|
+
const terminalBg = await detectTerminalBackground();
|
|
66
|
+
if (terminalBg) {
|
|
67
|
+
C.bg = terminalBg;
|
|
68
|
+
Object.assign(C, deriveSurfaceLayers(terminalBg));
|
|
69
|
+
}
|
|
70
|
+
let renderer: CliRenderer;
|
|
71
|
+
try {
|
|
72
|
+
renderer = await createCliRenderer();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error(
|
|
75
|
+
"Failed to start the interactive browser.\n",
|
|
76
|
+
`Reason: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
77
|
+
);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const root = renderer.root;
|
|
82
|
+
root.flexDirection = "column";
|
|
83
|
+
// ── State ──
|
|
84
|
+
let query = "";
|
|
85
|
+
let dirty = false; // input edited since last search → Enter re-runs the search
|
|
86
|
+
let packages: Package[] = [];
|
|
87
|
+
let selectedIdx = 0;
|
|
88
|
+
let isLoading = false;
|
|
89
|
+
// ── Outer frame ──
|
|
90
|
+
const outerBox = new BoxRenderable(renderer, {
|
|
91
|
+
flexGrow: 1,
|
|
92
|
+
flexDirection: "column",
|
|
93
|
+
backgroundColor: C.bg,
|
|
94
|
+
border: true,
|
|
95
|
+
borderColor: C.border,
|
|
96
|
+
borderStyle: "rounded",
|
|
97
|
+
paddingX: 0,
|
|
98
|
+
paddingY: 0,
|
|
99
|
+
});
|
|
100
|
+
// ── Header bar ──
|
|
101
|
+
const headerBox = new BoxRenderable(renderer, {
|
|
102
|
+
height: 1,
|
|
103
|
+
backgroundColor: C.bg,
|
|
104
|
+
paddingX: 1,
|
|
105
|
+
});
|
|
106
|
+
const headerText = new TextRenderable(renderer, {
|
|
107
|
+
content: t`${bold(fg(C.purple)("⟠ tulipsearch"))}${fg(C.muted)(" — Packages")}`,
|
|
108
|
+
fg: C.purple,
|
|
109
|
+
bg: C.bg,
|
|
110
|
+
height: 1,
|
|
111
|
+
});
|
|
112
|
+
headerBox.add(headerText);
|
|
113
|
+
outerBox.add(headerBox);
|
|
114
|
+
// ── Query input line ──
|
|
115
|
+
const inputBox = new BoxRenderable(renderer, {
|
|
116
|
+
height: 1,
|
|
117
|
+
flexDirection: "row",
|
|
118
|
+
backgroundColor: C.surface,
|
|
119
|
+
paddingX: 1,
|
|
120
|
+
});
|
|
121
|
+
let inputText: TextRenderable | null = null;
|
|
122
|
+
function renderInput() {
|
|
123
|
+
if (inputText) inputBox.remove(inputText);
|
|
124
|
+
const cursor = isLoading ? " " : "▊";
|
|
125
|
+
inputText = new TextRenderable(renderer, {
|
|
126
|
+
content: t`${fg(C.cyan)("❯ ")}${query}${dim(fg(C.muted)(cursor))}`,
|
|
127
|
+
fg: C.text,
|
|
128
|
+
bg: C.surface,
|
|
129
|
+
height: 1,
|
|
130
|
+
});
|
|
131
|
+
inputBox.add(inputText);
|
|
132
|
+
}
|
|
133
|
+
// ── Status line (loading / error / result count) ──
|
|
134
|
+
const statusBox = new BoxRenderable(renderer, {
|
|
135
|
+
height: 1,
|
|
136
|
+
backgroundColor: C.bg,
|
|
137
|
+
paddingX: 1,
|
|
138
|
+
});
|
|
139
|
+
let statusText: TextRenderable | null = null;
|
|
140
|
+
function setStatus(cfg: { content: StyledText; color?: string }) {
|
|
141
|
+
if (statusText) statusBox.remove(statusText);
|
|
142
|
+
statusText = new TextRenderable(renderer, {
|
|
143
|
+
content: cfg.content,
|
|
144
|
+
fg: cfg.color ?? C.text,
|
|
145
|
+
bg: C.bg,
|
|
146
|
+
height: 1,
|
|
147
|
+
});
|
|
148
|
+
statusBox.add(statusText);
|
|
149
|
+
}
|
|
150
|
+
// ── Package list (scrollable) ──
|
|
151
|
+
const scrollBox = new ScrollBoxRenderable(renderer, {
|
|
152
|
+
flexGrow: 1,
|
|
153
|
+
backgroundColor: C.bg,
|
|
154
|
+
scrollY: true,
|
|
155
|
+
scrollX: false,
|
|
156
|
+
viewportCulling: true,
|
|
157
|
+
});
|
|
158
|
+
const rowBoxes: BoxRenderable[] = [];
|
|
159
|
+
const rowTexts: TextRenderable[] = [];
|
|
160
|
+
function clearRows() {
|
|
161
|
+
for (const rb of rowBoxes) scrollBox.remove(rb);
|
|
162
|
+
rowBoxes.length = 0;
|
|
163
|
+
rowTexts.length = 0;
|
|
164
|
+
}
|
|
165
|
+
function rebuildList() {
|
|
166
|
+
clearRows();
|
|
167
|
+
packages.forEach((p, i) => {
|
|
168
|
+
const isEven = i % 2 === 0;
|
|
169
|
+
const isSelected = i === selectedIdx;
|
|
170
|
+
const rowBg = isSelected ? C.selectionBg : isEven ? C.bg : C.surface;
|
|
171
|
+
const rowText = new TextRenderable(renderer, {
|
|
172
|
+
content: formatPackageLine(p),
|
|
173
|
+
bg: rowBg,
|
|
174
|
+
height: 2,
|
|
175
|
+
});
|
|
176
|
+
const rowBox = new BoxRenderable(renderer, {
|
|
177
|
+
flexDirection: "row",
|
|
178
|
+
backgroundColor: rowBg,
|
|
179
|
+
});
|
|
180
|
+
rowBox.add(rowText);
|
|
181
|
+
rowBoxes.push(rowBox);
|
|
182
|
+
rowTexts.push(rowText);
|
|
183
|
+
scrollBox.add(rowBox);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// ── Footer bar ──
|
|
187
|
+
const footerBox = new BoxRenderable(renderer, {
|
|
188
|
+
height: 1,
|
|
189
|
+
backgroundColor: C.bg,
|
|
190
|
+
paddingX: 1,
|
|
191
|
+
});
|
|
192
|
+
const footerText = new TextRenderable(renderer, {
|
|
193
|
+
content: " type query ↵ search ↑↓ navigate ↵ open q/esc quit",
|
|
194
|
+
fg: C.muted,
|
|
195
|
+
bg: C.bg,
|
|
196
|
+
height: 1,
|
|
197
|
+
});
|
|
198
|
+
footerBox.add(footerText);
|
|
199
|
+
outerBox.add(footerBox);
|
|
200
|
+
root.add(outerBox);
|
|
201
|
+
outerBox.add(inputBox);
|
|
202
|
+
outerBox.add(statusBox);
|
|
203
|
+
const divider = new TextRenderable(renderer, {
|
|
204
|
+
content: "─".repeat(80),
|
|
205
|
+
fg: C.border,
|
|
206
|
+
bg: C.bg,
|
|
207
|
+
height: 1,
|
|
208
|
+
});
|
|
209
|
+
outerBox.add(divider);
|
|
210
|
+
outerBox.add(scrollBox);
|
|
211
|
+
// ── Fetch ──
|
|
212
|
+
async function runSearch() {
|
|
213
|
+
const q = query.trim();
|
|
214
|
+
if (!q) return;
|
|
215
|
+
isLoading = true;
|
|
216
|
+
dirty = false;
|
|
217
|
+
clearRows();
|
|
218
|
+
setStatus({
|
|
219
|
+
content: t`${dim(fg(C.muted)(` Loading packages for "${q}"...`))}`,
|
|
220
|
+
});
|
|
221
|
+
renderer.requestRender();
|
|
222
|
+
try {
|
|
223
|
+
const searcher = createPackageSearch();
|
|
224
|
+
const res = await searcher.searchPackage(q, 25);
|
|
225
|
+
packages = res.packages;
|
|
226
|
+
selectedIdx = 0;
|
|
227
|
+
rebuildList();
|
|
228
|
+
setStatus({
|
|
229
|
+
content: t`${fg(C.muted)(` ${packages.length} of ${fmtCount(res.totalCount)} results for `)}${fg(C.cyan)(`"${q}"`)}`,
|
|
230
|
+
});
|
|
231
|
+
} catch (err) {
|
|
232
|
+
packages = [];
|
|
233
|
+
clearRows();
|
|
234
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
235
|
+
setStatus({
|
|
236
|
+
content: t`${fg(C.red)(` Error: ${msg.slice(0, 70)} — press ↵ to retry`)}`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
isLoading = false;
|
|
240
|
+
renderInput();
|
|
241
|
+
renderer.requestRender();
|
|
242
|
+
}
|
|
243
|
+
// ── Helpers ──
|
|
244
|
+
function openSelectedPackage() {
|
|
245
|
+
if (selectedIdx < 0 || selectedIdx >= packages.length) return;
|
|
246
|
+
const p = packages[selectedIdx];
|
|
247
|
+
openUrl(p.url);
|
|
248
|
+
}
|
|
249
|
+
function moveSelection(delta: number) {
|
|
250
|
+
const oldIdx = selectedIdx;
|
|
251
|
+
const newIdx = Math.max(0, Math.min(packages.length - 1, oldIdx + delta));
|
|
252
|
+
if (newIdx === oldIdx) return;
|
|
253
|
+
selectedIdx = newIdx;
|
|
254
|
+
const setBg = (idx: number, selected: boolean) => {
|
|
255
|
+
if (idx < 0 || idx >= rowBoxes.length) return;
|
|
256
|
+
const bg = selected ? C.selectionBg : idx % 2 === 0 ? C.bg : C.surface;
|
|
257
|
+
rowBoxes[idx].backgroundColor = bg;
|
|
258
|
+
if (rowTexts[idx]) rowTexts[idx].bg = bg;
|
|
259
|
+
};
|
|
260
|
+
setBg(oldIdx, false);
|
|
261
|
+
setBg(newIdx, true);
|
|
262
|
+
renderer.requestRender();
|
|
263
|
+
}
|
|
264
|
+
// ── Global key events ──
|
|
265
|
+
renderer.keyInput.on("keypress", (key: import("@opentui/core").KeyEvent) => {
|
|
266
|
+
const name = key.name || "";
|
|
267
|
+
if (name === "q" || name === "escape") {
|
|
268
|
+
process.exit(0);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (name === "backspace") {
|
|
272
|
+
query = query.slice(0, -1);
|
|
273
|
+
dirty = true;
|
|
274
|
+
renderInput();
|
|
275
|
+
renderer.requestRender();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (name === "space") {
|
|
279
|
+
query += " ";
|
|
280
|
+
dirty = true;
|
|
281
|
+
renderInput();
|
|
282
|
+
renderer.requestRender();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
// Printable single-char inputs edit the query.
|
|
286
|
+
if (name.length === 1) {
|
|
287
|
+
query += name;
|
|
288
|
+
dirty = true;
|
|
289
|
+
renderInput();
|
|
290
|
+
renderer.requestRender();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (name === "up" || name === "k") {
|
|
294
|
+
moveSelection(-1);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (name === "down" || name === "j") {
|
|
298
|
+
moveSelection(1);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (name === "enter" || name === "return") {
|
|
302
|
+
if (dirty || packages.length === 0) {
|
|
303
|
+
runSearch();
|
|
304
|
+
} else {
|
|
305
|
+
openSelectedPackage();
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
// ── Start ──
|
|
311
|
+
renderer.start();
|
|
312
|
+
renderInput();
|
|
313
|
+
setStatus({
|
|
314
|
+
content: t`${fg(C.muted)(" Type a package query above and press ↵ to search")}`,
|
|
315
|
+
});
|
|
316
|
+
renderer.requestRender();
|
|
317
|
+
}
|