From 410d1d803d4e5463013ba9dd18ea4e82e3ded8f5 Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Sat, 4 Jul 2026 14:08:08 +0300 Subject: [PATCH] fix(editor): follow live terminal size in external editor 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. --- src/tools/editor.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/tools/editor.c b/src/tools/editor.c index 2974ed74..82b90f39 100644 --- a/src/tools/editor.c +++ b/src/tools/editor.c @@ -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; }