Skip to content

Commit ce966eb

Browse files
committed
Size PTY against window-screen-lines, not window-body-height
ghostel captured terminal height via `(window-body-height)' — which divides the window's pixel height by frame char height. But the standard `window-adjust-process-window-size-smallest' (and term.el, vterm, eat all do likewise) divides by `default-line-height' via `(window-screen-lines)', which respects `face-remapping-alist' and `:height' on the default face. When a theme remaps the buffer's default face — `nano-light' / `nano-dark' bump it ~7% higher than frame default — the two metrics disagree. Concrete report (#192): body 1307 px / 27 px frame char = 48 (window-body-height); 1307 px / 29 px default-line = 45.069 (window-screen-lines). Init captures 48, libghostty + PTY spawn at 48 rows; first redisplay fires `window-configuration-change-hook' (because body-pixel differs from the freshly-zeroed `old_body_pixel'), the standard adjust-fn runs, returns 45, and we issue an immediate startup SIGWINCH down to 45. Claude Code's /tui fullscreen sometimes mishandles the SIGWINCH-during-startup case and ends up with a collapsed input box. Switch every height-capture site to `floor(window-screen-lines)': - `ghostel--init-buffer' and `ghostel-exec' (the spawn paths). - `ghostel--commit-cropped-size' (minibuffer focus return). - `ghostel-compile.el' init/reconcile (3 sites) — same bug class. Add the `(max 1 …)' guard on the init-buffer height that `ghostel-exec' already had, since `floor' of a sub-1 value is a real (if unlikely) source of a 0-row terminal. Remove `ghostel--reconcile-display-size' and its `window-buffer-change-functions' registration (added in b9c12ca). Now redundant: with init capturing the right metric, the standard machinery (`window-configuration-change-hook' → `window--adjust-process-windows' → `--window-adjust-process-window-size') catches every transition the helper tried to catch — chrome reshuffles via `init_iterator''s body-pixel comparison (xdisp.c:3443-3474), buffer migration via `set_window_buffer' setting `FRAME_WINDOW_CHANGE'. Fix the "Size sync" block in `ghostel-debug-info' (added in 0b19011): compare `term-rows' against `(floor (window-screen-lines))' instead of `window-body-height', and surface the frame-vs-default line-height delta directly so a face-remap shows up in the report. Update affected tests to mock `window-screen-lines'.
1 parent ea594fc commit ce966eb

4 files changed

Lines changed: 68 additions & 215 deletions

File tree

