mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
9.5 KiB
279 lines
9.5 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Text; |
|
|
|
using ICSharpCode.AvalonEdit.Document; |
|
using ICSharpCode.AvalonEdit.Utils; |
|
|
|
namespace ICSharpCode.AvalonEdit.Editing |
|
{ |
|
/// <summary> |
|
/// Rectangular selection. |
|
/// </summary> |
|
public sealed class RectangleSelection : Selection |
|
{ |
|
TextDocument document; |
|
|
|
/// <summary> |
|
/// Gets the start position of the selection. |
|
/// </summary> |
|
public int StartOffset { get; private set; } |
|
|
|
/// <summary> |
|
/// Gets the end position of the selection. |
|
/// </summary> |
|
public int EndOffset { get; private set; } |
|
|
|
/// <summary> |
|
/// Creates a new rectangular selection. |
|
/// </summary> |
|
public RectangleSelection(TextDocument document, int start, int end) |
|
{ |
|
if (document == null) |
|
throw new ArgumentNullException("document"); |
|
this.document = document; |
|
this.StartOffset = start; |
|
this.EndOffset = end; |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override bool IsEmpty { |
|
get { |
|
TextLocation start = document.GetLocation(StartOffset); |
|
TextLocation end = document.GetLocation(EndOffset); |
|
return start.Column == end.Column; |
|
} |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override bool Contains(int offset) |
|
{ |
|
if (Math.Min(StartOffset, EndOffset) <= offset && offset <= Math.Max(StartOffset, EndOffset)) { |
|
foreach (ISegment s in this.Segments) { |
|
if (s.Contains(offset)) |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override string GetText(TextDocument document) |
|
{ |
|
StringBuilder b = new StringBuilder(); |
|
foreach (ISegment s in this.Segments) { |
|
if (b.Length > 0) |
|
b.AppendLine(); |
|
b.Append(document.GetText(s)); |
|
} |
|
return b.ToString(); |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override Selection StartSelectionOrSetEndpoint(int startOffset, int newEndOffset) |
|
{ |
|
return SetEndpoint(newEndOffset); |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override int Length { |
|
get { |
|
return this.Segments.Sum(s => s.Length); |
|
} |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override ISegment SurroundingSegment { |
|
get { |
|
return new SimpleSegment(Math.Min(StartOffset, EndOffset), Math.Abs(EndOffset - StartOffset)); |
|
} |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override IEnumerable<ISegment> Segments { |
|
get { |
|
TextLocation start = document.GetLocation(StartOffset); |
|
TextLocation end = document.GetLocation(EndOffset); |
|
DocumentLine line = document.GetLineByNumber(Math.Min(start.Line, end.Line)); |
|
int numberOfLines = Math.Abs(start.Line - end.Line); |
|
int startCol = Math.Min(start.Column, end.Column); |
|
int endCol = Math.Max(start.Column, end.Column); |
|
for (int i = 0; i <= numberOfLines; i++) { |
|
if (line.Length + 1 >= startCol) { |
|
int thisLineEndCol = Math.Min(endCol, line.Length + 1); |
|
yield return new SimpleSegment(line.Offset + startCol - 1, thisLineEndCol - startCol); |
|
} |
|
line = line.NextLine; |
|
} |
|
} |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override bool Equals(object obj) |
|
{ |
|
RectangleSelection r = obj as RectangleSelection; |
|
return r != null && r.document == this.document && r.StartOffset == this.StartOffset && r.EndOffset == this.EndOffset; |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override int GetHashCode() |
|
{ |
|
return StartOffset ^ EndOffset; |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override Selection SetEndpoint(int newEndOffset) |
|
{ |
|
return new RectangleSelection(this.document, this.StartOffset, newEndOffset); |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e) |
|
{ |
|
return new RectangleSelection(document, |
|
e.GetNewOffset(StartOffset, AnchorMovementType.AfterInsertion), |
|
e.GetNewOffset(EndOffset, AnchorMovementType.BeforeInsertion)); |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override void ReplaceSelectionWithText(TextArea textArea, string newText) |
|
{ |
|
if (textArea == null) |
|
throw new ArgumentNullException("textArea"); |
|
if (newText == null) |
|
throw new ArgumentNullException("newText"); |
|
using (textArea.Document.RunUpdate()) { |
|
TextLocation start = document.GetLocation(StartOffset); |
|
TextLocation end = document.GetLocation(EndOffset); |
|
int editColumn = Math.Min(start.Column, end.Column); |
|
if (NewLineFinder.NextNewLine(newText, 0) == SimpleSegment.Invalid) { |
|
// insert same text into every line |
|
foreach (ISegment lineSegment in this.Segments.Reverse()) { |
|
ReplaceSingleLineText(textArea, lineSegment, newText); |
|
} |
|
|
|
TextLocation newStart = new TextLocation(start.Line, editColumn + newText.Length); |
|
TextLocation newEnd = new TextLocation(end.Line, editColumn + newText.Length); |
|
textArea.Caret.Location = newEnd; |
|
textArea.Selection = new RectangleSelection(document, document.GetOffset(newStart), document.GetOffset(newEnd)); |
|
} else { |
|
// convert all segment start/ends to anchors |
|
var segments = this.Segments.Select(s => new AnchorSegment(this.document, s)).ToList(); |
|
SimpleSegment ds = NewLineFinder.NextNewLine(newText, 0); |
|
// we'll check whether all lines have the same length. If so, we can continue using a rectangular selection. |
|
int commonLength = -1; |
|
// now insert lines into rectangular selection |
|
int lastDelimiterEnd = 0; |
|
bool isAtEnd = false; |
|
int i; |
|
for (i = 0; i < segments.Count; i++) { |
|
string lineText; |
|
if (ds == SimpleSegment.Invalid || (i == segments.Count - 1)) { |
|
lineText = newText.Substring(lastDelimiterEnd); |
|
isAtEnd = true; |
|
// if we have more lines to insert than this selection is long, we cannot continue using a rectangular selection |
|
if (ds != SimpleSegment.Invalid) |
|
commonLength = -1; |
|
} else { |
|
lineText = newText.Substring(lastDelimiterEnd, ds.Offset - lastDelimiterEnd); |
|
} |
|
if (i == 0) { |
|
commonLength = lineText.Length; |
|
} else if (commonLength != lineText.Length) { |
|
commonLength = -1; |
|
} |
|
ReplaceSingleLineText(textArea, segments[i], lineText); |
|
if (isAtEnd) |
|
break; |
|
lastDelimiterEnd = ds.EndOffset; |
|
ds = NewLineFinder.NextNewLine(newText, lastDelimiterEnd); |
|
} |
|
if (commonLength >= 0) { |
|
TextLocation newStart = new TextLocation(start.Line, editColumn + commonLength); |
|
TextLocation newEnd = new TextLocation(start.Line + i, editColumn + commonLength); |
|
textArea.Selection = new RectangleSelection(document, document.GetOffset(newStart), document.GetOffset(newEnd)); |
|
} else { |
|
textArea.Selection = Selection.Empty; |
|
} |
|
} |
|
} |
|
} |
|
|
|
static void ReplaceSingleLineText(TextArea textArea, ISegment lineSegment, string newText) |
|
{ |
|
if (lineSegment.Length == 0) { |
|
if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.Offset)) { |
|
textArea.Document.Insert(lineSegment.Offset, newText); |
|
} |
|
} else { |
|
ISegment[] segmentsToDelete = textArea.GetDeletableSegments(lineSegment); |
|
for (int i = segmentsToDelete.Length - 1; i >= 0; i--) { |
|
if (i == segmentsToDelete.Length - 1) { |
|
textArea.Document.Replace(segmentsToDelete[i], newText); |
|
} else { |
|
textArea.Document.Remove(segmentsToDelete[i]); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Performs a rectangular paste operation. |
|
/// </summary> |
|
public static bool PerformRectangularPaste(TextArea textArea, int startOffset, 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'); |
|
TextLocation startLocation = textArea.Document.GetLocation(startOffset); |
|
TextLocation endLocation = new TextLocation(startLocation.Line + newLineCount, startLocation.Column); |
|
if (endLocation.Line <= textArea.Document.LineCount) { |
|
int endOffset = textArea.Document.GetOffset(endLocation); |
|
if (textArea.Document.GetLocation(endOffset) == endLocation) { |
|
RectangleSelection rsel = new RectangleSelection(textArea.Document, startOffset, endOffset); |
|
rsel.ReplaceSelectionWithText(textArea, text); |
|
if (selectInsertedText && textArea.Selection is RectangleSelection) { |
|
RectangleSelection sel = (RectangleSelection)textArea.Selection; |
|
textArea.Selection = new RectangleSelection(textArea.Document, startOffset, sel.EndOffset); |
|
} |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the name of the entry in the DataObject that signals rectangle selections. |
|
/// </summary> |
|
public const string RectangularSelectionDataType = "AvalonEditRectangularSelection"; |
|
|
|
/// <inheritdoc/> |
|
public override System.Windows.DataObject CreateDataObject(TextArea textArea) |
|
{ |
|
var data = base.CreateDataObject(textArea); |
|
|
|
MemoryStream isRectangle = new MemoryStream(1); |
|
isRectangle.WriteByte(1); |
|
data.SetData(RectangularSelectionDataType, isRectangle, false); |
|
return data; |
|
} |
|
|
|
/// <inheritdoc/> |
|
public override string ToString() |
|
{ |
|
// It's possible that ToString() gets called on old (invalid) selections, e.g. for "change from... to..." debug message |
|
// make sure we don't crash even when the desired locations don't exist anymore. |
|
if (StartOffset < document.TextLength && EndOffset < document.TextLength) |
|
return "[RectangleSelection " + document.GetLocation(StartOffset) + " to " + document.GetLocation(EndOffset) + "]"; |
|
else |
|
return "[RectangleSelection " + StartOffset + " to " + EndOffset + "]"; |
|
} |
|
} |
|
}
|
|
|