-
Notifications
You must be signed in to change notification settings - Fork 89
/
jedi-core.el
1433 lines (1181 loc) · 50.1 KB
/
jedi-core.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; jedi-core.el --- Common code of jedi.el and company-jedi.el -*- lexical-binding: t; -*-
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
;; Package-Requires: ((emacs "24") (epc "0.1.0") (python-environment "0.0.2") (cl-lib "0.5"))
;; Version: 0.3.0
;; This file is NOT part of GNU Emacs.
;; jedi.el is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; jedi.el is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with jedi.el.
;; If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(require 'cl-lib)
(require 'ring)
(require 'epc)
(require 'python-environment)
(declare-function popup-tip "popup")
(declare-function pos-tip-show "pos-tip")
(defgroup jedi nil
"Auto-completion for Python."
:group 'completion
:prefix "jedi:")
(defconst jedi:version "0.3.0")
(defvar jedi:source-dir (if load-file-name
(file-name-directory load-file-name)
default-directory))
(defvar jedi:epc nil)
(make-variable-buffer-local 'jedi:epc)
(defvar jedi:server-script
(convert-standard-filename
(expand-file-name "jediepcserver.py" jedi:source-dir))
"Full path to Jedi server script file ``jediepcserver.py``.")
(defvar jedi:setup-function nil)
(defvar jedi:mode-function nil)
;;; Configuration variables
(defcustom jedi:environment-root nil
"Name of Python environment to use.
If it is nil, `python-environment-default-root-name' is used.
You can specify a full path instead of a name (relative path).
In that case, `python-environment-directory' is ignored and
Python virtual environment is created at the specified path."
:group 'jedi
:type '(choice (directory
(const nil))))
(defcustom jedi:environment-virtualenv nil
"``virtualenv`` command to use. A list of string.
If it is nil, `python-environment-virtualenv' is used instead.
You must set non-`nil' value to `jedi:environment-root' in order
to make this setting work."
:group 'jedi
:type '(repeat string))
(defun jedi:-env-server-command ()
(let* ((getbin (lambda (x) (python-environment-bin x jedi:environment-root)))
(script (or (funcall getbin "jediepcserver")
(funcall getbin "jediepcserver.py"))))
(when script
(list script))))
(defcustom jedi:server-command
(or (jedi:-env-server-command)
(list "python" jedi:server-script))
"Command used to run Jedi server.
.. NOTE::
If you used `jedi:install-server' (recommended) to install
Python server jediepcserver.py, you don't need to mess around
with jediepcserver.py. Jedi.el handles everything
automatically.
If you install Python server jediepcserver.py using
`jedi:install-server' command, `jedi:server-command' should be
automatically set to::
'(\"~/.emacs.d/.python-environments/default/bin/jediepcserver.py\")
Otherwise, it is set to::
'(\"python\" \"JEDI:SOURCE-DIR/jediepcserver.py\")
.. NOTE:: If you installed jediepcserver.py manually, then you
have to set `jedi:server-command' appropriately.
If you can run ``jediepcserver.py --help`` in your shell, then
you can simply set::
(setq jedi:server-command '(\"jediepcserver.py\"))
Otherwise, you need to find where you installed
jediepcserver.py then set the path directly::
(setq jedi:server-command '(\"PATH/TO/jediepcserver.py\"))
If you want to use a specific version of Python, setup
`jedi:environment-virtualenv' variable appropriately and
reinstall jediepcserver.py.
If you want to pass some arguments to the Jedi server command,
use `jedi:server-args' instead of appending them
`jedi:server-command'."
:group 'jedi
:type '(repeat string))
(defcustom jedi:server-args nil
"Command line arguments to be appended to `jedi:server-command'.
If you want to add some special `sys.path' when starting Jedi
server, do something like this::
(setq jedi:server-args
'(\"--sys-path\" \"MY/SPECIAL/PATH\"
\"--sys-path\" \"MY/OTHER/SPECIAL/PATH\"))
If you want to include some virtualenv, do something like the
following. Note that actual environment variable ``VIRTUAL_ENV``
is treated automatically so you don't need to pass it. Also,
you need to start Jedi EPC server with the same python version
that you use for the virtualenv.::
(setq jedi:server-args
'(\"--virtual-env\" \"SOME/VIRTUAL_ENV_1\"
\"--virtual-env\" \"SOME/VIRTUAL_ENV_2\"))
To see what other arguments Jedi server can take, execute the
following command::
python jediepcserver.py --help
**Advanced usage**
Sometimes you want to configure how Jedi server is started per
buffer. To do that, you should make this variable buffer local
in `python-mode-hook' and set it to some buffer specific variable,
like this::
(defun my-jedi-server-setup ()
(let ((cmds (GET-SOME-PROJECT-SPECIFIC-COMMAND))
(args (GET-SOME-PROJECT-SPECIFIC-ARGS)))
(when cmds (set (make-local-variable 'jedi:server-command) cmds))
(when args (set (make-local-variable 'jedi:server-args) args))))
(add-hook 'python-mode-hook 'my-jedi-server-setup)
Note that Jedi server run by the same command is pooled. So,
there is only one Jedi server for the same set of command. If
you want to check how many EPC servers are running, use the EPC
GUI: M-x `epc:controller'. You will see a table of EPC connections
for Jedi.el and other EPC applications.
If you want to start a new ad-hoc server for the current buffer,
use the command `jedi:start-dedicated-server'."
:group 'jedi
:type '(repeat string))
(defcustom jedi:complete-on-dot nil
"Non-`nil' means automatically start completion after inserting a dot.
To make this option work, you need to use `jedi:setup' instead of
`jedi:ac-setup' to start Jedi."
:group 'jedi
:type 'boolean)
(defcustom jedi:tooltip-method '(pos-tip popup)
"Configuration for `jedi:tooltip-show'.
This is a list which may contain symbol(s) `pos-tip' and/or
`popup'. It determines tooltip method to use. Setting this
value to nil means to use minibuffer instead of tooltip."
:group 'jedi
:type '(repeat (choice (const pos-tip)
(const popup))))
(defcustom jedi:get-in-function-call-timeout 3000
"Cancel request to server for call signature after this period
specified in in millisecond."
:group 'jedi
:type 'integer)
(defcustom jedi:get-in-function-call-delay 1000
"How long Jedi should wait before showing call signature
tooltip in millisecond."
:group 'jedi
:type 'integer)
(defcustom jedi:goto-definition-config
'((nil nil nil)
(t nil nil)
(nil definition nil)
(t definition nil)
(nil nil t )
(t nil t )
(nil definition t )
(t definition t ))
"Configure how prefix argument modifies `jedi:goto-definition' behavior.
Each element of the list is arguments (list) passed to
`jedi:goto-definition'. Note that this variable has no effect on
`jedi:goto-definition' when it is used as a lisp function
The following setting is default (last parts are omitted).
Nth element is used as the argument when N universal prefix
arguments (``C-u``) are given.::
(setq jedi:goto-definition-config
'((nil nil nil) ; C-.
(t nil nil) ; C-u C-.
(nil definition nil) ; C-u C-u C-.
(t definition nil) ; C-u C-u C-u C-.
...))
For example, if you want to follow \"substitution path\" by default,
use the setting like this::
(setq jedi:goto-definition-config
'((nil definition nil)
(t definition nil)
(nil nil nil)
(t nil nil)
(nil definition t )
(t definition t )
(nil nil t )
(t nil t )))
You can rearrange the order to have most useful sets of arguments
at the top."
:group 'jedi
:type '(repeat (choice (const nil)
(const t)
(const definition))))
(defcustom jedi:doc-mode 'rst-mode
"Major mode to use when showing document."
:group 'jedi
:type 'function)
(defcustom jedi:doc-hook '(view-mode)
"The hook that's run after showing a document."
:type 'hook
:group 'jedi)
(defcustom jedi:goto-definition-hook nil
"The hook that's run after jumping to a definition location."
:type 'hook
:group 'jedi)
(defcustom jedi:doc-display-buffer 'display-buffer
"A function to be called with a buffer to show document."
:group 'jedi
:type 'function)
(defcustom jedi:install-imenu nil
"If `t', use Jedi to create `imenu' index."
:group 'jedi
:type 'boolean)
(defcustom jedi:imenu-create-index-function 'jedi:create-nested-imenu-index
"`imenu-create-index-function' for Jedi.el.
It must be a function that takes no argument and return an object
described in `imenu--index-alist'.
This can be set to `jedi:create-flat-imenu-index'.
Default is `jedi:create-nested-imenu-index'."
:group 'jedi
:type 'function)
(make-obsolete-variable 'jedi:setup-keys nil "0.1.3")
(defcustom jedi:setup-keys nil
"Setup recommended keybinds.
.. warning:: Use of this value is obsolete now. As of 0.1.3,
jedi.el has default keybinds, which are different than these. See also
`jedi-mode'.
.. admonition:: Default keybinds
``<C-tab>`` : = `jedi:key-complete'
Complete code at point. (`jedi:complete')
``C-.`` : = `jedi:key-goto-definition'
Goto the definition of the object at point. (`jedi:goto-definition')
``C-c d`` : = `jedi:key-show-doc'
Show the documentation of the object at point. (`jedi:show-doc')
``C-c r`` : = `jedi:key-related-names'
Find related names of the object at point.
(`helm-jedi-related-names' / `anything-jedi-related-names')
When `jedi:setup-keys' is non-`nil', recommended keybinds are set
in `jedi-mode-map' when **loading** jedi.el. Therefore, you must
set this value before jedi.el is loaded. As recommended usage of
jedi.el is to call `jedi:setup' via `python-mode-hook' where
`jedi:setup' is autloaded, setting `jedi:setup-keys' to `t' in
you emacs setup (e.g., ``.emacs.d/init.el``) works fine.::
(setq jedi:setup-keys t)
(add-hook 'python-mode-hook 'jedi:setup)
If you want to require jedi.el explicitly when loading Emacs,
make sure to set `jedi:setup-keys' before loading jedi.el::
(setq jedi:setup-keys t)
(require 'jedi)
Byte compiler warns about unbound variable if you set
`jedi:setup-keys' before loading jedi.el. The proper way to
suppress this warning is the following::
(eval-when-compile (require 'jedi nil t))
(setq jedi:setup-keys t)
You can change these keybinds by changing `jedi:key-complete',
`jedi:key-goto-definition', `jedi:key-show-doc', and
`jedi:key-related-names'. For example, default keybind for
ropemacs's `rope-show-doc' is same as `jedi:show-doc'. You can
avoid collision by something like this::
(setq jedi:key-show-doc (kbd \"C-c D\"))"
:group 'jedi
:type 'boolean)
(defun jedi:-custom-set-key-string (sym defs)
"Convert key sequence DEFS from string and store into in SYM."
(custom-set-default sym (kbd defs)))
(defun jedi:-custom-get-key-string (sym)
"Convert key sequence stored in SYM to string representation."
(key-description (default-value sym)))
(defcustom jedi:key-complete "<C-tab>"
"Keybind for command `jedi:complete'."
:group 'jedi
:type 'string
:set #'jedi:-custom-set-key-string
:get #'jedi:-custom-get-key-string)
(defcustom jedi:key-goto-definition "C-."
"Keybind for command `jedi:goto-definition'."
:group 'jedi
:type 'string
:set #'jedi:-custom-set-key-string
:get #'jedi:-custom-get-key-string)
(defcustom jedi:key-show-doc "C-c d"
"Keybind for command `jedi:show-doc'."
:group 'jedi
:type 'string
:set #'jedi:-custom-set-key-string
:get #'jedi:-custom-get-key-string)
(defcustom jedi:key-related-names "C-c r"
"Keybind for command `helm-jedi-related-names' or
`anything-jedi-related-names'."
:group 'jedi
:type 'string
:set #'jedi:-custom-set-key-string
:get #'jedi:-custom-get-key-string)
(defcustom jedi:key-goto-definition-pop-marker "C-,"
"Keybind for command `jedi:goto-definition-pop-marker'."
:group 'jedi
:type 'string
:set #'jedi:-custom-set-key-string
:get #'jedi:-custom-get-key-string)
(defcustom jedi:use-shortcuts nil
"If non-`nil', enable the following shortcuts:
| ``M-.`` `jedi:goto-definition'
| ``M-,`` `jedi:goto-definition-pop-marker'
"
:group 'jedi
:type 'boolean)
(defcustom jedi:import-python-el-settings t
"Automatically import settings from python.el variables."
:group 'jedi
:type 'boolean)
(defcustom jedi:goto-definition-marker-ring-length 16
"Length of marker ring to store `jedi:goto-definition' call positions"
:group 'jedi
:type 'integer)
;;; Internal variables
(defvar jedi:get-in-function-call--d nil
"Bound to deferred object while requesting get-in-function-call")
(defvar jedi:defined-names--singleton-d nil
"Bound to deferred object while requesting defined_names")
;;; Jedi mode
(defvar jedi-mode-map (make-sparse-keymap))
(defun jedi:handle-post-command ()
(jedi:get-in-function-call-when-idle))
(define-minor-mode jedi-mode
"Jedi mode.
When `jedi-mode' is on, call signature is automatically shown as
toolitp when inside of function call.
\\{jedi-mode-map}"
:keymap jedi-mode-map
:group 'jedi
(let ((map jedi-mode-map))
(when jedi:use-shortcuts
(define-key map (kbd "M-.") 'jedi:goto-definition)
(define-key map (kbd "M-,") 'jedi:goto-definition-pop-marker)))
(if jedi-mode
(progn
(when jedi:install-imenu
(jedi:defined-names-deferred)
(setq imenu-create-index-function jedi:imenu-create-index-function))
(add-hook 'post-command-hook 'jedi:handle-post-command nil t)
(add-hook 'kill-buffer-hook 'jedi:server-pool--gc-when-idle nil t))
(remove-hook 'post-command-hook 'jedi:handle-post-command t)
(remove-hook 'kill-buffer-hook 'jedi:server-pool--gc-when-idle t)
(jedi:server-pool--gc-when-idle))
(when jedi:mode-function
(funcall jedi:mode-function)))
;; Define keybinds.
;; See: https://github.com/tkf/emacs-jedi/issues/47
(let ((map jedi-mode-map))
(when (and (boundp 'auto-complete-mode) auto-complete-mode)
(define-key map (kbd "<C-tab>") 'jedi:complete))
(define-key map (kbd "C-c ?") 'jedi:show-doc)
(define-key map (kbd "C-c .") 'jedi:goto-definition)
(define-key map (kbd "C-c ,") 'jedi:goto-definition-pop-marker)
(let ((command (cond
((featurep 'helm) 'helm-jedi-related-names)
((featurep 'anything) 'anything-jedi-related-names)
((locate-library "helm") 'helm-jedi-related-names)
((locate-library "anything") 'anything-jedi-related-names))))
(when command
(define-key map (kbd "C-c /") command))))
(when jedi:setup-keys
(let ((map jedi-mode-map))
(define-key map jedi:key-complete 'jedi:complete)
(define-key map jedi:key-goto-definition 'jedi:goto-definition)
(define-key map jedi:key-show-doc 'jedi:show-doc)
(define-key map jedi:key-goto-definition-pop-marker
'jedi:goto-definition-pop-marker)
(let ((command (cond
((featurep 'helm) 'helm-jedi-related-names)
((featurep 'anything) 'anything-jedi-related-names))))
(when command
(define-key map jedi:key-related-names command)))))
;;; EPC utils
(defun jedi:epc--live-p (mngr)
"Return non-nil when MNGR is an EPC manager object with a live
connection."
(let ((proc (ignore-errors
(epc:connection-process (epc:manager-connection mngr)))))
(and (processp proc)
;; Same as `process-live-p' in Emacs >= 24:
(memq (process-status proc) '(run open listen connect stop)))))
(defmacro jedi:-with-run-on-error (body &rest run-on-error)
(declare (indent 1))
`(let ((something-happened t))
(unwind-protect
(prog1 ,body
(setq something-happened nil))
(when something-happened
,@run-on-error))))
(defun jedi:epc--make-server-output-msg (line-count)
"Extract up to LINE-COUNT last lines from last failed EPC buffer."
(let ((epc-buffer (get-buffer (epc:server-buffer-name epc:uid)))
is-part lines)
(if (not (buffer-live-p epc-buffer))
""
(with-current-buffer epc-buffer
(save-excursion
(goto-char (point-max))
(forward-line -10)
(setq is-part (not (eq (point) (point-min))))
(setq lines (buffer-substring-no-properties (point) (point-max)))))
(format "*** EPC Server Output (last %s lines) ***\n%s%s\n"
line-count
(if is-part "<...snipped, see all with `epc:pop-to-last-server-process-buffer'...>\n" "")
lines))))
(defun jedi:epc--start-epc (server-prog server-args)
"Same as `epc:start-epc', but set query-on-exit flag for
associated processes to nil."
(let ((mngr
(condition-case epc-error
(epc:start-epc server-prog server-args)
(error
(let* ((cmdline-args (append (list server-prog) server-args))
(cmd (executable-find server-prog))
(cmd-str (or cmd
(format "nil (%S not found in exec-path)"
server-prog)))
(virtual-env (getenv "VIRTUAL_ENV"))
(warning-msg (format "
================================
Failed to start Jedi EPC server.
================================
*** EPC Error ***
%s
%s
*** EPC Server Config ***
Server arguments: %S
Actual command: %s
VIRTUAL_ENV envvar: %S
*** jedi-mode is disabled in %S ***
Fix the problem and re-enable it.
*** You may need to run \"M-x jedi:install-server\". ***
This could solve the problem especially if you haven't run the command yet
since Jedi.el installation or update and if the server complains about
Python module imports."
(cadr epc-error)
(jedi:epc--make-server-output-msg 10)
cmdline-args cmd-str virtual-env
(current-buffer))))
(display-warning 'jedi warning-msg :error)
(jedi-mode 0))))))
(set-process-query-on-exit-flag (epc:connection-process
(epc:manager-connection mngr))
nil)
(set-process-query-on-exit-flag (epc:manager-server-process mngr) nil)
mngr))
;;; Server pool
(defvar jedi:server-pool--table (make-hash-table :test 'equal)
"A hash table that holds a pool of EPC server instances.")
(defun jedi:server-pool--resolve-command (command)
"Resolve COMMAND using current environment.
Tries to find (car command) in \"exec-path\"."
(let ((command-path (executable-find (car command))))
(if command-path
(cons command-path (cdr command))
command)))
(defun jedi:server-pool--start (command)
"Get an EPC server for COMMAND from server pool or start a new one."
(let* ((resolved-command (jedi:server-pool--resolve-command command))
(cached (gethash resolved-command jedi:server-pool--table)))
(if (and cached (jedi:epc--live-p cached))
cached
(let* ((default-directory "/")
(mngr (jedi:epc--start-epc (car resolved-command) (cdr command))))
(puthash resolved-command mngr jedi:server-pool--table)
(jedi:server-pool--gc-when-idle)
mngr))))
(defun jedi:-get-servers-in-use ()
"Return a list of non-nil `jedi:epc' in all buffers."
(cl-loop with mngr-list
for buffer in (buffer-list)
for mngr = (with-current-buffer buffer jedi:epc)
when (and mngr (not (memq mngr mngr-list)))
collect mngr into mngr-list
finally return mngr-list))
(defvar jedi:server-pool--gc-timer nil)
(defun jedi:server-pool--gc ()
"Stop unused servers."
(let ((servers-in-use (jedi:-get-servers-in-use)))
(maphash
(lambda (key mngr)
(unless (memq mngr servers-in-use)
(remhash key jedi:server-pool--table)
(epc:stop-epc mngr)))
jedi:server-pool--table))
;; Clear timer so that GC is started next time
;; `jedi:server-pool--gc-when-idle' is called.
(setq jedi:server-pool--gc-timer nil))
(defun jedi:server-pool--gc-when-idle ()
"Run `jedi:server-pool--gc' when idle."
(unless jedi:server-pool--gc-timer
(setq jedi:server-pool--gc-timer
(run-with-idle-timer 10 nil 'jedi:server-pool--gc))))
;;; Server management
(defun jedi:start-server ()
(if (jedi:epc--live-p jedi:epc)
(message "Jedi server is already started!")
(setq jedi:epc (jedi:server-pool--start
(append jedi:server-command jedi:server-args))))
jedi:epc)
(defun jedi:stop-server ()
"Stop Jedi server. Use this command when you want to restart
Jedi server (e.g., when you changed `jedi:server-command' or
`jedi:server-args'). Jedi srever will be restarted automatically
later when it is needed."
(interactive)
(if jedi:epc
(epc:stop-epc jedi:epc)
(message "Jedi server is already killed."))
(setq jedi:epc nil)
;; It could be non-nil due to some error. Rescue it in that case.
(setq jedi:get-in-function-call--d nil)
(setq jedi:defined-names--singleton-d nil))
(defun jedi:stop-all-servers ()
"Stop all live Jedi servers.
This is useful to apply new settings or VIRTUAL_ENV variable
value to all buffers."
(interactive)
;; Kill all servers attached to buffers
(cl-dolist (buf (buffer-list))
(when (buffer-live-p buf)
(with-current-buffer buf
(when (jedi:epc--live-p jedi:epc)
(jedi:stop-server)))))
;; Kill all unused servers too.
(jedi:server-pool--gc))
(defun jedi:get-epc ()
"Get an EPC instance of a running server or start a new one."
(if (jedi:epc--live-p jedi:epc)
jedi:epc
(jedi:start-server)))
;;;###autoload
(defun jedi:start-dedicated-server (command)
"Start Jedi server dedicated to this buffer.
This is useful, for example, when you want to use different
`sys.path' for some buffer. When invoked as an interactive
command, it asks you how to start the Jedi server. You can edit
the command in minibuffer to specify the way Jedi server run.
If you want to setup how Jedi server is started programmatically
per-buffer/per-project basis, make `jedi:server-command' and
`jedi:server-args' buffer local and set it in `python-mode-hook'.
See also: `jedi:server-args'."
(interactive
(list (split-string-and-unquote
(read-string "Run Jedi server: "
(mapconcat
#'identity
(append jedi:server-command
jedi:server-args)
" ")))))
;; Reset `jedi:epc' so that a new server is created when COMMAND is
;; new. If it is already in the server pool, the server instance
;; already in the pool is picked up by `jedi:start-server'.
(setq jedi:epc nil)
;; Set `jedi:server-command', so that this command is used
;; when restarting EPC server of this buffer.
(set (make-local-variable 'jedi:server-command) command)
(set (make-local-variable 'jedi:server-args) nil)
(jedi:start-server))
(defun jedi:-buffer-file-name ()
"Return `buffer-file-name' without text properties.
See: https://github.com/tkf/emacs-jedi/issues/54"
(substring-no-properties (or (buffer-file-name) "")))
(defun jedi:call-deferred (method-name)
"Call ``Script(...).METHOD-NAME()`` and return a deferred object."
(let ((source (buffer-substring-no-properties (point-min) (point-max)))
(source-path (jedi:-buffer-file-name))
;; line=0 is an error for jedi, but is possible for empty buffers.
(line (max 1 (count-lines (point-min) (min (1+ (point)) (point-max)))))
(column (- (point) (line-beginning-position))))
(epc:call-deferred (jedi:get-epc)
method-name
(list source line column source-path))))
;;; Completion
(defvar jedi:complete-reply nil
"Last reply to `jedi:complete-request'.")
(defvar jedi:complete-request-point 0
;; It is passed to `=', so do not initialize this value by `nil'.
"The point where `jedi:complete-request' is called.")
(defun jedi:complete-request ()
"Request ``Script(...).complete`` and return a deferred object.
`jedi:complete-reply' is set to the reply sent from the server."
(setq jedi:complete-request-point (point))
(deferred:nextc (jedi:call-deferred 'complete)
(lambda (reply)
(setq jedi:complete-reply reply))))
;;; Call signature (get_in_function_call)
(defface jedi:highlight-function-argument
'((t (:inherit bold)))
"Face used for the argument at point in a function's argument list"
:group 'jedi)
(cl-defun jedi:get-in-function-call--construct-call-signature
(&key params index call_name)
(if (not index)
(concat call_name "()")
(let ((current-arg (nth index params)))
(when (and current-arg (null jedi:tooltip-method))
(setf (nth index params)
(propertize current-arg 'face 'jedi:highlight-function-argument)))
(concat call_name "(" (mapconcat #'identity params ", ") ")"))))
(defun jedi:get-in-function-call--tooltip-show (args)
(when (and args (or (and (boundp 'ac-completing) (not ac-completing))
(and (boundp 'company-pseudo-tooltip-overlay)
(not company-pseudo-tooltip-overlay))))
(jedi:tooltip-show
(apply #'jedi:get-in-function-call--construct-call-signature args))))
(defun jedi:get-in-function-call ()
"Manually show call signature tooltip."
(interactive)
(deferred:nextc
(jedi:call-deferred 'get_in_function_call)
#'jedi:get-in-function-call--tooltip-show))
(defun jedi:get-in-function-call-when-idle ()
"Show tooltip when Emacs is ilde."
(unless jedi:get-in-function-call--d
(setq jedi:get-in-function-call--d
(deferred:try
(deferred:$
(deferred:wait-idle jedi:get-in-function-call-delay)
(deferred:nextc it
(lambda ()
(when jedi-mode ; cursor may be moved
(deferred:timeout
jedi:get-in-function-call-timeout
nil
(jedi:call-deferred 'get_in_function_call)))))
(deferred:nextc it #'jedi:get-in-function-call--tooltip-show))
:finally
(lambda ()
(setq jedi:get-in-function-call--d nil))))))
(defun jedi:tooltip-show (string)
(cond
((and (memq 'pos-tip jedi:tooltip-method) window-system
(featurep 'pos-tip))
(pos-tip-show (jedi:string-fill-paragraph string)
'popup-tip-face nil nil 0))
((and (memq 'popup jedi:tooltip-method)
(featurep 'popup))
(popup-tip string))
(t (when (stringp string)
(let ((message-log-max nil))
(message string))))))
(defun jedi:string-fill-paragraph (string &optional justify)
(with-temp-buffer
(erase-buffer)
(insert string)
(goto-char (point-min))
(fill-paragraph justify)
(buffer-string)))
;;; Goto
(defvar jedi:goto-definition--index nil)
(defvar jedi:goto-definition--cache nil)
(defvar jedi:goto-definition--marker-ring
(make-ring jedi:goto-definition-marker-ring-length)
"Marker ring that stores `jedi:goto-definition' call positions")
(defun jedi:goto-definition (&optional other-window deftype use-cache index)
"Goto the definition of the object at point.
See `jedi:goto-definition-config' for how this function works
when universal prefix arguments \(``C-u``) are given. If
*numeric* prefix argument(s) \(e.g., ``M-0``) are given, goto
point of the INDEX-th result. Note that you cannot mix universal
and numeric prefixes. It is Emacs's limitation. If you mix both
kinds of prefix, you get numeric prefix.
When used as a lisp function, popup a buffer when OTHER-WINDOW is
non-nil. DEFTYPE must be either `assignment' (default) or
`definition'. When USE-CACHE is non-nil, use the locations of
the last invocation of this command. If INDEX is specified, goto
INDEX-th result."
(interactive
(if (integerp current-prefix-arg)
(list nil nil nil current-prefix-arg)
(nth (let ((i (car current-prefix-arg)))
(if i (floor (log i 4)) 0))
jedi:goto-definition-config)))
(cond
((and (or use-cache index)
jedi:goto-definition--cache)
(setq jedi:goto-definition--index (or index 0))
(jedi:goto-definition--nth other-window))
((and (eq last-command 'jedi:goto-definition)
(> (length jedi:goto-definition--cache) 1))
(jedi:goto-definition-next other-window))
(t
(setq jedi:goto-definition--index (or index 0))
(deferred:nextc (jedi:call-deferred
(cl-case deftype
((assignment nil) 'goto)
(definition 'get_definition)
(t (error "Unsupported deftype: %s" deftype))))
(lambda (reply)
(jedi:goto-definition--callback reply other-window))))))
(defun jedi:goto-definition-push-marker ()
"Push point onto goto-definition marker ring."
(ring-insert jedi:goto-definition--marker-ring (point-marker)))
(defun jedi:goto-definition-pop-marker ()
"Goto the last point where `jedi:goto-definition' was called."
(interactive)
(if (ring-empty-p jedi:goto-definition--marker-ring)
(error "Jedi marker ring is empty, can't pop")
(let ((marker (ring-remove jedi:goto-definition--marker-ring 0)))
(switch-to-buffer (or (marker-buffer marker)
(error "Buffer has been deleted")))
(goto-char (marker-position marker))
;; Cleanup the marker so as to avoid them piling up.
(set-marker marker nil nil))))
(defun jedi:goto-definition-next (&optional other-window)
"Goto the next cached definition. See: `jedi:goto-definition'."
(interactive "P")
(let ((len (length jedi:goto-definition--cache))
(n (1+ jedi:goto-definition--index)))
(setq jedi:goto-definition--index (if (>= n len) 0 n))
(jedi:goto-definition--nth other-window)))
(defun jedi:goto-definition--callback (reply other-window)
(if (not reply)
(message "Definition not found.")
(setq jedi:goto-definition--cache reply)
(jedi:goto-definition--nth other-window t)))
(defun jedi:goto--line-column (line column)
"Like `goto-char' but specify the position by LINE and COLUMN."
(goto-char (point-min))
(forward-line (1- line))
(forward-char column))
(defvar jedi:find-file-function #'jedi:find-file
"Function that reads a source file for jedi navigation.
It must take these arguments: (file-to-read other-window-flag line_number column_number).")
(defun jedi:find-file (file line column other-window)
"Default function used by jedi to find a source FILE, LINE= and COLUMN, possibly in OTHER-WINDOW."
(funcall (if other-window #'find-file-other-window #'find-file)
file)
(jedi:goto--line-column line column))
(defun jedi:goto-definition--nth (other-window &optional try-next)
(let* ((len (length jedi:goto-definition--cache))
(n jedi:goto-definition--index)
(next (lambda ()
(when (< n (1- len))
(cl-incf jedi:goto-definition--index)
(jedi:goto-definition--nth other-window)
t))))
(cl-destructuring-bind (&key line_nr column module_path module_name
&allow-other-keys)
(nth n jedi:goto-definition--cache)
(cond
((equal module_name "__builtin__")
(unless (and try-next (funcall next))
(message "Cannot see the definition of __builtin__.")))
((not (and module_path (file-exists-p module_path)))
(unless (and try-next (funcall next))
(message "File '%s' does not exist." module_path)))
(t
(jedi:goto-definition-push-marker)
(jedi:find-file module_path line_nr column other-window)
(run-hooks 'jedi:goto-definition-hook)
(jedi:goto-definition--notify-alternatives len n))))))
(defun jedi:goto-definition--notify-alternatives (len n)
(unless (= len 1)
(message
"%d-th point in %d candidates.%s"
(1+ n)
len
;; Note: It must be `last-command', not `last-command' because
;; this function is called in deferred at the first time.
(if (eq last-command 'jedi:goto-definition)
(format " Type %s to go to the next point."
(key-description
(car (where-is-internal 'jedi:goto-definition))))
""))))
;;; Full name
(defun jedi:get-full-name-deferred ()
(deferred:$
(jedi:call-deferred 'get_definition)
(deferred:nextc it
(lambda (reply)
(cl-loop for def in reply
do (cl-destructuring-bind (&key full_name &allow-other-keys)
def
(when full_name
(return full_name))))))))
(cl-defun jedi:get-full-name-sync (&key (timeout 500))
(epc:sync
(jedi:get-epc)
(deferred:timeout timeout nil (jedi:get-full-name-deferred))))
;;; Related names
(defun jedi:related-names--source (name candidates)
`((name . ,name)
(candidates . ,candidates)
(recenter)
(type . file-line)))
(defun jedi:related-names--to-file-line (reply)
(mapcar
(lambda (x)
(cl-destructuring-bind
(&key line_nr module_name module_path description &allow-other-keys)
x
(format "%s:%s: %s - %s" module_path line_nr
module_name description)))
reply))
(defun jedi:related-names--helm (helm)
(deferred:nextc
(let ((to-file-line #'jedi:related-names--to-file-line))
(deferred:parallel
(deferred:nextc (jedi:call-deferred 'related_names) to-file-line)
(deferred:nextc (jedi:call-deferred 'goto) to-file-line)))
(lambda (candidates-list)
(funcall
helm
:sources (list (jedi:related-names--source "Jedi Related Names"
(car candidates-list))
(jedi:related-names--source "Jedi Goto"
(cadr candidates-list)))
:buffer (format "*%s jedi:related-names*" helm)))))