Add --wrap option and 'toggle-wrap' action (#3887)

* `--wrap`
* `--wrap-sign`
* `toggle-wrap`

Close #3619
Close #2236
Close #577
Close #461
This commit is contained in:
Junegunn Choi authored and GitHub committed 2024-06-25 17:08:47 +09:00
1 parent 724f8a1d45
commit 70bf8bc35d
7 files changed
+355 -118

No files matched your search

+82
View File
@@ -226,3 +226,85 @@ func (chars *Chars) Prepend(prefix string) {
chars.slice = append([]byte(prefix), chars.slice...)
}
}
func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int) ([][]rune, bool) {
text := make([]rune, chars.Length())
copy(text, chars.ToRunes())
lines := [][]rune{}
overflow := false
if !multiLine {
lines = append(lines, text)
} else {
from := 0
for off := 0; off < len(text); off++ {
if text[off] == '\n' {
lines = append(lines, text[from:off+1]) // Include '\n'
from = off + 1
if len(lines) >= maxLines {
break
}
}
}
var lastLine []rune
if from < len(text) {
lastLine = text[from:]
}
overflow = false
if len(lines) >= maxLines {
overflow = true
} else {
lines = append(lines, lastLine)
}
}
// If wrapping is disabled, we're done
if wrapCols == 0 {
return lines, overflow
}
wrapped := [][]rune{}
for _, line := range lines {
// Remove trailing '\n' and remember if it was there
newline := len(line) > 0 && line[len(line)-1] == '\n'
if newline {
line = line[:len(line)-1]
}
for {
cols := wrapCols
if len(wrapped) > 0 {
cols -= wrapSignWidth
}
_, overflowIdx := RunesWidth(line, 0, tabstop, cols)
if overflowIdx >= 0 {
// Might be a wide character
if overflowIdx == 0 {
overflowIdx = 1
}
if len(wrapped) >= maxLines {
return wrapped, true
}
wrapped = append(wrapped, line[:overflowIdx])
line = line[overflowIdx:]
continue
}
// Restore trailing '\n'
if newline {
line = append(line, '\n')
}
if len(wrapped) >= maxLines {
return wrapped, true
}
wrapped = append(wrapped, line)
break
}
}
return wrapped, false
}
+38 -1
View File
@@ -1,6 +1,9 @@
package util
import "testing"
import (
"fmt"
"testing"
)
func TestToCharsAscii(t *testing.T) {
chars := ToChars([]byte("foobar"))
@@ -44,3 +47,37 @@ func TestTrimLength(t *testing.T) {
check(" h o ", 5)
check(" ", 0)
}
func TestCharsLines(t *testing.T) {
chars := ToChars([]byte("abcdef\n가나다\n\tdef"))
check := func(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int, expectedNumLines int, expectedOverflow bool) {
lines, overflow := chars.Lines(multiLine, maxLines, wrapCols, wrapSignWidth, tabstop)
fmt.Println(lines, overflow)
if len(lines) != expectedNumLines || overflow != expectedOverflow {
t.Errorf("Invalid result: %d %v (expected %d %v)", len(lines), overflow, expectedNumLines, expectedOverflow)
}
}
// No wrap
check(true, 1, 0, 0, 8, 1, true)
check(true, 2, 0, 0, 8, 2, true)
check(true, 3, 0, 0, 8, 3, false)
// Wrap (2)
check(true, 4, 2, 0, 8, 4, true)
check(true, 5, 2, 0, 8, 5, true)
check(true, 6, 2, 0, 8, 6, true)
check(true, 7, 2, 0, 8, 7, true)
check(true, 8, 2, 0, 8, 8, true)
check(true, 9, 2, 0, 8, 9, false)
check(true, 9, 2, 0, 1, 8, false) // Smaller tab size
// With wrap sign (3 + 1)
check(true, 100, 3, 1, 1, 8, false)
// With wrap sign (3 + 2)
check(true, 100, 3, 2, 1, 12, false)
// With wrap sign (3 + 2) and no multi-line
check(false, 100, 3, 2, 1, 13, false)
}