From 7d6928f3df0287855f221bbfd1a3adfb4b3312ec Mon Sep 17 00:00:00 2001 From: asla9709 Date: Fri, 27 Feb 2026 15:39:34 -0800 Subject: [PATCH] feat: add option to copy using osc52 Add `oscClipboard` config option to enable OSC 52 clipboard support for headless/remote machines where traditional clipboard access isn't available. When enabled, the copy number (y) and copy url (Y) keybindings will use OSC 52 escape sequences instead of the system clipboard. This allows clipboard content to be forwarded to the local machine via terminal multiplexers like tmux or screen. Fixes #559 Co-Authored-By: Claude Opus 4.5 --- go.mod | 2 +- internal/config/parser.go | 1 + internal/tui/modelUtils.go | 14 ++++++++++++++ internal/tui/ui.go | 14 ++++++++++++-- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ea7672445..032df3636 100644 --- a/go.mod +++ b/go.mod @@ -68,7 +68,7 @@ require ( require ( github.com/alecthomas/chroma/v2 v2.19.0 // indirect github.com/aymanbagabas/git-module v1.8.4-0.20231101154130-8d27204ac6d2 - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/x/ansi v0.10.1 github.com/cli/browser v1.3.0 // indirect diff --git a/internal/config/parser.go b/internal/config/parser.go index ed7dfd713..569bdf75a 100644 --- a/internal/config/parser.go +++ b/internal/config/parser.go @@ -325,6 +325,7 @@ type Config struct { Theme *ThemeConfig `yaml:"theme,omitempty" validate:"omitempty"` Pager Pager `yaml:"pager"` ConfirmQuit bool `yaml:"confirmQuit"` + OscClipboard bool `yaml:"oscClipboard"` ShowAuthorIcons bool `yaml:"showAuthorIcons,omitempty"` SmartFilteringAtLaunch bool `yaml:"smartFilteringAtLaunch" default:"true"` IncludeReadNotifications bool `yaml:"includeReadNotifications" default:"true"` diff --git a/internal/tui/modelUtils.go b/internal/tui/modelUtils.go index dca13210f..83982d56c 100644 --- a/internal/tui/modelUtils.go +++ b/internal/tui/modelUtils.go @@ -11,6 +11,7 @@ import ( "text/template" "time" + "github.com/aymanbagabas/go-osc52/v2" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" log "github.com/charmbracelet/log" @@ -26,6 +27,19 @@ import ( "github.com/dlvhdr/gh-dash/v4/internal/tui/markdown" ) +func oscCopy(text string) error { + s := osc52.New(text) + + if _, ok := os.LookupEnv("TMUX"); ok { + s = s.Tmux() + } else if _, ok := os.LookupEnv("STY"); ok { + s = s.Screen() + } + + _, err := s.WriteTo(os.Stdout) + return err +} + func (m *Model) getCurrSection() section.Section { sections := m.getCurrentViewSections() if len(sections) == 0 || m.currSectionId >= len(sections) { diff --git a/internal/tui/ui.go b/internal/tui/ui.go index 2ee75065b..267213b9a 100644 --- a/internal/tui/ui.go +++ b/internal/tui/ui.go @@ -304,7 +304,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } number := fmt.Sprint(currRowData.GetNumber()) - err := clipboard.WriteAll(number) + var err error + if m.ctx.Config.OscClipboard { + err = oscCopy(number) + } else { + err = clipboard.WriteAll(number) + } if err != nil { cmd = m.notifyErr(fmt.Sprintf("Failed copying to clipboard %v", err)) } else { @@ -319,7 +324,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } url := currRowData.GetUrl() - err := clipboard.WriteAll(url) + var err error + if m.ctx.Config.OscClipboard { + err = oscCopy(url) + } else { + err = clipboard.WriteAll(url) + } if err != nil { cmd = m.notifyErr(fmt.Sprintf("Failed copying to clipboard %v", err)) } else {