/
niceSOFT
/
texinfo
Обзор
Документация
Войти
/
niceSOFT
/
texinfo
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
info/session.c
1 572 строки
42 KB
Gavin Smith
* info/signals.c, info/signals.h:
22 июл 2026, 23:44
22 июл 2026, 23:44
81b012b
Код
Авторство
О чём код?
/* session.c -- user windowing interface to Info. Copyright 1993-2026 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 <https://www.gnu.org/licenses/>. Originally written by Brian Fox. */ #include "info.h" #include "display.h" #include "session.h" #include "dribble.h" #include "util.h" #include "search.h" #include "nodes.h" #include "echo-area.h" #include "footnotes.h" #include "variables.h" #include "signals.h" #ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> #endif #ifdef __MINGW32__ # undef read # define read(f,b,s) w32_read(f,b,s) # undef _read # define _read(f,b,s) w32_read(f,b,s) extern ssize_t w32_read (int, void *, size_t); #endif #if defined (HAVE_SYS_TIME_H) # include <sys/time.h> # define HAVE_STRUCT_TIMEVAL #endif /* HAVE_SYS_TIME_H */ /* **************************************************************** */ /* */ /* Running an Info Session */ /* */ /* **************************************************************** */ static void mouse_event_handler (void); /* The place that we are reading input from. */ static FILE *info_input_stream = NULL; NODE *allfiles_node = 0; static void allfiles_create_node (char *term, REFERENCE **fref) { int i; struct text_buffer text; text_buffer_init (&text); text_buffer_printf (&text, "%s File names matching '%s'\n\n" "Info File Index\n" "***************\n\n" "File names that match '%s':\n", INFO_NODE_LABEL, term, term); /* Mark as an index so that destinations are never hidden. */ text_buffer_add_string (&text, "\0\b[index\0\b]", 11); text_buffer_printf (&text, "\n* Menu:\n\n"); for (i = 0; fref[i]; i++) { text_buffer_printf (&text, "* %4i: (%s)", i+1, fref[i]->filename); if (fref[i]->nodename) text_buffer_printf (&text, "%s", fref[i]->nodename); text_buffer_printf (&text, ".\n"); } allfiles_node = info_create_node (); allfiles_node->fullpath = xstrdup (""); allfiles_node->nodename = xstrdup ("*Info File Index*"); allfiles_node->contents = text_buffer_base (&text); allfiles_node->nodelen = text_buffer_off (&text); allfiles_node->body_start = strcspn (allfiles_node->contents, "\n"); scan_node_contents (allfiles_node, 0, 0); } /* Begin an info session finding the nodes specified by REFERENCES. For each loaded node, create a new window. Always split the largest of the available windows. Display ERROR in echo area if non-null. */ static void begin_multiple_window_info_session (REFERENCE **references, char *error) { register int i; WINDOW *window = 0; for (i = 0; references && references[i]; i++) { if (!window) { window = active_window; info_select_reference (window, references[i]); if (!window->node) window = 0; } 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 (); info_error ("%s", _("Cannot find a window!")); return; } active_window = largest; window = window_make_window (); info_select_reference (window, references[i]); if (!window->node) { /* We couldn't find the node referenced. */ window_delete_window (window); window = 0; } if (window) window_tile_windows (TILE_INTERNALS); else { display_update_display (); info_error ("%s", msg_win_too_small); return; } } } /* Load dir node as a back-up if there were no references given, or if none of them were valid. */ if (!window) { info_set_node_of_window (active_window, get_dir_node ()); return; } } static void display_startup_message (void) { const char *format; format = replace_in_documentation /* TRANSLATORS: Try to keep this message (when "expanded") at most 79 characters; anything after the 79th character will not actually be displayed on an 80-column terminal. */ (_("Welcome to Info version %s. Type \\[get-help-window] for help, \\[get-info-help-node] for tutorial."), 0); window_message_in_echo_area (format, VERSION, NULL); } /* Run an Info session. If USER_FILENAME is null, create a window for each node referenced in REF_LIST. ERROR is an optional error message to display at start-up. */ void info_session (REFERENCE **ref_list, char *error) { /* Initialize the Info session. */ initialize_info_session (); if (!error) display_startup_message (); else show_error_node (error); begin_multiple_window_info_session (ref_list, error); info_read_and_dispatch (); close_info_session (); } /* Used when "--all" was used on the command line. Display a file index with entries in REF_LIST. */ void info_session_allfiles (REFERENCE **ref_list, char *user_filename, char *error) { /* Initialize the Info session. */ initialize_info_session (); if (!error) display_startup_message (); else show_error_node (error); allfiles_create_node (user_filename, ref_list); info_set_node_of_window (active_window, allfiles_node); info_read_and_dispatch (); close_info_session (); } /* Start an info session with a single node displayed. */ void info_session_one_node (NODE *node) { initialize_info_session (); info_set_node_of_window (active_window, node); info_read_and_dispatch (); close_info_session (); } extern COMMAND_FUNCTION info_next_line; extern COMMAND_FUNCTION info_prev_line; /* Becomes non-zero when 'q' is typed to an Info window. */ static int quit_info_immediately = 0; void info_session_quit (void) { quit_info_immediately = 1; } void info_read_and_dispatch (void) { COMMAND_FUNCTION *cmd; int count; for (quit_info_immediately = 0; !quit_info_immediately; ) { if (!info_any_buffered_input_p ()) display_update_display (); /* Some redisplay might be necessary if the cursor has moved and a different reference (or no reference) has to be highlighted. */ if (hl_ref_rendition.mask) display_update_one_window (active_window); display_cursor_at_point (active_window); cmd = read_key_sequence (info_keymap, 1, 1, 0, &count); if (cmd) { if (!check_info_keyseq_displayed ()) window_clear_echo_area (); (*cmd) (active_window, count); /* Don't change the goal column when going up and down. This means we can go from a long line to a short line and back to a long line and end back in the same column. */ if (!(cmd == &info_next_line || cmd == &info_prev_line)) active_window->flags |= W_CurrentColGoal; /* Goal is current column. */ } } } /* Initialize terminal, read configuration file and set key bindings. */ void initialize_terminal_and_keymaps (char *init_file) { char *term_name = getenv ("TERM"); terminal_initialize_terminal (term_name); read_init_file (init_file); } /* Initialize the first info session by starting the terminal, window, and display systems. */ void initialize_info_session (void) { if (!terminal_prep_terminal ()) { /* Terminal too dumb to run interactively. */ char *term_name = getenv ("TERM"); info_error (_("Terminal type '%s' is not smart enough to run Info"), term_name); exit (EXIT_FAILURE); } terminal_clear_screen (); window_initialize_windows (screenwidth, screenheight); initialize_info_signal_handler (); display_initialize_display (screenwidth, screenheight); /* 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; } /* On program exit, leave the cursor at the bottom of the window, and restore the terminal I/O. */ void close_info_session (void) { terminal_goto_xy (0, screenheight - 1); terminal_clear_to_eol (); fflush (stdout); terminal_unprep_terminal (); close_dribble_file (); } /* 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; } /* **************************************************************** */ /* */ /* Input Character Buffering */ /* */ /* **************************************************************** */ static void fill_input_buffer (int wait); static int info_gather_typeahead (int); /* Largest number of characters that we can read in advance. */ #define MAX_INFO_INPUT_BUFFERING 512 static int pop_index = 0; /* Where to remove bytes from input buffer. */ static int push_index = 0; /* Where to add bytes to input buffer. */ static unsigned char info_input_buffer[MAX_INFO_INPUT_BUFFERING]; /* 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 get_byte_from_input_buffer (unsigned char *key) { if (push_index == pop_index) return 0; *key = info_input_buffer[pop_index++]; if (pop_index >= MAX_INFO_INPUT_BUFFERING) pop_index = 0; return 1; } int info_any_buffered_input_p (void) { fill_input_buffer (0); return push_index != pop_index; } int control_g_waiting (void) { fill_input_buffer (0); \ return info_input_buffer[pop_index] == Control ('g'); } /* Wrapper around info_gather_typeahead which handles read errors and reaching end-of-file. */ static void fill_input_buffer (int wait) { while (1) { int success; do { maybe_update_after_signal (); success = info_gather_typeahead (wait); } while (!success && errno == EINTR); /* Try again if the read was interrupted due to a signal. */ if (success || !wait) return; /* Reading failed. If we were reading from a dribble file with --restore, switch to standard input. Otherwise quit. */ if (info_input_stream != stdin) { fclose (info_input_stream); info_input_stream = stdin; display_inhibited = 0; display_update_display (); display_cursor_at_point (active_window); } else { close_info_session (); exit (EXIT_SUCCESS); } } } /* Read bytes and stuff them into info_input_buffer. If WAIT is true, wait for input; otherwise don't do anything if there is no input waiting. Return 1 on success, 0 on error. ERRNO may be set by read(). */ static int info_gather_typeahead (int wait) { 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; /* Clear errno. */ errno = 0; /* There may be characters left over from last time, in which case we don't want to wait for another key to be pressed. */ if (wait && pop_index == push_index) { char c; /* Wait until there is a byte waiting, and then stuff it into the input buffer. */ if (read (tty, &c, 1) <= 0) return 0; if (info_dribble_file) dribble (c); info_input_buffer[push_index++] = c; if (push_index >= MAX_INFO_INPUT_BUFFERING) push_index = 0; /* Continue to see if there are more bytes waiting. */ } /* Get the amount of space available in INFO_INPUT_BUFFER for new chars. */ if (pop_index > push_index) space_avail = pop_index - push_index; else space_avail = sizeof (info_input_buffer) - (push_index - pop_index); /* 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) && defined (F_GETFL) && defined (F_SETFL) { 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); } # else # ifdef __MINGW32__ { extern long w32_chars_avail (int); chars_avail = w32_chars_avail (tty); if (chars_avail > space_avail) chars_avail = space_avail; if (chars_avail) chars_avail = read (tty, &input[0], chars_avail); } # endif /* _WIN32 */ # endif/* __DJGPP__ */ # endif /* O_NDELAY */ #endif /* !FIONREAD */ while (i < chars_avail) { if (info_dribble_file) dribble (input[i]); /* Add KEY to the buffer of characters to be read. */ if (input[i] != Control ('g')) { info_input_buffer[push_index++] = input[i]; if (push_index >= MAX_INFO_INPUT_BUFFERING) push_index = 0; } else /* Flush all pending input in the case of C-g pressed. */ push_index = pop_index; i++; } /* If wait is true, there is at least one byte left in the input buffer. */ if (chars_avail <= 0 && !wait) return 0; return 1; } static int get_input_key_internal (void); /* Whether to process or skip mouse events in the input stream. */ unsigned char mouse_cb, mouse_cx, mouse_cy; /* Handle mouse event given that mouse_cb, mouse_cx and mouse_cy contain the data from the event. See the "XTerm Control Sequences" document for their meanings. */ void mouse_event_handler (void) { window_clear_echo_area(); if (mouse_cb & 0x40) { switch (mouse_cb & 0x03) { case 0: /* Mouse button 4 (scroll up). */ set_window_pagetop (active_window, active_window->pagetop - 3); break; case 1: /* Mouse button 5 (scroll down). */ set_window_pagetop (active_window, active_window->pagetop + 3); break; } } } /* Return number representing a key that has been pressed, which is an index into info_keymap and echo_area_keymap. */ int get_input_key (void) { int ret = -1; while (ret == -1) { ret = get_input_key_internal (); if (ret == KEY_MOUSE) { get_byte_from_input_buffer (&mouse_cb); get_byte_from_input_buffer (&mouse_cx); get_byte_from_input_buffer (&mouse_cy); } } return ret; } /* Time in milliseconds to wait for the next byte of a byte sequence corresponding to a key or key chord. Settable with the 'key-time' user variable. */ int key_time = 100; /* Read bytes from input and return what key has been pressed. Return -1 on reading an unrecognized key. */ static int get_input_key_internal (void) { BYTEMAP_ENTRY *b; unsigned char c; int esc_seen = 0; int pop_start; int byte_count = 0; fill_input_buffer (1); if (pop_index == push_index) return -1; /* No input waiting. This shouldn't happen. */ b = byte_seq_to_key; pop_start = pop_index; while (pop_index != push_index) { int in_map = 0; int unknown = 0; if (!get_byte_from_input_buffer (&c)) break; /* Incomplete byte sequence. */ byte_count++; switch (b[c].type) { case BYTEMAP_KEY: return b[c].key; case BYTEMAP_ESC: esc_seen = 1; /* Fall through. */ case BYTEMAP_MAP: in_map = 1; b = b[c].next; break; case BYTEMAP_NONE: unknown = 1; break; } if (unknown) break; /* If we read an incomplete byte sequence, pause a short while to see if more bytes follow. */ if (in_map && pop_index == push_index) { int ready = 0; #if defined (FD_SET) struct timeval timer, *timerp = 0; fd_set readfds; FD_ZERO (&readfds); FD_SET (fileno (info_input_stream), &readfds); timer.tv_sec = 0; timer.tv_usec = key_time * 1000; timerp = &timer; ready = select (fileno(info_input_stream)+1, &readfds, NULL, NULL, timerp); #else ready = 1; #endif /* FD_SET */ if (ready) fill_input_buffer (0); } } /* Incomplete or unknown byte sequence. Start again with the first byte. */ pop_index = pop_start; if (!esc_seen || (byte_count >= 3 && key_time == 0)) { /* If the sequence was incomplete, return the first byte. Also return the first byte for sequences with ESC that are at least three bytes long if 'key_time' is 0, to give some support for specifying byte sequences in infokey for those sent by unrecognized special keys (which would otherwise be skipped below). */ pop_index = pop_start; get_byte_from_input_buffer (&c); return c; } else { get_byte_from_input_buffer (&c); /* Should be ESC */ /* If there are no more characters, then decide that the escape key itself has been pressed. */ if (pop_index == push_index) return 033; /* Skip byte sequences that look like they could have come from unrecognized keys, e.g. F3 or C-S-Left, to avoid them as being interpreted as random garbage. These might produce sequences that look like "ESC O R" or "ESC [ 1 ; 6 ~", depending on the terminal. */ /* Check if the sequence starts ESC O. */ get_byte_from_input_buffer (&c); if (c == 'O') { /* If no more bytes, call it M-O. */ if (!info_any_buffered_input_p ()) return 'O' + KEYMAP_META_BASE; /* Otherwise it could be an unrecognized key producing a sequence ESC O (byte). Ignore it, discarding the next byte. */ get_byte_from_input_buffer (&c); return -1; } /* Unknown CSI-style sequences. */ else if (c == '[') { /* If no more bytes, call it M-[. */ if (!get_byte_from_input_buffer (&c)) return '[' + KEYMAP_META_BASE; /* Skip a control sequence as defined by ECMA-48. */ while (c >= 0x30 && c <= 0x3f) if (!get_byte_from_input_buffer (&c)) break; while (c >= 0x20 && c <= 0x2f) if (!get_byte_from_input_buffer (&c)) break; return -1; } else { /* The sequence started with ESC, but wasn't recognized. Treat it as introducing a sequence produced by a key chord with the meta key pressed. */ return c + KEYMAP_META_BASE; } } } #if defined (HAVE_SYS_TIME_H) # include <sys/time.h> # define HAVE_STRUCT_TIMEVAL #endif /* HAVE_SYS_TIME_H */ #if !defined (FD_SET) && defined (__MINGW32__) # define WIN32_LEAN_AND_MEAN # include <windows.h> #endif void pause_or_input (void) { #ifdef FD_SET struct timeval timer; fd_set readfds; #endif if (pop_index != push_index) return; /* Input is already waiting. */ #ifdef FD_SET FD_ZERO (&readfds); FD_SET (fileno (stdin), &readfds); timer.tv_sec = 2; timer.tv_usec = 0; select (fileno (stdin) + 1, &readfds, NULL, NULL, &timer); #elif defined (__MINGW32__) /* This is signalled on key release, so flush it and wait again. */ WaitForSingleObject (GetStdHandle (STD_INPUT_HANDLE), 2000); FlushConsoleInputBuffer (GetStdHandle (STD_INPUT_HANDLE)); WaitForSingleObject (GetStdHandle (STD_INPUT_HANDLE), 2000); #endif /* FD_SET */ } /* **************************************************************** */ /* */ /* Error handling */ /* */ /* **************************************************************** */ /* Non-zero means ring terminal bell on errors. */ int info_error_rings_bell_p = 1; /* Print AP according to FORMAT. If the window system was initialized, then the message is printed in the echo area. Otherwise, a message is output to stderr. */ static void vinfo_error (const char *format, va_list ap) { if (!info_windows_initialized_p || display_inhibited) { fprintf (stderr, "%s: ", program_name); vfprintf (stderr, format, ap); fprintf (stderr, "\n"); fflush (stderr); } else { if (!echo_area_is_active) { if (info_error_rings_bell_p) terminal_ring_bell (); vwindow_message_in_echo_area (format, ap); } else { NODE *temp = build_message_node (format, ap); if (info_error_rings_bell_p) terminal_ring_bell (); inform_in_echo_area (temp->contents); free (temp->contents); free (temp); } } } void info_error (const char *format, ...) { va_list ap; va_start (ap, format); vinfo_error (format, ap); va_end (ap); } void show_error_node (char *error) { if (info_error_rings_bell_p) terminal_ring_bell (); if (!info_windows_initialized_p) { info_error ("%s", error); } else if (!echo_area_is_active) { window_message_in_echo_area ("%s", error); } else inform_in_echo_area (error); } /* **************************************************************** */ /* */ /* Window node history */ /* */ /* **************************************************************** */ static void put_node_in_window (WINDOW *win, NODE *node) { win->node = node; win->pagetop = 0; win->point = 0; free_matches (&win->matches); free (win->line_starts); win->line_starts = 0; free (win->log_line_no); win->log_line_no = 0; win->flags |= W_UpdateWindow; } /* Go back one in the node history. */ int forget_node_fast (WINDOW *win) { int i = win->hist_index; if (i == 0) return 0; free_node (win->hist[i - 1]->node); free (win->hist[i - 1]); win->hist[i - 1] = 0; i = --win->hist_index; if (i == 0) /* Window history is empty. */ win->node = 0; else { put_node_in_window (win, win->hist[i - 1]->node); win->point = win->hist[i - 1]->point; } return i; } void forget_node (WINDOW *win) { int i = forget_node_fast (win); if (i == 0) { win->node = 0; return; /* Window history is empty. */ } window_set_node_of_window (win, win->hist[i - 1]->node); if (auto_footnotes_p) info_get_or_remove_footnotes (win); set_window_pagetop (win, win->hist[i - 1]->pagetop); win->point = win->hist[i - 1]->point; window_compute_line_map (win); win->node->display_pos = win->point; } /* Remove associated list of nodes of WINDOW. */ void forget_window_and_nodes (WINDOW *win) { size_t i; for (i = 0; i < win->hist_index; i++) { free_node (win->hist[i]->node); free (win->hist[i]); } free (win->hist); } /* Like info_set_node_of_window, but only do enough so to extend the window history, avoiding calculating line starts. */ void info_set_node_of_window_fast (WINDOW *win, NODE *node) { WINDOW_STATE *new; if (win->hist_index && win->hist[win->hist_index - 1]->node == win->node) { win->hist[win->hist_index - 1]->pagetop = win->pagetop; win->hist[win->hist_index - 1]->point = win->point; } put_node_in_window (win, node); new = xmalloc (sizeof (WINDOW_STATE)); new->node = win->node; new->pagetop = win->pagetop; new->point = win->point; add_pointer_to_array (new, win->hist_index, win->hist, win->hist_slots, 16); } /* Set WINDOW to show NODE. Remember the new window in our list of Info windows. If we are doing automatic footnote display, try to display the footnotes for this window. */ void info_set_node_of_window (WINDOW *win, NODE *node) { WINDOW_STATE *new; /* Remember the current values of pagetop and point if the remembered node is the same as the current one being displayed. */ if (win->hist_index && win->hist[win->hist_index - 1]->node == win->node) { win->hist[win->hist_index - 1]->pagetop = win->pagetop; win->hist[win->hist_index - 1]->point = win->point; } /* Put this node into the window. */ window_set_node_of_window (win, node); /* Remember this node, the currently displayed pagetop, and the current location of point in this window. */ new = xmalloc (sizeof (WINDOW_STATE)); new->node = win->node; new->pagetop = win->pagetop; new->point = win->point; add_pointer_to_array (new, win->hist_index, win->hist, win->hist_slots, 16); /* If doing auto-footnote display/undisplay, show the footnotes belonging to this window's node. Don't do that if it is a footnote node itself. */ if (auto_footnotes_p && !((win->node->flags & N_IsInternal) && !strcmp (win->node->nodename, "*Footnotes*"))) info_get_or_remove_footnotes (win); } /* 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->fullpath) return info_find_file (window->node->fullpath); return NULL; } /* **************************************************************** */ /* */ /* Reading Keys and Dispatching on Them */ /* */ /* **************************************************************** */ static void dispatch_error (int *keyseq) { const char *rep; rep = pretty_keyseq (keyseq); if (!echo_area_is_active) info_error (_("Unknown command (%s)"), rep); 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 int *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; } int check_info_keyseq_displayed (void) { return info_keyseq_displayed_p; } /* Add CHARACTER to the current key sequence. */ void add_char_to_keyseq (int character) { if (info_keyseq_index + 2 >= info_keyseq_size) info_keyseq = xrealloc (info_keyseq, sizeof (int) * (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) { const char *rep; if (!info_keyseq || info_keyseq_index == 0) return; rep = pretty_keyseq_ext (info_keyseq, expecting_future_input); 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 another key when keys have already been read as part of the current command (and possibly displayed in status line with display_info_keyseq). */ int get_another_input_key (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 ()) { #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 get_input_key (); } /* Non-zero means that an explicit argument has been passed to this command, as in C-u C-v. */ int info_explicit_arg = 0; /* As above, but used when C-u is typed in the echo area to avoid overwriting this information when "C-u ARG M-x" is typed. */ int ea_explicit_arg = 0; /* Create a default argument. */ void info_initialize_numeric_arg (void) { if (!echo_area_is_active) info_explicit_arg = 0; else ea_explicit_arg = 0; } extern COMMAND_FUNCTION info_universal_argument; extern COMMAND_FUNCTION info_add_digit_to_numeric_arg; extern COMMAND_FUNCTION info_do_lowercase_version; extern COMMAND_FUNCTION info_menu_digit; /* Read a key sequence and look up its command in MAP. Handle C-u style numeric args, as well as M--, and M-digits. Return argument in COUNT if it is non-null. Some commands can be executed directly, in which case null is returned instead: If MENU, call info_menu_digit on ACTIVE_WINDOW if a number key was pressed. If MOUSE, call mouse_event_handler if a mouse event occurred. If INSERT, call ea_insert if a printable character was input. */ COMMAND_FUNCTION * read_key_sequence (Keymap map, int menu, int mouse, int insert, int *count) { int key; int reading_universal_argument = 0; int numeric_arg = 1, numeric_arg_sign = 1, *which_explicit_arg; COMMAND_FUNCTION *func; /* Process the right numeric argument. */ if (!echo_area_is_active) which_explicit_arg = &info_explicit_arg; else which_explicit_arg = &ea_explicit_arg; *which_explicit_arg = 0; initialize_keyseq (); key = get_input_key (); if (key == KEY_MOUSE) { if (mouse) mouse_event_handler (); return 0; } if (insert && (key >= 040 && key < 0200 || ISO_Latin_p && key >= 0200 && key < 0400)) { ea_insert (the_echo_area, 1, key); return 0; } add_char_to_keyseq (key); while (1) { int dash_typed = 0, digit_typed = 0; func = 0; if (display_was_interrupted_p && !info_any_buffered_input_p ()) display_update_display (); if (active_window != the_echo_area) display_cursor_at_point (active_window); /* If reading a universal argument, both <digit> and M-<digit> help form the argument. Don't look up the pressed key in the key map. */ if (reading_universal_argument) { int k = key; if (k >= KEYMAP_META_BASE) k -= KEYMAP_META_BASE; if (k == '-') { dash_typed = 1; } else if (isdigit (k)) { digit_typed = 1; } else /* Note: we may still read another C-u after this. */ reading_universal_argument = 0; } if (!dash_typed && !digit_typed && map[key].type == ISFUNC) { func = map[key].value.function ? map[key].value.function->func : 0; if (!func) { dispatch_error (info_keyseq); return 0; } } if (dash_typed || digit_typed || func == &info_add_digit_to_numeric_arg) { int k = key; if (k > KEYMAP_META_BASE) k -= KEYMAP_META_BASE; reading_universal_argument = 1; if (dash_typed || k == '-') { if (!*which_explicit_arg) { numeric_arg_sign = -1; numeric_arg = 1; } } else if (digit_typed || isdigit (k)) { if (*which_explicit_arg) numeric_arg = numeric_arg * 10 + (k - '0'); else numeric_arg = (k - '0'); *which_explicit_arg = 1; } } else if (func == info_do_lowercase_version) { int lowerkey; if (key >= KEYMAP_META_BASE) { lowerkey = key; lowerkey -= KEYMAP_META_BASE; lowerkey = tolower (lowerkey); lowerkey += KEYMAP_META_BASE; } else lowerkey = tolower (key); if (lowerkey == key) { dispatch_error (info_keyseq); return 0; } key = lowerkey; continue; } else if (func == &info_universal_argument) { /* Multiply by 4. */ /* TODO: Maybe C-u should also terminate the universal argument sequence, as in Emacs. (C-u 6 4 C-u 1 inserts 64 1's.) */ if (!*which_explicit_arg) numeric_arg *= 4; reading_universal_argument = 1; } else if (menu && func == &info_menu_digit) { /* key can either be digit, or M-digit for --vi-keys. */ int k = key; if (k > KEYMAP_META_BASE) k -= KEYMAP_META_BASE; window_clear_echo_area (); menu_digit (active_window, k); return 0; } else if (insert && (func == &ea_possible_completions || func == &ea_complete) && !echo_area_completion_items) { ea_insert (the_echo_area, 1, key); return 0; } else if (func) { /* Don't update the key sequence if we have finished reading a key sequence in the echo area. This means that a key sequence like "C-u 2 Left" appears to take effect immediately, instead of there being a delay while the message is displayed. */ if (!echo_area_is_active && info_keyseq_displayed_p) display_info_keyseq (0); if (count) *count = numeric_arg * numeric_arg_sign; /* *which_explicit_arg has not been set yet if only a sequence of C-u's was typed (each of which has multiplied the argument by four). */ if (*count != 1 && !*which_explicit_arg) *which_explicit_arg = 1; return func; } else if (map[key].type == ISKMAP) { if (map[key].value.keymap != NULL) map = map[key].value.keymap; else { dispatch_error (info_keyseq); return 0; } if (info_keyseq_displayed_p) display_info_keyseq (1); } do key = get_another_input_key (); while (key == KEY_MOUSE); add_char_to_keyseq (key); } return 0; } void info_abort (void) { /* 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 ("%s", _("Quit")); info_initialize_numeric_arg (); } /* **************************************************************** */ /* */ /* Dumping and Printing Nodes */ /* */ /* **************************************************************** */ struct info_namelist_entry { struct info_namelist_entry *next; char name[1]; }; static int info_namelist_add (struct info_namelist_entry **ptop, const char *name) { struct info_namelist_entry *p; for (p = *ptop; p; p = p->next) if (fncmp (p->name, name) == 0) return 1; p = xmalloc (sizeof (*p) + strlen (name)); strcpy (p->name, name); p->next = *ptop; *ptop = p; return 0; } static void info_namelist_free (struct info_namelist_entry *top) { while (top) { struct info_namelist_entry *next = top->next; free (top); top = next; } } enum { DUMP_SUCCESS, DUMP_INFO_ERROR, DUMP_SYS_ERROR }; static int dump_node_to_stream (FILE_BUFFER *file_buffer, char *nodename, FILE *stream, int dump_subnodes); static void initialize_dumping (void); /* Dump the nodes specified with REFERENCES to the file named in OUTPUT_FILENAME. If DUMP_SUBNODES is set, recursively dump the nodes which appear in the menu of each node dumped. */ void dump_nodes_to_file (REFERENCE **references, char *output_filename, int dump_subnodes) { int i; FILE *output_stream; if (!references) return; /* Get the stream to print the nodes to. Special case of an output filename of "-" means to dump the nodes to stdout. */ if (strcmp (output_filename, "-") == 0) output_stream = stdout; else output_stream = fopen (output_filename, "w"); if (!output_stream) { info_error (_("Could not create output file '%s'"), output_filename); return; } /* Print each node to stream. */ for (i = 0; references[i]; i++) { FILE_BUFFER *file_buffer; char *nodename; initialize_dumping (); file_buffer = info_find_file (references[i]->filename); if (!file_buffer) { if (info_recent_file_error) info_error ("%s", info_recent_file_error); continue; } if (references[i]->nodename && *references[i]->nodename) nodename = references[i]->nodename; else nodename = "Top"; if (dump_node_to_stream (file_buffer, nodename, output_stream, dump_subnodes) == DUMP_SYS_ERROR) { info_error (_("error writing to %s: %s"), output_filename, strerror (errno)); exit (EXIT_FAILURE); } } if (output_stream != stdout) fclose (output_stream); debug (1, (_("closing %s"), output_filename)); } /* A place to remember already dumped nodes. */ static struct info_namelist_entry *dumped_already; static void initialize_dumping (void) { info_namelist_free (dumped_already); dumped_already = NULL; } /* 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 int dump_node_to_stream (FILE_BUFFER *file_buffer, char *nodename, FILE *stream, int dump_subnodes) { register int i; NODE *node; node = info_get_node_of_file_buffer (file_buffer, nodename); if (!node) { info_error (msg_cant_find_node, nodename); return DUMP_INFO_ERROR; } /* If we have already dumped this node, don't dump it again. */ if (info_namelist_add (&dumped_already, node->nodename)) { free_node (node); return DUMP_SUCCESS; } /* Maybe we should print some information about the node being output. */ debug (1, (_("writing node %s..."), node_printed_rep (node))); if (write_node_to_stream (node, stream)) { free_node (node); return DUMP_SYS_ERROR; } /* 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 = node->references; if (menu) { for (i = 0; menu[i]; i++) { if (REFERENCE_MENU_ITEM != menu[i]->type) continue; /* We don't dump Info files which are different than the current one. */ if (!menu[i]->filename) if (dump_node_to_stream (file_buffer, menu[i]->nodename, stream, dump_subnodes) == DUMP_SYS_ERROR) { free_node (node); return DUMP_SYS_ERROR; } } } } free_node (node); return DUMP_SUCCESS; } int write_node_to_stream (NODE *node, FILE *stream) { return fwrite (node->contents, node->nodelen, 1, stream) != 1; }