Making filenames/line numbers linkable in Emacs gud buffer
Asked Answered
J

4

13

I'm running pdb on my testcases in Python through the gud buffer. When I get a stacktrace/failure in my testcase, it looks like this:

FAIL: test_foo_function (__main__.TestFoo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test/testfoo.py", line 499, in test_foo_function
    self.assertEqual('foo', 'foo')

I'd love to be able to make the line(s) like:

File "test/testfoo.py", line 499, in test_foo_function

clickable and take be to line 499 in testfoo.py.

(edit) The folks on the python-mode list led me to pdbtrack and I was able to get it to work there. See answer below...

Jackiejackinoffice answered 18/1, 2010 at 18:35 Comment(2)
that would be pretty cool. I'm curious to know more about your setup for running tests in the gud buffer. sorry I can't answer your question thoughLibation
I've a post on my blog regarding my old setup (panela.blog-city.com/python_and_emacs_5_pdb_and_emacs.htm). Am currently using shell-mode/python-mode (python.org version not emacs version) and pdbtrackJackiejackinoffice
J
5

Thanks to a hint by Gerard B I figured it out. I'm doing this from pdbtrack (shell) instead of pure pdb, but it should work in both I believe. You need to enable compilation-shell-minor-mode. And have the following code in your .emacs:

;; if compilation-shell-minor-mode is on, then these regexes
;; will make errors linkable
(defun matt-add-global-compilation-errors (list)
  (dolist (x list)
    (add-to-list 'compilation-error-regexp-alist (car x))
    (setq compilation-error-regexp-alist-alist
      (cons x
            (assq-delete-all (car x)
                             compilation-error-regexp-alist-alist)))))

(matt-add-global-compilation-errors
 `(
   (matt-python ,(concat "^ *File \\(\"?\\)\\([^,\" \n    <>]+\\)\\1"
                    ", lines? \\([0-9]+\\)-?\\([0-9]+\\)?")
           2 (3 . 4) nil 2 2)
   (matt-pdb-stack ,(concat "^>?[[:space:]]*\\(\\([-_./a-zA-Z0-9 ]+\\)"
                       "(\\([0-9]+\\))\\)"
                       "[_a-zA-Z0-9]+()[[:space:]]*->")
              2 3 nil 0 1)
   (matt-python-unittest-err "^  File \"\\([-_./a-zA-Z0-9 ]+\\)\", line \\([0-9]+\\).*" 1 2)
   )
 )

(defun matt-set-local-compilation-errors (errors)
  "Set the buffer local compilation errors.

Ensures than any symbols given are defined in
compilation-error-regexp-alist-alist."
  (dolist (e errors)
     (when (symbolp e)
      (unless (assoc e compilation-error-regexp-alist-alist)
        (error (concat "Error %s is not listed in "
                       "compilation-error-regexp-alist-alist")
               e))))
  (set (make-local-variable 'compilation-error-regexp-alist)
       errors))

Then you can use standard compile mode navigation to zip through the error stack trace.

Jackiejackinoffice answered 23/1, 2010 at 6:53 Comment(3)
Note that this works when the file/buffer is first open. Over time there appears to be some "drift" (ie the cursor appears some lines after the actual line)Jackiejackinoffice
Thanks! But I notice that while lines at the top of a long traceback are clickable, lines further down are not.Penurious
Haven't had time to dissect the code to understand why it "drifts". Disabling and re-enabling compilation-shell-minor-mode appears to correct the drift, however.Emerald
M
2

I think what you want to customize is compilation-parse-errors-filename-function, which is a function that takes a filename, and returns a modified version of the filename to be displayed. This is a buffer local variable, so you should set it in each buffer that will be displaying python errors (there is probably an appropriate hook to use, I do not have python mode installed so I cannot look it up). You would use propertize to return a version of the input filename that acts as a hyperlink to load the actual file. propertize is well documented in the elisp manual.

If compilation-parse-errors-filename-function is not getting called, then you want to add a list to compilation-error-regexp-alist-alist (that does say alist-alist, that is not a typo) which is a list of mode names followed by regular expressions to match errors, and numerical indexes of the matching line number, filename etc. info in the error regexp match.

Malleus answered 18/1, 2010 at 23:45 Comment(0)
C
0

Adding to Justin's answer:

I have the following in my slime config which is supposed to jump to a file and line from a clojure stack trace.

Unfortunately I must admit that it doesn't actually work for me at the moment - the function isn't able to find the correct file - but as far as I can tell, that should be fixable by changing how project-root is defined or by changing the structure of my projects on the filesystem (I just haven't had the time or inclination to look into it).

It does bring up a good point though, in most functinality like this it's a bit tricky to figure out the project root in a generic and portable fashion. In this case we rely on a src directory, but that's probably not appropriate for your python projects.

So following on from where Justin left off, you should be able to take some tips from the function below and parse the file name and line numbers from the test case error, create a link to the line number, and use the compilation-parse-errors-filename-function and propertize to make the line in the gud buffer a link.

If you do get it to work, please add an answer to your own question. I think a lot of people would find it useful.

  (defun slime-jump-to-trace (&optional on)
    "Jump to the file/line that the current stack trace line references.
    Only works with files in your project root's src/, not in dependencies."
    (interactive)
    (save-excursion
      (beginning-of-line)
      (search-forward-regexp "[0-9]: \\([^$(]+\\).*?\\([0-9]*\\))")
      (let ((line (string-to-number (match-string 2)))
            (ns-path (split-string (match-string 1) "\\."))
            (project-root (locate-dominating-file default-directory "src/")))

        (find-file (format "%s/src/%s.clj" project-root
                           (mapconcat 'identity ns-path "/")))
        (goto-line line))))

I should also mention that I copied this function from somewhere on the web, but I can't remember the URL. It seems to be from Phil Hagelberg's (technomancy) excellent Emacs starter kit.

Casualty answered 19/1, 2010 at 9:14 Comment(0)
E
0

Here's a function to jump to the file and line specified in a trace.

If you put your point anywhere on the "File" line of the traceback, the function will find the file and move the point to the front of the specified line in another window:

FAIL: test_foo_function (__main__.TestFoo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test/testfoo.py", line 499, in test_foo_function
    self.assertEqual('foo', 'foo')
(defun my-jump-to-file-from-python-error ()
  "Jump to line in file specified by a Python traceback."
  (interactive)
  (let* ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position)))
         file
         number)

    (string-match "^ *File \\(\"?\\)\\([^,\" \n    <>]+\\)\\1, lines? \\([0-9]+\\)-?\\([0-9]+\\)?" line)

    (setq file (match-string 2 line))
    (setq number (match-string 3 line)) ; match is string, not numeric

    (cond ((and file number)
           (find-file-other-window file)
           (with-current-buffer (get-buffer (file-name-nondirectory file))
             (goto-char (point-min))
             (forward-line (1- (string-to-number number))))))))

I just copied the regex from @matt harrison's response. If you change it, I recommend using regexp-builder and pressing C-c C-e for reb-enter-subexp-mode. This is allows you to cycle through sub-expressions and know which match to use for the file and line number.

Emerald answered 7/1, 2022 at 0:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.