-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
osc52.go
executable file
·302 lines (275 loc) · 7.73 KB
/
osc52.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
// OSC52 is a terminal escape sequence that allows copying text to the clipboard.
//
// The sequence consists of the following:
//
// OSC 52 ; Pc ; Pd BEL
//
// Pc is the clipboard choice:
//
// c: clipboard
// p: primary
// q: secondary (not supported)
// s: select (not supported)
// 0-7: cut-buffers (not supported)
//
// Pd is the data to copy to the clipboard. This string should be encoded in
// base64 (RFC-4648).
//
// If Pd is "?", the terminal replies to the host with the current contents of
// the clipboard.
//
// If Pd is neither a base64 string nor "?", the terminal clears the clipboard.
//
// See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
// where Ps = 52 => Manipulate Selection Data.
//
// Examples:
//
// // copy "hello world" to the system clipboard
// fmt.Fprint(os.Stderr, osc52.New("hello world"))
//
// // copy "hello world" to the primary Clipboard
// fmt.Fprint(os.Stderr, osc52.New("hello world").Primary())
//
// // limit the size of the string to copy 10 bytes
// fmt.Fprint(os.Stderr, osc52.New("0123456789").Limit(10))
//
// // escape the OSC52 sequence for screen using DCS sequences
// fmt.Fprint(os.Stderr, osc52.New("hello world").Screen())
//
// // escape the OSC52 sequence for Tmux
// fmt.Fprint(os.Stderr, osc52.New("hello world").Tmux())
//
// // query the system Clipboard
// fmt.Fprint(os.Stderr, osc52.Query())
//
// // query the primary clipboard
// fmt.Fprint(os.Stderr, osc52.Query().Primary())
//
// // clear the system Clipboard
// fmt.Fprint(os.Stderr, osc52.Clear())
//
// // clear the primary Clipboard
// fmt.Fprint(os.Stderr, osc52.Clear().Primary())
package osc52
import (
"encoding/base64"
"fmt"
"io"
"strings"
)
// Clipboard is the clipboard buffer to use.
type Clipboard rune
const (
// SystemClipboard is the system clipboard buffer.
SystemClipboard Clipboard = 'c'
// PrimaryClipboard is the primary clipboard buffer (X11).
PrimaryClipboard = 'p'
)
// Mode is the mode to use for the OSC52 sequence.
type Mode uint
const (
// DefaultMode is the default OSC52 sequence mode.
DefaultMode Mode = iota
// ScreenMode escapes the OSC52 sequence for screen using DCS sequences.
ScreenMode
// TmuxMode escapes the OSC52 sequence for tmux. Not needed if tmux
// clipboard is set to `set-clipboard on`
TmuxMode
)
// Operation is the OSC52 operation.
type Operation uint
const (
// SetOperation is the copy operation.
SetOperation Operation = iota
// QueryOperation is the query operation.
QueryOperation
// ClearOperation is the clear operation.
ClearOperation
)
// Sequence is the OSC52 sequence.
type Sequence struct {
str string
limit int
op Operation
mode Mode
clipboard Clipboard
}
var _ fmt.Stringer = Sequence{}
var _ io.WriterTo = Sequence{}
// String returns the OSC52 sequence.
func (s Sequence) String() string {
var seq strings.Builder
// mode escape sequences start
seq.WriteString(s.seqStart())
// actual OSC52 sequence start
seq.WriteString(fmt.Sprintf("\x1b]52;%c;", s.clipboard))
switch s.op {
case SetOperation:
str := s.str
if s.limit > 0 && len(str) > s.limit {
return ""
}
b64 := base64.StdEncoding.EncodeToString([]byte(str))
switch s.mode {
case ScreenMode:
// Screen doesn't support OSC52 but will pass the contents of a DCS
// sequence to the outer terminal unchanged.
//
// Here, we split the encoded string into 76 bytes chunks and then
// join the chunks with <end-dsc><start-dsc> sequences. Finally,
// wrap the whole thing in
// <start-dsc><start-osc52><joined-chunks><end-osc52><end-dsc>.
// s := strings.SplitN(b64, "", 76)
s := make([]string, 0, len(b64)/76+1)
for i := 0; i < len(b64); i += 76 {
end := i + 76
if end > len(b64) {
end = len(b64)
}
s = append(s, b64[i:end])
}
seq.WriteString(strings.Join(s, "\x1b\\\x1bP"))
default:
seq.WriteString(b64)
}
case QueryOperation:
// OSC52 queries the clipboard using "?"
seq.WriteString("?")
case ClearOperation:
// OSC52 clears the clipboard if the data is neither a base64 string nor "?"
// we're using "!" as a default
seq.WriteString("!")
}
// actual OSC52 sequence end
seq.WriteString("\x07")
// mode escape end
seq.WriteString(s.seqEnd())
return seq.String()
}
// WriteTo writes the OSC52 sequence to the writer.
func (s Sequence) WriteTo(out io.Writer) (int64, error) {
n, err := out.Write([]byte(s.String()))
return int64(n), err
}
// Mode sets the mode for the OSC52 sequence.
func (s Sequence) Mode(m Mode) Sequence {
s.mode = m
return s
}
// Tmux sets the mode to TmuxMode.
// Used to escape the OSC52 sequence for `tmux`.
//
// Note: this is not needed if tmux clipboard is set to `set-clipboard on`. If
// TmuxMode is used, tmux must have `allow-passthrough on` set.
//
// This is a syntactic sugar for s.Mode(TmuxMode).
func (s Sequence) Tmux() Sequence {
return s.Mode(TmuxMode)
}
// Screen sets the mode to ScreenMode.
// Used to escape the OSC52 sequence for `screen`.
//
// This is a syntactic sugar for s.Mode(ScreenMode).
func (s Sequence) Screen() Sequence {
return s.Mode(ScreenMode)
}
// Clipboard sets the clipboard buffer for the OSC52 sequence.
func (s Sequence) Clipboard(c Clipboard) Sequence {
s.clipboard = c
return s
}
// Primary sets the clipboard buffer to PrimaryClipboard.
// This is the X11 primary clipboard.
//
// This is a syntactic sugar for s.Clipboard(PrimaryClipboard).
func (s Sequence) Primary() Sequence {
return s.Clipboard(PrimaryClipboard)
}
// Limit sets the limit for the OSC52 sequence.
// The default limit is 0 (no limit).
//
// Strings longer than the limit get ignored. Settting the limit to 0 or a
// negative value disables the limit. Each terminal defines its own escapse
// sequence limit.
func (s Sequence) Limit(l int) Sequence {
if l < 0 {
s.limit = 0
} else {
s.limit = l
}
return s
}
// Operation sets the operation for the OSC52 sequence.
// The default operation is SetOperation.
func (s Sequence) Operation(o Operation) Sequence {
s.op = o
return s
}
// Clear sets the operation to ClearOperation.
// This clears the clipboard.
//
// This is a syntactic sugar for s.Operation(ClearOperation).
func (s Sequence) Clear() Sequence {
return s.Operation(ClearOperation)
}
// Query sets the operation to QueryOperation.
// This queries the clipboard contents.
//
// This is a syntactic sugar for s.Operation(QueryOperation).
func (s Sequence) Query() Sequence {
return s.Operation(QueryOperation)
}
// SetString sets the string for the OSC52 sequence. Strings are joined with a
// space character.
func (s Sequence) SetString(strs ...string) Sequence {
s.str = strings.Join(strs, " ")
return s
}
// New creates a new OSC52 sequence with the given string(s). Strings are
// joined with a space character.
func New(strs ...string) Sequence {
s := Sequence{
str: strings.Join(strs, " "),
limit: 0,
mode: DefaultMode,
clipboard: SystemClipboard,
op: SetOperation,
}
return s
}
// Query creates a new OSC52 sequence with the QueryOperation.
// This returns a new OSC52 sequence to query the clipboard contents.
//
// This is a syntactic sugar for New().Query().
func Query() Sequence {
return New().Query()
}
// Clear creates a new OSC52 sequence with the ClearOperation.
// This returns a new OSC52 sequence to clear the clipboard.
//
// This is a syntactic sugar for New().Clear().
func Clear() Sequence {
return New().Clear()
}
func (s Sequence) seqStart() string {
switch s.mode {
case TmuxMode:
// Write the start of a tmux DCS escape sequence.
return "\x1bPtmux;\x1b"
case ScreenMode:
// Write the start of a DCS sequence.
return "\x1bP"
default:
return ""
}
}
func (s Sequence) seqEnd() string {
switch s.mode {
case TmuxMode, ScreenMode:
// Terminate the DCS escape sequence.
return "\x1b\\"
default:
return ""
}
}