You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fx/print.go

276 lines
7.5 KiB
Go

package main
import (
"fmt"
"github.com/charmbracelet/lipgloss"
"strings"
)
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 []string{merge(m.explode("null", sri.value, colors.null, path, selectableValues))}
case bool:
if v.(bool) {
return []string{merge(m.explode("true", sri.value, colors.boolean, path, selectableValues))}
} else {
return []string{merge(m.explode("false", sri.value, colors.boolean, path, selectableValues))}
}
case number:
return []string{merge(m.explode(v.(number).String(), sri.value, colors.number, path, selectableValues))}
case string:
line := fmt.Sprintf("%q", v)
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)
}
// No wrap
return []string{merge(chunks)}
case *dict:
connect(path, lineNumber)
if !m.expandedPaths[path] {
return []string{m.preview(v, path, selectableValues)}
}
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
s := m.searchResultsIndex[subpath]
connect(subpath, lineNumber)
key := fmt.Sprintf("%q", k)
key = merge(m.explode(key, s.key, colors.key, subpath, true))
value, _ := v.(*dict).get(k)
delim := m.printDelim(": ", s)
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)
}
output = append(output, lines...)
lineNumber += len(lines)
}
output = append(output, subident+m.printCloseBracket("}", sri, path, false))
return output
case array:
connect(path, lineNumber)
if !m.expandedPaths[path] {
return []string{m.preview(v, path, selectableValues)}
}
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)
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] += m.printComma(",", s)
}
lineNumber += len(lines)
output = append(output, lines...)
}
output = append(output, subident+m.printCloseBracket("]", sri, path, false))
return output
default:
return []string{"unknown type"}
}
}
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
}
printValue := func(value interface{}) string {
switch value.(type) {
case nil, bool, number:
return previewStyle.Render(fmt.Sprintf("%v", value))
case string:
return previewStyle.Render(fmt.Sprintf("%q", value))
case *dict:
return previewStyle.Render("{\u2026}")
case array:
return previewStyle.Render("[\u2026]")
}
return "..."
}
switch v.(type) {
case *dict:
output := m.printOpenBracket("{", searchResult, path, selectableValues)
keys := v.(*dict).keys
for _, k := range keys {
key := fmt.Sprintf("%q", k)
output += previewStyle.Render(key + ": ")
value, _ := v.(*dict).get(k)
output += printValue(value)
break
}
if len(keys) > 1 {
output += previewStyle.Render(", \u2026")
}
output += m.printCloseBracket("}", searchResult, path, selectableValues)
return output
case array:
output := m.printOpenBracket("[", searchResult, path, selectableValues)
slice := v.(array)
for _, value := range slice {
output += printValue(value)
break
}
if len(slice) > 1 {
output += previewStyle.Render(", \u2026")
}
output += m.printCloseBracket("]", searchResult, path, selectableValues)
return output
}
return "?"
}
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.
for _, chunk := range chunks {
buffer := ""
for _, ch := range chunk.value {
buffer += string(ch)
if pos == mWidth-1 {
wrappedLines = append(wrappedLines, ident+currentLine+chunk.Render(buffer))
currentLine = ""
buffer = ""
pos = width(subident) // Start counting from ident.
ident = subident // After first line, add ident to all.
} else {
pos++
}
}
currentLine += chunk.Render(buffer)
}
if width(currentLine) > 0 {
wrappedLines = append(wrappedLines, subident+currentLine)
}
return wrappedLines
}
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 {
if selectableValues && m.cursorPath() == path {
return colors.cursor.Render(line)
}
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 merge(chunks []withStyle) string {
out := ""
for _, chunk := range chunks {
out += chunk.Render(chunk.value)
}
return out
}