Skip to content

Commit 2d8dadc

Browse files
committed
Merge remote-tracking branch 'upstream/main' into report_issue
2 parents 40c292a + 6fa1cb2 commit 2d8dadc

File tree

3 files changed

+135
-73
lines changed

3 files changed

+135
-73
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { LanguageClient, LanguageClientOptions, ServerOptions } from "vscode-languageclient/node";
7+
import CompositeDisposable from "../CompositeDisposable";
8+
import { IDisposable } from "../Disposable";
9+
10+
/**
11+
* Implementation of the base LanguageClient type that allows for additional items to be disposed of
12+
* when the base LanguageClient instance is disposed.
13+
*/
14+
export class RoslynLanguageClient extends LanguageClient {
15+
16+
private readonly _disposables: CompositeDisposable;
17+
18+
constructor(
19+
id: string,
20+
name: string,
21+
serverOptions: ServerOptions,
22+
clientOptions: LanguageClientOptions,
23+
forceDebug?: boolean) {
24+
super(id, name, serverOptions, clientOptions, forceDebug);
25+
26+
this._disposables = new CompositeDisposable();
27+
}
28+
29+
override async dispose(timeout?: number | undefined): Promise<void> {
30+
this._disposables.dispose();
31+
return super.dispose(timeout);
32+
}
33+
34+
/**
35+
* Adds a disposable that should be disposed of when the LanguageClient instance gets disposed.
36+
*/
37+
public addDisposable(disposable: IDisposable) {
38+
this._disposables.add(disposable);
39+
}
40+
}

src/lsptoolshost/roslynLanguageServer.ts

