mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-02 10:48:37 +00:00
Add YAML syntax highlighting
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
// Copyright © 2017-2025 QL-Win Contributors
|
||||
//
|
||||
// This file is part of QuickLook program.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions.Dark;
|
||||
|
||||
public class YAMLHighlightingDefinition : DarkHighlightingDefinition
|
||||
{
|
||||
public override string Name => "YAML";
|
||||
|
||||
public override string Extension => ".yaml;.yml";
|
||||
|
||||
public override HighlightingRuleSet MainRuleSet => new()
|
||||
{
|
||||
Rules =
|
||||
{
|
||||
new HighlightingRule
|
||||
{
|
||||
Regex = new Regex(@"#.*", RegexOptions.Compiled),
|
||||
Color = GetNamedColor("Comment")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public override HighlightingColor GetNamedColor(string name)
|
||||
{
|
||||
return name switch
|
||||
{
|
||||
"Comment" => new HighlightingColor
|
||||
{
|
||||
Name = "Comment",
|
||||
Foreground = new SimpleHighlightingBrush("#6A9949".ToColor()),
|
||||
},
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<HighlightingColor> NamedHighlightingColors =>
|
||||
[
|
||||
GetNamedColor("Comment"),
|
||||
];
|
||||
|
||||
public override DocumentColorizingTransformer[] LineTransformers { get; } = [new KeyHighlighter()];
|
||||
|
||||
public class KeyHighlighter : DocumentColorizingTransformer
|
||||
{
|
||||
protected override void ColorizeLine(DocumentLine line)
|
||||
{
|
||||
var text = CurrentContext.Document.GetText(line);
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (string.IsNullOrWhiteSpace(text) || text.TrimStart().StartsWith("#"))
|
||||
return;
|
||||
|
||||
// Detect as array or object notation
|
||||
if (text.TrimStart().StartsWith("-"))
|
||||
{
|
||||
int idx = text.IndexOf('-');
|
||||
var val = text.Substring(idx + 1); // Here +1 to skip symbol '-'
|
||||
var valTrimmed = val.Trim();
|
||||
var type = DetecteType(val);
|
||||
|
||||
if (valTrimmed.Contains("#") && !(valTrimmed.StartsWith("\"") && !valTrimmed.StartsWith("'")))
|
||||
{
|
||||
int idxSharp = text.IndexOf('#');
|
||||
var valSharp = text.Substring(idxSharp).Trim();
|
||||
|
||||
// Value
|
||||
ChangeLinePart(line.Offset, line.Offset + text.Length - valSharp.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
|
||||
// Comment
|
||||
ChangeLinePart(line.Offset + text.Length - valSharp.Length, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#6A9949".ToBrush());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Value
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Detect as normal key-value pair
|
||||
else
|
||||
{
|
||||
int idx = text.IndexOf(':');
|
||||
var val = text.Substring(idx + 1);
|
||||
var valTrimmed = val.Trim();
|
||||
|
||||
if (idx <= 0)
|
||||
{
|
||||
// If no marker is found, it is considered a multi-line string
|
||||
if (valTrimmed.Contains("#") && !(valTrimmed.StartsWith("\"") && !valTrimmed.StartsWith("'")))
|
||||
{
|
||||
int idxSharp = text.IndexOf('#');
|
||||
var valSharp = text.Substring(idxSharp).Trim();
|
||||
|
||||
// Value
|
||||
ChangeLinePart(line.Offset, line.Offset + text.Length - valSharp.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(ValueType.String).ToBrush());
|
||||
});
|
||||
|
||||
// Comment
|
||||
ChangeLinePart(line.Offset + text.Length - valSharp.Length, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#6A9949".ToBrush());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Value
|
||||
ChangeLinePart(line.Offset, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(ValueType.String).ToBrush());
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Key
|
||||
ChangeLinePart(line.Offset, line.Offset + idx, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#719BD1".ToBrush());
|
||||
});
|
||||
|
||||
// Detect as Literal Block Scalar / Folded Block Scalar
|
||||
if (valTrimmed.StartsWith(">") || valTrimmed.StartsWith("|"))
|
||||
{
|
||||
// Detect as value with comment
|
||||
if (valTrimmed.Contains("#") && !(valTrimmed.StartsWith("\"") && !valTrimmed.StartsWith("'")))
|
||||
{
|
||||
int idxSharp = text.IndexOf('#');
|
||||
var valSharp = text.Substring(idxSharp).Trim();
|
||||
|
||||
// Scalar
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + idxSharp, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#C586C0".ToBrush());
|
||||
});
|
||||
|
||||
// Comment
|
||||
ChangeLinePart(line.Offset + text.Length - valSharp.Length, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#6A9949".ToBrush());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scalar
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#C586C0".ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
// Detect as others
|
||||
else
|
||||
{
|
||||
// Detect as value with comment
|
||||
if (valTrimmed.Contains("#") && !(valTrimmed.StartsWith("\"") && !valTrimmed.StartsWith("'")))
|
||||
{
|
||||
var valWithoutSharp = valTrimmed.Substring(0, valTrimmed.IndexOf('#')).Trim();
|
||||
int idxSharp = text.IndexOf('#');
|
||||
var valSharp = text.Substring(idxSharp).Trim();
|
||||
var type = DetecteType(valWithoutSharp);
|
||||
|
||||
// If value is a string, check for array or object notation
|
||||
if (type == ValueType.String)
|
||||
{
|
||||
// Detect as array or object notation
|
||||
if ((valWithoutSharp.StartsWith("[") && valWithoutSharp.EndsWith("]"))
|
||||
|| (valWithoutSharp.StartsWith("{") && valWithoutSharp.EndsWith("}")))
|
||||
{
|
||||
var parsed = InlineTextParser.ParseInlineText(val);
|
||||
|
||||
// Value
|
||||
foreach (var item in parsed)
|
||||
{
|
||||
if (item.Type == InlineType.None) continue;
|
||||
|
||||
var startIndex = (idx + 1) + item.StartIndex;
|
||||
var count = item.EndIndex - item.StartIndex;
|
||||
var dd = text.Substring(startIndex, count);
|
||||
|
||||
ChangeLinePart(line.Offset + startIndex, line.Offset + startIndex + count, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(item.GetValueColor().ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
// Detect as normal value
|
||||
else
|
||||
{
|
||||
// Value
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Value
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
|
||||
// Comment
|
||||
ChangeLinePart(line.Offset + text.Length - valSharp.Length, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#6A9949".ToBrush());
|
||||
});
|
||||
}
|
||||
// Detect as value without comment
|
||||
else
|
||||
{
|
||||
var type = DetecteType(val);
|
||||
|
||||
// If value is a string, check for array or object notation
|
||||
if (type == ValueType.String)
|
||||
{
|
||||
// Detect as array or object notation
|
||||
if ((valTrimmed.StartsWith("[") && valTrimmed.EndsWith("]"))
|
||||
|| (valTrimmed.StartsWith("{") && valTrimmed.EndsWith("}")))
|
||||
{
|
||||
var parsed = InlineTextParser.ParseInlineText(val);
|
||||
|
||||
// Value
|
||||
foreach (var item in parsed)
|
||||
{
|
||||
if (item.Type == InlineType.None) continue;
|
||||
|
||||
var startIndex = (idx + 1) + item.StartIndex;
|
||||
var count = item.EndIndex - item.StartIndex;
|
||||
var dd = text.Substring(startIndex, count);
|
||||
|
||||
ChangeLinePart(line.Offset + startIndex, line.Offset + startIndex + count, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(item.GetValueColor().ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ValueType DetecteType(string input)
|
||||
{
|
||||
if (double.TryParse(input, out _))
|
||||
return ValueType.Numeric;
|
||||
|
||||
if (bool.TryParse(input, out _))
|
||||
return ValueType.Boolean;
|
||||
|
||||
return ValueType.String;
|
||||
}
|
||||
|
||||
private string GetValueColor(ValueType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ValueType.Numeric => "#B5CEA8",
|
||||
ValueType.Boolean => "#719BD1",
|
||||
ValueType.String or _ => "#CE9178",
|
||||
};
|
||||
}
|
||||
|
||||
private enum ValueType
|
||||
{
|
||||
String,
|
||||
Numeric,
|
||||
Boolean,
|
||||
}
|
||||
|
||||
private static class InlineTextParser
|
||||
{
|
||||
public static InlineText[] ParseInlineText(string input)
|
||||
{
|
||||
var result = new List<InlineText>();
|
||||
int index = 0;
|
||||
var aoSymbol = default(char); // Array/Object symbol
|
||||
var prevSymbol = default(char);
|
||||
|
||||
while (index < input.Length)
|
||||
{
|
||||
char c = input[index];
|
||||
|
||||
// Symbol token
|
||||
if ("{}[]:,".Contains(c))
|
||||
{
|
||||
if (c == '[' || c == '{')
|
||||
{
|
||||
aoSymbol = c;
|
||||
}
|
||||
prevSymbol = c;
|
||||
result.Add(new InlineText(c.ToString(), c switch
|
||||
{
|
||||
'[' or ']' or '{' or '}' => InlineType.Symbol,
|
||||
':' or ',' or _ => InlineType.None,
|
||||
}, index, index + 1));
|
||||
index++;
|
||||
}
|
||||
// Quoted string
|
||||
else if (c == '\'' || c == '"')
|
||||
{
|
||||
int start = index;
|
||||
index++;
|
||||
while (index < input.Length && input[index] != c)
|
||||
index++;
|
||||
|
||||
index++; // include closing quote
|
||||
result.Add(new InlineText(input.Substring(start, index - start), InlineType.ValueString, start, index));
|
||||
}
|
||||
// Key or value (word or number)
|
||||
else if (char.IsLetterOrDigit(c) || c == '-' || c == '>' || c == '<' || c == '=')
|
||||
{
|
||||
int start = index;
|
||||
while (index < input.Length && (char.IsLetterOrDigit(input[index]) || "><=-.".Contains(input[index])))
|
||||
index++;
|
||||
|
||||
string token = input.Substring(start, index - start);
|
||||
result.Add(new InlineText(token, aoSymbol switch
|
||||
{
|
||||
'[' => double.TryParse(token, out _) ? InlineType.ValueNumeric : InlineType.ValueString,
|
||||
'{' or _ => prevSymbol switch
|
||||
{
|
||||
':' => InlineType.ValueNumeric,
|
||||
',' or _ => InlineType.Key,
|
||||
}
|
||||
}, start, index));
|
||||
}
|
||||
// Whitespace, skip
|
||||
else if (char.IsWhiteSpace(c))
|
||||
{
|
||||
result.Add(new InlineText(c.ToString(), InlineType.None, index, index + 1));
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown single char token
|
||||
result.Add(new InlineText(c.ToString(), InlineType.None, index, index + 1));
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{Text}: {Type} [{StartIndex}, {EndIndex}]")]
|
||||
private sealed class InlineText(string text, InlineType type, int start, int end)
|
||||
{
|
||||
public string Text { get; set; } = text;
|
||||
|
||||
public InlineType Type { get; set; } = type;
|
||||
|
||||
public int StartIndex { get; set; } = start;
|
||||
|
||||
public int EndIndex { get; set; } = end;
|
||||
|
||||
public string GetValueColor()
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
InlineType.Symbol => "#FFD700",
|
||||
InlineType.ValueNumeric => "#B5CEA8",
|
||||
InlineType.Key or InlineType.ValueBoolean => "#719BD1",
|
||||
InlineType.ValueString or _ => "#CE9178",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum InlineType
|
||||
{
|
||||
None,
|
||||
Key,
|
||||
ValueString,
|
||||
ValueNumeric,
|
||||
ValueBoolean,
|
||||
Symbol, // e.g. [ ] { } etc.
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,430 @@
|
||||
// Copyright © 2017-2025 QL-Win Contributors
|
||||
//
|
||||
// This file is part of QuickLook program.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions.Light;
|
||||
|
||||
public class YAMLHighlightingDefinition : LightHighlightingDefinition
|
||||
{
|
||||
public override string Name => "YAML";
|
||||
|
||||
public override string Extension => ".yaml;.yml";
|
||||
|
||||
public override HighlightingRuleSet MainRuleSet => new()
|
||||
{
|
||||
Rules =
|
||||
{
|
||||
new HighlightingRule
|
||||
{
|
||||
Regex = new Regex(@"#.*", RegexOptions.Compiled),
|
||||
Color = GetNamedColor("Comment")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public override HighlightingColor GetNamedColor(string name)
|
||||
{
|
||||
return name switch
|
||||
{
|
||||
"Comment" => new HighlightingColor
|
||||
{
|
||||
Name = "Comment",
|
||||
Foreground = new SimpleHighlightingBrush("#008000".ToColor()),
|
||||
},
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<HighlightingColor> NamedHighlightingColors =>
|
||||
[
|
||||
GetNamedColor("Comment"),
|
||||
];
|
||||
|
||||
public override DocumentColorizingTransformer[] LineTransformers { get; } = [new KeyHighlighter()];
|
||||
|
||||
public class KeyHighlighter : DocumentColorizingTransformer
|
||||
{
|
||||
protected override void ColorizeLine(DocumentLine line)
|
||||
{
|
||||
var text = CurrentContext.Document.GetText(line);
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (string.IsNullOrWhiteSpace(text) || text.TrimStart().StartsWith("#"))
|
||||
return;
|
||||
|
||||
// Detect as array or object notation
|
||||
if (text.TrimStart().StartsWith("-"))
|
||||
{
|
||||
int idx = text.IndexOf('-');
|
||||
var val = text.Substring(idx + 1); // Here +1 to skip symbol '-'
|
||||
var valTrimmed = val.Trim();
|
||||
var type = DetecteType(val);
|
||||
|
||||
if (valTrimmed.Contains("#") && !(valTrimmed.StartsWith("\"") && !valTrimmed.StartsWith("'")))
|
||||
{
|
||||
int idxSharp = text.IndexOf('#');
|
||||
var valSharp = text.Substring(idxSharp).Trim();
|
||||
|
||||
// Value
|
||||
ChangeLinePart(line.Offset, line.Offset + text.Length - valSharp.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
|
||||
// Comment
|
||||
ChangeLinePart(line.Offset + text.Length - valSharp.Length, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#008000".ToBrush());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Value
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Detect as normal key-value pair
|
||||
else
|
||||
{
|
||||
int idx = text.IndexOf(':');
|
||||
var val = text.Substring(idx + 1);
|
||||
var valTrimmed = val.Trim();
|
||||
|
||||
if (idx <= 0)
|
||||
{
|
||||
// If no marker is found, it is considered a multi-line string
|
||||
if (valTrimmed.Contains("#") && !(valTrimmed.StartsWith("\"") && !valTrimmed.StartsWith("'")))
|
||||
{
|
||||
int idxSharp = text.IndexOf('#');
|
||||
var valSharp = text.Substring(idxSharp).Trim();
|
||||
|
||||
// Value
|
||||
ChangeLinePart(line.Offset, line.Offset + text.Length - valSharp.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(ValueType.String).ToBrush());
|
||||
});
|
||||
|
||||
// Comment
|
||||
ChangeLinePart(line.Offset + text.Length - valSharp.Length, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#008000".ToBrush());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Value
|
||||
ChangeLinePart(line.Offset, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(ValueType.String).ToBrush());
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Key
|
||||
ChangeLinePart(line.Offset, line.Offset + idx, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#795E26".ToBrush());
|
||||
});
|
||||
|
||||
// Detect as Literal Block Scalar / Folded Block Scalar
|
||||
if (valTrimmed.StartsWith(">") || valTrimmed.StartsWith("|"))
|
||||
{
|
||||
// Detect as value with comment
|
||||
if (valTrimmed.Contains("#") && !(valTrimmed.StartsWith("\"") && !valTrimmed.StartsWith("'")))
|
||||
{
|
||||
int idxSharp = text.IndexOf('#');
|
||||
var valSharp = text.Substring(idxSharp).Trim();
|
||||
|
||||
// Scalar
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + idxSharp, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#267f99".ToBrush());
|
||||
});
|
||||
|
||||
// Comment
|
||||
ChangeLinePart(line.Offset + text.Length - valSharp.Length, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#008000".ToBrush());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scalar
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#267f99".ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
// Detect as others
|
||||
else
|
||||
{
|
||||
// Detect as value with comment
|
||||
if (valTrimmed.Contains("#") && !(valTrimmed.StartsWith("\"") && !valTrimmed.StartsWith("'")))
|
||||
{
|
||||
var valWithoutSharp = valTrimmed.Substring(0, valTrimmed.IndexOf('#')).Trim();
|
||||
int idxSharp = text.IndexOf('#');
|
||||
var valSharp = text.Substring(idxSharp).Trim();
|
||||
var type = DetecteType(valWithoutSharp);
|
||||
|
||||
// If value is a string, check for array or object notation
|
||||
if (type == ValueType.String)
|
||||
{
|
||||
// Detect as array or object notation
|
||||
if ((valWithoutSharp.StartsWith("[") && valWithoutSharp.EndsWith("]"))
|
||||
|| (valWithoutSharp.StartsWith("{") && valWithoutSharp.EndsWith("}")))
|
||||
{
|
||||
var parsed = InlineTextParser.ParseInlineText(val);
|
||||
|
||||
// Value
|
||||
foreach (var item in parsed)
|
||||
{
|
||||
if (item.Type == InlineType.None) continue;
|
||||
|
||||
var startIndex = (idx + 1) + item.StartIndex;
|
||||
var count = item.EndIndex - item.StartIndex;
|
||||
var dd = text.Substring(startIndex, count);
|
||||
|
||||
ChangeLinePart(line.Offset + startIndex, line.Offset + startIndex + count, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(item.GetValueColor().ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
// Detect as normal value
|
||||
else
|
||||
{
|
||||
// Value
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Value
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
|
||||
// Comment
|
||||
ChangeLinePart(line.Offset + text.Length - valSharp.Length, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush("#008000".ToBrush());
|
||||
});
|
||||
}
|
||||
// Detect as value without comment
|
||||
else
|
||||
{
|
||||
var type = DetecteType(val);
|
||||
|
||||
// If value is a string, check for array or object notation
|
||||
if (type == ValueType.String)
|
||||
{
|
||||
// Detect as array or object notation
|
||||
if ((valTrimmed.StartsWith("[") && valTrimmed.EndsWith("]"))
|
||||
|| (valTrimmed.StartsWith("{") && valTrimmed.EndsWith("}")))
|
||||
{
|
||||
var parsed = InlineTextParser.ParseInlineText(val);
|
||||
|
||||
// Value
|
||||
foreach (var item in parsed)
|
||||
{
|
||||
if (item.Type == InlineType.None) continue;
|
||||
|
||||
var startIndex = (idx + 1) + item.StartIndex;
|
||||
var count = item.EndIndex - item.StartIndex;
|
||||
var dd = text.Substring(startIndex, count);
|
||||
|
||||
ChangeLinePart(line.Offset + startIndex, line.Offset + startIndex + count, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(item.GetValueColor().ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeLinePart(line.Offset + idx + 1, line.Offset + text.Length, el =>
|
||||
{
|
||||
el.TextRunProperties.SetForegroundBrush(GetValueColor(type).ToBrush());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ValueType DetecteType(string input)
|
||||
{
|
||||
if (double.TryParse(input, out _))
|
||||
return ValueType.Numeric;
|
||||
|
||||
if (bool.TryParse(input, out _))
|
||||
return ValueType.Boolean;
|
||||
|
||||
return ValueType.String;
|
||||
}
|
||||
|
||||
private string GetValueColor(ValueType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ValueType.Numeric => "#098658",
|
||||
ValueType.Boolean => "#0000FF",
|
||||
ValueType.String or _ => "#A31515",
|
||||
};
|
||||
}
|
||||
|
||||
private enum ValueType
|
||||
{
|
||||
String,
|
||||
Numeric,
|
||||
Boolean,
|
||||
}
|
||||
|
||||
private static class InlineTextParser
|
||||
{
|
||||
public static InlineText[] ParseInlineText(string input)
|
||||
{
|
||||
var result = new List<InlineText>();
|
||||
int index = 0;
|
||||
var aoSymbol = default(char); // Array/Object symbol
|
||||
var prevSymbol = default(char);
|
||||
|
||||
while (index < input.Length)
|
||||
{
|
||||
char c = input[index];
|
||||
|
||||
// Symbol token
|
||||
if ("{}[]:,".Contains(c))
|
||||
{
|
||||
if (c == '[' || c == '{')
|
||||
{
|
||||
aoSymbol = c;
|
||||
}
|
||||
prevSymbol = c;
|
||||
result.Add(new InlineText(c.ToString(), c switch
|
||||
{
|
||||
'[' or ']' or '{' or '}' => InlineType.Symbol,
|
||||
':' or ',' or _ => InlineType.None,
|
||||
}, index, index + 1));
|
||||
index++;
|
||||
}
|
||||
// Quoted string
|
||||
else if (c == '\'' || c == '"')
|
||||
{
|
||||
int start = index;
|
||||
index++;
|
||||
while (index < input.Length && input[index] != c)
|
||||
index++;
|
||||
|
||||
index++; // include closing quote
|
||||
result.Add(new InlineText(input.Substring(start, index - start), InlineType.ValueString, start, index));
|
||||
}
|
||||
// Key or value (word or number)
|
||||
else if (char.IsLetterOrDigit(c) || c == '-' || c == '>' || c == '<' || c == '=')
|
||||
{
|
||||
int start = index;
|
||||
while (index < input.Length && (char.IsLetterOrDigit(input[index]) || "><=-.".Contains(input[index])))
|
||||
index++;
|
||||
|
||||
string token = input.Substring(start, index - start);
|
||||
result.Add(new InlineText(token, aoSymbol switch
|
||||
{
|
||||
'[' => double.TryParse(token, out _) ? InlineType.ValueNumeric : InlineType.ValueString,
|
||||
'{' or _ => prevSymbol switch
|
||||
{
|
||||
':' => InlineType.ValueNumeric,
|
||||
',' or _ => InlineType.Key,
|
||||
}
|
||||
}, start, index));
|
||||
}
|
||||
// Whitespace, skip
|
||||
else if (char.IsWhiteSpace(c))
|
||||
{
|
||||
result.Add(new InlineText(c.ToString(), InlineType.None, index, index + 1));
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown single char token
|
||||
result.Add(new InlineText(c.ToString(), InlineType.None, index, index + 1));
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return [.. result];
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{Text}: {Type} [{StartIndex}, {EndIndex}]")]
|
||||
private sealed class InlineText(string text, InlineType type, int start, int end)
|
||||
{
|
||||
public string Text { get; set; } = text;
|
||||
|
||||
public InlineType Type { get; set; } = type;
|
||||
|
||||
public int StartIndex { get; set; } = start;
|
||||
|
||||
public int EndIndex { get; set; } = end;
|
||||
|
||||
public string GetValueColor()
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
InlineType.Symbol => "#795E26",
|
||||
InlineType.ValueNumeric => "#098658",
|
||||
InlineType.Key or InlineType.ValueBoolean => "#0000FF",
|
||||
InlineType.ValueString or _ => "#A31515",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum InlineType
|
||||
{
|
||||
None,
|
||||
Key,
|
||||
ValueString,
|
||||
ValueNumeric,
|
||||
ValueBoolean,
|
||||
Symbol, // e.g. [ ] { } etc.
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user