fx/main.go

1187 lines
24 KiB
Go
Raw Normal View History

2023-09-07 23:53:51 +03:00
package main
import (
2023-09-15 22:55:21 +03:00
"encoding/json"
2023-09-12 17:20:17 +03:00
"errors"
2024-03-14 01:16:23 +03:00
"flag"
2023-09-07 23:53:51 +03:00
"fmt"
"io"
2023-09-12 14:29:58 +03:00
"io/fs"
2024-03-10 22:48:46 +03:00
"math"
2023-09-07 23:53:51 +03:00
"os"
2023-09-12 14:29:58 +03:00
"path"
2023-09-14 18:25:27 +03:00
"regexp"
2023-09-07 23:53:51 +03:00
"runtime/pprof"
2023-09-11 19:27:54 +03:00
"strconv"
2023-09-15 11:39:10 +03:00
"strings"
2023-09-08 10:56:12 +03:00
2023-09-15 11:39:10 +03:00
"github.com/antonmedv/clipboard"
2023-09-08 10:56:12 +03:00
"github.com/charmbracelet/bubbles/key"
2023-09-12 14:06:48 +03:00
"github.com/charmbracelet/bubbles/textinput"
2024-03-10 16:23:07 +03:00
"github.com/charmbracelet/bubbles/viewport"
2023-09-08 10:56:12 +03:00
tea "github.com/charmbracelet/bubbletea"
2023-09-12 14:06:48 +03:00
"github.com/charmbracelet/lipgloss"
"github.com/goccy/go-yaml"
2023-09-12 14:29:58 +03:00
"github.com/mattn/go-isatty"
2023-09-19 10:57:51 +03:00
"github.com/sahilm/fuzzy"
2023-09-12 14:06:48 +03:00
2024-03-14 01:16:23 +03:00
"github.com/antonmedv/fx/internal/complete"
2024-03-21 22:48:09 +03:00
. "github.com/antonmedv/fx/internal/jsonx"
"github.com/antonmedv/fx/internal/theme"
2023-09-13 16:52:31 +03:00
jsonpath "github.com/antonmedv/fx/path"
2023-09-07 23:53:51 +03:00
)
2023-09-11 19:37:02 +03:00
var (
2024-03-14 01:16:23 +03:00
flagYaml bool
flagComp bool
2023-09-11 19:37:02 +03:00
)
2023-09-07 23:53:51 +03:00
func main() {
2023-09-08 12:14:48 +03:00
if _, ok := os.LookupEnv("FX_PPROF"); ok {
f, err := os.Create("cpu.prof")
if err != nil {
panic(err)
}
err = pprof.StartCPUProfile(f)
if err != nil {
panic(err)
}
defer pprof.StopCPUProfile()
memProf, err := os.Create("mem.prof")
if err != nil {
panic(err)
}
defer pprof.WriteHeapProfile(memProf)
2023-09-08 00:45:34 +03:00
}
2023-09-07 23:53:51 +03:00
2024-03-14 01:16:23 +03:00
if complete.Complete() {
os.Exit(0)
return
}
2024-03-13 01:58:00 +03:00
2023-09-11 19:37:02 +03:00
var args []string
for _, arg := range os.Args[1:] {
2024-03-14 01:16:23 +03:00
if strings.HasPrefix(arg, "--comp") {
flagComp = true
continue
}
2023-09-11 19:37:02 +03:00
switch arg {
case "-h", "--help":
2024-03-14 01:16:23 +03:00
fmt.Println(usage(keyMap))
return
2023-09-11 19:37:02 +03:00
case "-v", "-V", "--version":
2024-03-14 01:16:23 +03:00
fmt.Println(version)
return
2023-09-12 17:52:53 +03:00
case "--themes":
2024-03-21 22:48:09 +03:00
theme.ThemeTester()
2023-09-12 17:52:53 +03:00
return
2023-09-24 15:22:50 +03:00
case "--export-themes":
2024-03-21 22:48:09 +03:00
theme.ExportThemes()
2023-09-24 15:22:50 +03:00
return
2023-09-11 19:37:02 +03:00
default:
args = append(args, arg)
}
}
2023-09-11 19:48:13 +03:00
2024-03-14 01:16:23 +03:00
if flagComp {
shell := flag.String("comp", "", "")
flag.Parse()
switch *shell {
case "bash":
fmt.Print(complete.Bash())
case "zsh":
fmt.Print(complete.Zsh())
2024-03-14 22:02:46 +03:00
case "fish":
fmt.Print(complete.Fish())
2024-03-14 01:16:23 +03:00
default:
fmt.Println("unknown shell type")
}
2023-09-11 19:37:02 +03:00
return
}
2023-09-12 14:29:58 +03:00
stdinIsTty := isatty.IsTerminal(os.Stdin.Fd())
var fileName string
var src io.Reader
2023-09-14 13:15:25 +03:00
if stdinIsTty && len(args) == 0 {
fmt.Println(usage(keyMap))
return
} else if stdinIsTty && len(args) == 1 {
2023-09-12 14:29:58 +03:00
filePath := args[0]
f, err := os.Open(filePath)
if err != nil {
2023-09-12 17:20:17 +03:00
var pathError *fs.PathError
if errors.As(err, &pathError) {
2023-09-12 14:29:58 +03:00
fmt.Println(err)
os.Exit(1)
2023-09-12 17:20:17 +03:00
} else {
2023-09-12 14:29:58 +03:00
panic(err)
}
}
fileName = path.Base(filePath)
src = f
hasYamlExt, _ := regexp.MatchString(`(?i)\.ya?ml$`, fileName)
if !flagYaml && hasYamlExt {
flagYaml = true
}
2023-09-13 17:25:11 +03:00
} else if !stdinIsTty && len(args) == 0 {
2023-09-12 14:29:58 +03:00
src = os.Stdin
2023-09-13 17:25:11 +03:00
} else {
2024-03-14 01:16:23 +03:00
reduce(os.Args[1:])
2023-09-13 17:25:11 +03:00
return
2023-09-12 14:29:58 +03:00
}
data, err := io.ReadAll(src)
2023-09-07 23:53:51 +03:00
if err != nil {
panic(err)
}
if flagYaml {
data, err = yaml.YAMLToJSON(data)
if err != nil {
fmt.Print(err.Error())
os.Exit(1)
return
}
}
2024-03-21 22:48:09 +03:00
head, err := Parse(data)
2023-09-07 23:53:51 +03:00
if err != nil {
fmt.Print(err.Error())
os.Exit(1)
return
}
2023-09-12 14:06:48 +03:00
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"))
2023-09-14 18:25:27 +03:00
searchInput := textinput.New()
searchInput.Prompt = "/"
2024-03-10 16:23:07 +03:00
help := viewport.New(80, 40)
help.HighPerformanceRendering = false
2023-09-07 23:53:51 +03:00
m := &model{
2023-09-15 00:25:52 +03:00
head: head,
top: head,
showCursor: true,
wrap: true,
fileName: fileName,
digInput: digInput,
searchInput: searchInput,
2023-09-15 10:16:36 +03:00
search: newSearch(),
2023-09-07 23:53:51 +03:00
}
2024-03-21 22:48:09 +03:00
lipgloss.SetColorProfile(theme.TermOutput.ColorProfile())
2024-03-10 23:56:08 +03:00
2024-03-14 22:02:46 +03:00
withMouse := tea.WithMouseCellMotion()
if _, ok := os.LookupEnv("FX_NO_MOUSE"); ok {
withMouse = tea.WithAltScreen()
}
2024-03-10 23:56:08 +03:00
p := tea.NewProgram(m,
tea.WithAltScreen(),
2024-03-14 22:02:46 +03:00
withMouse,
2024-03-10 23:56:08 +03:00
tea.WithOutput(os.Stderr),
)
2023-09-07 23:53:51 +03:00
_, err = p.Run()
if err != nil {
panic(err)
}
2024-03-10 23:56:08 +03:00
if m.printOnExit {
fmt.Println(m.cursorValue())
}
2023-09-07 23:53:51 +03:00
}
type model struct {
2023-09-08 10:56:12 +03:00
termWidth, termHeight int
2024-03-21 22:48:09 +03:00
head, top *Node
2023-09-08 10:56:12 +03:00
cursor int // cursor position [0, termHeight)
2023-09-15 00:25:52 +03:00
showCursor bool
2023-09-11 18:57:13 +03:00
wrap bool
2023-09-12 14:06:48 +03:00
margin int
2023-09-11 19:27:54 +03:00
fileName string
2023-09-12 14:06:48 +03:00
digInput textinput.Model
2023-09-14 18:25:27 +03:00
searchInput textinput.Model
2023-09-15 00:25:52 +03:00
search *search
2023-09-15 11:39:10 +03:00
yank bool
2024-03-10 16:23:07 +03:00
showHelp bool
help viewport.Model
2024-03-10 23:09:10 +03:00
showPreview bool
preview viewport.Model
2024-03-10 23:56:08 +03:00
printOnExit bool
2023-09-07 23:53:51 +03:00
}
func (m *model) Init() tea.Cmd {
return nil
}
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
2024-03-10 16:23:07 +03:00
if msg, ok := msg.(tea.WindowSizeMsg); ok {
2023-09-08 10:56:12 +03:00
m.termWidth = msg.Width
m.termHeight = msg.Height
2024-03-10 16:23:07 +03:00
m.help.Width = m.termWidth
m.help.Height = m.termHeight - 1
2024-03-10 23:09:10 +03:00
m.preview.Width = m.termWidth
m.preview.Height = m.termHeight - 1
2024-03-21 22:48:09 +03:00
WrapAll(m.top, m.termWidth)
2023-09-15 00:25:52 +03:00
m.redoSearch()
2024-03-10 16:23:07 +03:00
}
if m.showHelp {
return m.handleHelpKey(msg)
}
2023-09-07 23:53:51 +03:00
2024-03-10 23:09:10 +03:00
if m.showPreview {
return m.handlePreviewKey(msg)
}
2024-03-10 16:23:07 +03:00
switch msg := msg.(type) {
2023-09-07 23:53:51 +03:00
case tea.MouseMsg:
switch msg.Type {
case tea.MouseWheelUp:
2023-09-08 12:14:48 +03:00
m.up()
2023-09-07 23:53:51 +03:00
case tea.MouseWheelDown:
2023-09-08 12:14:48 +03:00
m.down()
2023-09-09 01:14:46 +03:00
case tea.MouseLeft:
2023-09-12 14:06:48 +03:00
m.digInput.Blur()
2023-09-15 00:25:52 +03:00
m.showCursor = true
2023-09-09 01:14:46 +03:00
if msg.Y < m.viewHeight() {
2023-09-11 15:13:28 +03:00
if m.cursor == msg.Y {
to := m.cursorPointsTo()
if to != nil {
2024-03-21 22:48:09 +03:00
if to.IsCollapsed() {
to.Expand()
2023-09-11 15:13:28 +03:00
} else {
2024-03-21 22:48:09 +03:00
to.Collapse()
2023-09-11 15:13:28 +03:00
}
}
2023-09-09 01:14:46 +03:00
} else {
2023-09-11 15:13:28 +03:00
to := m.at(msg.Y)
if to != nil {
m.cursor = msg.Y
2024-03-21 22:48:09 +03:00
if to.IsCollapsed() {
to.Expand()
2023-09-11 15:13:28 +03:00
}
}
2023-09-09 01:14:46 +03:00
}
}
2023-09-07 23:53:51 +03:00
}
case tea.KeyMsg:
2023-09-12 14:06:48 +03:00
if m.digInput.Focused() {
return m.handleDigKey(msg)
}
2023-09-14 18:25:27 +03:00
if m.searchInput.Focused() {
return m.handleSearchKey(msg)
}
2023-09-15 11:39:10 +03:00
if m.yank {
return m.handleYankKey(msg)
}
2023-09-07 23:53:51 +03:00
return m.handleKey(msg)
}
return m, nil
}
2023-09-12 14:06:48 +03:00
func (m *model) handleDigKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch {
2023-09-19 10:57:51 +03:00
case key.Matches(msg, arrowUp):
m.up()
m.digInput.SetValue(m.cursorPath())
m.digInput.CursorEnd()
case key.Matches(msg, arrowDown):
m.down()
m.digInput.SetValue(m.cursorPath())
m.digInput.CursorEnd()
case msg.Type == tea.KeyEscape:
m.digInput.Blur()
case msg.Type == tea.KeyTab:
m.digInput.SetValue(m.cursorPath())
m.digInput.CursorEnd()
case msg.Type == tea.KeyEnter:
2023-09-12 14:06:48 +03:00
m.digInput.Blur()
2023-09-19 10:57:51 +03:00
digPath, ok := jsonpath.Split(m.digInput.Value())
if ok {
n := m.selectByPath(digPath)
if n != nil {
m.selectNode(n)
}
}
2023-09-12 14:06:48 +03:00
2023-09-22 15:22:51 +03:00
case key.Matches(msg, key.NewBinding(key.WithKeys("ctrl+w"))):
digPath, ok := jsonpath.Split(m.digInput.Value())
if ok {
2023-09-25 08:21:46 +03:00
if len(digPath) > 0 {
digPath = digPath[:len(digPath)-1]
}
2023-09-22 15:22:51 +03:00
n := m.selectByPath(digPath)
if n != nil {
m.selectNode(n)
m.digInput.SetValue(m.cursorPath())
m.digInput.CursorEnd()
}
}
2023-09-24 16:01:39 +03:00
case key.Matches(msg, textinput.DefaultKeyMap.WordBackward):
value := m.digInput.Value()
pth, ok := jsonpath.Split(value[0:m.digInput.Position()])
if ok {
if len(pth) > 0 {
pth = pth[:len(pth)-1]
m.digInput.SetCursor(len(jsonpath.Join(pth)))
} else {
m.digInput.CursorStart()
}
}
case key.Matches(msg, textinput.DefaultKeyMap.WordForward):
value := m.digInput.Value()
fullPath, ok1 := jsonpath.Split(value)
pth, ok2 := jsonpath.Split(value[0:m.digInput.Position()])
if ok1 && ok2 {
if len(pth) < len(fullPath) {
pth = append(pth, fullPath[len(pth)])
m.digInput.SetCursor(len(jsonpath.Join(pth)))
} else {
m.digInput.CursorEnd()
}
}
2023-09-12 14:06:48 +03:00
default:
2023-09-22 15:22:51 +03:00
if key.Matches(msg, key.NewBinding(key.WithKeys("."))) {
if m.digInput.Position() == len(m.digInput.Value()) {
m.digInput.SetValue(m.cursorPath())
m.digInput.CursorEnd()
}
2023-09-22 15:22:51 +03:00
}
2023-09-12 14:06:48 +03:00
m.digInput, cmd = m.digInput.Update(msg)
n := m.dig(m.digInput.Value())
if n != nil {
m.selectNode(n)
}
}
return m, cmd
}
2024-03-10 16:23:07 +03:00
func (m *model) handleHelpKey(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
if msg, ok := msg.(tea.KeyMsg); ok {
switch {
2024-03-11 00:50:35 +03:00
case key.Matches(msg, keyMap.Quit), key.Matches(msg, keyMap.Help):
2024-03-10 16:23:07 +03:00
m.showHelp = false
}
}
m.help, cmd = m.help.Update(msg)
return m, cmd
}
2024-03-10 23:09:10 +03:00
func (m *model) handlePreviewKey(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
if msg, ok := msg.(tea.KeyMsg); ok {
switch {
2024-03-10 23:39:22 +03:00
case key.Matches(msg, keyMap.Quit),
key.Matches(msg, keyMap.Preview):
2024-03-10 23:09:10 +03:00
m.showPreview = false
2024-03-10 23:56:08 +03:00
case key.Matches(msg, keyMap.Print):
return m, m.print()
2024-03-10 23:09:10 +03:00
}
}
m.preview, cmd = m.preview.Update(msg)
return m, cmd
}
2023-09-14 18:25:27 +03:00
func (m *model) handleSearchKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch {
case msg.Type == tea.KeyEscape:
m.searchInput.Blur()
2023-09-14 22:30:17 +03:00
m.searchInput.SetValue("")
2023-09-15 00:25:52 +03:00
m.doSearch("")
m.showCursor = true
2023-09-14 18:25:27 +03:00
case msg.Type == tea.KeyEnter:
m.searchInput.Blur()
2023-09-15 00:25:52 +03:00
m.doSearch(m.searchInput.Value())
2023-09-14 18:25:27 +03:00
default:
m.searchInput, cmd = m.searchInput.Update(msg)
}
return m, cmd
}
2023-09-15 11:39:10 +03:00
func (m *model) handleYankKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch {
2023-09-15 22:55:21 +03:00
case key.Matches(msg, yankPath):
2023-09-15 11:39:10 +03:00
_ = clipboard.WriteAll(m.cursorPath())
2023-09-15 22:55:21 +03:00
case key.Matches(msg, yankKey):
_ = clipboard.WriteAll(m.cursorKey())
2024-03-14 22:02:46 +03:00
case key.Matches(msg, yankValueY, yankValueV):
2023-09-15 11:39:10 +03:00
_ = clipboard.WriteAll(m.cursorValue())
}
m.yank = false
return m, nil
}
2023-09-07 23:53:51 +03:00
func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch {
case key.Matches(msg, keyMap.Quit):
return m, tea.Quit
2024-03-10 16:23:07 +03:00
case key.Matches(msg, keyMap.Help):
2024-03-10 22:54:51 +03:00
m.help.SetContent(help(keyMap))
2024-03-10 16:23:07 +03:00
m.showHelp = true
2023-09-07 23:53:51 +03:00
case key.Matches(msg, keyMap.Up):
2023-09-08 12:14:48 +03:00
m.up()
2023-09-07 23:53:51 +03:00
case key.Matches(msg, keyMap.Down):
2023-09-08 12:14:48 +03:00
m.down()
case key.Matches(msg, keyMap.PageUp):
m.cursor = 0
for i := 0; i < m.viewHeight(); i++ {
m.up()
}
case key.Matches(msg, keyMap.PageDown):
m.cursor = m.viewHeight() - 1
for i := 0; i < m.viewHeight(); i++ {
m.down()
}
m.scrollIntoView()
2023-09-08 12:14:48 +03:00
case key.Matches(msg, keyMap.HalfPageUp):
m.cursor = 0
for i := 0; i < m.viewHeight()/2; i++ {
m.up()
}
case key.Matches(msg, keyMap.HalfPageDown):
m.cursor = m.viewHeight() - 1
for i := 0; i < m.viewHeight()/2; i++ {
m.down()
2023-09-08 10:56:12 +03:00
}
m.scrollIntoView()
2023-09-08 12:14:48 +03:00
case key.Matches(msg, keyMap.GotoTop):
m.head = m.top
2023-09-09 14:13:20 +03:00
m.cursor = 0
2023-09-15 00:25:52 +03:00
m.showCursor = true
2023-09-08 12:14:48 +03:00
case key.Matches(msg, keyMap.GotoBottom):
m.head = m.findBottom()
2023-09-09 14:13:20 +03:00
m.cursor = 0
2023-09-15 00:25:52 +03:00
m.showCursor = true
2023-09-08 12:14:48 +03:00
m.scrollIntoView()
2023-09-09 01:14:46 +03:00
case key.Matches(msg, keyMap.NextSibling):
pointsTo := m.cursorPointsTo()
2024-03-21 22:48:09 +03:00
var nextSibling *Node
if pointsTo.End != nil && pointsTo.End.Next != nil {
nextSibling = pointsTo.End.Next
2023-09-09 01:14:46 +03:00
} else {
2024-03-21 22:48:09 +03:00
nextSibling = pointsTo.Next
2023-09-09 01:14:46 +03:00
}
if nextSibling != nil {
2023-09-10 17:11:42 +03:00
m.selectNode(nextSibling)
2023-09-09 01:14:46 +03:00
}
case key.Matches(msg, keyMap.PrevSibling):
2023-09-10 16:57:12 +03:00
pointsTo := m.cursorPointsTo()
2024-03-21 22:48:09 +03:00
var prevSibling *Node
if pointsTo.Parent() != nil && pointsTo.Parent().End == pointsTo {
prevSibling = pointsTo.Parent()
} else if pointsTo.Prev != nil {
prevSibling = pointsTo.Prev
parent := prevSibling.Parent()
if parent != nil && parent.End == prevSibling {
2023-09-10 16:57:12 +03:00
prevSibling = parent
}
}
if prevSibling != nil {
2023-09-10 17:11:42 +03:00
m.selectNode(prevSibling)
2023-09-10 16:57:12 +03:00
}
2023-09-09 01:14:46 +03:00
2023-09-08 12:14:48 +03:00
case key.Matches(msg, keyMap.Collapse):
2023-09-10 17:11:42 +03:00
n := m.cursorPointsTo()
2024-03-21 22:48:09 +03:00
if n.HasChildren() && !n.IsCollapsed() {
n.Collapse()
2023-09-08 13:05:44 +03:00
} else {
2024-03-21 22:48:09 +03:00
if n.Parent() != nil {
n = n.Parent()
2023-09-10 17:11:42 +03:00
}
2023-09-08 13:05:44 +03:00
}
2023-09-10 17:11:42 +03:00
m.selectNode(n)
2023-09-08 13:05:44 +03:00
case key.Matches(msg, keyMap.Expand):
2024-03-21 22:48:09 +03:00
m.cursorPointsTo().Expand()
2023-09-15 00:25:52 +03:00
m.showCursor = true
2023-09-08 12:14:48 +03:00
2023-09-11 18:50:38 +03:00
case key.Matches(msg, keyMap.CollapseRecursively):
n := m.cursorPointsTo()
2024-03-21 22:48:09 +03:00
if n.HasChildren() {
n.CollapseRecursively()
2023-09-11 18:50:38 +03:00
}
2023-09-15 00:25:52 +03:00
m.showCursor = true
2023-09-11 18:50:38 +03:00
case key.Matches(msg, keyMap.ExpandRecursively):
n := m.cursorPointsTo()
2024-03-21 22:48:09 +03:00
if n.HasChildren() {
n.ExpandRecursively(0, math.MaxInt)
2023-09-11 18:50:38 +03:00
}
2023-09-15 00:25:52 +03:00
m.showCursor = true
2023-09-11 18:50:38 +03:00
case key.Matches(msg, keyMap.CollapseAll):
n := m.top
for n != nil {
2024-03-21 22:48:09 +03:00
n.CollapseRecursively()
if n.End == nil {
n = nil
} else {
2024-03-21 22:48:09 +03:00
n = n.End.Next
}
}
2023-09-11 18:50:38 +03:00
m.cursor = 0
m.head = m.top
2023-09-15 00:25:52 +03:00
m.showCursor = true
2023-09-11 18:50:38 +03:00
case key.Matches(msg, keyMap.ExpandAll):
at := m.cursorPointsTo()
n := m.top
for n != nil {
2024-03-21 22:48:09 +03:00
n.ExpandRecursively(0, math.MaxInt)
if n.End == nil {
n = nil
} else {
2024-03-21 22:48:09 +03:00
n = n.End.Next
}
}
2023-09-11 18:50:38 +03:00
m.selectNode(at)
2024-03-10 22:48:46 +03:00
case key.Matches(msg, keyMap.CollapseLevel):
at := m.cursorPointsTo()
2024-03-21 22:48:09 +03:00
if at != nil && at.HasChildren() {
2024-03-10 22:48:46 +03:00
toLevel, _ := strconv.Atoi(msg.String())
2024-03-21 22:48:09 +03:00
at.CollapseRecursively()
at.ExpandRecursively(0, toLevel)
2024-03-10 22:48:46 +03:00
m.showCursor = true
}
2023-09-11 18:57:13 +03:00
case key.Matches(msg, keyMap.ToggleWrap):
at := m.cursorPointsTo()
m.wrap = !m.wrap
if m.wrap {
2024-03-21 22:48:09 +03:00
WrapAll(m.top, m.termWidth)
2023-09-11 18:57:13 +03:00
} else {
2024-03-21 22:48:09 +03:00
DropWrapAll(m.top)
2023-09-11 18:57:13 +03:00
}
2024-03-21 22:48:09 +03:00
if at.Chunk != nil && at.Value == nil {
at = at.Parent()
2023-09-11 18:57:13 +03:00
}
2023-09-15 00:25:52 +03:00
m.redoSearch()
2023-09-11 18:57:13 +03:00
m.selectNode(at)
2023-09-15 11:39:10 +03:00
case key.Matches(msg, keyMap.Yank):
m.yank = true
2024-03-10 23:09:10 +03:00
case key.Matches(msg, keyMap.Preview):
2024-03-10 23:39:22 +03:00
m.showPreview = true
2024-03-11 00:42:53 +03:00
content := lipgloss.NewStyle().Width(m.termWidth).Render(m.cursorValue())
m.preview.SetContent(content)
2024-03-10 23:39:22 +03:00
m.preview.GotoTop()
2024-03-10 23:09:10 +03:00
2024-03-10 23:56:08 +03:00
case key.Matches(msg, keyMap.Print):
return m, m.print()
2023-09-12 14:06:48 +03:00
case key.Matches(msg, keyMap.Dig):
2023-09-19 10:57:51 +03:00
m.digInput.SetValue(m.cursorPath() + ".")
2023-09-12 14:06:48 +03:00
m.digInput.CursorEnd()
m.digInput.Width = m.termWidth - 1
m.digInput.Focus()
2023-09-14 18:25:27 +03:00
case key.Matches(msg, keyMap.Search):
m.searchInput.CursorEnd()
m.searchInput.Width = m.termWidth - 2 // -1 for the prompt, -1 for the cursor
m.searchInput.Focus()
case key.Matches(msg, keyMap.SearchNext):
2023-09-15 00:25:52 +03:00
m.selectSearchResult(m.search.cursor + 1)
2023-09-14 18:25:27 +03:00
case key.Matches(msg, keyMap.SearchPrev):
2023-09-15 00:25:52 +03:00
m.selectSearchResult(m.search.cursor - 1)
2023-09-12 14:06:48 +03:00
}
2023-09-07 23:53:51 +03:00
return m, nil
}
2023-09-08 12:14:48 +03:00
func (m *model) up() {
2023-09-15 00:25:52 +03:00
m.showCursor = true
2023-09-08 12:14:48 +03:00
m.cursor--
if m.cursor < 0 {
m.cursor = 0
2024-03-21 22:48:09 +03:00
if m.head.Prev != nil {
m.head = m.head.Prev
2023-09-08 12:14:48 +03:00
}
}
}
func (m *model) down() {
2023-09-15 00:25:52 +03:00
m.showCursor = true
2023-09-08 12:14:48 +03:00
m.cursor++
n := m.cursorPointsTo()
if n == nil {
m.cursor--
return
}
if m.cursor >= m.viewHeight() {
m.cursor = m.viewHeight() - 1
2024-03-21 22:48:09 +03:00
if m.head.Next != nil {
m.head = m.head.Next
2023-09-08 12:14:48 +03:00
}
}
}
func (m *model) visibleLines() int {
visibleLines := 0
n := m.head
for n != nil && visibleLines < m.viewHeight() {
visibleLines++
2024-03-21 22:48:09 +03:00
n = n.Next
2023-09-08 12:14:48 +03:00
}
return visibleLines
}
2023-09-08 13:05:44 +03:00
func (m *model) scrollIntoView() {
2023-09-08 12:14:48 +03:00
visibleLines := m.visibleLines()
2023-09-08 13:05:44 +03:00
if m.cursor >= visibleLines {
m.cursor = visibleLines - 1
}
2024-03-21 22:48:09 +03:00
for visibleLines < m.viewHeight() && m.head.Prev != nil {
2023-09-08 12:14:48 +03:00
visibleLines++
m.cursor++
2024-03-21 22:48:09 +03:00
m.head = m.head.Prev
2023-09-08 12:14:48 +03:00
}
}
2023-09-07 23:53:51 +03:00
func (m *model) View() string {
2024-03-10 16:23:07 +03:00
if m.showHelp {
2024-03-11 00:50:35 +03:00
statusBar := flex(m.termWidth, ": press q or ? to close help", "")
2024-03-21 22:48:09 +03:00
return m.help.View() + "\n" + string(theme.CurrentTheme.StatusBar([]byte(statusBar)))
2024-03-10 16:23:07 +03:00
}
2024-03-10 23:09:10 +03:00
if m.showPreview {
statusBar := flex(m.termWidth, m.cursorPath(), m.fileName)
2024-03-21 22:48:09 +03:00
return m.preview.View() + "\n" + string(theme.CurrentTheme.StatusBar([]byte(statusBar)))
2024-03-10 23:09:10 +03:00
}
2023-09-07 23:53:51 +03:00
var screen []byte
2023-09-08 21:49:59 +03:00
n := m.head
2023-09-08 10:19:29 +03:00
2023-09-08 21:49:59 +03:00
printedLines := 0
2023-09-14 22:30:17 +03:00
for lineNumber := 0; lineNumber < m.viewHeight(); lineNumber++ {
2023-09-08 21:49:59 +03:00
if n == nil {
2023-09-07 23:53:51 +03:00
break
}
2024-03-21 22:48:09 +03:00
for ident := 0; ident < int(n.Depth); ident++ {
2023-09-07 23:53:51 +03:00
screen = append(screen, ' ', ' ')
}
2023-09-11 14:16:10 +03:00
2023-09-15 00:25:52 +03:00
isSelected := m.cursor == lineNumber
if !m.showCursor {
isSelected = false // don't highlight the cursor while iterating search results
}
2023-09-11 14:16:10 +03:00
2024-03-21 22:48:09 +03:00
if n.Key != nil {
2023-09-15 00:25:52 +03:00
screen = append(screen, m.prettyKey(n, isSelected)...)
2024-03-21 22:48:09 +03:00
screen = append(screen, theme.Colon...)
2023-09-15 00:25:52 +03:00
isSelected = false // don't highlight the key's value
2023-09-07 23:53:51 +03:00
}
2023-09-11 14:16:10 +03:00
2023-09-15 00:25:52 +03:00
screen = append(screen, m.prettyPrint(n, isSelected)...)
2023-09-11 14:57:09 +03:00
2024-03-21 22:48:09 +03:00
if n.IsCollapsed() {
if n.Value[0] == '{' {
if n.Collapsed.Key != nil {
screen = append(screen, theme.CurrentTheme.Preview(n.Collapsed.Key)...)
screen = append(screen, theme.ColonPreview...)
2023-09-11 19:48:13 +03:00
}
2024-03-21 22:48:09 +03:00
screen = append(screen, theme.Dot3...)
screen = append(screen, theme.CloseCurlyBracket...)
} else if n.Value[0] == '[' {
screen = append(screen, theme.Dot3...)
screen = append(screen, theme.CloseSquareBracket...)
2023-09-08 13:05:44 +03:00
}
2024-03-21 22:48:09 +03:00
if n.End != nil && n.End.Comma {
screen = append(screen, theme.Comma...)
2023-09-15 10:03:35 +03:00
}
2023-09-08 13:05:44 +03:00
}
2024-03-21 22:48:09 +03:00
if n.Comma {
screen = append(screen, theme.Comma...)
2023-09-07 23:53:51 +03:00
}
2023-09-08 12:14:48 +03:00
2024-03-21 22:48:09 +03:00
if theme.ShowSizes && len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') {
if n.IsCollapsed() || n.Size > 1 {
screen = append(screen, theme.CurrentTheme.Size([]byte(fmt.Sprintf(" // %d", n.Size)))...)
}
}
2023-09-07 23:53:51 +03:00
screen = append(screen, '\n')
2023-09-08 21:49:59 +03:00
printedLines++
2024-03-21 22:48:09 +03:00
n = n.Next
2023-09-07 23:53:51 +03:00
}
2023-09-08 10:19:29 +03:00
2023-09-08 21:49:59 +03:00
for i := printedLines; i < m.viewHeight(); i++ {
2024-03-21 22:48:09 +03:00
screen = append(screen, theme.Empty...)
2023-09-08 21:49:59 +03:00
screen = append(screen, '\n')
2023-09-07 23:53:51 +03:00
}
2023-09-08 12:14:48 +03:00
2023-09-12 14:06:48 +03:00
if m.digInput.Focused() {
screen = append(screen, m.digInput.View()...)
} else {
2023-09-14 18:25:27 +03:00
statusBar := flex(m.termWidth, m.cursorPath(), m.fileName)
2024-03-21 22:48:09 +03:00
screen = append(screen, theme.CurrentTheme.StatusBar([]byte(statusBar))...)
2023-09-12 14:06:48 +03:00
}
2023-09-11 19:27:54 +03:00
2023-09-15 22:55:21 +03:00
if m.yank {
screen = append(screen, '\n')
screen = append(screen, []byte("(y)value (p)path (k)key")...)
} else if m.searchInput.Focused() {
2023-09-14 18:25:27 +03:00
screen = append(screen, '\n')
screen = append(screen, m.searchInput.View()...)
} else if m.searchInput.Value() != "" {
screen = append(screen, '\n')
re, ci := regexCase(m.searchInput.Value())
2023-09-14 22:30:17 +03:00
re = "/" + re + "/"
2023-09-14 18:25:27 +03:00
if ci {
2023-09-14 22:30:17 +03:00
re += "i"
2023-09-14 18:25:27 +03:00
}
2023-09-15 00:25:52 +03:00
if m.search.err != nil {
screen = append(screen, flex(m.termWidth, re, m.search.err.Error())...)
} else if len(m.search.results) == 0 {
2023-09-14 18:25:27 +03:00
screen = append(screen, flex(m.termWidth, re, "not found")...)
} else {
2023-09-15 00:25:52 +03:00
cursor := fmt.Sprintf("found: [%v/%v]", m.search.cursor+1, len(m.search.results))
2023-09-14 18:25:27 +03:00
screen = append(screen, flex(m.termWidth, re, cursor)...)
}
}
2023-09-07 23:53:51 +03:00
return string(screen)
}
2023-09-08 10:56:12 +03:00
2024-03-21 22:48:09 +03:00
func (m *model) prettyKey(node *Node, selected bool) []byte {
b := node.Key
2023-09-15 00:25:52 +03:00
2024-03-21 22:48:09 +03:00
style := theme.CurrentTheme.Key
2023-09-15 00:25:52 +03:00
if selected {
2024-03-21 22:48:09 +03:00
style = theme.CurrentTheme.Cursor
2023-09-15 00:25:52 +03:00
}
if indexes, ok := m.search.keys[node]; ok {
var out []byte
2023-09-15 10:03:35 +03:00
for i, p := range splitBytesByIndexes(b, indexes) {
2023-09-15 00:25:52 +03:00
if i%2 == 0 {
2023-09-15 10:03:35 +03:00
out = append(out, style(p.b)...)
} else if p.index == m.search.cursor {
2024-03-21 22:48:09 +03:00
out = append(out, theme.CurrentTheme.Cursor(p.b)...)
2023-09-15 00:25:52 +03:00
} else {
2024-03-21 22:48:09 +03:00
out = append(out, theme.CurrentTheme.Search(p.b)...)
2023-09-15 00:25:52 +03:00
}
}
return out
} else {
return style(b)
}
}
2024-03-21 22:48:09 +03:00
func (m *model) prettyPrint(node *Node, selected bool) []byte {
2023-09-14 22:30:17 +03:00
var b []byte
2024-03-21 22:48:09 +03:00
if node.Chunk != nil {
b = node.Chunk
2023-09-14 22:30:17 +03:00
} else {
2024-03-21 22:48:09 +03:00
b = node.Value
2023-09-14 22:30:17 +03:00
}
if len(b) == 0 {
return b
}
2024-03-21 22:48:09 +03:00
style := theme.Value(b, selected, node.Chunk != nil)
2023-09-14 22:30:17 +03:00
2023-09-15 00:25:52 +03:00
if indexes, ok := m.search.values[node]; ok {
2023-09-14 22:30:17 +03:00
var out []byte
2023-09-15 10:03:35 +03:00
for i, p := range splitBytesByIndexes(b, indexes) {
2023-09-14 22:30:17 +03:00
if i%2 == 0 {
2023-09-15 10:03:35 +03:00
out = append(out, style(p.b)...)
} else if p.index == m.search.cursor {
2024-03-21 22:48:09 +03:00
out = append(out, theme.CurrentTheme.Cursor(p.b)...)
2023-09-14 22:30:17 +03:00
} else {
2024-03-21 22:48:09 +03:00
out = append(out, theme.CurrentTheme.Search(p.b)...)
2023-09-14 22:30:17 +03:00
}
}
return out
} else {
return style(b)
}
}
2023-09-08 10:56:12 +03:00
func (m *model) viewHeight() int {
2023-09-14 18:25:27 +03:00
if m.searchInput.Focused() || m.searchInput.Value() != "" {
return m.termHeight - 2
}
2023-09-15 11:39:10 +03:00
if m.yank {
return m.termHeight - 2
}
2023-09-08 12:14:48 +03:00
return m.termHeight - 1
2023-09-08 10:56:12 +03:00
}
2024-03-21 22:48:09 +03:00
func (m *model) cursorPointsTo() *Node {
2023-09-11 15:13:28 +03:00
return m.at(m.cursor)
}
2024-03-21 22:48:09 +03:00
func (m *model) at(pos int) *Node {
2023-09-08 10:56:12 +03:00
head := m.head
2023-09-11 15:13:28 +03:00
for i := 0; i < pos; i++ {
2023-09-08 10:56:12 +03:00
if head == nil {
2023-09-08 12:14:48 +03:00
break
2023-09-08 10:56:12 +03:00
}
2024-03-21 22:48:09 +03:00
head = head.Next
2023-09-08 10:56:12 +03:00
}
return head
}
2024-03-21 22:48:09 +03:00
func (m *model) findBottom() *Node {
2023-09-08 12:14:48 +03:00
n := m.head
2024-03-21 22:48:09 +03:00
for n.Next != nil {
if n.End != nil {
n = n.End
2023-09-08 12:14:48 +03:00
} else {
2024-03-21 22:48:09 +03:00
n = n.Next
2023-09-08 12:14:48 +03:00
}
}
return n
2023-09-08 10:56:12 +03:00
}
2023-09-08 13:05:44 +03:00
2024-03-21 22:48:09 +03:00
func (m *model) nodeInsideView(n *Node) bool {
2023-09-08 13:05:44 +03:00
if n == nil {
return false
}
head := m.head
for i := 0; i < m.viewHeight(); i++ {
if head == nil {
break
}
if head == n {
return true
}
2024-03-21 22:48:09 +03:00
head = head.Next
2023-09-08 13:05:44 +03:00
}
return false
}
2024-03-21 22:48:09 +03:00
func (m *model) selectNodeInView(n *Node) {
2023-09-08 13:05:44 +03:00
head := m.head
for i := 0; i < m.viewHeight(); i++ {
if head == nil {
break
}
if head == n {
m.cursor = i
return
}
2024-03-21 22:48:09 +03:00
head = head.Next
2023-09-08 13:05:44 +03:00
}
}
2023-09-10 17:11:42 +03:00
2024-03-21 22:48:09 +03:00
func (m *model) selectNode(n *Node) {
2023-09-15 00:25:52 +03:00
m.showCursor = true
2023-09-10 17:11:42 +03:00
if m.nodeInsideView(n) {
m.selectNodeInView(n)
m.scrollIntoView()
} else {
m.cursor = 0
m.head = n
m.scrollIntoView()
}
2024-03-21 22:48:09 +03:00
parent := n.Parent()
2023-09-14 22:30:17 +03:00
for parent != nil {
2024-03-21 22:48:09 +03:00
parent.Expand()
parent = parent.Parent()
2023-09-14 22:30:17 +03:00
}
2023-09-10 17:11:42 +03:00
}
2023-09-11 19:27:54 +03:00
func (m *model) cursorPath() string {
path := ""
at := m.cursorPointsTo()
for at != nil {
2024-03-21 22:48:09 +03:00
if at.Prev != nil {
if at.Chunk != nil && at.Value == nil {
at = at.Parent()
2023-09-11 19:27:54 +03:00
}
2024-03-21 22:48:09 +03:00
if at.Key != nil {
quoted := string(at.Key)
2023-09-11 19:27:54 +03:00
unquoted, err := strconv.Unquote(quoted)
2023-09-24 16:01:39 +03:00
if err == nil && jsonpath.Identifier.MatchString(unquoted) {
2023-09-11 19:27:54 +03:00
path = "." + unquoted + path
} else {
path = "[" + quoted + "]" + path
}
2024-03-21 22:48:09 +03:00
} else if at.Index >= 0 {
path = "[" + strconv.Itoa(at.Index) + "]" + path
2023-09-11 19:27:54 +03:00
}
}
2024-03-21 22:48:09 +03:00
at = at.Parent()
2023-09-11 19:27:54 +03:00
}
return path
}
2023-09-12 14:06:48 +03:00
2023-09-15 11:39:10 +03:00
func (m *model) cursorValue() string {
at := m.cursorPointsTo()
if at == nil {
return ""
}
2024-03-21 22:48:09 +03:00
parent := at.Parent()
2024-03-11 00:38:04 +03:00
if parent != nil {
// wrapped string part
2024-03-21 22:48:09 +03:00
if at.Chunk != nil && at.Value == nil {
2024-03-11 00:38:04 +03:00
at = parent
}
2024-03-21 22:48:09 +03:00
if len(at.Value) == 1 && at.Value[0] == '}' || at.Value[0] == ']' {
2024-03-11 00:38:04 +03:00
at = parent
}
2024-03-10 23:39:22 +03:00
}
2024-03-21 22:48:09 +03:00
if len(at.Value) > 0 && at.Value[0] == '"' {
str, err := strconv.Unquote(string(at.Value))
2024-03-10 23:39:22 +03:00
if err == nil {
return str
}
2024-03-21 22:48:09 +03:00
return string(at.Value)
2023-09-15 11:39:10 +03:00
}
2024-03-10 23:39:22 +03:00
var out strings.Builder
2024-03-21 22:48:09 +03:00
out.Write(at.Value)
2024-03-10 23:39:22 +03:00
out.WriteString("\n")
2024-03-21 22:48:09 +03:00
if at.HasChildren() {
it := at.Next
if at.IsCollapsed() {
it = at.Collapsed
2023-09-15 11:39:10 +03:00
}
for it != nil {
2024-03-21 22:48:09 +03:00
out.WriteString(strings.Repeat(" ", int(it.Depth-at.Depth)))
if it.Key != nil {
out.Write(it.Key)
2023-09-15 11:39:10 +03:00
out.WriteString(": ")
}
2024-03-21 22:48:09 +03:00
if it.Value != nil {
out.Write(it.Value)
2023-09-15 11:39:10 +03:00
}
2024-03-21 22:48:09 +03:00
if it == at.End {
2023-09-15 11:39:10 +03:00
break
}
2024-03-21 22:48:09 +03:00
if it.Comma {
2024-03-10 23:39:22 +03:00
out.WriteString(",")
2023-09-15 11:39:10 +03:00
}
2024-03-10 23:39:22 +03:00
out.WriteString("\n")
2024-03-21 22:48:09 +03:00
if it.ChunkEnd != nil {
it = it.ChunkEnd.Next
} else if it.IsCollapsed() {
it = it.Collapsed
2023-09-15 11:39:10 +03:00
} else {
2024-03-21 22:48:09 +03:00
it = it.Next
2023-09-15 11:39:10 +03:00
}
}
}
return out.String()
}
2023-09-15 22:55:21 +03:00
func (m *model) cursorKey() string {
at := m.cursorPointsTo()
if at == nil {
return ""
}
2024-03-21 22:48:09 +03:00
if at.Key != nil {
2023-09-15 22:55:21 +03:00
var v string
2024-03-21 22:48:09 +03:00
_ = json.Unmarshal(at.Key, &v)
2023-09-15 22:55:21 +03:00
return v
}
2024-03-21 22:48:09 +03:00
return strconv.Itoa(at.Index)
2023-09-15 22:55:21 +03:00
}
2024-03-21 22:48:09 +03:00
func (m *model) selectByPath(path []any) *Node {
n := m.currentTopNode()
2023-09-19 10:57:51 +03:00
for _, part := range path {
2023-09-12 14:06:48 +03:00
if n == nil {
return nil
}
switch part := part.(type) {
case string:
2024-03-21 22:48:09 +03:00
n = n.FindChildByKey(part)
2023-09-12 14:06:48 +03:00
case int:
2024-03-21 22:48:09 +03:00
n = n.FindChildByIndex(part)
2023-09-12 14:06:48 +03:00
}
}
return n
}
2023-09-14 18:25:27 +03:00
2024-03-21 22:48:09 +03:00
func (m *model) currentTopNode() *Node {
at := m.cursorPointsTo()
if at == nil {
return nil
}
2024-03-21 22:48:09 +03:00
for at.Parent() != nil {
at = at.Parent()
}
return at
}
2023-09-15 00:25:52 +03:00
func (m *model) doSearch(s string) {
2023-09-15 10:16:36 +03:00
m.search = newSearch()
2023-09-15 00:25:52 +03:00
2023-09-14 22:57:14 +03:00
if s == "" {
return
}
2023-09-14 18:25:27 +03:00
code, ci := regexCase(s)
if ci {
code = "(?i)" + code
}
re, err := regexp.Compile(code)
if err != nil {
2023-09-15 00:25:52 +03:00
m.search.err = err
2023-09-14 18:25:27 +03:00
return
}
2023-09-14 22:30:17 +03:00
n := m.top
2023-09-14 22:45:51 +03:00
searchIndex := 0
2023-09-14 22:30:17 +03:00
for n != nil {
2024-03-21 22:48:09 +03:00
if n.Key != nil {
indexes := re.FindAllIndex(n.Key, -1)
2023-09-15 00:25:52 +03:00
if len(indexes) > 0 {
for i, pair := range indexes {
m.search.results = append(m.search.results, n)
m.search.keys[n] = append(m.search.keys[n], match{start: pair[0], end: pair[1], index: searchIndex + i})
}
searchIndex += len(indexes)
}
}
2024-03-21 22:48:09 +03:00
indexes := re.FindAllIndex(n.Value, -1)
2023-09-14 22:30:17 +03:00
if len(indexes) > 0 {
for range indexes {
2023-09-15 00:25:52 +03:00
m.search.results = append(m.search.results, n)
2023-09-14 22:30:17 +03:00
}
2024-03-21 22:48:09 +03:00
if n.Chunk != nil {
2023-09-14 22:30:17 +03:00
// String can be split into chunks, so we need to map the indexes to the chunks.
2024-03-21 22:48:09 +03:00
chunks := [][]byte{n.Chunk}
chunkNodes := []*Node{n}
2023-09-14 22:30:17 +03:00
2024-03-21 22:48:09 +03:00
it := n.Next
2023-09-14 22:30:17 +03:00
for it != nil {
chunkNodes = append(chunkNodes, it)
2024-03-21 22:48:09 +03:00
chunks = append(chunks, it.Chunk)
if it == n.ChunkEnd {
2023-09-14 22:30:17 +03:00
break
}
2024-03-21 22:48:09 +03:00
it = it.Next
2023-09-14 22:30:17 +03:00
}
2023-09-14 22:45:51 +03:00
chunkMatches := splitIndexesToChunks(chunks, indexes, searchIndex)
2023-09-14 22:30:17 +03:00
for i, matches := range chunkMatches {
2023-09-15 00:25:52 +03:00
m.search.values[chunkNodes[i]] = matches
2023-09-14 22:30:17 +03:00
}
} else {
2023-09-14 22:45:51 +03:00
for i, pair := range indexes {
2023-09-15 00:25:52 +03:00
m.search.values[n] = append(m.search.values[n], match{start: pair[0], end: pair[1], index: searchIndex + i})
2023-09-14 22:45:51 +03:00
}
2023-09-14 22:30:17 +03:00
}
2023-09-14 22:45:51 +03:00
searchIndex += len(indexes)
2023-09-14 22:30:17 +03:00
}
2024-03-21 22:48:09 +03:00
if n.IsCollapsed() {
n = n.Collapsed
2023-09-14 22:30:17 +03:00
} else {
2024-03-21 22:48:09 +03:00
n = n.Next
2023-09-14 18:25:27 +03:00
}
}
m.selectSearchResult(0)
}
func (m *model) selectSearchResult(i int) {
2023-09-15 00:25:52 +03:00
if len(m.search.results) == 0 {
2023-09-14 18:25:27 +03:00
return
}
if i < 0 {
2023-09-15 00:25:52 +03:00
i = len(m.search.results) - 1
2023-09-14 18:25:27 +03:00
}
2023-09-15 00:25:52 +03:00
if i >= len(m.search.results) {
2023-09-14 18:25:27 +03:00
i = 0
}
2023-09-15 00:25:52 +03:00
m.search.cursor = i
result := m.search.results[i]
2023-09-14 22:30:17 +03:00
m.selectNode(result)
2023-09-15 00:25:52 +03:00
m.showCursor = false
}
func (m *model) redoSearch() {
if m.searchInput.Value() != "" && len(m.search.results) > 0 {
cursor := m.search.cursor
m.doSearch(m.searchInput.Value())
m.selectSearchResult(cursor)
}
2023-09-14 18:25:27 +03:00
}
2023-09-19 10:57:51 +03:00
2024-03-21 22:48:09 +03:00
func (m *model) dig(v string) *Node {
2023-09-19 10:57:51 +03:00
p, ok := jsonpath.Split(v)
if !ok {
return nil
}
at := m.selectByPath(p)
if at != nil {
return at
}
lastPart := p[len(p)-1]
searchTerm, ok := lastPart.(string)
if !ok {
return nil
}
p = p[:len(p)-1]
at = m.selectByPath(p)
if at == nil {
return nil
}
2024-03-21 22:48:09 +03:00
keys, nodes := at.Children()
2023-09-19 10:57:51 +03:00
matches := fuzzy.Find(searchTerm, keys)
if len(matches) == 0 {
return nil
}
return nodes[matches[0].Index]
}
2024-03-10 23:56:08 +03:00
func (m *model) print() tea.Cmd {
m.printOnExit = true
return tea.Quit
}