Blog/Components/Pages/Git/GitCode.cs 2.3 K · 66 lines · raw · history

1 using Blog.Models;
2 using Microsoft.AspNetCore.Components;
3 using Microsoft.AspNetCore.Components.Rendering;
4
5 namespace Blog.Components.Pages.Git;
6
7 /// <summary>
8 /// One line of code, as the spans git.css can tell apart.
9 /// </summary>
10 /// <remarks>
11 /// A render tree rather than markup because the cell this lands in is <c>white-space: pre</c>: any
12 /// newline or indentation a .razor file left between two tokens would be rendered as part of the
13 /// file. A render tree emits exactly the nodes it is handed, and <c>AddContent</c> escapes them.
14 /// </remarks>
15 public sealed class GitCode : ComponentBase
16 {
17 [Parameter]
18 public IReadOnlyList<SyntaxToken> Tokens { get; set; } = [];
19
20 /// <summary>
21 /// A diff's <c>+</c> or <c>-</c>, repeated out of the gutter into the line so the diff still
22 /// reads as one when it is copied or read without colour. git.css marks it unselectable, so
23 /// copying gives back the code rather than the patch.
24 /// </summary>
25 [Parameter]
26 public char? Sign { get; set; }
27
28 protected override void BuildRenderTree(RenderTreeBuilder builder)
29 {
30 // Sequence numbers exist for diffing one render against the next, which these pages never
31 // do - they are rendered once, statically, and thrown away.
32 var sequence = 0;
33
34 if (Sign is { } sign)
35 {
36 builder.OpenElement(sequence++, "span");
37 builder.AddAttribute(sequence++, "class", "sign");
38 builder.AddContent(sequence++, sign);
39 builder.CloseElement();
40 }
41
42 foreach (var token in Tokens)
43 {
44 if (token.Kind is SyntaxKind.Text)
45 {
46 builder.AddContent(sequence++, token.Text);
47 continue;
48 }
49
50 builder.OpenElement(sequence++, "span");
51 builder.AddAttribute(sequence++, "class", Class(token.Kind));
52 builder.AddContent(sequence++, token.Text);
53 builder.CloseElement();
54 }
55 }
56
57 private static string Class(SyntaxKind kind) => kind switch
58 {
59 SyntaxKind.Comment => "tok-comment",
60 SyntaxKind.String => "tok-string",
61 SyntaxKind.Number => "tok-number",
62 SyntaxKind.Keyword => "tok-keyword",
63 SyntaxKind.Meta => "tok-meta",
64 _ => ""
65 };
66 }