home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS 1992 December / simtel1292_SIMTEL_1292_Walnut_Creek.iso / msdos / gnuish / mkinf10.arc / info.c < prev    next >
Text File  |  1990-11-01  |  60KB  |  2,335 lines

  1. /* info -- a stand-alone Info program
  2.  
  3.    Copyright (C) 1987 Free Software Foundation, Inc.
  4.  
  5.    This file is part of GNU Info.
  6.  
  7.    GNU Info is distributed in the hope that it will be useful,
  8.    but WITHOUT ANY WARRANTY.  No author or distributor accepts
  9.    responsibility to anyone for the consequences of using it or for
  10.    whether it serves any particular purpose or works at all, unless he
  11.    says so in writing.  Refer to the GNU Emacs General Public License
  12.    for full details.
  13.  
  14.    Everyone is granted permission to copy, modify and redistribute
  15.    GNU Info, but only under the conditions described in the GNU Emacs
  16.    General Public License.   A copy of this license is supposed to
  17.    have been given to you along with GNU Emacs so you can know your
  18.    rights and responsibilities.  It should be in a file named COPYING.
  19.    Among other things, the copyright notice and this notice must be
  20.    preserved on all copies.  */
  21.  
  22. /* MS-DOS port (c) 1990 by Thorsten Ohl, td12@ddagsi3.bitnet
  23.    This port is also distributed under the terms of the
  24.    GNU General Public License as published by the
  25.    Free Software Foundation.
  26.  
  27.    Please note that this file is not identical to the
  28.    original GNU release, you should have received this
  29.    code as patch to the official release.
  30.  
  31.    $Header: e:/gnu/info/RCS/info.c 0.1.1.2 90/10/26 20:05:12 tho Exp $
  32.  */
  33.  
  34. #include <sys/types.h>
  35. #include <sys/stat.h>
  36. #include <stdio.h>
  37. #ifdef MSDOS
  38. #include <process.h>
  39. #include <conio.h>
  40. #undef getc
  41. #define getc(stream)    pc_getc ()    /* read from console */
  42. #else /* not MSDOS */
  43. #include <sgtty.h>
  44. #endif /* not MSDOS */
  45. #include <signal.h>
  46. #ifndef MSDOS
  47. #include <pwd.h>
  48. #endif
  49.  
  50. #include "getopt.h"
  51.  
  52. #ifdef SYSV
  53. struct passwd *getpwnam ();
  54. #include <fcntl.h>
  55. #define bcopy(source, dest, count) memcpy(dest, source, count)
  56. #define index strchr
  57. #else
  58. #include <sys/file.h>
  59. #endif
  60.  
  61. #ifndef DEFAULT_INFOPATH
  62. #define DEFAULT_INFOPATH ".:/usr/gnu/info:/usr/local/emacs/info:/usr/local/lib/emacs/info"
  63. #endif
  64.  
  65.  
  66. #ifdef MSDOS
  67. #define LONG long
  68. #else /* not MSDOS */
  69. #define LONG int
  70. #endif /* not MSDOS */
  71.  
  72. typedef int boolean;
  73. #define true 1
  74. #define false 0
  75. #define UNIX
  76.  
  77. typedef struct nodeinfo
  78. {
  79.   char *filename;
  80.   char *nodename;
  81.   LONG pagetop;
  82.   LONG nodetop;
  83.   struct nodeinfo *next;
  84. }        NODEINFO;
  85.  
  86. typedef struct indirectinfo
  87. {
  88.   char *filename;
  89.   LONG first_byte;
  90. }            INDIRECT_INFO;
  91.  
  92. typedef int Function ();
  93.  
  94. #define PROJECT_NAME "GNU Info"
  95.  
  96. #define barf(msg) fprintf(stderr, "%s\n", msg)
  97.  
  98. /* Some character stuff. */
  99. #define control_character_threshold 0x020 /* smaller than this is control */
  100. #define meta_character_threshold 0x07f    /* larger than this is Meta. */
  101. #define control_character_bit 0x40    /* 0x000000, must be off. */
  102. #define meta_character_bit 0x080/* x0000000, must be on. */
  103.  
  104. #define info_separator_char '\037'
  105. #define start_of_node_string "\037"
  106.  
  107. #ifdef CTRL
  108. #undef CTRL
  109. #endif
  110.  
  111. #define CTRL(c) ((c) & (~control_character_bit))
  112. #define META(c) ((c) | meta_character_bit)
  113.  
  114. #define UNMETA(c) ((c) & (~meta_character_bit))
  115. #define UNCTRL(c) to_upper(((c)|control_character_bit))
  116.  
  117. #ifndef to_upper
  118. #define to_upper(c) (((c) < 'a' || (c) > 'z') ? c : c-32)
  119. #define to_lower(c) (((c) < 'A' || (c) > 'Z') ? c : c+32)
  120. #endif
  121.  
  122. #define CTRL_P(c) ((c) < control_character_threshold)
  123. #define META_P(c) ((c) > meta_character_threshold)
  124.  
  125. #define NEWLINE '\n'
  126. #define RETURN CTRL('M')
  127. #define DELETE 0x07f
  128. #define TAB '\t'
  129. #define ABORT_CHAR CTRL('G')
  130. #define PAGE CTRL('L')
  131. #define SPACE 0x020
  132. #define ESC CTRL('[')
  133. #define control_display_prefix '^'
  134.  
  135. #define TAG_TABLE_END_STRING "\037\nEND TAG TABLE"
  136. #define TAG_TABLE_BEG_STRING "\nTAG TABLE:\n"
  137. #define NODE_ID "Node:"
  138. #define FILENAME_LEN 256
  139. #define NODENAME_LEN 256
  140. #define STRING_SIZE 256
  141. #define nodeend_sequence "\n\037"
  142.  
  143. /* All right, some windows stuff. */
  144.  
  145. typedef struct
  146. {
  147.   int left, top, right, bottom;
  148.   int ch, cv;
  149. }      WINDOW;
  150.  
  151. typedef struct _wind_list
  152. {
  153.   int left, top, right, bottom;
  154.   int ch, cv;
  155.   struct _wind_list *next_window;
  156. }          WINDOW_LIST;
  157.  
  158. /* Stuff that everyone has to know about. */
  159. extern WINDOW the_window;
  160. extern WINDOW echo_area;
  161. extern LONG pagetop, nodetop, nodebot;
  162. extern int nodelines;
  163. extern char current_info_file[], current_info_node[], last_loaded_info_file[];
  164. extern char *info_file;
  165.  
  166. #ifdef MSDOS
  167.  
  168. #define VOID void
  169. #if defined (_MSC_VER) && (_MSC_VER >= 600)
  170. #define CDECL _cdecl
  171. #else
  172. #define CDECL cdecl
  173. #endif
  174.  
  175. #include <stdlib.h>
  176. #include <string.h>
  177. #include <stdarg.h>
  178. #include <ctype.h>
  179. #include <assert.h>
  180.  
  181. #include "pc_term.h"
  182.  
  183. static void replace_loosing_sscanf (char *buf, char *str, long *np);
  184.  
  185. char *extract_colon_unit (char *string, int *index);
  186. char *make_temp_filename (char *starter);
  187. char *opsys_filename (char *partial);
  188. char *xmalloc (int bytes);
  189. void add_completion (char *identifier, char *data);
  190. void advance (int amount);
  191. LONG back_lines (int count, LONG starting_pos);
  192. void begin_info_session (void);
  193. int blink_cursor (void);
  194. int brians_error (void);
  195. int build_menu (void);
  196. int build_notes (void);
  197. int character_output_function (char character);
  198. void charout (int character);
  199. void clean_up (char *string);
  200. void clear_echo_area (void);
  201. void clear_eol (void);
  202. void clear_eop (void);
  203. void clear_eop_slowly (void);
  204. void close_echo_area (void);
  205. void close_typeout (void);
  206. int complete (char *text, struct completion_entry *list);
  207. int deletefile (char *filename);
  208. void ding (void);
  209. void display_carefully (int character);
  210. int CDECL display_error (char *format_string, ...);
  211. void display_page (void);
  212. int display_width (int character, int hpos);
  213. int dump_current_node (char *filename);
  214. int extract_field (char *field_name, char *nodename, LONG offset);
  215. int file_error (char *file);
  216. LONG find_footnote_ref (LONG from);
  217. int find_menu_node (char *string, char *nodename);
  218. LONG find_node_in_file (char *nodename, LONG offset);
  219. LONG find_node_in_tag_table (char *nodename, LONG offset);
  220. LONG find_node_start (LONG start);
  221. int find_note_node (char *string, char *nodename);
  222. LONG forward_lines (int count, LONG starting_pos);
  223. void free_completion_list (void);
  224. void generic_page_display (void);
  225. int get_info_file (char *filename, int remember_name);
  226. int get_menu (int item);
  227. int get_node (char *filename, char *nodename, int popping);
  228. void get_node_extent (void);
  229. int get_y_or_n_p (void);
  230. void goto_xy (int xpos, int ypos);
  231. void help_possible_completions (char *text);
  232. void help_use_info (void);
  233. void indent_to (int screen_column);
  234. void CDECL info_signal_handler (int sig);
  235. void init_completer (void);
  236. void init_echo_area (int left, int top, int right, int bottom);
  237. void init_terminal_io (void);
  238. void install_signals (void);
  239. void I_goto_xy (int xpos, int ypos);
  240. int looking_at (char *string, LONG pointer);
  241. void CDECL main (int argc, char **argv);
  242. void make_modeline (void);
  243. void new_echo_area (void);
  244. int next_node (void);
  245. int next_page (void);
  246. void open_typeout (void);
  247. int pop_node (char *filename, char *nodename, LONG *nodetop, LONG *pagetop);
  248. void pop_window (void);
  249. int prev_node (void);
  250. int prev_page (void);
  251. void print_cr (void);
  252. void CDECL print_string (char *string, ...);
  253. void print_tab (void);
  254. int push_node (char *filename, char *nodename, LONG page_position,
  255.            LONG node_position);
  256. void push_window (void);
  257. int readline (char *prompt, char *line, int maxchars, int completing);
  258. void remember_completion (struct completion_entry *pointer);
  259. void restore_io (void);
  260. int scan_list (char *string, char *nodename);
  261. LONG search_backward (char *string, LONG starting_pos);
  262. LONG search_forward (char *string, LONG starting_pos);
  263. void set_search_constraints (char *buffer, LONG extent);
  264. void set_window (WINDOW *window);
  265. LONG skip_whitespace (LONG offset);
  266. LONG skip_whitespace_and_cr (LONG offset);
  267. LONG string_in_line (char *string, LONG pointer);
  268. void toploop (void);
  269. LONG to_beg_line (LONG from);
  270. LONG to_end_line (LONG from);
  271. int try_complete (char *text);
  272. int up_node (void);
  273. void usage (void);
  274. int CDECL with_output_to_window (WINDOW *window, int  (*function) (), ...);
  275. struct completion_entry *reverse_list (struct completion_entry *list);
  276.  
  277. #else /* not MSDOS */
  278.  
  279. #define VOID
  280. #define CDECL
  281.  
  282. char *malloc ();
  283. char *realloc ();
  284. char *xmalloc ();
  285. char *xrealloc ();
  286. char *getenv ();
  287. char *index ();
  288. char *strcat ();
  289. char *strcpy ();
  290.  
  291. #endif /* not MSDOS */
  292.  
  293. boolean build_menu ();
  294. boolean find_menu_node ();
  295. char *opsys_filename ();
  296.  
  297. #define MAX_INDIRECT_FILES 100 /* a crock, this should be done in a different way. */
  298. NODEINFO *Info_History = NULL;    /* the info history list. */
  299. INDIRECT_INFO indirect_list[MAX_INDIRECT_FILES];    /* ?Can't have more than xx files in the indirect list? */
  300.  
  301. char current_info_file[FILENAME_LEN];    /* the filename of the currently loaded info file. */
  302. char current_info_node[NODENAME_LEN];    /* the nodename of the node the user is looking at. */
  303. char last_loaded_info_file[FILENAME_LEN];    /* the last file actually loaded.  Not the same as current info file. */
  304. LONG nodetop, nodebot;        /* offsets in info_file of top and bottom of current_info_node. */
  305. int nodelines;            /* number of lines in this node. */
  306.  
  307. char *info_file = NULL;    /* buffer for the info file. */
  308. LONG info_buffer_len;        /* length of the above buffer. */
  309.  
  310. char *tag_table = NULL;    /* Pointer to the start of a tag table, or NULL to show none. */
  311. LONG tag_buffer_len;        /* length of the above buffer. */
  312.  
  313. boolean indirect = false;    /* If true, the tag table is indirect. */
  314. LONG indirect_top;
  315. LONG pagetop;            /* offset in the buffer of the current pagetop. */
  316.  
  317.  
  318. /* If non-NULL, this is a colon separated list of directories to search
  319.    for a specific info file.  The user places this variable into his or
  320.    her environment. */
  321. char *infopath = NULL;
  322.  
  323. char dumpfile[FILENAME_LEN];    /* If filled, the name of a file to write to. */
  324.  
  325. /* **************************************************************** */
  326. /*                                    */
  327. /*            Getting Started.                */
  328. /*                                    */
  329. /* **************************************************************** */
  330.  
  331. /* Begin the Info session. */
  332.  
  333. /* Global is on until we are out of trouble. */
  334. int totally_inhibit_errors = 1;
  335.  
  336. struct option long_options[] =
  337. {
  338.   {"directory", 1, 0, 'd'},
  339.   {"node", 1, 0, 'n'},
  340.   {"file", 1, 0, 'f'},
  341.   {"output", 1, 0, 'o'},
  342.   {NULL, 0, NULL, 0}
  343. };
  344.  
  345. #define savestring(x) strcpy (xmalloc (1 + strlen (x)), (x))
  346.  
  347. VOID CDECL
  348. main (argc, argv)
  349.      int argc;
  350.      char **argv;
  351. {
  352.   int c;
  353.   int ind;
  354.   char filename[FILENAME_LEN];
  355.   char nodename[NODENAME_LEN];
  356.   char *env_infopath = getenv ("INFOPATH");
  357.  
  358.   nodename[0] = filename[0] = '\0';
  359.  
  360.   if (env_infopath && *env_infopath)
  361.     infopath = savestring (env_infopath);
  362.   else
  363.     infopath = savestring (DEFAULT_INFOPATH);
  364.  
  365.   while ((c = getopt_long (argc, argv, "d:n:f:o:", long_options, &ind)) != EOF)
  366.     {
  367.       if (c == 0 && long_options[ind].flag == 0)
  368.     c = long_options[ind].val;
  369.       switch (c)
  370.     {
  371.     case 0:
  372.       break;
  373.       
  374.     case 'd':
  375.       free (infopath);
  376.       infopath = savestring (optarg);
  377.       break;
  378.       
  379.     case 'n':
  380.       strncpy (nodename, optarg, FILENAME_LEN);
  381.       break;
  382.       
  383.     case 'f':
  384.       strncpy (filename, optarg, FILENAME_LEN);
  385.       break;
  386.       
  387.     case 'o':
  388.       strncpy (dumpfile, optarg, FILENAME_LEN);
  389.       break;
  390.       
  391.     default:
  392.       usage ();
  393.     }
  394.     }
  395.  
  396.   /* Okay, flags are parsed.  Get possible Info menuname. */
  397.  
  398.   /* Start with DIR or whatever was specified. */
  399.   if (!get_node (filename, nodename, false)
  400.       && !get_node ((char *) NULL, (char *) NULL, true))
  401.       {
  402.     if (!*filename)
  403.       strcpy (filename, "dir");
  404.  
  405.     fprintf (stderr, "%s: Cannot find \"%s\", anywhere along the\n",
  406.          argv[0], filename);
  407.     fprintf (stderr, "search path of \"%s\".\n", infopath);
  408.  
  409.     exit (1);
  410.       }
  411.   totally_inhibit_errors = 0;
  412.   get_node (filename, nodename, false);
  413.  
  414.   if (optind != argc)
  415.     {
  416.       putchar ('\n');
  417.  
  418.       while (optind != argc)
  419.     {
  420.       if (build_menu () &&
  421.           find_menu_node (argv[optind], nodename) &&
  422.           get_node ((char *) NULL, nodename, false))
  423.         {
  424. #ifdef NEVER
  425. /* RMS says not to type this stuff out because he expects programs
  426.    to call Info instead of interactive users. */
  427.           printf ("%s.. ",argv[optind]);
  428.           fflush (stdout);
  429. #endif
  430.           optind++;
  431.         }
  432.       else
  433.         break;
  434.     }
  435.     }
  436.   begin_info_session ();
  437. }
  438.  
  439. VOID
  440. usage ()
  441. {
  442.   fprintf (stderr,
  443.        "Usage: info [-d dir-path] [-f info-file] [-n node-name] [-o output-file]\n");
  444.   fprintf (stderr,
  445.        "       [+directory dir-path] [+file info-file] [+node node-name]\n");
  446.   fprintf (stderr,
  447.        "       [+output output-file] [menu-selection...]\n");
  448.   exit (1);
  449. }
  450.  
  451. #ifdef SIGTSTP
  452. Function *old_stop;        /* last value of SIGSTOP. */
  453. Function *old_cont;        /* last value of SIGCONT. */
  454. #endif
  455.  
  456. #ifdef SIGWINCH
  457. Function *old_winch; /* last value of SIGWINCH. */
  458. #endif
  459.  
  460. VOID
  461. begin_info_session ()
  462. {
  463.   /* Start using Info. */
  464. #ifndef MSDOS
  465.   int info_signal_handler ();
  466. #endif /* not MSDOS */
  467.  
  468.   /* If the user just wants to dump the node, then do that. */
  469.   if (dumpfile[0])
  470.     {
  471.       dump_current_node (dumpfile);
  472.       exit (0);
  473.     }
  474.  
  475.   init_terminal_io ();
  476.  
  477.   /* Install handlers for restoring/breaking the screen. */
  478.  
  479.   install_signals ();
  480.   new_echo_area ();
  481.  
  482.   print_string ("Welcome to Info!  Type \"?\" for help. ");
  483.   close_echo_area ();
  484.   toploop ();
  485.   restore_io ();
  486. }
  487.  
  488. VOID CDECL
  489. info_signal_handler (sig)
  490.      int sig;
  491. {
  492.   /* Do the right thing with this signal. */
  493.  
  494.   switch (sig)
  495.     {
  496.  
  497. #ifdef SIGTSTP
  498.     case SIGTSTP:
  499.       restore_io ();
  500.       signal (SIGTSTP, old_stop);
  501.       kill (getpid (), SIGSTOP); /* Some weenie says this is the right thing. */
  502.       break;
  503.  
  504.     case SIGCONT:
  505.       /* Try to win some more.  Reset IO state, and stuff
  506.          like that. */
  507.  
  508.       clear_screen ();
  509.       display_page ();
  510.       goto_xy (the_window.ch, the_window.cv);
  511.       opsys_init_terminal ();
  512.       signal (SIGTSTP, info_signal_handler);
  513.       break;
  514. #endif /* SIGTSTP */
  515.  
  516. #ifdef SIGWINCH
  517.     case SIGWINCH:
  518.       /* Window has changed.  Get the size of the new window, and rebuild our
  519.          window chain. */
  520.       {
  521.     extern char *widest_line;
  522.     extern WINDOW terminal_window;
  523.     extern WINDOW_LIST *window_stack;
  524.     extern int terminal_lines, terminal_rows;
  525.     int delta_width, delta_height, right, bottom;
  526.  
  527.     get_terminal_info ();
  528.     right = get_term_width ();
  529.     bottom = get_term_height ();
  530.  
  531.     delta_width = right - terminal_rows;
  532.     delta_height = bottom - terminal_lines;
  533.  
  534.     terminal_rows = right;
  535.     terminal_lines = bottom - 3;
  536.     push_window ();
  537.     free (widest_line);
  538.     widest_line = (char *) xmalloc (right);
  539.     set_window (&terminal_window);
  540.     push_window ();
  541.  
  542.     /* Make the new window.  Map over all windows in window list. */
  543.     {
  544.       WINDOW_LIST *wind = window_stack;
  545.       extern WINDOW modeline_window;
  546.  
  547.       while (wind != (WINDOW_LIST *) NULL)
  548.         {
  549.           adjust_wind ((WINDOW *) wind, delta_width, delta_height);
  550.           wind = wind->next_window;
  551.         }
  552.  
  553.       /* Adjust the other windows that we know about. */
  554.  
  555.       adjust_wind (&terminal_window, delta_width, delta_height);
  556.       adjust_wind (&echo_area, delta_width, delta_height);
  557.       adjust_wind (&modeline_window, delta_width, delta_height);
  558.     }
  559.     pop_window ();
  560.     clear_screen ();
  561.     with_output_to_window (&terminal_window, display_page);
  562.     pop_window ();
  563.       }
  564.       break;
  565. #endif /* SIGWINCH */
  566.     case SIGINT:
  567.       restore_io ();
  568.       exit (1);
  569.       break;
  570.     }
  571. }
  572.  
  573. VOID
  574. install_signals ()
  575. {
  576. #ifdef SIGTSTP
  577.   old_stop = (Function *) signal (SIGTSTP, info_signal_handler);
  578.   old_cont = (Function *) signal (SIGCONT, info_signal_handler);
  579. #endif /* SIGTSTP */
  580.  
  581. #ifdef SIGWINCH
  582.   old_winch = (Function *) signal (SIGWINCH, info_signal_handler);
  583. #endif
  584.  
  585.   signal (SIGINT, info_signal_handler);
  586. }
  587.  
  588. #ifdef SIGWINCH
  589. adjust_wind (wind, delta_width, delta_height)
  590.      WINDOW *wind;
  591.      int delta_width, delta_height;
  592. {
  593.   wind->right += delta_width;
  594.   wind->bottom += delta_height;
  595.   if (wind->top)
  596.     wind->top += delta_height;
  597.   if (wind->left)
  598.     wind->left += delta_width;
  599. }
  600. #endif /* SIGWINCH */
  601.  
  602. /* **************************************************************** */
  603. /*                                    */
  604. /*            Completing Things                */
  605. /*                                    */
  606. /* **************************************************************** */
  607. typedef struct completion_entry
  608. {
  609.   char *identifier;
  610.   char *data;
  611.   struct completion_entry *next;
  612. }                COMP_ENTRY;
  613.  
  614. /* The linked list of COMP_ENTRY structures that you create. */
  615. COMP_ENTRY *completion_list = (COMP_ENTRY *) NULL;
  616.  
  617. /* The vector of COMP_ENTRY pointers that COMPLETE returns. */
  618. COMP_ENTRY **completions = NULL;
  619.  
  620. /* The number of elements in the above vector. */
  621. int completion_count;
  622.  
  623. /* Initial size of COMPLETIONS array. */
  624. #define INITIAL_COMPLETIONS_CORE_SIZE 200
  625.  
  626. /* Current size of the completion array in core. */
  627. int completions_core_size = 0;
  628.  
  629. /* Ease the typing task.  Another name for the I'th IDENTIFIER of COMPLETIONS. */
  630. #define completion_id(i) ((completions[(i)])->identifier)
  631.  
  632. /* The number of completions that can be present before the help
  633.    function starts asking you about whether it should print them
  634.    all or not. */
  635. int completion_query_threshold = 100;
  636.  
  637. VOID
  638. free_completion_list ()
  639. {
  640.   COMP_ENTRY *temp;
  641.   while (completion_list)
  642.     {
  643.       temp = completion_list;
  644.       if (completion_list->identifier)
  645.     free (completion_list->identifier);
  646.       if (completion_list->data)
  647.     free (completion_list->data);
  648.       completion_list = completion_list->next;
  649.       free (temp);
  650.     }
  651. }
  652.  
  653. /* Add a single completion to COMPLETION_LIST.
  654.    IDENTIFIER is the string that the user should type.
  655.    DATA should just be a pointer to some random data that you wish to
  656.    have associated with the identifier, but I'm too stupid for that, so
  657.    it must be a string as well.  This allocates the space for the strings
  658.    so you don't necessarily have to. */
  659. VOID
  660. add_completion (identifier, data)
  661.      char *identifier, *data;
  662. {
  663.   COMP_ENTRY *temp = (COMP_ENTRY *) xmalloc (sizeof (COMP_ENTRY));
  664.   temp->identifier = (char *) xmalloc (strlen (identifier) + 1);
  665.   strcpy (temp->identifier, identifier);
  666.   temp->data = (char *) xmalloc (strlen (data) + 1);
  667.   strcpy (temp->data, data);
  668.   temp->next = completion_list;
  669.   completion_list = temp;
  670. }
  671.  
  672. /* Function for reading a line.  Supports completion on COMPLETION_LIST
  673.    if you pass COMPLETING as true.  Prompt is either a prompt or NULL,
  674.    LINE is the place to store the characters that are read.  LINE may start
  675.    out already containing some characters; if so, they are printed.  MAXCHARS
  676.    tells how many characters can fit in the buffer at LINE.  READLINE returns
  677.    FALSE if the user types the abort character.  LINE is returned with a '\0'
  678.    at the end, not a '\n'.  */
  679. boolean
  680. readline (prompt, line, maxchars, completing)
  681.      char *prompt, *line;
  682.      int maxchars;
  683.      boolean completing;
  684. {
  685.   int character;
  686.   int readline_ch, readline_cv;
  687.   int current_len = strlen (line);
  688.   int meta_flag = 0;
  689.  
  690.   new_echo_area ();
  691.   if (prompt)
  692.     print_string (prompt);
  693.   readline_ch = the_window.ch;
  694.   readline_cv = the_window.cv;
  695.  
  696.   print_string (line);
  697.  
  698.   while (true)
  699.     {
  700.  
  701.       line[current_len] = '\0';
  702.       goto_xy (readline_ch, readline_cv);
  703.       print_string (line);
  704.       clear_eol ();
  705.  
  706.       character = blink_cursor ();
  707.       if (meta_flag)
  708.     {
  709.       character = META (character);
  710.       meta_flag = 0;
  711.     }
  712.  
  713.       if (META_P (character))
  714.     character = META (to_upper (UNMETA (character)));
  715.  
  716.       switch (character)
  717.     {
  718.  
  719.     case EOF:
  720.       character = '\n';
  721.  
  722.     case ESC:
  723.       meta_flag++;
  724.       break;
  725.  
  726.     case META (DELETE):
  727.     case CTRL ('W'):
  728.       while (current_len && line[current_len] == SPACE)
  729.         current_len--;
  730.       if (!current_len)
  731.         break;
  732.       while (current_len && line[current_len] != SPACE)
  733.         current_len--;
  734.       break;
  735.  
  736.     case CTRL ('U'):
  737.       current_len = 0;
  738.       break;
  739.  
  740.     case '\b':
  741.     case 0x07f:
  742.       if (current_len)
  743.         current_len--;
  744.       else
  745.         ding ();
  746.       break;
  747.  
  748.     case '\n':
  749.     case '\r':
  750.       if (completing)
  751.         {
  752.           extern int completion_count;
  753.           try_complete (line);
  754.           if (completion_count >= 1)
  755.         {
  756.           close_echo_area ();
  757.           return (true);
  758.         }
  759.           else
  760.         {
  761.           current_len = strlen (line);
  762.           break;
  763.         }
  764.         }
  765.       else
  766.         {
  767.           close_echo_area ();
  768.           return (true);
  769.         }
  770.  
  771.     case ABORT_CHAR:
  772.       ding ();
  773.       if (current_len)
  774.         {
  775.           current_len = 0;
  776.         }
  777.       else
  778.         {
  779.           close_echo_area ();
  780.           clear_echo_area ();
  781.           return (false);
  782.         }
  783.       break;
  784.  
  785.     case ' ':
  786.     case '\t':
  787.     case '?':
  788.       if (completing)
  789.         {
  790.           extern int completion_count;
  791.           if (character == '?')
  792.         {
  793.           help_possible_completions (line);
  794.           break;
  795.         }
  796.           else
  797.         {
  798.           char temp_line[NODENAME_LEN];
  799.           strcpy (temp_line, line);
  800.           try_complete (line);
  801.           if (completion_count != 1 && character == SPACE)
  802.             {
  803.               if (strcmp (temp_line, line) == 0)
  804.             {
  805.               line[current_len] = SPACE;
  806.               line[current_len + 1] = '\0';
  807.               strcpy (temp_line, line);
  808.               try_complete (line);
  809.               if (completion_count == 0)
  810.                 {
  811.                   line[current_len] = '\0';
  812.                   ding ();
  813.                 }
  814.             }
  815.             }
  816.           current_len = strlen (line);
  817.           if (completion_count == 0)
  818.             ding ();
  819.           break;
  820.         }
  821.         }
  822.       /* Do *NOT* put anything in-between the completing cases and
  823.          the default: case.  No. */
  824.     default:
  825.       if (!CTRL_P (character) && !META_P (character) &&
  826.           current_len < maxchars)
  827.         {
  828.           line[current_len++] = character;
  829.         }
  830.       else
  831.         ding ();
  832.     }
  833.     }
  834. }
  835.  
  836. VOID
  837. init_completer ()
  838. {
  839.   /* Initialize whatever the completer is using. */
  840.   if (completions_core_size != INITIAL_COMPLETIONS_CORE_SIZE)
  841.     {
  842.       if (completions)
  843.     free (completions);
  844.       completions = (COMP_ENTRY **) xmalloc ((sizeof (COMP_ENTRY *)) *
  845.            (completions_core_size = INITIAL_COMPLETIONS_CORE_SIZE));
  846.     }
  847.   completion_count = 0;
  848. }
  849.  
  850. /* Reverse the completion list passed in LIST, and
  851.    return a pointer to the new head. */
  852. COMP_ENTRY *
  853. reverse_list (list)
  854.      COMP_ENTRY *list;
  855. {
  856.   COMP_ENTRY *next;
  857.   COMP_ENTRY *prev = (COMP_ENTRY *) NULL;
  858.  
  859.   while (list)
  860.     {
  861.       next = list->next;
  862.       list->next = prev;
  863.       prev = list;
  864.       list = next;
  865.     }
  866.   return (prev);
  867. }
  868.  
  869. VOID
  870. remember_completion (pointer)
  871.      COMP_ENTRY *pointer;
  872. {
  873.   if (completion_count == completions_core_size)
  874.     {
  875.       COMP_ENTRY **temp = (COMP_ENTRY **) realloc (completions,
  876.                            ((sizeof (COMP_ENTRY *)) * (completions_core_size += INITIAL_COMPLETIONS_CORE_SIZE)));
  877.       if (!temp)
  878.     {
  879.       display_error ("Too many completions (~d)!  Out of core!", completion_count);
  880.       return;
  881.     }
  882.       else
  883.     completions = temp;
  884.     }
  885.   completions[completion_count++] = pointer;
  886. }
  887.  
  888. boolean
  889. complete (text, list)
  890.      char *text;
  891.      COMP_ENTRY *list;
  892. {
  893.   /* Complete TEXT from identifiers in LIST.  Place the resultant
  894.      completions in COMPLETIONS, and the number of completions in
  895.      COMPLETION_COUNT. Modify TEXT to contain the least common
  896.      denominator of all the completions found. */
  897.  
  898.   int low_match, i, index;
  899.   int string_length = strlen (text);
  900.  
  901.   init_completer ();
  902.   low_match = 1000; /* some large number. */
  903.  
  904.   while (list)
  905.     {
  906.       if (strnicmp (text, list->identifier, string_length) == 0)
  907.     remember_completion (list);
  908.       list = list->next;
  909.     }
  910.  
  911.   if (completion_count == 0)
  912.     return (false);
  913.  
  914.   if (completion_count == 1)
  915.     {                /* One completion */
  916.       strcpy (text, completion_id (0));
  917.       return (true);
  918.     }
  919.  
  920.   /* Else find the least common denominator */
  921.  
  922.   index = 1;
  923.  
  924.   while (index < completion_count)
  925.     {
  926.       int c1, c2;
  927.       for (i = 0;
  928.        (c1 = to_lower (completion_id (index - 1)[i])) &&
  929.        (c2 = to_lower (completion_id (index)[i]));
  930.        i++)
  931.     if (c1 != c2)
  932.       break;
  933.  
  934.       if (low_match > i)
  935.     low_match = i;
  936.       index++;
  937.     }
  938.  
  939.   strncpy (text, completion_id (0), low_match);
  940.   text[low_match] = '\0';
  941.   return (true);
  942. }
  943.  
  944. /* Complete TEXT from the completion structures in COMPLETION_LIST. */
  945. boolean
  946. try_complete (text)
  947.      char *text;
  948. {
  949.   return (complete (text, completion_list));
  950. }
  951.  
  952. VOID
  953. help_possible_completions (text)
  954.      char *text;
  955. {
  956.   /* The function that prints out the possible completions. */
  957.  
  958.   char temp_string[2000];
  959.  
  960.   goto_xy (the_window.left, the_window.top);
  961.   strcpy (temp_string, text);
  962.   try_complete (temp_string);
  963.  
  964.   open_typeout ();
  965.  
  966.   if (completion_count == 0)
  967.     {
  968.       print_string ("There are no possible completions.\n");
  969.       goto print_done;
  970.     }
  971.  
  972.   if (completion_count == 1)
  973.     {
  974.       print_string ("The only possible completion of what you have typed is:\n\n");
  975. #ifdef MSDOS            /* well, that was simply wrong...  */
  976.       print_string (completion_id (0));
  977. #else /* not MSDOS */
  978.       print_string (completions[0]);
  979. #endif /* not MSDOS */
  980.       goto print_done;
  981.     }
  982.  
  983.   if (completion_count >= completion_query_threshold)
  984.     {
  985.       print_string ("\nThere are %d completions.  Do you really want to see them all", completion_count);
  986.       if (!get_y_or_n_p ())
  987.     return;
  988.     }
  989.  
  990.   print_string ("\nThe %d completions of what you have typed are:\n\n", completion_count);
  991.  
  992.   {
  993.     int index = 0;
  994.     int counter = 0;
  995.     int columns = (the_window.right - the_window.left) / 30;
  996.  
  997.     while (index < completion_count)
  998.       {
  999.     if (counter == columns)
  1000.       {
  1001.         charout ('\n');
  1002.         counter = 0;
  1003.       }
  1004.     indent_to (counter * 30);
  1005.     print_string (completion_id (index));
  1006.     counter++;
  1007.     index++;
  1008.       }
  1009.   }
  1010.  
  1011. print_done:
  1012.   print_string ("\n\nDone.\n-----------------\n");
  1013.   close_typeout ();
  1014. }
  1015.  
  1016. /* **************************************************************** */
  1017. /*                                    */
  1018. /*            Getting Nodes                    */
  1019. /*                                    */
  1020. /* **************************************************************** */
  1021.  
  1022. /* A node name looks like:
  1023.    Node: nodename with spaces but not a comma,
  1024. or Node: (filename-containing-node)node-within-file
  1025. or Node: (filename)
  1026.  
  1027.    The latter case implies a nodename of "Top".  All files are
  1028.    supposed to have one.
  1029.  
  1030.    Lastly, the nodename specified could be "*", which specifies the
  1031.    entire file. */
  1032.  
  1033. /* Load FILENAME.  If REMEMBER_NAME is true, then remember the
  1034.    loaded filename in CURRENT_INFO_FILE.  In either case, remember
  1035.    the name of this file in LAST_LOADED_INFO_FILE. */
  1036. boolean
  1037. get_info_file (filename, remember_name)
  1038.      char *filename;
  1039.      boolean remember_name;
  1040. {
  1041.   FILE *input_stream;
  1042.   struct stat file_info;
  1043.   LONG pointer;
  1044.   int result;
  1045.   char tempname[FILENAME_LEN];
  1046.  
  1047.   /* Get real filename. */
  1048.   strcpy (tempname, opsys_filename (filename));
  1049.  
  1050.   /* See if the file exists. */
  1051.   if ((result = stat (tempname, &file_info)) != 0)
  1052.     {
  1053.       /* Try again, this time with the name in lower-case. */
  1054.       char temp_tempname[FILENAME_LEN];
  1055.       int i;
  1056.  
  1057.       for (i = 0; temp_tempname[i] = to_lower (tempname[i]); i++);
  1058.       /* Get real filename again. AMS */
  1059.       strcpy (temp_tempname, opsys_filename (temp_tempname));
  1060.  
  1061.       result = stat (temp_tempname, &file_info);
  1062.       if (!result)
  1063.     strcpy (tempname, temp_tempname);
  1064.     }
  1065.  
  1066.   /* See if this file is the last loaded one. */
  1067.   if (!result && (strcmp (last_loaded_info_file, tempname) == 0))
  1068.     {
  1069.       return (true);        /* Okay, the file is loaded. */
  1070.     }
  1071.  
  1072.   /* Now try to open the file. */
  1073.   if (result || (input_stream = fopen (tempname, "r")) == NULL)
  1074.     {
  1075.       return (file_error (tempname));
  1076.     }
  1077.  
  1078.   /* If we already have a file loaded, then free it first. */
  1079.   if (info_file)
  1080.     {
  1081.       free (info_file);
  1082.  
  1083.       if (!indirect)
  1084.     {
  1085.       /* Then the tag table is also no longer valid. */
  1086.       tag_table = (char *) NULL;
  1087.     }
  1088.     }
  1089.  
  1090.   /* Read the contents of the file into a new buffer. */
  1091.  
  1092. #ifdef MSDOS            /* Fix this! allow files > 64k */
  1093.   assert (file_info.st_size < (1L<<16));
  1094.   info_file = (char *) xmalloc ((size_t) file_info.st_size);
  1095.   info_buffer_len
  1096.     = fread (info_file, 1, (size_t) file_info.st_size, input_stream);
  1097. #else /* not MSDOS */
  1098.   info_file = (char *) xmalloc (info_buffer_len = file_info.st_size);
  1099.   fread (info_file, 1, info_buffer_len, input_stream);
  1100. #endif /* not MSDOS */
  1101.   fclose (input_stream);
  1102.   strcpy (last_loaded_info_file, tempname);
  1103.   if (remember_name)
  1104.     {
  1105.       strcpy (current_info_file, tempname);
  1106.       if (indirect)
  1107.     {
  1108.       int index;
  1109.       indirect = false;
  1110.       free (tag_table);
  1111.       for (index = 0; index < MAX_INDIRECT_FILES &&
  1112.            indirect_list[index].filename != (char *) NULL;
  1113.            index++)
  1114.         {
  1115.           free (indirect_list[index].filename);
  1116.           indirect_list[index].filename = (char *) NULL;
  1117.         }
  1118.     }
  1119.     }
  1120.   else
  1121.     return (true);
  1122.  
  1123.   /* The file has been read, and we don't know anything about it.
  1124.      Find out if it contains a tag table. */
  1125.  
  1126.   tag_table = NULL;        /* assume none. */
  1127.   indirect = false;
  1128.   tag_buffer_len = 0;
  1129.  
  1130.   set_search_constraints (info_file, info_buffer_len);
  1131.  
  1132.   /* Go to the last few lines in the file. */
  1133.   pointer = back_lines (8, info_buffer_len);
  1134.   pointer = search_forward (TAG_TABLE_END_STRING, pointer);
  1135.  
  1136.   if (pointer > -1)
  1137.     {
  1138.       /* Then there is a tag table.  Find the start of it,
  1139.      and remember that. */
  1140.       pointer = search_backward (TAG_TABLE_BEG_STRING, pointer);
  1141.  
  1142.       /* Handle error for malformed info file. */
  1143.       if (pointer < 0)
  1144.     display_error ("Start of tag table not found!");
  1145.       else
  1146.     {
  1147.       /* No problem.  If this file is an indirect file, then the contents
  1148.          of the tag table must remain in RAM the entire time.  Otherwise,
  1149.          we can flush the tag table with the file when the file is flushed.
  1150.          So, if indirect, remember that, and copy the table to another
  1151.          place.*/
  1152.  
  1153.       LONG indirect_check = forward_lines (2, pointer);
  1154.  
  1155.       tag_table = info_file + pointer;
  1156.       tag_buffer_len = info_buffer_len - pointer;
  1157.  
  1158.       /* Shorten the search constraints. */
  1159.       info_buffer_len = pointer;
  1160.  
  1161.       if (looking_at ("(Indirect)\n", indirect_check))
  1162.         {
  1163.  
  1164.           /* We have to find the start of the indirect file's
  1165.          information. */
  1166.  
  1167. #ifdef MSDOS
  1168.           assert (tag_buffer_len < (1L<<16));
  1169.           tag_table = (char *) xmalloc ((size_t) tag_buffer_len);
  1170.           bcopy (&info_file[indirect_check], tag_table,
  1171.              (size_t) tag_buffer_len);
  1172. #else /* not MSDOS */
  1173.           tag_table = (char *) xmalloc (tag_buffer_len);
  1174.           bcopy (&info_file[indirect_check], tag_table, tag_buffer_len);
  1175. #endif /* not MSDOS */
  1176.  
  1177.           /* Find the list of filenames. */
  1178.           indirect_top = search_backward ("Indirect:\n", indirect_check);
  1179.           if (indirect_top < 0)
  1180.         {
  1181.           free (tag_table);
  1182.           tag_table = (char *) NULL;
  1183.           display_error ("Start of INDIRECT tag table not found!");
  1184.           return (false);
  1185.         }
  1186.  
  1187.           /* Remember the filenames, and their byte offsets. */
  1188.           {
  1189.         /* Index into the filename/offsets array. */
  1190.         int index = 0;
  1191.         char temp_filename[FILENAME_LEN];
  1192.         LONG temp_first_byte;
  1193.  
  1194.         info_buffer_len = indirect_top;
  1195.  
  1196.         /* For each line, scan the info into globals.  Then save
  1197.                the information in the INDIRECT_INFO structure. */
  1198.  
  1199.         while (info_file[indirect_top] != info_separator_char &&
  1200.                index < MAX_INDIRECT_FILES)
  1201.           {
  1202.             indirect_top = forward_lines (1, indirect_top);
  1203.             if (info_file[indirect_top] == info_separator_char)
  1204.               break;
  1205.  
  1206.             /* Ignore blank lines. */
  1207.             if (info_file[indirect_top] == '\n')
  1208.               continue;
  1209.  
  1210. #ifdef MSDOS
  1211. #if 0 /* never */
  1212.             sscanf (&info_file[indirect_top], "%s%ld",
  1213.                 temp_filename, &temp_first_byte);
  1214. #endif /* never */
  1215.             /* For some obscure reason, Microsoft's sscanf ()
  1216.                looses from time to time, typically for the first
  1217.                node in a large tag table.  I don't pretent to
  1218.                understand this, but I have a fix. */
  1219.             replace_loosing_sscanf (&info_file[indirect_top],
  1220.                         temp_filename, &temp_first_byte);
  1221.  
  1222. #else /* not MSDOS */
  1223.             sscanf (&info_file[indirect_top], "%s%d",
  1224.                 temp_filename, &temp_first_byte);
  1225. #endif /* not MSDOS */
  1226.  
  1227.             if (strlen (temp_filename))
  1228.               {
  1229.             temp_filename[strlen (temp_filename) - 1] = '\0';
  1230.             indirect_list[index].filename =
  1231.               (char *) xmalloc (strlen (temp_filename) + 1);
  1232.             strcpy (indirect_list[index].filename, temp_filename);
  1233.             indirect_list[index].first_byte = temp_first_byte;
  1234.             index++;
  1235.               }
  1236.           }
  1237.         /* Terminate the table. */
  1238.         if (index == MAX_INDIRECT_FILES)
  1239.           {
  1240.             display_error ("Sorry, the INDIRECT file array isn't large enough.");
  1241.             index--;
  1242.           }
  1243.         indirect_list[index].filename = (char *) NULL;
  1244.           }
  1245.           indirect = true;
  1246.         }
  1247.     }
  1248.     }
  1249.   return (true);
  1250. }
  1251.  
  1252. boolean
  1253. get_node (filename, nodename, popping)
  1254.      char *nodename, *filename;
  1255.      boolean popping;
  1256. {
  1257.   /* Make current_info_node be NODENAME.  This could involve loading
  1258.      a file, etc.  POPPING is true if we got here because we are popping
  1259.      one level. */
  1260.  
  1261.   LONG pointer;
  1262.   char internal_filename[FILENAME_LEN];
  1263.   char internal_nodename[NODENAME_LEN];
  1264.  
  1265.   if (nodename && *nodename)
  1266.     {
  1267.       /* Maybe nodename looks like: (filename)nodename, or worse: (filename).
  1268.          If so, extract the stuff out. */
  1269.       if (*nodename == '(')
  1270.     {
  1271.       /* We have a filename at the start of the nodename.
  1272.          Find out what it is. */
  1273.  
  1274.       int temp = 1;
  1275.       int temp1 = 0;
  1276.       char character;
  1277.  
  1278.       filename = internal_filename;
  1279.       while ((character = nodename[temp]) && character != ')')
  1280.         {
  1281.           filename[temp - 1] = character;
  1282.           temp++;
  1283.         }
  1284.       filename[temp - 1] = '\0';
  1285.  
  1286.       /* We have the filename now.  The nodename follows. */
  1287.       internal_nodename[0] = '\0';
  1288.       if (nodename[temp])
  1289.         {
  1290.           temp++;
  1291.           while (internal_nodename[temp1++] = nodename[temp++]);
  1292.         }
  1293.       nodename = internal_nodename;
  1294.     }
  1295.     }
  1296.  
  1297.   if (!popping)
  1298.     push_node (current_info_file, current_info_node, pagetop, nodetop);
  1299.  
  1300.  
  1301.   if (!nodename || !*nodename)
  1302.     {
  1303.       nodename = internal_nodename;
  1304.       strcpy (nodename, "Top");
  1305.     }
  1306.  
  1307.   if (!filename || !*filename)
  1308.     {
  1309.       filename = internal_filename;
  1310.       strcpy (filename, current_info_file);
  1311.     }
  1312.  
  1313.   if (!*filename)
  1314.     strcpy (filename, "dir");
  1315.  
  1316.   if (!get_info_file (filename, true))
  1317.     goto node_not_found;
  1318.  
  1319.   if (strcmp (nodename, "*") == 0)
  1320.     {
  1321.       /* The "node" that we want is the entire file. */
  1322.       pointer = 0;
  1323.       goto found_node;
  1324.     }
  1325.   
  1326.   /* If we are using a tag table, see if we can find the nodename in it. */
  1327.   if (tag_table)
  1328.     {
  1329.       pointer = find_node_in_tag_table (nodename, 0);
  1330.       if (pointer < 1)
  1331.     {
  1332.       /* Then the search through the tag table failed.  Maybe
  1333.          we should try searching the buffer?  Nahh, just barf. */
  1334.       boolean pop_node ();
  1335.     node_not_found:
  1336.       if (popping)
  1337.         return (false);    /* second time through. */
  1338.       display_error
  1339.         ("Sorry, unable to find the node \"%s\" in the file \"%s\".", nodename, filename);
  1340.       current_info_file[0] = '\0';
  1341.       current_info_node[0] = '\0';
  1342.       last_loaded_info_file[0] = '\0';
  1343.       pop_node (internal_filename, internal_nodename, &nodetop, &pagetop);
  1344.       get_node (internal_filename, internal_nodename, true);
  1345.       return (false);
  1346.     }
  1347.  
  1348.       /* Otherwise, the desired location is right here.
  1349.          Scarf the position byte. */
  1350.       while (tag_table[pointer] != '\177')
  1351.     pointer++;
  1352. #ifdef MSDOS
  1353. #if 0 /* never */
  1354.       sscanf (&tag_table[pointer + 1], "%ld", &pointer);
  1355. #endif /* never */
  1356.       /* It may be paranoia ... (cf. replace_loosing_sscanf())  */
  1357.       pointer = atol (&tag_table[pointer + 1]);
  1358. #else /* not MSDOS */
  1359.       sscanf (&tag_table[pointer + 1], "%d", &pointer);
  1360. #endif /* not MSDOS */
  1361.  
  1362.       /* Okay, we have a position pointer.  If this is an indirect file,
  1363.          then we should look through the indirect_list for the first
  1364.          element.first_byte which is larger than this.  Then we can load
  1365.          the specified file, and win. */
  1366.       if (indirect)
  1367.     {
  1368.       /* Find the filename for this node. */
  1369.       int index;
  1370.       for (index = 0; index < MAX_INDIRECT_FILES &&
  1371.            indirect_list[index].filename != (char *) NULL; index++)
  1372.         {
  1373.           if (indirect_list[index].first_byte > pointer)
  1374.         {
  1375.           /* We found it. */
  1376.           break;
  1377.         }
  1378.         }
  1379.       if (!get_info_file (indirect_list[index - 1].filename, true))
  1380.         goto node_not_found;
  1381.       pointer -= indirect_list[index - 1].first_byte;
  1382.  
  1383.       /* Here is code to compensate for the header of an indirect file. */
  1384.       {
  1385.         LONG tt = find_node_start (0);
  1386.         if (tt > -1)
  1387.           pointer += tt;
  1388.       }
  1389.     }
  1390.       else
  1391.     {
  1392.       /* This tag table is *not* indirect.  The filename of the file
  1393.          containing this node is the same as the current file.  The
  1394.          line probably looks like:
  1395.          File: info,  Node: Checking25796 */
  1396.     }
  1397.     }
  1398.   else
  1399.     {
  1400.       /* We don't have a tag table.  The node can only be found by
  1401.          searching this file in its entirety.  */
  1402.       get_info_file (filename, true);
  1403.       pointer = 0;
  1404.     }
  1405.  
  1406.   /* Search this file, using pointer as a good guess where to start. */
  1407.   /* This is the same number that RMS used.  It might be right or wrong. */
  1408.   pointer -= 1000;
  1409.   if (pointer < 0)
  1410.     pointer = 0;
  1411.  
  1412.   pointer = find_node_in_file (nodename, pointer);
  1413.   if (pointer < 0)
  1414.     goto node_not_found;
  1415.  
  1416.   /* We found the node in it's file.  Remember exciting information. */
  1417.  
  1418. found_node:
  1419.   back_lines (0, pointer);
  1420.   nodetop = pagetop = pointer;
  1421.   strcpy (current_info_node, nodename);
  1422.   strcpy (current_info_file, filename);
  1423.   get_node_extent ();
  1424.   return (true);
  1425. }
  1426.  
  1427. /* Get the bounds for this node.  NODETOP points to the start of the
  1428.    node. Scan forward looking for info_separator_char, and remember
  1429.    that in NODEBOT. */
  1430. VOID
  1431. get_node_extent ()
  1432. {
  1433.   LONG index = nodetop;
  1434.   int character;
  1435.   boolean do_it_till_end = (strcmp (current_info_node, "*") == 0);
  1436.  
  1437.   nodelines = 0;
  1438.  
  1439. again:
  1440.   while ((index < info_buffer_len) &&
  1441.      ((character = info_file[index]) != info_separator_char))
  1442.     {
  1443.       if (character == '\n')
  1444.     nodelines++;
  1445.       index++;
  1446.     }
  1447.   if (do_it_till_end && index != info_buffer_len)
  1448.     {
  1449.       index++;
  1450.       goto again;
  1451.     }
  1452.   nodebot = index;
  1453. }
  1454.  
  1455. /* Locate the start of a node in the current search_buffer.  Return
  1456.    the offset to the node start, or minus one.  START is the place in
  1457.    the file at where to begin the search. */
  1458. LONG
  1459. find_node_start (start)
  1460.      LONG start;
  1461. {
  1462.   return (search_forward (start_of_node_string, start));
  1463. }
  1464.  
  1465. /* Find NODENAME in TAG_TABLE. */
  1466. LONG
  1467. find_node_in_tag_table (nodename, offset)
  1468.      char *nodename;
  1469.      LONG offset;
  1470. {
  1471.   LONG temp;
  1472.  
  1473.   set_search_constraints (tag_table, tag_buffer_len);
  1474.  
  1475.   temp = offset;
  1476.   while (true)
  1477.     {
  1478.       offset = search_forward (NODE_ID, temp);
  1479.       if (offset < 0)
  1480.     return (offset);
  1481.       temp = skip_whitespace (offset + strlen (NODE_ID));
  1482.       if (strnicmp (tag_table + temp, nodename, strlen (nodename)) == 0)
  1483.     if (*(tag_table + temp + strlen (nodename)) == '\177')
  1484.       return (temp);
  1485.     }
  1486. }
  1487.  
  1488. /* Find NODENAME in INFO_FILE. */
  1489. LONG
  1490. find_node_in_file (nodename, offset)
  1491.      char *nodename;
  1492.      LONG offset;
  1493. {
  1494.   LONG temp;
  1495.  
  1496.   set_search_constraints (info_file, info_buffer_len);
  1497.  
  1498.   while (true)
  1499.     {
  1500.       offset = find_node_start (offset);
  1501.       if (offset < 0)
  1502.     return (offset);
  1503.       else
  1504.     temp = forward_lines (1, offset);
  1505.       if (temp == offset)
  1506.     return (-1);        /* At last line now, just a node start. */
  1507.       else
  1508.     offset = temp;
  1509.       temp = string_in_line (NODE_ID, offset);
  1510.       if (temp > -1)
  1511.     {
  1512.       temp = skip_whitespace (temp + strlen (NODE_ID));
  1513.       if (strnicmp (info_file + temp, nodename, strlen (nodename)) == 0)
  1514.         {
  1515.           int check_exact = *(info_file + temp + strlen (nodename));
  1516.  
  1517.           if (check_exact == '\t' ||
  1518.           check_exact == ',' ||
  1519.           check_exact == '.' ||
  1520.           check_exact == '\n')
  1521.         return (offset);
  1522.         }
  1523.     }
  1524.     }
  1525. }
  1526.  
  1527. #ifdef MSDOS
  1528. /* Nomen est omen ...  */
  1529. void
  1530. replace_loosing_sscanf (char *buf, char *str, long *np)
  1531. {
  1532.   while (*buf && !isspace (*buf))
  1533.     *str++ = *buf++;
  1534.   *str = '\0';
  1535.   *np = atol (buf);
  1536. }
  1537. #endif /* not MSDOS */
  1538.  
  1539.  
  1540. /* **************************************************************** */
  1541. /*                                    */
  1542. /*            Dumping and Printing Nodes                */
  1543. /*                                    */
  1544. /* **************************************************************** */
  1545.  
  1546. /* Make a temporary filename based on STARTER and the PID of this Info. */
  1547. char *
  1548. make_temp_filename (starter)
  1549.      char *starter;
  1550. {
  1551. #ifdef MSDOS            /* make a digestable filename... */
  1552.   char *temp = (char *) xmalloc (strlen (starter) + 1);
  1553.   char *p = temp;
  1554.   while (*starter)
  1555.     {
  1556.       *p++ = isalnum (*starter) ? *starter : '-';
  1557.       starter++;
  1558.     }
  1559.   *p = '\0';
  1560. #else /* not MSDOS */
  1561.   char *temp = (char *) xmalloc (FILENAME_LEN);
  1562.   sprintf (temp, "%s-%d", starter, getpid ());
  1563. #endif /* not MSDOS */
  1564.   return (temp);
  1565. }
  1566.  
  1567. /* Delete a file.  Print errors if necessary. */
  1568. deletefile (filename)
  1569.      char *filename;
  1570. {
  1571.   if (unlink (filename) != 0)
  1572.     return (file_error (filename));
  1573.   else
  1574.     return (0);
  1575. }
  1576.  
  1577. #ifndef MSDOS
  1578.  
  1579. /* Print the contents of FILENAME using lpr. */
  1580. char *print_command = "lpr -p ";
  1581.  
  1582. printfile (filename)
  1583.      char *filename;
  1584. {
  1585.   int length = strlen (print_command) + strlen (filename) + strlen ("\n") + 1;
  1586.   char *command = (char *) xmalloc (length);
  1587.   int error;
  1588.  
  1589.   display_error ("Printing file `%s'...\n", filename);
  1590.   sprintf (command, "%s%s", print_command, filename);
  1591.   error = system (command);
  1592.   if (error)
  1593.     display_error ("Can't invoke `%s'", command);
  1594.   free (command);
  1595.   return (error);
  1596. }
  1597.  
  1598. #endif /* not MSDOS */
  1599.  
  1600. /* Dump the current node into a file named FILENAME.
  1601.    Return 0 if the dump was successful, otherwise,
  1602.    print error and exit. */
  1603. dump_current_node (filename)
  1604.      char *filename;
  1605. {
  1606.   LONG i = nodetop;
  1607.   int c;
  1608.   FILE *output_stream = fopen (filename, "w");
  1609.   if (output_stream == (FILE *) NULL)
  1610.     return (file_error (filename));
  1611.  
  1612.   while (i < nodebot && i < info_buffer_len)
  1613.     {
  1614.       c = info_file[i];
  1615.       if (c < 0x020 && !(index ("\n\t\f", c)))
  1616.     {
  1617.       putc ('^', output_stream);
  1618.     }
  1619.       if (putc (info_file[i++], output_stream) == EOF)
  1620.     {
  1621.       fclose (output_stream);
  1622.       return (file_error (filename));
  1623.     }
  1624.     }
  1625.   fclose (output_stream);
  1626.   return (0);
  1627. }
  1628.  
  1629. /* **************************************************************** */
  1630. /*                                    */
  1631. /*             Toplevel eval loop.                 */
  1632. /*                                    */
  1633. /* **************************************************************** */
  1634.  
  1635. #define MENU_HEADER "\n* Menu:"
  1636. #define MENU_ID "\n* "
  1637. #define FOOTNOTE_HEADER "*Note"
  1638.  
  1639. /* Number of items that the current menu has. */
  1640. int the_menu_size = 0;
  1641.  
  1642. /* The node that last made a menus completion list. */
  1643. #ifdef MSDOS
  1644. char menus_nodename[NODENAME_LEN]; /* this should be correct for GNU too...  */
  1645. char menus_filename[NODENAME_LEN];
  1646. #else /* not MSDOS */
  1647. char *menus_nodename[NODENAME_LEN];
  1648. char *menus_filename[NODENAME_LEN];
  1649. #endif /* not MSDOS */
  1650.  
  1651. boolean window_bashed = false;
  1652.  
  1653. char *visible_footnote = 0;    /* The default prompt string for the Follow Reference command. */
  1654. VOID
  1655. toploop ()
  1656. {
  1657.   boolean done = false;
  1658.   boolean inhibit_display = false;
  1659.   int command;
  1660.   char nodename[NODENAME_LEN];
  1661.  
  1662.   while (!done)
  1663.     {
  1664.  
  1665.       if (!inhibit_display || window_bashed)
  1666.     display_page ();
  1667.       inhibit_display = window_bashed = false;
  1668.  
  1669.       nodename[0] = '\0';    /* Don't display old text in input line. */
  1670.  
  1671.       goto_xy (echo_area.left, echo_area.top);
  1672.       command = blink_cursor ();
  1673.       clear_echo_area ();
  1674.  
  1675.       if (command == EOF)
  1676.     {
  1677.       done = true;
  1678.       continue;
  1679.     }
  1680.       command = to_upper (command);
  1681.  
  1682.       switch (command)
  1683.     {
  1684.  
  1685.     case 'D':
  1686.       get_node ((char *) NULL, "(dir)Top", false);
  1687.       break;
  1688.  
  1689.     case 'H':
  1690.       if ((the_window.bottom - the_window.top) < 24)
  1691.         get_node ((char *) NULL, "(info)Help-Small-Screen", false);
  1692.       else
  1693.         get_node ((char *) NULL, "(info)Help", false);
  1694.       break;
  1695.  
  1696.     case 'N':
  1697.       if (!next_node ())
  1698.         {
  1699.           display_error ("No NEXT for this node!");
  1700.           inhibit_display = true;
  1701.         }
  1702.       break;
  1703.  
  1704.     case 'P':
  1705.       if (!prev_node ())
  1706.         {
  1707.           display_error ("No PREV for this node!");
  1708.           inhibit_display = true;
  1709.         }
  1710.       break;
  1711.  
  1712.     case 'U':
  1713.       if (!up_node ())
  1714.         {
  1715.           display_error ("No UP for this node!");
  1716.           inhibit_display = true;
  1717.         }
  1718.       break;
  1719.  
  1720.     case 'M':
  1721.       if (!build_menu ())
  1722.         {
  1723.           display_error ("No menu in this node!");
  1724.           inhibit_display = true;
  1725.           break;
  1726.         }
  1727.  
  1728.       if (!readline ("Menu item: ", nodename, NODENAME_LEN, true))
  1729.         {
  1730.           clear_echo_area ();
  1731.           inhibit_display = true;
  1732.           break;
  1733.         }
  1734.  
  1735.       I_goto_xy (echo_area.left, echo_area.top);
  1736.       if (!find_menu_node (nodename, nodename))
  1737.         {
  1738.           display_error ("\"%s\" is not a menu item!", nodename);
  1739.           inhibit_display = true;
  1740.           break;
  1741.         }
  1742.  
  1743.       if (get_node ((char *) NULL, nodename, false))
  1744.         clear_echo_area ();
  1745.       break;
  1746.  
  1747.  
  1748.     case 'F':
  1749.       {
  1750.         char footnote[NODENAME_LEN];
  1751.         if (!build_notes ())
  1752.           {
  1753.         display_error ("No cross-references in this node!");
  1754.         inhibit_display = true;
  1755.         break;
  1756.           }
  1757.         strcpy (footnote, visible_footnote);
  1758.         if (!readline ("Follow reference: ", footnote, NODENAME_LEN, true))
  1759.           {
  1760.         inhibit_display = true;
  1761.         break;
  1762.           }
  1763.  
  1764.         I_goto_xy (echo_area.left, echo_area.top);
  1765.         if (!find_note_node (footnote, nodename))
  1766.           {
  1767.         display_error ("\"%s\" is not a cross-reference in this node!", footnote);
  1768.         inhibit_display = true;
  1769.         break;
  1770.           }
  1771.  
  1772.         if (get_node ((char *) NULL, nodename, false))
  1773.           clear_echo_area ();
  1774.         break;
  1775.       }
  1776.  
  1777.     case 'L':
  1778.       {
  1779.         char filename[FILENAME_LEN], nodename[NODENAME_LEN];
  1780.         LONG ptop, ntop;
  1781.         if (pop_node (filename, nodename, &ntop, &ptop) &&
  1782.         get_node (filename, nodename, true))
  1783.           {
  1784.         pagetop = ptop;
  1785.           }
  1786.         else
  1787.           inhibit_display = true;
  1788.         break;
  1789.       }
  1790.  
  1791.     case SPACE:
  1792.       if (!next_page ())
  1793.         {
  1794.           display_error ("At last page of this node now!");
  1795.           inhibit_display = true;
  1796.         }
  1797.       break;
  1798.  
  1799.     case DELETE:
  1800.       if (!prev_page ())
  1801.         {
  1802.           display_error ("At first page of this node now!");
  1803.           inhibit_display = true;
  1804.         }
  1805.       break;
  1806.  
  1807.     case 'B':
  1808.       if (pagetop == nodetop)
  1809.         {
  1810.           display_error ("Already at beginning of this node!");
  1811.           inhibit_display = true;
  1812.         }
  1813.       else
  1814.         pagetop = nodetop;
  1815.       break;
  1816.  
  1817.       /* I don't want to do this this way, but the documentation clearly states
  1818.          that '6' doesn't work.  It states this for a reason, and ours is not to
  1819.          wonder why... */
  1820.  
  1821.     case '1':
  1822.     case '2':
  1823.     case '3':
  1824.     case '4':
  1825.     case '5':
  1826.       {
  1827.         int item = command - '0';
  1828.         if (!build_menu ())
  1829.           {
  1830.         display_error ("No menu in this node!");
  1831.         inhibit_display = true;
  1832.         break;
  1833.           }
  1834.         if (item > the_menu_size)
  1835.           {
  1836.         display_error ("There are only %d items in the menu!",
  1837.                    the_menu_size);
  1838.         inhibit_display = true;
  1839.         break;
  1840.           }
  1841.  
  1842.         if (!get_menu (item))
  1843.           inhibit_display = true;
  1844.         break;
  1845.       }
  1846.  
  1847.     case 'G':
  1848.       if (!readline ("Goto node: ", nodename, NODENAME_LEN, false))
  1849.         {
  1850.           inhibit_display = true;
  1851.           break;
  1852.         }
  1853.       clear_echo_area ();
  1854.       get_node ((char *) NULL, nodename, false);
  1855.       break;
  1856.  
  1857.     case 'S':
  1858.       {
  1859.         /* Search from the starting position forward for a string.
  1860.            Of course, this should really wrap the search around, but
  1861.            it doesn't do that yet.  Select the node containing the
  1862.            desired string.  Put the top of the page screen_lines/2
  1863.            lines behind it, but not before nodetop. */
  1864.  
  1865.         extern LONG info_buffer_len;
  1866.         LONG pointer, temp;
  1867.         LONG search_start = pagetop;
  1868.  
  1869.         if (!readline ("Search for string: ", nodename, NODENAME_LEN, false))
  1870.           {
  1871.         inhibit_display = true;
  1872.         break;
  1873.           }
  1874.  
  1875.         I_goto_xy (echo_area.left, echo_area.top);
  1876.  
  1877.         if (indirect)
  1878.           {
  1879.         /* Put the indirect search right here. */
  1880.         display_error ("This is an indirect file, and I can't search these yet!");
  1881.         break;
  1882.           }
  1883.         else
  1884.           {
  1885.         set_search_constraints (info_file, info_buffer_len);
  1886.         pointer = search_forward (nodename, search_start);
  1887.         if (pointer == -1)
  1888.           {
  1889.             display_error ("\"%s\" not found!  Try another info file.", nodename);
  1890.             inhibit_display = true;
  1891.             break;
  1892.           }
  1893.           }
  1894.         temp = search_backward (start_of_node_string, pointer);
  1895.         if (temp == -1)
  1896.           temp = search_forward (start_of_node_string, pointer);
  1897.         if (temp == -1)
  1898.           {
  1899.         brians_error ();
  1900.         break;
  1901.           }
  1902.  
  1903.         search_start = pointer;
  1904.         pointer = forward_lines (1, temp);
  1905.         if (!extract_field ("Node:", nodename, pointer))
  1906.           {
  1907.         display_error ("There doesn't appear to be a nodename for this node.");
  1908.         get_node ((char *) NULL, "*", false);
  1909.         pagetop = pointer;
  1910.         break;
  1911.           }
  1912.  
  1913.         if (get_node ((char *) NULL, nodename, false))
  1914.           clear_echo_area ();
  1915.  
  1916.         /* Make the string that the user wanted be visible, and
  1917.            centered in the screen. */
  1918.         {
  1919.           pointer = back_lines ((the_window.bottom - the_window.top) / 2, search_start);
  1920.           if (pointer < nodetop)
  1921.         pointer = nodetop;
  1922.  
  1923.           pagetop = pointer;
  1924.         }
  1925.  
  1926.         break;
  1927.       }
  1928.  
  1929.     case CTRL ('H'):
  1930.     case '?':
  1931.       help_use_info ();
  1932.       break;
  1933.  
  1934.     case 'Q':
  1935.       done = true;
  1936.       break;
  1937.  
  1938.     case CTRL ('L'):    /* Control-l is redisplay */
  1939.       break;
  1940.  
  1941.     case '(':        /* You *must* be trying to type a complete nodename. */
  1942.       strcpy (nodename, "(");
  1943.       if (!readline ("Goto node: ", nodename, NODENAME_LEN, false))
  1944.         {
  1945.           inhibit_display = true;
  1946.           clear_echo_area ();
  1947.           break;
  1948.         }
  1949.       I_goto_xy (echo_area.left, echo_area.top);
  1950.       if (get_node ((char *) NULL, nodename, false))
  1951.         clear_echo_area ();
  1952.       break;
  1953.  
  1954.     case CTRL ('P'):
  1955.       /* Print the contents of this node on the default printer.  We
  1956.          would like to let the user specify the printer, but we don't
  1957.          want to ask her each time which printer to use.  Besides, he
  1958.          might not know, which is why it (the user) is in the need of
  1959.          Info. */
  1960.       {
  1961.         char *tempname = make_temp_filename (current_info_node);
  1962. #ifdef MSDOS
  1963.         if (dump_current_node (tempname) == 0)
  1964.           display_error ("Dumped node to `%s'.\n", tempname);
  1965. #else /* not MSDOS */
  1966.         if (dump_current_node (tempname) == 0 &&
  1967.         printfile (tempname) == 0 &&
  1968.         deletefile (tempname) == 0)
  1969.           {
  1970.         display_error ("Printed node.  Go pick up your output.\n");
  1971.           }
  1972. #endif /* not MSDOS */
  1973.         inhibit_display = true;
  1974.         free (tempname);
  1975.       }
  1976.       break;
  1977.  
  1978.     default:
  1979.       inhibit_display = true;
  1980.       display_error ("Unknown command! Press '?' for help.");
  1981.  
  1982.     }
  1983.     }
  1984. }
  1985.  
  1986. VOID
  1987. help_use_info ()
  1988. {
  1989.   /* Tell this person how to use Info. */
  1990.  
  1991.   open_typeout ();
  1992.   print_string ("\n\
  1993.           Commands in Info\n\
  1994. \n\
  1995. h    Invoke the Info tutorial.\n\
  1996. \n\
  1997. Selecting other nodes:\n\
  1998. n    Move to the \"next\" node of this node.\n\
  1999. p    Move to the \"previous\" node of this node.\n\
  2000. u    Move \"up\" from this node.\n\
  2001. m    Pick menu item specified by name.\n\
  2002.     Picking a menu item causes another node to be selected.\n\
  2003. f    Follow a cross reference.  Reads name of reference.\n\
  2004. l    Move to the last node you were at.\n\
  2005. \n\
  2006. Moving within a node:\n\
  2007. Space    Scroll forward a page.\n\
  2008. DEL    Scroll backward a page.\n\
  2009. b    Go to the beginning of this node.\n\
  2010. \n\
  2011. Advanced commands:\n\
  2012. q    Quit Info.\n\
  2013. 1    Pick first item in node's menu.\n\
  2014. 2 - 5   Pick second ... fifth item in node's menu.\n\
  2015. g    Move to node specified by name.\n\
  2016.     You may include a filename as well, as (FILENAME)NODENAME.\n\
  2017. s    Search through this Info file for a specified string,\n\
  2018.     and select the node in which the next occurrence is found.\n\
  2019. Ctl-p   Print the contents of this node using `lpr -p'.\n\
  2020. \n\
  2021. Done.\n\n");
  2022.   close_typeout ();
  2023. }
  2024.  
  2025. boolean
  2026. next_node ()
  2027. {
  2028.   /* Move to the node specified in the NEXT field. */
  2029.  
  2030.   char nodename[NODENAME_LEN];
  2031.  
  2032.   if (!extract_field ("Next:", nodename, nodetop))
  2033.     return (false);
  2034.   return (get_node ((char *) NULL, nodename, false));
  2035. }
  2036.  
  2037. boolean
  2038. prev_node ()
  2039. {
  2040.   /* Move to the node specified in the PREVIOUS field. */
  2041.  
  2042.   char nodename[NODENAME_LEN];
  2043.  
  2044.   if (!extract_field ("Previous:", nodename, nodetop)
  2045.       && !extract_field ("Prev:", nodename, nodetop))
  2046.     return (false);
  2047.   return (get_node ((char *) NULL, nodename, false));
  2048. }
  2049.  
  2050. boolean
  2051. up_node ()
  2052. {
  2053.   /* Move to the node specified in the UP field. */
  2054.  
  2055.   char nodename[NODENAME_LEN];
  2056.  
  2057.   if (!extract_field ("Up:", nodename, nodetop))
  2058.     return (false);
  2059.   return (get_node ((char *) NULL, nodename, false));
  2060. }
  2061.  
  2062. /* Build a completion list of menuname/nodename for each
  2063.    line in this node that is a menu item. */
  2064. boolean
  2065. build_menu ()
  2066. {
  2067.   LONG pointer = nodetop;
  2068.   char menuname[NODENAME_LEN];
  2069.   char nodename[NODENAME_LEN];
  2070.  
  2071.   if (strcmp (menus_nodename, current_info_node) == 0 &&
  2072.       strcmp (menus_filename, current_info_file) == 0)
  2073.     return (the_menu_size != 0);
  2074.  
  2075.   strcpy (menus_nodename, current_info_node);
  2076.   strcpy (menus_filename, current_info_file);
  2077.   free_completion_list ();
  2078.  
  2079.   set_search_constraints (info_file, nodebot);
  2080.   if ((pointer = search_forward (MENU_HEADER, nodetop)) < 0)
  2081. #ifdef MSDOS
  2082.     {
  2083.       /* We have *not* build a completion list!  */
  2084.       menus_filename[0] = menus_nodename[0] = '\0';
  2085.       return (false);
  2086.     }
  2087. #else /* not MSDOS */
  2088.     return (false);
  2089. #endif /* not MSDOS */
  2090.  
  2091.   /* There is a menu here.  Look for members of it. */
  2092.   pointer += strlen (MENU_HEADER);
  2093.  
  2094.   while (true)
  2095.     {
  2096.       int index;
  2097.  
  2098.       pointer = search_forward (MENU_ID, pointer);
  2099.       if (pointer < 0)
  2100.     break;            /* no more menus in this node. */
  2101.       pointer = (skip_whitespace (pointer + strlen (MENU_ID)));
  2102.       index = 0;
  2103.       while ((menuname[index] = info_file[pointer]) && menuname[index] != ':')
  2104.     {
  2105.       index++, pointer++;
  2106.     }
  2107.       menuname[index] = '\0';
  2108.       pointer++;
  2109.       if (info_file[pointer] == ':')
  2110.     {
  2111.       strcpy (nodename, menuname);
  2112.     }
  2113.       else
  2114.     {
  2115.       pointer = skip_whitespace (pointer);
  2116.       index = 0;
  2117.       while ((nodename[index] = info_file[pointer]) &&
  2118.          nodename[index] != '\t' &&
  2119.          nodename[index] != '.' &&
  2120.          nodename[index] != ',')
  2121.         {
  2122.           index++, pointer++;
  2123.         }
  2124.       nodename[index] = '\0';
  2125.     }
  2126.       add_completion (menuname, nodename);
  2127.       the_menu_size++;
  2128.     }
  2129.   if (the_menu_size)
  2130.     completion_list = reverse_list (completion_list);
  2131. #ifdef MSDOS
  2132.   else
  2133.     /* We have *not* build a completion list!  */
  2134.     menus_filename[0] = menus_nodename[0] = '\0';
  2135. #endif /* MSDOS */
  2136.  
  2137.   return (the_menu_size != 0);
  2138. }
  2139.  
  2140. boolean
  2141. get_menu (item)
  2142.      int item;
  2143. {
  2144.   /* Select ITEMth item from the list built by build_menu. */
  2145.  
  2146.   if (!build_menu ())
  2147.     return (false);
  2148.   if (item > the_menu_size)
  2149.     return (false);
  2150.   else
  2151.     {
  2152.       COMP_ENTRY *temp = completion_list;
  2153.       while (--item && temp)
  2154.     temp = temp->next;
  2155.       return (get_node ((char *) NULL, temp->data, false));
  2156.     }
  2157. }
  2158.  
  2159. boolean
  2160. find_menu_node (string, nodename)
  2161.      char *string, *nodename;
  2162. {
  2163.   /* Scan through the ?already? built menu list looking
  2164.      for STRING.  If you find it, put the corresponding nodes
  2165.      name in NODENAME. */
  2166.   return (scan_list (string, nodename));
  2167. }
  2168.  
  2169. boolean
  2170. scan_list (string, nodename)
  2171.      char *string, *nodename;
  2172. {
  2173.   /* The work part of find_menu_node and find_note_node. */
  2174.   COMP_ENTRY *temp = completion_list;
  2175.  
  2176.   while (temp)
  2177.     {
  2178.       if (strnicmp (string, temp->identifier, strlen (string)) == 0)
  2179.     {
  2180.       strcpy (nodename, temp->data);
  2181.       return (true);
  2182.     }
  2183.       temp = temp->next;
  2184.     }
  2185.   return (false);
  2186. }
  2187.  
  2188. VOID
  2189. clean_up (string)
  2190.      char *string;
  2191. {
  2192.   /* Remove <CR> and whitespace from string, replacing them with
  2193.      only one space.  Exception:  <CR> at end of string disappears. */
  2194.  
  2195.   char *to = string;
  2196.   char last_char = 0;
  2197.   boolean result;
  2198.  
  2199.   while (*to = *string++)
  2200.     {
  2201.       if (*to == '\n')
  2202.     {
  2203.       *to = SPACE;
  2204.       if (!(*(to + 1)))
  2205.         {
  2206.           *to = '\0';
  2207.           return;
  2208.         }
  2209.     }
  2210.       result = (last_char == SPACE);
  2211.       last_char = *to;
  2212.       if (last_char != SPACE)
  2213.     to++;
  2214.       else if (!result)
  2215.     to++;
  2216.     }
  2217. }
  2218.  
  2219. LONG
  2220. find_footnote_ref (from)
  2221.      LONG from;
  2222. {
  2223.   /* Find a reference to "*Note".  Return the offset of
  2224.      the start of that reference, or -1. */
  2225.  
  2226.   while (true)
  2227.     {
  2228.       from = search_forward (FOOTNOTE_HEADER, from);
  2229.       if (from < 0)
  2230.     return (from);
  2231.       else
  2232.     from += strlen (FOOTNOTE_HEADER);
  2233.       if (info_file[from] == ' ' ||
  2234.       info_file[from] == '\n' ||
  2235.       info_file[from] == '\t')
  2236.     return (from);
  2237.     }
  2238. }
  2239.  
  2240.  
  2241. boolean
  2242. build_notes ()
  2243. {
  2244.   /* Build an array of (footnote.nodename) for each footnote in this node. */
  2245.  
  2246.   LONG pointer;
  2247.   char notename[NODENAME_LEN];
  2248.   char nodename[NODENAME_LEN];
  2249.  
  2250.   set_search_constraints (info_file, nodebot);
  2251.  
  2252.   if ((find_footnote_ref (nodetop)) < 0)
  2253.     return (false);
  2254.   pointer = nodetop;
  2255.  
  2256.   menus_filename[0] = menus_nodename[0] = '\0';
  2257.   visible_footnote = "";
  2258.   free_completion_list ();
  2259.  
  2260.   while (true)
  2261.     {
  2262.       int index;
  2263.  
  2264.       pointer = find_footnote_ref (pointer);
  2265.       if (pointer < 0)
  2266.     break;            /* no more footnotes in this node. */
  2267.  
  2268.  
  2269.       pointer = skip_whitespace_and_cr (pointer);
  2270.       index = 0;
  2271.       while ((notename[index] = info_file[pointer]) && notename[index] != ':')
  2272.     {
  2273.       index++, pointer++;
  2274.     }
  2275.       notename[index] = '\0';
  2276.       clean_up (notename);
  2277.       pointer++;
  2278.       if (info_file[pointer] == ':')
  2279.     {
  2280.       strcpy (nodename, notename);
  2281.     }
  2282.       else
  2283.     {
  2284.       pointer = skip_whitespace (pointer);
  2285.       index = 0;
  2286.       while ((nodename[index] = info_file[pointer]) &&
  2287.          nodename[index] != '\t' &&
  2288.          nodename[index] != '.' &&
  2289.          nodename[index] != ',')
  2290.         {
  2291.           index++, pointer++;
  2292.         }
  2293.       nodename[index] = '\0';
  2294.     }
  2295.       /* Add the notename/nodename to the list. */
  2296.       add_completion (notename, nodename);
  2297.       the_menu_size++;
  2298.  
  2299.       /* Remember this identifier as the default if it is the first one in the
  2300.          page. */
  2301.       if (!(*visible_footnote) &&
  2302.       pointer > pagetop &&
  2303.       pointer < forward_lines (the_window.bottom - the_window.top, pointer))
  2304.     visible_footnote = completion_list->identifier;
  2305.     }
  2306.   if (the_menu_size)
  2307.     completion_list = reverse_list (completion_list);
  2308.   return (the_menu_size != 0);
  2309. }
  2310.  
  2311. boolean
  2312. find_note_node (string, nodename)
  2313.      char *string, *nodename;
  2314. {
  2315.   /* Scan through the ?already? built footnote list looking
  2316.      for STRING.  If found, place the corresponding node name
  2317.      in NODENAME. */
  2318.  
  2319.   return (scan_list (string, nodename));
  2320. }
  2321.  
  2322. /*===(cut here)===*/
  2323.  
  2324. /* Include the rest:  */
  2325.  
  2326. #include "info.d"
  2327.  
  2328.  
  2329. /* 
  2330.  * Local Variables:
  2331.  * mode:C
  2332.  * ChangeLog:ChangeLog
  2333.  * End:
  2334.  */
  2335.