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.

🛟 Updated. 28d5985 · on v1.0.2 · k33g · 15h ago
toml.go · 265 lines · 7.7 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
package syntax

import "strings"

// highlightTOML returns the spans to colour for a TOML document, one slice per
// line.
//
// TOML is scanned a line at a time rather than as one stream, because a span
// may not straddle a line break and every TOML construct but one fits on a
// line. The exception is the multi-line string, which is what carry says.
func highlightTOML(src string) [][]Span {
	return ScanLines(src, scanTOMLLine)
}

// multiline says which kind of multi-line string, if any, is still open at the
// start of a line.
type multiline uint8

const (
	noMultiline      multiline = iota
	basicMultiline             // opened with """
	literalMultiline           // opened with '''
)

// delimiter returns the three characters that close an open multi-line string.
func (m multiline) delimiter() string {
	if m == basicMultiline {
		return `"""`
	}
	return `'''`
}

// tomlScanner walks one line of TOML, emitting spans as it recognises things.
type tomlScanner struct {
	LineScanner
	carry multiline
}

// scanTOMLLine returns the spans of one line, and what is left open at its end.
func scanTOMLLine(line []rune, carry multiline) ([]Span, multiline) {
	s := &tomlScanner{LineScanner: LineScanner{line: line}, carry: carry}

	if carry != noMultiline {
		s.finishMultiline(carry)
	}
	for s.pos < len(s.line) {
		s.step()
	}
	return s.spans, s.carry
}

// step recognises whatever starts at the current position.
func (s *tomlScanner) step() {
	switch c := s.line[s.pos]; {
	case c == ' ' || c == '\t':
		s.pos++
	case c == '#':
		s.Emit(s.pos, len(s.line), ClassComment)
		s.pos = len(s.line)
	case isTOMLPunctuation(c):
		s.Emit(s.pos, s.pos+1, ClassPunctuation)
		s.pos++
	case c == '=':
		s.Emit(s.pos, s.pos+1, ClassOperator)
		s.pos++
	case c == '"' || c == '\'':
		s.scanString(c)
	case isTOMLBareRune(c):
		s.scanWord()
	default:
		s.pos++
	}
}

// scanString reads a quoted string, of any of TOML's four kinds.
func (s *tomlScanner) scanString(quote rune) {
	if kind, ok := s.openingMultiline(quote); ok {
		start := s.pos
		s.pos += 3 // past the opening delimiter, so it is not found as the closing one
		s.finishMultilineFrom(start, kind)
		return
	}

	start := s.pos
	s.pos++ // the opening quote
	for s.pos < len(s.line) {
		// Only a basic string has escapes; in a literal string a backslash is
		// a backslash, which is the point of literal strings.
		if quote == '"' && s.line[s.pos] == '\\' && s.pos+1 < len(s.line) {
			s.pos += 2
			continue
		}
		if s.line[s.pos] == quote {
			s.pos++
			s.Emit(start, s.pos, ClassString)
			return
		}
		s.pos++
	}

	// Unterminated: colour to the end of the line rather than giving up, so a
	// string being typed stays coloured while it is still half-written.
	s.Emit(start, len(s.line), ClassString)
}

// openingMultiline reports whether a triple quote starts here.
func (s *tomlScanner) openingMultiline(quote rune) (multiline, bool) {
	if s.pos+2 >= len(s.line) || s.line[s.pos+1] != quote || s.line[s.pos+2] != quote {
		return noMultiline, false
	}
	if quote == '"' {
		return basicMultiline, true
	}
	return literalMultiline, true
}

// finishMultiline colours the continuation of a multi-line string opened on an
// earlier line, which starts at the very beginning of this one.
func (s *tomlScanner) finishMultiline(kind multiline) {
	s.finishMultilineFrom(s.pos, kind)
}

