aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/progmodes/python.el
diff options
context:
space:
mode:
authorFabián Ezequiel Gallina2014-07-26 20:43:51 -0300
committerFabián Ezequiel Gallina2014-07-26 20:43:51 -0300
commit60cc81af68080a8d1edb7584ce0966754595e187 (patch)
treef1cc7f5b5194843d85d125b2ffd651679b05ca2e /lisp/progmodes/python.el
parent9e9f8582a893f1e97b1f8955f69b96f969ee1f85 (diff)
downloademacs-60cc81af68080a8d1edb7584ce0966754595e187.tar.gz
emacs-60cc81af68080a8d1edb7584ce0966754595e187.zip
Robust shell syntax highlighting. (Bug#18084, Bug#16875)
* lisp/progmodes/python.el: (python-shell-prompt-input-regexps): Add iPython block prompt. (python-shell-output-syntax-table): Delete var. (python-shell-font-lock-with-font-lock-buffer): New macro. (python-shell-font-lock-get-or-create-buffer) (python-shell-font-lock-kill-buffer) (python-shell-font-lock-cleanup-buffer) (python-shell-font-lock-post-command-hook) (python-shell-font-lock-turn-off): New functions. (python-shell-font-lock-turn-on): New function. (inferior-python-mode): Use it. (python-shell-font-lock-toggle): New command. (python-shell-font-lock-enable): Rename from python-shell-enable-font-lock. (run-python-internal): Use it. (python-shell-font-lock-comint-output-filter-function): New function. (python-shell-comint-end-of-output-p): New function. (python-shell-output-filter): Use it. (python-util-comint-last-prompt): New function. (python-util-text-properties-replace-name): New function.
Diffstat (limited to 'lisp/progmodes/python.el')
-rw-r--r--lisp/progmodes/python.el250
1 files changed, 197 insertions, 53 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 89ef12d49eb..d8866a1c930 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -31,9 +31,9 @@
31;; found in GNU/Emacs. 31;; found in GNU/Emacs.
32 32
33;; Implements Syntax highlighting, Indentation, Movement, Shell 33;; Implements Syntax highlighting, Indentation, Movement, Shell
34;; interaction, Shell completion, Shell virtualenv support, Pdb 34;; interaction, Shell completion, Shell virtualenv support, Shell
35;; tracking, Symbol completion, Skeletons, FFAP, Code Check, Eldoc, 35;; syntax highlighting, Pdb tracking, Symbol completion, Skeletons,
36;; Imenu. 36;; FFAP, Code Check, Eldoc, Imenu.
37 37
38;; Syntax highlighting: Fontification of code is provided and supports 38;; Syntax highlighting: Fontification of code is provided and supports
39;; python's triple quoted strings properly. 39;; python's triple quoted strings properly.
@@ -170,6 +170,12 @@
170;; introduced as simple way of adding paths to the PYTHONPATH without 170;; introduced as simple way of adding paths to the PYTHONPATH without
171;; affecting existing values. 171;; affecting existing values.
172 172
173;; Shell syntax highlighting: when enabled current input in shell is
174;; highlighted. The variable `python-shell-font-lock-enable' controls
175;; activation of this feature globally when shells are started.
176;; Activation/deactivation can be also controlled on the fly via the
177;; `python-shell-font-lock-toggle' command.
178
173;; Pdb tracking: when you execute a block of code that contains some 179;; Pdb tracking: when you execute a block of code that contains some
174;; call to pdb (or ipdb) it will prompt the block of code and will 180;; call to pdb (or ipdb) it will prompt the block of code and will
175;; follow the execution of pdb marking the current line with an arrow. 181;; follow the execution of pdb marking the current line with an arrow.
@@ -1750,6 +1756,7 @@ position, else returns nil."
1750(defcustom python-shell-prompt-input-regexps 1756(defcustom python-shell-prompt-input-regexps
1751 '(">>> " "\\.\\.\\. " ; Python 1757 '(">>> " "\\.\\.\\. " ; Python
1752 "In \\[[0-9]+\\]: " ; IPython 1758 "In \\[[0-9]+\\]: " ; IPython
1759 " \\.\\.\\.: " ; IPython
1753 ;; Using ipdb outside IPython may fail to cleanup and leave static 1760 ;; Using ipdb outside IPython may fail to cleanup and leave static
1754 ;; IPython prompts activated, this adds some safeguard for that. 1761 ;; IPython prompts activated, this adds some safeguard for that.
1755 "In : " "\\.\\.\\.: ") 1762 "In : " "\\.\\.\\.: ")
@@ -1785,13 +1792,16 @@ It should not contain a caret (^) at the beginning."
1785It should not contain a caret (^) at the beginning." 1792It should not contain a caret (^) at the beginning."
1786 :type 'string) 1793 :type 'string)
1787 1794
1788(defcustom python-shell-enable-font-lock t 1795(defcustom python-shell-font-lock-enable t
1789 "Should syntax highlighting be enabled in the Python shell buffer? 1796 "Should syntax highlighting be enabled in the Python shell buffer?
1790Restart the Python shell after changing this variable for it to take effect." 1797Restart the Python shell after changing this variable for it to take effect."
1791 :type 'boolean 1798 :type 'boolean
1792 :group 'python 1799 :group 'python
1793 :safe 'booleanp) 1800 :safe 'booleanp)
1794 1801
1802(define-obsolete-variable-alias
1803 'python-shell-enable-font-lock python-shell-font-lock-enable "24.4")
1804
1795(defcustom python-shell-process-environment nil 1805(defcustom python-shell-process-environment nil
1796 "List of environment variables for Python shell. 1806 "List of environment variables for Python shell.
1797This variable follows the same rules as `process-environment' 1807This variable follows the same rules as `process-environment'
@@ -2090,6 +2100,20 @@ uniqueness for different types of configurations."
2090 (directory-file-name python-shell-virtualenv-path)) 2100 (directory-file-name python-shell-virtualenv-path))
2091 path)))) 2101 path))))
2092 2102
2103(defun python-shell-comint-end-of-output-p (output)
2104 "Return non-nil if OUTPUT is ends with input prompt."
2105 (string-match
2106 ;; XXX: It seems on OSX an extra carriage return is attached
2107 ;; at the end of output, this handles that too.
2108 (concat
2109 "\r?\n?"
2110 ;; Remove initial caret from calculated regexp
2111 (replace-regexp-in-string
2112 (rx string-start ?^) ""
2113 python-shell--prompt-calculated-input-regexp)
2114 (rx eos))
2115 output))
2116
2093(defun python-comint-output-filter-function (output) 2117(defun python-comint-output-filter-function (output)
2094 "Hook run after content is put into comint buffer. 2118 "Hook run after content is put into comint buffer.
2095OUTPUT is a string with the contents of the buffer." 2119OUTPUT is a string with the contents of the buffer."
@@ -2097,19 +2121,140 @@ OUTPUT is a string with the contents of the buffer."
2097 2121
2098(defvar python-shell--parent-buffer nil) 2122(defvar python-shell--parent-buffer nil)
2099 2123
2100(defvar python-shell-output-syntax-table 2124(defvar python-shell--font-lock-buffer nil)
2101 (let ((table (make-syntax-table python-dotty-syntax-table))) 2125
2102 (modify-syntax-entry ?\' "." table) 2126(defun python-shell-font-lock-get-or-create-buffer ()
2103 (modify-syntax-entry ?\" "." table) 2127 "Get or create a font-lock buffer for current inferior process."
2104 (modify-syntax-entry ?\( "." table) 2128 (if python-shell--font-lock-buffer
2105 (modify-syntax-entry ?\[ "." table) 2129 python-shell--font-lock-buffer
2106 (modify-syntax-entry ?\{ "." table) 2130 (let ((process-name
2107 (modify-syntax-entry ?\) "." table) 2131 (process-name (get-buffer-process (current-buffer)))))
2108 (modify-syntax-entry ?\] "." table) 2132 (generate-new-buffer
2109 (modify-syntax-entry ?\} "." table) 2133 (format "*%s-font-lock*" process-name)))))
2110 table) 2134
2111 "Syntax table for shell output. 2135(defun python-shell-font-lock-kill-buffer ()
2112It makes parens and quotes be treated as punctuation chars.") 2136 "Kill the font-lock buffer safely."
2137 (when (and python-shell--font-lock-buffer
2138 (buffer-live-p python-shell--font-lock-buffer))
2139 (kill-buffer python-shell--font-lock-buffer)
2140 (when (eq major-mode 'inferior-python-mode)
2141 (setq python-shell--font-lock-buffer nil))))
2142
2143(defmacro python-shell-font-lock-with-font-lock-buffer (&rest body)
2144 "Execute the forms in BODY in the font-lock buffer.
2145The value returned is the value of the last form in BODY. See
2146also `with-current-buffer'."
2147 (declare (indent 0) (debug t))
2148 `(save-current-buffer
2149 (when (not (eq major-mode 'inferior-python-mode))
2150 (error "Current buffer is not in `inferior-python-mode'."))
2151 (when (not (and python-shell--font-lock-buffer
2152 (get-buffer python-shell--font-lock-buffer)))
2153 (setq python-shell--font-lock-buffer
2154 (python-shell-font-lock-get-or-create-buffer)))
2155 (set-buffer python-shell--font-lock-buffer)
2156 (set (make-local-variable 'delay-mode-hooks) t)
2157 (let ((python-indent-guess-indent-offset nil))
2158 (when (not (eq major-mode 'python-mode))
2159 (python-mode))
2160 ,@body)))
2161
2162(defun python-shell-font-lock-cleanup-buffer ()
2163 "Cleanup the font-lock buffer.
2164Provided as a command because this might be handy if something
2165goes wrong and syntax highlighting in the shell gets messed up."
2166 (interactive)
2167 (python-shell-font-lock-with-font-lock-buffer
2168 (delete-region (point-min) (point-max))))
2169
2170(defun python-shell-font-lock-comint-output-filter-function (output)
2171 "Clean up the font-lock buffer after any OUTPUT."
2172 (when (and (not (string= "" output))
2173 ;; Is end of output and is not just a prompt.
2174 (not (member
2175 (python-shell-comint-end-of-output-p
2176 (ansi-color-filter-apply output))
2177 '(nil 0))))
2178 ;; If output is other than an input prompt then "real" output has
2179 ;; been received and the font-lock buffer must be cleaned up.
2180 (python-shell-font-lock-cleanup-buffer))
2181 output)
2182
2183(defun python-shell-font-lock-post-command-hook ()
2184 "Fontifies current line in shell buffer."
2185 (if (eq this-command 'comint-send-input)
2186 ;; Add a newline when user sends input as this may be a block.
2187 (python-shell-font-lock-with-font-lock-buffer
2188 (goto-char (line-end-position))
2189 (newline))
2190 (when (and (python-util-comint-last-prompt)
2191 (> (point) (cdr (python-util-comint-last-prompt))))
2192 (let ((input (buffer-substring-no-properties
2193 (cdr (python-util-comint-last-prompt)) (point-max))))
2194 (delete-region (cdr (python-util-comint-last-prompt)) (point-max))
2195 (insert
2196 (python-shell-font-lock-with-font-lock-buffer
2197 (delete-region (line-beginning-position)
2198 (line-end-position))
2199 (insert input)
2200 ;; Ensure buffer is fontified, keeping it
2201 ;; compatible with Emacs < 24.4.
2202 (if (fboundp 'font-lock-ensure)
2203 (funcall 'font-lock-ensure)
2204 (font-lock-default-fontify-buffer))
2205 ;; Replace FACE text properties with FONT-LOCK-FACE so they
2206 ;; are not overwritten by current buffer's font-lock
2207 (python-util-text-properties-replace-name
2208 'face 'font-lock-face)
2209 (buffer-substring (line-beginning-position)
2210 (line-end-position))))))))
2211
2212(defun python-shell-font-lock-turn-on (&optional msg)
2213 "Turn on shell font-lock.
2214With argument MSG show activation message."
2215 (python-shell-font-lock-kill-buffer)
2216 (set (make-local-variable 'python-shell--font-lock-buffer) nil)
2217 (add-hook 'post-command-hook
2218 #'python-shell-font-lock-post-command-hook nil 'local)
2219 (add-hook 'kill-buffer-hook
2220 #'python-shell-font-lock-kill-buffer nil 'local)
2221 (add-hook 'comint-output-filter-functions
2222 #'python-shell-font-lock-comint-output-filter-function
2223 'append 'local)
2224 (when msg
2225 (message "Shell font-lock is enabled")))
2226
2227(defun python-shell-font-lock-turn-off (&optional msg)
2228 "Turn off shell font-lock.
2229With argument MSG show deactivation message."
2230 (python-shell-font-lock-kill-buffer)
2231 (when (python-util-comint-last-prompt)
2232 ;; Cleanup current fontification
2233 (remove-text-properties
2234 (cdr (python-util-comint-last-prompt))
2235 (line-end-position)
2236 '(face nil font-lock-face nil)))
2237 (set (make-local-variable 'python-shell--font-lock-buffer) nil)
2238 (remove-hook 'post-command-hook
2239 #'python-shell-font-lock-post-command-hook'local)
2240 (remove-hook 'kill-buffer-hook
2241 #'python-shell-font-lock-kill-buffer 'local)
2242 (remove-hook 'comint-output-filter-functions
2243 #'python-shell-font-lock-comint-output-filter-function
2244 'local)
2245 (when msg
2246 (message "Shell font-lock is disabled")))
2247
2248(defun python-shell-font-lock-toggle (&optional msg)
2249 "Toggle font-lock for shell.
2250With argument MSG show activation/deactivation message."
2251 (interactive "p")
2252 (set (make-local-variable 'python-shell-font-lock-enable)
2253 (not python-shell-font-lock-enable))
2254 (if python-shell-font-lock-enable
2255 (python-shell-font-lock-turn-on msg)
2256 (python-shell-font-lock-turn-off msg))
2257 python-shell-font-lock-enable)
2113 2258
2114(define-derived-mode inferior-python-mode comint-mode "Inferior Python" 2259(define-derived-mode inferior-python-mode comint-mode "Inferior Python"
2115 "Major mode for Python inferior process. 2260 "Major mode for Python inferior process.
@@ -2120,7 +2265,7 @@ interpreter is run. Variables
2120`python-shell-prompt-regexp', 2265`python-shell-prompt-regexp',
2121`python-shell-prompt-output-regexp', 2266`python-shell-prompt-output-regexp',
2122`python-shell-prompt-block-regexp', 2267`python-shell-prompt-block-regexp',
2123`python-shell-enable-font-lock', 2268`python-shell-font-lock-enable',
2124`python-shell-completion-setup-code', 2269`python-shell-completion-setup-code',
2125`python-shell-completion-string-code', 2270`python-shell-completion-string-code',
2126`python-shell-completion-module-string-code', 2271`python-shell-completion-module-string-code',
@@ -2165,29 +2310,8 @@ variable.
2165 (make-local-variable 'python-pdbtrack-buffers-to-kill) 2310 (make-local-variable 'python-pdbtrack-buffers-to-kill)
2166 (make-local-variable 'python-pdbtrack-tracked-buffer) 2311 (make-local-variable 'python-pdbtrack-tracked-buffer)
2167 (make-local-variable 'python-shell-internal-last-output) 2312 (make-local-variable 'python-shell-internal-last-output)
2168 (when python-shell-enable-font-lock 2313 (when python-shell-font-lock-enable
2169 (set-syntax-table python-mode-syntax-table) 2314 (python-shell-font-lock-turn-on))
2170 (set (make-local-variable 'font-lock-defaults)
2171 '(python-font-lock-keywords nil nil nil nil))
2172 (set (make-local-variable 'syntax-propertize-function)
2173 (eval
2174 ;; XXX: Unfortunately eval is needed here to make use of the
2175 ;; dynamic value of `comint-prompt-regexp'.
2176 `(syntax-propertize-rules
2177 (,comint-prompt-regexp
2178 (0 (ignore
2179 (put-text-property
2180 comint-last-input-start end 'syntax-table
2181 python-shell-output-syntax-table)
2182 ;; XXX: This might look weird, but it is the easiest
2183 ;; way to ensure font lock gets cleaned up before the
2184 ;; current prompt, which is needed for unclosed
2185 ;; strings to not mess up with current input.
2186 (font-lock-unfontify-region comint-last-input-start end))))
2187 (,(python-rx string-delimiter)
2188 (0 (ignore
2189 (and (not (eq (get-text-property start 'field) 'output))
2190 (python-syntax-stringify)))))))))
2191 (compilation-shell-minor-mode 1)) 2315 (compilation-shell-minor-mode 1))
2192 2316
2193(defun python-shell-make-comint (cmd proc-name &optional pop internal) 2317(defun python-shell-make-comint (cmd proc-name &optional pop internal)
@@ -2267,10 +2391,10 @@ difference with global or dedicated shells is that these ones are
2267attached to a configuration, not a buffer. This means that can 2391attached to a configuration, not a buffer. This means that can
2268be used for example to retrieve the sys.path and other stuff, 2392be used for example to retrieve the sys.path and other stuff,
2269without messing with user shells. Note that 2393without messing with user shells. Note that
2270`python-shell-enable-font-lock' and `inferior-python-mode-hook' 2394`python-shell-font-lock-enable' and `inferior-python-mode-hook'
2271are set to nil for these shells, so setup codes are not sent at 2395are set to nil for these shells, so setup codes are not sent at
2272startup." 2396startup."
2273 (let ((python-shell-enable-font-lock nil) 2397 (let ((python-shell-font-lock-enable nil)
2274 (inferior-python-mode-hook nil)) 2398 (inferior-python-mode-hook nil))
2275 (get-buffer-process 2399 (get-buffer-process
2276 (python-shell-make-comint 2400 (python-shell-make-comint
@@ -2390,16 +2514,7 @@ detecting a prompt at the end of the buffer."
2390 string (ansi-color-filter-apply string) 2514 string (ansi-color-filter-apply string)
2391 python-shell-output-filter-buffer 2515 python-shell-output-filter-buffer
2392 (concat python-shell-output-filter-buffer string)) 2516 (concat python-shell-output-filter-buffer string))
2393 (when (string-match 2517 (when (python-shell-comint-end-of-output-p
2394 ;; XXX: It seems on OSX an extra carriage return is attached
2395 ;; at the end of output, this handles that too.
2396 (concat
2397 "\r?\n"
2398 ;; Remove initial caret from calculated regexp
2399 (replace-regexp-in-string
2400 (rx string-start ?^) ""
2401 python-shell--prompt-calculated-input-regexp)
2402 "$")
2403 python-shell-output-filter-buffer) 2518 python-shell-output-filter-buffer)
2404 ;; Output ends when `python-shell-output-filter-buffer' contains 2519 ;; Output ends when `python-shell-output-filter-buffer' contains
2405 ;; the prompt attached at the end of it. 2520 ;; the prompt attached at the end of it.
@@ -3912,6 +4027,18 @@ to \"^python-\"."
3912 (cdr pair)))) 4027 (cdr pair))))
3913 (buffer-local-variables from-buffer))) 4028 (buffer-local-variables from-buffer)))
3914 4029
4030(defvar comint-last-prompt-overlay) ; Shut up, bytecompiler
4031
4032(defun python-util-comint-last-prompt ()
4033 "Return comint last prompt overlay start and end.
4034This is for compatibility with Emacs < 24.4."
4035 (cond ((bound-and-true-p comint-last-prompt-overlay)
4036 (cons (overlay-start comint-last-prompt-overlay)
4037 (overlay-end comint-last-prompt-overlay)))
4038 ((bound-and-true-p comint-last-prompt)
4039 comint-last-prompt)
4040 (t nil)))
4041
3915(defun python-util-forward-comment (&optional direction) 4042(defun python-util-forward-comment (&optional direction)
3916 "Python mode specific version of `forward-comment'. 4043 "Python mode specific version of `forward-comment'.
3917Optional argument DIRECTION defines the direction to move to." 4044Optional argument DIRECTION defines the direction to move to."
@@ -3939,6 +4066,23 @@ returned as is."
3939 n (1- n))) 4066 n (1- n)))
3940 (reverse acc)))) 4067 (reverse acc))))
3941 4068
4069(defun python-util-text-properties-replace-name
4070 (from to &optional start end)
4071 "Replace properties named FROM to TO, keeping its value.
4072Arguments START and END narrow the buffer region to work on."
4073 (save-excursion
4074 (goto-char (or start (point-min)))
4075 (while (not (eobp))
4076 (let ((plist (text-properties-at (point)))
4077 (next-change (or (next-property-change (point) (current-buffer))
4078 (or end (point-max)))))
4079 (when (plist-get plist from)
4080 (let* ((face (plist-get plist from))
4081 (plist (plist-put plist from nil))
4082 (plist (plist-put plist to face)))
4083 (set-text-properties (point) next-change plist (current-buffer))))
4084 (goto-char next-change)))))
4085
3942(defun python-util-strip-string (string) 4086(defun python-util-strip-string (string)
3943 "Strip STRING whitespace and newlines from end and beginning." 4087 "Strip STRING whitespace and newlines from end and beginning."
3944 (replace-regexp-in-string 4088 (replace-regexp-in-string