fix(terminal): restore terminal text copy behavior (#452)
* fix(terminal): restore terminal text copy behavior * fix(desktop): route Cmd+C through terminal-aware copy handler on macOS - Use a custom macOS Copy menu action to dispatch app copy events first, so terminal selections copy without native “no target” beep, while preserving DOM copy fallback for non-terminal contexts. --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
7bcf848035
commit
881e8bdf4f
@@ -339,29 +339,55 @@ const TerminalViewport = React.forwardRef<TerminalController, TerminalViewportPr
|
||||
};
|
||||
}, [useHiddenInputOverlay, focusHiddenInput]);
|
||||
|
||||
const copySelectionToClipboard = React.useCallback(async () => {
|
||||
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
||||
return;
|
||||
const getTerminalSelectionText = React.useCallback((): string => {
|
||||
const terminal = terminalRef.current as unknown as {
|
||||
getSelection?: () => string;
|
||||
} | null;
|
||||
if (!terminal || typeof terminal.getSelection !== 'function') {
|
||||
return '';
|
||||
}
|
||||
const text = terminal.getSelection();
|
||||
return typeof text === 'string' ? text : '';
|
||||
}, []);
|
||||
|
||||
const getDomSelectionTextInViewport = React.useCallback((): string => {
|
||||
if (typeof window === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
const selection = window.getSelection();
|
||||
if (!selection) {
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
const text = selection.toString();
|
||||
if (!text.trim()) {
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
const container = containerRef.current;
|
||||
if (!container) {
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
const anchorNode = selection.anchorNode;
|
||||
const focusNode = selection.focusNode;
|
||||
if (anchorNode && !container.contains(anchorNode)) {
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
if (focusNode && !container.contains(focusNode)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return text;
|
||||
}, []);
|
||||
|
||||
const copySelectionToClipboard = React.useCallback(async () => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = getTerminalSelectionText() || getDomSelectionTextInViewport();
|
||||
if (!text.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -388,37 +414,34 @@ const TerminalViewport = React.forwardRef<TerminalController, TerminalViewportPr
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
}, [getDomSelectionTextInViewport, getTerminalSelectionText]);
|
||||
|
||||
const hasCopyableSelectionInViewport = React.useCallback((): boolean => {
|
||||
const terminalSelection = getTerminalSelectionText();
|
||||
if (terminalSelection.trim()) {
|
||||
return true;
|
||||
}
|
||||
return Boolean(getDomSelectionTextInViewport().trim());
|
||||
}, [getDomSelectionTextInViewport, getTerminalSelectionText]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
const selection = window.getSelection();
|
||||
if (!selection) {
|
||||
return false;
|
||||
}
|
||||
const text = selection.toString();
|
||||
if (!text.trim()) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
const container = containerRef.current;
|
||||
if (!container) {
|
||||
return false;
|
||||
}
|
||||
const handleMenuCopy = (event: Event) => {
|
||||
if (!hasCopyableSelectionInViewport()) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
void copySelectionToClipboard();
|
||||
};
|
||||
|
||||
const anchorNode = selection.anchorNode;
|
||||
const focusNode = selection.focusNode;
|
||||
if (anchorNode && !container.contains(anchorNode)) {
|
||||
return false;
|
||||
}
|
||||
if (focusNode && !container.contains(focusNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, []);
|
||||
window.addEventListener('openchamber:copy', handleMenuCopy);
|
||||
return () => {
|
||||
window.removeEventListener('openchamber:copy', handleMenuCopy);
|
||||
};
|
||||
}, [copySelectionToClipboard, hasCopyableSelectionInViewport]);
|
||||
|
||||
const resetWriteState = React.useCallback(() => {
|
||||
pendingWriteRef.current = '';
|
||||
|
||||
Reference in New Issue
Block a user