Skip to content

Commit ec01e1a

Browse files
committed
feat: support OSC 52 clipboard passthrough in sidebar terminal
1 parent dd12d30 commit ec01e1a

4 files changed

Lines changed: 64 additions & 12 deletions

File tree

.github/workflows/build_vsix.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
6666
### What's New
6767
68-
- Supports code completion and Tab&Tab AI cross-file suggestions
68+
- support OSC 52 clipboard passthrough in sidebar terminal
6969
7070
### Installation
7171

VSIX/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

VSIX/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "snow-cli",
33
"displayName": "%extension.displayName%",
44
"description": "%extension.description%",
5-
"version": "0.4.25",
5+
"version": "0.4.26",
66
"publisher": "mufasa",
77
"icon": "snow.png",
88
"repository": {

VSIX/res/sidebarTerminal.js

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,57 @@
440440
const createClipboardAndContextController = ({term, sendInput}) => {
441441
const isMacPlatform = /mac/i.test(navigator.userAgent);
442442

443+
const writeClipboardText = (text, source) => {
444+
if (typeof text !== 'string' || text.length === 0) {
445+
return;
446+
}
447+
if (!navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') {
448+
logWarn('Clipboard write API is unavailable.', `source=${source}`);
449+
return;
450+
}
451+
452+
navigator.clipboard.writeText(text).catch(error => {
453+
logWarn('Failed to write text to clipboard.', {
454+
source,
455+
error: stringifyLogDetails(error),
456+
});
457+
});
458+
};
459+
460+
const registerOsc52ClipboardHandler = () => {
461+
const parser = term.parser;
462+
if (!parser || typeof parser.registerOscHandler !== 'function') {
463+
logWarn('OSC 52 clipboard passthrough is unavailable.');
464+
return undefined;
465+
}
466+
467+
return parser.registerOscHandler(52, data => {
468+
const parts = String(data).split(';');
469+
if (parts.length < 2) {
470+
return true;
471+
}
472+
473+
const base64 = parts.slice(1).join(';').trim();
474+
if (!base64 || base64 === '?') {
475+
return true;
476+
}
477+
478+
try {
479+
const binary = atob(base64);
480+
const bytes = new Uint8Array(binary.length);
481+
for (let index = 0; index < binary.length; index += 1) {
482+
bytes[index] = binary.charCodeAt(index);
483+
}
484+
const text = new TextDecoder('utf-8', {fatal: false}).decode(bytes);
485+
writeClipboardText(text, 'osc52');
486+
} catch (error) {
487+
logWarn('Failed to decode OSC 52 clipboard payload.', error);
488+
}
489+
490+
return true;
491+
});
492+
};
493+
443494
const shouldUseCtrlSelectionCopy = event => {
444495
if (
445496
isMacPlatform ||
@@ -470,9 +521,7 @@
470521
if (shouldUseCtrlSelectionCopy(event)) {
471522
const selection = term.getSelection();
472523
if (selection) {
473-
navigator.clipboard.writeText(selection).catch(() => {
474-
// Ignore clipboard write failures.
475-
});
524+
writeClipboardText(selection, 'selection-keyboard');
476525
}
477526
return false;
478527
}
@@ -483,9 +532,7 @@
483532
event.preventDefault();
484533
const selection = term.getSelection();
485534
if (selection) {
486-
navigator.clipboard.writeText(selection).catch(() => {
487-
// Ignore clipboard write failures.
488-
});
535+
writeClipboardText(selection, 'selection-context-menu');
489536
term.clearSelection();
490537
return;
491538
}
@@ -503,6 +550,7 @@
503550
return {
504551
allowTerminalKeyEvent,
505552
handleContextMenu,
553+
registerOsc52ClipboardHandler,
506554
};
507555
};
508556

@@ -1714,8 +1762,12 @@
17141762
addManagedListener(container, 'pointerdown', unlockTerminalAudio);
17151763
addManagedListener(container, 'keydown', unlockTerminalAudio);
17161764

1717-
const {allowTerminalKeyEvent, handleContextMenu} =
1718-
createClipboardAndContextController({term, sendInput});
1765+
const {
1766+
allowTerminalKeyEvent,
1767+
handleContextMenu,
1768+
registerOsc52ClipboardHandler,
1769+
} = createClipboardAndContextController({term, sendInput});
1770+
registerDisposable(registerOsc52ClipboardHandler());
17191771

17201772
// On macOS, Ctrl+V passes through to CLI which handles paste (including images).
17211773
// On Windows/Linux, Ctrl+V must be intercepted to suppress the raw \x16 that

0 commit comments

Comments
 (0)