From 896ac3e874c1b6d79731ac694d2c9e29671f67c5 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Mon, 24 May 2021 18:30:31 +0200 Subject: [PATCH 1/5] Add word wrapping tests --- m/linewrapper.go | 4 ++-- m/linewrapper_test.go | 26 ++++++++++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/m/linewrapper.go b/m/linewrapper.go index 694cd0e..b270d1b 100644 --- a/m/linewrapper.go +++ b/m/linewrapper.go @@ -20,7 +20,7 @@ func wrapLine(width int, line []twin.Cell) [][]twin.Cell { firstPart = twin.TrimSpaceLeft(firstPart) } - wrapped = append(wrapped, firstPart) + wrapped = append(wrapped, twin.TrimSpaceRight(firstPart)) line = twin.TrimSpaceLeft(line[width:]) } @@ -32,7 +32,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 diff --git a/m/linewrapper_test.go b/m/linewrapper_test.go index d5eea17..fd5000b 100644 --- a/m/linewrapper_test.go +++ b/m/linewrapper_test.go @@ -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,14 @@ 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 From ea7e9252b330e705771912a1ea40f21dafdafa68 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Mon, 24 May 2021 18:33:12 +0200 Subject: [PATCH 2/5] Extract wrap cutoff into its own function --- m/linewrapper.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/m/linewrapper.go b/m/linewrapper.go index b270d1b..0d47d01 100644 --- a/m/linewrapper.go +++ b/m/linewrapper.go @@ -1,6 +1,19 @@ package m -import "github.com/walles/moar/twin" +import ( + "fmt" + + "github.com/walles/moar/twin" +) + +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)) + } + + return maxWrapWidth +} func wrapLine(width int, line []twin.Cell) [][]twin.Cell { if len(line) == 0 { @@ -13,7 +26,8 @@ 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. @@ -22,7 +36,7 @@ func wrapLine(width int, line []twin.Cell) [][]twin.Cell { wrapped = append(wrapped, twin.TrimSpaceRight(firstPart)) - line = twin.TrimSpaceLeft(line[width:]) + line = twin.TrimSpaceLeft(line[wrapWidth:]) } if len(wrapped) > 0 { From dda01e25a39ff54901dce1c8b775f68bf1ed4311 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Mon, 24 May 2021 18:46:00 +0200 Subject: [PATCH 3/5] Break before whitespace --- m/linewrapper.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/m/linewrapper.go b/m/linewrapper.go index 0d47d01..f438b21 100644 --- a/m/linewrapper.go +++ b/m/linewrapper.go @@ -2,16 +2,38 @@ package m 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-- { + char := line[nextIndex].Rune + if !unicode.IsSpace(char) { + // Want to break before whitespace, this is not it, keep looking + continue + } + + if char == NO_BREAK_SPACE { + // Don't break at non-break whitespace + continue + } + + return nextIndex + } + + // No breakpoint found, give up return maxWrapWidth } From 3b7a906ce169f5f11ee870f74826dda1ea1f90d3 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Mon, 24 May 2021 19:10:31 +0200 Subject: [PATCH 4/5] Word wrap URLs after / --- m/linewrapper.go | 21 +++++++++++++-------- m/linewrapper_test.go | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/m/linewrapper.go b/m/linewrapper.go index f438b21..08d2dbf 100644 --- a/m/linewrapper.go +++ b/m/linewrapper.go @@ -19,18 +19,23 @@ func getWrapWidth(line []twin.Cell, maxWrapWidth int) int { // 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-- { - char := line[nextIndex].Rune - if !unicode.IsSpace(char) { - // Want to break before whitespace, this is not it, keep looking + 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 } - if char == NO_BREAK_SPACE { - // Don't break at non-break whitespace - 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 } - - return nextIndex } // No breakpoint found, give up diff --git a/m/linewrapper_test.go b/m/linewrapper_test.go index fd5000b..7249630 100644 --- a/m/linewrapper_test.go +++ b/m/linewrapper_test.go @@ -80,4 +80,20 @@ func TestWordWrap(t *testing.T) { 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/") +} From a24a16434d7e9081cc647e4125dabef1bf97b414 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Mon, 24 May 2021 19:14:19 +0200 Subject: [PATCH 5/5] README: We support word wrapping --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5a1dca9..2a4c094 100644 --- a/README.md +++ b/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 w [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 . * Showing unicode search hits should highlight the correct chars + +* [Word wrap text rather than character wrap it](m/linewrapper.go).