turbo-editors/turbo-corepublic Fork 0
v0.9.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.

🛟 Updated. 28d5985 · on v0.9.0 · k33g · 19h ago
yaml_key.go · 109 lines · 3.1 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
package syntax

import "strings"

// Keys and bare words: telling `image: nginx` from a scalar that merely
// contains a colon, which is most of what makes YAML awkward to scan.

// takeYAMLKey colours a mapping key and its colon, when the line has one.
//
// The key is recognised only when a colon follows it before any space, which is
// what tells `image: nginx` from a bare scalar such as `nginx:latest` in a list
// entry — the second has no space after its colon and is a value, not a key.
func takeYAMLKey(s *LineScanner) {
	end, ok := yamlKeyEnd(s)
	if !ok {
		return
	}

	s.Emit(s.Pos(), end, ClassIdentifier)
	s.Advance(end - s.Pos())
	s.Take(1, ClassPunctuation) // the colon
	s.SkipSpaces()
}

// yamlKeyEnd returns where a key ends, and whether the line starts with one.
func yamlKeyEnd(s *LineScanner) (int, bool) {
	if quote := s.Peek(0); quote == '"' || quote == '\'' {
		return quotedYAMLKeyEnd(s, quote)
	}

	for at := s.Pos(); at < s.Len(); at++ {
		switch s.line[at] {
		case ':':
			// A colon ends a key only when a space or the end of the line
			// follows it. `http://x` is a value, not a key called `http`.
			if at+1 >= s.Len() || s.line[at+1] == ' ' {
				return at, at > s.Pos()
			}
		case '#':
			return 0, false
		}
	}
	return 0, false
}

// quotedYAMLKeyEnd returns where a quoted key ends, and whether it is one.
func quotedYAMLKeyEnd(s *LineScanner, quote rune) (int, bool) {
	for at := s.Pos() + 1; at < s.Len(); at++ {
		if s.line[at] != quote {
			continue
		}
		if at+1 < s.Len() && s.line[at+1] == ':' {
			return at + 1, true
		}
		return 0, false
	}
	return 0, false
}

// takeYAMLWord colours a bare word, which is a constant when YAML names it and
// plain text otherwise.
//
// A colon inside the word is taken with it unless a space follows, which is
// what keeps `nginx:1.27` and `http://example.com/x` one span each rather than
// three.
func takeYAMLWord(s *LineScanner) {
	start := s.Pos()
	for !s.AtEnd() {
		switch {
		case isYAMLNameRune(s.Peek(0)):
			s.Advance(1)
		case s.Peek(0) == ':' && !endsWord(s, 1):
			s.Advance(1)
		case s.Peek(0) == '/' && !endsWord(s, 1):
			s.Advance(1)
		default:
			s.Emit(start, s.Pos(), yamlWordClass(s, start))
			return
		}
	}

	s.Emit(start, s.Pos(), yamlWordClass(s, start))
}

// yamlWordClass says whether the word running from start to the scanner's
// position is one the language names.
func yamlWordClass(s *LineScanner, start int) Class {
	if yamlConstants[strings.ToLower(string(s.line[start:s.Pos()]))] {
		return ClassConstant
	}
	return ClassIdentifier
}

// endsWord reports whether the rune at an offset is a space or the end of the
// line — which is what tells a separator from a character inside a scalar.
func endsWord(s *LineScanner, offset int) bool {
	r := s.Peek(offset)
	return r == 0 || r == ' ' || r == '\t'
}

// yamlConstants are the words YAML 1.1 reads as booleans and nulls. The
// spelling is generous on purpose: `yes` and `on` are still true to most YAML
// parsers in the wild, and a reader wants to see that they are not plain text.
var yamlConstants = map[string]bool{
	"true": true, "false": true,
	"yes": true, "no": true,
	"on": true, "off": true,
	"null": true, "~": true,
}