util: add a text wrapping function

This commit is contained in:
Michael Muré 2018-08-03 17:29:11 +02:00
parent d88d59e9c5
commit 5c86164f22
No known key found for this signature in database
GPG Key ID: A4457C029293126F
3 changed files with 191 additions and 24 deletions

View File

@ -2,8 +2,8 @@ package termui
import (
"fmt"
"github.com/MichaelMure/git-bug/util"
"github.com/jroimartin/gocui"
"strings"
)
const errorPopupView = "errorPopupView"
@ -37,7 +37,7 @@ func (ep *errorPopup) layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
width := minInt(30, maxX)
wrapped, nblines := word_wrap(ep.message, width-2)
wrapped, nblines := util.WordWrap(ep.message, width-2)
height := minInt(nblines+2, maxY)
x0 := (maxX - width) / 2
y0 := (maxY - height) / 2
@ -69,25 +69,3 @@ func (ep *errorPopup) close(g *gocui.Gui, v *gocui.View) error {
func (ep *errorPopup) activate(message string) {
ep.message = message
}
func word_wrap(text string, lineWidth int) (string, int) {
words := strings.Fields(strings.TrimSpace(text))
if len(words) == 0 {
return text, 1
}
lines := 1
wrapped := words[0]
spaceLeft := lineWidth - len(wrapped)
for _, word := range words[1:] {
if len(word)+1 > spaceLeft {
wrapped += "\n" + word
spaceLeft = lineWidth - len(word)
lines++
} else {
wrapped += " " + word
spaceLeft -= 1 + len(word)
}
}
return wrapped, lines
}

100
util/text.go Normal file
View File

@ -0,0 +1,100 @@
package util
import (
"bytes"
"strings"
)
func WordWrap(text string, lineWidth int) (string, int) {
words := strings.Fields(strings.TrimSpace(text))
if len(words) == 0 {
return "", 1
}
lines := 1
wrapped := words[0]
spaceLeft := lineWidth - len(wrapped)
for _, word := range words[1:] {
if len(word)+1 > spaceLeft {
wrapped += "\n" + word
spaceLeft = lineWidth - len(word)
lines++
} else {
wrapped += " " + word
spaceLeft -= 1 + len(word)
}
}
return wrapped, lines
}
func TextWrap(text string, lineWidth int) (string, int) {
var textBuffer bytes.Buffer
var lineBuffer bytes.Buffer
nbLine := 1
firstLine := true
// tabs are formatted as 4 spaces
text = strings.Replace(text, "\t", " ", 4)
for _, line := range strings.Split(text, "\n") {
spaceLeft := lineWidth
if !firstLine {
textBuffer.WriteString("\n")
nbLine++
}
firstWord := true
for _, word := range strings.Split(line, " ") {
if spaceLeft > len(word) {
if !firstWord {
lineBuffer.WriteString(" ")
spaceLeft -= 1
}
lineBuffer.WriteString(word)
spaceLeft -= len(word)
firstWord = false
} else {
if len(word) > lineWidth {
for len(word) > 0 {
l := minInt(spaceLeft, len(word))
part := word[:l]
word = word[l:]
lineBuffer.WriteString(part)
textBuffer.Write(lineBuffer.Bytes())
lineBuffer.Reset()
if len(word) > 0 {
textBuffer.WriteString("\n")
nbLine++
}
spaceLeft = lineWidth
}
} else {
textBuffer.WriteString(strings.TrimRight(lineBuffer.String(), " "))
textBuffer.WriteString("\n")
lineBuffer.Reset()
lineBuffer.WriteString(word)
firstWord = false
spaceLeft = lineWidth - len(word)
nbLine++
}
}
}
textBuffer.WriteString(strings.TrimRight(lineBuffer.String(), " "))
lineBuffer.Reset()
firstLine = false
}
return textBuffer.String(), nbLine
}
func minInt(a, b int) int {
if a > b {
return b
}
return a
}

89
util/text_test.go Normal file
View File

@ -0,0 +1,89 @@
package util
import (
"strings"
"testing"
)
func TestTextWrap(t *testing.T) {
cases := []struct {
Input, Output string
Lim int
}{
// A simple word passes through.
{
"foo",
"foo",
4,
},
// Word breaking
{
"foobarbaz",
"foob\narba\nz",
4,
},
// Lines are broken at whitespace.
{
"foo bar baz",
"foo\nbar\nbaz",
4,
},
// Word breaking
{
"foo bars bazzes",
"foo\nbars\nbazz\nes",
4,
},
// A word that would run beyond the width is wrapped.
{
"fo sop",
"fo\nsop",
4,
},
// A tab counts as 4 characters.
{
"foo\nb\t r\n baz",
"foo\nb\n r\n baz",
4,
},
// Trailing whitespace is removed after used for wrapping.
// Runs of whitespace on which a line is broken are removed.
{
"foo \nb ar ",
"foo\n\nb\nar\n",
4,
},
// An explicit line break at the end of the input is preserved.
{
"foo bar baz\n",
"foo\nbar\nbaz\n",
4,
},
// Explicit break are always preserved.
{
"\nfoo bar\n\n\nbaz\n",
"\nfoo\nbar\n\n\nbaz\n",
4,
},
// Complete example:
{
" This is a list: \n\n\t* foo\n\t* bar\n\n\n\t* baz \nBAM ",
" This\nis a\nlist:\n\n *\nfoo\n *\nbar\n\n\n *\nbaz\nBAM\n",
6,
},
}
for i, tc := range cases {
actual, lines := TextWrap(tc.Input, tc.Lim)
if actual != tc.Output {
t.Fatalf("Case %d Input:\n\n`%s`\n\nExpected Output:\n\n`%s`\n\nActual Output:\n\n`%s`",
i, tc.Input, tc.Output, actual)
}
expected := len(strings.Split(tc.Output, "\n"))
if expected != lines {
t.Fatalf("Nb lines mismatch\nExpected:%d\nActual:%d",
expected, lines)
}
}
}