Skip to content

Commit e1e27b1

Browse files
authored
Fix empty path handling in pythonProjects for workspace root projects (#1220)
For #1219
1 parent a210e47 commit e1e27b1

File tree

2 files changed

+508
-24
lines changed

2 files changed

+508
-24
lines changed

src/features/settings/settingHelpers.ts

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
import * as path from 'path';
2-
import {
3-
ConfigurationScope,
4-
ConfigurationTarget,
5-
Uri,
6-
workspace,
7-
WorkspaceConfiguration,
8-
WorkspaceFolder,
9-
} from 'vscode';
2+
import { ConfigurationScope, ConfigurationTarget, Uri, WorkspaceConfiguration, WorkspaceFolder } from 'vscode';
103
import { PythonProject } from '../../api';
114
import { DEFAULT_ENV_MANAGER_ID, DEFAULT_PACKAGE_MANAGER_ID } from '../../common/constants';
125
import { traceError, traceInfo, traceWarn } from '../../common/logging';
@@ -22,7 +15,7 @@ function getSettings(
2215

2316
if (overrides.length > 0 && scope instanceof Uri) {
2417
const pw = wm.get(scope);
25-
const w = workspace.getWorkspaceFolder(scope);
18+
const w = workspaceApis.getWorkspaceFolder(scope);
2619
if (pw && w) {
2720
const pwPath = path.normalize(pw.uri.fsPath);
2821
return overrides.find((s) => path.resolve(w.uri.fsPath, s.path) === pwPath);
@@ -106,7 +99,7 @@ export async function setAllManagerSettings(edits: EditAllManagerSettings[]): Pr
10699
.filter((e) => !!e.project)
107100
.map((e) => e as EditAllManagerSettingsInternal)
108101
.forEach((e) => {
109-
const w = workspace.getWorkspaceFolder(e.project.uri);
102+
const w = workspaceApis.getWorkspaceFolder(e.project.uri);
110103
if (w) {
111104
workspaces.set(w, [
112105
...(workspaces.get(w) || []),
@@ -136,13 +129,36 @@ export async function setAllManagerSettings(edits: EditAllManagerSettings[]): Pr
136129

137130
es.forEach((e) => {
138131
const pwPath = path.normalize(e.project.uri.fsPath);
132+
const isRoot = path.normalize(w.uri.fsPath) === pwPath;
139133
const index = overrides.findIndex((s) => path.resolve(w.uri.fsPath, s.path) === pwPath);
140-
if (index >= 0) {
134+
135+
// For workspace root in single-folder workspaces (no workspaceFile),
136+
// use default settings instead of pythonProjects entries
137+
if (isRoot && !workspaceFile) {
138+
// Remove existing entry if present (migration from buggy empty path)
139+
if (index >= 0) {
140+
overrides.splice(index, 1);
141+
}
142+
if (config.get('defaultEnvManager') !== e.envManager) {
143+
promises.push(config.update('defaultEnvManager', e.envManager, ConfigurationTarget.Workspace));
144+
}
145+
if (config.get('defaultPackageManager') !== e.packageManager) {
146+
promises.push(
147+
config.update('defaultPackageManager', e.packageManager, ConfigurationTarget.Workspace),
148+
);
149+
}
150+
} else if (index >= 0) {
141151
overrides[index].envManager = e.envManager;
142152
overrides[index].packageManager = e.packageManager;
153+
// Fix empty path to "." for workspace root (migration from buggy entries)
154+
if (overrides[index].path === '') {
155+
overrides[index].path = '.';
156+
}
143157
} else if (workspaceFile) {
158+
// Use "." for workspace root instead of empty string
159+
const relativePath = path.relative(w.uri.fsPath, pwPath).replace(/\\/g, '/');
144160
overrides.push({
145-
path: path.relative(w.uri.fsPath, pwPath).replace(/\\/g, '/'),
161+
path: relativePath || '.',
146162
envManager: e.envManager,
147163
packageManager: e.packageManager,
148164
});
@@ -160,13 +176,19 @@ export async function setAllManagerSettings(edits: EditAllManagerSettings[]): Pr
160176

161177
// Only write pythonProjects if:
162178
// 1. There was already an explicit setting OR
163-
// 2. adding new project entries
164-
const shouldWriteProjects = existingProjectsSetting !== undefined || overrides.length > originalOverridesLength;
179+
// 2. adding new project entries OR
180+
// 3. removing entries (migration from buggy empty path)
181+
const shouldWriteProjects =
182+
existingProjectsSetting !== undefined ||
183+
overrides.length > originalOverridesLength ||
184+
overrides.length < originalOverridesLength;
165185
if (shouldWriteProjects) {
186+
// If all entries are removed, set to undefined to clean up settings
187+
const valueToWrite = overrides.length > 0 ? overrides : undefined;
166188
promises.push(
167189
config.update(
168190
'pythonProjects',
169-
overrides,
191+
valueToWrite,
170192
workspaceFile ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace,
171193
),
172194
);
@@ -204,7 +226,7 @@ export async function setEnvironmentManager(edits: EditEnvManagerSettings[]): Pr
204226
.filter((e) => !!e.project)
205227
.map((e) => e as EditEnvManagerSettingsInternal)
206228
.forEach((e) => {
207-
const w = workspace.getWorkspaceFolder(e.project.uri);
229+
const w = workspaceApis.getWorkspaceFolder(e.project.uri);
208230
if (w) {
209231
workspaces.set(w, [...(workspaces.get(w) || []), { project: e.project, envManager: e.envManager }]);
210232
} else {
@@ -275,7 +297,7 @@ export async function setPackageManager(edits: EditPackageManagerSettings[]): Pr
275297
.filter((e) => !!e.project)
276298
.map((e) => e as EditPackageManagerSettingsInternal)
277299
.forEach((e) => {
278-
const w = workspace.getWorkspaceFolder(e.project.uri);
300+
const w = workspaceApis.getWorkspaceFolder(e.project.uri);
279301
if (w) {
280302
workspaces.set(w, [
281303
...(workspaces.get(w) || []),
@@ -348,7 +370,7 @@ export async function addPythonProjectSetting(edits: EditProjectSettings[]): Pro
348370
const pkgManager = globalConfig.get<string>('defaultPackageManager', DEFAULT_PACKAGE_MANAGER_ID);
349371

350372
edits.forEach((e) => {
351-
const w = workspace.getWorkspaceFolder(e.project.uri);
373+
const w = workspaceApis.getWorkspaceFolder(e.project.uri);
352374
if (w) {
353375
workspaces.set(w, [...(workspaces.get(w) || []), e]);
354376
} else {
@@ -366,10 +388,36 @@ export async function addPythonProjectSetting(edits: EditProjectSettings[]): Pro
366388
workspaces.forEach((es, w) => {
367389
const config = workspaceApis.getConfiguration('python-envs', w.uri);
368390
const overrides = config.get<PythonProjectSettings[]>('pythonProjects', []);
391+
let overridesModified = false;
369392
es.forEach((e) => {
370-
if (isMultiroot) {
371-
}
372393
const pwPath = path.normalize(e.project.uri.fsPath);
394+
const isRoot = path.normalize(w.uri.fsPath) === pwPath;
395+
396+
// For workspace root projects in single-folder workspaces, use default settings
397+
// instead of adding to pythonProjects with empty path
398+
if (isRoot && !isMultiroot) {
399+
// Remove existing entry if present (migration from buggy empty path)
400+
const existingIndex = overrides.findIndex((s) => path.resolve(w.uri.fsPath, s.path) === pwPath);
401+
if (existingIndex >= 0) {
402+
overrides.splice(existingIndex, 1);
403+
overridesModified = true;
404+
}
405+
406+
const effectiveEnvManager = e.envManager ?? envManager;
407+
const effectivePkgManager = e.packageManager ?? pkgManager;
408+
if (config.get('defaultEnvManager') !== effectiveEnvManager) {
409+
promises.push(
410+
config.update('defaultEnvManager', effectiveEnvManager, ConfigurationTarget.Workspace),
411+
);
412+
}
413+
if (config.get('defaultPackageManager') !== effectivePkgManager) {
414+
promises.push(
415+
config.update('defaultPackageManager', effectivePkgManager, ConfigurationTarget.Workspace),
416+
);
417+
}
418+
return;
419+
}
420+
373421
const index = overrides.findIndex((s) => {
374422
if (s.workspace) {
375423
// If the workspace is set, check workspace and path in existing overrides
@@ -381,16 +429,29 @@ export async function addPythonProjectSetting(edits: EditProjectSettings[]): Pro
381429
// Preserve existing manager settings if not explicitly provided
382430
overrides[index].envManager = e.envManager ?? overrides[index].envManager;
383431
overrides[index].packageManager = e.packageManager ?? overrides[index].packageManager;
432+
// Fix empty path to "." for workspace root in multi-root (migration from buggy entries)
433+
if (overrides[index].path === '') {
434+
overrides[index].path = '.';
435+
}
436+
overridesModified = true;
384437
} else {
438+
// Use "." for workspace root in multi-root workspaces instead of empty string
439+
const relativePath = path.relative(w.uri.fsPath, pwPath).replace(/\\/g, '/');
385440
overrides.push({
386-
path: path.relative(w.uri.fsPath, pwPath).replace(/\\/g, '/'),
441+
path: relativePath || '.',
387442
envManager,
388443
packageManager: pkgManager,
389444
workspace: isMultiroot ? w.name : undefined,
390445
});
446+
overridesModified = true;
391447
}
392448
});
393-
promises.push(config.update('pythonProjects', overrides, ConfigurationTarget.Workspace));
449+
// Write pythonProjects if modified (entries added, removed, or updated)
450+
if (overridesModified) {
451+
// If all entries are removed, set to undefined to clean up settings
452+
const valueToWrite = overrides.length > 0 ? overrides : undefined;
453+
promises.push(config.update('pythonProjects', valueToWrite, ConfigurationTarget.Workspace));
454+
}
394455
});
395456
await Promise.all(promises);
396457
}
@@ -399,7 +460,7 @@ export async function removePythonProjectSetting(edits: EditProjectSettings[]):
399460
const noWorkspace: EditProjectSettings[] = [];
400461
const workspaces = new Map<WorkspaceFolder, EditProjectSettings[]>();
401462
edits.forEach((e) => {
402-
const w = workspace.getWorkspaceFolder(e.project.uri);
463+
const w = workspaceApis.getWorkspaceFolder(e.project.uri);
403464
if (w) {
404465
workspaces.set(w, [...(workspaces.get(w) || []), e]);
405466
} else {

0 commit comments

Comments
 (0)