diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/texinfo/texinfo/info/echo-area.c |
Initial commit
Diffstat (limited to 'macros/texinfo/texinfo/info/echo-area.c')
-rw-r--r-- | macros/texinfo/texinfo/info/echo-area.c | 1543 |
1 files changed, 1543 insertions, 0 deletions
diff --git a/macros/texinfo/texinfo/info/echo-area.c b/macros/texinfo/texinfo/info/echo-area.c new file mode 100644 index 0000000000..89296a7118 --- /dev/null +++ b/macros/texinfo/texinfo/info/echo-area.c @@ -0,0 +1,1543 @@ +/* echo-area.c -- how to read a line in the echo area. + + Copyright 1993-2019 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. */ + +#include "info.h" +#include "session.h" +#include "display.h" +#include "info-utils.h" +#include "echo-area.h" + +/* Non-zero means that C-g was used to quit reading input. */ +int info_aborted_echo_area = 0; + +/* Non-zero means that the echo area is being used to read input. */ +int echo_area_is_active = 0; + +/* The address of the last command executed in the echo area. */ +static VFunction *ea_last_executed_command = NULL; + +/* Non-zero means that the last command executed while reading input + killed some text. */ +int echo_area_last_command_was_kill = 0; + +/* Variables which hold on to the current state of the input line. */ +static char input_line[1 + EA_MAX_INPUT]; /* Contents of echo area, including + any prompt. */ +static int input_line_point; /* Offset into input_line of point */ +static int input_line_beg; /* End of prompt, and start of user input. */ +static int input_line_end; /* End of user input. */ + +static NODE input_line_node = { + NULL, NULL, NULL, input_line, + EA_MAX_INPUT, 0, N_IsInternal +}; + +static void echo_area_initialize_node (void); +static void push_echo_area (void), pop_echo_area (void); +static int echo_area_stack_contains_completions_p (void); + +static void ea_kill_text (int from, int to); +static void ea_remove_text (int from, int to); + +/* Non-zero means we force the user to complete. */ +static int echo_area_must_complete_p = 0; +static int completions_window_p (WINDOW *window); + +/* If non-null, this is a window which was specifically created to display + possible completions output. We remember it so we can delete it when + appropriate. */ +static WINDOW *echo_area_completions_window = NULL; + +/* Variables which keep track of the window which was active prior to + entering the echo area. */ +static WINDOW *calling_window = NULL; +static NODE *calling_window_node = NULL; + +/* Remember the node and pertinent variables of the calling window. */ +static void +remember_calling_window (WINDOW *window) +{ + /* Only do this if the calling window is not the completions window, or, + if it is the completions window and there is no other window. */ + if (!completions_window_p (window) || + ((window == windows) && !(window->next))) + { + calling_window = window; + calling_window_node = window->node; + } +} + +/* Restore the caller's window so that it shows the node that it was showing + on entry to info_read_xxx_echo_area (). */ +static void +restore_calling_window (void) +{ + register WINDOW *win, *compwin = NULL; + + /* If the calling window is still visible, and it is the window that + we used for completions output, then restore the calling window. */ + for (win = windows; win; win = win->next) + { + if (completions_window_p (win)) + compwin = win; + + if (win == calling_window && win == compwin) + { + forget_node (win); + compwin = NULL; + break; + } + } + + /* Delete the completions window if it is still present, it isn't the + last window on the screen, and there aren't any prior echo area reads + pending which created a completions window. */ + if (compwin) + { + if ((compwin != windows || windows->next) + && !echo_area_stack_contains_completions_p ()) + { + info_delete_window_internal (compwin); + } + } +} + +/* Set up a new input line with PROMPT. */ +static void +initialize_input_line (const char *prompt) +{ + strcpy (input_line, prompt); + input_line_beg = input_line_end = input_line_point = strlen (prompt); +} + +static char * +echo_area_after_read (void) +{ + char *return_value; + + if (info_aborted_echo_area) + { + info_aborted_echo_area = 0; + return_value = NULL; + } + else + { + if (input_line_beg == input_line_end) + return_value = xstrdup (""); + else + { + int line_len = input_line_end - input_line_beg; + return_value = xmalloc (1 + line_len); + strncpy (return_value, &input_line[input_line_beg], line_len); + return_value[line_len] = '\0'; + } + } + return return_value; +} + +static void +read_and_dispatch_in_echo_area (void) +{ + while (1) + { + int count; + VFunction *cmd; + int lk = 0; + + lk = echo_area_last_command_was_kill; + echo_area_prep_read (); + + if (!info_any_buffered_input_p ()) + display_update_display (); + + display_cursor_at_point (active_window); + + /* Do the selected command. */ + cmd = read_key_sequence (echo_area_keymap, 0, 0, 1, &count); + if (cmd) + { + (*cmd) (the_echo_area, count); + ea_last_executed_command = cmd; + } + else + ea_last_executed_command = 0; + + /* 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 (cmd == ea_newline || info_aborted_echo_area) + { + ea_last_executed_command = NULL; + break; + } + } +} + +/* Read a line of text in the echo area. Return a malloc ()'ed string, + or NULL if the user aborted out of this read. PROMPT is the prompt + to print before reading the line. */ +char * +info_read_in_echo_area (const char *prompt) +{ + char *line; + + /* If the echo area is already active, remember the current state. */ + if (echo_area_is_active) + push_echo_area (); + + /* Initialize our local variables. */ + initialize_input_line (prompt); + + /* Initialize the echo area for the first (but maybe not the last) time. */ + echo_area_initialize_node (); + + /* Save away the original node of this window, and the window itself, + so echo area commands can temporarily use this window. */ + remember_calling_window (active_window); + + /* Let the rest of Info know that the echo area is active. */ + echo_area_is_active++; + active_window = the_echo_area; + + /* Read characters in the echo area. */ + read_and_dispatch_in_echo_area (); + + window_clear_echo_area (); + + echo_area_is_active--; + + /* Restore the original active window and show point in it. */ + active_window = calling_window; + restore_calling_window (); + display_cursor_at_point (active_window); + fflush (stdout); + + /* Get the value of the line. */ + line = echo_area_after_read (); + + /* If there is a previous loop waiting for us, restore it now. */ + if (echo_area_is_active) + pop_echo_area (); + + /* Return the results to the caller. */ + return line; +} + +/* (re) Initialize the echo area node. */ +static void +echo_area_initialize_node (void) +{ + register int i; + + for (i = input_line_end; (unsigned int) i < sizeof (input_line); i++) + input_line[i] = ' '; + + input_line[i - 1] = '\n'; + window_set_node_of_window (the_echo_area, &input_line_node); + input_line[input_line_end] = '\n'; +} + +/* Prepare to read characters in the echo area. This can initialize the + echo area node, but its primary purpose is to side effect the input + line buffer contents. */ +void +echo_area_prep_read (void) +{ + if (the_echo_area->node != &input_line_node) + echo_area_initialize_node (); + + the_echo_area->point = input_line_point; + input_line[input_line_end] = '\n'; + + /* Mark the line map as invalid. This causes window_compute_line_map to + recalculate it when it is called via display_cursor_at_point below. + Otherwise adding or removing multi-column characters (like tabs) lead + to incorrect cursor positioning. */ + the_echo_area->line_map.used = 0; + + display_update_one_window (the_echo_area); + display_cursor_at_point (active_window); +} + + +/* **************************************************************** */ +/* */ +/* Echo Area Movement Commands */ +/* */ +/* **************************************************************** */ + +DECLARE_INFO_COMMAND (ea_forward, _("Move forward a character")) +{ + if (count < 0) + ea_backward (window, -count); + else + { + mbi_iterator_t iter; + mbi_init (iter, input_line + input_line_point, + input_line_end - input_line_point); + while (mbi_avail (iter) && count--) + { + mbi_advance (iter); + input_line_point = mbi_cur_ptr (iter) - input_line; + if (input_line_point > input_line_end) + input_line_point = input_line_end; + } + } +} + +DECLARE_INFO_COMMAND (ea_backward, _("Move backward a character")) +{ + if (count < 0) + ea_forward (window, -count); + else + { + char *ptr = input_line + input_line_point; + while (count--) + { + /* Go back one character. Go back by bytes until we look at a valid + multi-byte sequence. */ + ptr = input_line + input_line_point; + while (ptr > input_line) + { + ptr--; + if ((long) mbrlen (ptr, + input_line + input_line_point - ptr, 0) > 0) + break; + } + input_line_point = ptr - input_line; + if (input_line_point < input_line_beg) + input_line_point = input_line_beg; + } + } +} + +DECLARE_INFO_COMMAND (ea_beg_of_line, _("Move to the start of this line")) +{ + input_line_point = input_line_beg; +} + +DECLARE_INFO_COMMAND (ea_end_of_line, _("Move to the end of this line")) +{ + input_line_point = input_line_end; +} + +#define alphabetic(c) (islower (c) || isupper (c) || isdigit (c)) + +/* Move forward a word in the input line. */ +DECLARE_INFO_COMMAND (ea_forward_word, _("Move forward a word")) +{ + int c; + + if (count < 0) + ea_backward_word (window, -count); + else + { + while (count--) + { + if (input_line_point == input_line_end) + return; + + /* If we are not in a word, move forward until we are in one. + Then, move forward until we hit a non-alphabetic character. */ + c = input_line[input_line_point]; + + if (!alphabetic (c)) + { + while (++input_line_point < input_line_end) + { + c = input_line[input_line_point]; + if (alphabetic (c)) + break; + } + } + + if (input_line_point == input_line_end) + return; + + while (++input_line_point < input_line_end) + { + c = input_line[input_line_point]; + if (!alphabetic (c)) + break; + } + } + } +} + +DECLARE_INFO_COMMAND (ea_backward_word, _("Move backward a word")) +{ + int c; + + if (count < 0) + ea_forward_word (window, -count); + else + { + while (count--) + { + if (input_line_point == input_line_beg) + return; + + /* Like ea_forward_word (), except that we look at the + characters just before point. */ + + c = input_line[input_line_point - 1]; + + if (!alphabetic (c)) + { + while ((--input_line_point) != input_line_beg) + { + c = input_line[input_line_point - 1]; + if (alphabetic (c)) + break; + } + } + + while (input_line_point != input_line_beg) + { + c = input_line[input_line_point - 1]; + if (!alphabetic (c)) + break; + else + --input_line_point; + } + } + } +} + +DECLARE_INFO_COMMAND (ea_delete, _("Delete the character under the cursor")) +{ + if (count < 0) + ea_rubout (window, -count); + else + { + int orig_point; + if (input_line_point == input_line_end) + return; + + orig_point = input_line_point; + ea_forward (window, count); + if (ea_explicit_arg || count > 1) + ea_kill_text (orig_point, input_line_point); + else + ea_remove_text (orig_point, input_line_point); + input_line_point = orig_point; + } +} + +DECLARE_INFO_COMMAND (ea_rubout, _("Delete the character behind the cursor")) +{ + if (count < 0) + ea_delete (window, -count); + else + { + int start; + + if (input_line_point == input_line_beg) + { + /* Abort echo area read if backspace at start of area and user input + is empty. */ + if (input_line_beg == input_line_end) + info_aborted_echo_area = 1; + return; + } + + start = input_line_point; + ea_backward (window, count); + + if (ea_explicit_arg || count > 1) + ea_kill_text (start, input_line_point); + else + ea_delete (window, count); + } +} + +DECLARE_INFO_COMMAND (ea_abort, _("Cancel or quit operation")) +{ + /* If any text, just discard it, and restore the calling window's node. + If no text, quit. */ + if (input_line_end != input_line_beg) + { + terminal_ring_bell (); + input_line_end = input_line_point = input_line_beg; + if (calling_window->node != calling_window_node) + restore_calling_window (); + } + else + info_aborted_echo_area = 1; +} + +DECLARE_INFO_COMMAND (ea_newline, _("Accept (or force completion of) this line")) +{ + /* Stub does nothing. Simply here to see if it has been executed. */ +} + +DECLARE_INFO_COMMAND (ea_quoted_insert, _("Insert next character verbatim")) +{ + int character; + + character = get_another_input_key (); + + if (character >= 256 || character < 0) + return; + + ea_insert (window, count, character); +} + +void +ea_insert (WINDOW *window, int count, int key) +{ + register int i; + + if ((input_line_end + 1) == EA_MAX_INPUT) + { + terminal_ring_bell (); + return; + } + + for (i = input_line_end + 1; i != input_line_point; i--) + input_line[i] = input_line[i - 1]; + + input_line[input_line_point] = key; + input_line_point++; + input_line_end++; + window_line_map_init (window); +} + +DECLARE_INFO_COMMAND (ea_tab_insert, _("Insert a TAB character")) +{ + ea_insert (window, count, '\t'); +} + +/* Swap characters in INPUT_LINE. The first starts at C1 and ends at C1E, the + second starts at C2 and ends at C2E, with C1 < C1E <= C2 < C2E. */ +static void +ea_swap_chars (int c1, int c1e, int c2, int c2e) +{ + int len1, len2; + char *tmp; + + len1 = c1e - c1; + len2 = c2e - c2; + + if (len1 >= len2) + { + /* Save first character. */ + tmp = xmalloc (len1); + memcpy (tmp, input_line + c1, len1); + + /* Move the second character to where the first was. */ + memcpy (input_line + c1, input_line + c2, len2); + + /* Shift the part in between the characters backwards. */ + memmove (input_line + c1 + len2, input_line + c1e, c2 - c1e); + + /* Restore the first character at the end. */ + memcpy (input_line + c2 - (len1 - len2), tmp, len1); + free (tmp); + } + else /* len2 > len1 */ + { + /* Save second character. */ + tmp = xmalloc (len2); + memcpy (tmp, input_line + c2, len2); + + /* Move first character to end of second character. */ + memcpy (input_line + c2e - len1, input_line + c1, len1); + + /* Shift the part in between the characters forwards. */ + memmove (input_line + c1e + (len2 - len1), input_line + c1e, c2 - c1e); + + /* Place the second character at the beginning. */ + memcpy (input_line + c1, tmp, len2); + free (tmp); + } +} + +/* Transpose the characters at point. If point is at the end of the line, + then transpose the characters before point. */ +DECLARE_INFO_COMMAND (ea_transpose_chars, _("Transpose characters at point")) +{ + while (count) + { + if (input_line_point == input_line_end || count < 0) + { + /* Swap two characters before point. */ + int c1, c2, c2e; + c2e = input_line_point; + + ea_backward (window, 1); + c2 = input_line_point; + + ea_backward (window, 1); + c1 = input_line_point; + + if (c1 != c2) /* There are two characters in this line. */ + ea_swap_chars (c1, c2, c2, c2e); + + if (count > 0) + /* Restore point. */ + input_line_point = c2e; + else + input_line_point = c1 + c2e - c2; + } + else + { + int c1, c2, c2e; + + c2 = input_line_point; + + ea_forward (window, 1); + c2e = input_line_point; + if (c2e == c2) + return; /* Shouldn't happen. */ + + input_line_point = c2; + ea_backward (window, 1); + c1 = input_line_point; + if (c1 == c2e) + return; /* Can't go earlier in line. */ + + ea_swap_chars (c1, c2, c2, c2e); + + /* Set point is after swapped pair. */ + input_line_point = c2e; + } + + if (count < 0) + count++; + else + count--; + } +} + +/* **************************************************************** */ +/* */ +/* Echo Area Killing and Yanking */ +/* */ +/* **************************************************************** */ + +static char **kill_ring = NULL; +static int kill_ring_index = 0; /* Number of kills appearing in KILL_RING. */ +static int kill_ring_slots = 0; /* Number of slots allocated to KILL_RING. */ +static int kill_ring_loc = 0; /* Location of current yank pointer. */ + +/* The largest number of kills that we remember at one time. */ +static int max_retained_kills = 15; + +DECLARE_INFO_COMMAND (ea_yank, _("Yank back the contents of the last kill")) +{ + register int i; + register char *text; + + if (!kill_ring_index) + { + inform_in_echo_area (_("Kill ring is empty")); + return; + } + + text = kill_ring[kill_ring_loc]; + + for (i = 0; text[i]; i++) + ea_insert (window, 1, text[i]); +} + +/* If the last command was yank, or yank_pop, and the text just before + point is identical to the current kill item, then delete that text + from the line, rotate the index down, and yank back some other text. */ +DECLARE_INFO_COMMAND (ea_yank_pop, _("Yank back a previous kill")) +{ + register int len; + + if (((ea_last_executed_command != (VFunction *) ea_yank) && + (ea_last_executed_command != (VFunction *) ea_yank_pop)) || + (kill_ring_index == 0)) + return; + + len = strlen (kill_ring[kill_ring_loc]); + + /* Delete the last yanked item from the line. */ + { + register int i, counter; + + counter = input_line_end - input_line_point; + + for (i = input_line_point - len; counter; i++, counter--) + input_line[i] = input_line[i + len]; + + input_line_end -= len; + input_line_point -= len; + } + + /* Get a previous kill, and yank that. */ + kill_ring_loc--; + if (kill_ring_loc < 0) + kill_ring_loc = kill_ring_index - 1; + + ea_yank (window, count); +} + +/* Delete the text from point to end of line. */ +DECLARE_INFO_COMMAND (ea_kill_line, _("Kill to the end of the line")) +{ + if (count < 0) + { + ea_kill_text (input_line_point, input_line_beg); + input_line_point = input_line_beg; + } + else + ea_kill_text (input_line_point, input_line_end); +} + +/* Delete the text from point to beg of line. */ +DECLARE_INFO_COMMAND (ea_backward_kill_line, + _("Kill to the beginning of the line")) +{ + if (count < 0) + ea_kill_text (input_line_point, input_line_end); + else + { + ea_kill_text (input_line_point, input_line_beg); + input_line_point = input_line_beg; + } +} + +/* Delete from point to the end of the current word. */ +DECLARE_INFO_COMMAND (ea_kill_word, _("Kill the word following the cursor")) +{ + int orig_point = input_line_point; + + if (count < 0) + ea_backward_kill_word (window, -count); + else + { + ea_forward_word (window, count); + + if (input_line_point != orig_point) + ea_kill_text (orig_point, input_line_point); + + input_line_point = orig_point; + } + window_line_map_init (window); +} + +/* Delete from point to the start of the current word. */ +DECLARE_INFO_COMMAND (ea_backward_kill_word, + _("Kill the word preceding the cursor")) +{ + int orig_point = input_line_point; + + if (count < 0) + ea_kill_word (window, -count); + else + { + ea_backward_word (window, count); + + if (input_line_point != orig_point) + ea_kill_text (orig_point, input_line_point); + } + window_line_map_init (window); +} + +/* Remove text from offsets FROM to TO. Unlike 'ea_kill_text' nothing is + saved in the kill ring. */ +static void +ea_remove_text (int from, int to) +{ + int distance, i, counter; + counter = input_line_end - to; + distance = to - from; + + for (i = from; counter; i++, counter--) + input_line[i] = input_line[i + distance]; + input_line_end -= distance; +} + +/* The way to kill something. This appends or prepends to the last + kill, if the last command was a kill command. If FROM is less + than TO, then the killed text is appended to the most recent kill, + otherwise it is prepended. If the last command was not a kill command, + then a new slot is made for this kill. */ +static void +ea_kill_text (int from, int to) +{ + register int distance; + int killing_backwards, slot; + char *killed_text; + + killing_backwards = (from > to); + + /* If killing backwards, reverse the values of FROM and TO. */ + if (killing_backwards) + { + int temp = from; + from = to; + to = temp; + } + + /* Remember the text that we are about to delete. */ + distance = to - from; + killed_text = xmalloc (1 + distance); + strncpy (killed_text, &input_line[from], distance); + killed_text[distance] = '\0'; + + /* Actually delete the text from the line. */ + ea_remove_text (from, to); + + /* If the last command was a kill, append or prepend the killed text to + the last command's killed text. */ + if (echo_area_last_command_was_kill) + { + char *old, *new; + + slot = kill_ring_loc; + old = kill_ring[slot]; + new = xmalloc (1 + strlen (old) + strlen (killed_text)); + + if (killing_backwards) + { + /* Prepend TEXT to current kill. */ + strcpy (new, killed_text); + strcat (new, old); + } + else + { + /* Append TEXT to current kill. */ + strcpy (new, old); + strcat (new, killed_text); + } + + free (old); + free (killed_text); + kill_ring[slot] = new; + } + else + { + /* Try to store the kill in a new slot, unless that would cause there + to be too many remembered kills. */ + slot = kill_ring_index; + + if (slot == max_retained_kills) + slot = 0; + + if (slot + 1 > kill_ring_slots) + kill_ring = xrealloc (kill_ring, + (kill_ring_slots += max_retained_kills) + * sizeof (char *)); + + if (slot != kill_ring_index) + free (kill_ring[slot]); + else + kill_ring_index++; + + kill_ring[slot] = killed_text; + + kill_ring_loc = slot; + } + + /* Notice that the last command was a kill. */ + echo_area_last_command_was_kill++; +} + +/* **************************************************************** */ +/* */ +/* Echo Area Completion */ +/* */ +/* **************************************************************** */ + +/* Pointer to an array of REFERENCE to complete over. */ +REFERENCE **echo_area_completion_items = NULL; + +/* Sorted array of REFERENCE * which is the possible completions found in + the variable echo_area_completion_items. If there is only one element, + it is the only possible completion. */ +static REFERENCE **completions_found = NULL; +static size_t completions_found_index = 0; +static size_t completions_found_slots = 0; + +/* The lowest common denominator found while completing. */ +static REFERENCE *LCD_completion; + +/* Function to choose which references to offer as completion options. */ +static reference_bool_fn completion_exclude_func = 0; + +/* Internal functions used by the user calls. */ +static void build_completions (void); +static void completions_must_be_rebuilt (void); + +/* Variable which holds the output of completions. */ +static NODE *possible_completions_output_node = NULL; + +static char *compwin_name = "*Completions*"; + +/* Return non-zero if WINDOW is a window used for completions output. */ +static int +completions_window_p (WINDOW *window) +{ + int result = 0; + + if (internal_info_node_p (window->node) && + (strcmp (window->node->nodename, compwin_name) == 0)) + result = 1; + + return result; +} + +/* Workhorse for completion readers. If FORCE is non-zero, the user cannot + exit unless the line read completes, or is empty. Use EXCLUDE_FUNC to + exclude items in COMPLETIONS. */ +char * +info_read_completing_internal (const char *prompt, REFERENCE **completions, + int force, reference_bool_fn exclude_func) +{ + char *line; + + /* If the echo area is already active, remember the current state. */ + if (echo_area_is_active) + push_echo_area (); + + echo_area_must_complete_p = force; + + /* Initialize our local variables. */ + initialize_input_line (prompt); + + /* Initialize the echo area for the first (but maybe not the last) time. */ + echo_area_initialize_node (); + + /* Save away the original node of this window, and the window itself, + so echo area commands can temporarily use this window. */ + remember_calling_window (active_window); + + /* Save away the list of items to complete over. */ + echo_area_completion_items = completions; + completions_must_be_rebuilt (); + completion_exclude_func = exclude_func; + + active_window = the_echo_area; + echo_area_is_active++; + window_line_map_init (active_window); + + /* Read characters in the echo area. */ + while (1) + { + read_and_dispatch_in_echo_area (); + + line = echo_area_after_read (); + + /* Force the completion to take place if the user hasn't accepted + a default or aborted, and if FORCE is active. */ + if (force && line && *line && completions) + { + size_t i; + + build_completions (); + + /* If there is only one completion, then make the line be that + completion. */ + if (completions_found_index == 1) + { + free (line); + line = xstrdup (completions_found[0]->label); + break; + } + + /* If one of the completions matches exactly, then that is okay, so + return the current line. */ + for (i = 0; i < completions_found_index; i++) + if (mbscasecmp (completions_found[i]->label, line) == 0) + { + free (line); + line = xstrdup (completions_found[i]->label); + break; + } + + /* If no match, go back and try again. */ + if (i == completions_found_index) + { + if (!completions_found_index) + inform_in_echo_area (_("No completions")); + else + inform_in_echo_area (_("Not complete")); + free (line); + continue; + } + } + break; + } + echo_area_is_active--; + window_clear_echo_area (); + + /* Restore the original active window and show point in it. */ + active_window = calling_window; + restore_calling_window (); + display_cursor_at_point (active_window); + fflush (stdout); + + echo_area_completion_items = NULL; + completions_must_be_rebuilt (); + + /* If there is a previous loop waiting for us, restore it now. */ + if (echo_area_is_active) + pop_echo_area (); + + return line; +} + +/* Read a line in the echo area with completion over COMPLETIONS. */ +char * +info_read_completing_in_echo_area (const char *prompt, REFERENCE **completions) +{ + return info_read_completing_internal (prompt, completions, 1, 0); +} + +/* Read a line in the echo area allowing completion over COMPLETIONS, but + not requiring it. */ +char * +info_read_maybe_completing (const char *prompt, REFERENCE **completions) +{ + return info_read_completing_internal (prompt, completions, 0, 0); +} + +/* Read a line in the echo area with completion over COMPLETIONS, using + EXCLUDE to exclude items from the completion list. */ +char * +info_read_completing_in_echo_area_with_exclusions (const char *prompt, + REFERENCE **completions, reference_bool_fn exclude) +{ + return info_read_completing_internal (prompt, completions, 1, exclude); +} + +DECLARE_INFO_COMMAND (ea_possible_completions, _("List possible completions")) +{ + build_completions (); + + if (!completions_found_index) + { + terminal_ring_bell (); + inform_in_echo_area (_("No completions")); + } + else if (completions_found_index == 1) + { + inform_in_echo_area (_("Sole completion")); + } + else + { + size_t i, l; + size_t limit, iterations, max_label = 0; + struct text_buffer message; + + text_buffer_init (&message); + text_buffer_printf (&message, ngettext ("%d completion:\n", + "%d completions:\n", + completions_found_index), + completions_found_index); + + /* Find the maximum length of a label. */ + for (i = 0; i < completions_found_index; i++) + { + int len = strlen (completions_found[i]->label); + if (len > max_label) + max_label = len; + } + + max_label += 4; + + /* Find out how many columns we should print in. */ + limit = calling_window->width / max_label; + if (limit != 1 && (limit * max_label == calling_window->width)) + limit--; + + /* Avoid a possible floating exception. If max_label > width then + the limit will be 0 and a divide-by-zero fault will result. */ + if (limit == 0) + limit = 1; + + /* How many iterations of the printing loop? */ + iterations = (completions_found_index + (limit - 1)) / limit; + + /* Watch out for special case. If the number of completions is less + than LIMIT, then just do the inner printing loop. */ + if (completions_found_index < limit) + iterations = 1; + + /* Print the sorted items, up-and-down alphabetically. */ + for (i = 0; i < iterations; i++) + { + register int j; + + for (j = 0, l = i; j < limit; j++) + { + if (l >= completions_found_index) + break; + else + { + char *label; + int printed_length, k; + + label = completions_found[l]->label; + printed_length = strlen (label); + text_buffer_printf (&message, "%s", label); + + if (j + 1 < limit) + { + for (k = 0; k < max_label - printed_length; k++) + text_buffer_printf (&message, " "); + } + } + l += iterations; + } + text_buffer_printf (&message, "\n"); + } + + /* Make a new node to hold onto possible completions. Don't destroy + dangling pointers. */ + { + NODE *temp; + + temp = text_buffer_to_node (&message); + name_internal_node (temp, xstrdup (compwin_name)); + possible_completions_output_node = temp; + possible_completions_output_node->flags |= N_WasRewritten; + } + + /* Find a suitable window for displaying the completions output. + First choice is an existing window showing completions output. + If there is only one window, and it is large, make another + (smaller) window, and use that one. Otherwise, use the caller's + window. */ + { + WINDOW *compwin; + + compwin = get_internal_info_window (compwin_name); + + if (!compwin) + { + /* If we can split the window to display most of the completion + items, then do so. */ + if (calling_window->height > (iterations * 2) + && calling_window->height / 2 >= WINDOW_MIN_SIZE) + { + remember_calling_window (calling_window); + + active_window = calling_window; + compwin = window_make_window (); + window_change_window_height + (compwin, -(compwin->height - (iterations + 2))); + + echo_area_completions_window = compwin; + active_window = the_echo_area; + } + else + compwin = calling_window; + } + + /* Clear any completion nodes already showing from the window history. + This could happen if the user presses TAB more than once. */ + while (compwin->node && (compwin->node->flags & N_IsInternal) + && !strcmp (compwin->node->nodename, compwin_name)) + forget_node (compwin); + + info_set_node_of_window (compwin, possible_completions_output_node); + + display_update_display (); + } + } +} + +DECLARE_INFO_COMMAND (ea_complete, _("Insert completion")) +{ + if (ea_last_executed_command == (VFunction *) ea_complete) + { + ea_possible_completions (window, count); + return; + } + + input_line_point = input_line_end; + build_completions (); + + if (!completions_found_index) + terminal_ring_bell (); + else if (LCD_completion->label[0] == '\0') + ea_possible_completions (window, count); + else + { + register int i; + input_line_point = input_line_end = input_line_beg; + for (i = 0; LCD_completion->label[i]; i++) + ea_insert (window, 1, LCD_completion->label[i]); + } +} + +/* Utility REFERENCE used to store possible LCD. */ +static REFERENCE LCD_reference = { + NULL, NULL, NULL, 0, 0, 0 +}; + +static void remove_completion_duplicates (void); + +/* Variables which remember the state of the most recent call + to build_completions (). */ +static char *last_completion_request = NULL; +static REFERENCE **last_completion_items = NULL; + +/* How to tell the completion builder to reset internal state. */ +static void +completions_must_be_rebuilt (void) +{ + free (last_completion_request); + last_completion_request = NULL; + last_completion_items = NULL; +} + +/* Build a list of possible completions from echo_area_completion_items, + and the contents of input_line. */ +static void +build_completions (void) +{ + size_t i; + int len; + register REFERENCE *entry; + char *request; + int informed_of_lengthy_job = 0; + + /* If there are no items to complete over, exit immediately. */ + if (!echo_area_completion_items) + { + completions_found_index = 0; + LCD_completion = NULL; + return; + } + + /* Check to see if this call to build completions is the same as the last + call to build completions. */ + len = input_line_end - input_line_beg; + request = xmalloc (1 + len); + strncpy (request, &input_line[input_line_beg], len); + request[len] = '\0'; + + if (last_completion_request && last_completion_items && + last_completion_items == echo_area_completion_items && + (strcmp (last_completion_request, request) == 0)) + { + free (request); + return; + } + + free (last_completion_request); + last_completion_request = request; + last_completion_items = echo_area_completion_items; + + /* Always start at the beginning of the list. */ + completions_found_index = 0; + LCD_completion = NULL; + + for (i = 0; (entry = echo_area_completion_items[i]); i++) + { + /* Skip certain items (for example, we might only want + a list of menu items). */ + if (completion_exclude_func && completion_exclude_func (entry)) + continue; + + if (mbsncasecmp (request, entry->label, len) == 0) + add_pointer_to_array (entry, completions_found_index, + completions_found, completions_found_slots, + 20); + + if (!informed_of_lengthy_job && completions_found_index > 100) + { + informed_of_lengthy_job = 1; + window_message_in_echo_area (_("Building completions...")); + } + } + + if (!completions_found_index) + return; + + /* Sort and prune duplicate entries from the completions array. */ + remove_completion_duplicates (); + + /* If there is only one completion, just return that. */ + if (completions_found_index == 1) + { + LCD_completion = completions_found[0]; + return; + } + + /* Find the least common denominator. */ + { + long shortest = 100000; + + for (i = 1; i < completions_found_index; i++) + { + register int j; + int c1, c2; + + for (j = 0; + (c1 = tolower (completions_found[i - 1]->label[j])) + && (c2 = tolower (completions_found[i]->label[j])); + j++) + if (c1 != c2) + break; + + if (shortest > j) + shortest = j; + } + + free (LCD_reference.label); + LCD_reference.label = xmalloc (1 + shortest); + /* Since both the sorting done inside remove_completion_duplicates + and all the comparisons above are case-insensitive, it's + possible that the completion we are going to return is + identical to what the user typed but for the letter-case. This + is confusing, since the user could type FOOBAR<TAB> and get her + string change letter-case for no good reason. So try to find a + possible completion whose letter-case is identical, and if so, + use that. */ + if (completions_found_index > 1) + { + int req_len = strlen (request); + + for (i = 0; i < completions_found_index; i++) + if (strncmp (request, completions_found[i]->label, req_len) == 0) + break; + /* If none of the candidates match exactly, use the first one. */ + if (i >= completions_found_index) + i = 0; + } + strncpy (LCD_reference.label, completions_found[i]->label, shortest); + LCD_reference.label[shortest] = '\0'; + LCD_completion = &LCD_reference; + } + + if (informed_of_lengthy_job) + echo_area_initialize_node (); +} + +/* Function called by qsort. */ +static int +compare_references (const void *entry1, const void *entry2) +{ + REFERENCE **e1 = (REFERENCE **) entry1; + REFERENCE **e2 = (REFERENCE **) entry2; + + return mbscasecmp ((*e1)->label, (*e2)->label); +} + +/* Prune duplicate entries from COMPLETIONS_FOUND. */ +static void +remove_completion_duplicates (void) +{ + size_t i, j; + REFERENCE **temp; + int newlen; + + if (!completions_found_index) + return; + + /* Sort the items. */ + qsort (completions_found, completions_found_index, sizeof (REFERENCE *), + compare_references); + + for (i = 0, newlen = 1; i < completions_found_index - 1; i++) + { + if (strcmp (completions_found[i]->label, + completions_found[i + 1]->label) == 0) + completions_found[i] = NULL; + else + newlen++; + } + + /* We have marked all the dead slots. It is faster to copy the live slots + twice than to prune the dead slots one by one. */ + temp = xmalloc ((1 + newlen) * sizeof (REFERENCE *)); + for (i = 0, j = 0; i < completions_found_index; i++) + if (completions_found[i]) + temp[j++] = completions_found[i]; + + for (i = 0; i < newlen; i++) + completions_found[i] = temp[i]; + + completions_found[i] = NULL; + completions_found_index = newlen; + free (temp); +} + +/* Scroll the "other" window. If there is a window showing completions, scroll + that one, otherwise scroll the window which was active on entering the read + function. */ +DECLARE_INFO_COMMAND (ea_scroll_completions_window, _("Scroll the completions window")) +{ + WINDOW *compwin; + + compwin = get_internal_info_window (compwin_name); + + if (!compwin) + compwin = calling_window; + + /* Let info_scroll_forward () do the work, and print any messages that + need to be displayed. */ + info_scroll_forward (compwin, count); +} + +/* Function which gets called when an Info window is deleted while the + echo area is active. WINDOW is the window which has just been deleted. */ +void +echo_area_inform_of_deleted_window (WINDOW *window) +{ + /* If this is the calling_window, forget what we remembered about it. */ + if (window == calling_window) + { + if (active_window != the_echo_area) + remember_calling_window (active_window); + else + remember_calling_window (windows); + } + + /* If this window was the echo_area_completions_window, then notice that + the window has been deleted. */ + if (window == echo_area_completions_window) + echo_area_completions_window = NULL; +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping the Echo Area */ +/* */ +/* **************************************************************** */ + +/* Push and Pop the echo area. */ +typedef struct { + char *line; + REFERENCE **comp_items; + int point, beg, end; + int must_complete; + NODE node; + WINDOW *compwin; +} PUSHED_EA; + +static PUSHED_EA **pushed_echo_areas = NULL; +static size_t pushed_echo_areas_index = 0; +static size_t pushed_echo_areas_slots = 0; + +/* Pushing the echo_area has a side effect of zeroing the completion_items. */ +static void +push_echo_area (void) +{ + PUSHED_EA *pushed; + + pushed = xmalloc (sizeof (PUSHED_EA)); + pushed->line = xstrdup (input_line); + pushed->point = input_line_point; + pushed->beg = input_line_beg; + pushed->end = input_line_end; + pushed->node = input_line_node; + pushed->comp_items = echo_area_completion_items; + pushed->must_complete = echo_area_must_complete_p; + pushed->compwin = echo_area_completions_window; + + add_pointer_to_array (pushed, pushed_echo_areas_index, pushed_echo_areas, + pushed_echo_areas_slots, 4); + + echo_area_completion_items = NULL; +} + +static void +pop_echo_area (void) +{ + PUSHED_EA *popped; + + popped = pushed_echo_areas[--pushed_echo_areas_index]; + + strcpy (input_line, popped->line); + free (popped->line); + input_line_point = popped->point; + input_line_beg = popped->beg; + input_line_end = popped->end; + input_line_node = popped->node; + echo_area_completion_items = popped->comp_items; + echo_area_must_complete_p = popped->must_complete; + echo_area_completions_window = popped->compwin; + completions_must_be_rebuilt (); + + /* If the completion window no longer exists, forget about it. */ + if (echo_area_completions_window) + { + register WINDOW *win; + + for (win = windows; win; win = win->next) + if (echo_area_completions_window == win) + break; + + /* If the window wasn't found, then it has already been deleted. */ + if (!win) + echo_area_completions_window = NULL; + } + + free (popped); +} + +/* Returns non-zero if any of the prior stacked calls to read in the echo + area produced a completions window. */ +static int +echo_area_stack_contains_completions_p (void) +{ + size_t i; + + for (i = 0; i < pushed_echo_areas_index; i++) + if (pushed_echo_areas[i]->compwin) + return 1; + + return 0; +} + +/* **************************************************************** */ +/* */ +/* Error Messages While Reading in Echo Area */ +/* */ +/* **************************************************************** */ + +/* Print MESSAGE right after the end of the current line, and wait + for input or a couple of seconds, whichever comes first. Then flush the + informational message that was printed. */ +void +inform_in_echo_area (const char *message) +{ + int i; + char *text; + int avail = EA_MAX_INPUT + 1 - input_line_end; + + text = xstrdup (message); + for (i = 0; text[i] && text[i] != '\n' && i < avail; i++) + ; + text[i] = 0; + + echo_area_initialize_node (); + sprintf (&input_line[input_line_end], "%s[%s]\n", + echo_area_is_active ? " ": "", text); + free (text); + the_echo_area->point = input_line_point; + display_update_one_window (the_echo_area); + display_cursor_at_point (active_window); + fflush (stdout); + pause_or_input (); + echo_area_initialize_node (); +} |