diff --git a/app/Console/Commands/RunHostingTerminalWorkerCommand.php b/app/Console/Commands/RunHostingTerminalWorkerCommand.php index 27b027d..cadfe3d 100644 --- a/app/Console/Commands/RunHostingTerminalWorkerCommand.php +++ b/app/Console/Commands/RunHostingTerminalWorkerCommand.php @@ -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', diff --git a/resources/js/hosting-terminal.js b/resources/js/hosting-terminal.js index d444569..b0873ac 100644 --- a/resources/js/hosting-terminal.js +++ b/resources/js/hosting-terminal.js @@ -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'); diff --git a/resources/views/hosting/panel/terminal.blade.php b/resources/views/hosting/panel/terminal.blade.php index 08e4cc3..0be94a3 100644 --- a/resources/views/hosting/panel/terminal.blade.php +++ b/resources/views/hosting/panel/terminal.blade.php @@ -57,7 +57,7 @@
bash
+ Ctrl/Cmd+V + paste + | Ctrl+C cancel |