Skip to content

Commit 3e8d9c7

Browse files
emil-edakra
authored andcommitted
Suppress spurious SIGWINCH on minibuffer-induced window shrink
When the minibuffer opens (M-x, vertico, consult, etc.), Emacs shrinks the window displaying the terminal, triggering window-configuration-change-hook → set-process-window-size and thus SIGWINCH to the shell. Closing the minibuffer sends a second one. Fish repaints its whole prompt on SIGWINCH; complex TUIs redraw the full screen. Both are jarring no-ops from the user's perspective. Solution: treat minibuffer-induced height shrinks as crops, matching how a normal buffer behaves when the minibuffer steals space — the bottom rows are simply hidden, no signal is sent. When the minibuffer closes and the window grows back, the size matches the committed PTY size, so no signal is sent either. Refinements to keep the cropping correct: - Only crop while the terminal is on the *primary* screen. Apps on the alternate screen buffer (vim, htop, less, Claude Code) own the full viewport and need SIGWINCH to re-layout for the smaller window. New Zig binding `ghostel--alt-screen-p' checks DEC modes 1049/1047/47 in one native call. - If the user switches focus into the ghostel window while the minibuffer is still open, commit the current (smaller) size so the shell/app knows its real viewport — they are now actively using it. Handled by `ghostel--commit-cropped-size' hooked into window-selection-change-functions. - Track `ghostel--term-cols' alongside `ghostel--term-rows' so no-op resizes (window merely moves) are skipped. Module version bumped to 0.17.0 for the new export.
1 parent dcbbf1d commit 3e8d9c7

6 files changed

Lines changed: 306 additions & 21 deletions

File tree

build.zig.zon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.{
22
.name = .ghostel,
3-
.version = "0.16.1",
3+
.version = "0.16.2",
44
.paths = .{""},
55
.fingerprint = 0x5a44bdd1198a0f4b,
66
.dependencies = .{

evil-ghostel.el

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
;; Author: Daniel Kraus <daniel@kraus.my>
66
;; URL: https://github.com/dakra/ghostel
7-
;; Version: 0.16.1
7+
;; Version: 0.16.2
88
;; Package-Requires: ((emacs "28.1") (evil "1.0") (ghostel "0.8.0"))
99
;; SPDX-License-Identifier: GPL-3.0-or-later
1010

ghostel.el

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
;; Author: Daniel Kraus <daniel@kraus.my>
66
;; URL: https://github.com/dakra/ghostel
7-
;; Version: 0.16.1
7+
;; Version: 0.16.2
88
;; Keywords: terminals
99
;; Package-Requires: ((emacs "28.1"))
1010
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -524,7 +524,7 @@ before sending the input."
524524
Customize this when downloading pre-built modules from a fork or mirror."
525525
:type 'string)
526526

527-
(defconst ghostel--minimum-module-version "0.16.1"
527+
(defconst ghostel--minimum-module-version "0.16.2"
528528
"Minimum native module version required by this Elisp version.
529529
Bump this only when the Elisp code requires a newer native module
530530
\(e.g. new Zig-exported function or changed calling convention).")
@@ -537,6 +537,7 @@ Bump this only when the Elisp code requires a newer native module
537537
(declare-function ghostel--encode-key "ghostel-module")
538538
(declare-function ghostel--focus-event "ghostel-module")
539539
(declare-function ghostel--mode-enabled "ghostel-module")
540+
(declare-function ghostel--alt-screen-p "ghostel-module")
540541
(declare-function ghostel--copy-all-text "ghostel-module")
541542
(declare-function ghostel--module-version "ghostel-module")
542543
(declare-function ghostel--mouse-event "ghostel-module")
@@ -795,6 +796,10 @@ entry points so tests run without the module present."
795796
"Row count of the native terminal, for viewport/scrollback arithmetic.
796797
Updated whenever the terminal is created or resized.")
797798

799+
(defvar-local ghostel--term-cols nil
800+
"Column count of the native terminal.
801+
Updated whenever the terminal is created or resized.")
802+
798803
(defvar-local ghostel--copy-mode-active nil
799804
"Non-nil when copy mode is active.")
800805

@@ -3159,24 +3164,81 @@ PROCESS is the shell process, WINDOWS is the list of windows."
31593164
(when (and size (buffer-live-p buffer))
31603165
(with-current-buffer buffer
31613166
(when ghostel--term
3162-
(ghostel--set-size ghostel--term (max 1 height) (max 1 width))
3163-
(setq ghostel--term-rows height)
3164-
(setq ghostel--force-next-redraw t)
3165-
;; Redraw synchronously so the buffer is updated before
3166-
;; Emacs displays the stale content at the new window size.
3167-
(when ghostel--redraw-timer
3168-
(cancel-timer ghostel--redraw-timer)
3169-
(setq ghostel--redraw-timer nil))
3170-
;; `ghostel--redraw-resize-active' lets `ghostel--window-anchored-p'
3171-
;; treat Emacs-induced `window-start' drift (from `keep-point-visible'
3172-
;; when a minibuffer-triggered resize shrinks the body) as drift,
3173-
;; not as a user scroll.
3174-
(let ((ghostel--redraw-resize-active t))
3175-
(ghostel--delayed-redraw buffer)))))
3167+
(cond
3168+
;; No change — skip entirely.
3169+
((and (eql height ghostel--term-rows)
3170+
(eql width ghostel--term-cols))
3171+
(setq size nil))
3172+
;; Crop: minibuffer is active, only height shrank, width unchanged, and the
3173+
;; terminal is on the primary screen. Defer the resize so no SIGWINCH is sent
3174+
;; — the Emacs window naturally hides the bottom rows, just like a normal
3175+
;; buffer. Alt-screen apps (vim, htop) own the full viewport and must be told
3176+
;; the real size to re-layout, so they take the normal-resize path. If the
3177+
;; user switches focus into the ghostel window while the minibuffer is still
3178+
;; open, `ghostel--commit-cropped-size' commits the smaller size then.
3179+
((and (> (minibuffer-depth) 0)
3180+
(eql width ghostel--term-cols)
3181+
(< height ghostel--term-rows)
3182+
(not (ghostel--alt-screen-p ghostel--term)))
3183+
(setq size nil))
3184+
;; Real resize — update the terminal model and redraw.
3185+
(t
3186+
(ghostel--set-size ghostel--term (max 1 height) (max 1 width))
3187+
(setq ghostel--term-rows height)
3188+
(setq ghostel--term-cols width)
3189+
(setq ghostel--force-next-redraw t)
3190+
;; Redraw synchronously so the buffer is updated before
3191+
;; Emacs displays the stale content at the new window size.
3192+
(when ghostel--redraw-timer
3193+
(cancel-timer ghostel--redraw-timer)
3194+
(setq ghostel--redraw-timer nil))
3195+
;; `ghostel--redraw-resize-active' lets `ghostel--window-anchored-p'
3196+
;; treat Emacs-induced `window-start' drift (from `keep-point-visible'
3197+
;; when a minibuffer-triggered resize shrinks the body) as drift,
3198+
;; not as a user scroll.
3199+
(let ((ghostel--redraw-resize-active t))
3200+
(ghostel--delayed-redraw buffer)))))))
31763201
;; Return size — Emacs calls set-process-window-size (SIGWINCH)
3177-
;; after this function returns, matching eat/vterm timing.
3202+
;; after this function returns. nil suppresses the call.
31783203
size))
31793204

