| 🛟 Updated. 28d5985 k33g 15h ago | 1 | package terminal |
| 2 | |
| 3 | import "github.com/gdamore/tcell/v2" |
| 4 | |
| 5 | // ansiColors are the sixteen colours a program names by number. |
| 6 | // |
| 7 | // They are given as tcell's own named colours rather than as fixed hex values, |
| 8 | // so what appears on screen is the palette the user chose for their terminal — |
| 9 | // which is what they expect a program's "red" to look like. |
| 10 | var ansiColors = [16]tcell.Color{ |
| 11 | tcell.ColorBlack, tcell.ColorMaroon, tcell.ColorGreen, tcell.ColorOlive, |
| 12 | tcell.ColorNavy, tcell.ColorPurple, tcell.ColorTeal, tcell.ColorSilver, |
| 13 | tcell.ColorGray, tcell.ColorRed, tcell.ColorLime, tcell.ColorYellow, |
| 14 | tcell.ColorBlue, tcell.ColorFuchsia, tcell.ColorAqua, tcell.ColorWhite, |
| 15 | } |
| 16 | |
| 17 | // runSGR performs a Select Graphic Rendition sequence, ESC [ … m, which is how |
| 18 | // a program sets colours and attributes. |
| 19 | // |
| 20 | // The parameters are read in order because two of them — 38 and 48 — swallow |
| 21 | // the ones that follow to spell out a colour. |
| 22 | func (p *Parser) runSGR() { |
| 23 | if len(p.params) == 0 { |
| 24 | p.screen.SetStyle(tcell.StyleDefault) |
| 25 | return |
| 26 | } |
| 27 | |
| 28 | style := p.screen.Style() |
| 29 | for i := 0; i < len(p.params); i++ { |
| 30 | style, i = applySGR(style, p.params, i) |
| 31 | } |
| 32 | p.screen.SetStyle(style) |
| 33 | } |
| 34 | |
| 35 | // directColors are the SGR codes that name one of the sixteen colours outright, |
| 36 | // as four ranges of eight: normal foreground, normal background, and the two |
| 37 | // bright halves that offset into the top of the palette. |
| 38 | var directColors = []struct { |
| 39 | low, high int |
| 40 | offset int |
| 41 | which side |
| 42 | }{ |
| 43 | {low: 30, high: 37, offset: 0, which: foreground}, |
| 44 | {low: 40, high: 47, offset: 0, which: background}, |
| 45 | {low: 90, high: 97, offset: 8, which: foreground}, |
| 46 | {low: 100, high: 107, offset: 8, which: background}, |
| 47 | } |
| 48 | |
| 49 | // applySGR applies the parameter at index i and returns the style and the |
| 50 | // index of the last parameter it consumed. |
| 51 | func applySGR(style tcell.Style, params []int, i int) (tcell.Style, int) { |
| 52 | code := params[i] |
| 53 | |
| 54 | // 38 and 48 are the only codes that read the parameters after them, which |
| 55 | // is why applying a run of them cannot simply be a loop over each one. |
| 56 | switch code { |
| 57 | case 38: |
| 58 | return extendedColor(style, params, i, foreground) |
| 59 | case 48: |
| 60 | return extendedColor(style, params, i, background) |
| 61 | } |
| 62 | |
| 63 | if coloured, ok := directColor(style, code); ok { |
| 64 | return coloured, i |
| 65 | } |
| 66 | return applyAttribute(style, code), i |
| 67 | } |
| 68 | |
| 69 | // directColor applies a code that names one of the sixteen colours, and reports |
| 70 | // whether the code was one of them. |
| 71 | func directColor(style tcell.Style, code int) (tcell.Style, bool) { |
| 72 | for _, r := range directColors { |
| 73 | if code >= r.low && code <= r.high { |
| 74 | return paint(style, r.which, ansiColors[code-r.low+r.offset]), true |
| 75 | } |
| 76 | } |
| 77 | return style, false |
| 78 | } |
| 79 | |
| 80 | // styleAttributes are the SGR codes that are not colours, and what each does to |
| 81 | // a style. |
| 82 | // |
| 83 | // A table rather than a switch because a table is what it is — a list out of |
| 84 | // ECMA-48 — and because the setting and the clearing of an attribute then sit |
| 85 | // one line apart, where a wrong pairing is visible. |
| 86 | var styleAttributes = map[int]func(tcell.Style) tcell.Style{ |
| 87 | 1: func(s tcell.Style) tcell.Style { return s.Bold(true) }, |
| 88 | 22: func(s tcell.Style) tcell.Style { return s.Bold(false).Dim(false) }, |
| 89 | 2: func(s tcell.Style) tcell.Style { return s.Dim(true) }, |
| 90 | 3: func(s tcell.Style) tcell.Style { return s.Italic(true) }, |
| 91 | 23: func(s tcell.Style) tcell.Style { return s.Italic(false) }, |
| 92 | 4: func(s tcell.Style) tcell.Style { return s.Underline(true) }, |
| 93 | 24: func(s tcell.Style) tcell.Style { return s.Underline(false) }, |
| 94 | 5: func(s tcell.Style) tcell.Style { return s.Blink(true) }, |
| 95 | 6: func(s tcell.Style) tcell.Style { return s.Blink(true) }, // "rapid" blink, blinked the same |
| 96 | 25: func(s tcell.Style) tcell.Style { return s.Blink(false) }, |
| 97 | 7: func(s tcell.Style) tcell.Style { return s.Reverse(true) }, |
| 98 | 27: func(s tcell.Style) tcell.Style { return s.Reverse(false) }, |
| 99 | 9: func(s tcell.Style) tcell.Style { return s.StrikeThrough(true) }, |
| 100 | 29: func(s tcell.Style) tcell.Style { return s.StrikeThrough(false) }, |
| 101 | 39: func(s tcell.Style) tcell.Style { return s.Foreground(tcell.ColorDefault) }, |
| 102 | 49: func(s tcell.Style) tcell.Style { return s.Background(tcell.ColorDefault) }, |
| 103 | } |
| 104 | |
| 105 | // applyAttribute applies the SGR codes that are not colours. |
| 106 | // |
| 107 | // A code this emulator does not implement leaves the style as it was, which is |
| 108 | // what a terminal is expected to do with one it does not know. |
| 109 | func applyAttribute(style tcell.Style, code int) tcell.Style { |
| 110 | if code == 0 { |
| 111 | return tcell.StyleDefault // reset, which no table entry could express |
| 112 | } |
| 113 | if apply, ok := styleAttributes[code]; ok { |
| 114 | return apply(style) |
| 115 | } |
| 116 | return style |
| 117 | } |
| 118 | |
| 119 | // side says which half of a style an extended colour sets. |
| 120 | type side int |
| 121 | |
| 122 | const ( |
| 123 | foreground side = iota |
| 124 | background |
| 125 | ) |
| 126 | |
| 127 | // extendedColor reads a 256-colour or 24-bit colour spelled out across several |
| 128 | // parameters, and returns the style and the index of the last one used. |
| 129 | // |
| 130 | // 38;5;n one of the 256 palette colours |
| 131 | // 38;2;r;g;b a 24-bit colour |
| 132 | // |
| 133 | // A sequence that runs out of parameters partway leaves the style alone: half |
| 134 | // a colour is not a colour. |
| 135 | func extendedColor(style tcell.Style, params []int, i int, which side) (tcell.Style, int) { |
| 136 | if i+1 >= len(params) { |
| 137 | return style, i |
| 138 | } |
| 139 | |
| 140 | switch params[i+1] { |
| 141 | case 5: |
| 142 | return paletteColor(style, params, i, which) |
| 143 | case 2: |
| 144 | return rgbColor(style, params, i, which) |
| 145 | default: |
| 146 | return style, i + 1 |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | // paletteColor reads the 38;5;n form, one of the 256 palette colours. |
| 151 | func paletteColor(style tcell.Style, params []int, i int, which side) (tcell.Style, int) { |
| 152 | if i+2 >= len(params) { |
| 153 | return style, len(params) - 1 |
| 154 | } |
| 155 | return paint(style, which, tcell.PaletteColor(params[i+2]&0xff)), i + 2 |
| 156 | } |
| 157 | |
| 158 | // rgbColor reads the 38;2;r;g;b form, a 24-bit colour. |
| 159 | func rgbColor(style tcell.Style, params []int, i int, which side) (tcell.Style, int) { |
| 160 | if i+4 >= len(params) { |
| 161 | return style, len(params) - 1 |
| 162 | } |
| 163 | |
| 164 | colour := tcell.NewRGBColor(int32(params[i+2]), int32(params[i+3]), int32(params[i+4])) |
| 165 | return paint(style, which, colour), i + 4 |
| 166 | } |
| 167 | |
| 168 | // paint sets one half of a style to a colour. |
| 169 | func paint(style tcell.Style, which side, colour tcell.Color) tcell.Style { |
| 170 | if which == foreground { |
| 171 | return style.Foreground(colour) |
| 172 | } |
| 173 | return style.Background(colour) |
| 174 | } |