1- // $Id: yacli.c,v 4.11 2025/03/09 22:18:34 bbonev Exp $
1+ // $Id: yacli.c,v 4.12 2026/04/29 23:03:05 bbonev Exp $
22//
33// Copyright © 2015-2020 Boian Bonev (bbonev@ipacct.com) {{{
44//
@@ -122,7 +122,8 @@ struct _yacli {
122122 history * hst_p ; // pointer in history, NULL when not in history
123123 yascreen * s ; // screen used to render output
124124 void (* cmdcb )(yacli * cli ,const char * cmd ,int code ); // callback for each executed command
125- void (* listcb )(yacli * cli ,void * ctx ,int code ); // callback for getting dynamic list items
125+ void (* listcb )(yacli * cli ,void * ctx ,int code ); // legacy: dynamic list callback (no prefix hint)
126+ void (* listcb2 )(yacli * cli ,void * ctx ,int code ,const char * prefix ); // dynamic list callback with prefix hint for fast-skip
126127 void (* parsedcb )(yacli * cli ,int ac ,char * * cmd ); // user callback for matching command
127128 void (* ctrlzcb )(yacli * cli ); // user callback to notify ctrl-z
128129 yacli_in_state state ; // input bytestream DFA state
@@ -213,7 +214,7 @@ inline void yacli_set_showtermsize(yacli *cli,int v) { // {{{
213214 cli -> showtsize = !!v ;
214215} // }}}
215216
216- static char myver []= "\0Yet another command line interface library (https://github.com/bbonev/yacli) $Revision: 4.11 $\n\n" ; // {{{
217+ static char myver []= "\0Yet another command line interface library (https://github.com/bbonev/yacli) $Revision: 4.12 $\n\n" ; // {{{
217218// }}}
218219
219220inline const char * yacli_ver (void ) { // {{{
@@ -1715,6 +1716,14 @@ inline void yacli_set_list_cb(yacli *cli,void (*listcb)(yacli *cli,void *ctx,int
17151716 if (!cli )
17161717 return ;
17171718 cli -> listcb = listcb ;
1719+ cli -> listcb2 = NULL ;
1720+ } // }}}
1721+
1722+ inline void yacli_set_list_cb2 (yacli * cli ,void (* listcb2 )(yacli * cli ,void * ctx ,int code ,const char * prefix )) { // {{{
1723+ if (!cli )
1724+ return ;
1725+ cli -> listcb2 = listcb2 ;
1726+ cli -> listcb = NULL ;
17181727} // }}}
17191728
17201729inline void yacli_set_cmd_cb (yacli * cli ,void (* cmdcb )(yacli * cli ,const char * cmd ,int code )) { // {{{
@@ -1762,15 +1771,15 @@ static inline void yacli_dyn_vacuum(cmnode *n) { // {{{
17621771 yacli_dyn_vacuum (n -> child );
17631772} // }}}
17641773
1765- static void yacli_dyn_upd (yacli * cli ,cmnode * n ) { // {{{
1774+ static void yacli_dyn_upd (yacli * cli ,cmnode * n , const char * prefix ) { // {{{
17661775 cmnode * pnode ;
17671776
17681777 if (!cli )
17691778 return ;
17701779
17711780 if (!n )
17721781 return ;
1773- if (!cli -> listcb ) // no way to update
1782+ if (!cli -> listcb && ! cli -> listcb2 ) // no way to update
17741783 return ;
17751784 if (n -> cmd [0 ]!= '@' ) // not a dynamic node
17761785 return ;
@@ -1780,7 +1789,10 @@ static void yacli_dyn_upd(yacli *cli,cmnode *n) { // {{{
17801789 n -> dyn = NULL ;
17811790 }
17821791 pnode = n ;
1783- cli -> listcb (cli ,pnode ,atoi (n -> cmd + 1 ));
1792+ if (cli -> listcb2 )
1793+ cli -> listcb2 (cli ,pnode ,atoi (n -> cmd + 1 ),prefix ?prefix :"" );
1794+ else
1795+ cli -> listcb (cli ,pnode ,atoi (n -> cmd + 1 ));
17841796} // }}}
17851797
17861798static inline int yacli_cmd_dump_node (yacli * cli ,cmnode * n ) { // {{{
@@ -1793,7 +1805,7 @@ static inline int yacli_cmd_dump_node(yacli *cli,cmnode *n) { // {{{
17931805 cmnode * p ;
17941806 int first = 1 ;
17951807
1796- yacli_dyn_upd (cli ,n );
1808+ yacli_dyn_upd (cli ,n , "" );
17971809 p = n -> dyn ;
17981810 if (!p )
17991811 return 1 ;
@@ -1980,7 +1992,7 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
19801992 int cmp ;
19811993
19821994 if (cn -> cmd [0 ]== '@' ) { // dynamic command
1983- yacli_dyn_upd (cli ,cn );
1995+ yacli_dyn_upd (cli ,cn , word );
19841996 dyn = 1 ;
19851997 cn = cn -> dyn ;
19861998 if (!cn )
@@ -2027,9 +2039,18 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
20272039 break ;
20282040 } else if (cmp < 0 ) { // check for single/multiple possibilities
20292041 int isprefix = strncmp (word ,cn -> cmd ,strlen (word ))== 0 && strlen (word )< strlen (cn -> cmd );
2030- int nxprefix = cn -> next && strncmp (word ,cn -> next -> cmd ,strlen (word ))== 0 && strlen (word )< strlen (cn -> next -> cmd );
2042+ int nxprefix = cn -> next && cn -> next -> cmd [ 0 ] != '^' && cn -> next -> cmd [ 0 ] != '@' && strncmp (word ,cn -> next -> cmd ,strlen (word ))== 0 && strlen (word )< strlen (cn -> next -> cmd );
20312043
2032- if (!isprefix ) {
2044+ if (!isprefix ) { // word sorts before this literal and isn't a prefix; fall through to regex/dyn tail siblings, if any
2045+ cmnode * t = cn -> next ;
2046+
2047+ while (t && t -> cmd [0 ]!= '^' && t -> cmd [0 ]!= '@' ) // skip remaining literals (all sort even later, can't match)
2048+ t = t -> next ;
2049+ if (t ) {
2050+ cn = t ;
2051+ lastcn = cn ;
2052+ continue ;
2053+ }
20332054 yacli_print_nof (cli ,"\nNo matched command (2)\n" );
20342055 cli -> redraw = 1 ;
20352056 free (fb );
@@ -2080,7 +2101,7 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
20802101 completex = 0 ;
20812102 cli -> parsedcb = NULL ;
20822103
2083- while (t ) {
2104+ while (t && t -> cmd [ 0 ] != '^' && t -> cmd [ 0 ] != '@' ) { // ignore regex/dyn tail siblings during literal partial-complete scan
20842105 int lisprefix = strncmp (word ,t -> cmd ,strlen (word ))== 0 && strlen (word )< strlen (cn -> cmd );
20852106
20862107 if (lisprefix )
@@ -2334,29 +2355,35 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
23342355 if (cn ) // if we didn't hit leaf in tree, go one node up
23352356 lastcn = lastcn -> parent ;
23362357
2337- p = lastcn -> child ;
2338- if (p && p -> cmd [0 ]== '@' ) {
2339- yacli_dyn_upd (cli ,p );
2340- dyn = 1 ;
2341- p = p -> dyn ;
2342- }
2343-
23442358 // calculate max len of printed stuff
23452359 if (lastcn -> isdyn ?lastcn -> parent -> cb :lastcn -> cb ) // print self, if valid alone
23462360 maxcmdlen = strlen ("<cr>" );
2347- while (p ) { // print children
2348- maxcmdlen = mymax (maxcmdlen ,yacli_cmd_help_len (p -> cmd ,p -> help ,!!p -> cb ));
2361+ p = lastcn -> child ;
2362+ while (p ) { // walk children, expanding any @-dynamic node inline
2363+ if (p -> cmd [0 ]== '@' ) {
2364+ cmnode * d ;
2365+
2366+ yacli_dyn_upd (cli ,p ,"" );
2367+ dyn = 1 ;
2368+ for (d = p -> dyn ;d ;d = d -> next )
2369+ maxcmdlen = mymax (maxcmdlen ,yacli_cmd_help_len (d -> cmd ,d -> isdyn ?d -> parent -> help :d -> help ,!!d -> cb ));
2370+ } else
2371+ maxcmdlen = mymax (maxcmdlen ,yacli_cmd_help_len (p -> cmd ,p -> help ,!!p -> cb ));
23492372 p = p -> next ;
23502373 }
23512374
23522375 // print in columns based on calculated max len
23532376 if (lastcn -> isdyn ?lastcn -> parent -> cb :lastcn -> cb )
23542377 yacli_cmd_help_pr (cli ,"" ,lastcn -> isdyn ?lastcn -> parent -> help :lastcn -> help ,1 ,maxcmdlen );
23552378 p = lastcn -> child ;
2356- if (p && p -> cmd [0 ]== '@' )
2357- p = p -> dyn ;
23582379 while (p ) {
2359- yacli_cmd_help_pr (cli ,p -> cmd ,p -> isdyn ?p -> parent -> help :p -> help ,!!p -> cb ,maxcmdlen );
2380+ if (p -> cmd [0 ]== '@' ) {
2381+ cmnode * d ;
2382+
2383+ for (d = p -> dyn ;d ;d = d -> next )
2384+ yacli_cmd_help_pr (cli ,d -> cmd ,d -> isdyn ?d -> parent -> help :d -> help ,!!d -> cb ,maxcmdlen );
2385+ } else
2386+ yacli_cmd_help_pr (cli ,p -> cmd ,p -> isdyn ?p -> parent -> help :p -> help ,!!p -> cb ,maxcmdlen );
23602387 p = p -> next ;
23612388 }
23622389 }
@@ -2383,29 +2410,50 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
23832410 else
23842411 if (cn && alonematch ) // last word was executable alone, so we have dived one more level, pop it up
23852412 lastcn = lastcn -> parent ;
2386- p = lastcn ;
2387- if (p && p -> cmd [0 ]== '@' ) {
2388- yacli_dyn_upd (cli ,p );
2389- dyn = 1 ;
2390- p = p -> dyn ;
2391- }
23922413 // calculate max len of printed stuff
2414+ p = lastcn ;
23932415 while (p ) {
2394- int isprefix = strncmp (lastword ,p -> cmd ,strlen (lastword ))== 0 && strlen (lastword )<=strlen (p -> cmd );
2416+ if (p -> cmd [0 ]== '@' ) {
2417+ cmnode * d ;
2418+
2419+ yacli_dyn_upd (cli ,p ,lastword );
2420+ dyn = 1 ;
2421+ for (d = p -> dyn ;d ;d = d -> next ) {
2422+ int isprefix = strncmp (lastword ,d -> cmd ,strlen (lastword ))== 0 && strlen (lastword )<=strlen (d -> cmd );
2423+
2424+ if (isprefix )
2425+ maxcmdlen = mymax (maxcmdlen ,yacli_cmd_help_len (d -> cmd ,d -> isdyn ?d -> parent -> help :d -> help ,!!d -> cb ));
2426+ }
2427+ } else if (p -> cmd [0 ]== '^' ) // regex placeholder always offered as a hint
2428+ maxcmdlen = mymax (maxcmdlen ,yacli_cmd_help_len (p -> cmd ,p -> help ,!!p -> cb ));
2429+ else {
2430+ int isprefix = strncmp (lastword ,p -> cmd ,strlen (lastword ))== 0 && strlen (lastword )<=strlen (p -> cmd );
23952431
2396- if (isprefix )
2397- maxcmdlen = mymax (maxcmdlen ,strlen (p -> cmd )+ (p -> cb ?5 :0 ));
2432+ if (isprefix )
2433+ maxcmdlen = mymax (maxcmdlen ,yacli_cmd_help_len (p -> cmd ,p -> help ,!!p -> cb ));
2434+ }
23982435 p = p -> next ;
23992436 }
24002437 // print in columns based on calculated max len
24012438 p = lastcn ;
2402- if (p && p -> cmd [0 ]== '@' )
2403- p = p -> dyn ;
24042439 while (p ) {
2405- int isprefix = strncmp (lastword ,p -> cmd ,strlen (lastword ))== 0 && strlen (lastword )<=strlen (p -> cmd );
2440+ if (p -> cmd [0 ]== '@' ) {
2441+ cmnode * d ;
24062442
2407- if (isprefix )
2408- yacli_cmd_help_pr (cli ,p -> cmd ,p -> isdyn ?p -> parent -> help :p -> help ,!!p -> cb ,maxcmdlen );
2443+ for (d = p -> dyn ;d ;d = d -> next ) {
2444+ int isprefix = strncmp (lastword ,d -> cmd ,strlen (lastword ))== 0 && strlen (lastword )<=strlen (d -> cmd );
2445+
2446+ if (isprefix )
2447+ yacli_cmd_help_pr (cli ,d -> cmd ,d -> isdyn ?d -> parent -> help :d -> help ,!!d -> cb ,maxcmdlen );
2448+ }
2449+ } else if (p -> cmd [0 ]== '^' )
2450+ yacli_cmd_help_pr (cli ,p -> cmd ,p -> help ,!!p -> cb ,maxcmdlen );
2451+ else {
2452+ int isprefix = strncmp (lastword ,p -> cmd ,strlen (lastword ))== 0 && strlen (lastword )<=strlen (p -> cmd );
2453+
2454+ if (isprefix )
2455+ yacli_cmd_help_pr (cli ,p -> cmd ,p -> isdyn ?p -> parent -> help :p -> help ,!!p -> cb ,maxcmdlen );
2456+ }
24092457 p = p -> next ;
24102458 }
24112459 }
@@ -2914,19 +2962,23 @@ inline void *yacli_add_cmd(yacli *cli,void *parent,const char *cmd,const char *h
29142962 else
29152963 place = & cli -> cmdt ;
29162964
2917- if ((cmd [0 ]== '^' || cmd [0 ]== '@' )&& * place ) // cannot combine dynamic/regex and static commands
2918- return NULL ;
2919- if (* place && ((* place )-> cmd [0 ]== '^' || (* place )-> cmd [0 ]== '@' ))
2920- return NULL ;
2965+ if (cmd [0 ]== '^' || cmd [0 ]== '@' ) { // regex/dynamic always tail; preserve insertion order among themselves
2966+ while (* place )
2967+ place = & (* place )-> next ;
2968+ } else { // literals: sorted; stop before any regex/dynamic siblings
2969+ while (* place ) {
2970+ int cmp ;
29212971
2922- while (* place ) {
2923- int cmp = strcmp (cmd ,(* place )-> cmd );
2972+ if ((* place )-> cmd [0 ]== '^' || (* place )-> cmd [0 ]== '@' ) // reached the regex/dyn tail; insert before it
2973+ break ;
2974+ cmp = strcmp (cmd ,(* place )-> cmd );
29242975
2925- if (cmp == 0 ) // duplicate command
2926- return NULL ;
2927- if (cmp < 0 ) // found proper place
2928- break ;
2929- place = & (* place )-> next ;
2976+ if (cmp == 0 ) // duplicate command
2977+ return NULL ;
2978+ if (cmp < 0 ) // found proper place
2979+ break ;
2980+ place = & (* place )-> next ;
2981+ }
29302982 }
29312983
29322984 t = calloc (1 ,sizeof * t );
0 commit comments