0

I have a regular expression and an path, e.g. ("[Bb]ackup" . "/home/user/backup.org"). Now I want every substring in the buffer that matches the regexp to look like an org-link and to be clickable so that a click opens the file. (I can't use actual org-links because it needs to work in non-org buffers as well.) I know about make-button but I'd like links to be created (or removed) dynamically as I type similar to syntax highlighting. make-button can't do that as far as I can see since you don't specify a regexp but the start and end point of the button in the buffer and you'd have to recalculate these yourself on each button press. highlight-regexp looks close to what I want but afaics it only allows me to apply faces, so it can't make clickable links.

4
  • Buttons are built on clickable text so maybe you can use that? However, recalculating after every change in the buffer sounds painfully slow, so you'll probably want to do it lazily (maybe when emacs is idle).
    – NickD
    Jan 10, 2021 at 22:32
  • What you describe is exactly what I'm currently doing, including the use of the idle timer. But I'd much prefer the links to appear instantly. I could live with this solution if necessary, but it's irritating and adds friction when I have to wait before a link appears and becomes usable. Not ideal.
    – tmalsburg
    Jan 11, 2021 at 15:22
  • Well, you can try calling your function from post-self-insert-hook so that the links are ready after every character insertion, but I suspect that then you'll have to wait for characters to appear, rather than links. I don't see a win-win solution here, only a trade-off.
    – NickD
    Jan 11, 2021 at 16:29
  • Syntax highlighting is instantaneous, so instant links should be possible as well. Links in Org mode also appear instantly as we type them. I actually read the code for org-links but since Org's source code is complex, it was difficult to pin down the key bits.
    – tmalsburg
    Jan 12, 2021 at 12:07

1 Answer 1

2

If you want a straight font-lock approach, I think this is what you want.

(defun next-notes (limit)
  (when (re-search-forward "[Nn]otes" limit t)
    (let ((map (make-sparse-keymap)))

      (define-key map [mouse-1]
    (lambda ()
      (interactive)
      (find-file "~/notes.org")))

      (set-text-properties
       (match-beginning 0) (match-end 0)
       `(local-map ,map mouse-face 'highlight
           help-echo "mouse-1: click me"))

      t)))

(font-lock-add-keywords
 nil
 '((next-notes (0  'link t)))
 t)

You can also use button-lock-mode:

(cl-loop for (regex . path) in '(("[Cc]heatsheet" . "/Users/jkitchin/backup.org"))
     do
     (button-lock-register-global-button
      regex
      `(lambda (event)
             (interactive "e")
         (find-file ,path))
      ;; I like to press return on functional text to activate it.
      :keyboard-binding "RET"
      ;; These are the rest of the properties
      :face (list 'link)
      :help-echo (format "Click me to open %s" path)))

(button-lock-mode +1)

It works (lightly tested) I think. It is sort of tedious to debug, I am not sure if (button-lock-clear-all-buttons) actually clears everything, and had to restart emacs a few times while testing it.

I wrote https://github.com/jkitchin/scimax/blob/master/scimax-functional-text.el sort of for this purpose. Hre is what it looks like. It feels a little clunky to use eval here, but it is a macro, and variables don't seem to expand in the call the right way. If anyone knows why or how to fix it, I would love to know. It has been a long time since I used this often, so it probably could use some attention.

(require 'scimax-functional-text)
 
(cl-loop for (regex . path) in '(("notes" . "~/notes.org")
                 ("[bB]ackup" . "~/backup.org"))
     do
     (eval
     `(scimax-functional-text ,regex ((find-file ,path)) :face '(link))))

(button-lock-mode +1)
3
  • This looks very promising. I tried to hack something with font-lock but failed to make the links clickable. I then thought I'd have to settle for overlays but your answer suggests that font-lock can do the job after all. Great!
    – tmalsburg
    Jan 11, 2021 at 15:31
  • 1
    I added a simpler font-lock example, and simplified the button code too. Since you don't use any information about what you are looking at, it isn't necessary to do the complicated search that was done before. Jan 11, 2021 at 15:57
  • Amazing! I would never have been able to come to this solution by reading the font-lock documentation (font-lock-keywords) which is either incomplete or too cryptic for me to understand.
    – tmalsburg
    Jan 12, 2021 at 12:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.