Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code Explorer Extreme Makeover (for some values of extreme) #4661

Merged
merged 23 commits into from
Jan 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bae177d
Match target project by ID, not name.
comintern Dec 19, 2018
5b624a4
Overhaul the code explorer.
comintern Dec 19, 2018
e653531
Merge with next
comintern Dec 19, 2018
829944d
Fix test setup.
comintern Dec 20, 2018
1f8f4df
Add support for finding reference references. Closes #4654
comintern Dec 22, 2018
408959e
Simplify binding paths in CE by moving styles closer to scopes, gener…
comintern Dec 22, 2018
93f99f4
Defer reference usage determination until parser is ready.
comintern Dec 22, 2018
39da95b
Address memory leaks.
comintern Dec 22, 2018
7baf5fb
Bind directly to VM, removes last of the leaked bindings.
comintern Dec 22, 2018
98b9f4a
Fix EvaluateCanExecute for find all implementations, closes #3045
comintern Dec 23, 2018
8263002
Add predeclared indicator to name with signature and custom icon. Clo…
comintern Dec 23, 2018
b6aa90c
Fix default and predeclared icons.
comintern Dec 23, 2018
0a22f25
Code Explorer performance rewrite.
comintern Jan 14, 2019
4ad676e
Fix test setup.
comintern Jan 14, 2019
5cf6423
Apply CE refactoring to CodeMetrics window.
comintern Jan 14, 2019
d84f3e7
Use static images and converters.
comintern Jan 15, 2019
d22d20f
Add comment for NOP setter.
retailcoder Jan 15, 2019
05a8bad
Address review comments.
comintern Jan 16, 2019
90f6116
Use "null" icon instead of exception icon for null declarations.
comintern Jan 16, 2019
ca8f38c
Sort out initial expanded states.
comintern Jan 16, 2019
58a1d5b
Merge branch 'refs' of https://github.com/comintern/Rubberduck into refs
comintern Jan 16, 2019
92534c9
Fix ordering of sync to avoid name collisions on default project names.
comintern Jan 16, 2019
6df5bde
Replace tabs with spaces.
comintern Jan 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
173 changes: 61 additions & 112 deletions Rubberduck.Core/CodeAnalysis/CodeMetrics/CodeMetricsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,136 +6,93 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Rubberduck.Navigation.CodeExplorer;
using System.Windows;
using Rubberduck.Navigation.Folders;
using Rubberduck.Parsing.UIContext;
using Rubberduck.VBEditor.SafeComWrappers.Abstract;

