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);
        }
Exemplo n.º 2
0
        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);
        }
Exemplo n.º 3
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));
 }
Exemplo n.º 4
0
 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);
 }
Exemplo n.º 5
0
 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);
 }
Exemplo n.º 6
0
        private VSCompletion GetVSCompletion(PresentationItem item)
        {
            VSCompletion value;

            if (!_presentationItemMap.TryGetValue(item, out value))
            {
                value = new CustomCommitCompletion(
                    _completionPresenterSession,
                    item);
                _presentationItemMap.Add(item, value);
            }

            return(value);
        }
Exemplo n.º 7
0
            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);
            }
Exemplo n.º 8
0
        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);
        }
Exemplo n.º 9
0
        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);
        }
Exemplo n.º 10
0
        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);
        }
Exemplo n.º 11
0
        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;
        }
Exemplo n.º 12
0
        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;
        }
Exemplo n.º 13
0
 public Model WithSelectedItem(PresentationItem selectedItem)
 {
     return With(selectedItem: selectedItem);
 }
Exemplo n.º 14
0
        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);
        }
Exemplo n.º 15
0
 private void CommitOnNonTypeChar(
     PresentationItem item, Model model)
 {
     Commit(item, model, commitChar: null, initialTextSnapshot: null, nextHandler: null);
 }
Exemplo n.º 16
0
 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);
 }
Exemplo n.º 17
0
        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();
        }
Exemplo n.º 18
0
 public FilterResult(PresentationItem presentationItem, string filterText, bool matchedFilterText)
 {
     PresentationItem  = presentationItem;
     MatchedFilterText = matchedFilterText;
     FilterText        = filterText;
 }
Exemplo n.º 19
0
        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);
        }
 public FilterResult(PresentationItem presentationItem, string filterText, bool matchedFilterText)
 {
     PresentationItem = presentationItem;
     MatchedFilterText = matchedFilterText;
     FilterText = filterText;
 }
Exemplo n.º 21
0
        private VSCompletion GetVSCompletion(PresentationItem item)
        {
            VSCompletion value;
            if (!PresentationItemMap.TryGetValue(item, out value))
            {
                value = new CustomCommitCompletion(
                    CompletionPresenterSession,
                    item);
                PresentationItemMap.Add(item, value);
            }

            return value;
        }
Exemplo n.º 22
0
        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);
        }
Exemplo n.º 23
0
        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);
        }
Exemplo n.º 24
0
        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);
        }
Exemplo n.º 25
0
        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);
        }
Exemplo n.º 26
0
 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;
            }
Exemplo n.º 28
0
 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);
 }
Exemplo n.º 29
0
 internal void OnCompletionItemCommitted(PresentationItem presentationItem)
 {
     AssertIsForeground();
     this.ItemCommitted?.Invoke(this, new PresentationItemEventArgs(presentationItem));
 }
Exemplo n.º 30
0
        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);
        }
Exemplo n.º 31
0
            private Model FilterModelInBackgroundWorker(
                Model model,
                int id,
                SnapshotPoint caretPosition,
                bool recheckCaretPosition,
                bool dismissIfEmptyAllowed,
                CompletionFilterReason filterReason)
            {
                if (model == null)
                {
                    return(null);
                }

                var filterState = model.FilterState;

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

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

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

                var textSnapshot     = caretPosition.Snapshot;
                var allFilteredItems = new List <PresentationItem>();
                var textSpanToText   = new Dictionary <TextSpan, string>();
                var helper           = this.Controller.GetCompletionHelper();

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

                var recentItems = this.Controller.GetRecentItems();

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

                model = model.WithCompletionItemToFilterText(itemToFilterText);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                return(result);
            }
Exemplo n.º 32
0
 private void CommitOnNonTypeChar(
     PresentationItem item, Model model)
 {
     Commit(item, model, commitChar: null, initialTextSnapshot: null, nextHandler: null);
 }
Exemplo n.º 33
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);
 }
Exemplo n.º 34
0
 internal void OnCompletionItemCommitted(PresentationItem presentationItem)
 {
     AssertIsForeground();
     this.ItemCommitted?.Invoke(this, new PresentationItemEventArgs(presentationItem));
 }
Exemplo n.º 35
0
 public Model WithSelectedItem(PresentationItem selectedItem)
 {
     return(With(selectedItem: selectedItem));
 }
Exemplo n.º 36
0
        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();
        }
Exemplo n.º 37
0
 public Model WithSuggestionModeItem(PresentationItem suggestionModeItem)
 {
     return(With(suggestionModeItem: suggestionModeItem));
 }
Exemplo n.º 38
0
        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);
 }