You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
384 lines
13 KiB
384 lines
13 KiB
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.Threading;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Threading;
|
|
|
|
using ICSharpCode.ILSpy.TreeNodes;
|
|
using Mono.Cecil;
|
|
|
|
namespace ICSharpCode.ILSpy
|
|
{
|
|
/// <summary>
|
|
/// Search pane
|
|
/// </summary>
|
|
public partial class SearchPane : UserControl, IPane
|
|
{
|
|
static SearchPane instance;
|
|
RunningSearch currentSearch;
|
|
|
|
public static SearchPane Instance {
|
|
get {
|
|
if (instance == null) {
|
|
App.Current.VerifyAccess();
|
|
instance = new SearchPane();
|
|
}
|
|
return instance;
|
|
}
|
|
}
|
|
|
|
private SearchPane()
|
|
{
|
|
InitializeComponent();
|
|
searchModeComboBox.Items.Add(new { Image = Images.Library, Name = "Types and Members" });
|
|
searchModeComboBox.Items.Add(new { Image = Images.Class, Name = "Type" });
|
|
searchModeComboBox.Items.Add(new { Image = Images.Property, Name = "Member" });
|
|
searchModeComboBox.Items.Add(new { Image = Images.Method, Name = "Method" });
|
|
searchModeComboBox.Items.Add(new { Image = Images.Field, Name = "Field" });
|
|
searchModeComboBox.Items.Add(new { Image = Images.Property, Name = "Property" });
|
|
searchModeComboBox.Items.Add(new { Image = Images.Event, Name = "Event" });
|
|
searchModeComboBox.Items.Add(new { Image = Images.Literal, Name = "Constant" });
|
|
searchModeComboBox.Items.Add(new { Image = Images.Library, Name = "Metadata Token" });
|
|
searchModeComboBox.SelectedIndex = (int)MainWindow.Instance.SessionSettings.SelectedSearchMode;
|
|
searchModeComboBox.SelectionChanged += (sender, e) => MainWindow.Instance.SessionSettings.SelectedSearchMode = (SearchMode)searchModeComboBox.SelectedIndex;
|
|
ContextMenuProvider.Add(listBox);
|
|
|
|
MainWindow.Instance.CurrentAssemblyListChanged += MainWindow_Instance_CurrentAssemblyListChanged;
|
|
}
|
|
|
|
bool runSearchOnNextShow;
|
|
|
|
void MainWindow_Instance_CurrentAssemblyListChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (IsVisible) {
|
|
StartSearch(this.SearchTerm);
|
|
} else {
|
|
StartSearch(null);
|
|
runSearchOnNextShow = true;
|
|
}
|
|
}
|
|
|
|
public void Show()
|
|
{
|
|
if (!IsVisible) {
|
|
MainWindow.Instance.ShowInTopPane("Search", this);
|
|
if (runSearchOnNextShow) {
|
|
runSearchOnNextShow = false;
|
|
StartSearch(this.SearchTerm);
|
|
}
|
|
}
|
|
Dispatcher.BeginInvoke(
|
|
DispatcherPriority.Background,
|
|
new Action(
|
|
delegate {
|
|
searchBox.Focus();
|
|
searchBox.SelectAll();
|
|
}));
|
|
}
|
|
|
|
public static readonly DependencyProperty SearchTermProperty =
|
|
DependencyProperty.Register("SearchTerm", typeof(string), typeof(SearchPane),
|
|
new FrameworkPropertyMetadata(string.Empty, OnSearchTermChanged));
|
|
|
|
public string SearchTerm {
|
|
get { return (string)GetValue(SearchTermProperty); }
|
|
set { SetValue(SearchTermProperty, value); }
|
|
}
|
|
|
|
static void OnSearchTermChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
((SearchPane)o).StartSearch((string)e.NewValue);
|
|
}
|
|
|
|
void SearchModeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
StartSearch(this.SearchTerm);
|
|
}
|
|
|
|
void StartSearch(string searchTerm)
|
|
{
|
|
if (currentSearch != null) {
|
|
currentSearch.Cancel();
|
|
}
|
|
if (string.IsNullOrEmpty(searchTerm)) {
|
|
currentSearch = null;
|
|
listBox.ItemsSource = null;
|
|
} else {
|
|
MainWindow mainWindow = MainWindow.Instance;
|
|
currentSearch = new RunningSearch(mainWindow.CurrentAssemblyList.GetAssemblies(), searchTerm, (SearchMode)searchModeComboBox.SelectedIndex, mainWindow.CurrentLanguage);
|
|
listBox.ItemsSource = currentSearch.Results;
|
|
new Thread(currentSearch.Run).Start();
|
|
}
|
|
}
|
|
|
|
void IPane.Closed()
|
|
{
|
|
this.SearchTerm = string.Empty;
|
|
}
|
|
|
|
void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
|
{
|
|
JumpToSelectedItem();
|
|
e.Handled = true;
|
|
}
|
|
|
|
void ListBox_KeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
if (e.Key == Key.Return) {
|
|
e.Handled = true;
|
|
JumpToSelectedItem();
|
|
}
|
|
}
|
|
|
|
void JumpToSelectedItem()
|
|
{
|
|
SearchResult result = listBox.SelectedItem as SearchResult;
|
|
if (result != null) {
|
|
MainWindow.Instance.JumpToReference(result.Member);
|
|
}
|
|
}
|
|
|
|
protected override void OnKeyDown(KeyEventArgs e)
|
|
{
|
|
base.OnKeyDown(e);
|
|
if (e.Key == Key.T && e.KeyboardDevice.Modifiers == ModifierKeys.Control) {
|
|
searchModeComboBox.SelectedIndex = (int)SearchMode.Type;
|
|
e.Handled = true;
|
|
} else if (e.Key == Key.M && e.KeyboardDevice.Modifiers == ModifierKeys.Control) {
|
|
searchModeComboBox.SelectedIndex = (int)SearchMode.Member;
|
|
e.Handled = true;
|
|
} else if (e.Key == Key.S && e.KeyboardDevice.Modifiers == ModifierKeys.Control) {
|
|
searchModeComboBox.SelectedIndex = (int)SearchMode.Literal;
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
void SearchBox_PreviewKeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
if (e.Key == Key.Down && listBox.HasItems) {
|
|
e.Handled = true;
|
|
listBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
|
|
listBox.SelectedIndex = 0;
|
|
}
|
|
}
|
|
|
|
sealed class RunningSearch
|
|
{
|
|
readonly Dispatcher dispatcher;
|
|
readonly CancellationTokenSource cts = new CancellationTokenSource();
|
|
readonly LoadedAssembly[] assemblies;
|
|
readonly string[] searchTerm;
|
|
readonly SearchMode searchMode;
|
|
readonly Language language;
|
|
public readonly ObservableCollection<SearchResult> Results = new ObservableCollection<SearchResult>();
|
|
int resultCount;
|
|
|
|
public RunningSearch(LoadedAssembly[] assemblies, string searchTerm, SearchMode searchMode, Language language)
|
|
{
|
|
this.dispatcher = Dispatcher.CurrentDispatcher;
|
|
this.assemblies = assemblies;
|
|
this.searchTerm = searchTerm.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
|
|
this.language = language;
|
|
this.searchMode = searchMode;
|
|
|
|
this.Results.Add(new SearchResult { Name = "Searching..." });
|
|
}
|
|
|
|
public void Cancel()
|
|
{
|
|
cts.Cancel();
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
try {
|
|
var searcher = GetSearchStrategy(searchMode, searchTerm);
|
|
foreach (var loadedAssembly in assemblies) {
|
|
ModuleDefinition module = loadedAssembly.GetModuleDefinitionOrNull();
|
|
if (module == null)
|
|
continue;
|
|
CancellationToken cancellationToken = cts.Token;
|
|
|
|
foreach (TypeDefinition type in module.Types) {
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
searcher.Search(type, language, AddResult);
|
|
}
|
|
}
|
|
} catch (OperationCanceledException) {
|
|
// ignore cancellation
|
|
}
|
|
// remove the 'Searching...' entry
|
|
dispatcher.BeginInvoke(
|
|
DispatcherPriority.Normal,
|
|
new Action(delegate { this.Results.RemoveAt(this.Results.Count - 1); }));
|
|
}
|
|
|
|
void AddResult(SearchResult result)
|
|
{
|
|
if (++resultCount == 1000) {
|
|
result = new SearchResult { Name = "Search aborted, more than 1000 results found." };
|
|
cts.Cancel();
|
|
}
|
|
dispatcher.BeginInvoke(
|
|
DispatcherPriority.Normal,
|
|
new Action(delegate { InsertResult(this.Results, result); }));
|
|
cts.Token.ThrowIfCancellationRequested();
|
|
}
|
|
|
|
void InsertResult(ObservableCollection<SearchResult> results, SearchResult result)
|
|
{
|
|
if (Options.DisplaySettingsPanel.CurrentDisplaySettings.SortResults)
|
|
{
|
|
// Keep results collection sorted by "Fitness" by inserting result into correct place
|
|
// Inserts in the beginning shifts all elements, but there can be no more than 1000 items.
|
|
for (int i = 0; i < results.Count; i++)
|
|
{
|
|
if (results[i].Fitness < result.Fitness)
|
|
{
|
|
results.Insert(i, result);
|
|
return;
|
|
}
|
|
}
|
|
results.Insert(results.Count - 1, result);
|
|
}
|
|
else
|
|
{
|
|
// Original Code
|
|
int index = results.BinarySearch(result, 0, results.Count - 1, SearchResult.Comparer);
|
|
results.Insert(index < 0 ? ~index : index, result);
|
|
}
|
|
}
|
|
|
|
AbstractSearchStrategy GetSearchStrategy(SearchMode mode, string[] terms)
|
|
{
|
|
if (terms.Length == 1) {
|
|
if (terms[0].StartsWith("tm:", StringComparison.Ordinal))
|
|
return new TypeAndMemberSearchStrategy(terms[0].Substring(3));
|
|
|
|
if (terms[0].StartsWith("t:", StringComparison.Ordinal))
|
|
return new TypeSearchStrategy(terms[0].Substring(2));
|
|
|
|
if (terms[0].StartsWith("m:", StringComparison.Ordinal))
|
|
return new MemberSearchStrategy(terms[0].Substring(2));
|
|
|
|
if (terms[0].StartsWith("md:", StringComparison.Ordinal))
|
|
return new MemberSearchStrategy(terms[0].Substring(3), MemberSearchKind.Method);
|
|
|
|
if (terms[0].StartsWith("f:", StringComparison.Ordinal))
|
|
return new MemberSearchStrategy(terms[0].Substring(2), MemberSearchKind.Field);
|
|
|
|
if (terms[0].StartsWith("p:", StringComparison.Ordinal))
|
|
return new MemberSearchStrategy(terms[0].Substring(2), MemberSearchKind.Property);
|
|
|
|
if (terms[0].StartsWith("e:", StringComparison.Ordinal))
|
|
return new MemberSearchStrategy(terms[0].Substring(2), MemberSearchKind.Event);
|
|
|
|
if (terms[0].StartsWith("c:", StringComparison.Ordinal))
|
|
return new LiteralSearchStrategy(terms[0].Substring(2));
|
|
|
|
if (terms[0].StartsWith("@", StringComparison.Ordinal))
|
|
return new MetadataTokenSearchStrategy(terms[0].Substring(1));
|
|
}
|
|
|
|
switch (mode)
|
|
{
|
|
case SearchMode.TypeAndMember:
|
|
return new TypeAndMemberSearchStrategy(terms);
|
|
case SearchMode.Type:
|
|
return new TypeSearchStrategy(terms);
|
|
case SearchMode.Member:
|
|
return new MemberSearchStrategy(terms);
|
|
case SearchMode.Literal:
|
|
return new LiteralSearchStrategy(terms);
|
|
case SearchMode.Method:
|
|
return new MemberSearchStrategy(terms, MemberSearchKind.Method);
|
|
case SearchMode.Field:
|
|
return new MemberSearchStrategy(terms, MemberSearchKind.Field);
|
|
case SearchMode.Property:
|
|
return new MemberSearchStrategy(terms, MemberSearchKind.Property);
|
|
case SearchMode.Event:
|
|
return new MemberSearchStrategy(terms, MemberSearchKind.Event);
|
|
case SearchMode.Token:
|
|
return new MetadataTokenSearchStrategy(terms);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
sealed class SearchResult : IMemberTreeNode
|
|
{
|
|
public static readonly System.Collections.Generic.IComparer<SearchResult> Comparer = new SearchResultComparer();
|
|
|
|
public MemberReference Member { get; set; }
|
|
public float Fitness { get; set; }
|
|
|
|
public string Location { get; set; }
|
|
public string Name { get; set; }
|
|
public ImageSource Image { get; set; }
|
|
public ImageSource LocationImage { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return Name;
|
|
}
|
|
|
|
class SearchResultComparer : System.Collections.Generic.IComparer<SearchResult>
|
|
{
|
|
public int Compare(SearchResult x, SearchResult y)
|
|
{
|
|
return StringComparer.Ordinal.Compare(x?.Name ?? "", y?.Name ?? "");
|
|
}
|
|
}
|
|
}
|
|
|
|
[ExportMainMenuCommand(Menu = "_View", Header = "Search...", MenuIcon = "Images/Find.png", MenuCategory = "View", MenuOrder = 100)]
|
|
[ExportToolbarCommand(ToolTip = "Search (Ctrl+Shift+F or Ctrl+E)", ToolbarIcon = "Images/Find.png", ToolbarCategory = "View", ToolbarOrder = 100)]
|
|
sealed class ShowSearchCommand : CommandWrapper
|
|
{
|
|
public ShowSearchCommand()
|
|
: base(NavigationCommands.Search)
|
|
{
|
|
NavigationCommands.Search.InputGestures.Clear();
|
|
NavigationCommands.Search.InputGestures.Add(new KeyGesture(Key.F, ModifierKeys.Control | ModifierKeys.Shift));
|
|
NavigationCommands.Search.InputGestures.Add(new KeyGesture(Key.E, ModifierKeys.Control));
|
|
}
|
|
}
|
|
|
|
public enum SearchMode
|
|
{
|
|
TypeAndMember,
|
|
Type,
|
|
Member,
|
|
Method,
|
|
Field,
|
|
Property,
|
|
Event,
|
|
Literal,
|
|
Token
|
|
}
|
|
}
|