turbo-editors/turbo-corepublic Fork 0
v1.0.0
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.

javascript.go · 209 lines · 7.2 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 21h ago1package syntax
2
3// javaScriptKeywords are the words that structure the language.
4var javaScriptKeywords = map[string]Class{
5 "await": ClassKeyword, "break": ClassKeyword, "case": ClassKeyword,
6 "catch": ClassKeyword, "class": ClassKeyword, "const": ClassKeyword,
7 "continue": ClassKeyword, "debugger": ClassKeyword, "default": ClassKeyword,
8 "delete": ClassKeyword, "do": ClassKeyword, "else": ClassKeyword,
9 "export": ClassKeyword, "extends": ClassKeyword, "finally": ClassKeyword,
10 "for": ClassKeyword, "from": ClassKeyword, "function": ClassKeyword,
11 "get": ClassKeyword, "if": ClassKeyword, "import": ClassKeyword,
12 "in": ClassKeyword, "instanceof": ClassKeyword, "let": ClassKeyword,
13 "new": ClassKeyword, "of": ClassKeyword, "return": ClassKeyword,
14 "set": ClassKeyword, "static": ClassKeyword, "super": ClassKeyword,
15 "switch": ClassKeyword, "throw": ClassKeyword, "try": ClassKeyword,
16 "typeof": ClassKeyword, "var": ClassKeyword, "void": ClassKeyword,
17 "while": ClassKeyword, "with": ClassKeyword, "yield": ClassKeyword,
18 "async": ClassKeyword,
19
20 "true": ClassConstant, "false": ClassConstant, "null": ClassConstant,
21 "undefined": ClassConstant, "NaN": ClassConstant, "Infinity": ClassConstant,
22 "this": ClassConstant,
23
24 // The globals worth telling apart from a variable of your own. Recognised
25 // by name, as Go's predeclared identifiers are, because a file may shadow
26 // them and colouring the shadowed one anyway is what every editor does.
27 "console": ClassBuiltin, "document": ClassBuiltin, "window": ClassBuiltin,
28 "Array": ClassBuiltin, "Object": ClassBuiltin, "String": ClassBuiltin,
29 "Number": ClassBuiltin, "Boolean": ClassBuiltin, "Promise": ClassBuiltin,
30 "Math": ClassBuiltin, "JSON": ClassBuiltin, "Date": ClassBuiltin,
31 "RegExp": ClassBuiltin, "Error": ClassBuiltin, "Map": ClassBuiltin,
32 "Set": ClassBuiltin, "Symbol": ClassBuiltin, "globalThis": ClassBuiltin,
33}
34
35// javaScriptCarry is what a line can leave open: a block comment, or a
36// template literal, which is the only string in the language that may span
37// lines.
38type javaScriptCarry uint8
39
40const (
41 javaScriptGround javaScriptCarry = iota
42 javaScriptInBlockComment
43 javaScriptInTemplate
44)
45
46// highlightJavaScript colours JavaScript, one slice of spans per line.
47//
48// Regular-expression literals are deliberately not recognised. Telling `/x/g`
49// from a division needs to know whether the previous token could end an
50// expression, and getting it wrong colours the rest of a line as a string —
51// a worse failure than leaving a regex the colour of an operator.
52func highlightJavaScript(src string) [][]Span {
53 return ScanLines(src, scanJavaScriptLine)
54}
55
56// scanJavaScriptLine returns the spans of one line, and what is left open.
57func scanJavaScriptLine(line []rune, carry javaScriptCarry) ([]Span, javaScriptCarry) {
58 s := &LineScanner{line: line}
59
60 switch carry {
61 case javaScriptInBlockComment:
62 if !FinishBlockComment(s, "*/", ClassComment) {
63 return s.spans, javaScriptInBlockComment
64 }
65 case javaScriptInTemplate:
66 if !finishTemplate(s) {
67 return s.spans, javaScriptInTemplate
68 }
69 }
70
71 for !s.AtEnd() {
72 if next, stop := stepJavaScript(s); stop {
73 return s.spans, next
74 }
75 }
76 return s.spans, javaScriptGround
77}
78
79// stepJavaScript recognises whatever starts at the current position, and says
80// whether it left something open at the end of the line.
81func stepJavaScript(s *LineScanner) (javaScriptCarry, bool) {
82 switch {
83 case s.line[s.pos] == ' ' || s.line[s.pos] == '\t':
84 s.pos++
85 case s.HasPrefix(0, "//"):
86 s.TakeRest(ClassComment)
87 case s.HasPrefix(0, "/*"):
88 if !OpenBlockComment(s, "/*", "*/", ClassComment) {
89 return javaScriptInBlockComment, true
90 }
91 case s.line[s.pos] == '`':
92 if !openTemplate(s) {
93 return javaScriptInTemplate, true
94 }
95 case s.line[s.pos] == '"' || s.line[s.pos] == '\'':
96 TakeQuoted(s, s.line[s.pos], ClassString)
97 case IsDigit(s.line[s.pos]):
98 takeJavaScriptNumber(s)
99 case isJavaScriptWordStart(s.line[s.pos]):
100 takeJavaScriptWord(s)
101 case IsPunctuationRune(s.line[s.pos]):
102 s.Take(1, ClassPunctuation)
103 case IsOperatorRune(s.line[s.pos]):
104 s.TakeWhile(ClassOperator, IsOperatorRune)
105 default:
106 s.pos++
107 }
108 return javaScriptGround, false
109}
110
111// isJavaScriptWordStart reports whether a rune can begin an identifier.
112func isJavaScriptWordStart(r rune) bool { return IsLetter(r) || r == '_' || r == '$' }
113
114// isJavaScriptWordRune reports whether a rune can continue one.
115func isJavaScriptWordRune(r rune) bool { return IsWordRune(r) || r == '$' }
116
117// takeJavaScriptWord colours an identifier, keyword, constant or global.
118//
119// A name followed by "(" is a call, which reads as a function — the same rule
120// the Go highlighter uses, and the same reason: it is what the eye is looking
121// for when scanning unfamiliar code.
122func takeJavaScriptWord(s *LineScanner) {
123 start := s.pos
124 for !s.AtEnd() && isJavaScriptWordRune(s.line[s.pos]) {
125 s.pos++
126 }
127
128 word := string(s.line[start:s.pos])
129 if class, ok := javaScriptKeywords[word]; ok {
130 s.Emit(start, s.pos, class)
131 return
132 }
133 if isCallSite(s) {
134 s.Emit(start, s.pos, ClassFunction)
135 return
136 }
137 s.Emit(start, s.pos, ClassIdentifier)
138}
139
140// isCallSite reports whether the next thing on the line, past any spaces, is
141// an opening parenthesis.
142func isCallSite(s *LineScanner) bool {
143 for at := s.pos; at < len(s.line); at++ {
144 switch s.line[at] {
145 case ' ', '\t':
146 case '(':
147 return true
148 default:
149 return false
150 }
151 }
152 return false
153}
154
155// openTemplate colours a template literal that starts here, and reports
156// whether it also ended on this line.
157//
158// The whole literal is a string, interpolations included. Colouring the code
159// inside ${…} would mean the scanner reaching back into itself with a nesting
160// depth to track, for a construct that is usually one short expression.
161func openTemplate(s *LineScanner) bool {
162 start := s.pos
163 s.pos++ // past the opening backtick, so it is not found as the closing one
164 return takeTemplateFrom(s, start)
165}
166
167// finishTemplate colours the continuation of a template opened on an earlier
168// line, and reports whether it ended on this one.
169func finishTemplate(s *LineScanner) bool { return takeTemplateFrom(s, s.pos) }
170
171// takeTemplateFrom colours from start to the end of the template, or to the
172// end of the line when it does not close here.
173//
174// The start is passed in rather than read back from the scanner afterwards:
175// runToBacktick has already moved the position, and emit drops empty spans, so
176// anything that tried to colour "what is left" would colour nothing at all.
177func takeTemplateFrom(s *LineScanner, start int) bool {
178 closed := runToBacktick(s)
179 if !closed {
180 s.pos = len(s.line)
181 }
182 s.Emit(start, s.pos, ClassString)
183 return closed
184}
185
186// runToBacktick advances to just past the next unescaped backtick, and reports
187// whether it found one.
188func runToBacktick(s *LineScanner) bool {
189 for !s.AtEnd() {
190 if s.line[s.pos] == '\\' && s.pos+1 < len(s.line) {
191 s.pos += 2
192 continue
193 }
194 if s.line[s.pos] == '`' {
195 s.pos++
196 return true
197 }
198 s.pos++
199 }
200 return false
201}
202
203// takeJavaScriptNumber colours a numeric literal, including the 0x, 0o and 0b
204// forms, underscore separators and the trailing n of a BigInt.
205func takeJavaScriptNumber(s *LineScanner) {
206 s.TakeWhile(ClassNumber, func(r rune) bool {
207 return IsDigit(r) || IsLetter(r) || r == '_' || r == '.'
208 })
209}