Error message
Hi @rolandwalker
Thanks for your good package.
I have a problem by setting a button to make LaTeX package in \usepackage{...} clickable with the following code:
(make-variable-buffer-local
(defvar *texdoc-buttons* '() "list of texdoc buttons"))
(require 'rx)
(defun* texdocs ()
(interactive)
(save-excursion
(let ((case-fold-search t))
(goto-char (point-min))
(while (re-search-forward "\\\\usepackage.*{\\(.+\\)}" nil t)
(let* ((package1 (match-string 1)))
(add-to-list '*texdoc-buttons*
(button-lock-set-button
(rx word-start (eval package1) word-end)
(lambda ()
(interactive)
(call-process "texdoc" nil 0 nil "--view" (match-string 1))))))))))
But it give me a Wrong type argument: stringp, nil error, do you know what the wrong with this code?
Hi!
I'm not sure where you are getting the error Wrong type argument: stringp, nil. The best way to resolve that is to use Edebug, by placing the point at the end of the definition of texdocs and pressing C-u C-M-x, as described at http://www.gnu.org/software/emacs/manual/html_node/elisp/Using-Edebug.html . After that, invoking texdocs will allow you to step through the function.
However, I can give a few comments on the above code:
-
rxis a macro, evaluated at the time that the defuntexdocsis compiled. So, the variablepackage1within the macro cannot hold different values at different iterations of the loop. There is an ordinary functionrx-to-stringwhich will be evaluated at runtime if you use this:(rx-to-string `(and word-start ,package1 word-end)) -
The
.*in the usepackage regular expression matches anything, and could run past where expected. If we are trying to handle eg\usepackage[utf8]{packagename}then perhaps this expression is better"\\\\usepackage[^ {}]*?{\\(.+\\)}" -
It is not necessary to create a separate button for each usepackage. When the function associated with a button is called, that function can see the position of the point. So, the bracketed string can be discovered at invocation time, using a single button definition:
(require 'thingatpt) (make-variable-buffer-local (defvar *texdoc-buttons* '() "list of texdoc buttons")) (defun* texdocs () (interactive) (add-to-list '*texdoc-buttons* (button-lock-set-button "\\\\usepackage[^ {}]*?{\\(.+\\)}" #'(lambda () (interactive) (let ((package-name (thing-at-point 'word))) (message "running texdoc on %s..." package-name) (call-process "texdoc" nil 0 nil "--view" package-name))) :grouping 1))) -
texdocsdoes not need to be an interactive command. The same logic could be applied automatically for all TeX buffers viatex-mode-hook:(require 'thingatpt) (make-variable-buffer-local (defvar *texdoc-buttons* '() "list of texdoc buttons")) (add-hook 'tex-mode-hook #'(lambda () (unless (and (boundp '*texdoc-buttons*) *texdoc-buttons*) (add-to-list '*texdoc-buttons* (button-lock-set-button "\\\\usepackage[^ {}]*?{\\(.+\\)}" #'(lambda () (interactive) (let ((package-name (thing-at-point 'word))) (message "running texdoc on %s..." package-name) (call-process "texdoc" nil 0 nil "--view" package-name))) :grouping 1)))))
@rolandwalker Thanks for your elaborated explanation, I really learned about something from your reply. Your code works like a charm.
Hope you enjoy your every days.