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 i

⟦03a51429e⟧ TextFile

    Length: 85962 (0x14fca)
    Types: TextFile
    Names: »info.c«

Derivation

└─⟦a05ed705a⟧ Bits:30007078 DKUUG GNU 2/12/89
    └─⟦92d325a64⟧ »./info.tar.Z« 
        └─⟦4a2cb6db9⟧ 
            └─⟦this⟧ »info.c« 

TextFile

/* Info.c -- a stand-alone Info program which does what Info in GNU
   Emacs does.

   Copyright (C) 1987 Free Software Foundation, Inc.

   This file is part of GNU Info.

   GNU Info is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.  No author or distributor accepts
   responsibility to anyone for the consequences of using it or for
   whether it serves any particular purpose or works at all, unless he
   says so in writing.  Refer to the GNU Emacs General Public License
   for full details.

   Everyone is granted permission to copy, modify and redistribute
   GNU Info, but only under the conditions described in the GNU Emacs
   General Public License.   A copy of this license is supposed to
   have been given to you along with GNU Emacs so you can know your
   rights and responsibilities.  It should be in a file named COPYING.
   Among other things, the copyright notice and this notice must be
   preserved on all copies.  */

/* Modification history:

   (1) Virgin release 1.0 on (Friday, October 9th: 103pm)
       implementation by Brian Fox.

   (2) Made the completer not use static size variables.
       Also made the completer more generic.  Maybe this will be of
       some use someday.
       Changed file_error to incorporate code suggestions from
       Roland McGrath.
       Changed idiot error messages.  We still need some canonical
       location for the initial Info directory path.

   Thu Feb 25 10:22am

      Made info_directory_paths try to get its value from the environment
      variable INFO_DIRECTORY_PATHS.  Added cons_string () in an attempt to
      get rid of the brain-damage (read brian-damage) I originally
      wrote in here.  Death to static limits!

   Wed Jul 6
      Indirect tags are not assumed to point close to the node.  Instead,
      they point to where the node would have been, had the the file never
      been split.  Thus, the header of a split file is compensated for, by
      dynamically figuring out how long it is every time we read in a file.
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <sgtty.h>
#include <sys/file.h>
#include <signal.h>
#include <pwd.h>

/* #include "info.h" */
/* File: info.h */

/* A bunch of stuff that has to go somewhere, and this seemed like
   a good place. */

typedef int boolean;
#define true 1
#define false 0
#define UNIX

typedef struct nodeinfo {
  char *filename;
  char *nodename;
  int   pagetop;
  int   nodetop;
  struct nodeinfo *next;
} NODEINFO;

typedef struct indirectinfo {
  char *filename;
  int first_byte;
} INDIRECT_INFO;

typedef int FUNCTION();

#ifndef NULL
#define NULL char *0x0
#endif

#define PROJECT_NAME "GNU Info"

#define barf(msg) fprintf(stderr, "%s\n", msg)

/* Some character stuff. */
#define control_character_threshold 0x020   /* smaller than this is control */
#define meta_character_threshold 0x07f	    /* larger than this is Meta. */
#define control_character_bit 0x40	    /* 0x000000, must be off. */
#define meta_character_bit 0x080	    /* x0000000, must be on. */

#define info_separator_char '\037'
#define start_of_node_string "\037"

#ifdef CTRL
#undef CTRL
#endif

#define CTRL(c) ((c) & (~control_character_bit))
#define META(c) ((c) | meta_character_bit)

#define UNMETA(c) ((c) & (~meta_character_bit))
#define UNCTRL(c) to_upper(((c)|control_character_bit))

#ifndef to_upper
#define to_upper(c) (((c) < 'a' || (c) > 'z') ? c : c-32)
#define to_lower(c) (((c) < 'A' || (c) > 'Z') ? c : c+32)
#endif

#define CTRL_P(c) ((c) < control_character_threshold)
#define META_P(c) ((c) > meta_character_threshold)

#define NEWLINE '\n'
#define RETURN CTRL('M')
#define DELETE 0x07f
#define TAB '\t'
#define ABORT_CHAR CTRL('G')
#define PAGE CTRL('L')
#define SPACE 0x020
#define ESC CTRL('[')
#define control_display_prefix '^'

#define TAG_TABLE_END_STRING "\037\nEND TAG TABLE"
#define TAG_TABLE_BEG_STRING "\nTAG TABLE:\n"
#define NODE_ID "Node:"
#define FILENAME_LEN 256
#define NODENAME_LEN 256
#define STRING_SIZE 256
#define nodeend_sequence "\n\037"

/* All right, some windows stuff. */

typedef struct {
  int left, top, right, bottom;
  int ch, cv;
} WINDOW;

typedef struct _wind_list {
  int left, top, right, bottom;
  int ch, cv;
  struct _wind_list *next_window;
} WINDOW_LIST;

/* Stuff that everyone has to know about. */
extern WINDOW the_window;
extern WINDOW echo_area;
extern int pagetop, nodetop, nodebot, nodelines;
extern char current_info_file[], current_info_node[], last_loaded_info_file[];
extern char *info_file, *xmalloc ();

#define MAX_INDIRECT_FILES 100	/* a crock, this should be done in a different way. */
NODEINFO *Info_History = NULL;	/* the info history list. */
INDIRECT_INFO indirect_list[MAX_INDIRECT_FILES]; /* ?Can't have more than xx files in the indirect list? */

char current_info_file[FILENAME_LEN];	/* the filename of the currently loaded info file. */
char current_info_node[NODENAME_LEN];	/* the nodename of the node the user is looking at. */
char last_loaded_info_file[FILENAME_LEN]; /* the last file actually loaded.  Not the same as current info file. */
int nodetop, nodebot;			/* offsets in info_file of top and bottom of current_info_node. */
int nodelines;			/* number of lines in this node. */

char *info_file = NULL;		/* buffer for the info file. */
int info_buffer_len;		/* length of the above buffer. */

char *tag_table = NULL;		/* Pointer to the start of a tag table, or NULL to show none. */
int tag_buffer_len;		/* length of the above buffer. */

boolean indirect = false;	/* If true, the tag table is indirect. */
int indirect_top;
int pagetop;			/* offset in the buffer of the current pagetop. */


/* If non-NULL, this is a colon separated list of directories to search
   for a specific info file.  The user places this variable into his or
   her environment. */
char *info_directory_paths = (char *)NULL;

char dumpfile[FILENAME_LEN];	/* If filled, the name of a file to write to. */

/* **************************************************************** */
/*								    */
/*			Getting Started.			    */
/*								    */
/* **************************************************************** */

/* Begin the Info session. */

/* Global is on until we are out of trouble. */
int totally_inhibit_errors = 1;

main (argc, argv)
     int argc;
     char **argv;
{
  int arg_index = 1;
  char filename[FILENAME_LEN];
  char nodename[NODENAME_LEN];
  char *cons_string ();
  
  char *getenv ();
  char *env_info_dirs = getenv ("INFO_DIRECTORY_PATHS");

  if (env_info_dirs) {
    if (*env_info_dirs)
      info_directory_paths = cons_string (env_info_dirs);
  }

  if (!info_directory_paths)
    info_directory_paths = 
      cons_string ("/usr/local/lib/emacs/info:/usr/lib/emacs/info:.");
  
  /* Parse argument flags from the input line. */
  while (arg_index != argc && *(argv[arg_index]) == '-') {
    
    /* There are flag arguments, so parse them */
    char arg_character;
    int  i = 1;
    int next_arg = 0;

    while ((!next_arg) && (arg_character = (argv[arg_index])[i])) {
      i++;

      switch (to_lower(arg_character)) {

      case 'd':
	/* Eat the next argument into INFO_DIRECTORY_PATH. */
	free (info_directory_paths);
	if (++arg_index == argc) {
	  fprintf (stdout, "info: Missing argument.\n");
	  exit (1);
	}
	info_directory_paths = (char *)cons_string (argv[arg_index]);
	next_arg++;
	break;

      case 'n':
	/* Eat the next argument into NODENAME. */
	eat_argument (++arg_index, argc, argv, nodename);
	next_arg++;
	break;

      case 'f':
	/* Eat the next argument into FILENAME. */
	eat_argument (++arg_index, argc, argv, filename);
	next_arg++;
	break;

      case 'o':
	/* Eat the next argument into DUMPNAME. */
	eat_argument (++arg_index, argc, argv, dumpfile);
	next_arg++;
	break;

      default:
	explain ();		/* you lose if you type a bad flag */
      }
    }
    arg_index++;
  }

  /* Okay, flags are parsed.  Get possible Info menuname. */

  /* Start with DIR or whatever was specified. */
  if (!get_node (filename, nodename, false))
    if (!get_node (NULL, NULL, true)) {
      fprintf (stderr, "%s: Cannot find \"%s\", or \"%s\".\n", argv[0],
	       filename, info_directory_paths);
      exit (1);
    }
  totally_inhibit_errors = 0;
  get_node (filename, nodename, false);

  if (arg_index != argc) {
    printf (stdout, "\n");

    while (arg_index != argc) {
      boolean build_menu (), find_menu_node ();
      
      if (build_menu () &&
	  find_menu_node (argv[arg_index], nodename) &&
	  get_node (NULL, nodename)) {

/* RMS says not to type this stuff out because he expects programs
   to call Info instead of interactive users.
        printf ("%s.. ",argv[arg_index]);
	fflush (stdout);
*/
	arg_index++;
      } else break;
    }
  }
  begin_info_session ();
}

eat_argument (index, limit, array, string)
     int index, limit;
     char **array, *string;
{
  /* Eat argument from ARRAY[INDEX] into STRING.  If INDEX
     equals LIMIT barf. */

  if (index == limit) {
    fprintf (stdout, "info: Missing argument.\n");
    exit (1);
  }

  strncpy (string, array[index], FILENAME_LEN);
}

explain () {			/* tell them how to use it... */
  fprintf (stderr, "usage: info [-d info-dir-path] [-n nodename] [-o output_file] [info-file ...]\n");
  exit (1);
}

FUNCTION *old_stop;		/* last value of SIGSTOP. */
FUNCTION *old_winch;		/* last value of SIGWINCH. */
FUNCTION *old_cont;		/* last value of SIGCONT. */

begin_info_session ()
{
  /* Start using Info. */
  int info_signal_handler ();

  /* If the user just wants to dump the node, then do that. */
  if (dumpfile[0]) {
    dump_current_node (dumpfile);
    exit (0);
  }

  init_terminal_io ();

  /* Install handlers for restoring/breaking the screen. */

  install_signals ();
  new_echo_area ();

  print_string ("Welcome to Info!  Type \"?\" for help. ");
  close_echo_area ();
  toploop ();
  restore_io ();
}

