// // aegis - project change supervisor // Copyright (C) 2002-2008 Peter Miller // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see // . // // // The following is abstracted from the bash(1) man page: // // Programmable Completion // When word completion is attempted for an argument to a command for // which a completion specification (a compspec) has been defined // using the complete builtin (see SHELL BUILTIN COMMANDS below), // the programmable completion facilities are invoked. // // First, the command name is identified. If a compspec has been // defined for that command, the compspec is used to generate the // list of possible completions for the word. If the command word // is a full pathname, a compspec for the full pathname is searched // for first. If no compspec is found for the full pathname, an // attempt is made to find a compspec for the portion following // the final slash. // // To use the aecomplete command, use a compspec of the form // complete -C aecomplete // The list of commands that are viable depends on how much of // aecomplete has been finished. // // Any command specified with the -C option is invoked in // an environment equivalent to command substitution. It should // print a list of completions, one per line, to the standard // output. Backslash may be used to escape a newline, if necessary. // // When the command is invoked, the COMP_LINE and COMP_POINT // variables are assigned values as described below. When the // command is invoked, the first argument is the name of the command // whose arguments are being completed, the second argument is // the word being completed, and the third argument is the word // preceding the word being completed on the current command line. // // COMP_LINE // The current command line. // // COMP_POINT // The index of the current cursor position relative to the beginning // of the current command. // #include #include #include #include #include #include #include #include #include #include #include struct shell_bash_ty { shell_ty inherited; string_ty *command; string_ty *prefix; }; static char * copy_of(const char *s, size_t len) { char *result; result = (char *)mem_alloc(len + 1); memcpy(result, s, len); result[len] = 0; return result; } static void destructor(shell_ty *sp) { shell_bash_ty *this_thing; this_thing = (shell_bash_ty *)sp; if (this_thing->command) str_free(this_thing->command); if (this_thing->prefix) str_free(this_thing->prefix); } static void usage(void) { const char *prog; prog = progname_get(); fprintf(stderr, "Usage: %s \n", prog); exit(1); } static int test(shell_ty *sp) { shell_bash_ty *this_thing; char *cp; char *end; unsigned long n; char *comp_line; unsigned long comp_point; int ac; int ac_max; char **av; int inco_ac; this_thing = (shell_bash_ty *)sp; // // The COMP_LINE environment variable must be set and valid. // comp_line = getenv("COMP_LINE"); if (!comp_line || !*comp_line) return 0; // // The COMP_POINT environment variable must be set and valid. // cp = getenv("COMP_POINT"); if (!cp) return 0; n = strtoul(cp, &end, 10); if (end == cp || *end) return 0; comp_point = n; if (comp_point > strlen(comp_line)) return 0; // // There should be exactly 3 command line arguments: // 1. the name of the command being completed // 2. the word to be completed // 3. the word before the word being completed // if (arglex_get_string() != arglex_token_string) usage(); this_thing->command = str_from_c(arglex_value.alv_string); if (arglex_get_string() != arglex_token_string) usage(); this_thing->prefix = str_from_c(arglex_value.alv_string); if (arglex_get_string() != arglex_token_string) usage(); if (arglex_get_string() != arglex_token_eoln) usage(); // // Generate the new command line, by splitting the comp_line string // into words. // ac = 0; ac_max = 0; av = 0; inco_ac = -1; for (cp = comp_line; ; ) { // // If the completion point is in the middle of white space, // or at the start of another word, insert an empty argument to // serve as the imcomplete argument in need of attention. // if ((unsigned long)(cp - comp_line) == comp_point) { // // insert the empty string as the incomplete argument. // if (ac >= ac_max) { ac_max = ac_max * 2 + 8; char **new_av = new char * [ac_max]; for (int k = 0; k < ac; ++k) new_av[k] = av[k]; delete [] av; av = new_av; } inco_ac = ac; av[ac++] = copy_of(cp, 0); comp_point = (unsigned long)-1; } // // end of the line // if (!*cp) { break; } // // Skip white space around words. // if (isspace((unsigned char)*cp)) { ++cp; continue; } // // Collect one word. // // Note that the completion point also terminates the word, // even if there is no white space present. // end = cp; while ( *end && !isspace((unsigned char)*end) && end != comp_line + comp_point ) { ++end; } // // Insert word into the list. // if (ac >= ac_max) { ac_max = ac_max * 2 + 8; char **new_av = new char * [ac_max]; for (int j = 0; j < ac; ++j) new_av[j] = av[j]; delete [] av; av = new_av; } if ( (size_t)(cp - comp_line) < comp_point && comp_point <= (size_t)(end - comp_line) ) { inco_ac = ac; comp_point = (unsigned long)-1; } av[ac++] = copy_of(cp, end - cp); // // Move past the word. // cp = end; } assert(inco_ac >= 0); // // NULL terminate the list opf word pointers. // if (ac >= ac_max) { ac_max = ac_max * 2 + 8; char **new_av = new char * [ac_max]; for (int j = 0; j < ac; ++j) new_av[j] = av[j]; delete [] av; av = new_av; } av[ac] = 0; // // Insert our fake command line into the command line processor. // arglex_synthetic(ac, av, inco_ac); // // Report success. // return 1; } static string_ty * command_get(shell_ty *sh) { shell_bash_ty *this_thing; this_thing = (shell_bash_ty *)sh; return this_thing->command; } static string_ty * prefix_get(shell_ty *sh) { shell_bash_ty *this_thing; this_thing = (shell_bash_ty *)sh; return this_thing->prefix; } static void emit(shell_ty *, string_ty *s) { char *cp; for (cp = s->str_text; *cp; ++cp) { switch (*cp) { case '\\': case '\n': putchar('\\'); // fall through... default: putchar(*cp); } } putchar('\n'); } static shell_vtbl_ty vtbl = { destructor, test, command_get, prefix_get, emit, sizeof(shell_bash_ty), "bash", }; shell_ty * shell_bash(void) { shell_ty *sp; shell_bash_ty *this_thing; sp = shell_new(&vtbl); this_thing = (shell_bash_ty *)sp; this_thing->command = 0; this_thing->prefix = 0; return sp; }