turbo-editors/turbo-corepublic Fork 0
d662cebdb65b319885da903daf7eff9ab1bfbb78
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 d662cebdb65b319885da903daf7eff9ab1bfbb78 · k33g · 21h ago
parser_sgr.go · 174 lines · 6.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
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
package terminal

import "github.com/gdamore/tcell/v2"

// ansiColors are the sixteen colours a program names by number.
//
// They are given as tcell's own named colours rather than as fixed hex values,
// so what appears on screen is the palette the user chose for their terminal —
// which is what they expect a program's "red" to look like.
var ansiColors = [16]tcell.Color{
	tcell.ColorBlack, tcell.ColorMaroon, tcell.ColorGreen, tcell.ColorOlive,
	tcell.ColorNavy, tcell.ColorPurple, tcell.ColorTeal, tcell.ColorSilver,
	tcell.ColorGray, tcell.ColorRed, tcell.ColorLime, tcell.ColorYellow,
	tcell.ColorBlue, tcell.ColorFuchsia, tcell.ColorAqua, tcell.ColorWhite,
}

// runSGR performs a Select Graphic Rendition sequence, ESC [ … m, which is how
// a program sets colours and attributes.
//
// The parameters are read in order because two of them — 38 and 48 — swallow
// the ones that follow to spell out a colour.
func (p *Parser) runSGR() {
	if len(p.params) == 0 {
		p.screen.SetStyle(tcell.StyleDefault)
		return
	}

	style := p.screen.Style()
	for i := 0; i < len(p.params); i++ {
		style, i = applySGR(style, p.params, i)
	}
	p.screen.SetStyle(style)
}

// directColors are the SGR codes that name one of the sixteen colours outright,
// as four ranges of eight: normal foreground, normal background, and the two
// bright halves that offset into the top of the palette.
var directColors = []struct {
	low, high int
	offset    int
	which     side
}{
	{low: 30, high: 37, offset: 0, which: foreground},
	{low: 40, high: 47, offset: 0, which: background},
	{low: 90, high: 97, offset: 8, which: foreground},
	{low: 100, high: 107, offset: 8, which: background},
}

// applySGR applies the parameter at index i and returns the style and the
// index of the last parameter it consumed.
func applySGR(style tcell.Style, params []int, i int) (tcell.Style, int) {
	code := params[i]

	// 38 and 48 are the only codes that read the parameters after them, which
	// is why applying a run of them cannot simply be a loop over each one.
	switch code {
	case 38:
		return extendedColor(style, params, i, foreground)
	case 48:
		return extendedColor(style, params, i, background)
	}

	if coloured, ok := directColor(style, code); ok {
		return coloured, i
	}
	return applyAttribute(style, code), i
}

// directColor applies a code that names one of the sixteen colours, and reports
// whether the code was one of them.
func directColor(style tcell.Style, code int) (tcell.Style, bool) {
	for _, r := range directColors {
		if code >= r.low && code <= r.high {
			return paint(style, r.which, ansiColors[code-r.low+r.offset]), true
		}
	}
	return style, false
}

// styleAttributes are the SGR codes that are not colours, and what each does to
// a style.
//
// A table rather than a switch because a table is what it is — a list out of
// ECMA-48 — and because the setting and the clearing of an attribute then sit
// one line apart, where a wrong pairing is visible.
var styleAttributes = map[int]func(tcell.Style) tcell.Style{
	1:  func(s tcell.Style) tcell.Style { return s.Bold(true) },
	22: func(s tcell.Style) tcell.Style { return s.Bold(false).Dim(false) },
	2:  func(s tcell.Style) tcell.Style { return s.Dim(true) },
	3:  func(s tcell.Style) tcell.Style { return s.Italic(true) },
	23: func(s tcell.Style) tcell.Style { return s.Italic(false) },
	4:  func(s tcell.Style) tcell.Style { return s.Underline(true) },
	24: func(s tcell.Style) tcell.Style { return s.Underline(false) },
	5:  func(s tcell.Style) tcell.Style { return s.Blink(true) },
	6:  func(s tcell.Style) tcell.Style { return s.Blink(true) }, // "rapid" blink, blinked the same
	25: func(s tcell.Style) tcell.Style { return s.Blink(false) },
	7:  func(s tcell.Style) tcell.Style { return s.Reverse(true) },
	27: func(s tcell.Style) tcell.Style { return s.Reverse(false) },
	9:  func(s tcell.Style) tcell.Style { return s.StrikeThrough(true) },
	29: func(s tcell.Style) tcell.Style { return s.StrikeThrough(false) },
	39: func(s tcell.Style) tcell.Style { return s.Foreground(tcell.ColorDefault) },
	49: func(s tcell.Style) tcell.Style { return s.Background(tcell.ColorDefault) },
}

// applyAttribute applies the SGR codes that are not colours.
//
// A code this emulator does not implement leaves the style as it was, which is
// what a terminal is expected to do with one it does not know.
func applyAttribute(style tcell.Style, code int) tcell.Style {
	if code == 0 {
		return tcell.StyleDefault // reset, which no table entry could express
	}
	if apply, ok := styleAttributes[code]; ok {
		return apply(style)
	}
	return style
}

// side says which half of a style an extended colour sets.
type side int

const (
	foreground side = iota
	background
)

// extendedColor reads a 256-colour or 24-bit colour spelled out across several
// parameters, and returns the style and the index of the last one used.
//
//	38;5;n        one of the 256 palette colours
//	38;2;r;g;b    a 24-bit colour
//
// A sequence that runs out of parameters partway leaves the style alone: half
// a colour is not a colour.
func extendedColor(style tcell.Style, params []int, i int, which side) (tcell.Style, int) {
	if i+1 >= len(params) {
		return style, i
	}

	switch params[i+1] {
	case 5:
		return paletteColor(style, params, i, which)
	case 2:
		return rgbColor(style, params, i, which)
	default:
		return style, i + 1
	}
}

// paletteColor reads the 38;5;n form, one of the 256 palette colours.
func paletteColor(style tcell.Style, params []int, i int, which side) (tcell.Style, int) {
	if i+2 >= len(params) {
		return style, len(params) - 1
	}
	return paint(style, which, tcell.PaletteColor(params[i+2]&0xff)), i + 2
}

// rgbColor reads the 38;2;r;g;b form, a 24-bit colour.
func rgbColor(style tcell.Style, params []int, i int, which side) (tcell.Style, int) {
	if i+4 >= len(params) {
		return style, len(params) - 1
	}

	colour := tcell.NewRGBColor(int32(params[i+2]), int32(params[i+3]), int32(params[i+4]))
	return paint(style, which, colour), i + 4
}

// paint sets one half of a style to a colour.
func paint(style tcell.Style, which side, colour tcell.Color) tcell.Style {
	if which == foreground {
		return style.Foreground(colour)
	}
	return style.Background(colour)
}