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) }