/*
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;
}
}