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:
parent
0dc490262b
commit
eaab95e3ef
2
moar.go
2
moar.go
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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 := "[m"
|
reset := "[m"
|
||||||
reversed := "[7m"
|
reversed := "[7m"
|
||||||
@ -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 := "[m"
|
reset := "[m"
|
||||||
reversed := "[7m"
|
reversed := "[7m"
|
||||||
@ -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 := "[m"
|
reset := "[m"
|
||||||
clearToEol := "[K"
|
clearToEol := "[K"
|
||||||
@ -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 := "[m"
|
reset := "[m"
|
||||||
reversed := "[7m"
|
reversed := "[7m"
|
||||||
@ -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 := "[m"
|
reset := "[m"
|
||||||
reversed := "[7m"
|
reversed := "[7m"
|
||||||
@ -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 := "[m"
|
reset := "[m"
|
||||||
white := "[37m"
|
white := "[37m"
|
||||||
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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\\")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user