diff --git a/src/utils/runes.go b/src/utils/runes.go new file mode 100644 index 0000000..76cb5e3 --- /dev/null +++ b/src/utils/runes.go @@ -0,0 +1,24 @@ +package utils + +import ( + rw "github.com/mattn/go-runewidth" +) + +func TruncateFront(s string, w int, prefix string) string { + if rw.StringWidth(s) <= w { + return s + } + r := []rune(s) + pw := rw.StringWidth(prefix) + w -= pw + width := 0 + i := len(r) - 1 + for ; i >= 0; i-- { + cw := rw.RuneWidth(r[i]) + width += cw + if width > w { + break + } + } + return prefix + string(r[i+1:len(r)]) +} diff --git a/src/utils/runes_test.go b/src/utils/runes_test.go new file mode 100644 index 0000000..67ceefc --- /dev/null +++ b/src/utils/runes_test.go @@ -0,0 +1,50 @@ +package utils + +import "testing" + +const ( + ELLIPSIS = "…" +) + +func TestTruncateFront(t *testing.T) { + tests := []struct { + s string + w int + prefix string + want string + }{ + {"", 0, ELLIPSIS, ""}, + {"", 1, ELLIPSIS, ""}, + {"", 10, ELLIPSIS, ""}, + + {"abcdef", 0, ELLIPSIS, ELLIPSIS}, + {"abcdef", 1, ELLIPSIS, ELLIPSIS}, + {"abcdef", 2, ELLIPSIS, ELLIPSIS + "f"}, + {"abcdef", 5, ELLIPSIS, ELLIPSIS + "cdef"}, + {"abcdef", 6, ELLIPSIS, "abcdef"}, + {"abcdef", 10, ELLIPSIS, "abcdef"}, + + {"abcdef", 0, "...", "..."}, + {"abcdef", 1, "...", "..."}, + {"abcdef", 3, "...", "..."}, + {"abcdef", 4, "...", "...f"}, + {"abcdef", 5, "...", "...ef"}, + {"abcdef", 6, "...", "abcdef"}, + {"abcdef", 10, "...", "abcdef"}, + + {"⦅full~width⦆", 15, ".", "⦅full~width⦆"}, + {"⦅full~width⦆", 14, ".", ".full~width⦆"}, + {"⦅full~width⦆", 13, ".", ".ull~width⦆"}, + {"⦅full~width⦆", 10, ".", ".~width⦆"}, + {"⦅full~width⦆", 9, ".", ".width⦆"}, + {"⦅full~width⦆", 8, ".", ".width⦆"}, + {"⦅full~width⦆", 3, ".", ".⦆"}, + {"⦅full~width⦆", 2, ".", "."}, + } + + for _, test := range tests { + if got := TruncateFront(test.s, test.w, test.prefix); got != test.want { + t.Errorf("TruncateFront(%q, %d, %q) = %q; want %q", test.s, test.w, test.prefix, got, test.want) + } + } +} diff --git a/src/widgets/proc.go b/src/widgets/proc.go index 3a1c61e..a598498 100644 --- a/src/widgets/proc.go +++ b/src/widgets/proc.go @@ -16,6 +16,7 @@ import ( ui "github.com/cjbassi/gotop/src/termui" "github.com/cjbassi/gotop/src/utils" tui "github.com/gizak/termui/v3" + rw "github.com/mattn/go-runewidth" ) const ( @@ -123,7 +124,8 @@ func (self *ProcWidget) HandleEvent(e tui.Event) bool { self.SetEditingFilter(false) case "": if self.filter != "" { - self.filter = self.filter[:len(self.filter)-1] + r := []rune(self.filter) + self.filter = string(r[:len(r)-1]) self.update() } case "": @@ -143,6 +145,8 @@ func (self *ProcWidget) Draw(buf *tui.Buffer) { } func (self *ProcWidget) drawFilter(buf *tui.Buffer) { + padding := 2 + style := self.TitleStyle label := " Filter: " if self.editingFilter { @@ -151,24 +155,34 @@ func (self *ProcWidget) drawFilter(buf *tui.Buffer) { } cursorStyle := tui.NewStyle(style.Bg, style.Fg, tui.ModifierClear) - p := image.Pt(self.Min.X+2, self.Max.Y-1) + p := image.Pt(self.Min.X+padding, self.Max.Y-1) buf.SetString(label, style, p) - p.X += utf8.RuneCountInString(label) + p.X += rw.StringWidth(label) - maxLen := self.Max.X - p.X - 5 - filter := self.filter - if l := utf8.RuneCountInString(filter); l > maxLen { - filter = ELLIPSIS + filter[l-maxLen+1:] + tail := " " + if self.editingFilter { + tail = "] " } + + maxLen := self.Max.X - p.X - padding - rw.StringWidth(tail) + if self.editingFilter { + maxLen -= 1 // for cursor + } + + filter := utils.TruncateFront(self.filter, maxLen, ELLIPSIS) buf.SetString(filter, self.TitleStyle, p) - p.X += utf8.RuneCountInString(filter) + p.X += rw.StringWidth(filter) if self.editingFilter { buf.SetString(CURSOR, cursorStyle, p) - p.X += 1 - remaining := self.Max.X - 2 - p.X - buf.SetString(fmt.Sprintf("%*s", remaining, "] "), style, p) + p.X += rw.StringWidth(CURSOR) + + if remaining := maxLen - rw.StringWidth(filter); remaining > 0 { + buf.SetString(strings.Repeat(" ", remaining), self.TitleStyle, p) + p.X += remaining + } } + buf.SetString(tail, style, p) } func (self *ProcWidget) filterProcs(procs []Proc) []Proc {