fix: use _term.paste() for Ctrl+Shift+V — enables bracketed paste mode
Raw WebSocket send bypassed xterm.js's paste pipeline, so multiline text was sent without bracketed paste markers (ESC[200~...ESC[201~). The shell treated each newline as Enter, executing lines separately. Fix: _term.paste(text) goes through the proper pipeline — converts CR/LF, wraps in bracketed paste markers, then fires onData → WebSocket.
This commit is contained in:
@@ -375,8 +375,12 @@ function openTerminal(sessionName, remoteId) {
|
||||
e.preventDefault(); // suppress browser native paste event (prevents double-paste via xterm textarea)
|
||||
if (navigator.clipboard && navigator.clipboard.readText) {
|
||||
navigator.clipboard.readText().then(function(text) {
|
||||
if (text && _ws && _ws.readyState === WebSocket.OPEN) {
|
||||
_ws.send(_encodePayload(0x30, text));
|
||||
if (text && _term) {
|
||||
// Use xterm.js paste() pipeline: converts \r\n→\r, wraps in bracketed
|
||||
// paste markers (ESC[200~...ESC[201~) so the shell treats multiline text
|
||||
// as a paste block rather than executing each line on Enter.
|
||||
// Fires onData → our WebSocket handler automatically.
|
||||
_term.paste(text);
|
||||
}
|
||||
}).catch(function() {});
|
||||
}
|
||||
|
||||
@@ -1012,6 +1012,33 @@ test('terminal.js Ctrl+Shift+V handler calls preventDefault to suppress double p
|
||||
);
|
||||
});
|
||||
|
||||
// --- Multiline paste (bracketed paste mode) ---
|
||||
|
||||
test('terminal.js Ctrl+Shift+V uses _term.paste() for bracketed paste mode', () => {
|
||||
// Regression: the handler sent raw bytes via _ws.send(_encodePayload(0x30, text)),
|
||||
// bypassing xterm.js's paste pipeline. Multiline text had no bracketed paste markers
|
||||
// (ESC[200~...ESC[201~), so the shell treated each \n as Enter — executing each line
|
||||
// separately instead of pasting as a block.
|
||||
// Fix: use _term.paste(text) which goes through the proper pipeline.
|
||||
const source = fs.readFileSync(new URL('../terminal.js', import.meta.url), 'utf8');
|
||||
|
||||
// Locate the Ctrl+Shift+V block
|
||||
const vIdx = source.indexOf("e.key === 'V'");
|
||||
assert.ok(vIdx !== -1, "must have a Ctrl+Shift+V key check");
|
||||
const blockStart = source.lastIndexOf('if (', vIdx);
|
||||
const blockEnd = source.indexOf('return false', vIdx);
|
||||
const vBlock = source.substring(blockStart, blockEnd);
|
||||
|
||||
assert.ok(
|
||||
vBlock.includes('_term.paste'),
|
||||
'Ctrl+Shift+V must use _term.paste() for proper bracketed paste mode support',
|
||||
);
|
||||
assert.ok(
|
||||
!vBlock.includes('_encodePayload') && !vBlock.includes('_ws.send'),
|
||||
'Ctrl+Shift+V must NOT send raw bytes via WebSocket (bypasses bracketed paste wrapping)',
|
||||
);
|
||||
});
|
||||
|
||||
// --- Federation reconnect routing ---
|
||||
|
||||
test('terminal.js reconnect uses federation connect path for remote sessions', () => {
|
||||
|
||||
Reference in New Issue
Block a user