Copy value to clipboard

This commit is contained in:
2026-01-23 16:43:44 -06:00
parent 0834792c57
commit 3f347d8342
4 changed files with 182 additions and 17 deletions

View File

@@ -32,6 +32,7 @@ import (
"github.com/bluesky-social/indigo/atproto/syntax"
"github.com/gdamore/tcell"
"github.com/spf13/viper"
"golang.design/x/clipboard"
)
type ScreenHome struct {
@@ -107,12 +108,24 @@ func (s *ScreenHome) Init(a *App) {
s.pdsListing.SetVimMode(viper.GetBool(data.KeyVimMode))
s.jsonContent = wd.NewJsonContent("jsoncontent", s.style)
brd := w.NewBorderedWidget("jsoncontentbrd", s.style, s.jsonContent)
brd.SetBorder(
km := s.jsonContent.GetKeyMap()
km.Add(
w.NewKey(w.BuildEK(tcell.KeyEnter), func(ev *tcell.EventKey) bool {
// Init returns an error if the package is not ready for use.
err := clipboard.Init()
if err != nil {
s.Log("Error initializing clipboard: %s", err.Error())
return true
}
clipboard.Write(clipboard.FmtText, []byte(s.jsonContent.GetSelectedValue()))
return true
}),
)
s.jsonContent.SetKeyMap(km)
s.jsonContent.SetBorder(
[]rune{'─', '┐', '│', '┘', '─', '─', ' ', '─', '├', '─', '┤', '┬', '│', '┴', '┼'},
)
s.columns.AddAll(s.pdsListing, brd)
s.columns.AddAll(s.pdsListing, s.jsonContent)
s.layout.AddAll(s.columns)
s.layout.SetWeight(s.columns, 4)
@@ -131,6 +144,10 @@ func (s *ScreenHome) HandleKey(ev *tcell.EventKey) bool {
return true
}
if s.doOpen {
if ev.Key() == tcell.KeyEscape {
s.doOpen = false
return true
}
if ev.Key() == tcell.KeyCtrlU {
s.openPdsEntry.SetValue("")
return true
@@ -328,8 +345,7 @@ func (s *ScreenHome) selectPdsListingEntry(idx int, nm string) bool {
return true
case models.TypeRecord:
// If signed in and we can edit this, activate jsonContent in 'editable' mode
s.jsonContent.SetEditable(true)
// If signed in and we can edit this, activate jsonContent
s.columns.ActivateWidget(s.jsonContent)
return true
}

4
go.mod
View File

@@ -10,6 +10,7 @@ require (
github.com/muesli/go-app-paths v0.2.2
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
golang.design/x/clipboard v0.7.1
)
require (
@@ -90,6 +91,9 @@ require (
go.uber.org/zap v1.26.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
golang.org/x/image v0.28.0 // indirect
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.3.0 // indirect

9
go.sum
View File

@@ -226,6 +226,8 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c=
golang.design/x/clipboard v0.7.1/go.mod h1:i5SiIqj0wLFw9P/1D7vfILFK0KHMk7ydE72HRrUIgkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -233,7 +235,14 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 h1:Wdx0vgH5Wgsw+lF//LJKmWOJBLWX6nprsMqnf99rYDE=
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f h1:/n+PL2HlfqeSiDCuhdBbRNlGS/g2fM4OHufalHaTVG8=
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f/go.mod h1:ESkJ836Z6LpG6mTVAhA48LpfW/8fNR0ifStlH2axyfg=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=

View File

@@ -31,7 +31,6 @@ import (
"github.com/gdamore/tcell"
)
// TODO: Format contents as json. For now this is a duplice of the Text widget
type JsonContent struct {
id string
@@ -40,6 +39,8 @@ type JsonContent struct {
style tcell.Style
x, y int
w, h int
border []rune
visible bool
active bool
focusable bool
@@ -49,6 +50,9 @@ type JsonContent struct {
value any
activeNode []string
vimMode bool
cursor int
}
var _ wd.Widget = (*JsonContent)(nil)
@@ -64,7 +68,45 @@ func (w *JsonContent) Init(id string, style tcell.Style) {
w.style = style
w.visible = true
w.focusable = false
w.keyMap = wd.BlankKeyMap()
w.keyMap = wd.NewKeyMap(
wd.NewKey(wd.BuildEK(tcell.KeyUp), func(_ *tcell.EventKey) bool { return w.MoveUp() }),
wd.NewKey(wd.BuildEK(tcell.KeyDown), func(_ *tcell.EventKey) bool { return w.MoveDown() }),
wd.NewKey(wd.BuildEK(tcell.KeyEnter), func(ev *tcell.EventKey) bool {
return false
}),
wd.NewKey(wd.BuildEK(tcell.KeyPgDn), func(_ *tcell.EventKey) bool { return w.PageDn() }),
wd.NewKey(wd.BuildEK(tcell.KeyPgUp), func(_ *tcell.EventKey) bool { return w.PageUp() }),
wd.NewKey(wd.BuildEKr('j'), func(ev *tcell.EventKey) bool {
if !w.vimMode {
return false
}
return w.MoveDown()
}),
wd.NewKey(wd.BuildEKr('k'), func(ev *tcell.EventKey) bool {
if !w.vimMode {
return false
}
return w.MoveUp()
}),
wd.NewKey(wd.BuildEKr('b'), func(ev *tcell.EventKey) bool {
if !w.vimMode {
return false
}
if ev.Modifiers()&tcell.ModCtrl != 0 {
return w.PageUp()
}
return false
}),
wd.NewKey(wd.BuildEKr('f'), func(ev *tcell.EventKey) bool {
if !w.vimMode {
return false
}
if ev.Modifiers()&tcell.ModCtrl != 0 {
return w.PageDn()
}
return false
}),
)
}
func (w *JsonContent) Id() string { return w.id }
@@ -73,15 +115,61 @@ func (w *JsonContent) HandleResize(ev *tcell.EventResize) { w.w, w.h = ev.Size()
func (w *JsonContent) GetKeyMap() *wd.KeyMap { return w.keyMap }
func (w *JsonContent) SetKeyMap(km *wd.KeyMap) { w.keyMap = km }
func (w *JsonContent) HandleKey(ev *tcell.EventKey) bool { return w.keyMap.Handle(ev) }
func (w *JsonContent) HandleKey(ev *tcell.EventKey) bool {
return w.keyMap.Handle(ev)
}
func (w *JsonContent) HandleTime(ev *tcell.EventTime) {}
func (w *JsonContent) Draw(screen tcell.Screen) {
if !w.visible {
return
}
y := w.y
for i := range w.contents {
wh.DrawText(w.x, y, w.contents[i], w.style, screen)
x, y := w.x, w.y
brdSz := 0
if len(w.border) > 0 {
brdSz = 2
wh.BorderFilled(x, y, x+w.w, y+w.h, w.border, w.style, screen)
}
x, y = x+1, y+1
h := w.h - brdSz
ln := len(w.contents)
st, ed := 0, ln-1
if ln == 0 {
return
}
if ln > w.h-2 {
mid := h / 2
if w.cursor < mid {
// contents need to start at 0
ed = h + 1
} else if w.cursor > ln-mid {
// contents need to begin at ln-h
st = ln - h + 1
} else {
st = w.cursor - mid
ed = st + h + 1
}
}
// ed cannot be higher than ln-1
if st < 0 {
st = 0
}
if ed > ln-1 {
ed = ln - 1
}
for i := st; i <= ed; i++ {
st := w.style
dim := true
if i == w.cursor {
dim = false
} else {
st = st.Foreground(tcell.ColorGreen)
}
txt := w.contents[i]
if len(txt) > w.w-brdSz && w.w-brdSz >= 0 {
txt = txt[:(w.w - brdSz)]
}
wh.DrawText(w.x, y, txt, st.Dim(dim).Bold(!dim), screen)
y++
}
}
@@ -120,6 +208,13 @@ func (w *JsonContent) SetValue(v any) error {
w.contents = strings.Split(w.valueString, "\n")
return nil
}
func (w *JsonContent) SetBorder(brd []rune) {
if len(brd) == 0 {
w.border = wh.BRD_SIMPLE
} else {
w.border = wh.ValidateBorder(brd)
}
}
func (w *JsonContent) SetEditable(v bool) { w.editable = v }
@@ -136,3 +231,44 @@ func (w *JsonContent) SetEditable(v bool) { w.editable = v }
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) MoveUp() bool {
w.cursor--
if w.cursor < 0 {
w.cursor = 0
}
return true
}
func (w *JsonContent) MoveDown() bool {
w.cursor++
if w.cursor >= len(w.contents) {
w.cursor = len(w.contents) - 1
}
return true
}
func (w *JsonContent) PageUp() bool {
w.cursor -= w.h
if w.cursor < 0 {
w.cursor = 0
}
return true
}
func (w *JsonContent) PageDn() bool {
w.cursor += w.h
if w.cursor > len(w.contents)-1 {
w.cursor = len(w.contents) - 1
}
return true
}
func (w *JsonContent) GetSelectedValue() string {
if w.cursor >= len(w.contents) {
w.cursor = len(w.contents) - 1
}
var ret string
ret = w.contents[w.cursor]
ret = ret[strings.Index(ret, "\": \"")+3:]
ret = ret[1 : len(ret)-1]
return ret
}