Skip to content

Commit 227ef8a

Browse files
committed
fix: add load check to more places
1 parent aaa3c85 commit 227ef8a

File tree

6 files changed

+128
-25
lines changed

6 files changed

+128
-25
lines changed

src/api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ export interface QuickCreateConfig {
333333
*/
334334
export interface EnvironmentManager {
335335
/**
336-
* The name of the environment manager.
336+
* The name of the environment manager. Allowed characters (a-z, A-Z, 0-9, -, _).
337337
*/
338338
readonly name: string;
339339

@@ -564,7 +564,7 @@ export interface DidChangePackagesEventArgs {
564564
*/
565565
export interface PackageManager {
566566
/**
567-
* The name of the package manager.
567+
* The name of the package manager. Allowed characters (a-z, A-Z, 0-9, -, _).
568568
*/
569569
name: string;
570570

src/common/localize.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ export namespace Common {
1717
export const installPython = l10n.t('Install Python');
1818
}
1919

20+
export namespace WorkbenchStrings {
21+
export const installExtension = l10n.t('Install Extension');
22+
}
23+
2024
export namespace Interpreter {
2125
export const statusBarSelect = l10n.t('Select Interpreter');
2226
export const browsePath = l10n.t('Browse...');

src/common/workbenchCommands.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { commands, Uri } from 'vscode';
2+
3+
export async function installExtension(
4+
extensionId: Uri | string,
5+
options?: {
6+
installOnlyNewlyAddedFromExtensionPackVSIX?: boolean;
7+
installPreReleaseVersion?: boolean;
8+
donotSync?: boolean;
9+
},
10+
): Promise<void> {
11+
await commands.executeCommand('workbench.extensions.installExtension', extensionId, options);
12+
}

src/features/common/managerReady.ts

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
1-
import { Disposable, Uri } from 'vscode';
1+
import { Disposable, l10n, Uri } from 'vscode';
22
import { EnvironmentManagers, PythonProjectManager } from '../../internal.api';
33
import { createDeferred, Deferred } from '../../common/utils/deferred';
44
import { allExtensions } from '../../common/extension.apis';
5-
import { traceError } from '../../common/logging';
5+
import { traceError, traceInfo } from '../../common/logging';
66
import { showErrorMessage } from '../../common/window.apis';
77
import { getDefaultEnvManagerSetting, getDefaultPkgManagerSetting } from '../settings/settingHelpers';
8+
import { WorkbenchStrings } from '../../common/localize';
9+
import { installExtension } from '../../common/workbenchCommands';
810

911
interface ManagerReady extends Disposable {
1012
waitForEnvManager(uris?: Uri[]): Promise<void>;
13+
waitForEnvManagerId(managerIds: string[]): Promise<void>;
14+
waitForAllEnvManagers(): Promise<void>;
1115
waitForPkgManager(uris?: Uri[]): Promise<void>;
16+
waitForPkgManagerId(managerIds: string[]): Promise<void>;
17+
}
18+
19+
function getExtensionId(managerId: string): string | undefined {
20+
// format <extension-id>:<manager-name>
21+
const regex = /^(.*):([a-zA-Z0-9-_]*)$/;
22+
const parts = regex.exec(managerId);
23+
return parts ? parts[1] : undefined;
1224
}
1325

1426
class ManagerReadyImpl implements ManagerReady {
@@ -44,10 +56,30 @@ class ManagerReadyImpl implements ManagerReady {
4456
const installed = allExtensions().some((ext) => managerId.startsWith(`${ext.id}:`));
4557
if (!installed && !this.checked.has(managerId)) {
4658
this.checked.add(managerId);
47-
traceError(`Extension for manager ${managerId} is not installed.`);
48-
showErrorMessage(`Extension for manager ${managerId} is not installed.`);
59+
const extId = getExtensionId(managerId);
60+
setImmediate(async () => {
61+
if (extId) {
62+
traceError(`Extension for manager ${extId} is not installed.`);
63+
const result = await showErrorMessage(
64+
l10n.t(`Extension for {0} is not installed or enabled for this workspace.`, extId),
65+
WorkbenchStrings.installExtension,
66+
);
67+
if (result === WorkbenchStrings.installExtension) {
68+
traceInfo(`Installing extension: ${extId}`);
69+
try {
70+
await installExtension(extId);
71+
traceInfo(`Extension ${extId} installed.`);
72+
} catch (err) {
73+
traceError(`Failed to install extension: ${extId}`, err);
74+
}
75+
}
76+
} else {
77+
showErrorMessage(
78+
l10n.t(`Extension for {0} is not installed or enabled for this workspace.`, managerId),
79+
);
80+
}
81+
});
4982
}
50-
return installed;
5183
}
5284

5385
public dispose(): void {
@@ -81,8 +113,28 @@ class ManagerReadyImpl implements ManagerReady {
81113
}
82114
}
83115

84-
ids.forEach((managerId) => this.checkExtension(managerId));
85-
await Promise.all(Array.from(ids).map((managerId) => this._waitForEnvManager(managerId)));
116+
await this.waitForEnvManagerId(Array.from(ids));
117+
}
118+
119+
public async waitForEnvManagerId(managerIds: string[]): Promise<void> {
120+
managerIds.forEach((managerId) => this.checkExtension(managerId));
121+
await Promise.all(managerIds.map((managerId) => this._waitForEnvManager(managerId)));
122+
}
123+
124+
public async waitForAllEnvManagers(): Promise<void> {
125+
const ids: Set<string> = new Set();
126+
this.pm.getProjects().forEach((project) => {
127+
const m = getDefaultEnvManagerSetting(this.pm, project.uri);
128+
if (m && !ids.has(m)) {
129+
ids.add(m);
130+
}
131+
});
132+
133+
const m = getDefaultEnvManagerSetting(this.pm, undefined);
134+
if (m) {
135+
ids.add(m);
136+
}
137+
await this.waitForEnvManagerId(Array.from(ids));
86138
}
87139

88140
private _waitForPkgManager(managerId: string): Promise<void> {
@@ -111,12 +163,11 @@ class ManagerReadyImpl implements ManagerReady {
111163
}
112164
}
113165

114-
ids.forEach((managerId) => this.checkExtension(managerId));
115-
await Promise.all(
116-
Array.from(ids).map((managerId) => {
117-
return this._waitForPkgManager(managerId);
118-
}),
119-
);
166+
await this.waitForPkgManagerId(Array.from(ids));
167+
}
168+
public async waitForPkgManagerId(managerIds: string[]): Promise<void> {
169+
managerIds.forEach((managerId) => this.checkExtension(managerId));
170+
await Promise.all(managerIds.map((managerId) => this._waitForPkgManager(managerId)));
120171
}
121172
}
122173

@@ -134,7 +185,22 @@ export async function waitForEnvManager(uris?: Uri[]): Promise<void> {
134185
return mr.waitForEnvManager(uris);
135186
}
136187

188+
export async function waitForEnvManagerId(managerIds: string[]): Promise<void> {
189+
const mr = await _deferred.promise;
190+
return mr.waitForEnvManagerId(managerIds);
191+
}
192+
193+
export async function waitForAllEnvManagers(): Promise<void> {
194+
const mr = await _deferred.promise;
195+
return mr.waitForAllEnvManagers();
196+
}
197+
137198
export async function waitForPkgManager(uris?: Uri[]): Promise<void> {
138199
const mr = await _deferred.promise;
139200
return mr.waitForPkgManager(uris);
140201
}
202+
203+
export async function waitForPkgManagerId(managerIds: string[]): Promise<void> {
204+
const mr = await _deferred.promise;
205+
return mr.waitForPkgManagerId(managerIds);
206+
}

src/features/envManagers.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
PythonProject,
1111
SetEnvironmentScope,
1212
} from '../api';
13-
import { traceError } from '../common/logging';
13+
import { traceError, traceVerbose } from '../common/logging';
1414
import {
1515
EditAllManagerSettings,
1616
getDefaultEnvManagerSetting,
@@ -39,7 +39,11 @@ import { sendTelemetryEvent } from '../common/telemetry/sender';
3939
import { EventNames } from '../common/telemetry/constants';
4040

4141
function generateId(name: string): string {
42-
return `${getCallingExtension()}:${name}`;
42+
const newName = name.toLowerCase().replace(/[^a-zA-Z0-9-_]/g, '_');
43+
if (name !== newName) {
44+
traceVerbose(`Environment manager name "${name}" was normalized to "${newName}"`);
45+
}
46+
return `${getCallingExtension()}:${newName}`;
4347
}
4448

4549
export class PythonEnvironmentManagers implements EnvironmentManagers {

src/features/pythonApi.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import { runInTerminal } from './terminal/runInTerminal';
4848
import { runInBackground } from './execution/runInBackground';
4949
import { EnvVarManager } from './execution/envVariableManager';
5050
import { checkUri } from '../common/utils/pathUtils';
51-
import { waitForEnvManager } from './common/managerReady';
51+
import { waitForAllEnvManagers, waitForEnvManager, waitForEnvManagerId } from './common/managerReady';
5252

5353
class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
5454
private readonly _onDidChangeEnvironments = new EventEmitter<DidChangeEnvironmentsEventArgs>();
@@ -163,7 +163,8 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
163163
return result;
164164
}
165165
}
166-
removeEnvironment(environment: PythonEnvironment): Promise<void> {
166+
async removeEnvironment(environment: PythonEnvironment): Promise<void> {
167+
await waitForEnvManagerId([environment.envId.managerId]);
167168
const manager = this.envManagers.getEnvironmentManager(environment);
168169
if (!manager) {
169170
return Promise.reject(new Error('No environment manager found'));
@@ -174,9 +175,12 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
174175
const currentScope = checkUri(scope) as RefreshEnvironmentsScope;
175176

176177
if (currentScope === undefined) {
178+
await waitForAllEnvManagers();
177179
await Promise.all(this.envManagers.managers.map((manager) => manager.refresh(currentScope)));
178180
return Promise.resolve();
179181
}
182+
183+
await waitForEnvManager([currentScope]);
180184
const manager = this.envManagers.getEnvironmentManager(currentScope);
181185
if (!manager) {
182186
return Promise.reject(new Error(`No environment manager found for: ${currentScope.fsPath}`));
@@ -186,10 +190,13 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
186190
async getEnvironments(scope: GetEnvironmentsScope): Promise<PythonEnvironment[]> {
187191
const currentScope = checkUri(scope) as GetEnvironmentsScope;
188192
if (currentScope === 'all' || currentScope === 'global') {
193+
await waitForAllEnvManagers();
189194
const promises = this.envManagers.managers.map((manager) => manager.getEnvironments(currentScope));
190195
const items = await Promise.all(promises);
191196
return items.flat();
192197
}
198+
199+
await waitForEnvManager([currentScope]);
193200
const manager = this.envManagers.getEnvironmentManager(currentScope);
194201
if (!manager) {
195202
return [];
@@ -199,14 +206,21 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
199206
return items;
200207
}
201208
onDidChangeEnvironments: Event<DidChangeEnvironmentsEventArgs> = this._onDidChangeEnvironments.event;
202-
setEnvironment(scope: SetEnvironmentScope, environment?: PythonEnvironment): Promise<void> {
203-
return this.envManagers.setEnvironment(checkUri(scope) as SetEnvironmentScope, environment);
209+
async setEnvironment(scope: SetEnvironmentScope, environment?: PythonEnvironment): Promise<void> {
210+
const currentScope = checkUri(scope) as SetEnvironmentScope;
211+
await waitForEnvManager(
212+
currentScope ? (currentScope instanceof Uri ? [currentScope] : currentScope) : undefined,
213+
);
214+
return this.envManagers.setEnvironment(currentScope, environment);
204215
}
205216
async getEnvironment(scope: GetEnvironmentScope): Promise<PythonEnvironment | undefined> {
206-
return this.envManagers.getEnvironment(checkUri(scope) as GetEnvironmentScope);
217+
const currentScope = checkUri(scope) as GetEnvironmentScope;
218+
await waitForEnvManager(currentScope ? [currentScope] : undefined);
219+
return this.envManagers.getEnvironment(currentScope);
207220
}
208221
onDidChangeEnvironment: Event<DidChangeEnvironmentEventArgs> = this._onDidChangeEnvironment.event;
209222
async resolveEnvironment(context: ResolveEnvironmentContext): Promise<PythonEnvironment | undefined> {
223+
await waitForAllEnvManagers();
210224
const projects = this.projectManager.getProjects();
211225
const projectEnvManagers: InternalEnvironmentManager[] = [];
212226
projects.forEach((p) => {
@@ -227,21 +241,24 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
227241
}
228242
return new Disposable(() => disposables.forEach((d) => d.dispose()));
229243
}
230-
managePackages(context: PythonEnvironment, options: PackageManagementOptions): Promise<void> {
244+
async managePackages(context: PythonEnvironment, options: PackageManagementOptions): Promise<void> {
245+
await waitForEnvManagerId([context.envId.managerId]);
231246
const manager = this.envManagers.getPackageManager(context);
232247
if (!manager) {
233248
return Promise.reject(new Error('No package manager found'));
234249
}
235250
return manager.manage(context, options);
236251
}
237-
refreshPackages(context: PythonEnvironment): Promise<void> {
252+
async refreshPackages(context: PythonEnvironment): Promise<void> {
253+
await waitForEnvManagerId([context.envId.managerId]);
238254
const manager = this.envManagers.getPackageManager(context);
239255
if (!manager) {
240256
return Promise.reject(new Error('No package manager found'));
241257
}
242258
return manager.refresh(context);
243259
}
244-
getPackages(context: PythonEnvironment): Promise<Package[] | undefined> {
260+
async getPackages(context: PythonEnvironment): Promise<Package[] | undefined> {
261+
await waitForEnvManagerId([context.envId.managerId]);
245262
const manager = this.envManagers.getPackageManager(context);
246263
if (!manager) {
247264
return Promise.resolve(undefined);

0 commit comments

Comments
 (0)