Browse Source

Completion performance improvements

pull/165/head
Ionite 1 year ago
parent
commit
51bc46375b
No known key found for this signature in database
  1. 89
      StabilityMatrix.Avalonia/Behaviors/TextEditorCompletionBehavior.cs
  2. 34
      StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionWindow.axaml.cs

89
StabilityMatrix.Avalonia/Behaviors/TextEditorCompletionBehavior.cs

@ -1,9 +1,11 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Threading;
using Avalonia.Xaml.Interactivity; using Avalonia.Xaml.Interactivity;
using AvaloniaEdit; using AvaloniaEdit;
using AvaloniaEdit.Document; using AvaloniaEdit.Document;
@ -74,6 +76,7 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
textEditor = editor; textEditor = editor;
textEditor.TextArea.TextEntered += TextArea_TextEntered; textEditor.TextArea.TextEntered += TextArea_TextEntered;
textEditor.TextArea.KeyDown += TextArea_KeyDown;
} }
protected override void OnDetaching() protected override void OnDetaching()
@ -81,6 +84,7 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
base.OnDetaching(); base.OnDetaching();
textEditor.TextArea.TextEntered -= TextArea_TextEntered; textEditor.TextArea.TextEntered -= TextArea_TextEntered;
textEditor.TextArea.KeyDown -= TextArea_KeyDown;
} }
private CompletionWindow CreateCompletionWindow(TextArea textArea) private CompletionWindow CreateCompletionWindow(TextArea textArea)
@ -96,43 +100,16 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
return window; return window;
} }
private void TextArea_TextEntered(object? sender, TextInputEventArgs e) public void InvokeManualCompletion()
{ {
Logger.ConditionalTrace($"Text entered: {e.Text.ToRepr()}"); if (CompletionProvider is null)
if (!IsEnabled || CompletionProvider is null)
{
Logger.ConditionalTrace("Skipping, not enabled");
return;
}
if (e.Text is not { } triggerText)
{ {
Logger.ConditionalTrace("Skipping, null trigger text"); throw new NullReferenceException("CompletionProvider is null");
return;
}
if (!triggerText.All(IsCompletionChar))
{
if (completionWindow is { } window)
{
// Disallowed chars, close completion window if its open
Logger.ConditionalTrace(
$"Closing completion window: '{triggerText}' not a valid completion char"
);
window.Close();
completionWindow = null;
}
else
{
Logger.ConditionalTrace($"Skipping, invalid trigger text: {triggerText.ToRepr()}");
}
return;
} }
// If window already open, skip since handled by completion window // If window already open, skip since handled by completion window
// Unless this is an end char, where we'll open a new window // Unless this is an end char, where we'll open a new window
if (completionWindow != null && !triggerText.All(IsCompletionEndChar)) if (completionWindow is { IsOpen: true })
{ {
Logger.ConditionalTrace("Skipping, completion window already open"); Logger.ConditionalTrace("Skipping, completion window already open");
return; return;
@ -159,7 +136,7 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
var tokenSegment = completionRequest.Segment; var tokenSegment = completionRequest.Segment;
var token = textEditor.Document.GetText(tokenSegment); var token = textEditor.Document.GetText(tokenSegment);
Logger.Trace("Using token {Token} ({@Segment})", token, tokenSegment); Logger.ConditionalTrace("Using token {Token} ({@Segment})", token, tokenSegment);
completionWindow = CreateCompletionWindow(textEditor.TextArea); completionWindow = CreateCompletionWindow(textEditor.TextArea);
completionWindow.StartOffset = tokenSegment.Offset; completionWindow.StartOffset = tokenSegment.Offset;
@ -167,7 +144,7 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
completionWindow.UpdateQuery(completionRequest); completionWindow.UpdateQuery(completionRequest);
completionWindow.Closed += delegate completionWindow.Closed += (_, _) =>
{ {
completionWindow = null; completionWindow = null;
}; };
@ -175,6 +152,52 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
completionWindow.Show(); completionWindow.Show();
} }
private void TextArea_TextEntered(object? sender, TextInputEventArgs e)
{
Logger.ConditionalTrace($"Text entered: {e.Text.ToRepr()}");
if (!IsEnabled || CompletionProvider is null)
{
Logger.ConditionalTrace("Skipping, not enabled");
return;
}
if (e.Text is not { } triggerText)
{
Logger.ConditionalTrace("Skipping, null trigger text");
return;
}
if (!triggerText.All(IsCompletionChar))
{
if (completionWindow is { } window)
{
// Disallowed chars, close completion window if its open
Logger.ConditionalTrace(
$"Closing completion window: '{triggerText}' not a valid completion char"
);
window.Close();
completionWindow = null;
}
else
{
Logger.ConditionalTrace($"Skipping, invalid trigger text: {triggerText.ToRepr()}");
}
return;
}
Dispatcher.UIThread.Post(InvokeManualCompletion, DispatcherPriority.Background);
}
private void TextArea_KeyDown(object? sender, KeyEventArgs e)
{
if (e is { Key: Key.Space, KeyModifiers: KeyModifiers.Control })
{
Dispatcher.UIThread.Post(InvokeManualCompletion, DispatcherPriority.Background);
e.Handled = true;
}
}
/// <summary> /// <summary>
/// Highlights the text segment in the text editor /// Highlights the text segment in the text editor
/// </summary> /// </summary>

34
StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionWindow.axaml.cs

@ -65,7 +65,9 @@ public class CompletionWindow : CompletionWindowBase
public CompletionWindow( public CompletionWindow(
TextArea textArea, TextArea textArea,
ICompletionProvider completionProvider, ICompletionProvider completionProvider,
ITokenizerProvider tokenizerProvider) : base(textArea) ITokenizerProvider tokenizerProvider
)
: base(textArea)
{ {
this.completionProvider = completionProvider; this.completionProvider = completionProvider;
this.tokenizerProvider = tokenizerProvider; this.tokenizerProvider = tokenizerProvider;
@ -113,9 +115,11 @@ public class CompletionWindow : CompletionWindowBase
private void CompletionList_SelectionChanged(object? sender, SelectionChangedEventArgs e) private void CompletionList_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{ {
// Skip if tooltip not enabled // Skip if tooltip not enabled
if (!IsSelectionTooltipEnabled) return; if (!IsSelectionTooltipEnabled)
return;
if (_toolTipContent == null || _toolTip == null) return; if (_toolTipContent == null || _toolTip == null)
return;
var item = CompletionList.SelectedItem; var item = CompletionList.SelectedItem;
if (item?.Description is { } descriptionText) if (item?.Description is { } descriptionText)
@ -157,7 +161,7 @@ public class CompletionWindow : CompletionWindowBase
private void CompletionList_InsertionRequested(object? sender, InsertionRequestEventArgs e) private void CompletionList_InsertionRequested(object? sender, InsertionRequestEventArgs e)
{ {
Hide(); Close();
// The window must close before Complete() is called. // The window must close before Complete() is called.
// If the Complete callback pushes stacked input handlers, we don't want to pop those when the CC window closes. // If the Complete callback pushes stacked input handlers, we don't want to pop those when the CC window closes.
@ -166,12 +170,13 @@ public class CompletionWindow : CompletionWindowBase
TextArea, TextArea,
new AnchorSegment(TextArea.Document, StartOffset, length), new AnchorSegment(TextArea.Document, StartOffset, length),
e, e,
completionProvider.PrepareInsertionText); completionProvider.PrepareInsertionText
);
} }
private void CompletionList_CloseRequested(object? sender, EventArgs e) private void CompletionList_CloseRequested(object? sender, EventArgs e)
{ {
Hide(); Close();
} }
private void AttachEvents() private void AttachEvents()
@ -208,14 +213,17 @@ public class CompletionWindow : CompletionWindowBase
private void TextArea_PreviewTextInput(object? sender, TextInputEventArgs e) private void TextArea_PreviewTextInput(object? sender, TextInputEventArgs e)
{ {
e.Handled = RaiseEventPair(this, null, TextInputEvent, e.Handled = RaiseEventPair(
new TextInputEventArgs { Text = e.Text }); this,
null,
TextInputEvent,
new TextInputEventArgs { Text = e.Text }
);
} }
private void TextArea_MouseWheel(object? sender, PointerWheelEventArgs e) private void TextArea_MouseWheel(object? sender, PointerWheelEventArgs e)
{ {
e.Handled = RaiseEventPair(GetScrollEventTarget(), e.Handled = RaiseEventPair(GetScrollEventTarget(), null, PointerWheelChangedEvent, e);
null, PointerWheelChangedEvent, e);
} }
private Control GetScrollEventTarget() private Control GetScrollEventTarget()
@ -302,10 +310,12 @@ public class CompletionWindow : CompletionWindowBase
// Fast path if the search term starts with the last search term // Fast path if the search term starts with the last search term
// and the last completion count was less than the max list length // and the last completion count was less than the max list length
// (such we won't get new results by searching again) // (such we won't get new results by searching again)
if (lastSearchRequest is not null if (
lastSearchRequest is not null
&& completionRequest.Type == lastSearchRequest.Type && completionRequest.Type == lastSearchRequest.Type
&& searchTerm.StartsWith(lastSearchRequest.Text) && searchTerm.StartsWith(lastSearchRequest.Text)
&& lastCompletionLength < MaxListLength) && lastCompletionLength < MaxListLength
)
{ {
CompletionList.SelectItem(searchTerm); CompletionList.SelectItem(searchTerm);
lastSearchRequest = completionRequest; lastSearchRequest = completionRequest;

Loading…
Cancel
Save