From e811c15936660d9873eb519154fa931a9733f307 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 21 Apr 2025 12:00:20 +1000 Subject: [PATCH 1/2] Allow the Roslyn client to log messages from Razor --- src/activateOmniSharp.ts | 5 ++- src/activateRoslyn.ts | 9 +++-- src/lsptoolshost/activate.ts | 7 ++-- .../server/roslynLanguageServer.ts | 34 ++++++++++++++++--- src/razor/razor.ts | 5 ++- src/razor/src/extension.ts | 7 ++-- src/razor/src/razorLogger.ts | 24 +++++++++++-- src/razor/src/razorLoggerNotification.ts | 10 ++++++ 8 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 src/razor/src/razorLoggerNotification.ts diff --git a/src/activateOmniSharp.ts b/src/activateOmniSharp.ts index 30e25b3345..dd78570ccb 100644 --- a/src/activateOmniSharp.ts +++ b/src/activateOmniSharp.ts @@ -13,6 +13,7 @@ import { activateOmniSharpLanguageServer } from './omnisharp/omnisharpLanguageSe import { EventStream } from './eventStream'; import { razorOptions } from './shared/options'; import { activateRazorExtension } from './razor/razor'; +import { RazorLogger } from './razor/src/razorLogger'; export function activateOmniSharp( context: vscode.ExtensionContext, @@ -39,6 +40,7 @@ export function activateOmniSharp( reporter ); + const razorLogger = new RazorLogger(); let omnisharpRazorPromise: Promise | undefined = undefined; if (!razorOptions.razorDevMode) { omnisharpRazorPromise = activateRazorExtension( @@ -48,7 +50,8 @@ export function activateOmniSharp( reporter, undefined, platformInfo, - /* useOmnisharpServer */ true + /* useOmnisharpServer */ true, + razorLogger ); } diff --git a/src/activateRoslyn.ts b/src/activateRoslyn.ts index 8ea90c8baf..3b2244f845 100644 --- a/src/activateRoslyn.ts +++ b/src/activateRoslyn.ts @@ -25,6 +25,7 @@ import { GlobalBrokeredServiceContainer } from '@microsoft/servicehub-framework' import { SolutionSnapshotProvider } from './lsptoolshost/solutionSnapshot/solutionSnapshotProvider'; import { BuildResultDiagnostics } from './lsptoolshost/diagnostics/buildResultReporterService'; import { getComponentFolder } from './lsptoolshost/extensions/builtInComponents'; +import { RazorLogger } from './razor/src/razorLogger'; export function activateRoslyn( context: vscode.ExtensionContext, @@ -39,6 +40,8 @@ export function activateRoslyn( const roslynLanguageServerEvents = new RoslynLanguageServerEvents(); context.subscriptions.push(roslynLanguageServerEvents); + const razorLogger = new RazorLogger(); + // Activate Razor. Needs to be activated before Roslyn so commands are registered in the correct order. // Otherwise, if Roslyn starts up first, they could execute commands that don't yet exist on Razor's end. // @@ -54,7 +57,8 @@ export function activateRoslyn( reporter, csharpDevkitExtension, platformInfo, - /* useOmnisharpServer */ false + /* useOmnisharpServer */ false, + razorLogger ); // Setup a listener for project initialization complete before we start the server. @@ -73,7 +77,8 @@ export function activateRoslyn( optionStream, csharpChannel, reporter, - roslynLanguageServerEvents + roslynLanguageServerEvents, + razorLogger ); debugSessionTracker.initializeDebugSessionHandlers(context); diff --git a/src/lsptoolshost/activate.ts b/src/lsptoolshost/activate.ts index 22dc536947..4212f2cd58 100644 --- a/src/lsptoolshost/activate.ts +++ b/src/lsptoolshost/activate.ts @@ -29,6 +29,7 @@ import { ProjectContextStatus } from './projectContext/projectContextStatus'; import { RoslynLanguageServer } from './server/roslynLanguageServer'; import { registerCopilotRelatedFilesProvider } from './copilot/relatedFilesProvider'; import { registerCopilotContextProviders } from './copilot/contextProviders'; +import { RazorLogger } from '../razor/src/razorLogger'; let _channel: vscode.LogOutputChannel; let _traceChannel: vscode.OutputChannel; @@ -43,7 +44,8 @@ export async function activateRoslynLanguageServer( optionObservable: Observable, outputChannel: vscode.LogOutputChannel, reporter: TelemetryReporter, - languageServerEvents: RoslynLanguageServerEvents + languageServerEvents: RoslynLanguageServerEvents, + razorLogger: RazorLogger ): Promise { // Create a channel for outputting general logs from the language server. _channel = outputChannel; @@ -69,7 +71,8 @@ export async function activateRoslynLanguageServer( additionalExtensionPaths, languageServerEvents, _channel, - _traceChannel + _traceChannel, + razorLogger ); registerLanguageStatusItems(context, languageServer, languageServerEvents); diff --git a/src/lsptoolshost/server/roslynLanguageServer.ts b/src/lsptoolshost/server/roslynLanguageServer.ts index 6716cbdc5d..86336cd407 100644 --- a/src/lsptoolshost/server/roslynLanguageServer.ts +++ b/src/lsptoolshost/server/roslynLanguageServer.ts @@ -8,7 +8,14 @@ import * as path from 'path'; import * as cp from 'child_process'; import * as uuid from 'uuid'; import * as net from 'net'; -import { LanguageClientOptions, MessageTransports, ProtocolRequestType, ServerOptions } from 'vscode-languageclient'; +import { + LanguageClientOptions, + LogMessageParams, + MessageTransports, + NotificationType, + ProtocolRequestType, + ServerOptions, +} from 'vscode-languageclient'; import { Trace, RequestType, @@ -71,6 +78,7 @@ import { copilotLanguageServerExtensionAssemblyName, copilotLanguageServerExtensionComponentName, } from '../copilot/contextProviders'; +import { RazorLogger } from '../../razor/src/razorLogger'; // Flag indicating if C# Devkit was installed the last time we activated. // Used to determine if we need to restart the server on extension changes. @@ -80,6 +88,7 @@ export class RoslynLanguageServer { // These are notifications we will get from the LSP server and will forward to the Razor extension. private static readonly provideRazorDynamicFileInfoMethodName: string = 'razor/provideDynamicFileInfo'; private static readonly removeRazorDynamicFileInfoMethodName: string = 'razor/removeDynamicFileInfo'; + private static readonly razorLogMessageMethodName: string = 'razor/log'; /** * The encoding to use when writing to and from the stream. @@ -119,7 +128,8 @@ export class RoslynLanguageServer { private _context: vscode.ExtensionContext, private _telemetryReporter: TelemetryReporter, private _languageServerEvents: RoslynLanguageServerEvents, - private _channel: vscode.LogOutputChannel + private _channel: vscode.LogOutputChannel, + logger: RazorLogger ) { this.registerSetTrace(); this.registerSendOpenSolution(); @@ -138,6 +148,7 @@ export class RoslynLanguageServer { // Register Razor dynamic file info handling this.registerDynamicFileInfo(); + this.registerRazorLogger(logger); this.registerDebuggerAttach(); @@ -253,7 +264,8 @@ export class RoslynLanguageServer { additionalExtensionPaths: string[], languageServerEvents: RoslynLanguageServerEvents, channel: vscode.LogOutputChannel, - traceChannel: vscode.OutputChannel + traceChannel: vscode.OutputChannel, + razorlogger: RazorLogger ): Promise { const devKit = getCSharpDevKit(); if (devKit) { @@ -316,7 +328,8 @@ export class RoslynLanguageServer { context, telemetryReporter, languageServerEvents, - channel + channel, + razorlogger ); client.registerFeature(server._onAutoInsertFeature); @@ -832,6 +845,19 @@ export class RoslynLanguageServer { ); } + private RazorLoggerNotification: NotificationType = new NotificationType( + RoslynLanguageServer.razorLogMessageMethodName + ); + + private registerRazorLogger(logger: RazorLogger) { + this._languageClient.onNotification( + this.RazorLoggerNotification, + (params: LogMessageParams) => { + logger.log(params.message, params.type); + } + ); + } + // eslint-disable-next-line @typescript-eslint/promise-function-async private WaitForAttachCompleteAsync(attachRequestId: string): Promise { return new Promise((resolve) => { diff --git a/src/razor/razor.ts b/src/razor/razor.ts index 8223d4815e..0fe92db802 100644 --- a/src/razor/razor.ts +++ b/src/razor/razor.ts @@ -12,6 +12,7 @@ import { EventStream } from '../eventStream'; import TelemetryReporter from '@vscode/extension-telemetry'; import { PlatformInformation } from '../shared/platform'; import { showWarningMessage } from '../shared/observers/utils/showMessage'; +import { RazorLogger } from './src/razorLogger'; export async function activateRazorExtension( context: vscode.ExtensionContext, @@ -20,7 +21,8 @@ export async function activateRazorExtension( vscodeTelemetryReporter: TelemetryReporter, csharpDevkitExtension: vscode.Extension | undefined, platformInfo: PlatformInformation, - useOmnisharpServer: boolean + useOmnisharpServer: boolean, + logger: RazorLogger ) { const razorConfig = vscode.workspace.getConfiguration('razor'); const configuredLanguageServerDir = razorConfig.get('languageServer.directory', ''); @@ -49,6 +51,7 @@ export async function activateRazorExtension( vscodeTelemetryReporter, csharpDevkitExtension, platformInfo, + logger, /* enableProposedApis: */ false ); } diff --git a/src/razor/src/extension.ts b/src/razor/src/extension.ts index 214ec7c3e2..604337a652 100644 --- a/src/razor/src/extension.ts +++ b/src/razor/src/extension.ts @@ -62,6 +62,7 @@ export async function activate( vscodeTelemetryReporter: TelemetryReporter, csharpDevkitExtension: vscode.Extension | undefined, platformInfo: PlatformInformation, + logger: RazorLogger, enableProposedApis = false ) { const razorTelemetryReporter = new RazorTelemetryReporter(eventStream); @@ -69,8 +70,6 @@ export async function activate( create: () => new vscode.EventEmitter(), }; - const logger = new RazorLogger(eventEmitterFactory); - try { const razorOptions: RazorLanguageServerOptions = resolveRazorLanguageServerOptions( vscodeType, @@ -82,6 +81,10 @@ export async function activate( // TODO: We still need a document manager for Html, so need to do _some_ of the below, just not sure what yet, // and it needs to be able to take a roslynLanguageServerClient instead of a razorLanguageServerClient I guess. + logger.logVerbose( + 'Razor cohosting is enabled, skipping language server activation. No rzls process will be created.' + ); + return; } diff --git a/src/razor/src/razorLogger.ts b/src/razor/src/razorLogger.ts index 985f5af3db..f0947468de 100644 --- a/src/razor/src/razorLogger.ts +++ b/src/razor/src/razorLogger.ts @@ -7,8 +7,8 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscodeAdapter from './vscodeAdapter'; import * as vscode from 'vscode'; -import { IEventEmitterFactory } from './IEventEmitterFactory'; import { RazorLanguageServerClient } from './razorLanguageServerClient'; +import { MessageType } from 'vscode-languageserver-protocol'; export class RazorLogger implements vscodeAdapter.Disposable { public static readonly logName = 'Razor Log'; @@ -19,9 +19,9 @@ export class RazorLogger implements vscodeAdapter.Disposable { private readonly onLogEmitter: vscodeAdapter.EventEmitter; - constructor(eventEmitterFactory: IEventEmitterFactory) { + constructor() { this.outputChannel = vscode.window.createOutputChannel(vscode.l10n.t('Razor Log'), { log: true }); - this.onLogEmitter = eventEmitterFactory.create(); + this.onLogEmitter = new vscode.EventEmitter(); this.processTraceLevel(); this.outputChannel.onDidChangeLogLevel(async () => { @@ -96,6 +96,24 @@ export class RazorLogger implements vscodeAdapter.Disposable { } } + public log(message: string, level: MessageType) { + switch (level) { + case MessageType.Error: + this.logError(message, new Error(message)); + break; + case MessageType.Warning: + this.logWarning(message); + break; + case MessageType.Info: + this.logMessage(message); + break; + case MessageType.Debug: + case MessageType.Log: + default: + this.logVerbose(message); + } + } + public dispose() { this.outputChannel.dispose(); } diff --git a/src/razor/src/razorLoggerNotification.ts b/src/razor/src/razorLoggerNotification.ts new file mode 100644 index 0000000000..dfbfe71faa --- /dev/null +++ b/src/razor/src/razorLoggerNotification.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LogLevel } from 'vscode'; + +export class RazorLoggerNotification { + constructor(public readonly message: string, public readonly level: LogLevel) {} +} From b34da5fff245fc0bcb02abaf0fb42c62eaca1cd9 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 22 Apr 2025 11:04:59 +1000 Subject: [PATCH 2/2] Move endpoints to its own file, as we're going to need more --- src/lsptoolshost/activate.ts | 5 +-- src/lsptoolshost/razor/razorEndpoints.ts | 20 +++++++++++ .../server/roslynLanguageServer.ts | 34 ++++++------------- src/razor/src/razorLoggerNotification.ts | 10 ------ 4 files changed, 34 insertions(+), 35 deletions(-) create mode 100644 src/lsptoolshost/razor/razorEndpoints.ts delete mode 100644 src/razor/src/razorLoggerNotification.ts diff --git a/src/lsptoolshost/activate.ts b/src/lsptoolshost/activate.ts index 4212f2cd58..789a3b1d48 100644 --- a/src/lsptoolshost/activate.ts +++ b/src/lsptoolshost/activate.ts @@ -30,6 +30,7 @@ import { RoslynLanguageServer } from './server/roslynLanguageServer'; import { registerCopilotRelatedFilesProvider } from './copilot/relatedFilesProvider'; import { registerCopilotContextProviders } from './copilot/contextProviders'; import { RazorLogger } from '../razor/src/razorLogger'; +import { registerRazorEndpoints } from './razor/razorEndpoints'; let _channel: vscode.LogOutputChannel; let _traceChannel: vscode.OutputChannel; @@ -71,8 +72,7 @@ export async function activateRoslynLanguageServer( additionalExtensionPaths, languageServerEvents, _channel, - _traceChannel, - razorLogger + _traceChannel ); registerLanguageStatusItems(context, languageServer, languageServerEvents); @@ -86,6 +86,7 @@ export async function activateRoslynLanguageServer( registerCodeActionFixAllCommands(context, languageServer, _channel); registerRazorCommands(context, languageServer); + registerRazorEndpoints(context, languageServer, razorLogger); registerUnitTestingCommands(context, languageServer); diff --git a/src/lsptoolshost/razor/razorEndpoints.ts b/src/lsptoolshost/razor/razorEndpoints.ts new file mode 100644 index 0000000000..01eeec44aa --- /dev/null +++ b/src/lsptoolshost/razor/razorEndpoints.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import * as vscode from 'vscode'; +import { LogMessageParams, NotificationType } from 'vscode-languageclient'; +import { RazorLogger } from '../../razor/src/razorLogger'; + +export function registerRazorEndpoints( + context: vscode.ExtensionContext, + languageServer: RoslynLanguageServer, + razorLogger: RazorLogger +) { + const logNotificationType = new NotificationType('razor/log'); + languageServer.registerOnNotificationWithParams(logNotificationType, (params) => + razorLogger.log(params.message, params.type) + ); +} diff --git a/src/lsptoolshost/server/roslynLanguageServer.ts b/src/lsptoolshost/server/roslynLanguageServer.ts index 86336cd407..2c5796563d 100644 --- a/src/lsptoolshost/server/roslynLanguageServer.ts +++ b/src/lsptoolshost/server/roslynLanguageServer.ts @@ -10,8 +10,8 @@ import * as uuid from 'uuid'; import * as net from 'net'; import { LanguageClientOptions, - LogMessageParams, MessageTransports, + NotificationHandler, NotificationType, ProtocolRequestType, ServerOptions, @@ -78,7 +78,6 @@ import { copilotLanguageServerExtensionAssemblyName, copilotLanguageServerExtensionComponentName, } from '../copilot/contextProviders'; -import { RazorLogger } from '../../razor/src/razorLogger'; // Flag indicating if C# Devkit was installed the last time we activated. // Used to determine if we need to restart the server on extension changes. @@ -88,7 +87,6 @@ export class RoslynLanguageServer { // These are notifications we will get from the LSP server and will forward to the Razor extension. private static readonly provideRazorDynamicFileInfoMethodName: string = 'razor/provideDynamicFileInfo'; private static readonly removeRazorDynamicFileInfoMethodName: string = 'razor/removeDynamicFileInfo'; - private static readonly razorLogMessageMethodName: string = 'razor/log'; /** * The encoding to use when writing to and from the stream. @@ -128,8 +126,7 @@ export class RoslynLanguageServer { private _context: vscode.ExtensionContext, private _telemetryReporter: TelemetryReporter, private _languageServerEvents: RoslynLanguageServerEvents, - private _channel: vscode.LogOutputChannel, - logger: RazorLogger + private _channel: vscode.LogOutputChannel ) { this.registerSetTrace(); this.registerSendOpenSolution(); @@ -148,7 +145,6 @@ export class RoslynLanguageServer { // Register Razor dynamic file info handling this.registerDynamicFileInfo(); - this.registerRazorLogger(logger); this.registerDebuggerAttach(); @@ -264,8 +260,7 @@ export class RoslynLanguageServer { additionalExtensionPaths: string[], languageServerEvents: RoslynLanguageServerEvents, channel: vscode.LogOutputChannel, - traceChannel: vscode.OutputChannel, - razorlogger: RazorLogger + traceChannel: vscode.OutputChannel ): Promise { const devKit = getCSharpDevKit(); if (devKit) { @@ -328,8 +323,7 @@ export class RoslynLanguageServer { context, telemetryReporter, languageServerEvents, - channel, - razorlogger + channel ); client.registerFeature(server._onAutoInsertFeature); @@ -450,6 +444,13 @@ export class RoslynLanguageServer { this._languageClient.addDisposable(this._languageClient.onNotification(method, handler)); } + public registerOnNotificationWithParams( + type: NotificationType, + handler: NotificationHandler + ) { + this._languageClient.addDisposable(this._languageClient.onNotification(type, handler)); + } + public async registerSolutionSnapshot(token: vscode.CancellationToken): Promise { const response = await this.sendRequest0(RoslynProtocol.RegisterSolutionSnapshotRequest.type, token); if (response) { @@ -845,19 +846,6 @@ export class RoslynLanguageServer { ); } - private RazorLoggerNotification: NotificationType = new NotificationType( - RoslynLanguageServer.razorLogMessageMethodName - ); - - private registerRazorLogger(logger: RazorLogger) { - this._languageClient.onNotification( - this.RazorLoggerNotification, - (params: LogMessageParams) => { - logger.log(params.message, params.type); - } - ); - } - // eslint-disable-next-line @typescript-eslint/promise-function-async private WaitForAttachCompleteAsync(attachRequestId: string): Promise { return new Promise((resolve) => { diff --git a/src/razor/src/razorLoggerNotification.ts b/src/razor/src/razorLoggerNotification.ts deleted file mode 100644 index dfbfe71faa..0000000000 --- a/src/razor/src/razorLoggerNotification.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { LogLevel } from 'vscode'; - -export class RazorLoggerNotification { - constructor(public readonly message: string, public readonly level: LogLevel) {} -}