From 3f347d8342c16016547d2de37120ed6b60edc399 Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Fri, 23 Jan 2026 16:43:44 -0600 Subject: [PATCH] Copy value to clipboard --- app/screen_home.go | 28 +++++-- go.mod | 4 + go.sum | 9 +++ widgets/json_content.go | 158 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 182 insertions(+), 17 deletions(-) diff --git a/app/screen_home.go b/app/screen_home.go index 71fb444..ea96379 100644 --- a/app/screen_home.go +++ b/app/screen_home.go @@ -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 } diff --git a/go.mod b/go.mod index 20d1c27..55dd6b7 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 0568f0c..42308ab 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/widgets/json_content.go b/widgets/json_content.go index e8a25fe..3ed4745 100644 --- a/widgets/json_content.go +++ b/widgets/json_content.go @@ -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,15 +39,20 @@ type JsonContent struct { style tcell.Style x, y int w, h int - visible bool - active bool - focusable bool - keyMap *wd.KeyMap + + border []rune + visible bool + active bool + focusable bool + keyMap *wd.KeyMap editable bool 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) HandleTime(ev *tcell.EventTime) {} +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 +}