Skip to content

Commit daec7ec

Browse files
committed
fix: ensure extension is installed and loading for contributions
1 parent 89395aa commit daec7ec

File tree

4 files changed

+161
-0
lines changed

4 files changed

+161
-0
lines changed

src/common/window.apis.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,22 @@ export function showWarningMessage(message: string, ...items: any[]): Thenable<s
302302
return window.showWarningMessage(message, ...items);
303303
}
304304

305+
export function showErrorMessage<T extends string>(message: string, ...items: T[]): Thenable<T | undefined>;
306+
export function showErrorMessage<T extends string>(
307+
message: string,
308+
options: MessageOptions,
309+
...items: T[]
310+
): Thenable<T | undefined>;
311+
export function showErrorMessage<T extends MessageItem>(message: string, ...items: T[]): Thenable<T | undefined>;
312+
export function showErrorMessage<T extends MessageItem>(
313+
message: string,
314+
options: MessageOptions,
315+
...items: T[]
316+
): Thenable<T | undefined>;
317+
export function showErrorMessage(message: string, ...items: any[]): Thenable<string | undefined> {
318+
return window.showErrorMessage(message, ...items);
319+
}
320+
305321
export function showInputBox(options?: InputBoxOptions, token?: CancellationToken): Thenable<string | undefined> {
306322
return window.showInputBox(options, token);
307323
}

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import { GetEnvironmentInfoTool, InstallPackageTool } from './features/copilotTo
5757
import { TerminalActivationImpl } from './features/terminal/terminalActivationState';
5858
import { getEnvironmentForTerminal } from './features/terminal/utils';
5959
import { sendManagerSelectionTelemetry } from './common/telemetry/helpers';
60+
import { createManagerReady } from './features/common/managerReady';
6061

