diff --git a/src/OmniSharp.Abstractions/Eventing/IEventEmitterExtensions.cs b/src/OmniSharp.Abstractions/Eventing/IEventEmitterExtensions.cs index 608787b405..c17f58bc6a 100644 --- a/src/OmniSharp.Abstractions/Eventing/IEventEmitterExtensions.cs +++ b/src/OmniSharp.Abstractions/Eventing/IEventEmitterExtensions.cs @@ -45,6 +45,11 @@ public static void UnresolvedDepdendencies(this IEventEmitter emitter, string pr }); } + public static void ProjectLoadingStarted(this IEventEmitter emitter, string projectPath) => + emitter.Emit( + EventTypes.ProjectLoadingStarted, + projectPath); + public static void ProjectInformation(this IEventEmitter emitter, HashedString projectId, HashedString sessionId, @@ -55,7 +60,8 @@ public static void ProjectInformation(this IEventEmitter emitter, IEnumerable references, IEnumerable fileExtensions, IEnumerable fileCounts, - bool sdkStyleProject) + bool sdkStyleProject, + string projectFilePath) { var projectConfiguration = new ProjectConfigurationMessage() { @@ -68,7 +74,8 @@ public static void ProjectInformation(this IEventEmitter emitter, References = references.Select(hashed => hashed.Value), FileExtensions = fileExtensions.Select(hashed => hashed.Value), FileCounts = fileCounts, - SdkStyleProject = sdkStyleProject + SdkStyleProject = sdkStyleProject, + ProjectFilePath = projectFilePath }; emitter.Emit( diff --git a/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs b/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs index 021439eade..60817afed7 100644 --- a/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs +++ b/src/OmniSharp.Abstractions/Models/Events/EventTypes.cs @@ -2,6 +2,7 @@ namespace OmniSharp.Models.Events { public static class EventTypes { + public const string ProjectLoadingStarted = nameof(ProjectLoadingStarted); public const string ProjectAdded = nameof(ProjectAdded); public const string ProjectChanged = nameof(ProjectChanged); public const string ProjectRemoved = nameof(ProjectRemoved); diff --git a/src/OmniSharp.Abstractions/Models/Events/ProjectConfigurationMessage.cs b/src/OmniSharp.Abstractions/Models/Events/ProjectConfigurationMessage.cs index ed188ddf63..c8c1656e3b 100644 --- a/src/OmniSharp.Abstractions/Models/Events/ProjectConfigurationMessage.cs +++ b/src/OmniSharp.Abstractions/Models/Events/ProjectConfigurationMessage.cs @@ -15,5 +15,6 @@ public class ProjectConfigurationMessage public IEnumerable FileExtensions { get; set; } public IEnumerable FileCounts { get; set; } public bool SdkStyleProject { get; set; } + public string ProjectFilePath { get; set; } } } diff --git a/src/OmniSharp.LanguageServerProtocol/Eventing/LanguageServerEventEmitter.cs b/src/OmniSharp.LanguageServerProtocol/Eventing/LanguageServerEventEmitter.cs index cd20d7111f..bcdad05bad 100644 --- a/src/OmniSharp.LanguageServerProtocol/Eventing/LanguageServerEventEmitter.cs +++ b/src/OmniSharp.LanguageServerProtocol/Eventing/LanguageServerEventEmitter.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; @@ -6,6 +7,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; +using OmniSharp.Extensions.LanguageServer.Protocol.Server.WorkDone; using OmniSharp.LanguageServerProtocol.Handlers; using OmniSharp.Models.Diagnostics; using OmniSharp.Models.Events; @@ -16,6 +18,8 @@ public class LanguageServerEventEmitter : IEventEmitter { private readonly ILanguageServer _server; private readonly DocumentVersions _documentVersions; + private readonly ConcurrentDictionary _projectObservers = new(); + private IWorkDoneObserver _restoreObserver; public LanguageServerEventEmitter(ILanguageServer server) { @@ -46,6 +50,14 @@ public void Emit(string kind, object args) } } break; + case EventTypes.ProjectLoadingStarted: + string projectPath = (string)args; + IWorkDoneObserver projectObserver = _server.WorkDoneManager + .Create(new WorkDoneProgressBegin { Title = $"Loading {projectPath}" }) + .GetAwaiter() + .GetResult(); + _projectObservers.TryAdd(projectPath, projectObserver); + break; case EventTypes.ProjectAdded: case EventTypes.ProjectChanged: case EventTypes.ProjectRemoved: @@ -54,13 +66,31 @@ public void Emit(string kind, object args) // work done?? case EventTypes.PackageRestoreStarted: + _server.SendNotification($"o#/{kind}".ToLowerInvariant(), JToken.FromObject(args)); + _restoreObserver = _server.WorkDoneManager + .Create(new WorkDoneProgressBegin { Title = "Restoring" }) + .GetAwaiter() + .GetResult(); + break; case EventTypes.PackageRestoreFinished: + _server.SendNotification($"o#/{kind}".ToLowerInvariant(), JToken.FromObject(args)); + _restoreObserver.OnNext(new WorkDoneProgressReport { Message = "Restored" }); + _restoreObserver.OnCompleted(); + break; case EventTypes.UnresolvedDependencies: _server.SendNotification($"o#/{kind}".ToLowerInvariant(), JToken.FromObject(args)); break; case EventTypes.Error: case EventTypes.ProjectConfiguration: + _server.SendNotification($"o#/{kind}".ToLowerInvariant(), JToken.FromObject(args)); + ProjectConfigurationMessage projectMessage = (ProjectConfigurationMessage)args; + if (_projectObservers.TryGetValue(projectMessage.ProjectFilePath, out IWorkDoneObserver obs)) + { + obs.OnNext(new WorkDoneProgressReport { Message = $"Loaded {projectMessage.ProjectFilePath}" }); + obs.OnCompleted(); + } + break; case EventTypes.ProjectDiagnosticStatus: _server.SendNotification($"o#/{kind}".ToLowerInvariant(), JToken.FromObject(args)); break; diff --git a/src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs b/src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs index 20c7d61de5..48dfaf0099 100644 --- a/src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs +++ b/src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs @@ -50,6 +50,7 @@ public class LanguageServerHost : IDisposable private CompositionHost _compositionHost; private IServiceProvider _serviceProvider; private readonly Action _configureLogging; + private IObserver _workDoneObserver; public LanguageServerHost( Stream input, @@ -65,6 +66,7 @@ public LanguageServerHost( .ConfigureLogging(AddLanguageProtocolLogging(application.LogLevel)) .OnInitialize(Initialize) .OnInitialized(Initialized) + .OnStarted(Started) .WithServices(ConfigureServices); _application = application; @@ -137,6 +139,19 @@ public async Task Start() logger.LogInformation($"Omnisharp server running using Lsp at location '{environment.TargetDirectory}' on host {environment.HostProcessId}."); + await Task.WhenAll( + _compositionHost + .GetExports() + .Select(ps => ps.WaitForIdleAsync()) + .ToArray()); + + _workDoneObserver?.OnNext(new WorkDoneProgressReport + { + Message = "Language Server ready", + Percentage = 100, + }); + _workDoneObserver?.OnCompleted(); + Console.CancelKeyPress += (sender, e) => { Cancel(); @@ -330,6 +345,7 @@ static Func> CreateInteropHandler( private Task Initialize(ILanguageServer server, InitializeParams initializeParams, CancellationToken cancellationToken) { + _workDoneObserver = server.WorkDoneManager.For(initializeParams, new WorkDoneProgressBegin { Message = "Initialize Language Server" }); (_serviceProvider, _compositionHost) = CreateCompositionHost(server, initializeParams, _application, _services, _configureLogging); var handlers = ConfigureCompositionHost(server, _compositionHost); @@ -342,18 +358,25 @@ private Task Initialize(ILanguageServer server, InitializeParams initializeParam _serviceProvider.GetRequiredService())); }); + _workDoneObserver.OnNext(new WorkDoneProgressReport { Message = "Initialized handlers", Percentage = 10 }); return Task.CompletedTask; } - public async Task Initialized(ILanguageServer server, InitializeParams request, InitializeResult response, CancellationToken cancellationToken) + public Task Initialized(ILanguageServer server, InitializeParams request, InitializeResult response, CancellationToken cancellationToken) { + _workDoneObserver.OnNext(new WorkDoneProgressReport { Message = "Initialize workspace", Percentage = 20 }); WorkspaceInitializer.Initialize(_serviceProvider, _compositionHost); + return Task.CompletedTask; + } - await Task.WhenAll( - _compositionHost - .GetExports() - .Select(ps => ps.WaitForIdleAsync()) - .ToArray()); + public Task Started(ILanguageServer server, CancellationToken cancellationToken) + { + _workDoneObserver.OnNext(new WorkDoneProgressReport + { + Message = "Language Server started", + Percentage = 30, + }); + return Task.CompletedTask; } internal void UnderTest(IServiceProvider serviceProvider, CompositionHost compositionHost) diff --git a/src/OmniSharp.MSBuild/Notification/IMSBuildEventSink.cs b/src/OmniSharp.MSBuild/Notification/IMSBuildEventSink.cs index 21ecb40926..af67a11994 100644 --- a/src/OmniSharp.MSBuild/Notification/IMSBuildEventSink.cs +++ b/src/OmniSharp.MSBuild/Notification/IMSBuildEventSink.cs @@ -2,6 +2,7 @@ { public interface IMSBuildEventSink { + void ProjectLoadingStarted(string projectPath); void ProjectLoaded(ProjectLoadedEventArgs e); } } diff --git a/src/OmniSharp.MSBuild/ProjectLoadListener.cs b/src/OmniSharp.MSBuild/ProjectLoadListener.cs index 77b3f66469..a9069b01eb 100644 --- a/src/OmniSharp.MSBuild/ProjectLoadListener.cs +++ b/src/OmniSharp.MSBuild/ProjectLoadListener.cs @@ -33,6 +33,9 @@ public ProjectLoadListener(ILoggerFactory loggerFactory, IEventEmitter eventEmit _eventEmitter = eventEmitter; } + public void ProjectLoadingStarted(string projectPath) => + _eventEmitter.ProjectLoadingStarted(projectPath); + public void ProjectLoaded(ProjectLoadedEventArgs args) { try @@ -53,7 +56,18 @@ public void ProjectLoaded(ProjectLoadedEventArgs args) var (hashedFileExtensions, fileCounts) = GetUniqueHashedFileExtensionsAndCounts(args); var sdkStyleProject = IsSdkStyleProject(args); - _eventEmitter.ProjectInformation(projectId, sessionId, (int)outputKind, projectCapabilities, targetFrameworks, sdkVersion, hashedReferences, hashedFileExtensions, fileCounts, sdkStyleProject); + _eventEmitter.ProjectInformation( + projectId, + sessionId, + (int)outputKind, + projectCapabilities, + targetFrameworks, + sdkVersion, + hashedReferences, + hashedFileExtensions, + fileCounts, + sdkStyleProject, + args.Project.ProjectFileLocation.File); } catch (Exception ex) { diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index ca0a023fc7..e43a8a2dcc 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -4,11 +4,13 @@ using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using OmniSharp.Eventing; @@ -18,15 +20,13 @@ using OmniSharp.MSBuild.Models.Events; using OmniSharp.MSBuild.Notification; using OmniSharp.MSBuild.ProjectFile; +using OmniSharp.Options; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Roslyn.CSharp.Services.Refactoring.V2; -using OmniSharp.Options; +using OmniSharp.Roslyn.EditorConfig; using OmniSharp.Roslyn.Utilities; using OmniSharp.Services; using OmniSharp.Utilities; -using System.Reflection; -using Microsoft.CodeAnalysis.Diagnostics; -using OmniSharp.Roslyn.EditorConfig; namespace OmniSharp.MSBuild { @@ -310,6 +310,17 @@ private void ProcessQueue(CancellationToken cancellationToken) private (ProjectFileInfo, ProjectLoadedEventArgs) LoadOrReloadProject(string projectFilePath, Func<(ProjectFileInfo, ImmutableArray, ProjectLoadedEventArgs)> loader) { _logger.LogInformation($"Loading project: {projectFilePath}"); + foreach (IMSBuildEventSink eventSink in _eventSinks) + { + try + { + eventSink.ProjectLoadingStarted(projectFilePath); + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception thrown while calling event sinks"); + } + } try { diff --git a/tests/OmniSharp.MSBuild.Tests/NotificationTests.cs b/tests/OmniSharp.MSBuild.Tests/NotificationTests.cs index f7c01ec35d..01eb774fa4 100644 --- a/tests/OmniSharp.MSBuild.Tests/NotificationTests.cs +++ b/tests/OmniSharp.MSBuild.Tests/NotificationTests.cs @@ -30,6 +30,10 @@ public void ProjectLoaded(ProjectLoadedEventArgs e) { _onLoaded(e); } + + public void ProjectLoadingStarted(string projectPath) + { + } } [Fact]