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 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 SetModelBuilderStateInBackground( Model model, bool includeBuilder) { if (model == null) { return null; } // We want to soft select if the user is switching the builder on, or if we were // already in soft select mode. var softSelect = includeBuilder || model.IsSoftSelection; // If the selected item is the builder, select the first filtered item instead. if (model.SelectedItem == model.DefaultSuggestionModeItem) { return model.WithSelectedItem(model.FilteredItems.First()) .WithHardSelection(!softSelect); } return model.WithHardSelection(!softSelect).WithUseSuggestionMode(includeBuilder); }
private Model SetModelBuilderStateInBackground( Model model, bool includeBuilder) { if (model == null) { return(null); } // We want to soft select if the user is switching the builder on, or if we were // already in soft select mode. var softSelect = includeBuilder || model.IsSoftSelection; // If the selected item is the builder, select the first filtered item instead. if (model.SelectedItem == model.DefaultBuilder) { return(model.WithSelectedItem(model.FilteredItems.First()) .WithHardSelection(!softSelect)); } return(model.WithHardSelection(!softSelect).WithUseSuggestionCompletionMode(includeBuilder)); }
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 SetModelBuilderStateInBackground( Model model, bool includeBuilder) { if (model == null) { return(null); } // We want to soft select if the user is switching the builder on, or if we were // already in soft select mode. var softSelect = includeBuilder || model.IsSoftSelection; if (model.SelectedItemOpt == model.SuggestionModeItem && !includeBuilder) { // Use had the builder selected, but turned off the builder. Switch to the // first filtered item. model = model.WithSelectedItem(model.FilteredItems.First()); } return(model.WithHardSelection(!softSelect) .WithUseSuggestionMode(includeBuilder)); }
private Model SetModelBuilderStateInBackground( Model model, bool includeBuilder) { if (model == null) { return null; } // We want to soft select if the user is switching the builder on, or if we were // already in soft select mode. var softSelect = includeBuilder || model.IsSoftSelection; if (model.SelectedItem == model.SuggestionModeItem && !includeBuilder) { // Use had the builder selected, but turned off the builder. Switch to the // first filtered item. model = model.WithSelectedItem(model.FilteredItems.First()); } return model.WithHardSelection(!softSelect) .WithUseSuggestionMode(includeBuilder); }
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); } }