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
1 change: 1 addition & 0 deletions news/3 Code Health/1747.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Prompt user to reload Visual Studio Code when toggling between the analysis engines.
75 changes: 75 additions & 0 deletions src/client/activation/activationService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { inject, injectable } from 'inversify';
import { ConfigurationChangeEvent, Disposable, OutputChannel, Uri } from 'vscode';
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types';
import { isPythonAnalysisEngineTest, STANDARD_OUTPUT_CHANNEL } from '../common/constants';
import '../common/extensions';
import { IConfigurationService, IDisposableRegistry, IOutputChannel, IPythonSettings } from '../common/types';
import { IServiceContainer } from '../ioc/types';
import { ExtensionActivators, IExtensionActivationService, IExtensionActivator } from './types';

const jediEnabledSetting: keyof IPythonSettings = 'jediEnabled';

type ActivatorInfo = { jedi: boolean; activator: IExtensionActivator };

@injectable()
export class ExtensionActivationService implements IExtensionActivationService, Disposable {
private currentActivator?: ActivatorInfo;
private readonly workspaceService: IWorkspaceService;
private readonly output: OutputChannel;
private readonly appShell: IApplicationShell;
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
this.workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
this.output = this.serviceContainer.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
this.appShell = this.serviceContainer.get<IApplicationShell>(IApplicationShell);

const disposables = serviceContainer.get<IDisposableRegistry>(IDisposableRegistry);
disposables.push(this);
disposables.push(this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)));
}
public async activate(): Promise<void> {
if (this.currentActivator) {
return;
}

const jedi = this.useJedi();

const engineName = jedi ? 'classic analysis engine' : 'analysis engine';
this.output.appendLine(`Starting the ${engineName}.`);
const activatorName = jedi ? ExtensionActivators.Jedi : ExtensionActivators.DotNet;
const activator = this.serviceContainer.get<IExtensionActivator>(IExtensionActivator, activatorName);
this.currentActivator = { jedi, activator };

await activator.activate();
}
public dispose() {
if (this.currentActivator) {
this.currentActivator.activator.deactivate().ignoreErrors();
}
}
private async onDidChangeConfiguration(event: ConfigurationChangeEvent) {
const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders!.map(workspace => workspace.uri) : [undefined];
if (workspacesUris.findIndex(uri => event.affectsConfiguration(`python.${jediEnabledSetting}`, uri)) === -1) {
return;
}
const jedi = this.useJedi();
if (this.currentActivator && this.currentActivator.jedi === jedi) {
return;
}

const item = await this.appShell.showInformationMessage('Please reload the window switching between the analysis engines.', 'Reload');
if (item === 'Reload') {
this.serviceContainer.get<ICommandManager>(ICommandManager).executeCommand('workbench.action.reloadWindow');
}
}
private useJedi(): boolean {
const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders!.map(item => item.uri) : [undefined];
const configuraionService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
const jediEnabledForAnyWorkspace = workspacesUris.filter(uri => configuraionService.getSettings(uri).jediEnabled).length > 0;
return !isPythonAnalysisEngineTest() && jediEnabledForAnyWorkspace;
}
}
18 changes: 10 additions & 8 deletions src/client/activation/analysis.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { inject, injectable } from 'inversify';
import * as path from 'path';
import { ExtensionContext, OutputChannel } from 'vscode';
import { Message } from 'vscode-jsonrpc';
Expand All @@ -10,7 +11,7 @@ import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants';
import { createDeferred, Deferred } from '../common/helpers';
import { IFileSystem, IPlatformService } from '../common/platform/types';
import { StopWatch } from '../common/stopWatch';
import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types';
import { IConfigurationService, IExtensionContext, IOutputChannel } from '../common/types';
import { IEnvironmentVariablesProvider } from '../common/variables/types';
import { IInterpreterService } from '../interpreter/contracts';
import { IServiceContainer } from '../ioc/types';
Expand Down Expand Up @@ -43,6 +44,7 @@ class LanguageServerStartupErrorHandler implements ErrorHandler {
}
}

