931 lines
34 KiB
C#
931 lines
34 KiB
C#
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using Unity.CodeEditor.Document;
|
|
using Unity.CodeEditor.Platform;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace Unity.CodeEditor.Editing
|
|
{
|
|
/// <summary>
|
|
/// We re-use the CommandBinding and InputBinding instances between multiple text areas,
|
|
/// so this class is static.
|
|
/// </summary>
|
|
internal class EditingCommandHandler
|
|
{
|
|
Dictionary<KeyGesture, EditingCommand> _editingCommands = new Dictionary<KeyGesture, EditingCommand>();
|
|
|
|
internal EditingCommandHandler(TextArea textArea)
|
|
{
|
|
TextArea = textArea ?? throw new ArgumentNullException(nameof(textArea));
|
|
|
|
var keymap = PlatformHotkeyConfiguration.Current;
|
|
|
|
_editingCommands.Add(new KeyGesture(KeyCode.Delete), EditingCommand.Delete);
|
|
_editingCommands.Add(new KeyGesture(KeyCode.Delete, KeyModifiers.Control), EditingCommand.DeleteNextWord);
|
|
_editingCommands.Add(new KeyGesture(KeyCode.Backspace), EditingCommand.Backspace);
|
|
_editingCommands.Add(new KeyGesture(KeyCode.Backspace, KeyModifiers.Shift), EditingCommand.Backspace); // make Shift-Backspace do the same as plain backspace
|
|
_editingCommands.Add(new KeyGesture(KeyCode.Backspace, KeyModifiers.Control), EditingCommand.DeletePreviousWord);
|
|
_editingCommands.Add(new KeyGesture(KeyCode.KeypadEnter), EditingCommand.EnterParagraphBreak);
|
|
_editingCommands.Add(new KeyGesture(KeyCode.Return), EditingCommand.EnterParagraphBreak);
|
|
_editingCommands.Add(new KeyGesture(KeyCode.Return, KeyModifiers.Shift), EditingCommand.EnterLineBreak);
|
|
_editingCommands.Add(new KeyGesture(KeyCode.Tab), EditingCommand.TabForward);
|
|
_editingCommands.Add(new KeyGesture(KeyCode.Tab, KeyModifiers.Shift), EditingCommand.TabBackward);
|
|
|
|
keymap.Copy.ForEach(g => _editingCommands.Add(g, EditingCommand.Copy));
|
|
keymap.Cut.ForEach(g => _editingCommands.Add(g, EditingCommand.Cut));
|
|
keymap.Paste.ForEach(g => _editingCommands.Add(g, EditingCommand.Paste));
|
|
|
|
_editingCommands.Add(new KeyGesture(KeyCode.Insert), EditingCommand.ToggleOverstrike);
|
|
keymap.DeleteWholeLine.ForEach(g => _editingCommands.Add(g, EditingCommand.DeleteLine));
|
|
_editingCommands.Add(new KeyGesture(KeyCode.I, keymap.CommandModifiers), EditingCommand.IndentSelection);
|
|
|
|
keymap.Undo.ForEach(g => _editingCommands.Add(g, EditingCommand.Undo));
|
|
keymap.Redo.ForEach(g => _editingCommands.Add(g, EditingCommand.Redo));
|
|
}
|
|
|
|
internal TextArea TextArea { get; }
|
|
|
|
internal void OnKeyDown(KeyDownEvent e)
|
|
{
|
|
if (TextArea?.Document == null)
|
|
return;
|
|
|
|
EditingCommand command = GetEditingCommand(e);
|
|
|
|
switch (command)
|
|
{
|
|
case EditingCommand.Delete:
|
|
Delete();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.Copy:
|
|
Copy();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.Cut:
|
|
Cut();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.Paste:
|
|
Paste();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.DeleteNextWord:
|
|
DeleteNextWord();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.Backspace:
|
|
Backspace();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.DeletePreviousWord:
|
|
DeletePreviousWord();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.EnterParagraphBreak:
|
|
EnterParagraphBreak();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.EnterLineBreak:
|
|
EnterLineBreak();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.TabForward:
|
|
TabForward();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.TabBackward:
|
|
TabBackward();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.ToggleOverstrike:
|
|
ToggleOverstrike();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.DeleteLine:
|
|
DeleteLine();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.IndentSelection:
|
|
IndentSelection();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.Undo:
|
|
ExecuteUndo();
|
|
e.StopPropagation();
|
|
break;
|
|
case EditingCommand.Redo:
|
|
ExecuteRedo();
|
|
e.StopPropagation();
|
|
break;
|
|
}
|
|
}
|
|
|
|
EditingCommand GetEditingCommand(KeyDownEvent e)
|
|
{
|
|
KeyGesture keyGesture = new KeyGesture(e.keyCode, e.GetKeyModifiers());
|
|
|
|
if (_editingCommands.TryGetValue(keyGesture, out EditingCommand command))
|
|
return command;
|
|
|
|
return EditingCommand.None;
|
|
}
|
|
|
|
void Paste()
|
|
{
|
|
OnPaste(TextArea);
|
|
}
|
|
|
|
void Cut()
|
|
{
|
|
OnCut(TextArea);
|
|
}
|
|
|
|
void IndentSelection()
|
|
{
|
|
OnIndentSelection(TextArea);
|
|
}
|
|
|
|
void DeleteLine()
|
|
{
|
|
OnDeleteLine(TextArea);
|
|
}
|
|
|
|
void ToggleOverstrike()
|
|
{
|
|
OnToggleOverstrike(TextArea);
|
|
}
|
|
|
|
void TabBackward()
|
|
{
|
|
OnShiftTab(TextArea);
|
|
}
|
|
|
|
void TabForward()
|
|
{
|
|
OnTab(TextArea);
|
|
}
|
|
|
|
void EnterLineBreak()
|
|
{
|
|
OnEnter(TextArea);
|
|
}
|
|
|
|
void EnterParagraphBreak()
|
|
{
|
|
OnEnter(TextArea);
|
|
}
|
|
|
|
void DeletePreviousWord()
|
|
{
|
|
OnDelete(TextArea, CaretMovementType.WordLeft);
|
|
}
|
|
|
|
void Backspace()
|
|
{
|
|
OnDelete(TextArea, CaretMovementType.Backspace);
|
|
}
|
|
|
|
void Copy()
|
|
{
|
|
OnCopy(TextArea);
|
|
}
|
|
|
|
void Delete()
|
|
{
|
|
OnDelete(TextArea, CaretMovementType.CharRight);
|
|
}
|
|
|
|
void DeleteNextWord()
|
|
{
|
|
OnDelete(TextArea, CaretMovementType.WordRight);
|
|
}
|
|
|
|
#region Text Transformation Helpers
|
|
|
|
private enum DefaultSegmentType
|
|
{
|
|
WholeDocument,
|
|
CurrentLine
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls transformLine on all lines in the selected range.
|
|
/// transformLine needs to handle read-only segments!
|
|
/// </summary>
|
|
private static void TransformSelectedLines(Action<TextArea, DocumentLine> transformLine, TextArea textArea,
|
|
DefaultSegmentType defaultSegmentType)
|
|
{
|
|
if (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
|
|
{
|
|
var 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls transformLine on all writable segment in the selected range.
|
|
/// </summary>
|
|
private static void TransformSelectedSegments(Action<TextArea, ISegment> transformSegment, TextArea textArea,
|
|
DefaultSegmentType defaultSegmentType)
|
|
{
|
|
if (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;
|
|
}
|
|
else
|
|
{
|
|
segments = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
segments = textArea.Selection.Segments;
|
|
}
|
|
if (segments != null)
|
|
{
|
|
foreach (var segment in segments.Reverse())
|
|
{
|
|
foreach (var writableSegment in textArea.GetDeletableSegments(segment).Reverse())
|
|
{
|
|
transformSegment(textArea, writableSegment);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
textArea.Caret.BringCaretToView();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region EnterLineBreak
|
|
|
|
private static void OnEnter(TextArea textArea)
|
|
{
|
|
if (textArea != null && textArea.IsFocused)
|
|
{
|
|
textArea.PerformTextInput("\n");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Tab
|
|
|
|
private static void OnTab(TextArea textArea)
|
|
{
|
|
if (textArea?.Document != null)
|
|
{
|
|
using (textArea.Document.RunUpdate())
|
|
{
|
|
if (textArea.Selection.IsMultiline)
|
|
{
|
|
var segment = textArea.Selection.SurroundingSegment;
|
|
var start = textArea.Document.GetLineByOffset(segment.Offset);
|
|
var 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;
|
|
var current = start;
|
|
while (true)
|
|
{
|
|
var 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
|
|
{
|
|
var indentationString = textArea.Options.GetIndentationString(textArea.Caret.Column);
|
|
textArea.ReplaceSelectionWithText(indentationString);
|
|
}
|
|
}
|
|
textArea.Caret.BringCaretToView();
|
|
}
|
|
}
|
|
|
|
private static void OnShiftTab(TextArea textArea)
|
|
{
|
|
TransformSelectedLines(
|
|
delegate (TextArea textArea, DocumentLine line)
|
|
{
|
|
var offset = line.Offset;
|
|
var 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);
|
|
}
|
|
}
|
|
}, textArea, DefaultSegmentType.CurrentLine);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Delete
|
|
|
|
static void OnDelete(TextArea textArea, CaretMovementType caretMovement)
|
|
{
|
|
if (textArea?.Document != null)
|
|
{
|
|
if (textArea.Selection.IsEmpty)
|
|
{
|
|
var startPos = textArea.Caret.Position;
|
|
var enableVirtualSpace = textArea.Options.EnableVirtualSpace;
|
|
// When pressing delete; don't move the caret further into virtual space - instead delete the newline
|
|
if (caretMovement == CaretMovementType.CharRight)
|
|
enableVirtualSpace = false;
|
|
var desiredXPos = textArea.Caret.DesiredXPos;
|
|
var endPos = CaretNavigationCommandHandler.GetNewCaretPosition(
|
|
textArea.TextView, startPos, caretMovement, enableVirtualSpace, ref desiredXPos);
|
|
// GetNewCaretPosition may return (0,0) as new position,
|
|
// thus we need to validate endPos before using it in the selection.
|
|
if (endPos.Line < 1 || endPos.Column < 1)
|
|
endPos = new TextViewPosition(Math.Max(endPos.Line, 1), Math.Max(endPos.Column, 1));
|
|
// Don't do anything if the number of lines of a rectangular selection would be changed by the deletion.
|
|
if (textArea.Selection is RectangleSelection && startPos.Line != endPos.Line)
|
|
return;
|
|
// Don't select the text to be deleted; just reuse the ReplaceSelectionWithText logic
|
|
// Reuse the existing selection, so that we continue using the same logic
|
|
textArea.Selection.StartSelectionOrSetEndpoint(startPos, endPos)
|
|
.ReplaceSelectionWithText(string.Empty);
|
|
}
|
|
else
|
|
{
|
|
textArea.RemoveSelectedText();
|
|
}
|
|
textArea.Caret.BringCaretToView();
|
|
}
|
|
}
|
|
|
|
private static bool CanDelete(TextArea textArea)
|
|
{
|
|
if (textArea?.Document != null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Clipboard commands
|
|
|
|
private static bool CanCut(TextArea textArea)
|
|
{
|
|
// HasSomethingSelected for copy and cut commands
|
|
if (textArea?.Document != null)
|
|
{
|
|
return (textArea.Options.CutCopyWholeLine || !textArea.Selection.IsEmpty) && !textArea.IsReadOnly;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool CanCopy(TextArea textArea)
|
|
{
|
|
// HasSomethingSelected for copy and cut commands
|
|
if (textArea?.Document != null)
|
|
{
|
|
return textArea.Options.CutCopyWholeLine || !textArea.Selection.IsEmpty;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static void OnCopy(TextArea textArea)
|
|
{
|
|
if (textArea?.Document != null)
|
|
{
|
|
if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine)
|
|
{
|
|
var currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
|
|
CopyWholeLine(textArea, currentLine);
|
|
}
|
|
else
|
|
{
|
|
CopySelectedText(textArea);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void OnCut(TextArea textArea)
|
|
{
|
|
if (textArea?.Document != null)
|
|
{
|
|
if (textArea.Selection.IsEmpty && textArea.Options.CutCopyWholeLine)
|
|
{
|
|
var currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
|
|
if (CopyWholeLine(textArea, currentLine))
|
|
{
|
|
var segmentsToDelete =
|
|
textArea.GetDeletableSegments(
|
|
new SimpleSegment(currentLine.Offset, currentLine.TotalLength));
|
|
for (var i = segmentsToDelete.Length - 1; i >= 0; i--)
|
|
{
|
|
textArea.Document.Remove(segmentsToDelete[i]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CopySelectedText(textArea))
|
|
textArea.RemoveSelectedText();
|
|
}
|
|
textArea.Caret.BringCaretToView();
|
|
}
|
|
}
|
|
|
|
private static bool CopySelectedText(TextArea textArea)
|
|
{
|
|
var text = textArea.Selection.GetText();
|
|
text = TextUtilities.NormalizeNewLines(text, Environment.NewLine);
|
|
|
|
SetClipboardText(text);
|
|
|
|
textArea.OnTextCopied(new TextArea.TextEventArgs(text));
|
|
return true;
|
|
}
|
|
|
|
private static void SetClipboardText(string text)
|
|
{
|
|
try
|
|
{
|
|
GUIUtility.systemCopyBuffer = text;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Apparently this exception sometimes happens randomly.
|
|
// The MS controls just ignore it, so we'll do the same.
|
|
}
|
|
}
|
|
|
|
private static bool CopyWholeLine(TextArea textArea, DocumentLine line)
|
|
{
|
|
ISegment wholeLine = new SimpleSegment(line.Offset, line.TotalLength);
|
|
var text = textArea.Document.GetText(wholeLine);
|
|
// Ignore empty line copy
|
|
if(string.IsNullOrEmpty(text)) return false;
|
|
// Ensure we use the appropriate newline sequence for the OS
|
|
text = TextUtilities.NormalizeNewLines(text, Environment.NewLine);
|
|
|
|
// TODO: formats
|
|
//DataObject data = new DataObject();
|
|
//if (ConfirmDataFormat(textArea, data, DataFormats.UnicodeText))
|
|
// data.SetText(text);
|
|
|
|
//// Also copy text in HTML format to clipboard - good for pasting text into Word
|
|
//// or to the SharpDevelop forums.
|
|
//if (ConfirmDataFormat(textArea, data, DataFormats.Html))
|
|
//{
|
|
// IHighlighter highlighter = textArea.GetService(typeof(IHighlighter)) as IHighlighter;
|
|
// HtmlClipboard.SetHtml(data,
|
|
// HtmlClipboard.CreateHtmlFragment(textArea.Document, highlighter, wholeLine,
|
|
// new HtmlOptions(textArea.Options)));
|
|
//}
|
|
|
|
//if (ConfirmDataFormat(textArea, data, LineSelectedType))
|
|
//{
|
|
// var lineSelected = new MemoryStream(1);
|
|
// lineSelected.WriteByte(1);
|
|
// data.SetData(LineSelectedType, lineSelected, false);
|
|
//}
|
|
|
|
//var copyingEventArgs = new DataObjectCopyingEventArgs(data, false);
|
|
//textArea.RaiseEvent(copyingEventArgs);
|
|
//if (copyingEventArgs.CommandCancelled)
|
|
// return false;
|
|
|
|
SetClipboardText(text);
|
|
|
|
textArea.OnTextCopied(new TextArea.TextEventArgs(text));
|
|
return true;
|
|
}
|
|
|
|
private static bool CanPaste(TextArea textArea)
|
|
{
|
|
if (textArea?.Document != null)
|
|
{
|
|
return textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static void OnPaste(TextArea textArea)
|
|
{
|
|
if (textArea?.Document != null)
|
|
{
|
|
textArea.Document.BeginUpdate();
|
|
|
|
string text = null;
|
|
try
|
|
{
|
|
text = GUIUtility.systemCopyBuffer;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
textArea.Document.EndUpdate();
|
|
return;
|
|
}
|
|
|
|
if (text == null)
|
|
{
|
|
textArea.Document.EndUpdate();
|
|
return;
|
|
}
|
|
|
|
|
|
text = GetTextToPaste(text, textArea);
|
|
|
|
if (!string.IsNullOrEmpty(text))
|
|
{
|
|
textArea.ReplaceSelectionWithText(text);
|
|
}
|
|
|
|
textArea.Caret.BringCaretToView();
|
|
|
|
textArea.Document.EndUpdate();
|
|
}
|
|
}
|
|
|
|
internal static string GetTextToPaste(string text, TextArea textArea)
|
|
{
|
|
try
|
|
{
|
|
// Try retrieving the text as one of:
|
|
// - the FormatToApply
|
|
// - UnicodeText
|
|
// - Text
|
|
// (but don't try the same format twice)
|
|
//if (pastingEventArgs.FormatToApply != null && dataObject.GetDataPresent(pastingEventArgs.FormatToApply))
|
|
// text = (string)dataObject.GetData(pastingEventArgs.FormatToApply);
|
|
//else if (pastingEventArgs.FormatToApply != DataFormats.UnicodeText &&
|
|
// dataObject.GetDataPresent(DataFormats.UnicodeText))
|
|
// text = (string)dataObject.GetData(DataFormats.UnicodeText);
|
|
//else if (pastingEventArgs.FormatToApply != DataFormats.Text &&
|
|
// dataObject.GetDataPresent(DataFormats.Text))
|
|
// text = (string)dataObject.GetData(DataFormats.Text);
|
|
//else
|
|
// return null; // no text data format
|
|
// convert text back to correct newlines for this document
|
|
var newLine = TextUtilities.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line);
|
|
text = TextUtilities.NormalizeNewLines(text, newLine);
|
|
text = textArea.Options.ConvertTabsToSpaces
|
|
? text.Replace("\t", new String(' ', textArea.Options.IndentationSize))
|
|
: text;
|
|
return text;
|
|
}
|
|
catch (OutOfMemoryException)
|
|
{
|
|
// may happen when trying to paste a huge string
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Toggle Overstrike
|
|
|
|
private static void OnToggleOverstrike(TextArea textArea)
|
|
{
|
|
if (textArea != null && textArea.Options.AllowToggleOverstrikeMode)
|
|
{
|
|
//textArea.OverstrikeMode = !textArea.OverstrikeMode;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DeleteLine
|
|
|
|
private static void OnDeleteLine(TextArea textArea)
|
|
{
|
|
if (textArea?.Document != null)
|
|
{
|
|
int firstLineIndex, lastLineIndex;
|
|
if (textArea.Selection.Length == 0)
|
|
{
|
|
// There is no selection, simply delete current line
|
|
firstLineIndex = lastLineIndex = textArea.Caret.Line;
|
|
}
|
|
else
|
|
{
|
|
// There is a selection, remove all lines affected by it (use Min/Max to be independent from selection direction)
|
|
firstLineIndex = Math.Min(textArea.Selection.StartPosition.Line,
|
|
textArea.Selection.EndPosition.Line);
|
|
lastLineIndex = Math.Max(textArea.Selection.StartPosition.Line,
|
|
textArea.Selection.EndPosition.Line);
|
|
}
|
|
var startLine = textArea.Document.GetLineByNumber(firstLineIndex);
|
|
var endLine = textArea.Document.GetLineByNumber(lastLineIndex);
|
|
textArea.Selection = Selection.Create(textArea, startLine.Offset,
|
|
endLine.Offset + endLine.TotalLength);
|
|
textArea.RemoveSelectedText();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Remove..Whitespace / Convert Tabs-Spaces
|
|
|
|
private static void OnRemoveLeadingWhitespace(TextArea textArea)
|
|
{
|
|
TransformSelectedLines(
|
|
delegate (TextArea textArea, DocumentLine line)
|
|
{
|
|
textArea.Document.Remove(TextUtilities.GetLeadingWhitespace(textArea.Document, line));
|
|
}, textArea, DefaultSegmentType.WholeDocument);
|
|
}
|
|
|
|
private static void OnRemoveTrailingWhitespace(TextArea textArea)
|
|
{
|
|
TransformSelectedLines(
|
|
delegate (TextArea textArea, DocumentLine line)
|
|
{
|
|
textArea.Document.Remove(TextUtilities.GetTrailingWhitespace(textArea.Document, line));
|
|
}, textArea, DefaultSegmentType.WholeDocument);
|
|
}
|
|
|
|
private static void OnConvertTabsToSpaces(TextArea textArea)
|
|
{
|
|
TransformSelectedSegments(ConvertTabsToSpaces, textArea, DefaultSegmentType.WholeDocument);
|
|
}
|
|
|
|
private static void OnConvertLeadingTabsToSpaces(TextArea textArea)
|
|
{
|
|
TransformSelectedLines(
|
|
delegate (TextArea textArea, DocumentLine line)
|
|
{
|
|
ConvertTabsToSpaces(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line));
|
|
}, textArea, DefaultSegmentType.WholeDocument);
|
|
}
|
|
|
|
private static void ConvertTabsToSpaces(TextArea textArea, ISegment segment)
|
|
{
|
|
var document = textArea.Document;
|
|
var endOffset = segment.EndOffset;
|
|
var indentationString = new string(' ', textArea.Options.IndentationSize);
|
|
for (var offset = segment.Offset; offset < endOffset; offset++)
|
|
{
|
|
if (document.GetCharAt(offset) == '\t')
|
|
{
|
|
document.Replace(offset, 1, indentationString, OffsetChangeMappingType.CharacterReplace);
|
|
endOffset += indentationString.Length - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void OnConvertSpacesToTabs(TextArea textArea)
|
|
{
|
|
TransformSelectedSegments(ConvertSpacesToTabs, textArea, DefaultSegmentType.WholeDocument);
|
|
}
|
|
|
|
private static void OnConvertLeadingSpacesToTabs(TextArea textArea)
|
|
{
|
|
TransformSelectedLines(
|
|
delegate (TextArea textArea, DocumentLine line)
|
|
{
|
|
ConvertSpacesToTabs(textArea, TextUtilities.GetLeadingWhitespace(textArea.Document, line));
|
|
}, textArea, DefaultSegmentType.WholeDocument);
|
|
}
|
|
|
|
private static void ConvertSpacesToTabs(TextArea textArea, ISegment segment)
|
|
{
|
|
var document = textArea.Document;
|
|
var endOffset = segment.EndOffset;
|
|
var indentationSize = textArea.Options.IndentationSize;
|
|
var spacesCount = 0;
|
|
for (var 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
|
|
|
|
private static void ConvertCase(Func<string, string> transformText, TextArea textArea)
|
|
{
|
|
TransformSelectedSegments(
|
|
delegate (TextArea textArea, ISegment segment)
|
|
{
|
|
var oldText = textArea.Document.GetText(segment);
|
|
var newText = transformText(oldText);
|
|
textArea.Document.Replace(segment.Offset, segment.Length, newText,
|
|
OffsetChangeMappingType.CharacterReplace);
|
|
}, textArea, DefaultSegmentType.WholeDocument);
|
|
}
|
|
|
|
private static void OnConvertToUpperCase(TextArea textArea)
|
|
{
|
|
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToUpper, textArea);
|
|
}
|
|
|
|
private static void OnConvertToLowerCase(object target, TextArea textArea)
|
|
{
|
|
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToLower, textArea);
|
|
}
|
|
|
|
private static void OnConvertToTitleCase(object target, TextArea textArea)
|
|
{
|
|
throw new NotSupportedException();
|
|
//ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToTitleCase, target, args);
|
|
}
|
|
|
|
private static void OnInvertCase(object target, TextArea textArea)
|
|
{
|
|
ConvertCase(InvertCase, textArea);
|
|
}
|
|
|
|
private static string InvertCase(string text)
|
|
{
|
|
// TODO: culture
|
|
//var culture = CultureInfo.CurrentCulture;
|
|
var buffer = text.ToCharArray();
|
|
for (var i = 0; i < buffer.Length; ++i)
|
|
{
|
|
var c = buffer[i];
|
|
buffer[i] = char.IsUpper(c) ? char.ToLower(c) : char.ToUpper(c);
|
|
}
|
|
return new string(buffer);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Undo / Redo
|
|
|
|
private UndoStack GetUndoStack()
|
|
{
|
|
var document = TextArea.Document;
|
|
return document?.UndoStack;
|
|
}
|
|
|
|
private void ExecuteUndo()
|
|
{
|
|
var undoStack = GetUndoStack();
|
|
if (undoStack != null)
|
|
{
|
|
if (undoStack.CanUndo)
|
|
{
|
|
undoStack.Undo();
|
|
TextArea.Caret.BringCaretToView();
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool CanExecuteUndo()
|
|
{
|
|
var undoStack = GetUndoStack();
|
|
if (undoStack != null)
|
|
{
|
|
return undoStack.CanUndo;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void ExecuteRedo()
|
|
{
|
|
var undoStack = GetUndoStack();
|
|
if (undoStack != null)
|
|
{
|
|
if (undoStack.CanRedo)
|
|
{
|
|
undoStack.Redo();
|
|
TextArea.Caret.BringCaretToView();
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool CanExecuteRedo()
|
|
{
|
|
var undoStack = GetUndoStack();
|
|
if (undoStack != null)
|
|
{
|
|
return undoStack.CanRedo;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endregion
|
|
private static void OnIndentSelection(TextArea textArea)
|
|
{
|
|
if (textArea?.Document != null && textArea.IndentationStrategy != 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();
|
|
}
|
|
}
|
|
}
|
|
} |