1

In my LaTeX code I have functions that generate hyperlinks in the PDF file. I have, e.g.:

\newcommand{\arXivid}[1]{\href{https://arxiv.org/abs/#1}{\tt arXiv:#1}}

So, in my LaTeX code I have strings like \arXivid{1207.7235} that points to https://arxiv.org/abs/1207.7235.

I'd like to have these strings clickable in the emacs' so I tried this function:

(defun my-follow-link () 
  (interactive)
  (require 'button)
  (save-excursion
    (goto-char (point-min))
    (while (search-forward-regexp "\\\\arXivid{\\([^}]+\\)}" nil t)
      (save-excursion
    (let* ((b (copy-marker (match-beginning 0)))
           (e (copy-marker (match-end 0)))
           (ID (match-string-no-properties 1))
           (URL (concat "https://arxiv.org/abs/" ID))
           )

      (make-button b e 'action (lambda(x) (browse-url URL)))

      ;; (let ((map (make-sparse-keymap)))
      ;;   (define-key map [mouse-2] '(lambda(x) (browse-url URL)))
      ;;   (put-text-property b e 'keymap map))
      )))
    )
  )

I get this error message:

Symbol’s value as variable is void: URL
  1. I understand that let-binding the URL variable was not a good idea. But I have no idea to fix my code.

  2. The button seems to respond only to the RETURN key, not to mouse click.

  3. I tried also to use text-property (the commented code) as explained here: https://www.gnu.org/software/emacs/manual/html_node/elisp/Clickable-Text.html but the code didn't work (I didn't understand it completely).

Where did I go wrong?

Gabriele Nicolardi
  • 1,199
  • 8
  • 17

2 Answers2

3

Your lambda has a reference to the variable 'URL', what you want is the value of 'URL'. One way to do that is with the backquote ` , which is similar to the normal quote, but allows you to evaluate things i.e.

(make-button b e 'action `(lambda(x) (browse-url ,URL)))

',URL' there is replaced with the value of 'URL'

See Backquote for a fuller explanation.

rpluim
  • 4,605
  • 8
  • 22
2

The exact behavior of the button depends on whether lexical binding was active when creating it or not. If yes, then the lambda will capture the URL variable because it references something outside of its own scope, that way it ensures that if the lambda is called at a later point, it will correctly resolve URL. For this to work, a so-called closure is created.

If lexical binding was not active, Emacs uses dynamic binding instead which basically means that it will defer resolving the URL variable for as long as possible. Calling the lambda may succeed or not, whether it does depends on whether the referenced variable is bound to a value at function call time. This means that you can do things such as (let ((URL "http://example.com")) (funcall the-lambda)) and it will work, but calling it in a context without such a binding will fail. In case you're wondering why anyone would want this kind of behavior, it easily allows overriding all kinds of variables on the fly, therefore allowing a greater level of freedom when hacking on Emacs.

Lexical binding is an opt-in thing, you'll have to enable it explicitly for each buffer you evaluate code in. Use M-x add-file-local-variable-prop-line RET lexical-binding RET t RET, save the buffer to a file, then M-x revert-buffer. From now an all evaluation of code in that buffer will use lexical binding.

wasamasa
  • 21,803
  • 1
  • 65
  • 97