public static CompletionItem Create(
            string displayText,
            Glyph?glyph = null,
            ImmutableArray <SymbolDisplayPart> description = default(ImmutableArray <SymbolDisplayPart>),
            string sortText           = null,
            string filterText         = null,
            int?matchPriority         = null,
            bool showsWarningIcon     = false,
            bool shouldFormatOnCommit = false,
            ImmutableDictionary <string, string> properties = null,
            ImmutableArray <string> tags = default(ImmutableArray <string>),
            CompletionItemRules rules    = null)
        {
            tags = tags.NullToEmpty();

            if (glyph != null)
            {
                // put glyph tags first
                tags = GlyphTags.GetTags(glyph.Value).AddRange(tags);
            }

            if (showsWarningIcon)
            {
                tags = tags.Add(CompletionTags.Warning);
            }

            properties = properties ?? ImmutableDictionary <string, string> .Empty;
            if (!description.IsDefault && description.Length > 0)
            {
                properties = properties.Add("Description", EncodeDescription(description));
            }

            rules = rules ?? CompletionItemRules.Default;
            rules = rules.WithMatchPriority(matchPriority.GetValueOrDefault())
                    .WithFormatOnCommit(shouldFormatOnCommit);

            return(CompletionItem.Create(
                       displayText: displayText,
                       filterText: filterText,
                       sortText: sortText,
                       properties: properties,
                       tags: tags,
                       rules: rules));
        }
Example #2
0
        public static CompletionItem Create(
            string displayText,
            string displayTextSuffix,
            CompletionItemRules rules,
            Glyph?glyph = null,
            ImmutableArray <SymbolDisplayPart> description = default,
            string sortText       = null,
            string filterText     = null,
            bool showsWarningIcon = false,
            ImmutableDictionary <string, string> properties = null,
            ImmutableArray <string> tags = default,
            string inlineDescription     = null,
            bool isComplexTextEdit       = false)
        {
            tags = tags.NullToEmpty();

            if (glyph != null)
            {
                // put glyph tags first
                tags = GlyphTags.GetTags(glyph.Value).AddRange(tags);
            }

            if (showsWarningIcon)
            {
                tags = tags.Add(WellKnownTags.Warning);
            }

            properties ??= ImmutableDictionary <string, string> .Empty;
            if (!description.IsDefault && description.Length > 0)
            {
                properties = properties.Add("Description", EncodeDescription(description));
            }

            return(CompletionItem.Create(
                       displayText: displayText,
                       displayTextSuffix: displayTextSuffix,
                       filterText: filterText,
                       sortText: sortText,
                       properties: properties,
                       tags: tags,
                       rules: rules,
                       inlineDescription: inlineDescription,
                       isComplexTextEdit: isComplexTextEdit));
        }
