Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@
"Pylance.proposePylanceMessage": "Try out a new faster, feature-rich language server for Python by Microsoft, Pylance! Install the extension now.",
"Pylance.tryItNow": "Try it now",
"Pylance.remindMeLater": "Remind me later",
"Pylance.installPylanceMessage": "Pylance extension is not installed. Click Yes to open Pylance installation page.",
"Pylance.pylanceNotInstalledMessage": "Pylance extension is not installed.",
"Pylance.pylanceInstalledReloadPromptMessage": "Pylance extension is now installed. Reload window to activate?",
"Pylance.pylanceRevertToJediPrompt": "The Pylance extension is not installed but the python.languageServer value is set to \"Pylance\". Would you like to install the Pylance extension to use Pylance, or revert back to Jedi?",
"Pylance.pylanceInstallPylance": "Install Pylance",
"Pylance.pylanceRevertToJedi": "Revert to Jedi",
"Experiments.inGroup": "User belongs to experiment group '{0}'",
"Experiments.optedOutOf": "User opted out of experiment group '{0}'",
"Interpreters.RefreshingInterpreters": "Refreshing Python Interpreters",
Expand Down
1 change: 0 additions & 1 deletion package.nls.ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"Pylance.proposePylanceMessage": "Попробуйте новый языковый сервер для Python от Microsoft: Pylance! Установите расширение Pylance.",
"Pylance.tryItNow": "Да, хочу",
"Pylance.remindMeLater": "Напомните позже",
"Pylance.installPylanceMessage": "Расширение Pylance не установлено. Нажмите Да чтобы открыть страницу установки Pylance.",
"Pylance.pylanceNotInstalledMessage": "Расширение Pylance не установлено.",
"Pylance.pylanceInstalledReloadPromptMessage": "Расширение Pylance установлено. Перезагрузить окно для его активации?",
"LanguageService.reloadAfterLanguageServerChange": "Пожалуйста, перезагрузите окно после смены типа языкового сервера."
Expand Down
1 change: 0 additions & 1 deletion package.nls.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"Pylance.proposePylanceMessage": "试试微软新的更快的、功能丰富的语言服务器 Pylance! ",
"Pylance.tryItNow": "立即安装",
"Pylance.remindMeLater": "稍后提醒",
"Pylance.installPylanceMessage": "Pylance 扩展未安装。点击 \"是\" 打开 Pylance 安装页面。",
"Pylance.pylanceNotInstalledMessage": "Pylance 扩展未安装。",
"Pylance.pylanceInstalledReloadPromptMessage": "Pylance 扩展未安装。重新加载窗口以激活?",
"Experiments.inGroup": "用户属于 '{0}' 实验组",
Expand Down
2 changes: 2 additions & 0 deletions src/client/activation/activationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export class LanguageServerExtensionActivationService
this.serviceContainer.get<IExtensions>(IExtensions),
this.serviceContainer.get<IApplicationShell>(IApplicationShell),
this.serviceContainer.get<ICommandManager>(ICommandManager),
this.serviceContainer.get<IWorkspaceService>(IWorkspaceService),
this.serviceContainer.get<IConfigurationService>(IConfigurationService),
);
disposables.push(this.languageServerChangeHandler);
}
Expand Down
2 changes: 1 addition & 1 deletion src/client/activation/common/activatorBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export abstract class LanguageServerActivatorBase implements ILanguageServerActi
protected resource?: Resource;
constructor(
protected readonly manager: ILanguageServerManager,
private readonly workspace: IWorkspaceService,
protected readonly workspace: IWorkspaceService,
protected readonly fs: IFileSystem,
protected readonly configurationService: IConfigurationService,
) {}
Expand Down
36 changes: 27 additions & 9 deletions src/client/activation/common/languageServerChangeHandler.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { Disposable } from 'vscode';
import { IApplicationShell, ICommandManager } from '../../common/application/types';
import { ConfigurationTarget, Disposable } from 'vscode';
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../common/application/types';
import { PYLANCE_EXTENSION_ID } from '../../common/constants';
import { IExtensions } from '../../common/types';
import { IConfigurationService, IExtensions } from '../../common/types';
import { createDeferred } from '../../common/utils/async';
import { Common, LanguageService, Pylance } from '../../common/utils/localize';
import { LanguageServerType } from '../types';

