Skip to content

Commit

Permalink
Merge default-language-server into main (#16142)
Browse files Browse the repository at this point in the history
* Bundle Pylance as part of an extension pack (not a hard dependency) (#16077)

   * extension pack
   * Undo package.json change
   * Add extension pack link at build time
   * Forgot to update all names

* Update readme + license at build time (#16134)

* Add license update

* Update package.json description

* Update readme

* Add news file

* Update header wording

* let -> const

* Wording

* Consistenly use VS Code long form

* Display an informational Pylance prompt for existing users (#16069)

* Export extension version memento

* Add localization strings

* Add prompt check

* Add to the list of things triggering on activation

* Add tests

* Rename file

* Remove unsupported newlines

* Fix localization + re-add newlines

* Change to be a non-blocking diagnostic check

* Change localization key

* Update memento on close instead of just on ok

* Fix localization

* Add initialMementoValue handler

* Links

* Fix tests

* Fix tests

* Set PYLANCE_PROMPT_MEMENTO to false

* Remove unused line

* Set to true directly

* Remove extra updateMemento calls

* I can't read

* Period

* Run in foreground

* Add handling test

* Remove extension pack category (#16149)

Co-authored-by: Jake Bailey <[email protected]>
  • Loading branch information
kimadeline and jakebailey authored May 5, 2021
1 parent 8be9f83 commit 3e79ef2
Show file tree
Hide file tree
Showing 16 changed files with 320 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .github/actions/build-vsix/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,8 @@ runs:
- run: npm run addExtensionDependencies
shell: bash

- run: npm run addExtensionPackDependencies
shell: bash

- run: npm run package
shell: bash
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Python extension for Visual Studio Code

A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) with rich support for the [Python language](https://www.python.org/) (for all [actively supported versions](https://devguide.python.org/#status-of-python-branches) of the language: >=3.6), including features such as IntelliSense, linting, debugging, code navigation, code formatting, refactoring, variable explorer, test explorer, and more!
A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) with rich support for the [Python language](https://www.python.org/) (for all [actively supported versions](https://devguide.python.org/#status-of-python-branches) of the language: >=3.6), including features such as IntelliSense (Pylance), linting, debugging, code navigation, code formatting, refactoring, variable explorer, test explorer, and more!

Additionally, the Python extension gives you an optimal and feature-rich experience for working with Jupyter notebooks through the [Jupyter extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter).
## Installed extensions

The Python extension will automatically install the [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) and [Jupyter](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter) extensions to give you the best experience when working with Python files and Jupyter notebooks. However, Pylance is an optional dependency, meaning the Python extension will remain fully functional if it fails to be installed. You can also [uninstall](https://code.visualstudio.com/docs/editor/extension-marketplace#_uninstall-an-extension) it at the expense of some features if you’re using a different language server.

Extensions installed through the marketplace are subject to the [Marketplace Terms of Use](https://cdn.vsassets.io/v/M146_20190123.39/_content/Microsoft-Visual-Studio-Marketplace-Terms-of-Use.pdf).

## Quick start

Expand Down
5 changes: 5 additions & 0 deletions build/license-header.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
PLEASE NOTE: This Python extension for Visual Studio Code has a hard dependency on the Jupyter extension for Visual Studio Code which is installed automatically alongside it. The Python extension for Visual Studio Code also holds an optional dependency on the Pylance extension for Visual Studio Code, which is also installed automatically but is separately licensed.

All the source code for the Python extension for Visual Studio Code is available under the MIT License (given below) as is the source code for the Jupyter extension for Visual Studio Code. But the optional Pylance extension for Visual Studio Code is only available in binary form and it is not licensed under the MIT License. The Pylance extension for Visual Studio Code is licensed under a Microsoft proprietary license, the terms of which are available here: https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license.

------------------------------------------------------------------------------
24 changes: 24 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ gulp.task('addExtensionDependencies', async () => {
await addExtensionDependencies();
});

gulp.task('addExtensionPackDependencies', async () => {
await buildLicense();
await addExtensionPackDependencies();
});

async function addExtensionDependencies() {
// Update the package.json to add extension dependencies at build time so that
// extension dependencies need not be installed during development
Expand All @@ -92,6 +97,25 @@ async function addExtensionDependencies() {
await fsExtra.writeFile('package.json', JSON.stringify(packageJson, null, 4), 'utf-8');
}

async function addExtensionPackDependencies() {
// Update the package.json to add extension pack dependencies at build time so that
// extension dependencies need not be installed during development
const packageJsonContents = await fsExtra.readFile('package.json', 'utf-8');
const packageJson = JSON.parse(packageJsonContents);
packageJson.extensionPack = ['ms-python.vscode-pylance'].concat(
packageJson.extensionPack ? packageJson.extensionPack : [],
);
await fsExtra.writeFile('package.json', JSON.stringify(packageJson, null, 4), 'utf-8');
}

async function buildLicense() {
const headerPath = path.join(__dirname, 'build', 'license-header.txt');
const licenseHeader = await fsExtra.readFile(headerPath, 'utf-8');
const license = await fsExtra.readFile('LICENSE', 'utf-8');

await fsExtra.writeFile('LICENSE', `${licenseHeader}\n${license}`, 'utf-8');
}

gulp.task('updateBuildNumber', async () => {
await updateBuildNumber(argv);
});
Expand Down
1 change: 1 addition & 0 deletions news/1 Enhancements/16116.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bundle Pylance with the extension as an optional dependency.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "python",
"displayName": "Python",
"description": "Linting, Debugging (multi-threaded, remote), Intellisense, Jupyter Notebooks, code formatting, refactoring, unit tests, and more.",
"description": "IntelliSense (Pylance), Linting, Debugging (multi-threaded, remote), Jupyter Notebooks, code formatting, refactoring, unit tests, and more.",
"version": "2021.5.0-dev",
"featureFlags": {
"usingNewInterpreterStorage": true
Expand Down Expand Up @@ -47,7 +47,6 @@
"Linters",
"Formatters",
"Other",
"Extension Packs",
"Data Science",
"Machine Learning",
"Notebooks"
Expand Down Expand Up @@ -2076,6 +2075,7 @@
"format-fix": "prettier --write 'src/**/*.ts' 'src/**/*.tsx' 'build/**/*.js' '.github/**/*.yml' gulpfile.js",
"clean": "gulp clean",
"addExtensionDependencies": "gulp addExtensionDependencies",
"addExtensionPackDependencies": "gulp addExtensionPackDependencies",
"updateBuildNumber": "gulp updateBuildNumber",
"verifyBundle": "gulp verifyBundle",
"webpack": "webpack"
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
"diagnostics.checkIsort5UpgradeGuide": "We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.",
"diagnostics.yesUpdateLaunch": "Yes, update launch.json",
"diagnostics.invalidTestSettings": "Your settings needs to be updated to change the setting \"python.unitTest.\" to \"python.testing.\", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?",
"diagnostics.pylanceDefaultMessage": "The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance’s license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license).",
"Common.canceled": "Canceled",
"Common.cancel": "Cancel",
"Common.yesPlease": "Yes, please",
Expand Down
91 changes: 91 additions & 0 deletions src/client/application/diagnostics/checks/pylanceDefault.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// eslint-disable-next-line max-classes-per-file
import { inject, named } from 'inversify';
import { DiagnosticSeverity } from 'vscode';
import { IStartPage } from '../../../common/startPage/types';
import { IDisposableRegistry, IExtensionContext, Resource } from '../../../common/types';
import { Diagnostics, Common } from '../../../common/utils/localize';
import { IServiceContainer } from '../../../ioc/types';
import { BaseDiagnostic, BaseDiagnosticsService } from '../base';
import { DiagnosticCodes } from '../constants';
import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler';
import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types';

export const PYLANCE_PROMPT_MEMENTO = 'pylanceDefaultPromptMemento';

export class PylanceDefaultDiagnostic extends BaseDiagnostic {
constructor(message: string, resource: Resource) {
super(
DiagnosticCodes.PylanceDefaultDiagnostic,
message,
DiagnosticSeverity.Information,
DiagnosticScope.Global,
resource,
);
}
}

export const PylanceDefaultDiagnosticServiceId = 'PylanceDefaultDiagnosticServiceId';

export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService {
constructor(
@inject(IServiceContainer) serviceContainer: IServiceContainer,
@inject(IExtensionContext) private readonly context: IExtensionContext,
@inject(IStartPage) private readonly startPage: IStartPage,
@inject(IDiagnosticHandlerService)
@named(DiagnosticCommandPromptHandlerServiceId)
protected readonly messageService: IDiagnosticHandlerService<MessageCommandPrompt>,
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry,
) {
super([DiagnosticCodes.PylanceDefaultDiagnostic], serviceContainer, disposableRegistry, true);
}

public async diagnose(resource: Resource): Promise<IDiagnostic[]> {
if (!(await this.shouldShowPrompt())) {
return [];
}

return [new PylanceDefaultDiagnostic(Diagnostics.pylanceDefaultMessage(), resource)];
}

protected async onHandle(diagnostics: IDiagnostic[]): Promise<void> {
if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) {
return;
}

const diagnostic = diagnostics[0];
if (await this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) {
return;
}

const options = [{ prompt: Common.ok() }];

await this.messageService.handle(diagnostic, {
commandPrompts: options,
onClose: this.updateMemento.bind(this),
});
}

private async updateMemento() {
await this.context.globalState.update(PYLANCE_PROMPT_MEMENTO, true);
}

private async shouldShowPrompt(): Promise<boolean> {
const savedVersion: string | undefined = this.startPage.initialMementoValue;
const promptShown: boolean | undefined = this.context.globalState.get(PYLANCE_PROMPT_MEMENTO);

// savedVersion being undefined means that this is the first time the user activates the extension,
// and we don't want to show the prompt to first-time users.
// We set PYLANCE_PROMPT_MEMENTO here to skip the prompt
// in case the user reloads the extension and savedVersion becomes set
if (savedVersion === undefined) {
await this.updateMemento();
return false;
}

// promptShown being undefined means that this is the first time we check if we should show the prompt.
return promptShown === undefined;
}
}
1 change: 1 addition & 0 deletions src/client/application/diagnostics/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export enum DiagnosticCodes {
ConsoleTypeDiagnostic = 'ConsoleTypeDiagnostic',
ConfigPythonPathDiagnostic = 'ConfigPythonPathDiagnostic',
UpgradeCodeRunnerDiagnostic = 'UpgradeCodeRunnerDiagnostic',
PylanceDefaultDiagnostic = 'PylanceDefaultDiagnostic',
}
8 changes: 8 additions & 0 deletions src/client/application/diagnostics/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
PowerShellActivationHackDiagnosticsService,
PowerShellActivationHackDiagnosticsServiceId,
} from './checks/powerShellActivation';
import { PylanceDefaultDiagnosticService, PylanceDefaultDiagnosticServiceId } from './checks/pylanceDefault';
import { InvalidPythonInterpreterService, InvalidPythonInterpreterServiceId } from './checks/pythonInterpreter';
import {
PythonPathDeprecatedDiagnosticService,
Expand Down Expand Up @@ -92,6 +93,13 @@ export function registerTypes(serviceManager: IServiceManager, languageServerTyp
UpgradeCodeRunnerDiagnosticService,
UpgradeCodeRunnerDiagnosticServiceId,
);

serviceManager.addSingleton<IDiagnosticsService>(
IDiagnosticsService,
PylanceDefaultDiagnosticService,
PylanceDefaultDiagnosticServiceId,
);

serviceManager.addSingleton<IDiagnosticsCommandFactory>(IDiagnosticsCommandFactory, DiagnosticsCommandFactory);
serviceManager.addSingleton<IApplicationDiagnostics>(IApplicationDiagnostics, ApplicationDiagnostics);

Expand Down
11 changes: 8 additions & 3 deletions src/client/common/startPage/startPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { WebviewPanelHost } from './webviewPanelHost';

const startPageDir = path.join(EXTENSION_ROOT_DIR, 'out', 'startPage-ui', 'viewers');

export const EXTENSION_VERSION_MEMENTO = 'extensionVersion';

// Class that opens, disposes and handles messages and actions for the Python Extension Start Page.
// It also runs when the extension activates.
@injectable()
Expand All @@ -39,6 +41,8 @@ export class StartPage extends WebviewPanelHost<IStartPageMapping>
private actionTakenOnFirstTime = false;
private firstTime = false;
private webviewDidLoad = false;
public initialMementoValue: string | undefined = undefined;

constructor(
@inject(IWebviewPanelProvider) provider: IWebviewPanelProvider,
@inject(ICodeCssGenerator) cssGenerator: ICodeCssGenerator,
Expand Down Expand Up @@ -66,6 +70,7 @@ export class StartPage extends WebviewPanelHost<IStartPageMapping>
false,
);
this.timer = new StopWatch();
this.initialMementoValue = this.context.globalState.get(EXTENSION_VERSION_MEMENTO);
}

public async activate(): Promise<void> {
Expand Down Expand Up @@ -129,7 +134,7 @@ export class StartPage extends WebviewPanelHost<IStartPageMapping>
sendTelemetryEvent(Telemetry.StartPageOpenBlankNotebook);
this.setTelemetryFlags();

const savedVersion: string | undefined = this.context.globalState.get('extensionVersion');
const savedVersion: string | undefined = this.context.globalState.get(EXTENSION_VERSION_MEMENTO);

if (savedVersion) {
await this.commandManager.executeCommand(
Expand Down Expand Up @@ -220,7 +225,7 @@ export class StartPage extends WebviewPanelHost<IStartPageMapping>

// Public for testing
public async extensionVersionChanged(): Promise<boolean> {
const savedVersion: string | undefined = this.context.globalState.get('extensionVersion');
const savedVersion: string | undefined = this.context.globalState.get(EXTENSION_VERSION_MEMENTO);
const version: string = this.appEnvironment.packageJson.version;
let shouldShowStartPage: boolean;

Expand All @@ -239,7 +244,7 @@ export class StartPage extends WebviewPanelHost<IStartPageMapping>

// savedVersion being undefined means this is the first time the user activates the extension.
// if savedVersion != version, there was an update
await this.context.globalState.update('extensionVersion', version);
await this.context.globalState.update(EXTENSION_VERSION_MEMENTO, version);
return shouldShowStartPage;
}

Expand Down
1 change: 1 addition & 0 deletions src/client/common/startPage/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type JSONArray = JSONValue[];

export const IStartPage = Symbol('IStartPage');
export interface IStartPage {
readonly initialMementoValue?: string;
open(): Promise<void>;
extensionVersionChanged(): Promise<boolean>;
}
Expand Down
4 changes: 4 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export namespace Diagnostics {
'diagnostics.checkIsort5UpgradeGuide',
'We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.',
);
export const pylanceDefaultMessage = localize(
'diagnostics.pylanceDefaultMessage',
'The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance’s license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license).',
);
}

export namespace Common {
Expand Down
Loading

0 comments on commit 3e79ef2

Please sign in to comment.