1

I want to be able to use the results of a regexp search as an helm source. The goal here is to find all the labels of a latex document, this in part because I find this useful but also to learn Helm and elisp. Here is what I came up with:

(defun my/matches-in-buffer (regexp)
  "return a list of matches of REGEXP."
  (let ((matches))
    (save-match-data
      (save-excursion
          (save-restriction
            (widen)
            (goto-char 1)
            (while (re-search-forward regexp nil t)
              (push (substring (match-string 1) 7 -1) matches))))
      )
matches
))

(defun my/find-labels ()
(my/matches-in-buffer "\\(\\\\label{\\(.*\\)}\\)")
)
(setq label-helm-source
      '((name . "HELM label")
        (candidates . my/find-labels)
        (action . (lambda (candidate)
                    (message "%s" candidate)))))
(defun helm-label ()
  (interactive)
  (helm :sources '(label-helm-source)))

However this does not seem to work as helm gives me no proposition when I do M-x helm-labels. On the other hand, executing (my/find-labels) in an org-mode file containing a \label{test} gives me the list with element test as expected.

What should I change in the code to have Helm use the results of the search from my/find-labels as a source?

Drew
  • 75,699
  • 9
  • 109
  • 225
TMat
  • 95
  • 9

1 Answers1

1

You’re on the right track, but are missing a couple of things.

The two most important ones are:

  1. You need to wrap your candidate computation in with-helm-current-buffer; otherwise, Helm will search for your labels in the Helm buffer and not in the buffer from which Helm was invoked.
  2. Ultimately, Helm will need to know where to jump to. In order to achieve this, you can utilize its (DISPLAY . REAL) form of a candidate, where DISPLAY will be the label and REAL will be its position.

The two less important ones are:

  1. Your candidates are in reverse order — you should nreverse them.
  2. The (substring (match-string 1) 7 -1) part makes no sense, why the hardcoded 7?

Bearing these in mind, I came up with the following proof of concept:

(defun my/matches-in-buffer (regexp)
  "Return a list of matches of REGEXP."
  (let ((matches))
    (save-match-data
      (save-excursion
        (save-restriction
          (widen)
          (goto-char 1)
          (while (re-search-forward regexp nil t)
            ;; (DISPLAY . REAL) candidate format
            (push (cons (match-string 1) (match-beginning 1)) matches)))))
    ;; Correct order of matches
    (nreverse matches)))

(defun my/helm-label-candidates ()
  "Compute candidates for `my/helm-label'."
  ;; Search in correct buffer
  (with-helm-current-buffer
    (my/matches-in-buffer "\\\\label{\\(.*\\)}")))

(defun my/helm-label-action (candidate)
  "Jump to CANDIDATE from `my/helm-label'."
  ;; Jump to candidate
  (goto-char candidate)
  (helm-highlight-current-line))

(defun my/helm-label ()
  (interactive)
  (helm :sources '(((name . "HELM label")
                    (candidates . my/helm-label-candidates)
                    (action . my/helm-label-action)))))

There are of course several improvements to be made: help, persistent action, better handling of narrowed regions when jumping, etc.

d125q
  • 1,418
  • 5
  • 9
  • Thanks this answers exactly my question. And the hard-coded 7 is the length of `\label{` because I wanted only the content and not the whole and I did not know how to do that with regexp. Your solution is a lot better. Thanks! – TMat Oct 16 '22 at 14:46