v0.14.0 76d1892

clipboard.go RawHistoryFolder

1package clipboard
2
3import (
4	"fmt"
5	"log"
6	"os/exec"
7)
8
9// CopyToClipboard copies the given text to the system clipboard.
10// It tries wl-copy first (Wayland), then falls back to xclip (X11).
11// Returns an error if both fail.
12func CopyToClipboard(text string) error {
13	if text == "" {
14		return fmt.Errorf("cannot copy empty text to clipboard")
15	}
16
17	// Try wl-copy first (Wayland)
18	if err := tryWlCopy(text); err == nil {
19		return nil
20	}
21
22	// Fallback to xclip (X11)
23	if err := tryXclip(text); err == nil {
24		return nil
25	}
26
27	return fmt.Errorf("clipboard copy failed: neither wl-copy nor xclip available")
28}
29
30// tryWlCopy attempts to copy text using wl-copy (Wayland clipboard)
31func tryWlCopy(text string) error {
32	cmd := exec.Command("wl-copy")
33	cmd.Stdin = nil
34
35	// Set up stdin pipe to pass the text
36	stdin, err := cmd.StdinPipe()
37	if err != nil {
38		return fmt.Errorf("failed to create stdin pipe for wl-copy: %w", err)
39	}
40
41	// Start the command
42	if err := cmd.Start(); err != nil {
43		if closeErr := stdin.Close(); closeErr != nil {
44			log.Printf("Warning: failed to close stdin: %v", closeErr)
45		}
46		return fmt.Errorf("failed to start wl-copy: %w", err)
47	}
48
49	// Write text to stdin and close it
50	_, writeErr := stdin.Write([]byte(text))
51	closeErr := stdin.Close()
52
53	// Wait for command to complete
54	waitErr := cmd.Wait()
55
56	// Return the first error encountered
57	if writeErr != nil {
58		return fmt.Errorf("failed to write to wl-copy stdin: %w", writeErr)
59	}
60	if closeErr != nil {
61		return fmt.Errorf("failed to close wl-copy stdin: %w", closeErr)
62	}
63	if waitErr != nil {
64		return fmt.Errorf("wl-copy command failed: %w", waitErr)
65	}
66
67	return nil
68}
69
70// tryXclip attempts to copy text using xclip (X11 clipboard)
71func tryXclip(text string) error {
72	cmd := exec.Command("xclip", "-selection", "clipboard")
73
74	// Set up stdin pipe to pass the text
75	stdin, err := cmd.StdinPipe()
76	if err != nil {
77		return fmt.Errorf("failed to create stdin pipe for xclip: %w", err)
78	}
79
80	// Start the command
81	if err := cmd.Start(); err != nil {
82		if closeErr := stdin.Close(); closeErr != nil {
83			log.Printf("Warning: failed to close stdin: %v", closeErr)
84		}
85		return fmt.Errorf("failed to start xclip: %w", err)
86	}
87
88	// Write text to stdin and close it
89	_, writeErr := stdin.Write([]byte(text))
90	closeErr := stdin.Close()
91
92	// Wait for command to complete
93	waitErr := cmd.Wait()
94
95	// Return the first error encountered
96	if writeErr != nil {
97		return fmt.Errorf("failed to write to xclip stdin: %w", writeErr)
98	}
99	if closeErr != nil {
100		return fmt.Errorf("failed to close xclip stdin: %w", closeErr)
101	}
102	if waitErr != nil {
103		return fmt.Errorf("xclip command failed: %w", waitErr)
104	}
105
106	return nil
107}
108
109// IsAvailable checks if clipboard functionality is available on the system
110func IsAvailable() bool {
111	// Check if wl-copy is available
112	if _, err := exec.LookPath("wl-copy"); err == nil {
113		return true
114	}
115
116	// Check if xclip is available
117	if _, err := exec.LookPath("xclip"); err == nil {
118		return true
119	}
120
121	return false
122}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close