Forge
csharpdeeb25a2
1#nullable enable
2
3namespace CascadeIDE.Views.Chat.Skia;
4
5internal enum SkiaMarkdownBlockKind
6{
7 Paragraph = 0,
8 Heading1 = 1,
9 Heading2 = 2,
10 Heading3 = 3,
11 Bullet = 4,
12 HorizontalRule = 5,
13 Blank = 6,
14}
15
16/// <summary>Одна визуальная строка документа (после wrap) с блочным контекстом.</summary>
17internal readonly record struct SkiaMarkdownRow(SkiaMarkdownBlockKind Kind, IReadOnlyList<SkiaMarkdownRun> Runs);
18
19/// <summary>Блочный Markdown subset для длинных локальных текстов (справка /help): заголовки, списки, HR, inline v1.</summary>
20internal static class SkiaMarkdownDocument
21{
22 public static IReadOnlyList<SkiaMarkdownRow> Layout(string text, int maxChars)
23 {
24 maxChars = Math.Max(8, maxChars);
25 if (string.IsNullOrEmpty(text))
26 return [];
27
28 var rows = new List<SkiaMarkdownRow>();
29 foreach (var rawLine in text.Replace("\r\n", "\n", StringComparison.Ordinal).Split('\n'))
30 {
31 var line = rawLine.TrimEnd();
32 if (line.Length == 0)
33 {
34 rows.Add(new SkiaMarkdownRow(SkiaMarkdownBlockKind.Blank, []));
35 continue;
36 }
37
38 if (IsHorizontalRule(line))
39 {
40 rows.Add(new SkiaMarkdownRow(SkiaMarkdownBlockKind.HorizontalRule, []));
41 continue;
42 }
43
44 if (line.StartsWith("### ", StringComparison.Ordinal))
45 {
46 AppendWrapped(rows, line[4..], maxChars, SkiaMarkdownBlockKind.Heading3);
47 continue;
48 }
49
50 if (line.StartsWith("## ", StringComparison.Ordinal))
51 {
52 AppendWrapped(rows, line[3..], maxChars, SkiaMarkdownBlockKind.Heading2);
53 continue;
54 }
55
56 if (line.StartsWith("# ", StringComparison.Ordinal))
57 {
58 AppendWrapped(rows, line[2..], maxChars, SkiaMarkdownBlockKind.Heading1);
59 continue;
60 }
61
62 if (line.StartsWith("- ", StringComparison.Ordinal) || line.StartsWith("* ", StringComparison.Ordinal))
63 {
64 AppendBulletWrapped(rows, line[2..], maxChars);
65 continue;
66 }
67
68 AppendWrapped(rows, line, maxChars, SkiaMarkdownBlockKind.Paragraph);
69 }
70
71 return rows;
72 }
73
74 private static void AppendWrapped(
75 List<SkiaMarkdownRow> rows,
76 string content,
77 int maxChars,
78 SkiaMarkdownBlockKind kind)
79 {
80 var wrapped = SkiaMarkdownLayout.WrapLines(SkiaMarkdownLayout.ParseInline(content), maxChars);
81 foreach (var line in wrapped)
82 rows.Add(new SkiaMarkdownRow(kind, line.Runs));
83 }
84
85 private static void AppendBulletWrapped(List<SkiaMarkdownRow> rows, string content, int maxChars)
86 {
87 var bulletMax = Math.Max(8, maxChars - 2);
88 var wrapped = SkiaMarkdownLayout.WrapLines(SkiaMarkdownLayout.ParseInline(content), bulletMax);
89 for (var i = 0; i < wrapped.Count; i++)
90 {
91 var lineRuns = new List<SkiaMarkdownRun> { new(i == 0 ? "• " : " ", SkiaMarkdownStyle.Plain) };
92 lineRuns.AddRange(wrapped[i].Runs);
93 rows.Add(new SkiaMarkdownRow(SkiaMarkdownBlockKind.Bullet, lineRuns));
94 }
95 }
96
97 private static bool IsHorizontalRule(string line)
98 {
99 var t = line.Trim();
100 if (t.Length < 3)
101 return false;
102 return t.All(c => c is '-' or '*' or ' ' or '_')
103 && t.Any(c => c is '-' or '*');
104 }
105}
106
View only · write via MCP/CIDE