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

Prototype extension API support #5884

Merged
merged 12 commits into from
Jul 22, 2023
8 changes: 6 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
"out/": true,
"vsix/": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"csharp.suppressDotnetRestoreNotification": true,
"typescript.tsdk": "./node_modules/typescript/lib",
"mocha.enabled": true,
"omnisharp.autoStart": false,
"editor.formatOnSave": false,
"eslint.lintTask.enable": true
}
"eslint.lintTask.enable": true,
"dotnet.defaultSolution": "disable"
}
11 changes: 11 additions & 0 deletions src/csharpExtensionExports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { Advisor } from './features/diagnosticsProvider';
import { EventStream } from './eventStream';
import TestManager from './features/dotnetTest';
import { GlobalBrokeredServiceContainer } from '@microsoft/servicehub-framework';
import { RequestType } from 'vscode-languageclient/node';

export interface OmnisharpExtensionExports {
initializationFinished: () => Promise<void>;
Expand All @@ -21,4 +23,13 @@ export interface CSharpExtensionExports {
logDirectory: string;
profferBrokeredServices: (container: GlobalBrokeredServiceContainer) => void;
determineBrowserType: () => Promise<string | undefined>;
experimental: CSharpExtensionExperimentalExports;
}

export interface CSharpExtensionExperimentalExports {
sendServerRequest: <Params, Response, Error>(
type: RequestType<Params, Response, Error>,
params: Params,
token: vscode.CancellationToken
) => Promise<Response>;
333fred marked this conversation as resolved.
Show resolved Hide resolved
}
42 changes: 39 additions & 3 deletions src/lsptoolshost/roslynLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ export class RoslynLanguageServer {
private hostExecutableResolver: IHostExecutableResolver,
private optionProvider: OptionProvider,
private context: vscode.ExtensionContext,
private telemetryReporter: TelemetryReporter
private telemetryReporter: TelemetryReporter,
private additionalExtensionPaths: string[]
) {}

/**
Expand Down Expand Up @@ -412,6 +413,10 @@ export class RoslynLanguageServer {
args.push('--logLevel', logLevel);
}

for (const extensionPath of this.additionalExtensionPaths) {
args.push('--extension', `"${extensionPath}"`);
}

// Get the brokered service pipe name from C# Dev Kit (if installed).
// We explicitly call this in the LSP server start action instead of awaiting it
// in our activation because C# Dev Kit depends on C# activation completing.
Expand Down Expand Up @@ -621,14 +626,22 @@ export async function activateRoslynLanguageServer(
outputChannel: vscode.OutputChannel,
dotnetTestChannel: vscode.OutputChannel,
reporter: TelemetryReporter
) {
): Promise<RoslynLanguageServer> {
// Create a channel for outputting general logs from the language server.
_channel = outputChannel;
// Create a separate channel for outputting trace logs - these are incredibly verbose and make other logs very difficult to see.
_traceChannel = vscode.window.createOutputChannel('C# LSP Trace Logs');

const hostExecutableResolver = new DotnetRuntimeExtensionResolver(platformInfo, getServerPath);
_languageServer = new RoslynLanguageServer(platformInfo, hostExecutableResolver, optionProvider, context, reporter);
const additionalExtensionPaths = scanExtensionPlugins();
_languageServer = new RoslynLanguageServer(
platformInfo,
hostExecutableResolver,
optionProvider,
context,
reporter,
additionalExtensionPaths
);

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

// Start the language server.
_languageServer.start();

return _languageServer;

function scanExtensionPlugins(): string[] {
333fred marked this conversation as resolved.
Show resolved Hide resolved
return vscode.extensions.all.flatMap((extension) => {
let loadPaths = extension.packageJSON.contributes?.['csharpExtensionLoadPaths'];
if (loadPaths === undefined || loadPaths === null) {
_traceChannel.appendLine(`Extension ${extension.id} does not contribute csharpExtensionLoadPaths`);
Copy link
Member

Choose a reason for hiding this comment

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

Given this is the common case, just skip the logging here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I figure that's what trace logging is for: the common case that someone is trying to debug what's happening.

Copy link
Member

Choose a reason for hiding this comment

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

Is the _traceChannel though the LSP channel? @dibarbet?

return [];
}

if (!Array.isArray(loadPaths) || loadPaths.some((loadPath) => typeof loadPath !== 'string')) {
_channel.appendLine(
`Extension ${extension.id} has invalid csharpExtensionLoadPaths. Expected string array, found ${loadPaths}`
);
return [];
}

loadPaths = loadPaths.map((loadPath) => path.join(extension.extensionPath, loadPath));
_traceChannel.appendLine(`Extension ${extension.id} contributes csharpExtensionLoadPaths: ${loadPaths}`);
return loadPaths;
333fred marked this conversation as resolved.
Show resolved Hide resolved
});
}
}

function getServerPath(options: Options, platformInfo: PlatformInformation) {
Expand Down
33 changes: 33 additions & 0 deletions src/lsptoolshost/roslynLanguageServerExportChannel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { RequestType } from 'vscode-languageclient/node';
import { RoslynLanguageServer } from './roslynLanguageServer';

export class RoslynLanguageServerExport {
private _server: RoslynLanguageServer | undefined;

constructor(private serverPromise: Promise<RoslynLanguageServer>) {}

private async ensureServer(): Promise<RoslynLanguageServer> {
if (this._server === undefined) {
this._server = await this.serverPromise;
}

return this._server;
}

public async sendRequest<Params, Response, Error>(
type: RequestType<Params, Response, Error>,
params: Params,
token: vscode.CancellationToken
): Promise<Response> {
const server = await this.ensureServer();
// We need to recreate the type parameter to ensure that the prototypes line up. The `RequestType` we receive could have been
// from a different version.
const newType = new RequestType<Params, Response, Error>(type.method);
return await server.sendRequest(newType, params, token);
}
}
13 changes: 11 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ import { installRuntimeDependencies } from './installRuntimeDependencies';
import { isValidDownload } from './packageManager/isValidDownload';
import { BackgroundWorkStatusBarObserver } from './observers/backgroundWorkStatusBarObserver';
import { getDotnetPackApi } from './dotnetPack';
import { SolutionSnapshotProvider, activateRoslynLanguageServer } from './lsptoolshost/roslynLanguageServer';
import {
RoslynLanguageServer,
SolutionSnapshotProvider,
activateRoslynLanguageServer,
} from './lsptoolshost/roslynLanguageServer';
import { Options } from './shared/options';
import { MigrateOptions } from './shared/migrateOptions';
import { getBrokeredServiceContainer } from './lsptoolshost/services/brokeredServicesHosting';
Expand All @@ -53,6 +57,7 @@ import { CSharpExtensionExports, OmnisharpExtensionExports } from './csharpExten
import { csharpDevkitExtensionId, getCSharpDevKit } from './utils/getCSharpDevKit';
import { BlazorDebugConfigurationProvider } from './razor/src/blazorDebug/blazorDebugConfigurationProvider';
import { RazorOmnisharpDownloader } from './razor/razorOmnisharpDownloader';
import { RoslynLanguageServerExport } from './lsptoolshost/roslynLanguageServerExportChannel';

export async function activate(
context: vscode.ExtensionContext
Expand Down Expand Up @@ -117,7 +122,7 @@ export async function activate(

let omnisharpLangServicePromise: Promise<OmniSharp.ActivationResult> | undefined = undefined;
let omnisharpRazorPromise: Promise<void> | undefined = undefined;
let roslynLanguageServerPromise: Promise<void> | undefined = undefined;
let roslynLanguageServerPromise: Promise<RoslynLanguageServer> | undefined = undefined;

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

const languageServerExport = new RoslynLanguageServerExport(roslynLanguageServerPromise!);
return {
initializationFinished: async () => {
await coreClrDebugPromise;
Expand All @@ -314,6 +320,9 @@ export async function activate(
profferBrokeredServices: (container) => profferBrokeredServices(context, container),
logDirectory: context.logUri.fsPath,
determineBrowserType: BlazorDebugConfigurationProvider.determineBrowserType,
experimental: {
sendServerRequest: async (t, p, ct) => await languageServerExport.sendRequest(t, p, ct),
},
};
} else {
return {
Expand Down