diff options
Diffstat (limited to 'web/glasgow/lit2x-0.16/literate/info-utils/info.c')
-rw-r--r-- | web/glasgow/lit2x-0.16/literate/info-utils/info.c | 4744 |
1 files changed, 4744 insertions, 0 deletions
diff --git a/web/glasgow/lit2x-0.16/literate/info-utils/info.c b/web/glasgow/lit2x-0.16/literate/info-utils/info.c new file mode 100644 index 0000000000..941955c89c --- /dev/null +++ b/web/glasgow/lit2x-0.16/literate/info-utils/info.c @@ -0,0 +1,4744 @@ +/* info -- a stand-alone Info program. + + Copyright (C) 1987, 1991 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. */ + +/* This is GNU Info: + + Version 1.44 (Change "major_version" and "minor_version" below.) + Fri Feb 7 1992 +*/ + +/* partain: to go w/ "make world" */ +#if defined(SYSV) +#define USG 1 +#endif +/* end partain */ + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> +#include <pwd.h> +#include <errno.h> + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#include <ctype.h> +#include "getopt.h" + +#if defined (USG) + struct passwd *getpwnam (); +# include <fcntl.h> +# include <termio.h> +# include <string.h> + +# if defined (USGr3) +# if !defined (M_XENIX) +# include <sys/stream.h> +# include <sys/ptem.h> +# undef TIOCGETC +# else /* M_XENIX */ +# define tchars tc +# include <sys/ttold.h> +# endif /* M_XENIX */ +# endif /* USGr3 */ +# define bcopy(source, dest, count) memcpy(dest, source, count) + char *index(s,c) char *s; { char *strchr(); return strchr(s,c); } + char *rindex(s,c) char *s; { char *strrchr(); return strrchr(s,c); } +#else /* !USG */ +# include <sys/file.h> +# include <sgtty.h> +# include <strings.h> +#endif /* USG */ + +#if !defined (DEFAULT_INFOPATH) +# define DEFAULT_INFOPATH \ + ".:/usr/gnu/info:/usr/local/emacs/info:/usr/local/lib/emacs/info" +#endif /* !DEFAULT_INFOPATH */ + +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 (); +#define VOID_SIGHANDLER +#if defined (VOID_SIGHANDLER) +# define SigHandler void +#else +# define SigHandler int +#endif /* !VOID_SIGHANDLER */ +#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) ((unsigned char) (c) < control_character_threshold) +#define META_P(c) ((unsigned char) (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 NNODENAME 4 /* Default amount to grow nodename list by. */ +#define FILENAME_LEN 256 +#define NODENAME_LEN 256 +#define STRING_SIZE 256 +#define nodeend_sequence "\n\037" + +#define whitespace(c) ((c) == ' ' || (c) == '\t') +#define cr_whitespace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') + +/* All right, some windows stuff. */ + +typedef struct { + /* Absolute x and y coordinates for usable portion of this window. */ + int left, top, right, bottom; + /* Absolute cursor position in this window. */ + int ch, cv; +} WINDOW; + +typedef struct _wind_list { + int left, top, right, bottom; + int ch, cv; + struct _wind_list *next_window; +} WINDOW_LIST; + +WINDOW the_window = {0, 0, 80, 24, 0, 0}; +WINDOW_LIST *window_stack = (WINDOW_LIST *)NULL; +WINDOW terminal_window = {0, 0, 80, 24, 0, 0}; + +/* Not really extern, but defined later in this file. */ +extern WINDOW echo_area; +void *xmalloc (), *xrealloc (); +char *getenv (), *next_info_file (), *opsys_filename (); +int build_menu (), find_menu_node (); +void swap_filestack (), pop_filestack (); + +/* A crock, this should be done in a different way. */ +#define MAX_INDIRECT_FILES 100 + +/* The info history list. */ +NODEINFO *Info_History = NULL; + +/* ?Can't have more than xx files in the indirect list? */ +INDIRECT_INFO indirect_list[MAX_INDIRECT_FILES]; + +/* The filename of the currently loaded info file. */ +char current_info_file[FILENAME_LEN]; + +/* The nodename of the node the user is looking at. */ +char current_info_node[NODENAME_LEN]; + +/* The last file actually loaded. Not the same as current info file. */ +char last_loaded_info_file[FILENAME_LEN]; + +/* Offsets in info_file of top and bottom of current_info_node. */ +int nodetop, nodebot; + +/* Number of lines in this node. */ +int nodelines; + +/* Buffer for the info file. */ +char *info_file = NULL; + +/* Length of the above buffer. */ +int info_buffer_len; + +/* Pointer to the start of a tag table, or NULL to show none. */ +char *tag_table = NULL; + +/* Length of the above buffer. */ +int tag_buffer_len; + +/* Non-zero means that the tag table is indirect. */ +int indirect = 0; +int indirect_top; + +/* Offset in the buffer of the current pagetop. */ +int pagetop; + +/* Offset in the buffer of the last displayed character. */ +int pagebot = 0; + +/* 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 *infopath = NULL; + +/* If filled, the name of a file to write to. */ +char dumpfile[FILENAME_LEN] = ""; + +/* This is the command to print a node. A default value is compiled in, + or it can be found from the environment as $INFO_PRINT_COMMAND. */ +char *print_command; + +/* Non-zero means forst redisplay before prompt for the next command. */ +int window_bashed = 0; + +/* **************************************************************** */ +/* */ +/* Getting Started. */ +/* */ +/* **************************************************************** */ + +/* Begin the Info session. */ + +/* Global is on until we are out of trouble. */ +int totally_inhibit_errors = 1; + +/* Non-zero means print version info only. */ +int version_flag = 0; + +/* The major and minor versions of Info. */ +int major_version = 1; +int minor_version = 44; + +struct option long_options[] = { + { "directory", 1, 0, 'd' }, + { "node", 1, 0, 'n' }, + { "file", 1, 0, 'f' }, + { "output", 1, 0, 'o' }, + { "version", 0, &version_flag, 1 }, + {NULL, 0, NULL, 0} +}; + +#define savestring(x) (char *) strcpy ((char *) xmalloc (1 + strlen (x)), (x)) + +main (argc, argv) + int argc; + char **argv; +{ + int c, ind, no_such_node = 0; + char filename[FILENAME_LEN]; + char *nodename; + char **nodenames; + int nodenames_size, nodenames_index; + char *ptr, *env_infopath, *env_print_command; + + nodenames_index = 0; + nodenames = (char **) xmalloc ((nodenames_size = 1) * sizeof (char *)); + nodenames[0] = (char *)NULL; + + env_infopath = getenv ("INFOPATH"); + env_print_command = getenv ("INFO_PRINT_COMMAND"); + + filename[0] = '\0'; + + if (env_infopath && *env_infopath) + infopath = savestring (env_infopath); + else + infopath = savestring (DEFAULT_INFOPATH); + + if (env_print_command && *env_print_command) + print_command = savestring (env_print_command); + else + print_command = savestring (INFO_PRINT_COMMAND); + + while ((c = getopt_long (argc, argv, "d:n:f:o:", long_options, &ind)) != EOF) + { + if (c == 0 && long_options[ind].flag == 0) + c = long_options[ind].val; + switch (c) + { + case 0: + break; + + case 'd': + free (infopath); + infopath = savestring (optarg); + break; + + case 'n': + + if (nodenames_index + 2 > nodenames_size) + nodenames = (char **) + xrealloc (nodenames, (nodenames_size += 10) * sizeof (char *)); + + nodenames[nodenames_index++] = optarg; + nodenames[nodenames_index] = (char *)NULL; + break; + + case 'f': + strncpy (filename, optarg, FILENAME_LEN); + break; + + case 'o': + strncpy (dumpfile, optarg, FILENAME_LEN); + break; + + default: + usage (); + } + } + + /* If the user specified `--version' then simply show the version + info at this time and exit. */ + if (version_flag) + { + show_version_info (stdout); + exit (0); + } + + /* Okay, flags are parsed. Get possible Info menuname. */ + + if (*filename && (ptr = rindex (filename,'/')) != NULL ) + { + /* Add filename's directory to path. */ + char *temp; + + temp = (char *) xmalloc (2 + strlen (filename) + strlen (infopath)); + strncpy (temp, filename, ptr - filename); + sprintf (temp + (ptr - filename), ":%s", infopath); + free (infopath); + infopath = temp; + } + + /* Start with DIR or whatever was specified. */ + if (!get_node (filename, (nodenames[0] == NULL) ? "Top" : nodenames[0], 0)) + { + if (filename[0]) + { + fprintf (stderr, "Try just plain `info'.\n"); + exit (1); + } + else + { + strcpy (filename, "DIR"); + + if (!get_node ((char *)NULL, (char *)NULL, 1)) + { + fprintf (stderr, + "%s: Cannot find \"%s\", anywhere along the search ", + argv[0], filename); + fprintf (stderr, "path of\n\"%s\".\n", infopath); + exit (1); + } + } + } + + totally_inhibit_errors = 0; + + for (ind = 1 ; ind < nodenames_index ; ind++) + get_node (filename, nodenames[ind], 0); + + nodename = nodenames[nodenames_index > 0 ? nodenames_index - 1 : 0]; + if (!nodename) + { + nodename = (char *) xmalloc (NODENAME_LEN); + *nodename = '\0'; + } + + if (optind != argc) + { + while (optind != argc) + { + if (!build_menu ()) + { + display_error ("There is no menu in node \"%s\"", + current_info_node ); + no_such_node++; + break; + } + else if (!find_menu_node (argv[optind], nodename)) + { + display_error + ("There is no menu entry for \"%s\" in node \"%s\"", + argv[optind], nodename ); + no_such_node++; + break; + } + else if (!get_node ((char *)NULL, nodename, 0)) + { + no_such_node++; + break; + } + else + { +#if 1 + /* RMS says not to type this stuff out because he expects + programs to call Info instead of interactive users. */ + printf ("%s.. ",argv[optind]); + fflush (stdout); +#endif + optind++; + } + } + } + + /* If we are outputting to a file, and the node was not found, exit. */ + if (no_such_node && dumpfile[0]) + exit (1); + else + begin_info_session (); + + exit (0); +} + +usage () +{ + fprintf (stderr,"%s\n%s\n%s\n%s\n", +"Usage: info [-d dir-path] [-f info-file] [-n node-name -n node-name ...]", +" [-o output-file] [--directory dir-path] [--file info-file]", +" [--node node-name --node node-name ...] [--version]", +" [--output output-file] [menu-selection...]"); + exit (1); +} + +/* Print the version of info to standard output. */ +show_version_info (stream) + FILE *stream; +{ + fprintf (stream, "GNU Info version %d.%d\n", major_version, minor_version); + fflush (stream); +} + +#if defined (SIGTSTP) +Function *old_tstp; +Function *old_ttou, *old_ttin; +#endif /* SIGTSTP */ + +#if defined (SIGWINCH) +Function *old_winch; +#endif /* SIGWINCH */ + +/* Start using Info. */ +begin_info_session () +{ + SigHandler 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 (); + goto_xy (the_window.left, the_window.bottom + 1); + restore_io (); +} + +/* What to do before processing a stop signal. */ +before_stop_signal () +{ + restore_io (); +} + +/* What to do after processing a stop signal. */ +after_stop_signal () +{ + clear_screen (); + display_page (); + goto_xy (the_window.ch, the_window.cv); + opsys_init_terminal (); +} + +/* partain: moved these here */ +char *widest_line; + +int terminal_columns; /* {tc_int, "co" */ +int terminal_rows; /* {tc_int, "li" */ + +/* Do the right thing with this signal. */ +SigHandler +info_signal_handler (sig) + int sig; +{ + switch (sig) + { +#if defined (SIGTSTP) + case SIGTSTP: + case SIGTTOU: + case SIGTTIN: + before_stop_signal (); + signal (sig, SIG_DFL); +#if !defined (USG) + sigsetmask (sigblock (0) & ~sigmask (sig)); +#endif /* !USG */ + kill (getpid (), sig); + after_stop_signal (); + signal (sig, info_signal_handler); + break; +#endif /* SIGTSTP */ + +#if defined (SIGWINCH) + case SIGWINCH: + /* Window has changed. Get the size of the new window, and rebuild our + window chain. */ + { + int display_page (); + extern char *widest_line; + extern WINDOW terminal_window; + extern WINDOW_LIST *window_stack; + extern int terminal_rows, terminal_columns; + int delta_width, delta_height, right, bottom; + + right = get_terminal_columns (); + bottom = get_terminal_rows (); + + delta_width = right - terminal_columns; + delta_height = bottom - terminal_rows; + + terminal_columns = right; + terminal_rows = bottom; + + /* Save current window, whatever it is. */ + push_window (); + + /* Change the value of the widest_line. */ + free (widest_line); + widest_line = (char *) xmalloc (right); + + /* 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 ((WINDOW *)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); + } + + /* Clear and redisplay the entire terminal window. */ + set_window (&terminal_window); + clear_screen (); + + /* Redisplay the contents of the screen. */ + with_output_to_window (&terminal_window, display_page); + + /* Get back the current window. */ + pop_window (); + } + break; +#endif /* SIGWINCH */ + + case SIGINT: + restore_io (); + exit (1); + break; + } +} + +install_signals () +{ +#if defined (SIGTSTP) + old_tstp = (Function *) signal (SIGTSTP, info_signal_handler); + old_ttou = (Function *) signal (SIGTTOU, info_signal_handler); + old_ttin = (Function *) signal (SIGTTIN, info_signal_handler); +#endif /* SIGTSTP */ + +#if defined (SIGWINCH) + old_winch = (Function *) signal (SIGWINCH, info_signal_handler); +#endif /* SIGWINCH */ + + 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; + wind->ch += delta_width; + wind->cv += delta_height; + + /* Ugly hack to fix busted windows code. If the window we are adjusting + already has a TOP offset, then adjust that also. */ + if (wind->top) + wind->top += delta_height; +} + +/* **************************************************************** */ +/* */ +/* 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 non-zero. 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 zero if the user types the + abort character. LINE is returned with a '\0' at the end, not a '\n'. */ +int +readline (prompt, line, maxchars, completing) + char *prompt, *line; + int maxchars; + int completing; +{ + int character; + int readline_ch, readline_cv; + int current_len = strlen (line); + int just_completed = 0; /* Have we just done a completion? */ + int meta_flag = 0; + + new_echo_area (); + + if (prompt) + print_string ("%s", prompt); + + readline_ch = the_window.ch; + readline_cv = the_window.cv; + + print_string ("%s", line); + + while (1) + { + line[current_len] = '\0'; + goto_xy (readline_ch, readline_cv); + print_string ("%s", line); + clear_eol (); + + if (just_completed) + just_completed--; + + 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 (1); + } + else + { + current_len = strlen (line); + break; + } + } + else + { + close_echo_area (); + return (1); + } + + case ABORT_CHAR: + ding (); + + if (current_len) + { + current_len = 0; + } + else + { + close_echo_area (); + clear_echo_area (); + return (0); + } + break; + + case ' ': + case '\t': + case '?': + if (completing) + { + extern int completion_count; + + if (character == '?' || just_completed) + { + help_possible_completions (line); + break; + } + else + { + char temp_line[NODENAME_LEN]; + strcpy (temp_line, line); + try_complete (line); just_completed = 2; + 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. Because the SPC, TAB and `?' get + treated as normal characters by falling through the + "if (completing)" test above. */ + default: + if (!CTRL_P (character) && + !META_P (character) && + current_len < maxchars) + line[current_len++] = character; + else + ding (); + } + } +} + +/* Initialize whatever the completer is using. */ +init_completer () +{ + 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 the possible completion passed in POINTER on the + completions list. */ +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; +} + +/* 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 +complete (text, list) + char *text; + COMP_ENTRY *list; +{ + int low_match, i, idx; + int string_length = strlen (text); + + init_completer (); + low_match = 100000; /* Some large number. */ + + while (list) + { + if (strnicmp (text, list->identifier, string_length) == 0) + remember_completion (list); + list = list->next; + } + + if (completion_count == 0) + return (0); + + if (completion_count == 1) + { /* One completion */ + strcpy (text, completion_id (0)); + return (1); + } + + /* Else find the least common denominator */ + + idx = 1; + + while (idx < completion_count) + { + int c1, c2; + for (i = 0; + (c1 = to_lower (completion_id (idx - 1)[i])) && + (c2 = to_lower (completion_id (idx)[i])); + i++) + if (c1 != c2) + break; + + if (low_match > i) + low_match = i; + idx++; + } + + strncpy (text, completion_id (0), low_match); + text[low_match] = '\0'; + return (1); +} + +/* Complete TEXT from the completion structures in COMPLETION_LIST. */ +int +try_complete (text) + char *text; +{ + return (complete (text, completion_list)); +} + +/* The function that prints out the possible completions. */ +help_possible_completions (text) + char *text; +{ + 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 ("%s", completion_id(0)); + goto print_done; + } + + if (completion_count >= completion_query_threshold) + { + 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 idx = 0; + int counter = 0; + int columns = (the_window.right - the_window.left) / 30; + + while (idx < completion_count) + { + if (counter == columns) + { + charout ('\n'); + counter = 0; + } + indent_to (counter * 30); + print_string ("%s", completion_id (idx)); + counter++; + idx++; + } + } + +print_done: + print_string ("\n\n-----------------\n"); + close_typeout (); +} + +/* Return the next file that should be searched, or NULL if we are at the end + of the info file. If the FILE argument is provided, begin the search there, + if REWIND is non-zero start the search at the beginning of the list. + + The list is the one built by an indirect tag table, on the supposition + that those files form a logical set to search if we are in one of them. + If no such list is current (either it doesn't exist, or FILE isn't on + it) the search list is set to be last_loaded_info_file */ +char * +next_info_file (file, rewind) + char *file; /* file to set `next' to. May be NULL. */ + int rewind; /* should I rewind the file list? */ +{ + static int index = -1; + + if (file != NULL) + { + char *ptr = rindex (file,'/'); + + if (ptr != NULL) + file = ptr + 1; + + for (index = 0; + index < MAX_INDIRECT_FILES && + indirect_list[index].filename != (char *)NULL; + index++) + { + if (strcmp (file, indirect_list[index].filename) == 0) + return (file); + } + + /* OK, we are not on the current indirect_list. This means that + we have switched to another node that has no indirect list, + so forget the old one. */ + 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; + } + return (indirect_list[0].filename = savestring (file)); + } + else if (rewind) + { + index = 0; + + if (indirect_list[0].filename == (char *)NULL) + indirect_list[0].filename = savestring (last_loaded_info_file); + } + else + index++; + + if (index < MAX_INDIRECT_FILES && + indirect_list[index].filename != (char *)NULL) + return (indirect_list[index].filename); + + index = -1; + return (NULL); +} + +/* **************************************************************** */ +/* */ +/* 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. */ + +/* Return the directory portion of FILENAME, i.e., everything before the + last slash. */ +static char * +file_directory (filename) + char *filename; +{ + register char *scan; + register int length; + char *result; + + scan = filename; + + while (*scan++ != '\0'); + + while (1) + { + if (scan == filename) + break; + + if ((*--scan) == '/') + { + scan++; + break; + } + } + + length = scan - filename; + result = (char *) xmalloc (length + 1); + strncpy (result, filename, length); + result[length] = '\0'; + + return (result); +} + +/* Given FILENAME and DIRECTORY return a newly allocated string which + is either the two concatenated, or simply FILENAME if it is absolute + already. */ +static char * +file_absolutize (filename, directory) + char *filename, *directory; +{ + register int filename_len, directory_len; + char *result; + + if (filename[0] == '/') + return (savestring (filename)); + + filename_len = strlen (filename); + directory_len = strlen (directory); + result = (char *) xmalloc (directory_len + filename_len + 1); + + strcpy (result, directory); + strcat (result, filename); + + return (result); +} + +/* Load FILENAME. If REMEMBER_NAME is non-zero, then remember the + loaded filename in CURRENT_INFO_FILE. In either case, remember + the name of this file in LAST_LOADED_INFO_FILE. */ +int +get_info_file (filename, remember_name) + char *filename; + int remember_name; +{ + FILE *input_stream; + struct stat file_info; + int pointer, result; + char tempname[FILENAME_LEN]; + + /* Get real filename. */ + strcpy (tempname, opsys_filename (filename)); + + /* If the file doesn't exist, try again with the name in lower case. */ + result = stat (tempname, &file_info); + + if (result < 0) + { + register int i; + char *lowered_name; + + lowered_name = (char *)xmalloc (1 + strlen (filename)); + + for (i = 0; lowered_name[i] = to_lower (filename[i]); i++) + ; + + strcpy (tempname, opsys_filename (lowered_name)); + result = stat (tempname, &file_info); + } + + /* See if this file is the last loaded one. */ + if (!result && (strcmp (last_loaded_info_file, tempname) == 0)) + return (1); + + /* Now try to open the file. */ + if (result || (input_stream = fopen (tempname, "r")) == NULL) + { + file_error (tempname); + return (0); + } + + /* 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 idx; + indirect = 0; + free (tag_table); + } + } + else + return (1); + + /* Force redisplay, since we are looking at a new file. */ + window_bashed = 1; + + /* 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 = 0; + 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 file's + 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; + display_error ("Start of INDIRECT tag table not found!"); + return (0); + } + + /* Remember the filenames, and their byte offsets. */ + { + /* Index into the filename/offsets array. */ + int idx, temp_first_byte; + char temp_filename[FILENAME_LEN]; + char *directory = file_directory (tempname); + + info_buffer_len = indirect_top; + + /* For each line, scan the info into globals. Then save + the information in the INDIRECT_INFO structure. */ + + for (idx = 0; idx < MAX_INDIRECT_FILES && + indirect_list[idx].filename != (char *) NULL; + idx++) + { + free (indirect_list[idx].filename); + indirect_list[idx].filename = (char *) NULL; + } + + for (idx = 0;info_file[indirect_top] != info_separator_char && + idx < 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[idx].filename = + file_absolutize (temp_filename, directory); + indirect_list[idx].first_byte = temp_first_byte; + idx++; + } + } + + free (directory); + + /* Terminate the table. */ + if (idx == MAX_INDIRECT_FILES) + { + display_error + ("Sorry, the INDIRECT file array isn't large enough."); + idx--; + } + indirect_list[idx].filename = (char *) NULL; + } + indirect = 1; + } else { + ; + } + } + } + return (1); +} + +/* Make current_info_node be NODENAME. This could involve loading + a file, etc. POPPING is non-zero if we got here because we are + popping one level. */ +int +get_node (filename, nodename, popping) + char *nodename, *filename; + int popping; +{ + 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 == '(') + { + int temp = 1, temp1 = 0; + char character; + + filename = internal_filename; + + while ((character = nodename[temp]) && character != ')') + { + filename[temp - 1] = character; + temp++; + } + filename[temp - 1] = '\0'; + temp++; /* skip the closing ')' */ + + /* We have the filename now. The nodename follows. */ + internal_nodename[0] = '\0'; + + while (nodename[temp] == ' ' || + nodename[temp] == '\t' || + nodename[temp] == '\n') + temp++; + + if (nodename[temp]) + while (internal_nodename[temp1++] = nodename[temp++]) + ; + else if (*filename != '\0') + strcpy (internal_nodename,"Top"); + + 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, 1)) + goto node_not_found; + + if (strcmp (nodename, "*") == 0) + { + /* The "node" that we want is the entire file. */ + pointer = 0; + goto found_node; + } + + /* If we are using a tag table, see if we can find the nodename in it. */ + if (tag_table) + { + pointer = find_node_in_tag_table (nodename, 0); + if (pointer < 1) + { + int pop_node (); + + /* The search through the tag table failed. Maybe we + should try searching the buffer? Nahh, just barf. */ + node_not_found: + if (popping) + return (0); /* Second time through. */ + + { + int save_inhibit = totally_inhibit_errors; + + totally_inhibit_errors = 0; + display_error + ("Sorry, unable to find the node \"%s\" in the file \"%s\".", + nodename, filename); + totally_inhibit_errors = save_inhibit; + } + + 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, 1); + return (0); + } + + /* 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 idx; + for (idx = 0; idx < MAX_INDIRECT_FILES && + indirect_list[idx].filename != (char *) NULL; idx++) + { + if (indirect_list[idx].first_byte > pointer) + { + /* We found it. */ + break; + } + } + if (!get_info_file (indirect_list[idx - 1].filename, 1)) + goto node_not_found; + pointer -= indirect_list[idx - 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: Checking25796 */ + } + } + else + { +#if defined (NOTDEF) + /* We don't have a tag table. The node can only be found by + searching this file in its entirety. */ + if (!get_info_file (filename, 1)) + return (0); +#endif /* NOTDEF */ + 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 its 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 (1); +} + +/* 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. */ +get_node_extent () +{ + int idx = nodetop; + int character; + int do_it_till_end = (strcmp (current_info_node, "*") == 0); + + nodelines = 0; + +again: + while ((idx < info_buffer_len) && + ((character = info_file[idx]) != info_separator_char)) + { + if (character == '\n') + nodelines++; + idx++; + } + if (do_it_till_end && idx != info_buffer_len) + { + idx++; + goto again; + } + nodebot = idx; +} + +/* 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 NODENAME in TAG_TABLE. */ +find_node_in_tag_table (nodename, offset) + char *nodename; + int offset; +{ + int temp; + + set_search_constraints (tag_table, tag_buffer_len); + + temp = offset; + while (1) + { + 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 NODENAME in INFO_FILE. */ +find_node_in_file (nodename, offset) + char *nodename; + int offset; +{ + int temp, last_offset = -1; + + set_search_constraints (info_file, info_buffer_len); + + while (1) + { + offset = find_node_start (offset); + + if (offset == last_offset) + offset = -1; + else + last_offset = 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 == '.' || + check_exact == '\n') + 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; +{ + register int i; + char *temp; + + temp = (char *) xmalloc (strlen (starter) + 10); + sprintf (temp, "%s-%d", starter, getpid ()); + + for (i = 0; temp[i]; i++) + if (!isalnum (temp[i])) + temp[i] = '-'; + + return (temp); +} + +/* Delete a file. Print errors if necessary. */ +deletefile (filename) + char *filename; +{ + if (unlink (filename) != 0) + { + file_error (filename); + return (1); + } + return (0); +} + +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 c, i = nodetop; + FILE *output_stream; + + if (strcmp (filename, "-") == 0) + output_stream = stdout; + else + output_stream = fopen (filename, "w"); + + if (output_stream == (FILE *) NULL) + { + file_error (filename); + return (1); + } + + while (i < nodebot && i < info_buffer_len) + { + c = info_file[i]; + if (CTRL_P (c) && !(index ("\n\t\f", c))) + { + putc ('^', output_stream); + c = UNCTRL (c); + } + + if (putc (c, output_stream) == EOF) + { + if (output_stream != stdout) + fclose (output_stream); + + file_error (filename); + return (1); + } + i++; + } + + if (output_stream != stdout) + 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]; + +static int search_start = 0; + +/* The default prompt string for the Follow Reference command. */ +char *visible_footnote = (char *)NULL; +toploop () +{ + int done, inhibit_display; + int command, last_command; + int last_pointer, count, new_ypos, last_pagetop; + char nodename[NODENAME_LEN]; + + done = inhibit_display = 0; + command = last_command = 0; + new_ypos = last_pagetop = -1; + + while (!done) + { + if (!inhibit_display && + (window_bashed || (pagetop != last_pagetop))) + display_page (); + + inhibit_display = window_bashed = 0; + last_pagetop = pagetop; + + nodename[0] = '\0'; /* Don't display old text in input line. */ + + last_command = command; + + if (last_command == 'S') + cursor_to (search_start); + else + goto_xy (echo_area.left, echo_area.top); + + command = blink_cursor (); + clear_echo_area (); + + if (command == EOF) + { + done = 1; + continue; + } + command = to_upper (command); + + switch (command) + { + case 'D': + get_node ((char *) NULL, "(dir)Top", 0); + break; + + case 'H': + if ((the_window.bottom - the_window.top) < 24) + get_node ((char *) NULL, "(info)Help-Small-Screen", 0); + else + get_node ((char *) NULL, "(info)Help", 0); + break; + + case 'N': + if (!next_node ()) + { + display_error ("No NEXT for this node!"); + inhibit_display = 1; + } + break; + + case 'P': + if (!prev_node ()) + { + display_error ("No PREV for this node!"); + inhibit_display = 1; + } + break; + + case 'U': + { + int savetop = pagetop; + + if (!up_node ()) + { + display_error ("No UP for this node!"); + inhibit_display = 1; + pagetop = savetop; + } + break; + } + + case 'M': + if (!build_menu ()) + { + display_error ("No menu in this node!"); + inhibit_display = 1; + break; + } + + if (!readline ("Menu item: ", nodename, NODENAME_LEN, 1)) + { + clear_echo_area (); + inhibit_display = 1; + 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 = 1; + break; + } + + if (get_node ((char *) NULL, nodename, 0)) + clear_echo_area (); + break; + + case 'F': + { + char footnote[NODENAME_LEN]; + + if (!build_notes ()) + { + display_error ("No cross-references in this node!"); + inhibit_display = 1; + break; + } + + strcpy (footnote, visible_footnote); + if (!readline ("Follow reference: ", footnote, NODENAME_LEN, 1)) + { + inhibit_display = 1; + 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 = 1; + break; + } + + if (get_node ((char *)NULL, nodename, 0)) + 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, 1)) + { + pagetop = ptop; + } + else + inhibit_display = 1; + break; + } + + case SPACE: + case CTRL ('V'): + if (!next_page ()) + { + display_error ("At last page of this node now!"); + inhibit_display = 1; + } + break; + + case META ('V'): + case DELETE: + if (!prev_page ()) + { + display_error ("At first page of this node now!"); + inhibit_display = 1; + } + break; + + case 'B': + if (pagetop == nodetop) + { + display_error ("Already at beginning of this node!"); + inhibit_display = 1; + } + 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 = 1; + break; + } + + if (item > the_menu_size) + { + display_error ("There are only %d items in the menu!", + the_menu_size); + inhibit_display = 1; + break; + } + + if (!get_menu (item)) + inhibit_display = 1; + } + break; + + case 'G': + if (!readline ("Goto node: ", nodename, NODENAME_LEN, 0)) + { + inhibit_display = 1; + break; + } + + if (get_node ((char *) NULL, nodename, 0)) + clear_echo_area (); + break; + + /* Search from the starting position forward for a string. + Select the node containing the desired string. Put the + top of the page screen_lines / 2 lines behind it, but not + before nodetop. */ + case 'S': + { + int pointer, temp; + char prompt[21 + NODENAME_LEN + 1]; + static char search_string[NODENAME_LEN] = ""; + static char *starting_filename = NULL, + *starting_nodename = NULL; + static int starting_pagetop = 0; + static int wrap_search = 0; + + sprintf (prompt, "Search for string [%s]: ", search_string); + + if (!readline (prompt , nodename, NODENAME_LEN, 0)) + { + inhibit_display = 1; + break; + } + + /* If the user defaulted the search string, and the previous + command was search, then this is a continuation of the + previous search. */ + if (((strcmp (nodename, search_string) == 0) || + (!*nodename && *search_string)) && + (last_command == 'S')) + { + search_start++; + } + else + { + /* Initialize the start of a new search. */ + if (starting_filename) + free (starting_filename); + + starting_filename = savestring (last_loaded_info_file); + + if (starting_nodename) + free (starting_nodename); + + starting_nodename = savestring (nodename ? nodename : ""); + + starting_pagetop = pagetop; + search_start = pagetop; + wrap_search = 0; + + if (*nodename != '\0') + strcpy (search_string, nodename); + } + + I_goto_xy (echo_area.left, echo_area.top); + + { + static int pushed = 0; /* How many files are pushed? */ + int found_string = 0; /* Did we find our string? */ + + if (wrap_search) + { + push_filestack (next_info_file ((char *)NULL, 1), 0); + pushed++; + search_start = 0; + } + + for (;;) + { + set_search_constraints (info_file, info_buffer_len); + pointer = search_forward (search_string, search_start); + + if (pointer != -1) + { + found_string = 1; + break; + } + else + { + char *next_file; + + next_file = next_info_file ((char *)NULL, 0); + + if (next_file != NULL) + { + if (pushed) + { + pop_filestack (); + pushed--; + } + + push_filestack (next_file, 0); + pushed++; + search_start = 0; +#if 1 + I_goto_xy (echo_area.left, echo_area.top); + print_string ("Searching file %s...\n", next_file); +#endif + continue; + } + + if (wrap_search) + { + display_error + ("\"%s\" not found!", + search_string); + + inhibit_display = 1; + wrap_search = 0; + + if (pushed) + { + pop_filestack (); + pushed--; + } + break; + } + else + { + display_error ("Search: End of file"); + inhibit_display = 1; + wrap_search = 1; + + if (pushed) + { + pop_filestack (); + pushed--; + } + break; + } + } + } + + if (pushed) + { + swap_filestack (); + pop_filestack (); + pushed--; + } + + if (!found_string) + break; + + wrap_search = 0; + temp = search_backward (start_of_node_string, pointer); + + if (temp != -1) + { + search_start = pointer; + pointer = forward_lines (1, temp); + } + + if (temp == -1 || !extract_field ("Node:", nodename, pointer)) + { + display_error + ("There doesn't appear to be a nodename for this node."); + get_node ((char *)NULL, "*", 0); + pagetop = pointer; + break; + } + + /* Get the node if it is different than the one already + loaded. */ + if (strcmp (nodename, starting_nodename) != 0) + { + free (starting_nodename); + starting_nodename = savestring (nodename); + + if (get_node ((char *) NULL, nodename, 0)) + clear_echo_area (); + } + + /* Reset the top of page if necessary. */ + { + if ((strcmp (last_loaded_info_file, starting_filename) != 0) || + (starting_pagetop != pagetop) || + (search_start > pagebot)) + { + pointer = + back_lines ((the_window.bottom - the_window.top) / 2, + forward_lines (1, search_start)); + + if (pointer < nodetop) + pointer = nodetop; + + pagetop = pointer; + window_bashed = 1; + } + else + inhibit_display = 1; + } + break; + } + } + + case CTRL ('H'): + case '?': + help_use_info (); + last_pagetop = -1; + break; + + case 'Q': + done = 1; + break; + + case CTRL ('L'): /* Control-l is redisplay. */ + window_bashed = 1; + if (last_command == 'S') + command = 'S'; + break; + + case '(': /* You *must* be trying to type a complete nodename. */ + strcpy (nodename, "("); + if (!readline ("Goto node: ", nodename, NODENAME_LEN, 0)) + { + inhibit_display = 1; + clear_echo_area (); + break; + } + I_goto_xy (echo_area.left, echo_area.top); + if (get_node ((char *) NULL, nodename, 0)) + 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 = 1; + free (tempname); + } + break; + + default: + inhibit_display = 1; + display_error ("Unknown command! Press '?' for help."); + + } + } +} + +/* Return the screen column width that the line from START to END + requires to display. */ +line_length (start, end) + int start, end; +{ + int count = 0; + + while (start < end) + { + if (info_file[start] == '\t') + count += 7 - (count % 8); + else if (CTRL_P (info_file[start])) + count += 2; + else + count++; + + start++; + } + + return (count); +} + +/* Tell this person how to use Info. */ +help_use_info () +{ + open_typeout (); + clear_screen (); + 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\ +d Move to the `directory' node. Equivalent to `gDIR'.\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 `%s'.\n\ +\n\ +Done.\n\n",print_command); + close_typeout (); +} + +/* Move to the node specified in the NEXT field. */ +int +next_node () +{ + char nodename[NODENAME_LEN]; + + if (!extract_field ("Next:", nodename, nodetop)) + return (0); + return (get_node ((char *) NULL, nodename, 0)); +} + +/* Move to the node specified in the PREVIOUS field. */ +int +prev_node () +{ + char nodename[NODENAME_LEN]; + + if (!extract_field ("Previous:", nodename, nodetop) + && !extract_field ("Prev:", nodename, nodetop)) + return (0); + return (get_node ((char *) NULL, nodename, 0)); +} + +/* Move to the node specified in the UP field. */ +int +up_node () +{ + char nodename[NODENAME_LEN]; + + if (!extract_field ("Up:", nodename, nodetop)) + return (0); + return (get_node ((char *) NULL, nodename, 0)); +} + +/* Build a completion list of menuname/nodename for each + line in this node that is a menu item. */ +int +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 (); + the_menu_size = 0; + + set_search_constraints (info_file, nodebot); + if ((pointer = search_forward (MENU_HEADER, nodetop)) < 0) + return (0); + + /* There is a menu here. Look for members of it. */ + pointer += strlen (MENU_HEADER); + + while (1) + { + int idx; + + pointer = search_forward (MENU_ID, pointer); + if (pointer < 0) + break; /* No more menus in this node. */ + + pointer = (skip_whitespace (pointer + strlen (MENU_ID))); + + idx = 0; + while ((menuname[idx] = info_file[pointer]) && menuname[idx] != ':') + idx++, pointer++; + + menuname[idx] = '\0'; + pointer++; + + if (info_file[pointer] == ':') + { + strcpy (nodename, menuname); + } + else + { + int in_parens; + + pointer = skip_whitespace (pointer); + idx = in_parens = 0; + + while ((nodename[idx] = info_file[pointer]) && + (in_parens || + (nodename[idx] != '\t' && + nodename[idx] != '.' && + nodename[idx] != ','))) + { + if (nodename[idx] == '(') + in_parens++; + else if (nodename[idx] == ')') + in_parens--; + + idx++, pointer++; + } + nodename[idx] = '\0'; + } + add_completion (menuname, nodename); + the_menu_size++; + } + if (the_menu_size) + completion_list = reverse_list (completion_list); + return (the_menu_size != 0); +} + +/* Select ITEMth item from a list built by build_menu (). */ +int +get_menu (item) + int item; +{ + if (!build_menu ()) + return (0); + + if (item > the_menu_size) + return (0); + else + { + COMP_ENTRY *temp = completion_list; + + while (--item && temp) + temp = temp->next; + + return (get_node ((char *) NULL, temp->data, 0)); + } +} + +/* Scan through the ?already? built menu list looking + for STRING. If you find it, put the corresponding nodes + name in NODENAME. */ +int +find_menu_node (string, nodename) + char *string, *nodename; +{ + return (scan_list (string, nodename)); +} + +/* The work part of find_menu_node and find_note_node. */ +int +scan_list (string, nodename) + char *string, *nodename; +{ + COMP_ENTRY *temp = completion_list; + + while (temp) + { + if (stricmp (string, temp->identifier, strlen (string)) == 0) + { + strcpy (nodename, temp->data); + return (1); + } + temp = temp->next; + } + return (0); +} + +/* Remove <CR> and whitespace from string, replacing them with + only one space. Exception: <CR> at end of string disappears. */ +clean_up (string) + char *string; +{ + char *to; + + /* Skip all whitespace characters found at the start of STRING. */ + while (whitespace (*string)) + string++; + + to = string; + + while (*to = *string++) + { + if (*to == '\n' || *to == ' ') + { + *to = ' '; + + while (*string == ' ' || *string == '\t') + string++; + } + to++; + } +} + +/* Find a reference to "*Note". Return the offset of the start + of that reference, or -1. */ +find_footnote_ref (from) + int from; +{ + while (1) + { + 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); + } +} + +/* Build an array of (footnote.nodename) for each footnote in this node. */ +int +build_notes () +{ + int pointer; + char notename[NODENAME_LEN]; + char nodename[NODENAME_LEN]; + + set_search_constraints (info_file, nodebot); + + if ((find_footnote_ref (nodetop)) < 0) + return (0); + pointer = nodetop; + + menus_filename[0] = menus_nodename[0] = '\0'; + visible_footnote = ""; + free_completion_list (); + + while (1) + { + int idx; + + pointer = find_footnote_ref (pointer); + if (pointer < 0) + break; /* no more footnotes in this node. */ + + pointer = skip_whitespace_and_cr (pointer); + idx = 0; + + while ((notename[idx] = info_file[pointer]) && notename[idx] != ':') + { + idx++, pointer++; + } + + notename[idx] = '\0'; + clean_up (notename); + pointer++; + if (info_file[pointer] == ':') + { + strcpy (nodename, notename); + } + else + { + int in_parens = 0; + + pointer = skip_whitespace (pointer); + idx = 0; + + while ((nodename[idx] = info_file[pointer]) && + (in_parens || + (nodename[idx] != '\t' && + nodename[idx] != '.' && + nodename[idx] != ','))) + { + if (nodename[idx] == '(') + in_parens++; + else if (nodename[idx] == ')') + in_parens--; + + idx++, pointer++; + } + nodename[idx] = '\0'; + clean_up (nodename); + } + /* 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); +} + +/* Scan through the ?already? built footnote list looking for STRING. + If found, place the corresponding node name in NODENAME. */ +int +find_note_node (string, nodename) + char *string, *nodename; +{ + return (scan_list (string, nodename)); +} + +/* **************************************************************** */ +/* */ +/* Page Display */ +/* */ +/* **************************************************************** */ + + +/* The display functions for GNU Info. */ +int display_ch, display_cv; +int display_point; + +/* Display the current page from pagetop down to the bottom of the + page or the bottom of the node, whichever comes first. */ +display_page () +{ + display_point = pagetop; + display_ch = the_window.left; + display_cv = the_window.top; + 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. */ +generic_page_display () +{ + int done_with_display = 0; + int character; + + goto_xy (display_ch, display_cv); + + while (!done_with_display) + { + if (display_point == nodebot) + { + clear_eop (); + goto display_finish; + } + + character = info_file[display_point]; + + 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: + pagebot = display_point; + make_modeline (); + done_with_display++; + continue; + } + else + display_point++; + } + fflush (stdout); +} + +/* Display character carefully, ensuring that no scrolling takes + place, even in the case of funky control characters. */ +display_carefully (character) + int character; +{ + 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); +} + +/* Move the cursor to POSITION in page. Return non-zero if successful. */ +cursor_to (position) + int position; +{ + int ch, cv, character; + int point; + + if (position > pagebot || position < pagetop) + return (0); + + point = pagetop; + ch = the_window.left; + cv = the_window.top; + + while (point < position) + { + character = info_file[point++]; + + ch += display_width (character, ch); + + if (ch >= the_window.right) + { + ch = ch - the_window.right; + cv++; + + if (cv >= the_window.bottom) + return (0); + } + } + goto_xy (ch, cv); + return (1); +} + +/* Move to the next page in this node. Return 0 if + we can't get to the next page. */ +int +next_page () +{ + int pointer; + + pointer = + forward_lines ((the_window.bottom - the_window.top) - 2, pagetop); + + if (pointer >= nodebot) + return (0); + + /* Hack for screens smaller than displayed line width. */ + if (pointer > display_point) + { + pointer = display_point; + back_lines (1); + } + pagetop = pointer; + return (1); +} + +/* Move to the previous page in this node. Return zero if + there is no previous page. */ +int +prev_page () +{ + int pointer = + back_lines ((the_window.bottom - the_window.top) - 2, pagetop); + + if (pagetop == nodetop) + return (0); + + if (pointer < nodetop) + pointer = nodetop; + + pagetop = pointer; + return (1); +} + + +/* **************************************************************** */ +/* */ +/* Utility Functions */ +/* */ +/* **************************************************************** */ + +char *search_buffer; /* area in ram to scan through. */ +int buffer_bottom; /* Length of this area. */ + +/* Set the global variables that all of these routines use. */ +set_search_constraints (buffer, extent) + char *buffer; + int extent; +{ + search_buffer = buffer; + buffer_bottom = extent; +} + +/* Move back to the start of this line. */ +to_beg_line (from) + int from; +{ + while (from && search_buffer[from - 1] != '\n') + from--; + return (from); +} + +/* Move forward to the end of this line. */ +to_end_line (from) +{ + while (from < buffer_bottom && search_buffer[from] != '\n') + from++; + return (from); +} + +/* Move back count lines in search_buffer starting at starting_pos. + Returns the start of that line. */ +back_lines (count, starting_pos) + int count, starting_pos; +{ + starting_pos = to_beg_line (starting_pos); + while (starting_pos && count) + { + starting_pos = to_beg_line (starting_pos - 1); + count--; + } + return (starting_pos); +} + +/* Move forward count lines starting at starting_pos. + Returns the start of that line. */ +forward_lines (count, starting_pos) + int count, starting_pos; +{ + 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 for STRING in SEARCH_BUFFER starting at STARTING_POS. + Return the location of the string, or -1 if not found. */ +search_forward (string, starting_pos) + char *string; + int starting_pos; +{ + register int c, i, len; + register char *buff, *end; + char *alternate; + + + /* We match characters in SEARCH_BUFFER against STRING and ALTERNATE. + ALTERNATE is a case reversed version of STRING; this is cheaper than + case folding each character before comparison. */ + + /* Build the alternate string. */ + alternate = savestring (string); + len = strlen (string); + + for (i = 0; i < len; i++) + { + c = alternate[i]; + + if (c >= 'a' && c <= 'z') + alternate[i] = c - 32; + else if (c >= 'A' && c <= 'Z') + alternate[i] = c + 32; + } + + buff = search_buffer + starting_pos; + end = search_buffer + buffer_bottom + 1; + + while (buff < end) + { + for (i = 0; i < len; i++) + { + c = buff[i]; + + if (c != string[i] && c != alternate[i]) + break; + } + + if (!string[i]) + { + free (alternate); + return (buff - search_buffer); + } + + buff++; + } + + free (alternate); + return (-1); +} + +/* Search for STRING in SEARCH_BUFFER starting at STARTING_POS. + Return the location of the string, or -1 if not found. */ +search_backward (string, starting_pos) + char *string; + int starting_pos; +{ + 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); +} + +/* Only search for STRING from POINTER to end of line. Return offset + of string, or -1 if not found. */ +string_in_line (string, pointer) + char *string; + int pointer; +{ + 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 characters at OFFSET in SEARCH_BUFFER. + Return the next non-whitespace character or -1 if BUFFER_BOTTOM + is reached. */ +skip_whitespace (offset) + int offset; +{ + int character; + + while (offset < buffer_bottom) + { + character = search_buffer[offset]; + if (character == ' ' || character == '\t') + offset++; + else + return (offset); + } + return (-1); +} + +/* 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. */ +skip_whitespace_and_cr (offset) + int offset; +{ + while (1) + { + offset = skip_whitespace (offset); + if (offset > 0 && search_buffer[offset] != '\n') + return (offset); + else + 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 +extract_field (field_name, nodename, offset) + char *field_name, *nodename; + int offset; +{ + int temp, character; + + temp = string_in_line (field_name, offset); + if (temp < 0) + return (0); + + 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 (1); +} + +/* Return non-zero if pointer is exactly at string, else zero. */ +int +looking_at (string, pointer) + char *string; + int pointer; +{ + if (strnicmp (search_buffer + pointer, string, strlen (string)) == 0) + return (1); + else + return (0); +} + +/* File stack stuff. This is currently only used to push one file while + searching indirect files, but we may as well write it in full + generality. */ +typedef struct filestack +{ + struct filestack *next; + char filename[FILENAME_LEN]; + char current_filename[FILENAME_LEN]; + char *tag_table; + char *info_file; + int info_buffer_len; +} FILESTACK; + +FILESTACK *filestack = NULL; + +int +push_filestack (filename, remember_name) + char *filename; + int remember_name; +{ + FILESTACK *element = (FILESTACK *) xmalloc (sizeof (FILESTACK)); + + element->next = filestack; + filestack = element; + + strcpy (filestack->filename, last_loaded_info_file); + strcpy (filestack->current_filename, current_info_file); + filestack->tag_table = tag_table; + filestack->info_file = info_file; + filestack->info_buffer_len = info_buffer_len; + + *last_loaded_info_file = '\0'; /* force the file to be read */ + info_file = (char *)NULL; /* Pretend we have no buffer. */ + if (get_info_file (filename, remember_name)) + { + return (1); + } + else + { + pop_filestack (); + return (0); + } +} + +void +pop_filestack () +{ + FILESTACK *temp; + + if (filestack == NULL) + { + fprintf (stderr , "File stack is empty and can't be popped\n"); + brians_error (); + return; + } + + free (info_file); + + strcpy (last_loaded_info_file, filestack->filename); + strcpy (current_info_file, filestack->current_filename); + tag_table = filestack->tag_table; + info_file = filestack->info_file; + info_buffer_len = filestack->info_buffer_len; + set_search_constraints (info_file, info_buffer_len); + + temp = filestack; + filestack = filestack->next; + free ((char *)temp); +} + +/* Swap the current info file with the bottom of the filestack */ +void +swap_filestack () +{ + char t_last_loaded_info_file[FILENAME_LEN]; + char t_current_info_file[FILENAME_LEN]; + char *t_tag_table; + char *t_info_file; + int t_info_buffer_len; + + if (filestack == NULL) + { + fprintf (stderr , "File stack is empty and can't be swapped\n"); + brians_error (); + return; + } + + strcpy (t_last_loaded_info_file, filestack->filename); + strcpy (t_current_info_file, filestack->current_filename); + t_tag_table = filestack->tag_table; + t_info_file = info_file; + t_info_buffer_len = info_buffer_len; + + strcpy (filestack->filename, last_loaded_info_file); + strcpy (filestack->current_filename, current_info_file); + filestack->tag_table = tag_table; + filestack->info_file = info_file; + filestack->info_buffer_len = info_buffer_len; + + strcpy (last_loaded_info_file, t_last_loaded_info_file); + strcpy (current_info_file, t_current_info_file); + tag_table = t_tag_table; + info_file = t_info_file; + info_buffer_len = t_info_buffer_len; +} + +/* Now the node history stack */ + +extern NODEINFO *Info_History; + +/* Save the current filename, nodename, and position on the history list. + We prepend. */ +int +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 (1); +} + +/* Pop one node from the node list, leaving the values in + passed variables. */ +int +pop_node (filename, nodename, nodetop, pagetop) + char *filename, *nodename; + int *nodetop, *pagetop; +{ + if (Info_History->next == (NODEINFO *) NULL) + { + display_error ("At beginning of history now!"); + return (0); + } + else + { + NODEINFO *releaser = Info_History; + + /* If the popped file is not the current file, then force + the popped file to be loaded. */ + if (strcmp (Info_History->filename, last_loaded_info_file) != 0) + last_loaded_info_file[0] = '\0'; + + 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 (1); + } +} + +/* Whoops, Unix doesn't have strnicmp. */ + +/* Compare at most COUNT characters from string1 to string2. Case + doesn't matter. */ +int +strnicmp (string1, string2, count) + char *string1, *string2; +{ + char ch1, ch2; + + while (count) + { + ch1 = *string1++; + ch2 = *string2++; + if (to_upper (ch1) == to_upper (ch2)) + count--; + else + break; + } + return (count); +} + +/* Compare string1 to string2. Case doesn't matter. */ +int +stricmp (string1, string2) + char *string1, *string2; +{ + char ch1, ch2; + + while (1) + { + ch1 = *string1++; + ch2 = *string2++; + + if (ch1 == '\0') + return (ch2 != '\0'); + + if ((ch2 == '\0') || + (to_upper (ch1) != to_upper (ch2))) + return (1); + } +} + +/* Make the user type "Y" or "N". */ +int +get_y_or_n_p () +{ + int character; + print_string (" (Y or N)?"); + clear_eol (); + +until_we_like_it: + + character = blink_cursor (); + if (character == EOF) + return (0); + if (to_upper (character) == 'Y') + { + charout (character); + return (1); + } + + if (to_upper (character) == 'N') + { + charout (character); + return (0); + } + + if (character == ABORT_CHAR) + { + ding (); + return (0); + } + + goto until_we_like_it; +} + +/* Move the cursor to the desired column in the window. */ +indent_to (screen_column) + int screen_column; +{ + int counter = screen_column - the_window.ch; + if (counter > 0) + { + while (counter--) + charout (' '); + } + else if (screen_column != 0) + charout (' '); +} + + +/* **************************************************************** */ +/* */ +/* Error output/handling. */ +/* */ +/* **************************************************************** */ + +/* Display specific error from known file error table. */ +file_error (file) + char *file; +{ + extern int errno; + extern int sys_nerr; + extern char *sys_errlist[]; + + if (errno < sys_nerr) + display_error ("%s: %s", file, sys_errlist[errno]); + else + display_error ("%s: Unknown error %d", file, errno); +} + +/* Display the error in the echo-area using format_string and args. + This is a specialized interface to printf. */ +display_error (format_string, arg1, arg2) + char *format_string; +{ + extern int terminal_inited_p; + char output_buffer[1024]; + + if (totally_inhibit_errors) + return; + + sprintf (output_buffer, format_string, arg1, arg2); + if (terminal_inited_p) + { + new_echo_area (); + ding (); + print_string ("%s", output_buffer); + close_echo_area (); + } + else + { + fprintf (stderr, "%s\n", output_buffer); + } +} + +/* Tell everybody what a loser I am. If you see this error, + send me a bug report. */ +brians_error () +{ + display_error ("You are never supposed to see this error.\n"); + display_error ("Tell bfox@ai.mit.edu to fix this someday.\n"); + 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 (); +extern char *tgoto (); + +#define Certainly_enough_space 2048 /* page 3, Section 1.1, para 4 */ + +#if defined (unix) +char termcap_buffer[Certainly_enough_space]; +#else /* !unix */ +#define termcap_buffer NULL +#endif /* !unix */ + +/* You CANNOT remove these next four vars. TERMCAP needs them to operate. */ +char PC; +char *BC; +char *UP; + +/* 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 */ + +#if 0 /* partain moved them */ +int terminal_columns; /* {tc_int, "co" */ +int terminal_rows; /* {tc_int, "li" */ +#endif +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_columns}, + {tc_int, "li", (char *) &terminal_rows}, + {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 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:"); + fflush (stdout); + fgets (terminal_name, 256, stdin); + 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 */ + +/* For each element of "from_array", read the corresponding variable's + value into the right place. */ +get_terminal_vars (from_array) + termcap_capability_struct from_array[]; +{ + int i; + register termcap_capability_struct *item; + char *buffer; + +#if !defined (GNU_TERMCAP) + buffer = (char *) xmalloc (sizeof (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 bfox@ai.mit.edu to fix this someday.\n"); + } + } + + PC = terminal_padding ? terminal_padding[0] : 0; + BC = terminal_cursor_left; + UP = terminal_cursor_up; + return (NO_ERROR); +} + +/* Return the number of rows this terminal has. */ +int +get_terminal_rows () +{ + int rows = 0; + +#if defined (TIOCGWINSZ) + { + int tty; + struct winsize size; + + tty = fileno (stdin); + + if (ioctl (tty, TIOCGWINSZ, &size) != -1) + rows = size.ws_row; + } +#endif /* TIOCGWINSZ */ + + if (!rows) + rows = tgetnum ("li"); + + if (rows <= 0) + rows = 24; + + return (rows); +} + +/* Return the number of columns this terminal has. */ +get_terminal_columns () +{ + int columns = 0; + +#if defined (TIOCGWINSZ) + { + int tty; + struct winsize size; + + tty = fileno (stdin); + + if (ioctl (tty, TIOCGWINSZ, &size) != -1) + columns = size.ws_col; + } +#endif /* TIOCGWINSZ */ + + if (!columns) + columns = tgetnum ("co"); + + if (columns <= 0) + columns = 80; + + return (columns); +} + +/* #define TERMINAL_INFO_PRINTING */ +#ifdef TERMINAL_INFO_PRINTING + +/* 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. */ +show_terminal_info (from_array) + termcap_capability_struct from_array[]; +{ + register 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"); + } +} + +/* Print the contents of string without sending anything that isn't + a normal printing ASCII character. */ +tc_pretty_print (string) + register char *string; +{ + register char c; + + while (c = *string++) + { + if (CTRLP (c)) + { + putchar ('^'); + c += 64; + } + putchar (c); + } +} +#endif /* TERMINAL_INFO_PRINTING */ + + +/* **************************************************************** */ +/* */ +/* Character IO, and driver */ +/* */ +/* **************************************************************** */ + +int terminal_inited_p = 0; + +/* Start up the character io stuff. */ +init_terminal_io () +{ + if (!terminal_inited_p) + { + opsys_init_terminal (); + terminal_rows = get_terminal_rows (); + terminal_columns = get_terminal_columns (); + + widest_line = (char *) xmalloc (terminal_columns); + + terminal_inited_p = 1; + } + + terminal_window.left = 0; + terminal_window.top = 0; + terminal_window.right = terminal_columns; + terminal_window.bottom = terminal_rows; + + set_window (&terminal_window); + + terminal_window.bottom -= 2; + + set_window (&terminal_window); + + init_echo_area (the_window.left, the_window.bottom + 1, + the_window.right, terminal_rows); + + /* 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_rows) /* We don't how many lines it has. */ + { + fprintf (stderr, + "Your terminal is not clever enough to run info. Sorry.\n"); + exit (1); + } +} + +/* Ring the terminal bell. */ +ding () +{ + extern char *terminal_ear_bell; + + if (terminal_ear_bell) + do_term (terminal_ear_bell); + else + putchar (CTRL ('G')); + + fflush (stdout); +} + +int untyi_char = 0; +int inhibit_output = 0; + +/* Return a character from stdin, or the last unread character + if there is one available. */ +blink_cursor () +{ + int character; + + fflush (stdout); + if (untyi_char) + { + character = untyi_char; + untyi_char = 0; + } + else + do { character = getc (stdin); } while (character == -1 && errno == EINTR); + + return (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. */ +charout (character) + int character; +{ + if (inhibit_output) + return; + + /* This character may need special treatment if it is + a control character. */ + if (CTRL_P (character)) + { + switch (character) + { + case NEWLINE: + case RETURN: + print_cr (); + break; + + case TAB: + print_tab (); + break; + + default: + charout ('^'); + charout (UNCTRL (character)); + } + } + else + { + putchar (character); + advance (1); + } +} + +/* Move the cursor AMOUNT character positions. */ +advance (amount) + int amount; +{ + 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 and args using charout */ +print_string (string, a1, a2, a3, a4, a5) + char *string; +{ + int character; + char buffer[2048]; + int idx = 0; + + sprintf (buffer, string, a1, a2, a3, a4, a5); + + while (character = buffer[idx++]) + charout (character); + + fflush (stdout); +} + +/* Display a carriage return. + Clears to the end of the line first. */ +print_cr () +{ + extern int typing_out; + 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 = 1; + 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); +} + +/* Move the cursor to the next tab stop, blanking the intervening + spaces along the way. */ +print_tab () +{ + int hpos, width, destination; + + hpos = the_window.ch - the_window.left; + width = ((hpos + 8) & 0xf8) - hpos; + + destination = hpos + width + 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) & 0xf8) - hpos; + break; + default: + width = 2; + } + } + return (width); +} + +/* Like GOTO_XY, but do it right away. */ +I_goto_xy (xpos, ypos) + int xpos, ypos; +{ + goto_xy (xpos, ypos); + fflush (stdout); +} + +/* Move the cursor, (and cursor variables) to xpos, ypos. */ +goto_xy (xpos, ypos) + int xpos, ypos; +{ + the_window.ch = xpos; + the_window.cv = ypos; + opsys_goto_pos (xpos, ypos); +} + +/* Clear the screen, leaving ch and cv at the top of the window. */ +clear_screen () +{ + 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 from current cursor position to end of page. */ +clear_eop () +{ + if (terminal_clearEOP) + do_term (terminal_clearEOP); + else + clear_eop_slowly (); +} + +/* Clear from current cursor position to end of screen line */ +clear_eol () +{ + 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); +} + +/* Call FUNCTION with WINDOW active. You can pass upto 5 args to the + function. This returns whatever FUNCTION returns. */ +int +with_output_to_window (window, function, arg1, arg2, arg3, arg4, arg5) + WINDOW *window; + Function *function; +{ + int result; + + push_window (); + set_window (window); + result = (*function) (arg1, arg2, arg3, arg4, arg5); + pop_window (); + return (result); +} + +/* Given a pointer to a window data structure, make that + the current window. */ +set_window (window) + WINDOW *window; +{ + bcopy (window, &the_window, sizeof (WINDOW)); +} + +/* Save the current window on the window stack. */ +push_window () +{ + 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 the top of the window_stack into the_window. */ +pop_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 Info to run on machines that don't + have Unix. */ + +extern char *terminal_use_begin, *terminal_use_end, *terminal_goto; + +#if defined (TIOCGETC) +struct tchars original_tchars; +#endif + +#if defined (TIOCGLTC) +struct ltchars original_ltchars; +#endif + +#if defined (USG) +struct termio original_termio, ttybuff; +#else +int original_tty_flags = 0; +int original_lmode; +struct sgttyb ttybuff; +#endif /* !USG */ + +/* Yes, that's right, do things that the machine needs to get + the terminal into a usable mode. */ +opsys_init_terminal () +{ + int tty = fileno (stdin); + +#if defined (USG) + ioctl (tty, TCGETA, &original_termio); + ioctl (tty, TCGETA, &ttybuff); + ttybuff.c_iflag &= (~ISTRIP & ~INLCR & ~IGNCR & ~ICRNL &~IXON); + ttybuff.c_oflag &= (~ONLCR & ~OCRNL); + ttybuff.c_lflag &= (~ICANON & ~ECHO); + + ttybuff.c_cc[VMIN] = 1; + ttybuff.c_cc[VTIME] = 0; + + if (ttybuff.c_cc[VINTR] = DELETE) + ttybuff.c_cc[VINTR] = -1; + + if (ttybuff.c_cc[VQUIT] = DELETE) + ttybuff.c_cc[VQUIT] = -1; + + ioctl (tty, TCSETA, &ttybuff); +#else /* !USG */ + + ioctl (tty, TIOCGETP, &ttybuff); + + if (!original_tty_flags) + original_tty_flags = ttybuff.sg_flags; + + /* Make this terminal pass 8 bits around while we are using it. */ +#ifdef PASS8 + ttybuff.sg_flags |= PASS8; +#endif + +#if defined (TIOCLGET) && defined (LPASS8) + { + int flags; + ioctl (tty, TIOCLGET, &flags); + original_lmode = flags; + flags |= LPASS8; + ioctl (tty, TIOCLSET, &flags); + } +#endif + +#ifdef TIOCGETC + { + struct tchars temp; + + ioctl (tty, TIOCGETC, &original_tchars); + bcopy (&original_tchars, &temp, sizeof (struct tchars)); + + temp.t_startc = temp.t_stopc = -1; + + /* If the quit character conflicts with one of our commands, then + make it go away. */ + if (temp.t_intrc == DELETE) + temp.t_intrc == -1; + + if (temp.t_quitc == DELETE) + temp.t_quitc == -1; + + ioctl (tty, TIOCSETC, &temp); + } +#endif /* TIOCGETC */ + +#ifdef TIOCGLTC + { + struct ltchars temp; + + ioctl (tty, TIOCGLTC, &original_ltchars); + bcopy (&original_ltchars, &temp, sizeof (struct ltchars)); + + /* Make the interrupt keys go away. Just enough to make people happy. */ + temp.t_lnextc = -1; /* C-v */ + + ioctl (tty, TIOCSLTC, &temp); + } +#endif /* TIOCGLTC */ + + ttybuff.sg_flags &= ~ECHO; + ttybuff.sg_flags |= CBREAK; + ioctl (tty, TIOCSETN, &ttybuff); +#endif /* !USG */ + + open_terminal_io (); + do_term (terminal_use_begin); +} + +/* Fix the terminal that I broke. */ +restore_io () +{ + int tty = fileno (stdin); + +#if defined (USG) + ioctl (tty, TCSETA, &original_termio); +#else + ioctl (tty, TIOCGETP, &ttybuff); + ttybuff.sg_flags = original_tty_flags; + ioctl (tty, TIOCSETN, &ttybuff); + +#ifdef TIOCGETC + ioctl (tty, TIOCSETC, &original_tchars); +#endif /* TIOCGETC */ + +#ifdef TIOCGLTC + ioctl (tty, TIOCSLTC, &original_ltchars); +#endif /* TIOCGLTC */ + +#if defined (TIOCLGET) && defined (LPASS8) + ioctl (tty, TIOCLSET, &original_lmode); +#endif + +#endif /* !USG */ + do_term (terminal_use_end); +} + +opsys_goto_pos (xpos, ypos) + int xpos, ypos; +{ + 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. */ + if (command) + tputs (command, 1, character_output_function); +} + +/* Filename manipulators, and the like. */ +char local_temp_filename[FILENAME_LEN]; + +char *info_suffixes[] = { + "", + ".info", + "-info", + (char *)NULL +}; + +/* A structure which associates the argument passed into a function with + the result from that function. */ +typedef struct { + char *called; + char *result; +} CALLED_RESULTS; + +/* An array of remembered arguments and results. */ +static CALLED_RESULTS **opsys_callers_and_results = (CALLED_RESULTS **)NULL; +static int next_caller_and_result = 0; +static int callers_and_results_size = 0; + +/* Find the result for having already called opsys_filename () with CALLER. */ +char * +find_opsys_filename_result (caller) + char *caller; +{ + if (caller && opsys_callers_and_results) + { + register int i; + for (i = 0; opsys_callers_and_results[i]; i++) + { + if (strcmp (opsys_callers_and_results[i]->called, caller) == 0) + return (opsys_callers_and_results[i]->result); + } + } + return (char *)NULL;; +} + +/* Add an argument and result to our list. */ +void +add_caller_and_result (caller, result) + char *caller, *result; +{ + CALLED_RESULTS *new; + + if (next_caller_and_result + 2 > callers_and_results_size) + { + int alloc_size; + callers_and_results_size += 10; + + alloc_size = callers_and_results_size * sizeof (CALLED_RESULTS *); + + opsys_callers_and_results = (CALLED_RESULTS **) + xrealloc (opsys_callers_and_results, alloc_size); + } + + new = (CALLED_RESULTS *)xmalloc (sizeof (CALLED_RESULTS)); + new->called = savestring (caller); + new->result = result ? savestring (result) : (char *)NULL; + + opsys_callers_and_results[next_caller_and_result++] = new; + opsys_callers_and_results[next_caller_and_result] = (CALLED_RESULTS *)NULL; +} + +/* 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; + char *my_infopath; + + if (partial && (initial_character = *partial)) + { + char *possible_result; + + if (initial_character == '/') + return (partial); + + possible_result = find_opsys_filename_result (partial); + if (possible_result) + return (possible_result); + + 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 == '.' && + (partial[1]) == '/' || (partial[1] == '.' && partial[2] == '/')) + { +#if defined (USG) + if (!getcwd (local_temp_filename, FILENAME_LEN)) +#else + if (!getwd (local_temp_filename)) +#endif + { + display_error (local_temp_filename); + return (partial); + } + + strcat (local_temp_filename, "/"); + strcat (local_temp_filename, partial); + return (local_temp_filename); + } + + /* If the current info file has a directory, then search that directory + first. */ + { + register char *ptr, *file; + + file = find_opsys_filename_result (current_info_file); + + if (file && (ptr = (char *)rindex (file, '/')) != (char *)NULL) + { + register int len = (ptr - file); + + my_infopath = (char *) + xmalloc (2 + strlen (file) + strlen (infopath)); + + strncpy (my_infopath, file, len); + sprintf (my_infopath + len, ":%s", infopath); + } + else + my_infopath = savestring (infopath); + } + + if (opsys_info_file_in_path (partial, my_infopath, local_temp_filename)) + { + free (my_infopath); + add_caller_and_result (partial, local_temp_filename); + return (local_temp_filename); + } + + free (my_infopath); + } + return (partial); +} + +#if !defined (S_ISREG) && defined (S_IFREG) +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif /* !S_ISREG && S_IFREG */ + +#if !defined (S_ISDIR) && defined (S_IFDIR) +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif /* !S_ISDIR && S_IFDIR */ + +/* Scan the list of directories in PATH looking for FILENAME. + If we find one that is a regular file, stuff the answer into + RETURN_STRING. Return non-zero if a file was found, zero otherwise. */ +int +opsys_info_file_in_path (filename, path, return_string) + char *filename, *path; + char *return_string; +{ + struct stat finfo; + char *temp_dirname, *extract_colon_unit (); + char *test_file = return_string; + int statable, dirname_index = 0; + + while (temp_dirname = extract_colon_unit (path, &dirname_index)) + { + register int i, pre_suffix_length; + + strcpy (test_file, temp_dirname); + if (temp_dirname[(strlen (temp_dirname)) - 1] != '/') + strcat (test_file, "/"); + strcat (test_file, filename); + + pre_suffix_length = strlen (test_file); + + free (temp_dirname); + + for (i = 0; info_suffixes[i]; i++) + { + strcpy (test_file + pre_suffix_length, info_suffixes[i]); + + statable = (stat (test_file, &finfo) == 0); + + /* If we have found a regular file, then use that. Else, if we + have found a directory, look in that directory for this file. */ + if (statable) + { + if (S_ISREG (finfo.st_mode)) + return (1); + else if (S_ISDIR (finfo.st_mode)) + { + char *newpath = savestring (test_file); + char *filename_only = (char *)rindex (filename, '/'); + + if (opsys_info_file_in_path + (filename_only ? filename_only : filename, + newpath, return_string)) + { + free (newpath); + return (1); + } + } + } + } + } + return (0); +} + +/* Given a string containing units of information separated by colons, + return the next one pointed to by IDX, or NULL if there are no more. + Advance IDX to the character after the colon. */ +char * +extract_colon_unit (string, idx) + char *string; + int *idx; +{ + register int i, start; + + i = start = *idx; + if ((i >= strlen (string)) || !string) + return ((char *) NULL); + + while (string[i] && string[i] != ':') + i++; + if (i == start) + { + return ((char *) NULL); + } + else + { + char *value = (char *) xmalloc (1 + (i - start)); + strncpy (value, &string[start], (i - start)); + value[i - start] = '\0'; + if (string[i]) + ++i; + *idx = i; + return (value); + } +} + +/* **************************************************************** */ +/* */ +/* The echo area. */ +/* */ +/* **************************************************************** */ + +WINDOW echo_area = {0, 0, 0, 0, 0, 0}; +int echo_area_open_p = 0; +char modeline[256]; +WINDOW modeline_window = {0, 0, 0, 0, 0, 0}; + +/* Define the location of the echo area. Also inits the + modeline as well. */ +init_echo_area (left, top, right, bottom) + int left, top, right, bottom; +{ + 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; +} + +/* Make the echo_area_window be the current window, and only allow + output in there. Clear the window to start. */ +new_echo_area () +{ + if (!echo_area_open_p) + { + push_window (); + set_window (&echo_area); + echo_area_open_p = 1; + } + goto_xy (the_window.left, the_window.top); + clear_eop (); +} + +/* Return output to the previous window. */ +close_echo_area () +{ + if (!echo_area_open_p) + return; + + pop_window (); + echo_area_open_p = 0; +} + +/* Clear the contents of the echo area. */ +clear_echo_area () +{ + new_echo_area (); + close_echo_area (); +} + +/* Create and display the modeline. */ +make_modeline () +{ + int width = modeline_window.right - modeline_window.left; + char textual_position[6]; + + if (pagetop == nodetop) + { + if (pagebot == nodebot) + sprintf (textual_position, "All"); + else + sprintf (textual_position, "Top"); + } + else + { + if (pagebot == nodebot) + sprintf (textual_position, "Bot"); + else + sprintf (textual_position, "%2d%%", + 100 * (pagetop - nodetop) / (nodebot - nodetop)); + } + + sprintf (modeline, "Info: (%s)%s, %d lines ---%s", + current_info_file, current_info_node, nodelines, textual_position); + + 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 idx = strlen (modeline); + while (idx != width) + modeline[idx++] = '-'; + modeline[idx] = '\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 ("%s", modeline); + if (terminal_inverse_begin) + do_term (terminal_end_attributes); + + pop_window (); +} + +int typing_out = 0; + +/* Prepare to do output to the typeout window. If the + typeout window is already open, ignore this clown. */ +open_typeout () +{ + if (typing_out) + return; + + push_window (); + set_window (&terminal_window); + goto_xy (the_window.ch, the_window.cv); + typing_out = window_bashed = 1; +} + +/* Close the currently open typeout window. */ +close_typeout () +{ + if (inhibit_output) + inhibit_output = 0; + else + { + do { untyi_char = getc (stdin); } + while (untyi_char == -1 && errno == EINTR); + + if (untyi_char == SPACE) + untyi_char = 0; + } + pop_window (); + typing_out = 0; +} + +void * +xrealloc (pointer, bytes) + char *pointer; + int bytes; +{ + void *temp; + + if (pointer == (char *)NULL) + temp = (void *) xmalloc (bytes); + else + temp = (void *) realloc (pointer, bytes); + + if (temp == (void *)NULL) + { + fprintf (stderr, "Virtual memory exhausted\n"); + restore_io (); + exit (2); + } + return (temp); +} + +void * +xmalloc (bytes) + int bytes; +{ + void *temp = (void *) malloc (bytes); + + if (temp == (void *) NULL) + { + fprintf (stderr, "Virtual memory exhausted\n"); + restore_io (); + exit (2); + } + return (temp); +} |