feat(ui): implement dynamic pad resizing

The ncurses pad used for rendering window history had a fixed height of 100
lines, which caused "Ncurses Overflow!" errors and broke scrolling once
the content exceeded this limit.

This commit replaces the fixed limit with a dynamic resizing strategy.
We use a minimum height of 100 lines.
If a pad reaches its threshold of 3000 lines, a full redraw is triggered
to reclaim space from discarded messages and reset the pad height.
Pads are shrunk back to the minimum height during every redraw to
minimize memory usage for inactive windows.

Message history should always be smoothly scrollable now.

d7e46d64fe set it from 1000 to 10000.
f27fa98717 set it from 1000 to 100.
Probably also relevant is 4fe2c423b1.

Close: https://github.com/profanity-im/profanity/pull/2074
Fixes: https://github.com/profanity-im/profanity/issues/2045
This commit is contained in:
Michael Vetter
2026-03-19 22:03:27 +01:00
parent b219ce9e60
commit 3e16ae1def

View File

@@ -39,7 +39,31 @@
#include "xmpp/xmpp.h" #include "xmpp/xmpp.h"
#include "xmpp/roster_list.h" #include "xmpp/roster_list.h"
static const int PAD_SIZE = 100; static const int PAD_MIN_HEIGHT = 100;
static const int PAD_THRESHOLD = 3000;
static gboolean _in_redraw = FALSE;
static void
_win_ensure_pad_capacity(ProfWin* window, WINDOW* win, int lines_needed)
{
if (!win) {
return;
}
int cur_height = getmaxy(win);
int cur_width = getmaxx(win);
if (lines_needed >= cur_height - 1) {
// If we are getting too large, trigger a redraw to clean up old lines
// but only if we are not already in a redraw process.
if (window && cur_height >= PAD_THRESHOLD && !_in_redraw) {
win_redraw(window);
} else {
// resize to required lines + some buffer for next messages
int new_height = lines_needed + 100;
wresize(win, new_height, cur_width);
}
}
}
static const char* LOADING_MESSAGE = "Loading older messages…"; static const char* LOADING_MESSAGE = "Loading older messages…";
static const char* CONS_WIN_TITLE = "Profanity. Type /help for help information."; static const char* CONS_WIN_TITLE = "Profanity. Type /help for help information.";
static const char* XML_WIN_TITLE = "XML Console"; static const char* XML_WIN_TITLE = "XML Console";
@@ -75,7 +99,7 @@ _win_create_simple_layout(void)
ProfLayoutSimple* layout = g_new0(ProfLayoutSimple, 1); ProfLayoutSimple* layout = g_new0(ProfLayoutSimple, 1);
layout->base.type = LAYOUT_SIMPLE; layout->base.type = LAYOUT_SIMPLE;
layout->base.win = newpad(PAD_SIZE, cols); layout->base.win = newpad(PAD_MIN_HEIGHT, cols);
wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
layout->base.buffer = buffer_create(); layout->base.buffer = buffer_create();
layout->base.y_pos = 0; layout->base.y_pos = 0;
@@ -92,7 +116,7 @@ _win_create_split_layout(void)
ProfLayoutSplit* layout = g_new0(ProfLayoutSplit, 1); ProfLayoutSplit* layout = g_new0(ProfLayoutSplit, 1);
layout->base.type = LAYOUT_SPLIT; layout->base.type = LAYOUT_SPLIT;
layout->base.win = newpad(PAD_SIZE, cols); layout->base.win = newpad(PAD_MIN_HEIGHT, cols);
wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
layout->base.buffer = buffer_create(); layout->base.buffer = buffer_create();
layout->base.y_pos = 0; layout->base.y_pos = 0;
@@ -162,12 +186,12 @@ win_create_muc(const char* const roomjid)
if (prefs_get_boolean(PREF_OCCUPANTS)) { if (prefs_get_boolean(PREF_OCCUPANTS)) {
int subwin_cols = win_occpuants_cols(); int subwin_cols = win_occpuants_cols();
layout->base.win = newpad(PAD_SIZE, cols - subwin_cols); layout->base.win = newpad(PAD_MIN_HEIGHT, cols - subwin_cols);
wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
layout->subwin = newpad(PAD_SIZE, subwin_cols); layout->subwin = newpad(PAD_MIN_HEIGHT, subwin_cols);
wbkgd(layout->subwin, theme_attrs(THEME_TEXT)); wbkgd(layout->subwin, theme_attrs(THEME_TEXT));
} else { } else {
layout->base.win = newpad(PAD_SIZE, (cols)); layout->base.win = newpad(PAD_MIN_HEIGHT, (cols));
wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
layout->subwin = NULL; layout->subwin = NULL;
} }
@@ -509,11 +533,11 @@ win_hide_subwin(ProfWin* window)
layout->subwin = NULL; layout->subwin = NULL;
layout->sub_y_pos = 0; layout->sub_y_pos = 0;
int cols = getmaxx(stdscr); int cols = getmaxx(stdscr);
wresize(layout->base.win, PAD_SIZE, cols); wresize(layout->base.win, PAD_MIN_HEIGHT, cols);
win_redraw(window); win_redraw(window);
} else { } else {
int cols = getmaxx(stdscr); int cols = getmaxx(stdscr);
wresize(window->layout->win, PAD_SIZE, cols); wresize(window->layout->win, PAD_MIN_HEIGHT, cols);
win_redraw(window); win_redraw(window);
} }
} }
@@ -535,9 +559,9 @@ win_show_subwin(ProfWin* window)
} }
ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout; ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
layout->subwin = newpad(PAD_SIZE, subwin_cols); layout->subwin = newpad(PAD_MIN_HEIGHT, subwin_cols);
wbkgd(layout->subwin, theme_attrs(THEME_TEXT)); wbkgd(layout->subwin, theme_attrs(THEME_TEXT));
wresize(layout->base.win, PAD_SIZE, cols - subwin_cols); wresize(layout->base.win, PAD_MIN_HEIGHT, cols - subwin_cols);
win_redraw(window); win_redraw(window);
} }
@@ -832,9 +856,9 @@ win_resize(ProfWin* window)
subwin_cols = win_occpuants_cols(); subwin_cols = win_occpuants_cols();
} }
wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
wresize(layout->base.win, PAD_SIZE, cols - subwin_cols); wresize(layout->base.win, PAD_MIN_HEIGHT, cols - subwin_cols);
wbkgd(layout->subwin, theme_attrs(THEME_TEXT)); wbkgd(layout->subwin, theme_attrs(THEME_TEXT));
wresize(layout->subwin, PAD_SIZE, subwin_cols); wresize(layout->subwin, PAD_MIN_HEIGHT, subwin_cols);
if (window->type == WIN_CONSOLE) { if (window->type == WIN_CONSOLE) {
rosterwin_roster(); rosterwin_roster();
} else if (window->type == WIN_MUC) { } else if (window->type == WIN_MUC) {
@@ -844,11 +868,11 @@ win_resize(ProfWin* window)
} }
} else { } else {
wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); wbkgd(layout->base.win, theme_attrs(THEME_TEXT));
wresize(layout->base.win, PAD_SIZE, cols); wresize(layout->base.win, PAD_MIN_HEIGHT, cols);
} }
} else { } else {
wbkgd(window->layout->win, theme_attrs(THEME_TEXT)); wbkgd(window->layout->win, theme_attrs(THEME_TEXT));
wresize(window->layout->win, PAD_SIZE, cols); wresize(window->layout->win, PAD_MIN_HEIGHT, cols);
} }
win_redraw(window); win_redraw(window);
@@ -1828,6 +1852,8 @@ _win_print_internal(ProfWin* window, const char* show_char, int pad_indent, GDat
} }
} }
_win_ensure_pad_capacity(window, window->layout->win, getcury(window->layout->win));
if (prefs_get_boolean(PREF_WRAP)) { if (prefs_get_boolean(PREF_WRAP)) {
_win_print_wrapped(window->layout->win, message + offset, indent, pad_indent); _win_print_wrapped(window->layout->win, message + offset, indent, pad_indent);
} else { } else {
@@ -1977,6 +2003,7 @@ _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pa
void void
win_print_trackbar(ProfWin* window) win_print_trackbar(ProfWin* window)
{ {
_win_ensure_pad_capacity(window, window->layout->win, getcury(window->layout->win));
int cols = getmaxx(window->layout->win); int cols = getmaxx(window->layout->win);
wbkgdset(window->layout->win, theme_attrs(THEME_TRACKBAR)); wbkgdset(window->layout->win, theme_attrs(THEME_TRACKBAR));
@@ -1993,11 +2020,19 @@ void
win_redraw(ProfWin* window) win_redraw(ProfWin* window)
{ {
unsigned int size = buffer_size(window->layout->buffer); unsigned int size = buffer_size(window->layout->buffer);
_in_redraw = TRUE;
// shrink pad back to minimum size and erase it
int cols = getmaxx(window->layout->win);
wresize(window->layout->win, PAD_MIN_HEIGHT, cols);
werase(window->layout->win); werase(window->layout->win);
for (unsigned int i = 0; i < size; i++) { for (unsigned int i = 0; i < size; i++) {
ProfBuffEntry* e = buffer_get_entry(window->layout->buffer, i); ProfBuffEntry* e = buffer_get_entry(window->layout->buffer, i);
// check if we need more space before printing
_win_ensure_pad_capacity(window, window->layout->win, getcury(window->layout->win));
e->y_start_pos = getcury(window->layout->win); e->y_start_pos = getcury(window->layout->win);
if (e->display_from == NULL && e->message && e->message[0] == '-') { if (e->display_from == NULL && e->message && e->message[0] == '-') {
// just an indicator to print the trackbar/separator not the actual message // just an indicator to print the trackbar/separator not the actual message
@@ -2008,6 +2043,8 @@ win_redraw(ProfWin* window)
} }
e->y_end_pos = getcury(window->layout->win); e->y_end_pos = getcury(window->layout->win);
} }
_in_redraw = FALSE;
} }
void void
@@ -2135,6 +2172,7 @@ win_toggle_attention(ProfWin* window)
void void
win_sub_print(WINDOW* win, char* msg, gboolean newline, gboolean wrap, int indent) win_sub_print(WINDOW* win, char* msg, gboolean newline, gboolean wrap, int indent)
{ {
_win_ensure_pad_capacity(NULL, win, getcury(win));
int maxx = getmaxx(win); int maxx = getmaxx(win);
int curx = getcurx(win); int curx = getcurx(win);
int cury = getcury(win); int cury = getcury(win);