66

Very often, compilations errors are displayed with the file:line syntax.

It would be nice to copy-paste this directly to open the file at the right line.

Emacs already has some mode to handle this in buffers (compile-mode, iirc), but I would like to have this available from the shell command line, since I use the standard shell most of the time outside of emacs.

Any idea how to tweak emacs to learn file:line syntax to open file at line line? (obviously, if file:line really exists on disk, it should be opened preferably)

Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174
elmarco
  • 31,633
  • 21
  • 64
  • 68
  • I'd like to see a way to open a file passing emacs a PHP error. That would look like this: Warning: Invalid argument supplied for foreach() in _drupal_schema_initialize() (line 6866 of /srv/work.electricgroups.com/dave/projects/htdocs/includes/common.inc). – Dave Cohen Aug 21 '12 at 04:58

14 Answers14

55

You can do this using emacsclient. e.g. to open FILE at line 4, column 3 in a new frame:

emacsclient +4:3 FILE

Leave off the :3 to simply open the file at line 4.

sanityinc
  • 15,002
  • 2
  • 49
  • 43
  • 5
    The "-c" flag says to create a new frame; you don't have to specify that in order for the jumping to line to work. – Damyan Jun 29 '10 at 14:06
  • Good point -- that was a matter of personal preference/habit. – sanityinc Jun 29 '10 at 19:32
  • 2
    while this is an interesting factoid, the solution by Ivan is much more functional when you're cutting and pasting file names from error messages, like the original question says – simpleuser Mar 09 '15 at 01:35
  • How can I open multiple files with specific line numbers for each? – HappyFace Jul 30 '20 at 10:49
  • It was actually very intutive: `emacsclient +x file +y anotherfile ...` – HappyFace Jul 30 '20 at 10:51
  • Is it possible to specify character index instead of column index? I keep getting the cursor way behind the intended place – BoteRock Sep 23 '20 at 17:39
18

I have the following in my .emacs, but I haven't found it as useful as I thought it would be.

;; Open files and goto lines like we see from g++ etc. i.e. file:line#
;; (to-do "make `find-file-line-number' work for emacsclient as well")
;; (to-do "make `find-file-line-number' check if the file exists")
(defadvice find-file (around find-file-line-number
                             (filename &optional wildcards)
                             activate)
  "Turn files like file.cpp:14 into file.cpp and going to the 14-th line."
  (save-match-data
    (let* ((matched (string-match "^\\(.*\\):\\([0-9]+\\):?$" filename))
           (line-number (and matched
                             (match-string 2 filename)
                             (string-to-number (match-string 2 filename))))
           (filename (if matched (match-string 1 filename) filename)))
      ad-do-it
      (when line-number
        ;; goto-line is for interactive use
        (goto-char (point-min))
        (forward-line (1- line-number))))))
Ivan Andrus
  • 5,221
  • 24
  • 31
  • nice! any idea how to hook that in the default open function? – elmarco Jun 29 '10 at 14:24
  • I'm not sure what you mean. find-file _is_ the default open function. What does C-h C-k C-x C-f show? – Ivan Andrus Jun 30 '10 at 13:02
  • 2
    "defadvice" modifies the definition of the next name (in this case find-file, the default open function). So this (defadvice find-file …) does in fact modify find-file to behave this way. – jackr Sep 24 '12 at 21:29
  • 1
    Great, this fixes `(find-file)`. But it's still not possible to do `emacsclient -c -n -a "" ~/src/file.cpp:14` – To1ne Jan 30 '13 at 11:21
  • @To1ne I think for command line arguments you should add a `+n` argument like this : `emacs +11 file.cpp` instead of appending a `:n` to the filename. – ychaouche Mar 09 '22 at 15:50
14

I suggest to add following code in your emacs config:

(defadvice server-visit-files (before parse-numbers-in-lines (files proc &optional nowait) activate)
  "looks for filenames like file:line or file:line:position and reparses name in such manner that position in file"
  (ad-set-arg 0
              (mapcar (lambda (fn)
                        (let ((name (car fn)))
                          (if (string-match "^\\(.*?\\):\\([0-9]+\\)\\(?::\\([0-9]+\\)\\)?$" name)
                              (cons
                               (match-string 1 name)
                               (cons (string-to-number (match-string 2 name))
                                     (string-to-number (or (match-string 3 name) "")))
                               )
                            fn))) files))
  )

by now you can open file with a line number right from command line like this:

emacsclient filename:linenumber:position

P.S. I hope i'm not too late with my answer.

