Skip to content

Commit cf6fb89

Browse files
Merge branch 'microsoft:main' into kustoQuery
2 parents e79df94 + d787b7b commit cf6fb89

31 files changed

Lines changed: 1257 additions & 251 deletions

docs/startup-flow.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
2+
# Startup Flow for Python Environments Extension
3+
4+
5+
user opens VS Code
6+
python environments extension begins activation
7+
8+
SYNC (`activate` in extension.ts):
9+
1. create core objects: ProjectManager, EnvironmentManagers, ManagerReady
10+
2. `setPythonApi()` — API object created, deferred resolved (API is now available to consumers)
11+
3. create views (EnvManagerView, ProjectView), status bar, terminal manager
12+
4. register all commands
13+
5. activate() returns — extension is "active" from VS Code's perspective
14+
15+
📊 TELEMETRY: EXTENSION.ACTIVATION_DURATION { duration }
16+
17+
ASYNC (setImmediate callback, still in extension.ts):
18+
1. spawn PET process (`createNativePythonFinder`)
19+
1. sets up a JSON-RPC connection to it over stdin/stdout
20+
2. register all built-in managers in parallel (Promise.all):
21+
- for each manager (system, conda, pyenv, pipenv, poetry):
22+
1. check if tool exists (e.g. `getConda(nativeFinder)` asks PET for the conda binary)
23+
2. if tool not found → log, return early (manager not registered)
24+
3. if tool found → create manager, call `api.registerEnvironmentManager(manager)`
25+
- this adds it to the `EnvironmentManagers` map
26+
- fires `onDidChangeEnvironmentManager``ManagerReady` deferred resolves for this manager
27+
3. all registrations complete (Promise.all resolves)
28+
29+
--- gate point: `applyInitialEnvironmentSelection` ---
30+
📊 TELEMETRY: ENV_SELECTION.STARTED { duration (activation→here), registeredManagerCount, registeredManagerIds, workspaceFolderCount }
31+
32+
1. for each workspace folder + global scope (no workspace case), run `resolvePriorityChainCore` to find manager:
33+
- P1: pythonProjects[] setting → specific manager for this project
34+
- P2: user-configured defaultEnvManager setting
35+
- P3: user-configured python.defaultInterpreterPath → nativeFinder.resolve(path)
36+
- P4: auto-discovery → try venv manager (local .venv), fall back to system python
37+
- for workspace scope: ask venv manager if there's a local env (.venv/venv in the folder)
38+
- if found → use venv manager with that env
39+
- if not found → fall back to system python manager
40+
- for global scope: use system python manager directly
41+
42+
2. get the environment from the winning priority level:
43+
44+
--- fork point: `result.environment ?? await result.manager.get(folder.uri)` ---
45+
left side truthy = envPreResolved | left side undefined = managerDiscovery
46+
47+
envPreResolved — P3 won (interpreter → manager):
48+
`resolvePriorityChainCore` calls `tryResolveInterpreterPath()`:
49+
1. `nativeFinder.resolve(path)` — single PET call, resolves just this one binary
50+
2. find which manager owns the resolved env (by managerId)
51+
3. return { manager, environment } — BOTH are known
52+
→ result.environment is set → the `??` short-circuits
53+
→ no `manager.get()` called, no `initialize()`, no full discovery
54+
55+
managerDiscovery — P1, P2, or P4 won (manager → interpreter):
56+
`resolvePriorityChainCore` returns { manager, environment: undefined }
57+
→ result.environment is undefined → falls through to `await result.manager.get(scope)`
58+
`manager.get(scope)` (e.g. `CondaEnvManager.get()`):
59+
4. `initialize()` — lazy, once-only per manager (guarded by deferred)
60+
a. `nativeFinder.refresh(hardRefresh=false)`:
61+
`handleSoftRefresh()` checks in-memory cache (Map) for key 'all' (bc one big scan, shared cache, all managers benefit)
62+
- on reload: cache is empty (Map was destroyed) → cache miss
63+
- falls through to `handleHardRefresh()`
64+
`handleHardRefresh()`:
65+
- adds request to WorkerPool queue (concurrency 1, so serialized)
66+
- when its turn comes, calls `doRefresh()`:
67+
1. `configure()` — JSON-RPC to PET with search paths, conda/poetry/pipenv paths, cache dir
68+
2. `refresh` — JSON-RPC to PET, PET scans filesystem
69+
- PET may use its own on-disk cache (cacheDirectory) to speed this up
70+
- PET streams back results as 'environment' and 'manager' notifications
71+
- envs missing version/prefix get an inline resolve() call
72+
3. returns NativeInfo[] (all envs of all types)
73+
- result stored in in-memory cache under key 'all'
74+
→ subsequent managers calling nativeFinder.refresh(false) get cache hit → instant
75+
b. filter results to this manager's env type (e.g. conda filters to kind=conda)
76+
c. convert NativeEnvInfo → PythonEnvironment objects → populate collection
77+
d. `loadEnvMap()` — reads persisted env path from workspace state
78+
→ matches path against freshly discovered collection via `findEnvironmentByPath()`
79+
→ populates `fsPathToEnv` map
80+
5. look up scope in `fsPathToEnv` → return the matched env
81+
82+
📊 TELEMETRY: ENV_SELECTION.RESULT (per scope) { duration (priority chain + manager.get), scope, prioritySource, managerId, path, hasPersistedSelection }
83+
84+
3. env is cached in memory (no settings.json write)
85+
4. Python extension / status bar can now get the selected env via `api.getEnvironment(scope)`
86+
87+
📊 TELEMETRY: EXTENSION.MANAGER_REGISTRATION_DURATION { duration (activation→here), result, failureStage?, errorType? }
88+
89+
POST-INIT:
90+
1. register terminal package watcher
91+
2. register settings change listener (`registerInterpreterSettingsChangeListener`) — re-runs priority chain if settings change
92+
3. initialize terminal manager
93+
4. send telemetry (manager selection, project structure, discovery summary)

