Line numbers in org-mode code blocks

| categories: orgmode, emacs | tags:

Some of my students have wanted to show line numbers in code blocks. This is especially useful for when you run a Python block, and you get an error message with a line number in it. Right now, to figure out which line that is, you have to into the code block, type C-c ' to get into edit mode, and turn line numbers on. We look into how to achieve that here.

You may want to see the video here: https://www.youtube.com/watch?v=kinWijGzXms .

First, we need to get the region that is the code block. We can find some info in the org-element, but, the :begin and :end include lines we don't want, like the header lines, and the results. But, we can get the beginning, and maybe from there search forward to the block. Run this code block to see where the point goes.

;; a boring comment

(progn
  (+ 40 2))

(goto-char (org-element-property :begin (org-element-context)))
(re-search-forward (regexp-quote (org-element-property :value (org-element-context))))
(goto-char (match-beginning 0))
;; number of lines in block. The last carriage return doesn't count.
(1- (length (s-split "\n" (org-element-property :value (org-element-context)))))
9

So, we can get the number of lines, and move the point to the first line. For numbers, we will use overlays. Here is a simple way to put a number at the beginning of a line.

(let (ov)
  (beginning-of-line)
  (setq ov (make-overlay (point) (point)))
  (overlay-put ov 'before-string "1"))
1

The next thing to do is make a function that puts a number at the beginning of a line. We might as well store these overlays in a variable, so they are easy to remove later. This is just for exploration of how to do it. Later we combine all these pieces together.

(defvar number-line-overlays '()
  "List of overlays for line numbers.")

(make-variable-buffer-local 'number-line-overlays)

(defun number-line (N)
 "Put an overlay at the beginning of a line."
  (beginning-of-line)
  (let (ov)
    (setq ov (make-overlay (point) (point)))
    (overlay-put ov 'before-string (format "%3s" (number-to-string N)))
    (add-to-list 'number-line-overlays ov)))

(number-line 4)
#<overlay from 1782 to 1782 in blog.org>

That looks promising. Let's make a function to clear those overlays. It is so easy it may not even be worth writing.

(defun number-line-clear ()
  (mapc 'delete-overlay number-line-overlays)
  (setq number-line-overlays '()))

(number-line-clear)

Finally, we are ready to hack up the code block numbering code. The numbers will not automatically update, so we will write a function that numbers the block, but only temporarily. Any key press will get rid of the numbers so we can get back to work. I am going to go ahead and make this a stand-alone function and block.

(defvar number-line-overlays '()
  "List of overlays for line numbers.")

(make-variable-buffer-local 'number-line-overlays)

(defun number-line-src-block ()
  (interactive)
  (save-excursion
    (let* ((src-block (org-element-context))
           (nlines (- (length
                       (s-split
                        "\n"
                        (org-element-property :value src-block)))
                      1)))
      (goto-char (org-element-property :begin src-block))
      (re-search-forward (regexp-quote (org-element-property :value src-block)))
      (goto-char (match-beginning 0))

      (loop for i from 1 to nlines
            do
            (beginning-of-line)
            (let (ov)
              (setq ov (make-overlay (point) (point)))
              (overlay-put ov 'before-string (format "%3s" (number-to-string i)))
              (add-to-list 'number-line-overlays ov))
            (next-line))))

  ;; now read a char to clear them
  (read-key "Press a key to clear numbers.")
  (mapc 'delete-overlay number-line-overlays)
  (setq number-line-overlays '()))

(number-line-src-block)

I am not sure how to get the numbers to automatically update smoothly like they do in linum-mode. That code uses a lot of hooks to make updates work, and embeds them in a minor mode to get rid of them. It also puts them in the fringe I think, but it is not clear how that is done.

We could modify what happens after the numbers are put on, e.g. pressing numbers might jump to a line, or some other kind of functionality. I don't have a critical need for this right now, so I didn't explore it more. Let me know if you have any good ideas for it!

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter