internal static bool IsHardSelection(
            string fullFilterText,
            CompletionTriggerKind initialTriggerKind,
            RoslynCompletionItem bestFilterMatch,
            CompletionHelper completionHelper,
            CompletionFilterReason filterReason,
            ImmutableArray <string> recentItems,
            bool useSuggestionMode)
        {
            if (bestFilterMatch == null || 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 shouldSoftSelect = ShouldSoftSelectItem(bestFilterMatch, fullFilterText);

            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 (!ItemManager.MatchesFilterText(completionHelper, bestFilterMatch, fullFilterText, initialTriggerKind, filterReason, recentItems))
            {
                return(false);
            }

            // There was either filter text, or this was a preselect match.  In either case, we
            // can hard select this.
            return(true);
        }
Exemple #2
0
        public static CompletionFilterReason GetFilterReason(this CompletionTriggerKind kind)
        {
            switch (kind)
            {
            case CompletionTriggerKind.Insertion:
                return(CompletionFilterReason.Insertion);

            case CompletionTriggerKind.Deletion:
                return(CompletionFilterReason.Deletion);

            case CompletionTriggerKind.Snippets:
            case CompletionTriggerKind.Invoke:
            case CompletionTriggerKind.InvokeAndCommitIfUnique:
                return(CompletionFilterReason.Other);

            default:
                throw ExceptionUtilities.UnexpectedValue(kind);
            }
        }
        internal static bool MatchesFilterText(
            CompletionHelper helper, RoslynCompletionItem item,
            string filterText, CompletionTriggerKind initialTriggerKind,
            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.Deletion &&
                initialTriggerKind == 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 && ItemManager.GetRecentItemIndex(recentItems, item) <= 0)
                {
                    return(true);
                }
            }

            // Checks if the given completion item matches the pattern provided so far.
            // A  completion item is checked against the pattern by see if it's
            // CompletionItem.FilterText matches the item.  That way, the pattern it checked
            // against terms like "IList" and not IList<>
            return(helper.MatchesPattern(item.FilterText, filterText, CultureInfo.CurrentCulture));
        }
        private bool ValidatePossibleTriggerCharacterSet(CompletionTriggerKind completionTriggerKind, IEnumerable <CompletionProvider> triggeredProviders,
                                                         Document document, SourceText text, int caretPosition)
        {
            // Only validate on insertion triggers.
            if (completionTriggerKind != CompletionTriggerKind.Insertion)
            {
                return(true);
            }

            var syntaxFactsService = document.GetLanguageService <ISyntaxFactsService>();

            if (caretPosition > 0 && syntaxFactsService != null)
            {
                // The trigger character has already been inserted before the current caret position.
                var character = text[caretPosition - 1];

                // Identifier characters are not part of the possible trigger character set, so don't validate them.
                var isIdentifierCharacter = syntaxFactsService.IsIdentifierStartCharacter(character) || syntaxFactsService.IsIdentifierEscapeCharacter(character);
                if (isIdentifierCharacter)
                {
                    return(true);
                }

                // Only verify against built in providers.  3rd party ones do not necessarily implement the possible trigger characters API.
                foreach (var provider in triggeredProviders)
                {
                    if (provider is LSPCompletionProvider lspProvider)
                    {
                        Debug.Assert(lspProvider.TriggerCharacters.Contains(character),
                                     $"the character {character} is not a valid trigger character for {lspProvider.Name}");
                    }
                }
            }

            return(true);
        }
