Skip to content

Commit 19b4bfc

Browse files
authored
Prototype extension API support (#5884)
* Prototype extension API support Adds a new experimental API to the C# extension that allows other extensions to communicate with the C# language server. This API is not yet finalized and may change in the future. Relates to dotnet/roslyn#68696. * Define an export for getting the path of the LSP server being run. * Move the experimental APIs into an explicitly labeled `experimental` block. * Prettier * Adjust settings.json for standard repo settings. * Implement extension path scanning instead of exposing the location of the LSP * Remove unused function * Revert the rest of the serverPath changes * PR Feedback * Prettier
1 parent 1341681 commit 19b4bfc

File tree

5 files changed

+100
-7
lines changed

5 files changed

+100
-7
lines changed

.vscode/settings.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111
"out/": true,
1212
"vsix/": true
1313
},
14+
"[typescript]": {
15+
"editor.defaultFormatter": "esbenp.prettier-vscode"
16+
},
1417
"csharp.suppressDotnetRestoreNotification": true,
1518
"typescript.tsdk": "./node_modules/typescript/lib",
1619
"mocha.enabled": true,
1720
"omnisharp.autoStart": false,
1821
"editor.formatOnSave": false,
19-
"eslint.lintTask.enable": true
20-
}
22+
"eslint.lintTask.enable": true,
23+
"dotnet.defaultSolution": "disable"
24+
}

src/csharpExtensionExports.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import * as vscode from 'vscode';
67
import { Advisor } from './features/diagnosticsProvider';
78
import { EventStream } from './eventStream';
89
import TestManager from './features/dotnetTest';
910
import { GlobalBrokeredServiceContainer } from '@microsoft/servicehub-framework';
11+
import { RequestType } from 'vscode-languageclient/node';
1012

