/* 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.Diagnostics; using System.Linq; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; namespace dnSpy.Contracts.Language.Intellisense { /// /// collection /// public class DsCompletionSet : CompletionSet { readonly Completion[] allCompletions; readonly Completion[] allCompletionBuilders; readonly FilteredCompletionCollection filteredCompletions; readonly FilteredCompletionCollection filteredCompletionBuilders; /// /// Gets the filtered s /// public override IList Completions => filteredCompletions; /// /// Gets the filtered builders /// public override IList CompletionBuilders => filteredCompletionBuilders; /// /// Gets or sets the text tracking span to which this completion applies /// public override ITrackingSpan ApplicableTo { get => base.ApplicableTo; protected set { searchText = null; base.ApplicableTo = value; } } /// /// Gets the filters /// public virtual IReadOnlyList Filters { get; } /// /// Constructor /// /// Unique non-localized identifier /// Name shown in the UI if there are multiple s /// Span that will be modified when a gets committed /// Completion items /// Completion builders /// Filters or null public DsCompletionSet(string moniker, string displayName, ITrackingSpan applicableTo, IEnumerable completions, IEnumerable completionBuilders, IReadOnlyList? filters) : base(moniker, displayName, applicableTo, Array.Empty(), Array.Empty()) { allCompletions = completions.ToArray(); allCompletionBuilders = completionBuilders.ToArray(); filteredCompletions = new FilteredCompletionCollection(allCompletions); filteredCompletionBuilders = new FilteredCompletionCollection(allCompletionBuilders); Filters = filters ?? Array.Empty(); } string SearchText { get { var atSpan = ApplicableTo; if (atSpan is null) return string.Empty; if (searchText is null || atSpan.TextBuffer.CurrentSnapshot.Version.VersionNumber != searchTextVersion) { searchTextVersion = atSpan.TextBuffer.CurrentSnapshot.Version.VersionNumber; searchText = atSpan.GetText(atSpan.TextBuffer.CurrentSnapshot); } return searchText; } } string? searchText; int searchTextVersion = -1; /// /// Gets highlighted text spans or null /// /// Text shown in the UI /// public override IReadOnlyList? GetHighlightedSpansInDisplayText(string displayText) => CreateCompletionFilter(SearchText).GetMatchSpans(displayText); /// /// Creates a /// /// Search text /// public virtual ICompletionFilter CreateCompletionFilter(string searchText) => new CompletionFilter(searchText); /// /// Uses to filter /// /// Result /// Completion items to filter protected virtual void Filter(List filteredResult, IList completions) => filteredResult.AddRange(completions); /// /// Filters the list. should be called after this method /// public override void Filter() { Debug2.Assert(ApplicableTo is not null, "You must initialize " + nameof(ApplicableTo) + " before calling this method"); var inputText = SearchText; var filteredList = new List(allCompletions.Length); Filter(filteredList, allCompletions); IList finalList; if (inputText.Length < CompletionConstants.MimimumSearchLengthForFilter) finalList = filteredList; else { var list = new List(filteredList.Count); finalList = list; var completionFilter = CreateCompletionFilter(inputText); foreach (var c in filteredList) { if (completionFilter.IsMatch(c)) list.Add(c); } } if (finalList.Count != 0) filteredCompletions.SetNewFilteredCollection(finalList); } /// /// Selects the best match and should be called after /// public override void SelectBestMatch() => SelectionStatus = GetBestMatch() ?? new CompletionSelectionStatus(null, false, false); readonly struct MruSelection { public Completion Completion { get; } public int Index { get; } public MruSelection(Completion completion, int index) { Completion = completion; Index = index; } } /// /// Gets the best match in /// /// protected virtual CompletionSelectionStatus GetBestMatch() { Debug2.Assert(ApplicableTo is not null, "You must initialize " + nameof(ApplicableTo) + " before calling this method"); var inputText = SearchText; var completionFilter = CreateCompletionFilter(inputText); int matches = 0; var selector = new BestMatchSelector(inputText); var mruSelectionCase = default(MruSelection); var mruSelection = default(MruSelection); if (inputText.Length > 0) { Debug2.Assert(searchText is not null); foreach (var completion in Completions) { if (!completionFilter.IsMatch(completion)) continue; matches++; selector.Select(completion); if (completion.DisplayText.StartsWith(searchText, StringComparison.Ordinal)) { int currentMruIndex = GetMruIndex(completion); if (mruSelectionCase.Completion is null || currentMruIndex < mruSelectionCase.Index) mruSelectionCase = new MruSelection(completion, currentMruIndex); } else if (completion.DisplayText.StartsWith(searchText, StringComparison.OrdinalIgnoreCase)) { int currentMruIndex = GetMruIndex(completion); if (mruSelection.Completion is null || currentMruIndex < mruSelection.Index) mruSelection = new MruSelection(completion, currentMruIndex); } } } // If it was an exact match, don't use the MRU-selected completion. Eg. // local 'i' exists, and we previously typed 'int', and we've just typed 'i', // then select 'i' and not 'int' var selectedCompletion = mruSelectionCase.Completion ?? mruSelection.Completion ?? selector.Result; if (selector.Result is not null && inputText.Equals(selector.Result!.TryGetFilterText(), StringComparison.OrdinalIgnoreCase)) selectedCompletion = selector.Result; bool isSelected = selectedCompletion is not null; bool isUnique = matches == 1; return new CompletionSelectionStatus(selectedCompletion, isSelected, isUnique); } /// /// Gets the MRU index of /// /// Completion item /// protected virtual int GetMruIndex(Completion completion) => int.MaxValue; } }