Work
This commit is contained in:
@@ -149,7 +149,12 @@ func (a *App) PostEvent(ev tcell.Event) error {
|
|||||||
func (a *App) Sync() { a.tScreen.Sync() }
|
func (a *App) Sync() { a.tScreen.Sync() }
|
||||||
func (a *App) ClearScreen() { a.tScreen.Clear() }
|
func (a *App) ClearScreen() { a.tScreen.Clear() }
|
||||||
func (a *App) Exit() { a.running = false }
|
func (a *App) Exit() { a.running = false }
|
||||||
func (a *App) Cleanup() { a.tScreen.Fini() }
|
func (a *App) Cleanup() {
|
||||||
|
a.running = false
|
||||||
|
a.tScreen.Sync()
|
||||||
|
a.tScreen.Clear()
|
||||||
|
a.tScreen.Fini()
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) GetRepo() *data.Repo { return a.repo }
|
func (a *App) GetRepo() *data.Repo { return a.repo }
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ func (s *ScreenHome) Init(a *App) {
|
|||||||
s.pdsListing.SetLogger(s.Log)
|
s.pdsListing.SetLogger(s.Log)
|
||||||
|
|
||||||
s.jsonContent = wd.NewJsonContent("jsoncontent", s.style)
|
s.jsonContent = wd.NewJsonContent("jsoncontent", s.style)
|
||||||
|
s.jsonContent.SetLogger(s.Log)
|
||||||
km := s.jsonContent.GetKeyMap()
|
km := s.jsonContent.GetKeyMap()
|
||||||
km.Add(
|
km.Add(
|
||||||
w.NewKey(w.BuildEK(tcell.KeyEnter), func(ev *tcell.EventKey) bool {
|
w.NewKey(w.BuildEK(tcell.KeyEnter), func(ev *tcell.EventKey) bool {
|
||||||
@@ -127,11 +128,21 @@ func (s *ScreenHome) Init(a *App) {
|
|||||||
s.Log("Error initializing clipboard: %s", err.Error())
|
s.Log("Error initializing clipboard: %s", err.Error())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
clipboard.Write(clipboard.FmtText, []byte(s.jsonContent.GetSelectedValue()))
|
v, err := s.jsonContent.GetSelectedValue()
|
||||||
|
if err != nil {
|
||||||
|
s.Log("Error getting selected value: %s", err.Error())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
clipboard.Write(clipboard.FmtText, []byte(v))
|
||||||
return true
|
return true
|
||||||
}),
|
}),
|
||||||
w.NewKey(w.BuildEKr('O'), func(ev *tcell.EventKey) bool {
|
w.NewKey(w.BuildEKr('O'), func(ev *tcell.EventKey) bool {
|
||||||
url := fmt.Sprintf("https://%s/xrpc/com.atproto.sync.getBlob?did=did:plc:pqwuemo2ic5tqmpwrajb2phi&cid=%s", s.activePds.AtId.String(), s.jsonContent.GetSelectedValue())
|
v, err := s.jsonContent.GetSelectedValue()
|
||||||
|
if err != nil {
|
||||||
|
s.Log("Error getting selected value: %s", err.Error())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
url := fmt.Sprintf("https://%s/xrpc/com.atproto.sync.getBlob?did=did:plc:pqwuemo2ic5tqmpwrajb2phi&cid=%s", s.activePds.AtId.String(), v)
|
||||||
open.Run(url)
|
open.Run(url)
|
||||||
return true
|
return true
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ package widgets
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -47,6 +48,9 @@ type JsonContent struct {
|
|||||||
|
|
||||||
editable bool
|
editable bool
|
||||||
value any
|
value any
|
||||||
|
jsonObj map[string]json.RawMessage
|
||||||
|
jsonArr json.RawMessage
|
||||||
|
isObj bool
|
||||||
|
|
||||||
activeNode []string
|
activeNode []string
|
||||||
|
|
||||||
@@ -55,6 +59,8 @@ type JsonContent struct {
|
|||||||
|
|
||||||
editKey, editVal bool
|
editKey, editVal bool
|
||||||
editValType int
|
editValType int
|
||||||
|
|
||||||
|
logger func(string, ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -63,6 +69,7 @@ const (
|
|||||||
JsonTpNumber
|
JsonTpNumber
|
||||||
JsonTpObject
|
JsonTpObject
|
||||||
JsonTpArray
|
JsonTpArray
|
||||||
|
JsonTpErr
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ wd.Widget = (*JsonContent)(nil)
|
var _ wd.Widget = (*JsonContent)(nil)
|
||||||
@@ -173,67 +180,24 @@ func (w *JsonContent) Draw(screen tcell.Screen) {
|
|||||||
if len(txt) > w.w-brdSz && w.w-brdSz >= 0 {
|
if len(txt) > w.w-brdSz && w.w-brdSz >= 0 {
|
||||||
txt = txt[:(w.w - brdSz)]
|
txt = txt[:(w.w - brdSz)]
|
||||||
}
|
}
|
||||||
if w.editable {
|
if w.editable && i == w.cursor {
|
||||||
w.drawEditableLine(screen, i, w.x, y, txt, stl, dim)
|
pth, err := w.GetSelectedPath()
|
||||||
|
if err != nil {
|
||||||
|
wh.DrawText(x, y, fmt.Sprintf("%s", err.Error()), stl, screen)
|
||||||
} else {
|
} else {
|
||||||
w.drawReadOnlyLine(screen, i, w.x, y, txt, stl, dim)
|
key, val, err := w.getItemKeyVal(w.cursor)
|
||||||
|
if err != nil {
|
||||||
|
wh.DrawText(x, y, fmt.Sprintf("%s: %s", pth, err.Error()), stl, screen)
|
||||||
|
} else {
|
||||||
|
wh.DrawText(x, y, fmt.Sprintf("%s: %s -> %s", pth, key, val), stl, screen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wh.DrawText(x, y, txt, stl.Dim(dim).Bold(!dim), screen)
|
||||||
}
|
}
|
||||||
y++
|
y++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (w *JsonContent) drawReadOnlyLine(screen tcell.Screen, idx, x, y int, txt string, stl tcell.Style, dim bool) {
|
|
||||||
wh.DrawText(x, y, txt, stl.Dim(dim).Bold(!dim), screen)
|
|
||||||
}
|
|
||||||
func (w *JsonContent) drawEditableLine(screen tcell.Screen, idx, x, y int, txt string, stl tcell.Style, dim bool) {
|
|
||||||
parseQuoted := func(txt string) (string, string) {
|
|
||||||
var v string
|
|
||||||
var slash bool
|
|
||||||
for i := range txt {
|
|
||||||
if txt[i] == '\\' {
|
|
||||||
slash = true
|
|
||||||
} else if slash {
|
|
||||||
slash = false
|
|
||||||
} else if txt[i] == '"' {
|
|
||||||
return v, txt[i+1:]
|
|
||||||
}
|
|
||||||
v = fmt.Sprintf("%s%c", v, txt[i])
|
|
||||||
}
|
|
||||||
// We didn't hit an ending quote
|
|
||||||
return v, ""
|
|
||||||
}
|
|
||||||
var key, val, wrk string
|
|
||||||
// The key should be a quoted value
|
|
||||||
for i := range txt {
|
|
||||||
if txt[i] == '"' && len(txt) > i {
|
|
||||||
key, wrk = parseQuoted(txt[i:])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
txt = strings.Trim(wrk, ": ")
|
|
||||||
if len(txt) > 0 {
|
|
||||||
switch txt[0] {
|
|
||||||
case '"':
|
|
||||||
val, wrk = parseQuoted(txt[1:])
|
|
||||||
w.editValType = JsonTpString
|
|
||||||
case '{':
|
|
||||||
w.editValType = JsonTpObject
|
|
||||||
case '[':
|
|
||||||
w.editValType = JsonTpArray
|
|
||||||
default:
|
|
||||||
if txt == "true" || txt == "false" {
|
|
||||||
// Editing a boolean value
|
|
||||||
w.editValType = JsonTpBool
|
|
||||||
} else {
|
|
||||||
// Must be a number, right?
|
|
||||||
w.editValType = JsonTpNumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyStr := fmt.Sprintf("%s: ", key)
|
|
||||||
wh.DrawText(w.x, y, keyStr, stl.Dim(dim).Bold(!dim), screen)
|
|
||||||
wh.DrawText(w.x, y+len(keyStr), val, stl.Dim(dim).Bold(!dim), screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *JsonContent) SetStyle(s tcell.Style) { w.style = s }
|
func (w *JsonContent) SetStyle(s tcell.Style) { w.style = s }
|
||||||
func (w *JsonContent) Active() bool { return w.active }
|
func (w *JsonContent) Active() bool { return w.active }
|
||||||
@@ -268,8 +232,28 @@ func (w *JsonContent) SetValue(v any) error {
|
|||||||
}
|
}
|
||||||
w.valueString = string(bts)
|
w.valueString = string(bts)
|
||||||
w.contents = strings.Split(w.valueString, "\n")
|
w.contents = strings.Split(w.valueString, "\n")
|
||||||
return nil
|
if bts[0] == '{' {
|
||||||
|
err = json.Unmarshal(bts, &w.jsonObj)
|
||||||
|
w.isObj = true
|
||||||
|
} else if bts[0] == '[' {
|
||||||
|
err = json.Unmarshal(bts, &w.jsonArr)
|
||||||
|
w.isObj = false
|
||||||
|
} else {
|
||||||
|
// Item is just a flat string/number/boolean
|
||||||
|
w.cursor = 0
|
||||||
|
}
|
||||||
|
if !w.isLineSelectable(w.cursor) {
|
||||||
|
idx := w.findPrevSelectableLine(w.cursor)
|
||||||
|
if idx == -1 {
|
||||||
|
idx = w.findNextSelectableLine(w.cursor)
|
||||||
|
}
|
||||||
|
if idx >= 0 {
|
||||||
|
w.cursor = idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *JsonContent) SetBorder(brd []rune) {
|
func (w *JsonContent) SetBorder(brd []rune) {
|
||||||
if len(brd) == 0 {
|
if len(brd) == 0 {
|
||||||
w.border = wh.BRD_SIMPLE
|
w.border = wh.BRD_SIMPLE
|
||||||
@@ -280,57 +264,239 @@ func (w *JsonContent) SetBorder(brd []rune) {
|
|||||||
|
|
||||||
func (w *JsonContent) SetEditable(v bool) { w.editable = v }
|
func (w *JsonContent) SetEditable(v bool) { w.editable = v }
|
||||||
|
|
||||||
/*
|
|
||||||
func (w *JsonContent) SetJsonContent(txt string) {
|
|
||||||
w.text = txt
|
|
||||||
if strings.Contains(w.text, "\n") {
|
|
||||||
w.contents = strings.Split(w.text, "\n")
|
|
||||||
} else {
|
|
||||||
w.contents = []string{w.text}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *JsonContent) GetJsonContent() string { return w.text }
|
|
||||||
func (w *JsonContent) GetContents() []string { return w.contents }
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (w *JsonContent) SetVimMode(b bool) { w.vimMode = b }
|
func (w *JsonContent) SetVimMode(b bool) { w.vimMode = b }
|
||||||
func (w *JsonContent) MoveUp() bool {
|
func (w *JsonContent) MoveUp() bool {
|
||||||
w.cursor--
|
idx := w.findPrevSelectableLine(w.cursor)
|
||||||
if w.cursor < 0 {
|
if idx < 0 {
|
||||||
w.cursor = 0
|
return false
|
||||||
}
|
}
|
||||||
|
w.cursor = idx
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
func (w *JsonContent) MoveDown() bool {
|
func (w *JsonContent) MoveDown() bool {
|
||||||
w.cursor++
|
idx := w.findNextSelectableLine(w.cursor)
|
||||||
if w.cursor >= len(w.contents) {
|
if idx < 0 {
|
||||||
w.cursor = len(w.contents) - 1
|
return false
|
||||||
}
|
}
|
||||||
|
w.cursor = idx
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
func (w *JsonContent) PageUp() bool {
|
func (w *JsonContent) PageUp() bool {
|
||||||
w.cursor -= w.h
|
idx := w.findPrevSelectableLine(w.cursor - w.h)
|
||||||
if w.cursor < 0 {
|
if idx < 0 {
|
||||||
w.cursor = 0
|
return false
|
||||||
}
|
}
|
||||||
|
w.cursor = idx
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
func (w *JsonContent) PageDn() bool {
|
func (w *JsonContent) PageDn() bool {
|
||||||
w.cursor += w.h
|
idx := w.findNextSelectableLine(w.cursor + w.h)
|
||||||
if w.cursor > len(w.contents)-1 {
|
if idx < 0 {
|
||||||
w.cursor = len(w.contents) - 1
|
return false
|
||||||
}
|
}
|
||||||
|
w.cursor = idx
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *JsonContent) GetSelectedValue() string {
|
func (w *JsonContent) GetSelectedPath() ([]string, error) { return w.getItemPath(w.cursor) }
|
||||||
if w.cursor >= len(w.contents) {
|
func (w *JsonContent) GetSelectedKey() (string, error) { return w.getItemKey(w.cursor) }
|
||||||
w.cursor = len(w.contents) - 1
|
func (w *JsonContent) GetSelectedValue() (string, error) { return w.getItemVal(w.cursor) }
|
||||||
|
func (w *JsonContent) getSelectedItemDepth() int { return w.getItemDepth(w.cursor) }
|
||||||
|
|
||||||
|
func (w *JsonContent) getItemPath(line int) ([]string, error) {
|
||||||
|
if line < 0 || line >= len(w.contents) {
|
||||||
|
return []string{}, errors.New("invalid line number")
|
||||||
}
|
}
|
||||||
var ret string
|
depth := w.getItemDepth(line)
|
||||||
ret = w.contents[w.cursor]
|
if depth == 1 {
|
||||||
ret = ret[strings.Index(ret, "\": \"")+3:]
|
key, err := w.getItemKey(line)
|
||||||
ret = ret[1 : len(ret)-1]
|
if err != nil {
|
||||||
return ret
|
return []string{}, err
|
||||||
|
}
|
||||||
|
return []string{key}, nil
|
||||||
|
}
|
||||||
|
pth, err := w.getItemPath(w.findItemParentLine(line))
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
key, err := w.getItemKey(line)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
return append(pth, key), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *JsonContent) getItemKeyVal(line int) (string, string, error) {
|
||||||
|
if line < 0 || line >= len(w.contents) {
|
||||||
|
return "", "", errors.New("invalid line number")
|
||||||
|
}
|
||||||
|
text := w.contents[line]
|
||||||
|
key, rest := w.getNextQuotedString(text)
|
||||||
|
if key == "" {
|
||||||
|
return "", "", errors.New("error finding quoted string")
|
||||||
|
} else if rest == "" {
|
||||||
|
// We have a 'key' value, but no 'rest' so this is likely an item in array
|
||||||
|
return "", key, nil
|
||||||
|
}
|
||||||
|
rest = strings.Trim(rest, ":{[}], ")
|
||||||
|
val, rest := w.getNextQuotedString(rest)
|
||||||
|
if val != "" {
|
||||||
|
return key, val, nil
|
||||||
|
}
|
||||||
|
return key, rest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *JsonContent) getItemKey(line int) (string, error) {
|
||||||
|
if line < 0 || line >= len(w.contents) {
|
||||||
|
return "", errors.New("invalid line number")
|
||||||
|
}
|
||||||
|
|
||||||
|
key, _ := w.getNextQuotedString(w.contents[line])
|
||||||
|
if key == "" {
|
||||||
|
return "", errors.New("error finding quoted string")
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *JsonContent) getItemVal(line int) (string, error) {
|
||||||
|
if line < 0 || line >= len(w.contents) {
|
||||||
|
return "", errors.New("invalid line number")
|
||||||
|
}
|
||||||
|
var key, val, rest string
|
||||||
|
key, rest = w.getNextQuotedString(w.contents[line])
|
||||||
|
if len(rest) > 0 {
|
||||||
|
val, rest = w.getNextQuotedString(rest)
|
||||||
|
}
|
||||||
|
if len(val) > 0 {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
if len(key) > 0 {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
return "", errors.New("error finding value")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (w *JsonContent) getItemValType(line int) int {
|
||||||
|
if line < 0 || line >= len(w.contents) {
|
||||||
|
return JsonTypeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
text := w.contents[line]
|
||||||
|
key, rest := w.getNextQuotedString(text)
|
||||||
|
rest = strings.Trim(rest, ", ")
|
||||||
|
if rest == "" {
|
||||||
|
if key != "" {
|
||||||
|
// Looks like a string value in an array
|
||||||
|
return JsonTypeString
|
||||||
|
} else rest == "true" || rest == "false" {
|
||||||
|
return JsonTypeBool
|
||||||
|
}
|
||||||
|
// see if we can parse this as a number
|
||||||
|
num, err := strconv.Atoi(rest)
|
||||||
|
if err == nil {
|
||||||
|
return JsonTypeNumber
|
||||||
|
}
|
||||||
|
return JsonType
|
||||||
|
}
|
||||||
|
if key == "" {
|
||||||
|
return "", "", errors.New("error finding quoted string")
|
||||||
|
} else if rest == "" {
|
||||||
|
// We have a 'key' value, but no 'rest' so this is likely an item in array
|
||||||
|
return "", key, nil
|
||||||
|
}
|
||||||
|
rest = strings.Trim(rest, ":, ")
|
||||||
|
if rest[0] == '{' {
|
||||||
|
|
||||||
|
}
|
||||||
|
val, rest := w.getNextQuotedString(rest)
|
||||||
|
if val != "" {
|
||||||
|
return key, val, nil
|
||||||
|
}
|
||||||
|
// Val isn't a quoted string,
|
||||||
|
return key, rest, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (w *JsonContent) getItemDepth(line int) int {
|
||||||
|
if line < 0 || line >= len(w.contents) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
txt := w.contents[line]
|
||||||
|
// Find the depth, we use two spaces to indent
|
||||||
|
for i := range txt {
|
||||||
|
if txt[i] != ' ' {
|
||||||
|
return i / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *JsonContent) findItemParentLine(line int) int {
|
||||||
|
depth := w.getItemDepth(line)
|
||||||
|
if depth <= 1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
for i := w.cursor; i >= 0; i-- {
|
||||||
|
if w.getItemDepth(i) < depth {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *JsonContent) getItemParentKey(line int) (string, error) {
|
||||||
|
return w.getItemKey(w.findItemParentLine(line))
|
||||||
|
}
|
||||||
|
func (w *JsonContent) getItemParentVal(line int) (string, error) {
|
||||||
|
return w.getItemVal(w.findItemParentLine(line))
|
||||||
|
}
|
||||||
|
func (w *JsonContent) getNextQuotedString(line string) (string, string) {
|
||||||
|
var inQ, slash bool
|
||||||
|
var ret string
|
||||||
|
for i := range line {
|
||||||
|
if inQ {
|
||||||
|
if line[i] == '"' && !slash {
|
||||||
|
return ret, line[i+1:]
|
||||||
|
} else if slash {
|
||||||
|
slash = false
|
||||||
|
} else if line[i] == '\\' {
|
||||||
|
slash = true
|
||||||
|
}
|
||||||
|
ret = fmt.Sprintf("%s%c", ret, line[i])
|
||||||
|
}
|
||||||
|
if line[i] == '"' {
|
||||||
|
inQ = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we're here, we didn't find a quoted string
|
||||||
|
return "", line
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *JsonContent) isLineSelectable(line int) bool {
|
||||||
|
if line < 0 || line >= len(w.contents) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.Contains(w.contents[line], "\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *JsonContent) findPrevSelectableLine(from int) int {
|
||||||
|
for i := from - 1; i >= 0; i-- {
|
||||||
|
if w.isLineSelectable(i) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *JsonContent) findNextSelectableLine(from int) int {
|
||||||
|
for i := from + 1; i < len(w.contents); i++ {
|
||||||
|
if w.isLineSelectable(i) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *JsonContent) SetLogger(l func(string, ...any)) { w.logger = l }
|
||||||
|
func (w *JsonContent) Log(txt string, args ...any) { w.logger(txt, args...) }
|
||||||
|
|||||||
Reference in New Issue
Block a user