info_signal_handler (sig, arg1, arg2)
     int sig;
{
  /* Do the right thing with this signal. */

  switch (sig) {
    
  case SIGTSTP:
    restore_io ();
    signal (SIGTSTP, old_stop);
    kill (getpid (), SIGSTOP);	/* Some weenie says this is the right thing. */
    break;

  case SIGCONT:
    /* Try to win some more.  Reset IO state, and stuff
       like that. */

    clear_screen ();
    display_page ();
    goto_xy (the_window.ch, the_window.cv);
    opsys_init_terminal ();
    signal (SIGTSTP, info_signal_handler);
    break;

  case SIGWINCH:
    /* Try to win.  If the window is changed, then get the difference */
    /* between the new and old windows. */
    {
      extern char *widest_line;
      extern WINDOW terminal_window;
      extern WINDOW_LIST *window_stack;
      extern display_page ();
      extern int terminal_lines, terminal_rows;
      extern int get_term_width (), get_term_height ();

      int right, bottom;
      int delta_width, delta_height;

      get_terminal_info ();
      right = get_term_width ();
      bottom = get_term_height ();

      if (terminal_rows > right) 
	delta_width = (0 - (terminal_rows - right));
      else delta_width = right - terminal_rows;

      if (terminal_lines > bottom)
	delta_height = (0 - (terminal_lines - bottom));
      else delta_height = (bottom - terminal_lines);

      push_window ();
      free (widest_line);
      widest_line = (char *)xmalloc (right);
      set_window (&terminal_window);
      push_window ();

      /* Make the new window.  Map over all windows in window list. */
      {
	WINDOW_LIST *wind = window_stack;
	extern WINDOW modeline_window;
	
	while (wind != (WINDOW_LIST *)NULL) {
	  adjust_wind (wind, delta_width, delta_height);
	  wind = wind->next_window;
	}

	/* Adjust the other windows that we know about. */

	adjust_wind (&terminal_window, delta_width, delta_height);
	adjust_wind (&echo_area, delta_width, delta_height);
	adjust_wind (&modeline_window, delta_width, delta_height);
      }
      pop_window ();
      clear_screen ();
      with_output_to_window (&terminal_window, display_page);
      pop_window ();
    }
    break;

  case SIGINT:
    restore_io ();
    exit (1);
    break;
  }
}

install_signals ()
{
  old_stop = signal (SIGTSTP, info_signal_handler);
  old_winch = signal (SIGWINCH, info_signal_handler);
  old_cont = signal (SIGCONT, info_signal_handler);
  signal (SIGINT, info_signal_handler);
}

adjust_wind (wind, delta_width, delta_height)
     WINDOW *wind;
     int delta_width, delta_height;
{
  wind->right += delta_width;
  wind->bottom += delta_height;
  if (wind->top) wind->top += delta_height;
  if (wind->left) wind->left += delta_width;
}


/* **************************************************************** */
/*								    */
/*			Completing Things			    */
/*								    */
/* **************************************************************** */
typedef struct completion_entry {
  char *identifier;
  char *data;
  struct completion_entry *next;
} COMP_ENTRY;

/* The linked list of COMP_ENTRY structures that you create. */
COMP_ENTRY *completion_list = (COMP_ENTRY *)NULL;

/* The vector of COMP_ENTRY pointers that COMPLETE returns. */
COMP_ENTRY **completions = NULL;

/* The number of elements in the above vector. */
int completion_count;

/* Initial size of COMPLETIONS array. */
#define INITIAL_COMPLETIONS_CORE_SIZE 200

/* Current size of the completion array in core. */
int completions_core_size = 0;

/* Ease the typing task.  Another name for the I'th IDENTIFIER of COMPLETIONS. */
#define completion_id(i) ((completions[(i)])->identifier)

/* The number of completions that can be present before the help
   function starts asking you about whether it should print them
   all or not. */
int completion_query_threshold = 100;

free_completion_list ()
{
  COMP_ENTRY *temp;
  while (completion_list) {
    temp = completion_list;
    if (completion_list->identifier) free (completion_list->identifier);
    if (completion_list->data) free (completion_list->data);
    completion_list = completion_list->next;
    free (temp);
  }
}

/* Add a single completion to COMPLETION_LIST.
   IDENTIFIER is the string that the user should type.
   DATA should just be a pointer to some random data that you wish to
   have associated with the identifier, but I'm too stupid for that, so
   it must be a string as well.  This allocates the space for the strings
   so you don't necessarily have to. */
add_completion (identifier, data)
     char *identifier, *data;
{
  COMP_ENTRY *temp = (COMP_ENTRY *)xmalloc (sizeof (COMP_ENTRY));
  temp->identifier = (char *)xmalloc (strlen (identifier) + 1);
  strcpy (temp->identifier, identifier);
  temp->data = (char *)xmalloc (strlen (data) + 1);
  strcpy (temp->data, data);
  temp->next = completion_list;
  completion_list = temp;
}

/* Function for reading a line.  Supports completion on COMPLETION_LIST
   if you pass COMPLETING as true.  Prompt is either a prompt or NULL,
   LINE is the place to store the characters that are read.  LINE may start
   out already containing some characters; if so, they are printed.  MAXCHARS
   tells how many characters can fit in the buffer at LINE.  READLINE returns
   FALSE if the user types the abort character.  LINE is returned with a '\0'
   at the end, not a '\n'.  */
boolean
readline (prompt, line, maxchars, completing)
     char *prompt, *line;
     int maxchars;
     boolean completing;
{
  int character;
  int readline_ch, readline_cv;
  int current_len = strlen(line);
  int meta_flag = 0;

  new_echo_area ();
  if (prompt) print_string (prompt);
  readline_ch = the_window.ch; readline_cv = the_window.cv;

  print_string (line);

  while (true) {

    line[current_len] = '\0';
    goto_xy(readline_ch, readline_cv);
    print_string(line);
    clear_eol();

    character = blink_cursor ();
    if (meta_flag) {
      character = META(character);
      meta_flag = 0;
    }

    if (META_P(character)) character = META(to_upper(UNMETA(character)));

    switch (character) {

    case EOF:
      character = '\n';

    case ESC:
      meta_flag++;
      break;

    case META(DELETE):
    case CTRL('W'):
      while (current_len && line[current_len] == SPACE)
	current_len--;
      if (!current_len) break;
      while (current_len && line[current_len] != SPACE)
	current_len--;
      break;
      
    case CTRL('U'):
      current_len = 0;
      break;

    case '\b':
    case 0x07f:
      if (current_len) current_len--;
      else ding();
      break;

    case '\n':
    case '\r':
      if (completing) {
	extern int completion_count;
	try_complete (line);
	if (completion_count >= 1) {
	  close_echo_area ();
	  return (true);
	} else {
	  current_len = strlen (line);
	  break;
	}	
      } else {
	close_echo_area ();
	return (true);
      }

    case ABORT_CHAR:
      ding ();
      if (current_len) {
	current_len = 0;
      } else {
	close_echo_area ();
	clear_echo_area ();
	return(false);
      }
      break;

    case ' ':
    case '\t':
    case '?':
      if (completing) {
	extern int completion_count;
	if (character == '?') {
	  help_possible_completions (line);
	  break;
	} else {
	  char temp_line[NODENAME_LEN];
	  strcpy (temp_line, line);
	  try_complete (line);
	  if (completion_count != 1 && character == SPACE) {
	    if (strcmp (temp_line, line) == 0) {
	      line[current_len] = SPACE;
	      line[current_len+1] = '\0';
	      strcpy (temp_line, line);
	      try_complete (line);
	      if (completion_count == 0) {
		line[current_len] = '\0';
		ding ();
	      }
	    }
	  }
	  current_len = strlen (line);
	  if (completion_count == 0) ding ();
	  break;
	}
      }
      /* Do *NOT* put anything in-between the completing cases and 
	 the default: case.  No. */
    default:
      if (!CTRL_P(character) && !META_P(character) &&
	  current_len < maxchars) {
	line[current_len++] = character;
      } else ding ();
    }
  }
}

init_completer ()
{
  /* Initialize whatever the completer is using. */
  if (completions_core_size != INITIAL_COMPLETIONS_CORE_SIZE) {
    if (completions) free (completions);
    completions = (COMP_ENTRY **)xmalloc ((sizeof (COMP_ENTRY *)) *
					  (completions_core_size = INITIAL_COMPLETIONS_CORE_SIZE));
  }
  completion_count = 0;
}

/* Reverse the completion list passed in LIST, and
   return a pointer to the new head. */
COMP_ENTRY *
reverse_list (list)
     COMP_ENTRY *list;
{
  COMP_ENTRY *next;
  COMP_ENTRY *prev = (COMP_ENTRY *)NULL;

  while (list) {
    next = list->next;
    list->next = prev;
    prev = list;
    list = next;
  }
  return (prev);
}

remember_completion (pointer)
     COMP_ENTRY *pointer;
{
  if (completion_count == completions_core_size) {
    COMP_ENTRY **temp = (COMP_ENTRY**)realloc (completions,
      ((sizeof (COMP_ENTRY *)) * (completions_core_size += INITIAL_COMPLETIONS_CORE_SIZE)));
    if (!temp) {
      display_error ("Too many completions (~d)!  Out of core!", completion_count);
      return;
    } else completions = temp;
  }
  completions[completion_count++] = pointer;
}

boolean
complete (text, list)
     char *text;
     COMP_ENTRY *list;
{
  /* Complete TEXT from identifiers in LIST.  Place the resultant
     completions in COMPLETIONS, and the number of completions in
     COMPLETION_COUNT. Modify TEXT to contain the least common
     denominator of all the completions found. */

  int low_match, i, index;
  int string_length = strlen (text);

  init_completer ();
  low_match = 1000;		/* some large number. */

  while (list) {
    if (strnicmp (text, list->identifier, string_length) == 0)
      remember_completion (list);
    list = list->next;
  }

  if (completion_count == 0) return (false);

  if (completion_count == 1) {	/* One completion */
    strcpy (text, completion_id(0));
    return (true);
  }

  /* Else find the least common denominator */

  index = 1;

  while (index < completion_count) {
    int c1, c2;
    for (i=0;
	 (c1 = to_lower(completion_id(index-1)[i])) &&
	 (c2 = to_lower(completion_id(index)[i]));
	 i++)
      if (c1 != c2) break;

    if (low_match > i ) low_match = i;
    index++;
  }

  strncpy (text, completion_id(0), low_match);
  text[low_match] = '\0';
  return (true);
}

/* Complete TEXT from the completion structures in COMPLETION_LIST. */
boolean
try_complete (text)
     char *text;
{
  return (complete (text, completion_list));
}

help_possible_completions (text)
     char *text;
{
  /* The function that prints out the possible completions. */

  char temp_string[2000];

  goto_xy (the_window.left, the_window.top);
  strcpy (temp_string, text);
  try_complete (temp_string);
  
  open_typeout ();

  if (completion_count == 0) {
    print_string ("There are no possible completions.\n");
    goto print_done;
  }

  if (completion_count == 1) {
    print_string ("The only possible completion of what you have typed is:\n\n");
    print_string (completions[0]);
    goto print_done;
  }

  if (completion_count >= completion_query_threshold) {
    int character;
    print_string ("\nThere are %d completions.  Do you really want to see them all", completion_count);
    if (!get_y_or_n_p ()) return (' ');
  }

  print_string ("\nThe %d completions of what you have typed are:\n\n", completion_count);

  {
    int character;
    int index = 0;
    int counter = 0;
    int columns = (the_window.right - the_window.left) / 30;

    while (index < completion_count) {
      if (counter == columns) {
	charout ('\n');
	counter = 0;
      }
      indent_to (counter * 30);
      print_string (completion_id(index));
      counter++;
      index++;
    }
  }

print_done:
  print_string ("\n\nDone.\n-----------------\n");
  close_typeout ();
}