Exemple #5
0
        public void TriggerAppliedToProjection_CSharp_ReturnsExpectedResult(string character, CompletionTriggerKind kind, bool expected)
        {
            // Arrange
            var completionHandler = new CompletionHandler(JoinableTaskContext, Mock.Of <LSPRequestInvoker>(), Mock.Of <LSPDocumentManager>(), Mock.Of <LSPProjectionProvider>());
            var context           = new CompletionContext()
            {
                TriggerCharacter = character,
                TriggerKind      = kind
            };

            // Act
            var result = completionHandler.TriggerAppliesToProjection(context, RazorLanguageKind.CSharp);

            // Assert
            Assert.Equal(expected, result);
        }
        private FilteredCompletionModel HandleNormalFiltering(
            Func <ImmutableArray <RoslynCompletionItem>, string, ImmutableArray <RoslynCompletionItem> > filterMethod,
            string filterText,
            ImmutableArray <CompletionFilterWithState> filters,
            CompletionTriggerKind initialRoslynTriggerKind,
            CompletionFilterReason filterReason,
            char typeChar,
            List <ExtendedFilterResult> itemsInList,
            ImmutableArray <CompletionItemWithHighlight> highlightedList,
            CompletionHelper completionHelper,
            bool hasSuggestedItemOptions)
        {
            // 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 matchingItems = itemsInList.Where(r => r.FilterResult.MatchedFilterText)
                                .Select(t => t.FilterResult.CompletionItem)
                                .AsImmutable();

            var chosenItems = filterMethod(matchingItems, filterText);

            var recentItems = _recentItemsManager.RecentItems;

            // Of the items the service returned, pick the one most recently committed
            var bestItem = GetBestCompletionItemBasedOnMRU(chosenItems, recentItems);
            VSCompletionItem uniqueItem = null;
            int selectedItemIndex       = 0;

            // 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.
            if (bestItem != null)
            {
                selectedItemIndex = itemsInList.IndexOf(i => Equals(i.FilterResult.CompletionItem, bestItem));
                if (selectedItemIndex > -1 && bestItem != null && matchingItems.Length == 1 && filterText.Length > 0)
                {
                    uniqueItem = highlightedList[selectedItemIndex].CompletionItem;
                }
            }

            // If we don't have a best completion item yet, then pick the first item from the list.
            var bestOrFirstCompletionItem = bestItem ?? itemsInList.First().FilterResult.CompletionItem;

            // Check that it is a filter symbol. We can be called for a non-filter symbol.
            if (filterReason == CompletionFilterReason.Insertion &&
                !IsPotentialFilterCharacter(typeChar) &&
                !string.IsNullOrEmpty(filterText) &&
                !Helpers.IsFilterCharacter(bestOrFirstCompletionItem, typeChar, filterText))
            {
                return(null);
            }

            bool isHardSelection = IsHardSelection(
                filterText, initialRoslynTriggerKind, bestOrFirstCompletionItem,
                completionHelper, filterReason, recentItems, hasSuggestedItemOptions);

            var updateSelectionHint = isHardSelection ? UpdateSelectionHint.Selected : UpdateSelectionHint.SoftSelected;

            // If no items found above, select the first item.
            if (selectedItemIndex == -1)
            {
                selectedItemIndex = 0;
            }

            return(new FilteredCompletionModel(
                       highlightedList, selectedItemIndex, filters,
                       updateSelectionHint, centerSelection: true, uniqueItem));
        }
Exemple #7
0
 internal CompletionTrigger(CompletionTriggerKind kind, char character = (char)0)
     : this()
 {
     this.Kind      = kind;
     this.Character = character;
 }
 public static Task <CompletionList> SendCompletion(this Server server, Uri uri, int line, int character, string triggerCharacter, CompletionTriggerKind triggerKind)
 {
     return(server.Completion(new CompletionParams {
         textDocument = new TextDocumentIdentifier {
             uri = uri
         },
         position = new Position {
             line = line,
             character = character
         },
         context = new CompletionContext {
             triggerCharacter = triggerCharacter,
             triggerKind = triggerKind
         }
     }, GetCancellationToken()));
 }
Exemple #9
0
 internal CompletionTrigger(CompletionTriggerKind kind, char character = (char)0)
     : this()
 {
     this.Kind = kind;
     this.Character = character;
 }