diff --git a/tools/utils/style/indent-and-wrap.go b/tools/utils/style/indent-and-wrap.go index e4c5091d7..27c40eb10 100644 --- a/tools/utils/style/indent-and-wrap.go +++ b/tools/utils/style/indent-and-wrap.go @@ -261,14 +261,26 @@ func (self hyperlink_state) as_escape_codes(for_close bool) string { } type line_builder struct { - buf []byte - cursor_pos int + 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) { - self.print_word() + } 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 { diff --git a/tools/utils/style/indent-and-wrap_test.go b/tools/utils/style/indent-and-wrap_test.go index 8878a510f..28c0433b6 100644 --- a/tools/utils/style/indent-and-wrap_test.go +++ b/tools/utils/style/indent-and-wrap_test.go @@ -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")