// *
// * Copyright (C) 2008 Roger Alsing : http://www.RogerAlsing.com
// *
// * This library is free software; you can redistribute it and/or modify it
// * under the terms of the GNU Lesser General Public License 2.1 or later, as
// * published by the Free Software Foundation. See the included license.txt
// * or http://www.gnu.org/copyleft/lesser.html for details.
// *
// *
// * contributions by Sebastian Faltoni
using System.Collections;
using System.Drawing;
namespace Alsing.SourceCode
{
///
/// Parser state of a row
///
public enum RowState
{
///
/// the row is not parsed
///
NotParsed = 0,
///
/// the row is span parsed
///
SpanParsed = 1,
///
/// the row is both span and keyword parsed
///
AllParsed = 2
}
///
/// The row class represents a row in a SyntaxDocument
///
public sealed class Row : IEnumerable
{
#region General Declarations
private RowState _RowState = RowState.NotParsed;
///
/// The owner document
///
public SyntaxDocument Document;
///
/// The first span that terminates on this row.
///
public Span endSpan;
///
/// Segments that ends in this row
///
public SpanList endSpans = new SpanList();
///
/// For public use only
///
public int Expansion_EndChar;
///
///
///
public Span expansion_EndSpan;
///
/// For public use only
///
public int Expansion_PixelEnd;
///
/// For public use only
///
public int Expansion_PixelStart;
///
/// For public use only
///
public int Expansion_StartChar;
///
///
///
public Span expansion_StartSpan;
public WordList FormattedWords = new WordList();
///
/// Collection of Image indices assigned to a row.
///
///
/// Add an image to the current row.
///
/// MySyntaxBox.Caret.CurrentRow.Images.Add(3);
///
///
public ImageIndexList Images = new ImageIndexList();
///
/// For public use only
///
public int Indent; //value indicating how much this line should be indented (c style)
///
/// Returns true if the row is in the owner documents keyword parse queue
///
public bool InKeywordQueue; //is this line in the parseQueue?
///
/// Returns true if the row is in the owner documents parse queue
///
public bool InQueue; //is this line in the parseQueue?
private bool mBookmarked; //is this line bookmarked?
private bool mBreakpoint; //Does this line have a breakpoint?
private string mText = "";
internal WordList words = new WordList();
///
/// The first collapsable span on this row.
///
public Span startSpan;
///
/// Segments that start on this row
///
public SpanList startSpans = new SpanList();
///
/// Object tag for storage of custom user data..
///
///
/// Assign custom data to a row
///
/// //custom data class
/// class CustomData{
/// public int abc=123;
/// publci string def="abc";
/// }
///
/// ...
///
/// //assign custom data to a row
/// Row MyRow=MySyntaxBox.Caret.CurrentRow;
/// CustomData MyData=new CustomData();
/// MyData.abc=1337;
/// MyRow.Tag=MyData;
///
/// ...
///
/// //read custom data from a row
/// Row MyRow=MySyntaxBox.Caret.CurrentRow;
/// if (MyRow.Tag != null){
/// CustomData MyData=(CustomData)MyRow.Tag;
/// if (MyData.abc==1337){
/// //Do something...
/// }
/// }
///
///
///
///
public object Tag;
#region PUBLIC PROPERTY BACKCOLOR
private Color _BackColor = Color.Transparent;
public Color BackColor
{
get { return _BackColor; }
set { _BackColor = value; }
}
#endregion
public int Depth
{
get
{
int i = 0;
Span s = startSpan;
while (s != null)
{
if (s.Scope != null && s.Scope.CauseIndent)
i++;
s = s.Parent;
}
// if (i>0)
// i--;
if (ShouldOutdent)
i--;
return i;
}
}
public bool ShouldOutdent
{
get
{
if (startSpan.EndRow == this)
{
if (startSpan.Scope.CauseIndent)
return true;
}
return false;
}
}
///
/// The parse state of this row
///
///
/// Test if the current row is fully parsed.
///
/// if (MySyntaxBox.Caret.CurrentRow.RowState==RowState.AllParsed)
/// {
/// //do something
/// }
///
///
public RowState RowState
{
get { return _RowState; }
set
{
if (value == _RowState)
return;
if (value == RowState.SpanParsed && !InKeywordQueue)
{
Document.KeywordQueue.Add(this);
InKeywordQueue = true;
}
if ((value == RowState.AllParsed || value == RowState.NotParsed) && InKeywordQueue)
{
Document.KeywordQueue.Remove(this);
InKeywordQueue = false;
}
_RowState = value;
}
}
#endregion
///
/// Gets or Sets if this row has a bookmark or not.
///
public bool Bookmarked
{
get { return mBookmarked; }
set
{
mBookmarked = value;
if (value)
Document.InvokeBookmarkAdded(this);
else
Document.InvokeBookmarkRemoved(this);
Document.InvokeChange();
}
}
///
/// Gets or Sets if this row has a breakpoint or not.
///
public bool Breakpoint
{
get { return mBreakpoint; }
set
{
mBreakpoint = value;
if (value)
Document.InvokeBreakPointAdded(this);
else
Document.InvokeBreakPointRemoved(this);
Document.InvokeChange();
}
}
///
/// Returns the number of words in the row.
/// (this only applied if the row is fully parsed)
///
public int Count
{
get { return words.Count; }
}
///
/// Gets or Sets the text of the row.
///
public string Text
{
get { return mText; }
set
{
bool ParsePreview = false;
if (mText != value)
{
ParsePreview = true;
Document.Modified = true;
}
mText = value;
if (Document != null)
{
if (ParsePreview)
{
Document.Parser.ParsePreviewLine(Document.IndexOf(this));
Document.OnApplyFormatRanges(this);
}
AddToParseQueue();
}
}
}
///
/// Return the Word object at the specified index.
///
public Word this[int index]
{
get
{
if (index >= 0)
return words[index];
return new Word();
}
}
public int StartWordIndex
{
get
{
if (expansion_StartSpan == null)
return 0;
// if (this.expansion_StartSpan.StartRow != this)
// return 0;
Word w = expansion_StartSpan.StartWord;
int i = 0;
foreach (Word wo in this)
{
if (wo == w)
break;
i += wo.Text.Length;
}
return i;
}
}
public Word FirstNonWsWord
{
get
{
foreach (Word w in this)
{
if (w.Type == WordType.Word)
return w;
}
return null;
}
}
///
/// Returns the index of this row in the owner SyntaxDocument.
///
public int Index
{
get { return Document.IndexOf(this); }
}
///
/// Returns the visible index of this row in the owner SyntaxDocument
///
public int VisibleIndex
{
get
{
int i = Document.VisibleRows.IndexOf(this);
if (i == -1)
{
if (startSpan != null &&
startSpan.StartRow != null &&
startSpan.StartRow != this)
return startSpan.StartRow.VisibleIndex;
return Index;
}
return Document.VisibleRows.IndexOf(this);
}
}
///
/// Returns the next visible row.
///
public Row NextVisibleRow
{
get
{
int i = VisibleIndex;
if (i > Document.VisibleRows.Count)
return null;
if (i + 1 < Document.VisibleRows.Count)
{
return Document.VisibleRows[i + 1];
}
return null;
}
}
///
/// Returns the next row
///
public Row NextRow
{
get
{
int i = Index;
if (i + 1 <= Document.Lines.Length - 1)
return Document[i + 1];
return null;
}
}
///
/// Returns the first visible row before this row.
///
public Row PrevVisibleRow
{
get
{
int i = VisibleIndex;
if (i < 0)
return null;
if (i - 1 >= 0)
return Document.VisibleRows[i - 1];
return null;
}
}
///
/// Returns true if the row is collapsed
///
public bool IsCollapsed
{
get
{
if (expansion_StartSpan != null)
if (expansion_StartSpan.Expanded == false)
return true;
return false;
}
}
///
/// Returns true if this row is the last part of a collepsed span
///
public bool IsCollapsedEndPart
{
get
{
if (expansion_EndSpan != null)
if (expansion_EndSpan.Expanded == false)
return true;
return false;
}
}
///
/// Returns true if this row can fold
///
public bool CanFold
{
get
{
return (expansion_StartSpan != null && expansion_StartSpan.EndRow != null &&
Document.IndexOf(expansion_StartSpan.EndRow) != 0);
}
}
///
/// Gets or Sets if this row is expanded.
///
public bool Expanded
{
get
{
if (CanFold)
{
return (expansion_StartSpan.Expanded);
}
return false;
}
set
{
if (CanFold)
{
expansion_StartSpan.Expanded = value;
}
}
}
public string ExpansionText
{
get { return expansion_StartSpan.Scope.ExpansionText; }
set
{
Scope oScope = expansion_StartSpan.Scope;
var oNewScope = new Scope
{
CaseSensitive = oScope.CaseSensitive,
CauseIndent = oScope.CauseIndent,
DefaultExpanded = oScope.DefaultExpanded,
EndPatterns = oScope.EndPatterns,
NormalizeCase = oScope.NormalizeCase,
Parent = oScope.Parent,
spawnSpanOnEnd = oScope.spawnSpanOnEnd,
spawnSpanOnStart = oScope.spawnSpanOnStart,
Start = oScope.Start,
Style = oScope.Style,
ExpansionText = value
};
expansion_StartSpan.Scope = oNewScope;
Document.InvokeChange();
}
}
///
/// Returns true if this row is the end part of a collapsable span
///
public bool CanFoldEndPart
{
get { return (expansion_EndSpan != null); }
}
///
/// For public use only
///
public bool HasExpansionLine
{
get { return (endSpan.Parent != null); }
}
///
/// Returns the last row of a collapsable span
/// (this only applies if this row is the start row of the span)
///
public Row Expansion_EndRow
{
get
{
if (CanFold)
return expansion_StartSpan.EndRow;
return this;
}
}
///
/// Returns the first row of a collapsable span
/// (this only applies if this row is the last row of the span)
///
public Row Expansion_StartRow
{
get
{
if (CanFoldEndPart)
return expansion_EndSpan.StartRow;
return this;
}
}
///
/// For public use only
///
public Row VirtualCollapsedRow
{
get
{
var r = new Row();
foreach (Word w in this)
{
if (expansion_StartSpan == w.Span)
break;
r.Add(w);
}
Word wo = r.Add(CollapsedText);
wo.Style = new TextStyle {BackColor = Color.Silver, ForeColor = Color.DarkBlue, Bold = true};
bool found = false;
if (Expansion_EndRow != null)
{
foreach (Word w in Expansion_EndRow)
{
if (found)
r.Add(w);
if (w == Expansion_EndRow.expansion_EndSpan.EndWord)
found = true;
}
}
return r;
}
}
///
/// Returns the text that should be displayed if the row is collapsed.
///
public string CollapsedText
{
get
{
string str = "";
int pos = 0;
foreach (Word w in this)
{
pos += w.Text.Length;
if (w.Span == expansion_StartSpan)
{
str = Text.Substring(pos).Trim();
break;
}
}
if (expansion_StartSpan.Scope.ExpansionText != "")
str = expansion_StartSpan.Scope.ExpansionText.Replace("***", str);
return str;
}
}
///
/// Returns the row before this row.
///
public Row PrevRow
{
get
{
int i = Index;
if (i - 1 >= 0)
return Document[i - 1];
return null;
}
}
#region IEnumerable Members
///
/// Get the Word enumerator for this row
///
///
public IEnumerator GetEnumerator()
{
return words.GetEnumerator();
}
#endregion
public void Clear()
{
words.Clear();
}
///
/// If the row is hidden inside a collapsed span , call this method to make the collapsed segments expanded.
///
public void EnsureVisible()
{
if (RowState == RowState.NotParsed)
return;
Span seg = startSpan;
while (seg != null)
{
seg.Expanded = true;
seg = seg.Parent;
}
Document.ResetVisibleRows();
}
public Word Add(string text)
{
var xw = new Word {Row = this, Text = text};
words.Add(xw);
return xw;
}
///
/// Adds this row to the parse queue
///
public void AddToParseQueue()
{
if (!InQueue)
Document.ParseQueue.Add(this);
InQueue = true;
RowState = RowState.NotParsed;
}
///
/// Assigns a new text to the row.
///
///
public void SetText(string text)
{
Document.StartUndoCapture();
var tp = new TextPoint(0, Index);
var tr = new TextRange {FirstColumn = 0, FirstRow = tp.Y, LastColumn = Text.Length, LastRow = tp.Y};
Document.StartUndoCapture();
//delete the current line
Document.PushUndoBlock(UndoAction.DeleteRange, Document.GetRange(tr), tr.FirstColumn, tr.FirstRow);
//alter the text
Document.PushUndoBlock(UndoAction.InsertRange, text, tp.X, tp.Y);
Text = text;
Document.EndUndoCapture();
Document.InvokeChange();
}
///
/// Call this method to make all words match the case of their patterns.
/// (this only applies if the row is fully parsed)
///
public void MatchCase()
{
string s = "";
foreach (Word w in words)
{
s = s + w.Text;
}
mText = s;
}
///
/// Force a span parse on the row.
///
public void Parse()
{
Document.ParseRow(this);
}
///
/// Forces the parser to parse this row directly
///
/// true if keywords and operators should be parsed
public void Parse(bool ParseKeywords)
{
Document.ParseRow(this, ParseKeywords);
}
public void SetExpansionSegment()
{
expansion_StartSpan = null;
expansion_EndSpan = null;
foreach (Span s in startSpans)
{
if (!endSpans.Contains(s))
{
expansion_StartSpan = s;
break;
}
}
foreach (Span s in endSpans)
{
if (!startSpans.Contains(s))
{
expansion_EndSpan = s;
break;
}
}
if (expansion_EndSpan != null)
expansion_StartSpan = null;
}
///
/// Returns the whitespace string at the begining of this row.
///
/// a string containing the whitespace at the begining of this row
public string GetLeadingWhitespace()
{
string s = mText;
int i;
s = s.Replace(" ", " ");
for (i = 0; i < s.Length; i++)
{
if (s.Substring(i, 1) == " ") {}
else
{
break;
}
}
return mText.Substring(0, i);
}
public string GetVirtualLeadingWhitespace()
{
int i = StartWordIndex;
string ws = "";
foreach (char c in Text)
{
if (c == '\t')
ws += c;
else
ws += ' ';
i--;
if (i <= 0)
break;
}
return ws;
}
///
/// Adds a word object to this row
///
/// Word object
public void Add(Word word)
{
word.Row = this;
words.Add(word);
}
///
/// Returns the index of a specific Word object
///
/// Word object to find
/// index of the word in the row
public int IndexOf(Word word)
{
return words.IndexOf(word);
}
///
/// For public use only
///
///
///
///
///
public Word FindRightWordByPatternList(PatternList PatternList, Word StartWord, bool IgnoreStartWord)
{
int i = StartWord.Index;
if (IgnoreStartWord)
i++;
while (i < words.Count)
{
Word w = this[i];
if (w.Pattern != null)
{
if (w.Pattern.Parent != null)
{
if (w.Pattern.Parent == PatternList && w.Type != WordType.Space && w.Type != WordType.Tab)
{
return w;
}
}
}
i++;
}
return null;
}
///
/// For public use only
///
///
///
///
///
public Word FindRightWordByPatternListName(string PatternListName, Word StartWord, bool IgnoreStartWord)
{
int i = StartWord.Index;
if (IgnoreStartWord)
i++;
while (i < words.Count)
{
Word w = this[i];
if (w.Pattern != null)
{
if (w.Pattern.Parent != null)
{
if (w.Pattern.Parent.Name == PatternListName && w.Type != WordType.Space &&
w.Type != WordType.Tab)
{
return w;
}
}
}
i++;
}
return null;
}
///
/// For public use only
///
///
///
///
///
public Word FindLeftWordByPatternList(PatternList PatternList, Word StartWord, bool IgnoreStartWord)
{
int i = StartWord.Index;
if (IgnoreStartWord)
i--;
while (i >= 0)
{
Word w = this[i];
if (w.Pattern != null)
{
if (w.Pattern.Parent != null)
{
if (w.Pattern.Parent == PatternList && w.Type != WordType.Space && w.Type != WordType.Tab)
{
return w;
}
}
}
i--;
}
return null;
}
///
/// For public use only
///
///
///
///
///
public Word FindLeftWordByPatternListName(string PatternListName, Word StartWord, bool IgnoreStartWord)
{
int i = StartWord.Index;
if (IgnoreStartWord)
i--;
while (i >= 0)
{
Word w = this[i];
if (w.Pattern != null)
{
if (w.Pattern.Parent != null)
{
if (w.Pattern.Parent.Name == PatternListName && w.Type != WordType.Space &&
w.Type != WordType.Tab)
{
return w;
}
}
}
i--;
}
return null;
}
///
/// For public use only
///
///
///
///
///
public Word FindLeftWordByBlockType(SpanDefinition spanDefinition, Word StartWord, bool IgnoreStartWord)
{
int i = StartWord.Index;
if (IgnoreStartWord)
i--;
while (i >= 0)
{
Word w = this[i];
if (w.Span.spanDefinition == spanDefinition && w.Type != WordType.Space && w.Type != WordType.Tab)
{
return w;
}
i--;
}
return null;
}
///
/// For public use only
///
///
///
///
///
public Word FindRightWordByBlockType(SpanDefinition spanDefinition, Word StartWord, bool IgnoreStartWord)
{
int i = StartWord.Index;
if (IgnoreStartWord)
i++;
while (i < words.Count)
{
Word w = this[i];
if (w.Span.spanDefinition == spanDefinition && w.Type != WordType.Space && w.Type != WordType.Tab)
{
return w;
}
i++;
}
return null;
}
///
/// For public use only
///
///
///
///
///
public Word FindLeftWordByBlockTypeName(string BlockTypeName, Word StartWord, bool IgnoreStartWord)
{
int i = StartWord.Index;
if (IgnoreStartWord)
i--;
while (i >= 0)
{
Word w = this[i];
if (w.Span.spanDefinition.Name == BlockTypeName && w.Type != WordType.Space && w.Type != WordType.Tab)
{
return w;
}
i--;
}
return null;
}
///
/// For public use only
///
///
///
///
///
public Word FindRightWordByBlockTypeName(string BlockTypeName, Word StartWord, bool IgnoreStartWord)
{
int i = StartWord.Index;
if (IgnoreStartWord)
i++;
while (i < words.Count)
{
Word w = this[i];
if (w.Span.spanDefinition.Name == BlockTypeName && w.Type != WordType.Space && w.Type != WordType.Tab)
{
return w;
}
i++;
}
return null;
}
}
}