|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T b
Length: 16966 (0x4246) Types: TextFile Names: »bashline.c«
└─⟦a05ed705a⟧ Bits:30007078 DKUUG GNU 2/12/89 └─⟦ca1f037a2⟧ »./bash-1.04.tar.Z« └─⟦46465a4db⟧ └─⟦this⟧ »bash-1.04/bashline.c«
/* bashline.c -- Bash's interface to the readline library. */ /* Copyright (C) 1989 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 <stdio.h> #include <readline/readline.h> #include "config.h" #include "general.h" #include "variables.h" #include "builtins.h" #include "quit.h" /* Called once from parse.y if we are going to use readline. */ initialize_readline () { char **attempt_shell_completion (), *bash_tilde_expand (); int shell_expand_line (); char *get_string_value (); rl_terminal_name = get_string_value ("TERM"); rl_instream = stdin, rl_outstream = stderr; rl_special_prefixes = "$@%"; /* Bind up our special shell functions. */ rl_add_defun ("shell-expand-line", shell_expand_line, META(CTRL('E'))); /* Tell the completer that we want a crack first. */ rl_attempted_completion_function = (Function *)attempt_shell_completion; /* Tell the tilde expander that we want a crack if it fails. */ rl_tilde_expander = (Function *)bash_tilde_expand; /* And don't forget to allow conditional parsing of the ~/.inputrc file. */ rl_readline_name = "Bash"; } /* Contains the line to push into readline. */ char *push_to_readline = (char *)NULL; /* Push the contents of push_to_readline into the readline buffer. */ bash_push_line () { if (push_to_readline) { rl_insert_text (push_to_readline); free (push_to_readline); push_to_readline = (char *)NULL; } } /* Call this to set the initial text for the next line to read from readline. */ bash_re_edit (line) char *line; { if (push_to_readline) free (push_to_readline); push_to_readline = savestring (line); rl_startup_hook = bash_push_line; } /* **************************************************************** */ /* */ /* Readline Stuff */ /* */ /* **************************************************************** */ /* If the user requests hostname completion, then simply build a list of hosts, and complete from that forever more. */ #ifndef 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. */ initialize_hostname_list () { char *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. */ add_host_name (name) char *name; { if (hostname_list_length + 2 > hostname_list_size) { if (!hostname_list) hostname_list = (char **)xmalloc (sizeof (char *)); 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. */ sort_hostname_list () { extern int qsort_string_compare (); if (hostname_list_needs_sorting && hostname_list) qsort (hostname_list, hostname_list_length, sizeof (char *), qsort_string_compare); hostname_list_needs_sorting = 0; } #define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c)) 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; /* 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. */ 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; 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); } /* 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. */ char ** attempt_shell_completion (text, start, end) char *text; int start, end; { int in_command_position = 0; char **matches = (char **)NULL; char *command_separator_chars = ";|&{("; /* Determine if this could be a command word. */ if (start == 0) in_command_position++; else { register int ti = start - 1; while (whitespace (rl_line_buffer[ti]) && ti > -1) ti--; if (ti < 0) in_command_position++; else { if (member (rl_line_buffer[ti], command_separator_chars) || member (*text, command_separator_chars)) in_command_position++; } } /* Variable name? */ if (*text == '$') { char *variable_completion_function (); 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 == '~' && !index (text, '/')) { char *username_completion_function (); 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 == '@') { char *hostname_completion_function (); 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) { char *command_word_completion_function (); int text_offset = 0; if (start && member (*text, command_separator_chars)) text_offset++, start++; if (*text != '/') matches = completion_matches (&text[text_offset], command_word_completion_function); } return (matches); } /* This is the function to call when the word to complete is at the start of a line. It grovels $PATH, looking for commands that match. It also scans for aliases, function names, and the shell_builtin table. */ 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 int path_index, hint_len, istate; static char *filename_hint = (char *)NULL; static int mapping_over = 0; static int local_index; static SHELL_VAR *varlist; char *filename_completion_function (); char *extract_colon_unit (); /* 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); path = get_string_value ("PATH"); path_index = 0; hint = savestring (hint_text); hint_len = strlen (hint); mapping_over = 0; val = (char *)NULL; /* Initialize the variables for each type of command word. */ local_index = 0; varlist = variable_list; } /* 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. */ #ifdef ALIAS while (aliases && aliases[local_index]) { local_index++; if (strncmp (aliases[local_index - 1]->name, hint, hint_len) == 0) return (savestring (aliases[local_index - 1]->name)); } local_index = 0; #endif /* ALIAS */ mapping_over++; case 1: /* Then function names. */ while (varlist) { if (function_p (varlist) && !invisible_p (varlist) && strncmp (varlist->name, hint, hint_len) == 0) { char *temp = savestring (varlist->name); varlist = varlist->next; return (temp); } else varlist = varlist->next; } mapping_over++; case 2: /* Then shell builtins. */ while (shell_builtins[local_index].function) { local_index++; if (strncmp (shell_builtins[local_index - 1].name, hint, hint_len) == 0) return (savestring (shell_builtins[local_index - 1].name)); } mapping_over++; local_index = 0; } /* Repeatadly call filename_completion_function 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))) return ((char *)NULL); if (!*current_path) { free (current_path); current_path = savestring ("."); } if (filename_hint) free (filename_hint); filename_hint = (char *)xmalloc (2 + strlen (current_path) + strlen (hint)); sprintf (filename_hint, "%s/%s", current_path, hint); free (current_path); } inner: val = filename_completion_function (filename_hint, istate); istate = 1; if (!val) { goto outer; } else { char *rindex (), *temp = rindex (val, '/'); temp++; if ((strncmp (hint, temp, hint_len) == 0) && executable_file (val)) { temp = (savestring (temp)); free (val); val = ""; /* So it won't be NULL */ return (temp); } else { free (val); goto inner; } } } /* Okay, now we write the entry_function for variable completion. */ char * variable_completion_function (text, state) int state; char *text; { static char *varname = (char *)NULL; static SHELL_VAR *list; static int namelen; if (!state) { if (varname) free (varname); varname = savestring (&text[1]); namelen = strlen (varname); list = variable_list; } while (list) { /* Compare. You can't do better than Zayre. No text is also a match. The name cannot be a function. */ if (!function_p (list) && !invisible_p (list) && (!*varname || (strncmp (varname, list->name, namelen) == 0))) break; list = list->next; } if (!list) { /* Then we are done. */ return ((char *)NULL); } else { char *value = (char *)xmalloc (2 + strlen (list->name)); *value = '$'; strcpy (&value[1], list->name); list = list->next; return (value); } } /* How about a completion function for hostnames? */ char * hostname_completion_function (text, state) int state; char *text; { static char **list = (char **)NULL; static int list_index = 0; /* If we don't have any state, make some. */ if (!state) { extern char **hostnames_matching (); if (list) free (list); list = (char **)NULL; list = hostnames_matching (*text ? &text[1] : &text[0]); list_index = 0; } if (list && list[list_index]) { char *t = (char *)xmalloc (2 + strlen (list[list_index])); *t = *text; strcpy (t + 1, list[list_index]); list_index++; return (t); } else return ((char *)NULL); } /* History and alias expand the line. But maybe do more? This is a test to see what users like. Do expand_string on the string. */ shell_expand_line (ignore) int ignore; { char *pre_process_line (), *new_line; new_line = pre_process_line (rl_line_buffer, 0, 0); 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. */ if (strcmp (new_line, rl_line_buffer) != 0) { rl_point = rl_end; rl_add_undo (UNDO_BEGIN, 0, 0, 0); rl_kill_text (0, rl_point); rl_point = rl_end = 0; rl_insert_text (new_line); rl_add_undo (UNDO_END, 0, 0, 0); } free (new_line); /* If there is variable expansion to perform, do that as a separate operation to be undone. */ { char *expand_string (), *expanded_string; char *string_list (); 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); } if (strcmp (new_line, rl_line_buffer) != 0) { rl_add_undo (UNDO_BEGIN, 0, 0 ,0); rl_kill_text (0, rl_end); rl_point = rl_end = 0; rl_insert_text (new_line); rl_add_undo (UNDO_END, 0, 0, 0); } 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 { /* There was an error in expansion. Let the preprocessor print the error here. Note that we know that pre_process_line () will return NULL, since it just did. */ fprintf (rl_outstream, "\n\r"); pre_process_line (rl_line_buffer, 1, 0); rl_forced_update_display (); } } /* If tilde_expand hasn't been able to expand the text, perhaps it is a special shell expansion. We handle that here. */ char * bash_tilde_expand (text) char *text; { char *result = (char *)NULL; if (strcmp (text, "-") == 0) result = get_string_value ("OLDPWD"); else if (strcmp (text, "+") == 0) result = get_string_value ("PWD"); if (result) result = savestring (result); return (result); } /* Shovel stuff right through to readline's parser. */