Witam na mojej prywatnej stronie internetowej!
[If this is all Polish to you, click here: English]
Uwaga: z oczywistych powodów nie mogę zagwarantować swojej nieomylności, choć staram się o zgodność tego, co piszę, z Prawdą. Jest również oczywiste, że nie gwarantuję takiej zgodności w przypadku komentarzy. Umieszczenie linku do strony spoza niniejszego serwisu nie musi oznaczać, że podzielam poglądy autora tej strony, a jedynie, że uważam ją za wartościową z takich czy innych powodów.
Marcin ‘mbork’ Borkowski
When I write blog posts containing several links (usually to Emacs documentation, sometimes to my other posts and sometimes to other websites), one of the things I do is to check if all the links are correct. Usually I just middle-click on every one of them in the browser and see if they lead to the right place. I thought, however, that it would be useful to be able to see where an Org link points to without actually going there.
The most natural thing that occurred to me was to show the link target in the echo area when the point is on the link. I asked how to do it and learned that I’m far from the only one wanting such a feature. It turned out that this is almost supported out of the box.
First of all, Org mode puts the link target in the help-echo text property. This means that hovering the mouse over a link shows a tooltip with the link target. Also, I learned that C-h . (display-local-help) shows that property in the echo area when the point is on the link.
I started to search for methods of doing this without coding it myself, and I was not disappointed. It turns out that Emacs can display “local help” automatically when it is available at point.
One way to do this is to enable the option help-at-pt-display-when-idle. It works very much like Eldoc and in fact it can conflict with it. If you use Eldoc (and as far as I know, (global) Eldoc mode is turned on by default in every buffer that supports it), there is another setting you can use – eldoc-help-at-pt. It’s less configurable than help-at-pt-display-when-idle (see its docstring to know how to fine-tune its behavior), but I don’t need the fine-tuning, so I enabled it in my config. (As is often the case with things I write about here, as of the time of writing this, eldoc-help-at-pt is only available for the brave people who compile Emacs from master.)
One more thing I’d like to be able to do would be to see the target of a link the point is not on. I think I’ve mentioned on my blog that I use Avy. It’s an excellent package from Oleh Krehel, famous for Swiper, Hydra, Lispy and several other Emacs packages. Avy lets the user perform actions at places other than point without moving to them, in a very fast and convenient way. (There is a great article about using and configuring Avy by Karthinks, too – I learned a lot from it!) It turns out, however, that making Avy select Org links is trickier than I thought.
Finding links in Org mode is easy – you just search for the org-link-any-re regex. However, (avy-jump org-link-any-re) doesn’t work because avy-jump only selects visible text, and Org mode only shows the links’ descriptions by default. (This can be changed by calling org-toggle-link-display, but I really don’t like the result, since it makes text with links very hard to read.)
Now what I did was a bit of poking in Avy sources, and for a moment I thought I was able to come up with a solution. I was not particularly happy with it because I don’t fully understand what is going on there
. It seems that Avy uses the avy--visible-p function (the two dashes mean that it’s an internal function, theoretically subject to change anytime) to determine whether a character is visible or not. This function looks like this:
(defun avy--visible-p (s)
(let ((invisible (get-char-property s 'invisible)))
(or (null invisible)
(eq t buffer-invisibility-spec)
(null (assoc invisible buffer-invisibility-spec)))))
so if buffer-invisibility-spec is set to t, it will always treat everything as visible (and frankly, I have no idea why it would do that):
(defun avy-goto-org-link ()
(interactive)
(let ((buffer-invisibility-spec t))
(avy-jump org-link-any-re)))
This, however, has an obvious drawback – now legitimately invisible links (for example, ones under folded headlines) are selected as Avy candidates. A natural solution would be to modify buffer-invisibility-spec so that only the link should become visible:
(defun avy-goto-org-link ()
(interactive)
(let ((buffer-invisibility-spec
(remove '(org-link) buffer-invisibility-spec)))
(avy-jump org-link-any-re)))
This seems to work, but has the same issue as using the org-toggle-link-display function – it makes the whole links (target plus description) visible for the duration of candidate selection.
I decided that the only way I can see is changing buffer-invisibility-spec only during the evaluation of avy--regex-candidates. The standard way of achieving things like that is advice. It is probably not optimal here since I want it to only be active temporarily, and I tried using cl-letf, but failed (and decided that debugging such a convoluted construct is not worth the time). So, advice it is.
(defun avy--regex-candidates-links-visible-advice (orig-fun &rest args)
"Call `avy--regex-candidates' but with Org links visible."
(let ((buffer-invisibility-spec
(if (listp buffer-invisibility-spec)
(remove '(org-link)
buffer-invisibility-spec)
buffer-invisibility-spec)))
(apply orig-fun args)))
(defun avy-goto-org-link ()
(interactive)
(advice-add 'avy--regex-candidates
:around
#'avy--regex-candidates-org-links-visible-advice)
(avy-jump org-link-any-re)
(advice-remove 'avy--regex-candidates
#'avy--regex-candidates-org-links-visible-advice))
And now it works really well! The last missing thing is a good keybinding for this. Since I bound avy-goto-char to M-j, I decided that M-J (that is, meta-shift-j) is a good enough keychord.
(define-key global-map (kbd "M-J") 'avy-goto-org-link)
Let’s now come back to the idea of displaying a links’ target using Avy.
(defun avy-action-display-local-help (pt)
"Call `display-local-help' at PT."
(save-excursion
(goto-char pt)
(display-local-help)))
(add-to-list 'avy-dispatch-alist
'(?. . avy-action-display-local-help)
t)
And now I can press M-J . and then the character Avy associates with a particular link visible on screen to see its target in the echo area, without moving the point.
A bonus I get here is that display-local-help is useful outside Org mode, too. For example, I use ESLint to find issues in my JavaScript code, and it uses the help-echo property to show ESLint messages when the mouse hovers over the problematic place. And now I can use my newly defined Avy action for that, too! Assuming that the first character in that place is an “h”, I can press M-j h . and the suitable character to see the ESLint message without moving the point nor touching my mouse. Neat!
CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode