Implement the trim_whitespace option

Needed for help text formatting
This commit is contained in:
Kovid Goyal 2023-03-29 21:28:47 +05:30
parent 34526517de
commit 266746c96e
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 66 additions and 16 deletions

View File

@ -263,12 +263,24 @@ func (self hyperlink_state) as_escape_codes(for_close bool) string {
type line_builder struct {
buf []byte
cursor_pos int
seen_non_space_chars bool
pos_of_trailing_whitespace int
}
func (self *line_builder) reset() string {
func (self *line_builder) reset(trim_whitespace bool) string {
ans := string(self.buf)
if trim_whitespace && self.pos_of_trailing_whitespace > -1 {
prefix := ans[:self.pos_of_trailing_whitespace]
tp := strings.TrimRightFunc(prefix, is_space)
if len(tp) != len(prefix) {
ans = tp + ans[self.pos_of_trailing_whitespace:]
}
}
self.buf = self.buf[:0]
self.cursor_pos = 0
self.seen_non_space_chars = false
self.pos_of_trailing_whitespace = -1
return ans
}
@ -277,15 +289,34 @@ func (self *line_builder) has_space_for_width(w, max_width int) bool {
}
func (self *line_builder) add_char(ch rune) {
self.seen_non_space_chars = true
self.buf = utf8.AppendRune(self.buf, ch)
self.cursor_pos += wcswidth.Runewidth(ch)
self.pos_of_trailing_whitespace = -1
}
func (self *line_builder) add_space(ch rune, trim_whitespace bool) {
if !trim_whitespace || self.seen_non_space_chars {
self.buf = utf8.AppendRune(self.buf, ch)
self.pos_of_trailing_whitespace = len(self.buf)
self.cursor_pos += wcswidth.Runewidth(ch)
}
}
func (self *line_builder) add_word(word []byte, width int) {
self.seen_non_space_chars = true
self.pos_of_trailing_whitespace = -1
self.buf = append(self.buf, word...)
self.cursor_pos += width
}
func (self *line_builder) add_indent(word string, width int) {
if word != "" {
self.buf = append(self.buf, word...)
self.cursor_pos += width
}
}
func (self *line_builder) add_escape_code(code string) {
self.buf = append(self.buf, code...)
}
@ -360,6 +391,7 @@ type wrapper struct {
ep wcswidth.EscapeCodeParser
indent string
width, indent_width int
trim_whitespace bool
sgr sgr_state
hyperlink hyperlink_state
@ -372,7 +404,7 @@ type wrapper struct {
func (self *wrapper) newline_prefix() {
self.current_line.add_escape_code(self.sgr.as_escape_codes(true))
self.current_line.add_escape_code(self.hyperlink.as_escape_codes(true))
self.current_line.add_word(utils.UnsafeStringToBytes(self.indent), self.indent_width)
self.current_line.add_indent(self.indent, self.indent_width)
self.current_line.add_escape_code(self.sgr.as_escape_codes(false))
self.current_line.add_escape_code(self.hyperlink.as_escape_codes(false))
}
@ -387,7 +419,7 @@ func (self *wrapper) append_line(line string) {
}
func (self *wrapper) end_current_line() {
line := self.current_line.reset()
line := self.current_line.reset(self.trim_whitespace)
if strings.HasSuffix(line, self.indent) && wcswidth.Stringwidth(line) == self.indent_width {
line = line[:len(line)-len(self.indent)]
}
@ -413,16 +445,22 @@ func (self *wrapper) print_word() {
})
}
func is_space(ch rune) bool {
return ch != 0xa0 && unicode.IsSpace(ch)
}
func (self *wrapper) handle_rune(ch rune) error {
if ch == '\n' {
self.print_word()
self.end_current_line()
} else if self.current_word.has_text() && ch != 0xa0 && unicode.IsSpace(ch) {
} else if is_space(ch) {
if self.current_word.has_text() {
self.print_word()
}
if self.current_line.cursor_pos >= self.width {
self.end_current_line()
}
self.current_line.add_char(ch)
self.current_line.add_space(ch, self.trim_whitespace)
} else {
num_of_bytes_written := self.current_word.add_rune(ch)
if self.current_word.width() > self.width {
@ -448,18 +486,18 @@ func (self *wrapper) wrap_text(text string) []string {
if text == "" {
return []string{""}
}
self.current_line.reset()
self.current_line.reset(self.trim_whitespace)
self.current_word.reset(func([]byte) {})
self.lines = self.lines[:0]
self.current_line.add_word(utils.UnsafeStringToBytes(self.indent), self.indent_width)
self.current_line.add_indent(self.indent, self.indent_width)
self.ep.ParseString(text)
if !self.current_word.is_empty() {
self.print_word()
}
self.end_current_line()
last_line := self.current_line.reset()
last_line := self.current_line.reset(self.trim_whitespace)
self.newline_prefix()
if last_line == self.current_line.reset() {
if last_line == self.current_line.reset(self.trim_whitespace) {
last_line = ""
}
if last_line != "" {
@ -470,7 +508,7 @@ func (self *wrapper) wrap_text(text string) []string {
func new_wrapper(opts WrapOptions, width int) *wrapper {
width = utils.Max(2, width)
ans := wrapper{indent: opts.Indent, width: width, indent_width: wcswidth.Stringwidth(opts.Indent)}
ans := wrapper{indent: opts.Indent, width: width, trim_whitespace: opts.Trim_whitespace, indent_width: wcswidth.Stringwidth(opts.Indent)}
if opts.Ignore_lines_containing != "" {
ans.ignore_lines_containing = utils.Splitlines(opts.Ignore_lines_containing)
}
@ -485,8 +523,8 @@ func new_wrapper(opts WrapOptions, width int) *wrapper {
type WrapOptions struct {
Ignore_lines_containing string
Trim_whitespace bool // trim whitespace at the start and end of lines (start is after indent)
Indent string
Trim_whitespace bool // trim whitespace at the start and end of lines (start is after indent).
Indent string // indent to add at the start of every line all formatting is cleared for the indent.
}
func WrapTextAsLines(text string, width int, opts WrapOptions) []string {

View File

@ -3,6 +3,7 @@
package style
import (
"encoding/json"
"strings"
"testing"
)
@ -16,7 +17,8 @@ func TestFormatWithIndent(t *testing.T) {
q := opts.Indent + strings.Join(expected, "")
actual := WrapText(text, screen_width, opts)
if actual != q {
t.Fatalf("\nFailed for: %#v\nexpected: %#v\nactual: %#v", text, q, actual)
os, _ := json.Marshal(opts)
t.Fatalf("\nFailed for: %#v\nOptions: %s\nexpected: %#v\nactual: %#v", text, os, q, actual)
}
}
@ -27,6 +29,16 @@ func TestFormatWithIndent(t *testing.T) {
screen_width = 3
tx("one tw", "one\n tw")
screen_width = 4
opts.Trim_whitespace = true
opts.Indent = "X"
tx("one two", "one\nXtwo")
tx("\x1b[2mone \x1b[mtwo", "\x1b[2mone\n\x1b[222mX\x1b[2m\x1b[mtwo")
screen_width = 3
tx("on tw", "on\nXtw")
opts.Indent = ""
opts.Trim_whitespace = false
opts.Indent = "__"
screen_width = 11
tx("testing\n\ntwo", "testing\n\n__two")