fx/search.go

250 lines
6.0 KiB
Go
Raw Permalink Normal View History

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
}