]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | ;;; elpy-shell.el --- Interactive Python support for elpy -*- lexical-binding: t -*- |
2 | ;; | |
3 | ;; Copyright (C) 2012-2019 Jorgen Schaefer | |
4 | ;; | |
5 | ;; Author: Jorgen Schaefer <contact@jorgenschaefer.de>, Rainer Gemulla <rgemulla@gmx.de>, Gaby Launay <gaby.launay@protonmail.com> | |
6 | ;; URL: https://github.com/jorgenschaefer/elpy | |
7 | ;; | |
8 | ;; This program is free software; you can redistribute it and/or | |
9 | ;; modify it under the terms of the GNU General Public License | |
10 | ;; as published by the Free Software Foundation; either version 3 | |
11 | ;; of the License, or (at your option) any later version. | |
12 | ;; | |
13 | ;; This program is distributed in the hope that it will be useful, | |
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | ;; GNU General Public License for more details. | |
17 | ;; | |
18 | ;; You should have received a copy of the GNU General Public License | |
19 | ;; along with this program. If not, see <http://www.gnu.org/licenses/>. | |
20 | ;; | |
21 | ;;; Commentary: | |
22 | ;; | |
23 | ;; Adds support for interactive Python to elpy | |
24 | ;; | |
25 | ;;; Code: | |
26 | ||
27 | (eval-when-compile (require 'subr-x)) | |
28 | (require 'python) | |
29 | ||
30 | ;;;;;;;;;;;;;;;;;;;;;; | |
31 | ;;; User customization | |
32 | ||
33 | (defcustom elpy-dedicated-shells nil | |
34 | "Non-nil if Elpy should use dedicated shells. | |
35 | ||
36 | Elpy can use a unique Python shell for all buffers and support | |
37 | manually started dedicated shells. Setting this option to non-nil | |
38 | force the creation of dedicated shells for each buffers." | |
39 | :type 'boolean | |
40 | :group 'elpy) | |
41 | (make-obsolete-variable 'elpy-dedicated-shells | |
42 | "Dedicated shells are no longer supported by Elpy. | |
43 | You can use `(add-hook 'elpy-mode-hook (lambda () (elpy-shell-toggle-dedicated-shell 1)))' to achieve the same result." | |
44 | "1.17.0") | |
45 | ||
46 | (defcustom elpy-shell-display-buffer-after-send nil ; | |
47 | "Whether to display the Python shell after sending something to it." | |
48 | :type 'boolean | |
49 | :group 'elpy) | |
50 | ||
51 | (defcustom elpy-shell-echo-output 'when-shell-not-visible | |
52 | "Whether to echo the Python shell output in the echo area after input has been sent to the shell. | |
53 | ||
54 | Possible choices are nil (=never), `when-shell-not-visible', or | |
55 | t (=always)." | |
56 | :type '(choice (const :tag "Never" nil) | |
57 | (const :tag "When shell not visible" when-shell-not-visible) | |
58 | (const :tag "Always" t)) | |
59 | :group 'elpy) | |
60 | ||
61 | (defcustom elpy-shell-capture-last-multiline-output t | |
62 | "Whether to capture the output of the last Python statement when sending multiple statements to the Python shell. | |
63 | ||
64 | If nil, no output is captured (nor echoed in the shell) when | |
65 | sending multiple statements. This is the default behavior of | |
66 | python.el. If non-nil and the last statement is an expression, | |
67 | captures its output so that it is echoed in the shell." | |
68 | :type 'boolean | |
69 | :group 'elpy) | |
70 | (make-obsolete-variable 'elpy-shell-capture-last-multiline-output | |
71 | "The last multiline output is now always captured." | |
72 | "February 2019") | |
73 | ||
74 | (defcustom elpy-shell-echo-input t | |
75 | "Whether to echo input sent to the Python shell as input in the | |
76 | shell buffer. | |
77 | ||
78 | Truncation of long inputs can be controlled via | |
79 | `elpy-shell-echo-input-lines-head' and | |
80 | `elpy-shell-echo-input-lines-tail'." | |
81 | :type 'boolean | |
82 | :group 'elpy) | |
83 | ||
84 | (defcustom elpy-shell-echo-input-cont-prompt t | |
85 | "Whether to show a continuation prompt when echoing multi-line | |
86 | input to the Python shell." | |
87 | :type 'boolean | |
88 | :group 'elpy) | |
89 | ||
90 | (defcustom elpy-shell-echo-input-lines-head 10 | |
91 | "Maximum number of lines to show before truncating input echoed | |
92 | in the Python shell." | |
93 | :type 'integer | |
94 | :group 'elpy) | |
95 | ||
96 | (defcustom elpy-shell-echo-input-lines-tail 10 | |
97 | "Maximum number of lines to show after truncating input echoed | |
98 | in the Python shell." | |
99 | :type 'integer | |
100 | :group 'elpy) | |
101 | ||
102 | (defcustom elpy-shell-starting-directory 'project-root | |
103 | "Directory in which Python shells will be started. | |
104 | ||
105 | Can be `project-root' (default) to use the current project root, | |
106 | `current-directory' to use the buffer current directory, or a | |
107 | string indicating a specific path. | |
108 | ||
109 | \\<elpy-mode-map> | |
110 | Running python interpeters need to be restarted (with | |
111 | \\[elpy-shell-kill] followed by \\[elpy-shell-switch-to-shell]) for | |
112 | this option to be taken into account." | |
113 | :type '(choice (const :tag "Project root" project-root) | |
114 | (const :tag "Current directory" current-directory) | |
115 | (string :tag "Specific directory")) | |
116 | :group 'elpy) | |
117 | ||
118 | (defcustom elpy-shell-use-project-root t | |
119 | "Whether to use project root as default directory when starting a Python shells. | |
120 | ||
121 | The project root is determined using `elpy-project-root`. If this | |
122 | variable is set to nil, the current directory is used instead." | |
123 | :type 'boolean | |
124 | :group 'elpy) | |
125 | (make-obsolete-variable 'elpy-shell-use-project-root | |
126 | 'elpy-shell-starting-directory | |
127 | "1.32.0") | |
128 | ||
129 | (defcustom elpy-shell-cell-boundary-regexp | |
130 | (concat "^\\(?:" | |
131 | "##.*" "\\|" | |
132 | "#\\s-*<.+>" "\\|" | |
133 | "#\\s-*\\(?:In\\|Out\\)\\[.*\\]:" | |
134 | "\\)\\s-*$") | |
135 | "Regular expression for matching a line indicating the boundary | |
136 | of a cell (beginning or ending). By default, lines starting with | |
137 | ``##`` are treated as a cell boundaries, as are the boundaries in | |
138 | Python files exported from IPython or Jupyter notebooks (e.g., | |
139 | ``# <markdowncell>``, ``# In[1]:'', or ``# Out[1]:``). | |
140 | ||
141 | Note that `elpy-shell-cell-beginning-regexp' must also match | |
142 | the first boundary of the code cell." | |
143 | ||
144 | :type 'string | |
145 | :group 'elpy) | |
146 | ||
147 | (defcustom elpy-shell-codecell-beginning-regexp | |
148 | (concat "^\\(?:" | |
149 | "##.*" "\\|" | |
150 | "#\\s-*<codecell>" "\\|" | |
151 | "#\\s-*In\\[.*\\]:" | |
152 | "\\)\\s-*$") | |
153 | "Regular expression for matching a line indicating the | |
154 | beginning of a code cell. By default, lines starting with ``##`` | |
155 | are treated as beginnings of a code cell, as are the code cell | |
156 | beginnings (and only the code cell beginnings) in Python files | |
157 | exported from IPython or Jupyter notebooks (e.g., ``# | |
158 | <codecell>`` or ``# In[1]:``). | |
159 | ||
160 | Note that `elpy-shell-cell-boundary-regexp' must also match | |
161 | the code cell beginnings defined here." | |
162 | :type 'string | |
163 | :group 'elpy) | |
164 | ||
165 | (defcustom elpy-shell-add-to-shell-history nil | |
166 | "If Elpy should make the code sent to the shell available in the | |
167 | shell history. This allows to use `comint-previous-input' in the | |
168 | python shell to get back the pieces of code sent by Elpy. This affects | |
169 | the following functions: | |
170 | - `elpy-shell-send-statement' | |
171 | - `elpy-shell-send-top-statement' | |
172 | - `elpy-shell-send-group' | |
173 | - `elpy-shell-send-codecell' | |
174 | - `elpy-shell-send-region-or-buffer'." | |
175 | :type 'boolean | |
176 | :group 'elpy) | |
177 | ||
178 | (defcustom elpy-shell-darwin-use-pty nil | |
179 | "Whether to connect to the Python shell through pty on MacOS. | |
180 | ||
181 | If nil, Elpy will connect to Python through a pipe. Any non-nil | |
182 | value will cause Elpy use a pseudo-terminal (pty) instead. This | |
183 | value should be set to nil when using a Python interpreter that | |
184 | uses the libedit version of Readline, such as the default MacOS | |
185 | Python interpreters. This value can be safely be set to true when | |
186 | using a version of Python that uses GNU Readline. | |
187 | ||
188 | This value is only used when `elpy-shell-get-or-create-process' | |
189 | creates a new Python process." | |
190 | :type 'boolean | |
191 | :group 'elpy) | |
192 | ||
193 | ||
194 | ;;;;;;;;;;;;;;;;;; | |
195 | ;;; Shell commands | |
196 | ||
197 | (defvar elpy--shell-last-py-buffer nil | |
198 | "Help keep track of python buffer when changing to pyshell.") | |
199 | ||
200 | (defun elpy-shell-display-buffer () | |
201 | "Display inferior Python process buffer." | |
202 | (display-buffer (process-buffer (elpy-shell-get-or-create-process)) | |
203 | nil | |
204 | 'visible)) | |
205 | ||
206 | ;; better name would be pop-to-shell | |
207 | (defun elpy-shell-switch-to-shell () | |
208 | "Switch to inferior Python process buffer." | |
209 | (interactive) | |
210 | (setq elpy--shell-last-py-buffer (buffer-name)) | |
211 | (pop-to-buffer (process-buffer (elpy-shell-get-or-create-process)))) | |
212 | ||
213 | (defun elpy-shell-switch-to-buffer () | |
214 | "Switch from inferior Python process buffer to recent Python buffer." | |
215 | (interactive) | |
216 | (pop-to-buffer elpy--shell-last-py-buffer)) | |
217 | ||
218 | (defun elpy-shell-switch-to-shell-in-current-window () | |
219 | (interactive) | |
220 | (setq elpy--shell-last-py-buffer (buffer-name)) | |
221 | (switch-to-buffer (process-buffer (elpy-shell-get-or-create-process)))) | |
222 | ||
223 | (defun elpy-shell-switch-to-buffer-in-current-window () | |
224 | (interactive) | |
225 | (switch-to-buffer elpy--shell-last-py-buffer)) | |
226 | ||
227 | (defun elpy-shell-kill (&optional kill-buff) | |
228 | "Kill the current python shell. | |
229 | ||
230 | If KILL-BUFF is non-nil, also kill the associated buffer." | |
231 | (interactive) | |
232 | (let ((shell-buffer (python-shell-get-buffer))) | |
233 | (cond | |
234 | (shell-buffer | |
235 | (delete-process shell-buffer) | |
236 | (when kill-buff | |
237 | (kill-buffer shell-buffer)) | |
238 | (message "Killed %s shell" shell-buffer)) | |
239 | (t | |
240 | (message "No python shell to kill"))))) | |
241 | ||
242 | (defun elpy-shell-kill-all (&optional kill-buffers ask-for-each-one) | |
243 | "Kill all active python shells. | |
244 | ||
245 | If KILL-BUFFERS is non-nil, also kill the associated buffers. | |
246 | If ASK-FOR-EACH-ONE is non-nil, ask before killing each python process." | |
247 | (interactive) | |
248 | (let ((python-buffer-list ())) | |
249 | ;; Get active python shell buffers and kill inactive ones (if asked) | |
250 | (cl-loop for buffer being the buffers do | |
251 | (when (and (buffer-name buffer) | |
252 | (string-match (rx bol "*Python" (opt "[" (* (not (any "]"))) "]") "*" eol) | |
253 | (buffer-name buffer))) | |
254 | (if (get-buffer-process buffer) | |
255 | (push buffer python-buffer-list) | |
256 | (when kill-buffers | |
257 | (kill-buffer buffer))))) | |
258 | (cond | |
259 | ;; Ask for each buffers and kill | |
260 | ((and python-buffer-list ask-for-each-one) | |
261 | (cl-loop for buffer in python-buffer-list do | |
262 | (when (y-or-n-p (format "Kill %s ? " buffer)) | |
263 | (delete-process buffer) | |
264 | (when kill-buffers | |
265 | (kill-buffer buffer))))) | |
266 | ;; Ask and kill every buffers | |
267 | (python-buffer-list | |
268 | (if (y-or-n-p (format "Kill %s python shells ? " (length python-buffer-list))) | |
269 | (cl-loop for buffer in python-buffer-list do | |
270 | (delete-process buffer) | |
271 | (when kill-buffers | |
272 | (kill-buffer buffer))))) | |
273 | ;; No shell to close | |
274 | (t | |
275 | (message "No python shell to close"))))) | |
276 | ||
277 | (defun elpy-executable-find-remote (command) | |
278 | "Emulate 'executable-find' REMOTE. | |
279 | Since Emacs 27, 'executable-find' accepts the 2nd argument. | |
280 | REMOVE THIS when Elpy no longer supports Emacs 26." | |
281 | (if (cdr (help-function-arglist 'executable-find)) ; 27+ | |
282 | (executable-find command t) | |
283 | (if (file-remote-p default-directory) | |
284 | (let ((res (locate-file ; code from files.el | |
285 | command | |
286 | (mapcar | |
287 | (lambda (x) (concat (file-remote-p default-directory) x)) | |
288 | (exec-path)) | |
289 | exec-suffixes 'file-executable-p))) | |
290 | (when (stringp res) (file-local-name res))) | |
291 | (executable-find command)))) ; local search | |
292 | ||
293 | (defun elpy-shell-get-or-create-process (&optional sit) | |
294 | "Get or create an inferior Python process for current buffer and return it. | |
295 | ||
296 | If SIT is non-nil, sit for that many seconds after creating a | |
297 | Python process. This allows the process to start up." | |
298 | (let* ((process-connection-type | |
299 | (if (string-equal system-type "darwin") elpy-shell-darwin-use-pty t)) ;; see https://github.com/jorgenschaefer/elpy/pull/1671 | |
300 | (bufname (format "*%s*" (python-shell-get-process-name nil))) | |
301 | (proc (get-buffer-process bufname))) | |
302 | (if proc | |
303 | proc | |
304 | (unless (elpy-executable-find-remote python-shell-interpreter) | |
305 | (error "Python shell interpreter `%s' cannot be found. Please set `python-shell-interpreter' to a valid python binary." | |
306 | python-shell-interpreter)) | |
307 | (let ((default-directory | |
308 | (cond ((eq elpy-shell-starting-directory 'project-root) | |
309 | (or (elpy-project-root) | |
310 | default-directory)) | |
311 | ((eq elpy-shell-starting-directory 'current-directory) | |
312 | default-directory) | |
313 | ((stringp elpy-shell-starting-directory) | |
314 | (file-name-as-directory | |
315 | (expand-file-name elpy-shell-starting-directory))) | |
316 | (t | |
317 | (error "Wrong value for `elpy-shell-starting-directory', please check this variable documentation and set it to a proper value"))))) | |
318 | ;; We cannot use `run-python` directly, as it selects the new shell | |
319 | ;; buffer. See https://github.com/jorgenschaefer/elpy/issues/1848 | |
320 | (python-shell-make-comint | |
321 | (python-shell-parse-command) | |
322 | (python-shell-get-process-name nil) | |
323 | t)) | |
324 | (when sit (sit-for sit)) | |
325 | (get-buffer-process bufname)))) | |
326 | ||
327 | (defun elpy-shell--send-setup-code () | |
328 | "Send setup code for the shell." | |
329 | (let ((process (python-shell-get-process))) | |
330 | (when (elpy-project-root) | |
331 | (python-shell-send-string-no-output | |
332 | (format "import sys;sys.path.append('%s');del sys" | |
333 | (elpy-project-root)) | |
334 | process)))) | |
335 | ||
336 | (defun elpy-shell-toggle-dedicated-shell (&optional arg) | |
337 | "Toggle the use of a dedicated python shell for the current buffer. | |
338 | ||
339 | if ARG is positive, enable the use of a dedicated shell. | |
340 | if ARG is negative or 0, disable the use of a dedicated shell." | |
341 | (interactive) | |
342 | (let ((arg (or arg | |
343 | (if (local-variable-p 'python-shell-buffer-name) 0 1)))) | |
344 | (if (<= arg 0) | |
345 | (kill-local-variable 'python-shell-buffer-name) | |
346 | (setq-local python-shell-buffer-name | |
347 | (format "Python[%s]" (buffer-name)))))) | |
348 | ||
349 | (defun elpy-shell-set-local-shell (&optional shell-name) | |
350 | "Associate the current buffer to a specific shell. | |
351 | ||
352 | Meaning that the code from the current buffer will be sent to this shell. | |
353 | ||
354 | If SHELL-NAME is not specified, ask with completion for a shell name. | |
355 | ||
356 | If SHELL-NAME is \"Global\", associate the current buffer to the main python | |
357 | shell (often \"*Python*\" shell)." | |
358 | (interactive) | |
359 | (let* ((current-shell-name (if (local-variable-p 'python-shell-buffer-name) | |
360 | (progn | |
361 | (string-match "Python\\[\\(.*?\\)\\]" | |
362 | python-shell-buffer-name) | |
363 | (match-string 1 python-shell-buffer-name)) | |
364 | "Global")) | |
365 | (shell-names (cl-loop | |
366 | for buffer in (buffer-list) | |
367 | for buffer-name = (substring-no-properties (buffer-name buffer)) | |
368 | if (string-match "\\*Python\\[\\(.*?\\)\\]\\*" buffer-name) | |
369 | collect (match-string 1 buffer-name))) | |
370 | (candidates (remove current-shell-name | |
371 | (delete-dups | |
372 | (append (list (buffer-name) "Global") | |
373 | shell-names)))) | |
374 | (prompt (format "Shell name (current: %s): " current-shell-name)) | |
375 | (shell-name (or shell-name (completing-read prompt candidates)))) | |
376 | (if (string= shell-name "Global") | |
377 | (kill-local-variable 'python-shell-buffer-name) | |
378 | (setq-local python-shell-buffer-name (format "Python[%s]" shell-name))))) | |
379 | ||
380 | (defun elpy-shell--ensure-shell-running () | |
381 | "Ensure that the Python shell for the current buffer is running. | |
382 | ||
383 | If the shell is not running, waits until the first prompt is visible and | |
384 | commands can be sent to the shell." | |
385 | (with-current-buffer (process-buffer (elpy-shell-get-or-create-process)) | |
386 | (let ((cumtime 0)) | |
387 | (while (and (when (boundp 'python-shell--first-prompt-received) | |
388 | (not python-shell--first-prompt-received)) | |
389 | (< cumtime 3)) | |
390 | (sleep-for 0.1) | |
391 | (setq cumtime (+ cumtime 0.1))))) | |
392 | (elpy-shell-get-or-create-process)) | |
393 | ||
394 | (defun elpy-shell--string-without-indentation (string) | |
395 | "Return the current string, but without indentation." | |
396 | (if (string-empty-p string) | |
397 | string | |
398 | (let ((indent-level nil) | |
399 | (indent-tabs-mode nil)) | |
400 | (with-temp-buffer | |
401 | (insert string) | |
402 | (goto-char (point-min)) | |
403 | (while (< (point) (point-max)) | |
404 | (cond | |
405 | ((or (elpy-shell--current-line-only-whitespace-p) | |
406 | (python-info-current-line-comment-p))) | |
407 | ((not indent-level) | |
408 | (setq indent-level (current-indentation))) | |
409 | ((and indent-level | |
410 | (< (current-indentation) indent-level)) | |
411 | (error (message "X%sX" (thing-at-point 'line))))) | |
412 | ;; (error "Can't adjust indentation, consecutive lines indented less than starting line"))) | |
413 | (forward-line)) | |
414 | (indent-rigidly (point-min) | |
415 | (point-max) | |
416 | (- indent-level)) | |
417 | ;; 'indent-rigidly' introduces tabs despite the fact that 'indent-tabs-mode' is nil | |
418 | ;; 'untabify' fix that | |
419 | (untabify (point-min) (point-max)) | |
420 | (buffer-string))))) | |
421 | ||
422 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
423 | ;; Flash input sent to shell | |
424 | ||
425 | ;; functions for flashing a region; only flashes when package eval-sexp-fu is | |
426 | ;; loaded and its minor mode enabled | |
427 | (defun elpy-shell--flash-and-message-region (begin end) | |
428 | "Displays information about code fragments sent to the shell. | |
429 | ||
430 | BEGIN and END refer to the region of the current buffer | |
431 | containing the code being sent. Displays a message with the code | |
432 | on the first line of that region. If `eval-sexp-fu-flash-mode' is | |
433 | active, additionally flashes that region briefly." | |
434 | (when (> end begin) | |
435 | (save-excursion | |
436 | (let* ((bounds | |
437 | (save-excursion | |
438 | (goto-char begin) | |
439 | (bounds-of-thing-at-point 'line))) | |
440 | (begin (max begin (car bounds))) | |
441 | (end (min end (cdr bounds))) | |
442 | (code-on-first-line (string-trim (buffer-substring begin end)))) | |
443 | (goto-char begin) | |
444 | (end-of-line) | |
445 | (if (<= end (point)) | |
446 | (message "Sent: %s" code-on-first-line) | |
447 | (message "Sent: %s..." code-on-first-line)) | |
448 | (when (bound-and-true-p eval-sexp-fu-flash-mode) | |
449 | (cl-multiple-value-bind (_bounds hi unhi _eflash) | |
450 | (eval-sexp-fu-flash (cons begin end)) | |
451 | (eval-sexp-fu-flash-doit (lambda () t) hi unhi))))))) | |
452 | ||
453 | ;;;;;;;;;;;;;;;;;;; | |
454 | ;; Helper functions | |
455 | ||
456 | (defun elpy-shell--current-line-else-or-elif-p () | |
457 | (eq (string-match-p "\\s-*el\\(?:se:\\|if[^\w]\\)" (thing-at-point 'line)) 0)) | |
458 | ||
459 | (defun elpy-shell--current-line-decorator-p () | |
460 | (eq (string-match-p "^\\s-*@[A-Za-z]" (thing-at-point 'line)) 0)) | |
461 | ||
462 | (defun elpy-shell--current-line-decorated-defun-p () | |
463 | (save-excursion (python-nav-backward-statement) | |
464 | (elpy-shell--current-line-decorator-p))) | |
465 | ||
466 | (defun elpy-shell--current-line-indented-p () | |
467 | (eq (string-match-p "\\s-+[^\\s-]+" (thing-at-point 'line)) 0)) | |
468 | ||
469 | (defun elpy-shell--current-line-only-whitespace-p () | |
470 | "Whether the current line contains only whitespace characters (or is empty)." | |
471 | (eq (string-match-p "\\s-*$" (thing-at-point 'line)) 0)) | |
472 | ||
473 | (defun elpy-shell--current-line-code-line-p () | |
474 | (and (not (elpy-shell--current-line-only-whitespace-p)) | |
475 | (not (python-info-current-line-comment-p)))) | |
476 | ||
477 | (defun elpy-shell--current-line-defun-p () | |
478 | "Whether a function definition starts at the current line." | |
479 | (eq (string-match-p | |
480 | "\\s-*\\(?:def\\|async\\s-+def\\)\\s\-" | |
481 | (thing-at-point 'line)) | |
482 | 0)) | |
483 | ||
484 | (defun elpy-shell--current-line-defclass-p () | |
485 | "Whether a class definition starts at the current line." | |
486 | (eq (string-match-p | |
487 | "\\s-*class\\s\-" | |
488 | (thing-at-point 'line)) | |
489 | 0)) | |
490 | ||
491 | (defun elpy-shell--skip-to-next-code-line (&optional backwards) | |
492 | "Move the point to the next line containing code. | |
493 | ||
494 | If the current line has code, point is not moved. If BACKWARDS is | |
495 | non-nil, skips backwards." | |
496 | (if backwards | |
497 | (while (and (not (elpy-shell--current-line-code-line-p)) | |
498 | (not (eq (point) (point-min)))) | |
499 | (forward-line -1)) | |
500 | (while (and (not (elpy-shell--current-line-code-line-p)) | |
501 | (not (eq (point) (point-max)))) | |
502 | (forward-line)))) | |
503 | ||
504 | (defun elpy-shell--check-if-shell-available () | |
505 | "Check if the associated python shell is available. | |
506 | ||
507 | Return non-nil is the shell is running and not busy, nil otherwise." | |
508 | (and (python-shell-get-process) | |
509 | (with-current-buffer (process-buffer (python-shell-get-process)) | |
510 | (save-excursion | |
511 | (goto-char (point-max)) | |
512 | (let ((inhibit-field-text-motion t)) | |
513 | (python-shell-comint-end-of-output-p | |
514 | (buffer-substring (line-beginning-position) | |
515 | (line-end-position)))))))) | |
516 | ;;;;;;;;;; | |
517 | ;; Echoing | |
518 | ||
519 | (defmacro elpy-shell--with-maybe-echo (body) | |
520 | ;; Echoing is apparently buggy for emacs < 25... | |
521 | (if (<= 25 emacs-major-version) | |
522 | `(elpy-shell--with-maybe-echo-output | |
523 | (elpy-shell--with-maybe-echo-input | |
524 | ,body)) | |
525 | body)) | |
526 | ||
527 | ||
528 | (defmacro elpy-shell--with-maybe-echo-input (body) | |
529 | "Run BODY so that it adheres `elpy-shell-echo-input' and `elpy-shell-display-buffer'." | |
530 | `(progn | |
531 | (elpy-shell--enable-echo) | |
532 | (prog1 | |
533 | (if elpy-shell-display-buffer-after-send | |
534 | (prog1 (progn ,body) | |
535 | (elpy-shell-display-buffer)) | |
536 | (cl-flet ((elpy-shell-display-buffer () ())) | |
537 | (progn ,body))) | |
538 | (elpy-shell--disable-echo)))) | |
539 | ||
540 | (defvar-local elpy-shell--capture-output nil | |
541 | "Non-nil when the Python shell should capture output for display in the echo area.") | |
542 | ||
543 | (defvar-local elpy-shell--captured-output nil | |
544 | "Current captured output of the Python shell.") | |
545 | ||
546 | (defmacro elpy-shell--with-maybe-echo-output (body) | |
547 | "Run BODY and grab shell output according to `elpy-shell-echo-output'." | |
548 | `(cl-letf (((symbol-function 'python-shell-send-file) | |
549 | (if elpy-shell-echo-output | |
550 | (symbol-function 'elpy-shell-send-file) | |
551 | (symbol-function 'python-shell-send-file)))) | |
552 | (let* ((process (elpy-shell--ensure-shell-running)) | |
553 | (process-buf (process-buffer process)) | |
554 | (shell-visible (or elpy-shell-display-buffer-after-send | |
555 | (get-buffer-window process-buf)))) | |
556 | (with-current-buffer process-buf | |
557 | (setq-local elpy-shell--capture-output | |
558 | (and elpy-shell-echo-output | |
559 | (or (not (eq elpy-shell-echo-output 'when-shell-not-visible)) | |
560 | (not shell-visible))))) | |
561 | (progn ,body)))) | |
562 | ||
563 | (defun elpy-shell--enable-output-filter () | |
564 | (add-hook 'comint-output-filter-functions 'elpy-shell--output-filter nil t)) | |
565 | ||
566 | (defun elpy-shell--output-filter (string) | |
567 | "Filter used in `elpy-shell--with-maybe-echo-output' to grab output. | |
568 | ||
569 | No actual filtering is performed. STRING is the output received | |
570 | to this point from the process. If `elpy-shell--capture-output' | |
571 | is set, captures and messages shell output in the echo area (once | |
572 | complete). Otherwise, does nothing." | |
573 | ;; capture the output and message it when complete | |
574 | (when elpy-shell--capture-output | |
575 | ;; remember the new output | |
576 | (setq-local elpy-shell--captured-output | |
577 | (concat elpy-shell--captured-output (ansi-color-filter-apply string))) | |
578 | ||
579 | ;; Output ends when `elpy-shell--captured-output' contains | |
580 | ;; the prompt attached at the end of it. If so, message it. | |
581 | (when (python-shell-comint-end-of-output-p elpy-shell--captured-output) | |
582 | (let ((output (substring | |
583 | elpy-shell--captured-output | |
584 | 0 (match-beginning 0))) | |
585 | (message-log-max)) | |
586 | (if (string-match-p "Traceback (most recent call last):" output) | |
587 | (message "Exception during evaluation.") | |
588 | (if (string-empty-p output) | |
589 | (message "No output was produced.") | |
590 | (message "%s" (replace-regexp-in-string "\n\\'" "" output)))) | |
591 | (setq-local elpy-shell--captured-output nil)))) | |
592 | ||
593 | ;; return input unmodified | |
594 | string) | |
595 | ||
596 | (defun elpy-shell--insert-and-font-lock (string face &optional no-font-lock) | |
597 | "Inject STRING into the Python shell buffer." | |
598 | (let ((from-point (point))) | |
599 | (insert string) | |
600 | (if (not no-font-lock) | |
601 | (add-text-properties from-point (point) | |
602 | (list 'front-sticky t 'font-lock-face face))))) | |
603 | ||
604 | (defun elpy-shell--append-to-shell-output (string &optional no-font-lock prepend-cont-prompt) | |
605 | "Append the given STRING to the output of the Python shell buffer. | |
606 | ||
607 | Unless NO-FONT-LOCK is set, formats STRING as shell input. | |
608 | Prepends a continuation promt if PREPEND-CONT-PROMPT is set." | |
609 | (unless (string-empty-p string) | |
610 | (let* ((process (elpy-shell-get-or-create-process)) | |
611 | (process-buf (process-buffer process)) | |
612 | (mark-point (process-mark process))) | |
613 | (with-current-buffer process-buf | |
614 | (save-excursion | |
615 | (goto-char mark-point) | |
616 | (if prepend-cont-prompt | |
617 | (let* ((column (+ (- (point) | |
618 | (let ((inhibit-field-text-motion t)) | |
619 | (forward-line -1) | |
620 | (end-of-line) | |
621 | (point))) | |
622 | 1)) | |
623 | (prompt (concat (make-string (max 0 (- column 6)) ? ) "... ")) | |
624 | (lines (split-string string "\n"))) | |
625 | (goto-char mark-point) | |
626 | (elpy-shell--insert-and-font-lock | |
627 | (car lines) 'comint-highlight-input no-font-lock) | |
628 | (when (cdr lines) | |
629 | ;; no additional newline at end for multiline | |
630 | (dolist (line (cdr lines)) | |
631 | (insert "\n") | |
632 | (let ((from-point (point))) | |
633 | (elpy-shell--insert-and-font-lock | |
634 | prompt 'comint-highlight-prompt no-font-lock) | |
635 | (add-text-properties | |
636 | from-point (point) | |
637 | '(field output inhibit-line-move-field-capture t | |
638 | rear-nonsticky t))) | |
639 | (elpy-shell--insert-and-font-lock | |
640 | line 'comint-highlight-input no-font-lock))) | |
641 | ;; but put one for single line | |
642 | (insert "\n")) | |
643 | (elpy-shell--insert-and-font-lock | |
644 | string 'comint-highlight-input no-font-lock)) | |
645 | (set-marker (process-mark process) (point))))))) | |
646 | ||
647 | (defun elpy-shell--string-head-lines (string n) | |
648 | "Extract the first N lines from STRING." | |
649 | (let* ((line "\\(?:\\(?:.*\n\\)\\|\\(?:.+\\'\\)\\)") | |
650 | (lines (concat line "\\{" (number-to-string n) "\\}")) | |
651 | (regexp (concat "\\`" "\\(" lines "\\)"))) | |
652 | (if (string-match regexp string) | |
653 | (match-string 1 string) | |
654 | string))) | |
655 | ||
656 | (defun elpy-shell--string-tail-lines (string n) | |
657 | "Extract the last N lines from STRING." | |
658 | (let* ((line "\\(?:\\(?:.*\n\\)\\|\\(?:.+\\'\\)\\)") | |
659 | (lines (concat line "\\{" (number-to-string n) "\\}")) | |
660 | (regexp (concat "\\(" lines "\\)" "\\'"))) | |
661 | (if (string-match regexp string) | |
662 | (match-string 1 string) | |
663 | string))) | |
664 | ||
665 | (defun elpy-shell--python-shell-send-string-echo-advice (string &optional _process _msg) | |
666 | "Advice to enable echoing of input in the Python shell." | |
667 | (interactive) | |
668 | (let* ((append-string ; strip setup code from Elpy | |
669 | (if (string-match "import sys, codecs, os, ast;__pyfile = codecs.open.*$" string) | |
670 | (replace-match "" nil nil string) | |
671 | string)) | |
672 | (append-string ; strip setup code from python.el | |
673 | (if (string-match "import codecs, os;__pyfile = codecs.open(.*;exec(compile(__code, .*$" append-string) | |
674 | (replace-match "" nil nil append-string) | |
675 | append-string)) | |
676 | (append-string ; here too | |
677 | (if (string-match "^# -\\*- coding: utf-8 -\\*-\n*$" append-string) | |
678 | (replace-match "" nil nil append-string) | |
679 | append-string)) | |
680 | (append-string ; Strip "if True:", added when sending regions | |
681 | (if (string-match "^if True:$" append-string) | |
682 | (replace-match "" nil nil append-string) | |
683 | append-string)) | |
684 | (append-string ; strip newlines from beginning and white space from end | |
685 | (string-trim-right | |
686 | (if (string-match "\\`\n+" append-string) | |
687 | (replace-match "" nil nil append-string) | |
688 | append-string))) | |
689 | (append-string ; Dedent region | |
690 | (elpy-shell--string-without-indentation append-string)) | |
691 | (head (elpy-shell--string-head-lines append-string elpy-shell-echo-input-lines-head)) | |
692 | (tail (elpy-shell--string-tail-lines append-string elpy-shell-echo-input-lines-tail)) | |
693 | (append-string (if (> (length append-string) (+ (length head) (length tail))) | |
694 | (concat head "...\n" tail) | |
695 | append-string))) | |
696 | ||
697 | ;; append the modified string to the shell output; prepend a newline for | |
698 | ;; multi-line strings | |
699 | (if elpy-shell-echo-input-cont-prompt | |
700 | (elpy-shell--append-to-shell-output append-string nil t) | |
701 | (elpy-shell--append-to-shell-output | |
702 | (concat (if (string-match "\n" append-string) "\n" "") | |
703 | append-string | |
704 | "\n"))))) | |
705 | ||
706 | (defun elpy-shell--enable-echo () | |
707 | "Enable input echoing when `elpy-shell-echo-input' is set." | |
708 | (when elpy-shell-echo-input | |
709 | (advice-add 'python-shell-send-string | |
710 | :before 'elpy-shell--python-shell-send-string-echo-advice))) | |
711 | ||
712 | (defun elpy-shell--disable-echo () | |
713 | "Disable input echoing." | |
714 | (advice-remove 'python-shell-send-string | |
715 | 'elpy-shell--python-shell-send-string-echo-advice)) | |
716 | ||
717 | (defun elpy-shell-send-file (file-name &optional process temp-file-name | |
718 | delete msg) | |
719 | "Like `python-shell-send-file' but evaluates last expression separately. | |
720 | ||
721 | See `python-shell-send-file' for a description of the | |
722 | arguments. This function differs in that it breaks up the | |
723 | Python code in FILE-NAME into statements. If the last statement | |
724 | is a Python expression, it is evaluated separately in 'eval' | |
725 | mode. This way, the interactive python shell can capture (and | |
726 | print) the output of the last expression." | |
727 | (interactive | |
728 | (list | |
729 | (read-file-name "File to send: ") ; file-name | |
730 | nil ; process | |
731 | nil ; temp-file-name | |
732 | nil ; delete | |
733 | t)) ; msg | |
734 | (let* ((process (or process (python-shell-get-process-or-error msg))) | |
735 | (encoding (with-temp-buffer | |
736 | (insert-file-contents | |
737 | (or temp-file-name file-name)) | |
738 | (python-info-encoding))) | |
739 | (file-name (expand-file-name | |
740 | (or (file-remote-p file-name 'localname) | |
741 | file-name))) | |
742 | (temp-file-name (when temp-file-name | |
743 | (expand-file-name | |
744 | (or (file-remote-p temp-file-name 'localname) | |
745 | temp-file-name))))) | |
746 | (python-shell-send-string | |
747 | (format | |
748 | (concat | |
749 | "import sys, codecs, os, ast;" | |
750 | "__pyfile = codecs.open('''%s''', encoding='''%s''');" | |
751 | "__code = __pyfile.read().encode('''%s''');" | |
752 | "__pyfile.close();" | |
753 | (when (and delete temp-file-name) | |
754 | (format "os.remove('''%s''');" temp-file-name)) | |
755 | "__block = ast.parse(__code, '''%s''', mode='exec');" | |
756 | ;; Has to ba a oneliner, which make conditionnal statements a bit complicated... | |
757 | " __block.body = (__block.body if not isinstance(__block.body[0], ast.If) else __block.body if not isinstance(__block.body[0].test, ast.Name) else __block.body if not __block.body[0].test.id == 'True' else __block.body[0].body) if sys.version_info[0] < 3 else (__block.body if not isinstance(__block.body[0], ast.If) else __block.body if not isinstance(__block.body[0].test, ast.NameConstant) else __block.body if not __block.body[0].test.value is True else __block.body[0].body);" | |
758 | "__last = __block.body[-1];" ;; the last statement | |
759 | "__isexpr = isinstance(__last,ast.Expr);" ;; is it an expression? | |
760 | "_ = __block.body.pop() if __isexpr else None;" ;; if so, remove it | |
761 | "exec(compile(__block, '''%s''', mode='exec'));" ;; execute everything else | |
762 | "eval(compile(ast.Expression(__last.value), '''%s''', mode='eval')) if __isexpr else None" ;; if it was an expression, it has been removed; now evaluate it | |
763 | ) | |
764 | (or temp-file-name file-name) encoding encoding file-name file-name file-name) | |
765 | process))) | |
766 | ||
767 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
768 | ;; Navigation commands for sending | |
769 | ||
770 | (defun elpy-shell--nav-beginning-of-statement () | |
771 | "Move the point to the beginning of the current or next Python statement. | |
772 | ||
773 | If the current line starts with a statement, behaves exactly like | |
774 | `python-nav-beginning-of-statement'. If the line is part of a | |
775 | statement but not a statement itself, goes backwards to the | |
776 | beginning of the statement. If the current line is not a code | |
777 | line, skips forward to the next code line and navigates from | |
778 | there." | |
779 | (elpy-shell--skip-to-next-code-line) | |
780 | (python-nav-beginning-of-statement) | |
781 | (let ((p)) | |
782 | (while (and (not (eq p (point))) | |
783 | (or (elpy-shell--current-line-else-or-elif-p) | |
784 | (elpy-shell--current-line-decorated-defun-p))) | |
785 | (elpy-nav-backward-block) | |
786 | (setq p (point))))) | |
787 | ||
788 | (defun elpy-shell--nav-end-of-statement () | |
789 | "Move the point to the end of the current Python statement. | |
790 | ||
791 | Assumes that the point is precisely at the beginning of a | |
792 | statement (e.g., after calling | |
793 | `elpy-shell--nav-beginning-of-statement')." | |
794 | (let ((continue t) | |
795 | (p)) | |
796 | (while (and (not (eq p (point))) | |
797 | continue) | |
798 | ;; if on a decorator, move to the associated function | |
799 | (when (elpy-shell--current-line-decorator-p) | |
800 | (elpy-nav-forward-block)) | |
801 | ||
802 | ;; check if there is a another block at the same indentation level | |
803 | (setq p (point)) | |
804 | (elpy-nav-forward-block) | |
805 | ||
806 | ;; if not, go to the end of the block and done | |
807 | (if (eq p (point)) | |
808 | (progn | |
809 | (python-nav-end-of-block) | |
810 | (setq continue nil)) | |
811 | ;; otherwise check if its an else/elif clause | |
812 | (unless (elpy-shell--current-line-else-or-elif-p) | |
813 | (forward-line -1) | |
814 | (elpy-shell--skip-to-next-code-line t) | |
815 | (setq continue nil))))) | |
816 | (end-of-line)) | |
817 | ||
818 | (defun elpy-shell--nav-beginning-of-top-statement () | |
819 | "Move the point to the beginning of the current or next top-level statement. | |
820 | ||
821 | If the point is within a top-level statement, moves to its | |
822 | beginning. Otherwise, moves to the beginning of the next top-level | |
823 | statement." | |
824 | (interactive) | |
825 | (elpy-shell--nav-beginning-of-statement) | |
826 | (let ((p)) | |
827 | (while (and (not (eq p (point))) | |
828 | (elpy-shell--current-line-indented-p)) | |
829 | (forward-line -1) | |
830 | (elpy-shell--skip-to-next-code-line t) | |
831 | (elpy-shell--nav-beginning-of-statement)))) | |
832 | ||
833 | (defun elpy-shell--nav-beginning-of-def (def-p) | |
834 | "Move point to the beginning of the current definition. | |
835 | ||
836 | DEF-P is a predicate function that decides whether the current | |
837 | line starts a definition. | |
838 | ||
839 | It the current line starts a definition, uses this definition. If | |
840 | the current line does not start a definition and is a code line, | |
841 | searches for the definition that contains the current line. | |
842 | Otherwise, searches for the definition that contains the next | |
843 | code line. | |
844 | ||
845 | If a definition is found, moves point to the start of the | |
846 | definition and returns t. Otherwise, retains point position and | |
847 | returns nil." | |
848 | (if (funcall def-p) | |
849 | (progn | |
850 | (python-nav-beginning-of-statement) | |
851 | t) | |
852 | (let ((beg-ts (save-excursion | |
853 | (elpy-shell--skip-to-next-code-line t) | |
854 | (elpy-shell--nav-beginning-of-top-statement) | |
855 | (point))) | |
856 | (orig-p (point)) | |
857 | (max-indent (save-excursion | |
858 | (elpy-shell--skip-to-next-code-line) | |
859 | (- (current-indentation) 1))) | |
860 | (found)) | |
861 | (while (and (not found) | |
862 | (>= (point) beg-ts)) | |
863 | (if (and (funcall def-p) | |
864 | (<= (current-indentation) max-indent)) | |
865 | (setq found t) | |
866 | (when (elpy-shell--current-line-code-line-p) | |
867 | (setq max-indent (min max-indent | |
868 | (- (current-indentation) 1)))) | |
869 | (forward-line -1))) | |
870 | (if found | |
871 | (python-nav-beginning-of-statement) | |
872 | (goto-char orig-p)) | |
873 | found))) | |
874 | ||
875 | (defun elpy-shell--nav-beginning-of-defun () | |
876 | "Move point to the beginning of the current function definition. | |
877 | ||
878 | If a definition is found, moves point to the start of the | |
879 | definition and returns t. Otherwise, retains point position and | |
880 | returns nil. | |
881 | ||
882 | See `elpy-shell--nav-beginning-of-def' for details." | |
883 | (when (or (elpy-shell--nav-beginning-of-def 'elpy-shell--current-line-defun-p) | |
884 | (elpy-shell--current-line-decorator-p)) | |
885 | (when (elpy-shell--current-line-decorated-defun-p) | |
886 | (python-nav-backward-statement)) | |
887 | t)) | |
888 | ||
889 | (defun elpy-shell--nav-beginning-of-defclass () | |
890 | "Move point to the beginning of the current class definition. | |
891 | ||
892 | If a definition is found, moves point to the start of the | |
893 | definition and returns t. Otherwise, retains point position and | |
894 | returns nil. | |
895 | ||
896 | See `elpy-shell--nav-beginning-of-def' for details." | |
897 | (elpy-shell--nav-beginning-of-def 'elpy-shell--current-line-defclass-p)) | |
898 | ||
899 | (defun elpy-shell--nav-beginning-of-group () | |
900 | "Move point to the beginning of the current or next group of top-level statements. | |
901 | ||
902 | A sequence of top-level statements is a group if they are not | |
903 | separated by empty lines. Empty lines within each top-level | |
904 | statement are ignored. | |
905 | ||
906 | If the point is within a top-level statement, moves to the | |
907 | beginning of the group containing this statement. Otherwise, moves | |
908 | to the first top-level statement below point." | |
909 | (elpy-shell--nav-beginning-of-top-statement) | |
910 | (while (not (or (elpy-shell--current-line-only-whitespace-p) | |
911 | (eq (point) (point-min)))) | |
912 | (unless (python-info-current-line-comment-p) | |
913 | (elpy-shell--nav-beginning-of-top-statement)) | |
914 | (forward-line -1) | |
915 | (beginning-of-line)) | |
916 | (when (elpy-shell--current-line-only-whitespace-p) | |
917 | (forward-line 1) | |
918 | (beginning-of-line))) | |
919 | ||
920 | ;;;;;;;;;;;;;;;;; | |
921 | ;;; Send commands | |
922 | ||
923 | (defun elpy-shell-send-statement-and-step () | |
924 | "Send current or next statement to Python shell and step. | |
925 | ||
926 | If the current line is part of a statement, sends this statement. | |
927 | Otherwise, skips forward to the next code line and sends the | |
928 | corresponding statement." | |
929 | (interactive) | |
930 | (elpy-shell--ensure-shell-running) | |
931 | (elpy-shell--nav-beginning-of-statement) | |
932 | ;; Make sure there is a statement to send | |
933 | (unless (looking-at "[[:space:]]*$") | |
934 | (unless elpy-shell-echo-input (elpy-shell--append-to-shell-output "\n")) | |
935 | (let ((beg (save-excursion (beginning-of-line) (point))) | |
936 | (end (progn (elpy-shell--nav-end-of-statement) (point)))) | |
937 | (unless (eq beg end) | |
938 | (elpy-shell--flash-and-message-region beg end) | |
939 | (elpy-shell--add-to-shell-history (buffer-substring beg end)) | |
940 | (elpy-shell--with-maybe-echo | |
941 | (python-shell-send-string | |
942 | (python-shell-buffer-substring beg end))))) | |
943 | (python-nav-forward-statement))) | |
944 | ||
945 | (defun elpy-shell-send-top-statement-and-step () | |
946 | "Send the current or next top-level statement to the Python shell and step. | |
947 | ||
948 | If the current line is part of a top-level statement, sends this | |
949 | top-level statement. Otherwise, skips forward to the next code | |
950 | line and sends the corresponding top-level statement." | |
951 | (interactive) | |
952 | (elpy-shell--ensure-shell-running) | |
953 | (let* ((beg (progn (elpy-shell--nav-beginning-of-top-statement) (point))) | |
954 | (end (progn (elpy-shell--nav-end-of-statement) (point)))) | |
955 | (elpy-shell--flash-and-message-region beg end) | |
956 | (if (string-match-p "\\`[^\n]*\\'" (buffer-substring beg end)) | |
957 | ;; single line | |
958 | (elpy-shell-send-statement-and-step) | |
959 | ;; multiple lines | |
960 | (elpy-shell--add-to-shell-history (buffer-substring beg end)) | |
961 | (elpy-shell--with-maybe-echo | |
962 | (python-shell-send-string (python-shell-buffer-substring beg end))) | |
963 | (setq mark-active nil) | |
964 | (python-nav-forward-statement)))) | |
965 | ||
966 | (defun elpy-shell-send-defun-and-step () | |
967 | "Send the function definition that contains the current line | |
968 | to the Python shell and steps. | |
969 | ||
970 | See `elpy-shell--nav-beginning-of-def' for details." | |
971 | (interactive) | |
972 | (if (elpy-shell--nav-beginning-of-defun) | |
973 | (elpy-shell-send-statement-and-step) | |
974 | (message "There is no function definition that includes the current line."))) | |
975 | ||
976 | (defun elpy-shell-send-defclass-and-step () | |
977 | "Send the class definition that contains the current line to | |
978 | the Python shell and steps. | |
979 | ||
980 | See `elpy-shell--nav-beginning-of-def' for details." | |
981 | (interactive) | |
982 | (if (elpy-shell--nav-beginning-of-defclass) | |
983 | (elpy-shell-send-statement-and-step) | |
984 | (message "There is no class definition that includes the current line."))) | |
985 | ||
986 | (defun elpy-shell-send-group-and-step () | |
987 | "Send the current or next group of top-level statements to the Python shell and step. | |
988 | ||
989 | A sequence of top-level statements is a group if they are not | |
990 | separated by empty lines. Empty lines within each top-level | |
991 | statement are ignored. | |
992 | ||
993 | If the point is within a top-level statement, send the group | |
994 | around this statement. Otherwise, go to the top-level statement | |
995 | below point and send the group around this statement." | |
996 | (interactive) | |
997 | (elpy-shell--ensure-shell-running) | |
998 | (let* ((beg (progn (elpy-shell--nav-beginning-of-group) (point))) | |
999 | (end (progn | |
1000 | ;; go forward to end of group | |
1001 | (unless (python-info-current-line-comment-p) | |
1002 | (elpy-shell--nav-end-of-statement)) | |
1003 | (let ((p)) | |
1004 | (while (not (eq p (point))) | |
1005 | (setq p (point)) | |
1006 | (forward-line) | |
1007 | (if (elpy-shell--current-line-only-whitespace-p) | |
1008 | (goto-char p) ;; done | |
1009 | (unless (python-info-current-line-comment-p) | |
1010 | (elpy-shell--nav-end-of-statement))))) | |
1011 | (point)))) | |
1012 | (if (> end beg) | |
1013 | (progn | |
1014 | (elpy-shell--flash-and-message-region beg end) | |
1015 | ;; send the region and jump to next statement | |
1016 | (if (string-match-p "\\`[^\n]*\\'" (buffer-substring beg end)) | |
1017 | ;; single line | |
1018 | (elpy-shell-send-statement-and-step) | |
1019 | ;; multiple lines | |
1020 | (unless elpy-shell-echo-input | |
1021 | (elpy-shell--append-to-shell-output "\n")) | |
1022 | (elpy-shell--add-to-shell-history (buffer-substring beg end)) | |
1023 | (elpy-shell--with-maybe-echo | |
1024 | (python-shell-send-string | |
1025 | (python-shell-buffer-substring beg end))) | |
1026 | (python-nav-forward-statement))) | |
1027 | (goto-char (point-max))) | |
1028 | (setq mark-active nil))) | |
1029 | ||
1030 | (defun elpy-shell-send-codecell-and-step () | |
1031 | "Send the current code cell to the Python shell and step. | |
1032 | ||
1033 | Signals an error if the point is not inside a code cell. | |
1034 | ||
1035 | Cell beginnings and cell boundaries can be customized via the | |
1036 | variables `elpy-shell-cell-boundary-regexp' and | |
1037 | `elpy-shell-codecell-beginning-regexp', which see." | |
1038 | (interactive) | |
1039 | (let ((beg (save-excursion | |
1040 | (end-of-line) | |
1041 | (re-search-backward elpy-shell-cell-boundary-regexp nil t) | |
1042 | (beginning-of-line) | |
1043 | (and (string-match-p elpy-shell-codecell-beginning-regexp | |
1044 | (thing-at-point 'line)) | |
1045 | (point)))) | |
1046 | (end (save-excursion | |
1047 | (forward-line) | |
1048 | (if (re-search-forward elpy-shell-cell-boundary-regexp nil t) | |
1049 | (forward-line -1) | |
1050 | (goto-char (point-max))) | |
1051 | (end-of-line) | |
1052 | (point)))) | |
1053 | (if beg | |
1054 | (progn | |
1055 | (elpy-shell--flash-and-message-region beg end) | |
1056 | (unless elpy-shell-echo-input | |
1057 | (elpy-shell--append-to-shell-output "\n")) | |
1058 | (elpy-shell--add-to-shell-history (buffer-substring beg end)) | |
1059 | (elpy-shell--with-maybe-echo | |
1060 | (python-shell-send-string (python-shell-buffer-substring beg end))) | |
1061 | (goto-char end) | |
1062 | (python-nav-forward-statement)) | |
1063 | (message "Not in a codecell.")))) | |
1064 | ||
1065 | (defun elpy-shell-send-region-or-buffer-and-step (&optional arg) | |
1066 | "Send the active region or the buffer to the Python shell and step. | |
1067 | ||
1068 | If there is an active region, send that. Otherwise, send the | |
1069 | whole buffer. | |
1070 | ||
1071 | In Emacs 24.3 and later, without prefix argument and when there | |
1072 | is no active region, this will escape the Python idiom of if | |
1073 | __name__ == '__main__' to be false to avoid accidental execution | |
1074 | of code. With prefix argument, this code is executed." | |
1075 | (interactive "P") | |
1076 | (if (use-region-p) | |
1077 | (elpy-shell--flash-and-message-region (region-beginning) (region-end)) | |
1078 | (elpy-shell--flash-and-message-region (point-min) (point-max))) | |
1079 | (elpy-shell--with-maybe-echo | |
1080 | (elpy-shell--send-region-or-buffer-internal arg)) | |
1081 | (if (use-region-p) | |
1082 | (goto-char (region-end)) | |
1083 | (goto-char (point-max)))) | |
1084 | ||
1085 | (defun elpy-shell--send-region-or-buffer-internal (&optional arg) | |
1086 | "Send the active region or the buffer to the Python shell and step. | |
1087 | ||
1088 | If there is an active region, send that. Otherwise, send the | |
1089 | whole buffer. | |
1090 | ||
1091 | In Emacs 24.3 and later, without prefix argument and when there | |
1092 | is no active region, this will escape the Python idiom of if | |
1093 | __name__ == '__main__' to be false to avoid accidental execution | |
1094 | of code. With prefix argument, this code is executed." | |
1095 | (interactive "P") | |
1096 | (elpy-shell--ensure-shell-running) | |
1097 | (unless elpy-shell-echo-input (elpy-shell--append-to-shell-output "\n")) | |
1098 | (let ((if-main-regex "^if +__name__ +== +[\"']__main__[\"'] *:") | |
1099 | (has-if-main-and-removed nil)) | |
1100 | (if (use-region-p) | |
1101 | (let ((region (python-shell-buffer-substring | |
1102 | (region-beginning) (region-end))) | |
1103 | (region-original (buffer-substring | |
1104 | (region-beginning) (region-end)))) | |
1105 | (when (string-match "\t" region) | |
1106 | (message "Region contained tabs, this might cause weird errors")) | |
1107 | ;; python-shell-buffer-substring (intentionally?) does not accurately | |
1108 | ;; respect (region-beginning); it always start on the first character | |
1109 | ;; of the respective line even if that's before the region beginning | |
1110 | ;; Here we post-process the output to remove the characters before | |
1111 | ;; (region-beginning) and the start of the line. The end of the region | |
1112 | ;; is handled correctly and needs no special treatment. | |
1113 | (let* ((bounds (save-excursion | |
1114 | (goto-char (region-beginning)) | |
1115 | (bounds-of-thing-at-point 'line))) | |
1116 | (used-part (string-trim | |
1117 | (buffer-substring-no-properties | |
1118 | (car bounds) | |
1119 | (min (cdr bounds) (region-end))))) | |
1120 | (relevant-part (string-trim | |
1121 | (buffer-substring-no-properties | |
1122 | (max (car bounds) (region-beginning)) | |
1123 | (min (cdr bounds) (region-end)))))) | |
1124 | (setq region | |
1125 | ;; replace just first match | |
1126 | (replace-regexp-in-string | |
1127 | (concat "\\(" (regexp-quote used-part) "\\)\\(?:.*\n?\\)*\\'") | |
1128 | relevant-part | |
1129 | region t t 1)) | |
1130 | (elpy-shell--add-to-shell-history region-original) | |
1131 | (python-shell-send-string region))) | |
1132 | (unless arg | |
1133 | (save-excursion | |
1134 | (goto-char (point-min)) | |
1135 | (setq has-if-main-and-removed (re-search-forward if-main-regex nil t)))) | |
1136 | (python-shell-send-buffer arg)) | |
1137 | (when has-if-main-and-removed | |
1138 | (message (concat "Removed if __name__ == '__main__' construct, " | |
1139 | "use a prefix argument to evaluate."))))) | |
1140 | ||
1141 | (defun elpy-shell-send-buffer-and-step (&optional arg) | |
1142 | "Send entire buffer to Python shell. | |
1143 | ||
1144 | In Emacs 24.3 and later, without prefix argument, this will | |
1145 | escape the Python idiom of if __name__ == '__main__' to be false | |
1146 | to avoid accidental execution of code. With prefix argument, this | |
1147 | code is executed." | |
1148 | (interactive "P") | |
1149 | (let ((p)) | |
1150 | (save-mark-and-excursion | |
1151 | (deactivate-mark) | |
1152 | (elpy-shell-send-region-or-buffer-and-step arg) | |
1153 | (setq p (point))) | |
1154 | (goto-char p))) | |
1155 | ||
1156 | (defun elpy-shell--add-to-shell-history (string) | |
1157 | "Add STRING to the shell command history." | |
1158 | (when elpy-shell-add-to-shell-history | |
1159 | (with-current-buffer (process-buffer (elpy-shell-get-or-create-process)) | |
1160 | (comint-add-to-input-history (string-trim string))))) | |
1161 | ||
1162 | ||
1163 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
1164 | ;; Send command variations (with/without step; with/without go) | |
1165 | ||
1166 | (defun elpy-shell--send-with-step-go (step-fun step go my-prefix-arg) | |
1167 | "Run a function with STEP and/or GO. | |
1168 | ||
1169 | STEP-FUN should be a function that sends something to the shell | |
1170 | and moves point to code position right after what has been sent. | |
1171 | ||
1172 | When STEP is nil, keeps point position. When GO is non-nil, | |
1173 | switches focus to Python shell buffer." | |
1174 | (let ((orig (point))) | |
1175 | (setq current-prefix-arg my-prefix-arg) | |
1176 | (call-interactively step-fun) | |
1177 | (unless step | |
1178 | (goto-char orig))) | |
1179 | (when go | |
1180 | (elpy-shell-switch-to-shell))) | |
1181 | ||
1182 | (defmacro elpy-shell--defun-step-go (fun-and-step) | |
1183 | "Defines fun, fun-and-go, fun-and-step-and-go for the given FUN-AND-STEP function." | |
1184 | (let ((name (string-remove-suffix "-and-step" (symbol-name fun-and-step)))) | |
1185 | (list | |
1186 | 'progn | |
1187 | (let ((fun (intern name))) | |
1188 | `(defun ,fun (&optional arg) | |
1189 | ,(concat "Run `" (symbol-name fun-and-step) "' but retain point position.") | |
1190 | (interactive "P") | |
1191 | (elpy-shell--send-with-step-go ',fun-and-step nil nil arg))) | |
1192 | (let ((fun-and-go (intern (concat name "-and-go")))) | |
1193 | `(defun ,fun-and-go (&optional arg) | |
1194 | ,(concat "Run `" (symbol-name fun-and-step) "' but retain point position and switch to Python shell.") | |
1195 | (interactive "P") | |
1196 | (elpy-shell--send-with-step-go ',fun-and-step nil t arg))) | |
1197 | (let ((fun-and-step-and-go (intern (concat name "-and-step-and-go")))) | |
1198 | `(defun ,fun-and-step-and-go (&optional arg) | |
1199 | ,(concat "Run `" (symbol-name fun-and-step) "' and switch to Python shell.") | |
1200 | (interactive "P") | |
1201 | (elpy-shell--send-with-step-go ',fun-and-step t t arg)))))) | |
1202 | ||
1203 | (elpy-shell--defun-step-go elpy-shell-send-statement-and-step) | |
1204 | (elpy-shell--defun-step-go elpy-shell-send-top-statement-and-step) | |
1205 | (elpy-shell--defun-step-go elpy-shell-send-defun-and-step) | |
1206 | (elpy-shell--defun-step-go elpy-shell-send-defclass-and-step) | |
1207 | (elpy-shell--defun-step-go elpy-shell-send-group-and-step) | |
1208 | (elpy-shell--defun-step-go elpy-shell-send-codecell-and-step) | |
1209 | (elpy-shell--defun-step-go elpy-shell-send-region-or-buffer-and-step) | |
1210 | (elpy-shell--defun-step-go elpy-shell-send-buffer-and-step) | |
1211 | ||
1212 | ||
1213 | ;;;;;;;;;;;;;;;;;;;;;;; | |
1214 | ;; Debugging features | |
1215 | ||
1216 | (when (version<= "25" emacs-version) | |
1217 | ||
1218 | (defun elpy-pdb--refresh-breakpoints (lines) | |
1219 | "Add new breakpoints at lines LINES of the current buffer." | |
1220 | ;; Forget old breakpoints | |
1221 | (python-shell-send-string-no-output "import bdb as __bdb; __bdb.Breakpoint.bplist={}; __bdb.Breakpoint.next=1;__bdb.Breakpoint.bpbynumber=[None]") | |
1222 | (python-shell-send-string-no-output "import pdb; __pdbi = pdb.Pdb()") | |
1223 | (dolist (line lines) | |
1224 | (python-shell-send-string-no-output | |
1225 | (format "__pdbi.set_break('''%s''', %s)" (buffer-file-name) line)))) | |
1226 | ||
1227 | (defun elpy-pdb--start-pdb (&optional output) | |
1228 | "Start pdb on the current script. | |
1229 | ||
1230 | if OUTPUT is non-nil, display the prompt after execution." | |
1231 | (let ((string (format "__pdbi._runscript('''%s''')" (buffer-file-name)))) | |
1232 | (if output | |
1233 | (python-shell-send-string string) | |
1234 | (python-shell-send-string-no-output string)))) | |
1235 | ||
1236 | (defun elpy-pdb--get-breakpoint-positions () | |
1237 | "Return a list of lines with breakpoints." | |
1238 | (let* ((overlays (overlay-lists)) | |
1239 | (overlays (append (car overlays) (cdr overlays))) | |
1240 | (bp-lines '())) | |
1241 | (dolist (ov overlays) | |
1242 | (when (overlay-get ov 'elpy-breakpoint) | |
1243 | (push (line-number-at-pos (overlay-start ov)) | |
1244 | bp-lines))) | |
1245 | bp-lines)) | |
1246 | ||
1247 | (defun elpy-pdb-debug-buffer (&optional arg) | |
1248 | "Run pdb on the current buffer. | |
1249 | ||
1250 | If breakpoints are set in the current buffer, jump to the first one. | |
1251 | If no breakpoints are set, debug from the beginning of the script. | |
1252 | ||
1253 | With a prefix argument, ignore the existing breakpoints." | |
1254 | (interactive "P") | |
1255 | (if (not (buffer-file-name)) | |
1256 | (error "Debugging only work for buffers visiting a file") | |
1257 | (elpy-shell--ensure-shell-running) | |
1258 | (save-buffer) | |
1259 | (let ((bp-lines (elpy-pdb--get-breakpoint-positions))) | |
1260 | (if (or arg (= 0 (length bp-lines))) | |
1261 | (progn | |
1262 | (elpy-pdb--refresh-breakpoints '()) | |
1263 | (elpy-pdb--start-pdb t)) | |
1264 | (elpy-pdb--refresh-breakpoints bp-lines) | |
1265 | (elpy-pdb--start-pdb) | |
1266 | (python-shell-send-string "continue"))) | |
1267 | (elpy-shell-display-buffer))) | |
1268 | ||
1269 | (defun elpy-pdb-break-at-point () | |
1270 | "Run pdb on the current buffer and break at the current line. | |
1271 | ||
1272 | Ignore the existing breakpoints. | |
1273 | Pdb can directly exit if the current line is not a statement | |
1274 | that is actually run (blank line, comment line, ...)." | |
1275 | (interactive) | |
1276 | (if (not (buffer-file-name)) | |
1277 | (error "Debugging only work for buffers visiting a file") | |
1278 | (elpy-shell--ensure-shell-running) | |
1279 | (save-buffer) | |
1280 | (elpy-pdb--refresh-breakpoints (list (line-number-at-pos))) | |
1281 | (elpy-pdb--start-pdb) | |
1282 | (python-shell-send-string "continue") | |
1283 | (elpy-shell-display-buffer))) | |
1284 | ||
1285 | (defun elpy-pdb-debug-last-exception () | |
1286 | "Run post-mortem pdb on the last exception." | |
1287 | (interactive) | |
1288 | (elpy-shell--ensure-shell-running) | |
1289 | ;; check if there is a last exception | |
1290 | (if (not (with-current-buffer (format "*%s*" | |
1291 | (python-shell-get-process-name nil)) | |
1292 | (save-excursion | |
1293 | (goto-char (point-max)) | |
1294 | (search-backward "Traceback (most recent call last):" | |
1295 | nil t)))) | |
1296 | (error "No traceback on the current shell") | |
1297 | (python-shell-send-string | |
1298 | "import pdb as __pdb;__pdb.pm()")) | |
1299 | (elpy-shell-display-buffer)) | |
1300 | ||
1301 | ;; Fringe indicators | |
1302 | ||
1303 | (when (fboundp 'define-fringe-bitmap) | |
1304 | (define-fringe-bitmap 'elpy-breakpoint-fringe-marker | |
1305 | (vector | |
1306 | #b00000000 | |
1307 | #b00111100 | |
1308 | #b01111110 | |
1309 | #b01111110 | |
1310 | #b01111110 | |
1311 | #b01111110 | |
1312 | #b00111100 | |
1313 | #b00000000))) | |
1314 | ||
1315 | (defcustom elpy-breakpoint-fringe-face 'elpy-breakpoint-fringe-face | |
1316 | "Face for breakpoint bitmaps appearing on the fringe." | |
1317 | :type 'face | |
1318 | :group 'elpy) | |
1319 | ||
1320 | (defface elpy-breakpoint-fringe-face | |
1321 | '((t (:foreground "red" | |
1322 | :box (:line-width 1 :color "red" :style released-button)))) | |
1323 | "Face for breakpoint bitmaps appearing on the fringe." | |
1324 | :group 'elpy) | |
1325 | ||
1326 | (defun elpy-pdb-toggle-breakpoint-at-point (&optional arg) | |
1327 | "Add or remove a breakpoint at the current line. | |
1328 | ||
1329 | With a prefix argument, remove all the breakpoints from the current | |
1330 | region or buffer." | |
1331 | (interactive "P") | |
1332 | (if arg | |
1333 | (elpy-pdb-clear-breakpoints) | |
1334 | (let ((overlays (overlays-in (line-beginning-position) | |
1335 | (line-end-position))) | |
1336 | bp-at-line) | |
1337 | ;; Check if already a breakpoint | |
1338 | (while overlays | |
1339 | (let ((overlay (pop overlays))) | |
1340 | (when (overlay-get overlay 'elpy-breakpoint) | |
1341 | (setq bp-at-line t)))) | |
1342 | (if bp-at-line | |
1343 | ;; If so, remove it | |
1344 | (remove-overlays (line-beginning-position) | |
1345 | (line-end-position) | |
1346 | 'elpy-breakpoint t) | |
1347 | ;; Check it the line is empty | |
1348 | (if (not (save-excursion | |
1349 | (beginning-of-line) | |
1350 | (looking-at "[[:space:]]*$"))) | |
1351 | ;; Else add a new breakpoint | |
1352 | (let* ((ov (make-overlay (line-beginning-position) | |
1353 | (+ 1 (line-beginning-position)))) | |
1354 | (marker-string "*fringe-dummy*") | |
1355 | (marker-length (length marker-string))) | |
1356 | (put-text-property 0 marker-length | |
1357 | 'display | |
1358 | (list 'left-fringe | |
1359 | 'elpy-breakpoint-fringe-marker | |
1360 | 'elpy-breakpoint-fringe-face) | |
1361 | marker-string) | |
1362 | (overlay-put ov 'before-string marker-string) | |
1363 | (overlay-put ov 'priority 200) | |
1364 | (overlay-put ov 'elpy-breakpoint t))))))) | |
1365 | ||
1366 | (defun elpy-pdb-clear-breakpoints () | |
1367 | "Remove the breakpoints in the current region or buffer." | |
1368 | (if (use-region-p) | |
1369 | (remove-overlays (region-beginning) (region-end) 'elpy-breakpoint t) | |
1370 | (remove-overlays (point-min) (point-max) 'elpy-breakpoint t)))) | |
1371 | ||
1372 | ||
1373 | ;;;;;;;;;;;;;;;;;;;;;;; | |
1374 | ;; Deprecated functions | |
1375 | ||
1376 | (defun elpy-use-ipython (&optional _ipython) | |
1377 | "Deprecated; see https://elpy.readthedocs.io/en/latest/ide.html#interpreter-setup" | |
1378 | (error "elpy-use-ipython is deprecated; see https://elpy.readthedocs.io/en/latest/ide.html#interpreter-setup")) | |
1379 | (make-obsolete 'elpy-use-ipython nil "Jan 2017") | |
1380 | ||
1381 | (defun elpy-use-cpython (&optional _cpython) | |
1382 | "Deprecated; see https://elpy.readthedocs.io/en/latest/ide.html#interpreter-setup" | |
1383 | (error "elpy-use-cpython is deprecated; see https://elpy.readthedocs.io/en/latest/ide.html#interpreter-setup")) | |
1384 | (make-obsolete 'elpy-use-cpython nil "Jan 2017") | |
1385 | ||
1386 | (provide 'elpy-shell) | |
1387 | ;;; elpy-shell.el ends here |