mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-11 12:25:47 +02:00
Use utils.StringWidth to optimize rendering performance
runewidth.StringWidth is an expensive call, even if the input string is pure ASCII. Improve this by providing a wrapper that short-circuits the call to len if the input is ASCII. Benchmark results show that for non-ASCII strings it makes no noticable difference, but for ASCII strings it provides a more than 200x speedup. BenchmarkStringWidthAsciiOriginal-10 718135 1637 ns/op BenchmarkStringWidthAsciiOptimized-10 159197538 7.545 ns/op BenchmarkStringWidthNonAsciiOriginal-10 486290 2391 ns/op BenchmarkStringWidthNonAsciiOptimized-10 502286 2383 ns/op
This commit is contained in:
parent
a67eda39a5
commit
26132cf5bd
5 changed files with 50 additions and 14 deletions
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
|
@ -272,7 +271,7 @@ func infoSectionChildren(args WindowArrangementArgs) []*boxlayout.Box {
|
|||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "searchPrefix",
|
||||
Size: runewidth.StringWidth(args.SearchPrefix),
|
||||
Size: utils.StringWidth(args.SearchPrefix),
|
||||
},
|
||||
{
|
||||
Window: "search",
|
||||
|
@ -325,7 +324,7 @@ func infoSectionChildren(args WindowArrangementArgs) []*boxlayout.Box {
|
|||
// app status appears very briefly in demos and dislodges the caption,
|
||||
// so better not to show it at all
|
||||
if args.AppStatus != "" {
|
||||
result = append(result, &boxlayout.Box{Window: "appStatus", Size: runewidth.StringWidth(args.AppStatus)})
|
||||
result = append(result, &boxlayout.Box{Window: "appStatus", Size: utils.StringWidth(args.AppStatus)})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,7 +337,7 @@ func infoSectionChildren(args WindowArrangementArgs) []*boxlayout.Box {
|
|||
&boxlayout.Box{
|
||||
Window: "information",
|
||||
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
|
||||
Size: runewidth.StringWidth(utils.Decolorise(args.InformationStr)),
|
||||
Size: utils.StringWidth(utils.Decolorise(args.InformationStr)),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
func (gui *Gui) informationStr() string {
|
||||
|
@ -34,7 +33,7 @@ func (gui *Gui) handleInfoClick() error {
|
|||
width, _ := view.Size()
|
||||
|
||||
if activeMode, ok := gui.helpers.Mode.GetActiveMode(); ok {
|
||||
if width-cx > runewidth.StringWidth(gui.c.Tr.ResetInParentheses) {
|
||||
if width-cx > utils.StringWidth(gui.c.Tr.ResetInParentheses) {
|
||||
return nil
|
||||
}
|
||||
return activeMode.Reset()
|
||||
|
@ -43,10 +42,10 @@ func (gui *Gui) handleInfoClick() error {
|
|||
var title, url string
|
||||
|
||||
// if we're not in an active mode we show the donate button
|
||||
if cx <= runewidth.StringWidth(gui.c.Tr.Donate) {
|
||||
if cx <= utils.StringWidth(gui.c.Tr.Donate) {
|
||||
url = constants.Links.Donate
|
||||
title = gui.c.Tr.Donate
|
||||
} else if cx <= runewidth.StringWidth(gui.c.Tr.Donate)+1+runewidth.StringWidth(gui.c.Tr.AskQuestion) {
|
||||
} else if cx <= utils.StringWidth(gui.c.Tr.Donate)+1+utils.StringWidth(gui.c.Tr.AskQuestion) {
|
||||
url = constants.Links.Discussions
|
||||
title = gui.c.Tr.AskQuestion
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ func getBranchDisplayStrings(
|
|||
// Recency is always three characters, plus one for the space
|
||||
availableWidth := viewWidth - 4
|
||||
if len(branchStatus) > 0 {
|
||||
availableWidth -= runewidth.StringWidth(utils.Decolorise(branchStatus)) + 1
|
||||
availableWidth -= utils.StringWidth(utils.Decolorise(branchStatus)) + 1
|
||||
}
|
||||
if icons.IsIconEnabled() {
|
||||
availableWidth -= 2 // one for the icon, one for the space
|
||||
|
@ -65,7 +65,7 @@ func getBranchDisplayStrings(
|
|||
availableWidth -= utils.COMMIT_HASH_SHORT_SIZE + 1
|
||||
}
|
||||
if checkedOutByWorkTree {
|
||||
availableWidth -= runewidth.StringWidth(worktreeIcon) + 1
|
||||
availableWidth -= utils.StringWidth(worktreeIcon) + 1
|
||||
}
|
||||
|
||||
displayName := b.Name
|
||||
|
@ -79,7 +79,7 @@ func getBranchDisplayStrings(
|
|||
}
|
||||
|
||||
// Don't bother shortening branch names that are already 3 characters or less
|
||||
if runewidth.StringWidth(displayName) > max(availableWidth, 3) {
|
||||
if utils.StringWidth(displayName) > max(availableWidth, 3) {
|
||||
// Never shorten the branch name to less then 3 characters
|
||||
len := max(availableWidth, 4)
|
||||
displayName = runewidth.Truncate(displayName, len, "…")
|
||||
|
|
|
@ -3,6 +3,7 @@ package utils
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/samber/lo"
|
||||
|
@ -21,10 +22,22 @@ type ColumnConfig struct {
|
|||
Alignment Alignment
|
||||
}
|
||||
|
||||
func StringWidth(s string) int {
|
||||
// We are intentionally not using a range loop here, because that would
|
||||
// convert the characters to runes, which is unnecessary work in this case.
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] > unicode.MaxASCII {
|
||||
return runewidth.StringWidth(s)
|
||||
}
|
||||
}
|
||||
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// WithPadding pads a string as much as you want
|
||||
func WithPadding(str string, padding int, alignment Alignment) string {
|
||||
uncoloredStr := Decolorise(str)
|
||||
width := runewidth.StringWidth(uncoloredStr)
|
||||
width := StringWidth(uncoloredStr)
|
||||
if padding < width {
|
||||
return str
|
||||
}
|
||||
|
@ -144,7 +157,7 @@ func getPadWidths(stringArrays [][]string) []int {
|
|||
return MaxFn(stringArrays, func(stringArray []string) int {
|
||||
uncoloredStr := Decolorise(stringArray[i])
|
||||
|
||||
return runewidth.StringWidth(uncoloredStr)
|
||||
return StringWidth(uncoloredStr)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -161,7 +174,7 @@ func MaxFn[T any](items []T, fn func(T) int) int {
|
|||
|
||||
// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis
|
||||
func TruncateWithEllipsis(str string, limit int) string {
|
||||
if runewidth.StringWidth(str) > limit && limit <= 2 {
|
||||
if StringWidth(str) > limit && limit <= 2 {
|
||||
return strings.Repeat(".", limit)
|
||||
}
|
||||
return runewidth.Truncate(str, limit, "…")
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -250,3 +251,27 @@ func TestRenderDisplayStrings(t *testing.T) {
|
|||
assert.EqualValues(t, test.expectedColumnPositions, columnPositions)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringWidthAsciiOriginal(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
runewidth.StringWidth("some ASCII string")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringWidthAsciiOptimized(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
StringWidth("some ASCII string")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringWidthNonAsciiOriginal(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
runewidth.StringWidth("some non-ASCII string 🍉")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringWidthNonAsciiOptimized(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
StringWidth("some non-ASCII string 🍉")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue