]> crepu.dev Git - config.git/blame - djavu-asus/elpa/elpy-20230803.1455/elpy.el
Configuracion en desarrollo PC pega
[config.git] / djavu-asus / elpa / elpy-20230803.1455 / elpy.el
CommitLineData
53e6db90
DC
1;;; elpy.el --- Emacs Python Development Environment -*- lexical-binding: t -*-
2
3;; Copyright (C) 2012-2019 Jorgen Schaefer
4
5;; Author: Jorgen Schaefer <contact@jorgenschaefer.de>, Gaby Launay <gaby.launay@protonmail.com>
6;; URL: https://github.com/jorgenschaefer/elpy
7;; Version: 1.35.0
8;; Keywords: Python, IDE, Languages, Tools
9;; Package-Requires: ((company "0.9.10") (emacs "24.4") (highlight-indentation "0.7.0") (pyvenv "1.20") (yasnippet "0.13.0") (s "1.12.0"))
10
11;; This program is free software; you can redistribute it and/or
12;; modify it under the terms of the GNU General Public License
13;; as published by the Free Software Foundation; either version 3
14;; of the License, or (at your option) any later version.
15
16;; This program is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with this program. If not, see <http://www.gnu.org/licenses/>.
23
24;;; Commentary:
25
26;; The Emacs Lisp Python Environment in Emacs
27
28;; Elpy is an Emacs package to bring powerful Python editing to Emacs.
29;; It combines a number of existing Emacs packages, both written in
30;; Emacs Lisp as well as Python.
31
32;; For more information, read the Elpy manual:
33
34;; https://elpy.readthedocs.io/en/latest/index.html
35
36;;; Code:
37
38(require 'cus-edit)
39(require 'etags)
40(require 'files-x)
41(require 'grep)
42(require 'hideshow)
43(require 'ido)
44(require 'json)
45(require 'python)
46(require 'subr-x)
47(require 'xref nil t)
48(require 'cl-lib) ; for `cl-every', `cl-copy-list', `cl-delete-if-not'
49
50(require 'elpy-refactor)
51(require 'elpy-django)
52(require 'elpy-profile)
53(require 'elpy-shell)
54(require 'elpy-rpc)
55(require 'pyvenv)
56
57(defconst elpy-version "1.35.0"
58 "The version of the Elpy Lisp code.")
59
60;;;;;;;;;;;;;;;;;;;;;;
61;;; User customization
62
63(defgroup elpy nil
64 "The Emacs Lisp Python Environment."
65 :prefix "elpy-"
66 :group 'languages)
67
68(defcustom elpy-mode-hook nil
69 "Hook run when `elpy-mode' is enabled.
70
71This can be used to enable minor modes for Python development."
72 :type 'hook
73 :options '(subword-mode hl-line-mode)
74 :group 'elpy)
75
76(defcustom elpy-modules '(elpy-module-sane-defaults
77 elpy-module-company
78 elpy-module-eldoc
79 elpy-module-flymake
80 elpy-module-highlight-indentation
81 elpy-module-pyvenv
82 elpy-module-yasnippet
83 elpy-module-django)
84 "Which Elpy modules to use.
85
86Elpy can use a number of modules for additional features, which
87can be individually enabled or disabled."
88 :type '(set (const :tag "Inline code completion (company-mode)"
89 elpy-module-company)
90 (const :tag "Show function signatures (ElDoc)"
91 elpy-module-eldoc)
92 (const :tag "Highlight syntax errors (Flymake)"
93 elpy-module-flymake)
94 (const :tag "Code folding"
95 elpy-module-folding)
96 (const :tag "Show the virtualenv in the mode line (pyvenv)"
97 elpy-module-pyvenv)
98 (const :tag "Display indentation markers (highlight-indentation)"
99 elpy-module-highlight-indentation)
100 (const :tag "Expand code snippets (YASnippet)"
101 elpy-module-yasnippet)
102 (const :tag "Django configurations (Elpy-Django)"
103 elpy-module-django)
104 (const :tag "Automatically update documentation (Autodoc)."
105 elpy-module-autodoc)
106 (const :tag "Configure some sane defaults for Emacs"
107 elpy-module-sane-defaults))
108 :group 'elpy)
109
110(defcustom elpy-project-ignored-directories
111 '(".tox" "build" "dist" ".cask" ".ipynb_checkpoints")
112 "Directories ignored by functions working on the whole project.
113This is in addition to `vc-directory-exclusion-list'
114and `grep-find-ignored-directories', as appropriate."
115 :type '(repeat string)
116 :safe (lambda (val)
117 (cl-every #'stringp val))
118 :group 'elpy)
119
120(defun elpy-project-ignored-directories ()
121 "Compute the list of ignored directories for project.
122Combines
123 `elpy-project-ignored-directories'
124 `vc-directory-exclusion-list'
125 `grep-find-ignored-directories'"
126 (delete-dups
127 (append elpy-project-ignored-directories
128 vc-directory-exclusion-list
129 (if (fboundp 'rgrep-find-ignored-directories)
130 (rgrep-find-ignored-directories (elpy-project-root))
131 (cl-delete-if-not #'stringp (cl-copy-list
132 grep-find-ignored-directories))))))
133
134(defcustom elpy-project-root nil
135 "The root of the project the current buffer is in.
136
137There is normally no use in setting this variable directly, as
138Elpy tries to detect the project root automatically. See
139`elpy-project-root-finder-functions' for a way of influencing
140this.
141
142Setting this variable globally will override Elpy's automatic
143project detection facilities entirely.
144
145Alternatively, you can set this in file- or directory-local
146variables using \\[add-file-local-variable] or
147\\[add-dir-local-variable].
148
149Do not use this variable in Emacs Lisp programs. Instead, call
150the `elpy-project-root' function. It will do the right thing."
151 :type 'directory
152 :safe 'file-directory-p
153 :group 'elpy)
154(make-variable-buffer-local 'elpy-project-root)
155
156(defcustom elpy-project-root-finder-functions
157 '(elpy-project-find-projectile-root
158 elpy-project-find-python-root
159 elpy-project-find-git-root
160 elpy-project-find-hg-root
161 elpy-project-find-svn-root)
162 "List of functions to ask for the current project root.
163
164These will be checked in turn. The first directory found is used."
165 :type '(set (const :tag "Projectile project root"
166 elpy-project-find-projectile-root)
167 (const :tag "Python project (setup.py, setup.cfg)"
168 elpy-project-find-python-root)
169 (const :tag "Git repository root (.git)"
170 elpy-project-find-git-root)
171 (const :tag "Mercurial project root (.hg)"
172 elpy-project-find-hg-root)
173 (const :tag "Subversion project root (.svn)"
174 elpy-project-find-svn-root)
175 (const :tag "Django project root (manage.py, django-admin.py)"
176 elpy-project-find-django-root))
177 :group 'elpy)
178
179(make-obsolete-variable 'elpy-company-hide-modeline
180 'elpy-remove-modeline-lighter
181 "1.10.0")
182(defcustom elpy-remove-modeline-lighter t
183 "Non-nil if Elpy should remove most mode line display.
184
185Modeline shows many minor modes currently active. For Elpy, this is mostly
186uninteresting information, but if you rely on your modeline in other modes,
187you might want to keep it."
188 :type 'boolean
189 :group 'elpy)
190
191(defcustom elpy-company-post-completion-function 'ignore
192 "Your preferred Company post completion function.
193
194Elpy can automatically insert parentheses after completing
195callable objects.
196
197The heuristic on when to insert these parentheses can easily be
198wrong, though, so this is disabled by default. Set this variable
199to the function `elpy-company-post-complete-parens' to enable
200this feature."
201 :type '(choice (const :tag "Ignore post complete" ignore)
202 (const :tag "Complete callables with parens"
203 elpy-company-post-complete-parens)
204 (function :tag "Other function"))
205 :group 'elpy)
206
207(defcustom elpy-get-info-from-shell nil
208 "If t, use the shell to gather docstrings and completions.
209
210Normally elpy provides completion and documentation using static code analysis (from jedi). With this option set to t, elpy will add the completion candidates and the docstrings from the associated python shell; This activates fallback completion candidates for cases when static code analysis fails."
211 :type 'boolean
212 :group 'elpy)
213
214(defcustom elpy-get-info-from-shell-timeout 1
215 "Timeout (in seconds) for gathering information from the shell."
216 :type 'number
217 :group 'elpy)
218
219(defcustom elpy-eldoc-show-current-function t
220 "If true, show the current function if no calltip is available.
221
222When Elpy can not find the calltip of the function call at point,
223it can show the name of the function or class and method being
224edited instead. Setting this variable to nil disables this feature."
225 :type 'boolean
226 :group 'elpy)
227
228(defcustom elpy-test-runner 'elpy-test-discover-runner
229 "The test runner to use to run tests."
230 :type '(choice (const :tag "Unittest Discover" elpy-test-discover-runner)
231 (const :tag "Green" elpy-test-green-runner)
232 (const :tag "Django Discover" elpy-test-django-runner)
233 (const :tag "Nose" elpy-test-nose-runner)
234 (const :tag "py.test" elpy-test-pytest-runner)
235 (const :tag "Twisted Trial" elpy-test-trial-runner))
236 :safe 'elpy-test-runner-p
237 :group 'elpy)
238
239(defcustom elpy-test-discover-runner-command '("python-shell-interpreter" "-m" "unittest")
240 "The command to use for `elpy-test-discover-runner'.
241If the string \"python-shell-interpreter\" is present, it will be replaced with
242the value of `python-shell-interpreter'."
243 :type '(repeat string)
244 :group 'elpy)
245
246(defcustom elpy-test-green-runner-command '("green")
247 "The command to use for `elpy-test-green-runner'."
248 :type '(repeat string)
249 :group 'elpy)
250
251(defcustom elpy-test-nose-runner-command '("nosetests")
252 "The command to use for `elpy-test-nose-runner'."
253 :type '(repeat string)
254 :group 'elpy)
255
256(defcustom elpy-test-trial-runner-command '("trial")
257 "The command to use for `elpy-test-trial-runner'."
258 :type '(repeat string)
259 :group 'elpy)
260
261(defcustom elpy-test-pytest-runner-command '("py.test")
262 "The command to use for `elpy-test-pytest-runner'."
263 :type '(repeat string)
264 :group 'elpy)
265
266(defcustom elpy-test-compilation-function 'compile
267 "Function used by `elpy-test-run' to run a test command.
268
269The function should behave similarly to `compile'. Another good
270option is `pdb'."
271 :type 'string
272 :group 'elpy)
273
274(defcustom elpy-rgrep-file-pattern "*.py"
275 "FILES to use for `elpy-rgrep-symbol'."
276 :type 'string
277 :group 'elpy)
278
279(defcustom elpy-disable-backend-error-display t
280 "Non-nil if Elpy should disable backend error display."
281 :type 'boolean
282 :group 'elpy)
283
284
285(defcustom elpy-formatter nil
286 "Auto formatter used by `elpy-format-code'.
287
288if nil, use the first formatter found amongst
289`yapf' , `autopep8' and `black'."
290 :type '(choice (const :tag "First one found" nil)
291 (const :tag "Yapf" yapf)
292 (const :tag "autopep8" autopep8)
293 (const :tag "Black" black))
294 :group 'elpy)
295
296(defcustom elpy-syntax-check-command "flake8"
297 "The command to use for `elpy-check'."
298 :type 'string
299 :group 'elpy)
300
301;;;;;;;;;;;;;
302;;; Elpy Mode
303(defvar elpy-refactor-map
304 (let ((map (make-sparse-keymap "Refactor")))
305 (define-key map (kbd "i") (cons (format "%snline"
306 (propertize "i" 'face 'bold))
307 'elpy-refactor-inline))
308 (define-key map (kbd "f") (cons (format "%sunction extraction"
309 (propertize "f" 'face 'bold))
310 'elpy-refactor-extract-function))
311 (define-key map (kbd "v") (cons (format "%sariable extraction"
312 (propertize "v" 'face 'bold))
313 'elpy-refactor-extract-variable))
314 (define-key map (kbd "r") (cons (format "%sename"
315 (propertize "r" 'face 'bold))
316 'elpy-refactor-rename))
317 map)
318 "Key map for the refactor command.")
319
320(defvar elpy-mode-map
321 (let ((map (make-sparse-keymap)))
322 ;; Alphabetical order to make it easier to find free C-c C-X
323 ;; bindings in the future. Heh.
324
325 ;; (define-key map (kbd "<backspace>") 'python-indent-dedent-line-backspace)
326 ;; (define-key map (kbd "<backtab>") 'python-indent-dedent-line)
327
328 ;; (define-key map (kbd "C-M-x") 'python-shell-send-defun)
329 ;; (define-key map (kbd "C-c <") 'python-indent-shift-left)
330 ;; (define-key map (kbd "C-c >") 'python-indent-shift-right)
331 (define-key map (kbd "C-c RET") 'elpy-importmagic-add-import)
332 (define-key map (kbd "C-c C-b") 'elpy-nav-expand-to-indentation)
333 (define-key map (kbd "C-c C-c") 'elpy-shell-send-region-or-buffer)
334 (define-key map (kbd "C-c C-d") 'elpy-doc)
335 (define-key map (kbd "C-c C-e") 'elpy-multiedit-python-symbol-at-point)
336 (define-key map (kbd "C-c C-f") 'elpy-find-file)
337 (define-key map (kbd "C-c C-n") 'elpy-flymake-next-error)
338 (define-key map (kbd "C-c C-o") 'elpy-occur-definitions)
339 (define-key map (kbd "C-c C-p") 'elpy-flymake-previous-error)
340 (define-key map (kbd "C-c @ C-c") 'elpy-folding-toggle-at-point)
341 (define-key map (kbd "C-c @ C-b") 'elpy-folding-toggle-docstrings)
342 (define-key map (kbd "C-c @ C-m") 'elpy-folding-toggle-comments)
343 (define-key map (kbd "C-c @ C-f") 'elpy-folding-hide-leafs)
344 (define-key map (kbd "C-c C-s") 'elpy-rgrep-symbol)
345 (define-key map (kbd "C-c C-t") 'elpy-test)
346 (define-key map (kbd "C-c C-v") 'elpy-check)
347 (define-key map (kbd "C-c C-z") 'elpy-shell-switch-to-shell)
348 (define-key map (kbd "C-c C-k") 'elpy-shell-kill)
349 (define-key map (kbd "C-c C-K") 'elpy-shell-kill-all)
350 (define-key map (kbd "C-c C-r") elpy-refactor-map)
351 (define-key map (kbd "C-c C-x") elpy-django-mode-map)
352
353 (define-key map (kbd "<S-return>") 'elpy-open-and-indent-line-below)
354 (define-key map (kbd "<C-S-return>") 'elpy-open-and-indent-line-above)
355
356 (define-key map (kbd "<C-return>") 'elpy-shell-send-statement-and-step)
357
358 (define-key map (kbd "<C-down>") 'elpy-nav-forward-block)
359 (define-key map (kbd "<C-up>") 'elpy-nav-backward-block)
360 (define-key map (kbd "<C-left>") 'elpy-nav-backward-indent)
361 (define-key map (kbd "<C-right>") 'elpy-nav-forward-indent)
362
363 (define-key map (kbd "<M-down>") 'elpy-nav-move-line-or-region-down)
364 (define-key map (kbd "<M-up>") 'elpy-nav-move-line-or-region-up)
365 (define-key map (kbd "<M-left>") 'elpy-nav-indent-shift-left)
366 (define-key map (kbd "<M-right>") 'elpy-nav-indent-shift-right)
367
368 (unless (fboundp 'xref-find-definitions)
369 (define-key map (kbd "M-.") 'elpy-goto-definition))
370 (if (not (fboundp 'xref-find-definitions-other-window))
371 (define-key map (kbd "C-x 4 M-.") 'elpy-goto-definition-other-window)
372 (define-key map (kbd "C-x 4 M-.") 'xref-find-definitions-other-window))
373 (when (fboundp 'xref-pop-marker-stack)
374 (define-key map (kbd "M-*") 'xref-pop-marker-stack))
375
376 (define-key map (kbd "M-TAB") 'elpy-company-backend)
377
378 map)
379 "Key map for the Emacs Lisp Python Environment.")
380
381(defvar elpy-shell-map
382 (let ((map (make-sparse-keymap)))
383 (define-key map (kbd "e") 'elpy-shell-send-statement)
384 (define-key map (kbd "E") 'elpy-shell-send-statement-and-go)
385 (define-key map (kbd "s") 'elpy-shell-send-top-statement)
386 (define-key map (kbd "S") 'elpy-shell-send-top-statement-and-go)
387 (define-key map (kbd "f") 'elpy-shell-send-defun)
388 (define-key map (kbd "F") 'elpy-shell-send-defun-and-go)
389 (define-key map (kbd "c") 'elpy-shell-send-defclass)
390 (define-key map (kbd "C") 'elpy-shell-send-defclass-and-go)
391 (define-key map (kbd "o") 'elpy-shell-send-group)
392 (define-key map (kbd "O") 'elpy-shell-send-group-and-go)
393 (define-key map (kbd "w") 'elpy-shell-send-codecell)
394 (define-key map (kbd "W") 'elpy-shell-send-codecell-and-go)
395 (define-key map (kbd "r") 'elpy-shell-send-region-or-buffer)
396 (define-key map (kbd "R") 'elpy-shell-send-region-or-buffer-and-go)
397 (define-key map (kbd "b") 'elpy-shell-send-buffer)
398 (define-key map (kbd "B") 'elpy-shell-send-buffer-and-go)
399 (define-key map (kbd "C-e") 'elpy-shell-send-statement-and-step)
400 (define-key map (kbd "C-S-E") 'elpy-shell-send-statement-and-step-and-go)
401 (define-key map (kbd "C-s") 'elpy-shell-send-top-statement-and-step)
402 (define-key map (kbd "C-S-S") 'elpy-shell-send-top-statement-and-step-and-go)
403 (define-key map (kbd "C-f") 'elpy-shell-send-defun-and-step)
404 (define-key map (kbd "C-S-F") 'elpy-shell-send-defun-and-step-and-go)
405 (define-key map (kbd "C-c") 'elpy-shell-send-defclass-and-step)
406 (define-key map (kbd "C-S-C") 'elpy-shell-send-defclass-and-step-and-go)
407 (define-key map (kbd "C-o") 'elpy-shell-send-group-and-step)
408 (define-key map (kbd "C-S-O") 'elpy-shell-send-group-and-step-and-go)
409 (define-key map (kbd "C-w") 'elpy-shell-send-codecell-and-step)
410 (define-key map (kbd "C-S-W") 'elpy-shell-send-codecell-and-step-and-go)
411 (define-key map (kbd "C-r") 'elpy-shell-send-region-or-buffer-and-step)
412 (define-key map (kbd "C-S-R") 'elpy-shell-send-region-or-buffer-and-step-and-go)
413 (define-key map (kbd "C-b") 'elpy-shell-send-buffer-and-step)
414 (define-key map (kbd "C-S-B") 'elpy-shell-send-buffer-and-step-and-go)
415 map)
416 "Key map for the shell related commands.")
417(fset 'elpy-shell-map elpy-shell-map)
418
419(defcustom elpy-shell-command-prefix-key "C-c C-y"
420 "Prefix key used to call elpy shell related commands.
421
422This option need to bet set through `customize' or `customize-set-variable' to be taken into account."
423 :type 'string
424 :group 'elpy
425 :set
426 (lambda (var key)
427 (when (and (boundp var) (symbol-value var))
428 (define-key elpy-mode-map (kbd (symbol-value var)) nil))
429 (when key
430 (define-key elpy-mode-map (kbd key) 'elpy-shell-map)
431 (set var key))))
432
433(defvar elpy-pdb-map
434 (let ((map (make-sparse-keymap)))
435 (define-key map (kbd "d") 'elpy-pdb-debug-buffer)
436 (define-key map (kbd "p") 'elpy-pdb-break-at-point)
437 (define-key map (kbd "e") 'elpy-pdb-debug-last-exception)
438 (define-key map (kbd "b") 'elpy-pdb-toggle-breakpoint-at-point)
439 map)
440 "Key map for the shell related commands.")
441(fset 'elpy-pdb-map elpy-pdb-map)
442(define-key elpy-mode-map (kbd "C-c C-u") 'elpy-pdb-map)
443
444(easy-menu-define elpy-menu elpy-mode-map
445 "Elpy Mode Menu"
446 '("Elpy"
447 ["Documentation" elpy-doc
448 :help "Get documentation for symbol at point"]
449 ["Run Tests" elpy-test
450 :help "Run test at point, or all tests in the project"]
451 ["Go to Definition" elpy-goto-definition
452 :help "Go to the definition of the symbol at point"]
453 ["Go to previous definition" pop-tag-mark
454 :active (if (version< emacs-version "25.1")
455 (not (ring-empty-p find-tag-marker-ring))
456 (> xref-marker-ring-length 0))
457 :help "Return to the position"]
458 ["Complete" elpy-company-backend
459 :keys "M-TAB"
460 :help "Complete at point"]
461 ["Refactor" elpy-refactor
462 :help "Refactor options"]
463 "---"
464 ("Interactive Python"
465 ["Switch to Python Shell" elpy-shell-switch-to-shell
466 :help "Start and switch to the interactive Python"]
467 ["Send Region or Buffer" elpy-shell-send-region-or-buffer
468 :label (if (use-region-p)
469 "Send Region to Python"
470 "Send Buffer to Python")
471 :help "Send the current region or the whole buffer to Python"]
472 ["Send Definition" elpy-shell-send-defun
473 :help "Send current definition to Python"]
474 ["Kill Python shell" elpy-shell-kill
475 :help "Kill the current Python shell"]
476 ["Kill all Python shells" elpy-shell-kill-all
477 :help "Kill all Python shells"])
478 ("Debugging"
479 ["Debug buffer" elpy-pdb-debug-buffer
480 :help "Debug the current buffer using pdb"]
481 ["Debug at point" elpy-pdb-break-at-point
482 :help "Debug the current buffer and stop at the current position"]
483 ["Debug last exception" elpy-pdb-debug-last-exception
484 :help "Run post-mortem pdb on the last exception"]
485 ["Add/remove breakpoint" elpy-pdb-toggle-breakpoint-at-point
486 :help "Add or remove a breakpoint at the current position"])
487 ("Project"
488 ["Find File" elpy-find-file
489 :help "Interactively find a file in the current project"]
490 ["Find Symbol" elpy-rgrep-symbol
491 :help "Find occurrences of a symbol in the current project"]
492 ["Set Project Root" elpy-set-project-root
493 :help "Change the current project root"]
494 ["Set Project Variable" elpy-set-project-variable
495 :help "Configure a project-specific option"])
496 ("Syntax Check"
497 ["Check Syntax" elpy-check
498 :help "Check the syntax of the current file"]
499 ["Next Error" elpy-flymake-next-error
500 :help "Go to the next inline error, if any"]
501 ["Previous Error" elpy-flymake-previous-error
502 :help "Go to the previous inline error, if any"])
503 ("Code folding"
504 ["Hide/show at point" elpy-folding-toggle-at-point
505 :help "Hide or show the block or docstring at point"]
506 ["Hide/show all docstrings" elpy-folding-toggle-docstrings
507 :help "Hide or show all the docstrings"]
508 ["Hide/show all comments" elpy-folding-toggle-comments
509 :help "Hide or show all the comments"]
510 ["Hide leafs" elpy-folding-hide-leafs
511 :help "Hide all leaf blocks (blocks not containing other blocks)"])
512 ("Indentation Blocks"
513 ["Dedent" python-indent-shift-left
514 :help "Dedent current block or region"
515 :suffix (if (use-region-p) "Region" "Block")]
516 ["Indent" python-indent-shift-right
517 :help "Indent current block or region"
518 :suffix (if (use-region-p) "Region" "Block")]
519 ["Up" elpy-nav-move-line-or-region-up
520 :help "Move current block or region up"
521 :suffix (if (use-region-p) "Region" "Block")]
522 ["Down" elpy-nav-move-line-or-region-down
523 :help "Move current block or region down"
524 :suffix (if (use-region-p) "Region" "Block")])
525 "---"
526 ["Configure" elpy-config t]))
527
528(defvar elpy-enabled-p nil
529 "Is Elpy enabled or not.")
530
531;;;###autoload
532(defun elpy-enable (&optional _ignored)
533 "Enable Elpy in all future Python buffers."
534 (interactive)
535 (unless elpy-enabled-p
536 (when (< emacs-major-version 24)
537 (error "Elpy requires Emacs 24 or newer"))
538 (when _ignored
539 (warn "The argument to `elpy-enable' is deprecated, customize `elpy-modules' instead"))
540 (let ((filename (find-lisp-object-file-name 'python-mode
541 'symbol-function)))
542 (when (and filename
543 (string-match "/python-mode\\.el\\'"
544 filename))
545 (error (concat "You are using python-mode.el. "
546 "Elpy only works with python.el from "
547 "Emacs 24 and above"))))
548 (elpy-modules-global-init)
549 (define-key inferior-python-mode-map (kbd "C-c C-z") 'elpy-shell-switch-to-buffer)
550 (add-hook 'python-mode-hook 'elpy-mode)
551 (add-hook 'pyvenv-post-activate-hooks 'elpy-rpc--disconnect)
552 (add-hook 'pyvenv-post-deactivate-hooks 'elpy-rpc--disconnect)
553 (add-hook 'inferior-python-mode-hook 'elpy-shell--enable-output-filter)
554 (add-hook 'python-shell-first-prompt-hook 'elpy-shell--send-setup-code t)
555 ;; Add codecell boundaries highligting
556 (font-lock-add-keywords
557 'python-mode
558 `((,(replace-regexp-in-string "\\\\" "\\\\"
559 elpy-shell-cell-boundary-regexp)
560 0 'elpy-codecell-boundary prepend)))
561 ;; Enable Elpy-mode in the opened python buffer
562 (setq elpy-enabled-p t)
563 (dolist (buffer (buffer-list))
564 (and (not (string-match "^ ?\\*" (buffer-name buffer)))
565 (with-current-buffer buffer
566 (when (string= major-mode 'python-mode)
567 (python-mode) ;; update codecell fontification
568 (elpy-mode t)))))
569 ))
570
571(defun elpy-disable ()
572 "Disable Elpy in all future Python buffers."
573 (interactive)
574 (elpy-modules-global-stop)
575 (define-key inferior-python-mode-map (kbd "C-c C-z") nil)
576 (remove-hook 'python-mode-hook 'elpy-mode)
577 (remove-hook 'pyvenv-post-activate-hooks 'elpy-rpc--disconnect)
578 (remove-hook 'pyvenv-post-deactivate-hooks 'elpy-rpc--disconnect)
579 (remove-hook 'inferior-python-mode-hook 'elpy-shell--enable-output-filter)
580 (remove-hook 'python-shell-first-prompt-hook 'elpy-shell--send-setup-code)
581 ;; Remove codecell boundaries highligting
582 (font-lock-remove-keywords
583 'python-mode
584 `((,(replace-regexp-in-string "\\\\" "\\\\"
585 elpy-shell-cell-boundary-regexp)
586 0 'elpy-codecell-boundary prepend)))
587 (setq elpy-enabled-p nil))
588
589;;;###autoload
590(define-minor-mode elpy-mode
591 "Minor mode in Python buffers for the Emacs Lisp Python Environment.
592
593This mode fully supports virtualenvs. Once you switch a
594virtualenv using \\[pyvenv-workon], you can use
595\\[elpy-rpc-restart] to make the elpy Python process use your
596virtualenv.
597
598\\{elpy-mode-map}"
599 :lighter " Elpy"
600 (unless (derived-mode-p 'python-mode 'python-ts-mode)
601 (error "Elpy only works with `python-mode'"))
602 (unless elpy-enabled-p
603 (error "Please enable Elpy with `(elpy-enable)` before using it"))
604 (when (boundp 'xref-backend-functions)
605 (add-hook 'xref-backend-functions #'elpy--xref-backend nil t))
606 ;; Set this for `elpy-check' command
607 (setq-local python-check-command elpy-syntax-check-command)
608 (cond
609 (elpy-mode
610 (elpy-modules-buffer-init))
611 ((not elpy-mode)
612 (elpy-modules-buffer-stop))))
613
614;;;;;;;;;;;;;;;
615;;; Elpy Config
616
617(defvar elpy-config--related-custom-groups
618 '(("Elpy" elpy "elpy-")
619 ("Python" python "python-")
620 ("Virtual Environments (Pyvenv)" pyvenv "pyvenv-")
621 ("Completion (Company)" company "company-")
622 ("Call Signatures (ElDoc)" eldoc "eldoc-")
623 ("Inline Errors (Flymake)" flymake "flymake-")
624 ("Code folding (hideshow)" hideshow "hs-")
625 ("Snippets (YASnippet)" yasnippet "yas-")
626 ("Directory Grep (rgrep)" grep "grep-")
627 ("Search as You Type (ido)" ido "ido-")
628 ("Django extension" elpy-django "elpy-django-")
629 ("Autodoc extension" elpy-autodoc "elpy-autodoc-")
630 ;; ffip does not use defcustom
631 ;; highlight-indent does not use defcustom, either. Its sole face
632 ;; is defined in basic-faces.
633 ))
634
635(defvar elpy-config--get-config "import json
636import sys
637
638import warnings
639warnings.filterwarnings('ignore', category=FutureWarning)
640warnings.filterwarnings('ignore', category=DeprecationWarning)
641warnings.filterwarnings('ignore', category=PendingDeprecationWarning)
642
643from distutils.version import LooseVersion
644
645try:
646 import urllib2 as urllib
647except ImportError:
648 import urllib.request as urllib
649
650
651# Check if we can connect to pypi quickly enough
652try:
653 response = urllib.urlopen('https://pypi.org/pypi', timeout=1)
654 CAN_CONNECT_TO_PYPI = True
655except:
656 CAN_CONNECT_TO_PYPI = False
657
658
659def latest(package, version=None):
660 if not CAN_CONNECT_TO_PYPI:
661 return None
662 try:
663 response = urllib.urlopen('https://pypi.org/pypi/{package}/json'.format(package=package),
664 timeout=2).read()
665 latest = json.loads(response)['info']['version']
666 if version is None or LooseVersion(version) < LooseVersion(latest):
667 return latest
668 else:
669 return None
670 except:
671 return None
672
673
674config = {}
675config['can_connect_to_pypi'] = CAN_CONNECT_TO_PYPI
676config['rpc_python_version'] = ('{major}.{minor}.{micro}'
677 .format(major=sys.version_info[0],
678 minor=sys.version_info[1],
679 micro=sys.version_info[2]))
680
681try:
682 import elpy
683 config['elpy_version'] = elpy.__version__
684except:
685 config['elpy_version'] = None
686
687try:
688 import jedi
689 if isinstance(jedi.__version__, tuple):
690 config['jedi_version'] = '.'.join(str(x) for x in jedi.__version__)
691 else:
692 config['jedi_version'] = jedi.__version__
693 config['jedi_latest'] = latest('jedi', config['jedi_version'])
694except:
695 config['jedi_version'] = None
696 config['jedi_latest'] = latest('jedi')
697
698try:
699 import autopep8
700 config['autopep8_version'] = autopep8.__version__
701 config['autopep8_latest'] = latest('autopep8', config['autopep8_version'])
702except:
703 config['autopep8_version'] = None
704 config['autopep8_latest'] = latest('autopep8')
705
706try:
707 import yapf
708 config['yapf_version'] = yapf.__version__
709 config['yapf_latest'] = latest('yapf', config['yapf_version'])
710except:
711 config['yapf_version'] = None
712 config['yapf_latest'] = latest('yapf')
713
714try:
715 import black
716 config['black_version'] = black.__version__
717 config['black_latest'] = latest('black', config['black_version'])
718except:
719 config['black_version'] = None
720 config['black_latest'] = latest('black')
721
722json.dump(config, sys.stdout)
723")
724
725(defun elpy-config-error (&optional fmt &rest args)
726 "Note a configuration problem.
727
728FMT is the formating string.
729
730This will show a message in the minibuffer that tells the user to
731use \\[elpy-config]."
732 (let ((msg (if fmt
733 (apply #'format fmt args)
734 "Elpy is not properly configured")))
735 (error "%s; use M-x elpy-config to configure it" msg)))
736
737;;;###autoload
738(defun elpy-config ()
739 "Configure Elpy.
740
741This function will pop up a configuration buffer, which is mostly
742a customize buffer, but has some more options."
743 (interactive)
744 (let ((buf (custom-get-fresh-buffer "*Elpy Config*"))
745 (config (elpy-config--get-config))
746 (custom-search-field nil))
747 (with-current-buffer buf
748 (elpy-insert--header "Elpy Configuration")
749
750 (elpy-config--insert-configuration-table config)
751 (insert "\n")
752
753 (elpy-insert--header "Warnings")
754
755 (elpy-config--insert-configuration-problems config)
756
757 (elpy-insert--header "Options")
758
759 (let ((custom-buffer-style 'tree))
760 (Custom-mode)
761 (elpy-config--insert-help)
762 (dolist (cust elpy-config--related-custom-groups)
763 (widget-create 'custom-group
764 :custom-last t
765 :custom-state 'hidden
766 :tag (car cust)
767 :value (cadr cust)))
768 (widget-setup)
769 (goto-char (point-min))))
770 (pop-to-buffer-same-window buf)))
771
772;;;###autoload
773(defun elpy-version ()
774 "Display the version of Elpy."
775 (interactive)
776 (message "Elpy %s (use M-x elpy-config for details)" elpy-version))
777
778(defun elpy-config--insert-help ()
779 "Insert the customization help."
780 (let ((start (point)))
781 ;; Help display from `customize-browse'
782 (widget-insert (format "\
783%s buttons; type RET or click mouse-1
784on a button to invoke its action.
785Invoke [+] to expand a group, and [-] to collapse an expanded group.\n"
786 (if custom-raised-buttons
787 "`Raised' text indicates"
788 "Square brackets indicate")))
789 (if custom-browse-only-groups
790 (widget-insert "\
791Invoke the [Group] button below to edit that item in another window.\n\n")
792 (widget-insert "Invoke the ")
793 (widget-create 'item
794 :format "%t"
795 :tag "[Group]"
796 :tag-glyph "folder")
797 (widget-insert ", ")
798 (widget-create 'item
799 :format "%t"
800 :tag "[Face]"
801 :tag-glyph "face")
802 (widget-insert ", and ")
803 (widget-create 'item
804 :format "%t"
805 :tag "[Option]"
806 :tag-glyph "option")
807 (widget-insert " buttons below to edit that
808item in another window.\n\n")
809
810 (fill-region start (point)))))
811
812(defun elpy-config--insert-configuration-problems (&optional config)
813 "Insert help text and widgets for configuration problems."
814 (unless config
815 (setq config (elpy-config--get-config)))
816 (let* ((rpc-python-version (gethash "rpc_python_version" config)))
817
818 ;; Python not found
819 (unless (gethash "rpc_python_executable" config)
820 (elpy-insert--para
821 "Elpy can not find the configured Python interpreter. Please make "
822 "sure that the variable `elpy-rpc-python-command' points to a "
823 "command in your PATH. You can change the variable below.\n\n"))
824
825 ;; No virtual env
826 (when (and (gethash "rpc_python_executable" config)
827 (not (gethash "virtual_env" config)))
828 (elpy-insert--para
829 "You have not activated a virtual env. It is not mandatory but"
830 " often a good idea to work inside a virtual env. You can use "
831 "`M-x pyvenv-activate` or `M-x pyvenv-workon` to activate one.\n\n"))
832
833 ;; No virtual env, but ~/.local/bin not in PATH
834 (when (and (not (memq system-type '(ms-dos windows-nt)))
835 (gethash "rpc_python_executable" config)
836 (not pyvenv-virtual-env)
837 (not (or (member (expand-file-name "~/.local/bin")
838 exec-path)
839 (member (expand-file-name "~/.local/bin/")
840 exec-path))))
841 (elpy-insert--para
842 "The directory ~/.local/bin/ is not in your PATH. As there is "
843 "no active virtualenv, installing Python packages locally will "
844 "place executables in that directory, so Emacs won't find them. "
845 "If you are missing some commands, do add this directory to your "
846 "PATH -- and then do `elpy-rpc-restart'.\n\n"))
847
848 ;; Python found, but can't find the elpy module
849 (when (and (gethash "rpc_python_executable" config)
850 (not (gethash "elpy_version" config)))
851 (elpy-insert--para
852 "The Python interpreter could not find the elpy module. "
853 "Please report to: "
854 "https://github.com/jorgenschaefer/elpy/issues/new."
855 "\n")
856 (insert "\n"))
857
858 ;; Bad backend version
859 (when (and (gethash "elpy_version" config)
860 (not (equal (gethash "elpy_version" config)
861 elpy-version)))
862 (let ((elpy-python-version (gethash "elpy_version" config)))
863 (elpy-insert--para
864 "The Elpy backend is version " elpy-python-version " while "
865 "the Emacs package is " elpy-version ". This is incompatible. "
866 "Please report to: https://github.com/jorgenschaefer/elpy/issues/new."
867 "\n")))
868
869 ;; Otherwise unparseable output.
870 (when (gethash "error_output" config)
871 (elpy-insert--para
872 "There was an unexpected problem starting the RPC process. Please "
873 "check the following output to see if this makes sense to you. "
874 "To me, it doesn't.\n")
875 (insert "\n"
876 (gethash "error_output" config) "\n"
877 "\n"))
878
879 ;; Interactive python interpreter not in the current virtual env
880 (when (and pyvenv-virtual-env
881 (not (string-prefix-p (expand-file-name pyvenv-virtual-env)
882 (executable-find
883 python-shell-interpreter))))
884 (elpy-insert--para
885 "The python interactive interpreter (" python-shell-interpreter
886 ") is not installed on the current virtualenv ("
887 pyvenv-virtual-env "). The system binary ("
888 (executable-find python-shell-interpreter)
889 ") will be used instead."
890 "\n")
891 (insert "\n")
892 (widget-create 'elpy-insert--pip-button
893 :package python-shell-interpreter :norpc t)
894 (insert "\n\n"))
895
896 ;; Couldn't connect to pypi to check package versions
897 (when (not (gethash "can_connect_to_pypi" config))
898 (elpy-insert--para
899 "Elpy could not connect to Pypi (or at least not quickly enough) "
900 "and check if the python packages were up-to-date. "
901 "You can still try to update all of them:"
902 "\n")
903 (insert "\n")
904 (widget-create 'elpy-insert--generic-button
905 :button-name "[Update python packages]"
906 :function (lambda () (with-elpy-rpc-virtualenv-activated
907 (elpy-rpc--install-dependencies))))
908 (insert "\n\n"))
909
910 ;; Pip not available in the rpc virtualenv
911 (when (and
912 (equal elpy-rpc-virtualenv-path 'default)
913 (elpy-rpc--pip-missing))
914 (elpy-insert--para
915 "Pip doesn't seem to be installed in the dedicated virtualenv "
916 "created by Elpy (" (elpy-rpc-get-virtualenv-path) "). "
917 "This may prevent some features from working properly"
918 " (completion, documentation, reformatting, ...). "
919 "You can try reinstalling the virtualenv. "
920 "If the problem persists, please report on Elpy's github page."
921 "\n\n")
922 (widget-create 'elpy-insert--generic-button
923 :button-name "[Reinstall RPC virtualenv]"
924 :function (lambda () (elpy-rpc-reinstall-virtualenv)))
925 (insert "\n\n"))
926
927 ;; Requested backend unavailable
928 (when (and (gethash "rpc_python_executable" config)
929 (not (gethash "jedi_version" config)))
930 (elpy-insert--para
931 "The Jedi package is not currently installed. "
932 "This package is needed for code completion, code navigation "
933 "and access to documentation.\n")
934 (insert "\n")
935 (widget-create 'elpy-insert--pip-button
936 :package "jedi")
937 (insert "\n\n"))
938
939 ;; Newer version of Jedi available
940 (when (and (gethash "jedi_version" config)
941 (gethash "jedi_latest" config))
942 (elpy-insert--para
943 "There is a newer version of Jedi available.\n")
944 (insert "\n")
945 (widget-create 'elpy-insert--pip-button
946 :package "jedi" :upgrade t)
947 (insert "\n\n"))
948
949
950 ;; No auto formatting tool available
951 (unless (or
952 (gethash "autopep8_version" config)
953 (gethash "yapf_version" config)
954 (gethash "black_version" config))
955 (elpy-insert--para
956 "No autoformatting package is currently installed. "
957 "At least one is needed (Autopep8, Yapf or Black) "
958 "to perform autoformatting (`C-c C-r f` in a python buffer).\n")
959 (insert "\n")
960 (widget-create 'elpy-insert--pip-button
961 :package "autopep8")
962 (insert "\n")
963 (widget-create 'elpy-insert--pip-button
964 :package "yapf")
965 (insert "\n")
966 (widget-create 'elpy-insert--pip-button
967 :package "black")
968 (insert "\n\n"))
969
970 ;; Newer version of autopep8 available
971 (when (and (gethash "autopep8_version" config)
972 (gethash "autopep8_latest" config))
973 (elpy-insert--para
974 "There is a newer version of the autopep8 package available.\n")
975 (insert "\n")
976 (widget-create 'elpy-insert--pip-button
977 :package "autopep8" :upgrade t)
978 (insert "\n\n"))
979
980 ;; Newer version of yapf available
981 (when (and (gethash "yapf_version" config)
982 (gethash "yapf_latest" config))
983 (elpy-insert--para
984 "There is a newer version of the yapf package available.\n")
985 (insert "\n")
986 (widget-create 'elpy-insert--pip-button
987 :package "yapf" :upgrade t)
988 (insert "\n\n"))
989
990 ;; Newer version of black available
991 (when (and (gethash "black_version" config)
992 (gethash "black_latest" config))
993 (elpy-insert--para
994 "There is a newer version of the black package available.\n")
995 (insert "\n")
996 (widget-create 'elpy-insert--pip-button
997 :package "black" :upgrade t)
998 (insert "\n\n"))
999
1000 ;; Syntax checker not available
1001 (unless (executable-find (car (split-string elpy-syntax-check-command)))
1002 (elpy-insert--para
1003 (format
1004 "The configured syntax checker (%s) could not be found. Elpy uses this "
1005 (car (split-string elpy-syntax-check-command)))
1006 "program to provide syntax checks of your code. You can either "
1007 "install it, or select another one using `elpy-syntax-check-command`.\n")
1008 (insert "\n")
1009 (widget-create 'elpy-insert--pip-button :package "flake8" :norpc t)
1010 (insert "\n\n"))
1011 ))
1012
1013(defun elpy-config--package-available-p (package)
1014 "Check if PACKAGE is installed in the rpc."
1015 (with-elpy-rpc-virtualenv-activated
1016 (equal 0 (call-process elpy-rpc-python-command nil nil nil "-c"
1017 (format "import %s" package)))))
1018
1019(defun elpy-config--get-config ()
1020 "Return the configuration from `elpy-rpc-python-command'.
1021
1022This returns a hash table with the following keys (all strings):
1023
1024emacs_version
1025elpy_version
1026python_interactive
1027python_interactive_version
1028python_interactive_executable
1029rpc_virtualenv
1030rpc_virtualenv_short
1031rpc_python
1032rpc_python_version
1033rpc_python_executable
1034jedi_version
1035virtual_env
1036virtual_env_short"
1037 (with-temp-buffer
1038 (let ((config (make-hash-table :test #'equal)))
1039 (puthash "emacs_version" emacs-version config)
1040 (let ((rpc-venv (elpy-rpc-get-or-create-virtualenv)))
1041 (puthash "rpc_virtualenv" rpc-venv config)
1042 (if rpc-venv
1043 (puthash "rpc_virtualenv_short"
1044 (file-name-nondirectory (directory-file-name rpc-venv))
1045 config)
1046 (puthash "rpc_virtualenv_short" nil config)))
1047 (with-elpy-rpc-virtualenv-activated
1048 (puthash "rpc_python" elpy-rpc-python-command config)
1049 (puthash "rpc_python_executable"
1050 (executable-find elpy-rpc-python-command)
1051 config))
1052 (let ((interactive-python (if (boundp 'python-python-command)
1053 python-python-command
1054 python-shell-interpreter)))
1055 (puthash "python_interactive"
1056 interactive-python
1057 config)
1058 (puthash "python_interactive_version"
1059 (let ((pversion (shell-command-to-string
1060 (format "%s --version"
1061 python-shell-interpreter))))
1062 (when (string-match "[0-9.]+" pversion)
1063 (match-string 0 pversion)))
1064 config)
1065 (puthash "python_interactive_executable"
1066 (executable-find interactive-python)
1067 config))
1068 (let ((venv (getenv "VIRTUAL_ENV")))
1069 (puthash "virtual_env" venv config)
1070 (if venv
1071 (puthash "virtual_env_short" (file-name-nondirectory
1072 (directory-file-name venv))
1073 config)
1074 (puthash "virtual_env_short" nil config)))
1075 (with-elpy-rpc-virtualenv-activated
1076 (let ((return-value (ignore-errors
1077 (let ((process-environment
1078 (elpy-rpc--environment))
1079 (default-directory "/"))
1080 (call-process elpy-rpc-python-command
1081 nil
1082 (current-buffer)
1083 nil
1084 "-c"
1085 elpy-config--get-config)))))
1086 (when return-value
1087 (let ((data (ignore-errors
1088 (let ((json-array-type 'list)
1089 (json-false nil)
1090 (json-encoding-pretty-print nil)) ;; Link to bug https://github.com/jorgenschaefer/elpy/issues/1521
1091 (goto-char (point-min))
1092 (json-read)))))
1093 (if (not data)
1094 (puthash "error_output" (buffer-string) config)
1095 (dolist (pair data)
1096 (puthash (symbol-name (car pair)) (cdr pair) config)))))))
1097 config)))
1098
1099(defun elpy-config--insert-configuration-table (&optional config)
1100 "Insert a table describing the current Elpy config."
1101 (unless config
1102 (setq config (elpy-config--get-config)))
1103 (let ((emacs-version (gethash "emacs_version" config))
1104 (elpy-python-version (gethash "elpy_version" config))
1105 (virtual-env (gethash "virtual_env" config))
1106 (virtual-env-short (gethash "virtual_env_short" config))
1107 (python-interactive (gethash "python_interactive" config))
1108 (python-interactive-version (gethash "python_interactive_version" config))
1109 (python-interactive-executable (gethash "python_interactive_executable"
1110 config))
1111 (rpc-python (gethash "rpc_python" config))
1112 (rpc-python-executable (gethash "rpc_python_executable" config))
1113 (rpc-python-version (gethash "rpc_python_version" config))
1114 (rpc-virtualenv (gethash "rpc_virtualenv" config))
1115 (rpc-virtualenv-short (gethash "rpc_virtualenv_short" config))
1116 (jedi-version (gethash "jedi_version" config))
1117 (jedi-latest (gethash "jedi_latest" config))
1118 (autopep8-version (gethash "autopep8_version" config))
1119 (autopep8-latest (gethash "autopep8_latest" config))
1120 (yapf-version (gethash "yapf_version" config))
1121 (yapf-latest (gethash "yapf_latest" config))
1122 (black-version (gethash "black_version" config))
1123 (black-latest (gethash "black_latest" config))
1124 table maxwidth)
1125 (setq table
1126 `(("Emacs" . ,emacs-version)
1127 ("Elpy" . ,(cond
1128 ((and elpy-python-version elpy-version
1129 (equal elpy-python-version elpy-version))
1130 elpy-version)
1131 (elpy-python-version
1132 (format "%s (Python), %s (Emacs Lisp)"
1133 elpy-python-version
1134 elpy-version))
1135 (t
1136 (format "Not found (Python), %s (Emacs Lisp)"
1137 elpy-version))))
1138 (("Virtualenv" (lambda ()
1139 (call-interactively 'pyvenv-workon)
1140 (elpy-config)))
1141 . ,(if (gethash "virtual_env" config)
1142 (format "%s (%s)"
1143 virtual-env-short
1144 virtual-env)
1145 "None"))
1146 (("Interactive Python" (lambda ()
1147 (customize-variable
1148 'python-shell-interpreter)))
1149 . ,(cond
1150 (python-interactive-executable
1151 (format "%s %s (%s)"
1152 python-interactive
1153 python-interactive-version
1154 python-interactive-executable))
1155 (python-interactive
1156 (format "%s (not found)"
1157 python-interactive))
1158 (t
1159 "Not configured")))
1160 ("RPC virtualenv"
1161 . ,(format "%s (%s)"
1162 (if (or (eq elpy-rpc-virtualenv-path 'system)
1163 (eq elpy-rpc-virtualenv-path 'global)) ;; for backward compatibility
1164 "system"
1165 rpc-virtualenv-short)
1166 rpc-virtualenv))
1167 ((" Python" (lambda ()
1168 (customize-variable
1169 'elpy-rpc-python-command)))
1170 . ,(cond
1171 (rpc-python-executable
1172 (format "%s %s (%s)"
1173 rpc-python
1174 rpc-python-version
1175 rpc-python-executable))
1176 (rpc-python-executable
1177 rpc-python-executable)
1178 (rpc-python
1179 (format "%s (not found)" rpc-python))
1180 (t
1181 (format "Not configured"))))
1182 (" Jedi" . ,(elpy-config--package-link "jedi"
1183 jedi-version
1184 jedi-latest))
1185 (" Autopep8" . ,(elpy-config--package-link "autopep8"
1186 autopep8-version
1187 autopep8-latest))
1188 (" Yapf" . ,(elpy-config--package-link "yapf"
1189 yapf-version
1190 yapf-latest))
1191 (" Black" . ,(elpy-config--package-link "black"
1192 black-version
1193 black-latest))
1194 (("Syntax checker" (lambda ()
1195 (customize-variable 'elpy-syntax-check-command)))
1196
1197 . ,(let ((syntax-checker
1198 (executable-find
1199 (car (split-string
1200 elpy-syntax-check-command)))))
1201 (if syntax-checker
1202 (format "%s (%s)"
1203 (file-name-nondirectory
1204 syntax-checker)
1205 syntax-checker)
1206 (format "Not found (%s)"
1207 elpy-syntax-check-command))))))
1208 (setq maxwidth 0)
1209 (dolist (row table)
1210 (let (length)
1211 (if (stringp (car row))
1212 (setq length (length (car row)))
1213 (setq length (length (car (car row)))))
1214 (when (> length maxwidth)
1215 (setq maxwidth length ))))
1216 (dolist (row table)
1217 (if (stringp (car row))
1218 (insert (car row)
1219 (make-string (- maxwidth (length (car row)))
1220 ?.))
1221 (widget-create 'elpy-insert--generic-button
1222 :button-name (car (car row))
1223 :function (car (cdr (car row))))
1224 (insert (make-string (- maxwidth (length (car (car row))))
1225 ?.)))
1226 (insert ": "
1227 (cdr row)
1228 "\n"))))
1229
1230(defun elpy-config--package-link (_name version latest)
1231 "Return a string detailing a Python package.
1232
1233NAME is the PyPI name of the package. VERSION is the currently
1234installed version. LATEST is the latest-available version on
1235PyPI, or nil if that's VERSION."
1236 (cond
1237 ((and (not version) (not latest))
1238 "Not found")
1239 ((not latest)
1240 version)
1241 ((not version)
1242 (format "Not found (%s available)" latest))
1243 (t
1244 (format "%s (%s available)" version latest))))
1245
1246;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1247;;; Elpy Formatted Insertion
1248
1249(defun elpy-insert--para (&rest messages)
1250 "Insert MESSAGES, a list of strings, and then fill it."
1251 (let ((start (point)))
1252 (mapc (lambda (obj)
1253 (if (stringp obj)
1254 (insert obj)
1255 (insert (format "%s" obj))))
1256 messages)
1257 (fill-region start (point))))
1258
1259(defun elpy-insert--header (&rest text)
1260 "Insert TEXT has a header for a buffer."
1261 (insert (propertize (mapconcat #'(lambda (x) x)
1262 text
1263 "")
1264 'face 'header-line)
1265 "\n"
1266 "\n"))
1267
1268(define-widget 'elpy-insert--generic-button 'item
1269 "A button that run a rgiven function."
1270 :button-prefix ""
1271 :button-suffix ""
1272 :format "%[%v%]"
1273 :value-create 'elpy-insert--generic-button-value-create
1274 :action 'elpy-insert--generic-button-action)
1275
1276(defun elpy-insert--generic-button-value-create (widget)
1277 "The :value-create option for the customize button widget."
1278 (insert (widget-get widget :button-name)))
1279
1280(defun elpy-insert--generic-button-action (widget &optional _event)
1281 "The :action option for the customize button widget."
1282 (funcall (widget-get widget :function)))
1283
1284(define-widget 'elpy-insert--pip-button 'item
1285 "A button that runs pip (or an alternative)."
1286 :button-prefix "["
1287 :button-suffix "]"
1288 :format "%[%v%]"
1289 :value-create 'elpy-insert--pip-button-value-create
1290 :action 'elpy-insert--pip-button-action)
1291
1292(defun elpy-insert--pip-button-value-create (widget)
1293 "The :value-create option for the pip button widget."
1294 (let* ((python-package (widget-get widget :package))
1295 (do-upgrade (widget-get widget :upgrade))
1296 (upgrade-option (if do-upgrade
1297 "--upgrade "
1298 ""))
1299 (command (cond
1300 ((= (call-process elpy-rpc-python-command
1301 nil nil nil
1302 "-m" "pip" "--help")
1303 0)
1304 (format "%s -m pip install %s%s"
1305 elpy-rpc-python-command
1306 upgrade-option
1307 python-package))
1308 ((executable-find "easy_install")
1309 (format "easy_install %s" python-package))
1310 (t
1311 (error "Neither easy_install nor pip found")))))
1312 (widget-put widget :command command)
1313 (if do-upgrade
1314 (insert (format "Update %s" python-package))
1315 (insert (format "Install %s" python-package)))))
1316
1317(defun elpy-insert--pip-button-action (widget &optional _event)
1318 "The :action option for the pip button widget."
1319 (let ((command (widget-get widget :command))
1320 (norpc (widget-get widget :norpc)))
1321 (if norpc
1322 (async-shell-command command)
1323 (with-elpy-rpc-virtualenv-activated
1324 (async-shell-command command)))))
1325
1326;;;;;;;;;;;;
1327;;; Projects
1328
1329(defvar elpy-project--variable-name-history nil
1330 "The history for `elpy-project--read-project-variable'.")
1331
1332(defun elpy-project-root ()
1333 "Return the root of the current buffer's project.
1334
1335This can very well be nil if the current file is not part of a
1336project.
1337
1338See `elpy-project-root-finder-functions' for a way to configure
1339how the project root is found. You can also set the variable
1340`elpy-project-root' in, for example, .dir-locals.el to override
1341this."
1342 (unless elpy-project-root
1343 (setq elpy-project-root
1344 (run-hook-with-args-until-success
1345 'elpy-project-root-finder-functions)))
1346 elpy-project-root)
1347
1348(defun elpy-set-project-root (new-root)
1349 "Set the Elpy project root to NEW-ROOT."
1350 (interactive "DNew project root: ")
1351 (setq elpy-project-root new-root))
1352
1353(defun elpy-project-find-python-root ()
1354 "Return the current Python project root, if any.
1355
1356This is marked with 'setup.py', 'setup.cfg' or 'pyproject.toml'."
1357 (or (locate-dominating-file default-directory "setup.py")
1358 (locate-dominating-file default-directory "setup.cfg")
1359 (locate-dominating-file default-directory "pyproject.toml")))
1360
1361(defun elpy-project-find-git-root ()
1362 "Return the current git repository root, if any."
1363 (locate-dominating-file default-directory ".git"))
1364
1365(defun elpy-project-find-hg-root ()
1366 "Return the current git repository root, if any."
1367 (locate-dominating-file default-directory ".hg"))
1368
1369(defun elpy-project-find-svn-root ()
1370 "Return the current git repository root, if any."
1371 (locate-dominating-file default-directory
1372 (lambda (dir)
1373 (and (file-directory-p (format "%s/.svn" dir))
1374 (not (file-directory-p (format "%s/../.svn"
1375 dir)))))))
1376
1377(defun elpy-project-find-projectile-root ()
1378 "Return the current project root according to projectile."
1379 ;; `ignore-errors' both to avoid an unbound function error as well
1380 ;; as ignore projectile saying there is no project root here.
1381 (ignore-errors
1382 (projectile-project-root)))
1383
1384(defun elpy-library-root ()
1385 "Return the root of the Python package chain of the current buffer.
1386
1387That is, if you have /foo/package/module.py, it will return /foo,
1388so that import package.module will pick up module.py."
1389 (locate-dominating-file default-directory
1390 (lambda (dir)
1391 (not (file-exists-p
1392 (format "%s/__init__.py"
1393 dir))))))
1394
1395(defun elpy-project--read-project-variable (prompt)
1396 "Prompt the user for a variable name to set project-wide using PROMPT."
1397 (let* ((prefixes (mapcar (lambda (cust)
1398 (nth 2 cust))
1399 elpy-config--related-custom-groups))
1400 (var-regex (format "^%s" (regexp-opt prefixes))))
1401 (intern
1402 (completing-read
1403 prompt
1404 obarray
1405 (lambda (sym)
1406 (and (get sym 'safe-local-variable)
1407 (string-match var-regex (symbol-name sym))
1408 (get sym 'custom-type)))
1409 :require-match
1410 nil
1411 'elpy-project--variable-name-history))))
1412
1413(defun elpy-project--read-variable-value (prompt variable)
1414 "Read the value for VARIABLE from the user using PROMPT."
1415 (let ((custom-type (get variable 'custom-type)))
1416 (if custom-type
1417 (widget-prompt-value (if (listp custom-type)
1418 custom-type
1419 (list custom-type))
1420 prompt
1421 (if (boundp variable)
1422 (funcall
1423 (or (get variable 'custom-get)
1424 'symbol-value)
1425 variable))
1426 (not (boundp variable)))
1427 (eval-minibuffer prompt))))
1428
1429(defun elpy-set-project-variable (variable value)
1430 "Set or remove a variable in the project-wide .dir-locals.el.
1431
1432With prefix argument, remove the variable."
1433 (interactive
1434 (let* ((variable (elpy-project--read-project-variable
1435 (if current-prefix-arg
1436 "Remove project variable: "
1437 "Set project variable: ")))
1438 (value (if current-prefix-arg
1439 nil
1440 (elpy-project--read-variable-value (format "Value for %s: "
1441 variable)
1442 variable))))
1443 (list variable value)))
1444 (with-current-buffer (find-file-noselect (format "%s/%s"
1445 (elpy-project-root)
1446 dir-locals-file))
1447 (modify-dir-local-variable nil
1448 variable
1449 value
1450 (if current-prefix-arg
1451 'delete
1452 'add-or-replace))))
1453
1454;;;;;;;;;;;;;;;;;;;;;;;;
1455;;; Search Project Files
1456
1457(defun elpy-rgrep-symbol (regexp)
1458 "Search for REGEXP in the current project.
1459
1460REGEXP defaults to the symbol at point, or the current region if
1461active.
1462
1463With a prefix argument, always prompt for a string to search for."
1464 (interactive
1465 (list
1466 (let ((symbol
1467 (if (use-region-p)
1468 (buffer-substring-no-properties (region-beginning)
1469 (region-end))
1470 (thing-at-point 'symbol))))
1471 (if (and symbol (not current-prefix-arg))
1472 (concat "\\<" symbol "\\>")
1473 (read-from-minibuffer "Search in project for regexp: " symbol)))))
1474 (grep-compute-defaults)
1475 (let ((grep-find-ignored-directories (elpy-project-ignored-directories)))
1476 (rgrep regexp
1477 elpy-rgrep-file-pattern
1478 (or (elpy-project-root)
1479 default-directory)))
1480 (with-current-buffer next-error-last-buffer
1481 (let ((inhibit-read-only t))
1482 (save-excursion
1483 (goto-char (point-min))
1484 (when (re-search-forward "^find .*" nil t)
1485 (replace-match (format "Searching for '%s'\n"
1486 (regexp-quote regexp))))))))
1487
1488;;;;;;;;;;;;;;;;;;;;;;
1489;;; Find Project Files
1490
1491(defcustom elpy-ffip-prune-patterns '()
1492 "Elpy-specific extension of `ffip-prune-patterns'.
1493This is in addition to `elpy-project-ignored-directories'
1494and `completion-ignored-extensions'.
1495The final value of `ffip-prune-patterns' used is computed
1496by the eponymous function `elpy-ffip-prune-patterns'."
1497 :type '(repeat string)
1498 :safe (lambda (val)
1499 (cl-every #'stringp val))
1500 :group 'elpy)
1501
1502(defun elpy-ffip-prune-patterns ()
1503 "Compute `ffip-prune-patterns' from other variables.
1504This combines
1505 `elpy-ffip-prune-patterns'
1506 `elpy-project-ignored-directories'
1507 `completion-ignored-extensions'
1508 `ffip-prune-patterns'."
1509 (delete-dups
1510 (nconc
1511 (mapcar (lambda (dir) (concat "*/" dir "/*"))
1512 elpy-project-ignored-directories)
1513 (mapcar (lambda (ext) (if (s-ends-with? "/" ext)
1514 (concat "*" ext "*")
1515 (concat "*" ext)))
1516 completion-ignored-extensions)
1517 (cl-copy-list elpy-ffip-prune-patterns)
1518 (cl-copy-list ffip-prune-patterns))))
1519
1520(defun elpy-find-file (&optional dwim)
1521 "Efficiently find a file in the current project.
1522
1523It necessitates `projectile' or `find-file-in-project' to be installed.
1524
1525With prefix argument (or DWIM non-nil), tries to guess what kind of
1526file the user wants to open:
1527- On an import line, it opens the file of that module.
1528- Otherwise, it opens a test file associated with the current file,
1529if one exists. A test file is named test_<name>.py if the current
1530file is <name>.py, and is either in the same directory or a
1531\"test\" or \"tests\" subdirectory."
1532 (interactive "P")
1533 (cond
1534 ((and dwim
1535 (buffer-file-name)
1536 (save-excursion
1537 (goto-char (line-beginning-position))
1538 (or (looking-at "^ *import +\\([[:alnum:]._]+\\)")
1539 (looking-at "^ *from +\\([[:alnum:]._]+\\) +import +\\([[:alnum:]._]+\\)"))))
1540 (let* ((module (if (match-string 2)
1541 (format "%s.%s" (match-string 1) (match-string 2))
1542 (match-string 1)))
1543 (path (elpy-find--resolve-module module)))
1544 (if path
1545 (find-file path)
1546 (elpy-find-file nil))))
1547 ((and dwim
1548 (buffer-file-name))
1549 (let ((test-file (elpy-find--test-file)))
1550 (if test-file
1551 (find-file test-file)
1552 (elpy-find-file nil))))
1553 ((fboundp 'projectile-find-file)
1554 (let ((projectile-globally-ignored-file-suffixes
1555 (delete-dups
1556 (nconc
1557 (cl-copy-list projectile-globally-ignored-file-suffixes)
1558 (cl-copy-list completion-ignored-extensions))))
1559 (projectile-globally-ignored-directories
1560 (delete-dups
1561 (nconc
1562 (cl-copy-list projectile-globally-ignored-directories)
1563 (cl-copy-list elpy-ffip-prune-patterns)
1564 (elpy-project-ignored-directories))))
1565 (projectile-project-root (or (elpy-project-root)
1566 default-directory)))
1567 (projectile-find-file)))
1568 ((fboundp 'find-file-in-project)
1569 (let ((ffip-prune-patterns (elpy-ffip-prune-patterns))
1570 (ffip-project-root (or (elpy-project-root)
1571 default-directory))
1572 ;; Set up ido to use vertical file lists.
1573 (ffip-prefer-ido-mode t)
1574 (ido-decorations '("\n" "" "\n" "\n..."
1575 "[" "]" " [No match]" " [Matched]"
1576 " [Not readable]" " [Too big]"
1577 " [Confirm]"))
1578 (ido-setup-hook (cons (lambda ()
1579 (define-key ido-completion-map (kbd "<down>")
1580 'ido-next-match)
1581 (define-key ido-completion-map (kbd "<up>")
1582 'ido-prev-match))
1583 ido-setup-hook)))
1584 (find-file-in-project)))
1585 (t
1586 (error "`elpy-find-file' necessitates `projectile' or `find-file-in-project' to be installed"))))
1587
1588(defun elpy-find--test-file ()
1589 "Return the test file for the current file, if any.
1590
1591If this is a test file, return the non-test file.
1592
1593A test file is named test_<name>.py if the current file is
1594<name>.py, and is either in the same directors or a \"test\" or
1595\"tests\" subdirectory."
1596 (let* ((project-root (or (elpy-project-root) default-directory))
1597 (filename (file-name-base (buffer-file-name)))
1598 (impl-file (when (string-match "test_\\(.*\\)" filename)
1599 (match-string 1 filename)))
1600 (files
1601 (cond
1602 ((fboundp 'projectile-find-file)
1603 (let ((projectile-project-root project-root))
1604 (projectile-current-project-files)))
1605 ((fboundp 'find-file-in-project)
1606 (let ((ffip-project-root project-root))
1607 (cl-map 'list 'cdr (ffip-project-search nil nil))))
1608 (t '())))
1609 (file (if impl-file
1610 (cl-remove-if (lambda (file)
1611 (not (string=
1612 impl-file
1613 (file-name-base file))))
1614 files)
1615 (cl-remove-if (lambda (file)
1616 (not (string=
1617 (format "test_%s" filename)
1618 (file-name-base file))))
1619 files))))
1620 (when file
1621 (if (> (length file) 1)
1622 (setq file (completing-read "Which file: " file))
1623 (setq file (car file)))
1624 (if (file-name-absolute-p file)
1625 file
1626 (concat (file-name-as-directory project-root) file)))))
1627
1628(defun elpy-find--module-path (module)
1629 "Return a directory path for MODULE.
1630
1631The resulting path is not guaranteed to exist. This simply
1632resolves leading periods relative to the current directory and
1633replaces periods in the middle of the string with slashes.
1634
1635Only works with absolute imports. Stop using implicit relative
1636imports. They're a bad idea."
1637 (let* ((relative-depth (when(string-match "^\\.+" module)
1638 (length (match-string 0 module))))
1639 (base-directory (if relative-depth
1640 (format "%s/%s"
1641 (buffer-file-name)
1642 (mapconcat (lambda (_)
1643 "../")
1644 (make-vector relative-depth
1645 nil)
1646 ""))
1647 (elpy-library-root)))
1648 (file-name (replace-regexp-in-string
1649 "\\."
1650 "/"
1651 (if relative-depth
1652 (substring module relative-depth)
1653 module))))
1654 (expand-file-name (format "%s/%s" base-directory file-name))))
1655
1656(defun elpy-find--resolve-module (module)
1657 "Resolve MODULE relative to the current file and project.
1658
1659Returns a full path name for that module."
1660 (catch 'return
1661 (let ((path (elpy-find--module-path module)))
1662 (while (string-prefix-p (expand-file-name (elpy-library-root))
1663 path)
1664 (dolist (name (list (format "%s.py" path)
1665 (format "%s/__init__.py" path)))
1666 (when (file-exists-p name)
1667 (throw 'return name)))
1668 (if (string-match "/$" path)
1669 (setq path (substring path 0 -1))
1670 (setq path (file-name-directory path)))))
1671 nil))
1672
1673;;;;;;;;;;;;;;;;;
1674;;; Syntax Checks
1675
1676(defun elpy-check (&optional whole-project-p)
1677 "Run `python-check-command' on the current buffer's file,
1678
1679or the project root if WHOLE-PROJECT-P is non-nil (interactively,
1680with a prefix argument)."
1681 (interactive "P")
1682 (unless (buffer-file-name)
1683 (error "Can't check a buffer without a file"))
1684 (save-some-buffers (not compilation-ask-about-save) nil)
1685 (let ((process-environment (python-shell-calculate-process-environment))
1686 (exec-path (python-shell-calculate-exec-path))
1687 (file-name-or-directory (expand-file-name
1688 (if whole-project-p
1689 (or (elpy-project-root)
1690 (buffer-file-name))
1691 (buffer-file-name))))
1692 (extra-args (if whole-project-p
1693 (concat
1694 (if (string-match "pylint$" python-check-command)
1695 " --ignore="
1696 " --exclude=")
1697 (mapconcat #'identity
1698 (elpy-project-ignored-directories)
1699 ","))
1700 "")))
1701 (compilation-start (concat python-check-command
1702 " "
1703 (shell-quote-argument file-name-or-directory)
1704 extra-args)
1705 nil
1706 (lambda (_mode-name)
1707 "*Python Check*"))))
1708
1709;;;;;;;;;;;;;;
1710;;; Navigation
1711
1712(defvar elpy-nav-expand--initial-position nil
1713 "Initial position before expanding to indentation.")
1714(make-variable-buffer-local 'elpy-nav-expand--initial-position)
1715
1716(defun elpy-rpc-warn-if-jedi-not-available ()
1717 "Display a warning if jedi is not available in the current RPC."
1718 (unless elpy-rpc--jedi-available
1719 (error "This feature requires the `jedi` package to be installed. Please check `elpy-config` for more information.")))
1720
1721(defun elpy-goto-definition ()
1722 "Go to the definition of the symbol at point, if found."
1723 (interactive)
1724 (elpy-rpc-warn-if-jedi-not-available)
1725 (let ((location (elpy-rpc-get-definition)))
1726 (if location
1727 (elpy-goto-location (car location) (cadr location))
1728 (error "No definition found"))))
1729
1730(defun elpy-goto-assignment ()
1731 "Go to the assignment of the symbol at point, if found."
1732 (interactive)
1733 (elpy-rpc-warn-if-jedi-not-available)
1734 (let ((location (elpy-rpc-get-assignment)))
1735 (if location
1736 (elpy-goto-location (car location) (cadr location))
1737 (error "No assignment found"))))
1738
1739(defun elpy-goto-definition-other-window ()
1740 "Go to the definition of the symbol at point in other window, if found."
1741 (interactive)
1742 (elpy-rpc-warn-if-jedi-not-available)
1743 (let ((location (elpy-rpc-get-definition)))
1744 (if location
1745 (elpy-goto-location (car location) (cadr location) 'other-window)
1746 (error "No definition found"))))
1747
1748(defun elpy-goto-assignment-other-window ()
1749 "Go to the assignment of the symbol at point in other window, if found."
1750 (interactive)
1751 (elpy-rpc-warn-if-jedi-not-available)
1752 (let ((location (elpy-rpc-get-assignment)))
1753 (if location
1754 (elpy-goto-location (car location) (cadr location) 'other-window)
1755 (error "No assignment found"))))
1756
1757(defun elpy-goto-location (filename offset &optional other-window-p)
1758 "Show FILENAME at OFFSET to the user.
1759
1760If OTHER-WINDOW-P is non-nil, show the same in other window."
1761
1762 ;; `find-tag-marker-ring' is marked as obsolete in 25.1
1763 ;; and not available in 27.
1764 (if (version< emacs-version "25.1")
1765 (ring-insert find-tag-marker-ring (point-marker))
1766 (xref-push-marker-stack))
1767 (let ((buffer (find-file-noselect filename)))
1768 (if other-window-p
1769 (pop-to-buffer buffer t)
1770 (switch-to-buffer buffer))
1771 (goto-char (1+ offset))
1772 ))
1773
1774(defun elpy-nav-forward-block ()
1775 "Move to the next line indented like point.
1776
1777This will skip over lines and statements with different
1778indentation levels."
1779 (interactive "^")
1780 (let ((indent (current-column))
1781 (start (point))
1782 (cur nil))
1783 (when (/= (% indent python-indent-offset)
1784 0)
1785 (setq indent (* (1+ (/ indent python-indent-offset))
1786 python-indent-offset)))
1787 (python-nav-forward-statement)
1788 (while (and (< indent (current-indentation))
1789 (not (eobp)))
1790 (when (equal (point) cur)
1791 (error "Statement does not finish"))
1792 (setq cur (point))
1793 (python-nav-forward-statement))
1794 (when (< (current-indentation)
1795 indent)
1796 (goto-char start))))
1797
1798(defun elpy-nav-backward-block ()
1799 "Move to the previous line indented like point.
1800
1801This will skip over lines and statements with different
1802indentation levels."
1803 (interactive "^")
1804 (let ((indent (current-column))
1805 (start (point))
1806 (cur nil))
1807 (when (/= (% indent python-indent-offset)
1808 0)
1809 (setq indent (* (1+ (/ indent python-indent-offset))
1810 python-indent-offset)))
1811 (python-nav-backward-statement)
1812 (while (and (< indent (current-indentation))
1813 (not (bobp)))
1814 (when (equal (point) cur)
1815 (error "Statement does not start"))
1816 (setq cur (point))
1817 (python-nav-backward-statement))
1818 (when (< (current-indentation)
1819 indent)
1820 (goto-char start))))
1821
1822(defun elpy-nav-forward-indent ()
1823 "Move forward to the next indent level, or over the next word."
1824 (interactive "^")
1825 (if (< (current-column) (current-indentation))
1826 (let* ((current (current-column))
1827 (next (* (1+ (/ current python-indent-offset))
1828 python-indent-offset)))
1829 (goto-char (+ (point-at-bol)
1830 next)))
1831 (let ((eol (point-at-eol)))
1832 (forward-word)
1833 (when (> (point) eol)
1834 (goto-char (point-at-bol))))))
1835
1836(defun elpy-nav-backward-indent ()
1837 "Move backward to the previous indent level, or over the previous word."
1838 (interactive "^")
1839 (if (and (<= (current-column) (current-indentation))
1840 (/= (current-column) 0))
1841 (let* ((current (current-column))
1842 (next (* (1- (/ current python-indent-offset))
1843 python-indent-offset)))
1844 (goto-char (+ (point-at-bol)
1845 next)))
1846 (backward-word)))
1847
1848(defun elpy-nav-move-line-or-region-down (&optional beg end)
1849 "Move the current line or active region down."
1850 (interactive
1851 (if (use-region-p)
1852 (list (region-beginning) (region-end))
1853 (list nil nil)))
1854 (if beg
1855 (elpy--nav-move-region-vertically beg end 1)
1856 (elpy--nav-move-line-vertically 1)))
1857
1858(defun elpy-nav-move-line-or-region-up (&optional beg end)
1859 "Move the current line or active region down."
1860 (interactive
1861 (if (use-region-p)
1862 (list (region-beginning) (region-end))
1863 (list nil nil)))
1864 (if beg
1865 (elpy--nav-move-region-vertically beg end -1)
1866 (elpy--nav-move-line-vertically -1)))
1867
1868(defun elpy--nav-move-line-vertically (dir)
1869 "Move the current line vertically in direction DIR."
1870 (let* ((beg (point-at-bol))
1871 (end (point-at-bol 2))
1872 (col (current-column))
1873 (region (delete-and-extract-region beg end)))
1874 (forward-line dir)
1875 (save-excursion
1876 (insert region))
1877 (goto-char (+ (point) col))))
1878
1879(defun elpy--nav-move-region-vertically (beg end dir)
1880 "Move the current region vertically in direction DIR."
1881 (let* ((point-before-mark (< (point) (mark)))
1882 (beg (save-excursion
1883 (goto-char beg)
1884 (point-at-bol)))
1885 (end (save-excursion
1886 (goto-char end)
1887 (if (bolp)
1888 (point)
1889 (point-at-bol 2))))
1890 (region (delete-and-extract-region beg end)))
1891 (goto-char beg)
1892 (forward-line dir)
1893 (save-excursion
1894 (insert region))
1895 (if point-before-mark
1896 (set-mark (+ (point)
1897 (length region)))
1898 (set-mark (point))
1899 (goto-char (+ (point)
1900 (length region))))
1901 (setq deactivate-mark nil)))
1902
1903(defun elpy-open-and-indent-line-below ()
1904 "Open a line below the current one, move there, and indent."
1905 (interactive)
1906 (move-end-of-line 1)
1907 (newline-and-indent))
1908
1909(defun elpy-open-and-indent-line-above ()
1910 "Open a line above the current one, move there, and indent."
1911 (interactive)
1912 (move-beginning-of-line 1)
1913 (save-excursion
1914 (insert "\n"))
1915 (indent-according-to-mode))
1916
1917(defun elpy-nav-expand-to-indentation ()
1918 "Select surrounding lines with current indentation."
1919 (interactive)
1920 (setq elpy-nav-expand--initial-position (point))
1921 (let ((indentation (current-indentation)))
1922 (if (= indentation 0)
1923 (progn
1924 (push-mark (point))
1925 (push-mark (point-max) nil t)
1926 (goto-char (point-min)))
1927 (while (<= indentation (current-indentation))
1928 (forward-line -1))
1929 (forward-line 1)
1930 (push-mark (point) nil t)
1931 (while (<= indentation (current-indentation))
1932 (forward-line 1))
1933 (backward-char))))
1934
1935(defadvice keyboard-quit (before collapse-region activate)
1936 "Abort elpy selection by indentation on quit."
1937 (when (eq last-command 'elpy-nav-expand-to-indentation)
1938 (goto-char elpy-nav-expand--initial-position)))
1939
1940(defun elpy-nav-normalize-region ()
1941 "If the first or last line are not fully selected, select them completely."
1942 (let ((beg (region-beginning))
1943 (end (region-end)))
1944 (goto-char beg)
1945 (beginning-of-line)
1946 (push-mark (point) nil t)
1947 (goto-char end)
1948 (unless (= (point) (line-beginning-position))
1949 (end-of-line))))
1950
1951(defun elpy-nav-indent-shift-right (&optional _count)
1952 "Shift current line by COUNT columns to the right.
1953
1954COUNT defaults to `python-indent-offset'.
1955If region is active, normalize the region and shift."
1956 (interactive)
1957 (if (use-region-p)
1958 (progn
1959 (elpy-nav-normalize-region)
1960 (python-indent-shift-right (region-beginning) (region-end) current-prefix-arg))
1961 (python-indent-shift-right (line-beginning-position) (line-end-position) current-prefix-arg)))
1962
1963(defun elpy-nav-indent-shift-left (&optional _count)
1964 "Shift current line by COUNT columns to the left.
1965
1966COUNT defaults to `python-indent-offset'.
1967If region is active, normalize the region and shift."
1968 (interactive)
1969 (if (use-region-p)
1970 (progn
1971 (elpy-nav-normalize-region)
1972 (python-indent-shift-left (region-beginning) (region-end) current-prefix-arg))
1973 (python-indent-shift-left (line-beginning-position) (line-end-position) current-prefix-arg)))
1974
1975
1976;;;;;;;;;;;;;;;;
1977;;; Test running
1978
1979(defvar elpy-set-test-runner-history nil
1980 "History variable for `elpy-set-test-runner'.")
1981
1982(defun elpy-test (&optional test-whole-project)
1983 "Run tests on the current test, or the whole project.
1984
1985If there is a test at point, run that test. If not, or if a
1986prefix is given, run all tests in the current project."
1987 (interactive "P")
1988 (let ((current-test (elpy-test-at-point)))
1989 (if test-whole-project
1990 ;; With prefix arg, test the whole project.
1991 (funcall elpy-test-runner
1992 (car current-test)
1993 nil nil nil)
1994 ;; Else, run only this test
1995 (apply elpy-test-runner current-test))))
1996
1997(defun elpy-set-test-runner (test-runner)
1998 "Tell Elpy to use TEST-RUNNER to run tests.
1999
2000See `elpy-test' for how to invoke it."
2001 (interactive
2002 (list
2003 (let* ((runners (mapcar (lambda (value)
2004 (cons (nth 2 value)
2005 (nth 3 value)))
2006 (cdr (get 'elpy-test-runner 'custom-type))))
2007 (current (cdr (assq elpy-test-runner
2008 (mapcar (lambda (cell)
2009 (cons (cdr cell) (car cell)))
2010 runners))))
2011 (choice (completing-read (if current
2012 (format "Test runner (currently %s): "
2013 current)
2014 "Test runner: ")
2015 runners
2016 nil t nil 'elpy-set-test-runner-history)))
2017 (if (equal choice "")
2018 elpy-test-runner
2019 (cdr (assoc choice runners))))))
2020 (setq elpy-test-runner test-runner))
2021
2022(defun elpy-test-at-point ()
2023 "Return a list specifying the test at point, if any.
2024
2025This is used as the interactive
2026
2027This list has four elements.
2028
2029- Top level directory:
2030 All test files should be importable from here.
2031- Test file:
2032 The current file name.
2033- Test module:
2034 The module name, relative to the top level directory.
2035- Test name:
2036 The full name of the current test within the module, for
2037 example TestClass.test_method
2038
2039If there is no test at point, test name is nil.
2040If the current buffer is not visiting a file, only the top level
2041directory is not nil."
2042 (if (not buffer-file-name)
2043 (progn
2044 (save-some-buffers)
2045 (list (elpy-library-root) nil nil nil))
2046 (let* ((top (elpy-library-root))
2047 (file buffer-file-name)
2048 (module (elpy-test--module-name-for-file top file))
2049 (test (elpy-test--current-test-name)))
2050 (if (and file (string-match "test" (or module test "")))
2051 (progn
2052 (save-buffer)
2053 (list top file module test))
2054 (save-some-buffers)
2055 (list top nil nil nil)))))
2056
2057(defun elpy-test--current-test-name ()
2058 "Return the name of the test at point."
2059 (let ((name (python-info-current-defun)))
2060 (if (and name
2061 (string-match "\\`\\([^.]+\\.[^.]+\\)\\." name))
2062 (match-string 1 name)
2063 name)))
2064
2065(defun elpy-test--module-name-for-file (top-level module-file)
2066 "Return the module name relative to TOP-LEVEL for MODULE-FILE.
2067
2068For example, for a top level of /project/root/ and a module file
2069of /project/root/package/module.py, this would return
2070\"package.module\"."
2071 (let* ((relative-name (file-relative-name module-file top-level))
2072 (no-extension (replace-regexp-in-string "\\.py\\'" "" relative-name))
2073 (no-init (replace-regexp-in-string "/__init__\\'" "" no-extension))
2074 (dotted (replace-regexp-in-string "/" "." no-init)))
2075 (if (string-match "^\\." dotted)
2076 (concat "." (replace-regexp-in-string (regexp-quote "...") "." dotted))
2077 dotted)))
2078
2079(defun elpy-test-runner-p (obj)
2080 "Return t iff OBJ is a test runner.
2081
2082This uses the `elpy-test-runner-p' symbol property."
2083 (get obj 'elpy-test-runner-p))
2084
2085(defun elpy-test-run (working-directory command &rest args)
2086 "Run COMMAND with ARGS in WORKING-DIRECTORY as a test command."
2087 (let ((default-directory working-directory))
2088 (funcall elpy-test-compilation-function
2089 (mapconcat #'shell-quote-argument
2090 (cons command args)
2091 " "))))
2092
2093(defun elpy-test-get-discover-runner ()
2094 "Return the test discover runner from `elpy-test-discover-runner-command'."
2095 (cl-loop
2096 for string in elpy-test-discover-runner-command
2097 if (string= string "python-shell-interpreter")
2098 collect python-shell-interpreter
2099 else
2100 collect string))
2101
2102(defun elpy-test-discover-runner (top _file module test)
2103 "Test the project using the python unittest discover runner.
2104
2105This requires Python 2.7 or later."
2106 (interactive (elpy-test-at-point))
2107 (let ((test (cond
2108 (test (format "%s.%s" module test))
2109 (module module)
2110 (t "discover"))))
2111 (apply #'elpy-test-run
2112 top
2113 (append (elpy-test-get-discover-runner)
2114 (list test)))))
2115(put 'elpy-test-discover-runner 'elpy-test-runner-p t)
2116
2117(defun elpy-test-green-runner (top _file module test)
2118 "Test the project using the green runner."
2119 (interactive (elpy-test-at-point))
2120 (let* ((test (cond
2121 (test (format "%s.%s" module test))
2122 (module module)))
2123 (command (if test
2124 (append elpy-test-green-runner-command (list test))
2125 elpy-test-green-runner-command)))
2126 (apply #'elpy-test-run top command)))
2127(put 'elpy-test-green-runner 'elpy-test-runner-p t)
2128
2129(defun elpy-test-nose-runner (top _file module test)
2130 "Test the project using the nose test runner.
2131
2132This requires the nose package to be installed."
2133 (interactive (elpy-test-at-point))
2134 (if module
2135 (apply #'elpy-test-run
2136 top
2137 (append elpy-test-nose-runner-command
2138 (list (if test
2139 (format "%s:%s" module test)
2140 module))))
2141 (apply #'elpy-test-run
2142 top
2143 elpy-test-nose-runner-command)))
2144(put 'elpy-test-nose-runner 'elpy-test-runner-p t)
2145
2146(defun elpy-test-trial-runner (top _file module test)
2147 "Test the project using Twisted's Trial test runner.
2148
2149This requires the twisted-core package to be installed."
2150 (interactive (elpy-test-at-point))
2151 (if module
2152 (apply #'elpy-test-run
2153 top
2154 (append elpy-test-trial-runner-command
2155 (list (if test
2156 (format "%s.%s" module test)
2157 module))))
2158 (apply #'elpy-test-run top elpy-test-trial-runner-command)))
2159(put 'elpy-test-trial-runner 'elpy-test-runner-p t)
2160
2161(defun elpy-test-pytest-runner (top file module test)
2162 "Test the project using the py.test test runner.
2163
2164This requires the pytest package to be installed."
2165 (interactive (elpy-test-at-point))
2166 (cond
2167 (test
2168 (let ((test-list (split-string test "\\.")))
2169 (apply #'elpy-test-run
2170 top
2171 (append elpy-test-pytest-runner-command
2172 (list (mapconcat #'identity
2173 (cons file test-list)
2174 "::"))))))
2175 (module
2176 (apply #'elpy-test-run top (append elpy-test-pytest-runner-command
2177 (list file))))
2178 (t
2179 (apply #'elpy-test-run top elpy-test-pytest-runner-command))))
2180(put 'elpy-test-pytest-runner 'elpy-test-runner-p t)
2181
2182;;;;;;;;;;;;;;;;;
2183;;; Documentation
2184
2185(defvar elpy-doc-history nil
2186 "History for the `elpy-doc' command.")
2187
2188(defun elpy-doc ()
2189 "Show documentation for the symbol at point.
2190
2191If there is no documentation for the symbol at point, or if a
2192prefix argument is given, prompt for a symbol from the user."
2193 (interactive)
2194 (let ((doc nil))
2195 (unless current-prefix-arg
2196 (setq doc (elpy-rpc-get-docstring))
2197 (unless doc
2198 (save-excursion
2199 (python-nav-backward-up-list)
2200 (setq doc (elpy-rpc-get-docstring))))
2201 (unless doc
2202 (setq doc (elpy-rpc-get-pydoc-documentation
2203 (elpy-doc--symbol-at-point))))
2204 (unless doc
2205 (save-excursion
2206 (python-nav-backward-up-list)
2207 (setq doc (elpy-rpc-get-pydoc-documentation
2208 (elpy-doc--symbol-at-point))))))
2209 (unless doc
2210 (setq doc (elpy-rpc-get-pydoc-documentation
2211 (elpy-doc--read-identifier-from-minibuffer
2212 (elpy-doc--symbol-at-point)))))
2213 (if doc
2214 (elpy-doc--show doc)
2215 (error "No documentation found"))))
2216
2217(defun elpy-doc--read-identifier-from-minibuffer (initial)
2218 "Read a pydoc-able identifier from the minibuffer."
2219 (completing-read "Pydoc for: "
2220 (completion-table-dynamic #'elpy-rpc-get-pydoc-completions)
2221 nil nil initial 'elpy-doc-history))
2222
2223(defun elpy-doc--show (documentation)
2224 "Show DOCUMENTATION to the user, replacing ^H with bold."
2225 (with-help-window "*Python Doc*"
2226 (with-current-buffer "*Python Doc*"
2227 (erase-buffer)
2228 (insert documentation)
2229 (goto-char (point-min))
2230 (while (re-search-forward "\\(.\\)\b\\1" nil t)
2231 (replace-match (propertize (match-string 1)
2232 'face 'bold)
2233 t t)))))
2234
2235(defun elpy-doc--symbol-at-point ()
2236 "Return the Python symbol at point, including dotted paths."
2237 (with-syntax-table python-dotty-syntax-table
2238 (let ((symbol (symbol-at-point)))
2239 (if symbol
2240 (symbol-name symbol)
2241 nil))))
2242
2243;;;;;;;;;;;;;;
2244;;; Buffer manipulation
2245
2246(defun elpy-buffer--replace-block (spec)
2247 "Replace a block. SPEC is (startline endline newblock)."
2248 (let ((start-line (nth 0 spec))
2249 (end-line (nth 1 spec))
2250 (new-block (nth 2 spec)))
2251 (save-excursion
2252 (save-restriction
2253 (widen)
2254 (goto-char (point-min))
2255 (forward-line start-line)
2256 (let ((beg (point))
2257 (end (progn (forward-line (- end-line start-line)) (point))))
2258 ;; Avoid deleting and re-inserting when the blocks are equal.
2259 (unless (string-equal (buffer-substring beg end) new-block)
2260 (delete-region beg end)
2261 (insert new-block)))))))
2262
2263(defun elpy-buffer--replace-region (beg end rep)
2264 "Replace text in BUFFER in region (BEG END) with REP."
2265 (unless (string-equal (buffer-substring beg end) rep)
2266 (save-excursion
2267 (goto-char end)
2268 (insert rep)
2269 (delete-region beg end))))
2270
2271;;;;;;;;;;;;;;;;;;;;;
2272;;; Importmagic - make obsolete
2273
2274(defun elpy-importmagic-add-import ()
2275 (interactive))
2276(defun elpy-importmagic-fixup ()
2277 (interactive))
2278
2279(make-obsolete 'elpy-importmagic-add-import "support for importmagic has been dropped." "1.17.0")
2280(make-obsolete 'elpy-importmagic-fixup "support for importmagic has been dropped." "1.17.0")
2281
2282;;;;;;;;;;;;;;;;;;;;;
2283;;; Code reformatting
2284
2285
2286(defun elpy-format-code ()
2287 "Format code using the available formatter."
2288 (interactive)
2289 (let ((elpy-formatter (or elpy-formatter
2290 (catch 'available
2291 (dolist (formatter '(yapf autopep8 black))
2292 (when (elpy-config--package-available-p
2293 formatter)
2294 (throw 'available formatter)))))))
2295 (unless elpy-formatter
2296 (error "No formatter installed, please install one using `elpy-config'"))
2297 (unless (elpy-config--package-available-p elpy-formatter)
2298 (error "The '%s' formatter is not installed, please install it using `elpy-config' or choose another one using `elpy-formatter'"
2299 elpy-formatter))
2300 (when (interactive-p) (message "Autoformatting code with %s."
2301 elpy-formatter))
2302 (funcall (intern (format "elpy-%s-fix-code" elpy-formatter)))))
2303
2304
2305(defun elpy-yapf-fix-code ()
2306 "Automatically formats Python code with yapf.
2307
2308Yapf can be configured with a style file placed in the project
2309root directory."
2310 (interactive)
2311 (elpy--fix-code-with-formatter "fix_code_with_yapf"))
2312
2313(defun elpy-autopep8-fix-code ()
2314 "Automatically formats Python code to conform to the PEP 8 style guide.
2315
2316Autopep8 can be configured with a style file placed in the project
2317root directory."
2318 (interactive)
2319 (elpy--fix-code-with-formatter "fix_code"))
2320
2321(defun elpy-black-fix-code ()
2322 "Automatically formats Python code with black.
2323Note: Requires 'toml' to be installed due to legacy reasons."
2324 (interactive)
2325 (elpy--fix-code-with-formatter "fix_code_with_black"))
2326
2327(defun elpy--fix-code-with-formatter (method)
2328 "Common routine for formatting python code."
2329 (let ((line (line-number-at-pos))
2330 (col (current-column))
2331 (directory (if (elpy-project-root)
2332 (expand-file-name (elpy-project-root))
2333 default-directory)))
2334 (if (use-region-p)
2335 (let ((new-block (elpy-rpc method
2336 (list (elpy-rpc--region-contents)
2337 directory)))
2338 (beg (region-beginning))
2339 (end (region-end)))
2340 (elpy-buffer--replace-region
2341 beg end (string-trim-right new-block))
2342 (goto-char end)
2343 (deactivate-mark))
2344 (let ((new-block (elpy-rpc method
2345 (list (elpy-rpc--buffer-contents)
2346 directory)))
2347 (beg (point-min))
2348 (end (point-max)))
2349 (elpy-buffer--replace-region beg end new-block)
2350 (when (bobp)
2351 (forward-line (1- line))
2352 (forward-char col))))))
2353
2354;;;;;;;;;;;;;;
2355;;; Multi-Edit
2356
2357(defvar elpy-multiedit-overlays nil
2358 "List of overlays currently being edited.")
2359
2360(defun elpy-multiedit-add-overlay (beg end)
2361 "Add an editable overlay between BEG and END.
2362
2363A modification in any of these overlays will modify all other
2364overlays, too."
2365 (interactive "r")
2366 (when (elpy-multiedit--overlays-in-p beg end)
2367 (error "Overlapping multiedit overlays are not allowed"))
2368 (let ((ov (make-overlay beg end nil nil :rear-advance)))
2369 (overlay-put ov 'elpy-multiedit t)
2370 (overlay-put ov 'face 'highlight)
2371 (overlay-put ov 'modification-hooks '(elpy-multiedit--overlay-changed))
2372 (overlay-put ov 'insert-in-front-hooks '(elpy-multiedit--overlay-changed))
2373 (overlay-put ov 'insert-behind-hooks '(elpy-multiedit--overlay-changed))
2374 (push ov elpy-multiedit-overlays)))
2375
2376(defun elpy-multiedit--overlays-in-p (beg end)
2377 "Return t iff there are multiedit overlays between beg and end."
2378 (catch 'return
2379 (dolist (ov (overlays-in beg end))
2380 (when (overlay-get ov 'elpy-multiedit)
2381 (throw 'return t)))
2382 nil))
2383
2384(defun elpy-multiedit-stop ()
2385 "Stop editing multiple places at once."
2386 (interactive)
2387 (dolist (ov elpy-multiedit-overlays)
2388 (delete-overlay ov))
2389 (setq elpy-multiedit-overlays nil))
2390
2391(defun elpy-multiedit--overlay-changed (ov after-change _beg _end
2392 &optional _pre-change-length)
2393 "Called for each overlay that changes.
2394
2395This updates all other overlays."
2396 (when (and after-change
2397 (not undo-in-progress)
2398 (overlay-buffer ov))
2399 (let ((text (buffer-substring (overlay-start ov)
2400 (overlay-end ov)))
2401 (inhibit-modification-hooks t))
2402 (dolist (other-ov elpy-multiedit-overlays)
2403 (when (and (not (equal other-ov ov))
2404 (buffer-live-p (overlay-buffer other-ov)))
2405 (with-current-buffer (overlay-buffer other-ov)
2406 (save-excursion
2407 (goto-char (overlay-start other-ov))
2408 (insert text)
2409 (delete-region (point) (overlay-end other-ov)))))))))
2410
2411(defun elpy-multiedit ()
2412 "Edit all occurences of the symbol at point, or the active region.
2413
2414If multiedit is active, stop it."
2415 (interactive)
2416 (if elpy-multiedit-overlays
2417 (elpy-multiedit-stop)
2418 (let ((regex (if (use-region-p)
2419 (regexp-quote (buffer-substring (region-beginning)
2420 (region-end)))
2421 (format "\\_<%s\\_>" (regexp-quote
2422 (symbol-name
2423 (symbol-at-point))))))
2424 (case-fold-search nil))
2425 (save-excursion
2426 (goto-char (point-min))
2427 (while (re-search-forward regex nil t)
2428 (elpy-multiedit-add-overlay (match-beginning 0)
2429 (match-end 0)))))))
2430
2431(defun elpy-multiedit-python-symbol-at-point (&optional use-symbol-p)
2432 "Edit all usages of the the Python symbol at point.
2433
2434With prefix arg, edit all syntactic usages of the symbol at
2435point. This might include unrelated symbols that just share the
2436name."
2437 (interactive "P")
2438 (if (or elpy-multiedit-overlays
2439 use-symbol-p
2440 (use-region-p))
2441 ;; If we are already doing a multiedit, or are explicitly told
2442 ;; to use the symbol at point, or if we are on an active region,
2443 ;; call the multiedit function that does just that already.
2444 (call-interactively 'elpy-multiedit)
2445 ;; Otherwise, fetch usages from backend.
2446 (save-some-buffers)
2447 (let ((usages (condition-case err
2448 (elpy-rpc-get-usages)
2449 ;; This is quite the stunt, but elisp parses JSON
2450 ;; null as nil, which is indistinguishable from
2451 ;; the empty list, we stick to the error.
2452 (error
2453 (if (and (eq (car err) 'error)
2454 (stringp (cadr err))
2455 (string-match "not implemented" (cadr err)))
2456 'not-supported
2457 (error (cadr err)))))))
2458 (cond
2459 ((eq usages 'not-supported)
2460 (call-interactively 'elpy-multiedit)
2461 (message (concat "Using syntactic editing "
2462 "as current backend does not support get_usages.")))
2463 ((null usages)
2464 (call-interactively 'elpy-multiedit)
2465 (if elpy-multiedit-overlays
2466 (message (concat "Using syntactic editing as no usages of the "
2467 "symbol at point were found by the backend."))
2468 (message "No occurrences of the symbol at point found")))
2469 (t
2470 (save-restriction
2471 (widen)
2472 (elpy-multiedit--usages usages)))))))
2473
2474(defun elpy-multiedit--usages (usages)
2475 "Mark the usages in USAGES for editing."
2476 (let ((name nil)
2477 (locations (make-hash-table :test #'equal)))
2478 (dolist (usage usages)
2479 (let* ((filename (cdr (assq 'filename usage)))
2480 (this-name (cdr (assq 'name usage)))
2481 (offset (cdr (assq 'offset usage))))
2482 (setq name this-name)
2483 (with-current-buffer (if filename
2484 (find-file-noselect filename)
2485 (current-buffer))
2486 (elpy-multiedit-add-overlay (+ offset 1)
2487 (+ offset 1 (length this-name)))
2488 (save-excursion
2489 (goto-char (+ offset 1))
2490 (puthash filename
2491 (cons (list offset
2492 (buffer-substring (line-beginning-position)
2493 (line-end-position))
2494 (- (point)
2495 (line-beginning-position))
2496 (- (+ (point) (length this-name))
2497 (line-beginning-position)))
2498 (gethash filename locations))
2499 locations)))))
2500 (if (<= (hash-table-count locations)
2501 1)
2502 (message "Editing %s usages of '%s' in this buffer"
2503 (length usages) name)
2504 (with-current-buffer (get-buffer-create "*Elpy Edit Usages*")
2505 (let ((inhibit-read-only t)
2506 (filenames nil))
2507 (erase-buffer)
2508 (elpy-insert--para
2509 "The symbol '" name "' was found in multiple files. Editing "
2510 "all locations:\n\n")
2511 (maphash (lambda (key _value)
2512 (unless (member key filenames)
2513 (setq filenames (cons key filenames))))
2514 locations)
2515 (dolist (filename (sort filenames #'string<))
2516 (elpy-insert--header filename)
2517 (dolist (location (sort (gethash filename locations)
2518 (lambda (loc1 loc2)
2519 (< (car loc1)
2520 (car loc2)))))
2521 (let ((line (nth 1 location))
2522 (start (+ (line-beginning-position)
2523 (nth 2 location)))
2524 (end (+ (line-end-position)
2525 (nth 3 location))))
2526 ;; Insert the \n first, else we extend the overlay.
2527 (insert line "\n")
2528 (elpy-multiedit-add-overlay start end)))
2529 (insert "\n"))
2530 (goto-char (point-min))
2531 (display-buffer (current-buffer)
2532 nil
2533 'visible))))))
2534
2535;;;;;;;;;;;;;;;;;;;;;
2536;;; Occur Definitions
2537
2538(defun elpy-occur-definitions ()
2539 "Display an occur buffer of all definitions in the current buffer.
2540
2541Also, switch to that buffer."
2542 (interactive)
2543 (let ((list-matching-lines-face nil))
2544 (occur "^\s*\\(\\(async\s\\|\\)def\\|class\\)\s"))
2545 (let ((window (get-buffer-window "*Occur*")))
2546 (if window
2547 (select-window window)
2548 (switch-to-buffer "*Occur*"))))
2549
2550;;;;;;;;;;;;;;;;
2551;;; Xref backend
2552
2553(defun elpy--xref-backend ()
2554 "Return the name of the elpy xref backend."
2555 ;; If no rpc available, start one and assume jedi is available
2556 (if (or (and (not (elpy-rpc--process-buffer-p elpy-rpc--buffer))
2557 (elpy-rpc--get-rpc-buffer))
2558 elpy-rpc--jedi-available)
2559 'elpy
2560 nil))
2561
2562(defvar elpy-xref--format-references
2563 (let ((str "%s:\t%s"))
2564 (put-text-property 0 4 'face 'compilation-line-number str)
2565 str)
2566 "String used to format references in xref buffers.")
2567
2568;; Elpy location structure
2569(when (featurep 'xref)
2570 (cl-defstruct (xref-elpy-location
2571 (:constructor xref-make-elpy-location (file pos)))
2572 "Location of a python symbol definition."
2573 file pos)
2574
2575 (defun xref-make-elpy-location (file pos)
2576 "Return an elpy location structure.
2577Points to file FILE, at position POS."
2578 (make-instance 'xref-etags-location
2579 :file (expand-file-name file)
2580 :pos pos))
2581
2582 (cl-defmethod xref-location-marker ((l xref-elpy-location))
2583 (with-current-buffer (find-file-noselect (xref-elpy-location-file l))
2584 (save-excursion
2585 (goto-char (xref-elpy-location-pos l))
2586 (point-marker))))
2587
2588 (cl-defmethod xref-location-group ((l xref-elpy-location))
2589 (xref-elpy-location-file l))
2590
2591 ;; Identifiers
2592 (cl-defmethod xref-backend-identifier-at-point ((_backend (eql elpy)))
2593 (elpy-xref--identifier-at-point))
2594
2595 (defun elpy-xref--identifier-at-point ()
2596 "Return identifier at point.
2597
2598Is a string, formatted as \"LINE_NUMBER: VARIABLE_NAME\".
2599"
2600 (let* ((symb (symbol-at-point))
2601 (symb-str (substring-no-properties (symbol-name symb))))
2602 (when symb
2603 (format "%s: %s" (line-number-at-pos) symb-str))))
2604
2605 (defun elpy-xref--identifier-name (id)
2606 "Return the identifier ID variable name."
2607 (string-match ".*: \\([^:]*\\)" id)
2608 (match-string 1 id))
2609
2610 (defun elpy-xref--identifier-line (id)
2611 "Return the identifier ID line number."
2612 (string-match "\\([^:]*\\):.*" id)
2613 (string-to-number (match-string 1 id)))
2614
2615 (defun elpy-xref--goto-identifier (id)
2616 "Goto the identifier ID in the current buffer.
2617This is needed to get information on the identifier with jedi
2618\(that work only on the symbol at point\)"
2619 (let ((case-fold-search nil))
2620 (goto-char (point-min))
2621 (forward-line (1- (elpy-xref--identifier-line id)))
2622 (search-forward (elpy-xref--identifier-name id) (line-end-position))
2623 (goto-char (match-beginning 0))))
2624
2625 ;; Find definition
2626 (cl-defmethod xref-backend-definitions ((_backend (eql elpy)) id)
2627 (elpy-xref--definitions id))
2628
2629 (defun elpy-xref--definitions (id)
2630 "Return SYMBOL definition position as a xref object."
2631 (save-excursion
2632 (elpy-xref--goto-identifier id)
2633 (let* ((location (elpy-rpc-get-definition)))
2634 (if (not location)
2635 (error "No definition found")
2636 (let* ((file (expand-file-name (car location)))
2637 (pos (+ 1 (cadr location)))
2638 (line (with-current-buffer (find-file-noselect file)
2639 (goto-char (+ pos 1))
2640 (buffer-substring (line-beginning-position) (line-end-position))))
2641 (linenumber (with-current-buffer (find-file-noselect file)
2642 (line-number-at-pos pos)))
2643 (summary (format elpy-xref--format-references linenumber line))
2644 (loc (xref-make-elpy-location file pos)))
2645 (list (xref-make summary loc)))))))
2646
2647 ;; Find references
2648 (cl-defmethod xref-backend-references ((_backend (eql elpy)) id)
2649 (elpy-xref--references id))
2650
2651 (defun elpy-xref--references (id)
2652 "Return SYMBOL references as a list of xref objects."
2653 (save-excursion
2654 (elpy-xref--goto-identifier id)
2655 (let* ((references (elpy-rpc-get-usages)))
2656 (cl-loop
2657 for ref in references
2658 for file = (alist-get 'filename ref)
2659 for pos = (+ (alist-get 'offset ref) 1)
2660 for line = (when file
2661 (with-current-buffer (find-file-noselect file)
2662 (save-excursion
2663 (goto-char (+ pos 1))
2664 (buffer-substring (line-beginning-position) (line-end-position)))))
2665 for linenumber = (when file
2666 (with-current-buffer (find-file-noselect file)
2667 (line-number-at-pos pos)))
2668 for summary = (format elpy-xref--format-references linenumber line)
2669 for loc = (xref-make-elpy-location file pos)
2670 if file
2671 collect (xref-make summary loc)))))
2672
2673 ;; Completion table (used when calling `xref-find-references`)
2674 (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql elpy)))
2675 (elpy-xref--get-completion-table))
2676
2677 (defun elpy-xref--get-completion-table ()
2678 "Return the completion table for identifiers."
2679 (cl-loop
2680 for ref in (nreverse (elpy-rpc-get-names))
2681 for offset = (+ (alist-get 'offset ref) 1)
2682 for line = (line-number-at-pos offset)
2683 for id = (format "%s: %s" line (alist-get 'name ref))
2684 collect id))
2685
2686 ;; Apropos
2687 (cl-defmethod xref-backend-apropos ((_backend (eql elpy)) regex)
2688 (elpy-xref--apropos regex))
2689
2690 (defun elpy-xref--apropos (regex)
2691 "Return identifiers matching REGEX."
2692 (let ((ids (elpy-rpc-get-names))
2693 (case-fold-search nil))
2694 (cl-loop
2695 for id in ids
2696 for name = (alist-get 'name id)
2697 for filename = (alist-get 'filename id)
2698 for pos = (+ (alist-get 'offset id) 1)
2699 if (string-match regex name)
2700 collect (with-current-buffer (find-file-noselect filename)
2701 (goto-char pos)
2702 (save-excursion
2703 (let* ((linenumber (line-number-at-pos))
2704 (line (buffer-substring
2705 (line-beginning-position)
2706 (line-end-position)))
2707 (summary (format elpy-xref--format-references linenumber line))
2708 (loc (xref-make-elpy-location filename pos)))
2709 (xref-make summary loc)))))))
2710 )
2711
2712;;;;;;;;;;;
2713;;; Modules
2714
2715(defvar elpy-modules-initialized-p nil
2716 "Boolean, set to true if modules were run with `global-init'.")
2717
2718(defun elpy-modules-run (command &rest args)
2719 "Run COMMAND with ARGS for all modules in `elpy-modules'."
2720 (dolist (module elpy-modules)
2721 (apply module command args)))
2722
2723(defun elpy-modules-global-init ()
2724 "Run the global-init method of Elpy modules.
2725
2726Make sure this only happens once."
2727 (unless elpy-modules-initialized-p
2728 (elpy-modules-run 'global-init)
2729 (setq elpy-modules-initialized-p t)))
2730
2731(defun elpy-modules-global-stop ()
2732 "Run the global-stop method of Elpy modules.
2733
2734Make sure this only happens once per global-init call."
2735 (when elpy-modules-initialized-p
2736 (elpy-modules-run 'global-stop)
2737 (setq elpy-modules-initialized-p nil)))
2738
2739(defun elpy-modules-buffer-init ()
2740 "Run the buffer-init method of Elpy modules.
2741
2742Make sure global-init is called first."
2743 (elpy-modules-global-init)
2744 (elpy-modules-run 'buffer-init))
2745
2746(defun elpy-modules-buffer-stop ()
2747 "Run the buffer-stop method of Elpy modules."
2748 (elpy-modules-run 'buffer-stop))
2749
2750(defun elpy-modules-remove-modeline-lighter (modename)
2751 "Remove the lighter for MODENAME.
2752
2753It should not be necessary to see (Python Elpy yas company ElDoc) all the
2754time.
2755
2756If you need your modeline, you can set the variable `elpy-remove-modeline-lighter' to nil"
2757
2758 (interactive)
2759 (when elpy-remove-modeline-lighter
2760 (cond
2761 ((eq modename 'eldoc-minor-mode)
2762 (setq eldoc-minor-mode-string nil))
2763 (t
2764 (let ((cell (assq modename minor-mode-alist)))
2765 (when cell
2766 (setcdr cell
2767 (list ""))))))))
2768
2769;;;;;;;;;;;;;;;;;;;;;;;;;
2770;;; Module: Sane Defaults
2771
2772(defun elpy-module-sane-defaults (command &rest _args)
2773 "Module for sane Emacs default for python."
2774 (pcase command
2775 (`buffer-init
2776 ;; Set `forward-sexp-function' to nil in python-mode. See
2777 ;; http://debbugs.gnu.org/db/13/13642.html
2778 (set (make-local-variable 'forward-sexp-function) nil)
2779 ;; PEP8 recommends two spaces in front of inline comments.
2780 (set (make-local-variable 'comment-inline-offset) 2))
2781 (`buffer-stop
2782 (kill-local-variable 'forward-sexp-function)
2783 (kill-local-variable 'comment-inline-offset))))
2784
2785;;;;;;;;;;;;;;;;;;;
2786;;; Module: Company
2787
2788(defun elpy-module-company (command &rest _args)
2789 "Module to support company-mode completions."
2790 (pcase command
2791 (`global-init
2792 (require 'company)
2793 (require 'company-capf)
2794 (elpy-modules-remove-modeline-lighter 'company-mode)
2795 (define-key company-active-map (kbd "C-d")
2796 'company-show-doc-buffer)
2797 (add-hook 'inferior-python-mode-hook
2798 (lambda ()
2799 ;; Workaround for company bug
2800 ;; (https://github.com/company-mode/company-mode/issues/759)
2801 (setq-local company-transformers
2802 (remove 'company-sort-by-occurrence
2803 company-transformers))
2804 ;; Be sure to trigger completion for one character variable
2805 ;; (i.e. `a.`)
2806 (setq-local company-minimum-prefix-length 2))))
2807
2808 (`buffer-init
2809 ;; We want immediate completions from company.
2810 (set (make-local-variable 'company-idle-delay)
2811 0.1)
2812 ;; And annotations should be right-aligned.
2813 (set (make-local-variable 'company-tooltip-align-annotations)
2814 t)
2815 ;; Also, dabbrev in comments and strings is nice.
2816 (set (make-local-variable 'company-dabbrev-code-everywhere)
2817 t)
2818 ;; Add our own backend and remove a bunch of backends that
2819 ;; interfere in Python mode.
2820 (set (make-local-variable 'company-backends)
2821 (cons 'elpy-company-backend
2822 (delq 'company-semantic
2823 (delq 'company-ropemacs
2824 (delq 'company-capf
2825 (mapcar #'identity company-backends))))))
2826 (company-mode 1)
2827 (when (> (buffer-size) elpy-rpc-ignored-buffer-size)
2828 (message
2829 (concat "Buffer %s larger than elpy-rpc-ignored-buffer-size (%d)."
2830 " Elpy will turn off completion.")
2831 (buffer-name) elpy-rpc-ignored-buffer-size)))
2832 (`buffer-stop
2833 (company-mode -1)
2834 (kill-local-variable 'company-idle-delay)
2835 (kill-local-variable 'company-tooltip-align-annotations)
2836 (kill-local-variable 'company-backends))
2837 ))
2838
2839(defvar elpy-company--cache nil
2840 "Buffer-local cache for candidate information.")
2841(make-variable-buffer-local 'elpy-company--cache)
2842
2843(defun elpy-company--cache-clear ()
2844 "Clear and initialize the cache."
2845 (if elpy-company--cache
2846 (clrhash elpy-company--cache)
2847 (setq elpy-company--cache
2848 (make-hash-table :test #'equal))))
2849
2850(defun elpy-company--cache-annotation (name)
2851 "Return the cached annotation for NAME."
2852 (when elpy-company--cache
2853 (cdr (assq 'annotation (gethash name elpy-company--cache)))))
2854
2855(defun elpy-company--cache-meta (name)
2856 "Return the cached annotation for NAME."
2857 (when elpy-company--cache
2858 (cdr (assq 'meta (gethash name elpy-company--cache)))))
2859
2860(defun elpy-company--cache-name (name)
2861 "Return the cached name for NAME.
2862
2863Confused yet? We pass \"our\" name, that is, prefix + suffix,
2864here, and return the \"name\" as used by the backend."
2865 (when elpy-company--cache
2866 (cdr (assq 'name (gethash name elpy-company--cache)))))
2867
2868(defun elpy-company--cache-completions (prefix result)
2869 "Store RESULT in the candidate cache and return candidates."
2870 (elpy-company--cache-clear)
2871 (mapcar (lambda (completion)
2872 (let* ((suffix (cdr (assq 'name completion)))
2873 (name (concat (s-chop-suffix (company-grab-symbol) prefix) suffix)))
2874 (puthash name completion elpy-company--cache)
2875 name))
2876 result))
2877
2878(defun elpy-company--python-exception-p (name)
2879 "Check whether NAME is a Python exception."
2880 (member name '("ArithmeticError"
2881 "AssertionError"
2882 "AttributeError"
2883 "BlockingIOError"
2884 "BrokenPipeError"
2885 "BufferError"
2886 "BytesWarning"
2887 "ChildProcessError"
2888 "ConnectionAbortedError"
2889 "ConnectionError"
2890 "ConnectionRefusedError"
2891 "ConnectionResetError"
2892 "DeprecationWarning"
2893 "EOFError"
2894 "EnvironmentError"
2895 "Exception"
2896 "FileExistsError"
2897 "FileNotFoundError"
2898 "FloatingPointError"
2899 "FutureWarning"
2900 "IOError"
2901 "ImportError"
2902 "ImportWarning"
2903 "IndentationError"
2904 "IndexError"
2905 "InterruptedError"
2906 "IsADirectoryError"
2907 "KeyError"
2908 "LookupError"
2909 "MemoryError"
2910 "NameError"
2911 "NotADirectoryError"
2912 "NotImplementedError"
2913 "OSError"
2914 "OverflowError"
2915 "PendingDeprecationWarning"
2916 "PermissionError"
2917 "ProcessLookupError"
2918 "RecursionError"
2919 "ReferenceError"
2920 "ResourceWarning"
2921 "RuntimeError"
2922 "RuntimeWarning"
2923 "StandardError"
2924 "StopAsyncIteration"
2925 "StopIteration"
2926 "SyntaxError"
2927 "SyntaxWarning"
2928 "SystemError"
2929 "TabError"
2930 "TimeoutError"
2931 "TypeError"
2932 "UnboundLocalError"
2933 "UnicodeDecodeError"
2934 "UnicodeEncodeError"
2935 "UnicodeError"
2936 "UnicodeTranslateError"
2937 "UnicodeWarning"
2938 "UserWarning"
2939 "ValueError"
2940 "Warning"
2941 "ZeroDivisionError")))
2942
2943(defun elpy-company-post-complete-parens (annotation name)
2944 "Complete functions, classes, and callable instances with parentheses.
2945
2946Add parentheses in case ANNOTATION is \"class\", \"function\", or
2947\"instance\",unless the completion is already looking at a left
2948 parenthesis,or unless NAME is a Python exception outside a reasonably
2949 formed raise statement,or unless NAME is no callable instance."
2950 (unless (looking-at-p "\(")
2951 (cond ((string= annotation "function")
2952 (insert "()")
2953 (backward-char 1))
2954 ((string= annotation "class")
2955 (cond ((elpy-company--python-exception-p name)
2956 (when (save-excursion
2957 (backward-word 2)
2958 (looking-at "\\_<raise\\_>"))
2959 (insert "()")
2960 (backward-char 1)))
2961 (t
2962 (insert "()")
2963 (backward-char 1))))
2964 ((string= annotation "instance")
2965 ;; The jedi backend annotates some callables as instances (e.g. numpy
2966 ;; and scipy) and `elpy-company--cache' does not allow to identify
2967 ;; callable instances.
2968 ;; It looks easy to modify `elpy-company--cache' cheaply for the jedi
2969 ;; backend to eliminate the `elpy-rpc-get-calltip' call below.
2970 (insert "()")
2971 (backward-char 1)
2972 (unless (elpy-rpc-get-calltip)
2973 (backward-char 1)
2974 (delete-char 2))))))
2975
2976(defun elpy-company--add-interpreter-completions-candidates (candidates)
2977 "Add completions candidates from the shell to the list of candidates.
2978
2979Get completions candidates at point from the shell, normalize them to look
2980like what elpy-company returns, merge them with the CANDIDATES list
2981and return the list."
2982 ;; Check if prompt available
2983 (if (not (and elpy-get-info-from-shell
2984 (elpy-shell--check-if-shell-available)))
2985 candidates
2986 ;; Completion need the cursor to be at the end of the shell buffer
2987 (save-excursion
2988 (with-current-buffer (process-buffer (python-shell-get-process))
2989 (goto-char (point-max)))
2990 ;; Try to get the info with timeout
2991 (let* ((new-candidates (with-timeout (elpy-get-info-from-shell-timeout
2992 '(nil nil nil))
2993 (python-completion-complete-at-point)))
2994 (start (nth 0 new-candidates))
2995 (end (nth 1 new-candidates))
2996 (completion-list (nth 2 new-candidates)))
2997 (if (not (and start end))
2998 candidates
2999 ;; Add the new candidates to the current ones
3000 (let ((candidate-names (cl-map 'list
3001 (lambda (el) (cdr (assoc 'name el)))
3002 candidates))
3003 (new-candidate-names (all-completions
3004 (buffer-substring start end)
3005 completion-list)))
3006 (cl-loop
3007 for pytel-cand in new-candidate-names
3008 for pytel-cand = (replace-regexp-in-string "($" "" pytel-cand)
3009 for pytel-cand = (replace-regexp-in-string "^.*\\." "" pytel-cand)
3010 for pytel-cand = (string-trim pytel-cand)
3011 unless (member pytel-cand candidate-names)
3012 do (push (list (cons 'name pytel-cand)) candidates)
3013 ))
3014 candidates)))))
3015
3016(defun elpy-company-backend (command &optional arg &rest ignored)
3017 "A company-mode backend for Elpy."
3018 (interactive (list 'interactive))
3019 (pcase command
3020 (`interactive
3021 (company-begin-backend 'elpy-company-backend))
3022 ;; init => Called once per buffer
3023 ;; prefix => return the prefix at point
3024 (`prefix
3025 (when (and elpy-mode
3026 (not (company-in-string-or-comment)))
3027 (company-grab-symbol-cons "\\." 1)))
3028 ;; candidates <prefix> => return candidates for this prefix
3029 (`candidates
3030 (cons :async
3031 (lambda (callback)
3032 (elpy-rpc-get-completions
3033 (lambda (result)
3034 ;; add completion candidates from python.el
3035 (setq result
3036 (elpy-company--add-interpreter-completions-candidates
3037 result))
3038 (elpy-company--cache-clear)
3039 (funcall
3040 callback
3041 (cond
3042 ;; The backend returned something
3043 (result
3044 (elpy-company--cache-completions arg result))
3045 ;; Nothing from the backend, try dabbrev-code.
3046 ((> (length arg) company-minimum-prefix-length)
3047 (elpy--sort-and-strip-duplicates
3048 (company-dabbrev-code 'candidates arg)))
3049 ;; Well, ok, let's go meh.
3050 (t
3051 nil))))))))
3052 ;; sorted => t if the list is already sorted
3053 (`sorted
3054 t)
3055 ;; duplicates => t if there could be duplicates
3056 (`duplicates
3057 nil)
3058 ;; no-cache <prefix> => t if company shouldn't cache results
3059 ;; meta <candidate> => short docstring for minibuffer
3060 (`meta
3061 (let ((meta (elpy-company--cache-meta arg)))
3062 (when (and meta
3063 (string-match "\\`\\(.*\n.*\\)\n.*" meta))
3064 (setq meta (match-string 1 meta)))
3065 meta))
3066 ;; annotation <candidate> => short docstring for completion buffer
3067 (`annotation
3068 (elpy-company--cache-annotation arg))
3069 ;; doc-buffer <candidate> => put doc buffer in `company-doc-buffer'
3070 (`doc-buffer
3071 (let* ((name (elpy-company--cache-name arg))
3072 (doc (when name
3073 (elpy-rpc-get-completion-docstring name))))
3074 (when doc
3075 (company-doc-buffer doc))))
3076 ;; require-match => Never require a match, even if the user
3077 ;; started to interact with company. See `company-require-match'.
3078 (`require-match
3079 'never)
3080 ;; location <candidate> => (buffer . point) or (file .
3081 ;; line-number)
3082 (`location
3083 (let* ((name (elpy-company--cache-name arg))
3084 (loc (when name
3085 (elpy-rpc-get-completion-location name))))
3086 (when loc
3087 (cons (car loc)
3088 (cadr loc)))))
3089 ;; match <candidate> => for non-prefix based backends
3090 ;; post-completion <candidate> => after insertion, for snippets
3091 (`post-completion
3092 (funcall elpy-company-post-completion-function
3093 (elpy-company--cache-annotation arg)
3094 (elpy-company--cache-name arg)))))
3095
3096(defun elpy--sort-and-strip-duplicates (seq)
3097 "Sort SEQ and remove any duplicates."
3098 (sort (delete-dups seq)
3099 (lambda (a b)
3100 (string< a b))))
3101
3102;;;;;;;;;;;;;;;;;
3103;;; Module: ElDoc
3104
3105(defun elpy-module-eldoc (command &rest _args)
3106 "Module to support ElDoc for Python files."
3107 (pcase command
3108 (`global-init
3109 (require 'eldoc)
3110 (elpy-modules-remove-modeline-lighter 'eldoc-minor-mode))
3111 (`buffer-init
3112 (eldoc-add-command-completions "python-indent-dedent-line-backspace")
3113 (set (make-local-variable 'company-frontends)
3114 (remq 'company-echo-metadata-frontend company-frontends))
3115 ;; New (Emacs >= 28) vs old eldoc API
3116 (if (and (version< "28.0.0" emacs-version)
3117 (boundp 'eldoc-documentation-functions))
3118 (add-hook 'eldoc-documentation-functions
3119 'elpy-eldoc-documentation nil t)
3120 (set (make-local-variable 'eldoc-documentation-function)
3121 'elpy-eldoc-documentation))
3122 (eldoc-mode 1))
3123 (`buffer-stop
3124 (eldoc-mode -1)
3125 (kill-local-variable 'eldoc-documentation-function))))
3126
3127(defun elpy-eldoc-documentation (&optional callback &rest _more)
3128 "Return some interesting information for the code at point.
3129
3130This function is meant to be added to `eldoc-documentation-functions'
3131\(for Emacs >= 28) or set in `eldoc-documentation-function' (for older
3132Emacs versions).
3133
3134This will return flymake errors for the line at point if there are
3135any. If not, this will do an asynchronous call to the RPC backend to
3136get a call tip, and display that using `eldoc-message'. If the backend
3137has no call tip, this will display the current class and method
3138instead.
3139
3140If specified, CALLBACK is the function called to display the
3141documentation (only used for Emacs >= 28)."
3142 (let ((cb (or callback
3143 (lambda (doc &rest plist)
3144 (let ((thing (plist-get plist :thing))
3145 (face (plist-get plist :face)))
3146 (when thing
3147 (setq thing (format "%s: "
3148 (propertize thing
3149 'face
3150 'font-lock-function-name-face)))
3151 (setq doc
3152 (if (version<= emacs-version "25")
3153 (format "%s%s" thing doc)
3154 (let ((eldoc-echo-area-use-multiline-p nil))
3155 (eldoc-docstring-format-sym-doc thing doc)))))
3156 (eldoc-message doc)))))
3157 (flymake-error (elpy-flymake-error-at-point)))
3158 (if flymake-error
3159 flymake-error
3160 (elpy-rpc-get-calltip-or-oneline-docstring
3161 (lambda (info)
3162 (cond
3163 ;; INFO is a string, just display it
3164 ((stringp info)
3165 (funcall cb info))
3166 ;; INFO is a calltip
3167 ((string= (cdr (assq 'kind info)) "calltip")
3168 (let ((name (cdr (assq 'name info)))
3169 (index (cdr (assq 'index info)))
3170 (params (cdr (assq 'params info))))
3171 (when index
3172 (setf (nth index params)
3173 (propertize (nth index params)
3174 'face
3175 'eldoc-highlight-function-argument)))
3176 (let* ((prefix (propertize name 'face
3177 'font-lock-function-name-face))
3178 (args (format "%s(%s)" prefix
3179 (mapconcat #'identity params ", "))))
3180 (funcall cb args))))
3181 ;; INFO is a oneline docstring
3182 ((string= (cdr (assq 'kind info)) "oneline_doc")
3183 (let ((name (cdr (assq 'name info)))
3184 (docs (cdr (assq 'doc info))))
3185 (funcall cb docs
3186 :thing name
3187 :face 'font-lock-function-name-face)))
3188 ;; INFO is nil, maybe display the current function
3189 (t
3190 (when elpy-eldoc-show-current-function
3191 (let ((current-defun (python-info-current-defun)))
3192 (when current-defun
3193 (eldoc-message
3194 (concat "In: "
3195 (propertize
3196 (format "%s()" current-defun)
3197 'face 'font-lock-function-name-face))))))))))
3198 (if callback
3199 ;; New protocol: return non-nil, non-string
3200 t
3201 ;; Old protocol: return the last message until we're done
3202 eldoc-last-message))))
3203
3204
3205;;;;;;;;;;;;;;;;;;;
3206;;; Module: Folding
3207
3208(defun elpy-module-folding (command &rest _args)
3209 "Module allowing code folding in Python."
3210 (pcase command
3211
3212 (`global-init
3213 (elpy-modules-remove-modeline-lighter 'hs-minor-mode))
3214
3215 (`buffer-init
3216 (hs-minor-mode 1)
3217 (setq-local hs-set-up-overlay 'elpy-folding--display-code-line-counts)
3218 (setq-local hs-allow-nesting t)
3219 (define-key elpy-mode-map [left-fringe mouse-1]
3220 'elpy-folding--click-fringe)
3221 (define-key elpy-mode-map (kbd "<mouse-1>") 'elpy-folding--click-text)
3222 (elpy-folding--mark-foldable-lines)
3223 (add-to-list 'after-change-functions 'elpy-folding--mark-foldable-lines))
3224
3225 (`buffer-stop
3226 (hs-minor-mode -1)
3227 (kill-local-variable 'hs-set-up-overlay)
3228 (kill-local-variable 'hs-allow-nesting)
3229 (define-key elpy-mode-map [left-fringe mouse-1] nil)
3230 (define-key elpy-mode-map (kbd "<mouse-1>") nil)
3231 (remove 'elpy-folding--mark-foldable-lines after-change-functions)
3232 (cl-loop for prop in '(elpy-hs-folded elpy-hs-foldable elpy-hs-fringe)
3233 do (remove-overlays (point-min) (point-max) prop t)))))
3234
3235;; Fringe and folding indicators
3236(when (fboundp 'define-fringe-bitmap)
3237 (define-fringe-bitmap 'elpy-folding-fringe-marker
3238 (vector #b00000000
3239 #b00000000
3240 #b00000000
3241 #b11000011
3242 #b11100111
3243 #b01111110
3244 #b00111100
3245 #b00011000))
3246
3247 (define-fringe-bitmap 'elpy-folding-fringe-foldable-marker
3248 (vector #b00100000
3249 #b00010000
3250 #b00001000
3251 #b00000100
3252 #b00000100
3253 #b00001000
3254 #b00010000
3255 #b00100000)))
3256
3257(defcustom elpy-folding-fringe-face 'elpy-folding-fringe-face
3258 "Face for folding bitmaps appearing on the fringe."
3259 :type 'face
3260 :group 'elpy)
3261
3262(defface elpy-folding-fringe-face
3263 '((t (:inherit 'font-lock-comment-face
3264 :box (:line-width 1 :style released-button))))
3265 "Face for folding bitmaps appearing on the fringe."
3266 :group 'elpy)
3267
3268(defcustom elpy-folding-face 'elpy-folding-face
3269 "Face for the folded region indicator."
3270 :type 'face
3271 :group 'elpy)
3272
3273(defface elpy-folding-face
3274 '((t (:inherit 'font-lock-comment-face :box t)))
3275 "Face for the folded region indicator."
3276 :group 'elpy)
3277
3278(defcustom elpy-folding-fringe-indicators t
3279 "Non-nil if Elpy should display folding fringe indicators."
3280 :set (lambda (var val) ;
3281 (set-default var val)
3282 (dolist (buffer (buffer-list))
3283 (when (and (with-current-buffer buffer elpy-mode)
3284 (member 'elpy-folding--mark-foldable-lines
3285 after-change-functions))
3286 (elpy-folding--mark-foldable-lines)))))
3287
3288(defvar elpy-folding-docstring-regex "[uU]?[rR]?\"\"\""
3289 "Regular expression matching docstrings openings and closings.")
3290
3291(defvar elpy-docstring-block-start-regexp
3292 "^\\s-*[uU]?[rR]?\"\"\"\n?\\s-*"
3293 "Version of `hs-block-start-regexp' for docstrings.")
3294
3295(defface elpy-codecell-boundary '((t :inherit 'highlight))
3296 "Face for elpy codecell boundary."
3297 :group 'elpy-mode)
3298
3299
3300;; Indicators
3301(defun elpy-folding--display-code-line-counts (ov)
3302 "Display a folded region indicator with the number of folded lines.
3303
3304Meant to be used as `hs-set-up-overlay'."
3305 (let* ((marker-string "*fringe-dummy*")
3306 (marker-length (length marker-string)))
3307 (cond
3308 ((eq 'code (overlay-get ov 'hs))
3309 (let* ((nmb-line (count-lines (overlay-start ov) (overlay-end ov)))
3310 (display-string (format "(%d)..." nmb-line)))
3311 ;; fringe indicator
3312 (when elpy-folding-fringe-indicators
3313 (put-text-property 0 marker-length 'display
3314 (list 'left-fringe 'elpy-folding-fringe-marker
3315 'elpy-folding-fringe-face)
3316 marker-string)
3317 (overlay-put ov 'before-string marker-string)
3318 (overlay-put ov 'elpy-hs-fringe t))
3319 ;; folding indicator
3320 (put-text-property 0 (length display-string)
3321 'face 'elpy-folding-face display-string)
3322 (put-text-property 0 (length display-string)
3323 'mouse-face 'highlight display-string)
3324 (overlay-put ov 'display display-string)
3325 (overlay-put ov 'elpy-hs-folded t)))
3326 ;; for docstring and comments, we don't display the number of line
3327 ((or (eq 'docstring (overlay-get ov 'hs))
3328 (eq 'comment (overlay-get ov 'hs)))
3329 (let ((display-string "..."))
3330 (put-text-property 0 (length display-string)
3331 'mouse-face 'highlight display-string)
3332 (overlay-put ov 'display display-string)
3333 (overlay-put ov 'elpy-hs-folded t))))))
3334
3335(defun elpy-folding--mark-foldable-lines (&optional beg end rm-text-len)
3336 "Add a fringe indicator for foldable lines.
3337
3338Meant to be used as a hook to `after-change-functions'."
3339 (when elpy-folding-fringe-indicators
3340 (save-excursion
3341 (save-restriction
3342 (save-match-data
3343 (when (and beg end)
3344 (narrow-to-region (progn (goto-char beg)
3345 (line-beginning-position))
3346 (progn (goto-char end)
3347 (line-end-position))))
3348 (remove-overlays (point-min) (point-max) 'elpy-hs-foldable t)
3349 (goto-char (point-min))
3350 (while (re-search-forward
3351 python-nav-beginning-of-defun-regexp nil t)
3352 (let* ((beg (line-beginning-position))
3353 (end (line-end-position))
3354 (ov (make-overlay beg end))
3355 (marker-string "*fringe-dummy*")
3356 (marker-length (length marker-string)))
3357 (when (version<= "25.2" emacs-version)
3358 (put-text-property 0 marker-length
3359 'display
3360 (list
3361 'left-fringe
3362 'elpy-folding-fringe-foldable-marker
3363 'elpy-folding-fringe-face)
3364 marker-string)
3365 (overlay-put ov 'before-string marker-string))
3366 (overlay-put ov 'elpy-hs-foldable t))))))))
3367
3368;; Mouse interaction
3369(defun elpy-folding--click-fringe (event)
3370 "Hide or show block on fringe click."
3371 (interactive "e")
3372 (hs-life-goes-on
3373 (when elpy-folding-fringe-indicators
3374 (mouse-set-point event)
3375 (let* ((folded (save-excursion
3376 (end-of-line)
3377 (cl-remove-if-not (lambda (ov)
3378 (overlay-get ov 'elpy-hs-folded))
3379 (overlays-at (point)))))
3380 (foldable (cl-remove-if-not (lambda (ov)
3381 (overlay-get ov 'elpy-hs-foldable))
3382 (overlays-at (point)))))
3383 (if folded
3384 (hs-show-block)
3385 (if foldable
3386 (hs-hide-block)))))))
3387
3388(defun elpy-folding--click-text (event)
3389 "Show block on click."
3390 (interactive "e")
3391 (hs-life-goes-on
3392 (save-excursion
3393 (let ((window (posn-window (event-end event)))
3394 (pos (posn-point (event-end event))))
3395 (with-current-buffer (window-buffer window)
3396 (goto-char pos)
3397 (when (hs-overlay-at (point))
3398 (hs-show-block)
3399 (deactivate-mark)))))))
3400
3401;; Hidding docstrings
3402(defun elpy-folding--hide-docstring-region (beg end)
3403 "Hide a region from BEG to END, marking it as a docstring.
3404
3405BEG and END have to be respectively on the first and last line
3406of the docstring, their values are adapted to hide only the
3407docstring body."
3408 (hs-life-goes-on
3409 ;; do not fold oneliners
3410 (when (not (save-excursion
3411 (goto-char beg)
3412 (beginning-of-line)
3413 (re-search-forward
3414 (concat elpy-folding-docstring-regex
3415 ".*"
3416 elpy-folding-docstring-regex)
3417 (line-end-position) t)))
3418 ;; get begining position (do not fold first doc line)
3419 (save-excursion
3420 (goto-char beg)
3421 (when (save-excursion
3422 (beginning-of-line)
3423 (re-search-forward
3424 (concat elpy-folding-docstring-regex
3425 "[[:space:]]*$")
3426 (line-end-position) t))
3427 (forward-line 1))
3428 (beginning-of-line)
3429 (back-to-indentation)
3430 (setq beg (point))
3431 (setq ov-beg (line-end-position)))
3432 ;; get end position
3433 (save-excursion
3434 (goto-char end)
3435 (setq end (line-beginning-position))
3436 (setq ov-end (line-end-position)))
3437 (hs-discard-overlays ov-beg ov-end)
3438 (hs-make-overlay ov-beg ov-end 'docstring (- beg ov-beg) (- end ov-end))
3439 (run-hooks 'hs-hide-hook)
3440 (goto-char beg))))
3441
3442(defun elpy-folding--hide-docstring-at-point ()
3443 "Hide the docstring at point."
3444 (hs-life-goes-on
3445 (let ((hs-block-start-regexp elpy-docstring-block-start-regexp))
3446 (when (and (python-info-docstring-p) (not (hs-already-hidden-p)))
3447 (let (beg end line-beg line-end)
3448 ;; Get first doc line
3449 (if (not (save-excursion (forward-line -1)
3450 (python-info-docstring-p)))
3451 (setq beg (line-beginning-position))
3452 (forward-line -1)
3453 (end-of-line)
3454 (re-search-backward (concat "^[[:space:]]*"
3455 elpy-folding-docstring-regex)
3456 nil t)
3457 (setq beg (line-beginning-position)))
3458 ;; Go to docstring opening (to be sure to be inside the docstring)
3459 (re-search-forward elpy-folding-docstring-regex nil t)
3460 (setq line-beg (line-number-at-pos))
3461 ;; Get last line
3462 (if (not (save-excursion (forward-line 1)
3463 (python-info-docstring-p)))
3464 (progn
3465 (setq end (line-end-position))
3466 (setq line-end (line-number-at-pos)))
3467 (re-search-forward elpy-folding-docstring-regex nil t)
3468 (setq end (line-end-position))
3469 (setq line-end (line-number-at-pos)))
3470 ;; hide the docstring
3471 (when (not (= line-end line-beg))
3472 (elpy-folding--hide-docstring-region beg end)))))))
3473
3474(defun elpy-folding--show-docstring-at-point ()
3475 "Show docstring at point."
3476 (hs-life-goes-on
3477 (let ((hs-block-start-regexp elpy-docstring-block-start-regexp))
3478 (when (python-info-docstring-p)
3479 (hs-show-block)))))
3480
3481(defvar-local elpy-folding-docstrings-hidden nil
3482 "If docstrings are globally hidden or not.")
3483
3484(defun elpy-folding-toggle-docstrings ()
3485 "Fold or unfold every docstrings in the current buffer."
3486 (interactive)
3487 (if (not hs-minor-mode)
3488 (message "Please enable the 'Folding module' to use this functionality.")
3489 (hs-life-goes-on
3490 (save-excursion
3491 (goto-char (point-min))
3492 (while (python-nav-forward-defun)
3493 (search-forward-regexp ")\\s-*:" nil t)
3494 (forward-line)
3495 (when (and (python-info-docstring-p)
3496 (progn
3497 (beginning-of-line)
3498 (search-forward-regexp elpy-folding-docstring-regex
3499 nil t)))
3500 (forward-char 2)
3501 (back-to-indentation)
3502 ;; be sure not to act on invisible docstrings
3503 (unless (and (hs-overlay-at (point))
3504 (not (eq (overlay-get (hs-overlay-at (point)) 'hs)
3505 'docstring)))
3506 (if elpy-folding-docstrings-hidden
3507 (elpy-folding--show-docstring-at-point)
3508 (elpy-folding--hide-docstring-at-point)))))))
3509 (setq elpy-folding-docstrings-hidden (not elpy-folding-docstrings-hidden))))
3510
3511;; Hiding comments
3512(defvar-local elpy-folding-comments-hidden nil
3513 "If comments are globally hidden or not.")
3514
3515(defun elpy-folding-toggle-comments ()
3516 "Fold or unfold every comment blocks in the current buffer."
3517 (interactive)
3518 (if (not hs-minor-mode)
3519 (message "Please enable the 'Folding module' to use this functionality.")
3520 (hs-life-goes-on
3521 (save-excursion
3522 (goto-char (point-min))
3523 (while (comment-search-forward (point-max) t)
3524 ;; be suse not to act on invisible comments
3525 (unless (and (hs-overlay-at (point))
3526 (not (eq (overlay-get (hs-overlay-at (point)) 'hs)
3527 'comment)))
3528 (if (not elpy-folding-comments-hidden)
3529 ;; be sure to be on a multiline comment
3530 (when (save-excursion (forward-line)
3531 (comment-only-p (line-beginning-position)
3532 (line-end-position)))
3533 (hs-hide-block))
3534 (hs-show-block)))
3535 (python-util-forward-comment (buffer-size))))
3536 (setq elpy-folding-comments-hidden (not elpy-folding-comments-hidden)))))
3537
3538;; Hiding leafs
3539;; taken from https://www.emacswiki.org/emacs/HideShow
3540(defun elpy-folding--hide-leafs (beg end)
3541 "Hide blocks that do not contain others blocks in region (BEG END)."
3542 (hs-life-goes-on
3543 (save-excursion
3544 (goto-char beg)
3545 ;; Find the current block beginning and end
3546 (when (hs-find-block-beginning)
3547 (setq beg (1+ (point)))
3548 (funcall hs-forward-sexp-func 1)
3549 (setq end (1- (point))))
3550 ;; Show all branches if nesting not allowed
3551 (unless hs-allow-nesting
3552 (hs-discard-overlays beg end))
3553 ;; recursively treat current block leafs
3554 (let ((leaf t))
3555 (while (progn
3556 (forward-comment (buffer-size))
3557 (and (< (point) end)
3558 (re-search-forward hs-block-start-regexp end t)))
3559 (setq pos (match-beginning hs-block-start-mdata-select))
3560 (if (elpy-folding--hide-leafs pos end)
3561 (save-excursion
3562 (goto-char pos)
3563 (hs-hide-block-at-point t)))
3564 (setq leaf nil))
3565 (goto-char end)
3566 (run-hooks 'hs-hide-hook)
3567 leaf))))
3568
3569(defun elpy-folding-hide-leafs ()
3570 "Hide all blocks that do not contain other blocks."
3571 (interactive)
3572 (if (not hs-minor-mode)
3573 (message "Please enable the 'Folding module' to use this functionality.")
3574 (hs-life-goes-on
3575 (let ((beg (save-excursion
3576 (goto-char (if (use-region-p) (region-beginning) (point-min)))
3577 (line-beginning-position)))
3578 (end (save-excursion
3579 (goto-char (if (use-region-p) (region-end) (point-max)))
3580 (1+ (line-end-position)))))
3581 (elpy-folding--hide-leafs beg end)))))
3582
3583;; DWIM functions
3584(defun elpy-folding--hide-region (beg end)
3585 "Hide the region betwwen BEG and END."
3586 (hs-life-goes-on
3587 (save-excursion
3588 (let ((beg-eol (progn (goto-char beg) (line-end-position)))
3589 (end-eol (progn (goto-char end) (line-end-position))))
3590 (hs-discard-overlays beg-eol end-eol)
3591 (hs-make-overlay beg-eol end-eol 'code (- beg beg-eol) (- end end-eol))
3592 (run-hooks 'hs-hide-hook)
3593 (deactivate-mark)))))
3594
3595(defun elpy-folding-toggle-at-point ()
3596 "Fold/Unfold the block, comment or docstring at point.
3597
3598If a region is selected, fold that region."
3599 (interactive)
3600 (if (not hs-minor-mode)
3601 (message "Please enable the 'Folding module' to use this functionality.")
3602 (hs-life-goes-on
3603 ;; Use selected region
3604 (if (use-region-p)
3605 (elpy-folding--hide-region (region-beginning) (region-end))
3606 ;; Adapt starting regexp if on a docstring
3607 (let ((hs-block-start-regexp
3608 (if (python-info-docstring-p)
3609 elpy-docstring-block-start-regexp
3610 hs-block-start-regexp)))
3611 ;; Hide or fold
3612 (cond
3613 ((hs-already-hidden-p)
3614 (hs-show-block))
3615 ((python-info-docstring-p)
3616 (elpy-folding--hide-docstring-at-point))
3617 (t
3618 (hs-hide-block))))))))
3619
3620;;;;;;;;;;;;;;;;;;;
3621;;; Module: Flymake
3622
3623(defun elpy-module-flymake (command &rest _args)
3624 "Enable Flymake support for Python."
3625 (pcase command
3626 (`global-init
3627 (require 'flymake)
3628 ;; flymake modeline is quite useful for emacs > 26.1
3629 (when (version< emacs-version "26.1")
3630 (elpy-modules-remove-modeline-lighter 'flymake-mode))
3631 ;; Add our initializer function.
3632 (unless (version<= "26.1" emacs-version)
3633 (add-to-list 'flymake-allowed-file-name-masks
3634 '("\\.py\\'" elpy-flymake-python-init))))
3635
3636 (`buffer-init
3637 ;; Avoid fringes clash between flymake and folding indicators
3638 (if (and (member 'elpy-module-folding elpy-modules)
3639 elpy-folding-fringe-indicators)
3640 (setq-local flymake-fringe-indicator-position 'right-fringe)
3641 (setq-local flymake-fringe-indicator-position 'left-fringe))
3642 ;; For emacs > 26.1, python.el natively supports flymake,
3643 ;; so we just tell python.el to use the wanted syntax checker
3644 (when (version<= "26.1" emacs-version)
3645 (setq-local python-flymake-command
3646 (let ((command (split-string elpy-syntax-check-command)))
3647 (if (string= (file-name-nondirectory (car command))
3648 "flake8")
3649 (append command '("-"))
3650 command))))
3651
3652 ;; `flymake-no-changes-timeout': The original value of 0.5 is too
3653 ;; short for Python code, as that will result in the current line
3654 ;; to be highlighted most of the time, and that's annoying. This
3655 ;; value might be on the long side, but at least it does not, in
3656 ;; general, interfere with normal interaction.
3657 (set (make-local-variable 'flymake-no-changes-timeout)
3658 60)
3659
3660 ;; `flymake-start-syntax-check-on-newline': This should be nil for
3661 ;; Python, as;; most lines with a colon at the end will mean the
3662 ;; next line is always highlighted as error, which is not helpful
3663 ;; and mostly annoying.
3664 (set (make-local-variable 'flymake-start-syntax-check-on-newline)
3665 nil)
3666
3667 ;; Enable warning faces for flake8 output.
3668 ;; Useless for emacs >= 26.1, as warning are handled fine
3669 ;; COMPAT: Obsolete variable as of 24.4
3670 (cond
3671 ((version<= "26.1" emacs-version)
3672 (setq-default python-flymake-msg-alist
3673 '(("^W[0-9]+" . :warning)
3674 ("^E[0-9]+" . :error))))
3675 ((boundp 'flymake-warning-predicate)
3676 (set (make-local-variable 'flymake-warning-predicate) "^W[0-9]"))
3677 (t
3678 (set (make-local-variable 'flymake-warning-re) "^W[0-9]")))
3679
3680 ;; for emacs >= 26.1, elpy relies on `python-flymake-command`, and
3681 ;; doesn't need `python-check-command` anymore.
3682 (when (and (buffer-file-name)
3683 (or (version<= "26.1" emacs-version)
3684 (executable-find python-check-command)))
3685 (flymake-mode 1)))
3686 (`buffer-stop
3687 (flymake-mode -1)
3688 (kill-local-variable 'flymake-no-changes-timeout)
3689 (kill-local-variable 'flymake-start-syntax-check-on-newline)
3690 ;; Disable warning faces for flake8 output.
3691 ;; Useless for emacs >= 26.1, as warning are handled fine
3692 ;; COMPAT: Obsolete variable as of 24.4
3693 (cond
3694 ((version<= "26.1" emacs-version) t)
3695 ((boundp 'flymake-warning-predicate)
3696 (kill-local-variable 'flymake-warning-predicate))
3697 (t
3698 (kill-local-variable 'flymake-warning-re))))))
3699
3700
3701(defun elpy-flymake-python-init ()
3702 ;; Make sure it's not a remote buffer as flymake would not work
3703 (unless (file-remote-p buffer-file-name)
3704 (let* ((temp-file (flymake-init-create-temp-buffer-copy
3705 'flymake-create-temp-inplace)))
3706 (list python-check-command
3707 (list temp-file)
3708 ;; Run flake8 from / to avoid import problems (#169)
3709 "/"))))
3710
3711(defun elpy-flymake-next-error ()
3712 "Move forward to the next Flymake error and show a description."
3713 (interactive)
3714 (flymake-goto-next-error)
3715 (elpy-flymake-show-error))
3716
3717(defun elpy-flymake-previous-error ()
3718 "Move backward to the previous Flymake error and show a description."
3719 (interactive)
3720 (flymake-goto-prev-error)
3721 (elpy-flymake-show-error))
3722
3723(defun elpy-flymake-show-error ()
3724 "Show the flymake error message at point."
3725 (interactive)
3726 (let ((error-message (elpy-flymake-error-at-point)))
3727 (when error-message
3728 (message "%s" error-message))))
3729
3730(defun elpy-flymake-error-at-point ()
3731 "Return the flymake error at point, or nil if there is none."
3732 (cond ((boundp 'flymake-err-info) ; emacs < 26
3733 (let* ((lineno (line-number-at-pos))
3734 (err-info (car (flymake-find-err-info flymake-err-info
3735 lineno))))
3736 (when err-info
3737 (mapconcat #'flymake-ler-text
3738 err-info
3739 ", "))))
3740 ((and (fboundp 'flymake-diagnostic-text)
3741 (fboundp 'flymake-diagnostics)) ; emacs >= 26
3742 (let ((diag (flymake-diagnostics (point))))
3743 (when diag
3744 (mapconcat #'flymake-diagnostic-text diag ", "))))))
3745
3746;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3747;;; Module: Highlight Indentation
3748
3749(defun elpy-module-highlight-indentation (command &rest _args)
3750 "Module to highlight indentation in Python files."
3751 (pcase command
3752 (`global-init
3753 (require 'highlight-indentation))
3754 (`buffer-init
3755 (highlight-indentation-mode 1))
3756 (`buffer-stop
3757 (highlight-indentation-mode -1))))
3758
3759;;;;;;;;;;;;;;;;;;
3760;;; Module: pyvenv
3761
3762(defun elpy-module-pyvenv (command &rest _args)
3763 "Module to display the current virtualenv in the mode line."
3764 (pcase command
3765 (`global-init
3766 (pyvenv-mode 1))
3767 (`global-stop
3768 (pyvenv-mode -1))))
3769
3770;;;;;;;;;;;;;;;;;;;;;
3771;;; Module: Yasnippet
3772
3773(defun elpy-module-yasnippet (command &rest _args)
3774 "Module to enable YASnippet snippets."
3775 (pcase command
3776 (`global-init
3777 (require 'yasnippet)
3778 (elpy-modules-remove-modeline-lighter 'yas-minor-mode)
3779
3780 ;; We provide some YASnippet snippets. Add them.
3781
3782 ;; yas-snippet-dirs can be a string for a single directory. Make
3783 ;; sure it's a list in that case so we can add our own entry.
3784 (unless (listp yas-snippet-dirs)
3785 (setq yas-snippet-dirs (list yas-snippet-dirs)))
3786 (add-to-list 'yas-snippet-dirs
3787 (concat (file-name-directory (locate-library "elpy"))
3788 "snippets/")
3789 t)
3790
3791 ;; Now load yasnippets.
3792 (yas-reload-all))
3793 (`global-stop
3794 (setq yas-snippet-dirs
3795 (delete (concat (file-name-directory (locate-library "elpy"))
3796 "snippets/")
3797 yas-snippet-dirs)))
3798 (`buffer-init
3799 (yas-minor-mode 1))
3800 (`buffer-stop
3801 (yas-minor-mode -1))))
3802
3803;;;;;;;;;;;;;;;;;;;;;;;;;;;
3804;;; Module: Elpy-Django
3805
3806(defun elpy-module-django (command &rest _args)
3807 "Module to provide Django support."
3808 (pcase command
3809 (`global-init
3810 (add-to-list 'elpy-project-root-finder-functions
3811 'elpy-project-find-django-root t))
3812 (`global-stop
3813 (setq elpy-project-root-finder-functions
3814 (remove 'elpy-project-find-django-root
3815 elpy-project-root-finder-functions)))
3816 (`buffer-init
3817 (elpy-django-setup))
3818 (`buffer-stop
3819 (elpy-django -1))))
3820
3821;;;;;;;;;;;;;;;;;;;;;;;;;;;
3822;;; Module: Autodoc
3823
3824(defun elpy-module-autodoc (command &rest _args)
3825 "Module to automatically update documentation."
3826 (pcase command
3827 (`buffer-init
3828 (add-hook 'pre-command-hook 'elpy-autodoc--pre-command nil t)
3829 (add-hook 'post-command-hook 'elpy-autodoc--post-command nil t)
3830 (make-local-variable 'company-frontends)
3831 (add-to-list 'company-frontends 'elpy-autodoc--frontend :append))
3832 (`buffer-stop
3833 (remove-hook 'pre-command-hook 'elpy-autodoc--pre-command t)
3834 (remove-hook 'post-command-hook 'elpy-autodoc--post-command t)
3835 (setq company-frontends (remove 'elpy-autodoc--frontend company-frontends)))))
3836
3837
3838;; Auto refresh documentation on cursor motion
3839(defvar elpy-autodoc--timer nil
3840 "Timer to refresh documentation.")
3841
3842(defcustom elpy-autodoc-delay .5
3843 "The idle delay in seconds until documentation is refreshed automatically."
3844 :type '(choice (const :tag "immediate (0)" 0)
3845 (number :tag "seconds"))
3846 :group 'elpy
3847 :group 'elpy-autodoc)
3848
3849(defun elpy-autodoc--pre-command ()
3850 "Cancel autodoc timer on user action."
3851 (when elpy-autodoc--timer
3852 (cancel-timer elpy-autodoc--timer)
3853 (setq elpy-autodoc--timer nil)))
3854
3855(defun elpy-autodoc--post-command ()
3856 "Set up autodoc timer after user action."
3857 (when elpy-autodoc-delay
3858 (setq elpy-autodoc--timer
3859 (run-with-timer elpy-autodoc-delay nil
3860 'elpy-autodoc--refresh-doc))))
3861
3862(defun elpy-autodoc--refresh-doc ()
3863 "Refresh the doc asynchronously with the symbol at point."
3864 (when (get-buffer-window "*Python Doc*")
3865 (elpy-rpc-get-docstring 'elpy-autodoc--show-doc
3866 (lambda (_reason) nil))))
3867
3868(defun elpy-autodoc--show-doc (doc)
3869 "Display DOC (if any) but only if the doc buffer is currently visible."
3870 (when (and doc (get-buffer-window "*Python Doc*"))
3871 (elpy-doc--show doc)))
3872
3873;; Auto refresh documentation in company candidate selection
3874(defun elpy-autodoc--frontend (command)
3875 "Elpy autodoc front-end for refreshing documentation."
3876 (pcase command
3877 (`post-command
3878 (when elpy-autodoc-delay
3879 (when elpy-autodoc--timer
3880 (cancel-timer elpy-autodoc--timer))
3881 (setq elpy-autodoc--timer
3882 (run-with-timer elpy-autodoc-delay
3883 nil
3884 'elpy-autodoc--refresh-doc-from-company))))
3885 (`hide
3886 (when elpy-autodoc--timer
3887 (cancel-timer elpy-autodoc--timer)))))
3888
3889(defun elpy-autodoc--refresh-doc-from-company ()
3890 "Refresh the doc asynchronously using the current company candidate."
3891 (let* ((symbol (nth company-selection company-candidates))
3892 (doc (elpy-rpc-get-completion-docstring symbol)))
3893 (elpy-autodoc--show-doc doc)))
3894
3895;;;;;;;;;;;;;;;;;;;;;;;;;;;
3896;;; Backwards compatibility
3897
3898;; TODO Some/most of this compatibility code can now go, as minimum required
3899;; Emacs version is 24.4.
3900
3901;; Functions for Emacs 24 before 24.3
3902(unless (fboundp 'python-info-current-defun)
3903 (defalias 'python-info-current-defun 'python-current-defun))
3904
3905(unless (fboundp 'python-nav-forward-statement)
3906 (defun python-nav-forward-statement (&rest ignored)
3907 "Function added in Emacs 24.3"
3908 (error "Enhanced Python navigation only available in Emacs 24.3+")))
3909
3910(unless (fboundp 'python-nav-backward-up-list)
3911 (defun python-nav-backward-up-list ()
3912 "Compatibility function for older Emacsen"
3913 (ignore-errors
3914 (backward-up-list))))
3915
3916(unless (fboundp 'python-shell-calculate-exec-path)
3917 (defun python-shell-calculate-exec-path ()
3918 "Compatibility function for older Emacsen."
3919 exec-path))
3920
3921(unless (fboundp 'python-shell-calculate-process-environment)
3922 (defun python-shell-calculate-process-environment ()
3923 "Compatibility function for older Emacsen."
3924 process-environment))
3925
3926(unless (fboundp 'python-shell-get-process-name)
3927 (defun python-shell-get-process-name (_dedicated)
3928 "Compatibility function for older Emacsen."
3929 "Python"))
3930
3931(unless (fboundp 'python-shell-parse-command)
3932 (defun python-shell-parse-command ()
3933 "Compatibility function for older Emacsen."
3934 python-python-command))
3935
3936(unless (fboundp 'python-shell-send-buffer)
3937 (defun python-shell-send-buffer (&optional _arg)
3938 (python-send-buffer)))
3939
3940(unless (fboundp 'python-shell-send-string)
3941 (defalias 'python-shell-send-string 'python-send-string))
3942
3943(unless (fboundp 'python-indent-shift-right)
3944 (defalias 'python-indent-shift-right 'python-shift-right))
3945
3946(unless (fboundp 'python-indent-shift-left)
3947 (defalias 'python-indent-shift-left 'python-shift-left))
3948
3949;; Emacs 24.2 made `locate-dominating-file' accept a predicate instead
3950;; of a string. Simply overwrite the current one, it's
3951;; backwards-compatible. The code below is taken from Emacs 24.3.
3952(when (or (< emacs-major-version 24)
3953 (and (= emacs-major-version 24)
3954 (<= emacs-minor-version 2)))
3955 (defun locate-dominating-file (file name)
3956 "Look up the directory hierarchy from FILE for a directory containing NAME.
3957Stop at the first parent directory containing a file NAME,
3958and return the directory. Return nil if not found.
3959Instead of a string, NAME can also be a predicate taking one argument
3960\(a directory) and returning a non-nil value if that directory is the one for
3961which we're looking."
3962 ;; We used to use the above locate-dominating-files code, but the
3963 ;; directory-files call is very costly, so we're much better off doing
3964 ;; multiple calls using the code in here.
3965 ;;
3966 ;; Represent /home/luser/foo as ~/foo so that we don't try to look for
3967 ;; `name' in /home or in /.
3968 (setq file (abbreviate-file-name file))
3969 (let ((root nil)
3970 ;; `user' is not initialized outside the loop because
3971 ;; `file' may not exist, so we may have to walk up part of the
3972 ;; hierarchy before we find the "initial UID". Note: currently unused
3973 ;; (user nil)
3974 try)
3975 (while (not (or root
3976 (null file)
3977 ;; FIXME: Disabled this heuristic because it is sometimes
3978 ;; inappropriate.
3979 ;; As a heuristic, we stop looking up the hierarchy of
3980 ;; directories as soon as we find a directory belonging
3981 ;; to another user. This should save us from looking in
3982 ;; things like /net and /afs. This assumes that all the
3983 ;; files inside a project belong to the same user.
3984 ;; (let ((prev-user user))
3985 ;; (setq user (nth 2 (file-attributes file)))
3986 ;; (and prev-user (not (equal user prev-user))))
3987 (string-match locate-dominating-stop-dir-regexp file)))
3988 (setq try (if (stringp name)
3989 (file-exists-p (expand-file-name name file))
3990 (funcall name file)))
3991 (cond (try (setq root file))
3992 ((equal file (setq file (file-name-directory
3993 (directory-file-name file))))
3994 (setq file nil))))
3995 (if root (file-name-as-directory root))))
3996 )
3997
3998;; highlight-indentation 0.5 does not use modes yet
3999(unless (fboundp 'highlight-indentation-mode)
4000 (defun highlight-indentation-mode (on-or-off)
4001 (cond
4002 ((and (> on-or-off 0)
4003 (not highlight-indent-active))
4004 (highlight-indentation))
4005 ((and (<= on-or-off 0)
4006 highlight-indent-active)
4007 (highlight-indentation)))))
4008
4009;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25753#44
4010(when (version< emacs-version "25.2")
4011 (defun python-shell-completion-native-try ()
4012 "Return non-nil if can trigger native completion."
4013 (let ((python-shell-completion-native-enable t)
4014 (python-shell-completion-native-output-timeout
4015 python-shell-completion-native-try-output-timeout))
4016 (python-shell-completion-native-get-completions
4017 (get-buffer-process (current-buffer))
4018 nil "_"))))
4019
4020;; python.el in Emacs 24 does not have functions to determine buffer encoding.
4021;; In these versions, we fix the encoding to utf-8 (safe choice when no encoding
4022;; is defined since Python 2 uses ASCII and Python 3 UTF-8).
4023(unless (fboundp 'python-info-encoding)
4024 (defun python-info-encoding ()
4025 'utf-8))
4026
4027;; first-prompt-hook has been added in emacs 25.
4028;; for earlier versions, make sure Elpy's setup code is
4029;; still send to the python shell.
4030(unless (boundp 'python-shell-first-prompt-hook)
4031 (add-hook 'inferior-python-mode-hook
4032 (lambda ()
4033 (when (elpy-project-root)
4034 (let ((process (get-buffer-process (current-buffer))))
4035 (python-shell-send-string
4036 (format "import sys;sys.path.append('%s');del sys"
4037 (elpy-project-root))
4038 process))))))
4039
4040
4041
4042;; Added in Emacs 25
4043(unless (fboundp 'python-shell-comint-end-of-output-p)
4044 (defun python-shell-comint-end-of-output-p (output)
4045 "Return non-nil if OUTPUT is ends with input prompt."
4046 (string-match
4047 ;; XXX: It seems on macOS an extra carriage return is attached
4048 ;; at the end of output, this handles that too.
4049 (concat
4050 "\r?\n?"
4051 ;; Remove initial caret from calculated regexp
4052 (replace-regexp-in-string
4053 (rx string-start ?^) ""
4054 python-shell--prompt-calculated-input-regexp)
4055 (rx eos))
4056 output)))
4057
4058(unless (fboundp 'python-info-docstring-p)
4059 (defun python-info-docstring-p (&optional syntax-ppss)
4060 "Return non-nil if point is in a docstring."
4061 (save-excursion
4062 (and (progn (python-nav-beginning-of-statement)
4063 (looking-at "\\(\"\\|'\\)"))
4064 (progn (forward-line -1)
4065 (beginning-of-line)
4066 (python-info-looking-at-beginning-of-defun))))))
4067
4068(unless (fboundp 'alist-get)
4069 (defun alist-get (key alist &rest rest)
4070 (cdr (assoc key alist))))
4071
4072(provide 'elpy)
4073;;; elpy.el ends here