/* nodemenu.c -- produce a menu of all visited nodes. Copyright 1993-2023 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 . Originally written by Brian Fox. */ #include "info.h" #include "session.h" #include "util.h" #include "scan.h" #include "echo-area.h" #include "variables.h" static NODE *get_visited_nodes (void); /* Return a line describing the format of a node information line. */ static const char * nodemenu_format_info (void) { /* TRANSLATORS: The "\n* Menu:\n\n" part of this should not be translated, as it is part of the Info syntax. */ return _("\n* Menu:\n\n\ (File)Node Lines Size Containing File\n\ ---------- ----- ---- ---------------"); } /* Produce a formatted line of information about NODE. Here is what we want the output listing to look like: * Menu: (File)Node Lines Size Containing File ---------- ----- ---- --------------- * (emacs)Buffers:: 48 2230 /usr/gnu/info/emacs/emacs-1 * (autoconf)Writing configure.in:: 123 58789 /usr/gnu/info/autoconf/autoconf-1 * (dir)Top:: 40 589 /usr/gnu/info/dir */ static char * format_node_info (NODE *node) { register int i; char *containing_file; static struct text_buffer line_buffer = { 0 }; if (!text_buffer_base (&line_buffer)) text_buffer_init (&line_buffer); else text_buffer_reset (&line_buffer); if (node->subfile) containing_file = node->subfile; else containing_file = node->fullpath; if (!containing_file || !*containing_file) text_buffer_printf (&line_buffer, "* %s::", node->nodename); else text_buffer_printf (&line_buffer, "* (%s)%s::", filename_non_directory (node->fullpath), node->nodename); for (i = text_buffer_off (&line_buffer); i < 36; i++) text_buffer_add_char (&line_buffer, ' '); { int lines = 1; for (i = 0; i < node->nodelen; i++) if (node->contents[i] == '\n') lines++; text_buffer_printf (&line_buffer, "%d", lines); } text_buffer_add_char (&line_buffer, ' '); for (i = text_buffer_off (&line_buffer); i < 44; i++) text_buffer_add_char (&line_buffer, ' '); text_buffer_printf (&line_buffer, "%ld", node->nodelen); if (containing_file) { for (i = text_buffer_off (&line_buffer); i < 51; i++) text_buffer_add_char (&line_buffer, ' '); text_buffer_printf (&line_buffer, containing_file); } return xstrdup (text_buffer_base (&line_buffer)); } /* Little string comparison routine for qsort (). */ static int compare_strings (const void *entry1, const void *entry2) { char **e1 = (char **) entry1; char **e2 = (char **) entry2; return mbscasecmp (*e1, *e2); } /* The name of the nodemenu node. */ static char *nodemenu_nodename = "*Node Menu*"; /* Produce an informative listing of all the visited nodes, and return it in a newly allocated node. */ static NODE * get_visited_nodes (void) { register int i; WINDOW *info_win; NODE *node; char **lines = NULL; size_t lines_index = 0, lines_slots = 0; struct text_buffer message; for (info_win = windows; info_win; info_win = info_win->next) { for (i = 0; i < info_win->hist_index; i++) { NODE *history_node = info_win->hist[i]->node; /* We skip mentioning "*Node Menu*" nodes. */ if (strcmp (history_node->nodename, nodemenu_nodename) == 0) continue; if (history_node) { char *line; line = format_node_info (history_node); add_pointer_to_array (line, lines_index, lines, lines_slots, 20); } } } /* Sort the array of information lines, if there are any. */ if (lines) { register int j, newlen; char **temp; qsort (lines, lines_index, sizeof (char *), compare_strings); /* Delete duplicates. */ for (i = 0, newlen = 1; i < lines_index - 1; i++) { /* Use FILENAME_CMP here, since the most important piece of info in each line is the file name of the node. */ if (FILENAME_CMP (lines[i], lines[i + 1]) == 0) { free (lines[i]); lines[i] = NULL; } else newlen++; } /* We have free ()'d and marked all of the duplicate slots. Copy the live slots rather than pruning the dead slots. */ temp = xmalloc ((1 + newlen) * sizeof (char *)); for (i = 0, j = 0; i < lines_index; i++) if (lines[i]) temp[j++] = lines[i]; temp[j] = NULL; free (lines); lines = temp; lines_index = newlen; } text_buffer_init (&message); text_buffer_printf (&message, "\n"); text_buffer_printf (&message, "%s", replace_in_documentation (_("Here is the menu of nodes you have recently visited.\n\ Select one from this menu, or use '\\[history-node]' in another window.\n"), 0)); text_buffer_printf (&message, "%s\n", nodemenu_format_info ()); for (i = 0; (lines != NULL) && (i < lines_index); i++) { text_buffer_printf (&message, "%s\n", lines[i]); free (lines[i]); } if (lines) free (lines); node = text_buffer_to_node (&message); scan_node_contents (node, 0, 0); return node; } DECLARE_INFO_COMMAND (list_visited_nodes, _("Make a window containing a menu of all of the currently visited nodes")) { WINDOW *new; NODE *node; /* If a window is visible and showing the buffer list already, re-use it. */ for (new = windows; new; new = new->next) { node = new->node; if (internal_info_node_p (node) && (strcmp (node->nodename, nodemenu_nodename) == 0)) break; } /* If we couldn't find an existing window, try to use the next window in the chain. */ if (!new) { if (window->next) new = window->next; /* If there is more than one window, wrap around. */ else if (window != windows) new = windows; } /* If we still don't have a window, make a new one to contain the list. */ if (!new) new = window_make_window (); /* If we couldn't make a new window, use this one. */ if (!new) new = window; /* Lines do not wrap in this window. */ new->flags |= W_NoWrap; node = get_visited_nodes (); name_internal_node (node, xstrdup (nodemenu_nodename)); node->flags |= N_WasRewritten; info_set_node_of_window (new, node); active_window = new; } DECLARE_INFO_COMMAND (select_visited_node, _("Select a node which has been previously visited in a visible window")) { char *line; NODE *node; node = get_visited_nodes (); line = info_read_completing_in_echo_area (_("Select visited node: "), node->references); window = active_window; if (!line) /* User aborts, just quit. */ info_abort_key (window, 0); else if (*line) { REFERENCE *entry; /* Find the selected label in the references. */ entry = info_get_menu_entry_by_label (node, line, 0); if (!entry) /* This shouldn't happen, because LINE was in the completion list built from the list of references. */ info_error (_("The reference disappeared! (%s)."), line); else info_select_reference (window, entry); } free (line); free (node); }