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