Ejemplo n.º 1
0
        public bool TryGetController(ITextView textView, ITextBuffer subjectBuffer, out Controller controller)
        {
            AssertIsForeground();

            // check whether this feature is on.
            if (!subjectBuffer.GetOption(InternalFeatureOnOffOptions.CompletionSet))
            {
                controller = null;
                return false;
            }

            // If we don't have a presenter, then there's no point in us even being involved.  Just
            // defer to the next handler in the chain.

            // Also, if there's an inline rename session then we do not want completion.
            if (_completionPresenter == null || _inlineRenameService.ActiveSession != null)
            {
                controller = null;
                return false;
            }

            var autobraceCompletionCharSet = GetAllAutoBraceCompletionChars(subjectBuffer.ContentType);
            controller = Controller.GetInstance(
                textView, subjectBuffer,
                _editorOperationsFactoryService, _undoHistoryRegistry, _completionPresenter,
                new AggregateAsynchronousOperationListener(_asyncListeners, FeatureAttribute.CompletionSet),
                _allCompletionProviders, autobraceCompletionCharSet);

            return true;
        }
Ejemplo n.º 2
0
        private void Commit(CompletionItem item, TextChange textChange, Model model, char?commitChar)
        {
            AssertIsForeground();

            // We could only be called if we had a model at this point.
            Contract.ThrowIfNull(model);

            item = Controller.GetExternallyUsableCompletionItem(item);

            // 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();

            // NOTE(cyrusn): It is intentional that we get the undo history for the
            // surface buffer and not the subject buffer.
            using (var transaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.IntelliSense))
            {
                // We want to merge with any of our other programmatic edits (e.g. automatic brace completion)
                transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance;

                // Check if the provider wants to perform custom commit itself.  Otherwise we will
                // handle things.
                var provider = item.CompletionProvider as ICustomCommitCompletionProvider;
                if (provider == null)
                {
                    var viewBuffer = this.TextView.TextBuffer;

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

                    using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null))
                    {
                        var viewSpan    = model.GetSubjectBufferFilterSpanInViewBuffer(textChange.Span);
                        var currentSpan = model.GetCurrentSpanInSnapshot(viewSpan, viewBuffer.CurrentSnapshot);

                        // 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;
                        var  finalText = GetFinalText(textChange, commitChar.GetValueOrDefault(), out textChanged);
                        currentSpan = GetFinalSpan(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, finalText);
                        textEdit.Apply();
                    }

                    // 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, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar);
                    transaction.Complete();
                }
            }

            var document          = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
            var formattingService = document.GetLanguageService <IEditorFormattingService>();

            if (formattingService != null &&
                (item.ShouldFormatOnCommit || (commitChar != null && formattingService.SupportsFormattingOnTypedCharacter(document, commitChar.GetValueOrDefault()))))
            {
                // 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 changes = formattingService.GetFormattingChangesAsync(document, textChange.Span, CancellationToken.None).WaitAndGetResult(CancellationToken.None);
                    document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, CancellationToken.None);
                    formattingTransaction.Complete();
                }
            }

            // Let the rules factories know that this item was committed.
            foreach (var rules in GetCompletionRules())
            {
                rules.CompletionItemComitted(item);
            }
        }