private Model FilterModelInBackgroundWorker(
                Model model,
                int id,
                SnapshotPoint caretPosition,
                bool recheckCaretPosition,
                bool dismissIfEmptyAllowed,
                CompletionFilterReason filterReason)
            {
                if (model == null)
                {
                    return null;
                }

                var filterState = model.FilterState;

                // If all the filters are on, or all the filters are off then we don't actually 
                // need to filter.
                if (filterState != null)
                {
                    if (filterState.Values.All(b => b) ||
                        filterState.Values.All(b => !b))
                    {
                        filterState = null;
                    }
                }

                // We want to dismiss the session if the caret ever moved outside our bounds.
                if (recheckCaretPosition && Controller.IsCaretOutsideAllItemBounds(model, caretPosition))
                {
                    return null;
                }

                if (id != _filterId)
                {
                    return model;
                }

                var textSnapshot = caretPosition.Snapshot;
                var textSpanToText = new Dictionary<TextSpan, string>();

                var document = this.Controller.GetDocument();
                var helper = this.Controller.GetCompletionHelper();

                var recentItems = this.Controller.GetRecentItems();

                var filterResults = new List<FilterResult>();

                var filterText = model.GetCurrentTextInSnapshot(model.OriginalList.Span, textSnapshot, textSpanToText);

                // If the user was typing a number, then immediately dismiss completion.
                var filterTextStartsWithANumber = filterText.Length > 0 && char.IsNumber(filterText[0]);
                if (filterTextStartsWithANumber)
                {
                    return null;
                }

                foreach (var currentItem in model.TotalItems)
                {
                    // Check if something new has happened and there's a later on filter operation
                    // in the chain.  If so, there's no need for us to do any more work (as it will
                    // just be superceded by the later work).
                    if (id != _filterId)
                    {
                        return model;
                    }

                    if (ItemIsFilteredOut(currentItem.Item, filterState))
                    {
                        continue;
                    }

                    // Check if the item matches the filter text typed so far.
                    var matchesFilterText = MatchesFilterText(helper, currentItem.Item, filterText, model.Trigger, filterReason, recentItems);

                    if (matchesFilterText)
                    {
                        filterResults.Add(new FilterResult(
                            currentItem, filterText, matchedFilterText: true));
                    }
                    else
                    {
                        if (filterText.Length <= 1)
                        {
                            // Even though the rule provider didn't match this, we'll still include it
                            // since we want to allow a user typing a single character and seeing all
                            // possibly completions.
                            filterResults.Add(new FilterResult(
                                currentItem, filterText, matchedFilterText: false));
                        }
                    }
                }

                model = model.WithFilterText(filterText);

                // If no items matched the filter text then determine what we should do.
                if (filterResults.Count == 0)
                {
                    return HandleAllItemsFilteredOut(model, filterReason, dismissIfEmptyAllowed);
                }

                // If this was deletion, then we control the entire behavior of deletion
                // ourselves.
                if (model.Trigger.Kind == CompletionTriggerKind.Deletion)
                {
                    return HandleDeletionTrigger(model, filterResults);
                }

                return HandleNormalFiltering(
                    model, filterReason, textSnapshot, document,
                    helper, recentItems, filterText, filterResults);
            }
