Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for multi root workspaces with the new language server server #4244

Merged
merged 12 commits into from
Feb 12, 2019
Merged
1 change: 1 addition & 0 deletions news/2 Fixes/3008.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for multi root workspaces with the new language server server
9 changes: 9 additions & 0 deletions src/client/activation/activationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ export class ExtensionActivationManager implements IExtensionActivationManager {
}
}
protected onWorkspaceFoldersChanged() {
//If an activated workspace folder was removed, delete its key
const workspaceKeys = this.workspaceService.workspaceFolders!.map(workspaceFolder => this.getWorkspaceKey(workspaceFolder.uri));
const activatedWkspcKeys = Array.from(this.activatedWorkspaces.keys());
const activatedWkspcFoldersRemoved = activatedWkspcKeys.filter(item => workspaceKeys.indexOf(item) < 0);
if (activatedWkspcFoldersRemoved.length > 0) {
for (const folder of activatedWkspcFoldersRemoved) {
this.activatedWorkspaces.delete(folder);
}
}
this.addRemoveDocOpenedHandlers();
}
protected hasMultipleWorkspaces() {
Expand Down
49 changes: 38 additions & 11 deletions src/client/activation/activationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import { EventName } from '../telemetry/constants';
import { IExtensionActivationService, ILanguageServerActivator, LanguageServerActivator } from './types';

const jediEnabledSetting: keyof IPythonSettings = 'jediEnabled';
const workspacePathNameForGlobalWorkspaces = '';
type ActivatorInfo = { jedi: boolean; activator: ILanguageServerActivator };

@injectable()
export class LanguageServerExtensionActivationService implements IExtensionActivationService, Disposable {
private lsActivatedWorkspaces = new Map<string, ILanguageServerActivator>();
private currentActivator?: ActivatorInfo;
private activatedOnce: boolean = false;
private jediActivatedOnce: boolean = false;
private readonly workspaceService: IWorkspaceService;
private readonly output: OutputChannel;
private readonly appShell: IApplicationShell;
Expand All @@ -40,45 +42,54 @@ export class LanguageServerExtensionActivationService implements IExtensionActiv
const disposables = serviceContainer.get<IDisposableRegistry>(IDisposableRegistry);
disposables.push(this);
disposables.push(this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)));
disposables.push(this.workspaceService.onDidChangeWorkspaceFolders(this.onWorkspaceFoldersChanged, this));
}

public async activate(resource: Resource): Promise<void> {
if (this.currentActivator || this.activatedOnce) {
return;
}
this.resource = resource;
this.activatedOnce = true;

let jedi = this.useJedi();
if (!jedi) {
if (this.lsActivatedWorkspaces.has(this.getWorkspacePathKey(resource))) {
return;
}
const diagnostic = await this.lsNotSupportedDiagnosticService.diagnose(undefined);
this.lsNotSupportedDiagnosticService.handle(diagnostic).ignoreErrors();
if (diagnostic.length) {
sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_PLATFORM_NOT_SUPPORTED);
jedi = true;
}
} else {
if (this.jediActivatedOnce) {
return;
}
this.jediActivatedOnce = true;
}

this.resource = resource;
await this.logStartup(jedi);

let activatorName = jedi ? LanguageServerActivator.Jedi : LanguageServerActivator.DotNet;
let activator = this.serviceContainer.get<ILanguageServerActivator>(ILanguageServerActivator, activatorName);
this.currentActivator = { jedi, activator };

try {
await activator.activate();
return;
await activator.activate(resource);
if (!jedi) {
this.lsActivatedWorkspaces.set(this.getWorkspacePathKey(resource), activator);
}
} catch (ex) {
if (jedi) {
return;
}
//Language server fails, reverting to jedi
if (this.jediActivatedOnce) {
return;
}
this.jediActivatedOnce = true;
jedi = true;
await this.logStartup(jedi);
activatorName = LanguageServerActivator.Jedi;
activator = this.serviceContainer.get<ILanguageServerActivator>(ILanguageServerActivator, activatorName);
this.currentActivator = { jedi, activator };
await activator.activate();
await activator.activate(resource);
}
}

Expand All @@ -88,6 +99,19 @@ export class LanguageServerExtensionActivationService implements IExtensionActiv
}
}

protected onWorkspaceFoldersChanged() {
//If an activated workspace folder was removed, dispose its activator
const workspaceKeys = this.workspaceService.workspaceFolders!.map(workspaceFolder => this.getWorkspacePathKey(workspaceFolder.uri));
const activatedWkspcKeys = Array.from(this.lsActivatedWorkspaces.keys());
const activatedWkspcFoldersRemoved = activatedWkspcKeys.filter(item => workspaceKeys.indexOf(item) < 0);
if (activatedWkspcFoldersRemoved.length > 0) {
for (const folder of activatedWkspcFoldersRemoved) {
this.lsActivatedWorkspaces.get(folder).dispose();
this.lsActivatedWorkspaces.delete(folder);
}
}
}

