mirror of
https://github.com/walles/moar.git
synced 2024-09-11 12:15:43 +03:00
Use our new tcell replacement
This commit is contained in:
parent
44a064c024
commit
90e374601d
2
build.sh
2
build.sh
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION="$(git describe --dirty)"
|
||||
VERSION="$(git describe --tags --dirty --always)"
|
||||
|
||||
BINARY="moar"
|
||||
if [ -n "$GOOS$GOARCH" ] ; then
|
||||
|
8
go.mod
8
go.mod
@ -1,12 +1,12 @@
|
||||
module github.com/walles/moar
|
||||
|
||||
go 1.12
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma v0.8.2
|
||||
github.com/gdamore/tcell/v2 v2.0.0
|
||||
github.com/google/go-cmp v0.3.0 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
|
||||
gotest.tools v2.2.0+incompatible
|
||||
)
|
||||
|
10
go.sum
10
go.sum
@ -13,10 +13,10 @@ github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.0.0 h1:GRWG8aLfWAlekj9Q6W29bVvkHENc6hp79XOqG4AWDOs=
|
||||
github.com/gdamore/tcell/v2 v2.0.0/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@ -65,9 +65,15 @@ golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HX
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
|
||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
|
@ -8,44 +8,37 @@ import (
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/walles/moar/twin"
|
||||
)
|
||||
|
||||
const _TabSize = 4
|
||||
|
||||
var manPageBold = tcell.StyleDefault.Bold(true)
|
||||
var manPageUnderline = tcell.StyleDefault.Underline(true)
|
||||
var manPageBold = twin.StyleDefault.WithAttr(twin.AttrBold)
|
||||
var manPageUnderline = twin.StyleDefault.WithAttr(twin.AttrUnderline)
|
||||
|
||||
// ESC[...m: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR
|
||||
var sgrSequencePattern = regexp.MustCompile("\x1b\\[([0-9;]*m)")
|
||||
|
||||
// Token is a rune with a style to be written to a cell on screen
|
||||
type Token struct {
|
||||
Rune rune
|
||||
Style tcell.Style
|
||||
}
|
||||
|
||||
// A Line represents a line of text that can / will be paged
|
||||
type Line struct {
|
||||
raw *string
|
||||
plain *string
|
||||
tokens []Token
|
||||
raw *string
|
||||
plain *string
|
||||
cells []twin.Cell
|
||||
}
|
||||
|
||||
// NewLine creates a new Line from a (potentially ANSI / man page formatted) string
|
||||
func NewLine(raw string) *Line {
|
||||
return &Line{
|
||||
raw: &raw,
|
||||
plain: nil,
|
||||
tokens: nil,
|
||||
raw: &raw,
|
||||
plain: nil,
|
||||
cells: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Tokens returns a representation of the string split into styled tokens
|
||||
func (line *Line) Tokens() []Token {
|
||||
func (line *Line) Tokens() []twin.Cell {
|
||||
line.parse()
|
||||
return line.tokens
|
||||
return line.cells
|
||||
}
|
||||
|
||||
// Plain returns a plain text representation of the initial string
|
||||
@ -60,7 +53,7 @@ func (line *Line) parse() {
|
||||
return
|
||||
}
|
||||
|
||||
line.tokens, line.plain = tokensFromString(*line.raw)
|
||||
line.cells, line.plain = cellsFromString(*line.raw)
|
||||
line.raw = nil
|
||||
}
|
||||
|
||||
@ -82,22 +75,23 @@ func SetManPageFormatFromEnv() {
|
||||
|
||||
// Used from tests
|
||||
func resetManPageFormatForTesting() {
|
||||
manPageBold = tcell.StyleDefault.Bold(true)
|
||||
manPageUnderline = tcell.StyleDefault.Underline(true)
|
||||
manPageBold = twin.StyleDefault.WithAttr(twin.AttrBold)
|
||||
manPageUnderline = twin.StyleDefault.WithAttr(twin.AttrUnderline)
|
||||
}
|
||||
|
||||
func termcapToStyle(termcap string) tcell.Style {
|
||||
func termcapToStyle(termcap string) twin.Style {
|
||||
// Add a character to be sure we have one to take the format from
|
||||
tokens, _ := tokensFromString(termcap + "x")
|
||||
return tokens[len(tokens)-1].Style
|
||||
cells, _ := cellsFromString(termcap + "x")
|
||||
return cells[len(cells)-1].Style
|
||||
}
|
||||
|
||||
// tokensFromString turns a (formatted) string into a series of tokens,
|
||||
// cellsFromString turns a (formatted) string into a series of screen cells,
|
||||
// and an unformatted string
|
||||
func tokensFromString(s string) ([]Token, *string) {
|
||||
var tokens []Token
|
||||
func cellsFromString(s string) ([]twin.Cell, *string) {
|
||||
var tokens []twin.Cell
|
||||
|
||||
styleBrokenUtf8 := tcell.StyleDefault.Background(tcell.ColorSilver).Foreground(tcell.ColorMaroon)
|
||||
// Specs: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
|
||||
styleBrokenUtf8 := twin.StyleDefault.Background(twin.NewColor16(1)).Foreground(twin.NewColor16(7))
|
||||
|
||||
for _, styledString := range styledStringsFromString(s) {
|
||||
for _, token := range tokensFromStyledString(styledString) {
|
||||
@ -105,7 +99,7 @@ func tokensFromString(s string) ([]Token, *string) {
|
||||
|
||||
case '\x09': // TAB
|
||||
for {
|
||||
tokens = append(tokens, Token{
|
||||
tokens = append(tokens, twin.Cell{
|
||||
Rune: ' ',
|
||||
Style: styledString.Style,
|
||||
})
|
||||
@ -117,13 +111,13 @@ func tokensFromString(s string) ([]Token, *string) {
|
||||
}
|
||||
|
||||
case '<27>': // Go's broken-UTF8 marker
|
||||
tokens = append(tokens, Token{
|
||||
tokens = append(tokens, twin.Cell{
|
||||
Rune: '?',
|
||||
Style: styleBrokenUtf8,
|
||||
})
|
||||
|
||||
case '\x08': // Backspace
|
||||
tokens = append(tokens, Token{
|
||||
tokens = append(tokens, twin.Cell{
|
||||
Rune: '<',
|
||||
Style: styleBrokenUtf8,
|
||||
})
|
||||
@ -144,7 +138,7 @@ func tokensFromString(s string) ([]Token, *string) {
|
||||
}
|
||||
|
||||
// Consume 'x<x', where '<' is backspace and the result is a bold 'x'
|
||||
func consumeBold(runes []rune, index int) (int, *Token) {
|
||||
func consumeBold(runes []rune, index int) (int, *twin.Cell) {
|
||||
if index+2 >= len(runes) {
|
||||
// Not enough runes left for a bold
|
||||
return index, nil
|
||||
@ -161,14 +155,14 @@ func consumeBold(runes []rune, index int) (int, *Token) {
|
||||
}
|
||||
|
||||
// We have a match!
|
||||
return index + 3, &Token{
|
||||
return index + 3, &twin.Cell{
|
||||
Rune: runes[index],
|
||||
Style: manPageBold,
|
||||
}
|
||||
}
|
||||
|
||||
// Consume '_<x', where '<' is backspace and the result is an underlined 'x'
|
||||
func consumeUnderline(runes []rune, index int) (int, *Token) {
|
||||
func consumeUnderline(runes []rune, index int) (int, *twin.Cell) {
|
||||
if index+2 >= len(runes) {
|
||||
// Not enough runes left for a underline
|
||||
return index, nil
|
||||
@ -185,7 +179,7 @@ func consumeUnderline(runes []rune, index int) (int, *Token) {
|
||||
}
|
||||
|
||||
// We have a match!
|
||||
return index + 3, &Token{
|
||||
return index + 3, &twin.Cell{
|
||||
Rune: runes[index+2],
|
||||
Style: manPageUnderline,
|
||||
}
|
||||
@ -194,7 +188,7 @@ func consumeUnderline(runes []rune, index int) (int, *Token) {
|
||||
// Consume '+<+<o<o' / '+<o', where '<' is backspace and the result is a unicode bullet.
|
||||
//
|
||||
// Used on man pages, try "man printf" on macOS for one example.
|
||||
func consumeBullet(runes []rune, index int) (int, *Token) {
|
||||
func consumeBullet(runes []rune, index int) (int, *twin.Cell) {
|
||||
patterns := [][]byte{[]byte("+\bo"), []byte("+\b+\bo\bo")}
|
||||
for _, pattern := range patterns {
|
||||
if index+len(pattern) > len(runes) {
|
||||
@ -215,18 +209,18 @@ func consumeBullet(runes []rune, index int) (int, *Token) {
|
||||
}
|
||||
|
||||
// We have a match!
|
||||
return index + len(pattern), &Token{
|
||||
return index + len(pattern), &twin.Cell{
|
||||
Rune: '•', // Unicode bullet point
|
||||
Style: tcell.StyleDefault,
|
||||
Style: twin.StyleDefault,
|
||||
}
|
||||
}
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func tokensFromStyledString(styledString _StyledString) []Token {
|
||||
func tokensFromStyledString(styledString _StyledString) []twin.Cell {
|
||||
runes := []rune(styledString.String)
|
||||
tokens := make([]Token, 0, len(runes))
|
||||
tokens := make([]twin.Cell, 0, len(runes))
|
||||
|
||||
for index := 0; index < len(runes); index++ {
|
||||
nextIndex, token := consumeBullet(runes, index)
|
||||
@ -250,7 +244,7 @@ func tokensFromStyledString(styledString _StyledString) []Token {
|
||||
continue
|
||||
}
|
||||
|
||||
tokens = append(tokens, Token{
|
||||
tokens = append(tokens, twin.Cell{
|
||||
Rune: runes[index],
|
||||
Style: styledString.Style,
|
||||
})
|
||||
@ -261,7 +255,7 @@ func tokensFromStyledString(styledString _StyledString) []Token {
|
||||
|
||||
type _StyledString struct {
|
||||
String string
|
||||
Style tcell.Style
|
||||
Style twin.Style
|
||||
}
|
||||
|
||||
func styledStringsFromString(s string) []_StyledString {
|
||||
@ -271,7 +265,7 @@ func styledStringsFromString(s string) []_StyledString {
|
||||
matches := sgrSequencePattern.FindAllStringIndex(s, -1)
|
||||
styledStrings := make([]_StyledString, 0, len(matches)+1)
|
||||
|
||||
style := tcell.StyleDefault
|
||||
style := twin.StyleDefault
|
||||
|
||||
beg := 0
|
||||
end := 0
|
||||
@ -303,7 +297,7 @@ func styledStringsFromString(s string) []_StyledString {
|
||||
}
|
||||
|
||||
// updateStyle parses a string of the form "ESC[33m" into changes to style
|
||||
func updateStyle(style tcell.Style, escapeSequence string) tcell.Style {
|
||||
func updateStyle(style twin.Style, escapeSequence string) twin.Style {
|
||||
numbers := strings.Split(escapeSequence[2:len(escapeSequence)-1], ";")
|
||||
index := 0
|
||||
for index < len(numbers) {
|
||||
@ -311,55 +305,55 @@ func updateStyle(style tcell.Style, escapeSequence string) tcell.Style {
|
||||
index++
|
||||
switch strings.TrimLeft(number, "0") {
|
||||
case "":
|
||||
style = tcell.StyleDefault
|
||||
style = twin.StyleDefault
|
||||
|
||||
case "1":
|
||||
style = style.Bold(true)
|
||||
style = style.WithAttr(twin.AttrBold)
|
||||
|
||||
case "2":
|
||||
style = style.Dim(true)
|
||||
style = style.WithAttr(twin.AttrDim)
|
||||
|
||||
case "3":
|
||||
style = style.Italic(true)
|
||||
style = style.WithAttr(twin.AttrItalic)
|
||||
|
||||
case "4":
|
||||
style = style.Underline(true)
|
||||
style = style.WithAttr(twin.AttrUnderline)
|
||||
|
||||
case "7":
|
||||
style = style.Reverse(true)
|
||||
style = style.WithAttr(twin.AttrReverse)
|
||||
|
||||
case "22":
|
||||
style = style.Bold(false).Dim(false)
|
||||
style = style.WithoutAttr(twin.AttrBold).WithoutAttr(twin.AttrDim)
|
||||
|
||||
case "23":
|
||||
style = style.Italic(false)
|
||||
style = style.WithoutAttr(twin.AttrItalic)
|
||||
|
||||
case "24":
|
||||
style = style.Underline(false)
|
||||
style = style.WithoutAttr(twin.AttrUnderline)
|
||||
|
||||
case "27":
|
||||
style = style.Reverse(false)
|
||||
style = style.WithoutAttr(twin.AttrReverse)
|
||||
|
||||
// Foreground colors, https://pkg.go.dev/github.com/gdamore/tcell#Color
|
||||
case "30":
|
||||
style = style.Foreground(tcell.ColorBlack)
|
||||
style = style.Foreground(twin.NewColor16(0))
|
||||
case "31":
|
||||
style = style.Foreground(tcell.ColorMaroon)
|
||||
style = style.Foreground(twin.NewColor16(1))
|
||||
case "32":
|
||||
style = style.Foreground(tcell.ColorGreen)
|
||||
style = style.Foreground(twin.NewColor16(2))
|
||||
case "33":
|
||||
style = style.Foreground(tcell.ColorOlive)
|
||||
style = style.Foreground(twin.NewColor16(3))
|
||||
case "34":
|
||||
style = style.Foreground(tcell.ColorNavy)
|
||||
style = style.Foreground(twin.NewColor16(4))
|
||||
case "35":
|
||||
style = style.Foreground(tcell.ColorPurple)
|
||||
style = style.Foreground(twin.NewColor16(5))
|
||||
case "36":
|
||||
style = style.Foreground(tcell.ColorTeal)
|
||||
style = style.Foreground(twin.NewColor16(6))
|
||||
case "37":
|
||||
style = style.Foreground(tcell.ColorSilver)
|
||||
style = style.Foreground(twin.NewColor16(7))
|
||||
case "38":
|
||||
var err error = nil
|
||||
var color *tcell.Color
|
||||
var color *twin.Color
|
||||
index, color, err = consumeCompositeColor(numbers, index-1)
|
||||
if err != nil {
|
||||
log.Warnf("Foreground: %s", err.Error())
|
||||
@ -367,28 +361,28 @@ func updateStyle(style tcell.Style, escapeSequence string) tcell.Style {
|
||||
}
|
||||
style = style.Foreground(*color)
|
||||
case "39":
|
||||
style = style.Foreground(tcell.ColorDefault)
|
||||
style = style.Foreground(twin.ColorDefault)
|
||||
|
||||
// Background colors, see https://pkg.go.dev/github.com/gdamore/tcell#Color
|
||||
// Background colors, see https://pkg.go.dev/github.com/gdamore/Color
|
||||
case "40":
|
||||
style = style.Background(tcell.ColorBlack)
|
||||
style = style.Background(twin.NewColor16(0))
|
||||
case "41":
|
||||
style = style.Background(tcell.ColorMaroon)
|
||||
style = style.Background(twin.NewColor16(1))
|
||||
case "42":
|
||||
style = style.Background(tcell.ColorGreen)
|
||||
style = style.Background(twin.NewColor16(2))
|
||||
case "43":
|
||||
style = style.Background(tcell.ColorOlive)
|
||||
style = style.Background(twin.NewColor16(3))
|
||||
case "44":
|
||||
style = style.Background(tcell.ColorNavy)
|
||||
style = style.Background(twin.NewColor16(4))
|
||||
case "45":
|
||||
style = style.Background(tcell.ColorPurple)
|
||||
style = style.Background(twin.NewColor16(5))
|
||||
case "46":
|
||||
style = style.Background(tcell.ColorTeal)
|
||||
style = style.Background(twin.NewColor16(6))
|
||||
case "47":
|
||||
style = style.Background(tcell.ColorSilver)
|
||||
style = style.Background(twin.NewColor16(7))
|
||||
case "48":
|
||||
var err error = nil
|
||||
var color *tcell.Color
|
||||
var color *twin.Color
|
||||
index, color, err = consumeCompositeColor(numbers, index-1)
|
||||
if err != nil {
|
||||
log.Warnf("Background: %s", err.Error())
|
||||
@ -396,30 +390,30 @@ func updateStyle(style tcell.Style, escapeSequence string) tcell.Style {
|
||||
}
|
||||
style = style.Background(*color)
|
||||
case "49":
|
||||
style = style.Background(tcell.ColorDefault)
|
||||
style = style.Background(twin.ColorDefault)
|
||||
|
||||
// Bright foreground colors: see https://pkg.go.dev/github.com/gdamore/tcell#Color
|
||||
// Bright foreground colors: see https://pkg.go.dev/github.com/gdamore/Color
|
||||
//
|
||||
// After testing vs less and cat on iTerm2 3.3.9 / macOS Catalina
|
||||
// 10.15.4 that's how they seem to handle this, tested with:
|
||||
// * TERM=xterm-256color
|
||||
// * TERM=screen-256color
|
||||
case "90":
|
||||
style = style.Foreground(tcell.ColorGray)
|
||||
style = style.Foreground(twin.NewColor16(8))
|
||||
case "91":
|
||||
style = style.Foreground(tcell.ColorRed)
|
||||
style = style.Foreground(twin.NewColor16(9))
|
||||
case "92":
|
||||
style = style.Foreground(tcell.ColorLime)
|
||||
style = style.Foreground(twin.NewColor16(10))
|
||||
case "93":
|
||||
style = style.Foreground(tcell.ColorYellow)
|
||||
style = style.Foreground(twin.NewColor16(11))
|
||||
case "94":
|
||||
style = style.Foreground(tcell.ColorBlue)
|
||||
style = style.Foreground(twin.NewColor16(12))
|
||||
case "95":
|
||||
style = style.Foreground(tcell.ColorFuchsia)
|
||||
style = style.Foreground(twin.NewColor16(13))
|
||||
case "96":
|
||||
style = style.Foreground(tcell.ColorAqua)
|
||||
style = style.Foreground(twin.NewColor16(14))
|
||||
case "97":
|
||||
style = style.Foreground(tcell.ColorWhite)
|
||||
style = style.Foreground(twin.NewColor16(15))
|
||||
|
||||
default:
|
||||
log.Warnf("Unrecognized ANSI SGR code <%s>", number)
|
||||
@ -435,7 +429,7 @@ func updateStyle(style tcell.Style, escapeSequence string) tcell.Style {
|
||||
// This method will return:
|
||||
// * The first index in the string that this function did not consume
|
||||
// * A color value that can be applied to a style
|
||||
func consumeCompositeColor(numbers []string, index int) (int, *tcell.Color, error) {
|
||||
func consumeCompositeColor(numbers []string, index int) (int, *twin.Color, error) {
|
||||
baseIndex := index
|
||||
if numbers[index] != "38" && numbers[index] != "48" {
|
||||
err := fmt.Errorf(
|
||||
@ -468,8 +462,7 @@ func consumeCompositeColor(numbers []string, index int) (int, *tcell.Color, erro
|
||||
return -1, nil, err
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/gdamore/tcell/issues/404
|
||||
colorValue := tcell.Color(int64(tcell.Color16) - 16 + int64(colorNumber))
|
||||
colorValue := twin.NewColor256(uint8(colorNumber))
|
||||
return index + 1, &colorValue, nil
|
||||
}
|
||||
|
||||
@ -489,21 +482,21 @@ func consumeCompositeColor(numbers []string, index int) (int, *tcell.Color, erro
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
rValue := int32(rValueX)
|
||||
rValue := uint8(rValueX)
|
||||
|
||||
gValueX, err := strconv.Atoi(numbers[gIndex])
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
gValue := int32(gValueX)
|
||||
gValue := uint8(gValueX)
|
||||
|
||||
bValueX, err := strconv.Atoi(numbers[bIndex])
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
bValue := int32(bValueX)
|
||||
bValue := uint8(bValueX)
|
||||
|
||||
colorValue := tcell.NewRGBColor(rValue, gValue, bValue)
|
||||
colorValue := twin.NewColor24Bit(rValue, gValue, bValue)
|
||||
return bIndex + 1, &colorValue, nil
|
||||
}
|
||||
|
||||
|
@ -9,9 +9,8 @@ import (
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/walles/moar/twin"
|
||||
"gotest.tools/assert"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
// Verify that we can tokenize all lines in ../sample-files/*
|
||||
@ -34,7 +33,7 @@ func TestTokenize(t *testing.T) {
|
||||
var loglines strings.Builder
|
||||
log.SetOutput(&loglines)
|
||||
|
||||
tokens, plainString := tokensFromString(line)
|
||||
tokens, plainString := cellsFromString(line)
|
||||
if len(tokens) != utf8.RuneCountInString(*plainString) {
|
||||
t.Errorf("%s:%d: len(tokens)=%d, len(plainString)=%d for: <%s>",
|
||||
fileName, lineNumber,
|
||||
@ -51,43 +50,43 @@ func TestTokenize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnderline(t *testing.T) {
|
||||
tokens, _ := tokensFromString("a\x1b[4mb\x1b[24mc")
|
||||
tokens, _ := cellsFromString("a\x1b[4mb\x1b[24mc")
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], Token{Rune: 'a', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[1], Token{Rune: 'b', Style: tcell.StyleDefault.Underline(true)})
|
||||
assert.Equal(t, tokens[2], Token{Rune: 'c', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[0], twin.Cell{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.Cell{Rune: 'b', Style: twin.StyleDefault.WithAttr(twin.AttrUnderline)})
|
||||
assert.Equal(t, tokens[2], twin.Cell{Rune: 'c', Style: twin.StyleDefault})
|
||||
}
|
||||
|
||||
func TestManPages(t *testing.T) {
|
||||
// Bold
|
||||
tokens, _ := tokensFromString("ab\bbc")
|
||||
tokens, _ := cellsFromString("ab\bbc")
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], Token{Rune: 'a', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[1], Token{Rune: 'b', Style: tcell.StyleDefault.Bold(true)})
|
||||
assert.Equal(t, tokens[2], Token{Rune: 'c', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[0], twin.Cell{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.Cell{Rune: 'b', Style: twin.StyleDefault.WithAttr(twin.AttrBold)})
|
||||
assert.Equal(t, tokens[2], twin.Cell{Rune: 'c', Style: twin.StyleDefault})
|
||||
|
||||
// Underline
|
||||
tokens, _ = tokensFromString("a_\bbc")
|
||||
tokens, _ = cellsFromString("a_\bbc")
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], Token{Rune: 'a', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[1], Token{Rune: 'b', Style: tcell.StyleDefault.Underline(true)})
|
||||
assert.Equal(t, tokens[2], Token{Rune: 'c', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[0], twin.Cell{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.Cell{Rune: 'b', Style: twin.StyleDefault.WithAttr(twin.AttrUnderline)})
|
||||
assert.Equal(t, tokens[2], twin.Cell{Rune: 'c', Style: twin.StyleDefault})
|
||||
|
||||
// Bullet point 1, taken from doing this on my macOS system:
|
||||
// env PAGER="hexdump -C" man printf | moar
|
||||
tokens, _ = tokensFromString("a+\b+\bo\bob")
|
||||
tokens, _ = cellsFromString("a+\b+\bo\bob")
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], Token{Rune: 'a', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[1], Token{Rune: '•', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[2], Token{Rune: 'b', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[0], twin.Cell{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.Cell{Rune: '•', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[2], twin.Cell{Rune: 'b', Style: twin.StyleDefault})
|
||||
|
||||
// Bullet point 2, taken from doing this using the "fish" shell on my macOS system:
|
||||
// man printf | hexdump -C | moar
|
||||
tokens, _ = tokensFromString("a+\bob")
|
||||
tokens, _ = cellsFromString("a+\bob")
|
||||
assert.Equal(t, len(tokens), 3)
|
||||
assert.Equal(t, tokens[0], Token{Rune: 'a', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[1], Token{Rune: '•', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[2], Token{Rune: 'b', Style: tcell.StyleDefault})
|
||||
assert.Equal(t, tokens[0], twin.Cell{Rune: 'a', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[1], twin.Cell{Rune: '•', Style: twin.StyleDefault})
|
||||
assert.Equal(t, tokens[2], twin.Cell{Rune: 'b', Style: twin.StyleDefault})
|
||||
}
|
||||
|
||||
func TestConsumeCompositeColorHappy(t *testing.T) {
|
||||
@ -96,13 +95,13 @@ func TestConsumeCompositeColorHappy(t *testing.T) {
|
||||
newIndex, color, err := consumeCompositeColor([]string{"38", "5", "74"}, 0)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, newIndex, 3)
|
||||
assert.Equal(t, *color, tcell.Color74)
|
||||
assert.Equal(t, *color, twin.NewColor256(74))
|
||||
|
||||
// 24 bit color
|
||||
newIndex, color, err = consumeCompositeColor([]string{"38", "2", "10", "20", "30"}, 0)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, newIndex, 5)
|
||||
assert.Equal(t, *color, tcell.NewRGBColor(10, 20, 30))
|
||||
assert.Equal(t, *color, twin.NewColor24Bit(10, 20, 30))
|
||||
}
|
||||
|
||||
func TestConsumeCompositeColorHappyMidSequence(t *testing.T) {
|
||||
@ -111,13 +110,13 @@ func TestConsumeCompositeColorHappyMidSequence(t *testing.T) {
|
||||
newIndex, color, err := consumeCompositeColor([]string{"whatever", "38", "5", "74"}, 1)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, newIndex, 4)
|
||||
assert.Equal(t, *color, tcell.Color74)
|
||||
assert.Equal(t, *color, twin.NewColor256(74))
|
||||
|
||||
// 24 bit color
|
||||
newIndex, color, err = consumeCompositeColor([]string{"whatever", "38", "2", "10", "20", "30"}, 1)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, newIndex, 6)
|
||||
assert.Equal(t, *color, tcell.NewRGBColor(10, 20, 30))
|
||||
assert.Equal(t, *color, twin.NewColor24Bit(10, 20, 30))
|
||||
}
|
||||
|
||||
func TestConsumeCompositeColorBadPrefix(t *testing.T) {
|
||||
@ -179,6 +178,6 @@ func TestConsumeCompositeColorIncomplete24Bit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateStyle(t *testing.T) {
|
||||
numberColored := updateStyle(tcell.StyleDefault, "\x1b[33m")
|
||||
assert.Equal(t, numberColored, tcell.StyleDefault.Foreground(tcell.ColorOlive))
|
||||
numberColored := updateStyle(twin.StyleDefault, "\x1b[33m")
|
||||
assert.Equal(t, numberColored, twin.StyleDefault.Foreground(twin.NewColor16(3)))
|
||||
}
|
||||
|
@ -1,24 +1,26 @@
|
||||
package m
|
||||
|
||||
import "github.com/gdamore/tcell/v2"
|
||||
import "github.com/walles/moar/twin"
|
||||
|
||||
// Page displays text in a pager.
|
||||
func (p *Pager) Page() error {
|
||||
screen, e := tcell.NewScreen()
|
||||
screen, e := twin.NewScreen()
|
||||
if e != nil {
|
||||
// Screen setup failed
|
||||
return e
|
||||
}
|
||||
defer func() {
|
||||
if p.DeInit {
|
||||
screen.Fini()
|
||||
screen.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// See: https://github.com/walles/moar/pull/39
|
||||
// FIXME: Consider moving this logic into the twin package.
|
||||
w, h := screen.Size()
|
||||
screen.ShowCursor(0, h - 1)
|
||||
screen.ShowCursorAt(0, h-1)
|
||||
for x := 0; x < w; x++ {
|
||||
screen.SetContent(x, h - 1, ' ', nil, tcell.StyleDefault)
|
||||
screen.SetCell(x, h-1, twin.NewCell(' ', twin.StyleDefault))
|
||||
}
|
||||
screen.Show()
|
||||
}()
|
||||
|
175
m/pager.go
175
m/pager.go
@ -2,7 +2,6 @@ package m
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -10,8 +9,7 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/walles/moar/twin"
|
||||
)
|
||||
|
||||
// FIXME: Profile the pager while searching through a large file
|
||||
@ -24,13 +22,19 @@ const (
|
||||
_NotFound _PagerMode = 2
|
||||
)
|
||||
|
||||
type eventSpinnerUpdate struct {
|
||||
spinner string
|
||||
}
|
||||
|
||||
type eventMoreLinesAvailable struct{}
|
||||
|
||||
// Styling of line numbers
|
||||
var _NumberStyle = tcell.StyleDefault.Dim(true)
|
||||
var _NumberStyle = twin.StyleDefault.WithAttr(twin.AttrDim)
|
||||
|
||||
// Pager is the main on-screen pager
|
||||
type Pager struct {
|
||||
reader *Reader
|
||||
screen tcell.Screen
|
||||
screen twin.Screen
|
||||
quit bool
|
||||
firstLineOneBased int
|
||||
leftColumnZeroBased int
|
||||
@ -132,12 +136,12 @@ func (p *Pager) _AddLine(fileLineNumber *int, numberPrefixLength int, screenLine
|
||||
break
|
||||
}
|
||||
|
||||
p.screen.SetContent(column, screenLineNumber, digit, nil, _NumberStyle)
|
||||
p.screen.SetCell(column, screenLineNumber, twin.NewCell(digit, _NumberStyle))
|
||||
}
|
||||
|
||||
tokens := createScreenLine(p.leftColumnZeroBased, screenWidth-numberPrefixLength, line, p.searchPattern)
|
||||
for column, token := range tokens {
|
||||
p.screen.SetContent(column+numberPrefixLength, screenLineNumber, token.Rune, nil, token.Style)
|
||||
p.screen.SetCell(column+numberPrefixLength, screenLineNumber, token)
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,14 +150,14 @@ func createScreenLine(
|
||||
screenColumnsCount int,
|
||||
line *Line,
|
||||
search *regexp.Regexp,
|
||||
) []Token {
|
||||
var returnMe []Token
|
||||
) []twin.Cell {
|
||||
var returnMe []twin.Cell
|
||||
searchHitDelta := 0
|
||||
if stringIndexAtColumnZero > 0 {
|
||||
// Indicate that it's possible to scroll left
|
||||
returnMe = append(returnMe, Token{
|
||||
returnMe = append(returnMe, twin.Cell{
|
||||
Rune: '<',
|
||||
Style: tcell.StyleDefault.Reverse(true),
|
||||
Style: twin.StyleDefault.WithAttr(twin.AttrReverse),
|
||||
})
|
||||
searchHitDelta = -1
|
||||
}
|
||||
@ -169,9 +173,9 @@ func createScreenLine(
|
||||
if len(returnMe) >= screenColumnsCount {
|
||||
// We are trying to add a character to the right of the screen.
|
||||
// Indicate that this line continues to the right.
|
||||
returnMe[len(returnMe)-1] = Token{
|
||||
returnMe[len(returnMe)-1] = twin.Cell{
|
||||
Rune: '>',
|
||||
Style: tcell.StyleDefault.Reverse(true),
|
||||
Style: twin.StyleDefault.WithAttr(twin.AttrReverse),
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -179,10 +183,10 @@ func createScreenLine(
|
||||
style := token.Style
|
||||
if matchRanges.InRange(len(returnMe) + stringIndexAtColumnZero + searchHitDelta) {
|
||||
// Search hits in reverse video
|
||||
style = style.Reverse(true)
|
||||
style = style.WithAttr(twin.AttrReverse)
|
||||
}
|
||||
|
||||
returnMe = append(returnMe, Token{
|
||||
returnMe = append(returnMe, twin.Cell{
|
||||
Rune: token.Rune,
|
||||
Style: style,
|
||||
})
|
||||
@ -196,12 +200,12 @@ func (p *Pager) _AddSearchFooter() {
|
||||
|
||||
pos := 0
|
||||
for _, token := range "Search: " + p.searchString {
|
||||
p.screen.SetContent(pos, height-1, token, nil, tcell.StyleDefault)
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(token, twin.StyleDefault))
|
||||
pos++
|
||||
}
|
||||
|
||||
// Add a cursor
|
||||
p.screen.SetContent(pos, height-1, ' ', nil, tcell.StyleDefault.Reverse(true))
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(' ', twin.StyleDefault.WithAttr(twin.AttrReverse)))
|
||||
}
|
||||
|
||||
func (p *Pager) _AddLines(spinner string) {
|
||||
@ -270,14 +274,14 @@ func (p *Pager) _SetFooter(footer string) {
|
||||
width, height := p.screen.Size()
|
||||
|
||||
pos := 0
|
||||
footerStyle := tcell.StyleDefault.Reverse(true)
|
||||
footerStyle := twin.StyleDefault.WithAttr(twin.AttrReverse)
|
||||
for _, token := range footer {
|
||||
p.screen.SetContent(pos, height-1, token, nil, footerStyle)
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(token, footerStyle))
|
||||
pos++
|
||||
}
|
||||
|
||||
for ; pos < width; pos++ {
|
||||
p.screen.SetContent(pos, height-1, ' ', nil, footerStyle)
|
||||
p.screen.SetCell(pos, height-1, twin.NewCell(' ', footerStyle))
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,12 +488,12 @@ func removeLastChar(s string) string {
|
||||
return s[:len(s)-size]
|
||||
}
|
||||
|
||||
func (p *Pager) _OnSearchKey(key tcell.Key) {
|
||||
func (p *Pager) _OnSearchKey(key twin.KeyCode) {
|
||||
switch key {
|
||||
case tcell.KeyEscape, tcell.KeyEnter:
|
||||
case twin.KeyEscape, twin.KeyEnter:
|
||||
p.mode = _Viewing
|
||||
|
||||
case tcell.KeyBackspace, tcell.KeyDEL:
|
||||
case twin.KeyBackspace, twin.KeyDelete:
|
||||
if len(p.searchString) == 0 {
|
||||
return
|
||||
}
|
||||
@ -497,22 +501,22 @@ func (p *Pager) _OnSearchKey(key tcell.Key) {
|
||||
p.searchString = removeLastChar(p.searchString)
|
||||
p._UpdateSearchPattern()
|
||||
|
||||
case tcell.KeyUp:
|
||||
case twin.KeyUp:
|
||||
// Clipping is done in _AddLines()
|
||||
p.firstLineOneBased--
|
||||
p.mode = _Viewing
|
||||
|
||||
case tcell.KeyDown:
|
||||
case twin.KeyDown:
|
||||
// Clipping is done in _AddLines()
|
||||
p.firstLineOneBased++
|
||||
p.mode = _Viewing
|
||||
|
||||
case tcell.KeyPgUp:
|
||||
case twin.KeyPgUp:
|
||||
_, height := p.screen.Size()
|
||||
p.firstLineOneBased -= (height - 1)
|
||||
p.mode = _Viewing
|
||||
|
||||
case tcell.KeyPgDn:
|
||||
case twin.KeyPgDown:
|
||||
_, height := p.screen.Size()
|
||||
p.firstLineOneBased += (height - 1)
|
||||
p.mode = _Viewing
|
||||
@ -541,16 +545,9 @@ func (p *Pager) _MoveRight(delta int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pager) _OnKey(key tcell.Key) {
|
||||
if key == tcell.KeyCtrlL {
|
||||
// This is useful when we're piping in from something writing to both
|
||||
// stdout and stderr.
|
||||
p.screen.Sync()
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Pager) _OnKey(keyCode twin.KeyCode) {
|
||||
if p.mode == _Searching {
|
||||
p._OnSearchKey(key)
|
||||
p._OnSearchKey(keyCode)
|
||||
return
|
||||
}
|
||||
if p.mode != _Viewing && p.mode != _NotFound {
|
||||
@ -560,40 +557,40 @@ func (p *Pager) _OnKey(key tcell.Key) {
|
||||
// Reset the not-found marker on non-search keypresses
|
||||
p.mode = _Viewing
|
||||
|
||||
switch key {
|
||||
case tcell.KeyEscape:
|
||||
switch keyCode {
|
||||
case twin.KeyEscape:
|
||||
p.Quit()
|
||||
|
||||
case tcell.KeyUp:
|
||||
case twin.KeyUp:
|
||||
// Clipping is done in _AddLines()
|
||||
p.firstLineOneBased--
|
||||
|
||||
case tcell.KeyDown, tcell.KeyEnter:
|
||||
case twin.KeyDown, twin.KeyEnter:
|
||||
// Clipping is done in _AddLines()
|
||||
p.firstLineOneBased++
|
||||
|
||||
case tcell.KeyRight:
|
||||
case twin.KeyRight:
|
||||
p._MoveRight(16)
|
||||
|
||||
case tcell.KeyLeft:
|
||||
case twin.KeyLeft:
|
||||
p._MoveRight(-16)
|
||||
|
||||
case tcell.KeyHome:
|
||||
case twin.KeyHome:
|
||||
p.firstLineOneBased = 1
|
||||
|
||||
case tcell.KeyEnd:
|
||||
case twin.KeyEnd:
|
||||
p.firstLineOneBased = p.reader.GetLineCount()
|
||||
|
||||
case tcell.KeyPgDn:
|
||||
case twin.KeyPgDown:
|
||||
_, height := p.screen.Size()
|
||||
p.firstLineOneBased += (height - 1)
|
||||
|
||||
case tcell.KeyPgUp:
|
||||
case twin.KeyPgUp:
|
||||
_, height := p.screen.Size()
|
||||
p.firstLineOneBased -= (height - 1)
|
||||
|
||||
default:
|
||||
log.Debugf("Unhandled key event %v", key)
|
||||
log.Debugf("Unhandled key event %v", keyCode)
|
||||
}
|
||||
}
|
||||
|
||||
@ -683,27 +680,18 @@ func (p *Pager) _OnRune(char rune) {
|
||||
}
|
||||
|
||||
// StartPaging brings up the pager on screen
|
||||
func (p *Pager) StartPaging(screen tcell.Screen) {
|
||||
// We want to match the terminal theme, see screen.Init() source code
|
||||
os.Setenv("TCELL_TRUECOLOR", "disable")
|
||||
|
||||
func (p *Pager) StartPaging(screen twin.Screen) {
|
||||
SetManPageFormatFromEnv()
|
||||
|
||||
if e := screen.Init(); e != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", e)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
p.screen = screen
|
||||
screen.EnableMouse()
|
||||
screen.Show()
|
||||
p._Redraw("")
|
||||
|
||||
go func() {
|
||||
for {
|
||||
// Wait for new lines to appear
|
||||
// Wait for new lines to appear...
|
||||
<-p.reader.moreLinesAdded
|
||||
screen.PostEvent(tcell.NewEventInterrupt(nil))
|
||||
|
||||
// ... and notify the main loop so it can show them:
|
||||
screen.Events() <- eventMoreLinesAvailable{}
|
||||
|
||||
// Delay updates a bit so that we don't waste time refreshing
|
||||
// the screen too often.
|
||||
@ -716,6 +704,7 @@ func (p *Pager) StartPaging(screen tcell.Screen) {
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// Spin the spinner as long as contents is still loading
|
||||
done := false
|
||||
spinnerFrames := [...]string{"/.\\", "-o-", "\\O/", "| |"}
|
||||
spinnerIndex := 0
|
||||
@ -732,7 +721,7 @@ func (p *Pager) StartPaging(screen tcell.Screen) {
|
||||
break
|
||||
}
|
||||
|
||||
screen.PostEvent(tcell.NewEventInterrupt(spinnerFrames[spinnerIndex]))
|
||||
screen.Events() <- eventSpinnerUpdate{spinnerFrames[spinnerIndex]}
|
||||
spinnerIndex++
|
||||
if spinnerIndex >= len(spinnerFrames) {
|
||||
spinnerIndex = 0
|
||||
@ -742,59 +731,55 @@ func (p *Pager) StartPaging(screen tcell.Screen) {
|
||||
}
|
||||
|
||||
// Empty our spinner, loading done!
|
||||
screen.PostEvent(tcell.NewEventInterrupt(""))
|
||||
screen.Events() <- eventSpinnerUpdate{""}
|
||||
}()
|
||||
|
||||
// Main loop
|
||||
spinner := ""
|
||||
for !p.quit {
|
||||
ev := screen.PollEvent()
|
||||
switch ev := ev.(type) {
|
||||
case *tcell.EventKey:
|
||||
if ev.Key() == tcell.KeyRune {
|
||||
p._OnRune(ev.Rune())
|
||||
} else {
|
||||
p._OnKey(ev.Key())
|
||||
}
|
||||
if len(screen.Events()) == 0 {
|
||||
// Nothing more to process for now, redraw the screen!
|
||||
p._Redraw(spinner)
|
||||
}
|
||||
|
||||
case *tcell.EventMouse:
|
||||
switch ev.Buttons() {
|
||||
case tcell.WheelUp:
|
||||
event := <-screen.Events()
|
||||
switch event := event.(type) {
|
||||
case twin.EventKeyCode:
|
||||
p._OnKey(event.KeyCode())
|
||||
|
||||
case twin.EventRune:
|
||||
p._OnRune(event.Rune())
|
||||
|
||||
case twin.EventMouse:
|
||||
switch event.Buttons() {
|
||||
case twin.MouseWheelUp:
|
||||
// Clipping is done in _AddLines()
|
||||
p.firstLineOneBased--
|
||||
|
||||
case tcell.WheelDown:
|
||||
case twin.MouseWheelDown:
|
||||
// Clipping is done in _AddLines()
|
||||
p.firstLineOneBased++
|
||||
|
||||
case tcell.WheelRight:
|
||||
p._MoveRight(16)
|
||||
|
||||
case tcell.WheelLeft:
|
||||
case twin.MouseWheelLeft:
|
||||
p._MoveRight(-16)
|
||||
|
||||
case twin.MouseWheelRight:
|
||||
p._MoveRight(16)
|
||||
}
|
||||
|
||||
case *tcell.EventResize:
|
||||
case twin.EventResize:
|
||||
// We'll be implicitly redrawn just by taking another lap in the loop
|
||||
|
||||
case *tcell.EventInterrupt:
|
||||
// This means we got more lines, look for NewEventInterrupt higher up
|
||||
// in this file. Doing nothing here is fine, the refresh happens after
|
||||
// this switch statement.
|
||||
data := ev.Data()
|
||||
if data != nil {
|
||||
// From: https://yourbasic.org/golang/interface-to-string/
|
||||
spinner = fmt.Sprintf("%v", data)
|
||||
}
|
||||
case eventMoreLinesAvailable:
|
||||
// Doing nothing here is fine; screen will be refreshed on the next
|
||||
// iteration of the main loop.
|
||||
|
||||
case eventSpinnerUpdate:
|
||||
spinner = event.spinner
|
||||
|
||||
default:
|
||||
log.Warnf("Unhandled event type: %v", ev)
|
||||
log.Warnf("Unhandled event type: %v", event)
|
||||
}
|
||||
|
||||
// FIXME: If more events are ready, skip this redraw, that
|
||||
// should speed up mouse wheel scrolling
|
||||
|
||||
p._Redraw(spinner)
|
||||
}
|
||||
|
||||
if p.reader.err != nil {
|
||||
|
194
m/pager_test.go
194
m/pager_test.go
@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/walles/moar/twin"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
@ -17,33 +17,24 @@ func TestUnicodeRendering(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var answers = []Token{
|
||||
createExpectedCell('å', tcell.StyleDefault),
|
||||
createExpectedCell('ä', tcell.StyleDefault),
|
||||
createExpectedCell('ö', tcell.StyleDefault),
|
||||
var answers = []twin.Cell{
|
||||
twin.NewCell('å', twin.StyleDefault),
|
||||
twin.NewCell('ä', twin.StyleDefault),
|
||||
twin.NewCell('ö', twin.StyleDefault),
|
||||
}
|
||||
|
||||
contents := startPaging(t, reader)
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
for pos, expected := range answers {
|
||||
expected.LogDifference(t, contents[pos])
|
||||
logDifference(t, expected, contents[pos])
|
||||
}
|
||||
}
|
||||
|
||||
func (expected Token) LogDifference(t *testing.T, actual tcell.SimCell) {
|
||||
if actual.Runes[0] == expected.Rune && actual.Style == expected.Style {
|
||||
func logDifference(t *testing.T, expected twin.Cell, actual twin.Cell) {
|
||||
if actual.Rune == expected.Rune && actual.Style == expected.Style {
|
||||
return
|
||||
}
|
||||
|
||||
t.Errorf("Expected '%s'/0x%x, got '%s'/0x%x",
|
||||
string(expected.Rune), expected.Style,
|
||||
string(actual.Runes[0]), actual.Style)
|
||||
}
|
||||
|
||||
func createExpectedCell(Rune rune, Style tcell.Style) Token {
|
||||
return Token{
|
||||
Rune: Rune,
|
||||
Style: Style,
|
||||
}
|
||||
t.Errorf("Expected %v, got %v", expected, actual)
|
||||
}
|
||||
|
||||
func TestFgColorRendering(t *testing.T) {
|
||||
@ -53,21 +44,21 @@ func TestFgColorRendering(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var answers = []Token{
|
||||
createExpectedCell('a', tcell.StyleDefault.Foreground(tcell.ColorBlack)),
|
||||
createExpectedCell('b', tcell.StyleDefault.Foreground(tcell.ColorMaroon)),
|
||||
createExpectedCell('c', tcell.StyleDefault.Foreground(tcell.ColorGreen)),
|
||||
createExpectedCell('d', tcell.StyleDefault.Foreground(tcell.ColorOlive)),
|
||||
createExpectedCell('e', tcell.StyleDefault.Foreground(tcell.ColorNavy)),
|
||||
createExpectedCell('f', tcell.StyleDefault.Foreground(tcell.ColorPurple)),
|
||||
createExpectedCell('g', tcell.StyleDefault.Foreground(tcell.ColorTeal)),
|
||||
createExpectedCell('h', tcell.StyleDefault.Foreground(tcell.ColorSilver)),
|
||||
createExpectedCell('i', tcell.StyleDefault),
|
||||
var answers = []twin.Cell{
|
||||
twin.NewCell('a', twin.StyleDefault.Foreground(twin.NewColor16(0))),
|
||||
twin.NewCell('b', twin.StyleDefault.Foreground(twin.NewColor16(1))),
|
||||
twin.NewCell('c', twin.StyleDefault.Foreground(twin.NewColor16(2))),
|
||||
twin.NewCell('d', twin.StyleDefault.Foreground(twin.NewColor16(3))),
|
||||
twin.NewCell('e', twin.StyleDefault.Foreground(twin.NewColor16(4))),
|
||||
twin.NewCell('f', twin.StyleDefault.Foreground(twin.NewColor16(5))),
|
||||
twin.NewCell('g', twin.StyleDefault.Foreground(twin.NewColor16(6))),
|
||||
twin.NewCell('h', twin.StyleDefault.Foreground(twin.NewColor16(7))),
|
||||
twin.NewCell('i', twin.StyleDefault),
|
||||
}
|
||||
|
||||
contents := startPaging(t, reader)
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
for pos, expected := range answers {
|
||||
expected.LogDifference(t, contents[pos])
|
||||
logDifference(t, expected, contents[pos])
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,37 +69,37 @@ func TestBrokenUtf8(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var answers = []Token{
|
||||
createExpectedCell('a', tcell.StyleDefault),
|
||||
createExpectedCell('b', tcell.StyleDefault),
|
||||
createExpectedCell('c', tcell.StyleDefault),
|
||||
createExpectedCell('?', tcell.StyleDefault.Foreground(tcell.ColorMaroon).Background(tcell.ColorSilver)),
|
||||
createExpectedCell('d', tcell.StyleDefault),
|
||||
createExpectedCell('e', tcell.StyleDefault),
|
||||
createExpectedCell('f', tcell.StyleDefault),
|
||||
var answers = []twin.Cell{
|
||||
twin.NewCell('a', twin.StyleDefault),
|
||||
twin.NewCell('b', twin.StyleDefault),
|
||||
twin.NewCell('c', twin.StyleDefault),
|
||||
twin.NewCell('?', twin.StyleDefault.Foreground(twin.NewColor16(7)).Background(twin.NewColor16(1))),
|
||||
twin.NewCell('d', twin.StyleDefault),
|
||||
twin.NewCell('e', twin.StyleDefault),
|
||||
twin.NewCell('f', twin.StyleDefault),
|
||||
}
|
||||
|
||||
contents := startPaging(t, reader)
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
for pos, expected := range answers {
|
||||
expected.LogDifference(t, contents[pos])
|
||||
logDifference(t, expected, contents[pos])
|
||||
}
|
||||
}
|
||||
|
||||
func startPaging(t *testing.T, reader *Reader) []tcell.SimCell {
|
||||
screen := tcell.NewSimulationScreen("UTF-8")
|
||||
func startPaging(t *testing.T, reader *Reader) *twin.FakeScreen {
|
||||
screen := twin.NewFakeScreen(20, 10)
|
||||
pager := NewPager(reader)
|
||||
pager.ShowLineNumbers = false
|
||||
|
||||
// Tell our Pager to quit immediately
|
||||
pager.Quit()
|
||||
|
||||
var loglines strings.Builder
|
||||
// Except for just quitting, this also associates our FakeScreen with the Pager
|
||||
pager.StartPaging(screen)
|
||||
contents, _, _ := screen.GetContents()
|
||||
|
||||
if len(loglines.String()) > 0 {
|
||||
t.Logf("%s", loglines.String())
|
||||
}
|
||||
// This makes sure at least one frame gets rendered
|
||||
pager._Redraw("")
|
||||
|
||||
return contents
|
||||
return screen
|
||||
}
|
||||
|
||||
// assertIndexOfFirstX verifies the (zero-based) index of the first 'x'
|
||||
@ -118,9 +109,9 @@ func assertIndexOfFirstX(t *testing.T, s string, expectedIndex int) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
contents := startPaging(t, reader)
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
for pos, cell := range contents {
|
||||
if cell.Runes[0] != 'x' {
|
||||
if cell.Rune != 'x' {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -171,27 +162,27 @@ func TestCodeHighlighting(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
packageKeywordStyle := tcell.StyleDefault.Bold(true).Foreground(tcell.NewHexColor(0x6AB825))
|
||||
packageNameStyle := tcell.StyleDefault.Foreground(tcell.NewHexColor(0xD0D0D0))
|
||||
var answers = []Token{
|
||||
createExpectedCell('p', packageKeywordStyle),
|
||||
createExpectedCell('a', packageKeywordStyle),
|
||||
createExpectedCell('c', packageKeywordStyle),
|
||||
createExpectedCell('k', packageKeywordStyle),
|
||||
createExpectedCell('a', packageKeywordStyle),
|
||||
createExpectedCell('g', packageKeywordStyle),
|
||||
createExpectedCell('e', packageKeywordStyle),
|
||||
createExpectedCell(' ', packageNameStyle),
|
||||
createExpectedCell('m', packageNameStyle),
|
||||
packageKeywordStyle := twin.StyleDefault.WithAttr(twin.AttrBold).Foreground(twin.NewColorHex(0x6AB825))
|
||||
packageNameStyle := twin.StyleDefault.Foreground(twin.NewColorHex(0xD0D0D0))
|
||||
var answers = []twin.Cell{
|
||||
twin.NewCell('p', packageKeywordStyle),
|
||||
twin.NewCell('a', packageKeywordStyle),
|
||||
twin.NewCell('c', packageKeywordStyle),
|
||||
twin.NewCell('k', packageKeywordStyle),
|
||||
twin.NewCell('a', packageKeywordStyle),
|
||||
twin.NewCell('g', packageKeywordStyle),
|
||||
twin.NewCell('e', packageKeywordStyle),
|
||||
twin.NewCell(' ', packageNameStyle),
|
||||
twin.NewCell('m', packageNameStyle),
|
||||
}
|
||||
|
||||
contents := startPaging(t, reader)
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
for pos, expected := range answers {
|
||||
expected.LogDifference(t, contents[pos])
|
||||
logDifference(t, expected, contents[pos])
|
||||
}
|
||||
}
|
||||
|
||||
func testManPageFormatting(t *testing.T, input string, expected Token) {
|
||||
func testManPageFormatting(t *testing.T, input string, expected twin.Cell) {
|
||||
reader := NewReaderFromStream("", strings.NewReader(input))
|
||||
if err := reader._Wait(); err != nil {
|
||||
panic(err)
|
||||
@ -203,17 +194,17 @@ func testManPageFormatting(t *testing.T, input string, expected Token) {
|
||||
os.Setenv("LESS_TERMCAP_us", "")
|
||||
resetManPageFormatForTesting()
|
||||
|
||||
contents := startPaging(t, reader)
|
||||
expected.LogDifference(t, contents[0])
|
||||
assert.Equal(t, contents[1].Runes[0], ' ')
|
||||
contents := startPaging(t, reader).GetRow(0)
|
||||
logDifference(t, expected, contents[0])
|
||||
assert.Equal(t, contents[1].Rune, ' ')
|
||||
}
|
||||
|
||||
func TestManPageFormatting(t *testing.T) {
|
||||
testManPageFormatting(t, "N\x08N", createExpectedCell('N', tcell.StyleDefault.Bold(true)))
|
||||
testManPageFormatting(t, "_\x08x", createExpectedCell('x', tcell.StyleDefault.Underline(true)))
|
||||
testManPageFormatting(t, "N\x08N", twin.NewCell('N', twin.StyleDefault.WithAttr(twin.AttrBold)))
|
||||
testManPageFormatting(t, "_\x08x", twin.NewCell('x', twin.StyleDefault.WithAttr(twin.AttrUnderline)))
|
||||
|
||||
// Corner cases
|
||||
testManPageFormatting(t, "\x08", createExpectedCell('<', tcell.StyleDefault.Foreground(tcell.ColorMaroon).Background(tcell.ColorSilver)))
|
||||
testManPageFormatting(t, "\x08", twin.NewCell('<', twin.StyleDefault.Foreground(twin.NewColor16(7)).Background(twin.NewColor16(1))))
|
||||
|
||||
// FIXME: Test two consecutive backspaces
|
||||
|
||||
@ -240,7 +231,7 @@ func TestToPattern(t *testing.T) {
|
||||
assert.Assert(t, toPattern(")g").MatchString(")g"))
|
||||
}
|
||||
|
||||
func assertTokenRangesEqual(t *testing.T, actual []Token, expected []Token) {
|
||||
func assertTokenRangesEqual(t *testing.T, actual []twin.Cell, expected []twin.Cell) {
|
||||
if len(actual) != len(expected) {
|
||||
t.Errorf("String lengths mismatch; expected %d but got %d",
|
||||
len(expected), len(actual))
|
||||
@ -257,10 +248,7 @@ func assertTokenRangesEqual(t *testing.T, actual []Token, expected []Token) {
|
||||
continue
|
||||
}
|
||||
|
||||
t.Errorf("At (0-based) position %d: Expected '%s'/0x%x, got '%s'/0x%x",
|
||||
pos,
|
||||
string(expectedToken.Rune), expectedToken.Style,
|
||||
string(actualToken.Rune), actualToken.Style)
|
||||
t.Errorf("At (0-based) position %d: Expected %v, got %v", pos, expectedToken, actualToken)
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,20 +261,20 @@ func TestCreateScreenLineBase(t *testing.T) {
|
||||
func TestCreateScreenLineOverflowRight(t *testing.T) {
|
||||
line := NewLine("012345")
|
||||
screenLine := createScreenLine(0, 3, line, nil)
|
||||
assertTokenRangesEqual(t, screenLine, []Token{
|
||||
createExpectedCell('0', tcell.StyleDefault),
|
||||
createExpectedCell('1', tcell.StyleDefault),
|
||||
createExpectedCell('>', tcell.StyleDefault.Reverse(true)),
|
||||
assertTokenRangesEqual(t, screenLine, []twin.Cell{
|
||||
twin.NewCell('0', twin.StyleDefault),
|
||||
twin.NewCell('1', twin.StyleDefault),
|
||||
twin.NewCell('>', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateScreenLineUnderflowLeft(t *testing.T) {
|
||||
line := NewLine("012")
|
||||
screenLine := createScreenLine(1, 3, line, nil)
|
||||
assertTokenRangesEqual(t, screenLine, []Token{
|
||||
createExpectedCell('<', tcell.StyleDefault.Reverse(true)),
|
||||
createExpectedCell('1', tcell.StyleDefault),
|
||||
createExpectedCell('2', tcell.StyleDefault),
|
||||
assertTokenRangesEqual(t, screenLine, []twin.Cell{
|
||||
twin.NewCell('<', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
twin.NewCell('1', twin.StyleDefault),
|
||||
twin.NewCell('2', twin.StyleDefault),
|
||||
})
|
||||
}
|
||||
|
||||
@ -298,10 +286,10 @@ func TestCreateScreenLineSearchHit(t *testing.T) {
|
||||
|
||||
line := NewLine("abc")
|
||||
screenLine := createScreenLine(0, 3, line, pattern)
|
||||
assertTokenRangesEqual(t, screenLine, []Token{
|
||||
createExpectedCell('a', tcell.StyleDefault),
|
||||
createExpectedCell('b', tcell.StyleDefault.Reverse(true)),
|
||||
createExpectedCell('c', tcell.StyleDefault),
|
||||
assertTokenRangesEqual(t, screenLine, []twin.Cell{
|
||||
twin.NewCell('a', twin.StyleDefault),
|
||||
twin.NewCell('b', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
twin.NewCell('c', twin.StyleDefault),
|
||||
})
|
||||
}
|
||||
|
||||
@ -313,10 +301,10 @@ func TestCreateScreenLineUtf8SearchHit(t *testing.T) {
|
||||
|
||||
line := NewLine("åäö")
|
||||
screenLine := createScreenLine(0, 3, line, pattern)
|
||||
assertTokenRangesEqual(t, screenLine, []Token{
|
||||
createExpectedCell('å', tcell.StyleDefault),
|
||||
createExpectedCell('ä', tcell.StyleDefault.Reverse(true)),
|
||||
createExpectedCell('ö', tcell.StyleDefault),
|
||||
assertTokenRangesEqual(t, screenLine, []twin.Cell{
|
||||
twin.NewCell('å', twin.StyleDefault),
|
||||
twin.NewCell('ä', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
twin.NewCell('ö', twin.StyleDefault),
|
||||
})
|
||||
}
|
||||
|
||||
@ -326,11 +314,11 @@ func TestCreateScreenLineScrolledUtf8SearchHit(t *testing.T) {
|
||||
line := NewLine("ååäö")
|
||||
screenLine := createScreenLine(1, 4, line, pattern)
|
||||
|
||||
assertTokenRangesEqual(t, screenLine, []Token{
|
||||
createExpectedCell('<', tcell.StyleDefault.Reverse(true)),
|
||||
createExpectedCell('å', tcell.StyleDefault),
|
||||
createExpectedCell('ä', tcell.StyleDefault.Reverse(true)),
|
||||
createExpectedCell('ö', tcell.StyleDefault),
|
||||
assertTokenRangesEqual(t, screenLine, []twin.Cell{
|
||||
twin.NewCell('<', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
twin.NewCell('å', twin.StyleDefault),
|
||||
twin.NewCell('ä', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
twin.NewCell('ö', twin.StyleDefault),
|
||||
})
|
||||
}
|
||||
|
||||
@ -340,11 +328,11 @@ func TestCreateScreenLineScrolled2Utf8SearchHit(t *testing.T) {
|
||||
line := NewLine("åååäö")
|
||||
screenLine := createScreenLine(2, 4, line, pattern)
|
||||
|
||||
assertTokenRangesEqual(t, screenLine, []Token{
|
||||
createExpectedCell('<', tcell.StyleDefault.Reverse(true)),
|
||||
createExpectedCell('å', tcell.StyleDefault),
|
||||
createExpectedCell('ä', tcell.StyleDefault.Reverse(true)),
|
||||
createExpectedCell('ö', tcell.StyleDefault),
|
||||
assertTokenRangesEqual(t, screenLine, []twin.Cell{
|
||||
twin.NewCell('<', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
twin.NewCell('å', twin.StyleDefault),
|
||||
twin.NewCell('ä', twin.StyleDefault.WithAttr(twin.AttrReverse)),
|
||||
twin.NewCell('ö', twin.StyleDefault),
|
||||
})
|
||||
}
|
||||
|
||||
|
18
moar.go
18
moar.go
@ -11,11 +11,10 @@ import (
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/walles/moar/m"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"github.com/walles/moar/twin"
|
||||
)
|
||||
|
||||
var versionString = "Should be set when building, please use build.sh to build"
|
||||
@ -86,6 +85,7 @@ func main() {
|
||||
}
|
||||
printVersion := flag.Bool("version", false, "Prints the moar version number")
|
||||
debug := flag.Bool("debug", false, "Print debug logs after exiting")
|
||||
trace := flag.Bool("trace", false, "Print trace logs after exiting")
|
||||
|
||||
// FIXME: Support --no-highlight
|
||||
|
||||
@ -96,7 +96,9 @@ func main() {
|
||||
}
|
||||
|
||||
log.SetLevel(log.InfoLevel)
|
||||
if *debug {
|
||||
if *trace {
|
||||
log.SetLevel(log.TraceLevel)
|
||||
} else if *debug {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
@ -104,8 +106,8 @@ func main() {
|
||||
TimestampFormat: time.RFC3339Nano,
|
||||
})
|
||||
|
||||
stdinIsRedirected := !terminal.IsTerminal(int(os.Stdin.Fd()))
|
||||
stdoutIsRedirected := !terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||
stdinIsRedirected := !term.IsTerminal(int(os.Stdin.Fd()))
|
||||
stdoutIsRedirected := !term.IsTerminal(int(os.Stdout.Fd()))
|
||||
if stdinIsRedirected && stdoutIsRedirected {
|
||||
io.Copy(os.Stdout, os.Stdin)
|
||||
os.Exit(0)
|
||||
@ -150,7 +152,7 @@ func main() {
|
||||
}
|
||||
|
||||
func startPaging(reader *m.Reader) {
|
||||
screen, e := tcell.NewScreen()
|
||||
screen, e := twin.NewScreen()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
@ -158,7 +160,7 @@ func startPaging(reader *m.Reader) {
|
||||
var loglines strings.Builder
|
||||
defer func() {
|
||||
// Restore screen...
|
||||
screen.Fini()
|
||||
screen.Close()
|
||||
|
||||
// ... before printing panic() output, otherwise the output will have
|
||||
// broken linefeeds and be hard to follow.
|
||||
|
2
test.sh
2
test.sh
@ -42,7 +42,7 @@ fi
|
||||
|
||||
echo Test --version...
|
||||
./moar --version > /dev/null # Should exit with code 0
|
||||
diff -u <(./moar --version) <(git describe --tags --dirty)
|
||||
diff -u <(./moar --version) <(git describe --tags --dirty --always)
|
||||
|
||||
# FIXME: On unknown command line options, test that help text goes to stderr
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user