fix remote terminal suggest (#286407)

This commit is contained in:
Megan Rogge
2026-01-08 11:48:54 -06:00
committed by GitHub
parent 1ed133172d
commit 28873d3999
2 changed files with 107 additions and 5 deletions

View File

@@ -339,6 +339,15 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
return undefined;
}
}
} else {
// `./` by itself means the current directory, use cwd directly to avoid
// trailing slash issues with URI.joinPath on some remote file systems.
try {
await this._fileService.stat(cwd);
lastWordFolderResource = cwd;
} catch {
return undefined;
}
}
}
@@ -368,7 +377,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
case 'tilde': {
const home = this._getHomeDir(useWindowsStylePath, capabilities);
if (home) {
lastWordFolderResource = URI.joinPath(URI.file(home), lastWordFolder.slice(1).replaceAll('\\ ', ' '));
lastWordFolderResource = URI.joinPath(createUriFromLocalPath(cwd, home), lastWordFolder.slice(1).replaceAll('\\ ', ' '));
}
if (!lastWordFolderResource) {
// Use less strong wording here as it's not as strong of a concept on Windows
@@ -381,9 +390,9 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
}
case 'absolute': {
if (shellType === WindowsShellType.GitBash) {
lastWordFolderResource = URI.file(gitBashToWindowsPath(lastWordFolder, this._processEnv.SystemDrive));
lastWordFolderResource = createUriFromLocalPath(cwd, gitBashToWindowsPath(lastWordFolder, this._processEnv.SystemDrive));
} else {
lastWordFolderResource = URI.file(lastWordFolder.replaceAll('\\ ', ' '));
lastWordFolderResource = createUriFromLocalPath(cwd, lastWordFolder.replaceAll('\\ ', ' '));
}
break;
}
@@ -549,7 +558,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
const cdPathEntries = cdPath.split(useWindowsStylePath ? ';' : ':');
for (const cdPathEntry of cdPathEntries) {
try {
const fileStat = await this._fileService.resolve(URI.file(cdPathEntry), { resolveSingleChildDescendants: true });
const fileStat = await this._fileService.resolve(createUriFromLocalPath(cwd, cdPathEntry), { resolveSingleChildDescendants: true });
if (fileStat?.children) {
for (const child of fileStat.children) {
if (!child.isDirectory) {
@@ -610,7 +619,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
let homeResource: URI | string | undefined;
const home = this._getHomeDir(useWindowsStylePath, capabilities);
if (home) {
homeResource = URI.joinPath(URI.file(home), lastWordFolder.slice(1).replaceAll('\\ ', ' '));
homeResource = createUriFromLocalPath(cwd, home);
}
if (!homeResource) {
// Use less strong wording here as it's not as strong of a concept on Windows
@@ -685,3 +694,15 @@ function getIsAbsolutePath(shellType: TerminalShellType | undefined, pathSeparat
}
return useWindowsStylePath ? /^[a-zA-Z]:[\\\/]/.test(lastWord) : lastWord.startsWith(pathSeparator);
}
/**
* Creates a URI from an absolute path, preserving the scheme and authority from the cwd.
* For local file:// URIs, uses URI.file() which handles Windows path normalization.
* For remote URIs (e.g., vscode-remote://wsl+Ubuntu), preserves the remote context.
*/
function createUriFromLocalPath(cwd: URI, absolutePath: string): URI {
if (cwd.scheme === 'file') {
return URI.file(absolutePath);
}
return cwd.with({ path: absolutePath });
}

View File

@@ -801,6 +801,87 @@ suite('TerminalCompletionService', () => {
});
});
}
if (!isWindows) {
suite('remote file completion (e.g. WSL)', () => {
const remoteAuthority = 'wsl+Ubuntu';
const remoteTestEnv: IProcessEnvironment = {
HOME: '/home/remoteuser',
USERPROFILE: '/home/remoteuser'
};
test('/absolute/path should preserve remote authority', async () => {
terminalCompletionService.processEnv = remoteTestEnv;
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser' }),
showDirectories: true,
pathSeparator: '/'
};
validResources = [
URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home' }),
URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser' }),
];
childResources = [
{ resource: URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser' }), isDirectory: true },
];
const result = await terminalCompletionService.resolveResources(resourceOptions, '/home/', 6, provider, capabilities);
// Check that results exist and have the correct scheme/authority
assert.ok(result && result.length > 0, 'Should return completions for remote absolute path');
// Verify completions contain paths resolved via the remote file service (not local file://)
const absoluteCompletion = result?.find(c => c.label === '/home/');
assert.ok(absoluteCompletion, 'Should have absolute path completion');
assert.ok(absoluteCompletion.detail?.includes('/home/'), 'Detail should show remote path');
});
test('~/ should preserve remote authority for tilde expansion', async () => {
terminalCompletionService.processEnv = remoteTestEnv;
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser/project' }),
showDirectories: true,
pathSeparator: '/'
};
validResources = [
URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser' }),
URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser/project' }),
];
childResources = [
{ resource: URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser/Documents' }), isDirectory: true },
{ resource: URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser/project' }), isDirectory: true },
];
const result = await terminalCompletionService.resolveResources(resourceOptions, '~/', 2, provider, capabilities);
// Check that results exist for remote tilde path
assert.ok(result && result.length > 0, 'Should return completions for remote tilde path');
// Verify the tilde path was resolved using the remote home directory
const documentsCompletion = result?.find(c => c.detail?.includes('Documents'));
assert.ok(documentsCompletion, 'Should find Documents folder from remote home');
});
test('./relative should preserve remote authority for relative paths', async () => {
terminalCompletionService.processEnv = remoteTestEnv;
const resourceOptions: TerminalCompletionResourceOptions = {
cwd: URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser/project' }),
showDirectories: true,
pathSeparator: '/'
};
validResources = [
URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser/project' }),
];
childResources = [
{ resource: URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser/project/src' }), isDirectory: true },
{ resource: URI.from({ scheme: 'vscode-remote', authority: remoteAuthority, path: '/home/remoteuser/project/docs' }), isDirectory: true },
];
const result = await terminalCompletionService.resolveResources(resourceOptions, './', 2, provider, capabilities);
// Check that results exist for remote relative path
assert.ok(result && result.length > 0, 'Should return completions for remote relative path');
// Verify completions are from the remote filesystem
const srcCompletion = result?.find(c => c.detail?.includes('/home/remoteuser/project/src'));
assert.ok(srcCompletion, 'Should find src folder completion with remote path in detail');
});
});
}
suite('completion label escaping', () => {
test('| should escape special characters in file/folder names for POSIX shells', async () => {
const resourceOptions: TerminalCompletionResourceOptions = {