/* **************************************************************** */
/*								    */
/*			Getting Nodes				    */
/*								    */
/* **************************************************************** */

/* A node name looks like:
   Node: nodename with spaces but not a comma,
or Node: (filename-containing-node)node-within-file
or Node: (filename)

   The latter case implies a nodename of "Top".  All files are
   supposed to have one.

   Lastly, the nodename specified could be "*", which specifies the
   entire file. */

boolean
get_info_file (filename, remember_name)	   /* load the specified info file. */
     char *filename;
     boolean remember_name;
{
  /* Load FILENAME.  If REMEMBER_NAME is true, then remember the loaded filename
     in CURRENT_INFO_FILE.  In either case, remember the name of this
     file in LAST_LOADED_INFO_FILE. */

  FILE *input_stream;
  struct stat file_info;
  int pointer, result;
  char tempname[FILENAME_LEN];
  char *opsys_filename ();

  /* Get real filename. */
  strcpy (tempname, opsys_filename (filename));

  /* See if the file exists. */
  if ((result = stat (tempname, &file_info)) != 0) {
    /* Try again, this time with the name in lower-case. */
    char temp_tempname[FILENAME_LEN];
    int i;

    for (i=0; temp_tempname[i] = to_lower(tempname[i]); i++);
    result = stat (temp_tempname, &file_info);
    if (!result) strcpy(tempname, temp_tempname);
  }

  /* See if this file is the last loaded one. */
  if (!result && (strcmp (last_loaded_info_file, tempname) == 0)) {
    return (true);			/* Okay, the file is loaded. */
  }

  /* Now try to open the file. */
  if (result || (input_stream = fopen (tempname, "r")) == NULL) {
    return (file_error (tempname));
  }
    
  /* If we already have a file loaded, then free it first. */
  if (info_file) {
    free (info_file);

    if (!indirect) {
      /* Then the tag table is also no longer valid. */
      tag_table = (char *)NULL;
    }
  }

  /* Read the contents of the file into a new buffer. */

  info_file = (char *)xmalloc (info_buffer_len = file_info.st_size);
  fread (info_file, 1, info_buffer_len, input_stream);
  fclose (input_stream);
  strcpy (last_loaded_info_file, tempname);
  if (remember_name) {
    strcpy (current_info_file, tempname);
    if (indirect) {
      int index;
      indirect = false;
      free (tag_table);
      for (index=0; index < MAX_INDIRECT_FILES &&
	   indirect_list[index].filename != (char *)NULL;
	   index++) {
	free (indirect_list[index].filename);
	indirect_list[index].filename = (char *)NULL;
      }
    }
  } else return (true);

  /* The file has been read, and we don't know anything about it.
     Find out if it contains a tag table. */

  tag_table = NULL;			/* assume none. */
  indirect = false;
  tag_buffer_len = 0;

  set_search_constraints (info_file, info_buffer_len);

  /* Go to the last few lines in the file. */
  pointer = back_lines (8, info_buffer_len);
  pointer = search_forward (TAG_TABLE_END_STRING, pointer);

  if (pointer > -1) {
    /* Then there is a tag table.  Find the start of it, and remember that. */
    pointer = search_backward (TAG_TABLE_BEG_STRING, pointer);
      
    /* Handle error for malformed info file. */
    if (pointer < 0) display_error ("Start of tag table not found!");
    else {
      /* No problem.  If this file is an indirect file, then the contents
	 of the tag table must remain in RAM the entire time.  Otherwise,
	 we can flush the tag table with the file when the file is flushed.
	 So, if indirect, remember that, and copy the table to another place.*/

      int indirect_check = forward_lines (2, pointer);

      tag_table = info_file+pointer;
      tag_buffer_len = info_buffer_len-pointer;

      /* Shorten the search constraints. */
      info_buffer_len = pointer;

      if (looking_at ("(Indirect)\n", indirect_check)) {

	/* We have to find the start of the indirect files information. */
	tag_table = (char *)xmalloc (tag_buffer_len);

	bcopy (&info_file[indirect_check], tag_table, tag_buffer_len);

	/* Find the list of filenames. */
	indirect_top = search_backward ("Indirect:\n", indirect_check);
	if (indirect_top < 0) {
	  free (tag_table);
	  tag_table = (char *)NULL;	  
	  return (display_error ("Start of INDIRECT tag table not found!"));
	}
	  
	/* Remember the filenames, and their byte offsets. */
	{
	  /* Index into the filename/offsets array. */
	  int index = 0;
	  char temp_filename[FILENAME_LEN];
	  int temp_first_byte;

	  info_buffer_len = indirect_top;

	  /* For each line, scan the info into globals.  Then save
	     the information in the INDIRECT_INFO structure. */

	  while (info_file[indirect_top] != info_separator_char &&
		 index < MAX_INDIRECT_FILES) {
	    indirect_top = forward_lines (1, indirect_top);
	    if (info_file[indirect_top] == info_separator_char) break;

	    /* Ignore blank lines. */
	    if (info_file[indirect_top] == '\n') continue;

	    sscanf (&info_file[indirect_top], "%s%d",
		    temp_filename, &temp_first_byte);

	    if (strlen (temp_filename)) {
	      temp_filename[strlen (temp_filename)-1] = '\0';
	      indirect_list[index].filename =
		(char *)xmalloc (strlen (temp_filename) + 1);
	      strcpy (indirect_list[index].filename, temp_filename);
	      indirect_list[index].first_byte = temp_first_byte;
	      index++;
	    }
	  }
	  /* Terminate the table. */
	  if (index == MAX_INDIRECT_FILES) {
	    display_error ("Sorry, the INDIRECT file array isn't large enough.");
	    index--;
	  }
	  indirect_list[index].filename = (char *)NULL;
	}
	indirect = true;
      }
    }
  }
  return (true);
}

boolean
get_node (filename, nodename, popping)
     char *nodename, *filename;
     boolean popping;
{
  /* Make current_info_node be NODENAME.  This could involve loading
     a file, etc.  POPPING is true if we got here because we are popping
     one level. */

  int pointer;
  char internal_filename[FILENAME_LEN];
  char internal_nodename[NODENAME_LEN];

  if (nodename && *nodename) {
  /* Maybe nodename looks like: (filename)nodename, or worse: (filename).
     If so, extract the stuff out. */
    if (*nodename == '(') {
      /* We have a filename at the start of the nodename.  Find out what it is. */

      int temp = 1;
      int temp1 = 0;
      char character;

      filename = internal_filename;
      while ((character = nodename[temp]) && character != ')') {
	filename[temp-1] = character;
	temp++;
      }
      filename[temp-1] = '\0';

      /* We have the filename now.  The nodename follows. */
      internal_nodename[0] = '\0';
      if (nodename[temp]) {
	temp++;
	while (internal_nodename[temp1++] = nodename[temp++]);
      }
      nodename = internal_nodename;
    }
  }
 
  if (!popping) {
    push_node (current_info_file, current_info_node, pagetop, nodetop);
  }

  if (!nodename || !*nodename) {
    nodename = internal_nodename;
    strcpy(nodename, "Top");
  }

  if (!filename || !*filename) {
    filename = internal_filename;
    strcpy (filename, current_info_file);
  }

  if (!*filename) strcpy(filename, "dir");

  if (!get_info_file (filename, true)) goto node_not_found;

  if (strcmp (nodename, "*") == 0) {
    /* The "node" that we want is the entire file. */
    pointer = 0;
    goto found_node;
  }

  if (tag_table) {

    /* If we are using a tag table, see if we can find the nodename in it. */
    pointer = find_node_in_tag_table (nodename, 0);
    if (pointer < 1) {
      /* Then the search through the tag table failed.  Maybe
	 we should try searching the buffer?  Nahh, just barf. */
      boolean pop_node ();
node_not_found:
      if (popping) return (false); /* second time through. */
      display_error
	("Sorry, unable to find the node \"%s\" in the file \"%s\".", nodename, filename);
      current_info_file[0] = '\0';
      current_info_node[0] = '\0';
      last_loaded_info_file[0] = '\0';
      pop_node (internal_filename, internal_nodename, &nodetop, &pagetop);
      get_node (internal_filename, internal_nodename, true);
      return (false);
    }

    /* Otherwise, the desired location is right here.
       Scarf the position byte. */
    while (tag_table[pointer] != '\177') pointer++;
    sscanf (&tag_table[pointer+1], "%d", &pointer);

    /* Okay, we have a position pointer.  If this is an indirect file,
       then we should look through the indirect_list for the first
       element.first_byte which is larger than this.  Then we can load
       the specified file, and win. */

    if (indirect) {
      /* Find the filename for this node. */
      int index;
      for (index = 0; index < MAX_INDIRECT_FILES &&
	   indirect_list[index].filename != (char *)NULL; index++) {
	if (indirect_list[index].first_byte > pointer) {
	  /* We found it. */
	  break;
	}
      }
      if (!get_info_file (indirect_list[index - 1].filename, false))
	goto node_not_found;
      pointer -= indirect_list[index - 1].first_byte;

      /* Here is code to compensate for the header of an indirect file. */
      {
	int tt = find_node_start (0);
	if (tt > -1)
	  pointer += tt;
      }

    } else {
      /* This tag table is *not* indirect.  The filename of the file
	 containing this node is the same as the current file.  The
	 line probably looks like:
	 File: info,  Node: Checking▶7f◀25796 */
    }
  } else {
    /* We don't have a tag table.  The node can only be found by
       searching this file in its entirety.  */
    get_info_file (filename, true);
    pointer = 0;
  }

  /* Search this file, using pointer as a good guess where to start. */
  /* This is the same number that RMS used.  It might be right or wrong. */
  pointer -= 1000;
  if (pointer < 0) pointer = 0;

  pointer = find_node_in_file (nodename, pointer);
  if (pointer < 0) {
    goto node_not_found;
  }

  /* We found the node in it's file.  Remember exciting information. */

found_node:
  back_lines (0, pointer);
  nodetop = pagetop = pointer;
  strcpy (current_info_node, nodename);
  strcpy (current_info_file, filename);
  get_node_extent ();
  return (true);
}

get_node_extent ()
{
  /* Get the bounds for this node.  NODETOP points to the start of the
     node. Scan forward looking for info_separator_char, and remember
     that in NODEBOT. */

  int index = nodetop;
  int character;
  boolean do_it_till_end = (strcmp (current_info_node, "*") == 0);

  nodelines = 0;

 again:
  while (((character = info_file[index]) != info_separator_char) &&
	 index < info_buffer_len) {
    if (character == '\n') nodelines++;
    index++;
  }
  if (do_it_till_end && index != info_buffer_len) {
    index++;
    goto again;
  }
  nodebot = index;
}

/* Locate the start of a node in the current search_buffer.  Return
   the offset to the node start, or minus one.  START is the place in
   the file at where to begin the search. */
find_node_start (start)
     int start;
{
  return (search_forward (start_of_node_string, start));
}

