branch: elpa/adoc-mode
commit 1dbb39c83301ab0c952058ad649bd5fb21d8adc5
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
[Fix #59] Add nested imenu index support
Add `adoc-imenu-create-nested-index` that builds a hierarchical imenu
reflecting the document's heading structure, so subheadings appear as
nested entries under their parent headings.
The new `adoc-imenu-create-index-function` customization variable
controls which index style is used (nested by default, flat available
via `adoc-imenu-create-index`).
---
CHANGELOG.md | 1 +
adoc-mode.el | 70 +++++++++++++++++++++++++++++++++++++++++++++++++-
test/adoc-mode-test.el | 24 +++++++++++++++++
3 files changed, 94 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 33cfdc6e4b..1b0e6171fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
- [#21](https://github.com/bbatsov/adoc-mode/pull/21): Add support for native
font-locking in code blocks.
- [#48](https://github.com/bbatsov/adoc-mode/pull/48): Add support for
displaying images.
- Add font-lock support for Asciidoctor inline macros: `kbd:[]`, `btn:[]`,
`menu:[]`, `pass:[]`, `stem:[]`, `latexmath:[]`, `asciimath:[]`.
+- [#59](https://github.com/bbatsov/adoc-mode/issues/59): Add nested `imenu`
index support (enabled by default via `adoc-imenu-create-index-function`).
### Changes
diff --git a/adoc-mode.el b/adoc-mode.el
index 8d354d700b..60ceb0a673 100644
--- a/adoc-mode.el
+++ b/adoc-mode.el
@@ -221,6 +221,15 @@ delimited block lines have a certain length."
number)
:group 'adoc)
+(defcustom adoc-imenu-create-index-function 'adoc-imenu-create-nested-index
+ "Function to create the imenu index.
+Use `adoc-imenu-create-nested-index' for a hierarchical index
+reflecting heading structure, or `adoc-imenu-create-index' for a
+flat list."
+ :type '(choice (function-item adoc-imenu-create-nested-index)
+ (function-item adoc-imenu-create-index))
+ :group 'adoc)
+
(defcustom adoc-title-style 'adoc-title-style-one-line
"Title style used for title tempo templates.
@@ -3533,6 +3542,65 @@ LOCAL-ATTRIBUTE-FACE-ALIST before it is looked up in
(cons (cons title-text title-pos) index-alist))))))
(nreverse index-alist)))
+(defun adoc-imenu-create-nested-index ()
+ "Create a nested imenu index reflecting the heading hierarchy."
+ (let ((flat-index (adoc-imenu-create-index)))
+ (adoc--imenu-nest flat-index)))
+
+(defun adoc--imenu-heading-level (_title-text pos)
+ "Return the heading level (0-4) for heading at POS.
+_TITLE-TEXT is unused but accepted for interface consistency."
+ (save-excursion
+ (goto-char pos)
+ (let ((descriptor (adoc-title-descriptor t)))
+ (if descriptor
+ (nth 2 descriptor)
+ 0))))
+
+(defun adoc--imenu-nest (flat-index)
+ "Convert FLAT-INDEX (a flat list of (name . pos)) into a nested alist.
+Each heading contains its sub-headings as a nested menu."
+ (let ((items (mapcar (lambda (item)
+ (cons (car item)
+ (cons (cdr item)
+ (adoc--imenu-heading-level
+ (car item) (cdr item)))))
+ flat-index)))
+ ;; items is now ((name pos . level) ...)
+ (adoc--imenu-build-tree items 0)))
+
+(defun adoc--imenu-build-tree (items min-level)
+ "Build a nested imenu tree from ITEMS starting at MIN-LEVEL.
+ITEMS is a list of (name pos . level)."
+ (let (result)
+ (while items
+ (let* ((item (car items))
+ (name (car item))
+ (pos (cadr item))
+ (level (cddr item)))
+ (cond
+ ;; Item is at a higher level than we're collecting — return
+ ((< level min-level)
+ (setq items nil))
+ ;; Item is at a deeper level — shouldn't happen if called correctly
+ ((> level min-level)
+ (setq items (cdr items)))
+ ;; Item is at our level — collect it and its children
+ (t
+ (setq items (cdr items))
+ ;; Collect children (items with level > current level, up to
+ ;; next item at same or higher level)
+ (let (children)
+ (while (and items (> (cddr (car items)) level))
+ (push (car items) children)
+ (setq items (cdr items)))
+ (if children
+ (let ((subtree (adoc--imenu-build-tree
+ (nreverse children) (1+ level))))
+ (push (cons name (cons (cons nil pos) subtree)) result))
+ (push (cons name pos) result)))))))
+ (nreverse result)))
+
(defvar adoc-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?$ "." table)
@@ -3783,7 +3851,7 @@ Turning on Adoc mode runs the normal hook
`adoc-mode-hook'."
;; it's the user's decision whether he wants to set imenu-sort-function to
;; nil, or even something else. See also similar comment in sgml-mode.
- (setq-local imenu-create-index-function 'adoc-imenu-create-index)
+ (setq-local imenu-create-index-function adoc-imenu-create-index-function)
;; compilation
(when (boundp 'compilation-error-regexp-alist-alist)
diff --git a/test/adoc-mode-test.el b/test/adoc-mode-test.el
index 1827c07d0b..69c789acf1 100644
--- a/test/adoc-mode-test.el
+++ b/test/adoc-mode-test.el
@@ -1118,6 +1118,30 @@ Don't use it for anything real.")
(cons "sub chapter 2.1" 262)))))
(kill-buffer "adoc-test")))
+(ert-deftest adoctest-test-imenu-create-nested-index ()
+ (unwind-protect
+ (progn
+ (set-buffer (get-buffer-create "adoc-test"))
+ (insert "= document title\n"
+ "== chapter 1\n"
+ "=== sub chapter 1.1\n"
+ "== chapter 2\n"
+ "=== sub chapter 2.1\n"
+ "=== sub chapter 2.2\n")
+ (should
+ (equal
+ (adoc-imenu-create-nested-index)
+ '(("document title"
+ (nil . 1)
+ ("chapter 1"
+ (nil . 18)
+ ("sub chapter 1.1" . 31))
+ ("chapter 2"
+ (nil . 51)
+ ("sub chapter 2.1" . 64)
+ ("sub chapter 2.2" . 84)))))))
+ (kill-buffer "adoc-test")))
+
(ert-deftest adoctest-adoc-kw-replacement ()
(unwind-protect
(progn