From 65d02dc1e47c28bee86fb0d98caf11bf345f438f Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 8 Mar 2009 22:41:27 +0000 Subject: [PATCH] Normalize newlines on copy/paste. Moved LineManager.NextDelimiter into NewLineFinder. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3838 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Document/ISegment.cs | 2 +- .../Document/LineManager.cs | 61 ++++------------ .../Document/NewLineFinder.cs | 71 +++++++++++++++++++ .../Document/TextDocument.cs | 12 ++-- .../Document/WeakLineTracker.cs | 2 +- .../Gui/CaretNavigationCommandHandler.cs | 1 - .../Gui/EditingCommandHandler.cs | 19 +++-- .../ICSharpCode.AvalonEdit/Gui/HeightTree.cs | 4 +- .../Gui/LineNumberMargin.cs | 3 +- .../ICSharpCode.AvalonEdit/Gui/TextArea.cs | 1 - .../ICSharpCode.AvalonEdit/Gui/TextEditor.cs | 1 - .../ICSharpCode.AvalonEdit/Gui/TextView.cs | 2 +- .../Highlighting/DocumentHighlighter.cs | 2 +- .../ICSharpCode.AvalonEdit.csproj | 3 +- 14 files changed, 113 insertions(+), 71 deletions(-) create mode 100644 src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/NewLineFinder.cs diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ISegment.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ISegment.cs index efb74d9727..cbc5cde63d 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ISegment.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ISegment.cs @@ -28,7 +28,7 @@ namespace ICSharpCode.AvalonEdit.Document /// /// Represents a simple segment (Offset,Length pair) that is not automatically updated - /// on document changed. + /// on document changes. /// struct SimpleSegment : IEquatable, ISegment { diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineManager.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineManager.cs index acde8b5541..adbd7904f4 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineManager.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineManager.cs @@ -26,14 +26,14 @@ namespace ICSharpCode.AvalonEdit.Document /// A copy of the line trackers. We need a copy so that line trackers may remove themselves /// while being notified (used e.g. by WeakLineTracker) /// - internal ILineTracker[] lineTracker; + internal ILineTracker[] lineTrackers; public LineManager(GapTextBuffer textBuffer, DocumentLineTree documentLineTree, TextDocument document) { this.document = document; this.textBuffer = textBuffer; this.documentLineTree = documentLineTree; - this.lineTracker = document.LineTracker.ToArray(); + this.lineTrackers = document.LineTrackers.ToArray(); } #endregion @@ -87,24 +87,24 @@ namespace ICSharpCode.AvalonEdit.Document { // keep the first document line DocumentLine ls = documentLineTree.GetByNumber(1); - DelimiterSegment ds = NextDelimiter(text, 0); + SimpleSegment ds = NewLineFinder.NextNewLine(text, 0); List lines = new List(); int lastDelimiterEnd = 0; - while (ds != null) { + while (ds != SimpleSegment.Invalid) { ls.TotalLength = ds.Offset + ds.Length - lastDelimiterEnd; ls.DelimiterLength = ds.Length; lastDelimiterEnd = ds.Offset + ds.Length; lines.Add(ls); ls = new DocumentLine(document); - ds = NextDelimiter(text, lastDelimiterEnd); + ds = NewLineFinder.NextNewLine(text, lastDelimiterEnd); } ls.ResetLine(); ls.TotalLength = text.Length - lastDelimiterEnd; lines.Add(ls); documentLineTree.RebuildTree(lines); - foreach (ILineTracker lt in lineTracker) - lt.RebuildDocument(); + foreach (ILineTracker lineTracker in lineTrackers) + lineTracker.RebuildDocument(); } #endregion @@ -168,7 +168,7 @@ namespace ICSharpCode.AvalonEdit.Document void RemoveLine(DocumentLine lineToRemove) { - foreach (ILineTracker lt in lineTracker) + foreach (ILineTracker lt in lineTrackers) lt.BeforeRemoveLine(lineToRemove); documentLineTree.RemoveLine(lineToRemove); // foreach (ILineTracker lt in lineTracker) @@ -197,8 +197,8 @@ namespace ICSharpCode.AvalonEdit.Document line = SetLineLength(line, 1); } - DelimiterSegment ds = NextDelimiter(text, 0); - if (ds == null) { + SimpleSegment ds = NewLineFinder.NextNewLine(text, 0); + if (ds == SimpleSegment.Invalid) { // no newline is being inserted, all text is inserted in a single line //line.InsertedLinePart(offset - line.Offset, text.Length); SetLineLength(line, line.TotalLength + text.Length); @@ -207,7 +207,7 @@ namespace ICSharpCode.AvalonEdit.Document //DocumentLine firstLine = line; //firstLine.InsertedLinePart(offset - firstLine.Offset, ds.Offset); int lastDelimiterEnd = 0; - while (ds != null) { + while (ds != SimpleSegment.Invalid) { // split line segment at line delimiter int lineBreakOffset = offset + ds.Offset + ds.Length; lineOffset = line.Offset; @@ -219,7 +219,7 @@ namespace ICSharpCode.AvalonEdit.Document line = newLine; lastDelimiterEnd = ds.Offset + ds.Length; - ds = NextDelimiter(text, lastDelimiterEnd); + ds = NewLineFinder.NextNewLine(text, lastDelimiterEnd); } //firstLine.SplitTo(line); // insert rest after last delimiter @@ -232,7 +232,7 @@ namespace ICSharpCode.AvalonEdit.Document DocumentLine InsertLineAfter(DocumentLine line, int length) { DocumentLine newLine = documentLineTree.InsertLineAfter(line, length); - foreach (ILineTracker lt in lineTracker) + foreach (ILineTracker lt in lineTrackers) lt.LineInserted(line, newLine); return newLine; } @@ -252,7 +252,7 @@ namespace ICSharpCode.AvalonEdit.Document // deletedOrChangedLines.Add(line); int delta = newTotalLength - line.TotalLength; if (delta != 0) { - foreach (ILineTracker lt in lineTracker) + foreach (ILineTracker lt in lineTrackers) lt.SetLineLength(line, newTotalLength); line.TotalLength = newTotalLength; DocumentLineTree.UpdateAfterChildrenChange(line); @@ -284,38 +284,5 @@ namespace ICSharpCode.AvalonEdit.Document return line; } #endregion - - #region Delimiter - sealed class DelimiterSegment - { - internal int Offset; - internal int Length; - } - - // always use the same DelimiterSegment object for the NextDelimiter - DelimiterSegment delimiterSegment = new DelimiterSegment(); - - DelimiterSegment NextDelimiter(string text, int offset) - { - for (int i = offset; i < text.Length; i++) { - switch (text[i]) { - case '\r': - if (i + 1 < text.Length) { - if (text[i + 1] == '\n') { - delimiterSegment.Offset = i; - delimiterSegment.Length = 2; - return delimiterSegment; - } - } - goto case '\n'; - case '\n': - delimiterSegment.Offset = i; - delimiterSegment.Length = 1; - return delimiterSegment; - } - } - return null; - } - #endregion } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/NewLineFinder.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/NewLineFinder.cs new file mode 100644 index 0000000000..f6534283da --- /dev/null +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/NewLineFinder.cs @@ -0,0 +1,71 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Text; + +namespace ICSharpCode.AvalonEdit.Document +{ + /// + /// Helper methods for finding new lines. + /// + static class NewLineFinder + { + /// + /// Gets the location of the next new line character, or SimpleSegment.Invalid + /// if none is found. + /// + public static SimpleSegment NextNewLine(string text, int offset) + { + for (int i = offset; i < text.Length; i++) { + switch (text[i]) { + case '\r': + if (i + 1 < text.Length) { + if (text[i + 1] == '\n') { + return new SimpleSegment(i, 2); + } + } + goto case '\n'; + case '\n': + return new SimpleSegment(i, 1); + } + } + return SimpleSegment.Invalid; + } + + /// + /// Gets whether the specified string is a newline sequence. + /// + public static bool IsNewLine(string newLine) + { + return newLine == "\r\n" || newLine == "\n" || newLine == "\r"; + } + + /// + /// Normalizes all new lines in to be . + /// + public static string NormalizeNewLines(string input, string newLine) + { + Debug.Assert(IsNewLine(newLine)); + SimpleSegment ds = NextNewLine(input, 0); + if (ds == SimpleSegment.Invalid) // text does not contain any new lines + return input; + StringBuilder b = new StringBuilder(input.Length); + int lastEndOffset = 0; + do { + b.Append(input, lastEndOffset, ds.Offset - lastEndOffset); + b.Append(newLine); + lastEndOffset = ds.GetEndOffset(); + ds = NextNewLine(input, lastEndOffset); + } while (ds != SimpleSegment.Invalid); + // remaining string (after last newline) + b.Append(input, lastEndOffset, input.Length - lastEndOffset); + return b.ToString(); + } + } +} diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs index 6bd8c6517c..e98969188f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs @@ -74,8 +74,8 @@ namespace ICSharpCode.AvalonEdit.Document //parserManager = new ParserManager(this); lineTree = new DocumentLineTree(this); lineManager = new LineManager(textBuffer, lineTree, this); - lineTracker.CollectionChanged += delegate { - lineManager.lineTracker = lineTracker.ToArray(); + lineTrackers.CollectionChanged += delegate { + lineManager.lineTrackers = lineTrackers.ToArray(); }; anchorTree = new TextAnchorTree(this); @@ -445,15 +445,15 @@ namespace ICSharpCode.AvalonEdit.Document return new TextLocation(line.LineNumber, offset - line.Offset + 1); } - readonly ObservableCollection lineTracker = new ObservableCollection(); + readonly ObservableCollection lineTrackers = new ObservableCollection(); /// - /// Gets the list of attached to this document. + /// Gets the list of s attached to this document. /// - public IList LineTracker { + public IList LineTrackers { get { VerifyAccess(); - return lineTracker; + return lineTrackers; } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/WeakLineTracker.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/WeakLineTracker.cs index 14e3c3fcfc..8d57b1165a 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/WeakLineTracker.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/WeakLineTracker.cs @@ -22,7 +22,7 @@ namespace ICSharpCode.AvalonEdit.Document void Deregister() { - textDocument.LineTracker.Remove(this); + textDocument.LineTrackers.Remove(this); } public void BeforeRemoveLine(DocumentLine line) diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs index 1129dd1c86..d162f6aae9 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs @@ -44,7 +44,6 @@ namespace ICSharpCode.AvalonEdit.Gui InputBindings.Add(new KeyBinding(command, key, modifiers)); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static CaretNavigationCommandHandler() { const ModifierKeys None = ModifierKeys.None; diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs index fe51bfe5b4..af5d4df398 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs @@ -7,7 +7,6 @@ using System; using System.Linq; -using System.Diagnostics; using System.Windows; using System.Windows.Documents; using System.Windows.Input; @@ -28,7 +27,6 @@ namespace ICSharpCode.AvalonEdit.Gui InputBindings.Add(new KeyBinding(command, key, modifiers)); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static EditingCommandHandler() { CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, OnDelete(ApplicationCommands.NotACommand), HasSomethingSelected)); @@ -198,7 +196,7 @@ namespace ICSharpCode.AvalonEdit.Gui { TextArea textArea = GetTextArea(target); if (textArea != null && textArea.Document != null) { - Clipboard.SetText(textArea.Selection.GetText(textArea.Document)); + CopySelectedText(textArea); args.Handled = true; } } @@ -207,13 +205,20 @@ namespace ICSharpCode.AvalonEdit.Gui { TextArea textArea = GetTextArea(target); if (textArea != null && textArea.Document != null) { - Clipboard.SetText(textArea.Selection.GetText(textArea.Document)); + CopySelectedText(textArea); textArea.RemoveSelectedText(); textArea.Caret.BringCaretToView(); args.Handled = true; } } + static void CopySelectedText(TextArea textArea) + { + string text = textArea.Selection.GetText(textArea.Document); + // ensure we use the appropriate newline sequence for the OS + Clipboard.SetText(NewLineFinder.NormalizeNewLines(text, Environment.NewLine)); + } + static void CanPaste(object target, CanExecuteRoutedEventArgs args) { TextArea textArea = GetTextArea(target); @@ -228,8 +233,10 @@ namespace ICSharpCode.AvalonEdit.Gui { TextArea textArea = GetTextArea(target); if (textArea != null && textArea.Document != null) { - // TODO: normalize newlines on paste - textArea.ReplaceSelectionWithText(Clipboard.GetText()); + // convert text back to correct newlines for this document + string newLine = GetLineDelimiter(textArea.Document, textArea.Caret.Line); + string text = NewLineFinder.NormalizeNewLines(Clipboard.GetText(), newLine); + textArea.ReplaceSelectionWithText(text); textArea.Caret.BringCaretToView(); args.Handled = true; } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/HeightTree.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/HeightTree.cs index 8244be597f..aa133a8fc1 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/HeightTree.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/HeightTree.cs @@ -51,7 +51,7 @@ namespace ICSharpCode.AvalonEdit.Gui { this.document = document; weakLineTracker = new WeakLineTracker(document, this); - document.LineTracker.Add(weakLineTracker); + document.LineTrackers.Add(weakLineTracker); this.DefaultLineHeight = defaultLineHeight; RebuildDocument(); } @@ -59,7 +59,7 @@ namespace ICSharpCode.AvalonEdit.Gui public void Dispose() { if (weakLineTracker != null) - document.LineTracker.Remove(weakLineTracker); + document.LineTrackers.Remove(weakLineTracker); this.dict = null; this.root = null; this.weakLineTracker = null; diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/LineNumberMargin.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/LineNumberMargin.cs index b6c49ee39d..100d50f72b 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/LineNumberMargin.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/LineNumberMargin.cs @@ -23,7 +23,6 @@ namespace ICSharpCode.AvalonEdit.Gui /// public class LineNumberMargin : AbstractMargin, IWeakEventListener { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static LineNumberMargin() { DefaultStyleKeyProperty.OverrideMetadata(typeof(LineNumberMargin), @@ -226,7 +225,7 @@ namespace ICSharpCode.AvalonEdit.Gui /// protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) { - // accept clicks even when clicking on the backgroudn + // accept clicks even when clicking on the background return new PointHitTestResult(this, hitTestParameters.HitPoint); } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs index 50866ab912..be20b7c94b 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs @@ -30,7 +30,6 @@ namespace ICSharpCode.AvalonEdit public class TextArea : Control, IScrollInfo, IWeakEventListener { #region Constructor - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static TextArea() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TextArea), diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs index 54ca9e2b30..aa26ce9bd1 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs @@ -29,7 +29,6 @@ namespace ICSharpCode.AvalonEdit [Localizability(LocalizationCategory.Text), ContentProperty("Text")] public class TextEditor : Control { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static TextEditor() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TextEditor), diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs index a0304ad1cc..ef5b2d7d2e 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs @@ -36,7 +36,6 @@ namespace ICSharpCode.AvalonEdit.Gui public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener { #region Constructor - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static TextView() { ClipToBoundsProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(true)); @@ -341,6 +340,7 @@ namespace ICSharpCode.AvalonEdit.Gui /// or use the method to force creating the visual lines /// when they are invalid. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] public ReadOnlyCollection VisualLines { get { if (visibleVisualLines == null) diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs index bf8bbb42ad..56ebf37714 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs @@ -39,7 +39,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting throw new ArgumentNullException("baseRuleSet"); this.document = document; this.baseRuleSet = baseRuleSet; - document.LineTracker.Add(new WeakLineTracker(document, this)); + document.LineTrackers.Add(new WeakLineTracker(document, this)); InvalidateHighlighting(); } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj index 103d09ca76..07caa9189f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj @@ -18,7 +18,7 @@ False File False - -Microsoft.Design#CA1020;-Microsoft.Design#CA1033 + -Microsoft.Design#CA1020;-Microsoft.Design#CA1033;-Microsoft.Performance#CA1810 ..\..\..\..\bin\ ..\..\..\..\bin\ICSharpCode.AvalonEdit.xml @@ -82,6 +82,7 @@ DocumentLine.cs + TextDocument.cs