diff --git a/m/embed-api.go b/m/embed-api.go index 2905ea69..c62a82d4 100644 --- a/m/embed-api.go +++ b/m/embed-api.go @@ -12,7 +12,7 @@ func (p *Pager) Page() error { return e } - p.StartPaging(screen) + p.StartPaging(screen, nil, nil) screen.Close() if p.DeInit { return nil diff --git a/m/pager.go b/m/pager.go index 10130258..dfd5addf 100644 --- a/m/pager.go +++ b/m/pager.go @@ -96,12 +96,6 @@ type Pager struct { // clear the last line, and show the cursor. DeInit bool - // Render the UI using this style - ChromaStyle *chroma.Style - - // Render the UI using this formatter - ChromaFormatter *chroma.Formatter - // Optional ANSI to prefix each text line with. Initialised using // ChromaStyle and ChromaFormatter. linePrefix string @@ -444,16 +438,17 @@ func (p *Pager) onRune(char rune) { } } -func (p *Pager) initStyle() { - if p.ChromaStyle == nil && p.ChromaFormatter == nil { - return +// Return an ANSI SGR sequence to use for plain text. Can be "". +func getLineColorPrefix(chromaStyle *chroma.Style, chromaFormatter *chroma.Formatter) string { + if chromaStyle == nil && chromaFormatter == nil { + return "" } - if p.ChromaStyle == nil || p.ChromaFormatter == nil { + if chromaStyle == nil || chromaFormatter == nil { panic("Both ChromaStyle and ChromaFormatter should be set or neither") } stringBuilder := strings.Builder{} - err := (*p.ChromaFormatter).Format(&stringBuilder, p.ChromaStyle, chroma.Literator(chroma.Token{ + err := (*chromaFormatter).Format(&stringBuilder, chromaStyle, chroma.Literator(chroma.Token{ Type: chroma.None, Value: "XXX", })) @@ -467,11 +462,11 @@ func (p *Pager) initStyle() { panic("XXX not found in " + formatted) } - p.linePrefix = formatted[:cutoff] + return formatted[:cutoff] } // StartPaging brings up the pager on screen -func (p *Pager) StartPaging(screen twin.Screen) { +func (p *Pager) StartPaging(screen twin.Screen, chromaStyle *chroma.Style, chromaFormatter *chroma.Formatter) { log.Trace("Pager starting") defer log.Trace("Pager done") @@ -482,10 +477,10 @@ func (p *Pager) StartPaging(screen twin.Screen) { }() unprintableStyle = p.UnprintableStyle - ConsumeLessTermcapEnvs(p.ChromaStyle, p.ChromaFormatter) + ConsumeLessTermcapEnvs(chromaStyle, chromaFormatter) p.screen = screen - p.initStyle() + p.linePrefix = getLineColorPrefix(chromaStyle, chromaFormatter) go func() { for range p.reader.moreLinesAdded { diff --git a/m/pager_test.go b/m/pager_test.go index fbfa15fb..fafc1975 100644 --- a/m/pager_test.go +++ b/m/pager_test.go @@ -106,7 +106,7 @@ func startPaging(t *testing.T, reader *Reader) *twin.FakeScreen { pager.Quit() // Except for just quitting, this also associates our FakeScreen with the Pager - pager.StartPaging(screen) + pager.StartPaging(screen, nil, nil) // This makes sure at least one frame gets rendered pager.redraw("") @@ -341,7 +341,7 @@ func TestScrollToBottomWrapNextToLastLine(t *testing.T) { pager.Quit() // Get contents onto our fake screen - pager.StartPaging(screen) + pager.StartPaging(screen, nil, nil) pager.redraw("") actual := strings.Join([]string{ @@ -372,7 +372,7 @@ func TestScrollToEndLongInput(t *testing.T) { // Connect the pager with a screen const screenHeight = 10 screen := twin.NewFakeScreen(20, screenHeight) - pager.StartPaging(screen) + pager.StartPaging(screen, nil, nil) // This is what we're really testing pager.scrollToEnd() @@ -507,7 +507,7 @@ func TestPageSamples(t *testing.T) { pager.Quit() // Get contents onto our fake screen - pager.StartPaging(screen) + pager.StartPaging(screen, nil, nil) pager.redraw("") firstReaderLine := myReader.GetLine(0) @@ -563,7 +563,7 @@ func TestClearToEndOfLine_ClearFromStartScrolledRight(t *testing.T) { // Except for just quitting, this also associates a FakeScreen with the Pager screen := twin.NewFakeScreen(3, 10) - pager.StartPaging(screen) + pager.StartPaging(screen, nil, nil) // Scroll right, this is what we're testing pager.leftColumnZeroBased = 44 @@ -583,22 +583,19 @@ func TestClearToEndOfLine_ClearFromStartScrolledRight(t *testing.T) { assert.DeepEqual(t, actual, expected, cmp.AllowUnexported(twin.Style{})) } -func TestInitStyle(t *testing.T) { - testMe := Pager{ - ChromaStyle: styles.Registry["gruvbox"], - ChromaFormatter: &formatters.TTY16m, - } - testMe.initStyle() - assert.Equal(t, testMe.linePrefix, "\x1b[38;2;235;219;178m") +func TestGetLineColorPrefix(t *testing.T) { + assert.Equal(t, + getLineColorPrefix(styles.Registry["gruvbox"], &formatters.TTY16m), + "\x1b[38;2;235;219;178m", + ) } func TestInitStyle256(t *testing.T) { - testMe := Pager{ - ChromaStyle: styles.Registry["catppuccin-macchiato"], - ChromaFormatter: &formatters.TTY256, - } - testMe.initStyle() - assert.Equal(t, testMe.linePrefix, "\x1b[38;5;189m") + assert.Equal(t, + getLineColorPrefix( + styles.Registry["catppuccin-macchiato"], + &formatters.TTY256), "\x1b[38;5;189m", + ) } func benchmarkSearch(b *testing.B, highlighted bool) { diff --git a/m/screenLines_test.go b/m/screenLines_test.go index 6fe9f5ce..7f955330 100644 --- a/m/screenLines_test.go +++ b/m/screenLines_test.go @@ -160,7 +160,7 @@ func TestWrapping(t *testing.T) { pager.Quit() // Get contents onto our fake screen - pager.StartPaging(screen) + pager.StartPaging(screen, nil, nil) pager.redraw("") actual := strings.Join([]string{ diff --git a/moar.go b/moar.go index 9a9ca057..1121fef8 100644 --- a/moar.go +++ b/moar.go @@ -166,7 +166,7 @@ func parseStyleOption(styleOption string) (chroma.Style, error) { return *style, nil } -func parseColorsOption(colorsOption string) (chroma.Formatter, error) { +func parseColorsOption(colorsOption string) (twin.ColorType, error) { if strings.ToLower(colorsOption) == "auto" { colorsOption = "16M" if strings.Contains(os.Getenv("TERM"), "256") { @@ -177,16 +177,17 @@ func parseColorsOption(colorsOption string) (chroma.Formatter, error) { switch strings.ToUpper(colorsOption) { case "8": - return formatters.TTY8, nil + return twin.ColorType8, nil case "16": - return formatters.TTY16, nil + return twin.ColorType16, nil case "256": - return formatters.TTY256, nil + return twin.ColorType256, nil case "16M": - return formatters.TTY16m, nil + return twin.ColorType24bit, nil } - return nil, fmt.Errorf("Valid counts are 8, 16, 256, 16M or auto.") + var noColor twin.ColorType + return noColor, fmt.Errorf("Valid counts are 8, 16, 256, 16M or auto.") } func parseStatusBarStyle(styleOption string) (m.StatusBarStyle, error) { @@ -385,7 +386,7 @@ func main() { if err != nil { panic(fmt.Errorf("Failed parsing default formatter: %w", err)) } - formatter := flagSetFunc(flagSet, + terminalColorsCount := flagSetFunc(flagSet, "colors", defaultFormatter, "Highlighting palette size: 8, 16, 256, 16M, auto", parseColorsOption) noLineNumbers := flagSet.Bool("no-linenumbers", false, "Hide line numbers on startup, press left arrow key to show") @@ -511,13 +512,22 @@ func main() { return } + formatter := formatters.TTY256 + if *terminalColorsCount == twin.ColorType8 { + formatter = formatters.TTY8 + } else if *terminalColorsCount == twin.ColorType16 { + formatter = formatters.TTY16 + } else if *terminalColorsCount == twin.ColorType24bit { + formatter = formatters.TTY + } + var reader *m.Reader if stdinIsRedirected { // Display input pipe contents reader = m.NewReaderFromStream("", os.Stdin) } else { // Display the input file contents - reader, err = m.NewReaderFromFilename(*inputFilename, *style, *formatter) + reader, err = m.NewReaderFromFilename(*inputFilename, *style, formatter) if err != nil { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) os.Exit(1) @@ -535,15 +545,13 @@ func main() { pager.ScrollLeftHint = *scrollLeftHint pager.ScrollRightHint = *scrollRightHint pager.SideScrollAmount = int(*shift) - pager.ChromaStyle = style - pager.ChromaFormatter = formatter pager.TargetLineNumberOneBased = targetLineNumberOneBased if *follow && pager.TargetLineNumberOneBased == 0 { pager.TargetLineNumberOneBased = math.MaxInt } - startPaging(pager, screen) + startPaging(pager, screen, style, &formatter) } // Define a generic flag with specified name, default value, and usage string. @@ -564,7 +572,7 @@ func flagSetFunc[T any](flagSet *flag.FlagSet, name string, defaultValue T, usag return &parsed } -func startPaging(pager *m.Pager, screen twin.Screen) { +func startPaging(pager *m.Pager, screen twin.Screen, chromaStyle *chroma.Style, chromaFormatter *chroma.Formatter) { defer func() { // Restore screen... screen.Close() @@ -583,5 +591,5 @@ func startPaging(pager *m.Pager, screen twin.Screen) { } }() - pager.StartPaging(screen) + pager.StartPaging(screen, chromaStyle, chromaFormatter) } diff --git a/twin/colors.go b/twin/colors.go index f4a5b272..47b82030 100644 --- a/twin/colors.go +++ b/twin/colors.go @@ -12,13 +12,19 @@ const ( colorTypeDefault ColorType = iota // https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit - colorType16 + // + // Note that this type is only used for output, on input we store 3 bit + // colors as 4 bit colors since they map to the same values. + ColorType8 + + // https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit + ColorType16 // https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit - colorType256 + ColorType256 // RGB: https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit - colorType24bit + ColorType24bit ) // Reset to default foreground / background color @@ -51,19 +57,19 @@ func newColor(colorType ColorType, value uint32) Color { // Four bit colors as defined here: // https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit func NewColor16(colorNumber0to15 int) Color { - return newColor(colorType16, uint32(colorNumber0to15)) + return newColor(ColorType16, uint32(colorNumber0to15)) } func NewColor256(colorNumber uint8) Color { - return newColor(colorType256, uint32(colorNumber)) + return newColor(ColorType256, uint32(colorNumber)) } func NewColor24Bit(red uint8, green uint8, blue uint8) Color { - return newColor(colorType24bit, (uint32(red)<<16)+(uint32(green)<<8)+(uint32(blue)<<0)) + return newColor(ColorType24bit, (uint32(red)<<16)+(uint32(green)<<8)+(uint32(blue)<<0)) } func NewColorHex(rgb uint32) Color { - return newColor(colorType24bit, rgb) + return newColor(ColorType24bit, rgb) } func (color Color) colorType() ColorType { @@ -85,7 +91,7 @@ func (color Color) ansiString(foreground bool) string { fgBgMarker = "4" } - if color.colorType() == colorType16 { + if color.colorType() == ColorType16 { if value < 8 { return fmt.Sprint("\x1b[", fgBgMarker, value, "m") } else if value <= 15 { @@ -97,13 +103,13 @@ func (color Color) ansiString(foreground bool) string { } } - if color.colorType() == colorType256 { + if color.colorType() == ColorType256 { if value <= 255 { return fmt.Sprint("\x1b[", fgBgMarker, "8;5;", value, "m") } } - if color.colorType() == colorType24bit { + if color.colorType() == ColorType24bit { red := (value & 0xff0000) >> 16 green := (value & 0xff00) >> 8 blue := value & 0xff @@ -133,16 +139,16 @@ func (color Color) String() string { case colorTypeDefault: return "Default color" - case colorType16: + case ColorType16: return colorNames16[int(color.colorValue())] - case colorType256: + case ColorType256: if color.colorValue() < 16 { return colorNames16[int(color.colorValue())] } return fmt.Sprintf("#%02x", color.colorValue()) - case colorType24bit: + case ColorType24bit: return fmt.Sprintf("#%06x", color.colorValue()) }