I would like emacs-jedi to detect when I am editing files in different projects, and use the corresponding virtualenv if it is available. By convention my virtualenvs have the same name as my projects. They are located in $HOME/.virtualenvs/
I found kenobi.el but it assumes that virtualenvs are found in the bin directory in the project root. It also has a couple of other features that I don't need at all.
With inspiration from kenobi.el, I wrote the following initialisation for jedi. It works pretty well, but not perfectly.
If I import library A
from my project, and A
imports B
. I am able to jump into definitions defined by A
, but once there, I'm not able to continue jumping into definitions from B
.
My initialisation:
(defun project-directory (buffer-name)
(let ((git-dir (file-name-directory buffer-name)))
(while (and (not (file-exists-p (concat git-dir ".git")))
git-dir)
(setq git-dir
(if (equal git-dir "/")
nil
(file-name-directory (directory-file-name git-dir)))))
git-dir))
(defun project-name (buffer-name)
(let ((git-dir (project-directory buffer-name)))
(if git-dir
(file-name-nondirectory
(directory-file-name git-dir))
nil)))
(defun virtualenv-directory (buffer-name)
(let ((venv-dir (expand-file-name
(concat "~/.virtualenvs/" (project-name buffer-name)))))
(if (and venv-dir (file-exists-p venv-dir))
venv-dir
nil)))
(defun jedi-setup-args ()
(let ((venv-dir (virtualenv-directory buffer-file-name)))
(when venv-dir
(set (make-local-variable 'jedi:server-args) (list "--virtual-env" venv-dir)))))
(setq jedi:setup-keys t)
(setq jedi:complete-on-dot t)
(add-hook 'python-mode-hook 'jedi-setup-args)
(add-hook 'python-mode-hook 'jedi:setup)
What is wrong with how I initialise jedi?
I have now settled on a solution that uses the virtualenvwrapper ELPA package to activate virtualenvs, allowing emacs-jedi to pick up the virtualenv path from the VIRTUAL_ENV environment variable.
Here is a complete, working, emacs-jedi initialisation:
(defun project-directory (buffer-name)
"Return the root directory of the project that contain the
given BUFFER-NAME. Any directory with a .git or .jedi file/directory
is considered to be a project root."
(interactive)
(let ((root-dir (file-name-directory buffer-name)))
(while (and root-dir
(not (file-exists-p (concat root-dir ".git")))
(not (file-exists-p (concat root-dir ".jedi"))))
(setq root-dir
(if (equal root-dir "/")
nil
(file-name-directory (directory-file-name root-dir)))))
root-dir))
(defun project-name (buffer-name)
"Return the name of the project that contain the given BUFFER-NAME."
(let ((root-dir (project-directory buffer-name)))
(if root-dir
(file-name-nondirectory
(directory-file-name root-dir))
nil)))
(defun jedi-setup-venv ()
"Activates the virtualenv of the current buffer."
(let ((project-name (project-name buffer-file-name)))
(when project-name (venv-workon project-name))))
(setq jedi:setup-keys t)
(setq jedi:complete-on-dot t)
(add-hook 'python-mode-hook 'jedi-setup-venv)
(add-hook 'python-mode-hook 'jedi:setup)
Remember that you have to install virtualenvwrapper first.
Read the virtualenvwrapper documentation for an alternative way of automatically activating project virtual evironments. In short you can create a .dir-locals.el
file in the root of your project, with the following content:
((python-mode . ((project-venv-name . "myproject-env"))))
Change "myproject-env"
to the name of your virtualenv and activate the virtualenvironment using the python-mode
hook:
(add-hook 'python-mode-hook (lambda ()
(hack-local-variables)
(venv-workon project-venv-name)))
(add-hook 'python-mode-hook 'jedi:setup)