public virtual ImmutableArray <CompletionItem> FilterItems( Document document, ImmutableArray <CompletionItem> items, string filterText) { var helper = CompletionHelper.GetHelper(document); return(FilterItems(helper, items, filterText)); }
private CompletionHelper GetCompletionHelper() { this.AssertIsForeground(); if (_completionHelper == null) { var document = GetDocument(); if (document != null) { _completionHelper = CompletionHelper.GetHelper(document); } } return _completionHelper; }
/// <summary> /// Given a list of completion items that match the current code typed by the user, /// returns the item that is considered the best match, and whether or not that /// item should be selected or not. /// /// itemToFilterText provides the values that each individual completion item should /// be filtered against. /// </summary> public virtual ImmutableArray <CompletionItem> FilterItems( Document document, ImmutableArray <CompletionItem> items, string filterText) { var helper = CompletionHelper.GetHelper(document); var itemsWithPatternMatch = new SegmentedList <(CompletionItem, PatternMatch?)>(items.Select( item => (item, helper.GetMatch(item.FilterText, filterText, includeMatchSpans: false, CultureInfo.CurrentCulture)))); var builder = ImmutableArray.CreateBuilder <CompletionItem>(); FilterItems(helper, itemsWithPatternMatch, filterText, builder); return(builder.ToImmutable()); }
/// <summary> /// Given a list of completion items that match the current code typed by the user, /// returns the item that is considered the best match, and whether or not that /// item should be selected or not. /// /// itemToFilterText provides the values that each individual completion item should /// be filtered against. /// </summary> public virtual ImmutableArray <CompletionItem> FilterItems( Document document, ImmutableArray <CompletionItem> items, string filterText) { var helper = CompletionHelper.GetHelper(document); var bestItems = ArrayBuilder <CompletionItem> .GetInstance(); foreach (var item in items) { if (bestItems.Count == 0) { // We've found no good items yet. So this is the best item currently. bestItems.Add(item); } else { var comparison = helper.CompareItems(item, bestItems.First(), filterText, CultureInfo.CurrentCulture); if (comparison < 0) { // This item is strictly better than the best items we've found so far. bestItems.Clear(); bestItems.Add(item); } else if (comparison == 0) { // This item is as good as the items we've been collecting. We'll return // it and let the controller decide what to do. (For example, it will // pick the one that has the best MRU index). bestItems.Add(item); } // otherwise, this item is strictly worse than the ones we've been collecting. // We can just ignore it. } } return(bestItems.ToImmutableAndFree()); }
private void CreateInstances() { _caseSensitiveInstance = new CompletionHelper(isCaseSensitive: true); _caseInsensitiveInstance = new CompletionHelper(isCaseSensitive: false); }
private bool IsHardSelection( Model model, PresentationItem bestFilterMatch, ITextSnapshot textSnapshot, CompletionHelper completionHelper, CompletionTrigger trigger, CompletionFilterReason reason) { if (model.SuggestionModeItem != null) { return bestFilterMatch != null && bestFilterMatch.Item.DisplayText == model.SuggestionModeItem.Item.DisplayText; } if (bestFilterMatch == null || model.UseSuggestionMode) { return false; } // We don't have a builder and we have a best match. Normally this will be hard // selected, except for a few cases. Specifically, if no filter text has been // provided, and this is not a preselect match then we will soft select it. This // happens when the completion list comes up implicitly and there is something in // the MRU list. In this case we do want to select it, but not with a hard // selection. Otherwise you can end up with the following problem: // // dim i as integer =<space> // // Completion will comes up after = with 'integer' selected (Because of MRU). We do // not want 'space' to commit this. var viewSpan = model.GetViewBufferSpan(bestFilterMatch.Item.Span); var fullFilterText = model.GetCurrentTextInSnapshot(viewSpan, textSnapshot, endPoint: null); var shouldSoftSelect = ShouldSoftSelectItem(bestFilterMatch.Item, fullFilterText, trigger); if (shouldSoftSelect) { return false; } // If the user moved the caret left after they started typing, the 'best' match may not match at all // against the full text span that this item would be replacing. if (!MatchesFilterText(completionHelper, bestFilterMatch.Item, fullFilterText, trigger, reason, this.Controller.GetRecentItems())) { return false; } // There was either filter text, or this was a preselect match. In either case, we // can hard select this. return true; }
private static bool MatchesFilterText( CompletionHelper helper, CompletionItem item, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray<string> recentItems) { // For the deletion we bake in the core logic for how matching should work. // This way deletion feels the same across all languages that opt into deletion // as a completion trigger. // Specifically, to avoid being too aggressive when matching an item during // completion, we require that the current filter text be a prefix of the // item in the list. if (filterReason == CompletionFilterReason.BackspaceOrDelete && trigger.Kind == CompletionTriggerKind.Deletion) { return item.FilterText.GetCaseInsensitivePrefixLength(filterText) > 0; } return helper.MatchesFilterText(item, filterText, trigger, recentItems); }
private static bool IsBetterFilterMatch( CompletionHelper helper, CompletionItem item1, CompletionItem item2, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray<string> recentItems) { // For the deletion we bake in the core logic for how betterness should work. // This way deletion feels the same across all languages that opt into deletion // as a completion trigger. if (filterReason == CompletionFilterReason.BackspaceOrDelete) { var prefixLength1 = item1.FilterText.GetCaseInsensitivePrefixLength(filterText); var prefixLength2 = item2.FilterText.GetCaseInsensitivePrefixLength(filterText); // Prefer the item that matches a longer prefix of the filter text. if (prefixLength1 > prefixLength2) { return true; } // If the lengths are the same, prefer the one with the higher match priority. // But only if it's an item that would have been hard selected. We don't want // to aggressively select an item that was only going to be softly offered. var item1Priority = item1.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection ? item1.Rules.MatchPriority : MatchPriority.Default; var item2Priority = item2.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection ? item2.Rules.MatchPriority : MatchPriority.Default; if (item1Priority > item2Priority) { return true; } return false; } return helper.IsBetterFilterMatch(item1, item2, filterText, trigger, recentItems); }
private static bool MatchesFilterText( CompletionHelper helper, CompletionItem item, string filterText, CompletionTrigger trigger, CompletionFilterReason filterReason, ImmutableArray<string> recentItems) { // For the deletion we bake in the core logic for how matching should work. // This way deletion feels the same across all languages that opt into deletion // as a completion trigger. // Specifically, to avoid being too aggressive when matching an item during // completion, we require that the current filter text be a prefix of the // item in the list. if (filterReason == CompletionFilterReason.BackspaceOrDelete && trigger.Kind == CompletionTriggerKind.Deletion) { return item.FilterText.GetCaseInsensitivePrefixLength(filterText) > 0; } // If the user hasn't typed anything, and this item was preselected, or was in the // MRU list, then we definitely want to include it. if (filterText.Length == 0) { if (item.Rules.MatchPriority > MatchPriority.Default) { return true; } if (!recentItems.IsDefault && GetRecentItemIndex(recentItems, item) < 0) { return true; } } if (filterText.Length > 0 && IsAllDigits(filterText)) { // The user is just typing a number. We never want this to match against // anything we would put in a completion list. return false; } return helper.MatchesFilterText(item, filterText, CultureInfo.CurrentCulture); }
private Model HandleNormalFiltering( Model model, CompletionFilterReason filterReason, ITextSnapshot textSnapshot, Document document, CompletionHelper helper, ImmutableArray<string> recentItems, string filterText, List<FilterResult> filterResults) { // Not deletion. Defer to the language to decide which item it thinks best // matches the text typed so far. // Ask the language to determine which of the *matched* items it wants to select. var service = this.Controller.GetCompletionService(); var matchingCompletionItems = filterResults.Where(r => r.MatchedFilterText) .Select(t => t.PresentationItem.Item) .AsImmutable(); var chosenItems = service.ChooseBestItems(document, matchingCompletionItems, filterText); // Of the items the service returned, pick the one most recently committed var bestCompletionItem = GetBestCompletionItemBasedOnMRU(chosenItems, recentItems); // If we don't have a best completion item yet, then pick the first item from the list. var bestOrFirstCompletionItem = bestCompletionItem ?? filterResults.First().PresentationItem.Item; var bestOrFirstPresentationItem = filterResults.Where( r => r.PresentationItem.Item == bestOrFirstCompletionItem).First().PresentationItem; var hardSelection = IsHardSelection( model, bestOrFirstPresentationItem, textSnapshot, helper, filterReason); // Determine if we should consider this item 'unique' or not. A unique item // will be automatically committed if the user hits the 'invoke completion' // without bringing up the completion list. An item is unique if it was the // only item to match the text typed so far, and there was at least some text // typed. i.e. if we have "Console.$$" we don't want to commit something // like "WriteLine" since no filter text has actually been provided. HOwever, // if "Console.WriteL$$" is typed, then we do want "WriteLine" to be committed. var matchingItemCount = matchingCompletionItems.Length; var isUnique = bestCompletionItem != null && matchingItemCount == 1 && filterText.Length > 0; var result = model.WithFilteredItems(filterResults.Select(r => r.PresentationItem).AsImmutable()) .WithSelectedItem(bestOrFirstPresentationItem) .WithHardSelection(hardSelection) .WithIsUnique(isUnique); return result; }
private CompletionHelper(Microsoft.CodeAnalysis.Completion.CompletionHelper inner) { _inner = inner; }
private CompletionHelper GetCompletionHelper() { _foregroundObject.AssertIsForeground(); if (_completionHelper == null) { var document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document != null) { _completionHelper = CompletionHelper.GetHelper(document, document.Project.LanguageServices.GetService<CompletionService>()); } } return _completionHelper; }
// Extract a local function to avoid creating a closure for code path of cache hit. static AsyncLazy <SyntaxContext> GetLazySyntaxContextWithSpeculativeModel(Document document, SharedSyntaxContextsWithSpeculativeModel self) { return(self._cache.GetOrAdd(document, d => AsyncLazy.Create(cancellationToken => CompletionHelper.CreateSyntaxContextWithExistingSpeculativeModelAsync(d, self._position, cancellationToken), cacheResult: true))); }
private bool IsHardSelection( Model model, CompletionItem bestFilterMatch, SnapshotPoint caretPosition, CompletionHelper completionHelper, CompletionFilterReason reason) { if (bestFilterMatch == null || model.UseSuggestionMode) { return false; } var textSnapshot = caretPosition.Snapshot; // We don't have a builder and we have a best match. Normally this will be hard // selected, except for a few cases. Specifically, if no filter text has been // provided, and this is not a preselect match then we will soft select it. This // happens when the completion list comes up implicitly and there is something in // the MRU list. In this case we do want to select it, but not with a hard // selection. Otherwise you can end up with the following problem: // // dim i as integer =<space> // // Completion will comes up after = with 'integer' selected (Because of MRU). We do // not want 'space' to commit this. var itemViewSpan = model.GetViewBufferSpan(bestFilterMatch.Span); var fullFilterText = model.GetCurrentTextInSnapshot(itemViewSpan, textSnapshot, endPoint: null); var trigger = model.Trigger; var shouldSoftSelect = ShouldSoftSelectItem(bestFilterMatch, fullFilterText, trigger); if (shouldSoftSelect) { return false; } // If the user moved the caret left after they started typing, the 'best' match may not match at all // against the full text span that this item would be replacing. if (!MatchesFilterText(completionHelper, bestFilterMatch, fullFilterText, trigger, reason, this.Controller.GetRecentItems())) { return false; } // Switch to soft selection, if user moved caret to the start of a non-empty filter span. // This prevents commiting if user types a commit character at this position later, but still has the list if user types filter character // i.e. blah| -> |blah -> !|blah // We want the filter span non-empty because we still want hard selection in the following case: // // A a = new | if (caretPosition == itemViewSpan.TextSpan.Start && itemViewSpan.TextSpan.Length > 0) { return false; } // There was either filter text, or this was a preselect match. In either case, we // can hard select this. return true; }