| /* infodoc.c -- functions which build documentation nodes. |
| |
| Copyright 1993-2022 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 "scan.h" |
| #include "util.h" |
| #include "filesys.h" |
| #include "session.h" |
| #include "doc.h" |
| #include "funs.h" |
| |
| /* The name of the node used in the help window. */ |
| static char *info_help_nodename = "*Info Help*"; |
| |
| /* A node containing printed key bindings and their documentation. */ |
| static NODE *internal_info_help_node = NULL; |
| |
| /* The (more or less) static text which appears in the internal info |
| help node. The actual key bindings are inserted. Keep the |
| underlines (****, etc.) in the same N_ call as the text lines they |
| refer to, so translations can make the number of *'s or -'s match. */ |
| static char *info_internal_help_text[] = { |
| N_("Basic Info command keys\n"), |
| "\n", |
| N_("\\%-10[quit-help] Close this help window.\n"), |
| N_("\\%-10[quit] Quit Info altogether.\n"), |
| N_("\\%-10[get-info-help-node] Invoke the Info tutorial.\n"), |
| "\n", |
| N_("\\%-10[prev-line] Move up one line.\n"), |
| N_("\\%-10[next-line] Move down one line.\n"), |
| N_("\\%-10[scroll-backward] Scroll backward one screenful.\n"), |
| N_("\\%-10[scroll-forward] Scroll forward one screenful.\n"), |
| N_("\\%-10[beginning-of-node] Go to the beginning of this node.\n"), |
| N_("\\%-10[end-of-node] Go to the end of this node.\n"), |
| "\n", |
| N_("\\%-10[move-to-next-xref] Skip to the next hypertext link.\n"), |
| N_("\\%-10[select-reference-this-line] Follow the hypertext link under the cursor.\n"), |
| N_("\\%-10[history-node] Go back to the last node seen in this window.\n"), |
| "\n", |
| N_("\\%-10[global-prev-node] Go to the previous node in the document.\n"), |
| N_("\\%-10[global-next-node] Go to the next node in the document.\n"), |
| N_("\\%-10[prev-node] Go to the previous node on this level.\n"), |
| N_("\\%-10[next-node] Go to the next node on this level.\n"), |
| N_("\\%-10[up-node] Go up one level.\n"), |
| N_("\\%-10[top-node] Go to the top node of this document.\n"), |
| N_("\\%-10[dir-node] Go to the main 'directory' node.\n"), |
| "\n", |
| N_("1...9 Pick the first...ninth item in this node's menu.\n"), |
| N_("\\%-10[last-menu-item] Pick the last item in this node's menu.\n"), |
| N_("\\%-10[menu-item] Pick a menu item specified by name.\n"), |
| N_("\\%-10[xref-item] Follow a cross reference specified by name.\n"), |
| N_("\\%-10[goto-node] Go to a node specified by name.\n"), |
| "\n", |
| N_("\\%-10[search] Search forward for a specified string.\n"), |
| N_("\\%-10[search-previous] Search for previous occurrence.\n"), |
| N_("\\%-10[search-next] Search for next occurrence.\n"), |
| N_("\\%-10[index-search] Search for a specified string in the index, and\n\ |
| select the node referenced by the first entry found.\n"), |
| N_("\\%-10[virtual-index] Synthesize menu of matching index entries.\n"), |
| "\n", |
| N_("\\%-10[abort-key] Cancel the current operation.\n"), |
| "\n", |
| NULL |
| }; |
| |
| static char *where_is_internal (Keymap map, InfoCommand *cmd); |
| |
| static void |
| dump_map_to_text_buffer (struct text_buffer *tb, int *prefix, |
| int prefix_len, Keymap map) |
| { |
| register int i; |
| int *new_prefix = xmalloc ((prefix_len + 2) * sizeof (int)); |
| |
| memcpy (new_prefix, prefix, prefix_len * sizeof (int)); |
| new_prefix[prefix_len + 1] = 0; |
| |
| for (i = 0; i < KEYMAP_SIZE; i++) |
| { |
| if (i == 128) |
| i = 256; |
| if (i == 128 + KEYMAP_META_BASE) |
| i = 256 + KEYMAP_META_BASE; |
| |
| new_prefix[prefix_len] = i; |
| if (map[i].type == ISKMAP) |
| { |
| dump_map_to_text_buffer (tb, new_prefix, prefix_len + 1, |
| map[i].value.keymap); |
| } |
| else if (map[i].value.function) |
| { |
| long start_of_line = tb->off; |
| register int last; |
| char *doc, *name; |
| |
| /* Hide some key mappings. */ |
| if (map[i].value.function |
| && (map[i].value.function->func == info_do_lowercase_version)) |
| continue; |
| |
| doc = function_documentation (map[i].value.function); |
| name = function_name (map[i].value.function); |
| |
| if (!*doc) |
| continue; |
| |
| /* Find out if there is a series of identical functions, as in |
| add-digit-to-numeric-arg. */ |
| for (last = i + 1; last < KEYMAP_SIZE; last++) |
| if ((map[last].type != ISFUNC) || |
| (map[last].value.function != map[i].value.function)) |
| break; |
| |
| if (last - 1 != i) |
| { |
| text_buffer_printf (tb, "%s .. ", pretty_keyseq (new_prefix)); |
| new_prefix[prefix_len] = last - 1; |
| text_buffer_printf (tb, "%s", pretty_keyseq (new_prefix)); |
| i = last - 1; |
| } |
| else |
| text_buffer_printf (tb, "%s", pretty_keyseq (new_prefix)); |
| |
| while (tb->off - start_of_line < 8) |
| text_buffer_printf (tb, " "); |
| |
| /* Print the name of the function, and some padding before the |
| documentation string is printed. */ |
| { |
| int length_so_far; |
| int desired_doc_start = 40; |
| |
| text_buffer_printf (tb, "(%s)", name); |
| length_so_far = tb->off - start_of_line; |
| |
| if ((desired_doc_start + strlen (doc)) |
| >= (unsigned int) the_screen->width) |
| text_buffer_printf (tb, "\n "); |
| else |
| { |
| while (length_so_far < desired_doc_start) |
| { |
| text_buffer_printf (tb, " "); |
| length_so_far++; |
| } |
| } |
| } |
| text_buffer_printf (tb, "%s\n", doc); |
| } |
| } |
| free (new_prefix); |
| } |
| |
| /* How to create internal_info_help_node. HELP_IS_ONLY_WINDOW_P says |
| whether we're going to end up in a second (or more) window of our |
| own, or whether there's only one window and we're going to usurp it. |
| This determines how to quit the help window. Maybe we should just |
| make q do the right thing in both cases. */ |
| |
| static void |
| create_internal_info_help_node (int help_is_only_window_p) |
| { |
| register int i; |
| NODE *node; |
| char *exec_keys; |
| |
| int printed_one_mx = 0; |
| struct text_buffer msg; |
| char *infopath_str = infopath_string (); |
| |
| text_buffer_init (&msg); |
| |
| for (i = 0; info_internal_help_text[i]; i++) |
| text_buffer_printf (&msg, replace_in_documentation |
| (_(info_internal_help_text[i]), |
| help_is_only_window_p), NULL, NULL, NULL); |
| |
| text_buffer_printf (&msg, "---------------------\n"); |
| text_buffer_printf (&msg, _("This is GNU Info version %s. "), VERSION); |
| text_buffer_printf (&msg, _("The current search path is:\n")); |
| text_buffer_printf (&msg, "%s\n", infopath_str); |
| text_buffer_printf (&msg, "---------------------\n\n"); |
| free (infopath_str); |
| |
| text_buffer_printf (&msg, _("Commands available in Info windows:\n\n")); |
| dump_map_to_text_buffer (&msg, 0, 0, info_keymap); |
| text_buffer_printf (&msg, "---------------------\n\n"); |
| text_buffer_printf (&msg, _("Commands available in the echo area:\n\n")); |
| dump_map_to_text_buffer (&msg, 0, 0, echo_area_keymap); |
| |
| /* Get a list of commands which have no keystroke equivs. */ |
| exec_keys = where_is (info_keymap, InfoCmd(info_execute_command)); |
| if (exec_keys) |
| exec_keys = xstrdup (exec_keys); |
| for (i = 0; function_doc_array[i].func; i++) |
| { |
| InfoCommand *cmd = &function_doc_array[i]; |
| |
| if (cmd->func != info_do_lowercase_version |
| && !where_is_internal (info_keymap, cmd) |
| && !where_is_internal (echo_area_keymap, cmd)) |
| { |
| if (!printed_one_mx) |
| { |
| text_buffer_printf (&msg, "---------------------\n\n"); |
| if (exec_keys && exec_keys[0]) |
| text_buffer_printf (&msg, |
| _("The following commands can only be invoked via " |
| "%s:\n\n"), |
| exec_keys); |
| else |
| text_buffer_printf (&msg, |
| _("The following commands cannot be invoked at all:\n\n")); |
| printed_one_mx = 1; |
| } |
| |
| text_buffer_printf (&msg, |
| "%s %s\n %s\n", |
| exec_keys, |
| function_doc_array[i].func_name, |
| replace_in_documentation (strlen (function_doc_array[i].doc) |
| ? _(function_doc_array[i].doc) : "", 0) |
| ); |
| |
| } |
| } |
| |
| free (exec_keys); |
| |
| node = text_buffer_to_node (&msg); |
| |
| internal_info_help_node = node; |
| |
| name_internal_node (internal_info_help_node, xstrdup (info_help_nodename)); |
| } |
| |
| /* Return a window which is the window showing help in this Info. */ |
| |
| /* If the eligible window's height is >= this, split it to make the help |
| window. Otherwise display the help window in the current window. */ |
| #define HELP_SPLIT_SIZE 24 |
| |
| static WINDOW * |
| info_find_or_create_help_window (void) |
| { |
| int help_is_only_window_p; |
| WINDOW *eligible = NULL; |
| WINDOW *help_window = get_internal_info_window (info_help_nodename); |
| |
| /* Close help window if in it already. */ |
| if (help_window && help_window == active_window) |
| { |
| info_delete_window_internal (help_window); |
| return NULL; |
| } |
| |
| /* If we couldn't find the help window, then make it. */ |
| if (!help_window) |
| { |
| WINDOW *window; |
| int max = 0; |
| |
| for (window = windows; window; window = window->next) |
| { |
| if (window->height > max) |
| { |
| max = window->height; |
| eligible = window; |
| } |
| } |
| |
| if (!eligible) |
| { |
| info_error ("%s", msg_cant_make_help); |
| return NULL; |
| } |
| } |
| |
| /* Make sure that we have a node containing the help text. The |
| argument is false if help will be the only window (so l must be used |
| to quit help), true if help will be one of several visible windows |
| (so CTRL-x 0 must be used to quit help). */ |
| help_is_only_window_p = ((help_window && !windows->next) |
| || (!help_window && eligible->height < HELP_SPLIT_SIZE)); |
| create_internal_info_help_node (help_is_only_window_p); |
| |
| /* Either use the existing window to display the help node, or create |
| a new window if there was no existing help window. */ |
| if (!help_window) |
| { /* Split the largest window into 2 windows, and show the help text |
| in that window. */ |
| if (eligible->height >= HELP_SPLIT_SIZE) |
| { |
| active_window = eligible; |
| help_window = window_make_window (); |
| info_set_node_of_window (help_window, internal_info_help_node); |
| } |
| else |
| { |
| info_set_node_of_window (active_window, internal_info_help_node); |
| help_window = active_window; |
| } |
| } |
| else |
| { /* Case where help node always gets regenerated, and we have an |
| existing window in which to place the node. */ |
| if (active_window != help_window) |
| { |
| active_window = help_window; |
| } |
| info_set_node_of_window (active_window, internal_info_help_node); |
| } |
| return help_window; |
| } |
| |
| /* Create or move to the help window. */ |
| DECLARE_INFO_COMMAND (info_get_help_window, _("Display help message")) |
| { |
| WINDOW *help_window; |
| |
| help_window = info_find_or_create_help_window (); |
| if (help_window) |
| { |
| active_window = help_window; |
| } |
| } |
| |
| /* Show the Info help node. This means that the "info" file is installed |
| where it can easily be found on your system. */ |
| DECLARE_INFO_COMMAND (info_get_info_help_node, _("Visit Info node '(info)Help'")) |
| { |
| NODE *node; |
| char *nodename; |
| |
| /* If there is a window on the screen showing the node "(info)Help" or |
| the node "(info)Help-Small-Screen", simply select that window. */ |
| { |
| WINDOW *win; |
| |
| for (win = windows; win; win = win->next) |
| { |
| if (win->node && win->node->fullpath |
| && !mbscasecmp ("info", |
| filename_non_directory (win->node->fullpath)) |
| && (!strcmp (win->node->nodename, "Help") |
| || !strcmp (win->node->nodename, "Help-Small-Screen"))) |
| { |
| active_window = win; |
| return; |
| } |
| } |
| } |
| |
| /* If there is more than one window on the screen, check if the user typed |
| "H" for help message before typing "h" for tutorial. If so, close help |
| message so the tutorial will not be in a small window. */ |
| if (windows->next) |
| { |
| WINDOW *help_window = get_internal_info_window (info_help_nodename); |
| if (help_window && help_window == active_window) |
| { |
| info_delete_window_internal (help_window); |
| } |
| } |
| |
| /* If the current window is small, show the small screen help. */ |
| if (active_window->height < 24) |
| nodename = "Help-Small-Screen"; |
| else |
| nodename = "Help"; |
| |
| /* Try to get the info file for Info. */ |
| node = info_get_node ("info", nodename); |
| |
| /* info.info is distributed with Emacs, not Texinfo, so fall back to |
| info-stnd.info if it isn't there. */ |
| if (!node) |
| node = info_get_node ("info-stnd", "Top"); |
| |
| if (!node) |
| { |
| if (info_recent_file_error) |
| info_error ("%s", info_recent_file_error); |
| else |
| info_error (msg_cant_file_node, "info", nodename); |
| |
| return; |
| } |
| |
| info_set_node_of_window (active_window, node); |
| } |
| |
| /* **************************************************************** */ |
| /* */ |
| /* Groveling Info Keymaps and Docs */ |
| /* */ |
| /* **************************************************************** */ |
| |
| /* Return the documentation associated with the Info command FUNCTION. */ |
| char * |
| function_documentation (InfoCommand *cmd) |
| { |
| char *doc; |
| |
| doc = cmd->doc; |
| |
| return replace_in_documentation ((strlen (doc) == 0) ? doc : _(doc), 0); |
| } |
| |
| /* Return the user-visible name of the function associated with the |
| Info command FUNCTION. */ |
| char * |
| function_name (InfoCommand *cmd) |
| { |
| return cmd->func_name; |
| } |
| |
| /* Return a pointer to the info command for function NAME. */ |
| InfoCommand * |
| named_function (char *name) |
| { |
| register int i; |
| |
| for (i = 0; function_doc_array[i].func; i++) |
| if (strcmp (function_doc_array[i].func_name, name) == 0) |
| break; |
| |
| if (!function_doc_array[i].func) |
| return 0; |
| else |
| return &function_doc_array[i]; |
| } |
| |
| DECLARE_INFO_COMMAND (describe_key, _("Print documentation for KEY")) |
| { |
| int keys[50]; |
| int keystroke; |
| int *k = keys; |
| Keymap map = info_keymap; |
| |
| *k = '\0'; |
| |
| for (;;) |
| { |
| message_in_echo_area (_("Describe key: %s"), pretty_keyseq (keys)); |
| keystroke = get_input_key (); |
| unmessage_in_echo_area (); |
| |
| /* Add the KEYSTROKE to our list. */ |
| *k++ = keystroke; |
| *k = '\0'; |
| |
| if (map[keystroke].value.function == NULL) |
| { |
| message_in_echo_area (_("%s is undefined"), pretty_keyseq (keys)); |
| return; |
| } |
| else if (map[keystroke].type == ISKMAP) |
| { |
| map = map[keystroke].value.keymap; |
| continue; |
| } |
| else |
| { |
| char *keyname, *message, *fundoc, *funname = ""; |
| |
| /* If the key is bound to do-lowercase-version, but its |
| lower-case variant is undefined, say that this key is |
| also undefined. This is especially important for unbound |
| edit keys that emit an escape sequence: it's terribly |
| confusing to see a message "Home (do-lowercase-version)" |
| or some such when Home is unbound. */ |
| if (map[keystroke].value.function |
| && map[keystroke].value.function->func |
| == info_do_lowercase_version) |
| { |
| int lowerkey; |
| |
| if (keystroke >= KEYMAP_META_BASE) |
| { |
| lowerkey = keystroke; |
| lowerkey -= KEYMAP_META_BASE; |
| lowerkey = tolower (lowerkey); |
| lowerkey += KEYMAP_META_BASE; |
| } |
| else |
| lowerkey = tolower (keystroke); |
| |
| if (map[lowerkey].value.function == NULL) |
| { |
| message_in_echo_area (_("%s is undefined"), |
| pretty_keyseq (keys)); |
| return; |
| } |
| } |
| |
| keyname = pretty_keyseq (keys); |
| |
| funname = function_name (map[keystroke].value.function); |
| |
| fundoc = function_documentation (map[keystroke].value.function); |
| |
| message = xmalloc |
| (10 + strlen (keyname) + strlen (fundoc) + strlen (funname)); |
| |
| sprintf (message, "%s (%s): %s.", keyname, funname, fundoc); |
| |
| window_message_in_echo_area ("%s", message); |
| free (message); |
| break; |
| } |
| } |
| } |
| |
| /* Return the pretty-printable name of a single key. */ |
| char * |
| pretty_keyname (int key) |
| { |
| static char rep_buffer[30]; |
| char *rep; |
| |
| if (key >= KEYMAP_META_BASE) |
| { |
| char temp[20]; |
| |
| rep = pretty_keyname (key - KEYMAP_META_BASE); |
| |
| sprintf (temp, "M-%s", rep); |
| strcpy (rep_buffer, temp); |
| rep = rep_buffer; |
| } |
| else if (Control_p (key)) |
| { |
| switch (key) |
| { |
| case '\n': rep = "LFD"; break; |
| case '\t': rep = "TAB"; break; |
| case '\r': rep = "RET"; break; |
| case ESC: rep = "ESC"; break; |
| |
| default: |
| sprintf (rep_buffer, "C-%c", UnControl (key)); |
| rep = rep_buffer; |
| } |
| } |
| else if (key >= 256) |
| switch (key) |
| { |
| case KEY_RIGHT_ARROW: |
| rep = "Right"; break; |
| case KEY_LEFT_ARROW: |
| rep = "Left"; break; |
| case KEY_UP_ARROW: |
| rep = "Up"; break; |
| case KEY_DOWN_ARROW: |
| rep = "Down"; break; |
| case KEY_PAGE_UP: |
| rep = "PgUp"; break; |
| case KEY_PAGE_DOWN: |
| rep = "PgDn"; break; |
| case KEY_HOME: |
| rep = "Home"; break; |
| case KEY_END: |
| rep = "End"; break; |
| case KEY_DELETE: |
| rep = "DEL"; break; |
| case KEY_INSERT: |
| rep = "INS"; break; |
| case KEY_BACK_TAB: |
| rep = "BackTab"; break; |
| case KEY_MOUSE: |
| rep = "(mouse event)"; break; |
| default: |
| rep = "(unknown key)"; break; /* This shouldn't be displayed. */ |
| } |
| else |
| { |
| switch (key) |
| { |
| case ' ': rep = "SPC"; break; |
| case DEL: rep = "DEL"; break; |
| default: |
| rep_buffer[0] = key; |
| rep_buffer[1] = '\0'; |
| rep = rep_buffer; |
| } |
| } |
| return rep; |
| } |
| |
| /* Return the pretty printable string which represents KEYSEQ. Return |
| value should not be freed by caller. */ |
| char * |
| pretty_keyseq (int *keyseq) |
| { |
| static struct text_buffer rep = { 0 }; |
| |
| if (!text_buffer_base (&rep)) |
| text_buffer_init (&rep); |
| else |
| text_buffer_reset (&rep); |
| |
| if (!*keyseq) |
| return ""; |
| |
| while (1) |
| { |
| text_buffer_printf (&rep, "%s", pretty_keyname (keyseq[0])); |
| keyseq++; |
| |
| if (!*keyseq) |
| break; |
| |
| text_buffer_add_char (&rep, ' '); |
| } |
| return text_buffer_base (&rep); |
| } |
| |
| /* Replace the names of functions with the key that invokes them. |
| Return value should not be freed by caller. */ |
| char * |
| replace_in_documentation (const char *string, int help_is_only_window_p) |
| { |
| register int i, start; |
| static struct text_buffer txtresult = {0}; |
| |
| text_buffer_free (&txtresult); |
| text_buffer_init (&txtresult); |
| text_buffer_alloc (&txtresult, strlen (string)); |
| |
| start = 0; |
| |
| /* Skip to the beginning of a replaceable function. */ |
| for (i = start; string[i]; i++) |
| { |
| int j = i + 1; |
| |
| /* Is this the start of a replaceable function name? */ |
| if (string[i] == '\\') |
| { |
| char *fmt = NULL; |
| |
| if(string[j] == '%') |
| { |
| if (string[++j] == '-') |
| j++; |
| if (isdigit(string[j])) |
| { |
| while (isdigit(string[j])) |
| j++; |
| if (string[j] == '.' && isdigit(string[j + 1])) |
| { |
| j += 1; |
| while (isdigit(string[j])) |
| j++; |
| } |
| fmt = xmalloc (j - i + 2); |
| strncpy (fmt, string + i + 1, j - i); |
| fmt[j - i - 1] = 's'; |
| fmt[j - i] = '\0'; |
| } |
| else |
| j = i + 1; |
| } |
| if (string[j] == '[') |
| { |
| char *rep_name, *fun_name, *rep; |
| InfoCommand *command; |
| unsigned replen; |
| |
| /* Copy in the old text. */ |
| text_buffer_add_string (&txtresult, string + start, i - start); |
| start = j + 1; |
| |
| /* Move to the end of the function name. */ |
| for (i = start; string[i] && (string[i] != ']'); i++); |
| |
| rep_name = xmalloc (1 + i - start); |
| strncpy (rep_name, string + start, i - start); |
| rep_name[i - start] = '\0'; |
| |
| start = i; |
| if (string[start] == ']') |
| start++; |
| |
| fun_name = rep_name; |
| if (strcmp (rep_name, "quit-help") == 0) |
| { |
| /* Special case for help window. If we have only one window |
| (because the window size was too small to split it), we |
| have to quit help by going back one node in the history |
| list, not deleting the window. */ |
| |
| fun_name = help_is_only_window_p ? "history-node" |
| : "get-help-window"; |
| } |
| |
| /* Find a key which invokes this function in the info_keymap. */ |
| command = named_function (fun_name); |
| |
| /* If the internal documentation string fails, there is a |
| problem with the associated command's documentation (probably |
| in the translation). */ |
| if (!command) |
| { |
| info_error ("bug: no command <%s>\n", rep_name); |
| sleep (1); |
| rep = "BUG"; |
| } |
| else |
| { |
| rep = where_is (info_keymap, command); |
| if (!rep) |
| rep = "N/A"; |
| } |
| replen = strlen (rep); |
| free (rep_name); |
| |
| if (fmt) |
| text_buffer_printf (&txtresult, fmt, rep); |
| else |
| text_buffer_add_string (&txtresult, rep, replen); |
| } |
| |
| free (fmt); |
| } |
| } |
| text_buffer_add_string (&txtresult, |
| string + start, strlen (string + start) + 1); |
| return text_buffer_base (&txtresult); |
| } |
| |
| /* Return a string of characters which could be typed from the keymap |
| MAP to invoke FUNCTION. */ |
| static char *where_is_rep = NULL; |
| static int where_is_rep_index = 0; |
| static int where_is_rep_size = 0; |
| |
| char * |
| where_is (Keymap map, InfoCommand *cmd) |
| { |
| char *rep; |
| |
| if (!where_is_rep_size) |
| where_is_rep = xmalloc (where_is_rep_size = 100); |
| where_is_rep_index = 0; |
| |
| rep = where_is_internal (map, cmd); |
| |
| /* If it couldn't be found, return "M-x Foo" (or equivalent). */ |
| if (!rep) |
| { |
| char *name; |
| |
| name = function_name (cmd); |
| if (!name) |
| return NULL; /* no such function */ |
| |
| rep = where_is_internal (map, InfoCmd(info_execute_command)); |
| if (!rep) |
| return ""; /* function exists but can't be got to by user */ |
| |
| sprintf (where_is_rep, "%s %s", rep, name); |
| |
| rep = where_is_rep; |
| } |
| return rep; |
| } |
| |
| /* Return the printed rep of the keystrokes that invoke FUNCTION, |
| as found in MAP, or NULL. */ |
| static char * |
| where_is_internal (Keymap map, InfoCommand *cmd) |
| { |
| register FUNCTION_KEYSEQ *k; |
| |
| for (k = cmd->keys; k; k = k->next) |
| if (k->map == map) |
| return pretty_keyseq (k->keyseq); |
| |
| return NULL; |
| } |
| |
| DECLARE_INFO_COMMAND (info_where_is, |
| _("Show what to type to execute a given command")) |
| { |
| char *command_name; |
| |
| command_name = read_function_name (_("Where is command: "), window); |
| |
| if (!command_name) |
| { |
| info_abort_key (active_window, count); |
| return; |
| } |
| |
| if (*command_name) |
| { |
| InfoCommand *command; |
| |
| command = named_function (command_name); |
| |
| if (command) |
| { |
| char *location; |
| |
| location = where_is (info_keymap, command); |
| |
| if (!location || !location[0]) |
| { |
| info_error (_("'%s' is not on any keys"), command_name); |
| } |
| else |
| { |
| if (strstr (location, function_name (command))) |
| window_message_in_echo_area |
| (_("%s can only be invoked via %s"), |
| command_name, location); |
| else |
| window_message_in_echo_area |
| (_("%s can be invoked via %s"), |
| command_name, location); |
| } |
| } |
| else |
| info_error (_("There is no function named '%s'"), command_name); |
| } |
| |
| free (command_name); |
| } |