export async function promptForPylanceInstall(
appShell: IApplicationShell,
commandManager: ICommandManager,
workspace: IWorkspaceService,
configService: IConfigurationService,
): Promise<void> {
// If not installed, point user to Pylance at the store.
const response = await appShell.showWarningMessage(
Pylance.installPylanceMessage(),
Common.bannerLabelYes(),
Common.bannerLabelNo(),
Pylance.pylanceRevertToJediPrompt(),
Pylance.pylanceInstallPylance(),
Pylance.pylanceRevertToJedi(),
Pylance.remindMeLater(),
);

if (response === Common.bannerLabelYes()) {
if (response === Pylance.pylanceInstallPylance()) {
commandManager.executeCommand('extension.open', PYLANCE_EXTENSION_ID);
} else if (response === Pylance.pylanceRevertToJedi()) {
const inspection = workspace.getConfiguration('python').inspect<string>('languageServer');

let target: ConfigurationTarget | undefined;
if (inspection?.workspaceValue) {
target = ConfigurationTarget.Workspace;
} else if (inspection?.globalValue) {
target = ConfigurationTarget.Global;
}

if (target) {
await configService.updateSetting('languageServer', LanguageServerType.Jedi, undefined, target);
commandManager.executeCommand('workbench.action.reloadWindow');
}
}
}

Expand All @@ -37,6 +53,8 @@ export class LanguageServerChangeHandler implements Disposable {
private readonly extensions: IExtensions,
private readonly appShell: IApplicationShell,
private readonly commands: ICommandManager,
private readonly workspace: IWorkspaceService,
private readonly configService: IConfigurationService,
) {
this.pylanceInstalled = this.isPylanceInstalled();
this.disposables.push(
Expand Down Expand Up @@ -70,7 +88,7 @@ export class LanguageServerChangeHandler implements Disposable {
let response: string | undefined;
if (lsType === LanguageServerType.Node && !this.isPylanceInstalled()) {
// If not installed, point user to Pylance at the store.
await promptForPylanceInstall(this.appShell, this.commands);
await promptForPylanceInstall(this.appShell, this.commands, this.workspace, this.configService);
// At this point Pylance is not yet installed. Skip reload prompt
// since we are going to show it when Pylance becomes available.
} else {
Expand Down
7 changes: 6 additions & 1 deletion src/client/activation/node/activator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ export class NodeLanguageServerActivator extends LanguageServerActivatorBase {
// Pylance is not yet installed. Throw will cause activator to use Jedi
// temporarily. Language server installation tracker will prompt for window
// reload when Pylance becomes available.
await promptForPylanceInstall(this.appShell, this.commandManager);
await promptForPylanceInstall(
this.appShell,
this.commandManager,
this.workspace,
this.configurationService,
);
throw new Error(Pylance.pylanceNotInstalledMessage());
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,6 @@ export namespace Pylance {
export const tryItNow = localize('Pylance.tryItNow', 'Try it now');
export const remindMeLater = localize('Pylance.remindMeLater', 'Remind me later');

export const installPylanceMessage = localize(
'Pylance.installPylanceMessage',
'Pylance extension is not installed. Click Yes to open Pylance installation page.',
);
export const pylanceNotInstalledMessage = localize(
'Pylance.pylanceNotInstalledMessage',
'Pylance extension is not installed.',
Expand All @@ -126,6 +122,13 @@ export namespace Pylance {
'Pylance.pylanceInstalledReloadPromptMessage',
'Pylance extension is now installed. Reload window to activate?',
);

export const pylanceRevertToJediPrompt = localize(
'Pylance.pylanceRevertToJediPrompt',
'The Pylance extension is not installed but the python.languageServer value is set to "Pylance". Would you like to install the Pylance extension to use Pylance, or revert back to Jedi?',
);
export const pylanceInstallPylance = localize('Pylance.pylanceInstallPylance', 'Install Pylance');
export const pylanceRevertToJedi = localize('Pylance.pylanceRevertToJedi', 'Revert to Jedi');
}

export namespace Jupyter {
Expand Down
13 changes: 10 additions & 3 deletions src/client/extensionActivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,19 @@ export async function activateComponents(
// `Promise.all()`, etc.) will flatten nested promises. Thus
// activation resolves `ActivationResult`, which can safely wrap
// the "inner" promise.

// TODO: As of now activateLegacy() registers various classes which might
// be required while activating components. Once registration from
// activateLegacy() are moved before we activate other components, we can
// activate them parallelly with the other components.
// https://github.com/microsoft/vscode-python/issues/15380
// These will go away eventually once everything is refactored into components.
const legacyActivationResult = await activateLegacy(ext);
const promises: Promise<ActivationResult>[] = [
// More component activations will go here
pythonEnvironments.activate(components.pythonEnvs),
// These will go away eventually.
activateLegacy(ext),
];
return Promise.all(promises);
return Promise.all([legacyActivationResult, ...promises]);
}

/// //////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Event } from 'vscode';
import { getSearchPathEntries } from '../../../../common/utils/exec';
import { Disposables, IDisposable } from '../../../../common/utils/resourceLifecycle';
import { isStandardPythonBinary } from '../../../common/commonUtils';
import { isPyenvShimDir } from '../../../discovery/locators/services/pyenvLocator';
import { isWindowsStoreDir } from '../../../discovery/locators/services/windowsStoreLocator';
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from '../../info';
import { ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../../locator';
import { Locators } from '../../locators';
Expand All @@ -31,6 +33,17 @@ export class WindowsPathEnvVarLocator implements ILocator, IDisposable {

constructor() {
const dirLocators: (ILocator & IDisposable)[] = getSearchPathEntries()
.filter((dirname) => {
// Filter out following directories:
// 1. Windows Store app directories: We have a store app locator that handles this. The
// python.exe available in these directories might not be python. It can be a store
// install shortcut that takes you to windows store.
//
// 2. Filter out pyenv shims: They are not actual python binaries, they are used to launch
// the binaries specified in .python-version file in the cwd. We should not be reporting
// those binaries as environments.
return !isWindowsStoreDir(dirname) && !isPyenvShimDir(dirname);
})
// Build a locator for each directory.
.map((dirname) => getDirFilesLocator(dirname, PythonEnvKind.Unknown));
this.disposables.push(...dirLocators);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
import * as fs from 'fs';
import * as path from 'path';
import { traceError, traceInfo } from '../../../../common/logger';

import { Architecture } from '../../../../common/utils/platform';
import { PythonEnvInfo, PythonEnvKind, PythonEnvSource, PythonReleaseLevel, PythonVersion } from '../../../base/info';
import { buildEnvInfo } from '../../../base/info/env';
import { parseVersion } from '../../../base/info/pythonVersion';
import { IPythonEnvsIterator, Locator } from '../../../base/locator';
import { getFileInfo, resolveSymbolicLink } from '../../../common/externalDependencies';
import { commonPosixBinPaths, isPosixPythonBinPattern } from '../../../common/posixUtils';
import { isPyenvShimDir } from './pyenvLocator';

async function getPythonBinFromKnownPaths(): Promise<string[]> {
const knownDirs = await commonPosixBinPaths();
// Filter out pyenv shims. They are not actual python binaries, they are used to launch
// the binaries specified in .python-version file in the cwd. We should not be reporting
// those binaries as environments.
const knownDirs = (await commonPosixBinPaths()).filter((dirname) => !isPyenvShimDir(dirname));
const pythonBins: Set<string> = new Set();
for (const dirname of knownDirs) {
const paths = (await fs.promises.readdir(dirname, { withFileTypes: true }))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
getInterpreterPathFromDir,
getPythonVersionFromPath,
} from '../../../common/commonUtils';
import { getFileInfo, getSubDirs, pathExists } from '../../../common/externalDependencies';
import { arePathsSame, getFileInfo, getSubDirs, pathExists } from '../../../common/externalDependencies';

function getPyenvDir(): string {
// Check if the pyenv environment variables exist: PYENV on Windows, PYENV_ROOT on Unix.
Expand All @@ -37,6 +37,17 @@ function getPyenvVersionsDir(): string {
return path.join(getPyenvDir(), 'versions');
}

/**
* Checks if a given directory path is same as `pyenv` shims path. This checks
* `~/.pyenv/shims` on posix and `~/.pyenv/pyenv-win/shims` on windows.
* @param {string} dirPath: Absolute path to any directory
* @returns {boolean}: Returns true if the patch is same as `pyenv` shims directory.
*/
export function isPyenvShimDir(dirPath: string): boolean {
const shimPath = path.join(getPyenvDir(), 'shims');
return arePathsSame(shimPath, dirPath) || arePathsSame(`${shimPath}${path.sep}`, dirPath);
}

/**
* Checks if the given interpreter belongs to a pyenv based environment.
* @param {string} interpreterPath: Absolute path to the python interpreter.
Expand Down
Loading