diff options
Diffstat (limited to 'bashline.c')
-rw-r--r-- | bashline.c | 1797 |
1 files changed, 1797 insertions, 0 deletions
diff --git a/bashline.c b/bashline.c new file mode 100644 index 0000000..a924b05 --- /dev/null +++ b/bashline.c @@ -0,0 +1,1797 @@ +/* bashline.c -- Bash's interface to the readline library. */ + +/* Copyright (C) 1987,1991 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash 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 1, or (at your option) + any later version. + + Bash 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 Bash; see the file COPYING. If not, write to the Free + Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "bashtypes.h" +#include "posixstat.h" + +#include <stdio.h> +#include "bashansi.h" +#include <readline/rlconf.h> +#include <readline/readline.h> +#include <readline/history.h> +#include "shell.h" +#include "builtins.h" +#include "builtins/common.h" +#include "bashhist.h" +#include "execute_cmd.h" + +#if defined (ALIAS) +# include "alias.h" +#endif + +#if defined (BRACE_EXPANSION) +# define BRACE_COMPLETION +#endif /* BRACE_EXPANSION */ + +#if defined (BRACE_COMPLETION) +extern void bash_brace_completion (); +#endif /* BRACE_COMPLETION */ + +/* Functions bound to keys in Readline for Bash users. */ +static void shell_expand_line (); +static void display_shell_version (), operate_and_get_next (); +static void history_expand_line (), bash_ignore_filenames (); + +/* Helper functions for Readline. */ +static int bash_directory_completion_hook (); +static void filename_completion_ignore (); +static void bash_push_line (); + +static char **attempt_shell_completion (); +static char *variable_completion_function (); +static char *hostname_completion_function (); +static char *command_word_completion_function (); +static char *command_subst_completion_function (); + +static void snarf_hosts_from_file (), add_host_name (); +static void sort_hostname_list (); + +#define DYNAMIC_HISTORY_COMPLETION +#if defined (DYNAMIC_HISTORY_COMPLETION) +static void dynamic_complete_history (); +#endif /* DYNAMIC_HISTORY_COMPLETION */ + +/* Variables used here but defined in other files. */ +extern int posixly_correct, no_symbolic_links; +extern int rl_explicit_arg; +extern char *current_prompt_string, *ps1_prompt; +extern STRING_INT_ALIST word_token_alist[]; +extern Function *rl_last_func; +extern int rl_filename_completion_desired; + +/* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual + completion functions which indicate what type of completion should be + done (at or before point) that can be bound to key sequences with + the readline library. */ +#define SPECIFIC_COMPLETION_FUNCTIONS + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) +static void + bash_specific_completion (), + bash_complete_filename (), bash_possible_filename_completions (), + bash_complete_filename_internal (), + bash_complete_username (), bash_possible_username_completions (), + bash_complete_username_internal (), + bash_complete_hostname (), bash_possible_hostname_completions (), + bash_complete_hostname_internal (), + bash_complete_variable (), bash_possible_variable_completions (), + bash_complete_variable_internal (), + bash_complete_command (), bash_possible_command_completions (), + bash_complete_command_internal (); +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + +/* Non-zero once initalize_readline () has been called. */ +int bash_readline_initialized = 0; + +#if defined (VI_MODE) +static void vi_edit_and_execute_command (); +extern char *rl_vi_comment_begin; +#endif + +static Function *old_rl_startup_hook = (Function *) NULL; + +/* Change the readline VI-mode keymaps into or out of Posix.2 compliance. + Called when the shell is put into or out of `posix' mode. */ +void +posix_readline_initialize (on_or_off) + int on_or_off; +{ +#if defined (VI_MODE) + if (on_or_off) + { + rl_bind_key_in_map (CTRL('I'), rl_insert, vi_insertion_keymap); + if (rl_vi_comment_begin) + free (rl_vi_comment_begin); + rl_vi_comment_begin = savestring ("#"); + } + else + rl_bind_key_in_map (CTRL('I'), rl_complete, vi_insertion_keymap); +#endif +} + +/* Called once from parse.y if we are going to use readline. */ +void +initialize_readline () +{ + if (bash_readline_initialized) + return; + + rl_terminal_name = get_string_value ("TERM"); + rl_instream = stdin; + rl_outstream = stderr; + rl_special_prefixes = "$@"; + + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name = "Bash"; + + /* Bind up our special shell functions. */ + rl_add_defun ("shell-expand-line", (Function *)shell_expand_line, -1); + rl_bind_key_in_map + (CTRL('E'), (Function *)shell_expand_line, emacs_meta_keymap); + + /* Bind up our special shell functions. */ + rl_add_defun ("history-expand-line", (Function *)history_expand_line, -1); + rl_bind_key_in_map ('^', (Function *)history_expand_line, emacs_meta_keymap); + + /* Backwards compatibility. */ + rl_add_defun ("insert-last-argument", rl_yank_last_arg, -1); + + rl_add_defun + ("operate-and-get-next", (Function *)operate_and_get_next, CTRL('O')); + + rl_add_defun + ("display-shell-version", (Function *)display_shell_version, -1); + + rl_bind_key_in_map + (CTRL ('V'), (Function *)display_shell_version, emacs_ctlx_keymap); + + /* In Bash, the user can switch editing modes with "set -o [vi emacs]", + so it is not necessary to allow C-M-j for context switching. Turn + off this occasionally confusing behaviour. */ + rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap); + rl_unbind_key_in_map (CTRL('M'), emacs_meta_keymap); +#if defined (VI_MODE) + rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap); +#endif + +#if defined (BRACE_COMPLETION) + rl_add_defun ("complete-into-braces", bash_brace_completion, -1); + rl_bind_key_in_map ('{', bash_brace_completion, emacs_meta_keymap); +#endif /* BRACE_COMPLETION */ + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) + rl_add_defun ("complete-filename", bash_complete_filename, -1); + rl_bind_key_in_map ('/', bash_complete_filename, emacs_meta_keymap); + rl_add_defun ("possible-filename-completions", + bash_possible_filename_completions, -1); + rl_bind_key_in_map ('/', bash_possible_filename_completions, + emacs_ctlx_keymap); + + rl_add_defun ("complete-username", bash_complete_username, -1); + rl_bind_key_in_map ('~', bash_complete_username, emacs_meta_keymap); + rl_add_defun ("possible-username-completions", + bash_possible_username_completions, -1); + rl_bind_key_in_map ('~', bash_possible_username_completions, + emacs_ctlx_keymap); + + rl_add_defun ("complete-hostname", bash_complete_hostname, -1); + rl_bind_key_in_map ('@', bash_complete_hostname, emacs_meta_keymap); + rl_add_defun ("possible-hostname-completions", + bash_possible_hostname_completions, -1); + rl_bind_key_in_map ('@', bash_possible_hostname_completions, + emacs_ctlx_keymap); + + rl_add_defun ("complete-variable", bash_complete_variable, -1); + rl_bind_key_in_map ('$', bash_complete_variable, emacs_meta_keymap); + rl_add_defun ("possible-variable-completions", + bash_possible_variable_completions, -1); + rl_bind_key_in_map ('$', bash_possible_variable_completions, + emacs_ctlx_keymap); + + rl_add_defun ("complete-command", bash_complete_command, -1); + rl_bind_key_in_map ('!', bash_complete_command, emacs_meta_keymap); + rl_add_defun ("possible-command-completions", + bash_possible_command_completions, -1); + rl_bind_key_in_map ('!', bash_possible_command_completions, + emacs_ctlx_keymap); + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + +#if defined (DYNAMIC_HISTORY_COMPLETION) + rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1); + rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap); +#endif /* DYNAMIC_HISTORY_COMPLETION */ + + /* Tell the completer that we want a crack first. */ + rl_attempted_completion_function = (CPPFunction *)attempt_shell_completion; + + /* Tell the completer that we might want to follow symbolic links or + do other expansion on directory names. */ + rl_directory_completion_hook = bash_directory_completion_hook; + + /* Tell the filename completer we want a chance to ignore some names. */ + rl_ignore_some_completions_function = (Function *)filename_completion_ignore; + +#if defined (VI_MODE) + rl_bind_key_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap); +#endif + + rl_completer_quote_characters = "'\""; + /* Need to modify this from the default; `$', `{', `\', and ``' are not + word break characters. */ + rl_completer_word_break_characters = " \t\n\"'@><=;|&("; /**/ + + if (posixly_correct) + posix_readline_initialize (1); + + bash_readline_initialized = 1; +} + +/* On Sun systems at least, rl_attempted_completion_function can end up + getting set to NULL, and rl_completion_entry_function set to do command + word completion if Bash is interrupted while trying to complete a command + word. This just resets all the completion functions to the right thing. + It's called from throw_to_top_level(). */ +void +bashline_reinitialize () +{ + tilde_initialize (); + rl_attempted_completion_function = attempt_shell_completion; + rl_completion_entry_function = (Function *)NULL; + rl_directory_completion_hook = bash_directory_completion_hook; + rl_ignore_some_completions_function = (Function *)filename_completion_ignore; +} + +/* Contains the line to push into readline. */ +static char *push_to_readline = (char *)NULL; + +/* Push the contents of push_to_readline into the + readline buffer. */ +static void +bash_push_line () +{ + if (push_to_readline) + { + rl_insert_text (push_to_readline); + free (push_to_readline); + push_to_readline = (char *)NULL; + rl_startup_hook = old_rl_startup_hook; + } +} + +/* Call this to set the initial text for the next line to read + from readline. */ +int +bash_re_edit (line) + char *line; +{ + if (push_to_readline) + free (push_to_readline); + + push_to_readline = savestring (line); + old_rl_startup_hook = rl_startup_hook; + rl_startup_hook = (Function *)bash_push_line; + + return (0); +} + +static void +display_shell_version (count, c) + int count, c; +{ + crlf (); + show_shell_version (); + putc ('\r', rl_outstream); + fflush (rl_outstream); + rl_on_new_line (); + rl_redisplay (); +} + +/* **************************************************************** */ +/* */ +/* Readline Stuff */ +/* */ +/* **************************************************************** */ + +/* If the user requests hostname completion, then simply build a list + of hosts, and complete from that forever more. */ +#if !defined (ETCHOSTS) +#define ETCHOSTS "/etc/hosts" +#endif + +/* The kept list of hostnames. */ +static char **hostname_list = (char **)NULL; + +/* The physical size of the above list. */ +static int hostname_list_size = 0; + +/* The length of the above list. */ +static int hostname_list_length = 0; + +/* Whether or not HOSTNAME_LIST has been initialized. */ +int hostname_list_initialized = 0; + +/* Non-zero means that HOSTNAME_LIST needs to be sorted. */ +static int hostname_list_needs_sorting = 0; + +/* Initialize the hostname completion table. */ +static void +initialize_hostname_list () +{ + char *temp; + + temp = get_string_value ("HOSTFILE"); + if (!temp) + temp = get_string_value ("hostname_completion_file"); + if (!temp) + temp = ETCHOSTS; + + snarf_hosts_from_file (temp); + sort_hostname_list (); + + if (hostname_list) + hostname_list_initialized++; +} + +/* Add NAME to the list of hosts. */ +static void +add_host_name (name) + char *name; +{ + if (hostname_list_length + 2 > hostname_list_size) + { + hostname_list = (char **) + xrealloc (hostname_list, + (1 + (hostname_list_size += 100)) * sizeof (char *)); + } + + hostname_list[hostname_list_length] = savestring (name); + hostname_list[++hostname_list_length] = (char *)NULL; + hostname_list_needs_sorting++; +} + +/* After you have added some names, you should sort the list of names. */ +static void +sort_hostname_list () +{ + if (hostname_list_needs_sorting && hostname_list) + sort_char_array (hostname_list); + hostname_list_needs_sorting = 0; +} + +#define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c)) + +static void +snarf_hosts_from_file (filename) + char *filename; +{ + FILE *file = fopen (filename, "r"); + char *temp, buffer[256], name[256]; + register int i, start; + + if (!file) + return; + + while (temp = fgets (buffer, 255, file)) + { + /* Skip to first character. */ + for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++); + + /* If comment, ignore. */ + if (buffer[i] == '#') + continue; + + /* If `preprocessor' directive, do the include. */ + if (strncmp (&buffer[i], "$include ", 9) == 0) + { + char *includefile = &buffer[i + 9]; + char *t; + + /* Find start of filename. */ + while (*includefile && whitespace (*includefile)) + includefile++; + + t = includefile; + + /* Find end of filename. */ + while (*t && !cr_whitespace (*t)) + t++; + + *t = '\0'; + + snarf_hosts_from_file (includefile); + continue; + } + + /* Skip internet address. */ + for (; buffer[i] && !cr_whitespace (buffer[i]); i++); + + /* Gobble up names. Each name is separated with whitespace. */ + while (buffer[i] && buffer[i] != '#') + { + for (; i && cr_whitespace (buffer[i]); i++); + if (buffer[i] == '#') + continue; + for (start = i; buffer[i] && !cr_whitespace (buffer[i]); i++); + if ((i - start) == 0) + continue; + strncpy (name, buffer + start, i - start); + name[i - start] = '\0'; + add_host_name (name); + } + } + fclose (file); +} + +/* Return a NULL terminated list of hostnames which begin with TEXT. + Initialize the hostname list the first time if neccessary. + The array is malloc ()'ed, but not the individual strings. */ +static char ** +hostnames_matching (text) + char *text; +{ + register int i, len = strlen (text); + register int begin, end; + int last_search = -1; + char **result = (char **)NULL; + + if (!hostname_list_initialized) + { + initialize_hostname_list (); + + if (!hostname_list_initialized) + return ((char **)NULL); + } + + sort_hostname_list (); + + /* The list is sorted. Do a binary search on it for the first character + in TEXT, and then grovel the names of interest. */ + begin = 0; end = hostname_list_length; + + /* Special case. If TEXT consists of nothing, then the whole list is + what is desired. */ + if (!*text) + { + result = (char **)xmalloc ((1 + hostname_list_length) * sizeof (char *)); + for (i = 0; i < hostname_list_length; i++) + result[i] = hostname_list[i]; + result[i] = (char *)NULL; + return (result); + } + + /* Scan until found, or failure. */ + while (end != begin) + { + int r = 0; + + i = ((end - begin) / 2) + begin; + if (i == last_search) + break; + + if (hostname_list[i] && + (r = strncmp (hostname_list[i], text, len)) == 0) + { + while (strncmp (hostname_list[i], text, len) == 0 && i) i--; + if (strncmp (hostname_list[i], text, len) != 0) i++; + + begin = i; + while (hostname_list[i] && + strncmp (hostname_list[i], text, len) == 0) i++; + end = i; + + result = (char **)xmalloc ((1 + (end - begin)) * sizeof (char *)); + for (i = 0; i + begin < end; i++) + result[i] = hostname_list[begin + i]; + result[i] = (char *)NULL; + return (result); + } + + last_search = i; + + if (r < 0) + begin = i; + else + end = i; + } + return ((char **)NULL); +} + +/* The equivalent of the K*rn shell C-o operate-and-get-next-history-line + editing command. */ +static int saved_history_line_to_use = 0; + +static void +set_saved_history () +{ + if (saved_history_line_to_use) + rl_get_previous_history (history_length - saved_history_line_to_use); + saved_history_line_to_use = 0; + rl_startup_hook = old_rl_startup_hook; +} + +static void +operate_and_get_next (count, c) + int count, c; +{ + int where; + + /* Accept the current line. */ + rl_newline (); + + /* Find the current line, and find the next line to use. */ + where = where_history (); + + if ((history_is_stifled () && (history_length >= max_input_history)) || + (where >= history_length - 1)) + saved_history_line_to_use = where; + else + saved_history_line_to_use = where + 1; + + old_rl_startup_hook = rl_startup_hook; + rl_startup_hook = (Function *)set_saved_history; +} + +#if defined (VI_MODE) +/* This vi mode command causes VI_EDIT_COMMAND to be run on the current + command being entered (if no explicit argument is given), otherwise on + a command from the history file. */ + +#define VI_EDIT_COMMAND "fc -e ${VISUAL:-${EDITOR:-vi}}" + +static void +vi_edit_and_execute_command (count, c) +{ + char *command; + + /* Accept the current line. */ + rl_newline (); + + if (rl_explicit_arg) + { + command = xmalloc (strlen (VI_EDIT_COMMAND) + 8); + sprintf (command, "%s %d", VI_EDIT_COMMAND, count); + } + else + { + /* Take the command we were just editing, add it to the history file, + then call fc to operate on it. We have to add a dummy command to + the end of the history because fc ignores the last command (assumes + it's supposed to deal with the command before the `fc'). */ + using_history (); + add_history (rl_line_buffer); + add_history (""); + history_lines_this_session++; + using_history (); + command = savestring (VI_EDIT_COMMAND); + } + parse_and_execute (command, "v", -1); + rl_line_buffer[0] = '\0'; /* erase pre-edited command */ +} +#endif /* VI_MODE */ + +/* **************************************************************** */ +/* */ +/* How To Do Shell Completion */ +/* */ +/* **************************************************************** */ + +/* Do some completion on TEXT. The indices of TEXT in RL_LINE_BUFFER are + at START and END. Return an array of matches, or NULL if none. */ +static char ** +attempt_shell_completion (text, start, end) + char *text; + int start, end; +{ + int in_command_position, ti; + char **matches = (char **)NULL; + char *command_separator_chars = ";|&{(`"; + + rl_ignore_some_completions_function = + (Function *)filename_completion_ignore; + + /* Determine if this could be a command word. It is if it appears at + the start of the line (ignoring preceding whitespace), or if it + appears after a character that separates commands. It cannot be a + command word if we aren't at the top-level prompt. */ + ti = start - 1; + + while ((ti > -1) && (whitespace (rl_line_buffer[ti]))) + ti--; + + in_command_position = 0; + if (ti < 0) + { + /* Only do command completion at the start of a line when we + are prompting at the top level. */ + if (current_prompt_string == ps1_prompt) + in_command_position++; + } + else if (member (rl_line_buffer[ti], command_separator_chars)) + { + register int this_char, prev_char; + + in_command_position++; + + /* Handle the two character tokens `>&', `<&', and `>|'. + We are not in a command position after one of these. */ + this_char = rl_line_buffer[ti]; + prev_char = rl_line_buffer[ti - 1]; + + if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) || + (this_char == '|' && prev_char == '>')) + in_command_position = 0; + else if (char_is_quoted (rl_line_buffer, ti)) + in_command_position = 0; + } + else + { + /* This still could be in command position. It is possible + that all of the previous words on the line are variable + assignments. */ + } + + /* Special handling for command substitution. XXX - this should handle + `$(' as well. */ + if (*text == '`' && unclosed_pair (rl_line_buffer, start, "`")) + matches = completion_matches (text, command_subst_completion_function); + + /* Variable name? */ + if (!matches && *text == '$') + matches = completion_matches (text, variable_completion_function); + + /* If the word starts in `~', and there is no slash in the word, then + try completing this word as a username. */ + if (!matches && *text == '~' && !strchr (text, '/')) + matches = completion_matches (text, username_completion_function); + + /* Another one. Why not? If the word starts in '@', then look through + the world of known hostnames for completion first. */ + if (!matches && *text == '@') + matches = completion_matches (text, hostname_completion_function); + + /* And last, (but not least) if this word is in a command position, then + complete over possible command names, including aliases, functions, + and command names. */ + if (!matches && in_command_position) + { + matches = completion_matches (text, command_word_completion_function); + /* If we are attempting command completion and nothing matches, we + do not want readline to perform filename completion for us. We + still want to be able to complete partial pathnames, so set the + completion ignore function to something which will remove filenames + and leave directories in the match list. */ + if (!matches) + rl_ignore_some_completions_function = (Function *)bash_ignore_filenames; + } + + return (matches); +} + +/* This is the function to call when the word to complete is in a position + where a command word can be found. It grovels $PATH, looking for commands + that match. It also scans aliases, function names, and the shell_builtin + table. */ +static char * +command_word_completion_function (hint_text, state) + char *hint_text; + int state; +{ + static char *hint = (char *)NULL; + static char *path = (char *)NULL; + static char *val = (char *)NULL; + static char *filename_hint = (char *)NULL; + static int path_index, hint_len, istate; + static int mapping_over, local_index; + static SHELL_VAR **varlist = (SHELL_VAR **)NULL; +#if defined (ALIAS) + static ASSOC **alias_list = (ASSOC **)NULL; +#endif /* ALIAS */ + + /* We have to map over the possibilities for command words. If we have + no state, then make one just for that purpose. */ + + if (!state) + { + if (hint) + free (hint); + + mapping_over = 0; + val = (char *)NULL; + + /* If this is an absolute program name, do not check it against + aliases, reserved words, functions or builtins. We must check + whether or not it is unique, and, if so, whether that filename + is executable. */ + if (absolute_program (hint_text)) + { + /* Perform tilde expansion on what's passed, so we don't end up + passing filenames with tildes directly to stat(). */ + if (*hint_text == '~') + hint = tilde_expand (hint_text); + else + hint = savestring (hint_text); + hint_len = strlen (hint); + + if (filename_hint) + free (filename_hint); + filename_hint = savestring (hint); + + mapping_over = 4; + istate = 0; + goto inner; + } + + hint = savestring (hint_text); + hint_len = strlen (hint); + + path = get_string_value ("PATH"); + path_index = 0; + + /* Initialize the variables for each type of command word. */ + local_index = 0; + + if (varlist) + free (varlist); + + varlist = all_visible_functions (); + +#if defined (ALIAS) + if (alias_list) + free (alias_list); + + alias_list = all_aliases (); +#endif /* ALIAS */ + } + + /* mapping_over says what we are currently hacking. Note that every case + in this list must fall through when there are no more possibilities. */ + + switch (mapping_over) + { + case 0: /* Aliases come first. */ +#if defined (ALIAS) + while (alias_list && alias_list[local_index]) + { + register char *alias; + + alias = alias_list[local_index++]->name; + + if (STREQN (alias, hint, hint_len)) + return (savestring (alias)); + } +#endif /* ALIAS */ + local_index = 0; + mapping_over++; + + case 1: /* Then shell reserved words. */ + { + while (word_token_alist[local_index].word) + { + register char *reserved_word; + + reserved_word = word_token_alist[local_index++].word; + + if (STREQN (reserved_word, hint, hint_len)) + return (savestring (reserved_word)); + } + local_index = 0; + mapping_over++; + } + + case 2: /* Then function names. */ + while (varlist && varlist[local_index]) + { + register char *varname; + + varname = varlist[local_index++]->name; + + if (STREQN (varname, hint, hint_len)) + return (savestring (varname)); + } + local_index = 0; + mapping_over++; + + case 3: /* Then shell builtins. */ + for (; local_index < num_shell_builtins; local_index++) + { + /* Ignore it if it doesn't have a function pointer or if it + is not currently enabled. */ + if (!shell_builtins[local_index].function || + (shell_builtins[local_index].flags & BUILTIN_ENABLED) == 0) + continue; + + if (STREQN (shell_builtins[local_index].name, hint, hint_len)) + { + int i = local_index++; + + return (savestring (shell_builtins[i].name)); + } + } + local_index = 0; + mapping_over++; + } + + /* Repeatedly call filename_completion_func<tion while we have + members of PATH left. Question: should we stat each file? + Answer: we call executable_file () on each file. */ + outer: + + istate = (val != (char *)NULL); + + if (!istate) + { + char *current_path; + + /* Get the next directory from the path. If there is none, then we + are all done. */ + if (!path || !path[path_index] || + (current_path = extract_colon_unit (path, &path_index)) == 0) + return ((char *)NULL); + + if (*current_path == 0) + { + free (current_path); + current_path = savestring ("."); + } + + if (*current_path == '~') + { + char *t; + + t = tilde_expand (current_path); + free (current_path); + current_path = t; + } + + if (filename_hint) + free (filename_hint); + + filename_hint = xmalloc (2 + strlen (current_path) + hint_len); + sprintf (filename_hint, "%s/%s", current_path, hint); + + free (current_path); + } + + inner: + val = filename_completion_function (filename_hint, istate); + istate = 1; + + if (!val) + { + /* If the hint text is an absolute program, then don't bother + searching through PATH. */ + if (absolute_program (hint)) + return ((char *)NULL); + + goto outer; + } + else + { + int match; + char *temp; + + if (absolute_program (hint)) + { + match = strncmp (val, hint, hint_len) == 0; + /* If we performed tilde expansion, restore the original + filename. */ + if (*hint_text == '~') + { + int l, tl, vl; + vl = strlen (val); + tl = strlen (hint_text); + l = vl - hint_len; /* # of chars added */ + temp = xmalloc (l + 2 + tl); + strcpy (temp, hint_text); + strcpy (temp + tl, val + vl - l); + } + else + temp = savestring (val); + } + else + { + temp = strrchr (val, '/'); + + if (temp) + { + temp++; + match = strncmp (temp, hint, hint_len) == 0; + if (match) + temp = savestring (temp); + } + else + match = 0; + } + + /* If we have found a match, and it is an executable file, return it. */ + if (match && executable_file (val)) + { + free (val); + val = ""; /* So it won't be NULL. */ + return (temp); + } + else + { + free (val); + goto inner; + } + } +} + +static char * +command_subst_completion_function (text, state) + int state; + char *text; +{ + static char **matches = (char **)NULL; + static char *orig_start, *filename_text = (char *)NULL; + static int cmd_index, start_len; + + if (state == 0) + { + if (filename_text) + free (filename_text); + orig_start = text; + if (*text == '`') + text++; + else if (*text == '$' && text[1] == '(') + text += 2; + start_len = text - orig_start; + filename_text = savestring (text); + if (matches) + free (matches); + matches = completion_matches (filename_text, command_word_completion_function); + cmd_index = 0; + } + + if (!matches || !matches[cmd_index]) + { + rl_filename_quoting_desired = 0; /* disable quoting */ + return ((char *)NULL); + } + else + { + char *value; + + value = xmalloc (1 + start_len + strlen (matches[cmd_index])); + + if (start_len == 1) + value[0] = *orig_start; + else + strncpy (value, orig_start, start_len); + + strcpy (value + start_len, matches[cmd_index]); + + cmd_index++; + return (value); + } +} + +/* Okay, now we write the entry_function for variable completion. */ +static char * +variable_completion_function (text, state) + int state; + char *text; +{ + register SHELL_VAR *var = (SHELL_VAR *)NULL; + static SHELL_VAR **varlist = (SHELL_VAR **)NULL; + static int varlist_index; + static char *varname = (char *)NULL; + static int namelen; + static int first_char, first_char_loc; + + if (!state) + { + if (varname) + free (varname); + + first_char_loc = 0; + first_char = text[0]; + + if (first_char == '$') + first_char_loc++; + + varname = savestring (text + first_char_loc); + + namelen = strlen (varname); + if (varlist) + free (varlist); + varlist = all_visible_variables (); + varlist_index = 0; + } + + while (varlist && varlist[varlist_index]) + { + var = varlist[varlist_index]; + + /* Compare. You can't do better than Zayre. No text is also + a match. */ + if (!*varname || (strncmp (varname, var->name, namelen) == 0)) + break; + varlist_index++; + } + + if (!varlist || !varlist[varlist_index]) + { + return ((char *)NULL); + } + else + { + char *value = xmalloc (2 + strlen (var->name)); + + if (first_char_loc) + *value = first_char; + + strcpy (&value[first_char_loc], var->name); + + varlist_index++; + return (value); + } +} + +/* How about a completion function for hostnames? */ +static char * +hostname_completion_function (text, state) + int state; + char *text; +{ + static char **list = (char **)NULL; + static int list_index = 0; + static int first_char, first_char_loc; + + /* If we don't have any state, make some. */ + if (!state) + { + if (list) + free (list); + + list = (char **)NULL; + + first_char_loc = 0; + first_char = *text; + + if (first_char == '@') + first_char_loc++; + + list = hostnames_matching (&text[first_char_loc]); + list_index = 0; + } + + if (list && list[list_index]) + { + char *t = xmalloc (2 + strlen (list[list_index])); + + *t = first_char; + strcpy (t + first_char_loc, list[list_index]); + list_index++; + return (t); + } + else + return ((char *)NULL); +} + +/* History and alias expand the line. */ +static char * +history_expand_line_internal (line) + char *line; +{ + char *new_line; + + new_line = pre_process_line (line, 0, 0); + return new_line; +} + +#if defined (ALIAS) +/* Perform alias expansion on LINE and return the new line. */ +static char * +alias_expand_line_internal (line) + char *line; +{ + char *alias_line; + + alias_line = alias_expand (line); + return alias_line; +} +#endif + +/* There was an error in expansion. Let the preprocessor print + the error here. */ +static void +cleanup_expansion_error () +{ + char *to_free; + + fprintf (rl_outstream, "\r\n"); + to_free = pre_process_line (rl_line_buffer, 1, 0); + free (to_free); + putc ('\r', rl_outstream); + rl_forced_update_display (); +} + +/* If NEW_LINE differs from what is in the readline line buffer, add an + undo record to get from the readline line buffer contents to the new + line and make NEW_LINE the current readline line. */ +static void +maybe_make_readline_line (new_line) + char *new_line; +{ + if (strcmp (new_line, rl_line_buffer) != 0) + { + rl_point = rl_end; + + rl_add_undo (UNDO_BEGIN, 0, 0, 0); + rl_delete_text (0, rl_point); + rl_point = rl_end = 0; + rl_insert_text (new_line); + rl_add_undo (UNDO_END, 0, 0, 0); + } +} + +/* Make NEW_LINE be the current readline line. This frees NEW_LINE. */ +static void +set_up_new_line (new_line) + char *new_line; +{ + int old_point = rl_point; + int at_end = rl_point == rl_end; + + /* If the line was history and alias expanded, then make that + be one thing to undo. */ + maybe_make_readline_line (new_line); + free (new_line); + + /* Place rl_point where we think it should go. */ + if (at_end) + rl_point = rl_end; + else if (old_point < rl_end) + { + rl_point = old_point; + if (!whitespace (rl_line_buffer[rl_point])) + rl_forward_word (1); + } +} + +/* History expand the line. */ +static void +history_expand_line (ignore) + int ignore; +{ + char *new_line; + + new_line = history_expand_line_internal (rl_line_buffer); + + if (new_line) + set_up_new_line (new_line); + else + cleanup_expansion_error (); +} + +/* History and alias expand the line. */ +static void +history_and_alias_expand_line (ignore) + int ignore; +{ + char *new_line; + + new_line = pre_process_line (rl_line_buffer, 0, 0); + +#if defined (ALIAS) + if (new_line) + { + char *alias_line; + + alias_line = alias_expand (new_line); + free (new_line); + new_line = alias_line; + } +#endif /* ALIAS */ + + if (new_line) + set_up_new_line (new_line); + else + cleanup_expansion_error (); +} + +/* History and alias expand the line, then perform the shell word + expansions by calling expand_string. */ +static void +shell_expand_line (ignore) + int ignore; +{ + char *new_line; + + new_line = pre_process_line (rl_line_buffer, 0, 0); + +#if defined (ALIAS) + if (new_line) + { + char *alias_line; + + alias_line = alias_expand (new_line); + free (new_line); + new_line = alias_line; + } +#endif /* ALIAS */ + + if (new_line) + { + int old_point = rl_point; + int at_end = rl_point == rl_end; + + /* If the line was history and alias expanded, then make that + be one thing to undo. */ + maybe_make_readline_line (new_line); + free (new_line); + + /* If there is variable expansion to perform, do that as a separate + operation to be undone. */ + { + WORD_LIST *expanded_string; + + expanded_string = expand_string (rl_line_buffer, 0); + if (!expanded_string) + new_line = savestring (""); + else + { + new_line = string_list (expanded_string); + dispose_words (expanded_string); + } + + maybe_make_readline_line (new_line); + free (new_line); + + /* Place rl_point where we think it should go. */ + if (at_end) + rl_point = rl_end; + else if (old_point < rl_end) + { + rl_point = old_point; + if (!whitespace (rl_line_buffer[rl_point])) + rl_forward_word (1); + } + } + } + else + cleanup_expansion_error (); +} + +/* Filename completion ignore. Emulates the "fignore" facility of + tcsh. If FIGNORE is set, then don't match files with the + given suffixes. If only one of the possibilities has an acceptable + suffix, delete the others, else just return and let the completer + signal an error. It is called by the completer when real + completions are done on filenames by the completer's internal + function, not for completion lists (M-?) and not on "other" + completion types, such as hostnames or commands. + + It is passed a NULL-terminated array of (char *)'s that must be + free()'d if they are deleted. The first element (names[0]) is the + least-common-denominator string of the matching patterns (i.e. + u<TAB> produces names[0] = "und", names[1] = "under.c", names[2] = + "undun.c", name[3] = NULL). */ + +struct ign { + char *val; + int len; +}; + +static struct ign *ignores; /* Store the ignore strings here */ +static int num_ignores; /* How many are there? */ +static char *last_fignore; /* Last value of fignore - cached for speed */ + +static void +setup_ignore_patterns () +{ + int numitems, maxitems, ptr; + char *colon_bit; + struct ign *p; + + char *this_fignore = get_string_value ("FIGNORE"); + + /* If nothing has changed then just exit now. */ + if ((this_fignore && + last_fignore && + strcmp (this_fignore, last_fignore) == 0) || + (!this_fignore && !last_fignore)) + { + return; + } + + /* Oops. FIGNORE has changed. Re-parse it. */ + num_ignores = 0; + + if (ignores) + { + for (p = ignores; p->val; p++) free(p->val); + free (ignores); + ignores = (struct ign*)NULL; + } + + if (last_fignore) + { + free (last_fignore); + last_fignore = (char *)NULL; + } + + if (!this_fignore || !*this_fignore) + return; + + last_fignore = savestring (this_fignore); + + numitems = maxitems = ptr = 0; + + while (colon_bit = extract_colon_unit (this_fignore, &ptr)) + { + if (numitems + 1 > maxitems) + ignores = (struct ign *) + xrealloc (ignores, (maxitems += 10) * sizeof (struct ign)); + + ignores[numitems].val = colon_bit; + ignores[numitems].len = strlen (colon_bit); + numitems++; + } + ignores[numitems].val = NULL; + num_ignores = numitems; +} + +static int +name_is_acceptable (name) + char *name; +{ + struct ign *p; + int nlen = strlen (name); + + for (p = ignores; p->val; p++) + { + if (nlen > p->len && p->len > 0 && + strcmp (p->val, &name[nlen - p->len]) == 0) + return (0); + } + + return (1); +} + +/* Internal function to test whether filenames in NAMES should be + ignored. NAME_FUNC is a pointer to a function to call with each + name. It returns non-zero if the name is acceptable to the particular + ignore function which called _ignore_names; zero if the name should + be removed from NAMES. */ +static void +_ignore_names (names, name_func) + char **names; + Function *name_func; +{ + char **newnames; + int idx, nidx; + + /* If there is only one completion, see if it is acceptable. If it is + not, free it up. In any case, short-circuit and return. This is a + special case because names[0] is not the prefix of the list of names + if there is only one completion; it is the completion itself. */ + if (names[1] == (char *)0) + { + if ((*name_func) (names[0]) == 0) + { + free (names[0]); + names[0] = (char *)NULL; + } + return; + } + + /* Allocate space for array to hold list of pointers to matching + filenames. The pointers are copied back to NAMES when done. */ + for (nidx = 1; names[nidx]; nidx++) + ; + newnames = (char **)xmalloc ((nidx + 1) * (sizeof (char *))); + + newnames[0] = names[0]; + for (idx = nidx = 1; names[idx]; idx++) + { + if ((*name_func) (names[idx])) + newnames[nidx++] = names[idx]; + else + free (names[idx]); + } + + newnames[nidx] = (char *)NULL; + + /* If none are acceptable then let the completer handle it. */ + if (nidx == 1) + { + free (names[0]); + names[0] = (char *)NULL; + free (newnames); + return; + } + + /* If only one is acceptable, copy it to names[0] and return. */ + if (nidx == 2) + { + free (names[0]); + names[0] = newnames[1]; + names[1] = (char *)NULL; + free (newnames); + return; + } + + /* Copy the acceptable names back to NAMES, set the new array end, + and return. */ + for (nidx = 1; newnames[nidx]; nidx++) + names[nidx] = newnames[nidx]; + names[nidx] = (char *)NULL; +} + +static void +filename_completion_ignore (names) + char **names; +{ + setup_ignore_patterns (); + + if (num_ignores == 0) + return; + + _ignore_names (names, name_is_acceptable); +} + +/* Return 1 if NAME is a directory. */ +static int +test_for_directory (name) + char *name; +{ + struct stat finfo; + char *fn; + + fn = tilde_expand (name); + if (stat (fn, &finfo) != 0) + { + free (fn); + return 0; + } + free (fn); + return (S_ISDIR (finfo.st_mode)); +} + +/* Remove files from NAMES, leaving directories. */ +static void +bash_ignore_filenames (names) + char **names; +{ + _ignore_names (names, test_for_directory); +} + +/* Handle symbolic link references and other directory name + expansions while hacking completion. */ +static int +bash_directory_completion_hook (dirname) + char **dirname; +{ + char *local_dirname, *t; + int return_value = 0; + WORD_LIST *wl; + + local_dirname = *dirname; + if (strchr (local_dirname, '$') || strchr (local_dirname, '`')) + { + wl = expand_string (local_dirname, 0); + if (wl) + { + *dirname = string_list (wl); + /* Tell the completer to replace the directory name only if we + actually expanded something. */ + return_value = STREQ (local_dirname, *dirname) == 0; + free (local_dirname); + dispose_words (wl); + local_dirname = *dirname; + } + else + { + free (local_dirname); + *dirname = savestring (""); + return 1; + } + } + + if (!no_symbolic_links && (local_dirname[0] != '.' || local_dirname[1])) + { + char *temp1, *temp2; + int len1, len2; + + t = get_working_directory ("symlink-hook"); + temp1 = make_absolute (local_dirname, t); + free (t); + temp2 = canonicalize_pathname (temp1); + len1 = strlen (temp1); + if (temp1[len1 - 1] == '/') + { + len2 = strlen (temp2); + temp2 = xrealloc (temp2, len2 + 2); + temp2[len2] = '/'; + temp2[len2 + 1] = '\0'; + } + free (local_dirname); + *dirname = temp2; + free (temp1); + } + return (return_value); +} + +#if defined (DYNAMIC_HISTORY_COMPLETION) +static char **history_completion_array = (char **)NULL; +static int harry_size = 0; +static int harry_len = 0; + +static void +build_history_completion_array () +{ + register int i; + + /* First, clear out the current dynamic history completion list. */ + if (harry_size) + { + for (i = 0; history_completion_array[i]; i++) + free (history_completion_array[i]); + + free (history_completion_array); + + history_completion_array = (char **)NULL; + harry_size = 0; + harry_len = 0; + } + + /* Next, grovel each line of history, making each shell-sized token + a separate entry in the history_completion_array. */ + { + HIST_ENTRY **hlist; + + hlist = history_list (); + + if (hlist) + { + register int j; + + for (i = 0; hlist[i]; i++) + { + char **tokens; + + /* Separate each token, and place into an array. */ + tokens = history_tokenize (hlist[i]->line); + + for (j = 0; tokens && tokens[j]; j++) + { + if (harry_len + 2 > harry_size) + history_completion_array = (char **) xrealloc + (history_completion_array, + (harry_size += 10) * sizeof (char *)); + + history_completion_array[harry_len++] = tokens[j]; + history_completion_array[harry_len] = (char *)NULL; + } + free (tokens); + } + + /* Sort the complete list of tokens. */ + qsort (history_completion_array, harry_len, sizeof (char *), + (Function *)qsort_string_compare); + + /* Instead of removing the duplicate entries here, we let the + code in the completer handle it. */ + } + } +} + +static char * +history_completion_generator (hint_text, state) + char *hint_text; + int state; +{ + static int local_index = 0; + static char *text = (char *)NULL; + static int len = 0; + + /* If this is the first call to the generator, then initialize the + list of strings to complete over. */ + if (!state) + { + local_index = 0; + build_history_completion_array (); + text = hint_text; + len = strlen (text); + } + + while (history_completion_array && history_completion_array[local_index]) + { + if (strncmp (text, history_completion_array[local_index++], len) == 0) + return (savestring (history_completion_array[local_index - 1])); + } + return ((char *)NULL); +} + +static void +dynamic_complete_history (count, key) + int count, key; +{ + Function *orig_func; + CPPFunction *orig_attempt_func; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + rl_completion_entry_function = (Function *)history_completion_generator; + rl_attempted_completion_function = (CPPFunction *)NULL; + + if (rl_last_func == (Function *)dynamic_complete_history) + rl_complete_internal ('?'); + else + rl_complete_internal (TAB); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; +} + +#endif /* DYNAMIC_HISTORY_COMPLETION */ + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) +static void +bash_complete_username (ignore, ignore2) + int ignore, ignore2; +{ + bash_complete_username_internal (TAB); +} + +static void +bash_possible_username_completions (ignore, ignore2) + int ignore, ignore2; +{ + bash_complete_username_internal ('?'); +} + +static void +bash_complete_username_internal (what_to_do) + int what_to_do; +{ + bash_specific_completion + (what_to_do, (Function *)username_completion_function); +} + +static void +bash_complete_filename (ignore, ignore2) + int ignore, ignore2; +{ + bash_complete_filename_internal (TAB); +} + +static void +bash_possible_filename_completions (ignore, ignore2) + int ignore, ignore2; +{ + bash_complete_filename_internal ('?'); +} + +static void +bash_complete_filename_internal (what_to_do) + int what_to_do; +{ + Function *orig_func, *orig_dir_func; + CPPFunction *orig_attempt_func; + char *orig_rl_completer_word_break_characters; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + orig_dir_func = rl_directory_completion_hook; + orig_rl_completer_word_break_characters = rl_completer_word_break_characters; + rl_completion_entry_function = (Function *)filename_completion_function; + rl_attempted_completion_function = (CPPFunction *)NULL; + rl_directory_completion_hook = (Function *)NULL; + rl_completer_word_break_characters = " \t\n\"\'"; + + rl_complete_internal (what_to_do); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + rl_directory_completion_hook = orig_dir_func; + rl_completer_word_break_characters = orig_rl_completer_word_break_characters; +} + +static void +bash_complete_hostname (ignore, ignore2) + int ignore, ignore2; +{ + bash_complete_hostname_internal (TAB); +} + +static void +bash_possible_hostname_completions (ignore, ignore2) + int ignore, ignore2; +{ + bash_complete_hostname_internal ('?'); +} + +static void +bash_complete_variable (ignore, ignore2) + int ignore, ignore2; +{ + bash_complete_variable_internal (TAB); +} + +static void +bash_possible_variable_completions (ignore, ignore2) + int ignore, ignore2; +{ + bash_complete_variable_internal ('?'); +} + +static void +bash_complete_command (ignore, ignore2) + int ignore, ignore2; +{ + bash_complete_command_internal (TAB); +} + +static void +bash_possible_command_completions (ignore, ignore2) + int ignore, ignore2; +{ + bash_complete_command_internal ('?'); +} + +static void +bash_complete_hostname_internal (what_to_do) + int what_to_do; +{ + bash_specific_completion + (what_to_do, (Function *)hostname_completion_function); +} + +static void +bash_complete_variable_internal (what_to_do) + int what_to_do; +{ + bash_specific_completion + (what_to_do, (Function *)variable_completion_function); +} + +static void +bash_complete_command_internal (what_to_do) + int what_to_do; +{ + bash_specific_completion + (what_to_do, (Function *)command_word_completion_function); +} + +static void +bash_specific_completion (what_to_do, generator) + int what_to_do; + Function *generator; +{ + Function *orig_func; + CPPFunction *orig_attempt_func; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + rl_completion_entry_function = generator; + rl_attempted_completion_function = (CPPFunction *)NULL; + + rl_complete_internal (what_to_do); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; +} + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ |