Ensure wrapping never results in lines longer than the specified word

This commit is contained in:
Kovid Goyal 2023-03-20 20:43:06 +05:30
parent 6c503985ce
commit 18b58c5cf9
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 51 additions and 25 deletions

View File

@ -7,7 +7,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8"
"kitty/tools/utils"
"kitty/tools/wcswidth" "kitty/tools/wcswidth"
) )
@ -305,47 +307,50 @@ type escape_code_ struct {
} }
type word_builder struct { type word_builder struct {
buf strings.Builder buf []byte
escape_codes []escape_code_ escape_codes []escape_code_
text_start_position int text_start_position int
wcswidth *wcswidth.WCWidthIterator
} }
func (self *word_builder) reset() string { func (self *word_builder) reset() string {
ans := self.buf.String() ans := utils.UnsafeBytesToString(self.buf)
sz := self.buf.Len() sz := utils.Min(utils.Max(64, len(ans)), 4096)
if sz < 64 { self.buf = make([]byte, 0, sz)
sz = 64
}
self.buf.Reset()
self.buf.Grow(sz)
self.escape_codes = self.escape_codes[:0] self.escape_codes = self.escape_codes[:0]
self.text_start_position = 0 self.text_start_position = 0
self.wcswidth.Reset()
return ans return ans
} }
func (self *word_builder) is_empty() bool { func (self *word_builder) is_empty() bool {
return self.buf.Len() == 0 return len(self.buf) == 0
} }
func (self *word_builder) width() int { func (self *word_builder) width() int {
return wcswidth.Stringwidth(self.buf.String()) return self.wcswidth.CurrentWidth()
} }
func (self *word_builder) add_escape_code(prefix string, body []byte, suffix string) { func (self *word_builder) add_escape_code(prefix string, body []byte, suffix string) {
e := escape_code_{prefix: prefix, body: string(body), suffix: suffix} e := escape_code_{prefix: prefix, body: string(body), suffix: suffix}
self.escape_codes = append(self.escape_codes, e) self.escape_codes = append(self.escape_codes, e)
self.buf.WriteString(prefix) self.buf = append(self.buf, prefix...)
self.buf.Write(body) self.buf = append(self.buf, body...)
self.buf.WriteString(suffix) self.buf = append(self.buf, suffix...)
} }
func (self *word_builder) has_text() bool { return self.text_start_position != 0 } func (self *word_builder) has_text() bool { return self.text_start_position != 0 }
func (self *word_builder) recalculate_width() {
self.wcswidth.Reset()
self.wcswidth.Parse(self.buf)
}
func (self *word_builder) trim_leading_spaces() { func (self *word_builder) trim_leading_spaces() {
if self.buf.Len() == 0 { if self.is_empty() {
return return
} }
s := self.buf.String() s := utils.UnsafeBytesToString(self.buf)
var before, after string var before, after string
if self.text_start_position != 0 { if self.text_start_position != 0 {
before, after = s[:self.text_start_position-1], s[self.text_start_position-1:] before, after = s[:self.text_start_position-1], s[self.text_start_position-1:]
@ -354,19 +359,30 @@ func (self *word_builder) trim_leading_spaces() {
} }
q := strings.TrimLeftFunc(after, unicode.IsSpace) q := strings.TrimLeftFunc(after, unicode.IsSpace)
if q != after { if q != after {
self.buf.Reset() self.buf = make([]byte, 0, len(s))
self.buf.Grow(len(before) + len(q)) self.buf = append(self.buf, before...)
self.buf.WriteString(before) self.buf = append(self.buf, q...)
self.buf.WriteString(q)
self.text_start_position = len(before) + 1 self.text_start_position = len(before) + 1
self.recalculate_width()
} }
} }
func (self *word_builder) add_rune(ch rune) { func (self *word_builder) add_rune(ch rune) (num_bytes_written int) {
self.buf.WriteRune(ch) before := len(self.buf)
if self.text_start_position == 0 { self.buf = utf8.AppendRune(self.buf, ch)
self.text_start_position = self.buf.Len() num_bytes_written = len(self.buf) - before
for _, b := range self.buf[before:] {
self.wcswidth.ParseByte(b)
} }
if self.text_start_position == 0 {
self.text_start_position = len(self.buf)
}
return
}
func (self *word_builder) remove_trailing_bytes(n int) {
self.buf = self.buf[:len(self.buf)-n]
self.recalculate_width()
} }
type wrapper struct { type wrapper struct {
@ -433,7 +449,12 @@ func (self *wrapper) handle_rune(ch rune) error {
self.print_word() self.print_word()
self.current_line.add_char(ch) self.current_line.add_char(ch)
} else { } else {
self.current_word.add_rune(ch) num_of_bytes_written := self.current_word.add_rune(ch)
if self.current_word.width() > self.width {
self.current_word.remove_trailing_bytes(num_of_bytes_written)
self.print_word()
return self.handle_rune(ch)
}
} }
return nil return nil
} }
@ -471,12 +492,14 @@ func (self *wrapper) wrap_text(text string) []string {
} }
func new_wrapper(indent string, width int) *wrapper { func new_wrapper(indent string, width int) *wrapper {
width = utils.Max(2, width)
ans := wrapper{indent: indent, width: width, indent_width: wcswidth.Stringwidth(indent)} ans := wrapper{indent: indent, width: width, indent_width: wcswidth.Stringwidth(indent)}
ans.ep.HandleRune = ans.handle_rune ans.ep.HandleRune = ans.handle_rune
ans.ep.HandleCSI = ans.handle_csi ans.ep.HandleCSI = ans.handle_csi
ans.ep.HandleOSC = ans.handle_osc ans.ep.HandleOSC = ans.handle_osc
ans.lines = make([]string, 0, 32) ans.lines = make([]string, 0, 32)
ans.current_word.escape_codes = make([]escape_code_, 0, 8) ans.current_word.escape_codes = make([]escape_code_, 0, 8)
ans.current_word.wcswidth = wcswidth.CreateWCWidthIterator()
return &ans return &ans
} }

View File

@ -31,5 +31,8 @@ func TestFormatWithIndent(t *testing.T) {
tx( tx(
"\x1b[31;4:3m\x1b]8;;XXX\x1b\\combined using\x1b]8;;\x1b\\ operators", "\x1b[31;4:3m\x1b]8;;XXX\x1b\\combined using\x1b]8;;\x1b\\ operators",
"\x1b[31;4:3m\x1b]8;;XXX\x1b\\combined\n\x1b[4:0;39m\x1b]8;;\x1b\\__\x1b[4:3;31m\x1b]8;;XXX\x1b\\using\x1b]8;;\x1b\\\n\x1b[4:0;39m__\x1b[4:3;31moperators") "\x1b[31;4:3m\x1b]8;;XXX\x1b\\combined\n\x1b[4:0;39m\x1b]8;;\x1b\\__\x1b[4:3;31m\x1b]8;;XXX\x1b\\using\x1b]8;;\x1b\\\n\x1b[4:0;39m__\x1b[4:3;31moperators")
indent = ""
screen_width = 3
tx("one", "one")
tx("four", "fou\nr")
} }