From 04334fe2a97d8f21f684fa2860f343b4a236a391 Mon Sep 17 00:00:00 2001 From: Arthur Miller Date: Tue, 30 May 2023 14:09:42 +0200 Subject: [PATCH] Use help- and Info-mode commands from any buffer Allow commands that act on help-mode and Info-mode to be called from other buffers than just help and Info buffer. * etc/NEWS: Mention the change. * doc/emacs/help.texi: Update relevant commands mentioned in the text. * lisp/help-mode.el (help-jump): New symbol used for both command and global variable. Command jumps to/from help window; var records 'jumped from'. * lisp/help-mode.el (help-view-source): * lisp/help-mode.el (help-goto-info): * lisp/help-mode.el (help-go-back): * lisp/help-mode.el (help-go-forward): * lisp/help-mode.el (help-goto-next-page): * lisp/help-mode.el (help-goto-previous-page): * lisp/help-mode.el (help-goto-lispref-info): * lisp/help-mode.el (help-customize): Help-mode commands adapted to be called from any buffer. * lisp/help-mode.el (help-quit-window): * lisp/help-mode.el (help-revert-buffer): * lisp/help-mode.el (help-describe-mode): * lisp/help-mode.el (help-beginning-of-buffer): * lisp/help-mode.el (help-end-of-buffer): * lisp/help-mode.el (help-scroll-up-command): * lisp/help-mode.el (help-scroll-down-command): * lisp/help-mode.el (help-forward-button): * lisp/help-mode.el (help-backward-button): * lisp/help-mode.el (help-button-describe): * lisp/help-mode.el (help-push-button): New commands. Do what they wrapped counterparts without 'help-' prefix do, but specifically in help buffer. * lisp/help-mode.el (help-mode-map): * lisp/help-mode.el (help-mode-menu): Update bindings to reflect the new commands for previously generic commands Bind help-jump to 'j' (previously unused). * lisp/help-mode.el (help-mode-prefix-key): New defcustom declaration. Prefix key for help-mode-map. * lisp/info.el (Info-mode-prefix-key): New defcustom declaration. Prefix key for Info-mode-map. * lisp/info.el (Info-menu): * lisp/info.el (Info-next): * lisp/info.el (Info-prev): * lisp/info.el (Info-up): * lisp/info.el (Info-history-back): * lisp/info.el (Info-history-forward): * lisp/info.el (Info-directory): * lisp/info.el (Info-toc): * lisp/info.el (Info-nth-menu-item): * lisp/info.el (Info-top-node): * lisp/info.el (Info-final-node): * lisp/info.el (Info-forward-node): * lisp/info.el (Info-backward-node): * lisp/info.el (Info-next-menu-item): * lisp/info.el (Info-last-menu-item): * lisp/info.el (Info-next-preorder): * lisp/info.el (Info-last-preorder): * lisp/info.el (Info-scroll-up): * lisp/info.el (Info-scroll-down): * lisp/info.el (Info-next-reference): * lisp/info.el (Info-prev-reference): * lisp/info.el (Info-index): * lisp/info.el (Info-index-next): * lisp/info.el (Info-virtual-next): * lisp/info.el (Info-apropos): * lisp/info.el (Info-finder): * lisp/info.el (Info-summary): * lisp/info.el (Info-copy-current-node-name): Info-mode commands adapted to be called from any buffer. * lisp/info.el (Info-describe-mode): * lisp/info.el (Info-quit-window): * lisp/info.el (Info-beginning-of-buffer): * lisp/info.el (Info-end-of-buffer): New commands. Do what they wrapped counterparts without 'Info-' prefix do, but specifically in Info buffer. * lisp/info.el (Info-jump): New symbol used for both command and global variable. Command jumps to/from Info window; var records 'jumped from'. * lisp/info.el (Info-mode-map): * lisp/info.el (Info-mode-menu): Update bindings to reflect the new commands for previously generic commands Bind Info-jump to 'j' (previously unused). --- doc/emacs/help.texi | 12 +- etc/NEWS | 19 + lisp/help-mode.el | 260 ++++++++-- lisp/info.el | 1156 +++++++++++++++++++++++++------------------ 4 files changed, 928 insertions(+), 519 deletions(-) diff --git a/doc/emacs/help.texi b/doc/emacs/help.texi index 945e12a05d2..b50905c3dc4 100644 --- a/doc/emacs/help.texi +++ b/doc/emacs/help.texi @@ -494,9 +494,9 @@ Help Mode @item @key{RET} Follow a cross reference at point (@code{help-follow}). @item @key{TAB} -Move point forward to the next hyperlink (@code{forward-button}). +Move point forward to the next hyperlink (@code{help-forward-button}). @item S-@key{TAB} -Move point back to the previous hyperlink (@code{backward-button}). +Move point back to the previous hyperlink (@code{help-backward-button}). @item mouse-1 @itemx mouse-2 Follow a hyperlink that you click on. @@ -544,12 +544,12 @@ Help Mode (@code{help-go-forward}). @kindex TAB @r{(Help mode)} -@findex forward-button +@findex help-forward-button @kindex S-TAB @r{(Help mode)} -@findex backward-button +@findex help-backward-button To move between hyperlinks in a help buffer, use @key{TAB} -(@code{forward-button}) to move forward to the next hyperlink and -@kbd{S-@key{TAB}} (@code{backward-button}) to move back to the +(@code{help-forward-button}) to move forward to the next hyperlink and +@kbd{S-@key{TAB}} (@code{help-backward-button}) to move back to the previous hyperlink. These commands act cyclically; for instance, typing @key{TAB} at the last hyperlink moves back to the first hyperlink. diff --git a/etc/NEWS b/etc/NEWS index 3c71e52fff4..fe241d604cb 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -30,6 +30,25 @@ applies, and please also update docstrings as needed. * Changes in Emacs 30.1 ++++ +** New user option 'info-mode-prefix-key. +Prefix key for `Info-mode-map, so it can be used from other buffer but +just *info* buffer. + +** Commands acting on Info buffer can now be used from any buffer +Similar to help-mode, keyboard-driven commands acting in Info-mode and +bound in Info-mode-map can now be used from any buffer (acting on Info +buffer). ++++ +** New user option 'help-mode-prefix-key'. +The key is used to assign `help-mode-map' to a prefix, so it can be +used as a prefix-key from other buffers but just *Help* buffer. + +** Commands acting on Help buffer can now be used from any buffer +This means that all commands that normally required a user to switch +to *Help* buffer before the invocation, can now be invoked from any +buffer. With other words, you can now control Help buffer from any +other buffer, which lessens amount of switching between buffers. --- ** New user option 'describe-bindings-outline-rules'. This user option controls outline visibility in the output buffer of diff --git a/lisp/help-mode.el b/lisp/help-mode.el index bf64d032b65..af41686e3fd 100644 --- a/lisp/help-mode.el +++ b/lisp/help-mode.el @@ -47,7 +47,22 @@ help-mode-map "s" #'help-view-source "I" #'help-goto-lispref-info "i" #'help-goto-info - "c" #'help-customize) + "j" #'help-jump + "c" #'help-customize + "g" #'help-revert-buffer + "q" #'help-quit-window + "<" #'help-beginning-of-buffer + ">" #'help-end-of-buffer + "h" #'help-describe-mode + "?" #'help-describe-mode + "DEL" #'help-scroll-down-command + "SPC" #'help-scroll-down-command + "S-SPC" #'help-scroll-up-command + "RET" #'help-push-button + "TAB" #'help-forward-button + "C-M-i" #'help-backward-button + "" #'help-backward-button + "ESC TAB" #'help-backward-button) (easy-menu-define help-mode-menu help-mode-map "Menu for Help mode." @@ -60,9 +75,9 @@ help-mode-menu ["Next Topic" help-go-forward :help "Go back to next topic in this help buffer" :active help-xref-forward-stack] - ["Move to Previous Button" backward-button + ["Move to Previous Button" help-backward-button :help "Move to the Previous Button in the help buffer"] - ["Move to Next Button" forward-button + ["Move to Next Button" help-forward-button :help "Move to the Next Button in the help buffer"] ["View Source" help-view-source :help "Go to the source file for the current help item"] @@ -150,6 +165,22 @@ help-mode-hook "Hook run by `help-mode'." :type 'hook :group 'help) + +;; with a little help from Helm +(defcustom help-mode-prefix-key "C-h M-h" + "`help-mode-map' prefix key for invocation from other buffers." + :version "30.1" + :type '(choice (string :tag "Key") (const :tag "no binding")) + :set (lambda (var key) + (when (and (boundp var) (symbol-value var)) + (define-key (current-global-map) + (read-kbd-macro (symbol-value var)) nil)) + (when key + (define-key (current-global-map) + (read-kbd-macro key) + help-mode-map)) + (set var key)) + :group 'help) ;; Button types used by help @@ -763,7 +794,127 @@ help-xref-on-pp ;;;###autoload (define-obsolete-function-alias 'help-xref-interned #'describe-symbol "25.1") + +;; commands from special-mode wrapped to work on help-mode only + +(defun help-quit-window () + "As `quit-window' but works only on *Help* buffer." + (interactive) + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (with-selected-window help-window + (quit-window nil help-window)))) + +(defun help-describe-mode () + "As `describe-mode' but for *Help* buffer only." + (interactive) + (let ((help-buffer (get-buffer (help-buffer)))) + (unless (window-live-p (get-buffer-window help-buffer)) + (user-error "Help buffer is not currently visible.")) + (describe-mode help-buffer))) + +(defun help-beginning-of-buffer () + "As `help-beginning-of-buffer' but for *Help* buffer only." + (interactive) + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (with-selected-window help-window + (goto-char (point-min))))) + +(defun help-end-of-buffer () + "As `help-end-of-buffer' but for *Help* buffer only." + (interactive) + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (with-selected-window help-window + (goto-char (point-max))))) + +;; from files.el, for completeness and to eliminate potential confusion +(defun help-revert-buffer () + "As `revert-buffer', but act on help buffer specifically." + (interactive) + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (with-selected-window help-window + (call-interactively #'revert-buffer)))) + +;; Commands from button.el wrapped to work on help-mode only + +(defun help-forward-button () + (interactive) + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (with-selected-window help-window + (forward-button 1)))) + +(defun help-backward-button () + (interactive) + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (with-selected-window help-window + (backward-button 1)))) + +(defun help-button-describe () + (interactive) + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (with-selected-window help-window + (button-describe)))) + +(defun help-push-button () + (interactive) + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (error "Help buffer is not currently visible.")) + (with-selected-window help-window + (push-button)))) + +;; Commands from window.el wrapped to work on help-mode only + +(defun help-scroll-up-command (&optional arg) + "As `scroll-up-command' but works only on *Help* buffer." + (interactive "^P") + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (error "Help buffer is not currently visible.")) + (with-selected-window help-window + (scroll-up-command arg)))) + +(defun help-scroll-down-command (&optional arg) + "As `scroll-down-command' but works only on *Help* buffer." + (interactive "^P") + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (with-selected-window help-window + (scroll-down-command arg)))) + + + +(defvar help-jump nil) +(defun help-jump () + "Jump to and from *Help* window." + (interactive) + (let ((help-window (get-buffer-window (help-buffer))) + (current-window (selected-window))) + (cond + ((eq help-window current-window) + (unless help-jump + (user-error "No previously selected window to jump to.")) + (select-window help-jump)) + (t + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (setq help-jump current-window) + (select-window help-window))))) ;; Navigation/hyperlinking with xrefs @@ -810,25 +961,37 @@ help-xref-go-forward (defun help-go-back () "Go back to previous topic in this help buffer." (interactive) - (if help-xref-stack - (help-xref-go-back (current-buffer)) - (user-error "No previous help buffer"))) + (let ((help-buffer (get-buffer (help-buffer)))) + (unless (window-live-p (get-buffer-window help-buffer)) + (user-error "Help buffer is not currently visible.")) + (with-current-buffer help-buffer + (if help-xref-stack + (help-xref-go-back (current-buffer)) + (user-error "No previous help buffer"))))) (defun help-go-forward () "Go to the next topic in this help buffer." (interactive) - (if help-xref-forward-stack - (help-xref-go-forward (current-buffer)) - (user-error "No next help buffer"))) + (let ((help-buffer (get-buffer (help-buffer)))) + (unless (window-live-p (get-buffer-window help-buffer)) + (user-error "Help buffer is not currently visible.")) + (with-current-buffer help-buffer + (if help-xref-forward-stack + (help-xref-go-forward (current-buffer)) + (user-error "No next help buffer"))))) (defun help-goto-next-page () "Go to the next page (if any) in the current buffer. The help buffers are divided into \"pages\" by the ^L character." (interactive nil help-mode) - (push-mark) - (forward-page) - (unless (eobp) - (forward-line 1))) + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (with-selected-window help-window + (push-mark) + (forward-page) + (unless (eobp) + (forward-line 1))))) (defun help-goto-previous-page () "Go to the previous page (if any) in the current buffer. @@ -836,47 +999,68 @@ help-goto-previous-page The help buffers are divided into \"pages\" by the ^L character." (interactive nil help-mode) - (push-mark) - (backward-page (if (looking-back "\f\n" (- (point) 5)) 2 1)) - (unless (bobp) - (forward-line 1))) + (let ((help-window (get-buffer-window (help-buffer)))) + (unless (window-live-p help-window) + (user-error "Help buffer is not currently visible.")) + (with-selected-window help-window + (push-mark) + (backward-page (if (looking-back "\f\n" (- (point) 5)) 2 1)) + (unless (bobp) + (forward-line 1))))) (defun help-view-source () "View the source of the current help item." (interactive nil help-mode) - (unless (plist-get help-mode--current-data :file) - (error "Source file for the current help item is not defined")) - (help-function-def--button-function - (plist-get help-mode--current-data :symbol) - (plist-get help-mode--current-data :file) - (plist-get help-mode--current-data :type))) + (let ((help-buffer (get-buffer (help-buffer)))) + (unless (window-live-p (get-buffer-window help-buffer)) + (user-error "Help buffer is not currently visible.")) + (with-current-buffer help-buffer + (unless (plist-get help-mode--current-data :file) + (error "Source file for the current help item is not defined")) + (help-function-def--button-function + (plist-get help-mode--current-data :symbol) + (plist-get help-mode--current-data :file) + (plist-get help-mode--current-data :type))))) (defun help-goto-info () "View the *info* node of the current help item." (interactive nil help-mode) - (unless help-mode--current-data - (error "No symbol to look up in the current buffer")) - (info-lookup-symbol (plist-get help-mode--current-data :symbol) - 'emacs-lisp-mode - help-window-keep-selected)) + (let ((help-buffer (get-buffer (help-buffer)))) + (unless (window-live-p (get-buffer-window help-buffer)) + (user-error "Help buffer is not currently visible.")) + (with-current-buffer help-buffer + (unless help-mode--current-data + (error "No symbol to look up in the current buffer")) + (with-selected-window (get-buffer-window help-buffer) + (info-lookup-symbol (plist-get help-mode--current-data :symbol) + 'emacs-lisp-mode + help-window-keep-selected))))) (defun help-goto-lispref-info () "View the Emacs Lisp manual *info* node of the current help item." (interactive nil help-mode) - (unless help-mode--current-data - (error "No symbol to look up in the current buffer")) - (info-lookup-symbol (plist-get help-mode--current-data :symbol) - 'emacs-lisp-only)) + (let ((help-buffer (get-buffer (help-buffer)))) + (unless (window-live-p (get-buffer-window help-buffer)) + (user-error "Help buffer is not currently visible.")) + (with-current-buffer help-buffer + (unless help-mode--current-data + (error "No symbol to look up in the current buffer")) + (info-lookup-symbol (plist-get help-mode--current-data :symbol) + 'emacs-lisp-only)))) (defun help-customize () "Customize variable or face whose doc string is shown in the current buffer." (interactive nil help-mode) - (let ((sym (plist-get help-mode--current-data :symbol))) - (unless (or (boundp sym) (facep sym)) - (user-error "No variable or face to customize")) - (cond - ((boundp sym) (customize-variable sym)) - ((facep sym) (customize-face sym))))) + (let ((help-buffer (get-buffer (help-buffer)))) + (unless (window-live-p (get-buffer-window help-buffer)) + (user-error "Help buffer is not currently visible.")) + (with-current-buffer help-buffer + (let ((sym (plist-get help-mode--current-data :symbol))) + (unless (or (boundp sym) (facep sym)) + (user-error "No variable or face to customize")) + (cond + ((boundp sym) (customize-variable sym)) + ((facep sym) (customize-face sym))))))) (defun help-do-xref (_pos function args) "Call the help cross-reference function FUNCTION with args ARGS. diff --git a/lisp/info.el b/lisp/info.el index 035dff66e75..dcc6ed667d8 100644 --- a/lisp/info.el +++ b/lisp/info.el @@ -819,6 +819,22 @@ info-standalone (save-buffers-kill-emacs))) (info))) +(defvar Info-jump nil) +(defun Info-jump () + "Jump to and from *info* window." + (interactive) + (let ((info-window (get-buffer-window "*info*")) + (current-window (selected-window))) + (cond + ((eq info-window (selected-window)) + (unless Info-jump + (error "No previously selected window to jump to.")) + (select-window Info-jump)) + (t + (when (window-live-p info-window) + (setq Info-jump current-window) + (select-window info-window)))))) + ;; See if the accessible portion of the buffer begins with a node ;; delimiter, and the node header line which follows matches REGEXP. ;; Typically, this test will be followed by a loop that examines the @@ -831,12 +847,12 @@ info-standalone ;; The return value is the value of point at the beginning of matching ;; REGEXP, if the function succeeds, nil otherwise. (defun Info-node-at-bob-matching (regexp) - (and (bobp) ; are we at beginning of buffer? - (looking-at "\^_") ; does it begin with node delimiter? + (and (bobp) ; are we at beginning of buffer? + (looking-at "\^_") ; does it begin with node delimiter? (let (beg) (forward-line 1) (setq beg (point)) - (forward-line 1) ; does the line after delimiter match REGEXP? + (forward-line 1) ; does the line after delimiter match REGEXP? (re-search-backward regexp beg t)))) (defun Info-find-file (filename &optional noerror no-pop-to-dir) @@ -2273,85 +2289,108 @@ Info-next "Go to the \"next\" node, staying on the same hierarchical level. This command doesn't descend into sub-nodes, like \\\\[Info-forward-node] does." (interactive nil Info-mode) - ;; In case another window is currently selected - (save-window-excursion - (or (derived-mode-p 'Info-mode) (switch-to-buffer "*info*")) - (Info-goto-node (Info-extract-pointer "next")))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + ;; In case another window is currently selected + (save-window-excursion + (or (derived-mode-p 'Info-mode) (switch-to-buffer "*info*")) + (Info-goto-node (Info-extract-pointer "next"))))) (defun Info-prev () "Go to the \"previous\" node, staying on the same hierarchical level. This command doesn't go up to the parent node, like \\\\[Info-backward-node] does." (interactive nil Info-mode) - ;; In case another window is currently selected - (save-window-excursion - (or (derived-mode-p 'Info-mode) (switch-to-buffer "*info*")) - (Info-goto-node (Info-extract-pointer "prev[ious]*" "previous")))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + ;; In case another window is currently selected + (save-window-excursion + (or (derived-mode-p 'Info-mode) (switch-to-buffer "*info*")) + (Info-goto-node (Info-extract-pointer "prev[ious]*" "previous")) + ;; for some reason my Emacs does not update correctly info buffer after + ;; going back to a previous node, reverting buffer fixes it + (revert-buffer t t t)))) (defun Info-up (&optional same-file) "Go to the superior node of this node. If SAME-FILE is non-nil, do not move to a different Info file." (interactive nil Info-mode) - ;; In case another window is currently selected - (save-window-excursion - (or (derived-mode-p 'Info-mode) (switch-to-buffer "*info*")) - (let ((old-node Info-current-node) - (old-file Info-current-file) - (node (Info-extract-pointer "up")) p) - (and same-file - (string-match "^(" node) - (error "Up node is in another Info file")) - (Info-goto-node node) - (setq p (point)) - (goto-char (point-min)) - (if (and (stringp old-file) - (search-forward "\n* Menu:" nil t) - (re-search-forward - (if (string-equal old-node "Top") - (concat "\n\\*[^:]+: +(" (file-name-nondirectory old-file) ")") - (concat "\n\\* +\\(" (regexp-quote old-node) - ":\\|[^:]+: +" (regexp-quote old-node) "\\)")) - nil t)) - (progn (beginning-of-line) (if (looking-at "^\\* ") (forward-char 2))) - (goto-char p) - (Info-restore-point Info-history)))) - ;; If scroll-conservatively is non-zero and less than 101, display - ;; as much of the superior node above the target line as possible. - (when (< 0 scroll-conservatively 101) - (recenter))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + ;; In case another window is currently selected + (save-window-excursion + (or (derived-mode-p 'Info-mode) (switch-to-buffer "*info*")) + (let ((old-node Info-current-node) + (old-file Info-current-file) + (node (Info-extract-pointer "up")) p) + (and same-file + (string-match "^(" node) + (error "Up node is in another Info file")) + (Info-goto-node node) + (setq p (point)) + (goto-char (point-min)) + (if (and (stringp old-file) + (search-forward "\n* Menu:" nil t) + (re-search-forward + (if (string-equal old-node "Top") + (concat "\n\\*[^:]+: +(" (file-name-nondirectory old-file) ")") + (concat "\n\\* +\\(" (regexp-quote old-node) + ":\\|[^:]+: +" (regexp-quote old-node) "\\)")) + nil t)) + (progn (beginning-of-line) (if (looking-at "^\\* ") (forward-char 2))) + (goto-char p) + (Info-restore-point Info-history))) + ;; If scroll-conservatively is non-zero and less than 101, display + ;; as much of the superior node above the target line as possible. + ;; (when (< 0 scroll-conservatively 101) (recenter)) + ;; for some reason, in my Emacs, this is the only obe that actually + ;; displays correctly, recentering leavs last line at the top + (revert-buffer t t t))))) (defun Info-history-back () "Go back in the history to the last node visited." (interactive nil Info-mode) - (or Info-history - (user-error "This is the first Info node you looked at")) - (let ((history-forward - (cons (list Info-current-file Info-current-node (point)) - Info-history-forward)) - filename nodename opoint) - (setq filename (car (car Info-history))) - (setq nodename (car (cdr (car Info-history)))) - (setq opoint (car (cdr (cdr (car Info-history))))) - (setq Info-history (cdr Info-history)) - (Info-find-node filename nodename) - (setq Info-history (cdr Info-history)) - (setq Info-history-forward history-forward) - (goto-char opoint))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (or Info-history + (user-error "This is the first Info node you looked at")) + (let ((history-forward + (cons (list Info-current-file Info-current-node (point)) + Info-history-forward)) + filename nodename opoint) + (setq filename (car (car Info-history))) + (setq nodename (car (cdr (car Info-history)))) + (setq opoint (car (cdr (cdr (car Info-history))))) + (setq Info-history (cdr Info-history)) + (Info-find-node filename nodename) + (setq Info-history (cdr Info-history)) + (setq Info-history-forward history-forward) + (goto-char opoint))))) (defalias 'Info-last 'Info-history-back) (defun Info-history-forward () "Go forward in the history of visited nodes." (interactive nil Info-mode) - (or Info-history-forward - (user-error "This is the last Info node you looked at")) - (let ((history-forward (cdr Info-history-forward)) - filename nodename opoint) - (setq filename (car (car Info-history-forward))) - (setq nodename (car (cdr (car Info-history-forward)))) - (setq opoint (car (cdr (cdr (car Info-history-forward))))) - (Info-find-node filename nodename) - (setq Info-history-forward history-forward) - (goto-char opoint))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (or Info-history-forward + (user-error "This is the last Info node you looked at")) + (let ((history-forward (cdr Info-history-forward)) + filename nodename opoint) + (setq filename (car (car Info-history-forward))) + (setq nodename (car (cdr (car Info-history-forward)))) + (setq opoint (car (cdr (cdr (car Info-history-forward))))) + (Info-find-node filename nodename) + (setq Info-history-forward history-forward) + (goto-char opoint))))) (add-to-list 'Info-virtual-files '("\\`dir\\'" @@ -2377,7 +2416,11 @@ Info-directory-find-node (defun Info-directory () "Go to the Info directory node." (interactive) - (Info-find-node "dir" "top")) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (Info-find-node "dir" "top")))) (add-to-list 'Info-virtual-files '("\\`\\*History\\*\\'" @@ -2416,9 +2459,13 @@ Info-history-find-node (defun Info-history () "Go to a node with a menu of visited nodes." (interactive nil Info-mode) - (Info-find-node "*History*" "Top") - (Info-next-reference) - (Info-next-reference)) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (Info-find-node "*History*" "Top") + (Info-next-reference) + (Info-next-reference)))) (add-to-list 'Info-virtual-nodes '("\\`\\*TOC\\*\\'" @@ -2453,12 +2500,16 @@ Info-toc "Go to a node with table of contents of the current Info file. Table of contents is created from the tree structure of menus." (interactive nil Info-mode) - (Info-find-node Info-current-file "*TOC*") - (let ((prev-node (nth 1 (car Info-history))) p) - (goto-char (point-min)) - (if (setq p (search-forward (concat "*Note " prev-node ":") nil t)) - (setq p (- p (length prev-node) 2))) - (goto-char (or p (point-min))))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (Info-find-node Info-current-file "*TOC*") + (let ((prev-node (nth 1 (car Info-history))) p) + (goto-char (point-min)) + (if (setq p (search-forward (concat "*Note " prev-node ":") nil t)) + (setq p (- p (length prev-node) 2))) + (goto-char (or p (point-min))))))) (defun Info-toc-insert (nodes node-list level curr-file) "Insert table of contents with references to nodes." @@ -2798,38 +2849,45 @@ Info-menu new buffer." (interactive (let (;; If point is within a menu item, use that item as the default - (default nil) - (p (point)) - beg - (case-fold-search t)) + (default nil) + (p (point)) + beg + (case-fold-search t) + (info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (if (not (eq (selected-window) info-window)) + (Info-jump) + (setq Info-jump nil)) (save-excursion (goto-char (point-min)) (if (not (search-forward "\n* menu:" nil t)) - (user-error "No menu in this node")) + (user-error "No menu in this node")) (setq beg (point)) (and (< (point) p) - (save-excursion - (goto-char p) - (end-of-line) - (if (re-search-backward (concat "\n\\* +\\(" - Info-menu-entry-name-re - "\\):") + (save-excursion + (goto-char p) + (end-of-line) + (if (re-search-backward (concat "\n\\* +\\(" + Info-menu-entry-name-re + "\\):") beg t) - (setq default (match-string-no-properties 1)))))) + (setq default (match-string-no-properties 1)))))) (let ((item nil)) (while (null item) - (setq item (let ((completion-ignore-case t) - (Info-complete-menu-buffer (current-buffer))) - (completing-read (format-prompt "Menu item" default) - #'Info-complete-menu-item nil t nil nil + (setq item (let ((completion-ignore-case t) + (Info-complete-menu-buffer (current-buffer))) + (completing-read (format-prompt "Menu item" default) + #'Info-complete-menu-item nil t nil nil default)))) (list item current-prefix-arg))) Info-mode) ;; there is a problem here in that if several menu items have the same ;; name you can only go to the node of the first with this command. (Info-goto-node (Info-extract-menu-item menu-item) - (and fork - (if (stringp fork) fork menu-item)))) + (and fork + (if (stringp fork) fork menu-item))) + (when Info-jump (Info-jump))) (defun Info-extract-menu-item (menu-item) (setq menu-item (regexp-quote menu-item)) @@ -2869,32 +2927,44 @@ Info-nth-menu-item "Go to the node of the Nth menu item. N is the digit argument used to invoke this command." (interactive nil Info-mode) - (Info-goto-node - (Info-extract-menu-counting - (- (aref (this-command-keys) (1- (length (this-command-keys)))) ?0)))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (Info-goto-node + (Info-extract-menu-counting + (- (aref (this-command-keys) (1- (length (this-command-keys)))) ?0)))))) (defun Info-top-node () "Go to the Top node of this file." (interactive nil Info-mode) - (Info-goto-node "Top")) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (Info-goto-node "Top")))) (defun Info-final-node () "Go to the final node in this file." (interactive nil Info-mode) - (Info-goto-node "Top") - (let ((Info-history nil) - (case-fold-search t)) - ;; Go to the last node in the menu of Top. But don't delve into - ;; detailed node listings. - (Info-goto-node (Info-extract-menu-counting nil t)) - ;; If the last node in the menu is not last in pointer structure, - ;; move forward (but not down- or upward - see bug#1116) until we - ;; can't go any farther. - (while (Info-forward-node t t t) nil) - ;; Then keep moving down to last subnode, unless we reach an index. - (while (and (not (Info-index-node)) - (save-excursion (search-forward "\n* Menu:" nil t))) - (Info-goto-node (Info-extract-menu-counting nil))))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (Info-goto-node "Top") + (let ((Info-history nil) + (case-fold-search t)) + ;; Go to the last node in the menu of Top. But don't delve into + ;; detailed node listings. + (Info-goto-node (Info-extract-menu-counting nil t)) + ;; If the last node in the menu is not last in pointer structure, + ;; move forward (but not down- or upward - see bug#1116) until we + ;; can't go any farther. + (while (Info-forward-node t t t) nil) + ;; Then keep moving down to last subnode, unless we reach an index. + (while (and (not (Info-index-node)) + (save-excursion (search-forward "\n* Menu:" nil t))) + (Info-goto-node (Info-extract-menu-counting nil))))))) (defun Info-forward-node (&optional not-down not-up no-error) "Go forward one node, considering all nodes as forming one sequence. @@ -2905,66 +2975,76 @@ Info-forward-node NOT-UP non-nil means don't go to parent nodes, and NO-ERROR non-nil means don't signal a user-error if there's no node to go to." (interactive nil Info-mode) - (goto-char (point-min)) - (forward-line 1) - (let ((case-fold-search t)) - ;; three possibilities, in order of priority: - ;; 1. next node is in a menu in this node (but not in an index) - ;; 2. next node is next at same level - ;; 3. next node is up and next - (cond ((and (not not-down) - (save-excursion (search-forward "\n* menu:" nil t)) - (not (Info-index-node))) - (Info-goto-node (Info-extract-menu-counting 1)) - t) - ((save-excursion (search-backward "next:" nil t)) - (Info-next) - t) - ((and (not not-up) - (save-excursion (search-backward "up:" nil t)) - ;; Use string-equal, not equal, to ignore text props. - (not (string-equal (downcase (Info-extract-pointer "up")) - "top"))) - (let ((old-node Info-current-node)) - (Info-up) - (let ((old-history Info-history) - success) - (unwind-protect - (setq success (Info-forward-node t nil no-error)) - (or success (Info-goto-node old-node))) - (if Info-history-skip-intermediate-nodes - (setq Info-history old-history))))) - (no-error nil) - (t (user-error "No pointer forward from this node"))))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (goto-char (point-min)) + (forward-line 1) + (let ((case-fold-search t)) + ;; three possibilities, in order of priority: + ;; 1. next node is in a menu in this node (but not in an index) + ;; 2. next node is next at same level + ;; 3. next node is up and next + (cond ((and (not not-down) + (save-excursion (search-forward "\n* menu:" nil t)) + (not (Info-index-node))) + (Info-goto-node (Info-extract-menu-counting 1)) + t) + ((save-excursion (search-backward "next:" nil t)) + (Info-next) + t) + ((and (not not-up) + (save-excursion (search-backward "up:" nil t)) + ;; Use string-equal, not equal, to ignore text props. + (not (string-equal (downcase (Info-extract-pointer "up")) + "top"))) + (let ((old-node Info-current-node)) + (Info-up) + (let ((old-history Info-history) + success) + (unwind-protect + (setq success (Info-forward-node t nil no-error)) + (or success (Info-goto-node old-node))) + (if Info-history-skip-intermediate-nodes + (setq Info-history old-history))))) + (no-error nil) + (t (user-error "No pointer forward from this node"))))))) (defun Info-backward-node () "Go backward one node, considering all nodes as forming one sequence. If the current node has a \"previous\" node, go to it, descending into its last sub-node, if any; otherwise go \"up\" to the parent node." (interactive nil Info-mode) - (let ((prevnode (Info-extract-pointer "prev[ious]*" t)) - (upnode (Info-extract-pointer "up" t)) - (case-fold-search t)) - (cond ((and upnode (string-search "(" upnode)) - (user-error "First node in file")) - ((and upnode (or (null prevnode) - ;; Use string-equal, not equal, - ;; to ignore text properties. - (string-equal (downcase prevnode) - (downcase upnode)))) - (Info-up)) - (prevnode - ;; If we move back at the same level, - ;; go down to find the last subnode*. - (Info-prev) - (let ((old-history Info-history)) - (while (and (not (Info-index-node)) - (save-excursion (search-forward "\n* Menu:" nil t))) - (Info-goto-node (Info-extract-menu-counting nil))) - (if Info-history-skip-intermediate-nodes - (setq Info-history old-history)))) - (t - (user-error "No pointer backward from this node"))))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (let ((prevnode (Info-extract-pointer "prev[ious]*" t)) + (upnode (Info-extract-pointer "up" t)) + (case-fold-search t)) + (cond ((and upnode (string-search "(" upnode)) + (user-error "First node in file")) + ((and upnode (or (null prevnode) + ;; Use string-equal, not equal, + ;; to ignore text properties. + (string-equal (downcase prevnode) + (downcase upnode)))) + (Info-up)) + (prevnode + ;; If we move back at the same level, + ;; go down to find the last subnode*. + (Info-prev) + (let ((old-history Info-history)) + (while (and (not (Info-index-node)) + (save-excursion (search-forward "\n* Menu:" nil t))) + (Info-goto-node (Info-extract-menu-counting nil))) + (if Info-history-skip-intermediate-nodes + (setq Info-history old-history)))) + (t + (user-error "No pointer backward from this node"))) + ;; Emacs sometimes display just the very last line + (goto-char (point-min)))))) (define-obsolete-function-alias 'Info-exit #'quit-window "27.1") @@ -2972,31 +3052,39 @@ Info-next-menu-item "Go to the node of the next menu item." (interactive nil Info-mode) ;; Bind this in case the user sets it to nil. - (let* ((case-fold-search t) - (node - (save-excursion - (forward-line -1) - (search-forward "\n* menu:" nil t) - (and (search-forward "\n* " nil t) - (Info-extract-menu-node-name))))) - (if node (Info-goto-node node) - (user-error "No more items in menu")))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (let* ((case-fold-search t) + (node + (save-excursion + (forward-line -1) + (search-forward "\n* menu:" nil t) + (and (search-forward "\n* " nil t) + (Info-extract-menu-node-name))))) + (if node (Info-goto-node node) + (user-error "No more items in menu")))))) (defun Info-last-menu-item () "Go to the node of the previous menu item." (interactive nil Info-mode) - (save-excursion - (forward-line 1) - ;; Bind this in case the user sets it to nil. - (let* ((case-fold-search t) - (beg (save-excursion - (and (search-backward "\n* menu:" nil t) - (point))))) - (or (and beg (search-backward "\n* " beg t)) - (user-error "No previous items in menu"))) - (Info-goto-node (save-excursion - (goto-char (match-end 0)) - (Info-extract-menu-node-name))))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (save-excursion + (forward-line 1) + ;; Bind this in case the user sets it to nil. + (let* ((case-fold-search t) + (beg (save-excursion + (and (search-backward "\n* menu:" nil t) + (point))))) + (or (and beg (search-backward "\n* " beg t)) + (user-error "No previous items in menu"))) + (Info-goto-node (save-excursion + (goto-char (match-end 0)) + (Info-extract-menu-node-name))))))) (defmacro Info-no-error (&rest body) `(condition-case nil (progn ,@body t) (error nil))) @@ -3004,61 +3092,69 @@ Info-no-error (defun Info-next-preorder () "Go to the next subnode or the next node, or go up a level." (interactive nil Info-mode) - (cond ((Info-no-error (Info-next-menu-item))) - ((Info-no-error (Info-next))) - ((Info-no-error (Info-up t)) - ;; Since we have already gone thru all the items in this menu, - ;; go up to the end of this node. - (goto-char (point-max)) - ;; Since logically we are done with the node with that menu, - ;; move on from it. But don't add intermediate nodes - ;; to the history on recursive calls. - (let ((old-history Info-history)) - (Info-next-preorder) - (if Info-history-skip-intermediate-nodes - (setq Info-history old-history)))) - (t - (user-error "No more nodes")))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (cond ((Info-no-error (Info-next-menu-item))) + ((Info-no-error (Info-next))) + ((Info-no-error (Info-up t)) + ;; Since we have already gone thru all the items in this menu, + ;; go up to the end of this node. + (goto-char (point-max)) + ;; Since logically we are done with the node with that menu, + ;; move on from it. But don't add intermediate nodes + ;; to the history on recursive calls. + (let ((old-history Info-history)) + (Info-next-preorder) + (if Info-history-skip-intermediate-nodes + (setq Info-history old-history)))) + (t + (user-error "No more nodes")))))) (defun Info-last-preorder () "Go to the last node, popping up a level if there is none." (interactive nil Info-mode) - (cond ((and Info-scroll-prefer-subnodes - (Info-no-error - (Info-last-menu-item) - ;; If we go down a menu item, go to the end of the node - ;; so we can scroll back through it. - (goto-char (point-max)))) - ;; Keep going down, as long as there are nested menu nodes. - (let ((old-history Info-history)) - (while (Info-no-error - (Info-last-menu-item) - ;; If we go down a menu item, go to the end of the node - ;; so we can scroll back through it. - (goto-char (point-max)))) - (if Info-history-skip-intermediate-nodes - (setq Info-history old-history))) - (recenter -1)) - ((and (Info-no-error (Info-extract-pointer "prev")) - (not (equal (Info-extract-pointer "up") - (Info-extract-pointer "prev")))) - (Info-no-error (Info-prev)) - (goto-char (point-max)) - (let ((old-history Info-history)) - (while (Info-no-error - (Info-last-menu-item) - ;; If we go down a menu item, go to the end of the node - ;; so we can scroll back through it. - (goto-char (point-max)))) - (if Info-history-skip-intermediate-nodes - (setq Info-history old-history))) - (recenter -1)) - ((Info-no-error (Info-up t)) - (goto-char (point-min)) - (let ((case-fold-search t)) - (or (search-forward "\n* Menu:" nil t) - (goto-char (point-max))))) - (t (user-error "No previous nodes")))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (cond ((and Info-scroll-prefer-subnodes + (Info-no-error + (Info-last-menu-item) + ;; If we go down a menu item, go to the end of the node + ;; so we can scroll back through it. + (goto-char (point-max)))) + ;; Keep going down, as long as there are nested menu nodes. + (let ((old-history Info-history)) + (while (Info-no-error + (Info-last-menu-item) + ;; If we go down a menu item, go to the end of the node + ;; so we can scroll back through it. + (goto-char (point-max)))) + (if Info-history-skip-intermediate-nodes + (setq Info-history old-history))) + (recenter -1)) + ((and (Info-no-error (Info-extract-pointer "prev")) + (not (equal (Info-extract-pointer "up") + (Info-extract-pointer "prev")))) + (Info-no-error (Info-prev)) + (goto-char (point-max)) + (let ((old-history Info-history)) + (while (Info-no-error + (Info-last-menu-item) + ;; If we go down a menu item, go to the end of the node + ;; so we can scroll back through it. + (goto-char (point-max)))) + (if Info-history-skip-intermediate-nodes + (setq Info-history old-history))) + (recenter -1)) + ((Info-no-error (Info-up t)) + (goto-char (point-min)) + (let ((case-fold-search t)) + (or (search-forward "\n* Menu:" nil t) + (goto-char (point-max))))) + (t (user-error "No previous nodes")))))) (defun Info-scroll-up () "Scroll one screenful forward in Info, considering all nodes as one sequence. @@ -3075,23 +3171,27 @@ Info-scroll-up in other ways.)" (interactive nil Info-mode) - (if (or (< (window-start) (point-min)) - (> (window-start) (point-max))) - (set-window-start (selected-window) (point))) - (let* ((case-fold-search t) - (virtual-end (save-excursion - (goto-char (point-min)) - (if (and Info-scroll-prefer-subnodes - (search-forward "\n* Menu:" nil t)) - (point) - (point-max))))) - (if (or (< virtual-end (window-start)) - (pos-visible-in-window-p virtual-end)) - (cond - (Info-scroll-prefer-subnodes (Info-next-preorder)) - ((Info-no-error (Info-goto-node (Info-extract-menu-counting 1)))) - (t (Info-next-preorder))) - (scroll-up)))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (if (or (< (window-start) (point-min)) + (> (window-start) (point-max))) + (set-window-start (selected-window) (point))) + (let* ((case-fold-search t) + (virtual-end (save-excursion + (goto-char (point-min)) + (if (and Info-scroll-prefer-subnodes + (search-forward "\n* Menu:" nil t)) + (point) + (point-max))))) + (if (or (< virtual-end (window-start)) + (pos-visible-in-window-p virtual-end)) + (cond + (Info-scroll-prefer-subnodes (Info-next-preorder)) + ((Info-no-error (Info-goto-node (Info-extract-menu-counting 1)))) + (t (Info-next-preorder))) + (scroll-up)))))) (defun Info-mouse-scroll-up (e) "Scroll one screenful forward in Info, using the mouse. @@ -3109,21 +3209,25 @@ Info-scroll-down beginning of a node, that goes to the previous node or back up to the parent node." (interactive nil Info-mode) - (if (or (< (window-start) (point-min)) - (> (window-start) (point-max))) - (set-window-start (selected-window) (point))) - (let* ((case-fold-search t) - (current-point (point)) - (virtual-end - (and Info-scroll-prefer-subnodes - (save-excursion - (setq current-point (line-beginning-position)) - (goto-char (point-min)) - (search-forward "\n* Menu:" current-point t))))) - (if (or virtual-end - (pos-visible-in-window-p (point-min) nil t)) - (Info-last-preorder) - (scroll-down)))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (if (or (< (window-start) (point-min)) + (> (window-start) (point-max))) + (set-window-start (selected-window) (point))) + (let* ((case-fold-search t) + (current-point (point)) + (virtual-end + (and Info-scroll-prefer-subnodes + (save-excursion + (setq current-point (line-beginning-position)) + (goto-char (point-min)) + (search-forward "\n* Menu:" current-point t))))) + (if (or virtual-end + (pos-visible-in-window-p (point-min) nil t)) + (Info-last-preorder) + (scroll-down)))))) (defun Info-mouse-scroll-down (e) "Scroll one screenful backward in Info, using the mouse. @@ -3175,55 +3279,63 @@ Info-next-reference If COUNT is non-nil (interactively with a prefix arg), jump over COUNT cross-references." (interactive "i\np" Info-mode) - (unless count - (setq count 1)) - (if (< count 0) - (Info-prev-reference recur (- count)) - (while (unless (zerop count) (setq count (1- count))) - (let ((pat "\\*note[ \n\t]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://") - (old-pt (point)) - (case-fold-search t)) - (or (eobp) (forward-char 1)) - (or (Info-next-reference-or-link pat 'link) - (progn - (goto-char (point-min)) - (or (Info-next-reference-or-link pat 'link) - (progn - (goto-char old-pt) - (user-error "No cross references in this node"))))) - (if (looking-at "\\* Menu:") - (if recur - (user-error "No cross references in this node") - (Info-next-reference t)) - (if (looking-at "^\\* ") - (forward-char 2))))))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (unless count + (setq count 1)) + (if (< count 0) + (Info-prev-reference recur (- count)) + (while (unless (zerop count) (setq count (1- count))) + (let ((pat "\\*note[ \n\t]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://") + (old-pt (point)) + (case-fold-search t)) + (or (eobp) (forward-char 1)) + (or (Info-next-reference-or-link pat 'link) + (progn + (goto-char (point-min)) + (or (Info-next-reference-or-link pat 'link) + (progn + (goto-char old-pt) + (user-error "No cross references in this node"))))) + (if (looking-at "\\* Menu:") + (if recur + (user-error "No cross references in this node") + (Info-next-reference t)) + (if (looking-at "^\\* ") + (forward-char 2))))))))) (defun Info-prev-reference (&optional recur count) "Move cursor to the previous cross-reference or menu item in the node. If COUNT is non-nil (interactively with a prefix arg), jump over COUNT cross-references." (interactive "i\np" Info-mode) - (unless count - (setq count 1)) - (if (< count 0) - (Info-next-reference recur (- count)) - (while (unless (zerop count) (setq count (1- count))) - (let ((pat "\\*note[ \n\t]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://") - (old-pt (point)) - (case-fold-search t)) - (or (Info-prev-reference-or-link pat 'link) - (progn - (goto-char (point-max)) - (or (Info-prev-reference-or-link pat 'link) - (progn - (goto-char old-pt) - (user-error "No cross references in this node"))))) - (if (looking-at "\\* Menu:") - (if recur - (user-error "No cross references in this node") - (Info-prev-reference t)) - (if (looking-at "^\\* ") - (forward-char 2))))))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (unless count + (setq count 1)) + (if (< count 0) + (Info-next-reference recur (- count)) + (while (unless (zerop count) (setq count (1- count))) + (let ((pat "\\*note[ \n\t]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://") + (old-pt (point)) + (case-fold-search t)) + (or (Info-prev-reference-or-link pat 'link) + (progn + (goto-char (point-max)) + (or (Info-prev-reference-or-link pat 'link) + (progn + (goto-char old-pt) + (user-error "No cross references in this node"))))) + (if (looking-at "\\* Menu:") + (if recur + (user-error "No cross references in this node") + (Info-prev-reference t)) + (if (looking-at "^\\* ") + (forward-char 2))))))))) (defun Info-index-nodes (&optional file) "Return a list of names of all index nodes in Info FILE. @@ -3345,64 +3457,71 @@ Info-index Give an empty topic name to go to the Index node itself." (interactive (list - (let ((completion-ignore-case t) - (Info-complete-menu-buffer (clone-buffer)) - (Info-complete-nodes (Info-index-nodes)) - (Info-history-list nil)) - (info--ensure-not-in-directory-node) - (unwind-protect - (with-current-buffer Info-complete-menu-buffer - (Info-goto-index) - (completing-read "Index topic: " #'Info-complete-menu-item)) - (kill-buffer Info-complete-menu-buffer))))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (if (not (eq (selected-window) info-window)) + (Info-jump) + (setq Info-jump nil)) + (let ((completion-ignore-case t) + (Info-complete-menu-buffer (clone-buffer)) + (Info-complete-nodes (Info-index-nodes)) + (Info-history-list nil)) + (info--ensure-not-in-directory-node) + (unwind-protect + (with-current-buffer Info-complete-menu-buffer + (Info-goto-index) + (completing-read "Index topic: " #'Info-complete-menu-item)) + (kill-buffer Info-complete-menu-buffer)))))) (info--ensure-not-in-directory-node) ;; Strip leading colon in topic; index format does not allow them. (if (and (stringp topic) - (> (length topic) 0) - (= (aref topic 0) ?:)) + (> (length topic) 0) + (= (aref topic 0) ?:)) (setq topic (substring topic 1))) (let ((orignode Info-current-node) - (pattern (format "\n\\* +\\([^\n]*\\(%s\\)[^\n]*\\):[ \t]+\\([^\n]*\\)\\.\\(?:[ \t\n]*(line +\\([0-9]+\\))\\)?" - (regexp-quote topic))) - node (nodes (Info-index-nodes)) - (ohist-list Info-history-list) - (case-fold-search t)) + (pattern (format "\n\\* +\\([^\n]*\\(%s\\)[^\n]*\\):[ \t]+\\([^\n]*\\)\\.\\(?:[ \t\n]*(line +\\([0-9]+\\))\\)?" + (regexp-quote topic))) + node (nodes (Info-index-nodes)) + (ohist-list Info-history-list) + (case-fold-search t)) (Info-goto-index) (or (equal topic "") - (let ((matches nil) - (exact nil) - ;; We bind Info-history to nil for internal node-switches so - ;; that we don't put junk in the history. In the first - ;; Info-goto-index call, above, we do update the history - ;; because that is what the user's previous node choice into it. - (Info-history nil) - found) - (while - (progn - (goto-char (point-min)) - (while (re-search-forward pattern nil t) - (let ((entry (match-string-no-properties 1)) - (nodename (match-string-no-properties 3)) - (line (string-to-number (concat "0" (match-string 4))))) - (add-text-properties - (- (match-beginning 2) (match-beginning 1)) - (- (match-end 2) (match-beginning 1)) - '(face info-index-match) entry) - (push (list entry nodename Info-current-node line) matches))) - (setq nodes (cdr nodes) node (car nodes))) - (Info-goto-node node)) - (or matches - (progn - (Info-goto-node orignode) - (user-error "No `%s' in index" topic))) - ;; Here it is a feature that assoc is case-sensitive. - (while (setq found (assoc topic matches)) - (setq exact (cons found exact) - matches (delq found matches))) + (let ((matches nil) + (exact nil) + ;; We bind Info-history to nil for internal node-switches so + ;; that we don't put junk in the history. In the first + ;; Info-goto-index call, above, we do update the history + ;; because that is what the user's previous node choice into it. + (Info-history nil) + found) + (while + (progn + (goto-char (point-min)) + (while (re-search-forward pattern nil t) + (let ((entry (match-string-no-properties 1)) + (nodename (match-string-no-properties 3)) + (line (string-to-number (concat "0" (match-string 4))))) + (add-text-properties + (- (match-beginning 2) (match-beginning 1)) + (- (match-end 2) (match-beginning 1)) + '(face info-index-match) entry) + (push (list entry nodename Info-current-node line) matches))) + (setq nodes (cdr nodes) node (car nodes))) + (Info-goto-node node)) + (or matches + (progn + (Info-goto-node orignode) + (user-error "No `%s' in index" topic))) + ;; Here it is a feature that assoc is case-sensitive. + (while (setq found (assoc topic matches)) + (setq exact (cons found exact) + matches (delq found matches))) (setq Info-history-list ohist-list) - (setq Info-index-alternatives (nconc exact (nreverse matches)) + (setq Info-index-alternatives (nconc exact (nreverse matches)) Info--current-index-alternative 0) - (Info-index-next 0))))) + (Info-index-next 0)))) + (when Info-jump (select-window Info-jump))) (defun Info-index-next (num) "Go to the next matching index item from the last \\\\[Info-index] command. @@ -3411,45 +3530,49 @@ Info-index-next Also see the `Info-warn-on-index-alternatives-wrap' user option." (interactive "p" Info-mode) - (unless Info-index-alternatives - (user-error "No previous `i' command")) - (let ((index (+ Info--current-index-alternative num)) - (total (length Info-index-alternatives)) - (next-key (key-description (where-is-internal - 'Info-index-next overriding-local-map t)))) - (if (and Info-warn-on-index-alternatives-wrap - (> total 1) - (cond - ((< index 0) - (setq Info--current-index-alternative (- total 2)) - (message - "No previous matches, use `%s' to continue from end of list" - next-key) - t) - ((>= index total) - (setq Info--current-index-alternative -1) - (message - "No previous matches, use `%s' to continue from start of list" - next-key) - t))) - () ; Do nothing - (setq index (mod index total) - Info--current-index-alternative index) - (let ((entry (nth index Info-index-alternatives))) - (Info-goto-node (nth 1 entry)) - (if (> (nth 3 entry) 0) - ;; Forward 2 lines less because `Info-find-node-2' initially - ;; puts point to the 2nd line. - (forward-line (- (nth 3 entry) 2)) - (forward-line 3) ; don't search in headers - (Info-find-index-name (car entry))) - (message "Found `%s' in %s. %s" - (car entry) - (nth 2 entry) - (if (> total 1) - (format-message - "(%s total; use `%s' for next)" total next-key) - "(Only match)")))))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (unless Info-index-alternatives + (user-error "No previous `i' command")) + (let ((index (+ Info--current-index-alternative num)) + (total (length Info-index-alternatives)) + (next-key (key-description (where-is-internal + 'Info-index-next overriding-local-map t)))) + (if (and Info-warn-on-index-alternatives-wrap + (> total 1) + (cond + ((< index 0) + (setq Info--current-index-alternative (- total 2)) + (message + "No previous matches, use `%s' to continue from end of list" + next-key) + t) + ((>= index total) + (setq Info--current-index-alternative -1) + (message + "No previous matches, use `%s' to continue from start of list" + next-key) + t))) + () ; Do nothing + (setq index (mod index total) + Info--current-index-alternative index) + (let ((entry (nth index Info-index-alternatives))) + (Info-goto-node (nth 1 entry)) + (if (> (nth 3 entry) 0) + ;; Forward 2 lines less because `Info-find-node-2' initially + ;; puts point to the 2nd line. + (forward-line (- (nth 3 entry) 2)) + (forward-line 3) ; don't search in headers + (Info-find-index-name (car entry))) + (message "Found `%s' in %s. %s" + (car entry) + (nth 2 entry) + (if (> total 1) + (format-message + "(%s total; use `%s' for next)" total next-key) + "(Only match)")))))))) (defun Info-find-index-name (name) "Move point to the place within the current node where NAME is defined." @@ -3534,32 +3657,39 @@ Info-virtual-index ;; `interactive' is a copy from `Info-index' (interactive (list - (let ((completion-ignore-case t) - (Info-complete-menu-buffer (clone-buffer)) - (Info-complete-nodes (Info-index-nodes)) - (Info-history-list nil)) - (info--ensure-not-in-directory-node) - (unwind-protect - (with-current-buffer Info-complete-menu-buffer - (Info-goto-index) - (completing-read "Index topic: " #'Info-complete-menu-item)) - (kill-buffer Info-complete-menu-buffer)))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (if (not (eq (selected-window) info-window)) + (Info-jump) + (setq Info-jump nil)) + (let ((completion-ignore-case t) + (Info-complete-menu-buffer (clone-buffer)) + (Info-complete-nodes (Info-index-nodes)) + (Info-history-list nil)) + (info--ensure-not-in-directory-node) + (unwind-protect + (with-current-buffer Info-complete-menu-buffer + (Info-goto-index) + (completing-read "Index topic: " #'Info-complete-menu-item)) + (kill-buffer Info-complete-menu-buffer))))) Info-mode) (if (equal topic "") (Info-find-node Info-current-file "*Index*") (unless (assoc (cons Info-current-file topic) Info-virtual-index-nodes) (let ((orignode Info-current-node) - (ohist-list Info-history-list)) - ;; Reuse `Info-index' to set `Info-index-alternatives'. - (Info-index topic) - (push (cons (cons Info-current-file topic) Info-index-alternatives) - Info-virtual-index-nodes) - ;; Clean up unnecessary side-effects of `Info-index'. - (setq Info-history-list ohist-list) - (Info-goto-node orignode) - (message ""))) + (ohist-list Info-history-list)) + ;; Reuse `Info-index' to set `Info-index-alternatives'. + (Info-index topic) + (push (cons (cons Info-current-file topic) Info-index-alternatives) + Info-virtual-index-nodes) + ;; Clean up unnecessary side-effects of `Info-index'. + (setq Info-history-list ohist-list) + (Info-goto-node orignode) + (message ""))) (Info-find-node Info-current-file - (format "*Index for ‘%s’*" topic)))) + (format "*Index for ‘%s’*" topic))) + (when Info-jump (Info-jump))) (add-to-list 'Info-virtual-files '("\\`\\*Apropos\\*\\'" @@ -3696,18 +3826,22 @@ info-apropos Display a menu of the possible matches." (interactive "sIndex apropos: \nP") - (if (equal string "") - (Info-find-node Info-apropos-file "Top") - (let ((nodes Info-apropos-nodes) - nodename) - (while (and nodes (not (equal string (nth 1 (car nodes))))) - (setq nodes (cdr nodes))) - (if nodes - (Info-find-node Info-apropos-file (car (car nodes)) nil nil t) - (setq nodename (format "Index for ‘%s’" string)) - (push (list nodename string (Info-apropos-matches string regexp)) - Info-apropos-nodes) - (Info-find-node Info-apropos-file nodename))))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (if (equal string "") + (Info-find-node Info-apropos-file "Top") + (let ((nodes Info-apropos-nodes) + nodename) + (while (and nodes (not (equal string (nth 1 (car nodes))))) + (setq nodes (cdr nodes))) + (if nodes + (Info-find-node Info-apropos-file (car (car nodes)) nil nil t) + (setq nodename (format "Index for ‘%s’" string)) + (push (list nodename string (Info-apropos-matches string regexp)) + Info-apropos-nodes) + (Info-find-node Info-apropos-file nodename))))))) (add-to-list 'Info-virtual-files '("\\`\\*Finder.*\\*\\'" @@ -3842,17 +3976,29 @@ info-finder (interactive (when current-prefix-arg (require 'finder) - (list - (completing-read-multiple - "Keywords (separated by comma): " - (mapcar #'symbol-name (mapcar #'car (append finder-known-keywords - (finder-unknown-keywords)))) - nil t)))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (if (not (eq (selected-window) info-window)) + (Info-jump) + (setq Info-jump nil)) + (list + (completing-read-multiple + "Keywords (separated by comma): " + (mapcar #'symbol-name (mapcar #'car (append finder-known-keywords + (finder-unknown-keywords)))) + nil t))))) (require 'finder) - (if keywords - (Info-find-node Info-finder-file (mapconcat 'identity keywords ", ")) - (Info-find-node Info-finder-file "Top"))) - + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (if (not (eq (selected-window) info-window)) + (Info-jump) + (setq Info-jump nil)) + (if keywords + (Info-find-node Info-finder-file (mapconcat 'identity keywords ", ")) + (Info-find-node Info-finder-file "Top"))) + (when Info-jump (Info-jump))) (defun Info-undefined () "Make command be undefined in Info." @@ -3871,22 +4017,26 @@ Info-help (defun Info-summary () "Display a brief summary of all Info commands." (interactive) - (save-window-excursion - (switch-to-buffer "*Help*") - (setq buffer-read-only nil) - (erase-buffer) - (insert (documentation 'Info-mode)) - (help-mode) - (goto-char (point-min)) - (let (ch flag) - (while (progn (setq flag (not (pos-visible-in-window-p (point-max)))) - (message (if flag "Type Space to see more" - "Type Space to return to Info")) - (if (not (eq ?\s (setq ch (read-event)))) - (progn (push ch unread-command-events) nil) - flag)) - (scroll-up))) - (bury-buffer "*Help*"))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (save-window-excursion + (switch-to-buffer "*Help*") + (setq buffer-read-only nil) + (erase-buffer) + (insert (documentation 'Info-mode)) + (help-mode) + (goto-char (point-min)) + (let (ch flag) + (while (progn (setq flag (not (pos-visible-in-window-p (point-max)))) + (message (if flag "Type Space to see more" + "Type Space to return to Info")) + (if (not (eq ?\s (setq ch (read-event)))) + (progn (push ch unread-command-events) nil) + flag)) + (scroll-up))) + (bury-buffer "*Help*"))))) (defun Info-get-token (pos start all &optional errorstring) "Return the token around POS. @@ -4038,7 +4188,7 @@ Info-mouse-follow-link (defvar Info-mode-map (let ((map (make-keymap))) (suppress-keymap map) - (define-key map "." 'beginning-of-buffer) + (define-key map "." 'Info-beginning-of-buffer) (define-key map " " 'Info-scroll-up) (define-key map [?\S-\ ] 'Info-scroll-down) (define-key map "\C-m" 'Info-follow-nearest-node) @@ -4060,10 +4210,10 @@ Info-mode-map (define-key map "[" 'Info-backward-node) (define-key map "<" 'Info-top-node) (define-key map ">" 'Info-final-node) - (define-key map "b" 'beginning-of-buffer) + (define-key map "b" 'Info-beginning-of-buffer) (put 'beginning-of-buffer :advertised-binding "b") (define-key map "d" 'Info-directory) - (define-key map "e" 'end-of-buffer) + (define-key map "e" 'Info-end-of-buffer) (define-key map "f" 'Info-follow-reference) (define-key map "g" 'Info-goto-node) (define-key map "G" 'Info-goto-node-web) @@ -4071,15 +4221,16 @@ Info-mode-map ;; This is for compatibility with standalone info (>~ version 5.2). ;; Though for some time, standalone info had H and h reversed. ;; See . - (define-key map "H" 'describe-mode) + (define-key map "H" 'Info-describe-mode) (define-key map "i" 'Info-index) + (define-key map "j" 'Info-jump) (define-key map "I" 'Info-virtual-index) (define-key map "l" 'Info-history-back) (define-key map "L" 'Info-history) (define-key map "m" 'Info-menu) (define-key map "n" 'Info-next) (define-key map "p" 'Info-prev) - (define-key map "q" 'quit-window) + (define-key map "q" 'Info-quit-window) (define-key map "r" 'Info-history-forward) (define-key map "s" 'Info-search) (define-key map "S" 'Info-search-case-sensitively) @@ -4104,6 +4255,21 @@ Info-mode-map map) "Keymap containing Info commands.") +;; with a little help from Helm +(defcustom Info-mode-prefix-key "C-h M-i" + "`Info-mode-map' prefix key for invocation from other buffers." + :version "30.1" + :type '(choice (string :tag "Key") (const :tag "no binding")) + :set (lambda (var key) + (when (and (boundp var) (symbol-value var)) + (define-key (current-global-map) + (read-kbd-macro (symbol-value var)) nil)) + (when key + (define-key (current-global-map) + (read-kbd-macro key) + Info-mode-map)) + (set var key)) + :group 'info) (defun Info-check-pointer (item) "Non-nil if ITEM is present in this node." @@ -4125,7 +4291,7 @@ Info-check-pointer :help "Go backward one node, considering all as a sequence"] ["Forward" Info-forward-node :help "Go forward one node, considering all as a sequence"] - ["Beginning" beginning-of-buffer + ["Beginning" Info-beginning-of-buffer :help "Go to beginning of this node"] ["Top" Info-top-node :help "Go to top node of file"] @@ -4323,20 +4489,24 @@ Info-copy-current-node-name The name of the Info file is prepended to the node name in parentheses. With a zero prefix arg, put the name inside a function call to `info'." (interactive "P" Info-mode) - (unless Info-current-node - (user-error "No current Info node")) - (let ((node (if (stringp Info-current-file) - (concat "(" (file-name-sans-extension - (file-name-nondirectory Info-current-file)) - ") " - Info-current-node)))) - (if (zerop (prefix-numeric-value arg)) - (setq node (concat "(info \"" node "\")"))) - (unless (stringp Info-current-file) - (setq node (format "(Info-find-node '%S '%S)" - Info-current-file Info-current-node))) - (kill-new node) - (message "%s" node))) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (unless Info-current-node + (user-error "No current Info node")) + (let ((node (if (stringp Info-current-file) + (concat "(" (file-name-sans-extension + (file-name-nondirectory Info-current-file)) + ") " + Info-current-node)))) + (if (zerop (prefix-numeric-value arg)) + (setq node (concat "(info \"" node "\")"))) + (unless (stringp Info-current-file) + (setq node (format "(Info-find-node '%S '%S)" + Info-current-file Info-current-node))) + (kill-new node) + (message "%s" node))))) ;; Info mode is suitable only for specially formatted data. @@ -5509,6 +5679,42 @@ info--manual-names (apply-partially #'Info-read-node-name-2 Info-directory-list (mapcar #'car Info-suffix-list)))))))) + +;; commands from special-mode wrapped to work on Info-mode only + +(defun Info-beginning-of-buffer () + "Move point to the beginning of *info* buffer." + (interactive) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (goto-char (point-min))))) + +(defun Info-end-of-buffer () + "Move point to the beginning of *info* buffer." + (interactive) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (goto-char (point-max))))) + +(defun Info-describe-mode () + "As `describe-mode' but for *Help* buffer only." + (interactive) + (let ((info-window (get-buffer "*info*"))) + (unless (window-live-p (get-buffer-window info-window)) + (user-error "There is no visible Info buffer.")) + (describe-mode info-window))) + +(defun Info-quit-window () + (interactive) + (let ((info-window (get-buffer-window "*info*"))) + (unless (window-live-p info-window) + (user-error "There is no visible Info buffer.")) + (with-selected-window info-window + (quit-window nil info-window)))) (provide 'info) -- 2.40.1