2

I would like to define my own rules for what a section of a document is, then use this so I can popup a list of sections I can auto-complete or use arrow keys to jump between them.

Take a simple example where sections match against ^SECTION\s(.*), how would this be possible?

eg:

Some Text

SECTION Section One

Some More text

SECTION Section Two

Other text.

How could I make a menu that pops up all sections, and activating the item navigates the cursor to the selected section?


Note that I'm not sure if this question is spesific to ivy.

Drew
  • 75,699
  • 9
  • 109
  • 225
ideasman42
  • 8,375
  • 1
  • 28
  • 105

2 Answers2

3

This is exactly what Imenu is for.

You can define positions to jump to using a menu. The menu can be in the menu-bar or popup, or you can use completion against it as a popup (e.g. in buffer *Completions*).

See the Emacs Wiki page linked above - it introduces multiple ways of using Imenu.

Imenu+, for instance, offers a few improvements for Imenu, such as better sorting of submenus and menu items, and toggling sorting and case-sensitivity.

For Elisp information about it see also the Elisp manual, node Imenu and its subnodes.

The general idea is that you set buffer-local variable imenu-generic-expression in a given buffer, to organize sections there to access by menu. The variable value is a list of elements (MENU-TITLE REGEXP INDEX), where REGEXP recognizes (defines) a section.


If you use Icicles then you can use completion (so different keys) to navigate among or search Imenu items - see Icicles Imenu.

Drew
  • 75,699
  • 9
  • 109
  • 225
  • Does Imenu allow me to do this in a way that can be assigned to a key? - so one function can initialize one list, another function a different kind of imenu? - I ask this because the docs read to me as if defining the imenu regex will prevent regular imenu from being used. – ideasman42 Feb 10 '18 at 23:56
  • Sorry, I don't understand your followup question in the comment. What list(s)? What "regular imenu"? Perhaps your original question is not clear/complete? – Drew Feb 10 '18 at 23:59
  • Say I want one key to jump between functions, another key to jump between sections of documentation (doxygen groups for eg). Can Imenu be setup so both are possible? – ideasman42 Feb 11 '18 at 00:00
  • That's a different question. Vanilla Imenu is an Emacs menu - usable with the mouse. There are other, related Imenu features linked to from that wiki page, however, some of which provide key access. – Drew Feb 11 '18 at 00:03
  • The question is not how to jump between functions/documentation, thats just an example use case. The question is - is it possible to define an imenu - that doesn't become the only kind of imenu for that buffer, so I can assign different key-bindings for different imenus. – ideasman42 Feb 11 '18 at 00:04
  • See the doc pointed to. An Imenu can, and typically does, have any number of submenus that each define different kinds of "sections", i.e., use different regexps to define different kinds of locations. With Icicles you can have individual commands for different kinds of things to navigate to. [Some such commands](https://www.emacswiki.org/emacs/Icicles_-_Other_Search_Commands#TypeSpecificImenuCommands) are predefined for Elisp entities, but you can easily define your own for any context. – Drew Feb 11 '18 at 00:08
  • Can the imenu submenus be accessed directly? - so I can have a binding for a certain kind of imenu that ignores all other kinds? - Asking because I'm not sure if I should build list data to pass to icicles or setup an imenu regex that doesn't conflict with other imenu use. – ideasman42 Feb 11 '18 at 00:16
  • I already answered yes to that. You can easily write an Icicles-search command that uses any of the regexps that define different kinds of Imenus. I said that some such are predefined and you can easily define others - you essentially need only provide the regexp to helper function `icicle-imenu-1`. See, e.g., the definition of command `icicle-imenu-face`, which navigates among face definitions using a regexp for such `defface` occurrences. – Drew Feb 11 '18 at 00:22
0

Here is a working example of a menu that jumps between sections using completing-read (which can be enhanced by enabling ivy):

;; complete init.el (for convenience)

(require 'package)
(package-initialize)
;; optional, you can disable this but `completing-read` is less useful.
(require 'ivy)

;; See: https://emacs.stackexchange.com/a/35033/2418

(defun custom-popup (menu-prompt menu-index menu-content) "
Pop up menu
Takes args: menu-prompt, default-index, menu-content).
Where the content is any number of (string, function) pairs,
each representing a menu item.
User can hit just the first char of a menu item to choose it.
Or click it with `mouse-1' or `mouse-2' to select it.
Or hit RET immediately to select the default item.
"
  (interactive)
  (let* (;; don't sort when 'completing-read' from 'ivy' is in use.
         (ivy-sort-functions-alist nil)
         (choice
          (completing-read
           ;; menu prompt & content
           menu-prompt menu-content nil t nil nil
           ;; default index
           (nth menu-index menu-content)))
         (action  (cdr (assoc choice menu-content))))
    (funcall action)))

;; helper functions

;; https://emacs.stackexchange.com/a/7156/2418 (with minor edits)
(defun matches-in-buffer-forward (regexp regexp-group &optional buffer)
"Return a list of matches and position pairs of REGEXP in BUFFER
or the current buffer if not given."
  (let ((matches))
    (save-match-data
      (save-excursion
        (with-current-buffer (or buffer (current-buffer))
          (save-restriction
            (widen)
            (end-of-buffer)
            ;; search backwards since resulting list will be reversed
            (while (search-backward-regexp regexp nil t 1)
              (push
               (list
                (match-string-no-properties regexp-group)
                (match-beginning 0))
               matches)))))
      matches)))


(defun doc-jump-section-from-matches (menu-prompt matches &optional buffer) "
Take a list of '(text, positions)' and use them to activate a menu
(with the active item closest above or matching the current line).
"
  (interactive)
  (setq
   menu-content '()
   menu-default-index -1
   index 0)
  (let ((point-eol (save-excursion (end-of-line) (point))))
    (dolist
        (section-pair matches)
      (let ((section-title (nth 0 section-pair))
            (section-pos (nth 1 section-pair)))
        (when (>= point-eol section-pos)
          (setq menu-default-index index))
        (add-to-list 'menu-content
         `
         (,section-title
          .
          (lambda
            ()
            (goto-char ,section-pos)
            ;; move the cursor after the section block
            ;; (forward-paragraph)
            ;; show the section at the top
            (recenter scroll-margin)))))
      (setq index (+ 1 index)))
    (setq menu-content (reverse menu-content))
    (custom-popup menu-prompt menu-default-index menu-content)))

;; Jump to 'SECTION' - an example.
(defun my-jump-to-section ()
  (interactive)
  (doc-jump-section-from-matches
   "Jump: "
   (matches-in-buffer-forward ".*SECTION\s*\\(.*\\)" 1)))

(global-set-key (kbd "<f8>") 'my-jump-to-section)
ideasman42
  • 8,375
  • 1
  • 28
  • 105
  • FWIW: The Lisp code here is not syntactically correct. E.g., missing paren in `my-jump-to-matches`, missing parameter list and `interactive` spec in `my-jump-to-section`. (And doc strings that start and finish in unconventional places - not an error, but odd.) – Drew Jun 01 '18 at 15:01
  • @Drew, thanks - updated the answer to a complete self contained working example. – ideasman42 Jun 02 '18 at 08:12