Skip to content

Commit bdeb96f

Browse files
committed
refactor(actions): split clipboard and save-to-file into separate keybindings
c: copies to clipboard (native tools → OSC 52, errors if nothing available). w: always writes a temp file and shows the path. Removes the silent fallback where clipboard failure would silently produce a file instead, making the two operations explicit and independently reachable.
1 parent 49197e8 commit bdeb96f

1 file changed

Lines changed: 31 additions & 28 deletions

File tree

actions/view.go

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -813,14 +813,11 @@ func (m *actionsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
813813
case "c":
814814
if m.focusTask != "" {
815815
text := m.buildCopyText(m.focusTask)
816-
res := copyToClipboardOrFile(context.Background(), text, m.focusTask)
817-
if res.err != nil {
818-
m.clipboardMsg = res.err.Error()
816+
method, err := copyToClipboard(context.Background(), text)
817+
if err != nil {
818+
m.clipboardMsg = "no clipboard tool available — use w to save to file"
819819
m.clipboardOK = false
820-
} else if res.method == "file" {
821-
m.clipboardMsg = "saved to " + res.path
822-
m.clipboardOK = true
823-
} else if res.method == "osc52" {
820+
} else if method == "osc52" {
824821
m.clipboardMsg = "sent via OSC 52 (terminal clipboard)"
825822
m.clipboardOK = true
826823
} else {
@@ -829,6 +826,19 @@ func (m *actionsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
829826
}
830827
m.clipboardMsgExpiry = time.Now().Add(4 * time.Second)
831828
}
829+
case "w":
830+
if m.focusTask != "" {
831+
text := m.buildCopyText(m.focusTask)
832+
res := writeToTempFile(text, m.focusTask)
833+
if res.err != nil {
834+
m.clipboardMsg = res.err.Error()
835+
m.clipboardOK = false
836+
} else {
837+
m.clipboardMsg = "saved to " + res.path
838+
m.clipboardOK = true
839+
}
840+
m.clipboardMsgExpiry = time.Now().Add(4 * time.Second)
841+
}
832842
case "r":
833843
if t := m.cursorTask; t != "" {
834844
if e, ok := m.taskState[t]; ok && e.state == actionFailed {
@@ -1149,6 +1159,7 @@ func (m *actionsModel) View() tea.View {
11491159
if m.focusTask != "" {
11501160
parts = append(parts, "Enter/f: all logs")
11511161
parts = append(parts, "c: copy logs")
1162+
parts = append(parts, "w: save to file")
11521163
} else {
11531164
parts = append(parts, "Enter/f: focus logs")
11541165
}
@@ -1532,33 +1543,25 @@ func stripActANSI(s string) string {
15321543
return b.String()
15331544
}
15341545

1535-
// copyResult holds the outcome of a copy operation.
1546+
// copyResult holds the outcome of a file-save operation.
15361547
type copyResult struct {
1537-
method string // "clipboard", "file"
1538-
path string // only set when method == "file"
1539-
err error
1548+
path string
1549+
err error
15401550
}
15411551

1542-
// copyToClipboardOrFile tries: native clipboard tools → OSC 52 → temp file.
1543-
// Native tools are tried first because they give a real exit-code signal.
1544-
// OSC 52 has no feedback mechanism (terminals silently ignore unsupported
1545-
// sequences), so it is used as a best-effort fallback for SSH sessions where
1546-
// native tools are unavailable.
1547-
func copyToClipboardOrFile(ctx context.Context, text, taskName string) copyResult {
1548-
// 1. Native clipboard tools — reliable success/failure via exit code.
1552+
// copyToClipboard tries native clipboard tools first, then OSC 52 as a
1553+
// best-effort fallback for SSH sessions. Returns the method used ("clipboard"
1554+
// or "osc52"), or an error if no tool is available.
1555+
func copyToClipboard(ctx context.Context, text string) (string, error) {
15491556
if tryNativeClipboard(ctx, text) {
1550-
return copyResult{method: "clipboard"}
1557+
return "clipboard", nil
15511558
}
1552-
1553-
// 2. OSC 52: best-effort for SSH sessions. The sequence is written to the
1554-
// terminal but there is no way to confirm the terminal honored it. We fall
1555-
// through to a temp file only if stdout is not a terminal at all.
1559+
// OSC 52 has no feedback mechanism — terminals silently ignore unsupported
1560+
// sequences — but it is useful over SSH where native tools are absent.
15561561
if tryOSC52(text) {
1557-
return copyResult{method: "osc52"}
1562+
return "osc52", nil
15581563
}
1559-
1560-
// 3. Fallback: write to temp file.
1561-
return writeToTempFile(text, taskName)
1564+
return "", fmt.Errorf("no clipboard tool available")
15621565
}
15631566

15641567
// tryOSC52 writes the OSC 52 escape sequence to stdout.
@@ -1625,7 +1628,7 @@ func writeToTempFile(text, taskName string) copyResult {
16251628
if _, err := f.WriteString(text); err != nil {
16261629
return copyResult{err: fmt.Errorf("failed to write temp file: %w", err)}
16271630
}
1628-
return copyResult{method: "file", path: f.Name()}
1631+
return copyResult{path: f.Name()}
16291632
}
16301633

16311634
func fmtActionDuration(d time.Duration) string {

0 commit comments

Comments
 (0)