Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/OneWare.ErrorList/BatchObservableCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;

namespace OneWare.ErrorList;

/// <summary>
/// ObservableCollection that can suppress change notifications while a batch of mutations is applied and
/// raises a single Reset afterwards.
/// This is required for <see cref="Avalonia.Collections.DataGridCollectionView" />, which crashes
/// (ArgumentOutOfRangeException in AdjustCurrencyForRemove) when an item that is filtered out of the view is
/// removed while the current position is 0. Handling a Reset makes the view rebuild itself safely.
/// </summary>
internal class BatchObservableCollection<T> : ObservableCollection<T>
{
private bool _isDirty;
private int _suspendLevel;

public IDisposable BeginBatch()
{
_suspendLevel++;
return new BatchScope(this);
}

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (_suspendLevel > 0)
{
_isDirty = true;
return;
}

base.OnCollectionChanged(e);
}

protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (_suspendLevel > 0) return;

base.OnPropertyChanged(e);
}

private void EndBatch()
{
if (_suspendLevel == 0) return;

_suspendLevel--;
if (_suspendLevel > 0 || !_isDirty) return;

_isDirty = false;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

private sealed class BatchScope(BatchObservableCollection<T> owner) : IDisposable
{
private bool _disposed;

public void Dispose()
{
if (_disposed) return;
_disposed = true;
owner.EndBatch();
}
}
}
36 changes: 27 additions & 9 deletions src/OneWare.ErrorList/ViewModels/ErrorListViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class ErrorListViewModel : ExtendedTool, IErrorService
{
public const string IconKey = "MaterialDesign.ErrorOutline";

private readonly ObservableCollection<ErrorListItem> _items = new();
private readonly BatchObservableCollection<ErrorListItem> _items = new();

private readonly IMainDockService _mainDockService;
private readonly IProjectExplorerService _projectExplorerExplorerViewModel;
Expand Down Expand Up @@ -194,7 +194,11 @@ public void RegisterErrorSource(string source)

public void ClearFile(string filePath)
{
ListEx.RemoveMany(_items, _items.Where(x => x.FilePath.EqualPaths(filePath)));
using (_items.BeginBatch())
{
ListEx.RemoveMany(_items, _items.Where(x => x.FilePath.EqualPaths(filePath)));
}

ErrorRefresh?.Invoke(this, filePath);
RefreshCountToggle();
}
Expand All @@ -204,7 +208,10 @@ public void Clear(string source)
var errors = _items.Where(x => x.Source == source).ToList();
var files = errors.Select(x => x.FilePath).Distinct();

ListEx.RemoveMany(_items, errors);
using (_items.BeginBatch())
{
ListEx.RemoveMany(_items, errors);
}

foreach (var file in files) ErrorRefresh?.Invoke(this, file);

Expand All @@ -231,10 +238,13 @@ public IEnumerable<ErrorListItem> GetErrorsForFile(string filePath)
/// </summary>
public void RefreshErrors(IList<ErrorListItem> errors, string source, string filePath)
{
ListEx.RemoveMany(_items,
_items.Where(x => x.FilePath.EqualPaths(filePath) && x.Source == source && !errors.Contains(x)));
using (_items.BeginBatch())
{
ListEx.RemoveMany(_items,
_items.Where(x => x.FilePath.EqualPaths(filePath) && x.Source == source && !errors.Contains(x)));

foreach (var e in errors) Add(e);
foreach (var e in errors) Add(e);
}

ErrorRefresh?.Invoke(this, filePath);
RefreshCountToggle();
Expand Down Expand Up @@ -328,15 +338,23 @@ private void RefreshCountToggle()

public void Clear(IProjectRoot project)
{
ListEx.RemoveMany(_items, _items.Where(x => x.Root == project));
using (_items.BeginBatch())
{
ListEx.RemoveMany(_items, _items.Where(x => x.Root == project));
}

ErrorRefresh?.Invoke(this, project);
RefreshCountToggle();
}

public void Clear(IProjectRoot project, string source)
{
ListEx.RemoveMany(_items,
_items.Where(x => x.Root == project && x.Source == source));
using (_items.BeginBatch())
{
ListEx.RemoveMany(_items,
_items.Where(x => x.Root == project && x.Source == source));
}

ErrorRefresh?.Invoke(this, project);
RefreshCountToggle();
}
Expand Down
Loading