diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 7c375d4ba70..fa79d5c378a 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -240,6 +240,8 @@ export async function getCompletionItemsFromSpecs( let foldersRequested = false; const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1); + // TODO: Normalize precedingText to ignore file extensions on Windows + // precedingText = precedingText.replace('.cmd', ''); let specificItemsProvided = false; for (const spec of specs) { @@ -251,6 +253,8 @@ export async function getCompletionItemsFromSpecs( for (const specLabel of specLabels) { const availableCommand = availableCommands.find(command => specLabel === command.label); + // TODO: Normalize commands to ignore file extensions on Windows https://github.com/microsoft/vscode/issues/237598 + // const availableCommand = availableCommands.find(command => command.label.startsWith(specLabel)); if (!availableCommand || (token && token.isCancellationRequested)) { continue; } @@ -263,27 +267,29 @@ export async function getCompletionItemsFromSpecs( continue; } + // TODO: Normalize commands to ignore file extensions on Windows https://github.com/microsoft/vscode/issues/237598 + // const commandAndAliases = availableCommands.filter(command => specLabel === (command.definitionCommand ?? command.label).replace('.cmd', '')); + // if (!commandAndAliases.some(e => terminalContext.commandLine.startsWith(`${e.label} `) || terminalContext.commandLine.startsWith(`${e.label}.cmd `))) { const commandAndAliases = availableCommands.filter(command => specLabel === (command.definitionCommand ?? command.label)); if (!commandAndAliases.some(e => terminalContext.commandLine.startsWith(`${e.label} `))) { // the spec label is not the first word in the command line, so do not provide options or args continue; } - const argsCompletionResult = handleArguments(specLabel, spec, terminalContext, precedingText); - if (argsCompletionResult) { - items.push(...argsCompletionResult.items); - filesRequested ||= argsCompletionResult.filesRequested; - foldersRequested ||= argsCompletionResult.foldersRequested; - specificItemsProvided ||= argsCompletionResult.items.length > 0; + const optionsCompletionResult = handleOptions(specLabel, spec, terminalContext, precedingText, prefix); + if (optionsCompletionResult) { + items.push(...optionsCompletionResult.items); + filesRequested ||= optionsCompletionResult.filesRequested; + foldersRequested ||= optionsCompletionResult.foldersRequested; + specificItemsProvided ||= optionsCompletionResult.items.length > 0; } - if (!argsCompletionResult?.items.length) { - // Arg completions are more specific, only get options if those are not provided. - const optionsCompletionResult = handleOptions(specLabel, spec, terminalContext, precedingText, prefix); - if (optionsCompletionResult) { - items.push(...optionsCompletionResult.items); - filesRequested ||= optionsCompletionResult.filesRequested; - foldersRequested ||= optionsCompletionResult.foldersRequested; - specificItemsProvided ||= optionsCompletionResult.items.length > 0; + if (!optionsCompletionResult?.isOptionArg) { + const argsCompletionResult = handleArguments(specLabel, spec, terminalContext, precedingText); + if (argsCompletionResult) { + items.push(...argsCompletionResult.items); + filesRequested ||= argsCompletionResult.filesRequested; + foldersRequested ||= argsCompletionResult.foldersRequested; + specificItemsProvided ||= argsCompletionResult.items.length > 0; } } } @@ -341,7 +347,7 @@ function handleArguments(specLabel: string, spec: Fig.Spec, terminalContext: { c return argsCompletions; } -function handleOptions(specLabel: string, spec: Fig.Spec, terminalContext: { commandLine: string; cursorPosition: number }, precedingText: string, prefix: string): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean } | undefined { +function handleOptions(specLabel: string, spec: Fig.Spec, terminalContext: { commandLine: string; cursorPosition: number }, precedingText: string, prefix: string): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; isOptionArg: boolean } | undefined { let options; if ('options' in spec && spec.options) { options = spec.options; @@ -387,12 +393,12 @@ function handleOptions(specLabel: string, spec: Fig.Spec, terminalContext: { com const argsCompletions = getCompletionItemsFromArgs(option.args, currentPrefix, terminalContext); if (argsCompletions) { - return { items: argsCompletions.items, filesRequested: argsCompletions.filesRequested, foldersRequested: argsCompletions.foldersRequested }; + return { items: argsCompletions.items, filesRequested: argsCompletions.filesRequested, foldersRequested: argsCompletions.foldersRequested, isOptionArg: true }; } } } - return { items: optionItems, filesRequested: false, foldersRequested: false }; + return { items: optionItems, filesRequested: false, foldersRequested: false, isOptionArg: false }; } @@ -409,11 +415,17 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray | undefined continue; } if (arg.template) { - if (arg.template === 'filepaths') { + if (Array.isArray(arg.template) ? arg.template.includes('filepaths') : arg.template === 'filepaths') { filesRequested = true; - } else if (arg.template === 'folders') { + } + if (Array.isArray(arg.template) ? arg.template.includes('folders') : arg.template === 'folders') { foldersRequested = true; } + // if (arg.template === 'filepaths') { + // filesRequested = true; + // } else if (arg.template === 'folders') { + // foldersRequested = true; + // } } if (arg.suggestions?.length) { // there are specific suggestions to show diff --git a/extensions/terminal-suggest/src/test/completions/code.test.ts b/extensions/terminal-suggest/src/test/completions/code.test.ts index f65cd2e4d5d..50ad0458a22 100644 --- a/extensions/terminal-suggest/src/test/completions/code.test.ts +++ b/extensions/terminal-suggest/src/test/completions/code.test.ts @@ -25,7 +25,7 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] { ...typingTests, // Basic arguments - { input: `${executable} |`, expectedCompletions: codeOptions }, + { input: `${executable} |`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, { input: `${executable} --locale |`, expectedCompletions: localeOptions }, { input: `${executable} --diff |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, { input: `${executable} --diff ./file1 |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, @@ -40,13 +40,13 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] { { input: `${executable} --log |`, expectedCompletions: logOptions }, { input: `${executable} --sync |`, expectedCompletions: syncOptions }, { input: `${executable} --extensions-dir |`, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, - { input: `${executable} --list-extensions |`, expectedCompletions: codeOptions }, - { input: `${executable} --show-versions |`, expectedCompletions: codeOptions }, + { input: `${executable} --list-extensions |`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: `${executable} --show-versions |`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, { input: `${executable} --category |`, expectedCompletions: categoryOptions }, { input: `${executable} --category a|`, expectedCompletions: categoryOptions.filter(c => c.startsWith('a')) }, // Middle of command - { input: `${executable} | --locale`, expectedCompletions: codeOptions }, + { input: `${executable} | --locale`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, ]; } diff --git a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts index dfd7da8a93e..36ea72e4083 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts @@ -67,27 +67,27 @@ export const lsTestSuiteSpec: ISuiteSpec = { // Basic options // TODO: The spec wants file paths and folders (which seems like it should only be folders), // but neither are requested https://github.com/microsoft/vscode/issues/239606 - { input: 'ls |', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls -|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls |', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls -|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, // Filtering options should request all options so client side can filter - { input: 'ls -a|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls -a|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, // Duplicate option // TODO: Duplicate options should not be presented https://github.com/microsoft/vscode/issues/239607 - // { input: 'ls -a -|', expectedCompletions: removeArrayEntry(allOptions, '-a'), expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + // { input: 'ls -a -|', expectedCompletions: removeArrayEntry(allOptions, '-a'), expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, // Relative paths - { input: 'ls c|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls child|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls .|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls ./|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls ./child|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls ..|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls c|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls child|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls .|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls ./|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls ./child|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls ..|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, // Relative directories (changes cwd due to /) - { input: 'ls child/|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwdChild } - { input: 'ls ../|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwdParent } - { input: 'ls ../sibling|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwdParent } + { input: 'ls child/|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwdChild } }, + { input: 'ls ../|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwdParent } }, + { input: 'ls ../sibling|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwdParent } }, ] }; diff --git a/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts new file mode 100644 index 00000000000..ad025c27a7b --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import { testPaths, type ISuiteSpec } from '../../helpers'; +import rmSpec from '../../../completions/upstream/rm'; + +const allOptions = [ + '-P', + '-R', + '-d', + '-f', + '-i', + '-r', + '-v', +]; + +export const rmTestSuiteSpec: ISuiteSpec = { + name: 'rm', + completionSpecs: rmSpec, + availableCommands: 'rm', + testSpecs: [ + // Empty input + { input: '|', expectedCompletions: ['rm'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Typing the command + { input: 'r|', expectedCompletions: ['rm'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'rm|', expectedCompletions: ['rm'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Basic options + { input: 'rm |', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Duplicate option + // TODO: Duplicate options should not be presented https://github.com/microsoft/vscode/issues/239607 + // { input: `rm -${allOptions[0]} -|`, expectedCompletions: removeArrayEntries(allOptions, allOptions[0]) }, + // { input: `rm -${allOptions[0]} -${allOptions[1]} -|`, expectedCompletions: removeArrayEntries(allOptions, allOptions[0], allOptions[1]) }, + ] +}; diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index a79f1c7432a..0cdf23b70d9 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -15,6 +15,7 @@ import { codeInsidersTestSuite } from './completions/code-insiders.test'; import { lsTestSuiteSpec } from './completions/upstream/ls.test'; import { echoTestSuiteSpec } from './completions/upstream/echo.test'; import { mkdirTestSuiteSpec } from './completions/upstream/mkdir.test'; +import { rmTestSuiteSpec } from './completions/upstream/rm.test'; const testSpecs2: ISuiteSpec[] = [ { @@ -38,6 +39,7 @@ const testSpecs2: ISuiteSpec[] = [ echoTestSuiteSpec, lsTestSuiteSpec, mkdirTestSuiteSpec, + rmTestSuiteSpec, ]; suite('Terminal Suggest', () => {