Skip to content
Draft
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
5 changes: 4 additions & 1 deletion Extension/src/Debugger/configurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,10 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
const folderPath: string | undefined = folder?.uri.fsPath || vscode.workspace.workspaceFolders?.[0].uri.fsPath;
const vars: ExpansionVars = config.variables ? config.variables : {};
vars.workspaceFolder = folderPath || '{workspaceFolder}';
vars.workspaceFolderBasename = folderPath ? path.basename(folderPath) : '{workspaceFolderBasename}';
// path.basename returns "" for filesystem roots like "/" or "C:\\", so
// fall back to the folder path itself to keep ${workspaceFolderBasename}
// non-empty for root workspaces.
vars.workspaceFolderBasename = folderPath ? path.basename(folderPath) || folderPath : '{workspaceFolderBasename}';
const expansionOptions: ExpansionOptions = { vars, recursive: true };
return expandAllStrings(config, expansionOptions);
}
Expand Down
10 changes: 9 additions & 1 deletion Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1007,8 +1007,16 @@ export class DefaultClient implements Client {
}

public get AdditionalEnvironment(): Record<string, string | string[]> {
// workspaceFolderBasename mirrors VS Code's ${workspaceFolderBasename}, which
// is defined as path.basename(folder.uri.fsPath). Falling back to this.Name
// (the WorkspaceFolder display name, which may be user-customized in
// multi-root) keeps a sensible value when fsPath is unavailable; falling
// back to the fsPath itself ensures we don't return "" for root workspaces
// (path.basename("/") and path.basename("C:\\") return "").
const rootFsPath: string = this.RootPath;
const baseName: string = rootFsPath ? path.basename(rootFsPath) || rootFsPath : this.Name;
return {
workspaceFolderBasename: this.Name,
workspaceFolderBasename: baseName,
workspaceStorage: this.workspaceStoragePath,
execPath: process.execPath,
pathSeparator: (os.platform() === 'win32') ? "\\" : "/",
Expand Down
10 changes: 5 additions & 5 deletions Extension/src/LanguageServer/configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ export class CppProperties {
Object.assign(result, this.configurationJson.env);
}

result["workspaceFolderBasename"] = this.rootUri ? path.basename(this.rootUri.fsPath) : "";
result["workspaceFolderBasename"] = this.rootUri ? path.basename(this.rootUri.fsPath) || this.rootUri.fsPath : "";
result["execPath"] = process.execPath;
result["pathSeparator"] = (os.platform() === 'win32') ? "\\" : "/";
result["/"] = (os.platform() === 'win32') ? "\\" : "/";
Expand Down Expand Up @@ -1680,7 +1680,7 @@ export class CppProperties {
compilerPathAndArgs.compilerPath = pathLocation;
} else if (rootUri) {
// Test if it was a relative path.
const absolutePath: string = rootUri.fsPath + path.sep + resolvedCompilerPath;
const absolutePath: string = path.join(rootUri.fsPath, resolvedCompilerPath);
if (!fs.existsSync(absolutePath)) {
if (existsWithExeAdded(absolutePath)) {
resolvedCompilerPath = absolutePath + ".exe";
Expand Down Expand Up @@ -1815,7 +1815,7 @@ export class CppProperties {
pathExists = false;
} else {
// Check for relative path if resolved path does not exists
const relativePath: string = this.rootUri.fsPath + path.sep + resolvedPath;
const relativePath: string = path.join(this.rootUri.fsPath, resolvedPath);
if (!fs.existsSync(relativePath)) {
pathExists = false;
} else {
Expand Down Expand Up @@ -2085,7 +2085,7 @@ export class CppProperties {
dotConfigPath = dotConfigPath !== '' ? dotConfigPath : undefined;

if (dotConfigPath && this.rootUri) {
const checkPathExists: any = util.checkPathExistsSync(dotConfigPath, this.rootUri.fsPath + path.sep, isWindows, true);
const checkPathExists: any = util.checkPathExistsSync(dotConfigPath, this.rootUri.fsPath, isWindows, true);
dotConfigPathExists = checkPathExists.pathExists;
dotConfigPath = checkPathExists.path;
}
Expand Down Expand Up @@ -2169,7 +2169,7 @@ export class CppProperties {
expandedPaths[index] = this.resolvePath(expandedPath);
}

const checkPathExists: any = util.checkPathExistsSync(expandedPaths[index], this.rootUri.fsPath + path.sep, isWindows, false);
const checkPathExists: any = util.checkPathExistsSync(expandedPaths[index], this.rootUri.fsPath, isWindows, false);
if (!checkPathExists.pathExists) {
// If there are multiple paths, store any non-existing paths to squiggle later on.
incorrectExpandedPaths.push(expandedPaths[index]);
Expand Down
15 changes: 11 additions & 4 deletions Extension/src/LanguageServer/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,10 +484,17 @@ async function onSwitchHeaderSource(): Promise<void> {
// then replace the RootRealPath with the RootPath (the symlink path).
let targetFileNameReplaced: boolean = false;
clients.forEach(client => {
if (!targetFileNameReplaced && client.RootRealPath && client.RootPath !== client.RootRealPath
&& targetFileName.startsWith(client.RootRealPath)) {
targetFileName = client.RootPath + targetFileName.substring(client.RootRealPath.length);
targetFileNameReplaced = true;
if (!targetFileNameReplaced && client.RootRealPath && client.RootPath !== client.RootRealPath) {
// Use path.relative + path.join so we correctly handle the case where
// RootRealPath is a filesystem root (e.g. "/" or "C:\"). A naive
// substring(RootRealPath.length) drops the trailing separator that is
// part of a root path and would produce a malformed path. path.relative
// also avoids false prefix matches such as "/foo" vs "/foobar".
const relativeToReal: string = path.relative(client.RootRealPath, targetFileName);
if (relativeToReal && !relativeToReal.startsWith('..' + path.sep) && relativeToReal !== '..' && !path.isAbsolute(relativeToReal)) {
targetFileName = path.join(client.RootPath, relativeToReal);
targetFileNameReplaced = true;
}
}
});
const document: vscode.TextDocument = await vscode.workspace.openTextDocument(targetFileName);
Expand Down
34 changes: 20 additions & 14 deletions Extension/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,30 +600,36 @@ export function checkDirectoryExistsSync(dirPath: string): boolean {
}
}

/** Test whether a relative path exists */
export function checkPathExistsSync(path: string, relativePath: string, _isWindows: boolean, isCompilerPath: boolean): { pathExists: boolean; path: string } {
/** Test whether a relative path exists.
*
* `inputPath` is checked first as-is. If it doesn't exist and `baseDir` is
* provided, `inputPath` is also tried joined with `baseDir`. Using path.join
* (rather than naive string concatenation with `path.sep`) ensures correct
* behavior when `baseDir` is a filesystem root such as "/" or "C:\\".
*/
export function checkPathExistsSync(inputPath: string, baseDir: string, _isWindows: boolean, isCompilerPath: boolean): { pathExists: boolean; path: string } {
let pathExists: boolean = true;
const existsWithExeAdded: (path: string) => boolean = (path: string) => isCompilerPath && _isWindows && fs.existsSync(path + ".exe");
if (!fs.existsSync(path)) {
if (existsWithExeAdded(path)) {
path += ".exe";
} else if (!relativePath) {
const existsWithExeAdded: (p: string) => boolean = (p: string) => isCompilerPath && _isWindows && fs.existsSync(p + ".exe");
if (!fs.existsSync(inputPath)) {
if (existsWithExeAdded(inputPath)) {
inputPath += ".exe";
} else if (!baseDir) {
pathExists = false;
} else {
// Check again for a relative path.
relativePath = relativePath + path;
if (!fs.existsSync(relativePath)) {
if (existsWithExeAdded(path)) {
path += ".exe";
// Check again as a path relative to baseDir.
const joinedPath: string = path.join(baseDir, inputPath);
if (!fs.existsSync(joinedPath)) {
if (existsWithExeAdded(inputPath)) {
inputPath += ".exe";
} else {
pathExists = false;
}
} else {
path = relativePath;
inputPath = joinedPath;
}
}
}
return { pathExists, path };
return { pathExists, path: inputPath };
}

/** Read the files in a directory */
Expand Down
10 changes: 7 additions & 3 deletions Extension/src/expand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,13 @@ async function expandStringImpl(input: string, options: ExpansionOptions): Promi
const full: string = match[0];
const key: string = match[1];
if (key !== 'dollar') {
// Replace dollar sign at the very end of the expanding process
const repl: string = options.vars[key];
if (!repl) {
// Replace dollar sign at the very end of the expanding process.
// Distinguish "variable not defined" (undefined) from "variable defined
// as empty string". A defined-but-empty value (e.g. workspaceFolderBasename
// when the workspace root is "/" or "C:\\") should substitute as "" rather
// than be reported as an invalid reference.
const repl: string | undefined = options.vars[key];
if (repl === undefined) {
void getOutputChannelLogger().showWarningMessage(localize('invalid.var.reference', 'Invalid variable reference {0} in string: {1}.', full, input));
} else {
subs.set(full, repl);
Expand Down