Skip to content

Commit 72a94a5

Browse files
committed
move the package manager completion logic directly into the parse method
1 parent 94f8491 commit 72a94a5

File tree

2 files changed

+100
-78
lines changed

2 files changed

+100
-78
lines changed

bin/completion-handlers.ts

Lines changed: 2 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -71,80 +71,13 @@ export function setupCompletionForPackageManager(
7171
setupBunCompletions(completion);
7272
}
7373

74-
completion.onBeforeParse(async (args: string[]) => {
75-
debugLog(`onBeforeParse: args =`, args);
76-
77-
if (args.length >= 1) {
78-
const potentialCliName = args[0];
79-
const knownCommands = [...completion.commands.keys()];
80-
81-
debugLog(
82-
`Potential CLI: ${potentialCliName}, Known commands:`,
83-
knownCommands
84-
);
85-
86-
if (knownCommands.includes(potentialCliName)) {
87-
debugLog(`${potentialCliName} is a known command, skipping CLI check`);
88-
return;
89-
}
90-
91-
const hasCompletions = await checkCliHasCompletions(
92-
potentialCliName,
93-
packageManager
94-
);
95-
if (hasCompletions) {
96-
debugLog(
97-
`${potentialCliName} supports completions, getting suggestions`
98-
);
99-
100-
const cliArgs = args.slice(1);
101-
const suggestions = await getCliCompletions(
102-
potentialCliName,
103-
packageManager,
104-
cliArgs
105-
);
106-
107-
if (suggestions.length > 0) {
108-
debugLog(`Processing ${suggestions.length} suggestions`);
109-
110-
completion.result.suppressDefault = true;
111-
112-
for (const suggestion of suggestions) {
113-
if (suggestion.startsWith(':')) {
114-
debugLog(`Skipping directive: ${suggestion}`);
115-
continue;
116-
}
117-
118-
if (suggestion.includes('\t')) {
119-
const [value, description] = suggestion.split('\t');
120-
debugLog(
121-
`Adding completion with description: ${value} -> ${description}`
122-
);
123-
completion.result.items.push({ value, description });
124-
} else {
125-
debugLog(`Adding completion without description: ${suggestion}`);
126-
completion.result.items.push({ value: suggestion });
127-
}
128-
}
129-
} else {
130-
debugLog(`No suggestions found for ${potentialCliName}`);
131-
}
132-
} else {
133-
debugLog(`${potentialCliName} does not support completions`);
134-
}
135-
}
136-
});
74+
completion.setPackageManager(packageManager);
13775
}
13876

