Delete old version

pull/268/head
Anton Medvedev 9 months ago
parent eac8fb6912
commit 8761945f38
No known key found for this signature in database

@ -1,38 +0,0 @@
module github.com/antonmedv/fx
go 1.17
require (
github.com/charmbracelet/bubbles v0.15.0
github.com/charmbracelet/bubbletea v0.23.2
github.com/charmbracelet/lipgloss v0.7.1
github.com/dop251/goja v0.0.0-20230402114112-623f9dda9079
github.com/mattn/go-isatty v0.0.18
github.com/mazznoer/colorgrad v0.9.1
github.com/muesli/termenv v0.15.1
github.com/stretchr/testify v1.7.1
)
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.3 // indirect
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/dlclark/regexp2 v1.9.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/google/pprof v0.0.0-20230406165453-00490a63f317 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mazznoer/csscolorparser v0.1.3 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

176
go.sum

@ -1,176 +0,0 @@
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 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=
github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
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.10.3 h1:fKarbRaObLn/DCsZO4Y3vKCwRUzynQD9L+gGev1E/ho=
github.com/charmbracelet/bubbles v0.10.3/go.mod h1:jOA+DUF1rjZm7gZHcNyIVW+YrBPALKfpGVdJu8UiJsA=
github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI=
github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74=
github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
github.com/charmbracelet/bubbletea v0.20.0 h1:/b8LEPgCbNr7WWZ2LuE/BV1/r4t5PyYJtDb+J3vpwxc=
github.com/charmbracelet/bubbletea v0.20.0/go.mod h1:zpkze1Rioo4rJELjRyGlm9T2YNou1Fm4LIJQSa5QMEM=
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
github.com/charmbracelet/bubbletea v0.23.2 h1:vuUJ9HJ7b/COy4I30e8xDVQ+VRDUEFykIjryPfgsdps=
github.com/charmbracelet/bubbletea v0.23.2/go.mod h1:FaP3WUivcTM0xOKNmhciz60M6I+weYLF76mr1JyI7sM=
github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM=
github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8=
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20220501172647-e1eca0b61fa9 h1:BXEAWJOT2C6ex9iOzVnrYWMFjTRccNs7p8fpLCLLcm0=
github.com/dop251/goja v0.0.0-20220501172647-e1eca0b61fa9/go.mod h1:TQJQ+ZNyFVvUtUEtCZxBhfWiH7RJqR3EivNmvD6Waik=
github.com/dop251/goja v0.0.0-20230402114112-623f9dda9079 h1:xkbJGxVnk5sM8/LXeTKaBOfAZrI+iqvIPyH8oK1c6CQ=
github.com/dop251/goja v0.0.0-20230402114112-623f9dda9079/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
github.com/google/pprof v0.0.0-20230406165453-00490a63f317/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
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.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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/mazznoer/colorgrad v0.8.1 h1:Bw/ks+KujOOg9E6YQvPqSqTLryiFnwliAH5VMZarSTI=
github.com/mazznoer/colorgrad v0.8.1/go.mod h1:xCjvoNkXHJIAPOUMSMrXkFdxTGQqk8zMYS3e5hSLghA=
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.0/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtdHtGfQ32xZ5PJQic=
github.com/mazznoer/csscolorparser v0.1.2 h1:/UBHuQg792ePmGFzTQAC9u+XbFr7/HzP/Gj70Phyz2A=
github.com/mazznoer/csscolorparser v0.1.2/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtdHtGfQ32xZ5PJQic=
github.com/mazznoer/csscolorparser v0.1.3 h1:vug4zh6loQxAUxfU1DZEu70gTPufDPspamZlHAkKcxE=
github.com/mazznoer/csscolorparser v0.1.3/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtdHtGfQ32xZ5PJQic=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
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.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -1,71 +0,0 @@
package main
import (
"fmt"
"reflect"
"strings"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/lipgloss"
)
func usage(keyMap KeyMap) string {
title := lipgloss.NewStyle().Bold(true)
pad := lipgloss.NewStyle().PaddingLeft(4)
return fmt.Sprintf(`
%v
Terminal JSON viewer
%v
fx data.json
fx data.json .field
curl ... | fx
%v
-h, --help print help
-v, --version print version
--print-code print code of the reducer
%v
%v
%v
[https://fx.wtf]
`,
title.Render("fx "+version),
title.Render("Usage"),
title.Render("Flags"),
title.Render("Key Bindings"),
strings.Join(keyMapInfo(keyMap, pad), "\n"),
title.Render("More info"),
)
}
func keyMapInfo(keyMap KeyMap, style lipgloss.Style) []string {
v := reflect.ValueOf(keyMap)
fields := reflect.VisibleFields(v.Type())
keys := make([]string, 0)
for i := range fields {
k := v.Field(i).Interface().(key.Binding)
str := k.Help().Key
if width(str) == 0 {
str = strings.Join(k.Keys(), ", ")
}
keys = append(keys, fmt.Sprintf("%v ", str))
}
desc := make([]string, 0)
for i := range fields {
k := v.Field(i).Interface().(key.Binding)
desc = append(desc, fmt.Sprintf("%v", k.Help().Desc))
}
content := lipgloss.JoinHorizontal(
lipgloss.Top,
strings.Join(keys, "\n"),
strings.Join(desc, "\n"),
)
return strings.Split(style.Render(content), "\n")
}

@ -1,121 +0,0 @@
package main
import "github.com/charmbracelet/bubbles/key"
type KeyMap struct {
Quit key.Binding
Help key.Binding
PageDown key.Binding
PageUp key.Binding
HalfPageUp key.Binding
HalfPageDown key.Binding
GotoTop key.Binding
GotoBottom key.Binding
Down key.Binding
Up key.Binding
Expand key.Binding
Collapse key.Binding
ExpandRecursively key.Binding
CollapseRecursively key.Binding
ExpandAll key.Binding
CollapseAll key.Binding
NextSibling key.Binding
PrevSibling key.Binding
ToggleWrap key.Binding
Search key.Binding
Next key.Binding
Prev key.Binding
}
func DefaultKeyMap() KeyMap {
return KeyMap{
Quit: key.NewBinding(
key.WithKeys("q", "ctrl+c", "esc"),
key.WithHelp("", "exit program"),
),
Help: key.NewBinding(
key.WithKeys("?"),
key.WithHelp("", "show help"),
),
PageDown: key.NewBinding(
key.WithKeys("pgdown", " ", "f"),
key.WithHelp("pgdown, space, f", "page down"),
),
PageUp: key.NewBinding(
key.WithKeys("pgup", "b"),
key.WithHelp("pgup, b", "page up"),
),
HalfPageUp: key.NewBinding(
key.WithKeys("u", "ctrl+u"),
key.WithHelp("", "half page up"),
),
HalfPageDown: key.NewBinding(
key.WithKeys("d", "ctrl+d"),
key.WithHelp("", "half page down"),
),
GotoTop: key.NewBinding(
key.WithKeys("g"),
key.WithHelp("", "goto top"),
),
GotoBottom: key.NewBinding(
key.WithKeys("G"),
key.WithHelp("", "goto bottom"),
),
Down: key.NewBinding(
key.WithKeys("down", "j"),
key.WithHelp("", "down"),
),
Up: key.NewBinding(
key.WithKeys("up", "k"),
key.WithHelp("", "up"),
),
Expand: key.NewBinding(
key.WithKeys("right", "l"),
key.WithHelp("", "expand"),
),
Collapse: key.NewBinding(
key.WithKeys("left", "h"),
key.WithHelp("", "collapse"),
),
ExpandRecursively: key.NewBinding(
key.WithKeys("L"),
key.WithHelp("", "expand recursively"),
),
CollapseRecursively: key.NewBinding(
key.WithKeys("H"),
key.WithHelp("", "collapse recursively"),
),
ExpandAll: key.NewBinding(
key.WithKeys("e"),
key.WithHelp("", "expand all"),
),
CollapseAll: key.NewBinding(
key.WithKeys("E"),
key.WithHelp("", "collapse all"),
),
NextSibling: key.NewBinding(
key.WithKeys("J"),
key.WithHelp("", "next sibling"),
),
PrevSibling: key.NewBinding(
key.WithKeys("K"),
key.WithHelp("", "previous sibling"),
),
ToggleWrap: key.NewBinding(
key.WithKeys("z"),
key.WithHelp("", "toggle strings wrap"),
),
Search: key.NewBinding(
key.WithKeys("/"),
key.WithHelp("", "search regexp"),
),
Next: key.NewBinding(
key.WithKeys("n"),
key.WithHelp("", "next search result"),
),
Prev: key.NewBinding(
key.WithKeys("N"),
key.WithHelp("", "prev search result"),
),
}
}

