dotemacs

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

commit 295f7f7458ec38718a9469de8002646a87100d2b
parent ce33ac2b119ec3a8ef13471a5a295d9ec575a920
Author: Lukas Henkel <lh@entf.net>
Date:   Fri, 17 Nov 2023 22:34:47 +0100

Add imenu-list

Diffstat:
Aelpa/imenu-list-0.9/imenu-list-autoloads.el | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aelpa/imenu-list-0.9/imenu-list-pkg.el | 12++++++++++++
Aelpa/imenu-list-0.9/imenu-list.el | 686+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minit.el | 4+++-
4 files changed, 768 insertions(+), 1 deletion(-)

diff --git a/elpa/imenu-list-0.9/imenu-list-autoloads.el b/elpa/imenu-list-0.9/imenu-list-autoloads.el @@ -0,0 +1,67 @@ +;;; imenu-list-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*- +;; Generated by the `loaddefs-generate' function. + +;; This file is part of GNU Emacs. + +;;; Code: + +(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path))) + + + +;;; Generated autoloads from imenu-list.el + +(autoload 'imenu-list-noselect "imenu-list" "\ +Update and show the imenu-list buffer, but don't select it. +If the imenu-list buffer doesn't exist, create it." t) +(autoload 'imenu-list "imenu-list" "\ +Update and show the imenu-list buffer. +If the imenu-list buffer doesn't exist, create it." t) +(defvar imenu-list-minor-mode nil "\ +Non-nil if Imenu-List minor mode is enabled. +See the `imenu-list-minor-mode' command +for a description of this minor mode. +Setting this variable directly does not take effect; +either customize it (see the info node `Easy Customization') +or call the function `imenu-list-minor-mode'.") +(custom-autoload 'imenu-list-minor-mode "imenu-list" nil) +(autoload 'imenu-list-minor-mode "imenu-list" "\ +Toggle Imenu-List minor mode on or off. + +This is a global minor mode. If called interactively, toggle the +`Imenu-List minor mode' mode. If the prefix argument is +positive, enable the mode, and if it is zero or negative, disable +the mode. + +If called from Lisp, toggle the mode if ARG is `toggle'. Enable +the mode if ARG is nil, omitted, or is a positive number. +Disable the mode if ARG is a negative number. + +To check whether the minor mode is enabled in the current buffer, +evaluate `(default-value \\='imenu-list-minor-mode)'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +(fn &optional ARG)" t) +(autoload 'imenu-list-smart-toggle "imenu-list" "\ +Enable or disable `imenu-list-minor-mode' according to buffer's visibility. +If the imenu-list buffer is displayed in any window, disable +`imenu-list-minor-mode', otherwise enable it. +Note that all the windows in every frame searched, even invisible ones, not +only those in the selected frame." t) +(register-definition-prefixes "imenu-list" '("imenu-list-")) + +;;; End of scraped data + +(provide 'imenu-list-autoloads) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; no-native-compile: t +;; coding: utf-8-emacs-unix +;; End: + +;;; imenu-list-autoloads.el ends here diff --git a/elpa/imenu-list-0.9/imenu-list-pkg.el b/elpa/imenu-list-0.9/imenu-list-pkg.el @@ -0,0 +1,12 @@ +(define-package "imenu-list" "0.9" "Show imenu entries in a separate buffer" + '((cl-lib "0.5")) + :commit "6cded436010a39592175238e4d02263a7cdb44c4" :authors + '(("Bar Magal (2015)")) + :maintainers + '(("Bar Magal (2015)")) + :maintainer + '("Bar Magal (2015)") + :url "https://github.com/bmag/imenu-list") +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/imenu-list-0.9/imenu-list.el b/elpa/imenu-list-0.9/imenu-list.el @@ -0,0 +1,686 @@ +;;; imenu-list.el --- Show imenu entries in a separate buffer + +;; Copyright (C) 2015-2021 Bar Magal & Contributors + +;; Author: Bar Magal (2015) +;; Version: 0.9 +;; Homepage: https://github.com/bmag/imenu-list +;; Package-Requires: ((cl-lib "0.5")) + +;; This file is not part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: +;; Shows a list of imenu entries for the current buffer, in another +;; buffer with the name "*Ilist*". +;; +;; Activation and deactivation: +;; M-x imenu-list-minor-mode +;; +;; Key shortcuts from "*Ilist*" buffer: +;; <enter>: Go to current definition +;; <space>: display current definition +;; <tab>: expand/collapse subtree +;; +;; Change "*Ilist*" buffer's position and size: +;; `imenu-list-position', `imenu-list-size'. +;; +;; Should invoking `imenu-list-minor-mode' also select the "*Ilist*" +;; window? +;; `imenu-list-focus-after-activation' + +;;; Code: + +(require 'imenu) +(require 'cl-lib) + +(defconst imenu-list-buffer-name "*Ilist*" + "Name of the buffer that is used to display imenu entries.") + +(defvar imenu-list--imenu-entries nil + "A copy of the imenu entries of the buffer we want to display in the +imenu-list buffer.") + +(defvar imenu-list--line-entries nil + "List of imenu entries displayed in the imenu-list buffer. +The first item in this list corresponds to the first line in the +imenu-list buffer, the second item matches the second line, and so on.") + +(defvar imenu-list--displayed-buffer nil + "The buffer who owns the saved imenu entries.") + +(defvar imenu-list--last-location nil + "Location from which last `imenu-list-update' was done. +Used to avoid updating if the point didn't move.") + +;;; fancy display + +(defgroup imenu-list nil + "Variables for `imenu-list' package." + :group 'imenu) + +(defcustom imenu-list-mode-line-format + '("%e" mode-line-front-space mode-line-mule-info mode-line-client + mode-line-modified mode-line-remote mode-line-frame-identification + (:propertize "%b" face mode-line-buffer-id) " " + (:eval (buffer-name imenu-list--displayed-buffer)) " " + mode-line-end-spaces) + "Local mode-line format for the imenu-list buffer. +This is the local value of `mode-line-format' to use in the imenu-list +buffer. See `mode-line-format' for allowed values." + :group 'imenu-list) + +(defcustom imenu-list-focus-after-activation nil + "Non-nil to select the imenu-list window automatically when +`imenu-list-minor-mode' is activated." + :group 'imenu-list + :type 'boolean) + +(defcustom imenu-list-custom-position-translator nil + "Custom translator of imenu positions to buffer positions. +Imenu can be customized on a per-buffer basis not to use regular buffer +positions as the positions that are stored in the imenu index. In such +cases, imenu-list needs to know how to translate imenu positions back to +buffer positions. `imenu-list-custom-position-translator' should be a +function that returns a position-translator function suitable for the +current buffer, or nil. See `imenu-list-position-translator' for details." + :group 'imenu-list + :type 'function) + +(defface imenu-list-entry-face + '((t)) + "Basic face for imenu-list entries in the imenu-list buffer." + :group 'imenu-list) + +(defface imenu-list-entry-face-0 + '((((class color) (background light)) + :inherit imenu-list-entry-face + :foreground "maroon") + (((class color) (background dark)) + :inherit imenu-list-entry-face + :foreground "gold")) + "Face for outermost imenu-list entries (depth 0)." + :group 'imenu-list) + +(defface imenu-list-entry-subalist-face-0 + '((t :inherit imenu-list-entry-face-0 + :weight bold :underline t)) + "Face for subalist entries with depth 0." + :group 'imenu-list) + +(defface imenu-list-entry-face-1 + '((((class color) (background light)) + :inherit imenu-list-entry-face + :foreground "dark green") + (((class color) (background dark)) + :inherit imenu-list-entry-face + :foreground "light green")) + "Face for imenu-list entries with depth 1." + :group 'imenu-list) + +(defface imenu-list-entry-subalist-face-1 + '((t :inherit imenu-list-entry-face-1 + :weight bold :underline t)) + "Face for subalist entries with depth 1." + :group 'imenu-list) + +(defface imenu-list-entry-face-2 + '((((class color) (background light)) + :inherit imenu-list-entry-face + :foreground "dark blue") + (((class color) (background dark)) + :inherit imenu-list-entry-face + :foreground "light blue")) + "Face for imenu-list entries with depth 2." + :group 'imenu-list) + +(defface imenu-list-entry-subalist-face-2 + '((t :inherit imenu-list-entry-face-2 + :weight bold :underline t)) + "Face for subalist entries with depth 2." + :group 'imenu-list) + +(defface imenu-list-entry-face-3 + '((((class color) (background light)) + :inherit imenu-list-entry-face + :foreground "orange red") + (((class color) (background dark)) + :inherit imenu-list-entry-face + :foreground "sandy brown")) + "Face for imenu-list entries with depth 3." + :group 'imenu-list) + +(defface imenu-list-entry-subalist-face-3 + '((t :inherit imenu-list-entry-face-3 + :weight bold :underline t)) + "Face for subalist entries with depth 0." + :group 'imenu-list) + +(defun imenu-list--get-face (depth subalistp) + "Get face for entry. +DEPTH is the depth of the entry in the list. +SUBALISTP non-nil means that there are more entries \"under\" the +current entry (current entry is a \"father\")." + (cl-case depth + (0 (if subalistp 'imenu-list-entry-subalist-face-0 'imenu-list-entry-face-0)) + (1 (if subalistp 'imenu-list-entry-subalist-face-1 'imenu-list-entry-face-1)) + (2 (if subalistp 'imenu-list-entry-subalist-face-2 'imenu-list-entry-face-2)) + (3 (if subalistp 'imenu-list-entry-subalist-face-3 'imenu-list-entry-face-3)) + (t (if subalistp 'imenu-list-entry-subalist-face-3 'imenu-list-entry-face-3)))) + +;;; collect entries + +(defun imenu-list-rescan-imenu () + "Force imenu to rescan the current buffer." + (setq imenu--index-alist nil) + (imenu--make-index-alist)) + +(defun imenu-list-collect-entries () + "Collect all `imenu' entries of the current buffer." + (imenu-list-rescan-imenu) + (setq imenu-list--imenu-entries imenu--index-alist) + (setq imenu-list--displayed-buffer (current-buffer))) + + +;;; print entries + +(defun imenu-list--depth-string (depth) + "Return a prefix string representing an entry's DEPTH." + (let ((indents (cl-loop for i from 1 to depth collect " "))) + (format "%s%s" + (mapconcat #'identity indents "") + (if indents " " "")))) + +(defun imenu-list--action-goto-entry (event) + "Goto the entry that was clicked. +EVENT holds the data of what was clicked." + (let ((window (posn-window (event-end event))) + (pos (posn-point (event-end event))) + (ilist-buffer (get-buffer imenu-list-buffer-name))) + (when (and (windowp window) + (eql (window-buffer window) ilist-buffer)) + (with-current-buffer ilist-buffer + (goto-char pos) + (imenu-list-goto-entry))))) + +(defun imenu-list--action-toggle-hs (event) + "Toggle hide/show state of current block. +EVENT holds the data of what was clicked. +See `hs-minor-mode' for information on what is hide/show." + (let ((window (posn-window (event-end event))) + (pos (posn-point (event-end event))) + (ilist-buffer (get-buffer imenu-list-buffer-name))) + (when (and (windowp window) + (eql (window-buffer window) ilist-buffer)) + (with-current-buffer ilist-buffer + (goto-char pos) + (hs-toggle-hiding))))) + +(defun imenu-list--insert-entry (entry depth) + "Insert a line for ENTRY with DEPTH." + (if (imenu--subalist-p entry) + (progn + (insert (imenu-list--depth-string depth)) + (insert-button (format "+ %s" (car entry)) + 'face (imenu-list--get-face depth t) + 'help-echo (format "Toggle: %s" + (car entry)) + 'follow-link t + 'action ;; #'imenu-list--action-goto-entry + #'imenu-list--action-toggle-hs + ) + (insert "\n")) + (insert (imenu-list--depth-string depth)) + (insert-button (format "%s" (car entry)) + 'face (imenu-list--get-face depth nil) + 'help-echo (format "Go to: %s" + (car entry)) + 'follow-link t + 'action #'imenu-list--action-goto-entry) + (insert "\n"))) + +(defun imenu-list--insert-entries-internal (index-alist depth) + "Insert all imenu entries in INDEX-ALIST into the current buffer. +DEPTH is the depth of the code block were the entries are written. +Each entry is inserted in its own line. +Each entry is appended to `imenu-list--line-entries' as well." + (dolist (entry index-alist) + (setq imenu-list--line-entries (append imenu-list--line-entries (list entry))) + (imenu-list--insert-entry entry depth) + (when (imenu--subalist-p entry) + (imenu-list--insert-entries-internal (cdr entry) (1+ depth))))) + +(defun imenu-list-insert-entries () + "Insert all imenu entries into the current buffer. +The entries are taken from `imenu-list--imenu-entries'. +Each entry is inserted in its own line. +Each entry is appended to `imenu-list--line-entries' as well + (`imenu-list--line-entries' is cleared in the beginning of this +function)." + (read-only-mode -1) + (erase-buffer) + (setq imenu-list--line-entries nil) + (imenu-list--insert-entries-internal imenu-list--imenu-entries 0) + (read-only-mode 1)) + + +;;; goto entries + +(defcustom imenu-list-after-jump-hook '(recenter) + "Hook to run after jumping to an entry from the imenu-list buffer. +This hook is ran also when the focus remains on the imenu-list +buffer, or in other words: this hook is ran by both +`imenu-list-goto-entry' and `imenu-list-display-entry'." + :group 'imenu-list + :type 'hook) + +(defun imenu-list--find-entry () + "Find in `imenu-list--line-entries' the entry in the current line." + (nth (1- (line-number-at-pos)) imenu-list--line-entries)) + +(defun imenu-list-goto-entry () + "Switch to the original buffer and display the entry under point." + (interactive) + (let ((entry (imenu-list--find-entry))) + (pop-to-buffer imenu-list--displayed-buffer) + (imenu entry) + (run-hooks 'imenu-list-after-jump-hook) + (imenu-list--show-current-entry))) + +(defun imenu-list-display-entry () + "Display in original buffer the entry under point." + (interactive) + (let ((entry (imenu-list--find-entry))) + (save-selected-window + (pop-to-buffer imenu-list--displayed-buffer) + (imenu entry) + (run-hooks 'imenu-list-after-jump-hook) + (imenu-list--show-current-entry)))) + +(defalias 'imenu-list-<= + (if (ignore-errors (<= 1 2 3)) + #'<= + #'(lambda (x y z) + "Return t if X <= Y and Y <= Z." + (and (<= x y) (<= y z))))) + +(defun imenu-list--translate-eglot-position (pos) + ;; when Eglot is in charge of Imenu, then the index is created by `eglot-imenu', with a fallback to + ;; `imenu-default-create-index-function' when `eglot-imenu' returns nil. If POS is an array, it means + ;; it was created by `eglot-imenu' and we need to extract its position. Otherwise, it was created by + ;; `imenu-default-create-index-function' and we should return it as-is. + (if (arrayp pos) + (eglot--lsp-position-to-point (plist-get (plist-get (aref pos 0) :range) :start) t) + pos)) + +(defun imenu-list-position-translator () + "Get the correct position translator function for the current buffer. +A position translator is a function that takes a position as described in +`imenu--index-alist' and returns a number or marker that points to the +real position in the buffer that the position parameter points to. +This is necessary because positions in `imenu--index-alist' do not have to +be numbers or markers, although usually they are. For example, +`semantic-create-imenu-index' uses overlays as position paramters. +If `imenu-list-custom-position-translator' is non-nil, then +`imenu-list-position-translator' asks it for a translator function. +If `imenu-list-custom-position-translator' is called and returns nil, then +continue with the regular logic to find a translator function." + (cond + ((and imenu-list-custom-position-translator + (funcall imenu-list-custom-position-translator))) + ((or (eq imenu-create-index-function 'semantic-create-imenu-index) + (and (eq imenu-create-index-function + 'spacemacs/python-imenu-create-index-python-or-semantic) + (bound-and-true-p semantic-mode))) + ;; semantic uses overlays, return overlay's start as position + #'overlay-start) + ((and (fboundp #'eglot-managed-p) (eglot-managed-p)) + #'imenu-list--translate-eglot-position) + ;; default - return position as is + (t #'identity))) + +(defun imenu-list--current-entry () + "Find entry in `imenu-list--line-entries' matching current position." + (let ((point-pos (point-marker)) + (offset (point-min-marker)) + (get-pos-fn (imenu-list-position-translator)) + match-entry) + (dolist (entry imenu-list--line-entries match-entry) + ;; "special entry" is described in `imenu--index-alist' + (unless (imenu--subalist-p entry) + (let* ((is-special-entry (listp (cdr entry))) + (entry-pos-raw (if is-special-entry + (cadr entry) + (cdr entry))) + ;; sometimes imenu doesn't use numbers/markers as positions, so we + ;; need to translate them back to "real" positions + ;; (see https://github.com/bmag/imenu-list/issues/20) + (entry-pos (funcall get-pos-fn entry-pos-raw))) + (when (imenu-list-<= offset entry-pos point-pos) + (setq offset entry-pos) + (setq match-entry entry))))))) + +(defun imenu-list--show-current-entry () + "Move the imenu-list buffer's point to the current position's entry." + (when (get-buffer-window (imenu-list-get-buffer-create)) + (let ((line-number (cl-position (imenu-list--current-entry) + imenu-list--line-entries + :test 'equal))) + (with-selected-window (get-buffer-window (imenu-list-get-buffer-create)) + (goto-char (point-min)) + (forward-line line-number) + (hl-line-mode 1))))) + +;;; window display settings + +(defcustom imenu-list-size 0.3 + "Size (height or width) for the imenu-list buffer. +Either a positive integer (number of rows/columns) or a percentage." + :group 'imenu-list + :type 'number) + +(defcustom imenu-list-position 'right + "Position of the imenu-list buffer. +Either 'right, 'left, 'above or 'below. This value is passed directly to +`split-window'." + :group 'imenu-list + :type '(choice (const above) + (const below) + (const left) + (const right))) + +(defcustom imenu-list-auto-resize nil + "If non-nil, auto-resize window after updating the imenu-list buffer. +Resizing the width works only for emacs 24.4 and newer. Resizing the +height doesn't suffer that limitation." + :group 'imenu-list + :type 'boolean) + +(defcustom imenu-list-update-hook nil + "Hook to run after updating the imenu-list buffer." + :group 'imenu-list + :type 'hook) + +(defun imenu-list-split-size () + "Convert `imenu-list-size' to proper argument for `split-window'." + (let ((frame-size (if (member imenu-list-position '(left right)) + (frame-width) + (frame-height)))) + (cond ((integerp imenu-list-size) (- imenu-list-size)) + (t (- (round (* frame-size imenu-list-size))))))) + +(defun imenu-list-display-buffer (buffer alist) + "Display the imenu-list buffer at the side. +This function should be used with `display-buffer-alist'. +See `display-buffer-alist' for a description of BUFFER and ALIST." + (or (get-buffer-window buffer) + (let ((window (ignore-errors (split-window (frame-root-window) (imenu-list-split-size) imenu-list-position)))) + (when window + ;; since Emacs 27.0.50, `window--display-buffer' doesn't take a + ;; `dedicated' argument, so instead call `set-window-dedicated-p' + ;; directly (works both on new and old Emacs versions) + (window--display-buffer buffer window 'window alist) + (set-window-dedicated-p window t) + window)))) + +(defun imenu-list-install-display-buffer () + "Install imenu-list display settings to `display-buffer-alist'." + (cl-pushnew `(,(concat "^" (regexp-quote imenu-list-buffer-name) "$") + imenu-list-display-buffer) + display-buffer-alist + :test #'equal)) + +(defun imenu-list-purpose-display-condition (_purpose buffer _alist) + "Display condition for use with window-purpose. +Return t if BUFFER is the imenu-list buffer. + +This function should be used in `purpose-special-action-sequences'. +See `purpose-special-action-sequences' for a description of _PURPOSE, +BUFFER and _ALIST." + (string-equal (buffer-name buffer) imenu-list-buffer-name)) + +(defun imenu-list-install-purpose-display () + "Install imenu-list display settings for window-purpose. +Install entry for imenu-list in `purpose-special-action-sequences'." + (cl-pushnew '(imenu-list-purpose-display-condition imenu-list-display-buffer) + purpose-special-action-sequences + :test #'equal)) + +(imenu-list-install-display-buffer) +(eval-after-load 'window-purpose + '(imenu-list-install-purpose-display)) + + +;;; define major mode + +(defun imenu-list-get-buffer-create () + "Return the imenu-list buffer. +If it doesn't exist, create it." + (or (get-buffer imenu-list-buffer-name) + (let ((buffer (get-buffer-create imenu-list-buffer-name))) + (with-current-buffer buffer + (imenu-list-major-mode) + buffer)))) + +(defun imenu-list-resize-window () + (let ((fit-window-to-buffer-horizontally t)) + (mapc #'fit-window-to-buffer + (get-buffer-window-list (imenu-list-get-buffer-create))))) + +(defun imenu-list-update (&optional raise-imenu-errors force-update) + "Update the imenu-list buffer. +If the imenu-list buffer doesn't exist, create it. +If RAISE-IMENU-ERRORS is non-nil, any errors encountered while trying to +create the index will be raised. Otherwise, such errors will be printed +instead. +When RAISE-IMENU-ERRORS is nil, then the return value indicates if an +error has occured. If the return value is nil, then there was no error. +Oherwise `imenu-list-update' will return the error that has occured, as + (ERROR-SYMBOL . SIGNAL-DATA). +If FORCE-UPDATE is non-nil, the imenu-list buffer is updated even if the +imenu entries did not change since the last update." + (catch 'index-failure + (let ((old-entries imenu-list--imenu-entries) + (location (point-marker))) + ;; don't update if `point' didn't move - fixes issue #11 + (unless (and (null force-update) + imenu-list--last-location + (marker-buffer imenu-list--last-location) + (= location imenu-list--last-location)) + (setq imenu-list--last-location location) + (if raise-imenu-errors + (imenu-list-collect-entries) + (condition-case err + (imenu-list-collect-entries) + (error + (message "imenu-list: couldn't create index because of error: %S" err) + (throw 'index-failure err)))) + (when (or force-update + ;; check if Ilist buffer is alive, in case it was killed + ;; since last update + (null (get-buffer imenu-list-buffer-name)) + (not (equal old-entries imenu-list--imenu-entries))) + (with-current-buffer (imenu-list-get-buffer-create) + (imenu-list-insert-entries))) + (imenu-list--show-current-entry) + (when imenu-list-auto-resize + (imenu-list-resize-window)) + (run-hooks 'imenu-list-update-hook) + nil)))) + +(defun imenu-list-refresh () + "Refresh imenu-list buffer." + (interactive) + (with-current-buffer imenu-list--displayed-buffer + (imenu-list-update nil t))) + +(defun imenu-list-show () + "Show the imenu-list buffer. +If the imenu-list buffer doesn't exist, create it." + (interactive) + (pop-to-buffer imenu-list-buffer-name)) + +(defun imenu-list-show-noselect () + "Show the imenu-list buffer, but don't select it. +If the imenu-list buffer doesn't exist, create it." + (interactive) + (display-buffer imenu-list-buffer-name)) + +;;;###autoload +(defun imenu-list-noselect () + "Update and show the imenu-list buffer, but don't select it. +If the imenu-list buffer doesn't exist, create it." + (interactive) + (imenu-list-update) + (imenu-list-show-noselect)) + +;;;###autoload +(defun imenu-list () + "Update and show the imenu-list buffer. +If the imenu-list buffer doesn't exist, create it." + (interactive) + (imenu-list-update) + (imenu-list-show)) + +(defun imenu-list-quit-window () + "Disable `imenu-list-minor-mode' and hide the imenu-list buffer. +If `imenu-list-minor-mode' is already disabled, just call `quit-window'." + (interactive) + ;; the reason not to call `(imenu-list-minor-mode -1)' regardless of current + ;; state, is that it quits all of imenu-list windows instead of just the + ;; current one. + (if imenu-list-minor-mode + ;; disabling `imenu-list-minor-mode' also quits the window + (imenu-list-minor-mode -1) + (quit-window))) + +(defvar imenu-list-major-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "RET") #'imenu-list-goto-entry) + (define-key map (kbd "SPC") #'imenu-list-display-entry) + (define-key map (kbd "n") #'next-line) + (define-key map (kbd "p") #'previous-line) + (define-key map (kbd "TAB") #'hs-toggle-hiding) + (define-key map (kbd "f") #'hs-toggle-hiding) + (define-key map (kbd "g") #'imenu-list-refresh) + (define-key map (kbd "q") #'imenu-list-quit-window) + map)) + +(define-derived-mode imenu-list-major-mode special-mode "Ilist" + "Major mode for showing the `imenu' entries of a buffer (an Ilist). +\\{imenu-list-mode-map}" + (read-only-mode 1) + (imenu-list-install-hideshow)) +(add-hook 'imenu-list-major-mode-hook #'hs-minor-mode) + +(defun imenu-list--set-mode-line () + "Locally change `mode-line-format' to `imenu-list-mode-line-format'." + (setq-local mode-line-format imenu-list-mode-line-format)) +(add-hook 'imenu-list-major-mode-hook #'imenu-list--set-mode-line) + +(defun imenu-list-install-hideshow () + "Install imenu-list settings for hideshow." + ;; "\\b\\B" is a regexp that can't match anything + (setq-local comment-start "\\b\\B") + (setq-local comment-end "\\b\\B") + (setq hs-special-modes-alist + (cl-delete 'imenu-list-major-mode hs-special-modes-alist :key #'car)) + (push `(imenu-list-major-mode "\\s-*\\+ " "\\s-*\\+ " ,comment-start imenu-list-forward-sexp nil) + hs-special-modes-alist)) + +(defun imenu-list-forward-sexp (&optional arg) + "Move to next entry of same depth. +This function is intended to be used by `hs-minor-mode'. Don't use it +for anything else. +ARG is ignored." + (beginning-of-line) + (while (= (char-after) 32) + (forward-char)) + ;; (when (= (char-after) ?+) + ;; (forward-char 2)) + (let ((spaces (- (point) (point-at-bol)))) + (forward-line) + ;; ignore-errors in case we're at the last line + (ignore-errors (forward-char spaces)) + (while (and (not (eobp)) + (= (char-after) 32)) + (forward-line) + ;; ignore-errors in case we're at the last line + (ignore-errors (forward-char spaces)))) + (forward-line -1) + (end-of-line)) + +;;; define minor mode + +(defvar imenu-list--timer nil) + +(defcustom imenu-list-idle-update-delay idle-update-delay + "Idle time delay before automatically updating the imenu-list buffer." + :group 'imenu-list + :type 'number + :initialize 'custom-initialize-default + :set (lambda (sym val) + (prog1 (set-default sym val) + (when imenu-list--timer (imenu-list-start-timer))))) + +(defun imenu-list-start-timer () + (imenu-list-stop-timer) + (setq imenu-list--timer + (run-with-idle-timer imenu-list-idle-update-delay t + #'imenu-list-update-safe))) + +(defun imenu-list-stop-timer () + (when imenu-list--timer + (cancel-timer imenu-list--timer) + (setq imenu-list--timer nil))) + +(defun imenu-list-update-safe () + "Call `imenu-list-update', return nil if an error occurs." + (ignore-errors (imenu-list-update t))) + +;;;###autoload +(define-minor-mode imenu-list-minor-mode + nil :global t :group 'imenu-list + (if imenu-list-minor-mode + (progn + (imenu-list-get-buffer-create) + (imenu-list-start-timer) + (let ((orig-buffer (current-buffer))) + (if imenu-list-focus-after-activation + (imenu-list-show) + (imenu-list-show-noselect)) + (with-current-buffer orig-buffer + (imenu-list-update nil t)))) + (imenu-list-stop-timer) + (ignore-errors (quit-windows-on imenu-list-buffer-name)) + ;; make sure *Ilist* is buried even if it wasn't shown in any window + (when (get-buffer imenu-list-buffer-name) + (bury-buffer (get-buffer imenu-list-buffer-name))))) + +;;;###autoload +(defun imenu-list-smart-toggle () + "Enable or disable `imenu-list-minor-mode' according to buffer's visibility. +If the imenu-list buffer is displayed in any window, disable +`imenu-list-minor-mode', otherwise enable it. +Note that all the windows in every frame searched, even invisible ones, not +only those in the selected frame." + (interactive) + (if (get-buffer-window imenu-list-buffer-name t) + (imenu-list-minor-mode -1) + (imenu-list-minor-mode 1))) + +(provide 'imenu-list) + +;;; imenu-list.el ends here diff --git a/init.el b/init.el @@ -142,6 +142,8 @@ ("M-o" . ace-window) + ("C-c '" . imenu-list-smart-toggle) + ("C-c r l" . org-roam-buffer-toggle) ("C-c r f" . org-roam-node-find) ("C-c r i" . org-roam-node-insert) @@ -383,7 +385,7 @@ ("melpa-stable" . "https://stable.melpa.org/packages/") ("melpa" . "https://melpa.org/packages/"))) '(package-selected-packages - '(diff-hl embark-consult embark all-the-icons-completion all-the-icons-ibuffer all-the-icons-dired sly-named-readtables sly-macrostep denote-refs denote-menu denote ox-epub ob-powershell powershell web-mode lexic editorconfig elfeed-tube-mpv elfeed-tube cider restclient-jq graphviz-dot-mode consult-eglot jq-mode ob-restclient restclient vterm deadgrep helpful pdf-tools paredit-menu paredit corfu sly eglot aggressive-indent project nov nhexl-mode elfeed magit yaml-mode json-mode lua-mode go-mode geiser-guile geiser org-contrib org ace-window expand-region consult marginalia uuidgen request diminish which-key)) + '(imenu-list diff-hl embark-consult embark all-the-icons-completion all-the-icons-ibuffer all-the-icons-dired sly-named-readtables sly-macrostep denote-refs denote-menu denote ox-epub ob-powershell powershell web-mode lexic editorconfig elfeed-tube-mpv elfeed-tube cider restclient-jq graphviz-dot-mode consult-eglot jq-mode ob-restclient restclient vterm deadgrep helpful pdf-tools paredit-menu paredit corfu sly eglot aggressive-indent project nov nhexl-mode elfeed magit yaml-mode json-mode lua-mode go-mode geiser-guile geiser org-contrib org ace-window expand-region consult marginalia uuidgen request diminish which-key)) '(pcomplete-ignore-case t t) '(pixel-scroll-precision-mode t) '(read-buffer-completion-ignore-case t)