From f94b3f5553955c5739158e5d4921acb01e3e06ef Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Tue, 28 Oct 2025 18:09:59 +1000 Subject: [PATCH] fix completion insertion --- .../Analysis/IdeApplyCompletionService.cs | 7 ++++--- .../Features/Analysis/RoslynAnalysis.cs | 15 +++++++++------ .../Features/CodeEditor/SharpIdeCodeEdit.cs | 19 +++++++------------ 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/SharpIDE.Application/Features/Analysis/IdeApplyCompletionService.cs b/src/SharpIDE.Application/Features/Analysis/IdeApplyCompletionService.cs index b04648e..6007e57 100644 --- a/src/SharpIDE.Application/Features/Analysis/IdeApplyCompletionService.cs +++ b/src/SharpIDE.Application/Features/Analysis/IdeApplyCompletionService.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Completion; using SharpIDE.Application.Features.FileWatching; using SharpIDE.Application.Features.SolutionDiscovery; @@ -9,9 +10,9 @@ public class IdeApplyCompletionService(RoslynAnalysis roslynAnalysis, FileChange private readonly RoslynAnalysis _roslynAnalysis = roslynAnalysis; private readonly FileChangedService _fileChangedService = fileChangedService; - public async Task ApplyCompletion(SharpIdeFile file, CompletionItem completionItem) + public async Task ApplyCompletion(SharpIdeFile file, CompletionItem completionItem, Document document) { - var (updatedDocumentText, newLinePosition) = await _roslynAnalysis.GetCompletionApplyChanges(file, completionItem); + var (updatedDocumentText, newLinePosition) = await _roslynAnalysis.GetCompletionApplyChanges(file, completionItem, document); await _fileChangedService.SharpIdeFileChanged(file, updatedDocumentText, FileChangeType.CompletionChange, newLinePosition); } } diff --git a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs index 49702e5..168193c 100644 --- a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs +++ b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs @@ -499,14 +499,18 @@ public class RoslynAnalysis(ILogger logger, BuildService buildSe return result; } - public async Task GetCodeCompletionsForDocumentAtPosition(SharpIdeFile fileModel, LinePosition linePosition) + // We store the document here, so that we have the correct version of the document when we compute completions + // This may not be the best way to do this, but it seems to work okay. It may only be a problem because I continue to update the doc in the workspace as the user continues typing, filtering the completion + // I could possibly pause updating the document while the completion list is open, but that seems more complex - handling accepted vs cancelled completions etc + public record IdeCompletionListResult(Document Document, CompletionList CompletionList); + public async Task GetCodeCompletionsForDocumentAtPosition(SharpIdeFile fileModel, LinePosition linePosition) { using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetCodeCompletionsForDocumentAtPosition)}"); await _solutionLoadedTcs.Task; var document = await GetDocumentForSharpIdeFile(fileModel); Guard.Against.Null(document, nameof(document)); var completions = await GetCompletionsAsync(document, linePosition).ConfigureAwait(false); - return completions; + return new IdeCompletionListResult(document, completions); } public async Task> GetCodeFixesForDocumentAtPosition(SharpIdeFile fileModel, LinePosition linePosition, CancellationToken cancellationToken = default) @@ -602,12 +606,11 @@ public class RoslynAnalysis(ILogger logger, BuildService buildSe return shouldTrigger; } - public async Task<(string updatedText, SharpIdeFileLinePosition sharpIdeFileLinePosition)> GetCompletionApplyChanges(SharpIdeFile file, CompletionItem completionItem, CancellationToken cancellationToken = default) + public async Task<(string updatedText, SharpIdeFileLinePosition sharpIdeFileLinePosition)> GetCompletionApplyChanges(SharpIdeFile file, CompletionItem completionItem, Document document, CancellationToken cancellationToken = default) { - var documentId = _workspace!.CurrentSolution.GetDocumentIdsWithFilePath(file.Path).Single(); - var document = SolutionExtensions.GetRequiredDocument(_workspace.CurrentSolution, documentId); + //var documentId = _workspace!.CurrentSolution.GetDocumentIdsWithFilePath(file.Path).Single(); + //var document = SolutionExtensions.GetRequiredDocument(_workspace.CurrentSolution, documentId); var completionService = CompletionService.GetService(document) ?? throw new InvalidOperationException("Completion service is not available for the document."); - var completionChange = await completionService.GetChangeAsync(document, completionItem, commitCharacter: '.', cancellationToken: cancellationToken); var sourceText = await document.GetTextAsync(cancellationToken); var newText = sourceText.WithChanges(completionChange.TextChange); diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs index 922ea26..2e02794 100644 --- a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs @@ -174,12 +174,6 @@ public partial class SharpIdeCodeEdit : CodeEdit private void OnTextChanged() { - var codeCompletionPopupClosed = GetCodeCompletionSelectedIndex() is -1; - var shouldShowCodeCompletion = codeCompletionPopupClosed && HasFocus(); // This is a bad solution - it will be triggered on copy paste, undo redo, applying code fixes etc - if (codeCompletionPopupClosed) - { - EmitSignalCodeCompletionRequested(); - } _ = Task.GodotRun(async () => { var __ = SharpIdeOtel.Source.StartActivity($"{nameof(SharpIdeCodeEdit)}.{nameof(OnTextChanged)}"); @@ -426,14 +420,15 @@ public partial class SharpIdeCodeEdit : CodeEdit var selectedIndex = GetCodeCompletionSelectedIndex(); var selectedText = GetCodeCompletionOption(selectedIndex); if (selectedText is null) return; - var completionItem = selectedText["default_value"].As>().Item; + var completionItem = selectedText["default_value"].As>().Item; _ = Task.GodotRun(async () => { - await _ideApplyCompletionService.ApplyCompletion(_currentFile, completionItem); + await _ideApplyCompletionService.ApplyCompletion(_currentFile, completionItem.CompletionItem, completionItem.Document); }); CancelCodeCompletion(); } + private record struct IdeCompletionItem(CompletionItem CompletionItem, Document Document); private void OnCodeCompletionRequested() { var (caretLine, caretColumn) = GetCaretPosition(); @@ -443,10 +438,10 @@ public partial class SharpIdeCodeEdit : CodeEdit { var linePos = new LinePosition(caretLine, caretColumn); - var completions = await _roslynAnalysis.GetCodeCompletionsForDocumentAtPosition(_currentFile, linePos); + var completionsResult = await _roslynAnalysis.GetCodeCompletionsForDocumentAtPosition(_currentFile, linePos); await this.InvokeAsync(() => { - foreach (var completionItem in completions.ItemsList) + foreach (var completionItem in completionsResult.CompletionList.ItemsList) { var symbolKindString = CollectionExtensions.GetValueOrDefault(completionItem.Properties, "SymbolKind"); var symbolKind = symbolKindString is null ? null : (SymbolKind?)int.Parse(symbolKindString); @@ -465,14 +460,14 @@ public partial class SharpIdeCodeEdit : CodeEdit _ => CodeCompletionKind.PlainText }; var isKeyword = wellKnownTags.Contains(WellKnownTags.Keyword); - AddCodeCompletionOption(godotCompletionType, completionItem.DisplayText, completionItem.DisplayText, icon: icon, value: new RefCountedContainer(completionItem)); var isExtensionMethod = wellKnownTags.Contains(WellKnownTags.ExtensionMethod); var icon = GetIconForCompletion(symbolKind, typeKind, accessibilityModifier, isKeyword, isExtensionMethod); + AddCodeCompletionOption(godotCompletionType, completionItem.DisplayText, completionItem.DisplayText, icon: icon, value: new RefCountedContainer(new IdeCompletionItem(completionItem, completionsResult.Document))); } // partially working - displays menu only when caret is what CodeEdit determines as valid UpdateCodeCompletionOptions(true); //RequestCodeCompletion(true); - GD.Print($"Found {completions.ItemsList.Count} completions, displaying menu"); + GD.Print($"Found {completionsResult.CompletionList.ItemsList.Count} completions, displaying menu"); }); }); }