fix(editor): follow live terminal size in external editor
All checks were successful
CI Code / Check coding style (pull_request) Successful in 31s
CI Code / Check spelling (pull_request) Successful in 1m6s
CI Code / Linux (ubuntu) (pull_request) Successful in 9m59s
CI Code / Linux (debian) (pull_request) Successful in 10m16s
CI Code / Code Coverage (pull_request) Successful in 10m30s
CI Code / Linux (arch) (pull_request) Successful in 14m9s

The compose editor is spawned via fork+execvp and inherits profanity's
environment, including LINES/COLUMNS. Those hold the terminal size
captured when profanity started (set by readline/ncurses at init) and
are never refreshed on resize. A curses-based editor (nano, vim, ...)
honors LINES/COLUMNS over the real window, so it renders at the
launch-time dimensions and leaves the rest of the window unused after
the terminal has been resized.

Build a copy of the environment without LINES/COLUMNS before forking and
point the child's environ at it, so the editor's curses falls back to
ioctl(TIOCGWINSZ) and tracks the current window. The env is assembled in
the parent so the child only swaps the environ pointer, which is
async-signal-safe -- unlike unsetenv() -- in the multithreaded
fork->exec window. profanity itself is unaffected: it already reads the
live size via ioctl in ui_resize(), and its own environment is intact.
This commit is contained in:
2026-07-04 14:08:08 +03:00
parent 622054fc6d
commit 410d1d803d

View File

@@ -25,6 +25,8 @@
#include "ui/ui.h"
#include "xmpp/xmpp.h"
extern char** environ;
typedef struct EditorContext
{
gchar* filename;
@@ -140,6 +142,22 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
GSource* sigchld_warmup = g_child_watch_source_new(getpid());
g_source_unref(sigchld_warmup);
// Build the editor's env without LINES/COLUMNS pre-fork, so the child only
// swaps environ (async-signal-safe) instead of calling unsetenv(). The
// editor's (n)curses then reads the live window via ioctl(TIOCGWINSZ).
gsize env_len = 0;
while (environ[env_len]) {
env_len++;
}
gchar** editor_env = g_new0(gchar*, env_len + 1);
gsize env_kept = 0;
for (gsize i = 0; i < env_len; i++) {
if (g_str_has_prefix(environ[i], "LINES=") || g_str_has_prefix(environ[i], "COLUMNS=")) {
continue;
}
editor_env[env_kept++] = environ[i];
}
pid_t pid = fork();
if (pid == -1) {
log_error("[Editor] Failed to fork: %s", strerror(errno));
@@ -148,12 +166,15 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
ui_resize();
cons_show_error("Failed to start editor: %s", strerror(errno));
g_strfreev(editor_argv);
g_free(editor_env); // array only; strings are borrowed from environ
g_free(ctx->filename);
g_free(ctx);
return TRUE;
} else if (pid == 0) {
// Child process: Inherits TTY from parent
environ = editor_env; // live TIOCGWINSZ size, not the inherited LINES/COLUMNS
// SIGTSTP=SIG_DFL lets vim's :stop / Ctrl-Z work; profanity catches
// the STOPPED state via editor_check_stopped() and drops to the shell.
signal(SIGINT, SIG_DFL);
@@ -170,6 +191,7 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
editor_pid = pid;
g_child_watch_add((GPid)pid, _editor_exit_cb, ctx);
g_strfreev(editor_argv);
g_free(editor_env); // array only; strings are borrowed from environ
return FALSE;
}