//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//
// This file contain implementations details that are subject to change without notice.
// Use at your own risk.
//
namespace Microsoft.VisualStudio.Text.Operations.Implementation
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
using Microsoft.VisualStudio.Text.Formatting;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio.Text.Outlining;
using Microsoft.VisualStudio.Text.Tagging;
#if WINDOWS
using Microsoft.VisualStudio.Language.Intellisense.Utilities;
#endif
///
/// Provides a default operations set on top of the text editor
///
internal class EditorOperations : IEditorOperations3
{
enum CaretMovementDirection
{
Previous = 0,
Next = 1,
}
enum LetterCase
{
Uppercase = 0,
Lowercase = 1,
}
enum SelectionUpdate
{
Preserve,
Reset,
ResetUnlessEmptyBox,
Ignore,
ClearVirtualSpace
};
#region Private Members
ITextView _textView;
EditorOperationsFactoryService _factory;
ITextDocument _textDocument;
ITextStructureNavigator _textStructureNavigator;
ITextUndoHistory _undoHistory;
IViewPrimitives _editorPrimitives;
IEditorOptions _editorOptions;
IMultiSelectionBroker _multiSelectionBroker;
private ITrackingSpan _immProvisionalComposition;
///
/// A data format used to tag the contents of the clipboard so that it's clear
/// the data has been put in the clipboard by our editor
///
private const string _clipboardLineBasedCutCopyTag = "VisualStudioEditorOperationsLineCutCopyClipboardTag";
///
/// A data format used to tag the contents of the clipboard as a box selection.
/// This is the same string that was used in VS9 and previous versions.
///
private const string _boxSelectionCutCopyTag = "MSDEVColumnSelect";
#endregion // Private Members
///
/// Constructs an bound to a given .
///
///
/// The text editor to which this operations provider should bind to
///
internal EditorOperations(ITextView textView,
EditorOperationsFactoryService factory)
{
// Validate
if (textView == null)
throw new ArgumentNullException(nameof(textView));
if (factory == null)
throw new ArgumentNullException(nameof(factory));
_textView = textView;
_factory = factory;
_multiSelectionBroker = _textView.GetMultiSelectionBroker();
_editorPrimitives = factory.EditorPrimitivesProvider.GetViewPrimitives(textView);
// Get the TextStructure Navigator
_textStructureNavigator = factory.TextStructureNavigatorFactory.GetTextStructureNavigator(_textView.TextBuffer);
Debug.Assert(_textStructureNavigator != null);
_undoHistory = factory.UndoHistoryRegistry.RegisterHistory(_textView.TextBuffer);
// Ensure that there is an ITextBufferUndoManager created for our TextBuffer
ITextBufferUndoManager textBufferUndoManager = factory.TextBufferUndoManagerProvider.GetTextBufferUndoManager(_textView.TextBuffer);
Debug.Assert(textBufferUndoManager != null);
_editorOptions = factory.EditorOptionsProvider.GetOptions(textView);
_factory.TextDocumentFactoryService.TryGetTextDocument(_textView.TextDataModel.DocumentBuffer, out _textDocument);
_textView.Closed += delegate {
_factory.UndoHistoryRegistry.RemoveHistory(_undoHistory);
_factory.TextBufferUndoManagerProvider.RemoveTextBufferUndoManager(_textView.TextBuffer);
};
}
#region IEditorOperations2 Members
public bool MoveSelectedLinesUp()
{
Func 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);
SnapshotPoint end = endViewLine.EndIncludingLineBreak;
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);
end = endLine.EndIncludingLineBreak;
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 top of the file, or trying to move a blank line
if (startLine.LineNumber < 1 || start == end)
{
// noop
success = true;
}
else
{
// find the line we are going to move over
ITextSnapshotLine prevLine = snapshot.GetLineFromLineNumber(startLine.LineNumber - 1);
// prevLineExtent is different from prevLine.Extent and avoids issues around collapsed regions
SnapshotPoint prevLineStart = GetLineStart(_textView, prevLine.Start).Start;
SnapshotSpan prevLineExtent = new SnapshotSpan(prevLineStart, prevLine.End);
SnapshotSpan prevLineExtentIncludingLineBreak = new SnapshotSpan(prevLineStart, prevLine.EndIncludingLineBreak);
using (ITextEdit edit = _textView.TextBuffer.CreateEdit())
{
int offset;
SnapshotSpan curLineExtent = new SnapshotSpan(startViewLine.Start, endViewLine.End);
SnapshotSpan curLineExtentIncLineBreak = new SnapshotSpan(startViewLine.Start, endViewLine.EndIncludingLineBreak);
string curLineText = curLineExtentIncLineBreak.GetText();
List> 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.MoveSelLinesUp))
{
BeforeCollapsedMoveUndoPrimitive undoPrim = new BeforeCollapsedMoveUndoPrimitive(outliningManager, _textView, collapsedSpansInCurLine);
undoTransaction.AddUndo(undoPrim);
undoTransaction.Complete();
}
}
}
string nextLineText = prevLineExtentIncludingLineBreak.GetText();
offset = nextLineText.Length;
// make the change
edit.Delete(curLineExtentIncLineBreak);
edit.Insert(prevLineExtentIncludingLineBreak.Start, curLineText);
// swap the line break around if needed for the last line of the selection
if (endViewLine.LineBreakLength == 0 && endViewLine.EndIncludingLineBreak == snapshot.Length)
{
// put the line break on the line we just moved that didn't have one
edit.Insert(prevLine.ExtentIncludingLineBreak.Start, prevLine.GetLineBreakText());
// delete the break from the line now at the end of the file
edit.Delete(new SnapshotSpan(prevLine.End, prevLine.EndIncludingLineBreak));
}
if (!edit.HasFailedChanges)
{
// store the position before the edit is applied
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;
// apply the edit
ITextSnapshot newSnapshot = edit.Apply();
if (newSnapshot != snapshot)
{
// 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 selection and caret position the same
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 simpleTagger =
_textView.TextBuffer.Properties.GetOrCreateSingletonProperty>(
() => new SimpleTagger(_textView.TextBuffer));
if (simpleTagger != null)
{
if (hasCollapsedRegions)
{
List> 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> spansForUndo = new List>();
foreach (var addedSpan in addedSpans)
{
simpleTagger.CreateTagSpan(addedSpan.Item1, addedSpan.Item2);
spansForUndo.Add(new Tuple(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 addedSnapshotSpans = addedSpans.Select(tuple => tuple.Item1.GetSpan(newSnapshot)).ToList();
bool disableOutliningUndo = _editorOptions.IsOutliningUndoEnabled();
// Recollapse the spans
// 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.MoveSelLinesUp))
{
AfterCollapsedMoveUndoPrimitive undoPrim = new AfterCollapsedMoveUndoPrimitive(outliningManager, _textView, spansForUndo);
undoTransaction.AddUndo(undoPrim);
undoTransaction.Complete();
}
}
}
}
}
success = true;
}
}
}
}
return success;
};
return ExecuteAction(Strings.MoveSelLinesUp, action, SelectionUpdate.Ignore, true);
}
public bool MoveSelectedLinesDown()
{
Func 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> 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 simpleTagger =
_textView.TextBuffer.Properties.GetOrCreateSingletonProperty>(() => new SimpleTagger(_textView.TextBuffer));
if (simpleTagger != null)
{
if (hasCollapsedRegions)
{
List> 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> spansForUndo = new List>();
// add spans to tracking
foreach (var addedSpan in addedSpans)
{
simpleTagger.CreateTagSpan(addedSpan.Item1, addedSpan.Item2);
spansForUndo.Add(new Tuple(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 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);
}
private static ITextViewLine GetLineStart(ITextView view, SnapshotPoint snapshotPoint)
{
ITextViewLine line = view.GetTextViewLineContainingBufferPosition(snapshotPoint);
while (!line.IsFirstTextViewLineForSnapshotLine)
{
line = view.GetTextViewLineContainingBufferPosition(line.Start - 1);
}
return line;
}
private static ITextViewLine GetLineEnd(ITextView view, SnapshotPoint snapshotPoint)
{
ITextViewLine line = view.GetTextViewLineContainingBufferPosition(snapshotPoint);
while (!line.IsLastTextViewLineForSnapshotLine)
{
line = view.GetTextViewLineContainingBufferPosition(line.EndIncludingLineBreak);
}
return line;
}
#endregion
#region IEditorOperations Members
public void SelectAndMoveCaret(VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint)
{
SelectAndMoveCaret(anchorPoint, activePoint, TextSelectionMode.Stream, EnsureSpanVisibleOptions.MinimumScroll);
}
public void SelectAndMoveCaret(VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint, TextSelectionMode selectionMode)
{
this.SelectAndMoveCaret(anchorPoint, activePoint, selectionMode, EnsureSpanVisibleOptions.MinimumScroll);
}
public void SelectAndMoveCaret(VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint, TextSelectionMode selectionMode, EnsureSpanVisibleOptions? scrollOptions)
{
bool empty = (anchorPoint == activePoint);
var selection = new Selection(anchorPoint, activePoint);
if (selectionMode == TextSelectionMode.Box)
{
_multiSelectionBroker.SetBoxSelection(selection);
}
else
{
_multiSelectionBroker.SetSelection(selection);
}
// 3) If scrollOptions were provided, we're going to try and make the span visible using the provided options.
if (scrollOptions.HasValue)
{
//Make sure scrollOptions forces EnsureSpanVisible to bring the start or end of the selection into view appropriately.
if (_textView.Selection.IsReversed)
{
scrollOptions = scrollOptions.Value | EnsureSpanVisibleOptions.ShowStart;
}
else
{
scrollOptions = scrollOptions.Value & (~EnsureSpanVisibleOptions.ShowStart);
}
// Try to make the span visible. Since we are setting the scrollOptions above, this will ensure the caret
// is visible as well (we do not need to worry about the case where the caret is at the end of a word-wrapped
// line since -- when the caret is moved to a VirtualSnapshotPoint -- it won't be).
_textView.ViewScroller.EnsureSpanVisible(_textView.Selection.StreamSelectionSpan, scrollOptions.Value);
}
}
///
/// Moves one character to the right.
///
///
/// Specifies whether selection is made as the caret is moved.
///
public void MoveToNextCharacter(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToNextCaretPosition : PredefinedSelectionTransformations.MoveToNextCaretPosition);
_textView.Caret.EnsureVisible();
}
///
/// Moves one character to the left.
///
///
/// Specifies whether selection is made as the caret is moved.
///
public void MoveToPreviousCharacter(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToPreviousCaretPosition : PredefinedSelectionTransformations.MoveToPreviousCaretPosition);
_textView.Caret.EnsureVisible();
}
///
/// Moves the caret to the next word.
///
///
/// Specifies whether or not selection is extended as the caret is moved.
///
public void MoveToNextWord(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToNextWord : PredefinedSelectionTransformations.MoveToNextWord);
_textView.Caret.EnsureVisible();
}
///
/// Moves the caret to the previous word.
///
///
/// Specifies whether or not selection is extended as the caret is moved.
///
public void MoveToPreviousWord(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToPreviousWord : PredefinedSelectionTransformations.MoveToPreviousWord);
_textView.Caret.EnsureVisible();
}
///
/// Sets the caret at the start of the document.
///
///
/// Specifies whether selection is made as the caret is moved.
///
public void MoveToStartOfDocument(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToStartOfDocument : PredefinedSelectionTransformations.MoveToStartOfDocument);
_textView.Caret.EnsureVisible();
}
///
/// Sets the caret at the end of the document.
///
///
/// Specifies whether selection is made as the caret is moved.
///
public void MoveToEndOfDocument(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToEndOfDocument : PredefinedSelectionTransformations.MoveToEndOfDocument);
_textView.Caret.EnsureVisible();
}
///
/// Moves the current line to the top of the view.
///
public void MoveCurrentLineToTop()
{
_editorPrimitives.View.MoveLineToTop(_editorPrimitives.Caret.LineNumber);
}
///
/// Moves the current line to the bottom of the view.
///
public void MoveCurrentLineToBottom()
{
_editorPrimitives.View.MoveLineToBottom(_editorPrimitives.Caret.LineNumber);
}
public void MoveToStartOfLineAfterWhiteSpace(bool select)
{
int firstTextColumn = _editorPrimitives.Caret.GetFirstNonWhiteSpaceCharacterOnViewLine().CurrentPosition;
if (firstTextColumn == _editorPrimitives.Caret.EndOfViewLine)
firstTextColumn = _editorPrimitives.Caret.StartOfViewLine;
_editorPrimitives.Caret.MoveTo(firstTextColumn, select);
}
public void MoveToStartOfNextLineAfterWhiteSpace(bool select)
{
DisplayTextPoint caretPoint = _editorPrimitives.Caret.Clone();
caretPoint.MoveToBeginningOfNextLine();
int firstTextColumn = caretPoint.GetFirstNonWhiteSpaceCharacterOnViewLine().CurrentPosition;
if (firstTextColumn == caretPoint.EndOfViewLine)
firstTextColumn = caretPoint.StartOfViewLine;
_editorPrimitives.Caret.MoveTo(firstTextColumn, select);
}
public void MoveToStartOfPreviousLineAfterWhiteSpace(bool select)
{
DisplayTextPoint caretPoint = _editorPrimitives.Caret.Clone();
caretPoint.MoveToBeginningOfPreviousLine();
int firstTextColumn = caretPoint.GetFirstNonWhiteSpaceCharacterOnViewLine().CurrentPosition;
if (firstTextColumn == caretPoint.EndOfViewLine)
firstTextColumn = caretPoint.StartOfViewLine;
_editorPrimitives.Caret.MoveTo(firstTextColumn, select);
}
public void MoveToLastNonWhiteSpaceCharacter(bool select)
{
int lastNonWhiteSpaceCharacterInLine = _editorPrimitives.Caret.EndOfViewLine - 1;
for (; lastNonWhiteSpaceCharacterInLine >= _editorPrimitives.Caret.StartOfViewLine; lastNonWhiteSpaceCharacterInLine--)
{
string nextCharacter = _editorPrimitives.View.GetTextPoint(lastNonWhiteSpaceCharacterInLine).GetNextCharacter();
if (!char.IsWhiteSpace(nextCharacter[0]))
{
break;
}
}
lastNonWhiteSpaceCharacterInLine = Math.Max(lastNonWhiteSpaceCharacterInLine, _editorPrimitives.Caret.StartOfLine);
if (lastNonWhiteSpaceCharacterInLine != _editorPrimitives.Caret.CurrentPosition)
{
_editorPrimitives.Caret.MoveTo(lastNonWhiteSpaceCharacterInLine, select);
}
}
public void MoveToTopOfView(bool select)
{
// TextViewLines may have both a partially and a fully hidden line at the top, or either, or neither.
ITextViewLineCollection lines = _editorPrimitives.View.AdvancedTextView.TextViewLines;
ITextViewLine firstVisibleLine = lines.FirstVisibleLine;
int iFirstVisibleLine = lines.GetIndexOfTextLine(firstVisibleLine);
ITextViewLine fullyVisibleLine = FindFullyVisibleLine(firstVisibleLine, iFirstVisibleLine + 1);
MoveCaretToTextLine(fullyVisibleLine, select);
}
public void MoveToBottomOfView(bool select)
{
// TextViewLines may have both a partially and a fully hidden line at the end, or either, or neither.
ITextViewLineCollection lines = _editorPrimitives.View.AdvancedTextView.TextViewLines;
ITextViewLine lastVisibleLine = lines.LastVisibleLine;
int iLastVisibleLine = lines.GetIndexOfTextLine(lastVisibleLine);
ITextViewLine fullyVisibleLine = FindFullyVisibleLine(lastVisibleLine, iLastVisibleLine - 1);
MoveCaretToTextLine(fullyVisibleLine, select);
}
///
/// Deletes the word to the right of the current caret position.
///
public bool DeleteWordToRight()
{
Func action = () =>
{
TextPoint startPointOfDelete = _editorPrimitives.Caret.Clone();
TextRange nextWord = startPointOfDelete.GetNextWord();
TextPoint endPointOfDelete = nextWord.GetStartPoint();
// If this delete did not start at the end of the line
// then only delete to the end of the line.
int endOfLine = startPointOfDelete.EndOfLine;
if (startPointOfDelete.CurrentPosition != endOfLine)
{
// It is possible that the text structure navigator has returned
// a word that spans multiple lines. In that case, just delete to
// the end of the line to match VS9 behavior.
if (endPointOfDelete.CurrentPosition > endOfLine)
{
endPointOfDelete.MoveTo(endOfLine);
}
else if (startPointOfDelete.CurrentPosition >= endPointOfDelete.CurrentPosition)
{
//If the startPointOfDelete was on the last word of the line then endPointOfDelete might be the
//start of the word so we should, instead, delete to the end of the line (or, at least, that is
//what we think is happening).
endPointOfDelete.MoveTo(endOfLine);
}
}
return ExpandRangeToIncludeSelection(startPointOfDelete.GetTextRange(endPointOfDelete)).Delete();
};
return ExecuteAction(Strings.DeleteWordToRight, action);
}
///
/// Deletes the word to the left of the current caret position.
///
public bool DeleteWordToLeft()
{
Func action = () =>
{
TextRange currentWord = _editorPrimitives.Caret.GetCurrentWord();
TextRange rangeToDelete = currentWord;
if (_editorPrimitives.Caret.CurrentPosition > currentWord.GetStartPoint().CurrentPosition)
{
rangeToDelete = currentWord.GetStartPoint().GetTextRange(_editorPrimitives.Caret);
}
else
{
TextRange previousWord = _editorPrimitives.Caret.GetPreviousWord();
rangeToDelete = previousWord.GetStartPoint().GetTextRange(_editorPrimitives.Caret);
}
return ExpandRangeToIncludeSelection(rangeToDelete).Delete();
};
return ExecuteAction(Strings.DeleteWordToLeft, action);
}
public bool DeleteToBeginningOfLine()
{
Func action = () =>
{
TextRange selectionRange = _editorPrimitives.Selection.Clone();
if (_editorPrimitives.Selection.IsReversed || _editorPrimitives.Selection.IsEmpty)
{
selectionRange.SetStart(_editorPrimitives.View.GetTextPoint(_editorPrimitives.Caret.StartOfLine));
}
return selectionRange.Delete();
};
return ExecuteAction(Strings.DeleteToBOL, action);
}
public bool DeleteToEndOfLine()
{
Func action = () =>
{
TextRange selectionRange = _editorPrimitives.Selection.Clone();
if (!_editorPrimitives.Selection.IsReversed || _editorPrimitives.Selection.IsEmpty)
{
selectionRange.SetEnd(_editorPrimitives.View.GetTextPoint(_editorPrimitives.Caret.EndOfViewLine));
}
return selectionRange.Delete();
};
return ExecuteAction(Strings.DeleteToEOL, action);
}
///
/// Deletes a character to the left of the current caret.
///
public bool Backspace()
{
bool success = true;
if (WillBackspaceCreateEdit())
{
var selections = _multiSelectionBroker.AllSelections;
var boxSelection = _multiSelectionBroker.BoxSelection;
var primarySelection = _multiSelectionBroker.PrimarySelection;
Func action = () =>
{
using (_multiSelectionBroker.BeginBatchOperation())
{
if (TryBackspaceEdit(selections))
{
return TryPostBackspaceSelectionUpdate(selections, primarySelection, boxSelection);
}
}
return false;
};
success = ExecuteAction(Strings.DeleteCharToLeft, action, SelectionUpdate.Ignore, ensureVisible: false);
}
else
{
success = TryBackspaceSelections();
}
if (success)
{
_multiSelectionBroker.TryEnsureVisible(_multiSelectionBroker.PrimarySelection, EnsureSpanVisibleOptions.MinimumScroll);
}
return success;
}
private bool TryPostBackspaceSelectionUpdate(IReadOnlyList selections, Selection primarySelection, Selection boxSelection)
{
// Throughout this method, the parameters passed in are the OLD values, and the parameters on _multiSelectionBroker are the NEW ones
if (boxSelection != Selection.Invalid)
{
// If this is an empty box, we may need to capture the new active/anchor points, as points in virtual space
// won't track as we want them to through the edit.
VirtualSnapshotPoint anchorPoint = _multiSelectionBroker.BoxSelection.AnchorPoint;
VirtualSnapshotPoint activePoint = _multiSelectionBroker.BoxSelection.ActivePoint;
if (primarySelection.IsEmpty)
{
if (boxSelection.AnchorPoint.IsInVirtualSpace)
{
anchorPoint = new VirtualSnapshotPoint(_multiSelectionBroker.BoxSelection.AnchorPoint.Position, boxSelection.AnchorPoint.VirtualSpaces - 1);
}
if (boxSelection.ActivePoint.IsInVirtualSpace)
{
activePoint = new VirtualSnapshotPoint(_multiSelectionBroker.BoxSelection.ActivePoint.Position, boxSelection.ActivePoint.VirtualSpaces - 1);
}
}
else
{
// Just take the starting points in the first and last selections
activePoint = selections[boxSelection.IsReversed ? 0 : selections.Count - 1].Start;
anchorPoint = selections[boxSelection.IsReversed ? selections.Count - 1 : 0].Start;
}
VirtualSnapshotPoint newAnchor = anchorPoint.TranslateTo(_textView.TextSnapshot);
VirtualSnapshotPoint newActive = activePoint.TranslateTo(_textView.TextSnapshot);
var newSelection = new Selection(insertionPoint: newActive, anchorPoint: newAnchor, activePoint: newActive, boxSelection.InsertionPointAffinity);
if (_multiSelectionBroker.BoxSelection != newSelection)
{
_multiSelectionBroker.SetBoxSelection(newSelection);
}
}
else
{
// Perf: This is actually an n^2 algorithm here, since TryPerform... also loops through all the selections. Try to avoid copying this code
// elsewhere. We need it here because we're actually modifying each one based on its context AND because merges can happen with backspace so we
// can't do anything funny like caching the transformers.
for (int i = 0; i < selections.Count; i++)
{
//Some could have merged away, ignore return values here intentionally.
_multiSelectionBroker.TryPerformActionOnSelection(selections[i], transformer =>
{
// We can't use the virtual snapshot point TranslateTo since it will remove the virtual space (because the line's line break was deleted).
// VirtualSnapshotPoint.TranslateTo doesn't know what to do with virtual whitespace, so we have to do this ourselves.
if (selections[i].IsEmpty && selections[i].InsertionPoint.IsInVirtualSpace)
{
// Move the caret back one if we have an empty selection
transformer.MoveTo(new VirtualSnapshotPoint(transformer.Selection.InsertionPoint.Position, selections[i].InsertionPoint.VirtualSpaces - 1),
select: false,
insertionPointAffinity: PositionAffinity.Successor);
}
else
{
//Move the caret to the start of the selection.
transformer.MoveTo(new VirtualSnapshotPoint(transformer.Selection.InsertionPoint.Position, selections[i].Start.VirtualSpaces),
select: false,
PositionAffinity.Successor);
}
}, out _);
}
}
return true;
}
private bool WillBackspaceCreateEdit()
{
if (_multiSelectionBroker.IsBoxSelection)
{
// Edits can not happen if we're a box selection at the beginning of a line
var primary = _multiSelectionBroker.PrimarySelection;
if (primary.IsEmpty && primary.Start.Position == primary.Start.Position.GetContainingLine().Start)
{
return false;
}
}
var selections = _multiSelectionBroker.AllSelections;
for (int i = 0; i < selections.Count; i++)
{
if ((!selections[i].Extent.SnapshotSpan.IsEmpty) ||
(selections[i].IsEmpty
&& !selections[i].InsertionPoint.IsInVirtualSpace
&& selections[i].InsertionPoint.Position.Position != 0))
{
return true;
}
}
return false;
}
private bool TryBackspaceEdit(IReadOnlyList selections)
{
using (var edit = _textView.TextBuffer.CreateEdit())
{
for (int i = (selections.Count - 1); i >= 0; i--)
{
var selection = selections[i];
if (selection.IsEmpty)
{
if (selection.Extent.IsInVirtualSpace)
{
continue;
}
if (!TryBackspaceEmptySelection(selection, edit))
{
return false;
}
}
else if (!edit.Delete(selection.Extent.SnapshotSpan))
{
return false;
}
}
edit.Apply();
return !edit.Canceled;
}
}
private bool TryBackspaceEmptySelection(Selection selection, ITextEdit edit)
{
// Assumptions:
// We should have already validated this before calling.
Debug.Assert(selection.IsEmpty);
// This method is only written to perform edits on text. Virtual space operations should be performed separately and not passed here.
Debug.Assert(!selection.InsertionPoint.IsInVirtualSpace);
// Performing deletion:
// Identify what should be deleted
if (selection.InsertionPoint.Position.Position == 0)
{
// We're at the beginning of the document, we're done.
return true;
}
// Get the span of the previous element
SnapshotSpan previousElementSpan = TextView.GetTextElementSpan(selection.InsertionPoint.Position - 1);
// Here we have some interesting decisions to make. If this is a collapsed region, we want to delete the whole thing.
// If this is a multi-byte character, we typically want to delete just one byte to allow for easier typing in chinese and other languages.
// However, if that multi-byte character is a surrogate pair or newline, we want to delete the whole thing.
// We start by looking to see if this is a collapsed region or something like one.
if ((previousElementSpan.Length > 0) &&
(_textView.TextViewModel.IsPointInVisualBuffer(selection.InsertionPoint.Position, PositionAffinity.Successor)) &&
(!_textView.TextViewModel.IsPointInVisualBuffer(previousElementSpan.End - 1, PositionAffinity.Successor)))
{
// Since the previous character is not visible but the current one is, delete
// the entire previous text element span.
return edit.Delete(previousElementSpan);
}
else
{
//Next we test for surrogate pairs and newline:
ITextSnapshot snapshot = edit.Snapshot;
int previousPosition = selection.InsertionPoint.Position.Position - 1;
int index = previousPosition;
char currentCharacter = snapshot[previousPosition];
// By default VS (and many other apps) will delete only the last character
// of a combining character sequence. The one exception to this rule is
// surrogate pais which we are handling here.
if (char.GetUnicodeCategory(currentCharacter) == UnicodeCategory.Surrogate)
{
index--;
}
if ((index > 0) &&
(currentCharacter == '\n') &&
(snapshot[previousPosition - 1] == '\r'))
{
index--;
}
// With index moved back in the cases of newline and surrogate pairs, this delete should handle all other cases.
return edit.Delete(new Span(index, previousPosition - index + 1));
}
}
private bool TryBackspaceSelections()
{
if (_multiSelectionBroker.IsBoxSelection && _multiSelectionBroker.PrimarySelection.InsertionPoint.IsInVirtualSpace)
{
_multiSelectionBroker.SetSelection(new Selection(_multiSelectionBroker.PrimarySelection.Start));
}
else if (!_multiSelectionBroker.IsBoxSelection)
{
_multiSelectionBroker.PerformActionOnAllSelections(transformer =>
{
if (transformer.Selection.IsEmpty)
{
if (transformer.Selection.InsertionPoint.IsInVirtualSpace)
{
MoveToPreviousTabStop(transformer);
}
else
{
transformer.PerformAction(PredefinedSelectionTransformations.MoveToPreviousCaretPosition);
}
}
else
{
transformer.MoveTo(transformer.Selection.Start, select: false, PositionAffinity.Successor);
}
});
}
return true;
}
private void MoveToPreviousTabStop(ISelectionTransformer transformer)
{
var previousStop = GetPreviousIndentStopInVirtualSpace(transformer.Selection.InsertionPoint);
transformer.MoveTo(previousStop, select: false, PositionAffinity.Successor);
}
private void ResetVirtualSelection()
{
//Move the caret to the same line as the active point, but at the left edge of the current selection.
VirtualSnapshotPoint start = _textView.Selection.Start;
ITextViewLine startLine = _textView.GetTextViewLineContainingBufferPosition(start.Position);
VirtualSnapshotPoint end = _textView.Selection.End;
ITextViewLine endLine = _textView.GetTextViewLineContainingBufferPosition(end.Position);
double leftEdge = Math.Min(startLine.GetExtendedCharacterBounds(start).Left, endLine.GetExtendedCharacterBounds(end).Left);
ITextViewLine activeLine = (_textView.Selection.IsReversed) ? startLine : endLine;
VirtualSnapshotPoint newCaret = activeLine.GetInsertionBufferPositionFromXCoordinate(leftEdge);
_multiSelectionBroker.ClearSecondarySelections();
Selection unused;
_multiSelectionBroker.TryPerformActionOnSelection(_multiSelectionBroker.PrimarySelection, transformer =>
{
transformer.MoveTo(newCaret, select: false, PositionAffinity.Successor);
}, out unused);
}
public bool DeleteFullLine()
{
return ExecuteAction(Strings.DeleteLine, () => GetFullLines().Delete());
}
public bool Tabify()
{
return ConvertLeadingWhitespace(Strings.Tabify, convertTabsToSpaces: false);
}
public bool Untabify()
{
return ConvertLeadingWhitespace(Strings.Untabify, convertTabsToSpaces: true);
}
public bool ConvertSpacesToTabs()
{
return ExecuteAction(Strings.ConvertSpacesToTabs, delegate { return ConvertSpacesAndTabsHelper(true); });
}
public bool ConvertTabsToSpaces()
{
return ExecuteAction(Strings.ConvertTabsToSpaces, delegate { return ConvertSpacesAndTabsHelper(false); });
}
public bool NormalizeLineEndings(string replacement)
{
return ExecuteAction(Strings.NormalizeLineEndings, () => NormalizeLineEndingsHelper(replacement));
}
///
/// Selects the current word.
///
public void SelectCurrentWord()
{
TextRange currentWord = _editorPrimitives.Caret.GetCurrentWord();
TextRange previousWord = _editorPrimitives.Caret.GetPreviousWord();
TextRange selection = _editorPrimitives.Selection.Clone();
if (!_editorPrimitives.Selection.IsEmpty)
{
if ((
(selection.GetStartPoint().CurrentPosition == currentWord.GetStartPoint().CurrentPosition) &&
(selection.GetEndPoint().CurrentPosition == currentWord.GetEndPoint().CurrentPosition)) ||
(selection.GetStartPoint().CurrentPosition == previousWord.GetStartPoint().CurrentPosition) &&
(selection.GetEndPoint().CurrentPosition == previousWord.GetEndPoint().CurrentPosition))
{
// If the selection is already correct, don't select again, but *do* ensure
// the existing span is visible, even though we aren't moving the selection.
_textView.ViewScroller.EnsureSpanVisible(_textView.Selection.StreamSelectionSpan.SnapshotSpan, EnsureSpanVisibleOptions.MinimumScroll);
return;
}
}
// If the current word is blank, use the previous word if it is on the same line.
if (currentWord.IsEmpty)
{
TextRange previous = currentWord.GetStartPoint().GetPreviousWord();
if (previous.GetStartPoint().LineNumber == currentWord.GetStartPoint().LineNumber)
currentWord = previous;
}
_editorPrimitives.Selection.SelectRange(currentWord);
}
///
/// Selects the enclosing.
///
public void SelectEnclosing()
{
SnapshotSpan selectionSpan;
if (_textView.Selection.IsEmpty || _textView.Selection.Mode == TextSelectionMode.Box)
{
selectionSpan = new SnapshotSpan(_textView.Caret.Position.BufferPosition, 0);
}
else
{
// This ignores virtual space
selectionSpan = _textView.Selection.StreamSelectionSpan.SnapshotSpan;
}
Span span = _textStructureNavigator.GetSpanOfEnclosing(selectionSpan);
TextRange enclosingRange = _editorPrimitives.Buffer.GetTextRange(span.Start, span.End);
_editorPrimitives.Selection.SelectRange(enclosingRange);
}
///
/// Selects the first child.
///
public void SelectFirstChild()
{
SnapshotSpan selectionSpan;
if (_textView.Selection.IsEmpty || _textView.Selection.Mode == TextSelectionMode.Box)
{
selectionSpan = new SnapshotSpan(_textView.Caret.Position.BufferPosition, 0);
}
else
{
// This ignores virtual space
selectionSpan = _textView.Selection.StreamSelectionSpan.SnapshotSpan;
}
Span span = _textStructureNavigator.GetSpanOfFirstChild(selectionSpan);
TextRange firstChildRange = _editorPrimitives.Buffer.GetTextRange(span.Start, span.End);
_editorPrimitives.Selection.SelectRange(firstChildRange);
}
///
/// Selects the next sibling.
///
/// Specifies whether the selection is to be extended or a new selection is to be made.
public void SelectNextSibling(bool extendSelection)
{
SnapshotSpan selectionSpan;
if (_textView.Selection.IsEmpty || _textView.Selection.Mode == TextSelectionMode.Box)
{
selectionSpan = new SnapshotSpan(_textView.Caret.Position.BufferPosition, 0);
}
else
{
// This ignores virtual space
selectionSpan = _textView.Selection.StreamSelectionSpan.SnapshotSpan;
}
Span span = _textStructureNavigator.GetSpanOfNextSibling(selectionSpan);
_textView.Selection.Clear();
if (!span.IsEmpty && extendSelection)
{
// extend the selection to the end of the next sibling
int start = (span.Start <= selectionSpan.Start) ? span.Start : selectionSpan.Start;
int end = (span.End <= selectionSpan.End) ? selectionSpan.End : span.End;
span = Span.FromBounds(start, end);
}
TextRange nextSiblingRange = _editorPrimitives.Buffer.GetTextRange(span.Start, span.End);
_editorPrimitives.Selection.SelectRange(nextSiblingRange);
}
///
/// Selects the previous sibling.
///
/// Specifies whether the selection is to be extended or a new selection is to be made.
public void SelectPreviousSibling(bool extendSelection)
{
SnapshotSpan selectionSpan;
if (_textView.Selection.IsEmpty || _textView.Selection.Mode == TextSelectionMode.Box)
{
selectionSpan = new SnapshotSpan(_textView.Caret.Position.BufferPosition, 0);
}
else
{
// This ignores virtual space
selectionSpan = _textView.Selection.StreamSelectionSpan.SnapshotSpan;
}
Span span = _textStructureNavigator.GetSpanOfPreviousSibling(selectionSpan);
_textView.Selection.Clear();
if (!span.IsEmpty && extendSelection)
{
// extend the selection to the start of the previous sibling
int start = (span.Start <= selectionSpan.Start) ? span.Start : selectionSpan.Start;
int end = (span.End <= selectionSpan.End) ? selectionSpan.End : span.End;
span = new Span(start, end - start);
}
TextRange previousSiblingRange = _editorPrimitives.Buffer.GetTextRange(span.Start, span.End);
_editorPrimitives.Selection.SelectRange(previousSiblingRange);
}
///
/// Selects all text.
///
public void SelectAll()
{
_editorPrimitives.Selection.SelectAll();
}
///
/// Extends the current selection span to the new selection end.
///
///
/// The new character position to extend the selection to.
///
/// is less than 0.
public void ExtendSelection(int newEnd)
{
_editorPrimitives.Selection.ExtendSelection(_editorPrimitives.Buffer.GetTextPoint(newEnd));
}
///
/// Moves the caret to the given at the given horizontal offset .
///
/// The on which to place the caret.
/// The horizontal location in the given at which to move the caret.
/// is null.
public void MoveCaret(ITextViewLine textLine, double horizontalOffset, bool extendSelection)
{
if (textLine == null)
{
throw new ArgumentNullException(nameof(textLine));
}
if (extendSelection)
{
VirtualSnapshotPoint anchor = _textView.Selection.AnchorPoint;
_textView.Caret.MoveTo(textLine, horizontalOffset);
// It is possible that the text was modified as part of this caret move so translate the anchor
_textView.Selection.Select(anchor.TranslateTo(_textView.TextSnapshot), _textView.Caret.Position.VirtualBufferPosition);
}
else
{
// Retain the selection mode, even though we are clearing it
bool inBox = _textView.Selection.Mode == TextSelectionMode.Box;
_textView.Selection.Clear();
if (inBox)
_textView.Selection.Mode = TextSelectionMode.Box;
_textView.Caret.MoveTo(textLine, horizontalOffset);
}
}
///
/// Puts the caret one line up.
///
///
/// Specifies whether selection is made as the caret is moved.
///
public void MoveLineUp(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToPreviousLine : PredefinedSelectionTransformations.MoveToPreviousLine);
_textView.Caret.EnsureVisible();
}
///
/// Puts the caret one line down.
///
///
/// Specifies whether selection is made as the caret is moved.
///
public void MoveLineDown(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToNextLine : PredefinedSelectionTransformations.MoveToNextLine);
_textView.Caret.EnsureVisible();
}
///
/// Moves the caret one page up.
///
///
/// Specifies whether selection is made as the caret is moved.
///
public void PageUp(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectPageUp : PredefinedSelectionTransformations.MovePageUp);
}
///
/// Moves the caret one page down.
///
///
/// Specifies whether selection is made as the caret is moved.
///
public void PageDown(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectPageDown : PredefinedSelectionTransformations.MovePageDown);
}
///
/// Moves the caret to the end of the view line.
///
///
/// Specifies whether selection is made as the caret is moved.
///
public void MoveToEndOfLine(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToEndOfLine : PredefinedSelectionTransformations.MoveToEndOfLine);
_textView.Caret.EnsureVisible();
}
public void MoveToHome(bool select)
{
_multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToHome : PredefinedSelectionTransformations.MoveToHome);
_textView.Caret.EnsureVisible();
}
public void MoveToStartOfLine(bool select)
{
_editorPrimitives.Caret.MoveToStartOfViewLine(select);
}
///
/// Inserts a new line at the current caret position.
///
public bool InsertNewLine()
{
Func action = () =>
{
bool editSucceeded = true;
ITextSnapshot snapshot = _textView.TextViewModel.EditBuffer.CurrentSnapshot;
using (var batchOp = _multiSelectionBroker.BeginBatchOperation())
{
var toIndent = new HashSet