Skip to content

Commit efb9d2f

Browse files
committed
docs
1 parent 0a10e18 commit efb9d2f

File tree

3 files changed

+72
-23
lines changed

3 files changed

+72
-23
lines changed

src/citty.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export default async function tab<T extends ArgsDef = ArgsDef>(instance: Command
136136
const parsed = parseArgs(extra, args);
137137
// TODO: this is not ideal at all
138138
const matchedCommand = parsed._.join(' ')
139+
console.log(completion)
139140
return completion.parse(extra, matchedCommand);
140141
}
141142
}

src/index.ts

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ export type Positional = {
6262
completion: Handler;
6363
};
6464

65-
type Items = {
65+
type Item = {
6666
description: string;
6767
value: string;
6868
}
6969

70-
type Handler = (previousArgs: string[], toComplete: string, endsWithSpace: boolean) => (Items[] | Promise<Items[]>);
70+
type Handler = (previousArgs: string[], toComplete: string, endsWithSpace: boolean) => (Item[] | Promise<Item[]>);
7171

7272
type Option = {
7373
description: string;
@@ -107,7 +107,7 @@ export class Completion {
107107
async parse(args: string[], potentialCommand: string) {
108108
const matchedCommand = this.commands.get(potentialCommand) ?? this.commands.get('')!;
109109
let directive = ShellCompDirective.ShellCompDirectiveDefault;
110-
const completions: string[] = [];
110+
const completions: Item[] = [];
111111

112112
const endsWithSpace = args[args.length - 1] === "";
113113
if (endsWithSpace) {
@@ -126,7 +126,6 @@ export class Completion {
126126
completions.push(
127127
...flagSuggestions
128128
.filter(comp => comp.value.startsWith(toComplete))
129-
.map(comp => `${comp.value}\t${comp.description ?? ""}`)
130129
);
131130
directive = ShellCompDirective.ShellCompDirectiveNoFileComp;
132131
completions.forEach(comp => console.log(comp));
@@ -147,9 +146,7 @@ export class Completion {
147146

148147
if (handler) {
149148
const suggestions = await handler(previousArgs, valueToComplete, endsWithSpace);
150-
completions.push(...suggestions.map(
151-
comp => `${comp.value}\t${comp.description ?? ""}`
152-
));
149+
completions.push(...suggestions)
153150
}
154151
} else if (!endsWithSpace) {
155152
const options = new Map(matchedCommand.options);
@@ -173,36 +170,69 @@ export class Completion {
173170

174171
completions.push(
175172
...availableFlags.map(
176-
flag => `${flag}\t${options.get(flag)!.description ?? ""}`
173+
flag => ({ value: flag, description: options.get(flag)!.description ?? "" })
177174
)
178175
);
179176
} else {
180177
const { handler } = matchedCommand.options.get(toComplete)!;
181178

182179
if (handler) {
183180
const suggestions = await handler(previousArgs, toComplete, endsWithSpace);
184-
completions.push(...suggestions.map(
185-
comp => `${comp.value}\t${comp.description ?? ""}`
186-
));
181+
completions.push(...suggestions)
187182
}
188183
}
189184
} else {
190-
const availableSubcommands = [...this.commands.keys()]
191-
.filter(cmd => cmd.startsWith(potentialCommand) && cmd !== potentialCommand)
192-
.map(cmd => cmd.replace(`${potentialCommand} `, "").split(" ")[0])
193-
.filter((subcmd, index, self) => self.indexOf(subcmd) === index) // Remove duplicates
194-
.filter(subcmd => subcmd.startsWith(toComplete));
195-
196-
completions.push(
197-
...availableSubcommands.map(
198-
subcmd => `${subcmd}\t${this.commands.get(`${potentialCommand} ${subcmd}`)?.description ?? ""}`
199-
)
200-
);
185+
const potentialCommandParts = potentialCommand.split(' ')
186+
for (const [k, v] of this.commands) {
187+
// if the command is root, skip it
188+
if (k === '') {
189+
continue
190+
}
191+
192+
const parts = k.split(' ')
193+
for (let i = 0; i < parts.length; i++) {
194+
const part = parts[i]
195+
const potentialPart = potentialCommandParts[i] || ''
196+
197+
// Skip if we've already added this suggestion
198+
const alreadyExists = completions.findIndex(item => item.value === part) !== -1
199+
if (alreadyExists) {
200+
break
201+
}
202+
203+
// If we're at the current word being completed
204+
if (i === potentialCommandParts.length - 1) {
205+
// Only add if it matches the current partial input
206+
if (part.startsWith(potentialPart)) {
207+
completions.push({ value: part, description: v.description })
208+
}
209+
break
210+
}
211+
212+
// For previous parts, they must match exactly
213+
if (part !== potentialPart) {
214+
break
215+
}
216+
}
217+
}
218+
completions.push({ value: 'dhere1', description: '' })
201219

202220
directive = ShellCompDirective.ShellCompDirectiveNoFileComp;
203221
}
222+
// vite [...items]
223+
// vite dev
224+
// vite lint [item]
225+
// vite dev build
226+
227+
// TODO: prettier (plus check in ci)
228+
// TODO: ci type check
229+
230+
// TODO: positionals (tomorrow night)
231+
// TODO: cac (tomorrow night)
232+
// TODO: check behaviour of the tests (tomorrow night)
233+
204234

205-
completions.forEach(comp => console.log(comp));
235+
completions.forEach(comp => console.log(`${comp.value}\t${comp.description ?? ""}`));
206236
console.log(`:${directive}`);
207237
}
208238
}

tests/cli.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,24 @@ describe.each(cliTools)("cli completion tests for %s", (cliTool) => {
100100
});
101101
});
102102

103+
// single positional command: `lint [file]`
104+
// vite ""
105+
// -> src/
106+
// -> ./
107+
108+
// vite src/ ""
109+
// -> nothing
110+
// should not suggest anything
111+
112+
// multiple postiionals command `lint [...files]`
113+
// vite ""
114+
// -> src/
115+
// -> ./
116+
117+
// vite src/ ""
118+
// -> src/
119+
// -> ./
120+
103121
describe("positional argument completions", () => {
104122
it("should complete single positional argument when ending with space (vite src/)", async () => {
105123
const command = `${commandPrefix} vite src/ ""`;

0 commit comments

Comments
 (0)