Move part of the code to pkg

sleep-stdin-bug
Anton Medvedev 2 years ago
parent aa0964a12e
commit bf166fafda

@ -1,26 +0,0 @@
package main
type dict struct {
keys []string
values map[string]interface{}
}
func newDict() *dict {
return &dict{
keys: make([]string, 0),
values: make(map[string]interface{}),
}
}
func (d *dict) get(key string) (interface{}, bool) {
val, exists := d.values[key]
return val, exists
}
func (d *dict) set(key string, value interface{}) {
_, exists := d.values[key]
if !exists {
d.keys = append(d.keys, key)
}
d.values[key] = value
}

@ -3,21 +3,23 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
"github.com/antonmedv/fx/pkg/reducer"
. "github.com/antonmedv/fx/pkg/theme"
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/muesli/termenv" "github.com/muesli/termenv"
"golang.org/x/term" "golang.org/x/term"
"io/fs"
"os" "os"
"path" "path"
"io/fs"
"runtime/pprof" "runtime/pprof"
"strings" "strings"
) )
type number = json.Number
func main() { func main() {
cpuProfile := os.Getenv("CPU_PROFILE") cpuProfile := os.Getenv("CPU_PROFILE")
if cpuProfile != "" { if cpuProfile != "" {
@ -35,12 +37,12 @@ func main() {
if !ok { if !ok {
themeId = "1" themeId = "1"
} }
theme, ok := themes[themeId] theme, ok := Themes[themeId]
if !ok { if !ok {
theme = themes["1"] theme = Themes["1"]
} }
if termenv.ColorProfile() == termenv.Ascii { if termenv.ColorProfile() == termenv.Ascii {
theme = themes["0"] theme = Themes["0"]
} }
filePath := "" filePath := ""
@ -71,7 +73,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
dec.UseNumber() dec.UseNumber()
jsonObject, err := parse(dec) jsonObject, err := Parse(dec)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -79,17 +81,17 @@ func main() {
tty := isatty.IsTerminal(os.Stdout.Fd()) tty := isatty.IsTerminal(os.Stdout.Fd())
if len(args) > 0 || !tty { if len(args) > 0 || !tty {
if len(args) > 0 && args[0] == "--print-code" { if len(args) > 0 && args[0] == "--print-code" {
fmt.Print(generateCode(args[1:])) fmt.Print(reducer.GenerateCode(args[1:]))
return return
} }
reduce(jsonObject, args, theme) reducer.Reduce(jsonObject, args, theme)
return return
} }
expand := map[string]bool{ expand := map[string]bool{
"": true, "": true,
} }
if array, ok := jsonObject.(array); ok { if array, ok := jsonObject.(Array); ok {
for i := range array { for i := range array {
expand[accessor("", i)] = true expand[accessor("", i)] = true
} }
@ -97,14 +99,14 @@ func main() {
parents := map[string]string{} parents := map[string]string{}
children := map[string][]string{} children := map[string][]string{}
canBeExpanded := map[string]bool{} canBeExpanded := map[string]bool{}
dfs(jsonObject, func(it iterator) { Dfs(jsonObject, func(it Iterator) {
parents[it.path] = it.parent parents[it.Path] = it.Parent
children[it.parent] = append(children[it.parent], it.path) children[it.Parent] = append(children[it.Parent], it.Path)
switch it.object.(type) { switch it.Object.(type) {
case *dict: case *Dict:
canBeExpanded[it.path] = len(it.object.(*dict).keys) > 0 canBeExpanded[it.Path] = len(it.Object.(*Dict).Keys) > 0
case array: case Array:
canBeExpanded[it.path] = len(it.object.(array)) > 0 canBeExpanded[it.Path] = len(it.Object.(Array)) > 0
} }
}) })
@ -152,7 +154,7 @@ type model struct {
json interface{} json interface{}
lines []string lines []string
mouseWheelDelta int // number of lines the mouse wheel will scroll mouseWheelDelta int // Number of lines the mouse wheel will scroll
offset int // offset is the vertical scroll position offset int // offset is the vertical scroll position
keyMap KeyMap keyMap KeyMap
@ -161,9 +163,9 @@ type model struct {
expandedPaths map[string]bool // set of expanded paths expandedPaths map[string]bool // set of expanded paths
canBeExpanded map[string]bool // set of path => can be expanded (i.e. dict or array) canBeExpanded map[string]bool // set of path => can be expanded (i.e. dict or array)
paths []string // array of paths on screen paths []string // array of paths on screen
pathToLineNumber map[string]int // map of path => line number pathToLineNumber map[string]int // map of path => line Number
pathToIndex map[string]int // map of path => index in m.paths pathToIndex map[string]int // map of path => index in m.paths
lineNumberToPath map[int]string // map of line number => path lineNumberToPath map[int]string // map of line Number => path
parents map[string]string // map of subpath => parent path parents map[string]string // map of subpath => parent path
children map[string][]string // map of path => child paths children map[string][]string // map of path => child paths
nextSiblings, prevSiblings map[string]string // map of path => sibling path nextSiblings, prevSiblings map[string]string // map of path => sibling path
@ -342,10 +344,10 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.render() m.render()
case key.Matches(msg, m.keyMap.ExpandAll): case key.Matches(msg, m.keyMap.ExpandAll):
dfs(m.json, func(it iterator) { Dfs(m.json, func(it Iterator) {
switch it.object.(type) { switch it.Object.(type) {
case *dict, array: case *Dict, Array:
m.expandedPaths[it.path] = true m.expandedPaths[it.Path] = true
} }
}) })
m.render() m.render()
@ -400,13 +402,13 @@ func (m *model) View() string {
if m.showHelp { if m.showHelp {
statusBar := "Press Esc or q to close help." statusBar := "Press Esc or q to close help."
statusBar += strings.Repeat(" ", max(0, m.width-width(statusBar))) statusBar += strings.Repeat(" ", max(0, m.width-width(statusBar)))
statusBar = m.theme.statusBar(statusBar) statusBar = m.theme.StatusBar(statusBar)
return strings.Join(lines, "\n") + extraLines + "\n" + statusBar return strings.Join(lines, "\n") + extraLines + "\n" + statusBar
} }
statusBar := m.cursorPath() + " " statusBar := m.cursorPath() + " "
statusBar += strings.Repeat(" ", max(0, m.width-width(statusBar)-width(m.fileName))) statusBar += strings.Repeat(" ", max(0, m.width-width(statusBar)-width(m.fileName)))
statusBar += m.fileName statusBar += m.fileName
statusBar = m.theme.statusBar(statusBar) statusBar = m.theme.StatusBar(statusBar)
output := strings.Join(lines, "\n") + extraLines + "\n" + statusBar output := strings.Join(lines, "\n") + extraLines + "\n" + statusBar
if m.searchInput.Focused() { if m.searchInput.Focused() {
output += "\n/" + m.searchInput.View() output += "\n/" + m.searchInput.View()
@ -506,22 +508,22 @@ func (m *model) collapseRecursively(path string) {
func (m *model) collectSiblings(v interface{}, path string) { func (m *model) collectSiblings(v interface{}, path string) {
switch v.(type) { switch v.(type) {
case *dict: case *Dict:
prev := "" prev := ""
for _, k := range v.(*dict).keys { for _, k := range v.(*Dict).Keys {
subpath := path + "." + k subpath := path + "." + k
if prev != "" { if prev != "" {
m.nextSiblings[prev] = subpath m.nextSiblings[prev] = subpath
m.prevSiblings[subpath] = prev m.prevSiblings[subpath] = prev
} }
prev = subpath prev = subpath
value, _ := v.(*dict).get(k) value, _ := v.(*Dict).Get(k)
m.collectSiblings(value, subpath) m.collectSiblings(value, subpath)
} }
case array: case Array:
prev := "" prev := ""
for i, value := range v.(array) { for i, value := range v.(Array) {
subpath := fmt.Sprintf("%v[%v]", path, i) subpath := fmt.Sprintf("%v[%v]", path, i)
if prev != "" { if prev != "" {
m.nextSiblings[prev] = subpath m.nextSiblings[prev] = subpath

@ -1,50 +0,0 @@
package main
import (
"encoding/json"
"strings"
"testing"
)
func Test_parse(t *testing.T) {
input := `{
"a": 1,
"b": 2,
"a": 3,
"slice": [{"z": "z", "1": "1"}]
}`
p, err := parse(json.NewDecoder(strings.NewReader(input)))
if err != nil {
t.Error("JSON parse error", err)
}
o := p.(*dict)
expectedKeys := []string{
"a",
"b",
"slice",
}
for i := range o.keys {
if o.keys[i] != expectedKeys[i] {
t.Error("Wrong key order ", i, o.keys[i], "!=", expectedKeys[i])
}
}
s, ok := o.get("slice")
if !ok {
t.Error("slice missing")
}
a := s.(array)
z := a[0].(*dict)
expectedKeys = []string{
"z",
"1",
}
for i := range z.keys {
if z.keys[i] != expectedKeys[i] {
t.Error("Wrong key order for nested map ", i, z.keys[i], "!=", expectedKeys[i])
}
}
}

@ -0,0 +1,26 @@
package dict
type Dict struct {
Keys []string
Values map[string]interface{}
}
func NewDict() *Dict {
return &Dict{
Keys: make([]string, 0),
Values: make(map[string]interface{}),
}
}
func (d *Dict) Get(key string) (interface{}, bool) {
val, exists := d.Values[key]
return val, exists
}
func (d *Dict) Set(key string, value interface{}) {
_, exists := d.Values[key]
if !exists {
d.Keys = append(d.Keys, key)
}
d.Values[key] = value
}

@ -1,26 +1,26 @@
package main package dict
import "testing" import "testing"
func Test_dict(t *testing.T) { func Test_dict(t *testing.T) {
d := newDict() d := NewDict()
d.set("number", 3) d.Set("number", 3)
v, _ := d.get("number") v, _ := d.Get("number")
if v.(int) != 3 { if v.(int) != 3 {
t.Error("Set number") t.Error("Set number")
} }
// string // string
d.set("string", "x") d.Set("string", "x")
v, _ = d.get("string") v, _ = d.Get("string")
if v.(string) != "x" { if v.(string) != "x" {
t.Error("Set string") t.Error("Set string")
} }
// string slice // string slice
d.set("strings", []string{ d.Set("strings", []string{
"t", "t",
"u", "u",
}) })
v, _ = d.get("strings") v, _ = d.Get("strings")
if v.([]string)[0] != "t" { if v.([]string)[0] != "t" {
t.Error("Set strings first index") t.Error("Set strings first index")
} }
@ -28,11 +28,11 @@ func Test_dict(t *testing.T) {
t.Error("Set strings second index") t.Error("Set strings second index")
} }
// mixed slice // mixed slice
d.set("mixed", []interface{}{ d.Set("mixed", []interface{}{
1, 1,
"1", "1",
}) })
v, _ = d.get("mixed") v, _ = d.Get("mixed")
if v.([]interface{})[0].(int) != 1 { if v.([]interface{})[0].(int) != 1 {
t.Error("Set mixed int") t.Error("Set mixed int")
} }
@ -40,19 +40,19 @@ func Test_dict(t *testing.T) {
t.Error("Set mixed string") t.Error("Set mixed string")
} }
// overriding existing key // overriding existing key
d.set("number", 4) d.Set("number", 4)
v, _ = d.get("number") v, _ = d.Get("number")
if v.(int) != 4 { if v.(int) != 4 {
t.Error("Override existing key") t.Error("Override existing key")
} }
// keys // Keys
expectedKeys := []string{ expectedKeys := []string{
"number", "number",
"string", "string",
"strings", "strings",
"mixed", "mixed",
} }
for i, key := range d.keys { for i, key := range d.Keys {
if key != expectedKeys[i] { if key != expectedKeys[i] {
t.Error("Keys method", key, "!=", expectedKeys[i]) t.Error("Keys method", key, "!=", expectedKeys[i])
} }

@ -1,10 +1,11 @@
package main package json
import ( import (
"encoding/json" "encoding/json"
. "github.com/antonmedv/fx/pkg/dict"
) )
func parse(dec *json.Decoder) (interface{}, error) { func Parse(dec *json.Decoder) (interface{}, error) {
token, err := dec.Token() token, err := dec.Token()
if err != nil { if err != nil {
return nil, err return nil, err
@ -20,8 +21,8 @@ func parse(dec *json.Decoder) (interface{}, error) {
return token, nil return token, nil
} }
func decodeDict(dec *json.Decoder) (*dict, error) { func decodeDict(dec *json.Decoder) (*Dict, error) {
d := newDict() d := NewDict()
for { for {
token, err := dec.Token() token, err := dec.Token()
if err != nil { if err != nil {
@ -50,14 +51,12 @@ func decodeDict(dec *json.Decoder) (*dict, error) {
} }
} }
} }
d.set(key, value) d.Set(key, value)
} }
} }
type array = []interface{}
func decodeArray(dec *json.Decoder) ([]interface{}, error) { func decodeArray(dec *json.Decoder) ([]interface{}, error) {
slice := make(array, 0) slice := make(Array, 0)
for index := 0; ; index++ { for index := 0; ; index++ {
token, err := dec.Token() token, err := dec.Token()
if err != nil { if err != nil {

@ -0,0 +1,51 @@
package json
import (
"encoding/json"
. "github.com/antonmedv/fx/pkg/dict"
"strings"
"testing"
)
func Test_parse(t *testing.T) {
input := `{
"a": 1,
"b": 2,
"a": 3,
"slice": [{"z": "z", "1": "1"}]
}`
p, err := Parse(json.NewDecoder(strings.NewReader(input)))
if err != nil {
t.Error("JSON parse error", err)
}
o := p.(*Dict)
expectedKeys := []string{
"a",
"b",
"slice",
}
for i := range o.Keys {
if o.Keys[i] != expectedKeys[i] {
t.Error("Wrong key order ", i, o.Keys[i], "!=", expectedKeys[i])
}
}
s, ok := o.Get("slice")
if !ok {
t.Error("slice missing")
}
a := s.(Array)
z := a[0].(*Dict)
expectedKeys = []string{
"z",
"1",
}
for i := range z.Keys {
if z.Keys[i] != expectedKeys[i] {
t.Error("Wrong key order for nested map ", i, z.Keys[i], "!=", expectedKeys[i])
}
}
}

@ -0,0 +1,71 @@
package json
import (
"encoding/json"
"fmt"
"github.com/antonmedv/fx/pkg/dict"
"github.com/antonmedv/fx/pkg/theme"
"strings"
)
func PrettyPrint(v interface{}, level int, theme theme.Theme) string {
ident := strings.Repeat(" ", level)
subident := strings.Repeat(" ", level-1)
switch v.(type) {
case nil:
return theme.Null("null")
case bool:
if v.(bool) {
return theme.Boolean("true")
} else {
return theme.Boolean("false")
}
case json.Number:
return theme.Number(v.(json.Number).String())
case string:
return theme.String(fmt.Sprintf("%q", v))
case *dict.Dict:
keys := v.(*dict.Dict).Keys
if len(keys) == 0 {
return theme.Syntax("{}")
}
output := theme.Syntax("{")
output += "\n"
for i, k := range keys {
key := theme.Key(i, len(keys))(fmt.Sprintf("%q", k))
value, _ := v.(*dict.Dict).Get(k)
delim := theme.Syntax(": ")
line := ident + key + delim + PrettyPrint(value, level+1, theme)
if i < len(keys)-1 {
line += theme.Syntax(",")
}
line += "\n"
output += line
}
return output + subident + theme.Syntax("}")
case []interface{}:
slice := v.([]interface{})
if len(slice) == 0 {
return theme.Syntax("[]")
}
output := theme.Syntax("[\n")
for i, value := range v.([]interface{}) {
line := ident + PrettyPrint(value, level+1, theme)
if i < len(slice)-1 {
line += ",\n"
} else {
line += "\n"
}
output += line
}
return output + subident + theme.Syntax("]")
default:
return "unknown type"
}
}

@ -1,10 +1,11 @@
package main package json
import ( import (
"fmt" "fmt"
. "github.com/antonmedv/fx/pkg/dict"
) )
func stringify(v interface{}) string { func Stringify(v interface{}) string {
switch v.(type) { switch v.(type) {
case nil: case nil:
return "null" return "null"
@ -16,28 +17,28 @@ func stringify(v interface{}) string {
return "false" return "false"
} }
case number: case Number:
return v.(number).String() return v.(Number).String()
case string: case string:
return fmt.Sprintf("%q", v) return fmt.Sprintf("%q", v)
case *dict: case *Dict:
result := "{" result := "{"
for i, key := range v.(*dict).keys { for i, key := range v.(*Dict).Keys {
line := fmt.Sprintf("%q", key) + ": " + stringify(v.(*dict).values[key]) line := fmt.Sprintf("%q", key) + ": " + Stringify(v.(*Dict).Values[key])
if i < len(v.(*dict).keys)-1 { if i < len(v.(*Dict).Keys)-1 {
line += "," line += ","
} }
result += line result += line
} }
return result + "}" return result + "}"
case array: case Array:
result := "[" result := "["
for i, value := range v.(array) { for i, value := range v.(Array) {
line := stringify(value) line := Stringify(value)
if i < len(v.(array))-1 { if i < len(v.(Array))-1 {
line += "," line += ","
} }
result += line result += line

@ -1,28 +1,31 @@
package main package json
import "testing" import (
. "github.com/antonmedv/fx/pkg/dict"
"testing"
)
func Test_stringify(t *testing.T) { func Test_stringify(t *testing.T) {
t.Run("dict", func(t *testing.T) { t.Run("dict", func(t *testing.T) {
arg := newDict() arg := NewDict()
arg.set("a", number("1")) arg.Set("a", Number("1"))
arg.set("b", number("2")) arg.Set("b", Number("2"))
want := `{"a": 1,"b": 2}` want := `{"a": 1,"b": 2}`
if got := stringify(arg); got != want { if got := Stringify(arg); got != want {
t.Errorf("stringify() = %v, want %v", got, want) t.Errorf("stringify() = %v, want %v", got, want)
} }
}) })
t.Run("array", func(t *testing.T) { t.Run("array", func(t *testing.T) {
arg := array{number("1"), number("2")} arg := Array{Number("1"), Number("2")}
want := `[1,2]` want := `[1,2]`
if got := stringify(arg); got != want { if got := Stringify(arg); got != want {
t.Errorf("stringify() = %v, want %v", got, want) t.Errorf("stringify() = %v, want %v", got, want)
} }
}) })
t.Run("array_with_dict", func(t *testing.T) { t.Run("array_with_dict", func(t *testing.T) {
arg := array{newDict(), array{}} arg := Array{NewDict(), Array{}}
want := `[{},[]]` want := `[{},[]]`
if got := stringify(arg); got != want { if got := Stringify(arg); got != want {
t.Errorf("stringify() = %v, want %v", got, want) t.Errorf("stringify() = %v, want %v", got, want)
} }
}) })

@ -0,0 +1,47 @@
package json
import (
"fmt"
. "github.com/antonmedv/fx/pkg/dict"
)
type Iterator struct {
Object interface{}
Path, Parent string
}
func Dfs(object interface{}, f func(it Iterator)) {
sub(Iterator{Object: object}, f)
}
func sub(it Iterator, f func(it Iterator)) {
f(it)
switch it.Object.(type) {
case *Dict:
keys := it.Object.(*Dict).Keys
for _, k := range keys {
subpath := it.Path + "." + k
value, _ := it.Object.(*Dict).Get(k)
sub(Iterator{
Object: value,
Path: subpath,
Parent: it.Path,
}, f)
}
case Array:
slice := it.Object.(Array)
for i, value := range slice {
subpath := accessor(it.Path, i)
sub(Iterator{
Object: value,
Path: subpath,
Parent: it.Path,
}, f)
}
}
}
func accessor(path string, to interface{}) string {
return fmt.Sprintf("%v[%v]", path, to)
}

@ -0,0 +1,6 @@
package json
import "encoding/json"
type Number = json.Number
type Array = []interface{}

@ -1,10 +1,12 @@
package main package reducer
import ( import (
"bytes" "bytes"
_ "embed" _ "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
. "github.com/antonmedv/fx/pkg/json"
. "github.com/antonmedv/fx/pkg/theme"
"os" "os"
"os/exec" "os/exec"
"regexp" "regexp"
@ -16,7 +18,7 @@ var template string
var flatMapRegex = regexp.MustCompile("^(\\.\\w*)+\\[]") var flatMapRegex = regexp.MustCompile("^(\\.\\w*)+\\[]")
func generateCode(args []string) string { func GenerateCode(args []string) string {
rs := "\n" rs := "\n"
for i, a := range args { for i, a := range args {
rs += " try {" rs += " try {"
@ -74,9 +76,9 @@ func generateCode(args []string) string {
} }
pointer := fmt.Sprintf( pointer := fmt.Sprintf(
"%v %v %v", "%v %v %v",
strings.Repeat(" ", width(pre)), strings.Repeat(" ", len(pre)),
strings.Repeat("^", width(a)), strings.Repeat("^", len(a)),
strings.Repeat(" ", width(post)), strings.Repeat(" ", len(post)),
) )
rs += fmt.Sprintf( rs += fmt.Sprintf(
" throw `\\n"+ " throw `\\n"+
@ -104,24 +106,24 @@ func fold(s []string) string {
return fmt.Sprintf("x => Object.values(%v).flatMap(%v)", obj, fold(s[1:])) return fmt.Sprintf("x => Object.values(%v).flatMap(%v)", obj, fold(s[1:]))
} }
func reduce(object interface{}, args []string, theme Theme) { func Reduce(object interface{}, args []string, theme Theme) {
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd := exec.Command("node", "-e", generateCode(args)) cmd := exec.Command("node", "-e", GenerateCode(args))
cmd.Env = os.Environ() cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "NODE_OPTIONS=--max-old-space-size=8192") cmd.Env = append(cmd.Env, "NODE_OPTIONS=--max-old-space-size=8192")
cmd.Stdin = strings.NewReader(stringify(object)) cmd.Stdin = strings.NewReader(Stringify(object))
cmd.Stdout = &stdout cmd.Stdout = &stdout
cmd.Stderr = &stderr cmd.Stderr = &stderr
err := cmd.Run() err := cmd.Run()
if err == nil { if err == nil {
dec := json.NewDecoder(&stdout) dec := json.NewDecoder(&stdout)
dec.UseNumber() dec.UseNumber()
jsonObject, err := parse(dec) jsonObject, err := Parse(dec)
if err == nil { if err == nil {
if str, ok := jsonObject.(string); ok { if str, ok := jsonObject.(string); ok {
fmt.Println(str) fmt.Println(str)
} else { } else {
fmt.Println(prettyPrint(jsonObject, 1, theme)) fmt.Println(PrettyPrint(jsonObject, 1, theme))
} }
} else { } else {
_, _ = fmt.Fprint(os.Stderr, stderr.String()) _, _ = fmt.Fprint(os.Stderr, stderr.String())
@ -136,65 +138,3 @@ func reduce(object interface{}, args []string, theme Theme) {
os.Exit(exitCode) os.Exit(exitCode)
} }
} }
func prettyPrint(v interface{}, level int, theme Theme) string {
ident := strings.Repeat(" ", level)
subident := strings.Repeat(" ", level-1)
switch v.(type) {
case nil:
return theme.null("null")
case bool:
if v.(bool) {
return theme.boolean("true")
} else {
return theme.boolean("false")
}
case number:
return theme.number(v.(number).String())
case string:
return theme.string(fmt.Sprintf("%q", v))
case *dict:
keys := v.(*dict).keys
if len(keys) == 0 {
return theme.syntax("{}")
}
output := theme.syntax("{")
output += "\n"
for i, k := range keys {
key := theme.key(i, len(keys))(fmt.Sprintf("%q", k))
value, _ := v.(*dict).get(k)
delim := theme.syntax(": ")
line := ident + key + delim + prettyPrint(value, level+1, theme)
if i < len(keys)-1 {
line += theme.syntax(",")
}
line += "\n"
output += line
}
return output + subident + theme.syntax("}")
case array:
slice := v.(array)
if len(slice) == 0 {
return theme.syntax("[]")
}
output := theme.syntax("[\n")
for i, value := range v.(array) {
line := ident + prettyPrint(value, level+1, theme)
if i < len(slice)-1 {
line += ",\n"
} else {
line += "\n"
}
output += line
}
return output + subident + theme.syntax("]")
default:
return "unknown type"
}
}

@ -0,0 +1,185 @@
package theme
import (
"github.com/charmbracelet/lipgloss"
"github.com/mazznoer/colorgrad"
"strings"
)
type Theme struct {
Cursor Color
Syntax Color
Preview Color
StatusBar Color
Search Color
Key func(i, len int) Color
String Color
Null Color
Boolean Color
Number Color
}
type Color func(s string) string
var (
defaultCursor = lipgloss.NewStyle().Reverse(true).Render
defaultPreview = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("8")).Render
defaultStatusBar = lipgloss.NewStyle().Background(lipgloss.Color("7")).Foreground(lipgloss.Color("0")).Render
defaultSearch = lipgloss.NewStyle().Background(lipgloss.Color("11")).Foreground(lipgloss.Color("16")).Render
defaultNull = fg("8")
)
var Themes = map[string]Theme{
"0": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: noColor,
StatusBar: noColor,
Search: defaultSearch,
Key: func(_, _ int) Color { return noColor },
String: noColor,
Null: noColor,
Boolean: noColor,
Number: noColor,
},
"1": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return boldFg("4") },
String: boldFg("2"),
Null: defaultNull,
Boolean: boldFg("3"),
Number: boldFg("6"),
},
"2": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return fg("#00F5D4") },
String: fg("#00BBF9"),
Null: defaultNull,
Boolean: fg("#F15BB5"),
Number: fg("#9B5DE5"),
},
"3": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return fg("#faf0ca") },
String: fg("#f4d35e"),
Null: defaultNull,
Boolean: fg("#ee964b"),
Number: fg("#ee964b"),
},
"4": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return fg("#4D96FF") },
String: fg("#6BCB77"),
Null: defaultNull,
Boolean: fg("#FF6B6B"),
Number: fg("#FFD93D"),
},
"5": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return boldFg("42") },
String: boldFg("213"),
Null: defaultNull,
Boolean: boldFg("201"),
Number: boldFg("201"),
},
"6": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return gradient("rgb(125,110,221)", "rgb(90%,45%,97%)", "hsl(229,79%,85%)") },
String: fg("195"),
Null: defaultNull,
Boolean: fg("195"),
Number: fg("195"),
},
"7": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: func(_, _ int) Color { return gradient("rgb(123,216,96)", "rgb(255,255,255)") },
String: noColor,
Null: defaultNull,
Boolean: noColor,
Number: noColor,
},
"8": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: gradientKeys("#ff0000", "#ff8700", "#ffd300", "#deff0a", "#a1ff0a", "#0aff99", "#0aefff", "#147df5", "#580aff", "#be0aff"),
String: noColor,
Null: defaultNull,
Boolean: noColor,
Number: noColor,
},
"9": {
Cursor: defaultCursor,
Syntax: noColor,
Preview: defaultPreview,
StatusBar: defaultStatusBar,
Search: defaultSearch,
Key: gradientKeys("rgb(34,126,34)", "rgb(168,251,60)"),
String: gradient("rgb(34,126,34)", "rgb(168,251,60)"),
Null: defaultNull,
Boolean: noColor,
Number: noColor,
},
}
func noColor(s string) string {
return s
}
func fg(color string) Color {
return lipgloss.NewStyle().Foreground(lipgloss.Color(color)).Render
}
func boldFg(color string) Color {
return lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(color)).Render
}
func gradient(colors ...string) Color {
grad, _ := colorgrad.NewGradient().HtmlColors(colors...).Build()
return func(s string) string {
runes := []rune(s)
colors := grad.ColorfulColors(uint(len(runes)))
var out strings.Builder
for i, r := range runes {
style := lipgloss.NewStyle().Foreground(lipgloss.Color(colors[i].Hex()))
out.WriteString(style.Render(string(r)))
}
return out.String()
}
}
func gradientKeys(colors ...string) func(i, len int) Color {
grad, _ := colorgrad.NewGradient().HtmlColors(colors...).Build()
return func(i, len int) Color {
return lipgloss.NewStyle().Foreground(lipgloss.Color(grad.At(float64(i) / float64(len)).Hex())).Render
}
}

@ -2,6 +2,9 @@ package main
import ( import (
"fmt" "fmt"
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
"github.com/antonmedv/fx/pkg/theme"
"strings" "strings"
) )
@ -27,34 +30,34 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
switch v.(type) { switch v.(type) {
case nil: case nil:
return []string{merge(m.explode("null", searchValue, m.theme.null, path, selectableValues))} return []string{merge(m.explode("null", searchValue, m.theme.Null, path, selectableValues))}
case bool: case bool:
if v.(bool) { if v.(bool) {
return []string{merge(m.explode("true", searchValue, m.theme.boolean, path, selectableValues))} return []string{merge(m.explode("true", searchValue, m.theme.Boolean, path, selectableValues))}
} else { } else {
return []string{merge(m.explode("false", searchValue, m.theme.boolean, path, selectableValues))} return []string{merge(m.explode("false", searchValue, m.theme.Boolean, path, selectableValues))}
} }
case number: case Number:
return []string{merge(m.explode(v.(number).String(), searchValue, m.theme.number, path, selectableValues))} return []string{merge(m.explode(v.(Number).String(), searchValue, m.theme.Number, path, selectableValues))}
case string: case string:
line := fmt.Sprintf("%q", v) line := fmt.Sprintf("%q", v)
chunks := m.explode(line, searchValue, m.theme.string, path, selectableValues) chunks := m.explode(line, searchValue, m.theme.String, path, selectableValues)
if m.wrap && keyEndPos+width(line) > m.width { if m.wrap && keyEndPos+width(line) > m.width {
return wrapLines(chunks, keyEndPos, m.width, subident) return wrapLines(chunks, keyEndPos, m.width, subident)
} }
// No wrap // No wrap
return []string{merge(chunks)} return []string{merge(chunks)}
case *dict: case *Dict:
if !m.expandedPaths[path] { if !m.expandedPaths[path] {
return []string{m.preview(v, path, selectableValues)} return []string{m.preview(v, path, selectableValues)}
} }
output := []string{m.printOpenBracket("{", highlight, path, selectableValues)} output := []string{m.printOpenBracket("{", highlight, path, selectableValues)}
lineNumber++ // bracket is on separate line lineNumber++ // bracket is on separate line
keys := v.(*dict).keys keys := v.(*Dict).Keys
for i, k := range keys { for i, k := range keys {
subpath := path + "." + k subpath := path + "." + k
highlight := m.highlightIndex[subpath] highlight := m.highlightIndex[subpath]
@ -65,10 +68,10 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
} }
m.connect(subpath, lineNumber) m.connect(subpath, lineNumber)
key := fmt.Sprintf("%q", k) key := fmt.Sprintf("%q", k)
keyTheme := m.theme.key(i, len(keys)) keyTheme := m.theme.Key(i, len(keys))
key = merge(m.explode(key, keyRanges, keyTheme, subpath, true)) key = merge(m.explode(key, keyRanges, keyTheme, subpath, true))
value, _ := v.(*dict).get(k) value, _ := v.(*Dict).Get(k)
delim := merge(m.explode(": ", delimRanges, m.theme.syntax, subpath, false)) delim := merge(m.explode(": ", delimRanges, m.theme.Syntax, subpath, false))
keyEndPos := width(ident) + width(key) + width(delim) keyEndPos := width(ident) + width(key) + width(delim)
lines := m.print(value, level+1, lineNumber, keyEndPos, subpath, false) lines := m.print(value, level+1, lineNumber, keyEndPos, subpath, false)
lines[0] = ident + key + delim + lines[0] lines[0] = ident + key + delim + lines[0]
@ -81,13 +84,13 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
output = append(output, subident+m.printCloseBracket("}", highlight, path, false)) output = append(output, subident+m.printCloseBracket("}", highlight, path, false))
return output return output
case array: case Array:
if !m.expandedPaths[path] { if !m.expandedPaths[path] {
return []string{m.preview(v, path, selectableValues)} return []string{m.preview(v, path, selectableValues)}
} }
output := []string{m.printOpenBracket("[", highlight, path, selectableValues)} output := []string{m.printOpenBracket("[", highlight, path, selectableValues)}
lineNumber++ // bracket is on separate line lineNumber++ // bracket is on separate line
slice := v.(array) slice := v.(Array)
for i, value := range slice { for i, value := range slice {
subpath := fmt.Sprintf("%v[%v]", path, i) subpath := fmt.Sprintf("%v[%v]", path, i)
s := m.highlightIndex[subpath] s := m.highlightIndex[subpath]
@ -110,32 +113,32 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
func (m *model) preview(v interface{}, path string, selectableValues bool) string { func (m *model) preview(v interface{}, path string, selectableValues bool) string {
searchResult := m.highlightIndex[path] searchResult := m.highlightIndex[path]
previewStyle := m.theme.preview previewStyle := m.theme.Preview
if selectableValues && m.cursorPath() == path { if selectableValues && m.cursorPath() == path {
previewStyle = m.theme.cursor previewStyle = m.theme.Cursor
} }
printValue := func(value interface{}) string { printValue := func(value interface{}) string {
switch value.(type) { switch value.(type) {
case nil, bool, number: case nil, bool, Number:
return previewStyle(fmt.Sprintf("%v", value)) return previewStyle(fmt.Sprintf("%v", value))
case string: case string:
return previewStyle(fmt.Sprintf("%q", value)) return previewStyle(fmt.Sprintf("%q", value))
case *dict: case *Dict:
return previewStyle("{\u2026}") return previewStyle("{\u2026}")
case array: case Array:
return previewStyle("[\u2026]") return previewStyle("[\u2026]")
} }
return "..." return "..."
} }
switch v.(type) { switch v.(type) {
case *dict: case *Dict:
output := m.printOpenBracket("{", searchResult, path, selectableValues) output := m.printOpenBracket("{", searchResult, path, selectableValues)
keys := v.(*dict).keys keys := v.(*Dict).Keys
for _, k := range keys { for _, k := range keys {
key := fmt.Sprintf("%q", k) key := fmt.Sprintf("%q", k)
output += previewStyle(key + ": ") output += previewStyle(key + ": ")
value, _ := v.(*dict).get(k) value, _ := v.(*Dict).Get(k)
output += printValue(value) output += printValue(value)
break break
} }
@ -145,9 +148,9 @@ func (m *model) preview(v interface{}, path string, selectableValues bool) strin
output += m.printCloseBracket("}", searchResult, path, selectableValues) output += m.printCloseBracket("}", searchResult, path, selectableValues)
return output return output
case array: case Array:
output := m.printOpenBracket("[", searchResult, path, selectableValues) output := m.printOpenBracket("[", searchResult, path, selectableValues)
slice := v.(array) slice := v.(Array)
for _, value := range slice { for _, value := range slice {
output += printValue(value) output += printValue(value)
break break
@ -194,62 +197,62 @@ func (w withStyle) Render(s string) string {
func (m *model) printOpenBracket(line string, s *rangeGroup, path string, selectableValues bool) string { func (m *model) printOpenBracket(line string, s *rangeGroup, path string, selectableValues bool) string {
if selectableValues && m.cursorPath() == path { if selectableValues && m.cursorPath() == path {
return m.theme.cursor(line) return m.theme.Cursor(line)
} }
if s != nil && s.openBracket != nil { if s != nil && s.openBracket != nil {
if s.openBracket.parent.index == m.searchResultsCursor { if s.openBracket.parent.index == m.searchResultsCursor {
return m.theme.cursor(line) return m.theme.Cursor(line)
} else { } else {
return m.theme.search(line) return m.theme.Search(line)
} }
} else { } else {
return m.theme.syntax(line) return m.theme.Syntax(line)
} }
} }
func (m *model) printCloseBracket(line string, s *rangeGroup, path string, selectableValues bool) string { func (m *model) printCloseBracket(line string, s *rangeGroup, path string, selectableValues bool) string {
if selectableValues && m.cursorPath() == path { if selectableValues && m.cursorPath() == path {
return m.theme.cursor(line) return m.theme.Cursor(line)
} }
if s != nil && s.closeBracket != nil { if s != nil && s.closeBracket != nil {
if s.closeBracket.parent.index == m.searchResultsCursor { if s.closeBracket.parent.index == m.searchResultsCursor {
return m.theme.cursor(line) return m.theme.Cursor(line)
} else { } else {
return m.theme.search(line) return m.theme.Search(line)
} }
} else { } else {
return m.theme.syntax(line) return m.theme.Syntax(line)
} }
} }
func (m *model) printComma(line string, s *rangeGroup) string { func (m *model) printComma(line string, s *rangeGroup) string {
if s != nil && s.comma != nil { if s != nil && s.comma != nil {
if s.comma.parent.index == m.searchResultsCursor { if s.comma.parent.index == m.searchResultsCursor {
return m.theme.cursor(line) return m.theme.Cursor(line)
} else { } else {
return m.theme.search(line) return m.theme.Search(line)
} }
} else { } else {
return m.theme.syntax(line) return m.theme.Syntax(line)
} }
} }
type withStyle struct { type withStyle struct {
value string value string
style Color style theme.Color
} }
func (m *model) explode(line string, highlightRanges []*foundRange, defaultStyle Color, path string, selectable bool) []withStyle { func (m *model) explode(line string, highlightRanges []*foundRange, defaultStyle theme.Color, path string, selectable bool) []withStyle {
if selectable && m.cursorPath() == path && m.showCursor { if selectable && m.cursorPath() == path && m.showCursor {
return []withStyle{{line, m.theme.cursor}} return []withStyle{{line, m.theme.Cursor}}
} }
out := make([]withStyle, 0, 1) out := make([]withStyle, 0, 1)
pos := 0 pos := 0
for _, r := range highlightRanges { for _, r := range highlightRanges {
style := m.theme.search style := m.theme.Search
if r.parent.index == m.searchResultsCursor { if r.parent.index == m.searchResultsCursor {
style = m.theme.cursor style = m.theme.Cursor
} }
out = append(out, withStyle{ out = append(out, withStyle{
value: line[pos:r.start], value: line[pos:r.start],

@ -2,6 +2,8 @@ package main
import ( import (
"fmt" "fmt"
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
"regexp" "regexp"
) )
@ -54,7 +56,7 @@ func (m *model) doSearch(s string) {
m.searchInput.Blur() m.searchInput.Blur()
return return
} }
indexes := re.FindAllStringIndex(stringify(m.json), -1) indexes := re.FindAllStringIndex(Stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil) m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
m.indexSearchResults() m.indexSearchResults()
m.searchInput.Blur() m.searchInput.Blur()
@ -79,8 +81,8 @@ func (m *model) remapSearchResult(object interface{}, path string, pos int, inde
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current) id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
return pos + len(s), id, current return pos + len(s), id, current
case number: case Number:
s := object.(number).String() s := object.(Number).String()
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current) id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
return pos + len(s), id, current return pos + len(s), id, current
@ -89,10 +91,10 @@ func (m *model) remapSearchResult(object interface{}, path string, pos int, inde
id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current) id, current = m.findRanges(valueRange, s, path, pos, indexes, id, current)
return pos + len(s), id, current return pos + len(s), id, current
case *dict: case *Dict:
id, current = m.findRanges(openBracketRange, "{", path, pos, indexes, id, current) id, current = m.findRanges(openBracketRange, "{", path, pos, indexes, id, current)
pos++ // { pos++ // {
for i, k := range object.(*dict).keys { for i, k := range object.(*Dict).Keys {
subpath := path + "." + k subpath := path + "." + k
key := fmt.Sprintf("%q", k) key := fmt.Sprintf("%q", k)
@ -103,8 +105,8 @@ func (m *model) remapSearchResult(object interface{}, path string, pos int, inde
id, current = m.findRanges(delimRange, delim, subpath, pos, indexes, id, current) id, current = m.findRanges(delimRange, delim, subpath, pos, indexes, id, current)
pos += len(delim) pos += len(delim)
pos, id, current = m.remapSearchResult(object.(*dict).values[k], subpath, pos, indexes, id, current) pos, id, current = m.remapSearchResult(object.(*Dict).Values[k], subpath, pos, indexes, id, current)
if i < len(object.(*dict).keys)-1 { if i < len(object.(*Dict).Keys)-1 {
comma := "," comma := ","
id, current = m.findRanges(commaRange, comma, subpath, pos, indexes, id, current) id, current = m.findRanges(commaRange, comma, subpath, pos, indexes, id, current)
pos += len(comma) pos += len(comma)
@ -114,13 +116,13 @@ func (m *model) remapSearchResult(object interface{}, path string, pos int, inde
pos++ // } pos++ // }
return pos, id, current return pos, id, current
case array: case Array:
id, current = m.findRanges(openBracketRange, "[", path, pos, indexes, id, current) id, current = m.findRanges(openBracketRange, "[", path, pos, indexes, id, current)
pos++ // [ pos++ // [
for i, v := range object.(array) { for i, v := range object.(Array) {
subpath := fmt.Sprintf("%v[%v]", path, i) subpath := fmt.Sprintf("%v[%v]", path, i)
pos, id, current = m.remapSearchResult(v, subpath, pos, indexes, id, current) pos, id, current = m.remapSearchResult(v, subpath, pos, indexes, id, current)
if i < len(object.(array))-1 { if i < len(object.(Array))-1 {
comma := "," comma := ","
id, current = m.findRanges(commaRange, comma, subpath, pos, indexes, id, current) id, current = m.findRanges(commaRange, comma, subpath, pos, indexes, id, current)
pos += len(comma) pos += len(comma)

@ -1,6 +1,8 @@
package main package main
import ( import (
. "github.com/antonmedv/fx/pkg/dict"
. "github.com/antonmedv/fx/pkg/json"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"regexp" "regexp"
"testing" "testing"
@ -15,7 +17,7 @@ func Test_search_values(t *testing.T) {
{name: "null", object: nil}, {name: "null", object: nil},
{name: "true", object: true}, {name: "true", object: true},
{name: "false", object: false}, {name: "false", object: false},
{name: "number", object: number("42")}, {name: "Number", object: Number("42")},
{name: "string", object: "Hello, World!"}, {name: "string", object: "Hello, World!"},
} }
for _, tt := range tests { for _, tt := range tests {
@ -24,7 +26,7 @@ func Test_search_values(t *testing.T) {
json: tt.object, json: tt.object,
} }
re, _ := regexp.Compile(".+") re, _ := regexp.Compile(".+")
str := stringify(m.json) str := Stringify(m.json)
indexes := re.FindAllStringIndex(str, -1) indexes := re.FindAllStringIndex(str, -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil) m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
@ -47,10 +49,10 @@ func Test_search_array(t *testing.T) {
^^^^^ ^^^^^^ ^^^^^ ^^^^^^
` `
m := &model{ m := &model{
json: array{"first", "second"}, json: Array{"first", "second"},
} }
re, _ := regexp.Compile("\\w+") re, _ := regexp.Compile("\\w+")
indexes := re.FindAllStringIndex(stringify(m.json), -1) indexes := re.FindAllStringIndex(Stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil) m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s1 := &searchResult{path: "[0]"} s1 := &searchResult{path: "[0]"}
@ -82,10 +84,10 @@ func Test_search_between_array(t *testing.T) {
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
` `
m := &model{ m := &model{
json: array{"first", "second"}, json: Array{"first", "second"},
} }
re, _ := regexp.Compile("\\w.+\\w") re, _ := regexp.Compile("\\w.+\\w")
indexes := re.FindAllStringIndex(stringify(m.json), -1) indexes := re.FindAllStringIndex(Stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil) m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s := &searchResult{path: "[0]"} s := &searchResult{path: "[0]"}
@ -120,13 +122,13 @@ func Test_search_dict(t *testing.T) {
{"key": "hello world"} {"key": "hello world"}
^^^^^ ^^^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^
` `
d := newDict() d := NewDict()
d.set("key", "hello world") d.Set("key", "hello world")
m := &model{ m := &model{
json: d, json: d,
} }
re, _ := regexp.Compile("\"[\\w\\s]+\"") re, _ := regexp.Compile("\"[\\w\\s]+\"")
indexes := re.FindAllStringIndex(stringify(m.json), -1) indexes := re.FindAllStringIndex(Stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil) m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s1 := &searchResult{path: ".key"} s1 := &searchResult{path: ".key"}
@ -157,14 +159,14 @@ func Test_search_dict_with_array(t *testing.T) {
{"first": [1,2],"second": []} {"first": [1,2],"second": []}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
` `
d := newDict() d := NewDict()
d.set("first", array{number("1"), number("2")}) d.Set("first", Array{Number("1"), Number("2")})
d.set("second", array{}) d.Set("second", Array{})
m := &model{ m := &model{
json: d, json: d,
} }
re, _ := regexp.Compile(".+") re, _ := regexp.Compile(".+")
indexes := re.FindAllStringIndex(stringify(m.json), -1) indexes := re.FindAllStringIndex(Stringify(m.json), -1)
m.remapSearchResult(m.json, "", 0, indexes, 0, nil) m.remapSearchResult(m.json, "", 0, indexes, 0, nil)
s := &searchResult{path: ""} s := &searchResult{path: ""}

@ -1,185 +0,0 @@
package main
import (
"github.com/charmbracelet/lipgloss"
"github.com/mazznoer/colorgrad"
"strings"
)
type Theme struct {
cursor Color
syntax Color
preview Color
statusBar Color
search Color
key func(i, len int) Color
string Color
null Color
boolean Color
number Color
}
type Color func(s string) string
func fg(color string) Color {
return lipgloss.NewStyle().Foreground(lipgloss.Color(color)).Render
}
func boldFg(color string) Color {
return lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(color)).Render
}
var (
defaultCursor = lipgloss.NewStyle().Reverse(true).Render
defaultPreview = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("8")).Render
defaultStatusBar = lipgloss.NewStyle().Background(lipgloss.Color("7")).Foreground(lipgloss.Color("0")).Render
defaultSearch = lipgloss.NewStyle().Background(lipgloss.Color("11")).Foreground(lipgloss.Color("16")).Render
defaultNull = fg("8")
)
var themes = map[string]Theme{
"0": {
cursor: defaultCursor,
syntax: noColor,
preview: noColor,
statusBar: noColor,
search: defaultSearch,
key: func(_, _ int) Color { return noColor },
string: noColor,
null: noColor,
boolean: noColor,
number: noColor,
},
"1": {
cursor: defaultCursor,
syntax: noColor,
preview: defaultPreview,
statusBar: defaultStatusBar,
search: defaultSearch,
key: func(_, _ int) Color { return boldFg("4") },
string: boldFg("2"),
null: defaultNull,
boolean: boldFg("3"),
number: boldFg("6"),
},
"2": {
cursor: defaultCursor,
syntax: noColor,
preview: defaultPreview,
statusBar: defaultStatusBar,
search: defaultSearch,
key: func(_, _ int) Color { return fg("#00F5D4") },
string: fg("#00BBF9"),
null: defaultNull,
boolean: fg("#F15BB5"),
number: fg("#9B5DE5"),
},
"3": {
cursor: defaultCursor,
syntax: noColor,
preview: defaultPreview,
statusBar: defaultStatusBar,
search: defaultSearch,
key: func(_, _ int) Color { return fg("#faf0ca") },
string: fg("#f4d35e"),
null: defaultNull,
boolean: fg("#ee964b"),
number: fg("#ee964b"),
},
"4": {
cursor: defaultCursor,
syntax: noColor,
preview: defaultPreview,
statusBar: defaultStatusBar,
search: defaultSearch,
key: func(_, _ int) Color { return fg("#4D96FF") },
string: fg("#6BCB77"),
null: defaultNull,
boolean: fg("#FF6B6B"),
number: fg("#FFD93D"),
},
"5": {
cursor: defaultCursor,
syntax: noColor,
preview: defaultPreview,
statusBar: defaultStatusBar,
search: defaultSearch,
key: func(_, _ int) Color { return boldFg("42") },
string: boldFg("213"),
null: defaultNull,
boolean: boldFg("201"),
number: boldFg("201"),
},
"6": {
cursor: defaultCursor,
syntax: noColor,
preview: defaultPreview,
statusBar: defaultStatusBar,
search: defaultSearch,
key: func(_, _ int) Color { return gradient("rgb(125,110,221)", "rgb(90%,45%,97%)", "hsl(229,79%,85%)") },
string: fg("195"),
null: defaultNull,
boolean: fg("195"),
number: fg("195"),
},
"7": {
cursor: defaultCursor,
syntax: noColor,
preview: defaultPreview,
statusBar: defaultStatusBar,
search: defaultSearch,
key: func(_, _ int) Color { return gradient("rgb(123,216,96)", "rgb(255,255,255)") },
string: noColor,
null: defaultNull,
boolean: noColor,
number: noColor,
},
"8": {
cursor: defaultCursor,
syntax: noColor,
preview: defaultPreview,
statusBar: defaultStatusBar,
search: defaultSearch,
key: gradientKeys("#ff0000", "#ff8700", "#ffd300", "#deff0a", "#a1ff0a", "#0aff99", "#0aefff", "#147df5", "#580aff", "#be0aff"),
string: noColor,
null: defaultNull,
boolean: noColor,
number: noColor,
},
"9": {
cursor: defaultCursor,
syntax: noColor,
preview: defaultPreview,
statusBar: defaultStatusBar,
search: defaultSearch,
key: gradientKeys("rgb(34,126,34)", "rgb(168,251,60)"),
string: gradient("rgb(34,126,34)", "rgb(168,251,60)"),
null: defaultNull,
boolean: noColor,
number: noColor,
},
}
func noColor(s string) string {
return s
}
func gradient(colors ...string) Color {
grad, _ := colorgrad.NewGradient().HtmlColors(colors...).Build()
return func(s string) string {
runes := []rune(s)
colors := grad.ColorfulColors(uint(len(runes)))
var out strings.Builder
for i, r := range runes {
style := lipgloss.NewStyle().Foreground(lipgloss.Color(colors[i].Hex()))
out.WriteString(style.Render(string(r)))
}
return out.String()
}
}
func gradientKeys(colors ...string) func(i, len int) Color {
grad, _ := colorgrad.NewGradient().HtmlColors(colors...).Build()
return func(i, len int) Color {
return lipgloss.NewStyle().Foreground(lipgloss.Color(grad.At(float64(i) / float64(len)).Hex())).Render
}
}

@ -1,38 +0,0 @@
package main
type iterator struct {
object interface{}
path, parent string
}
func dfs(object interface{}, f func(it iterator)) {
sub(iterator{object: object}, f)
}
func sub(it iterator, f func(it iterator)) {
f(it)
switch it.object.(type) {
case *dict:
keys := it.object.(*dict).keys
for _, k := range keys {
subpath := it.path + "." + k
value, _ := it.object.(*dict).get(k)
sub(iterator{
object: value,
path: subpath,
parent: it.path,
}, f)
}
case array:
slice := it.object.(array)
for i, value := range slice {
subpath := accessor(it.path, i)
sub(iterator{
object: value,
path: subpath,
parent: it.path,
}, f)
}
}
}

@ -81,8 +81,8 @@ func (m *model) LineDown(n int) {
return return
} }
// Make sure the number of lines by which we're going to scroll isn't // Make sure the Number of lines by which we're going to scroll isn't
// greater than the number of lines we actually have left before we reach // greater than the Number of lines we actually have left before we reach
// the bottom. // the bottom.
m.SetOffset(m.offset + n) m.SetOffset(m.offset + n)
} }
@ -92,8 +92,8 @@ func (m *model) LineUp(n int) {
return return
} }
// Make sure the number of lines by which we're going to scroll isn't // Make sure the Number of lines by which we're going to scroll isn't
// greater than the number of lines we are from the top. // greater than the Number of lines we are from the top.
m.SetOffset(m.offset - n) m.SetOffset(m.offset - n)
} }

Loading…
Cancel
Save