// finishMultilineFrom colours from start to the end of an open multi-line
// string, or to the end of the line when it does not close here.
//
// The span starts at start but the search for the closing delimiter starts at
// the current position, which is what keeps an opening """ from being found as
// its own closing one. start is a parameter rather than something patched onto
// the span afterwards because emit drops empty spans — an opening delimiter at
// the very end of a line emits nothing, and patching would then have rewritten
// whatever span came before it.
func (s *tomlScanner) finishMultilineFrom(start int, kind multiline) {
	closing := []rune(kind.delimiter())

	for at := s.pos; at+len(closing) <= len(s.line); at++ {
		if hasRunes(s.line, at, closing) {
			s.pos = at + len(closing)
			s.Emit(start, s.pos, ClassString)
			s.carry = noMultiline
			return
		}
	}

	s.pos = len(s.line)
	s.Emit(start, s.pos, ClassString)
	s.carry = kind
}

// scanWord reads a bare word: a key, a table name, a boolean, a number or a
// date, told apart by what follows it.
func (s *tomlScanner) scanWord() {
	start := s.pos
	for s.pos < len(s.line) && isTOMLBareRune(s.line[s.pos]) {
		s.pos++
	}

	word := string(s.line[start:s.pos])
	s.Emit(start, s.pos, tomlWordClass(word, s.followedByEquals(), s.insideTableHeader(start)))
}

// followedByEquals reports whether the next thing on the line is an "=", which
// is what makes a bare word a key rather than a value.
func (s *tomlScanner) followedByEquals() bool {
	for at := s.pos; at < len(s.line); at++ {
		switch s.line[at] {
		case ' ', '\t':
		case '.':
			return true // a dotted key: every part of it is still a key
		case '=':
			return true
		default:
			return false
		}
	}
	return false
}

// insideTableHeader reports whether the word starting at an offset is part of
// a [table] or [[array of tables]] header.
//
// It looks at what comes before rather than tracking a state, because a header
// is always the first thing on its line.
func (s *tomlScanner) insideTableHeader(start int) bool {
	for at := range start {
		switch s.line[at] {
		case ' ', '\t', '[':
		default:
			return false
		}
	}
	return start > 0 && s.line[start-1] == '['
}

// tomlWordClass decides what a bare word is.
//
// The classes are the ones the Go highlighter already uses, so a theme colours
// TOML without naming a single new key: a table header reads as a type, a key
// as an identifier, and true and false as the constants they are.
func tomlWordClass(word string, key, header bool) Class {
	if header {
		return ClassType
	}
	if key {
		return ClassIdentifier
	}
	return tomlValueClass(word)
}

// tomlValueClass decides what a bare word on the value side of an "=" is.
func tomlValueClass(word string) Class {
	switch {
	case word == "true" || word == "false":
		return ClassConstant
	case word == "inf" || word == "nan", startsLikeANumber(word):
		return ClassNumber
	default:
		return ClassIdentifier
	}
}

// startsLikeANumber reports whether a word is a number, a date or a time.
//
// TOML's dates and times start with a digit and carry on through the same
// characters a number may hold, so one test covers all of them.
func startsLikeANumber(word string) bool {
	if word == "" {
		return false
	}
	first := rune(word[0])
	if first == '+' || first == '-' {
		return len(word) > 1 && IsDigit(rune(word[1]))
	}
	return IsDigit(first)
}

// tomlPunctuation are the characters that structure a document rather than
// carry a value: table brackets, array brackets, inline-table braces, the
// separator, and the dot of a dotted key.
const tomlPunctuation = "[]{},."

// isTOMLPunctuation reports whether a rune is one of them.
func isTOMLPunctuation(r rune) bool { return strings.ContainsRune(tomlPunctuation, r) }

// bareExtras are the characters that may appear in a bare word besides letters
// and digits: key separators, number signs, and the colon of a time.
const bareExtras = "_-+:"

// isTOMLBareRune reports whether a rune may appear in a bare key, a number, a
// date or a boolean — everything that is neither punctuation nor a string.
func isTOMLBareRune(r rune) bool {
	return IsLetter(r) || IsDigit(r) || strings.ContainsRune(bareExtras, r)
}

// hasRunes reports whether want appears in line at offset at.
func hasRunes(line []rune, at int, want []rune) bool {
	if at+len(want) > len(line) {
		return false
	}
	for i, r := range want {
		if line[at+i] != r {
			return false
		}
	}
	return true
}