scripts/
static/
templates/
.gitignore
30 B
.gitsigners
112 B
AGENTS.md
7.4 KiB
LICENSE
89 B
README.md
1.9 KiB
deploy
723 B
discuss.go
16.7 KiB
git.go
3.7 KiB
git_cli.go
16.3 KiB
git_http.go
2.5 KiB
go.mod
664 B
go.sum
5.2 KiB
handler.go
10.8 KiB
handler_test.go
72.1 KiB
highlight.go
5.3 KiB
main.go
5.7 KiB
template.go
8.6 KiB
watch
272 B
highlight.go
raw
| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "html" |
| 5 | "html/template" |
| 6 | "path" |
| 7 | "strings" |
| 8 | |
| 9 | "github.com/alecthomas/chroma/v2" |
| 10 | "github.com/alecthomas/chroma/v2/lexers" |
| 11 | ) |
| 12 | |
| 13 | var radianceLexer = chroma.MustNewLexer( |
| 14 | &chroma.Config{Name: "Radiance"}, |
| 15 | func() chroma.Rules { |
| 16 | return chroma.Rules{"root": { |
| 17 | {Pattern: `//[^\n]*`, Type: chroma.CommentSingle}, |
| 18 | {Pattern: `"(?:\\.|[^"\\])*"|'[^']{1,2}'`, Type: chroma.LiteralString}, |
| 19 | {Pattern: `\b(?:0x[0-9a-fA-F]+|0b[01]+|[0-9]+(?:\.[0-9]+)?)\b`, Type: chroma.LiteralNumber}, |
| 20 | {Pattern: `@[a-zA-Z]+`, Type: chroma.NameBuiltin}, |
| 21 | {Pattern: `\b(?:fn|if|else|for|while|break|switch|match|set|record|union|constant|align|let|use|mod|module|case|continue|return|true|false|loop|extern|panic|device|register|bit|catch|throw|throws|test|trait|instance|at|mut|nil|undefined|static|in|is|where|export|as|and|or|xor|not|try|atomic|select|of|assert)\b`, Type: chroma.Keyword}, |
| 22 | {Pattern: `\b(?:bool|u8|u16|u32|u64|i8|i16|i32|i64|f32|void|opaque)\b`, Type: chroma.KeywordType}, |
| 23 | {Pattern: `->|=>|::|\.\.|<>|\?{1,2}|!{1,2}|>=?|<=?|[&*.=+\-/%]`, Type: chroma.Operator}, |
| 24 | {Pattern: `[(){}\[\]]`, Type: chroma.Punctuation}, |
| 25 | {Pattern: `\s+`, Type: chroma.TextWhitespace}, |
| 26 | {Pattern: `.`, Type: chroma.Text}, |
| 27 | }} |
| 28 | }, |
| 29 | ) |
| 30 | |
| 31 | var radianceILLexer = chroma.MustNewLexer( |
| 32 | &chroma.Config{Name: "Radiance IL"}, |
| 33 | func() chroma.Rules { |
| 34 | return chroma.Rules{"root": { |
| 35 | {Pattern: `//[^\n]*`, Type: chroma.CommentSingle}, |
| 36 | {Pattern: `"[^"\n]*"`, Type: chroma.LiteralString}, |
| 37 | {Pattern: `%[0-9]+`, Type: chroma.NameVariable}, |
| 38 | {Pattern: `@[A-Za-z_][A-Za-z#0-9_]*`, Type: chroma.NameLabel}, |
| 39 | {Pattern: `\$[A-Za-z_][A-Za-z0-9_]*`, Type: chroma.Name}, |
| 40 | {Pattern: `\b(?:-?[0-9]+|0x[a-fA-F0-9]+)\b`, Type: chroma.LiteralNumber}, |
| 41 | {Pattern: `\b(?:fn|data|extern|mut|align)\b`, Type: chroma.Keyword}, |
| 42 | {Pattern: `\b(?:br\.eq|br\.ne|br\.slt|br\.ult|reserve|load|sload|store|blit|copy|add|sub|mul|sdiv|udiv|srem|urem|neg|eq|ne|slt|sge|ult|uge|and|or|xor|shl|sshr|ushr|not|zext|sext|call|ret|jmp|switch|unreachable|ecall|ebreak)\b`, Type: chroma.NameBuiltin}, |
| 43 | {Pattern: `\b(?:str|sym|undef|w8|w16|w32|w64)\b`, Type: chroma.KeywordType}, |
| 44 | {Pattern: `[{}();:,]`, Type: chroma.Punctuation}, |
| 45 | {Pattern: `\s+`, Type: chroma.TextWhitespace}, |
| 46 | {Pattern: `.`, Type: chroma.Text}, |
| 47 | }} |
| 48 | }, |
| 49 | ) |
| 50 | |
| 51 | // highlightLines returns one escaped HTML fragment for each source line. |
| 52 | func highlightLines(filename string, lines []string) []template.HTML { |
| 53 | if len(lines) == 0 { |
| 54 | return nil |
| 55 | } |
| 56 | |
| 57 | lexer := lexerForFilename(filename) |
| 58 | iterator, err := lexer.Tokenise(nil, strings.Join(lines, "\n")) |
| 59 | if err != nil { |
| 60 | return escapeLines(lines) |
| 61 | } |
| 62 | |
| 63 | highlighted := make([]strings.Builder, len(lines)) |
| 64 | line := 0 |
| 65 | for token := iterator(); token != chroma.EOF; token = iterator() { |
| 66 | parts := strings.Split(token.Value, "\n") |
| 67 | class := tokenClass(token.Type) |
| 68 | for i, part := range parts { |
| 69 | if i > 0 { |
| 70 | line++ |
| 71 | } |
| 72 | if line >= len(highlighted) || part == "" { |
| 73 | continue |
| 74 | } |
| 75 | if class != "" { |
| 76 | highlighted[line].WriteString(`<span class="`) |
| 77 | highlighted[line].WriteString(class) |
| 78 | highlighted[line].WriteString(`">`) |
| 79 | } |
| 80 | highlighted[line].WriteString(html.EscapeString(part)) |
| 81 | if class != "" { |
| 82 | highlighted[line].WriteString(`</span>`) |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | result := make([]template.HTML, len(highlighted)) |
| 88 | for i := range highlighted { |
| 89 | result[i] = template.HTML(highlighted[i].String()) |
| 90 | } |
| 91 | return result |
| 92 | } |
| 93 | |
| 94 | func highlightDiff(file DiffFile, lines []DiffLine) { |
| 95 | oldSource := make([]string, 0, len(lines)) |
| 96 | newSource := make([]string, 0, len(lines)) |
| 97 | for i := range lines { |
| 98 | if lines[i].OldNum > 0 { |
| 99 | oldSource = append(oldSource, lines[i].Content) |
| 100 | } |
| 101 | if lines[i].NewNum > 0 { |
| 102 | newSource = append(newSource, lines[i].Content) |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | oldHighlighted := highlightLines(file.OldName, oldSource) |
| 107 | newHighlighted := highlightLines(file.NewName, newSource) |
| 108 | oldIndex, newIndex := 0, 0 |
| 109 | for i := range lines { |
| 110 | line := &lines[i] |
| 111 | if line.Type == "del" { |
| 112 | line.Highlighted = oldHighlighted[oldIndex] |
| 113 | } else { |
| 114 | line.Highlighted = newHighlighted[newIndex] |
| 115 | } |
| 116 | if line.OldNum > 0 { |
| 117 | oldIndex++ |
| 118 | } |
| 119 | if line.NewNum > 0 { |
| 120 | newIndex++ |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | func lexerForFilename(filename string) chroma.Lexer { |
| 126 | switch strings.ToLower(path.Ext(filename)) { |
| 127 | case ".rad": |
| 128 | return radianceLexer |
| 129 | case ".ril": |
| 130 | return radianceILLexer |
| 131 | } |
| 132 | if lexer := lexers.Match(filename); lexer != nil { |
| 133 | return chroma.Coalesce(lexer) |
| 134 | } |
| 135 | return lexers.Fallback |
| 136 | } |
| 137 | |
| 138 | func tokenClass(tokenType chroma.TokenType) string { |
| 139 | switch { |
| 140 | case tokenType.InCategory(chroma.Keyword): |
| 141 | return "syntax-keyword" |
| 142 | case tokenType.InSubCategory(chroma.NameBuiltin): |
| 143 | return "syntax-builtin" |
| 144 | case tokenType.InSubCategory(chroma.LiteralString): |
| 145 | return "syntax-string" |
| 146 | case tokenType.InSubCategory(chroma.LiteralNumber): |
| 147 | return "syntax-number" |
| 148 | case tokenType.InCategory(chroma.Comment): |
| 149 | return "syntax-comment" |
| 150 | case tokenType.InCategory(chroma.Operator): |
| 151 | return "syntax-operator" |
| 152 | case tokenType.InCategory(chroma.Punctuation): |
| 153 | return "syntax-punctuation" |
| 154 | case tokenType.InSubCategory(chroma.NameVariable), tokenType == chroma.NameLabel: |
| 155 | return "syntax-name" |
| 156 | default: |
| 157 | return "" |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | func escapeLines(lines []string) []template.HTML { |
| 162 | escaped := make([]template.HTML, len(lines)) |
| 163 | for i, line := range lines { |
| 164 | escaped[i] = template.HTML(html.EscapeString(line)) |
| 165 | } |
| 166 | return escaped |
| 167 | } |