@ -1,674 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io/fs"
"os"
"path"
"runtime/pprof"
"strings"
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
. "github.com/antonmedv/fx/pkg/reducer"
. "github.com/antonmedv/fx/pkg/theme"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/mattn/go-isatty"
"github.com/muesli/termenv"
)
var (
flagHelp bool
flagVersion bool
flagPrintCode bool
)
func main() {
var args []string
for _, arg := range os.Args[1:] {
switch arg {
case "-h", "--help":
flagHelp = true
case "-v", "-V", "--version":
flagVersion = true
case "--print-code":
flagPrintCode = true
default:
args = append(args, arg)
}
}
if flagHelp {
fmt.Println(usage(DefaultKeyMap()))
return
}
if flagVersion {
fmt.Println(version)
return
}
cpuProfile := os.Getenv("CPU_PROFILE")
if cpuProfile != "" {
f, err := os.Create(cpuProfile)
if err != nil {
panic(err)
}
err = pprof.StartCPUProfile(f)
if err != nil {
panic(err)
}
}
themeId, ok := os.LookupEnv("FX_THEME")
if !ok {
themeId = "1"
}
theme, ok := Themes[themeId]
if !ok {
theme = Themes["1"]
}
if termenv.ColorProfile() == termenv.Ascii {
theme = Themes["0"]
}
var showSize bool
if s, ok := os.LookupEnv("FX_SHOW_SIZE"); ok {
if s == "true" {
showSize = true
}
}
stdinIsTty := isatty.IsTerminal(os.Stdin.Fd())
stdoutIsTty := isatty.IsTerminal(os.Stdout.Fd())
filePath := ""
fileName := ""
var dec *json.Decoder
if stdinIsTty {
// Nothing was piped, maybe file argument?
if len(args) >= 1 {
filePath = args[0]
f, err := os.Open(filePath)
if err != nil {
switch err.(type) {
case *fs.PathError:
fmt.Println(err)
os.Exit(1)
default:
panic(err)
}
}
fileName = path.Base(filePath)
dec = json.NewDecoder(f)
args = args[1:]
}
} else {
dec = json.NewDecoder(os.Stdin)
}
if dec == nil {
fmt.Println(usage(DefaultKeyMap()))
os.Exit(1)
}
dec.UseNumber()
object, err := Parse(dec)
if err != nil {
fmt.Println("JSON Parse Error:", err.Error())
os.Exit(1)
}
lang, ok := os.LookupEnv("FX_LANG")
if !ok {
lang = "js"
}
var fxrc string
if lang == "js" || lang == "node" {
home, err := os.UserHomeDir()
if err == nil {
b, err := os.ReadFile(path.Join(home, ".fxrc.js"))
if err == nil {
fxrc = "\n" + string(b)
if lang == "js" {
parts := strings.SplitN(fxrc, "// nodejs:", 2)
fxrc = parts[0]
}
}
}
}
if dec.More() {
os.Exit(stream(dec, object, lang, args, theme, fxrc))
}
if len(args) > 0 || !stdoutIsTty {
if len(args) > 0 && flagPrintCode {
fmt.Print(GenerateCode(lang, args, fxrc))
return
}
if lang == "js" {
simplePath, ok := SplitSimplePath(args)
if ok {
output := GetBySimplePath(object, simplePath)
Echo(output, theme)
os.Exit(0)
}
vm, fn, err := CreateJS(args, fxrc)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
os.Exit(ReduceJS(vm, fn, object, theme))
} else {
os.Exit(Reduce(object, lang, args, theme, fxrc))
}
}
// Start interactive mode.
expand := map[string]bool{"": true}
if array, ok := object.(Array); ok {
for i := range array {
expand[accessor("", i)] = true
}
}
parents := map[string]string{}
children := map[string][]string{}
canBeExpanded := map[string]bool{}
Dfs(object, func(it Iterator) {
parents[it.Path] = it.Parent
children[it.Parent] = append(children[it.Parent], it.Path)
switch it.Object.(type) {
case *Dict:
canBeExpanded[it.Path] = len(it.Object.(*Dict).Keys) > 0
case Array:
canBeExpanded[it.Path] = len(it.Object.(Array)) > 0
}
})
input := textinput.New()
input.Prompt = ""
m := &model{
fileName: fileName,
theme: theme,
json: object,
showSize: showSize,
width: 80,
height: 60,
mouseWheelDelta: 3,
keyMap: DefaultKeyMap(),
expandedPaths: expand,
canBeExpanded: canBeExpanded,
parents: parents,
children: children,
nextSiblings: map[string]string{},
prevSiblings: map[string]string{},
wrap: true,
searchInput: input,
}
m.collectSiblings(m.json, "")
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion())
if err := p.Start(); err != nil {
panic(err)
}
if cpuProfile != "" {
pprof.StopCPUProfile()
}
os.Exit(m.exitCode)
}
type model struct {
exitCode int
width, height int
windowHeight int
footerHeight int
wrap bool
theme Theme
showSize bool // Show number of elements in preview
fileName string
json interface{}
lines []string
mouseWheelDelta int // Number of lines the mouse wheel will scroll
offset int // offset is the vertical scroll position
keyMap KeyMap
showHelp bool
expandedPaths map[string]bool // set of expanded paths
canBeExpanded map[string]bool // set of path => can be expanded (i.e. dict or array)
paths []string // array of paths on screen
pathToLineNumber map[string]int // map of path => line Number
pathToIndex map[string]int // map of path => index in m.paths
lineNumberToPath map[int]string // map of line Number => path
parents map[string]string // map of subpath => parent path
children map[string][]string // map of path => child paths
nextSiblings, prevSiblings map[string]string // map of path => sibling path
cursor int // cursor in [0, len(m.paths)]
showCursor bool
searchInput textinput.Model
searchRegexCompileError string
showSearchResults bool
searchResults []*searchResult
searchResultsCursor int
highlightIndex map[string]*rangeGroup
}
func (m *model) Init() tea.Cmd {
return nil
}
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.windowHeight = msg.Height
m.searchInput.Width = msg.Width - 2 // minus prompt
m.render()
case tea.MouseMsg:
switch msg.Type {
case tea.MouseWheelUp:
m.LineUp(m.mouseWheelDelta)
case tea.MouseWheelDown:
m.LineDown(m.mouseWheelDelta)
}
}
if m.searchInput.Focused() {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEsc:
m.searchInput.Blur()
m.clearSearchResults()
m.render()
case tea.KeyEnter:
m.doSearch(m.searchInput.Value())
}
}
var cmd tea.Cmd
m.searchInput, cmd = m.searchInput.Update(msg)
return m, cmd
}
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keyMap.PageDown):
m.ViewDown()
m.moveCursorToView()
m.render()
case key.Matches(msg, m.keyMap.PageUp):
m.ViewUp()
m.moveCursorToView()
m.render()
case key.Matches(msg, m.keyMap.HalfPageDown):
m.HalfViewDown()
m.moveCursorToView()
m.render()
case key.Matches(msg, m.keyMap.HalfPageUp):
m.HalfViewUp()
m.moveCursorToView()
m.render()
case key.Matches(msg, m.keyMap.GotoTop):
m.GotoTop()
m.moveCursorToTop()
m.render()
case key.Matches(msg, m.keyMap.GotoBottom):
m.GotoBottom()
m.moveCursorToBottom()
m.render()
}
}
if m.showHelp {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keyMap.Quit):
m.showHelp = false
m.render()
case key.Matches(msg, m.keyMap.Down):
m.LineDown(1)
case key.Matches(msg, m.keyMap.Up):
m.LineUp(1)
}
}
return m, nil
}
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keyMap.Quit):
m.exitCode = 0
return m, tea.Quit
case key.Matches(msg, m.keyMap.Help):
m.GotoTop()
m.showHelp = !m.showHelp
m.render()
case key.Matches(msg, m.keyMap.Down):
m.down()
m.render()
m.scrollDownToCursor()
case key.Matches(msg, m.keyMap.Up):
m.up()
m.render()
m.scrollUpToCursor()
case key.Matches(msg, m.keyMap.NextSibling):
nextSiblingPath, ok := m.nextSiblings[m.cursorPath()]
if ok {
m.showCursor = true
m.cursor = m.pathToIndex[nextSiblingPath]
} else {
m.down()
}
m.render()
m.scrollDownToCursor()
case key.Matches(msg, m.keyMap.PrevSibling):
prevSiblingPath, ok := m.prevSiblings[m.cursorPath()]
if ok {
m.showCursor = true
m.cursor = m.pathToIndex[prevSiblingPath]
} else {
m.up()
}
m.render()
m.scrollUpToCursor()
case key.Matches(msg, m.keyMap.Expand):
m.showCursor = true
if m.canBeExpanded[m.cursorPath()] {
m.expandedPaths[m.cursorPath()] = true
}
m.render()
case key.Matches(msg, m.keyMap.ExpandRecursively):
m.showCursor = true
if m.canBeExpanded[m.cursorPath()] {
m.expandRecursively(m.cursorPath())
}
m.render()
case key.Matches(msg, m.keyMap.Collapse):
m.showCursor = true
if m.canBeExpanded[m.cursorPath()] && m.expandedPaths[m.cursorPath()] {
m.expandedPaths[m.cursorPath()] = false
} else {
parentPath, ok := m.parents[m.cursorPath()]
if ok {
m.expandedPaths[parentPath] = false
m.cursor = m.pathToIndex[parentPath]
}
}
m.render()
m.scrollUpToCursor()
case key.Matches(msg, m.keyMap.CollapseRecursively):
m.showCursor = true
if m.canBeExpanded[m.cursorPath()] && m.expandedPaths[m.cursorPath()] {
m.collapseRecursively(m.cursorPath())
} else {
parentPath, ok := m.parents[m.cursorPath()]
if ok {
m.collapseRecursively(parentPath)
m.cursor = m.pathToIndex[parentPath]
}
}
m.render()
m.scrollUpToCursor()
case key.Matches(msg, m.keyMap.ToggleWrap):
m.wrap = !m.wrap
m.render()
case key.Matches(msg, m.keyMap.ExpandAll):
Dfs(m.json, func(it Iterator) {
switch it.Object.(type) {
case *Dict, Array:
m.expandedPaths[it.Path] = true
}
})
m.render()
case key.Matches(msg, m.keyMap.CollapseAll):
m.expandedPaths = map[string]bool{
"": true,
}
m.render()
case key.Matches(msg, m.keyMap.Search):
m.showSearchResults = false
m.searchRegexCompileError = ""
m.searchInput.Focus()
m.render()
return m, textinput.Blink
case key.Matches(msg, m.keyMap.Next):
if m.showSearchResults {
m.nextSearchResult()
}
case key.Matches(msg, m.keyMap.Prev):
if m.showSearchResults {
m.prevSearchResult()
}
}
case tea.MouseMsg:
switch msg.Type {
case tea.MouseLeft:
m.showCursor = true
if msg.Y >= m.height {
// Clicked on status bar or search input.
break
}
clickedPath, ok := m.lineNumberToPath[m.offset+msg.Y]
if ok {
if m.canBeExpanded[clickedPath] {
m.expandedPaths[clickedPath] = !m.expandedPaths[clickedPath]
}
m.cursor = m.pathToIndex[clickedPath]
m.render()
}
}
}
return m, nil
}
func (m *model) View() string {
lines := m.visibleLines()
extraLines := ""
if len(lines) < m.height {
extraLines = strings.Repeat("\n", max(0, m.height-len(lines)))
}
if m.showHelp {
statusBar := "Press Esc or q to close help."
statusBar += strings.Repeat(" ", max(0, m.width-width(statusBar)))
statusBar = m.theme.StatusBar(statusBar)
return strings.Join(lines, "\n") + extraLines + "\n" + statusBar
}
statusBar := m.cursorPath() + " "
statusBar += strings.Repeat(" ", max(0, m.width-width(statusBar)-width(m.fileName)))
statusBar += m.fileName
statusBar = m.theme.StatusBar(statusBar)
output := strings.Join(lines, "\n") + extraLines + "\n" + statusBar
if m.searchInput.Focused() {
output += "\n/" + m.searchInput.View()
}
if len(m.searchRegexCompileError) > 0 {
output += fmt.Sprintf("\n/%v/i %v", m.searchInput.Value(), m.searchRegexCompileError)
}
if m.showSearchResults {
if len(m.searchResults) == 0 {
output += fmt.Sprintf("\n/%v/i not found", m.searchInput.Value())
} else {
output += fmt.Sprintf("\n/%v/i found: [%v/%v]", m.searchInput.Value(), m.searchResultsCursor+1, len(m.searchResults))
}
}
return output
}
func (m *model) recalculateViewportHeight() {
m.height = m.windowHeight
m.height-- // status bar
if !m.showHelp {
if m.searchInput.Focused() {
m.height--
}
if m.showSearchResults {
m.height--
}
if len(m.searchRegexCompileError) > 0 {
m.height--
}
}
}
func (m *model) render() {
m.recalculateViewportHeight()
if m.showHelp {
m.lines = keyMapInfo(m.keyMap, lipgloss.NewStyle().PaddingLeft(4).PaddingTop(2).PaddingBottom(2))
return
}
m.paths = make([]string, 0)
m.pathToIndex = make(map[string]int, 0)
if m.pathToLineNumber == nil {
m.pathToLineNumber = make(map[string]int, 0)
} else {
m.pathToLineNumber = make(map[string]int, len(m.pathToLineNumber))
}
if m.lineNumberToPath == nil {
m.lineNumberToPath = make(map[int]string, 0)
} else {
m.lineNumberToPath = make(map[int]string, len(m.lineNumberToPath))
}
m.lines = m.print(m.json, 1, 0, 0, "", true)
if m.offset > len(m.lines)-1 {
m.GotoBottom()
}
}
func (m *model) cursorPath() string {
if m.cursor == 0 {
return ""
}
if 0 <= m.cursor && m.cursor < len(m.paths) {
return m.paths[m.cursor]
}
return "?"
}
func (m *model) cursorLineNumber() int {
if 0 <= m.cursor && m.cursor < len(m.paths) {
return m.pathToLineNumber[m.paths[m.cursor]]
}
return -1
}
func (m *model) expandRecursively(path string) {
if m.canBeExpanded[path] {
m.expandedPaths[path] = true
for _, childPath := range m.children[path] {
if childPath != "" {
m.expandRecursively(childPath)
}
}
}
}
func (m *model) collapseRecursively(path string) {
m.expandedPaths[path] = false
for _, childPath := range m.children[path] {
if childPath != "" {
m.collapseRecursively(childPath)
}
}
}
func (m *model) collectSiblings(v interface{}, path string) {
switch v.(type) {
case *Dict:
prev := ""
for _, k := range v.(*Dict).Keys {
subpath := path + "." + k
if prev != "" {
m.nextSiblings[prev] = subpath
m.prevSiblings[subpath] = prev
}
prev = subpath
value, _ := v.(*Dict).Get(k)
m.collectSiblings(value, subpath)
}
case Array:
prev := ""
for i, value := range v.(Array) {
subpath := fmt.Sprintf("%v[%v]", path, i)
if prev != "" {
m.nextSiblings[prev] = subpath
m.prevSiblings[subpath] = prev
}
prev = subpath
m.collectSiblings(value, subpath)
}
}
}
func (m *model) down() {
m.showCursor = true
if m.cursor < len(m.paths)-1 { // scroll till last element in m.paths
m.cursor++
} else {
// at the bottom of viewport maybe some hidden brackets, lets scroll to see them
if !m.AtBottom() {
m.LineDown(1)
}
}
if m.cursor >= len(m.paths) {
m.cursor = len(m.paths) - 1
}
}
func (m *model) up() {
m.showCursor = true
if m.cursor > 0 {
m.cursor--
}
if m.cursor >= len(m.paths) {
m.cursor = len(m.paths) - 1
}
}
func (m *model) moveCursorToView() {
top := 0
bottom := 0
// find the first path in the viewport
for i := 0; i < m.height && top == 0; i++ {
top = m.pathToIndex[m.lineNumberToPath[m.offset+i]]
}
// find the last path in the viewport
for i := 1; i < m.height && bottom == 0; i++ {
bottom = m.pathToIndex[m.lineNumberToPath[m.offset+m.height-i]]
}
m.showCursor = true
m.cursor = clamp(m.cursor, bottom, top)
}
func (m *model) moveCursorToTop() {
m.showCursor = true
m.cursor = 0
}
func (m *model) moveCursorToBottom() {
m.showCursor = true
m.cursor = len(m.paths) - 1
}

