fix: hybrid paste — bracketed paste markers + direct UTF-8 WebSocket send
_term.paste() garbled Unicode box-drawing characters (â instead of ─│┌┐). Fix: manually apply bracketed paste wrapping (ESC[200~...ESC[201~) for multiline protection, then send via _encodePayload/TextEncoder for correct UTF-8 encoding. Combines the multiline fix with the proven encoding path.
This commit is contained in:
@@ -375,12 +375,19 @@ function openTerminal(sessionName, remoteId) {
|
|||||||
e.preventDefault(); // suppress browser native paste event (prevents double-paste via xterm textarea)
|
e.preventDefault(); // suppress browser native paste event (prevents double-paste via xterm textarea)
|
||||||
if (navigator.clipboard && navigator.clipboard.readText) {
|
if (navigator.clipboard && navigator.clipboard.readText) {
|
||||||
navigator.clipboard.readText().then(function(text) {
|
navigator.clipboard.readText().then(function(text) {
|
||||||
if (text && _term) {
|
if (text && _ws && _ws.readyState === WebSocket.OPEN) {
|
||||||
// Use xterm.js paste() pipeline: converts \r\n→\r, wraps in bracketed
|
// Hybrid paste: manual bracketed paste wrapping + direct UTF-8 WebSocket send.
|
||||||
// paste markers (ESC[200~...ESC[201~) so the shell treats multiline text
|
// The xterm.js paste pipeline garbled Unicode box-drawing chars (â instead of
|
||||||
// as a paste block rather than executing each line on Enter.
|
// ─│┌┐) because its internals process text differently than TextEncoder encoding.
|
||||||
// Fires onData → our WebSocket handler automatically.
|
//
|
||||||
_term.paste(text);
|
// This approach gives us both:
|
||||||
|
// 1. \r\n→\r conversion (same as xterm.js prepareTextForTerminal)
|
||||||
|
// 2. Bracketed paste markers (ESC[200~...ESC[201~) so shells with bracketed
|
||||||
|
// paste mode treat the block as pasted text, not individual Enter-submitted lines
|
||||||
|
// 3. Direct _encodePayload/TextEncoder path → correct UTF-8 bytes (proven path)
|
||||||
|
var prepared = text.replace(/\r?\n/g, '\r');
|
||||||
|
var payload = '\x1b[200~' + prepared + '\x1b[201~';
|
||||||
|
_ws.send(_encodePayload(0x30, payload));
|
||||||
}
|
}
|
||||||
}).catch(function() {});
|
}).catch(function() {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1014,12 +1014,12 @@ test('terminal.js Ctrl+Shift+V handler calls preventDefault to suppress double p
|
|||||||
|
|
||||||
// --- Multiline paste (bracketed paste mode) ---
|
// --- Multiline paste (bracketed paste mode) ---
|
||||||
|
|
||||||
test('terminal.js Ctrl+Shift+V uses _term.paste() for bracketed paste mode', () => {
|
test('terminal.js Ctrl+Shift+V uses bracketed paste wrapping (multiline protection)', () => {
|
||||||
// Regression: the handler sent raw bytes via _ws.send(_encodePayload(0x30, text)),
|
// Regression history:
|
||||||
// bypassing xterm.js's paste pipeline. Multiline text had no bracketed paste markers
|
// v1 — raw _ws.send(): no bracketed paste markers, each \n executed as Enter
|
||||||
// (ESC[200~...ESC[201~), so the shell treated each \n as Enter — executing each line
|
// v2 — _term.paste(): bracketed paste mode works but garbles Unicode box-drawing chars
|
||||||
// separately instead of pasting as a block.
|
// v3 (current) — hybrid: manual bracketed markers + direct UTF-8 WebSocket send
|
||||||
// Fix: use _term.paste(text) which goes through the proper pipeline.
|
// This test validates the multiline protection aspect of v3.
|
||||||
const source = fs.readFileSync(new URL('../terminal.js', import.meta.url), 'utf8');
|
const source = fs.readFileSync(new URL('../terminal.js', import.meta.url), 'utf8');
|
||||||
|
|
||||||
// Locate the Ctrl+Shift+V block
|
// Locate the Ctrl+Shift+V block
|
||||||
@@ -1030,12 +1030,37 @@ test('terminal.js Ctrl+Shift+V uses _term.paste() for bracketed paste mode', ()
|
|||||||
const vBlock = source.substring(blockStart, blockEnd);
|
const vBlock = source.substring(blockStart, blockEnd);
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
vBlock.includes('_term.paste'),
|
vBlock.includes('\\x1b[200~') || vBlock.includes('\\u001b[200~') || vBlock.includes('[200~'),
|
||||||
'Ctrl+Shift+V must use _term.paste() for proper bracketed paste mode support',
|
'Ctrl+Shift+V must include bracketed paste markers (ESC[200~...ESC[201~) for multiline protection',
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Hybrid paste: bracketed markers + direct WebSocket send (Unicode fix) ---
|
||||||
|
|
||||||
|
test('terminal.js Ctrl+Shift+V uses bracketed paste with direct WebSocket send', () => {
|
||||||
|
// Regression: _term.paste() garbled Unicode box-drawing characters (â instead of ─│┌┐).
|
||||||
|
// Fix: manually apply bracketed paste wrapping (ESC[200~...ESC[201~) for multiline
|
||||||
|
// protection, then send via _encodePayload/TextEncoder for correct UTF-8 encoding.
|
||||||
|
const source = fs.readFileSync(new URL('../terminal.js', import.meta.url), 'utf8');
|
||||||
|
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);
|
||||||
|
// Must have bracketed paste markers
|
||||||
assert.ok(
|
assert.ok(
|
||||||
!vBlock.includes('_encodePayload') && !vBlock.includes('_ws.send'),
|
vBlock.includes('\\x1b[200~') || vBlock.includes('\\u001b[200~') || vBlock.includes('[200~'),
|
||||||
'Ctrl+Shift+V must NOT send raw bytes via WebSocket (bypasses bracketed paste wrapping)',
|
'Ctrl+Shift+V must include bracketed paste markers for multiline protection',
|
||||||
|
);
|
||||||
|
// Must send via WebSocket (not _term.paste)
|
||||||
|
assert.ok(
|
||||||
|
vBlock.includes('_ws.send') || vBlock.includes('_encodePayload'),
|
||||||
|
'Ctrl+Shift+V must send via WebSocket for correct UTF-8 encoding',
|
||||||
|
);
|
||||||
|
// Must NOT use _term.paste (causes Unicode garbling)
|
||||||
|
assert.ok(
|
||||||
|
!vBlock.includes('_term.paste'),
|
||||||
|
'Ctrl+Shift+V must NOT use _term.paste() — it garbles Unicode characters',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user