UNPKG

@tsports/go-osc52

Version:

OSC52 terminal clipboard escape sequences - TypeScript port of go-osc52

255 lines (254 loc) 8.93 kB
// @tsports/go-osc52 - Core OSC52 implementation import { Clipboard, Mode, Operation } from './types.js'; /** * Sequence represents an OSC52 escape sequence for clipboard operations. * This class implements both Stringer and WriterTo interfaces for compatibility with Go patterns. */ export class Sequence { str; limit; op; mode; clipboard; /** * Creates a new Sequence instance with the given parameters. * @param str - The string to copy to clipboard * @param limit - Maximum string length (0 = no limit) * @param op - The operation type * @param mode - The terminal mode * @param clipboard - The clipboard buffer */ constructor(str = '', limit = 0, op = Operation.SetOperation, mode = Mode.DefaultMode, clipboard = Clipboard.SystemClipboard) { this.str = str; this.limit = limit; this.op = op; this.mode = mode; this.clipboard = clipboard; } /** * toString returns the OSC52 sequence as a string. * This implements the Stringer interface. */ toString() { let seq = ''; // Mode escape sequences start seq += this.seqStart(); // Actual OSC52 sequence start seq += `\x1b]52;${this.clipboard};`; switch (this.op) { case Operation.SetOperation: { const str = this.str; if (this.limit > 0 && str.length > this.limit) { return ''; } const b64 = Buffer.from(str, 'utf-8').toString('base64'); if (this.mode === Mode.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-dcs><start-dcs> sequences. const chunks = []; for (let i = 0; i < b64.length; i += 76) { const end = Math.min(i + 76, b64.length); chunks.push(b64.slice(i, end)); } seq += chunks.join('\x1b\\\x1bP'); } else { seq += b64; } break; } case Operation.QueryOperation: // OSC52 queries the clipboard using "?" seq += '?'; break; case Operation.ClearOperation: // OSC52 clears the clipboard if the data is neither a base64 string nor "?" // we're using "!" as a default seq += '!'; break; } // Actual OSC52 sequence end seq += '\x07'; // Mode escape end seq += this.seqEnd(); return seq; } /** * writeTo writes the OSC52 sequence to the provided writer. * This implements the WriterTo interface. * @param writer - The writer to write to * @returns Promise resolving to the number of bytes written */ async writeTo(writer) { const data = this.toString(); const buffer = Buffer.from(data, 'utf-8'); // Handle different writer types if (typeof writer.write === 'function') { writer.write(buffer); return buffer.length; } throw new Error('Invalid writer: must have write method'); } /** * mode sets the mode for the OSC52 sequence. * @param m - The mode to set * @returns A new Sequence instance with the updated mode */ withMode(m) { return new Sequence(this.str, this.limit, this.op, m, this.clipboard); } /** * 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 syntactic sugar for withMode(Mode.TmuxMode). * @returns A new Sequence instance with TmuxMode */ tmux() { return this.withMode(Mode.TmuxMode); } /** * screen sets the mode to ScreenMode. * Used to escape the OSC52 sequence for `screen`. * * This is syntactic sugar for withMode(Mode.ScreenMode). * @returns A new Sequence instance with ScreenMode */ screen() { return this.withMode(Mode.ScreenMode); } /** * withClipboard sets the clipboard buffer for the OSC52 sequence. * @param c - The clipboard buffer to use * @returns A new Sequence instance with the updated clipboard */ withClipboard(c) { return new Sequence(this.str, this.limit, this.op, this.mode, c); } /** * primary sets the clipboard buffer to PrimaryClipboard. * This is the X11 primary clipboard. * * This is syntactic sugar for withClipboard(Clipboard.PrimaryClipboard). * @returns A new Sequence instance with primary clipboard */ primary() { return this.withClipboard(Clipboard.PrimaryClipboard); } /** * withLimit sets the limit for the OSC52 sequence. * The default limit is 0 (no limit). * * Strings longer than the limit get ignored. Setting the limit to 0 or a * negative value disables the limit. Each terminal defines its own escape * sequence limit. * @param l - The limit to set * @returns A new Sequence instance with the updated limit */ withLimit(l) { const limit = l < 0 ? 0 : l; return new Sequence(this.str, limit, this.op, this.mode, this.clipboard); } /** * withOperation sets the operation for the OSC52 sequence. * The default operation is SetOperation. * @param o - The operation to set * @returns A new Sequence instance with the updated operation */ withOperation(o) { return new Sequence(this.str, this.limit, o, this.mode, this.clipboard); } /** * clear sets the operation to ClearOperation. * This clears the clipboard. * * This is syntactic sugar for withOperation(Operation.ClearOperation). * @returns A new Sequence instance with clear operation */ clear() { return this.withOperation(Operation.ClearOperation); } /** * query sets the operation to QueryOperation. * This queries the clipboard contents. * * This is syntactic sugar for withOperation(Operation.QueryOperation). * @returns A new Sequence instance with query operation */ query() { return this.withOperation(Operation.QueryOperation); } /** * withString sets the string for the OSC52 sequence. * @param strs - The strings to join with space character * @returns A new Sequence instance with the updated string */ withString(...strs) { const str = strs.join(' '); return new Sequence(str, this.limit, this.op, this.mode, this.clipboard); } /** * Private method to get the sequence start based on mode */ seqStart() { switch (this.mode) { case Mode.TmuxMode: // Write the start of a tmux DCS escape sequence. return '\x1bPtmux;\x1b'; case Mode.ScreenMode: // Write the start of a DCS sequence. return '\x1bP'; default: return ''; } } /** * Private method to get the sequence end based on mode */ seqEnd() { switch (this.mode) { case Mode.TmuxMode: case Mode.ScreenMode: // Terminate the DCS escape sequence. return '\x1b\\'; default: return ''; } } } /** * newSequence creates a new OSC52 sequence with the given string(s). * Strings are joined with a space character. * @param strs - The strings to include in the sequence * @returns A new Sequence instance */ export function newSequence(...strs) { const str = strs.join(' '); return new Sequence(str, 0, Operation.SetOperation, Mode.DefaultMode, Clipboard.SystemClipboard); } /** * querySequence creates a new OSC52 sequence with the QueryOperation. * This returns a new OSC52 sequence to query the clipboard contents. * * This is syntactic sugar for newSequence().query(). * @returns A new Sequence instance with query operation */ export function querySequence() { return newSequence().query(); } /** * clearSequence creates a new OSC52 sequence with the ClearOperation. * This returns a new OSC52 sequence to clear the clipboard. * * This is syntactic sugar for newSequence().clear(). * @returns A new Sequence instance with clear operation */ export function clearSequence() { return newSequence().clear(); }