@ -1,26 +0,0 @@
package dict
type Dict struct {
Keys []string
Values map[string]interface{}
}
func NewDict() *Dict {
return &Dict{
Keys: make([]string, 0),
Values: make(map[string]interface{}),
}
}
func (d *Dict) Get(key string) (interface{}, bool) {
val, exists := d.Values[key]
return val, exists
}
func (d *Dict) Set(key string, value interface{}) {
_, exists := d.Values[key]
if !exists {
d.Keys = append(d.Keys, key)
}
d.Values[key] = value
}

@ -1,65 +0,0 @@
package dict
import "testing"
func Test_dict(t *testing.T) {
d := NewDict()
d.Set("number", 3)
v, _ := d.Get("number")
if v.(int) != 3 {
t.Error("Set number")
}
// string
d.Set("string", "x")
v, _ = d.Get("string")
if v.(string) != "x" {
t.Error("Set string")
}
// string slice
d.Set("strings", []string{
"t",
"u",
})
v, _ = d.Get("strings")
if v.([]string)[0] != "t" {
t.Error("Set strings first index")
}
if v.([]string)[1] != "u" {
t.Error("Set strings second index")
}
// mixed slice
d.Set("mixed", []interface{}{
1,
"1",
})
v, _ = d.Get("mixed")
if v.([]interface{})[0].(int) != 1 {
t.Error("Set mixed int")
}
if v.([]interface{})[1].(string) != "1" {
t.Error("Set mixed string")
}
// overriding existing key
d.Set("number", 4)
v, _ = d.Get("number")
if v.(int) != 4 {
t.Error("Override existing key")
}
// Keys
expectedKeys := []string{
"number",
"string",
"strings",
"mixed",
}
for i, key := range d.Keys {
if key != expectedKeys[i] {
t.Error("Keys method", key, "!=", expectedKeys[i])
}
}
for i, key := range expectedKeys {
if key != expectedKeys[i] {
t.Error("Keys method", key, "!=", expectedKeys[i])
}
}
}

@ -1,86 +0,0 @@
package json
import (
"encoding/json"
. "github.com/antonmedv/fx/pkg/dict"
)
func Parse(dec *json.Decoder) (interface{}, error) {
token, err := dec.Token()
if err != nil {
return nil, err
}
if delim, ok := token.(json.Delim); ok {
switch delim {
case '{':
return decodeDict(dec)
case '[':
return decodeArray(dec)
}
}
return token, nil
}
func decodeDict(dec *json.Decoder) (*Dict, error) {
d := NewDict()
for {
token, err := dec.Token()
if err != nil {
return nil, err
}
if delim, ok := token.(json.Delim); ok && delim == '}' {
return d, nil
}
key := token.(string)
token, err = dec.Token()
if err != nil {
return nil, err
}
var value interface{} = token
if delim, ok := token.(json.Delim); ok {
switch delim {
case '{':
value, err = decodeDict(dec)
if err != nil {
return nil, err
}
case '[':
value, err = decodeArray(dec)
if err != nil {
return nil, err
}
}
}
d.Set(key, value)
}
}
func decodeArray(dec *json.Decoder) ([]interface{}, error) {
slice := make(Array, 0)
for index := 0; ; index++ {
token, err := dec.Token()
if err != nil {
return nil, err
}
if delim, ok := token.(json.Delim); ok {
switch delim {
case '{':
value, err := decodeDict(dec)
if err != nil {
return nil, err
}
slice = append(slice, value)
case '[':
value, err := decodeArray(dec)
if err != nil {
return nil, err
}
slice = append(slice, value)
case ']':
return slice, nil
}
continue
}
slice = append(slice, token)
}
}

