(or emacs irrelevant)

Complete Python symbols using Ivy

I 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.