private async logStartup(isJedi: boolean): Promise<void> {
const outputLine = isJedi
? 'Starting Jedi Python language engine.'
Expand Down Expand Up @@ -119,4 +143,7 @@ export class LanguageServerExtensionActivationService implements IExtensionActiv
const configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
return configurationService.getSettings(this.resource).jediEnabled;
}
private getWorkspacePathKey(resource: Resource): string {
return this.workspaceService.getWorkspaceFolderIdentifier(resource, workspacePathNameForGlobalWorkspaces);
}
}
7 changes: 5 additions & 2 deletions src/client/activation/jedi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { inject, injectable } from 'inversify';
import { DocumentFilter, languages } from 'vscode';
import { PYTHON } from '../common/constants';
import { IConfigurationService, IExtensionContext, ILogger } from '../common/types';
import { IConfigurationService, IExtensionContext, ILogger, Resource } from '../common/types';
import { IShebangCodeLensProvider } from '../interpreter/contracts';
import { IServiceContainer, IServiceManager } from '../ioc/types';
import { JediFactory } from '../languageServices/jediProxyFactory';
Expand Down Expand Up @@ -33,7 +33,10 @@ export class JediExtensionActivator implements ILanguageServerActivator {
this.documentSelector = PYTHON;
}

public async activate(): Promise<void> {
public async activate(resource: Resource): Promise<void> {
if (this.jediFactory) {
throw new Error('Jedi already started');
}
const context = this.context;

const jediFactory = (this.jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager));
Expand Down
16 changes: 9 additions & 7 deletions src/client/activation/languageServer/activator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ export class LanguageServerExtensionActivator implements ILanguageServerActivato
@inject(ILanguageServerFolderService)
private readonly languageServerFolderService: ILanguageServerFolderService,
@inject(IConfigurationService) private readonly configurationService: IConfigurationService
) {}
) { }
@traceDecorators.error('Failed to activate language server')
public async activate(): Promise<void> {
const mainWorkspaceUri = this.workspace.hasWorkspaceFolders
? this.workspace.workspaceFolders![0].uri
: undefined;
await this.ensureLanguageServerIsAvailable(mainWorkspaceUri);
await this.manager.start(mainWorkspaceUri);
public async activate(resource: Resource): Promise<void> {
if (!resource) {
resource = this.workspace.hasWorkspaceFolders
? this.workspace.workspaceFolders![0].uri
: undefined;
}
await this.ensureLanguageServerIsAvailable(resource);
await this.manager.start(resource);
}
public dispose(): void {
this.manager.dispose();
Expand Down
17 changes: 13 additions & 4 deletions src/client/activation/languageServer/analysisOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as path from 'path';
import { CancellationToken, CompletionContext, ConfigurationChangeEvent, Disposable, Event, EventEmitter, OutputChannel, Position, TextDocument } from 'vscode';
import { LanguageClientOptions, ProvideCompletionItemsSignature } from 'vscode-languageclient';
import { IWorkspaceService } from '../../common/application/types';
import { isTestExecution, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from '../../common/constants';
import { isTestExecution, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from '../../common/constants';
import { traceDecorators, traceError } from '../../common/logger';
import { BANNER_NAME_PROPOSE_LS, IConfigurationService, IExtensionContext, IOutputChannel, IPathUtils, IPythonExtensionBanner, Resource } from '../../common/types';
import { debounce } from '../../common/utils/decorators';
Expand Down Expand Up @@ -79,7 +79,7 @@ export class LanguageServerAnalysisOptions implements ILanguageServerAnalysisOpt
properties['DatabasePath'] = path.join(this.context.extensionPath, this.languageServerFolder);

let searchPaths = interpreterData ? interpreterData.searchPaths.split(path.delimiter) : [];
const settings = this.configuration.getSettings();
const settings = this.configuration.getSettings(this.resource);
if (settings.autoComplete) {
const extraPaths = settings.autoComplete.extraPaths;
if (extraPaths && extraPaths.length > 0) {
Expand All @@ -99,11 +99,20 @@ export class LanguageServerAnalysisOptions implements ILanguageServerAnalysisOpt

this.excludedFiles = this.getExcludedFiles();
this.typeshedPaths = this.getTypeshedPaths();

const workspaceFolder = this.workspace.getWorkspaceFolder(this.resource);
const documentSelector = [
{ scheme: 'file', language: PYTHON_LANGUAGE },
{ scheme: 'untitled', language: PYTHON_LANGUAGE }
];
if (workspaceFolder){
// tslint:disable-next-line:no-any
(documentSelector[0] as any).pattern = `${workspaceFolder.uri.fsPath}/**/*`;
}
// Options to control the language client
return {
// Register the server for Python documents
documentSelector: PYTHON,
documentSelector,
workspaceFolder,
synchronize: {
configurationSection: PYTHON_LANGUAGE
},
Expand Down
38 changes: 38 additions & 0 deletions src/client/activation/languageServer/languageServerExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import { inject, injectable } from 'inversify';
import { Event, EventEmitter } from 'vscode';
import { ICommandManager } from '../../common/application/types';
import '../../common/extensions';
import { IDisposable } from '../../common/types';
import { ILanguageServerExtension } from '../types';

const loadExtensionCommand = 'python._loadLanguageServerExtension';

@injectable()
export class LanguageServerExtension implements ILanguageServerExtension {
public loadExtensionArgs?: {};
protected readonly _invoked = new EventEmitter<void>();
private disposable?: IDisposable;
constructor(@inject(ICommandManager) private readonly commandManager: ICommandManager) { }
public dispose() {
if (this.disposable) {
this.disposable.dispose();
}
}
public register(): Promise<void> {
if (this.disposable) {
return;
}
this.disposable = this.commandManager.registerCommand(loadExtensionCommand, args => {
this.loadExtensionArgs = args;
this._invoked.fire();
});
}
public get invoked(): Event<void> {
return this._invoked.event;
}
}
22 changes: 7 additions & 15 deletions src/client/activation/languageServer/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,25 @@
'use strict';

import { inject, injectable } from 'inversify';
import { ICommandManager } from '../../common/application/types';
import '../../common/extensions';
import { traceDecorators } from '../../common/logger';
import { IDisposable, Resource } from '../../common/types';
import { debounce } from '../../common/utils/decorators';
import { IServiceContainer } from '../../ioc/types';
import { captureTelemetry } from '../../telemetry';
import { EventName } from '../../telemetry/constants';
import { ILanguageServer, ILanguageServerAnalysisOptions, ILanguageServerManager } from '../types';

const loadExtensionCommand = 'python._loadLanguageServerExtension';
import { ILanguageServer, ILanguageServerAnalysisOptions, ILanguageServerExtension, ILanguageServerManager } from '../types';

@injectable()
export class LanguageServerManager implements ILanguageServerManager {
protected static loadExtensionArgs?: {};
private languageServer?: ILanguageServer;
private resource!: Resource;
private disposables: IDisposable[] = [];
constructor(
@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer,
@inject(ICommandManager) private readonly commandManager: ICommandManager,
@inject(ILanguageServerAnalysisOptions) private readonly analysisOptions: ILanguageServerAnalysisOptions
) {}
@inject(ILanguageServerAnalysisOptions) private readonly analysisOptions: ILanguageServerAnalysisOptions,
@inject(ILanguageServerExtension) private readonly lsExtension: ILanguageServerExtension
) { }
public dispose() {
if (this.languageServer) {
this.languageServer.dispose();
Expand All @@ -46,15 +42,11 @@ export class LanguageServerManager implements ILanguageServerManager {
await this.startLanguageServer();
}
protected registerCommandHandler() {
const disposable = this.commandManager.registerCommand(loadExtensionCommand, args => {
LanguageServerManager.loadExtensionArgs = args;
this.loadExtensionIfNecessary();
});
this.disposables.push(disposable);
this.lsExtension.invoked(this.loadExtensionIfNecessary, this, this.disposables);
}
protected loadExtensionIfNecessary() {
if (this.languageServer && LanguageServerManager.loadExtensionArgs) {
this.languageServer.loadExtension(LanguageServerManager.loadExtensionArgs);
if (this.languageServer && this.lsExtension.loadExtensionArgs) {
this.languageServer.loadExtension(this.lsExtension.loadExtensionArgs);
}
}
@debounce(1000)
Expand Down
6 changes: 4 additions & 2 deletions src/client/activation/serviceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,18 @@ import { InterpreterDataService } from './languageServer/interpreterDataService'
import { BaseLanguageClientFactory, DownloadedLanguageClientFactory, SimpleLanguageClientFactory } from './languageServer/languageClientFactory';
import { LanguageServer } from './languageServer/languageServer';
import { LanguageServerCompatibilityService } from './languageServer/languageServerCompatibilityService';
import { LanguageServerExtension } from './languageServer/languageServerExtension';
import { LanguageServerFolderService } from './languageServer/languageServerFolderService';
import { BetaLanguageServerPackageRepository, DailyLanguageServerPackageRepository, LanguageServerDownloadChannel, StableLanguageServerPackageRepository } from './languageServer/languageServerPackageRepository';
import { LanguageServerPackageService } from './languageServer/languageServerPackageService';
import { LanguageServerManager } from './languageServer/manager';
import { PlatformData } from './languageServer/platformData';
import { IDownloadChannelRule, IExtensionActivationManager, IExtensionActivationService, IInterpreterDataService, ILanguageClientFactory, ILanguageServer, ILanguageServerActivator, ILanguageServerAnalysisOptions, ILanguageServerCompatibilityService as ILanagueServerCompatibilityService, ILanguageServerDownloader, ILanguageServerFolderService, ILanguageServerManager, ILanguageServerPackageService, IPlatformData, LanguageClientFactory, LanguageServerActivator } from './types';
import { IDownloadChannelRule, IExtensionActivationManager, IExtensionActivationService, IInterpreterDataService, ILanguageClientFactory, ILanguageServer, ILanguageServerActivator, ILanguageServerAnalysisOptions, ILanguageServerCompatibilityService as ILanagueServerCompatibilityService, ILanguageServerDownloader, ILanguageServerExtension, ILanguageServerFolderService, ILanguageServerManager, ILanguageServerPackageService, IPlatformData, LanguageClientFactory, LanguageServerActivator } from './types';

export function registerTypes(serviceManager: IServiceManager) {
serviceManager.addSingleton<IExtensionActivationManager>(IExtensionActivationManager, ExtensionActivationManager);
serviceManager.addSingleton<IExtensionActivationService>(IExtensionActivationService, LanguageServerExtensionActivationService);
serviceManager.addSingleton<ILanguageServerExtension>(ILanguageServerExtension, LanguageServerExtension);
serviceManager.add<IExtensionActivationManager>(IExtensionActivationManager, ExtensionActivationManager);
serviceManager.add<ILanguageServerActivator>(ILanguageServerActivator, JediExtensionActivator, LanguageServerActivator.Jedi);
serviceManager.add<ILanguageServerActivator>(ILanguageServerActivator, LanguageServerExtensionActivator, LanguageServerActivator.DotNet);
serviceManager.addSingleton<IPythonExtensionBanner>(IPythonExtensionBanner, LanguageServerSurveyBanner, BANNER_NAME_LS_SURVEY);
Expand Down
8 changes: 7 additions & 1 deletion src/client/activation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export enum LanguageServerActivator {

export const ILanguageServerActivator = Symbol('ILanguageServerActivator');
export interface ILanguageServerActivator extends IDisposable {
activate(): Promise<void>;
activate(resource: Resource): Promise<void>;
}

export const IHttpClient = Symbol('IHttpClient');
Expand Down Expand Up @@ -88,6 +88,12 @@ export const ILanguageServerManager = Symbol('ILanguageServerManager');
export interface ILanguageServerManager extends IDisposable {
start(resource: Resource): Promise<void>;
}
export const ILanguageServerExtension = Symbol('ILanguageServerExtension');
export interface ILanguageServerExtension extends IDisposable {
readonly invoked: Event<void>;
loadExtensionArgs?: {};
register(): void;
}
export const ILanguageServer = Symbol('ILanguageServer');
export interface ILanguageServer extends IDisposable {
start(resource: Resource, options: LanguageClientOptions): Promise<void>;
Expand Down
12 changes: 12 additions & 0 deletions src/client/common/configSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,17 @@ export class PythonSettings implements IPythonSettings {
protected getPythonExecutable(pythonPath: string) {
return getPythonExecutable(pythonPath);
}
protected onWorkspaceFoldersChanged() {
//If an activated workspace folder was removed, delete its key
const workspaceKeys = this.workspace.workspaceFolders!.map(workspaceFolder => workspaceFolder.uri.fsPath);
const activatedWkspcKeys = Array.from(PythonSettings.pythonSettings.keys());
const activatedWkspcFoldersRemoved = activatedWkspcKeys.filter(item => workspaceKeys.indexOf(item) < 0);
if (activatedWkspcFoldersRemoved.length > 0) {
for (const folder of activatedWkspcFoldersRemoved) {
PythonSettings.pythonSettings.delete(folder);
}
}
}
protected initialize(): void {
const onDidChange = () => {
const currentConfig = this.workspace.getConfiguration('python', this.workspaceRoot);
Expand All @@ -382,6 +393,7 @@ export class PythonSettings implements IPythonSettings {
// Let's defer the change notification.
this.debounceChangeNotification();
};
this.disposables.push(this.workspace.onDidChangeWorkspaceFolders(this.onWorkspaceFoldersChanged, this));
this.disposables.push(this.interpreterAutoSelectionService.onDidChangeAutoSelectedInterpreter(onDidChange.bind(this)));
this.disposables.push(this.workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => {
if (event.affectsConfiguration('python')) {
Expand Down
Loading