@ -1,51 +0,0 @@
package json
import (
"encoding/json"
. "github.com/antonmedv/fx/pkg/dict"
"strings"
"testing"
)
func Test_parse(t *testing.T) {
input := `{
"a": 1,
"b": 2,
"a": 3,
"slice": [{"z": "z", "1": "1"}]
}`
p, err := Parse(json.NewDecoder(strings.NewReader(input)))
if err != nil {
t.Error("JSON parse error", err)
}
o := p.(*Dict)
expectedKeys := []string{
"a",
"b",
"slice",
}
for i := range o.Keys {
if o.Keys[i] != expectedKeys[i] {
t.Error("Wrong key order ", i, o.Keys[i], "!=", expectedKeys[i])
}
}
s, ok := o.Get("slice")
if !ok {
t.Error("slice missing")
}
a := s.(Array)
z := a[0].(*Dict)
expectedKeys = []string{
"z",
"1",
}
for i := range z.Keys {
if z.Keys[i] != expectedKeys[i] {
t.Error("Wrong key order for nested map ", i, z.Keys[i], "!=", expectedKeys[i])
}
}
}

@ -1,71 +0,0 @@
package json
import (
"encoding/json"
"fmt"
"github.com/antonmedv/fx/pkg/dict"
"github.com/antonmedv/fx/pkg/theme"
"strings"
)
func PrettyPrint(v interface{}, level int, theme theme.Theme) string {
ident := strings.Repeat(" ", level)
subident := strings.Repeat(" ", level-1)
switch v := v.(type) {
case nil:
return theme.Null("null")
case bool:
if v {
return theme.Boolean("true")
} else {
return theme.Boolean("false")
}
case json.Number:
return theme.Number(v.String())
case string:
return theme.String(fmt.Sprintf("%q", v))
case *dict.Dict:
keys := v.Keys
if len(keys) == 0 {
return theme.Syntax("{}")
}
output := theme.Syntax("{")
output += "\n"
for i, k := range keys {
key := theme.Key(i, len(keys))(fmt.Sprintf("%q", k))
value, _ := v.Get(k)
delim := theme.Syntax(": ")
line := ident + key + delim + PrettyPrint(value, level+1, theme)
if i < len(keys)-1 {
line += theme.Syntax(",")
}
line += "\n"
output += line
}
return output + subident + theme.Syntax("}")
case []interface{}:
slice := v
if len(slice) == 0 {
return theme.Syntax("[]")
}
output := theme.Syntax("[\n")
for i, value := range v {
line := ident + PrettyPrint(value, level+1, theme)
if i < len(slice)-1 {
line += ",\n"
} else {
line += "\n"
}
output += line
}
return output + subident + theme.Syntax("]")
default:
return "unknown type"
}
}

@ -1,51 +0,0 @@
package json
import (
"fmt"
. "github.com/antonmedv/fx/pkg/dict"
)
func Stringify(v interface{}) string {
switch v := v.(type) {
case nil:
return "null"
case bool:
if v {
return "true"
} else {
return "false"
}
case Number:
return v.String()
case string:
return fmt.Sprintf("%q", v)
case *Dict:
result := "{"
for i, key := range v.Keys {
line := fmt.Sprintf("%q", key) + ": " + Stringify(v.Values[key])
if i < len(v.Keys)-1 {
line += ","
}
result += line
}
return result + "}"
case Array:
result := "["
for i, value := range v {
line := Stringify(value)
if i < len(v)-1 {
line += ","
}
result += line
}
return result + "]"
default:
return "unknown type"
}
}

@ -1,32 +0,0 @@
package json
import (
. "github.com/antonmedv/fx/pkg/dict"
"testing"
)
func Test_stringify(t *testing.T) {
t.Run("dict", func(t *testing.T) {
arg := NewDict()
arg.Set("a", Number("1"))
arg.Set("b", Number("2"))
want := `{"a": 1,"b": 2}`
if got := Stringify(arg); got != want {
t.Errorf("stringify() = %v, want %v", got, want)
}
})
t.Run("array", func(t *testing.T) {
arg := Array{Number("1"), Number("2")}
want := `[1,2]`
if got := Stringify(arg); got != want {
t.Errorf("stringify() = %v, want %v", got, want)
}
})
t.Run("array_with_dict", func(t *testing.T) {
arg := Array{NewDict(), Array{}}
want := `[{},[]]`
if got := Stringify(arg); got != want {
t.Errorf("stringify() = %v, want %v", got, want)
}
})
}

@ -1,47 +0,0 @@
package json
import (
"fmt"
. "github.com/antonmedv/fx/pkg/dict"
)
type Iterator struct {
Object interface{}
Path, Parent string
}
func Dfs(object interface{}, f func(it Iterator)) {
sub(Iterator{Object: object}, f)
}
func sub(it Iterator, f func(it Iterator)) {
f(it)
switch it.Object.(type) {
case *Dict:
keys := it.Object.(*Dict).Keys
for _, k := range keys {
subpath := it.Path + "." + k
value, _ := it.Object.(*Dict).Get(k)
sub(Iterator{
Object: value,
Path: subpath,
Parent: it.Path,
}, f)
}
case Array:
slice := it.Object.(Array)
for i, value := range slice {
subpath := accessor(it.Path, i)
sub(Iterator{
Object: value,
Path: subpath,
Parent: it.Path,
}, f)
}
}
}
func accessor(path string, to interface{}) string {
return fmt.Sprintf("%v[%v]", path, to)
}

@ -1,6 +0,0 @@
package json
import "encoding/json"
type Number = json.Number
type Array = []interface{}

@ -1,98 +0,0 @@
package reducer
import (
_ "embed"
"encoding/json"
"fmt"
"strings"
. "github.com/antonmedv/fx/pkg/json"
. "github.com/antonmedv/fx/pkg/theme"
"github.com/dop251/goja"
)
//go:embed js.js
var templateJs string
func js(args []string, fxrc string) string {
rs := "\n"
for i, a := range args {
rs += " try {"
switch {
case flatMapRegex.MatchString(a):
code := fold(strings.Split(a, "[]"))
rs += fmt.Sprintf(
`
x = (
%v
)(x)
`, code)
case strings.HasPrefix(a, ".["):
rs += fmt.Sprintf(
`
x = function ()
{ return this%v }
.call(x)
`, a[1:])
case strings.HasPrefix(a, "."):
rs += fmt.Sprintf(
`
x = function ()
{ return this%v }
.call(x)
`, a)
default:
rs += fmt.Sprintf(
`
let f = function ()
{ return %v }
.call(x)
x = typeof f === 'function' ? f(x) : f
`, a)
}
// Generate a beautiful error message.
rs += " } catch (e) {\n"
pre, post, pointer := trace(args, i)
rs += fmt.Sprintf(
" throw `\\n ${%q} ${%q} ${%q}\\n %v\\n\\n${e.stack || e}`\n",
pre, a, post, pointer,
)
rs += " }\n"
}
return fmt.Sprintf(templateJs, fxrc, rs)
}
func CreateJS(args []string, fxrc string) (*goja.Runtime, goja.Callable, error) {
vm := goja.New()
_, err := vm.RunString(js(args, fxrc))
if err != nil {
return nil, nil, err
}
fn, ok := goja.AssertFunction(vm.Get("reduce"))
if !ok {
panic("Not a function")
}
return vm, fn, nil
}
func ReduceJS(vm *goja.Runtime, reduce goja.Callable, input interface{}, theme Theme) int {
value, err := reduce(goja.Undefined(), vm.ToValue(Stringify(input)))
if err != nil {
fmt.Println(err)
return 1
}
output := value.String()
dec := json.NewDecoder(strings.NewReader(output))
dec.UseNumber()
object, err := Parse(dec)
if err != nil {
fmt.Print(output)
return 0
}
Echo(object, theme)
return 0
}

@ -1,12 +0,0 @@
// .fxrc.js %v
function reduce(input) {
let x = JSON.parse(input)
// Reducers %v
if (typeof x === 'undefined') {
return 'null'
} else {
return JSON.stringify(x)
}
}

@ -1,83 +0,0 @@
package reducer
import (
_ "embed"
"fmt"
"os"
"os/exec"
"path"
"strings"
)
func CreateNodejs(args []string, fxrc string) *exec.Cmd {
cmd := exec.Command("node", "--input-type=module", "-e", nodejs(args, fxrc))
nodePath, exist := os.LookupEnv("NODE_PATH")
if exist {
cmd.Dir = path.Dir(nodePath)
}
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "NODE_OPTIONS=--max-old-space-size=8192")
workingDir, err := os.Getwd()
if err == nil {
cmd.Env = append(cmd.Env, "FX_CWD="+workingDir)
}
return cmd
}
//go:embed node.js
var templateNode string
func nodejs(args []string, fxrc string) string {
rs := "\n"
for i, a := range args {
rs += " try {"
switch {
case flatMapRegex.MatchString(a):
code := fold(strings.Split(a, "[]"))
rs += fmt.Sprintf(
`
x = (
%v
)(x)
`, code)
case strings.HasPrefix(a, ".["):
rs += fmt.Sprintf(
`
x = function ()
{ return this%v }
.call(x)
`, a[1:])
case strings.HasPrefix(a, "."):
rs += fmt.Sprintf(
`
x = function ()
{ return this%v }
.call(x)
`, a)
default:
rs += fmt.Sprintf(
`
let f = function ()
{ return %v }
.call(x)
x = typeof f === 'function' ? f(x) : f
`, a)
}
rs += `
x = await x
`
// Generate a beautiful error message.
rs += " } catch (e) {\n"
pre, post, pointer := trace(args, i)
rs += fmt.Sprintf(
" throw `\\n ${%q} ${%q} ${%q}\\n %v\\n\\n${e.stack || e}`\n",
pre, a, post, pointer,
)
rs += " }\n"
}
return fmt.Sprintf(templateNode, fxrc, rs)
}

