| /* footnotes.c -- Some functions for manipulating footnotes. | |
| 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 "session.h" | |
| #include "scan.h" | |
| #include "nodes.h" | |
| #include "footnotes.h" | |
| /* Nonzero means attempt to show footnotes when displaying a new window. */ | |
| int auto_footnotes_p = 0; | |
| static char *footnote_nodename = "*Footnotes*"; | |
| /* Find the window currently showing footnotes. */ | |
| static WINDOW * | |
| find_footnotes_window (void) | |
| { | |
| WINDOW *win; | |
| /* Try to find an existing window first. */ | |
| for (win = windows; win; win = win->next) | |
| if (win->node && (win->node->flags & N_IsInternal) | |
| && (strcmp (win->node->nodename, footnote_nodename) == 0)) | |
| break; | |
| return win; | |
| } | |
| /* Manufacture a node containing the footnotes of this node, and | |
| return the manufactured node. If NODE has no footnotes, return a | |
| NULL pointer. */ | |
| NODE * | |
| make_footnotes_node (NODE *node) | |
| { | |
| NODE *fn_node = 0, *footnotes_node = NULL, *result = NULL; | |
| long fn_start; | |
| char *fnptr; | |
| /* Make the initial assumption that the footnotes appear as simple | |
| text within this windows node. */ | |
| /* See if this node contains the magic footnote label. */ | |
| { | |
| char saved = node->contents[node->nodelen]; | |
| node->contents[node->nodelen] = '\0'; | |
| fnptr = strstr (node->contents, FOOTNOTE_LABEL); | |
| node->contents[node->nodelen] = saved; | |
| } | |
| if (fnptr) | |
| { | |
| fn_node = node; | |
| fn_start = fnptr - node->contents; | |
| } | |
| /* If it doesn't, check to see if it has an associated footnotes node. */ | |
| else | |
| { | |
| REFERENCE **refs; | |
| refs = node->references; | |
| if (refs) | |
| { | |
| register int i; | |
| char *refname; | |
| int reflen = strlen ("-Footnotes") + strlen (node->nodename); | |
| refname = xmalloc (reflen + 1); | |
| strcpy (refname, node->nodename); | |
| strcat (refname, "-Footnotes"); | |
| for (i = 0; refs[i]; i++) | |
| if (refs[i]->type == REFERENCE_XREF | |
| && (refs[i]->nodename != NULL) | |
| /* Support both the older "foo-Footnotes" and the new | |
| style "foo-Footnote-NN" references. */ | |
| && (strcmp (refs[i]->nodename, refname) == 0 || | |
| (strncmp (refs[i]->nodename, refname, reflen - 1) == 0 && | |
| refs[i]->nodename[reflen - 1] == '-' && | |
| isdigit ((unsigned char) refs[i]->nodename[reflen])))) | |
| { | |
| footnotes_node = info_get_node (node->fullpath, refname); | |
| if (footnotes_node) | |
| { | |
| fn_node = footnotes_node; | |
| fn_start = 0; | |
| } | |
| break; | |
| } | |
| free (refname); | |
| } | |
| } | |
| /* Quit if we never found the node associated to the footnote. */ | |
| if (fn_node == 0) | |
| return NULL; | |
| /* Make the new node. */ | |
| result = info_create_node (); | |
| /* Get the size of the footnotes appearing within this node. */ | |
| { | |
| char *header; | |
| long text_start = fn_start; | |
| xasprintf (&header, | |
| "*** Footnotes appearing in the node '%s' ***\n", | |
| node->nodename); | |
| /* Move the start of the displayed text to right after the first line. | |
| This effectively skips either "---- footno...", or "File: foo...". */ | |
| while (text_start < fn_node->nodelen) | |
| if (fn_node->contents[text_start++] == '\n') | |
| break; | |
| result->nodelen = strlen (header) + fn_node->nodelen - text_start; | |
| /* Set the contents of this node. */ | |
| result->contents = xmalloc (1 + result->nodelen); | |
| sprintf (result->contents, "%s", header); | |
| memcpy (result->contents + strlen (header), | |
| fn_node->contents + text_start, fn_node->nodelen - text_start); | |
| result->contents[strlen (header) + fn_node->nodelen - text_start] = '\0'; | |
| /* Copy and adjust references that appear in footnotes section. */ | |
| { | |
| REFERENCE **ref = fn_node->references; | |
| for (; *ref; ref++) | |
| { | |
| if ((*ref)->start > text_start) | |
| break; | |
| } | |
| result->references = info_copy_references (ref); | |
| for (ref = result->references; *ref; ref++) | |
| { | |
| (*ref)->start -= text_start - strlen (header); | |
| (*ref)->end -= text_start - strlen (header); | |
| } | |
| } | |
| result->nodename = xstrdup (footnote_nodename); | |
| result->flags |= N_IsInternal | N_WasRewritten; | |
| /* Needed in case the user follows a reference in the footnotes window. */ | |
| result->fullpath = fn_node->fullpath; | |
| result->subfile = fn_node->subfile; | |
| free (header); | |
| } | |
| free_node (footnotes_node); | |
| return result; | |
| } | |
| /* Create or delete the footnotes window depending on whether footnotes | |
| exist in WINDOW's node or not. Returns FN_FOUND if footnotes were found | |
| and displayed. Returns FN_UNFOUND if there were no footnotes found | |
| in WINDOW's node. Returns FN_UNABLE if there were footnotes, but the | |
| window to show them couldn't be made. */ | |
| int | |
| info_get_or_remove_footnotes (WINDOW *window) | |
| { | |
| WINDOW *fn_win; | |
| NODE *new_footnotes = 0; | |
| fn_win = find_footnotes_window (); | |
| /* If we are in the footnotes window, change nothing. */ | |
| if (fn_win == window) | |
| return FN_FOUND; | |
| /* Don't display footnotes for the "*" node (entire contents of file) or | |
| for nodes without a name like completion windows. */ | |
| if (window->node && window->node->nodename | |
| && strcmp ("*", window->node->nodename)) | |
| /* Try to find footnotes for this window's node. */ | |
| new_footnotes = make_footnotes_node (window->node); | |
| if (!new_footnotes) | |
| { | |
| /* If there was a window showing footnotes, and there are no footnotes | |
| for the current window, delete the old footnote window. */ | |
| if (fn_win && windows->next) | |
| info_delete_window_internal (fn_win); | |
| return FN_UNFOUND; | |
| } | |
| /* If there is no window around showing footnotes, try | |
| to make a new window. */ | |
| if (!fn_win) | |
| { | |
| WINDOW *old_active; | |
| WINDOW *last, *win; | |
| /* Always make this window be the last one appearing in the list. Find | |
| the last window in the chain. */ | |
| for (win = windows, last = windows; win; last = win, win = win->next); | |
| /* Try to split this window, and make the split window the one to | |
| contain the footnotes. */ | |
| old_active = active_window; | |
| active_window = last; | |
| fn_win = window_make_window (); | |
| active_window = old_active; | |
| /* If we are hacking automatic footnotes, and there are footnotes | |
| but we couldn't display them, print a message to that effect. */ | |
| if (!fn_win) | |
| { | |
| if (auto_footnotes_p) | |
| info_error (_("Footnotes could not be displayed")); | |
| return FN_UNABLE; | |
| } | |
| } | |
| /* Note that info_set_node_of_window calls this function | |
| (info_get_or_remove_footnotes), but we do not recurse indefinitely | |
| because we check if we are in the footnote window above. */ | |
| info_set_node_of_window (fn_win, new_footnotes); | |
| fn_win->flags |= W_TempWindow; | |
| /* Make the height be the number of lines appearing in the footnotes. */ | |
| if (new_footnotes) | |
| window_change_window_height (fn_win, fn_win->line_count - fn_win->height); | |
| return FN_FOUND; | |
| } | |
| /* Show the footnotes associated with this node in another window. */ | |
| DECLARE_INFO_COMMAND (info_show_footnotes, | |
| _("Show the footnotes associated with this node in another window")) | |
| { | |
| /* Make the window go away if it is already showing. */ | |
| WINDOW *fn_win = find_footnotes_window (); | |
| /* If there is an old footnotes window, and it isn't the only window | |
| on the screen, delete it. */ | |
| if (fn_win && windows->next) | |
| { | |
| info_delete_window_internal (fn_win); | |
| return; | |
| } | |
| switch (info_get_or_remove_footnotes (window)) | |
| { | |
| case FN_UNFOUND: | |
| info_error ("%s", _("No footnotes in this node")); | |
| break; | |
| case FN_UNABLE: | |
| info_error ("%s", msg_win_too_small); | |
| break; | |
| } | |
| } |