;;; elpy.el --- Emacs Lisp Python Environment
|
|
|
|
;; Copyright (C) 2012, 2013 Jorgen Schaefer <forcer@forcix.cx>
|
|
|
|
;; Author: Jorgen Schaefer <forcer@forcix.cx>
|
|
;; URL: https://github.com/jorgenschaefer/elpy
|
|
;; Version: 1.0
|
|
|
|
;; This program 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.
|
|
|
|
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
|
|
;; The Emacs Lisp Python Environment in Emacs
|
|
|
|
;; Elpy is an Emacs package to bring powerful Python editing to Emacs.
|
|
;; It combines a number of existing Emacs packages, and uses one of a
|
|
;; selection of Python packages for code introspection.
|
|
|
|
;; To use, you need to install not only this package, but a few Python
|
|
;; packages as well. See the installation instructions on the wiki.
|
|
|
|
;; Documentation is available there as well.
|
|
|
|
;; https://github.com/jorgenschaefer/elpy/wiki
|
|
|
|
;;; Code:
|
|
|
|
(require 'auto-complete-config)
|
|
(require 'elpy-refactor)
|
|
(require 'find-file-in-project)
|
|
(require 'flymake)
|
|
(require 'highlight-indentation)
|
|
(require 'idomenu)
|
|
(require 'json)
|
|
(require 'nose)
|
|
(require 'python)
|
|
(require 'thingatpt)
|
|
(require 'virtualenv)
|
|
(require 'yasnippet)
|
|
|
|
|
|
;;;;;;;;;;;;;;;
|
|
;;; Elpy itself
|
|
|
|
(defgroup elpy nil
|
|
"The Emacs Lisp Python Environment."
|
|
:prefix "elpy-"
|
|
:group 'languages)
|
|
|
|
(defcustom elpy-rpc-python-command "python"
|
|
"The command to be used for the RPC backend."
|
|
:type 'string
|
|
:group 'elpy)
|
|
|
|
(defcustom elpy-rpc-backend nil
|
|
"Your preferred backend.
|
|
|
|
nil - Select a backend automatically.
|
|
rope - Use the Rope refactoring library. This will create
|
|
.ropeproject directories in your project roots.
|
|
jedi - Use the Jedi completion library.
|
|
native - Do not use any backend, use native Python methods only."
|
|
:type '(choice (const "rope")
|
|
(const "jedi")
|
|
(const "native")
|
|
(const nil))
|
|
:group 'elpy)
|
|
|
|
(defcustom elpy-default-minor-modes '(eldoc-mode
|
|
flymake-mode
|
|
highlight-indentation-mode
|
|
yas-minor-mode
|
|
auto-complete-mode)
|
|
"Minor modes enabled when `elpy-mode' is enabled."
|
|
:group 'elpy)
|
|
|
|
(defcustom elpy-mode-hook nil
|
|
"Hook run when `elpy-mode' is enabled."
|
|
:group 'elpy)
|
|
|
|
(defvar elpy-mode-map
|
|
(let ((map (make-sparse-keymap)))
|
|
;; Alphabetical order to make it easier to find free C-c C-X
|
|
;; bindings in the future. Heh.
|
|
|
|
;; (define-key map (kbd "<backspace>") 'python-indent-dedent-line-backspace)
|
|
;; (define-key map (kbd "<backtab>") 'python-indent-dedent-line)
|
|
;; (define-key map (kbd "<tab>") 'ac-trigger-key)
|
|
|
|
;; (define-key map (kbd "C-M-x") 'python-shell-send-defun)
|
|
;; (define-key map (kbd "C-c <") 'python-indent-shift-left)
|
|
;; (define-key map (kbd "C-c >") 'python-indent-shift-right)
|
|
(define-key map (kbd "C-c C-c") 'elpy-shell-send-region-or-buffer)
|
|
(define-key map (kbd "C-c C-d") 'elpy-doc)
|
|
(define-key map (kbd "C-c C-f") 'find-file-in-project)
|
|
;; (define-key map (kbd "C-c C-i") 'yasnippet-expand)
|
|
(define-key map (kbd "C-c C-j") 'idomenu)
|
|
(define-key map (kbd "C-c C-n") 'elpy-flymake-forward-error)
|
|
(define-key map (kbd "C-c C-o") 'elpy-occur-definitions)
|
|
(define-key map (kbd "C-c C-p") 'elpy-flymake-backward-error)
|
|
(define-key map (kbd "C-c C-q") 'elpy-show-defun)
|
|
(define-key map (kbd "C-c C-r") 'elpy-refactor)
|
|
(define-key map (kbd "C-c C-s") 'elpy-rgrep-symbol)
|
|
(define-key map (kbd "C-c C-t") 'elpy-test)
|
|
(define-key map (kbd "C-c C-v") 'elpy-check)
|
|
(define-key map (kbd "C-c C-w") 'elpy-doc-websearch)
|
|
;; (define-key map (kbd "C-c C-z") 'python-shell-switch-to-shell)
|
|
|
|
(define-key map (kbd "<C-down>") 'elpy-forward-definition)
|
|
(define-key map (kbd "<C-up>") 'elpy-backward-definition)
|
|
;; (define-key map (kbd "M-,") 'iedit-mode
|
|
(define-key map (kbd "M-.") 'elpy-goto-definition)
|
|
(define-key map (kbd "M-a") 'elpy-nav-backward-statement)
|
|
(define-key map (kbd "M-e") 'elpy-nav-forward-statement)
|
|
(define-key map (kbd "M-n") 'elpy-forward-definition)
|
|
(define-key map (kbd "M-p") 'elpy-backward-definition)
|
|
|
|
map)
|
|
"Key map for the Emacs Lisp Python Environment.")
|
|
|
|
;;;###autoload
|
|
(defun elpy-enable (&optional skip-initialize-variables)
|
|
"Enable Elpy in all future Python buffers.
|
|
|
|
When SKIP-INITIALIZE-VARIABLES is non-nil, this will NOT call
|
|
`elpy-initialize-variables' to configure various modes in a way
|
|
that the Elpy author considers sensible. If you'd rather
|
|
configure those modes yourself, pass t here."
|
|
(interactive)
|
|
(when (< emacs-major-version 24)
|
|
(error "Elpy requires Emacs 24 or newer"))
|
|
(add-hook 'python-mode-hook 'elpy-mode)
|
|
(when (not skip-initialize-variables)
|
|
(elpy-initialize-variables)))
|
|
|
|
;;;###autoload
|
|
(defun elpy-disable ()
|
|
"Disable Elpy in all future Python buffers."
|
|
(interactive)
|
|
(remove-hook 'python-mode-hook 'elpy-mode))
|
|
|
|
;;;###autoload
|
|
(define-minor-mode elpy-mode
|
|
"Minor mode in Python buffers for the Emacs Lisp Python Environment.
|
|
|
|
This mode fully supports virtualenvs. Once you switch a
|
|
virtualenv using \\[virtualenv-workon], you can use
|
|
\\[elpy-rpc-restart] to make the elpy Python process use your
|
|
virtualenv.
|
|
|
|
See https://github.com/jorgenschaefer/elpy/wiki/Keybindings for a
|
|
more structured list.
|
|
|
|
\\{elpy-mode-map}"
|
|
:lighter " Elpy"
|
|
(when (not (eq major-mode 'python-mode))
|
|
(error "Elpy only works with `python-mode'"))
|
|
(cond
|
|
(elpy-mode
|
|
(when buffer-file-name
|
|
(setq ffip-project-root (elpy-project-root)))
|
|
(set (make-local-variable 'eldoc-documentation-function)
|
|
'elpy-eldoc-documentation)
|
|
(add-to-list 'ac-sources 'ac-source-elpy)
|
|
(add-to-list 'ac-sources 'ac-source-elpy-dot)
|
|
(add-hook 'before-save-hook 'elpy-rpc-before-save nil t)
|
|
(add-hook 'after-save-hook 'elpy-rpc-after-save nil t)
|
|
;; Enable modes, hence the 1.
|
|
(run-hook-with-args 'elpy-default-minor-modes 1))
|
|
(t
|
|
(setq ffip-project-root nil)
|
|
(kill-local-variable 'eldoc-documentation-function)
|
|
(setq ac-sources
|
|
(delq 'ac-source-elpy
|
|
(delq 'ac-source-elpy-dot
|
|
ac-sources)))
|
|
(remove-hook 'before-save-hook 'elpy-rpc-before-save t)
|
|
(remove-hook 'after-save-hook 'elpy-rpc-after-save t))))
|
|
|
|
(defun elpy-installation-instructions (message &optional show-elpy-module)
|
|
"Display a window with installation instructions for the Python
|
|
side of elpy.
|
|
|
|
MESSAGE is shown as the first paragraph.
|
|
|
|
If SHOW-ELPY-MODULE is non-nil, the help buffer will first
|
|
explain how to install the elpy module."
|
|
(with-help-window "*Elpy Installation*"
|
|
(with-current-buffer "*Elpy Installation*"
|
|
(let ((inhibit-read-only t))
|
|
(erase-buffer)
|
|
(insert "Elpy Installation Instructions\n")
|
|
(insert "\n")
|
|
(insert message)
|
|
(when (not (bolp))
|
|
(insert "\n"))
|
|
(insert "\n")
|
|
(when (and (boundp 'elpy-rpc-buffer)
|
|
elpy-rpc-buffer)
|
|
(let ((elpy-rpc-output (with-current-buffer elpy-rpc-buffer
|
|
(buffer-string))))
|
|
(when (not (equal elpy-rpc-output ""))
|
|
(insert (format "The contents of the %s buffer "
|
|
(buffer-name elpy-rpc-buffer))
|
|
"might provide further information "
|
|
"on the problem.\n")
|
|
(insert "\n"))))
|
|
(when show-elpy-module
|
|
(insert "Elpy requires the Python module \"elpy\". The module "
|
|
"is available from pypi, so you can install it using "
|
|
"the following command:\n")
|
|
(insert "\n")
|
|
(elpy-installation-command "elpy")
|
|
(insert "\n"))
|
|
(insert "To find possible completions, Elpy uses one of two "
|
|
"Python modules. Either \"rope\" or \"jedi\". To use "
|
|
"Elpy to its fullest potential, you should install "
|
|
"either one of them. Which one is a matter of taste. "
|
|
"You can try both and even switch at runtime using "
|
|
"M-x elpy-set-backend.\n")
|
|
(insert "\n")
|
|
(insert "Elpy also uses the Rope module for refactoring options, "
|
|
"so you likely want to install it even if you use jedi "
|
|
"for completion.\n")
|
|
(insert "\n")
|
|
(if (string-match "Python 3" (shell-command-to-string
|
|
"python --version"))
|
|
(elpy-installation-command "rope_py3k")
|
|
(elpy-installation-command "rope"))
|
|
(insert "\n")
|
|
(elpy-installation-command "jedi")
|
|
(insert "\n")
|
|
(insert "If you are using virtualenvs, you can use "
|
|
"M-x virtualenv-workon command to switch to a virtualenv "
|
|
"of your choice. Afterwards, running the command "
|
|
"M-x elpy-rpc-restart will use the packages in "
|
|
"that virtualenv.")
|
|
(fill-region (point-min) (point-max))))))
|
|
|
|
(defun elpy-installation-command (python-module)
|
|
"Insert an installation command description for PYTHON-MODULE."
|
|
(let* ((do-user-install (not (or (getenv "VIRTUAL_ENV")
|
|
virtualenv-workon-session)))
|
|
(user-option (if do-user-install
|
|
"--user "
|
|
""))
|
|
(command (cond
|
|
((executable-find "pip")
|
|
(format "pip install %s%s" user-option python-module))
|
|
((executable-find "easy_install")
|
|
(format "easy_install %s%s" user-option python-module))
|
|
(t
|
|
nil))))
|
|
(if (not command)
|
|
(insert "... hm. It appears you have neither pip nor easy_install "
|
|
"available. You might want to get the python-pip or "
|
|
"or python-setuptools package.\n")
|
|
(insert-text-button "[run]"
|
|
'action (lambda (button)
|
|
(async-shell-command
|
|
(button-get button 'command)))
|
|
'command command)
|
|
(insert " " command "\n"))))
|
|
|
|
(defun elpy-initialize-variables ()
|
|
"This sets some variables in other modes we like to set.
|
|
|
|
If you want to configure your own keys, do so after this function
|
|
is called (usually from `elpy-enable'), or override this function
|
|
using (defalias 'elpy-initialize-variables 'identity)"
|
|
;; Local variables in `python-mode'. This is not removed when Elpy
|
|
;; is disabled, which can cause some confusion.
|
|
(add-hook 'python-mode-hook 'elpy-initialize-local-variables)
|
|
|
|
;; Flymake support using flake8, including warning faces.
|
|
(when (executable-find "flake8")
|
|
(setq python-check-command "flake8"))
|
|
|
|
;; `flymake-no-changes-timeout': The original value of 0.5 is too
|
|
;; short for Python code, as that will result in the current line to
|
|
;; be highlighted most of the time, and that's annoying. This value
|
|
;; might be on the long side, but at least it does not, in general,
|
|
;; interfere with normal interaction.
|
|
(setq flymake-no-changes-timeout 60)
|
|
|
|
;; `flymake-start-syntax-check-on-newline': This should be nil for
|
|
;; Python, as;; most lines with a colon at the end will mean the next
|
|
;; line is always highlighted as error, which is not helpful and
|
|
;; mostly annoying.
|
|
(setq flymake-start-syntax-check-on-newline nil)
|
|
|
|
;; `ac-trigger-key': TAB is a great trigger key. We also need to
|
|
;; tell auto-complete about the new trigger key. This is a bad
|
|
;; interface to set the trigger key. Don't do this. Just let the
|
|
;; user set the key in the keymap. Stop second-guessing the user, or
|
|
;; Emacs.
|
|
(setq ac-trigger-key "TAB")
|
|
(when (fboundp 'ac-set-trigger-key)
|
|
(ac-set-trigger-key ac-trigger-key))
|
|
|
|
;; `ac-auto-show-menu': Short timeout because the menu is great.
|
|
(setq ac-auto-show-menu 0.4)
|
|
|
|
;; `ac-quick-help-delay': I'd like to show the menu right with the
|
|
;; completions, but this value should be greater than
|
|
;; `ac-auto-show-menu' to show help for the first entry as well.
|
|
(setq ac-quick-help-delay 0.5)
|
|
|
|
;; Fix some key bindings in ac completions. Using RET when a
|
|
;; completion is offered is not usually intended to complete (use
|
|
;; TAB for that), but done while typing and the inputer is considere
|
|
;; complete, with the intent to simply leave it as is and go to the
|
|
;; next line. Much like space will not complete, but leave it as is
|
|
;; and insert a space.
|
|
(define-key ac-completing-map (kbd "RET") nil)
|
|
(define-key ac-completing-map (kbd "<return>") nil)
|
|
|
|
;; `yas-trigger-key': TAB, as is the default, conflicts with the
|
|
;; autocompletion. We also need to tell yasnippet about the new
|
|
;; binding. This is a bad interface to set the trigger key. Stop
|
|
;; doing this.
|
|
(let ((old (when (boundp 'yas-trigger-key)
|
|
yas-trigger-key)))
|
|
(setq yas-trigger-key "C-c C-i")
|
|
(when (fboundp 'yas--trigger-key-reload)
|
|
(yas--trigger-key-reload old))))
|
|
|
|
(defun elpy-initialize-local-variables ()
|
|
"Initialize local variables in python-mode.
|
|
|
|
This should be run from `python-mode-hook'."
|
|
;; Set `forward-sexp-function' to nil in python-mode. See
|
|
;; http://debbugs.gnu.org/db/13/13642.html
|
|
(setq forward-sexp-function nil)
|
|
;; Enable warning faces for flake8 output.
|
|
(when (string-match "flake8" python-check-command)
|
|
(set (make-local-variable 'flymake-warning-re) "^W[0-9]"))
|
|
)
|
|
|
|
(defvar elpy-project-root 'not-initialized
|
|
"The root of the project the current buffer is in.")
|
|
(make-variable-buffer-local 'elpy-project-root)
|
|
|
|
(defun elpy-project-root ()
|
|
"Return the root of the current buffer's project.
|
|
|
|
You can set the variable `elpy-project-root' in, for example,
|
|
.dir-locals.el to configure this."
|
|
(when (eq elpy-project-root 'not-initialized)
|
|
;; Set it to nil so when the user runs C-g on the project root
|
|
;; prompt, it's set to "no project root".
|
|
(setq elpy-project-root nil)
|
|
(setq elpy-project-root
|
|
(or (elpy-project-find-root)
|
|
(read-directory-name "Project root: "
|
|
default-directory)))
|
|
(when (and (not (file-directory-p elpy-project-root))
|
|
(y-or-n-p "Directory does not exist, create? "))
|
|
(make-directory elpy-project-root t)))
|
|
elpy-project-root)
|
|
|
|
(defun elpy-project-find-root ()
|
|
"Find an appropriate project root for the current buffer.
|
|
|
|
If no root directory is found, nil is returned."
|
|
(or ;; (getenv "PROJECT_HOME")
|
|
(locate-dominating-file default-directory
|
|
'elpy-project-root-p)
|
|
(elpy-project-find-library-root t)
|
|
(read-directory-name "Project root: "
|
|
nil nil t)))
|
|
|
|
(defun elpy-project-root-p (dir)
|
|
"Return true iff the given directory is a project root."
|
|
(or (file-exists-p (format "%s/.git" dir))
|
|
(file-exists-p (format "%s/.hg" dir))
|
|
(file-exists-p (format "%s/.ropeproject" dir))
|
|
(file-exists-p (format "%s/setup.py" dir))
|
|
(and (file-exists-p (format "%s/.svn" dir))
|
|
(not (file-exists-p (format "%s/../.svn" dir))))))
|
|
|
|
(defun elpy-project-find-library-root (&optional skip-current-directory)
|
|
"Find the first directory in the tree not containing an __init__.py
|
|
|
|
If there is no __init__.py in the current directory, return the
|
|
current directory unless SKIP-CURRENT-DIRECTORY is non-nil."
|
|
(cond
|
|
((file-exists-p (format "%s/__init__.py" default-directory))
|
|
(locate-dominating-file default-directory
|
|
(lambda (dir)
|
|
(not (file-exists-p
|
|
(format "%s/__init__.py" dir))))))
|
|
((not skip-current-directory)
|
|
default-directory)
|
|
(t
|
|
nil)))
|
|
|
|
(defun elpy-set-project-root (new-root)
|
|
"Set the Elpy project root to NEW-ROOT."
|
|
(interactive "DNew project root: ")
|
|
(setq elpy-project-root new-root))
|
|
|
|
(defun elpy-use-ipython ()
|
|
"Set defaults to use IPython instead of the standard interpreter."
|
|
(interactive)
|
|
(if (boundp 'python-python-command)
|
|
;; Emacs 24 until 24.3
|
|
(setq python-python-command "ipython")
|
|
;; Emacs 24.3 and onwards.
|
|
|
|
;; This is from the python.el commentary.
|
|
;; Settings for IPython 0.11:
|
|
(setq python-shell-interpreter "ipython"
|
|
python-shell-interpreter-args ""
|
|
python-shell-prompt-regexp "In \\[[0-9]+\\]: "
|
|
python-shell-prompt-output-regexp "Out\\[[0-9]+\\]: "
|
|
python-shell-completion-setup-code
|
|
"from IPython.core.completerlib import module_completion"
|
|
python-shell-completion-module-string-code
|
|
"';'.join(module_completion('''%s'''))\n"
|
|
python-shell-completion-string-code
|
|
"';'.join(get_ipython().Completer.all_completions('''%s'''))\n")))
|
|
|
|
(defun elpy-use-cpython ()
|
|
"Set defaults to use the standard interpreter instead of IPython."
|
|
(interactive)
|
|
(if (boundp 'python-python-command)
|
|
;; Emacs 24 until 24.3
|
|
(setq python-python-command "python")
|
|
;; Emacs 24.3 and onwards.
|
|
(setq python-shell-interpreter "python"
|
|
python-shell-interpreter-args "-i"
|
|
python-shell-prompt-regexp ">>> "
|
|
python-shell-prompt-output-regexp ""
|
|
python-shell-completion-setup-code
|
|
"try:
|
|
import readline
|
|
except ImportError:
|
|
def __COMPLETER_all_completions(text): []
|
|
else:
|
|
import rlcompleter
|
|
readline.set_completer(rlcompleter.Completer().complete)
|
|
def __COMPLETER_all_completions(text):
|
|
import sys
|
|
completions = []
|
|
try:
|
|
i = 0
|
|
while True:
|
|
res = readline.get_completer()(text, i)
|
|
if not res: break
|
|
i += 1
|
|
completions.append(res)
|
|
except NameError:
|
|
pass
|
|
return completions"
|
|
python-shell-completion-module-string-code ""
|
|
python-shell-completion-string-code
|
|
"';'.join(__COMPLETER_all_completions('''%s'''))\n")))
|
|
|
|
(defun elpy-clean-modeline ()
|
|
"Clean up the mode line by removing some lighters.
|
|
|
|
It's not necessary to see (Python Elpy yas AC ElDoc) all the
|
|
time. Honestly."
|
|
(interactive)
|
|
(setq eldoc-minor-mode-string nil)
|
|
(dolist (mode '(elpy-mode yas-minor-mode auto-complete-mode
|
|
flymake-mode))
|
|
(setcdr (assq mode minor-mode-alist)
|
|
(list ""))))
|
|
|
|
(defun elpy-shell-send-region-or-buffer ()
|
|
"Send the active region or the buffer to the Python shell.
|
|
|
|
If there is an active region, send that. Otherwise, send the
|
|
whole buffer."
|
|
(interactive)
|
|
(if (region-active-p)
|
|
(python-shell-send-region (region-beginning)
|
|
(region-end))
|
|
(python-shell-send-buffer)))
|
|
|
|
(defun elpy-check ()
|
|
"Run `python-check-command' on the current buffer's file."
|
|
(interactive)
|
|
(when (not (buffer-file-name))
|
|
(error "Can't check a buffer without a file."))
|
|
(save-some-buffers (not compilation-ask-about-save) nil)
|
|
(let ((process-environment (python-shell-calculate-process-environment))
|
|
(exec-path (python-shell-calculate-exec-path)))
|
|
(compilation-start (concat python-check-command
|
|
" "
|
|
(shell-quote-argument (buffer-file-name)))
|
|
nil (lambda (mode-name)
|
|
"*Python Check*"))))
|
|
|
|
(defun elpy-show-defun ()
|
|
"Show the current class and method, in case they are not on
|
|
screen."
|
|
(interactive)
|
|
(let ((function (python-info-current-defun)))
|
|
(if function
|
|
(message "%s()" function)
|
|
(message "Not in a function"))))
|
|
|
|
(defun elpy-goto-definition ()
|
|
"Go to the definition of the symbol at point, if found."
|
|
(interactive)
|
|
(let ((location (elpy-rpc-get-definition)))
|
|
(if location
|
|
(elpy-goto-location (car location) (cadr location))
|
|
(error "No definition found"))))
|
|
|
|
(defun elpy-goto-location (filename offset)
|
|
"Show FILENAME at OFFSET to the user."
|
|
(let ((buffer (find-file filename)))
|
|
(with-current-buffer buffer
|
|
(with-selected-window (get-buffer-window buffer)
|
|
(goto-char (1+ offset))))))
|
|
|
|
(defun elpy-nav-forward-statement ()
|
|
"Move forward one statement.
|
|
|
|
This will go to the end of the current statement, or the end of
|
|
the next one if already at the end."
|
|
(interactive)
|
|
(let ((old (point)))
|
|
(python-nav-end-of-statement)
|
|
(when (= old (point))
|
|
(python-nav-forward-statement)
|
|
(python-nav-end-of-statement))))
|
|
|
|
(defun elpy-nav-backward-statement ()
|
|
"Move backward one statement.
|
|
|
|
This will go to the beginning of the current statement, or the
|
|
beginning of the previous one if already at the beginning."
|
|
(interactive)
|
|
(let ((old (point)))
|
|
(python-nav-beginning-of-statement)
|
|
(when (= old (point))
|
|
(python-nav-backward-statement))))
|
|
|
|
(defun elpy-forward-definition ()
|
|
"Move forward to the next definition (class or function)."
|
|
(interactive)
|
|
(if (save-excursion
|
|
(forward-char 1)
|
|
(re-search-forward "^ *\\(def\\|class\\) " nil t))
|
|
(goto-char (match-beginning 1))
|
|
(goto-char (point-max))))
|
|
|
|
(defun elpy-backward-definition ()
|
|
"Move backward to the previous definition (class or function)."
|
|
(interactive)
|
|
(if (save-excursion
|
|
(forward-char -1)
|
|
(re-search-backward "^ *\\(def\\|class\\) " nil t))
|
|
(goto-char (match-beginning 1))
|
|
(goto-char (point-min))))
|
|
|
|
(defun elpy-occur-definitions ()
|
|
"Display an occur buffer of all definitions in the current buffer.
|
|
|
|
Also, switch to that buffer."
|
|
(interactive)
|
|
(let ((list-matching-lines-face nil))
|
|
(occur "^ *\\(def\\|class\\) "))
|
|
(let ((window (get-buffer-window "*Occur*")))
|
|
(if window
|
|
(select-window window)
|
|
(switch-to-buffer "*Occur*"))))
|
|
|
|
(defun elpy-rgrep-symbol (symbol)
|
|
"Search for SYMBOL in the current project.
|
|
|
|
SYMBOL defaults to the symbol at point, or the current region if
|
|
active.
|
|
|
|
With a prefix argument, prompt for a string to search for."
|
|
(interactive
|
|
(list
|
|
(cond
|
|
(current-prefix-arg
|
|
(read-from-minibuffer "Search for symbol: "))
|
|
((use-region-p)
|
|
(buffer-substring-no-properties (region-beginning)
|
|
(region-end)))
|
|
(t
|
|
(or (thing-at-point 'symbol)
|
|
(read-from-minibuffer "Search for symbol: "))))))
|
|
(grep-compute-defaults)
|
|
(rgrep (format "\\b%s\\b" symbol)
|
|
"*.py"
|
|
(elpy-project-root))
|
|
(with-current-buffer next-error-last-buffer
|
|
(let ((inhibit-read-only t))
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(when (re-search-forward "^find .*" nil t)
|
|
(replace-match (format "\\1\nSearching for symbol %s\n"
|
|
symbol)))))))
|
|
|
|
(defun elpy-test (&optional arg)
|
|
"Run nosetests on the current project.
|
|
|
|
With no prefix arg, all tests are run.
|
|
With one prefix arg, only the current test is run.
|
|
With two prefix args, only the current module is run."
|
|
(interactive "p")
|
|
(save-some-buffers)
|
|
(cond
|
|
((>= arg 16) (nosetests-module))
|
|
((>= arg 4) (nosetests-one))
|
|
(t (nosetests-all))))
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;
|
|
;;; Documentation
|
|
|
|
(defvar elpy-doc-history nil
|
|
"History for the `elpy-doc' command.")
|
|
|
|
(defun elpy-doc-websearch (what)
|
|
"Search the Python web documentation for the string WHAT."
|
|
(interactive
|
|
(list (read-from-minibuffer "Search Python.org for: "
|
|
(symbol-name (symbol-at-point)))))
|
|
(browse-url
|
|
(format "https://www.google.com/search?q=site:docs.python.org%%20%s"
|
|
what)))
|
|
|
|
(defun elpy-doc (&optional use-pydoc-p symbol)
|
|
"Show documentation on the thing at point.
|
|
|
|
If USE-PYDOC is non-nil (interactively, when a prefix argument is
|
|
given), use pydoc on the symbol SYMBOL (interactively, the symbol
|
|
at point). With a single prefix argument, the user gets a
|
|
completion interface for possible symbols. With two prefix
|
|
arguments, the interface simply asks for a string."
|
|
(interactive
|
|
(list current-prefix-arg
|
|
(let ((initial (with-syntax-table python-dotty-syntax-table
|
|
(let ((symbol (symbol-at-point)))
|
|
(if symbol
|
|
(symbol-name symbol)
|
|
nil)))))
|
|
(cond
|
|
((and initial (not current-prefix-arg))
|
|
initial)
|
|
((equal current-prefix-arg '(16))
|
|
;; C-u C-u
|
|
(read-from-minibuffer "Pydoc: " initial nil nil
|
|
'elpy-doc-history))
|
|
(t
|
|
(elpy-ido-recursive-completing-read "Pydoc: "
|
|
'elpy-pydoc-completions
|
|
"."
|
|
t
|
|
initial
|
|
'elpy-doc-history))))))
|
|
(let ((doc (if use-pydoc-p
|
|
(elpy-rpc-get-pydoc-documentation symbol)
|
|
(or (elpy-rpc-get-docstring)
|
|
;; This will get the right position for
|
|
;; multiprocessing.Queue(quxqux_|_)
|
|
(ignore-errors
|
|
(save-excursion
|
|
(elpy-nav-backward-statement)
|
|
(with-syntax-table python-dotty-syntax-table
|
|
(forward-symbol 1)
|
|
(backward-char 1))
|
|
(elpy-rpc-get-docstring)))))))
|
|
(if doc
|
|
(with-help-window "*Python Doc*"
|
|
(with-current-buffer "*Python Doc*"
|
|
(erase-buffer)
|
|
(insert doc)
|
|
(goto-char (point-min))
|
|
(while (re-search-forward "\\(.\\)\\1" nil t)
|
|
(replace-match (propertize (match-string 1)
|
|
'face 'bold)
|
|
t t))))
|
|
(message "No documentation available."))))
|
|
|
|
(defun elpy-pydoc-completions (rcr-prefix)
|
|
"Return a list of modules available in pydoc starting with RCR-PREFIX."
|
|
(sort (if (or (not rcr-prefix)
|
|
(equal rcr-prefix ""))
|
|
(elpy-rpc "get_pydoc_completions")
|
|
(elpy-rpc "get_pydoc_completions" rcr-prefix))
|
|
(lambda (a b)
|
|
(if (and (string-prefix-p "_" b)
|
|
(not (string-prefix-p "_" a)))
|
|
t
|
|
(string< (downcase a)
|
|
(downcase b))))))
|
|
|
|
|
|
;;;;;;;;;;;;
|
|
;;; elpy-ido
|
|
|
|
;; This is a wrapper around ido-completing-read, which does not
|
|
;; provide for recursive reads by default.
|
|
|
|
(defvar elpy-ido-rcr-choice-function nil
|
|
"Internal variable for `elpy-ido-recursive-completing-read'.
|
|
|
|
Don't touch. Won't help.")
|
|
|
|
(defvar elpy-ido-rcr-selection nil
|
|
"Internal variable for `elpy-ido-recursive-completing-read'.
|
|
|
|
Don't touch. Won't help.")
|
|
|
|
(defvar elpy-ido-rcr-separator nil
|
|
"Internal variable for `elpy-ido-recursive-completing-read'.
|
|
|
|
Don't touch. Won't help.")
|
|
|
|
(defvar elpy-ido-rcr-choices nil
|
|
"Internal variable for `elpy-ido-recursive-completing-read'.
|
|
|
|
Don't touch. Won't help.")
|
|
|
|
(defun elpy-ido--rcr-selected ()
|
|
"Return the currently selected compound."
|
|
(mapconcat #'identity
|
|
(reverse elpy-ido-rcr-selection)
|
|
elpy-ido-rcr-separator))
|
|
|
|
(defun elpy-ido--rcr-setup-keymap ()
|
|
"Set up the ido keymap for `elpy-ido-recursive-completing-read'."
|
|
(define-key ido-completion-map (read-kbd-macro elpy-ido-rcr-separator)
|
|
'elpy-ido-rcr-complete)
|
|
(define-key ido-completion-map (kbd "DEL") 'elpy-ido-rcr-backspace))
|
|
|
|
(defun elpy-ido-rcr-complete ()
|
|
"Complete the current ido completion and attempt an extension."
|
|
(interactive)
|
|
(let* ((new (car ido-matches))
|
|
(full (concat (elpy-ido--rcr-selected)
|
|
elpy-ido-rcr-separator
|
|
new))
|
|
(choices (funcall elpy-ido-rcr-choice-function full)))
|
|
(when choices
|
|
(setq elpy-ido-rcr-choices choices
|
|
elpy-ido-rcr-selection (cons new elpy-ido-rcr-selection))
|
|
(throw 'continue t))))
|
|
|
|
(defun elpy-ido-rcr-backspace (&optional n)
|
|
"Delete the last character in the minibuffer.
|
|
|
|
If the minibuffer is empty, recurse to the last completion."
|
|
(interactive "p")
|
|
(if (= (minibuffer-prompt-end) (point))
|
|
(progn
|
|
(setq elpy-ido-rcr-selection (cdr elpy-ido-rcr-selection)
|
|
elpy-ido-rcr-choices (funcall elpy-ido-rcr-choice-function
|
|
(elpy-ido--rcr-selected)))
|
|
(throw 'continue t))
|
|
(delete-char (- n))))
|
|
|
|
(defun elpy-ido-recursive-completing-read (prompt choice-function
|
|
separator
|
|
&optional
|
|
require-match
|
|
initial-input
|
|
hist def)
|
|
"An alternative to `ido-completing-read' supporting recursive selection.
|
|
|
|
The CHOICE-FUNCTION is called with a prefix string and should
|
|
find all possible selections with this prefix. The user is then
|
|
prompted with those options. When the user hits RET, the
|
|
currently selected option is returned. When the user hits the
|
|
SEPARATOR key, though, the currently selected option is appended,
|
|
with the separator, to the selected prefix, and the user is
|
|
prompted for further completions returned by CHOICE-FUNCTION.
|
|
|
|
For REQUIRE-MATCH, INITIAL-INPUT, HIST and DEF, see
|
|
`completing-read'."
|
|
(let ((ido-setup-hook (cons 'elpy-ido--rcr-setup-keymap
|
|
ido-setup-hook))
|
|
(elpy-ido-rcr-choice-function choice-function)
|
|
(elpy-ido-rcr-separator separator)
|
|
elpy-ido-rcr-choices
|
|
elpy-ido-rcr-selection)
|
|
(when initial-input
|
|
(let ((parts (reverse (split-string initial-input
|
|
(regexp-quote separator)))))
|
|
(setq initial-input (car parts)
|
|
elpy-ido-rcr-selection (cdr parts))))
|
|
(setq elpy-ido-rcr-choices (funcall choice-function
|
|
(elpy-ido--rcr-selected)))
|
|
(catch 'return
|
|
(while t
|
|
(catch 'continue
|
|
(throw 'return
|
|
(let ((completion (ido-completing-read
|
|
(concat prompt
|
|
(elpy-ido--rcr-selected)
|
|
(if elpy-ido-rcr-selection
|
|
elpy-ido-rcr-separator
|
|
""))
|
|
elpy-ido-rcr-choices
|
|
nil require-match
|
|
initial-input hist def)))
|
|
(concat
|
|
(mapconcat (lambda (element)
|
|
(concat element elpy-ido-rcr-separator))
|
|
(reverse elpy-ido-rcr-selection)
|
|
"")
|
|
completion))))
|
|
;; after the first run, we don't want initial and default
|
|
;; anymore.
|
|
(setq initial-input nil
|
|
def nil)))))
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;
|
|
;;; elpy-rpc backends
|
|
|
|
;; elpy-rpc is a simple JSON-based RPC protocol. It's mostly JSON-RPC
|
|
;; 1.0, except we do not implement the full protocol as we do not need
|
|
;; all the features. Emacs starts a Python subprocess which runs a
|
|
;; special module. The module reads JSON-RPC requests and responds
|
|
;; with JSON-RPC responses.
|
|
|
|
(defvar elpy-rpc-call-id 0
|
|
"Call id for the current elpy-rpc call.
|
|
|
|
See `elpy-rpc-call'.")
|
|
(make-variable-buffer-local 'elpy-rpc-call-id)
|
|
|
|
(defvar elpy-rpc-buffer-p nil
|
|
"True iff the current buffer is an elpy-rpc buffer.")
|
|
(make-variable-buffer-local 'elpy-rpc-buffer-p)
|
|
|
|
(defvar elpy-rpc-buffer nil
|
|
"The global elpy-rpc buffer.")
|
|
|
|
(defun elpy-rpc (method-name &rest params)
|
|
"Run an elpy-rpc method on the elpy-rpc process."
|
|
(elpy-rpc-ensure-open)
|
|
(with-current-buffer elpy-rpc-buffer
|
|
(apply 'elpy-rpc-call method-name params)))
|
|
|
|
(defun elpy-rpc-ensure-open ()
|
|
"Ensure that the global elpy-rpc subprocess is active."
|
|
(when (not (and elpy-rpc-buffer
|
|
(get-buffer-process elpy-rpc-buffer)
|
|
(process-live-p (get-buffer-process elpy-rpc-buffer))))
|
|
(when elpy-rpc-buffer
|
|
(kill-buffer elpy-rpc-buffer))
|
|
(condition-case err
|
|
(setq elpy-rpc-buffer
|
|
(elpy-rpc-open "*elpy-rpc*"
|
|
elpy-rpc-python-command "-m" "elpy.__main__"))
|
|
(error
|
|
(elpy-installation-instructions
|
|
(format "Could not start the Python subprocess: %s"
|
|
(cadr err))
|
|
t)
|
|
(error (cadr err))))
|
|
(cond
|
|
;; User requested a backend that's not installed
|
|
(elpy-rpc-backend
|
|
(when (not (member elpy-rpc-backend (elpy-rpc-get-available-backends)))
|
|
(elpy-installation-instructions
|
|
(format (concat "The %s backend is unavailable. "
|
|
"Please install the appropriate Python library.")
|
|
elpy-rpc-backend))
|
|
(error (format "Backend %s not found" elpy-rpc-backend)))
|
|
(elpy-rpc-set-backend elpy-rpc-backend))
|
|
;; User did not specifically request the native backend, but it's
|
|
;; chosen by default.
|
|
((and (not elpy-rpc-backend)
|
|
(equal "native" (elpy-rpc-get-backend)))
|
|
(elpy-installation-instructions
|
|
(concat "Only the basic native backend is available. "
|
|
"You might want to install an appropriate "
|
|
"Python library. If you are happy with the native "
|
|
"backend, please add the following to your .emacs:"
|
|
"\n\n(setq elpy-rpc-backend \"native\")"))))))
|
|
|
|
(defun elpy-rpc-restart ()
|
|
"Restart the elpy-rpc subprocess if it is running.
|
|
|
|
Actually, just closes the elpy-rpc buffer"
|
|
(interactive)
|
|
(when elpy-rpc-buffer
|
|
(kill-buffer elpy-rpc-buffer)
|
|
(setq elpy-rpc-buffer nil)))
|
|
|
|
(defun elpy-rpc-open (name program &rest program-args)
|
|
"Start a new elpy-rpc subprocess.
|
|
|
|
NAME is a suggested name for the buffer and the name for the
|
|
process. The process will be PROGRAM called with PROGRAM-ARGS as
|
|
arguments.
|
|
|
|
This function returns the buffer created to communicate with
|
|
elpy-rpc. This buffer needs to be the current buffer for
|
|
subsequent calls to `elpy-rpc-call'."
|
|
(let* ((buffer (generate-new-buffer name))
|
|
;; Leaving process-connection-type non-nil can truncate
|
|
;; communication
|
|
(proc (let ((process-connection-type nil)
|
|
(default-directory "/"))
|
|
(apply #'start-process name buffer program program-args))))
|
|
(set-process-query-on-exit-flag proc nil)
|
|
(with-current-buffer buffer
|
|
(setq elpy-rpc-buffer-p t)
|
|
(let ((line (elpy-rpc--receive-line)))
|
|
(cond
|
|
((equal line "elpy-rpc ready")
|
|
buffer)
|
|
((string-match "No module named \\(.*\\)" line)
|
|
(goto-char (point-min))
|
|
(insert line "\n")
|
|
(set-marker (process-mark proc) (point))
|
|
(error (format "The Python module %s is not installed"
|
|
(match-string 1 line))))
|
|
(t
|
|
(goto-char (point-min))
|
|
(insert line "\n")
|
|
(set-marker (process-mark proc) (point))
|
|
(error "Unknown output from Python elpy-rpc")))))))
|
|
|
|
(defun elpy-rpc-call (method &rest params)
|
|
"Call the METHOD with PARAMS on the current RPC server.
|
|
|
|
Ths current buffer needs to be an elpy-rpc buffer."
|
|
(when (not elpy-rpc-buffer-p)
|
|
(error "`elpy-rpc-call' called outside of an RPC buffer"))
|
|
(erase-buffer)
|
|
(setq elpy-rpc-call-id (1+ elpy-rpc-call-id))
|
|
(elpy-rpc--send-json `((id . ,elpy-rpc-call-id)
|
|
(method . ,method)
|
|
(params . ,params)))
|
|
(let ((response (elpy-rpc--receive-json)))
|
|
(cond
|
|
((not (= elpy-rpc-call-id (cdr (assq 'id response))))
|
|
(error "Protocol desynchronization, restart subprocess"))
|
|
((cdr (assq 'error response))
|
|
(error (cdr (assq 'error response))))
|
|
(t
|
|
(cdr (assq 'result response))))))
|
|
|
|
(defun elpy-rpc--send-json (obj)
|
|
"Send an object encoded as JSON to the current process."
|
|
(process-send-string (get-buffer-process (current-buffer))
|
|
(format "%s\n" (json-encode obj))))
|
|
|
|
(defun elpy-rpc--receive-line ()
|
|
"Read a single line from the current process."
|
|
(let ((inhibit-quit nil))
|
|
(while (not (progn
|
|
(goto-char (point-min))
|
|
(re-search-forward "^\\(.*\\)\n" nil t)))
|
|
(accept-process-output)))
|
|
(let ((line (match-string 1)))
|
|
(replace-match "")
|
|
line))
|
|
|
|
(defun elpy-rpc--receive-json ()
|
|
"Read a single JSON object from the current process."
|
|
(let ((json-array-type 'list))
|
|
(json-read-from-string (elpy-rpc--receive-line))))
|
|
|
|
(defun elpy-rpc-get-completions ()
|
|
"Call the find_completions API function.
|
|
|
|
Returns a list of possible completions for the Python symbol at
|
|
point."
|
|
(when (elpy-project-root)
|
|
(elpy-rpc "get_completions"
|
|
(expand-file-name (elpy-project-root))
|
|
buffer-file-name
|
|
(buffer-string)
|
|
(- (point)
|
|
(point-min)))))
|
|
|
|
(defun elpy-rpc-get-calltip ()
|
|
"Call the get_calltip API function.
|
|
|
|
Returns a calltip string for the function call at point."
|
|
(when (elpy-project-root)
|
|
(elpy-rpc "get_calltip"
|
|
(expand-file-name (elpy-project-root))
|
|
buffer-file-name
|
|
(buffer-string)
|
|
(- (point)
|
|
(point-min)))))
|
|
|
|
(defun elpy-rpc-get-docstring ()
|
|
"Call the get_docstring API function.
|
|
|
|
Returns a possible multi-line docstring for the symbol at point."
|
|
(elpy-rpc "get_docstring"
|
|
(expand-file-name (elpy-project-root))
|
|
buffer-file-name
|
|
(buffer-string)
|
|
(- (point)
|
|
(point-min))))
|
|
|
|
(defun elpy-rpc-get-pydoc-documentation (symbol)
|
|
"Get the Pydoc documentation for SYMBOL.
|
|
|
|
Returns a possible multi-line docstring."
|
|
(elpy-rpc "get_pydoc_documentation" symbol))
|
|
|
|
(defun elpy-rpc-get-definition ()
|
|
"Call the find_definition API function.
|
|
|
|
Returns nil or a list of (filename, point)."
|
|
(elpy-rpc "get_definition"
|
|
(expand-file-name (elpy-project-root))
|
|
buffer-file-name
|
|
(buffer-string)
|
|
(- (point)
|
|
(point-min))))
|
|
|
|
(defun elpy-rpc-before-save ()
|
|
"Call the before_save API function.
|
|
|
|
Used for state keeping in the backend."
|
|
;; If there is no backend, we do not need to keep state.
|
|
(when elpy-rpc-buffer
|
|
(elpy-rpc "before_save"
|
|
(expand-file-name (elpy-project-root))
|
|
buffer-file-name)))
|
|
|
|
(defun elpy-rpc-after-save ()
|
|
"Call the after_save API function.
|
|
|
|
Used for state keeping in the backend."
|
|
;; If there is no backend, we do not need to keep state.
|
|
(when elpy-rpc-buffer
|
|
(elpy-rpc "before_save"
|
|
(expand-file-name (elpy-project-root))
|
|
buffer-file-name)))
|
|
|
|
(defun elpy-rpc-get-backend ()
|
|
"Call the get_backend API function.
|
|
|
|
Returns the name of the backend currently in use."
|
|
(elpy-rpc "get_backend"))
|
|
|
|
(defun elpy-rpc-get-available-backends ()
|
|
"Call the get_available_backends API function.
|
|
|
|
Returns a list of names of available backends, depending on which
|
|
Python libraries are installed."
|
|
(elpy-rpc "get_available_backends"))
|
|
|
|
(defun elpy-rpc-set-backend (backend)
|
|
"Call the set_backend API function.
|
|
|
|
This changes the current backend to the named backend. Raises an
|
|
error if the backend is not supported."
|
|
(elpy-rpc "set_backend" backend))
|
|
|
|
(defun elpy-set-backend (backend)
|
|
"Set the backend used by elpy."
|
|
(interactive
|
|
(list (completing-read
|
|
(format "Switch elpy backend (currently %s): "
|
|
(elpy-rpc-get-backend))
|
|
(elpy-rpc-get-available-backends)
|
|
nil t)))
|
|
(elpy-rpc-set-backend backend))
|
|
|
|
|
|
;;;;;;;;;
|
|
;;; Eldoc
|
|
|
|
(defun elpy-eldoc-documentation ()
|
|
"Return a call tip for the python call at point."
|
|
(let ((calltip (elpy-rpc-get-calltip)))
|
|
(when calltip
|
|
(with-temp-buffer
|
|
;; multiprocessing.queues.Queue.cancel_join_thread(self)
|
|
(insert calltip)
|
|
(goto-char (point-min))
|
|
;; First, remove the whole path up to the second-to-last dot. We
|
|
;; retain the class just to make it nicer.
|
|
(while (search-forward "." nil t)
|
|
nil)
|
|
(when (search-backward "." nil t 2)
|
|
(delete-region (point-min) (1+ (point))))
|
|
;; Then remove the occurrence of "self", that's not passed by
|
|
;; the user.
|
|
(when (re-search-forward "(self\\(, \\)?" nil t)
|
|
(replace-match "("))
|
|
(goto-char (point-min))
|
|
;; Lastly, we'd like to highlight the argument are on.
|
|
|
|
;; This is tricky with keyword vs. positions arguments, and
|
|
;; possibly quite complex argument values making calculation of
|
|
;; the current argument tricky.
|
|
|
|
;; Hence, we don't do anything for now.
|
|
(buffer-string)))))
|
|
|
|
|
|
;;;;;;;;;;;
|
|
;;; Flymake
|
|
|
|
(eval-after-load "flymake"
|
|
'(add-to-list 'flymake-allowed-file-name-masks
|
|
'("\\.py\\'" elpy-flymake-python-init)))
|
|
|
|
(defun elpy-flymake-python-init ()
|
|
;; Make sure it's not a remote buffer as flymake would not work
|
|
(when (not (file-remote-p buffer-file-name))
|
|
(let* ((temp-file (flymake-init-create-temp-buffer-copy
|
|
'flymake-create-temp-inplace)))
|
|
(list python-check-command (list temp-file)))))
|
|
|
|
(defun elpy-flymake-forward-error ()
|
|
"Move forward to the next Flymake error and show a
|
|
description."
|
|
(interactive)
|
|
(flymake-goto-next-error)
|
|
(elpy-flymake-show-error))
|
|
|
|
(defun elpy-flymake-backward-error ()
|
|
"Move backward to the previous Flymake error and show a
|
|
description."
|
|
(interactive)
|
|
(flymake-goto-prev-error)
|
|
(elpy-flymake-show-error))
|
|
|
|
(defun elpy-flymake-show-error ()
|
|
"Show the flymake error message at point."
|
|
(let* ((lineno (flymake-current-line-no))
|
|
(err-info (car (flymake-find-err-info flymake-err-info
|
|
lineno)))
|
|
(text (mapconcat #'flymake-ler-text
|
|
err-info
|
|
", ")))
|
|
(message "%s" text)))
|
|
|
|
|
|
;;;;;;;;
|
|
;;; nose
|
|
|
|
(eval-after-load "nose"
|
|
'(defalias 'nose-find-project-root 'elpy-project-find-library-root))
|
|
|
|
|
|
;;;;;;;;;;;;;
|
|
;;; Yasnippet
|
|
|
|
;; No added configuration needed. Nice mode. :o)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;
|
|
;;; Auto-Complete
|
|
|
|
(defvar elpy--ac-cache nil
|
|
"List of current expansions and docstrings.")
|
|
|
|
(defun elpy--ac-candidates ()
|
|
"Return a list of possible expansions at points.
|
|
|
|
This also initializes `elpy--ac-cache'."
|
|
(setq elpy--ac-cache nil)
|
|
(dolist (completion (condition-case err
|
|
(elpy-rpc-get-completions)
|
|
(error
|
|
(message "Getting completions: %s"
|
|
(cadr err))
|
|
nil)))
|
|
(let ((name (car completion))
|
|
(doc (cadr completion)))
|
|
(when (not (string-prefix-p "_" name))
|
|
(push (cons (concat ac-prefix name)
|
|
doc)
|
|
elpy--ac-cache))))
|
|
(mapcar #'car elpy--ac-cache))
|
|
|
|
(defun elpy--ac-document (name)
|
|
"Return the documentation for the symbol NAME."
|
|
(assoc-default name elpy--ac-cache))
|
|
|
|
(ac-define-source elpy
|
|
'((candidates . elpy--ac-candidates)
|
|
(symbol . "p")
|
|
(document . elpy--ac-document)
|
|
(cache . t)))
|
|
|
|
(ac-define-source elpy-dot
|
|
'((candidates . elpy--ac-candidates)
|
|
(symbol . "p")
|
|
(document . elpy--ac-document)
|
|
(cache . t)
|
|
(prefix . c-dot)
|
|
(requires . 0)))
|
|
|
|
|
|
;;;;;;;;;;;;;;
|
|
;;; Virtualenv
|
|
|
|
(defadvice virtualenv-workon (around ad-elpy-virtualenv-workon activate)
|
|
"Restart the elpy-rpc backend on virtualenv change."
|
|
(let ((old-env virtualenv-workon-session))
|
|
ad-do-it
|
|
(when (and virtualenv-workon-starts-python
|
|
elpy-rpc-buffer
|
|
(not (equal old-env virtualenv-workon-session))
|
|
(y-or-n-p "Virtualenv changed, restart Elpy-RPC? "))
|
|
(elpy-rpc-restart))))
|
|
|
|
(defadvice virtualenv-deactivate (after ad-elpy-virtualenv-deactivate activate)
|
|
"Restart the elpy-rpc backend on virtualenv change."
|
|
(when (and virtualenv-workon-starts-python
|
|
elpy-rpc-buffer
|
|
(y-or-n-p "Virtualenv deactivated, restart Elpy-RPC? "))
|
|
(elpy-rpc-restart)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Backwards compatibility
|
|
|
|
;; Functions for Emacs 24 before 24.3
|
|
(when (not (fboundp 'python-shell-send-region))
|
|
(defalias 'python-shell-send-region 'python-send-region))
|
|
(when (not (fboundp 'python-shell-send-buffer))
|
|
(defalias 'python-shell-send-buffer 'python-send-buffer))
|
|
(when (not (fboundp 'python-info-current-defun))
|
|
(defalias 'python-info-current-defun 'python-current-defun))
|
|
(when (not (fboundp 'python-nav-end-of-statement))
|
|
(defalias 'python-nav-end-of-statement 'python-end-of-statement))
|
|
(when (not (fboundp 'python-nav-beginning-of-statement))
|
|
(require 'thingatpt)
|
|
(defalias 'python-nav-beginning-of-statement 'beginning-of-sexp))
|
|
(when (not (fboundp 'python-nav-forward-statement))
|
|
(defalias 'python-nav-forward-statement 'forward-sexp))
|
|
(when (not (fboundp 'python-nav-backward-statement))
|
|
(defalias 'python-nav-backward-statement 'backward-sexp))
|
|
|
|
;; Emacs 24.2 made `locate-dominating-file' accept a predicate instead
|
|
;; of a string. Simply overwrite the current one, it's
|
|
;; backwards-compatible. The code below is taken from Emacs 24.3.
|
|
(when (or (< emacs-major-version 24)
|
|
(and (= emacs-major-version 24)
|
|
(<= emacs-minor-version 2)))
|
|
(defun locate-dominating-file (file name)
|
|
"Look up the directory hierarchy from FILE for a directory containing NAME.
|
|
Stop at the first parent directory containing a file NAME,
|
|
and return the directory. Return nil if not found.
|
|
Instead of a string, NAME can also be a predicate taking one argument
|
|
\(a directory) and returning a non-nil value if that directory is the one for
|
|
which we're looking."
|
|
;; We used to use the above locate-dominating-files code, but the
|
|
;; directory-files call is very costly, so we're much better off doing
|
|
;; multiple calls using the code in here.
|
|
;;
|
|
;; Represent /home/luser/foo as ~/foo so that we don't try to look for
|
|
;; `name' in /home or in /.
|
|
(setq file (abbreviate-file-name file))
|
|
(let ((root nil)
|
|
;; `user' is not initialized outside the loop because
|
|
;; `file' may not exist, so we may have to walk up part of the
|
|
;; hierarchy before we find the "initial UID". Note: currently unused
|
|
;; (user nil)
|
|
try)
|
|
(while (not (or root
|
|
(null file)
|
|
;; FIXME: Disabled this heuristic because it is sometimes
|
|
;; inappropriate.
|
|
;; As a heuristic, we stop looking up the hierarchy of
|
|
;; directories as soon as we find a directory belonging
|
|
;; to another user. This should save us from looking in
|
|
;; things like /net and /afs. This assumes that all the
|
|
;; files inside a project belong to the same user.
|
|
;; (let ((prev-user user))
|
|
;; (setq user (nth 2 (file-attributes file)))
|
|
;; (and prev-user (not (equal user prev-user))))
|
|
(string-match locate-dominating-stop-dir-regexp file)))
|
|
(setq try (if (stringp name)
|
|
(file-exists-p (expand-file-name name file))
|
|
(funcall name file)))
|
|
(cond (try (setq root file))
|
|
((equal file (setq file (file-name-directory
|
|
(directory-file-name file))))
|
|
(setq file nil))))
|
|
(if root (file-name-as-directory root))))
|
|
)
|
|
|
|
;; highlight-indentation 0.5 does not use modes yet
|
|
(when (not (fboundp 'highlight-indentation-mode))
|
|
(defun highlight-indentation-mode (on-or-off)
|
|
(cond
|
|
((and (= on-or-off 1)
|
|
(not highlight-indent-active))
|
|
(highlight-indentation))
|
|
((and (= on-or-off 0)
|
|
highlight-indent-active)
|
|
(highlight-indentation)))))
|
|
|
|
(provide 'elpy)
|
|
;;; elpy.el ends here
|