]>
Commit | Line | Data |
---|---|---|
1 | ;;; pyvenv.el --- Python virtual environment interface -*- lexical-binding: t -*- | |
2 | ||
3 | ;; Copyright (C) 2013-2017 Jorgen Schaefer <contact@jorgenschaefer.de> | |
4 | ||
5 | ;; Author: Jorgen Schaefer <contact@jorgenschaefer.de> | |
6 | ;; URL: http://github.com/jorgenschaefer/pyvenv | |
7 | ;; Version: 1.21 | |
8 | ;; Keywords: Python, Virtualenv, Tools | |
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 'eshell) | |
40 | (require 'json) | |
41 | (require 'subr-x) | |
42 | ||
43 | ;; User customization | |
44 | ||
45 | (defgroup pyvenv nil | |
46 | "Python Virtual Environment Interface." | |
47 | :prefix "pyvenv-" | |
48 | :group 'languages) | |
49 | ||
50 | (defcustom pyvenv-workon nil | |
51 | "The intended virtualenv in the virtualenvwrapper directory. | |
52 | ||
53 | This is rarely useful to set globally. Rather, set this in file- | |
54 | or directory-local variables using \\[add-file-local-variable] or | |
55 | \\[add-dir-local-variable]. | |
56 | ||
57 | When `pyvenv-mode' is enabled, pyvenv will switch to this | |
58 | virtualenv. If a virtualenv is already enabled, it will ask first." | |
59 | :type 'pyvenv-workon | |
60 | :safe #'stringp | |
61 | :group 'pyvenv) | |
62 | ||
63 | (defcustom pyvenv-activate nil | |
64 | "The intended virtualenv directory. | |
65 | ||
66 | This is rarely useful to set globally. Rather, set this in file- | |
67 | or directory-local variables using \\[add-file-local-variable] or | |
68 | \\[add-dir-local-variable]. | |
69 | ||
70 | When `pyvenv-mode' is enabled, pyvenv will switch to this | |
71 | virtualenv. If a virtualenv is already enabled, it will ask first." | |
72 | :type 'directory | |
73 | :safe #'stringp | |
74 | :group 'pyvenv) | |
75 | ||
76 | (defcustom pyvenv-tracking-ask-before-change nil | |
77 | "Non-nil means pyvenv will ask before automatically changing a virtualenv. | |
78 | ||
79 | This can happen when a new file is opened with a buffer-local | |
80 | value (from file-local or directory-local variables) for | |
81 | `pyvenv-workon' or `pyvenv-workon', or if `pyvenv-tracking-mode' | |
82 | is active, after every command." | |
83 | :type 'boolean | |
84 | :group 'pyvenv) | |
85 | ||
86 | (defcustom pyvenv-virtualenvwrapper-python | |
87 | (or (getenv "VIRTUALENVWRAPPER_PYTHON") | |
88 | (executable-find "python3") | |
89 | (executable-find "python") | |
90 | (executable-find "py") | |
91 | (executable-find "pythonw") | |
92 | "python") | |
93 | "The python process which has access to the virtualenvwrapper module. | |
94 | ||
95 | This should be $VIRTUALENVWRAPPER_PYTHON outside of Emacs, but | |
96 | virtualenvwrapper.sh does not export that variable. We make an | |
97 | educated guess, but that can be off." | |
98 | :type '(file :must-match t) | |
99 | :safe #'file-directory-p | |
100 | :group 'pyvenv) | |
101 | ||
102 | (defcustom pyvenv-exec-shell | |
103 | (or (executable-find "bash") | |
104 | (executable-find "sh") | |
105 | shell-file-name) | |
106 | "The path to a POSIX compliant shell to use for running | |
107 | virtualenv hooks. Useful if you use a non-POSIX shell (e.g. | |
108 | fish)." | |
109 | :type '(file :must-match t) | |
110 | :group 'pyvenv) | |
111 | ||
112 | (defcustom pyvenv-default-virtual-env-name nil | |
113 | "Default directory to use when prompting for a virtualenv directory | |
114 | in `pyvenv-activate'." | |
115 | :type 'string | |
116 | :group 'pyvenv) | |
117 | ||
118 | ;; API for other libraries | |
119 | ||
120 | (defvar pyvenv-virtual-env nil | |
121 | "The current virtual environment. | |
122 | ||
123 | Do not set this variable directly; use `pyvenv-activate' or | |
124 | `pyvenv-workon'.") | |
125 | ||
126 | (defvar pyvenv-virtual-env-path-directories nil | |
127 | "Directories added to PATH by the current virtual environment. | |
128 | ||
129 | Do not set this variable directly; use `pyvenv-activate' or | |
130 | `pyvenv-workon'.") | |
131 | ||
132 | (defvar pyvenv-virtual-env-name nil | |
133 | "The name of the current virtual environment. | |
134 | ||
135 | This is usually the base name of `pyvenv-virtual-env'.") | |
136 | ||
137 | ||
138 | (defvar pyvenv-pre-create-hooks nil | |
139 | "Hooks run before a virtual environment is created.") | |
140 | ||
141 | ||
142 | (defvar pyvenv-post-create-hooks nil | |
143 | "Hooks run after a virtual environment is created.") | |
144 | ||
145 | ||
146 | (defvar pyvenv-pre-activate-hooks nil | |
147 | "Hooks run before a virtual environment is activated. | |
148 | ||
149 | `pyvenv-virtual-env' is already set.") | |
150 | ||
151 | (defvar pyvenv-post-activate-hooks nil | |
152 | "Hooks run after a virtual environment is activated. | |
153 | ||
154 | `pyvenv-virtual-env' is set.") | |
155 | ||
156 | (defvar pyvenv-pre-deactivate-hooks nil | |
157 | "Hooks run before a virtual environment is deactivated. | |
158 | ||
159 | `pyvenv-virtual-env' is set.") | |
160 | ||
161 | (defvar pyvenv-post-deactivate-hooks nil | |
162 | "Hooks run after a virtual environment is deactivated. | |
163 | ||
164 | `pyvenv-virtual-env' is still set.") | |
165 | ||
166 | (defvar pyvenv-mode-line-indicator '(pyvenv-virtual-env-name | |
167 | ("[" pyvenv-virtual-env-name "] ")) | |
168 | "How `pyvenv-mode' will indicate the current environment in the mode line.") | |
169 | ||
170 | ;; Internal code. | |
171 | ||
172 | (defvar pyvenv-old-process-environment nil | |
173 | "The old process environment that needs to be restored after deactivating the current environment.") | |
174 | ||
175 | ||
176 | (defun pyvenv-create (venv-name python-executable) | |
177 | "Create virtualenv. VENV-NAME PYTHON-EXECUTABLE." | |
178 | (interactive (list | |
179 | (read-from-minibuffer "Name of virtual environment: ") | |
180 | (let ((dir (if pyvenv-virtualenvwrapper-python | |
181 | (file-name-directory pyvenv-virtualenvwrapper-python) | |
182 | nil)) | |
183 | (initial (if pyvenv-virtualenvwrapper-python | |
184 | (file-name-base pyvenv-virtualenvwrapper-python) | |
185 | nil))) | |
186 | (read-file-name "Python interpreter to use: " dir nil nil initial)))) | |
187 | (let ((venv-dir (concat (file-name-as-directory (pyvenv-workon-home)) | |
188 | venv-name))) | |
189 | (unless (file-exists-p venv-dir) | |
190 | (run-hooks 'pyvenv-pre-create-hooks) | |
191 | (cond | |
192 | ((executable-find "virtualenv") | |
193 | (with-current-buffer (generate-new-buffer "*virtualenv*") | |
194 | (call-process "virtualenv" nil t t | |
195 | "-p" python-executable venv-dir) | |
196 | (display-buffer (current-buffer)))) | |
197 | ((= 0 (call-process python-executable nil nil nil | |
198 | "-m" "venv" "-h")) | |
199 | (with-current-buffer (generate-new-buffer "*venv*") | |
200 | (call-process python-executable nil t t | |
201 | "-m" "venv" venv-dir) | |
202 | (display-buffer (current-buffer)))) | |
203 | (t | |
204 | (error "Pyvenv necessitates the 'virtualenv' python package"))) | |
205 | (run-hooks 'pyvenv-post-create-hooks)) | |
206 | (pyvenv-activate venv-dir))) | |
207 | ||
208 | ||
209 | ;;;###autoload | |
210 | (defun pyvenv-activate (directory) | |
211 | "Activate the virtual environment in DIRECTORY." | |
212 | (interactive (list (read-directory-name "Activate venv: " nil nil nil | |
213 | pyvenv-default-virtual-env-name))) | |
214 | (setq directory (expand-file-name directory)) | |
215 | (pyvenv-deactivate) | |
216 | (setq pyvenv-virtual-env (file-name-as-directory directory) | |
217 | pyvenv-virtual-env-name (file-name-nondirectory | |
218 | (directory-file-name directory)) | |
219 | python-shell-virtualenv-path directory | |
220 | python-shell-virtualenv-root directory) | |
221 | ;; Set venv name as parent directory for generic directories or for | |
222 | ;; the user's default venv name | |
223 | (when (or (member pyvenv-virtual-env-name '("venv" ".venv" "env" ".env")) | |
224 | (and pyvenv-default-virtual-env-name | |
225 | (string= pyvenv-default-virtual-env-name | |
226 | pyvenv-virtual-env-name))) | |
227 | (setq pyvenv-virtual-env-name | |
228 | (file-name-nondirectory | |
229 | (directory-file-name | |
230 | (file-name-directory | |
231 | (directory-file-name directory)))))) | |
232 | (pyvenv-run-virtualenvwrapper-hook "pre_activate" nil pyvenv-virtual-env) | |
233 | (run-hooks 'pyvenv-pre-activate-hooks) | |
234 | (setq pyvenv-virtual-env-path-directories (pyvenv--virtual-env-bin-dirs directory) | |
235 | ;; Variables that must be reset during deactivation. | |
236 | pyvenv-old-process-environment (list (cons "PYTHONHOME" (getenv "PYTHONHOME")) | |
237 | (cons "VIRTUAL_ENV" nil))) | |
238 | (setenv "VIRTUAL_ENV" directory) | |
239 | (setenv "PYTHONHOME" nil) | |
240 | (pyvenv--add-dirs-to-PATH pyvenv-virtual-env-path-directories) | |
241 | (pyvenv-run-virtualenvwrapper-hook "post_activate" 'propagate-env) | |
242 | (run-hooks 'pyvenv-post-activate-hooks)) | |
243 | ||
244 | ;;;###autoload | |
245 | (defun pyvenv-deactivate () | |
246 | "Deactivate any current virtual environment." | |
247 | (interactive) | |
248 | (when pyvenv-virtual-env | |
249 | (pyvenv-run-virtualenvwrapper-hook "pre_deactivate" 'propagate-env) | |
250 | (run-hooks 'pyvenv-pre-deactivate-hooks) | |
251 | (pyvenv--remove-dirs-from-PATH (pyvenv--virtual-env-bin-dirs pyvenv-virtual-env)) | |
252 | (dolist (envvar pyvenv-old-process-environment) | |
253 | (setenv (car envvar) (cdr envvar))) | |
254 | ;; Make sure PROPAGATE-ENV is nil here, so that it does not change | |
255 | ;; `exec-path', as $PATH is different | |
256 | (pyvenv-run-virtualenvwrapper-hook "post_deactivate" | |
257 | nil | |
258 | pyvenv-virtual-env) | |
259 | (run-hooks 'pyvenv-post-deactivate-hooks)) | |
260 | (setq pyvenv-virtual-env nil | |
261 | pyvenv-virtual-env-path-directories nil | |
262 | pyvenv-virtual-env-name nil | |
263 | python-shell-virtualenv-root nil | |
264 | python-shell-virtualenv-path nil)) | |
265 | ||
266 | (defvar pyvenv-workon-history nil | |
267 | "Prompt history for `pyvenv-workon'.") | |
268 | ||
269 | ;;;###autoload | |
270 | (defun pyvenv-workon (name) | |
271 | "Activate a virtual environment from $WORKON_HOME. | |
272 | ||
273 | If the virtual environment NAME is already active, this function | |
274 | does not try to reactivate the environment." | |
275 | (interactive | |
276 | (list | |
277 | (completing-read "Work on: " (pyvenv-virtualenv-list) | |
278 | nil t nil 'pyvenv-workon-history nil nil))) | |
279 | (unless (member name (list "" nil pyvenv-virtual-env-name)) | |
280 | (pyvenv-activate (format "%s/%s" | |
281 | (pyvenv-workon-home) | |
282 | name)))) | |
283 | ||
284 | (defun pyvenv-virtualenv-list (&optional noerror) | |
285 | "Prompt the user for a name in $WORKON_HOME. | |
286 | ||
287 | If NOERROR is set, do not raise an error if WORKON_HOME is not | |
288 | configured." | |
289 | (let ((workon-home (pyvenv-workon-home)) | |
290 | (result nil)) | |
291 | (if (not (file-directory-p workon-home)) | |
292 | (when (not noerror) | |
293 | (error "Can't find a workon home directory, set $WORKON_HOME")) | |
294 | (dolist (name (directory-files workon-home)) | |
295 | (when (or (file-exists-p (format "%s/%s/bin/activate" | |
296 | workon-home name)) | |
297 | (file-exists-p (format "%s/%s/bin/python" | |
298 | workon-home name)) | |
299 | (file-exists-p (format "%s/%s/Scripts/activate.bat" | |
300 | workon-home name)) | |
301 | (file-exists-p (format "%s/%s/python.exe" | |
302 | workon-home name))) | |
303 | (setq result (cons name result)))) | |
304 | (sort result (lambda (a b) | |
305 | (string-lessp (downcase a) | |
306 | (downcase b))))))) | |
307 | ||
308 | (define-widget 'pyvenv-workon 'choice | |
309 | "Select an available virtualenv from virtualenvwrapper." | |
310 | :convert-widget | |
311 | (lambda (widget) | |
312 | (setq widget (widget-copy widget)) | |
313 | (widget-put widget | |
314 | :args (cons '(const :tag "None" nil) | |
315 | (mapcar (lambda (env) | |
316 | (list 'const env)) | |
317 | (pyvenv-virtualenv-list t)))) | |
318 | (widget-types-convert-widget widget)) | |
319 | ||
320 | :prompt-value (lambda (_widget prompt _value _unbound) | |
321 | (let ((name (completing-read | |
322 | prompt | |
323 | (cons "None" | |
324 | (pyvenv-virtualenv-list t)) | |
325 | nil t))) | |
326 | (if (equal name "None") | |
327 | nil | |
328 | name)))) | |
329 | ||
330 | (defvar pyvenv-mode-map (make-sparse-keymap) | |
331 | "The mode keymap for `pyvenv-mode'.") | |
332 | ||
333 | (easy-menu-define pyvenv-menu pyvenv-mode-map | |
334 | "Pyvenv Menu" | |
335 | '("Virtual Envs" | |
336 | :visible pyvenv-mode | |
337 | ("Workon" | |
338 | :help "Activate a virtualenvwrapper environment" | |
339 | :filter (lambda (&optional ignored) | |
340 | (mapcar (lambda (venv) | |
341 | (vector venv `(pyvenv-workon ,venv) | |
342 | :style 'radio | |
343 | :selected `(equal pyvenv-virtual-env-name | |
344 | ,venv))) | |
345 | (pyvenv-virtualenv-list t)))) | |
346 | ["Activate" pyvenv-activate | |
347 | :help "Activate a virtual environment by directory"] | |
348 | ["Deactivate" pyvenv-deactivate | |
349 | :help "Deactivate the current virtual environment" | |
350 | :active pyvenv-virtual-env | |
351 | :suffix pyvenv-virtual-env-name] | |
352 | ["Restart Python Processes" pyvenv-restart-python | |
353 | :help "Restart all Python processes to use the current environment"])) | |
354 | ||
355 | ;;;###autoload | |
356 | (define-minor-mode pyvenv-mode | |
357 | "Global minor mode for pyvenv. | |
358 | ||
359 | Will show the current virtualenv in the mode line, and respect a | |
360 | `pyvenv-workon' setting in files." | |
361 | :global t | |
362 | (cond | |
363 | (pyvenv-mode | |
364 | (add-to-list 'mode-line-misc-info '(pyvenv-mode pyvenv-mode-line-indicator)) | |
365 | (add-hook 'hack-local-variables-hook #'pyvenv-track-virtualenv)) | |
366 | ((not pyvenv-mode) | |
367 | (setq mode-line-misc-info (delete '(pyvenv-mode pyvenv-mode-line-indicator) | |
368 | mode-line-misc-info)) | |
369 | (remove-hook 'hack-local-variables-hook #'pyvenv-track-virtualenv)))) | |
370 | ||
371 | ;;;###autoload | |
372 | (define-minor-mode pyvenv-tracking-mode | |
373 | "Global minor mode to track the current virtualenv. | |
374 | ||
375 | When this mode is active, pyvenv will activate a buffer-specific | |
376 | virtualenv whenever the user switches to a buffer with a | |
377 | buffer-local `pyvenv-workon' or `pyvenv-activate' variable." | |
378 | :global t | |
379 | (if pyvenv-tracking-mode | |
380 | (add-hook 'post-command-hook 'pyvenv-track-virtualenv) | |
381 | (remove-hook 'post-command-hook 'pyvenv-track-virtualenv))) | |
382 | ||
383 | (defun pyvenv-track-virtualenv () | |
384 | "Set a virtualenv as specified for the current buffer. | |
385 | ||
386 | If either `pyvenv-activate' or `pyvenv-workon' are specified, and | |
387 | they specify a virtualenv different from the current one, switch | |
388 | to that virtualenv." | |
389 | (cond | |
390 | (pyvenv-activate | |
391 | (when (and (not (equal (file-name-as-directory pyvenv-activate) | |
392 | pyvenv-virtual-env)) | |
393 | (or (not pyvenv-tracking-ask-before-change) | |
394 | (y-or-n-p (format "Switch to virtualenv %s (currently %s)" | |
395 | pyvenv-activate pyvenv-virtual-env)))) | |
396 | (pyvenv-activate pyvenv-activate))) | |
397 | (pyvenv-workon | |
398 | (when (and (not (equal pyvenv-workon pyvenv-virtual-env-name)) | |
399 | (or (not pyvenv-tracking-ask-before-change) | |
400 | (y-or-n-p (format "Switch to virtualenv %s (currently %s)" | |
401 | pyvenv-workon pyvenv-virtual-env-name)))) | |
402 | (pyvenv-workon pyvenv-workon))))) | |
403 | ||
404 | (defun pyvenv-run-virtualenvwrapper-hook (hook &optional propagate-env &rest args) | |
405 | "Run a virtualenvwrapper hook, and update the environment. | |
406 | ||
407 | This will run a virtualenvwrapper hook and update the local | |
408 | environment accordingly. | |
409 | ||
410 | CAREFUL! If PROPAGATE-ENV is non-nil, this will modify your | |
411 | `process-environment' and `exec-path'." | |
412 | (when (pyvenv-virtualenvwrapper-supported) | |
413 | (with-temp-buffer | |
414 | (let ((tmpfile (make-temp-file "pyvenv-virtualenvwrapper-")) | |
415 | (shell-file-name pyvenv-exec-shell)) | |
416 | (unwind-protect | |
417 | (let ((default-directory (pyvenv-workon-home))) | |
418 | (apply #'call-process | |
419 | pyvenv-virtualenvwrapper-python | |
420 | nil t nil | |
421 | "-m" "virtualenvwrapper.hook_loader" | |
422 | "--script" tmpfile | |
423 | (if (getenv "HOOK_VERBOSE_OPTION") | |
424 | (cons (getenv "HOOK_VERBOSE_OPTION") | |
425 | (cons hook args)) | |
426 | (cons hook args))) | |
427 | (call-process-shell-command | |
428 | (mapconcat 'identity | |
429 | (list | |
430 | (format "%s -c 'import os, json; print(json.dumps(dict(os.environ)))'" | |
431 | pyvenv-virtualenvwrapper-python) | |
432 | (format ". '%s'" tmpfile) | |
433 | (format | |
434 | "%s -c 'import os, json; print(\"\\n=-=-=\"); print(json.dumps(dict(os.environ)))'" | |
435 | pyvenv-virtualenvwrapper-python)) | |
436 | "; ") | |
437 | nil t nil)) | |
438 | (delete-file tmpfile))) | |
439 | (goto-char (point-min)) | |
440 | (when (not (re-search-forward "No module named '?virtualenvwrapper'?" nil t)) | |
441 | (let* ((json-key-type 'string) | |
442 | (env-before (json-read)) | |
443 | (hook-output-start-pos (point)) | |
444 | (hook-output-end-pos (when (re-search-forward "\n=-=-=\n" nil t) | |
445 | (match-beginning 0))) | |
446 | (env-after (when hook-output-end-pos (json-read)))) | |
447 | (when hook-output-end-pos | |
448 | (let ((output (string-trim (buffer-substring hook-output-start-pos | |
449 | hook-output-end-pos)))) | |
450 | (when (> (length output) 0) | |
451 | (with-help-window "*Virtualenvwrapper Hook Output*" | |
452 | (with-current-buffer "*Virtualenvwrapper Hook Output*" | |
453 | (let ((inhibit-read-only t)) | |
454 | (erase-buffer) | |
455 | (insert | |
456 | (format | |
457 | "Output from the virtualenvwrapper hook %s:\n\n" | |
458 | hook) | |
459 | output)))))) | |
460 | (when propagate-env | |
461 | (dolist (binding (pyvenv--env-diff (sort env-before (lambda (x y) (string-lessp (car x) (car y)))) | |
462 | (sort env-after (lambda (x y) (string-lessp (car x) (car y)))))) | |
463 | (setenv (car binding) (cdr binding)) | |
464 | (when (eq (car binding) 'PATH) | |
465 | (let ((new-path-elts (split-string (cdr binding) | |
466 | path-separator))) | |
467 | (setq exec-path new-path-elts) | |
468 | (setq-default eshell-path-env new-path-elts))))))))))) | |
469 | ||
470 | ||
471 | (defun pyvenv--env-diff (env-before env-after) | |
472 | "Calculate diff between ENV-BEFORE alist and ENV-AFTER alist. | |
473 | ||
474 | Both ENV-BEFORE and ENV-AFTER must be sorted alists of (STR . STR)." | |
475 | (let (env-diff) | |
476 | (while (or env-before env-after) | |
477 | (cond | |
478 | ;; K-V are the same, both proceed to the next one. | |
479 | ((equal (car-safe env-before) (car-safe env-after)) | |
480 | (setq env-before (cdr env-before) | |
481 | env-after (cdr env-after))) | |
482 | ||
483 | ;; env-after is missing one element: add (K-before . nil) to diff | |
484 | ((and env-before (or (null env-after) (string-lessp (caar env-before) | |
485 | (caar env-after)))) | |
486 | (setq env-diff (cons (cons (caar env-before) nil) env-diff) | |
487 | env-before (cdr env-before))) | |
488 | ;; Otherwise: add env-after element to the diff, progress env-after, | |
489 | ;; progress env-before only if keys matched. | |
490 | (t | |
491 | (setq env-diff (cons (car env-after) env-diff)) | |
492 | (when (equal (caar env-after) (caar env-before)) | |
493 | (setq env-before (cdr env-before))) | |
494 | (setq env-after (cdr env-after))))) | |
495 | (nreverse env-diff))) | |
496 | ||
497 | ||
498 | ;;;###autoload | |
499 | (defun pyvenv-restart-python () | |
500 | "Restart Python inferior processes." | |
501 | (interactive) | |
502 | (dolist (buf (buffer-list)) | |
503 | (with-current-buffer buf | |
504 | (when (and (eq major-mode 'inferior-python-mode) | |
505 | (get-buffer-process buf)) | |
506 | (let ((cmd (combine-and-quote-strings (process-command | |
507 | (get-buffer-process buf)))) | |
508 | (dedicated (if (string-match "\\[.*\\]$" (buffer-name buf)) | |
509 | t | |
510 | nil)) | |
511 | (show nil)) | |
512 | (delete-process (get-buffer-process buf)) | |
513 | (goto-char (point-max)) | |
514 | (insert "\n\n" | |
515 | "###\n" | |
516 | (format "### Restarting in virtualenv %s (%s)\n" | |
517 | pyvenv-virtual-env-name pyvenv-virtual-env) | |
518 | "###\n" | |
519 | "\n\n") | |
520 | (run-python cmd dedicated show) | |
521 | (goto-char (point-max))))))) | |
522 | ||
523 | (defun pyvenv-hook-dir () | |
524 | "Return the current hook directory. | |
525 | ||
526 | This is usually the value of $VIRTUALENVWRAPPER_HOOK_DIR, but | |
527 | virtualenvwrapper has stopped exporting that variable, so we go | |
528 | back to the default of $WORKON_HOME or even just ~/.virtualenvs/." | |
529 | (or (getenv "VIRTUALENVWRAPPER_HOOK_DIR") | |
530 | (pyvenv-workon-home))) | |
531 | ||
532 | (defun pyvenv-workon-home () | |
533 | "Return the current workon home. | |
534 | ||
535 | This is the value of $WORKON_HOME or ~/.virtualenvs." | |
536 | (or (getenv "WORKON_HOME") | |
537 | (expand-file-name "~/.virtualenvs"))) | |
538 | ||
539 | (defun pyvenv-virtualenvwrapper-supported () | |
540 | "Return true iff virtualenvwrapper is supported. | |
541 | ||
542 | Right now, this just checks if WORKON_HOME is set." | |
543 | (getenv "WORKON_HOME")) | |
544 | ||
545 | (defun pyvenv--virtual-env-bin-dirs (virtual-env) | |
546 | (let ((virtual-env | |
547 | (if (string= "/" (directory-file-name virtual-env)) | |
548 | "" | |
549 | (directory-file-name virtual-env)))) | |
550 | (append | |
551 | ;; Unix | |
552 | (when (file-exists-p (format "%s/bin" virtual-env)) | |
553 | (list (format "%s/bin" virtual-env))) | |
554 | ;; Windows | |
555 | (when (file-exists-p (format "%s/Scripts" virtual-env)) | |
556 | (list (format "%s/Scripts" virtual-env) | |
557 | ;; Apparently, some virtualenv | |
558 | ;; versions on windows put the | |
559 | ;; python.exe in the virtualenv root | |
560 | ;; for some reason? | |
561 | virtual-env))))) | |
562 | ||
563 | (defun pyvenv--replace-once-destructive (list oldvalue newvalue) | |
564 | "Replace one element equal to OLDVALUE with NEWVALUE values in LIST." | |
565 | (let ((cur-elt list)) | |
566 | (while (and cur-elt (not (equal oldvalue (car cur-elt)))) | |
567 | (setq cur-elt (cdr cur-elt))) | |
568 | (when cur-elt (setcar cur-elt newvalue)))) | |
569 | ||
570 | (defun pyvenv--remove-many-once (values-to-remove list) | |
571 | "Return a copy of LIST with each element from VALUES-TO-REMOVE removed once." | |
572 | ;; Non-interned symbol is not eq to anything but itself. | |
573 | (let ((values-to-remove (copy-sequence values-to-remove)) | |
574 | (sentinel (make-symbol "sentinel"))) | |
575 | (delq sentinel | |
576 | (mapcar (lambda (x) | |
577 | (if (pyvenv--replace-once-destructive values-to-remove x sentinel) | |
578 | sentinel | |
579 | x)) | |
580 | list)))) | |
581 | ||
582 | (defun pyvenv--prepend-to-pathsep-string (values-to-prepend str) | |
583 | "Prepend values from VALUES-TO-PREPEND list to path-delimited STR." | |
584 | (mapconcat 'identity | |
585 | (append values-to-prepend (split-string str path-separator)) | |
586 | path-separator)) | |
587 | ||
588 | (defun pyvenv--remove-from-pathsep-string (values-to-remove str) | |
589 | "Remove all values from VALUES-TO-REMOVE list from path-delimited STR." | |
590 | (mapconcat 'identity | |
591 | (pyvenv--remove-many-once values-to-remove (split-string str path-separator)) | |
592 | path-separator)) | |
593 | ||
594 | (defun pyvenv--add-dirs-to-PATH (dirs-to-add) | |
595 | "Add DIRS-TO-ADD to different variables related to execution paths." | |
596 | (let* ((new-eshell-path-env (pyvenv--prepend-to-pathsep-string dirs-to-add (default-value 'eshell-path-env))) | |
597 | (new-path-envvar (pyvenv--prepend-to-pathsep-string dirs-to-add (getenv "PATH")))) | |
598 | (setq exec-path (append dirs-to-add exec-path)) | |
599 | (setq-default eshell-path-env new-eshell-path-env) | |
600 | (setenv "PATH" new-path-envvar))) | |
601 | ||
602 | (defun pyvenv--remove-dirs-from-PATH (dirs-to-remove) | |
603 | "Remove DIRS-TO-REMOVE from different variables related to execution paths." | |
604 | (let* ((new-eshell-path-env (pyvenv--remove-from-pathsep-string dirs-to-remove (default-value 'eshell-path-env))) | |
605 | (new-path-envvar (pyvenv--remove-from-pathsep-string dirs-to-remove (getenv "PATH")))) | |
606 | (setq exec-path (pyvenv--remove-many-once dirs-to-remove exec-path)) | |
607 | (setq-default eshell-path-env new-eshell-path-env) | |
608 | (setenv "PATH" new-path-envvar))) | |
609 | ||
610 | ;;; Compatibility | |
611 | ||
612 | (when (not (fboundp 'file-name-base)) | |
613 | ;; Emacs 24.3 | |
614 | (defun file-name-base (&optional filename) | |
615 | "Return the base name of the FILENAME: no directory, no extension. | |
616 | FILENAME defaults to `buffer-file-name'." | |
617 | (file-name-sans-extension | |
618 | (file-name-nondirectory (or filename (buffer-file-name))))) | |
619 | ) | |
620 | ||
621 | (when (not (boundp 'mode-line-misc-info)) | |
622 | (defvar mode-line-misc-info nil | |
623 | "Compatibility variable for 24.3+") | |
624 | (let ((line mode-line-format)) | |
625 | (while line | |
626 | (when (eq 'which-func-mode | |
627 | (car-safe (car-safe (cdr line)))) | |
628 | (setcdr line (cons 'mode-line-misc-format | |
629 | (cdr line))) | |
630 | (setq line (cdr line))) | |
631 | (setq line (cdr line))))) | |
632 | ||
633 | (provide 'pyvenv) | |
634 | ;;; pyvenv.el ends here |