]>
Commit | Line | Data |
---|---|---|
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 | ||
37 | Elpy creates RPC buffers over time, depending on python interpreters | |
38 | and the project root. When there are many projects being worked on, | |
39 | these can accumulate. Setting this variable to an integer will close | |
40 | buffers and processes when they have not been used for this amount of | |
41 | seconds. | |
42 | ||
43 | Setting 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 | ||
51 | The Elpy RPC protocol uses JSON as the serialization format. | |
52 | Large buffers take a long time to encode, so Elpy can transmit | |
53 | them via temporary files. If a buffer is larger than this value, | |
54 | it 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 | ||
62 | To provide completion, Elpy's backends have to parse the whole | |
63 | file every time. For very large files, this is slow, and can make | |
64 | Emacs laggy. Elpy will simply not work on buffers larger than | |
65 | this 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 | ||
81 | This should NOT be an interactive shell like ipython or jupyter. | |
82 | ||
83 | As the RPC should be independent of any virtual environment, Elpy | |
84 | will try to use the system interpreter if it exists. If you wish | |
85 | to use a specific python interpreter (from a virtual environment | |
86 | for 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 | ||
106 | This should be a directory where the elpy module can be found. If | |
107 | this is nil, it's assumed elpy can be found in the standard path. | |
108 | Usually, 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 | ||
116 | When Elpy blocks Emacs to wait for a response from the RPC | |
117 | process, it will assume it won't come or wait too long after this | |
118 | many seconds. On a slow computer, or if you have a large project, | |
119 | you might want to increase this. | |
120 | ||
121 | A 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 | ||
132 | When Elpy encounters an error in the backend, it will display a | |
133 | lengthy description of the problem for a bug report. This hangs | |
134 | Emacs for a moment, and can be rather annoying if it happens | |
135 | repeatedly while editing a source file. | |
136 | ||
137 | If this variabl is non-nil, Elpy will not display the error | |
138 | message 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 | ||
145 | Used 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 | ||
167 | This 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 | ||
186 | Can 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 | |
189 | path or a function returning a virtualenv path. | |
190 | ||
191 | If the default virtual environment does not exist, it will be | |
192 | created using `elpy-rpc-python-command' and populated with the | |
193 | needed 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 | ||
250 | During 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 | ||
303 | Create the virtualenv if it does not exist yet. | |
304 | Update the virtualenv if the variable `elpy-rpc-python-command' has | |
305 | changed since the virtualenv creation. | |
306 | ||
307 | An additional file `elpy-rpc-python-path-command' is added in the | |
308 | virtualenv directory in order to keep track of the python | |
309 | binaries 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 | ||
470 | A promise is an object with a success and error callback. If the | |
471 | promise is resolved using `elpy-promise-resolve', the SUCCESS | |
472 | callback is called with the given value. The current buffer is | |
473 | restored, too. | |
474 | ||
475 | If the promise is rejected using `elpy-promise-reject', the ERROR | |
476 | callback is called. For this function, the current buffer is not | |
477 | necessarily restored, as it is also called when the buffer does | |
478 | not 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 | ||
541 | This will accept process output while waiting. | |
542 | ||
543 | This will wait for the current Elpy RPC process specifically, as | |
544 | Emacs currently has a bug where it can wait for the entire time | |
545 | of the timeout, even if output arrives. | |
546 | ||
547 | See 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 | ||
564 | If SUCCESS and optionally ERROR is given, return immediately and | |
565 | call those when a result is available. Otherwise, wait for a | |
566 | result 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 | ||
576 | Returns 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 | ||
601 | When a result is available, SUCCESS will be called with that | |
602 | value as its sole argument. If an error occurs, ERROR will be | |
603 | called with the error list. | |
604 | ||
605 | Returns 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 | ||
623 | Must 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, | |
632 | creating 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 | ||
644 | If BUFFER is a buffer for an elpy-rpc process, but the process | |
645 | died, 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 | ||
736 | As process sentinels are only ever called when the process | |
737 | terminates, this will call the error handler of all registered | |
738 | RPC 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 | ||
827 | This 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 | ||
942 | This 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 | ||
966 | This returns either a string, or a file object for the RPC | |
967 | protocol 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 | ||
1004 | This has to be called as the first method, else Elpy won't be | |
1005 | able 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 | ||
1026 | Returns 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 | ||
1039 | Returns 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 | ||
1052 | Returns 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 | ||
1064 | Returns a list of possible completions for the Python symbol at | |
1065 | point." | |
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 | ||
1078 | Returns 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 | ||
1084 | Returns 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 | ||
1090 | Returns 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 | ||
1101 | Returns 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 | ||
1112 | Returns 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 | ||
1128 | Returns 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 |