user3674075
  • 141
  • 1
  • 3
11

And here is my go at it. Calls the original find-file-at-point

(defun find-file-at-point-with-line()
  "if file has an attached line num goto that line, ie boom.rb:12"
  (interactive)
  (setq line-num 0)
  (save-excursion
    (search-forward-regexp "[^ ]:" (point-max) t)
    (if (looking-at "[0-9]+")
         (setq line-num (string-to-number (buffer-substring (match-beginning 0) (match-end 0))))))
  (find-file-at-point)
  (if (not (equal line-num 0))
      (goto-line line-num)))
jacob
  • 2,284
  • 3
  • 20
  • 21
9

You can use a bash script:

#! /bin/bash
file=$(awk '{sub(/:[0-9]*$/,"")}1' <<< "$1")
line=$(awk '{sub(/^.*:/,"")}1' <<< "$1")
emacs --no-splash "+$line" "$file" &

If you call this script for openline and you get an error message, e.g.

Error: file.cpp:1046

you can do

openline file.cpp:1046

to open the file.cpp in Emacs at line 1046..

Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174
  • 1
    Thanks!, This helped me!. I am using a slightly modified version: `emacs --no-splash "+$line" --file "$file"` – nacho4d Jan 04 '14 at 14:25
  • The code is a little bit simpler if you use `sed` instead of `awk` (`file=$(sed 's/:[0-9]*$//' <<< "$1"); line=$(sed 's/^.*://' <<< "$1")`). – Patrick Brinich-Langlois Sep 07 '20 at 19:08
  • Note that this solution (as well as the sed version by @PatrickBrinich-Langlois) breaks if there's no column following file name at all (e.g. just `file.cpp`). I came up with the following using sed with ERE: `file=$(sed 's|:[0-9].*||' <<< "$1"); linecol=$(sed -nr 's|^[^:]+:([0-9:]*).*$|+\1|p' <<< "$1")` – Vladislav Ivanishin Feb 01 '23 at 18:43
8

Another version of Ivan Andrus' nice find-file advice which does both line + optional column number, as you see in node and coffeescript errors:

;; Open files and go places like we see from error messages, i e: path:line:col
;; (to-do "make `find-file-line-number' work for emacsclient as well")
;; (to-do "make `find-file-line-number' check if the file exists")
(defadvice find-file (around find-file-line-number
                             (path &optional wildcards)
                             activate)
  "Turn files like file.js:14:10 into file.js and going to line 14, col 10."
  (save-match-data
    (let* ((match (string-match "^\\(.*?\\):\\([0-9]+\\):?\\([0-9]*\\)$" path))
           (line-no (and match
                         (match-string 2 path)
                         (string-to-number (match-string 2 path))))
           (col-no (and match
                        (match-string 3 path)
                        (string-to-number (match-string 3 path))))
           (path (if match (match-string 1 path) path)))
      ad-do-it
      (when line-no
        ;; goto-line is for interactive use
        (goto-char (point-min))
        (forward-line (1- line-no))
        (when (> col-no 0)
          (forward-char (1- col-no)))))))
ecmanaut
  • 5,030
  • 2
  • 44
  • 66
7

Emacs 25 does not use defadvice anymore. Refs.

So here is the version updated to the new syntax:

(defun find-file--line-number (orig-fun filename &optional wildcards)
  "Turn files like file.cpp:14 into file.cpp and going to the 14-th line."
  (save-match-data
    (let* ((matched (string-match "^\\(.*\\):\\([0-9]+\\):?$" filename))
           (line-number (and matched
                             (match-string 2 filename)
                             (string-to-number (match-string 2 filename))))
           (filename (if matched (match-string 1 filename) filename)))
      (apply orig-fun (list filename wildcards))
      (when line-number
        ;; goto-line is for interactive use
        (goto-char (point-min))
        (forward-line (1- line-number))))))

(advice-add 'find-file :around #'find-file--line-number)

This works if you call open file from inside emacs (C-x C-f), but not works anymore from command line, it seems that emacs 25 is not using find-file when you call it from command line and I don't know how to debug this kind of thing.

Billal Begueradj
  • 20,717
  • 43
  • 112
  • 130
3

You talk about pasting to open a file (I assume you mean at a find file prompt inside of emacs) and also doing something from the command line. If you want to copy & paste then you need to do something like what Ivan showed with the defadvice. If you want something from the command line you can do the following. I've adapted this from something I did a year ago with an emacs:// URI handler (for use from within Firefox):

