pi-extmgr 0.1.27 → 0.2.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 +21 -10
- package/package.json +21 -16
- package/src/commands/auto-update.ts +5 -5
- package/src/commands/cache.ts +1 -1
- package/src/commands/history.ts +5 -34
- package/src/commands/install.ts +2 -2
- package/src/commands/registry.ts +7 -7
- package/src/commands/types.ts +1 -1
- package/src/constants.ts +0 -8
- package/src/extensions/discovery.ts +125 -42
- package/src/index.ts +15 -15
- package/src/packages/catalog.ts +9 -8
- package/src/packages/discovery.ts +56 -19
- package/src/packages/extensions.ts +65 -103
- package/src/packages/install.ts +104 -74
- package/src/packages/management.ts +78 -65
- package/src/types/index.ts +20 -11
- package/src/ui/async-task.ts +101 -65
- package/src/ui/footer.ts +47 -31
- package/src/ui/help.ts +17 -13
- package/src/ui/package-config.ts +36 -48
- package/src/ui/remote.ts +714 -119
- package/src/ui/theme.ts +2 -2
- package/src/ui/unified.ts +964 -371
- package/src/utils/auto-update.ts +44 -39
- package/src/utils/cache.ts +208 -37
- package/src/utils/command.ts +1 -1
- package/src/utils/duration.ts +132 -0
- package/src/utils/format.ts +4 -33
- package/src/utils/fs.ts +8 -4
- package/src/utils/history.ts +47 -9
- package/src/utils/mode.ts +2 -2
- package/src/utils/notify.ts +1 -15
- package/src/utils/npm-exec.ts +1 -1
- package/src/utils/package-source.ts +35 -7
- package/src/utils/path-identity.ts +7 -0
- package/src/utils/relative-path-selection.ts +100 -0
- package/src/utils/settings.ts +11 -61
- package/src/utils/status.ts +12 -10
- package/src/utils/ui-helpers.ts +2 -2
- package/src/utils/retry.ts +0 -49
package/src/ui/remote.ts
CHANGED
|
@@ -1,23 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Remote package browsing UI
|
|
3
3
|
*/
|
|
4
|
-
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
5
|
-
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
|
6
|
-
import { Container, SelectList, Text, type SelectItem } from "@mariozechner/pi-tui";
|
|
7
|
-
import type { BrowseAction, NpmPackage } from "../types/index.js";
|
|
8
|
-
import { PAGE_SIZE, TIMEOUTS, CACHE_LIMITS } from "../constants.js";
|
|
9
|
-
import { truncate, dynamicTruncate, formatBytes } from "../utils/format.js";
|
|
10
|
-
import { parseChoiceByLabel, splitCommandArgs } from "../utils/command.js";
|
|
11
4
|
import {
|
|
12
|
-
|
|
5
|
+
DynamicBorder,
|
|
6
|
+
type ExtensionAPI,
|
|
7
|
+
type ExtensionCommandContext,
|
|
8
|
+
type Theme,
|
|
9
|
+
} from "@mariozechner/pi-coding-agent";
|
|
10
|
+
import {
|
|
11
|
+
Container,
|
|
12
|
+
fuzzyMatch,
|
|
13
|
+
type Focusable,
|
|
14
|
+
getKeybindings,
|
|
15
|
+
Input,
|
|
16
|
+
Key,
|
|
17
|
+
matchesKey,
|
|
18
|
+
Spacer,
|
|
19
|
+
Text,
|
|
20
|
+
truncateToWidth,
|
|
21
|
+
wrapTextWithAnsi,
|
|
22
|
+
} from "@mariozechner/pi-tui";
|
|
23
|
+
import { CACHE_LIMITS, PAGE_SIZE, TIMEOUTS, UI } from "../constants.js";
|
|
24
|
+
import {
|
|
25
|
+
clearSearchCache,
|
|
13
26
|
getSearchCache,
|
|
14
|
-
setSearchCache,
|
|
15
27
|
isCacheValid,
|
|
28
|
+
searchNpmPackages,
|
|
29
|
+
setSearchCache,
|
|
16
30
|
} from "../packages/discovery.js";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
import {
|
|
32
|
+
installPackage,
|
|
33
|
+
installPackageLocallyWithOutcome,
|
|
34
|
+
installPackageWithOutcome,
|
|
35
|
+
} from "../packages/install.js";
|
|
36
|
+
import { type BrowseAction, type NpmPackage, type SearchCache } from "../types/index.js";
|
|
37
|
+
import { parseChoiceByLabel, splitCommandArgs } from "../utils/command.js";
|
|
38
|
+
import { formatBytes, normalizePackageSource, parseNpmSource, truncate } from "../utils/format.js";
|
|
20
39
|
import { requireCustomUI, runCustomUI } from "../utils/mode.js";
|
|
40
|
+
import { fetchWithTimeout } from "../utils/network.js";
|
|
41
|
+
import { notify } from "../utils/notify.js";
|
|
42
|
+
import { execNpm } from "../utils/npm-exec.js";
|
|
43
|
+
import { getPackageSourceKind } from "../utils/package-source.js";
|
|
21
44
|
import { runTaskWithLoader } from "./async-task.js";
|
|
22
45
|
|
|
23
46
|
interface PackageInfoCacheEntry {
|
|
@@ -95,6 +118,30 @@ const packageInfoCache = new PackageInfoCache(
|
|
|
95
118
|
|
|
96
119
|
export function clearRemotePackageInfoCache(): void {
|
|
97
120
|
packageInfoCache.clear();
|
|
121
|
+
clearCommunityBrowseCache();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getCommunityBrowseCache(): SearchCache | null {
|
|
125
|
+
const cache = getSearchCache();
|
|
126
|
+
if (!cache || cache.query !== COMMUNITY_BROWSE_QUERY) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return isCacheValid(COMMUNITY_BROWSE_QUERY) ? cache : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function setCommunityBrowseCache(results: NpmPackage[]): void {
|
|
134
|
+
setSearchCache({
|
|
135
|
+
query: COMMUNITY_BROWSE_QUERY,
|
|
136
|
+
results,
|
|
137
|
+
timestamp: Date.now(),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function clearCommunityBrowseCache(): void {
|
|
142
|
+
if (getSearchCache()?.query === COMMUNITY_BROWSE_QUERY) {
|
|
143
|
+
clearSearchCache();
|
|
144
|
+
}
|
|
98
145
|
}
|
|
99
146
|
|
|
100
147
|
const REMOTE_MENU_CHOICES = {
|
|
@@ -110,6 +157,218 @@ const PACKAGE_DETAILS_CHOICES = {
|
|
|
110
157
|
back: "Back to results",
|
|
111
158
|
} as const;
|
|
112
159
|
|
|
160
|
+
const COMMUNITY_BROWSE_QUERY = "keywords:pi-package";
|
|
161
|
+
|
|
162
|
+
type RemoteBrowseSource = "community" | "npm";
|
|
163
|
+
|
|
164
|
+
type RemoteBrowseQueryPlan =
|
|
165
|
+
| {
|
|
166
|
+
kind: "browse";
|
|
167
|
+
rawQuery: typeof COMMUNITY_BROWSE_QUERY;
|
|
168
|
+
searchQuery: typeof COMMUNITY_BROWSE_QUERY;
|
|
169
|
+
displayQuery: "";
|
|
170
|
+
title: "Community packages";
|
|
171
|
+
}
|
|
172
|
+
| {
|
|
173
|
+
kind: "search";
|
|
174
|
+
rawQuery: string;
|
|
175
|
+
searchQuery: string;
|
|
176
|
+
displayQuery: string;
|
|
177
|
+
title: string;
|
|
178
|
+
exactPackageName?: string;
|
|
179
|
+
}
|
|
180
|
+
| {
|
|
181
|
+
kind: "unsupported";
|
|
182
|
+
rawQuery: string;
|
|
183
|
+
message: string;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
function findExactPackageLookup(query: string): string | undefined {
|
|
187
|
+
if (!query || /\s/.test(query)) {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const parsed = parseNpmSource(normalizePackageSource(query));
|
|
192
|
+
if (!parsed?.name) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (query.startsWith("npm:") || Boolean(parsed.version) || parsed.name.startsWith("@")) {
|
|
197
|
+
return parsed.name.toLowerCase();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function buildUnsupportedSearchMessage(query: string, kind: "local" | "git"): string {
|
|
204
|
+
const label = truncate(query, 60);
|
|
205
|
+
const sourceLabel = kind === "local" ? "local path" : "git source";
|
|
206
|
+
return `"${label}" looks like a ${sourceLabel}. Remote browse searches npm package names and keywords. Use Install by source instead.`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function createRemoteBrowseQueryPlan(query: string): RemoteBrowseQueryPlan {
|
|
210
|
+
const trimmed = query.trim();
|
|
211
|
+
if (!trimmed || trimmed === COMMUNITY_BROWSE_QUERY) {
|
|
212
|
+
return {
|
|
213
|
+
kind: "browse",
|
|
214
|
+
rawQuery: COMMUNITY_BROWSE_QUERY,
|
|
215
|
+
searchQuery: COMMUNITY_BROWSE_QUERY,
|
|
216
|
+
displayQuery: "",
|
|
217
|
+
title: "Community packages",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const sourceKind = getPackageSourceKind(trimmed);
|
|
222
|
+
if (sourceKind === "local" || sourceKind === "git") {
|
|
223
|
+
return {
|
|
224
|
+
kind: "unsupported",
|
|
225
|
+
rawQuery: trimmed,
|
|
226
|
+
message: buildUnsupportedSearchMessage(trimmed, sourceKind),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const exactPackageName = findExactPackageLookup(trimmed);
|
|
231
|
+
return {
|
|
232
|
+
kind: "search",
|
|
233
|
+
rawQuery: trimmed,
|
|
234
|
+
searchQuery: exactPackageName ?? trimmed,
|
|
235
|
+
displayQuery: trimmed,
|
|
236
|
+
title: "Remote packages",
|
|
237
|
+
...(exactPackageName ? { exactPackageName } : {}),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function createCommunityBrowsePlan(
|
|
242
|
+
query: string
|
|
243
|
+
): Exclude<RemoteBrowseQueryPlan, { kind: "unsupported" }> {
|
|
244
|
+
const trimmed = query.trim();
|
|
245
|
+
if (!trimmed || trimmed === COMMUNITY_BROWSE_QUERY) {
|
|
246
|
+
return {
|
|
247
|
+
kind: "browse",
|
|
248
|
+
rawQuery: COMMUNITY_BROWSE_QUERY,
|
|
249
|
+
searchQuery: COMMUNITY_BROWSE_QUERY,
|
|
250
|
+
displayQuery: "",
|
|
251
|
+
title: "Community packages",
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
kind: "search",
|
|
257
|
+
rawQuery: trimmed,
|
|
258
|
+
searchQuery: COMMUNITY_BROWSE_QUERY,
|
|
259
|
+
displayQuery: trimmed,
|
|
260
|
+
title: "Community packages",
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function resolveRemoteBrowseSource(query: string, source?: RemoteBrowseSource): RemoteBrowseSource {
|
|
265
|
+
if (source) {
|
|
266
|
+
return source;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const trimmed = query.trim();
|
|
270
|
+
return !trimmed || trimmed === COMMUNITY_BROWSE_QUERY ? "community" : "npm";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function getCommunitySearchFields(pkg: NpmPackage): {
|
|
274
|
+
primary: string[];
|
|
275
|
+
secondary: string[];
|
|
276
|
+
} {
|
|
277
|
+
return {
|
|
278
|
+
primary: [pkg.name, pkg.author ?? ""]
|
|
279
|
+
.map((value) => value.trim().toLowerCase())
|
|
280
|
+
.filter((value) => value.length > 0),
|
|
281
|
+
secondary: [pkg.description ?? "", ...(pkg.keywords ?? [])]
|
|
282
|
+
.map((value) => value.trim().toLowerCase())
|
|
283
|
+
.filter((value) => value.length > 0),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function scoreCommunityBrowseResult(pkg: NpmPackage, query: string): number | undefined {
|
|
288
|
+
const tokens = query
|
|
289
|
+
.trim()
|
|
290
|
+
.toLowerCase()
|
|
291
|
+
.split(/\s+/)
|
|
292
|
+
.filter((token) => token.length > 0);
|
|
293
|
+
if (tokens.length === 0) {
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const fields = getCommunitySearchFields(pkg);
|
|
298
|
+
let totalScore = 0;
|
|
299
|
+
|
|
300
|
+
for (const token of tokens) {
|
|
301
|
+
const primarySubstringScore = fields.primary.reduce<number | undefined>((best, field) => {
|
|
302
|
+
const index = field.indexOf(token);
|
|
303
|
+
if (index < 0) {
|
|
304
|
+
return best;
|
|
305
|
+
}
|
|
306
|
+
return best === undefined ? index : Math.min(best, index);
|
|
307
|
+
}, undefined);
|
|
308
|
+
if (primarySubstringScore !== undefined) {
|
|
309
|
+
totalScore += primarySubstringScore;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const secondarySubstringScore = fields.secondary.reduce<number | undefined>((best, field) => {
|
|
314
|
+
const index = field.indexOf(token);
|
|
315
|
+
if (index < 0) {
|
|
316
|
+
return best;
|
|
317
|
+
}
|
|
318
|
+
const score = 100 + index;
|
|
319
|
+
return best === undefined ? score : Math.min(best, score);
|
|
320
|
+
}, undefined);
|
|
321
|
+
if (secondarySubstringScore !== undefined) {
|
|
322
|
+
totalScore += secondarySubstringScore;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const primaryFuzzyScore = fields.primary.reduce<number | undefined>((best, field) => {
|
|
327
|
+
const match = fuzzyMatch(token, field);
|
|
328
|
+
if (!match.matches) {
|
|
329
|
+
return best;
|
|
330
|
+
}
|
|
331
|
+
const score = 200 + match.score;
|
|
332
|
+
return best === undefined ? score : Math.min(best, score);
|
|
333
|
+
}, undefined);
|
|
334
|
+
if (primaryFuzzyScore !== undefined) {
|
|
335
|
+
totalScore += primaryFuzzyScore;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return totalScore;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function filterCommunityBrowseResults(packages: NpmPackage[], query: string): NpmPackage[] {
|
|
346
|
+
const matches = packages
|
|
347
|
+
.map((pkg, index) => ({
|
|
348
|
+
pkg,
|
|
349
|
+
index,
|
|
350
|
+
score: scoreCommunityBrowseResult(pkg, query),
|
|
351
|
+
}))
|
|
352
|
+
.filter(
|
|
353
|
+
(match): match is { pkg: NpmPackage; index: number; score: number } =>
|
|
354
|
+
match.score !== undefined
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
matches.sort((a, b) => a.score - b.score || a.index - b.index);
|
|
358
|
+
return matches.map((match) => match.pkg);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function filterRemoteBrowseResults(
|
|
362
|
+
plan: Exclude<RemoteBrowseQueryPlan, { kind: "unsupported" }>,
|
|
363
|
+
packages: NpmPackage[]
|
|
364
|
+
): NpmPackage[] {
|
|
365
|
+
if (plan.kind !== "search" || !plan.exactPackageName) {
|
|
366
|
+
return packages;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return packages.filter((pkg) => pkg.name.toLowerCase() === plan.exactPackageName);
|
|
370
|
+
}
|
|
371
|
+
|
|
113
372
|
function createAbortError(): Error {
|
|
114
373
|
const error = new Error("Operation cancelled");
|
|
115
374
|
error.name = "AbortError";
|
|
@@ -131,15 +390,13 @@ async function fetchWeeklyDownloads(
|
|
|
131
390
|
packageName: string,
|
|
132
391
|
signal?: AbortSignal
|
|
133
392
|
): Promise<number | undefined> {
|
|
134
|
-
const controller = new AbortController();
|
|
135
|
-
const timer = setTimeout(() => controller.abort(), TIMEOUTS.weeklyDownloads);
|
|
136
|
-
const combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
137
|
-
|
|
138
393
|
try {
|
|
139
394
|
const encoded = encodeURIComponent(packageName);
|
|
140
|
-
const res = await
|
|
141
|
-
|
|
142
|
-
|
|
395
|
+
const res = await fetchWithTimeout(
|
|
396
|
+
`https://api.npmjs.org/downloads/point/last-week/${encoded}`,
|
|
397
|
+
TIMEOUTS.weeklyDownloads,
|
|
398
|
+
signal
|
|
399
|
+
);
|
|
143
400
|
|
|
144
401
|
if (!res.ok) return undefined;
|
|
145
402
|
const data = (await res.json()) as NpmDownloadsPoint;
|
|
@@ -149,8 +406,6 @@ async function fetchWeeklyDownloads(
|
|
|
149
406
|
throw error;
|
|
150
407
|
}
|
|
151
408
|
return undefined;
|
|
152
|
-
} finally {
|
|
153
|
-
clearTimeout(timer);
|
|
154
409
|
}
|
|
155
410
|
}
|
|
156
411
|
|
|
@@ -235,7 +490,7 @@ export async function showRemote(
|
|
|
235
490
|
return;
|
|
236
491
|
case "browse":
|
|
237
492
|
case "":
|
|
238
|
-
await browseRemotePackages(ctx,
|
|
493
|
+
await browseRemotePackages(ctx, COMMUNITY_BROWSE_QUERY, pi);
|
|
239
494
|
return;
|
|
240
495
|
}
|
|
241
496
|
|
|
@@ -253,7 +508,7 @@ async function showRemoteMenu(ctx: ExtensionCommandContext, pi: ExtensionAPI): P
|
|
|
253
508
|
|
|
254
509
|
switch (choice) {
|
|
255
510
|
case "browse":
|
|
256
|
-
await browseRemotePackages(ctx,
|
|
511
|
+
await browseRemotePackages(ctx, COMMUNITY_BROWSE_QUERY, pi);
|
|
257
512
|
return;
|
|
258
513
|
case "search":
|
|
259
514
|
await promptSearch(ctx, pi);
|
|
@@ -266,9 +521,287 @@ async function showRemoteMenu(ctx: ExtensionCommandContext, pi: ExtensionAPI): P
|
|
|
266
521
|
}
|
|
267
522
|
}
|
|
268
523
|
|
|
524
|
+
function formatRemotePackageLabel(pkg: NpmPackage, theme: Theme): string {
|
|
525
|
+
const name = theme.bold(pkg.name);
|
|
526
|
+
const version = pkg.version ? theme.fg("dim", `@${pkg.version}`) : "";
|
|
527
|
+
return `${name}${version}`;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function formatRemotePackageDetails(
|
|
531
|
+
pkg: NpmPackage,
|
|
532
|
+
selectedNumber: number,
|
|
533
|
+
totalResults: number
|
|
534
|
+
): string {
|
|
535
|
+
const parts = [
|
|
536
|
+
pkg.description || "No description",
|
|
537
|
+
pkg.author ? `by ${pkg.author}` : undefined,
|
|
538
|
+
`result ${selectedNumber} of ${totalResults}`,
|
|
539
|
+
pkg.keywords?.length ? `keywords: ${pkg.keywords.slice(0, 5).join(", ")}` : undefined,
|
|
540
|
+
pkg.date ? `updated ${pkg.date.slice(0, 10)}` : undefined,
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
return parts.filter(Boolean).join(" • ");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
class RemotePackageBrowser implements Focusable {
|
|
547
|
+
private readonly searchInput = new Input();
|
|
548
|
+
private selectedIndex = 0;
|
|
549
|
+
private searchActive = false;
|
|
550
|
+
private _focused = false;
|
|
551
|
+
|
|
552
|
+
constructor(
|
|
553
|
+
private readonly packages: NpmPackage[],
|
|
554
|
+
private readonly theme: Theme,
|
|
555
|
+
private readonly browseSource: RemoteBrowseSource,
|
|
556
|
+
private readonly queryLabel: string,
|
|
557
|
+
private readonly totalResults: number,
|
|
558
|
+
private readonly offset: number,
|
|
559
|
+
private readonly maxVisibleItems: number,
|
|
560
|
+
private readonly showPrevious: boolean,
|
|
561
|
+
private readonly showLoadMore: boolean,
|
|
562
|
+
private readonly onAction: (action: BrowseAction) => void
|
|
563
|
+
) {
|
|
564
|
+
this.searchInput.setValue(queryLabel);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
get focused(): boolean {
|
|
568
|
+
return this._focused;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
set focused(value: boolean) {
|
|
572
|
+
this._focused = value;
|
|
573
|
+
this.searchInput.focused = value && this.searchActive;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
invalidate(): void {
|
|
577
|
+
this.searchInput.invalidate();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
handleBrowseInput(data: string): boolean {
|
|
581
|
+
const kb = getKeybindings();
|
|
582
|
+
|
|
583
|
+
if (this.searchActive) {
|
|
584
|
+
if (matchesKey(data, Key.enter)) {
|
|
585
|
+
this.searchActive = false;
|
|
586
|
+
this.searchInput.focused = false;
|
|
587
|
+
this.onAction({ type: "search", query: this.searchInput.getValue().trim() });
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (matchesKey(data, Key.escape)) {
|
|
592
|
+
this.searchActive = false;
|
|
593
|
+
this.searchInput.focused = false;
|
|
594
|
+
this.searchInput.setValue(this.queryLabel);
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
this.searchInput.handleInput(data);
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (data === "/" || matchesKey(data, Key.ctrl("f"))) {
|
|
603
|
+
this.searchActive = true;
|
|
604
|
+
this.searchInput.setValue("");
|
|
605
|
+
this.searchInput.focused = this._focused;
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (kb.matches(data, "tui.select.up")) {
|
|
610
|
+
this.moveSelection(-1);
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (kb.matches(data, "tui.select.down")) {
|
|
615
|
+
this.moveSelection(1);
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (kb.matches(data, "tui.select.pageUp")) {
|
|
620
|
+
this.moveSelection(-Math.max(1, this.maxVisibleItems - 1));
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (kb.matches(data, "tui.select.pageDown")) {
|
|
625
|
+
this.moveSelection(Math.max(1, this.maxVisibleItems - 1));
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (matchesKey(data, Key.home)) {
|
|
630
|
+
this.selectedIndex = 0;
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (matchesKey(data, Key.end)) {
|
|
635
|
+
this.selectedIndex = Math.max(0, this.packages.length - 1);
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const selected = this.packages[this.selectedIndex];
|
|
640
|
+
if (selected && matchesKey(data, Key.enter)) {
|
|
641
|
+
this.onAction({ type: "package", name: selected.name });
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if ((data === "p" || data === "P") && this.showPrevious) {
|
|
646
|
+
this.onAction({ type: "prev" });
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if ((data === "n" || data === "N") && this.showLoadMore) {
|
|
651
|
+
this.onAction({ type: "next" });
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (data === "r" || data === "R") {
|
|
656
|
+
this.onAction({ type: "refresh" });
|
|
657
|
+
return true;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (data === "i") {
|
|
661
|
+
this.onAction({ type: "install" });
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (data === "m" || data === "M") {
|
|
666
|
+
this.onAction({ type: "menu" });
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (matchesKey(data, Key.escape)) {
|
|
671
|
+
this.onAction({ type: "cancel" });
|
|
672
|
+
return true;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
render(width: number): string[] {
|
|
679
|
+
const lines: string[] = [];
|
|
680
|
+
|
|
681
|
+
if (this.searchActive) {
|
|
682
|
+
lines.push(...this.searchInput.render(width));
|
|
683
|
+
lines.push("");
|
|
684
|
+
} else if (this.queryLabel) {
|
|
685
|
+
lines.push(
|
|
686
|
+
truncateToWidth(this.theme.fg("accent", ` Search: ${this.queryLabel}`), width, "")
|
|
687
|
+
);
|
|
688
|
+
lines.push("");
|
|
689
|
+
} else {
|
|
690
|
+
lines.push(
|
|
691
|
+
this.theme.fg(
|
|
692
|
+
"muted",
|
|
693
|
+
this.browseSource === "community"
|
|
694
|
+
? " Browse community packages · / search to filter loaded packages"
|
|
695
|
+
: " Browse remote search results · / search to search npm packages"
|
|
696
|
+
)
|
|
697
|
+
);
|
|
698
|
+
lines.push("");
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
lines.push(truncateToWidth(this.buildSummaryLine(), width, ""));
|
|
702
|
+
lines.push("");
|
|
703
|
+
|
|
704
|
+
const { startIndex, endIndex } = this.getVisibleRange();
|
|
705
|
+
for (const pkg of this.packages.slice(startIndex, endIndex)) {
|
|
706
|
+
lines.push(this.renderPackageLine(pkg, width));
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (startIndex > 0 || endIndex < this.packages.length) {
|
|
710
|
+
lines.push("");
|
|
711
|
+
lines.push(
|
|
712
|
+
this.theme.fg(
|
|
713
|
+
"dim",
|
|
714
|
+
` Showing ${startIndex + 1}-${endIndex} of ${this.packages.length} on this page`
|
|
715
|
+
)
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const selected = this.packages[this.selectedIndex];
|
|
720
|
+
if (selected) {
|
|
721
|
+
lines.push("");
|
|
722
|
+
const detailText = formatRemotePackageDetails(
|
|
723
|
+
selected,
|
|
724
|
+
this.offset + this.selectedIndex + 1,
|
|
725
|
+
this.totalResults
|
|
726
|
+
);
|
|
727
|
+
for (const line of wrapTextWithAnsi(detailText, width - 4)) {
|
|
728
|
+
lines.push(this.theme.fg("dim", ` ${line}`));
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
lines.push("");
|
|
733
|
+
lines.push(truncateToWidth(this.buildFooterLine(), width, ""));
|
|
734
|
+
return lines;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
private buildSummaryLine(): string {
|
|
738
|
+
const pageCount = Math.max(1, Math.ceil(this.totalResults / PAGE_SIZE));
|
|
739
|
+
const pageNumber = Math.floor(this.offset / PAGE_SIZE) + 1;
|
|
740
|
+
const rangeEnd = this.offset + this.packages.length;
|
|
741
|
+
const label = this.queryLabel
|
|
742
|
+
? `Search: ${truncate(this.queryLabel, 40)}`
|
|
743
|
+
: "Community packages";
|
|
744
|
+
return ` ${this.theme.fg("accent", label)} • ${this.theme.fg("muted", `${this.offset + 1}-${rangeEnd} of ${this.totalResults}`)} • ${this.theme.fg("muted", `page ${pageNumber}/${pageCount}`)}`;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private buildFooterLine(): string {
|
|
748
|
+
const parts = ["Enter details", "/ search"];
|
|
749
|
+
|
|
750
|
+
if (this.showPrevious) {
|
|
751
|
+
parts.push("p prev");
|
|
752
|
+
}
|
|
753
|
+
if (this.showLoadMore) {
|
|
754
|
+
parts.push("n next");
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
parts.push("r refresh", "i install", "m menu", "Esc back");
|
|
758
|
+
return ` ${this.theme.fg("dim", parts.join(" · "))}`;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
private renderPackageLine(pkg: NpmPackage, width: number): string {
|
|
762
|
+
const prefix =
|
|
763
|
+
this.packages[this.selectedIndex]?.name === pkg.name ? this.theme.fg("accent", "→ ") : " ";
|
|
764
|
+
return truncateToWidth(prefix + formatRemotePackageLabel(pkg, this.theme), width);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
private moveSelection(delta: number): void {
|
|
768
|
+
if (this.packages.length === 0) {
|
|
769
|
+
this.selectedIndex = 0;
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const nextIndex = this.selectedIndex + delta;
|
|
774
|
+
if (nextIndex < 0) {
|
|
775
|
+
this.selectedIndex = 0;
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (nextIndex >= this.packages.length) {
|
|
780
|
+
this.selectedIndex = this.packages.length - 1;
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
this.selectedIndex = nextIndex;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
private getVisibleRange(): { startIndex: number; endIndex: number } {
|
|
788
|
+
const maxVisible = Math.max(1, this.maxVisibleItems);
|
|
789
|
+
const startIndex = Math.max(
|
|
790
|
+
0,
|
|
791
|
+
Math.min(
|
|
792
|
+
this.selectedIndex - Math.floor(maxVisible / 2),
|
|
793
|
+
Math.max(0, this.packages.length - maxVisible)
|
|
794
|
+
)
|
|
795
|
+
);
|
|
796
|
+
const endIndex = Math.min(startIndex + maxVisible, this.packages.length);
|
|
797
|
+
return { startIndex, endIndex };
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
269
801
|
async function selectBrowseAction(
|
|
270
802
|
ctx: ExtensionCommandContext,
|
|
271
|
-
|
|
803
|
+
plan: Exclude<RemoteBrowseQueryPlan, { kind: "unsupported" }>,
|
|
804
|
+
browseSource: RemoteBrowseSource,
|
|
272
805
|
packages: NpmPackage[],
|
|
273
806
|
offset: number,
|
|
274
807
|
totalResults: number,
|
|
@@ -277,77 +810,56 @@ async function selectBrowseAction(
|
|
|
277
810
|
): Promise<BrowseAction | undefined> {
|
|
278
811
|
if (!ctx.hasUI) return undefined;
|
|
279
812
|
|
|
280
|
-
const items: SelectItem[] = packages.map((p) => ({
|
|
281
|
-
value: `pkg:${p.name}`,
|
|
282
|
-
label: `${p.name}${p.version ? ` @${p.version}` : ""}`,
|
|
283
|
-
description: dynamicTruncate(p.description || "No description", 35),
|
|
284
|
-
}));
|
|
285
|
-
|
|
286
|
-
if (showPrevious) {
|
|
287
|
-
items.push({ value: "nav:prev", label: "◀ Previous page" });
|
|
288
|
-
}
|
|
289
|
-
if (showLoadMore) {
|
|
290
|
-
items.push({
|
|
291
|
-
value: "nav:next",
|
|
292
|
-
label: `▶ Next page (${offset + 1}-${offset + packages.length} of ${totalResults})`,
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
items.push({ value: "nav:refresh", label: "🔄 Refresh search" });
|
|
296
|
-
items.push({ value: "nav:menu", label: "← Back to menu" });
|
|
297
|
-
|
|
298
813
|
return runCustomUI(ctx, "Remote package browsing", () =>
|
|
299
814
|
ctx.ui.custom<BrowseAction>((tui, theme, _keybindings, done) => {
|
|
300
815
|
const container = new Container();
|
|
301
|
-
const title = new Text("",
|
|
302
|
-
const
|
|
816
|
+
const title = new Text("", 2, 0);
|
|
817
|
+
const browser = new RemotePackageBrowser(
|
|
818
|
+
packages,
|
|
819
|
+
theme,
|
|
820
|
+
browseSource,
|
|
821
|
+
plan.displayQuery,
|
|
822
|
+
totalResults,
|
|
823
|
+
offset,
|
|
824
|
+
Math.max(4, Math.min(UI.maxListHeight, tui.terminal.rows - 10)),
|
|
825
|
+
showPrevious,
|
|
826
|
+
showLoadMore,
|
|
827
|
+
done
|
|
828
|
+
);
|
|
303
829
|
const syncThemedContent = (): void => {
|
|
304
|
-
title.setText(theme.fg("accent", theme.bold(
|
|
305
|
-
footer.setText(theme.fg("dim", "↑↓ wraps • enter select • esc cancel"));
|
|
830
|
+
title.setText(theme.fg("accent", theme.bold(plan.title)));
|
|
306
831
|
};
|
|
307
832
|
|
|
833
|
+
syncThemedContent();
|
|
308
834
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
309
835
|
container.addChild(title);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
selectedPrefix: (t) => theme.fg("accent", t),
|
|
313
|
-
selectedText: (t) => theme.fg("accent", t),
|
|
314
|
-
description: (t) => theme.fg("muted", t),
|
|
315
|
-
scrollInfo: (t) => theme.fg("dim", t),
|
|
316
|
-
noMatch: (t) => theme.fg("warning", t),
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
selectList.onSelect = (item) => {
|
|
320
|
-
if (item.value === "nav:prev") {
|
|
321
|
-
done({ type: "prev" });
|
|
322
|
-
} else if (item.value === "nav:next") {
|
|
323
|
-
done({ type: "next" });
|
|
324
|
-
} else if (item.value === "nav:refresh") {
|
|
325
|
-
done({ type: "refresh" });
|
|
326
|
-
} else if (item.value === "nav:menu") {
|
|
327
|
-
done({ type: "menu" });
|
|
328
|
-
} else if (item.value.startsWith("pkg:")) {
|
|
329
|
-
done({ type: "package", name: item.value.slice(4) });
|
|
330
|
-
} else {
|
|
331
|
-
done({ type: "cancel" });
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
selectList.onCancel = () => done({ type: "cancel" });
|
|
336
|
-
|
|
337
|
-
syncThemedContent();
|
|
338
|
-
container.addChild(selectList);
|
|
339
|
-
container.addChild(footer);
|
|
836
|
+
container.addChild(new Spacer(1));
|
|
837
|
+
container.addChild(browser);
|
|
340
838
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
341
839
|
|
|
840
|
+
let focused = false;
|
|
841
|
+
|
|
342
842
|
return {
|
|
343
|
-
|
|
344
|
-
|
|
843
|
+
get focused() {
|
|
844
|
+
return focused;
|
|
845
|
+
},
|
|
846
|
+
set focused(value: boolean) {
|
|
847
|
+
focused = value;
|
|
848
|
+
browser.focused = value;
|
|
849
|
+
},
|
|
850
|
+
render(width: number) {
|
|
851
|
+
syncThemedContent();
|
|
852
|
+
return container.render(width);
|
|
853
|
+
},
|
|
854
|
+
invalidate() {
|
|
345
855
|
container.invalidate();
|
|
856
|
+
browser.invalidate();
|
|
346
857
|
syncThemedContent();
|
|
347
858
|
},
|
|
348
|
-
handleInput
|
|
349
|
-
|
|
350
|
-
|
|
859
|
+
handleInput(data: string) {
|
|
860
|
+
if (browser.handleBrowseInput(data)) {
|
|
861
|
+
tui.requestRender();
|
|
862
|
+
}
|
|
351
863
|
},
|
|
352
864
|
};
|
|
353
865
|
})
|
|
@@ -358,7 +870,8 @@ export async function browseRemotePackages(
|
|
|
358
870
|
ctx: ExtensionCommandContext,
|
|
359
871
|
query: string,
|
|
360
872
|
pi: ExtensionAPI,
|
|
361
|
-
offset = 0
|
|
873
|
+
offset = 0,
|
|
874
|
+
source?: RemoteBrowseSource
|
|
362
875
|
): Promise<void> {
|
|
363
876
|
if (
|
|
364
877
|
!requireCustomUI(
|
|
@@ -370,25 +883,49 @@ export async function browseRemotePackages(
|
|
|
370
883
|
return;
|
|
371
884
|
}
|
|
372
885
|
|
|
886
|
+
const browseSource = resolveRemoteBrowseSource(query, source);
|
|
887
|
+
const plan =
|
|
888
|
+
browseSource === "community"
|
|
889
|
+
? createCommunityBrowsePlan(query)
|
|
890
|
+
: createRemoteBrowseQueryPlan(query);
|
|
891
|
+
if (plan.kind === "unsupported") {
|
|
892
|
+
notify(ctx, plan.message, "warning");
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const cacheQuery = browseSource === "community" ? COMMUNITY_BROWSE_QUERY : plan.rawQuery;
|
|
373
897
|
let allPackages: NpmPackage[] | undefined;
|
|
374
898
|
|
|
375
|
-
if (
|
|
376
|
-
const cache =
|
|
899
|
+
if (browseSource === "community") {
|
|
900
|
+
const cache = getCommunityBrowseCache();
|
|
377
901
|
if (cache) {
|
|
902
|
+
allPackages = filterCommunityBrowseResults(cache.results, plan.displayQuery);
|
|
903
|
+
}
|
|
904
|
+
} else if (isCacheValid(cacheQuery)) {
|
|
905
|
+
const cache = getSearchCache();
|
|
906
|
+
if (cache?.query === cacheQuery) {
|
|
378
907
|
allPackages = cache.results;
|
|
379
908
|
}
|
|
380
909
|
}
|
|
381
910
|
|
|
382
911
|
if (!allPackages) {
|
|
912
|
+
const searchLabel =
|
|
913
|
+
browseSource === "community"
|
|
914
|
+
? "community packages"
|
|
915
|
+
: plan.displayQuery || "community packages";
|
|
383
916
|
const results = await runTaskWithLoader(
|
|
384
917
|
ctx,
|
|
385
918
|
{
|
|
386
|
-
title:
|
|
387
|
-
message: `Searching npm for ${truncate(
|
|
919
|
+
title: plan.title,
|
|
920
|
+
message: `Searching npm for ${truncate(searchLabel, 40)}...`,
|
|
388
921
|
},
|
|
389
922
|
async ({ signal, setMessage }) => {
|
|
390
|
-
setMessage(`Searching npm for ${truncate(
|
|
391
|
-
return searchNpmPackages(
|
|
923
|
+
setMessage(`Searching npm for ${truncate(searchLabel, 40)}...`);
|
|
924
|
+
return searchNpmPackages(
|
|
925
|
+
browseSource === "community" ? COMMUNITY_BROWSE_QUERY : plan.searchQuery,
|
|
926
|
+
ctx,
|
|
927
|
+
{ signal }
|
|
928
|
+
);
|
|
392
929
|
}
|
|
393
930
|
);
|
|
394
931
|
|
|
@@ -397,40 +934,44 @@ export async function browseRemotePackages(
|
|
|
397
934
|
return;
|
|
398
935
|
}
|
|
399
936
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
937
|
+
if (browseSource === "community") {
|
|
938
|
+
setCommunityBrowseCache(results);
|
|
939
|
+
allPackages = filterCommunityBrowseResults(results, plan.displayQuery);
|
|
940
|
+
} else {
|
|
941
|
+
allPackages = filterRemoteBrowseResults(plan, results);
|
|
942
|
+
setSearchCache({
|
|
943
|
+
query: plan.rawQuery,
|
|
944
|
+
results: allPackages,
|
|
945
|
+
timestamp: Date.now(),
|
|
946
|
+
});
|
|
947
|
+
}
|
|
406
948
|
}
|
|
407
949
|
|
|
408
|
-
// Apply pagination from cached/filtered results
|
|
409
950
|
const totalResults = allPackages.length;
|
|
410
951
|
const packages = allPackages.slice(offset, offset + PAGE_SIZE);
|
|
952
|
+
const reloadQuery =
|
|
953
|
+
browseSource === "community" ? plan.displayQuery || COMMUNITY_BROWSE_QUERY : plan.rawQuery;
|
|
411
954
|
|
|
412
955
|
if (packages.length === 0) {
|
|
413
|
-
const msg =
|
|
956
|
+
const msg =
|
|
957
|
+
offset > 0
|
|
958
|
+
? "No more packages to show."
|
|
959
|
+
: `No packages found for: ${plan.displayQuery || "community packages"}`;
|
|
414
960
|
ctx.ui.notify(msg, "info");
|
|
415
961
|
|
|
416
962
|
if (offset > 0) {
|
|
417
|
-
await browseRemotePackages(ctx,
|
|
963
|
+
await browseRemotePackages(ctx, reloadQuery, pi, 0, browseSource);
|
|
418
964
|
}
|
|
419
965
|
return;
|
|
420
966
|
}
|
|
421
967
|
|
|
422
|
-
// Add navigation options
|
|
423
968
|
const showLoadMore = totalResults >= PAGE_SIZE && offset + PAGE_SIZE < totalResults;
|
|
424
969
|
const showPrevious = offset > 0;
|
|
425
970
|
|
|
426
|
-
const titleText =
|
|
427
|
-
offset > 0
|
|
428
|
-
? `Search Results (${offset + 1}-${offset + packages.length} of ${totalResults})`
|
|
429
|
-
: `Search: ${truncate(query, 40)} (${totalResults})`;
|
|
430
|
-
|
|
431
971
|
const result = await selectBrowseAction(
|
|
432
972
|
ctx,
|
|
433
|
-
|
|
973
|
+
plan,
|
|
974
|
+
browseSource,
|
|
434
975
|
packages,
|
|
435
976
|
offset,
|
|
436
977
|
totalResults,
|
|
@@ -442,23 +983,51 @@ export async function browseRemotePackages(
|
|
|
442
983
|
return;
|
|
443
984
|
}
|
|
444
985
|
|
|
445
|
-
// Handle result
|
|
446
986
|
switch (result.type) {
|
|
447
987
|
case "prev":
|
|
448
|
-
await browseRemotePackages(
|
|
988
|
+
await browseRemotePackages(
|
|
989
|
+
ctx,
|
|
990
|
+
reloadQuery,
|
|
991
|
+
pi,
|
|
992
|
+
Math.max(0, offset - PAGE_SIZE),
|
|
993
|
+
browseSource
|
|
994
|
+
);
|
|
449
995
|
return;
|
|
450
996
|
case "next":
|
|
451
|
-
await browseRemotePackages(ctx,
|
|
997
|
+
await browseRemotePackages(ctx, reloadQuery, pi, offset + PAGE_SIZE, browseSource);
|
|
452
998
|
return;
|
|
453
999
|
case "refresh":
|
|
454
|
-
|
|
455
|
-
|
|
1000
|
+
if (browseSource === "community") {
|
|
1001
|
+
clearCommunityBrowseCache();
|
|
1002
|
+
} else {
|
|
1003
|
+
clearSearchCache();
|
|
1004
|
+
}
|
|
1005
|
+
await browseRemotePackages(ctx, reloadQuery, pi, 0, browseSource);
|
|
1006
|
+
return;
|
|
1007
|
+
case "search": {
|
|
1008
|
+
const nextQuery = result.query.trim();
|
|
1009
|
+
if (browseSource === "community") {
|
|
1010
|
+
await browseRemotePackages(ctx, nextQuery || COMMUNITY_BROWSE_QUERY, pi, 0, "community");
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
await browseRemotePackages(
|
|
1014
|
+
ctx,
|
|
1015
|
+
nextQuery || COMMUNITY_BROWSE_QUERY,
|
|
1016
|
+
pi,
|
|
1017
|
+
0,
|
|
1018
|
+
nextQuery ? "npm" : undefined
|
|
1019
|
+
);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
case "install":
|
|
1023
|
+
await promptInstall(ctx, pi);
|
|
1024
|
+
await browseRemotePackages(ctx, reloadQuery, pi, offset, browseSource);
|
|
456
1025
|
return;
|
|
457
1026
|
case "menu":
|
|
458
1027
|
await showRemoteMenu(ctx, pi);
|
|
459
1028
|
return;
|
|
460
1029
|
case "package":
|
|
461
|
-
await showPackageDetails(result.name, ctx, pi,
|
|
1030
|
+
await showPackageDetails(result.name, ctx, pi, reloadQuery, offset, browseSource);
|
|
462
1031
|
return;
|
|
463
1032
|
}
|
|
464
1033
|
}
|
|
@@ -468,7 +1037,8 @@ async function showPackageDetails(
|
|
|
468
1037
|
ctx: ExtensionCommandContext,
|
|
469
1038
|
pi: ExtensionAPI,
|
|
470
1039
|
previousQuery: string,
|
|
471
|
-
previousOffset: number
|
|
1040
|
+
previousOffset: number,
|
|
1041
|
+
browseSource?: RemoteBrowseSource
|
|
472
1042
|
): Promise<void> {
|
|
473
1043
|
if (!ctx.hasUI) {
|
|
474
1044
|
console.log(`Package: ${packageName}`);
|
|
@@ -481,12 +1051,30 @@ async function showPackageDetails(
|
|
|
481
1051
|
);
|
|
482
1052
|
|
|
483
1053
|
switch (choice) {
|
|
484
|
-
case "installManaged":
|
|
485
|
-
await
|
|
1054
|
+
case "installManaged": {
|
|
1055
|
+
const outcome = await installPackageWithOutcome(`npm:${packageName}`, ctx, pi);
|
|
1056
|
+
if (outcome.reloaded) {
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
if (outcome.installed) {
|
|
1060
|
+
await browseRemotePackages(ctx, previousQuery, pi, previousOffset, browseSource);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
await showPackageDetails(packageName, ctx, pi, previousQuery, previousOffset, browseSource);
|
|
486
1064
|
return;
|
|
487
|
-
|
|
488
|
-
|
|
1065
|
+
}
|
|
1066
|
+
case "installStandalone": {
|
|
1067
|
+
const outcome = await installPackageLocallyWithOutcome(packageName, ctx, pi);
|
|
1068
|
+
if (outcome.reloaded) {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
if (outcome.installed) {
|
|
1072
|
+
await browseRemotePackages(ctx, previousQuery, pi, previousOffset, browseSource);
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
await showPackageDetails(packageName, ctx, pi, previousQuery, previousOffset, browseSource);
|
|
489
1076
|
return;
|
|
1077
|
+
}
|
|
490
1078
|
case "viewInfo":
|
|
491
1079
|
try {
|
|
492
1080
|
const text = await runTaskWithLoader(
|
|
@@ -500,7 +1088,14 @@ async function showPackageDetails(
|
|
|
500
1088
|
|
|
501
1089
|
if (!text) {
|
|
502
1090
|
notify(ctx, `Loading ${packageName} details was cancelled.`, "info");
|
|
503
|
-
await showPackageDetails(
|
|
1091
|
+
await showPackageDetails(
|
|
1092
|
+
packageName,
|
|
1093
|
+
ctx,
|
|
1094
|
+
pi,
|
|
1095
|
+
previousQuery,
|
|
1096
|
+
previousOffset,
|
|
1097
|
+
browseSource
|
|
1098
|
+
);
|
|
504
1099
|
return;
|
|
505
1100
|
}
|
|
506
1101
|
|
|
@@ -509,10 +1104,10 @@ async function showPackageDetails(
|
|
|
509
1104
|
const message = error instanceof Error ? error.message : String(error);
|
|
510
1105
|
ctx.ui.notify(`Package: ${packageName}\n${message}`, "warning");
|
|
511
1106
|
}
|
|
512
|
-
await showPackageDetails(packageName, ctx, pi, previousQuery, previousOffset);
|
|
1107
|
+
await showPackageDetails(packageName, ctx, pi, previousQuery, previousOffset, browseSource);
|
|
513
1108
|
return;
|
|
514
1109
|
case "back":
|
|
515
|
-
await browseRemotePackages(ctx, previousQuery, pi, previousOffset);
|
|
1110
|
+
await browseRemotePackages(ctx, previousQuery, pi, previousOffset, browseSource);
|
|
516
1111
|
return;
|
|
517
1112
|
default:
|
|
518
1113
|
return;
|
|
@@ -520,7 +1115,7 @@ async function showPackageDetails(
|
|
|
520
1115
|
}
|
|
521
1116
|
|
|
522
1117
|
async function promptSearch(ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
|
|
523
|
-
const query = await ctx.ui.input("Search packages", "
|
|
1118
|
+
const query = await ctx.ui.input("Search packages", "package name, keyword, or npm:@scope/pkg");
|
|
524
1119
|
if (!query?.trim()) return;
|
|
525
1120
|
await searchPackages(query.trim(), ctx, pi);
|
|
526
1121
|
}
|