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 | |
---|
19 | using System; |
---|
20 | using System.Collections.Generic; |
---|
21 | using System.Diagnostics; |
---|
22 | using System.Globalization; |
---|
23 | using System.IO; |
---|
24 | using System.Linq; |
---|
25 | using System.Runtime.InteropServices; |
---|
26 | using System.Windows; |
---|
27 | using System.Windows.Documents; |
---|
28 | using System.Windows.Input; |
---|
29 | using ICSharpCode.AvalonEdit.Document; |
---|
30 | using ICSharpCode.AvalonEdit.Highlighting; |
---|
31 | using ICSharpCode.AvalonEdit.Utils; |
---|
32 | #if NREFACTORY |
---|
33 | using ICSharpCode.NRefactory.Editor; |
---|
34 | #endif |
---|
35 | |
---|
36 | namespace 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 | } |
---|