commit de4d213a66b669e4e4e3a58f407af6d33096fc1b
parent f0c7d6152842e3a30e95de2d8ee91888b3d4f277
Author: Bharatvaj Hemanth <bharatvaj@yahoo.com>
Date: Tue, 13 Oct 2026 22:09:59 +0530
parent f0c7d6152842e3a30e95de2d8ee91888b3d4f277
Author: Bharatvaj Hemanth <bharatvaj@yahoo.com>
Date: Tue, 13 Oct 2026 22:09:59 +0530
Avoid repainting unchanged list rows as stream grows
3 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/HACKING b/HACKING @@ -107,7 +107,9 @@ Known simplifications thread, yields after each SEARCH_BATCH, and resumes through WM_SEARCH_STEP. It never yields while holding items_lock. The reader remains a separate thread. Matching rows are indexed in source order (fzy subsequence matching, - not score-ranked). No silent row cap. The bottom bar shows search progress + not score-ranked). No silent row cap. LB_SETCOUNT is wrapped with redraw + suppression: unchanged visible rows are not invalidated as the row count + grows; only newly visible rows are. The bottom bar shows search progress after stdin completes. - lnav inspiration: its logfile index tracks lines separately from the view (https://github.com/tstack/lnav/blob/master/src/logfile.hh, lf_index and
diff --git a/test_stream.c b/test_stream.c @@ -78,12 +78,22 @@ int main(int argc, char **argv) ReleaseDC(NULL, screen); refill_list(""); assert(SendMessageW(list_hwnd, LB_GETCOUNT, 0, 0) == 60000); + ShowWindow(main_hwnd, SW_SHOWNOACTIVATE); + ShowWindow(list_hwnd, SW_SHOWNOACTIVATE); + UpdateWindow(list_hwnd); + assert(!GetUpdateRect(list_hwnd, NULL, FALSE)); + add_item("item-60000"); + refill_list(""); + assert(SendMessageW(list_hwnd, LB_GETCOUNT, 0, 0) == 60001); + RECT dirty; + assert(!GetUpdateRect(list_hwnd, &dirty, FALSE) || IsRectEmpty(&dirty)); + /* Count/scrollbar may update, but the existing client rows stay untouched. */ refill_list("tail-special"); search_step(); input_done = false; add_item("tail-special"); while (search_pos < item_count) search_step(); - assert(match_count == 1 && matches[0] == 60000); + assert(match_count == 1 && matches[0] == 60001); input_done = true; update_progress(); assert(SendMessageW(list_hwnd, LB_GETCOUNT, 0, 0) == 1);
diff --git a/wmenu.c b/wmenu.c @@ -136,10 +136,27 @@ static int row_item(int row) static void update_count(void) { int count = search_query && *search_query ? match_count : item_count; - if (SendMessageW(list_hwnd, LB_GETCOUNT, 0, 0) == count) return; + int old_count = (int)SendMessageW(list_hwnd, LB_GETCOUNT, 0, 0); + if (old_count == count) return; int selected = (int)SendMessageW(list_hwnd, LB_GETCURSEL, 0, 0); + int top = (int)SendMessageW(list_hwnd, LB_GETTOPINDEX, 0, 0); + int height = (int)SendMessageW(list_hwnd, LB_GETITEMHEIGHT, 0, 0); + RECT client; + GetClientRect(list_hwnd, &client); + SendMessageW(list_hwnd, WM_SETREDRAW, FALSE, 0); SendMessageW(list_hwnd, LB_SETCOUNT, count, 0); if (count && selected < 0) SendMessageW(list_hwnd, LB_SETCURSEL, 0, 0); + SendMessageW(list_hwnd, WM_SETREDRAW, TRUE, 0); + /* LB_SETCOUNT may invalidate everything. Only newly visible rows need paint. */ + RedrawWindow(list_hwnd, NULL, NULL, RDW_VALIDATE | RDW_NOCHILDREN); + int new_top = (int)SendMessageW(list_hwnd, LB_GETTOPINDEX, 0, 0); + if (top != new_top || count < old_count || height <= 0) { + InvalidateRect(list_hwnd, NULL, FALSE); + } else if (old_count < new_top + (client.bottom - client.top + height - 1) / height) { + client.top += (old_count - new_top) * height; + if (client.top < 0) client.top = 0; + InvalidateRect(list_hwnd, &client, FALSE); + } } static void update_progress(void)
: nply/wmenu