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