Exemplo n.º 2
0
            private Model FilterModelInBackgroundWorker(
                Model model,
                int id,
                SnapshotPoint caretPosition,
                bool recheckCaretPosition,
                bool dismissIfEmptyAllowed,
                CompletionFilterReason filterReason)
            {
                if (model == null)
                {
                    return(null);
                }

                var filterState = model.FilterState;

                // If all the filters are on, or all the filters are off then we don't actually
                // need to filter.
                if (filterState != null)
                {
                    if (filterState.Values.All(b => b) ||
                        filterState.Values.All(b => !b))
                    {
                        filterState = null;
                    }
                }

                // We want to dismiss the session if the caret ever moved outside our bounds.
                if (recheckCaretPosition && Controller.IsCaretOutsideAllItemBounds(model, caretPosition))
                {
                    return(null);
                }

                if (id != _filterId)
                {
                    return(model);
                }

                var textSnapshot   = caretPosition.Snapshot;
                var textSpanToText = new Dictionary <TextSpan, string>();

                var document = this.Controller.GetDocument();
                var helper   = this.Controller.GetCompletionHelper();

                var recentItems = this.Controller.GetRecentItems();

                var filterResults = new List <FilterResult>();

                var filterText = model.GetCurrentTextInSnapshot(
                    model.OriginalList.Span, textSnapshot, textSpanToText);

                // 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.
                var filterTextStartsWithANumber = filterText.Length > 0 && char.IsNumber(filterText[0]);

                if (filterTextStartsWithANumber)
                {
                    if (!IsAfterDot(model, textSnapshot, textSpanToText))
                    {
                        return(null);
                    }
                }

                foreach (var currentItem in model.TotalItems)
                {
                    // Check if something new has happened and there's a later on filter operation
                    // in the chain.  If so, there's no need for us to do any more work (as it will
                    // just be superceded by the later work).
                    if (id != _filterId)
                    {
                        return(model);
                    }

                    if (ItemIsFilteredOut(currentItem, filterState))
                    {
                        continue;
                    }

                    // Check if the item matches the filter text typed so far.
                    var matchesFilterText = MatchesFilterText(helper, currentItem, filterText, model.Trigger, filterReason, recentItems);

                    if (matchesFilterText)
                    {
                        filterResults.Add(new FilterResult(
                                              currentItem, filterText, matchedFilterText: true));
                    }
                    else
                    {
                        if (filterText.Length <= 1)
                        {
                            // Even though the rule provider didn't match this, we'll still include it
                            // since we want to allow a user typing a single character and seeing all
                            // possibly completions.
                            filterResults.Add(new FilterResult(
                                                  currentItem, filterText, matchedFilterText: false));
                        }
                    }
                }

                model = model.WithFilterText(filterText);

                // If no items matched the filter text then determine what we should do.
                if (filterResults.Count == 0)
                {
                    return(HandleAllItemsFilteredOut(model, filterReason, dismissIfEmptyAllowed));
                }

                // If this was deletion, then we control the entire behavior of deletion
                // ourselves.
                if (model.Trigger.Kind == CompletionTriggerKind.Deletion)
                {
                    return(HandleDeletionTrigger(model, filterResults));
                }

                return(HandleNormalFiltering(
                           model, document, filterReason, textSnapshot,
                           helper, recentItems, filterText, filterResults));
            }
            private Model FilterModelInBackgroundWorker(
                Model model,
                int id,
                SnapshotPoint caretPosition,
                CompletionFilterReason filterReason,
                ImmutableDictionary <CompletionItemFilter, bool> filterState)
            {
                if (model == null)
                {
                    return(null);
                }

                // We want to dismiss the session if the caret ever moved outside our bounds.
                // Do this before we check the _filterId.  We don't want this work to not happen
                // just because the user typed more text and added more filter items.
                if (filterReason == CompletionFilterReason.CaretPositionChanged &&
                    Controller.IsCaretOutsideAllItemBounds(model, caretPosition))
                {
                    return(null);
                }

                // If the UI specified an updated filter state, then incorporate that
                // into our model. Do this before we check the _filterId.  We don't
                // want this work to not happen just because the user typed more text
                // and added more filter items.
                if (filterState != null)
                {
                    model = model.WithFilterState(filterState);
                }

                // If there's another request in the queue to filter items, then just
                // bail out immediately.  No point in doing extra work that's just
                // going to be overridden by the next filter task.
                if (id != _filterId)
                {
                    return(model);
                }

                var textSnapshot   = caretPosition.Snapshot;
                var textSpanToText = new Dictionary <TextSpan, string>();

                var document = this.Controller.GetDocument();
                var helper   = this.Controller.GetCompletionHelper();

                var recentItems = this.Controller.GetRecentItems();

                var filterResults = new List <FilterResult>();

                var filterText = model.GetCurrentTextInSnapshot(
                    model.OriginalList.Span, textSnapshot, textSpanToText);

                // 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.
                var filterTextStartsWithANumber = filterText.Length > 0 && char.IsNumber(filterText[0]);

                if (filterTextStartsWithANumber)
                {
                    if (!IsAfterDot(model, textSnapshot, textSpanToText))
                    {
                        return(null);
                    }
                }

                var effectiveFilterItemState = ComputeEffectiveFilterItemState(model);

                foreach (var currentItem in model.TotalItems)
                {
                    // Check if something new has happened and there's a later on filter operation
                    // in the chain.  If so, there's no need for us to do any more work (as it will
                    // just be superceded by the later work).
                    if (id != _filterId)
                    {
                        return(model);
                    }

                    if (CompletionItemFilter.ShouldBeFilteredOutOfCompletionList(
                            currentItem, effectiveFilterItemState))
                    {
                        continue;
                    }

                    // Check if the item matches the filter text typed so far.
                    var matchesFilterText = MatchesFilterText(helper, currentItem, filterText, model.Trigger, filterReason, recentItems);

                    if (matchesFilterText)
                    {
                        filterResults.Add(new FilterResult(
                                              currentItem, 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.

                        var wasTriggeredByDeleteOrSimpleInvoke =
                            model.Trigger.Kind == CompletionTriggerKind.Deletion ||
                            model.Trigger.Kind == CompletionTriggerKind.Invoke;
                        var shouldKeepItem = filterText.Length <= 1 || wasTriggeredByDeleteOrSimpleInvoke;

                        if (shouldKeepItem)
                        {
                            filterResults.Add(new FilterResult(
                                                  currentItem, filterText, matchedFilterText: false));
                        }
                    }
                }

                model = model.WithFilterText(filterText);

                // If no items matched the filter text then determine what we should do.
                if (filterResults.Count == 0)
                {
                    return(HandleAllItemsFilteredOut(model, filterReason));
                }

                // If this was deletion, then we control the entire behavior of deletion
                // ourselves.
                if (model.Trigger.Kind == CompletionTriggerKind.Deletion)
                {
                    return(HandleDeletionTrigger(model, filterReason, filterResults));
                }

                return(HandleNormalFiltering(
                           model, document, filterReason, caretPosition,
                           helper, recentItems, filterText, filterResults));
            }
            private Model FilterModelInBackgroundWorker(
                Model model,
                int id,
                SnapshotPoint caretPosition,
                bool recheckCaretPosition,
                bool dismissIfEmptyAllowed,
                CompletionFilterReason filterReason)
            {
                if (model == null)
                {
                    return null;
                }

                // We want to dismiss the session if the caret ever moved outside our bounds.
                // Do this before we check the _filterId.  We don't want this work to not happen
                // just because the user typed more text and added more filter items.
                if (recheckCaretPosition && Controller.IsCaretOutsideAllItemBounds(model, caretPosition))
                {
                    return null;
                }

                if (id != _filterId)
                {
                    return model;
                }

                var textSnapshot = caretPosition.Snapshot;
                var textSpanToText = new Dictionary<TextSpan, string>();

                var document = this.Controller.GetDocument();
                var helper = this.Controller.GetCompletionHelper();

                var recentItems = this.Controller.GetRecentItems();

                var filterResults = new List<FilterResult>();

                var filterText = model.GetCurrentTextInSnapshot(
                    model.OriginalList.Span, textSnapshot, textSpanToText);

                // 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.
                var filterTextStartsWithANumber = filterText.Length > 0 && char.IsNumber(filterText[0]);
                if (filterTextStartsWithANumber)
                {
                    if (!IsAfterDot(model, textSnapshot, textSpanToText))
                    {
                        return null;
                    }
                }

                var effectiveFilterItemState = ComputeEffectiveFilterItemState(model);
                foreach (var currentItem in model.TotalItems)
                {
                    // Check if something new has happened and there's a later on filter operation
                    // in the chain.  If so, there's no need for us to do any more work (as it will
                    // just be superceded by the later work).
                    if (id != _filterId)
                    {
                        return model;
                    }

                    if (ItemIsFilteredOut(currentItem, effectiveFilterItemState))
                    {
                        continue;
                    }

                    // Check if the item matches the filter text typed so far.
                    var matchesFilterText = MatchesFilterText(helper, currentItem, filterText, model.Trigger, filterReason, recentItems);

                    if (matchesFilterText)
                    {
                        filterResults.Add(new FilterResult(
                            currentItem, filterText, matchedFilterText: true));
                    }
                    else
                    {
                        if (filterText.Length <= 1)
                        {
                            // Even though the rule provider didn't match this, we'll still include it
                            // since we want to allow a user typing a single character and seeing all
                            // possibly completions.
                            filterResults.Add(new FilterResult(
                                currentItem, filterText, matchedFilterText: false));
                        }
                    }
                }

                model = model.WithFilterText(filterText);

                // If no items matched the filter text then determine what we should do.
                if (filterResults.Count == 0)
                {
                    return HandleAllItemsFilteredOut(model, filterReason, dismissIfEmptyAllowed);
                }

                // If this was deletion, then we control the entire behavior of deletion
                // ourselves.
                if (model.Trigger.Kind == CompletionTriggerKind.Deletion)
                {
                    return HandleDeletionTrigger(model, filterResults);
                }

                return HandleNormalFiltering(
                    model, document, filterReason, caretPosition,
                    helper, recentItems, filterText, filterResults);
            }