Skip to content
Closed
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
2 changes: 1 addition & 1 deletion pvsc.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
"**/.vscode-test/insider/**": true,
"**/.vscode-test/stable/**": true,
"**/out/**": true
}
},
}
}
59 changes: 44 additions & 15 deletions src/client/activation/jedi/multiplexingActivator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { inject, injectable } from 'inversify';
import { inject, injectable, named } from 'inversify';
import {
CancellationToken,
CompletionContext,
Expand All @@ -9,13 +9,20 @@ import {
Position,
ReferenceContext,
SignatureHelpContext,
TextDocument
TextDocument,
} from 'vscode';
// tslint:disable-next-line: import-name
import { IWorkspaceService } from '../../common/application/types';
import { isTestExecution } from '../../common/constants';
import { JediLSP } from '../../common/experiments/groups';
import { IFileSystem } from '../../common/platform/types';
import { IConfigurationService, IExperimentService, Resource } from '../../common/types';
import {
BANNER_NAME_PROPOSE_LS_FOR_JEDI_USERS,
IConfigurationService,
IExperimentService,
IPythonExtensionBanner,
Resource,
} from '../../common/types';
import { IServiceManager } from '../../ioc/types';
import { PythonEnvironment } from '../../pythonEnvironments/info';
import { JediExtensionActivator } from '../jedi';
Expand All @@ -33,41 +40,53 @@ import { JediLanguageServerActivator } from './activator';
@injectable()
export class MultiplexingJediLanguageServerActivator implements ILanguageServerActivator {
private realLanguageServerPromise: Promise<ILanguageServerActivator>;

private realLanguageServer: ILanguageServerActivator | undefined;

private onDidChangeCodeLensesEmitter = new EventEmitter<void>();

constructor(
@inject(IServiceManager) private readonly manager: IServiceManager,
@inject(IExperimentService) experimentService: IExperimentService
@inject(IExperimentService) experimentService: IExperimentService,
@inject(IPythonExtensionBanner)
@named(BANNER_NAME_PROPOSE_LS_FOR_JEDI_USERS)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is named?

private proposePylancePopup: IPythonExtensionBanner,
) {
// Check experiment service to see if using new Jedi LSP protocol
this.realLanguageServerPromise = experimentService.inExperiment(JediLSP.experiment).then((inExperiment) => {
// Pick how to launch jedi based on if in the experiment or not.
this.realLanguageServer = !inExperiment
? // Pick how to launch jedi based on if in the experiment or not.
new JediExtensionActivator(this.manager)
? new JediExtensionActivator(this.manager)
: new JediLanguageServerActivator(
this.manager.get<ILanguageServerManager>(ILanguageServerManager),
this.manager.get<IWorkspaceService>(IWorkspaceService),
this.manager.get<IFileSystem>(IFileSystem),
this.manager.get<IConfigurationService>(IConfigurationService)
);
this.manager.get<ILanguageServerManager>(ILanguageServerManager),
this.manager.get<IWorkspaceService>(IWorkspaceService),
this.manager.get<IFileSystem>(IFileSystem),
this.manager.get<IConfigurationService>(IConfigurationService),
);
return this.realLanguageServer;
});
}

public async start(resource: Resource, interpreter: PythonEnvironment | undefined): Promise<void> {
const realServer = await this.realLanguageServerPromise;
if (!isTestExecution()) {
this.proposePylancePopup.showBanner().ignoreErrors();
}
return realServer.start(resource, interpreter);
}

public activate(): void {
if (this.realLanguageServer) {
this.realLanguageServer.activate();
}
}

public deactivate(): void {
if (this.realLanguageServer) {
this.realLanguageServer.deactivate();
}
}

public get onDidChangeCodeLenses(): Event<void> {
return this.onDidChangeCodeLensesEmitter.event;
}
Expand All @@ -76,66 +95,76 @@ export class MultiplexingJediLanguageServerActivator implements ILanguageServerA
if (this.realLanguageServer) {
return this.realLanguageServer.connection;
}
return undefined;
}

public get capabilities() {
if (this.realLanguageServer) {
return this.realLanguageServer.capabilities;
}
return undefined;
}

public async provideRenameEdits(
document: TextDocument,
position: Position,
newName: string,
token: CancellationToken
token: CancellationToken,
) {
const server = await this.realLanguageServerPromise;
return server.provideRenameEdits(document, position, newName, token);
}

public async provideDefinition(document: TextDocument, position: Position, token: CancellationToken) {
const server = await this.realLanguageServerPromise;
return server.provideDefinition(document, position, token);
}

public async provideHover(document: TextDocument, position: Position, token: CancellationToken) {
const server = await this.realLanguageServerPromise;
return server.provideHover(document, position, token);
}

