Browse Source

completion performance improvements

pull/165/head
Ionite 1 year ago
parent
commit
2a9ddc4a8e
No known key found for this signature in database
  1. 4
      StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionData.cs
  2. 143
      StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionList.cs
  3. 8
      StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionListThemes.axaml

4
StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionData.cs

@ -36,7 +36,7 @@ public class CompletionData : ICompletionData
public InlineCollection TextInlines => _textInlines ??= CreateInlines();
/// <inheritdoc />
public double Priority { get; }
public double Priority { get; init; }
public CompletionData(string text)
{
@ -75,8 +75,6 @@ public class CompletionData : ICompletionData
throw new NullReferenceException("TextContent is null");
}
Debug.WriteLine($"Updating char highlighting for {Text} with search text {searchText}");
var defaultColor = ThemeColors.CompletionForegroundBrush;
var highlightColor = ThemeColors.CompletionSelectionForegroundBrush;

143
StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionList.cs

@ -18,9 +18,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
@ -28,6 +31,8 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml.Templates;
using AvaloniaEdit.Utils;
using StabilityMatrix.Avalonia.Models.TagCompletion;
using StabilityMatrix.Core.Extensions;
namespace StabilityMatrix.Avalonia.Controls.CodeCompletion;
@ -104,7 +109,8 @@ public class CompletionList : TemplatedControl
_listBox = e.NameScope.Find("PART_ListBox") as CompletionListBox;
if (_listBox is not null)
{
_listBox.ItemsSource = _completionData;
// _listBox.ItemsSource = _completionData;
_listBox.ItemsSource = FilteredCompletionData;
}
}
@ -137,6 +143,8 @@ public class CompletionList : TemplatedControl
/// Gets the list to which completion data can be added.
/// </summary>
public IList<ICompletionData> CompletionData => _completionData;
public ObservableCollection<ICompletionData> FilteredCompletionData { get; } = new();
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
@ -272,9 +280,12 @@ public class CompletionList : TemplatedControl
{
ApplyTemplate();
}
var timer = Stopwatch.StartNew();
if (IsFiltering)
{
// SelectItemFilteringLive(text, fullUpdate);
SelectItemFiltering(text, fullUpdate);
}
else
@ -282,14 +293,85 @@ public class CompletionList : TemplatedControl
SelectItemWithStart(text);
}
Debug.WriteLine($"SelectItem for '{text}' took {timer.Elapsed.TotalMilliseconds:F2} ms");
_currentText = text;
}
/// <summary>
/// Filters CompletionList items to show only those matching given query, and selects the best match.
/// </summary>
private void SelectItemFilteringLive(string query, bool fullUpdate = false)
{
if (_listBox is null) throw new NullReferenceException("ListBox not set");
var listToFilter = _completionData;
// if the user just typed one more character, don't filter all data but just filter what we are already displaying
if (!fullUpdate
&& FilteredCompletionData.Count > 0
&& !string.IsNullOrEmpty(_currentText)
&& !string.IsNullOrEmpty(query)
&& query.StartsWith(_currentText, StringComparison.Ordinal))
{
listToFilter = FilteredCompletionData;
}
var matchingItems = listToFilter
.Select(item => new {Item = item, Quality = GetMatchQuality(item.Text, query)})
.Where(x => x.Quality > 0)
.ToImmutableArray();
/*var matchingItems =
from item in listToFilter
let quality = GetMatchQuality(item.Text, query)
where quality > 0
select new { Item = item, Quality = quality };*/
// e.g. "DateTimeKind k = (*cc here suggests DateTimeKind*)"
var suggestedItem =
_listBox.SelectedIndex != -1 ? (ICompletionData)_listBox.SelectedItem! : null;
// Clear current items
FilteredCompletionData.Clear();
var bestIndex = -1;
var bestQuality = -1;
double bestPriority = 0;
var i = 0;
foreach (var matchingItem in matchingItems)
{
var priority =
matchingItem.Item == suggestedItem
? double.PositiveInfinity
: matchingItem.Item.Priority;
var quality = matchingItem.Quality;
if (quality > bestQuality || quality == bestQuality && priority > bestPriority)
{
bestIndex = i;
bestPriority = priority;
bestQuality = quality;
}
// Add to filtered list
FilteredCompletionData.Add(matchingItem.Item);
// Update the character highlighting
matchingItem.Item.UpdateCharHighlighting(query);
i++;
}
SelectIndex(bestIndex);
}
/// <summary>
/// Filters CompletionList items to show only those matching given query, and selects the best match.
/// </summary>
private void SelectItemFiltering(string query, bool fullUpdate = false)
{
if (_listBox is null) throw new NullReferenceException("ListBox not set");
var listToFilter = _completionData;
// if the user just typed one more character, don't filter all data but just filter what we are already displaying
@ -302,16 +384,24 @@ public class CompletionList : TemplatedControl
listToFilter = _currentList;
}
// Order first by quality, then by
var matchingItems = listToFilter
.Select(item => new { Item = item, Quality = GetMatchQuality(item.Text, query) })
.Where(x => x.Quality > 0)
.OrderByDescending(x => x.Quality)
.ThenByDescending(x => x.Item.Priority)
.ToImmutableArray();
var matchingItems =
/*var matchingItems =
from item in listToFilter
let quality = GetMatchQuality(item.Text, query)
where quality > 0
select new { Item = item, Quality = quality };
orderby quality
select new { Item = item, Quality = quality };*/
// e.g. "DateTimeKind k = (*cc here suggests DateTimeKind*)"
var suggestedItem =
_listBox.SelectedIndex != -1 ? (ICompletionData)_listBox.SelectedItem : null;
_listBox.SelectedIndex != -1 ? (ICompletionData)_listBox.SelectedItem! : null;
var listBoxItems = new ObservableCollection<ICompletionData>();
var bestIndex = -1;
@ -331,17 +421,20 @@ public class CompletionList : TemplatedControl
bestPriority = priority;
bestQuality = quality;
}
// Add to listbox
listBoxItems.Add(matchingItem.Item);
// Update the character highlighting
matchingItem.Item.UpdateCharHighlighting(query);
i++;
}
_currentList = listBoxItems;
//_listBox.Items = null; Makes no sense? Tooltip disappeared because of this
_listBox.ItemsSource = listBoxItems;
SelectIndexCentered(bestIndex);
SelectIndex(bestIndex);
}
/// <summary>
@ -395,28 +488,50 @@ public class CompletionList : TemplatedControl
SelectIndexCentered(bestIndex);
}
private void SelectIndexCentered(int bestIndex)
private void SelectIndexCentered(int index)
{
if (bestIndex < 0)
if (_listBox is null)
{
throw new NullReferenceException("ListBox not set");
}
if (index < 0)
{
_listBox.ClearSelection();
}
else
{
var firstItem = _listBox.FirstVisibleItem;
if (bestIndex < firstItem || firstItem + _listBox.VisibleItemCount <= bestIndex)
if (index < firstItem || firstItem + _listBox.VisibleItemCount <= index)
{
// CenterViewOn does nothing as CompletionListBox.ScrollViewer is null
_listBox.CenterViewOn(bestIndex);
_listBox.SelectIndex(bestIndex);
_listBox.CenterViewOn(index);
_listBox.SelectIndex(index);
}
else
{
_listBox.SelectIndex(bestIndex);
_listBox.SelectIndex(index);
}
}
}
private void SelectIndex(int index)
{
if (_listBox is null)
{
throw new NullReferenceException("ListBox not set");
}
if (index < 0)
{
_listBox.ClearSelection();
}
else
{
_listBox.SelectedIndex = index;
}
}
private int GetMatchQuality(string itemText, string query)
{
if (itemText == null)
@ -466,7 +581,7 @@ public class CompletionList : TemplatedControl
return -1;
}
private static bool CamelCaseMatch(string text, string query)
{
// We take the first letter of the text regardless of whether or not it's upper case so we match

8
StabilityMatrix.Avalonia/Controls/CodeCompletion/CompletionListThemes.axaml

@ -162,12 +162,12 @@
<cc:CompletionListBox.ItemTemplate>
<DataTemplate x:DataType="cc:ICompletionData">
<Grid ColumnDefinitions="20,Auto,*">
<Image
<!--<Image
Width="16"
Height="16"
Margin="0,0,2,0"
IsVisible="{Binding Image, Converter={x:Static ObjectConverters.IsNotNull}}"
Source="{Binding Image}" />
Source="{Binding Image}" />-->
<!-- Icon -->
<icons:Icon
@ -184,8 +184,8 @@
Grid.Column="1"
VerticalAlignment="Center"
FontFamily="Cascadia Code,Consolas,Menlo,Monospace"
FontSize="13"
Inlines="{Binding TextInlines}" />
Inlines="{Binding TextInlines}"
FontSize="13"/>
<!-- Description Text -->
<TextBlock

Loading…
Cancel
Save