Put this in your .emacs file:

(defun emacs-uri-handler (uri)
  "Handles emacs URIs in the form: emacs:///path/to/file/LINENUM"
  (save-match-data
    (if (string-match "emacs://\\(.*\\)/\\([0-9]+\\)$" uri)
        (let ((filename (match-string 1 uri))
              (linenum (match-string 2 uri)))
          (while (string-match "\\(%20\\)" filename)
            (setq filename (replace-match " " nil t filename 1)))
          (with-current-buffer (find-file filename)
            (goto-line (string-to-number linenum))))
      (beep)
      (message "Unable to parse the URI <%s>"  uri))))

and then create a shell script in your path (I called mine 'emacsat'):

#!/bin/bash
emacsclient --no-wait -e "(emacs-uri-handler \"emacs://$1/${2:-1}\")"

A DOS batch script would look similar, but I don't know how to do default values (though I'm pretty sure you can do it).

See How to configure firefox to run emacsclientw on certain links? for further instructions if you want to integrate with Firefox, too.

Community
  • 1
  • 1
Joe Casadonte
  • 15,888
  • 11
  • 45
  • 57
  • I can get some idea from that yes, although it's relying on the shell a bit too much (I could probably write a script to handle the file:no syntax as well, that's an idea) – elmarco Jun 29 '10 at 15:06
2

To return42's code, added column number support, and cleaned up case where column number is present, and line number is sought.

;; find file at point, jump to line no.
;; ====================================

