11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License.
33
4- import { commands , Disposable , Event , EventEmitter , Uri } from 'vscode' ;
4+ import { commands , Disposable , Event , EventEmitter , extensions , Uri } from 'vscode' ;
55import { traceError , traceLog } from './logging' ;
66import { PythonExtension , ResolvedEnvironment } from '@vscode/python-extension' ;
7+ import * as semver from 'semver' ;
8+ import type { PythonEnvironment , PythonEnvironmentsAPI } from '../typings/pythonEnvironments' ;
79import { PYTHON_MAJOR , PYTHON_MINOR , PYTHON_VERSION } from './constants' ;
810import { getProjectRoot } from './utilities' ;
911
@@ -12,6 +14,35 @@ export interface IInterpreterDetails {
1214 resource ?: Uri ;
1315}
1416
17+ function convertToResolvedEnvironment ( environment : PythonEnvironment ) : ResolvedEnvironment | undefined {
18+ const runConfig = environment . execInfo ?. activatedRun ?? environment . execInfo ?. run ;
19+ const executable = runConfig ?. executable ;
20+ if ( ! executable ) {
21+ return undefined ;
22+ }
23+ const coerced = semver . coerce ( environment . version ) ;
24+ return {
25+ id : environment . envId ?. id ?? '' ,
26+ path : executable ,
27+ executable : {
28+ uri : Uri . file ( executable ) ,
29+ bitness : 'Unknown' ,
30+ sysPrefix : environment . sysPrefix ?? '' ,
31+ } ,
32+ version : coerced
33+ ? {
34+ major : coerced . major ,
35+ minor : coerced . minor ,
36+ micro : coerced . patch ,
37+ release : { level : 'final' , serial : 0 } ,
38+ sysVersion : environment . version ?? '' ,
39+ }
40+ : undefined ,
41+ environment : undefined ,
42+ tools : [ ] ,
43+ } as ResolvedEnvironment ;
44+ }
45+
1546const onDidChangePythonInterpreterEvent = new EventEmitter < void > ( ) ;
1647export const onDidChangePythonInterpreter : Event < void > = onDidChangePythonInterpreterEvent . event ;
1748
@@ -24,6 +55,34 @@ async function getPythonExtensionAPI(): Promise<PythonExtension | undefined> {
2455 return _api ;
2556}
2657
58+ const PYTHON_ENVIRONMENTS_EXTENSION_ID = 'ms-python.vscode-python-envs' ;
59+
60+ let _envsApi : PythonEnvironmentsAPI | undefined ;
61+ async function getEnvironmentsExtensionAPI ( ) : Promise < PythonEnvironmentsAPI | undefined > {
62+ if ( _envsApi ) {
63+ return _envsApi ;
64+ }
65+ const extension = extensions . getExtension ( PYTHON_ENVIRONMENTS_EXTENSION_ID ) ;
66+ if ( ! extension ) {
67+ return undefined ;
68+ }
69+ try {
70+ if ( ! extension . isActive ) {
71+ await extension . activate ( ) ;
72+ }
73+ const api = extension . exports ;
74+ if ( ! api ) {
75+ traceError ( 'Python environments extension did not provide any exports.' ) ;
76+ return undefined ;
77+ }
78+ _envsApi = api as PythonEnvironmentsAPI ;
79+ return _envsApi ;
80+ } catch ( ex ) {
81+ traceError ( 'Failed to activate or retrieve API from Python environments extension.' , ex as Error ) ;
82+ return undefined ;
83+ }
84+ }
85+
2786function sameInterpreter ( a : string [ ] , b : string [ ] ) : boolean {
2887 if ( a . length !== b . length ) {
2988 return false ;
@@ -63,6 +122,22 @@ async function refreshServerPython(): Promise<void> {
63122
64123export async function initializePython ( disposables : Disposable [ ] ) : Promise < void > {
65124 try {
125+ // Prefer the Python Environments extension if it's available, as it provides a more comprehensive view of the available environments.
126+ const envsApi = await getEnvironmentsExtensionAPI ( ) ;
127+
128+ if ( envsApi ) {
129+ disposables . push (
130+ envsApi . onDidChangeEnvironment ( async ( ) => {
131+ await refreshServerPython ( ) ;
132+ } ) ,
133+ ) ;
134+
135+ traceLog ( 'Waiting for interpreter from Python environments extension.' ) ;
136+ await refreshServerPython ( ) ;
137+ return ;
138+ }
139+
140+ // Fall back to legacy ms-python.python extension API
66141 const api = await getPythonExtensionAPI ( ) ;
67142
68143 if ( api ) {
@@ -80,12 +155,51 @@ export async function initializePython(disposables: Disposable[]): Promise<void>
80155 }
81156}
82157
158+ // TODO: Unused code
83159export async function resolveInterpreter ( interpreter : string [ ] ) : Promise < ResolvedEnvironment | undefined > {
160+ const envsApi = await getEnvironmentsExtensionAPI ( ) ;
161+ if ( envsApi ) {
162+ const environment = await envsApi . resolveEnvironment ( Uri . file ( interpreter [ 0 ] ) ) ;
163+ if ( ! environment ) {
164+ return undefined ;
165+ }
166+ return convertToResolvedEnvironment ( environment ) ;
167+ }
84168 const api = await getPythonExtensionAPI ( ) ;
85169 return api ?. environments . resolveEnvironment ( interpreter [ 0 ] ) ;
86170}
87171
88172export async function getInterpreterDetails ( resource ?: Uri ) : Promise < IInterpreterDetails > {
173+ // Prefer the Python Environments extension if it's available, as it provides a more comprehensive view of the available environments.
174+ const envsApi = await getEnvironmentsExtensionAPI ( ) ;
175+ if ( envsApi ) {
176+ try {
177+ const environment = await envsApi . getEnvironment ( resource ) ;
178+ if ( environment ) {
179+ const coerced = semver . coerce ( environment . version ) ;
180+ const runConfig = environment . execInfo ?. activatedRun ?? environment . execInfo ?. run ;
181+ const executable = runConfig ?. executable ;
182+ const args = runConfig ?. args ?? [ ] ;
183+ if ( coerced && coerced . major === PYTHON_MAJOR && coerced . minor >= PYTHON_MINOR ) {
184+ if ( executable ) {
185+ return { path : [ executable , ...args ] , resource } ;
186+ }
187+ traceError ( 'No executable found for selected Python environment.' ) ;
188+ return { path : undefined , resource } ;
189+ }
190+ traceError ( `Python version ${ environment . version } is not supported.` ) ;
191+ traceError ( `Selected python path: ${ runConfig ?. executable } ` ) ;
192+ traceError ( `Supported versions are ${ PYTHON_VERSION } and above.` ) ;
193+ return { path : undefined , resource } ;
194+ }
195+ // No environment found via envs API, fall through to legacy resolver.
196+ } catch ( error ) {
197+ traceError ( 'Error getting interpreter from Python environments extension: ' , error ) ;
198+ // Fall through to legacy resolver.
199+ }
200+ }
201+
202+ // Fall back to legacy ms-python.python extension API
89203 const api = await getPythonExtensionAPI ( ) ;
90204 const environment = await api ?. environments . resolveEnvironment (
91205 api ?. environments . getActiveEnvironmentPath ( resource ) ,
@@ -96,13 +210,18 @@ export async function getInterpreterDetails(resource?: Uri): Promise<IInterprete
96210 return { path : undefined , resource } ;
97211}
98212
213+ // TODO: The Python Environments extension does not expose a debug API yet; uses legacy ms-python.python
99214export async function getDebuggerPath ( ) : Promise < string | undefined > {
100215 const api = await getPythonExtensionAPI ( ) ;
101216 return api ?. debug . getDebuggerPackagePath ( ) ;
102217}
103218
219+ // TODO: Unused code
104220export async function runPythonExtensionCommand ( command : string , ...rest : unknown [ ] ) {
105- await getPythonExtensionAPI ( ) ;
221+ const envsApi = await getEnvironmentsExtensionAPI ( ) ;
222+ if ( ! envsApi ) {
223+ await getPythonExtensionAPI ( ) ;
224+ }
106225 return await commands . executeCommand ( command , ...rest ) ;
107226}
108227
0 commit comments