mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
578 lines
22 KiB
578 lines
22 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.Globalization; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Runtime.InteropServices; |
|
using System.Text; |
|
using System.Windows; |
|
using System.Windows.Documents; |
|
using System.Windows.Input; |
|
using ICSharpCode.AvalonEdit.Document; |
|
using ICSharpCode.AvalonEdit.Highlighting; |
|
using ICSharpCode.AvalonEdit.Search; |
|
using ICSharpCode.AvalonEdit.Utils; |
|
|
|
namespace ICSharpCode.AvalonEdit.Editing |
|
{ |
|
/// <summary> |
|
/// We re-use the CommandBinding and InputBinding instances between multiple text areas, |
|
/// so this class is static. |
|
/// </summary> |
|
static class EditingCommandHandler |
|
{ |
|
/// <summary> |
|
/// Creates a new <see cref="TextAreaInputHandler"/> for the text area. |
|
/// </summary> |
|
public static TextAreaInputHandler Create(TextArea textArea) |
|
{ |
|
TextAreaInputHandler handler = new TextAreaInputHandler(textArea); |
|
handler.CommandBindings.AddRange(CommandBindings); |
|
handler.InputBindings.AddRange(InputBindings); |
|
return handler; |
|
} |
|
|
|
static readonly List<CommandBinding> CommandBindings = new List<CommandBinding>(); |
|
static readonly List<InputBinding> InputBindings = new List<InputBinding>(); |
|
|
|
static void AddBinding(ICommand command, ModifierKeys modifiers, Key key, ExecutedRoutedEventHandler handler) |
|
{ |
|
CommandBindings.Add(new CommandBinding(command, handler)); |
|
InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(command, modifiers, key)); |
|
} |
|
|
|
static EditingCommandHandler() |
|
{ |
|
CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, OnDelete(ApplicationCommands.NotACommand), CanDelete)); |
|
AddBinding(EditingCommands.Delete, ModifierKeys.None, Key.Delete, OnDelete(EditingCommands.SelectRightByCharacter)); |
|
AddBinding(EditingCommands.DeleteNextWord, ModifierKeys.Control, Key.Delete, OnDelete(EditingCommands.SelectRightByWord)); |
|
AddBinding(EditingCommands.Backspace, ModifierKeys.None, Key.Back, OnDelete(EditingCommands.SelectLeftByCharacter)); |
|
InputBindings.Add(TextAreaDefaultInputHandler.CreateFrozenKeyBinding(EditingCommands.Backspace, ModifierKeys.Shift, Key.Back)); // make Shift-Backspace do the same as plain backspace |
|
AddBinding(EditingCommands.DeletePreviousWord, ModifierKeys.Control, Key.Back, OnDelete(EditingCommands.SelectLeftByWord)); |
|
AddBinding(EditingCommands.EnterParagraphBreak, ModifierKeys.None, Key.Enter, OnEnter); |
|
AddBinding(EditingCommands.EnterLineBreak, ModifierKeys.Shift, Key.Enter, OnEnter); |
|
AddBinding(EditingCommands.TabForward, ModifierKeys.None, Key.Tab, OnTab); |
|
AddBinding(EditingCommands.TabBackward, ModifierKeys.Shift, Key.Tab, OnShiftTab); |
|
|
|
CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopy, CanCutOrCopy)); |
|
CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCut, CanCutOrCopy)); |
|
CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPaste, CanPaste)); |
|
|
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.DeleteLine, OnDeleteLine)); |
|
|
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveLeadingWhitespace, OnRemoveLeadingWhitespace)); |
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveTrailingWhitespace, OnRemoveTrailingWhitespace)); |
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToUppercase, OnConvertToUpperCase)); |
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToLowercase, OnConvertToLowerCase)); |
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToTitleCase, OnConvertToTitleCase)); |
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.InvertCase, OnInvertCase)); |
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertTabsToSpaces, OnConvertTabsToSpaces)); |
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertSpacesToTabs, OnConvertSpacesToTabs)); |
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingTabsToSpaces, OnConvertLeadingTabsToSpaces)); |
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingSpacesToTabs, OnConvertLeadingSpacesToTabs)); |
|
CommandBindings.Add(new CommandBinding(AvalonEditCommands.IndentSelection, OnIndentSelection)); |
|
|
|
TextAreaDefaultInputHandler.WorkaroundWPFMemoryLeak(InputBindings); |
|
} |
|
|
|
static TextArea GetTextArea(object target) |
|
{ |
|
return target as TextArea; |
|
} |
|
|
|
#region Text Transformation Helpers |
|
enum DefaultSegmentType |
|
{ |
|
None, |
|
WholeDocument, |
|
CurrentLine |
|
} |
|
|
|
/// <summary> |
|
/// Calls transformLine on all lines in the selected range. |
|
/// transformLine needs to handle read-only segments! |
|
/// </summary> |
|
static void TransformSelectedLines(Action<TextArea, DocumentLine> transformLine, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType) |
|
{ |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
using (textArea.Document.RunUpdate()) { |
|
DocumentLine start, end; |
|
if (textArea.Selection.IsEmpty) { |
|
if (defaultSegmentType == DefaultSegmentType.CurrentLine) { |
|
start = end = textArea.Document.GetLineByNumber(textArea.Caret.Line); |
|
} else if (defaultSegmentType == DefaultSegmentType.WholeDocument) { |
|
start = textArea.Document.Lines.First(); |
|
end = textArea.Document.Lines.Last(); |
|
} else { |
|
start = end = null; |
|
} |
|
} else { |
|
ISegment segment = textArea.Selection.SurroundingSegment; |
|
start = textArea.Document.GetLineByOffset(segment.Offset); |
|
end = textArea.Document.GetLineByOffset(segment.EndOffset); |
|
// don't include the last line if no characters on it are selected |
|
if (start != end && end.Offset == segment.EndOffset) |
|
end = end.PreviousLine; |
|
} |
|
if (start != null) { |
|
transformLine(textArea, start); |
|
while (start != end) { |
|
start = start.NextLine; |
|
transformLine(textArea, start); |
|
} |
|
} |
|
} |
|
textArea.Caret.BringCaretToView(); |
|
args.Handled = true; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Calls transformLine on all writable segment in the selected range. |
|
/// </summary> |
|
static void TransformSelectedSegments(Action<TextArea, ISegment> transformSegment, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType) |
|
{ |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
using (textArea.Document.RunUpdate()) { |
|
IEnumerable<ISegment> segments; |
|
if (textArea.Selection.IsEmpty) { |
|
if (defaultSegmentType == DefaultSegmentType.CurrentLine) { |
|
segments = new ISegment[] { textArea.Document.GetLineByNumber(textArea.Caret.Line) }; |
|
} else if (defaultSegmentType == DefaultSegmentType.WholeDocument) { |
|
segments = textArea.Document.Lines.Cast<ISegment>(); |
|
} else { |
|
segments = null; |
|
} |
|
} else { |
|
segments = textArea.Selection.Segments.Cast<ISegment>(); |
|
} |
|
if (segments != null) { |
|
foreach (ISegment segment in segments.Reverse()) { |
|
foreach (ISegment writableSegment in textArea.GetDeletableSegments(segment).Reverse()) { |
|
transformSegment(textArea, writableSegment); |
|
} |
|
} |
|
} |
|
} |
|
textArea.Caret.BringCaretToView(); |
|
args.Handled = true; |
|
} |
|
} |
|
#endregion |
|
|
|
#region EnterLineBreak |
|
static void OnEnter(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.IsKeyboardFocused) { |
|
textArea.PerformTextInput("\n"); |
|
args.Handled = true; |
|
} |
|
} |
|
#endregion |
|
|
|
#region Tab |
|
static void OnTab(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
using (textArea.Document.RunUpdate()) { |
|
if (textArea.Selection.IsMultiline) { |
|
var segment = textArea.Selection.SurroundingSegment; |
|
DocumentLine start = textArea.Document.GetLineByOffset(segment.Offset); |
|
DocumentLine end = textArea.Document.GetLineByOffset(segment.EndOffset); |
|
// don't include the last line if no characters on it are selected |
|
if (start != end && end.Offset == segment.EndOffset) |
|
end = end.PreviousLine; |
|
DocumentLine current = start; |
|
while (true) { |
|
int offset = current.Offset; |
|
if (textArea.ReadOnlySectionProvider.CanInsert(offset)) |
|
textArea.Document.Replace(offset, 0, textArea.Options.IndentationString, OffsetChangeMappingType.KeepAnchorBeforeInsertion); |
|
if (current == end) |
|
break; |
|
current = current.NextLine; |
|
} |
|
} else { |
|
string indentationString = textArea.Options.GetIndentationString(textArea.Caret.Column); |
|
textArea.ReplaceSelectionWithText(indentationString); |
|
} |
|
} |
|
textArea.Caret.BringCaretToView(); |
|
args.Handled = true; |
|
} |
|
} |
|
|
|
static void OnShiftTab(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TransformSelectedLines( |
|
delegate (TextArea textArea, DocumentLine line) { |
|
int offset = line.Offset; |
|
ISegment s = TextUtilities.GetSingleIndentationSegment(textArea.Document, offset, textArea.Options.IndentationSize); |
|
if (s.Length > 0) { |
|
s = textArea.GetDeletableSegments(s).FirstOrDefault(); |
|
if (s != null && s.Length > 0) { |
|
textArea.Document.Remove(s.Offset, s.Length); |
|
} |
|
} |
|
}, target, args, DefaultSegmentType.CurrentLine); |
|
} |
|
#endregion |
|
|
|
#region Delete |
|
static ExecutedRoutedEventHandler OnDelete(RoutedUICommand selectingCommand) |
|
{ |
|
return (target, args) => { |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
// call BeginUpdate before running the 'selectingCommand' |
|
// so that undoing the delete does not select the deleted character |
|
using (textArea.Document.RunUpdate()) { |
|
if (textArea.Selection.IsEmpty) { |
|
TextViewPosition oldCaretPosition = textArea.Caret.Position; |
|
if (textArea.Caret.IsInVirtualSpace && selectingCommand == EditingCommands.SelectRightByCharacter) |
|
EditingCommands.SelectRightByWord.Execute(args.Parameter, textArea); |
|
else |
|
selectingCommand.Execute(args.Parameter, textArea); |
|
bool hasSomethingDeletable = false; |
|
foreach (ISegment s in textArea.Selection.Segments) { |
|
if (textArea.GetDeletableSegments(s).Length > 0) { |
|
hasSomethingDeletable = true; |
|
break; |
|
} |
|
} |
|
if (!hasSomethingDeletable) { |
|
// If nothing in the selection is deletable; then reset caret+selection |
|
// to the previous value. This prevents the caret from moving through read-only sections. |
|
textArea.Caret.Position = oldCaretPosition; |
|
textArea.ClearSelection(); |
|
} |
|
} |
|
textArea.RemoveSelectedText(); |
|
} |
|
textArea.Caret.BringCaretToView(); |
|
args.Handled = true; |
|
} |
|
}; |
|
} |
|
|
|
static void CanDelete(object target, CanExecuteRoutedEventArgs args) |
|
{ |
|
// HasSomethingSelected for delete command |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
args.CanExecute = !textArea.Selection.IsEmpty; |
|
args.Handled = true; |
|
} |
|
} |
|
#endregion |
|
|
|
#region Clipboard commands |
|
static void CanCutOrCopy(object target, CanExecuteRoutedEventArgs args) |
|
{ |
|
// HasSomethingSelected for copy and cut commands |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
args.CanExecute = textArea.Options.CutCopyWholeLine || !textArea.Selection.IsEmpty; |
|
args.Handled = true; |
|
} |
|
} |
|
|
|
static void OnCopy(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine) { |
|
DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line); |
|
CopyWholeLine(textArea, currentLine); |
|
} else { |
|
CopySelectedText(textArea); |
|
} |
|
args.Handled = true; |
|
} |
|
} |
|
|
|
static void OnCut(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine) { |
|
DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line); |
|
CopyWholeLine(textArea, currentLine); |
|
ISegment[] segmentsToDelete = textArea.GetDeletableSegments(new SimpleSegment(currentLine.Offset, currentLine.TotalLength)); |
|
for (int i = segmentsToDelete.Length - 1; i >= 0; i--) { |
|
textArea.Document.Remove(segmentsToDelete[i]); |
|
} |
|
} else { |
|
CopySelectedText(textArea); |
|
textArea.RemoveSelectedText(); |
|
} |
|
textArea.Caret.BringCaretToView(); |
|
args.Handled = true; |
|
} |
|
} |
|
|
|
static void CopySelectedText(TextArea textArea) |
|
{ |
|
var data = textArea.Selection.CreateDataObject(textArea); |
|
|
|
try { |
|
Clipboard.SetDataObject(data, true); |
|
} catch (ExternalException) { |
|
// Apparently this exception sometimes happens randomly. |
|
// The MS controls just ignore it, so we'll do the same. |
|
return; |
|
} |
|
|
|
string text = textArea.Selection.GetText(); |
|
text = TextUtilities.NormalizeNewLines(text, Environment.NewLine); |
|
textArea.OnTextCopied(new TextEventArgs(text)); |
|
} |
|
|
|
const string LineSelectedType = "MSDEVLineSelect"; // This is the type VS 2003 and 2005 use for flagging a whole line copy |
|
|
|
static void CopyWholeLine(TextArea textArea, DocumentLine line) |
|
{ |
|
ISegment wholeLine = new SimpleSegment(line.Offset, line.TotalLength); |
|
string text = textArea.Document.GetText(wholeLine); |
|
// Ensure we use the appropriate newline sequence for the OS |
|
text = TextUtilities.NormalizeNewLines(text, Environment.NewLine); |
|
DataObject data = new DataObject(text); |
|
|
|
// Also copy text in HTML format to clipboard - good for pasting text into Word |
|
// or to the SharpDevelop forums. |
|
IHighlighter highlighter = textArea.GetService(typeof(IHighlighter)) as IHighlighter; |
|
HtmlClipboard.SetHtml(data, HtmlClipboard.CreateHtmlFragment(textArea.Document, highlighter, wholeLine, new HtmlOptions(textArea.Options))); |
|
|
|
MemoryStream lineSelected = new MemoryStream(1); |
|
lineSelected.WriteByte(1); |
|
data.SetData(LineSelectedType, lineSelected, false); |
|
|
|
try { |
|
Clipboard.SetDataObject(data, true); |
|
} catch (ExternalException) { |
|
// Apparently this exception sometimes happens randomly. |
|
// The MS controls just ignore it, so we'll do the same. |
|
return; |
|
} |
|
textArea.OnTextCopied(new TextEventArgs(text)); |
|
} |
|
|
|
static void CanPaste(object target, CanExecuteRoutedEventArgs args) |
|
{ |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
args.CanExecute = textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset) |
|
&& Clipboard.ContainsText(); |
|
// WPF Clipboard.ContainsText() is safe to call without catching ExternalExceptions |
|
// because it doesn't try to lock the clipboard - it just peeks inside with IsClipboardFormatAvailable(). |
|
args.Handled = true; |
|
} |
|
} |
|
|
|
static void OnPaste(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
IDataObject dataObject; |
|
try { |
|
dataObject = Clipboard.GetDataObject(); |
|
} catch (ExternalException) { |
|
return; |
|
} |
|
if (dataObject == null) |
|
return; |
|
Debug.WriteLine( dataObject.GetData(DataFormats.Html) as string ); |
|
|
|
// convert text back to correct newlines for this document |
|
string newLine = TextUtilities.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line); |
|
string text; |
|
try { |
|
text = (string)dataObject.GetData(DataFormats.UnicodeText); |
|
text = TextUtilities.NormalizeNewLines(text, newLine); |
|
} catch (OutOfMemoryException) { |
|
return; |
|
} |
|
|
|
if (!string.IsNullOrEmpty(text)) { |
|
bool fullLine = textArea.Options.CutCopyWholeLine && dataObject.GetDataPresent(LineSelectedType); |
|
bool rectangular = dataObject.GetDataPresent(RectangleSelection.RectangularSelectionDataType); |
|
if (fullLine) { |
|
DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line); |
|
if (textArea.ReadOnlySectionProvider.CanInsert(currentLine.Offset)) { |
|
textArea.Document.Insert(currentLine.Offset, text); |
|
} |
|
} else if (rectangular && textArea.Selection.IsEmpty && !(textArea.Selection is RectangleSelection)) { |
|
if (!RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, false)) |
|
textArea.ReplaceSelectionWithText(text); |
|
} else { |
|
textArea.ReplaceSelectionWithText(text); |
|
} |
|
} |
|
textArea.Caret.BringCaretToView(); |
|
args.Handled = true; |
|
} |
|
} |
|
#endregion |
|
|
|
#region DeleteLine |
|
static void OnDeleteLine(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line); |
|
textArea.Selection = Selection.Create(textArea, currentLine.Offset, currentLine.Offset + currentLine.TotalLength); |
|
textArea.RemoveSelectedText(); |
|
args.Handled = true; |
|
} |
|
} |
|
#endregion |
|
|
|
#region Remove..Whitespace / Convert Tabs-Spaces |
|
static void OnRemoveLeadingWhitespace(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TransformSelectedLines( |
|
delegate (TextArea textArea, DocumentLine line) { |
|
textArea.Document.Remove(TextUtilities.GetLeadingWhitespace(textArea.Document, line)); |
|
}, target, args, DefaultSegmentType.WholeDocument); |
|
} |
|
|
|
static void OnRemoveTrailingWhitespace(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TransformSelectedLines( |
|
delegate (TextArea textArea, DocumentLine line) { |
|
textArea.Document.Remove(TextUtilities.GetTrailingWhitespace(textArea.Document, line)); |
|
}, target, args, DefaultSegmentType.WholeDocument); |
|
} |
|
|
|
static void OnConvertTabsToSpaces(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TransformSelectedSegments(ConvertTabsToSpaces, target, args, DefaultSegmentType.WholeDocument); |
|
} |
|
|
|
static void OnConvertLeadingTabsToSpaces(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TransformSelectedLines( |
|
delegate (TextArea textArea, DocumentLine line) { |
|
ConvertTabsToSpaces(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line)); |
|
}, target, args, DefaultSegmentType.WholeDocument); |
|
} |
|
|
|
static void ConvertTabsToSpaces(TextArea textArea, ISegment segment) |
|
{ |
|
TextDocument document = textArea.Document; |
|
int endOffset = segment.EndOffset; |
|
string indentationString = new string(' ', textArea.Options.IndentationSize); |
|
for (int offset = segment.Offset; offset < endOffset; offset++) { |
|
if (document.GetCharAt(offset) == '\t') { |
|
document.Replace(offset, 1, indentationString, OffsetChangeMappingType.CharacterReplace); |
|
endOffset += indentationString.Length - 1; |
|
} |
|
} |
|
} |
|
|
|
static void OnConvertSpacesToTabs(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TransformSelectedSegments(ConvertSpacesToTabs, target, args, DefaultSegmentType.WholeDocument); |
|
} |
|
|
|
static void OnConvertLeadingSpacesToTabs(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TransformSelectedLines( |
|
delegate (TextArea textArea, DocumentLine line) { |
|
ConvertSpacesToTabs(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line)); |
|
}, target, args, DefaultSegmentType.WholeDocument); |
|
} |
|
|
|
static void ConvertSpacesToTabs(TextArea textArea, ISegment segment) |
|
{ |
|
TextDocument document = textArea.Document; |
|
int endOffset = segment.EndOffset; |
|
int indentationSize = textArea.Options.IndentationSize; |
|
int spacesCount = 0; |
|
for (int offset = segment.Offset; offset < endOffset; offset++) { |
|
if (document.GetCharAt(offset) == ' ') { |
|
spacesCount++; |
|
if (spacesCount == indentationSize) { |
|
document.Replace(offset - (indentationSize - 1), indentationSize, "\t", OffsetChangeMappingType.CharacterReplace); |
|
spacesCount = 0; |
|
offset -= indentationSize - 1; |
|
endOffset -= indentationSize - 1; |
|
} |
|
} else { |
|
spacesCount = 0; |
|
} |
|
} |
|
} |
|
#endregion |
|
|
|
#region Convert...Case |
|
static void ConvertCase(Func<string, string> transformText, object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TransformSelectedSegments( |
|
delegate (TextArea textArea, ISegment segment) { |
|
string oldText = textArea.Document.GetText(segment); |
|
string newText = transformText(oldText); |
|
textArea.Document.Replace(segment.Offset, segment.Length, newText, OffsetChangeMappingType.CharacterReplace); |
|
}, target, args, DefaultSegmentType.WholeDocument); |
|
} |
|
|
|
static void OnConvertToUpperCase(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToUpper, target, args); |
|
} |
|
|
|
static void OnConvertToLowerCase(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToLower, target, args); |
|
} |
|
|
|
static void OnConvertToTitleCase(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToTitleCase, target, args); |
|
} |
|
|
|
static void OnInvertCase(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
ConvertCase(InvertCase, target, args); |
|
} |
|
|
|
static string InvertCase(string text) |
|
{ |
|
CultureInfo culture = CultureInfo.CurrentCulture; |
|
char[] buffer = text.ToCharArray(); |
|
for (int i = 0; i < buffer.Length; ++i) { |
|
char c = buffer[i]; |
|
buffer[i] = char.IsUpper(c) ? char.ToLower(c, culture) : char.ToUpper(c, culture); |
|
} |
|
return new string(buffer); |
|
} |
|
#endregion |
|
|
|
static void OnIndentSelection(object target, ExecutedRoutedEventArgs args) |
|
{ |
|
TextArea textArea = GetTextArea(target); |
|
if (textArea != null && textArea.Document != null) { |
|
using (textArea.Document.RunUpdate()) { |
|
int start, end; |
|
if (textArea.Selection.IsEmpty) { |
|
start = 1; |
|
end = textArea.Document.LineCount; |
|
} else { |
|
start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset).LineNumber; |
|
end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.EndOffset).LineNumber; |
|
} |
|
textArea.IndentationStrategy.IndentLines(textArea.Document, start, end); |
|
} |
|
textArea.Caret.BringCaretToView(); |
|
args.Handled = true; |
|
} |
|
} |
|
} |
|
}
|
|
|