private Model SetModelSelectedItemInBackground(
                Model model,
                Func<Model, CompletionItem> selector)
            {
                if (model == null)
                {
                    return null;
                }

                // Switch to hard selection.
                var selectedItem = selector(model);
                Contract.ThrowIfFalse(model.TotalItems.Contains(selectedItem) || model.DefaultBuilder == selectedItem);

                if (model.FilteredItems.Contains(selectedItem))
                {
                    // Easy case, just set the selected item that's already in the filtered items
                    // list.

                    return model.WithSelectedItem(selector(model))
                                .WithHardSelection(true);
                }
                else
                {
                    // Item wasn't in the filtered list, so we need to recreate the filtered list
                    // with that item in it.
                    var filteredItemsSet = new HashSet<CompletionItem>(model.FilteredItems,
                        ReferenceEqualityComparer.Instance);

                    var newFilteredItems = model.TotalItems.Where(
                        i => filteredItemsSet.Contains(i) || i == selectedItem).ToList();
                    return model.WithFilteredItems(newFilteredItems)
                                .WithSelectedItem(selectedItem)
                                .WithHardSelection(true);
                }
            }
            private static Model HandleAllItemsFilteredOut(
                Model model,
                CompletionFilterReason filterReason)
            {
                if (model.DismissIfEmpty &&
                    filterReason == CompletionFilterReason.Insertion)
                {
                    // If the user was just typing, and the list went to empty *and* this is a
                    // language that wants to dismiss on empty, then just return a null model
                    // to stop the completion session.
                    return(null);
                }

                if (model.FilterState?.Values.Any(b => b) == true)
                {
                    // If the user has turned on some filtering states, and we filtered down to
                    // nothing, then we do want the UI to show that to them.  That way the user
                    // can turn off filters they don't want and get the right set of items.
                    return(model.WithFilteredItems(ImmutableArray <CompletionItem> .Empty)
                           .WithFilterText("")
                           .WithHardSelection(false)
                           .WithIsUnique(false));
                }
                else
                {
                    // If we are going to filter everything out, then just preserve the existing
                    // model (and all the previously filtered items), but switch over to soft
                    // selection.
                    return(model.WithHardSelection(false)
                           .WithIsUnique(false));
                }
            }
            private Model HandleDeletionTrigger(
                Model model, CompletionFilterReason filterReason, List <FilterResult> filterResults)
            {
                if (filterReason == CompletionFilterReason.Insertion &&
                    !filterResults.Any(r => r.MatchedFilterText))
                {
                    // The user has typed something, but nothing in the actual list matched what
                    // they were typing.  In this case, we want to dismiss completion entirely.
                    // The thought process is as follows: we aggressively brough up completion
                    // to help them when they typed delete (in case they wanted to pick another
                    // item).  However, they're typing something that doesn't seem to match at all
                    // The completion list is just distracting at this point.
                    return(null);
                }

                FilterResult?bestFilterResult = null;
                int          matchCount       = 0;

                foreach (var currentFilterResult in filterResults.Where(r => r.MatchedFilterText))
                {
                    if (bestFilterResult == null ||
                        IsBetterDeletionMatch(currentFilterResult, bestFilterResult.Value))
                    {
                        // We had no best result yet, so this is now our best result.
                        bestFilterResult = currentFilterResult;
                        matchCount++;
                    }
                }

                // If we had a matching item, then pick the best of the matching items and
                // choose that one to be hard selected.  If we had no actual matching items
                // (which can happen if the user deletes down to a single character and we
                // include everything), then we just soft select the first item.

                var filteredItems = filterResults.Select(r => r.CompletionItem).AsImmutable();

                model = model.WithFilteredItems(filteredItems);

                if (bestFilterResult != null)
                {
                    // Only hard select this result if it's a prefix match
                    // We need to do this so that
                    // * deleting and retyping a dot in a member access does not change the
                    //   text that originally appeared before the dot
                    // * deleting through a word from the end keeps that word selected
                    // This also preserves the behavior the VB had through Dev12.
                    var hardSelect = bestFilterResult.Value.CompletionItem.FilterText.StartsWith(model.FilterText, StringComparison.CurrentCultureIgnoreCase);
                    return(model.WithSelectedItem(bestFilterResult.Value.CompletionItem)
                           .WithHardSelection(hardSelect)
                           .WithIsUnique(matchCount == 1));
                }
                else
                {
                    return(model.WithHardSelection(false)
                           .WithIsUnique(false));
                }
            }
