dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

imenu-list.el (26349B)


      1 ;;; imenu-list.el --- Show imenu entries in a separate buffer
      2 
      3 ;; Copyright (C) 2015-2021 Bar Magal & Contributors
      4 
      5 ;; Author: Bar Magal (2015)
      6 ;; Version: 0.9
      7 ;; Homepage: https://github.com/bmag/imenu-list
      8 ;; Package-Requires: ((cl-lib "0.5"))
      9 
     10 ;; This file is not part of GNU Emacs.
     11 
     12 ;; This program is free software: you can redistribute it and/or modify
     13 ;; it under the terms of the GNU General Public License as published by
     14 ;; the Free Software Foundation, either version 3 of the License, or
     15 ;; (at your option) any later version.
     16 
     17 ;; This program is distributed in the hope that it will be useful,
     18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     20 ;; GNU General Public License for more details.
     21 
     22 ;; You should have received a copy of the GNU General Public License
     23 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
     24 
     25 ;;; Commentary:
     26 ;; Shows a list of imenu entries for the current buffer, in another
     27 ;; buffer with the name "*Ilist*".
     28 ;;
     29 ;; Activation and deactivation:
     30 ;; M-x imenu-list-minor-mode
     31 ;;
     32 ;; Key shortcuts from "*Ilist*" buffer:
     33 ;; <enter>: Go to current definition
     34 ;; <space>: display current definition
     35 ;; <tab>: expand/collapse subtree
     36 ;;
     37 ;; Change "*Ilist*" buffer's position and size:
     38 ;; `imenu-list-position', `imenu-list-size'.
     39 ;;
     40 ;; Should invoking `imenu-list-minor-mode' also select the "*Ilist*"
     41 ;; window?
     42 ;; `imenu-list-focus-after-activation'
     43 
     44 ;;; Code:
     45 
     46 (require 'imenu)
     47 (require 'cl-lib)
     48 
     49 (defconst imenu-list-buffer-name "*Ilist*"
     50   "Name of the buffer that is used to display imenu entries.")
     51 
     52 (defvar imenu-list--imenu-entries nil
     53   "A copy of the imenu entries of the buffer we want to display in the
     54 imenu-list buffer.")
     55 
     56 (defvar imenu-list--line-entries nil
     57   "List of imenu entries displayed in the imenu-list buffer.
     58 The first item in this list corresponds to the first line in the
     59 imenu-list buffer, the second item matches the second line, and so on.")
     60 
     61 (defvar imenu-list--displayed-buffer nil
     62   "The buffer who owns the saved imenu entries.")
     63 
     64 (defvar imenu-list--last-location nil
     65   "Location from which last `imenu-list-update' was done.
     66 Used to avoid updating if the point didn't move.")
     67 
     68 ;;; fancy display
     69 
     70 (defgroup imenu-list nil
     71   "Variables for `imenu-list' package."
     72   :group 'imenu)
     73 
     74 (defcustom imenu-list-mode-line-format
     75   '("%e" mode-line-front-space mode-line-mule-info mode-line-client
     76     mode-line-modified mode-line-remote mode-line-frame-identification
     77     (:propertize "%b" face mode-line-buffer-id) " "
     78     (:eval (buffer-name imenu-list--displayed-buffer)) " "
     79     mode-line-end-spaces)
     80   "Local mode-line format for the imenu-list buffer.
     81 This is the local value of `mode-line-format' to use in the imenu-list
     82 buffer.  See `mode-line-format' for allowed values."
     83   :group 'imenu-list)
     84 
     85 (defcustom imenu-list-focus-after-activation nil
     86   "Non-nil to select the imenu-list window automatically when
     87 `imenu-list-minor-mode' is activated."
     88   :group 'imenu-list
     89   :type 'boolean)
     90 
     91 (defcustom imenu-list-custom-position-translator nil
     92   "Custom translator of imenu positions to buffer positions.
     93 Imenu can be customized on a per-buffer basis not to use regular buffer
     94 positions as the positions that are stored in the imenu index.  In such
     95 cases, imenu-list needs to know how to translate imenu positions back to
     96 buffer positions.  `imenu-list-custom-position-translator' should be a
     97 function that returns a position-translator function suitable for the
     98 current buffer, or nil.  See `imenu-list-position-translator' for details."
     99   :group 'imenu-list
    100   :type 'function)
    101 
    102 (defface imenu-list-entry-face
    103   '((t))
    104   "Basic face for imenu-list entries in the imenu-list buffer."
    105   :group 'imenu-list)
    106 
    107 (defface imenu-list-entry-face-0
    108   '((((class color) (background light))
    109      :inherit imenu-list-entry-face
    110      :foreground "maroon")
    111     (((class color) (background dark))
    112      :inherit imenu-list-entry-face
    113      :foreground "gold"))
    114   "Face for outermost imenu-list entries (depth 0)."
    115   :group 'imenu-list)
    116 
    117 (defface imenu-list-entry-subalist-face-0
    118   '((t :inherit imenu-list-entry-face-0
    119        :weight bold :underline t))
    120   "Face for subalist entries with depth 0."
    121   :group 'imenu-list)
    122 
    123 (defface imenu-list-entry-face-1
    124   '((((class color) (background light))
    125      :inherit imenu-list-entry-face
    126      :foreground "dark green")
    127     (((class color) (background dark))
    128      :inherit imenu-list-entry-face
    129      :foreground "light green"))
    130   "Face for imenu-list entries with depth 1."
    131   :group 'imenu-list)
    132 
    133 (defface imenu-list-entry-subalist-face-1
    134   '((t :inherit imenu-list-entry-face-1
    135        :weight bold :underline t))
    136   "Face for subalist entries with depth 1."
    137   :group 'imenu-list)
    138 
    139 (defface imenu-list-entry-face-2
    140   '((((class color) (background light))
    141      :inherit imenu-list-entry-face
    142      :foreground "dark blue")
    143     (((class color) (background dark))
    144      :inherit imenu-list-entry-face
    145      :foreground "light blue"))
    146   "Face for imenu-list entries with depth 2."
    147   :group 'imenu-list)
    148 
    149 (defface imenu-list-entry-subalist-face-2
    150   '((t :inherit imenu-list-entry-face-2
    151        :weight bold :underline t))
    152   "Face for subalist entries with depth 2."
    153   :group 'imenu-list)
    154 
    155 (defface imenu-list-entry-face-3
    156   '((((class color) (background light))
    157      :inherit imenu-list-entry-face
    158      :foreground "orange red")
    159     (((class color) (background dark))
    160      :inherit imenu-list-entry-face
    161      :foreground "sandy brown"))
    162   "Face for imenu-list entries with depth 3."
    163   :group 'imenu-list)
    164 
    165 (defface imenu-list-entry-subalist-face-3
    166   '((t :inherit imenu-list-entry-face-3
    167        :weight bold :underline t))
    168   "Face for subalist entries with depth 0."
    169   :group 'imenu-list)
    170 
    171 (defun imenu-list--get-face (depth subalistp)
    172   "Get face for entry.
    173 DEPTH is the depth of the entry in the list.
    174 SUBALISTP non-nil means that there are more entries \"under\" the
    175 current entry (current entry is a \"father\")."
    176   (cl-case depth
    177     (0 (if subalistp 'imenu-list-entry-subalist-face-0 'imenu-list-entry-face-0))
    178     (1 (if subalistp 'imenu-list-entry-subalist-face-1 'imenu-list-entry-face-1))
    179     (2 (if subalistp 'imenu-list-entry-subalist-face-2 'imenu-list-entry-face-2))
    180     (3 (if subalistp 'imenu-list-entry-subalist-face-3 'imenu-list-entry-face-3))
    181     (t (if subalistp 'imenu-list-entry-subalist-face-3 'imenu-list-entry-face-3))))
    182 
    183 ;;; collect entries
    184 
    185 (defun imenu-list-rescan-imenu ()
    186   "Force imenu to rescan the current buffer."
    187   (setq imenu--index-alist nil)
    188   (imenu--make-index-alist))
    189 
    190 (defun imenu-list-collect-entries ()
    191   "Collect all `imenu' entries of the current buffer."
    192   (imenu-list-rescan-imenu)
    193   (setq imenu-list--imenu-entries imenu--index-alist)
    194   (setq imenu-list--displayed-buffer (current-buffer)))
    195 
    196 
    197 ;;; print entries
    198 
    199 (defun imenu-list--depth-string (depth)
    200   "Return a prefix string representing an entry's DEPTH."
    201   (let ((indents (cl-loop for i from 1 to depth collect "  ")))
    202     (format "%s%s"
    203             (mapconcat #'identity indents "")
    204             (if indents " " ""))))
    205 
    206 (defun imenu-list--action-goto-entry (event)
    207   "Goto the entry that was clicked.
    208 EVENT holds the data of what was clicked."
    209   (let ((window (posn-window (event-end event)))
    210         (pos (posn-point (event-end event)))
    211         (ilist-buffer (get-buffer imenu-list-buffer-name)))
    212     (when (and (windowp window)
    213                (eql (window-buffer window) ilist-buffer))
    214       (with-current-buffer ilist-buffer
    215         (goto-char pos)
    216         (imenu-list-goto-entry)))))
    217 
    218 (defun imenu-list--action-toggle-hs (event)
    219   "Toggle hide/show state of current block.
    220 EVENT holds the data of what was clicked.
    221 See `hs-minor-mode' for information on what is hide/show."
    222   (let ((window (posn-window (event-end event)))
    223         (pos (posn-point (event-end event)))
    224         (ilist-buffer (get-buffer imenu-list-buffer-name)))
    225     (when (and (windowp window)
    226                (eql (window-buffer window) ilist-buffer))
    227       (with-current-buffer ilist-buffer
    228         (goto-char pos)
    229         (hs-toggle-hiding)))))
    230 
    231 (defun imenu-list--insert-entry (entry depth)
    232   "Insert a line for ENTRY with DEPTH."
    233   (if (imenu--subalist-p entry)
    234       (progn
    235         (insert (imenu-list--depth-string depth))
    236         (insert-button (format "+ %s" (car entry))
    237                        'face (imenu-list--get-face depth t)
    238                        'help-echo (format "Toggle: %s"
    239                                           (car entry))
    240                        'follow-link t
    241                        'action ;; #'imenu-list--action-goto-entry
    242                        #'imenu-list--action-toggle-hs
    243                        )
    244         (insert "\n"))
    245     (insert (imenu-list--depth-string depth))
    246     (insert-button (format "%s" (car entry))
    247                    'face (imenu-list--get-face depth nil)
    248                    'help-echo (format "Go to: %s"
    249                                       (car entry))
    250                    'follow-link t
    251                    'action #'imenu-list--action-goto-entry)
    252     (insert "\n")))
    253 
    254 (defun imenu-list--insert-entries-internal (index-alist depth)
    255   "Insert all imenu entries in INDEX-ALIST into the current buffer.
    256 DEPTH is the depth of the code block were the entries are written.
    257 Each entry is inserted in its own line.
    258 Each entry is appended to `imenu-list--line-entries' as well."
    259   (dolist (entry index-alist)
    260     (setq imenu-list--line-entries (append imenu-list--line-entries (list entry)))
    261     (imenu-list--insert-entry entry depth)
    262     (when (imenu--subalist-p entry)
    263       (imenu-list--insert-entries-internal (cdr entry) (1+ depth)))))
    264 
    265 (defun imenu-list-insert-entries ()
    266   "Insert all imenu entries into the current buffer.
    267 The entries are taken from `imenu-list--imenu-entries'.
    268 Each entry is inserted in its own line.
    269 Each entry is appended to `imenu-list--line-entries' as well
    270  (`imenu-list--line-entries' is cleared in the beginning of this
    271 function)."
    272   (read-only-mode -1)
    273   (erase-buffer)
    274   (setq imenu-list--line-entries nil)
    275   (imenu-list--insert-entries-internal imenu-list--imenu-entries 0)
    276   (read-only-mode 1))
    277 
    278 
    279 ;;; goto entries
    280 
    281 (defcustom imenu-list-after-jump-hook '(recenter)
    282   "Hook to run after jumping to an entry from the imenu-list buffer.
    283 This hook is ran also when the focus remains on the imenu-list
    284 buffer, or in other words: this hook is ran by both
    285 `imenu-list-goto-entry' and `imenu-list-display-entry'."
    286   :group 'imenu-list
    287   :type 'hook)
    288 
    289 (defun imenu-list--find-entry ()
    290   "Find in `imenu-list--line-entries' the entry in the current line."
    291   (nth (1- (line-number-at-pos)) imenu-list--line-entries))
    292 
    293 (defun imenu-list-goto-entry ()
    294   "Switch to the original buffer and display the entry under point."
    295   (interactive)
    296   (let ((entry (imenu-list--find-entry)))
    297     (pop-to-buffer imenu-list--displayed-buffer)
    298     (imenu entry)
    299     (run-hooks 'imenu-list-after-jump-hook)
    300     (imenu-list--show-current-entry)))
    301 
    302 (defun imenu-list-display-entry ()
    303   "Display in original buffer the entry under point."
    304   (interactive)
    305   (let ((entry (imenu-list--find-entry)))
    306     (save-selected-window
    307       (pop-to-buffer imenu-list--displayed-buffer)
    308       (imenu entry)
    309       (run-hooks 'imenu-list-after-jump-hook)
    310       (imenu-list--show-current-entry))))
    311 
    312 (defalias 'imenu-list-<=
    313   (if (ignore-errors (<= 1 2 3))
    314       #'<=
    315     #'(lambda (x y z)
    316         "Return t if X <= Y and Y <= Z."
    317         (and (<= x y) (<= y z)))))
    318 
    319 (defun imenu-list--translate-eglot-position (pos)
    320   ;; when Eglot is in charge of Imenu, then the index is created by `eglot-imenu', with a fallback to
    321   ;; `imenu-default-create-index-function' when `eglot-imenu' returns nil. If POS is an array, it means
    322   ;; it was created by `eglot-imenu' and we need to extract its position. Otherwise, it was created by
    323   ;; `imenu-default-create-index-function' and we should return it as-is.
    324   (if (arrayp pos)
    325       (eglot--lsp-position-to-point (plist-get (plist-get (aref pos 0) :range) :start) t)
    326     pos))
    327 
    328 (defun imenu-list-position-translator ()
    329   "Get the correct position translator function for the current buffer.
    330 A position translator is a function that takes a position as described in
    331 `imenu--index-alist' and returns a number or marker that points to the
    332 real position in the buffer that the position parameter points to.
    333 This is necessary because positions in `imenu--index-alist' do not have to
    334 be numbers or markers, although usually they are.  For example,
    335 `semantic-create-imenu-index' uses overlays as position paramters.
    336 If `imenu-list-custom-position-translator' is non-nil, then
    337 `imenu-list-position-translator' asks it for a translator function.
    338 If `imenu-list-custom-position-translator' is called and returns nil, then
    339 continue with the regular logic to find a translator function."
    340   (cond
    341    ((and imenu-list-custom-position-translator
    342          (funcall imenu-list-custom-position-translator)))
    343    ((or (eq imenu-create-index-function 'semantic-create-imenu-index)
    344         (and (eq imenu-create-index-function
    345                  'spacemacs/python-imenu-create-index-python-or-semantic)
    346              (bound-and-true-p semantic-mode)))
    347     ;; semantic uses overlays, return overlay's start as position
    348     #'overlay-start)
    349    ((and (fboundp #'eglot-managed-p) (eglot-managed-p))
    350     #'imenu-list--translate-eglot-position)
    351    ;; default - return position as is
    352    (t #'identity)))
    353 
    354 (defun imenu-list--current-entry ()
    355   "Find entry in `imenu-list--line-entries' matching current position."
    356   (let ((point-pos (point-marker))
    357         (offset (point-min-marker))
    358         (get-pos-fn (imenu-list-position-translator))
    359         match-entry)
    360     (dolist (entry imenu-list--line-entries match-entry)
    361       ;; "special entry" is described in `imenu--index-alist'
    362       (unless (imenu--subalist-p entry)
    363         (let* ((is-special-entry (listp (cdr entry)))
    364                (entry-pos-raw (if is-special-entry
    365                                   (cadr entry)
    366                                 (cdr entry)))
    367                ;; sometimes imenu doesn't use numbers/markers as positions, so we
    368                ;; need to translate them back to "real" positions
    369                ;; (see https://github.com/bmag/imenu-list/issues/20)
    370                (entry-pos (funcall get-pos-fn entry-pos-raw)))
    371           (when (imenu-list-<= offset entry-pos point-pos)
    372             (setq offset entry-pos)
    373             (setq match-entry entry)))))))
    374 
    375 (defun imenu-list--show-current-entry ()
    376   "Move the imenu-list buffer's point to the current position's entry."
    377   (when (get-buffer-window (imenu-list-get-buffer-create))
    378     (let ((line-number (cl-position (imenu-list--current-entry)
    379                                     imenu-list--line-entries
    380                                     :test 'equal)))
    381       (with-selected-window (get-buffer-window (imenu-list-get-buffer-create))
    382         (goto-char (point-min))
    383         (forward-line line-number)
    384         (hl-line-mode 1)))))
    385 
    386 ;;; window display settings
    387 
    388 (defcustom imenu-list-size 0.3
    389   "Size (height or width) for the imenu-list buffer.
    390 Either a positive integer (number of rows/columns) or a percentage."
    391   :group 'imenu-list
    392   :type 'number)
    393 
    394 (defcustom imenu-list-position 'right
    395   "Position of the imenu-list buffer.
    396 Either 'right, 'left, 'above or 'below. This value is passed directly to
    397 `split-window'."
    398   :group 'imenu-list
    399   :type '(choice (const above)
    400                  (const below)
    401                  (const left)
    402                  (const right)))
    403 
    404 (defcustom imenu-list-auto-resize nil
    405   "If non-nil, auto-resize window after updating the imenu-list buffer.
    406 Resizing the width works only for emacs 24.4 and newer.  Resizing the
    407 height doesn't suffer that limitation."
    408   :group 'imenu-list
    409   :type 'boolean)
    410 
    411 (defcustom imenu-list-update-hook nil
    412   "Hook to run after updating the imenu-list buffer."
    413   :group 'imenu-list
    414   :type 'hook)
    415 
    416 (defun imenu-list-split-size ()
    417   "Convert `imenu-list-size' to proper argument for `split-window'."
    418   (let ((frame-size (if (member imenu-list-position '(left right))
    419                         (frame-width)
    420                       (frame-height))))
    421     (cond ((integerp imenu-list-size) (- imenu-list-size))
    422           (t (- (round (* frame-size imenu-list-size)))))))
    423 
    424 (defun imenu-list-display-buffer (buffer alist)
    425   "Display the imenu-list buffer at the side.
    426 This function should be used with `display-buffer-alist'.
    427 See `display-buffer-alist' for a description of BUFFER and ALIST."
    428   (or (get-buffer-window buffer)
    429       (let ((window (ignore-errors (split-window (frame-root-window) (imenu-list-split-size) imenu-list-position))))
    430         (when window
    431           ;; since Emacs 27.0.50, `window--display-buffer' doesn't take a
    432           ;; `dedicated' argument, so instead call `set-window-dedicated-p'
    433           ;; directly (works both on new and old Emacs versions)
    434           (window--display-buffer buffer window 'window alist)
    435           (set-window-dedicated-p window t)
    436           window))))
    437 
    438 (defun imenu-list-install-display-buffer ()
    439   "Install imenu-list display settings to `display-buffer-alist'."
    440   (cl-pushnew `(,(concat "^" (regexp-quote imenu-list-buffer-name) "$")
    441                 imenu-list-display-buffer)
    442               display-buffer-alist
    443               :test #'equal))
    444 
    445 (defun imenu-list-purpose-display-condition (_purpose buffer _alist)
    446   "Display condition for use with window-purpose.
    447 Return t if BUFFER is the imenu-list buffer.
    448 
    449 This function should be used in `purpose-special-action-sequences'.
    450 See `purpose-special-action-sequences' for a description of _PURPOSE,
    451 BUFFER and _ALIST."
    452   (string-equal (buffer-name buffer) imenu-list-buffer-name))
    453 
    454 (defun imenu-list-install-purpose-display ()
    455   "Install imenu-list display settings for window-purpose.
    456 Install entry for imenu-list in `purpose-special-action-sequences'."
    457   (cl-pushnew '(imenu-list-purpose-display-condition imenu-list-display-buffer)
    458               purpose-special-action-sequences
    459               :test #'equal))
    460 
    461 (imenu-list-install-display-buffer)
    462 (eval-after-load 'window-purpose
    463   '(imenu-list-install-purpose-display))
    464 
    465 
    466 ;;; define major mode
    467 
    468 (defun imenu-list-get-buffer-create ()
    469   "Return the imenu-list buffer.
    470 If it doesn't exist, create it."
    471   (or (get-buffer imenu-list-buffer-name)
    472       (let ((buffer (get-buffer-create imenu-list-buffer-name)))
    473         (with-current-buffer buffer
    474           (imenu-list-major-mode)
    475           buffer))))
    476 
    477 (defun imenu-list-resize-window ()
    478   (let ((fit-window-to-buffer-horizontally t))
    479     (mapc #'fit-window-to-buffer
    480           (get-buffer-window-list (imenu-list-get-buffer-create)))))
    481 
    482 (defun imenu-list-update (&optional raise-imenu-errors force-update)
    483   "Update the imenu-list buffer.
    484 If the imenu-list buffer doesn't exist, create it.
    485 If RAISE-IMENU-ERRORS is non-nil, any errors encountered while trying to
    486 create the index will be raised.  Otherwise, such errors will be printed
    487 instead.
    488 When RAISE-IMENU-ERRORS is nil, then the return value indicates if an
    489 error has occured.  If the return value is nil, then there was no error.
    490 Oherwise `imenu-list-update' will return the error that has occured, as
    491  (ERROR-SYMBOL . SIGNAL-DATA).
    492 If FORCE-UPDATE is non-nil, the imenu-list buffer is updated even if the
    493 imenu entries did not change since the last update."
    494   (catch 'index-failure
    495       (let ((old-entries imenu-list--imenu-entries)
    496             (location (point-marker)))
    497         ;; don't update if `point' didn't move - fixes issue #11
    498         (unless (and (null force-update)
    499                      imenu-list--last-location
    500                      (marker-buffer imenu-list--last-location)
    501                      (= location imenu-list--last-location))
    502           (setq imenu-list--last-location location)
    503           (if raise-imenu-errors
    504               (imenu-list-collect-entries)
    505             (condition-case err
    506                 (imenu-list-collect-entries)
    507               (error
    508                (message "imenu-list: couldn't create index because of error: %S" err)
    509                (throw 'index-failure err))))
    510           (when (or force-update
    511                     ;; check if Ilist buffer is alive, in case it was killed
    512                     ;; since last update
    513                     (null (get-buffer imenu-list-buffer-name))
    514                     (not (equal old-entries imenu-list--imenu-entries)))
    515             (with-current-buffer (imenu-list-get-buffer-create)
    516               (imenu-list-insert-entries)))
    517           (imenu-list--show-current-entry)
    518           (when imenu-list-auto-resize
    519             (imenu-list-resize-window))
    520           (run-hooks 'imenu-list-update-hook)
    521           nil))))
    522 
    523 (defun imenu-list-refresh ()
    524   "Refresh imenu-list buffer."
    525   (interactive)
    526   (with-current-buffer imenu-list--displayed-buffer
    527     (imenu-list-update nil t)))
    528 
    529 (defun imenu-list-show ()
    530   "Show the imenu-list buffer.
    531 If the imenu-list buffer doesn't exist, create it."
    532   (interactive)
    533   (pop-to-buffer imenu-list-buffer-name))
    534 
    535 (defun imenu-list-show-noselect ()
    536   "Show the imenu-list buffer, but don't select it.
    537 If the imenu-list buffer doesn't exist, create it."
    538   (interactive)
    539   (display-buffer imenu-list-buffer-name))
    540 
    541 ;;;###autoload
    542 (defun imenu-list-noselect ()
    543   "Update and show the imenu-list buffer, but don't select it.
    544 If the imenu-list buffer doesn't exist, create it."
    545   (interactive)
    546   (imenu-list-update)
    547   (imenu-list-show-noselect))
    548 
    549 ;;;###autoload
    550 (defun imenu-list ()
    551   "Update and show the imenu-list buffer.
    552 If the imenu-list buffer doesn't exist, create it."
    553   (interactive)
    554   (imenu-list-update)
    555   (imenu-list-show))
    556 
    557 (defun imenu-list-quit-window ()
    558   "Disable `imenu-list-minor-mode' and hide the imenu-list buffer.
    559 If `imenu-list-minor-mode' is already disabled, just call `quit-window'."
    560   (interactive)
    561   ;; the reason not to call `(imenu-list-minor-mode -1)' regardless of current
    562   ;; state, is that it quits all of imenu-list windows instead of just the
    563   ;; current one.
    564   (if imenu-list-minor-mode
    565       ;; disabling `imenu-list-minor-mode' also quits the window
    566       (imenu-list-minor-mode -1)
    567     (quit-window)))
    568 
    569 (defvar imenu-list-major-mode-map
    570   (let ((map (make-sparse-keymap)))
    571     (define-key map (kbd "RET") #'imenu-list-goto-entry)
    572     (define-key map (kbd "SPC") #'imenu-list-display-entry)
    573     (define-key map (kbd "n") #'next-line)
    574     (define-key map (kbd "p") #'previous-line)
    575     (define-key map (kbd "TAB") #'hs-toggle-hiding)
    576     (define-key map (kbd "f") #'hs-toggle-hiding)
    577     (define-key map (kbd "g") #'imenu-list-refresh)
    578     (define-key map (kbd "q") #'imenu-list-quit-window)
    579     map))
    580 
    581 (define-derived-mode imenu-list-major-mode special-mode "Ilist"
    582   "Major mode for showing the `imenu' entries of a buffer (an Ilist).
    583 \\{imenu-list-mode-map}"
    584   (read-only-mode 1)
    585   (imenu-list-install-hideshow))
    586 (add-hook 'imenu-list-major-mode-hook #'hs-minor-mode)
    587 
    588 (defun imenu-list--set-mode-line ()
    589   "Locally change `mode-line-format' to `imenu-list-mode-line-format'."
    590   (setq-local mode-line-format imenu-list-mode-line-format))
    591 (add-hook 'imenu-list-major-mode-hook #'imenu-list--set-mode-line)
    592 
    593 (defun imenu-list-install-hideshow ()
    594   "Install imenu-list settings for hideshow."
    595   ;; "\\b\\B" is a regexp that can't match anything
    596   (setq-local comment-start "\\b\\B")
    597   (setq-local comment-end "\\b\\B")
    598   (setq hs-special-modes-alist
    599         (cl-delete 'imenu-list-major-mode hs-special-modes-alist :key #'car))
    600   (push `(imenu-list-major-mode "\\s-*\\+ " "\\s-*\\+ " ,comment-start imenu-list-forward-sexp nil)
    601         hs-special-modes-alist))
    602 
    603 (defun imenu-list-forward-sexp (&optional arg)
    604   "Move to next entry of same depth.
    605 This function is intended to be used by `hs-minor-mode'.  Don't use it
    606 for anything else.
    607 ARG is ignored."
    608   (beginning-of-line)
    609   (while (= (char-after) 32)
    610     (forward-char))
    611   ;; (when (= (char-after) ?+)
    612   ;;   (forward-char 2))
    613   (let ((spaces (- (point) (point-at-bol))))
    614     (forward-line)
    615     ;; ignore-errors in case we're at the last line
    616     (ignore-errors (forward-char spaces))
    617     (while (and (not (eobp))
    618                 (= (char-after) 32))
    619       (forward-line)
    620       ;; ignore-errors in case we're at the last line
    621       (ignore-errors (forward-char spaces))))
    622   (forward-line -1)
    623   (end-of-line))
    624 
    625 ;;; define minor mode
    626 
    627 (defvar imenu-list--timer nil)
    628 
    629 (defcustom imenu-list-idle-update-delay idle-update-delay
    630   "Idle time delay before automatically updating the imenu-list buffer."
    631   :group 'imenu-list
    632   :type 'number
    633   :initialize 'custom-initialize-default
    634   :set (lambda (sym val)
    635          (prog1 (set-default sym val)
    636            (when imenu-list--timer (imenu-list-start-timer)))))
    637 
    638 (defun imenu-list-start-timer ()
    639   (imenu-list-stop-timer)
    640   (setq imenu-list--timer
    641         (run-with-idle-timer imenu-list-idle-update-delay t
    642                              #'imenu-list-update-safe)))
    643 
    644 (defun imenu-list-stop-timer ()
    645   (when imenu-list--timer
    646     (cancel-timer imenu-list--timer)
    647     (setq imenu-list--timer nil)))
    648 
    649 (defun imenu-list-update-safe ()
    650   "Call `imenu-list-update', return nil if an error occurs."
    651   (ignore-errors (imenu-list-update t)))
    652 
    653 ;;;###autoload
    654 (define-minor-mode imenu-list-minor-mode
    655   nil :global t :group 'imenu-list
    656   (if imenu-list-minor-mode
    657       (progn
    658         (imenu-list-get-buffer-create)
    659         (imenu-list-start-timer)
    660         (let ((orig-buffer (current-buffer)))
    661           (if imenu-list-focus-after-activation
    662               (imenu-list-show)
    663             (imenu-list-show-noselect))
    664           (with-current-buffer orig-buffer
    665             (imenu-list-update nil t))))
    666     (imenu-list-stop-timer)
    667     (ignore-errors (quit-windows-on imenu-list-buffer-name))
    668     ;; make sure *Ilist* is buried even if it wasn't shown in any window
    669     (when (get-buffer imenu-list-buffer-name)
    670       (bury-buffer (get-buffer imenu-list-buffer-name)))))
    671 
    672 ;;;###autoload
    673 (defun imenu-list-smart-toggle ()
    674   "Enable or disable `imenu-list-minor-mode' according to buffer's visibility.
    675 If the imenu-list buffer is displayed in any window, disable
    676 `imenu-list-minor-mode', otherwise enable it.
    677 Note that all the windows in every frame searched, even invisible ones, not
    678 only those in the selected frame."
    679   (interactive)
    680   (if (get-buffer-window imenu-list-buffer-name t)
    681       (imenu-list-minor-mode -1)
    682     (imenu-list-minor-mode 1)))
    683 
    684 (provide 'imenu-list)
    685 
    686 ;;; imenu-list.el ends here