-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use env extension when available #24564
Changes from all commits
7962f55
80c9263
7904245
d45b9de
ae0f286
bf02054
19fd283
3fd115e
6be99d0
28c89f7
472c1c1
cc548e8
c86a5a2
01497c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,9 @@ import { | |
} from './types'; | ||
import { traceVerbose } from '../../logging'; | ||
import { getConfiguration } from '../vscodeApis/workspaceApis'; | ||
import { useEnvExtension } from '../../envExt/api.internal'; | ||
import { ensureTerminalLegacy } from '../../envExt/api.legacy'; | ||
import { sleep } from '../utils/async'; | ||
import { isWindows } from '../utils/platform'; | ||
|
||
@injectable() | ||
|
@@ -132,22 +135,29 @@ export class TerminalService implements ITerminalService, Disposable { | |
if (this.terminal) { | ||
return; | ||
} | ||
this.terminalShellType = this.terminalHelper.identifyTerminalShell(this.terminal); | ||
this.terminal = this.terminalManager.createTerminal({ | ||
name: this.options?.title || 'Python', | ||
hideFromUser: this.options?.hideFromUser, | ||
}); | ||
this.terminalAutoActivator.disableAutoActivation(this.terminal); | ||
|
||
// Sometimes the terminal takes some time to start up before it can start accepting input. | ||
await new Promise((resolve) => setTimeout(resolve, 100)); | ||
if (useEnvExtension()) { | ||
this.terminal = await ensureTerminalLegacy(this.options?.resource, { | ||
name: this.options?.title || 'Python', | ||
hideFromUser: this.options?.hideFromUser, | ||
}); | ||
} else { | ||
this.terminalShellType = this.terminalHelper.identifyTerminalShell(this.terminal); | ||
this.terminal = this.terminalManager.createTerminal({ | ||
name: this.options?.title || 'Python', | ||
hideFromUser: this.options?.hideFromUser, | ||
}); | ||
this.terminalAutoActivator.disableAutoActivation(this.terminal); | ||
|
||
await sleep(100); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there reason why we wait for short duration before activateEnvironmentInTerminal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is also from the existing activation. This is to give shell initialization enough time to start. |
||
|
||
await this.terminalActivator.activateEnvironmentInTerminal(this.terminal, { | ||
resource: this.options?.resource, | ||
preserveFocus, | ||
interpreter: this.options?.interpreter, | ||
hideFromUser: this.options?.hideFromUser, | ||
}); | ||
await this.terminalActivator.activateEnvironmentInTerminal(this.terminal, { | ||
resource: this.options?.resource, | ||
preserveFocus, | ||
interpreter: this.options?.interpreter, | ||
hideFromUser: this.options?.hideFromUser, | ||
}); | ||
} | ||
|
||
if (!this.options?.hideFromUser) { | ||
this.terminal.show(preserveFocus); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { Terminal, Uri } from 'vscode'; | ||
import { getExtension } from '../common/vscodeApis/extensionsApi'; | ||
import { | ||
GetEnvironmentScope, | ||
PythonBackgroundRunOptions, | ||
PythonEnvironment, | ||
PythonEnvironmentApi, | ||
PythonProcess, | ||
RefreshEnvironmentsScope, | ||
} from './types'; | ||
import { executeCommand } from '../common/vscodeApis/commandApis'; | ||
|
||
export const ENVS_EXTENSION_ID = 'ms-python.vscode-python-envs'; | ||
|
||
let _useExt: boolean | undefined; | ||
export function useEnvExtension(): boolean { | ||
if (_useExt !== undefined) { | ||
return _useExt; | ||
} | ||
_useExt = !!getExtension(ENVS_EXTENSION_ID); | ||
return _useExt; | ||
} | ||
|
||
let _extApi: PythonEnvironmentApi | undefined; | ||
export async function getEnvExtApi(): Promise<PythonEnvironmentApi> { | ||
if (_extApi) { | ||
return _extApi; | ||
} | ||
const extension = getExtension(ENVS_EXTENSION_ID); | ||
if (!extension) { | ||
throw new Error('Python Environments extension not found.'); | ||
} | ||
if (extension?.isActive) { | ||
_extApi = extension.exports as PythonEnvironmentApi; | ||
return _extApi; | ||
} | ||
|
||
await extension.activate(); | ||
|
||
_extApi = extension.exports as PythonEnvironmentApi; | ||
return _extApi; | ||
} | ||
|
||
export async function runInBackground( | ||
environment: PythonEnvironment, | ||
options: PythonBackgroundRunOptions, | ||
): Promise<PythonProcess> { | ||
const envExtApi = await getEnvExtApi(); | ||
return envExtApi.runInBackground(environment, options); | ||
} | ||
|
||
export async function getEnvironment(scope: GetEnvironmentScope): Promise<PythonEnvironment | undefined> { | ||
const envExtApi = await getEnvExtApi(); | ||
return envExtApi.getEnvironment(scope); | ||
} | ||
|
||
export async function refreshEnvironments(scope: RefreshEnvironmentsScope): Promise<void> { | ||
const envExtApi = await getEnvExtApi(); | ||
return envExtApi.refreshEnvironments(scope); | ||
} | ||
|
||
export async function runInTerminal( | ||
resource: Uri | undefined, | ||
args?: string[], | ||
cwd?: string | Uri, | ||
show?: boolean, | ||
): Promise<Terminal> { | ||
const envExtApi = await getEnvExtApi(); | ||
const env = await getEnvironment(resource); | ||
const project = resource ? envExtApi.getPythonProject(resource) : undefined; | ||
if (env && resource) { | ||
return envExtApi.runInTerminal(env, { | ||
cwd: cwd ?? project?.uri ?? process.cwd(), | ||
args, | ||
show, | ||
}); | ||
} | ||
throw new Error('Invalid arguments to run in terminal'); | ||
} | ||
|
||
export async function runInDedicatedTerminal( | ||
resource: Uri | undefined, | ||
args?: string[], | ||
cwd?: string | Uri, | ||
show?: boolean, | ||
): Promise<Terminal> { | ||
const envExtApi = await getEnvExtApi(); | ||
const env = await getEnvironment(resource); | ||
const project = resource ? envExtApi.getPythonProject(resource) : undefined; | ||
if (env) { | ||
return envExtApi.runInDedicatedTerminal(resource ?? 'global', env, { | ||
cwd: cwd ?? project?.uri ?? process.cwd(), | ||
args, | ||
show, | ||
}); | ||
} | ||
throw new Error('Invalid arguments to run in dedicated terminal'); | ||
} | ||
|
||
export async function clearCache(): Promise<void> { | ||
const envExtApi = await getEnvExtApi(); | ||
if (envExtApi) { | ||
await executeCommand('python-envs.clearCache'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { Terminal, Uri } from 'vscode'; | ||
import { getEnvExtApi, getEnvironment } from './api.internal'; | ||
import { EnvironmentType, PythonEnvironment as PythonEnvironmentLegacy } from '../pythonEnvironments/info'; | ||
import { PythonEnvironment, PythonTerminalOptions } from './types'; | ||
import { Architecture } from '../common/utils/platform'; | ||
import { parseVersion } from '../pythonEnvironments/base/info/pythonVersion'; | ||
import { PythonEnvType } from '../pythonEnvironments/base/info'; | ||
import { traceError, traceInfo } from '../logging'; | ||
import { reportActiveInterpreterChanged } from '../environmentApi'; | ||
import { getWorkspaceFolder } from '../common/vscodeApis/workspaceApis'; | ||
|
||
function toEnvironmentType(pythonEnv: PythonEnvironment): EnvironmentType { | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('system')) { | ||
return EnvironmentType.System; | ||
} | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('venv')) { | ||
return EnvironmentType.Venv; | ||
} | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('virtualenv')) { | ||
return EnvironmentType.VirtualEnv; | ||
} | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('conda')) { | ||
return EnvironmentType.Conda; | ||
} | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('pipenv')) { | ||
return EnvironmentType.Pipenv; | ||
} | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('poetry')) { | ||
return EnvironmentType.Poetry; | ||
} | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('pyenv')) { | ||
return EnvironmentType.Pyenv; | ||
} | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('hatch')) { | ||
return EnvironmentType.Hatch; | ||
} | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('pixi')) { | ||
return EnvironmentType.Pixi; | ||
} | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('virtualenvwrapper')) { | ||
return EnvironmentType.VirtualEnvWrapper; | ||
} | ||
if (pythonEnv.envId.managerId.toLowerCase().endsWith('activestate')) { | ||
return EnvironmentType.ActiveState; | ||
} | ||
return EnvironmentType.Unknown; | ||
} | ||
|
||
function getEnvType(kind: EnvironmentType): PythonEnvType | undefined { | ||
switch (kind) { | ||
case EnvironmentType.Pipenv: | ||
case EnvironmentType.VirtualEnv: | ||
case EnvironmentType.Pyenv: | ||
case EnvironmentType.Venv: | ||
case EnvironmentType.Poetry: | ||
case EnvironmentType.Hatch: | ||
case EnvironmentType.Pixi: | ||
case EnvironmentType.VirtualEnvWrapper: | ||
case EnvironmentType.ActiveState: | ||
return PythonEnvType.Virtual; | ||
|
||
case EnvironmentType.Conda: | ||
return PythonEnvType.Conda; | ||
|
||
case EnvironmentType.MicrosoftStore: | ||
case EnvironmentType.Global: | ||
case EnvironmentType.System: | ||
default: | ||
return undefined; | ||
} | ||
} | ||
|
||
function toLegacyType(env: PythonEnvironment): PythonEnvironmentLegacy { | ||
const ver = parseVersion(env.version); | ||
const envType = toEnvironmentType(env); | ||
return { | ||
id: env.environmentPath.fsPath, | ||
displayName: env.displayName, | ||
detailedDisplayName: env.name, | ||
envType, | ||
envPath: env.sysPrefix, | ||
type: getEnvType(envType), | ||
path: env.environmentPath.fsPath, | ||
version: { | ||
raw: env.version, | ||
major: ver.major, | ||
minor: ver.minor, | ||
patch: ver.micro, | ||
build: [], | ||
prerelease: [], | ||
}, | ||
sysVersion: env.version, | ||
architecture: Architecture.x64, | ||
sysPrefix: env.sysPrefix, | ||
}; | ||
} | ||
|
||
const previousEnvMap = new Map<string, PythonEnvironment | undefined>(); | ||
export async function getActiveInterpreterLegacy(resource?: Uri): Promise<PythonEnvironmentLegacy | undefined> { | ||
const api = await getEnvExtApi(); | ||
const uri = resource ? api.getPythonProject(resource)?.uri : undefined; | ||
|
||
const pythonEnv = await getEnvironment(resource); | ||
const oldEnv = previousEnvMap.get(uri?.fsPath || ''); | ||
const newEnv = pythonEnv ? toLegacyType(pythonEnv) : undefined; | ||
if (newEnv && oldEnv?.envId.id !== pythonEnv?.envId.id) { | ||
reportActiveInterpreterChanged({ | ||
resource: getWorkspaceFolder(resource), | ||
path: newEnv.path, | ||
}); | ||
} | ||
return pythonEnv ? toLegacyType(pythonEnv) : undefined; | ||
} | ||
|
||
export async function ensureEnvironmentContainsPythonLegacy(pythonPath: string): Promise<void> { | ||
const api = await getEnvExtApi(); | ||
const pythonEnv = await api.resolveEnvironment(Uri.file(pythonPath)); | ||
if (!pythonEnv) { | ||
traceError(`EnvExt: Failed to resolve environment for ${pythonPath}`); | ||
return; | ||
} | ||
|
||
const envType = toEnvironmentType(pythonEnv); | ||
if (envType === EnvironmentType.Conda) { | ||
const packages = await api.getPackages(pythonEnv); | ||
if (packages && packages.length > 0 && packages.some((pkg) => pkg.name.toLowerCase() === 'python')) { | ||
return; | ||
} | ||
traceInfo(`EnvExt: Python not found in ${envType} environment ${pythonPath}`); | ||
traceInfo(`EnvExt: Installing Python in ${envType} environment ${pythonPath}`); | ||
await api.installPackages(pythonEnv, ['python']); | ||
} | ||
} | ||
|
||
export async function setInterpreterLegacy(pythonPath: string, uri: Uri | undefined): Promise<void> { | ||
const api = await getEnvExtApi(); | ||
const pythonEnv = await api.resolveEnvironment(Uri.file(pythonPath)); | ||
if (!pythonEnv) { | ||
traceError(`EnvExt: Failed to resolve environment for ${pythonPath}`); | ||
return; | ||
} | ||
await api.setEnvironment(uri, pythonEnv); | ||
} | ||
|
||
export async function resetInterpreterLegacy(uri: Uri | undefined): Promise<void> { | ||
const api = await getEnvExtApi(); | ||
await api.setEnvironment(uri, undefined); | ||
} | ||
|
||
export async function ensureTerminalLegacy( | ||
resource: Uri | undefined, | ||
options?: PythonTerminalOptions, | ||
): Promise<Terminal> { | ||
const api = await getEnvExtApi(); | ||
const pythonEnv = await api.getEnvironment(resource); | ||
const project = resource ? api.getPythonProject(resource) : undefined; | ||
|
||
if (pythonEnv && project) { | ||
const fixedOptions = options ? { ...options } : { cwd: project.uri }; | ||
const terminal = await api.createTerminal(pythonEnv, fixedOptions); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it seems like this is calling createTerminal from env extension API, Im trying to understand why this and the interpreter one are both called "legacy" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because it is temporary solution to the old features in the Python extension. These apis can be deleted when we delete the code for old Python features. |
||
return terminal; | ||
} | ||
throw new Error('Invalid arguments to create terminal'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this isn't 100% accurate in terms of always getting the shell type right, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not, this is the same detector as the one Python extension uses. We need the shell type API to be more accurate.