11/* eslint-disable comma-dangle */
22
3- /* eslint-disable implicit-arrow-linebreak */
3+ /* eslint-disable implicit-arrow-linebreak, max-classes-per-file */
44// Copyright (c) Microsoft Corporation. All rights reserved.
55// Licensed under the MIT License.
66
77import { inject , injectable , named } from 'inversify' ;
88import { dirname } from 'path' ;
9- import { Extension , Memento , Uri } from 'vscode' ;
9+ import { EventEmitter , Extension , Memento , Uri , workspace , Event } from 'vscode' ;
1010import type { SemVer } from 'semver' ;
1111import { IContextKeyManager , IWorkspaceService } from '../common/application/types' ;
1212import { JUPYTER_EXTENSION_ID , PYLANCE_EXTENSION_ID } from '../common/constants' ;
@@ -23,6 +23,7 @@ import { PylanceApi } from '../activation/node/pylanceApi';
2323import { ExtensionContextKey } from '../common/application/contextKeys' ;
2424import { getDebugpyPath } from '../debugger/pythonDebugger' ;
2525import type { Environment } from '../api/types' ;
26+ import { DisposableBase } from '../common/utils/resourceLifecycle' ;
2627
2728type PythonApiForJupyterExtension = {
2829 /**
@@ -170,3 +171,108 @@ export class JupyterExtensionIntegration {
170171 }
171172 }
172173}
174+
175+ export interface JupyterPythonEnvironmentApi {
176+ /**
177+ * This event is triggered when the environment associated with a Jupyter Notebook or Interactive Window changes.
178+ * The Uri in the event is the Uri of the Notebook/IW.
179+ */
180+ onDidChangePythonEnvironment ?: Event < Uri > ;
181+ /**
182+ * Returns the EnvironmentPath to the Python environment associated with a Jupyter Notebook or Interactive Window.
183+ * If the Uri is not associated with a Jupyter Notebook or Interactive Window, then this method returns undefined.
184+ * @param uri
185+ */
186+ getPythonEnvironment ?(
187+ uri : Uri ,
188+ ) :
189+ | undefined
190+ | {
191+ /**
192+ * The ID of the environment.
193+ */
194+ readonly id : string ;
195+ /**
196+ * Path to environment folder or path to python executable that uniquely identifies an environment. Environments
197+ * lacking a python executable are identified by environment folder paths, whereas other envs can be identified
198+ * using python executable path.
199+ */
200+ readonly path : string ;
201+ } ;
202+ }
203+
204+ @injectable ( )
205+ export class JupyterExtensionPythonEnvironments extends DisposableBase implements JupyterPythonEnvironmentApi {
206+ private jupyterExtension ?: JupyterPythonEnvironmentApi ;
207+
208+ private readonly _onDidChangePythonEnvironment = this . _register ( new EventEmitter < Uri > ( ) ) ;
209+
210+ public readonly onDidChangePythonEnvironment = this . _onDidChangePythonEnvironment . event ;
211+
212+ constructor ( @inject ( IExtensions ) private readonly extensions : IExtensions ) {
213+ super ( ) ;
214+ }
215+
216+ public getPythonEnvironment (
217+ uri : Uri ,
218+ ) :
219+ | undefined
220+ | {
221+ /**
222+ * The ID of the environment.
223+ */
224+ readonly id : string ;
225+ /**
226+ * Path to environment folder or path to python executable that uniquely identifies an environment. Environments
227+ * lacking a python executable are identified by environment folder paths, whereas other envs can be identified
228+ * using python executable path.
229+ */
230+ readonly path : string ;
231+ } {
232+ if ( ! isJupyterResource ( uri ) ) {
233+ return undefined ;
234+ }
235+ const api = this . getJupyterApi ( ) ;
236+ if ( api ?. getPythonEnvironment ) {
237+ return api . getPythonEnvironment ( uri ) ;
238+ }
239+ return undefined ;
240+ }
241+
242+ private getJupyterApi ( ) {
243+ if ( ! this . jupyterExtension ) {
244+ const ext = this . extensions . getExtension < JupyterPythonEnvironmentApi > ( JUPYTER_EXTENSION_ID ) ;
245+ if ( ! ext ) {
246+ return undefined ;
247+ }
248+ if ( ! ext . isActive ) {
249+ ext . activate ( ) . then ( ( ) => {
250+ this . hookupOnDidChangePythonEnvironment ( ext . exports ) ;
251+ } ) ;
252+ return undefined ;
253+ }
254+ this . hookupOnDidChangePythonEnvironment ( ext . exports ) ;
255+ }
256+ return this . jupyterExtension ;
257+ }
258+
259+ private hookupOnDidChangePythonEnvironment ( api : JupyterPythonEnvironmentApi ) {
260+ this . jupyterExtension = api ;
261+ if ( api . onDidChangePythonEnvironment ) {
262+ this . _register (
263+ api . onDidChangePythonEnvironment (
264+ this . _onDidChangePythonEnvironment . fire ,
265+ this . _onDidChangePythonEnvironment ,
266+ ) ,
267+ ) ;
268+ }
269+ }
270+ }
271+
272+ function isJupyterResource ( resource : Uri ) : boolean {
273+ // Jupyter extension only deals with Notebooks and Interactive Windows.
274+ return (
275+ resource . fsPath . endsWith ( '.ipynb' ) ||
276+ workspace . notebookDocuments . some ( ( item ) => item . uri . toString ( ) === resource . toString ( ) )
277+ ) ;
278+ }
0 commit comments