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); }
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); }
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)); }
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())); }