Repository navigation
Expand file tree
/
Copy pathextensionApi.ts
More file actions
664 lines (630 loc) · 31.3 KB
/
Copy pathextensionApi.ts
File metadata and controls
664 lines (630 loc) · 31.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
import { Disposable, Event, EventEmitter, l10n, TaskExecution, Terminal, Uri } from 'vscode';
import * as path from 'path';
import type { CapabilityContext, EnvironmentManagerCapability, PackageManagerCapability, Support } from './capabilities';
import { EnvironmentCapability, isKnownManagerCapability, PackageCapability } from './capabilities';
import { explain as parsePep440Version } from '@renovatebot/pep440';
import { EventNames } from './common/telemetry/constants';
import { sendTelemetryEvent } from './common/telemetry/sender';
import type {
CreateEnvironmentOptions,
CreateEnvironmentScope,
DidChangeEnvironmentEventArgs,
DidChangeEnvironmentsEventArgs,
DidChangeEnvironmentVariablesEventArgs,
DidChangePackagesEventArgs,
DidChangePythonProjectsEventArgs,
EnvironmentManager,
GetEnvironmentScope,
GetEnvironmentsScope,
GetPackageAvailableVersionsOptions,
GetPackagesOptions,
Package,
PackageId,
PackageInfo,
PackageManagementOptions,
PackageManager,
Pep440Version,
PythonBackgroundRunOptions,
PythonEnvironment,
PythonEnvironmentApi,
PythonEnvironmentId,
PythonEnvironmentInfo,
PythonProcess,
PythonProject,
PythonProjectCreator,
PythonTaskExecutionOptions,
PythonTerminalCreateOptions,
PythonTerminalExecutionOptions,
RefreshEnvironmentsScope,
RemoveEnvironmentOptions,
ResolveEnvironmentContext,
SetEnvironmentScope,
} from './types';
import { PackageVersionLookupNotSupportedError } from './publicErrors';
import { INLINE_SCRIPT_MANAGER_ID } from './common/constants';
import { traceError, traceInfo } from './common/logging';
import { pickEnvironmentManager } from './common/pickers/managers';
import { timeout } from './common/utils/asyncUtils';
import { createDeferred } from './common/utils/deferred';
import { checkUri, normalizePath } from './common/utils/pathUtils';
import { handlePythonPath } from './common/utils/pythonPath';
import type { EnvironmentManagers } from './features/envManagers';
import type { ProjectCreators } from './features/creators/projectCreators';
import type { PythonProjectManager } from './features/projectManager';
import type { InternalEnvironmentManager, InternalPackageManager } from './managers/common/registeredManagers';
import { PythonEnvironmentImpl, PythonPackageImpl } from './managers/common/models';
import { waitForAllEnvManagers, waitForEnvManager, waitForEnvManagerId } from './features/common/managerReady';
import { EnvVarManager } from './features/execution/envVariableManager';
import { runAsTask } from './features/execution/runAsTask';
import { runInBackground } from './features/execution/runInBackground';
import { runInTerminal } from './features/terminal/runInTerminal';
import { TerminalManager } from './features/terminal/terminalManager';
import { PythonToolsApiImpl } from './internal/pythonTools';
import type { PythonToolsApi } from './internal/pythonToolsApi';
// Maximum time getEnvironment will block before serving the last-known environment while a
// slow initial resolution/refresh continues in the background. Keeps consumers (e.g. Pylance's
// workspace/configuration handler) from hanging on the full environment enumeration at startup.
const GET_ENVIRONMENT_TIMEOUT_MS = 1000;
const GET_ENVIRONMENT_TIMED_OUT = Symbol('getEnvironmentTimedOut');
const CAPABILITY_TELEMETRY_VERSION_TIMEOUT_MS = 1000;
type ManagerCapabilityQuery =
| {
kind: 'environment';
manager: InternalEnvironmentManager | undefined;
managerId: string;
capability: EnvironmentManagerCapability;
context?: CapabilityContext;
}
| {
kind: 'package';
manager: InternalPackageManager | undefined;
capability: PackageManagerCapability;
context: CapabilityContext & { environment: PythonEnvironment };
};
function logCapabilityQueryError(error: unknown): void {
traceError('Failed to query manager capability:', error);
}
export class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
readonly __pythonTools: PythonToolsApi;
private readonly _onDidChangeEnvironments = new EventEmitter<DidChangeEnvironmentsEventArgs>();
private readonly _onDidChangeEnvironment = new EventEmitter<DidChangeEnvironmentEventArgs>();
private readonly _onDidChangePythonProjects = new EventEmitter<DidChangePythonProjectsEventArgs>();
private readonly _onDidChangePackages = new EventEmitter<DidChangePackagesEventArgs>();
private readonly _onDidChangeEnvironmentVariables = new EventEmitter<DidChangeEnvironmentVariablesEventArgs>();
// Tracks the last-known project set so we can compute an added/removed delta
// whenever the underlying project manager reports a change (fix for #1599).
private previousProjects: readonly PythonProject[] = [];
constructor(
private readonly envManagers: EnvironmentManagers,
private readonly projectManager: PythonProjectManager,
private readonly projectCreators: ProjectCreators,
private readonly terminalManager: TerminalManager,
private readonly envVarManager: EnvVarManager,
private readonly disposables: Disposable[] = [],
) {
this.__pythonTools = new PythonToolsApiImpl(envManagers, projectManager);
this.previousProjects = this.projectManager.getProjects();
this.disposables.push(
this._onDidChangeEnvironment,
this._onDidChangeEnvironments,
this._onDidChangePythonProjects,
this._onDidChangePackages,
this._onDidChangeEnvironmentVariables,
this.envManagers.onDidChangePackageProviderPackages((e) => this._onDidChangePackages.fire(e)),
this.envManagers.onDidChangeActiveEnvironment((e) => {
this._onDidChangeEnvironment.fire(e);
const location = e.uri?.fsPath ?? 'global';
traceInfo(
`Python API: Changed environment from ${e.old?.displayName} to ${e.new?.displayName} for: ${location}`,
);
}),
this.envVarManager.onDidChangeEnvironmentVariables((e) => this._onDidChangeEnvironmentVariables.fire(e)),
this.projectManager.onDidChangeProjects(() => {
const current = this.projectManager.getProjects();
const currentByUri = new Map(current.map((p) => [p.uri.toString(), p] as const));
const previousByUri = new Map(this.previousProjects.map((p) => [p.uri.toString(), p] as const));
const added = [...currentByUri.entries()]
.filter(([uri]) => !previousByUri.has(uri))
.map(([, project]) => project);
const removed = [...previousByUri.entries()]
.filter(([uri]) => !currentByUri.has(uri))
.map(([, project]) => project);
this.previousProjects = current;
if (added.length > 0 || removed.length > 0) {
traceInfo(`Python API: Projects changed. Added: ${added.length}, Removed: ${removed.length}`);
this._onDidChangePythonProjects.fire({ added, removed });
}
}),
);
}
registerEnvironmentManager(manager: EnvironmentManager, options?: { extensionId?: string }): Disposable {
const disposables: Disposable[] = [];
disposables.push(this.envManagers.registerEnvironmentManager(manager, options));
if (manager.onDidChangeEnvironments) {
disposables.push(manager.onDidChangeEnvironments((e) => this._onDidChangeEnvironments.fire(e)));
}
if (manager.onDidChangeEnvironment) {
disposables.push(
manager.onDidChangeEnvironment((e) => {
setImmediate(() => {
// Refresh the central cache for this scope. This ensures that only the
// *selected* manager's changes propagate (refreshEnvironment checks
// getEnvironmentManager(scope) internally). It updates the cache and
// fires onDidChangeActiveEnvironment, which the Python API listens to.
this.envManagers
.refreshEnvironment(e.uri)
.catch((err) => traceError('Failed to refresh environment on change:', err));
});
}),
);
}
return new Disposable(() => disposables.forEach((d) => d.dispose()));
}
/**
* Queries one registered environment provider without activation, waiting, or prompting.
* @param managerId Exact registered environment manager ID.
* @param capability Feature to query.
* @param context Optional scope, environment, and project forwarded to the provider.
*/
async getEnvironmentManagerCapability(
managerId: string,
capability: EnvironmentManagerCapability,
context?: CapabilityContext,
): Promise<Support> {
if (context?.environment && context.environment.envId.managerId !== managerId) {
throw new Error(l10n.t('The environment does not belong to environment manager {0}.', managerId));
}
const manager = this.envManagers.getEnvironmentManager(managerId);
return this.captureCapabilityQuery({
kind: 'environment',
manager,
managerId,
capability,
context,
});
}
/**
* Queries the registered package provider routed for an environment or explicit project without activation or waiting.
* @param environment Environment whose packages would be managed.
* @param capability Feature to query.
* @param project Optional project selecting its configured, project-bound provider.
*/
async getPackageManagerCapability(
environment: PythonEnvironment,
capability: PackageManagerCapability,
project?: PythonProject,
): Promise<Support> {
const registeredProject = project && this.projectManager.getProjects().find(
({ uri }) => uri.scheme === project.uri.scheme && (
uri.scheme === 'file'
? normalizePath(path.resolve(uri.fsPath)) === normalizePath(path.resolve(project.uri.fsPath))
: uri.toString() === project.uri.toString()
),
);
let manager = !project || registeredProject
? this.envManagers.getPackageManager(registeredProject?.uri ?? environment)
: undefined;
// A project-aware root is not a usable substitute for a missing scoped instance.
if (manager?.createForProject && !manager.project) {
manager = undefined;
}
return this.captureCapabilityQuery({
kind: 'package',
manager,
capability,
context: { environment, project: registeredProject ?? project },
});
}
private async captureCapabilityQuery(query: ManagerCapabilityQuery): Promise<Support> {
const { manager, capability } = query;
let outcome: 'supported' | 'unsupported' | 'unavailable' | 'error' = 'error';
let isLegacy = false;
try {
if (!query.manager) {
outcome = 'unavailable';
return {
supported: false,
reason: query.kind === 'environment'
? l10n.t('Environment manager {0} is unavailable.', query.managerId)
: l10n.t('No package manager is available for the environment and project context.'),
};
}
const support = query.kind === 'environment'
? await query.manager.getCapability(query.capability, query.context)
: await query.manager.getCapability(query.capability, query.context);
outcome = support.supported ? 'supported' : 'unsupported';
isLegacy = support.isLegacy === true;
return support;
} finally {
void this.getManagerVersionForTelemetryWithTimeout(query, CAPABILITY_TELEMETRY_VERSION_TIMEOUT_MS)
.then((managerVersion) => {
sendTelemetryEvent(EventNames.MANAGER_CAPABILITY_QUERY, undefined, {
managerId: manager?.id ?? 'none',
managerKind: query.kind,
managerVersion,
capability: isKnownManagerCapability(capability) ? capability : 'unknown',
outcome,
isLegacy,
});
})
.catch((error) => traceError('Failed to send manager capability query telemetry:', error));
}
}
private async getManagerVersionForTelemetryWithTimeout(
query: ManagerCapabilityQuery,
timeoutMs: number,
): Promise<string> {
if (!query.manager) {
return 'unknown';
}
let versionTimer: ReturnType<typeof setTimeout> | undefined;
try {
const version = await Promise.race([
query.kind === 'package'
? query.manager.getVersion(query.context.environment)
: query.manager.getVersion(),
new Promise<never>((_, reject) => {
versionTimer = setTimeout(
() => reject(new Error('Manager version lookup timed out.')),
timeoutMs,
);
}),
]);
return version ? parsePep440Version(version.public)?.public ?? 'unknown' : 'unknown';
} catch (error) {
traceError(`Failed to retrieve manager version for capability telemetry: ${query.manager.id}`, error);
return 'unknown';
} finally {
if (versionTimer !== undefined) {
clearTimeout(versionTimer);
}
}
}
createPythonEnvironmentItem(info: PythonEnvironmentInfo, manager: EnvironmentManager): PythonEnvironment {
const mgr = this.envManagers.managers.find((m) => m.equals(manager));
if (!mgr) {
throw new Error('Environment manager not found');
}
const randomStr = Math.random().toString(36).substring(2);
const envId: PythonEnvironmentId = {
managerId: mgr.id,
id: `${info.name}-${randomStr}`,
};
return new PythonEnvironmentImpl(envId, info);
}
async createEnvironment(
scope: CreateEnvironmentScope,
options: CreateEnvironmentOptions | undefined,
): Promise<PythonEnvironment | undefined> {
if (scope === 'global' || (!Array.isArray(scope) && scope instanceof Uri)) {
await waitForEnvManager(scope === 'global' ? undefined : [scope]);
const manager = this.envManagers.getEnvironmentManager(scope === 'global' ? undefined : scope);
if (!manager) {
throw new Error('No environment manager found');
}
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.Create, { scope })
.catch(logCapabilityQueryError);
if (!manager.supportsCreate) {
throw new Error(`Environment manager does not support creating environments: ${manager.id}`);
}
return manager.create(scope, options);
} else if (Array.isArray(scope) && scope.length === 1 && scope[0] instanceof Uri) {
return this.createEnvironment(scope[0], options);
} else if (Array.isArray(scope) && scope.length > 0 && scope.every((s) => s instanceof Uri)) {
await waitForEnvManager(scope);
const managers: InternalEnvironmentManager[] = [];
scope.forEach((s) => {
const manager = this.envManagers.getEnvironmentManager(s);
if (manager && !managers.includes(manager) && manager.supportsCreate) {
managers.push(manager);
}
});
if (managers.length === 0) {
throw new Error('No environment managers found');
}
const managerId = await pickEnvironmentManager(managers);
if (!managerId) {
throw new Error('No environment manager selected');
}
const manager = managers.find((m) => m.id === managerId);
if (!manager) {
throw new Error('No environment manager found');
}
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.Create, { scope })
.catch(logCapabilityQueryError);
const result = await manager.create(scope, options);
return result;
}
}
async removeEnvironment(environment: PythonEnvironment, options?: RemoveEnvironmentOptions): Promise<void> {
await waitForEnvManagerId([environment.envId.managerId]);
const manager = this.envManagers.getEnvironmentManager(environment);
if (!manager) {
return Promise.reject(new Error('No environment manager found'));
}
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.Remove, { environment })
.catch(logCapabilityQueryError);
return manager.remove(environment, options);
}
async refreshEnvironments(scope: RefreshEnvironmentsScope): Promise<void> {
const currentScope = checkUri(scope) as RefreshEnvironmentsScope;
if (currentScope === undefined) {
await waitForAllEnvManagers();
await Promise.all(this.envManagers.managers.map((manager) => {
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.List, { scope: currentScope })
.catch(logCapabilityQueryError);
return manager.refresh(currentScope);
}));
return Promise.resolve();
}
await waitForEnvManager([currentScope]);
const manager = this.envManagers.getEnvironmentManager(currentScope);
if (!manager) {
return Promise.reject(new Error(`No environment manager found for: ${currentScope.fsPath}`));
}
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.List, { scope: currentScope })
.catch(logCapabilityQueryError);
return manager.refresh(currentScope);
}
async getEnvironments(scope: GetEnvironmentsScope): Promise<PythonEnvironment[]> {
const currentScope = checkUri(scope) as GetEnvironmentsScope;
if (currentScope === 'all' || currentScope === 'global') {
await waitForAllEnvManagers();
const promises = this.envManagers.managers.map((manager) => {
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.List, { scope: currentScope })
.catch(logCapabilityQueryError);
return manager.getEnvironments(currentScope);
});
const items = await Promise.all(promises);
return items.flat();
}
await waitForEnvManager([currentScope]);
const manager = this.envManagers.getEnvironmentManager(currentScope);
if (!manager) {
return [];
}
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.List, { scope: currentScope })
.catch(logCapabilityQueryError);
const items = await manager.getEnvironments(currentScope);
return items;
}
onDidChangeEnvironments: Event<DidChangeEnvironmentsEventArgs> = this._onDidChangeEnvironments.event;
async setEnvironment(scope: SetEnvironmentScope, environment?: PythonEnvironment): Promise<void> {
const currentScope = checkUri(scope) as SetEnvironmentScope;
await waitForEnvManager(
currentScope ? (currentScope instanceof Uri ? [currentScope] : currentScope) : undefined,
);
if (Array.isArray(currentScope) && !environment) {
const scopesByManager = new Map<InternalEnvironmentManager, Uri[]>();
for (const uri of currentScope) {
const manager = this.envManagers.getEnvironmentManager(uri);
if (manager) {
scopesByManager.set(manager, [...(scopesByManager.get(manager) ?? []), uri]);
}
}
for (const [manager, scopes] of scopesByManager) {
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.SetSelected, { scope: scopes })
.catch(logCapabilityQueryError);
}
} else {
const manager = this.envManagers.getEnvironmentManager(
environment ?? (currentScope instanceof Uri ? currentScope : undefined),
);
if (manager) {
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.SetSelected, {
scope: currentScope,
environment,
}).catch(logCapabilityQueryError);
}
}
return this.envManagers.setEnvironment(currentScope, environment);
}
async getEnvironment(scope: GetEnvironmentScope): Promise<PythonEnvironment | undefined> {
const currentScope = checkUri(scope) as GetEnvironmentScope;
// Don't block callers (notably Pylance's workspace/configuration handler) on a potentially
// slow initial environment resolution/refresh. Race the real resolution against a short
// timeout; if it doesn't complete promptly, return the last-known environment for the scope.
// The resolution continues in the background and consumers are notified of the real value
// via onDidChangeEnvironment once it settles.
const resolution = (async () => {
await waitForEnvManager(currentScope ? [currentScope] : undefined);
const manager = this.envManagers.getEnvironmentManager(currentScope);
if (manager) {
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.GetSelected, { scope: currentScope })
.catch(logCapabilityQueryError);
}
return this.envManagers.getEnvironment(currentScope);
})();
const result = await Promise.race([
resolution,
timeout(GET_ENVIRONMENT_TIMEOUT_MS).then(() => GET_ENVIRONMENT_TIMED_OUT),
]);
if (result !== GET_ENVIRONMENT_TIMED_OUT) {
return result as PythonEnvironment | undefined;
}
// Keep the background resolution alive so the cache/last-known value gets populated and the
// change event fires once it finishes.
resolution.catch((ex) => traceError('Failed to resolve environment in background', ex));
// Inline-script environments are reclaimed from a shared cache, and the manager withholds
// one it cannot prove is still safe. Serving the last-known value here would hand back the
// descriptor that decision just rejected, so only the timeout is skipped for them; every
// other manager keeps the fast fallback.
if (this.envManagers.getEnvironmentManager(currentScope)?.id === INLINE_SCRIPT_MANAGER_ID) {
return resolution;
}
return this.envManagers.getLastKnownEnvironment(currentScope);
}
onDidChangeEnvironment: Event<DidChangeEnvironmentEventArgs> = this._onDidChangeEnvironment.event;
async resolveEnvironment(context: ResolveEnvironmentContext): Promise<PythonEnvironment | undefined> {
await waitForAllEnvManagers();
const projects = this.projectManager.getProjects();
const projectEnvManagers: InternalEnvironmentManager[] = [];
projects.forEach((p) => {
const manager = this.envManagers.getEnvironmentManager(p.uri);
if (manager && !projectEnvManagers.includes(manager)) {
projectEnvManagers.push(manager);
}
});
for (const manager of new Set([...projectEnvManagers, ...this.envManagers.managers])) {
void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.Resolve)
.catch(logCapabilityQueryError);
}
return await handlePythonPath(context, this.envManagers.managers, projectEnvManagers);
}
registerPackageManager(manager: PackageManager, options?: { extensionId?: string }): Disposable {
return this.envManagers.registerPackageManager(manager, options);
}
async managePackages(context: PythonEnvironment, options: PackageManagementOptions): Promise<void> {
await waitForEnvManagerId([context.envId.managerId]);
const manager = this.envManagers.getPackageManager(context);
if (!manager) {
return Promise.reject(new Error('No package manager found'));
}
const hasInstall = !!options.install?.length;
const hasUninstall = !!options.uninstall?.length;
const interactive = !hasInstall && !hasUninstall;
for (const [capability, requested] of [
[options.upgrade ? PackageCapability.Upgrade : PackageCapability.Install, hasInstall || interactive],
[PackageCapability.Uninstall, hasUninstall || interactive],
[PackageCapability.Install, interactive && options.upgrade],
] as const) {
if (requested) {
void this.getPackageManagerCapability(context, capability, manager.project).catch(logCapabilityQueryError);
}
}
return manager.manage(context, options);
}
async refreshPackages(context: PythonEnvironment): Promise<void> {
await waitForEnvManagerId([context.envId.managerId]);
const manager = this.envManagers.getPackageManager(context);
if (!manager) {
return Promise.reject(new Error('No package manager found'));
}
void this.getPackageManagerCapability(context, PackageCapability.List, manager.project)
.catch(logCapabilityQueryError);
return manager.refresh(context);
}
async getPackages(context: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined> {
await waitForEnvManagerId([context.envId.managerId]);
const manager = this.envManagers.getPackageManager(context);
if (!manager) {
return Promise.resolve(undefined);
}
void this.getPackageManagerCapability(context, PackageCapability.List, manager.project)
.catch(logCapabilityQueryError);
return manager.getPackages(context, options);
}
getPackageAvailableVersions(
context: PythonEnvironment,
packageName: string,
options: GetPackageAvailableVersionsOptions & { errorMode: 'throw' },
): Promise<Pep440Version[]>;
getPackageAvailableVersions(
context: PythonEnvironment,
packageName: string,
options?: GetPackageAvailableVersionsOptions,
): Promise<Pep440Version[] | undefined>;
async getPackageAvailableVersions(
context: PythonEnvironment,
packageName: string,
options?: GetPackageAvailableVersionsOptions,
): Promise<Pep440Version[] | undefined> {
await waitForEnvManagerId([context.envId.managerId]);
const manager = this.envManagers.getPackageManager(context);
if (!manager) {
if (options?.errorMode === 'throw') {
throw new PackageVersionLookupNotSupportedError(
`No package manager is available to look up versions for: ${context.envId.id}`,
);
}
return undefined;
}
void this.getPackageManagerCapability(context, PackageCapability.FetchAvailableVersions, manager.project)
.catch(logCapabilityQueryError);
return manager.getPackageAvailableVersions(context, packageName, options);
}
onDidChangePackages: Event<DidChangePackagesEventArgs> = this._onDidChangePackages.event;
createPackageItem(info: PackageInfo, environment: PythonEnvironment, manager: PackageManager): Package {
const mgr = this.envManagers.packageManagers.find((m) => m.equals(manager));
if (!mgr) {
throw new Error('Package manager not found');
}
const randomStr = Math.random().toString(36).substring(2);
const pkg: PackageId = {
managerId: mgr.id,
environmentId: environment.envId.id,
id: `${info.name}-${randomStr}`,
};
return new PythonPackageImpl(pkg, info);
}
addPythonProject(projects: PythonProject | PythonProject[]): void {
this.projectManager.add(projects);
}
removePythonProject(pyWorkspace: PythonProject): void {
this.projectManager.remove(pyWorkspace);
}
getPythonProjects(): readonly PythonProject[] {
return this.projectManager.getProjects();
}
onDidChangePythonProjects: Event<DidChangePythonProjectsEventArgs> = this._onDidChangePythonProjects.event;
getPythonProject(uri: Uri): PythonProject | undefined {
return this.projectManager.get(checkUri(uri) as Uri);
}
registerPythonProjectCreator(creator: PythonProjectCreator): Disposable {
return this.projectCreators.registerPythonProjectCreator(creator);
}
async createTerminal(environment: PythonEnvironment, options: PythonTerminalCreateOptions): Promise<Terminal> {
return this.terminalManager.create(environment, options);
}
async runInTerminal(environment: PythonEnvironment, options: PythonTerminalExecutionOptions): Promise<Terminal> {
const terminal = await this.terminalManager.getProjectTerminal(
options.cwd instanceof Uri ? options.cwd : Uri.file(options.cwd),
environment,
);
await runInTerminal(environment, terminal, options);
return terminal;
}
async runInDedicatedTerminal(
terminalKey: Uri | string,
environment: PythonEnvironment,
options: PythonTerminalExecutionOptions,
): Promise<Terminal> {
const terminal = await this.terminalManager.getDedicatedTerminal(
terminalKey,
options.cwd instanceof Uri ? options.cwd : Uri.file(options.cwd),
environment,
);
await runInTerminal(environment, terminal, options);
return Promise.resolve(terminal);
}
runAsTask(environment: PythonEnvironment, options: PythonTaskExecutionOptions): Promise<TaskExecution> {
return runAsTask(environment, options);
}
runInBackground(environment: PythonEnvironment, options: PythonBackgroundRunOptions): Promise<PythonProcess> {
return runInBackground(environment, options);
}
onDidChangeEnvironmentVariables: Event<DidChangeEnvironmentVariablesEventArgs> =
this._onDidChangeEnvironmentVariables.event;
getEnvironmentVariables(
uri: Uri,
overrides?: ({ [key: string]: string | undefined } | Uri)[],
baseEnvVar?: { [key: string]: string | undefined },
): Promise<{ [key: string]: string | undefined }> {
return this.envVarManager.getEnvironmentVariables(checkUri(uri) as Uri, overrides, baseEnvVar);
}
}
let _deferred = createDeferred<PythonEnvironmentApi>();
export function setPythonApi(
envMgr: EnvironmentManagers,
projectMgr: PythonProjectManager,
projectCreators: ProjectCreators,
terminalManager: TerminalManager,
envVarManager: EnvVarManager,
) {
_deferred.resolve(
new PythonEnvironmentApiImpl(envMgr, projectMgr, projectCreators, terminalManager, envVarManager),
);
}
export function getPythonApi(): Promise<PythonEnvironmentApi> {
return _deferred.promise;
}