From 42e8abec0d3ed272f564af3868032e68879b7c5d Mon Sep 17 00:00:00 2001 From: Jim Warner Date: Wed, 20 Jun 2018 00:00:00 -0500 Subject: [PATCH] top: allow collapsible forest view children, pgm logic The issue cited below really dealt with preserving the 'Other filter' criteria in the rcfile. But as an aside the htop 'F6' feature (collapsed children) was raised. I took that as an implied challenge and decided to try implementing a similar feature in top. So, this commit will now provide a brand new forest view toggle ('^V') which will be used to collapse/expand forked children. [ this patch will also lead to additional patches in ] [ support of more rational vertical scrolling, since ] [ many more tasks might now be hidden in some window ] Reference(s): . where this secondary issue was raised https://gitlab.com/procps-ng/procps/issues/99 Signed-off-by: Jim Warner --- top/top.c | 144 +++++++++++++++++++++++++++++++++++++++----------- top/top.h | 3 ++ top/top_nls.c | 3 +- top/top_nls.h | 3 +- 4 files changed, 120 insertions(+), 33 deletions(-) diff --git a/top/top.c b/top/top.c index 22b9a204..a5a01e1a 100644 --- a/top/top.c +++ b/top/top.c @@ -1665,12 +1665,14 @@ static struct { #define eu_TICS_ALL_C eu_LAST +2 #define eu_TIME_START eu_LAST +3 #define eu_ID_FUID eu_LAST +4 -#define eu_XTRA eu_LAST +5 +#define eu_LVL eu_LAST +5 +#define eu_HID eu_LAST +6 { -1, -1, -1, -1, PIDS_CMDLINE }, // str ( if Show_CMDLIN ) { -1, -1, -1, -1, PIDS_TICS_ALL_C }, // ull_int ( if Show_CTIMES ) { -1, -1, -1, -1, PIDS_TIME_START }, // ull_int ( if Show_FOREST ) { -1, -1, -1, -1, PIDS_ID_FUID }, // u_int ( if a usrseltyp ) - { -1, -1, -1, -1, PIDS_extra } // u_int ( if Show_FOREST ) + { -1, -1, -1, -1, PIDS_extra }, // u_int ( if Show_FOREST ) + { -1, -1, -1, -1, PIDS_extra } // s_ch ( if Show_FOREST ) #undef A_left #undef A_right }; @@ -1827,7 +1829,7 @@ static void build_headers (void) { // for 'busy' only processes, we'll need elapsed tics if (!CHKw(w, Show_IDLEPS)) ckITEM(EU_CPU); // with forest view mode, we'll need pid, tgid, ppid & start_time... - if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(eu_TIME_START); ckITEM(eu_XTRA); } + if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(eu_TIME_START); ckITEM(eu_LVL); ckITEM(eu_HID);} // for 'cumulative' times, we'll need equivalent of cutime & cstime if (Fieldstab[EU_TME].erel > -1 && CHKw(w, Show_CTIMES)) ckITEM(eu_TICS_ALL_C); if (Fieldstab[EU_TM2].erel > -1 && CHKw(w, Show_CTIMES)) ckITEM(eu_TICS_ALL_C); @@ -4229,13 +4231,20 @@ static inline int wins_usrselect (const WIN_t *q, struct pids_stack *p) { /*###### Forest View support ###########################################*/ /* - * We try to keep most existing code unaware of our activities - * ( plus, maintain alphabetical order with carefully chosen ) - * ( function names like such: forest_a, forest_b & forest_c ) - * ( each with exactly one letter more than its predecessor! ) */ + * We try keeping most existing code unaware of these activities + * ( plus, maintain alphabetical order within carefully chosen ) + * ( function names like such: forest_a, forest_b and forest_c ) + * ( with each name exactly 1 letter more than its predecessor ) */ static struct pids_stack **Seed_ppt; // temporary win ppt pointer static struct pids_stack **Tree_ppt; // forest_begin resizes this static int Tree_idx; // frame_make resets to zero + /* those next two support collapse/expand children. the Hide_pid + array holds parent pids whose children have been manipulated. + positive pid values represent parents with collapsed children + while a negative pid value means children have been expanded. + ( both of these are managed under the 'keys_task()' routine ) */ +static int *Hide_pid; // collapsible process array +static int Hide_tot; // total used in above array /* * This little recursive guy is the real forest view workhorse. @@ -4243,15 +4252,15 @@ static int Tree_idx; // frame_make resets to zero * level which is stored in an 'extra' result struct as a u_int. */ static void forest_adds (const int self, unsigned level) { // tailored 'results stack value' extractor macros - #define rSv(E,X) PID_VAL(E, s_int, Seed_ppt[X]) - // if xtra-procps-debug.h active, can't use PID_VAL as base due to assignment - #define rLevel Tree_ppt[Tree_idx]->head[Fieldstab[eu_XTRA].erel].result.u_int + #define rSv(E,X) PID_VAL(E, s_int, Seed_ppt[X]) + // if xtra-procps-debug.h active, can't use PID_VAL with assignment + #define rSv_Lvl Tree_ppt[Tree_idx]->head[Fieldstab[eu_LVL].erel].result.u_int int i; if (Tree_idx < PIDSmaxt) { // immunize against insanity if (level > 100) level = 101; // our arbitrary nests limit Tree_ppt[Tree_idx] = Seed_ppt[self]; // add this as root or child - rLevel = level; // while recording its level + rSv_Lvl = level; // while recording its level ++Tree_idx; #ifdef TREE_SCANALL for (i = 0; i < PIDSmaxt; i++) { @@ -4265,38 +4274,68 @@ static void forest_adds (const int self, unsigned level) { } } #undef rSv - #undef rLevel + #undef rSv_Lvl } // end: forest_adds /* - * This routine is responsible for preparing the stacks ptr array - * for forest display in the designated window. Upon completion, - * he'll replace the original window ppt with our specially - * ordered forest version. */ + * This function is responsible for making a stacks ptr array + * a forest display in a designated window. After completion, + * he will replace the original window ppt with our specially + * ordered forest version. He also marks any hidden children! */ static void forest_begin (WIN_t *q) { - // tailored 'results stack value' extractor macro - #define rLevel PID_VAL(eu_XTRA, u_int, Seed_ppt[i]) static int hwmsav; - int i; + int i, j; Seed_ppt = q->ppt; // avoid passing pointers if (!Tree_idx) { // do just once per frame if (hwmsav < PIDSmaxt) { // grow, but never shrink hwmsav = PIDSmaxt; Tree_ppt = alloc_r(Tree_ppt, sizeof(void*) * hwmsav); + Hide_pid = alloc_r(Hide_pid, sizeof(int) * hwmsav); } + #ifndef TREE_SCANALL if (!(procps_pids_sort(Pids_ctx, Seed_ppt, PIDSmaxt , PIDS_TIME_START, PIDS_SORT_ASCEND))) error_exit(fmtmk(N_fmt(LIB_errorpid_fmt),__LINE__, strerror(errno))); #endif - for (i = 0; i < PIDSmaxt; i++) // avoid any hidepid distortions - if (!rLevel) // identify real or pretend trees - forest_adds(i, 0); // add as parent plus its children - } + for (i = 0; i < PIDSmaxt; i++) { // avoid some hidepid distortions + if (!PID_VAL(eu_LVL, u_int, Seed_ppt[i])) // real & pseudo parents == 0 + forest_adds(i, 0); // add a parent with its children + } + + /* we're employing a couple of 'PIDS_extra' results in our stacks + eu_LVL (u_int): where level number is stored (0 - 100) + eu_HID (s_ch) : where 'x' == collapsed and 'z' == unseen */ + for (i = 0; i < Hide_tot; i++) { + #define rSv_Pid(X) PID_VAL(EU_PID, s_int, Tree_ppt[X]) + // if xtra-procps-debug.h active, can't use PID_VAL with assignment + #define rSv_Lvl(X) Tree_ppt[X]->head[Fieldstab[eu_LVL].erel].result.u_int + #define rSv_Hid(X) Tree_ppt[X]->head[Fieldstab[eu_HID].erel].result.s_ch + + if (Hide_pid[i] > 0) { + for (j = 0; j < PIDSmaxt; j++) { + if (rSv_Pid(j) == Hide_pid[i]) { + int idx = j; + unsigned lvl = rSv_Lvl(idx); + rSv_Hid(idx) = 'x'; + while (j+1 < PIDSmaxt && rSv_Lvl(j+1) > lvl) { + rSv_Hid(j+1) = 'z'; + idx = 0; + ++j; + } + // no children found, so unmark this puppy + if (idx) rSv_Hid(idx) = '\0'; + } + } + } + #undef rSv_Pid + #undef rSv_Lvl + #undef rSv_Hid + } + } // end: !Tree_idx memcpy(Seed_ppt, Tree_ppt, sizeof(void*) * PIDSmaxt); - #undef rLevel } // end: forest_begin @@ -4305,8 +4344,9 @@ static void forest_begin (WIN_t *q) { * when in forest view mode, otherwise he just returns 'em. */ static inline const char *forest_colour (const WIN_t *q, struct pids_stack *p) { // tailored 'results stack value' extractor macros - #define rSv(E) PID_VAL(E, str, p) - #define rLevel PID_VAL(eu_XTRA, u_int, p) + #define rSv(E) PID_VAL(E, str, p) + #define rSv_Lvl PID_VAL(eu_LVL, u_int, p) + #define rSv_Hid PID_VAL(eu_HID, s_ch, p) #ifndef SCROLLVAR_NO static char buf[1024*64*2]; // the same as libray's max buffer size #else @@ -4314,12 +4354,20 @@ static inline const char *forest_colour (const WIN_t *q, struct pids_stack *p) { #endif const char *which = (CHKw(q, Show_CMDLIN)) ? rSv(eu_CMDLINE) : rSv(EU_CMD); - if (!CHKw(q, Show_FOREST) || !rLevel) return which; - if (rLevel > 100) snprintf(buf, sizeof(buf), "%400s%s", " + ", which); - else snprintf(buf, sizeof(buf), "%*s%s", (4 * rLevel), " `- ", which); + if (!CHKw(q, Show_FOREST) || rSv_Lvl == 0) return which; +#ifndef TREE_VWINALL + if (q == Curwin) // note: the following is NOT indented +#endif + if (rSv_Hid == 'x') { + snprintf(buf, sizeof(buf), "+%*s%s", ((4 * rSv_Lvl) - 1), "`- ", which); + return buf; + } + if (rSv_Lvl > 100) snprintf(buf, sizeof(buf), "%400s%s", " + ", which); + else snprintf(buf, sizeof(buf), "%*s%s", (4 * rSv_Lvl), " `- ", which); return buf; #undef rSv - #undef rLevel + #undef rSv_Lvl + #undef rSv_Hid } // end: forest_colour /*###### Interactive Input Tertiary support ############################*/ @@ -4887,6 +4935,29 @@ static void keys_task (int ch) { capsmk(w); } break; + case kbd_CtrlV: + if (VIZCHKw(w)) { + if (CHKw(w, Show_FOREST)) { + int i, pid = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); +#ifdef TREE_VPROMPT + int got = get_int(fmtmk(N_txt(XTRA_vforest_fmt), pid)); + if (got < GET_NUM_NOT) break; + if (got > GET_NUM_NOT) pid = got; +#endif + for (i = 0; i < Hide_tot; i++) { + if (Hide_pid[i] == pid || Hide_pid[i] == -pid) { + Hide_pid[i] = -Hide_pid[i]; + break; + } + } + if (i == Hide_tot) Hide_pid[Hide_tot++] = pid; + // plenty of room, but if everything's expaned let's reset ... + for (i = 0; i < Hide_tot; i++) + if (Hide_pid[i] > 0) break; + if (i == Hide_tot) Hide_tot = 0; + } + } + break; default: // keep gcc happy break; } @@ -4899,12 +4970,14 @@ static void keys_window (int ch) { switch (ch) { case '+': if (ALTCHKw) wins_reflag(Flags_OFF, EQUWINS_xxx); + Hide_tot = 0; break; case '-': if (ALTCHKw) TOGw(w, Show_TASKON); break; case '=': win_reset(w); + Hide_tot = 0; break; case '_': if (ALTCHKw) wins_reflag(Flags_TOG, Show_TASKON); @@ -5094,7 +5167,7 @@ static void do_key (int ch) { { keys_task, { '#', '<', '>', 'b', 'c', 'i', 'J', 'j', 'n', 'O', 'o' , 'R', 'S', 'U', 'u', 'V', 'x', 'y', 'z' - , kbd_CtrlO, '\0' } }, + , kbd_CtrlO, kbd_CtrlV, '\0' } }, { keys_window, { '+', '-', '=', '_', '&', 'A', 'a', 'G', 'L', 'w' , kbd_UP, kbd_DOWN, kbd_LEFT, kbd_RIGHT, kbd_PGUP, kbd_PGDN @@ -5414,6 +5487,15 @@ static const char *task_show (const WIN_t *q, struct pids_stack *p) { char *rp; int x; + /* we're employing a couple of 'PIDS_extra' results in our stacks + eu_LVL (u_int): where level number is stored (0 - 100) + eu_HID (s_ch) : where 'x' == collapsed and 'z' == unseen */ +#ifndef TREE_VWINALL + if (q == Curwin) // note: the following is NOT indented +#endif + if (CHKw(q, Show_FOREST) && rSv(eu_HID, s_ch) == 'z') + return ""; + // we must begin a row with a possible window number in mind... *(rp = rbuf) = '\0'; if (Rc.mode_altscr) rp = scat(rp, " "); diff --git a/top/top.h b/top/top.h index ed4dfea0..07e60e59 100644 --- a/top/top.h +++ b/top/top.h @@ -48,6 +48,8 @@ //#define TERMIOS_ONLY /* just limp along with native input only */ //#define TREE_NORESET /* sort keys do NOT force forest view OFF */ //#define TREE_SCANALL /* rescan array w/ forest view, avoid sort */ +//#define TREE_VPROMPT /* pid collapse/expand prompt, vs. top row */ +//#define TREE_VWINALL /* pid collapse/expand impacts all windows */ //#define USE_X_COLHDR /* emphasize header vs. whole col, for 'x' */ //#define VALIDATE_NLS /* validate the integrity of all nls tbls */ //#define VER_J_RCFILE /* increase # of fields, rcfile ver to 'j' */ @@ -151,6 +153,7 @@ char *strcasestr(const char *haystack, const char *needle); #define kbd_INS 138 #define kbd_DEL 139 #define kbd_CtrlO '\017' +#define kbd_CtrlV '\026' /* Special value in Pseudo_row to force an additional procs refresh -- used at startup and for task/thread mode transitions */ diff --git a/top/top_nls.c b/top/top_nls.c index 0160268d..db0b0dfb 100644 --- a/top/top_nls.c +++ b/top/top_nls.c @@ -501,6 +501,7 @@ static void build_norm_nlstab (void) { Norm_nlstab[LIB_errorcpu_fmt] = _("library failed cpu statistics, at %d: %s"); Norm_nlstab[LIB_errorpid_fmt] = _("library failed pids statistics, at %d: %s"); Norm_nlstab[BAD_memscale_fmt] = _("bad memory scaling arg '%c'"); + Norm_nlstab[XTRA_vforest_fmt] = _("PID to collapse/expand [default pid = %d]"); } @@ -561,7 +562,7 @@ static void build_uniq_nlstab (void) { " z~5,~1b~5 . Toggle: '~1z~2' color/mono; '~1b~2' bold/reverse (only if 'x' or 'y')\n" " u,U,o,O . Filter by: '~1u~2'/'~1U~2' effective/any user; '~1o~2'/'~1O~2' other criteria\n" " n,#,^O . Set: '~1n~2'/'~1#~2' max tasks displayed; Show: ~1Ctrl~2+'~1O~2' other filter(s)\n" - " C,... . Toggle scroll coordinates msg for: ~1up~2,~1down~2,~1left~2,~1right~2,~1home~2,~1end~2\n" + " C,^V . Toggle: '~1C~2' coordinates; ~1Ctrl~2+'~1V~2' hide/show forest view children\n" "\n" "%s" " W,Y Write configuration file '~1W~2'; Inspect other output '~1Y~2'\n" diff --git a/top/top_nls.h b/top/top_nls.h index 7debd0d2..d8bdf232 100644 --- a/top/top_nls.h +++ b/top/top_nls.h @@ -82,7 +82,8 @@ enum norm_nls { WORD_abv_mem_txt, WORD_abv_swp_txt, WORD_allcpus_txt, WORD_another_txt, WORD_eachcpu_fmt, WORD_exclude_txt, WORD_include_txt, WORD_noneone_txt, WORD_process_txt, WORD_threads_txt, WRITE_rcfile_fmt, WRONG_switch_fmt, - XTRA_badflds_fmt, XTRA_fixwide_fmt, XTRA_warncfg_txt, XTRA_winsize_txt, + XTRA_badflds_fmt, XTRA_fixwide_fmt, XTRA_vforest_fmt, XTRA_warncfg_txt, + XTRA_winsize_txt, #ifndef INSP_OFFDEMO YINSP_demo01_txt, YINSP_demo02_txt, YINSP_demo03_txt, YINSP_deqfmt_txt, YINSP_deqtyp_txt, YINSP_dstory_txt,