Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Editing/EditingCommandHandler.cs @ 17328

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

#2077: created branch and added first version

File size: 25.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.Globalization;
23using System.IO;
24using System.Linq;
25using System.Runtime.InteropServices;
26using System.Windows;
27using System.Windows.Documents;
28using System.Windows.Input;
29using ICSharpCode.AvalonEdit.Document;
30using ICSharpCode.AvalonEdit.Highlighting;
31using ICSharpCode.AvalonEdit.Utils;
32#if NREFACTORY
33using ICSharpCode.NRefactory.Editor;
34#endif
35
36namespace ICSharpCode.AvalonEdit.Editing
37{
38  /// <summary>
39  /// We re-use the CommandBinding and InputBinding instances between multiple text areas,
40  /// so this class is static.
41  /// </summary>
42  static class EditingCommandHandler
43  {
44    /// <summary>
45    /// Creates a new <see cref="TextAreaInputHandler"/> for the text area.
46    /// </summary>
47    public static TextAreaInputHandler Create(TextArea textArea)
48    {
49      TextAreaInputHandler handler = new TextAreaInputHandler(textArea);
50      handler.CommandBindings.AddRange(CommandBindings);
51      handler.InputBindings.AddRange(InputBindings);
52      return handler;
53    }
54   
55    static readonly List<CommandBinding> CommandBindings = new List<CommandBinding>();
56    static readonly List<InputBinding> InputBindings = new List<InputBinding>();
57   
58    static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler)
59    {
60      CommandBindings.Add(new CommandBinding(command, handler));
61      InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key));
62    }
63   
64    static EditingCommandHandler()
65    {
66      CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, OnDelete(CaretMovementType.None), CanDelete));
67      AddBinding(EditingCommands.Delete, ModifierKeys.None, Key.Delete, OnDelete(CaretMovementType.CharRight));
68      AddBinding(EditingCommands.DeleteNextWord, ModifierKeys.Control, Key.Delete, OnDelete(CaretMovementType.WordRight));
69      AddBinding(EditingCommands.Backspace, ModifierKeys.None, Key.Back, OnDelete(CaretMovementType.Backspace));
70      InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(EditingCommands.Backspace, ModifierKeys.Shift, Key.Back)); // make Shift-Backspace do the same as plain backspace
71      AddBinding(EditingCommands.DeletePreviousWord, ModifierKeys.Control, Key.Back, OnDelete(CaretMovementType.WordLeft));
72      AddBinding(EditingCommands.EnterParagraphBreak, ModifierKeys.None, Key.Enter, OnEnter);
73      AddBinding(EditingCommands.EnterLineBreak, ModifierKeys.Shift, Key.Enter, OnEnter);
74      AddBinding(EditingCommands.TabForward, ModifierKeys.None, Key.Tab, OnTab);
75      AddBinding(EditingCommands.TabBackward, ModifierKeys.Shift, Key.Tab, OnShiftTab);
76     
77      CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopy, CanCutOrCopy));
78      CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCut, CanCutOrCopy));
79      CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPaste, CanPaste));
80     
81      CommandBindings.Add(new CommandBinding(AvalonEditCommands.DeleteLine, OnDeleteLine));
82     
83      CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveLeadingWhitespace, OnRemoveLeadingWhitespace));
84      CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveTrailingWhitespace, OnRemoveTrailingWhitespace));
85      CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToUppercase, OnConvertToUpperCase));
86      CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToLowercase, OnConvertToLowerCase));
87      CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToTitleCase, OnConvertToTitleCase));
88      CommandBindings.Add(new CommandBinding(AvalonEditCommands.InvertCase, OnInvertCase));
89      CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertTabsToSpaces, OnConvertTabsToSpaces));
90      CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertSpacesToTabs, OnConvertSpacesToTabs));
91      CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingTabsToSpaces, OnConvertLeadingTabsToSpaces));
92      CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingSpacesToTabs, OnConvertLeadingSpacesToTabs));
93      CommandBindings.Add(new CommandBinding(AvalonEditCommands.IndentSelection, OnIndentSelection));
94     
95      TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings);
96    }
97   
98    static TextArea GetTextArea(object target)
99    {
100      return target as TextArea;
101    }
102   
103    #region Text Transformation Helpers
104    enum DefaultSegmentType
105    {
106      None,
107      WholeDocument,
108      CurrentLine
109    }
110   
111    /// <summary>
112    /// Calls transformLine on all lines in the selected range.
113    /// transformLine needs to handle read-only segments!
114    /// </summary>
115    static void TransformSelectedLines(Action<TextArea, DocumentLine> transformLine, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType)
116    {
117      TextArea textArea = GetTextArea(target);
118      if (textArea != null && textArea.Document != null) {
119        using (textArea.Document.RunUpdate()) {
120          DocumentLine start, end;
121          if (textArea.Selection.IsEmpty) {
122            if (defaultSegmentType == DefaultSegmentType.CurrentLine) {
123              start = end = textArea.Document.GetLineByNumber(textArea.Caret.Line);
124            } else if (defaultSegmentType == DefaultSegmentType.WholeDocument) {
125              start = textArea.Document.Lines.First();
126              end = textArea.Document.Lines.Last();
127            } else {
128              start = end = null;
129            }
130          } else {
131            ISegment segment = textArea.Selection.SurroundingSegment;
132            start = textArea.Document.GetLineByOffset(segment.Offset);
133            end = textArea.Document.GetLineByOffset(segment.EndOffset);
134            // don't include the last line if no characters on it are selected
135            if (start != end && end.Offset == segment.EndOffset)
136              end = end.PreviousLine;
137          }
138          if (start != null) {
139            transformLine(textArea, start);
140            while (start != end) {
141              start = start.NextLine;
142              transformLine(textArea, start);
143            }
144          }
145        }
146        textArea.Caret.BringCaretToView();
147        args.Handled = true;
148      }
149    }
150   
151    /// <summary>
152    /// Calls transformLine on all writable segment in the selected range.
153    /// </summary>
154    static void TransformSelectedSegments(Action<TextArea, ISegment> transformSegment, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType)
155    {
156      TextArea textArea = GetTextArea(target);
157      if (textArea != null && textArea.Document != null) {
158        using (textArea.Document.RunUpdate()) {
159          IEnumerable<ISegment> segments;
160          if (textArea.Selection.IsEmpty) {
161            if (defaultSegmentType == DefaultSegmentType.CurrentLine) {
162              segments = new ISegment[] { textArea.Document.GetLineByNumber(textArea.Caret.Line) };
163            } else if (defaultSegmentType == DefaultSegmentType.WholeDocument) {
164              segments = textArea.Document.Lines.Cast<ISegment>();
165            } else {
166              segments = null;
167            }
168          } else {
169            segments = textArea.Selection.Segments.Cast<ISegment>();
170          }
171          if (segments != null) {
172            foreach (ISegment segment in segments.Reverse()) {
173              foreach (ISegment writableSegment in textArea.GetDeletableSegments(segment).Reverse()) {
174                transformSegment(textArea, writableSegment);
175              }
176            }
177          }
178        }
179        textArea.Caret.BringCaretToView();
180        args.Handled = true;
181      }
182    }
183    #endregion
184   
185    #region EnterLineBreak
186    static void OnEnter(object target, ExecutedRoutedEventArgs args)
187    {
188      TextArea textArea = GetTextArea(target);
189      if (textArea != null && textArea.IsKeyboardFocused) {
190        textArea.PerformTextInput("\n");
191        args.Handled = true;
192      }
193    }
194    #endregion
195   
196    #region Tab
197    static void OnTab(object target, ExecutedRoutedEventArgs args)
198    {
199      TextArea textArea = GetTextArea(target);
200      if (textArea != null && textArea.Document != null) {
201        using (textArea.Document.RunUpdate()) {
202          if (textArea.Selection.IsMultiline) {
203            var segment = textArea.Selection.SurroundingSegment;
204            DocumentLine start = textArea.Document.GetLineByOffset(segment.Offset);
205            DocumentLine end = textArea.Document.GetLineByOffset(segment.EndOffset);
206            // don't include the last line if no characters on it are selected
207            if (start != end && end.Offset == segment.EndOffset)
208              end = end.PreviousLine;
209            DocumentLine current = start;
210            while (true) {
211              int offset = current.Offset;
212              if (textArea.ReadOnlySectionProvider.CanInsert(offset))
213                textArea.Document.Replace(offset, 0, textArea.Options.IndentationString, OffsetChangeMappingType.KeepAnchorBeforeInsertion);
214              if (current == end)
215                break;
216              current = current.NextLine;
217            }
218          } else {
219            string indentationString = textArea.Options.GetIndentationString(textArea.Caret.Column);
220            textArea.ReplaceSelectionWithText(indentationString);
221          }
222        }
223        textArea.Caret.BringCaretToView();
224        args.Handled = true;
225      }
226    }
227   
228    static void OnShiftTab(object target, ExecutedRoutedEventArgs args)
229    {
230      TransformSelectedLines(
231        delegate (TextArea textArea, DocumentLine line) {
232          int offset = line.Offset;
233          ISegment s = TextUtilities.GetSingleIndentationSegment(textArea.Document, offset, textArea.Options.IndentationSize);
234          if (s.Length > 0) {
235            s = textArea.GetDeletableSegments(s).FirstOrDefault();
236            if (s != null && s.Length > 0) {
237              textArea.Document.Remove(s.Offset, s.Length);
238            }
239          }
240        }, target, args, DefaultSegmentType.CurrentLine);
241    }
242    #endregion
243   
244    #region Delete
245    static ExecutedRoutedEventHandler OnDelete(CaretMovementType caretMovement)
246    {
247      return (target, args) => {
248        TextArea textArea = GetTextArea(target);
249        if (textArea != null && textArea.Document != null) {
250          if (textArea.Selection.IsEmpty) {
251            TextViewPosition startPos = textArea.Caret.Position;
252            bool enableVirtualSpace = textArea.Options.EnableVirtualSpace;
253            // When pressing delete; don't move the caret further into virtual space - instead delete the newline
254            if (caretMovement == CaretMovementType.CharRight)
255              enableVirtualSpace = false;
256            double desiredXPos = textArea.Caret.DesiredXPos;
257            TextViewPosition endPos = CaretNavigationCommandHandler.GetNewCaretPosition(
258              textArea.TextView, startPos, caretMovement, enableVirtualSpace, ref desiredXPos);
259            // GetNewCaretPosition may return (0,0) as new position,
260            // thus we need to validate endPos before using it in the selection.
261            if (endPos.Line < 1 || endPos.Column < 1)
262              endPos = new TextViewPosition(Math.Max(endPos.Line, 1), Math.Max(endPos.Column, 1));
263            // Don't select the text to be deleted; just reuse the ReplaceSelectionWithText logic
264            var sel = new SimpleSelection(textArea, startPos, endPos);
265            sel.ReplaceSelectionWithText(string.Empty);
266          } else {
267            textArea.RemoveSelectedText();
268          }
269          textArea.Caret.BringCaretToView();
270          args.Handled = true;
271        }
272      };
273    }
274   
275    static void CanDelete(object target, CanExecuteRoutedEventArgs args)
276    {
277      // HasSomethingSelected for delete command
278      TextArea textArea = GetTextArea(target);
279      if (textArea != null && textArea.Document != null) {
280        args.CanExecute = !textArea.Selection.IsEmpty;
281        args.Handled = true;
282      }
283    }
284    #endregion
285   
286    #region Clipboard commands
287    static void CanCutOrCopy(object target, CanExecuteRoutedEventArgs args)
288    {
289      // HasSomethingSelected for copy and cut commands
290      TextArea textArea = GetTextArea(target);
291      if (textArea != null && textArea.Document != null) {
292        args.CanExecute = textArea.Options.CutCopyWholeLine || !textArea.Selection.IsEmpty;
293        args.Handled = true;
294      }
295    }
296   
297    static void OnCopy(object target, ExecutedRoutedEventArgs args)
298    {
299      TextArea textArea = GetTextArea(target);
300      if (textArea != null && textArea.Document != null) {
301        if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine) {
302          DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
303          CopyWholeLine(textArea, currentLine);
304        } else {
305          CopySelectedText(textArea);
306        }
307        args.Handled = true;
308      }
309    }
310   
311    static void OnCut(object target, ExecutedRoutedEventArgs args)
312    {
313      TextArea textArea = GetTextArea(target);
314      if (textArea != null && textArea.Document != null) {
315        if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine) {
316          DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
317          if (CopyWholeLine(textArea, currentLine)) {
318            ISegment[] segmentsToDelete = textArea.GetDeletableSegments(new SimpleSegment(currentLine.Offset, currentLine.TotalLength));
319            for (int i = segmentsToDelete.Length - 1; i >= 0; i--) {
320              textArea.Document.Remove(segmentsToDelete[i]);
321            }
322          }
323        } else {
324          if (CopySelectedText(textArea))
325            textArea.RemoveSelectedText();
326        }
327        textArea.Caret.BringCaretToView();
328        args.Handled = true;
329      }
330    }
331   
332    static bool CopySelectedText(TextArea textArea)
333    {
334      var data = textArea.Selection.CreateDataObject(textArea);
335      var copyingEventArgs = new DataObjectCopyingEventArgs(data, false);
336      textArea.RaiseEvent(copyingEventArgs);
337      if (copyingEventArgs.CommandCancelled)
338        return false;
339     
340      try {
341        Clipboard.SetDataObject(data, true);
342      } catch (ExternalException) {
343        // Apparently this exception sometimes happens randomly.
344        // The MS controls just ignore it, so we'll do the same.
345        return false;
346      }
347     
348      string text = textArea.Selection.GetText();
349      text = TextUtilities.NormalizeNewLines(text, Environment.NewLine);
350      textArea.OnTextCopied(new TextEventArgs(text));
351      return true;
352    }
353   
354    const string LineSelectedType = "MSDEVLineSelect";  // This is the type VS 2003 and 2005 use for flagging a whole line copy
355   
356    public static bool ConfirmDataFormat(TextArea textArea, DataObject dataObject, string format)
357    {
358      var e = new DataObjectSettingDataEventArgs(dataObject, format);
359      textArea.RaiseEvent(e);
360      return !e.CommandCancelled;
361    }
362   
363    static bool CopyWholeLine(TextArea textArea, DocumentLine line)
364    {
365      ISegment wholeLine = new SimpleSegment(line.Offset, line.TotalLength);
366      string text = textArea.Document.GetText(wholeLine);
367      // Ensure we use the appropriate newline sequence for the OS
368      text = TextUtilities.NormalizeNewLines(text, Environment.NewLine);
369      DataObject data = new DataObject();
370      if (ConfirmDataFormat(textArea, data, DataFormats.UnicodeText))
371        data.SetText(text);
372     
373      // Also copy text in HTML format to clipboard - good for pasting text into Word
374      // or to the SharpDevelop forums.
375      if (ConfirmDataFormat(textArea, data, DataFormats.Html)) {
376        IHighlighter highlighter = textArea.GetService(typeof(IHighlighter)) as IHighlighter;
377        HtmlClipboard.SetHtml(data, HtmlClipboard.CreateHtmlFragment(textArea.Document, highlighter, wholeLine, new HtmlOptions(textArea.Options)));
378      }
379     
380      if (ConfirmDataFormat(textArea, data, LineSelectedType)) {
381        MemoryStream lineSelected = new MemoryStream(1);
382        lineSelected.WriteByte(1);
383        data.SetData(LineSelectedType, lineSelected, false);
384      }
385     
386      var copyingEventArgs = new DataObjectCopyingEventArgs(data, false);
387      textArea.RaiseEvent(copyingEventArgs);
388      if (copyingEventArgs.CommandCancelled)
389        return false;
390     
391      try {
392        Clipboard.SetDataObject(data, true);
393      } catch (ExternalException) {
394        // Apparently this exception sometimes happens randomly.
395        // The MS controls just ignore it, so we'll do the same.
396        return false;
397      }
398      textArea.OnTextCopied(new TextEventArgs(text));
399      return true;
400    }
401   
402    static void CanPaste(object target, CanExecuteRoutedEventArgs args)
403    {
404      TextArea textArea = GetTextArea(target);
405      if (textArea != null && textArea.Document != null) {
406        args.CanExecute = textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset)
407          && Clipboard.ContainsText();
408        // WPF Clipboard.ContainsText() is safe to call without catching ExternalExceptions
409        // because it doesn't try to lock the clipboard - it just peeks inside with IsClipboardFormatAvailable().
410        args.Handled = true;
411      }
412    }
413   
414    static void OnPaste(object target, ExecutedRoutedEventArgs args)
415    {
416      TextArea textArea = GetTextArea(target);
417      if (textArea != null && textArea.Document != null) {
418        IDataObject dataObject;
419        try {
420          dataObject = Clipboard.GetDataObject();
421        } catch (ExternalException) {
422          return;
423        }
424        if (dataObject == null)
425          return;
426        Debug.WriteLine(dataObject.GetData(DataFormats.Html) as string);
427       
428        // convert text back to correct newlines for this document
429        string newLine = TextUtilities.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line);
430        string text;
431        try {
432          text = (string)dataObject.GetData(DataFormats.UnicodeText);
433          text = TextUtilities.NormalizeNewLines(text, newLine);
434        } catch (OutOfMemoryException) {
435          return;
436        }
437       
438        if (!string.IsNullOrEmpty(text)) {
439          bool fullLine = textArea.Options.CutCopyWholeLine && dataObject.GetDataPresent(LineSelectedType);
440          bool rectangular = dataObject.GetDataPresent(RectangleSelection.RectangularSelectionDataType);
441         
442          string pasteFormat;
443          // fill the suggested DataFormat used for the paste action:
444          if (fullLine)
445            pasteFormat = LineSelectedType;
446          else if (rectangular && textArea.Selection.IsEmpty && !(textArea.Selection is RectangleSelection))
447            pasteFormat = RectangleSelection.RectangularSelectionDataType;
448          else
449            pasteFormat = DataFormats.UnicodeText;
450         
451          var pastingEventArgs = new DataObjectPastingEventArgs(dataObject, false, pasteFormat);
452          textArea.RaiseEvent(pastingEventArgs);
453          if (pastingEventArgs.CommandCancelled)
454            return;
455         
456          // DataObject.PastingEvent handlers might have changed the format to apply.
457          pasteFormat = pastingEventArgs.FormatToApply;
458         
459          fullLine = pasteFormat == LineSelectedType;
460          rectangular = pasteFormat == RectangleSelection.RectangularSelectionDataType;
461         
462          if (fullLine) {
463            DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
464            if (textArea.ReadOnlySectionProvider.CanInsert(currentLine.Offset)) {
465              textArea.Document.Insert(currentLine.Offset, text);
466            }
467          } else if (rectangular && textArea.Selection.IsEmpty && !(textArea.Selection is RectangleSelection)) {
468            if (!RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, false))
469              textArea.ReplaceSelectionWithText(text);
470          } else {
471            textArea.ReplaceSelectionWithText(text);
472          }
473        }
474        textArea.Caret.BringCaretToView();
475        args.Handled = true;
476      }
477    }
478    #endregion
479   
480    #region DeleteLine
481    static void OnDeleteLine(object target, ExecutedRoutedEventArgs args)
482    {
483      TextArea textArea = GetTextArea(target);
484      if (textArea != null && textArea.Document != null) {
485        int firstLineIndex, lastLineIndex;
486        if (textArea.Selection.Length == 0) {
487          // There is no selection, simply delete current line
488          firstLineIndex = lastLineIndex = textArea.Caret.Line;
489        } else {
490          // There is a selection, remove all lines affected by it (use Min/Max to be independent from selection direction)
491          firstLineIndex = Math.Min(textArea.Selection.StartPosition.Line, textArea.Selection.EndPosition.Line);
492          lastLineIndex = Math.Max(textArea.Selection.StartPosition.Line, textArea.Selection.EndPosition.Line);
493        }
494        DocumentLine startLine = textArea.Document.GetLineByNumber(firstLineIndex);
495        DocumentLine endLine = textArea.Document.GetLineByNumber(lastLineIndex);
496        textArea.Selection = Selection.Create(textArea, startLine.Offset, endLine.Offset + endLine.TotalLength);
497        textArea.RemoveSelectedText();
498        args.Handled = true;
499      }
500    }
501    #endregion
502   
503    #region Remove..Whitespace / Convert Tabs-Spaces
504    static void OnRemoveLeadingWhitespace(object target, ExecutedRoutedEventArgs args)
505    {
506      TransformSelectedLines(
507        delegate (TextArea textArea, DocumentLine line) {
508          textArea.Document.Remove(TextUtilities.GetLeadingWhitespace(textArea.Document, line));
509        }, target, args, DefaultSegmentType.WholeDocument);
510    }
511   
512    static void OnRemoveTrailingWhitespace(object target, ExecutedRoutedEventArgs args)
513    {
514      TransformSelectedLines(
515        delegate (TextArea textArea, DocumentLine line) {
516          textArea.Document.Remove(TextUtilities.GetTrailingWhitespace(textArea.Document, line));
517        }, target, args, DefaultSegmentType.WholeDocument);
518    }
519   
520    static void OnConvertTabsToSpaces(object target, ExecutedRoutedEventArgs args)
521    {
522      TransformSelectedSegments(ConvertTabsToSpaces, target, args, DefaultSegmentType.WholeDocument);
523    }
524   
525    static void OnConvertLeadingTabsToSpaces(object target, ExecutedRoutedEventArgs args)
526    {
527      TransformSelectedLines(
528        delegate (TextArea textArea, DocumentLine line) {
529          ConvertTabsToSpaces(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line));
530        }, target, args, DefaultSegmentType.WholeDocument);
531    }
532   
533    static void ConvertTabsToSpaces(TextArea textArea, ISegment segment)
534    {
535      TextDocument document = textArea.Document;
536      int endOffset = segment.EndOffset;
537      string indentationString = new string(' ', textArea.Options.IndentationSize);
538      for (int offset = segment.Offset; offset < endOffset; offset++) {
539        if (document.GetCharAt(offset) == '\t') {
540          document.Replace(offset, 1, indentationString, OffsetChangeMappingType.CharacterReplace);
541          endOffset += indentationString.Length - 1;
542        }
543      }
544    }
545   
546    static void OnConvertSpacesToTabs(object target, ExecutedRoutedEventArgs args)
547    {
548      TransformSelectedSegments(ConvertSpacesToTabs, target, args, DefaultSegmentType.WholeDocument);
549    }
550   
551    static void OnConvertLeadingSpacesToTabs(object target, ExecutedRoutedEventArgs args)
552    {
553      TransformSelectedLines(
554        delegate (TextArea textArea, DocumentLine line) {
555          ConvertSpacesToTabs(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line));
556        }, target, args, DefaultSegmentType.WholeDocument);
557    }
558   
559    static void ConvertSpacesToTabs(TextArea textArea, ISegment segment)
560    {
561      TextDocument document = textArea.Document;
562      int endOffset = segment.EndOffset;
563      int indentationSize = textArea.Options.IndentationSize;
564      int spacesCount = 0;
565      for (int offset = segment.Offset; offset < endOffset; offset++) {
566        if (document.GetCharAt(offset) == ' ') {
567          spacesCount++;
568          if (spacesCount == indentationSize) {
569            document.Replace(offset - (indentationSize - 1), indentationSize, "\t", OffsetChangeMappingType.CharacterReplace);
570            spacesCount = 0;
571            offset -= indentationSize - 1;
572            endOffset -= indentationSize - 1;
573          }
574        } else {
575          spacesCount = 0;
576        }
577      }
578    }
579    #endregion
580   
581    #region Convert...Case
582    static void ConvertCase(Func<string, string> transformText, object target, ExecutedRoutedEventArgs args)
583    {
584      TransformSelectedSegments(
585        delegate (TextArea textArea, ISegment segment) {
586          string oldText = textArea.Document.GetText(segment);
587          string newText = transformText(oldText);
588          textArea.Document.Replace(segment.Offset, segment.Length, newText, OffsetChangeMappingType.CharacterReplace);
589        }, target, args, DefaultSegmentType.WholeDocument);
590    }
591   
592    static void OnConvertToUpperCase(object target, ExecutedRoutedEventArgs args)
593    {
594      ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToUpper, target, args);
595    }
596   
597    static void OnConvertToLowerCase(object target, ExecutedRoutedEventArgs args)
598    {
599      ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToLower, target, args);
600    }
601   
602    static void OnConvertToTitleCase(object target, ExecutedRoutedEventArgs args)
603    {
604      ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToTitleCase, target, args);
605    }
606   
607    static void OnInvertCase(object target, ExecutedRoutedEventArgs args)
608    {
609      ConvertCase(InvertCase, target, args);
610    }
611   
612    static string InvertCase(string text)
613    {
614      CultureInfo culture = CultureInfo.CurrentCulture;
615      char[] buffer = text.ToCharArray();
616      for (int i = 0; i < buffer.Length; ++i) {
617        char c = buffer[i];
618        buffer[i] = char.IsUpper(c) ? char.ToLower(c, culture) : char.ToUpper(c, culture);
619      }
620      return new string(buffer);
621    }
622    #endregion
623   
624    static void OnIndentSelection(object target, ExecutedRoutedEventArgs args)
625    {
626      TextArea textArea = GetTextArea(target);
627      if (textArea != null && textArea.Document != null) {
628        using (textArea.Document.RunUpdate()) {
629          int start, end;
630          if (textArea.Selection.IsEmpty) {
631            start = 1;
632            end = textArea.Document.LineCount;
633          } else {
634            start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset).LineNumber;
635            end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.EndOffset).LineNumber;
636          }
637          textArea.IndentationStrategy.IndentLines(textArea.Document, start, end);
638        }
639        textArea.Caret.BringCaretToView();
640        args.Handled = true;
641      }
642    }
643  }
644}
Note: See TracBrowser for help on using the repository browser.