Post

Fixing org-mode coding assistance

Introduction

Org mode, with its org-babel package, provides a great literate programming environment and forms a great alternative to jupyter notebooks. However, one thing that is currently missing when doing literate programming in org-mode is proper coding assistance.

In this article I describe how to solve various coding assistance issues when using org-mode for literate programming.

Usage

This article is meant to be read interactively from inside Emacs. So it is recommended to download the article from here and open it in Emacs. Of course the article can be read in a browser also, but interactive examples will not work and internal links will not be displayed.

Overview

This article describes how to fix dynamic and static coding assistance for org-mode. It is assumed that you are using Emacs version >= 29 with org-mode version >= 9.6.7, but all methods can also be implemented, maybe in a slightly modified form, on lower versions of Emacs and org-mode (they should work at least from Emacs version 27 with org-mode version 9).

Dynamic coding assistance only works (or exists) for interpreted languages. Examples for emacs-lisp and python are provided.

Static coding assistance within org-mode works for any language that provides some static coding analysis package that accepts buffer contents instead of files, like jedi.

Finally, the static coding assistance in special editing buffers works for any language that comes with a static code analysis package (which nowadays more or less means, any language with LSP support).

Acknowledgement

Some of the required investigations for writing this article have been made possible by MLP, an innovative AI company, that has hired me to investigate and solve some of these issues. It has been great fun working for MLP, and I am very thankful to MLP for providing me the great opportunity.

Coding assistance (in this article)

As coders we are spoiled by our editors. Generally, editors provide a lot of assistance like auto-indentation, auto-completion, signature hints, jump to definition, code checkers, refactoring features etc. Although these are all well known features, I could not find a single all-encompassing term for this body of features. In this article I refer to the collection of such basic features as coding assistance, and usage of the term should not be confused with its common usage for refering to more advanced coding assistance tools like copilot, tabnine etc.

Dynamic and static coding assistance

Most of the coding tools consist of a front end and a back end. For Emacs, auto-completion front ends are provided by company, corfu, auto-complete etc, the signature hints front end is provided by eldoc, jump to definition by xref, code checking by Flymake… etc. These front ends are provided information via back ends. A full collection of front ends is also provided by lsp clients (Eglot and LSP mode), see LSP below.

Dynamic coding assistance

For dynamic languages (languages with a REPL), the back end can be the REPL. This article refers to coding assistance using such backends as dynamic coding assistance. To provide coding assistance information, the REPL needs to be aware of your code and you must evaluate your code in the REPL (e.g. via ‘send to REPL/shell’ functions) before coding assistance works. Generally such coding assistance does not require the installation of extra packages (besides the package that provides the REPL for your language).

Static coding assistance

For both static and dynamic programming languages, coding assistance information can also be provided by ‘external packages’ like static analyzers, linters, formatters etc. This article refers to such coding assistance as static coding assistance.

LSP

Alternatively, LSP clients provide the full collection of front ends. Emacs 29 ships with the eglot lsp-client which simply reuses the exisiting emacs front ends mentioned in the previous paragraph. 1 Another popular, and probably somewhat more powerful, LSP client is provided by LSP mode. 2

Partial solutions

Many dynamic coding assistance issues can be solved by using emacs-jupyter if possible (i.e. if a kernel for your language is available).

Although I haven’t tried it, LSP mode is trying to implement coding assistance support for literate programming in org-mode.

PART 1: Fixing dynamic coding assistance for emacs-lisp

This section describes how to fix dynamic coding assistance for emacs-lisp. Subsequent sections describe how to fix dynamic and static (using some hack) coding assistance for python (and possibly other languages as well).

The problem

