mirror of
https://github.com/antonmedv/fx.git
synced 2024-09-11 18:27:10 +03:00
Add fuzzy key matching
This commit is contained in:
parent
936e96151a
commit
41f5d50b0d
1
go.mod
1
go.mod
@ -12,6 +12,7 @@ require (
|
|||||||
github.com/mattn/go-runewidth v0.0.15
|
github.com/mattn/go-runewidth v0.0.15
|
||||||
github.com/mazznoer/colorgrad v0.9.1
|
github.com/mazznoer/colorgrad v0.9.1
|
||||||
github.com/muesli/termenv v0.15.2
|
github.com/muesli/termenv v0.15.2
|
||||||
|
github.com/sahilm/fuzzy v0.1.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
3
go.sum
3
go.sum
@ -18,6 +18,7 @@ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2
|
|||||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||||
@ -44,6 +45,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||||
|
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
@ -131,4 +131,6 @@ var (
|
|||||||
yankValue = key.NewBinding(key.WithKeys("y"))
|
yankValue = key.NewBinding(key.WithKeys("y"))
|
||||||
yankKey = key.NewBinding(key.WithKeys("k"))
|
yankKey = key.NewBinding(key.WithKeys("k"))
|
||||||
yankPath = key.NewBinding(key.WithKeys("p"))
|
yankPath = key.NewBinding(key.WithKeys("p"))
|
||||||
|
arrowUp = key.NewBinding(key.WithKeys("up"))
|
||||||
|
arrowDown = key.NewBinding(key.WithKeys("down"))
|
||||||
)
|
)
|
||||||
|
69
main.go
69
main.go
@ -19,6 +19,7 @@ import (
|
|||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
|
"github.com/sahilm/fuzzy"
|
||||||
|
|
||||||
jsonpath "github.com/antonmedv/fx/path"
|
jsonpath "github.com/antonmedv/fx/path"
|
||||||
)
|
)
|
||||||
@ -219,9 +220,33 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
func (m *model) handleDigKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *model) handleDigKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
switch {
|
switch {
|
||||||
case msg.Type == tea.KeyEscape, msg.Type == tea.KeyEnter:
|
case key.Matches(msg, arrowUp):
|
||||||
|
m.up()
|
||||||
|
m.digInput.SetValue(m.cursorPath())
|
||||||
|
m.digInput.CursorEnd()
|
||||||
|
|
||||||
|
case key.Matches(msg, arrowDown):
|
||||||
|
m.down()
|
||||||
|
m.digInput.SetValue(m.cursorPath())
|
||||||
|
m.digInput.CursorEnd()
|
||||||
|
|
||||||
|
case msg.Type == tea.KeyEscape:
|
||||||
m.digInput.Blur()
|
m.digInput.Blur()
|
||||||
|
|
||||||
|
case msg.Type == tea.KeyTab:
|
||||||
|
m.digInput.SetValue(m.cursorPath())
|
||||||
|
m.digInput.CursorEnd()
|
||||||
|
|
||||||
|
case msg.Type == tea.KeyEnter:
|
||||||
|
m.digInput.Blur()
|
||||||
|
digPath, ok := jsonpath.Split(m.digInput.Value())
|
||||||
|
if ok {
|
||||||
|
n := m.selectByPath(digPath)
|
||||||
|
if n != nil {
|
||||||
|
m.selectNode(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
m.digInput, cmd = m.digInput.Update(msg)
|
m.digInput, cmd = m.digInput.Update(msg)
|
||||||
n := m.dig(m.digInput.Value())
|
n := m.dig(m.digInput.Value())
|
||||||
@ -396,7 +421,7 @@ func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
m.yank = true
|
m.yank = true
|
||||||
|
|
||||||
case key.Matches(msg, keyMap.Dig):
|
case key.Matches(msg, keyMap.Dig):
|
||||||
m.digInput.SetValue(m.cursorPath())
|
m.digInput.SetValue(m.cursorPath() + ".")
|
||||||
m.digInput.CursorEnd()
|
m.digInput.CursorEnd()
|
||||||
m.digInput.Width = m.termWidth - 1
|
m.digInput.Width = m.termWidth - 1
|
||||||
m.digInput.Focus()
|
m.digInput.Focus()
|
||||||
@ -777,13 +802,9 @@ func (m *model) cursorKey() string {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *model) dig(value string) *node {
|
func (m *model) selectByPath(path []any) *node {
|
||||||
p, ok := jsonpath.Split(value)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
n := m.top
|
n := m.top
|
||||||
for _, part := range p {
|
for _, part := range path {
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -893,3 +914,35 @@ func (m *model) redoSearch() {
|
|||||||
m.selectSearchResult(cursor)
|
m.selectSearchResult(cursor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *model) dig(v string) *node {
|
||||||
|
p, ok := jsonpath.Split(v)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
at := m.selectByPath(p)
|
||||||
|
if at != nil {
|
||||||
|
return at
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPart := p[len(p)-1]
|
||||||
|
searchTerm, ok := lastPart.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p = p[:len(p)-1]
|
||||||
|
|
||||||
|
at = m.selectByPath(p)
|
||||||
|
if at == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, nodes := at.children()
|
||||||
|
|
||||||
|
matches := fuzzy.Find(searchTerm, keys)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes[matches[0].Index]
|
||||||
|
}
|
||||||
|
67
node.go
67
node.go
@ -168,3 +168,70 @@ func (n *node) findChildByIndex(index int) *node {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *node) paths(prefix string, paths *[]string, nodes *[]*node) {
|
||||||
|
it := n.next
|
||||||
|
for it != nil && it != n.end {
|
||||||
|
var path string
|
||||||
|
|
||||||
|
if it.key != nil {
|
||||||
|
quoted := string(it.key)
|
||||||
|
unquoted, err := strconv.Unquote(quoted)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if identifier.MatchString(unquoted) {
|
||||||
|
path = prefix + "." + unquoted
|
||||||
|
} else {
|
||||||
|
path = prefix + "[" + quoted + "]"
|
||||||
|
}
|
||||||
|
} else if it.index >= 0 {
|
||||||
|
path = prefix + "[" + strconv.Itoa(it.index) + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
*paths = append(*paths, path)
|
||||||
|
*nodes = append(*nodes, it)
|
||||||
|
|
||||||
|
if it.hasChildren() {
|
||||||
|
it.paths(path, paths, nodes)
|
||||||
|
it = it.end.next
|
||||||
|
} else {
|
||||||
|
it = it.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) children() ([]string, []*node) {
|
||||||
|
if !n.hasChildren() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var paths []string
|
||||||
|
var nodes []*node
|
||||||
|
|
||||||
|
var it *node
|
||||||
|
if n.isCollapsed() {
|
||||||
|
it = n.collapsed
|
||||||
|
} else {
|
||||||
|
it = n.next
|
||||||
|
}
|
||||||
|
|
||||||
|
for it != nil && it != n.end {
|
||||||
|
if it.key != nil {
|
||||||
|
unquoted, err := strconv.Unquote(string(it.key))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
paths = append(paths, unquoted)
|
||||||
|
nodes = append(nodes, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.hasChildren() {
|
||||||
|
it = it.end.next
|
||||||
|
} else {
|
||||||
|
it = it.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths, nodes
|
||||||
|
}
|
||||||
|
26
node_test.go
Normal file
26
node_test.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNode_paths(t *testing.T) {
|
||||||
|
n, err := parse([]byte(`{"a": 1, "b": {"f": 2}, "c": [3, 4]}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var paths []string
|
||||||
|
var nodes []*node
|
||||||
|
n.paths("", &paths, &nodes)
|
||||||
|
assert.Equal(t, []string{".a", ".b", ".b.f", ".c", ".c[0]", ".c[1]"}, paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNode_children(t *testing.T) {
|
||||||
|
n, err := parse([]byte(`{"a": 1, "b": {"f": 2}, "c": [3, 4]}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
paths, _ := n.children()
|
||||||
|
assert.Equal(t, []string{"a", "b", "c"}, paths)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user