1
1
mirror of https://github.com/walles/moar.git synced 2024-09-21 00:49:59 +03:00

Merge branch 'walles/wrap'

Add optional line wrapping.

Fixes #26.
This commit is contained in:
Johan Walles 2021-05-22 16:05:52 +02:00
commit eea4afad18
8 changed files with 136 additions and 27 deletions

View File

@ -47,6 +47,8 @@ Installing
And now you can just invoke `moar` from the prompt!
Try `moar --help` to see options.
If a binary for your platform is not available, please
[file a ticket](https://github.com/walles/moar/releases) or contact
<johan.walles@gmail.com>.
@ -155,9 +157,7 @@ TODO
* Start at a certain line if run as "moar.rb file.txt:42"
* Redefine 'g' without any prefix to prompt for which line to go
to. This definition makes more sense to me than having to prefix 'g'
to jump.
* Define 'g' to prompt for a line number to go to.
* Handle search hits to the right of the right screen edge. Searching
forwards should move first right, then to the left edge and
@ -171,6 +171,11 @@ TODO
* Retain the search string when pressing / to search a second time.
* [Word wrap text rather than character wrap it](m/linewrapper.go).
* Arrow keys up / down while in wrapped mode should scroll by screen line, not
by input file line.
Done
----

23
m/linewrapper.go Normal file
View File

@ -0,0 +1,23 @@
package m
import "github.com/walles/moar/twin"
func wrapLine(width int, line []twin.Cell) [][]twin.Cell {
if len(line) == 0 {
return [][]twin.Cell{{}}
}
wrapped := make([][]twin.Cell, 0, len(line)/width)
for len(line) > width {
firstPart := line[:width]
wrapped = append(wrapped, firstPart)
line = line[width:]
}
if len(line) > 0 {
wrapped = append(wrapped, line)
}
return wrapped
}

47
m/linewrapper_test.go Normal file
View File

@ -0,0 +1,47 @@
package m
import (
"reflect"
"testing"
"github.com/walles/moar/twin"
"gotest.tools/assert"
)
func tokenize(input string) []twin.Cell {
line := NewLine(input)
return line.HighlightedTokens(nil)
}
func TestEnoughRoomNoWrapping(t *testing.T) {
toWrap := tokenize("This is a test")
wrapped := wrapLine(20, toWrap)
assert.Assert(t, reflect.DeepEqual(wrapped, [][]twin.Cell{toWrap}))
}
func TestWrapEmpty(t *testing.T) {
empty := tokenize("")
wrapped := wrapLine(20, empty)
assert.Assert(t, reflect.DeepEqual(wrapped, [][]twin.Cell{empty}))
}
func TestWordLongerThanLine(t *testing.T) {
toWrap := tokenize("intermediary")
wrapped := wrapLine(6, toWrap)
assert.Assert(t, reflect.DeepEqual(wrapped, [][]twin.Cell{
tokenize("interm"),
tokenize("ediary"),
}))
}
// FIXME: Test word wrapping
// FIXME: Test wrapping with multiple consecutive spaces
// FIXME: Test wrapping on single dashes
// FIXME: Test wrapping with double dashes (not sure what we should do with those)
// FIXME: Test wrapping formatted strings, is there formatting that should affect the wrapping
// FIXME: Test wrapping with trailing whitespace

View File

@ -11,8 +11,6 @@ import (
"github.com/walles/moar/twin"
)
// FIXME: Profile the pager while searching through a large file
type _PagerMode int
const (
@ -48,6 +46,8 @@ type Pager struct {
// NewPager shows lines by default, this field can hide them
ShowLineNumbers bool
WrapLongLines bool
// If true, pager will clear the screen on return. If false, pager will
// clear the last line, and show the cursor.
DeInit bool
@ -64,9 +64,10 @@ const _EofMarkerFormat = "\x1b[7m" // Reverse video
var _HelpReader = NewReaderFromText("Help", `
Welcome to Moar, the nice pager!
Quitting
--------
Miscellaneous
-------------
* Press 'q' or ESC to quit
* Press 'w' to toggle wrapping of long lines
Moving around
-------------
@ -116,7 +117,7 @@ func NewPager(r *Reader) *Pager {
}
}
func (p *Pager) _AddLine(fileLineNumber *int, numberPrefixLength int, screenLineNumber int, cells []twin.Cell) {
func (p *Pager) addLine(fileLineNumber *int, numberPrefixLength int, screenLineNumber int, cells []twin.Cell) {
screenWidth, _ := p.screen.Size()
lineNumberString := ""
@ -128,8 +129,6 @@ func (p *Pager) _AddLine(fileLineNumber *int, numberPrefixLength int, screenLine
"lineNumberString <%s> longer than numberPrefixLength %d",
lineNumberString, numberPrefixLength))
}
} else {
numberPrefixLength = 0
}
for column, digit := range lineNumberString {
@ -196,7 +195,7 @@ func (p *Pager) _AddSearchFooter() {
}
func (p *Pager) _AddLines(spinner string) {
_, height := p.screen.Size()
width, height := p.screen.Size()
wantedLineCount := height - 1
lines := p.reader.GetLines(p.firstLineOneBased, wantedLineCount)
@ -224,10 +223,37 @@ func (p *Pager) _AddLines(spinner string) {
}
screenLineNumber := 0
for i, line := range lines.lines {
lineNumber := p.firstLineOneBased + i
p._AddLine(&lineNumber, numberPrefixLength, screenLineNumber, line.HighlightedTokens(p.searchPattern))
screenFull := false
for lineIndex, line := range lines.lines {
lineNumber := p.firstLineOneBased + lineIndex
highlighted := line.HighlightedTokens(p.searchPattern)
var wrapped [][]twin.Cell
if p.WrapLongLines {
wrapped = wrapLine(width-numberPrefixLength, highlighted)
} else {
// All on one line
wrapped = [][]twin.Cell{highlighted}
}
for wrapIndex, linePart := range wrapped {
visibleLineNumber := &lineNumber
if wrapIndex > 0 {
visibleLineNumber = nil
}
p.addLine(visibleLineNumber, numberPrefixLength, screenLineNumber, linePart)
screenLineNumber++
if screenLineNumber >= height-1 {
// We have shown all the lines that can fit on the screen
screenFull = true
break
}
}
if screenFull {
break
}
}
eofSpinner := spinner
@ -236,7 +262,7 @@ func (p *Pager) _AddLines(spinner string) {
eofSpinner = "---"
}
spinnerLine := cellsFromString(_EofMarkerFormat + eofSpinner)
p._AddLine(nil, 0, screenLineNumber, spinnerLine)
p.addLine(nil, 0, screenLineNumber, spinnerLine)
switch p.mode {
case _Searching:
@ -661,6 +687,9 @@ func (p *Pager) _OnRune(char rune) {
case 'p', 'N':
p._ScrollToPreviousSearchHit()
case 'w':
p.WrapLongLines = !p.WrapLongLines
default:
log.Debugf("Unhandled rune keypress '%s'", string(char))
}

View File

@ -454,12 +454,14 @@ func (r *Reader) _CreateStatusUnlocked(firstLineOneBased int, lastLineOneBased i
return prefix + "<empty>"
}
if len(r.lines) == 1 {
return prefix + "1 line 100%"
}
percent := int(100 * float64(lastLineOneBased) / float64(len(r.lines)))
return fmt.Sprintf("%s%s-%s/%s %d%%",
return fmt.Sprintf("%s%s lines %d%%",
prefix,
formatNumber(uint(firstLineOneBased)),
formatNumber(uint(lastLineOneBased)),
formatNumber(uint(len(r.lines))),
percent)
}

View File

@ -259,12 +259,12 @@ func testStatusText(t *testing.T, fromLine int, toLine int, totalLines int, expe
}
func TestStatusText(t *testing.T) {
testStatusText(t, 1, 10, 20, "1-10/20 50%")
testStatusText(t, 1, 5, 5, "1-5/5 100%")
testStatusText(t, 998, 999, 1000, "998-999/1_000 99%")
testStatusText(t, 1, 10, 20, "20 lines 50%")
testStatusText(t, 1, 5, 5, "5 lines 100%")
testStatusText(t, 998, 999, 1000, "1_000 lines 99%")
testStatusText(t, 0, 0, 0, "<empty>")
testStatusText(t, 1, 1, 1, "1-1/1 100%")
testStatusText(t, 1, 1, 1, "1 line 100%")
// Test with filename
testMe, err := NewReaderFromFilename(getSamplesDir()+"/empty", *styles.Native, formatters.TTY16m)

11
moar.go
View File

@ -136,6 +136,7 @@ func main() {
printVersion := flagSet.Bool("version", false, "Prints the moar version number")
debug := flagSet.Bool("debug", false, "Print debug logs after exiting")
trace := flagSet.Bool("trace", false, "Print trace logs after exiting")
wrap := flagSet.Bool("wrap", false, "Wrap long lines")
styleOption := flagSet.String("style", "native",
"Highlighting style from https://xyproto.github.io/splash/docs/longer/all.html")
colorsOption := flagSet.String("colors", "16M", "Highlighting palette size: 8, 16, 256, 16M")
@ -235,7 +236,7 @@ func main() {
if stdinIsRedirected {
// Display input pipe contents
reader := m.NewReaderFromStream("", os.Stdin)
startPaging(reader)
startPaging(reader, *wrap)
return
}
@ -245,10 +246,10 @@ func main() {
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
os.Exit(1)
}
startPaging(reader)
startPaging(reader, *wrap)
}
func startPaging(reader *m.Reader) {
func startPaging(reader *m.Reader, wrapLongLines bool) {
screen, e := twin.NewScreen()
if e != nil {
panic(e)
@ -276,5 +277,7 @@ func startPaging(reader *m.Reader) {
}()
log.SetOutput(&loglines)
m.NewPager(reader).StartPaging(screen)
pager := m.NewPager(reader)
pager.WrapLongLines = wrapLongLines
pager.StartPaging(screen)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 311 KiB