mirror of
https://github.com/antonmedv/fx.git
synced 2024-09-11 18:27:10 +03:00
Refactor search result printing
This commit is contained in:
parent
afd7b96af5
commit
d682c0b49f
14
dict.go
14
dict.go
@ -7,11 +7,15 @@ type dict struct {
|
||||
}
|
||||
|
||||
func newDict() *dict {
|
||||
d := dict{}
|
||||
d.keys = []string{}
|
||||
d.indexes = map[string]int{}
|
||||
d.values = map[string]interface{}{}
|
||||
return &d
|
||||
return newDictOfCapacity(0)
|
||||
}
|
||||
|
||||
func newDictOfCapacity(capacity int) *dict {
|
||||
return &dict{
|
||||
keys: make([]string, 0, capacity),
|
||||
indexes: make(map[string]int, capacity),
|
||||
values: make(map[string]interface{}, capacity),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dict) get(key string) (interface{}, bool) {
|
||||
|
50
keymap.go
50
keymap.go
@ -9,19 +9,19 @@ type KeyMap struct {
|
||||
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
|
||||
NextSibling key.Binding
|
||||
PrevSibling key.Binding
|
||||
ExpandRecursively key.Binding
|
||||
CollapseRecursively key.Binding
|
||||
GotoTop key.Binding
|
||||
GotoBottom key.Binding
|
||||
ToggleWrap 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
|
||||
@ -53,6 +53,14 @@ func DefaultKeyMap() KeyMap {
|
||||
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"),
|
||||
@ -69,14 +77,6 @@ func DefaultKeyMap() KeyMap {
|
||||
key.WithKeys("left", "h"),
|
||||
key.WithHelp("", "collapse"),
|
||||
),
|
||||
NextSibling: key.NewBinding(
|
||||
key.WithKeys("J"),
|
||||
key.WithHelp("", "next sibling"),
|
||||
),
|
||||
PrevSibling: key.NewBinding(
|
||||
key.WithKeys("K"),
|
||||
key.WithHelp("", "previous sibling"),
|
||||
),
|
||||
ExpandRecursively: key.NewBinding(
|
||||
key.WithKeys("L"),
|
||||
key.WithHelp("", "expand recursively"),
|
||||
@ -85,18 +85,6 @@ func DefaultKeyMap() KeyMap {
|
||||
key.WithKeys("H"),
|
||||
key.WithHelp("", "collapse recursively"),
|
||||
),
|
||||
GotoTop: key.NewBinding(
|
||||
key.WithKeys("g"),
|
||||
key.WithHelp("", "goto top"),
|
||||
),
|
||||
GotoBottom: key.NewBinding(
|
||||
key.WithKeys("G"),
|
||||
key.WithHelp("", "goto bottom"),
|
||||
),
|
||||
ToggleWrap: key.NewBinding(
|
||||
key.WithKeys("z"),
|
||||
key.WithHelp("", "toggle strings wrap"),
|
||||
),
|
||||
ExpandAll: key.NewBinding(
|
||||
key.WithKeys("e"),
|
||||
key.WithHelp("", "expand all"),
|
||||
@ -105,6 +93,18 @@ func DefaultKeyMap() KeyMap {
|
||||
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"),
|
||||
|
260
main.go
260
main.go
@ -13,9 +13,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type number = json.Number
|
||||
|
||||
var colors = struct {
|
||||
cursor lipgloss.Style
|
||||
bracket lipgloss.Style
|
||||
syntax lipgloss.Style
|
||||
key lipgloss.Style
|
||||
null lipgloss.Style
|
||||
boolean lipgloss.Style
|
||||
@ -26,7 +28,7 @@ var colors = struct {
|
||||
search lipgloss.Style
|
||||
}{
|
||||
cursor: lipgloss.NewStyle().Reverse(true),
|
||||
bracket: lipgloss.NewStyle(),
|
||||
syntax: lipgloss.NewStyle(),
|
||||
key: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("4")),
|
||||
null: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("8")),
|
||||
boolean: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("3")),
|
||||
@ -39,7 +41,7 @@ var colors = struct {
|
||||
|
||||
func main() {
|
||||
filePath := ""
|
||||
args := []string{}
|
||||
var args []string
|
||||
var dec *json.Decoder
|
||||
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
if len(os.Args) >= 2 {
|
||||
@ -85,9 +87,10 @@ func main() {
|
||||
parents[it.path] = it.parent
|
||||
children[it.parent] = append(children[it.parent], it.path)
|
||||
switch it.object.(type) {
|
||||
case *dict, array:
|
||||
canBeExpanded[it.path] = true
|
||||
|
||||
case *dict:
|
||||
canBeExpanded[it.path] = len(it.object.(*dict).keys) > 0
|
||||
case array:
|
||||
canBeExpanded[it.path] = len(it.object.(array)) > 0
|
||||
}
|
||||
})
|
||||
input := textinput.New()
|
||||
@ -103,15 +106,18 @@ func main() {
|
||||
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)
|
||||
}
|
||||
os.Exit(m.exitCode)
|
||||
//os.Exit(m.exitCode)
|
||||
}
|
||||
|
||||
type model struct {
|
||||
@ -119,6 +125,7 @@ type model struct {
|
||||
width, height int
|
||||
windowHeight int
|
||||
footerHeight int
|
||||
wrap bool
|
||||
|
||||
fileName string
|
||||
json interface{}
|
||||
@ -130,21 +137,22 @@ type model struct {
|
||||
keyMap KeyMap
|
||||
showHelp bool
|
||||
|
||||
expandedPaths map[string]bool // a set with expanded paths
|
||||
canBeExpanded map[string]bool // a set for path => can be expanded (i.e. dict or array)
|
||||
pathToLineNumber *dict // dict with path => line number
|
||||
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
|
||||
cursor int // cursor in range of m.pathToLineNumber.keys slice
|
||||
showCursor bool
|
||||
expandedPaths map[string]bool // a set with expanded paths
|
||||
canBeExpanded map[string]bool // a set for path => can be expanded (i.e. dict or array)
|
||||
pathToLineNumber *dict // dict with path => line number
|
||||
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 range of m.pathToLineNumber.keys slice
|
||||
showCursor bool
|
||||
|
||||
wrap bool
|
||||
searchInput textinput.Model
|
||||
searchRegexCompileError string
|
||||
searchResults *dict // path => searchResult
|
||||
showSearchResults bool
|
||||
resultsCursor int // [0, searchResults length)
|
||||
searchResults []*searchResult
|
||||
searchResultsCursor int
|
||||
searchResultsIndex map[string]searchResultGroup
|
||||
}
|
||||
|
||||
func (m *model) Init() tea.Cmd {
|
||||
@ -174,7 +182,7 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
case tea.KeyEsc:
|
||||
m.searchInput.Blur()
|
||||
m.searchResults = newDict()
|
||||
//m.searchResults = newDict()
|
||||
m.render()
|
||||
|
||||
case tea.KeyEnter:
|
||||
@ -233,91 +241,82 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.render()
|
||||
|
||||
case key.Matches(msg, m.keyMap.Down):
|
||||
m.showCursor = true
|
||||
if m.cursor < len(m.pathToLineNumber.keys)-1 { // scroll till last element in m.pathToLineNumber
|
||||
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.pathToLineNumber.keys) {
|
||||
m.cursor = len(m.pathToLineNumber.keys) - 1
|
||||
}
|
||||
m.down()
|
||||
m.render()
|
||||
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)
|
||||
}
|
||||
|
||||
case key.Matches(msg, m.keyMap.NextSibling):
|
||||
m.showCursor = true
|
||||
// TODO: write code for collecting siblings,
|
||||
// and write code to getting sibling and jumping to it.
|
||||
m.render()
|
||||
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)
|
||||
}
|
||||
m.scrollDownToCursor()
|
||||
|
||||
case key.Matches(msg, m.keyMap.Up):
|
||||
m.showCursor = true
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
}
|
||||
if m.cursor >= len(m.pathToLineNumber.keys) {
|
||||
m.cursor = len(m.pathToLineNumber.keys) - 1
|
||||
m.up()
|
||||
m.render()
|
||||
m.scrollUpToCursor()
|
||||
|
||||
case key.Matches(msg, m.keyMap.NextSibling):
|
||||
nextSiblingPath, ok := m.nextSiblings[m.cursorPath()]
|
||||
if ok {
|
||||
index, _ := m.pathToLineNumber.indexes[nextSiblingPath]
|
||||
m.showCursor = true
|
||||
m.cursor = index
|
||||
} else {
|
||||
m.down()
|
||||
}
|
||||
m.render()
|
||||
at := m.cursorLineNumber()
|
||||
if at < m.offset+m.height { // cursor is above
|
||||
m.LineUp(max(0, m.offset-at))
|
||||
m.scrollDownToCursor()
|
||||
|
||||
case key.Matches(msg, m.keyMap.PrevSibling):
|
||||
prevSiblingPath, ok := m.prevSiblings[m.cursorPath()]
|
||||
if ok {
|
||||
index, _ := m.pathToLineNumber.indexes[prevSiblingPath]
|
||||
m.showCursor = true
|
||||
m.cursor = index
|
||||
} else {
|
||||
m.SetOffset(at)
|
||||
m.up()
|
||||
}
|
||||
m.render()
|
||||
m.scrollUpToCursor()
|
||||
|
||||
case key.Matches(msg, m.keyMap.Expand):
|
||||
m.showCursor = true
|
||||
m.expandedPaths[m.cursorPath()] = true
|
||||
if m.canBeExpanded[m.cursorPath()] {
|
||||
m.expandedPaths[m.cursorPath()] = true
|
||||
}
|
||||
m.render()
|
||||
|
||||
case key.Matches(msg, m.keyMap.ExpandRecursively):
|
||||
m.showCursor = true
|
||||
m.expandRecursively(m.cursorPath())
|
||||
if m.canBeExpanded[m.cursorPath()] {
|
||||
m.expandRecursively(m.cursorPath())
|
||||
}
|
||||
m.render()
|
||||
|
||||
case key.Matches(msg, m.keyMap.Collapse, m.keyMap.CollapseRecursively):
|
||||
case key.Matches(msg, m.keyMap.Collapse):
|
||||
m.showCursor = true
|
||||
if m.expandedPaths[m.cursorPath()] {
|
||||
if key.Matches(msg, m.keyMap.CollapseRecursively) {
|
||||
m.collapseRecursively(m.cursorPath())
|
||||
} else {
|
||||
m.expandedPaths[m.cursorPath()] = false
|
||||
}
|
||||
if m.canBeExpanded[m.cursorPath()] && m.expandedPaths[m.cursorPath()] {
|
||||
m.expandedPaths[m.cursorPath()] = false
|
||||
} else {
|
||||
parentPath, ok := m.parents[m.cursorPath()]
|
||||
if ok {
|
||||
if key.Matches(msg, m.keyMap.CollapseRecursively) {
|
||||
m.collapseRecursively(m.cursorPath())
|
||||
} else {
|
||||
m.expandedPaths[m.cursorPath()] = false
|
||||
}
|
||||
m.expandedPaths[parentPath] = false
|
||||
index, _ := m.pathToLineNumber.index(parentPath)
|
||||
m.cursor = index
|
||||
}
|
||||
}
|
||||
m.render()
|
||||
at := m.cursorLineNumber()
|
||||
if at < m.offset+m.height { // cursor is above
|
||||
m.LineUp(max(0, m.offset-at))
|
||||
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 {
|
||||
m.SetOffset(at)
|
||||
parentPath, ok := m.parents[m.cursorPath()]
|
||||
if ok {
|
||||
m.collapseRecursively(parentPath)
|
||||
index, _ := m.pathToLineNumber.index(parentPath)
|
||||
m.cursor = index
|
||||
}
|
||||
}
|
||||
m.render()
|
||||
m.scrollUpToCursor()
|
||||
|
||||
case key.Matches(msg, m.keyMap.ToggleWrap):
|
||||
m.wrap = !m.wrap
|
||||
@ -379,10 +378,13 @@ func (m *model) View() string {
|
||||
if len(lines) < m.height {
|
||||
extraLines = strings.Repeat("\n", max(0, m.height-len(lines)))
|
||||
}
|
||||
statusBar := m.cursorPath() + " "
|
||||
if m.showHelp {
|
||||
statusBar = "Press Esc or q to close help."
|
||||
statusBar := "Press Esc or q to close help."
|
||||
statusBar += strings.Repeat(" ", max(0, m.width-width(statusBar)))
|
||||
statusBar = colors.statusBar.Render(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 = colors.statusBar.Render(statusBar)
|
||||
@ -393,27 +395,29 @@ func (m *model) View() string {
|
||||
if len(m.searchRegexCompileError) > 0 {
|
||||
output += fmt.Sprintf("\n/%v/i %v", m.searchInput.Value(), m.searchRegexCompileError)
|
||||
}
|
||||
if m.showSearchResults {
|
||||
if len(m.searchResults.keys) == 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.resultsCursor+1, len(m.searchResults.keys))
|
||||
}
|
||||
}
|
||||
//if m.showSearchResults {
|
||||
// if len(m.searchResults.keys) == 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.keys))
|
||||
// }
|
||||
//}
|
||||
return output
|
||||
}
|
||||
|
||||
func (m *model) recalculateViewportHeight() {
|
||||
m.height = m.windowHeight
|
||||
m.height-- // status bar
|
||||
if m.searchInput.Focused() {
|
||||
m.height--
|
||||
}
|
||||
if m.showSearchResults {
|
||||
m.height--
|
||||
}
|
||||
if len(m.searchRegexCompileError) > 0 {
|
||||
m.height--
|
||||
if !m.showHelp {
|
||||
if m.searchInput.Focused() {
|
||||
m.height--
|
||||
}
|
||||
if m.showSearchResults {
|
||||
m.height--
|
||||
}
|
||||
if len(m.searchRegexCompileError) > 0 {
|
||||
m.height--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,9 +429,17 @@ func (m *model) render() {
|
||||
return
|
||||
}
|
||||
|
||||
m.pathToLineNumber = newDict()
|
||||
m.lineNumberToPath = map[int]string{}
|
||||
m.lines = m.print(m.json, 1, 0, 0, "", false)
|
||||
if m.pathToLineNumber == nil {
|
||||
m.pathToLineNumber = newDict()
|
||||
} else {
|
||||
m.pathToLineNumber = newDictOfCapacity(cap(m.pathToLineNumber.keys))
|
||||
}
|
||||
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()
|
||||
@ -470,3 +482,57 @@ func (m *model) collapseRecursively(path string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.pathToLineNumber.keys)-1 { // scroll till last element in m.pathToLineNumber
|
||||
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.pathToLineNumber.keys) {
|
||||
m.cursor = len(m.pathToLineNumber.keys) - 1
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) up() {
|
||||
m.showCursor = true
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
}
|
||||
if m.cursor >= len(m.pathToLineNumber.keys) {
|
||||
m.cursor = len(m.pathToLineNumber.keys) - 1
|
||||
}
|
||||
}
|
||||
|
336
print.go
336
print.go
@ -1,192 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func prettyPrint(v interface{}, level int) string {
|
||||
func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path string, selectableValues bool) []string {
|
||||
ident := strings.Repeat(" ", level)
|
||||
subident := strings.Repeat(" ", level-1)
|
||||
connect := func(path string, lineNumber int) {
|
||||
m.pathToLineNumber.set(path, lineNumber)
|
||||
m.lineNumberToPath[lineNumber] = path
|
||||
}
|
||||
sri := m.searchResultsIndex[path]
|
||||
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
return colors.null.Render("null")
|
||||
return []string{merge(m.explode("null", sri.value, colors.null, path, selectableValues))}
|
||||
|
||||
case bool:
|
||||
if v.(bool) {
|
||||
return colors.boolean.Render("true")
|
||||
return []string{merge(m.explode("true", sri.value, colors.boolean, path, selectableValues))}
|
||||
} else {
|
||||
return colors.boolean.Render("false")
|
||||
return []string{merge(m.explode("false", sri.value, colors.boolean, path, selectableValues))}
|
||||
}
|
||||
|
||||
case json.Number:
|
||||
return colors.number.Render(v.(json.Number).String())
|
||||
case number:
|
||||
return []string{merge(m.explode(v.(number).String(), sri.value, colors.number, path, selectableValues))}
|
||||
|
||||
case string:
|
||||
return colors.string.Render(fmt.Sprintf("%q", v))
|
||||
|
||||
case *dict:
|
||||
keys := v.(*dict).keys
|
||||
if len(keys) == 0 {
|
||||
return colors.bracket.Render("{}")
|
||||
}
|
||||
output := colors.bracket.Render("{\n")
|
||||
for i, k := range keys {
|
||||
key := colors.key.Render(fmt.Sprintf("%q", k))
|
||||
value, _ := v.(*dict).get(k)
|
||||
delim := ": "
|
||||
line := ident + key + delim + prettyPrint(value, level+1)
|
||||
if i < len(keys)-1 {
|
||||
line += ",\n"
|
||||
} else {
|
||||
line += "\n"
|
||||
}
|
||||
output += line
|
||||
}
|
||||
return output + subident + colors.bracket.Render("}")
|
||||
|
||||
case array:
|
||||
slice := v.(array)
|
||||
if len(slice) == 0 {
|
||||
return colors.bracket.Render("[]")
|
||||
}
|
||||
output := colors.bracket.Render("[\n")
|
||||
for i, value := range v.(array) {
|
||||
line := ident + prettyPrint(value, level+1)
|
||||
if i < len(slice)-1 {
|
||||
line += ",\n"
|
||||
} else {
|
||||
line += "\n"
|
||||
}
|
||||
output += line
|
||||
}
|
||||
return output + subident + colors.bracket.Render("]")
|
||||
|
||||
default:
|
||||
return "unknown type"
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path string, dontHighlightCursor bool) []string {
|
||||
ident := strings.Repeat(" ", level)
|
||||
subident := strings.Repeat(" ", level-1)
|
||||
|
||||
cursorOr := func(style lipgloss.Style) lipgloss.Style {
|
||||
if m.cursorPath() == path && !dontHighlightCursor && m.showCursor {
|
||||
return colors.cursor
|
||||
}
|
||||
return style
|
||||
}
|
||||
searchStyle := colors.search.Render
|
||||
if m.resultsCursorPath() == path {
|
||||
searchStyle = colors.cursor.Render
|
||||
}
|
||||
highlight := func(line string, style func(s string) string) string {
|
||||
chunks := m.split(line, path)
|
||||
return mergeChunks(chunks, style, searchStyle)
|
||||
}
|
||||
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
line := highlight("null", cursorOr(colors.null).Render)
|
||||
return []string{line}
|
||||
|
||||
case bool:
|
||||
line := highlight(stringify(v), cursorOr(colors.boolean).Render)
|
||||
return []string{line}
|
||||
|
||||
case json.Number:
|
||||
line := highlight(v.(json.Number).String(), cursorOr(colors.number).Render)
|
||||
return []string{line}
|
||||
|
||||
case string:
|
||||
stringStyle := cursorOr(colors.string).Render
|
||||
line := fmt.Sprintf("%q", v)
|
||||
chunks := m.split(line, path)
|
||||
chunks := m.explode(line, sri.value, colors.string, path, selectableValues)
|
||||
if m.wrap && keyEndPos+width(line) > m.width {
|
||||
return wrapLines(chunks, keyEndPos, m.width, subident, stringStyle, searchStyle)
|
||||
return wrapLines(chunks, keyEndPos, m.width, subident)
|
||||
}
|
||||
return []string{mergeChunks(chunks, stringStyle, searchStyle)}
|
||||
// No wrap
|
||||
return []string{merge(chunks)}
|
||||
|
||||
case *dict:
|
||||
m.pathToLineNumber.set(path, lineNumber)
|
||||
m.lineNumberToPath[lineNumber] = path
|
||||
bracketStyle := cursorOr(colors.bracket).Render
|
||||
if len(v.(*dict).keys) == 0 {
|
||||
return []string{bracketStyle("{}")}
|
||||
}
|
||||
connect(path, lineNumber)
|
||||
if !m.expandedPaths[path] {
|
||||
return []string{m.preview(v, path, dontHighlightCursor)}
|
||||
return []string{m.preview(v, path, selectableValues)}
|
||||
}
|
||||
output := []string{bracketStyle("{")}
|
||||
output := []string{m.printOpenBracket("{", sri, path, selectableValues)}
|
||||
lineNumber++ // bracket is on separate line
|
||||
keys := v.(*dict).keys
|
||||
for i, k := range keys {
|
||||
subpath := path + "." + k
|
||||
m.pathToLineNumber.set(subpath, lineNumber)
|
||||
m.lineNumberToPath[lineNumber] = subpath
|
||||
keyStyle := colors.key.Render
|
||||
if m.cursorPath() == subpath && m.showCursor {
|
||||
keyStyle = colors.cursor.Render
|
||||
}
|
||||
s := m.searchResultsIndex[subpath]
|
||||
connect(subpath, lineNumber)
|
||||
key := fmt.Sprintf("%q", k)
|
||||
{
|
||||
var indexes [][]int
|
||||
if m.searchResults != nil {
|
||||
sr, ok := m.searchResults.get(subpath)
|
||||
if ok {
|
||||
indexes = sr.(searchResult).key
|
||||
}
|
||||
}
|
||||
chunks := explode(key, indexes)
|
||||
searchStyle := colors.search.Render
|
||||
if m.resultsCursorPath() == subpath && !m.showCursor {
|
||||
searchStyle = colors.cursor.Render
|
||||
}
|
||||
key = mergeChunks(chunks, keyStyle, searchStyle)
|
||||
}
|
||||
key = merge(m.explode(key, s.key, colors.key, subpath, true))
|
||||
value, _ := v.(*dict).get(k)
|
||||
delim := ": "
|
||||
delim := m.printDelim(": ", s)
|
||||
keyEndPos := width(ident) + width(key) + width(delim)
|
||||
lines := m.print(value, level+1, lineNumber, keyEndPos, subpath, true)
|
||||
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] += ","
|
||||
lines[len(lines)-1] += m.printComma(",", s)
|
||||
}
|
||||
output = append(output, lines...)
|
||||
lineNumber += len(lines)
|
||||
}
|
||||
output = append(output, subident+colors.bracket.Render("}"))
|
||||
output = append(output, subident+m.printCloseBracket("}", sri, path, false))
|
||||
return output
|
||||
|
||||
case array:
|
||||
m.pathToLineNumber.set(path, lineNumber)
|
||||
m.lineNumberToPath[lineNumber] = path
|
||||
bracketStyle := cursorOr(colors.bracket).Render
|
||||
if len(v.(array)) == 0 {
|
||||
return []string{bracketStyle("[]")}
|
||||
}
|
||||
connect(path, lineNumber)
|
||||
if !m.expandedPaths[path] {
|
||||
return []string{bracketStyle(m.preview(v, path, dontHighlightCursor))}
|
||||
return []string{m.preview(v, path, selectableValues)}
|
||||
}
|
||||
output := []string{bracketStyle("[")}
|
||||
output := []string{m.printOpenBracket("[", sri, path, selectableValues)}
|
||||
lineNumber++ // bracket is on separate line
|
||||
slice := v.(array)
|
||||
for i, value := range slice {
|
||||
subpath := fmt.Sprintf("%v[%v]", path, i)
|
||||
m.pathToLineNumber.set(subpath, lineNumber)
|
||||
m.lineNumberToPath[lineNumber] = subpath
|
||||
lines := m.print(value, level+1, lineNumber, width(ident), subpath, false)
|
||||
s := m.searchResultsIndex[subpath]
|
||||
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] += ","
|
||||
lines[len(lines)-1] += m.printComma(",", s)
|
||||
}
|
||||
lineNumber += len(lines)
|
||||
output = append(output, lines...)
|
||||
}
|
||||
output = append(output, subident+colors.bracket.Render("]"))
|
||||
output = append(output, subident+m.printCloseBracket("]", sri, path, false))
|
||||
return output
|
||||
|
||||
default:
|
||||
@ -194,20 +94,15 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) preview(v interface{}, path string, dontHighlightCursor bool) string {
|
||||
cursorOr := func(style lipgloss.Style) lipgloss.Style {
|
||||
if m.cursorPath() == path && !dontHighlightCursor {
|
||||
return colors.cursor
|
||||
}
|
||||
return style
|
||||
func (m *model) preview(v interface{}, path string, selectableValues bool) string {
|
||||
searchResult := m.searchResultsIndex[path]
|
||||
previewStyle := colors.preview
|
||||
if selectableValues && m.cursorPath() == path {
|
||||
previewStyle = colors.cursor
|
||||
}
|
||||
|
||||
bracketStyle := cursorOr(colors.bracket)
|
||||
previewStyle := cursorOr(colors.preview)
|
||||
|
||||
printValue := func(value interface{}) string {
|
||||
switch value.(type) {
|
||||
case nil, bool, json.Number:
|
||||
case nil, bool, number:
|
||||
return previewStyle.Render(fmt.Sprintf("%v", value))
|
||||
case string:
|
||||
return previewStyle.Render(fmt.Sprintf("%q", value))
|
||||
@ -221,7 +116,7 @@ func (m *model) preview(v interface{}, path string, dontHighlightCursor bool) st
|
||||
|
||||
switch v.(type) {
|
||||
case *dict:
|
||||
output := bracketStyle.Render("{")
|
||||
output := m.printOpenBracket("{", searchResult, path, selectableValues)
|
||||
keys := v.(*dict).keys
|
||||
for _, k := range keys {
|
||||
key := fmt.Sprintf("%q", k)
|
||||
@ -230,47 +125,39 @@ func (m *model) preview(v interface{}, path string, dontHighlightCursor bool) st
|
||||
output += printValue(value)
|
||||
break
|
||||
}
|
||||
if len(keys) == 1 {
|
||||
output += bracketStyle.Render("}")
|
||||
} else {
|
||||
output += bracketStyle.Render(", \u2026}")
|
||||
if len(keys) > 1 {
|
||||
output += previewStyle.Render(", \u2026")
|
||||
}
|
||||
output += m.printCloseBracket("}", searchResult, path, selectableValues)
|
||||
return output
|
||||
|
||||
case array:
|
||||
output := bracketStyle.Render("[")
|
||||
output := m.printOpenBracket("[", searchResult, path, selectableValues)
|
||||
slice := v.(array)
|
||||
for _, value := range slice {
|
||||
output += printValue(value)
|
||||
break
|
||||
}
|
||||
if len(slice) == 1 {
|
||||
output += bracketStyle.Render("]")
|
||||
} else {
|
||||
output += bracketStyle.Render(", \u2026]")
|
||||
if len(slice) > 1 {
|
||||
output += previewStyle.Render(", \u2026")
|
||||
}
|
||||
output += m.printCloseBracket("]", searchResult, path, selectableValues)
|
||||
return output
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
func wrapLines(chunks []string, keyEndPos, mWidth int, subident string, stringStyle, searchStyle func(s string) string) []string {
|
||||
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.
|
||||
style := stringStyle
|
||||
for i, chunk := range chunks {
|
||||
if i%2 == 0 {
|
||||
style = stringStyle
|
||||
} else {
|
||||
style = searchStyle
|
||||
}
|
||||
for _, chunk := range chunks {
|
||||
buffer := ""
|
||||
for _, ch := range chunk {
|
||||
for _, ch := range chunk.value {
|
||||
buffer += string(ch)
|
||||
if pos == mWidth-1 {
|
||||
wrappedLines = append(wrappedLines, ident+currentLine+style(buffer))
|
||||
wrappedLines = append(wrappedLines, ident+currentLine+chunk.Render(buffer))
|
||||
currentLine = ""
|
||||
buffer = ""
|
||||
pos = width(subident) // Start counting from ident.
|
||||
@ -279,7 +166,7 @@ func wrapLines(chunks []string, keyEndPos, mWidth int, subident string, stringSt
|
||||
pos++
|
||||
}
|
||||
}
|
||||
currentLine += style(buffer)
|
||||
currentLine += chunk.Render(buffer)
|
||||
}
|
||||
if width(currentLine) > 0 {
|
||||
wrappedLines = append(wrappedLines, subident+currentLine)
|
||||
@ -287,37 +174,102 @@ func wrapLines(chunks []string, keyEndPos, mWidth int, subident string, stringSt
|
||||
return wrappedLines
|
||||
}
|
||||
|
||||
func (m *model) split(line, path string) []string {
|
||||
var indexes [][]int
|
||||
if m.searchResults != nil {
|
||||
sr, ok := m.searchResults.get(path)
|
||||
if ok {
|
||||
indexes = sr.(searchResult).value
|
||||
}
|
||||
}
|
||||
return explode(line, indexes)
|
||||
func (w withStyle) Render(s string) string {
|
||||
return w.style.Render(s)
|
||||
}
|
||||
|
||||
func explode(s string, indexes [][]int) []string {
|
||||
out := make([]string, 0)
|
||||
pos := 0
|
||||
for _, l := range indexes {
|
||||
out = append(out, s[pos:l[0]])
|
||||
out = append(out, s[l[0]:l[1]])
|
||||
pos = l[1]
|
||||
func (m *model) printOpenBracket(line string, s searchResultGroup, path string, selectableValues bool) string {
|
||||
if selectableValues && m.cursorPath() == path {
|
||||
return colors.cursor.Render(line)
|
||||
}
|
||||
out = append(out, s[pos:])
|
||||
if s.openBracket != nil {
|
||||
if s.openBracket.index == m.searchResultsCursor {
|
||||
return colors.cursor.Render(line)
|
||||
} else {
|
||||
return colors.search.Render(line)
|
||||
}
|
||||
} else {
|
||||
return colors.syntax.Render(line)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) printCloseBracket(line string, s searchResultGroup, path string, selectableValues bool) string {
|
||||
if selectableValues && m.cursorPath() == path {
|
||||
return colors.cursor.Render(line)
|
||||
}
|
||||
if s.closeBracket != nil {
|
||||
if s.closeBracket.index == m.searchResultsCursor {
|
||||
return colors.cursor.Render(line)
|
||||
} else {
|
||||
return colors.search.Render(line)
|
||||
}
|
||||
} else {
|
||||
return colors.syntax.Render(line)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) printDelim(line string, s searchResultGroup) string {
|
||||
if s.delim != nil {
|
||||
if s.delim.index == m.searchResultsCursor {
|
||||
return colors.cursor.Render(line)
|
||||
} else {
|
||||
return colors.search.Render(line)
|
||||
}
|
||||
} else {
|
||||
return colors.syntax.Render(line)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) printComma(line string, s searchResultGroup) string {
|
||||
if s.comma != nil {
|
||||
if s.comma.index == m.searchResultsCursor {
|
||||
return colors.cursor.Render(line)
|
||||
} else {
|
||||
return colors.search.Render(line)
|
||||
}
|
||||
} else {
|
||||
return colors.syntax.Render(line)
|
||||
}
|
||||
}
|
||||
|
||||
type withStyle struct {
|
||||
value string
|
||||
style lipgloss.Style
|
||||
}
|
||||
|
||||
func (m *model) explode(line string, searchResults []*searchResult, defaultStyle lipgloss.Style, path string, selectable bool) []withStyle {
|
||||
if selectable && m.cursorPath() == path {
|
||||
return []withStyle{{line, colors.cursor}}
|
||||
}
|
||||
|
||||
out := make([]withStyle, 0, 1)
|
||||
pos := 0
|
||||
for _, sr := range searchResults {
|
||||
style := colors.search
|
||||
if sr.index == m.searchResultsCursor {
|
||||
style = colors.cursor
|
||||
}
|
||||
out = append(out, withStyle{
|
||||
value: line[pos:sr.start],
|
||||
style: defaultStyle,
|
||||
})
|
||||
out = append(out, withStyle{
|
||||
value: line[sr.start:sr.end],
|
||||
style: style,
|
||||
})
|
||||
pos = sr.end
|
||||
}
|
||||
out = append(out, withStyle{
|
||||
value: line[pos:],
|
||||
style: defaultStyle,
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func mergeChunks(chunks []string, stringStyle, searchStyle func(s string) string) string {
|
||||
currentLine := ""
|
||||
for i, chunk := range chunks {
|
||||
if i%2 == 0 {
|
||||
currentLine += stringStyle(chunk)
|
||||
} else {
|
||||
currentLine += searchStyle(chunk)
|
||||
}
|
||||
func merge(chunks []withStyle) string {
|
||||
out := ""
|
||||
for _, chunk := range chunks {
|
||||
out += chunk.Render(chunk.value)
|
||||
}
|
||||
return currentLine
|
||||
return out
|
||||
}
|
||||
|
62
reduce.go
62
reduce.go
@ -136,3 +136,65 @@ func reduce(object interface{}, args []string) {
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func prettyPrint(v interface{}, level int) string {
|
||||
ident := strings.Repeat(" ", level)
|
||||
subident := strings.Repeat(" ", level-1)
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
return colors.null.Render("null")
|
||||
|
||||
case bool:
|
||||
if v.(bool) {
|
||||
return colors.boolean.Render("true")
|
||||
} else {
|
||||
return colors.boolean.Render("false")
|
||||
}
|
||||
|
||||
case number:
|
||||
return colors.number.Render(v.(number).String())
|
||||
|
||||
case string:
|
||||
return colors.string.Render(fmt.Sprintf("%q", v))
|
||||
|
||||
case *dict:
|
||||
keys := v.(*dict).keys
|
||||
if len(keys) == 0 {
|
||||
return colors.syntax.Render("{}")
|
||||
}
|
||||
output := colors.syntax.Render("{\n")
|
||||
for i, k := range keys {
|
||||
key := colors.key.Render(fmt.Sprintf("%q", k))
|
||||
value, _ := v.(*dict).get(k)
|
||||
delim := ": "
|
||||
line := ident + key + delim + prettyPrint(value, level+1)
|
||||
if i < len(keys)-1 {
|
||||
line += ",\n"
|
||||
} else {
|
||||
line += "\n"
|
||||
}
|
||||
output += line
|
||||
}
|
||||
return output + subident + colors.syntax.Render("}")
|
||||
|
||||
case array:
|
||||
slice := v.(array)
|
||||
if len(slice) == 0 {
|
||||
return colors.syntax.Render("[]")
|
||||
}
|
||||
output := colors.syntax.Render("[\n")
|
||||
for i, value := range v.(array) {
|
||||
line := ident + prettyPrint(value, level+1)
|
||||
if i < len(slice)-1 {
|
||||
line += ",\n"
|
||||
} else {
|
||||
line += "\n"
|
||||
}
|
||||
output += line
|
||||
}
|
||||
return output + subident + colors.syntax.Render("]")
|
||||
|
||||
default:
|
||||
return "unknown type"
|
||||
}
|
||||
}
|
||||
|
195
search.go
195
search.go
@ -1,95 +1,103 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type searchResult struct {
|
||||
key, value [][]int
|
||||
path string
|
||||
index int
|
||||
start, end int
|
||||
}
|
||||
|
||||
func (m *model) doSearch(s string) {
|
||||
re, err := regexp.Compile("(?i)" + s)
|
||||
if err != nil {
|
||||
m.searchRegexCompileError = err.Error()
|
||||
m.searchInput.Blur()
|
||||
return
|
||||
}
|
||||
m.searchRegexCompileError = ""
|
||||
results := newDict()
|
||||
addSearchResult := func(path string, indexes [][]int) {
|
||||
if indexes != nil {
|
||||
sr := searchResult{}
|
||||
prev, ok := results.get(path)
|
||||
if ok {
|
||||
sr = prev.(searchResult)
|
||||
}
|
||||
sr.value = indexes
|
||||
results.set(path, sr)
|
||||
}
|
||||
}
|
||||
type searchResultGroup struct {
|
||||
key []*searchResult
|
||||
value []*searchResult
|
||||
delim *searchResult
|
||||
openBracket *searchResult
|
||||
closeBracket *searchResult
|
||||
comma *searchResult
|
||||
}
|
||||
|
||||
dfs(m.json, func(it iterator) {
|
||||
switch it.object.(type) {
|
||||
case nil:
|
||||
line := "null"
|
||||
found := re.FindAllStringIndex(line, -1)
|
||||
addSearchResult(it.path, found)
|
||||
case bool:
|
||||
line := stringify(it.object)
|
||||
found := re.FindAllStringIndex(line, -1)
|
||||
addSearchResult(it.path, found)
|
||||
case json.Number:
|
||||
line := it.object.(json.Number).String()
|
||||
found := re.FindAllStringIndex(line, -1)
|
||||
addSearchResult(it.path, found)
|
||||
case string:
|
||||
line := fmt.Sprintf("%q", it.object)
|
||||
found := re.FindAllStringIndex(line, -1)
|
||||
addSearchResult(it.path, found)
|
||||
case *dict:
|
||||
keys := it.object.(*dict).keys
|
||||
for _, key := range keys {
|
||||
line := fmt.Sprintf("%q", key)
|
||||
subpath := it.path + "." + key
|
||||
indexes := re.FindAllStringIndex(line, -1)
|
||||
if indexes != nil {
|
||||
sr := searchResult{}
|
||||
prev, ok := results.get(subpath)
|
||||
if ok {
|
||||
sr = prev.(searchResult)
|
||||
}
|
||||
sr.key = indexes
|
||||
results.set(subpath, sr)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
m.searchResults = results
|
||||
m.searchInput.Blur()
|
||||
m.showSearchResults = true
|
||||
m.jumpToSearchResult(0)
|
||||
// TODO: Implement search.
|
||||
// TODO: Uncomment all code blocks.
|
||||
|
||||
func (m *model) doSearch(s string) {
|
||||
//re, err := regexp.Compile("(?i)" + s)
|
||||
//if err != nil {
|
||||
// m.searchRegexCompileError = err.Error()
|
||||
// m.searchInput.Blur()
|
||||
// return
|
||||
//}
|
||||
//m.searchRegexCompileError = ""
|
||||
//results := newDict()
|
||||
//addSearchResult := func(path string, indexes [][]int) {
|
||||
// if indexes != nil {
|
||||
// sr := searchResult{}
|
||||
// prev, ok := results.get(path)
|
||||
// if ok {
|
||||
// sr = prev.(searchResult)
|
||||
// }
|
||||
// sr.value = indexes
|
||||
// results.set(path, sr)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//dfs(m.json, func(it iterator) {
|
||||
// switch it.object.(type) {
|
||||
// case nil:
|
||||
// line := "null"
|
||||
// found := re.FindAllStringIndex(line, -1)
|
||||
// addSearchResult(it.path, found)
|
||||
// case bool:
|
||||
// line := stringify(it.object)
|
||||
// found := re.FindAllStringIndex(line, -1)
|
||||
// addSearchResult(it.path, found)
|
||||
// case number:
|
||||
// line := it.object.(number).String()
|
||||
// found := re.FindAllStringIndex(line, -1)
|
||||
// addSearchResult(it.path, found)
|
||||
// case string:
|
||||
// line := fmt.Sprintf("%q", it.object)
|
||||
// found := re.FindAllStringIndex(line, -1)
|
||||
// addSearchResult(it.path, found)
|
||||
// case *dict:
|
||||
// keys := it.object.(*dict).keys
|
||||
// for _, key := range keys {
|
||||
// line := fmt.Sprintf("%q", key)
|
||||
// subpath := it.path + "." + key
|
||||
// indexes := re.FindAllStringIndex(line, -1)
|
||||
// if indexes != nil {
|
||||
// sr := searchResult{}
|
||||
// prev, ok := results.get(subpath)
|
||||
// if ok {
|
||||
// sr = prev.(searchResult)
|
||||
// }
|
||||
// sr.key = indexes
|
||||
// results.set(subpath, sr)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//})
|
||||
//m.searchResults = results
|
||||
//m.searchInput.Blur()
|
||||
//m.showSearchResults = true
|
||||
//m.jumpToSearchResult(0)
|
||||
}
|
||||
|
||||
func (m *model) jumpToSearchResult(at int) {
|
||||
if m.searchResults == nil || len(m.searchResults.keys) == 0 {
|
||||
return
|
||||
}
|
||||
m.showCursor = false
|
||||
m.resultsCursor = at % len(m.searchResults.keys)
|
||||
desiredPath := m.searchResults.keys[m.resultsCursor]
|
||||
lineNumber, ok := m.pathToLineNumber.get(desiredPath)
|
||||
if ok {
|
||||
m.cursor = m.pathToLineNumber.indexes[desiredPath]
|
||||
m.SetOffset(lineNumber.(int))
|
||||
m.render()
|
||||
} else {
|
||||
m.expandToPath(desiredPath)
|
||||
m.render()
|
||||
m.jumpToSearchResult(at)
|
||||
}
|
||||
//if m.searchResults == nil || len(m.searchResults.keys) == 0 {
|
||||
// return
|
||||
//}
|
||||
//m.showCursor = false
|
||||
//m.searchResultsCursor = at % len(m.searchResults.keys)
|
||||
//desiredPath := m.searchResults.keys[m.searchResultsCursor]
|
||||
//lineNumber, ok := m.pathToLineNumber.get(desiredPath)
|
||||
//if ok {
|
||||
// m.cursor = m.pathToLineNumber.indexes[desiredPath]
|
||||
// m.SetOffset(lineNumber.(int))
|
||||
// m.render()
|
||||
//} else {
|
||||
// m.expandToPath(desiredPath)
|
||||
// m.render()
|
||||
// m.jumpToSearchResult(at)
|
||||
//}
|
||||
}
|
||||
|
||||
func (m *model) expandToPath(path string) {
|
||||
@ -100,20 +108,21 @@ func (m *model) expandToPath(path string) {
|
||||
}
|
||||
|
||||
func (m *model) nextSearchResult() {
|
||||
m.jumpToSearchResult((m.resultsCursor + 1) % len(m.searchResults.keys))
|
||||
//m.jumpToSearchResult((m.searchResultsCursor + 1) % len(m.searchResults.keys))
|
||||
}
|
||||
|
||||
func (m *model) prevSearchResult() {
|
||||
i := m.resultsCursor - 1
|
||||
if i < 0 {
|
||||
i = len(m.searchResults.keys) - 1
|
||||
}
|
||||
m.jumpToSearchResult(i)
|
||||
//i := m.searchResultsCursor - 1
|
||||
//if i < 0 {
|
||||
// i = len(m.searchResults.keys) - 1
|
||||
//}
|
||||
//m.jumpToSearchResult(i)
|
||||
}
|
||||
|
||||
func (m *model) resultsCursorPath() string {
|
||||
if m.searchResults == nil || len(m.searchResults.keys) == 0 {
|
||||
return "?"
|
||||
}
|
||||
return m.searchResults.keys[m.resultsCursor]
|
||||
//if m.searchResults == nil || len(m.searchResults.keys) == 0 {
|
||||
// return "?"
|
||||
//}
|
||||
//return m.searchResults.keys[m.searchResultsCursor]
|
||||
return ""
|
||||
}
|
||||
|
@ -1,42 +1 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_splitByFoundIndexes(t *testing.T) {
|
||||
s := fmt.Sprintf("%q", "0 aaa 123 \"bbb 44\" ccc 5")
|
||||
re := regexp.MustCompile("\"?\\d+\"?")
|
||||
indexes := re.FindAllStringIndex(s, -1)
|
||||
chunks := explode(s, indexes)
|
||||
expected := []string{"", "\"0", " aaa ", "123", " \\\"bbb ", "44", "\\\" ccc ", "5\"", ""}
|
||||
ok := reflect.DeepEqual(chunks, expected)
|
||||
if !ok {
|
||||
t.Errorf(
|
||||
"split error:\n"+
|
||||
" got %v,\n"+
|
||||
" expected %v",
|
||||
stringify(chunks),
|
||||
stringify(expected),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_splitByFoundIndexes_empty(t *testing.T) {
|
||||
s := fmt.Sprintf("%q", "foo")
|
||||
chunks := explode(s, nil)
|
||||
expected := []string{"\"foo\""}
|
||||
ok := reflect.DeepEqual(chunks, expected)
|
||||
if !ok {
|
||||
t.Errorf(
|
||||
"split error:\n"+
|
||||
" got %v,\n"+
|
||||
" expected %v",
|
||||
stringify(chunks),
|
||||
stringify(expected),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
50
stringify.go
Normal file
50
stringify.go
Normal file
@ -0,0 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func stringify(v interface{}) string {
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
return "null"
|
||||
|
||||
case bool:
|
||||
if v.(bool) {
|
||||
return "true"
|
||||
} else {
|
||||
return "false"
|
||||
}
|
||||
|
||||
case number:
|
||||
return v.(number).String()
|
||||
|
||||
case string:
|
||||
return fmt.Sprintf("%q", v)
|
||||
|
||||
case *dict:
|
||||
result := "{"
|
||||
for i, key := range v.(*dict).keys {
|
||||
line := fmt.Sprintf("%q", key) + ": " + stringify(v.(*dict).values[key])
|
||||
if i < len(v.(*dict).keys)-1 {
|
||||
line += ", "
|
||||
}
|
||||
result += line
|
||||
}
|
||||
return result + "}"
|
||||
|
||||
case array:
|
||||
result := "["
|
||||
for i, value := range v.(array) {
|
||||
line := stringify(value)
|
||||
if i < len(v.(array))-1 {
|
||||
line += ", "
|
||||
}
|
||||
result += line
|
||||
}
|
||||
return result + "]"
|
||||
|
||||
default:
|
||||
return "unknown type"
|
||||
}
|
||||
}
|
29
stringify_test.go
Normal file
29
stringify_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import "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)
|
||||
}
|
||||
})
|
||||
}
|
46
util.go
46
util.go
@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
@ -27,51 +26,6 @@ func max(a, b int) int {
|
||||
return b
|
||||
}
|
||||
|
||||
func stringify(v interface{}) string {
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
return "null"
|
||||
|
||||
case bool:
|
||||
if v.(bool) {
|
||||
return "true"
|
||||
} else {
|
||||
return "false"
|
||||
}
|
||||
|
||||
case json.Number:
|
||||
return v.(json.Number).String()
|
||||
|
||||
case string:
|
||||
return fmt.Sprintf("%q", v)
|
||||
|
||||
case *dict:
|
||||
result := "{"
|
||||
for i, key := range v.(*dict).keys {
|
||||
line := fmt.Sprintf("%q", key) + ":" + stringify(v.(*dict).values[key])
|
||||
if i < len(v.(*dict).keys)-1 {
|
||||
line += ","
|
||||
}
|
||||
result += line
|
||||
}
|
||||
return result + "}"
|
||||
|
||||
case array:
|
||||
result := "["
|
||||
for i, value := range v.(array) {
|
||||
line := stringify(value)
|
||||
if i < len(v.(array))-1 {
|
||||
line += ","
|
||||
}
|
||||
result += line
|
||||
}
|
||||
return result + "]"
|
||||
|
||||
default:
|
||||
return "unknown type"
|
||||
}
|
||||
}
|
||||
|
||||
func width(s string) int {
|
||||
return lipgloss.Width(s)
|
||||
}
|
||||
|
18
viewport.go
18
viewport.go
@ -108,3 +108,21 @@ func (m *model) GotoTop() {
|
||||
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…
Reference in New Issue
Block a user