/// <summary> /// create caret preserving edit transaction with automatic code change undo merging policy /// </summary> public static CaretPreservingEditTransaction CreateEditTransaction( this ITextView view, string description, ITextUndoHistoryRegistry registry, IEditorOperationsFactoryService service) { var transaction = new CaretPreservingEditTransaction(description, view, registry, service); transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance; return transaction; }
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); }