2022-03-11 15:15:24 +00:00
|
|
|
package main
|
|
|
|
|
2022-04-07 08:32:34 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
2022-04-29 21:42:56 +00:00
|
|
|
"regexp"
|
|
|
|
|
2022-04-17 20:57:12 +00:00
|
|
|
. "github.com/antonmedv/fx/pkg/dict"
|
|
|
|
. "github.com/antonmedv/fx/pkg/json"
|
2022-04-07 08:32:34 +00:00
|
|
|
)
|
|
|
|
|
2022-03-11 15:15:24 +00:00
|
|
|
type searchResult struct {
|
2022-04-07 08:32:34 +00:00
|
|
|
path string
|
|
|
|
index int
|
|
|
|
ranges []*foundRange
|
2022-03-11 15:15:24 +00:00
|
|
|
}
|
|
|
|
|
2022-04-07 08:32:34 +00:00
|
|
|
type rangeKind int
|
|
|
|
|
|
|
|
const (
|
|
|
|
keyRange rangeKind = 1 + iota
|
|
|
|
valueRange
|
|
|
|
delimRange
|
|
|
|
openBracketRange
|
|
|
|
closeBracketRange
|
|
|
|
commaRange
|
|
|
|
)
|
|
|
|
|
|
|
|
type foundRange struct {
|
2022-04-14 12:30:36 +00:00
|
|
|
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
|
2022-04-07 08:32:34 +00:00
|
|
|
start, end int
|
|
|
|
kind rangeKind
|
2022-03-29 16:13:41 +00:00
|
|
|
}
|
|
|
|
|
2022-04-07 08:32:34 +00:00
|
|
|
type rangeGroup struct {
|
|
|
|
key []*foundRange
|
|
|
|
value []*foundRange
|
2022-04-14 12:30:36 +00:00
|
|
|
delim []*foundRange
|
2022-04-07 08:32:34 +00:00
|
|
|
openBracket *foundRange
|
|
|
|
closeBracket *foundRange
|
|
|
|
comma *foundRange
|
|
|
|
}
|
2022-03-11 15:15:24 +00:00
|
|
|
|
2022-04-14 12:30:36 +00:00
|
|
|
func (m *model) clearSearchResults() {
|
2022-04-07 08:32:34 +00:00
|
|
|
m.searchRegexCompileError = ""
|
2022-04-14 12:30:36 +00:00
|
|
|
m.searchResults = nil
|
|
|
|
m.highlightIndex = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *model) doSearch(s string) {
|
|
|
|
m.clearSearchResults()
|
2022-04-07 08:32:34 +00:00
|
|
|
re, err := regexp.Compile("(?i)" + s)
|
|
|
|
if err != nil {
|
|
|
|
m.searchRegexCompileError = err.Error()
|
|
|
|
m.searchInput.Blur()
|
|
|
|
return
|
|
|
|
}
|
2022-04-17 20:57:12 +00:00
|
|
|
indexes := re.FindAllStringIndex(Stringify(m.json), -1)
|
2022-04-07 08:32:34 +00:00
|
|
|
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:
|
2022-04-14 12:30:36 +00:00
|
|
|
s := "null"
|
|
|
|
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
|
|
|
|
return pos + len(s), id, current
|
2022-04-07 08:32:34 +00:00
|
|
|
|
|
|
|
case bool:
|
2022-04-14 12:30:36 +00:00
|
|
|
var s string
|
2022-04-07 08:32:34 +00:00
|
|
|
if object.(bool) {
|
2022-04-14 12:30:36 +00:00
|
|
|
s = "true"
|
2022-04-07 08:32:34 +00:00
|
|
|
} else {
|
2022-04-14 12:30:36 +00:00
|
|
|
s = "false"
|
2022-04-07 08:32:34 +00:00
|
|
|
}
|
2022-04-14 12:30:36 +00:00
|
|
|
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
|
|
|
|
return pos + len(s), id, current
|
2022-04-07 08:32:34 +00:00
|
|
|
|
2022-04-17 20:57:12 +00:00
|
|
|
case Number:
|
|
|
|
s := object.(Number).String()
|
2022-04-14 12:30:36 +00:00
|
|
|
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
|
|
|
|
return pos + len(s), id, current
|
2022-04-07 08:32:34 +00:00
|
|
|
|
|
|
|
case string:
|
2022-04-29 21:42:56 +00:00
|
|
|
// TODO: Wrap the string, save a line number according to current wrap or no wrap mode.
|
2022-04-07 08:32:34 +00:00
|
|
|
s := fmt.Sprintf("%q", object)
|
|
|
|
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
|
|
|
|
return pos + len(s), id, current
|
2022-04-14 12:30:36 +00:00
|
|
|
|
2022-04-17 20:57:12 +00:00
|
|
|
case *Dict:
|
2022-04-14 12:30:36 +00:00
|
|
|
id, current = m.findRanges(openBracketRange, "{", path, pos, indexes, id, current)
|
2022-04-07 08:32:34 +00:00
|
|
|
pos++ // {
|
2022-04-17 20:57:12 +00:00
|
|
|
for i, k := range object.(*Dict).Keys {
|
2022-04-07 08:32:34 +00:00
|
|
|
subpath := path + "." + k
|
2022-04-14 12:30:36 +00:00
|
|
|
|
2022-04-07 08:32:34 +00:00
|
|
|
key := fmt.Sprintf("%q", k)
|
|
|
|
id, current = m.findRanges(keyRange, key, subpath, pos, indexes, id, current)
|
|
|
|
pos += len(key)
|
2022-04-14 12:30:36 +00:00
|
|
|
|
2022-04-07 08:32:34 +00:00
|
|
|
delim := ": "
|
2022-04-14 12:30:36 +00:00
|
|
|
id, current = m.findRanges(delimRange, delim, subpath, pos, indexes, id, current)
|
2022-04-07 08:32:34 +00:00
|
|
|
pos += len(delim)
|
2022-04-14 12:30:36 +00:00
|
|
|
|
2022-04-17 20:57:12 +00:00
|
|
|
pos, id, current = m.remapSearchResult(object.(*Dict).Values[k], subpath, pos, indexes, id, current)
|
|
|
|
if i < len(object.(*Dict).Keys)-1 {
|
2022-04-14 12:30:36 +00:00
|
|
|
comma := ","
|
|
|
|
id, current = m.findRanges(commaRange, comma, subpath, pos, indexes, id, current)
|
|
|
|
pos += len(comma)
|
2022-04-07 08:32:34 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-14 12:30:36 +00:00
|
|
|
id, current = m.findRanges(closeBracketRange, "}", path, pos, indexes, id, current)
|
2022-04-07 08:32:34 +00:00
|
|
|
pos++ // }
|
|
|
|
return pos, id, current
|
|
|
|
|
2022-04-17 20:57:12 +00:00
|
|
|
case Array:
|
2022-04-14 12:30:36 +00:00
|
|
|
id, current = m.findRanges(openBracketRange, "[", path, pos, indexes, id, current)
|
2022-04-07 08:32:34 +00:00
|
|
|
pos++ // [
|
2022-04-17 20:57:12 +00:00
|
|
|
for i, v := range object.(Array) {
|
2022-04-07 08:32:34 +00:00
|
|
|
subpath := fmt.Sprintf("%v[%v]", path, i)
|
|
|
|
pos, id, current = m.remapSearchResult(v, subpath, pos, indexes, id, current)
|
2022-04-17 20:57:12 +00:00
|
|
|
if i < len(object.(Array))-1 {
|
2022-04-14 12:30:36 +00:00
|
|
|
comma := ","
|
|
|
|
id, current = m.findRanges(commaRange, comma, subpath, pos, indexes, id, current)
|
|
|
|
pos += len(comma)
|
2022-04-07 08:32:34 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-14 12:30:36 +00:00
|
|
|
id, current = m.findRanges(closeBracketRange, "]", path, pos, indexes, id, current)
|
2022-04-07 08:32:34 +00:00
|
|
|
pos++ // ]
|
|
|
|
return pos, id, current
|
2022-04-14 12:30:36 +00:00
|
|
|
|
2022-04-07 08:32:34 +00:00
|
|
|
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,
|
2022-04-14 12:30:36 +00:00
|
|
|
path: path,
|
2022-04-07 08:32:34 +00:00
|
|
|
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 {
|
2022-04-14 12:30:36 +00:00
|
|
|
highlight, exist := m.highlightIndex[r.path]
|
2022-04-07 08:32:34 +00:00
|
|
|
if !exist {
|
|
|
|
highlight = &rangeGroup{}
|
2022-04-14 12:30:36 +00:00
|
|
|
m.highlightIndex[r.path] = highlight
|
2022-04-07 08:32:34 +00:00
|
|
|
}
|
|
|
|
switch r.kind {
|
|
|
|
case keyRange:
|
|
|
|
highlight.key = append(highlight.key, r)
|
|
|
|
case valueRange:
|
|
|
|
highlight.value = append(highlight.value, r)
|
|
|
|
case delimRange:
|
2022-04-14 12:30:36 +00:00
|
|
|
highlight.delim = append(highlight.delim, r)
|
2022-04-07 08:32:34 +00:00
|
|
|
case openBracketRange:
|
|
|
|
highlight.openBracket = r
|
|
|
|
case closeBracketRange:
|
|
|
|
highlight.closeBracket = r
|
|
|
|
case commaRange:
|
|
|
|
highlight.comma = r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-11 15:15:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *model) jumpToSearchResult(at int) {
|
2022-04-14 12:30:36 +00:00
|
|
|
if len(m.searchResults) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
m.showCursor = false
|
|
|
|
m.searchResultsCursor = at % len(m.searchResults)
|
|
|
|
desiredPath := m.searchResults[m.searchResultsCursor].path
|
|
|
|
lineNumber, ok := m.pathToLineNumber[desiredPath]
|
|
|
|
if ok {
|
|
|
|
m.cursor = m.pathToIndex[desiredPath]
|
|
|
|
m.SetOffset(lineNumber)
|
|
|
|
m.render()
|
|
|
|
} else {
|
|
|
|
m.expandToPath(desiredPath)
|
|
|
|
m.render()
|
|
|
|
m.jumpToSearchResult(at)
|
|
|
|
}
|
2022-03-11 15:15:24 +00:00
|
|
|
}
|
|
|
|
|
2022-03-28 20:13:48 +00:00
|
|
|
func (m *model) expandToPath(path string) {
|
2022-03-11 15:15:24 +00:00
|
|
|
m.expandedPaths[path] = true
|
|
|
|
if path != "" {
|
2022-03-28 20:13:48 +00:00
|
|
|
m.expandToPath(m.parents[path])
|
2022-03-11 15:15:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *model) nextSearchResult() {
|
2022-04-14 12:30:36 +00:00
|
|
|
if len(m.searchResults) > 0 {
|
|
|
|
m.jumpToSearchResult((m.searchResultsCursor + 1) % len(m.searchResults))
|
|
|
|
}
|
2022-03-11 15:15:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *model) prevSearchResult() {
|
2022-04-14 12:30:36 +00:00
|
|
|
i := m.searchResultsCursor - 1
|
|
|
|
if i < 0 {
|
|
|
|
i = len(m.searchResults) - 1
|
|
|
|
}
|
|
|
|
m.jumpToSearchResult(i)
|
2022-03-11 15:15:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *model) resultsCursorPath() string {
|
2022-04-14 12:30:36 +00:00
|
|
|
if len(m.searchResults) == 0 {
|
|
|
|
return "?"
|
|
|
|
}
|
|
|
|
return m.searchResults[m.searchResultsCursor].path
|
2022-03-11 15:15:24 +00:00
|
|
|
}
|