This commit is contained in:
2026-02-19 11:50:25 -06:00
parent 94fab7acee
commit 385b6ea67c
3 changed files with 276 additions and 94 deletions

View File

@@ -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 }

View File

@@ -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
}), }),

View File

@@ -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 {
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 { } else {
w.drawReadOnlyLine(screen, i, w.x, y, txt, stl, dim) 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...) }