Skip to content

Use a single language server instance when type set to Node #11315

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

Merged
merged 4 commits into from
Apr 22, 2020
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
6 changes: 6 additions & 0 deletions src/client/activation/activationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,12 @@ export class LanguageServerExtensionActivationService
}
}
private async getKey(resource: Resource, interpreter?: PythonInterpreter): Promise<string> {
const configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
const serverType = configurationService.getSettings(this.resource).languageServer;
if (serverType === LanguageServerType.Node) {
return 'shared-ls';
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 ensures only a single instance is used.

}

const resourcePortion = this.workspaceService.getWorkspaceFolderIdentifier(
resource,
workspacePathNameForGlobalWorkspaces
Expand Down
95 changes: 95 additions & 0 deletions src/client/activation/common/analysisOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { injectable } from 'inversify';
import { Disposable, Event, EventEmitter, WorkspaceFolder } from 'vscode';
import { DocumentFilter, LanguageClientOptions, RevealOutputChannelOn } from 'vscode-languageclient';

import { PYTHON_LANGUAGE } from '../../common/constants';
import { traceDecorators } from '../../common/logger';
import { IOutputChannel, Resource } from '../../common/types';
import { debounceSync } from '../../common/utils/decorators';
import { IEnvironmentVariablesProvider } from '../../common/variables/types';
import { PythonInterpreter } from '../../interpreter/contracts';
import { ILanguageServerAnalysisOptions, ILanguageServerOutputChannel } from '../types';

@injectable()
export abstract class LanguageServerAnalysisOptionsBase implements ILanguageServerAnalysisOptions {
protected disposables: Disposable[] = [];
protected readonly didChange = new EventEmitter<void>();
private envPythonPath: string = '';
private readonly output: IOutputChannel;

protected constructor(
private readonly envVarsProvider: IEnvironmentVariablesProvider,
lsOutputChannel: ILanguageServerOutputChannel
) {
this.output = lsOutputChannel.channel;
}

public async initialize(_resource: Resource, _interpreter: PythonInterpreter | undefined) {
const disposable = this.envVarsProvider.onDidEnvironmentVariablesChange(this.onEnvVarChange, this);
this.disposables.push(disposable);
}

public get onDidChange(): Event<void> {
return this.didChange.event;
}

public dispose(): void {
this.disposables.forEach((d) => d.dispose());
this.didChange.dispose();
}

@traceDecorators.error('Failed to get analysis options')
public async getAnalysisOptions(): Promise<LanguageClientOptions> {
const workspaceFolder = this.getWorkspaceFolder();
const documentSelector = this.getDocumentFilters(workspaceFolder);

return {
documentSelector,
workspaceFolder,
synchronize: {
configurationSection: PYTHON_LANGUAGE
},
outputChannel: this.output,
revealOutputChannelOn: RevealOutputChannelOn.Never,
initializationOptions: await this.getInitializationOptions()
};
}

protected getWorkspaceFolder(): WorkspaceFolder | undefined {
return undefined;
}

protected getDocumentFilters(_workspaceFolder?: WorkspaceFolder): DocumentFilter[] {
return [
{ scheme: 'file', language: PYTHON_LANGUAGE },
{ scheme: 'untitled', language: PYTHON_LANGUAGE }
];
}

// tslint:disable-next-line: no-any
protected async getInitializationOptions(): Promise<any> {
return undefined;
}

protected async getEnvPythonPath(): Promise<string> {
const vars = await this.envVarsProvider.getEnvironmentVariables();
this.envPythonPath = vars.PYTHONPATH || '';
return this.envPythonPath;
}

@debounceSync(1000)
protected onEnvVarChange(): void {
this.notifyifEnvPythonPathChanged().ignoreErrors();
}

protected async notifyifEnvPythonPathChanged(): Promise<void> {
const vars = await this.envVarsProvider.getEnvironmentVariables();
const envPythonPath = vars.PYTHONPATH || '';

if (this.envPythonPath !== envPythonPath) {
this.didChange.fire();
}
}
}
159 changes: 54 additions & 105 deletions src/client/activation/languageServer/analysisOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,81 @@
// Licensed under the MIT License.
import { inject, injectable } from 'inversify';
import * as path from 'path';
import { ConfigurationChangeEvent, Disposable, Event, EventEmitter, WorkspaceFolder } from 'vscode';
import { DocumentFilter, DocumentSelector, LanguageClientOptions, RevealOutputChannelOn } from 'vscode-languageclient';
import { ConfigurationChangeEvent, WorkspaceFolder } from 'vscode';
import { DocumentFilter } from 'vscode-languageclient';

import { IWorkspaceService } from '../../common/application/types';
import { isTestExecution, PYTHON_LANGUAGE } from '../../common/constants';
import { traceDecorators, traceError } from '../../common/logger';
import { IConfigurationService, IExtensionContext, IOutputChannel, IPathUtils, Resource } from '../../common/types';
import { IConfigurationService, IExtensionContext, IPathUtils, Resource } from '../../common/types';
import { debounceSync } from '../../common/utils/decorators';
import { IEnvironmentVariablesProvider } from '../../common/variables/types';
import { PythonInterpreter } from '../../interpreter/contracts';
import { ILanguageServerAnalysisOptions, ILanguageServerFolderService, ILanguageServerOutputChannel } from '../types';
import { LanguageServerAnalysisOptionsBase } from '../common/analysisOptions';
import { ILanguageServerFolderService, ILanguageServerOutputChannel } from '../types';

@injectable()
export class LanguageServerAnalysisOptions implements ILanguageServerAnalysisOptions {
private envPythonPath: string = '';
private excludedFiles: string[] = [];
private typeshedPaths: string[] = [];
private disposables: Disposable[] = [];
private languageServerFolder: string = '';
export class DotNetLanguageServerAnalysisOptions extends LanguageServerAnalysisOptionsBase {
private resource: Resource;
private interpreter: PythonInterpreter | undefined;
private output: IOutputChannel;
private readonly didChange = new EventEmitter<void>();
private languageServerFolder: string = '';
private excludedFiles: string[] = [];
private typeshedPaths: string[] = [];

constructor(
@inject(IExtensionContext) private readonly context: IExtensionContext,
@inject(IEnvironmentVariablesProvider) private readonly envVarsProvider: IEnvironmentVariablesProvider,
@inject(IEnvironmentVariablesProvider) envVarsProvider: IEnvironmentVariablesProvider,
@inject(IConfigurationService) private readonly configuration: IConfigurationService,
@inject(IWorkspaceService) private readonly workspace: IWorkspaceService,
@inject(ILanguageServerOutputChannel) private readonly lsOutputChannel: ILanguageServerOutputChannel,
@inject(ILanguageServerOutputChannel) lsOutputChannel: ILanguageServerOutputChannel,
@inject(IPathUtils) private readonly pathUtils: IPathUtils,
@inject(ILanguageServerFolderService) private readonly languageServerFolderService: ILanguageServerFolderService
) {
this.output = this.lsOutputChannel.channel;
super(envVarsProvider, lsOutputChannel);
}

public async initialize(resource: Resource, interpreter: PythonInterpreter | undefined) {
await super.initialize(resource, interpreter);

this.resource = resource;
this.interpreter = interpreter;
this.languageServerFolder = await this.languageServerFolderService.getLanguageServerFolderName(resource);

let disposable = this.workspace.onDidChangeConfiguration(this.onSettingsChangedHandler, this);
this.disposables.push(disposable);

disposable = this.envVarsProvider.onDidEnvironmentVariablesChange(this.onEnvVarChange, this);
const disposable = this.workspace.onDidChangeConfiguration(this.onSettingsChangedHandler, this);
this.disposables.push(disposable);
}
public get onDidChange(): Event<void> {
return this.didChange.event;

protected getWorkspaceFolder(): WorkspaceFolder | undefined {
return this.workspace.getWorkspaceFolder(this.resource);
}
public dispose(): void {
this.disposables.forEach((d) => d.dispose());
this.didChange.dispose();

protected getDocumentFilters(workspaceFolder?: WorkspaceFolder): DocumentFilter[] {
const filters = super.getDocumentFilters(workspaceFolder);

// Set the document selector only when in a multi-root workspace scenario.
if (
workspaceFolder &&
Array.isArray(this.workspace.workspaceFolders) &&
this.workspace.workspaceFolders!.length > 1
) {
filters[0].pattern = `${workspaceFolder.uri.fsPath}/**/*`;
}

return filters;
}
// tslint:disable-next-line: max-func-body-length
@traceDecorators.error('Failed to get analysis options')
public async getAnalysisOptions(): Promise<LanguageClientOptions> {

protected async getInitializationOptions() {
const properties: Record<string, {}> = {};

const interpreterInfo = this.interpreter;
if (!interpreterInfo) {
// tslint:disable-next-line:no-suspicious-comment
// TODO: How do we handle this? It is pretty unlikely...
throw Error('did not find an active interpreter');
}

// tslint:disable-next-line:no-string-literal
properties['InterpreterPath'] = interpreterInfo.path;
properties.InterpreterPath = interpreterInfo.path;

const version = interpreterInfo.version;
if (version) {
// tslint:disable-next-line:no-string-literal
properties['Version'] = `${version.major}.${version.minor}.${version.patch}`;
properties.Version = `${version.major}.${version.minor}.${version.patch}`;
} else {
traceError('Unable to determine Python version. Analysis may be limited.');
}
Expand All @@ -87,72 +91,25 @@ export class LanguageServerAnalysisOptions implements ILanguageServerAnalysisOpt
}
}

// tslint:disable-next-line: no-suspicious-comment
// TODO: remove this setting since LS 0.2.92+ is not using it.
// tslint:disable-next-line:no-string-literal
properties['DatabasePath'] = path.join(this.context.extensionPath, this.languageServerFolder);

const vars = await this.envVarsProvider.getEnvironmentVariables();
this.envPythonPath = vars.PYTHONPATH || '';
if (this.envPythonPath !== '') {
const paths = this.envPythonPath.split(this.pathUtils.delimiter).filter((item) => item.trim().length > 0);
const envPythonPath = await this.getEnvPythonPath();
if (envPythonPath !== '') {
const paths = envPythonPath.split(this.pathUtils.delimiter).filter((item) => item.trim().length > 0);
searchPaths.push(...paths);
}

searchPaths = searchPaths.map((p) => path.normalize(p));

this.excludedFiles = this.getExcludedFiles();
this.typeshedPaths = this.getTypeshedPaths();
const workspaceFolder = this.workspace.getWorkspaceFolder(this.resource);
const documentSelector = this.getDocumentSelector(workspaceFolder);
// Options to control the language client.
return {
// Register the server for Python documents.
documentSelector,
workspaceFolder,
synchronize: {
configurationSection: PYTHON_LANGUAGE
interpreter: {
properties
},
outputChannel: this.output,
revealOutputChannelOn: RevealOutputChannelOn.Never,
initializationOptions: {
interpreter: {
properties
},
displayOptions: {
preferredFormat: 'markdown',
trimDocumentationLines: false,
maxDocumentationLineLength: 0,
trimDocumentationText: false,
maxDocumentationTextLength: 0
},
searchPaths,
typeStubSearchPaths: this.typeshedPaths,
cacheFolderPath: this.getCacheFolderPath(),
excludeFiles: this.excludedFiles,
testEnvironment: isTestExecution(),
analysisUpdates: true,
traceLogging: true, // Max level, let LS decide through settings actual level of logging.
asyncStartup: true
}
searchPaths,
typeStubSearchPaths: this.typeshedPaths,
cacheFolderPath: this.getCacheFolderPath(),
excludeFiles: this.excludedFiles
};
}
protected getDocumentSelector(workspaceFolder?: WorkspaceFolder): DocumentSelector {
const documentSelector: DocumentFilter[] = [
{ scheme: 'file', language: PYTHON_LANGUAGE },
{ scheme: 'untitled', language: PYTHON_LANGUAGE }
];
// Set the document selector only when in a multi-root workspace scenario.
if (
workspaceFolder &&
Array.isArray(this.workspace.workspaceFolders) &&
this.workspace.workspaceFolders!.length > 1
) {
// tslint:disable-next-line:no-any
documentSelector[0].pattern = `${workspaceFolder.uri.fsPath}/**/*`;
}
return documentSelector;
}

protected getExcludedFiles(): string[] {
const list: string[] = ['**/Lib/**', '**/site-packages/**'];
this.getVsCodeExcludeSection('search.exclude', list);
Expand All @@ -170,35 +127,41 @@ export class LanguageServerAnalysisOptions implements ILanguageServerAnalysisOpt
.forEach((p) => list.push(p));
}
}

protected getPythonExcludeSection(list: string[]): void {
const pythonSettings = this.configuration.getSettings(this.resource);
const paths = pythonSettings && pythonSettings.linting ? pythonSettings.linting.ignorePatterns : undefined;
if (paths && Array.isArray(paths)) {
paths.filter((p) => p && p.length > 0).forEach((p) => list.push(p));
}
}

protected getTypeshedPaths(): string[] {
const settings = this.configuration.getSettings(this.resource);
return settings.analysis.typeshedPaths && settings.analysis.typeshedPaths.length > 0
? settings.analysis.typeshedPaths
: [path.join(this.context.extensionPath, this.languageServerFolder, 'Typeshed')];
}

protected getCacheFolderPath(): string | null {
const settings = this.configuration.getSettings(this.resource);
return settings.analysis.cacheFolderPath && settings.analysis.cacheFolderPath.length > 0
? settings.analysis.cacheFolderPath
: null;
}

protected async onSettingsChangedHandler(e?: ConfigurationChangeEvent): Promise<void> {
if (e && !e.affectsConfiguration('python', this.resource)) {
return;
}
this.onSettingsChanged();
}

@debounceSync(1000)
protected onSettingsChanged(): void {
this.notifyIfSettingsChanged().ignoreErrors();
}

@traceDecorators.verbose('Changes in python settings detected in analysis options')
protected async notifyIfSettingsChanged(): Promise<void> {
const excludedFiles = this.getExcludedFiles();
Expand All @@ -221,18 +184,4 @@ export class LanguageServerAnalysisOptions implements ILanguageServerAnalysisOpt
}
}
}

@debounceSync(1000)
protected onEnvVarChange(): void {
this.notifyifEnvPythonPathChanged().ignoreErrors();
}

protected async notifyifEnvPythonPathChanged(): Promise<void> {
const vars = await this.envVarsProvider.getEnvironmentVariables();
const envPythonPath = vars.PYTHONPATH || '';

if (this.envPythonPath !== envPythonPath) {
this.didChange.fire();
}
}
}
4 changes: 3 additions & 1 deletion src/client/activation/languageServer/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export class DotNetLanguageServerManager implements ILanguageServerManager {
private connected: boolean = false;
constructor(
@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer,
@inject(ILanguageServerAnalysisOptions) private readonly analysisOptions: ILanguageServerAnalysisOptions,
@inject(ILanguageServerAnalysisOptions)
@named(LanguageServerType.Microsoft)
private readonly analysisOptions: ILanguageServerAnalysisOptions,
@inject(ILanguageServerExtension) private readonly lsExtension: ILanguageServerExtension,
@inject(IPythonExtensionBanner)
@named(BANNER_NAME_LS_SURVEY)
Expand Down
17 changes: 17 additions & 0 deletions src/client/activation/node/analysisOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { inject, injectable } from 'inversify';

import { IEnvironmentVariablesProvider } from '../../common/variables/types';
import { LanguageServerAnalysisOptionsBase } from '../common/analysisOptions';
import { ILanguageServerOutputChannel } from '../types';

@injectable()
export class NodeLanguageServerAnalysisOptions extends LanguageServerAnalysisOptionsBase {
Copy link
Member Author

Choose a reason for hiding this comment

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

Empty class; no options are needed to configure this other than the defaults.

constructor(
@inject(IEnvironmentVariablesProvider) envVarsProvider: IEnvironmentVariablesProvider,
@inject(ILanguageServerOutputChannel) lsOutputChannel: ILanguageServerOutputChannel
) {
super(envVarsProvider, lsOutputChannel);
}
}
Loading