in src/Editor/Text/Impl/EditorOperations/EditorOperations.cs [339:550]
public bool MoveSelectedLinesDown()
{
Func<bool> action = () =>
{
bool success = false;
// find line start
ITextViewLine startViewLine = GetLineStart(_textView, _textView.Selection.Start.Position);
SnapshotPoint start = startViewLine.Start;
ITextSnapshotLine startLine = start.GetContainingLine();
// find the last line view
ITextViewLine endViewLine = GetLineEnd(_textView, _textView.Selection.End.Position);
ITextSnapshotLine endLine = endViewLine.End.GetContainingLine();
ITextSnapshot snapshot = endLine.Snapshot;
// Handle the case where multiple lines are selected and the caret is sitting just after the line break on the next line.
// Shortening the selection here handles the case where the last line is a collapsed region. Using endLine.End will give
// a line within the collapsed region instead of skipping it all together.
if (GetLineEnd(_textView, startViewLine.Start) != endViewLine
&& _textView.Selection.End.Position == GetLineStart(_textView, _textView.Selection.End.Position).Start
&& !_textView.Selection.End.IsInVirtualSpace)
{
endLine = snapshot.GetLineFromLineNumber(endLine.LineNumber - 1);
endViewLine = _textView.GetTextViewLineContainingBufferPosition(_textView.Selection.End.Position - 1);
}
#region Initial Asserts
Debug.Assert(_textView.Selection.Start.Position.Snapshot == _textView.TextSnapshot, "Selection is out of sync with view.");
Debug.Assert(_textView.TextSnapshot == _textView.TextBuffer.CurrentSnapshot, "View is out of sync with text buffer.");
Debug.Assert(_textView.TextSnapshot == snapshot, "Text view lines are out of sync with the view");
#endregion
// check if we are at the end of the file
if ((endLine.LineNumber + 1) >= snapshot.LineCount)
{
// noop
success = true;
}
else
{
// nextLineExtent is different from prevLine.Extent and avoids issues around collapsed regions
ITextViewLine lastNextLine = GetLineEnd(_textView, endViewLine.EndIncludingLineBreak);
SnapshotSpan nextLineExtent = new SnapshotSpan(endViewLine.EndIncludingLineBreak, lastNextLine.End);
SnapshotSpan nextLineExtentIncludingLineBreak = new SnapshotSpan(endViewLine.EndIncludingLineBreak, lastNextLine.EndIncludingLineBreak);
using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
{
SnapshotSpan curLineExtent = new SnapshotSpan(startViewLine.Start, endViewLine.End);
SnapshotSpan curLineExtentIncLineBreak = new SnapshotSpan(startViewLine.Start, endViewLine.EndIncludingLineBreak);
string curLineText = curLineExtentIncLineBreak.GetText();
string nextLineText = nextLineExtentIncludingLineBreak.GetText();
if (nextLineText.Length == 0)
{
// end of file - noop
success = true;
}
else
{
List<Tuple<Span, IOutliningRegionTag>> collapsedSpansInCurLine = null;
bool hasCollapsedRegions = false;
IOutliningManager outliningManager = (_factory.OutliningManagerService != null)
? _factory.OutliningManagerService.GetOutliningManager(_textView)
: null;
if (outliningManager != null)
{
collapsedSpansInCurLine = outliningManager.GetCollapsedRegions(new NormalizedSnapshotSpanCollection(curLineExtent))
.Select(collapsed => Tuple.Create(collapsed.Extent.GetSpan(curLineExtent.Snapshot).Span, collapsed.Tag)).ToList();
hasCollapsedRegions = collapsedSpansInCurLine.Count > 0;
// check if we have collapsed spans in the selection and add the undo primitive if so
if (hasCollapsedRegions)
{
using (ITextUndoTransaction undoTransaction = _undoHistory.CreateTransaction(Strings.MoveSelLinesDown))
{
BeforeCollapsedMoveUndoPrimitive undoPrim = new BeforeCollapsedMoveUndoPrimitive(outliningManager, _textView, collapsedSpansInCurLine);
undoTransaction.AddUndo(undoPrim);
undoTransaction.Complete();
}
}
}
int offset = nextLineText.Length;
// a line without a line break
if (nextLineExtent == nextLineExtentIncludingLineBreak)
{
string lineBreakText = new SnapshotSpan(startLine.End, startLine.EndIncludingLineBreak).GetText();
offset += lineBreakText.Length;
curLineText = lineBreakText + curLineText.Substring(0, curLineText.Length - lineBreakText.Length);
}
edit.Delete(curLineExtentIncLineBreak);
edit.Insert(nextLineExtentIncludingLineBreak.End, curLineText);
if (edit.HasFailedChanges)
{
success = false;
}
else
{
int anchorPos = _textView.Selection.AnchorPoint.Position.Position;
int anchorVirtualSpace = _textView.Selection.AnchorPoint.VirtualSpaces;
int activePos = _textView.Selection.ActivePoint.Position.Position;
int activeVirtualSpace = _textView.Selection.ActivePoint.VirtualSpaces;
var selectionMode = _textView.Selection.Mode;
ITextSnapshot newSnapshot = edit.Apply();
if (newSnapshot == snapshot)
{
success = false;
}
else
{
// Update the selection and caret position after the move
ITextSnapshot currentSnapshot = snapshot.TextBuffer.CurrentSnapshot;
VirtualSnapshotPoint desiredAnchor = new VirtualSnapshotPoint(new SnapshotPoint(newSnapshot, Math.Min(anchorPos + offset, newSnapshot.Length)),
anchorVirtualSpace).TranslateTo(currentSnapshot, PointTrackingMode.Negative);
VirtualSnapshotPoint desiredActive = new VirtualSnapshotPoint(new SnapshotPoint(newSnapshot, Math.Min(activePos + offset, newSnapshot.Length)),
activeVirtualSpace).TranslateTo(currentSnapshot, PointTrackingMode.Negative);
// keep the caret position and selection after the move
SelectAndMoveCaret(desiredAnchor, desiredActive, selectionMode, EnsureSpanVisibleOptions.None);
// Recollapse the spans
if (outliningManager != null && hasCollapsedRegions)
{
// This comes from adhocoutliner.cs in env\editor\pkg\impl\outlining and will not be available outside of VS
SimpleTagger<IOutliningRegionTag> simpleTagger =
_textView.TextBuffer.Properties.GetOrCreateSingletonProperty<SimpleTagger<IOutliningRegionTag>>(() => new SimpleTagger<IOutliningRegionTag>(_textView.TextBuffer));
if (simpleTagger != null)
{
if (hasCollapsedRegions)
{
List<Tuple<ITrackingSpan, IOutliningRegionTag>> addedSpans = collapsedSpansInCurLine.Select(tuple => Tuple.Create(newSnapshot.CreateTrackingSpan(tuple.Item1.Start + offset,
tuple.Item1.Length, SpanTrackingMode.EdgeExclusive), tuple.Item2)).ToList();
if (addedSpans.Count > 0)
{
List<Tuple<Span, IOutliningRegionTag>> spansForUndo = new List<Tuple<Span, IOutliningRegionTag>>();
// add spans to tracking
foreach (var addedSpan in addedSpans)
{
simpleTagger.CreateTagSpan(addedSpan.Item1, addedSpan.Item2);
spansForUndo.Add(new Tuple<Span, IOutliningRegionTag>(addedSpan.Item1.GetSpan(newSnapshot), addedSpan.Item2));
}
SnapshotSpan changedSpan = new SnapshotSpan(addedSpans.Select(tuple => tuple.Item1.GetSpan(newSnapshot).Start).Min(),
addedSpans.Select(tuple => tuple.Item1.GetSpan(newSnapshot).End).Max());
List<SnapshotSpan> addedSnapshotSpans = addedSpans.Select(tuple => tuple.Item1.GetSpan(newSnapshot)).ToList();
bool disableOutliningUndo = _editorOptions.IsOutliningUndoEnabled();
// Recollapse the span
// We need to disable the OutliningUndoManager for this operation otherwise an undo will expand it
try
{
if (disableOutliningUndo)
{
_textView.Options.SetOptionValue(DefaultTextViewOptions.OutliningUndoOptionId, false);
}
outliningManager.CollapseAll(changedSpan, collapsible => addedSnapshotSpans.Contains(collapsible.Extent.GetSpan(newSnapshot)));
}
finally
{
if (disableOutliningUndo)
{
_textView.Options.SetOptionValue(DefaultTextViewOptions.OutliningUndoOptionId, true);
}
}
// we need to recollapse after a redo
using (ITextUndoTransaction undoTransaction = _undoHistory.CreateTransaction(Strings.MoveSelLinesDown))
{
AfterCollapsedMoveUndoPrimitive undoPrim = new AfterCollapsedMoveUndoPrimitive(outliningManager, _textView, spansForUndo);
undoTransaction.AddUndo(undoPrim);
undoTransaction.Complete();
}
}
}
}
}
success = true;
}
}
}
}
}
return success;
};
return ExecuteAction(Strings.MoveSelLinesDown, action, SelectionUpdate.Ignore, true);
}