forked from toolshed/abra
		
	
		
			
				
	
	
		
			306 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			306 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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 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:
 | 
						|
		// Terminate the tmux escape sequence.
 | 
						|
		return "\x1b\\"
 | 
						|
	case ScreenMode:
 | 
						|
		// Write the end of a DCS sequence.
 | 
						|
		return "\x1b\x5c"
 | 
						|
	default:
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
}
 |