pull/268/head
Anton Medvedev 9 months ago
parent 845fa0e25e
commit 3c88d21ea4
No known key found for this signature in database

@ -0,0 +1,176 @@
package dig
import (
"strconv"
"unicode"
)
type state int
const (
start state = iota
unknown
propOrIndex
prop
index
indexEnd
number
doubleQuote
doubleQuoteEscape
singleQuote
singleQuoteEscape
)
func SplitPath(p string) ([]any, bool) {
path := make([]any, 0)
s := ""
state := start
for _, ch := range p {
switch state {
case start:
switch {
case ch == 'x':
state = unknown
case ch == '.':
state = propOrIndex
default:
return path, false
}
case unknown:
switch {
case ch == '.':
state = prop
s = ""
case ch == '[':
state = index
s = ""
default:
return path, false
}
case propOrIndex:
switch {
case isProp(ch):
state = prop
s = string(ch)
case ch == '[':
state = index
default:
return path, false
}
case prop:
switch {
case isProp(ch):
s += string(ch)
case ch == '.':
state = prop
path = append(path, s)
s = ""
case ch == '[':
state = index
path = append(path, s)
s = ""
default:
return path, false
}
case index:
switch {
case unicode.IsDigit(ch):
state = number
s = string(ch)
case ch == '"':
state = doubleQuote
s = ""
case ch == '\'':
state = singleQuote
s = ""
default:
return path, false
}
case indexEnd:
switch {
case ch == ']':
state = unknown
default:
return path, false
}
case number:
switch {
case unicode.IsDigit(ch):
s += string(ch)
case ch == ']':
state = unknown
n, err := strconv.Atoi(s)
if err != nil {
return path, false
}
path = append(path, n)
s = ""
default:
return path, false
}
case doubleQuote:
switch ch {
case '"':
state = indexEnd
path = append(path, s)
s = ""
case '\\':
state = doubleQuoteEscape
default:
s += string(ch)
}
case doubleQuoteEscape:
switch ch {
case '"':
state = doubleQuote
s += string(ch)
default:
return path, false
}
case singleQuote:
switch ch {
case '\'':
state = indexEnd
path = append(path, s)
s = ""
case '\\':
state = singleQuoteEscape
s += string(ch)
default:
s += string(ch)
}
case singleQuoteEscape:
switch ch {
case '\'':
state = singleQuote
s += string(ch)
default:
return path, false
}
}
}
if len(s) > 0 {
if state == prop {
path = append(path, s)
} else {
return path, false
}
}
return path, true
}
func isProp(ch rune) bool {
return unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_' || ch == '$'
}

@ -0,0 +1,137 @@
package dig_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/antonmedv/fx/new/dig"
)
func Test_SplitPath(t *testing.T) {
tests := []struct {
input string
want []any
}{
{
input: "",
want: []any{},
},
{
input: ".",
want: []any{},
},
{
input: "x",
want: []any{},
},
{
input: ".foo",
want: []any{"foo"},
},
{
input: "x.foo",
want: []any{"foo"},
},
{
input: "x[42]",
want: []any{42},
},
{
input: ".[42]",
want: []any{42},
},
{
input: ".42",
want: []any{"42"},
},
{
input: ".физ",
want: []any{"физ"},
},
{
input: ".foo.bar",
want: []any{"foo", "bar"},
},
{
input: ".foo[42]",
want: []any{"foo", 42},
},
{
input: ".foo[42].bar",
want: []any{"foo", 42, "bar"},
},
{
input: ".foo[1][2]",
want: []any{"foo", 1, 2},
},
{
input: ".foo[\"bar\"]",
want: []any{"foo", "bar"},
},
{
input: ".foo[\"bar\\\"\"]",
want: []any{"foo", "bar\""},
},
{
input: ".foo['bar']['baz\\'']",
want: []any{"foo", "bar", "baz\\'"},
},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
p, ok := dig.SplitPath(tt.input)
require.Equal(t, tt.want, p)
require.True(t, ok)
})
}
}
func Test_SplitPath_negative(t *testing.T) {
tests := []struct {
input string
}{
{
input: "./",
},
{
input: "x/",
},
{
input: "1+1",
},
{
input: "x[42",
},
{
input: ".i % 2",
},
{
input: "x[for x]",
},
{
input: "x['y'.",
},
{
input: "x[0?",
},
{
input: "x[\"\\u",
},
{
input: "x['\\n",
},
{
input: "x[9999999999999999999999999999999999999]",
},
{
input: "x[]",
},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
p, ok := dig.SplitPath(tt.input)
require.False(t, ok, p)
})
}
}

