turbo-editors/turbo-corepublic Fork 0
v1.0.2
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

html.go · 142 lines · 3.9 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 20h ago1package syntax
2
3import "strings"
4
5// highlightHTML colours an HTML document, one slice of spans per line.
6//
7// The contents of <script> and <style> are left as plain text rather than
8// coloured as JavaScript and CSS. Doing that means following the element to
9// its closing tag across lines and mapping another scanner's columns back out,
10// and there is no CSS scanner here to reach for anyway.
11func highlightHTML(src string) [][]Span {
12 return ScanLines(src, scanHTMLLine)
13}
14
15// htmlCarry is what a line can leave open. Only a comment spans lines in what
16// this scanner recognises.
17type htmlCarry uint8
18
19const (
20 htmlGround htmlCarry = iota
21 htmlInComment
22)
23
24// scanHTMLLine returns the spans of one line, and what is left open.
25func scanHTMLLine(line []rune, carry htmlCarry) ([]Span, htmlCarry) {
26 s := &LineScanner{line: line}
27
28 if carry == htmlInComment && !FinishBlockComment(s, "-->", ClassComment) {
29 return s.spans, htmlInComment
30 }
31
32 for !s.AtEnd() {
33 if openComment := stepHTML(s); openComment {
34 return s.spans, htmlInComment
35 }
36 }
37 return s.spans, htmlGround
38}
39
40// stepHTML recognises whatever starts at the current position, and reports
41// whether it left a comment open at the end of the line.
42func stepHTML(s *LineScanner) bool {
43 switch {
44 case s.HasPrefix(0, "<!--"):
45 return !OpenBlockComment(s, "<!--", "-->", ClassComment)
46 case s.HasPrefix(0, "<!"):
47 takeDoctype(s)
48 case s.line[s.pos] == '<':
49 takeTag(s)
50 case s.line[s.pos] == '&':
51 takeEntity(s)
52 default:
53 s.pos++
54 }
55 return false
56}
57
58// takeDoctype colours <!DOCTYPE html> and the other declarations.
59func takeDoctype(s *LineScanner) {
60 start := s.pos
61 for !s.AtEnd() && s.line[s.pos] != '>' {
62 s.pos++
63 }
64 if !s.AtEnd() {
65 s.pos++ // the closing bracket
66 }
67 s.Emit(start, s.pos, ClassKeyword)
68}
69
70// takeEntity colours &amp; and its numeric forms.
71//
72// A bare ampersand is left alone: it is legal text, and colouring the rest of
73// a paragraph after one would be worse than colouring nothing.
74func takeEntity(s *LineScanner) {
75 for at := s.pos + 1; at < len(s.line) && at <= s.pos+entityLimit; at++ {
76 if s.line[at] == ';' {
77 s.Take(at-s.pos+1, ClassConstant)
78 return
79 }
80 if !IsWordRune(s.line[at]) && s.line[at] != '#' {
81 break
82 }
83 }
84 s.pos++
85}
86
87// entityLimit is how far past an ampersand a semicolon may be and still make
88// an entity. The longest named entity in HTML is well under this.
89const entityLimit = 32
90
91// takeTag colours an element: its brackets and name as a tag, then the
92// attributes inside it, up to the closing bracket.
93func takeTag(s *LineScanner) {
94 start := s.pos
95 s.pos++ // the opening bracket
96 if !s.AtEnd() && s.line[s.pos] == '/' {
97 s.pos++ // a closing tag
98 }
99
100 for !s.AtEnd() && (IsWordRune(s.line[s.pos]) || s.line[s.pos] == '-' || s.line[s.pos] == ':') {
101 s.pos++
102 }
103 s.Emit(start, s.pos, ClassTag)
104
105 takeAttributes(s)
106}
107
108// takeAttributes colours what is between an element's name and its closing
109// bracket.
110func takeAttributes(s *LineScanner) {
111 for !s.AtEnd() {
112 switch {
113 case s.line[s.pos] == '>':
114 s.Take(1, ClassTag)
115 return
116 case s.HasPrefix(0, "/>"):
117 s.Take(2, ClassTag)
118 return
119 case s.line[s.pos] == '"' || s.line[s.pos] == '\'':
120 TakeQuoted(s, s.line[s.pos], ClassString)
121 case s.line[s.pos] == '=':
122 s.Take(1, ClassOperator)
123 case IsWordRune(s.line[s.pos]) || s.line[s.pos] == '-' || s.line[s.pos] == ':' || s.line[s.pos] == '@':
124 s.TakeWhile(ClassAttribute, isAttributeRune)
125 default:
126 s.pos++
127 }
128 }
129}
130
131// attributeExtras are the characters an attribute's name may hold besides
132// letters, digits and underscores.
133//
134// Hyphens, colons, at-signs and dots are in because data-*, xlink:href and the
135// framework spellings such as @click and v-bind.prop are all attribute names
136// in the wild.
137const attributeExtras = "-:@."
138
139// isAttributeRune reports whether a rune can appear in an attribute's name.
140func isAttributeRune(r rune) bool {
141 return IsWordRune(r) || strings.ContainsRune(attributeExtras, r)
142}