From f640958c7cafc30dad2057de8d9c1b2eb70986ab Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:11:48 +1000 Subject: [PATCH] batch selection changed calculations --- .../FileWatching/FileChangedService.cs | 12 +++++- .../Features/CodeEditor/SharpIdeCodeEdit.cs | 42 +++++++++++++------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/SharpIDE.Application/Features/FileWatching/FileChangedService.cs b/src/SharpIDE.Application/Features/FileWatching/FileChangedService.cs index bb704cf..cae6f38 100644 --- a/src/SharpIDE.Application/Features/FileWatching/FileChangedService.cs +++ b/src/SharpIDE.Application/Features/FileWatching/FileChangedService.cs @@ -25,13 +25,12 @@ public class FileChangedService private readonly RoslynAnalysis _roslynAnalysis; private readonly IdeOpenTabsFileManager _openTabsFileManager; private readonly AsyncBatchingWorkQueue _updateSolutionDiagnosticsQueue; - public static readonly IAsynchronousOperationListener NullListener = new AsynchronousOperationListenerProvider.NullOperationListener(); public FileChangedService(RoslynAnalysis roslynAnalysis, IdeOpenTabsFileManager openTabsFileManager) { _roslynAnalysis = roslynAnalysis; _openTabsFileManager = openTabsFileManager; - _updateSolutionDiagnosticsQueue = new AsyncBatchingWorkQueue(TimeSpan.FromMilliseconds(200), ProcessBatchAsync, NullListener, CancellationToken.None); + _updateSolutionDiagnosticsQueue = new AsyncBatchingWorkQueue(TimeSpan.FromMilliseconds(200), ProcessBatchAsync, IAsynchronousOperationListener.Instance, CancellationToken.None); } public SharpIdeSolutionModel SolutionModel { get; set; } = null!; @@ -177,3 +176,12 @@ public class FileChangedService _updateSolutionDiagnosticsQueue.AddWork(); } } + +public static class NullOperationListenerExtensions +{ + private static readonly IAsynchronousOperationListener _nullOperationListener = new AsynchronousOperationListenerProvider.NullOperationListener(); + extension(IAsynchronousOperationListener nullOperationListener) + { + public static IAsynchronousOperationListener Instance => _nullOperationListener; + } +} diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs index cfbc152..6d03c8f 100644 --- a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs @@ -3,6 +3,7 @@ using Godot; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Tags; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Threading; @@ -47,6 +48,7 @@ public partial class SharpIdeCodeEdit : CodeEdit private bool _settingWholeDocumentTextSuppressLineEditsEvent; // A dodgy workaround - setting the whole document doesn't guarantee that the line count stayed the same etc. We are still going to have broken highlighting. TODO: Investigate getting minimal text change ranges, and change those ranges only private bool _fileDeleted; private IDisposable? _projectDiagnosticsObserveDisposable; + private readonly AsyncBatchingWorkQueue _selectionChangedQueue; [Inject] private readonly IdeOpenTabsFileManager _openTabsFileManager = null!; [Inject] private readonly RunService _runService = null!; @@ -69,6 +71,11 @@ public partial class SharpIdeCodeEdit : CodeEdit "(", ",", "=", "\t", ":" ]; + public SharpIdeCodeEdit() + { + _selectionChangedQueue = new AsyncBatchingWorkQueue(TimeSpan.FromMilliseconds(150), ProcessSelectionChanged, IAsynchronousOperationListener.Instance, CancellationToken.None); + } + public override void _Ready() { // _filter_code_completion_candidates_impl uses these prefixes to determine where the completions menu is allowed to show. @@ -206,24 +213,35 @@ public partial class SharpIdeCodeEdit : CodeEdit //SetSymbolLookupWordAsValid(valid); SetSymbolLookupWordAsValid(true); } + + private async ValueTask ProcessSelectionChanged(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) return; + string? selectedText = null; + await this.InvokeAsync(() => + { + if (HasSelection() is false) return; + selectedText = GetSelectedText(); + }); + if (string.IsNullOrWhiteSpace(selectedText)) return; + var lineBreakCount = 0; + var slashRsToRemove = 0; + + foreach (var c in selectedText.AsSpan()) + { + if (c is '\n') lineBreakCount++; + else if (c is '\r') slashRsToRemove++; + } + var charLength = selectedText.Length - lineBreakCount - slashRsToRemove; + _editorCaretPositionService.SelectionInfo = (charLength, lineBreakCount); + } private void OnCaretChanged() { var caretPosition = GetCaretPosition(startAt1: true); if (HasSelection()) { - // Probably should be debounced - var selectedText = GetSelectedText(); - var lineBreakCount = 0; - var slashRsToRemove = 0; - - foreach (var c in selectedText.AsSpan()) - { - if (c is '\n') lineBreakCount++; - else if (c is '\r') slashRsToRemove++; - } - var charLength = selectedText.Length - lineBreakCount - slashRsToRemove; - _editorCaretPositionService.SelectionInfo = (charLength, lineBreakCount); + _selectionChangedQueue.AddWork(); } else {