@ -1,31 +0,0 @@
import os from 'node:os'
import fs from 'node:fs'
import path from 'node:path'
import {createRequire} from 'node:module'
const cwd = process.env.FX_CWD ? process.env.FX_CWD : process.cwd()
const require = createRequire(cwd)
// .fxrc.js %v
void async function () {
process.chdir(cwd)
let buffer = ''
process.stdin.setEncoding('utf8')
for await (let chunk of process.stdin) {
buffer += chunk
}
let x = JSON.parse(buffer)
// Reducers %v
if (typeof x === 'undefined') {
process.stderr.write('undefined')
} else {
process.stdout.write(JSON.stringify(x))
}
}().catch(err => {
console.error(err)
process.exitCode = 1
})

@ -1,38 +0,0 @@
package reducer
import (
_ "embed"
"fmt"
"os/exec"
)
func CreatePython(bin string, args []string) *exec.Cmd {
cmd := exec.Command(bin, "-c", python(args))
return cmd
}
//go:embed python.py
var templatePython string
func python(args []string) string {
rs := "\n"
for i, a := range args {
rs += fmt.Sprintf(
`try:
f = (lambda x: (%v))(x)
x = f(x) if callable(f) else f
`, a)
// Generate a beautiful error message.
rs += "except Exception as e:\n"
pre, post, pointer := trace(args, i)
rs += fmt.Sprintf(
` sys.stderr.write('\n {} {} {}\n %v\n\n{}\n'.format(%q, %q, %q, e))
sys.exit(1)`,
pointer,
pre, a, post,
)
rs += "\n"
}
return fmt.Sprintf(templatePython, rs)
}

@ -1,9 +0,0 @@
import json, sys, os
x = json.load(sys.stdin)
# Reducers %v
try:
print(json.dumps(x))
except:
print(json.dumps(list(x)))

@ -1,76 +0,0 @@
package reducer
import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"os/exec"
"strings"
. "github.com/antonmedv/fx/pkg/json"
. "github.com/antonmedv/fx/pkg/theme"
)
func GenerateCode(lang string, args []string, fxrc string) string {
switch lang {
case "js":
return js(args, fxrc)
case "node":
return nodejs(args, fxrc)
case "python", "python3":
return python(args)
case "ruby":
return ruby(args)
default:
panic("unknown lang")
}
}
func Reduce(input interface{}, lang string, args []string, theme Theme, fxrc string) int {
path, ok := SplitSimplePath(args)
if ok {
output := GetBySimplePath(input, path)
Echo(output, theme)
return 0
}
var cmd *exec.Cmd
switch lang {
case "node":
cmd = CreateNodejs(args, fxrc)
case "python", "python3":
cmd = CreatePython(lang, args)
case "ruby":
cmd = CreateRuby(args)
default:
panic("unknown lang")
}
// TODO: Reimplement stringify with io.Reader.
cmd.Stdin = strings.NewReader(Stringify(input))
output, err := cmd.CombinedOutput()
if err != nil {
exitCode := 1
status, ok := err.(*exec.ExitError)
if ok {
exitCode = status.ExitCode()
} else {
fmt.Println(err.Error())
}
fmt.Print(string(output))
return exitCode
}
dec := json.NewDecoder(bytes.NewReader(output))
dec.UseNumber()
object, err := Parse(dec)
if err != nil {
fmt.Print(string(output))
return 0
}
Echo(object, theme)
if dec.InputOffset() < int64(len(output)) {
fmt.Print(string(output[dec.InputOffset():]))
}
return 0
}

@ -1,37 +0,0 @@
package reducer
import (
_ "embed"
"fmt"
"os/exec"
)
func CreateRuby(args []string) *exec.Cmd {
cmd := exec.Command("ruby", "-e", ruby(args))
return cmd
}
//go:embed ruby.rb
var templateRuby string
func ruby(args []string) string {
rs := "\n"
for i, a := range args {
rs += fmt.Sprintf(
`begin
x = lambda {|x| %v }.call(x)
`, a)
// Generate a beautiful error message.
rs += "rescue Exception => e\n"
pre, post, pointer := trace(args, i)
rs += fmt.Sprintf(
` STDERR.puts "\n #{%q} #{%q} #{%q}\n %v\n\n#{e}\n"
exit(1)
`,
pre, a, post,
pointer,
)
rs += "end\n"
}
return fmt.Sprintf(templateRuby, rs)
}

@ -1,6 +0,0 @@
require 'json'
x = JSON.parse(STDIN.read)
# Reducers %v
puts JSON.generate(x)

@ -1,215 +0,0 @@
package reducer
import (
"strconv"
"unicode"
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
)
type state int
const (
start state = iota
unknown
propOrIndex
prop
index
indexEnd
number
doubleQuote
doubleQuoteEscape
singleQuote
singleQuoteEscape
)
func SplitSimplePath(args []string) ([]interface{}, bool) {
path := make([]interface{}, 0)
for _, arg := range args {
s := ""
state := start
for _, ch := range arg {
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 == '$'
}
func GetBySimplePath(object interface{}, path []interface{}) interface{} {
for _, get := range path {
switch get := get.(type) {
case string:
switch o := object.(type) {
case *Dict:
object = o.Values[get]
case string:
if get == "length" {
object = Number(strconv.Itoa(len([]rune(o))))
} else {
object = nil
}
case Array:
if get == "length" {
object = Number(strconv.Itoa(len(o)))
} else {
object = nil
}
default:
object = nil
}
case int:
switch o := object.(type) {
case Array:
object = o[get]
default:
object = nil
}
}
}
return object
}

@ -1,137 +0,0 @@
package reducer
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func Test_splitPath(t *testing.T) {
tests := []struct {
args []string
want []interface{}
}{
{
args: []string{},
want: []interface{}{},
},
{
args: []string{"."},
want: []interface{}{},
},
{
args: []string{"x"},
want: []interface{}{},
},
{
args: []string{".foo"},
want: []interface{}{"foo"},
},
{
args: []string{"x.foo"},
want: []interface{}{"foo"},
},
{
args: []string{"x[42]"},
want: []interface{}{42},
},
{
args: []string{".[42]"},
want: []interface{}{42},
},
{
args: []string{".42"},
want: []interface{}{"42"},
},
{
args: []string{".физ"},
want: []interface{}{"физ"},
},
{
args: []string{".foo.bar"},
want: []interface{}{"foo", "bar"},
},
{
args: []string{".foo", ".bar"},
want: []interface{}{"foo", "bar"},
},
{
args: []string{".foo[42]"},
want: []interface{}{"foo", 42},
},
{
args: []string{".foo[42].bar"},
want: []interface{}{"foo", 42, "bar"},
},
{
args: []string{".foo[1][2]"},
want: []interface{}{"foo", 1, 2},
},
{
args: []string{".foo[\"bar\"]"},
want: []interface{}{"foo", "bar"},
},
{
args: []string{".foo[\"bar\\\"\"]"},
want: []interface{}{"foo", "bar\""},
},
{
args: []string{".foo['bar']['baz\\'']"},
want: []interface{}{"foo", "bar", "baz\\'"},
},
}
for _, tt := range tests {
t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
path, ok := SplitSimplePath(tt.args)
require.Equal(t, tt.want, path)
require.True(t, ok)
})
}
}
func Test_splitPath_negative(t *testing.T) {
tests := []struct {
args []string
}{
{
args: []string{"./"},
},
{
args: []string{"x/"},
},
{
args: []string{"1+1"},
},
{
args: []string{"x[42"},
},
{
args: []string{".i % 2"},
},
{
args: []string{"x[for x]"},
},
{
args: []string{"x['y'."},
},
{
args: []string{"x[0?"},
},
{
args: []string{"x[\"\\u"},
},
{
args: []string{"x['\\n"},
},
{
args: []string{"x[9999999999999999999999999999999999999]"},
},
}
for _, tt := range tests {
t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
path, ok := SplitSimplePath(tt.args)
require.False(t, ok, path)
})
}
}

@ -1,51 +0,0 @@
package reducer
import (
"fmt"
"regexp"
"strings"
. "github.com/antonmedv/fx/pkg/json"
. "github.com/antonmedv/fx/pkg/theme"
)
func Echo(object interface{}, theme Theme) {
if s, ok := object.(string); ok {
fmt.Println(s)
} else {
fmt.Println(PrettyPrint(object, 1, theme))
}
}
func trace(args []string, i int) (pre, post, pointer string) {
pre = strings.Join(args[:i], " ")
if len(pre) > 20 {
pre = "..." + pre[len(pre)-20:]
}
post = strings.Join(args[i+1:], " ")
if len(post) > 20 {
post = post[:20] + "..."
}
pointer = fmt.Sprintf(
"%v %v %v",
strings.Repeat(" ", len(pre)),
strings.Repeat("^", len(args[i])),
strings.Repeat(" ", len(post)),
)
return
}
var flatMapRegex = regexp.MustCompile("^(\\.\\w*)+\\[]")
func fold(s []string) string {
if len(s) == 1 {
return "x => x" + s[0]
}
obj := s[0]
if obj == "." {
obj = "x"
} else {
obj = "x" + obj
}
return fmt.Sprintf("x => Object.values(%v).flatMap(%v)", obj, fold(s[1:]))
}

