Skip to content

Commit 5aed387

Browse files
committed
simplified solution on gear
1 parent 74ec72f commit 5aed387

File tree

7 files changed

+158
-92
lines changed

7 files changed

+158
-92
lines changed

package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
"python-envs.workspaceSearchPaths": {
125125
"type": "array",
126126
"description": "%python-envs.workspaceSearchPaths.description%",
127-
"default": [],
127+
"default": ["./**/.venv"],
128128
"scope": "resource",
129129
"items": {
130130
"type": "string"
@@ -557,11 +557,6 @@
557557
"group": "navigation",
558558
"when": "view == env-managers"
559559
},
560-
{
561-
"command": "python-envs.managerSearch",
562-
"group": "navigation",
563-
"when": "view == env-managers"
564-
},
565560
{
566561
"command": "python-envs.searchSettings",
567562
"group": "navigation",

package.nls.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
"python-envs.terminal.autoActivationType.shellStartup": "Activation using [shell integration](https://code.visualstudio.com/docs/terminal/shell-integration) or by modifying the terminal shell startup script. Enable `terminal.integrated.shellIntegration.enabled` or we may need to modify your shell startup scripts for the ideal experience.",
1212
"python-envs.terminal.autoActivationType.off": "No automatic activation of environments.",
1313
"python-envs.terminal.useEnvFile.description": "Controls whether environment variables from .env files and python.envFile setting are injected into terminals.",
14-
"python-envs.globalSearchPaths.description": "Global search paths for Python environments. Absolute directory paths that are searched at the user level.\n\n**Legacy Setting Support:** This setting is merged with the legacy `python.venvPath` and `python.venvFolders` settings. All paths from these three settings are combined into a single list of search paths. The legacy settings `python.venvPath` and `python.venvFolders` will be deprecated in the future, after which this setting will fully replace them. Please consider migrating your paths to this setting.",
15-
"python-envs.workspaceSearchPaths.description": "Workspace search paths for Python environments. Can be absolute paths or relative directory paths searched within the workspace.",
14+
"python-envs.globalSearchPaths.description": "Absolute paths to search for Python environments across all workspaces. Use for shared environment folders like `~/envs`.",
15+
"python-envs.workspaceSearchPaths.description": "Paths to search for environments in this workspace. Defaults to `./**/.venv` to find all `.venv` folders nested within your workspace.",
1616
"python-envs.terminal.revertStartupScriptChanges.title": "Revert Shell Startup Script Changes",
1717
"python-envs.reportIssue.title": "Report Issue",
1818
"python-envs.setEnvManager.title": "Set Environment Manager",

src/common/localize.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ export namespace EnvManagerSearchStrings {
206206
export const fullWorkspaceSearchDescription = l10n.t('Search the entire workspace for environments');
207207
export const saveSearchPrompt = l10n.t('Save this search setting for future discovery?');
208208
export const dontShowAgain = l10n.t("Don't show again");
209+
export const slowLoadingMessage = l10n.t(
210+
'Environment discovery is taking longer than expected. This may be due to workspace search paths.',
211+
);
212+
export const openSettings = l10n.t('Open Settings');
213+
export const removeWorkspaceSearch = l10n.t('Remove Workspace Search');
214+
export const dontShowForWorkspace = l10n.t("Don't Show for This Workspace");
209215
}
210216

211217
export namespace ActivationStrings {

src/extension.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ import { TerminalEnvVarInjector } from './features/terminal/terminalEnvVarInject
6565
import { TerminalManager, TerminalManagerImpl } from './features/terminal/terminalManager';
6666
import { registerTerminalPackageWatcher } from './features/terminal/terminalPackageWatcher';
6767
import { getEnvironmentForTerminal } from './features/terminal/utils';
68-
import { handleEnvManagerSearchAction, openSearchSettings } from './features/views/envManagerSearch';
68+
import {
69+
handleEnvManagerSearchAction,
70+
openSearchSettings,
71+
startSlowLoadingMonitor,
72+
} from './features/views/envManagerSearch';
6973
import { EnvManagerView } from './features/views/envManagersView';
7074
import { ProjectView } from './features/views/projectView';
7175
import { PythonStatusBarImpl } from './features/views/pythonStatusBar';
@@ -453,22 +457,31 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
453457
* Below are all the contributed features using the APIs.
454458
*/
455459
setImmediate(async () => {
460+
// Start monitoring for slow loading - will show notification if discovery takes too long
461+
const cancelSlowLoadingMonitor = startSlowLoadingMonitor();
462+
456463
// This is the finder that is used by all the built in environment managers
457464
const nativeFinder: NativePythonFinder = await createNativePythonFinder(outputChannel, api, context);
458465
nativeFinderDeferred.resolve(nativeFinder);
459466
context.subscriptions.push(nativeFinder);
460467
const sysMgr = new SysPythonManager(nativeFinder, api, outputChannel);
461468
sysPythonManager.resolve(sysMgr);
462-
await Promise.all([
463-
registerSystemPythonFeatures(nativeFinder, context.subscriptions, outputChannel, sysMgr),
464-
registerCondaFeatures(nativeFinder, context.subscriptions, outputChannel, projectManager),
465-
registerPyenvFeatures(nativeFinder, context.subscriptions, projectManager),
466-
registerPipenvFeatures(nativeFinder, context.subscriptions, projectManager),
467-
registerPoetryFeatures(nativeFinder, context.subscriptions, outputChannel, projectManager),
468-
shellStartupVarsMgr.initialize(),
469-
]);
470-
471-
await applyInitialEnvironmentSelection(envManagers, projectManager, nativeFinder, api);
469+
470+
try {
471+
await Promise.all([
472+
registerSystemPythonFeatures(nativeFinder, context.subscriptions, outputChannel, sysMgr),
473+
registerCondaFeatures(nativeFinder, context.subscriptions, outputChannel, projectManager),
474+
registerPyenvFeatures(nativeFinder, context.subscriptions, projectManager),
475+
registerPipenvFeatures(nativeFinder, context.subscriptions, projectManager),
476+
registerPoetryFeatures(nativeFinder, context.subscriptions, outputChannel, projectManager),
477+
shellStartupVarsMgr.initialize(),
478+
]);
479+
480+
await applyInitialEnvironmentSelection(envManagers, projectManager, nativeFinder, api);
481+
} finally {
482+
// Cancel the slow loading monitor once initialization completes (or fails)
483+
cancelSlowLoadingMonitor();
484+
}
472485

473486
// Register manager-agnostic terminal watcher for package-modifying commands
474487
registerTerminalPackageWatcher(api, terminalActivation, outputChannel, context.subscriptions);

src/features/views/envManagerSearch.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { EnvironmentManagers } from '../../internal.api';
99
import { NativePythonFinder } from '../../managers/common/nativePythonFinder';
1010

1111
const SUPPRESS_SAVE_PROMPT_KEY = 'python-envs.search.fullWorkspace.suppressSavePrompt';
12+
const SUPPRESS_SLOW_LOADING_KEY = 'python-envs.search.slowLoading.suppressPrompt';
13+
const SLOW_LOADING_THRESHOLD_MS = 10_000; // 10 seconds
1214

1315
/**
1416
* Handles the Environment Managers view search action.
@@ -22,10 +24,13 @@ export async function handleEnvManagerSearchAction(
2224
}
2325

2426
/**
25-
* Opens environment search settings.
27+
* Opens environment search settings at workspace level.
2628
*/
2729
export async function openSearchSettings(): Promise<void> {
28-
await commands.executeCommand('workbench.action.openSettings', '@ext:ms-python.vscode-python-envs "search path"');
30+
await commands.executeCommand(
31+
'workbench.action.openWorkspaceSettings',
32+
'@ext:ms-python.vscode-python-envs "search path"',
33+
);
2934
}
3035

3136
/**
@@ -105,3 +110,56 @@ export async function appendWorkspaceSearchPaths(searchPaths: string[]): Promise
105110
const nextPaths = [...currentPaths, ...filteredSearchPaths];
106111
await config.update('workspaceSearchPaths', nextPaths, ConfigurationTarget.Workspace);
107112
}
113+
114+
/**
115+
* Clears the workspace-level `workspaceSearchPaths` setting.
116+
*/
117+
export async function clearWorkspaceSearchPaths(): Promise<void> {
118+
const config = getConfiguration('python-envs');
119+
await config.update('workspaceSearchPaths', [], ConfigurationTarget.Workspace);
120+
traceLog('Cleared workspace search paths');
121+
}
122+
123+
/**
124+
* Monitors environment refresh and shows a notification if it takes too long.
125+
* Returns a cleanup function to cancel the timeout if refresh completes early.
126+
*/
127+
export function startSlowLoadingMonitor(): () => void {
128+
let timeoutId: NodeJS.Timeout | undefined;
129+
let notificationShown = false;
130+
131+
const showNotification = async (): Promise<void> => {
132+
const state = await getWorkspacePersistentState();
133+
const suppressNotification = await state.get<boolean>(SUPPRESS_SLOW_LOADING_KEY, false);
134+
if (suppressNotification || notificationShown) {
135+
return;
136+
}
137+
notificationShown = true;
138+
139+
const response = await window.showWarningMessage(
140+
EnvManagerSearchStrings.slowLoadingMessage,
141+
EnvManagerSearchStrings.openSettings,
142+
EnvManagerSearchStrings.removeWorkspaceSearch,
143+
EnvManagerSearchStrings.dontShowForWorkspace,
144+
);
145+
146+
if (response === EnvManagerSearchStrings.openSettings) {
147+
await openSearchSettings();
148+
} else if (response === EnvManagerSearchStrings.removeWorkspaceSearch) {
149+
await clearWorkspaceSearchPaths();
150+
} else if (response === EnvManagerSearchStrings.dontShowForWorkspace) {
151+
await state.set(SUPPRESS_SLOW_LOADING_KEY, true);
152+
}
153+
};
154+
155+
timeoutId = setTimeout(() => {
156+
showNotification();
157+
}, SLOW_LOADING_THRESHOLD_MS);
158+
159+
return () => {
160+
if (timeoutId) {
161+
clearTimeout(timeoutId);
162+
timeoutId = undefined;
163+
}
164+
};
165+
}

src/managers/common/nativePythonFinder.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import { PythonProjectApi } from '../../api';
88
import { spawnProcess } from '../../common/childProcess.apis';
99
import { ENVS_EXTENSION_ID, PYTHON_EXTENSION_ID } from '../../common/constants';
1010
import { getExtension } from '../../common/extension.apis';
11-
import { traceError, traceLog } from '../../common/logging';
11+
import { traceError, traceVerbose, traceWarn } from '../../common/logging';
1212
import { untildify, untildifyArray } from '../../common/utils/pathUtils';
1313
import { isWindows } from '../../common/utils/platformUtils';
1414
import { createRunningWorkerPool, WorkerPool } from '../../common/utils/workerPool';
15-
import { getConfiguration } from '../../common/workspace.apis';
15+
import { getConfiguration, getWorkspaceFolders } from '../../common/workspace.apis';
1616
import { noop } from './utils';
1717

1818
// Timeout constants for JSON-RPC requests (in milliseconds)
@@ -697,23 +697,34 @@ export async function getAllExtraSearchPaths(): Promise<string[]> {
697697
// Get workspaceSearchPaths
698698
const workspaceSearchPaths = getWorkspaceSearchPaths();
699699

700-
// Keep workspaceSearchPaths entries as provided (no workspace prefixing).
700+
// Resolve relative paths against workspace folders
701701
for (const searchPath of workspaceSearchPaths) {
702702
if (!searchPath || searchPath.trim() === '') {
703703
continue;
704704
}
705705

706-
searchDirectories.push(searchPath.trim());
706+
const trimmedPath = searchPath.trim();
707+
708+
if (path.isAbsolute(trimmedPath)) {
709+
// Absolute path - use as is
710+
searchDirectories.push(trimmedPath);
711+
} else {
712+
// Relative path - resolve against all workspace folders
713+
const workspaceFolders = getWorkspaceFolders();
714+
if (workspaceFolders) {
715+
for (const workspaceFolder of workspaceFolders) {
716+
const resolvedPath = path.resolve(workspaceFolder.uri.fsPath, trimmedPath);
717+
searchDirectories.push(resolvedPath);
718+
}
719+
} else {
720+
traceWarn('No workspace folders found for relative search path:', trimmedPath);
721+
}
722+
}
707723
}
708724

709725
// Remove duplicates and return
710726
const uniquePaths = Array.from(new Set(searchDirectories));
711-
traceLog(
712-
'getAllExtraSearchPaths completed. Total unique search directories:',
713-
uniquePaths.length,
714-
'Paths:',
715-
uniquePaths,
716-
);
727+
traceVerbose('Environment search directories:', uniquePaths.length, 'paths');
717728
return uniquePaths;
718729
}
719730

@@ -748,7 +759,7 @@ function getWorkspaceSearchPaths(): string[] {
748759
);
749760
}
750761

751-
// For workspace settings, prefer workspaceFolder > workspace
762+
// For workspace settings, prefer workspaceFolder > workspace > default
752763
if (inspection?.workspaceFolderValue) {
753764
return inspection.workspaceFolderValue;
754765
}
@@ -757,8 +768,8 @@ function getWorkspaceSearchPaths(): string[] {
757768
return inspection.workspaceValue;
758769
}
759770

760-
// Default empty array (don't use global value for workspace settings)
761-
return [];
771+
// Use the default value from package.json
772+
return inspection?.defaultValue ?? [];
762773
} catch (error) {
763774
traceError('Error getting workspaceSearchPaths:', error);
764775
return [];

0 commit comments

Comments
 (0)