pull/268/head
Anton Medvedev 9 months ago
parent 9282066284
commit 9e247c9cd2
No known key found for this signature in database

@ -122,12 +122,13 @@ func main() {
searchInput.Prompt = "/"
m := &model{
head: head,
top: head,
wrap: true,
fileName: fileName,
digInput: digInput,
searchInput: searchInput,
head: head,
top: head,
wrap: true,
fileName: fileName,
digInput: digInput,
searchInput: searchInput,
searchResultsIndexes: make(map[*node][][]int),
}
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion())
@ -147,8 +148,9 @@ type model struct {
digInput textinput.Model
searchInput textinput.Model
searchError error
searchResults []searchResult
searchResultCursor int
searchResults []*node
searchResultsCursor int
searchResultsIndexes map[*node][][]int
}
func (m *model) Init() tea.Cmd {
@ -227,6 +229,10 @@ func (m *model) handleSearchKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch {
case msg.Type == tea.KeyEscape:
m.searchInput.Blur()
m.searchInput.SetValue("")
m.searchError = nil
m.searchResults = nil
m.searchResultsCursor = 0
case msg.Type == tea.KeyEnter:
m.searchInput.Blur()
@ -373,10 +379,10 @@ func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.searchInput.Focus()
case key.Matches(msg, keyMap.SearchNext):
m.selectSearchResult(m.searchResultCursor + 1)
m.selectSearchResult(m.searchResultsCursor + 1)
case key.Matches(msg, keyMap.SearchPrev):
m.selectSearchResult(m.searchResultCursor - 1)
m.selectSearchResult(m.searchResultsCursor - 1)
}
return m, nil
}
@ -433,7 +439,7 @@ func (m *model) View() string {
n := m.head
printedLines := 0
for i := 0; i < m.viewHeight(); i++ {
for lineNumber := 0; lineNumber < m.viewHeight(); lineNumber++ {
if n == nil {
break
}
@ -441,15 +447,11 @@ func (m *model) View() string {
screen = append(screen, ' ', ' ')
}
valueOrChunk := n.value
if n.chunk != nil {
valueOrChunk = n.chunk
}
selected := m.cursor == i
selected := m.cursor == lineNumber
if n.key != nil {
keyColor := currentTheme.Key
if m.cursor == i {
if m.cursor == lineNumber {
keyColor = currentTheme.Cursor
}
screen = append(screen, keyColor(n.key)...)
@ -457,7 +459,7 @@ func (m *model) View() string {
selected = false // don't highlight the key's value
}
screen = append(screen, prettyPrint(valueOrChunk, selected, n.chunk != nil)...)
screen = append(screen, m.prettyPrint(n, selected)...)
if n.isCollapsed() {
if n.value[0] == '{' {
@ -499,17 +501,16 @@ func (m *model) View() string {
} else if m.searchInput.Value() != "" {
screen = append(screen, '\n')
re, ci := regexCase(m.searchInput.Value())
re = "/" + re + "/"
if ci {
re = "/" + re + "/i"
} else {
re = "/" + re + "/"
re += "i"
}
if m.searchError != nil {
screen = append(screen, flex(m.termWidth, re, m.searchError.Error())...)
} else if len(m.searchResults) == 0 {
screen = append(screen, flex(m.termWidth, re, "not found")...)
} else {
cursor := fmt.Sprintf("found: [%v/%v]", m.searchResultCursor+1, len(m.searchResults))
cursor := fmt.Sprintf("found: [%v/%v]", m.searchResultsCursor+1, len(m.searchResults))
screen = append(screen, flex(m.termWidth, re, cursor)...)
}
}
@ -517,6 +518,60 @@ func (m *model) View() string {
return string(screen)
}
func (m *model) prettyPrint(node *node, selected bool) []byte {
var b []byte
if node.chunk != nil {
b = node.chunk
} else {
b = node.value
}
if len(b) == 0 {
return b
}
var style color
if selected {
style = currentTheme.Cursor
} else if node.chunk != nil {
style = currentTheme.String
} else {
if node.chunk != nil {
style = currentTheme.String
}
switch b[0] {
case '"':
style = currentTheme.String
case 't', 'f':
style = currentTheme.Boolean
case 'n':
style = currentTheme.Null
case '{', '[', '}', ']':
style = currentTheme.Syntax
default:
if isDigit(b[0]) || b[0] == '-' {
style = currentTheme.Number
}
style = noColor
}
}
if indexes, ok := m.searchResultsIndexes[node]; ok {
bb := splitBytesByIndexes(b, indexes)
var out []byte
for i, b := range bb {
if i%2 == 0 {
out = append(out, style(b)...)
} else {
out = append(out, currentTheme.Search(b)...)
}
}
return out
} else {
return style(b)
}
}
func (m *model) viewHeight() int {
if m.searchInput.Focused() || m.searchInput.Value() != "" {
return m.termHeight - 2
@ -591,6 +646,11 @@ func (m *model) selectNode(n *node) {
m.head = n
m.scrollIntoView()
}
parent := n.parent()
for parent != nil {
parent.expand()
parent = parent.parent()
}
}
func (m *model) cursorPath() string {
@ -641,15 +701,10 @@ func (m *model) dig(value string) *node {
return n
}
type searchResult struct {
node *node
index []int
}
func (m *model) search(s string) {
m.searchError = nil
m.searchResults = nil
m.searchResultCursor = 0
m.searchResultsCursor = 0
code, ci := regexCase(s)
if ci {
@ -662,16 +717,48 @@ func (m *model) search(s string) {
return
}
node := m.top
for node != nil {
indexes := re.FindAllIndex(node.value, -1)
for _, index := range indexes {
m.searchResults = append(m.searchResults, searchResult{
node: node,
index: index,
})
n := m.top
for n != nil {
indexes := re.FindAllIndex(n.value, -1)
if len(indexes) > 0 {
for range indexes {
m.searchResults = append(m.searchResults, n)
}
if n.chunk != nil && n.hasChildren() {
// String can be split into chunks, so we need to map the indexes to the chunks.
chunks := [][]byte{n.chunk}
chunkNodes := []*node{n}
var it *node
if n.isCollapsed() {
it = n.collapsed
} else {
it = n.next
}
for it != nil {
chunkNodes = append(chunkNodes, it)
chunks = append(chunks, it.chunk)
if it == n.end {
break
}
it = it.next
}
chunkMatches := splitIndexesToChunks(chunks, indexes)
for i, matches := range chunkMatches {
m.searchResultsIndexes[chunkNodes[i]] = matches
}
} else {
m.searchResultsIndexes[n] = indexes
}
}
if n.isCollapsed() {
n = n.collapsed
} else {
n = n.next
}
node = node.next
}
m.selectSearchResult(0)
@ -687,7 +774,7 @@ func (m *model) selectSearchResult(i int) {
if i >= len(m.searchResults) {
i = 0
}
m.searchResultCursor = i
m.searchResultsCursor = i
result := m.searchResults[i]
m.selectNode(result.node)
m.selectNode(result)
}

@ -0,0 +1,70 @@
package main
type match struct {
start, end int
index int
}
func splitBytesByIndexes(b []byte, indexes []match) [][]byte {
out := make([][]byte, 0, 1)
pos := 0
for _, pair := range indexes {
out = append(out, safeSlice(b, pos, pair[0]))
out = append(out, safeSlice(b, pair[0], pair[1]))
pos = pair[1]
}
out = append(out, safeSlice(b, pos, len(b)))
return out
}
func safeSlice(b []byte, start, end int) []byte {
length := len(b)
if start > length {
start = length
}
if end > length {
end = length
}
if start < 0 {
start = 0
}
if end < 0 {
end = 0
}
return b[start:end]
}
func splitIndexesToChunks(chunks [][]byte, indexes [][]int) (chunkIndexes [][][]int) {
// Initialize a slice for indexes of each chunk
chunkIndexes = make([][][]int, len(chunks))
// Iterate over each index pair from regex results
for _, idx := range indexes {
// Calculate the current position in the whole byte slice
position := 0
for i, chunk := range chunks {
// If start index lies in this chunk
if idx[0] < position+len(chunk) {
// Calculate local start and end for this chunk
localStart := idx[0] - position
localEnd := idx[1] - position
// If the end index also lies in this chunk
if idx[1] <= position+len(chunk) {
chunkIndexes[i] = append(chunkIndexes[i], []int{localStart, localEnd})
break
} else {
// If the end index is outside this chunk, split the index
chunkIndexes[i] = append(chunkIndexes[i], []int{localStart, len(chunk)})
// Adjust the starting index for the next chunk
idx[0] = position + len(chunk)
}
}
position += len(chunk)
}
}
return
}

@ -22,34 +22,6 @@ func max(i, j int) int {
return j
}
func prettyPrint(b []byte, selected bool, isChunk bool) []byte {
if len(b) == 0 {
return b
}
if selected {
return currentTheme.Cursor(b)
} else {
if isChunk {
return currentTheme.String(b)
}
switch b[0] {
case '"':
return currentTheme.String(b)
case 't', 'f':
return currentTheme.Boolean(b)
case 'n':
return currentTheme.Null(b)
case '{', '[', '}', ']':
return currentTheme.Syntax(b)
default:
if isDigit(b[0]) || b[0] == '-' {
return currentTheme.Number(b)
}
return noColor(b)
}
}
}
func regexCase(code string) (string, bool) {
if strings.HasSuffix(code, "/i") {
return code[:len(code)-2], true

Loading…
Cancel
Save