fix replacement index,length for terminal completions (#236260)

fix bugs
This commit is contained in:
Megan Rogge
2024-12-16 13:24:42 -06:00
committed by GitHub
parent 50dca635cb
commit 466eb0be75
3 changed files with 78 additions and 10 deletions

View File

@@ -158,12 +158,14 @@ function getLabel(spec: Fig.Spec | Fig.Arg | Fig.Suggestion | string): string[]
return spec.name;
}
function createCompletionItem(commandLine: string, cursorPosition: number, prefix: string, label: string, description?: string, kind?: vscode.TerminalCompletionItemKind): vscode.TerminalCompletionItem {
function createCompletionItem(cursorPosition: number, prefix: string, label: string, description?: string, kind?: vscode.TerminalCompletionItemKind): vscode.TerminalCompletionItem {
const endsWithSpace = prefix.endsWith(' ');
const lastWord = endsWithSpace ? '' : prefix.split(' ').at(-1) ?? '';
return {
label,
detail: description ?? '',
replacementIndex: commandLine.length - prefix.length >= 0 ? commandLine.length - prefix.length : commandLine[cursorPosition - 1] === ' ' ? cursorPosition : cursorPosition - 1,
replacementLength: prefix.length,
replacementIndex: cursorPosition - lastWord.length,
replacementLength: lastWord.length > 0 ? lastWord.length : cursorPosition,
kind: kind ?? vscode.TerminalCompletionItemKind.Method
};
}
@@ -268,7 +270,7 @@ export async function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalCon
|| !!firstCommand && specLabel.startsWith(firstCommand)
) {
// push it to the completion items
items.push(createCompletionItem(terminalContext.commandLine, terminalContext.cursorPosition, prefix, specLabel));
items.push(createCompletionItem(terminalContext.cursorPosition, prefix, specLabel));
}
if (!terminalContext.commandLine.startsWith(specLabel)) {
// the spec label is not the first word in the command line, so do not provide options or args
@@ -283,7 +285,7 @@ export async function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalCon
}
for (const optionLabel of optionLabels) {
if (!items.find(i => i.label === optionLabel) && optionLabel.startsWith(prefix) || (prefix.length > specLabel.length && prefix.trim() === specLabel)) {
items.push(createCompletionItem(terminalContext.commandLine, terminalContext.cursorPosition, prefix, optionLabel, option.description, vscode.TerminalCompletionItemKind.Flag));
items.push(createCompletionItem(terminalContext.cursorPosition, prefix, optionLabel, option.description, vscode.TerminalCompletionItemKind.Flag));
}
const expectedText = `${specLabel} ${optionLabel} `;
if (!precedingText.includes(expectedText)) {
@@ -331,7 +333,7 @@ export async function getCompletionItemsFromSpecs(specs: Fig.Spec[], terminalCon
// Include builitin/available commands in the results
for (const command of availableCommands) {
if ((!terminalContext.commandLine.trim() || firstCommand && command.startsWith(firstCommand)) && !items.find(item => item.label === command)) {
items.push(createCompletionItem(terminalContext.commandLine, terminalContext.cursorPosition, prefix, command));
items.push(createCompletionItem(terminalContext.cursorPosition, prefix, command));
}
}
}
@@ -397,7 +399,7 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray<Fig.Arg> | undefined
}
if (suggestionLabel && suggestionLabel.startsWith(currentPrefix.trim())) {
const description = typeof suggestion !== 'string' ? suggestion.description : '';
items.push(createCompletionItem(terminalContext.commandLine, terminalContext.cursorPosition, wordBefore ?? '', suggestionLabel, description, vscode.TerminalCompletionItemKind.Argument));
items.push(createCompletionItem(terminalContext.cursorPosition, wordBefore ?? '', suggestionLabel, description, vscode.TerminalCompletionItemKind.Argument));
}
}
}

View File

@@ -249,8 +249,8 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
kind,
isDirectory,
isFile: kind === TerminalCompletionItemKind.File,
replacementIndex: cursorPosition - lastWord.length > 0 ? cursorPosition - lastWord.length : cursorPosition,
replacementLength: lastWord.length > 0 ? lastWord.length : label.length
replacementIndex: cursorPosition - lastWord.length,
replacementLength: lastWord.length > 0 ? lastWord.length : cursorPosition
});
}

View File

@@ -69,6 +69,72 @@ suite('TerminalCompletionService', () => {
});
suite('resolveResources should return folder completions', () => {
test('', async () => {
const resourceRequestConfig: TerminalResourceRequestConfig = {
cwd: URI.parse('file:///test'),
foldersRequested: true,
pathSeparator
};
validResources = [URI.parse('file:///test')];
const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false };
const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true };
childResources = [childFolder, childFile];
const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 1);
assert(!!result);
assert(result.length === 1);
assert.deepEqual(result![0], {
label: `.${pathSeparator}folder1${pathSeparator}`,
kind: TerminalCompletionItemKind.Folder,
isDirectory: true,
isFile: false,
replacementIndex: 1,
replacementLength: 1
});
});
test('.', async () => {
const resourceRequestConfig: TerminalResourceRequestConfig = {
cwd: URI.parse('file:///test'),
foldersRequested: true,
pathSeparator
};
validResources = [URI.parse('file:///test')];
const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false };
const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true };
childResources = [childFolder, childFile];
const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '.', 2);
assert(!!result);
assert(result.length === 1);
assert.deepEqual(result![0], {
label: `.${pathSeparator}folder1${pathSeparator}`,
kind: TerminalCompletionItemKind.Folder,
isDirectory: true,
isFile: false,
replacementIndex: 1,
replacementLength: 1
});
});
test('./', async () => {
const resourceRequestConfig: TerminalResourceRequestConfig = {
cwd: URI.parse('file:///test'),
foldersRequested: true,
pathSeparator
};
validResources = [URI.parse('file:///test')];
const childFolder = { resource: URI.parse('file:///test/folder1/'), name: 'folder1', isDirectory: true, isFile: false };
const childFile = { resource: URI.parse('file:///test/file1.txt'), name: 'file1.txt', isDirectory: false, isFile: true };
childResources = [childFolder, childFile];
const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 3);
assert(!!result);
assert(result.length === 1);
assert.deepEqual(result![0], {
label: `.${pathSeparator}folder1${pathSeparator}`,
kind: TerminalCompletionItemKind.Folder,
isDirectory: true,
isFile: false,
replacementIndex: 1,
replacementLength: 2
});
});
test('cd ', async () => {
const resourceRequestConfig: TerminalResourceRequestConfig = {
cwd: URI.parse('file:///test'),
@@ -88,7 +154,7 @@ suite('TerminalCompletionService', () => {
isDirectory: true,
isFile: false,
replacementIndex: 3,
replacementLength: 10
replacementLength: 3
});
});
test('cd .', async () => {