Skip to content

Commit

Permalink
Merge branch 'johan/scroll-hints'
Browse files Browse the repository at this point in the history
Fixes #94.
  • Loading branch information
walles committed Aug 7, 2022
2 parents a7156c5 + 7d91aca commit 1faf60d
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 24 deletions.
4 changes: 2 additions & 2 deletions m/ansiTokenizer.go
Expand Up @@ -30,8 +30,8 @@ type Line struct {
}

// NewLine creates a new Line from a (potentially ANSI / man page formatted) string
func NewLine(raw string) *Line {
return &Line{
func NewLine(raw string) Line {
return Line{
raw: raw,
plain: nil,
}
Expand Down
6 changes: 6 additions & 0 deletions m/pager.go
Expand Up @@ -69,6 +69,10 @@ type Pager struct {

WrapLongLines bool

// Ref: https://github.com/walles/moar/issues/94
ScrollLeftHint twin.Cell
ScrollRightHint twin.Cell

// If true, pager will clear the screen on return. If false, pager will
// clear the last line, and show the cursor.
DeInit bool
Expand Down Expand Up @@ -143,6 +147,8 @@ func NewPager(r *Reader) *Pager {
ShowLineNumbers: true,
ShowStatusBar: true,
DeInit: true,
ScrollLeftHint: twin.NewCell('<', twin.StyleDefault.WithAttr(twin.AttrReverse)),
ScrollRightHint: twin.NewCell('>', twin.StyleDefault.WithAttr(twin.AttrReverse)),
scrollPosition: newScrollPosition(name),
}
}
Expand Down
12 changes: 7 additions & 5 deletions m/reader.go
Expand Up @@ -191,7 +191,7 @@ func (reader *Reader) readStream(stream io.Reader, originalFileName *string, fro
reader.lock.Unlock()
break
}
reader.lines = append(reader.lines, newLine)
reader.lines = append(reader.lines, &newLine)
reader.lock.Unlock()
completeLine = completeLine[:0]

Expand Down Expand Up @@ -263,8 +263,9 @@ func NewReaderFromText(name string, text string) *Reader {
noExternalNewlines := strings.Trim(text, "\n")
lines := []*Line{}
if len(noExternalNewlines) > 0 {
for _, line := range strings.Split(noExternalNewlines, "\n") {
lines = append(lines, NewLine(line))
for _, lineString := range strings.Split(noExternalNewlines, "\n") {
line := NewLine(lineString)
lines = append(lines, &line)
}
}
done := make(chan bool, 1)
Expand Down Expand Up @@ -540,8 +541,9 @@ func (r *Reader) getLinesUnlocked(firstLineOneBased int, wantedLineCount int) *I
// Replace reader contents with the given text and mark as done
func (reader *Reader) setText(text string) {
lines := []*Line{}
for _, line := range strings.Split(text, "\n") {
lines = append(lines, NewLine(line))
for _, lineString := range strings.Split(text, "\n") {
line := NewLine(lineString)
lines = append(lines, &line)
}

if len(lines) > 0 && strings.HasSuffix(text, "\n") {
Expand Down
10 changes: 2 additions & 8 deletions m/screenLines.go
Expand Up @@ -211,18 +211,12 @@ func (p *Pager) decorateLine(lineNumberToShow *int, contents []twin.Cell) []twin
}

// Add can-scroll-left marker
newLine[0] = twin.Cell{
Rune: '<',
Style: twin.StyleDefault.WithAttr(twin.AttrReverse),
}
newLine[0] = p.ScrollLeftHint
}

// Add scroll right indicator
if len(contents)+numberPrefixLength-p.leftColumnZeroBased > width {
newLine[width-1] = twin.Cell{
Rune: '>',
Style: twin.StyleDefault.WithAttr(twin.AttrReverse),
}
newLine[width-1] = p.ScrollRightHint
}

return newLine
Expand Down
14 changes: 8 additions & 6 deletions m/screenLines_test.go
Expand Up @@ -9,13 +9,15 @@ import (
)

func testHorizontalCropping(t *testing.T, contents string, firstIndex int, lastIndex int, expected string) {
pager := Pager{
screen: twin.NewFakeScreen(1+lastIndex-firstIndex, 99),
leftColumnZeroBased: firstIndex,
scrollPosition: newScrollPosition("testHorizontalCropping"),
}
pager := NewPager(nil)
pager.ShowLineNumbers = false

pager.screen = twin.NewFakeScreen(1+lastIndex-firstIndex, 99)
pager.leftColumnZeroBased = firstIndex
pager.scrollPosition = newScrollPosition("testHorizontalCropping")

lineContents := NewLine(contents)
screenLine := pager.renderLine(lineContents, 0)
screenLine := pager.renderLine(&lineContents, 0)
assert.Equal(t, rowToString(screenLine[0].cells), expected)
}

Expand Down
18 changes: 18 additions & 0 deletions moar.1
Expand Up @@ -51,6 +51,24 @@ Hide the status bar, toggle with
\fB\-\-render\-unprintable\fR={\fBhighlight\fR | \fBwhitespace\fR}
How unprintable characters are rendered
.TP
\fB\-\-scroll\-left\-hint\fR=string
UTF-8 character indicating the view can scroll left, defaults to an inverse \fB<\fR.
This can be a string containing ANSI formatting.
The word
.B ESC
in caps will be interpreted as one escape character.
Example value for faint (using ANSI SGR code 2) tilde characters:
.B ESC[2m~
.TP
\fB\-\-scroll\-right\-hint\fR=string
UTF-8 character indicating the view can scroll right, defaults to an inverse \fB>\fR.
This can be a string containing ANSI formatting.
The word
.B ESC
in caps will be interpreted as one escape character.
Example value for faint (using ANSI SGR code 2) tilde characters:
.B ESC[2m~
.TP
\fB\-\-statusbar\fR={\fBinverse\fR | \fBplain\fR | \fBbold\fR}
Status bar style
.TP
Expand Down
39 changes: 36 additions & 3 deletions moar.go
Expand Up @@ -216,6 +216,23 @@ func parseUnprintableStyle(styleOption string, flagSet *flag.FlagSet) m.Unprinta
panic("os.Exit(1) just failed")
}

func parseScrollHint(scrollHint string, flagSet *flag.FlagSet) twin.Cell {
scrollHint = strings.ReplaceAll(scrollHint, "ESC", "\x1b")
hintParser := m.NewLine(scrollHint)
parsedTokens := hintParser.HighlightedTokens(nil)
if len(parsedTokens) == 1 {
return parsedTokens[0]
}

fmt.Fprintln(os.Stderr,
"ERROR: Scroll hint must be exactly one (optionally highlighted) character. For example: 'ESC[2m…'")
fmt.Fprintln(os.Stderr)
printUsage(os.Stderr, flagSet, true)

os.Exit(1)
panic("os.Exit(1) just failed")
}

func main() {
// FIXME: If we get a CTRL-C, get terminal back into a useful state before terminating

Expand Down Expand Up @@ -244,7 +261,12 @@ func main() {
noStatusBar := flagSet.Bool("no-statusbar", false, "Hide the status bar, toggle with '='")
noClearOnExit := flagSet.Bool("no-clear-on-exit", false, "Retain screen contents when exiting moar")
statusBarStyleOption := flagSet.String("statusbar", "inverse", "Status bar style: inverse, plain or bold")
UnprintableStyleOption := flagSet.String("render-unprintable", "highlight", "How unprintable characters are rendered: highlight or whitespace")
UnprintableStyleOption := flagSet.String("render-unprintable", "highlight",
"How unprintable characters are rendered: highlight or whitespace")
scrollLeftHintOption := flagSet.String("scroll-left-hint", "ESC[7m<",
"Shown when view can scroll left. One character with optional ANSI highlighting.")
scrollRightHintOption := flagSet.String("scroll-right-hint", "ESC[7m>",
"Shown when view can scroll right. One character with optional ANSI highlighting.")

// Combine flags from environment and from command line
flags := os.Args[1:]
Expand Down Expand Up @@ -275,6 +297,9 @@ func main() {
statusBarStyle := parseStatusBarStyle(*statusBarStyleOption, flagSet)
unprintableStyle := parseUnprintableStyle(*UnprintableStyleOption, flagSet)

scrollLeftHint := parseScrollHint(*scrollLeftHintOption, flagSet)
scrollRightHint := parseScrollHint(*scrollRightHintOption, flagSet)

log.SetLevel(log.InfoLevel)
if *trace {
log.SetLevel(log.TraceLevel)
Expand Down Expand Up @@ -343,7 +368,9 @@ func main() {
if stdinIsRedirected {
// Display input pipe contents
reader := m.NewReaderFromStream("", os.Stdin)
startPaging(reader, *wrap, *noLineNumbers, *noStatusBar, *noClearOnExit, statusBarStyle, unprintableStyle)
startPaging(reader,
*wrap, *noLineNumbers, *noStatusBar, *noClearOnExit, statusBarStyle, unprintableStyle,
scrollLeftHint, scrollRightHint)
return
}

Expand All @@ -353,13 +380,17 @@ func main() {
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
os.Exit(1)
}
startPaging(reader, *wrap, *noLineNumbers, *noStatusBar, *noClearOnExit, statusBarStyle, unprintableStyle)
startPaging(reader,
*wrap, *noLineNumbers, *noStatusBar, *noClearOnExit, statusBarStyle, unprintableStyle,
scrollLeftHint, scrollRightHint)
}

func startPaging(reader *m.Reader,
wrapLongLines, noLineNumbers, noStatusBar, noClearOnExit bool,
statusBarStyle m.StatusBarStyle,
unprintableStyle m.UnprintableStyle,
scrollLeftHint twin.Cell,
scrollRightHint twin.Cell,
) {
screen, e := twin.NewScreen()
if e != nil {
Expand All @@ -374,6 +405,8 @@ func startPaging(reader *m.Reader,
pager.ShowStatusBar = !noStatusBar
pager.StatusBarStyle = statusBarStyle
pager.UnprintableStyle = unprintableStyle
pager.ScrollLeftHint = scrollLeftHint
pager.ScrollRightHint = scrollRightHint

defer func() {
// Restore screen...
Expand Down
16 changes: 16 additions & 0 deletions moar_test.go
@@ -0,0 +1,16 @@
package main

import (
"testing"

"github.com/walles/moar/twin"
"gotest.tools/assert"
)

func TestParseScrollHint(t *testing.T) {
token := parseScrollHint("ESC[7m>", nil)
assert.Equal(t, token, twin.Cell{
Rune: '>',
Style: twin.StyleDefault.WithAttr(twin.AttrReverse),
})
}

0 comments on commit 1faf60d

Please sign in to comment.