From 37d46e61a194c87293a2e4ed54495a9436bd5422 Mon Sep 17 00:00:00 2001 From: Jose A Ortega Ruiz Date: Tue, 27 Sep 2022 05:45:00 +0100 Subject: [PATCH] docview: imenu access to table of contents When mutool is available, use it to extract a table of contents of supported documents and make it available via imenu. * lisp/doc-view.el (doc-view-imenu-enabled): user option to disable imenu generation. * lisp/doc-view.el (doc-view--outline-rx): (doc-view--pdf-outline, doc-view--imenu-subtree, doc-view-imenu-index): functions implementing the imenu index generation via mutool. * lisp/doc-view.el (doc-view-imenu-setup, doc-view-mode): setup of the new functionality in doc-view mode. * lisp/imenu.el (imenu-submenus-on-top): (imenu--split-menu): new local variable to optionally inhibit grouping of entries with children at the top of imenu menus. * doc/emacs/misc.texi: documentation for the new functionality. --- doc/emacs/misc.texi | 7 +++++ lisp/doc-view.el | 71 ++++++++++++++++++++++++++++++++++++++++++++- lisp/imenu.el | 16 +++++++--- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi index 10b44028bb..f3a6e6c33b 100644 --- a/doc/emacs/misc.texi +++ b/doc/emacs/misc.texi @@ -584,6 +584,13 @@ DocView Navigation default size for DocView, customize the variable @code{doc-view-resolution}. +@vindex doc-view-imenu-enabled + When the @command{mutool} executable is available, DocView will use +to generate entries for an outline menu, making it accessible via the +imenu facility (@pxref{Imenu}). To disable this functionality even +when @command{mutool} can be found in your @code{exec-path}, customize +the variable @code{doc-view-imenu-enabled}. + @node DocView Searching @subsection DocView Searching diff --git a/lisp/doc-view.el b/lisp/doc-view.el index fbd1427946..fe772efcfc 100644 --- a/lisp/doc-view.el +++ b/lisp/doc-view.el @@ -214,6 +214,11 @@ doc-view-mupdf-use-svg :type 'boolean :version "29.1") +(defcustom doc-view-imenu-enabled (and (executable-find "mutool") t) + "Whether to generate an imenu outline when mutool is available." + :type 'boolean + :version "29.1") + (defcustom doc-view-svg-background "white" "Background color for svg images. See `doc-view-mupdf-use-svg'." @@ -1874,6 +1879,69 @@ doc-view-search-previous-match (y-or-n-p "No more matches before current page. Wrap to last match? ")) (doc-view-goto-page (caar (last doc-view--current-search-matches))))))) +;;;; Imenu support +(defconst doc-view--outline-rx + "[^\t]+\\(\t+\\)\"\\(.+\\)\"\t#\\(?:page=\\)?\\([0-9]+\\)") + +(defun doc-view--pdf-outline (&optional file-name) + "Return a describing the outline of FILE-NAME (or current if nil). + +Each element in the list contains information about a section's +title, nesting level and page number. The list is flat: its tree +structure is extracted by `doc-view--imenu-subtree'." + (let* ((outline nil) + (fn (or file-name (buffer-file-name))) + (fn (shell-quote-argument (expand-file-name fn)))) + (with-temp-buffer + (insert (shell-command-to-string (format "mutool show %s outline" fn))) + (goto-char (point-min)) + (while (re-search-forward doc-view--outline-rx nil t) + (push `((level . ,(length (match-string 1))) + (title . ,(match-string 2)) + (page . ,(string-to-number (match-string 3)))) + outline))) + (nreverse outline))) + +(defun doc-view--imenu-subtree (outline act) + "Construct a tree of imenu items for the given outline list and action. + +This auxliary function constructs recursively all the items for +the first node in the outline and all its siblings at the same +level. Returns that imenu alist together with any other pending outline +entries at an upper level." + (let ((level (alist-get 'level (car outline))) + (index nil)) + (while (and (car outline) (<= level (alist-get 'level (car outline)))) + (let-alist (car outline) + (let ((title (format "%s (%s)" .title .page))) + (if (> .level level) + (let ((sub (doc-view--imenu-subtree outline act)) + (fst (car index))) + (setq index (cdr index)) + (push (cons (car fst) (cons fst (car sub))) index) + (setq outline (cdr sub))) + (push `(,title 0 ,act ,.page) index) + (setq outline (cdr outline)))))) + (cons (nreverse index) outline))) + +(defun doc-view-imenu-index (&optional file-name goto-page-fn) + "Create an imenu index using mutool to extract its outline. + +For extensibility, a FILE-NAME other than the current buffer and +a jumping function, GOTO-PAGE-FN other than `doc-view-goto-page' +can be specified." + (let* ((goto (or goto-page-fn 'doc-view-goto-page)) + (act (lambda (_name _pos page) (funcall goto page)))) + (car (doc-view--imenu-subtree (doc-view--pdf-outline file-name) act)))) + +(defun doc-view-imenu-setup () + "Set up local state in the current buffer for imenu, if needed." + (when (and doc-view-imenu-enabled (executable-find "mutool")) + (setq-local imenu-create-index-function #'doc-view-imenu-index + imenu-submenus-on-top nil + imenu-sort-function nil) + (imenu-add-to-menubar "Outline"))) + ;;;; User interface commands and the mode (put 'doc-view-mode 'mode-class 'special) @@ -2047,7 +2115,7 @@ doc-view-mode "Major mode in DocView buffers. DocView Mode is an Emacs document viewer. It displays PDF, PS -and DVI files (as PNG images) in Emacs buffers. +and DVI files (as PNG or SVG images) in Emacs buffers. You can use \\\\[doc-view-toggle-display] to toggle between displaying the document or editing it as text. @@ -2142,6 +2210,7 @@ doc-view-mode (setq mode-name "DocView" buffer-read-only t major-mode 'doc-view-mode) + (doc-view-imenu-setup) (doc-view-initiate-display) ;; Switch off view-mode explicitly, because doc-view-mode is the ;; canonical view mode for PDF/PS/DVI files. This could be diff --git a/lisp/imenu.el b/lisp/imenu.el index c407f501d6..bfc2429100 100644 --- a/lisp/imenu.el +++ b/lisp/imenu.el @@ -208,6 +208,13 @@ imenu-create-index-function See `imenu--index-alist' for the format of the buffer index alist.") +;;;###autoload +(defvar-local imenu-submenus-on-top t + "Flag specifiying whether items with sublists should be kept at top. + +For some indexes, such as those describing sections in a document, it +makes sense to keep their original order even in the menubar.") + ;;;###autoload (defvar-local imenu-prev-index-position-function 'beginning-of-defun "Function for finding the next index position. @@ -373,10 +380,11 @@ imenu--split-menu (if (memq imenu--rescan-item menulist) (setq keep-at-top (list imenu--rescan-item) menulist (delq imenu--rescan-item menulist))) - (dolist (item menulist) - (when (imenu--subalist-p item) - (push item keep-at-top) - (setq menulist (delq item menulist)))) + (if imenu-submenus-on-top + (dolist (item menulist) + (when (imenu--subalist-p item) + (push item keep-at-top) + (setq menulist (delq item menulist))))) (if imenu-sort-function (setq menulist (sort menulist imenu-sort-function))) (if (> (length menulist) imenu-max-items) -- 2.37.2