namespace Rubberduck.CodeAnalysis.CodeMetrics
{
public class CodeMetricsViewModel : ViewModelBase, IDisposable
public sealed class CodeMetricsViewModel : ViewModelBase, IDisposable
{
private readonly RubberduckParserState _state;
private readonly ICodeMetricsAnalyst _analyst;
private readonly FolderHelper _folderHelper;
private readonly IVBE _vbe;
private readonly IUiDispatcher _uiDispatcher;

public CodeMetricsViewModel(RubberduckParserState state, ICodeMetricsAnalyst analyst, FolderHelper folderHelper, IVBE vbe)
public CodeMetricsViewModel(
RubberduckParserState state,
ICodeMetricsAnalyst analyst,
IVBE vbe,
IUiDispatcher uiDispatcher)
{
_state = state;
_analyst = analyst;
_folderHelper = folderHelper;
_state.StateChanged += OnStateChanged;

_analyst = analyst;
_vbe = vbe;
_uiDispatcher = uiDispatcher;

OnPropertyChanged(nameof(Projects));
}

private void OnStateChanged(object sender, ParserStateEventArgs e)
private bool _unparsed = true;
public bool Unparsed
{
if (e.State != ParserState.Ready && e.State != ParserState.Error && e.State != ParserState.ResolverError && e.State != ParserState.UnexpectedError)
get => _unparsed;
set
{
IsBusy = true;
}

if (e.State == ParserState.Ready)
if (_unparsed == value)
{
UpdateData();
IsBusy = false;
return;
}

if (e.State == ParserState.Error || e.State == ParserState.ResolverError || e.State == ParserState.UnexpectedError)
{
IsBusy = false;
_unparsed = value;
OnPropertyChanged();
}
}

private void UpdateData()
private void OnStateChanged(object sender, ParserStateEventArgs e)
{
IsBusy = true;

var metricResults = _analyst.GetMetrics(_state);
resultsByDeclaration = metricResults.GroupBy(r => r.Declaration).ToDictionary(g => g.Key, g => g.ToList());
Unparsed = false;
IsBusy = _state.Status != ParserState.Pending && _state.Status <= ParserState.ResolvedDeclarations;

if (Projects == null)
if (e.State != ParserState.ResolvedDeclarations)
{
Projects = new ObservableCollection<CodeExplorerItemViewModel>();
return;
}

IsBusy = _state.Status != ParserState.Pending && _state.Status <= ParserState.ResolvedDeclarations;

var userDeclarations = _state.DeclarationFinder.AllUserDeclarations
.GroupBy(declaration => declaration.ProjectId)
.ToList();

var newProjects = userDeclarations
.Where(grouping => grouping.Any(declaration => declaration.DeclarationType == DeclarationType.Project))
.Select(grouping =>
new CodeExplorerProjectViewModel(_folderHelper,
grouping.SingleOrDefault(declaration => declaration.DeclarationType == DeclarationType.Project),
grouping,
_vbe)).ToList();

UpdateNodes(Projects, newProjects);

Projects = new ObservableCollection<CodeExplorerItemViewModel>(newProjects);

IsBusy = false;
Synchronize(_state.DeclarationFinder.AllUserDeclarations.ToList());
}

private void UpdateNodes(IEnumerable<CodeExplorerItemViewModel> oldList, IEnumerable<CodeExplorerItemViewModel> newList)
{
foreach (var item in newList)
private void Synchronize(List<Declaration> declarations)
{
CodeExplorerItemViewModel oldItem;
var metricResults = _analyst.GetMetrics(_state);
_resultsByDeclaration = metricResults.GroupBy(r => r.Declaration).ToDictionary(g => g.Key, g => g.ToList());

if (item is CodeExplorerCustomFolderViewModel)
_uiDispatcher.Invoke(() =>
{
oldItem = oldList.FirstOrDefault(i => i.Name == item.Name);
}
else
{
oldItem = oldList.FirstOrDefault(i =>
item.QualifiedSelection != null && i.QualifiedSelection != null &&
i.QualifiedSelection.Value.QualifiedName.ProjectId ==
item.QualifiedSelection.Value.QualifiedName.ProjectId &&
i.QualifiedSelection.Value.QualifiedName.ComponentName ==
item.QualifiedSelection.Value.QualifiedName.ComponentName &&
i.QualifiedSelection.Value.Selection == item.QualifiedSelection.Value.Selection);
}
var existing = Projects.OfType<CodeExplorerProjectViewModel>().ToList();

if (oldItem != null)
foreach (var project in existing)
{
item.IsExpanded = oldItem.IsExpanded;
item.IsSelected = oldItem.IsSelected;

if (oldItem.Items.Any() && item.Items.Any())
project.Synchronize(declarations);
if (project.Declaration is null)
retailcoder marked this conversation as resolved.
Show resolved Hide resolved
{
UpdateNodes(oldItem.Items, item.Items);
}
}
Projects.Remove(project);
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
var adding = declarations.OfType<ProjectDeclaration>().ToList();

private bool _isDisposed;
protected virtual void Dispose(bool disposing)
{
if (_isDisposed || !disposing)
foreach (var project in adding)
{
return;
var model = new CodeExplorerProjectViewModel(project, declarations, _state, _vbe, false);
Projects.Add(model);
model.IsExpanded = true;
}
_isDisposed = true;

_state.StateChanged -= OnStateChanged;
});
}

private Dictionary<Declaration, List<ICodeMetricResult>> resultsByDeclaration;

private CodeExplorerItemViewModel _selectedItem;
public CodeExplorerItemViewModel SelectedItem
private ICodeExplorerNode _selectedItem;
public ICodeExplorerNode SelectedItem
{
get => _selectedItem;
set
Expand All @@ -150,27 +107,15 @@ public CodeExplorerItemViewModel SelectedItem
}
}

private ObservableCollection<CodeExplorerItemViewModel> _projects;
public ObservableCollection<CodeExplorerItemViewModel> Projects
{
get => _projects;
set
{
_projects = new ObservableCollection<CodeExplorerItemViewModel>(value.OrderBy(o => o.NameWithSignature));

OnPropertyChanged();
OnPropertyChanged(nameof(TreeViewVisibility));
}
}

public Visibility TreeViewVisibility => Projects == null || Projects.Count == 0 ? Visibility.Collapsed : Visibility.Visible;
public ObservableCollection<ICodeExplorerNode> Projects { get; } = new ObservableCollection<ICodeExplorerNode>();

private Dictionary<Declaration, List<ICodeMetricResult>> _resultsByDeclaration;
public ObservableCollection<ICodeMetricResult> Metrics
{
get
{
var results = resultsByDeclaration?.FirstOrDefault(f => f.Key == SelectedItem.GetSelectedDeclaration());
return !results.HasValue || results.Value.Value == null ? new ObservableCollection<ICodeMetricResult>() : new ObservableCollection<ICodeMetricResult>(results.Value.Value);
var results = _resultsByDeclaration?.FirstOrDefault(f => ReferenceEquals(f.Key, SelectedItem.Declaration));
return results?.Value == null ? new ObservableCollection<ICodeMetricResult>() : new ObservableCollection<ICodeMetricResult>(results.Value.Value);
}
}

Expand All @@ -181,23 +126,27 @@ public bool IsBusy
set
{
_isBusy = value;
EmptyUIRefreshMessageVisibility = false;
OnPropertyChanged();
}
}

private bool _emptyUIRefreshMessageVisibility = true;
public bool EmptyUIRefreshMessageVisibility
public void Dispose()
{
get => _emptyUIRefreshMessageVisibility;
set
Dispose(true);
GC.SuppressFinalize(this);
}

private bool _isDisposed;

private void Dispose(bool disposing)
{
if (_emptyUIRefreshMessageVisibility != value)
if (_isDisposed || !disposing)
{
_emptyUIRefreshMessageVisibility = value;
OnPropertyChanged();
}
return;
}
_isDisposed = true;

_state.StateChanged -= OnStateChanged;
}
}
}