@ -1,185 +0,0 @@
package theme
import (
"github.com/charmbracelet/lipgloss"
"github.com/mazznoer/colorgrad"
"strings"
)
type Theme struct {
Cursor Color
Syntax Color
Preview Color
StatusBar Color
Search Color
Key func(i, len int) Color
String Color
Null Color
Boolean Color
Number Color
}
type Color func(s ...string) string
var (
defaultCursor = lipgloss.NewStyle().Reverse(true).Render
defaultPreview = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("8")).Render
defaultStatusBar = lipgloss.NewStyle().Background(lipgloss.Color("7")).Foreground(lipgloss.Color("0")).Render
defaultSearch = lipgloss.NewStyle().Background(lipgloss.Color("11")).Foreground(lipgloss.Color("16")).Render
defaultNull = fg("8")
)
var Themes = map[string]Theme{
"0": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: noColor,
StatusBar: noColor,
Search: defaultSearch,
Key: func(_, _ int) Color { return noColor },
String: noColor,
Null: noColor,
Boolean: noColor,
Number: noColor,
},
"1": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return boldFg("4") },
String: boldFg("2"),
Null: defaultNull,
Boolean: boldFg("3"),
Number: boldFg("6"),
},
"2": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return fg("#00F5D4") },
String: fg("#00BBF9"),
Null: defaultNull,
Boolean: fg("#F15BB5"),
Number: fg("#9B5DE5"),
},
"3": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return fg("#faf0ca") },
String: fg("#f4d35e"),
Null: defaultNull,
Boolean: fg("#ee964b"),
Number: fg("#ee964b"),
},
"4": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return fg("#4D96FF") },
String: fg("#6BCB77"),
Null: defaultNull,
Boolean: fg("#FF6B6B"),
Number: fg("#FFD93D"),
},
"5": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return boldFg("42") },
String: boldFg("213"),
Null: defaultNull,
Boolean: boldFg("201"),
Number: boldFg("201"),
},
"6": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return gradient("rgb(125,110,221)", "rgb(90%,45%,97%)", "hsl(229,79%,85%)") },
String: fg("195"),
Null: defaultNull,
Boolean: fg("195"),
Number: fg("195"),
},
"7": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return gradient("rgb(123,216,96)", "rgb(255,255,255)") },
String: noColor,
Null: defaultNull,
Boolean: noColor,
Number: noColor,
},
"8": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: gradientKeys("#ff0000", "#ff8700", "#ffd300", "#deff0a", "#a1ff0a", "#0aff99", "#0aefff", "#147df5", "#580aff", "#be0aff"),
String: noColor,
Null: defaultNull,
Boolean: noColor,
Number: noColor,
},
"9": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: gradientKeys("rgb(34,126,34)", "rgb(168,251,60)"),
String: gradient("rgb(34,126,34)", "rgb(168,251,60)"),
Null: defaultNull,
Boolean: noColor,
Number: noColor,
},
}
func noColor(s ...string) string {
return s[0]
}
func fg(color string) Color {
return lipgloss.NewStyle().Foreground(lipgloss.Color(color)).Render
}
func boldFg(color string) Color {
return lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(color)).Render
}
func gradient(colors ...string) Color {
grad, _ := colorgrad.NewGradient().HtmlColors(colors...).Build()
return func(s ...string) string {
runes := []rune(s[0])
colors := grad.ColorfulColors(uint(len(runes)))
var out strings.Builder
for i, r := range runes {
style := lipgloss.NewStyle().Foreground(lipgloss.Color(colors[i].Hex()))
out.WriteString(style.Render(string(r)))
}
return out.String()
}
}
func gradientKeys(colors ...string) func(i, len int) Color {
grad, _ := colorgrad.NewGradient().HtmlColors(colors...).Build()
return func(i, len int) Color {
return lipgloss.NewStyle().Foreground(lipgloss.Color(grad.At(float64(i) / float64(len)).Hex())).Render
}
}

@ -1,296 +0,0 @@
package main
import (
"fmt"
"strings"
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
"github.com/antonmedv/fx/pkg/theme"
)
func (m *model) connect(path string, lineNumber int) {
if _, exist := m.pathToLineNumber[path]; exist {
return
}
m.paths = append(m.paths, path)
m.pathToIndex[path] = len(m.paths) - 1
m.pathToLineNumber[path] = lineNumber
m.lineNumberToPath[lineNumber] = path
}
func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path string, selectableValues bool) []string {
m.connect(path, lineNumber)
ident := strings.Repeat(" ", level)
subident := strings.Repeat(" ", level-1)
highlight := m.highlightIndex[path]
var searchValue []*foundRange
if highlight != nil {
searchValue = highlight.value
}
switch v.(type) {
case nil:
return []string{merge(m.explode("null", searchValue, m.theme.Null, path, selectableValues))}
case bool:
if v.(bool) {
return []string{merge(m.explode("true", searchValue, m.theme.Boolean, path, selectableValues))}
} else {
return []string{merge(m.explode("false", searchValue, m.theme.Boolean, path, selectableValues))}
}
case Number:
return []string{merge(m.explode(v.(Number).String(), searchValue, m.theme.Number, path, selectableValues))}
case string:
line := fmt.Sprintf("%q", v)
chunks := m.explode(line, searchValue, m.theme.String, path, selectableValues)
if m.wrap && keyEndPos+width(line) > m.width {
return wrapLines(chunks, keyEndPos, m.width, subident)
}
// No wrap
return []string{merge(chunks)}
case *Dict:
if !m.expandedPaths[path] {
return []string{m.preview(v, path, selectableValues)}
}
output := []string{m.printOpenBracket("{", highlight, path, selectableValues)}
lineNumber++ // bracket is on separate line
keys := v.(*Dict).Keys
for i, k := range keys {
subpath := path + "." + k
highlight := m.highlightIndex[subpath]
var keyRanges, delimRanges []*foundRange
if highlight != nil {
keyRanges = highlight.key
delimRanges = highlight.delim
}
m.connect(subpath, lineNumber)
key := fmt.Sprintf("%q", k)
keyTheme := m.theme.Key(i, len(keys))
key = merge(m.explode(key, keyRanges, keyTheme, subpath, true))
value, _ := v.(*Dict).Get(k)
delim := merge(m.explode(": ", delimRanges, m.theme.Syntax, subpath, false))
keyEndPos := width(ident) + width(key) + width(delim)
lines := m.print(value, level+1, lineNumber, keyEndPos, subpath, false)
lines[0] = ident + key + delim + lines[0]
if i < len(keys)-1 {
lines[len(lines)-1] += m.printComma(",", highlight)
}
output = append(output, lines...)
lineNumber += len(lines)
}
output = append(output, subident+m.printCloseBracket("}", highlight, path, false))
return output
case Array:
if !m.expandedPaths[path] {
return []string{m.preview(v, path, selectableValues)}
}
output := []string{m.printOpenBracket("[", highlight, path, selectableValues)}
lineNumber++ // bracket is on separate line
slice := v.(Array)
for i, value := range slice {
subpath := fmt.Sprintf("%v[%v]", path, i)
s := m.highlightIndex[subpath]
m.connect(subpath, lineNumber)
lines := m.print(value, level+1, lineNumber, width(ident), subpath, true)
lines[0] = ident + lines[0]
if i < len(slice)-1 {
lines[len(lines)-1] += m.printComma(",", s)
}
lineNumber += len(lines)
output = append(output, lines...)
}
output = append(output, subident+m.printCloseBracket("]", highlight, path, false))
return output
default:
return []string{"unknown type"}
}
}
func (m *model) preview(v interface{}, path string, selectableValues bool) string {
searchResult := m.highlightIndex[path]
previewStyle := m.theme.Preview
if selectableValues && m.cursorPath() == path {
previewStyle = m.theme.Cursor
}
printValue := func(v interface{}) string {
switch v := v.(type) {
case nil, bool, Number:
return previewStyle(fmt.Sprintf("%v", v))
case string:
return previewStyle(fmt.Sprintf("%q", v))
case *Dict:
if m.showSize {
return previewStyle(toLowerNumber(fmt.Sprintf("{\u2026%v\u2026}", len(v.Keys))))
} else {
return previewStyle("{\u2026}")
}
case Array:
if m.showSize {
return previewStyle(toLowerNumber(fmt.Sprintf("[\u2026%v\u2026]", len(v))))
} else {
return previewStyle("[\u2026]")
}
}
return "..."
}
switch v := v.(type) {
case *Dict:
output := m.printOpenBracket("{", searchResult, path, selectableValues)
keys := v.Keys
for _, k := range keys {
key := fmt.Sprintf("%q", k)
output += previewStyle(key + ": ")
value, _ := v.Get(k)
output += printValue(value)
break
}
if len(keys) > 1 {
if m.showSize {
output += previewStyle(toLowerNumber(fmt.Sprintf(", \u2026%v\u2026", len(v.Keys)-1)))
} else {
output += previewStyle(", \u2026")
}
}
output += m.printCloseBracket("}", searchResult, path, selectableValues)
return output
case Array:
output := m.printOpenBracket("[", searchResult, path, selectableValues)
for _, value := range v {
output += printValue(value)
break
}
if len(v) > 1 {
if m.showSize {
output += previewStyle(toLowerNumber(fmt.Sprintf(", \u2026%v\u2026", len(v)-1)))
} else {
output += previewStyle(", \u2026")
}
}
output += m.printCloseBracket("]", searchResult, path, selectableValues)
return output
}
return "?"
}
func wrapLines(chunks []withStyle, keyEndPos, mWidth int, subident string) []string {
wrappedLines := make([]string, 0)
currentLine := ""
ident := "" // First line stays on the same line with a "key",
pos := keyEndPos // so no ident is needed. Start counting from the "key" offset.
for _, chunk := range chunks {
buffer := ""
for _, ch := range chunk.value {
buffer += string(ch)
if pos == mWidth-1 {
wrappedLines = append(wrappedLines, ident+currentLine+chunk.Render(buffer))
currentLine = ""
buffer = ""
pos = width(subident) // Start counting from ident.
ident = subident // After first line, add ident to all.
} else {
pos++
}
}
currentLine += chunk.Render(buffer)
}
if width(currentLine) > 0 {
wrappedLines = append(wrappedLines, subident+currentLine)
}
return wrappedLines
}
func (w withStyle) Render(s string) string {
return w.style(s)
}
func (m *model) printOpenBracket(line string, s *rangeGroup, path string, selectableValues bool) string {
if selectableValues && m.cursorPath() == path {
return m.theme.Cursor(line)
}
if s != nil && s.openBracket != nil {
if s.openBracket.parent.index == m.searchResultsCursor {
return m.theme.Cursor(line)
} else {
return m.theme.Search(line)
}
} else {
return m.theme.Syntax(line)
}
}
func (m *model) printCloseBracket(line string, s *rangeGroup, path string, selectableValues bool) string {
if selectableValues && m.cursorPath() == path {
return m.theme.Cursor(line)
}
if s != nil && s.closeBracket != nil {
if s.closeBracket.parent.index == m.searchResultsCursor {
return m.theme.Cursor(line)
} else {
return m.theme.Search(line)
}
} else {
return m.theme.Syntax(line)
}
}
func (m *model) printComma(line string, s *rangeGroup) string {
if s != nil && s.comma != nil {
if s.comma.parent.index == m.searchResultsCursor {
return m.theme.Cursor(line)
} else {
return m.theme.Search(line)
}
} else {
return m.theme.Syntax(line)
}
}
type withStyle struct {
value string
style theme.Color
}
func (m *model) explode(line string, highlightRanges []*foundRange, defaultStyle theme.Color, path string, selectable bool) []withStyle {
if selectable && m.cursorPath() == path && m.showCursor {
return []withStyle{{line, m.theme.Cursor}}
}
out := make([]withStyle, 0, 1)
pos := 0
for _, r := range highlightRanges {
style := m.theme.Search
if r.parent.index == m.searchResultsCursor {
style = m.theme.Cursor
}
out = append(out, withStyle{
value: line[pos:r.start],
style: defaultStyle,
})
out = append(out, withStyle{
value: line[r.start:r.end],
style: style,
})
pos = r.end
}
out = append(out, withStyle{
value: line[pos:],
style: defaultStyle,
})
return out
}
func merge(chunks []withStyle) string {
out := ""
for _, chunk := range chunks {
out += chunk.Render(chunk.value)
}
return out
}