6162
export async function activate(context: ExtensionContext): Promise<PythonEnvironmentApi> {
6263
const start = new StopWatch();
@@ -80,6 +81,7 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
8081
context.subscriptions.push(envVarManager);
8182

8283
const envManagers: EnvironmentManagers = new PythonEnvironmentManagers(projectManager);
84+
createManagerReady(envManagers, projectManager, context.subscriptions);
8385
context.subscriptions.push(envManagers);
8486

8587
const terminalActivation = new TerminalActivationImpl();
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { Disposable, Uri } from 'vscode';
2+
import { EnvironmentManagers, PythonProjectManager } from '../../internal.api';
3+
import { createDeferred, Deferred } from '../../common/utils/deferred';
4+
import { allExtensions } from '../../common/extension.apis';
5+
import { traceError } from '../../common/logging';
6+
import { showErrorMessage } from '../../common/window.apis';
7+
import { getDefaultEnvManagerSetting, getDefaultPkgManagerSetting } from '../settings/settingHelpers';
8+
9+
interface ManagerReady extends Disposable {
10+
waitForEnvManager(uris?: Uri[]): Promise<void>;
11+
waitForPkgManager(uris?: Uri[]): Promise<void>;
12+
}
13+
14+
class ManagerReadyImpl implements ManagerReady {
15+
private readonly envManagers: Map<string, Deferred<void>> = new Map();
16+
private readonly pkgManagers: Map<string, Deferred<void>> = new Map();
17+
private readonly checked: Set<string> = new Set();
18+
private readonly disposables: Disposable[] = [];
19+
20+
constructor(em: EnvironmentManagers, private readonly pm: PythonProjectManager) {
21+
this.disposables.push(
22+
em.onDidChangeEnvironmentManager((e) => {
23+
if (this.envManagers.has(e.manager.id)) {
24+
this.envManagers.get(e.manager.id)?.resolve();
25+
} else {
26+
const deferred = createDeferred<void>();
27+
this.envManagers.set(e.manager.id, deferred);
28+
deferred.resolve();
29+
}
30+
}),
31+
em.onDidChangePackageManager((e) => {
32+
if (this.pkgManagers.has(e.manager.id)) {
33+
this.pkgManagers.get(e.manager.id)?.resolve();
34+
} else {
35+
const deferred = createDeferred<void>();
36+
this.pkgManagers.set(e.manager.id, deferred);
37+
deferred.resolve();
38+
}
39+
}),
40+
);
41+
}
42+
43+
private checkExtension(managerId: string) {
44+
const installed = allExtensions().some((ext) => managerId.startsWith(`${ext.id}:`));
45+
if (!installed && !this.checked.has(managerId)) {
46+
this.checked.add(managerId);
47+
traceError(`Extension for manager ${managerId} is not installed.`);
48+
showErrorMessage(`Extension for manager ${managerId} is not installed.`);
49+
}
50+
return installed;
51+
}
52+
53+
public dispose(): void {
54+
this.disposables.forEach((d) => d.dispose());
55+
this.envManagers.clear();
56+
this.pkgManagers.clear();
57+
}
58+
59+
private _waitForEnvManager(managerId: string): Promise<void> {
60+
if (this.envManagers.has(managerId)) {
61+
return this.envManagers.get(managerId)!.promise;
62+
}
63+
const deferred = createDeferred<void>();
64+
this.envManagers.set(managerId, deferred);
65+
return deferred.promise;
66+
}
67+
68+
public async waitForEnvManager(uris?: Uri[]): Promise<void> {
69+
const ids: Set<string> = new Set();
70+
if (uris) {
71+
uris.forEach((uri) => {
72+
const m = getDefaultEnvManagerSetting(this.pm, uri);
73+
if (!ids.has(m)) {
74+
ids.add(m);
75+
}
76+
});
77+
} else {
78+
const m = getDefaultEnvManagerSetting(this.pm, undefined);
79+
if (m) {
80+
ids.add(m);
81+
}
82+
}
83+
84+
ids.forEach((managerId) => this.checkExtension(managerId));
85+
await Promise.all(Array.from(ids).map((managerId) => this._waitForEnvManager(managerId)));
86+
}
87+
88+
private _waitForPkgManager(managerId: string): Promise<void> {
89+
if (this.pkgManagers.has(managerId)) {
90+
return this.pkgManagers.get(managerId)!.promise;
91+
}
92+
const deferred = createDeferred<void>();
93+
this.pkgManagers.set(managerId, deferred);
94+
return deferred.promise;
95+
}
96+
97+
public async waitForPkgManager(uris?: Uri[]): Promise<void> {
98+
const ids: Set<string> = new Set();
99+
100+
if (uris) {
101+
uris.forEach((uri) => {
102+
const m = getDefaultPkgManagerSetting(this.pm, uri);
103+
if (!ids.has(m)) {
104+
ids.add(m);
105+
}
106+
});
107+
} else {
108+
const m = getDefaultPkgManagerSetting(this.pm, undefined);
109+
if (m) {
110+
ids.add(m);
111+
}
112+
}
113+
114+
ids.forEach((managerId) => this.checkExtension(managerId));
115+
await Promise.all(
116+
Array.from(ids).map((managerId) => {
117+
return this._waitForPkgManager(managerId);
118+
}),
119+
);
120+
}
121+
}
122+
123+
let _deferred = createDeferred<ManagerReady>();
124+
export function createManagerReady(em: EnvironmentManagers, pm: PythonProjectManager, disposables: Disposable[]) {
125+
if (!_deferred.completed) {
126+
const mr = new ManagerReadyImpl(em, pm);
127+
disposables.push(mr);
128+
_deferred.resolve(mr);
129+
}
130+
}
131+
132+
export async function waitForEnvManager(uris?: Uri[]): Promise<void> {
133+
const mr = await _deferred.promise;
134+
return mr.waitForEnvManager(uris);
135+
}
136+
137+
export async function waitForPkgManager(uris?: Uri[]): Promise<void> {
138+
const mr = await _deferred.promise;
139+
return mr.waitForPkgManager(uris);
140+
}

src/features/pythonApi.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +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';
5152

5253
class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
5354
private readonly _onDidChangeEnvironments = new EventEmitter<DidChangeEnvironmentsEventArgs>();
@@ -123,6 +124,7 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
123124
options: CreateEnvironmentOptions | undefined,
124125
): Promise<PythonEnvironment | undefined> {
125126
if (scope === 'global' || (!Array.isArray(scope) && scope instanceof Uri)) {
127+
await waitForEnvManager(scope === 'global' ? undefined : [scope]);
126128
const manager = this.envManagers.getEnvironmentManager(scope === 'global' ? undefined : scope);
127129
if (!manager) {
128130
throw new Error('No environment manager found');
@@ -134,6 +136,7 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
134136
} else if (Array.isArray(scope) && scope.length === 1 && scope[0] instanceof Uri) {
135137
return this.createEnvironment(scope[0], options);
136138
} else if (Array.isArray(scope) && scope.length > 0 && scope.every((s) => s instanceof Uri)) {
139+
await waitForEnvManager(scope);
137140
const managers: InternalEnvironmentManager[] = [];
138141
scope.forEach((s) => {
139142
const manager = this.envManagers.getEnvironmentManager(s);

0 commit comments

Comments
 (0)