Mesen-X/GUI.NET/Debugger/FastColoredTextBox/Range.cs
2017-08-30 18:31:27 -04:00

1678 lines
53 KiB
C#

using System;
using System.Text;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Collections.Generic;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Diapason of text chars
/// </summary>
public class Range : IEnumerable<Place>
{
Place start;
Place end;
public readonly FastColoredTextBox tb;
int preferedPos = -1;
int updating = 0;
string cachedText;
List<Place> cachedCharIndexToPlace;
int cachedTextVersion = -1;
/// <summary>
/// Constructor
/// </summary>
public Range(FastColoredTextBox tb)
{
this.tb = tb;
}
/// <summary>
/// Return true if no selected text
/// </summary>
public virtual bool IsEmpty
{
get
{
if (ColumnSelectionMode)
return Start.iChar == End.iChar;
return Start == End;
}
}
private bool columnSelectionMode;
/// <summary>
/// Column selection mode
/// </summary>
public bool ColumnSelectionMode
{
get { return columnSelectionMode; }
set { columnSelectionMode = value; }
}
/// <summary>
/// Constructor
/// </summary>
public Range(FastColoredTextBox tb, int iStartChar, int iStartLine, int iEndChar, int iEndLine)
: this(tb)
{
start = new Place(iStartChar, iStartLine);
end = new Place(iEndChar, iEndLine);
}
/// <summary>
/// Constructor
/// </summary>
public Range(FastColoredTextBox tb, Place start, Place end)
: this(tb)
{
this.start = start;
this.end = end;
}
/// <summary>
/// Constructor. Creates range of the line
/// </summary>
public Range(FastColoredTextBox tb, int iLine)
: this(tb)
{
start = new Place(0, iLine);
end = new Place(tb[iLine].Count, iLine);
}
public bool Contains(Place place)
{
if (place.iLine < Math.Min(start.iLine, end.iLine)) return false;
if (place.iLine > Math.Max(start.iLine, end.iLine)) return false;
Place s = start;
Place e = end;
//normalize start and end
if (s.iLine > e.iLine || (s.iLine == e.iLine && s.iChar > e.iChar))
{
var temp = s;
s = e;
e = temp;
}
if (columnSelectionMode)
{
if (place.iChar < s.iChar || place.iChar > e.iChar) return false;
}
else
{
if (place.iLine == s.iLine && place.iChar < s.iChar) return false;
if (place.iLine == e.iLine && place.iChar > e.iChar) return false;
}
return true;
}
/// <summary>
/// Returns intersection with other range,
/// empty range returned otherwise
/// </summary>
/// <param name="range"></param>
/// <returns></returns>
public virtual Range GetIntersectionWith(Range range)
{
if (ColumnSelectionMode)
return GetIntersectionWith_ColumnSelectionMode(range);
Range r1 = this.Clone();
Range r2 = range.Clone();
r1.Normalize();
r2.Normalize();
Place newStart = r1.Start > r2.Start ? r1.Start : r2.Start;
Place newEnd = r1.End < r2.End ? r1.End : r2.End;
if (newEnd < newStart)
return new Range(tb, start, start);
return tb.GetRange(newStart, newEnd);
}
/// <summary>
/// Returns union with other range.
/// </summary>
/// <param name="range"></param>
/// <returns></returns>
public Range GetUnionWith(Range range)
{
Range r1 = this.Clone();
Range r2 = range.Clone();
r1.Normalize();
r2.Normalize();
Place newStart = r1.Start < r2.Start ? r1.Start : r2.Start;
Place newEnd = r1.End > r2.End ? r1.End : r2.End;
return tb.GetRange(newStart, newEnd);
}
/// <summary>
/// Select all chars of control
/// </summary>
public void SelectAll()
{
ColumnSelectionMode = false;
Start = new Place(0, 0);
if (tb.LinesCount == 0)
Start = new Place(0, 0);
else
{
end = new Place(0, 0);
start = new Place(tb[tb.LinesCount - 1].Count, tb.LinesCount - 1);
}
if (this == tb.Selection)
tb.Invalidate();
}
/// <summary>
/// Start line and char position
/// </summary>
public Place Start
{
get { return start; }
set
{
end = start = value;
preferedPos = -1;
OnSelectionChanged();
}
}
/// <summary>
/// Finish line and char position
/// </summary>
public Place End
{
get
{
return end;
}
set
{
end = value;
OnSelectionChanged();
}
}
/// <summary>
/// Text of range
/// </summary>
/// <remarks>This property has not 'set' accessor because undo/redo stack works only with
/// FastColoredTextBox.Selection range. So, if you want to set text, you need to use FastColoredTextBox.Selection
/// and FastColoredTextBox.InsertText() mehtod.
/// </remarks>
public virtual string Text
{
get
{
if (ColumnSelectionMode)
return Text_ColumnSelectionMode;
int fromLine = Math.Min(end.iLine, start.iLine);
int toLine = Math.Max(end.iLine, start.iLine);
int fromChar = FromX;
int toChar = ToX;
if (fromLine < 0) return null;
//
StringBuilder sb = new StringBuilder();
for (int y = fromLine; y <= toLine; y++)
{
int fromX = y == fromLine ? fromChar : 0;
int toX = y == toLine ? Math.Min(tb[y].Count - 1, toChar - 1) : tb[y].Count - 1;
for (int x = fromX; x <= toX; x++)
sb.Append(tb[y][x].c);
if (y != toLine && fromLine != toLine)
sb.AppendLine();
}
return sb.ToString();
}
}
internal void GetText(out string text, out List<Place> charIndexToPlace)
{
//try get cached text
if (tb.TextVersion == cachedTextVersion)
{
text = cachedText;
charIndexToPlace = cachedCharIndexToPlace;
return;
}
//
int fromLine = Math.Min(end.iLine, start.iLine);
int toLine = Math.Max(end.iLine, start.iLine);
int fromChar = FromX;
int toChar = ToX;
StringBuilder sb = new StringBuilder((toLine - fromLine)*50);
charIndexToPlace = new List<Place>(sb.Capacity);
if (fromLine >= 0)
{
for (int y = fromLine; y <= toLine; y++)
{
int fromX = y == fromLine ? fromChar : 0;
int toX = y == toLine ? Math.Min(toChar - 1, tb[y].Count - 1) : tb[y].Count - 1;
for (int x = fromX; x <= toX; x++)
{
sb.Append(tb[y][x].c);
charIndexToPlace.Add(new Place(x, y));
}
if (y != toLine && fromLine != toLine)
foreach (char c in Environment.NewLine)
{
sb.Append(c);
charIndexToPlace.Add(new Place(tb[y].Count/*???*/, y));
}
}
}
text = sb.ToString();
charIndexToPlace.Add(End > Start ? End : Start);
//caching
cachedText = text;
cachedCharIndexToPlace = charIndexToPlace;
cachedTextVersion = tb.TextVersion;
}
/// <summary>
/// Returns first char after Start place
/// </summary>
public char CharAfterStart
{
get
{
if (Start.iChar >= tb[Start.iLine].Count)
return '\n';
return tb[Start.iLine][Start.iChar].c;
}
}
/// <summary>
/// Returns first char before Start place
/// </summary>
public char CharBeforeStart
{
get
{
if (Start.iChar > tb[Start.iLine].Count)
return '\n';
if (Start.iChar <= 0)
return '\n';
return tb[Start.iLine][Start.iChar - 1].c;
}
}
/// <summary>
/// Returns required char's number before start of the Range
/// </summary>
public string GetCharsBeforeStart(int charsCount)
{
var pos = tb.PlaceToPosition(Start) - charsCount;
if(pos < 0) pos = 0;
return new Range(tb, tb.PositionToPlace(pos), Start).Text;
}
/// <summary>
/// Returns required char's number after start of the Range
/// </summary>
public string GetCharsAfterStart(int charsCount)
{
return GetCharsBeforeStart(-charsCount);
}
/// <summary>
/// Clone range
/// </summary>
/// <returns></returns>
public Range Clone()
{
return (Range)MemberwiseClone();
}
/// <summary>
/// Return minimum of end.X and start.X
/// </summary>
internal int FromX
{
get
{
if (end.iLine < start.iLine) return end.iChar;
if (end.iLine > start.iLine) return start.iChar;
return Math.Min(end.iChar, start.iChar);
}
}
/// <summary>
/// Return maximum of end.X and start.X
/// </summary>
internal int ToX
{
get
{
if (end.iLine < start.iLine) return start.iChar;
if (end.iLine > start.iLine) return end.iChar;
return Math.Max(end.iChar, start.iChar);
}
}
public int FromLine
{
get { return Math.Min(Start.iLine, End.iLine); }
}
public int ToLine
{
get { return Math.Max(Start.iLine, End.iLine); }
}
/// <summary>
/// Move range right
/// </summary>
/// <remarks>This method jump over folded blocks</remarks>
public bool GoRight()
{
Place prevStart = start;
GoRight(false);
return prevStart != start;
}
/// <summary>
/// Move range left
/// </summary>
/// <remarks>This method can to go inside folded blocks</remarks>
public virtual bool GoRightThroughFolded()
{
if (ColumnSelectionMode)
return GoRightThroughFolded_ColumnSelectionMode();
if (start.iLine >= tb.LinesCount - 1 && start.iChar >= tb[tb.LinesCount - 1].Count)
return false;
if (start.iChar < tb[start.iLine].Count)
start.Offset(1, 0);
else
start = new Place(0, start.iLine + 1);
preferedPos = -1;
end = start;
OnSelectionChanged();
return true;
}
/// <summary>
/// Move range left
/// </summary>
/// <remarks>This method jump over folded blocks</remarks>
public bool GoLeft()
{
ColumnSelectionMode = false;
Place prevStart = start;
GoLeft(false);
return prevStart != start;
}
/// <summary>
/// Move range left
/// </summary>
/// <remarks>This method can to go inside folded blocks</remarks>
public bool GoLeftThroughFolded()
{
ColumnSelectionMode = false;
if (start.iChar == 0 && start.iLine == 0)
return false;
if (start.iChar > 0)
start.Offset(-1, 0);
else
start = new Place(tb[start.iLine - 1].Count, start.iLine - 1);
preferedPos = -1;
end = start;
OnSelectionChanged();
return true;
}
public void GoLeft(bool shift)
{
ColumnSelectionMode = false;
if (!shift)
if (start > end)
{
Start = End;
return;
}
if (start.iChar != 0 || start.iLine != 0)
{
if (start.iChar > 0 && tb.LineInfos[start.iLine].VisibleState == VisibleState.Visible)
start.Offset(-1, 0);
else
{
int i = tb.FindPrevVisibleLine(start.iLine);
if (i == start.iLine) return;
start = new Place(tb[i].Count, i);
}
}
if (!shift)
end = start;
OnSelectionChanged();
preferedPos = -1;
}
public void GoRight(bool shift)
{
ColumnSelectionMode = false;
if (!shift)
if (start < end)
{
Start = End;
return;
}
if (start.iLine < tb.LinesCount - 1 || start.iChar < tb[tb.LinesCount - 1].Count)
{
if (start.iChar < tb[start.iLine].Count && tb.LineInfos[start.iLine].VisibleState == VisibleState.Visible)
start.Offset(1, 0);
else
{
int i = tb.FindNextVisibleLine(start.iLine);
if (i == start.iLine) return;
start = new Place(0, i);
}
}
if (!shift)
end = start;
OnSelectionChanged();
preferedPos = -1;
}
internal void GoUp(bool shift)
{
ColumnSelectionMode = false;
if (!shift)
if (start.iLine > end.iLine)
{
Start = End;
return;
}
if (preferedPos < 0)
preferedPos = start.iChar - tb.LineInfos[start.iLine].GetWordWrapStringStartPosition(tb.LineInfos[start.iLine].GetWordWrapStringIndex(start.iChar));
int iWW = tb.LineInfos[start.iLine].GetWordWrapStringIndex(start.iChar);
if (iWW == 0)
{
if (start.iLine <= 0) return;
int i = tb.FindPrevVisibleLine(start.iLine);
if (i == start.iLine) return;
start.iLine = i;
iWW = tb.LineInfos[start.iLine].WordWrapStringsCount;
}
if (iWW > 0)
{
int finish = tb.LineInfos[start.iLine].GetWordWrapStringFinishPosition(iWW - 1, tb[start.iLine]);
start.iChar = tb.LineInfos[start.iLine].GetWordWrapStringStartPosition(iWW - 1) + preferedPos;
if (start.iChar > finish + 1)
start.iChar = finish + 1;
}
if (!shift)
end = start;
OnSelectionChanged();
}
internal void GoPageUp(bool shift)
{
ColumnSelectionMode = false;
if (preferedPos < 0)
preferedPos = start.iChar - tb.LineInfos[start.iLine].GetWordWrapStringStartPosition(tb.LineInfos[start.iLine].GetWordWrapStringIndex(start.iChar));
int pageHeight = tb.ClientRectangle.Height / tb.CharHeight - 1;
for (int i = 0; i < pageHeight; i++)
{
int iWW = tb.LineInfos[start.iLine].GetWordWrapStringIndex(start.iChar);
if (iWW == 0)
{
if (start.iLine <= 0) break;
//pass hidden
int newLine = tb.FindPrevVisibleLine(start.iLine);
if (newLine == start.iLine) break;
start.iLine = newLine;
iWW = tb.LineInfos[start.iLine].WordWrapStringsCount;
}
if (iWW > 0)
{
int finish = tb.LineInfos[start.iLine].GetWordWrapStringFinishPosition(iWW - 1, tb[start.iLine]);
start.iChar = tb.LineInfos[start.iLine].GetWordWrapStringStartPosition(iWW - 1) + preferedPos;
if (start.iChar > finish + 1)
start.iChar = finish + 1;
}
}
if (!shift)
end = start;
OnSelectionChanged();
}
internal void GoDown(bool shift)
{
ColumnSelectionMode = false;
if(!shift)
if (start.iLine < end.iLine)
{
Start = End;
return;
}
if (preferedPos < 0)
preferedPos = start.iChar - tb.LineInfos[start.iLine].GetWordWrapStringStartPosition(tb.LineInfos[start.iLine].GetWordWrapStringIndex(start.iChar));
int iWW = tb.LineInfos[start.iLine].GetWordWrapStringIndex(start.iChar);
if (iWW >= tb.LineInfos[start.iLine].WordWrapStringsCount - 1)
{
if (start.iLine >= tb.LinesCount - 1) return;
//pass hidden
int i = tb.FindNextVisibleLine(start.iLine);
if (i == start.iLine) return;
start.iLine = i;
iWW = -1;
}
if (iWW < tb.LineInfos[start.iLine].WordWrapStringsCount - 1)
{
int finish = tb.LineInfos[start.iLine].GetWordWrapStringFinishPosition(iWW + 1, tb[start.iLine]);
start.iChar = tb.LineInfos[start.iLine].GetWordWrapStringStartPosition(iWW + 1) + preferedPos;
if (start.iChar > finish + 1)
start.iChar = finish + 1;
}
if (!shift)
end = start;
OnSelectionChanged();
}
internal void GoPageDown(bool shift)
{
ColumnSelectionMode = false;
if (preferedPos < 0)
preferedPos = start.iChar - tb.LineInfos[start.iLine].GetWordWrapStringStartPosition(tb.LineInfos[start.iLine].GetWordWrapStringIndex(start.iChar));
int pageHeight = tb.ClientRectangle.Height / tb.CharHeight - 1;
for (int i = 0; i < pageHeight; i++)
{
int iWW = tb.LineInfos[start.iLine].GetWordWrapStringIndex(start.iChar);
if (iWW >= tb.LineInfos[start.iLine].WordWrapStringsCount - 1)
{
if (start.iLine >= tb.LinesCount - 1) break;
//pass hidden
int newLine = tb.FindNextVisibleLine(start.iLine);
if (newLine == start.iLine) break;
start.iLine = newLine;
iWW = -1;
}
if (iWW < tb.LineInfos[start.iLine].WordWrapStringsCount - 1)
{
int finish = tb.LineInfos[start.iLine].GetWordWrapStringFinishPosition(iWW + 1, tb[start.iLine]);
start.iChar = tb.LineInfos[start.iLine].GetWordWrapStringStartPosition(iWW + 1) + preferedPos;
if (start.iChar > finish + 1)
start.iChar = finish + 1;
}
}
if (!shift)
end = start;
OnSelectionChanged();
}
internal void GoHome(bool shift)
{
ColumnSelectionMode = false;
if (start.iLine < 0)
return;
if (tb.LineInfos[start.iLine].VisibleState != VisibleState.Visible)
return;
start = new Place(0, start.iLine);
if (!shift)
end = start;
OnSelectionChanged();
preferedPos = -1;
}
internal void GoEnd(bool shift)
{
ColumnSelectionMode = false;
if (start.iLine < 0)
return;
if (tb.LineInfos[start.iLine].VisibleState != VisibleState.Visible)
return;
start = new Place(tb[start.iLine].Count, start.iLine);
if (!shift)
end = start;
OnSelectionChanged();
preferedPos = -1;
}
/// <summary>
/// Set style for range
/// </summary>
public void SetStyle(Style style)
{
//search code for style
int code = tb.GetOrSetStyleLayerIndex(style);
//set code to chars
SetStyle(ToStyleIndex(code));
//
tb.Invalidate();
}
/// <summary>
/// Set style for given regex pattern
/// </summary>
public void SetStyle(Style style, string regexPattern)
{
//search code for style
StyleIndex layer = ToStyleIndex(tb.GetOrSetStyleLayerIndex(style));
SetStyle(layer, regexPattern, RegexOptions.None);
}
/// <summary>
/// Set style for given regex
/// </summary>
public void SetStyle(Style style, Regex regex)
{
//search code for style
StyleIndex layer = ToStyleIndex(tb.GetOrSetStyleLayerIndex(style));
SetStyle(layer, regex);
}
/// <summary>
/// Set style for given regex pattern
/// </summary>
public void SetStyle(Style style, string regexPattern, RegexOptions options)
{
//search code for style
StyleIndex layer = ToStyleIndex(tb.GetOrSetStyleLayerIndex(style));
SetStyle(layer, regexPattern, options);
}
/// <summary>
/// Set style for given regex pattern
/// </summary>
public void SetStyle(StyleIndex styleLayer, string regexPattern, RegexOptions options)
{
if (Math.Abs(Start.iLine - End.iLine) > 1000)
options |= SyntaxHighlighter.RegexCompiledOption;
//
foreach (var range in GetRanges(regexPattern, options))
range.SetStyle(styleLayer);
//
tb.Invalidate();
}
/// <summary>
/// Set style for given regex pattern
/// </summary>
public void SetStyle(StyleIndex styleLayer, Regex regex)
{
foreach (var range in GetRanges(regex))
range.SetStyle(styleLayer);
//
tb.Invalidate();
}
/// <summary>
/// Appends style to chars of range
/// </summary>
public void SetStyle(StyleIndex styleIndex)
{
//set code to chars
int fromLine = Math.Min(End.iLine, Start.iLine);
int toLine = Math.Max(End.iLine, Start.iLine);
int fromChar = FromX;
int toChar = ToX;
if (fromLine < 0) return;
//
for (int y = fromLine; y <= toLine; y++)
{
int fromX = y == fromLine ? fromChar : 0;
int toX = y == toLine ? Math.Min(toChar - 1, tb[y].Count - 1) : tb[y].Count - 1;
for (int x = fromX; x <= toX; x++)
{
Char c = tb[y][x];
c.style |= styleIndex;
tb[y][x] = c;
}
}
}
/// <summary>
/// Sets folding markers
/// </summary>
/// <param name="startFoldingPattern">Pattern for start folding line</param>
/// <param name="finishFoldingPattern">Pattern for finish folding line</param>
public void SetFoldingMarkers(string startFoldingPattern, string finishFoldingPattern)
{
SetFoldingMarkers(startFoldingPattern, finishFoldingPattern, SyntaxHighlighter.RegexCompiledOption);
}
/// <summary>
/// Sets folding markers
/// </summary>
/// <param name="startFoldingPattern">Pattern for start folding line</param>
/// <param name="finishFoldingPattern">Pattern for finish folding line</param>
public void SetFoldingMarkers(string startFoldingPattern, string finishFoldingPattern, RegexOptions options)
{
if (startFoldingPattern == finishFoldingPattern)
{
SetFoldingMarkers(startFoldingPattern, options);
return;
}
foreach (var range in GetRanges(startFoldingPattern, options))
tb[range.Start.iLine].FoldingStartMarker = startFoldingPattern;
foreach (var range in GetRanges(finishFoldingPattern, options))
tb[range.Start.iLine].FoldingEndMarker = startFoldingPattern;
//
tb.Invalidate();
}
/// <summary>
/// Sets folding markers
/// </summary>
/// <param name="startEndFoldingPattern">Pattern for start and end folding line</param>
public void SetFoldingMarkers(string foldingPattern, RegexOptions options)
{
foreach (var range in GetRanges(foldingPattern, options))
{
if (range.Start.iLine > 0)
tb[range.Start.iLine-1].FoldingEndMarker = foldingPattern;
tb[range.Start.iLine].FoldingStartMarker = foldingPattern;
}
tb.Invalidate();
}
/// <summary>
/// Finds ranges for given regex pattern
/// </summary>
/// <param name="regexPattern">Regex pattern</param>
/// <returns>Enumeration of ranges</returns>
public IEnumerable<Range> GetRanges(string regexPattern)
{
return GetRanges(regexPattern, RegexOptions.None);
}
/// <summary>
/// Finds ranges for given regex pattern
/// </summary>
/// <param name="regexPattern">Regex pattern</param>
/// <returns>Enumeration of ranges</returns>
public IEnumerable<Range> GetRanges(string regexPattern, RegexOptions options)
{
//get text
string text;
List<Place> charIndexToPlace;
GetText(out text, out charIndexToPlace);
//create regex
Regex regex = new Regex(regexPattern, options);
//
foreach (Match m in regex.Matches(text))
{
Range r = new Range(this.tb);
//try get 'range' group, otherwise use group 0
Group group = m.Groups["range"];
if (!group.Success)
group = m.Groups[0];
//
r.Start = charIndexToPlace[group.Index];
r.End = charIndexToPlace[group.Index + group.Length];
yield return r;
}
}
/// <summary>
/// Finds ranges for given regex pattern.
/// Search is separately in each line.
/// This method requires less memory than GetRanges().
/// </summary>
/// <param name="regexPattern">Regex pattern</param>
/// <returns>Enumeration of ranges</returns>
public IEnumerable<Range> GetRangesByLines(string regexPattern, RegexOptions options)
{
var regex = new Regex(regexPattern, options);
foreach (var r in GetRangesByLines(regex))
yield return r;
}
/// <summary>
/// Finds ranges for given regex.
/// Search is separately in each line.
/// This method requires less memory than GetRanges().
/// </summary>
/// <param name="regex">Regex</param>
/// <returns>Enumeration of ranges</returns>
public IEnumerable<Range> GetRangesByLines(Regex regex)
{
Normalize();
var fts = tb.TextSource as FileTextSource; //<----!!!! ugly
//enumaerate lines
for (int iLine = Start.iLine; iLine <= End.iLine; iLine++)
{
//
bool isLineLoaded = fts != null ? fts.IsLineLoaded(iLine) : true;
//
var r = new Range(tb, new Place(0, iLine), new Place(tb[iLine].Count, iLine));
if (iLine == Start.iLine || iLine == End.iLine)
r = r.GetIntersectionWith(this);
foreach (var foundRange in r.GetRanges(regex))
yield return foundRange;
if (!isLineLoaded)
fts.UnloadLine(iLine);
}
}
/// <summary>
/// Finds ranges for given regex pattern.
/// Search is separately in each line (order of lines is reversed).
/// This method requires less memory than GetRanges().
/// </summary>
/// <param name="regexPattern">Regex pattern</param>
/// <returns>Enumeration of ranges</returns>
public IEnumerable<Range> GetRangesByLinesReversed(string regexPattern, RegexOptions options)
{
Normalize();
//create regex
Regex regex = new Regex(regexPattern, options);
//
var fts = tb.TextSource as FileTextSource; //<----!!!! ugly
//enumaerate lines
for (int iLine = End.iLine; iLine >= Start.iLine; iLine--)
{
//
bool isLineLoaded = fts != null ? fts.IsLineLoaded(iLine) : true;
//
var r = new Range(tb, new Place(0, iLine), new Place(tb[iLine].Count, iLine));
if (iLine == Start.iLine || iLine == End.iLine)
r = r.GetIntersectionWith(this);
var list = new List<Range>();
foreach (var foundRange in r.GetRanges(regex))
list.Add(foundRange);
for (int i = list.Count - 1; i >= 0; i--)
yield return list[i];
if (!isLineLoaded)
fts.UnloadLine(iLine);
}
}
/// <summary>
/// Finds ranges for given regex
/// </summary>
/// <returns>Enumeration of ranges</returns>
public IEnumerable<Range> GetRanges(Regex regex)
{
//get text
string text;
List<Place> charIndexToPlace;
GetText(out text, out charIndexToPlace);
//
foreach (Match m in regex.Matches(text))
{
Range r = new Range(this.tb);
//try get 'range' group, otherwise use group 0
Group group = m.Groups["range"];
if (!group.Success)
group = m.Groups[0];
//
r.Start = charIndexToPlace[group.Index];
r.End = charIndexToPlace[group.Index + group.Length];
yield return r;
}
}
/// <summary>
/// Clear styles of range
/// </summary>
public void ClearStyle(params Style[] styles)
{
try
{
ClearStyle(tb.GetStyleIndexMask(styles));
}
catch { ;}
}
/// <summary>
/// Clear styles of range
/// </summary>
public void ClearStyle(StyleIndex styleIndex)
{
//set code to chars
int fromLine = Math.Min(End.iLine, Start.iLine);
int toLine = Math.Max(End.iLine, Start.iLine);
int fromChar = FromX;
int toChar = ToX;
if (fromLine < 0) return;
//
for (int y = fromLine; y <= toLine; y++)
{
int fromX = y == fromLine ? fromChar : 0;
int toX = y == toLine ? Math.Min(toChar - 1, tb[y].Count - 1) : tb[y].Count - 1;
for (int x = fromX; x <= toX; x++)
{
Char c = tb[y][x];
c.style &= ~styleIndex;
tb[y][x] = c;
}
}
//
tb.Invalidate();
}
/// <summary>
/// Clear folding markers of all lines of range
/// </summary>
public void ClearFoldingMarkers()
{
//set code to chars
int fromLine = Math.Min(End.iLine, Start.iLine);
int toLine = Math.Max(End.iLine, Start.iLine);
if (fromLine < 0) return;
//
for (int y = fromLine; y <= toLine; y++)
tb[y].ClearFoldingMarkers();
//
tb.Invalidate();
}
void OnSelectionChanged()
{
//clear cache
cachedTextVersion = -1;
cachedText = null;
cachedCharIndexToPlace = null;
//
if (tb.Selection == this)
if (updating == 0)
tb.OnSelectionChanged();
}
/// <summary>
/// Starts selection position updating
/// </summary>
public void BeginUpdate()
{
updating++;
}
/// <summary>
/// Ends selection position updating
/// </summary>
public void EndUpdate()
{
updating--;
if (updating == 0)
OnSelectionChanged();
}
public override string ToString()
{
return "Start: " + Start + " End: " + End;
}
/// <summary>
/// Exchanges Start and End if End appears before Start
/// </summary>
public void Normalize()
{
if (Start > End)
Inverse();
}
/// <summary>
/// Exchanges Start and End
/// </summary>
public void Inverse()
{
var temp = start;
start = end;
end = temp;
}
/// <summary>
/// Expands range from first char of Start line to last char of End line
/// </summary>
public void Expand()
{
Normalize();
start = new Place(0, start.iLine);
end = new Place(tb.GetLineLength(end.iLine), end.iLine);
}
IEnumerator<Place> IEnumerable<Place>.GetEnumerator()
{
if (ColumnSelectionMode)
{
foreach(var p in GetEnumerator_ColumnSelectionMode())
yield return p;
yield break;
}
int fromLine = Math.Min(end.iLine, start.iLine);
int toLine = Math.Max(end.iLine, start.iLine);
int fromChar = FromX;
int toChar = ToX;
if (fromLine < 0) yield break;
//
for (int y = fromLine; y <= toLine; y++)
{
int fromX = y == fromLine ? fromChar : 0;
int toX = y == toLine ? Math.Min(toChar - 1, tb[y].Count - 1) : tb[y].Count - 1;
for (int x = fromX; x <= toX; x++)
yield return new Place(x, y);
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return (this as IEnumerable<Place>).GetEnumerator();
}
/// <summary>
/// Chars of range (exclude \n)
/// </summary>
public IEnumerable<Char> Chars
{
get
{
if (ColumnSelectionMode)
{
foreach (var p in GetEnumerator_ColumnSelectionMode())
yield return tb[p];
yield break;
}
int fromLine = Math.Min(end.iLine, start.iLine);
int toLine = Math.Max(end.iLine, start.iLine);
int fromChar = FromX;
int toChar = ToX;
if (fromLine < 0) yield break;
//
for (int y = fromLine; y <= toLine; y++)
{
int fromX = y == fromLine ? fromChar : 0;
int toX = y == toLine ? Math.Min(toChar - 1, tb[y].Count - 1) : tb[y].Count - 1;
var line = tb[y];
for (int x = fromX; x <= toX; x++)
yield return line[x];
}
}
}
/// <summary>
/// Get fragment of text around Start place. Returns maximal matched to pattern fragment.
/// </summary>
/// <param name="allowedSymbolsPattern">Allowed chars pattern for fragment</param>
/// <returns>Range of found fragment</returns>
public Range GetFragment(string allowedSymbolsPattern)
{
return GetFragment(allowedSymbolsPattern, RegexOptions.None);
}
/// <summary>
/// Get fragment of text around Start place. Returns maximal matched to given Style.
/// </summary>
/// <param name="style">Allowed style for fragment</param>
/// <returns>Range of found fragment</returns>
public Range GetFragment(Style style, bool allowLineBreaks)
{
var mask = tb.GetStyleIndexMask(new Style[] { style });
//
Range r = new Range(tb);
r.Start = Start;
//go left, check style
while (r.GoLeftThroughFolded())
{
if (!allowLineBreaks && r.CharAfterStart == '\n')
break;
if (r.Start.iChar < tb.GetLineLength(r.Start.iLine))
if ((tb[r.Start].style & mask) == 0)
{
r.GoRightThroughFolded();
break;
}
}
Place startFragment = r.Start;
r.Start = Start;
//go right, check style
do
{
if (!allowLineBreaks && r.CharAfterStart == '\n')
break;
if (r.Start.iChar < tb.GetLineLength(r.Start.iLine))
if ((tb[r.Start].style & mask) == 0)
break;
} while (r.GoRightThroughFolded());
Place endFragment = r.Start;
return new Range(tb, startFragment, endFragment);
}
/// <summary>
/// Get fragment of text around Start place. Returns maximal mathed to pattern fragment.
/// </summary>
/// <param name="allowedSymbolsPattern">Allowed chars pattern for fragment</param>
/// <returns>Range of found fragment</returns>
public Range GetFragment(string allowedSymbolsPattern, RegexOptions options)
{
Range r = new Range(tb);
r.Start = Start;
Regex regex = new Regex(allowedSymbolsPattern, options);
//go left, check symbols
while (r.GoLeftThroughFolded())
{
if (!regex.IsMatch(r.CharAfterStart.ToString()))
{
r.GoRightThroughFolded();
break;
}
}
Place startFragment = r.Start;
r.Start = Start;
//go right, check symbols
do
{
if (!regex.IsMatch(r.CharAfterStart.ToString()))
break;
} while (r.GoRightThroughFolded()) ;
Place endFragment = r.Start;
return new Range(tb, startFragment, endFragment);
}
bool IsIdentifierChar(char c)
{
return char.IsLetterOrDigit(c) || c == '_';
}
bool IsSpaceChar(char c)
{
return c == ' ' || c == '\t';
}
public void GoWordLeft(bool shift)
{
ColumnSelectionMode = false;
if (!shift && start > end)
{
Start = End;
return;
}
Range range = this.Clone();//to OnSelectionChanged disable
bool wasSpace = false;
while (IsSpaceChar(range.CharBeforeStart))
{
wasSpace = true;
range.GoLeft(shift);
}
bool wasIdentifier = false;
while (IsIdentifierChar(range.CharBeforeStart))
{
wasIdentifier = true;
range.GoLeft(shift);
}
if (!wasIdentifier && (!wasSpace || range.CharBeforeStart != '\n'))
range.GoLeft(shift);
this.Start = range.Start;
this.End = range.End;
if (tb.LineInfos[Start.iLine].VisibleState != VisibleState.Visible)
GoRight(shift);
}
public void GoWordRight(bool shift, bool goToStartOfNextWord = false)
{
ColumnSelectionMode = false;
if (!shift && start < end)
{
Start = End;
return;
}
Range range = this.Clone();//to OnSelectionChanged disable
bool wasNewLine = false;
if (range.CharAfterStart == '\n')
{
range.GoRight(shift);
wasNewLine = true;
}
bool wasSpace = false;
while (IsSpaceChar(range.CharAfterStart))
{
wasSpace = true;
range.GoRight(shift);
}
if (!((wasSpace || wasNewLine) && goToStartOfNextWord))
{
bool wasIdentifier = false;
while (IsIdentifierChar(range.CharAfterStart))
{
wasIdentifier = true;
range.GoRight(shift);
}
if (!wasIdentifier)
range.GoRight(shift);
if (goToStartOfNextWord && !wasSpace)
while (IsSpaceChar(range.CharAfterStart))
range.GoRight(shift);
}
this.Start = range.Start;
this.End = range.End;
if (tb.LineInfos[Start.iLine].VisibleState != VisibleState.Visible)
GoLeft(shift);
}
internal void GoFirst(bool shift)
{
ColumnSelectionMode = false;
start = new Place(0, 0);
if (tb.LineInfos[Start.iLine].VisibleState != VisibleState.Visible)
tb.ExpandBlock(Start.iLine);
if(!shift)
end = start;
OnSelectionChanged();
}
internal void GoLast(bool shift)
{
ColumnSelectionMode = false;
start = new Place(tb[tb.LinesCount - 1].Count, tb.LinesCount-1);
if (tb.LineInfos[Start.iLine].VisibleState != VisibleState.Visible)
tb.ExpandBlock(Start.iLine);
if (!shift)
end = start;
OnSelectionChanged();
}
public static StyleIndex ToStyleIndex(int i)
{
return (StyleIndex)(1 << i);
}
public RangeRect Bounds
{
get
{
int minX = Math.Min(Start.iChar, End.iChar);
int minY = Math.Min(Start.iLine, End.iLine);
int maxX = Math.Max(Start.iChar, End.iChar);
int maxY = Math.Max(Start.iLine, End.iLine);
return new RangeRect(minY, minX, maxY, maxX);
}
}
public IEnumerable<Range> GetSubRanges(bool includeEmpty)
{
if (!ColumnSelectionMode)
{
yield return this;
yield break;
}
var rect = Bounds;
for (int y = rect.iStartLine; y <= rect.iEndLine; y++)
{
if (rect.iStartChar > tb[y].Count && !includeEmpty)
continue;
var r = new Range(tb, rect.iStartChar, y, Math.Min(rect.iEndChar, tb[y].Count), y);
yield return r;
}
}
/// <summary>
/// Range is readonly?
/// This property return True if any char of the range contains ReadOnlyStyle.
/// Set this property to True/False to mark chars of the range as Readonly/Writable.
/// </summary>
public bool ReadOnly
{
get
{
if (tb.ReadOnly) return true;
ReadOnlyStyle readonlyStyle = null;
foreach (var style in tb.Styles)
if (style is ReadOnlyStyle)
{
readonlyStyle = (ReadOnlyStyle)style;
break;
}
if (readonlyStyle != null)
{
var si = ToStyleIndex(tb.GetStyleIndex(readonlyStyle));
if (IsEmpty)
{
//check previous and next chars
var line = tb[start.iLine];
if (columnSelectionMode)
{
foreach (var sr in GetSubRanges(false))
{
line = tb[sr.start.iLine];
if (sr.start.iChar < line.Count && sr.start.iChar > 0)
{
var left = line[sr.start.iChar - 1];
var right = line[sr.start.iChar];
if ((left.style & si) != 0 &&
(right.style & si) != 0) return true;//we are between readonly chars
}
}
}else
if (start.iChar < line.Count && start.iChar > 0)
{
var left = line[start.iChar - 1];
var right = line[start.iChar];
if ((left.style & si) != 0 &&
(right.style & si) != 0) return true;//we are between readonly chars
}
}
else
foreach (Char c in Chars)
if ((c.style & si) != 0)//found char with ReadonlyStyle
return true;
}
return false;
}
set
{
//find exists ReadOnlyStyle of style buffer
ReadOnlyStyle readonlyStyle = null;
foreach (var style in tb.Styles)
if (style is ReadOnlyStyle)
{
readonlyStyle = (ReadOnlyStyle)style;
break;
}
//create ReadOnlyStyle
if(readonlyStyle == null)
readonlyStyle = new ReadOnlyStyle();
//set/clear style
if (value)
SetStyle(readonlyStyle);
else
ClearStyle(readonlyStyle);
}
}
/// <summary>
/// Is char before range readonly
/// </summary>
/// <returns></returns>
public bool IsReadOnlyLeftChar()
{
if (tb.ReadOnly) return true;
var r = Clone();
r.Normalize();
if (r.start.iChar == 0) return false;
if (ColumnSelectionMode)
r.GoLeft_ColumnSelectionMode();
else
r.GoLeft(true);
return r.ReadOnly;
}
/// <summary>
/// Is char after range readonly
/// </summary>
/// <returns></returns>
public bool IsReadOnlyRightChar()
{
if (tb.ReadOnly) return true;
var r = Clone();
r.Normalize();
if (r.end.iChar >= tb[end.iLine].Count) return false;
if (ColumnSelectionMode)
r.GoRight_ColumnSelectionMode();
else
r.GoRight(true);
return r.ReadOnly;
}
public IEnumerable<Place> GetPlacesCyclic(Place startPlace, bool backward = false)
{
if (backward)
{
var r = new Range(this.tb, startPlace, startPlace);
while (r.GoLeft() && r.start >= Start)
{
if (r.Start.iChar < tb[r.Start.iLine].Count)
yield return r.Start;
}
r = new Range(this.tb, End, End);
while (r.GoLeft() && r.start >= startPlace)
{
if (r.Start.iChar < tb[r.Start.iLine].Count)
yield return r.Start;
}
}
else
{
var r = new Range(this.tb, startPlace, startPlace);
if (startPlace < End)
do
{
if (r.Start.iChar < tb[r.Start.iLine].Count)
yield return r.Start;
} while (r.GoRight());
r = new Range(this.tb, Start, Start);
if (r.Start < startPlace)
do
{
if (r.Start.iChar < tb[r.Start.iLine].Count)
yield return r.Start;
} while (r.GoRight() && r.Start < startPlace);
}
}
#region ColumnSelectionMode
private Range GetIntersectionWith_ColumnSelectionMode(Range range)
{
if (range.Start.iLine != range.End.iLine)
return new Range(tb, Start, Start);
var rect = Bounds;
if (range.Start.iLine < rect.iStartLine || range.Start.iLine > rect.iEndLine)
return new Range(tb, Start, Start);
return new Range(tb, rect.iStartChar, range.Start.iLine, rect.iEndChar, range.Start.iLine).GetIntersectionWith(range);
}
private bool GoRightThroughFolded_ColumnSelectionMode()
{
var boundes = Bounds;
var endOfLines = true;
for (int iLine = boundes.iStartLine; iLine <= boundes.iEndLine; iLine++)
if(boundes.iEndChar < tb[iLine].Count)
{
endOfLines = false;
break;
}
if (endOfLines)
return false;
var start = Start;
var end = End;
start.Offset(1, 0);
end.Offset(1, 0);
BeginUpdate();
Start = start;
End = end;
EndUpdate();
return true;
}
private IEnumerable<Place> GetEnumerator_ColumnSelectionMode()
{
var bounds = Bounds;
if (bounds.iStartLine < 0) yield break;
//
for (int y = bounds.iStartLine; y <= bounds.iEndLine; y++)
{
for (int x = bounds.iStartChar; x < bounds.iEndChar; x++)
{
if (x < tb[y].Count)
yield return new Place(x, y);
}
}
}
private string Text_ColumnSelectionMode
{
get
{
StringBuilder sb = new StringBuilder();
var bounds = Bounds;
if (bounds.iStartLine < 0) return "";
//
for (int y = bounds.iStartLine; y <= bounds.iEndLine; y++)
{
for (int x = bounds.iStartChar; x < bounds.iEndChar; x++)
{
if (x < tb[y].Count)
sb.Append(tb[y][x].c);
}
if (bounds.iEndLine != bounds.iStartLine && y != bounds.iEndLine)
sb.AppendLine();
}
return sb.ToString();
}
}
internal void GoDown_ColumnSelectionMode()
{
var iLine = tb.FindNextVisibleLine(End.iLine);
End = new Place(End.iChar, iLine);
}
internal void GoUp_ColumnSelectionMode()
{
var iLine = tb.FindPrevVisibleLine(End.iLine);
End = new Place(End.iChar, iLine);
}
internal void GoRight_ColumnSelectionMode()
{
End = new Place(End.iChar + 1, End.iLine);
}
internal void GoLeft_ColumnSelectionMode()
{
if (End.iChar > 0)
End = new Place(End.iChar - 1, End.iLine);
}
#endregion
}
public struct RangeRect
{
public RangeRect(int iStartLine, int iStartChar, int iEndLine, int iEndChar)
{
this.iStartLine = iStartLine;
this.iStartChar = iStartChar;
this.iEndLine = iEndLine;
this.iEndChar = iEndChar;
}
public int iStartLine;
public int iStartChar;
public int iEndLine;
public int iEndChar;
}
}