Complete Python symbols using Ivy
26 Aug 2015I had a fascination with Python at one point, until I got too annoyed with the indentation rules. I still have a few scripts left over, so I'm sympathetic to Python support in Emacs. The reason for today's post comes from this question on Emacs StackExchange. Essentially, the user wants to insert parens automatically when a function name is completed.
The completion candidates come from
jedi plugin. To quickly see
where the list of strings is coming from, I've examined ac-sources
variable and saw that it contains ac-source-jedi-direct
, which evaluates to:
((candidates . jedi:ac-direct-matches)
(prefix . jedi:ac-direct-prefix)
(init . jedi:complete-request)
(requires . -1))
This means that jedi:complete-request
has to be called at point,
followed by jedi:ac-direct-matches
to obtain the list of strings. So
basically, this code:
(progn
(deferred:sync!
(jedi:complete-request))
(jedi:ac-direct-matches))
Note the call to deferred:sync!
. I had to add that one to make sure
that jedi:complete-request
completes before jedi:ac-direct-matches
is called.
Testing with some Python code, where |
is the point:
params = {"foo":"bar"}
params.i
The Elisp code above will return:
(#("items"
0 5 (summary
"function: __builtin__.dict.items"
symbol
"f"
document
"items(self)
D.items() -> list of D's (key, value) pairs, as 2-tuples"))
#("iteritems"
0 9 (summary
"function: __builtin__.dict.iteritems"
symbol
"f"
document
"iteritems(self)
D.iteritems() -> an iterator over the (key, value) items of D"))
#("iterkeys"
0 8 (summary
"function: __builtin__.dict.iterkeys"
symbol
"f"
document
"iterkeys(self)
D.iterkeys() -> an iterator over the keys of D"))
#("itervalues"
0 10 (summary
"function: __builtin__.dict.itervalues"
symbol
"f"
document
"itervalues(self)
D.itervalues() -> an iterator over the values of D")))
So these strings come with the symbol documentation and symbol type encoded as string properties. After this, the rest of the Elisp code follows very easily, you can find it as part of counsel package on MELPA:
(defun counsel-jedi ()
"Python completion at point."
(interactive)
(let ((bnd (bounds-of-thing-at-point 'symbol)))
(if bnd
(progn
(setq counsel-completion-beg (car bnd))
(setq counsel-completion-end (cdr bnd)))
(setq counsel-completion-beg nil)
(setq counsel-completion-end nil)))
(deferred:sync!
(jedi:complete-request))
(ivy-read "Symbol name: " (jedi:ac-direct-matches)
:action #'counsel--py-action))
(defun counsel--py-action (symbol)
"Insert SYMBOL, erasing the previous one."
(when (stringp symbol)
(with-ivy-window
(when counsel-completion-beg
(delete-region
counsel-completion-beg
counsel-completion-end))
(setq counsel-completion-beg
(move-marker (make-marker) (point)))
(insert symbol)
(setq counsel-completion-end
(move-marker (make-marker) (point)))
(when (equal (get-text-property 0 'symbol symbol) "f")
(insert "()")
(setq counsel-completion-end
(move-marker (make-marker) (point)))
(backward-char 1)))))
Essentially, the last interesting part is (equal (get-text-property 0
'symbol symbol) "f")
which test if the string corresponds to a
function or not. The rest of the code just fiddles with symbol markers
with ensure that the previous symbol is erased before the new symbol
is inserted if you press C-M-n (ivy-next-line-and-call
),
or use ivy-resume
. Just to describe how this would be useful for the
Python code above: I call counsel-jedi
, followed by C-M-n
to get:
params = {"foo":"bar"}
params.iteritems(|)
Pressing C-M-n again will result in:
params = {"foo":"bar"}
params.iterkeys(|)
Once I'm satisfied with my selected candidate, I can press either
C-m or C-j or C-g. Most of the above
code can be used almost verbatim if you're a Helm fan and want to
implement helm-jedi
. Basically, the approach I described (going
through ac-sources
) can be used to implement alternative completion
in a lot of cases where auto-complete-mode
completion is already
available.
Finally, if you like the idea of auto-inserting parens with completion and are using C/C++, have a look at function-args - this package does that too, with Ivy, Ido and Helm as available back ends.