examples/sample1/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 32 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "vscode-python-envs",
33
"displayName": "Python Environments",
44
"description": "Provides a unified python environment experience",
5-
"version": "1.25.0",
5+
"version": "1.27.0",
66
"publisher": "ms-python",
77
"preview": true,
88
"engines": {

src/common/localize.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export namespace Common {
1515
export const ok = l10n.t('Ok');
1616
export const quickCreate = l10n.t('Quick Create');
1717
export const installPython = l10n.t('Install Python');
18+
export const dontShowAgain = l10n.t("Don't Show Again");
19+
export const dontAskAgain = l10n.t("Don't ask again");
1820
}
1921

2022
export namespace WorkbenchStrings {
@@ -136,6 +138,13 @@ export namespace SysManagerStrings {
136138
export const packageRefreshError = l10n.t('Error refreshing packages');
137139
}
138140

141+
export namespace PixiStrings {
142+
export const pixiExtensionRecommendation = l10n.t(
143+
'Pixi environments were detected. Install the Pixi extension for full support including activation and environment management.',
144+
);
145+
export const install = l10n.t('Open on Marketplace');
146+
}
147+
139148
export namespace CondaStrings {
140149
export const condaManager = l10n.t('Manages Conda environments');
141150
export const condaDiscovering = l10n.t('Discovering Conda environments');
@@ -215,6 +224,9 @@ export namespace ActivationStrings {
215224
Commands.viewLogs,
216225
);
217226
export const activatingEnvironment = l10n.t('Activating environment');
227+
export const envFileInjectionDisabled = l10n.t(
228+
'An environment file is configured but terminal environment injection is disabled. Enable "python.terminal.useEnvFile" to use environment variables from .env files in terminals.',
229+
);
218230
}
219231

220232
export namespace UvInstallStrings {
@@ -240,7 +252,6 @@ export namespace UvInstallStrings {
240252
export const uvInstallRestartRequired = l10n.t(
241253
'uv was installed but may not be available in the current terminal. Please restart VS Code or open a new terminal and try again.',
242254
);
243-
export const dontAskAgain = l10n.t("Don't ask again");
244255
export const clickToInstallPython = l10n.t('No Python found, click to install');
245256
export const selectPythonVersion = l10n.t('Select Python version to install');
246257
export const installed = l10n.t('installed');

0 commit comments

Comments
 (0)