Lines changed: 94 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { UriConverter } from './uriConverter';
1414
import {
1515
DidChangeTextDocumentNotification,
1616
DidCloseTextDocumentNotification,
17-
LanguageClient,
1817
LanguageClientOptions,
1918
ServerOptions,
2019
DidCloseTextDocumentParams,
@@ -53,17 +52,16 @@ import { Options } from '../shared/options';
5352
import { ServerStateChange } from './ServerStateChange';
5453
import TelemetryReporter from '@vscode/extension-telemetry';
5554
import CSharpIntelliCodeExports from '../CSharpIntelliCodeExports';
56-
import { csharpDevkitExtensionId, getCSharpDevKit } from '../utils/getCSharpDevKit';
55+
import { csharpDevkitExtensionId, csharpDevkitIntelliCodeExtensionId, getCSharpDevKit } from '../utils/getCSharpDevKit';
5756
import { randomUUID } from 'crypto';
5857
import { DotnetRuntimeExtensionResolver } from './dotnetRuntimeExtensionResolver';
5958
import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver';
59+
import { RoslynLanguageClient } from './roslynLanguageClient';
6060

6161
let _languageServer: RoslynLanguageServer;
6262
let _channel: vscode.OutputChannel;
6363
let _traceChannel: vscode.OutputChannel;
6464

65-
const csharpDevkitIntelliCodeExtensionId = "ms-dotnettools.vscodeintellicode-csharp";
66-
6765
export class RoslynLanguageServer {
6866

6967
// These are commands that are invoked by the Razor extension, and are used to send LSP requests to the Roslyn LSP server
@@ -90,7 +88,7 @@ export class RoslynLanguageServer {
9088
* The timeout for stopping the language server (in ms).
9189
*/
9290
private static _stopTimeout: number = 10000;
93-
private _languageClient: LanguageClient | undefined;
91+
private _languageClient: RoslynLanguageClient | undefined;
9492

9593
/**
9694
* Flag indicating if C# Devkit was installed the last time we activated.
@@ -119,38 +117,7 @@ export class RoslynLanguageServer {
119117
private optionProvider: OptionProvider,
120118
private context: vscode.ExtensionContext,
121119
private telemetryReporter: TelemetryReporter
122-
) {
123-
// subscribe to extension change events so that we can get notified if C# Dev Kit is added/removed later.
124-
this.context.subscriptions.push(vscode.extensions.onDidChange(async () => {
125-
let csharpDevkitExtension = getCSharpDevKit();
126-
127-
if (this._wasActivatedWithCSharpDevkit === undefined) {
128-
// Haven't activated yet.
129-
return;
130-
}
131-
132-
const title = 'Restart Language Server';
133-
const command = 'dotnet.restartServer';
134-
if (csharpDevkitExtension && !this._wasActivatedWithCSharpDevkit) {
135-
// We previously started without C# Dev Kit and its now installed.
136-
// Offer a prompt to restart the server to use C# Dev Kit.
137-
_channel.appendLine(`Detected new installation of ${csharpDevkitExtensionId}`);
138-
let message = `Detected installation of ${csharpDevkitExtensionId}. Would you like to relaunch the language server for added features?`;
139-
ShowInformationMessage(vscode, message, { title, command });
140-
} else {
141-
// Any other change to extensions is irrelevant - an uninstall requires a reload of the window
142-
// which will automatically restart this extension too.
143-
}
144-
}));
145-
146-
// Subscribe to telemetry events so we can enable/disable as needed
147-
this.context.subscriptions.push(vscode.env.onDidChangeTelemetryEnabled((isEnabled: boolean) => {
148-
const title = 'Restart Language Server';
149-
const command = 'dotnet.restartServer';
150-
const message = 'Detected change in telemetry settings. These will not take effect until the language server is restarted, would you like to restart?';
151-
ShowInformationMessage(vscode, message, { title, command });
152-
}));
153-
}
120+
) { }
154121

155122
/**
156123
* Resolves server options and starts the dotnet language server process. The process is started asynchronously and this method will not wait until
@@ -194,7 +161,7 @@ export class RoslynLanguageServer {
194161
};
195162

196163
// Create the language client and start the client.
197-
let client = new LanguageClient(
164+
let client = new RoslynLanguageClient(
198165
'microsoft-codeanalysis-languageserver',
199166
'Microsoft.CodeAnalysis.LanguageServer',
200167
serverOptions,
@@ -225,11 +192,14 @@ export class RoslynLanguageServer {
225192
this._eventBus.emit(RoslynLanguageServer.serverStateChangeEvent, ServerStateChange.ProjectInitializationComplete);
226193
});
227194

195+
this.registerExtensionsChanged(this._languageClient);
196+
this.registerTelemetryChanged(this._languageClient);
197+
228198
// Start the client. This will also launch the server
229199
this._languageClient.start();
230200

231201
// Register Razor dynamic file info handling
232-
this.registerRazor(this._languageClient);
202+
this.registerDynamicFileInfo(this._languageClient);
233203
}
234204

235205
public async stop(): Promise<void> {
@@ -288,6 +258,18 @@ export class RoslynLanguageServer {
288258
return response;
289259
}
290260

261+
/**
262+
* Sends an LSP notification to the server with a given method and parameters.
263+
*/
264+
public async sendNotification<Params>(method: string, params: Params): Promise<any> {
265+
if (!this.isRunning()) {
266+
throw new Error('Tried to send request while server is not started.');
267+
}
268+
269+
let response = await this._languageClient!.sendNotification(method, params);
270+
return response;
271+
}
272+
291273
public async registerSolutionSnapshot(token: vscode.CancellationToken) : Promise<SolutionSnapshotId> {
292274
let response = await _languageServer.sendRequest0(RegisterSolutionSnapshotRequest.type, token);
293275
if (response)
@@ -437,7 +419,7 @@ export class RoslynLanguageServer {
437419
return childProcess;
438420
}
439421

440-
private registerRazor(client: LanguageClient) {
422+
private registerDynamicFileInfo(client: RoslynLanguageClient) {
441423
// When the Roslyn language server sends a request for Razor dynamic file info, we forward that request along to Razor via
442424
// a command.
443425
client.onRequest(
@@ -446,43 +428,41 @@ export class RoslynLanguageServer {
446428
client.onNotification(
447429
RoslynLanguageServer.removeRazorDynamicFileInfoMethodName,
448430
async notification => vscode.commands.executeCommand(DynamicFileInfoHandler.removeDynamicFileInfoCommand, notification));
431+
}
449432

450-
// Razor will call into us (via command) for generated file didChange/didClose notifications. We'll then forward these
451-
// notifications along to Roslyn. didOpen notifications are handled separately via the vscode.openTextDocument method.
452-
vscode.commands.registerCommand(RoslynLanguageServer.roslynDidChangeCommand, (notification: DidChangeTextDocumentParams) => {
453-
client.sendNotification(DidChangeTextDocumentNotification.method, notification);
454-
});
455-
vscode.commands.registerCommand(RoslynLanguageServer.roslynDidCloseCommand, (notification: DidCloseTextDocumentParams) => {
456-
client.sendNotification(DidCloseTextDocumentNotification.method, notification);
457-
});
458-
vscode.commands.registerCommand(RoslynLanguageServer.roslynPullDiagnosticCommand, async (request: DocumentDiagnosticParams) => {
459-
let diagnosticRequestType = new RequestType<DocumentDiagnosticParams, DocumentDiagnosticReport, any>(DocumentDiagnosticRequest.method);
460-
return await this.sendRequest(diagnosticRequestType, request, CancellationToken.None);
461-
});
433+
private registerExtensionsChanged(languageClient: RoslynLanguageClient) {
434+
// subscribe to extension change events so that we can get notified if C# Dev Kit is added/removed later.
435+
languageClient.addDisposable(vscode.extensions.onDidChange(async () => {
436+
let csharpDevkitExtension = getCSharpDevKit();
462437

463-
// The VS Code API for code actions (and the vscode.CodeAction type) doesn't support everything that LSP supports,
464-
// namely the data property, which Razor needs to identify which code actions are on their allow list, so we need
465-
// to expose a command for them to directly invoke our code actions LSP endpoints, rather than use built-in commands.
466-
vscode.commands.registerCommand(RoslynLanguageServer.provideCodeActionsCommand, async (request: CodeActionParams) => {
467-
return await this.sendRequest(CodeActionRequest.type, request, CancellationToken.None);
468-
});
469-
vscode.commands.registerCommand(RoslynLanguageServer.resolveCodeActionCommand, async (request: CodeAction) => {
470-
return await this.sendRequest(CodeActionResolveRequest.type, request, CancellationToken.None);
471-
});
438+
if (this._wasActivatedWithCSharpDevkit === undefined) {
439+
// Haven't activated yet.
440+
return;
441+
}
472442

473-
vscode.commands.registerCommand(RoslynLanguageServer.provideCompletionsCommand, async (request: CompletionParams) => {
474-
return await this.sendRequest(CompletionRequest.type, request, CancellationToken.None);
475-
});
476-
vscode.commands.registerCommand(RoslynLanguageServer.resolveCompletionsCommand, async (request: CompletionItem) => {
477-
return await this.sendRequest(CompletionResolveRequest.type, request, CancellationToken.None);
478-
});
443+
const title = 'Restart Language Server';
444+
const command = 'dotnet.restartServer';
445+
if (csharpDevkitExtension && !this._wasActivatedWithCSharpDevkit) {
446+
// We previously started without C# Dev Kit and its now installed.
447+
// Offer a prompt to restart the server to use C# Dev Kit.
448+
_channel.appendLine(`Detected new installation of ${csharpDevkitExtensionId}`);
449+
let message = `Detected installation of ${csharpDevkitExtensionId}. Would you like to relaunch the language server for added features?`;
450+
ShowInformationMessage(vscode, message, { title, command });
451+
} else {
452+
// Any other change to extensions is irrelevant - an uninstall requires a reload of the window
453+
// which will automatically restart this extension too.
454+
}
455+
}));
456+
}
479457

480-
// Roslyn is responsible for producing a json file containing information for Razor, that comes from the compilation for
481-
// a project. We want to defer this work until necessary, so this command is called by the Razor document manager to tell
482-
// us when they need us to initialize the Razor things.
483-
vscode.commands.registerCommand(RoslynLanguageServer.razorInitializeCommand, () => {
484-
client.sendNotification("razor/initialize", { });
485-
});
458+
private registerTelemetryChanged(languageClient: RoslynLanguageClient) {
459+
// Subscribe to telemetry events so we can enable/disable as needed
460+
languageClient.addDisposable(vscode.env.onDidChangeTelemetryEnabled((isEnabled: boolean) => {
461+
const title = 'Restart Language Server';
462+
const command = 'dotnet.restartServer';
463+
const message = 'Detected change in telemetry settings. These will not take effect until the language server is restarted, would you like to restart?';
464+
ShowInformationMessage(vscode, message, { title, command });
465+
}));
486466
}
487467

488468
private async getCSharpDevkitExportArgs(csharpDevkitExtension: vscode.Extension<CSharpDevKitExports>, options: Options) : Promise<string[]> {
@@ -564,6 +544,8 @@ export async function activateRoslynLanguageServer(context: vscode.ExtensionCont
564544
// Register any commands that need to be handled by the extension.
565545
registerCommands(context, _languageServer, optionProvider, hostExecutableResolver);
566546

547+
registerRazorCommands(context, _languageServer);
548+
567549
// Register any needed debugger components that need to communicate with the language server.
568550
registerDebugger(context, _languageServer, platformInfo, optionProvider, _channel);
569551

@@ -635,6 +617,45 @@ function getServerFileName(platformInfo: PlatformInformation) {
635617
return `${serverFileName}${extension}`;
636618
}
637619

620+
function registerRazorCommands(context: vscode.ExtensionContext, languageServer: RoslynLanguageServer) {
621+
// Razor will call into us (via command) for generated file didChange/didClose notifications. We'll then forward these
622+
// notifications along to Roslyn. didOpen notifications are handled separately via the vscode.openTextDocument method.
623+
context.subscriptions.push(vscode.commands.registerCommand(RoslynLanguageServer.roslynDidChangeCommand, async (notification: DidChangeTextDocumentParams) => {
624+
await languageServer.sendNotification(DidChangeTextDocumentNotification.method, notification);
625+
}));
626+
context.subscriptions.push(vscode.commands.registerCommand(RoslynLanguageServer.roslynDidCloseCommand, async (notification: DidCloseTextDocumentParams) => {
627+
await languageServer.sendNotification(DidCloseTextDocumentNotification.method, notification);
628+
}));
629+
context.subscriptions.push(vscode.commands.registerCommand(RoslynLanguageServer.roslynPullDiagnosticCommand, async (request: DocumentDiagnosticParams) => {
630+
let diagnosticRequestType = new RequestType<DocumentDiagnosticParams, DocumentDiagnosticReport, any>(DocumentDiagnosticRequest.method);
631+
return await languageServer.sendRequest(diagnosticRequestType, request, CancellationToken.None);
632+
}));
633+
634+
// The VS Code API for code actions (and the vscode.CodeAction type) doesn't support everything that LSP supports,
635+
// namely the data property, which Razor needs to identify which code actions are on their allow list, so we need
636+
// to expose a command for them to directly invoke our code actions LSP endpoints, rather than use built-in commands.
637+
context.subscriptions.push(vscode.commands.registerCommand(RoslynLanguageServer.provideCodeActionsCommand, async (request: CodeActionParams) => {
638+
return await languageServer.sendRequest(CodeActionRequest.type, request, CancellationToken.None);
639+
}));
640+
context.subscriptions.push(vscode.commands.registerCommand(RoslynLanguageServer.resolveCodeActionCommand, async (request: CodeAction) => {
641+
return await languageServer.sendRequest(CodeActionResolveRequest.type, request, CancellationToken.None);
642+
}));
643+
644+
context.subscriptions.push(vscode.commands.registerCommand(RoslynLanguageServer.provideCompletionsCommand, async (request: CompletionParams) => {
645+
return await languageServer.sendRequest(CompletionRequest.type, request, CancellationToken.None);
646+
}));
647+
context.subscriptions.push(vscode.commands.registerCommand(RoslynLanguageServer.resolveCompletionsCommand, async (request: CompletionItem) => {
648+
return await languageServer.sendRequest(CompletionResolveRequest.type, request, CancellationToken.None);
649+
}));
650+
651+
// Roslyn is responsible for producing a json file containing information for Razor, that comes from the compilation for
652+
// a project. We want to defer this work until necessary, so this command is called by the Razor document manager to tell
653+
// us when they need us to initialize the Razor things.
654+
context.subscriptions.push(vscode.commands.registerCommand(RoslynLanguageServer.razorInitializeCommand, async () => {
655+
await languageServer.sendNotification("razor/initialize", { });
656+
}));
657+
}
658+
638659
async function applyAutoInsertEdit(e: vscode.TextDocumentChangeEvent, token: vscode.CancellationToken) {
639660
const change = e.contentChanges[0];
640661

src/utils/getCSharpDevKit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
77
import { CSharpDevKitExports } from "../CSharpDevKitExports";
88

99
export const csharpDevkitExtensionId = "ms-dotnettools.csdevkit";
10+
export const csharpDevkitIntelliCodeExtensionId = "ms-dotnettools.vscodeintellicode-csharp";
1011

1112
export function getCSharpDevKit(): vscode.Extension<CSharpDevKitExports> | undefined {
1213
return vscode.extensions.getExtension<CSharpDevKitExports>(csharpDevkitExtensionId);

0 commit comments

Comments
 (0)