From 84eb99fda7090f6bf94230ae4fdc7c7b021bc235 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Thu, 10 Oct 2024 01:57:05 -0700 Subject: [PATCH 1/3] Add miscellaneous file open notifications --- l10n/bundle.l10n.json | 4 ++ src/lsptoolshost/languageStatusBar.ts | 9 +-- src/lsptoolshost/miscellaneousFileNotifier.ts | 65 +++++++++++++++++++ src/lsptoolshost/roslynLanguageServer.ts | 19 +++++- 4 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 src/lsptoolshost/miscellaneousFileNotifier.ts diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 5cd775f7cb..ef183d28c2 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -172,6 +172,10 @@ "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: ", + "{0} is not part of the open workspace. Not all language features will be available.": "{0} 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", + "Do not show again": "Do not show again", "Open solution": "Open solution", "Restart server": "Restart server", "C# Workspace Status": "C# Workspace Status", diff --git a/src/lsptoolshost/languageStatusBar.ts b/src/lsptoolshost/languageStatusBar.ts index 10e258814e..9341ce65bb 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,7 +78,7 @@ 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; diff --git a/src/lsptoolshost/miscellaneousFileNotifier.ts b/src/lsptoolshost/miscellaneousFileNotifier.ts new file mode 100644 index 0000000000..e5a4576d9e --- /dev/null +++ b/src/lsptoolshost/miscellaneousFileNotifier.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { RoslynLanguageServer } from './roslynLanguageServer'; +import { ActionOption, showWarningMessage } from '../shared/observers/utils/showMessage'; +import { ServerState } from './serverStateChange'; +import path = require('path'); + +const NotifyMiscellaneousFilesOption = 'dotnet.miscellaneousFilesNotification.enabled'; +const RecentlyNotifiedDocuments = new Set(); +const CooldownTime = 60 * 1000; + +export function registerMiscellaneousFileNotifier( + context: vscode.ExtensionContext, + languageServer: RoslynLanguageServer +) { + context.workspaceState.update(NotifyMiscellaneousFilesOption, undefined); + context.globalState.update(NotifyMiscellaneousFilesOption, undefined); + + languageServer._projectContextService.onActiveFileContextChanged((e) => { + if (RecentlyNotifiedDocuments.has(e.uri)) { + return; + } + + if (!e.context._vs_is_miscellaneous || languageServer.state !== ServerState.ProjectInitializationComplete) { + return; + } + + if (!context.globalState.get(NotifyMiscellaneousFilesOption, true)) { + return; + } + + if (!context.workspaceState.get(NotifyMiscellaneousFilesOption, true)) { + return; + } + + RecentlyNotifiedDocuments.add(e.uri); + + const message = vscode.l10n.t( + '{0} is not part of the open workspace. Not all language features will be available.', + path.basename(e.uri.fsPath) + ); + const dismissItem = vscode.l10n.t('Dismiss'); + const disableWorkspace: ActionOption = { + title: vscode.l10n.t('Do not show for this workspace'), + action: async () => { + context.workspaceState.update(NotifyMiscellaneousFilesOption, false); + }, + }; + const disableGlobal: ActionOption = { + title: vscode.l10n.t('Do not show again'), + action: async () => { + context.globalState.update(NotifyMiscellaneousFilesOption, false); + }, + }; + showWarningMessage(vscode, message, disableWorkspace, disableGlobal, dismissItem); + + setTimeout(() => { + RecentlyNotifiedDocuments.delete(e.uri); + }, CooldownTime); + }); +} diff --git a/src/lsptoolshost/roslynLanguageServer.ts b/src/lsptoolshost/roslynLanguageServer.ts index 83f11799b8..817321b9d7 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. From 890919ecd0dc8bd635633b0c43614ee904d22638 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Thu, 10 Oct 2024 16:16:01 -0700 Subject: [PATCH 2/3] Add setting to suppress notifications. --- l10n/bundle.l10n.json | 3 +- package.json | 5 +++ package.nls.json | 1 + src/lsptoolshost/languageStatusBar.ts | 6 +++ src/lsptoolshost/miscellaneousFileNotifier.ts | 41 ++++++++----------- src/shared/options.ts | 4 ++ 6 files changed, 34 insertions(+), 26 deletions(-) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index ef183d28c2..2c1332f49c 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -172,10 +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: ", - "{0} is not part of the open workspace. Not all language features will be available.": "{0} 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.": "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", - "Do not show again": "Do not show again", "Open solution": "Open solution", "Restart server": "Restart server", "C# Workspace Status": "C# Workspace Status", diff --git a/package.json b/package.json index 4a4ebb5f29..b4a35cc655 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 80988e2f3c..addcc8d7e5 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 9341ce65bb..ba09b4d9c7 100644 --- a/src/lsptoolshost/languageStatusBar.ts +++ b/src/lsptoolshost/languageStatusBar.ts @@ -85,6 +85,12 @@ class ProjectContextStatus { } 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 index e5a4576d9e..c6e76d55c7 100644 --- a/src/lsptoolshost/miscellaneousFileNotifier.ts +++ b/src/lsptoolshost/miscellaneousFileNotifier.ts @@ -4,24 +4,24 @@ *--------------------------------------------------------------------------------------------*/ 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 path = require('path'); +import { languageServerOptions } from '../shared/options'; -const NotifyMiscellaneousFilesOption = 'dotnet.miscellaneousFilesNotification.enabled'; -const RecentlyNotifiedDocuments = new Set(); -const CooldownTime = 60 * 1000; +const SuppressMiscellaneousFilesToastsOption = 'dotnet.server.suppressMiscellaneousFilesToasts'; +const NotifiedDocuments = new Set(); export function registerMiscellaneousFileNotifier( context: vscode.ExtensionContext, languageServer: RoslynLanguageServer ) { - context.workspaceState.update(NotifyMiscellaneousFilesOption, undefined); - context.globalState.update(NotifyMiscellaneousFilesOption, undefined); + context.workspaceState.update(SuppressMiscellaneousFilesToastsOption, undefined); languageServer._projectContextService.onActiveFileContextChanged((e) => { - if (RecentlyNotifiedDocuments.has(e.uri)) { + const hash = createHash(e.uri.toString(/*skipEncoding:*/ true)); + if (NotifiedDocuments.has(hash)) { return; } @@ -29,37 +29,30 @@ export function registerMiscellaneousFileNotifier( return; } - if (!context.globalState.get(NotifyMiscellaneousFilesOption, true)) { + if (languageServerOptions.suppressMiscellaneousFilesToasts) { return; } - if (!context.workspaceState.get(NotifyMiscellaneousFilesOption, true)) { + if (context.workspaceState.get(SuppressMiscellaneousFilesToastsOption, false)) { return; } - RecentlyNotifiedDocuments.add(e.uri); + NotifiedDocuments.add(hash); const message = vscode.l10n.t( - '{0} is not part of the open workspace. Not all language features will be available.', - path.basename(e.uri.fsPath) + 'The active document is not part of the open workspace. Not all language features will be available.' ); const dismissItem = vscode.l10n.t('Dismiss'); const disableWorkspace: ActionOption = { title: vscode.l10n.t('Do not show for this workspace'), action: async () => { - context.workspaceState.update(NotifyMiscellaneousFilesOption, false); + context.workspaceState.update(SuppressMiscellaneousFilesToastsOption, true); }, }; - const disableGlobal: ActionOption = { - title: vscode.l10n.t('Do not show again'), - action: async () => { - context.globalState.update(NotifyMiscellaneousFilesOption, false); - }, - }; - showWarningMessage(vscode, message, disableWorkspace, disableGlobal, dismissItem); - - setTimeout(() => { - RecentlyNotifiedDocuments.delete(e.uri); - }, CooldownTime); + showWarningMessage(vscode, message, dismissItem, disableWorkspace); }); } + +function createHash(data: string): string { + return crypto.createHash('sha256').update(data).digest('hex'); +} diff --git a/src/shared/options.ts b/src/shared/options.ts index e511a73bdc..469c01124c 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); } From 321ffe39a4bfd6ba59cf348b448e835f7bad23a6 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Thu, 10 Oct 2024 16:56:15 -0700 Subject: [PATCH 3/3] Move notified document check --- src/lsptoolshost/miscellaneousFileNotifier.ts | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lsptoolshost/miscellaneousFileNotifier.ts b/src/lsptoolshost/miscellaneousFileNotifier.ts index c6e76d55c7..0cec79154c 100644 --- a/src/lsptoolshost/miscellaneousFileNotifier.ts +++ b/src/lsptoolshost/miscellaneousFileNotifier.ts @@ -17,32 +17,33 @@ export function registerMiscellaneousFileNotifier( context: vscode.ExtensionContext, languageServer: RoslynLanguageServer ) { - context.workspaceState.update(SuppressMiscellaneousFilesToastsOption, undefined); - languageServer._projectContextService.onActiveFileContextChanged((e) => { - const hash = createHash(e.uri.toString(/*skipEncoding:*/ true)); - if (NotifiedDocuments.has(hash)) { - return; - } - + // Only warn for miscellaneous files when the workspace is fully initialized. if (!e.context._vs_is_miscellaneous || languageServer.state !== ServerState.ProjectInitializationComplete) { return; } - if (languageServerOptions.suppressMiscellaneousFilesToasts) { + // Check settings and workspaceState to see if we should suppress the toast. + if ( + languageServerOptions.suppressMiscellaneousFilesToasts || + context.workspaceState.get(SuppressMiscellaneousFilesToastsOption, false) + ) { return; } - if (context.workspaceState.get(SuppressMiscellaneousFilesToastsOption, false)) { + // 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); } - 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 () => {