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