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 >
Wrap
Text File
|
1990-11-01
|
60KB
|
2,335 lines
/* info -- a stand-alone Info program
Copyright (C) 1987 Free Software Foundation, Inc.
This file is part of GNU Info.
GNU Info is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY. No author or distributor accepts
responsibility to anyone for the consequences of using it or for
whether it serves any particular purpose or works at all, unless he
says so in writing. Refer to the GNU Emacs General Public License
for full details.
Everyone is granted permission to copy, modify and redistribute
GNU Info, but only under the conditions described in the GNU Emacs
General Public License. A copy of this license is supposed to
have been given to you along with GNU Emacs so you can know your
rights and responsibilities. It should be in a file named COPYING.
Among other things, the copyright notice and this notice must be
preserved on all copies. */
/* MS-DOS port (c) 1990 by Thorsten Ohl, td12@ddagsi3.bitnet
This port is also distributed under the terms of the
GNU General Public License as published by the
Free Software Foundation.
Please note that this file is not identical to the
original GNU release, you should have received this
code as patch to the official release.
$Header: e:/gnu/info/RCS/info.c 0.1.1.2 90/10/26 20:05:12 tho Exp $
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#ifdef MSDOS
#include <process.h>
#include <conio.h>
#undef getc
#define getc(stream) pc_getc () /* read from console */
#else /* not MSDOS */
#include <sgtty.h>
#endif /* not MSDOS */
#include <signal.h>
#ifndef MSDOS
#include <pwd.h>
#endif
#include "getopt.h"
#ifdef SYSV
struct passwd *getpwnam ();
#include <fcntl.h>
#define bcopy(source, dest, count) memcpy(dest, source, count)
#define index strchr
#else
#include <sys/file.h>
#endif
#ifndef DEFAULT_INFOPATH
#define DEFAULT_INFOPATH ".:/usr/gnu/info:/usr/local/emacs/info:/usr/local/lib/emacs/info"
#endif
#ifdef MSDOS
#define LONG long
#else /* not MSDOS */
#define LONG int
#endif /* not MSDOS */
typedef int boolean;
#define true 1
#define false 0
#define UNIX
typedef struct nodeinfo
{
char *filename;
char *nodename;
LONG pagetop;
LONG nodetop;
struct nodeinfo *next;
} NODEINFO;
typedef struct indirectinfo
{
char *filename;
LONG first_byte;
} INDIRECT_INFO;
typedef int Function ();
#define PROJECT_NAME "GNU Info"
#define barf(msg) fprintf(stderr, "%s\n", msg)
/* Some character stuff. */
#define control_character_threshold 0x020 /* smaller than this is control */
#define meta_character_threshold 0x07f /* larger than this is Meta. */
#define control_character_bit 0x40 /* 0x000000, must be off. */
#define meta_character_bit 0x080/* x0000000, must be on. */
#define info_separator_char '\037'
#define start_of_node_string "\037"
#ifdef CTRL
#undef CTRL
#endif
#define CTRL(c) ((c) & (~control_character_bit))
#define META(c) ((c) | meta_character_bit)
#define UNMETA(c) ((c) & (~meta_character_bit))
#define UNCTRL(c) to_upper(((c)|control_character_bit))
#ifndef to_upper
#define to_upper(c) (((c) < 'a' || (c) > 'z') ? c : c-32)
#define to_lower(c) (((c) < 'A' || (c) > 'Z') ? c : c+32)
#endif
#define CTRL_P(c) ((c) < control_character_threshold)
#define META_P(c) ((c) > meta_character_threshold)
#define NEWLINE '\n'
#define RETURN CTRL('M')
#define DELETE 0x07f
#define TAB '\t'
#define ABORT_CHAR CTRL('G')
#define PAGE CTRL('L')
#define SPACE 0x020
#define ESC CTRL('[')
#define control_display_prefix '^'
#define TAG_TABLE_END_STRING "\037\nEND TAG TABLE"
#define TAG_TABLE_BEG_STRING "\nTAG TABLE:\n"
#define NODE_ID "Node:"
#define FILENAME_LEN 256
#define NODENAME_LEN 256
#define STRING_SIZE 256
#define nodeend_sequence "\n\037"
/* All right, some windows stuff. */
typedef struct
{
int left, top, right, bottom;
int ch, cv;
} WINDOW;
typedef struct _wind_list
{
int left, top, right, bottom;
int ch, cv;
struct _wind_list *next_window;
} WINDOW_LIST;
/* Stuff that everyone has to know about. */
extern WINDOW the_window;
extern WINDOW echo_area;
extern LONG pagetop, nodetop, nodebot;
extern int nodelines;
extern char current_info_file[], current_info_node[], last_loaded_info_file[];
extern char *info_file;
#ifdef MSDOS
#define VOID void
#if defined (_MSC_VER) && (_MSC_VER >= 600)
#define CDECL _cdecl
#else
#define CDECL cdecl
#endif
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <assert.h>
#include "pc_term.h"
static void replace_loosing_sscanf (char *buf, char *str, long *np);
char *extract_colon_unit (char *string, int *index);
char *make_temp_filename (char *starter);
char *opsys_filename (char *partial);
char *xmalloc (int bytes);
void add_completion (char *identifier, char *data);
void advance (int amount);
LONG back_lines (int count, LONG starting_pos);
void begin_info_session (void);
int blink_cursor (void);
int brians_error (void);
int build_menu (void);
int build_notes (void);
int character_output_function (char character);
void charout (int character);
void clean_up (char *string);
void clear_echo_area (void);
void clear_eol (void);
void clear_eop (void);
void clear_eop_slowly (void);
void close_echo_area (void);
void close_typeout (void);
int complete (char *text, struct completion_entry *list);
int deletefile (char *filename);
void ding (void);
void display_carefully (int character);
int CDECL display_error (char *format_string, ...);
void display_page (void);
int display_width (int character, int hpos);
int dump_current_node (char *filename);
int extract_field (char *field_name, char *nodename, LONG offset);
int file_error (char *file);
LONG find_footnote_ref (LONG from);
int find_menu_node (char *string, char *nodename);
LONG find_node_in_file (char *nodename, LONG offset);
LONG find_node_in_tag_table (char *nodename, LONG offset);
LONG find_node_start (LONG start);
int find_note_node (char *string, char *nodename);
LONG forward_lines (int count, LONG starting_pos);
void free_completion_list (void);
void generic_page_display (void);
int get_info_file (char *filename, int remember_name);
int get_menu (int item);
int get_node (char *filename, char *nodename, int popping);
void get_node_extent (void);
int get_y_or_n_p (void);
void goto_xy (int xpos, int ypos);
void help_possible_completions (char *text);
void help_use_info (void);
void indent_to (int screen_column);
void CDECL info_signal_handler (int sig);
void init_completer (void);
void init_echo_area (int left, int top, int right, int bottom);
void init_terminal_io (void);
void install_signals (void);
void I_goto_xy (int xpos, int ypos);
int looking_at (char *string, LONG pointer);
void CDECL main (int argc, char **argv);
void make_modeline (void);
void new_echo_area (void);
int next_node (void);
int next_page (void);
void open_typeout (void);
int pop_node (char *filename, char *nodename, LONG *nodetop, LONG *pagetop);
void pop_window (void);
int prev_node (void);
int prev_page (void);
void print_cr (void);
void CDECL print_string (char *string, ...);
void print_tab (void);
int push_node (char *filename, char *nodename, LONG page_position,
LONG node_position);
void push_window (void);
int readline (char *prompt, char *line, int maxchars, int completing);
void remember_completion (struct completion_entry *pointer);
void restore_io (void);
int scan_list (char *string, char *nodename);
LONG search_backward (char *string, LONG starting_pos);
LONG search_forward (char *string, LONG starting_pos);
void set_search_constraints (char *buffer, LONG extent);
void set_window (WINDOW *window);
LONG skip_whitespace (LONG offset);
LONG skip_whitespace_and_cr (LONG offset);
LONG string_in_line (char *string, LONG pointer);
void toploop (void);
LONG to_beg_line (LONG from);
LONG to_end_line (LONG from);
int try_complete (char *text);
int up_node (void);
void usage (void);
int CDECL with_output_to_window (WINDOW *window, int (*function) (), ...);
struct completion_entry *reverse_list (struct completion_entry *list);
#else /* not MSDOS */
#define VOID
#define CDECL
char *malloc ();
char *realloc ();
char *xmalloc ();
char *xrealloc ();
char *getenv ();
char *index ();
char *strcat ();
char *strcpy ();
#endif /* not MSDOS */
boolean build_menu ();
boolean find_menu_node ();
char *opsys_filename ();
#define MAX_INDIRECT_FILES 100 /* a crock, this should be done in a different way. */
NODEINFO *Info_History = NULL; /* the info history list. */
INDIRECT_INFO indirect_list[MAX_INDIRECT_FILES]; /* ?Can't have more than xx files in the indirect list? */
char current_info_file[FILENAME_LEN]; /* the filename of the currently loaded info file. */
char current_info_node[NODENAME_LEN]; /* the nodename of the node the user is looking at. */
char last_loaded_info_file[FILENAME_LEN]; /* the last file actually loaded. Not the same as current info file. */
LONG nodetop, nodebot; /* offsets in info_file of top and bottom of current_info_node. */
int nodelines; /* number of lines in this node. */
char *info_file = NULL; /* buffer for the info file. */
LONG info_buffer_len; /* length of the above buffer. */
char *tag_table = NULL; /* Pointer to the start of a tag table, or NULL to show none. */
LONG tag_buffer_len; /* length of the above buffer. */
boolean indirect = false; /* If true, the tag table is indirect. */
LONG indirect_top;
LONG pagetop; /* offset in the buffer of the current pagetop. */
/* If non-NULL, this is a colon separated list of directories to search
for a specific info file. The user places this variable into his or
her environment. */
char *infopath = NULL;
char dumpfile[FILENAME_LEN]; /* If filled, the name of a file to write to. */
/* **************************************************************** */
/* */
/* Getting Started. */
/* */
/* **************************************************************** */
/* Begin the Info session. */
/* Global is on until we are out of trouble. */
int totally_inhibit_errors = 1;
struct option long_options[] =
{
{"directory", 1, 0, 'd'},
{"node", 1, 0, 'n'},
{"file", 1, 0, 'f'},
{"output", 1, 0, 'o'},
{NULL, 0, NULL, 0}
};
#define savestring(x) strcpy (xmalloc (1 + strlen (x)), (x))
VOID CDECL
main (argc, argv)
int argc;
char **argv;
{
int c;
int ind;
char filename[FILENAME_LEN];
char nodename[NODENAME_LEN];
char *env_infopath = getenv ("INFOPATH");
nodename[0] = filename[0] = '\0';
if (env_infopath && *env_infopath)
infopath = savestring (env_infopath);
else
infopath = savestring (DEFAULT_INFOPATH);
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':
strncpy (nodename, optarg, FILENAME_LEN);
break;
case 'f':
strncpy (filename, optarg, FILENAME_LEN);
break;
case 'o':
strncpy (dumpfile, optarg, FILENAME_LEN);
break;
default:
usage ();
}
}
/* Okay, flags are parsed. Get possible Info menuname. */
/* Start with DIR or whatever was specified. */
if (!get_node (filename, nodename, false)
&& !get_node ((char *) NULL, (char *) NULL, true))
{
if (!*filename)
strcpy (filename, "dir");
fprintf (stderr, "%s: Cannot find \"%s\", anywhere along the\n",
argv[0], filename);
fprintf (stderr, "search path of \"%s\".\n", infopath);
exit (1);
}
totally_inhibit_errors = 0;
get_node (filename, nodename, false);
if (optind != argc)
{
putchar ('\n');
while (optind != argc)
{
if (build_menu () &&
find_menu_node (argv[optind], nodename) &&
get_node ((char *) NULL, nodename, false))
{
#ifdef NEVER
/* 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++;
}
else
break;
}
}
begin_info_session ();
}
VOID
usage ()
{
fprintf (stderr,
"Usage: info [-d dir-path] [-f info-file] [-n node-name] [-o output-file]\n");
fprintf (stderr,
" [+directory dir-path] [+file info-file] [+node node-name]\n");
fprintf (stderr,
" [+output output-file] [menu-selection...]\n");
exit (1);
}
#ifdef SIGTSTP
Function *old_stop; /* last value of SIGSTOP. */
Function *old_cont; /* last value of SIGCONT. */
#endif
#ifdef SIGWINCH
Function *old_winch; /* last value of SIGWINCH. */
#endif
VOID
begin_info_session ()
{
/* Start using Info. */
#ifndef MSDOS
int info_signal_handler ();
#endif /* not MSDOS */
/* If the user just wants to dump the node, then do that. */
if (dumpfile[0])
{
dump_current_node (dumpfile);
exit (0);
}
init_terminal_io ();
/* Install handlers for restoring/breaking the screen. */
install_signals ();
new_echo_area ();
print_string ("Welcome to Info! Type \"?\" for help. ");
close_echo_area ();
toploop ();
restore_io ();
}
VOID CDECL
info_signal_handler (sig)
int sig;
{
/* Do the right thing with this signal. */
switch (sig)
{
#ifdef SIGTSTP
case SIGTSTP:
restore_io ();
signal (SIGTSTP, old_stop);
kill (getpid (), SIGSTOP); /* Some weenie says this is the right thing. */
break;
case SIGCONT:
/* Try to win some more. Reset IO state, and stuff
like that. */
clear_screen ();
display_page ();
goto_xy (the_window.ch, the_window.cv);
opsys_init_terminal ();
signal (SIGTSTP, info_signal_handler);
break;
#endif /* SIGTSTP */
#ifdef SIGWINCH
case SIGWINCH:
/* Window has changed. Get the size of the new window, and rebuild our
window chain. */
{
extern char *widest_line;
extern WINDOW terminal_window;
extern WINDOW_LIST *window_stack;
extern int terminal_lines, terminal_rows;
int delta_width, delta_height, right, bottom;
get_terminal_info ();
right = get_term_width ();
bottom = get_term_height ();
delta_width = right - terminal_rows;
delta_height = bottom - terminal_lines;
terminal_rows = right;
terminal_lines = bottom - 3;
push_window ();
free (widest_line);
widest_line = (char *) xmalloc (right);
set_window (&terminal_window);
push_window ();
/* Make the new window. Map over all windows in window list. */
{
WINDOW_LIST *wind = window_stack;
extern WINDOW modeline_window;
while (wind != (WINDOW_LIST *) NULL)
{
adjust_wind ((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);
}
pop_window ();
clear_screen ();
with_output_to_window (&terminal_window, display_page);
pop_window ();
}
break;
#endif /* SIGWINCH */
case SIGINT:
restore_io ();
exit (1);
break;
}
}
VOID
install_signals ()
{
#ifdef SIGTSTP
old_stop = (Function *) signal (SIGTSTP, info_signal_handler);
old_cont = (Function *) signal (SIGCONT, info_signal_handler);
#endif /* SIGTSTP */
#ifdef SIGWINCH
old_winch = (Function *) signal (SIGWINCH, info_signal_handler);
#endif
signal (SIGINT, info_signal_handler);
}
#ifdef SIGWINCH
adjust_wind (wind, delta_width, delta_height)
WINDOW *wind;
int delta_width, delta_height;
{
wind->right += delta_width;
wind->bottom += delta_height;
if (wind->top)
wind->top += delta_height;
if (wind->left)
wind->left += delta_width;
}
#endif /* SIGWINCH */
/* **************************************************************** */
/* */
/* 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;
VOID
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. */
VOID
add_completion (identifier, data)
char *identifier, *data;
{
COMP_ENTRY *temp = (COMP_ENTRY *) xmalloc (sizeof (COMP_ENTRY));
temp->identifier = (char *) xmalloc (strlen (identifier) + 1);
strcpy (temp->identifier, identifier);
temp->data = (char *) xmalloc (strlen (data) + 1);
strcpy (temp->data, data);
temp->next = completion_list;
completion_list = temp;
}
/* Function for reading a line. Supports completion on COMPLETION_LIST
if you pass COMPLETING as true. Prompt is either a prompt or NULL,
LINE is the place to store the characters that are read. LINE may start
out already containing some characters; if so, they are printed. MAXCHARS
tells how many characters can fit in the buffer at LINE. READLINE returns
FALSE if the user types the abort character. LINE is returned with a '\0'
at the end, not a '\n'. */
boolean
readline (prompt, line, maxchars, completing)
char *prompt, *line;
int maxchars;
boolean completing;
{
int character;
int readline_ch, readline_cv;
int current_len = strlen (line);
int meta_flag = 0;
new_echo_area ();
if (prompt)
print_string (prompt);
readline_ch = the_window.ch;
readline_cv = the_window.cv;
print_string (line);
while (true)
{
line[current_len] = '\0';
goto_xy (readline_ch, readline_cv);
print_string (line);
clear_eol ();
character = blink_cursor ();
if (meta_flag)
{
character = META (character);
meta_flag = 0;
}
if (META_P (character))
character = META (to_upper (UNMETA (character)));
switch (character)
{
case EOF:
character = '\n';
case ESC:
meta_flag++;
break;
case META (DELETE):
case CTRL ('W'):
while (current_len && line[current_len] == SPACE)
current_len--;
if (!current_len)
break;
while (current_len && line[current_len] != SPACE)
current_len--;
break;
case CTRL ('U'):
current_len = 0;
break;
case '\b':
case 0x07f:
if (current_len)
current_len--;
else
ding ();
break;
case '\n':
case '\r':
if (completing)
{
extern int completion_count;
try_complete (line);
if (completion_count >= 1)
{
close_echo_area ();
return (true);
}
else
{
current_len = strlen (line);
break;
}
}
else
{
close_echo_area ();
return (true);
}
case ABORT_CHAR:
ding ();
if (current_len)
{
current_len = 0;
}
else
{
close_echo_area ();
clear_echo_area ();
return (false);
}
break;
case ' ':
case '\t':
case '?':
if (completing)
{
extern int completion_count;
if (character == '?')
{
help_possible_completions (line);
break;
}
else
{
char temp_line[NODENAME_LEN];
strcpy (temp_line, line);
try_complete (line);
if (completion_count != 1 && character == SPACE)
{
if (strcmp (temp_line, line) == 0)
{
line[current_len] = SPACE;
line[current_len + 1] = '\0';
strcpy (temp_line, line);
try_complete (line);
if (completion_count == 0)
{
line[current_len] = '\0';
ding ();
}
}
}
current_len = strlen (line);
if (completion_count == 0)
ding ();
break;
}
}
/* Do *NOT* put anything in-between the completing cases and
the default: case. No. */
default:
if (!CTRL_P (character) && !META_P (character) &&
current_len < maxchars)
{
line[current_len++] = character;
}
else
ding ();
}
}
}
VOID
init_completer ()
{
/* Initialize whatever the completer is using. */
if (completions_core_size != INITIAL_COMPLETIONS_CORE_SIZE)
{
if (completions)
free (completions);
completions = (COMP_ENTRY **) xmalloc ((sizeof (COMP_ENTRY *)) *
(completions_core_size = INITIAL_COMPLETIONS_CORE_SIZE));
}
completion_count = 0;
}
/* Reverse the completion list passed in LIST, and
return a pointer to the new head. */
COMP_ENTRY *
reverse_list (list)
COMP_ENTRY *list;
{
COMP_ENTRY *next;
COMP_ENTRY *prev = (COMP_ENTRY *) NULL;
while (list)
{
next = list->next;
list->next = prev;
prev = list;
list = next;
}
return (prev);
}
VOID
remember_completion (pointer)
COMP_ENTRY *pointer;
{
if (completion_count == completions_core_size)
{
COMP_ENTRY **temp = (COMP_ENTRY **) realloc (completions,
((sizeof (COMP_ENTRY *)) * (completions_core_size += INITIAL_COMPLETIONS_CORE_SIZE)));
if (!temp)
{
display_error ("Too many completions (~d)! Out of core!", completion_count);
return;
}
else
completions = temp;
}
completions[completion_count++] = pointer;
}
boolean
complete (text, list)
char *text;
COMP_ENTRY *list;
{
/* Complete TEXT from identifiers in LIST. Place the resultant
completions in COMPLETIONS, and the number of completions in
COMPLETION_COUNT. Modify TEXT to contain the least common
denominator of all the completions found. */
int low_match, i, index;
int string_length = strlen (text);
init_completer ();
low_match = 1000; /* some large number. */
while (list)
{
if (strnicmp (text, list->identifier, string_length) == 0)
remember_completion (list);
list = list->next;
}
if (completion_count == 0)
return (false);
if (completion_count == 1)
{ /* One completion */
strcpy (text, completion_id (0));
return (true);
}
/* Else find the least common denominator */
index = 1;
while (index < completion_count)
{
int c1, c2;
for (i = 0;
(c1 = to_lower (completion_id (index - 1)[i])) &&
(c2 = to_lower (completion_id (index)[i]));
i++)
if (c1 != c2)
break;
if (low_match > i)
low_match = i;
index++;
}
strncpy (text, completion_id (0), low_match);
text[low_match] = '\0';
return (true);
}
/* Complete TEXT from the completion structures in COMPLETION_LIST. */
boolean
try_complete (text)
char *text;
{
return (complete (text, completion_list));
}
VOID
help_possible_completions (text)
char *text;
{
/* The function that prints out the possible completions. */
char temp_string[2000];
goto_xy (the_window.left, the_window.top);
strcpy (temp_string, text);
try_complete (temp_string);
open_typeout ();
if (completion_count == 0)
{
print_string ("There are no possible completions.\n");
goto print_done;
}
if (completion_count == 1)
{
print_string ("The only possible completion of what you have typed is:\n\n");
#ifdef MSDOS /* well, that was simply wrong... */
print_string (completion_id (0));
#else /* not MSDOS */
print_string (completions[0]);
#endif /* not MSDOS */
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 index = 0;
int counter = 0;
int columns = (the_window.right - the_window.left) / 30;
while (index < completion_count)
{
if (counter == columns)
{
charout ('\n');
counter = 0;
}
indent_to (counter * 30);
print_string (completion_id (index));
counter++;
index++;
}
}
print_done:
print_string ("\n\nDone.\n-----------------\n");
close_typeout ();
}
/* **************************************************************** */
/* */
/* Getting Nodes */
/* */
/* **************************************************************** */
/* A node name looks like:
Node: nodename with spaces but not a comma,
or Node: (filename-containing-node)node-within-file
or Node: (filename)
The latter case implies a nodename of "Top". All files are
supposed to have one.
Lastly, the nodename specified could be "*", which specifies the
entire file. */
/* Load FILENAME. If REMEMBER_NAME is true, then remember the
loaded filename in CURRENT_INFO_FILE. In either case, remember
the name of this file in LAST_LOADED_INFO_FILE. */
boolean
get_info_file (filename, remember_name)
char *filename;
boolean remember_name;
{
FILE *input_stream;
struct stat file_info;
LONG pointer;
int result;
char tempname[FILENAME_LEN];
/* Get real filename. */
strcpy (tempname, opsys_filename (filename));
/* See if the file exists. */
if ((result = stat (tempname, &file_info)) != 0)
{
/* Try again, this time with the name in lower-case. */
char temp_tempname[FILENAME_LEN];
int i;
for (i = 0; temp_tempname[i] = to_lower (tempname[i]); i++);
/* Get real filename again. AMS */
strcpy (temp_tempname, opsys_filename (temp_tempname));
result = stat (temp_tempname, &file_info);
if (!result)
strcpy (tempname, temp_tempname);
}
/* See if this file is the last loaded one. */
if (!result && (strcmp (last_loaded_info_file, tempname) == 0))
{
return (true); /* Okay, the file is loaded. */
}
/* Now try to open the file. */
if (result || (input_stream = fopen (tempname, "r")) == NULL)
{
return (file_error (tempname));
}
/* If we already have a file loaded, then free it first. */
if (info_file)
{
free (info_file);
if (!indirect)
{
/* Then the tag table is also no longer valid. */
tag_table = (char *) NULL;
}
}
/* Read the contents of the file into a new buffer. */
#ifdef MSDOS /* Fix this! allow files > 64k */
assert (file_info.st_size < (1L<<16));
info_file = (char *) xmalloc ((size_t) file_info.st_size);
info_buffer_len
= fread (info_file, 1, (size_t) file_info.st_size, input_stream);
#else /* not MSDOS */
info_file = (char *) xmalloc (info_buffer_len = file_info.st_size);
fread (info_file, 1, info_buffer_len, input_stream);
#endif /* not MSDOS */
fclose (input_stream);
strcpy (last_loaded_info_file, tempname);
if (remember_name)
{
strcpy (current_info_file, tempname);
if (indirect)
{
int index;
indirect = false;
free (tag_table);
for (index = 0; index < MAX_INDIRECT_FILES &&
indirect_list[index].filename != (char *) NULL;
index++)
{
free (indirect_list[index].filename);
indirect_list[index].filename = (char *) NULL;
}
}
}
else
return (true);
/* The file has been read, and we don't know anything about it.
Find out if it contains a tag table. */
tag_table = NULL; /* assume none. */
indirect = false;
tag_buffer_len = 0;
set_search_constraints (info_file, info_buffer_len);
/* Go to the last few lines in the file. */
pointer = back_lines (8, info_buffer_len);
pointer = search_forward (TAG_TABLE_END_STRING, pointer);
if (pointer > -1)
{
/* Then there is a tag table. Find the start of it,
and remember that. */
pointer = search_backward (TAG_TABLE_BEG_STRING, pointer);
/* Handle error for malformed info file. */
if (pointer < 0)
display_error ("Start of tag table not found!");
else
{
/* No problem. If this file is an indirect file, then the contents
of the tag table must remain in RAM the entire time. Otherwise,
we can flush the tag table with the file when the file is flushed.
So, if indirect, remember that, and copy the table to another
place.*/
LONG 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. */
#ifdef MSDOS
assert (tag_buffer_len < (1L<<16));
tag_table = (char *) xmalloc ((size_t) tag_buffer_len);
bcopy (&info_file[indirect_check], tag_table,
(size_t) tag_buffer_len);
#else /* not MSDOS */
tag_table = (char *) xmalloc (tag_buffer_len);
bcopy (&info_file[indirect_check], tag_table, tag_buffer_len);
#endif /* not MSDOS */
/* 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 (false);
}
/* Remember the filenames, and their byte offsets. */
{
/* Index into the filename/offsets array. */
int index = 0;
char temp_filename[FILENAME_LEN];
LONG temp_first_byte;
info_buffer_len = indirect_top;
/* For each line, scan the info into globals. Then save
the information in the INDIRECT_INFO structure. */
while (info_file[indirect_top] != info_separator_char &&
index < MAX_INDIRECT_FILES)
{
indirect_top = forward_lines (1, indirect_top);
if (info_file[indirect_top] == info_separator_char)
break;
/* Ignore blank lines. */
if (info_file[indirect_top] == '\n')
continue;
#ifdef MSDOS
#if 0 /* never */
sscanf (&info_file[indirect_top], "%s%ld",
temp_filename, &temp_first_byte);
#endif /* never */
/* For some obscure reason, Microsoft's sscanf ()
looses from time to time, typically for the first
node in a large tag table. I don't pretent to
understand this, but I have a fix. */
replace_loosing_sscanf (&info_file[indirect_top],
temp_filename, &temp_first_byte);
#else /* not MSDOS */
sscanf (&info_file[indirect_top], "%s%d",
temp_filename, &temp_first_byte);
#endif /* not MSDOS */
if (strlen (temp_filename))
{
temp_filename[strlen (temp_filename) - 1] = '\0';
indirect_list[index].filename =
(char *) xmalloc (strlen (temp_filename) + 1);
strcpy (indirect_list[index].filename, temp_filename);
indirect_list[index].first_byte = temp_first_byte;
index++;
}
}
/* Terminate the table. */
if (index == MAX_INDIRECT_FILES)
{
display_error ("Sorry, the INDIRECT file array isn't large enough.");
index--;
}
indirect_list[index].filename = (char *) NULL;
}
indirect = true;
}
}
}
return (true);
}
boolean
get_node (filename, nodename, popping)
char *nodename, *filename;
boolean popping;
{
/* Make current_info_node be NODENAME. This could involve loading
a file, etc. POPPING is true if we got here because we are popping
one level. */
LONG pointer;
char internal_filename[FILENAME_LEN];
char internal_nodename[NODENAME_LEN];
if (nodename && *nodename)
{
/* Maybe nodename looks like: (filename)nodename, or worse: (filename).
If so, extract the stuff out. */
if (*nodename == '(')
{
/* We have a filename at the start of the nodename.
Find out what it is. */
int temp = 1;
int temp1 = 0;
char character;
filename = internal_filename;
while ((character = nodename[temp]) && character != ')')
{
filename[temp - 1] = character;
temp++;
}
filename[temp - 1] = '\0';
/* We have the filename now. The nodename follows. */
internal_nodename[0] = '\0';
if (nodename[temp])
{
temp++;
while (internal_nodename[temp1++] = nodename[temp++]);
}
nodename = internal_nodename;
}
}
if (!popping)
push_node (current_info_file, current_info_node, pagetop, nodetop);
if (!nodename || !*nodename)
{
nodename = internal_nodename;
strcpy (nodename, "Top");
}
if (!filename || !*filename)
{
filename = internal_filename;
strcpy (filename, current_info_file);
}
if (!*filename)
strcpy (filename, "dir");
if (!get_info_file (filename, true))
goto node_not_found;
if (strcmp (nodename, "*") == 0)
{
/* The "node" that we want is the entire file. */
pointer = 0;
goto found_node;
}
/* If 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)
{
/* Then the search through the tag table failed. Maybe
we should try searching the buffer? Nahh, just barf. */
boolean pop_node ();
node_not_found:
if (popping)
return (false); /* second time through. */
display_error
("Sorry, unable to find the node \"%s\" in the file \"%s\".", nodename, filename);
current_info_file[0] = '\0';
current_info_node[0] = '\0';
last_loaded_info_file[0] = '\0';
pop_node (internal_filename, internal_nodename, &nodetop, &pagetop);
get_node (internal_filename, internal_nodename, true);
return (false);
}
/* Otherwise, the desired location is right here.
Scarf the position byte. */
while (tag_table[pointer] != '\177')
pointer++;
#ifdef MSDOS
#if 0 /* never */
sscanf (&tag_table[pointer + 1], "%ld", &pointer);
#endif /* never */
/* It may be paranoia ... (cf. replace_loosing_sscanf()) */
pointer = atol (&tag_table[pointer + 1]);
#else /* not MSDOS */
sscanf (&tag_table[pointer + 1], "%d", &pointer);
#endif /* not MSDOS */
/* Okay, we have a position pointer. If this is an indirect file,
then we should look through the indirect_list for the first
element.first_byte which is larger than this. Then we can load
the specified file, and win. */
if (indirect)
{
/* Find the filename for this node. */
int index;
for (index = 0; index < MAX_INDIRECT_FILES &&
indirect_list[index].filename != (char *) NULL; index++)
{
if (indirect_list[index].first_byte > pointer)
{
/* We found it. */
break;
}
}
if (!get_info_file (indirect_list[index - 1].filename, true))
goto node_not_found;
pointer -= indirect_list[index - 1].first_byte;
/* Here is code to compensate for the header of an indirect file. */
{
LONG 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
{
/* We don't have a tag table. The node can only be found by
searching this file in its entirety. */
get_info_file (filename, true);
pointer = 0;
}
/* Search this file, using pointer as a good guess where to start. */
/* This is the same number that RMS used. It might be right or wrong. */
pointer -= 1000;
if (pointer < 0)
pointer = 0;
pointer = find_node_in_file (nodename, pointer);
if (pointer < 0)
goto node_not_found;
/* We found the node in it's file. Remember exciting information. */
found_node:
back_lines (0, pointer);
nodetop = pagetop = pointer;
strcpy (current_info_node, nodename);
strcpy (current_info_file, filename);
get_node_extent ();
return (true);
}
/* Get 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. */
VOID
get_node_extent ()
{
LONG index = nodetop;
int character;
boolean do_it_till_end = (strcmp (current_info_node, "*") == 0);
nodelines = 0;
again:
while ((index < info_buffer_len) &&
((character = info_file[index]) != info_separator_char))
{
if (character == '\n')
nodelines++;
index++;
}
if (do_it_till_end && index != info_buffer_len)
{
index++;
goto again;
}
nodebot = index;
}
/* Locate the start of a node in the current search_buffer. Return
the offset to the node start, or minus one. START is the place in
the file at where to begin the search. */
LONG
find_node_start (start)
LONG start;
{
return (search_forward (start_of_node_string, start));
}
/* Find NODENAME in TAG_TABLE. */
LONG
find_node_in_tag_table (nodename, offset)
char *nodename;
LONG offset;
{
LONG temp;
set_search_constraints (tag_table, tag_buffer_len);
temp = offset;
while (true)
{
offset = search_forward (NODE_ID, temp);
if (offset < 0)
return (offset);
temp = skip_whitespace (offset + strlen (NODE_ID));
if (strnicmp (tag_table + temp, nodename, strlen (nodename)) == 0)
if (*(tag_table + temp + strlen (nodename)) == '\177')
return (temp);
}
}
/* Find NODENAME in INFO_FILE. */
LONG
find_node_in_file (nodename, offset)
char *nodename;
LONG offset;
{
LONG temp;
set_search_constraints (info_file, info_buffer_len);
while (true)
{
offset = find_node_start (offset);
if (offset < 0)
return (offset);
else
temp = forward_lines (1, offset);
if (temp == offset)
return (-1); /* At last line now, just a node start. */
else
offset = temp;
temp = string_in_line (NODE_ID, offset);
if (temp > -1)
{
temp = skip_whitespace (temp + strlen (NODE_ID));
if (strnicmp (info_file + temp, nodename, strlen (nodename)) == 0)
{
int check_exact = *(info_file + temp + strlen (nodename));
if (check_exact == '\t' ||
check_exact == ',' ||
check_exact == '.' ||
check_exact == '\n')
return (offset);
}
}
}
}
#ifdef MSDOS
/* Nomen est omen ... */
void
replace_loosing_sscanf (char *buf, char *str, long *np)
{
while (*buf && !isspace (*buf))
*str++ = *buf++;
*str = '\0';
*np = atol (buf);
}
#endif /* not MSDOS */
/* **************************************************************** */
/* */
/* Dumping and Printing Nodes */
/* */
/* **************************************************************** */
/* Make a temporary filename based on STARTER and the PID of this Info. */
char *
make_temp_filename (starter)
char *starter;
{
#ifdef MSDOS /* make a digestable filename... */
char *temp = (char *) xmalloc (strlen (starter) + 1);
char *p = temp;
while (*starter)
{
*p++ = isalnum (*starter) ? *starter : '-';
starter++;
}
*p = '\0';
#else /* not MSDOS */
char *temp = (char *) xmalloc (FILENAME_LEN);
sprintf (temp, "%s-%d", starter, getpid ());
#endif /* not MSDOS */
return (temp);
}
/* Delete a file. Print errors if necessary. */
deletefile (filename)
char *filename;
{
if (unlink (filename) != 0)
return (file_error (filename));
else
return (0);
}
#ifndef MSDOS
/* Print the contents of FILENAME using lpr. */
char *print_command = "lpr -p ";
printfile (filename)
char *filename;
{
int length = strlen (print_command) + strlen (filename) + strlen ("\n") + 1;
char *command = (char *) xmalloc (length);
int error;
display_error ("Printing file `%s'...\n", filename);
sprintf (command, "%s%s", print_command, filename);
error = system (command);
if (error)
display_error ("Can't invoke `%s'", command);
free (command);
return (error);
}
#endif /* not MSDOS */
/* 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;
{
LONG i = nodetop;
int c;
FILE *output_stream = fopen (filename, "w");
if (output_stream == (FILE *) NULL)
return (file_error (filename));
while (i < nodebot && i < info_buffer_len)
{
c = info_file[i];
if (c < 0x020 && !(index ("\n\t\f", c)))
{
putc ('^', output_stream);
}
if (putc (info_file[i++], output_stream) == EOF)
{
fclose (output_stream);
return (file_error (filename));
}
}
fclose (output_stream);
return (0);
}
/* **************************************************************** */
/* */
/* Toplevel eval loop. */
/* */
/* **************************************************************** */
#define MENU_HEADER "\n* Menu:"
#define MENU_ID "\n* "
#define FOOTNOTE_HEADER "*Note"
/* Number of items that the current menu has. */
int the_menu_size = 0;
/* The node that last made a menus completion list. */
#ifdef MSDOS
char menus_nodename[NODENAME_LEN]; /* this should be correct for GNU too... */
char menus_filename[NODENAME_LEN];
#else /* not MSDOS */
char *menus_nodename[NODENAME_LEN];
char *menus_filename[NODENAME_LEN];
#endif /* not MSDOS */
boolean window_bashed = false;
char *visible_footnote = 0; /* The default prompt string for the Follow Reference command. */
VOID
toploop ()
{
boolean done = false;
boolean inhibit_display = false;
int command;
char nodename[NODENAME_LEN];
while (!done)
{
if (!inhibit_display || window_bashed)
display_page ();
inhibit_display = window_bashed = false;
nodename[0] = '\0'; /* Don't display old text in input line. */
goto_xy (echo_area.left, echo_area.top);
command = blink_cursor ();
clear_echo_area ();
if (command == EOF)
{
done = true;
continue;
}
command = to_upper (command);
switch (command)
{
case 'D':
get_node ((char *) NULL, "(dir)Top", false);
break;
case 'H':
if ((the_window.bottom - the_window.top) < 24)
get_node ((char *) NULL, "(info)Help-Small-Screen", false);
else
get_node ((char *) NULL, "(info)Help", false);
break;
case 'N':
if (!next_node ())
{
display_error ("No NEXT for this node!");
inhibit_display = true;
}
break;
case 'P':
if (!prev_node ())
{
display_error ("No PREV for this node!");
inhibit_display = true;
}
break;
case 'U':
if (!up_node ())
{
display_error ("No UP for this node!");
inhibit_display = true;
}
break;
case 'M':
if (!build_menu ())
{
display_error ("No menu in this node!");
inhibit_display = true;
break;
}
if (!readline ("Menu item: ", nodename, NODENAME_LEN, true))
{
clear_echo_area ();
inhibit_display = true;
break;
}
I_goto_xy (echo_area.left, echo_area.top);
if (!find_menu_node (nodename, nodename))
{
display_error ("\"%s\" is not a menu item!", nodename);
inhibit_display = true;
break;
}
if (get_node ((char *) NULL, nodename, false))
clear_echo_area ();
break;
case 'F':
{
char footnote[NODENAME_LEN];
if (!build_notes ())
{
display_error ("No cross-references in this node!");
inhibit_display = true;
break;
}
strcpy (footnote, visible_footnote);
if (!readline ("Follow reference: ", footnote, NODENAME_LEN, true))
{
inhibit_display = true;
break;
}
I_goto_xy (echo_area.left, echo_area.top);
if (!find_note_node (footnote, nodename))
{
display_error ("\"%s\" is not a cross-reference in this node!", footnote);
inhibit_display = true;
break;
}
if (get_node ((char *) NULL, nodename, false))
clear_echo_area ();
break;
}
case 'L':
{
char filename[FILENAME_LEN], nodename[NODENAME_LEN];
LONG ptop, ntop;
if (pop_node (filename, nodename, &ntop, &ptop) &&
get_node (filename, nodename, true))
{
pagetop = ptop;
}
else
inhibit_display = true;
break;
}
case SPACE:
if (!next_page ())
{
display_error ("At last page of this node now!");
inhibit_display = true;
}
break;
case DELETE:
if (!prev_page ())
{
display_error ("At first page of this node now!");
inhibit_display = true;
}
break;
case 'B':
if (pagetop == nodetop)
{
display_error ("Already at beginning of this node!");
inhibit_display = true;
}
else
pagetop = nodetop;
break;
/* I don't want to do this this way, but the documentation clearly states
that '6' doesn't work. It states this for a reason, and ours is not to
wonder why... */
case '1':
case '2':
case '3':
case '4':
case '5':
{
int item = command - '0';
if (!build_menu ())
{
display_error ("No menu in this node!");
inhibit_display = true;
break;
}
if (item > the_menu_size)
{
display_error ("There are only %d items in the menu!",
the_menu_size);
inhibit_display = true;
break;
}
if (!get_menu (item))
inhibit_display = true;
break;
}
case 'G':
if (!readline ("Goto node: ", nodename, NODENAME_LEN, false))
{
inhibit_display = true;
break;
}
clear_echo_area ();
get_node ((char *) NULL, nodename, false);
break;
case 'S':
{
/* Search from the starting position forward for a string.
Of course, this should really wrap the search around, but
it doesn't do that yet. Select the node containing the
desired string. Put the top of the page screen_lines/2
lines behind it, but not before nodetop. */
extern LONG info_buffer_len;
LONG pointer, temp;
LONG search_start = pagetop;
if (!readline ("Search for string: ", nodename, NODENAME_LEN, false))
{
inhibit_display = true;
break;
}
I_goto_xy (echo_area.left, echo_area.top);
if (indirect)
{
/* Put the indirect search right here. */
display_error ("This is an indirect file, and I can't search these yet!");
break;
}
else
{
set_search_constraints (info_file, info_buffer_len);
pointer = search_forward (nodename, search_start);
if (pointer == -1)
{
display_error ("\"%s\" not found! Try another info file.", nodename);
inhibit_display = true;
break;
}
}
temp = search_backward (start_of_node_string, pointer);
if (temp == -1)
temp = search_forward (start_of_node_string, pointer);
if (temp == -1)
{
brians_error ();
break;
}
search_start = pointer;
pointer = forward_lines (1, temp);
if (!extract_field ("Node:", nodename, pointer))
{
display_error ("There doesn't appear to be a nodename for this node.");
get_node ((char *) NULL, "*", false);
pagetop = pointer;
break;
}
if (get_node ((char *) NULL, nodename, false))
clear_echo_area ();
/* Make the string that the user wanted be visible, and
centered in the screen. */
{
pointer = back_lines ((the_window.bottom - the_window.top) / 2, search_start);
if (pointer < nodetop)
pointer = nodetop;
pagetop = pointer;
}
break;
}
case CTRL ('H'):
case '?':
help_use_info ();
break;
case 'Q':
done = true;
break;
case CTRL ('L'): /* Control-l is redisplay */
break;
case '(': /* You *must* be trying to type a complete nodename. */
strcpy (nodename, "(");
if (!readline ("Goto node: ", nodename, NODENAME_LEN, false))
{
inhibit_display = true;
clear_echo_area ();
break;
}
I_goto_xy (echo_area.left, echo_area.top);
if (get_node ((char *) NULL, nodename, false))
clear_echo_area ();
break;
case CTRL ('P'):
/* Print the contents of this node on the default printer. We
would like to let the user specify the printer, but we don't
want to ask her each time which printer to use. Besides, he
might not know, which is why it (the user) is in the need of
Info. */
{
char *tempname = make_temp_filename (current_info_node);
#ifdef MSDOS
if (dump_current_node (tempname) == 0)
display_error ("Dumped node to `%s'.\n", tempname);
#else /* not MSDOS */
if (dump_current_node (tempname) == 0 &&
printfile (tempname) == 0 &&
deletefile (tempname) == 0)
{
display_error ("Printed node. Go pick up your output.\n");
}
#endif /* not MSDOS */
inhibit_display = true;
free (tempname);
}
break;
default:
inhibit_display = true;
display_error ("Unknown command! Press '?' for help.");
}
}
}
VOID
help_use_info ()
{
/* Tell this person how to use Info. */
open_typeout ();
print_string ("\n\
Commands in Info\n\
\n\
h Invoke the Info tutorial.\n\
\n\
Selecting other nodes:\n\
n Move to the \"next\" node of this node.\n\
p Move to the \"previous\" node of this node.\n\
u Move \"up\" from this node.\n\
m Pick menu item specified by name.\n\
Picking a menu item causes another node to be selected.\n\
f Follow a cross reference. Reads name of reference.\n\
l Move to the last node you were at.\n\
\n\
Moving within a node:\n\
Space Scroll forward a page.\n\
DEL Scroll backward a page.\n\
b Go to the beginning of this node.\n\
\n\
Advanced commands:\n\
q Quit Info.\n\
1 Pick first item in node's menu.\n\
2 - 5 Pick second ... fifth item in node's menu.\n\
g Move to node specified by name.\n\
You may include a filename as well, as (FILENAME)NODENAME.\n\
s Search through this Info file for a specified string,\n\
and select the node in which the next occurrence is found.\n\
Ctl-p Print the contents of this node using `lpr -p'.\n\
\n\
Done.\n\n");
close_typeout ();
}
boolean
next_node ()
{
/* Move to the node specified in the NEXT field. */
char nodename[NODENAME_LEN];
if (!extract_field ("Next:", nodename, nodetop))
return (false);
return (get_node ((char *) NULL, nodename, false));
}
boolean
prev_node ()
{
/* Move to the node specified in the PREVIOUS field. */
char nodename[NODENAME_LEN];
if (!extract_field ("Previous:", nodename, nodetop)
&& !extract_field ("Prev:", nodename, nodetop))
return (false);
return (get_node ((char *) NULL, nodename, false));
}
boolean
up_node ()
{
/* Move to the node specified in the UP field. */
char nodename[NODENAME_LEN];
if (!extract_field ("Up:", nodename, nodetop))
return (false);
return (get_node ((char *) NULL, nodename, false));
}
/* Build a completion list of menuname/nodename for each
line in this node that is a menu item. */
boolean
build_menu ()
{
LONG pointer = nodetop;
char menuname[NODENAME_LEN];
char nodename[NODENAME_LEN];
if (strcmp (menus_nodename, current_info_node) == 0 &&
strcmp (menus_filename, current_info_file) == 0)
return (the_menu_size != 0);
strcpy (menus_nodename, current_info_node);
strcpy (menus_filename, current_info_file);
free_completion_list ();
set_search_constraints (info_file, nodebot);
if ((pointer = search_forward (MENU_HEADER, nodetop)) < 0)
#ifdef MSDOS
{
/* We have *not* build a completion list! */
menus_filename[0] = menus_nodename[0] = '\0';
return (false);
}
#else /* not MSDOS */
return (false);
#endif /* not MSDOS */
/* There is a menu here. Look for members of it. */
pointer += strlen (MENU_HEADER);
while (true)
{
int index;
pointer = search_forward (MENU_ID, pointer);
if (pointer < 0)
break; /* no more menus in this node. */
pointer = (skip_whitespace (pointer + strlen (MENU_ID)));
index = 0;
while ((menuname[index] = info_file[pointer]) && menuname[index] != ':')
{
index++, pointer++;
}
menuname[index] = '\0';
pointer++;
if (info_file[pointer] == ':')
{
strcpy (nodename, menuname);
}
else
{
pointer = skip_whitespace (pointer);
index = 0;
while ((nodename[index] = info_file[pointer]) &&
nodename[index] != '\t' &&
nodename[index] != '.' &&
nodename[index] != ',')
{
index++, pointer++;
}
nodename[index] = '\0';
}
add_completion (menuname, nodename);
the_menu_size++;
}
if (the_menu_size)
completion_list = reverse_list (completion_list);
#ifdef MSDOS
else
/* We have *not* build a completion list! */
menus_filename[0] = menus_nodename[0] = '\0';
#endif /* MSDOS */
return (the_menu_size != 0);
}
boolean
get_menu (item)
int item;
{
/* Select ITEMth item from the list built by build_menu. */
if (!build_menu ())
return (false);
if (item > the_menu_size)
return (false);
else
{
COMP_ENTRY *temp = completion_list;
while (--item && temp)
temp = temp->next;
return (get_node ((char *) NULL, temp->data, false));
}
}
boolean
find_menu_node (string, nodename)
char *string, *nodename;
{
/* Scan through the ?already? built menu list looking
for STRING. If you find it, put the corresponding nodes
name in NODENAME. */
return (scan_list (string, nodename));
}
boolean
scan_list (string, nodename)
char *string, *nodename;
{
/* The work part of find_menu_node and find_note_node. */
COMP_ENTRY *temp = completion_list;
while (temp)
{
if (strnicmp (string, temp->identifier, strlen (string)) == 0)
{
strcpy (nodename, temp->data);
return (true);
}
temp = temp->next;
}
return (false);
}
VOID
clean_up (string)
char *string;
{
/* Remove <CR> and whitespace from string, replacing them with
only one space. Exception: <CR> at end of string disappears. */
char *to = string;
char last_char = 0;
boolean result;
while (*to = *string++)
{
if (*to == '\n')
{
*to = SPACE;
if (!(*(to + 1)))
{
*to = '\0';
return;
}
}
result = (last_char == SPACE);
last_char = *to;
if (last_char != SPACE)
to++;
else if (!result)
to++;
}
}
LONG
find_footnote_ref (from)
LONG from;
{
/* Find a reference to "*Note". Return the offset of
the start of that reference, or -1. */
while (true)
{
from = search_forward (FOOTNOTE_HEADER, from);
if (from < 0)
return (from);
else
from += strlen (FOOTNOTE_HEADER);
if (info_file[from] == ' ' ||
info_file[from] == '\n' ||
info_file[from] == '\t')
return (from);
}
}
boolean
build_notes ()
{
/* Build an array of (footnote.nodename) for each footnote in this node. */
LONG pointer;
char notename[NODENAME_LEN];
char nodename[NODENAME_LEN];
set_search_constraints (info_file, nodebot);
if ((find_footnote_ref (nodetop)) < 0)
return (false);
pointer = nodetop;
menus_filename[0] = menus_nodename[0] = '\0';
visible_footnote = "";
free_completion_list ();
while (true)
{
int index;
pointer = find_footnote_ref (pointer);
if (pointer < 0)
break; /* no more footnotes in this node. */
pointer = skip_whitespace_and_cr (pointer);
index = 0;
while ((notename[index] = info_file[pointer]) && notename[index] != ':')
{
index++, pointer++;
}
notename[index] = '\0';
clean_up (notename);
pointer++;
if (info_file[pointer] == ':')
{
strcpy (nodename, notename);
}
else
{
pointer = skip_whitespace (pointer);
index = 0;
while ((nodename[index] = info_file[pointer]) &&
nodename[index] != '\t' &&
nodename[index] != '.' &&
nodename[index] != ',')
{
index++, pointer++;
}
nodename[index] = '\0';
}
/* Add the notename/nodename to the list. */
add_completion (notename, nodename);
the_menu_size++;
/* Remember this identifier as the default if it is the first one in the
page. */
if (!(*visible_footnote) &&
pointer > pagetop &&
pointer < forward_lines (the_window.bottom - the_window.top, pointer))
visible_footnote = completion_list->identifier;
}
if (the_menu_size)
completion_list = reverse_list (completion_list);
return (the_menu_size != 0);
}
boolean
find_note_node (string, nodename)
char *string, *nodename;
{
/* Scan through the ?already? built footnote list looking
for STRING. If found, place the corresponding node name
in NODENAME. */
return (scan_list (string, nodename));
}
/*===(cut here)===*/
/* Include the rest: */
#include "info.d"
/*
* Local Variables:
* mode:C
* ChangeLog:ChangeLog
* End:
*/