From 7608530e17adada90bd3ca3a158f0f936c86fc0c Mon Sep 17 00:00:00 2001 From: vain0 Date: Wed, 18 May 2016 13:15:02 +0900 Subject: [PATCH 1/5] [WIP] Add SwapLinesUp/Down commands --- ICSharpCode.AvalonEdit/AvalonEditCommands.cs | 22 +++- .../Editing/EditingCommandHandler.cs | 117 +++++++++++++++++- 2 files changed, 136 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.AvalonEdit/AvalonEditCommands.cs b/ICSharpCode.AvalonEdit/AvalonEditCommands.cs index 399cccea..e6c90293 100644 --- a/ICSharpCode.AvalonEdit/AvalonEditCommands.cs +++ b/ICSharpCode.AvalonEdit/AvalonEditCommands.cs @@ -45,7 +45,27 @@ public static class AvalonEditCommands new InputGestureCollection { new KeyGesture(Key.D, ModifierKeys.Control) }); - + + /// + /// Swap the current line and the previous line. + /// The default shortcut is Alt+Up. + /// + public static readonly RoutedCommand SwapLinesUp = new RoutedCommand( + "SwapLinesUp", typeof(TextEditor), + new InputGestureCollection { + new KeyGesture(Key.Up, ModifierKeys.Alt) + }); + + /// + /// Swap the current line and the next line. + /// The default shortcut is Alt+Down. + /// + public static readonly RoutedCommand SwapLinesDown = new RoutedCommand( + "SwapLinesDown", typeof(TextEditor), + new InputGestureCollection { + new KeyGesture(Key.Down, ModifierKeys.Alt) + }); + /// /// Removes leading whitespace from the selected lines (or the whole document if the selection is empty). /// diff --git a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs index 73ff8b15..4a9a3ed6 100644 --- a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs +++ b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs @@ -73,13 +73,16 @@ static EditingCommandHandler() AddBinding(EditingCommands.EnterLineBreak, ModifierKeys.Shift, Key.Enter, OnEnter); AddBinding(EditingCommands.TabForward, ModifierKeys.None, Key.Tab, OnTab); AddBinding(EditingCommands.TabBackward, ModifierKeys.Shift, Key.Tab, OnShiftTab); - + CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopy, CanCutOrCopy)); CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCut, CanCutOrCopy)); CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPaste, CanPaste)); CommandBindings.Add(new CommandBinding(AvalonEditCommands.ToggleOverstrike, OnToggleOverstrike)); CommandBindings.Add(new CommandBinding(AvalonEditCommands.DeleteLine, OnDeleteLine)); + + CommandBindings.Add(new CommandBinding(AvalonEditCommands.SwapLinesUp, OnSwapLinesUp)); + CommandBindings.Add(new CommandBinding(AvalonEditCommands.SwapLinesDown, OnSwapLinesDown)); CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveLeadingWhitespace, OnRemoveLeadingWhitespace)); CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveTrailingWhitespace, OnRemoveTrailingWhitespace)); @@ -516,7 +519,65 @@ static void OnDeleteLine(object target, ExecutedRoutedEventArgs args) } } #endregion - + + #region SwapLines + static void OnSwapLinesUp(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) { + TextLocation caretLocationBackup = textArea.Caret.Location; + + var selectionLineRange = new SelectionLineRange(textArea.Caret, textArea.Selection); + if (selectionLineRange.FirstLine == 1) return; + + DocumentLine movedLine = textArea.Document.GetLineByNumber(selectionLineRange.FirstLine - 1); + DocumentLine lastLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine); + + using (textArea.Document.RunUpdate()) { + textArea.Selection = Selection.Create(textArea, movedLine.Offset, movedLine.Offset + movedLine.TotalLength); + string movedLineText = textArea.Selection.GetText(); + if (lastLine.NextLine == null) { + movedLineText = movedLineText.RotateTailLinebreak(); + } + textArea.Document.Insert(lastLine.Offset + lastLine.TotalLength, movedLineText); + textArea.RemoveSelectedText(); + + // TODO: Restore selection + textArea.Caret.Location = new TextLocation(caretLocationBackup.Line - 1, caretLocationBackup.Column); + } + args.Handled = true; + } + } + + static void OnSwapLinesDown(object target, ExecutedRoutedEventArgs args) + { + TextArea textArea = GetTextArea(target); + if (textArea != null && textArea.Document != null) + { + TextLocation caretLocationBackup = textArea.Caret.Location; + + var selectionLineRange = new SelectionLineRange(textArea.Caret, textArea.Selection); + if (selectionLineRange.LastLine == textArea.Document.LineCount) return; + + DocumentLine firstLine = textArea.Document.GetLineByNumber(selectionLineRange.FirstLine); + DocumentLine lastLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine); + DocumentLine movedLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine + 1); + + using (textArea.Document.RunUpdate()) + { + textArea.Selection = Selection.Create(textArea, lastLine.EndOffset, movedLine.EndOffset); + string movedLineText = textArea.Selection.GetText().RotateHeadLinebreak(); + textArea.Document.Insert(firstLine.Offset, movedLineText); + textArea.RemoveSelectedText(); + + // TODO: Restore selection + textArea.Caret.Location = new TextLocation(caretLocationBackup.Line + 1, caretLocationBackup.Column); + } + args.Handled = true; + } + } + #endregion + #region Remove..Whitespace / Convert Tabs-Spaces static void OnRemoveLeadingWhitespace(object target, ExecutedRoutedEventArgs args) { @@ -658,4 +719,56 @@ static void OnIndentSelection(object target, ExecutedRoutedEventArgs args) } } } + + struct SelectionLineRange + { + public SelectionLineRange(Caret caret, Selection selection) + { + if (selection.IsEmpty) { + firstLine = lastLine = caret.Line; + } else { + int l1 = selection.StartPosition.Line, l2 = selection.EndPosition.Line; + firstLine = Math.Min(l1, l2); + lastLine = Math.Max(l1, l2); + } + } + + int firstLine; + int lastLine; + public int FirstLine { get { return firstLine; } } + public int LastLine { get { return lastLine; } } + } + + static class StringExtensions + { + /// + /// Move the head linebreak to the tail. + /// + /// A string which begins with one or more linebreaks. + /// + public static string RotateHeadLinebreak(this string self) + { + int len = self.Length; + string linebreak = + (len >= 2 && self.Substring(0, 2) == "\r\n") + ? "\r\n" + : self.Substring(0, 1); + return self.Remove(0, linebreak.Length) + linebreak; + } + + /// + /// Move the tailing lineberak to the head. + /// + /// A string which ends with one or more linebreaks. + /// + public static string RotateTailLinebreak(this string self) + { + int len = self.Length; + string linebreak = + (len >= 2 && self.Substring(len - 2, 2) == "\r\n") + ? "\r\n" + : self.Substring(len - 1, 1); + return linebreak + self.Remove(len - linebreak.Length); + } + } } From 85bc8c91720a688aa3158d2dbb861a02c78246c5 Mon Sep 17 00:00:00 2001 From: vain0 Date: Thu, 19 May 2016 21:13:11 +0900 Subject: [PATCH 2/5] [WIP] Try preserve selection but this sometime doesn't work --- .../Editing/EditingCommandHandler.cs | 144 +++++++++++++----- 1 file changed, 109 insertions(+), 35 deletions(-) diff --git a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs index 4a9a3ed6..dc15126d 100644 --- a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs +++ b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs @@ -525,25 +525,23 @@ static void OnSwapLinesUp(object target, ExecutedRoutedEventArgs args) { TextArea textArea = GetTextArea(target); if (textArea != null && textArea.Document != null) { - TextLocation caretLocationBackup = textArea.Caret.Location; + using (var caretSelectionPreserver = CaretSelectionPreserver.Create(textArea)) { + var selectionLineRange = new SelectionLineRange(textArea.Caret, textArea.Selection); + if (selectionLineRange.FirstLine == 1) return; - var selectionLineRange = new SelectionLineRange(textArea.Caret, textArea.Selection); - if (selectionLineRange.FirstLine == 1) return; + DocumentLine movedLine = textArea.Document.GetLineByNumber(selectionLineRange.FirstLine - 1); + DocumentLine lastLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine); - DocumentLine movedLine = textArea.Document.GetLineByNumber(selectionLineRange.FirstLine - 1); - DocumentLine lastLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine); - - using (textArea.Document.RunUpdate()) { - textArea.Selection = Selection.Create(textArea, movedLine.Offset, movedLine.Offset + movedLine.TotalLength); - string movedLineText = textArea.Selection.GetText(); - if (lastLine.NextLine == null) { - movedLineText = movedLineText.RotateTailLinebreak(); + using (textArea.Document.RunUpdate()) { + textArea.Selection = Selection.Create(textArea, movedLine.Offset, movedLine.Offset + movedLine.TotalLength); + string movedLineText = textArea.Selection.GetText(); + if (lastLine.NextLine == null) { + movedLineText = movedLineText.RotateTailLinebreak(); + } + textArea.Document.Insert(lastLine.Offset + lastLine.TotalLength, movedLineText); + textArea.RemoveSelectedText(); + caretSelectionPreserver.MoveLine(-1); } - textArea.Document.Insert(lastLine.Offset + lastLine.TotalLength, movedLineText); - textArea.RemoveSelectedText(); - - // TODO: Restore selection - textArea.Caret.Location = new TextLocation(caretLocationBackup.Line - 1, caretLocationBackup.Column); } args.Handled = true; } @@ -552,26 +550,22 @@ static void OnSwapLinesUp(object target, ExecutedRoutedEventArgs args) static void OnSwapLinesDown(object target, ExecutedRoutedEventArgs args) { TextArea textArea = GetTextArea(target); - if (textArea != null && textArea.Document != null) - { - TextLocation caretLocationBackup = textArea.Caret.Location; - - var selectionLineRange = new SelectionLineRange(textArea.Caret, textArea.Selection); - if (selectionLineRange.LastLine == textArea.Document.LineCount) return; - - DocumentLine firstLine = textArea.Document.GetLineByNumber(selectionLineRange.FirstLine); - DocumentLine lastLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine); - DocumentLine movedLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine + 1); + if (textArea != null && textArea.Document != null) { + using (var caretSelectionPreserver = CaretSelectionPreserver.Create(textArea)) { + var selectionLineRange = new SelectionLineRange(textArea.Caret, textArea.Selection); + if (selectionLineRange.LastLine == textArea.Document.LineCount) return; - using (textArea.Document.RunUpdate()) - { - textArea.Selection = Selection.Create(textArea, lastLine.EndOffset, movedLine.EndOffset); - string movedLineText = textArea.Selection.GetText().RotateHeadLinebreak(); - textArea.Document.Insert(firstLine.Offset, movedLineText); - textArea.RemoveSelectedText(); + DocumentLine firstLine = textArea.Document.GetLineByNumber(selectionLineRange.FirstLine); + DocumentLine lastLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine); + DocumentLine movedLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine + 1); - // TODO: Restore selection - textArea.Caret.Location = new TextLocation(caretLocationBackup.Line + 1, caretLocationBackup.Column); + using (textArea.Document.RunUpdate()) { + textArea.Selection = Selection.Create(textArea, lastLine.EndOffset, movedLine.EndOffset); + string movedLineText = textArea.Selection.GetText().RotateHeadLinebreak(); + textArea.Document.Insert(firstLine.Offset, movedLineText); + textArea.RemoveSelectedText(); + caretSelectionPreserver.MoveLine(+1); + } } args.Handled = true; } @@ -720,6 +714,86 @@ static void OnIndentSelection(object target, ExecutedRoutedEventArgs args) } } + abstract class CaretSelectionPreserver + : IDisposable + { + protected CaretSelectionPreserver(TextArea _textArea) + { + isDisposed = false; + textArea = _textArea; + } + + public static CaretSelectionPreserver Create(TextArea textArea) + { + if (textArea.Selection.IsEmpty) { + return new CaretPreserver(textArea); + } else { + return new SelectionPreserver(textArea); + } + } + + public void Dispose() + { + if (isDisposed) return; + isDisposed = true; + Restore(); + } + + public abstract void Restore(); + public abstract void MoveLine(int i); + + private bool isDisposed; + + private TextArea textArea; + public TextArea TextArea { get { return textArea; } } + } + + class CaretPreserver + : CaretSelectionPreserver + { + public CaretPreserver(TextArea textArea) + : base(textArea) + { + caretLocation = textArea.Caret.Location; + } + + public override void Restore() + { + TextArea.Caret.Location = caretLocation; + } + + public override void MoveLine(int i) + { + caretLocation = new TextLocation(caretLocation.Line + i, caretLocation.Column); + } + + TextLocation caretLocation; + } + + class SelectionPreserver + : CaretSelectionPreserver + { + public SelectionPreserver(TextArea textArea) + : base(textArea) + { + startPosition = textArea.Selection.StartPosition; + endPosition = textArea.Selection.EndPosition; + } + + public override void Restore() + { + TextArea.Selection = Selection.Create(TextArea, startPosition, endPosition); + } + + public override void MoveLine(int i) + { + startPosition = new TextViewPosition(startPosition.Line + i, startPosition.Column); + endPosition = new TextViewPosition(endPosition.Line + i, endPosition.Column); + } + + TextViewPosition startPosition, endPosition; + } + struct SelectionLineRange { public SelectionLineRange(Caret caret, Selection selection) @@ -738,7 +812,7 @@ public SelectionLineRange(Caret caret, Selection selection) public int FirstLine { get { return firstLine; } } public int LastLine { get { return lastLine; } } } - + static class StringExtensions { /// From 86eb3c6ae1da9873c2b24d4d17d84f2b613af7af Mon Sep 17 00:00:00 2001 From: vain0 Date: Thu, 19 May 2016 21:15:18 +0900 Subject: [PATCH 3/5] [WIP] Sometime works, other time doesn't work, too. --- .../Editing/EditingCommandHandler.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs index dc15126d..967a082b 100644 --- a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs +++ b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs @@ -776,22 +776,22 @@ class SelectionPreserver public SelectionPreserver(TextArea textArea) : base(textArea) { - startPosition = textArea.Selection.StartPosition; - endPosition = textArea.Selection.EndPosition; + startLocation = textArea.Selection.StartPosition.Location; + endLocation = textArea.Selection.EndPosition.Location; } public override void Restore() { - TextArea.Selection = Selection.Create(TextArea, startPosition, endPosition); + TextArea.Selection = Selection.Create(TextArea, new TextViewPosition(startLocation), new TextViewPosition(endLocation)); } public override void MoveLine(int i) { - startPosition = new TextViewPosition(startPosition.Line + i, startPosition.Column); - endPosition = new TextViewPosition(endPosition.Line + i, endPosition.Column); + startLocation = new TextLocation(startLocation.Line + i, startLocation.Column); + endLocation = new TextLocation(endLocation.Line + i, endLocation.Column); } - TextViewPosition startPosition, endPosition; + TextLocation startLocation, endLocation; } struct SelectionLineRange From c15a9ca130f018dcd4e375ec6b5af8cb8107637b Mon Sep 17 00:00:00 2001 From: dgosbell Date: Fri, 18 Jun 2021 20:19:44 +1000 Subject: [PATCH 4/5] fixing the preservation of the selection position --- .../Editing/EditingCommandHandler.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs index 1d5e63eb..997a7638 100644 --- a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs +++ b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs @@ -752,6 +752,9 @@ public void Dispose() public TextArea TextArea { get { return textArea; } } } + /// + /// This class moves the current caret position when a line is moved up or down + /// class CaretPreserver : CaretSelectionPreserver { @@ -774,8 +777,17 @@ public override void MoveLine(int i) TextLocation caretLocation; } + /// + /// This class moves the current selection when the lines containing that selection + /// are moved up or down. + /// + /// NOTE: The SelectionPreserver must inherit from the CaretPreserver as if the caret + /// is not within the selection then the selection is not considered valid and is + /// cleared. By inheriting from the CaretPreserver we move both the selection and + /// the caret at the same time. + /// class SelectionPreserver - : CaretSelectionPreserver + : CaretPreserver { public SelectionPreserver(TextArea textArea) : base(textArea) @@ -786,11 +798,14 @@ public SelectionPreserver(TextArea textArea) public override void Restore() { + base.Restore(); TextArea.Selection = Selection.Create(TextArea, new TextViewPosition(startLocation), new TextViewPosition(endLocation)); } + public override void MoveLine(int i) { + base.MoveLine(i); startLocation = new TextLocation(startLocation.Line + i, startLocation.Column); endLocation = new TextLocation(endLocation.Line + i, endLocation.Column); } @@ -835,7 +850,7 @@ public static string RotateHeadLinebreak(this string self) } /// - /// Move the tailing lineberak to the head. + /// Move the tailing linebreak to the head. /// /// A string which ends with one or more linebreaks. /// From f9789a07396d10c2e847b9d3a9a3619157b1cbba Mon Sep 17 00:00:00 2001 From: dgosbell Date: Fri, 25 Jun 2021 18:58:51 +1000 Subject: [PATCH 5/5] refactoring to move CaretSelectionPreserver to its own file --- .../Editing/CaretSelectionPreserver.cs | 121 +++++++++++ .../Editing/EditingCommandHandler.cs | 201 ++++-------------- 2 files changed, 166 insertions(+), 156 deletions(-) create mode 100644 ICSharpCode.AvalonEdit/Editing/CaretSelectionPreserver.cs diff --git a/ICSharpCode.AvalonEdit/Editing/CaretSelectionPreserver.cs b/ICSharpCode.AvalonEdit/Editing/CaretSelectionPreserver.cs new file mode 100644 index 00000000..f78807e3 --- /dev/null +++ b/ICSharpCode.AvalonEdit/Editing/CaretSelectionPreserver.cs @@ -0,0 +1,121 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using ICSharpCode.AvalonEdit.Document; + +namespace ICSharpCode.AvalonEdit.Editing +{ + /// + /// Base class used by the CaretPreserver and SelectionPreserver + /// + abstract class CaretSelectionPreserver + : IDisposable + { + protected CaretSelectionPreserver(TextArea _textArea) + { + isDisposed = false; + textArea = _textArea; + } + + public static CaretSelectionPreserver Create(TextArea textArea) + { + if (textArea.Selection.IsEmpty) { + return new CaretPreserver(textArea); + } else { + return new SelectionPreserver(textArea); + } + } + + public void Dispose() + { + if (isDisposed) return; + isDisposed = true; + Restore(); + } + + public abstract void Restore(); + public abstract void MoveLine(int i); + + private bool isDisposed; + + private TextArea textArea; + public TextArea TextArea { get { return textArea; } } + } + + /// + /// This class moves the current caret position when a line is moved up or down + /// + class CaretPreserver + : CaretSelectionPreserver + { + public CaretPreserver(TextArea textArea) + : base(textArea) + { + caretLocation = textArea.Caret.Location; + } + + public override void Restore() + { + TextArea.Caret.Location = caretLocation; + } + + public override void MoveLine(int i) + { + caretLocation = new TextLocation(caretLocation.Line + i, caretLocation.Column); + } + + TextLocation caretLocation; + } + + /// + /// This class moves the current selection when the lines containing that selection + /// are moved up or down. + /// + /// NOTE: The SelectionPreserver must inherit from the CaretPreserver as if the caret + /// is not within the selection then the selection is not considered valid and is + /// cleared. By inheriting from the CaretPreserver we move both the selection and + /// the caret at the same time. + /// + class SelectionPreserver + : CaretPreserver + { + public SelectionPreserver(TextArea textArea) + : base(textArea) + { + startLocation = textArea.Selection.StartPosition.Location; + endLocation = textArea.Selection.EndPosition.Location; + } + + public override void Restore() + { + base.Restore(); + TextArea.Selection = Selection.Create(TextArea, new TextViewPosition(startLocation), new TextViewPosition(endLocation)); + } + + + public override void MoveLine(int i) + { + base.MoveLine(i); + startLocation = new TextLocation(startLocation.Line + i, startLocation.Column); + endLocation = new TextLocation(endLocation.Line + i, endLocation.Column); + } + + TextLocation startLocation, endLocation; + } +} diff --git a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs index 997a7638..91ae00b7 100644 --- a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs +++ b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs @@ -530,11 +530,13 @@ static void OnSwapLinesUp(object target, ExecutedRoutedEventArgs args) TextArea textArea = GetTextArea(target); if (textArea != null && textArea.Document != null) { using (var caretSelectionPreserver = CaretSelectionPreserver.Create(textArea)) { - var selectionLineRange = new SelectionLineRange(textArea.Caret, textArea.Selection); - if (selectionLineRange.FirstLine == 1) return; + var selectionFirstLine = textArea.Selection.IsEmpty ? textArea.Caret.Line : textArea.Selection.StartPosition.Line; + var selectionLastLine = textArea.Selection.IsEmpty ? textArea.Caret.Line : textArea.Selection.EndPosition.Line; + // we cannot move up if the selection is on the first line + if (selectionFirstLine == 1) return; - DocumentLine movedLine = textArea.Document.GetLineByNumber(selectionLineRange.FirstLine - 1); - DocumentLine lastLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine); + DocumentLine movedLine = textArea.Document.GetLineByNumber(selectionFirstLine - 1); + DocumentLine lastLine = textArea.Document.GetLineByNumber(selectionLastLine); using (textArea.Document.RunUpdate()) { textArea.Selection = Selection.Create(textArea, movedLine.Offset, movedLine.Offset + movedLine.TotalLength); @@ -556,12 +558,14 @@ static void OnSwapLinesDown(object target, ExecutedRoutedEventArgs args) TextArea textArea = GetTextArea(target); if (textArea != null && textArea.Document != null) { using (var caretSelectionPreserver = CaretSelectionPreserver.Create(textArea)) { - var selectionLineRange = new SelectionLineRange(textArea.Caret, textArea.Selection); - if (selectionLineRange.LastLine == textArea.Document.LineCount) return; + var selectionFirstLine = textArea.Selection.IsEmpty ? textArea.Caret.Line : textArea.Selection.StartPosition.Line; + var selectionLastLine = textArea.Selection.IsEmpty ? textArea.Caret.Line : textArea.Selection.EndPosition.Line; + // we cannot move down if the selection is on the last line + if (selectionLastLine == textArea.Document.LineCount) return; - DocumentLine firstLine = textArea.Document.GetLineByNumber(selectionLineRange.FirstLine); - DocumentLine lastLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine); - DocumentLine movedLine = textArea.Document.GetLineByNumber(selectionLineRange.LastLine + 1); + DocumentLine firstLine = textArea.Document.GetLineByNumber(selectionFirstLine); + DocumentLine lastLine = textArea.Document.GetLineByNumber(selectionLastLine); + DocumentLine movedLine = textArea.Document.GetLineByNumber(selectionLastLine + 1); using (textArea.Document.RunUpdate()) { textArea.Selection = Selection.Create(textArea, lastLine.EndOffset, movedLine.EndOffset); @@ -574,6 +578,38 @@ static void OnSwapLinesDown(object target, ExecutedRoutedEventArgs args) args.Handled = true; } } + + /// + /// Move the leading linebreak to the end. + /// + /// A string which starts with one or more linebreaks. + /// + private static string RotateHeadLinebreak(this string self) + { + int len = self.Length; + // check if the line break is /n or /r/n + string linebreak = + (len >= 2 && self.Substring(0, 2) == "\r\n") + ? "\r\n" + : self.Substring(0, 1); + return self.Remove(0, linebreak.Length) + linebreak; + } + + /// + /// Move the tailing linebreak to the head. + /// + /// A string which ends with one or more linebreaks. + /// + private static string RotateTailLinebreak(this string self) + { + int len = self.Length; + // check if the line break is /n or /r/n + string linebreak = + (len >= 2 && self.Substring(len - 2, 2) == "\r\n") + ? "\r\n" + : self.Substring(len - 1, 1); + return linebreak + self.Remove(len - linebreak.Length); + } #endregion #region Remove..Whitespace / Convert Tabs-Spaces @@ -717,151 +753,4 @@ static void OnIndentSelection(object target, ExecutedRoutedEventArgs args) } } } - - abstract class CaretSelectionPreserver - : IDisposable - { - protected CaretSelectionPreserver(TextArea _textArea) - { - isDisposed = false; - textArea = _textArea; - } - - public static CaretSelectionPreserver Create(TextArea textArea) - { - if (textArea.Selection.IsEmpty) { - return new CaretPreserver(textArea); - } else { - return new SelectionPreserver(textArea); - } - } - - public void Dispose() - { - if (isDisposed) return; - isDisposed = true; - Restore(); - } - - public abstract void Restore(); - public abstract void MoveLine(int i); - - private bool isDisposed; - - private TextArea textArea; - public TextArea TextArea { get { return textArea; } } - } - - /// - /// This class moves the current caret position when a line is moved up or down - /// - class CaretPreserver - : CaretSelectionPreserver - { - public CaretPreserver(TextArea textArea) - : base(textArea) - { - caretLocation = textArea.Caret.Location; - } - - public override void Restore() - { - TextArea.Caret.Location = caretLocation; - } - - public override void MoveLine(int i) - { - caretLocation = new TextLocation(caretLocation.Line + i, caretLocation.Column); - } - - TextLocation caretLocation; - } - - /// - /// This class moves the current selection when the lines containing that selection - /// are moved up or down. - /// - /// NOTE: The SelectionPreserver must inherit from the CaretPreserver as if the caret - /// is not within the selection then the selection is not considered valid and is - /// cleared. By inheriting from the CaretPreserver we move both the selection and - /// the caret at the same time. - /// - class SelectionPreserver - : CaretPreserver - { - public SelectionPreserver(TextArea textArea) - : base(textArea) - { - startLocation = textArea.Selection.StartPosition.Location; - endLocation = textArea.Selection.EndPosition.Location; - } - - public override void Restore() - { - base.Restore(); - TextArea.Selection = Selection.Create(TextArea, new TextViewPosition(startLocation), new TextViewPosition(endLocation)); - } - - - public override void MoveLine(int i) - { - base.MoveLine(i); - startLocation = new TextLocation(startLocation.Line + i, startLocation.Column); - endLocation = new TextLocation(endLocation.Line + i, endLocation.Column); - } - - TextLocation startLocation, endLocation; - } - - struct SelectionLineRange - { - public SelectionLineRange(Caret caret, Selection selection) - { - if (selection.IsEmpty) { - firstLine = lastLine = caret.Line; - } else { - int l1 = selection.StartPosition.Line, l2 = selection.EndPosition.Line; - firstLine = Math.Min(l1, l2); - lastLine = Math.Max(l1, l2); - } - } - - int firstLine; - int lastLine; - public int FirstLine { get { return firstLine; } } - public int LastLine { get { return lastLine; } } - } - - static class StringExtensions - { - /// - /// Move the head linebreak to the tail. - /// - /// A string which begins with one or more linebreaks. - /// - public static string RotateHeadLinebreak(this string self) - { - int len = self.Length; - string linebreak = - (len >= 2 && self.Substring(0, 2) == "\r\n") - ? "\r\n" - : self.Substring(0, 1); - return self.Remove(0, linebreak.Length) + linebreak; - } - - /// - /// Move the tailing linebreak to the head. - /// - /// A string which ends with one or more linebreaks. - /// - public static string RotateTailLinebreak(this string self) - { - int len = self.Length; - string linebreak = - (len >= 2 && self.Substring(len - 2, 2) == "\r\n") - ? "\r\n" - : self.Substring(len - 1, 1); - return linebreak + self.Remove(len - linebreak.Length); - } - } }