From 7075f71da48cb6580715efab68325fe4d404c4ff Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 5 Dec 2025 09:40:36 +0530 Subject: [PATCH] Cleanup highlighting of positions in text and add some tests --- kittens/choose_files/results.go | 78 +++++++++++++++++----------- kittens/choose_files/results_test.go | 41 +++++++++++++++ 2 files changed, 88 insertions(+), 31 deletions(-) create mode 100644 kittens/choose_files/results_test.go diff --git a/kittens/choose_files/results.go b/kittens/choose_files/results.go index 5fdbbb9da..1b6ea56a6 100644 --- a/kittens/choose_files/results.go +++ b/kittens/choose_files/results.go @@ -62,6 +62,45 @@ const matching_position_style = "fg=green" const selected_style = "fg=magenta" const current_style = "fg=intense-white bold" +type text_chunk struct { + text string + emphasize bool +} + +func split_up_text(text string, add_ellipsis bool, positions []int) func(func(x text_chunk) bool) { + return func(yield func(x text_chunk) bool) { + if len(positions) == 0 { + if !yield(text_chunk{text, false}) { + return + } + } else { + at := 0 + runes := []rune(text) + limit := len(runes) + for _, p := range positions { + if max(p, at) >= limit || p < at { + break + } + if at < p && !yield(text_chunk{string(runes[at:p]), false}) { + return + } + if !yield(text_chunk{string(runes[p]), true}) { + return + } + at = p + 1 + } + if at < len(runes) { + if !yield(text_chunk{string(runes[at:]), false}) { + return + } + } + } + if add_ellipsis { + yield(text_chunk{"…", false}) + } + } +} + func (h *Handler) render_match_with_positions(text string, add_ellipsis bool, positions []int, is_current bool) { prefix, suffix, _ := strings.Cut(h.lp.SprintStyled(matching_position_style, " "), " ") if is_current { @@ -70,39 +109,16 @@ func (h *Handler) render_match_with_positions(text string, add_ellipsis bool, po defer h.lp.QueueWriteString(s) suffix += p } - write_chunk := func(text string, emphasize bool) { - if text == "" { - return - } - if emphasize { - h.lp.QueueWriteString(prefix) - defer func() { - h.lp.QueueWriteString(suffix) - }() - } - h.lp.QueueWriteString(text) - } - if len(positions) == 0 { - write_chunk(text, false) - } else { - at := 0 - runes := []rune(text) - limit := len(runes) - for _, p := range positions { - if p >= limit || at >= limit || p <= at { - break + for chunk := range split_up_text(text, add_ellipsis, positions) { + if chunk.text != "" { + if chunk.emphasize { + h.lp.QueueWriteString(prefix) + defer func() { + h.lp.QueueWriteString(suffix) + }() } - before := runes[at:p] - write_chunk(string(before), false) - write_chunk(string(runes[p]), true) - at = p + 1 + h.lp.QueueWriteString(chunk.text) } - if at < len(runes) { - write_chunk(string(runes[at:]), false) - } - } - if add_ellipsis { - write_chunk("…", false) } } diff --git a/kittens/choose_files/results_test.go b/kittens/choose_files/results_test.go new file mode 100644 index 000000000..49784d3ce --- /dev/null +++ b/kittens/choose_files/results_test.go @@ -0,0 +1,41 @@ +package choose_files + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" +) + +var _ = fmt.Print + +func TestSplitWithPositions(t *testing.T) { + for _, c := range []struct { + src string + positions []int + expected []string + }{ + {"abc", nil, []string{"abc"}}, + {"abc", []int{0}, []string{"a", "bc"}}, + {"abc", []int{1}, []string{"a", "b", "c"}}, + {"abc", []int{2}, []string{"ab", "c"}}, + {"abc", []int{0, 1}, []string{"a", "b", "c"}}, + {"abc", []int{0, 1, 2}, []string{"a", "b", "c"}}, + {"abc", []int{0, 2}, []string{"a", "b", "c"}}, + // invalid positions + {"abc", []int{-1}, []string{"abc"}}, + {"abc", []int{3}, []string{"abc"}}, + {"abc", []int{0, 3}, []string{"a", "bc"}}, + {"abc", []int{2, 0}, []string{"ab", "c"}}, + {"abc", []int{2, 1}, []string{"ab", "c"}}, + {"abc", []int{1, 0}, []string{"a", "b", "c"}}, + } { + actual := make([]string, 0, len(c.expected)) + for ch := range split_up_text(c.src, false, c.positions) { + actual = append(actual, ch.text) + } + if diff := cmp.Diff(c.expected, actual); diff != "" { + t.Fatalf("Failed for src: %#v positions: %v\n%s", c.src, c.positions, diff) + } + } +}