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: 8 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 @@ -20,4 +22,10 @@ export interface CSharpExtensionExports {
initializationFinished: () => Promise<void>;
logDirectory: string;
profferBrokeredServices: (container: GlobalBrokeredServiceContainer) => void;
sendRequest: <Params, Response, Error>(
333fred marked this conversation as resolved.
Show resolved Hide resolved
333fred marked this conversation as resolved.
Show resolved Hide resolved
333fred marked this conversation as resolved.
Show resolved Hide resolved
type: RequestType<Params, Response, Error>,
params: Params,
token: vscode.CancellationToken
) => Promise<Response>;
333fred marked this conversation as resolved.
Show resolved Hide resolved
serverExecutablePath: () => Promise<string | undefined>;
333fred marked this conversation as resolved.
Show resolved Hide resolved
}
23 changes: 17 additions & 6 deletions src/lsptoolshost/roslynLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ export class RoslynLanguageServer {
*/
private _solutionFile: vscode.Uri | undefined;

/**
* The path to the server executable that is being run, or undefined if none was.
*/
private _serverExecutablePath: string | undefined;

constructor(
private platformInfo: PlatformInformation,
private hostExecutableResolver: IHostExecutableResolver,
Expand Down Expand Up @@ -360,7 +365,7 @@ export class RoslynLanguageServer {

private async startServer(logLevel: string | undefined): Promise<cp.ChildProcess> {
const options = this.optionProvider.GetLatestOptions();
const serverPath = getServerPath(options, this.platformInfo);
this._serverExecutablePath = getServerPath(options, this.platformInfo);

const dotnetInfo = await this.hostExecutableResolver.getHostExecutableInfo(options);
const dotnetRuntimePath = path.dirname(dotnetInfo.path);
Expand Down Expand Up @@ -417,7 +422,7 @@ export class RoslynLanguageServer {
}

if (logLevel && [Trace.Messages, Trace.Verbose].includes(this.GetTraceLevel(logLevel))) {
_channel.appendLine(`Starting server at ${serverPath}`);
_channel.appendLine(`Starting server at ${this._serverExecutablePath}`);
}

// shouldn't this arg only be set if it's running with CSDevKit?
Expand All @@ -430,13 +435,13 @@ export class RoslynLanguageServer {
env: env,
};

if (serverPath.endsWith('.dll')) {
if (this._serverExecutablePath.endsWith('.dll')) {
// If we were given a path to a dll, launch that via dotnet.
const argsWithPath = [serverPath].concat(args);
const argsWithPath = [this._serverExecutablePath].concat(args);
childProcess = cp.spawn(dotnetExecutablePath, argsWithPath, cpOptions);
} else {
// Otherwise assume we were given a path to an executable.
childProcess = cp.spawn(serverPath, args, cpOptions);
childProcess = cp.spawn(this._serverExecutablePath, args, cpOptions);
}

return childProcess;
Expand Down Expand Up @@ -558,6 +563,10 @@ export class RoslynLanguageServer {
throw new Error(`Invalid log level ${logLevel}`);
}
}

public getServerExecutablePath(): string | undefined {
return this._serverExecutablePath;
}
333fred marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -575,7 +584,7 @@ export async function activateRoslynLanguageServer(
optionProvider: OptionProvider,
outputChannel: 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.
Expand Down Expand Up @@ -624,6 +633,8 @@ export async function activateRoslynLanguageServer(

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

return _languageServer;
}

function getServerPath(options: Options, platformInfo: PlatformInformation) {
Expand Down
38 changes: 38 additions & 0 deletions src/lsptoolshost/roslynLanguageServerExportChannel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* 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);
}

public async serverExecutablePath(): Promise<string | undefined> {
const server = await this.ensureServer();
return server.getServerExecutablePath();
}
}
14 changes: 12 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 @@ -51,6 +55,7 @@ import Descriptors from './lsptoolshost/services/descriptors';
import { GlobalBrokeredServiceContainer } from '@microsoft/servicehub-framework';
import { CSharpExtensionExports, OmnisharpExtensionExports } from './csharpExtensionExports';
import { csharpDevkitExtensionId, getCSharpDevKit } from './utils/getCSharpDevKit';
import { RoslynLanguageServerExport } from './lsptoolshost/roslynLanguageServerExportChannel';

export async function activate(
context: vscode.ExtensionContext
Expand Down Expand Up @@ -114,7 +119,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 @@ -281,13 +286,18 @@ export async function activate(
if (!useOmnisharpServer) {
tryGetCSharpDevKitExtensionExports(csharpLogObserver);

const languageServerExport = new RoslynLanguageServerExport(
<Promise<RoslynLanguageServer>>roslynLanguageServerPromise
333fred marked this conversation as resolved.
Show resolved Hide resolved
);
return {
initializationFinished: async () => {
await coreClrDebugPromise;
await roslynLanguageServerPromise;
},
profferBrokeredServices: (container) => profferBrokeredServices(context, container),
logDirectory: context.logUri.fsPath,
sendRequest: async (t, p, ct) => await languageServerExport.sendRequest(t, p, ct),
serverExecutablePath: async () => await languageServerExport.serverExecutablePath(),
};
} else {
return {
Expand Down