fix fuzzy matching not working properly (#2321)
* fix fuzzy matching not working properly * delete continuousRangesmine
parent
e94f991ad4
commit
d0ec55737a
|
|
@ -96,3 +96,17 @@ func serviceAccountMatches(podSA, saName string) bool {
|
||||||
}
|
}
|
||||||
return podSA == saName
|
return podSA == saName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContinuousRanges takes a sorted slice of integers and returns a slice of
|
||||||
|
// sub-slices representing continuous ranges of integers.
|
||||||
|
func ContinuousRanges(indexes []int) [][]int {
|
||||||
|
var ranges [][]int
|
||||||
|
for i, p := 1, 0; i <= len(indexes); i++ {
|
||||||
|
if i == len(indexes) || indexes[i]-indexes[p] != i-p {
|
||||||
|
ranges = append(ranges, []int{indexes[p], indexes[i-1] + 1})
|
||||||
|
p = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,3 +60,35 @@ func TestServiceAccountMatches(t *testing.T) {
|
||||||
assert.Equal(t, u.expect, serviceAccountMatches(u.podTemplate.ServiceAccountName, u.saName))
|
assert.Equal(t, u.expect, serviceAccountMatches(u.podTemplate.ServiceAccountName, u.saName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContinuousRanges(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Indexes []int
|
||||||
|
Ranges [][]int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Indexes: []int{0},
|
||||||
|
Ranges: [][]int{{0, 1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Indexes: []int{1},
|
||||||
|
Ranges: [][]int{{1, 2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Indexes: []int{0, 1, 2},
|
||||||
|
Ranges: [][]int{{0, 3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Indexes: []int{4, 5, 6},
|
||||||
|
Ranges: [][]int{{4, 7}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Indexes: []int{0, 2, 4, 5, 6},
|
||||||
|
Ranges: [][]int{{0, 1}, {2, 3}, {4, 7}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
assert.Equal(t, tt.Ranges, ContinuousRanges(tt.Indexes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -69,28 +68,13 @@ func (d *Describe) filter(q string, lines []string) fuzzy.Matches {
|
||||||
if dao.IsFuzzySelector(q) {
|
if dao.IsFuzzySelector(q) {
|
||||||
return d.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
|
return d.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
|
||||||
}
|
}
|
||||||
return d.rxFilter(q, lines)
|
return rxFilter(q, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Describe) fuzzyFilter(q string, lines []string) fuzzy.Matches {
|
func (*Describe) fuzzyFilter(q string, lines []string) fuzzy.Matches {
|
||||||
return fuzzy.Find(q, lines)
|
return fuzzy.Find(q, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Describe) rxFilter(q string, lines []string) fuzzy.Matches {
|
|
||||||
rx, err := regexp.Compile(`(?i)` + q)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
matches := make(fuzzy.Matches, 0, len(lines))
|
|
||||||
for i, l := range lines {
|
|
||||||
if loc := rx.FindStringIndex(l); len(loc) == 2 {
|
|
||||||
matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Describe) fireResourceChanged(lines []string, matches fuzzy.Matches) {
|
func (d *Describe) fireResourceChanged(lines []string, matches fuzzy.Matches) {
|
||||||
for _, l := range d.listeners {
|
for _, l := range d.listeners {
|
||||||
l.ResourceChanged(lines, matches)
|
l.ResourceChanged(lines, matches)
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
"github.com/sahilm/fuzzy"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -37,3 +39,25 @@ func NewExpBackOff(ctx context.Context, start, max time.Duration) backoff.BackOf
|
||||||
bf.InitialInterval, bf.MaxElapsedTime = start, max
|
bf.InitialInterval, bf.MaxElapsedTime = start, max
|
||||||
return backoff.WithContext(bf, ctx)
|
return backoff.WithContext(bf, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rxFilter(q string, lines []string) fuzzy.Matches {
|
||||||
|
rx, err := regexp.Compile(`(?i)` + q)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := make(fuzzy.Matches, 0, len(lines))
|
||||||
|
for i, l := range lines {
|
||||||
|
locs := rx.FindAllStringIndex(l, -1)
|
||||||
|
for _, loc := range locs {
|
||||||
|
indexes := make([]int, 0, loc[1]-loc[0])
|
||||||
|
for v := loc[0]; v < loc[1]; v++ {
|
||||||
|
indexes = append(indexes, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: indexes})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,12 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sahilm/fuzzy"
|
"github.com/sahilm/fuzzy"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestYAML_rxFilter(t *testing.T) {
|
func Test_rxFilter(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
q string
|
q string
|
||||||
lines []string
|
lines []string
|
||||||
|
|
@ -32,7 +31,7 @@ func TestYAML_rxFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
Str: "foo",
|
Str: "foo",
|
||||||
Index: 0,
|
Index: 0,
|
||||||
MatchedIndexes: []int{0, 3},
|
MatchedIndexes: []int{0, 1, 2},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -43,26 +42,25 @@ func TestYAML_rxFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
Str: "foo",
|
Str: "foo",
|
||||||
Index: 0,
|
Index: 0,
|
||||||
MatchedIndexes: []int{0, 3},
|
MatchedIndexes: []int{0, 1, 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Str: "foo",
|
Str: "foo",
|
||||||
Index: 2,
|
Index: 2,
|
||||||
MatchedIndexes: []int{0, 3},
|
MatchedIndexes: []int{0, 1, 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Str: "foo",
|
Str: "foo",
|
||||||
Index: 2,
|
Index: 2,
|
||||||
MatchedIndexes: []int{8, 11},
|
MatchedIndexes: []int{8, 9, 10},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var y YAML
|
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, u.e, y.rxFilter(u.q, u.lines))
|
assert.Equal(t, u.e, rxFilter(u.q, u.lines))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
|
|
@ -115,24 +114,9 @@ func (t *Text) filter(q string, lines []string) fuzzy.Matches {
|
||||||
if dao.IsFuzzySelector(q) {
|
if dao.IsFuzzySelector(q) {
|
||||||
return t.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
|
return t.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
|
||||||
}
|
}
|
||||||
return t.rxFilter(q, lines)
|
return rxFilter(q, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Text) fuzzyFilter(q string, lines []string) fuzzy.Matches {
|
func (*Text) fuzzyFilter(q string, lines []string) fuzzy.Matches {
|
||||||
return fuzzy.Find(q, lines)
|
return fuzzy.Find(q, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Text) rxFilter(q string, lines []string) fuzzy.Matches {
|
|
||||||
rx, err := regexp.Compile(`(?i)` + q)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
matches := make(fuzzy.Matches, 0, len(lines))
|
|
||||||
for i, l := range lines {
|
|
||||||
if loc := rx.FindStringIndex(l); len(loc) == 2 {
|
|
||||||
matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@ func (t *Tree) reconcile(ctx context.Context) error {
|
||||||
|
|
||||||
root.Sort()
|
root.Sort()
|
||||||
if t.query != "" {
|
if t.query != "" {
|
||||||
t.root = root.Filter(t.query, rxFilter)
|
t.root = root.Filter(t.query, rxMatch)
|
||||||
}
|
}
|
||||||
if t.root == nil || t.root.Diff(root) {
|
if t.root == nil || t.root.Diff(root) {
|
||||||
t.root = root
|
t.root = root
|
||||||
|
|
@ -277,7 +277,7 @@ func (t *Tree) getMeta(ctx context.Context, gvr string) (ResourceMeta, error) {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
func rxFilter(q, path string) bool {
|
func rxMatch(q, path string) bool {
|
||||||
rx := regexp.MustCompile(`(?i)` + q)
|
rx := regexp.MustCompile(`(?i)` + q)
|
||||||
|
|
||||||
tokens := strings.Split(path, "::")
|
tokens := strings.Split(path, "::")
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -93,28 +92,13 @@ func (v *Values) filter(q string, lines []string) fuzzy.Matches {
|
||||||
if dao.IsFuzzySelector(q) {
|
if dao.IsFuzzySelector(q) {
|
||||||
return v.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
|
return v.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
|
||||||
}
|
}
|
||||||
return v.rxFilter(q, lines)
|
return rxFilter(q, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Values) fuzzyFilter(q string, lines []string) fuzzy.Matches {
|
func (*Values) fuzzyFilter(q string, lines []string) fuzzy.Matches {
|
||||||
return fuzzy.Find(q, lines)
|
return fuzzy.Find(q, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Values) rxFilter(q string, lines []string) fuzzy.Matches {
|
|
||||||
rx, err := regexp.Compile(`(?i)` + q)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
matches := make(fuzzy.Matches, 0, len(lines))
|
|
||||||
for i, l := range lines {
|
|
||||||
if loc := rx.FindStringIndex(l); len(loc) == 2 {
|
|
||||||
matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Values) fireResourceChanged(lines []string, matches fuzzy.Matches) {
|
func (v *Values) fireResourceChanged(lines []string, matches fuzzy.Matches) {
|
||||||
for _, l := range v.listeners {
|
for _, l := range v.listeners {
|
||||||
l.ResourceChanged(lines, matches)
|
l.ResourceChanged(lines, matches)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -78,28 +77,13 @@ func (y *YAML) filter(q string, lines []string) fuzzy.Matches {
|
||||||
if dao.IsFuzzySelector(q) {
|
if dao.IsFuzzySelector(q) {
|
||||||
return y.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
|
return y.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
|
||||||
}
|
}
|
||||||
return y.rxFilter(q, lines)
|
return rxFilter(q, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*YAML) fuzzyFilter(q string, lines []string) fuzzy.Matches {
|
func (*YAML) fuzzyFilter(q string, lines []string) fuzzy.Matches {
|
||||||
return fuzzy.Find(q, lines)
|
return fuzzy.Find(q, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*YAML) rxFilter(q string, lines []string) fuzzy.Matches {
|
|
||||||
rx, err := regexp.Compile(`(?i)` + q)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
matches := make(fuzzy.Matches, 0, len(lines))
|
|
||||||
for i, l := range lines {
|
|
||||||
locs := rx.FindAllStringIndex(l, -1)
|
|
||||||
for _, loc := range locs {
|
|
||||||
matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matches
|
|
||||||
}
|
|
||||||
|
|
||||||
func (y *YAML) fireResourceChanged(lines []string, matches fuzzy.Matches) {
|
func (y *YAML) fireResourceChanged(lines []string, matches fuzzy.Matches) {
|
||||||
for _, l := range y.listeners {
|
for _, l := range y.listeners {
|
||||||
l.ResourceChanged(lines, matches)
|
l.ResourceChanged(lines, matches)
|
||||||
|
|
|
||||||
|
|
@ -102,19 +102,12 @@ func (d *Details) TextChanged(lines []string) {
|
||||||
|
|
||||||
// TextFiltered notifies when the filter changed.
|
// TextFiltered notifies when the filter changed.
|
||||||
func (d *Details) TextFiltered(lines []string, matches fuzzy.Matches) {
|
func (d *Details) TextFiltered(lines []string, matches fuzzy.Matches) {
|
||||||
d.currentRegion, d.maxRegions = 0, 0
|
d.currentRegion, d.maxRegions = 0, len(matches)
|
||||||
|
ll := linesWithRegions(lines, matches)
|
||||||
ll := make([]string, len(lines))
|
|
||||||
copy(ll, lines)
|
|
||||||
for _, m := range matches {
|
|
||||||
loc, line := m.MatchedIndexes, ll[m.Index]
|
|
||||||
ll[m.Index] = line[:loc[0]] + fmt.Sprintf(`<<<"search_%d">>>`, d.maxRegions) + line[loc[0]:loc[1]] + `<<<"">>>` + line[loc[1]:]
|
|
||||||
d.maxRegions++
|
|
||||||
}
|
|
||||||
|
|
||||||
d.text.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(ll, "\n")))
|
d.text.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(ll, "\n")))
|
||||||
d.text.Highlight()
|
d.text.Highlight()
|
||||||
if d.maxRegions > 0 {
|
if len(matches) > 0 {
|
||||||
d.text.Highlight("search_0")
|
d.text.Highlight("search_0")
|
||||||
d.text.ScrollToHighlight()
|
d.text.ScrollToHighlight()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,14 @@ import (
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/derailed/k9s/internal/model"
|
"github.com/derailed/k9s/internal/model"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
"github.com/derailed/tcell/v2"
|
"github.com/derailed/tcell/v2"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/sahilm/fuzzy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func clipboardWrite(text string) error {
|
func clipboardWrite(text string) error {
|
||||||
|
|
@ -240,3 +242,22 @@ func decorateCpuMemHeaderRows(app *App, data *render.TableData) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchTag(i int, s string) string {
|
||||||
|
return `<<<"search_` + strconv.Itoa(i) + `">>>` + s + `<<<"">>>`
|
||||||
|
}
|
||||||
|
|
||||||
|
func linesWithRegions(lines []string, matches fuzzy.Matches) []string {
|
||||||
|
ll := make([]string, len(lines))
|
||||||
|
copy(ll, lines)
|
||||||
|
offsetForLine := make(map[int]int)
|
||||||
|
for i, m := range matches {
|
||||||
|
for _, loc := range dao.ContinuousRanges(m.MatchedIndexes) {
|
||||||
|
start, end := loc[0]+offsetForLine[m.Index], loc[1]+offsetForLine[m.Index]
|
||||||
|
regionStr := matchTag(i, ll[m.Index][start:end])
|
||||||
|
ll[m.Index] = ll[m.Index][:start] + regionStr + ll[m.Index][end:]
|
||||||
|
offsetForLine[m.Index] += len(regionStr) - (end - start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ll
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/derailed/tcell/v2"
|
"github.com/derailed/tcell/v2"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/sahilm/fuzzy"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
)
|
)
|
||||||
|
|
@ -266,3 +267,61 @@ func TestContainerID(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_linesWithRegions(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
lines []string
|
||||||
|
matches fuzzy.Matches
|
||||||
|
e []string
|
||||||
|
}{
|
||||||
|
"empty-lines": {
|
||||||
|
e: []string{},
|
||||||
|
},
|
||||||
|
"no-match": {
|
||||||
|
lines: []string{"bar"},
|
||||||
|
e: []string{"bar"},
|
||||||
|
},
|
||||||
|
"single-match": {
|
||||||
|
lines: []string{"foo", "bar", "baz"},
|
||||||
|
matches: fuzzy.Matches{
|
||||||
|
{Index: 1, MatchedIndexes: []int{0, 1, 2}},
|
||||||
|
},
|
||||||
|
e: []string{"foo", matchTag(0, "bar"), "baz"},
|
||||||
|
},
|
||||||
|
"single-character": {
|
||||||
|
lines: []string{"foo", "bar", "baz"},
|
||||||
|
matches: fuzzy.Matches{
|
||||||
|
{Index: 1, MatchedIndexes: []int{1}},
|
||||||
|
},
|
||||||
|
e: []string{"foo", "b" + matchTag(0, "a") + "r", "baz"},
|
||||||
|
},
|
||||||
|
"multiple-matches": {
|
||||||
|
lines: []string{"foo", "bar", "baz"},
|
||||||
|
matches: fuzzy.Matches{
|
||||||
|
{Index: 1, MatchedIndexes: []int{0, 1, 2}},
|
||||||
|
{Index: 2, MatchedIndexes: []int{0, 1, 2}},
|
||||||
|
},
|
||||||
|
e: []string{"foo", matchTag(0, "bar"), matchTag(1, "baz")},
|
||||||
|
},
|
||||||
|
"multiple-matches-same-line": {
|
||||||
|
lines: []string{"foosfoo baz", "dfbarfoos bar"},
|
||||||
|
matches: fuzzy.Matches{
|
||||||
|
{Index: 0, MatchedIndexes: []int{0, 1, 2}},
|
||||||
|
{Index: 0, MatchedIndexes: []int{4, 5, 6}},
|
||||||
|
{Index: 1, MatchedIndexes: []int{5, 6, 7}},
|
||||||
|
},
|
||||||
|
e: []string{
|
||||||
|
matchTag(0, "foo") + "s" + matchTag(1, "foo") + " baz",
|
||||||
|
"dfbar" + matchTag(2, "foo") + "s bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert.Equal(t, u.e, linesWithRegions(u.lines, u.matches))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,36 +100,17 @@ func (v *LiveView) ResourceFailed(err error) {
|
||||||
v.text.SetText(cowTalk(err.Error(), x+w))
|
v.text.SetText(cowTalk(err.Error(), x+w))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*LiveView) linesWithRegions(lines []string, matches fuzzy.Matches) []string {
|
|
||||||
ll := make([]string, len(lines))
|
|
||||||
copy(ll, lines)
|
|
||||||
offsetForLine := make(map[int]int)
|
|
||||||
for i, m := range matches {
|
|
||||||
loc, line := m.MatchedIndexes, ll[m.Index]
|
|
||||||
if len(loc) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
offset := offsetForLine[m.Index]
|
|
||||||
|
|
||||||
loc[0], loc[1] = loc[0]+offset, loc[1]+offset
|
|
||||||
regionStr := `<<<"search_` + strconv.Itoa(i) + `">>>` + line[loc[0]:loc[1]] + `<<<"">>>`
|
|
||||||
ll[m.Index] = line[:loc[0]] + regionStr + line[loc[1]:]
|
|
||||||
offsetForLine[m.Index] += len(regionStr) - (loc[1] - loc[0])
|
|
||||||
}
|
|
||||||
return ll
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceChanged notifies when the filter changes.
|
// ResourceChanged notifies when the filter changes.
|
||||||
func (v *LiveView) ResourceChanged(lines []string, matches fuzzy.Matches) {
|
func (v *LiveView) ResourceChanged(lines []string, matches fuzzy.Matches) {
|
||||||
v.app.QueueUpdateDraw(func() {
|
v.app.QueueUpdateDraw(func() {
|
||||||
v.text.SetTextAlign(tview.AlignLeft)
|
v.text.SetTextAlign(tview.AlignLeft)
|
||||||
v.maxRegions = len(matches)
|
v.currentRegion, v.maxRegions = 0, len(matches)
|
||||||
|
|
||||||
if v.text.GetText(true) == "" {
|
if v.text.GetText(true) == "" {
|
||||||
v.text.ScrollToBeginning()
|
v.text.ScrollToBeginning()
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = v.linesWithRegions(lines, matches)
|
lines = linesWithRegions(lines, matches)
|
||||||
v.text.SetText(colorizeYAML(v.app.Styles.Views().Yaml, strings.Join(lines, "\n")))
|
v.text.SetText(colorizeYAML(v.app.Styles.Views().Yaml, strings.Join(lines, "\n")))
|
||||||
v.text.Highlight()
|
v.text.Highlight()
|
||||||
if v.currentRegion < v.maxRegions {
|
if v.currentRegion < v.maxRegions {
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,12 @@ package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/sahilm/fuzzy"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func matchTag(i int, s string) string {
|
|
||||||
return `<<<"search_` + strconv.Itoa(i) + `">>>` + s + `<<<"">>>`
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLiveViewSetText(t *testing.T) {
|
func TestLiveViewSetText(t *testing.T) {
|
||||||
s := `
|
s := `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
|
@ -30,54 +24,3 @@ apiVersion: v1
|
||||||
|
|
||||||
assert.Equal(t, s, sanitizeEsc(v.text.GetText(true)))
|
assert.Equal(t, s, sanitizeEsc(v.text.GetText(true)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLiveView_linesWithRegions(t *testing.T) {
|
|
||||||
uu := map[string]struct {
|
|
||||||
lines []string
|
|
||||||
matches fuzzy.Matches
|
|
||||||
e []string
|
|
||||||
}{
|
|
||||||
"empty-lines": {
|
|
||||||
e: []string{},
|
|
||||||
},
|
|
||||||
"no-match": {
|
|
||||||
lines: []string{"bar"},
|
|
||||||
e: []string{"bar"},
|
|
||||||
},
|
|
||||||
"single-match": {
|
|
||||||
lines: []string{"foo", "bar", "baz"},
|
|
||||||
matches: fuzzy.Matches{
|
|
||||||
{Index: 1, MatchedIndexes: []int{0, 3}},
|
|
||||||
},
|
|
||||||
e: []string{"foo", matchTag(0, "bar"), "baz"},
|
|
||||||
},
|
|
||||||
"multiple-matches": {
|
|
||||||
lines: []string{"foo", "bar", "baz"},
|
|
||||||
matches: fuzzy.Matches{
|
|
||||||
{Index: 1, MatchedIndexes: []int{0, 3}},
|
|
||||||
{Index: 2, MatchedIndexes: []int{0, 3}},
|
|
||||||
},
|
|
||||||
e: []string{"foo", matchTag(0, "bar"), matchTag(1, "baz")},
|
|
||||||
},
|
|
||||||
"multiple-matches-same-line": {
|
|
||||||
lines: []string{"foosfoo baz", "dfbarfoos bar"},
|
|
||||||
matches: fuzzy.Matches{
|
|
||||||
{Index: 0, MatchedIndexes: []int{0, 3}},
|
|
||||||
{Index: 0, MatchedIndexes: []int{4, 7}},
|
|
||||||
{Index: 1, MatchedIndexes: []int{5, 8}},
|
|
||||||
},
|
|
||||||
e: []string{
|
|
||||||
matchTag(0, "foo") + "s" + matchTag(1, "foo") + " baz",
|
|
||||||
"dfbar" + matchTag(2, "foo") + "s bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var v LiveView
|
|
||||||
for k := range uu {
|
|
||||||
u := uu[k]
|
|
||||||
t.Run(k, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
assert.Equal(t, u.e, v.linesWithRegions(u.lines, u.matches))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue