Skip to content

Commit 997f99b

Browse files
authored
Prompt to reload VS Code when changing analysis engines (microsoft#1754)
* Prompt to reload VS Code when chaning analysis engines * Change message * Validate reloading as well
1 parent 0420bd4 commit 997f99b

13 files changed

Lines changed: 374 additions & 38 deletions

File tree

news/3 Code Health/1747.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prompt user to reload Visual Studio Code when toggling between the analysis engines.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable } from 'inversify';
7+
import { ConfigurationChangeEvent, Disposable, OutputChannel, Uri } from 'vscode';
8+
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types';
9+
import { isPythonAnalysisEngineTest, STANDARD_OUTPUT_CHANNEL } from '../common/constants';
10+
import '../common/extensions';
11+
import { IConfigurationService, IDisposableRegistry, IOutputChannel, IPythonSettings } from '../common/types';
12+
import { IServiceContainer } from '../ioc/types';
13+
import { ExtensionActivators, IExtensionActivationService, IExtensionActivator } from './types';
14+
15+
const jediEnabledSetting: keyof IPythonSettings = 'jediEnabled';
16+
17+
type ActivatorInfo = { jedi: boolean; activator: IExtensionActivator };
18+
19+
@injectable()
20+
export class ExtensionActivationService implements IExtensionActivationService, Disposable {
21+
private currentActivator?: ActivatorInfo;
22+
private readonly workspaceService: IWorkspaceService;
23+
private readonly output: OutputChannel;
24+
private readonly appShell: IApplicationShell;
25+
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
26+
this.workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
27+
this.output = this.serviceContainer.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
28+
this.appShell = this.serviceContainer.get<IApplicationShell>(IApplicationShell);
29+
30+
const disposables = serviceContainer.get<IDisposableRegistry>(IDisposableRegistry);
31+
disposables.push(this);
32+
disposables.push(this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)));
33+
}
34+
public async activate(): Promise<void> {
35+
if (this.currentActivator) {
36+
return;
37+
}
38+
39+
const jedi = this.useJedi();
40+
41+
const engineName = jedi ? 'classic analysis engine' : 'analysis engine';
42+
this.output.appendLine(`Starting the ${engineName}.`);
43+
const activatorName = jedi ? ExtensionActivators.Jedi : ExtensionActivators.DotNet;
44+
const activator = this.serviceContainer.get<IExtensionActivator>(IExtensionActivator, activatorName);
45+
this.currentActivator = { jedi, activator };
46+
47+
await activator.activate();
48+
}
49+
public dispose() {
50+
if (this.currentActivator) {
51+
this.currentActivator.activator.deactivate().ignoreErrors();
52+
}
53+
}
54+
private async onDidChangeConfiguration(event: ConfigurationChangeEvent) {
55+
const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders!.map(workspace => workspace.uri) : [undefined];
56+
if (workspacesUris.findIndex(uri => event.affectsConfiguration(`python.${jediEnabledSetting}`, uri)) === -1) {
57+
return;
58+
}
59+
const jedi = this.useJedi();
60+
if (this.currentActivator && this.currentActivator.jedi === jedi) {
61+
return;
62+
}
63+
64+
const item = await this.appShell.showInformationMessage('Please reload the window switching between the analysis engines.', 'Reload');
65+
if (item === 'Reload') {
66+
this.serviceContainer.get<ICommandManager>(ICommandManager).executeCommand('workbench.action.reloadWindow');
67+
}
68+
}
69+
private useJedi(): boolean {
70+
const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders!.map(item => item.uri) : [undefined];
71+
const configuraionService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
72+
const jediEnabledForAnyWorkspace = workspacesUris.filter(uri => configuraionService.getSettings(uri).jediEnabled).length > 0;
73+
return !isPythonAnalysisEngineTest() && jediEnabledForAnyWorkspace;
74+
}
75+
}

src/client/activation/analysis.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
import { inject, injectable } from 'inversify';
45
import * as path from 'path';
56
import { ExtensionContext, OutputChannel } from 'vscode';
67
import { Message } from 'vscode-jsonrpc';
@@ -10,7 +11,7 @@ import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants';
1011
import { createDeferred, Deferred } from '../common/helpers';
1112
import { IFileSystem, IPlatformService } from '../common/platform/types';
1213
import { StopWatch } from '../common/stopWatch';
13-
import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types';
14+
import { IConfigurationService, IExtensionContext, IOutputChannel } from '../common/types';
1415
import { IEnvironmentVariablesProvider } from '../common/variables/types';
1516
import { IInterpreterService } from '../interpreter/contracts';
1617
import { IServiceContainer } from '../ioc/types';
@@ -43,6 +44,7 @@ class LanguageServerStartupErrorHandler implements ErrorHandler {
4344
}
4445
}
4546

