// DefaultEditActions.cs // // Author: // Mike Krüger // // Copyright (c) 2007 Novell, Inc (http://www.novell.com) // // 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.Diagnostics; using System.Text; using System.Linq; using ICSharpCode.NRefactory; namespace Mono.TextEditor { public static class MiscActions { public static void GotoMatchingBracket (TextEditorData data) { using (var undoGroup = data.OpenUndoGroup ()) { int matchingBracketOffset = data.Document.GetMatchingBracketOffset (data.Caret.Offset); if (matchingBracketOffset == -1 && data.Caret.Offset > 0) matchingBracketOffset = data.Document.GetMatchingBracketOffset (data.Caret.Offset - 1); if (matchingBracketOffset != -1) data.Caret.Offset = matchingBracketOffset; } } public static int RemoveTabInLine (TextEditorData data, DocumentLine line) { if (line.LengthIncludingDelimiter == 0) return 0; using (var undoGroup = data.OpenUndoGroup ()) { char ch = data.Document.GetCharAt (line.Offset); if (ch == '\t') { data.Remove (line.Offset, 1); data.Document.CommitLineUpdate (line); return 1; } else if (ch == ' ') { int removeCount = 0; for (int i = 0; i < data.Options.IndentationSize;) { ch = data.Document.GetCharAt (line.Offset + i); if (ch == ' ') { removeCount++; i++; } else if (ch == '\t') { removeCount++; i += data.Options.TabSize; } else { break; } } data.Remove (line.Offset, removeCount); data.Document.CommitLineUpdate (line); return removeCount; } } return 0; } public static void RemoveIndentSelection (TextEditorData data) { if (!data.IsSomethingSelected) return; int startLineNr, endLineNr; GetSelectedLines (data, out startLineNr, out endLineNr); using (var undo = data.OpenUndoGroup (OperationType.Format)) { var anchor = data.MainSelection.Anchor; var lead = data.MainSelection.Lead; bool first = true; bool removedFromLast = false; int removeLast = 0; bool removedFromFirst = false; int removeFirst = 0; foreach (var line in data.SelectedLines) { int remove = RemoveTabInLine (data, line); removedFromLast |= remove > 0; removeLast = remove; if (first) { removedFromFirst = remove > 0; removeFirst = remove; first = false; } } var ac = System.Math.Max (DocumentLocation.MinColumn, anchor.Column - (anchor < lead ? removeFirst : removeLast)); var lc = System.Math.Max (DocumentLocation.MinColumn, lead.Column - (anchor < lead ? removeLast : removeFirst)); if (anchor < lead) { if (!removedFromFirst) ac = anchor.Column; if (!removedFromLast) lc = lead.Column; } else { if (!removedFromFirst) lc = lead.Column; if (!removedFromLast) ac = anchor.Column; } data.SetSelection (anchor.Line, ac, lead.Line, lc); } data.Document.RequestUpdate (new MultipleLineUpdate (startLineNr, endLineNr)); data.Document.CommitDocumentUpdate (); } public static void RemoveTab (TextEditorData data) { if (data.IsSomethingSelected) { if (data.CanEditSelection) RemoveIndentSelection (data); return; } var line = data.Document.GetLine (data.Caret.Line); if (line != null) RemoveTabInLine (data, line); } public static void GetSelectedLines (TextEditorData data, out int startLineNr, out int endLineNr) { if (data.IsSomethingSelected) { DocumentLocation start, end; if (data.MainSelection.Anchor < data.MainSelection.Lead) { start = data.MainSelection.Anchor; end = data.MainSelection.Lead; } else { start = data.MainSelection.Lead; end = data.MainSelection.Anchor; } startLineNr = start.Line; endLineNr = end.Column == DocumentLocation.MinColumn ? end.Line - 1 : end.Line; } else { startLineNr = endLineNr = data.Caret.Line; } if (endLineNr < DocumentLocation.MinLine) endLineNr = data.Document.LineCount; } public static void IndentSelection (TextEditorData data) { if (!data.IsSomethingSelected) return; int startLineNr, endLineNr; GetSelectedLines (data, out startLineNr, out endLineNr); var anchor = data.MainSelection.Anchor; var lead = data.MainSelection.Lead; var indentationString = data.Options.IndentationString; using (var undo = data.OpenUndoGroup (OperationType.Format)) { foreach (DocumentLine line in data.SelectedLines) { if (data.Options.IndentStyle == IndentStyle.Virtual && line.Length == 0) continue; data.Insert (line.Offset, indentationString); } } int chars = indentationString.Length; var leadCol = lead.Column > 1 || lead < anchor ? lead.Column + chars : 1; var anchorCol = anchor.Column > 1 || anchor < lead ? anchor.Column + chars : 1; data.SetSelection (anchor.Line, anchorCol, lead.Line, leadCol); data.Document.RequestUpdate (new MultipleLineUpdate (startLineNr, endLineNr)); data.Document.CommitDocumentUpdate (); } public static void InsertTab (TextEditorData data) { if (!data.CanEditSelection) return; if (data.IsMultiLineSelection && data.MainSelection.SelectionMode != SelectionMode.Block) { IndentSelection (data); return; } using (var undo = data.OpenUndoGroup ()) { string indentationString = "\t"; bool convertTabToSpaces = data.TabsToSpaces; if (!convertTabToSpaces && !data.Options.AllowTabsAfterNonTabs) { for (int i = 1; i < data.Caret.Column; i++) { if (data.Document.GetCharAt (data.Caret.Offset - i) != '\t') { convertTabToSpaces = true; break; } } } if (convertTabToSpaces) { DocumentLocation visualLocation = data.LogicalToVisualLocation (data.Caret.Location); int tabWidth = TextViewMargin.GetNextTabstop (data, visualLocation.Column) - visualLocation.Column; indentationString = new string (' ', tabWidth); } if (data.IsSomethingSelected) data.DeleteSelectedText (data.MainSelection.SelectionMode != SelectionMode.Block); data.InsertAtCaret (indentationString); } } public static void InsertNewLinePreserveCaretPosition (TextEditorData data) { if (!data.CanEditSelection) return; DocumentLocation loc = data.Caret.Location; InsertNewLine (data); data.Caret.Location = loc; } public static void InsertNewLineAtEnd (TextEditorData data) { if (!data.CanEditSelection) return; using (var undoGroup = data.OpenUndoGroup ()) { DocumentLine line = data.Document.GetLine (data.Caret.Line); data.Caret.Column = line.Length + 1; InsertNewLine (data); } } static void NewLineSmartIndent (TextEditorData data) { using (var undo = data.OpenUndoGroup ()) { data.EnsureCaretIsNotVirtual (); string indentString; if (data.HasIndentationTracker) { indentString = data.IndentationTracker.GetIndentationString (data.Caret.Location); } else { indentString = data.GetIndentationString (data.Caret.Location); } data.InsertAtCaret (data.EolMarker); data.InsertAtCaret (indentString); } } public static void InsertNewLine (TextEditorData data) { if (!data.CanEditSelection) return; using (var undo = data.OpenUndoGroup ()) { if (data.IsSomethingSelected) { var end = data.MainSelection.End; data.DeleteSelectedText (); if (end.Column == 1) { CaretMoveActions.InternalCaretMoveHome (data, true, false); return; } } switch (data.Options.IndentStyle) { case IndentStyle.None: data.InsertAtCaret (data.EolMarker); break; case IndentStyle.Auto: data.EnsureCaretIsNotVirtual (); var indent = data.Document.GetLineIndent (data.Caret.Line); data.InsertAtCaret (data.EolMarker); data.EnsureCaretIsNotVirtual (); if (data.GetLine (data.Caret.Line).Length == 0) data.InsertAtCaret (indent); break; case IndentStyle.Smart: if (!data.HasIndentationTracker) goto case IndentStyle.Auto; NewLineSmartIndent (data); break; case IndentStyle.Virtual: if (!data.HasIndentationTracker) goto case IndentStyle.Auto; var oldLine = data.Caret.Line; var curLine = data.GetLine (oldLine); var indentCol = data.GetVirtualIndentationColumn (data.Caret.Location); if (curLine.Length >= data.Caret.Column) { NewLineSmartIndent (data); data.FixVirtualIndentation (); data.FixVirtualIndentation (oldLine); break; } data.Insert (data.Caret.Offset, data.EolMarker); data.FixVirtualIndentation (oldLine); data.Caret.Column = indentCol; break; default: throw new ArgumentOutOfRangeException (); } } } public static void SwitchCaretMode (TextEditorData data) { data.Caret.IsInInsertMode = !data.Caret.IsInInsertMode; data.Document.RequestUpdate (new SinglePositionUpdate (data.Caret.Line, data.Caret.Column)); data.Document.CommitDocumentUpdate (); } public static void Undo (TextEditorData data) { if (data.Document.ReadOnly) return; if (CancelPreEditMode (data)) return; var offset = data.Document.UndoBeginOffset; data.Document.Undo (); if (offset >= 0) data.ScrollTo (offset); } public static bool CancelPreEditMode (TextEditorData data) { var editor = data.Parent; if (editor != null && !string.IsNullOrEmpty (editor.preeditString)) { editor.ResetIMContext (); return true; } return false; } public static void Redo (TextEditorData data) { if (data.Document.ReadOnly) return; if (CancelPreEditMode (data)) return; var offset = data.Document.RedoBeginOffset; data.Document.Redo (); if (offset >= 0) data.ScrollTo (offset); } public static void MoveBlockUp (TextEditorData data) { int lineStart = data.Caret.Line; int lineEnd = data.Caret.Line; bool setSelection = lineStart != lineEnd; DocumentLocation anchor = DocumentLocation.Empty, lead = DocumentLocation.Empty; if (data.IsSomethingSelected) { setSelection = true; lineStart = data.MainSelection.MinLine; lineEnd = data.MainSelection.MaxLine; anchor = data.MainSelection.Anchor; lead = data.MainSelection.Lead; } if (lineStart <= 0) return; using (var undo = data.OpenUndoGroup ()) { //Mono.TextEditor.LineSegment startLine = data.Document.GetLine (lineStart); //int relCaretOffset = data.Caret.Offset - startLine.Offset; Mono.TextEditor.DocumentLine prevLine = data.Document.GetLine (lineStart - 1); string text = data.Document.GetTextAt (prevLine.Offset, prevLine.Length); List prevLineMarkers = new List (prevLine.Markers); prevLine.ClearMarker (); var loc = data.Caret.Location; for (int i = lineStart - 1; i <= lineEnd; i++) { DocumentLine cur = data.Document.GetLine (i); DocumentLine next = data.Document.GetLine (i + 1); data.Replace (cur.Offset, cur.Length, i != lineEnd ? data.Document.GetTextAt (next.Offset, next.Length) : text); data.Document.GetLine (i).ClearMarker (); foreach (TextLineMarker marker in (i != lineEnd ? data.Document.GetLine (i + 1).Markers : prevLineMarkers)) { data.Document.GetLine (i).AddMarker (marker); } } data.Caret.Location = new DocumentLocation (loc.Line - 1, loc.Column); if (setSelection) data.SetSelection (anchor.Line - 1, anchor.Column, lead.Line - 1, lead.Column); } } public static void MoveBlockDown (TextEditorData data) { int lineStart = data.Caret.Line; int lineEnd = data.Caret.Line; bool setSelection = lineStart != lineEnd; DocumentLocation anchor = DocumentLocation.Empty, lead = DocumentLocation.Empty; if (data.IsSomethingSelected) { setSelection = true; lineStart = data.MainSelection.MinLine; lineEnd = data.MainSelection.MaxLine; anchor = data.MainSelection.Anchor; lead = data.MainSelection.Lead; } if (lineStart <= 0) return; using (var undo = data.OpenUndoGroup ()) { //Mono.TextEditor.LineSegment startLine = data.Document.GetLine (lineStart); //int relCaretOffset = data.Caret.Offset - startLine.Offset; Mono.TextEditor.DocumentLine nextLine = data.Document.GetLine (lineEnd + 1); if (nextLine == null) return; string text = data.Document.GetTextAt (nextLine.Offset, nextLine.Length); List prevLineMarkers = new List (nextLine.Markers); nextLine.ClearMarker (); var loc = data.Caret.Location; for (int i = lineEnd + 1; i >= lineStart; i--) { DocumentLine cur = data.Document.GetLine (i); DocumentLine prev = data.Document.GetLine (i - 1); data.Replace (cur.Offset, cur.Length, i != lineStart ? data.Document.GetTextAt (prev.Offset, prev.Length) : text); data.Document.GetLine (i).ClearMarker (); foreach (TextLineMarker marker in (i != lineStart ? data.Document.GetLine (i - 1).Markers : prevLineMarkers)) { data.Document.GetLine (i).AddMarker (marker); } } data.Caret.Location = new DocumentLocation (loc.Line + 1, loc.Column); if (setSelection) data.SetSelection (anchor.Line + 1, anchor.Column, lead.Line + 1, lead.Column); } } /// /// Transpose characters (Emacs C-t) /// public static void TransposeCharacters (TextEditorData data) { if (data.Caret.Offset == 0) return; DocumentLine line = data.Document.GetLine (data.Caret.Line); if (line == null) return; using (var undoGroup = data.OpenUndoGroup ()) { int transposeOffset = data.Caret.Offset - 1; char ch; if (data.Caret.Column == 0) { DocumentLine lineAbove = data.Document.GetLine (data.Caret.Line - 1); if (lineAbove.Length == 0 && line.Length == 0) return; if (line.Length != 0) { ch = data.Document.GetCharAt (data.Caret.Offset); data.Remove (data.Caret.Offset, 1); data.Insert (lineAbove.Offset + lineAbove.Length, ch.ToString ()); data.Document.CommitLineUpdate (data.Caret.Line - 1); return; } int lastCharOffset = lineAbove.Offset + lineAbove.Length - 1; ch = data.Document.GetCharAt (lastCharOffset); data.Remove (lastCharOffset, 1); data.InsertAtCaret (ch.ToString ()); return; } int offset = data.Caret.Offset; if (data.Caret.Column >= line.Length + 1) { offset = line.Offset + line.Length - 1; transposeOffset = offset - 1; // case one char in line: if (transposeOffset < line.Offset) { DocumentLine lineAbove = data.Document.GetLine (data.Caret.Line - 1); transposeOffset = lineAbove.Offset + lineAbove.Length; ch = data.Document.GetCharAt (offset); data.Remove (offset, 1); data.Insert (transposeOffset, ch.ToString ()); data.Caret.Offset = line.Offset; data.Document.CommitLineUpdate (data.Caret.Line - 1); return; } } ch = data.Document.GetCharAt (offset); data.Replace (offset, 1, data.Document.GetCharAt (transposeOffset).ToString ()); data.Replace (transposeOffset, 1, ch.ToString ()); if (data.Caret.Column < line.Length + 1) data.Caret.Offset = offset + 1; } } /// /// Emacs c-l recenter editor command. /// public static void RecenterEditor (TextEditorData data) { data.RequestRecenter (); } public static void DuplicateLine (TextEditorData data) { using (var undoGroup = data.OpenUndoGroup ()) { if (data.IsSomethingSelected) { var selectedText = data.SelectedText; data.ClearSelection (); data.InsertAtCaret (selectedText); } else { DocumentLine line = data.Document.GetLine (data.Caret.Line); if (line == null) return; data.Insert (line.Offset, data.GetTextAt (line.SegmentIncludingDelimiter)); } } } } }