Beispiel #4
0
            private Model HandleNormalFiltering(
                Model model,
                Document document,
                CompletionFilterReason filterReason,
                ITextSnapshot textSnapshot,
                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();

                if (service == null)
                {
                    return(null);
                }

                var matchingCompletionItems = filterResults.Where(r => r.MatchedFilterText)
                                              .Select(t => t.PresentationItem.Item)
                                              .AsImmutable();
                var chosenItems = service.FilterItems(
                    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);
            }
            private Model HandleDeletionTrigger(
                Model model, CompletionFilterReason filterReason, List <FilterResult> filterResults)
            {
                if (filterReason == CompletionFilterReason.Insertion &&
                    !filterResults.Any(r => r.MatchedFilterText))
                {
                    // The user has typed something, but nothing in the actual list matched what
                    // they were typing.  In this case, we want to dismiss completion entirely.
                    // The thought process is as follows: we aggressively brough up completion
                    // to help them when they typed delete (in case they wanted to pick another
                    // item).  However, they're typing something that doesn't seem to match at all
                    // The completion list is just distracting at this point.
                    return(null);
                }

                FilterResult?bestFilterResult = null;
                int          matchCount       = 0;

                foreach (var currentFilterResult in filterResults.Where(r => r.MatchedFilterText))
                {
                    if (bestFilterResult == null ||
                        IsBetterDeletionMatch(currentFilterResult, bestFilterResult.Value))
                    {
                        // We had no best result yet, so this is now our best result.
                        bestFilterResult = currentFilterResult;
                        matchCount++;
                    }
                }

                // If we had a matching item, then pick the best of the matching items and
                // choose that one to be hard selected.  If we had no actual matching items
                // (which can happen if the user deletes down to a single character and we
                // include everything), then we just soft select the first item.

                var filteredItems = filterResults.Select(r => r.CompletionItem).AsImmutable();

                model = model.WithFilteredItems(filteredItems);

                if (bestFilterResult != null)
                {
                    return(model.WithSelectedItem(bestFilterResult.Value.CompletionItem)
                           .WithHardSelection(true)
                           .WithIsUnique(matchCount == 1));
                }
                else
                {
                    return(model.WithHardSelection(false)
                           .WithIsUnique(false));
                }
            }
Beispiel #6
0
            private Model HandleDeletionTrigger(Model model, List <FilterResult> filterResults)
            {
                FilterResult?bestFilterResult = null;
                int          matchCount       = 0;

                foreach (var currentFilterResult in filterResults.Where(r => r.MatchedFilterText))
                {
                    if (bestFilterResult == null ||
                        IsBetterDeletionMatch(currentFilterResult, bestFilterResult.Value))
                    {
                        // We had no best result yet, so this is now our best result.
                        bestFilterResult = currentFilterResult;
                        matchCount++;
                    }
                }

                // If we had a matching item, then pick the best of the matching items and
                // choose that one to be hard selected.  If we had no actual matching items
                // (which can happen if the user deletes down to a single character and we
                // include everything), then we just soft select the first item.

                var filteredItems = filterResults.Select(r => r.CompletionItem).AsImmutable();

                model = model.WithFilteredItems(filteredItems);

                if (bestFilterResult != null)
                {
                    return(model.WithSelectedItem(bestFilterResult.Value.CompletionItem)
                           .WithHardSelection(true)
                           .WithIsUnique(matchCount == 1));
                }
                else
                {
                    return(model.WithHardSelection(false)
                           .WithIsUnique(false));
                }
            }
            private Model SetModelSelectedItemInBackground(
                Model model,
                Func <Model, PresentationItem> selector)
            {
                if (model == null)
                {
                    return(null);
                }

                // Switch to hard selection.
                var selectedItem = selector(model);

                Contract.ThrowIfFalse(model.TotalItems.Contains(selectedItem) || model.DefaultSuggestionModeItem == selectedItem);

                if (model.FilteredItems.Contains(selectedItem))
                {
                    // Easy case, just set the selected item that's already in the filtered items
                    // list.

                    return(model.WithSelectedItem(selector(model))
                           .WithHardSelection(true));
                }
                else
                {
                    // Item wasn't in the filtered list, so we need to recreate the filtered list
                    // with that item in it.
                    var filteredItemsSet = new HashSet <PresentationItem>(model.FilteredItems,
                                                                          ReferenceEqualityComparer.Instance);

                    var newFilteredItems = model.TotalItems.Where(
                        i => filteredItemsSet.Contains(i) || i == selectedItem).ToImmutableArrayOrEmpty();
                    return(model.WithFilteredItems(newFilteredItems)
                           .WithSelectedItem(selectedItem)
                           .WithHardSelection(true));
                }
            }
