private bool IsCaretOutsideItemBounds( Model model, SnapshotPoint caretPoint, PresentationItem item, Dictionary<TextSpan, string> textSpanToText, Dictionary<TextSpan, ViewTextSpan> textSpanToViewSpan) { // Easy first check. See if the caret point is before the start of the item. ViewTextSpan filterSpanInViewBuffer; if (!textSpanToViewSpan.TryGetValue(item.Item.Span, out filterSpanInViewBuffer)) { filterSpanInViewBuffer = model.GetViewBufferSpan(item.Item.Span); textSpanToViewSpan[item.Item.Span] = filterSpanInViewBuffer; } if (caretPoint < filterSpanInViewBuffer.TextSpan.Start) { return true; } var textSnapshot = caretPoint.Snapshot; var currentText = model.GetCurrentTextInSnapshot(item.Item.Span, textSnapshot, textSpanToText); var currentTextSpan = new TextSpan(filterSpanInViewBuffer.TextSpan.Start, currentText.Length); return !currentTextSpan.IntersectsWith(caretPoint); }
private bool IsFilterTextEmpty( Model model, SnapshotPoint caretPoint, PresentationItem item, Dictionary <TextSpan, string> textSpanToText, Dictionary <TextSpan, ViewTextSpan> textSpanToViewSpan) { // Easy first check. See if the caret point is before the start of the item. ViewTextSpan filterSpanInViewBuffer; if (!textSpanToViewSpan.TryGetValue(item.Item.Span, out filterSpanInViewBuffer)) { filterSpanInViewBuffer = model.GetViewBufferSpan(item.Item.Span); textSpanToViewSpan[item.Item.Span] = filterSpanInViewBuffer; } if (caretPoint < filterSpanInViewBuffer.TextSpan.Start) { return(true); } var textSnapshot = caretPoint.Snapshot; var currentText = model.GetCurrentTextInSnapshot(item.Item.Span, textSnapshot, textSpanToText); var currentTextSpan = new TextSpan(filterSpanInViewBuffer.TextSpan.Start, currentText.Length); return(currentText.Length == 0); }
public Model WithSuggestionModeItem(PresentationItem suggestionModeItem) { return(suggestionModeItem == this.SuggestionModeItem ? this : new Model(_disconnectedBufferGraph, OriginalList, TotalItems, FilteredItems, SelectedItem, CompletionItemFilters, FilterState, CompletionItemToFilterText, IsHardSelection, IsUnique, UseSuggestionMode, suggestionModeItem, DefaultSuggestionModeItem, Trigger, CommitTrackingSpanEndPoint, DismissIfEmpty)); }
public CustomCommitCompletion( CompletionPresenterSession completionPresenterSession, PresentationItem presentationItem) : base() { // PERF: Note that the base class contains a constructor taking the displayText string // but we're intentionally NOT using that here because it allocates a private CompletionState // object. By overriding the public property getters (DisplayText, InsertionText, etc.) the // extra allocation is avoided. _completionPresenterSession = completionPresenterSession; this.PresentationItem = presentationItem; _imageMoniker = ImageMonikers.GetImageMoniker(PresentationItem.Item.Tags, presentationItem.CompletionService.Language); }
public CustomCommitCompletion( CompletionPresenterSession completionPresenterSession, PresentationItem presentationItem) : base() { // PERF: Note that the base class contains a constructor taking the displayText string // but we're intentionally NOT using that here because it allocates a private CompletionState // object. By overriding the public property getters (DisplayText, InsertionText, etc.) the // extra allocation is avoided. _completionPresenterSession = completionPresenterSession; this.PresentationItem = presentationItem; _imageMoniker = ImageMonikers.GetImageMoniker(PresentationItem.Item.Tags); }
private VSCompletion GetVSCompletion(PresentationItem item) { VSCompletion value; if (!_presentationItemMap.TryGetValue(item, out value)) { value = new CustomCommitCompletion( _completionPresenterSession, item); _presentationItemMap.Add(item, value); } return(value); }
private bool IsHardSelection( Model model, PresentationItem bestFilterMatch, ITextSnapshot textSnapshot, CompletionHelper completionRules, CompletionTrigger trigger, CompletionFilterReason reason) { if (model.SuggestionModeItem != null) { return(bestFilterMatch != null && bestFilterMatch.Item.DisplayText == model.SuggestionModeItem.Item.DisplayText); } if (bestFilterMatch == null || model.UseSuggestionMode) { return(false); } // We don't have a builder and we have a best match. Normally this will be hard // selected, except for a few cases. Specifically, if no filter text has been // provided, and this is not a preselect match then we will soft select it. This // happens when the completion list comes up implicitly and there is something in // the MRU list. In this case we do want to select it, but not with a hard // selection. Otherwise you can end up with the following problem: // // dim i as integer =<space> // // Completion will comes up after = with 'integer' selected (Because of MRU). We do // not want 'space' to commit this. var viewSpan = model.GetViewBufferSpan(bestFilterMatch.Item.Span); var fullFilterText = model.GetCurrentTextInSnapshot(viewSpan, textSnapshot, endPoint: null); var shouldSoftSelect = completionRules.ShouldSoftSelectItem(bestFilterMatch.Item, fullFilterText, trigger); if (shouldSoftSelect) { return(false); } // If the user moved the caret left after they started typing, the 'best' match may not match at all // against the full text span that this item would be replacing. if (!completionRules.MatchesFilterText(bestFilterMatch.Item, fullFilterText, trigger, reason, this.Controller.GetRecentItems())) { return(false); } // There was either filter text, or this was a preselect match. In either case, we // can hard select this. return(true); }
private void CommitItem(PresentationItem item) { AssertIsForeground(); // We should not be getting called if we didn't even have a computation running. Contract.ThrowIfNull(this.sessionOpt); Contract.ThrowIfNull(this.sessionOpt.Computation.InitialUnfilteredModel); // If the selected item is the builder, there's not actually any work to do to commit if (item.IsSuggestionModeItem) { this.StopModelComputation(); return; } this.Commit(item, this.sessionOpt.Computation.InitialUnfilteredModel, commitChar: null); }
private void Commit(PresentationItem item, Model model, char?commitChar) { AssertIsForeground(); // We could only be called if we had a model at this point. Contract.ThrowIfNull(model); // Now that we've captured the model at this point, we can stop ourselves entirely. // This is also desirable as we may have reentrancy problems when we call into // custom commit completion providers. I.e. if the custom provider moves the caret, // then we do not want to process that move as it may put us into an unexpected state. // // TODO(cyrusn): We still have a general reentrancy problem where calling into a custom // commit provider (or just calling into the editor) may cause something to call back // into us. However, for now, we just hope that no such craziness will occur. this.StopModelComputation(); Commit(item, model, commitChar, CancellationToken.None); }
private void Commit(PresentationItem item, Model model, char? commitChar) { AssertIsForeground(); // We could only be called if we had a model at this point. Contract.ThrowIfNull(model); // Now that we've captured the model at this point, we can stop ourselves entirely. // This is also desirable as we may have reentrancy problems when we call into // custom commit completion providers. I.e. if the custom provider moves the caret, // then we do not want to process that move as it may put us into an unexpected state. // // TODO(cyrusn): We still have a general reentrancy problem where calling into a custom // commit provider (or just calling into the editor) may cause something to call back // into us. However, for now, we just hope that no such craziness will occur. this.StopModelComputation(); Commit(item, model, commitChar, CancellationToken.None); }
private Model( DisconnectedBufferGraph disconnectedBufferGraph, CompletionList originalList, ImmutableArray<PresentationItem> totalItems, ImmutableArray<PresentationItem> filteredItems, PresentationItem selectedItem, ImmutableArray<CompletionItemFilter> completionItemFilters, ImmutableDictionary<CompletionItemFilter, bool> filterState, IReadOnlyDictionary<CompletionItem, string> completionItemToFilterText, bool isHardSelection, bool isUnique, bool useSuggestionMode, PresentationItem suggestionModeItem, PresentationItem defaultSuggestionModeItem, CompletionTrigger trigger, ITrackingPoint commitSpanEndPoint, bool dismissIfEmpty) { Contract.ThrowIfNull(selectedItem); Contract.ThrowIfFalse(totalItems.Length != 0, "Must have at least one item."); Contract.ThrowIfFalse(filteredItems.Length != 0, "Must have at least one filtered item."); Contract.ThrowIfFalse(filteredItems.Contains(selectedItem) || defaultSuggestionModeItem == selectedItem, "Selected item must be in filtered items."); _disconnectedBufferGraph = disconnectedBufferGraph; this.OriginalList = originalList; this.TotalItems = totalItems; this.FilteredItems = filteredItems; this.FilterState = filterState; this.SelectedItem = selectedItem; this.CompletionItemFilters = completionItemFilters; this.CompletionItemToFilterText = completionItemToFilterText; this.IsHardSelection = isHardSelection; this.IsUnique = isUnique; this.UseSuggestionMode = useSuggestionMode; this.SuggestionModeItem = suggestionModeItem; this.DefaultSuggestionModeItem = defaultSuggestionModeItem; this.Trigger = trigger; this.CommitTrackingSpanEndPoint = commitSpanEndPoint; this.DismissIfEmpty = dismissIfEmpty; }
private Model( DisconnectedBufferGraph disconnectedBufferGraph, CompletionList originalList, ImmutableArray <PresentationItem> totalItems, ImmutableArray <PresentationItem> filteredItems, PresentationItem selectedItem, ImmutableArray <CompletionItemFilter> completionItemFilters, ImmutableDictionary <CompletionItemFilter, bool> filterState, IReadOnlyDictionary <CompletionItem, string> completionItemToFilterText, bool isHardSelection, bool isUnique, bool useSuggestionMode, PresentationItem suggestionModeItem, PresentationItem defaultSuggestionModeItem, CompletionTrigger trigger, ITrackingPoint commitSpanEndPoint, bool dismissIfEmpty) { Contract.ThrowIfNull(selectedItem); Contract.ThrowIfFalse(totalItems.Length != 0, "Must have at least one item."); Contract.ThrowIfFalse(filteredItems.Length != 0, "Must have at least one filtered item."); Contract.ThrowIfFalse(filteredItems.Contains(selectedItem) || defaultSuggestionModeItem == selectedItem, "Selected item must be in filtered items."); _disconnectedBufferGraph = disconnectedBufferGraph; this.OriginalList = originalList; this.TotalItems = totalItems; this.FilteredItems = filteredItems; this.FilterState = filterState; this.SelectedItem = selectedItem; this.CompletionItemFilters = completionItemFilters; this.CompletionItemToFilterText = completionItemToFilterText; this.IsHardSelection = isHardSelection; this.IsUnique = isUnique; this.UseSuggestionMode = useSuggestionMode; this.SuggestionModeItem = suggestionModeItem; this.DefaultSuggestionModeItem = defaultSuggestionModeItem; this.Trigger = trigger; this.CommitTrackingSpanEndPoint = commitSpanEndPoint; this.DismissIfEmpty = dismissIfEmpty; }
public Model WithSelectedItem(PresentationItem selectedItem) { return With(selectedItem: selectedItem); }
private void Commit( PresentationItem item, Model model, char? commitChar, ITextSnapshot initialTextSnapshot, Action nextHandler) { AssertIsForeground(); // We could only be called if we had a model at this point. Contract.ThrowIfNull(model); // Now that we've captured the model at this point, we can stop ourselves entirely. // This is also desirable as we may have reentrancy problems when we call into // custom commit completion providers. I.e. if the custom provider moves the caret, // then we do not want to process that move as it may put us into an unexpected state. // // TODO(cyrusn): We still have a general reentrancy problem where calling into a custom // commit provider (or just calling into the editor) may cause something to call back // into us. However, for now, we just hope that no such craziness will occur. this.StopModelComputation(); CompletionChange completionChange; using (var transaction = new CaretPreservingEditTransaction( EditorFeaturesResources.IntelliSense, TextView, _undoHistoryRegistry, _editorOperationsFactoryService)) { // We want to merge with any of our other programmatic edits (e.g. automatic brace completion) transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance; var provider = GetCompletionProvider(item.Item) as ICustomCommitCompletionProvider; if (provider != null) { provider.Commit(item.Item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar); } else { // Right before calling Commit, we may have passed the commitChar through to the // editor. That was so that undoing completion will get us back to the state we // we would be in if completion had done nothing. However, now that we're going // to actually commit, we want to roll back to where we were before we pushed // commit character into the buffer. This has multiple benefits: // // 1) the buffer is in a state we expect it to be in. i.e. we don't have to // worry about what might have happened (like brace-completion) when the // commit char was inserted. // 2) after we commit the item, we'll pass the commit character again into the // buffer (unless the items asks us not to). By doing this, we can make sure // that things like brace-completion or formatting trigger as we expect them // to. var characterWasSentIntoBuffer = commitChar != null && initialTextSnapshot.Version.VersionNumber != this.SubjectBuffer.CurrentSnapshot.Version.VersionNumber; if (characterWasSentIntoBuffer) { RollbackToBeforeTypeChar(initialTextSnapshot); } // Now, get the change the item wants to make. Note that the change will be relative // to the initial snapshot/document the item was triggered from. We'll map that change // forward, then apply it to our current snapshot. var triggerDocument = model.TriggerDocument; var triggerSnapshot = model.TriggerSnapshot; var completionService = CompletionService.GetService(triggerDocument); Contract.ThrowIfNull(completionService, nameof(completionService)); completionChange = completionService.GetChangeAsync( triggerDocument, item.Item, commitChar, CancellationToken.None).WaitAndGetResult(CancellationToken.None); var textChange = completionChange.TextChange; var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan()); var mappedSpan = triggerSnapshotSpan.TranslateTo( this.SubjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); // Now actually make the text change to the document. using (var textEdit = this.SubjectBuffer.CreateEdit(EditOptions.None, reiteratedVersionNumber: null, editTag: null)) { var adjustedNewText = AdjustForVirtualSpace(textChange); textEdit.Replace(mappedSpan.Span, adjustedNewText); textEdit.Apply(); } // adjust the caret position if requested by completion service if (completionChange.NewPosition != null) { TextView.Caret.MoveTo(new SnapshotPoint( this.SubjectBuffer.CurrentSnapshot, completionChange.NewPosition.Value)); } // Now, pass along the commit character unless the completion item said not to if (characterWasSentIntoBuffer && !completionChange.IncludesCommitCharacter) { nextHandler(); } // If the insertion is long enough, the caret will scroll out of the visible area. // Re-center the view. this.TextView.Caret.EnsureVisible(); } transaction.Complete(); } // Let the completion rules know that this item was committed. this.MakeMostRecentItem(item.Item.DisplayText); }
private void CommitOnNonTypeChar( PresentationItem item, Model model) { Commit(item, model, commitChar: null, initialTextSnapshot: null, nextHandler: null); }
void ICompletionSet.SetCompletionItems(IList<PresentationItem> completionItems, PresentationItem selectedItem, PresentationItem presetBuilder, bool suggestionMode, bool isSoftSelected, ImmutableArray<CompletionItemFilter> completionItemFilters, IReadOnlyDictionary<CompletionItem, string> completionItemToFilterText) { _roslynCompletionSet.SetCompletionItems(completionItems, selectedItem, presetBuilder, suggestionMode, isSoftSelected, completionItemFilters, completionItemToFilterText); }
public void PresentItems( ITrackingSpan triggerSpan, IList <PresentationItem> completionItems, PresentationItem selectedItem, PresentationItem suggestionModeItem, bool suggestionMode, bool isSoftSelected, ImmutableArray <CompletionItemFilter> completionItemFilters, string filterText) { AssertIsForeground(); // check if this update is still relevant if (_textView.IsClosed || _isDismissed) { return; } if (triggerSpan != null) { _completionSet.SetTrackingSpan(triggerSpan); } _ignoreSelectionStatusChangedEvent = true; try { _completionSet.SetCompletionItems( completionItems, selectedItem, suggestionModeItem, suggestionMode, isSoftSelected, completionItemFilters, filterText); } finally { _ignoreSelectionStatusChangedEvent = false; } if (_editorSessionOpt == null) { // We're tracking the caret. Don't have the editor do it. // Map the span instead of a point to avoid affinity problems. _editorSessionOpt = _completionBroker.CreateCompletionSession( _textView, triggerSpan.GetStartTrackingPoint(PointTrackingMode.Negative), trackCaret: false); var debugTextView = _textView as IDebuggerTextView; if (debugTextView != null && !debugTextView.IsImmediateWindow) { debugTextView.HACK_StartCompletionSession(_editorSessionOpt); } _editorSessionOpt.Dismissed += (s, e) => OnEditorSessionDismissed(); // So here's the deal. We cannot create the editor session and give it the right // items (even though we know what they are). Instead, the session will call // back into the ICompletionSourceProvider (which is us) to get those values. It // will pass itself along with the calls back into ICompletionSourceProvider. // So, in order to make that connection work, we add properties to the session // so that we can call back into ourselves, get the items and add it to the // session. _editorSessionOpt.Properties.AddProperty(Key, this); _editorSessionOpt.Start(); } // Call so that the editor will refresh the completion text to embolden. _editorSessionOpt?.Match(); }
public FilterResult(PresentationItem presentationItem, string filterText, bool matchedFilterText) { PresentationItem = presentationItem; MatchedFilterText = matchedFilterText; FilterText = filterText; }
private void Commit(PresentationItem item, Model model, char? commitChar, CancellationToken cancellationToken) { var textChanges = ImmutableArray<TextChange>.Empty; // NOTE(cyrusn): It is intentional that we get the undo history for the // surface buffer and not the subject buffer. // There have been some watsons where the ViewBuffer hadn't been registered, // so use TryGetHistory instead. ITextUndoHistory undoHistory; _undoHistoryRegistry.TryGetHistory(this.TextView.TextBuffer, out undoHistory); using (var transaction = undoHistory?.CreateTransaction(EditorFeaturesResources.IntelliSense)) { // We want to merge with any of our other programmatic edits (e.g. automatic brace completion) if (transaction != null) { transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance; } // Check if the provider wants to perform custom commit itself. Otherwise we will // handle things. var provider = GetCompletionProvider(item.Item) as ICustomCommitCompletionProvider; if (provider == null) { var viewBuffer = this.TextView.TextBuffer; var commitDocument = this.SubjectBuffer.CurrentSnapshot.AsText().GetDocumentWithFrozenPartialSemanticsAsync(cancellationToken).WaitAndGetResult(cancellationToken); // adjust commit item span foward to match current document that is passed to GetChangeAsync below var commitItem = item.Item; var currentItemSpan = GetCurrentItemSpan(commitItem, model); commitItem = commitItem.WithSpan(currentItemSpan); var completionService = CompletionService.GetService(commitDocument); var commitChange = completionService.GetChangeAsync(commitDocument, commitItem, commitChar, cancellationToken).WaitAndGetResult(cancellationToken); textChanges = commitChange.TextChanges; // Use character based diffing here to avoid overwriting the commit character placed into the editor. var editOptions = new EditOptions(new StringDifferenceOptions { DifferenceType = StringDifferenceTypes.Character, IgnoreTrimWhiteSpace = EditOptions.DefaultMinimalChange.DifferenceOptions.IgnoreTrimWhiteSpace }); // edit subject buffer (not view) because text changes are in terms of current document. using (var textEdit = this.SubjectBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) { for (int iChange = 0; iChange < textChanges.Length; iChange++) { var textChange = textChanges[iChange]; var isFirst = iChange == 0; var isLast = iChange == textChanges.Length - 1; // add commit char to end of last change if not already included if (isLast && !commitChange.IncludesCommitCharacter && commitChar.HasValue) { textChange = new TextChange(textChange.Span, textChange.NewText + commitChar.Value); } var currentSpan = new SnapshotSpan(this.SubjectBuffer.CurrentSnapshot, new Span(textChange.Span.Start, textChange.Span.Length)); // In order to play nicely with automatic brace completion, we need to // not touch the opening paren. We'll check our span and textchange // for ( and adjust them accordingly if we find them. // all this is needed since we don't use completion set mechanism provided by VS but we implement everything ourselves. // due to that, existing brace completion engine in editor that should take care of interaction between brace completion // and intellisense doesn't work for us. so we need this kind of workaround to support it nicely. bool textChanged; string newText = textChange.NewText; if (isFirst) { newText = AdjustFirstText(textChange); } if (isLast) { newText = AdjustLastText(newText, commitChar.GetValueOrDefault(), out textChanged); currentSpan = AdjustLastSpan(currentSpan, commitChar.GetValueOrDefault(), textChanged); } var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); var virtualCaretPoint = this.TextView.GetVirtualCaretPoint(this.SubjectBuffer); if (caretPoint.HasValue && virtualCaretPoint.HasValue) { // TODO(dustinca): We need to call a different API here. TryMoveCaretToAndEnsureVisible might center within the view. this.TextView.TryMoveCaretToAndEnsureVisible(new VirtualSnapshotPoint(caretPoint.Value)); } caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); // Now that we're doing character level diffing, we need to move the caret to the end of // the span being replaced. Otherwise, we can replace M|ai with Main and wind up with // M|ain, since character based diffing makes that quite legit. if (caretPoint.HasValue) { var endInSubjectBuffer = this.TextView.BufferGraph.MapDownToBuffer(currentSpan.End, PointTrackingMode.Positive, caretPoint.Value.Snapshot.TextBuffer, PositionAffinity.Predecessor); if (caretPoint.Value < endInSubjectBuffer) { this.TextView.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(currentSpan.Snapshot.TextBuffer.CurrentSnapshot, currentSpan.End.Position)); } } textEdit.Replace(currentSpan, newText); } textEdit.Apply(); } // adjust the caret position if requested by completion service if (commitChange.NewPosition != null) { var target = new SnapshotPoint(this.SubjectBuffer.CurrentSnapshot, commitChange.NewPosition.Value); this.TextView.TryMoveCaretToAndEnsureVisible(target); } // We've manipulated the caret position in order to generate the correct edit. However, // if the insertion is long enough, the caret will scroll out of the visible area. // Re-center the view. using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) { var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); if (caretPoint.HasValue) { this.TextView.Caret.EnsureVisible(); } } transaction?.Complete(); } else { // Let the provider handle this. provider.Commit(item.Item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar); transaction?.Complete(); } } var document = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); var formattingService = document.GetLanguageService<IEditorFormattingService>(); var commitCharTriggersFormatting = commitChar != null && (formattingService?.SupportsFormattingOnTypedCharacter(document, commitChar.GetValueOrDefault()) ?? false); if (formattingService != null && (item.Item.Rules.FormatOnCommit || commitCharTriggersFormatting)) { // Formatting the completion item affected span is done as a separate transaction because this gives the user // the flexibility to undo the formatting but retain the changes associated with the completion item using (var formattingTransaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.IntelliSenseCommitFormatting)) { var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); IList<TextChange> changes = null; if (commitCharTriggersFormatting && caretPoint.HasValue) { // if the commit character is supported by formatting service, then let the formatting service // find the appropriate range to format. changes = formattingService.GetFormattingChangesAsync(document, commitChar.Value, caretPoint.Value.Position, cancellationToken).WaitAndGetResult(cancellationToken); } else if (textChanges.Length > 0) { // if this is not a supported trigger character for formatting service (space or tab etc.) // then format the span of the textchange. var totalSpan = TextSpan.FromBounds(textChanges.Min(c => c.Span.Start), textChanges.Max(c => c.Span.End)); changes = formattingService.GetFormattingChangesAsync(document, totalSpan, cancellationToken).WaitAndGetResult(cancellationToken); } if (changes != null && !changes.IsEmpty()) { document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken); } formattingTransaction.Complete(); } } // Let the completion rules know that this item was committed. this.MakeMostRecentItem(item.Item.DisplayText); }
private VSCompletion GetVSCompletion(PresentationItem item) { VSCompletion value; if (!PresentationItemMap.TryGetValue(item, out value)) { value = new CustomCommitCompletion( CompletionPresenterSession, item); PresentationItemMap.Add(item, value); } return value; }
public void SetCompletionItems( IList<PresentationItem> completionItems, PresentationItem selectedItem, PresentationItem presetBuilder, bool suggestionMode, bool isSoftSelected, ImmutableArray<CompletionItemFilter> completionItemFilters, string filterText) { this.AssertIsForeground(); VSCompletion selectedCompletionItem = null; // Initialize the completion map to a reasonable default initial size (+1 for the builder) PresentationItemMap = PresentationItemMap ?? new Dictionary<PresentationItem, VSCompletion>(completionItems.Count + 1); FilterText = filterText; try { VsCompletionSet.WritableCompletionBuilders.BeginBulkOperation(); VsCompletionSet.WritableCompletionBuilders.Clear(); this.SetupFilters(completionItemFilters); var applicableToText = VsCompletionSet.ApplicableTo.GetText( VsCompletionSet.ApplicableTo.TextBuffer.CurrentSnapshot); SimplePresentationItem filteredSuggestionModeItem = null; if (selectedItem != null) { var completionItem = CompletionItem.Create(displayText: applicableToText); completionItem.Span = VsCompletionSet.ApplicableTo.GetSpan( VsCompletionSet.ApplicableTo.TextBuffer.CurrentSnapshot).Span.ToTextSpan(); filteredSuggestionModeItem = new SimplePresentationItem( completionItem, selectedItem.CompletionService, isSuggestionModeItem: true); } var showBuilder = suggestionMode || presetBuilder != null; var bestSuggestionModeItem = applicableToText.Length > 0 ? filteredSuggestionModeItem : presetBuilder ?? filteredSuggestionModeItem; if (showBuilder && bestSuggestionModeItem != null) { var suggestionModeCompletion = GetVSCompletion(bestSuggestionModeItem); VsCompletionSet.WritableCompletionBuilders.Add(suggestionModeCompletion); if (selectedItem != null && selectedItem.IsSuggestionModeItem) { selectedCompletionItem = suggestionModeCompletion; } } } finally { VsCompletionSet.WritableCompletionBuilders.EndBulkOperation(); } try { VsCompletionSet.WritableCompletions.BeginBulkOperation(); VsCompletionSet.WritableCompletions.Clear(); foreach (var item in completionItems) { var completionItem = GetVSCompletion(item); VsCompletionSet.WritableCompletions.Add(completionItem); if (item == selectedItem) { selectedCompletionItem = completionItem; } } } finally { VsCompletionSet.WritableCompletions.EndBulkOperation(); } VsCompletionSet.SelectionStatus = new CompletionSelectionStatus( selectedCompletionItem, isSelected: !isSoftSelected, isUnique: selectedCompletionItem != null); }
private void Commit(PresentationItem item, Model model, char?commitChar, CancellationToken cancellationToken) { var textChanges = ImmutableArray <TextChange> .Empty; // NOTE(cyrusn): It is intentional that we get the undo history for the // surface buffer and not the subject buffer. // There have been some watsons where the ViewBuffer hadn't been registered, // so use TryGetHistory instead. ITextUndoHistory undoHistory; _undoHistoryRegistry.TryGetHistory(this.TextView.TextBuffer, out undoHistory); using (var transaction = undoHistory?.CreateTransaction(EditorFeaturesResources.IntelliSense)) { // We want to merge with any of our other programmatic edits (e.g. automatic brace completion) if (transaction != null) { transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance; } // Check if the provider wants to perform custom commit itself. Otherwise we will // handle things. var provider = GetCompletionProvider(item.Item) as ICustomCommitCompletionProvider; if (provider == null) { var viewBuffer = this.TextView.TextBuffer; var commitDocument = this.SubjectBuffer.CurrentSnapshot.AsText().GetDocumentWithFrozenPartialSemanticsAsync(cancellationToken).WaitAndGetResult(cancellationToken); // adjust commit item span foward to match current document that is passed to GetChangeAsync below var commitItem = item.Item; var currentItemSpan = GetCurrentItemSpan(commitItem, model); commitItem = commitItem.WithSpan(currentItemSpan); var completionService = CompletionService.GetService(commitDocument); var commitChange = completionService.GetChangeAsync(commitDocument, commitItem, commitChar, cancellationToken).WaitAndGetResult(cancellationToken); textChanges = commitChange.TextChanges; // Use character based diffing here to avoid overwriting the commit character placed into the editor. var editOptions = new EditOptions(new StringDifferenceOptions { DifferenceType = StringDifferenceTypes.Character, IgnoreTrimWhiteSpace = EditOptions.DefaultMinimalChange.DifferenceOptions.IgnoreTrimWhiteSpace }); // edit subject buffer (not view) because text changes are in terms of current document. using (var textEdit = this.SubjectBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) { for (int iChange = 0; iChange < textChanges.Length; iChange++) { var textChange = textChanges[iChange]; var isFirst = iChange == 0; var isLast = iChange == textChanges.Length - 1; // add commit char to end of last change if not already included if (isLast && !commitChange.IncludesCommitCharacter && commitChar.HasValue) { textChange = new TextChange(textChange.Span, textChange.NewText + commitChar.Value); } var currentSpan = new SnapshotSpan(this.SubjectBuffer.CurrentSnapshot, new Span(textChange.Span.Start, textChange.Span.Length)); // In order to play nicely with automatic brace completion, we need to // not touch the opening paren. We'll check our span and textchange // for ( and adjust them accordingly if we find them. // all this is needed since we don't use completion set mechanism provided by VS but we implement everything ourselves. // due to that, existing brace completion engine in editor that should take care of interaction between brace completion // and intellisense doesn't work for us. so we need this kind of workaround to support it nicely. bool textChanged; string newText = textChange.NewText; if (isFirst) { newText = AdjustFirstText(textChange); } if (isLast) { newText = AdjustLastText(newText, commitChar.GetValueOrDefault(), out textChanged); currentSpan = AdjustLastSpan(currentSpan, commitChar.GetValueOrDefault(), textChanged); } var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); var virtualCaretPoint = this.TextView.GetVirtualCaretPoint(this.SubjectBuffer); if (caretPoint.HasValue && virtualCaretPoint.HasValue) { // TODO(dustinca): We need to call a different API here. TryMoveCaretToAndEnsureVisible might center within the view. this.TextView.TryMoveCaretToAndEnsureVisible(new VirtualSnapshotPoint(caretPoint.Value)); } caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); // Now that we're doing character level diffing, we need to move the caret to the end of // the span being replaced. Otherwise, we can replace M|ai with Main and wind up with // M|ain, since character based diffing makes that quite legit. if (caretPoint.HasValue) { var endInSubjectBuffer = this.TextView.BufferGraph.MapDownToBuffer(currentSpan.End, PointTrackingMode.Positive, caretPoint.Value.Snapshot.TextBuffer, PositionAffinity.Predecessor); if (caretPoint.Value < endInSubjectBuffer) { this.TextView.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(currentSpan.Snapshot.TextBuffer.CurrentSnapshot, currentSpan.End.Position)); } } textEdit.Replace(currentSpan, newText); } textEdit.Apply(); } // adjust the caret position if requested by completion service if (commitChange.NewPosition != null) { var target = new SnapshotPoint(this.SubjectBuffer.CurrentSnapshot, commitChange.NewPosition.Value); this.TextView.TryMoveCaretToAndEnsureVisible(target); } // We've manipulated the caret position in order to generate the correct edit. However, // if the insertion is long enough, the caret will scroll out of the visible area. // Re-center the view. using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) { var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); if (caretPoint.HasValue) { this.TextView.Caret.EnsureVisible(); } } transaction?.Complete(); } else { // Let the provider handle this. provider.Commit(item.Item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar); transaction?.Complete(); } } var document = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); var formattingService = document.GetLanguageService <IEditorFormattingService>(); var commitCharTriggersFormatting = commitChar != null && (formattingService?.SupportsFormattingOnTypedCharacter(document, commitChar.GetValueOrDefault()) ?? false); if (formattingService != null && (item.Item.Rules.FormatOnCommit || commitCharTriggersFormatting)) { // Formatting the completion item affected span is done as a separate transaction because this gives the user // the flexibility to undo the formatting but retain the changes associated with the completion item using (var formattingTransaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.IntelliSenseCommitFormatting)) { var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); IList <TextChange> changes = null; if (commitCharTriggersFormatting && caretPoint.HasValue) { // if the commit character is supported by formatting service, then let the formatting service // find the appropriate range to format. changes = formattingService.GetFormattingChangesAsync(document, commitChar.Value, caretPoint.Value.Position, cancellationToken).WaitAndGetResult(cancellationToken); } else if (textChanges.Length > 0) { // if this is not a supported trigger character for formatting service (space or tab etc.) // then format the span of the textchange. var totalSpan = TextSpan.FromBounds(textChanges.Min(c => c.Span.Start), textChanges.Max(c => c.Span.End)); changes = formattingService.GetFormattingChangesAsync(document, totalSpan, cancellationToken).WaitAndGetResult(cancellationToken); } if (changes != null && !changes.IsEmpty()) { document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken); } formattingTransaction.Complete(); } } // Let the completion rules know that this item was committed. this.MakeMostRecentItem(item.Item.DisplayText); }
void ICompletionSet.SetCompletionItems( IList<PresentationItem> completionItems, PresentationItem selectedItem, PresentationItem presetBuilder, bool suggestionMode, bool isSoftSelected, ImmutableArray<CompletionItemFilter> completionItemFilters, IReadOnlyDictionary<CompletionItem, string> completionItemToFilterText) { this._foregroundObject.AssertIsForeground(); VSCompletion selectedCompletionItem = null; // Initialize the completion map to a reasonable default initial size (+1 for the builder) _presentationItemMap = _presentationItemMap ?? new Dictionary<PresentationItem, VSCompletion>(completionItems.Count + 1); _completionItemToFilterText = completionItemToFilterText; try { this.WritableCompletionBuilders.BeginBulkOperation(); this.WritableCompletionBuilders.Clear(); #if DEV15 // If more than one filter was provided, then present it to the user. if (_filters == null && completionItemFilters.Length > 1) { _filters = completionItemFilters.Select(f => new IntellisenseFilter2(this, f, GetLanguage())) .ToArray(); } #endif var applicableToText = this.ApplicableTo.GetText(this.ApplicableTo.TextBuffer.CurrentSnapshot); SimplePresentationItem filteredSuggestionModeItem = null; if (selectedItem != null) { filteredSuggestionModeItem = new SimplePresentationItem( CompletionItem.Create( displayText: applicableToText, span: this.ApplicableTo.GetSpan(this.ApplicableTo.TextBuffer.CurrentSnapshot).Span.ToTextSpan()), selectedItem.CompletionService, isSuggestionModeItem: true); } var showBuilder = suggestionMode || presetBuilder != null; var bestSuggestionModeItem = applicableToText.Length > 0 ? filteredSuggestionModeItem : presetBuilder ?? filteredSuggestionModeItem; if (showBuilder && bestSuggestionModeItem != null) { var suggestionModeCompletion = GetVSCompletion(bestSuggestionModeItem); this.WritableCompletionBuilders.Add(suggestionModeCompletion); if (selectedItem != null && selectedItem.IsSuggestionModeItem) { selectedCompletionItem = suggestionModeCompletion; } } } finally { this.WritableCompletionBuilders.EndBulkOperation(); } try { this.WritableCompletions.BeginBulkOperation(); this.WritableCompletions.Clear(); foreach (var item in completionItems) { var completionItem = GetVSCompletion(item); this.WritableCompletions.Add(completionItem); if (item == selectedItem) { selectedCompletionItem = completionItem; } } } finally { this.WritableCompletions.EndBulkOperation(); } this.SelectionStatus = new CompletionSelectionStatus( selectedCompletionItem, isSelected: !isSoftSelected, isUnique: selectedCompletionItem != null); }
public void SetCompletionItems( IList <PresentationItem> completionItems, PresentationItem selectedItem, PresentationItem presetBuilder, bool suggestionMode, bool isSoftSelected, ImmutableArray <CompletionItemFilter> completionItemFilters, IReadOnlyDictionary <CompletionItem, string> completionItemToFilterText) { this.AssertIsForeground(); VSCompletion selectedCompletionItem = null; // Initialize the completion map to a reasonable default initial size (+1 for the builder) PresentationItemMap = PresentationItemMap ?? new Dictionary <PresentationItem, VSCompletion>(completionItems.Count + 1); CompletionItemToFilterText = completionItemToFilterText; try { VsCompletionSet.WritableCompletionBuilders.BeginBulkOperation(); VsCompletionSet.WritableCompletionBuilders.Clear(); this.SetupFilters(completionItemFilters); var applicableToText = VsCompletionSet.ApplicableTo.GetText( VsCompletionSet.ApplicableTo.TextBuffer.CurrentSnapshot); SimplePresentationItem filteredSuggestionModeItem = null; if (selectedItem != null) { filteredSuggestionModeItem = new SimplePresentationItem( CompletionItem.Create( displayText: applicableToText, span: VsCompletionSet.ApplicableTo.GetSpan( VsCompletionSet.ApplicableTo.TextBuffer.CurrentSnapshot).Span.ToTextSpan()), selectedItem.CompletionService, isSuggestionModeItem: true); } var showBuilder = suggestionMode || presetBuilder != null; var bestSuggestionModeItem = applicableToText.Length > 0 ? filteredSuggestionModeItem : presetBuilder ?? filteredSuggestionModeItem; if (showBuilder && bestSuggestionModeItem != null) { var suggestionModeCompletion = GetVSCompletion(bestSuggestionModeItem); VsCompletionSet.WritableCompletionBuilders.Add(suggestionModeCompletion); if (selectedItem != null && selectedItem.IsSuggestionModeItem) { selectedCompletionItem = suggestionModeCompletion; } } } finally { VsCompletionSet.WritableCompletionBuilders.EndBulkOperation(); } try { VsCompletionSet.WritableCompletions.BeginBulkOperation(); VsCompletionSet.WritableCompletions.Clear(); foreach (var item in completionItems) { var completionItem = GetVSCompletion(item); VsCompletionSet.WritableCompletions.Add(completionItem); if (item == selectedItem) { selectedCompletionItem = completionItem; } } } finally { VsCompletionSet.WritableCompletions.EndBulkOperation(); } VsCompletionSet.SelectionStatus = new CompletionSelectionStatus( selectedCompletionItem, isSelected: !isSoftSelected, isUnique: selectedCompletionItem != null); }
public Model WithSuggestionModeItem(PresentationItem suggestionModeItem) { return With(suggestionModeItem: suggestionModeItem); }
private bool IsHardSelection( Model model, PresentationItem bestFilterMatch, ITextSnapshot textSnapshot, CompletionHelper completionHelper, CompletionTrigger trigger, CompletionFilterReason reason) { if (model.SuggestionModeItem != null) { return bestFilterMatch != null && bestFilterMatch.Item.DisplayText == model.SuggestionModeItem.Item.DisplayText; } if (bestFilterMatch == null || model.UseSuggestionMode) { return false; } // We don't have a builder and we have a best match. Normally this will be hard // selected, except for a few cases. Specifically, if no filter text has been // provided, and this is not a preselect match then we will soft select it. This // happens when the completion list comes up implicitly and there is something in // the MRU list. In this case we do want to select it, but not with a hard // selection. Otherwise you can end up with the following problem: // // dim i as integer =<space> // // Completion will comes up after = with 'integer' selected (Because of MRU). We do // not want 'space' to commit this. var viewSpan = model.GetViewBufferSpan(bestFilterMatch.Item.Span); var fullFilterText = model.GetCurrentTextInSnapshot(viewSpan, textSnapshot, endPoint: null); var shouldSoftSelect = ShouldSoftSelectItem(bestFilterMatch.Item, fullFilterText, trigger); if (shouldSoftSelect) { return false; } // If the user moved the caret left after they started typing, the 'best' match may not match at all // against the full text span that this item would be replacing. if (!MatchesFilterText(completionHelper, bestFilterMatch.Item, fullFilterText, trigger, reason, this.Controller.GetRecentItems())) { return false; } // There was either filter text, or this was a preselect match. In either case, we // can hard select this. return true; }
void ICompletionSet.SetCompletionItems(IList <PresentationItem> completionItems, PresentationItem selectedItem, PresentationItem presetBuilder, bool suggestionMode, bool isSoftSelected, ImmutableArray <CompletionItemFilter> completionItemFilters, string filterText) { _roslynCompletionSet.SetCompletionItems(completionItems, selectedItem, presetBuilder, suggestionMode, isSoftSelected, completionItemFilters, filterText); }
internal void OnCompletionItemCommitted(PresentationItem presentationItem) { AssertIsForeground(); this.ItemCommitted?.Invoke(this, new PresentationItemEventArgs(presentationItem)); }
private void Commit( PresentationItem item, Model model, char?commitChar, ITextSnapshot initialTextSnapshot, Action nextHandler) { AssertIsForeground(); // We could only be called if we had a model at this point. Contract.ThrowIfNull(model); // Now that we've captured the model at this point, we can stop ourselves entirely. // This is also desirable as we may have reentrancy problems when we call into // custom commit completion providers. I.e. if the custom provider moves the caret, // then we do not want to process that move as it may put us into an unexpected state. // // TODO(cyrusn): We still have a general reentrancy problem where calling into a custom // commit provider (or just calling into the editor) may cause something to call back // into us. However, for now, we just hope that no such craziness will occur. this.StopModelComputation(); CompletionChange completionChange; using (var transaction = new CaretPreservingEditTransaction( EditorFeaturesResources.IntelliSense, TextView, _undoHistoryRegistry, _editorOperationsFactoryService)) { // We want to merge with any of our other programmatic edits (e.g. automatic brace completion) transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance; var provider = GetCompletionProvider(item.Item) as ICustomCommitCompletionProvider; if (provider != null) { provider.Commit(item.Item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar); } else { // Right before calling Commit, we may have passed the commitChar through to the // editor. That was so that undoing completion will get us back to the state we // we would be in if completion had done nothing. However, now that we're going // to actually commit, we want to roll back to where we were before we pushed // commit character into the buffer. This has multiple benefits: // // 1) the buffer is in a state we expect it to be in. i.e. we don't have to // worry about what might have happened (like brace-completion) when the // commit char was inserted. // 2) after we commit the item, we'll pass the commit character again into the // buffer (unless the items asks us not to). By doing this, we can make sure // that things like brace-completion or formatting trigger as we expect them // to. var characterWasSentIntoBuffer = commitChar != null && initialTextSnapshot.Version.VersionNumber != this.SubjectBuffer.CurrentSnapshot.Version.VersionNumber; if (characterWasSentIntoBuffer) { RollbackToBeforeTypeChar(initialTextSnapshot); } // Now, get the change the item wants to make. Note that the change will be relative // to the initial snapshot/document the item was triggered from. We'll map that change // forward, then apply it to our current snapshot. var triggerDocument = model.TriggerDocument; var triggerSnapshot = model.TriggerSnapshot; var completionService = CompletionService.GetService(triggerDocument); completionChange = completionService.GetChangeAsync( triggerDocument, item.Item, commitChar, CancellationToken.None).WaitAndGetResult(CancellationToken.None); var textChange = completionChange.TextChange; var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan()); var mappedSpan = triggerSnapshotSpan.TranslateTo( this.SubjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); // Now actually make the text change to the document. using (var textEdit = this.SubjectBuffer.CreateEdit(EditOptions.None, reiteratedVersionNumber: null, editTag: null)) { var adjustedNewText = AdjustForVirtualSpace(textChange); textEdit.Replace(mappedSpan.Span, adjustedNewText); textEdit.Apply(); } // adjust the caret position if requested by completion service if (completionChange.NewPosition != null) { TextView.Caret.MoveTo(new SnapshotPoint( this.SubjectBuffer.CurrentSnapshot, completionChange.NewPosition.Value)); } // Now, pass along the commit character unless the completion item said not to if (characterWasSentIntoBuffer && !completionChange.IncludesCommitCharacter) { nextHandler(); } // If the insertion is long enough, the caret will scroll out of the visible area. // Re-center the view. this.TextView.Caret.EnsureVisible(); } transaction.Complete(); } // Let the completion rules know that this item was committed. this.MakeMostRecentItem(item.Item.DisplayText); }
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); }
public Model WithSuggestionModeItem(PresentationItem suggestionModeItem) { return suggestionModeItem == this.SuggestionModeItem ? this : new Model(_disconnectedBufferGraph, OriginalList, TotalItems, FilteredItems, SelectedItem, CompletionItemFilters, FilterState, CompletionItemToFilterText, IsHardSelection, IsUnique, UseSuggestionMode, suggestionModeItem, DefaultSuggestionModeItem, Trigger, CommitTrackingSpanEndPoint, DismissIfEmpty); }
public Model WithSelectedItem(PresentationItem selectedItem) { return(With(selectedItem: selectedItem)); }
public void PresentItems( ITrackingSpan triggerSpan, IList<PresentationItem> completionItems, PresentationItem selectedItem, PresentationItem suggestionModeItem, bool suggestionMode, bool isSoftSelected, ImmutableArray<CompletionItemFilter> completionItemFilters, IReadOnlyDictionary<CompletionItem, string> completionItemToFilterText) { AssertIsForeground(); // check if this update is still relevant if (_textView.IsClosed || _isDismissed) { return; } if (triggerSpan != null) { _completionSet.SetTrackingSpan(triggerSpan); } _ignoreSelectionStatusChangedEvent = true; try { _completionSet.SetCompletionItems( completionItems, selectedItem, suggestionModeItem, suggestionMode, isSoftSelected, completionItemFilters, completionItemToFilterText); } finally { _ignoreSelectionStatusChangedEvent = false; } if (_editorSessionOpt == null) { // We're tracking the caret. Don't have the editor do it. // Map the span instead of a point to avoid affinity problems. _editorSessionOpt = _completionBroker.CreateCompletionSession( _textView, triggerSpan.GetStartTrackingPoint(PointTrackingMode.Negative), trackCaret: false); var debugTextView = _textView as IDebuggerTextView; if (debugTextView != null && !debugTextView.IsImmediateWindow) { debugTextView.HACK_StartCompletionSession(_editorSessionOpt); } _editorSessionOpt.Dismissed += (s, e) => OnEditorSessionDismissed(); // So here's the deal. We cannot create the editor session and give it the right // items (even though we know what they are). Instead, the session will call // back into the ICompletionSourceProvider (which is us) to get those values. It // will pass itself along with the calls back into ICompletionSourceProvider. // So, in order to make that connection work, we add properties to the session // so that we can call back into ourselves, get the items and add it to the // session. _editorSessionOpt.Properties.AddProperty(Key, this); _editorSessionOpt.Start(); } // Call so that the editor will refresh the completion text to embolden. _editorSessionOpt?.Match(); }
public Model WithSuggestionModeItem(PresentationItem suggestionModeItem) { return(With(suggestionModeItem: suggestionModeItem)); }
void ICompletionSet.SetCompletionItems( IList <PresentationItem> completionItems, PresentationItem selectedItem, PresentationItem presetBuilder, bool suggestionMode, bool isSoftSelected, ImmutableArray <CompletionItemFilter> completionItemFilters, IReadOnlyDictionary <CompletionItem, string> completionItemToFilterText) { this._foregroundObject.AssertIsForeground(); VSCompletion selectedCompletionItem = null; // Initialize the completion map to a reasonable default initial size (+1 for the builder) _presentationItemMap = _presentationItemMap ?? new Dictionary <PresentationItem, VSCompletion>(completionItems.Count + 1); _completionItemToFilterText = completionItemToFilterText; try { this.WritableCompletionBuilders.BeginBulkOperation(); this.WritableCompletionBuilders.Clear(); #if DEV15 // If more than one filter was provided, then present it to the user. if (_filters == null && completionItemFilters.Length > 1) { _filters = completionItemFilters.Select(f => new IntellisenseFilter2(this, f, GetLanguage())) .ToArray(); } #endif var applicableToText = this.ApplicableTo.GetText(this.ApplicableTo.TextBuffer.CurrentSnapshot); var filteredSuggestionModeItem = new SimplePresentationItem( CompletionItem.Create( displayText: applicableToText, span: this.ApplicableTo.GetSpan(this.ApplicableTo.TextBuffer.CurrentSnapshot).Span.ToTextSpan()), selectedItem.CompletionService, isSuggestionModeItem: true); var showBuilder = suggestionMode || presetBuilder != null; var bestSuggestionModeItem = applicableToText.Length > 0 ? filteredSuggestionModeItem : presetBuilder ?? filteredSuggestionModeItem; if (showBuilder && bestSuggestionModeItem != null) { var suggestionModeCompletion = GetVSCompletion(bestSuggestionModeItem); this.WritableCompletionBuilders.Add(suggestionModeCompletion); if (selectedItem != null && selectedItem.IsSuggestionModeItem) { selectedCompletionItem = suggestionModeCompletion; } } } finally { this.WritableCompletionBuilders.EndBulkOperation(); } try { this.WritableCompletions.BeginBulkOperation(); this.WritableCompletions.Clear(); foreach (var item in completionItems) { var completionItem = GetVSCompletion(item); this.WritableCompletions.Add(completionItem); if (item == selectedItem) { selectedCompletionItem = completionItem; } } } finally { this.WritableCompletions.EndBulkOperation(); } this.SelectionStatus = new CompletionSelectionStatus( selectedCompletionItem, isSelected: !isSoftSelected, isUnique: selectedCompletionItem != null); }
protected bool Equals(PresentationItem other) { return(string.Equals(Title, other.Title) && string.Equals(AddedBy, other.AddedBy) && ImageUrl == other.ImageUrl); }