Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Editing/CaretNavigationCommandHandler.cs @ 12062

Last change on this file since 12062 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 18.5 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Collections.Generic;
21using System.Diagnostics;
22using System.Linq;
23using System.Windows;
24using System.Windows.Documents;
25using System.Windows.Input;
26using System.Windows.Media.TextFormatting;
27using ICSharpCode.AvalonEdit.Document;
28using ICSharpCode.AvalonEdit.Rendering;
29using ICSharpCode.AvalonEdit.Utils;
30
31namespace ICSharpCode.AvalonEdit.Editing
32{
33  enum CaretMovementType
34  {
35    None,
36    CharLeft,
37    CharRight,
38    Backspace,
39    WordLeft,
40    WordRight,
41    LineUp,
42    LineDown,
43    PageUp,
44    PageDown,
45    LineStart,
46    LineEnd,
47    DocumentStart,
48    DocumentEnd
49  }
50 
51  static class CaretNavigationCommandHandler
52  {
53    /// <summary>
54    /// Creates a new <see cref="TextAreaInputHandler"/> for the text area.
55    /// </summary>
56    public static TextAreaInputHandler Create(TextArea textArea)
57    {
58      TextAreaInputHandler handler = new TextAreaInputHandler(textArea);
59      handler.CommandBindings.AddRange(CommandBindings);
60      handler.InputBindings.AddRange(InputBindings);
61      return handler;
62    }
63   
64    static readonly List<CommandBinding> CommandBindings = new List<CommandBinding>();
65    static readonly List<InputBinding> InputBindings = new List<InputBinding>();
66   
67    static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler)
68    {
69      CommandBindings.Add(new CommandBinding(command, handler));
70      InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key));
71    }
72   
73    static CaretNavigationCommandHandler()
74    {
75      const ModifierKeys None = ModifierKeys.None;
76      const ModifierKeys Ctrl = ModifierKeys.Control;
77      const ModifierKeys Shift = ModifierKeys.Shift;
78      const ModifierKeys Alt = ModifierKeys.Alt;
79     
80      AddBinding(EditingCommands.MoveLeftByCharacter, None, Key.Left, OnMoveCaret(CaretMovementType.CharLeft));
81      AddBinding(EditingCommands.SelectLeftByCharacter, Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.CharLeft));
82      AddBinding(RectangleSelection.BoxSelectLeftByCharacter, Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.CharLeft));
83      AddBinding(EditingCommands.MoveRightByCharacter, None, Key.Right, OnMoveCaret(CaretMovementType.CharRight));
84      AddBinding(EditingCommands.SelectRightByCharacter, Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.CharRight));
85      AddBinding(RectangleSelection.BoxSelectRightByCharacter, Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.CharRight));
86     
87      AddBinding(EditingCommands.MoveLeftByWord, Ctrl, Key.Left, OnMoveCaret(CaretMovementType.WordLeft));
88      AddBinding(EditingCommands.SelectLeftByWord, Ctrl | Shift, Key.Left, OnMoveCaretExtendSelection(CaretMovementType.WordLeft));
89      AddBinding(RectangleSelection.BoxSelectLeftByWord, Ctrl | Alt | Shift, Key.Left, OnMoveCaretBoxSelection(CaretMovementType.WordLeft));
90      AddBinding(EditingCommands.MoveRightByWord, Ctrl, Key.Right, OnMoveCaret(CaretMovementType.WordRight));
91      AddBinding(EditingCommands.SelectRightByWord, Ctrl | Shift, Key.Right, OnMoveCaretExtendSelection(CaretMovementType.WordRight));
92      AddBinding(RectangleSelection.BoxSelectRightByWord, Ctrl | Alt | Shift, Key.Right, OnMoveCaretBoxSelection(CaretMovementType.WordRight));
93     
94      AddBinding(EditingCommands.MoveUpByLine, None, Key.Up, OnMoveCaret(CaretMovementType.LineUp));
95      AddBinding(EditingCommands.SelectUpByLine, Shift, Key.Up, OnMoveCaretExtendSelection(CaretMovementType.LineUp));
96      AddBinding(RectangleSelection.BoxSelectUpByLine, Alt | Shift, Key.Up, OnMoveCaretBoxSelection(CaretMovementType.LineUp));
97      AddBinding(EditingCommands.MoveDownByLine, None, Key.Down, OnMoveCaret(CaretMovementType.LineDown));
98      AddBinding(EditingCommands.SelectDownByLine, Shift, Key.Down, OnMoveCaretExtendSelection(CaretMovementType.LineDown));
99      AddBinding(RectangleSelection.BoxSelectDownByLine, Alt | Shift, Key.Down, OnMoveCaretBoxSelection(CaretMovementType.LineDown));
100     
101      AddBinding(EditingCommands.MoveDownByPage, None, Key.PageDown, OnMoveCaret(CaretMovementType.PageDown));
102      AddBinding(EditingCommands.SelectDownByPage, Shift, Key.PageDown, OnMoveCaretExtendSelection(CaretMovementType.PageDown));
103      AddBinding(EditingCommands.MoveUpByPage, None, Key.PageUp, OnMoveCaret(CaretMovementType.PageUp));
104      AddBinding(EditingCommands.SelectUpByPage, Shift, Key.PageUp, OnMoveCaretExtendSelection(CaretMovementType.PageUp));
105     
106      AddBinding(EditingCommands.MoveToLineStart, None, Key.Home, OnMoveCaret(CaretMovementType.LineStart));
107      AddBinding(EditingCommands.SelectToLineStart, Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.LineStart));
108      AddBinding(RectangleSelection.BoxSelectToLineStart, Alt | Shift, Key.Home, OnMoveCaretBoxSelection(CaretMovementType.LineStart));
109      AddBinding(EditingCommands.MoveToLineEnd, None, Key.End, OnMoveCaret(CaretMovementType.LineEnd));
110      AddBinding(EditingCommands.SelectToLineEnd, Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.LineEnd));
111      AddBinding(RectangleSelection.BoxSelectToLineEnd, Alt | Shift, Key.End, OnMoveCaretBoxSelection(CaretMovementType.LineEnd));
112     
113      AddBinding(EditingCommands.MoveToDocumentStart, Ctrl, Key.Home, OnMoveCaret(CaretMovementType.DocumentStart));
114      AddBinding(EditingCommands.SelectToDocumentStart, Ctrl | Shift, Key.Home, OnMoveCaretExtendSelection(CaretMovementType.DocumentStart));
115      AddBinding(EditingCommands.MoveToDocumentEnd, Ctrl, Key.End, OnMoveCaret(CaretMovementType.DocumentEnd));
116      AddBinding(EditingCommands.SelectToDocumentEnd, Ctrl | Shift, Key.End, OnMoveCaretExtendSelection(CaretMovementType.DocumentEnd));
117     
118      CommandBindings.Add(new CommandBinding(ApplicationCommands.SelectAll, OnSelectAll));
119     
120      TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings);
121    }
122   
123    static void OnSelectAll(object target, ExecutedRoutedEventArgs args)
124    {
125      TextArea textArea = GetTextArea(target);
126      if (textArea != null && textArea.Document != null) {
127        args.Handled = true;
128        textArea.Caret.Offset = textArea.Document.TextLength;
129        textArea.Selection = SimpleSelection.Create(textArea, 0, textArea.Document.TextLength);
130      }
131    }
132   
133    static TextArea GetTextArea(object target)
134    {
135      return target as TextArea;
136    }
137   
138    static ExecutedRoutedEventHandler OnMoveCaret(CaretMovementType direction)
139    {
140      return (target, args) => {
141        TextArea textArea = GetTextArea(target);
142        if (textArea != null && textArea.Document != null) {
143          args.Handled = true;
144          textArea.ClearSelection();
145          MoveCaret(textArea, direction);
146          textArea.Caret.BringCaretToView();
147        }
148      };
149    }
150   
151    static ExecutedRoutedEventHandler OnMoveCaretExtendSelection(CaretMovementType direction)
152    {
153      return (target, args) => {
154        TextArea textArea = GetTextArea(target);
155        if (textArea != null && textArea.Document != null) {
156          args.Handled = true;
157          TextViewPosition oldPosition = textArea.Caret.Position;
158          MoveCaret(textArea, direction);
159          textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
160          textArea.Caret.BringCaretToView();
161        }
162      };
163    }
164   
165    static ExecutedRoutedEventHandler OnMoveCaretBoxSelection(CaretMovementType direction)
166    {
167      return (target, args) => {
168        TextArea textArea = GetTextArea(target);
169        if (textArea != null && textArea.Document != null) {
170          args.Handled = true;
171          // First, convert the selection into a rectangle selection
172          // (this is required so that virtual space gets enabled for the caret movement)
173          if (textArea.Options.EnableRectangularSelection && !(textArea.Selection is RectangleSelection)) {
174            if (textArea.Selection.IsEmpty) {
175              textArea.Selection = new RectangleSelection(textArea, textArea.Caret.Position, textArea.Caret.Position);
176            } else {
177              // Convert normal selection to rectangle selection
178              textArea.Selection = new RectangleSelection(textArea, textArea.Selection.StartPosition, textArea.Caret.Position);
179            }
180          }
181          // Now move the caret and extend the selection
182          TextViewPosition oldPosition = textArea.Caret.Position;
183          MoveCaret(textArea, direction);
184          textArea.Selection = textArea.Selection.StartSelectionOrSetEndpoint(oldPosition, textArea.Caret.Position);
185          textArea.Caret.BringCaretToView();
186        }
187      };
188    }
189   
190    #region Caret movement
191    internal static void MoveCaret(TextArea textArea, CaretMovementType direction)
192    {
193      double desiredXPos = textArea.Caret.DesiredXPos;
194      textArea.Caret.Position = GetNewCaretPosition(textArea.TextView, textArea.Caret.Position, direction, textArea.Selection.EnableVirtualSpace, ref desiredXPos);
195      textArea.Caret.DesiredXPos = desiredXPos;
196    }
197   
198    internal static TextViewPosition GetNewCaretPosition(TextView textView, TextViewPosition caretPosition, CaretMovementType direction, bool enableVirtualSpace, ref double desiredXPos)
199    {
200      switch (direction) {
201        case CaretMovementType.None:
202          return caretPosition;
203        case CaretMovementType.DocumentStart:
204          desiredXPos = double.NaN;
205          return new TextViewPosition(0, 0);
206        case CaretMovementType.DocumentEnd:
207          desiredXPos = double.NaN;
208          return new TextViewPosition(textView.Document.GetLocation(textView.Document.TextLength));
209      }
210      DocumentLine caretLine = textView.Document.GetLineByNumber(caretPosition.Line);
211      VisualLine visualLine = textView.GetOrConstructVisualLine(caretLine);
212      TextLine textLine = visualLine.GetTextLine(caretPosition.VisualColumn, caretPosition.IsAtEndOfLine);
213      switch (direction) {
214        case CaretMovementType.CharLeft:
215          desiredXPos = double.NaN;
216          return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.Normal, enableVirtualSpace);
217        case CaretMovementType.Backspace:
218          desiredXPos = double.NaN;
219          return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.EveryCodepoint, enableVirtualSpace);
220        case CaretMovementType.CharRight:
221          desiredXPos = double.NaN;
222          return GetNextCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.Normal, enableVirtualSpace);
223        case CaretMovementType.WordLeft:
224          desiredXPos = double.NaN;
225          return GetPrevCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.WordStart, enableVirtualSpace);
226        case CaretMovementType.WordRight:
227          desiredXPos = double.NaN;
228          return GetNextCaretPosition(textView, caretPosition, visualLine, CaretPositioningMode.WordStart, enableVirtualSpace);
229        case CaretMovementType.LineUp:
230        case CaretMovementType.LineDown:
231        case CaretMovementType.PageUp:
232        case CaretMovementType.PageDown:
233          return GetUpDownCaretPosition(textView, caretPosition, direction, visualLine, textLine, enableVirtualSpace, ref desiredXPos);
234        case CaretMovementType.LineStart:
235          desiredXPos = double.NaN;
236          return GetStartOfLineCaretPosition(caretPosition.VisualColumn, visualLine, textLine, enableVirtualSpace);
237        case CaretMovementType.LineEnd:
238          desiredXPos = double.NaN;
239          return GetEndOfLineCaretPosition(visualLine, textLine);
240        default:
241          throw new NotSupportedException(direction.ToString());
242      }
243    }
244    #endregion
245   
246    #region Home/End
247    static TextViewPosition GetStartOfLineCaretPosition(int oldVC, VisualLine visualLine, TextLine textLine, bool enableVirtualSpace)
248    {
249      int newVC = visualLine.GetTextLineVisualStartColumn(textLine);
250      if (newVC == 0)
251        newVC = visualLine.GetNextCaretPosition(newVC - 1, LogicalDirection.Forward, CaretPositioningMode.WordStart, enableVirtualSpace);
252      if (newVC < 0)
253        throw ThrowUtil.NoValidCaretPosition();
254      // when the caret is already at the start of the text, jump to start before whitespace
255      if (newVC == oldVC)
256        newVC = 0;
257      return visualLine.GetTextViewPosition(newVC);
258    }
259   
260    static TextViewPosition GetEndOfLineCaretPosition(VisualLine visualLine, TextLine textLine)
261    {
262      int newVC = visualLine.GetTextLineVisualStartColumn(textLine) + textLine.Length - textLine.TrailingWhitespaceLength;
263      TextViewPosition pos = visualLine.GetTextViewPosition(newVC);
264      pos.IsAtEndOfLine = true;
265      return pos;
266    }
267    #endregion
268   
269    #region By-character / By-word movement
270    static TextViewPosition GetNextCaretPosition(TextView textView, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode, bool enableVirtualSpace)
271    {
272      int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Forward, mode, enableVirtualSpace);
273      if (pos >= 0) {
274        return visualLine.GetTextViewPosition(pos);
275      } else {
276        // move to start of next line
277        DocumentLine nextDocumentLine = visualLine.LastDocumentLine.NextLine;
278        if (nextDocumentLine != null) {
279          VisualLine nextLine = textView.GetOrConstructVisualLine(nextDocumentLine);
280          pos = nextLine.GetNextCaretPosition(-1, LogicalDirection.Forward, mode, enableVirtualSpace);
281          if (pos < 0)
282            throw ThrowUtil.NoValidCaretPosition();
283          return nextLine.GetTextViewPosition(pos);
284        } else {
285          // at end of document
286          Debug.Assert(visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength == textView.Document.TextLength);
287          return new TextViewPosition(textView.Document.GetLocation(textView.Document.TextLength));
288        }
289      }
290    }
291   
292    static TextViewPosition GetPrevCaretPosition(TextView textView, TextViewPosition caretPosition, VisualLine visualLine, CaretPositioningMode mode, bool enableVirtualSpace)
293    {
294      int pos = visualLine.GetNextCaretPosition(caretPosition.VisualColumn, LogicalDirection.Backward, mode, enableVirtualSpace);
295      if (pos >= 0) {
296        return visualLine.GetTextViewPosition(pos);
297      } else {
298        // move to end of previous line
299        DocumentLine previousDocumentLine = visualLine.FirstDocumentLine.PreviousLine;
300        if (previousDocumentLine != null) {
301          VisualLine previousLine = textView.GetOrConstructVisualLine(previousDocumentLine);
302          pos = previousLine.GetNextCaretPosition(previousLine.VisualLength + 1, LogicalDirection.Backward, mode, enableVirtualSpace);
303          if (pos < 0)
304            throw ThrowUtil.NoValidCaretPosition();
305          return previousLine.GetTextViewPosition(pos);
306        } else {
307          // at start of document
308          Debug.Assert(visualLine.FirstDocumentLine.Offset == 0);
309          return new TextViewPosition(0, 0);
310        }
311      }
312    }
313    #endregion
314
315    #region Line+Page up/down
316    static TextViewPosition GetUpDownCaretPosition(TextView textView, TextViewPosition caretPosition, CaretMovementType direction, VisualLine visualLine, TextLine textLine, bool enableVirtualSpace, ref double xPos)
317    {
318      // moving up/down happens using the desired visual X position
319      if (double.IsNaN(xPos))
320        xPos = visualLine.GetTextLineVisualXPosition(textLine, caretPosition.VisualColumn);
321      // now find the TextLine+VisualLine where the caret will end up in
322      VisualLine targetVisualLine = visualLine;
323      TextLine targetLine;
324      int textLineIndex = visualLine.TextLines.IndexOf(textLine);
325      switch (direction) {
326        case CaretMovementType.LineUp:
327          {
328            // Move up: move to the previous TextLine in the same visual line
329            // or move to the last TextLine of the previous visual line
330            int prevLineNumber = visualLine.FirstDocumentLine.LineNumber - 1;
331            if (textLineIndex > 0) {
332              targetLine = visualLine.TextLines[textLineIndex - 1];
333            } else if (prevLineNumber >= 1) {
334              DocumentLine prevLine = textView.Document.GetLineByNumber(prevLineNumber);
335              targetVisualLine = textView.GetOrConstructVisualLine(prevLine);
336              targetLine = targetVisualLine.TextLines[targetVisualLine.TextLines.Count - 1];
337            } else {
338              targetLine = null;
339            }
340            break;
341          }
342        case CaretMovementType.LineDown:
343          {
344            // Move down: move to the next TextLine in the same visual line
345            // or move to the first TextLine of the next visual line
346            int nextLineNumber = visualLine.LastDocumentLine.LineNumber + 1;
347            if (textLineIndex < visualLine.TextLines.Count - 1) {
348              targetLine = visualLine.TextLines[textLineIndex + 1];
349            } else if (nextLineNumber <= textView.Document.LineCount) {
350              DocumentLine nextLine = textView.Document.GetLineByNumber(nextLineNumber);
351              targetVisualLine = textView.GetOrConstructVisualLine(nextLine);
352              targetLine = targetVisualLine.TextLines[0];
353            } else {
354              targetLine = null;
355            }
356            break;
357          }
358        case CaretMovementType.PageUp:
359        case CaretMovementType.PageDown:
360          {
361            // Page up/down: find the target line using its visual position
362            double yPos = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineMiddle);
363            if (direction == CaretMovementType.PageUp)
364              yPos -= textView.RenderSize.Height;
365            else
366              yPos += textView.RenderSize.Height;
367            DocumentLine newLine = textView.GetDocumentLineByVisualTop(yPos);
368            targetVisualLine = textView.GetOrConstructVisualLine(newLine);
369            targetLine = targetVisualLine.GetTextLineByVisualYPosition(yPos);
370            break;
371          }
372        default:
373          throw new NotSupportedException(direction.ToString());
374      }
375      if (targetLine != null) {
376        double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle);
377        int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos), enableVirtualSpace);
378       
379        // prevent wrapping to the next line; TODO: could 'IsAtEnd' help here?
380        int targetLineStartCol = targetVisualLine.GetTextLineVisualStartColumn(targetLine);
381        if (newVisualColumn >= targetLineStartCol + targetLine.Length) {
382          if (newVisualColumn <= targetVisualLine.VisualLength)
383            newVisualColumn = targetLineStartCol + targetLine.Length - 1;
384        }
385        return targetVisualLine.GetTextViewPosition(newVisualColumn);
386      } else {
387        return caretPosition;
388      }
389    }
390    #endregion
391  }
392}
Note: See TracBrowser for help on using the repository browser.