feat: auto-copy mouse selection + OSC 52 tmux clipboard bridge
Mouse selection: onSelectionChange auto-copies to system clipboard on every selection change (last fire on mouse-up captures final text). Matches iTerm2/WezTerm/ttyd native select-to-copy behavior. OSC 52: Custom parser.registerOscHandler(52, ...) decodes base64 clipboard data from tmux. When tmux copies text (set-clipboard on in .tmux.conf), it sends OSC 52 to the terminal. We intercept and write to system clipboard via _copyToClipboard (with execCommand fallback for HTTP/LAN contexts). Together: mouse-select auto-copies, and tmux Ctrl+B [ -> select -> Enter copies to system clipboard via OSC 52.
This commit is contained in:
@@ -312,6 +312,36 @@ function openTerminal(sessionName, sourceUrl) {
|
||||
return true; // let xterm handle all other keys normally
|
||||
});
|
||||
|
||||
// Auto-copy: when mouse selection ends, copy to system clipboard.
|
||||
// Matches terminal emulator conventions (iTerm2, WezTerm, ttyd native).
|
||||
// onSelectionChange fires whenever selection changes — copy if text is selected.
|
||||
// When selection is cleared (empty string), we skip the clipboard write.
|
||||
_term.onSelectionChange(function() {
|
||||
var sel = _term.getSelection();
|
||||
if (sel) {
|
||||
_copyToClipboard(sel);
|
||||
}
|
||||
});
|
||||
|
||||
// OSC 52 clipboard integration — bridges tmux clipboard to the browser.
|
||||
// When tmux copies text (with `set-clipboard on` in .tmux.conf), it sends
|
||||
// an OSC 52 escape sequence to the terminal. xterm.js surfaces this via the
|
||||
// parser API. We intercept and write the decoded text to the system clipboard
|
||||
// so that: Ctrl+B [ → select → Enter (tmux copy) → system clipboard receives it.
|
||||
_term.parser.registerOscHandler(52, function(data) {
|
||||
// OSC 52 format: Pc ; Pd — Pc = selection target (c/p/q/s/0-7), Pd = base64 text
|
||||
var parts = data.split(';');
|
||||
if (parts.length >= 2) {
|
||||
try {
|
||||
var text = atob(parts[1]);
|
||||
_copyToClipboard(text);
|
||||
} catch (e) {
|
||||
// Invalid base64 or unsupported — silently ignore
|
||||
}
|
||||
}
|
||||
return true; // Handled — don't pass to xterm's default handler
|
||||
});
|
||||
|
||||
if (_fitAddon) {
|
||||
// requestAnimationFrame guarantees one full browser layout pass after the flex
|
||||
// container becomes visible before fit() measures dimensions.
|
||||
|
||||
@@ -49,6 +49,8 @@ function loadTerminal() {
|
||||
focus: () => { focusCallCount++; },
|
||||
attachCustomKeyEventHandler: () => {},
|
||||
getSelection: () => '',
|
||||
onSelectionChange: () => {},
|
||||
parser: { registerOscHandler: () => {} },
|
||||
};
|
||||
|
||||
// Capture all messages sent via WebSocket.send()
|
||||
@@ -335,6 +337,8 @@ function createMultiSessionEnv() {
|
||||
focus: () => {},
|
||||
attachCustomKeyEventHandler: () => {},
|
||||
getSelection: () => '',
|
||||
onSelectionChange: () => {},
|
||||
parser: { registerOscHandler: () => {} },
|
||||
writeMessages: [],
|
||||
};
|
||||
t.write = (data) => t.writeMessages.push(data);
|
||||
@@ -921,4 +925,28 @@ test('_setTerminalFontSize sets _term.options.fontSize and calls _fitAddon.fit()
|
||||
);
|
||||
});
|
||||
|
||||
// --- Clipboard Issue 1: auto-copy mouse selection via onSelectionChange ---
|
||||
|
||||
test('terminal.js auto-copies mouse selection to clipboard via onSelectionChange', () => {
|
||||
const source = fs.readFileSync(new URL('../terminal.js', import.meta.url), 'utf8');
|
||||
assert.ok(
|
||||
source.includes('onSelectionChange'),
|
||||
'must register onSelectionChange handler to auto-copy mouse selection to clipboard',
|
||||
);
|
||||
});
|
||||
|
||||
// --- Clipboard Issue 2: OSC 52 handler bridges tmux clipboard to browser ---
|
||||
|
||||
test('terminal.js registers OSC 52 handler for tmux clipboard bridge', () => {
|
||||
const source = fs.readFileSync(new URL('../terminal.js', import.meta.url), 'utf8');
|
||||
assert.ok(
|
||||
source.includes('registerOscHandler'),
|
||||
'must call parser.registerOscHandler to intercept tmux OSC 52 clipboard sequences',
|
||||
);
|
||||
assert.ok(
|
||||
source.includes('atob'),
|
||||
'must decode base64 OSC 52 clipboard payload with atob()',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user