Skip to content

Commit

Permalink
Add server->client notification to show toast and implement for project
Browse files Browse the repository at this point in the history
load failures
  • Loading branch information
dibarbet committed Aug 7, 2023
1 parent 38480cc commit 05bdea6
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
using System.Composition;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer.Handler.DebugConfiguration;
using Microsoft.CodeAnalysis.LanguageServer.LanguageServer;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.MSBuild.Build;
using Microsoft.CodeAnalysis.MSBuild.Logging;
Expand All @@ -20,7 +19,9 @@
using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace;

Expand Down Expand Up @@ -76,7 +77,13 @@ public LanguageServerProjectSystem(

public async Task OpenSolutionAsync(string solutionFilePath)
{
await TryEnsureMSBuildLoadedAsync(Path.GetDirectoryName(solutionFilePath)!);
var result = await TryEnsureMSBuildLoadedAsync(Path.GetDirectoryName(solutionFilePath)!);
if (!result)
{
var message = string.Format(LanguageServerResources.There_were_errors_loading_solution_0_See_log_for_details, Path.GetFileName(solutionFilePath));
await ShowToastNotification.ShowToastNotificationAsync(LSP.MessageType.Error, message, CancellationToken.None, ShowToastNotification.ShowCSharpLogsCommand);
}

await OpenSolutionCoreAsync(solutionFilePath);
}

Expand Down Expand Up @@ -207,7 +214,26 @@ private async Task LoadOrReloadProjectAsync(string projectPath, ProjectBuildMana
{
foreach (var logItem in loadedFile.Log)
{
_logger.LogWarning($"{logItem.Kind} while loading {logItem.ProjectFilePath}: {logItem.Message}");
var projectName = Path.GetFileName(projectPath);
var messageType = logItem.Kind switch
{
WorkspaceDiagnosticKind.Failure => LSP.MessageType.Error,
WorkspaceDiagnosticKind.Warning => LSP.MessageType.Warning,
_ => throw ExceptionUtilities.UnexpectedValue(logItem.Kind)
};

if (messageType is LSP.MessageType.Error)
{
_logger.LogError($"{logItem.Kind} while loading {logItem.ProjectFilePath}: {logItem.Message}");
var message = string.Format(LanguageServerResources.There_were_errors_loading_project_0_See_log_for_details, projectName);
await ShowToastNotification.ShowToastNotificationAsync(messageType, message, cancellationToken, ShowToastNotification.ShowCSharpLogsCommand);
}
else
{
_logger.LogWarning($"{logItem.Kind} while loading {logItem.ProjectFilePath}: {logItem.Message}");
var message = string.Format(LanguageServerResources.There_were_warnings_loading_project_0_See_log_for_details, projectName);
await ShowToastNotification.ShowToastNotificationAsync(messageType, message, cancellationToken, ShowToastNotification.ShowCSharpLogsCommand);
}
}
}
else
Expand All @@ -219,6 +245,8 @@ private async Task LoadOrReloadProjectAsync(string projectPath, ProjectBuildMana
catch (Exception e)
{
_logger.LogError(e, $"Exception thrown while loading {projectPath}");
var message = string.Format(LanguageServerResources.There_were_errors_loading_project_0_See_log_for_details, Path.GetFileName(projectPath));
await ShowToastNotification.ShowToastNotificationAsync(LSP.MessageType.Error, message, cancellationToken, ShowToastNotification.ShowCSharpLogsCommand);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@
<data name="Running_tests" xml:space="preserve">
<value>Running tests...</value>
</data>
<data name="Show_csharp_logs" xml:space="preserve">
<value>Show C# logs</value>
</data>
<data name="Stack_Trace" xml:space="preserve">
<value>Stack Trace</value>
</data>
Expand All @@ -180,4 +183,13 @@
<data name="Test_run_error" xml:space="preserve">
<value>Test run error: {0}</value>
</data>
<data name="There_were_errors_loading_project_0_See_log_for_details" xml:space="preserve">
<value>There were errors loading project {0}. See log for details.</value>
</data>
<data name="There_were_errors_loading_solution_0_See_log_for_details" xml:space="preserve">
<value>There were errors loading solutution {0}. See log for details.</value>
</data>
<data name="There_were_warnings_loading_project_0_See_log_for_details" xml:space="preserve">
<value>There were warnings loading project {0}. See log for details.</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Runtime.Serialization;
using Microsoft.CodeAnalysis.LanguageServer.LanguageServer;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer;

/// <summary>
/// Implements a custom version of the standard 'window/showMessageRequest' to display a toast on the client.
/// The standard version requires us to wait for a response and then do something on the server with it.
/// That can be useful in certain cases, but a lot of the time we just want to show a toast with buttons that map to client side commands.
/// This request allows us to do just that.
/// </summary>
internal class ShowToastNotification
{
private const string ShowToastNotificationName = "window/showToast";

public static readonly LSP.Command ShowCSharpLogsCommand = new()
{
Title = LanguageServerResources.Show_csharp_logs,
CommandIdentifier = "csharp.showOutputWindow"
};

public static async Task ShowToastNotificationAsync(LSP.MessageType messageType, string message, CancellationToken cancellationToken, params LSP.Command[] commands)
{
Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through.");
var languageServerManager = LanguageServerHost.Instance.GetRequiredLspService<IClientLanguageServerManager>();
var toastParams = new ShowToastNotificationParams(messageType, message, commands);
await languageServerManager.SendNotificationAsync(ShowToastNotificationName, toastParams, cancellationToken);
}

[DataContract]
private record ShowToastNotificationParams(
[property: DataMember(Name = "messageType")] LSP.MessageType MessageType,
[property: DataMember(Name = "message")] string Message,
[property: DataMember(Name = "commands")] LSP.Command[] Commands);
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 05bdea6

Please sign in to comment.