using System;
using System.Collections.Generic;
namespace FastColoredTextBoxNS
{
///
/// Insert single char
///
/// This operation includes also insertion of new line and removing char by backspace
public class InsertCharCommand : UndoableCommand
{
public char c;
char deletedChar = '\x0';
///
/// Constructor
///
/// Underlaying textbox
/// Inserting char
public InsertCharCommand(TextSource ts, char c): base(ts)
{
this.c = c;
}
///
/// Undo operation
///
public override void Undo()
{
ts.OnTextChanging();
switch (c)
{
case '\n': MergeLines(sel.Start.iLine, ts); break;
case '\r': break;
case '\b':
ts.CurrentTB.Selection.Start = lastSel.Start;
char cc = '\x0';
if (deletedChar != '\x0')
{
ts.CurrentTB.ExpandBlock(ts.CurrentTB.Selection.Start.iLine);
InsertChar(deletedChar, ref cc, ts);
}
break;
case '\t':
ts.CurrentTB.ExpandBlock(sel.Start.iLine);
for (int i = sel.FromX; i < lastSel.FromX; i++)
ts[sel.Start.iLine].RemoveAt(sel.Start.iChar);
ts.CurrentTB.Selection.Start = sel.Start;
break;
default:
ts.CurrentTB.ExpandBlock(sel.Start.iLine);
ts[sel.Start.iLine].RemoveAt(sel.Start.iChar);
ts.CurrentTB.Selection.Start = sel.Start;
break;
}
ts.NeedRecalc(new TextSource.TextChangedEventArgs(sel.Start.iLine, sel.Start.iLine));
base.Undo();
}
///
/// Execute operation
///
public override void Execute()
{
ts.CurrentTB.ExpandBlock(ts.CurrentTB.Selection.Start.iLine);
string s = c.ToString();
ts.OnTextChanging(ref s);
if (s.Length == 1)
c = s[0];
if (String.IsNullOrEmpty(s))
throw new ArgumentOutOfRangeException();
if (ts.Count == 0)
InsertLine(ts);
InsertChar(c, ref deletedChar, ts);
ts.NeedRecalc(new TextSource.TextChangedEventArgs(ts.CurrentTB.Selection.Start.iLine, ts.CurrentTB.Selection.Start.iLine));
base.Execute();
}
internal static void InsertChar(char c, ref char deletedChar, TextSource ts)
{
var tb = ts.CurrentTB;
switch (c)
{
case '\n':
if (!ts.CurrentTB.AllowInsertRemoveLines)
throw new ArgumentOutOfRangeException("Cant insert this char in ColumnRange mode");
if (ts.Count == 0)
InsertLine(ts);
InsertLine(ts);
break;
case '\r': break;
case '\b'://backspace
if (tb.Selection.Start.iChar == 0 && tb.Selection.Start.iLine == 0)
return;
if (tb.Selection.Start.iChar == 0)
{
if (!ts.CurrentTB.AllowInsertRemoveLines)
throw new ArgumentOutOfRangeException("Cant insert this char in ColumnRange mode");
if (tb.LineInfos[tb.Selection.Start.iLine - 1].VisibleState != VisibleState.Visible)
tb.ExpandBlock(tb.Selection.Start.iLine - 1);
deletedChar = '\n';
MergeLines(tb.Selection.Start.iLine - 1, ts);
}
else
{
deletedChar = ts[tb.Selection.Start.iLine][tb.Selection.Start.iChar - 1].c;
ts[tb.Selection.Start.iLine].RemoveAt(tb.Selection.Start.iChar - 1);
tb.Selection.Start = new Place(tb.Selection.Start.iChar - 1, tb.Selection.Start.iLine);
}
break;
case '\t':
int spaceCountNextTabStop = tb.TabLength - (tb.Selection.Start.iChar % tb.TabLength);
if (spaceCountNextTabStop == 0)
spaceCountNextTabStop = tb.TabLength;
for (int i = 0; i < spaceCountNextTabStop; i++)
ts[tb.Selection.Start.iLine].Insert(tb.Selection.Start.iChar, new Char(' '));
tb.Selection.Start = new Place(tb.Selection.Start.iChar + spaceCountNextTabStop, tb.Selection.Start.iLine);
break;
default:
ts[tb.Selection.Start.iLine].Insert(tb.Selection.Start.iChar, new Char(c));
tb.Selection.Start = new Place(tb.Selection.Start.iChar + 1, tb.Selection.Start.iLine);
break;
}
}
internal static void InsertLine(TextSource ts)
{
var tb = ts.CurrentTB;
if (!tb.Multiline && tb.LinesCount > 0)
return;
if (ts.Count == 0)
ts.InsertLine(0, ts.CreateLine());
else
BreakLines(tb.Selection.Start.iLine, tb.Selection.Start.iChar, ts);
tb.Selection.Start = new Place(0, tb.Selection.Start.iLine + 1);
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
///
/// Merge lines i and i+1
///
internal static void MergeLines(int i, TextSource ts)
{
var tb = ts.CurrentTB;
if (i + 1 >= ts.Count)
return;
tb.ExpandBlock(i);
tb.ExpandBlock(i + 1);
int pos = ts[i].Count;
//
/*
if(ts[i].Count == 0)
ts.RemoveLine(i);
else*/
if (ts[i + 1].Count == 0)
ts.RemoveLine(i + 1);
else
{
ts[i].AddRange(ts[i + 1]);
ts.RemoveLine(i + 1);
}
tb.Selection.Start = new Place(pos, i);
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
internal static void BreakLines(int iLine, int pos, TextSource ts)
{
Line newLine = ts.CreateLine();
for(int i=pos;i
/// Insert text
///
public class InsertTextCommand : UndoableCommand
{
public string InsertedText;
///
/// Constructor
///
/// Underlaying textbox
/// Text for inserting
public InsertTextCommand(TextSource ts, string insertedText): base(ts)
{
this.InsertedText = insertedText;
}
///
/// Undo operation
///
public override void Undo()
{
ts.CurrentTB.Selection.Start = sel.Start;
ts.CurrentTB.Selection.End = lastSel.Start;
ts.OnTextChanging();
ClearSelectedCommand.ClearSelected(ts);
base.Undo();
}
///
/// Execute operation
///
public override void Execute()
{
ts.OnTextChanging(ref InsertedText);
InsertText(InsertedText, ts);
base.Execute();
}
internal static void InsertText(string insertedText, TextSource ts)
{
var tb = ts.CurrentTB;
try
{
tb.Selection.BeginUpdate();
char cc = '\x0';
if (ts.Count == 0)
{
InsertCharCommand.InsertLine(ts);
tb.Selection.Start = Place.Empty;
}
tb.ExpandBlock(tb.Selection.Start.iLine);
var len = insertedText.Length;
for (int i = 0; i < len; i++)
{
var c = insertedText[i];
if(c == '\r' && (i >= len - 1 || insertedText[i + 1] != '\n'))
InsertCharCommand.InsertChar('\n', ref cc, ts);
else
InsertCharCommand.InsertChar(c, ref cc, ts);
}
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
finally {
tb.Selection.EndUpdate();
}
}
public override UndoableCommand Clone()
{
return new InsertTextCommand(ts, InsertedText);
}
}
///
/// Insert text into given ranges
///
public class ReplaceTextCommand : UndoableCommand
{
string insertedText;
List ranges;
List prevText = new List();
///
/// Constructor
///
/// Underlaying textbox
/// List of ranges for replace
/// Text for inserting
public ReplaceTextCommand(TextSource ts, List ranges, string insertedText)
: base(ts)
{
//sort ranges by place
ranges.Sort((r1, r2)=>
{
if (r1.Start.iLine == r2.Start.iLine)
return r1.Start.iChar.CompareTo(r2.Start.iChar);
return r1.Start.iLine.CompareTo(r2.Start.iLine);
});
//
this.ranges = ranges;
this.insertedText = insertedText;
lastSel = sel = new RangeInfo(ts.CurrentTB.Selection);
}
///
/// Undo operation
///
public override void Undo()
{
var tb = ts.CurrentTB;
ts.OnTextChanging();
tb.BeginUpdate();
tb.Selection.BeginUpdate();
for (int i = 0; i 0)
ts.OnTextChanged(ranges[0].Start.iLine, ranges[ranges.Count - 1].End.iLine);
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
///
/// Execute operation
///
public override void Execute()
{
var tb = ts.CurrentTB;
prevText.Clear();
ts.OnTextChanging(ref insertedText);
tb.Selection.BeginUpdate();
tb.BeginUpdate();
for (int i = ranges.Count - 1; i >= 0; i--)
{
tb.Selection.Start = ranges[i].Start;
tb.Selection.End = ranges[i].End;
prevText.Add(tb.Selection.Text);
ClearSelected(ts);
if (insertedText != "")
InsertTextCommand.InsertText(insertedText, ts);
}
if(ranges.Count > 0)
ts.OnTextChanged(ranges[0].Start.iLine, ranges[ranges.Count - 1].End.iLine);
tb.EndUpdate();
tb.Selection.EndUpdate();
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
lastSel = new RangeInfo(tb.Selection);
}
public override UndoableCommand Clone()
{
return new ReplaceTextCommand(ts, new List(ranges), insertedText);
}
internal static void ClearSelected(TextSource ts)
{
var tb = ts.CurrentTB;
tb.Selection.Normalize();
Place start = tb.Selection.Start;
Place end = tb.Selection.End;
int fromLine = Math.Min(end.iLine, start.iLine);
int toLine = Math.Max(end.iLine, start.iLine);
int fromChar = tb.Selection.FromX;
int toChar = tb.Selection.ToX;
if (fromLine < 0) return;
//
if (fromLine == toLine)
ts[fromLine].RemoveRange(fromChar, toChar - fromChar);
else
{
ts[fromLine].RemoveRange(fromChar, ts[fromLine].Count - fromChar);
ts[toLine].RemoveRange(0, toChar);
ts.RemoveLine(fromLine + 1, toLine - fromLine - 1);
InsertCharCommand.MergeLines(fromLine, ts);
}
}
}
///
/// Clear selected text
///
public class ClearSelectedCommand : UndoableCommand
{
string deletedText;
///
/// Construstor
///
/// Underlaying textbox
public ClearSelectedCommand(TextSource ts): base(ts)
{
}
///
/// Undo operation
///
public override void Undo()
{
ts.CurrentTB.Selection.Start = new Place(sel.FromX, Math.Min(sel.Start.iLine, sel.End.iLine));
ts.OnTextChanging();
InsertTextCommand.InsertText(deletedText, ts);
ts.OnTextChanged(sel.Start.iLine, sel.End.iLine);
ts.CurrentTB.Selection.Start = sel.Start;
ts.CurrentTB.Selection.End = sel.End;
}
///
/// Execute operation
///
public override void Execute()
{
var tb = ts.CurrentTB;
string temp = null;
ts.OnTextChanging(ref temp);
if (temp == "")
throw new ArgumentOutOfRangeException();
deletedText = tb.Selection.Text;
ClearSelected(ts);
lastSel = new RangeInfo(tb.Selection);
ts.OnTextChanged(lastSel.Start.iLine, lastSel.Start.iLine);
}
internal static void ClearSelected(TextSource ts)
{
var tb = ts.CurrentTB;
Place start = tb.Selection.Start;
Place end = tb.Selection.End;
int fromLine = Math.Min(end.iLine, start.iLine);
int toLine = Math.Max(end.iLine, start.iLine);
int fromChar = tb.Selection.FromX;
int toChar = tb.Selection.ToX;
if (fromLine < 0) return;
//
if (fromLine == toLine)
ts[fromLine].RemoveRange(fromChar, toChar - fromChar);
else
{
ts[fromLine].RemoveRange(fromChar, ts[fromLine].Count - fromChar);
ts[toLine].RemoveRange(0, toChar);
ts.RemoveLine(fromLine + 1, toLine - fromLine - 1);
InsertCharCommand.MergeLines(fromLine, ts);
}
//
tb.Selection.Start = new Place(fromChar, fromLine);
//
ts.NeedRecalc(new TextSource.TextChangedEventArgs(fromLine, toLine));
}
public override UndoableCommand Clone()
{
return new ClearSelectedCommand(ts);
}
}
///
/// Replaces text
///
public class ReplaceMultipleTextCommand : UndoableCommand
{
List ranges;
List prevText = new List();
public class ReplaceRange
{
public Range ReplacedRange { get; set; }
public String ReplaceText { get; set; }
}
///
/// Constructor
///
/// Underlaying textsource
/// List of ranges for replace
public ReplaceMultipleTextCommand(TextSource ts, List ranges)
: base(ts)
{
//sort ranges by place
ranges.Sort((r1, r2) =>
{
if (r1.ReplacedRange.Start.iLine == r2.ReplacedRange.Start.iLine)
return r1.ReplacedRange.Start.iChar.CompareTo(r2.ReplacedRange.Start.iChar);
return r1.ReplacedRange.Start.iLine.CompareTo(r2.ReplacedRange.Start.iLine);
});
//
this.ranges = ranges;
lastSel = sel = new RangeInfo(ts.CurrentTB.Selection);
}
///
/// Undo operation
///
public override void Undo()
{
var tb = ts.CurrentTB;
ts.OnTextChanging();
tb.Selection.BeginUpdate();
for (int i = 0; i < ranges.Count; i++)
{
tb.Selection.Start = ranges[i].ReplacedRange.Start;
for (int j = 0; j < ranges[i].ReplaceText.Length; j++)
tb.Selection.GoRight(true);
ClearSelectedCommand.ClearSelected(ts);
var prevTextIndex = ranges.Count - 1 - i;
InsertTextCommand.InsertText(prevText[prevTextIndex], ts);
ts.OnTextChanged(ranges[i].ReplacedRange.Start.iLine, ranges[i].ReplacedRange.Start.iLine);
}
tb.Selection.EndUpdate();
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
///
/// Execute operation
///
public override void Execute()
{
var tb = ts.CurrentTB;
prevText.Clear();
ts.OnTextChanging();
tb.Selection.BeginUpdate();
for (int i = ranges.Count - 1; i >= 0; i--)
{
tb.Selection.Start = ranges[i].ReplacedRange.Start;
tb.Selection.End = ranges[i].ReplacedRange.End;
prevText.Add(tb.Selection.Text);
ClearSelectedCommand.ClearSelected(ts);
InsertTextCommand.InsertText(ranges[i].ReplaceText, ts);
ts.OnTextChanged(ranges[i].ReplacedRange.Start.iLine, ranges[i].ReplacedRange.End.iLine);
}
tb.Selection.EndUpdate();
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
lastSel = new RangeInfo(tb.Selection);
}
public override UndoableCommand Clone()
{
return new ReplaceMultipleTextCommand(ts, new List(ranges));
}
}
///
/// Removes lines
///
public class RemoveLinesCommand : UndoableCommand
{
List iLines;
List prevText = new List();
///
/// Constructor
///
/// Underlaying textbox
/// List of ranges for replace
/// Text for inserting
public RemoveLinesCommand(TextSource ts, List iLines)
: base(ts)
{
//sort iLines
iLines.Sort();
//
this.iLines = iLines;
lastSel = sel = new RangeInfo(ts.CurrentTB.Selection);
}
///
/// Undo operation
///
public override void Undo()
{
var tb = ts.CurrentTB;
ts.OnTextChanging();
tb.Selection.BeginUpdate();
//tb.BeginUpdate();
for (int i = 0; i < iLines.Count; i++)
{
var iLine = iLines[i];
if(iLine < ts.Count)
tb.Selection.Start = new Place(0, iLine);
else
tb.Selection.Start = new Place(ts[ts.Count - 1].Count, ts.Count - 1);
InsertCharCommand.InsertLine(ts);
tb.Selection.Start = new Place(0, iLine);
var text = prevText[prevText.Count - i - 1];
InsertTextCommand.InsertText(text, ts);
ts[iLine].IsChanged = true;
if (iLine < ts.Count - 1)
ts[iLine + 1].IsChanged = true;
else
ts[iLine - 1].IsChanged = true;
if(text.Trim() != string.Empty)
ts.OnTextChanged(iLine, iLine);
}
//tb.EndUpdate();
tb.Selection.EndUpdate();
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
///
/// Execute operation
///
public override void Execute()
{
var tb = ts.CurrentTB;
prevText.Clear();
ts.OnTextChanging();
tb.Selection.BeginUpdate();
for(int i = iLines.Count - 1; i >= 0; i--)
{
var iLine = iLines[i];
prevText.Add(ts[iLine].Text);//backward
ts.RemoveLine(iLine);
//ts.OnTextChanged(ranges[i].Start.iLine, ranges[i].End.iLine);
}
tb.Selection.Start = new Place(0, 0);
tb.Selection.EndUpdate();
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
lastSel = new RangeInfo(tb.Selection);
}
public override UndoableCommand Clone()
{
return new RemoveLinesCommand(ts, new List(iLines));
}
}
///
/// Wrapper for multirange commands
///
public class MultiRangeCommand : UndoableCommand
{
private UndoableCommand cmd;
private Range range;
private List commandsByRanges = new List();
public MultiRangeCommand(UndoableCommand command):base(command.ts)
{
this.cmd = command;
range = ts.CurrentTB.Selection.Clone();
}
public override void Execute()
{
commandsByRanges.Clear();
var prevSelection = range.Clone();
var iChar = -1;
var iStartLine = prevSelection.Start.iLine;
var iEndLine = prevSelection.End.iLine;
ts.CurrentTB.Selection.ColumnSelectionMode = false;
ts.CurrentTB.Selection.BeginUpdate();
ts.CurrentTB.BeginUpdate();
ts.CurrentTB.AllowInsertRemoveLines = false;
try
{
if (cmd is InsertTextCommand)
ExecuteInsertTextCommand(ref iChar, (cmd as InsertTextCommand).InsertedText);
else
if (cmd is InsertCharCommand && (cmd as InsertCharCommand).c != '\x0' && (cmd as InsertCharCommand).c != '\b')//if not DEL or BACKSPACE
ExecuteInsertTextCommand(ref iChar, (cmd as InsertCharCommand).c.ToString());
else
ExecuteCommand(ref iChar);
}
catch (ArgumentOutOfRangeException)
{
}
finally
{
ts.CurrentTB.AllowInsertRemoveLines = true;
ts.CurrentTB.EndUpdate();
ts.CurrentTB.Selection = range;
if (iChar >= 0)
{
ts.CurrentTB.Selection.Start = new Place(iChar, iStartLine);
ts.CurrentTB.Selection.End = new Place(iChar, iEndLine);
}
ts.CurrentTB.Selection.ColumnSelectionMode = true;
ts.CurrentTB.Selection.EndUpdate();
}
}
private void ExecuteInsertTextCommand(ref int iChar, string text)
{
var lines = text.Split('\n');
var iLine = 0;
foreach (var r in range.GetSubRanges(true))
{
var line = ts.CurrentTB[r.Start.iLine];
var lineIsEmpty = r.End < r.Start && line.StartSpacesCount == line.Count;
if (!lineIsEmpty)
{
var insertedText = lines[iLine%lines.Length];
if (r.End < r.Start && insertedText!="")
{
//add forwarding spaces
insertedText = new string(' ', r.Start.iChar - r.End.iChar) + insertedText;
r.Start = r.End;
}
ts.CurrentTB.Selection = r;
var c = new InsertTextCommand(ts, insertedText);
c.Execute();
if (ts.CurrentTB.Selection.End.iChar > iChar)
iChar = ts.CurrentTB.Selection.End.iChar;
commandsByRanges.Add(c);
}
iLine++;
}
}
private void ExecuteCommand(ref int iChar)
{
foreach (var r in range.GetSubRanges(false))
{
ts.CurrentTB.Selection = r;
var c = cmd.Clone();
c.Execute();
if (ts.CurrentTB.Selection.End.iChar > iChar)
iChar = ts.CurrentTB.Selection.End.iChar;
commandsByRanges.Add(c);
}
}
public override void Undo()
{
ts.CurrentTB.BeginUpdate();
ts.CurrentTB.Selection.BeginUpdate();
try
{
for (int i = commandsByRanges.Count - 1; i >= 0; i--)
commandsByRanges[i].Undo();
}
finally
{
ts.CurrentTB.Selection.EndUpdate();
ts.CurrentTB.EndUpdate();
}
ts.CurrentTB.Selection = range.Clone();
ts.CurrentTB.OnTextChanged(range);
ts.CurrentTB.OnSelectionChanged();
ts.CurrentTB.Selection.ColumnSelectionMode = true;
}
public override UndoableCommand Clone()
{
throw new NotImplementedException();
}
}
///
/// Remembers current selection and restore it after Undo
///
public class SelectCommand : UndoableCommand
{
public SelectCommand(TextSource ts):base(ts)
{
}
public override void Execute()
{
//remember selection
lastSel = new RangeInfo(ts.CurrentTB.Selection);
}
protected override void OnTextChanged(bool invert)
{
}
public override void Undo()
{
//restore selection
ts.CurrentTB.Selection = new Range(ts.CurrentTB, lastSel.Start, lastSel.End);
}
public override UndoableCommand Clone()
{
var result = new SelectCommand(ts);
if(lastSel!=null)
result.lastSel = new RangeInfo(new Range(ts.CurrentTB, lastSel.Start, lastSel.End));
return result;
}
}
}