Other features

This commit is contained in:
Anton Medvedev 2022-03-28 22:13:48 +02:00
parent fa226f2ae8
commit afd7b96af5
5 changed files with 222 additions and 42 deletions

View File

@ -3,24 +3,28 @@ package main
import "github.com/charmbracelet/bubbles/key"
type KeyMap struct {
Quit key.Binding
Help key.Binding
PageDown key.Binding
PageUp key.Binding
HalfPageUp key.Binding
HalfPageDown key.Binding
Down key.Binding
Up key.Binding
Expand key.Binding
Collapse key.Binding
GotoTop key.Binding
GotoBottom key.Binding
ToggleWrap key.Binding
ExpandAll key.Binding
CollapseAll key.Binding
Search key.Binding
Next key.Binding
Prev key.Binding
Quit key.Binding
Help key.Binding
PageDown key.Binding
PageUp key.Binding
HalfPageUp key.Binding
HalfPageDown key.Binding
Down key.Binding
Up key.Binding
Expand key.Binding
Collapse key.Binding
NextSibling key.Binding
PrevSibling key.Binding
ExpandRecursively key.Binding
CollapseRecursively key.Binding
GotoTop key.Binding
GotoBottom key.Binding
ToggleWrap key.Binding
ExpandAll key.Binding
CollapseAll key.Binding
Search key.Binding
Next key.Binding
Prev key.Binding
}
func DefaultKeyMap() KeyMap {
@ -65,6 +69,22 @@ func DefaultKeyMap() KeyMap {
key.WithKeys("left", "h"),
key.WithHelp("", "collapse"),
),
NextSibling: key.NewBinding(
key.WithKeys("J"),
key.WithHelp("", "next sibling"),
),
PrevSibling: key.NewBinding(
key.WithKeys("K"),
key.WithHelp("", "previous sibling"),
),
ExpandRecursively: key.NewBinding(
key.WithKeys("L"),
key.WithHelp("", "expand recursively"),
),
CollapseRecursively: key.NewBinding(
key.WithKeys("H"),
key.WithHelp("", "collapse recursively"),
),
GotoTop: key.NewBinding(
key.WithKeys("g"),
key.WithHelp("", "goto top"),

91
main.go
View File

@ -39,6 +39,7 @@ var colors = struct {
func main() {
filePath := ""
args := []string{}
var dec *json.Decoder
if term.IsTerminal(int(os.Stdin.Fd())) {
if len(os.Args) >= 2 {
@ -48,9 +49,11 @@ func main() {
panic(err)
}
dec = json.NewDecoder(f)
args = os.Args[2:]
}
} else {
dec = json.NewDecoder(os.Stdin)
args = os.Args[1:]
}
dec.UseNumber()
jsonObject, err := parse(dec)
@ -58,24 +61,37 @@ func main() {
panic(err)
}
if len(args) > 0 {
if args[0] == "--print-code" {
fmt.Print(generateCode(args[1:]))
return
}
reduce(jsonObject, args)
return
}
expand := map[string]bool{
"": true,
}
if array, ok := jsonObject.(array); ok {
for i := range array {
expand[accessor("", i)] = true
}
}
parents := map[string]string{}
children := map[string][]string{}
canBeExpanded := map[string]bool{}
dfs(jsonObject, func(it iterator) {
parents[it.path] = it.parent
})
children[it.parent] = append(children[it.parent], it.path)
switch it.object.(type) {
case *dict, array:
canBeExpanded[it.path] = true
}
})
input := textinput.New()
input.Prompt = ""
m := &model{
fileName: path.Base(filePath),
json: jsonObject,
@ -84,7 +100,9 @@ func main() {
mouseWheelDelta: 3,
keyMap: DefaultKeyMap(),
expandedPaths: expand,
canBeExpanded: canBeExpanded,
parents: parents,
children: children,
wrap: true,
searchInput: input,
}
@ -112,12 +130,13 @@ type model struct {
keyMap KeyMap
showHelp bool
expandedPaths map[string]bool // a set with expanded paths
canBeExpanded map[string]bool // a set for path => can be expanded (i.e. dict or array)
pathToLineNumber *dict // dict with path => line number
lineNumberToPath map[int]string // map of line number => path
parents map[string]string // map of subpath => parent path
cursor int // cursor in range of m.pathToLineNumber.keys slice
expandedPaths map[string]bool // a set with expanded paths
canBeExpanded map[string]bool // a set for path => can be expanded (i.e. dict or array)
pathToLineNumber *dict // dict with path => line number
lineNumberToPath map[int]string // map of line number => path
parents map[string]string // map of subpath => parent path
children map[string][]string // map of path => child paths
cursor int // cursor in range of m.pathToLineNumber.keys slice
showCursor bool
wrap bool
@ -234,6 +253,18 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.SetOffset(at)
}
case key.Matches(msg, m.keyMap.NextSibling):
m.showCursor = true
// TODO: write code for collecting siblings,
// and write code to getting sibling and jumping to it.
m.render()
at := m.cursorLineNumber()
if m.offset <= at { // cursor is lower
m.LineDown(max(0, at-(m.offset+m.height-1))) // minus one is due to cursorLineNumber() starts from 0
} else {
m.SetOffset(at)
}
case key.Matches(msg, m.keyMap.Up):
m.showCursor = true
if m.cursor > 0 {
@ -255,14 +286,27 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.expandedPaths[m.cursorPath()] = true
m.render()
case key.Matches(msg, m.keyMap.Collapse):
case key.Matches(msg, m.keyMap.ExpandRecursively):
m.showCursor = true
m.expandRecursively(m.cursorPath())
m.render()
case key.Matches(msg, m.keyMap.Collapse, m.keyMap.CollapseRecursively):
m.showCursor = true
if m.expandedPaths[m.cursorPath()] {
m.expandedPaths[m.cursorPath()] = false
if key.Matches(msg, m.keyMap.CollapseRecursively) {
m.collapseRecursively(m.cursorPath())
} else {
m.expandedPaths[m.cursorPath()] = false
}
} else {
parentPath, ok := m.parents[m.cursorPath()]
if ok {
m.expandedPaths[parentPath] = false
if key.Matches(msg, m.keyMap.CollapseRecursively) {
m.collapseRecursively(m.cursorPath())
} else {
m.expandedPaths[m.cursorPath()] = false
}
index, _ := m.pathToLineNumber.index(parentPath)
m.cursor = index
}
@ -382,7 +426,6 @@ func (m *model) render() {
}
m.pathToLineNumber = newDict()
m.canBeExpanded = map[string]bool{}
m.lineNumberToPath = map[int]string{}
m.lines = m.print(m.json, 1, 0, 0, "", false)
@ -407,3 +450,23 @@ func (m *model) cursorLineNumber() int {
}
return -1
}
func (m *model) expandRecursively(path string) {
if m.canBeExpanded[path] {
m.expandedPaths[path] = true
for _, childPath := range m.children[path] {
if childPath != "" {
m.expandRecursively(childPath)
}
}
}
}
func (m *model) collapseRecursively(path string) {
m.expandedPaths[path] = false
for _, childPath := range m.children[path] {
if childPath != "" {
m.collapseRecursively(childPath)
}
}
}

View File

@ -7,6 +7,68 @@ import (
"strings"
)
func prettyPrint(v interface{}, level int) string {
ident := strings.Repeat(" ", level)
subident := strings.Repeat(" ", level-1)
switch v.(type) {
case nil:
return colors.null.Render("null")
case bool:
if v.(bool) {
return colors.boolean.Render("true")
} else {
return colors.boolean.Render("false")
}
case json.Number:
return colors.number.Render(v.(json.Number).String())
case string:
return colors.string.Render(fmt.Sprintf("%q", v))
case *dict:
keys := v.(*dict).keys
if len(keys) == 0 {
return colors.bracket.Render("{}")
}
output := colors.bracket.Render("{\n")
for i, k := range keys {
key := colors.key.Render(fmt.Sprintf("%q", k))
value, _ := v.(*dict).get(k)
delim := ": "
line := ident + key + delim + prettyPrint(value, level+1)
if i < len(keys)-1 {
line += ",\n"
} else {
line += "\n"
}
output += line
}
return output + subident + colors.bracket.Render("}")
case array:
slice := v.(array)
if len(slice) == 0 {
return colors.bracket.Render("[]")
}
output := colors.bracket.Render("[\n")
for i, value := range v.(array) {
line := ident + prettyPrint(value, level+1)
if i < len(slice)-1 {
line += ",\n"
} else {
line += "\n"
}
output += line
}
return output + subident + colors.bracket.Render("]")
default:
return "unknown type"
}
}
func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path string, dontHighlightCursor bool) []string {
ident := strings.Repeat(" ", level)
subident := strings.Repeat(" ", level-1)
@ -50,7 +112,6 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
case *dict:
m.pathToLineNumber.set(path, lineNumber)
m.canBeExpanded[path] = true
m.lineNumberToPath[lineNumber] = path
bracketStyle := cursorOr(colors.bracket).Render
if len(v.(*dict).keys) == 0 {
@ -102,7 +163,6 @@ func (m *model) print(v interface{}, level, lineNumber, keyEndPos int, path stri
case array:
m.pathToLineNumber.set(path, lineNumber)
m.canBeExpanded[path] = true
m.lineNumberToPath[lineNumber] = path
bracketStyle := cursorOr(colors.bracket).Render
if len(v.(array)) == 0 {

View File

@ -86,16 +86,16 @@ func (m *model) jumpToSearchResult(at int) {
m.SetOffset(lineNumber.(int))
m.render()
} else {
m.recursiveExpand(desiredPath)
m.expandToPath(desiredPath)
m.render()
m.jumpToSearchResult(at)
}
}
func (m *model) recursiveExpand(path string) {
func (m *model) expandToPath(path string) {
m.expandedPaths[path] = true
if path != "" {
m.recursiveExpand(m.parents[path])
m.expandToPath(m.parents[path])
}
}

47
util.go
View File

@ -27,12 +27,49 @@ func max(a, b int) int {
return b
}
func stringify(x interface{}) string {
str, err := json.Marshal(x)
if err != nil {
panic(err)
func stringify(v interface{}) string {
switch v.(type) {
case nil:
return "null"
case bool:
if v.(bool) {
return "true"
} else {
return "false"
}
case json.Number:
return v.(json.Number).String()
case string:
return fmt.Sprintf("%q", v)
case *dict:
result := "{"
for i, key := range v.(*dict).keys {
line := fmt.Sprintf("%q", key) + ":" + stringify(v.(*dict).values[key])
if i < len(v.(*dict).keys)-1 {
line += ","
}
result += line
}
return result + "}"
case array:
result := "["
for i, value := range v.(array) {
line := stringify(value)
if i < len(v.(array))-1 {
line += ","
}
result += line
}
return result + "]"
default:
return "unknown type"
}
return string(str)
}
func width(s string) int {