1
1
mirror of https://github.com/walles/moar.git synced 2024-11-22 21:50:43 +03:00

Inform ansiString() about terminal color count

So that it can downsample colors when needed.
This commit is contained in:
Johan Walles 2023-12-17 16:34:34 +01:00
parent 0dc490262b
commit eaab95e3ef
6 changed files with 44 additions and 28 deletions

View File

@ -500,7 +500,7 @@ func main() {
panic("Invariant broken: stdout is not a terminal") panic("Invariant broken: stdout is not a terminal")
} }
screen, err := twin.NewScreenWithMouseMode(*mouseMode) screen, err := twin.NewScreenWithMouseModeAndColorType(*mouseMode, *terminalColorsCount)
if err != nil { if err != nil {
// Ref: https://github.com/walles/moar/issues/149 // Ref: https://github.com/walles/moar/issues/149
log.Debug("Failed to set up screen for paging, pumping to stdout instead: ", err) log.Debug("Failed to set up screen for paging, pumping to stdout instead: ", err)

View File

@ -1,6 +1,8 @@
package twin package twin
import "fmt" import (
"fmt"
)
// Create using NewColor16(), NewColor256 or NewColor24Bit(), or use // Create using NewColor16(), NewColor256 or NewColor24Bit(), or use
// ColorDefault. // ColorDefault.
@ -83,7 +85,8 @@ func (color Color) colorValue() uint32 {
// Render color into an ANSI string. // Render color into an ANSI string.
// //
// Ref: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters // Ref: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
func (color Color) ansiString(foreground bool) string { func (color Color) ansiString(foreground bool, terminalColorCount ColorType) string {
// FIXME: Downsample colors to at most terminalColorCount colors as needed.
value := color.colorValue() value := color.colorValue()
fgBgMarker := "3" fgBgMarker := "3"
@ -124,14 +127,14 @@ func (color Color) ansiString(foreground bool) string {
panic(fmt.Errorf("unhandled color type=%d value=%#x", color.colorType(), value)) panic(fmt.Errorf("unhandled color type=%d value=%#x", color.colorType(), value))
} }
func (color Color) ForegroundAnsiString() string { func (color Color) ForegroundAnsiString(terminalColorCount ColorType) string {
// FIXME: Test this function with all different color types. // FIXME: Test this function with all different color types.
return color.ansiString(true) return color.ansiString(true, terminalColorCount)
} }
func (color Color) BackgroundAnsiString() string { func (color Color) BackgroundAnsiString(terminalColorCount ColorType) string {
// FIXME: Test this function with all different color types. // FIXME: Test this function with all different color types.
return color.ansiString(false) return color.ansiString(false, terminalColorCount)
} }
func (color Color) String() string { func (color Color) String() string {

View File

@ -77,6 +77,8 @@ type UnixScreen struct {
ttyOut *os.File ttyOut *os.File
oldTtyOutMode uint32 //nolint Windows only oldTtyOutMode uint32 //nolint Windows only
terminalColorCount ColorType
} }
// Example event: "\x1b[<65;127;41M" // Example event: "\x1b[<65;127;41M"
@ -94,18 +96,29 @@ type UnixScreen struct {
// * "M" marks the end of the mouse event. // * "M" marks the end of the mouse event.
var MOUSE_EVENT_REGEX = regexp.MustCompile("^\x1b\\[<([0-9]+);([0-9]+);([0-9]+)M") var MOUSE_EVENT_REGEX = regexp.MustCompile("^\x1b\\[<([0-9]+);([0-9]+);([0-9]+)M")
// NewScreen() requires Close() to be called after you are done with your new
// screen, most likely somewhere in your shutdown code.
func NewScreen() (Screen, error) { func NewScreen() (Screen, error) {
return NewScreenWithMouseMode(MouseModeAuto) return NewScreenWithMouseMode(MouseModeAuto)
} }
// NewScreen() requires Close() to be called after you are done with your new
// screen, most likely somewhere in your shutdown code.
func NewScreenWithMouseMode(mouseMode MouseMode) (Screen, error) { func NewScreenWithMouseMode(mouseMode MouseMode) (Screen, error) {
terminalColorCount := ColorType24bit
if strings.Contains(os.Getenv("TERM"), "256") {
// Covers "xterm-256color" as used by the macOS Terminal
terminalColorCount = ColorType256
}
return NewScreenWithMouseModeAndColorType(mouseMode, terminalColorCount)
}
func NewScreenWithMouseModeAndColorType(mouseMode MouseMode, terminalColorCount ColorType) (Screen, error) {
if !term.IsTerminal(int(os.Stdout.Fd())) { if !term.IsTerminal(int(os.Stdout.Fd())) {
return nil, fmt.Errorf("stdout (fd=%d) must be a terminal for paging to work", os.Stdout.Fd()) return nil, fmt.Errorf("stdout (fd=%d) must be a terminal for paging to work", os.Stdout.Fd())
} }
screen := UnixScreen{} screen := UnixScreen{
terminalColorCount: terminalColorCount,
}
// The number "80" here is from manual testing on my MacBook: // The number "80" here is from manual testing on my MacBook:
// //
@ -505,7 +518,7 @@ func (screen *UnixScreen) Clear() {
// Returns the rendered line, plus how many information carrying cells went into // Returns the rendered line, plus how many information carrying cells went into
// it // it
func renderLine(row []Cell) (string, int) { func renderLine(row []Cell, terminalColorCount ColorType) (string, int) {
// Strip trailing whitespace // Strip trailing whitespace
lastSignificantCellIndex := len(row) - 1 lastSignificantCellIndex := len(row) - 1
for ; lastSignificantCellIndex >= 0; lastSignificantCellIndex-- { for ; lastSignificantCellIndex >= 0; lastSignificantCellIndex-- {
@ -538,7 +551,7 @@ func renderLine(row []Cell) (string, int) {
} }
if style != lastStyle { if style != lastStyle {
builder.WriteString(style.RenderUpdateFrom(lastStyle)) builder.WriteString(style.RenderUpdateFrom(lastStyle, terminalColorCount))
lastStyle = style lastStyle = style
} }
@ -547,7 +560,7 @@ func renderLine(row []Cell) (string, int) {
// Clear to end of line // Clear to end of line
// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences
builder.WriteString(StyleDefault.RenderUpdateFrom(lastStyle)) builder.WriteString(StyleDefault.RenderUpdateFrom(lastStyle, terminalColorCount))
builder.WriteString("\x1b[K") builder.WriteString("\x1b[K")
return builder.String(), len(row) return builder.String(), len(row)
@ -572,7 +585,7 @@ func (screen *UnixScreen) showNLines(height int, clearFirst bool) {
} }
for row := 0; row < height; row++ { for row := 0; row < height; row++ {
rendered, lineLength := renderLine(screen.cells[row]) rendered, lineLength := renderLine(screen.cells[row], screen.terminalColorCount)
builder.WriteString(rendered) builder.WriteString(rendered)
wasLastLine := row == (height - 1) wasLastLine := row == (height - 1)

View File

@ -61,7 +61,7 @@ func TestRenderLine(t *testing.T) {
}, },
} }
rendered, count := renderLine(row) rendered, count := renderLine(row, ColorType16)
assert.Equal(t, count, 2) assert.Equal(t, count, 2)
reset := "" reset := ""
reversed := "" reversed := ""
@ -76,7 +76,7 @@ func TestRenderLine(t *testing.T) {
func TestRenderLineEmpty(t *testing.T) { func TestRenderLineEmpty(t *testing.T) {
row := []Cell{} row := []Cell{}
rendered, count := renderLine(row) rendered, count := renderLine(row, ColorType16)
assert.Equal(t, count, 0) assert.Equal(t, count, 0)
// All lines are expected to stand on their own, so we always need to clear // All lines are expected to stand on their own, so we always need to clear
@ -92,7 +92,7 @@ func TestRenderLineLastReversed(t *testing.T) {
}, },
} }
rendered, count := renderLine(row) rendered, count := renderLine(row, ColorType16)
assert.Equal(t, count, 1) assert.Equal(t, count, 1)
reset := "" reset := ""
reversed := "" reversed := ""
@ -110,7 +110,7 @@ func TestRenderLineLastNonSpace(t *testing.T) {
}, },
} }
rendered, count := renderLine(row) rendered, count := renderLine(row, ColorType16)
assert.Equal(t, count, 1) assert.Equal(t, count, 1)
reset := "" reset := ""
clearToEol := "" clearToEol := ""
@ -131,7 +131,7 @@ func TestRenderLineLastReversedPlusTrailingSpace(t *testing.T) {
}, },
} }
rendered, count := renderLine(row) rendered, count := renderLine(row, ColorType16)
assert.Equal(t, count, 1) assert.Equal(t, count, 1)
reset := "" reset := ""
reversed := "" reversed := ""
@ -153,7 +153,7 @@ func TestRenderLineOnlyTrailingSpaces(t *testing.T) {
}, },
} }
rendered, count := renderLine(row) rendered, count := renderLine(row, ColorType16)
assert.Equal(t, count, 0) assert.Equal(t, count, 0)
// All lines are expected to stand on their own, so we always need to clear // All lines are expected to stand on their own, so we always need to clear
@ -169,7 +169,7 @@ func TestRenderLineLastReversedSpaces(t *testing.T) {
}, },
} }
rendered, count := renderLine(row) rendered, count := renderLine(row, ColorType16)
assert.Equal(t, count, 1) assert.Equal(t, count, 1)
reset := "" reset := ""
reversed := "" reversed := ""
@ -186,7 +186,7 @@ func TestRenderLineNonPrintable(t *testing.T) {
}, },
} }
rendered, count := renderLine(row) rendered, count := renderLine(row, ColorType16)
assert.Equal(t, count, 1) assert.Equal(t, count, 1)
reset := "" reset := ""
white := "" white := ""
@ -207,7 +207,7 @@ func TestRenderHyperlinkAtEndOfLine(t *testing.T) {
}, },
} }
rendered, count := renderLine(row) rendered, count := renderLine(row, ColorType16)
assert.Equal(t, count, 1) assert.Equal(t, count, 1)
assert.Equal(t, assert.Equal(t,
@ -232,7 +232,7 @@ func TestMultiCharHyperlink(t *testing.T) {
}, },
} }
rendered, count := renderLine(row) rendered, count := renderLine(row, ColorType16)
assert.Equal(t, count, 3) assert.Equal(t, count, 3)
assert.Equal(t, assert.Equal(t,

View File

@ -137,7 +137,7 @@ func (style Style) Foreground(color Color) Style {
// Emit an ANSI escape sequence switching from a previous style to the current // Emit an ANSI escape sequence switching from a previous style to the current
// one. // one.
func (current Style) RenderUpdateFrom(previous Style) string { func (current Style) RenderUpdateFrom(previous Style, terminalColorCount ColorType) string {
if current == previous { if current == previous {
// Shortcut for the common case // Shortcut for the common case
return "" return ""
@ -150,11 +150,11 @@ func (current Style) RenderUpdateFrom(previous Style) string {
var builder strings.Builder var builder strings.Builder
if current.fg != previous.fg { if current.fg != previous.fg {
builder.WriteString(current.fg.ForegroundAnsiString()) builder.WriteString(current.fg.ForegroundAnsiString(terminalColorCount))
} }
if current.bg != previous.bg { if current.bg != previous.bg {
builder.WriteString(current.bg.BackgroundAnsiString()) builder.WriteString(current.bg.BackgroundAnsiString(terminalColorCount))
} }
// Handle AttrDim / AttrBold changes // Handle AttrDim / AttrBold changes

View File

@ -12,6 +12,6 @@ func TestHyperlinkToNormal(t *testing.T) {
style := StyleDefault.WithHyperlink(&url) style := StyleDefault.WithHyperlink(&url)
assert.Equal(t, assert.Equal(t,
strings.ReplaceAll(StyleDefault.RenderUpdateFrom(style), "", "ESC"), strings.ReplaceAll(StyleDefault.RenderUpdateFrom(style, ColorType16), "", "ESC"),
"ESC]8;;ESC\\") "ESC]8;;ESC\\")
} }