diff options
Diffstat (limited to 'info/session.c')
-rw-r--r-- | info/session.c | 5455 |
1 files changed, 5455 insertions, 0 deletions
diff --git a/info/session.c b/info/session.c new file mode 100644 index 0000000..6eaea61 --- /dev/null +++ b/info/session.c @@ -0,0 +1,5455 @@ +/* session.c -- user windowing interface to Info. + $Id: session.c,v 1.43 2008/06/11 17:38:33 gray Exp $ + + Copyright (C) 1993, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, + 2004, 2007, 2008 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Originally written by Brian Fox (bfox@ai.mit.edu). */ + +#include "info.h" +#include "search.h" +#include <sys/ioctl.h> + +#if defined (HAVE_SYS_TIME_H) +# include <sys/time.h> +# define HAVE_STRUCT_TIMEVAL +#endif /* HAVE_SYS_TIME_H */ + +#if defined (HANDLE_MAN_PAGES) +# include "man.h" +#endif + +static void info_clear_pending_input (void); +static void info_set_pending_input (unsigned char key); +static void info_handle_pointer (char *label, WINDOW *window); +static void display_info_keyseq (int expecting_future_input); +char *node_printed_rep (NODE *node); + +/* **************************************************************** */ +/* */ +/* Running an Info Session */ +/* */ +/* **************************************************************** */ + +/* The place that we are reading input from. */ +static FILE *info_input_stream = NULL; + +/* The last executed command. */ +VFunction *info_last_executed_command = NULL; + +/* Becomes non-zero when 'q' is typed to an Info window. */ +int quit_info_immediately = 0; + +/* Array of structures describing for each window which nodes have been + visited in that window. */ +INFO_WINDOW **info_windows = NULL; + +/* Where to add the next window, if we need to add one. */ +static int info_windows_index = 0; + +/* Number of slots allocated to `info_windows'. */ +static int info_windows_slots = 0; + +/* Whether to use regexps or not for search. */ +static int use_regex = 1; + +void remember_window_and_node (WINDOW *window, NODE *node); +void forget_window_and_nodes (WINDOW *window); +void display_startup_message_and_start (void); + +/* Begin an info session finding the nodes specified by FILENAME and NODENAMES. + For each loaded node, create a new window. Always split the largest of the + available windows. */ +void +begin_multiple_window_info_session (char *filename, char **nodenames) +{ + register int i; + WINDOW *window = NULL; + + for (i = 0; nodenames[i]; i++) + { + NODE *node; + + node = info_get_node (filename, nodenames[i]); + + if (!node) + break; + + /* If this is the first node, initialize the info session. */ + if (!window) + { + initialize_info_session (node, 1); + window = active_window; + } + else + { + /* Find the largest window in WINDOWS, and make that be the active + one. Then split it and add our window and node to the list + of remembered windows and nodes. Then tile the windows. */ + WINDOW *win, *largest = NULL; + int max_height = 0; + + for (win = windows; win; win = win->next) + if (win->height > max_height) + { + max_height = win->height; + largest = win; + } + + if (!largest) + { + display_update_display (windows); + info_error (msg_cant_find_window, NULL, NULL); + info_session (); + xexit (0); + } + + active_window = largest; + window = window_make_window (node); + if (window) + { + window_tile_windows (TILE_INTERNALS); + remember_window_and_node (window, node); + } + else + { + display_update_display (windows); + info_error (msg_win_too_small, NULL, NULL); + info_session (); + xexit (0); + } + } + } + display_startup_message_and_start (); +} + +/* Start an info session with INITIAL_NODE, and an error message in the echo + area made from FORMAT and ARG. */ +void +begin_info_session_with_error (NODE *initial_node, const char *format, + void *arg1, void *arg2) +{ + initialize_info_session (initial_node, 1); + info_error (format, arg1, arg2); + info_session (); +} + +/* Start an info session with INITIAL_NODE. */ +void +begin_info_session (NODE *initial_node) +{ + initialize_info_session (initial_node, 1); + display_startup_message_and_start (); +} + +void +display_startup_message_and_start (void) +{ + char *format; + + format = replace_in_documentation + (_("Welcome to Info version %s. Type \\[get-help-window] for help, \\[menu-item] for menu item."), + 0); + + window_message_in_echo_area (format, VERSION, NULL); + info_session (); +} + +/* Run an info session with an already initialized window and node. */ +void +info_session (void) +{ + display_update_display (windows); + info_last_executed_command = NULL; + info_read_and_dispatch (); + /* On program exit, leave the cursor at the bottom of the window, and + restore the terminal I/O. */ + terminal_goto_xy (0, screenheight - 1); + terminal_clear_to_eol (); + fflush (stdout); + terminal_unprep_terminal (); + close_dribble_file (); +} + +/* Here is a window-location dependent event loop. Called from the + functions info_session (), and from read_xxx_in_echo_area (). */ +void +info_read_and_dispatch (void) +{ + unsigned char key; + int done; + done = 0; + + while (!done && !quit_info_immediately) + { + int lk = 0; + + /* If we haven't just gone up or down a line, there is no + goal column for this window. */ + if ((info_last_executed_command != (VFunction *) info_next_line) && + (info_last_executed_command != (VFunction *) info_prev_line)) + active_window->goal_column = -1; + + if (echo_area_is_active) + { + lk = echo_area_last_command_was_kill; + echo_area_prep_read (); + } + + if (!info_any_buffered_input_p ()) + display_update_display (windows); + + display_cursor_at_point (active_window); + info_initialize_numeric_arg (); + + initialize_keyseq (); + key = info_get_input_char (); + + /* No errors yet. We just read a character, that's all. Only clear + the echo_area if it is not currently active. */ + if (!echo_area_is_active) + window_clear_echo_area (); + + info_error_was_printed = 0; + + /* Do the selected command. */ + info_dispatch_on_key (key, active_window->keymap); + + if (echo_area_is_active) + { + /* Echo area commands that do killing increment the value of + ECHO_AREA_LAST_COMMAND_WAS_KILL. Thus, if there is no + change in the value of this variable, the last command + executed was not a kill command. */ + if (lk == echo_area_last_command_was_kill) + echo_area_last_command_was_kill = 0; + + if (ea_last_executed_command == (VFunction *) ea_newline || + info_aborted_echo_area) + { + ea_last_executed_command = NULL; + done = 1; + } + + if (info_last_executed_command == (VFunction *) info_quit) + quit_info_immediately = 1; + } + else if (info_last_executed_command == (VFunction *) info_quit) + done = 1; + } +} + +/* Found in signals.c */ +extern void initialize_info_signal_handler (void ); + +/* Initialize the first info session by starting the terminal, window, + and display systems. If CLEAR_SCREEN is 0, don't clear the screen. */ +void +initialize_info_session (NODE *node, int clear_screen) +{ + char *term_name = getenv ("TERM"); + terminal_initialize_terminal (term_name); + + if (terminal_is_dumb_p) + { + if (!term_name) + term_name = "dumb"; + + info_error (msg_term_too_dumb, term_name, NULL); + xexit (1); + } + + if (clear_screen) + { + terminal_prep_terminal (); + terminal_clear_screen (); + } + + initialize_info_keymaps (); + window_initialize_windows (screenwidth, screenheight); + initialize_info_signal_handler (); + display_initialize_display (screenwidth, screenheight); + info_set_node_of_window (0, active_window, node); + + /* Tell the window system how to notify us when a window needs to be + asynchronously deleted (e.g., user resizes window very small). */ + window_deletion_notifier = (VFunction *) forget_window_and_nodes; + + /* If input has not been redirected yet, make it come from unbuffered + standard input. */ + if (!info_input_stream) + { + setbuf (stdin, NULL); + info_input_stream = stdin; + } + + info_windows_initialized_p = 1; +} + +/* Tell Info that input is coming from the file FILENAME. */ +void +info_set_input_from_file (char *filename) +{ + FILE *stream; + + /* Input may include binary characters. */ + stream = fopen (filename, FOPEN_RBIN); + + if (!stream) + return; + + if ((info_input_stream != NULL) && + (info_input_stream != stdin)) + fclose (info_input_stream); + + info_input_stream = stream; + + if (stream != stdin) + display_inhibited = 1; +} + +/* Return the INFO_WINDOW containing WINDOW, or NULL if there isn't one. */ +static INFO_WINDOW * +get_info_window_of_window (WINDOW *window) +{ + register int i; + INFO_WINDOW *info_win = NULL; + + for (i = 0; info_windows && (info_win = info_windows[i]); i++) + if (info_win->window == window) + break; + + return info_win; +} + +/* Reset the remembered pagetop and point of WINDOW to WINDOW's current + values if the window and node are the same as the current one being + displayed. */ +void +set_remembered_pagetop_and_point (WINDOW *window) +{ + INFO_WINDOW *info_win; + + info_win = get_info_window_of_window (window); + + if (!info_win) + return; + + if (info_win->nodes_index && + (info_win->nodes[info_win->current] == window->node)) + { + info_win->pagetops[info_win->current] = window->pagetop; + info_win->points[info_win->current] = window->point; + } +} + +void +remember_window_and_node (WINDOW *window, NODE *node) +{ + /* See if we already have this window in our list. */ + INFO_WINDOW *info_win = get_info_window_of_window (window); + + /* If the window wasn't already on our list, then make a new entry. */ + if (!info_win) + { + info_win = xmalloc (sizeof (INFO_WINDOW)); + info_win->window = window; + info_win->nodes = NULL; + info_win->pagetops = NULL; + info_win->points = NULL; + info_win->current = 0; + info_win->nodes_index = 0; + info_win->nodes_slots = 0; + + add_pointer_to_array (info_win, info_windows_index, info_windows, + info_windows_slots, 10, INFO_WINDOW *); + } + + /* If this node, the current pagetop, and the current point are the + same as the current saved node and pagetop, don't really add this to + the list of history nodes. This may happen only at the very + beginning of the program, I'm not sure. --karl */ + if (info_win->nodes + && info_win->current >= 0 + && info_win->nodes[info_win->current]->contents == node->contents + && info_win->pagetops[info_win->current] == window->pagetop + && info_win->points[info_win->current] == window->point) + return; + + /* Remember this node, the currently displayed pagetop, and the current + location of point in this window. Because we are updating pagetops + and points as well as nodes, it is more efficient to avoid the + add_pointer_to_array macro here. */ + if (info_win->nodes_index + 2 >= info_win->nodes_slots) + { + info_win->nodes_slots += 20; + info_win->nodes = (NODE **) xrealloc (info_win->nodes, + info_win->nodes_slots * sizeof (NODE *)); + info_win->pagetops = (int *) xrealloc (info_win->pagetops, + info_win->nodes_slots * sizeof (int)); + info_win->points = (long *) xrealloc (info_win->points, + info_win->nodes_slots * sizeof (long)); + } + + info_win->nodes[info_win->nodes_index] = node; + info_win->pagetops[info_win->nodes_index] = window->pagetop; + info_win->points[info_win->nodes_index] = window->point; + info_win->current = info_win->nodes_index++; + info_win->nodes[info_win->nodes_index] = NULL; + info_win->pagetops[info_win->nodes_index] = 0; + info_win->points[info_win->nodes_index] = 0; +} + +#define DEBUG_FORGET_WINDOW_AND_NODES +#if defined (DEBUG_FORGET_WINDOW_AND_NODES) +static void +consistency_check_info_windows (void) +{ + register int i; + + for (i = 0; i < info_windows_index; i++) + { + WINDOW *win; + + for (win = windows; win; win = win->next) + if (win == info_windows[i]->window) + break; + + if (!win) + abort (); + } +} +#endif /* DEBUG_FORGET_WINDOW_AND_NODES */ + +/* Remove WINDOW and its associated list of nodes from INFO_WINDOWS. */ +void +forget_window_and_nodes (WINDOW *window) +{ + register int i; + INFO_WINDOW *info_win = NULL; + + for (i = 0; info_windows && (info_win = info_windows[i]); i++) + if (info_win->window == window) + break; + + /* If we found the window to forget, then do so. */ + if (info_win) + { + while (i < info_windows_index) + { + info_windows[i] = info_windows[i + 1]; + i++; + } + + info_windows_index--; + info_windows[info_windows_index] = NULL; + + if (info_win->nodes) + { + /* Free the node structures which held onto internal node contents + here. This doesn't free the contents; we have a garbage collector + which does that. */ + for (i = 0; info_win->nodes[i]; i++) + if (internal_info_node_p (info_win->nodes[i])) + free (info_win->nodes[i]); + free (info_win->nodes); + + maybe_free (info_win->pagetops); + maybe_free (info_win->points); + } + + free (info_win); + } +#if defined (DEBUG_FORGET_WINDOW_AND_NODES) + consistency_check_info_windows (); +#endif /* DEBUG_FORGET_WINDOW_AND_NODES */ +} + +/* Set WINDOW to show NODE. Remember the new window in our list of Info + windows. If we are doing automatic footnote display, also try to display + the footnotes for this window. If REMEMBER is nonzero, first call + set_remembered_pagetop_and_point. */ +void +info_set_node_of_window (int remember, WINDOW *window, NODE *node) +{ + if (remember) + set_remembered_pagetop_and_point (window); + + /* Put this node into the window. */ + window_set_node_of_window (window, node); + + /* Remember this node and window in our list of info windows. */ + remember_window_and_node (window, node); + + /* If doing auto-footnote display/undisplay, show the footnotes belonging + to this window's node. */ + if (auto_footnotes_p) + info_get_or_remove_footnotes (window); +} + + +/* **************************************************************** */ +/* */ +/* Info Movement Commands */ +/* */ +/* **************************************************************** */ + +/* Change the pagetop of WINDOW to DESIRED_TOP, perhaps scrolling the screen + to do so. */ +void +set_window_pagetop (WINDOW *window, int desired_top) +{ + int point_line, old_pagetop; + + if (desired_top < 0) + desired_top = 0; + else if (desired_top > window->line_count) + desired_top = window->line_count - 1; + + if (window->pagetop == desired_top) + return; + + old_pagetop = window->pagetop; + window->pagetop = desired_top; + + /* Make sure that point appears in this window. */ + point_line = window_line_of_point (window); + if ((point_line < window->pagetop) || + ((point_line - window->pagetop) > window->height - 1)) + window->point = + window->line_starts[window->pagetop] - window->node->contents; + + window->flags |= W_UpdateWindow; + + /* Find out which direction to scroll, and scroll the window in that + direction. Do this only if there would be a savings in redisplay + time. This is true if the amount to scroll is less than the height + of the window, and if the number of lines scrolled would be greater + than 10 % of the window's height. */ + if (old_pagetop < desired_top) + { + int start, end, amount; + + amount = desired_top - old_pagetop; + + if ((amount >= window->height) || + (((window->height - amount) * 10) < window->height)) + return; + + start = amount + window->first_row; + end = window->height + window->first_row; + + display_scroll_display (start, end, -amount); + } + else + { + int start, end, amount; + + amount = old_pagetop - desired_top; + + if ((amount >= window->height) || + (((window->height - amount) * 10) < window->height)) + return; + + start = window->first_row; + end = (window->first_row + window->height) - amount; + display_scroll_display (start, end, amount); + } +} + +/* Immediately make WINDOW->point visible on the screen, and move the + terminal cursor there. */ +static void +info_show_point (WINDOW *window) +{ + int old_pagetop; + + old_pagetop = window->pagetop; + window_adjust_pagetop (window); + if (old_pagetop != window->pagetop) + { + int new_pagetop; + + new_pagetop = window->pagetop; + window->pagetop = old_pagetop; + set_window_pagetop (window, new_pagetop); + } + + if (window->flags & W_UpdateWindow) + display_update_one_window (window); + + display_cursor_at_point (window); +} + +/* Move WINDOW->point from OLD line index to NEW line index. */ +static void +move_to_new_line (int old, int new, WINDOW *window) +{ + if (old == -1) + { + info_error (msg_cant_find_point, NULL, NULL); + } + else + { + int goal; + + if (new >= window->line_count || new < 0) + return; + + goal = window_get_goal_column (window); + window->goal_column = goal; + + window->point = window->line_starts[new] - window->node->contents; + window->point += window_chars_to_goal (window, goal); + info_show_point (window); + } +} + +static int forward_move_node_structure (WINDOW *window, int behaviour); +static int backward_move_node_structure (WINDOW *window, int behaviour); + +/* Move WINDOW's point down to the next line if possible. */ +DECLARE_INFO_COMMAND (info_next_line, _("Move down to the next line")) +{ + int old_line, new_line; + + if (count < 0) + info_prev_line (window, -count, key); + else + while (count) + { + int diff; + + old_line = window_line_of_point (window); + diff = window->line_count - old_line; + if (diff > count) + diff = count; + + count -= diff; + new_line = old_line + diff; + if (new_line >= window->line_count) + { + if (cursor_movement_scrolls_p) + { + if (forward_move_node_structure (window, + info_scroll_behaviour)) + break; + move_to_new_line (0, 0, window); + } + else + break; + } + else + move_to_new_line (old_line, new_line, window); + } +} + +/* Move WINDOW's point up to the previous line if possible. */ +DECLARE_INFO_COMMAND (info_prev_line, _("Move up to the previous line")) +{ + int old_line, new_line; + + if (count < 0) + info_next_line (window, -count, key); + else + while (count) + { + int diff; + + old_line = window_line_of_point (window); + diff = old_line + 1; + if (diff > count) + diff = count; + + count -= diff; + new_line = old_line - diff; + + if (new_line < 0 + && cursor_movement_scrolls_p) + { + if (backward_move_node_structure (window, info_scroll_behaviour)) + break; + if (window->line_count > window->height) + set_window_pagetop (window, window->line_count - window->height); + move_to_new_line (window->line_count, + window->line_count - 1, window); + } + else + move_to_new_line (old_line, new_line, window); + } +} + +/* Return true if POINT sits on a newline character. */ +static int +_looking_at_newline (WINDOW *win, long point) +{ + mbi_iterator_t iter; + + mbi_init (iter, win->node->contents + point, + win->node->nodelen - point); + mbi_avail (iter); + return mbi_cur (iter).wc_valid && mbi_cur (iter).wc == '\n'; +} + +/* Advance point of WIN to the beginning of the next logical line. + Return 1 if there is no next line. */ +static int +point_next_line (WINDOW *win) +{ + int line = window_line_of_point (win); + if (line + 1 >= win->line_count) + return 1; + win->point = win->line_starts[line + 1] - win->node->contents; + window_compute_line_map (win); + return 0; +} + +/* Move point of WIN to the beginning of the previous logical + line. + Return 1 if there is no previous line. */ +static int +point_prev_line (WINDOW *win) +{ + int line = window_line_of_point (win); + if (line == 0) + return 1; + win->point = win->line_starts[line - 1] - win->node->contents; + window_compute_line_map (win); + return 0; +} + +/* Advance point to the next multibyte character. Return 1 if this would + cause pointing past the end of node buffer. */ +static int +point_forward_char (WINDOW *win) +{ + long point = win->point; + int col; + + window_compute_line_map (win); + col = window_point_to_column (win, point, &point) + 1; + if (col >= win->line_map.used) + { + if (point_next_line (win)) + return 1; + col = 0; + } + win->point = win->line_map.map[col]; + return 0; +} + +/* Set point to the previous multibyte character. + Return 1 if already on the beginning of node buffer. */ +static int +point_backward_char (WINDOW *win) +{ + long point = win->point; + int col; + + window_compute_line_map (win); + col = window_point_to_column (win, point, &point); + for (; col >= 0 && win->line_map.map[col] == point; col--) + ; + if (col < 0) + { + if (point_prev_line (win)) + return 1; + col = win->line_map.used - 1; + } + win->point = win->line_map.map[col]; + return 0; +} + +/* Skip forward any white space characters starting from column *PCOL in + the current line, advancing line if necessary. Return 1 if going past + the end of node buffer. */ +static int +point_skip_ws_forward (WINDOW *win, int *pcol) +{ + mbi_iterator_t iter; + int col = *pcol; + + while (1) + { + char *buffer = win->node->contents; + size_t buflen = win->node->nodelen; + + for (; col < win->line_map.used; col++) + { + mbi_init (iter, buffer + win->line_map.map[col], + buflen - win->line_map.map[col]); + mbi_avail (iter); + if (!mbi_cur (iter).wc_valid || iswalnum (mbi_cur (iter).wc)) + { + *pcol = col; + return 0; + } + } + if (point_next_line (win)) + return 1; + col = 0; + } + return 1; +} + +/* Skip backward any white space characters starting from column *PCOL in + the current line, retracting line if necessary. Return 1 if going + before the beginning of node buffer. */ +static int +point_skip_ws_backward (WINDOW *win, int *pcol) +{ + mbi_iterator_t iter; + int col = *pcol; + + while (1) + { + char *buffer = win->node->contents; + size_t buflen = win->node->nodelen; + + for (; col > 0; col--) + { + mbi_init (iter, buffer + win->line_map.map[col], + buflen - win->line_map.map[col]); + mbi_avail (iter); + if (!mbi_cur (iter).wc_valid || iswalnum (mbi_cur (iter).wc)) + { + *pcol = col; + return 0; + } + } + if (point_prev_line (win)) + return 1; + col = win->line_map.used - 1; + } + return 1; +} + +/* Advance window point to the beginning of the next word. Return 1 + if there are no more words in the buffer. */ +static int +point_forward_word (WINDOW *win) +{ + mbi_iterator_t iter; + int col; + + window_compute_line_map (win); + col = window_point_to_column (win, win->point, &win->point); + + if (point_skip_ws_forward (win, &col)) + return 1; + + while (1) + { + char *buffer = win->node->contents; + size_t buflen = win->node->nodelen; + + for (; col < win->line_map.used; col++) + { + mbi_init (iter, buffer + win->line_map.map[col], + buflen - win->line_map.map[col]); + mbi_avail (iter); + if (!(mbi_cur (iter).wc_valid && iswalnum (mbi_cur (iter).wc))) + { + if (point_skip_ws_forward (win, &col)) + return 1; + win->point = win->line_map.map[col]; + return 0; + } + } + if (point_next_line (win)) + return 1; + col = 0; + } + return 1; +} + +/* Set window point to the beginning of the previous word. Return 1 + if looking at the very first word in the buffer. */ +static int +point_backward_word (WINDOW *win) +{ + mbi_iterator_t iter; + int col; + + window_compute_line_map (win); + col = window_point_to_column (win, win->point, &win->point); + + while (1) + { + long point; + char *buffer; + size_t buflen; + + if (col <= 0) + { + if (point_prev_line (win)) + return 1; + col = win->line_map.used; + } + col--; + if (point_skip_ws_backward (win, &col)) + return 1; + + buffer = win->node->contents; + buflen = win->node->nodelen; + + for (; col >= 0; col--) + { + mbi_init (iter, buffer + win->line_map.map[col], + buflen - win->line_map.map[col]); + mbi_avail (iter); + if (!(mbi_cur (iter).wc_valid && iswalnum (mbi_cur (iter).wc))) + { + win->point = win->line_map.map[col+1]; + return 0; + } + } + point = win->line_map.map[0] - 1; + if (point > 0 && _looking_at_newline (win, point)) + { + win->point = win->line_map.map[0]; + return 0; + } + } + return 1; +} + +/* Move WINDOW's point to the end of the true line. */ +DECLARE_INFO_COMMAND (info_end_of_line, _("Move to the end of the line")) +{ + int point = window_end_of_line (window); + if (point != window->point) + { + window->point = point; + info_show_point (window); + } +} + +/* Move WINDOW's point to the beginning of the true line. */ +DECLARE_INFO_COMMAND (info_beginning_of_line, _("Move to the start of the line")) +{ + int old_point = window->point; + int point; + + while (1) + { + window_compute_line_map (window); + point = window->line_map.map[0]; + if (point == 0 || _looking_at_newline (window, point-1)) + break; + point_prev_line (window); + } + + if (point != old_point) + { + window->point = point; + info_show_point (window); + } + else + window->point = old_point; +} + +/* Move point forward in the node. */ +DECLARE_INFO_COMMAND (info_forward_char, _("Move forward a character")) +{ + if (count < 0) + info_backward_char (window, -count, key); + else + { + while (count) + { + if (point_forward_char (window)) + { + if (cursor_movement_scrolls_p + && forward_move_node_structure (window, + info_scroll_behaviour) == 0) + window->point = 0; + else + { + window->point = window->node->nodelen - 1; + break; + } + } + count--; + } + info_show_point (window); + } +} + +/* Move point backward in the node. */ +DECLARE_INFO_COMMAND (info_backward_char, _("Move backward a character")) +{ + if (count < 0) + info_forward_char (window, -count, key); + else + { + while (count) + { + if (point_backward_char (window)) + { + if (cursor_movement_scrolls_p + && backward_move_node_structure (window, + info_scroll_behaviour) == 0) + { + window->point = window->node->nodelen - 1; + if (window->line_count > window->height) + set_window_pagetop (window, + window->line_count - window->height); + } + else + { + window->point = 0; + break; + } + } + count--; + } + info_show_point (window); + } +} + +/* Move forward a word in this node. */ +DECLARE_INFO_COMMAND (info_forward_word, _("Move forward a word")) +{ + if (count < 0) + { + info_backward_word (window, -count, key); + return; + } + + while (count) + { + if (point_forward_word (window)) + { + if (cursor_movement_scrolls_p + && forward_move_node_structure (window, + info_scroll_behaviour) == 0) + window->point = 0; + else + return; + } + --count; + } + info_show_point (window); +} + +DECLARE_INFO_COMMAND (info_backward_word, _("Move backward a word")) +{ + if (count < 0) + { + info_forward_word (window, -count, key); + return; + } + + while (count) + { + if (point_backward_word (window)) + { + if (cursor_movement_scrolls_p + && backward_move_node_structure (window, + info_scroll_behaviour) == 0) + { + if (window->line_count > window->height) + set_window_pagetop (window, + window->line_count - window->height); + window->point = window->node->nodelen; + } + else + break; + } + --count; + } + info_show_point (window); +} + +/* Variable controlling the behaviour of default scrolling when you are + already at the bottom of a node. Possible values are defined in session.h. + The meanings are: + + IS_Continuous Try to get first menu item, or failing that, the + "Next:" pointer, or failing that, the "Up:" and + "Next:" of the up. + IS_NextOnly Try to get "Next:" menu item. + IS_PageOnly Simply give up at the bottom of a node. */ + +int info_scroll_behaviour = IS_Continuous; + +/* Choices used by the completer when reading a value for the user-visible + variable "scroll-behaviour". */ +char *info_scroll_choices[] = { + "Continuous", "Next Only", "Page Only", NULL +}; + +/* Controls whether scroll-behavior affects line movement commands */ +int cursor_movement_scrolls_p = 1; + +/* Choices for the scroll-last-node variable */ +char *scroll_last_node_choices[] = { + "Stop", "Scroll", "Top", NULL +}; + +/* Controls what to do when a scrolling command is issued at the end of the + last node. */ +int scroll_last_node = SLN_Stop; + +/* Default window sizes for scrolling commands. */ +int default_window_size = -1; /* meaning 1 window-full */ +int default_scroll_size = -1; /* meaning half screen size */ + +#define INFO_LABEL_FOUND() \ + (info_parsed_nodename || (info_parsed_filename \ + && !is_dir_name (info_parsed_filename))) + +static int +last_node_p (NODE *node) +{ + info_next_label_of_node (node); + if (!INFO_LABEL_FOUND ()) + { + info_up_label_of_node (node); + return !INFO_LABEL_FOUND () || strcmp (info_parsed_nodename, "Top") == 0; + } + return 0; +} + +/* Move to 1st menu item, Next, Up/Next, or error in this window. */ +static int +forward_move_node_structure (WINDOW *window, int behaviour) +{ + switch (behaviour) + { + case IS_PageOnly: + info_error (msg_at_node_bottom, NULL, NULL); + return 1; + + case IS_NextOnly: + info_next_label_of_node (window->node); + if (!info_parsed_nodename && !info_parsed_filename) + { + info_error (msg_no_pointer, (char *) _("Next"), NULL); + return 1; + } + else + { + info_handle_pointer ("Next", window); + } + break; + + case IS_Continuous: + { + if (last_node_p (window->node)) + { + switch (scroll_last_node) + { + case SLN_Stop: + info_error (_("No more nodes within this document."), + NULL, NULL); + return 1; + + case SLN_Scroll: + break; + + case SLN_Top: + info_top_node (window, 1, 0); + return 0; + + default: + abort (); + } + } + + /* First things first. If this node contains a menu, move down + into the menu. */ + { + REFERENCE **menu; + + menu = info_menu_of_node (window->node); + + if (menu) + { + info_free_references (menu); + info_menu_digit (window, 1, '1'); + return 0; + } + } + + /* Okay, this node does not contain a menu. If it contains a + "Next:" pointer, use that. */ + info_next_label_of_node (window->node); + if (INFO_LABEL_FOUND ()) + { + info_handle_pointer ("Next", window); + return 0; + } + + /* Okay, there wasn't a "Next:" for this node. Move "Up:" until we + can move "Next:". If that isn't possible, complain that there + are no more nodes. */ + { + int up_counter, old_current; + INFO_WINDOW *info_win; + + /* Remember the current node and location. */ + info_win = get_info_window_of_window (window); + old_current = info_win->current; + + /* Back up through the "Up:" pointers until we have found a "Next:" + that isn't the same as the first menu item found in that node. */ + up_counter = 0; + while (!info_error_was_printed) + { + info_up_label_of_node (window->node); + if (INFO_LABEL_FOUND ()) + { + info_handle_pointer ("Up", window); + if (info_error_was_printed) + continue; + + up_counter++; + + info_next_label_of_node (window->node); + + /* If no "Next" pointer, keep backing up. */ + if (!INFO_LABEL_FOUND ()) + continue; + + /* If this node's first menu item is the same as this node's + Next pointer, keep backing up. */ + if (!info_parsed_filename) + { + REFERENCE **menu; + char *next_nodename; + + /* Remember the name of the Next node, since reading + the menu can overwrite the contents of the + info_parsed_xxx strings. */ + next_nodename = xstrdup (info_parsed_nodename); + + menu = info_menu_of_node (window->node); + if (menu && + (strcmp + (menu[0]->nodename, next_nodename) == 0)) + { + info_free_references (menu); + free (next_nodename); + continue; + } + else + { + /* Restore the world to where it was before + reading the menu contents. */ + info_free_references (menu); + free (next_nodename); + info_next_label_of_node (window->node); + } + } + + /* This node has a "Next" pointer, and it is not the + same as the first menu item found in this node. */ + info_handle_pointer ("Next", window); + return 0; + } + else + { + /* No more "Up" pointers. Print an error, and call it + quits. */ + register int i; + + for (i = 0; i < up_counter; i++) + { + info_win->nodes_index--; + free (info_win->nodes[info_win->nodes_index]); + info_win->nodes[info_win->nodes_index] = NULL; + } + info_win->current = old_current; + window->node = info_win->nodes[old_current]; + window->pagetop = info_win->pagetops[old_current]; + window->point = info_win->points[old_current]; + recalculate_line_starts (window); + window->flags |= W_UpdateWindow; + info_error (_("No more nodes within this document."), + NULL, NULL); + return 1; + } + } + } + break; + } + } + return info_error_was_printed; /*FIXME*/ +} + +/* Move Prev, Up or error in WINDOW depending on BEHAVIOUR. */ +static int +backward_move_node_structure (WINDOW *window, int behaviour) +{ + switch (behaviour) + { + case IS_PageOnly: + info_error (msg_at_node_top, NULL, NULL); + return 1; + + case IS_NextOnly: + info_prev_label_of_node (window->node); + if (!info_parsed_nodename && !info_parsed_filename) + { + info_error (_("No `Prev' for this node."), NULL, NULL); + return 1; + } + else + { + info_handle_pointer ("Prev", window); + } + break; + + case IS_Continuous: + info_prev_label_of_node (window->node); + + if (!info_parsed_nodename && (!info_parsed_filename + || is_dir_name (info_parsed_filename))) + { + info_up_label_of_node (window->node); + if (!info_parsed_nodename && (!info_parsed_filename + || is_dir_name (info_parsed_filename))) + { + info_error ( + _("No `Prev' or `Up' for this node within this document."), + NULL, NULL); + return 1; + } + else + { + info_handle_pointer ("Up", window); + } + } + else + { + REFERENCE **menu; + int inhibit_menu_traversing = 0; + + /* Watch out! If this node's Prev is the same as the Up, then + move Up. Otherwise, we could move Prev, and then to the last + menu item in the Prev. This would cause the user to loop + through a subsection of the info file. */ + if (!info_parsed_filename && info_parsed_nodename) + { + char *pnode; + + pnode = xstrdup (info_parsed_nodename); + info_up_label_of_node (window->node); + + if (!info_parsed_filename && info_parsed_nodename && + strcmp (info_parsed_nodename, pnode) == 0) + { + /* The nodes are the same. Inhibit moving to the last + menu item. */ + free (pnode); + inhibit_menu_traversing = 1; + } + else + { + free (pnode); + info_prev_label_of_node (window->node); + } + } + + /* Move to the previous node. If this node now contains a menu, + and we have not inhibited movement to it, move to the node + corresponding to the last menu item. */ + info_handle_pointer ("Prev", window); + + if (!inhibit_menu_traversing) + { + while (!info_error_was_printed && + (menu = info_menu_of_node (window->node))) + { + info_free_references (menu); + info_menu_digit (window, 1, '0'); + } + } + } + break; + } + return 0; +} + +/* Move continuously forward through the node structure of this info file. */ +DECLARE_INFO_COMMAND (info_global_next_node, + _("Move forwards or down through node structure")) +{ + if (count < 0) + info_global_prev_node (window, -count, key); + else + { + while (count && !info_error_was_printed) + { + forward_move_node_structure (window, IS_Continuous); + count--; + } + } +} + +/* Move continuously backward through the node structure of this info file. */ +DECLARE_INFO_COMMAND (info_global_prev_node, + _("Move backwards or up through node structure")) +{ + if (count < 0) + info_global_next_node (window, -count, key); + else + { + while (count && !info_error_was_printed) + { + backward_move_node_structure (window, IS_Continuous); + count--; + } + } +} + +static void _scroll_forward(WINDOW *window, int count, + unsigned char key, int behaviour); +static void _scroll_backward(WINDOW *window, int count, + unsigned char key, int behaviour); + +static void +_scroll_forward(WINDOW *window, int count, unsigned char key, int behaviour) +{ + if (count < 0) + _scroll_backward (window, -count, key, behaviour); + else + { + int desired_top; + + /* Without an explicit numeric argument, scroll the bottom two + lines to the top of this window, Or, if at bottom of window, + and the chosen behaviour is to scroll through nodes get the + "Next" node for this window. */ + if (default_window_size > 0) + desired_top = window->pagetop + default_window_size; + else if (!info_explicit_arg && count == 1) + { + desired_top = window->pagetop + (window->height - 2); + + /* If there are no more lines to scroll here, error, or get + another node, depending on BEHAVIOUR. */ + if (desired_top > window->line_count) + { + if (forward_move_node_structure (window, behaviour)) + info_end_of_node (window, 1, 0); + return; + } + } + else + desired_top = window->pagetop + count; + + if (desired_top >= window->line_count) + desired_top = window->line_count - 2; + + if (window->pagetop > desired_top) + return; + else + set_window_pagetop (window, desired_top); + } +} + +static void +_scroll_backward(WINDOW *window, int count, unsigned char key, int behaviour) +{ + if (count < 0) + _scroll_forward (window, -count, key, behaviour); + else + { + int desired_top; + + /* Without an explicit numeric argument, scroll the top two lines + to the bottom of this window, or, depending on the selected + behaviour, move to the previous, or Up'th node. */ + if (default_window_size > 0) + desired_top = window->pagetop - default_window_size; + else if (!info_explicit_arg && count == 1) + { + desired_top = window->pagetop - (window->height - 2); + + if ((desired_top < 0) && (window->pagetop == 0)) + { + if ((backward_move_node_structure (window, behaviour) == 0) + && (cursor_movement_scrolls_p)) + info_end_of_node (window, 1, 0); + window->point = (window->line_starts[window->pagetop] + - window->node->contents); + return; + } + } + else + desired_top = window->pagetop - count; + + if (desired_top < 0) + desired_top = 0; + + set_window_pagetop (window, desired_top); + window->point = (window->line_starts[window->pagetop] + - window->node->contents); + } +} + +/* Show the next screen of WINDOW's node. */ +DECLARE_INFO_COMMAND (info_scroll_forward, _("Scroll forward in this window")) +{ + _scroll_forward (window, count, key, info_scroll_behaviour); +} + +/* Like info_scroll_forward, but sets default_window_size as a side + effect. */ +DECLARE_INFO_COMMAND (info_scroll_forward_set_window, + _("Scroll forward in this window and set default window size")) +{ + if (info_explicit_arg) + default_window_size = count; + _scroll_forward (window, count, key, info_scroll_behaviour); +} + +/* Show the next screen of WINDOW's node but never advance to next node. */ +DECLARE_INFO_COMMAND (info_scroll_forward_page_only, _("Scroll forward in this window staying within node")) +{ + _scroll_forward (window, count, key, IS_PageOnly); +} + +/* Like info_scroll_forward_page_only, but sets default_window_size as a side + effect. */ +DECLARE_INFO_COMMAND (info_scroll_forward_page_only_set_window, + _("Scroll forward in this window staying within node and set default window size")) +{ + if (info_explicit_arg) + default_window_size = count; + _scroll_forward (window, count, key, IS_PageOnly); +} + +/* Show the previous screen of WINDOW's node. */ +DECLARE_INFO_COMMAND (info_scroll_backward, _("Scroll backward in this window")) +{ + _scroll_backward (window, count, key, info_scroll_behaviour); +} + +/* Like info_scroll_backward, but sets default_window_size as a side + effect. */ +DECLARE_INFO_COMMAND (info_scroll_backward_set_window, + _("Scroll backward in this window and set default window size")) +{ + if (info_explicit_arg) + default_window_size = count; + _scroll_backward (window, count, key, info_scroll_behaviour); +} + +/* Show the previous screen of WINDOW's node but never move to previous + node. */ +DECLARE_INFO_COMMAND (info_scroll_backward_page_only, _("Scroll backward in this window staying within node")) +{ + _scroll_backward (window, count, key, IS_PageOnly); +} + +/* Like info_scroll_backward_page_only, but sets default_window_size as a side + effect. */ +DECLARE_INFO_COMMAND (info_scroll_backward_page_only_set_window, + _("Scroll backward in this window staying within node and set default window size")) +{ + if (info_explicit_arg) + default_window_size = count; + _scroll_backward (window, count, key, IS_PageOnly); +} + +/* Move to the beginning of the node. */ +DECLARE_INFO_COMMAND (info_beginning_of_node, _("Move to the start of this node")) +{ + window->pagetop = window->point = 0; + window->flags |= W_UpdateWindow; +} + +/* Move to the end of the node. */ +DECLARE_INFO_COMMAND (info_end_of_node, _("Move to the end of this node")) +{ + window->point = window->node->nodelen - 1; + info_show_point (window); +} + +/* Scroll the window forward by N lines. */ +DECLARE_INFO_COMMAND (info_down_line, _("Scroll down by lines")) +{ + if (count < 0) + info_up_line (window, -count, key); + else + { + int desired_top = window->pagetop + count; + + if (desired_top >= window->line_count) + desired_top = window->line_count - 2; + + if (window->pagetop <= desired_top) + set_window_pagetop (window, desired_top); + } +} + +/* Scroll the window backward by N lines. */ +DECLARE_INFO_COMMAND (info_up_line, _("Scroll up by lines")) +{ + if (count < 0) + info_down_line (window, -count, key); + else + { + int desired_top = window->pagetop - count; + + if (desired_top < 0) + desired_top = 0; + + set_window_pagetop (window, desired_top); + } +} + +/* Scroll the window forward by N lines and remember N as default for + subsequent commands. */ +DECLARE_INFO_COMMAND (info_scroll_half_screen_down, + _("Scroll down by half screen size")) +{ + if (count < 0) + info_scroll_half_screen_up (window, -count, key); + else + { + int scroll_size = (the_screen->height + 1) / 2; + int desired_top; + + if (info_explicit_arg) + default_scroll_size = count; + if (default_scroll_size > 0) + scroll_size = default_scroll_size; + + desired_top = window->pagetop + scroll_size; + if (desired_top >= window->line_count) + desired_top = window->line_count - 2; + + if (window->pagetop <= desired_top) + set_window_pagetop (window, desired_top); + } +} + +/* Scroll the window backward by N lines and remember N as default for + subsequent commands. */ +DECLARE_INFO_COMMAND (info_scroll_half_screen_up, + _("Scroll up by half screen size")) +{ + if (count < 0) + info_scroll_half_screen_down (window, -count, key); + else + { + int scroll_size = (the_screen->height + 1) / 2; + int desired_top; + + if (info_explicit_arg) + default_scroll_size = count; + if (default_scroll_size > 0) + scroll_size = default_scroll_size; + + desired_top = window->pagetop - scroll_size; + if (desired_top < 0) + desired_top = 0; + + set_window_pagetop (window, desired_top); + } +} + +/* **************************************************************** */ +/* */ +/* Commands for Manipulating Windows */ +/* */ +/* **************************************************************** */ + +/* Make the next window in the chain be the active window. */ +DECLARE_INFO_COMMAND (info_next_window, _("Select the next window")) +{ + if (count < 0) + { + info_prev_window (window, -count, key); + return; + } + + /* If no other window, error now. */ + if (!windows->next && !echo_area_is_active) + { + info_error (msg_one_window, NULL, NULL); + return; + } + + while (count--) + { + if (window->next) + window = window->next; + else + { + if (window == the_echo_area || !echo_area_is_active) + window = windows; + else + window = the_echo_area; + } + } + + if (active_window != window) + { + if (auto_footnotes_p) + info_get_or_remove_footnotes (window); + + window->flags |= W_UpdateWindow; + active_window = window; + } +} + +/* Make the previous window in the chain be the active window. */ +DECLARE_INFO_COMMAND (info_prev_window, _("Select the previous window")) +{ + if (count < 0) + { + info_next_window (window, -count, key); + return; + } + + /* Only one window? */ + + if (!windows->next && !echo_area_is_active) + { + info_error (msg_one_window, NULL, NULL); + return; + } + + while (count--) + { + /* If we are in the echo area, or if the echo area isn't active and we + are in the first window, find the last window in the chain. */ + if (window == the_echo_area || + (window == windows && !echo_area_is_active)) + { + register WINDOW *win, *last = NULL; + + for (win = windows; win; win = win->next) + last = win; + + window = last; + } + else + { + if (window == windows) + window = the_echo_area; + else + window = window->prev; + } + } + + if (active_window != window) + { + if (auto_footnotes_p) + info_get_or_remove_footnotes (window); + + window->flags |= W_UpdateWindow; + active_window = window; + } +} + +/* Split WINDOW into two windows, both showing the same node. If we + are automatically tiling windows, re-tile after the split. */ +DECLARE_INFO_COMMAND (info_split_window, _("Split the current window")) +{ + WINDOW *split, *old_active; + int pagetop; + + /* Remember the current pagetop of the window being split. If it doesn't + change, we can scroll its contents around after the split. */ + pagetop = window->pagetop; + + /* Make the new window. */ + old_active = active_window; + active_window = window; + split = window_make_window (window->node); + active_window = old_active; + + if (!split) + { + info_error (msg_win_too_small, NULL, NULL); + } + else + { +#if defined (SPLIT_BEFORE_ACTIVE) + /* Try to scroll the old window into its new postion. */ + if (pagetop == window->pagetop) + { + int start, end, amount; + + start = split->first_row; + end = start + window->height; + amount = split->height + 1; + display_scroll_display (start, end, amount); + } +#else /* !SPLIT_BEFORE_ACTIVE */ + /* Make sure point still appears in the active window. */ + info_show_point (window); +#endif /* !SPLIT_BEFORE_ACTIVE */ + + /* If the window just split was one internal to Info, try to display + something else in it. */ + if (internal_info_node_p (split->node)) + { + register int i, j; + INFO_WINDOW *iw; + NODE *node = NULL; + char *filename; + + for (i = 0; (iw = info_windows[i]); i++) + { + for (j = 0; j < iw->nodes_index; j++) + if (!internal_info_node_p (iw->nodes[j])) + { + if (iw->nodes[j]->parent) + filename = iw->nodes[j]->parent; + else + filename = iw->nodes[j]->filename; + + node = info_get_node (filename, iw->nodes[j]->nodename); + if (node) + { + window_set_node_of_window (split, node); + i = info_windows_index - 1; + break; + } + } + } + } + split->pagetop = window->pagetop; + + if (auto_tiling_p) + window_tile_windows (DONT_TILE_INTERNALS); + else + window_adjust_pagetop (split); + + remember_window_and_node (split, split->node); + } +} + +/* Delete WINDOW, forgetting the list of last visited nodes. If we are + automatically displaying footnotes, show or remove the footnotes + window. If we are automatically tiling windows, re-tile after the + deletion. */ +DECLARE_INFO_COMMAND (info_delete_window, _("Delete the current window")) +{ + if (!windows->next) + { + info_error (msg_cant_kill_last, NULL, NULL); + } + else if (window->flags & W_WindowIsPerm) + { + info_error (_("Cannot delete a permanent window"), NULL, NULL); + } + else + { + info_delete_window_internal (window); + + if (auto_footnotes_p) + info_get_or_remove_footnotes (active_window); + + if (auto_tiling_p) + window_tile_windows (DONT_TILE_INTERNALS); + } +} + +/* Do the physical deletion of WINDOW, and forget this window and + associated nodes. */ +void +info_delete_window_internal (WINDOW *window) +{ + if (windows->next && ((window->flags & W_WindowIsPerm) == 0)) + { + /* We not only delete the window from the display, we forget it from + our list of remembered windows. */ + forget_window_and_nodes (window); + window_delete_window (window); + + if (echo_area_is_active) + echo_area_inform_of_deleted_window (window); + } +} + +/* Just keep WINDOW, deleting all others. */ +DECLARE_INFO_COMMAND (info_keep_one_window, _("Delete all other windows")) +{ + int num_deleted; /* The number of windows we deleted. */ + int pagetop, start, end; + + /* Remember a few things about this window. We may be able to speed up + redisplay later by scrolling its contents. */ + pagetop = window->pagetop; + start = window->first_row; + end = start + window->height; + + num_deleted = 0; + + while (1) + { + WINDOW *win; + + /* Find an eligible window and delete it. If no eligible windows + are found, we are done. A window is eligible for deletion if + is it not permanent, and it is not WINDOW. */ + for (win = windows; win; win = win->next) + if (win != window && ((win->flags & W_WindowIsPerm) == 0)) + break; + + if (!win) + break; + + info_delete_window_internal (win); + num_deleted++; + } + + /* Scroll the contents of this window into the right place so that the + user doesn't have to wait any longer than necessary for redisplay. */ + if (num_deleted) + { + int amount; + + amount = (window->first_row - start); + amount -= (window->pagetop - pagetop); + display_scroll_display (start, end, amount); + } + + window->flags |= W_UpdateWindow; +} + +/* Scroll the "other" window of WINDOW. */ +DECLARE_INFO_COMMAND (info_scroll_other_window, _("Scroll the other window")) +{ + WINDOW *other; + + /* If only one window, give up. */ + if (!windows->next) + { + info_error (msg_one_window, NULL, NULL); + return; + } + + other = window->next; + + if (!other) + other = window->prev; + + info_scroll_forward (other, count, key); +} + +/* Scroll the "other" window of WINDOW. */ +DECLARE_INFO_COMMAND (info_scroll_other_window_backward, + _("Scroll the other window backward")) +{ + info_scroll_other_window (window, -count, key); +} + +/* Change the size of WINDOW by AMOUNT. */ +DECLARE_INFO_COMMAND (info_grow_window, _("Grow (or shrink) this window")) +{ + window_change_window_height (window, count); +} + +/* When non-zero, tiling takes place automatically when info_split_window + is called. */ +int auto_tiling_p = 0; + +/* Tile all of the visible windows. */ +DECLARE_INFO_COMMAND (info_tile_windows, + _("Divide the available screen space among the visible windows")) +{ + window_tile_windows (TILE_INTERNALS); +} + +/* Toggle the state of this window's wrapping of lines. */ +DECLARE_INFO_COMMAND (info_toggle_wrap, + _("Toggle the state of line wrapping in the current window")) +{ + window_toggle_wrap (window); +} + +/* Toggle the usage of regular expressions in searches. */ +DECLARE_INFO_COMMAND (info_toggle_regexp, + _("Toggle the usage of regular expressions in searches")) +{ + use_regex = 1 - use_regex; + window_message_in_echo_area (use_regex + ? _("Using regular expressions for searches.") + : _("Using literal strings for searches."), + NULL, NULL); +} + +/* **************************************************************** */ +/* */ +/* Info Node Commands */ +/* */ +/* **************************************************************** */ + +/* Return (FILENAME)NODENAME for NODE, or just NODENAME if NODE's + filename is not set. */ +char * +node_printed_rep (NODE *node) +{ + char *rep; + + if (node->filename) + { + char *filename + = filename_non_directory (node->parent ? node->parent : node->filename); + rep = xmalloc (1 + strlen (filename) + 1 + strlen (node->nodename) + 1); + sprintf (rep, "(%s)%s", filename, node->nodename); + } + else + rep = node->nodename; + + return rep; +} + + +/* Using WINDOW for various defaults, select the node referenced by ENTRY + in it. If the node is selected, the window and node are remembered. */ +void +info_select_reference (WINDOW *window, REFERENCE *entry) +{ + NODE *node; + char *filename, *nodename, *file_system_error; + + file_system_error = NULL; + + filename = entry->filename; + if (!filename) + filename = window->node->parent; + if (!filename) + filename = window->node->filename; + + if (filename) + filename = xstrdup (filename); + + if (entry->nodename) + nodename = xstrdup (entry->nodename); + else + nodename = xstrdup ("Top"); + + node = info_get_node (filename, nodename); + + /* Try something a little weird. If the node couldn't be found, and the + reference was of the form "foo::", see if the entry->label can be found + as a file, with a node of "Top". */ + if (!node) + { + if (info_recent_file_error) + file_system_error = xstrdup (info_recent_file_error); + + if (entry->nodename && (strcmp (entry->nodename, entry->label) == 0)) + { + node = info_get_node (entry->label, "Top"); + if (!node && info_recent_file_error) + { + maybe_free (file_system_error); + file_system_error = xstrdup (info_recent_file_error); + } + } + } + + if (!node) + { + if (file_system_error) + info_error (file_system_error, NULL, NULL); + else + info_error (msg_cant_find_node, nodename, NULL); + } + + maybe_free (file_system_error); + maybe_free (filename); + maybe_free (nodename); + + if (node) + info_set_node_of_window (1, window, node); +} + +/* Parse the node specification in LINE using WINDOW to default the filename. + Select the parsed node in WINDOW and remember it, or error if the node + couldn't be found. */ +static void +info_parse_and_select (char *line, WINDOW *window) +{ + REFERENCE entry; + + info_parse_node (line, DONT_SKIP_NEWLINES); + + entry.nodename = info_parsed_nodename; + entry.filename = info_parsed_filename; + entry.label = "*info-parse-and-select*"; + + info_select_reference (window, &entry); +} + +/* Given that the values of INFO_PARSED_FILENAME and INFO_PARSED_NODENAME + are previously filled, try to get the node represented by them into + WINDOW. The node should have been pointed to by the LABEL pointer of + WINDOW->node. */ +static void +info_handle_pointer (char *label, WINDOW *window) +{ + if (info_parsed_filename || info_parsed_nodename) + { + char *filename, *nodename; + NODE *node; + + filename = nodename = NULL; + + if (info_parsed_filename) + filename = xstrdup (info_parsed_filename); + else + { + if (window->node->parent) + filename = xstrdup (window->node->parent); + else if (window->node->filename) + filename = xstrdup (window->node->filename); + } + + if (info_parsed_nodename) + nodename = xstrdup (info_parsed_nodename); + else + nodename = xstrdup ("Top"); + + node = info_get_node (filename, nodename); + + if (node) + { + INFO_WINDOW *info_win; + + info_win = get_info_window_of_window (window); + if (info_win) + { + info_win->pagetops[info_win->current] = window->pagetop; + info_win->points[info_win->current] = window->point; + } + info_set_node_of_window (1, window, node); + } + else + { + if (info_recent_file_error) + info_error (info_recent_file_error, NULL, NULL); + else + info_error (msg_cant_file_node, filename, nodename); + } + + free (filename); + free (nodename); + } + else + { + info_error (msg_no_pointer, label, NULL); + } +} + +/* Make WINDOW display the "Next:" node of the node currently being + displayed. */ +DECLARE_INFO_COMMAND (info_next_node, _("Select the Next node")) +{ + info_next_label_of_node (window->node); + info_handle_pointer ("Next", window); +} + +/* Make WINDOW display the "Prev:" node of the node currently being + displayed. */ +DECLARE_INFO_COMMAND (info_prev_node, _("Select the Prev node")) +{ + info_prev_label_of_node (window->node); + info_handle_pointer ("Prev", window); +} + +/* Make WINDOW display the "Up:" node of the node currently being + displayed. */ +DECLARE_INFO_COMMAND (info_up_node, _("Select the Up node")) +{ + info_up_label_of_node (window->node); + info_handle_pointer ("Up", window); +} + +/* Make WINDOW display the last node of this info file. */ +DECLARE_INFO_COMMAND (info_last_node, _("Select the last node in this file")) +{ + register int i; + FILE_BUFFER *fb = file_buffer_of_window (window); + NODE *node = NULL; + + if (fb && fb->tags) + { + int last_node_tag_idx = -1; + + /* If no explicit argument, or argument of zero, default to the + last node. */ + if (count == 0 || (count == 1 && !info_explicit_arg)) + count = -1; + for (i = 0; count && fb->tags[i]; i++) + if (fb->tags[i]->nodelen != 0) /* don't count anchor tags */ + { + count--; + last_node_tag_idx = i; + } + if (count > 0) + i = last_node_tag_idx + 1; + if (i > 0) + node = info_get_node (fb->filename, fb->tags[i - 1]->nodename); + } + + if (!node) + info_error (_("This window has no additional nodes"), NULL, NULL); + else + info_set_node_of_window (1, window, node); +} + +/* Make WINDOW display the first node of this info file. */ +DECLARE_INFO_COMMAND (info_first_node, _("Select the first node in this file")) +{ + FILE_BUFFER *fb = file_buffer_of_window (window); + NODE *node = NULL; + + /* If no explicit argument, or argument of zero, default to the + first node. */ + if (count == 0) + count = 1; + if (fb && fb->tags) + { + register int i; + int last_node_tag_idx = -1; + + for (i = 0; count && fb->tags[i]; i++) + if (fb->tags[i]->nodelen != 0) /* don't count anchor tags */ + { + count--; + last_node_tag_idx = i; + } + if (count > 0) + i = last_node_tag_idx + 1; + if (i > 0) + node = info_get_node (fb->filename, fb->tags[i - 1]->nodename); + } + + if (!node) + info_error (_("This window has no additional nodes"), NULL, NULL); + else + info_set_node_of_window (1, window, node); +} + +/* Select the last menu item in WINDOW->node. */ +DECLARE_INFO_COMMAND (info_last_menu_item, + _("Select the last item in this node's menu")) +{ + info_menu_digit (window, 1, '0'); +} + +/* Use KEY (a digit) to select the Nth menu item in WINDOW->node. */ +DECLARE_INFO_COMMAND (info_menu_digit, _("Select this menu item")) +{ + register int i, item; + register REFERENCE **menu; + + menu = info_menu_of_node (window->node); + + if (!menu) + { + info_error (msg_no_menu_node, NULL, NULL); + return; + } + + /* We have the menu. See if there are this many items in it. */ + item = key - '0'; + + /* Special case. Item "0" is the last item in this menu. */ + if (item == 0) + for (i = 0; menu[i + 1]; i++); + else + { + for (i = 0; menu[i]; i++) + if (i == item - 1) + break; + } + + if (menu[i]) + { + info_select_reference (window, menu[i]); + if (menu[i]->line_number > 0) + info_next_line (window, menu[i]->line_number - 1, key); + } + else + info_error (_("There aren't %d items in this menu."), + (void *) (long) item, NULL); + + info_free_references (menu); + return; +} + + + +/* Return a pointer to the xref in XREF_LIST that is nearest to POS, or + NULL if XREF_LIST is empty. That is, if POS is within any of the + given xrefs, return that one. Otherwise, return the one with the + nearest beginning or end. If there are two that are equidistant, + prefer the one forward. The return is in newly-allocated memory, + since the caller frees it. + + This is called from info_menu_or_ref_item with XREF_LIST being all + the xrefs in the node, and POS being point. The ui function that + starts it all off is select-reference-this-line. + + This is not the same logic as in info.el. Info-get-token prefers + searching backwards to searching forwards, and has a hardwired search + limit of 200 chars (in Emacs 21.2). */ + +static REFERENCE ** +nearest_xref (REFERENCE **xref_list, long int pos) +{ + int this_xref; + int nearest = -1; + long best_delta = -1; + + for (this_xref = 0; xref_list[this_xref]; this_xref++) + { + long delta; + REFERENCE *xref = xref_list[this_xref]; + if (xref->start <= pos && pos <= xref->end) + { /* POS is within this xref, we're done */ + nearest = this_xref; + break; + } + + /* See how far POS is from this xref. Take into account the + `*Note' that begins the xref, since as far as the user is + concerned, that's where it starts. */ + delta = MIN (labs (pos - (xref->start - strlen (INFO_XREF_LABEL))), + labs (pos - xref->end)); + + /* It's the <= instead of < that makes us choose the forward xref + of POS if two are equidistant. Of course, because of all the + punctuation surrounding xrefs, it's not necessarily obvious + where one ends. */ + if (delta <= best_delta || best_delta < 0) + { + nearest = this_xref; + best_delta = delta; + } + } + + /* Maybe there was no list to search through. */ + if (nearest < 0) + return NULL; + + /* Ok, we have a nearest xref, make a list of it. */ + { + REFERENCE **ret = xmalloc (sizeof (REFERENCE *) * 2); + ret[0] = info_copy_reference (xref_list[nearest]); + ret[1] = NULL; + return ret; + } +} + + +/* Read a menu or followed reference from the user defaulting to the + reference found on the current line, and select that node. The + reading is done with completion. BUILDER is the function used + to build the list of references. ASK_P is non-zero if the user + should be prompted, or zero to select the default item. */ +static void +info_menu_or_ref_item (WINDOW *window, int count, + unsigned char key, REFERENCE **(*builder) (NODE *node), int ask_p) +{ + char *line; + REFERENCE *entry; + REFERENCE *defentry = NULL; + REFERENCE **menu = (*builder) (window->node); + + if (!menu) + { + if (builder == info_menu_of_node) + info_error (msg_no_menu_node, NULL, NULL); + else + info_error (msg_no_xref_node, NULL, NULL); + return; + } + + /* Default the selected reference to the one which is on the line that + point is in. */ + { + REFERENCE **refs = NULL; + int point_line = window_line_of_point (window); + + if (point_line != -1) + { + SEARCH_BINDING binding; + + binding.buffer = window->node->contents; + binding.start = window->line_starts[point_line] - binding.buffer; + if (window->line_starts[point_line + 1]) + binding.end = window->line_starts[point_line + 1] - binding.buffer; + else + binding.end = window->node->nodelen; + binding.flags = 0; + + if (builder == info_menu_of_node) + { + if (point_line) + { + binding.start--; + refs = info_menu_items (&binding); + } + } + else + { +#if defined (HANDLE_MAN_PAGES) + if (window->node->flags & N_IsManPage) + refs = manpage_xrefs_in_binding (window->node, &binding); + else +#endif /* HANDLE_MAN_PAGES */ + refs = nearest_xref (menu, window->point); + } + + if (refs && refs[0]) + { + if (strcmp (refs[0]->label, "Menu") != 0 + || builder == info_xrefs_of_node) + { + int which = 0; + + /* For xrefs, find the closest reference to point, + unless we only have one reference (as we will if + we've called nearest_xref above). It would be better + to have only one piece of code, but the conditions + when we call this are tangled. */ + if (builder == info_xrefs_of_node && refs[1]) + { + int closest = -1; + + for (; refs[which]; which++) + { + if (window->point >= refs[which]->start + && window->point <= refs[which]->end) + { + closest = which; + break; + } + else if (window->point < refs[which]->start) + break; + } + if (which > 0) + { + if (closest == -1) + which--; + else + which = closest; + } + } + + defentry = xmalloc (sizeof (REFERENCE)); + defentry->label = xstrdup (refs[which]->label); + defentry->filename = refs[which]->filename; + defentry->nodename = refs[which]->nodename; + defentry->line_number = refs[which]->line_number; + + if (defentry->filename) + defentry->filename = xstrdup (defentry->filename); + if (defentry->nodename) + defentry->nodename = xstrdup (defentry->nodename); + } + info_free_references (refs); + } + } + } + + /* If we are going to ask the user a question, do it now. */ + if (ask_p) + { + char *prompt; + + /* Build the prompt string. */ + if (builder == info_menu_of_node) + { + if (defentry) + { + prompt = xmalloc (strlen (defentry->label) + + strlen (_("Menu item (%s): "))); + sprintf (prompt, _("Menu item (%s): "), defentry->label); + } + else + prompt = xstrdup (_("Menu item: ")); + } + else + { + if (defentry) + { + prompt = xmalloc (strlen (defentry->label) + + strlen (_("Follow xref (%s): "))); + sprintf (prompt, _("Follow xref (%s): "), defentry->label); + } + else + prompt = xstrdup (_("Follow xref: ")); + } + + line = info_read_completing_in_echo_area (window, prompt, menu); + free (prompt); + + window = active_window; + + /* User aborts, just quit. */ + if (!line) + { + maybe_free (defentry); + info_free_references (menu); + info_abort_key (window, 0, 0); + return; + } + + /* If we had a default and the user accepted it, use that. */ + if (!*line) + { + free (line); + if (defentry) + line = xstrdup (defentry->label); + else + line = NULL; + } + } + else + { + /* Not going to ask any questions. If we have a default entry, use + that, otherwise return. */ + if (!defentry) + return; + else + line = xstrdup (defentry->label); + } + + if (line) + { + /* It is possible that the references have more than a single + entry with the same label, and also LINE is down-cased, which + complicates matters even more. Try to be as accurate as we + can: if they've chosen the default, use defentry directly. */ + if (defentry && strcmp (line, defentry->label) == 0) + entry = defentry; + else + /* Find the selected label in the references. If there are + more than one label which matches, find the one that's + closest to point. */ + { + register int i; + int best = -1, min_dist = window->node->nodelen; + REFERENCE *ref; + + for (i = 0; menu && (ref = menu[i]); i++) + { + /* Need to use mbscasecmp because LINE is downcased + inside info_read_completing_in_echo_area. */ + if (mbscasecmp (line, ref->label) == 0) + { + /* ref->end is more accurate estimate of position + for menus than ref->start. Go figure. */ + int dist = abs (window->point - ref->end); + + if (dist < min_dist) + { + min_dist = dist; + best = i; + } + } + } + if (best != -1) + entry = menu[best]; + else + entry = NULL; + } + + if (!entry && defentry) + info_error (_("The reference disappeared! (%s)."), line, NULL); + else + { + NODE *orig = window->node; + info_select_reference (window, entry); + + if (builder == info_xrefs_of_node && window->node != orig + && !(window->node->flags & N_FromAnchor)) + { /* Search for this reference in the node. */ + long offset; + long start; + + if (window->line_count > 0) + start = window->line_starts[1] - window->node->contents; + else + start = 0; + + offset = + info_target_search_node (window->node, entry->label, start); + + if (offset != -1) + { + window->point = offset; + window_adjust_pagetop (window); + } + } + + if (entry->line_number > 0) + /* next_line starts at line 1? Anyway, the -1 makes it + move to the right line. */ + info_next_line (window, entry->line_number - 1, key); + } + + free (line); + if (defentry) + { + free (defentry->label); + maybe_free (defentry->filename); + maybe_free (defentry->nodename); + free (defentry); + } + } + + info_free_references (menu); + + if (!info_error_was_printed) + window_clear_echo_area (); +} + +/* Read a line (with completion) which is the name of a menu item, + and select that item. */ +DECLARE_INFO_COMMAND (info_menu_item, _("Read a menu item and select its node")) +{ + info_menu_or_ref_item (window, count, key, info_menu_of_node, 1); +} + +/* Read a line (with completion) which is the name of a reference to + follow, and select the node. */ +DECLARE_INFO_COMMAND + (info_xref_item, _("Read a footnote or cross reference and select its node")) +{ + info_menu_or_ref_item (window, count, key, info_xrefs_of_node, 1); +} + +/* Position the cursor at the start of this node's menu. */ +DECLARE_INFO_COMMAND (info_find_menu, _("Move to the start of this node's menu")) +{ + SEARCH_BINDING binding; + long position; + + binding.buffer = window->node->contents; + binding.start = 0; + binding.end = window->node->nodelen; + binding.flags = S_FoldCase | S_SkipDest; + + position = search (INFO_MENU_LABEL, &binding); + + if (position == -1) + info_error (msg_no_menu_node, NULL, NULL); + else + { + window->point = position; + window_adjust_pagetop (window); + window->flags |= W_UpdateWindow; + } +} + +/* Visit as many menu items as is possible, each in a separate window. */ +DECLARE_INFO_COMMAND (info_visit_menu, + _("Visit as many menu items at once as possible")) +{ + register int i; + REFERENCE *entry, **menu; + + menu = info_menu_of_node (window->node); + + if (!menu) + info_error (msg_no_menu_node, NULL, NULL); + + for (i = 0; (!info_error_was_printed) && (entry = menu[i]); i++) + { + WINDOW *new; + + new = window_make_window (window->node); + window_tile_windows (TILE_INTERNALS); + + if (!new) + info_error (msg_win_too_small, NULL, NULL); + else + { + active_window = new; + info_select_reference (new, entry); + } + } +} + +/* Read a line of input which is a node name, and go to that node. */ +DECLARE_INFO_COMMAND (info_goto_node, _("Read a node name and select it")) +{ + char *line; + +#define GOTO_COMPLETES +#if defined (GOTO_COMPLETES) + /* Build a completion list of all of the known nodes. */ + { + register int fbi, i; + FILE_BUFFER *current; + REFERENCE **items = NULL; + int items_index = 0; + int items_slots = 0; + + current = file_buffer_of_window (window); + + for (fbi = 0; info_loaded_files && info_loaded_files[fbi]; fbi++) + { + FILE_BUFFER *fb; + REFERENCE *entry; + int this_is_the_current_fb; + + fb = info_loaded_files[fbi]; + this_is_the_current_fb = (current == fb); + + entry = xmalloc (sizeof (REFERENCE)); + entry->filename = entry->nodename = NULL; + entry->label = xmalloc (4 + strlen (fb->filename)); + sprintf (entry->label, "(%s)*", fb->filename); + + add_pointer_to_array + (entry, items_index, items, items_slots, 10, REFERENCE *); + + if (fb->tags) + { + for (i = 0; fb->tags[i]; i++) + { + entry = xmalloc (sizeof (REFERENCE)); + entry->filename = entry->nodename = NULL; + if (this_is_the_current_fb) + entry->label = xstrdup (fb->tags[i]->nodename); + else + { + entry->label = xmalloc + (4 + strlen (fb->filename) + + strlen (fb->tags[i]->nodename)); + sprintf (entry->label, "(%s)%s", + fb->filename, fb->tags[i]->nodename); + } + + add_pointer_to_array + (entry, items_index, items, items_slots, 100, REFERENCE *); + } + } + } + line = info_read_maybe_completing (window, _("Goto node: "), + items); + info_free_references (items); + } +#else /* !GOTO_COMPLETES */ + line = info_read_in_echo_area (window, _("Goto node: ")); +#endif /* !GOTO_COMPLETES */ + + /* If the user aborted, quit now. */ + if (!line) + { + info_abort_key (window, 0, 0); + return; + } + + canonicalize_whitespace (line); + + if (*line) + info_parse_and_select (line, window); + + free (line); + if (!info_error_was_printed) + window_clear_echo_area (); +} + +/* Follow the menu list in MENUS (list of strings terminated by a NULL + entry) from INITIAL_NODE. If can't continue at any point (no menu or + no menu entry for the next item), return the node so far -- that + might be INITIAL_NODE itself. If error, *ERRSTR and *ERRARG[12] will + be set to the error message and argument for message, otherwise they + will be NULL. */ + +NODE * +info_follow_menus (NODE *initial_node, char **menus, + const char **errstr, char **errarg1, char **errarg2) +{ + NODE *node = NULL; + *errstr = *errarg1 = *errarg2 = NULL; + + for (; *menus; menus++) + { + static char *first_arg = NULL; + REFERENCE **menu; + REFERENCE *entry; + char *arg = *menus; /* Remember the name of the menu entry we want. */ + + /* A leading space is certainly NOT part of a node name. Most + probably, they typed a space after the separating comma. The + strings in menus[] have their whitespace canonicalized, so + there's at most one space to ignore. */ + if (*arg == ' ') + arg++; + if (!first_arg) + first_arg = arg; + + /* Build and return a list of the menu items in this node. */ + menu = info_menu_of_node (initial_node); + + /* If no menu item in this node, stop here, but let the user + continue to use Info. Perhaps they wanted this node and didn't + realize it. */ + if (!menu) + { + if (arg == first_arg) + { + node = make_manpage_node (first_arg); + if (node) + goto maybe_got_node; + } + *errstr = _("No menu in node `%s'."); + *errarg1 = node_printed_rep (initial_node); + return initial_node; + } + + /* Find the specified menu item. */ + entry = info_get_labeled_reference (arg, menu); + + /* If the item wasn't found, search the list sloppily. Perhaps this + user typed "buffer" when they really meant "Buffers". */ + if (!entry) + { + int i; + int best_guess = -1; + + for (i = 0; (entry = menu[i]); i++) + { + if (mbscasecmp (entry->label, arg) == 0) + break; + else + if ((best_guess == -1) + && (mbsncasecmp (entry->label, arg, strlen (arg)) == 0)) + best_guess = i; + } + + if (!entry && best_guess != -1) + entry = menu[best_guess]; + } + + /* If we still failed to find the reference, start Info with the current + node anyway. It is probably a misspelling. */ + if (!entry) + { + if (arg == first_arg) + { + /* Maybe they typed "info foo" instead of "info -f foo". */ + node = info_get_node (first_arg, 0); + if (node) + add_file_directory_to_path (first_arg); + else + node = make_manpage_node (first_arg); + if (node) + goto maybe_got_node; + } + + info_free_references (menu); + *errstr = _("No menu item `%s' in node `%s'."); + *errarg1 = arg; + *errarg2 = node_printed_rep (initial_node); + return initial_node; + } + + /* We have found the reference that the user specified. If no + filename in this reference, define it. */ + if (!entry->filename) + entry->filename = xstrdup (initial_node->parent ? initial_node->parent + : initial_node->filename); + + /* Try to find this node. */ + node = info_get_node (entry->filename, entry->nodename); + if (!node && arg == first_arg) + { + node = make_manpage_node (first_arg); + if (node) + goto maybe_got_node; + } + + /* Since we cannot find it, try using the label of the entry as a + file, i.e., "(LABEL)Top". */ + if (!node && entry->nodename + && strcmp (entry->label, entry->nodename) == 0) + node = info_get_node (entry->label, "Top"); + + maybe_got_node: + if (!node) + { + *errstr = _("Unable to find node referenced by `%s' in `%s'."); + *errarg1 = xstrdup (entry->label); + *errarg2 = node_printed_rep (initial_node); + info_free_references (menu); + return initial_node; + } + + info_free_references (menu); + + /* Success. Go round the loop again. */ + free (initial_node); + initial_node = node; + } + + return initial_node; +} + +/* Split STR into individual node names by writing null bytes in wherever + there are commas and constructing a list of the resulting pointers. + (We can do this since STR has had canonicalize_whitespace called on it.) + Return array terminated with NULL. */ + +static char ** +split_list_of_nodenames (char *str) +{ + unsigned len = 2; + char **nodes = xmalloc (len * sizeof (char *)); + + nodes[len - 2] = str; + + while (*str++) + { + if (*str == ',') + { + *str++ = 0; /* get past the null byte */ + len++; + nodes = xrealloc (nodes, len * sizeof (char *)); + nodes[len - 2] = str; + } + } + + nodes[len - 1] = NULL; + + return nodes; +} + + +/* Read a line of input which is a sequence of menus (starting from + dir), and follow them. */ +DECLARE_INFO_COMMAND (info_menu_sequence, + _("Read a list of menus starting from dir and follow them")) +{ + char *line = info_read_in_echo_area (window, _("Follow menus: ")); + + /* If the user aborted, quit now. */ + if (!line) + { + info_abort_key (window, 0, 0); + return; + } + + canonicalize_whitespace (line); + + if (*line) + { + const char *errstr; + char *errarg1, *errarg2; + NODE *dir_node = info_get_node (NULL, NULL); + char **nodes = split_list_of_nodenames (line); + NODE *node = NULL; + + /* If DIR_NODE is NULL, they might be reading a file directly, + like in "info -d . -f ./foo". Try using "Top" instead. */ + if (!dir_node) + { + char *file_name = window->node->parent; + + if (!file_name) + file_name = window->node->filename; + dir_node = info_get_node (file_name, NULL); + } + + /* If we still cannot find the starting point, give up. + We cannot allow a NULL pointer inside info_follow_menus. */ + if (!dir_node) + info_error (msg_cant_find_node, "Top", NULL); + else + node = info_follow_menus (dir_node, nodes, &errstr, &errarg1, &errarg2); + + free (nodes); + if (!errstr) + info_set_node_of_window (1, window, node); + else + info_error (errstr, errarg1, errarg2); + } + + free (line); + if (!info_error_was_printed) + window_clear_echo_area (); +} + +/* Search the menu MENU for a (possibly mis-spelled) entry ARG. + Return the menu entry, or the best guess for what they meant by ARG, + or NULL if there's nothing in this menu seems to fit the bill. + If EXACT is non-zero, allow only exact matches. */ +static REFERENCE * +entry_in_menu (char *arg, REFERENCE **menu, int exact) +{ + REFERENCE *entry; + + /* First, try to find the specified menu item verbatim. */ + entry = info_get_labeled_reference (arg, menu); + + /* If the item wasn't found, search the list sloppily. Perhaps we + have "Option Summary", but ARG is "option". */ + if (!entry && !exact) + { + int i; + int best_guess = -1; + + for (i = 0; (entry = menu[i]); i++) + { + if (mbscasecmp (entry->label, arg) == 0) + break; + else + if (mbsncasecmp (entry->label, arg, strlen (arg)) == 0) + best_guess = i; + } + + if (!entry && best_guess != -1) + entry = menu[best_guess]; + } + + return entry; +} + +/* Find the node that is the best candidate to list the PROGRAM's + invocation info and its command-line options, by looking for menu + items and chains of menu items with characteristic names. */ +void +info_intuit_options_node (WINDOW *window, NODE *initial_node, char *program) +{ + /* The list of node names typical for GNU manuals where the program + usage and specifically the command-line arguments are described. + This is pure heuristics. I gathered these node names by looking + at all the Info files I could put my hands on. If you are + looking for evidence to complain to the GNU project about + non-uniform style of documentation, here you have your case! */ + static const char *invocation_nodes[] = { + "%s invocation", + "Invoking %s", + "Preliminaries", /* m4 has Invoking under Preliminaries! */ + "Invocation", + "Command Arguments",/* Emacs */ + "Invoking `%s'", + "%s options", + "Options", + "Option ", /* e.g. "Option Summary" */ + "Invoking", + "All options", /* tar, paxutils */ + "Arguments", + "%s cmdline", /* ar */ + "%s", /* last resort */ + (const char *)0 + }; + NODE *node = NULL; + REFERENCE **menu; + const char **try_node; + + /* We keep looking deeper and deeper in the menu structure until + there are no more menus or no menu items from the above list. + Some manuals have the invocation node sitting 3 or 4 levels deep + in the menu hierarchy... */ + for (node = initial_node; node; initial_node = node) + { + REFERENCE *entry = NULL; + + /* Build and return a list of the menu items in this node. */ + menu = info_menu_of_node (initial_node); + + /* If no menu item in this node, stop here. Perhaps this node + is the one they need. */ + if (!menu) + break; + + /* Look for node names typical for usage nodes in this menu. */ + for (try_node = invocation_nodes; *try_node; try_node++) + { + char *nodename; + + nodename = xmalloc (strlen (program) + strlen (*try_node)); + sprintf (nodename, *try_node, program); + /* The last resort "%s" is dangerous, so we restrict it + to exact matches here. */ + entry = entry_in_menu (nodename, menu, + strcmp (*try_node, "%s") == 0); + free (nodename); + if (entry) + break; + } + + if (!entry) + break; + + if (!entry->filename) + entry->filename = xstrdup (initial_node->parent ? initial_node->parent + : initial_node->filename); + /* Try to find this node. */ + node = info_get_node (entry->filename, entry->nodename); + info_free_references (menu); + if (!node) + break; + } + + /* We've got our best shot at the invocation node. Now select it. */ + if (initial_node) + info_set_node_of_window (1, window, initial_node); + if (!info_error_was_printed) + window_clear_echo_area (); +} + +/* Given a name of an Info file, find the name of the package it + describes by removing the leading directories and extensions. */ +char * +program_name_from_file_name (char *file_name) +{ + int i; + char *program_name = xstrdup (filename_non_directory (file_name)); + + for (i = strlen (program_name) - 1; i > 0; i--) + if (program_name[i] == '.' + && (FILENAME_CMPN (program_name + i, ".info", 5) == 0 + || FILENAME_CMPN (program_name + i, ".inf", 4) == 0 +#ifdef __MSDOS__ + || FILENAME_CMPN (program_name + i, ".i", 2) == 0 +#endif + || isdigit (program_name[i + 1]))) /* a man page foo.1 */ + { + program_name[i] = 0; + break; + } + return program_name; +} + +DECLARE_INFO_COMMAND (info_goto_invocation_node, + _("Find the node describing program invocation")) +{ + const char *invocation_prompt = _("Find Invocation node of [%s]: "); + char *program_name, *line; + char *default_program_name, *prompt, *file_name; + NODE *top_node; + + /* Intuit the name of the program they are likely to want. + We use the file name of the current Info file as a hint. */ + file_name = window->node->parent ? window->node->parent + : window->node->filename; + default_program_name = program_name_from_file_name (file_name); + + prompt = xmalloc (strlen (default_program_name) + + strlen (invocation_prompt)); + sprintf (prompt, invocation_prompt, default_program_name); + line = info_read_in_echo_area (window, prompt); + free (prompt); + if (!line) + { + info_abort_key (window, 0, 0); + return; + } + if (*line) + program_name = line; + else + program_name = default_program_name; + + /* In interactive usage they'd probably expect us to begin looking + from the Top node. */ + top_node = info_get_node (file_name, NULL); + if (!top_node) + info_error (msg_cant_find_node, "Top", NULL); + + info_intuit_options_node (window, top_node, program_name); + free (line); + free (default_program_name); +} + +#if defined (HANDLE_MAN_PAGES) +DECLARE_INFO_COMMAND (info_man, _("Read a manpage reference and select it")) +{ + char *line; + + line = info_read_in_echo_area (window, _("Get Manpage: ")); + + if (!line) + { + info_abort_key (window, 0, 0); + return; + } + + canonicalize_whitespace (line); + + if (*line) + { + char *goto_command; + + goto_command = xmalloc + (4 + strlen (MANPAGE_FILE_BUFFER_NAME) + strlen (line)); + + sprintf (goto_command, "(%s)%s", MANPAGE_FILE_BUFFER_NAME, line); + + info_parse_and_select (goto_command, window); + free (goto_command); + } + + free (line); + if (!info_error_was_printed) + window_clear_echo_area (); +} +#endif /* HANDLE_MAN_PAGES */ + +/* Move to the "Top" node in this file. */ +DECLARE_INFO_COMMAND (info_top_node, _("Select the node `Top' in this file")) +{ + info_parse_and_select ("Top", window); +} + +/* Move to the node "(dir)Top". */ +DECLARE_INFO_COMMAND (info_dir_node, _("Select the node `(dir)'")) +{ + info_parse_and_select ("(dir)Top", window); +} + + +/* Read the name of a node to kill. The list of available nodes comes + from the nodes appearing in the current window configuration. */ +static char * +read_nodename_to_kill (WINDOW *window) +{ + int iw; + char *nodename; + INFO_WINDOW *info_win; + REFERENCE **menu = NULL; + int menu_index = 0, menu_slots = 0; + char *default_nodename = xstrdup (active_window->node->nodename); + char *prompt = xmalloc (strlen (_("Kill node (%s): ")) + strlen (default_nodename)); + + sprintf (prompt, _("Kill node (%s): "), default_nodename); + + for (iw = 0; (info_win = info_windows[iw]); iw++) + { + REFERENCE *entry = xmalloc (sizeof (REFERENCE)); + entry->label = xstrdup (info_win->window->node->nodename); + entry->filename = entry->nodename = NULL; + + add_pointer_to_array (entry, menu_index, menu, menu_slots, 10, + REFERENCE *); + } + + nodename = info_read_completing_in_echo_area (window, prompt, menu); + free (prompt); + info_free_references (menu); + if (nodename && !*nodename) + { + free (nodename); + nodename = default_nodename; + } + else + free (default_nodename); + + return nodename; +} + + +/* Delete NODENAME from this window, showing the most + recently selected node in this window. */ +static void +kill_node (WINDOW *window, char *nodename) +{ + int iw, i; + INFO_WINDOW *info_win; + NODE *temp; + + /* If there is no nodename to kill, quit now. */ + if (!nodename) + { + info_abort_key (window, 0, 0); + return; + } + + /* If there is a nodename, find it in our window list. */ + for (iw = 0; (info_win = info_windows[iw]); iw++) + if (strcmp (nodename, info_win->nodes[info_win->current]->nodename) == 0 + && info_win->window == window) + break; + + if (!info_win) + { + if (*nodename) + info_error (_("Cannot kill node `%s'"), nodename, NULL); + else + window_clear_echo_area (); + + return; + } + + /* If there are no more nodes left anywhere to view, complain and exit. */ + if (info_windows_index == 1 && info_windows[0]->nodes_index == 1) + { + info_error (_("Cannot kill the last node"), NULL, NULL); + return; + } + + /* INFO_WIN contains the node that the user wants to stop viewing. Delete + this node from the list of nodes previously shown in this window. */ + for (i = info_win->current; i < info_win->nodes_index; i++) + info_win->nodes[i] = info_win->nodes[i + 1]; + + /* There is one less node in this window's history list. */ + info_win->nodes_index--; + + /* Make this window show the most recent history node. */ + info_win->current = info_win->nodes_index - 1; + + /* If there aren't any nodes left in this window, steal one from the + next window. */ + if (info_win->current < 0) + { + INFO_WINDOW *stealer; + int which, pagetop; + long point; + + if (info_windows[iw + 1]) + stealer = info_windows[iw + 1]; + else + stealer = info_windows[0]; + + /* If the node being displayed in the next window is not the most + recently loaded one, get the most recently loaded one. */ + if ((stealer->nodes_index - 1) != stealer->current) + which = stealer->nodes_index - 1; + + /* Else, if there is another node behind the stealers current node, + use that one. */ + else if (stealer->current > 0) + which = stealer->current - 1; + + /* Else, just use the node appearing in STEALER's window. */ + else + which = stealer->current; + + /* Copy this node. */ + { + NODE *copy = xmalloc (sizeof (NODE)); + + temp = stealer->nodes[which]; + point = stealer->points[which]; + pagetop = stealer->pagetops[which]; + + copy->filename = temp->filename; + copy->parent = temp->parent; + copy->nodename = temp->nodename; + copy->contents = temp->contents; + copy->nodelen = temp->nodelen; + copy->flags = temp->flags; + copy->display_pos = temp->display_pos; + + temp = copy; + } + + window_set_node_of_window (info_win->window, temp); + window->point = point; + window->pagetop = pagetop; + remember_window_and_node (info_win->window, temp); + } + else + { + temp = info_win->nodes[info_win->current]; + temp->display_pos = info_win->points[info_win->current]; + window_set_node_of_window (info_win->window, temp); + } + + if (!info_error_was_printed) + window_clear_echo_area (); + + if (auto_footnotes_p) + info_get_or_remove_footnotes (window); +} + +/* Kill current node, thus going back one in the node history. I (karl) + do not think this is completely correct yet, because of the + window-changing stuff in kill_node, but it's a lot better than the + previous implementation, which did not account for nodes being + visited twice at all. */ +DECLARE_INFO_COMMAND (info_history_node, + _("Select the most recently selected node")) +{ + kill_node (window, active_window->node->nodename); +} + +/* Kill named node. */ +DECLARE_INFO_COMMAND (info_kill_node, _("Kill this node")) +{ + char *nodename = read_nodename_to_kill (window); + kill_node (window, nodename); +} + + +/* Read the name of a file and select the entire file. */ +DECLARE_INFO_COMMAND (info_view_file, _("Read the name of a file and select it")) +{ + char *line; + + line = info_read_in_echo_area (window, _("Find file: ")); + if (!line) + { + info_abort_key (active_window, 1, 0); + return; + } + + if (*line) + { + NODE *node; + + node = info_get_node (line, "*"); + if (!node) + { + if (info_recent_file_error) + info_error (info_recent_file_error, NULL, NULL); + else + info_error (_("Cannot find `%s'."), line, NULL); + } + else + info_set_node_of_window (1, window, node); + + free (line); + } + + if (!info_error_was_printed) + window_clear_echo_area (); +} + +/* **************************************************************** */ +/* */ +/* Dumping and Printing Nodes */ +/* */ +/* **************************************************************** */ + +#define VERBOSE_NODE_DUMPING +static void write_node_to_stream (NODE *node, FILE *stream); +static void dump_node_to_stream (char *filename, char *nodename, + FILE *stream, int dump_subnodes); +static void initialize_dumping (void); + +/* Dump the nodes specified by FILENAME and NODENAMES to the file named + in OUTPUT_FILENAME. If DUMP_SUBNODES is non-zero, recursively dump + the nodes which appear in the menu of each node dumped. */ +void +dump_nodes_to_file (char *filename, char **nodenames, + char *output_filename, int dump_subnodes) +{ + register int i; + FILE *output_stream; + + /* Get the stream to print the nodes to. Special case of an output + filename of "-" means to dump the nodes to stdout. */ + if (strcmp (output_filename, "-") == 0) + output_stream = stdout; + else + output_stream = fopen (output_filename, "w"); + + if (!output_stream) + { + info_error (_("Could not create output file `%s'."), + output_filename, NULL); + return; + } + + /* Print each node to stream. */ + initialize_dumping (); + for (i = 0; nodenames[i]; i++) + dump_node_to_stream (filename, nodenames[i], output_stream, dump_subnodes); + + if (output_stream != stdout) + fclose (output_stream); + +#if defined (VERBOSE_NODE_DUMPING) + info_error (_("Done."), NULL, NULL); +#endif /* VERBOSE_NODE_DUMPING */ +} + +/* A place to remember already dumped nodes. */ +static char **dumped_already = NULL; +static int dumped_already_index = 0; +static int dumped_already_slots = 0; + +static void +initialize_dumping (void) +{ + dumped_already_index = 0; +} + +/* Get and print the node specified by FILENAME and NODENAME to STREAM. + If DUMP_SUBNODES is non-zero, recursively dump the nodes which appear + in the menu of each node dumped. */ +static void +dump_node_to_stream (char *filename, char *nodename, + FILE *stream, int dump_subnodes) +{ + register int i; + NODE *node; + + node = info_get_node (filename, nodename); + + if (!node) + { + if (info_recent_file_error) + info_error (info_recent_file_error, NULL, NULL); + else + { + if (filename && *nodename != '(') + info_error (msg_cant_file_node, + filename_non_directory (filename), + nodename); + else + info_error (msg_cant_find_node, nodename, NULL); + } + return; + } + + /* If we have already dumped this node, don't dump it again. */ + for (i = 0; i < dumped_already_index; i++) + if (strcmp (node->nodename, dumped_already[i]) == 0) + { + free (node); + return; + } + add_pointer_to_array (node->nodename, dumped_already_index, dumped_already, + dumped_already_slots, 50, char *); + +#if defined (VERBOSE_NODE_DUMPING) + /* Maybe we should print some information about the node being output. */ + info_error (_("Writing node %s..."), node_printed_rep (node), NULL); +#endif /* VERBOSE_NODE_DUMPING */ + + write_node_to_stream (node, stream); + + /* If we are dumping subnodes, get the list of menu items in this node, + and dump each one recursively. */ + if (dump_subnodes) + { + REFERENCE **menu = NULL; + + /* If this node is an Index, do not dump the menu references. */ + if (string_in_line ("Index", node->nodename) == -1) + menu = info_menu_of_node (node); + + if (menu) + { + for (i = 0; menu[i]; i++) + { + /* We don't dump Info files which are different than the + current one. */ + if (!menu[i]->filename) + dump_node_to_stream + (filename, menu[i]->nodename, stream, dump_subnodes); + } + info_free_references (menu); + } + } + + free (node); +} + +/* Dump NODE to FILENAME. If DUMP_SUBNODES is non-zero, recursively dump + the nodes which appear in the menu of each node dumped. */ +void +dump_node_to_file (NODE *node, char *filename, int dump_subnodes) +{ + FILE *output_stream; + char *nodes_filename; + + /* Get the stream to print this node to. Special case of an output + filename of "-" means to dump the nodes to stdout. */ + if (strcmp (filename, "-") == 0) + output_stream = stdout; + else + output_stream = fopen (filename, "w"); + + if (!output_stream) + { + info_error (_("Could not create output file `%s'."), filename, + NULL); + return; + } + + if (node->parent) + nodes_filename = node->parent; + else + nodes_filename = node->filename; + + initialize_dumping (); + dump_node_to_stream + (nodes_filename, node->nodename, output_stream, dump_subnodes); + + if (output_stream != stdout) + fclose (output_stream); + +#if defined (VERBOSE_NODE_DUMPING) + info_error (_("Done."), NULL, NULL); +#endif /* VERBOSE_NODE_DUMPING */ +} + +#if !defined (DEFAULT_INFO_PRINT_COMMAND) +# define DEFAULT_INFO_PRINT_COMMAND "lpr" +#endif /* !DEFAULT_INFO_PRINT_COMMAND */ + +DECLARE_INFO_COMMAND (info_print_node, + _("Pipe the contents of this node through INFO_PRINT_COMMAND")) +{ + print_node (window->node); +} + +/* Print NODE on a printer piping it into INFO_PRINT_COMMAND. */ +void +print_node (NODE *node) +{ + FILE *printer_pipe; + char *print_command = getenv ("INFO_PRINT_COMMAND"); + int piping = 0; + + if (!print_command || !*print_command) + print_command = DEFAULT_INFO_PRINT_COMMAND; + + /* Note that on MS-DOS/MS-Windows, this MUST open the pipe in the + (default) text mode, since the printer drivers there need to see + DOS-style CRLF pairs at the end of each line. + + FIXME: if we are to support Mac-style text files, we might need + to convert the text here. */ + + /* INFO_PRINT_COMMAND which says ">file" means write to that file. + Presumably, the name of the file is the local printer device. */ + if (*print_command == '>') + printer_pipe = fopen (++print_command, "w"); + else + { + printer_pipe = popen (print_command, "w"); + piping = 1; + } + + if (!printer_pipe) + { + info_error (_("Cannot open pipe to `%s'."), print_command, NULL); + return; + } + +#if defined (VERBOSE_NODE_DUMPING) + /* Maybe we should print some information about the node being output. */ + info_error (_("Printing node %s..."), node_printed_rep (node), NULL); +#endif /* VERBOSE_NODE_DUMPING */ + + write_node_to_stream (node, printer_pipe); + if (piping) + pclose (printer_pipe); + else + fclose (printer_pipe); + +#if defined (VERBOSE_NODE_DUMPING) + info_error (_("Done."), NULL, NULL); +#endif /* VERBOSE_NODE_DUMPING */ +} + +static void +write_node_to_stream (NODE *node, FILE *stream) +{ + fwrite (node->contents, 1, node->nodelen, stream); +} + +/* **************************************************************** */ +/* */ +/* Info Searching Commands */ +/* */ +/* **************************************************************** */ + +/* Variable controlling the garbage collection of files briefly visited + during searches. Such files are normally gc'ed, unless they were + compressed to begin with. If this variable is non-zero, it says + to gc even those file buffer contents which had to be uncompressed. */ +int gc_compressed_files = 0; + +static void info_gc_file_buffers (void); +static void info_search_1 (WINDOW *window, int count, + unsigned char key, int case_sensitive, int ask_for_string); + +static char *search_string = NULL; +static int search_string_size = 0; +static int isearch_is_active = 0; + +static int last_search_direction = 0; +static int last_search_case_sensitive = 0; + +/* Return the file buffer which belongs to WINDOW's node. */ +FILE_BUFFER * +file_buffer_of_window (WINDOW *window) +{ + /* If this window has no node, then it has no file buffer. */ + if (!window->node) + return NULL; + + if (window->node->parent) + return info_find_file (window->node->parent); + + if (window->node->filename) + return info_find_file (window->node->filename); + + return NULL; +} + +/* Search for STRING in NODE starting at START. Return -1 if the string + was not found, or the location of the string if it was. If WINDOW is + passed as non-null, set the window's node to be NODE, its point to be + the found string, and readjust the window's pagetop. The DIR argument + says which direction to search in. If it is positive, search + forward, else backwards. + + The last argument, RESBND, makes sense only when USE_REGEX is set. + If the regexp search succeeds, RESBND is filled with the final state + of the search binding. In particular, its START and END fields contain + bounds of the found string instance. +*/ +static long +info_search_in_node_internal (char *string, NODE *node, long int start, + WINDOW *window, int dir, int case_sensitive, + SEARCH_BINDING *resbnd) +{ + SEARCH_BINDING binding; + long offset; + + binding.buffer = node->contents; + binding.start = start; + binding.end = node->nodelen; + binding.flags = 0; + if (!case_sensitive) + binding.flags |= S_FoldCase; + + if (dir < 0) + { + binding.end = 0; + binding.flags |= S_SkipDest; + } + + if (binding.start < 0) + return -1; + + /* For incremental searches, we always wish to skip past the string. */ + if (isearch_is_active) + binding.flags |= S_SkipDest; + + offset = (use_regex ? + regexp_search (string, &binding, node->nodelen, resbnd): + search (string, &binding)); + + if (offset != -1 && window) + { + set_remembered_pagetop_and_point (window); + if (window->node != node) + window_set_node_of_window (window, node); + window->point = offset; + window_adjust_pagetop (window); + } + return offset; +} + +long +info_search_in_node (char *string, NODE *node, long int start, + WINDOW *window, int dir, int case_sensitive) +{ + return info_search_in_node_internal (string, node, start, + window, dir, case_sensitive, NULL); +} + +/* Search NODE, looking for the largest possible match of STRING. Start the + search at START. Return the absolute position of the match, or -1, if + no part of the string could be found. */ +long +info_target_search_node (NODE *node, char *string, long int start) +{ + register int i; + long offset = 0; + char *target; + + target = xstrdup (string); + i = strlen (target); + + /* Try repeatedly searching for this string while removing words from + the end of it. */ + while (i) + { + target[i] = '\0'; + offset = info_search_in_node (target, node, start, NULL, 1, 0); + + if (offset != -1) + break; + + /* Delete the last word from TARGET. */ + for (; i && (!whitespace (target[i]) && (target[i] != ',')); i--); + } + free (target); + return offset; +} + +/* Search for STRING starting in WINDOW. The starting position is determined + by DIR and RESBND argument. If the latter is given, and its START field + is not -1, it gives starting position. Otherwise, the search begins at + window point + DIR. + + If the string is found in this node, set point to that position. + Otherwise, get the file buffer associated with WINDOW's node, and search + through each node in that file. + + If the search succeeds and RESBND is given, its START and END fields + contain bounds of the found string instance (only for regexp searches). + + If the search fails, return non-zero, else zero. Side-effect window + leaving the node and point where the string was found current. */ +static int +info_search_internal (char *string, WINDOW *window, + int dir, int case_sensitive, + SEARCH_BINDING *resbnd) +{ + register int i; + FILE_BUFFER *file_buffer; + char *initial_nodename; + long ret, start; + + file_buffer = file_buffer_of_window (window); + initial_nodename = window->node->nodename; + + if (resbnd && resbnd->start != -1) + start = resbnd->start; + else + /* This used to begin from window->point, unless this was a repeated + search command. But invoking search with an argument loses with + that logic, since info_last_executed_command is then set to + info_add_digit_to_numeric_arg. I think there's no sense in + ``finding'' a string that is already under the cursor, anyway. */ + start = window->point + dir; + + ret = info_search_in_node_internal + (string, window->node, start, window, dir, + case_sensitive, resbnd); + + if (ret != -1) + { + /* We won! */ + if (!echo_area_is_active && !isearch_is_active) + window_clear_echo_area (); + return 0; + } + + start = 0; + + /* The string wasn't found in the current node. Search through the + window's file buffer, iff the current node is not "*". */ + if (!file_buffer || (strcmp (initial_nodename, "*") == 0)) + return -1; + + /* If this file has tags, search through every subfile, starting at + this node's subfile and node. Otherwise, search through the + file's node list. */ + if (file_buffer->tags) + { + register int current_tag = 0, number_of_tags; + char *last_subfile; + TAG *tag; + char *msg = NULL; + + /* Find number of tags and current tag. */ + last_subfile = NULL; + for (i = 0; file_buffer->tags[i]; i++) + if (strcmp (initial_nodename, file_buffer->tags[i]->nodename) == 0) + { + current_tag = i; + last_subfile = file_buffer->tags[i]->filename; + } + + number_of_tags = i; + + /* If there is no last_subfile, our tag wasn't found. */ + if (!last_subfile) + return -1; + + /* Search through subsequent nodes, wrapping around to the top + of the info file until we find the string or return to this + window's node and point. */ + while (1) + { + NODE *node; + + /* Allow C-g to quit the search, failing it if pressed. */ + return_if_control_g (-1); + + /* Find the next tag that isn't an anchor. */ + for (i = current_tag + dir; i != current_tag; i += dir) + { + if (i < 0) + { + msg = N_("Search continued from the end of the document."); + i = number_of_tags - 1; + } + else if (i == number_of_tags) + { + msg = N_("Search continued from the beginning of the document."); + i = 0; + } + + tag = file_buffer->tags[i]; + if (tag->nodelen != 0) + break; + } + + /* If we got past out starting point, bail out. */ + if (i == current_tag) + return -1; + current_tag = i; + + if (!echo_area_is_active && (last_subfile != tag->filename)) + { + window_message_in_echo_area + (_("Searching subfile %s ..."), + filename_non_directory (tag->filename), NULL); + + last_subfile = tag->filename; + } + + node = info_get_node (file_buffer->filename, tag->nodename); + + if (!node) + { + /* If not doing i-search... */ + if (!echo_area_is_active) + { + if (info_recent_file_error) + info_error (info_recent_file_error, NULL, NULL); + else + info_error (msg_cant_file_node, + filename_non_directory (file_buffer->filename), + tag->nodename); + } + return -1; + } + + if (dir < 0) + start = tag->nodelen; + + ret = + info_search_in_node_internal (string, node, start, window, dir, + case_sensitive, resbnd); + + /* Did we find the string in this node? */ + if (ret != -1) + { + /* Yes! We win. */ + remember_window_and_node (window, node); + if (!echo_area_is_active) + { + if (msg) + window_message_in_echo_area ("%s", (char *) _(msg), NULL); + else + window_clear_echo_area (); + } + return 0; + } + + /* No. Free this node, and make sure that we haven't passed + our starting point. */ + free (node); + + if (strcmp (initial_nodename, tag->nodename) == 0) + return -1; + } + } + return -1; +} + +DECLARE_INFO_COMMAND (info_search_case_sensitively, + _("Read a string and search for it case-sensitively")) +{ + last_search_direction = count > 0 ? 1 : -1; + last_search_case_sensitive = 1; + info_search_1 (window, count, key, 1, 1); +} + +DECLARE_INFO_COMMAND (info_search, _("Read a string and search for it")) +{ + last_search_direction = count > 0 ? 1 : -1; + last_search_case_sensitive = 0; + info_search_1 (window, count, key, 0, 1); +} + +DECLARE_INFO_COMMAND (info_search_backward, + _("Read a string and search backward for it")) +{ + last_search_direction = count > 0 ? -1 : 1; + last_search_case_sensitive = 0; + info_search_1 (window, -count, key, 0, 1); +} + +static void +info_search_1 (WINDOW *window, int count, unsigned char key, + int case_sensitive, int ask_for_string) +{ + char *line, *prompt; + int result, old_pagetop; + int direction; + + if (count < 0) + { + direction = -1; + count = -count; + } + else + { + direction = 1; + if (count == 0) + count = 1; /* for backward compatibility */ + } + + /* Read a string from the user, defaulting the search to SEARCH_STRING. */ + if (!search_string) + { + search_string = xmalloc (search_string_size = 100); + search_string[0] = '\0'; + } + + if (ask_for_string) + { + prompt = xmalloc (strlen (_("%s%s%s [%s]: ")) + + strlen (_("Regexp search")) + + strlen (_(" case-sensitively")) + + strlen (_(" backward")) + + strlen (search_string)); + + sprintf (prompt, _("%s%s%s [%s]: "), + use_regex ? _("Regexp search") : _("Search"), + case_sensitive ? _(" case-sensitively") : "", + direction < 0 ? _(" backward") : "", + search_string); + + line = info_read_in_echo_area (window, prompt); + free (prompt); + + if (!line) + { + info_abort_key (window, 0, 0); + return; + } + + if (*line) + { + if (strlen (line) + 1 > (unsigned int) search_string_size) + search_string = xrealloc + (search_string, (search_string_size += 50 + strlen (line))); + + strcpy (search_string, line); + free (line); + } + } + + /* If the search string includes upper-case letters, make the search + case-sensitive. */ + if (case_sensitive == 0) + for (line = search_string; *line; line++) + if (isupper (*line)) + { + case_sensitive = 1; + break; + } + + old_pagetop = active_window->pagetop; + for (result = 0; result == 0 && count--; ) + result = info_search_internal (search_string, + active_window, direction, case_sensitive, + NULL); + + if (result != 0 && !info_error_was_printed) + info_error (_("Search failed."), NULL, NULL); + else if (old_pagetop != active_window->pagetop) + { + int new_pagetop; + + new_pagetop = active_window->pagetop; + active_window->pagetop = old_pagetop; + set_window_pagetop (active_window, new_pagetop); + if (auto_footnotes_p) + info_get_or_remove_footnotes (active_window); + } + + /* Perhaps free the unreferenced file buffers that were searched, but + not retained. */ + info_gc_file_buffers (); +} + +DECLARE_INFO_COMMAND (info_search_next, + _("Repeat last search in the same direction")) +{ + if (!last_search_direction) + info_error (_("No previous search string"), NULL, NULL); + else + info_search_1 (window, last_search_direction * count, + key, last_search_case_sensitive, 0); +} + +DECLARE_INFO_COMMAND (info_search_previous, + _("Repeat last search in the reverse direction")) +{ + if (!last_search_direction) + info_error (_("No previous search string"), NULL, NULL); + else + info_search_1 (window, -last_search_direction * count, + key, last_search_case_sensitive, 0); +} + +/* **************************************************************** */ +/* */ +/* Incremental Searching */ +/* */ +/* **************************************************************** */ + +static void incremental_search (WINDOW *window, int count, + unsigned char ignore); + +DECLARE_INFO_COMMAND (isearch_forward, + _("Search interactively for a string as you type it")) +{ + incremental_search (window, count, key); +} + +DECLARE_INFO_COMMAND (isearch_backward, + _("Search interactively for a string as you type it")) +{ + incremental_search (window, -count, key); +} + +/* Incrementally search for a string as it is typed. */ +/* The last accepted incremental search string. */ +static char *last_isearch_accepted = NULL; + +/* The current incremental search string. */ +static char *isearch_string = NULL; +static int isearch_string_index = 0; +static int isearch_string_size = 0; +static unsigned char isearch_terminate_search_key = ESC; + +/* Array of search states. */ +static SEARCH_STATE **isearch_states = NULL; +static int isearch_states_index = 0; +static int isearch_states_slots = 0; + +/* Push the state of this search. */ +static void +push_isearch (WINDOW *window, int search_index, int direction, int failing) +{ + SEARCH_STATE *state; + + state = xmalloc (sizeof (SEARCH_STATE)); + window_get_state (window, state); + state->search_index = search_index; + state->direction = direction; + state->failing = failing; + + add_pointer_to_array (state, isearch_states_index, isearch_states, + isearch_states_slots, 20, SEARCH_STATE *); +} + +/* Pop the state of this search to WINDOW, SEARCH_INDEX, and DIRECTION. */ +static void +pop_isearch (WINDOW *window, int *search_index, int *direction, int *failing) +{ + SEARCH_STATE *state; + + if (isearch_states_index) + { + isearch_states_index--; + state = isearch_states[isearch_states_index]; + window_set_state (window, state); + *search_index = state->search_index; + *direction = state->direction; + *failing = state->failing; + + free (state); + isearch_states[isearch_states_index] = NULL; + } +} + +/* Free the memory used by isearch_states. */ +static void +free_isearch_states (void) +{ + register int i; + + for (i = 0; i < isearch_states_index; i++) + { + free (isearch_states[i]); + isearch_states[i] = NULL; + } + isearch_states_index = 0; +} + +/* Display the current search in the echo area. */ +static void +show_isearch_prompt (int dir, unsigned char *string, int failing_p) +{ + register int i; + const char *prefix; + char *prompt, *p_rep; + unsigned int prompt_len, p_rep_index, p_rep_size; + + if (dir < 0) + prefix = use_regex ? _("Regexp I-search backward: ") + : _("I-search backward: "); + else + prefix = use_regex ? _("Regexp I-search: ") + : _("I-search: "); + + p_rep_index = p_rep_size = 0; + p_rep = NULL; + for (i = 0; string[i]; i++) + { + char *rep; + + switch (string[i]) + { + case ' ': rep = " "; break; + case LFD: rep = "\\n"; break; + case TAB: rep = "\\t"; break; + default: + rep = pretty_keyname (string[i]); + } + if ((p_rep_index + strlen (rep) + 1) >= p_rep_size) + p_rep = xrealloc (p_rep, p_rep_size += 100); + + strcpy (p_rep + p_rep_index, rep); + p_rep_index += strlen (rep); + } + + prompt_len = strlen (prefix) + p_rep_index + 1; + if (failing_p) + prompt_len += strlen (_("Failing ")); + prompt = xmalloc (prompt_len); + sprintf (prompt, "%s%s%s", failing_p ? _("Failing ") : "", prefix, + p_rep ? p_rep : ""); + + window_message_in_echo_area ("%s", prompt, NULL); + maybe_free (p_rep); + free (prompt); + display_cursor_at_point (active_window); +} + +static void +incremental_search (WINDOW *window, int count, unsigned char ignore) +{ + unsigned char key; + int last_search_result, search_result, dir; + SEARCH_STATE mystate, orig_state; + char *p; + int case_sensitive = 0; + SEARCH_BINDING bnd; + + bnd.start = -1; + + if (count < 0) + dir = -1; + else + dir = 1; + + last_search_result = search_result = 0; + + window_get_state (window, &orig_state); + + isearch_string_index = 0; + if (!isearch_string_size) + isearch_string = xmalloc (isearch_string_size = 50); + + /* Show the search string in the echo area. */ + isearch_string[isearch_string_index] = '\0'; + show_isearch_prompt (dir, (unsigned char *) isearch_string, search_result); + + isearch_is_active = 1; + + while (isearch_is_active) + { + VFunction *func = NULL; + int quoted = 0; + + /* If a recent display was interrupted, then do the redisplay now if + it is convenient. */ + if (!info_any_buffered_input_p () && display_was_interrupted_p) + { + display_update_one_window (window); + display_cursor_at_point (active_window); + } + + /* Read a character and dispatch on it. */ + key = info_get_input_char (); + window_get_state (window, &mystate); + + if (key == DEL || key == Control ('h')) + { + /* User wants to delete one level of search? */ + if (!isearch_states_index) + { + terminal_ring_bell (); + continue; + } + else + { + pop_isearch + (window, &isearch_string_index, &dir, &search_result); + isearch_string[isearch_string_index] = '\0'; + show_isearch_prompt (dir, (unsigned char *) isearch_string, + search_result); + goto after_search; + } + } + else if (key == Control ('q')) + { + key = info_get_input_char (); + quoted = 1; + } + + /* We are about to search again, or quit. Save the current search. */ + push_isearch (window, isearch_string_index, dir, search_result); + + if (quoted) + goto insert_and_search; + + if (!Meta_p (key) || key > 32) + { + /* If this key is not a keymap, get its associated function, + if any. If it is a keymap, then it's probably ESC from an + arrow key, and we handle that case below. */ + char type = window->keymap[key].type; + func = type == ISFUNC + ? InfoFunction(window->keymap[key].function) + : NULL; /* function member is a Keymap if ISKMAP */ + + if (isprint (key) || (type == ISFUNC && func == NULL)) + { + insert_and_search: + + if (isearch_string_index + 2 >= isearch_string_size) + isearch_string = xrealloc + (isearch_string, isearch_string_size += 100); + + isearch_string[isearch_string_index++] = key; + isearch_string[isearch_string_index] = '\0'; + goto search_now; + } + else if (func == (VFunction *) isearch_forward + || func == (VFunction *) isearch_backward) + { + /* If this key invokes an incremental search, then this + means that we will either search again in the same + direction, search again in the reverse direction, or + insert the last search string that was accepted through + incremental searching. */ + if ((func == (VFunction *) isearch_forward && dir > 0) || + (func == (VFunction *) isearch_backward && dir < 0)) + { + /* If the user has typed no characters, then insert the + last successful search into the current search string. */ + if (isearch_string_index == 0) + { + /* Of course, there must be something to insert. */ + if (last_isearch_accepted) + { + if (strlen ((char *) last_isearch_accepted) + 1 + >= (unsigned int) isearch_string_size) + isearch_string = (char *) + xrealloc (isearch_string, + isearch_string_size += 10 + + strlen (last_isearch_accepted)); + strcpy (isearch_string, last_isearch_accepted); + isearch_string_index = strlen (isearch_string); + goto search_now; + } + else + continue; + } + else + { + /* Search again in the same direction. This means start + from a new place if the last search was successful. */ + if (search_result == 0) + { + window->point += dir; + bnd.start = -1; + } + } + } + else + { + /* Reverse the direction of the search. */ + dir = -dir; + } + } + else if (func == (VFunction *) info_abort_key) + { + /* If C-g pressed, and the search is failing, pop the search + stack back to the last unfailed search. */ + if (isearch_states_index && (search_result != 0)) + { + terminal_ring_bell (); + while (isearch_states_index && (search_result != 0)) + pop_isearch + (window, &isearch_string_index, &dir, &search_result); + isearch_string[isearch_string_index] = '\0'; + show_isearch_prompt (dir, (unsigned char *) isearch_string, + search_result); + continue; + } + else + goto exit_search; + } + else + goto exit_search; + } + else + { + exit_search: + /* The character is not printable, or it has a function which is + non-null. Exit the search, remembering the search string. If + the key is not the same as the isearch_terminate_search_key, + then push it into pending input. */ + if (isearch_string_index && func != (VFunction *) info_abort_key) + { + maybe_free (last_isearch_accepted); + last_isearch_accepted = xstrdup (isearch_string); + } + + /* If the key is the isearch_terminate_search_key, but some buffered + input is pending, it is almost invariably because the ESC key is + actually the beginning of an escape sequence, like in case they + pressed an arrow key. So don't gobble the ESC key, push it back + into pending input. */ + /* FIXME: this seems like a kludge! We need a more reliable + mechanism to know when ESC is a separate key and when it is + part of an escape sequence. */ + if (key != RET /* Emacs addicts want RET to get lost */ + && (key != isearch_terminate_search_key + || info_any_buffered_input_p ())) + info_set_pending_input (key); + + if (func == (VFunction *) info_abort_key) + { + if (isearch_states_index) + window_set_state (window, &orig_state); + } + + if (!echo_area_is_active) + window_clear_echo_area (); + + if (auto_footnotes_p) + info_get_or_remove_footnotes (active_window); + + isearch_is_active = 0; + continue; + } + + /* Search for the contents of isearch_string. */ + search_now: + show_isearch_prompt (dir, (unsigned char *) isearch_string, search_result); + + /* If the search string includes upper-case letters, make the + search case-sensitive. */ + for (p = isearch_string; *p; p++) + if (isupper (*p)) + { + case_sensitive = 1; + break; + } + + /* Regex isearch means we better search again every time. We + might have had a failed search for "\", for example, but now we + have "\.". */ + if (use_regex) + { + search_result = info_search_internal (isearch_string, + window, dir, case_sensitive, + &bnd); + } + else if (search_result == 0) + { /* We test for search_result being zero because a non-zero + value means the string was not found in entire document. */ + /* Check to see if the current search string is right here. If + we are looking at it, then don't bother calling the search + function. */ + if (((dir < 0) && + ((case_sensitive ? strncmp : mbsncasecmp) + (window->node->contents + window->point, + isearch_string, isearch_string_index) == 0)) || + ((dir > 0) && + ((window->point - isearch_string_index) >= 0) && + ((case_sensitive ? strncmp : mbsncasecmp) + (window->node->contents + + (window->point - (isearch_string_index - 1)), + isearch_string, isearch_string_index) == 0))) + { + if (dir > 0) + window->point++; + } + else + search_result = info_search_internal (isearch_string, + window, dir, case_sensitive, + NULL); + } + + /* If this search failed, and we didn't already have a failed search, + then ring the terminal bell. */ + if (search_result != 0 && last_search_result == 0) + terminal_ring_bell (); + + after_search: + show_isearch_prompt (dir, (unsigned char *) isearch_string, search_result); + + if (search_result == 0) + { + if ((mystate.node == window->node) && + (mystate.pagetop != window->pagetop)) + { + int newtop = window->pagetop; + window->pagetop = mystate.pagetop; + set_window_pagetop (window, newtop); + } + display_update_one_window (window); + display_cursor_at_point (window); + } + + last_search_result = search_result; + } + + /* Free the memory used to remember each search state. */ + free_isearch_states (); + + /* Perhaps GC some file buffers. */ + info_gc_file_buffers (); + + /* After searching, leave the window in the correct state. */ + if (!echo_area_is_active) + window_clear_echo_area (); +} + +/* GC some file buffers. A file buffer can be gc-ed if there we have + no nodes in INFO_WINDOWS that reference this file buffer's contents. + Garbage collecting a file buffer means to free the file buffers + contents. */ +static void +info_gc_file_buffers (void) +{ + register int fb_index, iw_index, i; + register FILE_BUFFER *fb; + register INFO_WINDOW *iw; + + if (!info_loaded_files) + return; + + for (fb_index = 0; (fb = info_loaded_files[fb_index]); fb_index++) + { + int fb_referenced_p = 0; + + /* If already gc-ed, do nothing. */ + if (!fb->contents) + continue; + + /* If this file had to be uncompressed, check to see if we should + gc it. This means that the user-variable "gc-compressed-files" + is non-zero. */ + if ((fb->flags & N_IsCompressed) && !gc_compressed_files) + continue; + + /* If this file's contents are not gc-able, move on. */ + if (fb->flags & N_CannotGC) + continue; + + /* Check each INFO_WINDOW to see if it has any nodes which reference + this file. */ + for (iw_index = 0; (iw = info_windows[iw_index]); iw_index++) + { + for (i = 0; iw->nodes && iw->nodes[i]; i++) + { + if ((FILENAME_CMP (fb->fullpath, iw->nodes[i]->filename) == 0) || + (FILENAME_CMP (fb->filename, iw->nodes[i]->filename) == 0)) + { + fb_referenced_p = 1; + break; + } + } + } + + /* If this file buffer wasn't referenced, free its contents. */ + if (!fb_referenced_p) + { + free (fb->contents); + fb->contents = NULL; + } + } +} + +/* **************************************************************** */ +/* */ +/* Traversing and Selecting References */ +/* */ +/* **************************************************************** */ + +/* Move to the next or previous cross reference in this node. */ +static int +info_move_to_xref (WINDOW *window, int count, unsigned char key, int dir) +{ + long firstmenu, firstxref; + long nextmenu, nextxref; + long placement = -1; + long start = 0; + NODE *node = window->node; + int save_use_regex = use_regex; + + /* Most of our keywords contain * characters; don't use regexes. */ + use_regex = 0; + + if (dir < 0) + start = node->nodelen; + + /* This search is only allowed to fail if there is no menu or cross + reference in the current node. Otherwise, the first menu or xref + found is moved to. */ + + firstmenu = info_search_in_node + (INFO_MENU_ENTRY_LABEL, node, start, NULL, dir, 0); + + /* FIRSTMENU may point directly to the line defining the menu. Skip that + and go directly to the first item. */ + + if (firstmenu != -1) + { + char *text = node->contents + firstmenu; + + if (strncmp (text, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL)) == 0) + firstmenu = info_search_in_node + (INFO_MENU_ENTRY_LABEL, node, firstmenu + dir, NULL, dir, 0); + } + + firstxref = + info_search_in_node (INFO_XREF_LABEL, node, start, NULL, dir, 0); + +#if defined (HANDLE_MAN_PAGES) + if ((firstxref == -1) && (node->flags & N_IsManPage)) + { + firstxref = locate_manpage_xref (node, start, dir); + } +#endif /* HANDLE_MAN_PAGES */ + + if (firstmenu == -1 && firstxref == -1) + { + if (!cursor_movement_scrolls_p) + info_error (msg_no_xref_node, NULL, NULL); + use_regex = save_use_regex; + return cursor_movement_scrolls_p; + } + + /* There is at least one cross reference or menu entry in this node. + Try hard to find the next available one. */ + + nextmenu = info_search_in_node + (INFO_MENU_ENTRY_LABEL, node, window->point + dir, NULL, dir, 0); + + nextxref = info_search_in_node + (INFO_XREF_LABEL, node, window->point + dir, NULL, dir, 0); + +#if defined (HANDLE_MAN_PAGES) + if ((nextxref == -1) && (node->flags & N_IsManPage) && (firstxref != -1)) + nextxref = locate_manpage_xref (node, window->point + dir, dir); +#endif /* HANDLE_MAN_PAGES */ + + /* Ignore "Menu:" as a menu item. */ + if (nextmenu != -1) + { + char *text = node->contents + nextmenu; + + if (strncmp (text, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL)) == 0) + nextmenu = info_search_in_node + (INFO_MENU_ENTRY_LABEL, node, nextmenu + dir, NULL, dir, 0); + } + + /* No more searches, back to whatever the user wanted. */ + use_regex = save_use_regex; + + /* If there is both a next menu entry, and a next xref entry, choose the + one which occurs first. Otherwise, select the one which actually + appears in this node following point. */ + if (nextmenu != -1 && nextxref != -1) + { + if (((dir == 1) && (nextmenu < nextxref)) || + ((dir == -1) && (nextmenu > nextxref))) + placement = nextmenu + 1; + else + placement = nextxref; + } + else if (nextmenu != -1) + placement = nextmenu + 1; + else if (nextxref != -1) + placement = nextxref; + + /* If there was neither a menu or xref entry appearing in this node after + point, choose the first menu or xref entry appearing in this node. */ + if (placement == -1) + { + if (cursor_movement_scrolls_p) + return 1; + else + { + if (firstmenu != -1 && firstxref != -1) + { + if (((dir == 1) && (firstmenu < firstxref)) || + ((dir == -1) && (firstmenu > firstxref))) + placement = firstmenu + 1; + else + placement = firstxref; + } + else if (firstmenu != -1) + placement = firstmenu + 1; + else + placement = firstxref; + } + } + window->point = placement; + window_adjust_pagetop (window); + window->flags |= W_UpdateWindow; + return 0; +} + +DECLARE_INFO_COMMAND (info_move_to_prev_xref, + _("Move to the previous cross reference")) +{ + if (count < 0) + info_move_to_prev_xref (window, -count, key); + else + { + while (info_move_to_xref (window, count, key, -1)) + { + info_error_was_printed = 0; + if (backward_move_node_structure (window, info_scroll_behaviour)) + break; + move_to_new_line (window->line_count, window->line_count - 1, + window); + } + } +} + +DECLARE_INFO_COMMAND (info_move_to_next_xref, + _("Move to the next cross reference")) +{ + if (count < 0) + info_move_to_next_xref (window, -count, key); + else + { + /* Note: This can cause some blinking when the next cross reference is + located several nodes further. This effect can be easily suppressed + by setting display_inhibited to 1, however this will also make + error messages to be dumped on stderr, instead on the echo area. */ + while (info_move_to_xref (window, count, key, 1)) + { + info_error_was_printed = 0; + if (forward_move_node_structure (window, info_scroll_behaviour)) + break; + move_to_new_line (0, 0, window); + } + } +} + +/* Select the menu item or reference that appears on this line. */ +DECLARE_INFO_COMMAND (info_select_reference_this_line, + _("Select reference or menu item appearing on this line")) +{ + char *line; + + if (window->line_starts) + line = window->line_starts[window_line_of_point (window)]; + else + line = ""; + + /* If this line contains a menu item, select that one. */ + if (strncmp ("* ", line, 2) == 0) + info_menu_or_ref_item (window, count, key, info_menu_of_node, 0); + else + info_menu_or_ref_item (window, count, key, info_xrefs_of_node, 0); +} + +/* **************************************************************** */ +/* */ +/* Miscellaneous Info Commands */ +/* */ +/* **************************************************************** */ + +/* What to do when C-g is pressed in a window. */ +DECLARE_INFO_COMMAND (info_abort_key, _("Cancel current operation")) +{ + /* If error printing doesn't oridinarily ring the bell, do it now, + since C-g always rings the bell. Otherwise, let the error printer + do it. */ + if (!info_error_rings_bell_p) + terminal_ring_bell (); + info_error (_("Quit"), NULL, NULL); + + info_initialize_numeric_arg (); + info_clear_pending_input (); + info_last_executed_command = NULL; +} + +/* Move the cursor to the desired line of the window. */ +DECLARE_INFO_COMMAND (info_move_to_window_line, + _("Move the cursor to a specific line of the window")) +{ + int line; + + /* With no numeric argument of any kind, default to the center line. */ + if (!info_explicit_arg && count == 1) + line = (window->height / 2) + window->pagetop; + else + { + if (count < 0) + line = (window->height + count) + window->pagetop; + else + line = window->pagetop + count; + } + + /* If the line doesn't appear in this window, make it do so. */ + if ((line - window->pagetop) >= window->height) + line = window->pagetop + (window->height - 1); + + /* If the line is too small, make it fit. */ + if (line < window->pagetop) + line = window->pagetop; + + /* If the selected line is past the bottom of the node, force it back. */ + if (line >= window->line_count) + line = window->line_count - 1; + + window->point = (window->line_starts[line] - window->node->contents); +} + +/* Clear the screen and redraw its contents. Given a numeric argument, + move the line the cursor is on to the COUNT'th line of the window. */ +DECLARE_INFO_COMMAND (info_redraw_display, _("Redraw the display")) +{ + if ((!info_explicit_arg && count == 1) || echo_area_is_active) + { + terminal_clear_screen (); + display_clear_display (the_display); + window_mark_chain (windows, W_UpdateWindow); + display_update_display (windows); + } + else + { + int desired_line, point_line; + int new_pagetop; + + point_line = window_line_of_point (window) - window->pagetop; + + if (count < 0) + desired_line = window->height + count; + else + desired_line = count; + + if (desired_line < 0) + desired_line = 0; + + if (desired_line >= window->height) + desired_line = window->height - 1; + + if (desired_line == point_line) + return; + + new_pagetop = window->pagetop + (point_line - desired_line); + + set_window_pagetop (window, new_pagetop); + } +} +/* This command does nothing. It is the fact that a key is bound to it + that has meaning. See the code at the top of info_session (). */ +DECLARE_INFO_COMMAND (info_quit, _("Quit using Info")) +{} + + +/* **************************************************************** */ +/* */ +/* Reading Keys and Dispatching on Them */ +/* */ +/* **************************************************************** */ + +/* Declaration only. Special cased in info_dispatch_on_key (). + Doc string is to avoid ugly results with describe_key etc. */ +DECLARE_INFO_COMMAND (info_do_lowercase_version, + _("Run command bound to this key's lowercase variant")) +{} + +static void +dispatch_error (char *keyseq) +{ + char *rep; + + rep = pretty_keyseq (keyseq); + + if (!echo_area_is_active) + info_error (_("Unknown command (%s)."), rep, NULL); + else + { + char *temp = xmalloc (1 + strlen (rep) + strlen (_("\"%s\" is invalid"))); + sprintf (temp, _("`%s' is invalid"), rep); + terminal_ring_bell (); + inform_in_echo_area (temp); + free (temp); + } +} + +/* Keeping track of key sequences. */ +static char *info_keyseq = NULL; +static int info_keyseq_index = 0; +static int info_keyseq_size = 0; +static int info_keyseq_displayed_p = 0; + +/* Initialize the length of the current key sequence. */ +void +initialize_keyseq (void) +{ + info_keyseq_index = 0; + info_keyseq_displayed_p = 0; +} + +/* Add CHARACTER to the current key sequence. */ +void +add_char_to_keyseq (char character) +{ + if (info_keyseq_index + 2 >= info_keyseq_size) + info_keyseq = (char *)xrealloc (info_keyseq, info_keyseq_size += 10); + + info_keyseq[info_keyseq_index++] = character; + info_keyseq[info_keyseq_index] = '\0'; +} + +/* Display the current value of info_keyseq. If argument EXPECTING is + non-zero, input is expected to be read after the key sequence is + displayed, so add an additional prompting character to the sequence. */ +static void +display_info_keyseq (int expecting_future_input) +{ + char *rep; + + rep = pretty_keyseq (info_keyseq); + if (expecting_future_input) + strcat (rep, "-"); + + if (echo_area_is_active) + inform_in_echo_area (rep); + else + { + window_message_in_echo_area (rep, NULL, NULL); + display_cursor_at_point (active_window); + } + info_keyseq_displayed_p = 1; +} + +/* Called by interactive commands to read a keystroke. */ +unsigned char +info_get_another_input_char (void) +{ + int ready = !info_keyseq_displayed_p; /* ready if new and pending key */ + + /* If there isn't any input currently available, then wait a + moment looking for input. If we don't get it fast enough, + prompt a little bit with the current key sequence. */ + if (!info_keyseq_displayed_p) + { + ready = 1; + if (!info_any_buffered_input_p () && + !info_input_pending_p ()) + { +#if defined (FD_SET) + struct timeval timer; + fd_set readfds; + + FD_ZERO (&readfds); + FD_SET (fileno (info_input_stream), &readfds); + timer.tv_sec = 1; + timer.tv_usec = 750; + ready = select (fileno(info_input_stream)+1, &readfds, + NULL, NULL, &timer); +#else + ready = 0; +#endif /* FD_SET */ + } + } + + if (!ready) + display_info_keyseq (1); + + return info_get_input_char (); +} + +/* Do the command associated with KEY in MAP. If the associated command is + really a keymap, then read another key, and dispatch into that map. */ +void +info_dispatch_on_key (unsigned char key, Keymap map) +{ +#if !defined(INFOKEY) + if (Meta_p (key) && (!ISO_Latin_p || map[key].function != ea_insert)) + { + if (map[ESC].type == ISKMAP) + { + map = (Keymap)map[ESC].function; + add_char_to_keyseq (ESC); + key = UnMeta (key); + info_dispatch_on_key (key, map); + } + else + { + dispatch_error (info_keyseq); + } + return; + } +#endif /* INFOKEY */ + + switch (map[key].type) + { + case ISFUNC: + { + VFunction *func; + + func = InfoFunction(map[key].function); + if (func != NULL) + { + /* Special case info_do_lowercase_version (). */ + if (func == (VFunction *) info_do_lowercase_version) + { +#if defined(INFOKEY) + unsigned char lowerkey; + + lowerkey = Meta_p(key) ? Meta (tolower (UnMeta (key))) : tolower (key); + if (lowerkey == key) + { + add_char_to_keyseq (key); + dispatch_error (info_keyseq); + return; + } + info_dispatch_on_key (lowerkey, map); +#else /* !INFOKEY */ + info_dispatch_on_key (tolower (key), map); +#endif /* INFOKEY */ + return; + } + + add_char_to_keyseq (key); + + if (info_keyseq_displayed_p) + display_info_keyseq (0); + + { + WINDOW *where; + + where = active_window; + (*InfoFunction(map[key].function)) + (active_window, info_numeric_arg * info_numeric_arg_sign, key); + + /* If we have input pending, then the last command was a prefix + command. Don't change the value of the last function vars. + Otherwise, remember the last command executed in the var + appropriate to the window in which it was executed. */ + if (!info_input_pending_p ()) + { + if (where == the_echo_area) + ea_last_executed_command = InfoFunction(map[key].function); + else + info_last_executed_command = InfoFunction(map[key].function); + } + } + } + else + { + add_char_to_keyseq (key); + dispatch_error (info_keyseq); + return; + } + } + break; + + case ISKMAP: + add_char_to_keyseq (key); + if (map[key].function != NULL) + { + unsigned char newkey; + + newkey = info_get_another_input_char (); + info_dispatch_on_key (newkey, (Keymap)map[key].function); + } + else + { + dispatch_error (info_keyseq); + return; + } + break; + } +} + +/* **************************************************************** */ +/* */ +/* Numeric Arguments */ +/* */ +/* **************************************************************** */ + +/* Handle C-u style numeric args, as well as M--, and M-digits. */ + +/* Non-zero means that an explicit argument has been passed to this + command, as in C-u C-v. */ +int info_explicit_arg = 0; + +/* The sign of the numeric argument. */ +int info_numeric_arg_sign = 1; + +/* The value of the argument itself. */ +int info_numeric_arg = 1; + +/* Add the current digit to the argument in progress. */ +DECLARE_INFO_COMMAND (info_add_digit_to_numeric_arg, + _("Add this digit to the current numeric argument")) +{ + info_numeric_arg_digit_loop (window, 0, key); +} + +/* C-u, universal argument. Multiply the current argument by 4. + Read a key. If the key has nothing to do with arguments, then + dispatch on it. If the key is the abort character then abort. */ +DECLARE_INFO_COMMAND (info_universal_argument, + _("Start (or multiply by 4) the current numeric argument")) +{ + info_numeric_arg *= 4; + info_numeric_arg_digit_loop (window, 0, 0); +} + +/* Create a default argument. */ +void +info_initialize_numeric_arg (void) +{ + info_numeric_arg = info_numeric_arg_sign = 1; + info_explicit_arg = 0; +} + +DECLARE_INFO_COMMAND (info_numeric_arg_digit_loop, + _("Internally used by \\[universal-argument]")) +{ + unsigned char pure_key; + Keymap keymap = window->keymap; + + while (1) + { + if (key) + pure_key = key; + else + { + if (display_was_interrupted_p && !info_any_buffered_input_p ()) + display_update_display (windows); + + if (active_window != the_echo_area) + display_cursor_at_point (active_window); + + pure_key = key = info_get_another_input_char (); + +#if !defined(INFOKEY) + if (Meta_p (key)) + add_char_to_keyseq (ESC); + + add_char_to_keyseq (UnMeta (key)); +#else /* defined(INFOKEY) */ + add_char_to_keyseq (key); +#endif /* defined(INFOKEY) */ + } + +#if !defined(INFOKEY) + if (Meta_p (key)) + key = UnMeta (key); +#endif /* !defined(INFOKEY) */ + + if (keymap[key].type == ISFUNC + && InfoFunction(keymap[key].function) + == (VFunction *) info_universal_argument) + { + info_numeric_arg *= 4; + key = 0; + continue; + } + +#if defined(INFOKEY) + if (Meta_p (key)) + key = UnMeta (key); +#endif /* !defined(INFOKEY) */ + + + if (isdigit (key)) + { + if (info_explicit_arg) + info_numeric_arg = (info_numeric_arg * 10) + (key - '0'); + else + info_numeric_arg = (key - '0'); + info_explicit_arg = 1; + } + else + { + if (key == '-' && !info_explicit_arg) + { + info_numeric_arg_sign = -1; + info_numeric_arg = 1; + } + else + { + info_keyseq_index--; + info_dispatch_on_key (pure_key, keymap); + return; + } + } + key = 0; + } +} + +/* **************************************************************** */ +/* */ +/* Input Character Buffering */ +/* */ +/* **************************************************************** */ + +/* Character waiting to be read next. */ +static int pending_input_character = 0; + +/* How to make there be no pending input. */ +static void +info_clear_pending_input (void) +{ + pending_input_character = 0; +} + +/* How to set the pending input character. */ +static void +info_set_pending_input (unsigned char key) +{ + pending_input_character = key; +} + +/* How to see if there is any pending input. */ +unsigned char +info_input_pending_p (void) +{ + return pending_input_character; +} + +/* Largest number of characters that we can read in advance. */ +#define MAX_INFO_INPUT_BUFFERING 512 + +static int pop_index = 0, push_index = 0; +static unsigned char info_input_buffer[MAX_INFO_INPUT_BUFFERING]; + +/* Add KEY to the buffer of characters to be read. */ +static void +info_push_typeahead (unsigned char key) +{ + /* Flush all pending input in the case of C-g pressed. */ + if (key == Control ('g')) + { + push_index = pop_index; + info_set_pending_input (Control ('g')); + } + else + { + info_input_buffer[push_index++] = key; + if ((unsigned int) push_index >= sizeof (info_input_buffer)) + push_index = 0; + } +} + +/* Return the amount of space available in INFO_INPUT_BUFFER for new chars. */ +static int +info_input_buffer_space_available (void) +{ + if (pop_index > push_index) + return pop_index - push_index; + else + return sizeof (info_input_buffer) - (push_index - pop_index); +} + +/* Get a key from the buffer of characters to be read. + Return the key in KEY. + Result is non-zero if there was a key, or 0 if there wasn't. */ +static int +info_get_key_from_typeahead (unsigned char *key) +{ + if (push_index == pop_index) + return 0; + + *key = info_input_buffer[pop_index++]; + + if ((unsigned int) pop_index >= sizeof (info_input_buffer)) + pop_index = 0; + + return 1; +} + +int +info_any_buffered_input_p (void) +{ + info_gather_typeahead (); + return push_index != pop_index; +} + +/* If characters are available to be read, then read them and stuff them into + info_input_buffer. Otherwise, do nothing. */ +void +info_gather_typeahead (void) +{ + register int i = 0; + int tty, space_avail; + long chars_avail; + unsigned char input[MAX_INFO_INPUT_BUFFERING]; + + tty = fileno (info_input_stream); + chars_avail = 0; + + space_avail = info_input_buffer_space_available (); + + /* If we can just find out how many characters there are to read, do so. */ +#if defined (FIONREAD) + { + ioctl (tty, FIONREAD, &chars_avail); + + if (chars_avail > space_avail) + chars_avail = space_avail; + + if (chars_avail) + chars_avail = read (tty, &input[0], chars_avail); + } +#else /* !FIONREAD */ +# if defined (O_NDELAY) + { + int flags; + + flags = fcntl (tty, F_GETFL, 0); + + fcntl (tty, F_SETFL, (flags | O_NDELAY)); + chars_avail = read (tty, &input[0], space_avail); + fcntl (tty, F_SETFL, flags); + + if (chars_avail == -1) + chars_avail = 0; + } +# else /* !O_NDELAY */ +# ifdef __DJGPP__ + { + extern long pc_term_chars_avail (void); + + if (isatty (tty)) + chars_avail = pc_term_chars_avail (); + else + { + /* We could be more accurate by calling ltell, but we have no idea + whether tty is buffered by stdio functions, and if so, how many + characters are already waiting in the buffer. So we punt. */ + struct stat st; + + if (fstat (tty, &st) < 0) + chars_avail = 1; + else + chars_avail = st.st_size; + } + if (chars_avail > space_avail) + chars_avail = space_avail; + if (chars_avail) + chars_avail = read (tty, &input[0], chars_avail); + } +# endif/* __DJGPP__ */ +# endif /* O_NDELAY */ +#endif /* !FIONREAD */ + + while (i < chars_avail) + { + info_push_typeahead (input[i]); + i++; + } +} + +/* How to read a single character. */ +unsigned char +info_get_input_char (void) +{ + unsigned char keystroke; + + info_gather_typeahead (); + + if (pending_input_character) + { + keystroke = pending_input_character; + pending_input_character = 0; + } + else if (info_get_key_from_typeahead (&keystroke) == 0) + { + int rawkey; + unsigned char c; + int tty = fileno (info_input_stream); + + /* Using stream I/O causes FIONREAD etc to fail to work + so unless someone can find a portable way of finding + out how many characters are currently buffered, we + should stay with away from stream I/O. + --Egil Kvaleberg <egilk@sn.no>, January 1997. */ +#ifdef EINTR + /* Keep reading if we got EINTR, so that we don't just exit. + --Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>, + 22 Dec 1997. */ + { + int n; + do + n = read (tty, &c, 1); + while (n == -1 && errno == EINTR); + rawkey = n == 1 ? c : EOF; + } +#else + rawkey = (read (tty, &c, 1) == 1) ? c : EOF; +#endif + + keystroke = rawkey; + + if (rawkey == EOF) + { + if (info_input_stream != stdin) + { + fclose (info_input_stream); + info_input_stream = stdin; + tty = fileno (info_input_stream); + display_inhibited = 0; + display_update_display (windows); + display_cursor_at_point (active_window); + rawkey = (read (tty, &c, 1) == 1) ? c : EOF; + keystroke = rawkey; + } + + if (rawkey == EOF) + { + terminal_unprep_terminal (); + close_dribble_file (); + xexit (0); + } + } + } + + if (info_dribble_file) + dribble (keystroke); + + return keystroke; +} |