Skip to content

Fix copy/paste behavior when interacting with terminals #498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 109 additions & 17 deletions gdbgui/src/js/Terminals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { store } from "statorgfc";
import "xterm/css/xterm.css";
import constants from "./constants";
import Actions from "./Actions";
import ToolTip from "./ToolTip";

function customKeyEventHandler(config: {
pty_name: string;
Expand All @@ -17,22 +18,76 @@ function customKeyEventHandler(config: {
if (!(e.type === "keydown")) {
return true;
}
if (e.shiftKey && e.ctrlKey) {
if (e.ctrlKey && e.altKey) {
const key = e.key.toLowerCase();
if (key === "c") {
const toCopy = config.pty.getSelection();
navigator.clipboard.writeText(toCopy);
try {
await navigator.clipboard.writeText(toCopy);
// Show toast message at cursor position
const target = e.target as HTMLElement;
if (target) {
const rect = target.getBoundingClientRect();
const node = {
getBoundingClientRect: () => ({
x: rect.x,
y: rect.y,
offsetHeight: rect.height
})
};
ToolTip.show_copied_tooltip_on_node(node);
}
} catch (err) {
// Fallback for browsers that don't support clipboard API
const textarea = document.createElement('textarea');
textarea.value = toCopy;
document.body.appendChild(textarea);
textarea.select();
const success = document.execCommand('copy');
document.body.removeChild(textarea);

// Show toast message at cursor position
const target = e.target as HTMLElement;
if (target) {
const rect = target.getBoundingClientRect();
const node = {
getBoundingClientRect: () => ({
x: rect.x,
y: rect.y,
offsetHeight: rect.height
})
};
if (success) {
ToolTip.show_copied_tooltip_on_node(node);
} else {
ToolTip.show_tooltip_on_node("Failed to copy", node, null);
}
}
}
config.pty.focus();
return false;
} else if (key === "v") {
if (!config.canPaste) {
return false;
}
const toPaste = await navigator.clipboard.readText();
let pastedText = '';
try {
pastedText = await navigator.clipboard.readText();
} catch (err) {
// Fallback for browsers that don't support clipboard API
const textarea = document.createElement('textarea');
document.body.appendChild(textarea);
textarea.focus();
document.execCommand('paste');
pastedText = textarea.value;
document.body.removeChild(textarea);
}

GdbApi.getSocket().emit("pty_interaction", {
data: { pty_name: config.pty_name, key: toPaste, action: "write" }
});
if (pastedText) {
GdbApi.getSocket().emit("pty_interaction", {
data: { pty_name: config.pty_name, key: pastedText, action: "write" }
});
}
return false;
}
}
Expand All @@ -43,30 +98,64 @@ export class Terminals extends React.Component {
userPtyRef: React.RefObject<any>;
programPtyRef: React.RefObject<any>;
gdbguiPtyRef: React.RefObject<any>;
userPty: Terminal | null = null;
programPty: Terminal | null = null;
gdbguiPty: Terminal | null = null;

constructor(props: any) {
super(props);
this.userPtyRef = React.createRef();
this.programPtyRef = React.createRef();
this.gdbguiPtyRef = React.createRef();
this.terminal = this.terminal.bind(this);
this.handleInputSubmit = this.handleInputSubmit.bind(this);
}

handleInputSubmit(e: React.KeyboardEvent<HTMLInputElement>, ptyName: string) {
if (e.key === 'Enter') {
e.preventDefault();
const input = e.currentTarget.value;
if (input) {
// Send each character to the terminal
GdbApi.getSocket().emit("pty_interaction", {
data: { pty_name: ptyName, key: input, action: "write" }
});
// Send newline to simulate Enter
GdbApi.getSocket().emit("pty_interaction", {
data: { pty_name: ptyName, key: "\n", action: "write" }
});
// Clear the input
e.currentTarget.value = '';
}
}
}

terminal(ref: React.RefObject<any>) {
let className = " bg-black p-0 m-0 h-full align-baseline ";
terminal(ref: React.RefObject<any>, ptyName: string, canPaste: boolean = true) {
return (
<div className={className}>
<div className="absolute h-full w-1/3 align-baseline " ref={ref}></div>
<div className="relative h-full flex flex-col overflow-hidden">
<div className={`flex-grow overflow-hidden bg-black`}>
<div className="w-full h-full" ref={ref}></div>
</div>
{canPaste && (
<div className="flex-none bg-gray-900 p-2">
<input
type="text"
className="w-full h-full px-2 py-1 bg-gray-800 text-white border border-gray-700 rounded"
placeholder="Type or paste command here..."
onKeyDown={(e) => this.handleInputSubmit(e, ptyName)}
/>
</div>
)}
</div>
);
}

render() {
let terminalsClass = "w-full h-full relative grid grid-cols-3 ";
return (
<div className={terminalsClass}>
{this.terminal(this.userPtyRef)}
{/* <GdbGuiTerminal /> */}
{this.terminal(this.gdbguiPtyRef)}
{this.terminal(this.programPtyRef)}
<div className="w-full h-full grid grid-cols-3">
{this.terminal(this.userPtyRef, "user_pty")}
{this.terminal(this.gdbguiPtyRef, "unused", false)}
{this.terminal(this.programPtyRef, "program_pty")}
</div>
);
}
Expand All @@ -81,6 +170,7 @@ export class Terminals extends React.Component {
macOptionIsMeta: true,
scrollback: 9999
});
this.userPty = userPty;
userPty.loadAddon(fitAddon);
userPty.open(this.userPtyRef.current);
userPty.writeln(`running command: ${store.get("gdb_command")}`);
Expand Down Expand Up @@ -111,6 +201,7 @@ export class Terminals extends React.Component {
macOptionIsMeta: true,
scrollback: 9999
});
this.programPty = programPty;
programPty.loadAddon(programFitAddon);
programPty.open(this.programPtyRef.current);
programPty.attachCustomKeyEventHandler(
Expand Down Expand Up @@ -144,10 +235,11 @@ export class Terminals extends React.Component {
disableStdin: true
// theme: { background: "#888" }
});
this.gdbguiPty = gdbguiPty;
gdbguiPty.write(constants.xtermColors.grey);
gdbguiPty.writeln("gdbgui output (read-only)");
gdbguiPty.writeln(
"Copy/Paste available in all terminals with ctrl+shift+c, ctrl+shift+v"
"Copy/Paste available in all terminals with ctrl+alt+c, ctrl+alt+v"
);
gdbguiPty.write(constants.xtermColors.reset);

Expand Down