13977
export function setupPnpmCompletions(completion: Completion) {
14078
completion.addCommand('add', 'Install a package', [], async () => []);
14179
completion.addCommand('remove', 'Remove a package', [], async () => []);
142-
completion.addCommand(
143-
'install',
144-
'Install all dependencies',
145-
[],
146-
async () => []
147-
);
80+
completion.addCommand('install', 'Install all dependencies', [], async () => []);
14881
completion.addCommand('update', 'Update packages', [], async () => []);
14982
completion.addCommand('exec', 'Execute a command', [], async () => []);
15083
completion.addCommand('run', 'Run a script', [], async () => []);

src/index.ts

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,63 @@ import * as zsh from './zsh';
22
import * as bash from './bash';
33
import * as fish from './fish';
44
import * as powershell from './powershell';
5+
import { execSync } from 'child_process';
6+
7+
const DEBUG = false;
8+
9+
function debugLog(...args: any[]) {
10+
if (DEBUG) {
11+
console.error('[DEBUG]', ...args);
12+
}
13+
}
14+
15+
async function checkCliHasCompletions(
16+
cliName: string,
17+
packageManager: string
18+
): Promise<boolean> {
19+
try {
20+
debugLog(`Checking if ${cliName} has completions via ${packageManager}`);
21+
const command = `${packageManager} ${cliName} complete --`;
22+
const result = execSync(command, {
23+
encoding: 'utf8',
24+
stdio: ['pipe', 'pipe', 'ignore'],
25+
timeout: 1000,
26+
});
27+
const hasCompletions = !!result.trim();
28+
debugLog(`${cliName} supports completions: ${hasCompletions}`);
29+
return hasCompletions;
30+
} catch (error) {
31+
debugLog(`Error checking completions for ${cliName}:`, error);
32+
return false;
33+
}
34+
}
35+
36+
async function getCliCompletions(
37+
cliName: string,
38+
packageManager: string,
39+
args: string[]
40+
): Promise<string[]> {
41+
try {
42+
const completeArgs = args.map((arg) =>
43+
arg.includes(' ') ? `"${arg}"` : arg
44+
);
45+
const completeCommand = `${packageManager} ${cliName} complete -- ${completeArgs.join(' ')}`;
46+
debugLog(`Getting completions with command: ${completeCommand}`);
47+
48+
const result = execSync(completeCommand, {
49+
encoding: 'utf8',
50+
stdio: ['pipe', 'pipe', 'ignore'],
51+
timeout: 1000,
52+
});
53+
54+
const completions = result.trim().split('\n').filter(Boolean);
55+
debugLog(`Got ${completions.length} completions from ${cliName}`);
56+
return completions;
57+
} catch (error) {
58+
debugLog(`Error getting completions from ${cliName}:`, error);
59+
return [];
60+
}
61+
}
562

663
// ShellCompRequestCmd is the name of the hidden command that is used to request
764
// completion results from the program. It is used by the shell completion scripts.
@@ -97,10 +154,10 @@ export class Completion {
97154
completions: Item[] = [];
98155
directive = ShellCompDirective.ShellCompDirectiveDefault;
99156
result: CompletionResult = { items: [], suppressDefault: false };
100-
private beforeParseFn: ((args: string[]) => Promise<void>) | null = null;
157+
private packageManager: string | null = null;
101158

102-
onBeforeParse(fn: (args: string[]) => Promise<void>) {
103-
this.beforeParseFn = fn;
159+
setPackageManager(packageManager: string) {
160+
this.packageManager = packageManager;
104161
}
105162

106163
// vite <entry> <another> [...files]
@@ -184,12 +241,44 @@ export class Completion {
184241
async parse(args: string[]) {
185242
this.result = { items: [], suppressDefault: false };
186243

187-
if (this.beforeParseFn) {
188-
await this.beforeParseFn(args);
189-
if (this.result.suppressDefault && this.result.items.length > 0) {
190-
this.completions = this.result.items;
191-
this.complete('');
192-
return;
244+
// Handle package manager completions first
245+
if (this.packageManager && args.length >= 1) {
246+
const potentialCliName = args[0];
247+
const knownCommands = [...this.commands.keys()];
248+
249+
if (!knownCommands.includes(potentialCliName)) {
250+
const hasCompletions = await checkCliHasCompletions(
251+
potentialCliName,
252+
this.packageManager
253+
);
254+
255+
if (hasCompletions) {
256+
const cliArgs = args.slice(1);
257+
const suggestions = await getCliCompletions(
258+
potentialCliName,
259+
this.packageManager,
260+
cliArgs
261+
);
262+
263+
if (suggestions.length > 0) {
264+
this.result.suppressDefault = true;
265+
266+
for (const suggestion of suggestions) {
267+
if (suggestion.startsWith(':')) continue;
268+
269+
if (suggestion.includes('\t')) {
270+
const [value, description] = suggestion.split('\t');
271+
this.result.items.push({ value, description });
272+
} else {
273+
this.result.items.push({ value: suggestion });
274+
}
275+
}
276+
277+
this.completions = this.result.items;
278+
this.complete('');
279+
return;
280+
}
281+
}
193282
}
194283
}
195284

0 commit comments

Comments
 (0)