HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Rubberduck "GoTo Anything" Navigation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
anythingnavigationgotorubberduck

Problem

The next version of rubberduck is going to include a pretty nifty "goto anything" / find symbol navigation tool that lets the user enter an identifier name in a combobox:

UI

To achieve the templated dropdown I had to introduce some WPF interop. Here's the markup:


    

    
    
        
        
    
    
    
        
        
            
                
            
        

        
            
                
                    
                    
                    
                
            
        
    
    
    
        
    
    


...and the code-behind:

namespace Rubberduck.UI.FindSymbol
{
    /// 
    /// Interaction logic for FindSymbolControl.xaml
    /// 
    public partial class FindSymbolControl : UserControl
    {
        public FindSymbolControl()
        {
            InitializeComponent();
        }

        private FindSymbolViewModel ViewModel { get { return (FindSymbolViewModel)DataContext; } }

        private static readonly ICommand _goCommand = new RoutedCommand();
        public static ICommand GoCommand { get { return _goCommand; } }

        private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            ViewModel.Execute();
        }

        private void CommandBinding_OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if (ViewModel == null)
            {
                return;
            }

            e.CanExecute = ViewModel.CanExecute();
            e.Handled = true;
        }
    }
}


The WPF control is embedded inside a WinForms host, which is basically only responsible for setting the ViewModel:

```
namespace Rubberduck.UI.FindSymbol
{
public partial class FindSymbolDialog : Form
{
public FindSymbolDialog(FindSymbolViewModel viewModel)
: this()
{
findSymbolControl1.DataContext = viewModel;
}

public FindSymbolDialog()
{
InitializeComponent()

Solution

It looks like you can do the filtering and sorting of _declarations just once in the view-model constructor (parse results don't change, and ExcludedTypes doesn't change):

_declarations = declarations
    .Where(declaration => !ExcludedTypes.Contains(declaration.DeclarationType))
    .OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())
    .ToList();


While we're at it, it should be more efficient to use the overload of OrderBy that takes an IComparer, so we're not making lots of throw-away strings:

_declarations = declarations
    .Where(declaration => !ExcludedTypes.Contains(declaration.DeclarationType))
    .OrderBy(declaration => declaration.IdentifierName, StringComparer.OrdinalIgnoreCase)
    .ToList();


This also lets us simplify Search:

private void Search(string value)
{
    var declarations = _declarations;
    if (!string.IsNullOrEmpty(value))
    {
        declarations = declarations
            .Where(declaration => declaration.IdentifierName.IndexOf(value, StringComparison.OrdinalIgnoreCase) != -1);
    }

    var results = declarations
        .Select(declaration => new SearchResult(declaration, _cache[declaration]));

    MatchResults = new ObservableCollection(results);
}


Here I've used IndexOf instead of Contains so we can pass it a StringComparer, again saving on the creation of string objects.

(Consider using string.IsNullOrWhiteSpace instead of string.IsNullOrEmpty depending on the behaviour you want.)

We can save a call to Search if _searchString hasn't changed. Not sure if it will help much in this case, but it's something to consider.

set
{
    if (_searchString == value)
    {
        return;
    }

    _searchString = value; 
    Search(value);
}

Code Snippets

_declarations = declarations
    .Where(declaration => !ExcludedTypes.Contains(declaration.DeclarationType))
    .OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())
    .ToList();
_declarations = declarations
    .Where(declaration => !ExcludedTypes.Contains(declaration.DeclarationType))
    .OrderBy(declaration => declaration.IdentifierName, StringComparer.OrdinalIgnoreCase)
    .ToList();
private void Search(string value)
{
    var declarations = _declarations;
    if (!string.IsNullOrEmpty(value))
    {
        declarations = declarations
            .Where(declaration => declaration.IdentifierName.IndexOf(value, StringComparison.OrdinalIgnoreCase) != -1);
    }

    var results = declarations
        .Select(declaration => new SearchResult(declaration, _cache[declaration]));

    MatchResults = new ObservableCollection<SearchResult>(results);
}
set
{
    if (_searchString == value)
    {
        return;
    }

    _searchString = value; 
    Search(value);
}

Context

StackExchange Code Review Q#91499, answer score: 4

Revisions (0)

No revisions yet.