/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Windows.Data; using System.Windows.Input; using dnlib.DotNet; using dnSpy.AsmEditor.Properties; using dnSpy.AsmEditor.ViewHelpers; using dnSpy.Contracts.Decompiler; using dnSpy.Contracts.Documents; using dnSpy.Contracts.Documents.TreeView; using dnSpy.Contracts.MVVM; using dnSpy.Contracts.Search; using dnSpy.Contracts.TreeView; using dnSpy.Contracts.Utilities; namespace dnSpy.AsmEditor.DnlibDialogs { sealed class MemberPickerVM : ViewModelBase { const int DEFAULT_DELAY_SEARCH_MS = 100; public IOpenAssembly OpenAssembly { set => openAssembly = value; } IOpenAssembly? openAssembly; public ICommand OpenCommand => new RelayCommand(a => OpenNewAssembly(), a => CanOpenAssembly); public bool CanOpenAssembly { get => true; set { if (canOpenAssembly != value) { canOpenAssembly = value; OnPropertyChanged(nameof(CanOpenAssembly)); } } } bool canOpenAssembly = true; public object? SelectedItem { get => selectedItem; set { if (selectedItem != value) { selectedItem = value; OnPropertyChanged(nameof(SelectedItem)); if (value is not null) { searchResult = null; OnPropertyChanged(nameof(SearchResult)); } HasErrorUpdated(); } } } object? selectedItem; public object? SelectedDnlibObject { get { var res = SearchResult; if (res is not null) { var obj = res.Object; if (obj is AssemblyDef && filter.GetResult((AssemblyDef)obj).IsMatch) return res.Document; if (obj is ModuleDef && filter.GetResult((ModuleDef)obj).IsMatch) return res.Document; if (obj is IDsDocument && filter.GetResult((IDsDocument)obj).IsMatch) return (IDsDocument)obj; if (obj is string && filter.GetResult((string)obj, res.Document).IsMatch) return (string)obj; if (obj is TypeDef && filter.GetResult((TypeDef)obj).IsMatch) return obj; if (obj is FieldDef && filter.GetResult((FieldDef)obj).IsMatch) return obj; if (obj is MethodDef && filter.GetResult((MethodDef)obj).IsMatch) return obj; if (obj is PropertyDef && filter.GetResult((PropertyDef)obj).IsMatch) return obj; if (obj is EventDef && filter.GetResult((EventDef)obj).IsMatch) return obj; if (obj is AssemblyRef && filter.GetResult((AssemblyRef)obj).IsMatch) return (AssemblyRef)obj; if (obj is ModuleRef && filter.GetResult((ModuleRef)obj).IsMatch) return (ModuleRef)obj; } var item = documentTreeView.TreeView.FromImplNode(SelectedItem); if (item is not null) { if (item is AssemblyDocumentNode && filter.GetResult(((AssemblyDocumentNode)item).Document.AssemblyDef!).IsMatch) return ((AssemblyDocumentNode)item).Document; else if (item is ModuleDocumentNode && filter.GetResult(((ModuleDocumentNode)item).Document.ModuleDef!).IsMatch) return ((ModuleDocumentNode)item).Document; else if (item is DsDocumentNode && filter.GetResult(((DsDocumentNode)item).Document).IsMatch) return ((DsDocumentNode)item).Document; if (item is NamespaceNode && filter.GetResult(((NamespaceNode)item).Name, ((ModuleDocumentNode)((NamespaceNode)item).TreeNode.Parent!.Data).Document).IsMatch) return ((NamespaceNode)item).Name; if (item is TypeNode && filter.GetResult(((TypeNode)item).TypeDef).IsMatch) return ((TypeNode)item).TypeDef; if (item is FieldNode && filter.GetResult(((FieldNode)item).FieldDef).IsMatch) return ((FieldNode)item).FieldDef; if (item is MethodNode && filter.GetResult(((MethodNode)item).MethodDef).IsMatch) return ((MethodNode)item).MethodDef; if (item is PropertyNode && filter.GetResult(((PropertyNode)item).PropertyDef).IsMatch) return ((PropertyNode)item).PropertyDef; if (item is EventNode && filter.GetResult(((EventNode)item).EventDef).IsMatch) return ((EventNode)item).EventDef; if (item is AssemblyReferenceNode && filter.GetResult(((AssemblyReferenceNode)item).AssemblyRef).IsMatch) return ((AssemblyReferenceNode)item).AssemblyRef; if (item is ModuleReferenceNode && filter.GetResult(((ModuleReferenceNode)item).ModuleRef).IsMatch) return ((ModuleReferenceNode)item).ModuleRef; } return null; } } public bool TooManyResults { get => tooManyResults; set { if (tooManyResults != value) { tooManyResults = value; OnPropertyChanged(nameof(TooManyResults)); } } } bool tooManyResults; public ICollectionView SearchResultsCollectionView => searchResultsCollectionView; readonly ListCollectionView searchResultsCollectionView; public ObservableCollection SearchResults { get; } public ISearchResult? SelectedSearchResult { get => selectedSearchResult; set { if (selectedSearchResult != value) { selectedSearchResult = value; OnPropertyChanged(nameof(SelectedSearchResult)); } } } ISearchResult? selectedSearchResult; public string SearchText { get => searchText; set { if (searchText != value) { bool hasSearchTextChanged = string.IsNullOrEmpty(searchText) != string.IsNullOrEmpty(value); searchText = value; OnPropertyChanged(nameof(SearchText)); if (hasSearchTextChanged) OnPropertyChanged(nameof(HasSearchText)); delayedSearch.Start(); } } } string searchText = string.Empty; readonly DelayedAction delayedSearch; public bool HasSearchText => !string.IsNullOrEmpty(searchText); public ISearchResult? SearchResult { get => searchResult; set { if (searchResult != value) { searchResult = value; OnPropertyChanged(nameof(SearchResult)); if (value is not null) { selectedItem = null; OnPropertyChanged(nameof(SelectedItem)); } HasErrorUpdated(); } } } ISearchResult? searchResult; public ObservableCollection AllLanguages => allDecompilers; readonly ObservableCollection allDecompilers; public DecompilerVM Language { get => decompiler; set { if (decompiler != value) { decompiler = value; OnPropertyChanged(nameof(Language)); RefreshTreeView(); } } } DecompilerVM decompiler; readonly IDecompilerService decompilerService; readonly IDocumentTreeView documentTreeView; readonly IDocumentTreeNodeFilter filter; readonly IDocumentSearcherProvider fileSearcherProvider; public bool SyntaxHighlight { get; set; } public string Title { get; } bool CaseSensitive { get; } bool MatchWholeWords { get; } bool MatchAnySearchTerm { get; } public MemberPickerVM(IDocumentSearcherProvider fileSearcherProvider, IDocumentTreeView documentTreeView, IDecompilerService decompilerService, IDocumentTreeNodeFilter filter, string title, IEnumerable assemblies) { Title = title; this.fileSearcherProvider = fileSearcherProvider; this.decompilerService = decompilerService; this.documentTreeView = documentTreeView; allDecompilers = new ObservableCollection(decompilerService.AllDecompilers.Select(a => new DecompilerVM(a))); decompiler = allDecompilers.FirstOrDefault(a => a.Decompiler == decompilerService.Decompiler) ?? throw new InvalidOperationException(); this.filter = filter; delayedSearch = new DelayedAction(DEFAULT_DELAY_SEARCH_MS, DelayStartSearch); SearchResults = new ObservableCollection(); searchResultsCollectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(SearchResults); searchResultsCollectionView.CustomSort = new SearchResult_Comparer(); foreach (var file in assemblies) documentTreeView.DocumentService.ForceAdd(file, false, null); documentTreeView.DocumentService.CollectionChanged += (s, e) => Restart(); CaseSensitive = false; MatchWholeWords = false; MatchAnySearchTerm = false; RefreshTreeView(); } public bool SelectItem(object? item) { var node = documentTreeView.FindNode(item); if (node is null) return false; documentTreeView.TreeView.SelectItems(new TreeNodeData[] { node }); SelectedItem = documentTreeView.TreeView.ToImplNode(node); return true; } void RefreshTreeView() { documentTreeView.SetDecompiler(Language.Decompiler); Restart(); } void OpenNewAssembly() { if (openAssembly is null) throw new InvalidOperationException(); var file = openAssembly.Open(); if (file is null) return; documentTreeView.DocumentService.GetOrAdd(file); } void DelayStartSearch() => Restart(); void StartSearch() { CancelSearch(); if (string.IsNullOrEmpty(SearchText)) SearchResults.Clear(); else { var options = new DocumentSearcherOptions { SearchComparer = SearchComparerFactory.Create(SearchText, CaseSensitive, MatchWholeWords, MatchAnySearchTerm), Filter = filter, SearchDecompiledData = false, }; fileSearcher = fileSearcherProvider.Create(options, documentTreeView); fileSearcher.SyntaxHighlight = SyntaxHighlight; fileSearcher.Decompiler = Language.Decompiler; fileSearcher.OnSearchCompleted += FileSearcher_OnSearchCompleted; fileSearcher.OnNewSearchResults += FileSearcher_OnNewSearchResults; fileSearcher.Start(documentTreeView.TreeView.Root.DataChildren.OfType()); } } IDocumentSearcher? fileSearcher; bool searchCompleted; void FileSearcher_OnSearchCompleted(object? sender, EventArgs e) { if (sender is null || sender != fileSearcher || searchCompleted) return; searchCompleted = true; SearchResults.Remove(fileSearcher!.SearchingResult!); TooManyResults = fileSearcher.TooManyResults; } void FileSearcher_OnNewSearchResults(object? sender, SearchResultEventArgs e) { if (sender is null || sender != fileSearcher) return; Debug.Assert(!searchCompleted); if (searchCompleted) return; foreach (var vm in e.Results) SearchResults.Add(vm); } public void Restart() { StopSearch(); SearchResults.Clear(); StartSearch(); } void StopSearch() { CancelSearch(); delayedSearch.Cancel(); } public void Clear() { SearchText = string.Empty; StopSearch(); SearchResults.Clear(); } void CancelSearch() { TooManyResults = false; delayedSearch.Cancel(); if (fileSearcher is not null) { fileSearcher.Cancel(); fileSearcher = null; } searchCompleted = false; } protected override string? Verify(string columnName) { if (columnName == nameof(SelectedItem) || columnName == nameof(SearchResult)) { if (SelectedItem is null && SearchResult is null) return dnSpy_AsmEditor_Resources.PickMember_TypeMustBeSelected; if (SelectedDnlibObject is null) return GetErrorMessage(); return string.Empty; } return string.Empty; } string GetErrorMessage() => dnSpy_AsmEditor_Resources.PickMember_SelectCorrectNode; public override bool HasError { get { if (!string.IsNullOrEmpty(Verify(nameof(SelectedItem)))) return true; if (!string.IsNullOrEmpty(Verify(nameof(SearchResult)))) return true; return false; } } } sealed class SearchResult_Comparer : System.Collections.IComparer { public int Compare(object? x, object? y) { var a = x as ISearchResult; var b = y as ISearchResult; if (a is null) return 1; if (b is null) return -1; if (a == b) return 0; return a.CompareTo(b); } } }