Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,13 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
const lastWordFolderHasDotPrefix = !!lastWordFolder.match(/^\.\.?[\\\/]/);
const lastWordFolderHasTildePrefix = !!lastWordFolder.match(/^~[\\\/]?/);
const isAbsolutePath = getIsAbsolutePath(shellType, resourceOptions.pathSeparator, lastWordFolder, useWindowsStylePath);
const type = lastWordFolderHasTildePrefix ? 'tilde' : isAbsolutePath ? 'absolute' : 'relative';
let type: 'tilde' | 'absolute' | 'relative' = lastWordFolderHasTildePrefix ? 'tilde' : isAbsolutePath ? 'absolute' : 'relative';
const cwd = URI.revive(resourceOptions.cwd);
let lastWordFolderResource: URI | string | undefined;
// When a relative folder prefix can't be resolved against cwd but matches a folder under
// a $CDPATH entry, this is set to that absolute resource so completions are emitted using
// absolute paths instead of failing or producing misleading relative ones. See #241858.
let cdPathFallbackResource: URI | undefined;
if (type === 'relative' && lastWordFolder.length > 0) {
// If the typed folder matches the tail of cwd (common when the extension already
// resolved the path, such as `./src/vs/`), reuse cwd to avoid duplicating segments.
Expand Down Expand Up @@ -358,7 +362,33 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
await this._fileService.stat(folderToResolve);
lastWordFolderResource = folderToResolve;
} catch {
return undefined;
// The relative path doesn't exist under cwd. If we're completing a `cd`
// argument, the input may match a folder reachable through $CDPATH (for
// example zsh's tab completion replaces the typed prefix with an exact
// CDPATH-folder name plus a trailing `/`). In that case, resolve against
// CDPATH entries and fall through to emit absolute path completions so the
// suggestions actually match where `cd` would take the user. See #241858.
if (!hasDotPrefix && promptValue.startsWith('cd ')) {
const cdPathConfig = this._configurationService.getValue(TerminalSuggestSettingId.CdPath);
if (cdPathConfig === 'absolute' || cdPathConfig === 'relative') {
const cdPath = this._getEnvVar('CDPATH', capabilities);
if (cdPath) {
const cdPathEntries = cdPath.split(useWindowsStylePath ? ';' : ':');
for (const cdPathEntry of cdPathEntries) {
const candidate = URI.joinPath(createUriFromLocalPath(cwd, cdPathEntry), normalizedFolder);
try {
await this._fileService.stat(candidate);
cdPathFallbackResource = candidate;
break;
} catch { /* try next CDPATH entry */ }
}
}
}
}
if (!cdPathFallbackResource) {
return undefined;
}
lastWordFolderResource = cdPathFallbackResource;
}
}
} else if (type === 'relative') {
Expand All @@ -373,6 +403,18 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
}
}

// When the relative input was resolved via $CDPATH, emit completions using absolute paths
// rather than relative ones so they actually point to the right directory. See #241858.
if (cdPathFallbackResource) {
type = 'absolute';
let absolutePath = cdPathFallbackResource.fsPath;
if (!absolutePath.endsWith(resourceOptions.pathSeparator)) {
absolutePath += resourceOptions.pathSeparator;
}
lastWordFolder = absolutePath;
lastWordFolderResource = cdPathFallbackResource;
}

switch (type) {
case 'tilde': {
const home = this._getHomeDir(useWindowsStylePath, capabilities);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,35 @@ suite('TerminalCompletionService', () => {
], { replacementRange: [3, 3] });
});

test('cd folder1/ should suggest absolute paths under matching $CDPATH entry (#241858)', async () => {
// Simulates zsh tab-completing a CDPATH entry name: the user typed `cd vsce`,
// pressed Tab, and zsh replaced it with `vscode-vsce/`. The folder doesn't exist
// in cwd but does exist under $CDPATH, so suggestions should use absolute paths.
configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'absolute');
validResources = [
URI.parse('file:///test'),
URI.parse('file:///cdpath_value'),
URI.parse('file:///cdpath_value/folder1')
];
childResources = [
{ resource: URI.parse('file:///cdpath_value/folder1/'), isDirectory: true },
{ resource: URI.parse('file:///cdpath_value/folder1/inner1/'), isDirectory: true },
{ resource: URI.parse('file:///cdpath_value/folder1/inner2/'), isDirectory: true },
];
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.parse('file:///test'),
showDirectories: true,
showFiles: true,
pathSeparator
};
const result = await terminalCompletionService.resolveResources(resourceOptions, 'cd folder1/', 11, provider, capabilities);

assertPartialCompletionsExist(result, [
{ label: '/cdpath_value/folder1/inner1/', detail: '/cdpath_value/folder1/inner1/' },
{ label: '/cdpath_value/folder1/inner2/', detail: '/cdpath_value/folder1/inner2/' },
], { replacementRange: [3, 11] });
Comment on lines +694 to +697
});

test('cd | should support pulling from multiple paths in $CDPATH', async () => {
configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'relative');
const pathPrefix = isWindows ? 'c:\\' : '/';
Expand Down
Loading