5

Is there some emacs lisp code that would automatically find /nfs file paths in the buffer and highlight/link to them? So clicking on them would open that file?

Example path: /nfs/foo/bar/file.txt

7

There's probably a package that does this already, but I don't know of it.

This bit of code adds buttons to text it thinks looks like a path to a file. You can add the function 'buttonize-buffer to find-file-hooks or run it manually (or conditionally).

(define-button-type 'find-file-button
  'follow-link t
  'action #'find-file-button)

(defun find-file-button (button)
  (find-file (buffer-substring (button-start button) (button-end button))))

(defun buttonize-buffer ()
  "turn all file paths into buttons"
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "/[^ \t]*" nil t)
      (make-button (match-beginning 0) (match-end 0) :type 'find-file-button))))

; (add-hook 'find-file-hook 'buttonize-buffer)   ; uncomment to add to find file
  • 2
    This is perfect. I did some more characters to avoid... re-search-forward "/nfs[^ \t\n,\'\"]*" .. there's probably more that I will find, but it's easy enough to add. – Gregory Aug 14 '10 at 17:40
5

Try the built-in package ffap (find file at point):

http://www.emacswiki.org/emacs/FindFileAtPoint

http://www.gnu.org/software/emacs/manual/html_node/emacs/FFAP.html

I don't use the minor mode, instead binding a key to ffap that I hit when on a filename.

  • 2
    this might work too, but has the extra step of moving the pointer to the place of the path and hitting a key. Trey's answer is mouse driven and very efficient. – Gregory Aug 14 '10 at 17:42
2

Great solution. But I agree in using ffap, which is part of GNU Emacs. ffap solves many subtle problems, it expands environment variables and also catches URLs.

However, ffap cannot be used easily from own Lisp. My reimplementation of buttonize-buffer is based on ffap-next-regexp and ffap-guesser. The hard part was to work arround the bug mentioned below, and to get the start point of the file or URL, which ffap-guesser does not provide.

The parser scans through the current buffer and prints details into the message buffer; there you can see which strings are guessed to be files, which match, and which are buttonized.

(defun buttonize-buffer ()
  "Turn all file paths and URLs into buttons."
  (interactive)
  (require 'ffap)
  (deactivate-mark)
  (let (token guess beg end reached bound len)
    (save-excursion
      (setq reached (point-min))
      (goto-char (point-min))
      (while (re-search-forward ffap-next-regexp nil t)
        ;; There seems to be a bug in ffap, Emacs 23.3.1: `ffap-file-at-point'
        ;; enters endless loop when the string at point is "//".
        (setq token (ffap-string-at-point))
        (unless (string= "//" (substring token 0 2))
          ;; Note that `ffap-next-regexp' only finds some "indicator string" for a
          ;; file or URL. `ffap-string-at-point' blows this up into a token.
          (save-excursion
            (beginning-of-line)
            (when (search-forward token (point-at-eol) t)
              (setq beg (match-beginning 0)
                    end (match-end 0)
                    reached end))
            )
          (message "Found token %s at (%d-%d)" token beg (- end 1))
          ;; Now let `ffap-guesser' identify the actual file path or URL at
          ;; point.
          (when (setq guess (ffap-guesser))
            (message "  Guessing %s" guess)
            (save-excursion
              (beginning-of-line)
              (when (search-forward guess (point-at-eol) t)
                (setq len (length guess) end (point) beg (- end len))
                ;; Finally we found something worth buttonizing. It shall have
                ;; at least 2 chars, however.
                (message "    Matched at (%d-%d]" beg (- end 1))
                (unless (or (< (length guess) 2))
                  (message "      Buttonize!")
                  (make-button beg end :type 'find-file-button))
                )
              )
            )
          ;; Continue one character after the guess, or the original token.
          (goto-char (max reached end))
          (message "Continuing at %d" (point))
          )
        )
      )
    )
  )

To permanently install the function:

(add-hook 'find-file-hook 'buttonize-buffer)

A nicer solution is to "lazily buttonize" buffers:

(defun buttonize-current-buffer-on-idle (&optional secs)
  "Idle-timer (see \\[run-with-idle-timer]) that buttonizes filenames and URLs.
SECS defaults to 60 seconds idle time."
  (interactive)
  (run-with-idle-timer (or secs 60) t 'buttonize-buffer))

(add-hook 'after-init-hook 'buttonize-current-buffer-on-idle)
  • Nice. One just has to clean the message in order to make it work in the *Messages* buffer. Also ffap-guesser will strip line-number from the filename. Setting (when (setq guess token) instead will keep it. – Flint Nov 29 '20 at 15:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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