Skip to content

Commit 376d6b9

Browse files
authored
Use a single language server instance when type set to Node (#11315)
* Split analysisOption implementations * Move all workspace-scoping to DotNet class * Move onDidChange back down * Clean up obsolete options
1 parent 4c430eb commit 376d6b9

File tree

10 files changed

+203
-121
lines changed

10 files changed

+203
-121
lines changed

src/client/activation/activationService.ts

+6
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,12 @@ export class LanguageServerExtensionActivationService
322322
}
323323
}
324324
private async getKey(resource: Resource, interpreter?: PythonInterpreter): Promise<string> {
325+
const configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
326+
const serverType = configurationService.getSettings(this.resource).languageServer;
327+
if (serverType === LanguageServerType.Node) {
328+
return 'shared-ls';
329+
}
330+
325331
const resourcePortion = this.workspaceService.getWorkspaceFolderIdentifier(
326332
resource,
327333
workspacePathNameForGlobalWorkspaces
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
import { injectable } from 'inversify';
4+
import { Disposable, Event, EventEmitter, WorkspaceFolder } from 'vscode';
5+
import { DocumentFilter, LanguageClientOptions, RevealOutputChannelOn } from 'vscode-languageclient';
6+
7+
import { PYTHON_LANGUAGE } from '../../common/constants';
8+
import { traceDecorators } from '../../common/logger';
9+
import { IOutputChannel, Resource } from '../../common/types';
10+
import { debounceSync } from '../../common/utils/decorators';
11+
import { IEnvironmentVariablesProvider } from '../../common/variables/types';
12+
import { PythonInterpreter } from '../../interpreter/contracts';
13+
import { ILanguageServerAnalysisOptions, ILanguageServerOutputChannel } from '../types';
14+
15+
@injectable()
16+
export abstract class LanguageServerAnalysisOptionsBase implements ILanguageServerAnalysisOptions {
17+
protected disposables: Disposable[] = [];
18+
protected readonly didChange = new EventEmitter<void>();
19+
private envPythonPath: string = '';
20+
private readonly output: IOutputChannel;
21+
22+
protected constructor(
23+
private readonly envVarsProvider: IEnvironmentVariablesProvider,
24+
lsOutputChannel: ILanguageServerOutputChannel
25+
) {
26+
this.output = lsOutputChannel.channel;
27+
}
28+
29+
public async initialize(_resource: Resource, _interpreter: PythonInterpreter | undefined) {
30+
const disposable = this.envVarsProvider.onDidEnvironmentVariablesChange(this.onEnvVarChange, this);
31+
this.disposables.push(disposable);
32+
}
33+
34+
public get onDidChange(): Event<void> {
35+
return this.didChange.event;
36+
}
37+
38+
public dispose(): void {
39+
this.disposables.forEach((d) => d.dispose());
40+
this.didChange.dispose();
41+
}
42+
43+
@traceDecorators.error('Failed to get analysis options')
44+
public async getAnalysisOptions(): Promise<LanguageClientOptions> {
45+
const workspaceFolder = this.getWorkspaceFolder();
46+
const documentSelector = this.getDocumentFilters(workspaceFolder);
47+
48+
return {
49+
documentSelector,
50+
workspaceFolder,
51+
synchronize: {
52+
configurationSection: PYTHON_LANGUAGE
53+
},
54+
outputChannel: this.output,
55+
revealOutputChannelOn: RevealOutputChannelOn.Never,
56+
initializationOptions: await this.getInitializationOptions()
57+
};
58+
}
59+
60+
protected getWorkspaceFolder(): WorkspaceFolder | undefined {
61+
return undefined;
62+
}
63+
64+
protected getDocumentFilters(_workspaceFolder?: WorkspaceFolder): DocumentFilter[] {
65+
return [
66+
{ scheme: 'file', language: PYTHON_LANGUAGE },
67+
{ scheme: 'untitled', language: PYTHON_LANGUAGE }
68+
];
69+
}
70+
71+
// tslint:disable-next-line: no-any
72+
protected async getInitializationOptions(): Promise<any> {
73+
return undefined;
74+
}
75+
76+
protected async getEnvPythonPath(): Promise<string> {
77+
const vars = await this.envVarsProvider.getEnvironmentVariables();
78+
this.envPythonPath = vars.PYTHONPATH || '';
79+
return this.envPythonPath;
80+
}
81+
82+
@debounceSync(1000)
83+
protected onEnvVarChange(): void {
84+
this.notifyifEnvPythonPathChanged().ignoreErrors();
85+
}
86+
87+
protected async notifyifEnvPythonPathChanged(): Promise<void> {
88+
const vars = await this.envVarsProvider.getEnvironmentVariables();
89+
const envPythonPath = vars.PYTHONPATH || '';
90+
91+
if (this.envPythonPath !== envPythonPath) {
92+
this.didChange.fire();
93+
}
94+
}
95+
}

src/client/activation/languageServer/analysisOptions.ts

+54-105
Original file line numberDiff line numberDiff line change
@@ -2,77 +2,81 @@
22
// Licensed under the MIT License.
33
import { inject, injectable } from 'inversify';
44
import * as path from 'path';
5-
import { ConfigurationChangeEvent, Disposable, Event, EventEmitter, WorkspaceFolder } from 'vscode';
6-
import { DocumentFilter, DocumentSelector, LanguageClientOptions, RevealOutputChannelOn } from 'vscode-languageclient';
5+
import { ConfigurationChangeEvent, WorkspaceFolder } from 'vscode';
6+
import { DocumentFilter } from 'vscode-languageclient';
77

88
import { IWorkspaceService } from '../../common/application/types';
9-
import { isTestExecution, PYTHON_LANGUAGE } from '../../common/constants';
109
import { traceDecorators, traceError } from '../../common/logger';
11-
import { IConfigurationService, IExtensionContext, IOutputChannel, IPathUtils, Resource } from '../../common/types';
10+
import { IConfigurationService, IExtensionContext, IPathUtils, Resource } from '../../common/types';
1211
import { debounceSync } from '../../common/utils/decorators';
1312
import { IEnvironmentVariablesProvider } from '../../common/variables/types';
1413
import { PythonInterpreter } from '../../interpreter/contracts';
15-
import { ILanguageServerAnalysisOptions, ILanguageServerFolderService, ILanguageServerOutputChannel } from '../types';
14+
import { LanguageServerAnalysisOptionsBase } from '../common/analysisOptions';
15+
import { ILanguageServerFolderService, ILanguageServerOutputChannel } from '../types';
1616

1717
@injectable()
18-
export class LanguageServerAnalysisOptions implements ILanguageServerAnalysisOptions {
19-
private envPythonPath: string = '';
20-
private excludedFiles: string[] = [];
21-
private typeshedPaths: string[] = [];
22-
private disposables: Disposable[] = [];
23-
private languageServerFolder: string = '';
18+
export class DotNetLanguageServerAnalysisOptions extends LanguageServerAnalysisOptionsBase {
2419
private resource: Resource;
2520
private interpreter: PythonInterpreter | undefined;
26-
private output: IOutputChannel;
27-
private readonly didChange = new EventEmitter<void>();
21+
private languageServerFolder: string = '';
22+
private excludedFiles: string[] = [];
23+
private typeshedPaths: string[] = [];
24+
2825
constructor(
2926
@inject(IExtensionContext) private readonly context: IExtensionContext,
30-
@inject(IEnvironmentVariablesProvider) private readonly envVarsProvider: IEnvironmentVariablesProvider,
27+
@inject(IEnvironmentVariablesProvider) envVarsProvider: IEnvironmentVariablesProvider,
3128
@inject(IConfigurationService) private readonly configuration: IConfigurationService,
3229
@inject(IWorkspaceService) private readonly workspace: IWorkspaceService,
33-
@inject(ILanguageServerOutputChannel) private readonly lsOutputChannel: ILanguageServerOutputChannel,
30+
@inject(ILanguageServerOutputChannel) lsOutputChannel: ILanguageServerOutputChannel,
3431
@inject(IPathUtils) private readonly pathUtils: IPathUtils,
3532
@inject(ILanguageServerFolderService) private readonly languageServerFolderService: ILanguageServerFolderService
3633
) {
37-
this.output = this.lsOutputChannel.channel;
34+
super(envVarsProvider, lsOutputChannel);
3835
}
36+
3937
public async initialize(resource: Resource, interpreter: PythonInterpreter | undefined) {
38+
await super.initialize(resource, interpreter);
39+
4040
this.resource = resource;
4141
this.interpreter = interpreter;
4242
this.languageServerFolder = await this.languageServerFolderService.getLanguageServerFolderName(resource);
4343

44-
let disposable = this.workspace.onDidChangeConfiguration(this.onSettingsChangedHandler, this);
45-
this.disposables.push(disposable);
46-
47-
disposable = this.envVarsProvider.onDidEnvironmentVariablesChange(this.onEnvVarChange, this);
44+
const disposable = this.workspace.onDidChangeConfiguration(this.onSettingsChangedHandler, this);
4845
this.disposables.push(disposable);
4946
}
50-
public get onDidChange(): Event<void> {
51-
return this.didChange.event;
47+
48+
protected getWorkspaceFolder(): WorkspaceFolder | undefined {
49+
return this.workspace.getWorkspaceFolder(this.resource);
5250
}
53-
public dispose(): void {
54-
this.disposables.forEach((d) => d.dispose());
55-
this.didChange.dispose();
51+
52+
protected getDocumentFilters(workspaceFolder?: WorkspaceFolder): DocumentFilter[] {
53+
const filters = super.getDocumentFilters(workspaceFolder);
54+
55+
// Set the document selector only when in a multi-root workspace scenario.
56+
if (
57+
workspaceFolder &&
58+
Array.isArray(this.workspace.workspaceFolders) &&
59+
this.workspace.workspaceFolders!.length > 1
60+
) {
61+
filters[0].pattern = `${workspaceFolder.uri.fsPath}/**/*`;
62+
}
63+
64+
return filters;
5665
}
57-
// tslint:disable-next-line: max-func-body-length
58-
@traceDecorators.error('Failed to get analysis options')
59-
public async getAnalysisOptions(): Promise<LanguageClientOptions> {
66+
67+
protected async getInitializationOptions() {
6068
const properties: Record<string, {}> = {};
6169

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

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

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

90-
// tslint:disable-next-line: no-suspicious-comment
91-
// TODO: remove this setting since LS 0.2.92+ is not using it.
92-
// tslint:disable-next-line:no-string-literal
93-
properties['DatabasePath'] = path.join(this.context.extensionPath, this.languageServerFolder);
94-
95-
const vars = await this.envVarsProvider.getEnvironmentVariables();
96-
this.envPythonPath = vars.PYTHONPATH || '';
97-
if (this.envPythonPath !== '') {
98-
const paths = this.envPythonPath.split(this.pathUtils.delimiter).filter((item) => item.trim().length > 0);
94+
const envPythonPath = await this.getEnvPythonPath();
95+
if (envPythonPath !== '') {
96+
const paths = envPythonPath.split(this.pathUtils.delimiter).filter((item) => item.trim().length > 0);
9997
searchPaths.push(...paths);
10098
}
10199

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

104-
this.excludedFiles = this.getExcludedFiles();
105-
this.typeshedPaths = this.getTypeshedPaths();
106-
const workspaceFolder = this.workspace.getWorkspaceFolder(this.resource);
107-
const documentSelector = this.getDocumentSelector(workspaceFolder);
108-
// Options to control the language client.
109102
return {
110-
// Register the server for Python documents.
111-
documentSelector,
112-
workspaceFolder,
113-
synchronize: {
114-
configurationSection: PYTHON_LANGUAGE
103+
interpreter: {
104+
properties
115105
},
116-
outputChannel: this.output,
117-
revealOutputChannelOn: RevealOutputChannelOn.Never,
118-
initializationOptions: {
119-
interpreter: {
120-
properties
121-
},
122-
displayOptions: {
123-
preferredFormat: 'markdown',
124-
trimDocumentationLines: false,
125-
maxDocumentationLineLength: 0,
126-
trimDocumentationText: false,
127-
maxDocumentationTextLength: 0
128-
},
129-
searchPaths,
130-
typeStubSearchPaths: this.typeshedPaths,
131-
cacheFolderPath: this.getCacheFolderPath(),
132-
excludeFiles: this.excludedFiles,
133-
testEnvironment: isTestExecution(),
134-
analysisUpdates: true,
135-
traceLogging: true, // Max level, let LS decide through settings actual level of logging.
136-
asyncStartup: true
137-
}
106+
searchPaths,
107+
typeStubSearchPaths: this.typeshedPaths,
108+
cacheFolderPath: this.getCacheFolderPath(),
109+
excludeFiles: this.excludedFiles
138110
};
139111
}
140-
protected getDocumentSelector(workspaceFolder?: WorkspaceFolder): DocumentSelector {
141-
const documentSelector: DocumentFilter[] = [
142-
{ scheme: 'file', language: PYTHON_LANGUAGE },
143-
{ scheme: 'untitled', language: PYTHON_LANGUAGE }
144-
];
145-
// Set the document selector only when in a multi-root workspace scenario.
146-
if (
147-
workspaceFolder &&
148-
Array.isArray(this.workspace.workspaceFolders) &&
149-
this.workspace.workspaceFolders!.length > 1
150-
) {
151-
// tslint:disable-next-line:no-any
152-
documentSelector[0].pattern = `${workspaceFolder.uri.fsPath}/**/*`;
153-
}
154-
return documentSelector;
155-
}
112+
156113
protected getExcludedFiles(): string[] {
157114
const list: string[] = ['**/Lib/**', '**/site-packages/**'];
158115
this.getVsCodeExcludeSection('search.exclude', list);
@@ -170,35 +127,41 @@ export class LanguageServerAnalysisOptions implements ILanguageServerAnalysisOpt
170127
.forEach((p) => list.push(p));
171128
}
172129
}
130+
173131
protected getPythonExcludeSection(list: string[]): void {
174132
const pythonSettings = this.configuration.getSettings(this.resource);
175133
const paths = pythonSettings && pythonSettings.linting ? pythonSettings.linting.ignorePatterns : undefined;
176134
if (paths && Array.isArray(paths)) {
177135
paths.filter((p) => p && p.length > 0).forEach((p) => list.push(p));
178136
}
179137
}
138+
180139
protected getTypeshedPaths(): string[] {
181140
const settings = this.configuration.getSettings(this.resource);
182141
return settings.analysis.typeshedPaths && settings.analysis.typeshedPaths.length > 0
183142
? settings.analysis.typeshedPaths
184143
: [path.join(this.context.extensionPath, this.languageServerFolder, 'Typeshed')];
185144
}
145+
186146
protected getCacheFolderPath(): string | null {
187147
const settings = this.configuration.getSettings(this.resource);
188148
return settings.analysis.cacheFolderPath && settings.analysis.cacheFolderPath.length > 0
189149
? settings.analysis.cacheFolderPath
190150
: null;
191151
}
152+
192153
protected async onSettingsChangedHandler(e?: ConfigurationChangeEvent): Promise<void> {
193154
if (e && !e.affectsConfiguration('python', this.resource)) {
194155
return;
195156
}
196157
this.onSettingsChanged();
197158
}
159+
198160
@debounceSync(1000)
199161
protected onSettingsChanged(): void {
200162
this.notifyIfSettingsChanged().ignoreErrors();
201163
}
164+
202165
@traceDecorators.verbose('Changes in python settings detected in analysis options')
203166
protected async notifyIfSettingsChanged(): Promise<void> {
204167
const excludedFiles = this.getExcludedFiles();
@@ -221,18 +184,4 @@ export class LanguageServerAnalysisOptions implements ILanguageServerAnalysisOpt
221184
}
222185
}
223186
}
224-
225-
@debounceSync(1000)
226-
protected onEnvVarChange(): void {
227-
this.notifyifEnvPythonPathChanged().ignoreErrors();
228-
}
229-
230-
protected async notifyifEnvPythonPathChanged(): Promise<void> {
231-
const vars = await this.envVarsProvider.getEnvironmentVariables();
232-
const envPythonPath = vars.PYTHONPATH || '';
233-
234-
if (this.envPythonPath !== envPythonPath) {
235-
this.didChange.fire();
236-
}
237-
}
238187
}

src/client/activation/languageServer/manager.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ export class DotNetLanguageServerManager implements ILanguageServerManager {
3838
private connected: boolean = false;
3939
constructor(
4040
@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer,
41-
@inject(ILanguageServerAnalysisOptions) private readonly analysisOptions: ILanguageServerAnalysisOptions,
41+
@inject(ILanguageServerAnalysisOptions)
42+
@named(LanguageServerType.Microsoft)
43+
private readonly analysisOptions: ILanguageServerAnalysisOptions,
4244
@inject(ILanguageServerExtension) private readonly lsExtension: ILanguageServerExtension,
4345
@inject(IPythonExtensionBanner)
4446
@named(BANNER_NAME_LS_SURVEY)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
import { inject, injectable } from 'inversify';
4+
5+
import { IEnvironmentVariablesProvider } from '../../common/variables/types';
6+
import { LanguageServerAnalysisOptionsBase } from '../common/analysisOptions';
7+
import { ILanguageServerOutputChannel } from '../types';
8+
9+
@injectable()
10+
export class NodeLanguageServerAnalysisOptions extends LanguageServerAnalysisOptionsBase {
11+
constructor(
12+
@inject(IEnvironmentVariablesProvider) envVarsProvider: IEnvironmentVariablesProvider,
13+
@inject(ILanguageServerOutputChannel) lsOutputChannel: ILanguageServerOutputChannel
14+
) {
15+
super(envVarsProvider, lsOutputChannel);
16+
}
17+
}

0 commit comments

Comments
 (0)