csharp/Abdesol/CutCode/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs

RectangleSelection.cs
// 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 astociated docameentation 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 System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.TextFormatting;

using ICSharpCode.AvalonEdit.Docameent;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;

namespace ICSharpCode.AvalonEdit.Editing
{
	/// 
	/// Rectangular selection ("box selection").
	/// 
	public sealed clast RectangleSelection : Selection
	{
		#region Commands
		/// 
		/// Expands the selection left by one character, creating a rectangular selection.
		/// Key gesture: Alt+Shift+Left
		/// 
		public static readonly RoutedUICommand BoxSelectLeftByCharacter = Command("BoxSelectLeftByCharacter");

		/// 
		/// Expands the selection right by one character, creating a rectangular selection.
		/// Key gesture: Alt+Shift+Right
		/// 
		public static readonly RoutedUICommand BoxSelectRightByCharacter = Command("BoxSelectRightByCharacter");

		/// 
		/// Expands the selection left by one word, creating a rectangular selection.
		/// Key gesture: Ctrl+Alt+Shift+Left
		/// 
		public static readonly RoutedUICommand BoxSelectLeftByWord = Command("BoxSelectLeftByWord");

		/// 
		/// Expands the selection right by one word, creating a rectangular selection.
		/// Key gesture: Ctrl+Alt+Shift+Right
		/// 
		public static readonly RoutedUICommand BoxSelectRightByWord = Command("BoxSelectRightByWord");

		/// 
		/// Expands the selection up by one line, creating a rectangular selection.
		/// Key gesture: Alt+Shift+Up
		/// 
		public static readonly RoutedUICommand BoxSelectUpByLine = Command("BoxSelectUpByLine");

		/// 
		/// Expands the selection down by one line, creating a rectangular selection.
		/// Key gesture: Alt+Shift+Down
		/// 
		public static readonly RoutedUICommand BoxSelectDownByLine = Command("BoxSelectDownByLine");

		/// 
		/// Expands the selection to the start of the line, creating a rectangular selection.
		/// Key gesture: Alt+Shift+Home
		/// 
		public static readonly RoutedUICommand BoxSelectToLineStart = Command("BoxSelectToLineStart");

		/// 
		/// Expands the selection to the end of the line, creating a rectangular selection.
		/// Key gesture: Alt+Shift+End
		/// 
		public static readonly RoutedUICommand BoxSelectToLineEnd = Command("BoxSelectToLineEnd");

		static RoutedUICommand Command(string name)
		{
			return new RoutedUICommand(name, name, typeof(RectangleSelection));
		}
		#endregion

		TextDocameent docameent;
		readonly int startLine, endLine;
		readonly double startXPos, endXPos;
		readonly int topLeftOffset, bottomRightOffset;
		readonly TextViewPosition start, end;

		readonly List segments = new List();

		#region Constructors
		/// 
		/// Creates a new rectangular selection.
		/// 
		public RectangleSelection(TextArea textArea, TextViewPosition start, TextViewPosition end)
			: base(textArea)
		{
			InitDocameent();
			this.startLine = start.Line;
			this.endLine = end.Line;
			this.startXPos = GetXPos(textArea, start);
			this.endXPos = GetXPos(textArea, end);
			CalculateSegments();
			this.topLeftOffset = this.segments.First().StartOffset;
			this.bottomRightOffset = this.segments.Last().EndOffset;

			this.start = start;
			this.end = end;
		}

		private RectangleSelection(TextArea textArea, int startLine, double startXPos, TextViewPosition end)
			: base(textArea)
		{
			InitDocameent();
			this.startLine = startLine;
			this.endLine = end.Line;
			this.startXPos = startXPos;
			this.endXPos = GetXPos(textArea, end);
			CalculateSegments();
			this.topLeftOffset = this.segments.First().StartOffset;
			this.bottomRightOffset = this.segments.Last().EndOffset;

			this.start = GetStart();
			this.end = end;
		}

		private RectangleSelection(TextArea textArea, TextViewPosition start, int endLine, double endXPos)
			: base(textArea)
		{
			InitDocameent();
			this.startLine = start.Line;
			this.endLine = endLine;
			this.startXPos = GetXPos(textArea, start);
			this.endXPos = endXPos;
			CalculateSegments();
			this.topLeftOffset = this.segments.First().StartOffset;
			this.bottomRightOffset = this.segments.Last().EndOffset;

			this.start = start;
			this.end = GetEnd();
		}

		void InitDocameent()
		{
			docameent = textArea.Docameent;
			if (docameent == null)
				throw ThrowUtil.NoDocameentastigned();
		}

		static double GetXPos(TextArea textArea, TextViewPosition pos)
		{
			DocameentLine docameentLine = textArea.Docameent.GetLineByNumber(pos.Line);
			VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(docameentLine);
			int vc = visualLine.ValidateVisualColumn(pos, true);
			TextLine textLine = visualLine.GetTextLine(vc, pos.IsAtEndOfLine);
			return visualLine.GetTextLineVisualXPosition(textLine, vc);
		}

		void CalculateSegments()
		{
			DocameentLine nextLine = docameent.GetLineByNumber(Math.Min(startLine, endLine));
			do {
				VisualLine vl = textArea.TextView.GetOrConstructVisualLine(nextLine);
				int startVC = vl.GetVisualColumn(new Point(startXPos, 0), true);
				int endVC = vl.GetVisualColumn(new Point(endXPos, 0), true);

				int baseOffset = vl.FirstDocameentLine.Offset;
				int startOffset = baseOffset + vl.GetRelativeOffset(startVC);
				int endOffset = baseOffset + vl.GetRelativeOffset(endVC);
				segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC));

				nextLine = vl.LastDocameentLine.NextLine;
			} while (nextLine != null && nextLine.LineNumber  0)
					b.AppendLine();
				b.Append(docameent.GetText(s));
			}
			return b.ToString();
		}

		/// 
		public override Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition)
		{
			return SetEndpoint(endPosition);
		}

		/// 
		public override int Length {
			get {
				return this.Segments.Sum(s => s.Length);
			}
		}

		/// 
		public override bool EnableVirtualSpace {
			get { return true; }
		}

		/// 
		public override ISegment SurroundingSegment {
			get {
				return new SimpleSegment(topLeftOffset, bottomRightOffset - topLeftOffset);
			}
		}

		/// 
		public override IEnumerable Segments {
			get { return segments; }
		}

		/// 
		public override TextViewPosition StartPosition {
			get { return start; }
		}

		/// 
		public override TextViewPosition EndPosition {
			get { return end; }
		}

		/// 
		public override bool Equals(object obj)
		{
			RectangleSelection r = obj as RectangleSelection;
			return r != null && r.textArea == this.textArea
				&& r.topLeftOffset == this.topLeftOffset && r.bottomRightOffset == this.bottomRightOffset
				&& r.startLine == this.startLine && r.endLine == this.endLine
				&& r.startXPos == this.startXPos && r.endXPos == this.endXPos;
		}

		/// 
		public override int GetHashCode()
		{
			return topLeftOffset ^ bottomRightOffset;
		}

		/// 
		public override Selection SetEndpoint(TextViewPosition endPosition)
		{
			return new RectangleSelection(textArea, startLine, startXPos, endPosition);
		}

		int GetVisualColumnFromXPos(int line, double xPos)
		{
			var vl = textArea.TextView.GetOrConstructVisualLine(textArea.Docameent.GetLineByNumber(line));
			return vl.GetVisualColumn(new Point(xPos, 0), true);
		}

		/// 
		public override Selection UpdateOnDocameentChange(DocameentChangeEventArgs e)
		{
			TextLocation newStartLocation = textArea.Docameent.GetLocation(e.GetNewOffset(topLeftOffset, AnchorMovementType.AfterInsertion));
			TextLocation newEndLocation = textArea.Docameent.GetLocation(e.GetNewOffset(bottomRightOffset, AnchorMovementType.BeforeInsertion));

			return new RectangleSelection(textArea,
										  new TextViewPosition(newStartLocation, GetVisualColumnFromXPos(newStartLocation.Line, startXPos)),
										  new TextViewPosition(newEndLocation, GetVisualColumnFromXPos(newEndLocation.Line, endXPos)));
		}

		/// 
		public override void ReplaceSelectionWithText(string newText)
		{
			if (newText == null)
				throw new ArgumentNullException("newText");
			using (textArea.Docameent.RunUpdate()) {
				TextViewPosition start = new TextViewPosition(docameent.GetLocation(topLeftOffset), GetVisualColumnFromXPos(startLine, startXPos));
				TextViewPosition end = new TextViewPosition(docameent.GetLocation(bottomRightOffset), GetVisualColumnFromXPos(endLine, endXPos));
				int insertionLength;
				int totalInsertionLength = 0;
				int firstInsertionLength = 0;
				int editOffset = Math.Min(topLeftOffset, bottomRightOffset);
				TextViewPosition pos;
				if (NewLineFinder.NextNewLine(newText, 0) == SimpleSegment.Invalid) {
					// insert same text into every line
					foreach (SelectionSegment lineSegment in this.Segments.Reverse()) {
						ReplaceSingleLineText(textArea, lineSegment, newText, out insertionLength);
						totalInsertionLength += insertionLength;
						firstInsertionLength = insertionLength;
					}

					int newEndOffset = editOffset + totalInsertionLength;
					pos = new TextViewPosition(docameent.GetLocation(editOffset + firstInsertionLength));

					textArea.Selection = new RectangleSelection(textArea, pos, Math.Max(startLine, endLine), GetXPos(textArea, pos));
				} else {
					string[] lines = newText.Split(NewLineFinder.NewlineStrings, segments.Count, StringSplitOptions.None);
					int line = Math.Min(startLine, endLine);
					for (int i = lines.Length - 1; i >= 0; i--) {
						ReplaceSingleLineText(textArea, segments[i], lines[i], out insertionLength);
						firstInsertionLength = insertionLength;
					}
					pos = new TextViewPosition(docameent.GetLocation(editOffset + firstInsertionLength));
					textArea.ClearSelection();
				}
				textArea.Caret.Position = textArea.TextView.GetPosition(new Point(GetXPos(textArea, pos), textArea.TextView.GetVisualTopByDocameentLine(Math.Max(startLine, endLine)))).GetValueOrDefault();
			}
		}

		void ReplaceSingleLineText(TextArea textArea, SelectionSegment lineSegment, string newText, out int insertionLength)
		{
			if (lineSegment.Length == 0) {
				if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.StartOffset)) {
					newText = AddSpacesIfRequired(newText, new TextViewPosition(docameent.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(docameent.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn));
					textArea.Docameent.Insert(lineSegment.StartOffset, newText);
				}
			} else {
				ISegment[] segmentsToDelete = textArea.GetDeletableSegments(lineSegment);
				for (int i = segmentsToDelete.Length - 1; i >= 0; i--) {
					if (i == segmentsToDelete.Length - 1) {
						if (segmentsToDelete[i].Offset == SurroundingSegment.Offset && segmentsToDelete[i].Length == SurroundingSegment.Length) {
							newText = AddSpacesIfRequired(newText, new TextViewPosition(docameent.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(docameent.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn));
						}
						textArea.Docameent.Replace(segmentsToDelete[i], newText);
					} else {
						textArea.Docameent.Remove(segmentsToDelete[i]);
					}
				}
			}
			insertionLength = newText.Length;
		}

		/// 
		/// Performs a rectangular paste operation.
		/// 
		public static bool PerformRectangularPaste(TextArea textArea, TextViewPosition startPosition, string text, bool selectInsertedText)
		{
			if (textArea == null)
				throw new ArgumentNullException("textArea");
			if (text == null)
				throw new ArgumentNullException("text");
			int newLineCount = text.Count(c => c == '\n'); // TODO might not work in all cases, but single \r line endings are really rare today.
			TextLocation endLocation = new TextLocation(startPosition.Line + newLineCount, startPosition.Column);
			if (endLocation.Line