]> crepu.dev Git - config.git/blame - djavu-asus/emacs/elpa/elpy-20230803.1455/elpy-rpc.el
Reorganización de directorios
[config.git] / djavu-asus / emacs / elpa / elpy-20230803.1455 / elpy-rpc.el
CommitLineData
53e6db90
DC
1;;; elpy-rpc.el --- RPC protocol for elpy -*- lexical-binding: t -*-
2;;
3;; Copyright (C) 2012-2019 Jorgen Schaefer
4;;
5;; Author: Jorgen Schaefer <contact@jorgenschaefer.de>, Rainer Gemulla <rgemulla@gmx.de>, Gaby Launay <gaby.launay@protonmail.com>
6;; URL: https://github.com/jorgenschaefer/elpy
7;;
8;; This program is free software; you can redistribute it and/or
9;; modify it under the terms of the GNU General Public License
10;; as published by the Free Software Foundation; either version 3
11;; of the License, or (at your option) any later version.
12;;
13;; This program is distributed in the hope that it will be useful,
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;; GNU General Public License for more details.
17;;
18;; You should have received a copy of the GNU General Public License
19;; along with this program. If not, see <http://www.gnu.org/licenses/>.
20;;
21;;; Commentary:
22;;
23;; elpy-rpc is a simple JSON-based RPC protocol. It's mostly JSON-RPC
24;; 1.0, except we do not implement the full protocol as we do not need
25;; all the features. Emacs starts a Python subprocess which runs a
26;; special module. The module reads JSON-RPC requests and responds
27;; with JSON-RPC responses.
28;;
29;;; Code:
30
31(require 'json)
32(require 'pyvenv)
33
34(defcustom elpy-rpc-maximum-buffer-age (* 5 60)
35 "Seconds after which Elpy automatically closes an unused RPC buffer.
36
37Elpy creates RPC buffers over time, depending on python interpreters
38and the project root. When there are many projects being worked on,
39these can accumulate. Setting this variable to an integer will close
40buffers and processes when they have not been used for this amount of
41seconds.
42
43Setting this variable to nil will disable the behavior."
44 :type '(choice (const :tag "Never" nil)
45 integer)
46 :group 'elpy)
47
48(defcustom elpy-rpc-large-buffer-size 4096
49 "Size for a source buffer up to which it will be sent directly.
50
51The Elpy RPC protocol uses JSON as the serialization format.
52Large buffers take a long time to encode, so Elpy can transmit
53them via temporary files. If a buffer is larger than this value,
54it is sent via a temporary file."
55 :type 'integer
56 :safe #'integerp
57 :group 'elpy)
58
59(defcustom elpy-rpc-ignored-buffer-size 102400
60 "Size for a source buffer over which Elpy completion will not work.
61
62To provide completion, Elpy's backends have to parse the whole
63file every time. For very large files, this is slow, and can make
64Emacs laggy. Elpy will simply not work on buffers larger than
65this to prevent this from happening."
66 :type 'integer
67 :safe #'integerp
68 :group 'elpy)
69
70(defcustom elpy-rpc-python-command (if (equal system-type 'windows-nt)
71 (or (executable-find "py")
72 (executable-find "pythonw")
73 "python")
74 (if (executable-find "python")
75 "python"
76 ;; Fallback on systems where python is not
77 ;; symlinked to python3.
78 "python3"))
79 "The Python interpreter for the RPC backend.
80
81This should NOT be an interactive shell like ipython or jupyter.
82
83As the RPC should be independent of any virtual environment, Elpy
84will try to use the system interpreter if it exists. If you wish
85to use a specific python interpreter (from a virtual environment
86for example), set this to the full interpreter path."
87 :type '(choice (const :tag "python" "python")
88 (const :tag "python2" "python2")
89 (const :tag "python3" "python3")
90 (const :tag "pythonw (Python on Windows)" "pythonw")
91 (const :tag "py (other Python on Windows)" "py")
92 (string :tag "Other"))
93 :safe (lambda (val)
94 (member val '("python" "python2" "python3" "pythonw")))
95 ;; Make sure there is no obsolete rpc running
96 :set (lambda (var val) ;
97 (set-default var val)
98 (when (and (fboundp 'elpy-rpc-restart)
99 (not (autoloadp #'elpy-rpc-restart)))
100 (elpy-rpc-restart)))
101 :group 'elpy)
102
103(defcustom elpy-rpc-pythonpath (file-name-directory load-file-name)
104 "A directory to add to the PYTHONPATH for the RPC process.
105
106This should be a directory where the elpy module can be found. If
107this is nil, it's assumed elpy can be found in the standard path.
108Usually, there is no need to change this."
109 :type 'directory
110 :safe #'file-directory-p
111 :group 'elpy)
112
113(defcustom elpy-rpc-timeout 1
114 "Number of seconds to wait for a response when blocking.
115
116When Elpy blocks Emacs to wait for a response from the RPC
117process, it will assume it won't come or wait too long after this
118many seconds. On a slow computer, or if you have a large project,
119you might want to increase this.
120
121A setting of nil means to block indefinitely."
122 :type '(choice (const :tag "Block indefinitely" nil)
123 integer)
124 :safe (lambda (val)
125 (or (integerp val)
126 (null val)))
127 :group 'elpy)
128
129(defcustom elpy-rpc-error-timeout 30
130 "Minimum number of seconds between error popups.
131
132When Elpy encounters an error in the backend, it will display a
133lengthy description of the problem for a bug report. This hangs
134Emacs for a moment, and can be rather annoying if it happens
135repeatedly while editing a source file.
136
137If this variabl is non-nil, Elpy will not display the error
138message again within this amount of seconds."
139 :type 'integer
140 :group 'elpy)
141
142(defvar elpy-rpc--call-id 0
143 "Call id of the last call to `elpy-rpc`.
144
145Used to associate responses to callbacks.")
146(make-variable-buffer-local 'elpy-rpc--call-id)
147
148(defvar elpy-rpc--buffer-p nil
149 "Non-nil if the current buffer is an elpy-rpc buffer.")
150(make-variable-buffer-local 'elpy-rpc--buffer-p)
151
152(defvar elpy-rpc--buffer nil
153 "The elpy-rpc buffer associated with this buffer.")
154(make-variable-buffer-local 'elpy-rpc--buffer)
155
156(defvar elpy-rpc--backend-library-root nil
157 "The project root used by this backend.")
158(make-variable-buffer-local 'elpy-rpc--backend-library-root)
159
160(defvar elpy-rpc--backend-python-command nil
161 "The Python interpreter used by this backend.")
162(make-variable-buffer-local 'elpy-rpc--backend-python-command)
163
164(defvar elpy-rpc--backend-callbacks nil
165 "The callbacks registered for calls to the current backend.
166
167This maps call IDs to functions.")
168(make-variable-buffer-local 'elpy-rpc--backend-callbacks)
169
170(defvar elpy-rpc--last-call nil
171 "The time of the last RPC call issued for this backend.")
172(make-variable-buffer-local 'elpy-rpc--last-call)
173
174(defvar elpy-rpc--last-error-popup nil
175 "The last time an error popup happened.")
176
177(defvar elpy-rpc--jedi-available nil
178 "Whether jedi is available or not.")
179
180;;;;;;;;;;;;;;;;;;;
181;;; RPC virualenv
182
183(defcustom elpy-rpc-virtualenv-path 'default
184 "Path to the virtualenv used by the RPC.
185
186Can be `default' (create a dedicated virtualenv in
187\".emacs.d/elpy\"), `system' (use the system environment),
188`current' (use the currently active environment), a virtualenv
189path or a function returning a virtualenv path.
190
191If the default virtual environment does not exist, it will be
192created using `elpy-rpc-python-command' and populated with the
193needed packages from `elpy-rpc--get-package-list'."
194
195 :type '(choice (const :tag "Dedicated environment" default)
196 (const :tag "Global environment" system)
197 (const :tag "Current environment" current)
198 (string :tag "Virtualenv path")
199 (function :tag "Function returning the virtualenv path"))
200 :group 'elpy)
201
202(defun elpy-rpc-default-virtualenv-path ()
203 "Return the default virtualenv path."
204 (expand-file-name (locate-user-emacs-file "elpy/rpc-venv")))
205
206(defun elpy-rpc-get-virtualenv-path ()
207 "Return the RPC virutalenv path to use."
208 (cond
209 ((eq elpy-rpc-virtualenv-path 'default)
210 (elpy-rpc-default-virtualenv-path))
211 ((or (eq elpy-rpc-virtualenv-path 'system)
212 (eq elpy-rpc-virtualenv-path 'global)) ;; for backward compatibility
213 (let ((exec-path (reverse exec-path)))
214 (directory-file-name
215 (file-name-directory
216 (directory-file-name
217 (file-name-directory
218 (executable-find elpy-rpc-python-command)))))))
219 ((eq elpy-rpc-virtualenv-path 'current)
220 (directory-file-name
221 (file-name-directory
222 (directory-file-name
223 (file-name-directory
224 (executable-find elpy-rpc-python-command))))))
225 ((stringp elpy-rpc-virtualenv-path)
226 (expand-file-name elpy-rpc-virtualenv-path))
227 ((functionp elpy-rpc-virtualenv-path)
228 (expand-file-name (funcall elpy-rpc-virtualenv-path)))
229 (t
230 (error "Invalid value for `elpy-rpc-virtualenv-path', please set it to a proper value using customize"))))
231
232(defun elpy-rpc--get-package-list ()
233 "Return the list of packages to be installed in the RPC virtualenv."
234 (let ((rpc-python-version (elpy-rpc--get-python-version)))
235 (if (version< rpc-python-version "3.6.0")
236 '("jedi" "flake8" "autopep8" "yapf")
237 '("jedi" "flake8" "autopep8" "yapf" "black"))))
238
239(defun elpy-rpc--get-python-version ()
240 "Return the RPC python version."
241 (with-temp-buffer
242 (call-process elpy-rpc-python-command nil t nil "--version")
243 (goto-char (point-min))
244 (re-search-forward "Python \\([0-9.]+\\)")
245 (match-string 1)))
246
247(defmacro with-elpy-rpc-virtualenv-activated (&rest body)
248 "Run BODY with Elpy's RPC virtualenv activated.
249
250During the execution of BODY the following variables are available:
251- `current-environment': current environment path.
252- `current-environment-binaries': current environment python binaries path.
253- `current-environment-is-deactivated': non-nil if the current
254 environment has been deactivated (it is not if the RPC environment and
255 the current environment are the same)."
256 `(if (not (executable-find elpy-rpc-python-command))
257 (error "Cannot find executable '%s', please set 'elpy-rpc-python-command' to an existing executable." elpy-rpc-python-command)
258 (let* ((pyvenv-post-activate-hooks (remq 'elpy-rpc--disconnect
259 pyvenv-post-activate-hooks))
260 (pyvenv-post-deactivate-hooks (remq 'elpy-rpc--disconnect
261 pyvenv-post-deactivate-hooks))
262 (venv-was-activated pyvenv-virtual-env)
263 (current-environment-binaries (executable-find
264 elpy-rpc-python-command))
265 (current-environment (directory-file-name (file-name-directory (directory-file-name (file-name-directory current-environment-binaries)))))
266 ;; No need to change of venv if they are the same
267 (same-venv (or (string= current-environment
268 (elpy-rpc-get-virtualenv-path))
269 (file-equal-p current-environment
270 (elpy-rpc-get-virtualenv-path))))
271 current-environment-is-deactivated)
272 ;; If different than the current one, try to activate the RPC virtualenv
273 (unless same-venv
274 (condition-case err
275 (pyvenv-activate (elpy-rpc-get-or-create-virtualenv))
276 ((error quit) (if venv-was-activated
277 (pyvenv-activate venv-was-activated)
278 (pyvenv-deactivate))))
279 (setq current-environment-is-deactivated t))
280 (let (venv-err result)
281 ;; Run BODY and catch errors and quit to avoid keeping the RPC
282 ;; virtualenv activated
283 (condition-case err
284 (setq result (progn ,@body))
285 (error (setq venv-err
286 (if (stringp err)
287 err
288 (car (cdr err)))))
289 (quit nil))
290 ;; Reactivate the previous environment if necessary
291 (unless same-venv
292 (if venv-was-activated
293 (pyvenv-activate venv-was-activated)
294 (pyvenv-deactivate)))
295 ;; Raise errors that could have happened in BODY
296 (when venv-err
297 (error venv-err))
298 result))))
299
300(defun elpy-rpc-get-or-create-virtualenv ()
301 "Return Elpy's RPC virtualenv.
302
303Create the virtualenv if it does not exist yet.
304Update the virtualenv if the variable `elpy-rpc-python-command' has
305changed since the virtualenv creation.
306
307An additional file `elpy-rpc-python-path-command' is added in the
308virtualenv directory in order to keep track of the python
309binaries used to create the virtualenv."
310 (let* ((rpc-venv-path (elpy-rpc-get-virtualenv-path))
311 (is-venv-exist (file-exists-p rpc-venv-path))
312 (is-default-rpc-venv
313 (and rpc-venv-path
314 (string= rpc-venv-path
315 (elpy-rpc-default-virtualenv-path))))
316 (venv-python-path-command-file
317 (concat (file-name-as-directory rpc-venv-path)
318 "elpy-rpc-python-path-command"))
319 (venv-python-path-command
320 (when (file-exists-p venv-python-path-command-file)
321 (with-temp-buffer
322 (insert-file-contents venv-python-path-command-file)
323 (buffer-string))))
324 (venv-need-update
325 (and is-venv-exist
326 is-default-rpc-venv
327 (not (string= venv-python-path-command
328 elpy-rpc-python-command))))
329 (venv-creation-allowed
330 (and (or (not is-venv-exist) venv-need-update)
331 (or is-default-rpc-venv
332 (y-or-n-p
333 (format "`elpy-rpc-virtualenv-path' was set to '%s', but this virtualenv does not exist, create it ? " rpc-venv-path))))))
334 ;; Delete the rpc virtualenv if obsolete
335 (when venv-need-update
336 (delete-directory rpc-venv-path t)
337 (setq is-venv-exist nil))
338 ;; Create a new rpc venv if necessary
339 (unless is-venv-exist
340 (if (not venv-creation-allowed)
341 (message "Please indicate the virtualenv you wish to use with `elpy-rpc-virtualenv-path'.")
342 (let ((deact-venv pyvenv-virtual-env))
343 (message "Elpy is %s the RPC virtualenv ('%s')"
344 (if venv-need-update "updating" "creating")
345 rpc-venv-path)
346 (elpy-rpc--create-virtualenv rpc-venv-path)
347 ;; Make sure the rpc venv is deacivated on quit
348 (condition-case nil
349 (progn
350 (pyvenv-activate rpc-venv-path)
351 ;; Add file to keep track of the `elpy-rpc-python-command` used
352 (with-temp-file venv-python-path-command-file
353 (insert elpy-rpc-python-command))
354 ;; safeguard to be sure we don't install stuff in the wrong venv
355 (when (file-equal-p pyvenv-virtual-env rpc-venv-path)
356 (elpy-rpc--install-dependencies))
357 (elpy-rpc-restart))
358 (quit nil))
359 ;; Deactivate the rpc venv
360 (if deact-venv
361 (pyvenv-activate (directory-file-name deact-venv))
362 (pyvenv-deactivate))
363 (message "Done"))))
364 rpc-venv-path))
365
366(defun elpy-rpc--create-virtualenv (rpc-venv-path)
367 "Create a virtualenv for the RPC in RPC-VENV-PATH."
368 ;; venv cannot create a proper virtualenv from inside another virtualenv
369 (let* ((elpy-rpc-virtualenv-path 'system)
370 success
371 (elpy-venv-buffname-visible "*elpy-virtualenv*")
372 (elpy-venv-buffname (concat " " elpy-venv-buffname-visible)))
373 (when (get-buffer elpy-venv-buffname)
374 (kill-buffer elpy-venv-buffname))
375 (when (get-buffer elpy-venv-buffname-visible)
376 (kill-buffer elpy-venv-buffname-visible))
377 (with-elpy-rpc-virtualenv-activated
378 (cond
379 ((and (= 0 (call-process elpy-rpc-python-command nil nil nil
380 "-m" "venv" "-h"))
381 ;; see https://github.com/jorgenschaefer/elpy/issues/1756
382 (= 0 (call-process elpy-rpc-python-command nil nil nil
383 "-m" "ensurepip" "-h")))
384 (with-current-buffer (get-buffer-create elpy-venv-buffname)
385 (insert (concat "Running '" elpy-rpc-python-command " -m venv "
386 rpc-venv-path "':\n\n"))
387 (setq success (call-process elpy-rpc-python-command nil t t
388 "-m" "venv" rpc-venv-path))))
389 ((executable-find "virtualenv")
390 (with-current-buffer (get-buffer-create elpy-venv-buffname)
391 (insert (concat "Running 'virtualenv -p "
392 elpy-rpc-python-command " " rpc-venv-path
393 "':\n\n"))
394 (setq success (call-process "virtualenv" nil t t "-p"
395 elpy-rpc-python-command rpc-venv-path))))
396 (t
397 (error "Elpy needs the 'virtualenv' or 'venv' python packages to create its virtualenv. Please install one of them or disable the dedicated virtualenv with `(setq elpy-rpc-virtualenv-path 'current)`"))))
398 ;; warn us if something wrong happened
399 (unless (= 0 success)
400 (with-current-buffer elpy-venv-buffname
401 (rename-buffer elpy-venv-buffname-visible)
402 (goto-char (point-max))
403 (insert
404 (concat
405 "\n\n"
406 "Elpy failed to install its dedicated virtualenv due to the above\n"
407 "error. If the error details does not help you fixing it, You can\n"
408 "report this problem on Elpy repository on github.\n"
409 "In the meantime, setting the `elpy-rpc-virtualenv-path' option to\n"
410 "either `system' or `current' should temporarily fix the issue.")))
411 (error (concat "Elpy failed to create its dedicated virtualenv. "
412 "Please check the `" elpy-venv-buffname-visible
413 "' buffer.")))))
414
415(defun elpy-rpc--install-dependencies ()
416 "Install the RPC dependencies in the current virtualenv."
417 (if (y-or-n-p "Automatically install the RPC dependencies from PyPI (needed for completion, autoformatting and documentation) ? ")
418 (with-temp-buffer
419 (message "Elpy is installing the RPC dependencies...")
420 (when (/= (apply 'call-process elpy-rpc-python-command
421 nil t nil
422 "-m" "pip" "install" "--upgrade"
423 (elpy-rpc--get-package-list))
424 0)
425 (message "Elpy failed to install some of the RPC dependencies, please use `elpy-config' to install them.\n%s" (buffer-string))
426 ))
427 (message "Some of Elpy's functionnalities will not work, please use `elpy-config' to install the needed python dependencies.")))
428
429(defun elpy-rpc-reinstall-virtualenv ()
430 "Re-install the RPC virtualenv."
431 (interactive)
432 (let ((rpc-venv-path (elpy-rpc-get-virtualenv-path)))
433 (when
434 (cond
435 ((or (eq elpy-rpc-virtualenv-path 'system)
436 (eq elpy-rpc-virtualenv-path 'global)) ;; backward compatibility
437 (error "Cannot reinstall the system environment, please reinstall the necessary packages manually"))
438 ((string= (elpy-rpc-default-virtualenv-path) rpc-venv-path)
439 t)
440 (t
441 (y-or-n-p (format "Are you sure you want to reinstall the virtualenv in '%s' (every manual modifications will be lost) ? " rpc-venv-path))))
442 (delete-directory rpc-venv-path t)
443 (elpy-rpc-get-or-create-virtualenv))))
444
445(defun elpy-rpc--pip-missing ()
446 "Return t if pip is not installed in the RPC virtualenv."
447 (let* ((rpc-venv-path (file-name-as-directory
448 (elpy-rpc-get-virtualenv-path)))
449 (base-pip-scripts (concat rpc-venv-path
450 (file-name-as-directory "Scripts")
451 "pip"))
452 (base-pip-bin (concat rpc-venv-path
453 (file-name-as-directory "bin")
454 "pip")))
455 (not (or
456 (file-exists-p base-pip-scripts)
457 (file-exists-p base-pip-bin)
458 (file-exists-p (concat base-pip-scripts ".exe"))
459 (file-exists-p (concat base-pip-bin ".exe"))))))
460
461;;;;;;;;;;;;;;;;;;;
462;;; Promise objects
463
464(defvar elpy-promise-marker (make-symbol "*elpy-promise*")
465 "An uninterned symbol marking an Elpy promise object.")
466
467(defun elpy-promise (success &optional error)
468 "Return a new promise.
469
470A promise is an object with a success and error callback. If the
471promise is resolved using `elpy-promise-resolve', the SUCCESS
472callback is called with the given value. The current buffer is
473restored, too.
474
475If the promise is rejected using `elpy-promise-reject', the ERROR
476callback is called. For this function, the current buffer is not
477necessarily restored, as it is also called when the buffer does
478not exist anymore."
479 (vector elpy-promise-marker ; 0 id
480 success ; 1 success-callback
481 error ; 2 error-callback
482 (current-buffer) ; 3 current-buffer
483 nil ; 4 run
484 ))
485
486(defun elpy-promise-p (obj)
487 "Return non-nil if OBJ is a promise object."
488 (and (vectorp obj)
489 (= (length obj) 5)
490 (eq (aref obj 0) elpy-promise-marker)))
491
492(defsubst elpy-promise-success-callback (promise)
493 "Return the success callback for PROMISE."
494 (aref promise 1))
495
496(defsubst elpy-promise-error-callback (promise)
497 "Return the error callback for PROMISE."
498 (aref promise 2))
499
500(defsubst elpy-promise-buffer (promise)
501 "Return the buffer for PROMISE."
502 (aref promise 3))
503
504(defsubst elpy-promise-resolved-p (promise)
505 "Return non-nil if the PROMISE has been resolved or rejected."
506 (aref promise 4))
507
508(defsubst elpy-promise-set-resolved (promise)
509 "Mark PROMISE as having been resolved."
510 (aset promise 4 t))
511
512(defun elpy-promise-resolve (promise value)
513 "Resolve PROMISE with VALUE."
514 (unless (elpy-promise-resolved-p promise)
515 (unwind-protect
516 (let ((success-callback (elpy-promise-success-callback promise)))
517 (when success-callback
518 (condition-case err
519 (with-current-buffer (elpy-promise-buffer promise)
520 (funcall success-callback value))
521 (error
522 (elpy-promise-reject promise err)))))
523 (elpy-promise-set-resolved promise))))
524
525(defun elpy-promise-reject (promise reason)
526 "Reject PROMISE because of REASON."
527 (unless (elpy-promise-resolved-p promise)
528 (unwind-protect
529 (let ((error-callback (elpy-promise-error-callback promise)))
530 (when error-callback
531 (if (buffer-live-p (elpy-promise-buffer promise))
532 (with-current-buffer (elpy-promise-buffer promise)
533 (funcall error-callback reason))
534 (with-temp-buffer
535 (funcall error-callback reason)))))
536 (elpy-promise-set-resolved promise))))
537
538(defun elpy-promise-wait (promise &optional timeout)
539 "Wait for PROMISE to be resolved, for up to TIMEOUT seconds.
540
541This will accept process output while waiting.
542
543This will wait for the current Elpy RPC process specifically, as
544Emacs currently has a bug where it can wait for the entire time
545of the timeout, even if output arrives.
546
547See http://debbugs.gnu.org/cgi/bugreport.cgi?bug=17647"
548 (let ((end-time (when timeout
549 (time-add (current-time)
550 (seconds-to-time timeout))))
551 (process (get-buffer-process (elpy-rpc--get-rpc-buffer))))
552 (while (and (not (elpy-promise-resolved-p promise))
553 (or (not end-time)
554 (time-less-p (current-time)
555 end-time)))
556 (accept-process-output process timeout))))
557
558;;;;;;;;;;;;;;;;;
559;;; Elpy RPC
560
561(defun elpy-rpc (method params &optional success error)
562 "Call METHOD with PARAMS in the backend.
563
564If SUCCESS and optionally ERROR is given, return immediately and
565call those when a result is available. Otherwise, wait for a
566result and return that."
567 (unless error
568 (setq error #'elpy-rpc--default-error-callback))
569 (if success
570 (elpy-rpc--call method params success error)
571 (elpy-rpc--call-blocking method params)))
572
573(defun elpy-rpc--call-blocking (method-name params)
574 "Call METHOD-NAME with PARAMS in the current RPC backend.
575
576Returns the result, blocking until this arrived."
577 (let* ((result-arrived nil)
578 (error-occured nil)
579 (result-value nil)
580 (error-object nil)
581 (promise (elpy-rpc--call method-name params
582 (lambda (result)
583 (setq result-value result
584 result-arrived t))
585 (lambda (err)
586 (setq error-object err
587 error-occured t)))))
588 (elpy-promise-wait promise elpy-rpc-timeout)
589 (cond
590 (error-occured
591 (elpy-rpc--default-error-callback error-object))
592 (result-arrived
593 result-value)
594 (t
595 (error "Timeout during RPC call %s from backend"
596 method-name)))))
597
598(defun elpy-rpc--call (method-name params success error)
599 "Call METHOD-NAME with PARAMS in the current RPC backend.
600
601When a result is available, SUCCESS will be called with that
602value as its sole argument. If an error occurs, ERROR will be
603called with the error list.
604
605Returns a PROMISE object."
606 (let ((promise (elpy-promise success error)))
607 (with-current-buffer (elpy-rpc--get-rpc-buffer)
608 (setq elpy-rpc--call-id (1+ elpy-rpc--call-id)
609 elpy-rpc--last-call (float-time))
610 (elpy-rpc--register-callback elpy-rpc--call-id promise)
611 (process-send-string
612 (get-buffer-process (current-buffer))
613 (let ((json-encoding-pretty-print nil)) ;; Link to bug https://github.com/jorgenschaefer/elpy/issues/1521
614 (concat (json-encode `((id . ,elpy-rpc--call-id)
615 (method . ,method-name)
616 (params . ,params)))
617 "\n"))))
618 promise))
619
620(defun elpy-rpc--register-callback (call-id promise)
621 "Register for PROMISE to be called when CALL-ID returns.
622
623Must be called in an elpy-rpc buffer."
624 (unless elpy-rpc--buffer-p
625 (error "Must be called in RPC buffer"))
626 (unless elpy-rpc--backend-callbacks
627 (setq elpy-rpc--backend-callbacks (make-hash-table :test #'equal)))
628 (puthash call-id promise elpy-rpc--backend-callbacks))
629
630(defun elpy-rpc--get-rpc-buffer ()
631 "Return the RPC buffer associated with the current buffer,
632creating one if necessary."
633 (unless (elpy-rpc--process-buffer-p elpy-rpc--buffer)
634 (setq elpy-rpc--buffer
635 (or (elpy-rpc--find-buffer (elpy-library-root)
636 elpy-rpc-python-command)
637 (elpy-rpc--open (elpy-library-root)
638 elpy-rpc-python-command))))
639 elpy-rpc--buffer)
640
641(defun elpy-rpc--process-buffer-p (buffer)
642 "Return non-nil when BUFFER is a live elpy-rpc process buffer.
643
644If BUFFER is a buffer for an elpy-rpc process, but the process
645died, this will kill the process and buffer."
646 (cond
647 ((or (not buffer)
648 (not (buffer-live-p buffer)))
649 nil)
650 ((not (buffer-local-value 'elpy-rpc--buffer-p buffer))
651 nil)
652 ((and (get-buffer-process buffer)
653 (process-live-p (get-buffer-process buffer)))
654 t)
655 (t
656 (ignore-errors
657 (kill-process (get-buffer-process buffer)))
658 (ignore-errors
659 (kill-buffer buffer))
660 nil)))
661
662(defun elpy-rpc--find-buffer (library-root python-command)
663 "Return an existing RPC buffer for this project root and command."
664 (catch 'return
665 (let ((full-python-command (executable-find python-command)))
666 (dolist (buf (buffer-list))
667 (when (and (elpy-rpc--process-buffer-p buf)
668 (equal (buffer-local-value 'elpy-rpc--backend-library-root
669 buf)
670 library-root)
671 (equal (buffer-local-value 'elpy-rpc--backend-python-command
672 buf)
673 full-python-command))
674 (throw 'return buf))))
675 nil))
676
677(defun elpy-rpc--open (library-root python-command)
678 "Start a new RPC process and return the associated buffer."
679 (elpy-rpc--cleanup-buffers)
680 (with-elpy-rpc-virtualenv-activated
681 (let* ((full-python-command (executable-find python-command))
682 (name (format " *elpy-rpc [project:%s environment:%s]*"
683 library-root
684 current-environment))
685 (new-elpy-rpc-buffer (generate-new-buffer name))
686 (proc nil))
687 (unless full-python-command
688 (error "Can't find Python command, configure `elpy-rpc-python-command'"))
689 (with-current-buffer new-elpy-rpc-buffer
690 (setq elpy-rpc--buffer-p t
691 elpy-rpc--buffer (current-buffer)
692 elpy-rpc--backend-library-root library-root
693 elpy-rpc--backend-python-command full-python-command
694 default-directory "/"
695 proc (condition-case err
696 (let ((process-connection-type nil)
697 (process-environment (elpy-rpc--environment)))
698 (start-process name
699 (current-buffer)
700 full-python-command
701 "-W" "ignore"
702 "-m" "elpy.__main__"))
703 (error
704 (elpy-config-error
705 "Elpy can't start Python (%s: %s)"
706 (car err) (cadr err)))))
707 (set-process-query-on-exit-flag proc nil)
708 (set-process-sentinel proc #'elpy-rpc--sentinel)
709 (set-process-filter proc #'elpy-rpc--filter)
710 (elpy-rpc-init library-root
711 (when current-environment-is-deactivated
712 current-environment-binaries)
713 (lambda (result)
714 (setq elpy-rpc--jedi-available
715 (cdr (assq 'jedi_available result))))))
716 new-elpy-rpc-buffer)))
717
718(defun elpy-rpc--cleanup-buffers ()
719 "Close RPC buffers that have not been used in five minutes."
720 (when elpy-rpc-maximum-buffer-age
721 (let ((old (- (float-time)
722 elpy-rpc-maximum-buffer-age)))
723 (dolist (buffer (buffer-list))
724 (when (and (elpy-rpc--process-buffer-p buffer)
725 (< (or (buffer-local-value 'elpy-rpc--last-call buffer)
726 old)
727 old))
728 (ignore-errors
729 (kill-process (get-buffer-process buffer)))
730 (ignore-errors
731 (kill-buffer buffer)))))))
732
733(defun elpy-rpc--sentinel (process event)
734 "The sentinel for the RPC process.
735
736As process sentinels are only ever called when the process
737terminates, this will call the error handler of all registered
738RPC calls with the event."
739 (let ((buffer (process-buffer process))
740 (err (list 'process-sentinel (substring event 0 -1))))
741 (when (and buffer
742 (buffer-live-p buffer))
743 (with-current-buffer buffer
744 (when elpy-rpc--backend-callbacks
745 (maphash (lambda (_call-id promise)
746 (ignore-errors
747 (elpy-promise-reject promise err)))
748 elpy-rpc--backend-callbacks)
749 (setq elpy-rpc--backend-callbacks nil))))))
750
751(defun elpy-rpc--filter (process output)
752 "The filter for the RPC process."
753 (let ((buffer (process-buffer process)))
754 (when (and buffer
755 (buffer-live-p buffer))
756 (with-current-buffer buffer
757 (goto-char (point-max))
758 (insert output)
759 (while (progn
760 (goto-char (point-min))
761 (search-forward "\n" nil t))
762 (let ((line-end (point))
763 (json nil)
764 (did-read-json nil))
765 (goto-char (point-min))
766 (condition-case _err
767 (progn
768 (setq json (let ((json-array-type 'list)
769 (json-false nil)
770 (json-encoding-pretty-print nil)) ;; Link to bug https://github.com/jorgenschaefer/elpy/issues/1521
771 (json-read)))
772 (if (listp json)
773 (setq line-end (1+ (point))
774 did-read-json t)
775 (goto-char (point-min))))
776 (error
777 (goto-char (point-min))))
778 (cond
779 (did-read-json
780 (delete-region (point-min) line-end)
781 (elpy-rpc--handle-json json))
782 ((looking-at "elpy-rpc ready\n")
783 (replace-match "")
784 (elpy-rpc--check-backend-version "1.1"))
785 ((looking-at "elpy-rpc ready (\\([^ ]*\\))\n")
786 (let ((rpc-version (match-string 1)))
787 (replace-match "")
788 (elpy-rpc--check-backend-version rpc-version)))
789 ((looking-at ".*No module named elpy\n")
790 (replace-match "")
791 (elpy-config-error "Elpy module not found"))
792 (t
793 (let ((line (buffer-substring (point-min)
794 line-end)))
795 (delete-region (point-min) line-end)
796 (elpy-rpc--handle-unexpected-line line))))))))))
797
798(defmacro elpy-insert--popup (buffer-name &rest body)
799 "Pop up a help buffer named BUFFER-NAME and execute BODY in it."
800 (declare (indent 1))
801 `(with-help-window ,buffer-name
802 (with-current-buffer standard-output
803 ,@body)))
804
805(defun elpy-rpc--check-backend-version (rpc-version)
806 "Check that we are using the right version."
807 (unless (equal rpc-version elpy-version)
808 (elpy-insert--popup "*Elpy Version Mismatch*"
809 (elpy-insert--header "Elpy Version Mismatch")
810 (elpy-insert--para
811 "You are not using the same version of Elpy in Emacs Lisp "
812 "compared to Python. This can cause random problems. Please "
813 "do make sure to use compatible versions.\n\n"
814 "This often happens because you have an obsolete elpy python "
815 "package installed on your system/virtualenv. This package "
816 "shadows the elpy python package shipped with elpy, leading "
817 "to this mismatch. If it is the case, uninstalling the elpy "
818 "python package (with pip for example) should resolve the issue.\n")
819 (insert
820 "\n"
821 "Elpy Emacs Lisp version: " elpy-version "\n"
822 "Elpy Python version....: " rpc-version "\n"))))
823
824(defun elpy-rpc--handle-unexpected-line (line)
825 "Handle an unexpected line from the backend.
826
827This is usually an error or backtrace."
828 (let ((buf (get-buffer "*Elpy Output*")))
829 (unless buf
830 (elpy-insert--popup "*Elpy Output*"
831 (elpy-insert--header "Output from Backend")
832 (elpy-insert--para
833 "There was some unexpected output from the Elpy backend. "
834 "This is usually not a problem and should usually not be "
835 "reported as a bug with Elpy. You can safely hide this "
836 "buffer and ignore it. You can also see the output below "
837 "in case there is an actual problem.\n\n")
838 (elpy-insert--header "Output")
839 (setq buf (current-buffer))))
840 (with-current-buffer buf
841 (goto-char (point-max))
842 (let ((inhibit-read-only t))
843 (insert line)))))
844
845(defun elpy-rpc--handle-json (json)
846 "Handle a single JSON object from the RPC backend."
847 (let ((call-id (cdr (assq 'id json)))
848 (error-object (cdr (assq 'error json)))
849 (result (cdr (assq 'result json))))
850 (let ((promise (gethash call-id elpy-rpc--backend-callbacks)))
851 (unless promise
852 (error "Received a response for unknown call-id %s" call-id))
853 (remhash call-id elpy-rpc--backend-callbacks)
854 (if error-object
855 (elpy-promise-reject promise error-object)
856 (elpy-promise-resolve promise result)))))
857
858(defun elpy-rpc--default-error-callback (error-object)
859 "Display an error from the RPC backend."
860 ;; We actually might get an (error "foo") thing here.
861 (if (and (consp error-object)
862 (not (consp (car error-object))))
863 (signal (car error-object) (cdr error-object))
864 (let ((message (cdr (assq 'message error-object)))
865 (code (cdr (assq 'code error-object)))
866 (data (cdr (assq 'data error-object))))
867 (cond
868 ((not (numberp code))
869 (error "Bad response from RPC: %S" error-object))
870 ((< code 300)
871 (message "Elpy warning: %s" message))
872 ((< code 500)
873 (error "Elpy error: %s" message))
874 ((and elpy-rpc-error-timeout
875 elpy-rpc--last-error-popup
876 (<= (float-time)
877 (+ elpy-rpc--last-error-popup
878 elpy-rpc-error-timeout)))
879 (message "Elpy error popup ignored, see `elpy-rpc-error-timeout': %s"
880 message))
881 ((not elpy-disable-backend-error-display)
882 (let ((config (elpy-config--get-config)))
883 (elpy-insert--popup "*Elpy Error*"
884 (elpy-insert--header "Elpy Error")
885 (elpy-insert--para
886 "The backend encountered an unexpected error. This indicates "
887 "a bug in Elpy. Please open a bug report with the data below "
888 "in the Elpy bug tracker:")
889 (insert "\n"
890 "\n")
891 (insert-button
892 "https://github.com/jorgenschaefer/elpy/issues/new"
893 'action (lambda (button)
894 (browse-url (button-get button 'url)))
895 'url "https://github.com/jorgenschaefer/elpy/issues/new")
896 (insert "\n"
897 "\n"
898 "```\n")
899 (elpy-insert--header "Error Message")
900 (insert message "\n\n")
901 (elpy-insert--header "Configuration")
902 (elpy-config--insert-configuration-table config)
903 (let ((traceback (cdr (assq 'traceback data))))
904 (when traceback
905 (insert "\n")
906 (elpy-insert--header "Traceback")
907 (insert traceback)))
908 (let ((jedi-info (cdr (assq 'jedi_debug_info data))))
909 (when jedi-info
910 (insert "\n")
911 (elpy-insert--header "Jedi Debug Information")
912 (pcase (cdr (assq 'debug_info jedi-info))
913 (`nil (insert "Jedi did not emit any debug info.\n"))
914 (infos
915 (dolist (outstr infos)
916 (insert outstr "\n"))))
917 (insert "\n"
918 "```\n"
919 "\n"
920 "Reproduction:\n"
921 "\n")
922 (let ((method (cdr (assq 'method jedi-info)))
923 (source (cdr (assq 'source jedi-info)))
924 (script-args (cdr (assq 'script_args jedi-info))))
925 (insert "```Python\n")
926 (insert "import jedi\n"
927 "\n"
928 "source = '''\\\n"
929 source
930 "'''\n"
931 "\n"
932 "script = jedi.Script(" script-args ")\n"
933 "script." method "()\n"))))
934 (unless (= 0 (current-column))
935 (insert "\n"))
936 (insert "```"))
937 (setq elpy-rpc--last-error-popup (float-time))))))))
938
939(defun elpy-rpc--environment ()
940 "Return a `process-environment' for the RPC process.
941
942This includes `elpy-rpc-pythonpath' in the PYTHONPATH, if set."
943 (if (or (not elpy-rpc-pythonpath)
944 (not (file-exists-p (expand-file-name "elpy/__init__.py"
945 elpy-rpc-pythonpath))))
946 process-environment
947 (let* ((old-pythonpath (getenv "PYTHONPATH"))
948 (new-pythonpath (if old-pythonpath
949 (concat elpy-rpc-pythonpath
950 path-separator
951 old-pythonpath)
952 elpy-rpc-pythonpath)))
953 (cons (concat "PYTHONPATH=" new-pythonpath)
954 (append process-environment
955 (when (and (string-equal system-type "windows-nt")
956 (>= (string-match-p
957 (regexp-quote "utf-8")
958 (format "%s" buffer-file-coding-system))) 0)
959 (list
960 "PYTHONIOENCODING=utf-8"
961 "PYTHONLEGACYWINDOWSSTDIO=1")))))))
962
963(defun elpy-rpc--buffer-contents ()
964 "Return the contents of the current buffer.
965
966This returns either a string, or a file object for the RPC
967protocol if the buffer is larger than
968`elpy-rpc-large-buffer-size'."
969 (if (< (buffer-size) elpy-rpc-large-buffer-size)
970 (buffer-string)
971 (let ((file-name (make-temp-file "elpy-rpc-"))
972 (coding-system-for-write 'utf-8))
973 (write-region nil nil file-name nil :nomessage)
974 `((filename . ,file-name)
975 (delete_after_use . t)))))
976
977(defun elpy-rpc--region-contents ()
978 "Return the selected region as a string."
979 (if (use-region-p)
980 (buffer-substring (region-beginning) (region-end))))
981
982(defun elpy-rpc--disconnect ()
983 "Disconnect rpc process from elpy buffers."
984 (dolist (buf (buffer-list))
985 (with-current-buffer buf
986 (when elpy-mode
987 (setq elpy-rpc--buffer nil)))))
988
989;; RPC API functions
990
991(defun elpy-rpc-restart ()
992 "Restart all RPC processes."
993 (interactive)
994 (dolist (buffer (buffer-list))
995 (when (elpy-rpc--process-buffer-p buffer)
996 (ignore-errors
997 (kill-process (get-buffer-process buffer)))
998 (ignore-errors
999 (kill-buffer buffer)))))
1000
1001(defun elpy-rpc-init (library-root environment-binaries &optional success error)
1002 "Initialize the backend.
1003
1004This has to be called as the first method, else Elpy won't be
1005able to respond to other calls.
1006
1007+LIBRARY-ROOT is the current project root,
1008+ENVIRONMENT-BINARIES is the path to the python binaries of the environment to work in."
1009 (elpy-rpc "init"
1010 ;; This uses a vector because otherwise, json-encode in
1011 ;; older Emacsen gets seriously confused, especially when
1012 ;; backend is nil.
1013 (vector `((project_root . ,(expand-file-name library-root))
1014 (environment . ,(when environment-binaries
1015 (expand-file-name
1016 environment-binaries)))))
1017 success error))
1018
1019
1020;;;;;;;;;;;;;;
1021;;; RPC API
1022
1023(defun elpy-rpc-get-calltip (&optional success error)
1024 "Call the get_calltip API function.
1025
1026Returns a calltip string for the function call at point."
1027 (when (< (buffer-size) elpy-rpc-ignored-buffer-size)
1028 (elpy-rpc "get_calltip"
1029 (list buffer-file-name
1030 (elpy-rpc--buffer-contents)
1031 (- (point)
1032 (point-min)))
1033 success error)))
1034
1035
1036(defun elpy-rpc-get-calltip-or-oneline-docstring (&optional success error)
1037 "Call the get_calltip_or_oneline_doc API function.
1038
1039Returns a calltip string or a oneline docstring for the function call at point."
1040 (when (< (buffer-size) elpy-rpc-ignored-buffer-size)
1041 (elpy-rpc "get_calltip_or_oneline_docstring"
1042 (list buffer-file-name
1043 (elpy-rpc--buffer-contents)
1044 (- (point)
1045 (point-min)))
1046 success error)))
1047
1048
1049(defun elpy-rpc-get-oneline-docstring (&optional success error)
1050 "Call the get_oneline_docstring API function.
1051
1052Returns a oneline docstring string for the symbol at point."
1053 (when (< (buffer-size) elpy-rpc-ignored-buffer-size)
1054 (elpy-rpc "get_oneline_docstring"
1055 (list buffer-file-name
1056 (elpy-rpc--buffer-contents)
1057 (- (point)
1058 (point-min)))
1059 success error)))
1060
1061(defun elpy-rpc-get-completions (&optional success error)
1062 "Call the get_completions API function.
1063
1064Returns a list of possible completions for the Python symbol at
1065point."
1066 (when (and (< (buffer-size) elpy-rpc-ignored-buffer-size)
1067 (not (string-match "^[0-9]+$" (symbol-name (symbol-at-point)))))
1068 (elpy-rpc "get_completions"
1069 (list buffer-file-name
1070 (elpy-rpc--buffer-contents)
1071 (- (point)
1072 (point-min)))
1073 success error)))
1074
1075(defun elpy-rpc-get-completion-docstring (completion &optional success error)
1076 "Call the get_completion_docstring API function.
1077
1078Returns a doc string or nil"
1079 (elpy-rpc "get_completion_docstring" (list completion) success error))
1080
1081(defun elpy-rpc-get-completion-location (completion &optional success error)
1082 "Call the get_completion_location API function.
1083
1084Returns a list of file name and line number, or nil"
1085 (elpy-rpc "get_completion_location" (list completion) success error))
1086
1087(defun elpy-rpc-get-definition (&optional success error)
1088 "Call the find_definition API function.
1089
1090Returns nil or a list of (filename, point)."
1091 (elpy-rpc "get_definition"
1092 (list buffer-file-name
1093 (elpy-rpc--buffer-contents)
1094 (- (point)
1095 (point-min)))
1096 success error))
1097
1098(defun elpy-rpc-get-assignment (&optional success error)
1099 "Call the find_assignment API function.
1100
1101Returns nil or a list of (filename, point)."
1102 (elpy-rpc "get_assignment"
1103 (list buffer-file-name
1104 (elpy-rpc--buffer-contents)
1105 (- (point)
1106 (point-min)))
1107 success error))
1108
1109(defun elpy-rpc-get-docstring (&optional success error)
1110 "Call the get_docstring API function.
1111
1112Returns a possible multi-line docstring for the symbol at point."
1113 (elpy-rpc "get_docstring"
1114 (list buffer-file-name
1115 (elpy-rpc--buffer-contents)
1116 (- (point)
1117 (point-min)))
1118 success error))
1119
1120(defun elpy-rpc-get-pydoc-completions (prefix &optional success error)
1121 "Return a list of modules available in pydoc starting with PREFIX."
1122 (elpy-rpc "get_pydoc_completions" (list prefix)
1123 success error))
1124
1125(defun elpy-rpc-get-pydoc-documentation (symbol &optional success error)
1126 "Get the Pydoc documentation for SYMBOL.
1127
1128Returns a possible multi-line docstring."
1129 (elpy-rpc "get_pydoc_documentation" (list symbol)
1130 success error))
1131
1132(defun elpy-rpc-get-usages (&optional success error)
1133 "Return the symbol under point usages as a list."
1134 (elpy-rpc "get_usages"
1135 (list buffer-file-name
1136 (elpy-rpc--buffer-contents)
1137 (- (point)
1138 (point-min)))
1139 success error))
1140
1141(defun elpy-rpc-get-names (&optional success error)
1142 "Return all names (possible candidates for jumping to definition)."
1143 (elpy-rpc "get_names"
1144 (list buffer-file-name
1145 (elpy-rpc--buffer-contents)
1146 (- (point)
1147 (point-min)))
1148 success error))
1149
1150(defun elpy-rpc-get-rename-diff (new-name &optional success error)
1151 "Return the diffs resulting from renaming the thing at point to NEW-NAME."
1152 (elpy-rpc "get_rename_diff"
1153 (list buffer-file-name
1154 (elpy-rpc--buffer-contents)
1155 (- (point)
1156 (point-min))
1157 new-name)
1158 success error))
1159
1160(defun elpy-rpc-get-extract-variable-diff (new-name beg-line end-line beg-col end-col &optional success error)
1161 "Return the diffs resulting from renaming the thing at point to NEW-NAME."
1162 (elpy-rpc "get_extract_variable_diff"
1163 (list buffer-file-name
1164 (elpy-rpc--buffer-contents)
1165 (- (point)
1166 (point-min))
1167 new-name
1168 beg-line end-line beg-col end-col)
1169 success error))
1170
1171(defun elpy-rpc-get-extract-function-diff (new-name beg-line end-line beg-col end-col &optional success error)
1172 "Return the diffs resulting from renaming the thing at point to NEW-NAME."
1173 (elpy-rpc "get_extract_function_diff"
1174 (list buffer-file-name
1175 (elpy-rpc--buffer-contents)
1176 (- (point)
1177 (point-min))
1178 new-name
1179 beg-line end-line beg-col end-col)
1180 success error))
1181
1182(defun elpy-rpc-get-inline-diff (&optional success error)
1183 "Return the diffs resulting from inlineing the variable at point."
1184 (elpy-rpc "get_inline_diff"
1185 (list buffer-file-name
1186 (elpy-rpc--buffer-contents)
1187 (- (point)
1188 (point-min)))
1189 success error))
1190
1191
1192(provide 'elpy-rpc)
1193;;; elpy-rpc.el ends here