Forge
csharp4405de34
1#nullable enable
2
3using System.Text.RegularExpressions;
4using Avalonia;
5using Avalonia.Controls;
6using Avalonia.Controls.Documents;
7using Avalonia.Media;
8using Markdig.Syntax;
9
10namespace CascadeIDE.Services.MarkdownPreview;
11
12/// <summary>Лёгкая подсветка fenced code (без полноценного highlighter).</summary>
13public static partial class MarkdownPreviewFencedCodeHighlighter
14{
15 private static readonly IBrush KeywordBrush = new SolidColorBrush(Color.Parse("#569CD6"));
16 private static readonly IBrush StringBrush = new SolidColorBrush(Color.Parse("#CE9178"));
17 private static readonly IBrush CommentBrush = new SolidColorBrush(Color.Parse("#6A9955"));
18 private static readonly IBrush NumberBrush = new SolidColorBrush(Color.Parse("#B5CEA8"));
19 private static readonly IBrush DefaultBrush = new SolidColorBrush(Color.Parse("#D4D4D4"));
20
21 public static Control Create(FencedCodeBlock block)
22 {
23 var text = block.Lines.ToString();
24 var language = block.Info?.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).FirstOrDefault() ?? "";
25
26 var textBlock = new TextBlock
27 {
28 FontFamily = new FontFamily("Consolas,Cascadia Code,monospace"),
29 TextWrapping = TextWrapping.NoWrap,
30 Foreground = DefaultBrush,
31 };
32
33 if (textBlock.Inlines is null)
34 return CreatePlainCodeBox(text);
35
36 if (IsCSharpLike(language))
37 PopulateCSharpInlines(textBlock.Inlines, text);
38 else if (IsJsonLike(language))
39 PopulateJsonInlines(textBlock.Inlines, text);
40 else
41 textBlock.Inlines.Add(new Run(text));
42
43 var scroll = new ScrollViewer
44 {
45 HorizontalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto,
46 Content = textBlock,
47 };
48
49 return new Border
50 {
51 Background = new SolidColorBrush(Color.Parse("#12000000")),
52 BorderBrush = new SolidColorBrush(Color.Parse("#40888888")),
53 BorderThickness = new Thickness(1),
54 Padding = new Thickness(10),
55 Child = scroll,
56 };
57 }
58
59 private static bool IsCSharpLike(string language) =>
60 language.Equals("csharp", StringComparison.OrdinalIgnoreCase)
61 || language.Equals("cs", StringComparison.OrdinalIgnoreCase)
62 || language.Equals("c#", StringComparison.OrdinalIgnoreCase);
63
64 private static bool IsJsonLike(string language) =>
65 language.Equals("json", StringComparison.OrdinalIgnoreCase);
66
67 private static void PopulateCSharpInlines(InlineCollection inlines, string text)
68 {
69 foreach (Match match in CSharpTokenRegex().Matches(text))
70 {
71 var value = match.Value;
72 var brush = match.Groups["keyword"].Success ? KeywordBrush
73 : match.Groups["str"].Success ? StringBrush
74 : match.Groups["comment"].Success ? CommentBrush
75 : match.Groups["number"].Success ? NumberBrush
76 : DefaultBrush;
77 inlines.Add(new Run(value) { Foreground = brush });
78 }
79 }
80
81 private static void PopulateJsonInlines(InlineCollection inlines, string text)
82 {
83 foreach (Match match in JsonTokenRegex().Matches(text))
84 {
85 var value = match.Value;
86 var brush = match.Groups["key"].Success ? KeywordBrush
87 : match.Groups["str"].Success ? StringBrush
88 : match.Groups["number"].Success ? NumberBrush
89 : DefaultBrush;
90 inlines.Add(new Run(value) { Foreground = brush });
91 }
92 }
93
94 private static TextBox CreatePlainCodeBox(string? text) =>
95 new()
96 {
97 Text = text ?? "",
98 IsReadOnly = true,
99 AcceptsReturn = true,
100 TextWrapping = TextWrapping.NoWrap,
101 FontFamily = new FontFamily("Consolas,Cascadia Code,monospace"),
102 Background = new SolidColorBrush(Color.Parse("#12000000")),
103 BorderBrush = new SolidColorBrush(Color.Parse("#40888888")),
104 BorderThickness = new Thickness(1),
105 Padding = new Thickness(10),
106 HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch,
107 };
108
109 [GeneratedRegex(
110 @"(?<comment>//[^\n]*|/\*[\s\S]*?\*/)|(?<str>@?""(?:\\.|[^""\\])*""|'(?:\\.|[^'\\])*')|(?<keyword>\b(?: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|goto|if|implicit|in|int|interface|internal|is|lock|long|namespace|new|null|object|operator|out|override|params|private|protected|public|readonly|record|ref|return|sealed|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|using|virtual|void|volatile|while|yield)\b)|(?<number>\b\d+(?:\.\d+)?\b)|(?<other>[^\n]+|\n)",
111 RegexOptions.CultureInvariant)]
112 private static partial Regex CSharpTokenRegex();
113
114 [GeneratedRegex(
115 @"(?<key>""(?:\\.|[^""\\])+""\s*:)|(?<str>""(?:\\.|[^""\\])+"")|(?<number>-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)|(?<other>[^\n]+|\n)",
116 RegexOptions.CultureInvariant)]
117 private static partial Regex JsonTokenRegex();
118}
119
View only · write via MCP/CIDE