@injectable()
export class AnalysisExtensionActivator implements IExtensionActivator {
private readonly configuration: IConfigurationService;
private readonly appShell: IApplicationShell;
Expand All @@ -53,10 +55,11 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
private readonly interpreterService: IInterpreterService;
private readonly disposables: Disposable[] = [];
private languageClient: LanguageClient | undefined;
private context: ExtensionContext | undefined;
private readonly context: ExtensionContext;
private interpreterHash: string = '';

constructor(private readonly services: IServiceContainer, pythonSettings: IPythonSettings) {
constructor(@inject(IServiceContainer) private readonly services: IServiceContainer) {
this.context = this.services.get<IExtensionContext>(IExtensionContext);
this.configuration = this.services.get<IConfigurationService>(IConfigurationService);
this.appShell = this.services.get<IApplicationShell>(IApplicationShell);
this.output = this.services.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
Expand All @@ -65,15 +68,14 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
this.interpreterService = this.services.get<IInterpreterService>(IInterpreterService);
}

public async activate(context: ExtensionContext): Promise<boolean> {
public async activate(): Promise<boolean> {
this.sw.reset();
this.context = context;
const clientOptions = await this.getAnalysisOptions(context);
const clientOptions = await this.getAnalysisOptions(this.context);
if (!clientOptions) {
return false;
}
this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.restartLanguageServer()));
return this.startLanguageServer(context, clientOptions);
return this.startLanguageServer(this.context, clientOptions);
}

public async deactivate(): Promise<void> {
Expand All @@ -94,7 +96,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
if (!idata || idata.hash !== this.interpreterHash) {
this.interpreterHash = idata ? idata.hash : '';
await this.deactivate();
await this.activate(this.context);
await this.activate();
}
}

Expand Down
30 changes: 21 additions & 9 deletions src/client/activation/classic.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { DocumentFilter, ExtensionContext, languages, OutputChannel } from 'vscode';
import { STANDARD_OUTPUT_CHANNEL } from '../common/constants';
import { ILogger, IOutputChannel, IPythonSettings } from '../common/types';
import { inject, injectable } from 'inversify';
import { DocumentFilter, languages, OutputChannel } from 'vscode';
import { PYTHON, STANDARD_OUTPUT_CHANNEL } from '../common/constants';
import { IConfigurationService, IExtensionContext, ILogger, IOutputChannel } from '../common/types';
import { IShebangCodeLensProvider } from '../interpreter/contracts';
import { IServiceManager } from '../ioc/types';
import { JediFactory } from '../languageServices/jediProxyFactory';
Expand All @@ -19,15 +20,22 @@ import { PythonSymbolProvider } from '../providers/symbolProvider';
import { IUnitTestManagementService } from '../unittests/types';
import { IExtensionActivator } from './types';

@injectable()
export class ClassicExtensionActivator implements IExtensionActivator {
constructor(private serviceManager: IServiceManager, private pythonSettings: IPythonSettings, private documentSelector: DocumentFilter[]) {
private readonly context: IExtensionContext;
private jediFactory?: JediFactory;
private readonly documentSelector: DocumentFilter[];
constructor(@inject(IServiceManager) private serviceManager: IServiceManager) {
this.context = this.serviceManager.get<IExtensionContext>(IExtensionContext);
this.documentSelector = PYTHON;
}

public async activate(context: ExtensionContext): Promise<boolean> {
public async activate(): Promise<boolean> {
const context = this.context;
const standardOutputChannel = this.serviceManager.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
activateSimplePythonRefactorProvider(context, standardOutputChannel, this.serviceManager);

const jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager);
const jediFactory = this.jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager);
context.subscriptions.push(jediFactory);
context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory));

Expand All @@ -44,7 +52,8 @@ export class ClassicExtensionActivator implements IExtensionActivator {
const symbolProvider = new PythonSymbolProvider(jediFactory);
context.subscriptions.push(languages.registerDocumentSymbolProvider(this.documentSelector, symbolProvider));

if (this.pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) {
const pythonSettings = this.serviceManager.get<IConfigurationService>(IConfigurationService).getSettings();
if (pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) {
context.subscriptions.push(languages.registerSignatureHelpProvider(this.documentSelector, new PythonSignatureProvider(jediFactory), '(', ','));
}

Expand All @@ -56,6 +65,9 @@ export class ClassicExtensionActivator implements IExtensionActivator {
return true;
}

// tslint:disable-next-line:no-empty
public async deactivate(): Promise<void> { }
public async deactivate(): Promise<void> {
if (this.jediFactory) {
this.jediFactory.dispose();
}
}
}
16 changes: 16 additions & 0 deletions src/client/activation/serviceRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { IServiceManager } from '../ioc/types';
import { ExtensionActivationService } from './activationService';
import { AnalysisExtensionActivator } from './analysis';
import { ClassicExtensionActivator } from './classic';
import { ExtensionActivators, IExtensionActivationService, IExtensionActivator } from './types';

export function registerTypes(serviceManager: IServiceManager) {
serviceManager.addSingleton<IExtensionActivationService>(IExtensionActivationService, ExtensionActivationService);
serviceManager.add<IExtensionActivator>(IExtensionActivator, ClassicExtensionActivator, ExtensionActivators.Jedi);
serviceManager.add<IExtensionActivator>(IExtensionActivator, AnalysisExtensionActivator, ExtensionActivators.DotNet);
}
13 changes: 11 additions & 2 deletions src/client/activation/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import * as vscode from 'vscode';
export const IExtensionActivationService = Symbol('IExtensionActivationService');
export interface IExtensionActivationService {
activate(): Promise<void>;
}

export enum ExtensionActivators {
Jedi = 'Jedi',
DotNet = 'DotNet'
}

export const IExtensionActivator = Symbol('IExtensionActivator');
export interface IExtensionActivator {
activate(context: vscode.ExtensionContext): Promise<boolean>;
activate(): Promise<boolean>;
deactivate(): Promise<void>;
}
3 changes: 1 addition & 2 deletions src/client/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ export namespace LinterErrors {
export const STANDARD_OUTPUT_CHANNEL = 'STANDARD_OUTPUT_CHANNEL';

export function isTestExecution(): boolean {
// tslint:disable-next-line:interface-name no-string-literal
return process.env['VSC_PYTHON_CI_TEST'] === '1';
return process.env.VSC_PYTHON_CI_TEST === '1';
}
export function isPythonAnalysisEngineTest(): boolean {
return process.env.VSC_PYTHON_ANALYSIS === '1';
Expand Down
7 changes: 6 additions & 1 deletion src/client/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
// Licensed under the MIT License.

import { Socket } from 'net';
import { ConfigurationTarget, DiagnosticSeverity, Disposable, Uri } from 'vscode';
import { ConfigurationTarget, DiagnosticSeverity, Disposable, ExtensionContext, OutputChannel, Uri } from 'vscode';

import { EnvironmentVariables } from './variables/types';
export const IOutputChannel = Symbol('IOutputChannel');
export interface IOutputChannel extends OutputChannel { }
export const IDocumentSymbolProvider = Symbol('IDocumentSymbolProvider');
export const IsWindows = Symbol('IS_WINDOWS');
export const Is64Bit = Symbol('Is64Bit');
export const IDisposableRegistry = Symbol('IDiposableRegistry');
export type IDisposableRegistry = Disposable[];
export const IMemento = Symbol('IGlobalMemento');
export const GLOBAL_MEMENTO = Symbol('IGlobalMemento');
export const WORKSPACE_MEMENTO = Symbol('IWorkspaceMemento');
Expand Down Expand Up @@ -233,3 +235,6 @@ export interface ISocketServer extends Disposable {
readonly client: Promise<Socket>;
Start(options?: { port?: number; host?: string }): Promise<number>;
}

export const IExtensionContext = Symbol('ExtensionContext');
export interface IExtensionContext extends ExtensionContext { }
24 changes: 11 additions & 13 deletions src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import {
extensions, IndentAction, languages, Memento,
OutputChannel, window
} from 'vscode';
import { AnalysisExtensionActivator } from './activation/analysis';
import { ClassicExtensionActivator } from './activation/classic';
import { IExtensionActivator } from './activation/types';
import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry';
import { IExtensionActivationService } from './activation/types';
import { PythonSettings } from './common/configSettings';
import { isPythonAnalysisEngineTest, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants';
import { PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants';
import { FeatureDeprecationManager } from './common/featureDeprecationManager';
import { createDeferred } from './common/helpers';
import { PythonInstaller } from './common/installer/pythonInstallation';
Expand All @@ -25,7 +24,7 @@ import { registerTypes as processRegisterTypes } from './common/process/serviceR
import { registerTypes as commonRegisterTypes } from './common/serviceRegistry';
import { StopWatch } from './common/stopWatch';
import { ITerminalHelper } from './common/terminal/types';
import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types';
import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, IExtensionContext, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types';
import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry';
import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts';
import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider';
Expand All @@ -37,7 +36,7 @@ import { ICondaService, IInterpreterService } from './interpreter/contracts';
import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry';
import { ServiceContainer } from './ioc/container';
import { ServiceManager } from './ioc/serviceManager';
import { IServiceContainer } from './ioc/types';
import { IServiceContainer, IServiceManager } from './ioc/types';
import { LinterCommands } from './linters/linterCommands';
import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry';
import { ILintingEngine } from './linters/types';
Expand Down Expand Up @@ -76,18 +75,14 @@ export async function activate(context: ExtensionContext) {
const configuration = serviceManager.get<IConfigurationService>(IConfigurationService);
const pythonSettings = configuration.getSettings();

const activator: IExtensionActivator = isPythonAnalysisEngineTest() || !pythonSettings.jediEnabled
? new AnalysisExtensionActivator(serviceManager, pythonSettings)
: new ClassicExtensionActivator(serviceManager, pythonSettings, PYTHON);

await activator.activate(context);
const activationService = serviceContainer.get<IExtensionActivationService>(IExtensionActivationService);
await activationService.activate();

const standardOutputChannel = serviceManager.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
sortImports.activate(context, standardOutputChannel, serviceManager);

serviceManager.get<ICodeExecutionManager>(ICodeExecutionManager).registerCommands();
// tslint:disable-next-line:no-floating-promises
sendStartupTelemetry(activated, serviceContainer);
sendStartupTelemetry(activated, serviceContainer).ignoreErrors();

const pythonInstaller = new PythonInstaller(serviceContainer);
pythonInstaller.checkPythonInstallation(PythonSettings.getInstance())
Expand Down Expand Up @@ -160,15 +155,18 @@ export async function activate(context: ExtensionContext) {

function registerServices(context: ExtensionContext, serviceManager: ServiceManager, serviceContainer: ServiceContainer) {
serviceManager.addSingletonInstance<IServiceContainer>(IServiceContainer, serviceContainer);
serviceManager.addSingletonInstance<IServiceManager>(IServiceManager, serviceManager);
serviceManager.addSingletonInstance<Disposable[]>(IDisposableRegistry, context.subscriptions);
serviceManager.addSingletonInstance<Memento>(IMemento, context.globalState, GLOBAL_MEMENTO);
serviceManager.addSingletonInstance<Memento>(IMemento, context.workspaceState, WORKSPACE_MEMENTO);
serviceManager.addSingletonInstance<IExtensionContext>(IExtensionContext, context);

const standardOutputChannel = window.createOutputChannel('Python');
const unitTestOutChannel = window.createOutputChannel('Python Test Log');
serviceManager.addSingletonInstance<OutputChannel>(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL);
serviceManager.addSingletonInstance<OutputChannel>(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL);

activationRegisterTypes(serviceManager);
commonRegisterTypes(serviceManager);
processRegisterTypes(serviceManager);
variableRegisterTypes(serviceManager);
Expand Down
1 change: 0 additions & 1 deletion src/client/formatters/baseFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export abstract class BaseFormatter {
return vscode.Uri.file(__dirname);
}
protected async provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, args: string[], cwd?: string): Promise<vscode.TextEdit[]> {
this.outputChannel.clear();
if (typeof cwd !== 'string' || cwd.length === 0) {
cwd = this.getWorkspaceUri(document).fsPath;
}
Expand Down
Loading