156164Sru/* session.c -- user windowing interface to Info.
2146520Sru   $Id: session.c,v 1.16 2004/12/14 00:15:36 karl Exp $
321495Sjmacd
4146520Sru   Copyright (C) 1993, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004
5116530Sru   Free Software Foundation, Inc.
621495Sjmacd
721495Sjmacd   This program is free software; you can redistribute it and/or modify
821495Sjmacd   it under the terms of the GNU General Public License as published by
921495Sjmacd   the Free Software Foundation; either version 2, or (at your option)
1021495Sjmacd   any later version.
1121495Sjmacd
1221495Sjmacd   This program is distributed in the hope that it will be useful,
1321495Sjmacd   but WITHOUT ANY WARRANTY; without even the implied warranty of
1421495Sjmacd   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1521495Sjmacd   GNU General Public License for more details.
1621495Sjmacd
1721495Sjmacd   You should have received a copy of the GNU General Public License
1821495Sjmacd   along with this program; if not, write to the Free Software
1921495Sjmacd   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
2021495Sjmacd
21146520Sru   Originally written by Brian Fox (bfox@ai.mit.edu). */
2221495Sjmacd
2321495Sjmacd#include "info.h"
24146520Sru#include "search.h"
2521495Sjmacd#include <sys/ioctl.h>
2621495Sjmacd
2721495Sjmacd#if defined (HAVE_SYS_TIME_H)
2821495Sjmacd#  include <sys/time.h>
2921495Sjmacd#  define HAVE_STRUCT_TIMEVAL
3021495Sjmacd#endif /* HAVE_SYS_TIME_H */
3121495Sjmacd
3221495Sjmacd#if defined (HANDLE_MAN_PAGES)
3321495Sjmacd#  include "man.h"
3421495Sjmacd#endif
3521495Sjmacd
36146520Srustatic void info_clear_pending_input (void);
37146520Srustatic void info_set_pending_input (unsigned char key);
38146520Srustatic void info_handle_pointer (char *label, WINDOW *window);
39146520Srustatic void display_info_keyseq (int expecting_future_input);
40146520Sruchar *node_printed_rep (NODE *node);
4156164Sru
4221495Sjmacd/* **************************************************************** */
4342664Smarkm/*                                                                  */
4442664Smarkm/*                   Running an Info Session                        */
4542664Smarkm/*                                                                  */
4621495Sjmacd/* **************************************************************** */
4721495Sjmacd
4821495Sjmacd/* The place that we are reading input from. */
4942664Smarkmstatic FILE *info_input_stream = NULL;
5021495Sjmacd
5121495Sjmacd/* The last executed command. */
5242664SmarkmVFunction *info_last_executed_command = NULL;
5321495Sjmacd
5421495Sjmacd/* Becomes non-zero when 'q' is typed to an Info window. */
5521495Sjmacdint quit_info_immediately = 0;
5621495Sjmacd
5721495Sjmacd/* Array of structures describing for each window which nodes have been
5821495Sjmacd   visited in that window. */
5942664SmarkmINFO_WINDOW **info_windows = NULL;
6021495Sjmacd
6121495Sjmacd/* Where to add the next window, if we need to add one. */
6221495Sjmacdstatic int info_windows_index = 0;
6321495Sjmacd
6442664Smarkm/* Number of slots allocated to `info_windows'. */
6521495Sjmacdstatic int info_windows_slots = 0;
6621495Sjmacd
67146520Sruvoid remember_window_and_node (WINDOW *window, NODE *node);
68146520Sruvoid forget_window_and_nodes (WINDOW *window);
69146520Sruvoid display_startup_message_and_start (void);
7021495Sjmacd
7121495Sjmacd/* Begin an info session finding the nodes specified by FILENAME and NODENAMES.
7221495Sjmacd   For each loaded node, create a new window.  Always split the largest of the
7321495Sjmacd   available windows. */
7421495Sjmacdvoid
75146520Srubegin_multiple_window_info_session (char *filename, char **nodenames)
7621495Sjmacd{
7721495Sjmacd  register int i;
7821495Sjmacd  WINDOW *window = (WINDOW *)NULL;
7921495Sjmacd
8021495Sjmacd  for (i = 0; nodenames[i]; i++)
8121495Sjmacd    {
8221495Sjmacd      NODE *node;
8321495Sjmacd
8421495Sjmacd      node = info_get_node (filename, nodenames[i]);
8521495Sjmacd
8621495Sjmacd      if (!node)
8742664Smarkm        break;
8821495Sjmacd
8921495Sjmacd      /* If this is the first node, initialize the info session. */
9021495Sjmacd      if (!window)
9142664Smarkm        {
9242664Smarkm          initialize_info_session (node, 1);
9342664Smarkm          window = active_window;
9442664Smarkm        }
9521495Sjmacd      else
9642664Smarkm        {
9742664Smarkm          /* Find the largest window in WINDOWS, and make that be the active
9842664Smarkm             one.  Then split it and add our window and node to the list
9942664Smarkm             of remembered windows and nodes.  Then tile the windows. */
10056164Sru          WINDOW *win, *largest = NULL;
10142664Smarkm          int max_height = 0;
10221495Sjmacd
10342664Smarkm          for (win = windows; win; win = win->next)
10442664Smarkm            if (win->height > max_height)
10542664Smarkm              {
10642664Smarkm                max_height = win->height;
10742664Smarkm                largest = win;
10842664Smarkm              }
10921495Sjmacd
11042664Smarkm          if (!largest)
11142664Smarkm            {
11242664Smarkm              display_update_display (windows);
113146520Sru              info_error ((char *) msg_cant_find_window, NULL, NULL);
11442664Smarkm              info_session ();
11556164Sru              xexit (0);
11642664Smarkm            }
11721495Sjmacd
11842664Smarkm          active_window = largest;
11942664Smarkm          window = window_make_window (node);
12042664Smarkm          if (window)
12142664Smarkm            {
12242664Smarkm              window_tile_windows (TILE_INTERNALS);
12342664Smarkm              remember_window_and_node (window, node);
12442664Smarkm            }
12542664Smarkm          else
12642664Smarkm            {
12742664Smarkm              display_update_display (windows);
128146520Sru              info_error ((char *) msg_win_too_small, NULL, NULL);
12942664Smarkm              info_session ();
13056164Sru              xexit (0);
13142664Smarkm            }
13242664Smarkm        }
13321495Sjmacd    }
13421495Sjmacd  display_startup_message_and_start ();
13521495Sjmacd}
13621495Sjmacd
13721495Sjmacd/* Start an info session with INITIAL_NODE, and an error message in the echo
13821495Sjmacd   area made from FORMAT and ARG. */
13921495Sjmacdvoid
140146520Srubegin_info_session_with_error (NODE *initial_node, char *format,
141146520Sru    void *arg1, void *arg2)
14221495Sjmacd{
14342664Smarkm  initialize_info_session (initial_node, 1);
14456164Sru  info_error (format, arg1, arg2);
14521495Sjmacd  info_session ();
14621495Sjmacd}
14721495Sjmacd
14821495Sjmacd/* Start an info session with INITIAL_NODE. */
14921495Sjmacdvoid
150146520Srubegin_info_session (NODE *initial_node)
15121495Sjmacd{
15242664Smarkm  initialize_info_session (initial_node, 1);
15321495Sjmacd  display_startup_message_and_start ();
15421495Sjmacd}
15521495Sjmacd
15621495Sjmacdvoid
157146520Srudisplay_startup_message_and_start (void)
15821495Sjmacd{
15921495Sjmacd  char *format;
16021495Sjmacd
16121495Sjmacd  format = replace_in_documentation
162146520Sru    ((char *) _("Welcome to Info version %s. Type \\[get-help-window] for help, \\[menu-item] for menu item."),
163146520Sru     0);
16421495Sjmacd
165146520Sru  window_message_in_echo_area (format, VERSION, NULL);
16621495Sjmacd  info_session ();
16721495Sjmacd}
16821495Sjmacd
16921495Sjmacd/* Run an info session with an already initialized window and node. */
17021495Sjmacdvoid
171146520Sruinfo_session (void)
17221495Sjmacd{
17321495Sjmacd  display_update_display (windows);
17442664Smarkm  info_last_executed_command = NULL;
17521495Sjmacd  info_read_and_dispatch ();
17621495Sjmacd  /* On program exit, leave the cursor at the bottom of the window, and
17721495Sjmacd     restore the terminal I/O. */
17821495Sjmacd  terminal_goto_xy (0, screenheight - 1);
17921495Sjmacd  terminal_clear_to_eol ();
18021495Sjmacd  fflush (stdout);
18121495Sjmacd  terminal_unprep_terminal ();
18221495Sjmacd  close_dribble_file ();
18321495Sjmacd}
18421495Sjmacd
18521495Sjmacd/* Here is a window-location dependent event loop.  Called from the
18621495Sjmacd   functions info_session (), and from read_xxx_in_echo_area (). */
18721495Sjmacdvoid
188146520Sruinfo_read_and_dispatch (void)
18921495Sjmacd{
19021495Sjmacd  unsigned char key;
19121495Sjmacd  int done;
19221495Sjmacd  done = 0;
19321495Sjmacd
19421495Sjmacd  while (!done && !quit_info_immediately)
19521495Sjmacd    {
196146520Sru      int lk = 0;
19721495Sjmacd
19821495Sjmacd      /* If we haven't just gone up or down a line, there is no
19942664Smarkm         goal column for this window. */
200146520Sru      if ((info_last_executed_command != (VFunction *) info_next_line) &&
201146520Sru          (info_last_executed_command != (VFunction *) info_prev_line))
20242664Smarkm        active_window->goal_column = -1;
20321495Sjmacd
20421495Sjmacd      if (echo_area_is_active)
20542664Smarkm        {
20642664Smarkm          lk = echo_area_last_command_was_kill;
20742664Smarkm          echo_area_prep_read ();
20842664Smarkm        }
20921495Sjmacd
21021495Sjmacd      if (!info_any_buffered_input_p ())
21142664Smarkm        display_update_display (windows);
21221495Sjmacd
21321495Sjmacd      display_cursor_at_point (active_window);
21421495Sjmacd      info_initialize_numeric_arg ();
21521495Sjmacd
21621495Sjmacd      initialize_keyseq ();
21721495Sjmacd      key = info_get_input_char ();
21821495Sjmacd
21921495Sjmacd      /* No errors yet.  We just read a character, that's all.  Only clear
22042664Smarkm         the echo_area if it is not currently active. */
22121495Sjmacd      if (!echo_area_is_active)
22242664Smarkm        window_clear_echo_area ();
22321495Sjmacd
22421495Sjmacd      info_error_was_printed = 0;
22521495Sjmacd
22621495Sjmacd      /* Do the selected command. */
22721495Sjmacd      info_dispatch_on_key (key, active_window->keymap);
22821495Sjmacd
22921495Sjmacd      if (echo_area_is_active)
23042664Smarkm        {
23142664Smarkm          /* Echo area commands that do killing increment the value of
23242664Smarkm             ECHO_AREA_LAST_COMMAND_WAS_KILL.  Thus, if there is no
23342664Smarkm             change in the value of this variable, the last command
23442664Smarkm             executed was not a kill command. */
23542664Smarkm          if (lk == echo_area_last_command_was_kill)
23642664Smarkm            echo_area_last_command_was_kill = 0;
23721495Sjmacd
238146520Sru          if (ea_last_executed_command == (VFunction *) ea_newline ||
23942664Smarkm              info_aborted_echo_area)
24042664Smarkm            {
24142664Smarkm              ea_last_executed_command = (VFunction *)NULL;
24242664Smarkm              done = 1;
24342664Smarkm            }
24421495Sjmacd
245146520Sru          if (info_last_executed_command == (VFunction *) info_quit)
24642664Smarkm            quit_info_immediately = 1;
24742664Smarkm        }
248146520Sru      else if (info_last_executed_command == (VFunction *) info_quit)
24942664Smarkm        done = 1;
25021495Sjmacd    }
25121495Sjmacd}
25221495Sjmacd
25321495Sjmacd/* Found in signals.c */
254146520Sruextern void initialize_info_signal_handler (void );
25521495Sjmacd
25621495Sjmacd/* Initialize the first info session by starting the terminal, window,
25742664Smarkm   and display systems.  If CLEAR_SCREEN is 0, don't clear the screen.  */
25821495Sjmacdvoid
259146520Sruinitialize_info_session (NODE *node, int clear_screen)
26021495Sjmacd{
26142664Smarkm  char *term_name = getenv ("TERM");
26221495Sjmacd  terminal_initialize_terminal (term_name);
26321495Sjmacd
26421495Sjmacd  if (terminal_is_dumb_p)
26521495Sjmacd    {
26621495Sjmacd      if (!term_name)
26742664Smarkm        term_name = "dumb";
26821495Sjmacd
269146520Sru      info_error ((char *) msg_term_too_dumb, term_name, NULL);
27056164Sru      xexit (1);
27121495Sjmacd    }
27221495Sjmacd
27342664Smarkm  if (clear_screen)
27442664Smarkm    {
27542664Smarkm      terminal_prep_terminal ();
27642664Smarkm      terminal_clear_screen ();
27742664Smarkm    }
27842664Smarkm
27921495Sjmacd  initialize_info_keymaps ();
28021495Sjmacd  window_initialize_windows (screenwidth, screenheight);
28121495Sjmacd  initialize_info_signal_handler ();
28221495Sjmacd  display_initialize_display (screenwidth, screenheight);
28356164Sru  info_set_node_of_window (0, active_window, node);
28421495Sjmacd
28521495Sjmacd  /* Tell the window system how to notify us when a window needs to be
28621495Sjmacd     asynchronously deleted (e.g., user resizes window very small). */
287146520Sru  window_deletion_notifier = (VFunction *) forget_window_and_nodes;
28821495Sjmacd
28942664Smarkm  /* If input has not been redirected yet, make it come from unbuffered
29042664Smarkm     standard input. */
29121495Sjmacd  if (!info_input_stream)
29242664Smarkm    {
29356164Sru      setbuf (stdin, NULL);
29442664Smarkm      info_input_stream = stdin;
29542664Smarkm    }
29621495Sjmacd
29721495Sjmacd  info_windows_initialized_p = 1;
29821495Sjmacd}
29921495Sjmacd
30021495Sjmacd/* Tell Info that input is coming from the file FILENAME. */
30121495Sjmacdvoid
302146520Sruinfo_set_input_from_file (char *filename)
30321495Sjmacd{
30421495Sjmacd  FILE *stream;
30521495Sjmacd
30656164Sru  /* Input may include binary characters.  */
30756164Sru  stream = fopen (filename, FOPEN_RBIN);
30821495Sjmacd
30921495Sjmacd  if (!stream)
31021495Sjmacd    return;
31121495Sjmacd
31221495Sjmacd  if ((info_input_stream != (FILE *)NULL) &&
31321495Sjmacd      (info_input_stream != stdin))
31421495Sjmacd    fclose (info_input_stream);
31521495Sjmacd
31621495Sjmacd  info_input_stream = stream;
31721495Sjmacd
31821495Sjmacd  if (stream != stdin)
31921495Sjmacd    display_inhibited = 1;
32021495Sjmacd}
32121495Sjmacd
32221495Sjmacd/* Return the INFO_WINDOW containing WINDOW, or NULL if there isn't one. */
32321495Sjmacdstatic INFO_WINDOW *
324146520Sruget_info_window_of_window (WINDOW *window)
32521495Sjmacd{
32621495Sjmacd  register int i;
32721495Sjmacd  INFO_WINDOW *info_win = (INFO_WINDOW *)NULL;
32821495Sjmacd
32921495Sjmacd  for (i = 0; info_windows && (info_win = info_windows[i]); i++)
33021495Sjmacd    if (info_win->window == window)
33121495Sjmacd      break;
33221495Sjmacd
33321495Sjmacd  return (info_win);
33421495Sjmacd}
33521495Sjmacd
33621495Sjmacd/* Reset the remembered pagetop and point of WINDOW to WINDOW's current
33721495Sjmacd   values if the window and node are the same as the current one being
33821495Sjmacd   displayed. */
33921495Sjmacdvoid
340146520Sruset_remembered_pagetop_and_point (WINDOW *window)
34121495Sjmacd{
34221495Sjmacd  INFO_WINDOW *info_win;
34321495Sjmacd
34421495Sjmacd  info_win = get_info_window_of_window (window);
34521495Sjmacd
34621495Sjmacd  if (!info_win)
34721495Sjmacd    return;
34821495Sjmacd
34921495Sjmacd  if (info_win->nodes_index &&
35021495Sjmacd      (info_win->nodes[info_win->current] == window->node))
35121495Sjmacd    {
35221495Sjmacd      info_win->pagetops[info_win->current] = window->pagetop;
35321495Sjmacd      info_win->points[info_win->current] = window->point;
35421495Sjmacd    }
35521495Sjmacd}
35621495Sjmacd
35721495Sjmacdvoid
358146520Sruremember_window_and_node (WINDOW *window, NODE *node)
35921495Sjmacd{
36021495Sjmacd  /* See if we already have this window in our list. */
36142664Smarkm  INFO_WINDOW *info_win = get_info_window_of_window (window);
36221495Sjmacd
36321495Sjmacd  /* If the window wasn't already on our list, then make a new entry. */
36421495Sjmacd  if (!info_win)
36521495Sjmacd    {
36621495Sjmacd      info_win = (INFO_WINDOW *)xmalloc (sizeof (INFO_WINDOW));
36721495Sjmacd      info_win->window = window;
36821495Sjmacd      info_win->nodes = (NODE **)NULL;
36921495Sjmacd      info_win->pagetops = (int *)NULL;
37021495Sjmacd      info_win->points = (long *)NULL;
37121495Sjmacd      info_win->current = 0;
37221495Sjmacd      info_win->nodes_index = 0;
37321495Sjmacd      info_win->nodes_slots = 0;
37421495Sjmacd
37521495Sjmacd      add_pointer_to_array (info_win, info_windows_index, info_windows,
37642664Smarkm                            info_windows_slots, 10, INFO_WINDOW *);
37721495Sjmacd    }
37821495Sjmacd
37921495Sjmacd  /* If this node, the current pagetop, and the current point are the
38042664Smarkm     same as the current saved node and pagetop, don't really add this to
38142664Smarkm     the list of history nodes.  This may happen only at the very
38242664Smarkm     beginning of the program, I'm not sure.  --karl  */
38342664Smarkm  if (info_win->nodes
38442664Smarkm      && info_win->current >= 0
38542664Smarkm      && info_win->nodes[info_win->current]->contents == node->contents
38642664Smarkm      && info_win->pagetops[info_win->current] == window->pagetop
38742664Smarkm      && info_win->points[info_win->current] == window->point)
38842664Smarkm  return;
38921495Sjmacd
39021495Sjmacd  /* Remember this node, the currently displayed pagetop, and the current
39121495Sjmacd     location of point in this window.  Because we are updating pagetops
39221495Sjmacd     and points as well as nodes, it is more efficient to avoid the
39321495Sjmacd     add_pointer_to_array macro here. */
39421495Sjmacd  if (info_win->nodes_index + 2 >= info_win->nodes_slots)
39521495Sjmacd    {
39642664Smarkm      info_win->nodes_slots += 20;
39742664Smarkm      info_win->nodes = (NODE **) xrealloc (info_win->nodes,
39842664Smarkm                                      info_win->nodes_slots * sizeof (NODE *));
39942664Smarkm      info_win->pagetops = (int *) xrealloc (info_win->pagetops,
40042664Smarkm                                      info_win->nodes_slots * sizeof (int));
40142664Smarkm      info_win->points = (long *) xrealloc (info_win->points,
40242664Smarkm                                      info_win->nodes_slots * sizeof (long));
40321495Sjmacd    }
40421495Sjmacd
40521495Sjmacd  info_win->nodes[info_win->nodes_index] = node;
40621495Sjmacd  info_win->pagetops[info_win->nodes_index] = window->pagetop;
40721495Sjmacd  info_win->points[info_win->nodes_index] = window->point;
40821495Sjmacd  info_win->current = info_win->nodes_index++;
40942664Smarkm  info_win->nodes[info_win->nodes_index] = NULL;
41021495Sjmacd  info_win->pagetops[info_win->nodes_index] = 0;
41121495Sjmacd  info_win->points[info_win->nodes_index] = 0;
41221495Sjmacd}
41321495Sjmacd
41421495Sjmacd#define DEBUG_FORGET_WINDOW_AND_NODES
41521495Sjmacd#if defined (DEBUG_FORGET_WINDOW_AND_NODES)
41621495Sjmacdstatic void
417146520Sruconsistency_check_info_windows (void)
41821495Sjmacd{
41921495Sjmacd  register int i;
42021495Sjmacd
42121495Sjmacd  for (i = 0; i < info_windows_index; i++)
42221495Sjmacd    {
42321495Sjmacd      WINDOW *win;
42421495Sjmacd
42521495Sjmacd      for (win = windows; win; win = win->next)
42642664Smarkm        if (win == info_windows[i]->window)
42742664Smarkm          break;
42821495Sjmacd
42921495Sjmacd      if (!win)
43042664Smarkm        abort ();
43121495Sjmacd    }
43221495Sjmacd}
43321495Sjmacd#endif /* DEBUG_FORGET_WINDOW_AND_NODES */
43421495Sjmacd
43521495Sjmacd/* Remove WINDOW and its associated list of nodes from INFO_WINDOWS. */
43621495Sjmacdvoid
437146520Sruforget_window_and_nodes (WINDOW *window)
43821495Sjmacd{
43921495Sjmacd  register int i;
44021495Sjmacd  INFO_WINDOW *info_win = (INFO_WINDOW *)NULL;
44121495Sjmacd
44221495Sjmacd  for (i = 0; info_windows && (info_win = info_windows[i]); i++)
44321495Sjmacd    if (info_win->window == window)
44421495Sjmacd      break;
44521495Sjmacd
44621495Sjmacd  /* If we found the window to forget, then do so. */
44721495Sjmacd  if (info_win)
44821495Sjmacd    {
44921495Sjmacd      while (i < info_windows_index)
45042664Smarkm        {
45142664Smarkm          info_windows[i] = info_windows[i + 1];
45242664Smarkm          i++;
45342664Smarkm        }
45421495Sjmacd
45521495Sjmacd      info_windows_index--;
45621495Sjmacd      info_windows[info_windows_index] = (INFO_WINDOW *)NULL;
45721495Sjmacd
45821495Sjmacd      if (info_win->nodes)
45942664Smarkm        {
46042664Smarkm          /* Free the node structures which held onto internal node contents
46142664Smarkm             here.  This doesn't free the contents; we have a garbage collector
46242664Smarkm             which does that. */
46342664Smarkm          for (i = 0; info_win->nodes[i]; i++)
46442664Smarkm            if (internal_info_node_p (info_win->nodes[i]))
46542664Smarkm              free (info_win->nodes[i]);
46642664Smarkm          free (info_win->nodes);
46721495Sjmacd
46842664Smarkm          maybe_free (info_win->pagetops);
46942664Smarkm          maybe_free (info_win->points);
47042664Smarkm        }
47121495Sjmacd
47221495Sjmacd      free (info_win);
47321495Sjmacd    }
47421495Sjmacd#if defined (DEBUG_FORGET_WINDOW_AND_NODES)
47521495Sjmacd  consistency_check_info_windows ();
47621495Sjmacd#endif /* DEBUG_FORGET_WINDOW_AND_NODES */
47721495Sjmacd}
47821495Sjmacd
47921495Sjmacd/* Set WINDOW to show NODE.  Remember the new window in our list of Info
48021495Sjmacd   windows.  If we are doing automatic footnote display, also try to display
48156164Sru   the footnotes for this window.  If REMEMBER is nonzero, first call
48256164Sru   set_remembered_pagetop_and_point.  */
48321495Sjmacdvoid
484146520Sruinfo_set_node_of_window (int remember, WINDOW *window, NODE *node)
48521495Sjmacd{
48656164Sru  if (remember)
48756164Sru    set_remembered_pagetop_and_point (window);
48856164Sru
48921495Sjmacd  /* Put this node into the window. */
49021495Sjmacd  window_set_node_of_window (window, node);
49121495Sjmacd
49221495Sjmacd  /* Remember this node and window in our list of info windows. */
49321495Sjmacd  remember_window_and_node (window, node);
49421495Sjmacd
49521495Sjmacd  /* If doing auto-footnote display/undisplay, show the footnotes belonging
49621495Sjmacd     to this window's node. */
49721495Sjmacd  if (auto_footnotes_p)
49821495Sjmacd    info_get_or_remove_footnotes (window);
49921495Sjmacd}
50021495Sjmacd
50121495Sjmacd
50221495Sjmacd/* **************************************************************** */
50342664Smarkm/*                                                                  */
50442664Smarkm/*                     Info Movement Commands                       */
50542664Smarkm/*                                                                  */
50621495Sjmacd/* **************************************************************** */
50721495Sjmacd
50821495Sjmacd/* Change the pagetop of WINDOW to DESIRED_TOP, perhaps scrolling the screen
50921495Sjmacd   to do so. */
51021495Sjmacdvoid
511146520Sruset_window_pagetop (WINDOW *window, int desired_top)
51221495Sjmacd{
51321495Sjmacd  int point_line, old_pagetop;
51421495Sjmacd
51521495Sjmacd  if (desired_top < 0)
51621495Sjmacd    desired_top = 0;
51721495Sjmacd  else if (desired_top > window->line_count)
51821495Sjmacd    desired_top = window->line_count - 1;
51921495Sjmacd
52021495Sjmacd  if (window->pagetop == desired_top)
52121495Sjmacd    return;
52221495Sjmacd
52321495Sjmacd  old_pagetop = window->pagetop;
52421495Sjmacd  window->pagetop = desired_top;
52521495Sjmacd
52621495Sjmacd  /* Make sure that point appears in this window. */
52721495Sjmacd  point_line = window_line_of_point (window);
52821495Sjmacd  if ((point_line < window->pagetop) ||
52921495Sjmacd      ((point_line - window->pagetop) > window->height - 1))
53021495Sjmacd    window->point =
53121495Sjmacd      window->line_starts[window->pagetop] - window->node->contents;
53221495Sjmacd
53321495Sjmacd  window->flags |= W_UpdateWindow;
53421495Sjmacd
53521495Sjmacd  /* Find out which direction to scroll, and scroll the window in that
53621495Sjmacd     direction.  Do this only if there would be a savings in redisplay
53721495Sjmacd     time.  This is true if the amount to scroll is less than the height
53821495Sjmacd     of the window, and if the number of lines scrolled would be greater
53921495Sjmacd     than 10 % of the window's height. */
54021495Sjmacd  if (old_pagetop < desired_top)
54121495Sjmacd    {
54221495Sjmacd      int start, end, amount;
54321495Sjmacd
54421495Sjmacd      amount = desired_top - old_pagetop;
54521495Sjmacd
54621495Sjmacd      if ((amount >= window->height) ||
54742664Smarkm          (((window->height - amount) * 10) < window->height))
54842664Smarkm        return;
54921495Sjmacd
55021495Sjmacd      start = amount + window->first_row;
55121495Sjmacd      end = window->height + window->first_row;
55221495Sjmacd
55321495Sjmacd      display_scroll_display (start, end, -amount);
55421495Sjmacd    }
55521495Sjmacd  else
55621495Sjmacd    {
55721495Sjmacd      int start, end, amount;
55821495Sjmacd
55921495Sjmacd      amount = old_pagetop - desired_top;
56021495Sjmacd
56121495Sjmacd      if ((amount >= window->height) ||
56242664Smarkm          (((window->height - amount) * 10) < window->height))
56342664Smarkm        return;
56421495Sjmacd
56521495Sjmacd      start = window->first_row;
56621495Sjmacd      end = (window->first_row + window->height) - amount;
56721495Sjmacd      display_scroll_display (start, end, amount);
56821495Sjmacd    }
56921495Sjmacd}
57021495Sjmacd
57121495Sjmacd/* Immediately make WINDOW->point visible on the screen, and move the
57221495Sjmacd   terminal cursor there. */
57321495Sjmacdstatic void
574146520Sruinfo_show_point (WINDOW *window)
57521495Sjmacd{
57621495Sjmacd  int old_pagetop;
57721495Sjmacd
57821495Sjmacd  old_pagetop = window->pagetop;
57921495Sjmacd  window_adjust_pagetop (window);
58021495Sjmacd  if (old_pagetop != window->pagetop)
58121495Sjmacd    {
58221495Sjmacd      int new_pagetop;
58321495Sjmacd
58421495Sjmacd      new_pagetop = window->pagetop;
58521495Sjmacd      window->pagetop = old_pagetop;
58621495Sjmacd      set_window_pagetop (window, new_pagetop);
58721495Sjmacd    }
58821495Sjmacd
58921495Sjmacd  if (window->flags & W_UpdateWindow)
59021495Sjmacd    display_update_one_window (window);
59121495Sjmacd
59221495Sjmacd  display_cursor_at_point (window);
59321495Sjmacd}
59421495Sjmacd
59521495Sjmacd/* Move WINDOW->point from OLD line index to NEW line index. */
59621495Sjmacdstatic void
597146520Srumove_to_new_line (int old, int new, WINDOW *window)
59821495Sjmacd{
59921495Sjmacd  if (old == -1)
60021495Sjmacd    {
601146520Sru      info_error ((char *) msg_cant_find_point, NULL, NULL);
60221495Sjmacd    }
60321495Sjmacd  else
60421495Sjmacd    {
60521495Sjmacd      int goal;
60621495Sjmacd
60721495Sjmacd      if (new >= window->line_count || new < 0)
60842664Smarkm        return;
60921495Sjmacd
61021495Sjmacd      goal = window_get_goal_column (window);
61121495Sjmacd      window->goal_column = goal;
61221495Sjmacd
61321495Sjmacd      window->point = window->line_starts[new] - window->node->contents;
61421495Sjmacd      window->point += window_chars_to_goal (window->line_starts[new], goal);
61521495Sjmacd      info_show_point (window);
61621495Sjmacd    }
61721495Sjmacd}
61821495Sjmacd
61921495Sjmacd/* Move WINDOW's point down to the next line if possible. */
62042664SmarkmDECLARE_INFO_COMMAND (info_next_line, _("Move down to the next line"))
62121495Sjmacd{
62221495Sjmacd  int old_line, new_line;
62321495Sjmacd
62421495Sjmacd  if (count < 0)
62521495Sjmacd    info_prev_line (window, -count, key);
62621495Sjmacd  else
62721495Sjmacd    {
62821495Sjmacd      old_line = window_line_of_point (window);
62921495Sjmacd      new_line = old_line + count;
63021495Sjmacd      move_to_new_line (old_line, new_line, window);
63121495Sjmacd    }
63221495Sjmacd}
63321495Sjmacd
63421495Sjmacd/* Move WINDOW's point up to the previous line if possible. */
63542664SmarkmDECLARE_INFO_COMMAND (info_prev_line, _("Move up to the previous line"))
63621495Sjmacd{
63721495Sjmacd  int old_line, new_line;
63821495Sjmacd
63921495Sjmacd  if (count < 0)
64021495Sjmacd    info_next_line (window, -count, key);
64121495Sjmacd  else
64221495Sjmacd    {
64321495Sjmacd      old_line = window_line_of_point (window);
64421495Sjmacd      new_line = old_line - count;
64521495Sjmacd      move_to_new_line (old_line, new_line, window);
64621495Sjmacd    }
64721495Sjmacd}
64821495Sjmacd
64921495Sjmacd/* Move WINDOW's point to the end of the true line. */
65042664SmarkmDECLARE_INFO_COMMAND (info_end_of_line, _("Move to the end of the line"))
65121495Sjmacd{
65221495Sjmacd  register int point, len;
65321495Sjmacd  register char *buffer;
65421495Sjmacd
65521495Sjmacd  buffer = window->node->contents;
65621495Sjmacd  len = window->node->nodelen;
65721495Sjmacd
65821495Sjmacd  for (point = window->point;
65921495Sjmacd       (point < len) && (buffer[point] != '\n');
66021495Sjmacd       point++);
66121495Sjmacd
66221495Sjmacd  if (point != window->point)
66321495Sjmacd    {
66421495Sjmacd      window->point = point;
66521495Sjmacd      info_show_point (window);
66621495Sjmacd    }
66721495Sjmacd}
66821495Sjmacd
66921495Sjmacd/* Move WINDOW's point to the beginning of the true line. */
67042664SmarkmDECLARE_INFO_COMMAND (info_beginning_of_line, _("Move to the start of the line"))
67121495Sjmacd{
67221495Sjmacd  register int point;
67321495Sjmacd  register char *buffer;
67421495Sjmacd
67521495Sjmacd  buffer = window->node->contents;
67621495Sjmacd  point = window->point;
67721495Sjmacd
67821495Sjmacd  for (; (point) && (buffer[point - 1] != '\n'); point--);
67921495Sjmacd
68056164Sru  /* If at a line start already, do nothing. */
68121495Sjmacd  if (point != window->point)
68221495Sjmacd    {
68321495Sjmacd      window->point = point;
68421495Sjmacd      info_show_point (window);
68521495Sjmacd    }
68621495Sjmacd}
68721495Sjmacd
68821495Sjmacd/* Move point forward in the node. */
68942664SmarkmDECLARE_INFO_COMMAND (info_forward_char, _("Move forward a character"))
69021495Sjmacd{
69121495Sjmacd  if (count < 0)
69221495Sjmacd    info_backward_char (window, -count, key);
69321495Sjmacd  else
69421495Sjmacd    {
69521495Sjmacd      window->point += count;
69621495Sjmacd
69721495Sjmacd      if (window->point >= window->node->nodelen)
69842664Smarkm        window->point = window->node->nodelen - 1;
69921495Sjmacd
70021495Sjmacd      info_show_point (window);
70121495Sjmacd    }
70221495Sjmacd}
70321495Sjmacd
70421495Sjmacd/* Move point backward in the node. */
70542664SmarkmDECLARE_INFO_COMMAND (info_backward_char, _("Move backward a character"))
70621495Sjmacd{
70721495Sjmacd  if (count < 0)
70821495Sjmacd    info_forward_char (window, -count, key);
70921495Sjmacd  else
71021495Sjmacd    {
71121495Sjmacd      window->point -= count;
71221495Sjmacd
71321495Sjmacd      if (window->point < 0)
71442664Smarkm        window->point = 0;
71521495Sjmacd
71621495Sjmacd      info_show_point (window);
71721495Sjmacd    }
71821495Sjmacd}
71921495Sjmacd
72021495Sjmacd#define alphabetic(c) (islower (c) || isupper (c) || isdigit (c))
72121495Sjmacd
72221495Sjmacd/* Move forward a word in this node. */
72342664SmarkmDECLARE_INFO_COMMAND (info_forward_word, _("Move forward a word"))
72421495Sjmacd{
72521495Sjmacd  long point;
72621495Sjmacd  char *buffer;
72721495Sjmacd  int end, c;
72821495Sjmacd
72921495Sjmacd  if (count < 0)
73021495Sjmacd    {
73121495Sjmacd      info_backward_word (window, -count, key);
73221495Sjmacd      return;
73321495Sjmacd    }
73421495Sjmacd
73521495Sjmacd  point = window->point;
73621495Sjmacd  buffer = window->node->contents;
73721495Sjmacd  end = window->node->nodelen;
73821495Sjmacd
73921495Sjmacd  while (count)
74021495Sjmacd    {
74121495Sjmacd      if (point + 1 >= end)
74242664Smarkm        return;
74321495Sjmacd
74421495Sjmacd      /* If we are not in a word, move forward until we are in one.
74542664Smarkm         Then, move forward until we hit a non-alphabetic character. */
74621495Sjmacd      c = buffer[point];
74721495Sjmacd
74821495Sjmacd      if (!alphabetic (c))
74942664Smarkm        {
75042664Smarkm          while (++point < end)
75142664Smarkm            {
75242664Smarkm              c = buffer[point];
75342664Smarkm              if (alphabetic (c))
75442664Smarkm                break;
75542664Smarkm            }
75642664Smarkm        }
75721495Sjmacd
75821495Sjmacd      if (point >= end) return;
75921495Sjmacd
76021495Sjmacd      while (++point < end)
76142664Smarkm        {
76242664Smarkm          c = buffer[point];
76342664Smarkm          if (!alphabetic (c))
76442664Smarkm            break;
76542664Smarkm        }
76621495Sjmacd      --count;
76721495Sjmacd    }
76821495Sjmacd  window->point = point;
76921495Sjmacd  info_show_point (window);
77021495Sjmacd}
77121495Sjmacd
77242664SmarkmDECLARE_INFO_COMMAND (info_backward_word, _("Move backward a word"))
77321495Sjmacd{
77421495Sjmacd  long point;
77521495Sjmacd  char *buffer;
77621495Sjmacd  int c;
77721495Sjmacd
77821495Sjmacd  if (count < 0)
77921495Sjmacd    {
78021495Sjmacd      info_forward_word (window, -count, key);
78121495Sjmacd      return;
78221495Sjmacd    }
78321495Sjmacd
78421495Sjmacd  buffer = window->node->contents;
78521495Sjmacd  point = window->point;
78621495Sjmacd
78721495Sjmacd  while (count)
78821495Sjmacd    {
78921495Sjmacd      if (point == 0)
79042664Smarkm        break;
79121495Sjmacd
79221495Sjmacd      /* Like info_forward_word (), except that we look at the
79342664Smarkm         characters just before point. */
79421495Sjmacd
79521495Sjmacd      c = buffer[point - 1];
79621495Sjmacd
79721495Sjmacd      if (!alphabetic (c))
79842664Smarkm        {
79942664Smarkm          while (--point)
80042664Smarkm            {
80142664Smarkm              c = buffer[point - 1];
80242664Smarkm              if (alphabetic (c))
80342664Smarkm                break;
80442664Smarkm            }
80542664Smarkm        }
80621495Sjmacd
80721495Sjmacd      while (point)
80842664Smarkm        {
80942664Smarkm          c = buffer[point - 1];
81042664Smarkm          if (!alphabetic (c))
81142664Smarkm            break;
81242664Smarkm          else
81342664Smarkm            --point;
81442664Smarkm        }
81521495Sjmacd      --count;
81621495Sjmacd    }
81721495Sjmacd  window->point = point;
81821495Sjmacd  info_show_point (window);
81921495Sjmacd}
82021495Sjmacd
82121495Sjmacd/* Variable controlling the behaviour of default scrolling when you are
82221495Sjmacd   already at the bottom of a node.  Possible values are defined in session.h.
82321495Sjmacd   The meanings are:
82421495Sjmacd
82542664Smarkm   IS_Continuous        Try to get first menu item, or failing that, the
82642664Smarkm                        "Next:" pointer, or failing that, the "Up:" and
82742664Smarkm                        "Next:" of the up.
82842664Smarkm   IS_NextOnly          Try to get "Next:" menu item.
82942664Smarkm   IS_PageOnly          Simply give up at the bottom of a node. */
83021495Sjmacd
83121495Sjmacdint info_scroll_behaviour = IS_Continuous;
83221495Sjmacd
83321495Sjmacd/* Choices used by the completer when reading a value for the user-visible
83421495Sjmacd   variable "scroll-behaviour". */
83521495Sjmacdchar *info_scroll_choices[] = {
83621495Sjmacd  "Continuous", "Next Only", "Page Only", (char *)NULL
83721495Sjmacd};
83821495Sjmacd
83956164Sru/* Default window sizes for scrolling commands.  */
84056164Sruint default_window_size = -1;	/* meaning 1 window-full */
84156164Sruint default_scroll_size = -1;	/* meaning half screen size */
84256164Sru
84356164Sru#define INFO_LABEL_FOUND() \
84456164Sru  (info_parsed_nodename || (info_parsed_filename \
84556164Sru                            && !is_dir_name (info_parsed_filename)))
84656164Sru
84721495Sjmacd/* Move to 1st menu item, Next, Up/Next, or error in this window. */
84821495Sjmacdstatic void
849146520Sruforward_move_node_structure (WINDOW *window, int behaviour)
85021495Sjmacd{
85121495Sjmacd  switch (behaviour)
85221495Sjmacd    {
85321495Sjmacd    case IS_PageOnly:
854146520Sru      info_error ((char *) msg_at_node_bottom, NULL, NULL);
85521495Sjmacd      break;
85621495Sjmacd
85721495Sjmacd    case IS_NextOnly:
85821495Sjmacd      info_next_label_of_node (window->node);
85921495Sjmacd      if (!info_parsed_nodename && !info_parsed_filename)
860146520Sru        info_error ((char *) msg_no_pointer, (char *) _("Next"), NULL);
86121495Sjmacd      else
86242664Smarkm        {
863146520Sru          window_message_in_echo_area ((char *) _("Following Next node..."),
864146520Sru              NULL, NULL);
86556164Sru          info_handle_pointer ("Next", window);
86642664Smarkm        }
86721495Sjmacd      break;
86821495Sjmacd
86921495Sjmacd    case IS_Continuous:
87021495Sjmacd      {
87142664Smarkm        /* First things first.  If this node contains a menu, move down
87242664Smarkm           into the menu. */
87342664Smarkm        {
87442664Smarkm          REFERENCE **menu;
87521495Sjmacd
87642664Smarkm          menu = info_menu_of_node (window->node);
87721495Sjmacd
87842664Smarkm          if (menu)
87942664Smarkm            {
88042664Smarkm              info_free_references (menu);
881146520Sru              window_message_in_echo_area ((char *) _("Selecting first menu item..."),
882146520Sru                  NULL, NULL);
88342664Smarkm              info_menu_digit (window, 1, '1');
88442664Smarkm              return;
88542664Smarkm            }
88642664Smarkm        }
88721495Sjmacd
88842664Smarkm        /* Okay, this node does not contain a menu.  If it contains a
88942664Smarkm           "Next:" pointer, use that. */
89042664Smarkm        info_next_label_of_node (window->node);
89156164Sru        if (INFO_LABEL_FOUND ())
89242664Smarkm          {
893146520Sru            window_message_in_echo_area ((char *) _("Selecting Next node..."),
894146520Sru                NULL, NULL);
89556164Sru            info_handle_pointer ("Next", window);
89642664Smarkm            return;
89742664Smarkm          }
89821495Sjmacd
89942664Smarkm        /* Okay, there wasn't a "Next:" for this node.  Move "Up:" until we
90042664Smarkm           can move "Next:".  If that isn't possible, complain that there
90142664Smarkm           are no more nodes. */
90242664Smarkm        {
90342664Smarkm          int up_counter, old_current;
90442664Smarkm          INFO_WINDOW *info_win;
90521495Sjmacd
90642664Smarkm          /* Remember the current node and location. */
90742664Smarkm          info_win = get_info_window_of_window (window);
90842664Smarkm          old_current = info_win->current;
90921495Sjmacd
91042664Smarkm          /* Back up through the "Up:" pointers until we have found a "Next:"
91142664Smarkm             that isn't the same as the first menu item found in that node. */
91242664Smarkm          up_counter = 0;
91342664Smarkm          while (!info_error_was_printed)
91442664Smarkm            {
91542664Smarkm              info_up_label_of_node (window->node);
91656164Sru              if (INFO_LABEL_FOUND ())
91742664Smarkm                {
91856164Sru                  info_handle_pointer ("Up", window);
91942664Smarkm                  if (info_error_was_printed)
92042664Smarkm                    continue;
92121495Sjmacd
92242664Smarkm                  up_counter++;
92321495Sjmacd
92442664Smarkm                  info_next_label_of_node (window->node);
92521495Sjmacd
92642664Smarkm                  /* If no "Next" pointer, keep backing up. */
92756164Sru                  if (!INFO_LABEL_FOUND ())
92842664Smarkm                    continue;
92921495Sjmacd
93042664Smarkm                  /* If this node's first menu item is the same as this node's
93142664Smarkm                     Next pointer, keep backing up. */
93242664Smarkm                  if (!info_parsed_filename)
93342664Smarkm                    {
93442664Smarkm                      REFERENCE **menu;
93542664Smarkm                      char *next_nodename;
93621495Sjmacd
93742664Smarkm                      /* Remember the name of the Next node, since reading
93842664Smarkm                         the menu can overwrite the contents of the
93942664Smarkm                         info_parsed_xxx strings. */
94042664Smarkm                      next_nodename = xstrdup (info_parsed_nodename);
94121495Sjmacd
94242664Smarkm                      menu = info_menu_of_node (window->node);
94342664Smarkm                      if (menu &&
94442664Smarkm                          (strcmp
94542664Smarkm                           (menu[0]->nodename, next_nodename) == 0))
94642664Smarkm                        {
94742664Smarkm                          info_free_references (menu);
94842664Smarkm                          free (next_nodename);
94942664Smarkm                          continue;
95042664Smarkm                        }
95142664Smarkm                      else
95242664Smarkm                        {
95342664Smarkm                          /* Restore the world to where it was before
95442664Smarkm                             reading the menu contents. */
95542664Smarkm                          info_free_references (menu);
95642664Smarkm                          free (next_nodename);
95742664Smarkm                          info_next_label_of_node (window->node);
95842664Smarkm                        }
95942664Smarkm                    }
96021495Sjmacd
96142664Smarkm                  /* This node has a "Next" pointer, and it is not the
96242664Smarkm                     same as the first menu item found in this node. */
96342664Smarkm                  window_message_in_echo_area
964146520Sru                    ((char *) _("Moving Up %d time(s), then Next."),
965146520Sru                     (void *) (long) up_counter, NULL);
96621495Sjmacd
96756164Sru                  info_handle_pointer ("Next", window);
96842664Smarkm                  return;
96942664Smarkm                }
97042664Smarkm              else
97142664Smarkm                {
97242664Smarkm                  /* No more "Up" pointers.  Print an error, and call it
97342664Smarkm                     quits. */
97442664Smarkm                  register int i;
97521495Sjmacd
97642664Smarkm                  for (i = 0; i < up_counter; i++)
97742664Smarkm                    {
97842664Smarkm                      info_win->nodes_index--;
97942664Smarkm                      free (info_win->nodes[info_win->nodes_index]);
98042664Smarkm                      info_win->nodes[info_win->nodes_index] = (NODE *)NULL;
98142664Smarkm                    }
98242664Smarkm                  info_win->current = old_current;
98342664Smarkm                  window->node = info_win->nodes[old_current];
98442664Smarkm                  window->pagetop = info_win->pagetops[old_current];
98542664Smarkm                  window->point = info_win->points[old_current];
98642664Smarkm                  recalculate_line_starts (window);
98742664Smarkm                  window->flags |= W_UpdateWindow;
988146520Sru                  info_error ((char *) _("No more nodes within this document."),
989146520Sru                      NULL, NULL);
99042664Smarkm                }
99142664Smarkm            }
99242664Smarkm        }
99342664Smarkm        break;
99421495Sjmacd      }
99521495Sjmacd    }
99621495Sjmacd}
99721495Sjmacd
99821495Sjmacd/* Move Prev, Up or error in WINDOW depending on BEHAVIOUR. */
99921495Sjmacdstatic void
1000146520Srubackward_move_node_structure (WINDOW *window, int behaviour)
100121495Sjmacd{
100221495Sjmacd  switch (behaviour)
100321495Sjmacd    {
100421495Sjmacd    case IS_PageOnly:
1005146520Sru      info_error ((char *) msg_at_node_top, NULL, NULL);
100621495Sjmacd      break;
100721495Sjmacd
100821495Sjmacd    case IS_NextOnly:
100921495Sjmacd      info_prev_label_of_node (window->node);
101021495Sjmacd      if (!info_parsed_nodename && !info_parsed_filename)
1011146520Sru        info_error ((char *) _("No `Prev' for this node."), NULL, NULL);
101221495Sjmacd      else
101342664Smarkm        {
1014146520Sru          window_message_in_echo_area ((char *) _("Moving Prev in this window."),
1015146520Sru              NULL, NULL);
101656164Sru          info_handle_pointer ("Prev", window);
101742664Smarkm        }
101821495Sjmacd      break;
101921495Sjmacd
102021495Sjmacd    case IS_Continuous:
102121495Sjmacd      info_prev_label_of_node (window->node);
102221495Sjmacd
102356164Sru      if (!info_parsed_nodename && (!info_parsed_filename
102456164Sru                                    || is_dir_name (info_parsed_filename)))
102542664Smarkm        {
102642664Smarkm          info_up_label_of_node (window->node);
102756164Sru          if (!info_parsed_nodename && (!info_parsed_filename
102856164Sru                                        || is_dir_name (info_parsed_filename)))
1029146520Sru            info_error ((char *)
1030146520Sru                _("No `Prev' or `Up' for this node within this document."),
1031146520Sru                NULL, NULL);
103242664Smarkm          else
103342664Smarkm            {
1034146520Sru              window_message_in_echo_area ((char *) _("Moving Up in this window."),
1035146520Sru                  NULL, NULL);
103656164Sru              info_handle_pointer ("Up", window);
103742664Smarkm            }
103842664Smarkm        }
103921495Sjmacd      else
104042664Smarkm        {
104142664Smarkm          REFERENCE **menu;
104242664Smarkm          int inhibit_menu_traversing = 0;
104321495Sjmacd
104442664Smarkm          /* Watch out!  If this node's Prev is the same as the Up, then
104542664Smarkm             move Up.  Otherwise, we could move Prev, and then to the last
104642664Smarkm             menu item in the Prev.  This would cause the user to loop
104742664Smarkm             through a subsection of the info file. */
104842664Smarkm          if (!info_parsed_filename && info_parsed_nodename)
104942664Smarkm            {
105042664Smarkm              char *pnode;
105121495Sjmacd
105242664Smarkm              pnode = xstrdup (info_parsed_nodename);
105342664Smarkm              info_up_label_of_node (window->node);
105421495Sjmacd
105542664Smarkm              if (!info_parsed_filename && info_parsed_nodename &&
105642664Smarkm                  strcmp (info_parsed_nodename, pnode) == 0)
105742664Smarkm                {
105842664Smarkm                  /* The nodes are the same.  Inhibit moving to the last
105942664Smarkm                     menu item. */
106042664Smarkm                  free (pnode);
106142664Smarkm                  inhibit_menu_traversing = 1;
106242664Smarkm                }
106342664Smarkm              else
106442664Smarkm                {
106542664Smarkm                  free (pnode);
106642664Smarkm                  info_prev_label_of_node (window->node);
106742664Smarkm                }
106842664Smarkm            }
106921495Sjmacd
107042664Smarkm          /* Move to the previous node.  If this node now contains a menu,
107142664Smarkm             and we have not inhibited movement to it, move to the node
107242664Smarkm             corresponding to the last menu item. */
1073146520Sru          window_message_in_echo_area ((char *) _("Moving Prev in this window."),
1074146520Sru              NULL, NULL);
107556164Sru          info_handle_pointer ("Prev", window);
107621495Sjmacd
107742664Smarkm          if (!inhibit_menu_traversing)
107842664Smarkm            {
107942664Smarkm              while (!info_error_was_printed &&
108042664Smarkm                     (menu = info_menu_of_node (window->node)))
108142664Smarkm                {
108242664Smarkm                  info_free_references (menu);
108342664Smarkm                  window_message_in_echo_area
1084146520Sru                    ((char *) _("Moving to `Prev's last menu item."), NULL, NULL);
108542664Smarkm                  info_menu_digit (window, 1, '0');
108642664Smarkm                }
108742664Smarkm            }
108842664Smarkm        }
108921495Sjmacd      break;
109021495Sjmacd    }
109121495Sjmacd}
109221495Sjmacd
109321495Sjmacd/* Move continuously forward through the node structure of this info file. */
109421495SjmacdDECLARE_INFO_COMMAND (info_global_next_node,
109542664Smarkm                      _("Move forwards or down through node structure"))
109621495Sjmacd{
109721495Sjmacd  if (count < 0)
109821495Sjmacd    info_global_prev_node (window, -count, key);
109921495Sjmacd  else
110021495Sjmacd    {
110121495Sjmacd      while (count && !info_error_was_printed)
110242664Smarkm        {
110342664Smarkm          forward_move_node_structure (window, IS_Continuous);
110442664Smarkm          count--;
110542664Smarkm        }
110621495Sjmacd    }
110721495Sjmacd}
110821495Sjmacd
110921495Sjmacd/* Move continuously backward through the node structure of this info file. */
111021495SjmacdDECLARE_INFO_COMMAND (info_global_prev_node,
111142664Smarkm                      _("Move backwards or up through node structure"))
111221495Sjmacd{
111321495Sjmacd  if (count < 0)
111421495Sjmacd    info_global_next_node (window, -count, key);
111521495Sjmacd  else
111621495Sjmacd    {
111721495Sjmacd      while (count && !info_error_was_printed)
111842664Smarkm        {
111942664Smarkm          backward_move_node_structure (window, IS_Continuous);
112042664Smarkm          count--;
112142664Smarkm        }
112221495Sjmacd    }
112321495Sjmacd}
112421495Sjmacd
1125146520Srustatic void _scroll_forward(WINDOW *window, int count,
1126146520Sru    unsigned char key, int behaviour);
1127146520Srustatic void _scroll_backward(WINDOW *window, int count,
1128146520Sru    unsigned char key, int behaviour);
112993142Sru
113093142Srustatic void
1131146520Sru_scroll_forward(WINDOW *window, int count, unsigned char key, int behaviour)
113221495Sjmacd{
113321495Sjmacd  if (count < 0)
113493142Sru    _scroll_backward (window, -count, key, behaviour);
113521495Sjmacd  else
113621495Sjmacd    {
113721495Sjmacd      int desired_top;
113821495Sjmacd
113921495Sjmacd      /* Without an explicit numeric argument, scroll the bottom two
114042664Smarkm         lines to the top of this window,  Or, if at bottom of window,
114193142Sru         and the chosen behaviour is to scroll through nodes get the
114293142Sru	 "Next" node for this window. */
114356164Sru      if (default_window_size > 0)
114456164Sru        desired_top = window->pagetop + default_window_size;
114556164Sru      else if (!info_explicit_arg && count == 1)
114642664Smarkm        {
114742664Smarkm          desired_top = window->pagetop + (window->height - 2);
114821495Sjmacd
114942664Smarkm          /* If there are no more lines to scroll here, error, or get
115093142Sru             another node, depending on BEHAVIOUR. */
115142664Smarkm          if (desired_top > window->line_count)
115242664Smarkm            {
115342664Smarkm              forward_move_node_structure (window, behaviour);
115442664Smarkm              return;
115542664Smarkm            }
115642664Smarkm        }
115721495Sjmacd      else
115842664Smarkm        desired_top = window->pagetop + count;
115921495Sjmacd
116021495Sjmacd      if (desired_top >= window->line_count)
116142664Smarkm        desired_top = window->line_count - 2;
116221495Sjmacd
116321495Sjmacd      if (window->pagetop > desired_top)
116442664Smarkm        return;
116521495Sjmacd      else
116642664Smarkm        set_window_pagetop (window, desired_top);
116721495Sjmacd    }
116821495Sjmacd}
116921495Sjmacd
117093142Srustatic void
1171146520Sru_scroll_backward(WINDOW *window, int count, unsigned char key, int behaviour)
117256164Sru{
117321495Sjmacd  if (count < 0)
117493142Sru    _scroll_forward (window, -count, key, behaviour);
117521495Sjmacd  else
117621495Sjmacd    {
117721495Sjmacd      int desired_top;
117821495Sjmacd
117921495Sjmacd      /* Without an explicit numeric argument, scroll the top two lines
118093142Sru         to the bottom of this window, or, depending on the selected
118193142Sru	 behaviour, move to the previous, or Up'th node. */
118256164Sru      if (default_window_size > 0)
118356164Sru        desired_top = window->pagetop - default_window_size;
118456164Sru      else if (!info_explicit_arg && count == 1)
118542664Smarkm        {
118642664Smarkm          desired_top = window->pagetop - (window->height - 2);
118721495Sjmacd
118842664Smarkm          if ((desired_top < 0) && (window->pagetop == 0))
118942664Smarkm            {
119042664Smarkm              backward_move_node_structure (window, behaviour);
119142664Smarkm              return;
119242664Smarkm            }
119342664Smarkm        }
119421495Sjmacd      else
119542664Smarkm        desired_top = window->pagetop - count;
119621495Sjmacd
119721495Sjmacd      if (desired_top < 0)
119842664Smarkm        desired_top = 0;
119921495Sjmacd
120021495Sjmacd      set_window_pagetop (window, desired_top);
120121495Sjmacd    }
120221495Sjmacd}
120321495Sjmacd
120493142Sru/* Show the next screen of WINDOW's node. */
120593142SruDECLARE_INFO_COMMAND (info_scroll_forward, _("Scroll forward in this window"))
120693142Sru{
120793142Sru  _scroll_forward (window, count, key, info_scroll_behaviour);
120893142Sru}
120993142Sru
121093142Sru/* Like info_scroll_forward, but sets default_window_size as a side
121193142Sru   effect.  */
121293142SruDECLARE_INFO_COMMAND (info_scroll_forward_set_window,
121393142Sru		      _("Scroll forward in this window and set default window size"))
121493142Sru{
121593142Sru  if (info_explicit_arg)
121693142Sru    default_window_size = count;
121793142Sru  _scroll_forward (window, count, key, info_scroll_behaviour);
121893142Sru}
121993142Sru
122093142Sru/* Show the next screen of WINDOW's node but never advance to next node. */
122193142SruDECLARE_INFO_COMMAND (info_scroll_forward_page_only, _("Scroll forward in this window staying within node"))
122293142Sru{
122393142Sru  _scroll_forward (window, count, key, IS_PageOnly);
122493142Sru}
122593142Sru
122693142Sru/* Like info_scroll_forward_page_only, but sets default_window_size as a side
122793142Sru   effect.  */
122893142SruDECLARE_INFO_COMMAND (info_scroll_forward_page_only_set_window,
122993142Sru		      _("Scroll forward in this window staying within node and set default window size"))
123093142Sru{
123193142Sru  if (info_explicit_arg)
123293142Sru    default_window_size = count;
123393142Sru  _scroll_forward (window, count, key, IS_PageOnly);
123493142Sru}
123593142Sru
123693142Sru/* Show the previous screen of WINDOW's node. */
123793142SruDECLARE_INFO_COMMAND (info_scroll_backward, _("Scroll backward in this window"))
123893142Sru{
123993142Sru  _scroll_backward (window, count, key, info_scroll_behaviour);
124093142Sru}
124193142Sru
124256164Sru/* Like info_scroll_backward, but sets default_window_size as a side
124356164Sru   effect.  */
124456164SruDECLARE_INFO_COMMAND (info_scroll_backward_set_window,
124556164Sru		      _("Scroll backward in this window and set default window size"))
124656164Sru{
124756164Sru  if (info_explicit_arg)
124856164Sru    default_window_size = count;
124993142Sru  _scroll_backward (window, count, key, info_scroll_behaviour);
125056164Sru}
125156164Sru
125293142Sru/* Show the previous screen of WINDOW's node but never move to previous
125393142Sru   node. */
125493142SruDECLARE_INFO_COMMAND (info_scroll_backward_page_only, _("Scroll backward in this window staying within node"))
125593142Sru{
125693142Sru  _scroll_backward (window, count, key, IS_PageOnly);
125793142Sru}
125893142Sru
125993142Sru/* Like info_scroll_backward_page_only, but sets default_window_size as a side
126093142Sru   effect.  */
126193142SruDECLARE_INFO_COMMAND (info_scroll_backward_page_only_set_window,
126293142Sru		      _("Scroll backward in this window staying within node and set default window size"))
126393142Sru{
126493142Sru  if (info_explicit_arg)
126593142Sru    default_window_size = count;
126693142Sru  _scroll_backward (window, count, key, IS_PageOnly);
126793142Sru}
126893142Sru
126921495Sjmacd/* Move to the beginning of the node. */
127042664SmarkmDECLARE_INFO_COMMAND (info_beginning_of_node, _("Move to the start of this node"))
127121495Sjmacd{
127221495Sjmacd  window->pagetop = window->point = 0;
127321495Sjmacd  window->flags |= W_UpdateWindow;
127421495Sjmacd}
127521495Sjmacd
127621495Sjmacd/* Move to the end of the node. */
127742664SmarkmDECLARE_INFO_COMMAND (info_end_of_node, _("Move to the end of this node"))
127821495Sjmacd{
127921495Sjmacd  window->point = window->node->nodelen - 1;
128021495Sjmacd  info_show_point (window);
128121495Sjmacd}
128256164Sru
128356164Sru/* Scroll the window forward by N lines.  */
128456164SruDECLARE_INFO_COMMAND (info_down_line, _("Scroll down by lines"))
128556164Sru{
128656164Sru  if (count < 0)
128756164Sru    info_up_line (window, -count, key);
128856164Sru  else
128956164Sru    {
129056164Sru      int desired_top = window->pagetop + count;
129156164Sru
129256164Sru      if (desired_top >= window->line_count)
129356164Sru	desired_top = window->line_count - 2;
129456164Sru
129556164Sru      if (window->pagetop <= desired_top)
129656164Sru	set_window_pagetop (window, desired_top);
129756164Sru    }
129856164Sru}
129956164Sru
130056164Sru/* Scroll the window backward by N lines.  */
130156164SruDECLARE_INFO_COMMAND (info_up_line, _("Scroll up by lines"))
130256164Sru{
130356164Sru  if (count < 0)
130456164Sru    info_down_line (window, -count, key);
130556164Sru  else
130656164Sru    {
130756164Sru      int desired_top = window->pagetop - count;
130856164Sru
130956164Sru      if (desired_top < 0)
131056164Sru	desired_top = 0;
131156164Sru
131256164Sru      set_window_pagetop (window, desired_top);
131356164Sru    }
131456164Sru}
131556164Sru
131656164Sru/* Scroll the window forward by N lines and remember N as default for
131756164Sru   subsequent commands.  */
131856164SruDECLARE_INFO_COMMAND (info_scroll_half_screen_down,
131956164Sru		      _("Scroll down by half screen size"))
132056164Sru{
132156164Sru  if (count < 0)
132293142Sru    info_scroll_half_screen_up (window, -count, key);
132356164Sru  else
132456164Sru    {
132556164Sru      int scroll_size = (the_screen->height + 1) / 2;
132656164Sru      int desired_top;
132756164Sru
132856164Sru      if (info_explicit_arg)
132956164Sru	default_scroll_size = count;
133056164Sru      if (default_scroll_size > 0)
133156164Sru	scroll_size = default_scroll_size;
133256164Sru
133356164Sru      desired_top = window->pagetop + scroll_size;
133456164Sru      if (desired_top >= window->line_count)
133556164Sru	desired_top = window->line_count - 2;
133656164Sru
133756164Sru      if (window->pagetop <= desired_top)
133856164Sru	set_window_pagetop (window, desired_top);
133956164Sru    }
134056164Sru}
134156164Sru
134256164Sru/* Scroll the window backward by N lines and remember N as default for
134356164Sru   subsequent commands.  */
134456164SruDECLARE_INFO_COMMAND (info_scroll_half_screen_up,
134556164Sru		      _("Scroll up by half screen size"))
134656164Sru{
134756164Sru  if (count < 0)
134893142Sru    info_scroll_half_screen_down (window, -count, key);
134956164Sru  else
135056164Sru    {
135156164Sru      int scroll_size = (the_screen->height + 1) / 2;
135256164Sru      int desired_top;
135356164Sru
135456164Sru      if (info_explicit_arg)
135556164Sru	default_scroll_size = count;
135656164Sru      if (default_scroll_size > 0)
135756164Sru	scroll_size = default_scroll_size;
135856164Sru
135956164Sru      desired_top = window->pagetop - scroll_size;
136056164Sru      if (desired_top < 0)
136156164Sru	desired_top = 0;
136256164Sru
136356164Sru      set_window_pagetop (window, desired_top);
136456164Sru    }
136556164Sru}
136621495Sjmacd
136721495Sjmacd/* **************************************************************** */
136842664Smarkm/*                                                                  */
136942664Smarkm/*                 Commands for Manipulating Windows                */
137042664Smarkm/*                                                                  */
137121495Sjmacd/* **************************************************************** */
137221495Sjmacd
137321495Sjmacd/* Make the next window in the chain be the active window. */
137442664SmarkmDECLARE_INFO_COMMAND (info_next_window, _("Select the next window"))
137521495Sjmacd{
137621495Sjmacd  if (count < 0)
137721495Sjmacd    {
137821495Sjmacd      info_prev_window (window, -count, key);
137921495Sjmacd      return;
138021495Sjmacd    }
138121495Sjmacd
138221495Sjmacd  /* If no other window, error now. */
138321495Sjmacd  if (!windows->next && !echo_area_is_active)
138421495Sjmacd    {
1385146520Sru      info_error ((char *) msg_one_window, NULL, NULL);
138621495Sjmacd      return;
138721495Sjmacd    }
138821495Sjmacd
138921495Sjmacd  while (count--)
139021495Sjmacd    {
139121495Sjmacd      if (window->next)
139242664Smarkm        window = window->next;
139321495Sjmacd      else
139442664Smarkm        {
139542664Smarkm          if (window == the_echo_area || !echo_area_is_active)
139642664Smarkm            window = windows;
139742664Smarkm          else
139842664Smarkm            window = the_echo_area;
139942664Smarkm        }
140021495Sjmacd    }
140121495Sjmacd
140221495Sjmacd  if (active_window != window)
140321495Sjmacd    {
140421495Sjmacd      if (auto_footnotes_p)
140542664Smarkm        info_get_or_remove_footnotes (window);
140621495Sjmacd
140721495Sjmacd      window->flags |= W_UpdateWindow;
140821495Sjmacd      active_window = window;
140921495Sjmacd    }
141021495Sjmacd}
141121495Sjmacd
141221495Sjmacd/* Make the previous window in the chain be the active window. */
141342664SmarkmDECLARE_INFO_COMMAND (info_prev_window, _("Select the previous window"))
141421495Sjmacd{
141521495Sjmacd  if (count < 0)
141621495Sjmacd    {
141721495Sjmacd      info_next_window (window, -count, key);
141821495Sjmacd      return;
141921495Sjmacd    }
142021495Sjmacd
142121495Sjmacd  /* Only one window? */
142221495Sjmacd
142321495Sjmacd  if (!windows->next && !echo_area_is_active)
142421495Sjmacd    {
1425146520Sru      info_error ((char *) msg_one_window, NULL, NULL);
142621495Sjmacd      return;
142721495Sjmacd    }
142821495Sjmacd
142921495Sjmacd  while (count--)
143021495Sjmacd    {
143121495Sjmacd      /* If we are in the echo area, or if the echo area isn't active and we
143242664Smarkm         are in the first window, find the last window in the chain. */
143321495Sjmacd      if (window == the_echo_area ||
143442664Smarkm          (window == windows && !echo_area_is_active))
143542664Smarkm        {
1436146520Sru          register WINDOW *win, *last = NULL;
143721495Sjmacd
143842664Smarkm          for (win = windows; win; win = win->next)
143942664Smarkm            last = win;
144021495Sjmacd
144142664Smarkm          window = last;
144242664Smarkm        }
144321495Sjmacd      else
144442664Smarkm        {
144542664Smarkm          if (window == windows)
144642664Smarkm            window = the_echo_area;
144742664Smarkm          else
144842664Smarkm            window = window->prev;
144942664Smarkm        }
145021495Sjmacd    }
145121495Sjmacd
145221495Sjmacd  if (active_window != window)
145321495Sjmacd    {
145421495Sjmacd      if (auto_footnotes_p)
145542664Smarkm        info_get_or_remove_footnotes (window);
145621495Sjmacd
145721495Sjmacd      window->flags |= W_UpdateWindow;
145821495Sjmacd      active_window = window;
145921495Sjmacd    }
146021495Sjmacd}
146121495Sjmacd
146221495Sjmacd/* Split WINDOW into two windows, both showing the same node.  If we
146321495Sjmacd   are automatically tiling windows, re-tile after the split. */
146442664SmarkmDECLARE_INFO_COMMAND (info_split_window, _("Split the current window"))
146521495Sjmacd{
146621495Sjmacd  WINDOW *split, *old_active;
146721495Sjmacd  int pagetop;
146821495Sjmacd
146921495Sjmacd  /* Remember the current pagetop of the window being split.  If it doesn't
147021495Sjmacd     change, we can scroll its contents around after the split. */
147121495Sjmacd  pagetop = window->pagetop;
147221495Sjmacd
147321495Sjmacd  /* Make the new window. */
147421495Sjmacd  old_active = active_window;
147521495Sjmacd  active_window = window;
147621495Sjmacd  split = window_make_window (window->node);
147721495Sjmacd  active_window = old_active;
147821495Sjmacd
147921495Sjmacd  if (!split)
148021495Sjmacd    {
1481146520Sru      info_error ((char *) msg_win_too_small, NULL, NULL);
148221495Sjmacd    }
148321495Sjmacd  else
148421495Sjmacd    {
148521495Sjmacd#if defined (SPLIT_BEFORE_ACTIVE)
148621495Sjmacd      /* Try to scroll the old window into its new postion. */
148721495Sjmacd      if (pagetop == window->pagetop)
148842664Smarkm        {
148942664Smarkm          int start, end, amount;
149021495Sjmacd
149142664Smarkm          start = split->first_row;
149242664Smarkm          end = start + window->height;
149342664Smarkm          amount = split->height + 1;
149442664Smarkm          display_scroll_display (start, end, amount);
149542664Smarkm        }
149621495Sjmacd#else /* !SPLIT_BEFORE_ACTIVE */
149721495Sjmacd      /* Make sure point still appears in the active window. */
149821495Sjmacd      info_show_point (window);
149921495Sjmacd#endif /* !SPLIT_BEFORE_ACTIVE */
150021495Sjmacd
150121495Sjmacd      /* If the window just split was one internal to Info, try to display
150242664Smarkm         something else in it. */
150321495Sjmacd      if (internal_info_node_p (split->node))
150442664Smarkm        {
150542664Smarkm          register int i, j;
150642664Smarkm          INFO_WINDOW *iw;
150742664Smarkm          NODE *node = (NODE *)NULL;
150842664Smarkm          char *filename;
150921495Sjmacd
151042664Smarkm          for (i = 0; (iw = info_windows[i]); i++)
151142664Smarkm            {
151242664Smarkm              for (j = 0; j < iw->nodes_index; j++)
151342664Smarkm                if (!internal_info_node_p (iw->nodes[j]))
151442664Smarkm                  {
151542664Smarkm                    if (iw->nodes[j]->parent)
151642664Smarkm                      filename = iw->nodes[j]->parent;
151742664Smarkm                    else
151842664Smarkm                      filename = iw->nodes[j]->filename;
151921495Sjmacd
152042664Smarkm                    node = info_get_node (filename, iw->nodes[j]->nodename);
152142664Smarkm                    if (node)
152242664Smarkm                      {
152342664Smarkm                        window_set_node_of_window (split, node);
152442664Smarkm                        i = info_windows_index - 1;
152542664Smarkm                        break;
152642664Smarkm                      }
152742664Smarkm                  }
152842664Smarkm            }
152942664Smarkm        }
153021495Sjmacd      split->pagetop = window->pagetop;
153121495Sjmacd
153221495Sjmacd      if (auto_tiling_p)
153342664Smarkm        window_tile_windows (DONT_TILE_INTERNALS);
153421495Sjmacd      else
153542664Smarkm        window_adjust_pagetop (split);
153621495Sjmacd
153721495Sjmacd      remember_window_and_node (split, split->node);
153821495Sjmacd    }
153921495Sjmacd}
154021495Sjmacd
154121495Sjmacd/* Delete WINDOW, forgetting the list of last visited nodes.  If we are
154221495Sjmacd   automatically displaying footnotes, show or remove the footnotes
154321495Sjmacd   window.  If we are automatically tiling windows, re-tile after the
154421495Sjmacd   deletion. */
154542664SmarkmDECLARE_INFO_COMMAND (info_delete_window, _("Delete the current window"))
154621495Sjmacd{
154721495Sjmacd  if (!windows->next)
154821495Sjmacd    {
1549146520Sru      info_error ((char *) msg_cant_kill_last, NULL, NULL);
155021495Sjmacd    }
155121495Sjmacd  else if (window->flags & W_WindowIsPerm)
155221495Sjmacd    {
1553146520Sru      info_error ((char *) _("Cannot delete a permanent window"), NULL, NULL);
155421495Sjmacd    }
155521495Sjmacd  else
155621495Sjmacd    {
155721495Sjmacd      info_delete_window_internal (window);
155821495Sjmacd
155921495Sjmacd      if (auto_footnotes_p)
156042664Smarkm        info_get_or_remove_footnotes (active_window);
156121495Sjmacd
156221495Sjmacd      if (auto_tiling_p)
156342664Smarkm        window_tile_windows (DONT_TILE_INTERNALS);
156421495Sjmacd    }
156521495Sjmacd}
156621495Sjmacd
156721495Sjmacd/* Do the physical deletion of WINDOW, and forget this window and
156821495Sjmacd   associated nodes. */
156921495Sjmacdvoid
1570146520Sruinfo_delete_window_internal (WINDOW *window)
157121495Sjmacd{
157221495Sjmacd  if (windows->next && ((window->flags & W_WindowIsPerm) == 0))
157321495Sjmacd    {
157421495Sjmacd      /* We not only delete the window from the display, we forget it from
157542664Smarkm         our list of remembered windows. */
157621495Sjmacd      forget_window_and_nodes (window);
157721495Sjmacd      window_delete_window (window);
157821495Sjmacd
157921495Sjmacd      if (echo_area_is_active)
158042664Smarkm        echo_area_inform_of_deleted_window (window);
158121495Sjmacd    }
158221495Sjmacd}
158321495Sjmacd
158421495Sjmacd/* Just keep WINDOW, deleting all others. */
158542664SmarkmDECLARE_INFO_COMMAND (info_keep_one_window, _("Delete all other windows"))
158621495Sjmacd{
158742664Smarkm  int num_deleted;              /* The number of windows we deleted. */
158821495Sjmacd  int pagetop, start, end;
158921495Sjmacd
159021495Sjmacd  /* Remember a few things about this window.  We may be able to speed up
159121495Sjmacd     redisplay later by scrolling its contents. */
159221495Sjmacd  pagetop = window->pagetop;
159321495Sjmacd  start = window->first_row;
159421495Sjmacd  end = start + window->height;
159521495Sjmacd
159621495Sjmacd  num_deleted = 0;
159721495Sjmacd
159821495Sjmacd  while (1)
159921495Sjmacd    {
160021495Sjmacd      WINDOW *win;
160121495Sjmacd
160221495Sjmacd      /* Find an eligible window and delete it.  If no eligible windows
160342664Smarkm         are found, we are done.  A window is eligible for deletion if
160442664Smarkm         is it not permanent, and it is not WINDOW. */
160521495Sjmacd      for (win = windows; win; win = win->next)
160642664Smarkm        if (win != window && ((win->flags & W_WindowIsPerm) == 0))
160742664Smarkm          break;
160821495Sjmacd
160921495Sjmacd      if (!win)
161042664Smarkm        break;
161121495Sjmacd
161221495Sjmacd      info_delete_window_internal (win);
161321495Sjmacd      num_deleted++;
161421495Sjmacd    }
161521495Sjmacd
161621495Sjmacd  /* Scroll the contents of this window into the right place so that the
161721495Sjmacd     user doesn't have to wait any longer than necessary for redisplay. */
161821495Sjmacd  if (num_deleted)
161921495Sjmacd    {
162021495Sjmacd      int amount;
162121495Sjmacd
162221495Sjmacd      amount = (window->first_row - start);
162321495Sjmacd      amount -= (window->pagetop - pagetop);
162421495Sjmacd      display_scroll_display (start, end, amount);
162521495Sjmacd    }
162621495Sjmacd
162721495Sjmacd  window->flags |= W_UpdateWindow;
162821495Sjmacd}
162921495Sjmacd
163021495Sjmacd/* Scroll the "other" window of WINDOW. */
163142664SmarkmDECLARE_INFO_COMMAND (info_scroll_other_window, _("Scroll the other window"))
163221495Sjmacd{
163321495Sjmacd  WINDOW *other;
163421495Sjmacd
163521495Sjmacd  /* If only one window, give up. */
163621495Sjmacd  if (!windows->next)
163721495Sjmacd    {
1638146520Sru      info_error ((char *) msg_one_window, NULL, NULL);
163921495Sjmacd      return;
164021495Sjmacd    }
164121495Sjmacd
164221495Sjmacd  other = window->next;
164321495Sjmacd
164421495Sjmacd  if (!other)
164521495Sjmacd    other = window->prev;
164621495Sjmacd
164721495Sjmacd  info_scroll_forward (other, count, key);
164821495Sjmacd}
164921495Sjmacd
165056164Sru/* Scroll the "other" window of WINDOW. */
165156164SruDECLARE_INFO_COMMAND (info_scroll_other_window_backward,
165256164Sru                      _("Scroll the other window backward"))
165356164Sru{
165456164Sru  info_scroll_other_window (window, -count, key);
165556164Sru}
165656164Sru
165721495Sjmacd/* Change the size of WINDOW by AMOUNT. */
165842664SmarkmDECLARE_INFO_COMMAND (info_grow_window, _("Grow (or shrink) this window"))
165921495Sjmacd{
166021495Sjmacd  window_change_window_height (window, count);
166121495Sjmacd}
166221495Sjmacd
166321495Sjmacd/* When non-zero, tiling takes place automatically when info_split_window
166421495Sjmacd   is called. */
166521495Sjmacdint auto_tiling_p = 0;
166621495Sjmacd
166721495Sjmacd/* Tile all of the visible windows. */
166821495SjmacdDECLARE_INFO_COMMAND (info_tile_windows,
166942664Smarkm    _("Divide the available screen space among the visible windows"))
167021495Sjmacd{
167121495Sjmacd  window_tile_windows (TILE_INTERNALS);
167221495Sjmacd}
167321495Sjmacd
167421495Sjmacd/* Toggle the state of this window's wrapping of lines. */
167521495SjmacdDECLARE_INFO_COMMAND (info_toggle_wrap,
167642664Smarkm              _("Toggle the state of line wrapping in the current window"))
167721495Sjmacd{
167821495Sjmacd  window_toggle_wrap (window);
167921495Sjmacd}
168021495Sjmacd
168121495Sjmacd/* **************************************************************** */
168242664Smarkm/*                                                                  */
168342664Smarkm/*                      Info Node Commands                          */
168442664Smarkm/*                                                                  */
168521495Sjmacd/* **************************************************************** */
168621495Sjmacd
168756164Sru/* Return (FILENAME)NODENAME for NODE, or just NODENAME if NODE's
168856164Sru   filename is not set. */
168956164Sruchar *
1690146520Srunode_printed_rep (NODE *node)
169156164Sru{
169256164Sru  char *rep;
169356164Sru
169456164Sru  if (node->filename)
169556164Sru    {
169656164Sru      char *filename
169756164Sru       = filename_non_directory (node->parent ? node->parent : node->filename);
169856164Sru      rep = xmalloc (1 + strlen (filename) + 1 + strlen (node->nodename) + 1);
169956164Sru      sprintf (rep, "(%s)%s", filename, node->nodename);
170056164Sru    }
170156164Sru  else
170256164Sru    rep = node->nodename;
170356164Sru
170456164Sru  return rep;
170556164Sru}
170656164Sru
170756164Sru
170821495Sjmacd/* Using WINDOW for various defaults, select the node referenced by ENTRY
170921495Sjmacd   in it.  If the node is selected, the window and node are remembered. */
171021495Sjmacdvoid
1711146520Sruinfo_select_reference (WINDOW *window, REFERENCE *entry)
171221495Sjmacd{
171321495Sjmacd  NODE *node;
171421495Sjmacd  char *filename, *nodename, *file_system_error;
171521495Sjmacd
171621495Sjmacd  file_system_error = (char *)NULL;
171721495Sjmacd
171821495Sjmacd  filename = entry->filename;
171921495Sjmacd  if (!filename)
172021495Sjmacd    filename = window->node->parent;
172121495Sjmacd  if (!filename)
172221495Sjmacd    filename = window->node->filename;
172321495Sjmacd
172421495Sjmacd  if (filename)
172542664Smarkm    filename = xstrdup (filename);
172621495Sjmacd
172721495Sjmacd  if (entry->nodename)
172842664Smarkm    nodename = xstrdup (entry->nodename);
172921495Sjmacd  else
173042664Smarkm    nodename = xstrdup ("Top");
173121495Sjmacd
173221495Sjmacd  node = info_get_node (filename, nodename);
173321495Sjmacd
173421495Sjmacd  /* Try something a little weird.  If the node couldn't be found, and the
173521495Sjmacd     reference was of the form "foo::", see if the entry->label can be found
173621495Sjmacd     as a file, with a node of "Top". */
173721495Sjmacd  if (!node)
173821495Sjmacd    {
173921495Sjmacd      if (info_recent_file_error)
174042664Smarkm        file_system_error = xstrdup (info_recent_file_error);
174121495Sjmacd
174221495Sjmacd      if (entry->nodename && (strcmp (entry->nodename, entry->label) == 0))
174342664Smarkm        {
174442664Smarkm          node = info_get_node (entry->label, "Top");
174542664Smarkm          if (!node && info_recent_file_error)
174642664Smarkm            {
174742664Smarkm              maybe_free (file_system_error);
174842664Smarkm              file_system_error = xstrdup (info_recent_file_error);
174942664Smarkm            }
175042664Smarkm        }
175121495Sjmacd    }
175221495Sjmacd
175321495Sjmacd  if (!node)
175421495Sjmacd    {
175521495Sjmacd      if (file_system_error)
1756146520Sru        info_error (file_system_error, NULL, NULL);
175721495Sjmacd      else
1758146520Sru        info_error ((char *) msg_cant_find_node, nodename, NULL);
175921495Sjmacd    }
176021495Sjmacd
176121495Sjmacd  maybe_free (file_system_error);
176221495Sjmacd  maybe_free (filename);
176321495Sjmacd  maybe_free (nodename);
176421495Sjmacd
176521495Sjmacd  if (node)
176656164Sru    info_set_node_of_window (1, window, node);
176721495Sjmacd}
176821495Sjmacd
176921495Sjmacd/* Parse the node specification in LINE using WINDOW to default the filename.
177021495Sjmacd   Select the parsed node in WINDOW and remember it, or error if the node
177121495Sjmacd   couldn't be found. */
177221495Sjmacdstatic void
1773146520Sruinfo_parse_and_select (char *line, WINDOW *window)
177421495Sjmacd{
177521495Sjmacd  REFERENCE entry;
177621495Sjmacd
177721495Sjmacd  info_parse_node (line, DONT_SKIP_NEWLINES);
177821495Sjmacd
177921495Sjmacd  entry.nodename = info_parsed_nodename;
178021495Sjmacd  entry.filename = info_parsed_filename;
178121495Sjmacd  entry.label = "*info-parse-and-select*";
178221495Sjmacd
178321495Sjmacd  info_select_reference (window, &entry);
178421495Sjmacd}
178521495Sjmacd
178621495Sjmacd/* Given that the values of INFO_PARSED_FILENAME and INFO_PARSED_NODENAME
178721495Sjmacd   are previously filled, try to get the node represented by them into
178821495Sjmacd   WINDOW.  The node should have been pointed to by the LABEL pointer of
178921495Sjmacd   WINDOW->node. */
179021495Sjmacdstatic void
1791146520Sruinfo_handle_pointer (char *label, WINDOW *window)
179221495Sjmacd{
179321495Sjmacd  if (info_parsed_filename || info_parsed_nodename)
179421495Sjmacd    {
179521495Sjmacd      char *filename, *nodename;
179621495Sjmacd      NODE *node;
179721495Sjmacd
179821495Sjmacd      filename = nodename = (char *)NULL;
179921495Sjmacd
180021495Sjmacd      if (info_parsed_filename)
180142664Smarkm        filename = xstrdup (info_parsed_filename);
180221495Sjmacd      else
180342664Smarkm        {
180442664Smarkm          if (window->node->parent)
180542664Smarkm            filename = xstrdup (window->node->parent);
180642664Smarkm          else if (window->node->filename)
180742664Smarkm            filename = xstrdup (window->node->filename);
180842664Smarkm        }
180921495Sjmacd
181021495Sjmacd      if (info_parsed_nodename)
181142664Smarkm        nodename = xstrdup (info_parsed_nodename);
181221495Sjmacd      else
181342664Smarkm        nodename = xstrdup ("Top");
181421495Sjmacd
181521495Sjmacd      node = info_get_node (filename, nodename);
181621495Sjmacd
181721495Sjmacd      if (node)
181842664Smarkm        {
181942664Smarkm          INFO_WINDOW *info_win;
182021495Sjmacd
182142664Smarkm          info_win = get_info_window_of_window (window);
182242664Smarkm          if (info_win)
182342664Smarkm            {
182442664Smarkm              info_win->pagetops[info_win->current] = window->pagetop;
182542664Smarkm              info_win->points[info_win->current] = window->point;
182642664Smarkm            }
182756164Sru          info_set_node_of_window (1, window, node);
182842664Smarkm        }
182921495Sjmacd      else
183042664Smarkm        {
183142664Smarkm          if (info_recent_file_error)
1832146520Sru            info_error (info_recent_file_error, NULL, NULL);
183342664Smarkm          else
1834146520Sru            info_error ((char *) msg_cant_file_node, filename, nodename);
183542664Smarkm        }
183621495Sjmacd
183721495Sjmacd      free (filename);
183821495Sjmacd      free (nodename);
183921495Sjmacd    }
184021495Sjmacd  else
184121495Sjmacd    {
1842146520Sru      info_error ((char *) msg_no_pointer, label, NULL);
184321495Sjmacd    }
184421495Sjmacd}
184521495Sjmacd
184621495Sjmacd/* Make WINDOW display the "Next:" node of the node currently being
184721495Sjmacd   displayed. */
184856164SruDECLARE_INFO_COMMAND (info_next_node, _("Select the Next node"))
184921495Sjmacd{
185021495Sjmacd  info_next_label_of_node (window->node);
185156164Sru  info_handle_pointer ("Next", window);
185221495Sjmacd}
185321495Sjmacd
185421495Sjmacd/* Make WINDOW display the "Prev:" node of the node currently being
185521495Sjmacd   displayed. */
185656164SruDECLARE_INFO_COMMAND (info_prev_node, _("Select the Prev node"))
185721495Sjmacd{
185821495Sjmacd  info_prev_label_of_node (window->node);
185956164Sru  info_handle_pointer ("Prev", window);
186021495Sjmacd}
186121495Sjmacd
186221495Sjmacd/* Make WINDOW display the "Up:" node of the node currently being
186321495Sjmacd   displayed. */
186456164SruDECLARE_INFO_COMMAND (info_up_node, _("Select the Up node"))
186521495Sjmacd{
186621495Sjmacd  info_up_label_of_node (window->node);
186756164Sru  info_handle_pointer ("Up", window);
186821495Sjmacd}
186921495Sjmacd
187021495Sjmacd/* Make WINDOW display the last node of this info file. */
187142664SmarkmDECLARE_INFO_COMMAND (info_last_node, _("Select the last node in this file"))
187221495Sjmacd{
187321495Sjmacd  register int i;
187421495Sjmacd  FILE_BUFFER *fb = file_buffer_of_window (window);
187521495Sjmacd  NODE *node = (NODE *)NULL;
187621495Sjmacd
187721495Sjmacd  if (fb && fb->tags)
187821495Sjmacd    {
187956164Sru      int last_node_tag_idx = -1;
188056164Sru
188156164Sru      /* If no explicit argument, or argument of zero, default to the
188256164Sru         last node.  */
188356164Sru      if (count == 0 || (count == 1 && !info_explicit_arg))
188456164Sru        count = -1;
188556164Sru      for (i = 0; count && fb->tags[i]; i++)
188656164Sru        if (fb->tags[i]->nodelen != 0) /* don't count anchor tags */
188756164Sru          {
188856164Sru            count--;
188956164Sru            last_node_tag_idx = i;
189056164Sru          }
189156164Sru      if (count > 0)
189256164Sru        i = last_node_tag_idx + 1;
189356164Sru      if (i > 0)
189456164Sru        node = info_get_node (fb->filename, fb->tags[i - 1]->nodename);
189521495Sjmacd    }
189621495Sjmacd
189721495Sjmacd  if (!node)
1898146520Sru    info_error ((char *) _("This window has no additional nodes"), NULL, NULL);
189921495Sjmacd  else
190056164Sru    info_set_node_of_window (1, window, node);
190121495Sjmacd}
190221495Sjmacd
190321495Sjmacd/* Make WINDOW display the first node of this info file. */
190442664SmarkmDECLARE_INFO_COMMAND (info_first_node, _("Select the first node in this file"))
190521495Sjmacd{
190621495Sjmacd  FILE_BUFFER *fb = file_buffer_of_window (window);
190721495Sjmacd  NODE *node = (NODE *)NULL;
190821495Sjmacd
190956164Sru  /* If no explicit argument, or argument of zero, default to the
191056164Sru     first node.  */
191156164Sru  if (count == 0)
191256164Sru    count = 1;
191321495Sjmacd  if (fb && fb->tags)
191456164Sru    {
191556164Sru      register int i;
191656164Sru      int last_node_tag_idx = -1;
191721495Sjmacd
191856164Sru      for (i = 0; count && fb->tags[i]; i++)
191956164Sru        if (fb->tags[i]->nodelen != 0) /* don't count anchor tags */
192056164Sru          {
192156164Sru            count--;
192256164Sru            last_node_tag_idx = i;
192356164Sru          }
192456164Sru      if (count > 0)
192556164Sru        i = last_node_tag_idx + 1;
192656164Sru      if (i > 0)
192756164Sru        node = info_get_node (fb->filename, fb->tags[i - 1]->nodename);
192856164Sru    }
192956164Sru
193021495Sjmacd  if (!node)
1931146520Sru    info_error ((char *) _("This window has no additional nodes"), NULL, NULL);
193221495Sjmacd  else
193356164Sru    info_set_node_of_window (1, window, node);
193421495Sjmacd}
193521495Sjmacd
193621495Sjmacd/* Select the last menu item in WINDOW->node. */
193721495SjmacdDECLARE_INFO_COMMAND (info_last_menu_item,
193842664Smarkm   _("Select the last item in this node's menu"))
193921495Sjmacd{
194021495Sjmacd  info_menu_digit (window, 1, '0');
194121495Sjmacd}
194221495Sjmacd
194321495Sjmacd/* Use KEY (a digit) to select the Nth menu item in WINDOW->node. */
194442664SmarkmDECLARE_INFO_COMMAND (info_menu_digit, _("Select this menu item"))
194521495Sjmacd{
194621495Sjmacd  register int i, item;
1947146520Sru  register REFERENCE **menu;
194821495Sjmacd
194921495Sjmacd  menu = info_menu_of_node (window->node);
195021495Sjmacd
195121495Sjmacd  if (!menu)
195221495Sjmacd    {
1953146520Sru      info_error ((char *) msg_no_menu_node, NULL, NULL);
195421495Sjmacd      return;
195521495Sjmacd    }
195621495Sjmacd
195721495Sjmacd  /* We have the menu.  See if there are this many items in it. */
195821495Sjmacd  item = key - '0';
195921495Sjmacd
196021495Sjmacd  /* Special case.  Item "0" is the last item in this menu. */
196121495Sjmacd  if (item == 0)
196221495Sjmacd    for (i = 0; menu[i + 1]; i++);
196321495Sjmacd  else
196421495Sjmacd    {
1965146520Sru      for (i = 0; menu[i]; i++)
196642664Smarkm        if (i == item - 1)
196742664Smarkm          break;
196821495Sjmacd    }
196921495Sjmacd
197021495Sjmacd  if (menu[i])
1971146520Sru    {
1972146520Sru      info_select_reference (window, menu[i]);
1973146520Sru      if (menu[i]->line_number > 0)
1974146520Sru        info_next_line (window, menu[i]->line_number - 1, key);
1975146520Sru    }
197621495Sjmacd  else
1977146520Sru    info_error ((char *) _("There aren't %d items in this menu."),
1978146520Sru                (void *) (long) item, NULL);
197921495Sjmacd
198021495Sjmacd  info_free_references (menu);
198121495Sjmacd  return;
198221495Sjmacd}
198321495Sjmacd
1984116530Sru
1985116530Sru
1986116530Sru/* Return a pointer to the xref in XREF_LIST that is nearest to POS, or
1987116530Sru   NULL if XREF_LIST is empty.  That is, if POS is within any of the
1988116530Sru   given xrefs, return that one.  Otherwise, return the one with the
1989116530Sru   nearest beginning or end.  If there are two that are equidistant,
1990116530Sru   prefer the one forward.  The return is in newly-allocated memory,
1991116530Sru   since the caller frees it.
1992116530Sru
1993116530Sru   This is called from info_menu_or_ref_item with XREF_LIST being all
1994116530Sru   the xrefs in the node, and POS being point.  The ui function that
1995116530Sru   starts it all off is select-reference-this-line.
1996116530Sru
1997116530Sru   This is not the same logic as in info.el.  Info-get-token prefers
1998116530Sru   searching backwards to searching forwards, and has a hardwired search
1999116530Sru   limit of 200 chars (in Emacs 21.2).  */
2000116530Sru
2001116530Srustatic REFERENCE **
2002146520Srunearest_xref (REFERENCE **xref_list, long int pos)
2003116530Sru{
2004116530Sru  int this_xref;
2005116530Sru  int nearest = -1;
2006116530Sru  long best_delta = -1;
2007116530Sru
2008116530Sru  for (this_xref = 0; xref_list[this_xref]; this_xref++)
2009116530Sru    {
2010116530Sru      long delta;
2011116530Sru      REFERENCE *xref = xref_list[this_xref];
2012116530Sru      if (xref->start <= pos && pos <= xref->end)
2013116530Sru        { /* POS is within this xref, we're done */
2014116530Sru          nearest = this_xref;
2015116530Sru          break;
2016116530Sru        }
2017116530Sru
2018116530Sru      /* See how far POS is from this xref.  Take into account the
2019116530Sru         `*Note' that begins the xref, since as far as the user is
2020116530Sru         concerned, that's where it starts.  */
2021116530Sru      delta = MIN (labs (pos - (xref->start - strlen (INFO_XREF_LABEL))),
2022116530Sru                   labs (pos - xref->end));
2023116530Sru
2024116530Sru      /* It's the <= instead of < that makes us choose the forward xref
2025116530Sru         of POS if two are equidistant.  Of course, because of all the
2026116530Sru         punctuation surrounding xrefs, it's not necessarily obvious
2027116530Sru         where one ends.  */
2028116530Sru      if (delta <= best_delta || best_delta < 0)
2029116530Sru        {
2030116530Sru          nearest = this_xref;
2031116530Sru          best_delta = delta;
2032116530Sru        }
2033116530Sru    }
2034116530Sru
2035116530Sru  /* Maybe there was no list to search through.  */
2036116530Sru  if (nearest < 0)
2037116530Sru    return NULL;
2038116530Sru
2039116530Sru  /* Ok, we have a nearest xref, make a list of it.  */
2040116530Sru  {
2041116530Sru    REFERENCE **ret = xmalloc (sizeof (REFERENCE *) * 2);
2042116530Sru    ret[0] = info_copy_reference (xref_list[nearest]);
2043116530Sru    ret[1] = NULL;
2044116530Sru    return ret;
2045116530Sru  }
2046116530Sru}
2047116530Sru
2048116530Sru
204921495Sjmacd/* Read a menu or followed reference from the user defaulting to the
205021495Sjmacd   reference found on the current line, and select that node.  The
205121495Sjmacd   reading is done with completion.  BUILDER is the function used
205221495Sjmacd   to build the list of references.  ASK_P is non-zero if the user
205321495Sjmacd   should be prompted, or zero to select the default item. */
205421495Sjmacdstatic void
2055146520Sruinfo_menu_or_ref_item (WINDOW *window, int count,
2056146520Sru    unsigned char key, REFERENCE **(*builder) (NODE *node), int ask_p)
205721495Sjmacd{
205821495Sjmacd  char *line;
2059116530Sru  REFERENCE *entry;
2060116530Sru  REFERENCE *defentry = NULL;
2061116530Sru  REFERENCE **menu = (*builder) (window->node);
206221495Sjmacd
206321495Sjmacd  if (!menu)
206421495Sjmacd    {
206521495Sjmacd      if (builder == info_menu_of_node)
2066146520Sru        info_error ((char *) msg_no_menu_node, NULL, NULL);
206721495Sjmacd      else
2068146520Sru        info_error ((char *) msg_no_xref_node, NULL, NULL);
206921495Sjmacd      return;
207021495Sjmacd    }
207121495Sjmacd
207221495Sjmacd  /* Default the selected reference to the one which is on the line that
207321495Sjmacd     point is in.  */
207421495Sjmacd  {
2075116530Sru    REFERENCE **refs = NULL;
2076116530Sru    int point_line = window_line_of_point (window);
207721495Sjmacd
207821495Sjmacd    if (point_line != -1)
207921495Sjmacd      {
208042664Smarkm        SEARCH_BINDING binding;
208121495Sjmacd
208242664Smarkm        binding.buffer = window->node->contents;
208342664Smarkm        binding.start = window->line_starts[point_line] - binding.buffer;
208442664Smarkm        if (window->line_starts[point_line + 1])
208542664Smarkm          binding.end = window->line_starts[point_line + 1] - binding.buffer;
208642664Smarkm        else
208742664Smarkm          binding.end = window->node->nodelen;
208842664Smarkm        binding.flags = 0;
208921495Sjmacd
209042664Smarkm        if (builder == info_menu_of_node)
209142664Smarkm          {
209242664Smarkm            if (point_line)
209342664Smarkm              {
209442664Smarkm                binding.start--;
209542664Smarkm                refs = info_menu_items (&binding);
209642664Smarkm              }
209742664Smarkm          }
209842664Smarkm        else
209942664Smarkm          {
210021495Sjmacd#if defined (HANDLE_MAN_PAGES)
210142664Smarkm            if (window->node->flags & N_IsManPage)
210242664Smarkm              refs = manpage_xrefs_in_binding (window->node, &binding);
210342664Smarkm            else
210421495Sjmacd#endif /* HANDLE_MAN_PAGES */
2105116530Sru              refs = nearest_xref (menu, window->point);
210642664Smarkm          }
210721495Sjmacd
2108116530Sru        if (refs && refs[0])
210942664Smarkm          {
2110116530Sru            if (strcmp (refs[0]->label, "Menu") != 0
2111116530Sru                || builder == info_xrefs_of_node)
211242664Smarkm              {
211342664Smarkm                int which = 0;
211421495Sjmacd
2115116530Sru                /* For xrefs, find the closest reference to point,
2116116530Sru                   unless we only have one reference (as we will if
2117116530Sru                   we've called nearest_xref above).  It would be better
2118116530Sru                   to have only one piece of code, but the conditions
2119116530Sru                   when we call this are tangled.  */
2120116530Sru                if (builder == info_xrefs_of_node && refs[1])
212142664Smarkm                  {
212242664Smarkm                    int closest = -1;
212321495Sjmacd
212442664Smarkm                    for (; refs[which]; which++)
212542664Smarkm                      {
2126116530Sru                        if (window->point >= refs[which]->start
2127116530Sru                            && window->point <= refs[which]->end)
212842664Smarkm                          {
212942664Smarkm                            closest = which;
213042664Smarkm                            break;
213142664Smarkm                          }
213242664Smarkm                        else if (window->point < refs[which]->start)
2133116530Sru                          break;
213442664Smarkm                      }
2135146520Sru		    if (which > 0)
2136146520Sru		      {
2137146520Sru			if (closest == -1)
2138146520Sru			  which--;
2139146520Sru			else
2140146520Sru			  which = closest;
2141146520Sru		      }
214242664Smarkm                  }
214321495Sjmacd
214442664Smarkm                defentry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
214542664Smarkm                defentry->label = xstrdup (refs[which]->label);
214642664Smarkm                defentry->filename = refs[which]->filename;
214742664Smarkm                defentry->nodename = refs[which]->nodename;
2148146520Sru                defentry->line_number = refs[which]->line_number;
214921495Sjmacd
215042664Smarkm                if (defentry->filename)
215142664Smarkm                  defentry->filename = xstrdup (defentry->filename);
215242664Smarkm                if (defentry->nodename)
215342664Smarkm                  defentry->nodename = xstrdup (defentry->nodename);
215442664Smarkm              }
215542664Smarkm            info_free_references (refs);
215642664Smarkm          }
215721495Sjmacd      }
215821495Sjmacd  }
215921495Sjmacd
216021495Sjmacd  /* If we are going to ask the user a question, do it now. */
216121495Sjmacd  if (ask_p)
216221495Sjmacd    {
216321495Sjmacd      char *prompt;
216421495Sjmacd
216521495Sjmacd      /* Build the prompt string. */
216621495Sjmacd      if (builder == info_menu_of_node)
216742664Smarkm        {
216842664Smarkm          if (defentry)
2169146520Sru	    {
2170146520Sru	      prompt = xmalloc (strlen (defentry->label)
2171146520Sru				+ strlen (_("Menu item (%s): ")));
2172146520Sru	      sprintf (prompt, _("Menu item (%s): "), defentry->label);
2173146520Sru	    }
217442664Smarkm          else
2175146520Sru	    prompt = xstrdup (_("Menu item: "));
217642664Smarkm        }
217721495Sjmacd      else
217842664Smarkm        {
217942664Smarkm          if (defentry)
2180146520Sru	    {
2181146520Sru	      prompt = xmalloc (strlen (defentry->label)
2182146520Sru				+ strlen (_("Follow xref (%s): ")));
2183146520Sru	      sprintf (prompt, _("Follow xref (%s): "), defentry->label);
2184146520Sru	    }
218542664Smarkm          else
2186146520Sru	    prompt = xstrdup (_("Follow xref: "));
218742664Smarkm        }
218821495Sjmacd
218921495Sjmacd      line = info_read_completing_in_echo_area (window, prompt, menu);
219021495Sjmacd      free (prompt);
219121495Sjmacd
219221495Sjmacd      window = active_window;
219321495Sjmacd
219421495Sjmacd      /* User aborts, just quit. */
219521495Sjmacd      if (!line)
219642664Smarkm        {
219742664Smarkm          maybe_free (defentry);
219842664Smarkm          info_free_references (menu);
219942664Smarkm          info_abort_key (window, 0, 0);
220042664Smarkm          return;
220142664Smarkm        }
220221495Sjmacd
220321495Sjmacd      /* If we had a default and the user accepted it, use that. */
220421495Sjmacd      if (!*line)
220542664Smarkm        {
220642664Smarkm          free (line);
220742664Smarkm          if (defentry)
220842664Smarkm            line = xstrdup (defentry->label);
220942664Smarkm          else
221042664Smarkm            line = (char *)NULL;
221142664Smarkm        }
221221495Sjmacd    }
221321495Sjmacd  else
221421495Sjmacd    {
221521495Sjmacd      /* Not going to ask any questions.  If we have a default entry, use
221642664Smarkm         that, otherwise return. */
221721495Sjmacd      if (!defentry)
221842664Smarkm        return;
221921495Sjmacd      else
222042664Smarkm        line = xstrdup (defentry->label);
222121495Sjmacd    }
222221495Sjmacd
222321495Sjmacd  if (line)
222421495Sjmacd    {
222593142Sru      /* It is possible that the references have more than a single
222693142Sru         entry with the same label, and also LINE is down-cased, which
222793142Sru         complicates matters even more.  Try to be as accurate as we
222893142Sru         can: if they've chosen the default, use defentry directly. */
222993142Sru      if (defentry && strcmp (line, defentry->label) == 0)
223093142Sru        entry = defentry;
223193142Sru      else
223293142Sru        /* Find the selected label in the references.  If there are
223393142Sru           more than one label which matches, find the one that's
223493142Sru           closest to point.  */
223593142Sru        {
223693142Sru          register int i;
223793142Sru          int best = -1, min_dist = window->node->nodelen;
223893142Sru          REFERENCE *ref;
223921495Sjmacd
224093142Sru          for (i = 0; menu && (ref = menu[i]); i++)
224193142Sru            {
224293142Sru              /* Need to use strcasecmp because LINE is downcased
224393142Sru                 inside info_read_completing_in_echo_area.  */
224493142Sru              if (strcasecmp (line, ref->label) == 0)
224593142Sru                {
224693142Sru                  /* ref->end is more accurate estimate of position
224793142Sru                     for menus than ref->start.  Go figure.  */
224893142Sru                  int dist = abs (window->point - ref->end);
224993142Sru
225093142Sru                  if (dist < min_dist)
225193142Sru                    {
225293142Sru                      min_dist = dist;
225393142Sru                      best = i;
225493142Sru                    }
225593142Sru                }
225693142Sru            }
225793142Sru          if (best != -1)
225893142Sru            entry = menu[best];
225993142Sru          else
226093142Sru            entry = (REFERENCE *)NULL;
226193142Sru        }
226293142Sru
226321495Sjmacd      if (!entry && defentry)
2264146520Sru        info_error ((char *) _("The reference disappeared! (%s)."), line, NULL);
226521495Sjmacd      else
226642664Smarkm        {
226756164Sru          NODE *orig = window->node;
226842664Smarkm          info_select_reference (window, entry);
2269146520Sru
227056164Sru          if (builder == info_xrefs_of_node && window->node != orig
227156164Sru              && !(window->node->flags & N_FromAnchor))
227256164Sru            { /* Search for this reference in the node.  */
227342664Smarkm              long offset;
227442664Smarkm              long start;
227521495Sjmacd
227642664Smarkm              if (window->line_count > 0)
227742664Smarkm                start = window->line_starts[1] - window->node->contents;
227842664Smarkm              else
227942664Smarkm                start = 0;
228021495Sjmacd
228142664Smarkm              offset =
228242664Smarkm                info_target_search_node (window->node, entry->label, start);
228321495Sjmacd
228442664Smarkm              if (offset != -1)
228542664Smarkm                {
228642664Smarkm                  window->point = offset;
228742664Smarkm                  window_adjust_pagetop (window);
228842664Smarkm                }
228942664Smarkm            }
2290146520Sru
2291146520Sru            if (entry->line_number > 0)
2292146520Sru              /* next_line starts at line 1?  Anyway, the -1 makes it
2293146520Sru                 move to the right line.  */
2294146520Sru              info_next_line (window, entry->line_number - 1, key);
229542664Smarkm        }
229621495Sjmacd
229721495Sjmacd      free (line);
229821495Sjmacd      if (defentry)
229942664Smarkm        {
230042664Smarkm          free (defentry->label);
230142664Smarkm          maybe_free (defentry->filename);
230242664Smarkm          maybe_free (defentry->nodename);
230342664Smarkm          free (defentry);
230442664Smarkm        }
230521495Sjmacd    }
230621495Sjmacd
230721495Sjmacd  info_free_references (menu);
230821495Sjmacd
230921495Sjmacd  if (!info_error_was_printed)
231021495Sjmacd    window_clear_echo_area ();
231121495Sjmacd}
231221495Sjmacd
231321495Sjmacd/* Read a line (with completion) which is the name of a menu item,
231421495Sjmacd   and select that item. */
231542664SmarkmDECLARE_INFO_COMMAND (info_menu_item, _("Read a menu item and select its node"))
231621495Sjmacd{
231721495Sjmacd  info_menu_or_ref_item (window, count, key, info_menu_of_node, 1);
231821495Sjmacd}
231921495Sjmacd
232021495Sjmacd/* Read a line (with completion) which is the name of a reference to
232121495Sjmacd   follow, and select the node. */
232221495SjmacdDECLARE_INFO_COMMAND
232342664Smarkm  (info_xref_item, _("Read a footnote or cross reference and select its node"))
232421495Sjmacd{
232521495Sjmacd  info_menu_or_ref_item (window, count, key, info_xrefs_of_node, 1);
232621495Sjmacd}
232721495Sjmacd
232821495Sjmacd/* Position the cursor at the start of this node's menu. */
232942664SmarkmDECLARE_INFO_COMMAND (info_find_menu, _("Move to the start of this node's menu"))
233021495Sjmacd{
233121495Sjmacd  SEARCH_BINDING binding;
233221495Sjmacd  long position;
233321495Sjmacd
233421495Sjmacd  binding.buffer = window->node->contents;
233521495Sjmacd  binding.start  = 0;
233621495Sjmacd  binding.end = window->node->nodelen;
233721495Sjmacd  binding.flags = S_FoldCase | S_SkipDest;
233821495Sjmacd
233921495Sjmacd  position = search (INFO_MENU_LABEL, &binding);
234021495Sjmacd
234121495Sjmacd  if (position == -1)
2342146520Sru    info_error ((char *) msg_no_menu_node, NULL, NULL);
234321495Sjmacd  else
234421495Sjmacd    {
234521495Sjmacd      window->point = position;
234621495Sjmacd      window_adjust_pagetop (window);
234721495Sjmacd      window->flags |= W_UpdateWindow;
234821495Sjmacd    }
234921495Sjmacd}
235021495Sjmacd
235121495Sjmacd/* Visit as many menu items as is possible, each in a separate window. */
235221495SjmacdDECLARE_INFO_COMMAND (info_visit_menu,
235342664Smarkm  _("Visit as many menu items at once as possible"))
235421495Sjmacd{
235521495Sjmacd  register int i;
235621495Sjmacd  REFERENCE *entry, **menu;
235721495Sjmacd
235821495Sjmacd  menu = info_menu_of_node (window->node);
235921495Sjmacd
236021495Sjmacd  if (!menu)
2361146520Sru    info_error ((char *) msg_no_menu_node, NULL, NULL);
236221495Sjmacd
236321495Sjmacd  for (i = 0; (!info_error_was_printed) && (entry = menu[i]); i++)
236421495Sjmacd    {
236521495Sjmacd      WINDOW *new;
236621495Sjmacd
236721495Sjmacd      new = window_make_window (window->node);
236821495Sjmacd      window_tile_windows (TILE_INTERNALS);
236921495Sjmacd
237021495Sjmacd      if (!new)
2371146520Sru        info_error ((char *) msg_win_too_small, NULL, NULL);
237221495Sjmacd      else
237342664Smarkm        {
237442664Smarkm          active_window = new;
237542664Smarkm          info_select_reference (new, entry);
237642664Smarkm        }
237721495Sjmacd    }
237821495Sjmacd}
237921495Sjmacd
238021495Sjmacd/* Read a line of input which is a node name, and go to that node. */
238142664SmarkmDECLARE_INFO_COMMAND (info_goto_node, _("Read a node name and select it"))
238221495Sjmacd{
238321495Sjmacd  char *line;
238421495Sjmacd
238521495Sjmacd#define GOTO_COMPLETES
238621495Sjmacd#if defined (GOTO_COMPLETES)
238721495Sjmacd  /* Build a completion list of all of the known nodes. */
238821495Sjmacd  {
238921495Sjmacd    register int fbi, i;
239021495Sjmacd    FILE_BUFFER *current;
239121495Sjmacd    REFERENCE **items = (REFERENCE **)NULL;
239221495Sjmacd    int items_index = 0;
239321495Sjmacd    int items_slots = 0;
239421495Sjmacd
239521495Sjmacd    current = file_buffer_of_window (window);
239621495Sjmacd
239721495Sjmacd    for (fbi = 0; info_loaded_files && info_loaded_files[fbi]; fbi++)
239821495Sjmacd      {
239942664Smarkm        FILE_BUFFER *fb;
240042664Smarkm        REFERENCE *entry;
240142664Smarkm        int this_is_the_current_fb;
240221495Sjmacd
240342664Smarkm        fb = info_loaded_files[fbi];
240442664Smarkm        this_is_the_current_fb = (current == fb);
240521495Sjmacd
240642664Smarkm        entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
240742664Smarkm        entry->filename = entry->nodename = (char *)NULL;
240842664Smarkm        entry->label = (char *)xmalloc (4 + strlen (fb->filename));
240942664Smarkm        sprintf (entry->label, "(%s)*", fb->filename);
241021495Sjmacd
241142664Smarkm        add_pointer_to_array
241242664Smarkm          (entry, items_index, items, items_slots, 10, REFERENCE *);
241321495Sjmacd
241442664Smarkm        if (fb->tags)
241542664Smarkm          {
241642664Smarkm            for (i = 0; fb->tags[i]; i++)
241742664Smarkm              {
241842664Smarkm                entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
241942664Smarkm                entry->filename = entry->nodename = (char *)NULL;
242056164Sru		if (this_is_the_current_fb)
242156164Sru		  entry->label = xstrdup (fb->tags[i]->nodename);
242256164Sru		else
242356164Sru		  {
242456164Sru		    entry->label = (char *) xmalloc
242556164Sru		      (4 + strlen (fb->filename) +
242656164Sru		       strlen (fb->tags[i]->nodename));
242756164Sru		    sprintf (entry->label, "(%s)%s",
242856164Sru			     fb->filename, fb->tags[i]->nodename);
242956164Sru		  }
243021495Sjmacd
243142664Smarkm                add_pointer_to_array
243242664Smarkm                  (entry, items_index, items, items_slots, 100, REFERENCE *);
243342664Smarkm              }
243442664Smarkm          }
243521495Sjmacd      }
2436146520Sru    line = info_read_maybe_completing (window, (char *) _("Goto node: "),
2437146520Sru        items);
243821495Sjmacd    info_free_references (items);
243921495Sjmacd  }
244021495Sjmacd#else /* !GOTO_COMPLETES */
2441146520Sru  line = info_read_in_echo_area (window, (char *) _("Goto node: "));
244221495Sjmacd#endif /* !GOTO_COMPLETES */
244321495Sjmacd
244421495Sjmacd  /* If the user aborted, quit now. */
244521495Sjmacd  if (!line)
244621495Sjmacd    {
244721495Sjmacd      info_abort_key (window, 0, 0);
244821495Sjmacd      return;
244921495Sjmacd    }
245021495Sjmacd
245121495Sjmacd  canonicalize_whitespace (line);
245221495Sjmacd
245321495Sjmacd  if (*line)
245421495Sjmacd    info_parse_and_select (line, window);
245521495Sjmacd
245621495Sjmacd  free (line);
245721495Sjmacd  if (!info_error_was_printed)
245821495Sjmacd    window_clear_echo_area ();
245921495Sjmacd}
246056164Sru
246156164Sru/* Follow the menu list in MENUS (list of strings terminated by a NULL
246256164Sru   entry) from INITIAL_NODE.  If can't continue at any point (no menu or
246356164Sru   no menu entry for the next item), return the node so far -- that
246456164Sru   might be INITIAL_NODE itself.  If error, *ERRSTR and *ERRARG[12] will
246556164Sru   be set to the error message and argument for message, otherwise they
246656164Sru   will be NULL.  */
246721495Sjmacd
246856164SruNODE *
2469146520Sruinfo_follow_menus (NODE *initial_node, char **menus,
2470146520Sru    const char **errstr, char **errarg1, char **errarg2)
247156164Sru{
247256164Sru  NODE *node = NULL;
247356164Sru  *errstr = *errarg1 = *errarg2 = NULL;
247456164Sru
247556164Sru  for (; *menus; menus++)
247656164Sru    {
247756164Sru      static char *first_arg = NULL;
247856164Sru      REFERENCE **menu;
247956164Sru      REFERENCE *entry;
248056164Sru      char *arg = *menus; /* Remember the name of the menu entry we want. */
248156164Sru
248256164Sru      /* A leading space is certainly NOT part of a node name.  Most
248356164Sru	 probably, they typed a space after the separating comma.  The
248456164Sru	 strings in menus[] have their whitespace canonicalized, so
248556164Sru	 there's at most one space to ignore.  */
248656164Sru      if (*arg == ' ')
248756164Sru	arg++;
248856164Sru      if (!first_arg)
248956164Sru        first_arg = arg;
249056164Sru
249156164Sru      /* Build and return a list of the menu items in this node. */
249256164Sru      menu = info_menu_of_node (initial_node);
249356164Sru
249456164Sru      /* If no menu item in this node, stop here, but let the user
249556164Sru         continue to use Info.  Perhaps they wanted this node and didn't
249656164Sru         realize it. */
249756164Sru      if (!menu)
249856164Sru        {
249956164Sru          if (arg == first_arg)
250056164Sru            {
250156164Sru              node = make_manpage_node (first_arg);
250256164Sru              if (node)
250356164Sru                goto maybe_got_node;
250456164Sru            }
250556164Sru          *errstr = _("No menu in node `%s'.");
250656164Sru          *errarg1 = node_printed_rep (initial_node);
250756164Sru          return initial_node;
250856164Sru        }
250956164Sru
251056164Sru      /* Find the specified menu item. */
251156164Sru      entry = info_get_labeled_reference (arg, menu);
251256164Sru
251356164Sru      /* If the item wasn't found, search the list sloppily.  Perhaps this
251456164Sru         user typed "buffer" when they really meant "Buffers". */
251556164Sru      if (!entry)
251656164Sru        {
251756164Sru          int i;
251856164Sru          int best_guess = -1;
251956164Sru
252056164Sru          for (i = 0; (entry = menu[i]); i++)
252156164Sru            {
252256164Sru              if (strcasecmp (entry->label, arg) == 0)
252356164Sru                break;
252456164Sru              else
2525146520Sru                if ((best_guess == -1)
2526146520Sru                    && (strncasecmp (entry->label, arg, strlen (arg)) == 0))
252756164Sru                  best_guess = i;
252856164Sru            }
252956164Sru
253056164Sru          if (!entry && best_guess != -1)
253156164Sru            entry = menu[best_guess];
253256164Sru        }
253356164Sru
253456164Sru      /* If we still failed to find the reference, start Info with the current
253556164Sru         node anyway.  It is probably a misspelling. */
253656164Sru      if (!entry)
253756164Sru        {
253856164Sru          if (arg == first_arg)
253956164Sru            {
254093142Sru	      /* Maybe they typed "info foo" instead of "info -f foo".  */
254193142Sru	      node = info_get_node (first_arg, 0);
254293142Sru	      if (node)
254393142Sru		add_file_directory_to_path (first_arg);
254493142Sru	      else
254593142Sru		node = make_manpage_node (first_arg);
254656164Sru              if (node)
254756164Sru                goto maybe_got_node;
254856164Sru            }
254956164Sru
255056164Sru          info_free_references (menu);
255156164Sru          *errstr = _("No menu item `%s' in node `%s'.");
255256164Sru          *errarg1 = arg;
255356164Sru          *errarg2 = node_printed_rep (initial_node);
255456164Sru          return initial_node;
255556164Sru        }
255656164Sru
255756164Sru      /* We have found the reference that the user specified.  If no
255856164Sru         filename in this reference, define it. */
255956164Sru      if (!entry->filename)
256056164Sru        entry->filename = xstrdup (initial_node->parent ? initial_node->parent
256156164Sru                                                     : initial_node->filename);
256256164Sru
256356164Sru      /* Try to find this node.  */
256456164Sru      node = info_get_node (entry->filename, entry->nodename);
256556164Sru      if (!node && arg == first_arg)
256656164Sru	{
256756164Sru	  node = make_manpage_node (first_arg);
256856164Sru	  if (node)
256956164Sru	    goto maybe_got_node;
257056164Sru	}
257156164Sru
257256164Sru      /* Since we cannot find it, try using the label of the entry as a
257356164Sru         file, i.e., "(LABEL)Top".  */
257456164Sru      if (!node && entry->nodename
257556164Sru          && strcmp (entry->label, entry->nodename) == 0)
257656164Sru        node = info_get_node (entry->label, "Top");
257756164Sru
257856164Sru    maybe_got_node:
257956164Sru      if (!node)
258056164Sru        {
258156164Sru          *errstr = _("Unable to find node referenced by `%s' in `%s'.");
258256164Sru          *errarg1 = xstrdup (entry->label);
258356164Sru          *errarg2 = node_printed_rep (initial_node);
258456164Sru          info_free_references (menu);
258556164Sru          return initial_node;
258656164Sru        }
258756164Sru
258856164Sru      info_free_references (menu);
258956164Sru
259056164Sru      /* Success.  Go round the loop again.  */
259156164Sru      free (initial_node);
259256164Sru      initial_node = node;
259356164Sru    }
259456164Sru
259556164Sru  return initial_node;
259656164Sru}
259756164Sru
259856164Sru/* Split STR into individual node names by writing null bytes in wherever
259956164Sru   there are commas and constructing a list of the resulting pointers.
260056164Sru   (We can do this since STR has had canonicalize_whitespace called on it.)
260156164Sru   Return array terminated with NULL.  */
260256164Sru
260356164Srustatic char **
2604146520Srusplit_list_of_nodenames (char *str)
260556164Sru{
260656164Sru  unsigned len = 2;
260756164Sru  char **nodes = xmalloc (len * sizeof (char *));
260856164Sru
260956164Sru  nodes[len - 2] = str;
261056164Sru
261156164Sru  while (*str++)
261256164Sru    {
261356164Sru      if (*str == ',')
261456164Sru        {
261556164Sru          *str++ = 0;		/* get past the null byte */
261656164Sru          len++;
261756164Sru          nodes = xrealloc (nodes, len * sizeof (char *));
261856164Sru          nodes[len - 2] = str;
261956164Sru        }
262056164Sru    }
262156164Sru
262256164Sru  nodes[len - 1] = NULL;
262356164Sru
262456164Sru  return nodes;
262556164Sru}
262656164Sru
262756164Sru
262856164Sru/* Read a line of input which is a sequence of menus (starting from
262956164Sru   dir), and follow them.  */
263056164SruDECLARE_INFO_COMMAND (info_menu_sequence,
263156164Sru   _("Read a list of menus starting from dir and follow them"))
263256164Sru{
2633146520Sru  char *line = info_read_in_echo_area (window, (char *) _("Follow menus: "));
263456164Sru
263556164Sru  /* If the user aborted, quit now. */
263656164Sru  if (!line)
263756164Sru    {
263856164Sru      info_abort_key (window, 0, 0);
263956164Sru      return;
264056164Sru    }
264156164Sru
264256164Sru  canonicalize_whitespace (line);
264356164Sru
264456164Sru  if (*line)
264556164Sru    {
2646146520Sru      const char *errstr;
2647146520Sru      char *errarg1, *errarg2;
264856164Sru      NODE *dir_node = info_get_node (NULL, NULL);
264956164Sru      char **nodes = split_list_of_nodenames (line);
2650146520Sru      NODE *node = NULL;
265156164Sru
265256164Sru      /* If DIR_NODE is NULL, they might be reading a file directly,
265356164Sru	 like in "info -d . -f ./foo".  Try using "Top" instead.  */
265456164Sru      if (!dir_node)
265556164Sru	{
265656164Sru	  char *file_name = window->node->parent;
265756164Sru
265856164Sru	  if (!file_name)
265956164Sru	    file_name = window->node->filename;
266056164Sru	  dir_node = info_get_node (file_name, NULL);
266156164Sru	}
266256164Sru
266356164Sru      /* If we still cannot find the starting point, give up.
266456164Sru	 We cannot allow a NULL pointer inside info_follow_menus.  */
266556164Sru      if (!dir_node)
2666146520Sru	info_error ((char *) msg_cant_find_node, "Top", NULL);
266756164Sru      else
2668146520Sru	node = info_follow_menus (dir_node, nodes, &errstr, &errarg1, &errarg2);
266956164Sru
267056164Sru      free (nodes);
267156164Sru      if (!errstr)
267256164Sru        info_set_node_of_window (1, window, node);
267356164Sru      else
2674146520Sru        info_error ((char *) errstr, errarg1, errarg2);
267556164Sru    }
267656164Sru
267756164Sru  free (line);
267856164Sru  if (!info_error_was_printed)
267956164Sru    window_clear_echo_area ();
268056164Sru}
268156164Sru
268256164Sru/* Search the menu MENU for a (possibly mis-spelled) entry ARG.
268356164Sru   Return the menu entry, or the best guess for what they meant by ARG,
268456164Sru   or NULL if there's nothing in this menu seems to fit the bill.
268556164Sru   If EXACT is non-zero, allow only exact matches.  */
268656164Srustatic REFERENCE *
2687146520Sruentry_in_menu (char *arg, REFERENCE **menu, int exact)
268856164Sru{
268956164Sru  REFERENCE *entry;
269056164Sru
269156164Sru  /* First, try to find the specified menu item verbatim.  */
269256164Sru  entry = info_get_labeled_reference (arg, menu);
269356164Sru
269456164Sru  /* If the item wasn't found, search the list sloppily.  Perhaps we
269556164Sru     have "Option Summary", but ARG is "option".  */
269656164Sru  if (!entry && !exact)
269756164Sru    {
269856164Sru      int i;
269956164Sru      int best_guess = -1;
270056164Sru
270156164Sru      for (i = 0; (entry = menu[i]); i++)
270256164Sru	{
270356164Sru	  if (strcasecmp (entry->label, arg) == 0)
270456164Sru	    break;
270556164Sru	  else
270656164Sru	    if (strncasecmp (entry->label, arg, strlen (arg)) == 0)
270756164Sru	      best_guess = i;
270856164Sru	}
270956164Sru
271056164Sru      if (!entry && best_guess != -1)
271156164Sru	entry = menu[best_guess];
271256164Sru    }
271356164Sru
271456164Sru  return entry;
271556164Sru}
271656164Sru
271756164Sru/* Find the node that is the best candidate to list the PROGRAM's
271856164Sru   invocation info and its command-line options, by looking for menu
271956164Sru   items and chains of menu items with characteristic names.  */
272056164Sruvoid
2721146520Sruinfo_intuit_options_node (WINDOW *window, NODE *initial_node, char *program)
272256164Sru{
272356164Sru  /* The list of node names typical for GNU manuals where the program
272456164Sru     usage and specifically the command-line arguments are described.
272556164Sru     This is pure heuristics.  I gathered these node names by looking
272656164Sru     at all the Info files I could put my hands on.  If you are
272756164Sru     looking for evidence to complain to the GNU project about
272856164Sru     non-uniform style of documentation, here you have your case!  */
272956164Sru  static const char *invocation_nodes[] = {
273056164Sru    "%s invocation",
273156164Sru    "Invoking %s",
273256164Sru    "Preliminaries",	/* m4 has Invoking under Preliminaries! */
273356164Sru    "Invocation",
273456164Sru    "Command Arguments",/* Emacs */
273556164Sru    "Invoking `%s'",
273656164Sru    "%s options",
273756164Sru    "Options",
273856164Sru    "Option ",		/* e.g. "Option Summary" */
273956164Sru    "Invoking",
274056164Sru    "All options",	/* tar, paxutils */
274156164Sru    "Arguments",
274256164Sru    "%s cmdline",	/* ar */
274356164Sru    "%s",		/* last resort */
274456164Sru    (const char *)0
274556164Sru  };
274656164Sru  NODE *node = NULL;
274756164Sru  REFERENCE **menu;
274856164Sru  const char **try_node;
274956164Sru
275056164Sru  /* We keep looking deeper and deeper in the menu structure until
275156164Sru     there are no more menus or no menu items from the above list.
275256164Sru     Some manuals have the invocation node sitting 3 or 4 levels deep
275356164Sru     in the menu hierarchy...  */
275456164Sru  for (node = initial_node; node; initial_node = node)
275556164Sru    {
2756146520Sru      REFERENCE *entry = NULL;
275756164Sru
275856164Sru      /* Build and return a list of the menu items in this node. */
275956164Sru      menu = info_menu_of_node (initial_node);
276056164Sru
276156164Sru      /* If no menu item in this node, stop here.  Perhaps this node
276256164Sru	 is the one they need.  */
276356164Sru      if (!menu)
276456164Sru	break;
276556164Sru
276656164Sru      /* Look for node names typical for usage nodes in this menu.  */
276756164Sru      for (try_node = invocation_nodes; *try_node; try_node++)
276856164Sru	{
2769146520Sru	  char *nodename;
277056164Sru
2771146520Sru	  nodename = xmalloc (strlen (program) + strlen (*try_node));
277256164Sru	  sprintf (nodename, *try_node, program);
277356164Sru	  /* The last resort "%s" is dangerous, so we restrict it
277456164Sru             to exact matches here.  */
277556164Sru	  entry = entry_in_menu (nodename, menu,
277656164Sru				 strcmp (*try_node, "%s") == 0);
2777146520Sru	  free (nodename);
277856164Sru	  if (entry)
277956164Sru	    break;
278056164Sru	}
278156164Sru
278256164Sru      if (!entry)
278356164Sru	break;
278456164Sru
278556164Sru      if (!entry->filename)
278656164Sru	entry->filename = xstrdup (initial_node->parent ? initial_node->parent
278756164Sru				   : initial_node->filename);
278856164Sru      /* Try to find this node.  */
278956164Sru      node = info_get_node (entry->filename, entry->nodename);
279056164Sru      info_free_references (menu);
279156164Sru      if (!node)
279256164Sru	break;
279356164Sru    }
279456164Sru
279556164Sru  /* We've got our best shot at the invocation node.  Now select it.  */
279656164Sru  if (initial_node)
279756164Sru    info_set_node_of_window (1, window, initial_node);
279856164Sru  if (!info_error_was_printed)
279956164Sru    window_clear_echo_area ();
280056164Sru}
280156164Sru
280256164Sru/* Given a name of an Info file, find the name of the package it
280356164Sru   describes by removing the leading directories and extensions.  */
280456164Sruchar *
2805146520Sruprogram_name_from_file_name (char *file_name)
280656164Sru{
280756164Sru  int i;
280856164Sru  char *program_name = xstrdup (filename_non_directory (file_name));
280956164Sru
281056164Sru  for (i = strlen (program_name) - 1; i > 0; i--)
281156164Sru    if (program_name[i] == '.'
281256164Sru	&& (FILENAME_CMPN (program_name + i, ".info", 5) == 0
281356164Sru	    || FILENAME_CMPN (program_name + i, ".inf", 4) == 0
281456164Sru#ifdef __MSDOS__
281556164Sru	    || FILENAME_CMPN (program_name + i, ".i", 2) == 0
281656164Sru#endif
281756164Sru	    || isdigit (program_name[i + 1]))) /* a man page foo.1 */
281856164Sru      {
281956164Sru	program_name[i] = 0;
282056164Sru	break;
282156164Sru      }
282256164Sru  return program_name;
282356164Sru}
282456164Sru
282556164SruDECLARE_INFO_COMMAND (info_goto_invocation_node,
282656164Sru		      _("Find the node describing program invocation"))
282756164Sru{
2828116530Sru  const char *invocation_prompt = _("Find Invocation node of [%s]: ");
282956164Sru  char *program_name, *line;
283056164Sru  char *default_program_name, *prompt, *file_name;
283156164Sru  NODE *top_node;
283256164Sru
283356164Sru  /* Intuit the name of the program they are likely to want.
283456164Sru     We use the file name of the current Info file as a hint.  */
283556164Sru  file_name = window->node->parent ? window->node->parent
283656164Sru				   : window->node->filename;
283756164Sru  default_program_name = program_name_from_file_name (file_name);
283856164Sru
283956164Sru  prompt = (char *)xmalloc (strlen (default_program_name) +
284056164Sru			    strlen (invocation_prompt));
284156164Sru  sprintf (prompt, invocation_prompt, default_program_name);
284256164Sru  line = info_read_in_echo_area (window, prompt);
284356164Sru  free (prompt);
284456164Sru  if (!line)
284556164Sru    {
2846146520Sru      info_abort_key (window, 0, 0);
284756164Sru      return;
284856164Sru    }
284956164Sru  if (*line)
285056164Sru    program_name = line;
285156164Sru  else
285256164Sru    program_name = default_program_name;
285356164Sru
285456164Sru  /* In interactive usage they'd probably expect us to begin looking
285556164Sru     from the Top node.  */
285656164Sru  top_node = info_get_node (file_name, NULL);
285756164Sru  if (!top_node)
2858146520Sru    info_error ((char *) msg_cant_find_node, "Top", NULL);
285956164Sru
286056164Sru  info_intuit_options_node (window, top_node, program_name);
286156164Sru  free (line);
286256164Sru  free (default_program_name);
286356164Sru}
286456164Sru
286521495Sjmacd#if defined (HANDLE_MAN_PAGES)
286642664SmarkmDECLARE_INFO_COMMAND (info_man, _("Read a manpage reference and select it"))
286721495Sjmacd{
286821495Sjmacd  char *line;
286921495Sjmacd
2870146520Sru  line = info_read_in_echo_area (window, (char *) _("Get Manpage: "));
287121495Sjmacd
287221495Sjmacd  if (!line)
287321495Sjmacd    {
287421495Sjmacd      info_abort_key (window, 0, 0);
287521495Sjmacd      return;
287621495Sjmacd    }
287721495Sjmacd
287821495Sjmacd  canonicalize_whitespace (line);
287921495Sjmacd
288021495Sjmacd  if (*line)
288121495Sjmacd    {
288221495Sjmacd      char *goto_command;
288321495Sjmacd
288421495Sjmacd      goto_command = (char *)xmalloc
288542664Smarkm        (4 + strlen (MANPAGE_FILE_BUFFER_NAME) + strlen (line));
288621495Sjmacd
288721495Sjmacd      sprintf (goto_command, "(%s)%s", MANPAGE_FILE_BUFFER_NAME, line);
288821495Sjmacd
288921495Sjmacd      info_parse_and_select (goto_command, window);
289021495Sjmacd      free (goto_command);
289121495Sjmacd    }
289221495Sjmacd
289321495Sjmacd  free (line);
289421495Sjmacd  if (!info_error_was_printed)
289521495Sjmacd    window_clear_echo_area ();
289621495Sjmacd}
289721495Sjmacd#endif /* HANDLE_MAN_PAGES */
289821495Sjmacd
289921495Sjmacd/* Move to the "Top" node in this file. */
290042664SmarkmDECLARE_INFO_COMMAND (info_top_node, _("Select the node `Top' in this file"))
290121495Sjmacd{
290256164Sru  info_parse_and_select ("Top", window);
290321495Sjmacd}
290421495Sjmacd
290521495Sjmacd/* Move to the node "(dir)Top". */
290642664SmarkmDECLARE_INFO_COMMAND (info_dir_node, _("Select the node `(dir)'"))
290721495Sjmacd{
290821495Sjmacd  info_parse_and_select ("(dir)Top", window);
290921495Sjmacd}
291021495Sjmacd
291142664Smarkm
291242664Smarkm/* Read the name of a node to kill.  The list of available nodes comes
291342664Smarkm   from the nodes appearing in the current window configuration. */
291442664Smarkmstatic char *
2915146520Sruread_nodename_to_kill (WINDOW *window)
291621495Sjmacd{
291742664Smarkm  int iw;
291842664Smarkm  char *nodename;
291942664Smarkm  INFO_WINDOW *info_win;
292042664Smarkm  REFERENCE **menu = NULL;
292142664Smarkm  int menu_index = 0, menu_slots = 0;
292242664Smarkm  char *default_nodename = xstrdup (active_window->node->nodename);
2923146520Sru  char *prompt = xmalloc (strlen (_("Kill node (%s): ")) + strlen (default_nodename));
292421495Sjmacd
292542664Smarkm  sprintf (prompt, _("Kill node (%s): "), default_nodename);
292621495Sjmacd
292742664Smarkm  for (iw = 0; (info_win = info_windows[iw]); iw++)
292842664Smarkm    {
292942664Smarkm      REFERENCE *entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
293042664Smarkm      entry->label = xstrdup (info_win->window->node->nodename);
293142664Smarkm      entry->filename = entry->nodename = (char *)NULL;
293221495Sjmacd
293342664Smarkm      add_pointer_to_array (entry, menu_index, menu, menu_slots, 10,
293442664Smarkm                            REFERENCE *);
293542664Smarkm    }
293621495Sjmacd
293742664Smarkm  nodename = info_read_completing_in_echo_area (window, prompt, menu);
293842664Smarkm  free (prompt);
293942664Smarkm  info_free_references (menu);
294042664Smarkm  if (nodename && !*nodename)
294142664Smarkm    {
294242664Smarkm      free (nodename);
294342664Smarkm      nodename = default_nodename;
294442664Smarkm    }
294542664Smarkm  else
294642664Smarkm    free (default_nodename);
294721495Sjmacd
294842664Smarkm  return nodename;
294942664Smarkm}
295021495Sjmacd
295121495Sjmacd
295242664Smarkm/* Delete NODENAME from this window, showing the most
295342664Smarkm   recently selected node in this window. */
295442664Smarkmstatic void
2955146520Srukill_node (WINDOW *window, char *nodename)
295642664Smarkm{
295742664Smarkm  int iw, i;
295842664Smarkm  INFO_WINDOW *info_win;
295942664Smarkm  NODE *temp;
296056164Sru
296121495Sjmacd  /* If there is no nodename to kill, quit now. */
296221495Sjmacd  if (!nodename)
296321495Sjmacd    {
296421495Sjmacd      info_abort_key (window, 0, 0);
296521495Sjmacd      return;
296621495Sjmacd    }
296721495Sjmacd
296821495Sjmacd  /* If there is a nodename, find it in our window list. */
296942664Smarkm  for (iw = 0; (info_win = info_windows[iw]); iw++)
297056164Sru    if (strcmp (nodename, info_win->nodes[info_win->current]->nodename) == 0
297156164Sru	&& info_win->window == window)
297221495Sjmacd      break;
297321495Sjmacd
297421495Sjmacd  if (!info_win)
297521495Sjmacd    {
297621495Sjmacd      if (*nodename)
2977146520Sru        info_error ((char *) _("Cannot kill node `%s'"), nodename, NULL);
297821495Sjmacd      else
297942664Smarkm        window_clear_echo_area ();
298021495Sjmacd
298121495Sjmacd      return;
298221495Sjmacd    }
298321495Sjmacd
298421495Sjmacd  /* If there are no more nodes left anywhere to view, complain and exit. */
298521495Sjmacd  if (info_windows_index == 1 && info_windows[0]->nodes_index == 1)
298621495Sjmacd    {
2987146520Sru      info_error ((char *) _("Cannot kill the last node"), NULL, NULL);
298821495Sjmacd      return;
298921495Sjmacd    }
299021495Sjmacd
299142664Smarkm  /* INFO_WIN contains the node that the user wants to stop viewing.  Delete
299242664Smarkm     this node from the list of nodes previously shown in this window. */
299321495Sjmacd  for (i = info_win->current; i < info_win->nodes_index; i++)
299456164Sru    info_win->nodes[i] = info_win->nodes[i + 1];
299521495Sjmacd
299621495Sjmacd  /* There is one less node in this window's history list. */
299721495Sjmacd  info_win->nodes_index--;
299821495Sjmacd
299921495Sjmacd  /* Make this window show the most recent history node. */
300021495Sjmacd  info_win->current = info_win->nodes_index - 1;
300121495Sjmacd
300221495Sjmacd  /* If there aren't any nodes left in this window, steal one from the
300321495Sjmacd     next window. */
300421495Sjmacd  if (info_win->current < 0)
300521495Sjmacd    {
300621495Sjmacd      INFO_WINDOW *stealer;
300721495Sjmacd      int which, pagetop;
300821495Sjmacd      long point;
300921495Sjmacd
301021495Sjmacd      if (info_windows[iw + 1])
301142664Smarkm        stealer = info_windows[iw + 1];
301221495Sjmacd      else
301342664Smarkm        stealer = info_windows[0];
301421495Sjmacd
301521495Sjmacd      /* If the node being displayed in the next window is not the most
301642664Smarkm         recently loaded one, get the most recently loaded one. */
301721495Sjmacd      if ((stealer->nodes_index - 1) != stealer->current)
301842664Smarkm        which = stealer->nodes_index - 1;
301921495Sjmacd
302021495Sjmacd      /* Else, if there is another node behind the stealers current node,
302142664Smarkm         use that one. */
302221495Sjmacd      else if (stealer->current > 0)
302342664Smarkm        which = stealer->current - 1;
302421495Sjmacd
302521495Sjmacd      /* Else, just use the node appearing in STEALER's window. */
302621495Sjmacd      else
302742664Smarkm        which = stealer->current;
302821495Sjmacd
302921495Sjmacd      /* Copy this node. */
303021495Sjmacd      {
303142664Smarkm        NODE *copy = xmalloc (sizeof (NODE));
303256164Sru
303342664Smarkm        temp = stealer->nodes[which];
303442664Smarkm        point = stealer->points[which];
303542664Smarkm        pagetop = stealer->pagetops[which];
303621495Sjmacd
303742664Smarkm        copy->filename = temp->filename;
303842664Smarkm        copy->parent = temp->parent;
303942664Smarkm        copy->nodename = temp->nodename;
304042664Smarkm        copy->contents = temp->contents;
304142664Smarkm        copy->nodelen = temp->nodelen;
304242664Smarkm        copy->flags = temp->flags;
304356164Sru        copy->display_pos = temp->display_pos;
304421495Sjmacd
304542664Smarkm        temp = copy;
304621495Sjmacd      }
304721495Sjmacd
304821495Sjmacd      window_set_node_of_window (info_win->window, temp);
304921495Sjmacd      window->point = point;
305021495Sjmacd      window->pagetop = pagetop;
305121495Sjmacd      remember_window_and_node (info_win->window, temp);
305221495Sjmacd    }
305321495Sjmacd  else
305421495Sjmacd    {
305521495Sjmacd      temp = info_win->nodes[info_win->current];
305656164Sru      temp->display_pos = info_win->points[info_win->current];
305721495Sjmacd      window_set_node_of_window (info_win->window, temp);
305821495Sjmacd    }
305942664Smarkm
306021495Sjmacd  if (!info_error_was_printed)
306121495Sjmacd    window_clear_echo_area ();
306242664Smarkm
306342664Smarkm  if (auto_footnotes_p)
306442664Smarkm    info_get_or_remove_footnotes (window);
306521495Sjmacd}
306621495Sjmacd
306742664Smarkm/* Kill current node, thus going back one in the node history.  I (karl)
306842664Smarkm   do not think this is completely correct yet, because of the
306942664Smarkm   window-changing stuff in kill_node, but it's a lot better than the
307042664Smarkm   previous implementation, which did not account for nodes being
307142664Smarkm   visited twice at all.  */
307242664SmarkmDECLARE_INFO_COMMAND (info_history_node,
307342664Smarkm                      _("Select the most recently selected node"))
307442664Smarkm{
307542664Smarkm  kill_node (window, active_window->node->nodename);
307642664Smarkm}
307742664Smarkm
307842664Smarkm/* Kill named node.  */
307942664SmarkmDECLARE_INFO_COMMAND (info_kill_node, _("Kill this node"))
308042664Smarkm{
308142664Smarkm  char *nodename = read_nodename_to_kill (window);
308242664Smarkm  kill_node (window, nodename);
308342664Smarkm}
308442664Smarkm
308542664Smarkm
308621495Sjmacd/* Read the name of a file and select the entire file. */
308742664SmarkmDECLARE_INFO_COMMAND (info_view_file, _("Read the name of a file and select it"))
308821495Sjmacd{
308921495Sjmacd  char *line;
309021495Sjmacd
3091146520Sru  line = info_read_in_echo_area (window, (char *) _("Find file: "));
309221495Sjmacd  if (!line)
309321495Sjmacd    {
309421495Sjmacd      info_abort_key (active_window, 1, 0);
309521495Sjmacd      return;
309621495Sjmacd    }
309721495Sjmacd
309821495Sjmacd  if (*line)
309921495Sjmacd    {
310021495Sjmacd      NODE *node;
310121495Sjmacd
310221495Sjmacd      node = info_get_node (line, "*");
310321495Sjmacd      if (!node)
310442664Smarkm        {
310542664Smarkm          if (info_recent_file_error)
3106146520Sru            info_error (info_recent_file_error, NULL, NULL);
310742664Smarkm          else
3108146520Sru            info_error ((char *) _("Cannot find `%s'."), line, NULL);
310942664Smarkm        }
311021495Sjmacd      else
311156164Sru        info_set_node_of_window (1, window, node);
311256164Sru
311321495Sjmacd      free (line);
311421495Sjmacd    }
311521495Sjmacd
311621495Sjmacd  if (!info_error_was_printed)
311721495Sjmacd    window_clear_echo_area ();
311821495Sjmacd}
311921495Sjmacd
312021495Sjmacd/* **************************************************************** */
312142664Smarkm/*                                                                  */
312242664Smarkm/*                 Dumping and Printing Nodes                       */
312342664Smarkm/*                                                                  */
312421495Sjmacd/* **************************************************************** */
312521495Sjmacd
312621495Sjmacd#define VERBOSE_NODE_DUMPING
3127146520Srustatic void write_node_to_stream (NODE *node, FILE *stream);
3128146520Srustatic void dump_node_to_stream (char *filename, char *nodename,
3129146520Sru    FILE *stream, int dump_subnodes);
3130146520Srustatic void initialize_dumping (void);
313121495Sjmacd
313221495Sjmacd/* Dump the nodes specified by FILENAME and NODENAMES to the file named
313321495Sjmacd   in OUTPUT_FILENAME.  If DUMP_SUBNODES is non-zero, recursively dump
313421495Sjmacd   the nodes which appear in the menu of each node dumped. */
313521495Sjmacdvoid
3136146520Srudump_nodes_to_file (char *filename, char **nodenames,
3137146520Sru    char *output_filename, int dump_subnodes)
313821495Sjmacd{
313921495Sjmacd  register int i;
314021495Sjmacd  FILE *output_stream;
314121495Sjmacd
314221495Sjmacd  /* Get the stream to print the nodes to.  Special case of an output
314321495Sjmacd     filename of "-" means to dump the nodes to stdout. */
314421495Sjmacd  if (strcmp (output_filename, "-") == 0)
314521495Sjmacd    output_stream = stdout;
314621495Sjmacd  else
314721495Sjmacd    output_stream = fopen (output_filename, "w");
314821495Sjmacd
314921495Sjmacd  if (!output_stream)
315021495Sjmacd    {
3151146520Sru      info_error ((char *) _("Could not create output file `%s'."),
3152146520Sru          output_filename, NULL);
315321495Sjmacd      return;
315421495Sjmacd    }
315521495Sjmacd
315621495Sjmacd  /* Print each node to stream. */
315721495Sjmacd  initialize_dumping ();
315821495Sjmacd  for (i = 0; nodenames[i]; i++)
315921495Sjmacd    dump_node_to_stream (filename, nodenames[i], output_stream, dump_subnodes);
316021495Sjmacd
316121495Sjmacd  if (output_stream != stdout)
316221495Sjmacd    fclose (output_stream);
316321495Sjmacd
316421495Sjmacd#if defined (VERBOSE_NODE_DUMPING)
3165146520Sru  info_error ((char *) _("Done."), NULL, NULL);
316621495Sjmacd#endif /* VERBOSE_NODE_DUMPING */
316721495Sjmacd}
316821495Sjmacd
316921495Sjmacd/* A place to remember already dumped nodes. */
317021495Sjmacdstatic char **dumped_already = (char **)NULL;
317121495Sjmacdstatic int dumped_already_index = 0;
317221495Sjmacdstatic int dumped_already_slots = 0;
317321495Sjmacd
317421495Sjmacdstatic void
3175146520Sruinitialize_dumping (void)
317621495Sjmacd{
317721495Sjmacd  dumped_already_index = 0;
317821495Sjmacd}
317921495Sjmacd
318021495Sjmacd/* Get and print the node specified by FILENAME and NODENAME to STREAM.
318121495Sjmacd   If DUMP_SUBNODES is non-zero, recursively dump the nodes which appear
318221495Sjmacd   in the menu of each node dumped. */
318321495Sjmacdstatic void
3184146520Srudump_node_to_stream (char *filename, char *nodename,
3185146520Sru    FILE *stream, int dump_subnodes)
318621495Sjmacd{
318721495Sjmacd  register int i;
318821495Sjmacd  NODE *node;
318921495Sjmacd
319021495Sjmacd  node = info_get_node (filename, nodename);
319121495Sjmacd
319221495Sjmacd  if (!node)
319321495Sjmacd    {
319421495Sjmacd      if (info_recent_file_error)
3195146520Sru        info_error (info_recent_file_error, NULL, NULL);
319621495Sjmacd      else
319742664Smarkm        {
319842664Smarkm          if (filename && *nodename != '(')
3199146520Sru            info_error ((char *) msg_cant_file_node,
3200146520Sru                filename_non_directory (filename),
3201146520Sru                nodename);
320242664Smarkm          else
3203146520Sru            info_error ((char *) msg_cant_find_node, nodename, NULL);
320442664Smarkm        }
320521495Sjmacd      return;
320621495Sjmacd    }
320721495Sjmacd
320821495Sjmacd  /* If we have already dumped this node, don't dump it again. */
320921495Sjmacd  for (i = 0; i < dumped_already_index; i++)
321021495Sjmacd    if (strcmp (node->nodename, dumped_already[i]) == 0)
321121495Sjmacd      {
321242664Smarkm        free (node);
321342664Smarkm        return;
321421495Sjmacd      }
321521495Sjmacd  add_pointer_to_array (node->nodename, dumped_already_index, dumped_already,
321642664Smarkm                        dumped_already_slots, 50, char *);
321721495Sjmacd
321821495Sjmacd#if defined (VERBOSE_NODE_DUMPING)
321921495Sjmacd  /* Maybe we should print some information about the node being output. */
3220146520Sru  info_error ((char *) _("Writing node %s..."), node_printed_rep (node), NULL);
322121495Sjmacd#endif /* VERBOSE_NODE_DUMPING */
322221495Sjmacd
322321495Sjmacd  write_node_to_stream (node, stream);
322421495Sjmacd
322521495Sjmacd  /* If we are dumping subnodes, get the list of menu items in this node,
322621495Sjmacd     and dump each one recursively. */
322721495Sjmacd  if (dump_subnodes)
322821495Sjmacd    {
322921495Sjmacd      REFERENCE **menu = (REFERENCE **)NULL;
323021495Sjmacd
323121495Sjmacd      /* If this node is an Index, do not dump the menu references. */
323221495Sjmacd      if (string_in_line ("Index", node->nodename) == -1)
323342664Smarkm        menu = info_menu_of_node (node);
323421495Sjmacd
323521495Sjmacd      if (menu)
323642664Smarkm        {
323742664Smarkm          for (i = 0; menu[i]; i++)
323842664Smarkm            {
323942664Smarkm              /* We don't dump Info files which are different than the
324042664Smarkm                 current one. */
324142664Smarkm              if (!menu[i]->filename)
324242664Smarkm                dump_node_to_stream
324342664Smarkm                  (filename, menu[i]->nodename, stream, dump_subnodes);
324442664Smarkm            }
324542664Smarkm          info_free_references (menu);
324642664Smarkm        }
324721495Sjmacd    }
324821495Sjmacd
324921495Sjmacd  free (node);
325021495Sjmacd}
325121495Sjmacd
325221495Sjmacd/* Dump NODE to FILENAME.  If DUMP_SUBNODES is non-zero, recursively dump
325321495Sjmacd   the nodes which appear in the menu of each node dumped. */
325421495Sjmacdvoid
3255146520Srudump_node_to_file (NODE *node, char *filename, int dump_subnodes)
325621495Sjmacd{
325721495Sjmacd  FILE *output_stream;
325821495Sjmacd  char *nodes_filename;
325921495Sjmacd
326021495Sjmacd  /* Get the stream to print this node to.  Special case of an output
326121495Sjmacd     filename of "-" means to dump the nodes to stdout. */
326221495Sjmacd  if (strcmp (filename, "-") == 0)
326321495Sjmacd    output_stream = stdout;
326421495Sjmacd  else
326521495Sjmacd    output_stream = fopen (filename, "w");
326621495Sjmacd
326721495Sjmacd  if (!output_stream)
326821495Sjmacd    {
3269146520Sru      info_error ((char *) _("Could not create output file `%s'."), filename,
3270146520Sru          NULL);
327121495Sjmacd      return;
327221495Sjmacd    }
327321495Sjmacd
327421495Sjmacd  if (node->parent)
327521495Sjmacd    nodes_filename = node->parent;
327621495Sjmacd  else
327721495Sjmacd    nodes_filename = node->filename;
327821495Sjmacd
327921495Sjmacd  initialize_dumping ();
328021495Sjmacd  dump_node_to_stream
328121495Sjmacd    (nodes_filename, node->nodename, output_stream, dump_subnodes);
328221495Sjmacd
328321495Sjmacd  if (output_stream != stdout)
328421495Sjmacd    fclose (output_stream);
328521495Sjmacd
328621495Sjmacd#if defined (VERBOSE_NODE_DUMPING)
3287146520Sru  info_error ((char *) _("Done."), NULL, NULL);
328821495Sjmacd#endif /* VERBOSE_NODE_DUMPING */
328921495Sjmacd}
329021495Sjmacd
329121495Sjmacd#if !defined (DEFAULT_INFO_PRINT_COMMAND)
329221495Sjmacd#  define DEFAULT_INFO_PRINT_COMMAND "lpr"
329321495Sjmacd#endif /* !DEFAULT_INFO_PRINT_COMMAND */
329421495Sjmacd
329521495SjmacdDECLARE_INFO_COMMAND (info_print_node,
329642664Smarkm _("Pipe the contents of this node through INFO_PRINT_COMMAND"))
329721495Sjmacd{
329821495Sjmacd  print_node (window->node);
329921495Sjmacd}
330021495Sjmacd
330121495Sjmacd/* Print NODE on a printer piping it into INFO_PRINT_COMMAND. */
330221495Sjmacdvoid
3303146520Sruprint_node (NODE *node)
330421495Sjmacd{
330521495Sjmacd  FILE *printer_pipe;
330642664Smarkm  char *print_command = getenv ("INFO_PRINT_COMMAND");
330756164Sru  int piping = 0;
330821495Sjmacd
330921495Sjmacd  if (!print_command || !*print_command)
331021495Sjmacd    print_command = DEFAULT_INFO_PRINT_COMMAND;
331121495Sjmacd
331256164Sru  /* Note that on MS-DOS/MS-Windows, this MUST open the pipe in the
331356164Sru     (default) text mode, since the printer drivers there need to see
331456164Sru     DOS-style CRLF pairs at the end of each line.
331521495Sjmacd
331656164Sru     FIXME: if we are to support Mac-style text files, we might need
331756164Sru     to convert the text here.  */
331856164Sru
331956164Sru  /* INFO_PRINT_COMMAND which says ">file" means write to that file.
332056164Sru     Presumably, the name of the file is the local printer device.  */
332156164Sru  if (*print_command == '>')
332256164Sru    printer_pipe = fopen (++print_command, "w");
332356164Sru  else
332456164Sru    {
332556164Sru      printer_pipe = popen (print_command, "w");
332656164Sru      piping = 1;
332756164Sru    }
332856164Sru
332921495Sjmacd  if (!printer_pipe)
333021495Sjmacd    {
3331146520Sru      info_error ((char *) _("Cannot open pipe to `%s'."), print_command, NULL);
333221495Sjmacd      return;
333321495Sjmacd    }
333421495Sjmacd
333521495Sjmacd#if defined (VERBOSE_NODE_DUMPING)
333621495Sjmacd  /* Maybe we should print some information about the node being output. */
3337146520Sru  info_error ((char *) _("Printing node %s..."), node_printed_rep (node), NULL);
333821495Sjmacd#endif /* VERBOSE_NODE_DUMPING */
333921495Sjmacd
334021495Sjmacd  write_node_to_stream (node, printer_pipe);
334156164Sru  if (piping)
334256164Sru    pclose (printer_pipe);
334356164Sru  else
334456164Sru    fclose (printer_pipe);
334521495Sjmacd
334621495Sjmacd#if defined (VERBOSE_NODE_DUMPING)
3347146520Sru  info_error ((char *) _("Done."), NULL, NULL);
334821495Sjmacd#endif /* VERBOSE_NODE_DUMPING */
334921495Sjmacd}
335021495Sjmacd
335121495Sjmacdstatic void
3352146520Sruwrite_node_to_stream (NODE *node, FILE *stream)
335321495Sjmacd{
335421495Sjmacd  fwrite (node->contents, 1, node->nodelen, stream);
335521495Sjmacd}
335621495Sjmacd
335721495Sjmacd/* **************************************************************** */
335842664Smarkm/*                                                                  */
335942664Smarkm/*                    Info Searching Commands                       */
336042664Smarkm/*                                                                  */
336121495Sjmacd/* **************************************************************** */
336221495Sjmacd
336321495Sjmacd/* Variable controlling the garbage collection of files briefly visited
336421495Sjmacd   during searches.  Such files are normally gc'ed, unless they were
336521495Sjmacd   compressed to begin with.  If this variable is non-zero, it says
336621495Sjmacd   to gc even those file buffer contents which had to be uncompressed. */
336721495Sjmacdint gc_compressed_files = 0;
336821495Sjmacd
3369146520Srustatic void info_gc_file_buffers (void);
3370146520Srustatic void info_search_1 (WINDOW *window, int count,
3371146520Sru    unsigned char key, int case_sensitive, int ask_for_string);
337221495Sjmacd
337321495Sjmacdstatic char *search_string = (char *)NULL;
337421495Sjmacdstatic int search_string_size = 0;
337521495Sjmacdstatic int isearch_is_active = 0;
337621495Sjmacd
337756164Srustatic int last_search_direction = 0;
337856164Srustatic int last_search_case_sensitive = 0;
337956164Sru
338021495Sjmacd/* Return the file buffer which belongs to WINDOW's node. */
338121495SjmacdFILE_BUFFER *
3382146520Srufile_buffer_of_window (WINDOW *window)
338321495Sjmacd{
338421495Sjmacd  /* If this window has no node, then it has no file buffer. */
338521495Sjmacd  if (!window->node)
338621495Sjmacd    return ((FILE_BUFFER *)NULL);
338721495Sjmacd
338821495Sjmacd  if (window->node->parent)
338921495Sjmacd    return (info_find_file (window->node->parent));
339021495Sjmacd
339121495Sjmacd  if (window->node->filename)
339221495Sjmacd    return (info_find_file (window->node->filename));
339321495Sjmacd
339421495Sjmacd  return ((FILE_BUFFER *)NULL);
339521495Sjmacd}
339621495Sjmacd
339721495Sjmacd/* Search for STRING in NODE starting at START.  Return -1 if the string
339821495Sjmacd   was not found, or the location of the string if it was.  If WINDOW is
339921495Sjmacd   passed as non-null, set the window's node to be NODE, its point to be
340021495Sjmacd   the found string, and readjust the window's pagetop.  Final argument
340121495Sjmacd   DIR says which direction to search in.  If it is positive, search
340221495Sjmacd   forward, else backwards. */
340321495Sjmacdlong
3404146520Sruinfo_search_in_node (char *string, NODE *node, long int start,
3405146520Sru    WINDOW *window, int dir, int case_sensitive)
340621495Sjmacd{
340721495Sjmacd  SEARCH_BINDING binding;
340821495Sjmacd  long offset;
340921495Sjmacd
341021495Sjmacd  binding.buffer = node->contents;
341121495Sjmacd  binding.start = start;
341221495Sjmacd  binding.end = node->nodelen;
341356164Sru  binding.flags = 0;
341456164Sru  if (!case_sensitive)
341556164Sru    binding.flags |= S_FoldCase;
341621495Sjmacd
341721495Sjmacd  if (dir < 0)
341821495Sjmacd    {
341921495Sjmacd      binding.end = 0;
342021495Sjmacd      binding.flags |= S_SkipDest;
342121495Sjmacd    }
342221495Sjmacd
342321495Sjmacd  if (binding.start < 0)
342421495Sjmacd    return (-1);
342521495Sjmacd
342621495Sjmacd  /* For incremental searches, we always wish to skip past the string. */
342721495Sjmacd  if (isearch_is_active)
342821495Sjmacd    binding.flags |= S_SkipDest;
342921495Sjmacd
343021495Sjmacd  offset = search (string, &binding);
343121495Sjmacd
343221495Sjmacd  if (offset != -1 && window)
343321495Sjmacd    {
343421495Sjmacd      set_remembered_pagetop_and_point (window);
343521495Sjmacd      if (window->node != node)
343642664Smarkm        window_set_node_of_window (window, node);
343721495Sjmacd      window->point = offset;
343821495Sjmacd      window_adjust_pagetop (window);
343921495Sjmacd    }
344021495Sjmacd  return (offset);
344121495Sjmacd}
344221495Sjmacd
344321495Sjmacd/* Search NODE, looking for the largest possible match of STRING.  Start the
344421495Sjmacd   search at START.  Return the absolute position of the match, or -1, if
344521495Sjmacd   no part of the string could be found. */
344621495Sjmacdlong
3447146520Sruinfo_target_search_node (NODE *node, char *string, long int start)
344821495Sjmacd{
344921495Sjmacd  register int i;
3450146520Sru  long offset = 0;
345121495Sjmacd  char *target;
345221495Sjmacd
345342664Smarkm  target = xstrdup (string);
345421495Sjmacd  i = strlen (target);
345521495Sjmacd
345621495Sjmacd  /* Try repeatedly searching for this string while removing words from
345721495Sjmacd     the end of it. */
345821495Sjmacd  while (i)
345921495Sjmacd    {
346021495Sjmacd      target[i] = '\0';
346156164Sru      offset = info_search_in_node (target, node, start, (WINDOW *)NULL, 1, 0);
346221495Sjmacd
346321495Sjmacd      if (offset != -1)
346442664Smarkm        break;
346521495Sjmacd
346621495Sjmacd      /* Delete the last word from TARGET. */
346721495Sjmacd      for (; i && (!whitespace (target[i]) && (target[i] != ',')); i--);
346821495Sjmacd    }
346921495Sjmacd  free (target);
347021495Sjmacd  return (offset);
347121495Sjmacd}
347221495Sjmacd
347321495Sjmacd/* Search for STRING starting in WINDOW at point.  If the string is found
347421495Sjmacd   in this node, set point to that position.  Otherwise, get the file buffer
347521495Sjmacd   associated with WINDOW's node, and search through each node in that file.
347621495Sjmacd   If the search fails, return non-zero, else zero.  Side-effect window
347721495Sjmacd   leaving the node and point where the string was found current. */
347821495Sjmacdstatic int
3479146520Sruinfo_search_internal (char *string, WINDOW *window,
3480146520Sru    int dir, int case_sensitive)
348121495Sjmacd{
348221495Sjmacd  register int i;
348321495Sjmacd  FILE_BUFFER *file_buffer;
348421495Sjmacd  char *initial_nodename;
348521495Sjmacd  long ret, start = 0;
348621495Sjmacd
348721495Sjmacd  file_buffer = file_buffer_of_window (window);
348821495Sjmacd  initial_nodename = window->node->nodename;
348921495Sjmacd
349056164Sru  /* This used to begin from window->point, unless this was a repeated
349156164Sru     search command.  But invoking search with an argument loses with
349256164Sru     that logic, since info_last_executed_command is then set to
349356164Sru     info_add_digit_to_numeric_arg.  I think there's no sense in
349456164Sru     ``finding'' a string that is already under the cursor, anyway.  */
349556164Sru  ret = info_search_in_node
349656164Sru        (string, window->node, window->point + dir, window, dir,
349756164Sru         case_sensitive);
349821495Sjmacd
349921495Sjmacd  if (ret != -1)
350021495Sjmacd    {
350121495Sjmacd      /* We won! */
350221495Sjmacd      if (!echo_area_is_active && !isearch_is_active)
350342664Smarkm        window_clear_echo_area ();
350421495Sjmacd      return (0);
350521495Sjmacd    }
350621495Sjmacd
350721495Sjmacd  /* The string wasn't found in the current node.  Search through the
350821495Sjmacd     window's file buffer, iff the current node is not "*". */
350921495Sjmacd  if (!file_buffer || (strcmp (initial_nodename, "*") == 0))
351021495Sjmacd    return (-1);
351121495Sjmacd
351221495Sjmacd  /* If this file has tags, search through every subfile, starting at
351321495Sjmacd     this node's subfile and node.  Otherwise, search through the
351421495Sjmacd     file's node list. */
351521495Sjmacd  if (file_buffer->tags)
351621495Sjmacd    {
3517146520Sru      register int current_tag = 0, number_of_tags;
351821495Sjmacd      char *last_subfile;
351921495Sjmacd      TAG *tag;
352021495Sjmacd
352121495Sjmacd      /* Find number of tags and current tag. */
352221495Sjmacd      last_subfile = (char *)NULL;
352321495Sjmacd      for (i = 0; file_buffer->tags[i]; i++)
352442664Smarkm        if (strcmp (initial_nodename, file_buffer->tags[i]->nodename) == 0)
352542664Smarkm          {
352642664Smarkm            current_tag = i;
352742664Smarkm            last_subfile = file_buffer->tags[i]->filename;
352842664Smarkm          }
352921495Sjmacd
353021495Sjmacd      number_of_tags = i;
353121495Sjmacd
353221495Sjmacd      /* If there is no last_subfile, our tag wasn't found. */
353321495Sjmacd      if (!last_subfile)
353442664Smarkm        return (-1);
353521495Sjmacd
353621495Sjmacd      /* Search through subsequent nodes, wrapping around to the top
353742664Smarkm         of the info file until we find the string or return to this
353842664Smarkm         window's node and point. */
353921495Sjmacd      while (1)
354042664Smarkm        {
354142664Smarkm          NODE *node;
354221495Sjmacd
354342664Smarkm          /* Allow C-g to quit the search, failing it if pressed. */
354442664Smarkm          return_if_control_g (-1);
354521495Sjmacd
354656164Sru          /* Find the next tag that isn't an anchor.  */
354756164Sru          for (i = current_tag + dir; i != current_tag; i += dir)
354856164Sru            {
354956164Sru              if (i < 0)
355056164Sru                i = number_of_tags - 1;
355156164Sru              else if (i == number_of_tags)
355256164Sru                i = 0;
355321495Sjmacd
355456164Sru              tag = file_buffer->tags[i];
355556164Sru              if (tag->nodelen != 0)
355656164Sru                break;
355756164Sru            }
355821495Sjmacd
355956164Sru          /* If we got past out starting point, bail out.  */
356056164Sru          if (i == current_tag)
356156164Sru            return (-1);
356256164Sru          current_tag = i;
356321495Sjmacd
356442664Smarkm          if (!echo_area_is_active && (last_subfile != tag->filename))
356542664Smarkm            {
356642664Smarkm              window_message_in_echo_area
3567146520Sru                ((char *) _("Searching subfile %s ..."),
3568146520Sru                 filename_non_directory (tag->filename), NULL);
356921495Sjmacd
357042664Smarkm              last_subfile = tag->filename;
357142664Smarkm            }
357221495Sjmacd
357342664Smarkm          node = info_get_node (file_buffer->filename, tag->nodename);
357421495Sjmacd
357542664Smarkm          if (!node)
357642664Smarkm            {
357742664Smarkm              /* If not doing i-search... */
357842664Smarkm              if (!echo_area_is_active)
357942664Smarkm                {
358042664Smarkm                  if (info_recent_file_error)
3581146520Sru                    info_error (info_recent_file_error, NULL, NULL);
358242664Smarkm                  else
3583146520Sru                    info_error ((char *) msg_cant_file_node,
358442664Smarkm                                filename_non_directory (file_buffer->filename),
358542664Smarkm                                tag->nodename);
358642664Smarkm                }
358742664Smarkm              return (-1);
358842664Smarkm            }
358921495Sjmacd
359042664Smarkm          if (dir < 0)
359142664Smarkm            start = tag->nodelen;
359221495Sjmacd
359342664Smarkm          ret =
359456164Sru            info_search_in_node (string, node, start, window, dir,
359556164Sru                                 case_sensitive);
359621495Sjmacd
359742664Smarkm          /* Did we find the string in this node? */
359842664Smarkm          if (ret != -1)
359942664Smarkm            {
360042664Smarkm              /* Yes!  We win. */
360142664Smarkm              remember_window_and_node (window, node);
360242664Smarkm              if (!echo_area_is_active)
360342664Smarkm                window_clear_echo_area ();
360442664Smarkm              return (0);
360542664Smarkm            }
360621495Sjmacd
360742664Smarkm          /* No.  Free this node, and make sure that we haven't passed
360842664Smarkm             our starting point. */
360942664Smarkm          free (node);
361021495Sjmacd
361142664Smarkm          if (strcmp (initial_nodename, tag->nodename) == 0)
361242664Smarkm            return (-1);
361342664Smarkm        }
361421495Sjmacd    }
361521495Sjmacd  return (-1);
361621495Sjmacd}
361721495Sjmacd
361856164SruDECLARE_INFO_COMMAND (info_search_case_sensitively,
361956164Sru                      _("Read a string and search for it case-sensitively"))
362056164Sru{
362156164Sru  last_search_direction = count > 0 ? 1 : -1;
362256164Sru  last_search_case_sensitive = 1;
362356164Sru  info_search_1 (window, count, key, 1, 1);
362456164Sru}
362556164Sru
362642664SmarkmDECLARE_INFO_COMMAND (info_search, _("Read a string and search for it"))
362721495Sjmacd{
362856164Sru  last_search_direction = count > 0 ? 1 : -1;
362956164Sru  last_search_case_sensitive = 0;
363056164Sru  info_search_1 (window, count, key, 0, 1);
363156164Sru}
363256164Sru
363356164SruDECLARE_INFO_COMMAND (info_search_backward,
363456164Sru		      _("Read a string and search backward for it"))
363556164Sru{
363656164Sru  last_search_direction = count > 0 ? -1 : 1;
363756164Sru  last_search_case_sensitive = 0;
363856164Sru  info_search_1 (window, -count, key, 0, 1);
363956164Sru}
364056164Sru
364156164Srustatic void
3642146520Sruinfo_search_1 (WINDOW *window, int count, unsigned char key,
3643146520Sru    int case_sensitive, int ask_for_string)
364456164Sru{
364521495Sjmacd  char *line, *prompt;
364621495Sjmacd  int result, old_pagetop;
364721495Sjmacd  int direction;
364821495Sjmacd
364921495Sjmacd  if (count < 0)
365056164Sru    {
365156164Sru      direction = -1;
365256164Sru      count = -count;
365356164Sru    }
365421495Sjmacd  else
365556164Sru    {
365656164Sru      direction = 1;
365756164Sru      if (count == 0)
365856164Sru        count = 1;	/* for backward compatibility */
365956164Sru    }
366021495Sjmacd
366121495Sjmacd  /* Read a string from the user, defaulting the search to SEARCH_STRING. */
366221495Sjmacd  if (!search_string)
366321495Sjmacd    {
366421495Sjmacd      search_string = (char *)xmalloc (search_string_size = 100);
366521495Sjmacd      search_string[0] = '\0';
366621495Sjmacd    }
366721495Sjmacd
366856164Sru  if (ask_for_string)
366956164Sru    {
3670146520Sru      prompt = (char *)xmalloc (strlen (_("%s%sfor string [%s]: "))
3671146520Sru				+ strlen (_("Search backward"))
3672146520Sru				+ strlen (_("Search"))
3673146520Sru				+ strlen (_(" case-sensitively "))
3674146520Sru				+ strlen (_(" "))
3675146520Sru				+ strlen (search_string));
367621495Sjmacd
367756164Sru      sprintf (prompt, _("%s%sfor string [%s]: "),
367856164Sru               direction < 0 ? _("Search backward") : _("Search"),
367956164Sru               case_sensitive ? _(" case-sensitively ") : _(" "),
368056164Sru               search_string);
368121495Sjmacd
368256164Sru      line = info_read_in_echo_area (window, prompt);
368356164Sru      free (prompt);
368421495Sjmacd
368556164Sru      if (!line)
368656164Sru        {
3687146520Sru          info_abort_key (window, 0, 0);
368856164Sru          return;
368956164Sru        }
369021495Sjmacd
369156164Sru      if (*line)
369256164Sru        {
3693146520Sru          if (strlen (line) + 1 > (unsigned int) search_string_size)
369456164Sru            search_string = (char *) xrealloc
369556164Sru              (search_string, (search_string_size += 50 + strlen (line)));
369621495Sjmacd
369756164Sru          strcpy (search_string, line);
369856164Sru          free (line);
369956164Sru        }
370021495Sjmacd    }
370121495Sjmacd
370256164Sru  /* If the search string includes upper-case letters, make the search
370356164Sru     case-sensitive.  */
370456164Sru  if (case_sensitive == 0)
370556164Sru    for (line = search_string; *line; line++)
370656164Sru      if (isupper (*line))
370756164Sru        {
370856164Sru          case_sensitive = 1;
370956164Sru          break;
371056164Sru        }
371156164Sru
371221495Sjmacd  old_pagetop = active_window->pagetop;
371356164Sru  for (result = 0; result == 0 && count--; )
371456164Sru    result = info_search_internal (search_string,
371556164Sru                                   active_window, direction, case_sensitive);
371621495Sjmacd
371721495Sjmacd  if (result != 0 && !info_error_was_printed)
3718146520Sru    info_error ((char *) _("Search failed."), NULL, NULL);
371921495Sjmacd  else if (old_pagetop != active_window->pagetop)
372021495Sjmacd    {
372121495Sjmacd      int new_pagetop;
372221495Sjmacd
372321495Sjmacd      new_pagetop = active_window->pagetop;
372421495Sjmacd      active_window->pagetop = old_pagetop;
372521495Sjmacd      set_window_pagetop (active_window, new_pagetop);
372621495Sjmacd      if (auto_footnotes_p)
372742664Smarkm        info_get_or_remove_footnotes (active_window);
372821495Sjmacd    }
372921495Sjmacd
373021495Sjmacd  /* Perhaps free the unreferenced file buffers that were searched, but
373121495Sjmacd     not retained. */
373221495Sjmacd  info_gc_file_buffers ();
373321495Sjmacd}
373421495Sjmacd
373556164SruDECLARE_INFO_COMMAND (info_search_next,
373656164Sru		      _("Repeat last search in the same direction"))
373756164Sru{
373856164Sru  if (!last_search_direction)
3739146520Sru    info_error ((char *) _("No previous search string"), NULL, NULL);
374056164Sru  else
374156164Sru    info_search_1 (window, last_search_direction * count,
374256164Sru		   key, last_search_case_sensitive, 0);
374356164Sru}
374456164Sru
374556164SruDECLARE_INFO_COMMAND (info_search_previous,
374656164Sru		      _("Repeat last search in the reverse direction"))
374756164Sru{
374856164Sru  if (!last_search_direction)
3749146520Sru    info_error ((char *) _("No previous search string"), NULL, NULL);
375056164Sru  else
375156164Sru    info_search_1 (window, -last_search_direction * count,
375256164Sru		   key, last_search_case_sensitive, 0);
375356164Sru}
375456164Sru
375521495Sjmacd/* **************************************************************** */
375642664Smarkm/*                                                                  */
375742664Smarkm/*                      Incremental Searching                       */
375842664Smarkm/*                                                                  */
375921495Sjmacd/* **************************************************************** */
376021495Sjmacd
3761146520Srustatic void incremental_search (WINDOW *window, int count,
3762146520Sru    unsigned char ignore);
376321495Sjmacd
376421495SjmacdDECLARE_INFO_COMMAND (isearch_forward,
376542664Smarkm                      _("Search interactively for a string as you type it"))
376621495Sjmacd{
376721495Sjmacd  incremental_search (window, count, key);
376821495Sjmacd}
376921495Sjmacd
377021495SjmacdDECLARE_INFO_COMMAND (isearch_backward,
377142664Smarkm                      _("Search interactively for a string as you type it"))
377221495Sjmacd{
377321495Sjmacd  incremental_search (window, -count, key);
377421495Sjmacd}
377521495Sjmacd
377621495Sjmacd/* Incrementally search for a string as it is typed. */
377721495Sjmacd/* The last accepted incremental search string. */
377821495Sjmacdstatic char *last_isearch_accepted = (char *)NULL;
377921495Sjmacd
378021495Sjmacd/* The current incremental search string. */
378121495Sjmacdstatic char *isearch_string = (char *)NULL;
378221495Sjmacdstatic int isearch_string_index = 0;
378321495Sjmacdstatic int isearch_string_size = 0;
378421495Sjmacdstatic unsigned char isearch_terminate_search_key = ESC;
378521495Sjmacd
378621495Sjmacd/* Array of search states. */
378721495Sjmacdstatic SEARCH_STATE **isearch_states = (SEARCH_STATE **)NULL;
378821495Sjmacdstatic int isearch_states_index = 0;
378921495Sjmacdstatic int isearch_states_slots = 0;
379021495Sjmacd
379121495Sjmacd/* Push the state of this search. */
379221495Sjmacdstatic void
3793146520Srupush_isearch (WINDOW *window, int search_index, int direction, int failing)
379421495Sjmacd{
379521495Sjmacd  SEARCH_STATE *state;
379621495Sjmacd
379721495Sjmacd  state = (SEARCH_STATE *)xmalloc (sizeof (SEARCH_STATE));
379821495Sjmacd  window_get_state (window, state);
379921495Sjmacd  state->search_index = search_index;
380021495Sjmacd  state->direction = direction;
380121495Sjmacd  state->failing = failing;
380221495Sjmacd
380321495Sjmacd  add_pointer_to_array (state, isearch_states_index, isearch_states,
380442664Smarkm                        isearch_states_slots, 20, SEARCH_STATE *);
380521495Sjmacd}
380621495Sjmacd
380721495Sjmacd/* Pop the state of this search to WINDOW, SEARCH_INDEX, and DIRECTION. */
380821495Sjmacdstatic void
3809146520Srupop_isearch (WINDOW *window, int *search_index, int *direction, int *failing)
381021495Sjmacd{
381121495Sjmacd  SEARCH_STATE *state;
381221495Sjmacd
381321495Sjmacd  if (isearch_states_index)
381421495Sjmacd    {
381521495Sjmacd      isearch_states_index--;
381621495Sjmacd      state = isearch_states[isearch_states_index];
381721495Sjmacd      window_set_state (window, state);
381821495Sjmacd      *search_index = state->search_index;
381921495Sjmacd      *direction = state->direction;
382021495Sjmacd      *failing = state->failing;
382121495Sjmacd
382221495Sjmacd      free (state);
382321495Sjmacd      isearch_states[isearch_states_index] = (SEARCH_STATE *)NULL;
382421495Sjmacd    }
382521495Sjmacd}
382621495Sjmacd
382721495Sjmacd/* Free the memory used by isearch_states. */
382821495Sjmacdstatic void
3829146520Srufree_isearch_states (void)
383021495Sjmacd{
383121495Sjmacd  register int i;
383221495Sjmacd
383321495Sjmacd  for (i = 0; i < isearch_states_index; i++)
383421495Sjmacd    {
383521495Sjmacd      free (isearch_states[i]);
383621495Sjmacd      isearch_states[i] = (SEARCH_STATE *)NULL;
383721495Sjmacd    }
383821495Sjmacd  isearch_states_index = 0;
383921495Sjmacd}
384021495Sjmacd
384121495Sjmacd/* Display the current search in the echo area. */
384221495Sjmacdstatic void
3843146520Srushow_isearch_prompt (int dir, unsigned char *string, int failing_p)
384421495Sjmacd{
384521495Sjmacd  register int i;
3846116530Sru  const char *prefix;
3847116530Sru  char *prompt, *p_rep;
3848146520Sru  unsigned int prompt_len, p_rep_index, p_rep_size;
384921495Sjmacd
385021495Sjmacd  if (dir < 0)
385142664Smarkm    prefix = _("I-search backward: ");
385221495Sjmacd  else
385342664Smarkm    prefix = _("I-search: ");
385421495Sjmacd
385521495Sjmacd  p_rep_index = p_rep_size = 0;
385621495Sjmacd  p_rep = (char *)NULL;
385721495Sjmacd  for (i = 0; string[i]; i++)
385821495Sjmacd    {
385921495Sjmacd      char *rep;
386021495Sjmacd
386121495Sjmacd      switch (string[i])
386242664Smarkm        {
386342664Smarkm        case ' ': rep = " "; break;
386442664Smarkm        case LFD: rep = "\\n"; break;
386542664Smarkm        case TAB: rep = "\\t"; break;
386642664Smarkm        default:
386742664Smarkm          rep = pretty_keyname (string[i]);
386842664Smarkm        }
386921495Sjmacd      if ((p_rep_index + strlen (rep) + 1) >= p_rep_size)
387042664Smarkm        p_rep = (char *)xrealloc (p_rep, p_rep_size += 100);
387121495Sjmacd
387221495Sjmacd      strcpy (p_rep + p_rep_index, rep);
387321495Sjmacd      p_rep_index += strlen (rep);
387421495Sjmacd    }
387521495Sjmacd
3876116530Sru  prompt_len = strlen (prefix) + p_rep_index + 1;
3877116530Sru  if (failing_p)
3878116530Sru    prompt_len += strlen (_("Failing "));
3879116530Sru  prompt = xmalloc (prompt_len);
388042664Smarkm  sprintf (prompt, "%s%s%s", failing_p ? _("Failing ") : "", prefix,
388142664Smarkm           p_rep ? p_rep : "");
388221495Sjmacd
3883146520Sru  window_message_in_echo_area ("%s", prompt, NULL);
388421495Sjmacd  maybe_free (p_rep);
388521495Sjmacd  free (prompt);
388621495Sjmacd  display_cursor_at_point (active_window);
388721495Sjmacd}
388821495Sjmacd
388921495Sjmacdstatic void
3890146520Sruincremental_search (WINDOW *window, int count, unsigned char ignore)
389121495Sjmacd{
389221495Sjmacd  unsigned char key;
389321495Sjmacd  int last_search_result, search_result, dir;
389421495Sjmacd  SEARCH_STATE mystate, orig_state;
389556164Sru  char *p;
389656164Sru  int case_sensitive = 0;
389721495Sjmacd
389821495Sjmacd  if (count < 0)
389921495Sjmacd    dir = -1;
390021495Sjmacd  else
390121495Sjmacd    dir = 1;
390221495Sjmacd
390321495Sjmacd  last_search_result = search_result = 0;
390421495Sjmacd
390521495Sjmacd  window_get_state (window, &orig_state);
390621495Sjmacd
390721495Sjmacd  isearch_string_index = 0;
390821495Sjmacd  if (!isearch_string_size)
390921495Sjmacd    isearch_string = (char *)xmalloc (isearch_string_size = 50);
391021495Sjmacd
391121495Sjmacd  /* Show the search string in the echo area. */
391221495Sjmacd  isearch_string[isearch_string_index] = '\0';
3913146520Sru  show_isearch_prompt (dir, (unsigned char *) isearch_string, search_result);
391421495Sjmacd
391521495Sjmacd  isearch_is_active = 1;
391621495Sjmacd
391721495Sjmacd  while (isearch_is_active)
391821495Sjmacd    {
391921495Sjmacd      VFunction *func = (VFunction *)NULL;
392021495Sjmacd      int quoted = 0;
392121495Sjmacd
392221495Sjmacd      /* If a recent display was interrupted, then do the redisplay now if
392342664Smarkm         it is convenient. */
392421495Sjmacd      if (!info_any_buffered_input_p () && display_was_interrupted_p)
392542664Smarkm        {
392642664Smarkm          display_update_one_window (window);
392742664Smarkm          display_cursor_at_point (active_window);
392842664Smarkm        }
392921495Sjmacd
393021495Sjmacd      /* Read a character and dispatch on it. */
393121495Sjmacd      key = info_get_input_char ();
393221495Sjmacd      window_get_state (window, &mystate);
393321495Sjmacd
3934114477Sru      if (key == DEL || key == Control ('h'))
393542664Smarkm        {
393642664Smarkm          /* User wants to delete one level of search? */
393742664Smarkm          if (!isearch_states_index)
393842664Smarkm            {
393942664Smarkm              terminal_ring_bell ();
394042664Smarkm              continue;
394142664Smarkm            }
394242664Smarkm          else
394342664Smarkm            {
394442664Smarkm              pop_isearch
394542664Smarkm                (window, &isearch_string_index, &dir, &search_result);
394642664Smarkm              isearch_string[isearch_string_index] = '\0';
3947146520Sru              show_isearch_prompt (dir, (unsigned char *) isearch_string,
3948146520Sru                  search_result);
394942664Smarkm              goto after_search;
395042664Smarkm            }
395142664Smarkm        }
395221495Sjmacd      else if (key == Control ('q'))
395342664Smarkm        {
395442664Smarkm          key = info_get_input_char ();
395542664Smarkm          quoted = 1;
395642664Smarkm        }
395721495Sjmacd
395821495Sjmacd      /* We are about to search again, or quit.  Save the current search. */
395921495Sjmacd      push_isearch (window, isearch_string_index, dir, search_result);
396021495Sjmacd
396121495Sjmacd      if (quoted)
396242664Smarkm        goto insert_and_search;
396321495Sjmacd
396456164Sru      if (!Meta_p (key) || key > 32)
396542664Smarkm        {
3966114477Sru          /* If this key is not a keymap, get its associated function,
3967114477Sru             if any.  If it is a keymap, then it's probably ESC from an
3968114477Sru             arrow key, and we handle that case below.  */
3969114477Sru          char type = window->keymap[key].type;
3970114477Sru          func = type == ISFUNC
3971114477Sru                 ? InfoFunction(window->keymap[key].function)
3972114477Sru                 : NULL;  /* function member is a Keymap if ISKMAP */
397321495Sjmacd
3974114477Sru          if (isprint (key) || (type == ISFUNC && func == NULL))
397542664Smarkm            {
397693142Sru            insert_and_search:
397793142Sru
397893142Sru              if (isearch_string_index + 2 >= isearch_string_size)
397993142Sru                isearch_string = (char *)xrealloc
398093142Sru                  (isearch_string, isearch_string_size += 100);
398193142Sru
398293142Sru              isearch_string[isearch_string_index++] = key;
398393142Sru              isearch_string[isearch_string_index] = '\0';
398493142Sru              goto search_now;
398593142Sru            }
3986146520Sru          else if (func == (VFunction *) isearch_forward
3987146520Sru              || func == (VFunction *) isearch_backward)
398893142Sru            {
398993142Sru	      /* If this key invokes an incremental search, then this
399093142Sru		 means that we will either search again in the same
399193142Sru		 direction, search again in the reverse direction, or
399293142Sru		 insert the last search string that was accepted through
399393142Sru		 incremental searching. */
3994146520Sru              if ((func == (VFunction *) isearch_forward && dir > 0) ||
3995146520Sru                  (func == (VFunction *) isearch_backward && dir < 0))
399642664Smarkm                {
399742664Smarkm                  /* If the user has typed no characters, then insert the
399842664Smarkm                     last successful search into the current search string. */
399942664Smarkm                  if (isearch_string_index == 0)
400042664Smarkm                    {
400142664Smarkm                      /* Of course, there must be something to insert. */
400242664Smarkm                      if (last_isearch_accepted)
400342664Smarkm                        {
4004146520Sru                          if (strlen ((char *) last_isearch_accepted) + 1
4005146520Sru                              >= (unsigned int) isearch_string_size)
400642664Smarkm                            isearch_string = (char *)
400742664Smarkm                              xrealloc (isearch_string,
400842664Smarkm                                        isearch_string_size += 10 +
400942664Smarkm                                        strlen (last_isearch_accepted));
401042664Smarkm                          strcpy (isearch_string, last_isearch_accepted);
401142664Smarkm                          isearch_string_index = strlen (isearch_string);
401242664Smarkm                          goto search_now;
401342664Smarkm                        }
401442664Smarkm                      else
401542664Smarkm                        continue;
401642664Smarkm                    }
401742664Smarkm                  else
401842664Smarkm                    {
401942664Smarkm                      /* Search again in the same direction.  This means start
402042664Smarkm                         from a new place if the last search was successful. */
402142664Smarkm                      if (search_result == 0)
402242664Smarkm                        window->point += dir;
402342664Smarkm                    }
402442664Smarkm                }
402542664Smarkm              else
402642664Smarkm                {
402742664Smarkm                  /* Reverse the direction of the search. */
402842664Smarkm                  dir = -dir;
402942664Smarkm                }
403042664Smarkm            }
4031146520Sru          else if (func == (VFunction *) info_abort_key)
403242664Smarkm            {
403342664Smarkm              /* If C-g pressed, and the search is failing, pop the search
403442664Smarkm                 stack back to the last unfailed search. */
403542664Smarkm              if (isearch_states_index && (search_result != 0))
403642664Smarkm                {
403742664Smarkm                  terminal_ring_bell ();
403842664Smarkm                  while (isearch_states_index && (search_result != 0))
403942664Smarkm                    pop_isearch
404042664Smarkm                      (window, &isearch_string_index, &dir, &search_result);
404142664Smarkm                  isearch_string[isearch_string_index] = '\0';
4042146520Sru                  show_isearch_prompt (dir, (unsigned char *) isearch_string,
4043146520Sru                      search_result);
404442664Smarkm                  continue;
404542664Smarkm                }
404642664Smarkm              else
404742664Smarkm                goto exit_search;
404842664Smarkm            }
404942664Smarkm          else
405042664Smarkm            goto exit_search;
405142664Smarkm        }
405221495Sjmacd      else
405342664Smarkm        {
405442664Smarkm        exit_search:
405542664Smarkm          /* The character is not printable, or it has a function which is
405642664Smarkm             non-null.  Exit the search, remembering the search string.  If
405742664Smarkm             the key is not the same as the isearch_terminate_search_key,
405842664Smarkm             then push it into pending input. */
4059146520Sru          if (isearch_string_index && func != (VFunction *) info_abort_key)
406042664Smarkm            {
406142664Smarkm              maybe_free (last_isearch_accepted);
406242664Smarkm              last_isearch_accepted = xstrdup (isearch_string);
406342664Smarkm            }
406421495Sjmacd
406556164Sru	  /* If the key is the isearch_terminate_search_key, but some buffered
406656164Sru	     input is pending, it is almost invariably because the ESC key is
406756164Sru	     actually the beginning of an escape sequence, like in case they
406856164Sru	     pressed an arrow key.  So don't gobble the ESC key, push it back
406956164Sru	     into pending input.  */
407056164Sru	  /* FIXME: this seems like a kludge!  We need a more reliable
407156164Sru	     mechanism to know when ESC is a separate key and when it is
407256164Sru	     part of an escape sequence.  */
407393142Sru          if (key != RET  /* Emacs addicts want RET to get lost */
407493142Sru	      && (key != isearch_terminate_search_key
407593142Sru		  || info_any_buffered_input_p ()))
407642664Smarkm            info_set_pending_input (key);
407721495Sjmacd
4078146520Sru          if (func == (VFunction *) info_abort_key)
407942664Smarkm            {
408042664Smarkm              if (isearch_states_index)
408142664Smarkm                window_set_state (window, &orig_state);
408242664Smarkm            }
408321495Sjmacd
408442664Smarkm          if (!echo_area_is_active)
408542664Smarkm            window_clear_echo_area ();
408621495Sjmacd
408742664Smarkm          if (auto_footnotes_p)
408842664Smarkm            info_get_or_remove_footnotes (active_window);
408921495Sjmacd
409042664Smarkm          isearch_is_active = 0;
409142664Smarkm          continue;
409242664Smarkm        }
409321495Sjmacd
409421495Sjmacd      /* Search for the contents of isearch_string. */
409521495Sjmacd    search_now:
4096146520Sru      show_isearch_prompt (dir, (unsigned char *) isearch_string, search_result);
409721495Sjmacd
409856164Sru      /* If the search string includes upper-case letters, make the
409956164Sru         search case-sensitive.  */
410056164Sru      for (p = isearch_string; *p; p++)
410156164Sru        if (isupper (*p))
410256164Sru          {
410356164Sru            case_sensitive = 1;
410456164Sru            break;
410556164Sru          }
410656164Sru
410756164Sru
410821495Sjmacd      if (search_result == 0)
410942664Smarkm        {
411042664Smarkm          /* Check to see if the current search string is right here.  If
411142664Smarkm             we are looking at it, then don't bother calling the search
411242664Smarkm             function. */
411342664Smarkm          if (((dir < 0) &&
411456164Sru	       ((case_sensitive ? strncmp : strncasecmp)
411556164Sru                            (window->node->contents + window->point,
411642664Smarkm                             isearch_string, isearch_string_index) == 0)) ||
411742664Smarkm              ((dir > 0) &&
411842664Smarkm               ((window->point - isearch_string_index) >= 0) &&
411956164Sru	       ((case_sensitive ? strncmp : strncasecmp)
412056164Sru                            (window->node->contents +
412142664Smarkm                             (window->point - (isearch_string_index - 1)),
412242664Smarkm                             isearch_string, isearch_string_index) == 0)))
412342664Smarkm            {
412442664Smarkm              if (dir > 0)
412542664Smarkm                window->point++;
412642664Smarkm            }
412742664Smarkm          else
412856164Sru            search_result = info_search_internal (isearch_string,
412956164Sru						  window, dir, case_sensitive);
413042664Smarkm        }
413121495Sjmacd
413221495Sjmacd      /* If this search failed, and we didn't already have a failed search,
413342664Smarkm         then ring the terminal bell. */
413421495Sjmacd      if (search_result != 0 && last_search_result == 0)
413542664Smarkm        terminal_ring_bell ();
413621495Sjmacd
413721495Sjmacd    after_search:
4138146520Sru      show_isearch_prompt (dir, (unsigned char *) isearch_string, search_result);
413921495Sjmacd
414021495Sjmacd      if (search_result == 0)
414142664Smarkm        {
414242664Smarkm          if ((mystate.node == window->node) &&
414342664Smarkm              (mystate.pagetop != window->pagetop))
414442664Smarkm            {
414542664Smarkm              int newtop = window->pagetop;
414642664Smarkm              window->pagetop = mystate.pagetop;
414742664Smarkm              set_window_pagetop (window, newtop);
414842664Smarkm            }
414942664Smarkm          display_update_one_window (window);
415042664Smarkm          display_cursor_at_point (window);
415142664Smarkm        }
415221495Sjmacd
415321495Sjmacd      last_search_result = search_result;
415421495Sjmacd    }
415521495Sjmacd
415621495Sjmacd  /* Free the memory used to remember each search state. */
415721495Sjmacd  free_isearch_states ();
415821495Sjmacd
415921495Sjmacd  /* Perhaps GC some file buffers. */
416021495Sjmacd  info_gc_file_buffers ();
416121495Sjmacd
416221495Sjmacd  /* After searching, leave the window in the correct state. */
416321495Sjmacd  if (!echo_area_is_active)
416421495Sjmacd    window_clear_echo_area ();
416521495Sjmacd}
416621495Sjmacd
416721495Sjmacd/* GC some file buffers.  A file buffer can be gc-ed if there we have
416821495Sjmacd   no nodes in INFO_WINDOWS that reference this file buffer's contents.
416921495Sjmacd   Garbage collecting a file buffer means to free the file buffers
417021495Sjmacd   contents. */
417121495Sjmacdstatic void
4172146520Sruinfo_gc_file_buffers (void)
417321495Sjmacd{
417421495Sjmacd  register int fb_index, iw_index, i;
417521495Sjmacd  register FILE_BUFFER *fb;
417621495Sjmacd  register INFO_WINDOW *iw;
417721495Sjmacd
417821495Sjmacd  if (!info_loaded_files)
417921495Sjmacd    return;
418021495Sjmacd
418142664Smarkm  for (fb_index = 0; (fb = info_loaded_files[fb_index]); fb_index++)
418221495Sjmacd    {
418321495Sjmacd      int fb_referenced_p = 0;
418421495Sjmacd
418521495Sjmacd      /* If already gc-ed, do nothing. */
418621495Sjmacd      if (!fb->contents)
418742664Smarkm        continue;
418821495Sjmacd
418921495Sjmacd      /* If this file had to be uncompressed, check to see if we should
419042664Smarkm         gc it.  This means that the user-variable "gc-compressed-files"
419142664Smarkm         is non-zero. */
419221495Sjmacd      if ((fb->flags & N_IsCompressed) && !gc_compressed_files)
419342664Smarkm        continue;
419421495Sjmacd
419521495Sjmacd      /* If this file's contents are not gc-able, move on. */
419621495Sjmacd      if (fb->flags & N_CannotGC)
419742664Smarkm        continue;
419821495Sjmacd
419921495Sjmacd      /* Check each INFO_WINDOW to see if it has any nodes which reference
420042664Smarkm         this file. */
420142664Smarkm      for (iw_index = 0; (iw = info_windows[iw_index]); iw_index++)
420242664Smarkm        {
420342664Smarkm          for (i = 0; iw->nodes && iw->nodes[i]; i++)
420442664Smarkm            {
420556164Sru              if ((FILENAME_CMP (fb->fullpath, iw->nodes[i]->filename) == 0) ||
420656164Sru                  (FILENAME_CMP (fb->filename, iw->nodes[i]->filename) == 0))
420742664Smarkm                {
420842664Smarkm                  fb_referenced_p = 1;
420942664Smarkm                  break;
421042664Smarkm                }
421142664Smarkm            }
421242664Smarkm        }
421321495Sjmacd
421421495Sjmacd      /* If this file buffer wasn't referenced, free its contents. */
421521495Sjmacd      if (!fb_referenced_p)
421642664Smarkm        {
421742664Smarkm          free (fb->contents);
421842664Smarkm          fb->contents = (char *)NULL;
421942664Smarkm        }
422021495Sjmacd    }
422121495Sjmacd}
422221495Sjmacd
422321495Sjmacd/* **************************************************************** */
422442664Smarkm/*                                                                  */
422542664Smarkm/*                Traversing and Selecting References               */
422642664Smarkm/*                                                                  */
422721495Sjmacd/* **************************************************************** */
422821495Sjmacd
422921495Sjmacd/* Move to the next or previous cross reference in this node. */
423021495Sjmacdstatic void
4231146520Sruinfo_move_to_xref (WINDOW *window, int count, unsigned char key, int dir)
423221495Sjmacd{
423321495Sjmacd  long firstmenu, firstxref;
423421495Sjmacd  long nextmenu, nextxref;
423521495Sjmacd  long placement = -1;
423621495Sjmacd  long start = 0;
423721495Sjmacd  NODE *node = window->node;
423821495Sjmacd
423921495Sjmacd  if (dir < 0)
424021495Sjmacd    start = node->nodelen;
424121495Sjmacd
424221495Sjmacd  /* This search is only allowed to fail if there is no menu or cross
424321495Sjmacd     reference in the current node.  Otherwise, the first menu or xref
424421495Sjmacd     found is moved to. */
424521495Sjmacd
424621495Sjmacd  firstmenu = info_search_in_node
424756164Sru    (INFO_MENU_ENTRY_LABEL, node, start, (WINDOW *)NULL, dir, 0);
424821495Sjmacd
424921495Sjmacd  /* FIRSTMENU may point directly to the line defining the menu.  Skip that
425021495Sjmacd     and go directly to the first item. */
425121495Sjmacd
425221495Sjmacd  if (firstmenu != -1)
425321495Sjmacd    {
425421495Sjmacd      char *text = node->contents + firstmenu;
425521495Sjmacd
425621495Sjmacd      if (strncmp (text, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL)) == 0)
425742664Smarkm        firstmenu = info_search_in_node
425856164Sru          (INFO_MENU_ENTRY_LABEL, node, firstmenu + dir, (WINDOW *)NULL, dir, 0);
425921495Sjmacd    }
426021495Sjmacd
426121495Sjmacd  firstxref =
426256164Sru    info_search_in_node (INFO_XREF_LABEL, node, start, (WINDOW *)NULL, dir, 0);
426321495Sjmacd
426421495Sjmacd#if defined (HANDLE_MAN_PAGES)
426521495Sjmacd  if ((firstxref == -1) && (node->flags & N_IsManPage))
426621495Sjmacd    {
426721495Sjmacd      firstxref = locate_manpage_xref (node, start, dir);
426821495Sjmacd    }
426921495Sjmacd#endif /* HANDLE_MAN_PAGES */
427021495Sjmacd
427121495Sjmacd  if (firstmenu == -1 && firstxref == -1)
427221495Sjmacd    {
4273146520Sru      info_error ((char *) msg_no_xref_node, NULL, NULL);
427421495Sjmacd      return;
427521495Sjmacd    }
427621495Sjmacd
427721495Sjmacd  /* There is at least one cross reference or menu entry in this node.
427821495Sjmacd     Try hard to find the next available one. */
427921495Sjmacd
428021495Sjmacd  nextmenu = info_search_in_node
428156164Sru    (INFO_MENU_ENTRY_LABEL, node, window->point + dir, (WINDOW *)NULL, dir, 0);
428221495Sjmacd
428321495Sjmacd  nextxref = info_search_in_node
428456164Sru    (INFO_XREF_LABEL, node, window->point + dir, (WINDOW *)NULL, dir, 0);
428521495Sjmacd
428621495Sjmacd#if defined (HANDLE_MAN_PAGES)
428721495Sjmacd  if ((nextxref == -1) && (node->flags & N_IsManPage) && (firstxref != -1))
428821495Sjmacd    nextxref = locate_manpage_xref (node, window->point + dir, dir);
428921495Sjmacd#endif /* HANDLE_MAN_PAGES */
429021495Sjmacd
429121495Sjmacd  /* Ignore "Menu:" as a menu item. */
429221495Sjmacd  if (nextmenu != -1)
429321495Sjmacd    {
429421495Sjmacd      char *text = node->contents + nextmenu;
429521495Sjmacd
429621495Sjmacd      if (strncmp (text, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL)) == 0)
429742664Smarkm        nextmenu = info_search_in_node
429856164Sru          (INFO_MENU_ENTRY_LABEL, node, nextmenu + dir, (WINDOW *)NULL, dir, 0);
429921495Sjmacd    }
430021495Sjmacd
430121495Sjmacd  /* If there is both a next menu entry, and a next xref entry, choose the
430221495Sjmacd     one which occurs first.  Otherwise, select the one which actually
430321495Sjmacd     appears in this node following point. */
430421495Sjmacd  if (nextmenu != -1 && nextxref != -1)
430521495Sjmacd    {
430621495Sjmacd      if (((dir == 1) && (nextmenu < nextxref)) ||
430742664Smarkm          ((dir == -1) && (nextmenu > nextxref)))
430842664Smarkm        placement = nextmenu + 1;
430921495Sjmacd      else
431042664Smarkm        placement = nextxref;
431121495Sjmacd    }
431221495Sjmacd  else if (nextmenu != -1)
431321495Sjmacd    placement = nextmenu + 1;
431421495Sjmacd  else if (nextxref != -1)
431521495Sjmacd    placement = nextxref;
431621495Sjmacd
431721495Sjmacd  /* If there was neither a menu or xref entry appearing in this node after
431821495Sjmacd     point, choose the first menu or xref entry appearing in this node. */
431921495Sjmacd  if (placement == -1)
432021495Sjmacd    {
432121495Sjmacd      if (firstmenu != -1 && firstxref != -1)
432242664Smarkm        {
432342664Smarkm          if (((dir == 1) && (firstmenu < firstxref)) ||
432442664Smarkm              ((dir == -1) && (firstmenu > firstxref)))
432542664Smarkm            placement = firstmenu + 1;
432642664Smarkm          else
432742664Smarkm            placement = firstxref;
432842664Smarkm        }
432921495Sjmacd      else if (firstmenu != -1)
433042664Smarkm        placement = firstmenu + 1;
433121495Sjmacd      else
433242664Smarkm        placement = firstxref;
433321495Sjmacd    }
433421495Sjmacd  window->point = placement;
433521495Sjmacd  window_adjust_pagetop (window);
433621495Sjmacd  window->flags |= W_UpdateWindow;
433721495Sjmacd}
433821495Sjmacd
433921495SjmacdDECLARE_INFO_COMMAND (info_move_to_prev_xref,
434042664Smarkm                      _("Move to the previous cross reference"))
434121495Sjmacd{
434221495Sjmacd  if (count < 0)
434321495Sjmacd    info_move_to_prev_xref (window, -count, key);
434421495Sjmacd  else
434521495Sjmacd    info_move_to_xref (window, count, key, -1);
434621495Sjmacd}
434721495Sjmacd
434821495SjmacdDECLARE_INFO_COMMAND (info_move_to_next_xref,
434942664Smarkm                      _("Move to the next cross reference"))
435021495Sjmacd{
435121495Sjmacd  if (count < 0)
435221495Sjmacd    info_move_to_next_xref (window, -count, key);
435321495Sjmacd  else
435421495Sjmacd    info_move_to_xref (window, count, key, 1);
435521495Sjmacd}
435621495Sjmacd
435721495Sjmacd/* Select the menu item or reference that appears on this line. */
435821495SjmacdDECLARE_INFO_COMMAND (info_select_reference_this_line,
435942664Smarkm                      _("Select reference or menu item appearing on this line"))
436021495Sjmacd{
436121495Sjmacd  char *line;
436221495Sjmacd
4363146520Sru  if (window->line_starts)
4364146520Sru    line = window->line_starts[window_line_of_point (window)];
4365146520Sru  else
4366146520Sru    line = "";
436721495Sjmacd
436821495Sjmacd  /* If this line contains a menu item, select that one. */
436921495Sjmacd  if (strncmp ("* ", line, 2) == 0)
437021495Sjmacd    info_menu_or_ref_item (window, count, key, info_menu_of_node, 0);
437121495Sjmacd  else
437221495Sjmacd    info_menu_or_ref_item (window, count, key, info_xrefs_of_node, 0);
437321495Sjmacd}
437421495Sjmacd
437521495Sjmacd/* **************************************************************** */
437642664Smarkm/*                                                                  */
437742664Smarkm/*                  Miscellaneous Info Commands                     */
437842664Smarkm/*                                                                  */
437921495Sjmacd/* **************************************************************** */
438021495Sjmacd
438121495Sjmacd/* What to do when C-g is pressed in a window. */
438242664SmarkmDECLARE_INFO_COMMAND (info_abort_key, _("Cancel current operation"))
438321495Sjmacd{
438421495Sjmacd  /* If error printing doesn't oridinarily ring the bell, do it now,
438521495Sjmacd     since C-g always rings the bell.  Otherwise, let the error printer
438621495Sjmacd     do it. */
438721495Sjmacd  if (!info_error_rings_bell_p)
438821495Sjmacd    terminal_ring_bell ();
4389146520Sru  info_error ((char *) _("Quit"), NULL, NULL);
439021495Sjmacd
439121495Sjmacd  info_initialize_numeric_arg ();
439221495Sjmacd  info_clear_pending_input ();
439321495Sjmacd  info_last_executed_command = (VFunction *)NULL;
439421495Sjmacd}
439521495Sjmacd
439621495Sjmacd/* Move the cursor to the desired line of the window. */
439721495SjmacdDECLARE_INFO_COMMAND (info_move_to_window_line,
439856164Sru   _("Move the cursor to a specific line of the window"))
439921495Sjmacd{
440021495Sjmacd  int line;
440121495Sjmacd
440221495Sjmacd  /* With no numeric argument of any kind, default to the center line. */
440321495Sjmacd  if (!info_explicit_arg && count == 1)
440421495Sjmacd    line = (window->height / 2) + window->pagetop;
440521495Sjmacd  else
440621495Sjmacd    {
440721495Sjmacd      if (count < 0)
440842664Smarkm        line = (window->height + count) + window->pagetop;
440921495Sjmacd      else
441042664Smarkm        line = window->pagetop + count;
441121495Sjmacd    }
441221495Sjmacd
441321495Sjmacd  /* If the line doesn't appear in this window, make it do so. */
441421495Sjmacd  if ((line - window->pagetop) >= window->height)
441521495Sjmacd    line = window->pagetop + (window->height - 1);
441621495Sjmacd
441721495Sjmacd  /* If the line is too small, make it fit. */
441821495Sjmacd  if (line < window->pagetop)
441921495Sjmacd    line = window->pagetop;
442021495Sjmacd
442121495Sjmacd  /* If the selected line is past the bottom of the node, force it back. */
442221495Sjmacd  if (line >= window->line_count)
442321495Sjmacd    line = window->line_count - 1;
442421495Sjmacd
442521495Sjmacd  window->point = (window->line_starts[line] - window->node->contents);
442621495Sjmacd}
442721495Sjmacd
442821495Sjmacd/* Clear the screen and redraw its contents.  Given a numeric argument,
442921495Sjmacd   move the line the cursor is on to the COUNT'th line of the window. */
443042664SmarkmDECLARE_INFO_COMMAND (info_redraw_display, _("Redraw the display"))
443121495Sjmacd{
443221495Sjmacd  if ((!info_explicit_arg && count == 1) || echo_area_is_active)
443321495Sjmacd    {
443421495Sjmacd      terminal_clear_screen ();
443521495Sjmacd      display_clear_display (the_display);
443621495Sjmacd      window_mark_chain (windows, W_UpdateWindow);
443721495Sjmacd      display_update_display (windows);
443821495Sjmacd    }
443921495Sjmacd  else
444021495Sjmacd    {
444121495Sjmacd      int desired_line, point_line;
444221495Sjmacd      int new_pagetop;
444321495Sjmacd
444421495Sjmacd      point_line = window_line_of_point (window) - window->pagetop;
444521495Sjmacd
444621495Sjmacd      if (count < 0)
444742664Smarkm        desired_line = window->height + count;
444821495Sjmacd      else
444942664Smarkm        desired_line = count;
445021495Sjmacd
445121495Sjmacd      if (desired_line < 0)
445242664Smarkm        desired_line = 0;
445321495Sjmacd
445421495Sjmacd      if (desired_line >= window->height)
445542664Smarkm        desired_line = window->height - 1;
445621495Sjmacd
445721495Sjmacd      if (desired_line == point_line)
445842664Smarkm        return;
445921495Sjmacd
446021495Sjmacd      new_pagetop = window->pagetop + (point_line - desired_line);
446121495Sjmacd
446221495Sjmacd      set_window_pagetop (window, new_pagetop);
446321495Sjmacd    }
446421495Sjmacd}
446521495Sjmacd/* This command does nothing.  It is the fact that a key is bound to it
446621495Sjmacd   that has meaning.  See the code at the top of info_session (). */
446742664SmarkmDECLARE_INFO_COMMAND (info_quit, _("Quit using Info"))
446821495Sjmacd{}
446921495Sjmacd
447021495Sjmacd
447121495Sjmacd/* **************************************************************** */
447242664Smarkm/*                                                                  */
447342664Smarkm/*               Reading Keys and Dispatching on Them               */
447442664Smarkm/*                                                                  */
447521495Sjmacd/* **************************************************************** */
447621495Sjmacd
447793142Sru/* Declaration only.  Special cased in info_dispatch_on_key ().
447893142Sru   Doc string is to avoid ugly results with describe_key etc.  */
447993142SruDECLARE_INFO_COMMAND (info_do_lowercase_version,
448093142Sru		      _("Run command bound to this key's lowercase variant"))
448121495Sjmacd{}
448221495Sjmacd
448321495Sjmacdstatic void
4484146520Srudispatch_error (char *keyseq)
448521495Sjmacd{
448621495Sjmacd  char *rep;
448721495Sjmacd
448821495Sjmacd  rep = pretty_keyseq (keyseq);
448921495Sjmacd
449021495Sjmacd  if (!echo_area_is_active)
4491146520Sru    info_error ((char *) _("Unknown command (%s)."), rep, NULL);
449221495Sjmacd  else
449321495Sjmacd    {
4494146520Sru      char *temp = xmalloc (1 + strlen (rep) + strlen (_("\"%s\" is invalid")));
4495146520Sru      sprintf (temp, _("`%s' is invalid"), rep);
449621495Sjmacd      terminal_ring_bell ();
449721495Sjmacd      inform_in_echo_area (temp);
449821495Sjmacd      free (temp);
449921495Sjmacd    }
450021495Sjmacd}
450121495Sjmacd
450221495Sjmacd/* Keeping track of key sequences. */
450321495Sjmacdstatic char *info_keyseq = (char *)NULL;
450421495Sjmacdstatic int info_keyseq_index = 0;
450521495Sjmacdstatic int info_keyseq_size = 0;
450621495Sjmacdstatic int info_keyseq_displayed_p = 0;
450721495Sjmacd
450821495Sjmacd/* Initialize the length of the current key sequence. */
450921495Sjmacdvoid
4510146520Sruinitialize_keyseq (void)
451121495Sjmacd{
451221495Sjmacd  info_keyseq_index = 0;
451321495Sjmacd  info_keyseq_displayed_p = 0;
451421495Sjmacd}
451521495Sjmacd
451621495Sjmacd/* Add CHARACTER to the current key sequence. */
451721495Sjmacdvoid
4518146520Sruadd_char_to_keyseq (char character)
451921495Sjmacd{
452021495Sjmacd  if (info_keyseq_index + 2 >= info_keyseq_size)
452121495Sjmacd    info_keyseq = (char *)xrealloc (info_keyseq, info_keyseq_size += 10);
452221495Sjmacd
452321495Sjmacd  info_keyseq[info_keyseq_index++] = character;
452421495Sjmacd  info_keyseq[info_keyseq_index] = '\0';
452521495Sjmacd}
452621495Sjmacd
452721495Sjmacd/* Display the current value of info_keyseq.  If argument EXPECTING is
452821495Sjmacd   non-zero, input is expected to be read after the key sequence is
452921495Sjmacd   displayed, so add an additional prompting character to the sequence. */
4530146520Srustatic void
4531146520Srudisplay_info_keyseq (int expecting_future_input)
453221495Sjmacd{
453321495Sjmacd  char *rep;
453421495Sjmacd
453521495Sjmacd  rep = pretty_keyseq (info_keyseq);
453621495Sjmacd  if (expecting_future_input)
453721495Sjmacd    strcat (rep, "-");
453821495Sjmacd
453921495Sjmacd  if (echo_area_is_active)
454021495Sjmacd    inform_in_echo_area (rep);
454121495Sjmacd  else
454221495Sjmacd    {
4543146520Sru      window_message_in_echo_area (rep, NULL, NULL);
454421495Sjmacd      display_cursor_at_point (active_window);
454521495Sjmacd    }
454621495Sjmacd  info_keyseq_displayed_p = 1;
454721495Sjmacd}
454821495Sjmacd
454921495Sjmacd/* Called by interactive commands to read a keystroke. */
455021495Sjmacdunsigned char
4551146520Sruinfo_get_another_input_char (void)
455221495Sjmacd{
455342664Smarkm  int ready = !info_keyseq_displayed_p; /* ready if new and pending key */
455421495Sjmacd
455521495Sjmacd  /* If there isn't any input currently available, then wait a
455621495Sjmacd     moment looking for input.  If we don't get it fast enough,
455721495Sjmacd     prompt a little bit with the current key sequence. */
455842664Smarkm  if (!info_keyseq_displayed_p)
455921495Sjmacd    {
456042664Smarkm      ready = 1;
456142664Smarkm      if (!info_any_buffered_input_p () &&
456242664Smarkm          !info_input_pending_p ())
456342664Smarkm        {
456421495Sjmacd#if defined (FD_SET)
456542664Smarkm          struct timeval timer;
456642664Smarkm          fd_set readfds;
456721495Sjmacd
456842664Smarkm          FD_ZERO (&readfds);
456942664Smarkm          FD_SET (fileno (info_input_stream), &readfds);
457042664Smarkm          timer.tv_sec = 1;
457142664Smarkm          timer.tv_usec = 750;
457242664Smarkm          ready = select (fileno(info_input_stream)+1, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timer);
457342664Smarkm#else
457442664Smarkm          ready = 0;
457521495Sjmacd#endif /* FD_SET */
457642664Smarkm      }
457721495Sjmacd    }
457821495Sjmacd
457921495Sjmacd  if (!ready)
458021495Sjmacd    display_info_keyseq (1);
458121495Sjmacd
458221495Sjmacd  return (info_get_input_char ());
458321495Sjmacd}
458421495Sjmacd
458521495Sjmacd/* Do the command associated with KEY in MAP.  If the associated command is
458621495Sjmacd   really a keymap, then read another key, and dispatch into that map. */
458721495Sjmacdvoid
4588146520Sruinfo_dispatch_on_key (unsigned char key, Keymap map)
458921495Sjmacd{
459093142Sru#if !defined(INFOKEY)
459121495Sjmacd  if (Meta_p (key) && (!ISO_Latin_p || map[key].function != ea_insert))
459221495Sjmacd    {
459321495Sjmacd      if (map[ESC].type == ISKMAP)
459442664Smarkm        {
459542664Smarkm          map = (Keymap)map[ESC].function;
459642664Smarkm          add_char_to_keyseq (ESC);
459742664Smarkm          key = UnMeta (key);
459842664Smarkm          info_dispatch_on_key (key, map);
459942664Smarkm        }
460021495Sjmacd      else
460142664Smarkm        {
460242664Smarkm          dispatch_error (info_keyseq);
460342664Smarkm        }
460421495Sjmacd      return;
460521495Sjmacd    }
460693142Sru#endif /* INFOKEY */
460721495Sjmacd
460821495Sjmacd  switch (map[key].type)
460921495Sjmacd    {
461021495Sjmacd    case ISFUNC:
461121495Sjmacd      {
461242664Smarkm        VFunction *func;
461321495Sjmacd
461493142Sru        func = InfoFunction(map[key].function);
461542664Smarkm        if (func != (VFunction *)NULL)
461642664Smarkm          {
461742664Smarkm            /* Special case info_do_lowercase_version (). */
4618146520Sru            if (func == (VFunction *) info_do_lowercase_version)
461942664Smarkm              {
462093142Sru#if defined(INFOKEY)
462193142Sru		unsigned char lowerkey;
462293142Sru
462393142Sru		lowerkey = Meta_p(key) ? Meta (tolower (UnMeta (key))) : tolower (key);
462493142Sru		if (lowerkey == key)
462593142Sru		  {
462693142Sru		    add_char_to_keyseq (key);
462793142Sru		    dispatch_error (info_keyseq);
462893142Sru		    return;
462993142Sru		  }
463093142Sru                info_dispatch_on_key (lowerkey, map);
463193142Sru#else /* !INFOKEY */
463242664Smarkm                info_dispatch_on_key (tolower (key), map);
463393142Sru#endif /* INFOKEY */
463442664Smarkm                return;
463542664Smarkm              }
463621495Sjmacd
463742664Smarkm            add_char_to_keyseq (key);
463821495Sjmacd
463942664Smarkm            if (info_keyseq_displayed_p)
464042664Smarkm              display_info_keyseq (0);
464121495Sjmacd
464242664Smarkm            {
464342664Smarkm              WINDOW *where;
464421495Sjmacd
464542664Smarkm              where = active_window;
464693142Sru              (*InfoFunction(map[key].function))
464742664Smarkm                (active_window, info_numeric_arg * info_numeric_arg_sign, key);
464821495Sjmacd
464942664Smarkm              /* If we have input pending, then the last command was a prefix
465042664Smarkm                 command.  Don't change the value of the last function vars.
465142664Smarkm                 Otherwise, remember the last command executed in the var
465242664Smarkm                 appropriate to the window in which it was executed. */
465342664Smarkm              if (!info_input_pending_p ())
465442664Smarkm                {
465542664Smarkm                  if (where == the_echo_area)
465693142Sru                    ea_last_executed_command = InfoFunction(map[key].function);
465742664Smarkm                  else
465893142Sru                    info_last_executed_command = InfoFunction(map[key].function);
465942664Smarkm                }
466042664Smarkm            }
466142664Smarkm          }
466242664Smarkm        else
466342664Smarkm          {
466442664Smarkm            add_char_to_keyseq (key);
466542664Smarkm            dispatch_error (info_keyseq);
466642664Smarkm            return;
466742664Smarkm          }
466821495Sjmacd      }
466921495Sjmacd      break;
467021495Sjmacd
467121495Sjmacd    case ISKMAP:
467221495Sjmacd      add_char_to_keyseq (key);
467393142Sru      if (map[key].function != (InfoCommand *)NULL)
467442664Smarkm        {
467542664Smarkm          unsigned char newkey;
467621495Sjmacd
467742664Smarkm          newkey = info_get_another_input_char ();
467842664Smarkm          info_dispatch_on_key (newkey, (Keymap)map[key].function);
467942664Smarkm        }
468021495Sjmacd      else
468142664Smarkm        {
468242664Smarkm          dispatch_error (info_keyseq);
468342664Smarkm          return;
468442664Smarkm        }
468521495Sjmacd      break;
468621495Sjmacd    }
468721495Sjmacd}
468821495Sjmacd
468921495Sjmacd/* **************************************************************** */
469042664Smarkm/*                                                                  */
469142664Smarkm/*                      Numeric Arguments                           */
469242664Smarkm/*                                                                  */
469321495Sjmacd/* **************************************************************** */
469421495Sjmacd
469521495Sjmacd/* Handle C-u style numeric args, as well as M--, and M-digits. */
469621495Sjmacd
469721495Sjmacd/* Non-zero means that an explicit argument has been passed to this
469821495Sjmacd   command, as in C-u C-v. */
469921495Sjmacdint info_explicit_arg = 0;
470021495Sjmacd
470121495Sjmacd/* The sign of the numeric argument. */
470221495Sjmacdint info_numeric_arg_sign = 1;
470321495Sjmacd
470421495Sjmacd/* The value of the argument itself. */
470521495Sjmacdint info_numeric_arg = 1;
470621495Sjmacd
470721495Sjmacd/* Add the current digit to the argument in progress. */
470821495SjmacdDECLARE_INFO_COMMAND (info_add_digit_to_numeric_arg,
470942664Smarkm                      _("Add this digit to the current numeric argument"))
471021495Sjmacd{
471121495Sjmacd  info_numeric_arg_digit_loop (window, 0, key);
471221495Sjmacd}
471321495Sjmacd
471421495Sjmacd/* C-u, universal argument.  Multiply the current argument by 4.
471521495Sjmacd   Read a key.  If the key has nothing to do with arguments, then
471621495Sjmacd   dispatch on it.  If the key is the abort character then abort. */
471721495SjmacdDECLARE_INFO_COMMAND (info_universal_argument,
471842664Smarkm                      _("Start (or multiply by 4) the current numeric argument"))
471921495Sjmacd{
472021495Sjmacd  info_numeric_arg *= 4;
472121495Sjmacd  info_numeric_arg_digit_loop (window, 0, 0);
472221495Sjmacd}
472321495Sjmacd
472421495Sjmacd/* Create a default argument. */
472521495Sjmacdvoid
4726146520Sruinfo_initialize_numeric_arg (void)
472721495Sjmacd{
472821495Sjmacd  info_numeric_arg = info_numeric_arg_sign = 1;
472921495Sjmacd  info_explicit_arg = 0;
473021495Sjmacd}
473121495Sjmacd
473221495SjmacdDECLARE_INFO_COMMAND (info_numeric_arg_digit_loop,
473342664Smarkm                      _("Internally used by \\[universal-argument]"))
473421495Sjmacd{
473521495Sjmacd  unsigned char pure_key;
473621495Sjmacd  Keymap keymap = window->keymap;
473721495Sjmacd
473821495Sjmacd  while (1)
473921495Sjmacd    {
474021495Sjmacd      if (key)
474142664Smarkm        pure_key = key;
474221495Sjmacd      else
474342664Smarkm        {
474442664Smarkm          if (display_was_interrupted_p && !info_any_buffered_input_p ())
474542664Smarkm            display_update_display (windows);
474621495Sjmacd
474742664Smarkm          if (active_window != the_echo_area)
474842664Smarkm            display_cursor_at_point (active_window);
474921495Sjmacd
475042664Smarkm          pure_key = key = info_get_another_input_char ();
475121495Sjmacd
475293142Sru#if !defined(INFOKEY)
475342664Smarkm          if (Meta_p (key))
475442664Smarkm            add_char_to_keyseq (ESC);
475521495Sjmacd
475642664Smarkm          add_char_to_keyseq (UnMeta (key));
475793142Sru#else /* defined(INFOKEY) */
475893142Sru          add_char_to_keyseq (key);
475993142Sru#endif /* defined(INFOKEY) */
476042664Smarkm        }
476121495Sjmacd
476293142Sru#if !defined(INFOKEY)
476321495Sjmacd      if (Meta_p (key))
476442664Smarkm        key = UnMeta (key);
476593142Sru#endif /* !defined(INFOKEY) */
476621495Sjmacd
4767146520Sru      if (keymap[key].type == ISFUNC
4768146520Sru          && InfoFunction(keymap[key].function)
4769146520Sru              == (VFunction *) info_universal_argument)
477042664Smarkm        {
477142664Smarkm          info_numeric_arg *= 4;
477242664Smarkm          key = 0;
477342664Smarkm          continue;
477442664Smarkm        }
477521495Sjmacd
477693142Sru#if defined(INFOKEY)
477793142Sru      if (Meta_p (key))
477893142Sru        key = UnMeta (key);
477993142Sru#endif /* !defined(INFOKEY) */
478093142Sru
478193142Sru
478221495Sjmacd      if (isdigit (key))
478342664Smarkm        {
478442664Smarkm          if (info_explicit_arg)
478542664Smarkm            info_numeric_arg = (info_numeric_arg * 10) + (key - '0');
478642664Smarkm          else
478742664Smarkm            info_numeric_arg = (key - '0');
478842664Smarkm          info_explicit_arg = 1;
478942664Smarkm        }
479021495Sjmacd      else
479142664Smarkm        {
479242664Smarkm          if (key == '-' && !info_explicit_arg)
479342664Smarkm            {
479442664Smarkm              info_numeric_arg_sign = -1;
479542664Smarkm              info_numeric_arg = 1;
479642664Smarkm            }
479742664Smarkm          else
479842664Smarkm            {
479942664Smarkm              info_keyseq_index--;
480042664Smarkm              info_dispatch_on_key (pure_key, keymap);
480142664Smarkm              return;
480242664Smarkm            }
480342664Smarkm        }
480421495Sjmacd      key = 0;
480521495Sjmacd    }
480621495Sjmacd}
480721495Sjmacd
480821495Sjmacd/* **************************************************************** */
480942664Smarkm/*                                                                  */
481042664Smarkm/*                      Input Character Buffering                   */
481142664Smarkm/*                                                                  */
481221495Sjmacd/* **************************************************************** */
481321495Sjmacd
481421495Sjmacd/* Character waiting to be read next. */
481521495Sjmacdstatic int pending_input_character = 0;
481621495Sjmacd
481721495Sjmacd/* How to make there be no pending input. */
481821495Sjmacdstatic void
4819146520Sruinfo_clear_pending_input (void)
482021495Sjmacd{
482121495Sjmacd  pending_input_character = 0;
482221495Sjmacd}
482321495Sjmacd
482421495Sjmacd/* How to set the pending input character. */
482521495Sjmacdstatic void
4826146520Sruinfo_set_pending_input (unsigned char key)
482721495Sjmacd{
482821495Sjmacd  pending_input_character = key;
482921495Sjmacd}
483021495Sjmacd
483121495Sjmacd/* How to see if there is any pending input. */
483221495Sjmacdunsigned char
4833146520Sruinfo_input_pending_p (void)
483421495Sjmacd{
483521495Sjmacd  return (pending_input_character);
483621495Sjmacd}
483721495Sjmacd
483821495Sjmacd/* Largest number of characters that we can read in advance. */
483921495Sjmacd#define MAX_INFO_INPUT_BUFFERING 512
484021495Sjmacd
484121495Sjmacdstatic int pop_index = 0, push_index = 0;
484221495Sjmacdstatic unsigned char info_input_buffer[MAX_INFO_INPUT_BUFFERING];
484321495Sjmacd
484421495Sjmacd/* Add KEY to the buffer of characters to be read. */
484521495Sjmacdstatic void
4846146520Sruinfo_push_typeahead (unsigned char key)
484721495Sjmacd{
484821495Sjmacd  /* Flush all pending input in the case of C-g pressed. */
484921495Sjmacd  if (key == Control ('g'))
485021495Sjmacd    {
485121495Sjmacd      push_index = pop_index;
485221495Sjmacd      info_set_pending_input (Control ('g'));
485321495Sjmacd    }
485421495Sjmacd  else
485521495Sjmacd    {
485621495Sjmacd      info_input_buffer[push_index++] = key;
4857146520Sru      if ((unsigned int) push_index >= sizeof (info_input_buffer))
485842664Smarkm        push_index = 0;
485921495Sjmacd    }
486021495Sjmacd}
486121495Sjmacd
486221495Sjmacd/* Return the amount of space available in INFO_INPUT_BUFFER for new chars. */
486321495Sjmacdstatic int
4864146520Sruinfo_input_buffer_space_available (void)
486521495Sjmacd{
486621495Sjmacd  if (pop_index > push_index)
486721495Sjmacd    return (pop_index - push_index);
486821495Sjmacd  else
486921495Sjmacd    return (sizeof (info_input_buffer) - (push_index - pop_index));
487021495Sjmacd}
487121495Sjmacd
487221495Sjmacd/* Get a key from the buffer of characters to be read.
487321495Sjmacd   Return the key in KEY.
487421495Sjmacd   Result is non-zero if there was a key, or 0 if there wasn't. */
487521495Sjmacdstatic int
4876146520Sruinfo_get_key_from_typeahead (unsigned char *key)
487721495Sjmacd{
487821495Sjmacd  if (push_index == pop_index)
487921495Sjmacd    return (0);
488021495Sjmacd
488121495Sjmacd  *key = info_input_buffer[pop_index++];
488221495Sjmacd
4883146520Sru  if ((unsigned int) pop_index >= sizeof (info_input_buffer))
488421495Sjmacd    pop_index = 0;
488521495Sjmacd
488621495Sjmacd  return (1);
488721495Sjmacd}
488821495Sjmacd
488921495Sjmacdint
4890146520Sruinfo_any_buffered_input_p (void)
489121495Sjmacd{
489221495Sjmacd  info_gather_typeahead ();
489321495Sjmacd  return (push_index != pop_index);
489421495Sjmacd}
489521495Sjmacd
489621495Sjmacd/* If characters are available to be read, then read them and stuff them into
489721495Sjmacd   info_input_buffer.  Otherwise, do nothing. */
489821495Sjmacdvoid
4899146520Sruinfo_gather_typeahead (void)
490021495Sjmacd{
490121495Sjmacd  register int i = 0;
490221495Sjmacd  int tty, space_avail;
490321495Sjmacd  long chars_avail;
490421495Sjmacd  unsigned char input[MAX_INFO_INPUT_BUFFERING];
490521495Sjmacd
490621495Sjmacd  tty = fileno (info_input_stream);
490721495Sjmacd  chars_avail = 0;
490821495Sjmacd
490921495Sjmacd  space_avail = info_input_buffer_space_available ();
491021495Sjmacd
491121495Sjmacd  /* If we can just find out how many characters there are to read, do so. */
491221495Sjmacd#if defined (FIONREAD)
491321495Sjmacd  {
491421495Sjmacd    ioctl (tty, FIONREAD, &chars_avail);
491521495Sjmacd
491621495Sjmacd    if (chars_avail > space_avail)
491721495Sjmacd      chars_avail = space_avail;
491821495Sjmacd
491921495Sjmacd    if (chars_avail)
492042664Smarkm      chars_avail = read (tty, &input[0], chars_avail);
492121495Sjmacd  }
492221495Sjmacd#else /* !FIONREAD */
492321495Sjmacd#  if defined (O_NDELAY)
492421495Sjmacd  {
492521495Sjmacd    int flags;
492621495Sjmacd
492721495Sjmacd    flags = fcntl (tty, F_GETFL, 0);
492821495Sjmacd
492921495Sjmacd    fcntl (tty, F_SETFL, (flags | O_NDELAY));
493021495Sjmacd      chars_avail = read (tty, &input[0], space_avail);
493121495Sjmacd    fcntl (tty, F_SETFL, flags);
493221495Sjmacd
493321495Sjmacd    if (chars_avail == -1)
493421495Sjmacd      chars_avail = 0;
493521495Sjmacd  }
493656164Sru#  else  /* !O_NDELAY */
493756164Sru#   ifdef __DJGPP__
493856164Sru  {
493956164Sru    extern long pc_term_chars_avail (void);
494056164Sru
494156164Sru    if (isatty (tty))
494256164Sru      chars_avail = pc_term_chars_avail ();
494356164Sru    else
494456164Sru      {
494556164Sru	/* We could be more accurate by calling ltell, but we have no idea
494656164Sru	   whether tty is buffered by stdio functions, and if so, how many
494756164Sru	   characters are already waiting in the buffer.  So we punt.  */
494856164Sru	struct stat st;
494956164Sru
495056164Sru	if (fstat (tty, &st) < 0)
495156164Sru	  chars_avail = 1;
495256164Sru	else
495356164Sru	  chars_avail = st.st_size;
495456164Sru      }
495556164Sru    if (chars_avail > space_avail)
495656164Sru      chars_avail = space_avail;
495756164Sru    if (chars_avail)
495856164Sru      chars_avail = read (tty, &input[0], chars_avail);
495956164Sru  }
496056164Sru#   endif/* __DJGPP__ */
496121495Sjmacd#  endif /* O_NDELAY */
496221495Sjmacd#endif /* !FIONREAD */
496321495Sjmacd
496421495Sjmacd  while (i < chars_avail)
496521495Sjmacd    {
496621495Sjmacd      info_push_typeahead (input[i]);
496721495Sjmacd      i++;
496821495Sjmacd    }
496921495Sjmacd}
497021495Sjmacd
497121495Sjmacd/* How to read a single character. */
497221495Sjmacdunsigned char
4973146520Sruinfo_get_input_char (void)
497421495Sjmacd{
497521495Sjmacd  unsigned char keystroke;
497621495Sjmacd
497721495Sjmacd  info_gather_typeahead ();
497821495Sjmacd
497921495Sjmacd  if (pending_input_character)
498021495Sjmacd    {
498121495Sjmacd      keystroke = pending_input_character;
498221495Sjmacd      pending_input_character = 0;
498321495Sjmacd    }
498421495Sjmacd  else if (info_get_key_from_typeahead (&keystroke) == 0)
498521495Sjmacd    {
498621495Sjmacd      int rawkey;
498742664Smarkm      unsigned char c;
498842664Smarkm      int tty = fileno (info_input_stream);
498921495Sjmacd
499042664Smarkm      /* Using stream I/O causes FIONREAD etc to fail to work
499142664Smarkm         so unless someone can find a portable way of finding
499242664Smarkm         out how many characters are currently buffered, we
499342664Smarkm         should stay with away from stream I/O.
499442664Smarkm         --Egil Kvaleberg <egilk@sn.no>, January 1997.  */
499542664Smarkm#ifdef EINTR
499642664Smarkm      /* Keep reading if we got EINTR, so that we don't just exit.
499742664Smarkm         --Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>,
499842664Smarkm         22 Dec 1997.  */
499942664Smarkm      {
500042664Smarkm        int n;
500142664Smarkm        do
500242664Smarkm	  n = read (tty, &c, 1);
500342664Smarkm        while (n == -1 && errno == EINTR);
500442664Smarkm        rawkey = n == 1 ? c : EOF;
500542664Smarkm      }
500642664Smarkm#else
500742664Smarkm      rawkey = (read (tty, &c, 1) == 1) ? c : EOF;
500842664Smarkm#endif
500942664Smarkm
501021495Sjmacd      keystroke = rawkey;
501121495Sjmacd
501221495Sjmacd      if (rawkey == EOF)
501342664Smarkm        {
501442664Smarkm          if (info_input_stream != stdin)
501542664Smarkm            {
501642664Smarkm              fclose (info_input_stream);
501742664Smarkm              info_input_stream = stdin;
501856164Sru	      tty = fileno (info_input_stream);
501942664Smarkm              display_inhibited = 0;
502042664Smarkm              display_update_display (windows);
502142664Smarkm              display_cursor_at_point (active_window);
502242664Smarkm              rawkey = (read (tty, &c, 1) == 1) ? c : EOF;
502342664Smarkm              keystroke = rawkey;
502442664Smarkm            }
502521495Sjmacd
502642664Smarkm          if (rawkey == EOF)
502742664Smarkm            {
502842664Smarkm              terminal_unprep_terminal ();
502942664Smarkm              close_dribble_file ();
503056164Sru              xexit (0);
503142664Smarkm            }
503242664Smarkm        }
503321495Sjmacd    }
503421495Sjmacd
503521495Sjmacd  if (info_dribble_file)
503621495Sjmacd    dribble (keystroke);
503721495Sjmacd
503842664Smarkm  return keystroke;
503921495Sjmacd}
5040