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