Refactor search to use search ranges

sleep-stdin-bug
Anton Medvedev 2 years ago
parent 841eb4f1b5
commit 68212c0c7a

@ -6,18 +6,22 @@ require (
github.com/charmbracelet/bubbles v0.10.3
github.com/charmbracelet/bubbletea v0.20.0
github.com/charmbracelet/lipgloss v0.5.0
github.com/stretchr/testify v1.7.1
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
)
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

@ -12,6 +12,8 @@ github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
@ -33,18 +35,27 @@ github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -10,6 +10,7 @@ import (
"golang.org/x/term"
"os"
"path"
"regexp"
"strings"
)
@ -113,11 +114,22 @@ func main() {
}
m.collectSiblings(m.json, "")
// DEBUG START
re, _ := regexp.Compile("\"[\\w\\s]+\"")
s := stringify(m.json)
indexes := re.FindAllStringIndex(s, -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
m.indexSearchResults()
searchResults := m.searchResults
highlightIndex := m.highlightIndex
fmt.Println(searchResults, highlightIndex)
// DEBUG END
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 {
@ -154,7 +166,7 @@ type model struct {
showSearchResults bool
searchResults []*searchResult
searchResultsCursor int
searchResultsIndex map[string]searchResultGroup
highlightIndex map[string]*rangeGroup
}
func (m *model) Init() tea.Cmd {

@ -20,25 +20,29 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
m.connect(path, lineNumber)
ident := strings.Repeat(" ", level)
subident := strings.Repeat(" ", level-1)
sri := m.searchResultsIndex[path]
highlight := m.highlightIndex[path]
var searchValue []*foundRange
if highlight != nil {
searchValue = highlight.value
}
switch v.(type) {
case nil:
return []string{merge(m.explode("null", sri.value, colors.null, path, selectableValues))}
return []string{merge(m.explode("null", searchValue, colors.null, path, selectableValues))}
case bool:
if v.(bool) {
return []string{merge(m.explode("true", sri.value, colors.boolean, path, selectableValues))}
return []string{merge(m.explode("true", searchValue, colors.boolean, path, selectableValues))}
} else {
return []string{merge(m.explode("false", sri.value, colors.boolean, path, selectableValues))}
return []string{merge(m.explode("false", searchValue, colors.boolean, path, selectableValues))}
}
case number:
return []string{merge(m.explode(v.(number).String(), sri.value, colors.number, path, selectableValues))}
return []string{merge(m.explode(v.(number).String(), searchValue, colors.number, path, selectableValues))}
case string:
line := fmt.Sprintf("%q", v)
chunks := m.explode(line, sri.value, colors.string, path, selectableValues)
chunks := m.explode(line, searchValue, colors.string, path, selectableValues)
if m.wrap && keyEndPos+width(line) > m.width {
return wrapLines(chunks, keyEndPos, m.width, subident)
}
@ -49,39 +53,43 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
if !m.expandedPaths[path] {
return []string{m.preview(v, path, selectableValues)}
}
output := []string{m.printOpenBracket("{", sri, path, selectableValues)}
output := []string{m.printOpenBracket("{", highlight, path, selectableValues)}
lineNumber++ // bracket is on separate line
keys := v.(*dict).keys
for i, k := range keys {
subpath := path + "." + k
s := m.searchResultsIndex[subpath]
highlight := m.highlightIndex[subpath]
var searchKey []*foundRange
if highlight != nil {
searchKey = highlight.key
}
m.connect(subpath, lineNumber)
key := fmt.Sprintf("%q", k)
key = merge(m.explode(key, s.key, colors.key, subpath, true))
key = merge(m.explode(key, searchKey, colors.key, subpath, true))
value, _ := v.(*dict).get(k)
delim := m.printDelim(": ", s)
delim := m.printDelim(": ", highlight)
keyEndPos := width(ident) + width(key) + width(delim)
lines := m.print(value, level+1, lineNumber, keyEndPos, subpath, false)
lines[0] = ident + key + delim + lines[0]
if i < len(keys)-1 {
lines[len(lines)-1] += m.printComma(",", s)
lines[len(lines)-1] += m.printComma(",", highlight)
}
output = append(output, lines...)
lineNumber += len(lines)
}
output = append(output, subident+m.printCloseBracket("}", sri, path, false))
output = append(output, subident+m.printCloseBracket("}", highlight, path, false))
return output
case array:
if !m.expandedPaths[path] {
return []string{m.preview(v, path, selectableValues)}
}
output := []string{m.printOpenBracket("[", sri, path, selectableValues)}
output := []string{m.printOpenBracket("[", highlight, path, selectableValues)}
lineNumber++ // bracket is on separate line
slice := v.(array)
for i, value := range slice {
subpath := fmt.Sprintf("%v[%v]", path, i)
s := m.searchResultsIndex[subpath]
s := m.highlightIndex[subpath]
m.connect(subpath, lineNumber)
lines := m.print(value, level+1, lineNumber, width(ident), subpath, true)
lines[0] = ident + lines[0]
@ -91,7 +99,7 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
lineNumber += len(lines)
output = append(output, lines...)
}
output = append(output, subident+m.printCloseBracket("]", sri, path, false))
output = append(output, subident+m.printCloseBracket("]", highlight, path, false))
return output
default:
@ -100,7 +108,7 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
}
func (m *model) preview(v interface{}, path string, selectableValues bool) string {
searchResult := m.searchResultsIndex[path]
searchResult := m.highlightIndex[path]
previewStyle := colors.preview
if selectableValues && m.cursorPath() == path {
previewStyle = colors.cursor
@ -183,12 +191,12 @@ func (w withStyle) Render(s string) string {
return w.style.Render(s)
}
func (m *model) printOpenBracket(line string, s searchResultGroup, path string, selectableValues bool) string {
func (m *model) printOpenBracket(line string, s *rangeGroup, path string, selectableValues bool) string {
if selectableValues && m.cursorPath() == path {
return colors.cursor.Render(line)
}
if s.openBracket != nil {
if s.openBracket.index == m.searchResultsCursor {
if s != nil && s.openBracket != nil {
if s.openBracket.parent.index == m.searchResultsCursor {
return colors.cursor.Render(line)
} else {
return colors.search.Render(line)
@ -198,12 +206,12 @@ func (m *model) printOpenBracket(line string, s searchResultGroup, path string,
}
}
func (m *model) printCloseBracket(line string, s searchResultGroup, path string, selectableValues bool) string {
func (m *model) printCloseBracket(line string, s *rangeGroup, 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 {
if s != nil && s.closeBracket != nil {
if s.closeBracket.parent.index == m.searchResultsCursor {
return colors.cursor.Render(line)
} else {
return colors.search.Render(line)
@ -213,9 +221,9 @@ func (m *model) printCloseBracket(line string, s searchResultGroup, path string,
}
}
func (m *model) printDelim(line string, s searchResultGroup) string {
if s.delim != nil {
if s.delim.index == m.searchResultsCursor {
func (m *model) printDelim(line string, s *rangeGroup) string {
if s != nil && s.delim != nil {
if s.delim.parent.index == m.searchResultsCursor {
return colors.cursor.Render(line)
} else {
return colors.search.Render(line)
@ -225,9 +233,9 @@ func (m *model) printDelim(line string, s searchResultGroup) string {
}
}
func (m *model) printComma(line string, s searchResultGroup) string {
if s.comma != nil {
if s.comma.index == m.searchResultsCursor {
func (m *model) printComma(line string, s *rangeGroup) string {
if s != nil && s.comma != nil {
if s.comma.parent.index == m.searchResultsCursor {
return colors.cursor.Render(line)
} else {
return colors.search.Render(line)
@ -242,27 +250,27 @@ type withStyle struct {
style lipgloss.Style
}
func (m *model) explode(line string, searchResults []*searchResult, defaultStyle lipgloss.Style, path string, selectable bool) []withStyle {
func (m *model) explode(line string, highlightRanges []*foundRange, 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 {
for _, r := range highlightRanges {
style := colors.search
if sr.index == m.searchResultsCursor {
if r.parent.index == m.searchResultsCursor {
style = colors.cursor
}
out = append(out, withStyle{
value: line[pos:sr.start],
value: line[pos:r.start],
style: defaultStyle,
})
out = append(out, withStyle{
value: line[sr.start:sr.end],
value: line[r.start:r.end],
style: style,
})
pos = sr.end
pos = r.end
}
out = append(out, withStyle{
value: line[pos:],

@ -1,84 +1,169 @@
package main
import (
"fmt"
"regexp"
)
type searchResult struct {
path string
index int
start, end int
path string
index int
ranges []*foundRange
}
type searchResultGroup struct {
key []*searchResult
value []*searchResult
delim *searchResult
openBracket *searchResult
closeBracket *searchResult
comma *searchResult
type rangeKind int
const (
keyRange rangeKind = 1 + iota
valueRange
delimRange
openBracketRange
closeBracketRange
commaRange
)
type foundRange struct {
parent *searchResult
start, end int
kind rangeKind
}
// TODO: Implement search.
// TODO: Uncomment all code blocks.
type rangeGroup struct {
key []*foundRange
value []*foundRange
delim *foundRange
openBracket *foundRange
closeBracket *foundRange
comma *foundRange
}
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)
m.searchRegexCompileError = ""
re, err := regexp.Compile("(?i)" + s)
if err != nil {
m.searchRegexCompileError = err.Error()
m.searchInput.Blur()
return
}
indexes := re.FindAllStringIndex(stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
m.indexSearchResults()
m.searchInput.Blur()
m.showSearchResults = true
m.jumpToSearchResult(0)
}
func (m *model) remapSearchResult(object interface{}, path string, pos int, indexes [][]int, id int, current *searchResult) (int, int, *searchResult) {
switch object.(type) {
case nil:
return pos + len("null"), id, current
case bool:
if object.(bool) {
return pos + len("true"), id, current
} else {
return pos + len("false"), id, current
}
case number:
return pos + len(object.(number).String()), id, current
case string:
s := fmt.Sprintf("%q", object)
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
return pos + len(s), id, current
case *dict:
pos++ // {
for i, k := range object.(*dict).keys {
subpath := path + "." + k
key := fmt.Sprintf("%q", k)
id, current = m.findRanges(keyRange, key, subpath, pos, indexes, id, current)
pos += len(key)
delim := ": "
pos += len(delim)
pos, id, current = m.remapSearchResult(object.(*dict).values[k], subpath, pos, indexes, id, current)
if i < len(object.(*dict).keys)-1 {
pos += len(", ")
}
}
pos++ // }
return pos, id, current
case array:
pos++ // [
for i, v := range object.(array) {
subpath := fmt.Sprintf("%v[%v]", path, i)
pos, id, current = m.remapSearchResult(v, subpath, pos, indexes, id, current)
if i < len(object.(array))-1 {
pos += len(", ")
}
}
pos++ // ]
return pos, id, current
default:
panic("unexpected object type")
}
}
func (m *model) findRanges(kind rangeKind, s string, path string, pos int, indexes [][]int, id int, current *searchResult) (int, *searchResult) {
for ; id < len(indexes); id++ {
start, end := indexes[id][0]-pos, indexes[id][1]-pos
if end <= 0 {
current = nil
continue
}
if start < len(s) {
if current == nil {
current = &searchResult{
path: path,
index: len(m.searchResults),
}
m.searchResults = append(m.searchResults, current)
}
found := &foundRange{
parent: current,
start: max(start, 0),
end: min(end, len(s)),
kind: kind,
}
current.ranges = append(current.ranges, found)
if end < len(s) {
current = nil
} else {
break
}
} else {
break
}
}
return id, current
}
func (m *model) indexSearchResults() {
m.highlightIndex = map[string]*rangeGroup{}
for _, s := range m.searchResults {
for _, r := range s.ranges {
highlight, exist := m.highlightIndex[r.parent.path]
if !exist {
highlight = &rangeGroup{}
m.highlightIndex[r.parent.path] = highlight
}
switch r.kind {
case keyRange:
highlight.key = append(highlight.key, r)
case valueRange:
highlight.value = append(highlight.value, r)
case delimRange:
highlight.delim = r
case openBracketRange:
highlight.openBracket = r
case closeBracketRange:
highlight.closeBracket = r
case commaRange:
highlight.comma = r
}
}
}
}
func (m *model) jumpToSearchResult(at int) {

@ -1 +1,105 @@
package main
import (
"github.com/stretchr/testify/require"
"regexp"
"testing"
)
func Test_model_remapSearchResult_array(t *testing.T) {
msg := `
["first", "second"]
^^^^^ ^^^^^^
`
m := &model{
json: array{"first", "second"},
}
re, _ := regexp.Compile("\\w+")
indexes := re.FindAllStringIndex(stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s1 := &searchResult{path: "[0]"}
s1.ranges = append(s1.ranges,
&foundRange{
parent: s1,
start: 1,
end: 6,
kind: valueRange,
},
)
s2 := &searchResult{path: "[1]", index: 1}
s2.ranges = append(s2.ranges,
&foundRange{
parent: s2,
start: 1,
end: 7,
kind: valueRange,
},
)
require.Equal(t, []*searchResult{s1, s2}, m.searchResults, msg)
}
func Test_model_remapSearchResult_between_array(t *testing.T) {
msg := `
["first", "second"]
^^^^^^^^^^^^^^^
`
m := &model{
json: array{"first", "second"},
}
re, _ := regexp.Compile("\\w.+\\w")
indexes := re.FindAllStringIndex(stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s := &searchResult{path: "[0]"}
s.ranges = append(s.ranges,
&foundRange{
parent: s,
start: 1,
end: 7,
kind: valueRange,
},
&foundRange{
parent: s,
start: 0,
end: 7,
kind: valueRange,
},
)
require.Equal(t, []*searchResult{s}, m.searchResults, msg)
}
func Test_model_remapSearchResult_dict(t *testing.T) {
msg := `
{"key": "hello world"}
^^^^^ ^^^^^^^^^^^^^
`
d := newDict()
d.set("key", "hello world")
m := &model{
json: d,
}
re, _ := regexp.Compile("\"[\\w\\s]+\"")
indexes := re.FindAllStringIndex(stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s1 := &searchResult{path: ".key"}
s1.ranges = append(s1.ranges,
&foundRange{
parent: s1,
start: 0,
end: 5,
kind: keyRange,
},
)
s2 := &searchResult{path: ".key", index: 1}
s2.ranges = append(s2.ranges,
&foundRange{
parent: s2,
start: 0,
end: 13,
kind: valueRange,
},
)
require.Equal(t, []*searchResult{s1, s2}, m.searchResults, msg)
}

Loading…
Cancel
Save