diff --git a/etc/shell/ghostel.bash b/etc/shell/ghostel.bash index 913d3f53..a41c6b9d 100644 --- a/etc/shell/ghostel.bash +++ b/etc/shell/ghostel.bash @@ -19,9 +19,23 @@ # kernel echo input immediately. builtin command stty echo 2>/dev/null +# Capture gethostname(2) for OSC 7. $HOSTNAME is inherited from the environment; +# toolbox/container runtimes export it with a value that disagrees with the +# kernel hostname - so Emacs' (system-name), which calls gethostname(2), would +# see a mismatch and ghostel would misclassify the buffer as remote, switching +# on TRAMP. Bash captures gethostname(2) at startup into the value behind the +# \H prompt escape; ${var@P} (bash 4.4+) reads it back without forking. +# On bash <4.4 the @P transform is unavailable, so fall back to $HOSTNAME. +if ((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4))); then + __ghostel_host=$'\\H' + __ghostel_host=${__ghostel_host@P} +else + __ghostel_host=$HOSTNAME +fi + # Report working directory to the terminal via OSC 7 __ghostel_osc7() { - printf '\e]7;file://%s%s\a' "$HOSTNAME" "$PWD" + printf '\e]7;file://%s%s\a' "$__ghostel_host" "$PWD" } # --- Semantic prompt markers (OSC 133) --- diff --git a/test/ghostel-test.el b/test/ghostel-test.el index e9e28d19..b0618db9 100644 --- a/test/ghostel-test.el +++ b/test/ghostel-test.el @@ -1773,6 +1773,62 @@ that collided with a fish-internal local variable, leaking (ghostel-compile-view-mode) (should (equal list-buffers-directory default-directory))))) +;; ----------------------------------------------------------------------- +;; Test: bash OSC 7 ignores $HOSTNAME (#276) +;; ----------------------------------------------------------------------- + +(ert-deftest ghostel-test-bash-osc7-ignores-env-hostname () + "Bash OSC 7 must report gethostname(2), not $HOSTNAME (#276). + +Toolbox/container runtimes export HOSTNAME with a value that +disagrees with the kernel hostname. Emacs function `system-name' +reads gethostname(2); if bash's integration emits $HOSTNAME the +local-host comparison in `ghostel--update-directory' fails and +the buffer is misclassified as remote, switching on TRAMP." + (skip-unless (executable-find "bash")) + ;; The test exercises the bash 4.4+ ${var@P} path. On bash <4.4 + ;; (notably macOS /bin/bash 3.2) the integration deliberately falls + ;; back to $HOSTNAME - pre-#276 behavior, no regression for those + ;; users - so the assertion below would not hold and the test would + ;; be testing the wrong invariant. + (let ((ver (with-temp-buffer + (call-process "bash" nil t nil "-c" + "printf '%s.%s' \"$BASH_VERSINFO\" \"${BASH_VERSINFO[1]}\"") + (buffer-string)))) + (skip-unless + (and (string-match "\\`\\([0-9]+\\)\\.\\([0-9]+\\)\\'" ver) + (let ((major (string-to-number (match-string 1 ver))) + (minor (string-to-number (match-string 2 ver)))) + (or (> major 4) (and (= major 4) (>= minor 4))))))) + (let* ((root (or (ghostel--resource-root) + (file-name-directory (locate-library "ghostel")))) + (shell-bash (expand-file-name "etc/shell/ghostel.bash" root))) + (skip-unless (file-exists-p shell-bash)) + (let* ((fake "ghostel-test-fake-host-zzz") + (process-environment + (append (list (format "HOSTNAME=%s" fake) + "INSIDE_EMACS=ghostel") + process-environment)) + (probe (format "cd /; source %s; __ghostel_osc7" + shell-bash)) + (output (with-temp-buffer + (call-process "bash" nil (current-buffer) nil + "--noprofile" "--norc" "-c" probe) + (buffer-string)))) + ;; Probe emits: \e]7;file://HOST/\a + (should (string-match "\e\\]7;file://\\([^/]*\\)/" output)) + (let ((emitted (match-string 1 output))) + ;; Polluted $HOSTNAME must not appear in the OSC 7 host. + (should-not (equal emitted fake)) + ;; Whatever bash emits must pass the same locality check the elisp side + ;; applies in `ghostel--update-directory'. Asserting the predicate + ;; (not strict equality with `system-name') is deliberate: on hosts + ;; where (system-name) is an FQDN but \H is the short form (or vice + ;; versa) the two strings differ, yet `ghostel--local-host-p' accepts + ;; either via its split- on-`.' fallback - which is exactly the + ;; production behavior we care about. + (should (ghostel--local-host-p emitted)))))) + ;; ----------------------------------------------------------------------- ;; Test: OSC 7 end-to-end through libghostty ;; -----------------------------------------------------------------------