pi-extmgr 0.1.28 → 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 +18 -8
- package/package.json +10 -2
- package/src/commands/auto-update.ts +1 -1
- package/src/commands/history.ts +2 -31
- package/src/constants.ts +0 -8
- package/src/extensions/discovery.ts +121 -39
- package/src/packages/discovery.ts +34 -0
- package/src/packages/extensions.ts +55 -98
- package/src/packages/install.ts +85 -56
- package/src/packages/management.ts +25 -38
- package/src/types/index.ts +4 -2
- package/src/ui/footer.ts +49 -29
- package/src/ui/help.ts +15 -11
- package/src/ui/remote.ts +704 -112
- package/src/ui/unified.ts +922 -311
- package/src/utils/auto-update.ts +34 -29
- package/src/utils/cache.ts +205 -34
- package/src/utils/duration.ts +132 -0
- package/src/utils/format.ts +0 -30
- package/src/utils/fs.ts +8 -4
- package/src/utils/history.ts +43 -7
- package/src/utils/mode.ts +1 -1
- package/src/utils/notify.ts +0 -14
- package/src/utils/package-source.ts +2 -5
- package/src/utils/path-identity.ts +7 -0
- package/src/utils/relative-path-selection.ts +100 -0
- package/src/utils/settings.ts +4 -63
- package/src/utils/retry.ts +0 -49
package/src/ui/remote.ts
CHANGED
|
@@ -5,22 +5,42 @@ import {
|
|
|
5
5
|
DynamicBorder,
|
|
6
6
|
type ExtensionAPI,
|
|
7
7
|
type ExtensionCommandContext,
|
|
8
|
+
type Theme,
|
|
8
9
|
} from "@mariozechner/pi-coding-agent";
|
|
9
|
-
import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
|
|
10
|
-
import { CACHE_LIMITS, PAGE_SIZE, TIMEOUTS } from "../constants.js";
|
|
11
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,
|
|
12
26
|
getSearchCache,
|
|
13
27
|
isCacheValid,
|
|
14
28
|
searchNpmPackages,
|
|
15
29
|
setSearchCache,
|
|
16
30
|
} from "../packages/discovery.js";
|
|
17
|
-
import {
|
|
18
|
-
|
|
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";
|
|
19
37
|
import { parseChoiceByLabel, splitCommandArgs } from "../utils/command.js";
|
|
20
|
-
import {
|
|
38
|
+
import { formatBytes, normalizePackageSource, parseNpmSource, truncate } from "../utils/format.js";
|
|
21
39
|
import { requireCustomUI, runCustomUI } from "../utils/mode.js";
|
|
40
|
+
import { fetchWithTimeout } from "../utils/network.js";
|
|
22
41
|
import { notify } from "../utils/notify.js";
|
|
23
42
|
import { execNpm } from "../utils/npm-exec.js";
|
|
43
|
+
import { getPackageSourceKind } from "../utils/package-source.js";
|
|
24
44
|
import { runTaskWithLoader } from "./async-task.js";
|
|
25
45
|
|
|
26
46
|
interface PackageInfoCacheEntry {
|
|
@@ -98,6 +118,30 @@ const packageInfoCache = new PackageInfoCache(
|
|
|
98
118
|
|
|
99
119
|
export function clearRemotePackageInfoCache(): void {
|
|
100
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
|
+
}
|
|
101
145
|
}
|
|
102
146
|
|
|
103
147
|
const REMOTE_MENU_CHOICES = {
|
|
@@ -113,6 +157,218 @@ const PACKAGE_DETAILS_CHOICES = {
|
|
|
113
157
|
back: "Back to results",
|
|
114
158
|
} as const;
|
|
115
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
|
+
|
|
116
372
|
function createAbortError(): Error {
|
|
117
373
|
const error = new Error("Operation cancelled");
|
|
118
374
|
error.name = "AbortError";
|
|
@@ -134,15 +390,13 @@ async function fetchWeeklyDownloads(
|
|
|
134
390
|
packageName: string,
|
|
135
391
|
signal?: AbortSignal
|
|
136
392
|
): Promise<number | undefined> {
|
|
137
|
-
const controller = new AbortController();
|
|
138
|
-
const timer = setTimeout(() => controller.abort(), TIMEOUTS.weeklyDownloads);
|
|
139
|
-
const combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
140
|
-
|
|
141
393
|
try {
|
|
142
394
|
const encoded = encodeURIComponent(packageName);
|
|
143
|
-
const res = await
|
|
144
|
-
|
|
145
|
-
|
|
395
|
+
const res = await fetchWithTimeout(
|
|
396
|
+
`https://api.npmjs.org/downloads/point/last-week/${encoded}`,
|
|
397
|
+
TIMEOUTS.weeklyDownloads,
|
|
398
|
+
signal
|
|
399
|
+
);
|
|
146
400
|
|
|
147
401
|
if (!res.ok) return undefined;
|
|
148
402
|
const data = (await res.json()) as NpmDownloadsPoint;
|
|
@@ -152,8 +406,6 @@ async function fetchWeeklyDownloads(
|
|
|
152
406
|
throw error;
|
|
153
407
|
}
|
|
154
408
|
return undefined;
|
|
155
|
-
} finally {
|
|
156
|
-
clearTimeout(timer);
|
|
157
409
|
}
|
|
158
410
|
}
|
|
159
411
|
|
|
@@ -238,7 +490,7 @@ export async function showRemote(
|
|
|
238
490
|
return;
|
|
239
491
|
case "browse":
|
|
240
492
|
case "":
|
|
241
|
-
await browseRemotePackages(ctx,
|
|
493
|
+
await browseRemotePackages(ctx, COMMUNITY_BROWSE_QUERY, pi);
|
|
242
494
|
return;
|
|
243
495
|
}
|
|
244
496
|
|
|
@@ -256,7 +508,7 @@ async function showRemoteMenu(ctx: ExtensionCommandContext, pi: ExtensionAPI): P
|
|
|
256
508
|
|
|
257
509
|
switch (choice) {
|
|
258
510
|
case "browse":
|
|
259
|
-
await browseRemotePackages(ctx,
|
|
511
|
+
await browseRemotePackages(ctx, COMMUNITY_BROWSE_QUERY, pi);
|
|
260
512
|
return;
|
|
261
513
|
case "search":
|
|
262
514
|
await promptSearch(ctx, pi);
|
|
@@ -269,9 +521,287 @@ async function showRemoteMenu(ctx: ExtensionCommandContext, pi: ExtensionAPI): P
|
|
|
269
521
|
}
|
|
270
522
|
}
|
|
271
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
|
+
|
|
272
801
|
async function selectBrowseAction(
|
|
273
802
|
ctx: ExtensionCommandContext,
|
|
274
|
-
|
|
803
|
+
plan: Exclude<RemoteBrowseQueryPlan, { kind: "unsupported" }>,
|
|
804
|
+
browseSource: RemoteBrowseSource,
|
|
275
805
|
packages: NpmPackage[],
|
|
276
806
|
offset: number,
|
|
277
807
|
totalResults: number,
|
|
@@ -280,77 +810,56 @@ async function selectBrowseAction(
|
|
|
280
810
|
): Promise<BrowseAction | undefined> {
|
|
281
811
|
if (!ctx.hasUI) return undefined;
|
|
282
812
|
|
|
283
|
-
const items: SelectItem[] = packages.map((p) => ({
|
|
284
|
-
value: `pkg:${p.name}`,
|
|
285
|
-
label: `${p.name}${p.version ? ` @${p.version}` : ""}`,
|
|
286
|
-
description: dynamicTruncate(p.description || "No description", 35),
|
|
287
|
-
}));
|
|
288
|
-
|
|
289
|
-
if (showPrevious) {
|
|
290
|
-
items.push({ value: "nav:prev", label: "◀ Previous page" });
|
|
291
|
-
}
|
|
292
|
-
if (showLoadMore) {
|
|
293
|
-
items.push({
|
|
294
|
-
value: "nav:next",
|
|
295
|
-
label: `▶ Next page (${offset + 1}-${offset + packages.length} of ${totalResults})`,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
items.push({ value: "nav:refresh", label: "🔄 Refresh search" });
|
|
299
|
-
items.push({ value: "nav:menu", label: "← Back to menu" });
|
|
300
|
-
|
|
301
813
|
return runCustomUI(ctx, "Remote package browsing", () =>
|
|
302
814
|
ctx.ui.custom<BrowseAction>((tui, theme, _keybindings, done) => {
|
|
303
815
|
const container = new Container();
|
|
304
|
-
const title = new Text("",
|
|
305
|
-
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
|
+
);
|
|
306
829
|
const syncThemedContent = (): void => {
|
|
307
|
-
title.setText(theme.fg("accent", theme.bold(
|
|
308
|
-
footer.setText(theme.fg("dim", "↑↓ wraps • enter select • esc cancel"));
|
|
830
|
+
title.setText(theme.fg("accent", theme.bold(plan.title)));
|
|
309
831
|
};
|
|
310
832
|
|
|
833
|
+
syncThemedContent();
|
|
311
834
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
312
835
|
container.addChild(title);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
selectedPrefix: (t) => theme.fg("accent", t),
|
|
316
|
-
selectedText: (t) => theme.fg("accent", t),
|
|
317
|
-
description: (t) => theme.fg("muted", t),
|
|
318
|
-
scrollInfo: (t) => theme.fg("dim", t),
|
|
319
|
-
noMatch: (t) => theme.fg("warning", t),
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
selectList.onSelect = (item) => {
|
|
323
|
-
if (item.value === "nav:prev") {
|
|
324
|
-
done({ type: "prev" });
|
|
325
|
-
} else if (item.value === "nav:next") {
|
|
326
|
-
done({ type: "next" });
|
|
327
|
-
} else if (item.value === "nav:refresh") {
|
|
328
|
-
done({ type: "refresh" });
|
|
329
|
-
} else if (item.value === "nav:menu") {
|
|
330
|
-
done({ type: "menu" });
|
|
331
|
-
} else if (item.value.startsWith("pkg:")) {
|
|
332
|
-
done({ type: "package", name: item.value.slice(4) });
|
|
333
|
-
} else {
|
|
334
|
-
done({ type: "cancel" });
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
selectList.onCancel = () => done({ type: "cancel" });
|
|
339
|
-
|
|
340
|
-
syncThemedContent();
|
|
341
|
-
container.addChild(selectList);
|
|
342
|
-
container.addChild(footer);
|
|
836
|
+
container.addChild(new Spacer(1));
|
|
837
|
+
container.addChild(browser);
|
|
343
838
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
344
839
|
|
|
840
|
+
let focused = false;
|
|
841
|
+
|
|
345
842
|
return {
|
|
346
|
-
|
|
347
|
-
|
|
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() {
|
|
348
855
|
container.invalidate();
|
|
856
|
+
browser.invalidate();
|
|
349
857
|
syncThemedContent();
|
|
350
858
|
},
|
|
351
|
-
handleInput
|
|
352
|
-
|
|
353
|
-
|
|
859
|
+
handleInput(data: string) {
|
|
860
|
+
if (browser.handleBrowseInput(data)) {
|
|
861
|
+
tui.requestRender();
|
|
862
|
+
}
|
|
354
863
|
},
|
|
355
864
|
};
|
|
356
865
|
})
|
|
@@ -361,7 +870,8 @@ export async function browseRemotePackages(
|
|
|
361
870
|
ctx: ExtensionCommandContext,
|
|
362
871
|
query: string,
|
|
363
872
|
pi: ExtensionAPI,
|
|
364
|
-
offset = 0
|
|
873
|
+
offset = 0,
|
|
874
|
+
source?: RemoteBrowseSource
|
|
365
875
|
): Promise<void> {
|
|
366
876
|
if (
|
|
367
877
|
!requireCustomUI(
|
|
@@ -373,25 +883,49 @@ export async function browseRemotePackages(
|
|
|
373
883
|
return;
|
|
374
884
|
}
|
|
375
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;
|
|
376
897
|
let allPackages: NpmPackage[] | undefined;
|
|
377
898
|
|
|
378
|
-
if (
|
|
379
|
-
const cache =
|
|
899
|
+
if (browseSource === "community") {
|
|
900
|
+
const cache = getCommunityBrowseCache();
|
|
380
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) {
|
|
381
907
|
allPackages = cache.results;
|
|
382
908
|
}
|
|
383
909
|
}
|
|
384
910
|
|
|
385
911
|
if (!allPackages) {
|
|
912
|
+
const searchLabel =
|
|
913
|
+
browseSource === "community"
|
|
914
|
+
? "community packages"
|
|
915
|
+
: plan.displayQuery || "community packages";
|
|
386
916
|
const results = await runTaskWithLoader(
|
|
387
917
|
ctx,
|
|
388
918
|
{
|
|
389
|
-
title:
|
|
390
|
-
message: `Searching npm for ${truncate(
|
|
919
|
+
title: plan.title,
|
|
920
|
+
message: `Searching npm for ${truncate(searchLabel, 40)}...`,
|
|
391
921
|
},
|
|
392
922
|
async ({ signal, setMessage }) => {
|
|
393
|
-
setMessage(`Searching npm for ${truncate(
|
|
394
|
-
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
|
+
);
|
|
395
929
|
}
|
|
396
930
|
);
|
|
397
931
|
|
|
@@ -400,40 +934,44 @@ export async function browseRemotePackages(
|
|
|
400
934
|
return;
|
|
401
935
|
}
|
|
402
936
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
+
}
|
|
409
948
|
}
|
|
410
949
|
|
|
411
|
-
// Apply pagination from cached/filtered results
|
|
412
950
|
const totalResults = allPackages.length;
|
|
413
951
|
const packages = allPackages.slice(offset, offset + PAGE_SIZE);
|
|
952
|
+
const reloadQuery =
|
|
953
|
+
browseSource === "community" ? plan.displayQuery || COMMUNITY_BROWSE_QUERY : plan.rawQuery;
|
|
414
954
|
|
|
415
955
|
if (packages.length === 0) {
|
|
416
|
-
const msg =
|
|
956
|
+
const msg =
|
|
957
|
+
offset > 0
|
|
958
|
+
? "No more packages to show."
|
|
959
|
+
: `No packages found for: ${plan.displayQuery || "community packages"}`;
|
|
417
960
|
ctx.ui.notify(msg, "info");
|
|
418
961
|
|
|
419
962
|
if (offset > 0) {
|
|
420
|
-
await browseRemotePackages(ctx,
|
|
963
|
+
await browseRemotePackages(ctx, reloadQuery, pi, 0, browseSource);
|
|
421
964
|
}
|
|
422
965
|
return;
|
|
423
966
|
}
|
|
424
967
|
|
|
425
|
-
// Add navigation options
|
|
426
968
|
const showLoadMore = totalResults >= PAGE_SIZE && offset + PAGE_SIZE < totalResults;
|
|
427
969
|
const showPrevious = offset > 0;
|
|
428
970
|
|
|
429
|
-
const titleText =
|
|
430
|
-
offset > 0
|
|
431
|
-
? `Search Results (${offset + 1}-${offset + packages.length} of ${totalResults})`
|
|
432
|
-
: `Search: ${truncate(query, 40)} (${totalResults})`;
|
|
433
|
-
|
|
434
971
|
const result = await selectBrowseAction(
|
|
435
972
|
ctx,
|
|
436
|
-
|
|
973
|
+
plan,
|
|
974
|
+
browseSource,
|
|
437
975
|
packages,
|
|
438
976
|
offset,
|
|
439
977
|
totalResults,
|
|
@@ -445,23 +983,51 @@ export async function browseRemotePackages(
|
|
|
445
983
|
return;
|
|
446
984
|
}
|
|
447
985
|
|
|
448
|
-
// Handle result
|
|
449
986
|
switch (result.type) {
|
|
450
987
|
case "prev":
|
|
451
|
-
await browseRemotePackages(
|
|
988
|
+
await browseRemotePackages(
|
|
989
|
+
ctx,
|
|
990
|
+
reloadQuery,
|
|
991
|
+
pi,
|
|
992
|
+
Math.max(0, offset - PAGE_SIZE),
|
|
993
|
+
browseSource
|
|
994
|
+
);
|
|
452
995
|
return;
|
|
453
996
|
case "next":
|
|
454
|
-
await browseRemotePackages(ctx,
|
|
997
|
+
await browseRemotePackages(ctx, reloadQuery, pi, offset + PAGE_SIZE, browseSource);
|
|
455
998
|
return;
|
|
456
999
|
case "refresh":
|
|
457
|
-
|
|
458
|
-
|
|
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);
|
|
459
1025
|
return;
|
|
460
1026
|
case "menu":
|
|
461
1027
|
await showRemoteMenu(ctx, pi);
|
|
462
1028
|
return;
|
|
463
1029
|
case "package":
|
|
464
|
-
await showPackageDetails(result.name, ctx, pi,
|
|
1030
|
+
await showPackageDetails(result.name, ctx, pi, reloadQuery, offset, browseSource);
|
|
465
1031
|
return;
|
|
466
1032
|
}
|
|
467
1033
|
}
|
|
@@ -471,7 +1037,8 @@ async function showPackageDetails(
|
|
|
471
1037
|
ctx: ExtensionCommandContext,
|
|
472
1038
|
pi: ExtensionAPI,
|
|
473
1039
|
previousQuery: string,
|
|
474
|
-
previousOffset: number
|
|
1040
|
+
previousOffset: number,
|
|
1041
|
+
browseSource?: RemoteBrowseSource
|
|
475
1042
|
): Promise<void> {
|
|
476
1043
|
if (!ctx.hasUI) {
|
|
477
1044
|
console.log(`Package: ${packageName}`);
|
|
@@ -484,12 +1051,30 @@ async function showPackageDetails(
|
|
|
484
1051
|
);
|
|
485
1052
|
|
|
486
1053
|
switch (choice) {
|
|
487
|
-
case "installManaged":
|
|
488
|
-
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);
|
|
489
1064
|
return;
|
|
490
|
-
|
|
491
|
-
|
|
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);
|
|
492
1076
|
return;
|
|
1077
|
+
}
|
|
493
1078
|
case "viewInfo":
|
|
494
1079
|
try {
|
|
495
1080
|
const text = await runTaskWithLoader(
|
|
@@ -503,7 +1088,14 @@ async function showPackageDetails(
|
|
|
503
1088
|
|
|
504
1089
|
if (!text) {
|
|
505
1090
|
notify(ctx, `Loading ${packageName} details was cancelled.`, "info");
|
|
506
|
-
await showPackageDetails(
|
|
1091
|
+
await showPackageDetails(
|
|
1092
|
+
packageName,
|
|
1093
|
+
ctx,
|
|
1094
|
+
pi,
|
|
1095
|
+
previousQuery,
|
|
1096
|
+
previousOffset,
|
|
1097
|
+
browseSource
|
|
1098
|
+
);
|
|
507
1099
|
return;
|
|
508
1100
|
}
|
|
509
1101
|
|
|
@@ -512,10 +1104,10 @@ async function showPackageDetails(
|
|
|
512
1104
|
const message = error instanceof Error ? error.message : String(error);
|
|
513
1105
|
ctx.ui.notify(`Package: ${packageName}\n${message}`, "warning");
|
|
514
1106
|
}
|
|
515
|
-
await showPackageDetails(packageName, ctx, pi, previousQuery, previousOffset);
|
|
1107
|
+
await showPackageDetails(packageName, ctx, pi, previousQuery, previousOffset, browseSource);
|
|
516
1108
|
return;
|
|
517
1109
|
case "back":
|
|
518
|
-
await browseRemotePackages(ctx, previousQuery, pi, previousOffset);
|
|
1110
|
+
await browseRemotePackages(ctx, previousQuery, pi, previousOffset, browseSource);
|
|
519
1111
|
return;
|
|
520
1112
|
default:
|
|
521
1113
|
return;
|
|
@@ -523,7 +1115,7 @@ async function showPackageDetails(
|
|
|
523
1115
|
}
|
|
524
1116
|
|
|
525
1117
|
async function promptSearch(ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
|
|
526
|
-
const query = await ctx.ui.input("Search packages", "
|
|
1118
|
+
const query = await ctx.ui.input("Search packages", "package name, keyword, or npm:@scope/pkg");
|
|
527
1119
|
if (!query?.trim()) return;
|
|
528
1120
|
await searchPackages(query.trim(), ctx, pi);
|
|
529
1121
|
}
|