find_node_in_tag_table (nodename, offset)
     char *nodename;
     int offset;
{
  /* Find NODENAME in TAG_TABLE. */

  int temp;
  set_search_constraints (tag_table, tag_buffer_len);

  temp = offset;
  while (true) {
    offset = search_forward (NODE_ID, temp);
    if (offset < 0) return (offset);
    temp = skip_whitespace (offset + strlen (NODE_ID));
    if (strnicmp (tag_table + temp, nodename, strlen (nodename)) == 0)
      if (*(tag_table + temp + strlen (nodename)) == '\177')
	return (temp);
  }
}

find_node_in_file (nodename, offset)
     char *nodename;
     int offset;
{
  /* Find NODENAME in INFO_FILE. */

  int temp;
  set_search_constraints (info_file, info_buffer_len);

  while (true) {
    offset = find_node_start (offset);
    if (offset < 0 ) return (offset);
    else temp = forward_lines (1, offset);
    if (temp == offset) return (-1); /* At last line now, just a node start. */
    else offset = temp;
    temp = string_in_line (NODE_ID, offset);
    if (temp > -1) {
      temp = skip_whitespace (temp+strlen (NODE_ID));
      if (strnicmp (info_file+temp, nodename, strlen (nodename)) == 0) {
	int check_exact = *(info_file + temp + strlen (nodename));
	if (check_exact == '\t' || check_exact == ',' || check_exact == '.')
	  return (offset);
      }
    }
  }
}


/* **************************************************************** */
/*								    */
/*		    Dumping and Printing Nodes			    */
/*								    */
/* **************************************************************** */

/* Make a temporary filename based on STARTER and the PID of this Info. */
char *
make_temp_filename (starter)
     char *starter;
{
  char *temp = (char *)xmalloc (FILENAME_LEN);
  sprintf (temp, "%s-%d", starter, getpid ());
  return (temp);
}

/* Delete a file.  Print errors if necessary. */
deletefile (filename)
     char *filename;
{
  if (unlink (filename) != 0)
    return (file_error (filename));
}

/* Print the contents of FILENAME using lpr. */
char *print_command = "lpr -p ";

printfile (filename)
     char *filename;
{
  int length = strlen (print_command) + strlen (filename) + strlen ("\n") + 1;
  char *command = (char *)xmalloc (length);
  int error;

  display_error ("Printing file `%s'...\n", filename);
  sprintf (command, "%s%s", print_command, filename);
  error = system (command);
  if (error) display_error ("Can't invoke `%s'", command);
  free (command);
  return (error);
}
  
/* Dump the current node into a file named FILENAME.
   Return 0 if the dump was successful, otherwise,
   print error and exit. */
dump_current_node (filename)
     char *filename;
{
  int i = nodetop;
  int c;
  FILE *output_stream = fopen (filename, "w");
  if (output_stream == (FILE *)NULL) return (file_error (filename));

  while (i < nodebot && i < info_buffer_len) {
    c = info_file[i];
    if (c < 0x020 && !(index ("\n\t\f", c))) {
      putc ('^', output_stream);
    }
    if (putc (info_file[i++], output_stream) == EOF) {
      fclose (output_stream);
      return (file_error (filename));
    }
  }
  fclose (output_stream);
  return (0);
}

/* **************************************************************** */
/*								    */
/*			 Toplevel eval loop. 			    */
/*								    */
/* **************************************************************** */

#define MENU_HEADER "\n* Menu:"
#define MENU_ID "\n* "
#define FOOTNOTE_HEADER "*Note"

/* Number of items that the current menu has. */
int the_menu_size = 0;

/* The node that last made a menus completion list. */
char *menus_nodename[NODENAME_LEN];
char *menus_filename[NODENAME_LEN];

boolean window_bashed = false;

char *visible_footnote = 0;	/* The default prompt string for the Follow Reference command. */
toploop ()
{
  boolean done = false;
  boolean inhibit_display = false;
  int command;
  char nodename[NODENAME_LEN];

  while (!done) {

    if (!inhibit_display || window_bashed) display_page();
    inhibit_display = window_bashed = false;

    nodename[0] = '\0';		/* Don't display old text in input line. */
    
    goto_xy (echo_area.left, echo_area.top);
    command = blink_cursor ();
    clear_echo_area ();

    if (command == EOF) {
      done = true;
      continue;
    }
    command = to_upper (command);

    switch (command) {

    case 'D':
      get_node (NULL, "(dir)Top");
      break;

    case 'H':
      if ((the_window.bottom - the_window.top) < 24)
	get_node (NULL, "(info)Help-Small-Screen");
      else get_node (NULL, "(info)Help");
      break;

    case 'N':
      if (!next_node ()) {
	display_error ("No NEXT for this node!");
	inhibit_display = true;
      }
      break;

    case 'P':
      if (!prev_node ()) {
	display_error ("No PREV for this node!");
	inhibit_display = true;
      }
      break;

    case 'U':
      if (!up_node ()) {
	display_error ("No UP for this node!");
	inhibit_display = true;
      }
      break;

    case 'M':
      if (!build_menu ()) {
	display_error ("No menu in this node!");
	inhibit_display = true;
	break;
      }

      if (!readline ("Menu item: ", nodename, NODENAME_LEN, true)) {
	clear_echo_area ();
	inhibit_display = true;
	break;
      }

      I_goto_xy (echo_area.left, echo_area.top);
      if (!find_menu_node (nodename, nodename)) {
	display_error ("\"%s\" is not a menu item!", nodename);
	inhibit_display = true;
	break;
      }

      if (get_node (NULL, nodename, false)) clear_echo_area ();
      break;


    case 'F':
      {
	char footnote[NODENAME_LEN];
	if (!build_notes ()) {
	  display_error("No cross-references in this node!");
	  inhibit_display = true;
	  break;
	}
	strcpy (footnote, visible_footnote);
	if (!readline ("Follow reference: ", footnote, NODENAME_LEN, true)) {
	  inhibit_display = true;
	  break;
	}

	I_goto_xy (echo_area.left, echo_area.top);
	if (!find_note_node (footnote, nodename)) {
	  display_error ("\"%s\" is not a cross-reference in this node!", footnote);
	  inhibit_display = true;
	  break;
	}
	
	if (get_node (NULL, nodename, false)) clear_echo_area ();
	break;
      }

    case 'L':
      {
	char filename[FILENAME_LEN], nodename[NODENAME_LEN];
	int ptop, ntop;
      if (pop_node (filename, nodename, &ntop, &ptop) &&
	  get_node (filename, nodename, true)) {
	pagetop = ptop;
      } else inhibit_display = true;
      break;
      }

    case SPACE:
      if (!next_page ()) {
	display_error ("At last page of this node now!");
	inhibit_display = true;
      }
      break;

    case DELETE:
      if (!prev_page ()) {
	display_error ("At first page of this node now!");
	inhibit_display = true;
      }
      break;

    case 'B':
      if (pagetop == nodetop) {
	display_error ("Already at beginning of this node!");
	inhibit_display = true;
      } else pagetop = nodetop;
      break;

      /* I don't want to do this this way, but the documentation clearly states
	 that '6' doesn't work.  It states this for a reason, and ours is not to
	 wonder why... */

    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
      {
	int item = command - '0';
	if (!build_menu ()) {
	  display_error ("No menu in this node!");
	  inhibit_display = true;
	  break;
	}
	if (item > the_menu_size) {
	  display_error ("There are only %d items in the menu!",
			 the_menu_size);
	  inhibit_display = true;
	  break;
	}

	if (!get_menu (item))
	  inhibit_display = true;
	break;
      }

    case 'G':
      if (!readline ("Goto node: ", nodename, NODENAME_LEN, false)) {
	inhibit_display = true;
	break;
      }
      clear_echo_area ();
      get_node (NULL, nodename, false);
      break;

    case 'S':
      {
	/* Search from the starting position forward for a string.
	   Of course, this should really wrap the search around, but
	   it doesn't do that yet.  Select the node containing the
	   desired string.  Put the top of the page screen_lines/2
	   lines behind it, but not before nodetop. */

	extern int info_buffer_len;
	int pointer, temp;
	int search_start = pagetop;

	if (!readline ("Search for string: ", nodename, NODENAME_LEN, false)) {
	  inhibit_display = true;
	  break;
	}

	I_goto_xy (echo_area.left, echo_area.top);

	if (indirect) {
	  /* Put the indirect search right here. */
	  display_error ("This is an indirect file, and I can't search these yet!");
	  break;
	} else {
	  set_search_constraints (info_file, info_buffer_len);
	  pointer = search_forward (nodename, search_start);
	  if (pointer == -1) {
	    display_error ("\"%s\" not found!  Try another info file.", nodename);
	    inhibit_display = true;
	    break;
	  }
	}
	temp = search_backward (start_of_node_string, pointer);
	if (temp == -1)
	  temp = search_forward (start_of_node_string, pointer);
	if (temp == -1) {
	  brians_error ();
	  break;
	}
	
	search_start = pointer;
	pointer = forward_lines (1, temp);
	if (!extract_field ("Node:", nodename, pointer)) {
	  display_error ("There doesn't appear to be a nodename for this node.");
	  get_node (NULL, "*", false);
	  pagetop = pointer;
	  break;
	}

	if (get_node (NULL, nodename, false)) clear_echo_area ();

	/* Make the string that the user wanted be visible, and
	   centered in the screen. */
	{
	  pointer = back_lines ((the_window.bottom - the_window.top) / 2, search_start);
	  if (pointer < nodetop) pointer = nodetop;

	  pagetop = pointer;
	}
	
	break;
      }

    case CTRL('H'):
    case '?':
      help_use_info();
      break;

    case 'Q':
      done = true;
      break;

    case CTRL('L'):		/* Control-l is redisplay */
      break;

    case '(':			/* You *must* be trying to type a complete nodename. */
      strcpy (nodename, "(");
      if (!readline ("Goto node: ", nodename, NODENAME_LEN, false)) {
	inhibit_display = true;
	clear_echo_area ();
	break;
      }
      I_goto_xy (echo_area.left, echo_area.top);
      if (get_node (NULL, nodename, false)) clear_echo_area ();
      break;

    case CTRL('P'):
      /* Print the contents of this node on the default printer.  We
	 would like to let the user specify the printer, but we don't
	 want to ask her each time which printer to use.  Besides, he
	 might not know, which is why it (the user) is in the need of
	 Info. */
      {
	char *tempname = make_temp_filename (current_info_node);
	if (dump_current_node (tempname) == 0 &&
	    printfile (tempname) == 0 &&
	    deletefile (tempname) == 0) {
	  display_error ("Printed node.  Go pick up your output.\n");
	}
	inhibit_display = true;
	free (tempname);
      }
      break;

    default:
      inhibit_display = true;
      display_error("Unknown command! Press '?' for help.");
	  
    }
  }
}

