private bool InsertText()

in src/Editor/Text/Impl/EditorOperations/EditorOperations.cs [3370:3617]


        private bool InsertText(string text, bool final, string undoText, bool isOverwriteModeEnabled)
        {
            // Validate
            if (text == null)
            {
                throw new ArgumentNullException(nameof(text));
            }

            if ((text.Length == 0) && !final)
            {
                throw new ArgumentException("Provisional TextInput cannot be zero-length");
            }

            using (ITextUndoTransaction undoTransaction = _undoHistory.CreateTransaction(undoText))
            {
                // Determine undo merge direction(s).  By default we allow merging, but in two cases we don't want to merge
                // with previous transactions:
                // 1. If the document is clean (else you won't be able to undo back to a clean state (Dev10 672382)).
                // 2. If we are replacing selected text (replacing selected text should be an atomic undo operation).
                var mergeDirections = TextTransactionMergeDirections.Forward | TextTransactionMergeDirections.Backward;
                if ((!_textView.Selection.IsEmpty && !IsEmptyBoxSelection()) // replacing selected text.
                    || (_textDocument != null && !_textDocument.IsDirty))    // document is clean.
                {
                    mergeDirections = TextTransactionMergeDirections.Forward;
                }
                undoTransaction.MergePolicy = new TextTransactionMergePolicy(mergeDirections);

                this.AddBeforeTextBufferChangePrimitive();

                IEnumerable<VirtualSnapshotSpan> replaceSpans;

                TextEditAction action = TextEditAction.Type;

                // If this is a non-empty stream selection and *not* an empty box
                if (!_textView.Selection.IsEmpty && _immProvisionalComposition == null)
                {
                    // If the caret is in overwrite mode and this is an empty box, treat
                    // it like an overwrite on each line.
                    if (_textView.Options.IsOverwriteModeEnabled() && IsEmptyBoxSelection())
                    {
                        List<VirtualSnapshotSpan> spans = new List<VirtualSnapshotSpan>();

                        foreach (var span in _textView.Selection.VirtualSelectedSpans)
                        {
                            SnapshotPoint startPoint = span.Start.Position;

                            if (span.Start.IsInVirtualSpace ||
                                startPoint.GetContainingLine().End == startPoint)
                            {
                                spans.Add(span);
                            }
                            else
                            {
                                spans.Add(new VirtualSnapshotSpan(
                                       new SnapshotSpan(startPoint, _textView.GetTextElementSpan(startPoint).End)));
                            }
                        }

                        replaceSpans = spans;
                    }
                    else
                    {
                        replaceSpans = _multiSelectionBroker.VirtualSelectedSpans;
                    }

                    // The provisional composition span should be null here (the IME should
                    // finalize before doing anything that could cause a selection) but we treat
                    // this as a soft error since something time sliced on the UI thread could
                    // affect the selection. So we simply ignore the old provisional span.
                }
                else if (_immProvisionalComposition != null)
                {
                    SnapshotSpan provisionalSpan = _immProvisionalComposition.GetSpan(_textView.TextSnapshot);

                    if (IsEmptyBoxSelection() && final)
                    {
                        // For an empty box, replace the equivalent to the provisional span
                        // on each line
                        IList<VirtualSnapshotSpan> spans = new List<VirtualSnapshotSpan>();
                        foreach (SnapshotPoint start in _textView.Selection.VirtualSelectedSpans.Select(s => s.Start.Position))
                        {
                            if (start.Position - provisionalSpan.Length >= 0)
                            {
                                spans.Add(new VirtualSnapshotSpan(
                                        new SnapshotSpan(start - provisionalSpan.Length, start)));
                            }
                            else
                            {
                                Debug.Fail("Asked to replace a provisional span that would go past the beginning of the buffer; ignoring.");
                            }
                        }

                        replaceSpans = spans;
                    }
                    else
                    {
                        replaceSpans = new VirtualSnapshotSpan[] {
                            new VirtualSnapshotSpan(provisionalSpan) };
                    }

                    // The length of the replacement should always be > 0 but there are scenarios
                    // (such as a replace by some asynchronous command time-sliced to the UI thread)
                    // that could zero out the span.

                    action = TextEditAction.ProvisionalOverwrite;
                }
                else
                {
                    var spans = new List<VirtualSnapshotSpan>();
                    foreach (var caret in _multiSelectionBroker.GetSelectionsIntersectingSpan(new SnapshotSpan(_multiSelectionBroker.CurrentSnapshot, 0, _multiSelectionBroker.CurrentSnapshot.Length)))
                    {
                        var insertionPoint = caret.InsertionPoint;
                        if (isOverwriteModeEnabled && !insertionPoint.IsInVirtualSpace)
                        {
                            SnapshotPoint point = insertionPoint.Position;
                            spans.Add(new VirtualSnapshotSpan(new SnapshotSpan(point, _textView.GetTextElementSpan(point).End)));
                        }
                        else
                        {
                            spans.Add(new VirtualSnapshotSpan(insertionPoint, insertionPoint));
                        }
                    }
                    replaceSpans = spans;
                }

                ITextVersion currentVersion = _textView.TextSnapshot.Version;
                ITrackingSpan newProvisionalSpan = null;

                bool editSuccessful = true;

                int? firstLineInsertedVirtualSpaces = null;
                int lastLineInsertedVirtualSpaces = 0;

                ITrackingPoint endPoint = null;

                using (ITextEdit textEdit = _textView.TextBuffer.CreateEdit(EditOptions.None, null, action))
                {
                    bool firstSpan = true;
                    foreach (var span in replaceSpans)
                    {
                        string lineText = text;

                        // Remember the endPoint for a possible provisional span.
                        // Use negative tracking, so it doesn't move with the newly inserted text.
                        endPoint = _textView.TextSnapshot.CreateTrackingPoint(span.Start.Position, PointTrackingMode.Negative);

                        if (span.Start.IsInVirtualSpace)
                        {
                            string whitespace = GetWhitespaceForVirtualSpace(span.Start);
                            if (firstSpan)
                            {
                                //Keep track of the number of whitespace characters -- which might include tabs -- so that the caller can figure out where the inserted text
                                //actually lands.
                                _textView.TextBuffer.Properties["WhitespaceInserted"] = whitespace.Length;
                            }
                            lineText = whitespace + text;
                        }

                        if (!firstLineInsertedVirtualSpaces.HasValue)
                            firstLineInsertedVirtualSpaces = lineText.Length - text.Length;

                        lastLineInsertedVirtualSpaces = lineText.Length - text.Length;

                        if (!textEdit.Replace(span.SnapshotSpan, lineText) || textEdit.Canceled)
                        {
                            editSuccessful = false;
                            break;
                        }

                        firstSpan = false;
                    }

                    if (editSuccessful)
                    {
                        textEdit.Apply();
                        editSuccessful = !textEdit.Canceled;
                    }
                }

                if (editSuccessful)
                {
                    if (_multiSelectionBroker.IsBoxSelection)
                    {
                        _textView.Caret.MoveTo(_textView.Caret.Position.BufferPosition);
                        _textView.Selection.Select(
                                new VirtualSnapshotPoint(_textView.Selection.AnchorPoint.Position),
                                new VirtualSnapshotPoint(_textView.Selection.ActivePoint.Position));

                        // If the selection ends up being non-empty (meaning not an empty
                        // single selection *or* an empty box), then clear it.
                        if (_textView.Selection.VirtualSelectedSpans.Any(s => !s.IsEmpty))
                            _textView.Selection.Clear();

                    }
                    else
                    {
                        _multiSelectionBroker.PerformActionOnAllSelections(transformer =>
                        {
                            // We've done the edit now. We need to both remove virtual space and clear selections.
                            var newInsertion = new VirtualSnapshotPoint(transformer.Selection.InsertionPoint.Position, 0);
                            transformer.MoveTo(newInsertion, select: false, PositionAffinity.Successor);
                        });
                    }

                    _textView.Caret.EnsureVisible();

                    this.AddAfterTextBufferChangePrimitive();

                    undoTransaction.Complete();

                    if (final)
                    {
                        newProvisionalSpan = null;
                    }
                    else if (_textView.Selection.IsReversed)
                    {
                        // The active point is at the start.
                        int virtualSpaces = firstLineInsertedVirtualSpaces.HasValue ? firstLineInsertedVirtualSpaces.Value : 0;

                        newProvisionalSpan = currentVersion.Next.CreateTrackingSpan(
                            new Span(replaceSpans.First().Start.Position + virtualSpaces, text.Length),
                            SpanTrackingMode.EdgeExclusive);
                    }
                    else
                    {
                        // The active point is at the end.
                        // Since text may have been inserted before this point, we need to accommodate the
                        // amount of inserted text, using the endPoint we constructed earlier.

                        int lastSpanStart = endPoint.GetPoint(_textView.TextSnapshot).Position;

                        newProvisionalSpan = currentVersion.Next.CreateTrackingSpan(
                            new Span(lastSpanStart + lastLineInsertedVirtualSpaces, text.Length),
                            SpanTrackingMode.EdgeExclusive);
                    }
                }

                // This test is for null to non-null or vice versa (two different ITrackingSpans -- even if they cover the same span -- are not
                // considered equal).
                if (_immProvisionalComposition != newProvisionalSpan)
                {
                    _immProvisionalComposition = newProvisionalSpan;
                    _textView.ProvisionalTextHighlight = _immProvisionalComposition;
                }

                return editSuccessful;
            }
        }