Skip to content

Commit 94755ed

Browse files
authored
Terminal suggest - include persistent options in suggestions and improve suggestion grouping (#276409)
* Add support for persistent options in Fig suggestions * Group arguments together * Group symlink files/folders together * Update test * Fix test * Use helper function
1 parent 18e24e8 commit 94755ed

File tree

4 files changed

+96
-2
lines changed

4 files changed

+96
-2
lines changed

extensions/terminal-suggest/src/fig/figInterface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ export async function collectCompletionItemResult(
341341
}
342342
if (parsedArguments.suggestionFlags & SuggestionFlag.Options) {
343343
await addSuggestions(parsedArguments.completionObj.options, vscode.TerminalCompletionItemKind.Flag, parsedArguments);
344+
await addSuggestions(parsedArguments.completionObj.persistentOptions, vscode.TerminalCompletionItemKind.Flag, parsedArguments);
344345
}
345346

346347
return { showFiles, showFolders, fileExtensions };

extensions/terminal-suggest/src/test/fig.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,56 @@ export const figGenericTestSuites: ISuiteSpec[] = [
183183
{ input: 'foo b|', expectedCompletions: ['b', 'foo'] },
184184
{ input: 'foo c|', expectedCompletions: ['c', 'foo'] },
185185
]
186+
},
187+
{
188+
name: 'Fig persistent options',
189+
completionSpecs: [
190+
{
191+
name: 'foo',
192+
description: 'Foo',
193+
options: [
194+
{ name: '--help', description: 'Show help', isPersistent: true },
195+
{ name: '--docs', description: 'Show docs' },
196+
{ name: '--version', description: 'Version info', isPersistent: false }
197+
],
198+
subcommands: [
199+
{
200+
name: 'bar',
201+
description: 'Bar subcommand',
202+
options: [
203+
{ name: '--local', description: 'Local option' }
204+
]
205+
},
206+
{
207+
name: 'baz',
208+
description: 'Baz subcommand',
209+
options: [
210+
{ name: '--another', description: 'Another option' }
211+
],
212+
subcommands: [
213+
{
214+
name: 'nested',
215+
description: 'Nested subcommand'
216+
}
217+
]
218+
}
219+
]
220+
}
221+
],
222+
availableCommands: 'foo',
223+
testSpecs: [
224+
// Top-level should show all options including persistent
225+
{ input: 'foo |', expectedCompletions: ['--help', '--docs', '--version', 'bar', 'baz'] },
226+
// First-level subcommand should only inherit persistent options (not --docs or --version)
227+
{ input: 'foo bar |', expectedCompletions: ['--help', '--local'] },
228+
// Another first-level subcommand should also inherit only persistent options
229+
{ input: 'foo baz |', expectedCompletions: ['--help', '--another', 'nested'] },
230+
// Nested subcommand should inherit persistent options from top level
231+
{ input: 'foo baz nested |', expectedCompletions: ['--help'] },
232+
// Persistent options should be available even after using local options
233+
{ input: 'foo bar --local |', expectedCompletions: ['--help'] },
234+
]
186235
}
187236
];
188237

238+