@ -1,249 +0,0 @@
package main
import (
"fmt"
"regexp"
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
)
type searchResult struct {
path string
index int
ranges []*foundRange
}
type rangeKind int
const (
keyRange rangeKind = 1 + iota
valueRange
delimRange
openBracketRange
closeBracketRange
commaRange
)
type foundRange struct {
parent *searchResult
// Range needs separate path, as for one searchResult's path
// there can be multiple ranges for different paths (within parent path).
path string
start, end int
kind rangeKind
}
type rangeGroup struct {
key []*foundRange
value []*foundRange
delim []*foundRange
openBracket *foundRange
closeBracket *foundRange
comma *foundRange
}
func (m *model) clearSearchResults() {
m.searchRegexCompileError = ""
m.searchResults = nil
m.highlightIndex = nil
}
func (m *model) doSearch(s string) {
m.clearSearchResults()
re, err := regexp.Compile("(?i)" + s)
if err != nil {
m.searchRegexCompileError = err.Error()
m.searchInput.Blur()
return
}
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
m.indexSearchResults()
m.searchInput.Blur()
m.showSearchResults = true
m.jumpToSearchResult(0)
}
func (m *model) remapSearchResult(object interface{}, path string, pos int, indexes [][]int, id int, current *searchResult) (int, int, *searchResult) {
switch object.(type) {
case nil:
s := "null"
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
return pos + len(s), id, current
case bool:
var s string
if object.(bool) {
s = "true"
} else {
s = "false"
}
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
return pos + len(s), id, current
case Number:
s := object.(Number).String()
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
return pos + len(s), id, current
case string:
// TODO: Wrap the string, save a line number according to current wrap or no wrap mode.
s := fmt.Sprintf("%q", object)
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
return pos + len(s), id, current
case *Dict:
id, current = m.findRanges(openBracketRange, "{", path, pos, indexes, id, current)
pos++ // {
for i, k := range object.(*Dict).Keys {
subpath := path + "." + k
key := fmt.Sprintf("%q", k)
id, current = m.findRanges(keyRange, key, subpath, pos, indexes, id, current)
pos += len(key)
delim := ": "
id, current = m.findRanges(delimRange, delim, subpath, pos, indexes, id, current)
pos += len(delim)
pos, id, current = m.remapSearchResult(object.(*Dict).Values[k], subpath, pos, indexes, id, current)
if i < len(object.(*Dict).Keys)-1 {
comma := ","
id, current = m.findRanges(commaRange, comma, subpath, pos, indexes, id, current)
pos += len(comma)
}
}
id, current = m.findRanges(closeBracketRange, "}", path, pos, indexes, id, current)
pos++ // }
return pos, id, current
case Array:
id, current = m.findRanges(openBracketRange, "[", path, pos, indexes, id, current)
pos++ // [
for i, v := range object.(Array) {
subpath := fmt.Sprintf("%v[%v]", path, i)
pos, id, current = m.remapSearchResult(v, subpath, pos, indexes, id, current)
if i < len(object.(Array))-1 {
comma := ","
id, current = m.findRanges(commaRange, comma, subpath, pos, indexes, id, current)
pos += len(comma)
}
}
id, current = m.findRanges(closeBracketRange, "]", path, pos, indexes, id, current)
pos++ // ]
return pos, id, current
default:
panic("unexpected object type")
}
}
func (m *model) findRanges(kind rangeKind, s string, path string, pos int, indexes [][]int, id int, current *searchResult) (int, *searchResult) {
for ; id < len(indexes); id++ {
start, end := indexes[id][0]-pos, indexes[id][1]-pos
if end <= 0 {
current = nil
continue
}
if start < len(s) {
if current == nil {
current = &searchResult{
path: path,
index: len(m.searchResults),
}
m.searchResults = append(m.searchResults, current)
}
found := &foundRange{
parent: current,
path: path,
start: max(start, 0),
end: min(end, len(s)),
kind: kind,
}
current.ranges = append(current.ranges, found)
if end < len(s) {
current = nil
} else {
break
}
} else {
break
}
}
return id, current
}
func (m *model) indexSearchResults() {
m.highlightIndex = map[string]*rangeGroup{}
for _, s := range m.searchResults {
for _, r := range s.ranges {
highlight, exist := m.highlightIndex[r.path]
if !exist {
highlight = &rangeGroup{}
m.highlightIndex[r.path] = highlight
}
switch r.kind {
case keyRange:
highlight.key = append(highlight.key, r)
case valueRange:
highlight.value = append(highlight.value, r)
case delimRange:
highlight.delim = append(highlight.delim, r)
case openBracketRange:
highlight.openBracket = r
case closeBracketRange:
highlight.closeBracket = r
case commaRange:
highlight.comma = r
}
}
}
}
func (m *model) jumpToSearchResult(at int) {
if len(m.searchResults) == 0 {
return
}
m.showCursor = false
m.searchResultsCursor = at % len(m.searchResults)
desiredPath := m.searchResults[m.searchResultsCursor].path
_, ok := m.pathToLineNumber[desiredPath]
if ok {
m.cursor = m.pathToIndex[desiredPath]
m.scrollDownToCursor()
m.render()
} else {
m.expandToPath(desiredPath)
m.render()
m.jumpToSearchResult(at)
}
}
func (m *model) expandToPath(path string) {
m.expandedPaths[path] = true
if path != "" {
m.expandToPath(m.parents[path])
}
}
func (m *model) nextSearchResult() {
if len(m.searchResults) > 0 {
m.jumpToSearchResult((m.searchResultsCursor + 1) % len(m.searchResults))
}
}
func (m *model) prevSearchResult() {
i := m.searchResultsCursor - 1
if i < 0 {
i = len(m.searchResults) - 1
}
m.jumpToSearchResult(i)
}
func (m *model) resultsCursorPath() string {
if len(m.searchResults) == 0 {
return "?"
}
return m.searchResults[m.searchResultsCursor].path
}