3205+
(defun ghostel--commit-cropped-size (window)
3206+
"Commit WINDOW's size if the user focused into a cropped ghostel window.
3207+
When the minibuffer opens, `ghostel--window-adjust-process-window-size'
3208+
skips the resize so the PTY keeps its original row count and the bottom
3209+
rows are just hidden. But if the user switches focus into the ghostel
3210+
window while the minibuffer is still up, they are now actively using
3211+
the smaller viewport — commit the size so the shell/app knows its real
3212+
dimensions.
3213+
3214+
Intended for buffer-local `window-selection-change-functions'. When
3215+
registered buffer-locally, Emacs calls this with WINDOW and makes its
3216+
buffer current; we only commit when WINDOW has become the selected
3217+
window (not when it has just been deselected)."
3218+
(when (and (> (minibuffer-depth) 0)
3219+
(window-live-p window)
3220+
(eq window (frame-selected-window (window-frame window)))
3221+
ghostel--term
3222+
ghostel--process
3223+
(process-live-p ghostel--process))
3224+
(let ((height (window-body-height window))
3225+
(width (window-max-chars-per-line window))
3226+
(buf (current-buffer)))
3227+
(unless (and (eql height ghostel--term-rows)
3228+
(eql width ghostel--term-cols))
3229+
(ghostel--set-size ghostel--term
3230+
(max 1 height) (max 1 width))
3231+
(setq ghostel--term-rows height
3232+
ghostel--term-cols width
3233+
ghostel--force-next-redraw t)
3234+
(set-process-window-size ghostel--process
3235+
(max 1 height) (max 1 width))
3236+
(when ghostel--redraw-timer
3237+
(cancel-timer ghostel--redraw-timer)
3238+
(setq ghostel--redraw-timer nil))
3239+
(let ((ghostel--redraw-resize-active t))
3240+
(ghostel--delayed-redraw buf))))))
3241+
31803242

31813243

31823244
;;; Major mode
@@ -3203,6 +3265,10 @@ PROCESS is the shell process, WINDOWS is the list of windows."
32033265
(add-function :after after-focus-change-function #'ghostel--focus-change)
32043266
(add-hook 'window-selection-change-functions #'ghostel--focus-change)
32053267
(add-hook 'window-buffer-change-functions #'ghostel--focus-change)
3268+
;; Buffer-local so it only fires for windows showing this buffer, and
3269+
;; receives WINDOW directly (rather than FRAME as the default binding).
3270+
(add-hook 'window-selection-change-functions
3271+
#'ghostel--commit-cropped-size nil t)
32063272
(ghostel--suppress-interfering-modes)
32073273
(setq ghostel--scroll-intercept-active t)
32083274
;; Let C-g reach the keymap instead of triggering keyboard-quit.
@@ -3245,6 +3311,7 @@ displayed, so a mismatch at creation time self-corrects."
32453311
(setq ghostel--term
32463312
(ghostel--new height width ghostel-max-scrollback))
32473313
(setq ghostel--term-rows height)
3314+
(setq ghostel--term-cols width)
32483315
(ghostel--apply-palette ghostel--term))
32493316
(ghostel--start-process))))
32503317

