Update: Text Channel Stuff

Bugs: Files don't work
Bugs: Video In-Line don't work

Added: idk, everything?
This commit is contained in:
2026-06-03 13:19:21 -04:00
parent cd2d809322
commit f819d7284e
20 changed files with 3447 additions and 908 deletions

View File

@@ -0,0 +1,310 @@
using System.Text.RegularExpressions;
namespace RelayClient.Helpers;
public static class SyntaxHighlighter
{
private static readonly Color DefaultColor = Color.FromArgb("#D4D4D4");
private static readonly Color KeywordColor = Color.FromArgb("#569CD6");
private static readonly Color StringColor = Color.FromArgb("#CE9178");
private static readonly Color NumberColor = Color.FromArgb("#B5CEA8");
private static readonly Color CommentColor = Color.FromArgb("#6A9955");
private static readonly Color TypeColor = Color.FromArgb("#4EC9B0");
private static readonly Color FunctionColor = Color.FromArgb("#DCDCAA");
private static readonly Color OperatorColor = Color.FromArgb("#D4D4D4");
private static readonly Color TagColor = Color.FromArgb("#569CD6");
private static readonly Color AttrColor = Color.FromArgb("#9CDCFE");
private const string FontFamily = "AnonymousProRegular";
private static readonly Dictionary<string, string> Aliases = new(StringComparer.OrdinalIgnoreCase)
{
["cs"] = "csharp",
["c#"] = "csharp",
["js"] = "javascript",
["jsx"] = "javascript",
["ts"] = "typescript",
["tsx"] = "typescript",
["py"] = "python",
["sh"] = "bash",
["shell"] = "bash",
["zsh"] = "bash",
["htm"] = "html",
["xml"] = "html",
["yml"] = "yaml"
};
private static readonly Dictionary<string, HashSet<string>> Keywords = new(StringComparer.OrdinalIgnoreCase)
{
["csharp"] = new(StringComparer.Ordinal)
{
"abstract","as","async","await","base","bool","break","byte","case","catch","char","checked",
"class","const","continue","decimal","default","delegate","do","double","else","enum","event",
"explicit","extern","false","finally","fixed","float","for","foreach","get","goto","if",
"implicit","in","int","interface","internal","is","lock","long","namespace","new","null",
"object","operator","out","override","params","partial","private","protected","public",
"readonly","record","ref","return","sbyte","sealed","set","short","sizeof","stackalloc",
"static","string","struct","switch","this","throw","true","try","typeof","uint","ulong",
"unchecked","unsafe","ushort","using","var","virtual","void","volatile","while","yield",
"nameof","when","where","global","init","required","file","scoped","with"
},
["javascript"] = new(StringComparer.Ordinal)
{
"async","await","break","case","catch","class","const","continue","debugger","default",
"delete","do","else","enum","export","extends","false","finally","for","from","function",
"get","if","implements","import","in","instanceof","let","new","null","of","package",
"private","protected","public","return","set","static","super","switch","this","throw",
"true","try","typeof","undefined","var","void","while","with","yield"
},
["typescript"] = new(StringComparer.Ordinal)
{
"any","as","async","await","boolean","break","case","catch","class","const","continue",
"debugger","declare","default","delete","do","else","enum","export","extends","false",
"finally","for","from","function","get","if","implements","import","in","instanceof",
"interface","is","keyof","let","namespace","never","new","null","number","of","package",
"private","protected","public","readonly","return","set","static","string","super",
"switch","this","throw","true","try","type","typeof","undefined","unknown","var","void",
"while","with","yield"
},
["python"] = new(StringComparer.Ordinal)
{
"and","as","assert","async","await","break","class","continue","def","del","elif","else",
"except","False","finally","for","from","global","if","import","in","is","lambda","None",
"nonlocal","not","or","pass","raise","return","True","try","while","with","yield","self",
"cls","match","case"
},
["sql"] = new(StringComparer.OrdinalIgnoreCase)
{
"select","from","where","insert","update","delete","create","alter","drop","table","index",
"view","join","inner","outer","left","right","full","cross","on","as","group","by","order",
"having","distinct","union","all","into","values","set","null","not","and","or","in","like",
"between","is","true","false","primary","key","foreign","references","default","limit",
"offset","with","case","when","then","else","end","exists","cast","begin","commit","rollback"
},
["bash"] = new(StringComparer.Ordinal)
{
"if","then","else","elif","fi","for","in","do","done","while","until","case","esac",
"function","return","break","continue","exit","echo","printf","export","local","readonly",
"source","alias","unset","trap","set","eval","exec","shift","let","declare","typeset"
},
["json"] = new(StringComparer.Ordinal) { "true","false","null" },
["yaml"] = new(StringComparer.Ordinal) { "true","false","null","yes","no","on","off" },
["css"] = new(StringComparer.OrdinalIgnoreCase)
{
"important","inherit","initial","unset","auto","none","normal","bold","italic","center",
"left","right","top","bottom","flex","grid","block","inline","absolute","relative","fixed",
"sticky","static"
}
};
private static readonly Dictionary<string, Regex> Tokenizers = new(StringComparer.Ordinal);
static SyntaxHighlighter()
{
const RegexOptions opts = RegexOptions.Compiled | RegexOptions.Singleline;
Tokenizers["csharp"] = new Regex(
@"(?<comment>//[^\n]*|/\*.*?\*/)" +
@"|(?<string>@""(?:""""|[^""])*""|\$""(?:\\.|[^""\\])*""|""(?:\\.|[^""\\])*""|'(?:\\.|[^'\\])*')" +
@"|(?<number>\b\d+(?:\.\d+)?[fFdDmMuUlL]*\b)" +
@"|(?<word>[A-Za-z_]\w*)",
opts);
Tokenizers["javascript"] = new Regex(
@"(?<comment>//[^\n]*|/\*.*?\*/)" +
@"|(?<string>""(?:\\.|[^""\\])*""|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)" +
@"|(?<number>\b\d+(?:\.\d+)?\b)" +
@"|(?<word>[A-Za-z_$][\w$]*)",
opts);
Tokenizers["typescript"] = Tokenizers["javascript"];
Tokenizers["python"] = new Regex(
@"(?<comment>\#[^\n]*)" +
@"|(?<string>""""""[\s\S]*?""""""|'''[\s\S]*?'''|""(?:\\.|[^""\\])*""|'(?:\\.|[^'\\])*')" +
@"|(?<number>\b\d+(?:\.\d+)?\b)" +
@"|(?<word>[A-Za-z_]\w*)",
opts);
Tokenizers["sql"] = new Regex(
@"(?<comment>--[^\n]*|/\*.*?\*/)" +
@"|(?<string>'(?:''|[^'])*')" +
@"|(?<number>\b\d+(?:\.\d+)?\b)" +
@"|(?<word>[A-Za-z_]\w*)",
opts);
Tokenizers["bash"] = new Regex(
@"(?<comment>\#[^\n]*)" +
@"|(?<string>""(?:\\.|[^""\\])*""|'[^']*')" +
@"|(?<number>\b\d+\b)" +
@"|(?<variable>\$\{?[A-Za-z_]\w*\}?)" +
@"|(?<word>[A-Za-z_][\w-]*)",
opts);
Tokenizers["json"] = new Regex(
@"(?<string>""(?:\\.|[^""\\])*"")" +
@"|(?<number>-?\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)" +
@"|(?<word>true|false|null)",
opts);
Tokenizers["yaml"] = new Regex(
@"(?<comment>\#[^\n]*)" +
@"|(?<string>""(?:\\.|[^""\\])*""|'[^']*')" +
@"|(?<key>^[ \t]*[A-Za-z_][\w-]*(?=\s*:))" +
@"|(?<number>\b\d+(?:\.\d+)?\b)" +
@"|(?<word>[A-Za-z_][\w-]*)",
opts | RegexOptions.Multiline);
Tokenizers["html"] = new Regex(
@"(?<comment><!--.*?-->)" +
@"|(?<string>""[^""]*""|'[^']*')" +
@"|(?<tag></?[A-Za-z][A-Za-z0-9-]*)" +
@"|(?<attr>\b[A-Za-z_][\w-]*(?==))",
opts);
Tokenizers["css"] = new Regex(
@"(?<comment>/\*.*?\*/)" +
@"|(?<string>""[^""]*""|'[^']*')" +
@"|(?<number>-?\b\d+(?:\.\d+)?(?:px|em|rem|%|vh|vw|s|ms|deg)?\b)" +
@"|(?<selector>[.#]?[A-Za-z_][\w-]*(?=\s*[{,]))" +
@"|(?<prop>[A-Za-z-]+(?=\s*:))" +
@"|(?<word>[A-Za-z_][\w-]*)",
opts);
Tokenizers["diff"] = new Regex(
@"(?<add>^\+[^\n]*)" +
@"|(?<del>^-[^\n]*)" +
@"|(?<hunk>^@@[^\n]*)",
opts | RegexOptions.Multiline);
Tokenizers["markdown"] = new Regex(
@"(?<header>^#{1,6}[^\n]*)" +
@"|(?<bold>\*\*[^*\n]+\*\*|__[^_\n]+__)" +
@"|(?<italic>\*[^*\n]+\*|_[^_\n]+_)" +
@"|(?<code>`[^`\n]+`)" +
@"|(?<link>\[[^\]]+\]\([^)]+\))",
opts | RegexOptions.Multiline);
}
public static List<Span> Highlight(string code, string? language, double fontSize)
{
var lang = Resolve(language);
var spans = new List<Span>();
if (lang is null || !Tokenizers.TryGetValue(lang, out var tokenizer))
{
spans.Add(MakeSpan(code, DefaultColor, fontSize));
return spans;
}
var keywords = Keywords.GetValueOrDefault(lang);
int cursor = 0;
foreach (Match m in tokenizer.Matches(code))
{
if (m.Index > cursor)
spans.Add(MakeSpan(code[cursor..m.Index], DefaultColor, fontSize));
spans.Add(SpanForMatch(m, lang, keywords, fontSize));
cursor = m.Index + m.Length;
}
if (cursor < code.Length)
spans.Add(MakeSpan(code[cursor..], DefaultColor, fontSize));
return spans;
}
private static Span SpanForMatch(Match m, string lang, HashSet<string>? keywords, double fontSize)
{
if (m.Groups["comment"].Success)
return MakeSpan(m.Value, CommentColor, fontSize, italic: true);
if (m.Groups["string"].Success)
return MakeSpan(m.Value, StringColor, fontSize);
if (m.Groups["number"].Success)
return MakeSpan(m.Value, NumberColor, fontSize);
if (m.Groups["variable"].Success)
return MakeSpan(m.Value, AttrColor, fontSize);
if (m.Groups["tag"].Success)
return MakeSpan(m.Value, TagColor, fontSize);
if (m.Groups["attr"].Success)
return MakeSpan(m.Value, AttrColor, fontSize);
if (m.Groups["selector"].Success)
return MakeSpan(m.Value, TypeColor, fontSize);
if (m.Groups["prop"].Success)
return MakeSpan(m.Value, AttrColor, fontSize);
if (m.Groups["key"].Success)
return MakeSpan(m.Value, AttrColor, fontSize);
if (m.Groups["add"].Success)
return MakeSpan(m.Value, Color.FromArgb("#6A9955"), fontSize);
if (m.Groups["del"].Success)
return MakeSpan(m.Value, Color.FromArgb("#F48771"), fontSize);
if (m.Groups["hunk"].Success)
return MakeSpan(m.Value, KeywordColor, fontSize);
if (m.Groups["header"].Success)
return MakeSpan(m.Value, KeywordColor, fontSize, bold: true);
if (m.Groups["bold"].Success)
return MakeSpan(m.Value, DefaultColor, fontSize, bold: true);
if (m.Groups["italic"].Success)
return MakeSpan(m.Value, DefaultColor, fontSize, italic: true);
if (m.Groups["code"].Success)
return MakeSpan(m.Value, StringColor, fontSize);
if (m.Groups["link"].Success)
return MakeSpan(m.Value, AttrColor, fontSize);
if (m.Groups["word"].Success)
{
var word = m.Value;
var compareSet = keywords;
if (compareSet is not null && compareSet.Contains(word))
return MakeSpan(word, KeywordColor, fontSize);
if (lang is "csharp" or "javascript" or "typescript" && word.Length > 0 && char.IsUpper(word[0]))
return MakeSpan(word, TypeColor, fontSize);
return MakeSpan(word, DefaultColor, fontSize);
}
return MakeSpan(m.Value, DefaultColor, fontSize);
}
private static Span MakeSpan(string text, Color color, double fontSize, bool bold = false, bool italic = false)
{
var attrs = FontAttributes.None;
if (bold) attrs |= FontAttributes.Bold;
if (italic) attrs |= FontAttributes.Italic;
return new Span
{
Text = text,
TextColor = color,
FontSize = fontSize,
FontFamily = FontFamily,
FontAttributes = attrs
};
}
private static string? Resolve(string? language)
{
if (string.IsNullOrWhiteSpace(language)) return null;
var lower = language.Trim().ToLowerInvariant();
return Aliases.GetValueOrDefault(lower, lower);
}
}