diff --git a/package-lock.json b/package-lock.json index 41c5c2e..b4043af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@types/fs-extra": "^9.0.13", "@types/glob": "^8.0.0", "@types/node": "14.x", - "@types/vscode": "1.64.0", + "@types/vscode": "1.71.0", "@typescript-eslint/eslint-plugin": "^5.39.0", "@typescript-eslint/parser": "^5.39.0", "@vscode/test-electron": "^2.1.5", @@ -30,7 +30,7 @@ "webpack-cli": "^4.10.0" }, "engines": { - "vscode": "^1.64.0" + "vscode": "^1.72.0" } }, "node_modules/@discoveryjs/json-ext": { @@ -274,9 +274,9 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.64.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.64.0.tgz", - "integrity": "sha512-bSlAWz5WtcSL3cO9tAT/KpEH9rv5OBnm93OIIFwdCshaAiqr2bp1AUyEwW9MWeCvZBHEXc3V0fTYVdVyzDNwHA==", + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.71.0.tgz", + "integrity": "sha512-nB50bBC9H/x2CpwW9FzRRRDrTZ7G0/POttJojvN/LiVfzTGfLyQIje1L1QRMdFXK9G41k5UJN/1B9S4of7CSzA==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -4514,9 +4514,9 @@ "dev": true }, "@types/vscode": { - "version": "1.64.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.64.0.tgz", - "integrity": "sha512-bSlAWz5WtcSL3cO9tAT/KpEH9rv5OBnm93OIIFwdCshaAiqr2bp1AUyEwW9MWeCvZBHEXc3V0fTYVdVyzDNwHA==", + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.71.0.tgz", + "integrity": "sha512-nB50bBC9H/x2CpwW9FzRRRDrTZ7G0/POttJojvN/LiVfzTGfLyQIje1L1QRMdFXK9G41k5UJN/1B9S4of7CSzA==", "dev": true }, "@typescript-eslint/eslint-plugin": { diff --git a/package.json b/package.json index cef941c..4247fde 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "" ], "engines": { - "vscode": "^1.64.0" + "vscode": "^1.72.0" }, "categories": [ "Programming Languages", @@ -147,7 +147,7 @@ }, "devDependencies": { "@types/fs-extra": "^9.0.13", - "@types/vscode": "1.64.0", + "@types/vscode": "1.71.0", "@types/glob": "^8.0.0", "@types/node": "14.x", "@typescript-eslint/eslint-plugin": "^5.39.0", diff --git a/src/common/python.ts b/src/common/python.ts index 8b3b454..ee7658a 100644 --- a/src/common/python.ts +++ b/src/common/python.ts @@ -2,58 +2,201 @@ // Licensed under the MIT License. /* eslint-disable @typescript-eslint/naming-convention */ -import { commands, Disposable, Event, EventEmitter, extensions, Uri } from 'vscode'; +import { commands, Disposable, Event, EventEmitter, extensions, Uri, WorkspaceFolder } from 'vscode'; import { traceError, traceLog } from './log/logging'; -export enum PythonEnvKind { - Unknown = 'unknown', - // "global" - System = 'global-system', - WindowsStore = 'global-windows-store', - Pyenv = 'global-pyenv', - Poetry = 'poetry', - Custom = 'global-custom', - OtherGlobal = 'global-other', - // "virtual" - Venv = 'virt-venv', - VirtualEnv = 'virt-virtualenv', - VirtualEnvWrapper = 'virt-virtualenvwrapper', - Pipenv = 'virt-pipenv', - Conda = 'virt-conda', - OtherVirtual = 'virt-other', -} -export interface EnvPathType { +type Environment = EnvironmentPath & { /** - * Path to environment folder or path to interpreter that uniquely identifies an environment. - * Virtual environments lacking an interpreter are identified by environment folder paths, - * whereas other envs can be identified using interpreter path. + * Carries details about python executable. */ - path: string; - pathType: 'envFolderPath' | 'interpreterPath'; -} + readonly executable: { + /** + * Uri of the python interpreter/executable. Carries `undefined` in case an executable does not belong to + * the environment. + */ + readonly uri: Uri | undefined; + /** + * Bitness if known at this moment. + */ + readonly bitness: Bitness | undefined; + /** + * Value of `sys.prefix` in sys module if known at this moment. + */ + readonly sysPrefix: string | undefined; + }; + /** + * Carries details if it is an environment, otherwise `undefined` in case of global interpreters and others. + */ + readonly environment: + | { + /** + * Type of the environment. + */ + readonly type: EnvironmentType; + /** + * Name to the environment if any. + */ + readonly name: string | undefined; + /** + * Uri of the environment folder. + */ + readonly folderUri: Uri; + /** + * Any specific workspace folder this environment is created for. + */ + readonly workspaceFolder: Uri | undefined; + } + | undefined; + /** + * Carries Python version information known at this moment. + */ + readonly version: VersionInfo & { + /** + * Value of `sys.version` in sys module if known at this moment. + */ + readonly sysVersion: string | undefined; + }; + /** + * Tools/plugins which created the environment or where it came from. First value in array corresponds + * to the primary tool which manages the environment, which never changes over time. + * + * Array is empty if no tool is responsible for creating/managing the environment. Usually the case for + * global interpreters. + */ + readonly tools: readonly EnvironmentTools[]; +}; -export interface EnvironmentDetailsOptions { - useCache: boolean; -} +/** + * Derived form of {@link Environment} where certain properties can no longer be `undefined`. Meant to represent an + * {@link Environment} with complete information. + */ +type ResolvedEnvironment = Environment & { + /** + * Carries complete details about python executable. + */ + readonly executable: { + /** + * Uri of the python interpreter/executable. Carries `undefined` in case an executable does not belong to + * the environment. + */ + readonly uri: Uri | undefined; + /** + * Bitness of the environment. + */ + readonly bitness: Bitness; + /** + * Value of `sys.prefix` in sys module. + */ + readonly sysPrefix: string; + }; + /** + * Carries complete Python version information. + */ + readonly version: ResolvedVersionInfo & { + /** + * Value of `sys.version` in sys module if known at this moment. + */ + readonly sysVersion: string; + }; +}; -export interface EnvironmentDetails { - interpreterPath: string; - envFolderPath?: string; - version: string[]; - environmentType: PythonEnvKind[]; - metadata: Record; -} +type EnvironmentsChangeEvent = { + readonly env: Environment; + /** + * * "add": New environment is added. + * * "remove": Existing environment in the list is removed. + * * "update": New information found about existing environment. + */ + readonly type: 'add' | 'remove' | 'update'; +}; -export interface ActiveEnvironmentChangedParams { +type ActiveEnvironmentPathChangeEvent = EnvironmentPath & { /** - * Path to environment folder or path to interpreter that uniquely identifies an environment. - * Virtual environments lacking an interpreter are identified by environment folder paths, - * whereas other envs can be identified using interpreter path. + * Workspace folder the environment changed for. */ - path: string; - resource?: Uri; -} + readonly resource: WorkspaceFolder | undefined; +}; + +/** + * Uri of a file inside a workspace or workspace folder itself. + */ +type Resource = Uri | WorkspaceFolder; + +type EnvironmentPath = { + /** + * The ID of the environment. + */ + readonly id: string; + /** + * Path to environment folder or path to python executable that uniquely identifies an environment. Environments + * lacking a python executable are identified by environment folder paths, whereas other envs can be identified + * using python executable path. + */ + readonly path: string; +}; + +/** + * Tool/plugin where the environment came from. It can be {@link KnownEnvironmentTools} or custom string which + * was contributed. + */ +type EnvironmentTools = KnownEnvironmentTools | string; +/** + * Tools or plugins the Python extension currently has built-in support for. Note this list is expected to shrink + * once tools have their own separate extensions. + */ +type KnownEnvironmentTools = + | 'Conda' + | 'Pipenv' + | 'Poetry' + | 'VirtualEnv' + | 'Venv' + | 'VirtualEnvWrapper' + | 'Pyenv' + | 'Unknown'; + +/** + * Type of the environment. It can be {@link KnownEnvironmentTypes} or custom string which was contributed. + */ +type EnvironmentType = KnownEnvironmentTypes | string; +/** + * Environment types the Python extension is aware of. Note this list is expected to shrink once tools have their + * own separate extensions, in which case they're expected to provide the type themselves. + */ +type KnownEnvironmentTypes = 'VirtualEnvironment' | 'Conda' | 'Unknown'; + +/** + * Carries bitness for an environment. + */ +type Bitness = '64-bit' | '32-bit' | 'Unknown'; + +/** + * The possible Python release levels. + */ +type PythonReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final'; + +/** + * Release information for a Python version. + */ +type PythonVersionRelease = { + readonly level: PythonReleaseLevel; + readonly serial: number; +}; + +type VersionInfo = { + readonly major: number | undefined; + readonly minor: number | undefined; + readonly micro: number | undefined; + readonly release: PythonVersionRelease | undefined; +}; + +type ResolvedVersionInfo = { + readonly major: number; + readonly minor: number; + readonly micro: number; + readonly release: PythonVersionRelease; +}; + interface IExtensionApi { ready: Promise; @@ -61,15 +204,12 @@ interface IExtensionApi { getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean): Promise; getDebuggerPackagePath(): Promise; }; - settings: { - readonly onDidChangeExecutionDetails: Event; - getExecutionDetails(resource?: Uri | undefined): { - execCommand: string[] | undefined; - }; - }; - environment: { - getActiveEnvironmentPath(resource?: Uri | undefined): Promise; - onDidActiveEnvironmentChanged: Event; + environments: { + getActiveEnvironmentPath(resource?: Resource): EnvironmentPath; + resolveEnvironment( + environment: Environment | EnvironmentPath | string, + ): Promise; + readonly onDidChangeActiveEnvironmentPath: Event; }; } @@ -102,8 +242,8 @@ export async function initializePython(disposables: Disposable[]): Promise if (api) { disposables.push( - api.environment.onDidActiveEnvironmentChanged((e) => { - onDidChangePythonInterpreterEvent.fire({ path: [e.path], resource: e.resource }); + api.environments.onDidChangeActiveEnvironmentPath((e) => { + onDidChangePythonInterpreterEvent.fire({ path: [e.path], resource: e.resource?.uri }); }), ); @@ -117,9 +257,9 @@ export async function initializePython(disposables: Disposable[]): Promise export async function getInterpreterDetails(resource?: Uri): Promise { const api = await getPythonExtensionAPI(); - const interpreter = await api?.environment.getActiveEnvironmentPath(resource); - if (interpreter && interpreter.pathType === 'interpreterPath') { - return { path: [interpreter.path], resource }; + const environment = await api?.environments.resolveEnvironment(api?.environments.getActiveEnvironmentPath(resource)); + if (environment?.executable.uri) { + return { path: [environment?.executable.uri.fsPath], resource }; } return { path: undefined, resource }; }