@@ -529,9 +529,68 @@ static char *list_completion_function(const char *text, int state) {
529529 return result ;
530530}
531531
532- char * * builtin_completion (const char * text , int UNUSED start , int UNUSED end ) {
533- char * * matches = NULL ;
532+ /* detect if we're currently at the start of a line. works ~80% well */
533+ static Boolean cmdstart (int point ) {
534+ int i ;
535+ Boolean quote = FALSE, start = TRUE;
536+ for (i = 0 ; i < point ; i ++ ) {
537+ char c = rl_line_buffer [i ];
538+ switch (c ) {
539+ case '\'' :
540+ quote = !quote ;
541+ break ;
542+ case '&' : case '|' : case '{' : case '`' :
543+ if (!quote ) start = TRUE;
544+ break ;
545+ /* \n doesn't work right :( */
546+ case ' ' : case '\t' : case '\n' : case '!' :
547+ break ;
548+ default :
549+ if (!quote ) start = FALSE;
550+ }
551+ }
552+ return start ;
553+ }
534554
555+ /* first-position completion. includes
556+ * - built-ins: local let for fn %closure match
557+ * - functions
558+ * - if absolute (including home), absolute executable files
559+ */
560+ static List * listexecutables (char * text ) {
561+ int i = 0 ;
562+ char * s ;
563+ List * compl = NULL ;
564+ static char * builtins [] = {"local" , "let" , "for" , "fn" , "%closure" , "match" };
565+ for (i = 0 ; i < 6 ; i ++ )
566+ if (strneq (text , builtins [i ], strlen (text )))
567+ compl = mklist (mkstr (builtins [i ]), compl );
568+
569+ compl = append (fnswithprefix (text ), compl );
570+
571+ if (isabsolute (text ) || * text == '~' ) {
572+ i = 0 ;
573+ /* goofy hack :) */
574+ while ((s = rl_filename_completion_function (text , i )) != NULL ) {
575+ i = 1 ;
576+ struct stat st ;
577+ /* TODO: ~/foo doesn't stat(), so don't try */
578+ if (* s != '~' ) {
579+ if (stat (s , & st ) == -1 )
580+ continue ;
581+ /* 1 == EXEC */
582+ if (testperm (& st , 1 ) != 0 )
583+ continue ;
584+ }
585+ /* TODO: recurse in directories? */
586+ compl = mklist (mkstr (s ), compl );
587+ }
588+ }
589+ return compl ;
590+ }
591+
592+ char * * builtin_completion (const char * text , int start , int UNUSED end ) {
593+ /* variable or primitive completion */
535594 if (* text == '$' ) {
536595 wordslistgen = varswithprefix ;
537596 complprefix = "$" ;
@@ -543,14 +602,22 @@ char **builtin_completion(const char *text, int UNUSED start, int UNUSED end) {
543602 case '^' : complprefix = "$^" ; break ;
544603 case '#' : complprefix = "$#" ; break ;
545604 }
546- matches = rl_completion_matches (text , list_completion_function );
605+ return rl_completion_matches (text , list_completion_function );
547606 }
548607
549608 /* ~foo => username. ~foo/bar already gets completed as filename. */
550- if (!matches && * text == '~' && !strchr (text , '/' ))
551- matches = rl_completion_matches (text , rl_username_completion_function );
609+ if (* text == '~' && !strchr (text , '/' ))
610+ return rl_completion_matches (text , rl_username_completion_function );
611+
612+ /* first-word completion, which is ~special~ */
613+ if (cmdstart (start )) {
614+ wordslistgen = listexecutables ;
615+ complprefix = "" ;
616+ rl_attempted_completion_over = 1 ;
617+ return rl_completion_matches (text , list_completion_function );
618+ }
552619
553- return matches ;
620+ return NULL ; /* fall back to normal filename completion */
554621}
555622#endif /* HAVE_READLINE */
556623
0 commit comments