Example #3
0
        private FilteredCompletionModel UpdateCompletionList(
            IAsyncCompletionSession session,
            AsyncCompletionSessionDataSnapshot data,
            CancellationToken cancellationToken)
        {
            if (!session.Properties.TryGetProperty(CompletionSource.HasSuggestionItemOptions, out bool hasSuggestedItemOptions))
            {
                // This is the scenario when the session is created out of Roslyn, in some other provider, e.g. in Debugger.
                // For now, the default hasSuggestedItemOptions is false.
                hasSuggestedItemOptions = false;
            }

            hasSuggestedItemOptions |= data.DisplaySuggestionItem;

            var filterText = session.ApplicableToSpan.GetText(data.Snapshot);
            var reason     = data.Trigger.Reason;

            if (!session.Properties.TryGetProperty(CompletionSource.InitialTriggerKind, out CompletionTriggerKind initialRoslynTriggerKind))
            {
                initialRoslynTriggerKind = CompletionTriggerKind.Invoke;
            }

            // Check if the user is typing a number. If so, only proceed if it's a number
            // directly after a <dot>. That's because it is actually reasonable for completion
            // to be brought up after a <dot> and for the user to want to filter completion
            // items based on a number that exists in the name of the item. However, when
            // we are not after a dot (i.e. we're being brought up after <space> is typed)
            // then we don't want to filter things. Consider the user writing:
            //
            //      dim i =<space>
            //
            // We'll bring up the completion list here (as VB has completion on <space>).
            // If the user then types '3', we don't want to match against Int32.
            if (filterText.Length > 0 && char.IsNumber(filterText[0]))
            {
                if (!IsAfterDot(data.Snapshot, session.ApplicableToSpan))
                {
                    // Dismiss the session.
                    return(null);
                }
            }

            // We need to filter if a non-empty strict subset of filters are selected
            var selectedFilters = data.SelectedFilters.Where(f => f.IsSelected).Select(f => f.Filter).ToImmutableArray();
            var needToFilter    = selectedFilters.Length > 0 && selectedFilters.Length < data.SelectedFilters.Length;
            var filterReason    = Helpers.GetFilterReason(data.Trigger);

            // If the session was created/maintained out of Roslyn, e.g. in debugger; no properties are set and we should use data.Snapshot.
            // However, we prefer using the original snapshot in some projection scenarios.
            if (!session.Properties.TryGetProperty(CompletionSource.TriggerSnapshot, out ITextSnapshot snapshotForDocument))
            {
                snapshotForDocument = data.Snapshot;
            }

            var document          = snapshotForDocument.TextBuffer.AsTextContainer().GetOpenDocumentInCurrentContext();
            var completionService = document?.GetLanguageService <CompletionService>();
            var completionRules   = completionService?.GetRules() ?? CompletionRules.Default;
            var completionHelper  = document != null?CompletionHelper.GetHelper(document) : _defaultCompletionHelper;

            var initialListOfItemsToBeIncluded = new List <ExtendedFilterResult>();

            foreach (var item in data.InitialSortedList)
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (needToFilter && ShouldBeFilteredOutOfCompletionList(item, selectedFilters))
                {
                    continue;
                }

                if (!item.Properties.TryGetProperty(CompletionSource.RoslynItem, out RoslynCompletionItem roslynItem))
                {
                    roslynItem = RoslynCompletionItem.Create(
                        displayText: item.DisplayText,
                        filterText: item.FilterText,
                        sortText: item.SortText,
                        displayTextSuffix: item.Suffix);
                }

                if (MatchesFilterText(completionHelper, roslynItem, filterText, initialRoslynTriggerKind, filterReason, _recentItemsManager.RecentItems))
                {
                    initialListOfItemsToBeIncluded.Add(new ExtendedFilterResult(item, new FilterResult(roslynItem, filterText, matchedFilterText: true)));
                }
                else
                {
                    // The item didn't match the filter text.  We'll still keep it in the list
                    // if one of two things is true:
                    //
                    //  1. The user has only typed a single character.  In this case they might
                    //     have just typed the character to get completion.  Filtering out items
                    //     here is not desirable.
                    //
                    //  2. They brough up completion with ctrl-j or through deletion.  In these
                    //     cases we just always keep all the items in the list.
                    if (initialRoslynTriggerKind == CompletionTriggerKind.Deletion ||
                        initialRoslynTriggerKind == CompletionTriggerKind.Invoke ||
                        filterText.Length <= 1)
                    {
                        initialListOfItemsToBeIncluded.Add(new ExtendedFilterResult(item, new FilterResult(roslynItem, filterText, matchedFilterText: false)));
                    }
                }
            }

            if (data.Trigger.Reason == CompletionTriggerReason.Backspace &&
                completionRules.DismissIfLastCharacterDeleted &&
                session.ApplicableToSpan.GetText(data.Snapshot).Length == 0)
            {
                // Dismiss the session
                return(null);
            }

            if (initialListOfItemsToBeIncluded.Count == 0)
            {
                return(HandleAllItemsFilteredOut(reason, data.SelectedFilters, completionRules));
            }

            var options = document?.Project.Solution.Options;
            var highlightMatchingPortions = options?.GetOption(CompletionOptions.HighlightMatchingPortionsOfCompletionListItems, document.Project.Language) ?? true;
            var showCompletionItemFilters = options?.GetOption(CompletionOptions.ShowCompletionItemFilters, document.Project.Language) ?? true;

            var updatedFilters = showCompletionItemFilters
                ? GetUpdatedFilters(initialListOfItemsToBeIncluded, data.SelectedFilters)
                : ImmutableArray <CompletionFilterWithState> .Empty;

            var highlightedList = GetHighlightedList(initialListOfItemsToBeIncluded, filterText, completionHelper, highlightMatchingPortions).ToImmutableArray();

            // If this was deletion, then we control the entire behavior of deletion ourselves.
            if (initialRoslynTriggerKind == CompletionTriggerKind.Deletion)
            {
                return(HandleDeletionTrigger(data.Trigger.Reason, initialListOfItemsToBeIncluded, filterText, updatedFilters, highlightedList));
            }

            Func <ImmutableArray <RoslynCompletionItem>, string, ImmutableArray <RoslynCompletionItem> > filterMethod;

            if (completionService == null)
            {
                filterMethod = (items, text) => CompletionService.FilterItems(completionHelper, items, text);
            }
            else
            {
                filterMethod = (items, text) => completionService.FilterItems(document, items, text);
            }

            return(HandleNormalFiltering(
                       filterMethod,
                       filterText,
                       updatedFilters,
                       initialRoslynTriggerKind,
                       filterReason,
                       data.Trigger.Character,
                       initialListOfItemsToBeIncluded,
                       highlightedList,
                       completionHelper,
                       hasSuggestedItemOptions));
        }