summaryrefslogtreecommitdiff
path: root/gnu/usr.bin/texinfo/info/session.c
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/usr.bin/texinfo/info/session.c')
-rw-r--r--gnu/usr.bin/texinfo/info/session.c4266
1 files changed, 4266 insertions, 0 deletions
diff --git a/gnu/usr.bin/texinfo/info/session.c b/gnu/usr.bin/texinfo/info/session.c
new file mode 100644
index 00000000000..50befb8388b
--- /dev/null
+++ b/gnu/usr.bin/texinfo/info/session.c
@@ -0,0 +1,4266 @@
+/* session.c -- The user windowing interface to Info. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+ stored in Info format.
+
+ Copyright (C) 1993 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#if defined (HAVE_SYS_FILE_H)
+#include <sys/file.h>
+#endif /* HAVE_SYS_FILE_H */
+#include <sys/ioctl.h>
+#include <fcntl.h>
+
+#if defined (HAVE_SYS_TIME_H)
+# include <sys/time.h>
+# define HAVE_STRUCT_TIMEVAL
+#endif /* HAVE_SYS_TIME_H */
+
+#if defined (HANDLE_MAN_PAGES)
+# include "man.h"
+#endif
+
+static void info_clear_pending_input (), info_set_pending_input ();
+static void info_handle_pointer ();
+
+/* **************************************************************** */
+/* */
+/* Running an Info Session */
+/* */
+/* **************************************************************** */
+
+/* The place that we are reading input from. */
+static FILE *info_input_stream = (FILE *)NULL;
+
+/* The last executed command. */
+VFunction *info_last_executed_command = (VFunction *)NULL;
+
+/* Becomes non-zero when 'q' is typed to an Info window. */
+int quit_info_immediately = 0;
+
+/* Array of structures describing for each window which nodes have been
+ visited in that window. */
+INFO_WINDOW **info_windows = (INFO_WINDOW **)NULL;
+
+/* Where to add the next window, if we need to add one. */
+static int info_windows_index = 0;
+
+/* Number of slots allocated to INFO_WINDOWS. */
+static int info_windows_slots = 0;
+
+void remember_window_and_node (), forget_window_and_nodes ();
+void initialize_info_session (), info_session ();
+void display_startup_message_and_start ();
+
+/* Begin an info session finding the nodes specified by FILENAME and NODENAMES.
+ For each loaded node, create a new window. Always split the largest of the
+ available windows. */
+void
+begin_multiple_window_info_session (filename, nodenames)
+ char *filename;
+ char **nodenames;
+{
+ register int i;
+ WINDOW *window = (WINDOW *)NULL;
+
+ for (i = 0; nodenames[i]; i++)
+ {
+ NODE *node;
+
+ node = info_get_node (filename, nodenames[i]);
+
+ if (!node)
+ break;
+
+ /* If this is the first node, initialize the info session. */
+ if (!window)
+ {
+ initialize_info_session (node);
+ window = active_window;
+ }
+ else
+ {
+ /* Find the largest window in WINDOWS, and make that be the active
+ one. Then split it and add our window and node to the list
+ of remembered windows and nodes. Then tile the windows. */
+ register WINDOW *win, *largest = (WINDOW *)NULL;
+ int max_height = 0;
+
+ for (win = windows; win; win = win->next)
+ if (win->height > max_height)
+ {
+ max_height = win->height;
+ largest = win;
+ }
+
+ if (!largest)
+ {
+ display_update_display (windows);
+ info_error (CANT_FIND_WIND);
+ info_session ();
+ exit (0);
+ }
+
+ active_window = largest;
+ window = window_make_window (node);
+ if (window)
+ {
+ window_tile_windows (TILE_INTERNALS);
+ remember_window_and_node (window, node);
+ }
+ else
+ {
+ display_update_display (windows);
+ info_error (WIN_TOO_SMALL);
+ info_session ();
+ exit (0);
+ }
+ }
+ }
+ display_startup_message_and_start ();
+}
+
+/* Start an info session with INITIAL_NODE, and an error message in the echo
+ area made from FORMAT and ARG. */
+void
+begin_info_session_with_error (initial_node, format, arg)
+ NODE *initial_node;
+ char *format;
+ void *arg;
+{
+ initialize_info_session (initial_node);
+ info_error (format, arg, (void *)NULL);
+ info_session ();
+}
+
+/* Start an info session with INITIAL_NODE. */
+void
+begin_info_session (initial_node)
+ NODE *initial_node;
+{
+ initialize_info_session (initial_node);
+ display_startup_message_and_start ();
+}
+
+void
+display_startup_message_and_start ()
+{
+ char *format;
+
+ format = replace_in_documentation
+ ("Welcome to Info version %s. \"\\[get-help-window]\" for help, \"\\[menu-item]\" for menu item.");
+
+ window_message_in_echo_area (format, version_string ());
+ info_session ();
+}
+
+/* Run an info session with an already initialized window and node. */
+void
+info_session ()
+{
+ terminal_prep_terminal ();
+ display_update_display (windows);
+ info_last_executed_command = (VFunction *)NULL;
+ info_read_and_dispatch ();
+ /* On program exit, leave the cursor at the bottom of the window, and
+ restore the terminal I/O. */
+ terminal_goto_xy (0, screenheight - 1);
+ terminal_clear_to_eol ();
+ fflush (stdout);
+ terminal_unprep_terminal ();
+ close_dribble_file ();
+}
+
+/* Here is a window-location dependent event loop. Called from the
+ functions info_session (), and from read_xxx_in_echo_area (). */
+void
+info_read_and_dispatch ()
+{
+ unsigned char key;
+ int done;
+ done = 0;
+
+ while (!done && !quit_info_immediately)
+ {
+ int lk;
+
+ /* If we haven't just gone up or down a line, there is no
+ goal column for this window. */
+ if ((info_last_executed_command != info_next_line) &&
+ (info_last_executed_command != info_prev_line))
+ active_window->goal_column = -1;
+
+ if (echo_area_is_active)
+ {
+ lk = echo_area_last_command_was_kill;
+ echo_area_prep_read ();
+ }
+
+ if (!info_any_buffered_input_p ())
+ display_update_display (windows);
+
+ display_cursor_at_point (active_window);
+ info_initialize_numeric_arg ();
+
+ initialize_keyseq ();
+ key = info_get_input_char ();
+
+ /* No errors yet. We just read a character, that's all. Only clear
+ the echo_area if it is not currently active. */
+ if (!echo_area_is_active)
+ window_clear_echo_area ();
+
+ info_error_was_printed = 0;
+
+ /* Do the selected command. */
+ info_dispatch_on_key (key, active_window->keymap);
+
+ if (echo_area_is_active)
+ {
+ /* Echo area commands that do killing increment the value of
+ ECHO_AREA_LAST_COMMAND_WAS_KILL. Thus, if there is no
+ change in the value of this variable, the last command
+ executed was not a kill command. */
+ if (lk == echo_area_last_command_was_kill)
+ echo_area_last_command_was_kill = 0;
+
+ if (ea_last_executed_command == ea_newline ||
+ info_aborted_echo_area)
+ {
+ ea_last_executed_command = (VFunction *)NULL;
+ done = 1;
+ }
+
+ if (info_last_executed_command == info_quit)
+ quit_info_immediately = 1;
+ }
+ else if (info_last_executed_command == info_quit)
+ done = 1;
+ }
+}
+
+/* Found in signals.c */
+extern void initialize_info_signal_handler ();
+
+/* Initialize the first info session by starting the terminal, window,
+ and display systems. */
+void
+initialize_info_session (node)
+ NODE *node;
+{
+ char *getenv (), *term_name;
+
+ term_name = getenv ("TERM");
+ terminal_initialize_terminal (term_name);
+
+ if (terminal_is_dumb_p)
+ {
+ if (!term_name)
+ term_name = "dumb";
+
+ info_error (TERM_TOO_DUMB, term_name);
+ exit (1);
+ }
+ terminal_clear_screen ();
+ initialize_info_keymaps ();
+ window_initialize_windows (screenwidth, screenheight);
+ initialize_info_signal_handler ();
+ display_initialize_display (screenwidth, screenheight);
+ info_set_node_of_window (active_window, node);
+
+ /* Tell the window system how to notify us when a window needs to be
+ asynchronously deleted (e.g., user resizes window very small). */
+ window_deletion_notifier = forget_window_and_nodes;
+
+ /* If input has not been redirected yet, make it come from STDIN. */
+ if (!info_input_stream)
+ info_input_stream = stdin;
+
+ info_windows_initialized_p = 1;
+}
+
+/* Tell Info that input is coming from the file FILENAME. */
+void
+info_set_input_from_file (filename)
+ char *filename;
+{
+ FILE *stream;
+
+ stream = fopen (filename, "r");
+
+ if (!stream)
+ return;
+
+ if ((info_input_stream != (FILE *)NULL) &&
+ (info_input_stream != stdin))
+ fclose (info_input_stream);
+
+ info_input_stream = stream;
+
+ if (stream != stdin)
+ display_inhibited = 1;
+}
+
+/* Return the INFO_WINDOW containing WINDOW, or NULL if there isn't one. */
+static INFO_WINDOW *
+get_info_window_of_window (window)
+ WINDOW *window;
+{
+ register int i;
+ INFO_WINDOW *info_win = (INFO_WINDOW *)NULL;
+
+ for (i = 0; info_windows && (info_win = info_windows[i]); i++)
+ if (info_win->window == window)
+ break;
+
+ return (info_win);
+}
+
+/* Reset the remembered pagetop and point of WINDOW to WINDOW's current
+ values if the window and node are the same as the current one being
+ displayed. */
+void
+set_remembered_pagetop_and_point (window)
+ WINDOW *window;
+{
+ INFO_WINDOW *info_win;
+
+ info_win = get_info_window_of_window (window);
+
+ if (!info_win)
+ return;
+
+ if (info_win->nodes_index &&
+ (info_win->nodes[info_win->current] == window->node))
+ {
+ info_win->pagetops[info_win->current] = window->pagetop;
+ info_win->points[info_win->current] = window->point;
+ }
+}
+
+void
+remember_window_and_node (window, node)
+ WINDOW *window;
+ NODE *node;
+{
+ INFO_WINDOW *info_win;
+
+ /* See if we already have this window in our list. */
+ info_win = get_info_window_of_window (window);
+
+ /* If the window wasn't already on our list, then make a new entry. */
+ if (!info_win)
+ {
+ info_win = (INFO_WINDOW *)xmalloc (sizeof (INFO_WINDOW));
+ info_win->window = window;
+ info_win->nodes = (NODE **)NULL;
+ info_win->pagetops = (int *)NULL;
+ info_win->points = (long *)NULL;
+ info_win->current = 0;
+ info_win->nodes_index = 0;
+ info_win->nodes_slots = 0;
+
+ add_pointer_to_array (info_win, info_windows_index, info_windows,
+ info_windows_slots, 10, INFO_WINDOW *);
+ }
+
+ /* If this node, the current pagetop, and the current point are the
+ same as the last saved node and pagetop, don't really add this to
+ the list of history nodes. */
+ {
+ int ni = info_win->nodes_index - 1;
+
+ if ((ni != -1) &&
+ (info_win->nodes[ni]->contents == node->contents) &&
+ (info_win->pagetops[ni] == window->pagetop) &&
+ (info_win->points[ni] == window->point))
+ return;
+ }
+
+ /* Remember this node, the currently displayed pagetop, and the current
+ location of point in this window. Because we are updating pagetops
+ and points as well as nodes, it is more efficient to avoid the
+ add_pointer_to_array macro here. */
+ if (info_win->nodes_index + 2 >= info_win->nodes_slots)
+ {
+ info_win->nodes = (NODE **)
+ xrealloc (info_win->nodes,
+ (info_win->nodes_slots += 20) * sizeof (NODE *));
+
+ info_win->pagetops = (int *)
+ xrealloc (info_win->pagetops, info_win->nodes_slots * sizeof (int));
+
+ info_win->points = (long *)
+ xrealloc (info_win->points, info_win->nodes_slots * sizeof (long));
+ }
+
+ info_win->nodes[info_win->nodes_index] = node;
+ info_win->pagetops[info_win->nodes_index] = window->pagetop;
+ info_win->points[info_win->nodes_index] = window->point;
+ info_win->current = info_win->nodes_index++;
+ info_win->nodes[info_win->nodes_index] = (NODE *)NULL;
+ info_win->pagetops[info_win->nodes_index] = 0;
+ info_win->points[info_win->nodes_index] = 0;
+}
+
+#define DEBUG_FORGET_WINDOW_AND_NODES
+#if defined (DEBUG_FORGET_WINDOW_AND_NODES)
+static void
+consistency_check_info_windows ()
+{
+ register int i;
+ INFO_WINDOW *info_win;
+
+ for (i = 0; i < info_windows_index; i++)
+ {
+ WINDOW *win;
+
+ for (win = windows; win; win = win->next)
+ if (win == info_windows[i]->window)
+ break;
+
+ if (!win)
+ abort ();
+ }
+}
+#endif /* DEBUG_FORGET_WINDOW_AND_NODES */
+
+/* Remove WINDOW and its associated list of nodes from INFO_WINDOWS. */
+void
+forget_window_and_nodes (window)
+ WINDOW *window;
+{
+ register int i;
+ INFO_WINDOW *info_win = (INFO_WINDOW *)NULL;
+
+ for (i = 0; info_windows && (info_win = info_windows[i]); i++)
+ if (info_win->window == window)
+ break;
+
+ /* If we found the window to forget, then do so. */
+ if (info_win)
+ {
+ while (i < info_windows_index)
+ {
+ info_windows[i] = info_windows[i + 1];
+ i++;
+ }
+
+ info_windows_index--;
+ info_windows[info_windows_index] = (INFO_WINDOW *)NULL;
+
+ if (info_win->nodes)
+ {
+ /* Free the node structures which held onto internal node contents
+ here. This doesn't free the contents; we have a garbage collector
+ which does that. */
+ for (i = 0; info_win->nodes[i]; i++)
+ if (internal_info_node_p (info_win->nodes[i]))
+ free (info_win->nodes[i]);
+ free (info_win->nodes);
+
+ maybe_free (info_win->pagetops);
+ maybe_free (info_win->points);
+ }
+
+ free (info_win);
+ }
+#if defined (DEBUG_FORGET_WINDOW_AND_NODES)
+ consistency_check_info_windows ();
+#endif /* DEBUG_FORGET_WINDOW_AND_NODES */
+}
+
+/* Set WINDOW to show NODE. Remember the new window in our list of Info
+ windows. If we are doing automatic footnote display, also try to display
+ the footnotes for this window. */
+void
+info_set_node_of_window (window, node)
+ WINDOW *window;
+ NODE *node;
+{
+ /* Put this node into the window. */
+ window_set_node_of_window (window, node);
+
+ /* Remember this node and window in our list of info windows. */
+ remember_window_and_node (window, node);
+
+ /* If doing auto-footnote display/undisplay, show the footnotes belonging
+ to this window's node. */
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (window);
+}
+
+
+/* **************************************************************** */
+/* */
+/* Info Movement Commands */
+/* */
+/* **************************************************************** */
+
+/* Change the pagetop of WINDOW to DESIRED_TOP, perhaps scrolling the screen
+ to do so. */
+void
+set_window_pagetop (window, desired_top)
+ WINDOW *window;
+ int desired_top;
+{
+ int point_line, old_pagetop;
+
+ if (desired_top < 0)
+ desired_top = 0;
+ else if (desired_top > window->line_count)
+ desired_top = window->line_count - 1;
+
+ if (window->pagetop == desired_top)
+ return;
+
+ old_pagetop = window->pagetop;
+ window->pagetop = desired_top;
+
+ /* Make sure that point appears in this window. */
+ point_line = window_line_of_point (window);
+ if ((point_line < window->pagetop) ||
+ ((point_line - window->pagetop) > window->height - 1))
+ window->point =
+ window->line_starts[window->pagetop] - window->node->contents;
+
+ window->flags |= W_UpdateWindow;
+
+ /* Find out which direction to scroll, and scroll the window in that
+ direction. Do this only if there would be a savings in redisplay
+ time. This is true if the amount to scroll is less than the height
+ of the window, and if the number of lines scrolled would be greater
+ than 10 % of the window's height. */
+ if (old_pagetop < desired_top)
+ {
+ int start, end, amount;
+
+ amount = desired_top - old_pagetop;
+
+ if ((amount >= window->height) ||
+ (((window->height - amount) * 10) < window->height))
+ return;
+
+ start = amount + window->first_row;
+ end = window->height + window->first_row;
+
+ display_scroll_display (start, end, -amount);
+ }
+ else
+ {
+ int start, end, amount;
+
+ amount = old_pagetop - desired_top;
+
+ if ((amount >= window->height) ||
+ (((window->height - amount) * 10) < window->height))
+ return;
+
+ start = window->first_row;
+ end = (window->first_row + window->height) - amount;
+ display_scroll_display (start, end, amount);
+ }
+}
+
+/* Immediately make WINDOW->point visible on the screen, and move the
+ terminal cursor there. */
+static void
+info_show_point (window)
+ WINDOW *window;
+{
+ int old_pagetop;
+
+ old_pagetop = window->pagetop;
+ window_adjust_pagetop (window);
+ if (old_pagetop != window->pagetop)
+ {
+ int new_pagetop;
+
+ new_pagetop = window->pagetop;
+ window->pagetop = old_pagetop;
+ set_window_pagetop (window, new_pagetop);
+ }
+
+ if (window->flags & W_UpdateWindow)
+ display_update_one_window (window);
+
+ display_cursor_at_point (window);
+}
+
+/* Move WINDOW->point from OLD line index to NEW line index. */
+static void
+move_to_new_line (old, new, window)
+ int old, new;
+ WINDOW *window;
+{
+ if (old == -1)
+ {
+ info_error (CANT_FIND_POINT);
+ }
+ else
+ {
+ int goal;
+
+ if (new >= window->line_count || new < 0)
+ return;
+
+ goal = window_get_goal_column (window);
+ window->goal_column = goal;
+
+ window->point = window->line_starts[new] - window->node->contents;
+ window->point += window_chars_to_goal (window->line_starts[new], goal);
+ info_show_point (window);
+ }
+}
+
+/* Move WINDOW's point down to the next line if possible. */
+DECLARE_INFO_COMMAND (info_next_line, "Move down to the next line")
+{
+ int old_line, new_line;
+
+ if (count < 0)
+ info_prev_line (window, -count, key);
+ else
+ {
+ old_line = window_line_of_point (window);
+ new_line = old_line + count;
+ move_to_new_line (old_line, new_line, window);
+ }
+}
+
+/* Move WINDOW's point up to the previous line if possible. */
+DECLARE_INFO_COMMAND (info_prev_line, "Move up to the previous line")
+{
+ int old_line, new_line;
+
+ if (count < 0)
+ info_next_line (window, -count, key);
+ else
+ {
+ old_line = window_line_of_point (window);
+ new_line = old_line - count;
+ move_to_new_line (old_line, new_line, window);
+ }
+}
+
+/* Move WINDOW's point to the end of the true line. */
+DECLARE_INFO_COMMAND (info_end_of_line, "Move to the end of the line")
+{
+ register int point, len;
+ register char *buffer;
+
+ buffer = window->node->contents;
+ len = window->node->nodelen;
+
+ for (point = window->point;
+ (point < len) && (buffer[point] != '\n');
+ point++);
+
+ if (point != window->point)
+ {
+ window->point = point;
+ info_show_point (window);
+ }
+}
+
+/* Move WINDOW's point to the beginning of the true line. */
+DECLARE_INFO_COMMAND (info_beginning_of_line, "Move to the start of the line")
+{
+ register int point;
+ register char *buffer;
+
+ buffer = window->node->contents;
+ point = window->point;
+
+ for (; (point) && (buffer[point - 1] != '\n'); point--);
+
+ /* If at a line start alreay, do nothing. */
+ if (point != window->point)
+ {
+ window->point = point;
+ info_show_point (window);
+ }
+}
+
+/* Move point forward in the node. */
+DECLARE_INFO_COMMAND (info_forward_char, "Move forward a character")
+{
+ if (count < 0)
+ info_backward_char (window, -count, key);
+ else
+ {
+ window->point += count;
+
+ if (window->point >= window->node->nodelen)
+ window->point = window->node->nodelen - 1;
+
+ info_show_point (window);
+ }
+}
+
+/* Move point backward in the node. */
+DECLARE_INFO_COMMAND (info_backward_char, "Move backward a character")
+{
+ if (count < 0)
+ info_forward_char (window, -count, key);
+ else
+ {
+ window->point -= count;
+
+ if (window->point < 0)
+ window->point = 0;
+
+ info_show_point (window);
+ }
+}
+
+#define alphabetic(c) (islower (c) || isupper (c) || isdigit (c))
+
+/* Move forward a word in this node. */
+DECLARE_INFO_COMMAND (info_forward_word, "Move forward a word")
+{
+ long point;
+ char *buffer;
+ int end, c;
+
+ if (count < 0)
+ {
+ info_backward_word (window, -count, key);
+ return;
+ }
+
+ point = window->point;
+ buffer = window->node->contents;
+ end = window->node->nodelen;
+
+ while (count)
+ {
+ if (point + 1 >= end)
+ return;
+
+ /* If we are not in a word, move forward until we are in one.
+ Then, move forward until we hit a non-alphabetic character. */
+ c = buffer[point];
+
+ if (!alphabetic (c))
+ {
+ while (++point < end)
+ {
+ c = buffer[point];
+ if (alphabetic (c))
+ break;
+ }
+ }
+
+ if (point >= end) return;
+
+ while (++point < end)
+ {
+ c = buffer[point];
+ if (!alphabetic (c))
+ break;
+ }
+ --count;
+ }
+ window->point = point;
+ info_show_point (window);
+}
+
+DECLARE_INFO_COMMAND (info_backward_word, "Move backward a word")
+{
+ long point;
+ char *buffer;
+ int c;
+
+ if (count < 0)
+ {
+ info_forward_word (window, -count, key);
+ return;
+ }
+
+ buffer = window->node->contents;
+ point = window->point;
+
+ while (count)
+ {
+ if (point == 0)
+ break;
+
+ /* Like info_forward_word (), except that we look at the
+ characters just before point. */
+
+ c = buffer[point - 1];
+
+ if (!alphabetic (c))
+ {
+ while (--point)
+ {
+ c = buffer[point - 1];
+ if (alphabetic (c))
+ break;
+ }
+ }
+
+ while (point)
+ {
+ c = buffer[point - 1];
+ if (!alphabetic (c))
+ break;
+ else
+ --point;
+ }
+ --count;
+ }
+ window->point = point;
+ info_show_point (window);
+}
+
+/* Here is a list of time counter names which correspond to ordinal numbers.
+ It is used to print "once" instead of "1". */
+static char *counter_names[] = {
+ "not at all", "once", "twice", "three", "four", "five", "six",
+ (char *)NULL
+};
+
+/* Buffer used to return values from times_description (). */
+static char td_buffer[50];
+
+/* Function returns a static string fully describing the number of times
+ present in COUNT. */
+static char *
+times_description (count)
+ int count;
+{
+ register int i;
+
+ td_buffer[0] = '\0';
+
+ for (i = 0; counter_names[i]; i++)
+ if (count == i)
+ break;
+
+ if (counter_names[i])
+ sprintf (td_buffer, "%s%s", counter_names[i], count > 2 ? " times" : "");
+ else
+ sprintf (td_buffer, "%d times", count);
+
+ return (td_buffer);
+}
+
+/* Variable controlling the behaviour of default scrolling when you are
+ already at the bottom of a node. Possible values are defined in session.h.
+ The meanings are:
+
+ IS_Continuous Try to get first menu item, or failing that, the
+ "Next:" pointer, or failing that, the "Up:" and
+ "Next:" of the up.
+ IS_NextOnly Try to get "Next:" menu item.
+ IS_PageOnly Simply give up at the bottom of a node. */
+
+int info_scroll_behaviour = IS_Continuous;
+
+/* Choices used by the completer when reading a value for the user-visible
+ variable "scroll-behaviour". */
+char *info_scroll_choices[] = {
+ "Continuous", "Next Only", "Page Only", (char *)NULL
+};
+
+/* Move to 1st menu item, Next, Up/Next, or error in this window. */
+static void
+forward_move_node_structure (window, behaviour)
+ WINDOW *window;
+ int behaviour;
+{
+ switch (behaviour)
+ {
+ case IS_PageOnly:
+ info_error (AT_NODE_BOTTOM);
+ break;
+
+ case IS_NextOnly:
+ info_next_label_of_node (window->node);
+ if (!info_parsed_nodename && !info_parsed_filename)
+ info_error ("No \"Next\" pointer for this node.");
+ else
+ {
+ window_message_in_echo_area ("Following \"Next\" node...");
+ info_handle_pointer ("Next", window);
+ }
+ break;
+
+ case IS_Continuous:
+ {
+ /* First things first. If this node contains a menu, move down
+ into the menu. */
+ {
+ REFERENCE **menu;
+
+ menu = info_menu_of_node (window->node);
+
+ if (menu)
+ {
+ info_free_references (menu);
+ window_message_in_echo_area ("Selecting first menu item...");
+ info_menu_digit (window, 1, '1');
+ return;
+ }
+ }
+
+ /* Okay, this node does not contain a menu. If it contains a
+ "Next:" pointer, use that. */
+ info_next_label_of_node (window->node);
+ if (info_label_was_found)
+ {
+ window_message_in_echo_area ("Selecting \"Next\" node...");
+ info_handle_pointer ("Next", window);
+ return;
+ }
+
+ /* Okay, there wasn't a "Next:" for this node. Move "Up:" until we
+ can move "Next:". If that isn't possible, complain that there
+ are no more nodes. */
+ {
+ int up_counter, old_current;
+ INFO_WINDOW *info_win;
+
+ /* Remember the current node and location. */
+ info_win = get_info_window_of_window (window);
+ old_current = info_win->current;
+
+ /* Back up through the "Up:" pointers until we have found a "Next:"
+ that isn't the same as the first menu item found in that node. */
+ up_counter = 0;
+ while (!info_error_was_printed)
+ {
+ info_up_label_of_node (window->node);
+ if (info_label_was_found)
+ {
+ info_handle_pointer ("Up", window);
+ if (info_error_was_printed)
+ continue;
+
+ up_counter++;
+
+ info_next_label_of_node (window->node);
+
+ /* If no "Next" pointer, keep backing up. */
+ if (!info_label_was_found)
+ continue;
+
+ /* If this node's first menu item is the same as this node's
+ Next pointer, keep backing up. */
+ if (!info_parsed_filename)
+ {
+ REFERENCE **menu;
+ char *next_nodename;
+
+ /* Remember the name of the Next node, since reading
+ the menu can overwrite the contents of the
+ info_parsed_xxx strings. */
+ next_nodename = strdup (info_parsed_nodename);
+
+ menu = info_menu_of_node (window->node);
+ if (menu &&
+ (strcmp
+ (menu[0]->nodename, next_nodename) == 0))
+ {
+ info_free_references (menu);
+ free (next_nodename);
+ continue;
+ }
+ else
+ {
+ /* Restore the world to where it was before
+ reading the menu contents. */
+ info_free_references (menu);
+ free (next_nodename);
+ info_next_label_of_node (window->node);
+ }
+ }
+
+ /* This node has a "Next" pointer, and it is not the
+ same as the first menu item found in this node. */
+ window_message_in_echo_area
+ ("Moving \"Up\" %s, then \"Next\".",
+ times_description (up_counter));
+
+ info_handle_pointer ("Next", window);
+ return;
+ }
+ else
+ {
+ /* No more "Up" pointers. Print an error, and call it
+ quits. */
+ register int i;
+
+ for (i = 0; i < up_counter; i++)
+ {
+ info_win->nodes_index--;
+ free (info_win->nodes[info_win->nodes_index]);
+ info_win->nodes[info_win->nodes_index] = (NODE *)NULL;
+ }
+ info_win->current = old_current;
+ window->node = info_win->nodes[old_current];
+ window->pagetop = info_win->pagetops[old_current];
+ window->point = info_win->points[old_current];
+ recalculate_line_starts (window);
+ window->flags |= W_UpdateWindow;
+ info_error ("No more nodes.");
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+/* Move Prev, Up or error in WINDOW depending on BEHAVIOUR. */
+static void
+backward_move_node_structure (window, behaviour)
+ WINDOW *window;
+ int behaviour;
+{
+ switch (behaviour)
+ {
+ case IS_PageOnly:
+ info_error (AT_NODE_TOP);
+ break;
+
+ case IS_NextOnly:
+ info_prev_label_of_node (window->node);
+ if (!info_parsed_nodename && !info_parsed_filename)
+ info_error ("No \"Prev\" for this node.");
+ else
+ {
+ window_message_in_echo_area ("Moving \"Prev\" in this window.");
+ info_handle_pointer ("Prev", window);
+ }
+ break;
+
+ case IS_Continuous:
+ info_prev_label_of_node (window->node);
+
+ if (!info_parsed_nodename && !info_parsed_filename)
+ {
+ info_up_label_of_node (window->node);
+ if (!info_parsed_nodename && !info_parsed_filename)
+ info_error ("No \"Prev\" or \"Up\" for this node.");
+ else
+ {
+ window_message_in_echo_area ("Moving \"Up\" in this window.");
+ info_handle_pointer ("Up", window);
+ }
+ }
+ else
+ {
+ REFERENCE **menu;
+ int inhibit_menu_traversing = 0;
+
+ /* Watch out! If this node's Prev is the same as the Up, then
+ move Up. Otherwise, we could move Prev, and then to the last
+ menu item in the Prev. This would cause the user to loop
+ through a subsection of the info file. */
+ if (!info_parsed_filename && info_parsed_nodename)
+ {
+ char *pnode;
+
+ pnode = strdup (info_parsed_nodename);
+ info_up_label_of_node (window->node);
+
+ if (!info_parsed_filename && info_parsed_nodename &&
+ strcmp (info_parsed_nodename, pnode) == 0)
+ {
+ /* The nodes are the same. Inhibit moving to the last
+ menu item. */
+ free (pnode);
+ inhibit_menu_traversing = 1;
+ }
+ else
+ {
+ free (pnode);
+ info_prev_label_of_node (window->node);
+ }
+ }
+
+ /* Move to the previous node. If this node now contains a menu,
+ and we have not inhibited movement to it, move to the node
+ corresponding to the last menu item. */
+ window_message_in_echo_area ("Moving \"Prev\" in this window.");
+ info_handle_pointer ("Prev", window);
+
+ if (!inhibit_menu_traversing)
+ {
+ while (!info_error_was_printed &&
+ (menu = info_menu_of_node (window->node)))
+ {
+ info_free_references (menu);
+ window_message_in_echo_area
+ ("Moving to \"Prev\"'s last menu item.");
+ info_menu_digit (window, 1, '0');
+ }
+ }
+ }
+ break;
+ }
+}
+
+/* Move continuously forward through the node structure of this info file. */
+DECLARE_INFO_COMMAND (info_global_next_node,
+ "Move forwards or down through node structure")
+{
+ if (count < 0)
+ info_global_prev_node (window, -count, key);
+ else
+ {
+ while (count && !info_error_was_printed)
+ {
+ forward_move_node_structure (window, IS_Continuous);
+ count--;
+ }
+ }
+}
+
+/* Move continuously backward through the node structure of this info file. */
+DECLARE_INFO_COMMAND (info_global_prev_node,
+ "Move backwards or up through node structure")
+{
+ if (count < 0)
+ info_global_next_node (window, -count, key);
+ else
+ {
+ while (count && !info_error_was_printed)
+ {
+ backward_move_node_structure (window, IS_Continuous);
+ count--;
+ }
+ }
+}
+
+/* Show the next screen of WINDOW's node. */
+DECLARE_INFO_COMMAND (info_scroll_forward, "Scroll forward in this window")
+{
+ if (count < 0)
+ info_scroll_backward (window, -count, key);
+ else
+ {
+ int desired_top;
+
+ /* Without an explicit numeric argument, scroll the bottom two
+ lines to the top of this window, Or, if at bottom of window,
+ and the user wishes to scroll through nodes get the "Next" node
+ for this window. */
+ if (!info_explicit_arg && count == 1)
+ {
+ desired_top = window->pagetop + (window->height - 2);
+
+ /* If there are no more lines to scroll here, error, or get
+ another node, depending on INFO_SCROLL_BEHAVIOUR. */
+ if (desired_top > window->line_count)
+ {
+ int behaviour = info_scroll_behaviour;
+
+ /* Here is a hack. If the key being used is not SPC, do the
+ PageOnly behaviour. */
+ if (key != SPC && key != DEL)
+ behaviour = IS_PageOnly;
+
+ forward_move_node_structure (window, behaviour);
+ return;
+ }
+ }
+ else
+ desired_top = window->pagetop + count;
+
+ if (desired_top >= window->line_count)
+ desired_top = window->line_count - 2;
+
+ if (window->pagetop > desired_top)
+ return;
+ else
+ set_window_pagetop (window, desired_top);
+ }
+}
+
+/* Show the previous screen of WINDOW's node. */
+DECLARE_INFO_COMMAND (info_scroll_backward, "Scroll backward in this window")
+{
+ if (count < 0)
+ info_scroll_forward (window, -count, key);
+ else
+ {
+ int desired_top;
+
+ /* Without an explicit numeric argument, scroll the top two lines
+ to the bottom of this window, or move to the previous, or Up'th
+ node. */
+ if (!info_explicit_arg && count == 1)
+ {
+ desired_top = window->pagetop - (window->height - 2);
+
+ if ((desired_top < 0) && (window->pagetop == 0))
+ {
+ int behaviour = info_scroll_behaviour;
+
+ /* Same kind of hack as in info_scroll_forward. If the key
+ used to invoke this command is not DEL, do only the PageOnly
+ behaviour. */
+ if (key != DEL && key != SPC)
+ behaviour = IS_PageOnly;
+
+ backward_move_node_structure (window, behaviour);
+ return;
+ }
+ }
+ else
+ desired_top = window->pagetop - count;
+
+ if (desired_top < 0)
+ desired_top = 0;
+
+ set_window_pagetop (window, desired_top);
+ }
+}
+
+/* Move to the beginning of the node. */
+DECLARE_INFO_COMMAND (info_beginning_of_node, "Move to the start of this node")
+{
+ window->pagetop = window->point = 0;
+ window->flags |= W_UpdateWindow;
+}
+
+/* Move to the end of the node. */
+DECLARE_INFO_COMMAND (info_end_of_node, "Move to the end of this node")
+{
+ window->point = window->node->nodelen - 1;
+ info_show_point (window);
+}
+
+/* **************************************************************** */
+/* */
+/* Commands for Manipulating Windows */
+/* */
+/* **************************************************************** */
+
+/* Make the next window in the chain be the active window. */
+DECLARE_INFO_COMMAND (info_next_window, "Select the next window")
+{
+ if (count < 0)
+ {
+ info_prev_window (window, -count, key);
+ return;
+ }
+
+ /* If no other window, error now. */
+ if (!windows->next && !echo_area_is_active)
+ {
+ info_error (ONE_WINDOW);
+ return;
+ }
+
+ while (count--)
+ {
+ if (window->next)
+ window = window->next;
+ else
+ {
+ if (window == the_echo_area || !echo_area_is_active)
+ window = windows;
+ else
+ window = the_echo_area;
+ }
+ }
+
+ if (active_window != window)
+ {
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (window);
+
+ window->flags |= W_UpdateWindow;
+ active_window = window;
+ }
+}
+
+/* Make the previous window in the chain be the active window. */
+DECLARE_INFO_COMMAND (info_prev_window, "Select the previous window")
+{
+ if (count < 0)
+ {
+ info_next_window (window, -count, key);
+ return;
+ }
+
+ /* Only one window? */
+
+ if (!windows->next && !echo_area_is_active)
+ {
+ info_error (ONE_WINDOW);
+ return;
+ }
+
+ while (count--)
+ {
+ /* If we are in the echo area, or if the echo area isn't active and we
+ are in the first window, find the last window in the chain. */
+ if (window == the_echo_area ||
+ (window == windows && !echo_area_is_active))
+ {
+ register WINDOW *win, *last;
+
+ for (win = windows; win; win = win->next)
+ last = win;
+
+ window = last;
+ }
+ else
+ {
+ if (window == windows)
+ window = the_echo_area;
+ else
+ window = window->prev;
+ }
+ }
+
+ if (active_window != window)
+ {
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (window);
+
+ window->flags |= W_UpdateWindow;
+ active_window = window;
+ }
+}
+
+/* Split WINDOW into two windows, both showing the same node. If we
+ are automatically tiling windows, re-tile after the split. */
+DECLARE_INFO_COMMAND (info_split_window, "Split the current window")
+{
+ WINDOW *split, *old_active;
+ int pagetop;
+
+ /* Remember the current pagetop of the window being split. If it doesn't
+ change, we can scroll its contents around after the split. */
+ pagetop = window->pagetop;
+
+ /* Make the new window. */
+ old_active = active_window;
+ active_window = window;
+ split = window_make_window (window->node);
+ active_window = old_active;
+
+ if (!split)
+ {
+ info_error (WIN_TOO_SMALL);
+ }
+ else
+ {
+#if defined (SPLIT_BEFORE_ACTIVE)
+ /* Try to scroll the old window into its new postion. */
+ if (pagetop == window->pagetop)
+ {
+ int start, end, amount;
+
+ start = split->first_row;
+ end = start + window->height;
+ amount = split->height + 1;
+ display_scroll_display (start, end, amount);
+ }
+#else /* !SPLIT_BEFORE_ACTIVE */
+ /* Make sure point still appears in the active window. */
+ info_show_point (window);
+#endif /* !SPLIT_BEFORE_ACTIVE */
+
+ /* If the window just split was one internal to Info, try to display
+ something else in it. */
+ if (internal_info_node_p (split->node))
+ {
+ register int i, j;
+ INFO_WINDOW *iw;
+ NODE *node = (NODE *)NULL;
+ char *filename;
+
+ for (i = 0; iw = info_windows[i]; i++)
+ {
+ for (j = 0; j < iw->nodes_index; j++)
+ if (!internal_info_node_p (iw->nodes[j]))
+ {
+ if (iw->nodes[j]->parent)
+ filename = iw->nodes[j]->parent;
+ else
+ filename = iw->nodes[j]->filename;
+
+ node = info_get_node (filename, iw->nodes[j]->nodename);
+ if (node)
+ {
+ window_set_node_of_window (split, node);
+ i = info_windows_index - 1;
+ break;
+ }
+ }
+ }
+ }
+ split->pagetop = window->pagetop;
+
+ if (auto_tiling_p)
+ window_tile_windows (DONT_TILE_INTERNALS);
+ else
+ window_adjust_pagetop (split);
+
+ remember_window_and_node (split, split->node);
+ }
+}
+
+/* Delete WINDOW, forgetting the list of last visited nodes. If we are
+ automatically displaying footnotes, show or remove the footnotes
+ window. If we are automatically tiling windows, re-tile after the
+ deletion. */
+DECLARE_INFO_COMMAND (info_delete_window, "Delete the current window")
+{
+ if (!windows->next)
+ {
+ info_error (CANT_KILL_LAST);
+ }
+ else if (window->flags & W_WindowIsPerm)
+ {
+ info_error ("Cannot delete a permanent window");
+ }
+ else
+ {
+ info_delete_window_internal (window);
+
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (active_window);
+
+ if (auto_tiling_p)
+ window_tile_windows (DONT_TILE_INTERNALS);
+ }
+}
+
+/* Do the physical deletion of WINDOW, and forget this window and
+ associated nodes. */
+void
+info_delete_window_internal (window)
+ WINDOW *window;
+{
+ if (windows->next && ((window->flags & W_WindowIsPerm) == 0))
+ {
+ /* We not only delete the window from the display, we forget it from
+ our list of remembered windows. */
+ forget_window_and_nodes (window);
+ window_delete_window (window);
+
+ if (echo_area_is_active)
+ echo_area_inform_of_deleted_window (window);
+ }
+}
+
+/* Just keep WINDOW, deleting all others. */
+DECLARE_INFO_COMMAND (info_keep_one_window, "Delete all other windows")
+{
+ int num_deleted; /* The number of windows we deleted. */
+ int pagetop, start, end;
+
+ /* Remember a few things about this window. We may be able to speed up
+ redisplay later by scrolling its contents. */
+ pagetop = window->pagetop;
+ start = window->first_row;
+ end = start + window->height;
+
+ num_deleted = 0;
+
+ while (1)
+ {
+ WINDOW *win;
+
+ /* Find an eligible window and delete it. If no eligible windows
+ are found, we are done. A window is eligible for deletion if
+ is it not permanent, and it is not WINDOW. */
+ for (win = windows; win; win = win->next)
+ if (win != window && ((win->flags & W_WindowIsPerm) == 0))
+ break;
+
+ if (!win)
+ break;
+
+ info_delete_window_internal (win);
+ num_deleted++;
+ }
+
+ /* Scroll the contents of this window into the right place so that the
+ user doesn't have to wait any longer than necessary for redisplay. */
+ if (num_deleted)
+ {
+ int amount;
+
+ amount = (window->first_row - start);
+ amount -= (window->pagetop - pagetop);
+ display_scroll_display (start, end, amount);
+ }
+
+ window->flags |= W_UpdateWindow;
+}
+
+/* Scroll the "other" window of WINDOW. */
+DECLARE_INFO_COMMAND (info_scroll_other_window, "Scroll the other window")
+{
+ WINDOW *other;
+
+ /* If only one window, give up. */
+ if (!windows->next)
+ {
+ info_error (ONE_WINDOW);
+ return;
+ }
+
+ other = window->next;
+
+ if (!other)
+ other = window->prev;
+
+ info_scroll_forward (other, count, key);
+}
+
+/* Change the size of WINDOW by AMOUNT. */
+DECLARE_INFO_COMMAND (info_grow_window, "Grow (or shrink) this window")
+{
+ window_change_window_height (window, count);
+}
+
+/* When non-zero, tiling takes place automatically when info_split_window
+ is called. */
+int auto_tiling_p = 0;
+
+/* Tile all of the visible windows. */
+DECLARE_INFO_COMMAND (info_tile_windows,
+ "Divide the available screen space among the visible windows")
+{
+ window_tile_windows (TILE_INTERNALS);
+}
+
+/* Toggle the state of this window's wrapping of lines. */
+DECLARE_INFO_COMMAND (info_toggle_wrap,
+ "Toggle the state of line wrapping in the current window")
+{
+ window_toggle_wrap (window);
+}
+
+/* **************************************************************** */
+/* */
+/* Info Node Commands */
+/* */
+/* **************************************************************** */
+
+/* Using WINDOW for various defaults, select the node referenced by ENTRY
+ in it. If the node is selected, the window and node are remembered. */
+void
+info_select_reference (window, entry)
+ WINDOW *window;
+ REFERENCE *entry;
+{
+ NODE *node;
+ char *filename, *nodename, *file_system_error;
+
+ file_system_error = (char *)NULL;
+
+ filename = entry->filename;
+ if (!filename)
+ filename = window->node->parent;
+ if (!filename)
+ filename = window->node->filename;
+
+ if (filename)
+ filename = strdup (filename);
+
+ if (entry->nodename)
+ nodename = strdup (entry->nodename);
+ else
+ nodename = strdup ("Top");
+
+ node = info_get_node (filename, nodename);
+
+ /* Try something a little weird. If the node couldn't be found, and the
+ reference was of the form "foo::", see if the entry->label can be found
+ as a file, with a node of "Top". */
+ if (!node)
+ {
+ if (info_recent_file_error)
+ file_system_error = strdup (info_recent_file_error);
+
+ if (entry->nodename && (strcmp (entry->nodename, entry->label) == 0))
+ {
+ node = info_get_node (entry->label, "Top");
+ if (!node && info_recent_file_error)
+ {
+ maybe_free (file_system_error);
+ file_system_error = strdup (info_recent_file_error);
+ }
+ }
+ }
+
+ if (!node)
+ {
+ if (file_system_error)
+ info_error (file_system_error);
+ else
+ info_error (CANT_FIND_NODE, nodename);
+ }
+
+ maybe_free (file_system_error);
+ maybe_free (filename);
+ maybe_free (nodename);
+
+ if (node)
+ {
+ set_remembered_pagetop_and_point (window);
+ info_set_node_of_window (window, node);
+ }
+}
+
+/* Parse the node specification in LINE using WINDOW to default the filename.
+ Select the parsed node in WINDOW and remember it, or error if the node
+ couldn't be found. */
+static void
+info_parse_and_select (line, window)
+ char *line;
+ WINDOW *window;
+{
+ REFERENCE entry;
+
+ info_parse_node (line, DONT_SKIP_NEWLINES);
+
+ entry.nodename = info_parsed_nodename;
+ entry.filename = info_parsed_filename;
+ entry.label = "*info-parse-and-select*";
+
+ info_select_reference (window, &entry);
+}
+
+/* Given that the values of INFO_PARSED_FILENAME and INFO_PARSED_NODENAME
+ are previously filled, try to get the node represented by them into
+ WINDOW. The node should have been pointed to by the LABEL pointer of
+ WINDOW->node. */
+static void
+info_handle_pointer (label, window)
+ char *label;
+ WINDOW *window;
+{
+ if (info_parsed_filename || info_parsed_nodename)
+ {
+ char *filename, *nodename;
+ NODE *node;
+
+ filename = nodename = (char *)NULL;
+
+ if (info_parsed_filename)
+ filename = strdup (info_parsed_filename);
+ else
+ {
+ if (window->node->parent)
+ filename = strdup (window->node->parent);
+ else if (window->node->filename)
+ filename = strdup (window->node->filename);
+ }
+
+ if (info_parsed_nodename)
+ nodename = strdup (info_parsed_nodename);
+ else
+ nodename = strdup ("Top");
+
+ node = info_get_node (filename, nodename);
+
+ if (node)
+ {
+ INFO_WINDOW *info_win;
+
+ info_win = get_info_window_of_window (window);
+ if (info_win)
+ {
+ info_win->pagetops[info_win->current] = window->pagetop;
+ info_win->points[info_win->current] = window->point;
+ }
+ set_remembered_pagetop_and_point (window);
+ info_set_node_of_window (window, node);
+ }
+ else
+ {
+ if (info_recent_file_error)
+ info_error (info_recent_file_error);
+ else
+ info_error (CANT_FILE_NODE, filename, nodename);
+ }
+
+ free (filename);
+ free (nodename);
+ }
+ else
+ {
+ info_error (NO_POINTER, label);
+ }
+}
+
+/* Make WINDOW display the "Next:" node of the node currently being
+ displayed. */
+DECLARE_INFO_COMMAND (info_next_node, "Select the `Next' node")
+{
+ info_next_label_of_node (window->node);
+ info_handle_pointer ("Next", window);
+}
+
+/* Make WINDOW display the "Prev:" node of the node currently being
+ displayed. */
+DECLARE_INFO_COMMAND (info_prev_node, "Select the `Prev' node")
+{
+ info_prev_label_of_node (window->node);
+ info_handle_pointer ("Prev", window);
+}
+
+/* Make WINDOW display the "Up:" node of the node currently being
+ displayed. */
+DECLARE_INFO_COMMAND (info_up_node, "Select the `Up' node")
+{
+ info_up_label_of_node (window->node);
+ info_handle_pointer ("Up", window);
+}
+
+/* Make WINDOW display the last node of this info file. */
+DECLARE_INFO_COMMAND (info_last_node, "Select the last node in this file")
+{
+ register int i;
+ FILE_BUFFER *fb = file_buffer_of_window (window);
+ NODE *node = (NODE *)NULL;
+
+ if (fb && fb->tags)
+ {
+ for (i = 0; fb->tags[i]; i++);
+ node = info_get_node (fb->filename, fb->tags[i - 1]->nodename);
+ }
+
+ if (!node)
+ info_error ("This window has no additional nodes");
+ else
+ {
+ set_remembered_pagetop_and_point (window);
+ info_set_node_of_window (window, node);
+ }
+}
+
+/* Make WINDOW display the first node of this info file. */
+DECLARE_INFO_COMMAND (info_first_node, "Select the first node in this file")
+{
+ FILE_BUFFER *fb = file_buffer_of_window (window);
+ NODE *node = (NODE *)NULL;
+
+ if (fb && fb->tags)
+ node = info_get_node (fb->filename, fb->tags[0]->nodename);
+
+ if (!node)
+ info_error ("This window has no additional nodes");
+ else
+ {
+ set_remembered_pagetop_and_point (window);
+ info_set_node_of_window (window, node);
+ }
+}
+
+/* Make WINDOW display the previous node displayed in this window. */
+DECLARE_INFO_COMMAND (info_history_node,
+ "Select the most recently selected node")
+{
+ INFO_WINDOW *info_win;
+
+ /* Find the INFO_WINDOW which contains WINDOW. */
+ info_win = get_info_window_of_window (window);
+
+ if (!info_win)
+ {
+ info_error ("Requested window is not present!");
+ return;
+ }
+
+ set_remembered_pagetop_and_point (window);
+ if (!info_win->current)
+ {
+ if (info_win->nodes_index > 1)
+ {
+ window_message_in_echo_area
+ ("Now wrapped around to beginning of history.");
+ info_win->current = info_win->nodes_index;
+ }
+ else
+ {
+ info_error ("No earlier nodes in this window.");
+ return;
+ }
+ }
+
+ info_win->current--;
+ window_set_node_of_window (window, info_win->nodes[info_win->current]);
+ window->pagetop = info_win->pagetops[info_win->current];
+ window->point = info_win->points[info_win->current];
+ window->flags |= W_UpdateWindow;
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (window);
+}
+
+/* Select the last menu item in WINDOW->node. */
+DECLARE_INFO_COMMAND (info_last_menu_item,
+ "Select the last item in this node's menu")
+{
+ info_menu_digit (window, 1, '0');
+}
+
+/* Use KEY (a digit) to select the Nth menu item in WINDOW->node. */
+DECLARE_INFO_COMMAND (info_menu_digit, "Select this menu item")
+{
+ register int i, item;
+ register REFERENCE *entry, **menu;
+
+ menu = info_menu_of_node (window->node);
+
+ if (!menu)
+ {
+ info_error (NO_MENU_NODE);
+ return;
+ }
+
+ /* We have the menu. See if there are this many items in it. */
+ item = key - '0';
+
+ /* Special case. Item "0" is the last item in this menu. */
+ if (item == 0)
+ for (i = 0; menu[i + 1]; i++);
+ else
+ {
+ for (i = 0; entry = menu[i]; i++)
+ if (i == item - 1)
+ break;
+ }
+
+ if (menu[i])
+ info_select_reference (window, menu[i]);
+ else
+ info_error ("There aren't %d items in this menu.", item);
+
+ info_free_references (menu);
+ return;
+}
+
+/* Read a menu or followed reference from the user defaulting to the
+ reference found on the current line, and select that node. The
+ reading is done with completion. BUILDER is the function used
+ to build the list of references. ASK_P is non-zero if the user
+ should be prompted, or zero to select the default item. */
+static void
+info_menu_or_ref_item (window, count, key, builder, ask_p)
+ WINDOW *window;
+ int count;
+ unsigned char key;
+ REFERENCE **(*builder) ();
+ int ask_p;
+{
+ REFERENCE **menu, *entry, *defentry = (REFERENCE *)NULL;
+ char *line;
+
+ menu = (*builder) (window->node);
+
+ if (!menu)
+ {
+ if (builder == info_menu_of_node)
+ info_error (NO_MENU_NODE);
+ else
+ info_error (NO_XREF_NODE);
+ return;
+ }
+
+ /* Default the selected reference to the one which is on the line that
+ point is in. */
+ {
+ REFERENCE **refs = (REFERENCE **)NULL;
+ int point_line;
+
+ point_line = window_line_of_point (window);
+
+ if (point_line != -1)
+ {
+ SEARCH_BINDING binding;
+
+ binding.start = 0;
+ binding.buffer = window->line_starts[point_line];
+ if (window->line_starts[point_line + 1])
+ binding.end = window->line_starts[point_line + 1] - binding.buffer;
+ else
+ binding.end =
+ (window->node->contents + window->node->nodelen) - binding.buffer;
+ binding.flags = 0;
+
+ if (builder == info_menu_of_node)
+ {
+ if (point_line)
+ {
+ binding.buffer--;
+ binding.end++;
+
+ refs = info_menu_items (&binding);
+ }
+ }
+ else
+ {
+#if defined (HANDLE_MAN_PAGES)
+ if (window->node->flags & N_IsManPage)
+ refs = manpage_xrefs_in_binding (window->node, &binding);
+ else
+#endif /* HANDLE_MAN_PAGES */
+ refs = info_xrefs (&binding);
+ }
+
+ if (refs)
+ {
+ if ((strcmp (refs[0]->label, "Menu") != 0) ||
+ (builder == info_xrefs_of_node))
+ {
+ int which = 0;
+
+ /* Find the closest reference to point. */
+ if (builder == info_xrefs_of_node)
+ {
+ int closest = -1;
+
+ for (; refs[which]; which++)
+ {
+ if ((window->point >= refs[which]->start) &&
+ (window->point <= refs[which]->end))
+ {
+ closest = which;
+ break;
+ }
+ else if (window->point < refs[which]->start)
+ {
+ break;
+ }
+ }
+ if (closest == -1)
+ which--;
+ else
+ which = closest;
+ }
+
+ defentry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+ defentry->label = strdup (refs[which]->label);
+ defentry->filename = refs[which]->filename;
+ defentry->nodename = refs[which]->nodename;
+
+ if (defentry->filename)
+ defentry->filename = strdup (defentry->filename);
+ if (defentry->nodename)
+ defentry->nodename = strdup (defentry->nodename);
+ }
+ info_free_references (refs);
+ }
+ }
+ }
+
+ /* If we are going to ask the user a question, do it now. */
+ if (ask_p)
+ {
+ char *prompt;
+
+ /* Build the prompt string. */
+ if (defentry)
+ prompt = (char *)xmalloc (20 + strlen (defentry->label));
+ else
+ prompt = (char *)xmalloc (20);
+
+ if (builder == info_menu_of_node)
+ {
+ if (defentry)
+ sprintf (prompt, "Menu item (%s): ", defentry->label);
+ else
+ sprintf (prompt, "Menu item: ");
+ }
+ else
+ {
+ if (defentry)
+ sprintf (prompt, "Follow xref (%s): ", defentry->label);
+ else
+ sprintf (prompt, "Follow xref: ");
+ }
+
+ line = info_read_completing_in_echo_area (window, prompt, menu);
+ free (prompt);
+
+ window = active_window;
+
+ /* User aborts, just quit. */
+ if (!line)
+ {
+ maybe_free (defentry);
+ info_free_references (menu);
+ info_abort_key (window, 0, 0);
+ return;
+ }
+
+ /* If we had a default and the user accepted it, use that. */
+ if (!*line)
+ {
+ free (line);
+ if (defentry)
+ line = strdup (defentry->label);
+ else
+ line = (char *)NULL;
+ }
+ }
+ else
+ {
+ /* Not going to ask any questions. If we have a default entry, use
+ that, otherwise return. */
+ if (!defentry)
+ return;
+ else
+ line = strdup (defentry->label);
+ }
+
+ if (line)
+ {
+ /* Find the selected label in the references. */
+ entry = info_get_labeled_reference (line, menu);
+
+ if (!entry && defentry)
+ info_error ("The reference disappeared! (%s).", line);
+ else
+ {
+ NODE *orig;
+
+ orig = window->node;
+ info_select_reference (window, entry);
+ if ((builder == info_xrefs_of_node) && (window->node != orig))
+ {
+ long offset;
+ long start;
+
+ if (window->line_count > 0)
+ start = window->line_starts[1] - window->node->contents;
+ else
+ start = 0;
+
+ offset =
+ info_target_search_node (window->node, entry->label, start);
+
+ if (offset != -1)
+ {
+ window->point = offset;
+ window_adjust_pagetop (window);
+ }
+ }
+ }
+
+ free (line);
+ if (defentry)
+ {
+ free (defentry->label);
+ maybe_free (defentry->filename);
+ maybe_free (defentry->nodename);
+ free (defentry);
+ }
+ }
+
+ info_free_references (menu);
+
+ if (!info_error_was_printed)
+ window_clear_echo_area ();
+}
+
+/* Read a line (with completion) which is the name of a menu item,
+ and select that item. */
+DECLARE_INFO_COMMAND (info_menu_item, "Read a menu item and select its node")
+{
+ info_menu_or_ref_item (window, count, key, info_menu_of_node, 1);
+}
+
+/* Read a line (with completion) which is the name of a reference to
+ follow, and select the node. */
+DECLARE_INFO_COMMAND
+ (info_xref_item, "Read a footnote or cross reference and select its node")
+{
+ info_menu_or_ref_item (window, count, key, info_xrefs_of_node, 1);
+}
+
+/* Position the cursor at the start of this node's menu. */
+DECLARE_INFO_COMMAND (info_find_menu, "Move to the start of this node's menu")
+{
+ SEARCH_BINDING binding;
+ long position;
+
+ binding.buffer = window->node->contents;
+ binding.start = 0;
+ binding.end = window->node->nodelen;
+ binding.flags = S_FoldCase | S_SkipDest;
+
+ position = search (INFO_MENU_LABEL, &binding);
+
+ if (position == -1)
+ info_error (NO_MENU_NODE);
+ else
+ {
+ window->point = position;
+ window_adjust_pagetop (window);
+ window->flags |= W_UpdateWindow;
+ }
+}
+
+/* Visit as many menu items as is possible, each in a separate window. */
+DECLARE_INFO_COMMAND (info_visit_menu,
+ "Visit as many menu items at once as possible")
+{
+ register int i;
+ REFERENCE *entry, **menu;
+
+ menu = info_menu_of_node (window->node);
+
+ if (!menu)
+ info_error (NO_MENU_NODE);
+
+ for (i = 0; (!info_error_was_printed) && (entry = menu[i]); i++)
+ {
+ WINDOW *new;
+
+ new = window_make_window (window->node);
+ window_tile_windows (TILE_INTERNALS);
+
+ if (!new)
+ info_error (WIN_TOO_SMALL);
+ else
+ {
+ active_window = new;
+ info_select_reference (new, entry);
+ }
+ }
+}
+
+/* Read a line of input which is a node name, and go to that node. */
+DECLARE_INFO_COMMAND (info_goto_node, "Read a node name and select it")
+{
+ char *line;
+ NODE *node;
+
+#define GOTO_COMPLETES
+#if defined (GOTO_COMPLETES)
+ /* Build a completion list of all of the known nodes. */
+ {
+ register int fbi, i;
+ FILE_BUFFER *current;
+ REFERENCE **items = (REFERENCE **)NULL;
+ int items_index = 0;
+ int items_slots = 0;
+
+ current = file_buffer_of_window (window);
+
+ for (fbi = 0; info_loaded_files && info_loaded_files[fbi]; fbi++)
+ {
+ FILE_BUFFER *fb;
+ REFERENCE *entry;
+ int this_is_the_current_fb;
+
+ fb = info_loaded_files[fbi];
+ this_is_the_current_fb = (current == fb);
+
+ entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+ entry->filename = entry->nodename = (char *)NULL;
+ entry->label = (char *)xmalloc (4 + strlen (fb->filename));
+ sprintf (entry->label, "(%s)*", fb->filename);
+
+ add_pointer_to_array
+ (entry, items_index, items, items_slots, 10, REFERENCE *);
+
+ if (fb->tags)
+ {
+ for (i = 0; fb->tags[i]; i++)
+ {
+ entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+ entry->filename = entry->nodename = (char *)NULL;
+ entry->label = (char *) xmalloc
+ (4 + strlen (fb->filename) + strlen (fb->tags[i]->nodename));
+ sprintf (entry->label, "(%s)%s",
+ fb->filename, fb->tags[i]->nodename);
+
+ add_pointer_to_array
+ (entry, items_index, items, items_slots, 100, REFERENCE *);
+ }
+
+ if (this_is_the_current_fb)
+ {
+ for (i = 0; fb->tags[i]; i++)
+ {
+ entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+ entry->filename = entry->nodename = (char *)NULL;
+ entry->label = strdup (fb->tags[i]->nodename);
+ add_pointer_to_array (entry, items_index, items,
+ items_slots, 100, REFERENCE *);
+ }
+ }
+ }
+ }
+ line = info_read_maybe_completing (window, "Goto Node: ", items);
+ info_free_references (items);
+ }
+#else /* !GOTO_COMPLETES */
+ line = info_read_in_echo_area (window, "Goto Node: ");
+#endif /* !GOTO_COMPLETES */
+
+ /* If the user aborted, quit now. */
+ if (!line)
+ {
+ info_abort_key (window, 0, 0);
+ return;
+ }
+
+ canonicalize_whitespace (line);
+
+ if (*line)
+ info_parse_and_select (line, window);
+
+ free (line);
+ if (!info_error_was_printed)
+ window_clear_echo_area ();
+}
+
+#if defined (HANDLE_MAN_PAGES)
+DECLARE_INFO_COMMAND (info_man, "Read a manpage reference and select it")
+{
+ char *line;
+ NODE *node;
+
+ line = info_read_in_echo_area (window, "Get Manpage: ");
+
+ if (!line)
+ {
+ info_abort_key (window, 0, 0);
+ return;
+ }
+
+ canonicalize_whitespace (line);
+
+ if (*line)
+ {
+ char *goto_command;
+
+ goto_command = (char *)xmalloc
+ (4 + strlen (MANPAGE_FILE_BUFFER_NAME) + strlen (line));
+
+ sprintf (goto_command, "(%s)%s", MANPAGE_FILE_BUFFER_NAME, line);
+
+ info_parse_and_select (goto_command, window);
+ free (goto_command);
+ }
+
+ free (line);
+ if (!info_error_was_printed)
+ window_clear_echo_area ();
+}
+#endif /* HANDLE_MAN_PAGES */
+
+/* Move to the "Top" node in this file. */
+DECLARE_INFO_COMMAND (info_top_node, "Select the node `Top' in this file")
+{
+ info_parse_and_select ("Top", window);
+}
+
+/* Move to the node "(dir)Top". */
+DECLARE_INFO_COMMAND (info_dir_node, "Select the node `(dir)'")
+{
+ info_parse_and_select ("(dir)Top", window);
+}
+
+/* Try to delete the current node appearing in this window, showing the most
+ recently selected node in this window. */
+DECLARE_INFO_COMMAND (info_kill_node, "Kill this node")
+{
+ register int iw, i;
+ register INFO_WINDOW *info_win;
+ char *nodename = (char *)NULL;
+ NODE *temp = (NODE *)NULL;
+
+ /* Read the name of a node to kill. The list of available nodes comes
+ from the nodes appearing in the current window configuration. */
+ {
+ REFERENCE **menu = (REFERENCE **)NULL;
+ int menu_index = 0, menu_slots = 0;
+ char *default_nodename, *prompt;
+
+ for (iw = 0; info_win = info_windows[iw]; iw++)
+ {
+ REFERENCE *entry;
+
+ entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+ entry->label = strdup (info_win->window->node->nodename);
+ entry->filename = entry->nodename = (char *)NULL;
+
+ add_pointer_to_array
+ (entry, menu_index, menu, menu_slots, 10, REFERENCE *);
+ }
+
+ default_nodename = strdup (active_window->node->nodename);
+ prompt = (char *)xmalloc (40 + strlen (default_nodename));
+ sprintf (prompt, "Kill node (%s): ", default_nodename);
+
+ nodename = info_read_completing_in_echo_area (window, prompt, menu);
+ free (prompt);
+ info_free_references (menu);
+ if (nodename && !*nodename)
+ {
+ free (nodename);
+ nodename = default_nodename;
+ }
+ else
+ free (default_nodename);
+ }
+
+ /* If there is no nodename to kill, quit now. */
+ if (!nodename)
+ {
+ info_abort_key (window, 0, 0);
+ return;
+ }
+
+ /* If there is a nodename, find it in our window list. */
+ for (iw = 0; info_win = info_windows[iw]; iw++)
+ if (strcmp (nodename, info_win->nodes[info_win->current]->nodename) == 0)
+ break;
+
+ if (!info_win)
+ {
+ if (*nodename)
+ info_error ("Cannot kill the node `%s'", nodename);
+ else
+ window_clear_echo_area ();
+
+ return;
+ }
+
+ /* If there are no more nodes left anywhere to view, complain and exit. */
+ if (info_windows_index == 1 && info_windows[0]->nodes_index == 1)
+ {
+ info_error ("Cannot kill the last node");
+ return;
+ }
+
+ /* INFO_WIN contains the node that the user wants to stop viewing.
+ Delete this node from the list of nodes previously shown in this
+ window. */
+ for (i = info_win->current; i < info_win->nodes_index; i++)
+ info_win->nodes[i] = info_win->nodes[i++];
+
+ /* There is one less node in this window's history list. */
+ info_win->nodes_index--;
+
+ /* Make this window show the most recent history node. */
+ info_win->current = info_win->nodes_index - 1;
+
+ /* If there aren't any nodes left in this window, steal one from the
+ next window. */
+ if (info_win->current < 0)
+ {
+ INFO_WINDOW *stealer;
+ int which, pagetop;
+ long point;
+
+ if (info_windows[iw + 1])
+ stealer = info_windows[iw + 1];
+ else
+ stealer = info_windows[0];
+
+ /* If the node being displayed in the next window is not the most
+ recently loaded one, get the most recently loaded one. */
+ if ((stealer->nodes_index - 1) != stealer->current)
+ which = stealer->nodes_index - 1;
+
+ /* Else, if there is another node behind the stealers current node,
+ use that one. */
+ else if (stealer->current > 0)
+ which = stealer->current - 1;
+
+ /* Else, just use the node appearing in STEALER's window. */
+ else
+ which = stealer->current;
+
+ /* Copy this node. */
+ {
+ NODE *copy;
+
+ temp = stealer->nodes[which];
+ point = stealer->points[which];
+ pagetop = stealer->pagetops[which];
+
+ copy = (NODE *)xmalloc (sizeof (NODE));
+ copy->filename = temp->filename;
+ copy->parent = temp->parent;
+ copy->nodename = temp->nodename;
+ copy->contents = temp->contents;
+ copy->nodelen = temp->nodelen;
+ copy->flags = temp->flags;
+
+ temp = copy;
+ }
+
+ window_set_node_of_window (info_win->window, temp);
+ window->point = point;
+ window->pagetop = pagetop;
+ remember_window_and_node (info_win->window, temp);
+ }
+ else
+ {
+ temp = info_win->nodes[info_win->current];
+ window_set_node_of_window (info_win->window, temp);
+ }
+ if (!info_error_was_printed)
+ window_clear_echo_area ();
+}
+
+/* Read the name of a file and select the entire file. */
+DECLARE_INFO_COMMAND (info_view_file, "Read the name of a file and select it")
+{
+ char *line;
+
+ line = info_read_in_echo_area (window, "Find file: ");
+ if (!line)
+ {
+ info_abort_key (active_window, 1, 0);
+ return;
+ }
+
+ if (*line)
+ {
+ NODE *node;
+
+ node = info_get_node (line, "*");
+ if (!node)
+ {
+ if (info_recent_file_error)
+ info_error (info_recent_file_error);
+ else
+ info_error ("Cannot find \"%s\".", line);
+ }
+ else
+ {
+ set_remembered_pagetop_and_point (active_window);
+ info_set_node_of_window (window, node);
+ }
+ free (line);
+ }
+
+ if (!info_error_was_printed)
+ window_clear_echo_area ();
+}
+
+/* **************************************************************** */
+/* */
+/* Dumping and Printing Nodes */
+/* */
+/* **************************************************************** */
+
+#define VERBOSE_NODE_DUMPING
+static void write_node_to_stream ();
+static void dump_node_to_stream ();
+static void initialize_dumping ();
+
+/* Dump the nodes specified by FILENAME and NODENAMES to the file named
+ in OUTPUT_FILENAME. If DUMP_SUBNODES is non-zero, recursively dump
+ the nodes which appear in the menu of each node dumped. */
+void
+dump_nodes_to_file (filename, nodenames, output_filename, dump_subnodes)
+ char *filename;
+ char **nodenames;
+ char *output_filename;
+ int dump_subnodes;
+{
+ register int i;
+ FILE *output_stream;
+
+ /* Get the stream to print the nodes to. Special case of an output
+ filename of "-" means to dump the nodes to stdout. */
+ if (strcmp (output_filename, "-") == 0)
+ output_stream = stdout;
+ else
+ output_stream = fopen (output_filename, "w");
+
+ if (!output_stream)
+ {
+ info_error ("Could not create output file \"%s\".", output_filename);
+ return;
+ }
+
+ /* Print each node to stream. */
+ initialize_dumping ();
+ for (i = 0; nodenames[i]; i++)
+ dump_node_to_stream (filename, nodenames[i], output_stream, dump_subnodes);
+
+ if (output_stream != stdout)
+ fclose (output_stream);
+
+#if defined (VERBOSE_NODE_DUMPING)
+ info_error ("Done.");
+#endif /* VERBOSE_NODE_DUMPING */
+}
+
+/* A place to remember already dumped nodes. */
+static char **dumped_already = (char **)NULL;
+static int dumped_already_index = 0;
+static int dumped_already_slots = 0;
+
+static void
+initialize_dumping ()
+{
+ dumped_already_index = 0;
+}
+
+/* Get and print the node specified by FILENAME and NODENAME to STREAM.
+ If DUMP_SUBNODES is non-zero, recursively dump the nodes which appear
+ in the menu of each node dumped. */
+static void
+dump_node_to_stream (filename, nodename, stream, dump_subnodes)
+ char *filename, *nodename;
+ FILE *stream;
+ int dump_subnodes;
+{
+ register int i;
+ NODE *node;
+
+ node = info_get_node (filename, nodename);
+
+ if (!node)
+ {
+ if (info_recent_file_error)
+ info_error (info_recent_file_error);
+ else
+ {
+ if (filename && *nodename != '(')
+ info_error
+ (CANT_FILE_NODE, filename_non_directory (filename), nodename);
+ else
+ info_error (CANT_FIND_NODE, nodename);
+ }
+ return;
+ }
+
+ /* If we have already dumped this node, don't dump it again. */
+ for (i = 0; i < dumped_already_index; i++)
+ if (strcmp (node->nodename, dumped_already[i]) == 0)
+ {
+ free (node);
+ return;
+ }
+ add_pointer_to_array (node->nodename, dumped_already_index, dumped_already,
+ dumped_already_slots, 50, char *);
+
+#if defined (VERBOSE_NODE_DUMPING)
+ /* Maybe we should print some information about the node being output. */
+ if (node->filename)
+ info_error ("Writing node \"(%s)%s\"...",
+ filename_non_directory (node->filename), node->nodename);
+ else
+ info_error ("Writing node \"%s\"...", node->nodename);
+#endif /* VERBOSE_NODE_DUMPING */
+
+ write_node_to_stream (node, stream);
+
+ /* If we are dumping subnodes, get the list of menu items in this node,
+ and dump each one recursively. */
+ if (dump_subnodes)
+ {
+ REFERENCE **menu = (REFERENCE **)NULL;
+
+ /* If this node is an Index, do not dump the menu references. */
+ if (string_in_line ("Index", node->nodename) == -1)
+ menu = info_menu_of_node (node);
+
+ if (menu)
+ {
+ for (i = 0; menu[i]; i++)
+ {
+ /* We don't dump Info files which are different than the
+ current one. */
+ if (!menu[i]->filename)
+ dump_node_to_stream
+ (filename, menu[i]->nodename, stream, dump_subnodes);
+ }
+ info_free_references (menu);
+ }
+ }
+
+ free (node);
+}
+
+/* Dump NODE to FILENAME. If DUMP_SUBNODES is non-zero, recursively dump
+ the nodes which appear in the menu of each node dumped. */
+void
+dump_node_to_file (node, filename, dump_subnodes)
+ NODE *node;
+ char *filename;
+ int dump_subnodes;
+{
+ FILE *output_stream;
+ char *nodes_filename;
+
+ /* Get the stream to print this node to. Special case of an output
+ filename of "-" means to dump the nodes to stdout. */
+ if (strcmp (filename, "-") == 0)
+ output_stream = stdout;
+ else
+ output_stream = fopen (filename, "w");
+
+ if (!output_stream)
+ {
+ info_error ("Could not create output file \"%s\".", filename);
+ return;
+ }
+
+ if (node->parent)
+ nodes_filename = node->parent;
+ else
+ nodes_filename = node->filename;
+
+ initialize_dumping ();
+ dump_node_to_stream
+ (nodes_filename, node->nodename, output_stream, dump_subnodes);
+
+ if (output_stream != stdout)
+ fclose (output_stream);
+
+#if defined (VERBOSE_NODE_DUMPING)
+ info_error ("Done.");
+#endif /* VERBOSE_NODE_DUMPING */
+}
+
+#if !defined (DEFAULT_INFO_PRINT_COMMAND)
+# define DEFAULT_INFO_PRINT_COMMAND "lpr"
+#endif /* !DEFAULT_INFO_PRINT_COMMAND */
+
+DECLARE_INFO_COMMAND (info_print_node,
+ "Pipe the contents of this node through INFO_PRINT_COMMAND")
+{
+ print_node (window->node);
+}
+
+/* Print NODE on a printer piping it into INFO_PRINT_COMMAND. */
+void
+print_node (node)
+ NODE *node;
+{
+ char *print_command, *getenv ();
+ FILE *printer_pipe;
+
+ print_command = getenv ("INFO_PRINT_COMMAND");
+
+ if (!print_command || !*print_command)
+ print_command = DEFAULT_INFO_PRINT_COMMAND;
+
+ printer_pipe = popen (print_command, "w");
+
+ if (!printer_pipe)
+ {
+ info_error ("Cannot open pipe to \"%s\".", print_command);
+ return;
+ }
+
+#if defined (VERBOSE_NODE_DUMPING)
+ /* Maybe we should print some information about the node being output. */
+ if (node->filename)
+ info_error ("Printing node \"(%s)%s\"...",
+ filename_non_directory (node->filename), node->nodename);
+ else
+ info_error ("Printing node \"%s\"...", node->nodename);
+#endif /* VERBOSE_NODE_DUMPING */
+
+ write_node_to_stream (node, printer_pipe);
+ pclose (printer_pipe);
+
+#if defined (VERBOSE_NODE_DUMPING)
+ info_error ("Done.");
+#endif /* VERBOSE_NODE_DUMPING */
+}
+
+static void
+write_node_to_stream (node, stream)
+ NODE *node;
+ FILE *stream;
+{
+ fwrite (node->contents, 1, node->nodelen, stream);
+}
+
+/* **************************************************************** */
+/* */
+/* Info Searching Commands */
+/* */
+/* **************************************************************** */
+
+/* Variable controlling the garbage collection of files briefly visited
+ during searches. Such files are normally gc'ed, unless they were
+ compressed to begin with. If this variable is non-zero, it says
+ to gc even those file buffer contents which had to be uncompressed. */
+int gc_compressed_files = 0;
+
+static void info_gc_file_buffers ();
+
+static char *search_string = (char *)NULL;
+static int search_string_index = 0;
+static int search_string_size = 0;
+static int isearch_is_active = 0;
+
+/* Return the file buffer which belongs to WINDOW's node. */
+FILE_BUFFER *
+file_buffer_of_window (window)
+ WINDOW *window;
+{
+ /* If this window has no node, then it has no file buffer. */
+ if (!window->node)
+ return ((FILE_BUFFER *)NULL);
+
+ if (window->node->parent)
+ return (info_find_file (window->node->parent));
+
+ if (window->node->filename)
+ return (info_find_file (window->node->filename));
+
+ return ((FILE_BUFFER *)NULL);
+}
+
+/* Search for STRING in NODE starting at START. Return -1 if the string
+ was not found, or the location of the string if it was. If WINDOW is
+ passed as non-null, set the window's node to be NODE, its point to be
+ the found string, and readjust the window's pagetop. Final argument
+ DIR says which direction to search in. If it is positive, search
+ forward, else backwards. */
+long
+info_search_in_node (string, node, start, window, dir)
+ char *string;
+ NODE *node;
+ long start;
+ WINDOW *window;
+ int dir;
+{
+ SEARCH_BINDING binding;
+ long offset;
+
+ binding.buffer = node->contents;
+ binding.start = start;
+ binding.end = node->nodelen;
+ binding.flags = S_FoldCase;
+
+ if (dir < 0)
+ {
+ binding.end = 0;
+ binding.flags |= S_SkipDest;
+ }
+
+ if (binding.start < 0)
+ return (-1);
+
+ /* For incremental searches, we always wish to skip past the string. */
+ if (isearch_is_active)
+ binding.flags |= S_SkipDest;
+
+ offset = search (string, &binding);
+
+ if (offset != -1 && window)
+ {
+ set_remembered_pagetop_and_point (window);
+ if (window->node != node)
+ window_set_node_of_window (window, node);
+ window->point = offset;
+ window_adjust_pagetop (window);
+ }
+ return (offset);
+}
+
+/* Search NODE, looking for the largest possible match of STRING. Start the
+ search at START. Return the absolute position of the match, or -1, if
+ no part of the string could be found. */
+long
+info_target_search_node (node, string, start)
+ NODE *node;
+ char *string;
+ long start;
+{
+ register int i;
+ long offset;
+ char *target;
+
+ target = strdup (string);
+ i = strlen (target);
+
+ /* Try repeatedly searching for this string while removing words from
+ the end of it. */
+ while (i)
+ {
+ target[i] = '\0';
+ offset = info_search_in_node (target, node, start, (WINDOW *)NULL, 1);
+
+ if (offset != -1)
+ break;
+
+ /* Delete the last word from TARGET. */
+ for (; i && (!whitespace (target[i]) && (target[i] != ',')); i--);
+ }
+ free (target);
+ return (offset);
+}
+
+/* Search for STRING starting in WINDOW at point. If the string is found
+ in this node, set point to that position. Otherwise, get the file buffer
+ associated with WINDOW's node, and search through each node in that file.
+ If the search fails, return non-zero, else zero. Side-effect window
+ leaving the node and point where the string was found current. */
+static char *last_searched_for_string = (char *)NULL;
+static int
+info_search_internal (string, window, dir)
+ char *string;
+ WINDOW *window;
+ int dir;
+{
+ register int i;
+ FILE_BUFFER *file_buffer;
+ char *initial_nodename;
+ long ret, start = 0;
+
+ file_buffer = file_buffer_of_window (window);
+ initial_nodename = window->node->nodename;
+
+ if ((info_last_executed_command == info_search) &&
+ (last_searched_for_string) &&
+ (strcmp (last_searched_for_string, string) == 0))
+ {
+ ret = info_search_in_node
+ (string, window->node, window->point + dir, window, dir);
+ }
+ else
+ {
+ ret = info_search_in_node
+ (string, window->node, window->point, window, dir);
+ }
+
+ maybe_free (last_searched_for_string);
+ last_searched_for_string = strdup (string);
+
+ if (ret != -1)
+ {
+ /* We won! */
+ if (!echo_area_is_active && !isearch_is_active)
+ window_clear_echo_area ();
+ return (0);
+ }
+
+ /* The string wasn't found in the current node. Search through the
+ window's file buffer, iff the current node is not "*". */
+ if (!file_buffer || (strcmp (initial_nodename, "*") == 0))
+ return (-1);
+
+ /* If this file has tags, search through every subfile, starting at
+ this node's subfile and node. Otherwise, search through the
+ file's node list. */
+ if (file_buffer->tags)
+ {
+ register int current_tag, number_of_tags;
+ char *last_subfile;
+ TAG *tag;
+
+ /* Find number of tags and current tag. */
+ last_subfile = (char *)NULL;
+ for (i = 0; file_buffer->tags[i]; i++)
+ if (strcmp (initial_nodename, file_buffer->tags[i]->nodename) == 0)
+ {
+ current_tag = i;
+ last_subfile = file_buffer->tags[i]->filename;
+ }
+
+ number_of_tags = i;
+
+ /* If there is no last_subfile, our tag wasn't found. */
+ if (!last_subfile)
+ return (-1);
+
+ /* Search through subsequent nodes, wrapping around to the top
+ of the info file until we find the string or return to this
+ window's node and point. */
+ while (1)
+ {
+ NODE *node;
+
+ /* Allow C-g to quit the search, failing it if pressed. */
+ return_if_control_g (-1);
+
+ current_tag += dir;
+
+ if (current_tag < 0)
+ current_tag = number_of_tags - 1;
+ else if (current_tag == number_of_tags)
+ current_tag = 0;
+
+ tag = file_buffer->tags[current_tag];
+
+ if (!echo_area_is_active && (last_subfile != tag->filename))
+ {
+ window_message_in_echo_area
+ ("Searching subfile \"%s\"...",
+ filename_non_directory (tag->filename));
+
+ last_subfile = tag->filename;
+ }
+
+ node = info_get_node (file_buffer->filename, tag->nodename);
+
+ if (!node)
+ {
+ /* If not doing i-search... */
+ if (!echo_area_is_active)
+ {
+ if (info_recent_file_error)
+ info_error (info_recent_file_error);
+ else
+ info_error (CANT_FILE_NODE,
+ filename_non_directory (file_buffer->filename),
+ tag->nodename);
+ }
+ return (-1);
+ }
+
+ if (dir < 0)
+ start = tag->nodelen;
+
+ ret =
+ info_search_in_node (string, node, start, window, dir);
+
+ /* Did we find the string in this node? */
+ if (ret != -1)
+ {
+ /* Yes! We win. */
+ remember_window_and_node (window, node);
+ if (!echo_area_is_active)
+ window_clear_echo_area ();
+ return (0);
+ }
+
+ /* No. Free this node, and make sure that we haven't passed
+ our starting point. */
+ free (node);
+
+ if (strcmp (initial_nodename, tag->nodename) == 0)
+ return (-1);
+ }
+ }
+ return (-1);
+}
+
+DECLARE_INFO_COMMAND (info_search, "Read a string and search for it")
+{
+ char *line, *prompt;
+ int result, old_pagetop;
+ int direction;
+
+ if (count < 0)
+ direction = -1;
+ else
+ direction = 1;
+
+ /* Read a string from the user, defaulting the search to SEARCH_STRING. */
+ if (!search_string)
+ {
+ search_string = (char *)xmalloc (search_string_size = 100);
+ search_string[0] = '\0';
+ }
+
+ prompt = (char *)xmalloc (50 + strlen (search_string));
+
+ sprintf (prompt, "%s for string [%s]: ",
+ direction < 0 ? "Search backward" : "Search",
+ search_string);
+
+ line = info_read_in_echo_area (window, prompt);
+ free (prompt);
+
+ if (!line)
+ {
+ info_abort_key ();
+ return;
+ }
+
+ if (*line)
+ {
+ if (strlen (line) + 1 > search_string_size)
+ search_string = (char *)
+ xrealloc (search_string, (search_string_size += 50 + strlen (line)));
+
+ strcpy (search_string, line);
+ search_string_index = strlen (line);
+ free (line);
+ }
+
+ old_pagetop = active_window->pagetop;
+ result = info_search_internal (search_string, active_window, direction);
+
+ if (result != 0 && !info_error_was_printed)
+ info_error ("Search failed.");
+ else if (old_pagetop != active_window->pagetop)
+ {
+ int new_pagetop;
+
+ new_pagetop = active_window->pagetop;
+ active_window->pagetop = old_pagetop;
+ set_window_pagetop (active_window, new_pagetop);
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (active_window);
+ }
+
+ /* Perhaps free the unreferenced file buffers that were searched, but
+ not retained. */
+ info_gc_file_buffers ();
+}
+
+/* **************************************************************** */
+/* */
+/* Incremental Searching */
+/* */
+/* **************************************************************** */
+
+static void incremental_search ();
+
+DECLARE_INFO_COMMAND (isearch_forward,
+ "Search interactively for a string as you type it")
+{
+ incremental_search (window, count, key);
+}
+
+DECLARE_INFO_COMMAND (isearch_backward,
+ "Search interactively for a string as you type it")
+{
+ incremental_search (window, -count, key);
+}
+
+/* Incrementally search for a string as it is typed. */
+/* The last accepted incremental search string. */
+static char *last_isearch_accepted = (char *)NULL;
+
+/* The current incremental search string. */
+static char *isearch_string = (char *)NULL;
+static int isearch_string_index = 0;
+static int isearch_string_size = 0;
+static unsigned char isearch_terminate_search_key = ESC;
+
+/* Structure defining the current state of an incremental search. */
+typedef struct {
+ WINDOW_STATE_DECL; /* The node, pagetop and point. */
+ int search_index; /* Offset of the last char in the search string. */
+ int direction; /* The direction that this search is heading in. */
+ int failing; /* Whether or not this search failed. */
+} SEARCH_STATE;
+
+/* Array of search states. */
+static SEARCH_STATE **isearch_states = (SEARCH_STATE **)NULL;
+static int isearch_states_index = 0;
+static int isearch_states_slots = 0;
+
+/* Push the state of this search. */
+static void
+push_isearch (window, search_index, direction, failing)
+ WINDOW *window;
+ int search_index, direction, failing;
+{
+ SEARCH_STATE *state;
+
+ state = (SEARCH_STATE *)xmalloc (sizeof (SEARCH_STATE));
+ window_get_state (window, state);
+ state->search_index = search_index;
+ state->direction = direction;
+ state->failing = failing;
+
+ add_pointer_to_array (state, isearch_states_index, isearch_states,
+ isearch_states_slots, 20, SEARCH_STATE *);
+}
+
+/* Pop the state of this search to WINDOW, SEARCH_INDEX, and DIRECTION. */
+static void
+pop_isearch (window, search_index, direction, failing)
+ WINDOW *window;
+ int *search_index, *direction, *failing;
+{
+ SEARCH_STATE *state;
+
+ if (isearch_states_index)
+ {
+ isearch_states_index--;
+ state = isearch_states[isearch_states_index];
+ window_set_state (window, state);
+ *search_index = state->search_index;
+ *direction = state->direction;
+ *failing = state->failing;
+
+ free (state);
+ isearch_states[isearch_states_index] = (SEARCH_STATE *)NULL;
+ }
+}
+
+/* Free the memory used by isearch_states. */
+static void
+free_isearch_states ()
+{
+ register int i;
+
+ for (i = 0; i < isearch_states_index; i++)
+ {
+ free (isearch_states[i]);
+ isearch_states[i] = (SEARCH_STATE *)NULL;
+ }
+ isearch_states_index = 0;
+}
+
+/* Display the current search in the echo area. */
+static void
+show_isearch_prompt (dir, string, failing_p)
+ int dir;
+ unsigned char *string;
+ int failing_p;
+{
+ register int i;
+ char *prefix, *prompt, *p_rep;
+ int prompt_len, p_rep_index, p_rep_size;
+
+ if (dir < 0)
+ prefix = "I-search backward: ";
+ else
+ prefix = "I-search: ";
+
+ p_rep_index = p_rep_size = 0;
+ p_rep = (char *)NULL;
+ for (i = 0; string[i]; i++)
+ {
+ char *rep;
+
+ switch (string[i])
+ {
+ case ' ': rep = " "; break;
+ case LFD: rep = "\\n"; break;
+ case TAB: rep = "\\t"; break;
+ default:
+ rep = pretty_keyname (string[i]);
+ }
+ if ((p_rep_index + strlen (rep) + 1) >= p_rep_size)
+ p_rep = (char *)xrealloc (p_rep, p_rep_size += 100);
+
+ strcpy (p_rep + p_rep_index, rep);
+ p_rep_index += strlen (rep);
+ }
+
+ prompt_len = strlen (prefix) + p_rep_index + 20;
+ prompt = (char *)xmalloc (prompt_len);
+ sprintf (prompt, "%s%s%s", failing_p ? "Failing " : "", prefix,
+ p_rep ? p_rep : "");
+
+ window_message_in_echo_area ("%s", prompt);
+ maybe_free (p_rep);
+ free (prompt);
+ display_cursor_at_point (active_window);
+}
+
+static void
+incremental_search (window, count, ignore)
+ WINDOW *window;
+ int count;
+ unsigned char ignore;
+{
+ unsigned char key;
+ int last_search_result, search_result, dir;
+ SEARCH_STATE mystate, orig_state;
+
+ if (count < 0)
+ dir = -1;
+ else
+ dir = 1;
+
+ last_search_result = search_result = 0;
+
+ window_get_state (window, &orig_state);
+
+ isearch_string_index = 0;
+ if (!isearch_string_size)
+ isearch_string = (char *)xmalloc (isearch_string_size = 50);
+
+ /* Show the search string in the echo area. */
+ isearch_string[isearch_string_index] = '\0';
+ show_isearch_prompt (dir, isearch_string, search_result);
+
+ isearch_is_active = 1;
+
+ while (isearch_is_active)
+ {
+ VFunction *func = (VFunction *)NULL;
+ int quoted = 0;
+
+ /* If a recent display was interrupted, then do the redisplay now if
+ it is convenient. */
+ if (!info_any_buffered_input_p () && display_was_interrupted_p)
+ {
+ display_update_one_window (window);
+ display_cursor_at_point (active_window);
+ }
+
+ /* Read a character and dispatch on it. */
+ key = info_get_input_char ();
+ window_get_state (window, &mystate);
+
+ if (key == DEL)
+ {
+ /* User wants to delete one level of search? */
+ if (!isearch_states_index)
+ {
+ terminal_ring_bell ();
+ continue;
+ }
+ else
+ {
+ pop_isearch
+ (window, &isearch_string_index, &dir, &search_result);
+ isearch_string[isearch_string_index] = '\0';
+ show_isearch_prompt (dir, isearch_string, search_result);
+ goto after_search;
+ }
+ }
+ else if (key == Control ('q'))
+ {
+ key = info_get_input_char ();
+ quoted = 1;
+ }
+
+ /* We are about to search again, or quit. Save the current search. */
+ push_isearch (window, isearch_string_index, dir, search_result);
+
+ if (quoted)
+ goto insert_and_search;
+
+ if (!Meta_p (key) || (ISO_Latin_p && key < 160))
+ {
+ func = window->keymap[key].function;
+
+ /* If this key invokes an incremental search, then this means that
+ we will either search again in the same direction, search
+ again in the reverse direction, or insert the last search
+ string that was accepted through incremental searching. */
+ if (func == isearch_forward || func == isearch_backward)
+ {
+ if ((func == isearch_forward && dir > 0) ||
+ (func == isearch_backward && dir < 0))
+ {
+ /* If the user has typed no characters, then insert the
+ last successful search into the current search string. */
+ if (isearch_string_index == 0)
+ {
+ /* Of course, there must be something to insert. */
+ if (last_isearch_accepted)
+ {
+ if (strlen (last_isearch_accepted) + 1 >=
+ isearch_string_size)
+ isearch_string = (char *)
+ xrealloc (isearch_string,
+ isearch_string_size += 10 +
+ strlen (last_isearch_accepted));
+ strcpy (isearch_string, last_isearch_accepted);
+ isearch_string_index = strlen (isearch_string);
+ goto search_now;
+ }
+ else
+ continue;
+ }
+ else
+ {
+ /* Search again in the same direction. This means start
+ from a new place if the last search was successful. */
+ if (search_result == 0)
+ window->point += dir;
+ }
+ }
+ else
+ {
+ /* Reverse the direction of the search. */
+ dir = -dir;
+ }
+ }
+ else if (isprint (key) || func == (VFunction *)NULL)
+ {
+ insert_and_search:
+
+ if (isearch_string_index + 2 >= isearch_string_size)
+ isearch_string = (char *)xrealloc
+ (isearch_string, isearch_string_size += 100);
+
+ isearch_string[isearch_string_index++] = key;
+ isearch_string[isearch_string_index] = '\0';
+ goto search_now;
+ }
+ else if (func == info_abort_key)
+ {
+ /* If C-g pressed, and the search is failing, pop the search
+ stack back to the last unfailed search. */
+ if (isearch_states_index && (search_result != 0))
+ {
+ terminal_ring_bell ();
+ while (isearch_states_index && (search_result != 0))
+ pop_isearch
+ (window, &isearch_string_index, &dir, &search_result);
+ isearch_string[isearch_string_index] = '\0';
+ show_isearch_prompt (dir, isearch_string, search_result);
+ continue;
+ }
+ else
+ goto exit_search;
+ }
+ else
+ goto exit_search;
+ }
+ else
+ {
+ exit_search:
+ /* The character is not printable, or it has a function which is
+ non-null. Exit the search, remembering the search string. If
+ the key is not the same as the isearch_terminate_search_key,
+ then push it into pending input. */
+ if (isearch_string_index && func != info_abort_key)
+ {
+ maybe_free (last_isearch_accepted);
+ last_isearch_accepted = strdup (isearch_string);
+ }
+
+ if (key != isearch_terminate_search_key)
+ info_set_pending_input (key);
+
+ if (func == info_abort_key)
+ {
+ if (isearch_states_index)
+ window_set_state (window, &orig_state);
+ }
+
+ if (!echo_area_is_active)
+ window_clear_echo_area ();
+
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (active_window);
+
+ isearch_is_active = 0;
+ continue;
+ }
+
+ /* Search for the contents of isearch_string. */
+ search_now:
+ show_isearch_prompt (dir, isearch_string, search_result);
+
+ if (search_result == 0)
+ {
+ /* Check to see if the current search string is right here. If
+ we are looking at it, then don't bother calling the search
+ function. */
+ if (((dir < 0) &&
+ (strncasecmp (window->node->contents + window->point,
+ isearch_string, isearch_string_index) == 0)) ||
+ ((dir > 0) &&
+ ((window->point - isearch_string_index) >= 0) &&
+ (strncasecmp (window->node->contents +
+ (window->point - (isearch_string_index - 1)),
+ isearch_string, isearch_string_index) == 0)))
+ {
+ if (dir > 0)
+ window->point++;
+ }
+ else
+ search_result = info_search_internal (isearch_string, window, dir);
+ }
+
+ /* If this search failed, and we didn't already have a failed search,
+ then ring the terminal bell. */
+ if (search_result != 0 && last_search_result == 0)
+ terminal_ring_bell ();
+
+ after_search:
+ show_isearch_prompt (dir, isearch_string, search_result);
+
+ if (search_result == 0)
+ {
+ if ((mystate.node == window->node) &&
+ (mystate.pagetop != window->pagetop))
+ {
+ int newtop = window->pagetop;
+ window->pagetop = mystate.pagetop;
+ set_window_pagetop (window, newtop);
+ }
+ display_update_one_window (window);
+ display_cursor_at_point (window);
+ }
+
+ last_search_result = search_result;
+ }
+
+ /* Free the memory used to remember each search state. */
+ free_isearch_states ();
+
+ /* Perhaps GC some file buffers. */
+ info_gc_file_buffers ();
+
+ /* After searching, leave the window in the correct state. */
+ if (!echo_area_is_active)
+ window_clear_echo_area ();
+}
+
+/* GC some file buffers. A file buffer can be gc-ed if there we have
+ no nodes in INFO_WINDOWS that reference this file buffer's contents.
+ Garbage collecting a file buffer means to free the file buffers
+ contents. */
+static void
+info_gc_file_buffers ()
+{
+ register int fb_index, iw_index, i;
+ register FILE_BUFFER *fb;
+ register INFO_WINDOW *iw;
+
+ if (!info_loaded_files)
+ return;
+
+ for (fb_index = 0; fb = info_loaded_files[fb_index]; fb_index++)
+ {
+ int fb_referenced_p = 0;
+
+ /* If already gc-ed, do nothing. */
+ if (!fb->contents)
+ continue;
+
+ /* If this file had to be uncompressed, check to see if we should
+ gc it. This means that the user-variable "gc-compressed-files"
+ is non-zero. */
+ if ((fb->flags & N_IsCompressed) && !gc_compressed_files)
+ continue;
+
+ /* If this file's contents are not gc-able, move on. */
+ if (fb->flags & N_CannotGC)
+ continue;
+
+ /* Check each INFO_WINDOW to see if it has any nodes which reference
+ this file. */
+ for (iw_index = 0; iw = info_windows[iw_index]; iw_index++)
+ {
+ for (i = 0; iw->nodes && iw->nodes[i]; i++)
+ {
+ if ((strcmp (fb->fullpath, iw->nodes[i]->filename) == 0) ||
+ (strcmp (fb->filename, iw->nodes[i]->filename) == 0))
+ {
+ fb_referenced_p = 1;
+ break;
+ }
+ }
+ }
+
+ /* If this file buffer wasn't referenced, free its contents. */
+ if (!fb_referenced_p)
+ {
+ free (fb->contents);
+ fb->contents = (char *)NULL;
+ }
+ }
+}
+
+/* **************************************************************** */
+/* */
+/* Traversing and Selecting References */
+/* */
+/* **************************************************************** */
+
+/* Move to the next or previous cross reference in this node. */
+static void
+info_move_to_xref (window, count, key, dir)
+ WINDOW *window;
+ int count;
+ unsigned char key;
+ int dir;
+{
+ long firstmenu, firstxref;
+ long nextmenu, nextxref;
+ long placement = -1;
+ long start = 0;
+ NODE *node = window->node;
+
+ if (dir < 0)
+ start = node->nodelen;
+
+ /* This search is only allowed to fail if there is no menu or cross
+ reference in the current node. Otherwise, the first menu or xref
+ found is moved to. */
+
+ firstmenu = info_search_in_node
+ (INFO_MENU_ENTRY_LABEL, node, start, (WINDOW *)NULL, dir);
+
+ /* FIRSTMENU may point directly to the line defining the menu. Skip that
+ and go directly to the first item. */
+
+ if (firstmenu != -1)
+ {
+ char *text = node->contents + firstmenu;
+
+ if (strncmp (text, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL)) == 0)
+ firstmenu = info_search_in_node
+ (INFO_MENU_ENTRY_LABEL, node, firstmenu + dir, (WINDOW *)NULL, dir);
+ }
+
+ firstxref =
+ info_search_in_node (INFO_XREF_LABEL, node, start, (WINDOW *)NULL, dir);
+
+#if defined (HANDLE_MAN_PAGES)
+ if ((firstxref == -1) && (node->flags & N_IsManPage))
+ {
+ firstxref = locate_manpage_xref (node, start, dir);
+ }
+#endif /* HANDLE_MAN_PAGES */
+
+ if (firstmenu == -1 && firstxref == -1)
+ {
+ info_error ("No cross references in this node.");
+ return;
+ }
+
+ /* There is at least one cross reference or menu entry in this node.
+ Try hard to find the next available one. */
+
+ nextmenu = info_search_in_node
+ (INFO_MENU_ENTRY_LABEL, node, window->point + dir, (WINDOW *)NULL, dir);
+
+ nextxref = info_search_in_node
+ (INFO_XREF_LABEL, node, window->point + dir, (WINDOW *)NULL, dir);
+
+#if defined (HANDLE_MAN_PAGES)
+ if ((nextxref == -1) && (node->flags & N_IsManPage) && (firstxref != -1))
+ nextxref = locate_manpage_xref (node, window->point + dir, dir);
+#endif /* HANDLE_MAN_PAGES */
+
+ /* Ignore "Menu:" as a menu item. */
+ if (nextmenu != -1)
+ {
+ char *text = node->contents + nextmenu;
+
+ if (strncmp (text, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL)) == 0)
+ nextmenu = info_search_in_node
+ (INFO_MENU_ENTRY_LABEL, node, nextmenu + dir, (WINDOW *)NULL, dir);
+ }
+
+ /* If there is both a next menu entry, and a next xref entry, choose the
+ one which occurs first. Otherwise, select the one which actually
+ appears in this node following point. */
+ if (nextmenu != -1 && nextxref != -1)
+ {
+ if (((dir == 1) && (nextmenu < nextxref)) ||
+ ((dir == -1) && (nextmenu > nextxref)))
+ placement = nextmenu + 1;
+ else
+ placement = nextxref;
+ }
+ else if (nextmenu != -1)
+ placement = nextmenu + 1;
+ else if (nextxref != -1)
+ placement = nextxref;
+
+ /* If there was neither a menu or xref entry appearing in this node after
+ point, choose the first menu or xref entry appearing in this node. */
+ if (placement == -1)
+ {
+ if (firstmenu != -1 && firstxref != -1)
+ {
+ if (((dir == 1) && (firstmenu < firstxref)) ||
+ ((dir == -1) && (firstmenu > firstxref)))
+ placement = firstmenu + 1;
+ else
+ placement = firstxref;
+ }
+ else if (firstmenu != -1)
+ placement = firstmenu + 1;
+ else
+ placement = firstxref;
+ }
+ window->point = placement;
+ window_adjust_pagetop (window);
+ window->flags |= W_UpdateWindow;
+}
+
+DECLARE_INFO_COMMAND (info_move_to_prev_xref,
+ "Move to the previous cross reference")
+{
+ if (count < 0)
+ info_move_to_prev_xref (window, -count, key);
+ else
+ info_move_to_xref (window, count, key, -1);
+}
+
+DECLARE_INFO_COMMAND (info_move_to_next_xref,
+ "Move to the next cross reference")
+{
+ if (count < 0)
+ info_move_to_next_xref (window, -count, key);
+ else
+ info_move_to_xref (window, count, key, 1);
+}
+
+/* Select the menu item or reference that appears on this line. */
+DECLARE_INFO_COMMAND (info_select_reference_this_line,
+ "Select reference or menu item appearing on this line")
+{
+ char *line;
+ NODE *orig;
+
+ line = window->line_starts[window_line_of_point (window)];
+ orig = window->node;
+
+ /* If this line contains a menu item, select that one. */
+ if (strncmp ("* ", line, 2) == 0)
+ info_menu_or_ref_item (window, count, key, info_menu_of_node, 0);
+ else
+ info_menu_or_ref_item (window, count, key, info_xrefs_of_node, 0);
+}
+
+/* **************************************************************** */
+/* */
+/* Miscellaneous Info Commands */
+/* */
+/* **************************************************************** */
+
+/* What to do when C-g is pressed in a window. */
+DECLARE_INFO_COMMAND (info_abort_key, "Cancel current operation")
+{
+ /* If error printing doesn't oridinarily ring the bell, do it now,
+ since C-g always rings the bell. Otherwise, let the error printer
+ do it. */
+ if (!info_error_rings_bell_p)
+ terminal_ring_bell ();
+ info_error ("Quit");
+
+ info_initialize_numeric_arg ();
+ info_clear_pending_input ();
+ info_last_executed_command = (VFunction *)NULL;
+}
+
+/* Move the cursor to the desired line of the window. */
+DECLARE_INFO_COMMAND (info_move_to_window_line,
+ "Move to the cursor to a specific line of the window")
+{
+ int line;
+
+ /* With no numeric argument of any kind, default to the center line. */
+ if (!info_explicit_arg && count == 1)
+ line = (window->height / 2) + window->pagetop;
+ else
+ {
+ if (count < 0)
+ line = (window->height + count) + window->pagetop;
+ else
+ line = window->pagetop + count;
+ }
+
+ /* If the line doesn't appear in this window, make it do so. */
+ if ((line - window->pagetop) >= window->height)
+ line = window->pagetop + (window->height - 1);
+
+ /* If the line is too small, make it fit. */
+ if (line < window->pagetop)
+ line = window->pagetop;
+
+ /* If the selected line is past the bottom of the node, force it back. */
+ if (line >= window->line_count)
+ line = window->line_count - 1;
+
+ window->point = (window->line_starts[line] - window->node->contents);
+}
+
+/* Clear the screen and redraw its contents. Given a numeric argument,
+ move the line the cursor is on to the COUNT'th line of the window. */
+DECLARE_INFO_COMMAND (info_redraw_display, "Redraw the display")
+{
+ if ((!info_explicit_arg && count == 1) || echo_area_is_active)
+ {
+ terminal_clear_screen ();
+ display_clear_display (the_display);
+ window_mark_chain (windows, W_UpdateWindow);
+ display_update_display (windows);
+ }
+ else
+ {
+ int desired_line, point_line;
+ int new_pagetop;
+
+ point_line = window_line_of_point (window) - window->pagetop;
+
+ if (count < 0)
+ desired_line = window->height + count;
+ else
+ desired_line = count;
+
+ if (desired_line < 0)
+ desired_line = 0;
+
+ if (desired_line >= window->height)
+ desired_line = window->height - 1;
+
+ if (desired_line == point_line)
+ return;
+
+ new_pagetop = window->pagetop + (point_line - desired_line);
+
+ set_window_pagetop (window, new_pagetop);
+ }
+}
+/* This command does nothing. It is the fact that a key is bound to it
+ that has meaning. See the code at the top of info_session (). */
+DECLARE_INFO_COMMAND (info_quit, "Quit using Info")
+{}
+
+
+/* **************************************************************** */
+/* */
+/* Reading Keys and Dispatching on Them */
+/* */
+/* **************************************************************** */
+
+/* Declaration only. Special cased in info_dispatch_on_key (). */
+DECLARE_INFO_COMMAND (info_do_lowercase_version, "")
+{}
+
+static void
+dispatch_error (keyseq)
+ char *keyseq;
+{
+ char *rep;
+
+ rep = pretty_keyseq (keyseq);
+
+ if (!echo_area_is_active)
+ info_error ("Unknown command (%s).", rep);
+ else
+ {
+ char *temp;
+
+ temp = (char *)xmalloc (1 + strlen (rep) + strlen ("\"\" is invalid"));
+
+ sprintf (temp, "\"%s\" is invalid", rep);
+ terminal_ring_bell ();
+ inform_in_echo_area (temp);
+ free (temp);
+ }
+}
+
+/* Keeping track of key sequences. */
+static char *info_keyseq = (char *)NULL;
+static char keyseq_rep[100];
+static int info_keyseq_index = 0;
+static int info_keyseq_size = 0;
+static int info_keyseq_displayed_p = 0;
+
+/* Initialize the length of the current key sequence. */
+void
+initialize_keyseq ()
+{
+ info_keyseq_index = 0;
+ info_keyseq_displayed_p = 0;
+}
+
+/* Add CHARACTER to the current key sequence. */
+void
+add_char_to_keyseq (character)
+ char character;
+{
+ if (info_keyseq_index + 2 >= info_keyseq_size)
+ info_keyseq = (char *)xrealloc (info_keyseq, info_keyseq_size += 10);
+
+ info_keyseq[info_keyseq_index++] = character;
+ info_keyseq[info_keyseq_index] = '\0';
+}
+
+/* Return the pretty printable string which represents KEYSEQ. */
+char *
+pretty_keyseq (keyseq)
+ char *keyseq;
+{
+ register int i;
+
+ keyseq_rep[0] = '\0';
+
+ for (i = 0; keyseq[i]; i++)
+ {
+ sprintf (keyseq_rep + strlen (keyseq_rep), "%s%s",
+ strlen (keyseq_rep) ? " " : "",
+ pretty_keyname (keyseq[i]));
+ }
+
+ return (keyseq_rep);
+}
+
+/* Display the current value of info_keyseq. If argument EXPECTING is
+ non-zero, input is expected to be read after the key sequence is
+ displayed, so add an additional prompting character to the sequence. */
+void
+display_info_keyseq (expecting_future_input)
+ int expecting_future_input;
+{
+ char *rep;
+
+ rep = pretty_keyseq (info_keyseq);
+ if (expecting_future_input)
+ strcat (rep, "-");
+
+ if (echo_area_is_active)
+ inform_in_echo_area (rep);
+ else
+ {
+ window_message_in_echo_area (rep);
+ display_cursor_at_point (active_window);
+ }
+ info_keyseq_displayed_p = 1;
+}
+
+/* Called by interactive commands to read a keystroke. */
+unsigned char
+info_get_another_input_char ()
+{
+ int ready = 0;
+
+ /* If there isn't any input currently available, then wait a
+ moment looking for input. If we don't get it fast enough,
+ prompt a little bit with the current key sequence. */
+ if (!info_keyseq_displayed_p &&
+ !info_any_buffered_input_p () &&
+ !info_input_pending_p ())
+ {
+#if defined (FD_SET)
+ struct timeval timer;
+ fd_set readfds;
+
+ FD_ZERO (&readfds);
+ FD_SET (fileno (info_input_stream), &readfds);
+ timer.tv_sec = 1;
+ timer.tv_usec = 750;
+ ready = select (1, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timer);
+#endif /* FD_SET */
+ }
+
+ if (!ready)
+ display_info_keyseq (1);
+
+ return (info_get_input_char ());
+}
+
+/* Do the command associated with KEY in MAP. If the associated command is
+ really a keymap, then read another key, and dispatch into that map. */
+void
+info_dispatch_on_key (key, map)
+ unsigned char key;
+ Keymap map;
+{
+ if (Meta_p (key) && (!ISO_Latin_p || map[key].function != ea_insert))
+ {
+ if (map[ESC].type == ISKMAP)
+ {
+ map = (Keymap)map[ESC].function;
+ add_char_to_keyseq (ESC);
+ key = UnMeta (key);
+ info_dispatch_on_key (key, map);
+ }
+ else
+ {
+ dispatch_error (info_keyseq);
+ }
+ return;
+ }
+
+ switch (map[key].type)
+ {
+ case ISFUNC:
+ {
+ VFunction *func;
+
+ func = map[key].function;
+ if (func != (VFunction *)NULL)
+ {
+ /* Special case info_do_lowercase_version (). */
+ if (func == info_do_lowercase_version)
+ {
+ info_dispatch_on_key (tolower (key), map);
+ return;
+ }
+
+ add_char_to_keyseq (key);
+
+ if (info_keyseq_displayed_p)
+ display_info_keyseq (0);
+
+ {
+ WINDOW *where;
+
+ where = active_window;
+ (*map[key].function)
+ (active_window, info_numeric_arg * info_numeric_arg_sign, key);
+
+ /* If we have input pending, then the last command was a prefix
+ command. Don't change the value of the last function vars.
+ Otherwise, remember the last command executed in the var
+ appropriate to the window in which it was executed. */
+ if (!info_input_pending_p ())
+ {
+ if (where == the_echo_area)
+ ea_last_executed_command = map[key].function;
+ else
+ info_last_executed_command = map[key].function;
+ }
+ }
+ }
+ else
+ {
+ add_char_to_keyseq (key);
+ dispatch_error (info_keyseq);
+ return;
+ }
+ }
+ break;
+
+ case ISKMAP:
+ add_char_to_keyseq (key);
+ if (map[key].function != (VFunction *)NULL)
+ {
+ unsigned char newkey;
+
+ newkey = info_get_another_input_char ();
+ info_dispatch_on_key (newkey, (Keymap)map[key].function);
+ }
+ else
+ {
+ dispatch_error (info_keyseq);
+ return;
+ }
+ break;
+ }
+}
+
+/* **************************************************************** */
+/* */
+/* Numeric Arguments */
+/* */
+/* **************************************************************** */
+
+/* Handle C-u style numeric args, as well as M--, and M-digits. */
+
+/* Non-zero means that an explicit argument has been passed to this
+ command, as in C-u C-v. */
+int info_explicit_arg = 0;
+
+/* The sign of the numeric argument. */
+int info_numeric_arg_sign = 1;
+
+/* The value of the argument itself. */
+int info_numeric_arg = 1;
+
+/* Add the current digit to the argument in progress. */
+DECLARE_INFO_COMMAND (info_add_digit_to_numeric_arg,
+ "Add this digit to the current numeric argument")
+{
+ info_numeric_arg_digit_loop (window, 0, key);
+}
+
+/* C-u, universal argument. Multiply the current argument by 4.
+ Read a key. If the key has nothing to do with arguments, then
+ dispatch on it. If the key is the abort character then abort. */
+DECLARE_INFO_COMMAND (info_universal_argument,
+ "Start (or multiply by 4) the current numeric argument")
+{
+ info_numeric_arg *= 4;
+ info_numeric_arg_digit_loop (window, 0, 0);
+}
+
+/* Create a default argument. */
+void
+info_initialize_numeric_arg ()
+{
+ info_numeric_arg = info_numeric_arg_sign = 1;
+ info_explicit_arg = 0;
+}
+
+DECLARE_INFO_COMMAND (info_numeric_arg_digit_loop,
+ "Internally used by \\[universal-argument]")
+{
+ unsigned char pure_key;
+ Keymap keymap = window->keymap;
+
+ while (1)
+ {
+ if (key)
+ pure_key = key;
+ else
+ {
+ if (display_was_interrupted_p && !info_any_buffered_input_p ())
+ display_update_display (windows);
+
+ if (active_window != the_echo_area)
+ display_cursor_at_point (active_window);
+
+ pure_key = key = info_get_another_input_char ();
+
+ if (Meta_p (key))
+ add_char_to_keyseq (ESC);
+
+ add_char_to_keyseq (UnMeta (key));
+ }
+
+ if (Meta_p (key))
+ key = UnMeta (key);
+
+ if (keymap[key].type == ISFUNC &&
+ keymap[key].function == info_universal_argument)
+ {
+ info_numeric_arg *= 4;
+ key = 0;
+ continue;
+ }
+
+ if (isdigit (key))
+ {
+ if (info_explicit_arg)
+ info_numeric_arg = (info_numeric_arg * 10) + (key - '0');
+ else
+ info_numeric_arg = (key - '0');
+ info_explicit_arg = 1;
+ }
+ else
+ {
+ if (key == '-' && !info_explicit_arg)
+ {
+ info_numeric_arg_sign = -1;
+ info_numeric_arg = 1;
+ }
+ else
+ {
+ info_keyseq_index--;
+ info_dispatch_on_key (pure_key, keymap);
+ return;
+ }
+ }
+ key = 0;
+ }
+}
+
+/* **************************************************************** */
+/* */
+/* Input Character Buffering */
+/* */
+/* **************************************************************** */
+
+/* Character waiting to be read next. */
+static int pending_input_character = 0;
+
+/* How to make there be no pending input. */
+static void
+info_clear_pending_input ()
+{
+ pending_input_character = 0;
+}
+
+/* How to set the pending input character. */
+static void
+info_set_pending_input (key)
+ unsigned char key;
+{
+ pending_input_character = key;
+}
+
+/* How to see if there is any pending input. */
+unsigned char
+info_input_pending_p ()
+{
+ return (pending_input_character);
+}
+
+/* Largest number of characters that we can read in advance. */
+#define MAX_INFO_INPUT_BUFFERING 512
+
+static int pop_index = 0, push_index = 0;
+static unsigned char info_input_buffer[MAX_INFO_INPUT_BUFFERING];
+
+/* Add KEY to the buffer of characters to be read. */
+static void
+info_push_typeahead (key)
+ unsigned char key;
+{
+ /* Flush all pending input in the case of C-g pressed. */
+ if (key == Control ('g'))
+ {
+ push_index = pop_index;
+ info_set_pending_input (Control ('g'));
+ }
+ else
+ {
+ info_input_buffer[push_index++] = key;
+ if (push_index >= sizeof (info_input_buffer))
+ push_index = 0;
+ }
+}
+
+/* Return the amount of space available in INFO_INPUT_BUFFER for new chars. */
+static int
+info_input_buffer_space_available ()
+{
+ if (pop_index > push_index)
+ return (pop_index - push_index);
+ else
+ return (sizeof (info_input_buffer - (push_index - pop_index)));
+}
+
+/* Get a key from the buffer of characters to be read.
+ Return the key in KEY.
+ Result is non-zero if there was a key, or 0 if there wasn't. */
+static int
+info_get_key_from_typeahead (key)
+ unsigned char *key;
+{
+ if (push_index == pop_index)
+ return (0);
+
+ *key = info_input_buffer[pop_index++];
+
+ if (pop_index >= sizeof (info_input_buffer))
+ pop_index = 0;
+
+ return (1);
+}
+
+int
+info_any_buffered_input_p ()
+{
+ info_gather_typeahead ();
+ return (push_index != pop_index);
+}
+
+/* Push KEY into the *front* of the input buffer. Returns non-zero if
+ successful, zero if there is no space left in the buffer. */
+static int
+info_replace_key_to_typeahead (key)
+ unsigned char key;
+{
+ if (info_input_buffer_space_available ())
+ {
+ pop_index--;
+ if (pop_index < 0)
+ pop_index = sizeof (info_input_buffer) - 1;
+ info_input_buffer[pop_index] = key;
+ return (1);
+ }
+ return (0);
+}
+
+/* If characters are available to be read, then read them and stuff them into
+ info_input_buffer. Otherwise, do nothing. */
+void
+info_gather_typeahead ()
+{
+ int tty, space_avail;
+ long chars_avail;
+ unsigned char input[MAX_INFO_INPUT_BUFFERING];
+
+ tty = fileno (info_input_stream);
+ chars_avail = 0;
+
+ space_avail = info_input_buffer_space_available ();
+
+ /* If we can just find out how many characters there are to read, do so. */
+#if defined (FIONREAD)
+ {
+ ioctl (tty, FIONREAD, &chars_avail);
+
+ if (chars_avail > space_avail)
+ chars_avail = space_avail;
+
+ if (chars_avail)
+ read (tty, &input[0], chars_avail);
+ }
+#else /* !FIONREAD */
+# if defined (O_NDELAY)
+ {
+ int flags;
+
+ flags = fcntl (tty, F_GETFL, 0);
+
+ fcntl (tty, F_SETFL, (flags | O_NDELAY));
+ chars_avail = read (tty, &input[0], space_avail);
+ fcntl (tty, F_SETFL, flags);
+
+ if (chars_avail == -1)
+ chars_avail = 0;
+ }
+# endif /* O_NDELAY */
+#endif /* !FIONREAD */
+
+ /* Store the input characters just read into our input buffer. */
+ {
+ register int i;
+
+ for (i = 0; i < chars_avail; i++)
+ info_push_typeahead (input[i]);
+ }
+}
+
+/* How to read a single character. */
+unsigned char
+info_get_input_char ()
+{
+ unsigned char keystroke;
+
+ info_gather_typeahead ();
+
+ if (pending_input_character)
+ {
+ keystroke = pending_input_character;
+ pending_input_character = 0;
+ }
+ else if (info_get_key_from_typeahead (&keystroke) == 0)
+ {
+ int rawkey;
+
+ rawkey = getc (info_input_stream);
+ keystroke = rawkey;
+
+ if (rawkey == EOF)
+ {
+ if (info_input_stream != stdin)
+ {
+ fclose (info_input_stream);
+ info_input_stream = stdin;
+ display_inhibited = 0;
+ display_update_display (windows);
+ display_cursor_at_point (active_window);
+ rawkey = getc (info_input_stream);
+ keystroke = rawkey;
+ }
+
+ if (rawkey == EOF)
+ {
+ terminal_unprep_terminal ();
+ close_dribble_file ();
+ exit (0);
+ }
+ }
+ }
+
+ if (info_dribble_file)
+ dribble (keystroke);
+
+ return (keystroke);
+}