1113
export interface OmnisharpExtensionExports {
1214
initializationFinished: () => Promise<void>;
@@ -21,4 +23,13 @@ export interface CSharpExtensionExports {
2123
logDirectory: string;
2224
profferBrokeredServices: (container: GlobalBrokeredServiceContainer) => void;
2325
determineBrowserType: () => Promise<string | undefined>;
26+
experimental: CSharpExtensionExperimentalExports;
27+
}
28+
29+
export interface CSharpExtensionExperimentalExports {
30+
sendServerRequest: <Params, Response, Error>(
31+
type: RequestType<Params, Response, Error>,
32+
params: Params,
33+
token: vscode.CancellationToken
34+
) => Promise<Response>;
2435
}

src/lsptoolshost/roslynLanguageServer.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ export class RoslynLanguageServer {
119119
private hostExecutableResolver: IHostExecutableResolver,
120120
private optionProvider: OptionProvider,
121121
private context: vscode.ExtensionContext,
122-
private telemetryReporter: TelemetryReporter
122+
private telemetryReporter: TelemetryReporter,
123+
private additionalExtensionPaths: string[]
123124
) {}
124125

125126
/**
@@ -412,6 +413,10 @@ export class RoslynLanguageServer {
412413
args.push('--logLevel', logLevel);
413414
}
414415

416+
for (const extensionPath of this.additionalExtensionPaths) {
417+
args.push('--extension', `"${extensionPath}"`);
418+
}
419+
415420
// Get the brokered service pipe name from C# Dev Kit (if installed).
416421
// We explicitly call this in the LSP server start action instead of awaiting it
417422
// in our activation because C# Dev Kit depends on C# activation completing.
@@ -621,14 +626,22 @@ export async function activateRoslynLanguageServer(
621626
outputChannel: vscode.OutputChannel,
622627
dotnetTestChannel: vscode.OutputChannel,
623628
reporter: TelemetryReporter
624-
) {
629+
): Promise<RoslynLanguageServer> {
625630
// Create a channel for outputting general logs from the language server.
626631
_channel = outputChannel;
627632
// Create a separate channel for outputting trace logs - these are incredibly verbose and make other logs very difficult to see.
628633
_traceChannel = vscode.window.createOutputChannel('C# LSP Trace Logs');
629634

630635
const hostExecutableResolver = new DotnetRuntimeExtensionResolver(platformInfo, getServerPath);
631-
_languageServer = new RoslynLanguageServer(platformInfo, hostExecutableResolver, optionProvider, context, reporter);
636+
const additionalExtensionPaths = scanExtensionPlugins();
637+
_languageServer = new RoslynLanguageServer(
638+
platformInfo,
639+
hostExecutableResolver,
640+
optionProvider,
641+
context,
642+
reporter,
643+
additionalExtensionPaths
644+
);
632645

633646
// Register any commands that need to be handled by the extension.
634647
registerCommands(context, _languageServer, optionProvider, hostExecutableResolver);
@@ -672,6 +685,29 @@ export async function activateRoslynLanguageServer(
672685

673686
// Start the language server.
674687
_languageServer.start();
688+
689+
return _languageServer;
690+
691+
function scanExtensionPlugins(): string[] {
692+
return vscode.extensions.all.flatMap((extension) => {
693+
let loadPaths = extension.packageJSON.contributes?.['csharpExtensionLoadPaths'];
694+
if (loadPaths === undefined || loadPaths === null) {
695+
_traceChannel.appendLine(`Extension ${extension.id} does not contribute csharpExtensionLoadPaths`);
696+
return [];
697+
}
698+
699+
if (!Array.isArray(loadPaths) || loadPaths.some((loadPath) => typeof loadPath !== 'string')) {
700+
_channel.appendLine(
701+
`Extension ${extension.id} has invalid csharpExtensionLoadPaths. Expected string array, found ${loadPaths}`
702+
);
703+
return [];
704+
}
705+
706+
loadPaths = loadPaths.map((loadPath) => path.join(extension.extensionPath, loadPath));
707+
_traceChannel.appendLine(`Extension ${extension.id} contributes csharpExtensionLoadPaths: ${loadPaths}`);
708+
return loadPaths;
709+
});
710+
}
675711
}
676712

677713
function getServerPath(options: Options, platformInfo: PlatformInformation) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
import * as vscode from 'vscode';
6+
import { RequestType } from 'vscode-languageclient/node';
7+
import { RoslynLanguageServer } from './roslynLanguageServer';
8+
9+
export class RoslynLanguageServerExport {
10+
private _server: RoslynLanguageServer | undefined;
11+
12+
constructor(private serverPromise: Promise<RoslynLanguageServer>) {}
13+
14+
private async ensureServer(): Promise<RoslynLanguageServer> {
15+
if (this._server === undefined) {
16+
this._server = await this.serverPromise;
17+
}
18+
19+
return this._server;
20+
}
21+
22+
public async sendRequest<Params, Response, Error>(
23+
type: RequestType<Params, Response, Error>,
24+
params: Params,
25+
token: vscode.CancellationToken
26+
): Promise<Response> {
27+
const server = await this.ensureServer();
28+
// We need to recreate the type parameter to ensure that the prototypes line up. The `RequestType` we receive could have been
29+
// from a different version.
30+
const newType = new RequestType<Params, Response, Error>(type.method);
31+
return await server.sendRequest(newType, params, token);
32+
}
33+
}

src/main.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ import { installRuntimeDependencies } from './installRuntimeDependencies';
4242
import { isValidDownload } from './packageManager/isValidDownload';
4343
import { BackgroundWorkStatusBarObserver } from './observers/backgroundWorkStatusBarObserver';
4444
import { getDotnetPackApi } from './dotnetPack';
45-
import { SolutionSnapshotProvider, activateRoslynLanguageServer } from './lsptoolshost/roslynLanguageServer';
45+
import {
46+
RoslynLanguageServer,
47+
SolutionSnapshotProvider,
48+
activateRoslynLanguageServer,
49+
} from './lsptoolshost/roslynLanguageServer';
4650
import { Options } from './shared/options';
4751
import { MigrateOptions } from './shared/migrateOptions';
4852
import { getBrokeredServiceContainer } from './lsptoolshost/services/brokeredServicesHosting';
@@ -53,6 +57,7 @@ import { CSharpExtensionExports, OmnisharpExtensionExports } from './csharpExten
5357
import { csharpDevkitExtensionId, getCSharpDevKit } from './utils/getCSharpDevKit';
5458
import { BlazorDebugConfigurationProvider } from './razor/src/blazorDebug/blazorDebugConfigurationProvider';
5559
import { RazorOmnisharpDownloader } from './razor/razorOmnisharpDownloader';
60+
import { RoslynLanguageServerExport } from './lsptoolshost/roslynLanguageServerExportChannel';
5661

5762
export async function activate(
5863
context: vscode.ExtensionContext
@@ -117,7 +122,7 @@ export async function activate(
117122

118123
let omnisharpLangServicePromise: Promise<OmniSharp.ActivationResult> | undefined = undefined;
119124
let omnisharpRazorPromise: Promise<void> | undefined = undefined;
120-
let roslynLanguageServerPromise: Promise<void> | undefined = undefined;
125+
let roslynLanguageServerPromise: Promise<RoslynLanguageServer> | undefined = undefined;
121126

122127
if (!useOmnisharpServer) {
123128
// Activate Razor. Needs to be activated before Roslyn so commands are registered in the correct order.
@@ -306,6 +311,7 @@ export async function activate(
306311
if (!useOmnisharpServer) {
307312
tryGetCSharpDevKitExtensionExports(csharpLogObserver);
308313

314+
const languageServerExport = new RoslynLanguageServerExport(roslynLanguageServerPromise!);
309315
return {
310316
initializationFinished: async () => {
311317
await coreClrDebugPromise;
@@ -314,6 +320,9 @@ export async function activate(
314320
profferBrokeredServices: (container) => profferBrokeredServices(context, container),
315321
logDirectory: context.logUri.fsPath,
316322
determineBrowserType: BlazorDebugConfigurationProvider.determineBrowserType,
323+
experimental: {
324+
sendServerRequest: async (t, p, ct) => await languageServerExport.sendRequest(t, p, ct),
325+
},
317326
};
318327
} else {
319328
return {

0 commit comments

Comments
 (0)