diff --git a/ICSharpCode.AvalonEdit/Editing/Caret.cs b/ICSharpCode.AvalonEdit/Editing/Caret.cs index d8df683..2149bdf 100644 --- a/ICSharpCode.AvalonEdit/Editing/Caret.cs +++ b/ICSharpCode.AvalonEdit/Editing/Caret.cs @@ -281,14 +281,14 @@ namespace ICSharpCode.AvalonEdit.Editing int caretOffset = textView.Document.GetOffset(position); int firstDocumentLineOffset = visualLine.FirstDocumentLine.Offset; - position.VisualColumn = visualLine.ValidateVisualColumn(position, textArea.Options.EnableVirtualSpace); + position.VisualColumn = visualLine.ValidateVisualColumn(position, textArea.Selection.EnableVirtualSpace); // search possible caret positions - int newVisualColumnForwards = visualLine.GetNextCaretPosition(position.VisualColumn - 1, LogicalDirection.Forward, CaretPositioningMode.Normal); + int newVisualColumnForwards = visualLine.GetNextCaretPosition(position.VisualColumn - 1, LogicalDirection.Forward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace); // If position.VisualColumn was valid, we're done with validation. if (newVisualColumnForwards != position.VisualColumn) { // also search backwards so that we can pick the better match - int newVisualColumnBackwards = visualLine.GetNextCaretPosition(position.VisualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.Normal); + int newVisualColumnBackwards = visualLine.GetNextCaretPosition(position.VisualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.Normal, textArea.Selection.EnableVirtualSpace); if (newVisualColumnForwards < 0 && newVisualColumnBackwards < 0) throw ThrowUtil.NoValidCaretPosition(); diff --git a/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs b/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs index 5d458d9..4d995f5 100644 --- a/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs +++ b/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs @@ -180,7 +180,7 @@ namespace ICSharpCode.AvalonEdit.Editing #region Home/End static void MoveCaretToStartOfLine(TextArea textArea, VisualLine visualLine) { - int newVC = visualLine.GetNextCaretPosition(-1, LogicalDirection.Forward, CaretPositioningMode.WordStart); + int newVC = visualLine.GetNextCaretPosition(-1, LogicalDirection.Forward, CaretPositioningMode.WordStart, textArea.Selection.EnableVirtualSpace); if (newVC < 0) throw ThrowUtil.NoValidCaretPosition(); // when the caret is already at the start of the text, jump to start before whitespace @@ -201,7 +201,7 @@ namespace ICSharpCode.AvalonEdit.Editing #region By-character / By-word movement static void MoveCaretRight(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode) { - int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode); + int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace); if (pos >= 0) { SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset); } else { @@ -209,7 +209,7 @@ namespace ICSharpCode.AvalonEdit.Editing DocumentLine nextDocumentLine = visualLine.LastDocumentLine.NextLine; if (nextDocumentLine != null) { VisualLine nextLine = textArea.TextView.GetOrConstructVisualLine(nextDocumentLine); - pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode); + pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode, textArea.Selection.EnableVirtualSpace); if (pos < 0) throw ThrowUtil.NoValidCaretPosition(); SetCaretPosition(textArea, pos, nextLine.GetRelativeOffset(pos) + nextLine.FirstDocumentLine.Offset); @@ -223,7 +223,7 @@ namespace ICSharpCode.AvalonEdit.Editing static void MoveCaretLeft(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode) { - int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode); + int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace); if (pos >= 0) { SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset); } else { @@ -231,7 +231,7 @@ namespace ICSharpCode.AvalonEdit.Editing DocumentLine previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine; if (previousDocumentLine != null) { VisualLine previousLine = textArea.TextView.GetOrConstructVisualLine(previousDocumentLine); - pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode); + pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode, textArea.Selection.EnableVirtualSpace); if (pos < 0) throw ThrowUtil.NoValidCaretPosition(); SetCaretPosition(textArea, pos, previousLine.GetRelativeOffset(pos) + previousLine.FirstDocumentLine.Offset); @@ -307,7 +307,7 @@ namespace ICSharpCode.AvalonEdit.Editing } if (targetLine != null) { double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle); - int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos)); + int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos), textArea.Selection.EnableVirtualSpace); SetCaretPosition(textArea, targetVisualLine, targetLine, newVisualColumn, false); textArea.Caret.DesiredXPos = xPos; } diff --git a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs index dbf3741..18b8591 100644 --- a/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs +++ b/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs @@ -398,7 +398,7 @@ namespace ICSharpCode.AvalonEdit.Editing if (textArea.ReadOnlySectionProvider.CanInsert(currentLine.Offset)) { textArea.Document.Insert(currentLine.Offset, text); } - } else if (rectangular && textArea.Selection.IsEmpty) { + } else if (rectangular && textArea.Selection.IsEmpty && !(textArea.Selection is RectangleSelection)) { if (!RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, false)) textArea.ReplaceSelectionWithText(text); } else { diff --git a/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs b/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs index 341befb..c201780 100644 --- a/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs +++ b/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Windows; using System.Windows.Media.TextFormatting; + using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.AvalonEdit.Utils; @@ -42,8 +43,8 @@ namespace ICSharpCode.AvalonEdit.Editing InitDocument(); this.startLine = start.Line; this.endLine = end.Line; - this.startXPos = GetXPos(start); - this.endXPos = GetXPos(end); + this.startXPos = GetXPos(textArea, start); + this.endXPos = GetXPos(textArea, end); this.startOffset = document.GetOffset(start); this.endOffset = document.GetOffset(end); CalculateSegments(); @@ -56,7 +57,7 @@ namespace ICSharpCode.AvalonEdit.Editing this.startLine = startLine; this.endLine = end.Line; this.startXPos = startXPos; - this.endXPos = GetXPos(end); + this.endXPos = GetXPos(textArea, end); this.startOffset = startOffset; this.endOffset = document.GetOffset(end); CalculateSegments(); @@ -68,19 +69,26 @@ namespace ICSharpCode.AvalonEdit.Editing InitDocument(); this.startLine = start.Line; this.endLine = endLine; - this.startXPos = GetXPos(start); + this.startXPos = GetXPos(textArea, start); this.endXPos = endXPos; this.startOffset = document.GetOffset(start); this.endOffset = endOffset; CalculateSegments(); } - double GetXPos(TextViewPosition pos) + static double GetXPos(TextArea textArea, TextViewPosition pos) { - DocumentLine documentLine = document.GetLineByNumber(pos.Line); + DocumentLine documentLine = textArea.Document.GetLineByNumber(pos.Line); VisualLine visualLine = textArea.TextView.GetOrConstructVisualLine(documentLine); - TextLine textLine = visualLine.GetTextLine(pos.VisualColumn); - return visualLine.GetTextLineVisualXPosition(textLine, pos.VisualColumn); + int vc = visualLine.ValidateVisualColumn(pos, true); + TextLine textLine = visualLine.GetTextLine(vc); + return visualLine.GetTextLineVisualXPosition(textLine, vc); + } + + int GetVisualColumnFromXPos(int line, double xPos) + { + var vl = textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(line)); + return vl.GetVisualColumn(new Point(xPos, 0), true); } /// @@ -108,6 +116,11 @@ namespace ICSharpCode.AvalonEdit.Editing } } + /// + public override bool EnableVirtualSpace { + get { return true; } + } + /// public override ISegment SurroundingSegment { get { @@ -120,6 +133,7 @@ namespace ICSharpCode.AvalonEdit.Editing get { return segments; } } + void CalculateSegments() { DocumentLine nextLine = document.GetLineByNumber(Math.Min(startLine, endLine)); @@ -131,10 +145,10 @@ namespace ICSharpCode.AvalonEdit.Editing int baseOffset = vl.FirstDocumentLine.Offset; int startOffset = baseOffset + vl.GetRelativeOffset(startVC); int endOffset = baseOffset + vl.GetRelativeOffset(endVC); - segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC, true)); + segments.Add(new SelectionSegment(startOffset, startVC, endOffset, endVC)); nextLine = vl.LastDocumentLine.NextLine; - } while (nextLine.LineNumber <= Math.Max(startLine, endLine)); + } while (nextLine != null && nextLine.LineNumber <= Math.Max(startLine, endLine)); } /// @@ -156,97 +170,81 @@ namespace ICSharpCode.AvalonEdit.Editing /// public override Selection SetEndpoint(TextViewPosition endPosition) { - return new RectangleSelection(textArea, startLine, startXPos, startOffset, endPosition); + var r = new RectangleSelection(textArea, startLine, startXPos, startOffset, endPosition); + return r; } /// public override Selection UpdateOnDocumentChange(DocumentChangeEventArgs e) { - throw new NotImplementedException(); -// return new RectangleSelection(textArea, -// e.GetNewOffset(StartOffset, AnchorMovementType.AfterInsertion), -// e.GetNewOffset(EndOffset, AnchorMovementType.BeforeInsertion)); + TextLocation newStartLocation = textArea.Document.GetLocation(e.GetNewOffset(startOffset, AnchorMovementType.AfterInsertion)); + TextLocation newEndLocation = textArea.Document.GetLocation(e.GetNewOffset(endOffset, 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) { - throw new NotImplementedException(); /* 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); + TextViewPosition start = new TextViewPosition(document.GetLocation(startOffset), GetVisualColumnFromXPos(startLine, startXPos)); + TextViewPosition end = new TextViewPosition(document.GetLocation(endOffset), GetVisualColumnFromXPos(endLine, endXPos)); + int insertionLength; + int totalInsertionLength = 0; + int firstInsertionLength = 0; + int editOffset = Math.Min(startOffset, endOffset); if (NewLineFinder.NextNewLine(newText, 0) == SimpleSegment.Invalid) { // insert same text into every line - foreach (ISegment lineSegment in this.Segments.Reverse()) { - ReplaceSingleLineText(textArea, lineSegment, newText); + foreach (SelectionSegment lineSegment in this.Segments.Reverse()) { + ReplaceSingleLineText(textArea, lineSegment, newText, out insertionLength); + totalInsertionLength += insertionLength; + firstInsertionLength = insertionLength; } - 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)); + int newEndOffset = editOffset + totalInsertionLength; + TextViewPosition pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength)); + + textArea.Selection = new RectangleSelection(textArea, pos, Math.Max(startLine, endLine), GetXPos(textArea, pos), newEndOffset); + textArea.Caret.Position = textArea.TextView.GetPosition(new Point(GetXPos(textArea, pos), textArea.TextView.GetVisualTopByDocumentLine(Math.Max(startLine, endLine)))).GetValueOrDefault(); } 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; + string[] lines = newText.Split(new[] { "\r\n", "\r", "\n" }, 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; } + TextViewPosition pos = new TextViewPosition(document.GetLocation(editOffset + firstInsertionLength)); + textArea.ClearSelection(); + textArea.Caret.Position = textArea.TextView.GetPosition(new Point(GetXPos(textArea, pos), textArea.TextView.GetVisualTopByDocumentLine(Math.Max(startLine, endLine)))).GetValueOrDefault(); } - }*/ + } } - static void ReplaceSingleLineText(TextArea textArea, ISegment lineSegment, string newText) + void ReplaceSingleLineText(TextArea textArea, SelectionSegment lineSegment, string newText, out int insertionLength) { if (lineSegment.Length == 0) { - if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.Offset)) { - textArea.Document.Insert(lineSegment.Offset, newText); + if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.StartOffset)) { + newText = AddSpacesIfRequired(newText, new TextViewPosition(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn)); + textArea.Document.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(document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn)); + } textArea.Document.Replace(segmentsToDelete[i], newText); } else { textArea.Document.Remove(segmentsToDelete[i]); } } } + insertionLength = newText.Length; } /// @@ -258,12 +256,12 @@ namespace ICSharpCode.AvalonEdit.Editing throw new ArgumentNullException("textArea"); if (text == null) throw new ArgumentNullException("text"); - int newLineCount = text.Count(c => c == '\n'); + 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 <= textArea.Document.LineCount) { int endOffset = textArea.Document.GetOffset(endLocation); - if (textArea.Document.GetLocation(endOffset) == endLocation) { - RectangleSelection rsel = new RectangleSelection(textArea, startPosition, new TextViewPosition(endLocation)); + if (textArea.Selection.EnableVirtualSpace || textArea.Document.GetLocation(endOffset) == endLocation) { + RectangleSelection rsel = new RectangleSelection(textArea, startPosition, endLocation.Line, GetXPos(textArea, startPosition), endOffset); rsel.ReplaceSelectionWithText(text); if (selectInsertedText && textArea.Selection is RectangleSelection) { RectangleSelection sel = (RectangleSelection)textArea.Selection; @@ -296,7 +294,7 @@ namespace ICSharpCode.AvalonEdit.Editing { // 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. - return "[RectangleSelection " + startOffset + " to " + endOffset + "]"; + return string.Format("[RectangleSelection {0} {1} {2} to {3} {4} {5}]", startLine, startOffset, startXPos, endLine, endOffset, endXPos); } } } diff --git a/ICSharpCode.AvalonEdit/Editing/Selection.cs b/ICSharpCode.AvalonEdit/Editing/Selection.cs index 354c0ee..0a11937 100644 --- a/ICSharpCode.AvalonEdit/Editing/Selection.cs +++ b/ICSharpCode.AvalonEdit/Editing/Selection.cs @@ -81,7 +81,7 @@ namespace ICSharpCode.AvalonEdit.Editing internal string AddSpacesIfRequired(string newText, TextViewPosition pos) { - if (textArea.Options.EnableVirtualSpace && !string.IsNullOrEmpty(newText)) { + if (EnableVirtualSpace && !string.IsNullOrEmpty(newText) && newText != "\r\n" && newText != "\n" && newText != "\r") { var line = textArea.Document.GetLineByNumber(pos.Line); string lineText = textArea.Document.GetText(line); var vLine = textArea.TextView.GetOrConstructVisualLine(line); @@ -112,6 +112,10 @@ namespace ICSharpCode.AvalonEdit.Editing get { return Length == 0; } } + public virtual bool EnableVirtualSpace { + get { return textArea.Options.EnableVirtualSpace; } + } + /// /// Gets the selection length. /// diff --git a/ICSharpCode.AvalonEdit/Editing/SelectionColorizer.cs b/ICSharpCode.AvalonEdit/Editing/SelectionColorizer.cs index 7af054e..e75f043 100644 --- a/ICSharpCode.AvalonEdit/Editing/SelectionColorizer.cs +++ b/ICSharpCode.AvalonEdit/Editing/SelectionColorizer.cs @@ -28,19 +28,24 @@ namespace ICSharpCode.AvalonEdit.Editing int lineStartOffset = context.VisualLine.FirstDocumentLine.Offset; int lineEndOffset = context.VisualLine.LastDocumentLine.Offset + context.VisualLine.LastDocumentLine.TotalLength; - foreach (var segment in textArea.Selection.Segments) { + foreach (SelectionSegment segment in textArea.Selection.Segments) { int segmentStart = segment.StartOffset; int segmentEnd = segment.EndOffset; if (segmentEnd <= lineStartOffset) continue; if (segmentStart >= lineEndOffset) continue; - int startColumn = segment.StartVisualColumn; - int endColumn = segment.EndVisualColumn; - if (startColumn < 0) - startColumn = context.VisualLine.GetVisualColumn(Math.Max(0, segmentStart - lineStartOffset)); - if (endColumn < 0) - endColumn = context.VisualLine.GetVisualColumn(segmentEnd - lineStartOffset); + int startColumn; + if (segmentStart < lineStartOffset) + startColumn = 0; + else + startColumn = context.VisualLine.ValidateVisualColumn(segment.StartOffset, segment.StartVisualColumn, textArea.Selection.EnableVirtualSpace); + + int endColumn; + if (segmentEnd > lineEndOffset) + endColumn = textArea.Selection.EnableVirtualSpace ? int.MaxValue : context.VisualLine.VisualLengthWithEndOfLineMarker; + else + endColumn = context.VisualLine.ValidateVisualColumn(segment.EndOffset, segment.EndVisualColumn, textArea.Selection.EnableVirtualSpace); ChangeVisualElements( startColumn, endColumn, diff --git a/ICSharpCode.AvalonEdit/Editing/SelectionLayer.cs b/ICSharpCode.AvalonEdit/Editing/SelectionLayer.cs index 1e47cc0..2c56008 100644 --- a/ICSharpCode.AvalonEdit/Editing/SelectionLayer.cs +++ b/ICSharpCode.AvalonEdit/Editing/SelectionLayer.cs @@ -39,7 +39,7 @@ namespace ICSharpCode.AvalonEdit.Editing BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder(); geoBuilder.AlignToMiddleOfPixels = true; - geoBuilder.ExtendToFullWidthAtLineEnd = textArea.Options.EnableVirtualSpace; + geoBuilder.ExtendToFullWidthAtLineEnd = textArea.Selection.EnableVirtualSpace; geoBuilder.CornerRadius = textArea.SelectionCornerRadius; foreach (var segment in textArea.Selection.Segments) { geoBuilder.AddSegment(textView, segment); diff --git a/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs b/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs index b27b2e3..915e430 100644 --- a/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs +++ b/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs @@ -9,8 +9,8 @@ using System.Runtime.InteropServices; using System.Windows; using System.Windows.Documents; using System.Windows.Input; +using System.Windows.Media.TextFormatting; using System.Windows.Threading; - using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.AvalonEdit.Utils; @@ -454,11 +454,11 @@ namespace ICSharpCode.AvalonEdit.Editing pos += textView.ScrollOffset; VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y); if (line != null) { - int visualColumn = line.GetVisualColumn(pos); - int wordStartVC = line.GetNextCaretPosition(visualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.WordStartOrSymbol); + int visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace); + int wordStartVC = line.GetNextCaretPosition(visualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.WordStartOrSymbol, textArea.Selection.EnableVirtualSpace); if (wordStartVC == -1) wordStartVC = 0; - int wordEndVC = line.GetNextCaretPosition(wordStartVC, LogicalDirection.Forward, CaretPositioningMode.WordBorderOrSymbol); + int wordEndVC = line.GetNextCaretPosition(wordStartVC, LogicalDirection.Forward, CaretPositioningMode.WordBorderOrSymbol, textArea.Selection.EnableVirtualSpace); if (wordEndVC == -1) wordEndVC = line.VisualLength; int relOffset = line.FirstDocumentLine.Offset; @@ -507,7 +507,27 @@ namespace ICSharpCode.AvalonEdit.Editing pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon; VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y); if (line != null) { - visualColumn = line.GetVisualColumn(pos); + visualColumn = line.GetVisualColumn(pos, textArea.Selection.EnableVirtualSpace); + return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset; + } + return -1; + } + + int GetOffsetFromMousePositionFirstTextLineOnly(Point positionRelativeToTextView, out int visualColumn) + { + visualColumn = 0; + TextView textView = textArea.TextView; + Point pos = positionRelativeToTextView; + if (pos.Y < 0) + pos.Y = 0; + if (pos.Y > textView.ActualHeight) + pos.Y = textView.ActualHeight; + pos += textView.ScrollOffset; + if (pos.Y > textView.DocumentHeight) + pos.Y = textView.DocumentHeight - ExtensionMethods.Epsilon; + VisualLine line = textView.GetVisualLineFromVisualTop(pos.Y); + if (line != null) { + visualColumn = line.GetVisualColumn(line.TextLines.First(), positionRelativeToTextView.X, textArea.Selection.EnableVirtualSpace); return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset; } return -1; @@ -548,7 +568,11 @@ namespace ICSharpCode.AvalonEdit.Editing void SetCaretOffsetToMousePosition(MouseEventArgs e, ISegment allowedSegment) { int visualColumn; - int offset = GetOffsetFromMousePosition(e, out visualColumn); + int offset; + if (mode == SelectionMode.Rectangular) + offset = GetOffsetFromMousePositionFirstTextLineOnly(e.GetPosition(textArea.TextView), out visualColumn); + else + offset = GetOffsetFromMousePosition(e, out visualColumn); if (allowedSegment != null) { offset = offset.CoerceValue(allowedSegment.Offset, allowedSegment.EndOffset); } diff --git a/ICSharpCode.AvalonEdit/Editing/SelectionSegment.cs b/ICSharpCode.AvalonEdit/Editing/SelectionSegment.cs index 729c325..eff919b 100644 --- a/ICSharpCode.AvalonEdit/Editing/SelectionSegment.cs +++ b/ICSharpCode.AvalonEdit/Editing/SelectionSegment.cs @@ -14,8 +14,9 @@ namespace ICSharpCode.AvalonEdit.Editing readonly int startOffset, endOffset; readonly int startVC, endVC; - public bool AllowVirtualSpace { get; private set; } - + /// + /// Creates a SelectionSegment from two offsets. + /// public SelectionSegment(int startOffset, int endOffset) { this.startOffset = Math.Min(startOffset, endOffset); @@ -23,9 +24,12 @@ namespace ICSharpCode.AvalonEdit.Editing this.startVC = this.endVC = -1; } - public SelectionSegment(int startOffset, int startVC, int endOffset, int endVC, bool allowVirtualSpace) + /// + /// Creates a SelectionSegment from two offsets and visual columns. + /// + public SelectionSegment(int startOffset, int startVC, int endOffset, int endVC) { - if (startOffset <= endOffset) { + if (startOffset < endOffset || (startOffset == endOffset && startVC <= endVC)) { this.startOffset = startOffset; this.startVC = startVC; this.endOffset = endOffset; @@ -36,25 +40,37 @@ namespace ICSharpCode.AvalonEdit.Editing this.endOffset = startOffset; this.endVC = startVC; } - this.AllowVirtualSpace = allowVirtualSpace; } + /// + /// Gets the start offset. + /// public int StartOffset { get { return startOffset; } } + /// + /// Gets the end offset. + /// public int EndOffset { get { return endOffset; } } + /// + /// Gets the start visual column. + /// public int StartVisualColumn { get { return startVC; } } + /// + /// Gets the end visual column. + /// public int EndVisualColumn { get { return endVC; } } + /// int ISegment.Offset { get { return startOffset; } } @@ -64,6 +80,7 @@ namespace ICSharpCode.AvalonEdit.Editing get { return endOffset - startOffset; } } + /// public override string ToString() { return string.Format("[SelectionSegment StartOffset={0}, EndOffset={1}, StartVC={2}, EndVC={3}]", startOffset, endOffset, startVC, endVC); diff --git a/ICSharpCode.AvalonEdit/Editing/SimpleSelection.cs b/ICSharpCode.AvalonEdit/Editing/SimpleSelection.cs index f2a88c9..2da357a 100644 --- a/ICSharpCode.AvalonEdit/Editing/SimpleSelection.cs +++ b/ICSharpCode.AvalonEdit/Editing/SimpleSelection.cs @@ -33,7 +33,7 @@ namespace ICSharpCode.AvalonEdit.Editing /// public override IEnumerable Segments { get { - return ExtensionMethods.Sequence(new SelectionSegment(startOffset, start.VisualColumn, endOffset, end.VisualColumn, textArea.Options.EnableVirtualSpace)); + return ExtensionMethods.Sequence(new SelectionSegment(startOffset, start.VisualColumn, endOffset, end.VisualColumn)); } } diff --git a/ICSharpCode.AvalonEdit/Editing/TextArea.cs b/ICSharpCode.AvalonEdit/Editing/TextArea.cs index d8f26d5..a8dc300 100644 --- a/ICSharpCode.AvalonEdit/Editing/TextArea.cs +++ b/ICSharpCode.AvalonEdit/Editing/TextArea.cs @@ -378,6 +378,7 @@ namespace ICSharpCode.AvalonEdit.Editing /// /// Gets/Sets the selection in this text area. /// + public Selection Selection { get { return selection; } set { @@ -390,7 +391,7 @@ namespace ICSharpCode.AvalonEdit.Editing if (textView != null) { ISegment oldSegment = selection.SurroundingSegment; ISegment newSegment = value.SurroundingSegment; - if (!Options.EnableVirtualSpace && (selection is SimpleSelection && value is SimpleSelection && oldSegment != null && newSegment != null)) { + if (!Selection.EnableVirtualSpace && (selection is SimpleSelection && value is SimpleSelection && oldSegment != null && newSegment != null)) { // perf optimization: // When a simple selection changes, don't redraw the whole selection, but only the changed parts. int oldSegmentOffset = oldSegment.Offset; @@ -870,19 +871,19 @@ namespace ICSharpCode.AvalonEdit.Editing } } - internal void RemoveSelectedText() - { - if (this.Document == null) - throw ThrowUtil.NoDocumentAssigned(); - selection.ReplaceSelectionWithText(string.Empty); - #if DEBUG - if (!selection.IsEmpty) { - foreach (ISegment s in selection.Segments) { - Debug.Assert(this.ReadOnlySectionProvider.GetDeletableSegments(s).Count() == 0); - } - } - #endif +internal void RemoveSelectedText() +{ + if (this.Document == null) + throw ThrowUtil.NoDocumentAssigned(); + selection.ReplaceSelectionWithText(string.Empty); + #if DEBUG + if (!selection.IsEmpty) { + foreach (ISegment s in selection.Segments) { + Debug.Assert(this.ReadOnlySectionProvider.GetDeletableSegments(s).Count() == 0); } + } + #endif +} internal void ReplaceSelectionWithText(string newText) { diff --git a/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj index 7637c92..3d644e2 100644 --- a/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj +++ b/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj @@ -262,7 +262,6 @@ - FormattedTextElement.cs @@ -340,7 +339,9 @@ - + + ObserveAddRemoveCollection.cs + diff --git a/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs b/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs index 9d5d74c..e2f0dcd 100644 --- a/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs +++ b/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs @@ -124,7 +124,7 @@ namespace ICSharpCode.AvalonEdit.Rendering int segmentEndVC; if (segmentEnd > vlEndOffset) - segmentEndVC = extendToFullWidthAtLineEnd ? int.MaxValue : vl.VisualLength; + segmentEndVC = extendToFullWidthAtLineEnd ? int.MaxValue : vl.VisualLengthWithEndOfLineMarker; else segmentEndVC = vl.ValidateVisualColumn(end, extendToFullWidthAtLineEnd); @@ -170,8 +170,8 @@ namespace ICSharpCode.AvalonEdit.Rendering lastRect = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height); } } - if (segmentEndVC >= vl.VisualLength) { - double left = (segmentStartVC > vl.VisualLength ? vl.GetTextLineVisualXPosition(lastTextLine, segmentStartVC) : line.Width) - scrollOffset.X; + if (segmentEndVC >= vl.VisualLengthWithEndOfLineMarker) { + double left = (segmentStartVC > vl.VisualLengthWithEndOfLineMarker ? vl.GetTextLineVisualXPosition(lastTextLine, segmentStartVC) : line.Width) - scrollOffset.X; double right = ((segmentEndVC == int.MaxValue || line != lastTextLine) ? Math.Max(((IScrollInfo)textView).ExtentWidth, ((IScrollInfo)textView).ViewportWidth) : vl.GetTextLineVisualXPosition(lastTextLine, segmentEndVC)) - scrollOffset.X; Rect extendSelection = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height); if (!lastRect.IsEmpty) { diff --git a/ICSharpCode.AvalonEdit/Rendering/NewLineElementGenerator.cs b/ICSharpCode.AvalonEdit/Rendering/NewLineElementGenerator.cs deleted file mode 100644 index 0718010..0000000 --- a/ICSharpCode.AvalonEdit/Rendering/NewLineElementGenerator.cs +++ /dev/null @@ -1,88 +0,0 @@ -// 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.Windows; -using System.Windows.Documents; -using System.Windows.Media; -using System.Windows.Media.TextFormatting; -using ICSharpCode.AvalonEdit.Document; -using ICSharpCode.AvalonEdit.Utils; - -namespace ICSharpCode.AvalonEdit.Rendering -{ - // This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions. - - /// - /// Elements generator that displays "¶" at the end of lines. - /// - /// - /// This element generator can be easily enabled and configured using the - /// . - /// - sealed class NewLineElementGenerator : VisualLineElementGenerator, IBuiltinElementGenerator - { - void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options) - { - } - - public override int GetFirstInterestedOffset(int startOffset) - { - DocumentLine lastDocumentLine = CurrentContext.VisualLine.LastDocumentLine; - if (lastDocumentLine.DelimiterLength > 0) - return lastDocumentLine.Offset + lastDocumentLine.Length; - else - return -1; - } - - public override VisualLineElement ConstructElement(int offset) - { - string newlineText; - DocumentLine lastDocumentLine = CurrentContext.VisualLine.LastDocumentLine; - if (lastDocumentLine.DelimiterLength == 2) { - newlineText = "\u00B6"; - } else if (lastDocumentLine.DelimiterLength == 1) { - char newlineChar = CurrentContext.Document.GetCharAt(lastDocumentLine.Offset + lastDocumentLine.Length); - if (newlineChar == '\r') - newlineText = "\\r"; - else if (newlineChar == '\n') - newlineText = "\\n"; - else - newlineText = "?"; - } else { - return null; - } - return new NewLineTextElement(CurrentContext.TextView.cachedElements.GetTextForNonPrintableCharacter(newlineText, CurrentContext)); - } - - sealed class NewLineTextElement : FormattedTextElement - { - public NewLineTextElement(TextLine text) : base(text, 0) - { - BreakBefore = LineBreakCondition.BreakPossible; - BreakAfter = LineBreakCondition.BreakRestrained; - } - - public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode) - { - // only place a caret stop before the newline, no caret stop after it - if (visualColumn > this.VisualColumn && direction == LogicalDirection.Backward || - visualColumn < this.VisualColumn && direction == LogicalDirection.Forward) - { - return this.VisualColumn; - } else { - return -1; - } - } - - public override bool IsWhitespace(int visualColumn) - { - return true; - } - - public override bool HandlesLineBorders { - get { return true; } - } - } - } -} diff --git a/ICSharpCode.AvalonEdit/Rendering/TextView.cs b/ICSharpCode.AvalonEdit/Rendering/TextView.cs index 41a7db2..a03d344 100644 --- a/ICSharpCode.AvalonEdit/Rendering/TextView.cs +++ b/ICSharpCode.AvalonEdit/Rendering/TextView.cs @@ -263,7 +263,7 @@ namespace ICSharpCode.AvalonEdit.Rendering #endregion #region Builtin ElementGenerators - NewLineElementGenerator newLineElementGenerator; +// NewLineElementGenerator newLineElementGenerator; SingleCharacterElementGenerator singleCharacterElementGenerator; LinkElementGenerator linkElementGenerator; MailLinkElementGenerator mailLinkElementGenerator; @@ -272,7 +272,7 @@ namespace ICSharpCode.AvalonEdit.Rendering { TextEditorOptions options = this.Options; - AddRemoveDefaultElementGeneratorOnDemand(ref newLineElementGenerator, options.ShowEndOfLine); +// AddRemoveDefaultElementGeneratorOnDemand(ref newLineElementGenerator, options.ShowEndOfLine); AddRemoveDefaultElementGeneratorOnDemand(ref singleCharacterElementGenerator, options.ShowBoxForControlCharacters || options.ShowSpaces || options.ShowTabs); AddRemoveDefaultElementGeneratorOnDemand(ref linkElementGenerator, options.EnableHyperlinks); AddRemoveDefaultElementGeneratorOnDemand(ref mailLinkElementGenerator, options.EnableEmailHyperlinks); @@ -1013,7 +1013,7 @@ namespace ICSharpCode.AvalonEdit.Rendering var textLines = new List(); paragraphProperties.indent = 0; paragraphProperties.firstLineInParagraph = true; - while (textOffset <= visualLine.VisualLength) { + while (textOffset <= visualLine.VisualLengthWithEndOfLineMarker) { TextLine textLine = formatter.FormatLine( textSource, textOffset, @@ -1025,7 +1025,7 @@ namespace ICSharpCode.AvalonEdit.Rendering textOffset += textLine.Length; // exit loop so that we don't do the indentation calculation if there's only a single line - if (textOffset >= visualLine.VisualLength) + if (textOffset >= visualLine.VisualLengthWithEndOfLineMarker) break; if (paragraphProperties.firstLineInParagraph) { diff --git a/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs b/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs index ea86ef9..f6ac5e1 100644 --- a/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs +++ b/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs @@ -65,6 +65,14 @@ namespace ICSharpCode.AvalonEdit.Rendering /// public int VisualLength { get; private set; } + public int VisualLengthWithEndOfLineMarker { + get { + int length = VisualLength; + if (textView.Options.ShowEndOfLine && LastDocumentLine.NextLine != null) length++; + return length; + } + } + /// /// Gets the height of the visual line in device-independent pixels. /// @@ -226,7 +234,7 @@ namespace ICSharpCode.AvalonEdit.Rendering { if (visualColumn < 0) throw new ArgumentOutOfRangeException("visualColumn"); - if (visualColumn >= VisualLength) + if (visualColumn >= VisualLengthWithEndOfLineMarker) return TextLines[TextLines.Count - 1]; foreach (TextLine line in TextLines) { if (visualColumn < line.Length) @@ -322,9 +330,9 @@ namespace ICSharpCode.AvalonEdit.Rendering if (textLine == null) throw new ArgumentNullException("textLine"); double xPos = textLine.GetDistanceFromCharacterHit( - new CharacterHit(Math.Min(visualColumn, VisualLength), 0)); - if (visualColumn > VisualLength) { - xPos += (visualColumn - VisualLength) * textView.WideSpaceWidth; + new CharacterHit(Math.Min(visualColumn, VisualLengthWithEndOfLineMarker), 0)); + if (visualColumn > VisualLengthWithEndOfLineMarker) { + xPos += (visualColumn - VisualLengthWithEndOfLineMarker) * textView.WideSpaceWidth; } return xPos; } @@ -340,35 +348,43 @@ namespace ICSharpCode.AvalonEdit.Rendering public int GetVisualColumn(Point point, bool allowVirtualSpace) { - TextLine textLine = GetTextLineByVisualYPosition(point.Y); - if (point.X > textLine.WidthIncludingTrailingWhitespace) { + return GetVisualColumn(GetTextLineByVisualYPosition(point.Y), point.X, allowVirtualSpace); + } + + public int GetVisualColumn(TextLine textLine, double xPos, bool allowVirtualSpace) + { + if (xPos > textLine.WidthIncludingTrailingWhitespace) { if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) { - int virtualX = (int)Math.Round((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth); - return VisualLength + virtualX; + int virtualX = (int)Math.Round((xPos - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth); + return VisualLengthWithEndOfLineMarker + virtualX; } } - CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X); + CharacterHit ch = textLine.GetCharacterHitFromDistance(xPos); return ch.FirstCharacterIndex + ch.TrailingLength; } public int ValidateVisualColumn(TextViewPosition position, bool allowVirtualSpace) { - int offset = Document.GetOffset(position); + return ValidateVisualColumn(Document.GetOffset(position), position.VisualColumn, allowVirtualSpace); + } + + public int ValidateVisualColumn(int offset, int visualColumn, bool allowVirtualSpace) + { int firstDocumentLineOffset = this.FirstDocumentLine.Offset; - if (position.VisualColumn < 0) { + if (visualColumn < 0) { return GetVisualColumn(offset - firstDocumentLineOffset); } else { - int offsetFromVisualColumn = GetRelativeOffset(position.VisualColumn); + int offsetFromVisualColumn = GetRelativeOffset(visualColumn); offsetFromVisualColumn += firstDocumentLineOffset; if (offsetFromVisualColumn != offset) { return GetVisualColumn(offset - firstDocumentLineOffset); } else { - if (position.VisualColumn > VisualLength && !allowVirtualSpace) { + if (visualColumn > VisualLength && !allowVirtualSpace) { return VisualLength; } } } - return position.VisualColumn; + return visualColumn; } /// @@ -387,7 +403,7 @@ namespace ICSharpCode.AvalonEdit.Rendering if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) { // clicking virtual space in the last line int virtualX = (int)((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth); - return VisualLength + virtualX; + return VisualLengthWithEndOfLineMarker + virtualX; } else { // GetCharacterHitFromDistance returns a hit with FirstCharacterIndex=last character in line // and TrailingLength=1 when clicking behind the line, so the floor function needs to handle this case @@ -407,11 +423,11 @@ namespace ICSharpCode.AvalonEdit.Rendering /// /// Gets the next possible caret position after visualColumn, or -1 if there is no caret position. /// - public int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode) + public int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode, bool allowVirtualSpace) { if (elements.Count == 0) { // special handling for empty visual lines: - if (textView.Options.EnableVirtualSpace) { + if (allowVirtualSpace) { if (direction == LogicalDirection.Forward) return Math.Max(0, visualColumn + 1); else if (visualColumn > 0) @@ -436,7 +452,7 @@ namespace ICSharpCode.AvalonEdit.Rendering // If the last element doesn't handle line borders, return the line end as caret stop if (visualColumn > this.VisualLength && !elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) { - if (textView.Options.EnableVirtualSpace) + if (allowVirtualSpace) return visualColumn - 1; else return this.VisualLength; @@ -478,10 +494,10 @@ namespace ICSharpCode.AvalonEdit.Rendering } // if we've found nothing, and the last element doesn't handle line borders, // return the line end as caret stop - if (!elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) { + if ((allowVirtualSpace || !elements[elements.Count-1].HandlesLineBorders) && HasImplicitStopAtLineEnd(mode)) { if (visualColumn < this.VisualLength) return this.VisualLength; - else if (textView.Options.EnableVirtualSpace) + else if (allowVirtualSpace) return visualColumn + 1; } } diff --git a/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs b/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs index 375a09b..a9c05c6 100644 --- a/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs +++ b/ICSharpCode.AvalonEdit/Rendering/VisualLineTextSource.cs @@ -49,12 +49,33 @@ namespace ICSharpCode.AvalonEdit.Rendering return run; } } + if (TextView.Options.ShowEndOfLine && textSourceCharacterIndex == VisualLine.VisualLength) { + return CreateTextRunForNewLine(); + } return new TextEndOfParagraph(1); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); throw; } } + + TextRun CreateTextRunForNewLine() + { + string newlineText = ""; + DocumentLine lastDocumentLine = VisualLine.LastDocumentLine; + if (lastDocumentLine.DelimiterLength == 2) { + newlineText = "¶"; + } else if (lastDocumentLine.DelimiterLength == 1) { + char newlineChar = Document.GetCharAt(lastDocumentLine.Offset + lastDocumentLine.Length); + if (newlineChar == '\r') + newlineText = "\\r"; + else if (newlineChar == '\n') + newlineText = "\\n"; + else + newlineText = "?"; + } + return new FormattedTextRun(new FormattedTextElement(TextView.cachedElements.GetTextForNonPrintableCharacter(newlineText, this), 0), GlobalTextRunProperties); + } public override TextSpan GetPrecedingText(int textSourceCharacterIndexLimit) { diff --git a/ICSharpCode.AvalonEdit/Utils/ExtensionMethods.cs b/ICSharpCode.AvalonEdit/Utils/ExtensionMethods.cs index 2dec831..0676256 100644 --- a/ICSharpCode.AvalonEdit/Utils/ExtensionMethods.cs +++ b/ICSharpCode.AvalonEdit/Utils/ExtensionMethods.cs @@ -192,5 +192,15 @@ namespace ICSharpCode.AvalonEdit.Utils if (f != null && !f.IsFrozen) Debug.WriteLine("Performance warning: Not frozen: " + f.ToString()); } + + [Conditional("DEBUG")] + public static void Log(bool condition, string format, params object[] args) + { + if (condition) { + string output = DateTime.Now.ToString("hh:MM:ss") + ": " + string.Format(format, args) + Environment.NewLine + Environment.StackTrace; + Console.WriteLine(output); + Debug.WriteLine(output); + } + } } }