@ -1,190 +0,0 @@
package main
import (
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
"github.com/stretchr/testify/require"
"regexp"
"testing"
)
func Test_search_values(t *testing.T) {
tests := []struct {
name string
object interface{}
want *foundRange
}{
{name: "null", object: nil},
{name: "true", object: true},
{name: "false", object: false},
{name: "Number", object: Number("42")},
{name: "string", object: "Hello, World!"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &model{
json: tt.object,
}
re, _ := regexp.Compile(".+")
str := Stringify(m.json)
indexes := re.FindAllStringIndex(str, -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s := &searchResult{path: ""}
s.ranges = append(s.ranges, &foundRange{
parent: s,
path: "",
start: 0,
end: len(str),
kind: valueRange,
})
require.Equal(t, []*searchResult{s}, m.searchResults)
})
}
}
func Test_search_array(t *testing.T) {
msg := `
["first","second"]
^^^^^ ^^^^^^
`
m := &model{
json: Array{"first", "second"},
}
re, _ := regexp.Compile("\\w+")
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s1 := &searchResult{path: "[0]"}
s1.ranges = append(s1.ranges,
&foundRange{
parent: s1,
path: "[0]",
start: 1,
end: 6,
kind: valueRange,
},
)
s2 := &searchResult{path: "[1]", index: 1}
s2.ranges = append(s2.ranges,
&foundRange{
parent: s2,
path: "[1]",
start: 1,
end: 7,
kind: valueRange,
},
)
require.Equal(t, []*searchResult{s1, s2}, m.searchResults, msg)
}
func Test_search_between_array(t *testing.T) {
msg := `
["first","second"]
^^^^^^^^^^^^^^
`
m := &model{
json: Array{"first", "second"},
}
re, _ := regexp.Compile("\\w.+\\w")
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s := &searchResult{path: "[0]"}
s.ranges = append(s.ranges,
&foundRange{
parent: s,
path: "[0]",
start: 1,
end: 7,
kind: valueRange,
},
&foundRange{
parent: s,
path: "[0]",
start: 0,
end: 1,
kind: commaRange,
},
&foundRange{
parent: s,
path: "[1]",
start: 0,
end: 7,
kind: valueRange,
},
)
require.Equal(t, []*searchResult{s}, m.searchResults, msg)
}
func Test_search_dict(t *testing.T) {
msg := `
{"key": "hello world"}
^^^^^ ^^^^^^^^^^^^^
`
d := NewDict()
d.Set("key", "hello world")
m := &model{
json: d,
}
re, _ := regexp.Compile("\"[\\w\\s]+\"")
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s1 := &searchResult{path: ".key"}
s1.ranges = append(s1.ranges,
&foundRange{
parent: s1,
path: ".key",
start: 0,
end: 5,
kind: keyRange,
},
)
s2 := &searchResult{path: ".key", index: 1}
s2.ranges = append(s2.ranges,
&foundRange{
parent: s2,
path: ".key",
start: 0,
end: 13,
kind: valueRange,
},
)
require.Equal(t, []*searchResult{s1, s2}, m.searchResults, msg)
}
func Test_search_dict_with_array(t *testing.T) {
msg := `
{"first": [1,2],"second": []}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`
d := NewDict()
d.Set("first", Array{Number("1"), Number("2")})
d.Set("second", Array{})
m := &model{
json: d,
}
re, _ := regexp.Compile(".+")
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s := &searchResult{path: ""}
s.ranges = append(s.ranges,
/* { */ &foundRange{parent: s, path: "", start: 0, end: 1, kind: openBracketRange},
/* "first" */ &foundRange{parent: s, path: ".first", start: 0, end: 7, kind: keyRange},
/* : */ &foundRange{parent: s, path: ".first", start: 0, end: 2, kind: delimRange},
/* [ */ &foundRange{parent: s, path: ".first", start: 0, end: 1, kind: openBracketRange},
/* 1 */ &foundRange{parent: s, path: ".first[0]", start: 0, end: 1, kind: valueRange},
/* , */ &foundRange{parent: s, path: ".first[0]", start: 0, end: 1, kind: commaRange},
/* 2 */ &foundRange{parent: s, path: ".first[1]", start: 0, end: 1, kind: valueRange},
/* ] */ &foundRange{parent: s, path: ".first", start: 0, end: 1, kind: closeBracketRange},
/* , */ &foundRange{parent: s, path: ".first", start: 0, end: 1, kind: commaRange},
/* "second" */ &foundRange{parent: s, path: ".second", start: 0, end: 8, kind: keyRange},
/* : */ &foundRange{parent: s, path: ".second", start: 0, end: 2, kind: delimRange},
/* [ */ &foundRange{parent: s, path: ".second", start: 0, end: 1, kind: openBracketRange},
/* ] */ &foundRange{parent: s, path: ".second", start: 0, end: 1, kind: closeBracketRange},
/* } */ &foundRange{parent: s, path: "", start: 0, end: 1, kind: closeBracketRange},
)
require.Equal(t, []*searchResult{s}, m.searchResults, msg)
}

@ -1,42 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io"
. "github.com/antonmedv/fx/pkg/json"
. "github.com/antonmedv/fx/pkg/reducer"
. "github.com/antonmedv/fx/pkg/theme"
"github.com/dop251/goja"
)
func stream(dec *json.Decoder, object interface{}, lang string, args []string, theme Theme, fxrc string) int {
var vm *goja.Runtime
var fn goja.Callable
var err error
if lang == "js" {
vm, fn, err = CreateJS(args, fxrc)
if err != nil {
fmt.Println(err)
return 1
}
}
for {
if object != nil {
if lang == "js" {
ReduceJS(vm, fn, object, theme)
} else {
Reduce(object, lang, args, theme, fxrc)
}
}
object, err = Parse(dec)
if err == io.EOF {
return 0
}
if err != nil {
fmt.Println("JSON Parse Error:", err.Error())
return 1
}
}
}

@ -1,50 +0,0 @@
package main
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
)
func clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v))
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func width(s string) int {
return lipgloss.Width(s)
}
func accessor(path string, to interface{}) string {
return fmt.Sprintf("%v[%v]", path, to)
}
func toLowerNumber(s string) string {
var out strings.Builder
for _, r := range s {
switch {
case '0' <= r && r <= '9':
out.WriteRune('\u2080' + (r - '\u0030'))
default:
out.WriteRune(r)
}
}
return out.String()
}

@ -1,26 +0,0 @@
package main
import "testing"
func Test_clamp(t *testing.T) {
got := clamp(1, 2, 3)
if got != 2 {
t.Errorf("clamp() = %v, want 2", got)
}
}
func Test_max(t *testing.T) {
got := max(1, 2)
if got != 2 {
t.Errorf("max() = %v, want 2", got)
}
}
func Test_min(t *testing.T) {
got := min(1, 2)
if got != 1 {
t.Errorf("min() = %v, want 1", got)
}
}

@ -1,3 +0,0 @@
package main
const version = "24.1.0"

@ -1,128 +0,0 @@
package main
import (
"math"
)
func (m *model) AtTop() bool {
return m.offset <= 0
}
func (m *model) AtBottom() bool {
return m.offset >= m.maxYOffset()
}
func (m *model) PastBottom() bool {
return m.offset > m.maxYOffset()
}
func (m *model) ScrollPercent() float64 {
if m.height >= len(m.lines) {
return 1.0
}
y := float64(m.offset)
h := float64(m.height)
t := float64(len(m.lines) - 1)
v := y / (t - h)
return math.Max(0.0, math.Min(1.0, v))
}
func (m *model) maxYOffset() int {
return max(0, len(m.lines)-m.height)
}
func (m *model) visibleLines() (lines []string) {
if len(m.lines) > 0 {
top := max(0, m.offset)
bottom := clamp(m.offset+m.height, top, len(m.lines))
lines = m.lines[top:bottom]
}
return lines
}
func (m *model) SetOffset(n int) {
m.offset = clamp(n, 0, m.maxYOffset())
}
func (m *model) ViewDown() {
if m.AtBottom() {
return
}
m.SetOffset(m.offset + m.height)
}
func (m *model) ViewUp() {
if m.AtTop() {
return
}
m.SetOffset(m.offset - m.height)
}
func (m *model) HalfViewDown() {
if m.AtBottom() {
return
}
m.SetOffset(m.offset + m.height/2)
}
func (m *model) HalfViewUp() {
if m.AtTop() {
return
}
m.SetOffset(m.offset - m.height/2)
}
func (m *model) LineDown(n int) {
if m.AtBottom() || n == 0 {
return
}
// Make sure the Number of lines by which we're going to scroll isn't
// greater than the Number of lines we actually have left before we reach
// the bottom.
m.SetOffset(m.offset + n)
}
func (m *model) LineUp(n int) {
if m.AtTop() || n == 0 {
return
}
// Make sure the Number of lines by which we're going to scroll isn't
// greater than the Number of lines we are from the top.
m.SetOffset(m.offset - n)
}
func (m *model) GotoTop() {
if m.AtTop() {
return
}
m.SetOffset(0)
}
func (m *model) GotoBottom() {
m.SetOffset(m.maxYOffset())
}
func (m *model) scrollDownToCursor() {
at := m.cursorLineNumber()
if m.offset <= at { // cursor is lower
m.LineDown(max(0, at-(m.offset+m.height-1))) // minus one is due to cursorLineNumber() starts from 0
} else {
m.SetOffset(at)
}
}
func (m *model) scrollUpToCursor() {
at := m.cursorLineNumber()
if at < m.offset+m.height { // cursor is above
m.LineUp(max(0, m.offset-at))
} else {
m.SetOffset(at)
}
}
Loading…
Cancel
Save