Beispiel #1
0
        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;
        }
Beispiel #3
0
        /// <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());
        }
Beispiel #4
0
        /// <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;
            }
Beispiel #11
0
 private CompletionHelper(Microsoft.CodeAnalysis.Completion.CompletionHelper inner)
 {
     _inner = inner;
 }
Beispiel #12
0
        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;
            }