From 52bf87faca8a78da7542395b7c2d9ba26ebbfc6c Mon Sep 17 00:00:00 2001 From: Mathieu Guindon Date: Tue, 26 Mar 2024 22:21:31 -0400 Subject: [PATCH] update document state on change --- .../Document/CodeDocumentTabViewModel.cs | 74 +++++++++++++------ .../Shell/Document/DocumentTabViewModel.cs | 11 +-- .../Shell/Document/IDocumentTabViewModel.cs | 2 +- .../Document/SourceCodeEditorControl.xaml.cs | 41 +++++----- .../Language/DocumentDiagnosticHandler.cs | 6 +- .../Handlers/Language/FoldingRangeHandler.cs | 4 +- 6 files changed, 85 insertions(+), 53 deletions(-) diff --git a/Client/Rubberduck.Editor/Shell/Document/CodeDocumentTabViewModel.cs b/Client/Rubberduck.Editor/Shell/Document/CodeDocumentTabViewModel.cs index abf9cb63..722bcf2c 100644 --- a/Client/Rubberduck.Editor/Shell/Document/CodeDocumentTabViewModel.cs +++ b/Client/Rubberduck.Editor/Shell/Document/CodeDocumentTabViewModel.cs @@ -1,18 +1,18 @@ -using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using AsyncAwaitBestPractices; +using OmniSharp.Extensions.LanguageServer.Protocol.Client; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using Rubberduck.InternalApi.Extensions; using Rubberduck.InternalApi.ServerPlatform.LanguageServer; using Rubberduck.InternalApi.Settings.Model.Editor; using Rubberduck.UI.Command.SharedHandlers; +using Rubberduck.UI.Services; using Rubberduck.UI.Shell.Document; using Rubberduck.UI.Shell.StatusBar; using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using OmniSharp.Extensions.LanguageServer.Protocol.Document; -using Rubberduck.InternalApi.Extensions; using System.Linq; using System.Threading; -using Rubberduck.UI.Services; +using System.Threading.Tasks; namespace Rubberduck.Editor.Shell.Document { @@ -21,6 +21,8 @@ namespace Rubberduck.Editor.Shell.Document /// public abstract class CodeDocumentTabViewModel : DocumentTabViewModel, ICodeDocumentTabViewModel { + public event EventHandler CodeDocumentStateChanged = delegate { }; + private readonly Lazy _languageClient; private readonly UIServiceHelper _service; @@ -33,6 +35,7 @@ public CodeDocumentTabViewModel(CodeDocumentState state, bool isReadOnly, { _languageClient = new Lazy(() => lsp.Invoke(), isThreadSafe: true); _service = service; + _uri = state.Uri; Title = state.Name; SettingKey = nameof(EditorSettings); @@ -46,12 +49,13 @@ public CodeDocumentTabViewModel(CodeDocumentState state, bool isReadOnly, /// private Timer IdleTimer { get; } private TimeSpan IdleDelay => UIServiceHelper.Instance!.Settings.EditorSettings.IdleTimerDuration; - private void IdleTimerCallback(object? _) => NotifyDocumentChanged(); + private void IdleTimerCallback(object? _) => NotifyDocumentChangedAsync().SafeFireAndForget(); /// /// Resets the idle timer to fire a callback in IdleDelay milliseconds. /// private void ResetIdleTimer() => IdleTimer.Change(Convert.ToInt32(IdleDelay.TotalMilliseconds), Timeout.Infinite); + private void DisableIdleTimer() => IdleTimer.Change(Timeout.Infinite, Timeout.Infinite); private ILanguageClient LanguageClient => _languageClient.Value; @@ -62,14 +66,27 @@ public CodeDocumentState CodeDocumentState set { _state = value; - DocumentState = value; OnPropertyChanged(); + CodeDocumentStateChanged?.Invoke(this, EventArgs.Empty); } } + public override DocumentState DocumentState + { + get => _state; + set => CodeDocumentState = value as CodeDocumentState ?? throw new InvalidOperationException(); + } + + public override Uri DocumentUri + { + get => _uri; + set => CodeDocumentUri = value as WorkspaceFileUri ?? throw new InvalidOperationException(); + } + protected override void OnTextChanged() { - // todo notify server, etc. + DisableIdleTimer(); + NotifyDocumentChangedAsync().SafeFireAndForget(e => _service.LogException(e)); } public string LanguageId => _state.Language.Id; @@ -91,9 +108,9 @@ public WorkspaceFileUri CodeDocumentUri public override SupportedDocumentType DocumentType => SupportedDocumentType.SourceFile; - private void NotifyDocumentChanged() => NotifyDocumentChanged(null!, null!); + private async Task NotifyDocumentChangedAsync() => await NotifyDocumentChangedAsync(null!, null!); - private void NotifyDocumentChanged(OmniSharp.Extensions.LanguageServer.Protocol.Models.Range? range, string? text) + private async Task NotifyDocumentChangedAsync(OmniSharp.Extensions.LanguageServer.Protocol.Models.Range? range, string? text) { // increment local version first... DocumentState = _state with { Version = _state.Version + 1 }; @@ -114,43 +131,58 @@ private void NotifyDocumentChanged(OmniSharp.Extensions.LanguageServer.Protocol. }) }; + _service.LogDebug($"Notifying server of document changes.", $"DocumentId: {DocumentState.Id} Version: {DocumentState.Version}"); LanguageClient.DidChangeTextDocument(request); + + await Task.WhenAll(RequestFoldingsAsync(), RequestDiagnosticsAsync()).ConfigureAwait(false); } - private async Task> RequestDiagnosticsAsync() + private async Task RequestDiagnosticsAsync() { - var report = await LanguageClient.RequestDocumentDiagnostic(new DocumentDiagnosticParams + var request = new DocumentDiagnosticParams { Identifier = "RDE", TextDocument = new TextDocumentIdentifier { Uri = _uri.AbsoluteLocation, } - }); + }; + + _service.LogDebug($"Requesting document diagnostics."); + var report = await LanguageClient.RequestDocumentDiagnostic(request); if (report is IFullDocumentDiagnosticReport fullReport) { + _service.LogDebug($"Received {fullReport.Items.Count()} diagnostics."); CodeDocumentState = _state.WithDiagnostics(fullReport.Items); - return fullReport.Items; } else { - return _state.Diagnostics; + _service.LogDebug($"Received a diagnostic report that was not a IFullDocumentDiagnosticReport.", $"Report type : {report?.GetType().Name ?? "(null)"}"); } } - private async Task> RequestFoldingsAsync() + private async Task RequestFoldingsAsync() { - var foldings = await LanguageClient.RequestFoldingRange(new FoldingRangeRequestParam + var request = new FoldingRangeRequestParam { TextDocument = new TextDocumentIdentifier { Uri = _uri.AbsoluteLocation, } - }); + }; - return foldings?.ToList() ?? []; + _service.LogDebug($"Requesting document folding ranges."); + var foldings = await LanguageClient.RequestFoldingRange(request); + if (foldings is not null) + { + _service.LogDebug($"Received {foldings.Count()} document folding ranges."); + CodeDocumentState = _state.WithFoldings(foldings); + } + else + { + _service.LogDebug($"Received a null response for folding ranges."); + } } - } } diff --git a/Client/Rubberduck.Editor/Shell/Document/DocumentTabViewModel.cs b/Client/Rubberduck.Editor/Shell/Document/DocumentTabViewModel.cs index 12333a2c..80cecb0d 100644 --- a/Client/Rubberduck.Editor/Shell/Document/DocumentTabViewModel.cs +++ b/Client/Rubberduck.Editor/Shell/Document/DocumentTabViewModel.cs @@ -12,8 +12,6 @@ namespace Rubberduck.Editor.Shell.Document /// public abstract class DocumentTabViewModel : WindowViewModel, IDocumentTabViewModel { - public event EventHandler DocumentStateChanged = delegate { }; - public DocumentTabViewModel(DocumentState state, bool isReadOnly, ShowRubberduckSettingsCommand showSettingsCommand, CloseToolWindowCommand closeToolWindowCommand, @@ -31,7 +29,7 @@ public DocumentTabViewModel(DocumentState state, bool isReadOnly, } private DocumentState _state; - public DocumentState DocumentState + public virtual DocumentState DocumentState { get => _state; set @@ -47,7 +45,7 @@ public DocumentState DocumentState public IDocumentStatusViewModel Status { get; } private Uri _uri; - public Uri DocumentUri + public virtual Uri DocumentUri { get => _uri; set @@ -79,10 +77,7 @@ public string TextContent } } - protected virtual void OnTextChanged() - { - - } + protected virtual void OnTextChanged() { } private bool _isReadOnly; public bool IsReadOnly diff --git a/Client/Rubberduck.UI/Shell/Document/IDocumentTabViewModel.cs b/Client/Rubberduck.UI/Shell/Document/IDocumentTabViewModel.cs index 26973400..72f28510 100644 --- a/Client/Rubberduck.UI/Shell/Document/IDocumentTabViewModel.cs +++ b/Client/Rubberduck.UI/Shell/Document/IDocumentTabViewModel.cs @@ -30,7 +30,6 @@ public interface IDocumentTabViewModel : ITabViewModel { Uri DocumentUri { get; set; } DocumentState DocumentState { get; set; } - event EventHandler DocumentStateChanged; bool IsReadOnly { get; set; } @@ -40,6 +39,7 @@ public interface IDocumentTabViewModel : ITabViewModel public interface ICodeDocumentTabViewModel : IDocumentTabViewModel { + event EventHandler CodeDocumentStateChanged; string LanguageId { get; } WorkspaceFileUri CodeDocumentUri { get; set; } diff --git a/Client/Rubberduck.UI/Shell/Document/SourceCodeEditorControl.xaml.cs b/Client/Rubberduck.UI/Shell/Document/SourceCodeEditorControl.xaml.cs index e938cdac..22c809cd 100644 --- a/Client/Rubberduck.UI/Shell/Document/SourceCodeEditorControl.xaml.cs +++ b/Client/Rubberduck.UI/Shell/Document/SourceCodeEditorControl.xaml.cs @@ -125,44 +125,43 @@ private void HideMarkerToolTip() private void ExpandAllFoldings() { - foreach (var folding in _foldings.AllFoldings) + Dispatcher.Invoke(() => { - folding.IsFolded = false; - } + foreach (var folding in _foldings.AllFoldings) + { + folding.IsFolded = false; + } + }); } private void CollapseAllFoldings() { - foreach (var folding in _foldings.AllFoldings) + Dispatcher.Invoke(() => { - folding.IsFolded = true; - } + foreach (var folding in _foldings.AllFoldings) + { + folding.IsFolded = true; + } + }); } private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { ViewModel = (ICodeDocumentTabViewModel)e.NewValue; - ViewModel.DocumentStateChanged += OnServerDocumentStateChanged; - HandleDataContextChangedAsync().SafeFireAndForget(); - } + ViewModel.CodeDocumentStateChanged += ViewModelDocumentStateChanged; + DataContextChanged -= OnDataContextChanged; - private void OnServerDocumentStateChanged(object? sender, EventArgs e) - { + Editor.Text = ViewModel.TextContent; UpdateFoldingsAsync().SafeFireAndForget(); UpdateDiagnostics(); + UpdateStatusInfo(); } - private async Task HandleDataContextChangedAsync() + private void ViewModelDocumentStateChanged(object? sender, EventArgs e) { - if (ViewModel.DocumentType == SupportedDocumentType.SourceFile) - { - Editor.Text = ViewModel.TextContent; - var foldingsTask = UpdateFoldingsAsync(); - - await Task.WhenAll([foldingsTask]).ConfigureAwait(false); - } - UpdateStatusInfo(); + UpdateDiagnostics(); + UpdateFoldingsAsync().SafeFireAndForget(); } private async Task UpdateFoldingsAsync() @@ -179,6 +178,7 @@ await Dispatcher.InvokeAsync(() => { var newFoldings = foldings .Select(e => e.ToNewFolding(Editor.Document)) + .Where(e => e.EndOffset < Editor.Document.TextLength) .OrderBy(e => e.StartOffset) .ToArray(); if (firstErrorRange != null) @@ -193,6 +193,7 @@ await Dispatcher.InvokeAsync(() => private void UpdateDiagnostics() { _markers.RemoveAll(e => true); + _margin.InvalidateVisual(); foreach (var diagnostic in ViewModel.CodeDocumentState.Diagnostics) { diagnostic.WithTextMarker(Editor, _markers); diff --git a/Server/Rubberduck.LanguageServer/Handlers/Language/DocumentDiagnosticHandler.cs b/Server/Rubberduck.LanguageServer/Handlers/Language/DocumentDiagnosticHandler.cs index 8ecaf9d9..7a87c69e 100644 --- a/Server/Rubberduck.LanguageServer/Handlers/Language/DocumentDiagnosticHandler.cs +++ b/Server/Rubberduck.LanguageServer/Handlers/Language/DocumentDiagnosticHandler.cs @@ -3,6 +3,7 @@ using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Rubberduck.InternalApi.ServerPlatform.LanguageServer; using Rubberduck.InternalApi.Services; +using Rubberduck.ServerPlatform; using System; using System.Threading; using System.Threading.Tasks; @@ -11,11 +12,13 @@ namespace Rubberduck.LanguageServer.Handlers.Language; public class DocumentDiagnosticHandler : DocumentDiagnosticHandlerBase { + private readonly ServerPlatformServiceHelper _service; private readonly IAppWorkspacesService _workspaces; private readonly TextDocumentSelector _selector; - public DocumentDiagnosticHandler(IAppWorkspacesService workspaces, SupportedLanguage language) + public DocumentDiagnosticHandler(ServerPlatformServiceHelper service, IAppWorkspacesService workspaces, SupportedLanguage language) { + _service = service; _workspaces = workspaces; _selector = language.ToTextDocumentSelector(); } @@ -29,6 +32,7 @@ public async override Task Handle(DocumentDiagn if (workspace.TryGetWorkspaceFile(uri, out var state) && state is CodeDocumentState document) { + _service.LogInformation($"Found {document.Diagnostics.Count} diagnostics for document at uri '{uri}'."); return new RelatedFullDocumentDiagnosticReport { Items = new Container(document.Diagnostics) diff --git a/Server/Rubberduck.LanguageServer/Handlers/Language/FoldingRangeHandler.cs b/Server/Rubberduck.LanguageServer/Handlers/Language/FoldingRangeHandler.cs index 32bdab57..8b330b42 100644 --- a/Server/Rubberduck.LanguageServer/Handlers/Language/FoldingRangeHandler.cs +++ b/Server/Rubberduck.LanguageServer/Handlers/Language/FoldingRangeHandler.cs @@ -61,12 +61,12 @@ public FoldingRangeHandler(ServerPlatformServiceHelper service, IAppWorkspacesSt } }, out var exception, nameof(FoldingRangeHandler)) && exception != null) { - // in case of failure, we throw here to return an error response: + // in case of failure, we throw here to return an error response (exception was already logged): throw exception; } var result = new Container(items); - return await Task.FromResult(result); + return result; } protected override FoldingRangeRegistrationOptions CreateRegistrationOptions(FoldingRangeCapability capability, ClientCapabilities clientCapabilities)