replace ObservableHashSet with ObservableSortedSet
This commit is contained in:
@@ -11,8 +11,8 @@ public class SharpIdeFolder : ISharpIdeNode, IExpandableSharpIdeNode, IChildShar
|
||||
public required string Path { get; set; }
|
||||
public string ChildNodeBasePath => Path;
|
||||
public required string Name { get; set; }
|
||||
public ObservableHashSet<SharpIdeFile> Files { get; init; }
|
||||
public ObservableHashSet<SharpIdeFolder> Folders { get; init; }
|
||||
public ObservableSortedSet<SharpIdeFile> Files { get; init; }
|
||||
public ObservableSortedSet<SharpIdeFolder> Folders { get; init; }
|
||||
public bool Expanded { get; set; }
|
||||
|
||||
[SetsRequiredMembers]
|
||||
@@ -21,8 +21,8 @@ public class SharpIdeFolder : ISharpIdeNode, IExpandableSharpIdeNode, IChildShar
|
||||
Parent = parent;
|
||||
Path = folderInfo.FullName;
|
||||
Name = folderInfo.Name;
|
||||
Files = new ObservableHashSet<SharpIdeFile>(folderInfo.GetFiles(this, allFiles));
|
||||
Folders = new ObservableHashSet<SharpIdeFolder>(this.GetSubFolders(this, allFiles, allFolders));
|
||||
Files = new ObservableSortedSet<SharpIdeFile>(folderInfo.GetFiles(this, allFiles), SharpIdeFileComparer.Instance);
|
||||
Folders = new ObservableSortedSet<SharpIdeFolder>(this.GetSubFolders(this, allFiles, allFolders), SharpIdeFolderComparer.Instance);
|
||||
}
|
||||
|
||||
public SharpIdeFolder()
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace SharpIDE.Application.Features.SolutionDiscovery;
|
||||
|
||||
public class SharpIdeFileComparer : IComparer<SharpIdeFile>
|
||||
{
|
||||
public static readonly SharpIdeFileComparer Instance = new SharpIdeFileComparer();
|
||||
public int Compare(SharpIdeFile? x, SharpIdeFile? y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return 0;
|
||||
if (x is null) return -1;
|
||||
if (y is null) return 1;
|
||||
|
||||
int result = string.Compare(x.Path, y.Path, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class SharpIdeFolderComparer : IComparer<SharpIdeFolder>
|
||||
{
|
||||
public static readonly SharpIdeFolderComparer Instance = new SharpIdeFolderComparer();
|
||||
public int Compare(SharpIdeFolder? x, SharpIdeFolder? y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return 0;
|
||||
if (x is null) return -1;
|
||||
if (y is null) return 1;
|
||||
|
||||
int result = string.Compare(x.Path, y.Path, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ public interface IExpandableSharpIdeNode
|
||||
|
||||
public interface IFolderOrProject : IExpandableSharpIdeNode, IChildSharpIdeNode
|
||||
{
|
||||
public ObservableHashSet<SharpIdeFolder> Folders { get; init; }
|
||||
public ObservableHashSet<SharpIdeFile> Files { get; init; }
|
||||
public ObservableSortedSet<SharpIdeFolder> Folders { get; init; }
|
||||
public ObservableSortedSet<SharpIdeFile> Files { get; init; }
|
||||
public string Name { get; set; }
|
||||
public string ChildNodeBasePath { get; }
|
||||
}
|
||||
@@ -96,8 +96,8 @@ public class SharpIdeProjectModel : ISharpIdeNode, IExpandableSharpIdeNode, IChi
|
||||
public required string Name { get; set; }
|
||||
public required string FilePath { get; set; }
|
||||
public string ChildNodeBasePath => Path.GetDirectoryName(FilePath)!;
|
||||
public required ObservableHashSet<SharpIdeFolder> Folders { get; init; }
|
||||
public required ObservableHashSet<SharpIdeFile> Files { get; init; }
|
||||
public required ObservableSortedSet<SharpIdeFolder> Folders { get; init; }
|
||||
public required ObservableSortedSet<SharpIdeFile> Files { get; init; }
|
||||
public bool Expanded { get; set; }
|
||||
public required IExpandableSharpIdeNode Parent { get; set; }
|
||||
public bool Running { get; set; }
|
||||
@@ -110,8 +110,8 @@ public class SharpIdeProjectModel : ISharpIdeNode, IExpandableSharpIdeNode, IChi
|
||||
Parent = parent;
|
||||
Name = projectModel.Model.ActualDisplayName;
|
||||
FilePath = projectModel.FullFilePath;
|
||||
Files = new ObservableHashSet<SharpIdeFile>(TreeMapperV2.GetFiles(projectModel.FullFilePath, this, allFiles));
|
||||
Folders = new ObservableHashSet<SharpIdeFolder>(TreeMapperV2.GetSubFolders(projectModel.FullFilePath, this, allFiles, allFolders));
|
||||
Files = new ObservableSortedSet<SharpIdeFile>(TreeMapperV2.GetFiles(projectModel.FullFilePath, this, allFiles), SharpIdeFileComparer.Instance);
|
||||
Folders = new ObservableSortedSet<SharpIdeFolder>(TreeMapperV2.GetSubFolders(projectModel.FullFilePath, this, allFiles, allFolders), SharpIdeFolderComparer.Instance);
|
||||
MsBuildEvaluationProjectTask = ProjectEvaluation.GetProject(projectModel.FullFilePath);
|
||||
allProjects.Add(this);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public partial class ObservableSortedSet<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
|
||||
{
|
||||
return new View<TView>(this, transform);
|
||||
}
|
||||
|
||||
sealed class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
public ISynchronizedViewFilter<T, TView> Filter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
|
||||
readonly ObservableSortedSet<T> source;
|
||||
readonly Func<T, TView> selector;
|
||||
readonly Dictionary<T, (T, TView)> dict;
|
||||
int filteredCount;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
|
||||
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public View(ObservableSortedSet<T> source, Func<T, TView> selector)
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
this.dict = source._set.ToDictionary(x => x, x => (x, selector(x)));
|
||||
this.filteredCount = dict.Count;
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return filteredCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int UnfilteredCount
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dict.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
if (filter.IsNullFilter())
|
||||
{
|
||||
ResetFilter();
|
||||
return;
|
||||
}
|
||||
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
this.filteredCount = 0;
|
||||
foreach (var (_, (value, view)) in dict)
|
||||
{
|
||||
if (filter.IsMatch(value, view))
|
||||
{
|
||||
filteredCount++;
|
||||
}
|
||||
}
|
||||
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.filteredCount = dict.Count;
|
||||
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
|
||||
}
|
||||
}
|
||||
|
||||
public ISynchronizedViewList<TView> ToViewList()
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true);
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false);
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher);
|
||||
}
|
||||
|
||||
public IEnumerator<TView> GetEnumerator()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
if (filter.IsMatch(item.Value))
|
||||
{
|
||||
yield return item.Value.Item2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public IEnumerable<(T Value, TView View)> Filtered
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
if (filter.IsMatch(item.Value))
|
||||
{
|
||||
yield return item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(T Value, TView View)> Unfiltered
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
yield return item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
dict.Add(e.NewItem, v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var i = e.NewStartingIndex;
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
dict.Add(item, v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, i++);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
if (dict.Remove(e.OldItem, out var value))
|
||||
{
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, value, -1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
{
|
||||
if (dict.Remove(item, out var value))
|
||||
{
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, value, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
dict.Clear();
|
||||
this.InvokeOnReset(ref filteredCount, ViewChanged);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
// Courtesy of Copilot Agent
|
||||
// Replace with https://github.com/Cysharp/ObservableCollections/pull/111 if it gets merged
|
||||
namespace ObservableCollections
|
||||
{
|
||||
// can not implements ISet<T> because set operation can not get added/removed values.
|
||||
public partial class ObservableSortedSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T> where T : notnull
|
||||
{
|
||||
private readonly SortedSet<T> _set;
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public ObservableSortedSet()
|
||||
{
|
||||
_set = new SortedSet<T>();
|
||||
}
|
||||
|
||||
public ObservableSortedSet(IComparer<T>? comparer)
|
||||
{
|
||||
_set = new SortedSet<T>(comparer: comparer);
|
||||
}
|
||||
|
||||
public ObservableSortedSet(IEnumerable<T> collection)
|
||||
{
|
||||
_set = new SortedSet<T>(collection: collection);
|
||||
}
|
||||
|
||||
public ObservableSortedSet(IEnumerable<T> collection, IComparer<T>? comparer)
|
||||
{
|
||||
_set = new SortedSet<T>(collection: collection, comparer: comparer);
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T>? CollectionChanged;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public bool Add(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (_set.Add(item))
|
||||
{
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, -1));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (!items.TryGetNonEnumeratedCount(out var capacity))
|
||||
{
|
||||
capacity = 4;
|
||||
}
|
||||
|
||||
using (var list = new ResizableArray<T>(capacity))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (_set.Add(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(list.Span, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(T[] items)
|
||||
{
|
||||
AddRange(items.AsSpan());
|
||||
}
|
||||
|
||||
public void AddRange(ReadOnlySpan<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
using (var list = new ResizableArray<T>(items.Length))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (_set.Add(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(list.Span, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (_set.Remove(item))
|
||||
{
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(item, -1));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRange(IEnumerable<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (!items.TryGetNonEnumeratedCount(out var capacity))
|
||||
{
|
||||
capacity = 4;
|
||||
}
|
||||
|
||||
using (var list = new ResizableArray<T>(capacity))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (_set.Remove(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(list.Span, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRange(T[] items)
|
||||
{
|
||||
RemoveRange(items.AsSpan());
|
||||
}
|
||||
|
||||
public void RemoveRange(ReadOnlySpan<T> items)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
using (var list = new ResizableArray<T>(items.Length))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (_set.Remove(item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(list.Span, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
_set.Clear();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reset());
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETSTANDARD2_0 && !NET_STANDARD_2_0 && !NET_4_6
|
||||
|
||||
public bool TryGetValue(T equalValue, [MaybeNullWhen(false)] out T actualValue)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.TryGetValue(equalValue, out actualValue);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.IsProperSubsetOf(other);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.IsProperSupersetOf(other);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.IsSubsetOf(other);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.IsSupersetOf(other);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.Overlaps(other);
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.SetEquals(other);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
foreach (var item in _set)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public IComparer<T> Comparer
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.Comparer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SortedSet-specific properties
|
||||
public T? Min
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.Count > 0 ? _set.Min : default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T? Max
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.Count > 0 ? _set.Max : default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SortedSet-specific methods
|
||||
public IEnumerable<T> Reverse()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.Reverse().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetViewBetween(T lowerValue, T upperValue)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _set.GetViewBetween(lowerValue, upperValue).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Publicize Include="ObservableCollections" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.Remote.Razor" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.Razor.Workspaces" />
|
||||
<Publicize Include="Microsoft.CodeAnalysis.Razor.Compiler" />
|
||||
|
||||
Reference in New Issue
Block a user