Fix browser terminal paste for hosting clients.
Deploy Ladill Hosting / deploy (push) Successful in 42s
Deploy Ladill Hosting / deploy (push) Successful in 42s
Use bracketed paste, reliably bind paste capture, stop blocking mousedown focus, and give shells a short prompt with horizontal-scroll readline so long pasted commands do not wrap incorrectly. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -183,6 +183,14 @@ class RunHostingTerminalWorkerCommand extends Command
|
||||
? $homeDirectory
|
||||
: $homeDirectory.$relativePath;
|
||||
|
||||
// Short \W prompt + bracketed paste keep long pasted commands readable in
|
||||
// the narrow browser terminal used by all hosting clients.
|
||||
$inputrc = implode("\n", [
|
||||
'set enable-bracketed-paste on',
|
||||
'set horizontal-scroll-mode on',
|
||||
'set blink-matching-paren on',
|
||||
]);
|
||||
|
||||
return implode('; ', [
|
||||
'export HOME='.escapeshellarg($homeDirectory),
|
||||
'export USER='.escapeshellarg($account->username),
|
||||
@@ -192,7 +200,9 @@ class RunHostingTerminalWorkerCommand extends Command
|
||||
'export COLORTERM=truecolor',
|
||||
'export COLUMNS='.(int) $cols,
|
||||
'export LINES='.(int) $rows,
|
||||
'export PS1='.escapeshellarg($account->username.'@ladill:\w\$ '),
|
||||
'export PS1='.escapeshellarg($account->username.'@ladill:\W\$ '),
|
||||
'printf %s '.escapeshellarg($inputrc).' > /tmp/ladill-term-inputrc-$$',
|
||||
'export INPUTRC=/tmp/ladill-term-inputrc-$$',
|
||||
'cd '.escapeshellarg($workingDirectory).' 2>/dev/null || cd '.escapeshellarg($homeDirectory),
|
||||
'stty rows '.(int) $rows.' cols '.(int) $cols.' echo 2>/dev/null || true',
|
||||
'exec /bin/bash --noprofile --norc -i',
|
||||
|
||||
@@ -101,10 +101,11 @@ window.hostingInteractiveTerminal = (config = {}) => {
|
||||
this.queueInput(data);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Delay initial fit to ensure container has dimensions
|
||||
requestAnimationFrame(() => {
|
||||
this.fitAddon.fit();
|
||||
this.bindTerminalInputCapture();
|
||||
this.term.focus();
|
||||
});
|
||||
|
||||
@@ -113,7 +114,7 @@ window.hostingInteractiveTerminal = (config = {}) => {
|
||||
this.resizeObserver = new ResizeObserver(() => this.queueResize());
|
||||
this.resizeObserver.observe(this.$refs.terminal);
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener('resize', () => this.queueResize());
|
||||
window.addEventListener('beforeunload', () => this.shutdown(true));
|
||||
|
||||
@@ -256,7 +257,23 @@ window.hostingInteractiveTerminal = (config = {}) => {
|
||||
},
|
||||
|
||||
handleTerminalPaste(event) {
|
||||
if (!this.shouldCaptureTerminalInput(event)) {
|
||||
if (!this.sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const terminalElement = this.$refs.terminal;
|
||||
const target = event.target;
|
||||
const inTerminal = Boolean(
|
||||
terminalElement
|
||||
&& target instanceof Node
|
||||
&& (terminalElement === target || terminalElement.contains(target))
|
||||
);
|
||||
const termFocused = Boolean(
|
||||
this.term?.textarea && document.activeElement === this.term.textarea
|
||||
);
|
||||
|
||||
// Panel paste often targets the wrapper/div, not the xterm textarea.
|
||||
if (!this.terminalActive && !inTerminal && !termFocused) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,14 +283,21 @@ window.hostingInteractiveTerminal = (config = {}) => {
|
||||
return;
|
||||
}
|
||||
|
||||
this.terminalActive = true;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation?.();
|
||||
this.queueInput(text.replace(/\r?\n/g, '\r'));
|
||||
|
||||
// Bracketed paste keeps bash/readline from mangling long pasted commands.
|
||||
const normalized = text
|
||||
.replace(/\r\n/g, '\n')
|
||||
.replace(/\r/g, '\n')
|
||||
.replace(/\n/g, '\r');
|
||||
this.queueInput(`\x1b[200~${normalized}\x1b[201~`);
|
||||
},
|
||||
|
||||
shouldCaptureTerminalInput(event) {
|
||||
if (!this.sessionId || !this.terminalActive) {
|
||||
if (!this.sessionId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -281,9 +305,19 @@ window.hostingInteractiveTerminal = (config = {}) => {
|
||||
const terminalElement = this.$refs.terminal;
|
||||
|
||||
if (terminalElement && target instanceof Node && terminalElement.contains(target)) {
|
||||
this.terminalActive = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.term?.textarea && document.activeElement === this.term.textarea) {
|
||||
this.terminalActive = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.terminalActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return false;
|
||||
}
|
||||
@@ -535,6 +569,7 @@ window.hostingInteractiveTerminal = (config = {}) => {
|
||||
console.log('[Terminal] focusTerminal called');
|
||||
if (this.term) {
|
||||
this.terminalActive = true;
|
||||
this.bindTerminalInputCapture();
|
||||
this.$refs.terminal?.focus();
|
||||
this.term.focus();
|
||||
console.log('[Terminal] terminalActive set to true');
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<div class="relative min-h-0 flex-1 overflow-hidden">
|
||||
<div
|
||||
x-ref="terminal"
|
||||
@mousedown.prevent="focusTerminal()"
|
||||
@mousedown="focusTerminal()"
|
||||
@click="focusTerminal()"
|
||||
tabindex="0"
|
||||
class="h-full w-full cursor-text bg-[#0a0a0a] p-2"
|
||||
@@ -89,6 +89,9 @@
|
||||
<span class="font-mono">bash</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 text-[10px] text-white/25">
|
||||
<kbd class="rounded bg-white/5 px-1 py-0.5 font-mono">Ctrl/Cmd+V</kbd>
|
||||
<span>paste</span>
|
||||
<span class="mx-2 text-white/10">|</span>
|
||||
<kbd class="rounded bg-white/5 px-1 py-0.5 font-mono">Ctrl+C</kbd>
|
||||
<span>cancel</span>
|
||||
<span class="mx-2 text-white/10">|</span>
|
||||
|
||||
Reference in New Issue
Block a user