@@ -3297,6 +3364,7 @@ Signals `user-error' if BUFFER already has a live ghostel process."
32973364
(setq ghostel--term
32983365
(ghostel--new height width ghostel-max-scrollback))
32993366
(setq ghostel--term-rows height)
3367+
(setq ghostel--term-cols width)
33003368
(ghostel--apply-palette ghostel--term)
33013369
(ghostel--spawn-pty program args height width
33023370
"erase '^?' iutf8 -ixon echo" nil remote-p)))))

src/module.zig

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const input = @import("input.zig");
1313
const c = emacs.c;
1414

1515
/// Module version — keep in sync with ghostel.el and build.zig.zon.
16-
const version = "0.16.1";
16+
const version = "0.16.2";
1717

1818
// ---------------------------------------------------------------------------
1919
// Module entry point
@@ -42,6 +42,7 @@ export fn emacs_module_init(runtime: *c.struct_emacs_runtime) callconv(.c) c_int
4242
env.bindFunction("ghostel--set-palette", 2, 2, &fnSetPalette, "Set the ANSI color palette.\n\n(ghostel--set-palette TERM COLORS-STRING)");
4343
env.bindFunction("ghostel--set-default-colors", 3, 3, &fnSetDefaultColors, "Set default foreground and background colors.\n\n(ghostel--set-default-colors TERM FG-HEX BG-HEX)");
4444
env.bindFunction("ghostel--mode-enabled", 2, 2, &fnModeEnabled, "Return t if terminal DEC private MODE is enabled.\n\n(ghostel--mode-enabled TERM MODE)");
45+
env.bindFunction("ghostel--alt-screen-p", 1, 1, &fnAltScreen, "Return t if terminal is on the alternate screen buffer.\n\n(ghostel--alt-screen-p TERM)");
4546
env.bindFunction("ghostel--cursor-position", 1, 1, &fnCursorPosition, "Return terminal cursor position as (COL . ROW), 0-indexed.\n\n(ghostel--cursor-position TERM)");
4647
env.bindFunction("ghostel--cursor-pending-wrap-p", 1, 1, &fnCursorPendingWrap, "Return t if the cursor is in pending-wrap state.\n\n(ghostel--cursor-pending-wrap-p TERM)");
4748
env.bindFunction("ghostel--debug-state", 1, 1, &fnDebugState, "Return debug info about terminal/render state.\n\n(ghostel--debug-state TERM)");
@@ -767,6 +768,14 @@ fn fnModeEnabled(raw_env: ?*c.emacs_env, _: isize, args: [*c]c.emacs_value, _: ?
767768
return if (term.isModeEnabled(mode)) env.t() else env.nil();
768769
}
769770

771+
/// (ghostel--alt-screen-p TERM)
772+
/// Return t if the terminal is on the alternate screen buffer.
773+
fn fnAltScreen(raw_env: ?*c.emacs_env, _: isize, args: [*c]c.emacs_value, _: ?*anyopaque) callconv(.c) c.emacs_value {
774+
const env = emacs.Env.init(raw_env.?);
775+
const term = env.getUserPtr(Terminal, args[0]) orelse return env.nil();
776+
return if (term.isAltScreen()) env.t() else env.nil();
777+
}
778+
770779
/// (ghostel--set-palette TERM COLORS-STRING)
771780
/// Set the 16 ANSI colors from a concatenated hex string like "#000000#aa0000...".
772781
/// The remaining 240 palette entries are taken from the terminal's current palette.

src/terminal.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,15 @@ pub fn isModeEnabled(self: *Self, mode: gt.c.GhosttyMode) bool {
278278
return enabled;
279279
}
280280

281+
/// Returns true if the terminal is on the alternate screen buffer
282+
/// (DEC private modes 1049, 1047, or legacy 47 set). Used to decide
283+
/// whether full-screen apps (vim, htop, less) own the viewport.
284+
pub fn isAltScreen(self: *Self) bool {
285+
return self.isModeEnabled(@as(gt.c.GhosttyMode, 1049)) or
286+
self.isModeEnabled(@as(gt.c.GhosttyMode, 1047)) or
287+
self.isModeEnabled(@as(gt.c.GhosttyMode, 47));
288+
}
289+
281290
/// Get the total number of rows (scrollback + active screen).
282291
pub fn getTotalRows(self: *Self) usize {
283292
var total: usize = 0;

0 commit comments

Comments
 (0)