help_use_info ()
{
  /* Tell this person how to use Info. */

  open_typeout ();
  print_string ("\n\
          Commands in Info\n\
\n\
h	Invoke the Info tutorial.\n\
\n\
Selecting other nodes:\n\
n	Move to the \"next\" node of this node.\n\
p	Move to the \"previous\" node of this node.\n\
u	Move \"up\" from this node.\n\
m	Pick menu item specified by name.\n\
	Picking a menu item causes another node to be selected.\n\
f	Follow a cross reference.  Reads name of reference.\n\
l	Move to the last node you were at.\n\
\n\
Moving within a node:\n\
Space	Scroll forward a page.\n\
DEL	Scroll backward a page.\n\
b	Go to the beginning of this node.\n\
\n\
Advanced commands:\n\
q	Quit Info.\n\
1	Pick first item in node's menu.\n\
2 - 5   Pick second ... fifth item in node's menu.\n\
g	Move to node specified by name.\n\
	You may include a filename as well, as (FILENAME)NODENAME.\n\
s	Search through this Info file for a specified string,\n\
	and select the node in which the next occurrence is found.\n\
Ctl-p   Print the contents of this node using `lpr -p'.\n\
\n\
Done.\n\n");
  return (close_typeout ());
}
    
boolean
next_node ()
{
  /* Move to the node specified in the NEXT field. */

  char nodename[NODENAME_LEN];

  if (!extract_field ("Next:", nodename, nodetop)) return (false);
  return (get_node (NULL, nodename, false));
}

boolean
prev_node ()
{
  /* Move to the node specified in the PREVIOUS field. */

  char nodename[NODENAME_LEN];

  if (!extract_field ("Previous:", nodename, nodetop)
      && !extract_field ("Prev:", nodename, nodetop))
    return (false);
  return (get_node (NULL, nodename, false));
}

boolean
up_node ()
{
  /* Move to the node specified in the UP field. */

  char nodename[NODENAME_LEN];

  if (!extract_field ("Up:", nodename, nodetop)) return (false);
  return (get_node (NULL, nodename, false));
}

/* Build a completion list of menuname/nodename for each
   line in this node that is a menu item. */
boolean
build_menu ()
{
  int pointer = nodetop;
  char menuname[NODENAME_LEN];
  char nodename[NODENAME_LEN];

  if (strcmp (menus_nodename, current_info_node) == 0 &&
      strcmp (menus_filename, current_info_file) == 0) return (the_menu_size != 0);

  strcpy (menus_nodename, current_info_node);
  strcpy (menus_filename, current_info_file);
  free_completion_list ();

  set_search_constraints (info_file, nodebot);
  if ((pointer = search_forward (MENU_HEADER, nodetop)) < 0) return (false);

  /* There is a menu here.  Look for members of it. */
  pointer += strlen (MENU_HEADER);

  while (true) {
    int index;

    pointer = search_forward (MENU_ID, pointer);
    if (pointer < 0) break;	/* no more menus in this node. */
    pointer = (skip_whitespace (pointer + strlen (MENU_ID)));
    index = 0;
    while ((menuname[index] = info_file[pointer]) && menuname[index] != ':') {
      index++, pointer++;
    }
    menuname[index] = '\0';
    pointer++;
    if (info_file[pointer] == ':') {
      strcpy (nodename, menuname);
    } else {
      pointer = skip_whitespace (pointer);
      index = 0;
      while ((nodename[index] = info_file[pointer]) &&
	     nodename[index] != '\t' &&
	     nodename[index] != '.' &&
	     nodename[index] != ',') {
	index++, pointer++;
      }
      nodename[index] = '\0';
    }
    add_completion (menuname, nodename);
    the_menu_size++;
  }
  if (the_menu_size) completion_list = reverse_list (completion_list);
  return (the_menu_size != 0);
}

boolean
get_menu (item)
     int item;
{
  /* Select ITEMth item from the list built by build_menu. */

  if (!build_menu ()) return (false);
  if (item > the_menu_size) return (false);
  else {
    COMP_ENTRY *temp = completion_list;
    while (--item && temp) temp = temp->next;
    return (get_node (NULL, temp->data, false));
  }
}

boolean
find_menu_node (string, nodename)
     char *string, *nodename;
{
  /* Scan through the ?already? built menu list looking
     for STRING.  If you find it, put the corresponding nodes
     name in NODENAME. */
  return (scan_list (string, nodename));
}

boolean
scan_list (string, nodename)
     char *string, *nodename;
{
  /* The work part of find_menu_node and find_note_node. */
  COMP_ENTRY *temp = completion_list;

  while (temp) {
    if (strnicmp (string, temp->identifier, strlen(string)) == 0) {
      strcpy (nodename, temp->data);
      return (true);
    }
    temp = temp->next;
  }
  return (false);
}

clean_up (string)
     char *string;
{
  /* Remove <CR> and whitespace from string, replacing them with
     only one space.  Exception:  <CR> at end of string disappears. */

  char *to = string;
  char last_char = 0;
  boolean result;

  while (*to = *string++) {
    if (*to == '\n') {
      *to = SPACE;
      if (!(*(to+1))) {
	*to = '\0';
	return;
      }
    }
    result = (last_char == SPACE);
    last_char = *to;
    if (last_char != SPACE) to++;
    else if (!result) to++;
  }
}

find_footnote_ref (from)
     int from;
{
  /* Find a reference to "*Note".  Return the offset of
     the start of that reference, or -1. */

  while (true) {
    from = search_forward (FOOTNOTE_HEADER, from);
    if (from < 0) return (from);
    else from += strlen (FOOTNOTE_HEADER);
    if (info_file[from] == ' ' ||
	info_file[from] == '\n' ||
	info_file[from] == '\t') return (from);
  }
}
  

boolean
build_notes ()
{
  /* Build an array of (footnote.nodename) for each footnote in this node. */

  int pointer, temp;
  char notename[NODENAME_LEN];
  char nodename[NODENAME_LEN];

  set_search_constraints (info_file, nodebot);

  if ((find_footnote_ref (nodetop)) < 0) return (false);
  pointer = nodetop;

  menus_filename[0] = menus_nodename[0] = '\0';
  visible_footnote = "";
  free_completion_list ();

  while (true) {
    int index;

    pointer = find_footnote_ref (pointer);
    if (pointer < 0) break;	/* no more footnotes in this node. */

      
    pointer = skip_whitespace_and_cr (pointer);
    index = 0;
    while ((notename[index] = info_file[pointer]) && notename[index] != ':') {
      index++, pointer++;
    }
    notename[index] = '\0';
    clean_up (notename);
    pointer++;
    if (info_file[pointer] == ':') {
      strcpy (nodename, notename);
    } else {
      pointer = skip_whitespace (pointer);
      index = 0;
      while ((nodename[index] = info_file[pointer]) &&
	     nodename[index] != '\t' &&
	     nodename[index] != '.' &&
	     nodename[index] != ',') {
	index++, pointer++;
      }
      nodename[index] = '\0';
    }
    /* Add the notename/nodename to the list. */
    add_completion (notename, nodename);
    the_menu_size++;

    /* Remember this identifier as the default if it is the first one in the
       page. */
    if (!(*visible_footnote) &&
	pointer > pagetop &&
	pointer < forward_lines (the_window.bottom - the_window.top), pointer)
      visible_footnote = completion_list->identifier;
  }
  if (the_menu_size) completion_list = reverse_list (completion_list);
  return (the_menu_size != 0);
}

boolean
find_note_node (string, nodename)
     char *string, *nodename;
{
  /* Scan through the ?already? built footnote list looking
     for STRING.  If found, place the corresponding node name
     in NODENAME. */

  return (scan_list (string, nodename));
}

/* **************************************************************** */
/*								    */
/*			Page Display 				    */
/*								    */
/* **************************************************************** */


/* The display functions for GNU Info. */
   
int display_ch, display_cv;
int point_ch, point_cv;		/* the located values of Point */
int display_point;

display_page()
{
  /* Display the current page from pagetop down to the bottom of the
     page or the bottom of the node, whichever comes first. */

  display_point = pagetop;
  display_ch = the_window.left;
  display_cv = the_window.top;
  generic_page_display();
}

generic_page_display()
{
  /* Print the page from display_point to bottom of node, or window,
     whichever comes first.  Start printing at display_ch, display_cv. */

  int done_with_display = 0;
  int character;

  goto_xy(display_ch, display_cv);

  while (!done_with_display) {
    
    character = info_file[display_point];

    if (display_point == nodebot) {
      clear_eop ();
      goto display_finish;
    }
    
    if ((display_width(character, the_window.ch)+the_window.ch)
	>= the_window.right) {
      display_carefully(character);
    } else charout(character);

    if ((the_window.cv >= the_window.bottom)
	|| (the_window.cv == the_window.top
	    && the_window.ch == the_window.left)) {
display_finish:
      make_modeline();
      done_with_display++;
      continue;
    }
    else display_point++;
  }
}

display_carefully (character)
int character;
{
  /* Display character carefully, insuring that no scrolling takes place, even
     in the case of funky control characters. */

  if (CTRL_P(character)) {
    switch (character) {
    case RETURN:
    case NEWLINE:
    case TAB:
      clear_eol ();
      advance (the_window.right-the_window.ch);
      break;
    default:
      charout ('^');
      if (the_window.cv == the_window.bottom) break;
      else charout (UNCTRL(character));
    }
  } else charout (character);
}

boolean
next_page ()
{
  /* Move to the next page in this node.  Return FALSE if
     we can't get to the next page. */

  int pointer =
    forward_lines ((the_window.bottom - the_window.top) - 2, pagetop);
  if (pointer >= nodebot) return (false);

  pagetop = pointer;
  return (true);
}

boolean
prev_page ()
{
  /* Move to the previous page in this node.  Return FALSE if
     there is no previous page. */

  int pointer =
    back_lines ((the_window.bottom - the_window.top) - 2, pagetop);

  if (pagetop == nodetop) return (false);
  if (pointer < nodetop) pointer = nodetop;

  pagetop = pointer;
  return (true);
}


/* **************************************************************** */
/*								    */
/*			Utility Functions			    */
/*								    */
/* **************************************************************** */

char *
cons_string (string)
     char *string;
{
  char *temp = (char *)xmalloc (strlen (string));
  strcpy (temp, string);
  return (temp);
}

char *search_buffer;		/* area in ram to scan through. */
int   buffer_bottom;		/* Length of this area. */

set_search_constraints (buffer, extent)
     char *buffer;
     int extent;
{
  /* Set the global variables that all of these routines use. */

  search_buffer = buffer;
  buffer_bottom = extent;
}

to_beg_line (from)
     int from;
{
  /* Move back to the start of this line. */
  while (from  && search_buffer[from - 1] != '\n') from--;
  return (from);
}

to_end_line (from)
{
  /* Move forward to the end of this line. */
  while (from < buffer_bottom && search_buffer[from] != '\n') from++;
  return (from);
}

back_lines (count, starting_pos)
     int count, starting_pos;
{
  /* Move back count lines in search_buffer starting at starting_pos.
     Returns the start of that line. */
  starting_pos = to_beg_line (starting_pos);
  while(starting_pos && count) {
    starting_pos = to_beg_line (starting_pos-1);
    count--;
  }
  return (starting_pos);
}

forward_lines (count, starting_pos)
     int count, starting_pos;
{
  /* Move forward count lines starting at starting_pos.
     Returns the start of that line. */
  starting_pos = to_end_line (starting_pos);
  while (starting_pos < buffer_bottom && count) {
    starting_pos = to_end_line (starting_pos+1);
    count--;
  }
  return (to_beg_line (starting_pos));
}

