aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/progmodes/python.el
diff options
context:
space:
mode:
authorFabián Ezequiel Gallina2015-07-06 07:57:14 -0300
committerFabián Ezequiel Gallina2015-07-06 07:57:14 -0300
commit0fdc3f2ee839646cf41691f04a33252f05b7060e (patch)
tree279651545ad92c693ffa4f007049592f19f9e9a0 /lisp/progmodes/python.el
parent8c81ac97fdd0d1dff7256dace45bdd48324a0963 (diff)
downloademacs-0fdc3f2ee839646cf41691f04a33252f05b7060e.tar.gz
emacs-0fdc3f2ee839646cf41691f04a33252f05b7060e.zip
python.el: Respect process environment for remote shells
* lisp/progmodes/python.el (python-shell-calculate-process-environment): Calculate process-environment or tramp-remote-process-environment depending whether current file is remote. (python-shell-calculate-exec-path): Calculate exec-path or tramp-remote-path depending whether current file is remote. (python-shell-with-environment): New macro. (python-shell-prompt-detect, python-shell-calculate-command) (python-shell-make-comint, python-check): Use it.
Diffstat (limited to 'lisp/progmodes/python.el')
-rw-r--r--lisp/progmodes/python.el293
1 files changed, 167 insertions, 126 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 339f2403c26..1c0f105ceaa 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -180,6 +180,12 @@
180;; shell so that relative imports work properly using the 180;; shell so that relative imports work properly using the
181;; `python-shell-package-enable' command. 181;; `python-shell-package-enable' command.
182 182
183;; Shell remote support: remote Python shells are started with the
184;; correct environment for files opened remotely through tramp, also
185;; respecting dir-local variables provided `enable-remote-dir-locals'
186;; is non-nil. The logic for this is transparently handled by the
187;; `python-shell-with-environment' macro.
188
183;; Shell syntax highlighting: when enabled current input in shell is 189;; Shell syntax highlighting: when enabled current input in shell is
184;; highlighted. The variable `python-shell-font-lock-enable' controls 190;; highlighted. The variable `python-shell-font-lock-enable' controls
185;; activation of this feature globally when shells are started. 191;; activation of this feature globally when shells are started.
@@ -255,6 +261,7 @@
255(require 'cl-lib) 261(require 'cl-lib)
256(require 'comint) 262(require 'comint)
257(require 'json) 263(require 'json)
264(require 'tramp-sh)
258 265
259;; Avoid compiler warnings 266;; Avoid compiler warnings
260(defvar view-return-to-alist) 267(defvar view-return-to-alist)
@@ -2001,6 +2008,77 @@ virtualenv."
2001 :type '(alist string) 2008 :type '(alist string)
2002 :group 'python) 2009 :group 'python)
2003 2010
2011(defun python-shell-calculate-process-environment ()
2012 "Calculate `process-environment' or `tramp-remote-process-environment'.
2013Pre-appends `python-shell-process-environment', sets extra
2014pythonpaths from `python-shell-extra-pythonpaths' and sets a few
2015virtualenv related vars. If `default-directory' points to a
2016remote machine, the returned value is intended for
2017`tramp-remote-process-environment'."
2018 (let* ((remote-p (file-remote-p default-directory))
2019 (process-environment (append
2020 python-shell-process-environment
2021 (if remote-p
2022 tramp-remote-process-environment
2023 process-environment) nil))
2024 (virtualenv (if python-shell-virtualenv-root
2025 (directory-file-name python-shell-virtualenv-root)
2026 nil)))
2027 (when python-shell-unbuffered
2028 (setenv "PYTHONUNBUFFERED" "1"))
2029 (when python-shell-extra-pythonpaths
2030 (setenv "PYTHONPATH" (python-shell-calculate-pythonpath)))
2031 (if (not virtualenv)
2032 process-environment
2033 (setenv "PYTHONHOME" nil)
2034 (setenv "VIRTUAL_ENV" virtualenv))
2035 process-environment))
2036
2037(defun python-shell-calculate-exec-path ()
2038 "Calculate `exec-path' or `tramp-remote-path'.
2039Pre-appends `python-shell-exec-path' and adds the binary
2040directory for virtualenv if `python-shell-virtualenv-root' is
2041set. If `default-directory' points to a remote machine, the
2042returned value is intended for `tramp-remote-path'."
2043 (let ((path (append
2044 ;; Use nil as the tail so that the list is a full copy,
2045 ;; this is a paranoid safeguard for side-effects.
2046 python-shell-exec-path
2047 (if (file-remote-p default-directory)
2048 tramp-remote-path
2049 exec-path)
2050 nil)))
2051 (if (not python-shell-virtualenv-root)
2052 path
2053 (cons (expand-file-name "bin" python-shell-virtualenv-root)
2054 path))))
2055
2056(defmacro python-shell-with-environment (&rest body)
2057 "Modify shell environment during execution of BODY.
2058Temporarily sets `process-environment' and `exec-path' during
2059execution of body. If `default-directory' points to a remote
2060machine then modifies `tramp-remote-process-environment' and
2061`tramp-remote-path' instead."
2062 (declare (indent 0) (debug (body)))
2063 (let ((remote-p (file-remote-p default-directory)))
2064 `(let ((process-environment
2065 (if ,remote-p
2066 process-environment
2067 (python-shell-calculate-process-environment)))
2068 (tramp-remote-process-environment
2069 (if ,remote-p
2070 (python-shell-calculate-process-environment)
2071 tramp-remote-process-environment))
2072 (exec-path
2073 (if ,remote-p
2074 (python-shell-calculate-exec-path)
2075 exec-path))
2076 (tramp-remote-path
2077 (if ,remote-p
2078 (python-shell-calculate-exec-path)
2079 tramp-remote-path)))
2080 ,(macroexp-progn body))))
2081
2004(defvar python-shell--prompt-calculated-input-regexp nil 2082(defvar python-shell--prompt-calculated-input-regexp nil
2005 "Calculated input prompt regexp for inferior python shell. 2083 "Calculated input prompt regexp for inferior python shell.
2006Do not set this variable directly, instead use 2084Do not set this variable directly, instead use
@@ -2023,69 +2101,68 @@ shows a warning with instructions to avoid hangs and returns nil.
2023When `python-shell-prompt-detect-enabled' is nil avoids any 2101When `python-shell-prompt-detect-enabled' is nil avoids any
2024detection and just returns nil." 2102detection and just returns nil."
2025 (when python-shell-prompt-detect-enabled 2103 (when python-shell-prompt-detect-enabled
2026 (let* ((process-environment (python-shell-calculate-process-environment)) 2104 (python-shell-with-environment
2027 (exec-path (python-shell-calculate-exec-path)) 2105 (let* ((code (concat
2028 (code (concat 2106 "import sys\n"
2029 "import sys\n" 2107 "ps = [getattr(sys, 'ps%s' % i, '') for i in range(1,4)]\n"
2030 "ps = [getattr(sys, 'ps%s' % i, '') for i in range(1,4)]\n" 2108 ;; JSON is built manually for compatibility
2031 ;; JSON is built manually for compatibility 2109 "ps_json = '\\n[\"%s\", \"%s\", \"%s\"]\\n' % tuple(ps)\n"
2032 "ps_json = '\\n[\"%s\", \"%s\", \"%s\"]\\n' % tuple(ps)\n" 2110 "print (ps_json)\n"
2033 "print (ps_json)\n" 2111 "sys.exit(0)\n"))
2034 "sys.exit(0)\n")) 2112 (output
2035 (output 2113 (with-temp-buffer
2036 (with-temp-buffer 2114 ;; TODO: improve error handling by using
2037 ;; TODO: improve error handling by using 2115 ;; `condition-case' and displaying the error message to
2038 ;; `condition-case' and displaying the error message to 2116 ;; the user in the no-prompts warning.
2039 ;; the user in the no-prompts warning. 2117 (ignore-errors
2040 (ignore-errors 2118 (let ((code-file (python-shell--save-temp-file code)))
2041 (let ((code-file (python-shell--save-temp-file code))) 2119 ;; Use `process-file' as it is remote-host friendly.
2042 ;; Use `process-file' as it is remote-host friendly. 2120 (process-file
2043 (process-file 2121 python-shell-interpreter
2044 python-shell-interpreter 2122 code-file
2045 code-file 2123 '(t nil)
2046 '(t nil) 2124 nil
2047 nil 2125 python-shell-interpreter-interactive-arg)
2048 python-shell-interpreter-interactive-arg) 2126 ;; Try to cleanup
2049 ;; Try to cleanup 2127 (delete-file code-file)))
2050 (delete-file code-file))) 2128 (buffer-string)))
2051 (buffer-string))) 2129 (prompts
2052 (prompts 2130 (catch 'prompts
2053 (catch 'prompts 2131 (dolist (line (split-string output "\n" t))
2054 (dolist (line (split-string output "\n" t)) 2132 (let ((res
2055 (let ((res 2133 ;; Check if current line is a valid JSON array
2056 ;; Check if current line is a valid JSON array 2134 (and (string= (substring line 0 2) "[\"")
2057 (and (string= (substring line 0 2) "[\"") 2135 (ignore-errors
2058 (ignore-errors 2136 ;; Return prompts as a list, not vector
2059 ;; Return prompts as a list, not vector 2137 (append (json-read-from-string line) nil)))))
2060 (append (json-read-from-string line) nil))))) 2138 ;; The list must contain 3 strings, where the first
2061 ;; The list must contain 3 strings, where the first 2139 ;; is the input prompt, the second is the block
2062 ;; is the input prompt, the second is the block 2140 ;; prompt and the last one is the output prompt. The
2063 ;; prompt and the last one is the output prompt. The 2141 ;; input prompt is the only one that can't be empty.
2064 ;; input prompt is the only one that can't be empty. 2142 (when (and (= (length res) 3)
2065 (when (and (= (length res) 3) 2143 (cl-every #'stringp res)
2066 (cl-every #'stringp res) 2144 (not (string= (car res) "")))
2067 (not (string= (car res) ""))) 2145 (throw 'prompts res))))
2068 (throw 'prompts res)))) 2146 nil)))
2069 nil))) 2147 (when (and (not prompts)
2070 (when (and (not prompts) 2148 python-shell-prompt-detect-failure-warning)
2071 python-shell-prompt-detect-failure-warning) 2149 (lwarn
2072 (lwarn 2150 '(python python-shell-prompt-regexp)
2073 '(python python-shell-prompt-regexp) 2151 :warning
2074 :warning 2152 (concat
2075 (concat 2153 "Python shell prompts cannot be detected.\n"
2076 "Python shell prompts cannot be detected.\n" 2154 "If your emacs session hangs when starting python shells\n"
2077 "If your emacs session hangs when starting python shells\n" 2155 "recover with `keyboard-quit' and then try fixing the\n"
2078 "recover with `keyboard-quit' and then try fixing the\n" 2156 "interactive flag for your interpreter by adjusting the\n"
2079 "interactive flag for your interpreter by adjusting the\n" 2157 "`python-shell-interpreter-interactive-arg' or add regexps\n"
2080 "`python-shell-interpreter-interactive-arg' or add regexps\n" 2158 "matching shell prompts in the directory-local friendly vars:\n"
2081 "matching shell prompts in the directory-local friendly vars:\n" 2159 " + `python-shell-prompt-regexp'\n"
2082 " + `python-shell-prompt-regexp'\n" 2160 " + `python-shell-prompt-block-regexp'\n"
2083 " + `python-shell-prompt-block-regexp'\n" 2161 " + `python-shell-prompt-output-regexp'\n"
2084 " + `python-shell-prompt-output-regexp'\n" 2162 "Or alternatively in:\n"
2085 "Or alternatively in:\n" 2163 " + `python-shell-prompt-input-regexps'\n"
2086 " + `python-shell-prompt-input-regexps'\n" 2164 " + `python-shell-prompt-output-regexps'")))
2087 " + `python-shell-prompt-output-regexps'"))) 2165 prompts))))
2088 prompts)))
2089 2166
2090(defun python-shell-prompt-validate-regexps () 2167(defun python-shell-prompt-validate-regexps ()
2091 "Validate all user provided regexps for prompts. 2168 "Validate all user provided regexps for prompts.
@@ -2181,14 +2258,12 @@ the `buffer-name'."
2181 2258
2182(defun python-shell-calculate-command () 2259(defun python-shell-calculate-command ()
2183 "Calculate the string used to execute the inferior Python process." 2260 "Calculate the string used to execute the inferior Python process."
2184 (let ((exec-path (python-shell-calculate-exec-path))) 2261 (python-shell-with-environment
2185 ;; `exec-path' gets tweaked so that virtualenv's specific 2262 ;; `exec-path' gets tweaked so that virtualenv's specific
2186 ;; `python-shell-interpreter' absolute path can be found by 2263 ;; `python-shell-interpreter' absolute path can be found by
2187 ;; `executable-find'. 2264 ;; `executable-find'.
2188 (format "%s %s" 2265 (format "%s %s"
2189 ;; FIXME: Why executable-find? 2266 (shell-quote-argument python-shell-interpreter)
2190 (shell-quote-argument
2191 (executable-find python-shell-interpreter))
2192 python-shell-interpreter-args))) 2267 python-shell-interpreter-args)))
2193 2268
2194(define-obsolete-function-alias 2269(define-obsolete-function-alias
@@ -2205,38 +2280,6 @@ the `buffer-name'."
2205 (concat extra path-separator pythonpath) 2280 (concat extra path-separator pythonpath)
2206 extra))) 2281 extra)))
2207 2282
2208(defun python-shell-calculate-process-environment ()
2209 "Calculate process environment given `python-shell-virtualenv-root'."
2210 (let ((process-environment (append
2211 python-shell-process-environment
2212 process-environment nil))
2213 (virtualenv (if python-shell-virtualenv-root
2214 (directory-file-name python-shell-virtualenv-root)
2215 nil)))
2216 (when python-shell-unbuffered
2217 (setenv "PYTHONUNBUFFERED" "1"))
2218 (when python-shell-extra-pythonpaths
2219 (setenv "PYTHONPATH" (python-shell-calculate-pythonpath)))
2220 (if (not virtualenv)
2221 process-environment
2222 (setenv "PYTHONHOME" nil)
2223 (setenv "PATH" (format "%s/bin%s%s"
2224 virtualenv path-separator
2225 (or (getenv "PATH") "")))
2226 (setenv "VIRTUAL_ENV" virtualenv))
2227 process-environment))
2228
2229(defun python-shell-calculate-exec-path ()
2230 "Calculate exec path given `python-shell-virtualenv-root'."
2231 (let ((path (append
2232 ;; Use nil as the tail so that the list is a full copy,
2233 ;; this is a paranoid safeguard for side-effects.
2234 python-shell-exec-path exec-path nil)))
2235 (if (not python-shell-virtualenv-root)
2236 path
2237 (cons (expand-file-name "bin" python-shell-virtualenv-root)
2238 path))))
2239
2240(defvar python-shell--package-depth 10) 2283(defvar python-shell--package-depth 10)
2241 2284
2242(defun python-shell-package-enable (directory package) 2285(defun python-shell-package-enable (directory package)
@@ -2561,31 +2604,30 @@ convention for temporary/internal buffers, and also makes sure
2561the user is not queried for confirmation when the process is 2604the user is not queried for confirmation when the process is
2562killed." 2605killed."
2563 (save-excursion 2606 (save-excursion
2564 (let* ((proc-buffer-name 2607 (python-shell-with-environment
2565 (format (if (not internal) "*%s*" " *%s*") proc-name)) 2608 (let* ((proc-buffer-name
2566 (process-environment (python-shell-calculate-process-environment)) 2609 (format (if (not internal) "*%s*" " *%s*") proc-name)))
2567 (exec-path (python-shell-calculate-exec-path))) 2610 (when (not (comint-check-proc proc-buffer-name))
2568 (when (not (comint-check-proc proc-buffer-name)) 2611 (let* ((cmdlist (split-string-and-unquote cmd))
2569 (let* ((cmdlist (split-string-and-unquote cmd)) 2612 (interpreter (car cmdlist))
2570 (interpreter (car cmdlist)) 2613 (args (cdr cmdlist))
2571 (args (cdr cmdlist)) 2614 (buffer (apply #'make-comint-in-buffer proc-name proc-buffer-name
2572 (buffer (apply #'make-comint-in-buffer proc-name proc-buffer-name 2615 interpreter nil args))
2573 interpreter nil args)) 2616 (python-shell--parent-buffer (current-buffer))
2574 (python-shell--parent-buffer (current-buffer)) 2617 (process (get-buffer-process buffer))
2575 (process (get-buffer-process buffer)) 2618 ;; Users can override the interpreter and args
2576 ;; Users can override the interpreter and args 2619 ;; interactively when calling `run-python', let-binding
2577 ;; interactively when calling `run-python', let-binding 2620 ;; these allows to have the new right values in all
2578 ;; these allows to have the new right values in all 2621 ;; setup code that is done in `inferior-python-mode',
2579 ;; setup code that is done in `inferior-python-mode', 2622 ;; which is important, especially for prompt detection.
2580 ;; which is important, especially for prompt detection. 2623 (python-shell--interpreter interpreter)
2581 (python-shell--interpreter interpreter) 2624 (python-shell--interpreter-args
2582 (python-shell--interpreter-args 2625 (mapconcat #'identity args " ")))
2583 (mapconcat #'identity args " "))) 2626 (with-current-buffer buffer
2584 (with-current-buffer buffer 2627 (inferior-python-mode))
2585 (inferior-python-mode)) 2628 (when show (display-buffer buffer))
2586 (when show (display-buffer buffer)) 2629 (and internal (set-process-query-on-exit-flag process nil))))
2587 (and internal (set-process-query-on-exit-flag process nil)))) 2630 proc-buffer-name))))
2588 proc-buffer-name)))
2589 2631
2590;;;###autoload 2632;;;###autoload
2591(defun run-python (&optional cmd dedicated show) 2633(defun run-python (&optional cmd dedicated show)
@@ -3984,8 +4026,7 @@ See `python-check-command' for the default."
3984 ""))))))) 4026 "")))))))
3985 (setq python-check-custom-command command) 4027 (setq python-check-custom-command command)
3986 (save-some-buffers (not compilation-ask-about-save) nil) 4028 (save-some-buffers (not compilation-ask-about-save) nil)
3987 (let ((process-environment (python-shell-calculate-process-environment)) 4029 (python-shell-with-environment
3988 (exec-path (python-shell-calculate-exec-path)))
3989 (compilation-start command nil 4030 (compilation-start command nil
3990 (lambda (_modename) 4031 (lambda (_modename)
3991 (format python-check-buffer-name command))))) 4032 (format python-check-buffer-name command)))))