DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T b

⟦74de7fc07⟧ TextFile

    Length: 16966 (0x4246)
    Types: TextFile
    Names: »bashline.c«

Derivation

└─⟦a05ed705a⟧ Bits:30007078 DKUUG GNU 2/12/89
    └─⟦ca1f037a2⟧ »./bash-1.04.tar.Z« 
        └─⟦46465a4db⟧ 
            └─⟦this⟧ »bash-1.04/bashline.c« 

TextFile

/* 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. */