@@ -7,7 +7,7 @@ import { PythonProjectApi } from '../../api';
77import { spawnProcess } from '../../common/childProcess.apis' ;
88import { ENVS_EXTENSION_ID , PYTHON_EXTENSION_ID } from '../../common/constants' ;
99import { getExtension } from '../../common/extension.apis' ;
10- import { traceError , traceLog , traceVerbose , traceWarn } from '../../common/logging' ;
10+ import { traceError , traceLog , traceWarn } from '../../common/logging' ;
1111import { untildify , untildifyArray } from '../../common/utils/pathUtils' ;
1212import { isWindows } from '../../common/utils/platformUtils' ;
1313import { createRunningWorkerPool , WorkerPool } from '../../common/utils/workerPool' ;
@@ -52,7 +52,7 @@ export interface NativeEnvManagerInfo {
5252
5353export type NativeInfo = NativeEnvInfo | NativeEnvManagerInfo ;
5454
55- export function isNativeEnvInfo ( info : NativeInfo ) : boolean {
55+ export function isNativeEnvInfo ( info : NativeInfo ) : info is NativeEnvInfo {
5656 return ! ( info as NativeEnvManagerInfo ) . tool ;
5757}
5858
@@ -149,7 +149,8 @@ class NativePythonFinderImpl implements NativePythonFinder {
149149 return options ;
150150 }
151151 if ( Array . isArray ( options ) ) {
152- return options . map ( ( item ) => item . fsPath ) . join ( ',' ) ;
152+ // Use null character as separator to avoid collisions with paths containing commas
153+ return options . map ( ( item ) => item . fsPath ) . join ( '\0' ) ;
153154 }
154155 return 'all' ;
155156 }
@@ -158,9 +159,9 @@ class NativePythonFinderImpl implements NativePythonFinder {
158159 const key = this . getKey ( options ) ;
159160 this . cache . delete ( key ) ;
160161 if ( ! options ) {
161- traceVerbose ( ' Finder - refreshing all environments') ;
162+ this . outputChannel . debug ( '[ Finder] Refreshing all environments') ;
162163 } else {
163- traceVerbose ( ' Finder - from cache environments' , key ) ;
164+ this . outputChannel . debug ( `[ Finder] Hard refresh for key: ${ key } ` ) ;
164165 }
165166 const result = await this . pool . addToQueue ( options ) ;
166167 // Validate result from worker pool
@@ -185,9 +186,9 @@ class NativePythonFinderImpl implements NativePythonFinder {
185186 }
186187
187188 if ( ! options ) {
188- traceVerbose ( ' Finder - from cache refreshing all environments ') ;
189+ this . outputChannel . debug ( '[ Finder] Returning cached environments for all') ;
189190 } else {
190- traceVerbose ( ' Finder - from cache environments' , key ) ;
191+ this . outputChannel . debug ( `[ Finder] Returning cached environments for key: ${ key } ` ) ;
191192 }
192193 return cacheResult ;
193194 }
@@ -199,7 +200,10 @@ class NativePythonFinderImpl implements NativePythonFinder {
199200 }
200201
201202 private getRefreshOptions ( options ?: NativePythonEnvironmentKind | Uri [ ] ) : RefreshOptions | undefined {
202- // settings on where else to search
203+ // Note: venvFolders is also fetched in getAllExtraSearchPaths() for configure().
204+ // This duplication is intentional: when searchPaths is provided to the native finder,
205+ // it may override (not supplement) the configured environmentDirectories.
206+ // We must include venvFolders here to ensure they're always searched during targeted refreshes.
203207 const venvFolders = getPythonSettingAndUntildify < string [ ] > ( 'venvFolders' ) ?? [ ] ;
204208 if ( options ) {
205209 if ( typeof options === 'string' ) {
@@ -234,7 +238,16 @@ class NativePythonFinderImpl implements NativePythonFinder {
234238 dispose : ( ) => {
235239 try {
236240 if ( proc . exitCode === null ) {
237- proc . kill ( ) ;
241+ // Attempt graceful shutdown by closing stdin before killing
242+ // This gives the process a chance to clean up
243+ this . outputChannel . debug ( '[pet] Shutting down Python Locator server' ) ;
244+ proc . stdin . end ( ) ;
245+ // Give process a moment to exit gracefully, then force kill
246+ setTimeout ( ( ) => {
247+ if ( proc . exitCode === null ) {
248+ proc . kill ( ) ;
249+ }
250+ } , 500 ) ;
238251 }
239252 } catch ( ex ) {
240253 this . outputChannel . error ( '[pet] Error disposing finder' , ex ) ;
@@ -350,8 +363,8 @@ class NativePythonFinderImpl implements NativePythonFinder {
350363 poetryExecutable : getPythonSettingAndUntildify < string > ( 'poetryPath' ) ,
351364 cacheDirectory : this . cacheDirectory ?. fsPath ,
352365 } ;
353- // No need to send a configuration request, is there are no changes.
354- if ( JSON . stringify ( options ) === JSON . stringify ( this . lastConfiguration || { } ) ) {
366+ // No need to send a configuration request if there are no changes.
367+ if ( this . lastConfiguration && this . configurationEquals ( options , this . lastConfiguration ) ) {
355368 this . outputChannel . debug ( '[pet] configure: No changes detected, skipping configuration update.' ) ;
356369 return ;
357370 }
@@ -363,6 +376,43 @@ class NativePythonFinderImpl implements NativePythonFinder {
363376 this . outputChannel . error ( '[pet] configure: Configuration error' , ex ) ;
364377 }
365378 }
379+
380+ /**
381+ * Compares two ConfigurationOptions objects for equality.
382+ * Uses property-by-property comparison to avoid issues with JSON.stringify
383+ * (property order, undefined values serialization).
384+ */
385+ private configurationEquals ( a : ConfigurationOptions , b : ConfigurationOptions ) : boolean {
386+ // Compare simple optional string properties
387+ if ( a . condaExecutable !== b . condaExecutable ) {
388+ return false ;
389+ }
390+ if ( a . poetryExecutable !== b . poetryExecutable ) {
391+ return false ;
392+ }
393+ if ( a . cacheDirectory !== b . cacheDirectory ) {
394+ return false ;
395+ }
396+
397+ // Compare array properties using sorted comparison to handle order differences
398+ const arraysEqual = ( arr1 : string [ ] , arr2 : string [ ] ) : boolean => {
399+ if ( arr1 . length !== arr2 . length ) {
400+ return false ;
401+ }
402+ const sorted1 = [ ...arr1 ] . sort ( ) ;
403+ const sorted2 = [ ...arr2 ] . sort ( ) ;
404+ return sorted1 . every ( ( val , idx ) => val === sorted2 [ idx ] ) ;
405+ } ;
406+
407+ if ( ! arraysEqual ( a . workspaceDirectories , b . workspaceDirectories ) ) {
408+ return false ;
409+ }
410+ if ( ! arraysEqual ( a . environmentDirectories , b . environmentDirectories ) ) {
411+ return false ;
412+ }
413+
414+ return true ;
415+ }
366416}
367417
368418type ConfigurationOptions = {
0 commit comments