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

Merge branch 'walles/wordwrap'

This is what was *actually* requested in #26.
This commit is contained in:
Johan Walles 2021-05-24 19:16:17 +02:00
commit 05c1b278d6
3 changed files with 85 additions and 20 deletions

View File

@ -23,6 +23,8 @@ Doing the right thing includes:
`git diff` [| `riff`](https://github.com/walles/riff) for example)
* Supports UTF-8 input and output
* The position in the file is always shown
* Supports **word wrapping** (on actual word boundaries) if requested using
`--wrap` or by pressing <kbd>w</kbd>
[For compatibility reasons](https://github.com/walles/moar/issues/14), `moar`
uses the formats declared in these environment variables when viewing man pages:
@ -171,10 +173,8 @@ 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.
* Arrow keys up / down while in line wrapping mode should scroll by screen line,
not by input file line.
Done
----
@ -215,3 +215,5 @@ Done
<https://github.com/walles/moar/issues/7>.
* Showing unicode search hits should highlight the correct chars
* [Word wrap text rather than character wrap it](m/linewrapper.go).

View File

@ -1,6 +1,46 @@
package m
import "github.com/walles/moar/twin"
import (
"fmt"
"unicode"
"github.com/walles/moar/twin"
)
// From: https://www.compart.com/en/unicode/U+00A0
const NO_BREAK_SPACE = '\xa0'
func getWrapWidth(line []twin.Cell, maxWrapWidth int) int {
if len(line) <= maxWrapWidth {
panic(fmt.Errorf("cannot compute wrap width when input isn't longer than max (%d<=%d)",
len(line), maxWrapWidth))
}
// Find the last whitespace in the input. Since we want to break *before*
// whitespace, we loop through characters to the right of the current one.
for nextIndex := maxWrapWidth; nextIndex > 0; nextIndex-- {
next := line[nextIndex].Rune
if unicode.IsSpace(next) && next != NO_BREAK_SPACE {
// Break-OK whitespace, cut before this one!
return nextIndex
}
if nextIndex < 2 {
// Can't check for single slashes
continue
}
// Break after single slashes, this is to enable breaking inside URLs / paths
current := line[nextIndex-1].Rune
previous := line[nextIndex-2].Rune
if previous != '/' && current == '/' && next != '/' {
return nextIndex
}
}
// No breakpoint found, give up
return maxWrapWidth
}
func wrapLine(width int, line []twin.Cell) [][]twin.Cell {
if len(line) == 0 {
@ -13,16 +53,17 @@ func wrapLine(width int, line []twin.Cell) [][]twin.Cell {
wrapped := make([][]twin.Cell, 0, len(line)/width)
for len(line) > width {
firstPart := line[:width]
wrapWidth := getWrapWidth(line, width)
firstPart := line[:wrapWidth]
if len(wrapped) > 0 {
// Leading whitespace on wrapped lines would just look like
// indentation, which would be weird for wrapped text.
firstPart = twin.TrimSpaceLeft(firstPart)
}
wrapped = append(wrapped, firstPart)
wrapped = append(wrapped, twin.TrimSpaceRight(firstPart))
line = twin.TrimSpaceLeft(line[width:])
line = twin.TrimSpaceLeft(line[wrapWidth:])
}
if len(wrapped) > 0 {
@ -32,7 +73,7 @@ func wrapLine(width int, line []twin.Cell) [][]twin.Cell {
}
if len(line) > 0 {
wrapped = append(wrapped, line)
wrapped = append(wrapped, twin.TrimSpaceRight(line))
}
return wrapped

View File

@ -29,23 +29,21 @@ func toString(cellLines [][]twin.Cell) string {
return returnMe
}
func assertEqual(t *testing.T, a [][]twin.Cell, b [][]twin.Cell) {
if reflect.DeepEqual(a, b) {
return
}
t.Errorf("Expected equal:\n%s\n\n%s", toString(a), toString(b))
}
func assertWrap(t *testing.T, input string, width int, wrappedLines ...string) {
toWrap := tokenize(input)
wrapped := wrapLine(width, toWrap)
actual := wrapLine(width, toWrap)
expected := [][]twin.Cell{}
for _, wrappedLine := range wrappedLines {
expected = append(expected, tokenize(wrappedLine))
}
assertEqual(t, wrapped, expected)
if reflect.DeepEqual(actual, expected) {
return
}
t.Errorf("When wrapping <%s> at width %d:\n--Expected--\n%s\n\n--Actual--\n%s",
input, width, toString(expected), toString(actual))
}
func TestEnoughRoomNoWrapping(t *testing.T) {
@ -72,6 +70,30 @@ func TestLeadingWrappedSpace(t *testing.T) {
assertWrap(t, "ab cd", 2, "ab", "cd")
}
// FIXME: Test word wrapping
func TestWordWrap(t *testing.T) {
assertWrap(t, "abc 123", 8, "abc 123")
assertWrap(t, "abc 123", 7, "abc 123")
assertWrap(t, "abc 123", 6, "abc", "123")
assertWrap(t, "abc 123", 5, "abc", "123")
assertWrap(t, "abc 123", 4, "abc", "123")
assertWrap(t, "abc 123", 3, "abc", "123")
assertWrap(t, "abc 123", 2, "ab", "c", "12", "3")
}
// FIXME: Test wrapping on single dashes
func TestWordWrapUrl(t *testing.T) {
assertWrap(t, "http://apa/bepa/", 17, "http://apa/bepa/")
assertWrap(t, "http://apa/bepa/", 16, "http://apa/bepa/")
assertWrap(t, "http://apa/bepa/", 15, "http://apa/", "bepa/")
assertWrap(t, "http://apa/bepa/", 14, "http://apa/", "bepa/")
assertWrap(t, "http://apa/bepa/", 13, "http://apa/", "bepa/")
assertWrap(t, "http://apa/bepa/", 12, "http://apa/", "bepa/")
assertWrap(t, "http://apa/bepa/", 11, "http://apa/", "bepa/")
assertWrap(t, "http://apa/bepa/", 10, "http://apa", "/bepa/")
assertWrap(t, "http://apa/bepa/", 9, "http://ap", "a/bepa/")
assertWrap(t, "http://apa/bepa/", 8, "http://a", "pa/bepa/")
assertWrap(t, "http://apa/bepa/", 7, "http://", "apa/", "bepa/")
assertWrap(t, "http://apa/bepa/", 6, "http:/", "/apa/", "bepa/")
assertWrap(t, "http://apa/bepa/", 5, "http:", "//apa", "/bepa", "/")
assertWrap(t, "http://apa/bepa/", 4, "http", "://a", "pa/", "bepa", "/")
assertWrap(t, "http://apa/bepa/", 3, "htt", "p:/", "/ap", "a/", "bep", "a/")
}