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.
915 lines
37 KiB
915 lines
37 KiB
// <file>
|
|
// <copyright see="prj:///doc/copyright.txt"/>
|
|
// <license see="prj:///doc/license.txt"/>
|
|
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
|
// <version>$Revision$</version>
|
|
// </file>
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
using ICSharpCode.TextEditor.Document;
|
|
|
|
namespace ICSharpCode.TextEditor.Actions
|
|
{
|
|
public class Tab : AbstractEditAction
|
|
{
|
|
public static string GetIndentationString(IDocument document)
|
|
{
|
|
return GetIndentationString(document, textArea: null);
|
|
}
|
|
|
|
public static string GetIndentationString(IDocument document, TextArea textArea)
|
|
{
|
|
var indent = new StringBuilder();
|
|
|
|
if (document.TextEditorProperties.ConvertTabsToSpaces)
|
|
{
|
|
var tabIndent = document.TextEditorProperties.IndentationSize;
|
|
if (textArea != null)
|
|
{
|
|
var column = textArea.TextView.GetVisualColumn(textArea.Caret.Line, textArea.Caret.Column);
|
|
indent.Append(new string(c: ' ', tabIndent - column%tabIndent));
|
|
}
|
|
else
|
|
{
|
|
indent.Append(new string(c: ' ', tabIndent));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
indent.Append(value: '\t');
|
|
}
|
|
|
|
return indent.ToString();
|
|
}
|
|
|
|
private void InsertTabs(IDocument document, ISelection selection, int y1, int y2)
|
|
{
|
|
var indentationString = GetIndentationString(document);
|
|
for (var i = y2; i >= y1; --i)
|
|
{
|
|
var line = document.GetLineSegment(i);
|
|
if (i == y2 && i == selection.EndPosition.Y && selection.EndPosition.X == 0)
|
|
continue;
|
|
|
|
// this bit is optional - but useful if you are using block tabbing to sort out
|
|
// a source file with a mixture of tabs and spaces
|
|
// string newLine = document.GetText(line.Offset,line.Length);
|
|
// document.Replace(line.Offset,line.Length,newLine);
|
|
|
|
document.Insert(line.Offset, indentationString);
|
|
}
|
|
}
|
|
|
|
private void InsertTabAtCaretPosition(TextArea textArea)
|
|
{
|
|
switch (textArea.Caret.CaretMode)
|
|
{
|
|
case CaretMode.InsertMode:
|
|
textArea.InsertString(GetIndentationString(textArea.Document, textArea));
|
|
break;
|
|
case CaretMode.OverwriteMode:
|
|
var indentStr = GetIndentationString(textArea.Document, textArea);
|
|
textArea.ReplaceChar(indentStr[index: 0]);
|
|
if (indentStr.Length > 1)
|
|
textArea.InsertString(indentStr.Substring(startIndex: 1));
|
|
break;
|
|
}
|
|
|
|
textArea.SetDesiredColumn();
|
|
}
|
|
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
if (textArea.SelectionManager.SelectionIsReadonly)
|
|
return;
|
|
textArea.Document.UndoStack.StartUndoGroup();
|
|
if (textArea.SelectionManager.HasSomethingSelected)
|
|
{
|
|
foreach (var selection in textArea.SelectionManager.SelectionCollection)
|
|
{
|
|
var startLine = selection.StartPosition.Y;
|
|
var endLine = selection.EndPosition.Y;
|
|
if (startLine != endLine)
|
|
{
|
|
textArea.BeginUpdate();
|
|
InsertTabs(textArea.Document, selection, startLine, endLine);
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, startLine, endLine));
|
|
textArea.EndUpdate();
|
|
}
|
|
else
|
|
{
|
|
InsertTabAtCaretPosition(textArea);
|
|
break;
|
|
}
|
|
}
|
|
|
|
textArea.Document.CommitUpdate();
|
|
textArea.AutoClearSelection = false;
|
|
}
|
|
else
|
|
{
|
|
InsertTabAtCaretPosition(textArea);
|
|
}
|
|
|
|
textArea.Document.UndoStack.EndUndoGroup();
|
|
}
|
|
}
|
|
|
|
public class ShiftTab : AbstractEditAction
|
|
{
|
|
private void RemoveTabs(IDocument document, ISelection selection, int y1, int y2)
|
|
{
|
|
document.UndoStack.StartUndoGroup();
|
|
for (var i = y2; i >= y1; --i)
|
|
{
|
|
var line = document.GetLineSegment(i);
|
|
if (i == y2 && line.Offset == selection.EndOffset)
|
|
continue;
|
|
if (line.Length > 0)
|
|
if (line.Length > 0)
|
|
{
|
|
var charactersToRemove = 0;
|
|
if (document.GetCharAt(line.Offset) == '\t')
|
|
{
|
|
// first character is a tab - just remove it
|
|
charactersToRemove = 1;
|
|
}
|
|
else if (document.GetCharAt(line.Offset) == ' ')
|
|
{
|
|
int leadingSpaces;
|
|
var tabIndent = document.TextEditorProperties.IndentationSize;
|
|
for (leadingSpaces = 1; leadingSpaces < line.Length && document.GetCharAt(line.Offset + leadingSpaces) == ' '; leadingSpaces++)
|
|
{
|
|
// deliberately empty
|
|
}
|
|
|
|
if (leadingSpaces >= tabIndent)
|
|
charactersToRemove = tabIndent;
|
|
else if (line.Length > leadingSpaces && document.GetCharAt(line.Offset + leadingSpaces) == '\t')
|
|
charactersToRemove = leadingSpaces + 1;
|
|
else
|
|
charactersToRemove = leadingSpaces;
|
|
}
|
|
|
|
if (charactersToRemove > 0)
|
|
document.Remove(line.Offset, charactersToRemove);
|
|
}
|
|
}
|
|
|
|
document.UndoStack.EndUndoGroup();
|
|
}
|
|
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
if (textArea.SelectionManager.HasSomethingSelected)
|
|
{
|
|
foreach (var selection in textArea.SelectionManager.SelectionCollection)
|
|
{
|
|
var startLine = selection.StartPosition.Y;
|
|
var endLine = selection.EndPosition.Y;
|
|
textArea.BeginUpdate();
|
|
RemoveTabs(textArea.Document, selection, startLine, endLine);
|
|
textArea.Document.UpdateQueue.Clear();
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, startLine, endLine));
|
|
textArea.EndUpdate();
|
|
}
|
|
|
|
textArea.AutoClearSelection = false;
|
|
}
|
|
else
|
|
{
|
|
// Pressing Shift-Tab with nothing selected the cursor will move back to the
|
|
// previous tab stop. It will stop at the beginning of the line. Also, the desired
|
|
// column is updated to that column.
|
|
var line = textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset);
|
|
//var startOfLine = textArea.Document.GetText(line.Offset, textArea.Caret.Offset - line.Offset);
|
|
var tabIndent = textArea.Document.TextEditorProperties.IndentationSize;
|
|
var currentColumn = textArea.Caret.Column;
|
|
var remainder = currentColumn%tabIndent;
|
|
if (remainder == 0)
|
|
textArea.Caret.DesiredColumn = Math.Max(val1: 0, currentColumn - tabIndent);
|
|
else
|
|
textArea.Caret.DesiredColumn = Math.Max(val1: 0, currentColumn - remainder);
|
|
textArea.SetCaretToDesiredColumn();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class ToggleComment : AbstractEditAction
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
if (textArea.Document.ReadOnly)
|
|
return;
|
|
|
|
if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("LineComment"))
|
|
new ToggleLineComment().Execute(textArea);
|
|
else if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentBegin") &&
|
|
textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentBegin"))
|
|
new ToggleBlockComment().Execute(textArea);
|
|
}
|
|
}
|
|
|
|
public class ToggleLineComment : AbstractEditAction
|
|
{
|
|
private int firstLine;
|
|
private int lastLine;
|
|
|
|
private void RemoveCommentAt(IDocument document, string comment, ISelection selection, int y1, int y2)
|
|
{
|
|
firstLine = y1;
|
|
lastLine = y2;
|
|
|
|
for (var i = y2; i >= y1; --i)
|
|
{
|
|
var line = document.GetLineSegment(i);
|
|
if (selection != null && i == y2 && line.Offset == selection.Offset + selection.Length)
|
|
{
|
|
--lastLine;
|
|
continue;
|
|
}
|
|
|
|
var lineText = document.GetText(line.Offset, line.Length);
|
|
if (lineText.Trim().StartsWith(comment))
|
|
document.Remove(line.Offset + lineText.IndexOf(comment), comment.Length);
|
|
}
|
|
}
|
|
|
|
private void SetCommentAt(IDocument document, string comment, ISelection selection, int y1, int y2)
|
|
{
|
|
firstLine = y1;
|
|
lastLine = y2;
|
|
|
|
for (var i = y2; i >= y1; --i)
|
|
{
|
|
var line = document.GetLineSegment(i);
|
|
if (selection != null && i == y2 && line.Offset == selection.Offset + selection.Length)
|
|
{
|
|
--lastLine;
|
|
continue;
|
|
}
|
|
|
|
// var lineText = document.GetText(line.Offset, line.Length);
|
|
document.Insert(line.Offset, comment);
|
|
}
|
|
}
|
|
|
|
private bool ShouldComment(IDocument document, string comment, ISelection selection, int startLine, int endLine)
|
|
{
|
|
for (var i = endLine; i >= startLine; --i)
|
|
{
|
|
var line = document.GetLineSegment(i);
|
|
if (selection != null && i == endLine && line.Offset == selection.Offset + selection.Length)
|
|
{
|
|
--lastLine;
|
|
continue;
|
|
}
|
|
|
|
var lineText = document.GetText(line.Offset, line.Length);
|
|
if (!lineText.Trim().StartsWith(comment))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
if (textArea.Document.ReadOnly)
|
|
return;
|
|
|
|
string comment = null;
|
|
if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("LineComment"))
|
|
comment = textArea.Document.HighlightingStrategy.Properties["LineComment"];
|
|
|
|
if (comment == null || comment.Length == 0)
|
|
return;
|
|
|
|
textArea.Document.UndoStack.StartUndoGroup();
|
|
if (textArea.SelectionManager.HasSomethingSelected)
|
|
{
|
|
var shouldComment = true;
|
|
foreach (var selection in textArea.SelectionManager.SelectionCollection)
|
|
if (!ShouldComment(textArea.Document, comment, selection, selection.StartPosition.Y, selection.EndPosition.Y))
|
|
{
|
|
shouldComment = false;
|
|
break;
|
|
}
|
|
|
|
foreach (var selection in textArea.SelectionManager.SelectionCollection)
|
|
{
|
|
textArea.BeginUpdate();
|
|
if (shouldComment)
|
|
SetCommentAt(textArea.Document, comment, selection, selection.StartPosition.Y, selection.EndPosition.Y);
|
|
else
|
|
RemoveCommentAt(textArea.Document, comment, selection, selection.StartPosition.Y, selection.EndPosition.Y);
|
|
textArea.Document.UpdateQueue.Clear();
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, firstLine, lastLine));
|
|
textArea.EndUpdate();
|
|
}
|
|
|
|
textArea.Document.CommitUpdate();
|
|
textArea.AutoClearSelection = false;
|
|
}
|
|
else
|
|
{
|
|
textArea.BeginUpdate();
|
|
var caretLine = textArea.Caret.Line;
|
|
if (ShouldComment(textArea.Document, comment, selection: null, caretLine, caretLine))
|
|
SetCommentAt(textArea.Document, comment, selection: null, caretLine, caretLine);
|
|
else
|
|
RemoveCommentAt(textArea.Document, comment, selection: null, caretLine, caretLine);
|
|
textArea.Document.UpdateQueue.Clear();
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, caretLine));
|
|
textArea.EndUpdate();
|
|
}
|
|
|
|
textArea.Document.UndoStack.EndUndoGroup();
|
|
}
|
|
}
|
|
|
|
public class ToggleBlockComment : AbstractEditAction
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
if (textArea.Document.ReadOnly)
|
|
return;
|
|
|
|
string commentStart = null;
|
|
if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentBegin"))
|
|
commentStart = textArea.Document.HighlightingStrategy.Properties["BlockCommentBegin"];
|
|
|
|
string commentEnd = null;
|
|
if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentEnd"))
|
|
commentEnd = textArea.Document.HighlightingStrategy.Properties["BlockCommentEnd"];
|
|
|
|
if (commentStart == null || commentStart.Length == 0 || commentEnd == null || commentEnd.Length == 0)
|
|
return;
|
|
|
|
int selectionStartOffset;
|
|
int selectionEndOffset;
|
|
|
|
if (textArea.SelectionManager.HasSomethingSelected)
|
|
{
|
|
selectionStartOffset = textArea.SelectionManager.SelectionCollection[index: 0].Offset;
|
|
selectionEndOffset = textArea.SelectionManager.SelectionCollection[textArea.SelectionManager.SelectionCollection.Count - 1].EndOffset;
|
|
}
|
|
else
|
|
{
|
|
selectionStartOffset = textArea.Caret.Offset;
|
|
selectionEndOffset = selectionStartOffset;
|
|
}
|
|
|
|
var commentRegion = FindSelectedCommentRegion(textArea.Document, commentStart, commentEnd, selectionStartOffset, selectionEndOffset);
|
|
|
|
textArea.Document.UndoStack.StartUndoGroup();
|
|
if (commentRegion != null)
|
|
RemoveComment(textArea.Document, commentRegion);
|
|
else if (textArea.SelectionManager.HasSomethingSelected)
|
|
SetCommentAt(textArea.Document, selectionStartOffset, selectionEndOffset, commentStart, commentEnd);
|
|
textArea.Document.UndoStack.EndUndoGroup();
|
|
|
|
textArea.Document.CommitUpdate();
|
|
textArea.AutoClearSelection = false;
|
|
}
|
|
|
|
public static BlockCommentRegion FindSelectedCommentRegion(IDocument document, string commentStart, string commentEnd, int selectionStartOffset, int selectionEndOffset)
|
|
{
|
|
if (document.TextLength == 0)
|
|
return null;
|
|
|
|
// Find start of comment in selected text.
|
|
|
|
int commentEndOffset;
|
|
var selectedText = document.GetText(selectionStartOffset, selectionEndOffset - selectionStartOffset);
|
|
|
|
var commentStartOffset = selectedText.IndexOf(commentStart);
|
|
if (commentStartOffset >= 0)
|
|
commentStartOffset += selectionStartOffset;
|
|
|
|
// Find end of comment in selected text.
|
|
|
|
if (commentStartOffset >= 0)
|
|
commentEndOffset = selectedText.IndexOf(commentEnd, commentStartOffset + commentStart.Length - selectionStartOffset);
|
|
else
|
|
commentEndOffset = selectedText.IndexOf(commentEnd);
|
|
|
|
if (commentEndOffset >= 0)
|
|
commentEndOffset += selectionStartOffset;
|
|
|
|
// Find start of comment before or partially inside the
|
|
// selected text.
|
|
|
|
if (commentStartOffset == -1)
|
|
{
|
|
var offset = selectionEndOffset + commentStart.Length - 1;
|
|
if (offset > document.TextLength)
|
|
offset = document.TextLength;
|
|
var text = document.GetText(offset: 0, offset);
|
|
commentStartOffset = text.LastIndexOf(commentStart);
|
|
if (commentStartOffset >= 0)
|
|
{
|
|
// Find end of comment before comment start.
|
|
var commentEndBeforeStartOffset = text.IndexOf(commentEnd, commentStartOffset, selectionStartOffset - commentStartOffset);
|
|
if (commentEndBeforeStartOffset > commentStartOffset)
|
|
commentStartOffset = -1;
|
|
}
|
|
}
|
|
|
|
// Find end of comment after or partially after the
|
|
// selected text.
|
|
|
|
if (commentEndOffset == -1)
|
|
{
|
|
var offset = selectionStartOffset + 1 - commentEnd.Length;
|
|
if (offset < 0)
|
|
offset = selectionStartOffset;
|
|
var text = document.GetText(offset, document.TextLength - offset);
|
|
commentEndOffset = text.IndexOf(commentEnd);
|
|
if (commentEndOffset >= 0)
|
|
commentEndOffset += offset;
|
|
}
|
|
|
|
if (commentStartOffset != -1 && commentEndOffset != -1)
|
|
return new BlockCommentRegion(commentStart, commentEnd, commentStartOffset, commentEndOffset);
|
|
|
|
return null;
|
|
}
|
|
|
|
private void SetCommentAt(IDocument document, int offsetStart, int offsetEnd, string commentStart, string commentEnd)
|
|
{
|
|
document.Insert(offsetEnd, commentEnd);
|
|
document.Insert(offsetStart, commentStart);
|
|
}
|
|
|
|
private void RemoveComment(IDocument document, BlockCommentRegion commentRegion)
|
|
{
|
|
document.Remove(commentRegion.EndOffset, commentRegion.CommentEnd.Length);
|
|
document.Remove(commentRegion.StartOffset, commentRegion.CommentStart.Length);
|
|
}
|
|
}
|
|
|
|
public class BlockCommentRegion
|
|
{
|
|
/// <summary>
|
|
/// The end offset is the offset where the comment end string starts from.
|
|
/// </summary>
|
|
public BlockCommentRegion(string commentStart, string commentEnd, int startOffset, int endOffset)
|
|
{
|
|
CommentStart = commentStart;
|
|
CommentEnd = commentEnd;
|
|
StartOffset = startOffset;
|
|
EndOffset = endOffset;
|
|
}
|
|
|
|
public string CommentStart { get; } = string.Empty;
|
|
|
|
public string CommentEnd { get; } = string.Empty;
|
|
|
|
public int StartOffset { get; } = -1;
|
|
|
|
public int EndOffset { get; } = -1;
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
var hashCode = 0;
|
|
unchecked
|
|
{
|
|
if (CommentStart != null) hashCode += 1000000007*CommentStart.GetHashCode();
|
|
if (CommentEnd != null) hashCode += 1000000009*CommentEnd.GetHashCode();
|
|
hashCode += 1000000021*StartOffset.GetHashCode();
|
|
hashCode += 1000000033*EndOffset.GetHashCode();
|
|
}
|
|
|
|
return hashCode;
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
var other = obj as BlockCommentRegion;
|
|
if (other == null) return false;
|
|
return CommentStart == other.CommentStart && CommentEnd == other.CommentEnd && StartOffset == other.StartOffset && EndOffset == other.EndOffset;
|
|
}
|
|
}
|
|
|
|
public class Backspace : AbstractEditAction
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
if (textArea.SelectionManager.HasSomethingSelected)
|
|
{
|
|
Delete.DeleteSelection(textArea);
|
|
}
|
|
else
|
|
{
|
|
if (textArea.Caret.Offset > 0 && !textArea.IsReadOnly(textArea.Caret.Offset - 1))
|
|
{
|
|
textArea.BeginUpdate();
|
|
var curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
|
|
var curLineOffset = textArea.Document.GetLineSegment(curLineNr).Offset;
|
|
|
|
if (curLineOffset == textArea.Caret.Offset)
|
|
{
|
|
var line = textArea.Document.GetLineSegment(curLineNr - 1);
|
|
// var lastLine = curLineNr == textArea.Document.TotalNumberOfLines;
|
|
var lineEndOffset = line.Offset + line.Length;
|
|
var lineLength = line.Length;
|
|
textArea.Document.Remove(lineEndOffset, curLineOffset - lineEndOffset);
|
|
textArea.Caret.Position = new TextLocation(lineLength, curLineNr - 1);
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(column: 0, curLineNr - 1)));
|
|
}
|
|
else
|
|
{
|
|
var caretOffset = textArea.Caret.Offset - 1;
|
|
textArea.Caret.Position = textArea.Document.OffsetToPosition(caretOffset);
|
|
textArea.Document.Remove(caretOffset, length: 1);
|
|
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToLineEnd, new TextLocation(textArea.Caret.Offset - textArea.Document.GetLineSegment(curLineNr).Offset, curLineNr)));
|
|
}
|
|
|
|
textArea.EndUpdate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class Delete : AbstractEditAction
|
|
{
|
|
internal static void DeleteSelection(TextArea textArea)
|
|
{
|
|
Debug.Assert(textArea.SelectionManager.HasSomethingSelected);
|
|
if (textArea.SelectionManager.SelectionIsReadonly)
|
|
return;
|
|
textArea.BeginUpdate();
|
|
textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[index: 0].StartPosition;
|
|
textArea.SelectionManager.RemoveSelectedText();
|
|
textArea.ScrollToCaret();
|
|
textArea.EndUpdate();
|
|
}
|
|
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
if (textArea.SelectionManager.HasSomethingSelected)
|
|
{
|
|
DeleteSelection(textArea);
|
|
}
|
|
else
|
|
{
|
|
if (textArea.IsReadOnly(textArea.Caret.Offset))
|
|
return;
|
|
|
|
if (textArea.Caret.Offset < textArea.Document.TextLength)
|
|
{
|
|
textArea.BeginUpdate();
|
|
var curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
|
|
var curLine = textArea.Document.GetLineSegment(curLineNr);
|
|
|
|
if (curLine.Offset + curLine.Length == textArea.Caret.Offset)
|
|
{
|
|
if (curLineNr + 1 < textArea.Document.TotalNumberOfLines)
|
|
{
|
|
var nextLine = textArea.Document.GetLineSegment(curLineNr + 1);
|
|
|
|
textArea.Document.Remove(textArea.Caret.Offset, nextLine.Offset - textArea.Caret.Offset);
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(column: 0, curLineNr)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
textArea.Document.Remove(textArea.Caret.Offset, length: 1);
|
|
// textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToLineEnd, new TextLocation(textArea.Caret.Offset - textArea.Document.GetLineSegment(curLineNr).Offset, curLineNr)));
|
|
}
|
|
|
|
textArea.UpdateMatchingBracket();
|
|
textArea.EndUpdate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class MovePageDown : AbstractEditAction
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
var curLineNr = textArea.Caret.Line;
|
|
var requestedLineNumber = Math.Min(textArea.Document.GetNextVisibleLineAbove(curLineNr, textArea.TextView.VisibleLineCount), textArea.Document.TotalNumberOfLines - 1);
|
|
|
|
if (curLineNr != requestedLineNumber)
|
|
{
|
|
textArea.Caret.Position = new TextLocation(column: 0, requestedLineNumber);
|
|
textArea.SetCaretToDesiredColumn();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class MovePageUp : AbstractEditAction
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
var curLineNr = textArea.Caret.Line;
|
|
var requestedLineNumber = Math.Max(textArea.Document.GetNextVisibleLineBelow(curLineNr, textArea.TextView.VisibleLineCount), val2: 0);
|
|
|
|
if (curLineNr != requestedLineNumber)
|
|
{
|
|
textArea.Caret.Position = new TextLocation(column: 0, requestedLineNumber);
|
|
textArea.SetCaretToDesiredColumn();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class Return : AbstractEditAction
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="TextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
if (textArea.Document.ReadOnly)
|
|
return;
|
|
textArea.BeginUpdate();
|
|
textArea.Document.UndoStack.StartUndoGroup();
|
|
try
|
|
{
|
|
if (textArea.HandleKeyPress(ch: '\n'))
|
|
return;
|
|
|
|
textArea.InsertString(Environment.NewLine);
|
|
|
|
var curLineNr = textArea.Caret.Line;
|
|
textArea.Document.FormattingStrategy.FormatLine(textArea, curLineNr, textArea.Caret.Offset, charTyped: '\n');
|
|
textArea.SetDesiredColumn();
|
|
|
|
textArea.Document.UpdateQueue.Clear();
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(column: 0, curLineNr - 1)));
|
|
}
|
|
finally
|
|
{
|
|
textArea.Document.UndoStack.EndUndoGroup();
|
|
textArea.EndUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class ToggleEditMode : AbstractEditAction
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
if (textArea.Document.ReadOnly)
|
|
return;
|
|
switch (textArea.Caret.CaretMode)
|
|
{
|
|
case CaretMode.InsertMode:
|
|
textArea.Caret.CaretMode = CaretMode.OverwriteMode;
|
|
break;
|
|
case CaretMode.OverwriteMode:
|
|
textArea.Caret.CaretMode = CaretMode.InsertMode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class Undo : AbstractEditAction
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
textArea.MotherTextEditorControl.Undo();
|
|
}
|
|
}
|
|
|
|
public class Redo : AbstractEditAction
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
textArea.MotherTextEditorControl.Redo();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// handles the ctrl-backspace key
|
|
/// functionality attempts to roughly mimic MS Developer studio
|
|
/// I will implement this as deleting back to the point that ctrl-leftarrow would
|
|
/// take you to
|
|
/// </summary>
|
|
public class WordBackspace : AbstractEditAction
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
// if anything is selected we will just delete it first
|
|
if (textArea.SelectionManager.HasSomethingSelected)
|
|
{
|
|
Delete.DeleteSelection(textArea);
|
|
return;
|
|
}
|
|
|
|
textArea.BeginUpdate();
|
|
// now delete from the caret to the beginning of the word
|
|
var line =
|
|
textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset);
|
|
// if we are not at the beginning of a line
|
|
if (textArea.Caret.Offset > line.Offset)
|
|
{
|
|
var prevWordStart = TextUtilities.FindPrevWordStart(
|
|
textArea.Document,
|
|
textArea.Caret.Offset);
|
|
if (prevWordStart < textArea.Caret.Offset)
|
|
if (!textArea.IsReadOnly(prevWordStart, textArea.Caret.Offset - prevWordStart))
|
|
{
|
|
textArea.Document.Remove(
|
|
prevWordStart,
|
|
textArea.Caret.Offset - prevWordStart);
|
|
textArea.Caret.Position = textArea.Document.OffsetToPosition(prevWordStart);
|
|
}
|
|
}
|
|
|
|
// if we are now at the beginning of a line
|
|
if (textArea.Caret.Offset == line.Offset)
|
|
{
|
|
// if we are not on the first line
|
|
var curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset);
|
|
if (curLineNr > 0)
|
|
{
|
|
// move to the end of the line above
|
|
var lineAbove = textArea.Document.GetLineSegment(curLineNr - 1);
|
|
var endOfLineAbove = lineAbove.Offset + lineAbove.Length;
|
|
var charsToDelete = textArea.Caret.Offset - endOfLineAbove;
|
|
if (!textArea.IsReadOnly(endOfLineAbove, charsToDelete))
|
|
{
|
|
textArea.Document.Remove(endOfLineAbove, charsToDelete);
|
|
textArea.Caret.Position = textArea.Document.OffsetToPosition(endOfLineAbove);
|
|
}
|
|
}
|
|
}
|
|
|
|
textArea.SetDesiredColumn();
|
|
textArea.EndUpdate();
|
|
// if there are now less lines, we need this or there are redraw problems
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(column: 0, textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset))));
|
|
textArea.Document.CommitUpdate();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// handles the ctrl-delete key
|
|
/// functionality attempts to mimic MS Developer studio
|
|
/// I will implement this as deleting forwardto the point that
|
|
/// ctrl-leftarrow would take you to
|
|
/// </summary>
|
|
public class DeleteWord : Delete
|
|
{
|
|
/// <remarks>
|
|
/// Executes this edit action
|
|
/// </remarks>
|
|
/// <param name="textArea">The <see cref="ItextArea" /> which is used for callback purposes</param>
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
if (textArea.SelectionManager.HasSomethingSelected)
|
|
{
|
|
DeleteSelection(textArea);
|
|
return;
|
|
}
|
|
|
|
// if anything is selected we will just delete it first
|
|
textArea.BeginUpdate();
|
|
// now delete from the caret to the beginning of the word
|
|
var line = textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset);
|
|
if (textArea.Caret.Offset == line.Offset + line.Length)
|
|
{
|
|
// if we are at the end of a line
|
|
base.Execute(textArea);
|
|
}
|
|
else
|
|
{
|
|
var nextWordStart = TextUtilities.FindNextWordStart(
|
|
textArea.Document,
|
|
textArea.Caret.Offset);
|
|
if (nextWordStart > textArea.Caret.Offset)
|
|
if (!textArea.IsReadOnly(textArea.Caret.Offset, nextWordStart - textArea.Caret.Offset))
|
|
textArea.Document.Remove(textArea.Caret.Offset, nextWordStart - textArea.Caret.Offset);
|
|
}
|
|
|
|
textArea.UpdateMatchingBracket();
|
|
textArea.EndUpdate();
|
|
// if there are now less lines, we need this or there are redraw problems
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(column: 0, textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset))));
|
|
textArea.Document.CommitUpdate();
|
|
}
|
|
}
|
|
|
|
public class DeleteLine : AbstractEditAction
|
|
{
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
var lineNr = textArea.Caret.Line;
|
|
var line = textArea.Document.GetLineSegment(lineNr);
|
|
if (textArea.IsReadOnly(line.Offset, line.Length))
|
|
return;
|
|
textArea.Document.Remove(line.Offset, line.TotalLength);
|
|
textArea.Caret.Position = textArea.Document.OffsetToPosition(line.Offset);
|
|
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(column: 0, lineNr)));
|
|
textArea.UpdateMatchingBracket();
|
|
textArea.Document.CommitUpdate();
|
|
}
|
|
}
|
|
|
|
public class DeleteToLineEnd : AbstractEditAction
|
|
{
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
var lineNr = textArea.Caret.Line;
|
|
var line = textArea.Document.GetLineSegment(lineNr);
|
|
|
|
var numRemove = line.Offset + line.Length - textArea.Caret.Offset;
|
|
if (numRemove > 0 && !textArea.IsReadOnly(textArea.Caret.Offset, numRemove))
|
|
{
|
|
textArea.Document.Remove(textArea.Caret.Offset, numRemove);
|
|
textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, new TextLocation(column: 0, lineNr)));
|
|
textArea.Document.CommitUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class GotoMatchingBrace : AbstractEditAction
|
|
{
|
|
public override void Execute(TextArea textArea)
|
|
{
|
|
var highlight = textArea.FindMatchingBracketHighlight();
|
|
if (highlight != null)
|
|
{
|
|
var p1 = new TextLocation(highlight.CloseBrace.X + 1, highlight.CloseBrace.Y);
|
|
var p2 = new TextLocation(highlight.OpenBrace.X + 1, highlight.OpenBrace.Y);
|
|
if (p1 == textArea.Caret.Position)
|
|
{
|
|
if (textArea.Document.TextEditorProperties.BracketMatchingStyle == BracketMatchingStyle.After)
|
|
textArea.Caret.Position = p2;
|
|
else
|
|
textArea.Caret.Position = new TextLocation(p2.X - 1, p2.Y);
|
|
}
|
|
else
|
|
{
|
|
if (textArea.Document.TextEditorProperties.BracketMatchingStyle == BracketMatchingStyle.After)
|
|
textArea.Caret.Position = p1;
|
|
else
|
|
textArea.Caret.Position = new TextLocation(p1.X - 1, p1.Y);
|
|
}
|
|
|
|
textArea.SetDesiredColumn();
|
|
}
|
|
}
|
|
}
|
|
}
|