From f0cfe0980ca67d050f8fa01eb7bd6a7b6f44817b Mon Sep 17 00:00:00 2001 From: jam Date: Fri, 27 Feb 2026 15:22:34 +0900 Subject: [PATCH] feat(proc): add E hotkey to collapse/expand all tree nodes Pressing E in tree mode collapses all processes if any parent is currently expanded, or expands all if everything is already collapsed. This gives a quick per-application resource summary without having to manually toggle each node. Implemented across all platforms (Linux, macOS, FreeBSD, OpenBSD, NetBSD). --- src/btop_input.cpp | 5 +++++ src/btop_menu.cpp | 1 + src/btop_shared.hpp | 2 +- src/freebsd/btop_collect.cpp | 17 ++++++++++++++++- src/linux/btop_collect.cpp | 17 ++++++++++++++++- src/netbsd/btop_collect.cpp | 17 ++++++++++++++++- src/openbsd/btop_collect.cpp | 17 ++++++++++++++++- src/osx/btop_collect.cpp | 17 ++++++++++++++++- 8 files changed, 87 insertions(+), 6 deletions(-) diff --git a/src/btop_input.cpp b/src/btop_input.cpp index 28a96677..ff68d5e1 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -346,6 +346,11 @@ namespace Input { no_update = false; Config::set("update_following", true); } + else if (key == "E" and Config::getB("proc_tree")) { + atomic_wait(Runner::active); + Proc::collapse_all = 1; + no_update = false; + } else if (is_in(key, "u")) { Config::flip("pause_proc_list"); } diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index fbaadf32..36e39589 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -205,6 +205,7 @@ namespace Menu { {"c", "Toggle per-core cpu usage of processes."}, {"r", "Reverse sorting order in processes box."}, {"e", "Toggle processes tree view."}, + {"E", "Collapse/expand all processes in tree view."}, {"%", "Toggles memory display mode in processes box."}, {"Selected +, -", "Expand/collapse the selected process in tree view."}, {"Selected t", "Terminate selected process with SIGTERM - 15."}, diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 96f4de32..00020b11 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -360,7 +360,7 @@ namespace Proc { extern bool shown, redraw; extern int select_max; extern atomic detailed_pid; - extern int selected_pid, start, selected, collapse, expand, filter_found, selected_depth, toggle_children; + extern int selected_pid, start, selected, collapse, expand, filter_found, selected_depth, toggle_children, collapse_all; extern int scroll_pos; extern string selected_name; extern atomic resized; diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 17f916fe..f4318b2b 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -1007,7 +1007,7 @@ namespace Proc { fs::file_time_type passwd_time; uint64_t cputimes; - int collapse = -1, expand = -1, toggle_children = -1; + int collapse = -1, expand = -1, toggle_children = -1, collapse_all = -1; uint64_t old_cputimes = 0; atomic numpids = 0; int filter_found = 0; @@ -1301,6 +1301,21 @@ namespace Proc { } collapse = expand = -1; } + + if (collapse_all != -1) { + //? If any parent process is currently expanded, collapse all; otherwise expand all + std::unordered_set parent_pids; + for (const auto& p : current_procs) + parent_pids.insert(static_cast(p.ppid)); + const bool do_collapse = rng::any_of(current_procs, [&parent_pids](const proc_info& p) { + return parent_pids.contains(p.pid) and not p.collapsed; + }); + for (auto& p : current_procs) + p.collapsed = do_collapse; + collapse_all = -1; + if (Config::ints.at("proc_selected") > 0) locate_selection = true; + } + if (should_filter or not filter.empty()) filter_found = 0; vector tree_procs; diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 454f8e87..be4526dd 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -2879,7 +2879,7 @@ namespace Proc { fs::file_time_type passwd_time; uint64_t cputimes; - int collapse = -1, expand = -1, toggle_children = -1; + int collapse = -1, expand = -1, toggle_children = -1, collapse_all = -1; uint64_t old_cputimes{}; atomic numpids{}; int filter_found{}; @@ -3354,6 +3354,21 @@ namespace Proc { } collapse = expand = -1; } + + if (collapse_all != -1) { + //? If any parent process is currently expanded, collapse all; otherwise expand all + std::unordered_set parent_pids; + for (const auto& p : current_procs) + parent_pids.insert(static_cast(p.ppid)); + const bool do_collapse = rng::any_of(current_procs, [&parent_pids](const proc_info& p) { + return parent_pids.contains(p.pid) and not p.collapsed; + }); + for (auto& p : current_procs) + p.collapsed = do_collapse; + collapse_all = -1; + if (Config::ints.at("proc_selected") > 0) locate_selection = true; + } + if (should_filter or not filter.empty()) filter_found = 0; vector tree_procs; diff --git a/src/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp index 9f56899e..875cdc5d 100644 --- a/src/netbsd/btop_collect.cpp +++ b/src/netbsd/btop_collect.cpp @@ -1100,7 +1100,7 @@ namespace Proc { fs::file_time_type passwd_time; uint64_t cputimes; - int collapse = -1, expand = -1, toggle_children = -1; + int collapse = -1, expand = -1, toggle_children = -1, collapse_all = -1; uint64_t old_cputimes = 0; atomic numpids = 0; int filter_found = 0; @@ -1362,6 +1362,21 @@ namespace Proc { } collapse = expand = -1; } + + if (collapse_all != -1) { + //? If any parent process is currently expanded, collapse all; otherwise expand all + std::unordered_set parent_pids; + for (const auto& p : current_procs) + parent_pids.insert(static_cast(p.ppid)); + const bool do_collapse = rng::any_of(current_procs, [&parent_pids](const proc_info& p) { + return parent_pids.contains(p.pid) and not p.collapsed; + }); + for (auto& p : current_procs) + p.collapsed = do_collapse; + collapse_all = -1; + if (Config::ints.at("proc_selected") > 0) locate_selection = true; + } + if (should_filter or not filter.empty()) filter_found = 0; vector tree_procs; diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp index bb822ddf..e5557c3f 100644 --- a/src/openbsd/btop_collect.cpp +++ b/src/openbsd/btop_collect.cpp @@ -991,7 +991,7 @@ namespace Proc { fs::file_time_type passwd_time; uint64_t cputimes; - int collapse = -1, expand = -1, toggle_children = -1; + int collapse = -1, expand = -1, toggle_children = -1, collapse_all = -1; uint64_t old_cputimes = 0; atomic numpids = 0; int filter_found = 0; @@ -1265,6 +1265,21 @@ namespace Proc { } collapse = expand = -1; } + + if (collapse_all != -1) { + //? If any parent process is currently expanded, collapse all; otherwise expand all + std::unordered_set parent_pids; + for (const auto& p : current_procs) + parent_pids.insert(static_cast(p.ppid)); + const bool do_collapse = rng::any_of(current_procs, [&parent_pids](const proc_info& p) { + return parent_pids.contains(p.pid) and not p.collapsed; + }); + for (auto& p : current_procs) + p.collapsed = do_collapse; + collapse_all = -1; + if (Config::ints.at("proc_selected") > 0) locate_selection = true; + } + if (should_filter or not filter.empty()) filter_found = 0; vector tree_procs; diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index b720bb2d..49f65e8c 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -1590,7 +1590,7 @@ namespace Proc { fs::file_time_type passwd_time; uint64_t cputimes; - int collapse = -1, expand = -1, toggle_children = -1; + int collapse = -1, expand = -1, toggle_children = -1, collapse_all = -1; uint64_t old_cputimes = 0; atomic numpids = 0; int filter_found = 0; @@ -1922,6 +1922,21 @@ namespace Proc { } collapse = expand = -1; } + + if (collapse_all != -1) { + //? If any parent process is currently expanded, collapse all; otherwise expand all + std::unordered_set parent_pids; + for (const auto& p : current_procs) + parent_pids.insert(static_cast(p.ppid)); + const bool do_collapse = rng::any_of(current_procs, [&parent_pids](const proc_info& p) { + return parent_pids.contains(p.pid) and not p.collapsed; + }); + for (auto& p : current_procs) + p.collapsed = do_collapse; + collapse_all = -1; + if (Config::ints.at("proc_selected") > 0) locate_selection = true; + } + if (should_filter or not filter.empty()) filter_found = 0; vector tree_procs;