public async provideReferences(
document: TextDocument,
position: Position,
context: ReferenceContext,
token: CancellationToken
token: CancellationToken,
) {
const server = await this.realLanguageServerPromise;
return server.provideReferences(document, position, context, token);
}

public async provideCompletionItems(
document: TextDocument,
position: Position,
token: CancellationToken,
context: CompletionContext
context: CompletionContext,
) {
const server = await this.realLanguageServerPromise;
return server.provideCompletionItems(document, position, token, context);
}

public async provideCodeLenses(document: TextDocument, token: CancellationToken) {
const server = await this.realLanguageServerPromise;
return server.provideCodeLenses(document, token);
}

public async provideDocumentSymbols(document: TextDocument, token: CancellationToken) {
const server = await this.realLanguageServerPromise;
return server.provideDocumentSymbols(document, token);
}

public async provideSignatureHelp(
document: TextDocument,
position: Position,
token: CancellationToken,
context: SignatureHelpContext
context: SignatureHelpContext,
) {
const server = await this.realLanguageServerPromise;
return server.provideSignatureHelp(document, position, token, context);
}

public dispose(): void {
if (this.realLanguageServer) {
this.realLanguageServer.dispose();
Expand Down
9 changes: 8 additions & 1 deletion src/client/activation/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

import { registerTypes as registerDotNetTypes } from '../common/dotnet/serviceRegistry';
import { INugetRepository } from '../common/nuget/types';
import { BANNER_NAME_PROPOSE_LS, IPythonExtensionBanner } from '../common/types';
import { BANNER_NAME_PROPOSE_LS, BANNER_NAME_PROPOSE_LS_FOR_JEDI_USERS, IPythonExtensionBanner } from '../common/types';
import { IServiceManager } from '../ioc/types';
import { ProposePylanceBanner } from '../languageServices/proposeLanguageServerBanner';
import { ProposePylanceBannerJedi } from '../languageServices/proposeLanguageServerBannerJedi';
import { AATesting } from './aaTesting';
import { ExtensionActivationManager } from './activationManager';
import { LanguageServerExtensionActivationService } from './activationService';
Expand Down Expand Up @@ -81,6 +82,12 @@ export function registerTypes(serviceManager: IServiceManager, languageServerTyp
BANNER_NAME_PROPOSE_LS
);

serviceManager.addSingleton<IPythonExtensionBanner>(
IPythonExtensionBanner,
ProposePylanceBannerJedi,
BANNER_NAME_PROPOSE_LS_FOR_JEDI_USERS
);

if (languageServerType === LanguageServerType.Microsoft) {
serviceManager.add<ILanguageServerAnalysisOptions>(
ILanguageServerAnalysisOptions,
Expand Down
4 changes: 3 additions & 1 deletion src/client/common/experiments/groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ export enum DeprecatePythonPath {

// Experiment to offer switch to Pylance language server
export enum TryPylance {
experiment = 'tryPylance'
experiment = 'tryPylance',
jediPrompt1 = 'tryPylancePromptText1',
jediPrompt2 = 'tryPylancePromptText2'
}

// Experiment for the content of the tip being displayed on first extension launch:
Expand Down
1 change: 1 addition & 0 deletions src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ export interface IPythonExtensionBanner {
showBanner(): Promise<void>;
}
export const BANNER_NAME_PROPOSE_LS: string = 'ProposePylance';
export const BANNER_NAME_PROPOSE_LS_FOR_JEDI_USERS: string = 'ProposePylanceJedi';

export type DeprecatedSettingAndValue = {
setting: string;
Expand Down
3 changes: 2 additions & 1 deletion src/client/languageServices/proposeLanguageServerBanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ export class ProposePylanceBanner implements IPythonExtensionBanner {
this.disabledInCurrentSession = true;
userAction = 'later';
}
sendTelemetryEvent(EventName.LANGUAGE_SERVER_TRY_PYLANCE, undefined, { userAction });
const experimentName = TryPylance.experiment;
sendTelemetryEvent(EventName.LANGUAGE_SERVER_TRY_PYLANCE, undefined, { userAction, experimentName });
}

public async shouldShowBanner(): Promise<boolean> {
Expand Down
119 changes: 119 additions & 0 deletions src/client/languageServices/proposeLanguageServerBannerJedi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { inject, injectable } from 'inversify';
import { LanguageServerType } from '../activation/types';
import { IApplicationEnvironment, IApplicationShell } from '../common/application/types';
import { PYLANCE_EXTENSION_ID } from '../common/constants';
import { TryPylance } from '../common/experiments/groups';
import '../common/extensions';
import {
IConfigurationService,
IExperimentService,
IExtensions,
IPersistentStateFactory,
// tslint:disable-next-line: prettier
IPythonExtensionBanner,
} from '../common/types';
import { Common, Pylance } from '../common/utils/localize';
import { sendTelemetryEvent } from '../telemetry';
import { EventName } from '../telemetry/constants';

export function getPylanceExtensionUri(appEnv: IApplicationEnvironment): string {
return `${appEnv.uriScheme}:extension/${PYLANCE_EXTENSION_ID}`;
}

// persistent state names, exported to make use of in testing
export enum ProposeLSStateKeys {
ShowBannerJedi = 'TryPylanceBannerJedi'
}

/*
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is basically the same as the previous tryPylance banner ProposePylanceBanner.ts

just the data and some of the logic have changed

This class represents a popup that propose that the user try out a new
feature of the extension, and optionally enable that new feature if they
choose to do so. It is meant to be shown only to a subset of our users,
and will show as soon as it is instructed to do so, if a random sample
function enables the popup for this user.
*/
@injectable()
export class ProposePylanceBannerJedi implements IPythonExtensionBanner {
private disabledInCurrentSession = false;

constructor(
@inject(IApplicationShell) private appShell: IApplicationShell,
@inject(IApplicationEnvironment) private appEnv: IApplicationEnvironment,
@inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory,
@inject(IConfigurationService) private configuration: IConfigurationService,
@inject(IExperimentService) private experiments: IExperimentService,
// eslint-disable-next-line comma-dangle
@inject(IExtensions) readonly extensions: IExtensions
) {}

public get enabled(): boolean {
const lsType = this.configuration.getSettings().languageServer ?? LanguageServerType.Jedi;
if (lsType !== LanguageServerType.Jedi) {
return false;
}
return this.persistentState.createGlobalPersistentState<boolean>(ProposeLSStateKeys.ShowBannerJedi, true).value;
}

public async showBanner(): Promise<void> {
if (!this.enabled) {
return;
}

const show = await this.shouldShowBanner();
if (!show) {
return;
}

let experimentName = '';
let promptContent: string | undefined;
if (await this.experiments.inExperiment(TryPylance.jediPrompt1)) {
promptContent = await this.experiments.getExperimentValue<string>(TryPylance.jediPrompt1);
experimentName = TryPylance.jediPrompt1;
} else if (await this.experiments.inExperiment(TryPylance.jediPrompt2)) {
promptContent = await this.experiments.getExperimentValue<string>(TryPylance.jediPrompt2);
experimentName = TryPylance.jediPrompt2;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new way to check if we're in an experiment and also get the banner string


if (promptContent === undefined) {
return;
}

const response = await this.appShell.showInformationMessage(
promptContent,
Pylance.tryItNow(),
Common.bannerLabelNo(),
Pylance.remindMeLater()
);

let userAction: string;
if (response === Pylance.tryItNow()) {
this.appShell.openUrl(getPylanceExtensionUri(this.appEnv));
userAction = 'yes';
await this.disable();
} else if (response === Common.bannerLabelNo()) {
await this.disable();
userAction = 'no';
} else {
this.disabledInCurrentSession = true;
userAction = 'later';
}
sendTelemetryEvent(EventName.LANGUAGE_SERVER_TRY_PYLANCE, undefined, { userAction, experimentName });
}

public async shouldShowBanner(): Promise<boolean> {
// Do not prompt if Pylance is already installed.
if (this.extensions.getExtension(PYLANCE_EXTENSION_ID)) {
return false;
}
return this.enabled && !this.disabledInCurrentSession;
}

public async disable(): Promise<void> {
await this.persistentState
.createGlobalPersistentState<boolean>(ProposeLSStateKeys.ShowBannerJedi, false)
.updateValue(false);
}
}
4 changes: 3 additions & 1 deletion src/client/telemetry/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

'use strict';


export enum EventName {
COMPLETION = 'COMPLETION',
COMPLETION_ADD_BRACKETS = 'COMPLETION.ADD_BRACKETS',
Expand Down Expand Up @@ -115,7 +116,8 @@ export enum EventName {
HASHED_PACKAGE_NAME = 'HASHED_PACKAGE_NAME',
HASHED_PACKAGE_PERF = 'HASHED_PACKAGE_PERF',

JEDI_MEMORY = 'JEDI_MEMORY'
JEDI_MEMORY = 'JEDI_MEMORY',
BANNER_NAME_PROPOSE_LS_FOR_JEDI_USERS = "BANNER_NAME_PROPOSE_LS_FOR_JEDI_USERS"
}

export enum PlatformErrors {
Expand Down
1 change: 1 addition & 0 deletions src/client/telemetry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,7 @@ export interface IEventNamePropertyMapping {
* @type {string}
*/
userAction: string;
experimentName: string;
};
/**
* Telemetry captured for enabling reload.
Expand Down
Loading