@ -5,14 +5,18 @@ go 1.20
require (
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.7.1
github.com/mattn/go-runewidth v0.0.14
github.com/charmbracelet/lipgloss v0.8.0
github.com/mattn/go-runewidth v0.0.15
github.com/mazznoer/colorgrad v0.9.1
github.com/muesli/termenv v0.15.2
github.com/stretchr/testify v1.8.4
)
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
@ -20,10 +24,11 @@ require (
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.3.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

@ -1,13 +1,17 @@
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
@ -15,8 +19,8 @@ github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mazznoer/colorgrad v0.9.1 h1:MB80JYVndKWSMEM1beNqnuOowWGhoQc3DXWXkFp6JlM=
github.com/mazznoer/colorgrad v0.9.1/go.mod h1:WX2R9wt9B47+txJZVVpM9LY+LAGIdi4lTI5wIyreDH4=
github.com/mazznoer/csscolorparser v0.1.2 h1:/UBHuQg792ePmGFzTQAC9u+XbFr7/HzP/Gj70Phyz2A=
@ -27,17 +31,26 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -25,6 +25,7 @@ type KeyMap struct {
Search key.Binding
Next key.Binding
Prev key.Binding
Dig key.Binding
}
var keyMap KeyMap
@ -119,5 +120,9 @@ func init() {
key.WithKeys("N"),
key.WithHelp("", "prev search result"),
),
Dig: key.NewBinding(
key.WithKeys("."),
key.WithHelp("", "dig json"),
),
}
}

@ -9,7 +9,11 @@ import (
"strings"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/antonmedv/fx/new/dig"
)
var (
@ -70,10 +74,20 @@ func main() {
return
}
digInput := textinput.New()
digInput.Prompt = ""
digInput.TextStyle = lipgloss.NewStyle().
Background(lipgloss.Color("7")).
Foreground(lipgloss.Color("0"))
digInput.Cursor.Style = lipgloss.NewStyle().
Background(lipgloss.Color("15")).
Foreground(lipgloss.Color("0"))
m := &model{
head: head,
top: head,
wrap: true,
head: head,
top: head,
wrap: true,
digInput: digInput,
}
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion())
@ -88,7 +102,9 @@ type model struct {
head, top *node
cursor int // cursor position [0, termHeight)
wrap bool
margin int
fileName string
digInput textinput.Model
}
func (m *model) Init() tea.Cmd {
@ -111,6 +127,7 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.down()
case tea.MouseLeft:
m.digInput.Blur()
if msg.Y < m.viewHeight() {
if m.cursor == msg.Y {
to := m.cursorPointsTo()
@ -134,11 +151,30 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case tea.KeyMsg:
if m.digInput.Focused() {
return m.handleDigKey(msg)
}
return m.handleKey(msg)
}
return m, nil
}
func (m *model) handleDigKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch {
case msg.Type == tea.KeyEscape, msg.Type == tea.KeyEnter:
m.digInput.Blur()
default:
m.digInput, cmd = m.digInput.Update(msg)
n := m.dig(m.digInput.Value())
if n != nil {
m.selectNode(n)
}
}
return m, cmd
}
func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch {
case key.Matches(msg, keyMap.Quit):
@ -259,8 +295,14 @@ func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
at = at.parent()
}
m.selectNode(at)
}
case key.Matches(msg, keyMap.Dig):
m.digInput.SetValue(m.cursorPath())
m.digInput.CursorEnd()
m.digInput.Width = m.termWidth - 1
m.digInput.Focus()
}
return m, nil
}
@ -369,10 +411,15 @@ func (m *model) View() string {
screen = append(screen, '\n')
}
statusBar := m.cursorPath() + " "
statusBar += strings.Repeat(" ", max(0, m.termWidth-len(statusBar)-len(m.fileName)))
statusBar += m.fileName
screen = append(screen, currentTheme.StatusBar([]byte(statusBar))...)
if m.digInput.Focused() {
screen = append(screen, m.digInput.View()...)
} else {
var statusBar string
statusBar += m.cursorPath() + " "
statusBar += strings.Repeat(" ", max(0, m.termWidth-len(statusBar)-len(m.fileName)))
statusBar += m.fileName
screen = append(screen, currentTheme.StatusBar([]byte(statusBar))...)
}
return string(screen)
}
@ -455,7 +502,7 @@ func (m *model) cursorPath() string {
at := m.cursorPointsTo()
for at != nil {
if at.prev != nil {
if at.chunk != nil {
if at.chunk != nil && at.value == nil {
at = at.parent()
}
if at.key != nil {
@ -477,3 +524,23 @@ func (m *model) cursorPath() string {
}
return path
}
func (m *model) dig(value string) *node {
p, ok := dig.SplitPath(value)
if !ok {
return nil
}
n := m.top
for _, part := range p {
if n == nil {
return nil
}
switch part := part.(type) {
case string:
n = n.findChildByKey(part)
case int:
n = n.findChildByIndex(part)
}
}
return n
}

@ -1,5 +1,9 @@
package main
import (
"strconv"
)
type node struct {
prev, next, end *node
directParent *node
@ -126,3 +130,35 @@ func (n *node) expandRecursively() {
at = at.next
}
}
func (n *node) findChildByKey(key string) *node {
for at := n.next; at != nil && at != n.end; {
k, err := strconv.Unquote(string(at.key))
if err != nil {
return nil
}
if k == key {
return at
}
if at.end != nil {
at = at.end.next
} else {
at = at.next
}
}
return nil
}
func (n *node) findChildByIndex(index int) *node {
for at := n.next; at != nil && at != n.end; {
if at.index == index {
return at
}
if at.end != nil {
at = at.end.next
} else {
at = at.next
}
}
return nil
}

Loading…
Cancel
Save