Beispiel #8
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 allFilteredItems = new List <PresentationItem>();
                var textSpanToText   = new Dictionary <TextSpan, string>();
                var helper           = this.Controller.GetCompletionHelper();

                // isUnique tracks if there is a single
                bool?            isUnique        = null;
                PresentationItem bestFilterMatch = null;
                bool             filterTextIsPotentialIdentifier = false;

                var recentItems = this.Controller.GetRecentItems();

                var itemToFilterText = new Dictionary <CompletionItem, string>();

                model = model.WithCompletionItemToFilterText(itemToFilterText);

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

                    // We may have wrapped some items in the list in DescriptionModifying items,
                    // but we should use the actual underlying items when filtering. That way
                    // our rules can access the underlying item's provider.

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

                    var filterText        = model.GetCurrentTextInSnapshot(currentItem.Item.Span, textSnapshot, textSpanToText);
                    var matchesFilterText = helper.MatchesFilterText(currentItem.Item, filterText, model.Trigger, filterReason, recentItems);
                    itemToFilterText[currentItem.Item] = filterText;

                    if (matchesFilterText)
                    {
                        allFilteredItems.Add(currentItem);

                        // If we have no best match, or this match is better than the last match,
                        // then the current item is the best filter match.
                        if (bestFilterMatch == null ||
                            helper.IsBetterFilterMatch(currentItem.Item, bestFilterMatch.Item, filterText, model.Trigger, filterReason, recentItems))
                        {
                            bestFilterMatch = currentItem;
                        }

                        // If isUnique is null, then this is the first time we've seen an item that
                        // matches the filter text.  That item is now considered unique.  However, if
                        // isUnique is non-null, then this is the second (or third, or fourth, etc.)
                        // that a provider said to include. It's no longer unique.
                        //
                        // Note: We only want to do this if any filter text was actually provided.
                        // This is so we can handle the following cases properly:
                        //
                        //    Console.WriteLi$$
                        //
                        // If they try to commit unique item there, we want to commit to
                        // "WriteLine".  However, if they just have:
                        //
                        //    Console.$$
                        //
                        // And they try to commit unique item, we won't commit something just
                        // because it was in the MRU list.
                        if (filterText != string.Empty)
                        {
                            isUnique = isUnique == null || false;
                        }
                    }
                    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.  However, we don't consider it either unique or a
                            // filter match, so we won't select it.
                            allFilteredItems.Add(currentItem);
                        }

                        // We want to dismiss the list if the user is typing a # and nothing matches
                        filterTextIsPotentialIdentifier = filterTextIsPotentialIdentifier ||
                                                          filterText.Length == 0 ||
                                                          (!char.IsDigit(filterText[0]) && filterText[0] != '-' && filterText[0] != '.');
                    }
                }

                if (!filterTextIsPotentialIdentifier && bestFilterMatch == null)
                {
                    // We had no matches, and the user is typing a #, dismiss the list
                    return(null);
                }

                if (allFilteredItems.Count == 0)
                {
                    if (dismissIfEmptyAllowed &&
                        model.DismissIfEmpty &&
                        filterReason != CompletionFilterReason.BackspaceOrDelete)
                    {
                        return(null);
                    }

                    if (model.FilterState != null && model.FilterState.Values.Any(b => b))
                    {
                        // If the user has turned on some filtering states, and we filtered down to
                        // nothing, then we do want the UI to show that to them.
                        return(model.WithFilteredItems(allFilteredItems.ToImmutableArray())
                               .WithHardSelection(false)
                               .WithIsUnique(false));
                    }
                    else
                    {
                        // If we are going to filter everything out, then just preserve the existing
                        // model, but switch over to soft selection.  Also, nothing is unique at that
                        // point.
                        return(model.WithHardSelection(false)
                               .WithIsUnique(false));
                    }
                }

                // If we have a best item, then select it.  Otherwise just use the first item
                // in the list.
                var selectedItem = bestFilterMatch ?? allFilteredItems.First();

                // If we have a best item, then we want to hard select it.  Otherwise we want
                // soft selection.  However, no hard selection if there's a builder.
                var hardSelection = IsHardSelection(model, bestFilterMatch, textSnapshot, helper, model.Trigger, filterReason);

                var result = model.WithFilteredItems(allFilteredItems.ToImmutableArray())
                             .WithSelectedItem(selectedItem)
                             .WithHardSelection(hardSelection)
                             .WithIsUnique(isUnique.HasValue && isUnique.Value);

                return(result);
            }
            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 allFilteredItems = new List<PresentationItem>();
                var textSpanToText = new Dictionary<TextSpan, string>();
                var helper = this.Controller.GetCompletionHelper();

                // isUnique tracks if there is a single 
                bool? isUnique = null;
                PresentationItem bestFilterMatch = null;
                bool filterTextIsPotentialIdentifier = false;

                var recentItems = this.Controller.GetRecentItems();

                var itemToFilterText = new Dictionary<CompletionItem, string>();
                model = model.WithCompletionItemToFilterText(itemToFilterText);

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

                    // We may have wrapped some items in the list in DescriptionModifying items,
                    // but we should use the actual underlying items when filtering. That way
                    // our rules can access the underlying item's provider.

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

                    var filterText = model.GetCurrentTextInSnapshot(currentItem.Item.Span, textSnapshot, textSpanToText);
                    var matchesFilterText = MatchesFilterText(helper, currentItem.Item, filterText, model.Trigger, filterReason, recentItems);
                    itemToFilterText[currentItem.Item] = filterText;

                    if (matchesFilterText)
                    {
                        allFilteredItems.Add(currentItem);

                        // If we have no best match, or this match is better than the last match,
                        // then the current item is the best filter match.
                        if (bestFilterMatch == null ||
                            IsBetterFilterMatch(helper, currentItem.Item, bestFilterMatch.Item, filterText, model.Trigger, filterReason, recentItems))
                        {
                            bestFilterMatch = currentItem;
                        }

                        // If isUnique is null, then this is the first time we've seen an item that
                        // matches the filter text.  That item is now considered unique.  However, if
                        // isUnique is non-null, then this is the second (or third, or fourth, etc.)
                        // that a provider said to include. It's no longer unique.
                        //
                        // Note: We only want to do this if any filter text was actually provided.
                        // This is so we can handle the following cases properly:
                        //
                        //    Console.WriteLi$$
                        //
                        // If they try to commit unique item there, we want to commit to
                        // "WriteLine".  However, if they just have:
                        //
                        //    Console.$$
                        //
                        // And they try to commit unique item, we won't commit something just
                        // because it was in the MRU list.
                        if (filterText != string.Empty)
                        {
                            isUnique = isUnique == null || false;
                        }
                    }
                    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.  However, we don't consider it either unique or a
                            // filter match, so we won't select it.
                            allFilteredItems.Add(currentItem);
                        }

                        // We want to dismiss the list if the user is typing a # and nothing matches
                        filterTextIsPotentialIdentifier = filterTextIsPotentialIdentifier ||
                            filterText.Length == 0 ||
                            (!char.IsDigit(filterText[0]) && filterText[0] != '-' && filterText[0] != '.');
                    }
                }

                if (!filterTextIsPotentialIdentifier && bestFilterMatch == null)
                {
                    // We had no matches, and the user is typing a #, dismiss the list
                    return null;
                }

                if (allFilteredItems.Count == 0)
                {
                    if (dismissIfEmptyAllowed &&
                        model.DismissIfEmpty &&
                        filterReason != CompletionFilterReason.BackspaceOrDelete)
                    {
                        return null;
                    }

                    if (model.FilterState != null && model.FilterState.Values.Any(b => b))
                    {
                        // If the user has turned on some filtering states, and we filtered down to 
                        // nothing, then we do want the UI to show that to them.
                        return model.WithFilteredItems(allFilteredItems.ToImmutableArray())
                                    .WithHardSelection(false)
                                    .WithIsUnique(false);
                    }
                    else
                    {
                        // If we are going to filter everything out, then just preserve the existing
                        // model, but switch over to soft selection.  Also, nothing is unique at that
                        // point.
                        return model.WithHardSelection(false)
                                    .WithIsUnique(false);
                    }
                }

                // If we have a best item, then select it.  Otherwise just use the first item
                // in the list.
                var selectedItem = bestFilterMatch ?? allFilteredItems.First();

                // If we have a best item, then we want to hard select it.  Otherwise we want
                // soft selection.  However, no hard selection if there's a builder.
                var hardSelection = IsHardSelection(model, bestFilterMatch, textSnapshot, helper, model.Trigger, filterReason);

                var result = model.WithFilteredItems(allFilteredItems.ToImmutableArray())
                                  .WithSelectedItem(selectedItem)
                                  .WithHardSelection(hardSelection)
                                  .WithIsUnique(isUnique.HasValue && isUnique.Value);

                return result;
            }
            private static Model HandleAllItemsFilteredOut(
                Model model,
                CompletionFilterReason filterReason,
                bool dismissIfEmptyAllowed)
            {
                if (dismissIfEmptyAllowed &&
                    model.DismissIfEmpty &&
                    filterReason == CompletionFilterReason.TypeChar)
                {
                    // If the user was just typing, and the list went to empty *and* this is a 
                    // language that wants to dismiss on empty, then just return a null model
                    // to stop the completion session.
                    return null;
                }

                if (model.FilterState?.Values.Any(b => b) == true)
                {
                    // If the user has turned on some filtering states, and we filtered down to 
                    // nothing, then we do want the UI to show that to them.  That way the user
                    // can turn off filters they don't want and get the right set of items.
                    return model.WithFilteredItems(ImmutableArray<PresentationItem>.Empty)
                                .WithFilterText("")
                                .WithHardSelection(false)
                                .WithIsUnique(false);
                }
                else
                {
                    // If we are going to filter everything out, then just preserve the existing
                    // model (and all the previously filtered items), but switch over to soft 
                    // selection.
                    return model.WithHardSelection(false)
                                .WithIsUnique(false);
                }
            }
            private Model HandleDeletionTrigger(Model model, List<FilterResult> filterResults)
            {
                FilterResult? bestFilterResult = null;
                int matchCount = 0;
                foreach (var currentFilterResult in filterResults.Where(r => r.MatchedFilterText))
                {
                    if (bestFilterResult == null ||
                        IsBetterDeletionMatch(currentFilterResult, bestFilterResult.Value))
                    {
                        // We had no best result yet, so this is now our best result.
                        bestFilterResult = currentFilterResult;
                        matchCount++;
                    }
                }

                // If we had a matching item, then pick the best of the matching items and
                // choose that one to be hard selected.  If we had no actual matching items
                // (which can happen if the user deletes down to a single character and we
                // include everything), then we just soft select the first item.

                var filteredItems = filterResults.Select(r => r.PresentationItem).AsImmutable();
                model = model.WithFilteredItems(filteredItems);

                if (bestFilterResult != null)
                {
                    return model.WithSelectedItem(bestFilterResult.Value.PresentationItem)
                                .WithHardSelection(true)
                                .WithIsUnique(matchCount == 1);
                }
                else
                {
                    return model.WithHardSelection(false)
                                .WithIsUnique(false);
                }
            }
            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;
            }