0

I'm writing an org babel implementation, and one thing that I'd like is to be able to embed proper line-numbers pointing back to my .org file in my language code. So for example, if I had a file /some/path/foo.org containing

* Some header

#+begin_src julia
function foo()
    1
end

foo()
#+end_src

some text

and then if someone did org-ctrl-c-ctrl-c on that src block, I want my implementation of org-babel-execute:julia to know that the code it receives came from /some/path/foo.org at line numbers 4-8.

Does anyone know how to accomplish this?

2 Answers 2

2

The filename can be obtained in elisp by calling buffer-file-name. The line numbers are trickier in that Emacs in general does not care about line numbers, but only about positions in the buffer. The latter can be obtained by parsing the source code block with org-element-at-point and then retrieving the positions of the beginning and the end. You can then obtain line numbers by going to those points and using line-number-at-pos.

One complication is that the beginning is the position in the buffer just before the # in #+begin_src but the end can be several lines below the #+end_src line, so you can infer the beginning line as one more than what line-number-at-pos gives you for the beginning position, but for the end you'll need to back-up a bit to find the #+end_src line. At that point, you can infer that the end of the program is at a line number one less than what line-number-at-pos gives you for that point.

Here's an interactive test using the file above:

* Some header

#+begin_src julia
function foo()
    1
end

foo()
#+end_src

some text

* Code

#+begin_src elisp :results drawer
  (save-excursion
     (goto-char 49)
     (let* ((context (org-element-context (org-element-at-point)))
            (beg (org-element-property :begin context))
            (end (org-element-property :end context))
            (filename (buffer-file-name)))
       (list filename beg end)))

#+end_src

#+RESULTS:
:results:
(/tmp/foo.org 16 77)
:end:

Executing the elisp code block takes you somewhere inside the Julia code block, parses the code block and returns the filename, beginning position and ending position of the Julia code block, as shown in the results: 16 is the position just before the # in #+begin_src of the Julia block; 77 is the position just before the s of some text - a couple of lines below the #+end_src line.

The next iteration of the code block returns a list of the filename and the line numbers. The beginning line number is relatively easy, but the ending line number is a bit more complicated because we need to search backward for the #+end_src string:

#+begin_src elisp :results drawer
  (save-excursion
     (goto-char 49)
     (let* ((context (org-element-context (org-element-at-point)))
            (beg (org-element-property :begin context))
            (beg-line (save-excursion
                        (goto-char beg)
                        (line-number-at-pos)))
            (end (org-element-property :end context))
            (end-line (save-excursion
                        (goto-char end)
                        (search-backward "#+end_src")
                        (line-number-at-pos)))
            (filename (buffer-file-name)))
       (list filename (+ beg-line 1) (- end-line 1))))
#+end_src

#+RESULTS:
:results:
(/tmp/foo.org 4 8)
:end:

The assumptions above are that when the execution function of the block runs, it will be the case that the current buffer is still the buffer of the Org mode file containing the code block, otherwise buffer-file-name will return the filename of a different buffer (or nil if there is file associated with that buffer); and that the current position in that buffer is still somewhere inside the code block of interest (wherever point was when you pressed C-c C-c), otherwise org-element-at-point will not parse the code block but something else. Some light testing shows that both of these assumptions are correct, so you should be able to plop the code in the code block (without the surrounding save-excursion, the initial goto-char, and the return of the final list, all of which are artifacts of the testing) into your org-babel-execute:julia function and get the information you want. Just to avoid misunderstanding, this is the code you should use:

  (let* ((context (org-element-context (org-element-at-point)))
         (beg (org-element-property :begin context))
         (beg-line (save-excursion
                     (goto-char beg)
                     (line-number-at-pos)))
         (end (org-element-property :end context))
         (end-line (save-excursion
                     (goto-char end)
                     (search-backward "#+end_src")
                     (line-number-at-pos)))
         (filename (buffer-file-name)))
    ;; you can now use filename, (+ beg-line 1) and (- end-line 1) as you wish...
8
  • Interesting, thanks Nick. The main way I could see this not working would be in the execution during org-export, but if this is the only way to get that information then oh well. Thanks for the well laid out answer!
    – Mason
    Commented Apr 16, 2022 at 5:06
  • Do you have evidence that it is not going to work during export or are you speculating?
    – NickD
    Commented Apr 16, 2022 at 22:04
  • @NickD Do you think it'd be possible to execute code line by line using this? grab line number and modify the body before executing?
    – eugene
    Commented Aug 23, 2023 at 12:51
  • 1
    Executing code line-by-line does not make sense in most programming languages: in Lisp e.g. the syntactic element that corresponds to an executable construct is an S-expression; in C/C++/Python/etc. it's a statement - all debuggers I know of work at that level. There may be multiple such constructs per line or a construct can be split across multiple lines. If you want to debug a code block, you could do C-c ' (org-edit-special) to get the code in a buffer with the appropriate language mode; then you can use a debugger to step through the code.
    – NickD
    Commented Aug 23, 2023 at 19:23
  • See also emacs.stackexchange.com/questions/13244/… for a way to edebug code in an Emacs Lisp code block.
    – NickD
    Commented Aug 23, 2023 at 19:30
0

This functionality is built into org-babel already albeit not using line numbers but embedding comments in the source code.

The function you want to call when in the generated source (julia) file is org-babel-tangle-jump-to-org

I'll use elisp as an example

In the org file you need (The :comments could be link)

#+PROPERTY:header-args :cache yes :tangle yes :comments noweb
#+NAME: name
#+begin_src emacs-lisp
(message "hello")
#+end_src

This generates

;; [[file:~/tmp/t.org::name][name]]
(message "hello")
;; name ends here

The only drawback is that you need to name each source block.

I use code from https://github.com/grettke/help/blob/master/Org-Mode_Fundamentals.org to automatically add a #+NAME: if it is missing.

The hook needed for this is

(add-hook 'org-babel-pre-tangle-hook #'help/org-prp-src-blk)

The author also says you need to give each heading an id.

I would note that this seems not to work if you use weaving as the link gets too complex but for sequential code in the org file it works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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