src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionModel.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,16 @@ const compareCompletionsFn = (leadingLineContent: string, a: TerminalCompletionI
130130
if ((b.completion.kind === TerminalCompletionItemKind.Method || b.completion.kind === TerminalCompletionItemKind.Alias) && (a.completion.kind !== TerminalCompletionItemKind.Method && a.completion.kind !== TerminalCompletionItemKind.Alias)) {
131131
return 1; // Methods and aliases should come first
132132
}
133-
if ((a.completion.kind === TerminalCompletionItemKind.File || a.completion.kind === TerminalCompletionItemKind.Folder) && (b.completion.kind !== TerminalCompletionItemKind.File && b.completion.kind !== TerminalCompletionItemKind.Folder)) {
133+
if (a.completion.kind === TerminalCompletionItemKind.Argument && b.completion.kind !== TerminalCompletionItemKind.Argument) {
134+
return -1; // Arguments should come before other kinds
135+
}
136+
if (b.completion.kind === TerminalCompletionItemKind.Argument && a.completion.kind !== TerminalCompletionItemKind.Argument) {
137+
return 1; // Arguments should come before other kinds
138+
}
139+
if (isResourceKind(a.completion.kind) && !isResourceKind(b.completion.kind)) {
134140
return 1; // Resources should come last
135141
}
136-
if ((b.completion.kind === TerminalCompletionItemKind.File || b.completion.kind === TerminalCompletionItemKind.Folder) && (a.completion.kind !== TerminalCompletionItemKind.File && a.completion.kind !== TerminalCompletionItemKind.Folder)) {
142+
if (isResourceKind(b.completion.kind) && !isResourceKind(a.completion.kind)) {
137143
return -1; // Resources should come last
138144
}
139145
}
@@ -143,6 +149,12 @@ const compareCompletionsFn = (leadingLineContent: string, a: TerminalCompletionI
143149
return a.labelLow.localeCompare(b.labelLow, undefined, { ignorePunctuation: true });
144150
};
145151

152+
const isResourceKind = (kind: TerminalCompletionItemKind | undefined) =>
153+
kind === TerminalCompletionItemKind.File ||
154+
kind === TerminalCompletionItemKind.Folder ||
155+
kind === TerminalCompletionItemKind.SymbolicLinkFile ||
156+
kind === TerminalCompletionItemKind.SymbolicLinkFolder;
157+
146158
// TODO: This should be based on the process OS, not the local OS
147159
// File score boosts for specific file extensions on Windows. This only applies when the file is the
148160
// _first_ part of the command line.

src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionModel.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,35 @@ suite('TerminalCompletionModel', function () {
360360
assertItems(model, ['main', 'master', 'dev']);
361361
});
362362
});
363+
364+
suite('mixed kind sorting', () => {
365+
test('should sort arguments before flags and options', () => {
366+
const items = [
367+
createItem({ kind: TerminalCompletionItemKind.Flag, label: '--verbose' }),
368+
createItem({ kind: TerminalCompletionItemKind.Option, label: '--config' }),
369+
createItem({ kind: TerminalCompletionItemKind.Argument, label: 'value2' }),
370+
createItem({ kind: TerminalCompletionItemKind.Argument, label: 'value1' }),
371+
createItem({ kind: TerminalCompletionItemKind.Flag, label: '--all' }),
372+
];
373+
const model = new TerminalCompletionModel(items, new LineContext('cmd ', 0));
374+
assertItems(model, ['value1', 'value2', '--all', '--config', '--verbose']);
375+
});
376+
377+
test('should sort by kind hierarchy: methods/aliases, arguments, others, files/folders', () => {
378+
const items = [
379+
createItem({ kind: TerminalCompletionItemKind.File, label: 'file.txt' }),
380+
createItem({ kind: TerminalCompletionItemKind.Flag, label: '--flag' }),
381+
createItem({ kind: TerminalCompletionItemKind.Argument, label: 'arg' }),
382+
createItem({ kind: TerminalCompletionItemKind.Method, label: 'method' }),
383+
createItem({ kind: TerminalCompletionItemKind.Folder, label: 'folder/' }),
384+
createItem({ kind: TerminalCompletionItemKind.Option, label: '--option' }),
385+
createItem({ kind: TerminalCompletionItemKind.Alias, label: 'alias' }),
386+
createItem({ kind: TerminalCompletionItemKind.SymbolicLinkFile, label: 'file2.txt' }),
387+
createItem({ kind: TerminalCompletionItemKind.SymbolicLinkFolder, label: 'folder2/' }),
388+
];
389+
const model = new TerminalCompletionModel(items, new LineContext('', 0));
390+
assertItems(model, ['alias', 'method', 'arg', '--flag', '--option', 'file2.txt', 'file.txt', 'folder/', 'folder2/']);
391+
});
392+
});
363393
});
394+

0 commit comments

Comments
 (0)