...

Source file src/github.com/aymanbagabas/go-osc52/v2/osc52.go

Documentation: github.com/aymanbagabas/go-osc52/v2

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

View as plain text