(require 'ffap)

(defun find-file-at-point-with-line (&optional filename)
  "Opens file at point and moves point to line specified next to file name."
  (interactive)
  (let* ((filename (or filename (if current-prefix-arg (ffap-prompter) (ffap-guesser))))
         (line-number
          (and (or (looking-at ".* line \\(\[0-9\]+\\)")
                   (looking-at "[^:]*:\\(\[0-9\]+\\)"))
               (string-to-number (match-string-no-properties 1))))
         (column-number
          (or 
           (and (looking-at "[^:]*:\[0-9\]+:\\(\[0-9\]+\\)")
                (string-to-number (match-string-no-properties 1)))
           (let 'column-number 0))))
    (message "%s --> %s:%s" filename line-number column-number)
    (cond ((ffap-url-p filename)
           (let (current-prefix-arg)
             (funcall ffap-url-fetcher filename)))
          ((and line-number
                (file-exists-p filename))
           (progn (find-file-other-window filename)
                  ;; goto-line is for interactive use
                  (goto-char (point-min))
                  (forward-line (1- line-number))
                  (forward-char column-number)))
          ((and ffap-pass-wildcards-to-dired
                ffap-dired-wildcards
                (string-match ffap-dired-wildcards filename))
           (funcall ffap-directory-finder filename))
          ((and ffap-dired-wildcards
                (string-match ffap-dired-wildcards filename)
                find-file-wildcards
                ;; Check if it's find-file that supports wildcards arg
                (memq ffap-file-finder '(find-file find-alternate-file)))
           (funcall ffap-file-finder (expand-file-name filename) t))
          ((or (not ffap-newfile-prompt)
               (file-exists-p filename)
               (y-or-n-p "File does not exist, create buffer? "))
           (funcall ffap-file-finder
                    ;; expand-file-name fixes "~/~/.emacs" bug sent by CHUCKR.
                    (expand-file-name filename)))
          ;; User does not want to find a non-existent file:
          ((signal 'file-error (list "Opening file buffer"
                                     "no such file or directory"
                                     filename))))))
ericP
  • 1,675
  • 19
  • 21
DrChandra
  • 181
  • 1
  • 6
1

Here is a zsh function that works if you put it into your .zshrc file.

Since I'm running my code in zsh usually, and this is where i see the errors. Kudos to @sanityinc for the emacs part. Just thought this should be on google.


emn () {
blah=$1
filen=(${(s.:.)blah})
/Applications/Emacs.app/Contents/MacOS/Emacs +$filen[2] $filen[1] &
}

Use like emn /long/stack/error.rb:123

pjammer
  • 9,489
  • 5
  • 46
  • 56
1

I've modified ivan-andrus defadvice so it works with emacsclient:

(defadvice find-file-noselect (around find-file-noselect-at-line
                                      (filename &optional nowarn rawfile wildcards)
                                      activate)
  "Turn files like file.cpp:14 into file.cpp and going to the 14-th line."
  (save-match-data
    (let* ((matched (string-match "^\\(.*\\):\\([0-9]+\\):?$" filename))
           (line-number (and matched
                             (match-string 2 filename)
                             (string-to-number (match-string 2 filename))))
           (filename (if matched (match-string 1 filename) filename))
           (buffer-name ad-do-it))
      (when line-number
        (with-current-buffer buffer-name
          (goto-char (point-min))
          (forward-line (1- line-number)))))))
To1ne
  • 1,108
  • 2
  • 12
  • 22
1

I made a little rewrite of the find-file-at-point function.

If there is a line number match, the file will be opened within an other window and the courser will be placed in this line. If no line number match, do what ffap normally does ...

;; find file at point, jump to line no.
;; ====================================

(require 'ffap)

(defun find-file-at-point-with-line (&optional filename)
  "Opens file at point and moves point to line specified next to file name."
  (interactive)
  (let* ((filename (or filename (ffap-prompter)))
     (line-number
      (and (or (looking-at ".* line \\(\[0-9\]+\\)")
           (looking-at ".*:\\(\[0-9\]+\\):"))
       (string-to-number (match-string-no-properties 1)))))
(message "%s --> %s" filename line-number)
(cond ((ffap-url-p filename)
       (let (current-prefix-arg)
     (funcall ffap-url-fetcher filename)))
      ((and line-number
        (file-exists-p filename))
       (progn (find-file-other-window filename)
          (goto-line line-number)))
      ((and ffap-pass-wildcards-to-dired
        ffap-dired-wildcards
        (string-match ffap-dired-wildcards filename))
       (funcall ffap-directory-finder filename))
      ((and ffap-dired-wildcards
        (string-match ffap-dired-wildcards filename)
        find-file-wildcards
        ;; Check if it's find-file that supports wildcards arg
        (memq ffap-file-finder '(find-file find-alternate-file)))
       (funcall ffap-file-finder (expand-file-name filename) t))
      ((or (not ffap-newfile-prompt)
       (file-exists-p filename)
       (y-or-n-p "File does not exist, create buffer? "))
       (funcall ffap-file-finder
        ;; expand-file-name fixes "~/~/.emacs" bug sent by CHUCKR.
        (expand-file-name filename)))
      ;; User does not want to find a non-existent file:
      ((signal 'file-error (list "Opening file buffer"
                 "no such file or directory"
                 filename))))))

If you have an old version of the ffap (2008) you should update your emacs or apply an other small patch ...

--- Emacs/lisp/ffap.el
+++ Emacs/lisp/ffap.el
@@ -1170,7 +1170,7 @@ which may actually result in an url rather than a filename."
          ;; remote, you probably already have a connection.
          ((and (not abs) (ffap-file-exists-string name)))
          ;; Try stripping off line numbers; good for compilation/grep output.
-         ((and (not abs) (string-match ":[0-9]" name)
+         ((and (string-match ":[0-9]" name)
                (ffap-file-exists-string (substring name 0 (match-beginning 0)))))
          ;; Try stripping off prominent (non-root - #) shell prompts
return42
  • 543
  • 3
  • 10
0

I use this a lot, I made it with command-line-1:

;;;; Open files and goto lines like we see from g++ etc. i.e. file:line#
(defun command-line-1--line-number (orig-fun args)
  (setq new-args ())
  (dolist (f args)
    (setq new-args
          (append new-args
                  (if (string-match "^\\(.*\\):\\([0-9]+\\):?$" f)
                      (list (concat "+" (match-string 2 f))
                            (match-string 1 f))
                    (list f)))))
  (apply orig-fun (list new-args)))

(advice-add 'command-line-1 :around #'command-line-1--line-number)
0

Another solution with error messages when it fails

(defun find-file-at-point-with-line ()
  "Like `find-file-at-point' but use line num, if any, to go to that line
 Example: boom.rb:12"

  (interactive)

  (let ((filename (thing-at-point 'filename))
        (line nil))
    
    (unless filename
      (error "Cannot find file at point"))
    
    ;; Extract line number 
    (let ((line-str-pos (string-match ":\\([0-9]+\\)$" filename)))
      (when line-str-pos
        (setq line (string-to-number (match-string 1 filename)))
        (setq filename (substring filename 0 line-str-pos))))
   
    ;; Open file and jump to line (if any)
    (unless (file-exists-p filename)
      (error "File does not exist: %s" filename))
    (find-file filename)
    (when line 
      (goto-line line))))
Picaud Vincent
  • 10,518
  • 5
  • 31
  • 70