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.
935 lines
39 KiB
935 lines
39 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.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Text;
|
|
|
|
namespace ICSharpCode.TextEditor.Document
|
|
{
|
|
public class DefaultHighlightingStrategy : IHighlightingStrategyUsingRuleSets
|
|
{
|
|
protected HighlightRuleSet activeRuleSet;
|
|
protected Span activeSpan;
|
|
protected int currentLength;
|
|
|
|
// Line state variable
|
|
protected LineSegment currentLine;
|
|
protected int currentLineNumber;
|
|
|
|
// Line scanning state variables
|
|
protected int currentOffset;
|
|
|
|
// Span stack state variable
|
|
protected SpanStack currentSpanStack;
|
|
|
|
private HighlightRuleSet defaultRuleSet;
|
|
private Dictionary<string, HighlightColor> environmentColors;
|
|
|
|
// Span state variables
|
|
protected bool inSpan;
|
|
|
|
// Default environment, can be overridden
|
|
public static readonly DefaultHighlightingStrategy Default = new();
|
|
|
|
public DefaultHighlightingStrategy(string name)
|
|
{
|
|
Name = name;
|
|
|
|
environmentColors = Default.environmentColors;
|
|
DigitColor = Default.DigitColor;
|
|
DefaultTextColor = Default.DefaultTextColor;
|
|
}
|
|
|
|
private DefaultHighlightingStrategy() {}
|
|
|
|
static DefaultHighlightingStrategy()
|
|
{
|
|
Default.Name = "Default";
|
|
Default.DigitColor = new HighlightColor(nameof(SystemColors.WindowText), bold: false, italic: false);
|
|
Default.DefaultTextColor = new HighlightColor(nameof(SystemColors.WindowText), bold: false, italic: false);
|
|
|
|
// set small 'default color environment'
|
|
// colors that are not system colors will be adapted to the theme
|
|
Default.environmentColors = new Dictionary<string, HighlightColor>
|
|
{
|
|
["Default"] = new HighlightBackground(nameof(SystemColors.WindowText), nameof(SystemColors.Window), bold: false, italic: false),
|
|
["Selection"] = new HighlightColor(SystemColors.WindowText, Color.FromArgb(0xc3, 0xc3, 0xff), bold: false, italic: false, adaptable: true),
|
|
["VRuler"] = new HighlightColor(nameof(SystemColors.ControlLight), nameof(SystemColors.Window), bold: false, italic: false),
|
|
["InvalidLines"] = new HighlightColor(Color.FromArgb(0xB6, 0xB6, 0xC0), bold: false, italic: false),
|
|
["CaretMarker"] = new HighlightColor(nameof(SystemColors.MenuBar), bold: false, italic: false),
|
|
["CaretLine"] = new HighlightBackground(nameof(SystemColors.ControlLight), nameof(SystemColors.Window), bold: false, italic: false),
|
|
["LineNumbers"] = new HighlightBackground(nameof(SystemColors.GrayText), nameof(SystemColors.Window), bold: false, italic: false),
|
|
["FoldLine"] = new HighlightColor(nameof(SystemColors.ControlDark), bold: false, italic: false),
|
|
["FoldMarker"] = new HighlightColor(nameof(SystemColors.WindowText), nameof(SystemColors.Window), bold: false, italic: false),
|
|
["SelectedFoldLine"] = new HighlightColor(nameof(SystemColors.WindowText), bold: false, italic: false),
|
|
["EOLMarkers"] = new HighlightColor(Color.FromArgb(0xca, 0xca, 0xd2), bold: false, italic: false),
|
|
["SpaceMarkers"] = new HighlightColor(Color.FromArgb(0xB6, 0xB6, 0xC0), bold: false, italic: false),
|
|
["TabMarkers"] = new HighlightColor(Color.FromArgb(0xB6, 0xB6, 0xC0), bold: false, italic: false)
|
|
};
|
|
}
|
|
|
|
public HighlightColor DigitColor { get; set; }
|
|
|
|
public IEnumerable<KeyValuePair<string, HighlightColor>> EnvironmentColors => environmentColors;
|
|
|
|
public List<HighlightRuleSet> Rules { get; private set; } = new List<HighlightRuleSet>();
|
|
|
|
// internal void SetDefaultColor(HighlightBackground color)
|
|
// {
|
|
// return (HighlightColor)environmentColors[name];
|
|
// defaultColor = color;
|
|
// }
|
|
|
|
public HighlightColor DefaultTextColor { get; private set; }
|
|
|
|
public Dictionary<string, string> Properties { get; private set; } = new Dictionary<string, string>();
|
|
|
|
public string Name { get; private set; }
|
|
|
|
public string[] Extensions { set; get; }
|
|
|
|
public HighlightColor GetColorFor(string name)
|
|
{
|
|
if (environmentColors.TryGetValue(name, out var color))
|
|
return color;
|
|
return DefaultTextColor;
|
|
}
|
|
|
|
public HighlightColor GetColor(IDocument document, LineSegment currentSegment, int currentOffset, int currentLength)
|
|
{
|
|
return GetColor(defaultRuleSet, document, currentSegment, currentOffset, currentLength);
|
|
}
|
|
|
|
public HighlightRuleSet GetRuleSet(Span aSpan)
|
|
{
|
|
if (aSpan == null)
|
|
return defaultRuleSet;
|
|
|
|
if (aSpan.RuleSet != null)
|
|
{
|
|
if (aSpan.RuleSet.Reference != null)
|
|
return aSpan.RuleSet.Highlighter.GetRuleSet(span: null);
|
|
|
|
return aSpan.RuleSet;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public virtual void MarkTokens(IDocument document)
|
|
{
|
|
if (Rules.Count == 0)
|
|
return;
|
|
|
|
var lineNumber = 0;
|
|
|
|
while (lineNumber < document.TotalNumberOfLines)
|
|
{
|
|
var previousLine = lineNumber > 0 ? document.GetLineSegment(lineNumber - 1) : null;
|
|
if (lineNumber >= document.LineSegmentCollection.Count)
|
|
break; // then the last line is not in the collection :)
|
|
|
|
currentSpanStack = previousLine?.HighlightSpanStack?.Clone();
|
|
|
|
if (currentSpanStack != null)
|
|
{
|
|
while (!currentSpanStack.IsEmpty && currentSpanStack.Peek().StopEOL)
|
|
currentSpanStack.Pop();
|
|
if (currentSpanStack.IsEmpty) currentSpanStack = null;
|
|
}
|
|
|
|
currentLine = document.LineSegmentCollection[lineNumber];
|
|
|
|
if (currentLine.Length == -1)
|
|
return;
|
|
|
|
currentLineNumber = lineNumber;
|
|
var words = ParseLine(document);
|
|
// Alex: clear old words
|
|
currentLine.Words?.Clear();
|
|
currentLine.Words = words;
|
|
currentLine.HighlightSpanStack = currentSpanStack == null || currentSpanStack.IsEmpty ? null : currentSpanStack;
|
|
|
|
++lineNumber;
|
|
}
|
|
|
|
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
|
|
document.CommitUpdate();
|
|
currentLine = null;
|
|
}
|
|
|
|
public virtual void MarkTokens(IDocument document, List<LineSegment> inputLines)
|
|
{
|
|
if (Rules.Count == 0)
|
|
return;
|
|
|
|
var processedLines = new Dictionary<LineSegment, bool>();
|
|
|
|
var spanChanged = false;
|
|
var documentLineSegmentCount = document.LineSegmentCollection.Count;
|
|
|
|
foreach (var lineToProcess in inputLines)
|
|
if (!processedLines.ContainsKey(lineToProcess))
|
|
{
|
|
var lineNumber = lineToProcess.LineNumber;
|
|
var processNextLine = true;
|
|
|
|
if (lineNumber != -1)
|
|
while (processNextLine && lineNumber < documentLineSegmentCount)
|
|
{
|
|
processNextLine = MarkTokensInLine(document, lineNumber, ref spanChanged);
|
|
processedLines[currentLine] = true;
|
|
++lineNumber;
|
|
}
|
|
}
|
|
|
|
if (spanChanged || inputLines.Count > 20)
|
|
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
|
|
else
|
|
foreach (var lineToProcess in inputLines)
|
|
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, lineToProcess.LineNumber));
|
|
document.CommitUpdate();
|
|
currentLine = null;
|
|
}
|
|
|
|
protected void ImportSettingsFrom(DefaultHighlightingStrategy source)
|
|
{
|
|
if (source == null)
|
|
throw new ArgumentNullException(nameof(source));
|
|
Properties = source.Properties;
|
|
Extensions = source.Extensions;
|
|
DigitColor = source.DigitColor;
|
|
defaultRuleSet = source.defaultRuleSet;
|
|
Name = source.Name;
|
|
Rules = source.Rules;
|
|
environmentColors = source.environmentColors;
|
|
DefaultTextColor = source.DefaultTextColor;
|
|
}
|
|
|
|
public HighlightRuleSet FindHighlightRuleSet(string name)
|
|
{
|
|
foreach (var ruleSet in Rules)
|
|
if (ruleSet.Name == name)
|
|
return ruleSet;
|
|
return null;
|
|
}
|
|
|
|
public void AddRuleSet(HighlightRuleSet aRuleSet)
|
|
{
|
|
var existing = FindHighlightRuleSet(aRuleSet.Name);
|
|
if (existing != null)
|
|
existing.MergeFrom(aRuleSet);
|
|
else
|
|
Rules.Add(aRuleSet);
|
|
}
|
|
|
|
public void ResolveReferences()
|
|
{
|
|
// Resolve references from Span definitions to RuleSets
|
|
ResolveRuleSetReferences();
|
|
// Resolve references from RuleSet defintitions to Highlighters defined in an external mode file
|
|
ResolveExternalReferences();
|
|
}
|
|
|
|
private void ResolveRuleSetReferences()
|
|
{
|
|
foreach (var ruleSet in Rules)
|
|
{
|
|
if (ruleSet.Name == null)
|
|
defaultRuleSet = ruleSet;
|
|
|
|
foreach (Span aSpan in ruleSet.Spans)
|
|
if (aSpan.Rule != null)
|
|
{
|
|
var found = false;
|
|
foreach (var refSet in Rules)
|
|
if (refSet.Name == aSpan.Rule)
|
|
{
|
|
found = true;
|
|
aSpan.RuleSet = refSet;
|
|
break;
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
aSpan.RuleSet = null;
|
|
throw new HighlightingDefinitionInvalidException("The RuleSet " + aSpan.Rule + " could not be found in mode definition " + Name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aSpan.RuleSet = null;
|
|
}
|
|
}
|
|
|
|
if (defaultRuleSet == null)
|
|
throw new HighlightingDefinitionInvalidException("No default RuleSet is defined for mode definition " + Name);
|
|
}
|
|
|
|
private void ResolveExternalReferences()
|
|
{
|
|
foreach (var ruleSet in Rules)
|
|
{
|
|
ruleSet.Highlighter = this;
|
|
if (ruleSet.Reference != null)
|
|
{
|
|
var highlighter = HighlightingManager.Manager.FindHighlighter(ruleSet.Reference);
|
|
|
|
if (highlighter == null)
|
|
throw new HighlightingDefinitionInvalidException("The mode defintion " + ruleSet.Reference + " which is refered from the " + Name + " mode definition could not be found");
|
|
if (highlighter is IHighlightingStrategyUsingRuleSets sets)
|
|
ruleSet.Highlighter = sets;
|
|
else
|
|
throw new HighlightingDefinitionInvalidException("The mode defintion " + ruleSet.Reference + " which is refered from the " + Name + " mode definition does not implement IHighlightingStrategyUsingRuleSets");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetColorFor(string name, HighlightColor color)
|
|
{
|
|
if (name == "Default")
|
|
DefaultTextColor = new HighlightColor(color.Color, color.Bold, color.Italic, adaptable: color.Adaptable);
|
|
environmentColors[name] = color;
|
|
}
|
|
|
|
protected virtual HighlightColor GetColor(HighlightRuleSet ruleSet, IDocument document, LineSegment currentSegment, int currentOffset, int currentLength)
|
|
{
|
|
if (ruleSet != null)
|
|
{
|
|
if (ruleSet.Reference != null)
|
|
return ruleSet.Highlighter.GetColor(document, currentSegment, currentOffset, currentLength);
|
|
|
|
return (HighlightColor)ruleSet.KeyWords[document, currentSegment, currentOffset, currentLength];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private bool MarkTokensInLine(IDocument document, int lineNumber, ref bool spanChanged)
|
|
{
|
|
currentLineNumber = lineNumber;
|
|
var processNextLine = false;
|
|
var previousLine = lineNumber > 0 ? document.GetLineSegment(lineNumber - 1) : null;
|
|
|
|
currentSpanStack = previousLine != null && previousLine.HighlightSpanStack != null ? previousLine.HighlightSpanStack.Clone() : null;
|
|
if (currentSpanStack != null)
|
|
{
|
|
while (!currentSpanStack.IsEmpty && currentSpanStack.Peek().StopEOL)
|
|
currentSpanStack.Pop();
|
|
if (currentSpanStack.IsEmpty)
|
|
currentSpanStack = null;
|
|
}
|
|
|
|
currentLine = document.LineSegmentCollection[lineNumber];
|
|
|
|
if (currentLine.Length == -1)
|
|
return false;
|
|
|
|
var words = ParseLine(document);
|
|
|
|
if (currentSpanStack != null && currentSpanStack.IsEmpty)
|
|
currentSpanStack = null;
|
|
|
|
// Check if the span state has changed, if so we must re-render the next line
|
|
// This check may seem utterly complicated but I didn't want to introduce any function calls
|
|
// or allocations here for perf reasons.
|
|
if (currentLine.HighlightSpanStack != currentSpanStack)
|
|
{
|
|
if (currentLine.HighlightSpanStack == null)
|
|
{
|
|
foreach (var sp in currentSpanStack)
|
|
if (!sp.StopEOL)
|
|
{
|
|
spanChanged = true;
|
|
processNextLine = true;
|
|
break;
|
|
}
|
|
}
|
|
else if (currentSpanStack == null)
|
|
{
|
|
foreach (var sp in currentLine.HighlightSpanStack)
|
|
if (!sp.StopEOL)
|
|
{
|
|
spanChanged = true;
|
|
processNextLine = true;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var e1 = currentSpanStack.GetEnumerator();
|
|
var e2 = currentLine.HighlightSpanStack.GetEnumerator();
|
|
var done = false;
|
|
while (!done)
|
|
{
|
|
var blockSpanIn1 = false;
|
|
while (e1.MoveNext())
|
|
if (!e1.Current.StopEOL)
|
|
{
|
|
blockSpanIn1 = true;
|
|
break;
|
|
}
|
|
|
|
var blockSpanIn2 = false;
|
|
while (e2.MoveNext())
|
|
if (!e2.Current.StopEOL)
|
|
{
|
|
blockSpanIn2 = true;
|
|
break;
|
|
}
|
|
|
|
if (blockSpanIn1 || blockSpanIn2)
|
|
{
|
|
if (blockSpanIn1 && blockSpanIn2)
|
|
{
|
|
if (e1.Current != e2.Current)
|
|
{
|
|
done = true;
|
|
processNextLine = true;
|
|
spanChanged = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
spanChanged = true;
|
|
done = true;
|
|
processNextLine = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
done = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//// Alex: remove old words
|
|
currentLine.Words?.Clear();
|
|
currentLine.Words = words;
|
|
currentLine.HighlightSpanStack = currentSpanStack != null && !currentSpanStack.IsEmpty ? currentSpanStack : null;
|
|
|
|
return processNextLine;
|
|
}
|
|
|
|
private void UpdateSpanStateVariables()
|
|
{
|
|
inSpan = currentSpanStack != null && !currentSpanStack.IsEmpty;
|
|
activeSpan = inSpan ? currentSpanStack.Peek() : null;
|
|
activeRuleSet = GetRuleSet(activeSpan);
|
|
}
|
|
|
|
private List<TextWord> ParseLine(IDocument document)
|
|
{
|
|
var words = new List<TextWord>();
|
|
HighlightColor markNext = null;
|
|
|
|
currentOffset = 0;
|
|
currentLength = 0;
|
|
UpdateSpanStateVariables();
|
|
|
|
var currentLineLength = currentLine.Length;
|
|
var currentLineOffset = currentLine.Offset;
|
|
|
|
for (var i = 0; i < currentLineLength; ++i)
|
|
{
|
|
var ch = document.GetCharAt(currentLineOffset + i);
|
|
switch (ch)
|
|
{
|
|
case '\n':
|
|
case '\r':
|
|
PushCurWord(document, ref markNext, words);
|
|
++currentOffset;
|
|
continue;
|
|
case ' ':
|
|
PushCurWord(document, ref markNext, words);
|
|
if (activeSpan != null && activeSpan.Color.HasBackground)
|
|
words.Add(new TextWord.SpaceTextWord(activeSpan.Color));
|
|
else
|
|
words.Add(TextWord.Space);
|
|
++currentOffset;
|
|
continue;
|
|
case '\t':
|
|
PushCurWord(document, ref markNext, words);
|
|
if (activeSpan != null && activeSpan.Color.HasBackground)
|
|
words.Add(new TextWord.TabTextWord(activeSpan.Color));
|
|
else
|
|
words.Add(TextWord.Tab);
|
|
++currentOffset;
|
|
continue;
|
|
}
|
|
|
|
// handle escape characters
|
|
var escapeCharacter = '\0';
|
|
if (activeSpan != null && activeSpan.EscapeCharacter != '\0')
|
|
escapeCharacter = activeSpan.EscapeCharacter;
|
|
else if (activeRuleSet != null)
|
|
escapeCharacter = activeRuleSet.EscapeCharacter;
|
|
if (escapeCharacter != '\0' && escapeCharacter == ch)
|
|
{
|
|
// we found the escape character
|
|
if (activeSpan != null && activeSpan.End != null && activeSpan.End.Length == 1
|
|
&& escapeCharacter == activeSpan.End[0])
|
|
{
|
|
// the escape character is a end-doubling escape character
|
|
// it may count as escape only when the next character is the escape, too
|
|
if (i + 1 < currentLineLength)
|
|
if (document.GetCharAt(currentLineOffset + i + 1) == escapeCharacter)
|
|
{
|
|
currentLength += 2;
|
|
PushCurWord(document, ref markNext, words);
|
|
++i;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// this is a normal \-style escape
|
|
++currentLength;
|
|
if (i + 1 < currentLineLength)
|
|
++currentLength;
|
|
PushCurWord(document, ref markNext, words);
|
|
++i;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// highlight digits
|
|
if (!inSpan && (char.IsDigit(ch) || ch == '.' && i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1))) && currentLength == 0)
|
|
{
|
|
var ishex = false;
|
|
var isfloatingpoint = false;
|
|
|
|
if (ch == '0' && i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'X')
|
|
{
|
|
// hex digits
|
|
const string hex = "0123456789ABCDEF";
|
|
++currentLength;
|
|
++i; // skip 'x'
|
|
++currentLength;
|
|
ishex = true;
|
|
while (i + 1 < currentLineLength && hex.IndexOf(char.ToUpper(document.GetCharAt(currentLineOffset + i + 1))) != -1)
|
|
{
|
|
++i;
|
|
++currentLength;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++currentLength;
|
|
while (i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1)))
|
|
{
|
|
++i;
|
|
++currentLength;
|
|
}
|
|
}
|
|
|
|
if (!ishex && i + 1 < currentLineLength && document.GetCharAt(currentLineOffset + i + 1) == '.')
|
|
{
|
|
isfloatingpoint = true;
|
|
++i;
|
|
++currentLength;
|
|
while (i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1)))
|
|
{
|
|
++i;
|
|
++currentLength;
|
|
}
|
|
}
|
|
|
|
if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'E')
|
|
{
|
|
isfloatingpoint = true;
|
|
++i;
|
|
++currentLength;
|
|
if (i + 1 < currentLineLength && (document.GetCharAt(currentLineOffset + i + 1) == '+' || document.GetCharAt(currentLine.Offset + i + 1) == '-'))
|
|
{
|
|
++i;
|
|
++currentLength;
|
|
}
|
|
|
|
while (i + 1 < currentLine.Length && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1)))
|
|
{
|
|
++i;
|
|
++currentLength;
|
|
}
|
|
}
|
|
|
|
if (i + 1 < currentLine.Length)
|
|
{
|
|
var nextch = char.ToUpper(document.GetCharAt(currentLineOffset + i + 1));
|
|
if (nextch == 'F' || nextch == 'M' || nextch == 'D')
|
|
{
|
|
isfloatingpoint = true;
|
|
++i;
|
|
++currentLength;
|
|
}
|
|
}
|
|
|
|
if (!isfloatingpoint)
|
|
{
|
|
var isunsigned = false;
|
|
if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'U')
|
|
{
|
|
++i;
|
|
++currentLength;
|
|
isunsigned = true;
|
|
}
|
|
|
|
if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'L')
|
|
{
|
|
++i;
|
|
++currentLength;
|
|
if (!isunsigned && i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'U')
|
|
{
|
|
++i;
|
|
++currentLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, DigitColor, hasDefaultColor: false));
|
|
currentOffset += currentLength;
|
|
currentLength = 0;
|
|
continue;
|
|
}
|
|
|
|
// Check for SPAN ENDs
|
|
if (inSpan)
|
|
if (activeSpan.End != null && activeSpan.End.Length > 0)
|
|
if (MatchExpr(currentLine, activeSpan.End, i, document, activeSpan.IgnoreCase))
|
|
{
|
|
PushCurWord(document, ref markNext, words);
|
|
var regex = GetRegString(currentLine, activeSpan.End, i, document);
|
|
currentLength += regex.Length;
|
|
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, activeSpan.EndColor, hasDefaultColor: false));
|
|
currentOffset += currentLength;
|
|
currentLength = 0;
|
|
i += regex.Length - 1;
|
|
currentSpanStack.Pop();
|
|
UpdateSpanStateVariables();
|
|
continue;
|
|
}
|
|
|
|
// check for SPAN BEGIN
|
|
if (activeRuleSet != null)
|
|
{
|
|
Span mySpan = null;
|
|
foreach (Span span in activeRuleSet.Spans)
|
|
{
|
|
if (span.IsBeginSingleWord && currentLength != 0)
|
|
continue;
|
|
if (span.IsBeginStartOfLine.HasValue &&
|
|
span.IsBeginStartOfLine.Value !=
|
|
(currentLength == 0 && words.TrueForAll(
|
|
delegate(TextWord textWord) { return textWord.Type != TextWordType.Word; })))
|
|
continue;
|
|
if (!MatchExpr(currentLine, span.Begin, i, document, activeRuleSet.IgnoreCase))
|
|
continue;
|
|
mySpan = span;
|
|
break;
|
|
}
|
|
|
|
if (mySpan != null)
|
|
{
|
|
PushCurWord(document, ref markNext, words);
|
|
var regex = GetRegString(currentLine, mySpan.Begin, i, document);
|
|
|
|
if (!OverrideSpan(regex, document, words, mySpan, ref i))
|
|
{
|
|
currentLength += regex.Length;
|
|
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, mySpan.BeginColor, hasDefaultColor: false));
|
|
currentOffset += currentLength;
|
|
currentLength = 0;
|
|
|
|
i += regex.Length - 1;
|
|
if (currentSpanStack == null)
|
|
currentSpanStack = new SpanStack();
|
|
currentSpanStack.Push(mySpan);
|
|
mySpan.IgnoreCase = activeRuleSet.IgnoreCase;
|
|
|
|
UpdateSpanStateVariables();
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// check if the char is a delimiter
|
|
if (activeRuleSet != null && ch < 256 && activeRuleSet.Delimiters[ch])
|
|
{
|
|
PushCurWord(document, ref markNext, words);
|
|
if (currentOffset + currentLength + 1 < currentLine.Length)
|
|
{
|
|
++currentLength;
|
|
PushCurWord(document, ref markNext, words);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
++currentLength;
|
|
}
|
|
|
|
PushCurWord(document, ref markNext, words);
|
|
|
|
OnParsedLine(document, currentLine, words);
|
|
|
|
return words;
|
|
}
|
|
|
|
protected virtual void OnParsedLine(IDocument document, LineSegment currentLine, List<TextWord> words)
|
|
{
|
|
}
|
|
|
|
protected virtual bool OverrideSpan(string spanBegin, IDocument document, List<TextWord> words, Span span, ref int lineOffset)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// pushes the curWord string on the word list, with the
|
|
/// correct color.
|
|
/// </summary>
|
|
private void PushCurWord(IDocument document, ref HighlightColor markNext, List<TextWord> words)
|
|
{
|
|
// Svante Lidman : Need to look through the next prev logic.
|
|
if (currentLength > 0)
|
|
{
|
|
if (words.Count > 0 && activeRuleSet != null)
|
|
{
|
|
var pInd = words.Count - 1;
|
|
while (pInd >= 0)
|
|
{
|
|
if (!words[pInd].IsWhiteSpace)
|
|
{
|
|
var prevWord = words[pInd];
|
|
if (prevWord.HasDefaultColor)
|
|
{
|
|
var marker = (PrevMarker)activeRuleSet.PrevMarkers[document, currentLine, currentOffset, currentLength];
|
|
if (marker != null)
|
|
prevWord.SyntaxColor = marker.Color;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
pInd--;
|
|
}
|
|
}
|
|
|
|
if (inSpan)
|
|
{
|
|
HighlightColor c;
|
|
var hasDefaultColor = true;
|
|
if (activeSpan.Rule == null)
|
|
{
|
|
c = activeSpan.Color;
|
|
}
|
|
else
|
|
{
|
|
c = GetColor(activeRuleSet, document, currentLine, currentOffset, currentLength);
|
|
hasDefaultColor = false;
|
|
}
|
|
|
|
if (c == null)
|
|
{
|
|
c = activeSpan.Color;
|
|
if (c.Color == Color.Transparent)
|
|
c = DefaultTextColor;
|
|
hasDefaultColor = true;
|
|
}
|
|
|
|
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, markNext ?? c, hasDefaultColor));
|
|
}
|
|
else
|
|
{
|
|
var c = markNext ?? GetColor(activeRuleSet, document, currentLine, currentOffset, currentLength);
|
|
if (c == null)
|
|
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, DefaultTextColor, hasDefaultColor: true));
|
|
else
|
|
words.Add(new TextWord(document, currentLine, currentOffset, currentLength, c, hasDefaultColor: false));
|
|
}
|
|
|
|
if (activeRuleSet != null)
|
|
{
|
|
var nextMarker = (NextMarker)activeRuleSet.NextMarkers[document, currentLine, currentOffset, currentLength];
|
|
if (nextMarker != null)
|
|
{
|
|
System.Diagnostics.Debug.Assert(words.Count > 0, "Logic changed, no longer words.Count > 0");
|
|
if (nextMarker.MarkMarker)
|
|
{
|
|
var prevword = words[words.Count - 1];
|
|
prevword.SyntaxColor = nextMarker.Color;
|
|
}
|
|
|
|
markNext = nextMarker.Color;
|
|
}
|
|
else
|
|
{
|
|
markNext = null;
|
|
}
|
|
}
|
|
|
|
currentOffset += currentLength;
|
|
currentLength = 0;
|
|
}
|
|
}
|
|
|
|
#region Matching
|
|
|
|
/// <summary>
|
|
/// get the string, which matches the regular expression expr,
|
|
/// in string s2 at index
|
|
/// </summary>
|
|
private static string GetRegString(LineSegment lineSegment, char[] expr, int index, IDocument document)
|
|
{
|
|
var j = 0;
|
|
var regexpr = new StringBuilder();
|
|
|
|
for (var i = 0; i < expr.Length; ++i, ++j)
|
|
{
|
|
if (index + j >= lineSegment.Length)
|
|
break;
|
|
|
|
switch (expr[i])
|
|
{
|
|
case '@': // "special" meaning
|
|
++i;
|
|
if (i == expr.Length)
|
|
throw new HighlightingDefinitionInvalidException("Unexpected end of @ sequence, use @@ to look for a single @.");
|
|
switch (expr[i])
|
|
{
|
|
case '!': // don't match the following expression
|
|
var whatmatch = new StringBuilder();
|
|
++i;
|
|
while (i < expr.Length && expr[i] != '@')
|
|
whatmatch.Append(expr[i++]);
|
|
break;
|
|
case '@': // matches @
|
|
regexpr.Append(document.GetCharAt(lineSegment.Offset + index + j));
|
|
break;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
if (expr[i] != document.GetCharAt(lineSegment.Offset + index + j))
|
|
return regexpr.ToString();
|
|
regexpr.Append(document.GetCharAt(lineSegment.Offset + index + j));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return regexpr.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// returns true, if the get the string s2 at index matches the expression expr
|
|
/// </summary>
|
|
private static bool MatchExpr(LineSegment lineSegment, char[] expr, int index, IDocument document, bool ignoreCase)
|
|
{
|
|
for (int i = 0, j = 0; i < expr.Length; ++i, ++j)
|
|
switch (expr[i])
|
|
{
|
|
case '@': // "special" meaning
|
|
++i;
|
|
if (i == expr.Length)
|
|
throw new HighlightingDefinitionInvalidException("Unexpected end of @ sequence, use @@ to look for a single @.");
|
|
switch (expr[i])
|
|
{
|
|
case 'C': // match whitespace or punctuation
|
|
if (index + j == lineSegment.Offset || index + j >= lineSegment.Offset + lineSegment.Length)
|
|
{
|
|
// nothing (EOL or SOL)
|
|
}
|
|
else
|
|
{
|
|
var ch = document.GetCharAt(lineSegment.Offset + index + j);
|
|
if (!char.IsWhiteSpace(ch) && !char.IsPunctuation(ch))
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
case '!': // don't match the following expression
|
|
{
|
|
var whatmatch = new StringBuilder();
|
|
++i;
|
|
while (i < expr.Length && expr[i] != '@')
|
|
whatmatch.Append(expr[i++]);
|
|
if (lineSegment.Offset + index + j + whatmatch.Length < document.TextLength)
|
|
{
|
|
var k = 0;
|
|
for (; k < whatmatch.Length; ++k)
|
|
{
|
|
var docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index + j + k)) : document.GetCharAt(lineSegment.Offset + index + j + k);
|
|
var spanChar = ignoreCase ? char.ToUpperInvariant(whatmatch[k]) : whatmatch[k];
|
|
if (docChar != spanChar)
|
|
break;
|
|
}
|
|
|
|
if (k >= whatmatch.Length)
|
|
return false;
|
|
}
|
|
|
|
// --j;
|
|
break;
|
|
}
|
|
case '-': // don't match the expression before
|
|
{
|
|
var whatmatch = new StringBuilder();
|
|
++i;
|
|
while (i < expr.Length && expr[i] != '@')
|
|
whatmatch.Append(expr[i++]);
|
|
if (index - whatmatch.Length >= 0)
|
|
{
|
|
var k = 0;
|
|
for (; k < whatmatch.Length; ++k)
|
|
{
|
|
var docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index - whatmatch.Length + k)) : document.GetCharAt(lineSegment.Offset + index - whatmatch.Length + k);
|
|
var spanChar = ignoreCase ? char.ToUpperInvariant(whatmatch[k]) : whatmatch[k];
|
|
if (docChar != spanChar)
|
|
break;
|
|
}
|
|
|
|
if (k >= whatmatch.Length)
|
|
return false;
|
|
}
|
|
|
|
// --j;
|
|
break;
|
|
}
|
|
case '@': // matches @
|
|
if (index + j >= lineSegment.Length || '@' != document.GetCharAt(lineSegment.Offset + index + j))
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
{
|
|
if (index + j >= lineSegment.Length)
|
|
return false;
|
|
var offset = lineSegment.Offset;
|
|
var docChar = document.GetCharAt(offset + index + j);
|
|
var spanChar = expr[i];
|
|
if (ignoreCase)
|
|
{
|
|
docChar = char.ToUpperInvariant(docChar);
|
|
spanChar = char.ToUpperInvariant(spanChar);
|
|
}
|
|
|
|
if (docChar != spanChar)
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|