lisp/ghostel-compile.el

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,9 @@ so there is no remote-integration round-trip on TRAMP buffers."
548548
(ghostel--load-module t)
549549
(let* ((buffer (get-buffer-create name))
550550
(win (or (get-buffer-window buffer t) (selected-window)))
551-
(height (if (window-live-p win) (window-body-height win) 24))
551+
(height (if (window-live-p win)
552+
(with-selected-window win (floor (window-screen-lines)))
553+
24))
552554
(width (if (window-live-p win) (window-max-chars-per-line win) 80)))
553555
(with-current-buffer buffer
554556
;; Set `default-directory' before `ghostel-mode' so the mode's
@@ -626,7 +628,8 @@ honour custom compile-mode subclasses the caller passed to
626628
;; column and look garbled until the user's first resize triggers
627629
;; `ghostel--window-adjust-process-window-size'.
628630
(when (and outwin ghostel--term)
629-
(let ((oh (max 1 (window-body-height outwin)))
631+
(let ((oh (max 1 (with-selected-window outwin
632+
(floor (window-screen-lines)))))
630633
(ow (max 1 (window-max-chars-per-line outwin))))
631634
(ghostel--set-size ghostel--term oh ow)
632635
(setq ghostel--term-rows oh)))
@@ -659,8 +662,9 @@ honour custom compile-mode subclasses the caller passed to
659662
;; window, e.g. `allow-no-window'). Use `window-max-chars-per-line'
660663
;; as the canonical width measure, matching `ghostel--spawn-pty'.
661664
(let* ((height (max 1 (if outwin
662-
(window-body-height outwin)
663-
(window-body-height))))
665+
(with-selected-window outwin
666+
(floor (window-screen-lines)))
667+
(floor (window-screen-lines)))))
664668
(width (max 1 (if outwin
665669
(window-max-chars-per-line outwin)
666670
(window-max-chars-per-line))))

lisp/ghostel-debug.el

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -570,11 +570,13 @@ ghostel settings into *ghostel-debug* for pasting into bug reports."
570570
(if copy "active" "off"))))
571571
(insert "Term handle: nil (no terminal)\n"))
572572
;; Size sync — surfaces #192-class bugs.
573-
;; If body-rows ≠ term-rows but cur=recorded body pixels, then
574-
;; Emacs already absorbed the chrome change but ghostel didn't
575-
;; reconcile (a real ghostel bug). If both differ, the next
576-
;; redisplay will fire `window-{size,configuration}-change-hook'
577-
;; and `--window-adjust-process-window-size' will reconcile.
573+
;; Compare term-rows against `floor(window-screen-lines)' (what
574+
;; `window-adjust-process-window-size-smallest' uses), NOT
575+
;; `window-body-height': the latter divides by frame char
576+
;; height while screen-lines divides by `default-line-height'
577+
;; (face-remap-aware). When a theme remaps the default face
578+
;; height, the two disagree and the body-height comparison
579+
;; cries wolf.
578580
(when (and term (window-live-p win))
579581
(insert "\n--- Size sync ---\n")
580582
(let* ((cur-body-px (window-body-height win t))
@@ -584,12 +586,21 @@ ghostel settings into *ghostel-debug* for pasting into bug reports."
584586
(screen-lines (with-selected-window win
585587
(window-screen-lines)))
586588
(body-rows (window-body-height win))
587-
(rows-match (eql body-rows term-rows))
589+
(frame-ch (frame-char-height))
590+
(default-lh (with-selected-window win
591+
(default-line-height)))
592+
(target-rows (floor screen-lines))
593+
(rows-match (eql target-rows term-rows))
588594
(px-match (eql cur-body-px old-body-px)))
589-
(insert (format "Body rows: %d (window) vs %s (term) %s\n"
590-
body-rows term-rows
595+
(insert (format "screen-lines: %.3f → target %d (term=%s) %s\n"
596+
screen-lines target-rows term-rows
591597
(if rows-match "[in sync]" "[MISMATCH]")))
592-
(insert (format "window-screen-lines: %s\n" screen-lines))
598+
(insert (format "Body rows (frame): %d (window-body-height — frame chars)\n"
599+
body-rows))
600+
(insert (format "Line height: frame=%d px default-face=%d px%s\n"
601+
frame-ch default-lh
602+
(if (eql frame-ch default-lh) ""
603+
" [face-remap or theme bumps height]")))
593604
(insert (format "Body pixels: cur=%d recorded=%d %s\n"
594605
cur-body-px old-body-px
595606
(if px-match "" "[redisplay pending]")))
@@ -599,8 +610,8 @@ ghostel settings into *ghostel-debug* for pasting into bug reports."
599610
(rows-match
600611
(insert "Diagnosis: in sync\n"))
601612
(px-match
602-
(insert "Diagnosis: Emacs absorbed the chrome change\n")
603-
(insert " but ghostel didn't reconcile (#192)\n"))
613+
(insert "Diagnosis: Emacs absorbed the change but\n")
614+
(insert " ghostel didn't reconcile (#192)\n"))
604615
(t
605616
(insert "Diagnosis: pending redisplay; hooks will fire\n")
606617
(insert " on next paint\n"))))))))

lisp/ghostel.el

Lines changed: 26 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3454,7 +3454,8 @@ window (not when it has just been deselected)."
34543454
ghostel--term
34553455
ghostel--process
34563456
(process-live-p ghostel--process))
3457-
(let ((height (window-body-height window))
3457+
(let ((height (with-selected-window window
3458+
(floor (window-screen-lines))))
34583459
(width (window-max-chars-per-line window))
34593460
(buf (current-buffer)))
34603461
(unless (and (eql height ghostel--term-rows)
@@ -3471,41 +3472,6 @@ window (not when it has just been deselected)."
34713472
(setq ghostel--redraw-timer nil))
34723473
(let ((ghostel--redraw-resize-active t))
34733474
(ghostel--delayed-redraw buf))))))
3474-
3475-
(defun ghostel--reconcile-display-size (window)
3476-
"Reconcile terminal size when this buffer is (re-)displayed in WINDOW.
3477-
Buffer migration to a window of a different size produces no
3478-
window-size-change event — Emacs's `adjust-window-size-function'
3479-
machinery does not fire — but `window-buffer-change-functions' does.
3480-
Catch it here and resize libghostty + PTY to match WINDOW.
3481-
3482-
Intended for buffer-local `window-buffer-change-functions'. The
3483-
single-window guard avoids ping-pong if the buffer is shown in two
3484-
differently-sized windows simultaneously — that case is handled by
3485-
the existing `window-adjust-process-window-size-smallest' path."
3486-
(when (and (window-live-p window)
3487-
(eq (window-buffer window) (current-buffer))
3488-
ghostel--term
3489-
ghostel--process
3490-
(process-live-p ghostel--process)
3491-
(= 1 (length (get-buffer-window-list (current-buffer) nil t))))
3492-
(let ((height (window-body-height window))
3493-
(width (window-max-chars-per-line window)))
3494-
(unless (and (eql height ghostel--term-rows)
3495-
(eql width ghostel--term-cols))
3496-
(ghostel--set-size ghostel--term (max 1 height) (max 1 width))
3497-
(setq ghostel--term-rows height
3498-
ghostel--term-cols width
3499-
ghostel--force-next-redraw t)
3500-
(set-process-window-size ghostel--process
3501-
(max 1 height) (max 1 width))
3502-
(when ghostel--redraw-timer
3503-
(cancel-timer ghostel--redraw-timer)
3504-
(setq ghostel--redraw-timer nil))
3505-
(let ((ghostel--redraw-resize-active t))
3506-
(ghostel--delayed-redraw (current-buffer)))))))
3507-
3508-
35093475

35103476
;;; Major mode
35113477

@@ -3543,8 +3509,6 @@ the existing `window-adjust-process-window-size-smallest' path."
35433509
#'ghostel--commit-cropped-size nil t)
35443510
(add-hook 'window-buffer-change-functions
35453511
#'ghostel--reshow-snap nil t)
3546-
(add-hook 'window-buffer-change-functions
3547-
#'ghostel--reconcile-display-size nil t)
35483512
(ghostel--suppress-interfering-modes)
35493513
(setq ghostel--scroll-intercept-active t)
35503514
;; Let C-g reach the keymap instead of triggering keyboard-quit.
@@ -3584,17 +3548,31 @@ buffer can be found again after title-tracking renames it."
35843548
(defun ghostel--init-buffer (buffer &optional identity)
35853549
"Initialize BUFFER as a ghostel terminal if no terminal handle exists yet.
35863550
Terminal dimensions come from BUFFER's displayed window when one
3587-
exists, otherwise from the selected window. Subsequent migrations
3588-
to differently-sized windows are reconciled by
3589-
`ghostel--reconcile-display-size' on `window-buffer-change-functions'.
3551+
exists, otherwise from the selected window. Height uses
3552+
`window-screen-lines' (the metric the standard
3553+
`adjust-window-size-function' path also uses), not
3554+
`window-body-height'. The former divides the window's pixel height
3555+
by the buffer's `default-line-height', which respects
3556+
`face-remapping-alist' and `:height' on the default face; the latter
3557+
divides by frame char height. When a theme remaps default —
3558+
`nano-light' / `nano-dark' do this — the two metrics disagree, and
3559+
using `window-body-height' would size the terminal to N rows only to
3560+
have the standard adjust-fn immediately resize to N-K, sending a
3561+
startup SIGWINCH that some TUI apps (Claude Code's /tui fullscreen)
3562+
handle imperfectly (issue #192).
35903563
IDENTITY, if given, is stored as `ghostel--buffer-identity' so the
35913564
buffer can be found again after title-tracking renames it."
35923565
(with-current-buffer buffer
35933566
(unless ghostel--term
35943567
(ghostel--prepare-buffer buffer identity)
35953568
(let* ((w (or (get-buffer-window buffer t) (selected-window)))
3596-
(height (if (window-live-p w) (window-body-height w) 24))
3597-
(width (if (window-live-p w) (window-max-chars-per-line w) 80)))
3569+
(height (max 1 (if (window-live-p w)
3570+
(with-selected-window w
3571+
(floor (window-screen-lines)))
3572+
24)))
3573+
(width (max 1 (if (window-live-p w)
3574+
(window-max-chars-per-line w)
3575+
80))))
35983576
(setq ghostel--term
35993577
(ghostel--new height width ghostel-max-scrollback))
36003578
(setq ghostel--term-rows height)
@@ -3658,7 +3636,11 @@ Signals `user-error' if BUFFER already has a live ghostel process."
36583636
(let ((window (or (get-buffer-window buffer t) (selected-window))))
36593637
(with-current-buffer buffer
36603638
(ghostel--prepare-buffer buffer nil)
3661-
(let* ((height (max 1 (window-body-height window)))
3639+
;; Use `window-screen-lines' (not `window-body-height') so the
3640+
;; height matches the unit `window-adjust-process-window-size-smallest'
3641+
;; uses — see `ghostel--init-buffer' for why.
3642+
(let* ((height (max 1 (with-selected-window window
3643+
(floor (window-screen-lines)))))
36623644
(width (max 1 (window-max-chars-per-line window)))
36633645
(remote-p (file-remote-p default-directory)))
36643646
(setq ghostel--term

0 commit comments

Comments
 (0)