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:
commit
05c1b278d6
10
README.md
10
README.md
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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/")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user