First download this article as descibed in the Usage section above. Then place your cursor behind (def in the following source block, and do M-x completion-at-point (C-M-i)

(def

You will find that nothing happens, as emacs-lisp completion does not work by default inside source blocks.

Now, in the above source block, complete def to defun by typing. If you are using Emacs 29, again you will find that nothing happens, as eldoc functionality does not work in code-blocks by default (although org-eldoc which is part of org-contrib fixes it for some languages).

Fixing dynamic coding assistance

The recommended way of editing source blocks with dynamic coding assistance, is by using org-edit-special. However, for simple edits it is often more convenient to edit directly within the org-buffer itself.

Completion

Fixing dynamic completion for emacs-lisp source blocks is straightforward. Just add the elisp-completion-at-point function to your buffer-local completion-at-point-functions.

(add-hook 'completion-at-point-functions 'elisp-completion-at-point nil t)

However, the above solution activates elisp-completion-at-point everywhere, not just within emacs-lisp code blocks. Therefore, we borrow a trick from org-eldoc (part of org-contrib) to limit its activation to the regions within emacs-lisp code blocks

(defun org-completion-at-point ()
  (let ((element (org-element-at-point)))
    (when (member (org-element-property :language element)
		  '("emacs-lisp" "elisp"))
      (funcall #'elisp-completion-at-point))))
(add-hook 'completion-at-point-functions 'org-completion-at-point nil t)

That’s it, this has fixed completion within source blocks for emacs-lisp. Check it by placing you cursor after (def in the following source block and pressing C-M-i:

(def

Load the above function and automatically activate it within source blocks only, as follows:

(add-hook 'org-mode-hook (lambda ()
 (add-hook 'completion-at-point-functions 'org-completion-at-point nil t)))

On-the-fly documentation (signature hints)

For a selection of languages, the org-eldoc package, part of org-contrib, should already provide on-the-fly documentation within source blocks (the rest of this article assumes that the org-contrib is available). When activating the org-contrib library it automatically adds org-eldoc-load to the org-mode-hook. However, currently, there is a tiny but quite severe bug in org-eldoc which can be fixed as follows:

(eldoc-add-command 'org-self-insert-command)

After installing org-contrib and evaluating the above source block, place your cursor on the block its header line, and you will see header line documentation getting printed in the echo area.

Signature hints

The org-eldoc package should already fix signature hints for emacs-lisp source blocks, but this functionality seems broken for Emacs 29. However this can be fixed by simply commenting out the first clause in the ‘cond’ of the emacs-lisp/elisp case in the original org-eldoc-documentation-function as follows (and reloading the function, try it :)

(cond
 ;; ((and (boundp 'eldoc-documentation-functions) ; Emacs>=28
 ;;       (fboundp 'elisp-eldoc-var-docstring)
 ;;       (fboundp 'elisp-eldoc-funcall))
 ;;  (let ((eldoc-documentation-functions
 ;;         '(elisp-eldoc-var-docstring elisp-eldoc-funcall)))
 ;;    (eldoc-print-current-symbol-info)))
 ((fboundp 'elisp-eldoc-documentation-function)
  (elisp-eldoc-documentation-function))
 (t            ; Emacs<25
  (let (eldoc-documentation-function)
    (eldoc-print-current-symbol-info))))

Voila! This has also fixed eldoc functionality within emacs-lisp source blocks.

PART 2: Fixing python dynamic coding assistance

The previous section showed how to fix dynamic coding assistance within org-mode emacs-lisp source code blocks. The current section shows how to fix dynamic coding assistance for python source blocks, including support for working with virtual environments.

Completion

The org-eldoc trick from the previous section can also be used to fix auto-completion inside python source blocks. However, it is handy to modify somewhat the org-completion-at-point funtion to make it easily extensible to other languages via a org-completion-functions-alist:

(defun org-completion-at-point ()
  (let* ((element (org-element-at-point))
	 (lang (org-element-property :language element)))
    (when-let (fn (alist-get lang org-completion-functions-alist
			     nil nil #'string=))
      (funcall fn))))

The python-completion-at-point function fetches completion candidates from the inferior process buffer. So python completion requires a running inferior process, hence it is required to include a :session header argument. In this article we set the header argument for the document via the #+PROPERTY keyword at the top of the document. You can optionally configure default header arguments via the org-babel-default-header-args:python variable. Finally, inspired by python-completion-at-point, a custom org-python-completion-at-point function fetches completions from the session its inferior process buffer. Like in the previous section it can be activated via a local hook (the org-completion-at-point makes sure that it only completes inside source-blocks=):

(defvar org-completion-functions-alist
  '(("emacs-lisp" . elisp-completion-at-point)
    ("python"     . org-python-completion-at-point))
  "Alist for configuring language completion functions.")


(defun org-python-completion-at-point ()
  "For org-mode modified version of `python-completion-at-point'."
  (let* ((info (org-babel-get-src-block-info))
	 (session (alist-get :session (nth 2 info)))
	 (buffer (get-buffer (org-babel-python-with-earmuffs session)))
	 (process (get-buffer-process buffer)))
    (when (and process
	       (with-current-buffer buffer
                 (python-util-comint-end-of-output-p)))
      (python-shell-completion-at-point process))))

(add-hook 'org-mode-hook (lambda ()
			   (add-hook 'completion-at-point-functions 'org-completion-at-point nil t)))

Now find that, after evaluating the first source block below, completion is working in the second source block below, by typing in that second block a . after sys, followed by pressing C-M-i

import sys
sys

On-the-fly documentation (signature hints)

Note the fix of this bug in the On-the-fly documentation (signature hints) section.

The org-eldoc package should already fix on-the-fly documentation in source blocks for several languages, but by default no support is included for python. However, the original org-eldoc-documentation-function function does not allow for easy extension to different languages. Therefore, we first modify the org-eldoc-documentation-function, and make it extensible via an org-eldoc-documentation-function-alist:

(defvar org-eldoc-documentation-function-alist
  '((("org"))
    (("emacs-lisp" "elisp") elisp-eldoc-documentation-function)
    (("c" "C") . (when (require 'c-eldoc nil t)
		   (c-eldoc-print-current-symbol-info)))
    (("css") . (when (require 'css-eldoc nil t)
                 (css-eldoc-function)))
    (("php") . (when (require 'php-eldoc nil t)
                 (php-eldoc-function)))
    (("go" "golang") . (when (require 'go-eldoc nil t)
			 (go-eldoc--documentation-function))))
  "Alist of expressions to call for different languages.
The functions need to be suitable for use in org-mode. Usually it
is quite straightforward to create a modified version from the
language its original eldoc function..")

(defun org-eldoc-documentation-function (&rest args)
  "Modified version of the original eldoc.el version.
This version allows for easily adding support for other languages
via the `org-eldoc-documentation-function-alist'.

THIS FUNCTION LACKS THE 'ELSE' CLAUSE"
  (or
   (org-eldoc-get-breadcrumb)
   (org-eldoc-get-src-header)
   (when-let (sexp (cdr (seq-find (lambda (c)
				    (member (org-eldoc-get-src-lang) (car c)))
				  org-eldoc-documentation-function-alist)))
     (when sexp
       (eval sexp)))))

Now a python documentation function, this time inspired by the python-eldoc-function, which, like the python completion function, requires a running session, could be created and added to the org-eldoc-documentation-function-alist. However, by default, the python-eldoc-function does not return signatures. Its behavior could be changed via the python-eldoc-setup-code, but here it is changed by creating separate variable to only deviate from the default behavior in org-mode buffers

(defvar org-python-eldoc-setup-code
  "def __PYDOC_get_help(obj):
    try:
        import inspect
        try:
            str_type = basestring
            argspec_function = inspect.getargspec
        except NameError:
            str_type = str
            argspec_function = inspect.getfullargspec
        if isinstance(obj, str_type):
            obj = eval(obj, globals())
        doc = inspect.signature(obj)
    except:
        doc = ''
    return str(doc)"
  "For org-mode modified version of `python-eldoc-setup-code'.
This version retrieves signature hints instead of description
hints.")

(defun org-python-eldoc--get-signature-at-point ()
  "For org-mode modified version of `python-eldoc--get-doc-at-point'.
This version uses `org-python-eldoc-setup-code' which, by
default, retrieves a signature (instead of docstring)."
  ;; narrow to block is required for
  ;; `python-eldoc--get-symbol-at-point' to work reliably
  (save-restriction
    (org-narrow-to-block)
    (let* ((input (python-eldoc--get-symbol-at-point))
	   (info (org-babel-get-src-block-info))
	   (session (alist-get :session (nth 2 info)))
	   (buffer (get-buffer (org-babel-python-with-earmuffs session)))
	   (process (get-buffer-process buffer))
	   (docstring
            (when (and input process)
              ;; Prevent resizing the echo area when iPython is
              ;; enabled.  Bug#18794.
              (python-util-strip-string
               (python-shell-send-string-no-output
		(format
		 "%s\nprint(__PYDOC_get_help(%s))"
		 org-python-eldoc-setup-code
		 ;; "help(%s)\n"
		 (python-shell--encode-string input))
		process)))))
      (unless (string-empty-p docstring)
	docstring))))

(add-to-list 'org-eldoc-documentation-function-alist
	     '(("python") org-python-eldoc--get-signature-at-point))

After evaluating the source blocks above (including the code to fix the org-eldoc bug) and evaluating the first block below, you will find that Emacs now shows signature hints when placing your cursor after chdir in the second block below (also try typing a ( after it)

import os
os.chdir

Virtual environments

If you would like the session to use some virtual environment then configure the virtual environments interpreter via the :python keyword.

To test it, first create a virtual environment for example by evaluating the following code block

mkdir -p ~/.virtualenvs
cd ~/.virtualenvs
python -m venv testenv
source testenv/bin/activate
pip install num2words

After evaluating the above code block and the first source block below, type 2 after num2words.num in the second source block below and press C-M-i. Then type a ( to find that eldoc functionality works also

import num2words
num2words.num2

Note Obviously, different environments can not share the same sessions

Implementing org static coding assistance with anaconda-mode

Here we implement static coding assistance in an org buffer for a single virtual environment (and ‘assuming’ that the org buffer is the only file in the project). As the LSP protocol is difficult to hack on, we simply use anaconda-mode (which is equivalent to using pylsp as they both rely jedi for completion, signature hints and goto definition). We use a simple trick to make anaconda-mode work well with org buffers; we simply comment out the non-python lines before sending the buffer contents to the jedi server (as it works even better, we actually replace non-python lines by a line of line length 79 of only # characters). As buffer positions are expressed in line an column numbers (i.e. not by point), the extra buffer contents does not affect the jedi/flymake functionality. We keep it simple, and assume that all python blocks are part of the code (you can simply modify/extend the ‘filter’ if that is not the case for you):

(defun org-babel-python-filter (contents)
  "Comment out lines outside of python src-blocks.
The filter does not differentiate code blocks with different
session/kernel/tangle values."
  (let ((lines (split-string contents "\n"))
	(replace (make-string 79 (string-to-char "#")))
        in)
    (mapconcat #'identity
               (mapcar (lambda (line)
                         (let (l)
                           (when (string-match-p "^#\\+end_src" line)
                             (setq in nil))
                           (setq l (if in line replace))
                           (when (string-match-p "^#\\+begin_src python" line)
                             (setq in t))
                           l))
                       lines)
               "\n")))

(with-eval-after-load 'anaconda-mode
  ;; Modified version of original `anaconda-mode-jsonrpc-request-data'.
  ;; This version passes the transformed contents instead.
  (defun anaconda-mode-jsonrpc-request-data (command)
    "Prepare buffer data for COMMAND call."
    `((jsonrpc . "2.0")
      (id . 1)
      (method . ,command)
      (params . ((source . ,(let ((buffer-string (buffer-substring-no-properties
						  (point-min) (point-max))))
			      (if (and (eq major-mode 'org-mode)
				       (nth 1 (org-babel-params-from-properties "python")))
				  (org-babel-python-filter buffer-string)
				buffer-string)))
		 (line . ,(line-number-at-pos (point)))
		 (column . ,(- (point) (line-beginning-position)))
		 (path . ,(pythonic-python-readable-file-name buffer-file-name)))))))

(defun org-python-eldoc-function (&rest args)
  (if (and (boundp 'anaconda-mode) anaconda-mode)
      (apply #'anaconda-mode-eldoc-function args)
    (org-python-eldoc--get-signature-at-point)))

(add-to-list 'org-eldoc-documentation-function-alist
	     '(("python") . #'org-python-eldoc-function))

After evaluating the code above, and then activating anaconda-mode, insert a ( directly after os.getenv in the code-block below, and notice the signature hint in the echo area. Note that now it was not necessary to evaluate the import statement first as anaconda-mode does static analysis.

import os
os.getenv
x = "awesome"

Also, place your cursor on os in the code block above and do M-x anaconda-mode-find-assignments or M-x anaconda-mode-find-definitions to navigate to the os module source.

Finally, place your cursor directly after x in the code block below and again do =M-x anaconda-mode-find-assignments to find that your cursor jumps to the location where x got assigned

x

Unfortunately, when using company-anaconda we find that it also shows python candidates outside of source blocks, for example type . after the os below

os

Therefore, we have to create an ‘org’ alternative for the company-anaconda backend which will suggest python-candidates only when the cursor is inside a python code block. For that we simple modify the company-anaconda function.

(defun org-company-anaconda (command &optional arg &rest _args)
  "Anaconda backend for company-mode in org buffers.
See `company-backends' for more info about COMMAND and ARG."
  (interactive (list 'interactive))
  (require 'company-anaconda)
  (cl-case command
    (interactive (company-begin-backend 'company-anaconda))
    (prefix (company-anaconda-prefix))
    (candidates (let ((element (org-element-at-point)))
		  (when (member (org-element-property :language element)
				'("python"))
		    (cons :async
			  (let ((given-prefix (s-chop-suffix (company-grab-symbol) arg)))
			    (lambda (callback)
			      (company-anaconda-candidates callback given-prefix)))))))
    (doc-buffer (company-anaconda-doc-buffer arg))
    (meta (company-anaconda-meta arg))
    (annotation (funcall company-anaconda-annotation-function arg))
    (location (company-anaconda-location arg))
    (ignore-case company-anaconda-case-insensitive)
    (sorted t)))

(autoload 'org-company-anaconda "company-anaconda")

Virtual environment

Simply activating anaconda-mode would activate it in your global python environment. If you would like to use anaconda-mode with some virtual environment, then simply set python-shell-virtualenv-root before activating anaconda-mode. Alhtough it would probably be not too much work to extend the above code, and modify anaconda-mode, to support multiple virtual environments in a buffer, here, we simply limit the functionality to only support a single virtual environment per org buffer.

org hook

Finally, we could make anaconda-mode get activated in some virtual environment if the org buffer defines a :python header-arg globally (see top of this file).

(defun org-maybe-activate-python-tools ()
  "Activate python tools if a kernel has been defined.
The kernel must be defined via a #+PROPERTY line.

This function is meant to be used via the `org-mode-hook'."
  (when-let (py-header-args (nth 1 (org-babel-params-from-properties "python")))
    (when-let (interpreter (alist-get :python py-header-args))
      (setq python-shell-virtualenv-root interpreter))

    (with-eval-after-load 'company
      (make-variable-buffer-local 'company-backends)
      ;; (add-to-list 'company-backends '(company-anaconda :with company-capf)))
      (add-to-list 'company-backends 'org-company-anaconda))
    ;; (setq-local eldoc-documentation-strategy 'eldoc-documentation-enthusiast)
    (anaconda-mode)))

(add-hook 'org-mode-hook #'org-maybe-activate-python-tools)

After evaluating the above source block, close and find again this document, and type a . after num2words to find that anaconda-mode now uses the correct virtual environment

import num2words
num2words

PART 3: Coding assistance for Emacs Jupyter

For languages with Jupyter support, the easiest way to work with source blocks is by using emacs-jupyter. Its README file explains how to use it. This package comes with completion and documentation (although not on-the-fly) inside source blocks and special edit buffers by default. Here we assume that you are using (org-babel-jupyter-override-src-block "python")

When using the ‘override’, the main difference with using python directly is that the :python header arg has no effect. Instead jupyter expects a :kernel header arg. For python, you can read here how to install a kernel in some virtual environment.

source ~/.virtualenvs/testenv/bin/activate
pip install ipykernel
python -m ipykernel install --user --name testenv
(jupyter-available-kernelspecs t)

Virtual environment

Now let’s update our org-maybe-activate-python-tools function to make it look for the :kernel header arg first if the jupyter package has been activated (so that the jupyter-kernelspec-plist variable is defined), and otherwise look for the :python arg for auto configuring the environment for anaconda-mode

(defun org-maybe-activate-python-tools ()
  "Activate python tools if a kernel has been defined.
The kernel must be defined via a #+PROPERTY line.

This function is meant to be used via the `org-mode-hook'."
  (when-let (py-header-args (nth 1 (org-babel-params-from-properties "python")))
    (when-let ((interpreter (if-let (kernel (and (fboundp 'jupyter-kernelspec-plist)
						 (alist-get :kernel py-header-args)))
				(elt (plist-get (jupyter-kernelspec-plist (jupyter-get-kernelspec kernel))
						:argv)
				     0)
			      (alist-get :python py-header-args))))
      (setq python-shell-virtualenv-root interpreter))


    (with-eval-after-load 'company
      (make-variable-buffer-local 'company-backends)
      ;; (add-to-list 'company-backends '(company-anaconda :with company-capf)))
      (add-to-list 'company-backends 'org-company-anaconda))
    ;; (setq-local eldoc-documentation-strategy 'eldoc-documentation-enthusiast)
    (anaconda-mode)))

(add-hook 'org-mode-hook #'org-maybe-activate-python-tools)

Now close and find again this document and insert a . after num2words in the source block below to find that anaconda-mode now uses the correct virtual environment

import num2words
num2words

Python/Jupyter compatibility

Although we have fixed the static coding assistance to always use the correct virtual environment (provided that emacs-jupyter has been activated), we did not yet fix it for the (evaluation of the) source blocks themselves (for example after evaluating (org-babel-jupyter-restore-src-block "python")). Therefore, let’s fix the org-babel-execute:python function to always use the ‘correct’ virtual environment

(defun org-babel-execute:python (body params)
  "Execute a block of Python code with Babel.
This function is called by `org-babel-execute-src-block'."
  (let* ((org-babel-python-command
	  ;; here we duplicate the 'if-let' from
	  ;; 'org-maybe-activate-python-tools', we could also decide
	  ;; to extract it into a separete function
	  (or (if-let (kernel (and (fboundp 'jupyter-kernelspec-plist)
				   (alist-get :kernel py-header-args)))
		  (elt (plist-get (jupyter-kernelspec-plist (jupyter-get-kernelspec kernel))
						:argv)
		       0)
		(alist-get :python params))
	      org-babel-python-command))
	 (session (org-babel-python-initiate-session
		   (cdr (assq :session params))))
         (result-params (cdr (assq :result-params params)))
         (result-type (cdr (assq :result-type params)))
	 (return-val (when (eq result-type 'value)
		       (cdr (assq :return params))))
	 (preamble (cdr (assq :preamble params)))
	 (async (org-babel-comint-use-async params))
         (full-body
	  (concat
	   (org-babel-expand-body:generic
	    body params
	    (org-babel-variable-assignments:python params))
	   (when return-val
	     (format (if session "\n%s" "\nreturn %s") return-val))))
         (result (org-babel-python-evaluate
		  session full-body result-type
		  result-params preamble async)))
    (org-babel-reassemble-table
     result
     (org-babel-pick-name (cdr (assq :colname-names params))
			  (cdr (assq :colnames params)))
     (org-babel-pick-name (cdr (assq :rowname-names params))
			  (cdr (assq :rownames params))))))

PART 4: Fixing coding assistance in special editing buffers

In the previous articles we have fixed ‘in-org’ coding assistance. However, except for the smallest edits, editing is better done in a special editing buffer. Therefore, in this article we will fix coding assistance in special editing buffers.

Dynamic coding assistance

The emacs-jupyter package, provides dynamic coding assistance for special editing buffers by default. To fix coding assistance in the special edit buffer, just associate the buffer with the correct python project via the org-babel-edit-prep:python function

(defun org-babel-edit-prep:python (info)
  (setq python-shell-buffer-name (alist-get :session (nth 2 info))))

Static coding assistance

To get static coding assistance in the special editing buffer, we could just configure some static coding assistance tool to get activated automatically in buffers os the major mode associated with the language. However, when using the static analysis tool in special editing buffers, a problem is that the tool will only be aware of the code contents in the special editing buffer without context, i.e. only the code of the current source block being edited and not that of other source blocks.

A nice solution to make the special editing buffer ‘context aware’ has been proposed by the user karthink here. Although that solution is probably not yet ready to get merged into org-mode, it is already usable and does it’s job well.

Caveats

There are a few caveats when using karthink’s org-src-context.el solution:

  • due to the ‘limited’ functionality of the org-babel-tangle-collect-blocks function, org-src-context.el does not add collect context by :session but by :tangle. Therefore, if working with multiple sections, you should additionally use :tangle arguments to ‘indicate/control’ which blocks belong to which context. When using org-src-context.el to edit a block without :tangle argument, then it includes all code blocks of the same language as ‘context’.
  • although ‘jump to definition’ works fine for definitions not part of the buffer, it works a bit problematic for definitions (things defined) within the same buffer
  1. also

    • when using tangle detangle -> duplicate headings
    (defun org-edit-src-tangle ()
      (interactive)
      (let* ((headline (org-get-heading t t t t))
    	 (line (print (thing-at-point 'line t)))
    	 (column (current-column))
    	 (info (org-babel-get-src-block-info 'no-eval))
    	 (params (nth 2 info))
    	 (offset (count-lines (nth 5 info) (point)))
    	 (session (cdr (assq :session params)))
             (jupyterp (advice--p (advice--symbol-function 'org-babel-execute:python)))
             (client-buffer (if jupyterp
    			    (org-babel-jupyter-initiate-session session args)
    			  (org-babel-python-initiate-session session)))
    	 (tangle-dest (alist-get :tangle params)))
        (org-babel-tangle '(16) nil "python")
        (find-file tangle-dest)
        (if jupyterp
            (jupyter-repl-associate-buffer client-buffer) ;from org-babel-edit-prep:jupyter
          (setq python-shell-buffer-name (substring client-buffer 1 -1)))
        (when headline
          (search-forward (concat "*" headline)))
        (search-forward line)
        (goto-char (match-beginning 0))
        (recenter offset)
        (forward-char column)))
    
    ((nil (eglot-workspace-configuration
           :pylsp (:plugins
                   (:jedi
    		(:environment "~/.virtualenvs/testenv"))))))
    

Footnotes

1 This was mentioned somewhere in the eglot contribution notes, but I can not find it anymore

2 You can find a discussion about some differences at https://github.com/joaotavora/eglot/issues/180

This post is licensed under CC BY 4.0 by the author.

Trending Tags