lotsa changes and inclusion of elpy
[emacs.git] / .emacs.d / elisp / pyvenv / pyvenv.el
1 ;;; pyvenv.el --- Python virtual environment interface -*- lexical-binding: t -*-
2
3 ;; Copyright (C) 2013 Jorgen Schaefer <contact@jorgenschaefer.de>
4
5 ;; Author: Jorgen Schaefer <contact@jorgenschaefer.de>
6 ;; URL: http://github.com/jorgenschaefer/pyvenv
7 ;; Version: 20140314.110
8 ;; X-Original-Version: 1.1
9
10 ;; This program is free software; you can redistribute it and/or
11 ;; modify it under the terms of the GNU General Public License
12 ;; as published by the Free Software Foundation; either version 3
13 ;; of the License, or (at your option) any later version.
14
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
22
23 ;;; Commentary:
24
25 ;; This is a simple global minor mode which will replicate the changes
26 ;; done by virtualenv activation inside Emacs.
27
28 ;; The main entry points are `pyvenv-activate', which queries the user
29 ;; for a virtual environment directory to activate, and
30 ;; `pyvenv-workon', which queries for a virtual environment in
31 ;; $WORKON_HOME (from virtualenvwrapper.sh).
32
33 ;; If you want your inferior Python processes to be restarted
34 ;; automatically when you switch your virtual environment, add
35 ;; `pyvenv-restart-python' to `pyvenv-post-activate-hooks'.
36
37 ;;; Code:
38
39 (require 'json)
40
41 ;; API for other libraries or user customization.
42
43 (defvar pyvenv-virtual-env nil
44 "The current virtual environment.
45
46 Do not set this variable directly; use `pyvenv-activate' or
47 `pyvenv-workon'.")
48
49 (defvar pyvenv-virtual-env-name nil
50 "The name of the current virtual environment.
51
52 This is usually the base name of `pyvenv-virtual-env'.")
53
54 (defvar pyvenv-pre-activate-hooks nil
55 "Hooks run before a virtual environment is activated.
56
57 `pyvenv-virtual-env' is already set.")
58
59 (defvar pyvenv-post-activate-hooks nil
60 "Hooks run after a virtual environment is activated.
61
62 `pyvenv-virtual-env' is set.")
63
64 (defvar pyvenv-pre-deactivate-hooks nil
65 "Hooks run before a virtual environment is deactivated.
66
67 `pyvenv-virtual-env' is set.")
68
69 (defvar pyvenv-post-deactivate-hooks nil
70 "Hooks run after a virtual environment is deactivated.
71
72 `pyvenv-virtual-env' is still set.")
73
74 (defvar pyvenv-workon nil
75 "A variable requesting a specific virtualenv.
76
77 This is meant to be set in file- or directory-local variables.
78
79 When `pyvenv-mode' is enabled, pyvenv will switch to this
80 virtualenv. If a virtualenv is already enabled, it will ask first.")
81 (put 'pyvenv-workon 'safe-local-variable #'stringp)
82
83 (defgroup pyvenv nil
84 "Python Virtual Environment Interface."
85 :prefix "pyvenv-"
86 :group 'languages)
87
88 (defcustom pyvenv-mode-line-indicator '(pyvenv-virtual-env-name
89 ("[" pyvenv-virtual-env-name "] "))
90 "How `pyvenv-mode' will indicate the current environment in the mode line."
91 :group 'pyvenv)
92
93 ;; Internal code.
94
95 (defvar pyvenv-old-process-environment nil
96 "The old process environment before the last activate.")
97
98 (defvar pyvenv-old-exec-path nil
99 "The old exec path before the last activate.")
100
101 ;;;###autoload
102 (defun pyvenv-activate (directory)
103 "Activate the virtual environment in DIRECTORY."
104 (interactive "DActivate venv: ")
105 (setq directory (expand-file-name directory))
106 (pyvenv-deactivate)
107 (setq pyvenv-virtual-env directory
108 pyvenv-virtual-env-name (file-name-base directory))
109 ;; Preserve variables from being overwritten.
110 (let ((old-exec-path exec-path)
111 (old-process-environment process-environment))
112 (unwind-protect
113 (pyvenv-run-virtualenvwrapper-hook "pre_activate" pyvenv-virtual-env)
114 (setq exec-path old-exec-path
115 process-environment old-process-environment)))
116 (run-hooks 'pyvenv-pre-activate-hooks)
117 (setq pyvenv-old-exec-path exec-path
118 pyvenv-old-process-environment process-environment
119 ;; For some reason, Emacs adds some directories to `exec-path'
120 ;; but not to `process-environment'?
121 exec-path (cons (format "%s/bin" directory)
122 exec-path)
123 process-environment (append
124 (list
125 (format "VIRTUAL_ENV=%s" directory)
126 (format "PATH=%s" (mapconcat (lambda (x)
127 (or x "."))
128 exec-path
129 ":"))
130 ;; No "=" means to unset
131 "PYTHONHOME")
132 process-environment)
133 )
134 (pyvenv-run-virtualenvwrapper-hook "post_activate")
135 (run-hooks 'pyvenv-post-activate-hooks))
136
137 ;;;###autoload
138 (defun pyvenv-deactivate ()
139 "Deactivate any current virtual environment."
140 (interactive)
141 (when pyvenv-virtual-env
142 (pyvenv-run-virtualenvwrapper-hook "pre_deactivate")
143 (run-hooks 'pyvenv-pre-deactivate-hooks))
144 (when pyvenv-old-process-environment
145 (setq process-environment pyvenv-old-process-environment
146 pyvenv-old-process-environment nil))
147 (when pyvenv-old-exec-path
148 (setq exec-path pyvenv-old-exec-path
149 pyvenv-old-exec-path nil))
150 (when pyvenv-virtual-env
151 ;; Make sure this does not change `exec-path', as $PATH is
152 ;; different
153 (let ((old-exec-path exec-path)
154 (old-process-environment process-environment))
155 (unwind-protect
156 (pyvenv-run-virtualenvwrapper-hook "post_deactivate"
157 pyvenv-virtual-env)
158 (setq exec-path old-exec-path
159 process-environment old-process-environment)))
160 (run-hooks 'pyvenv-post-deactivate-hooks))
161 (setq pyvenv-virtual-env nil
162 pyvenv-virtual-env-name nil))
163
164 (defvar pyvenv-workon-history nil
165 "Prompt history for `pyvenv-workon'.")
166
167 ;;;###autoload
168 (defun pyvenv-workon (name)
169 "Activate a virtual environment from $WORKON_HOME."
170 (interactive
171 (list
172 (completing-read "Work on: " (pyvenv-virtualenv-list)
173 nil t nil 'pyvenv-workon-history nil nil)))
174 (when (not (or (equal name "")
175 ;; Some completion frameworks can return nil for the
176 ;; default, see
177 ;; https://github.com/jorgenschaefer/elpy/issues/144
178 (equal name nil)))
179 (pyvenv-activate (format "%s/%s"
180 (or (getenv "WORKON_HOME")
181 "~/.virtualenvs")
182 name))))
183
184 (defun pyvenv-virtualenv-list ()
185 "Prompt the user for a name in $WORKON_HOME."
186 (let ((workon-home (or (getenv "WORKON_HOME")
187 "~/.virtualenvs"))
188 (result nil))
189 (when (not (file-directory-p workon-home))
190 (error "Can't find a workon home directory, set $WORKON_HOME"))
191 (dolist (name (directory-files workon-home))
192 (when (file-exists-p (format "%s/%s/bin/activate"
193 workon-home name))
194 (setq result (cons name result))))
195 (sort result (lambda (a b)
196 (string-lessp (downcase a)
197 (downcase b))))))
198
199 ;;;###autoload
200 (define-minor-mode pyvenv-mode
201 "Global minor mode for pyvenv.
202
203 Will show the current virtual env in the mode line, and respect a
204 `pyvenv-workon' setting in files."
205 :global t
206 (cond
207 (pyvenv-mode
208 (add-to-list 'mode-line-misc-info pyvenv-mode-line-indicator)
209 (add-hook 'hack-local-variables-hook #'pyvenv-set-file-virtualenv))
210 ((not pyvenv-mode)
211 (setq mode-line-misc-info (delete pyvenv-mode-line-indicator
212 mode-line-misc-info))
213 (remove-hook 'hack-local-variables-hook #'pyvenv-set-file-virtualenv))))
214
215 (defun pyvenv-set-file-virtualenv ()
216 "If `pyvenv-workon' is set, switch to that virtual env."
217 (cond
218 ((and pyvenv-workon (not pyvenv-virtual-env))
219 (pyvenv-workon pyvenv-workon))
220 ((and pyvenv-workon (not (equal pyvenv-workon pyvenv-virtual-env-name)))
221 (when (y-or-n-p (format "Switch to virtual env %s (currently %s)? "
222 pyvenv-workon pyvenv-virtual-env))
223 (pyvenv-workon pyvenv-workon)))))
224
225 (defvar pyvenv-virtualenvwrapper-python
226 (or (getenv "VIRTUALENVWRAPPER_PYTHON")
227 (executable-find "python"))
228 "The python process which has access to the virtualenvwrapper module.
229
230 This should be $VIRTUALENVWRAPPER_PYTHON outside of Emacs, but
231 virtualenvwrapper.sh does not export that variable, so we do not
232 usually see it.")
233
234 (defun pyvenv-run-virtualenvwrapper-hook (hook &rest args)
235 "Run a virtualenvwrapper hook, and update the environment.
236
237 This will run a virtualenvwrapper hook and update the local
238 environment accordingly.
239
240 CAREFUL! This will modify your `process-environment' and
241 `exec-path'."
242 (when (getenv "VIRTUALENVWRAPPER_LOG_DIR")
243 (with-temp-buffer
244 (let ((tmpfile (make-temp-file "pyvenv-virtualenvwrapper-")))
245 (unwind-protect
246 (progn
247 (apply #'call-process
248 pyvenv-virtualenvwrapper-python
249 nil t nil
250 "-c"
251 "from virtualenvwrapper.hook_loader import main; main()"
252 "--script" tmpfile
253 (if (getenv "HOOK_VERBOSE_OPTION")
254 (cons (getenv "HOOK_VERBOSE_OPTION")
255 (cons hook args))
256 (cons hook args)))
257 (call-process-shell-command
258 (format ". '%s' ; echo ; echo =-=-= ; python -c \"import os, json ; print json.dumps(dict(os.environ))\""
259 tmpfile)
260 nil t nil))
261 (delete-file tmpfile)))
262 (goto-char (point-min))
263 (when (re-search-forward "\n=-=-=\n" nil t)
264 (let ((output (buffer-substring (point-min)
265 (match-beginning 0))))
266 (when (> (length output) 0)
267 (with-help-window "*Virtualenvwrapper Hook Output*"
268 (with-current-buffer "*Virtualenvwrapper Hook Output*"
269 (let ((inhibit-read-only t))
270 (erase-buffer)
271 (insert
272 (format
273 "Output from the virtualenvwrapper hook %s:\n\n"
274 hook)
275 output))))))
276 (dolist (binding (json-read))
277 (let ((env (format "%s=%s" (car binding) (cdr binding))))
278 (when (not (member env process-environment))
279 (setq process-environment (cons env process-environment))))
280 (when (eq (car binding) 'PATH)
281 (setq exec-path (split-string (cdr binding) ":"))))))))
282
283 ;;;###autoload
284 (defun pyvenv-restart-python ()
285 "Restart Python inferior processes."
286 (interactive)
287 (dolist (buf (buffer-list))
288 (with-current-buffer buf
289 (when (and (eq major-mode 'inferior-python-mode)
290 (get-buffer-process buf))
291 (let ((cmd (combine-and-quote-strings (process-command
292 (get-buffer-process buf))))
293 (dedicated (if (string-match "\\[.*\\]$" (buffer-name buf))
294 t
295 nil))
296 (show nil))
297 (delete-process (get-buffer-process buf))
298 (goto-char (point-max))
299 (insert "\n\n"
300 "###\n"
301 (format "### Restarting in virtual env %s (%s)\n"
302 pyvenv-virtual-env-name pyvenv-virtual-env)
303 "###\n"
304 "\n\n")
305 (run-python cmd dedicated show)
306 (goto-char (point-max)))))))
307
308 ;;; Compatibility
309
310 (when (not (fboundp 'file-name-base))
311 ;; Emacs 24.3
312 (defun file-name-base (&optional filename)
313 "Return the base name of the FILENAME: no directory, no extension.
314 FILENAME defaults to `buffer-file-name'."
315 (file-name-sans-extension
316 (file-name-nondirectory (or filename (buffer-file-name)))))
317 )
318
319 (provide 'pyvenv)
320 ;;; pyvenv.el ends here