search_forward (string, starting_pos)
     char *string;
     int starting_pos;
{
  /* Search for STRING in SEARCH_BUFFER starting at STARTING_POS.
     Return the location of the string, or -1 if not found. */
  int len = strlen(string);

  while ((starting_pos+len) < buffer_bottom) {
    if (strnicmp (search_buffer+starting_pos, string, len) == 0)
      return (starting_pos);
    else starting_pos++;
  }
  return (-1);
}

search_backward (string, starting_pos)
     char *string;
     int starting_pos;
{
  /* Search for STRING in SEARCH_BUFFER starting at STARTING_POS.
     Return the location of the string, or -1 if not found. */
  int len = strlen (string);
  while (starting_pos-len > -1) {
    if (strnicmp (search_buffer + (starting_pos - len), string, len) == 0)
      return (starting_pos - len);
    else starting_pos--;
  }
  return (-1);
}

string_in_line (string, pointer)
     char *string;
     int pointer;
{
  /* Only search for STRING from POINTER to end of line.  Return offset
     of string, or -1 if not found. */
  int old_buffer_bottom = buffer_bottom;

  set_search_constraints (search_buffer, to_end_line (pointer));
  pointer = search_forward (string, pointer);
  buffer_bottom = old_buffer_bottom;
  return (pointer);
}

skip_whitespace (offset)
     int offset;
{
  /* Skip whitespace characters at OFFSET in SEARCH_BUFFER.
     Return the next non-whitespace character or -1 if BUFFER_BOTTOM
     is reached. */

  int character;

  while (offset < buffer_bottom) {
    character = search_buffer[offset];
    if (character == ' ' || character == '\t') offset++;
    else return (offset);
  }
  return (-1);
}

skip_whitespace_and_cr (offset)
     int offset;
{
  /* Skip whitespace characters including <CR> at OFFSET in
     SEARCH_BUFFER.  Return the position of the next non-whitespace
     character, or -1 if BUFFER_BOTTOM is reached. */

  while (true) {
    offset = skip_whitespace (offset);
    if (offset > 0 && search_buffer[offset] != '\n') return (offset);
    else offset++;
  }
}

boolean
extract_field (field_name, nodename, offset)
     char *field_name, *nodename;
     int offset;
{
  /* Extract the node name part of the of the text after the FIELD.
     Place the node name into NODENAME.  Assume the line starts at
     OFFSET in SEARCH_BUFFER. */

  int temp, i, character;

  temp = string_in_line (field_name, offset);
  if (temp < 0) return (false);

  temp += strlen (field_name);
  temp = skip_whitespace (temp);

  /* Okay, place the following text into NODENAME. */

  while ((character = search_buffer[temp]) != ','
	 && character != '\n'
	 && character != '\t') {
    *nodename = character;
    nodename++; temp++;
  }
  *nodename = '\0';
  return (true);
}  

boolean
looking_at (string, pointer)
     char *string;
     int pointer;
{
  /* Return true if pointer is exactly at string, else false. */

  if (strnicmp (search_buffer + pointer, string, strlen (string)) == 0)
    return (true);
  else return (false);
}

extern NODEINFO *Info_History;

/* Save the current filename, nodename, and position on the history list.
   We prepend. */
boolean
push_node (filename, nodename, page_position, node_position)
     char *filename, *nodename;
     int page_position, node_position;
{
  NODEINFO *newnode = (NODEINFO *)xmalloc (sizeof (NODEINFO));

  newnode->next = Info_History;

  newnode->filename = (char *)xmalloc (strlen (filename) + 1);
  strcpy (newnode->filename, filename);

  newnode->nodename = (char *)xmalloc (strlen (nodename) + 1);
  strcpy (newnode->nodename, nodename);

  newnode->pagetop = page_position;
  newnode->nodetop = node_position;

  Info_History = newnode;
  return (true);
}

boolean
pop_node (filename, nodename, nodetop, pagetop)
     char *filename, *nodename;
     int  *nodetop, *pagetop;
{
  /* Pop one node from the node list, leaving the values in
     passed variables. */

  if (Info_History->next == (NODEINFO *)NULL) {
    display_error ("At beginning of history now!");
    return (false);
  } else {
    NODEINFO *releaser = Info_History;

    if (strcmp (Info_History->filename, last_loaded_info_file) != 0)
          last_loaded_info_file[0] = '\0'; /* Force the reloading of the file. */
    strcpy (filename, Info_History->filename);
    strcpy (nodename, Info_History->nodename);
    *pagetop = Info_History->pagetop;
    *nodetop = Info_History->nodetop;
    free (Info_History->nodename);
    free (Info_History->filename);
    Info_History = Info_History->next;
    free (releaser);
    return (true);
  }
}

/* Whoops, Unix doesn't have strnicmp. */

strnicmp (string1, string2, count)
     char *string1, *string2;
{
  /* Compare at most COUNT characters from string1 to string2.  Case
     doesn't matter. */
  char ch1, ch2;

  while (count) {
    ch1 = *string1++;
    ch2 = *string2++;
    if (to_upper(ch1) == to_upper(ch2)) count--;
    else break;
  }
  return (count);
}

boolean get_y_or_n_p ()
{
  /* Make the user type "Y" or "N". */

  int character;
  print_string (" (Y or N)?");
  clear_eol ();

until_we_like_it:
  
  character = blink_cursor ();
  if (character == EOF) return (false);
  if (to_upper(character) == 'Y') {
    charout (character);
    return (true);
  }

  if (to_upper(character) == 'N') {
    charout (character);
    return (false);
  }

  if (character == ABORT_CHAR) {
    ding ();
    return (false);
  }

  goto until_we_like_it;
}

indent_to (screen_column)
     int screen_column;
{
  /* Move the cursor to the desired column in the window. */

  int counter = screen_column - the_window.ch;
  if (counter > 0) {
    while (counter--) charout (' ');
  } else if (screen_column != 0) charout (' ');
}


/* **************************************************************** */
/*								    */
/*			Error output/handling.			    */
/*								    */
/* **************************************************************** */

file_error (file)
     char *file;
{
  /* Display specific error from known file error table. */
  extern int errno;
  extern int sys_nerr;
  extern char *sys_errlist[];

  if (errno < sys_nerr)
    return (display_error ("%s: %s", file, sys_errlist[errno]));
  else return (display_error ("%s: Unknown error %d", file, errno));
}