47+
@injectable()
4648
export class AnalysisExtensionActivator implements IExtensionActivator {
4749
private readonly configuration: IConfigurationService;
4850
private readonly appShell: IApplicationShell;
@@ -53,10 +55,11 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
5355
private readonly interpreterService: IInterpreterService;
5456
private readonly disposables: Disposable[] = [];
5557
private languageClient: LanguageClient | undefined;
56-
private context: ExtensionContext | undefined;
58+
private readonly context: ExtensionContext;
5759
private interpreterHash: string = '';
5860

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

68-
public async activate(context: ExtensionContext): Promise<boolean> {
71+
public async activate(): Promise<boolean> {
6972
this.sw.reset();
70-
this.context = context;
71-
const clientOptions = await this.getAnalysisOptions(context);
73+
const clientOptions = await this.getAnalysisOptions(this.context);
7274
if (!clientOptions) {
7375
return false;
7476
}
7577
this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.restartLanguageServer()));
76-
return this.startLanguageServer(context, clientOptions);
78+
return this.startLanguageServer(this.context, clientOptions);
7779
}
7880

7981
public async deactivate(): Promise<void> {
@@ -94,7 +96,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator {
9496
if (!idata || idata.hash !== this.interpreterHash) {
9597
this.interpreterHash = idata ? idata.hash : '';
9698
await this.deactivate();
97-
await this.activate(this.context);
99+
await this.activate();
98100
}
99101
}
100102

src/client/activation/classic.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

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

23+
@injectable()
2224
export class ClassicExtensionActivator implements IExtensionActivator {
23-
constructor(private serviceManager: IServiceManager, private pythonSettings: IPythonSettings, private documentSelector: DocumentFilter[]) {
25+
private readonly context: IExtensionContext;
26+
private jediFactory?: JediFactory;
27+
private readonly documentSelector: DocumentFilter[];
28+
constructor(@inject(IServiceManager) private serviceManager: IServiceManager) {
29+
this.context = this.serviceManager.get<IExtensionContext>(IExtensionContext);
30+
this.documentSelector = PYTHON;
2431
}
2532

26-
public async activate(context: ExtensionContext): Promise<boolean> {
33+
public async activate(): Promise<boolean> {
34+
const context = this.context;
2735
const standardOutputChannel = this.serviceManager.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
2836
activateSimplePythonRefactorProvider(context, standardOutputChannel, this.serviceManager);
2937

30-
const jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager);
38+
const jediFactory = this.jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager);
3139
context.subscriptions.push(jediFactory);
3240
context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory));
3341

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

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

@@ -56,6 +65,9 @@ export class ClassicExtensionActivator implements IExtensionActivator {
5665
return true;
5766
}
5867

59-
// tslint:disable-next-line:no-empty
60-
public async deactivate(): Promise<void> { }
68+
public async deactivate(): Promise<void> {
69+
if (this.jediFactory) {
70+
this.jediFactory.dispose();
71+
}
72+
}
6173
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { IServiceManager } from '../ioc/types';
7+
import { ExtensionActivationService } from './activationService';
8+
import { AnalysisExtensionActivator } from './analysis';
9+
import { ClassicExtensionActivator } from './classic';
10+
import { ExtensionActivators, IExtensionActivationService, IExtensionActivator } from './types';
11+
12+
export function registerTypes(serviceManager: IServiceManager) {
13+
serviceManager.addSingleton<IExtensionActivationService>(IExtensionActivationService, ExtensionActivationService);
14+
serviceManager.add<IExtensionActivator>(IExtensionActivator, ClassicExtensionActivator, ExtensionActivators.Jedi);
15+
serviceManager.add<IExtensionActivator>(IExtensionActivator, AnalysisExtensionActivator, ExtensionActivators.DotNet);
16+
}

src/client/activation/types.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import * as vscode from 'vscode';
4+
export const IExtensionActivationService = Symbol('IExtensionActivationService');
5+
export interface IExtensionActivationService {
6+
activate(): Promise<void>;
7+
}
8+
9+
export enum ExtensionActivators {
10+
Jedi = 'Jedi',
11+
DotNet = 'DotNet'
12+
}
513

14+
export const IExtensionActivator = Symbol('IExtensionActivator');
615
export interface IExtensionActivator {
7-
activate(context: vscode.ExtensionContext): Promise<boolean>;
16+
activate(): Promise<boolean>;
817
deactivate(): Promise<void>;
918
}

src/client/common/constants.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ export namespace LinterErrors {
7171
export const STANDARD_OUTPUT_CHANNEL = 'STANDARD_OUTPUT_CHANNEL';
7272

7373
export function isTestExecution(): boolean {
74-
// tslint:disable-next-line:interface-name no-string-literal
75-
return process.env['VSC_PYTHON_CI_TEST'] === '1';
74+
return process.env.VSC_PYTHON_CI_TEST === '1';
7675
}
7776
export function isPythonAnalysisEngineTest(): boolean {
7877
return process.env.VSC_PYTHON_ANALYSIS === '1';

src/client/common/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
// Licensed under the MIT License.
44

55
import { Socket } from 'net';
6-
import { ConfigurationTarget, DiagnosticSeverity, Disposable, Uri } from 'vscode';
6+
import { ConfigurationTarget, DiagnosticSeverity, Disposable, ExtensionContext, OutputChannel, Uri } from 'vscode';
77

88
import { EnvironmentVariables } from './variables/types';
99
export const IOutputChannel = Symbol('IOutputChannel');
10+
export interface IOutputChannel extends OutputChannel { }
1011
export const IDocumentSymbolProvider = Symbol('IDocumentSymbolProvider');
1112
export const IsWindows = Symbol('IS_WINDOWS');
1213
export const Is64Bit = Symbol('Is64Bit');
1314
export const IDisposableRegistry = Symbol('IDiposableRegistry');
15+
export type IDisposableRegistry = Disposable[];
1416
export const IMemento = Symbol('IGlobalMemento');
1517
export const GLOBAL_MEMENTO = Symbol('IGlobalMemento');
1618
export const WORKSPACE_MEMENTO = Symbol('IWorkspaceMemento');
@@ -233,3 +235,6 @@ export interface ISocketServer extends Disposable {
233235
readonly client: Promise<Socket>;
234236
Start(options?: { port?: number; host?: string }): Promise<number>;
235237
}
238+
239+
export const IExtensionContext = Symbol('ExtensionContext');
240+
export interface IExtensionContext extends ExtensionContext { }

src/client/extension.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ import {
1111
extensions, IndentAction, languages, Memento,
1212
OutputChannel, window
1313
} from 'vscode';
14-
import { AnalysisExtensionActivator } from './activation/analysis';
15-
import { ClassicExtensionActivator } from './activation/classic';
16-
import { IExtensionActivator } from './activation/types';
14+
import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry';
15+
import { IExtensionActivationService } from './activation/types';
1716
import { PythonSettings } from './common/configSettings';
18-
import { isPythonAnalysisEngineTest, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants';
17+
import { PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants';
1918
import { FeatureDeprecationManager } from './common/featureDeprecationManager';
2019
import { createDeferred } from './common/helpers';
2120
import { PythonInstaller } from './common/installer/pythonInstallation';
@@ -25,7 +24,7 @@ import { registerTypes as processRegisterTypes } from './common/process/serviceR
2524
import { registerTypes as commonRegisterTypes } from './common/serviceRegistry';
2625
import { StopWatch } from './common/stopWatch';
2726
import { ITerminalHelper } from './common/terminal/types';
28-
import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types';
27+
import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, IExtensionContext, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types';
2928
import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry';
3029
import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts';
3130
import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider';
@@ -37,7 +36,7 @@ import { ICondaService, IInterpreterService } from './interpreter/contracts';
3736
import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry';
3837
import { ServiceContainer } from './ioc/container';
3938
import { ServiceManager } from './ioc/serviceManager';
40-
import { IServiceContainer } from './ioc/types';
39+
import { IServiceContainer, IServiceManager } from './ioc/types';
4140
import { LinterCommands } from './linters/linterCommands';
4241
import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry';
4342
import { ILintingEngine } from './linters/types';
@@ -76,18 +75,14 @@ export async function activate(context: ExtensionContext) {
7675
const configuration = serviceManager.get<IConfigurationService>(IConfigurationService);
7776
const pythonSettings = configuration.getSettings();
7877

79-
const activator: IExtensionActivator = isPythonAnalysisEngineTest() || !pythonSettings.jediEnabled
80-
? new AnalysisExtensionActivator(serviceManager, pythonSettings)
81-
: new ClassicExtensionActivator(serviceManager, pythonSettings, PYTHON);
82-
83-
await activator.activate(context);
78+
const activationService = serviceContainer.get<IExtensionActivationService>(IExtensionActivationService);
79+
await activationService.activate();
8480

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

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

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

161156
function registerServices(context: ExtensionContext, serviceManager: ServiceManager, serviceContainer: ServiceContainer) {
162157
serviceManager.addSingletonInstance<IServiceContainer>(IServiceContainer, serviceContainer);
158+
serviceManager.addSingletonInstance<IServiceManager>(IServiceManager, serviceManager);
163159
serviceManager.addSingletonInstance<Disposable[]>(IDisposableRegistry, context.subscriptions);
164160
serviceManager.addSingletonInstance<Memento>(IMemento, context.globalState, GLOBAL_MEMENTO);
165161
serviceManager.addSingletonInstance<Memento>(IMemento, context.workspaceState, WORKSPACE_MEMENTO);
162+
serviceManager.addSingletonInstance<IExtensionContext>(IExtensionContext, context);
166163

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

169+
activationRegisterTypes(serviceManager);
172170
commonRegisterTypes(serviceManager);
173171
processRegisterTypes(serviceManager);
174172
variableRegisterTypes(serviceManager);

src/client/formatters/baseFormatter.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ export abstract class BaseFormatter {
4141
return vscode.Uri.file(__dirname);
4242
}
4343
protected async provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, args: string[], cwd?: string): Promise<vscode.TextEdit[]> {
44-
this.outputChannel.clear();
4544
if (typeof cwd !== 'string' || cwd.length === 0) {
4645
cwd = this.getWorkspaceUri(document).fsPath;
4746
}

0 commit comments

Comments
 (0)