// License: GPLv3 Copyright: 2022, Kovid Goyal, package wcswidth import ( "fmt" "strconv" "strings" "github.com/kovidgoyal/kitty/tools/utils" ) var _ = fmt.Print type WCWidthIterator struct { prev_ch rune prev_width, current_width int seg GraphemeSegmentationResult can_combine bool parser EscapeCodeParser rune_count uint } func CreateWCWidthIterator() *WCWidthIterator { var ans WCWidthIterator ans.parser.HandleRune = ans.handle_rune ans.parser.HandleCSI = ans.handle_csi ans.parser.HandleOSC = ans.handle_st_terminated ans.parser.HandleDCS = ans.handle_st_terminated ans.parser.HandlePM = ans.handle_st_terminated ans.parser.HandleSOS = ans.handle_st_terminated ans.parser.HandleAPC = ans.handle_st_terminated return &ans } func (self *WCWidthIterator) Reset() { self.prev_ch = 0 self.prev_width = 0 self.current_width = 0 self.rune_count = 0 self.can_combine = false self.seg = 0 self.parser.Reset() } func (self *WCWidthIterator) handle_csi(csi []byte) error { if len(csi) > 1 && csi[len(csi)-1] == 'b' { num_string := utils.UnsafeBytesToString(csi[:len(csi)-1]) n, err := strconv.Atoi(num_string) if err == nil && n > 0 { for range n { err = self.handle_rune(self.prev_ch) if err != nil { return err } } } } self.can_combine = false self.seg = 0 return nil } func (self *WCWidthIterator) handle_st_terminated(data []byte) error { self.can_combine = false self.seg = 0 return nil } func (self *WCWidthIterator) handle_rune(ch rune) error { self.rune_count += 1 cp := CharPropsFor(ch) self.seg = self.seg.Step(cp) if self.can_combine && self.seg.Add_to_current_cell() == 1 { switch ch { case 0xfe0f: if CharPropsFor(self.prev_ch).Is_emoji_presentation_base() == 1 && self.prev_width == 1 { self.current_width += 1 self.prev_width = 2 } case 0xfe0e: if CharPropsFor(self.prev_ch).Is_emoji_presentation_base() == 1 && self.prev_width == 2 { self.current_width -= 1 self.prev_width = 1 } } } else { width := cp.Width() switch width { case -1: case 0: self.prev_width = 0 case 2: self.prev_width = 2 default: self.prev_width = 1 } self.current_width += self.prev_width self.can_combine = true } self.prev_ch = ch return nil } func (self *WCWidthIterator) ParseByte(b byte) (ans int) { self.parser.ParseByte(b) return self.current_width } func (self *WCWidthIterator) Parse(b []byte) (ans int) { self.current_width = 0 self.parser.Parse(b) return self.current_width } func (self *WCWidthIterator) CurrentWidth() int { return self.current_width } func Stringwidth(text string) int { w := CreateWCWidthIterator() return w.Parse(utils.UnsafeStringToBytes(text)) } func StripEscapeCodes(text string) string { out := strings.Builder{} out.Grow(len(text)) p := EscapeCodeParser{} p.HandleRune = func(ch rune) error { out.WriteRune(ch) return nil } p.ParseString(text) return out.String() }