display_error (format_string, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
     char *format_string;
{
  /* Display the error in the echo-area using format_string and args.
     This is a specialized interface to printf. */

  extern boolean terminal_inited_p;
  char output_buffer[1024];

  if (totally_inhibit_errors) return (1);
  sprintf (output_buffer, format_string, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
  if (terminal_inited_p) {
    new_echo_area ();
    ding ();
    print_string (output_buffer);
    close_echo_area ();
  } else fprintf (stderr, "%s\n", output_buffer);
  return (1);
}


brians_error ()
{
  /* Tell everybody what a loser I am.  If you see this error,
     send me a bug report. */

  display_error ("You are never supposed to see this error.  Tell Brian to fix this.");
  return (-1);
}


/* **************************************************************** */
/*								    */
/*			Terminal IO, and Driver			    */
/*								    */
/* **************************************************************** */

/* The Unix termcap interface code. */

#define NO_ERROR 0
#define GENERIC_ERROR 1
#define NO_TERMINAL_DESCRIPTOR 2
#define OUT_OF_MEMORY 3
#define BAD_TERMINAL 4

#define FERROR(msg)	fprintf (stderr, msg); exit (GENERIC_ERROR)

extern int tgetnum(), tgetflag();
extern char *tgetstr();

#define certainly_enough_space 2048	/* page 3, Section 1.1, para 4 */

#ifdef UNIX
char termcap_buffer[certainly_enough_space];
#else
#define termcap_buffer NULL
#endif

/* You CANNOT remove these next four vars.  TERMCAP needs them to operate. */
char  PC;
char *BC;
char *UP;
int ospeed;

/* A huge array of stuff to get from termcap initialization. */

#define tc_int 0
#define tc_char tc_int+1
#define tc_flag tc_char+1
#define tc_last tc_flag+1

typedef int flag;

/* First, the variables which this array refers to */

/* Capabilities */

int  terminal_rows;			/* {tc_int, "co" */
int  terminal_lines;			/* {tc_int, "li" */
flag terminal_is_generic;		/* {tc_flag,"gn" */

   /* Cursor Motion */

char *terminal_goto;			/* {tc_char,"cm" */
char *terminal_home;			/* {tc_char,"ho" */

char *terminal_cursor_left;		/* {tc_char,"le" */
char *terminal_cursor_right;		/* {tc_char,"nd" */
char *terminal_cursor_up;		/* {tc_char,"up" */
char *terminal_cursor_down;		/* {tc_char,"do" */

/* Screen Clearing */

char *terminal_clearpage;		/* {tc_char,"cl" */
char *terminal_clearEOP;		/* {tc_char,"cd" */
char *terminal_clearEOL;		/* {tc_char,"ce" */

/* "Standout" */
char *terminal_standout_begin;		/* {tc_char,"so" */
char *terminal_standout_end;		/* {tc_char,"se" */

/* Reverse Video */
char *terminal_inverse_begin;		/* {tc_char,"mr" */
char *terminal_end_attributes;		/* {tc_char,"me" */

/* Ding! */

char *terminal_ear_bell;		/* {tc_char,"bl" */

/* Terminal Initialization */

char *terminal_use_begin;		/* {tc_char,"ti" */
char *terminal_use_end;			/* {tc_char,"te" */

/* Padding Stuff */

char *terminal_padding;			/* {tc_char,"pc" */

/* Now the whopping big array */

typedef struct {
    char type;
    char *name;
    char *value;
} termcap_capability_struct;

termcap_capability_struct capabilities[] = {

/* Capabilities */

    {tc_int, "co", (char *)&terminal_rows},
    {tc_int, "li", (char *)&terminal_lines},
    {tc_flag,"gn", (char *)&terminal_is_generic},

/* Cursor Motion */

    {tc_char,"cm", (char *)&terminal_goto},
    {tc_char,"ho", (char *)&terminal_home},

    {tc_char,"le", (char *)&terminal_cursor_left},
    {tc_char,"nd", (char *)&terminal_cursor_right},
    {tc_char,"up", (char *)&terminal_cursor_up},
    {tc_char,"do", (char *)&terminal_cursor_down},


/* Screen Clearing */

    {tc_char,"cl", (char *)&terminal_clearpage},
    {tc_char,"cd", (char *)&terminal_clearEOP},
    {tc_char,"ce", (char *)&terminal_clearEOL},

/* "Standout" */
    {tc_char,"so", (char *)&terminal_standout_begin},
    {tc_char,"se", (char *)&terminal_standout_end},

/* Reverse Video */
    {tc_char,"mr", (char *)&terminal_inverse_begin},
    {tc_char,"me", (char *)&terminal_end_attributes},

/* Ding! */

    {tc_char,"bl", (char *)&terminal_ear_bell},

/* Terminal Initialization */

    {tc_char,"ti", (char *)&terminal_use_begin},
    {tc_char,"te", (char *)&terminal_use_end},

/* Padding Stuff */

    {tc_char,"pc", (char *)&terminal_padding},

/* Terminate this array with a var of type tc_last */
    {tc_last,NULL, NULL}

};

int terminal_opened_p = 0;

open_terminal_io()
{
   int error;
   
   if (terminal_opened_p) return (NO_ERROR);
   if ((error = get_terminal_info ()) != NO_ERROR) return (error);
   if ((error = get_terminal_vars (capabilities)) != NO_ERROR) return (error);
   
   /* Now, make sure we have the capabilites that we need. */
   if (terminal_is_generic) return (BAD_TERMINAL);
   terminal_opened_p++;
   return (NO_ERROR);
}

get_terminal_info()
{
    char *getenv();
    char temp_string_buffer[256];
    int result;

    char *terminal_name = getenv ("TERM");

    if (terminal_name == NULL || *terminal_name == 0
    	|| (strcmp (terminal_name, "dialup") == 0)) {
	terminal_name = temp_string_buffer;
	printf ("\nTerminal Type:");
	gets (terminal_name);
	if (!(*terminal_name))
	    return (NO_TERMINAL_DESCRIPTOR);
    }

/* #define VERBOSE_GET_TERMINAL 1 */
#ifdef VERBOSE_GET_TERMINAL

#define buffer_limit 256
#define opsys_termcap_filename "/etc/termcap"

    /* We hack question mark if that is what the user typed.  All this means
       is we read /etc/termcap, and prettily print out the names of terminals
       that we find. */

    if (terminal_name[0] == '?' && !terminal_name[1]) {
	FILE *termcap_file;
	if ((termcap_file = fopen (opsys_termcap_filename, "r")) != NULL) {
	    int result;
	    char line_buffer[buffer_limit];
	    int terminal_count = 0;

	    while ((readline_termcap(termcap_file, line_buffer)) != EOF) {
		char first_char = *line_buffer;
		if (first_char == '#' || first_char == ' '
		 || first_char == '\t' || first_char == '\n')
		      ;
		else {
		    /* print the names the pretty way. */
		    printf("\n%s",line_buffer);	/* liar */
		    terminal_count++;
		}
	    }
	    fclose(termcap_file);
	    if (terminal_count) {
		printf("\n%d terminals listed.\n",terminal_count);
	    } else printf("\nNo terminals were listed.  Brian's mistake.\n");
	} else {
	    fprintf(stderr,
	       "\nNo such system file as %s!\nWe lose badly.\n",
	       	    opsys_termcap_filename);
    	    return(NO_TERMINAL_DESCRIPTOR);
	}
    return(get_terminal_info());
    }

#endif	    /* VERBOSE_GET_TERMINAL */

    result = tgetent(termcap_buffer, terminal_name);
    if (!result) return (NO_TERMINAL_DESCRIPTOR);
    else return(NO_ERROR);
}

#ifdef VERBOSE_GET_TERMINAL
readline_termcap (stream, buffer)
FILE *stream;
char *buffer;
{
    int c;
    int buffer_index = 0;

    while ((c = getc(stream)) != EOF && c != '\n') {
	if (buffer_index != buffer_limit-1) {
	    buffer[buffer_index++] = c;
	}
    }
    buffer[buffer_index] = 0;
    if (c == EOF) {
	return ((buffer_index) ? 0 : EOF);
    } else return (0);
}
#endif	/* VERBOSE_GET_TERMINAL */

get_terminal_vars (from_array)
termcap_capability_struct from_array[];
{
   /* For each element of "from_array", read the corresponding variable's
      value into the right place.  You should comment out the parts of the
      array that you don't need, and their corresponding variable
      declarations. */

   int i;
   register termcap_capability_struct *item;

#ifdef UNIX				/* this shit makes my code ugly. */
   char *buffer = (char *)xmalloc (strlen (termcap_buffer) + 1);
#define buffer_space &buffer
#else
#define buffer_space 0
#endif

   for (i=0; (item = &from_array[i]) && (item->type != tc_last); i++) {

      switch (item->type) {

	 case tc_int:
	   *((int *)(item->value)) = tgetnum (item->name);
	   break;
	 
	 case tc_flag:
	    *((int *)item->value) = tgetflag (item->name);
	    break;
	    
	 case tc_char:
	    *((char **)item->value) = tgetstr (item->name, buffer_space);
	    break;
	 
	 default:
	    FERROR ("Bad entry scanned in tc_struct[].\n \
	       Ask Brian to fix this someday.\n");
      }
   }
   
   PC = terminal_padding ? terminal_padding[0] : 0;
   BC = terminal_cursor_left;
   UP = terminal_cursor_up;
   return (NO_ERROR);
}

get_term_width ()
{
  return (tgetnum ("co"));
}

get_term_height ()
{
  return (tgetnum ("li"));
}

/* #define TERMINAL_INFO_PRINTING */
#ifdef TERMINAL_INFO_PRINTING

show_terminal_info (from_array)
termcap_capability_struct from_array[];
{
   /* Scan this (already "get_terminal_vars"ed) array, printing out the
      capability name, and value for each entry.  Pretty print the value
      so that the terminal doesn't actually do anything, shitbrain. */
   
   int i;
   register termcap_capability_struct *item;
   
   for (i=0; ((item = &from_array[i]) && ((item->type) != tc_last)); i++) {
      
      char *type_name;
      switch (item->type) {
	 case  tc_int: type_name = "int "; break;
	 case tc_flag: type_name = "flag"; break;
	 case tc_char: type_name = "char"; break;
	 default: type_name = "Broken";
      }

      printf ("\t%s\t%s = ",type_name, item->name);
      
      switch (item->type) {
	 case  tc_int:
	 case tc_flag: printf ("%d",*((int *)item->value)); break;
         case tc_char: tc_pretty_print (*((char **)item->value)); break;
      }
      printf ("\n");
   }
}

tc_pretty_print (string)
char *string;
{
   /* Print the contents of string without sending anything that isn't
      a normal printing ASCII character. */
   
   char c;
   while (c = *string++) {
      if (c<' ') {
	 printf ("%c",'^');
	 c += 64;
      }
      printf ("%c",c);
   }
}

#endif TERMINAL_INFO_PRINTING


/* **************************************************************** */
/*								    */
/*			Character IO, and driver		    */
/*								    */
/* **************************************************************** */

WINDOW the_window = { 0,0,80,24, 0,0};
WINDOW_LIST *window_stack = NULL;
WINDOW terminal_window = {0,0,80,24, 0,0};

char *widest_line;
boolean terminal_inited_p = false;

init_terminal_io ()
{
  /* Start up the character io stuff. */
  if (!terminal_inited_p) {
    opsys_init_terminal ();
    widest_line = (char *)xmalloc (terminal_rows);
    if (terminal_lines <= 0) terminal_lines = 24;
    terminal_inited_p = true;
  }

  terminal_window.left = 0;
  terminal_window.top = 0;
  terminal_window.right = terminal_rows;
  terminal_window.bottom = terminal_lines;

  set_window (&terminal_window);

  terminal_window.bottom -= 3;

  set_window (&terminal_window);

  init_echo_area (the_window.left, the_window.bottom+1,
		  the_window.right, terminal_lines);

  /* Here is a list of things that the terminal has to be able to do. Do
     you think that this is too harsh? */
  if (
      !terminal_goto ||		/* we can't move the cursor. */
      !terminal_lines		/* we don't how many lines it has. */
     ) {}
}


ding ()
{
  /* Ring the terminal bell. */
  extern char *terminal_ear_bell;
  if (terminal_ear_bell) do_term (terminal_ear_bell);
  else putchar (CTRL('G'));
  fflush (stdout);
}

int untyi_char = 0;
boolean inhibit_output = false;

blink_cursor ()
{
  /* Return a character from stdin, or the last unread character
     if there is one available. */

  int character;
  if (untyi_char) {
    character = untyi_char;
    untyi_char = 0;
  } else character = getc (stdin);
  return (character);
}

charout (character)
     int character;
{
  /* Display single character on the terminal screen.  If the 
     character would run off the right hand edge of the screen,
     advance the cursor to the next line. */

  if (inhibit_output) return;

  if (CTRL_P(character)) {
    /* Display this character special if it is Control. */
    switch (character) {
    case NEWLINE:
    case RETURN:
      print_cr ();
      break;
      
    case TAB:
      print_tab ();
      break;
      
    default:
      charout ('^');
      charout (UNCTRL(character));
    }
  } else {
    printf ("%c", character);
    advance (1);
  }
}

advance (amount)
     int amount;
{
  /* Move the cursor amount character positions. */

  int old_window_cv = the_window.cv;

  while (amount-- > 0) {
    the_window.ch++;
    if (the_window.ch >= the_window.right) {
      the_window.ch = (the_window.ch - the_window.right) + the_window.left;
      the_window.cv++;
      if (the_window.cv >= the_window.bottom) {
	the_window.cv = the_window.top;
      }
    }
  }
  if (the_window.cv != old_window_cv) {
    goto_xy (the_window.ch, the_window.cv);
  }
}

print_string (string, a1, a2, a3, a4, a5)
     char *string;
{
  /* Print string using charout */
  int character;
  char buffer[2048];
  int index = 0;

  sprintf(buffer, string, a1, a2, a3, a4, a5);
  while (character = buffer[index++]) charout (character);
}

print_cr()
{
  extern boolean typing_out;
  /* Display a carriage return. Clears to the end of the line first. */
  clear_eol ();
  if (typing_out) {		/* Do the "MORE" stuff. */
    int response;

    if (the_window.cv+2 == the_window.bottom) {

      goto_xy (the_window.left, the_window.cv+1);
      clear_eol ();
      print_string ("[More]");
      response = blink_cursor ();
      if (response != SPACE) {
	untyi_char = response;
	inhibit_output = true;
	return;
      } else {
	goto_xy (the_window.left, the_window.cv);
	clear_eol ();
	goto_xy (the_window.left, the_window.top);
	return;
      }
    }
  }
  advance (the_window.right - the_window.ch);
}

print_tab ()
{
  /* Move the cursor to the next tab stop, blanking the intervening
     spaces along the way. */

  int destination =
    (((the_window.ch - the_window.left) + 8) &0x0f8) + the_window.left;

  if (destination >= the_window.right)
    destination -= the_window.right;

  while (the_window.ch != destination) charout (SPACE);
}

display_width (character, hpos)
int character, hpos;
{
  int width = 1;

  if (CTRL_P(character)) {
    switch (character) {
    case RETURN:
    case NEWLINE:
      width = the_window.right - hpos;
      break;
    case TAB:
      width = ((hpos + 8) & 0xf7) - hpos;
      break;
    default: width = 2;
    }
  }
  return (width);
}
  
I_goto_xy (xpos, ypos)
     int xpos, ypos;
{
  /* Like GOTO_XY, but do it right away. */
  goto_xy (xpos, ypos);
  fflush (stdout);
}
	     
goto_xy (xpos, ypos)
     int xpos, ypos;
{
  /* Move the cursor, (and cursor variables) to xpos, ypos. */

  the_window.ch = xpos; the_window.cv = ypos;
  opsys_goto_pos (xpos, ypos);
}

clear_screen ()
{
  /* Clear the screen, leaving ch and cv at the top of the window. */

  goto_xy (the_window.left, the_window.top);
  clear_eop_slowly ();
}

clear_eop_slowly()
{
  int temp_ch = the_window.ch;
  int temp_cv = the_window.cv;
  
  clear_eol ();
  
  while (++the_window.cv < the_window.bottom) {
    goto_xy (the_window.left, the_window.cv);
    clear_eol ();
  }
  goto_xy (temp_ch, temp_cv);
}

clear_eop ()
{
  /* Clear from current cursor position to end of page. */

  if (terminal_clearEOP) do_term (terminal_clearEOP);
  else clear_eop_slowly ();
}

clear_eol ()
{
  /* Clear from current cursor position to end of screen line */

  int temp_ch = the_window.ch;

  if (terminal_clearEOL) do_term (terminal_clearEOL);
  else {
    char *line = widest_line;
    int i;

    for (i=0; i< the_window.right-the_window.ch; i++)
      line[i] = ' ';
    line[i] = '\0';

    printf("%s", line);
  }
  goto_xy (temp_ch, the_window.cv);
}

with_output_to_window (window, function, arg1, arg2, arg3, arg4, arg5)
     WINDOW *window;
     FUNCTION *function;
{
  /* call FUNCTION with WINDOW active.  You can pass upto 5 args to the
     function.  This returns whatever FUNCTION returns. */

  int result;

  push_window ();
  set_window (window);
  result = (*function) (arg1, arg2, arg3, arg4, arg5);
  pop_window ();
  return (result);
}

set_window (window)
     WINDOW *window;
{
  /* Given a pointer to a window data structure, make that the current
     window. */

  bcopy (window, &the_window, sizeof (WINDOW));
}

push_window ()
{
  /* save the current window on the window stack. */
  WINDOW_LIST *new_window = (WINDOW_LIST *)xmalloc (sizeof (WINDOW_LIST));

  new_window->next_window = window_stack;
  window_stack = new_window;
  new_window->ch = the_window.ch;
  new_window->cv = the_window.cv;
  new_window->top = the_window.top;
  new_window->bottom = the_window.bottom;
  new_window->left = the_window.left;
  new_window->right = the_window.right;
}

pop_window ()
{
  /* pop the top of the window_stack into the_window. */
  
  set_window ((WINDOW *)window_stack);

  if (window_stack->next_window) {
    WINDOW_LIST *thing_to_free = window_stack;
    window_stack = window_stack->next_window;
    free (thing_to_free);
  }

  goto_xy (the_window.ch, the_window.cv);
}


/* **************************************************************** */
/*								    */
/*			"Opsys" functions.			    */
/*								    */
/* **************************************************************** */

/* The lowlevel terminal/file interface.  Nothing ever really gets low level
   when you're writing in C, though. 

   This file contains all of the "opsys" labels.  You have to make a different
   one if you want GNU Infos to run on machines that don't have unix.  */

extern char *terminal_use_begin, *terminal_use_end, *terminal_goto;

int original_tty_flags = 0;

opsys_init_terminal()
{
 /* Yes, that's right, do things that the machine needs to get the terminal
     into a usable mode. */

  {
    struct sgttyb ttybuff;
    void ioctl();
    int fildes;

    fildes = open("/dev/tty", O_RDONLY);
    if (fildes < 1) barf("Can't get tty descriptor");
    ioctl(fildes,TIOCGETP, &ttybuff);
    
    if (!original_tty_flags) original_tty_flags = ttybuff.sg_flags;
    ttybuff.sg_flags = CBREAK;
    ospeed = ttybuff.sg_ospeed;
    ioctl(fildes, TIOCSETP, &ttybuff);
    close(fildes);
  }
  open_terminal_io();
  do_term(terminal_use_begin);
}

restore_io()
{
  /* Fix the terminal that I broke. */

  struct sgttyb ttybuff;
  void ioctl();
  int fildes;
  
  fildes = open("/dev/tty", O_RDONLY);
  if (fildes < 1) barf("Can't get tty descriptor");
  ioctl(fildes,TIOCGETP, &ttybuff);
  
  ttybuff.sg_flags = original_tty_flags;
  ioctl(fildes, TIOCSETP, &ttybuff);
  close(fildes);
  do_term(terminal_use_end);
}


opsys_goto_pos (xpos, ypos)
int xpos, ypos;
{
  char *tgoto();

  do_term (tgoto (terminal_goto, xpos, ypos));
}

character_output_function (character)
     char character;
{
  putchar (character);
}

/* Generic interface to tputs. */
do_term (command)
char *command;
{
  /* Send command to the terminal, with appropriate padding. */
  
  tputs (command,1, character_output_function);
}

/* Filename manipulators, and the like. */

char local_temp_filename[FILENAME_LEN];

/* Expand the filename in partial to make a real name for
   this operating system.  This looks in INFO_PATHS in order to
   find the correct file.  If it can't find the file, it just
   returns the path as you gave it. */
char *
opsys_filename (partial)
     char *partial;
{
  int initial_character;

  if (partial && (initial_character = *partial)) {

    if (initial_character == '/') return (partial);
    if (initial_character == '~') {
      if (partial[1] == '/') {
	/* Return the concatenation of HOME and the rest of the string. */
	strcpy (local_temp_filename, getenv ("HOME"));
	strcat (local_temp_filename, &partial[2]);
	return (local_temp_filename);
      } else {
	struct passwd *user_entry;
	int i, c;
	char username[257];

	for (i=1; c = partial[i]; i++) {
	  if (c == '/') break;
	  else username[i - 1] = c;
	}
	username[i - 1] = '\0';
	
	if (!(user_entry = getpwnam (username))) {
	  display_error ("Not a registered user!");
	  return (partial);
	}
	strcpy (local_temp_filename, user_entry->pw_dir);
	strcat (local_temp_filename, &partial[i]);
	return (local_temp_filename);
      }
    }
    
    if (initial_character == '.') {
      if (!getwd (local_temp_filename)) {
	display_error (local_temp_filename);
	return (partial);
      }
      strcat (local_temp_filename, &partial[1]);
      return (local_temp_filename);
    }

    /* Scan the list of directories in INFO_DIRECTORY_PATHS. */
    {
      struct stat finfo;
      char *temp_dirname, *extract_colon_unit ();
      int dirname_index = 0;

      while (temp_dirname = extract_colon_unit (info_directory_paths,
						&dirname_index)) {
	strcpy (local_temp_filename, temp_dirname);
	if (temp_dirname[(strlen (temp_dirname)) - 1] != '/')
	  strcat (local_temp_filename, "/");
	strcat (local_temp_filename, partial);
	free (temp_dirname);
	if (stat (local_temp_filename, &finfo) == 0)
	  return (local_temp_filename);
      }
    }
  }
  return (partial);
}

/* Given a string containing units of information separated by colons,
   return the next one pointed to by INDEX, or NULL if there are no more.
   Advance INDEX to the character after the colon. */
char *
extract_colon_unit (string, index)
     char *string;
     int *index;
{
  register int i, start;
  char *xmalloc ();

  i = start = *index;
  if ((i >= strlen (string)) || !string) return ((char *)NULL);

  while (string[i] && string[i] != ':') i++;
  if (i == start) {
    return ((char *)NULL);
  } else {
    char *value = xmalloc (1 + (i - start));
    strncpy (value, &string[start], (i - start));
    value [i - start] = '\0';
    if (string[i]) ++i;
    *index = i;
    return (value);
  }
}

/* **************************************************************** */
/*								    */
/*			The echo area.				    */
/*								    */
/* **************************************************************** */

/* echoarea.c -- some functions to aid in user interaction. */

WINDOW echo_area = {0,0,0,0,0,0};
boolean echo_area_open_p = false;
char modeline[256];
WINDOW modeline_window = {0,0,0,0, 0,0};

init_echo_area (left, top, right, bottom)
     int left, top, right, bottom;
{
  /* define the location of the echo area. Also inits the
     modeline as well. */

  echo_area.left = modeline_window.left = left;
  echo_area.top = top;  modeline_window.top = top-1;
  echo_area.right = modeline_window.right = right;
  echo_area.bottom = bottom; modeline_window.bottom = modeline_window.top;
}

new_echo_area ()
{
  /* Make the echo_area_window be the current window, and only allow */
  /* output in there.  Clear the window to start. */

  if (!echo_area_open_p) {
    push_window ();
    set_window (&echo_area);
    echo_area_open_p = true;
  }
  goto_xy (the_window.left, the_window.top);
  clear_eop ();
}

close_echo_area ()
{
  /* Return output to the previous window. */

  if (!echo_area_open_p) return;
  pop_window ();
  echo_area_open_p = false;
}

with_output_to_echo_area (function, arg1, arg2, arg3, arg4, arg5)
     FUNCTION *function;
{
  /* Do FUNCTION with output taking place in the echo area. */
  with_output_to_window (&echo_area, function, arg1, arg2, arg3, arg4, arg5);
}


clear_echo_area ()
{
  /* Clear the contents of the echo area. */

  new_echo_area ();
  close_echo_area ();
}

make_modeline ()
{
  /* Create and display the modeline. */
  extern int info_buffer_len;
  int width = modeline_window.right - modeline_window.left;

  sprintf(modeline, "Info: (%s)%s, %d lines",
	  current_info_file, current_info_node, nodelines);
  if (strnicmp
      (opsys_filename (current_info_file), last_loaded_info_file,
       strlen (last_loaded_info_file)) != 0) {
    sprintf (&modeline[strlen (modeline)], ", Subfile: %s", last_loaded_info_file);
  }

  if (strlen (modeline) < width) {
    int index = strlen (modeline);
    while (index != width) modeline[index++] = '-';
    modeline [index] = '\0';
  }

  if (strlen (modeline) > width)
    modeline[width] = '\0';
  push_window ();
  set_window (&modeline_window);
  goto_xy (the_window.left, the_window.top);

  if (terminal_inverse_begin) do_term (terminal_inverse_begin);
  print_string (modeline);
  if (terminal_inverse_begin) do_term (terminal_end_attributes);

  pop_window ();
}

boolean typing_out = false;

open_typeout ()
{
  /* Prepare to do output to the typeout window.  If the
     typeout window is already open, ignore this clown. */
  if (typing_out) return;

  push_window ();
  set_window (&terminal_window);
  goto_xy (the_window.ch, the_window.cv);
  typing_out = window_bashed = true;
}

close_typeout ()
{
  /* Close the currently open typeout window. */

  if (inhibit_output) inhibit_output = false;
  else {
    untyi_char = getc (stdin);
    if (untyi_char == SPACE) untyi_char = 0;
  }
  pop_window ();
  typing_out = false;
}

char *
xrealloc (pointer, bytes)
     char *pointer;
     int bytes;
{
  char *temp = (char *)realloc (pointer, bytes);
  if (!temp) {
    fprintf (stderr, "Out of virtual memory!\n");
    restore_io ();
    exit (2);
  }
  return (temp);
}

char *
xmalloc (bytes)
     int bytes;
{
  char *temp = (char *)malloc (bytes);
  if (!temp) {
    fprintf (stderr, "Out of virtual memory!\n");
    restore_io ();
    exit (2);
  }
  return (temp);
}
\f


/*
 * Local variables:
 * compile-command: "gcc -g -o info info.c -ltermcap"
 * end:
 */