diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 5cd775f7c..2c1332f49 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -172,6 +172,9 @@ "C# configuration has changed. Would you like to reload the window to apply your changes?": "C# configuration has changed. Would you like to reload the window to apply your changes?", "Nested Code Action": "Nested Code Action", "Fix All: ": "Fix All: ", + "The active document is not part of the open workspace. Not all language features will be available.": "The active document is not part of the open workspace. Not all language features will be available.", + "Dismiss": "Dismiss", + "Do not show for this workspace": "Do not show for this workspace", "Open solution": "Open solution", "Restart server": "Restart server", "C# Workspace Status": "C# Workspace Status", diff --git a/package.json b/package.json index 4a4ebb5f2..b4a35cc65 100644 --- a/package.json +++ b/package.json @@ -1479,6 +1479,11 @@ "default": false, "description": "%configuration.dotnet.server.suppressLspErrorToasts%" }, + "dotnet.server.suppressMiscellaneousFilesToasts": { + "type": "boolean", + "default": false, + "description": "%configuration.dotnet.server.suppressMiscellaneousFilesToasts%" + }, "dotnet.server.useServerGC": { "type": "boolean", "default": true, diff --git a/package.nls.json b/package.nls.json index 80988e2f3..addcc8d7e 100644 --- a/package.nls.json +++ b/package.nls.json @@ -35,6 +35,7 @@ "configuration.dotnet.server.extensionPaths": "Override for path to language server --extension arguments", "configuration.dotnet.server.crashDumpPath": "Sets a folder path where crash dumps are written to if the language server crashes. Must be writeable by the user.", "configuration.dotnet.server.suppressLspErrorToasts": "Suppresses error toasts from showing up if the server encounters a recoverable error.", + "configuration.dotnet.server.suppressMiscellaneousFilesToasts": "Suppress warning toasts from showing up if the active document is outside the open workspace.", "configuration.dotnet.server.useServerGC": "Configure the language server to use .NET server garbage collection. Server garbage collection generally provides better performance at the expensive of higher memory consumption.", "configuration.dotnet.enableXamlTools": "Enables XAML tools when using C# Dev Kit", "configuration.dotnet.projects.enableAutomaticRestore": "Enables automatic NuGet restore if the extension detects assets are missing.", diff --git a/src/lsptoolshost/languageStatusBar.ts b/src/lsptoolshost/languageStatusBar.ts index 10e258814..ba09b4d9c 100644 --- a/src/lsptoolshost/languageStatusBar.ts +++ b/src/lsptoolshost/languageStatusBar.ts @@ -11,18 +11,11 @@ import { ServerState } from './serverStateChange'; import { getCSharpDevKit } from '../utils/getCSharpDevKit'; import { RazorLanguage } from '../razor/src/razorLanguage'; -let currentServerState: ServerState = ServerState.Stopped; - export function registerLanguageStatusItems( context: vscode.ExtensionContext, languageServer: RoslynLanguageServer, languageServerEvents: RoslynLanguageServerEvents ) { - // Track the current server state. - languageServerEvents.onServerStateChange((e) => { - currentServerState = e.state; - }); - // DevKit will provide an equivalent workspace status item. if (!getCSharpDevKit()) { WorkspaceStatus.createStatusItem(context, languageServerEvents); @@ -85,13 +78,19 @@ class ProjectContextStatus { // Show a warning when the active file is part of the Miscellaneous File workspace and // project initialization is complete. - if (currentServerState === ServerState.ProjectInitializationComplete) { + if (languageServer.state === ServerState.ProjectInitializationComplete) { item.severity = e.context._vs_is_miscellaneous ? vscode.LanguageStatusSeverity.Warning : vscode.LanguageStatusSeverity.Information; } else { item.severity = vscode.LanguageStatusSeverity.Information; } + + item.detail = e.context._vs_is_miscellaneous + ? vscode.l10n.t( + 'The active document is not part of the open workspace. Not all language features will be available.' + ) + : vscode.l10n.t('Active File Context'); }); // Trigger a refresh, but don't block creation on the refresh completing. diff --git a/src/lsptoolshost/miscellaneousFileNotifier.ts b/src/lsptoolshost/miscellaneousFileNotifier.ts new file mode 100644 index 000000000..0cec79154 --- /dev/null +++ b/src/lsptoolshost/miscellaneousFileNotifier.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as crypto from 'crypto'; +import { RoslynLanguageServer } from './roslynLanguageServer'; +import { ActionOption, showWarningMessage } from '../shared/observers/utils/showMessage'; +import { ServerState } from './serverStateChange'; +import { languageServerOptions } from '../shared/options'; + +const SuppressMiscellaneousFilesToastsOption = 'dotnet.server.suppressMiscellaneousFilesToasts'; +const NotifiedDocuments = new Set(); + +export function registerMiscellaneousFileNotifier( + context: vscode.ExtensionContext, + languageServer: RoslynLanguageServer +) { + languageServer._projectContextService.onActiveFileContextChanged((e) => { + // Only warn for miscellaneous files when the workspace is fully initialized. + if (!e.context._vs_is_miscellaneous || languageServer.state !== ServerState.ProjectInitializationComplete) { + return; + } + + // Check settings and workspaceState to see if we should suppress the toast. + if ( + languageServerOptions.suppressMiscellaneousFilesToasts || + context.workspaceState.get(SuppressMiscellaneousFilesToastsOption, false) + ) { + return; + } + + // Check to see if we have already notified the user about this document. + const hash = createHash(e.uri.toString(/*skipEncoding:*/ true)); + if (NotifiedDocuments.has(hash)) { + return; + } else { + NotifiedDocuments.add(hash); + } + + const message = vscode.l10n.t( + 'The active document is not part of the open workspace. Not all language features will be available.' + ); + const dismissItem = vscode.l10n.t('Dismiss'); + // Provide the user a way to easily disable the toast without changing settings. + const disableWorkspace: ActionOption = { + title: vscode.l10n.t('Do not show for this workspace'), + action: async () => { + context.workspaceState.update(SuppressMiscellaneousFilesToastsOption, true); + }, + }; + showWarningMessage(vscode, message, dismissItem, disableWorkspace); + }); +} + +function createHash(data: string): string { + return crypto.createHash('sha256').update(data).digest('hex'); +} diff --git a/src/lsptoolshost/roslynLanguageServer.ts b/src/lsptoolshost/roslynLanguageServer.ts index 83f11799b..817321b9d 100644 --- a/src/lsptoolshost/roslynLanguageServer.ts +++ b/src/lsptoolshost/roslynLanguageServer.ts @@ -75,6 +75,7 @@ import { showInformationMessage, } from '../shared/observers/utils/showMessage'; import { registerSourceGeneratedFilesContentProvider } from './sourceGeneratedFilesContentProvider'; +import { registerMiscellaneousFileNotifier } from './miscellaneousFileNotifier'; let _channel: vscode.OutputChannel; let _traceChannel: vscode.OutputChannel; @@ -115,8 +116,10 @@ export class RoslynLanguageServer { public readonly _onAutoInsertFeature: OnAutoInsertFeature; - public _buildDiagnosticService: BuildDiagnosticsService; - public _projectContextService: ProjectContextService; + public readonly _buildDiagnosticService: BuildDiagnosticsService; + public readonly _projectContextService: ProjectContextService; + + private _state: ServerState = ServerState.Stopped; constructor( private _languageClient: RoslynLanguageClient, @@ -129,6 +132,7 @@ export class RoslynLanguageServer { this.registerSendOpenSolution(); this.registerProjectInitialization(); this.registerServerStateChanged(); + this.registerServerStateTracking(); this.registerReportProjectConfiguration(); this.registerExtensionsChanged(); this.registerTelemetryChanged(); @@ -151,6 +155,10 @@ export class RoslynLanguageServer { this._onAutoInsertFeature = new OnAutoInsertFeature(this._languageClient); } + public get state(): ServerState { + return this._state; + } + private registerSetTrace() { // Set the language client trace level based on the log level option. // setTrace only works after the client is already running. @@ -179,6 +187,12 @@ export class RoslynLanguageServer { }); } + private registerServerStateTracking() { + this._languageServerEvents.onServerStateChange((e) => { + this._state = e.state; + }); + } + private registerSendOpenSolution() { this._languageClient.onDidChangeState(async (state) => { if (state.newState === State.Running) { @@ -1053,6 +1067,7 @@ export async function activateRoslynLanguageServer( ); registerLanguageStatusItems(context, languageServer, languageServerEvents); + registerMiscellaneousFileNotifier(context, languageServer); registerCopilotExtension(languageServer, _channel); // Register any commands that need to be handled by the extension. diff --git a/src/shared/options.ts b/src/shared/options.ts index e511a73bd..469c01124 100644 --- a/src/shared/options.ts +++ b/src/shared/options.ts @@ -80,6 +80,7 @@ export interface LanguageServerOptions { readonly componentPaths: { [key: string]: string } | null; readonly enableXamlTools: boolean; readonly suppressLspErrorToasts: boolean; + readonly suppressMiscellaneousFilesToasts: boolean; readonly useServerGC: boolean; } @@ -411,6 +412,9 @@ class LanguageServerOptionsImpl implements LanguageServerOptions { public get suppressLspErrorToasts() { return readOption('dotnet.server.suppressLspErrorToasts', false); } + public get suppressMiscellaneousFilesToasts() { + return readOption('dotnet.server.suppressMiscellaneousFilesToasts', false); + } public get useServerGC() { return readOption('dotnet.server.useServerGC', true); }