improve completion triggering, filtering, insertion
This commit is contained in:
@@ -603,12 +603,14 @@ public partial class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService
|
|||||||
// 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
|
// 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
|
// 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, LinePosition LinePosition);
|
public record IdeCompletionListResult(Document Document, CompletionList CompletionList, LinePosition LinePosition);
|
||||||
public async Task<IdeCompletionListResult> GetCodeCompletionsForDocumentAtPosition(SharpIdeFile fileModel, LinePosition linePosition, CompletionTrigger completionTrigger)
|
public async Task<IdeCompletionListResult> GetCodeCompletionsForDocumentAtPosition(SharpIdeFile fileModel, string documentText, LinePosition linePosition, CompletionTrigger completionTrigger)
|
||||||
{
|
{
|
||||||
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetCodeCompletionsForDocumentAtPosition)}");
|
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetCodeCompletionsForDocumentAtPosition)}");
|
||||||
await _solutionLoadedTcs.Task;
|
await _solutionLoadedTcs.Task;
|
||||||
var document = await GetDocumentForSharpIdeFile(fileModel);
|
var document = await GetDocumentForSharpIdeFile(fileModel);
|
||||||
Guard.Against.Null(document, nameof(document));
|
Guard.Against.Null(document, nameof(document));
|
||||||
|
// The document in the workspace may have been further updated since the completion request was made, so we need to fork a document with the text at the time of the completion request
|
||||||
|
document = document.WithText(SourceText.From(documentText, Encoding.UTF8));
|
||||||
var (completions, triggerLinePosition) = await GetCompletionsAsync(document, linePosition, completionTrigger).ConfigureAwait(false);
|
var (completions, triggerLinePosition) = await GetCompletionsAsync(document, linePosition, completionTrigger).ConfigureAwait(false);
|
||||||
return new IdeCompletionListResult(document, completions, triggerLinePosition);
|
return new IdeCompletionListResult(document, completions, triggerLinePosition);
|
||||||
}
|
}
|
||||||
@@ -738,7 +740,6 @@ public partial class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService
|
|||||||
return (completions, triggerLinePosition);
|
return (completions, triggerLinePosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently unused
|
|
||||||
public async Task<bool> ShouldTriggerCompletionAsync(SharpIdeFile file, string documentText, LinePosition linePosition, CompletionTrigger completionTrigger, CancellationToken cancellationToken = default)
|
public async Task<bool> ShouldTriggerCompletionAsync(SharpIdeFile file, string documentText, LinePosition linePosition, CompletionTrigger completionTrigger, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await _solutionLoadedTcs.Task;
|
await _solutionLoadedTcs.Task;
|
||||||
@@ -746,7 +747,6 @@ public partial class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService
|
|||||||
var completionService = CompletionService.GetService(document);
|
var completionService = CompletionService.GetService(document);
|
||||||
if (completionService is null) throw new InvalidOperationException("Completion service is not available for the document.");
|
if (completionService is null) throw new InvalidOperationException("Completion service is not available for the document.");
|
||||||
|
|
||||||
//var sourceText = await document.GetTextAsync(cancellationToken);
|
|
||||||
var sourceText = SourceText.From(documentText, Encoding.UTF8);
|
var sourceText = SourceText.From(documentText, Encoding.UTF8);
|
||||||
var position = sourceText.Lines.GetPosition(linePosition);
|
var position = sourceText.Lines.GetPosition(linePosition);
|
||||||
var shouldTrigger = completionService.ShouldTriggerCompletion(document.Project, document.Project.Services, sourceText, position, completionTrigger, CompletionOptions.Default, document.Project.Solution.Options ?? OptionSet.Empty);
|
var shouldTrigger = completionService.ShouldTriggerCompletion(document.Project, document.Project.Services, sourceText, position, completionTrigger, CompletionOptions.Default, document.Project.Solution.Options ?? OptionSet.Empty);
|
||||||
|
|||||||
@@ -216,22 +216,24 @@ public partial class SharpIdeCodeEdit : CodeEdit
|
|||||||
|
|
||||||
private void OnTextChanged()
|
private void OnTextChanged()
|
||||||
{
|
{
|
||||||
|
var text = Text;
|
||||||
|
var pendingCompletionTrigger = _pendingCompletionTrigger;
|
||||||
|
_pendingCompletionTrigger = null;
|
||||||
|
var cursorPosition = GetCaretPosition();
|
||||||
_ = Task.GodotRun(async () =>
|
_ = Task.GodotRun(async () =>
|
||||||
{
|
{
|
||||||
var __ = SharpIdeOtel.Source.StartActivity($"{nameof(SharpIdeCodeEdit)}.{nameof(OnTextChanged)}");
|
var __ = SharpIdeOtel.Source.StartActivity($"{nameof(SharpIdeCodeEdit)}.{nameof(OnTextChanged)}");
|
||||||
_currentFile.IsDirty.Value = true;
|
_currentFile.IsDirty.Value = true;
|
||||||
await _fileChangedService.SharpIdeFileChanged(_currentFile, Text, FileChangeType.IdeUnsavedChange);
|
await _fileChangedService.SharpIdeFileChanged(_currentFile, text, FileChangeType.IdeUnsavedChange);
|
||||||
if (pendingCompletionTrigger is not null)
|
if (pendingCompletionTrigger is not null)
|
||||||
{
|
{
|
||||||
var cursorPosition = GetCaretPosition();
|
|
||||||
var linePosition = new LinePosition(cursorPosition.line, cursorPosition.col);
|
|
||||||
completionTrigger = pendingCompletionTrigger;
|
completionTrigger = pendingCompletionTrigger;
|
||||||
pendingCompletionTrigger = null;
|
var linePosition = new LinePosition(cursorPosition.line, cursorPosition.col);
|
||||||
var shouldTriggerCompletion = await _roslynAnalysis.ShouldTriggerCompletionAsync(_currentFile, Text, linePosition, completionTrigger!.Value);
|
var shouldTriggerCompletion = await _roslynAnalysis.ShouldTriggerCompletionAsync(_currentFile, text, linePosition, completionTrigger!.Value);
|
||||||
GD.Print($"Code completion trigger typed: '{completionTrigger.Value.Character}' at {linePosition.Line}:{linePosition.Character} should trigger: {shouldTriggerCompletion}");
|
GD.Print($"Code completion trigger typed: '{completionTrigger.Value.Character}' at {linePosition.Line}:{linePosition.Character} should trigger: {shouldTriggerCompletion}");
|
||||||
if (shouldTriggerCompletion)
|
if (shouldTriggerCompletion)
|
||||||
{
|
{
|
||||||
await OnCodeCompletionRequested(completionTrigger.Value);
|
await OnCodeCompletionRequested(completionTrigger.Value, text, cursorPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (pendingCompletionFilterReason is not null)
|
else if (pendingCompletionFilterReason is not null)
|
||||||
|
|||||||
@@ -67,11 +67,11 @@ public partial class SharpIdeCodeEdit
|
|||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
private EventWrapper<CompletionTrigger, Task> CustomCodeCompletionRequested { get; } = new(_ => Task.CompletedTask);
|
private EventWrapper<CompletionTrigger, string, (int,int), Task> CustomCodeCompletionRequested { get; } = new((_, _, _) => Task.CompletedTask);
|
||||||
private CompletionList? completionList;
|
private CompletionList? completionList;
|
||||||
private Document? completionResultDocument;
|
private Document? completionResultDocument;
|
||||||
private CompletionTrigger? completionTrigger;
|
private CompletionTrigger? completionTrigger;
|
||||||
private CompletionTrigger? pendingCompletionTrigger;
|
private CompletionTrigger? _pendingCompletionTrigger;
|
||||||
private CompletionFilterReason? pendingCompletionFilterReason;
|
private CompletionFilterReason? pendingCompletionFilterReason;
|
||||||
|
|
||||||
private readonly List<string> _codeCompletionTriggers =
|
private readonly List<string> _codeCompletionTriggers =
|
||||||
@@ -94,39 +94,36 @@ public partial class SharpIdeCodeEdit
|
|||||||
private async Task CustomFilterCodeCompletionCandidates(CompletionFilterReason filterReason)
|
private async Task CustomFilterCodeCompletionCandidates(CompletionFilterReason filterReason)
|
||||||
{
|
{
|
||||||
if (completionList is null || completionList.ItemsList.Count is 0) return;
|
if (completionList is null || completionList.ItemsList.Count is 0) return;
|
||||||
var cursorPosition = GetCaretPosition();
|
var cursorPosition = await this.InvokeAsync(() => GetCaretPosition());
|
||||||
var linePosition = new LinePosition(cursorPosition.line, cursorPosition.col);
|
var linePosition = new LinePosition(cursorPosition.line, cursorPosition.col);
|
||||||
var filteredCompletions = RoslynAnalysis.FilterCompletions(_currentFile, Text, linePosition, completionList, completionTrigger!.Value, filterReason);
|
var filteredCompletions = RoslynAnalysis.FilterCompletions(_currentFile, Text, linePosition, completionList, completionTrigger!.Value, filterReason);
|
||||||
_codeCompletionOptions = filteredCompletions;
|
_codeCompletionOptions = filteredCompletions;
|
||||||
await this.InvokeAsync(QueueRedraw);
|
await this.InvokeAsync(QueueRedraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnCodeCompletionRequested(CompletionTrigger completionTrigger)
|
private async Task OnCodeCompletionRequested(CompletionTrigger completionTrigger, string documentTextAtTimeOfCompletionRequest, (int, int) completionCaretPosition)
|
||||||
{
|
{
|
||||||
var (caretLine, caretColumn) = GetCaretPosition();
|
var (caretLine, caretColumn) = completionCaretPosition;
|
||||||
|
|
||||||
GD.Print($"Code completion requested at line {caretLine}, column {caretColumn}");
|
GD.Print($"Code completion requested at line {caretLine}, column {caretColumn}");
|
||||||
_ = Task.GodotRun(async () =>
|
var linePos = new LinePosition(caretLine, caretColumn);
|
||||||
{
|
|
||||||
var linePos = new LinePosition(caretLine, caretColumn);
|
|
||||||
|
|
||||||
var completionsResult = await _roslynAnalysis.GetCodeCompletionsForDocumentAtPosition(_currentFile, linePos, completionTrigger);
|
var completionsResult = await _roslynAnalysis.GetCodeCompletionsForDocumentAtPosition(_currentFile, documentTextAtTimeOfCompletionRequest, linePos, completionTrigger);
|
||||||
|
|
||||||
// We can't draw until we get this position
|
// We can't draw until we get this position
|
||||||
_completionTriggerPosition = await this.InvokeAsync(() => GetPosAtLineColumn(completionsResult.LinePosition.Line, completionsResult.LinePosition.Character));
|
_completionTriggerPosition = await this.InvokeAsync(() => GetPosAtLineColumn(completionsResult.LinePosition.Line, completionsResult.LinePosition.Character));
|
||||||
|
|
||||||
completionList = completionsResult.CompletionList;
|
completionList = completionsResult.CompletionList;
|
||||||
completionResultDocument = completionsResult.Document;
|
completionResultDocument = completionsResult.Document;
|
||||||
var filterReason = completionTrigger.Kind switch
|
var filterReason = completionTrigger.Kind switch
|
||||||
{
|
{
|
||||||
CompletionTriggerKind.Insertion => CompletionFilterReason.Insertion,
|
CompletionTriggerKind.Insertion => CompletionFilterReason.Insertion,
|
||||||
CompletionTriggerKind.Deletion => CompletionFilterReason.Deletion,
|
CompletionTriggerKind.Deletion => CompletionFilterReason.Deletion,
|
||||||
CompletionTriggerKind.InvokeAndCommitIfUnique => CompletionFilterReason.Other,
|
CompletionTriggerKind.InvokeAndCommitIfUnique => CompletionFilterReason.Other,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(completionTrigger.Kind), completionTrigger.Kind, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(completionTrigger.Kind), completionTrigger.Kind, null),
|
||||||
};
|
};
|
||||||
await CustomFilterCodeCompletionCandidates(filterReason);
|
await CustomFilterCodeCompletionCandidates(filterReason);
|
||||||
GD.Print($"Found {completionsResult.CompletionList.ItemsList.Count} completions, displaying menu");
|
GD.Print($"Found {completionsResult.CompletionList.ItemsList.Count} completions, displaying menu");
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplySelectedCodeCompletion()
|
public void ApplySelectedCodeCompletion()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public partial class SharpIdeCodeEdit
|
|||||||
if (@event.IsActionPressed(InputStringNames.CodeEditorRequestCompletions))
|
if (@event.IsActionPressed(InputStringNames.CodeEditorRequestCompletions))
|
||||||
{
|
{
|
||||||
completionTrigger = new CompletionTrigger(CompletionTriggerKind.InvokeAndCommitIfUnique);
|
completionTrigger = new CompletionTrigger(CompletionTriggerKind.InvokeAndCommitIfUnique);
|
||||||
CustomCodeCompletionRequested.InvokeParallelFireAndForget(completionTrigger!.Value);
|
CustomCodeCompletionRequested.InvokeParallelFireAndForget(completionTrigger!.Value, Text, GetCaretPosition());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ public partial class SharpIdeCodeEdit
|
|||||||
|
|
||||||
if (isCodeCompletionPopupOpen is false && _codeCompletionTriggers.Contains(unicodeString, StringComparer.OrdinalIgnoreCase))
|
if (isCodeCompletionPopupOpen is false && _codeCompletionTriggers.Contains(unicodeString, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
pendingCompletionTrigger = CompletionTrigger.CreateInsertionTrigger(unicodeString[0]);
|
_pendingCompletionTrigger = CompletionTrigger.CreateInsertionTrigger(unicodeString[0]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user