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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Threading;
using Avalonia.Xaml.Interactivity;
using AvaloniaEdit;
using AvaloniaEdit.Document;
@ -74,6 +76,7 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
textEditor = editor;
textEditor.TextArea.TextEntered += TextArea_TextEntered;
textEditor.TextArea.KeyDown += TextArea_KeyDown;
}
protected override void OnDetaching()
@ -81,6 +84,7 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
base.OnDetaching();
textEditor.TextArea.TextEntered -= TextArea_TextEntered;
textEditor.TextArea.KeyDown -= TextArea_KeyDown;
}
private CompletionWindow CreateCompletionWindow(TextArea textArea)
@ -96,43 +100,16 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
return window;
}
private void TextArea_TextEntered(object? sender, TextInputEventArgs e)
public void InvokeManualCompletion()
{
Logger.ConditionalTrace($"Text entered: {e.Text.ToRepr()}");
if (!IsEnabled || CompletionProvider is null)
{
Logger.ConditionalTrace("Skipping, not enabled");
return;
}
if (e.Text is not { } triggerText)
if (CompletionProvider is null)
{
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;
throw new NullReferenceException("CompletionProvider is null");
}
// If window already open, skip since handled by completion 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");
return;
@ -159,7 +136,7 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
var tokenSegment = completionRequest.Segment;
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.StartOffset = tokenSegment.Offset;
@ -167,7 +144,7 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
completionWindow.UpdateQuery(completionRequest);
completionWindow.Closed += delegate
completionWindow.Closed += (_, _) =>
{
completionWindow = null;
};
@ -175,6 +152,52 @@ public class TextEditorCompletionBehavior : Behavior<TextEditor>
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>
/// Highlights the text segment in the text editor
/// </summary>

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

@ -65,7 +65,9 @@ public class CompletionWindow : CompletionWindowBase
public CompletionWindow(
TextArea textArea,
ICompletionProvider completionProvider,
ITokenizerProvider tokenizerProvider) : base(textArea)
ITokenizerProvider tokenizerProvider
)
: base(textArea)
{
this.completionProvider = completionProvider;
this.tokenizerProvider = tokenizerProvider;
@ -113,9 +115,11 @@ public class CompletionWindow : CompletionWindowBase
private void CompletionList_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
// 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;
if (item?.Description is { } descriptionText)
@ -157,7 +161,7 @@ public class CompletionWindow : CompletionWindowBase
private void CompletionList_InsertionRequested(object? sender, InsertionRequestEventArgs e)
{
Hide();
Close();
// 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.
@ -166,12 +170,13 @@ public class CompletionWindow : CompletionWindowBase
TextArea,
new AnchorSegment(TextArea.Document, StartOffset, length),
e,
completionProvider.PrepareInsertionText);
completionProvider.PrepareInsertionText
);
}
private void CompletionList_CloseRequested(object? sender, EventArgs e)
{
Hide();
Close();
}
private void AttachEvents()
@ -208,14 +213,17 @@ public class CompletionWindow : CompletionWindowBase
private void TextArea_PreviewTextInput(object? sender, TextInputEventArgs e)
{
e.Handled = RaiseEventPair(this, null, TextInputEvent,
new TextInputEventArgs { Text = e.Text });
e.Handled = RaiseEventPair(
this,
null,
TextInputEvent,
new TextInputEventArgs { Text = e.Text }
);
}
private void TextArea_MouseWheel(object? sender, PointerWheelEventArgs e)
{
e.Handled = RaiseEventPair(GetScrollEventTarget(),
null, PointerWheelChangedEvent, e);
e.Handled = RaiseEventPair(GetScrollEventTarget(), null, PointerWheelChangedEvent, e);
}
private Control GetScrollEventTarget()
@ -302,10 +310,12 @@ public class CompletionWindow : CompletionWindowBase
// Fast path if the search term starts with the last search term
// and the last completion count was less than the max list length
// (such we won't get new results by searching again)
if (lastSearchRequest is not null
if (
lastSearchRequest is not null
&& completionRequest.Type == lastSearchRequest.Type
&& searchTerm.StartsWith(lastSearchRequest.Text)
&& lastCompletionLength < MaxListLength)
&& lastCompletionLength < MaxListLength
)
{
CompletionList.SelectItem(searchTerm);
lastSearchRequest = completionRequest;

Loading…
Cancel
Save