11import * 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' ;
103import { PythonProject } from '../../api' ;
114import { DEFAULT_ENV_MANAGER_ID , DEFAULT_PACKAGE_MANAGER_ID } from '../../common/constants' ;
125import { 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