From 8c332d955009683f0c949bf7982a90b3a21df057 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Tue, 2 Jun 2009 18:31:00 +0000 Subject: [PATCH] AvalonEdit: fixed 'Home' key and CaretPositioningMode.WordStart over line borders. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4206 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Project/Commands/SearchToolbarCommands.cs | 2 +- .../ICSharpCode.AvalonEdit/Editing/Caret.cs | 12 ++++--- .../Editing/CaretNavigationCommandHandler.cs | 36 +++++++++++++++---- .../Editing/EditingCommandHandler.cs | 10 ------ .../Rendering/VisualLine.cs | 23 ++++++++---- .../Utils/NullSafeCollection.cs | 2 +- .../Utils/ObserveAddRemoveCollection.cs | 5 ++- .../Utils/TextUtilities.cs | 5 +++ .../ICSharpCode.AvalonEdit/Utils/ThrowUtil.cs | 7 +++- .../Src/Gui/Workbench/WpfWorkbench.xaml | 2 +- 10 files changed, 72 insertions(+), 32 deletions(-) diff --git a/src/AddIns/Misc/SearchAndReplace/Project/Commands/SearchToolbarCommands.cs b/src/AddIns/Misc/SearchAndReplace/Project/Commands/SearchToolbarCommands.cs index 738bef2e9b..4b1366daf5 100644 --- a/src/AddIns/Misc/SearchAndReplace/Project/Commands/SearchToolbarCommands.cs +++ b/src/AddIns/Misc/SearchAndReplace/Project/Commands/SearchToolbarCommands.cs @@ -35,8 +35,8 @@ namespace SearchAndReplace void OnKeyPress(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) { - CommitSearch(); e.Handled = true; + CommitSearch(); } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs index 472bb13523..cdeed6b49b 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs @@ -5,13 +5,13 @@ // $Revision$ // +using ICSharpCode.AvalonEdit.Utils; using System; using System.Diagnostics; using System.Windows; using System.Windows.Documents; using System.Windows.Media.TextFormatting; using System.Windows.Threading; - using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Rendering; @@ -58,7 +58,7 @@ namespace ICSharpCode.AvalonEdit.Editing /// /// Gets/Sets the position of the caret. - /// Retrieving this property will validate the visual column. + /// Retrieving this property will validate the visual column (which can be expensive). /// Use the property instead if you don't need the visual column. /// public TextViewPosition Position { @@ -264,7 +264,9 @@ namespace ICSharpCode.AvalonEdit.Editing // then try backwards newVisualColumn = visualLine.GetNextCaretPosition(position.VisualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.Normal); } - if (newVisualColumn >= 0 && newVisualColumn != position.VisualColumn) { + if (newVisualColumn < 0) + throw ThrowUtil.NoValidCaretPosition(); + if (newVisualColumn != position.VisualColumn) { int newOffset = visualLine.GetRelativeOffset(newVisualColumn) + firstDocumentLineOffset; this.Position = new TextViewPosition(textView.Document.GetLocation(newOffset), newVisualColumn); } @@ -294,7 +296,9 @@ namespace ICSharpCode.AvalonEdit.Editing { if (textView != null) { VisualLine visualLine = textView.GetOrConstructVisualLine(textView.Document.GetLineByNumber(position.Line)); - textView.MakeVisible(CalcCaretRectangle(visualLine)); + Rect caretRectangle = CalcCaretRectangle(visualLine); + caretRectangle.Inflate(30, 30); // leave at least 30 pixels distance to the view border + textView.MakeVisible(caretRectangle); } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs index 6d3d8b0f36..18a5717778 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media.TextFormatting; @@ -185,9 +186,8 @@ namespace ICSharpCode.AvalonEdit.Editing static void MoveCaretToStartOfLine(TextArea textArea, VisualLine visualLine) { int newVC = visualLine.GetNextCaretPosition(-1, LogicalDirection.Forward, CaretPositioningMode.WordStart); - // in empty lines (whitespace only), jump to the end if (newVC < 0) - newVC = visualLine.VisualLength; + throw ThrowUtil.NoValidCaretPosition(); // when the caret is already at the start of the text, jump to start before whitespace if (newVC == textArea.Caret.VisualColumn) newVC = 0; @@ -211,18 +211,40 @@ namespace ICSharpCode.AvalonEdit.Editing SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset); } else { // move to start of next line - SetCaretPosition(textArea, 0, visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength); + DocumentLine nextDocumentLine = visualLine.LastDocumentLine.NextLine; + if (nextDocumentLine != null) { + VisualLine nextLine = textArea.TextView.GetOrConstructVisualLine(nextDocumentLine); + pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode); + if (pos < 0) + throw ThrowUtil.NoValidCaretPosition(); + SetCaretPosition(textArea, pos, nextLine.GetRelativeOffset(pos) + nextLine.FirstDocumentLine.Offset); + } else { + // at end of document + Debug.Assert(visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength == textArea.Document.TextLength); + SetCaretPosition(textArea, -1, textArea.Document.TextLength); + } } } - + static void MoveCaretLeft(TextArea textArea, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode) { int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode); if (pos >= 0) { SetCaretPosition(textArea, pos, visualLine.GetRelativeOffset(pos) + visualLine.FirstDocumentLine.Offset); - } else if (caretPosition.Line > 1) { - DocumentLine prevLine = textArea.Document.GetLineByNumber(caretPosition.Line - 1); - SetCaretPosition(textArea, -1, prevLine.Offset + prevLine.Length); + } else { + // move to end of previous line + DocumentLine previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine; + if (previousDocumentLine != null) { + VisualLine previousLine = textArea.TextView.GetOrConstructVisualLine(previousDocumentLine); + pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode); + if (pos < 0) + throw ThrowUtil.NoValidCaretPosition(); + SetCaretPosition(textArea, pos, previousLine.GetRelativeOffset(pos) + previousLine.FirstDocumentLine.Offset); + } else { + // at start of document + Debug.Assert(visualLine.FirstDocumentLine.Offset == 0); + SetCaretPosition(textArea, 0, 0); + } } } #endregion diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs index c8afb01915..737c48034f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs @@ -232,16 +232,6 @@ namespace ICSharpCode.AvalonEdit.Editing if (textArea.Selection.IsEmpty) selectingCommand.Execute(args.Parameter, textArea); textArea.RemoveSelectedText(); - if (selectingCommand == EditingCommands.SelectLeftByWord) { - // Special case: when Ctrl+Backspace deletes until the start of the line, - // also delete the previous line delimiter. - // This allows deleting lines that consist only of indentation using a single - // press on Ctrl+Backspace. - if (textArea.Caret.Column == 1 && textArea.Caret.VisualColumn == 0 && textArea.Caret.Line > 1) { - DocumentLine previousLine = textArea.Document.GetLineByNumber(textArea.Caret.Line - 1); - textArea.Document.Remove(previousLine.EndOffset, previousLine.DelimiterLength); - } - } } textArea.Caret.BringCaretToView(); args.Handled = true; diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs index 4def3e5364..6140ea04ea 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs @@ -334,7 +334,8 @@ namespace ICSharpCode.AvalonEdit.Rendering if (direction == LogicalDirection.Backward) { // Search Backwards: // 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) { + // (we don't check the mode here: we treat a line end as word start, border, etc. + if (visualColumn > this.VisualLength && !elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) { return this.VisualLength; } // skip elements that start after or at visualColumn @@ -350,14 +351,14 @@ namespace ICSharpCode.AvalonEdit.Rendering if (pos >= 0) return pos; } - // if we've found nothing, and the first element doesn't handle line borders, - // return the line start as caret stop - if (visualColumn > 0 && !elements[0].HandlesLineBorders) + // If we've found nothing, and the first element doesn't handle line borders, + // return the line start as normal caret stop. + if (visualColumn > 0 && !elements[0].HandlesLineBorders && HasImplicitStopAtLineStart(mode)) return 0; } else { // Search Forwards: // If the first element doesn't handle line borders, return the line start as caret stop - if (visualColumn < 0 && !elements[0].HandlesLineBorders) + if (visualColumn < 0 && !elements[0].HandlesLineBorders && HasImplicitStopAtLineStart(mode)) return 0; // skip elements that end before or at visualColumn for (i = 0; i < elements.Count; i++) { @@ -374,11 +375,21 @@ 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 (visualColumn < this.VisualLength && !elements[elements.Count-1].HandlesLineBorders) + if (visualColumn < this.VisualLength && !elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) return this.VisualLength; } // we've found nothing, return -1 and let the caret search continue in the next line return -1; } + + static bool HasImplicitStopAtLineStart(CaretPositioningMode mode) + { + return mode == CaretPositioningMode.Normal; + } + + static bool HasImplicitStopAtLineEnd(CaretPositioningMode mode) + { + return true; + } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/NullSafeCollection.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/NullSafeCollection.cs index 0151a8c2c7..4f54f94eef 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/NullSafeCollection.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/NullSafeCollection.cs @@ -13,7 +13,7 @@ namespace ICSharpCode.AvalonEdit.Utils /// /// A collection that cannot contain null values. /// - public sealed class NullSafeCollection : Collection where T : class + public class NullSafeCollection : Collection where T : class { /// protected override void InsertItem(int index, T item) diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ObserveAddRemoveCollection.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ObserveAddRemoveCollection.cs index 2f13a4f972..7e761e4210 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ObserveAddRemoveCollection.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ObserveAddRemoveCollection.cs @@ -12,6 +12,8 @@ namespace ICSharpCode.AvalonEdit.Utils { /// /// A collection where adding and removing items causes a callback. + /// It is valid for the onAdd callback to throw an exception - this will prevent the new item from + /// being added to the collection. /// sealed class ObserveAddRemoveCollection : Collection { @@ -54,7 +56,8 @@ namespace ICSharpCode.AvalonEdit.Utils if (onAdd != null) onAdd(item); } catch { - // when adding the new item fails, just remove the old one + // When adding the new item fails, just remove the old one + // (we cannot keep the old item since we already successfully called onRemove for it) base.RemoveAt(index); throw; } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextUtilities.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextUtilities.cs index 1b626635a8..86a5b64ad9 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextUtilities.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextUtilities.cs @@ -191,6 +191,11 @@ namespace ICSharpCode.AvalonEdit.Utils /// The mode for caret positioning. /// The offset of the next caret position, or -1 if there is no further caret position /// in the text source. + /// + /// This method is NOT equivalent to the actual caret movement when using VisualLine.GetNextCaretPosition. + /// In real caret movement, there are additional caret stops at line starts and ends. However, this method + /// doesn't know anything about lines: it is often called with a textSource that represents only a single VisualTextElement. + /// public static int GetNextCaretPosition(ITextSource textSource, int offset, LogicalDirection direction, CaretPositioningMode mode) { if (textSource == null) diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ThrowUtil.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ThrowUtil.cs index 90832ea91a..a545e26285 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ThrowUtil.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ThrowUtil.cs @@ -22,7 +22,7 @@ namespace ICSharpCode.AvalonEdit.Utils /// Use this method to throw an ArgumentNullException when using parameters for base /// constructor calls. /// - /// public VisualLineText(string text) : base(ThrowUtil.CheckNull(text, "text").Length) + /// public VisualLineText(string text) : base(ThrowUtil.CheckNotNull(text, "text").Length) /// /// public static T CheckNotNull(T val, string parameterName) where T : class @@ -43,5 +43,10 @@ namespace ICSharpCode.AvalonEdit.Utils { throw new InvalidOperationException("Document is null"); } + + public static InvalidOperationException NoValidCaretPosition() + { + throw new InvalidOperationException("Could not find a valid caret position in the line"); + } } } diff --git a/src/Main/Base/Project/Src/Gui/Workbench/WpfWorkbench.xaml b/src/Main/Base/Project/Src/Gui/Workbench/WpfWorkbench.xaml index 036530e9b5..4444d51dbe 100644 --- a/src/Main/Base/Project/Src/Gui/Workbench/WpfWorkbench.xaml +++ b/src/Main/Base/Project/Src/Gui/Workbench/WpfWorkbench.xaml @@ -9,6 +9,6 @@ - + \ No newline at end of file