第二次提交

This commit is contained in:
2022-02-13 13:27:08 +08:00
parent df1957e6b2
commit 34316e44cd
3851 changed files with 449361 additions and 6 deletions

View File

@@ -0,0 +1,48 @@
;;; anaconda-mode-autoloads.el --- automatically extracted autoloads
;;
;;; Code:
(add-to-list 'load-path (directory-file-name
(or (file-name-directory #$) (car load-path))))
;;;### (autoloads nil "anaconda-mode" "anaconda-mode.el" (0 0 0 0))
;;; Generated autoloads from anaconda-mode.el
(autoload 'anaconda-mode "anaconda-mode" "\
Code navigation, documentation lookup and completion for Python.
If called interactively, enable Anaconda mode if ARG is positive,
and disable it if ARG is zero or negative. If called from Lisp,
also enable the mode if ARG is omitted or nil, and toggle it if
ARG is `toggle'; disable the mode otherwise.
\\{anaconda-mode-map}
\(fn &optional ARG)" t nil)
(autoload 'anaconda-eldoc-mode "anaconda-mode" "\
Toggle echo area display of Python objects at point.
If called interactively, enable Anaconda-Eldoc mode if ARG is
positive, and disable it if ARG is zero or negative. If called
from Lisp, also enable the mode if ARG is omitted or nil, and
toggle it if ARG is `toggle'; disable the mode otherwise.
\(fn &optional ARG)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "anaconda-mode" '("anaconda-" "turn-o")))
;;;***
;;;### (autoloads nil nil ("anaconda-mode-pkg.el") (0 0 0 0))
;;;***
;; Local Variables:
;; version-control: never
;; no-byte-compile: t
;; no-update-autoloads: t
;; coding: utf-8
;; End:
;;; anaconda-mode-autoloads.el ends here

View File

@@ -0,0 +1,14 @@
(define-package "anaconda-mode" "20211122.817" "Code navigation, documentation lookup and completion for Python"
'((emacs "25.1")
(pythonic "0.1.0")
(dash "2.6.0")
(s "1.9")
(f "0.16.2"))
:commit "cbea0fb3182321d34ff93981c5a59f8dd72d82a5" :authors
'(("Artem Malyshev" . "proofit404@gmail.com"))
:maintainer
'("Artem Malyshev" . "proofit404@gmail.com")
:url "https://github.com/proofit404/anaconda-mode")
;; Local Variables:
;; no-byte-compile: t
;; End:

View File

@@ -0,0 +1,779 @@
;;; anaconda-mode.el --- Code navigation, documentation lookup and completion for Python -*- lexical-binding: t; -*-
;; Copyright (C) 2013-2018 by Artem Malyshev
;; Author: Artem Malyshev <proofit404@gmail.com>
;; URL: https://github.com/proofit404/anaconda-mode
;; Version: 0.1.15
;; Package-Requires: ((emacs "25.1") (pythonic "0.1.0") (dash "2.6.0") (s "1.9") (f "0.16.2"))
;; 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:
;; See the README for more details.
;;; Code:
(require 'ansi-color)
(require 'pythonic)
(require 'tramp)
(require 'xref)
(require 'json)
(require 'dash)
(require 'url)
(require 's)
(require 'f)
(defgroup anaconda nil
"Code navigation, documentation lookup and completion for Python."
:group 'programming)
(defcustom anaconda-mode-installation-directory
(locate-user-emacs-file "anaconda-mode")
"Installation directory for `anaconda-mode' server."
:type 'directory)
(defcustom anaconda-mode-eldoc-as-single-line nil
"If not nil, trim eldoc string to frame width."
:type 'boolean)
(defcustom anaconda-mode-lighter " Anaconda"
"Text displayed in the mode line when `anaconda-mode is active."
:type 'sexp)
(defcustom anaconda-mode-localhost-address "127.0.0.1"
"Address used by `anaconda-mode' to resolve localhost."
:type 'string)
(defcustom anaconda-mode-doc-frame-background (face-attribute 'default :background)
"Doc frame background color, default color is current theme's background."
:type 'string)
(defcustom anaconda-mode-doc-frame-foreground (face-attribute 'default :foreground)
"Doc frame foreground color, default color is current theme's foreground."
:type 'string)
(defcustom anaconda-mode-use-posframe-show-doc nil
"If the value is not nil, use posframe to show eldoc."
:type 'boolean)
(defcustom anaconda-mode-tunnel-setup-sleep 2
"Time in seconds `anaconda-mode' waits after tunnel creation before first RPC call."
:type 'integer)
(defcustom anaconda-mode-sync-request-timeout 2
"Time in seconds `anaconda-mode' waits for a synchronous response."
:type 'integer)
;;; Compatibility
;; Functions from posframe which is an optional dependency
(declare-function posframe-workable-p "posframe")
(declare-function posframe-hide "posframe")
(declare-function posframe-show "posframe")
;;; Server.
(defvar anaconda-mode-server-version "0.1.15"
"Server version needed to run `anaconda-mode'.")
(defvar anaconda-mode-process-name "anaconda-mode"
"Process name for `anaconda-mode' processes.")
(defvar anaconda-mode-process-buffer "*anaconda-mode*"
"Buffer name for `anaconda-mode' process.")
(defvar anaconda-mode-process nil
"Currently running `anaconda-mode' process.")
(defvar anaconda-mode-response-buffer "*anaconda-response*"
"Buffer name for error report when `anaconda-mode' fail to read server response.")
(defvar anaconda-mode-socat-process-name "anaconda-socat"
"Process name for `anaconda-mode' socat companion process.")
(defvar anaconda-mode-socat-process-buffer "*anaconda-socat*"
"Buffer name for `anaconda-mode' socat companion process.")
(defvar anaconda-mode-socat-process nil
"Currently running `anaconda-mode' socat companion process.")
(defvar anaconda-mode-ssh-process-name "anaconda-ssh"
"Process name for `anaconda-mode' ssh port forward companion process.")
(defvar anaconda-mode-ssh-process-buffer "*anaconda-ssh*"
"Buffer name for `anaconda-mode' ssh port forward companion process.")
(defvar anaconda-mode-ssh-process nil
"Currently running `anaconda-mode' ssh port forward companion process.")
(defvar anaconda-mode-doc-frame-name "*Anaconda Posframe*"
"The posframe to show anaconda documentation.")
(defvar anaconda-mode-frame-last-point 0
"The last point of anaconda doc view frame, use for hide frame after move point.")
(defvar anaconda-mode-frame-last-scroll-offset 0
"The last scroll offset when show doc view frame, use for hide frame after window scroll.")
(defun anaconda-mode-server-directory ()
"Anaconda mode installation directory."
(f-short (f-join anaconda-mode-installation-directory
anaconda-mode-server-version)))
(defun anaconda-mode-host ()
"Target host with `anaconda-mode' server."
(cond
((pythonic-remote-docker-p)
anaconda-mode-localhost-address)
((pythonic-remote-p)
(pythonic-remote-host))
(t
anaconda-mode-localhost-address)))
(defun anaconda-mode-port ()
"Port for `anaconda-mode' connection."
(process-get anaconda-mode-process 'port))
(defun anaconda-mode-start (&optional callback)
"Start `anaconda-mode' server.
CALLBACK function will be called when `anaconda-mode-port' will
be bound."
(when (anaconda-mode-need-restart)
(anaconda-mode-stop))
(if (anaconda-mode-running-p)
(and callback
(anaconda-mode-bound-p)
(funcall callback))
(anaconda-mode-bootstrap callback)))
(defun anaconda-mode-stop ()
"Stop `anaconda-mode' server."
(when (anaconda-mode-running-p)
(set-process-filter anaconda-mode-process nil)
(set-process-sentinel anaconda-mode-process nil)
(kill-process anaconda-mode-process)
(setq anaconda-mode-process nil))
(when (anaconda-mode-socat-running-p)
(kill-process anaconda-mode-socat-process)
(setq anaconda-mode-socat-process nil))
(when (anaconda-mode-ssh-running-p)
(kill-process anaconda-mode-ssh-process)
(setq anaconda-mode-ssh-process nil)))
(defun anaconda-mode-running-p ()
"Is `anaconda-mode' server running."
(and anaconda-mode-process
(process-live-p anaconda-mode-process)))
(defun anaconda-mode-socat-running-p ()
"Is `anaconda-mode' socat companion process running."
(and anaconda-mode-socat-process
(process-live-p anaconda-mode-socat-process)))
(defun anaconda-mode-ssh-running-p ()
"Is `anaconda-mode' ssh port forward companion process running."
(and anaconda-mode-ssh-process
(process-live-p anaconda-mode-ssh-process)))
(defun anaconda-mode-bound-p ()
"Is `anaconda-mode' port bound."
(numberp (anaconda-mode-port)))
(defun anaconda-mode-need-restart ()
"Check if we need to restart `anaconda-mode-server'."
(when (and (anaconda-mode-running-p)
(anaconda-mode-bound-p))
(not (and (equal (process-get anaconda-mode-process 'interpreter)
python-shell-interpreter)
(equal (process-get anaconda-mode-process 'virtualenv)
python-shell-virtualenv-root)
(equal (process-get anaconda-mode-process 'remote-p)
(pythonic-remote-p))
(if (pythonic-local-p)
t
(equal (process-get anaconda-mode-process 'remote-method)
(pythonic-remote-method))
(equal (process-get anaconda-mode-process 'remote-user)
(pythonic-remote-user))
(equal (process-get anaconda-mode-process 'remote-host)
(pythonic-remote-host))
(equal (process-get anaconda-mode-process 'remote-port)
(pythonic-remote-port)))))))
(defun anaconda-mode-get-server-process-cwd ()
"Get the working directory for starting the anaconda server process.
The current working directory ends up being on sys.path, which may
result in conflicts with stdlib modules.
When running python from the local machine, we start the server
process from `anaconda-mode-installation-directory'.
This function creates that directory if it doesn't exist yet."
(when (pythonic-local-p)
(unless (file-directory-p anaconda-mode-installation-directory)
(make-directory anaconda-mode-installation-directory t))
anaconda-mode-installation-directory))
(defun anaconda-mode-server-command-args ()
"Return list of arguments to start anaconda-mode server.
Passes local file anaconda-mode.py if local, or uses python
module as string if connecting through TRAMP.
Arguments are:
1. anaconda-mode.py (local) or -c anaconda-mode.py string (remote)
2. anaconda-mode-server-directory
3. anaconda-mode-localhost-address (local) or 0.0.0.0 (remote)
4. python-shell-virtualenv-root or empty string if not set"
(let ((server-command-file (concat (file-name-directory (locate-library "anaconda-mode")) "anaconda-mode.py"))
(arg-list (list (anaconda-mode-server-directory)
(if (pythonic-remote-p)
"0.0.0.0"
anaconda-mode-localhost-address)
(or python-shell-virtualenv-root "") ))
server-command)
(if (pythonic-remote-p)
(with-temp-buffer
(insert-file-contents server-command-file)
(setq server-command (list "-c" (buffer-string))))
(setq server-command (list server-command-file)))
(append server-command arg-list)))
(defun anaconda-mode-bootstrap (&optional callback)
"Run `anaconda-mode' server.
CALLBACK function will be called when `anaconda-mode-port' will
be bound."
(setq anaconda-mode-process
(pythonic-start-process :process anaconda-mode-process-name
:cwd (anaconda-mode-get-server-process-cwd)
:buffer (get-buffer-create anaconda-mode-process-buffer)
:query-on-exit nil
:filter (lambda (process output)
(anaconda-mode-bootstrap-filter process output callback))
:sentinel (lambda (_process _event))
:args (anaconda-mode-server-command-args)))
(process-put anaconda-mode-process 'interpreter python-shell-interpreter)
(process-put anaconda-mode-process 'virtualenv python-shell-virtualenv-root)
(process-put anaconda-mode-process 'port nil)
(when (pythonic-remote-p)
(process-put anaconda-mode-process 'remote-p t)
(process-put anaconda-mode-process 'remote-method (pythonic-remote-method))
(process-put anaconda-mode-process 'remote-user (pythonic-remote-user))
(process-put anaconda-mode-process 'remote-host (pythonic-remote-host))
(process-put anaconda-mode-process 'remote-port (pythonic-remote-port))))
(defun anaconda-jump-proxy-string ()
"Create -J option string for SSH tunnel."
(let ((dfn
(tramp-dissect-file-name (pythonic-aliased-path default-directory))))
(when (tramp-file-name-hop dfn)
(let ((hop-list (split-string (tramp-file-name-hop dfn) "|"))
(result "-J "))
(delete "" hop-list) ;; remove empty string after final pipe
(dolist (elt hop-list result)
;; tramp-dissect-file-name expects a filename so give it dummy.file
(let ((ts (tramp-dissect-file-name (concat "/" elt ":/dummy.file"))))
(setq result (concat result
(format "%s@%s:%s,"
(tramp-file-name-user ts)
(tramp-file-name-host ts)
(or (tramp-file-name-port-or-default ts) 22))))))
;; Remove final comma
(substring result 0 -1)))))
(defun anaconda-mode-bootstrap-filter (process output &optional callback)
"Set `anaconda-mode-port' from PROCESS OUTPUT.
Connect to the `anaconda-mode' server. CALLBACK function will be
called when `anaconda-mode-port' will be bound."
;; Mimic default filter.
(when (buffer-live-p (process-buffer process))
(with-current-buffer (process-buffer process)
(save-excursion
(goto-char (process-mark process))
(insert (ansi-color-apply output))
(set-marker (process-mark process) (point)))))
(unless (anaconda-mode-bound-p)
(--when-let (s-match "anaconda_mode port \\([0-9]+\\)" output)
(process-put anaconda-mode-process 'port (string-to-number (cadr it)))
(cond ((pythonic-remote-docker-p)
(let* ((container-raw-description (with-output-to-string
(with-current-buffer
standard-output
(call-process "docker" nil t nil "inspect" (pythonic-remote-host)))))
(container-description (let ((json-array-type 'list))
(json-read-from-string container-raw-description)))
(container-ip (cdr (assoc 'IPAddress
(cdadr (assoc 'Networks
(cdr (assoc 'NetworkSettings
(car container-description)))))))))
(setq anaconda-mode-socat-process
(start-process anaconda-mode-socat-process-name
anaconda-mode-socat-process-buffer
"socat"
(format "TCP4-LISTEN:%d" (anaconda-mode-port))
(format "TCP4:%s:%d" container-ip (anaconda-mode-port))))
(set-process-query-on-exit-flag anaconda-mode-socat-process nil)))
((pythonic-remote-ssh-p)
(let ((jump (anaconda-jump-proxy-string)))
(message (format "Anaconda Jump Proxy: %s" jump))
(setq anaconda-mode-ssh-process
(if jump
(start-process anaconda-mode-ssh-process-name
anaconda-mode-ssh-process-buffer
"ssh" jump "-nNT"
"-L" (format "%s:localhost:%s" (anaconda-mode-port) (anaconda-mode-port))
(format "%s@%s" (pythonic-remote-user) (pythonic-remote-host))
"-p" (number-to-string (or (pythonic-remote-port) 22)))
(start-process anaconda-mode-ssh-process-name
anaconda-mode-ssh-process-buffer
"ssh" "-nNT"
"-L" (format "%s:localhost:%s" (anaconda-mode-port) (anaconda-mode-port))
(if (pythonic-remote-user)
(format "%s@%s" (pythonic-remote-user) (pythonic-remote-host))
;; Asssume remote host is an ssh alias
(pythonic-remote-host))
"-p" (number-to-string (or (pythonic-remote-port) 22)))))
;; prevent race condition between tunnel setup and first use
(sleep-for anaconda-mode-tunnel-setup-sleep)
(set-process-query-on-exit-flag anaconda-mode-ssh-process nil))))
(when callback
(funcall callback)))))
;;; Interaction.
(defun anaconda-mode-call (command callback)
"Make remote procedure call for COMMAND.
Apply CALLBACK to the result asynchronously."
(anaconda-mode-start
(lambda () (anaconda-mode-jsonrpc command callback))))
(defun anaconda-mode-call-sync (command callback)
"Make remote procedure call for COMMAND.
Apply CALLBACK to the result synchronously."
(let ((start-time (current-time))
(result 'pending))
(anaconda-mode-call
command
(lambda (r) (setq result r)))
(while (eq result 'pending)
(accept-process-output nil 0.01)
(when (> (cadr (time-subtract (current-time) start-time))
anaconda-mode-sync-request-timeout)
(error "%s request timed out" command)))
(funcall callback result)))
(defun anaconda-mode-jsonrpc (command callback)
"Perform JSONRPC call for COMMAND.
Apply CALLBACK to the call result when retrieve it. Remote
COMMAND must expect four arguments: python buffer content, line
number position, column number position and file path."
(let ((url-request-method "POST")
(url-request-data (anaconda-mode-jsonrpc-request command)))
(url-retrieve
(format "http://%s:%s" anaconda-mode-localhost-address (anaconda-mode-port))
(anaconda-mode-create-response-handler callback)
nil
t)))
(defun anaconda-mode-jsonrpc-request (command)
"Prepare JSON encoded buffer data for COMMAND call."
(encode-coding-string (json-encode (anaconda-mode-jsonrpc-request-data command)) 'utf-8))
(defun anaconda-mode-jsonrpc-request-data (command)
"Prepare buffer data for COMMAND call."
`((jsonrpc . "2.0")
(id . 1)
(method . ,command)
(params . ((source . ,(buffer-substring-no-properties (point-min) (point-max)))
(line . ,(line-number-at-pos (point)))
(column . ,(- (point) (line-beginning-position)))
(path . ,(when (buffer-file-name)
(pythonic-python-readable-file-name (buffer-file-name))))))))
(defun anaconda-mode-create-response-handler (callback)
"Create server response handler based on CALLBACK function."
(let ((anaconda-mode-request-point (point))
(anaconda-mode-request-buffer (current-buffer))
(anaconda-mode-request-window (selected-window))
(anaconda-mode-request-tick (buffer-chars-modified-tick)))
(lambda (status)
(let ((http-buffer (current-buffer)))
(unwind-protect
(if (or (not (equal anaconda-mode-request-window (selected-window)))
(with-current-buffer (window-buffer anaconda-mode-request-window)
(or (not (equal anaconda-mode-request-buffer (current-buffer)))
(not (equal anaconda-mode-request-point (point)))
(not (equal anaconda-mode-request-tick (buffer-chars-modified-tick))))))
nil
(search-forward-regexp "\r?\n\r?\n" nil t)
(let ((response (condition-case nil
(json-read)
((json-readtable-error json-end-of-file end-of-file)
(let ((response (concat (format "# status: %s\n# point: %s\n" status (point))
(buffer-string))))
(with-current-buffer (get-buffer-create anaconda-mode-response-buffer)
(erase-buffer)
(insert response)
(goto-char (point-min)))
nil)))))
(if (null response)
(message "Cannot read anaconda-mode server response")
(if (assoc 'error response)
(let* ((error-structure (cdr (assoc 'error response)))
(error-message (cdr (assoc 'message error-structure)))
(error-data (cdr (assoc 'data error-structure)))
(error-template (concat (if error-data "%s: %s" "%s")
" - see " anaconda-mode-process-buffer
" for more information.")))
(apply 'message error-template (delq nil (list error-message error-data))))
(with-current-buffer anaconda-mode-request-buffer
(let ((result (cdr (assoc 'result response))))
;; Terminate `apply' call with empty list so response
;; will be treated as single argument.
(condition-case nil
(apply callback result nil)
(quit nil))))))))
(kill-buffer http-buffer))))))
;;; Code completion.
(defun anaconda-mode-complete ()
"Request completion candidates."
(interactive)
(unless (python-syntax-comment-or-string-p)
(anaconda-mode-call "complete" 'anaconda-mode-complete-callback)))
(defun anaconda-mode-complete-callback (result)
"Start interactive completion on RESULT receiving."
(let* ((bounds (bounds-of-thing-at-point 'symbol))
(start (or (car bounds) (point)))
(stop (or (cdr bounds) (point)))
(collection (anaconda-mode-complete-extract-names result))
(completion-extra-properties '(:annotation-function anaconda-mode-complete-annotation)))
(completion-in-region start stop collection)))
(defun anaconda-mode-complete-extract-names (result)
"Extract completion names from `anaconda-mode' RESULT."
(--map (let ((name (aref it 0))
(type (aref it 1)))
(put-text-property 0 1 'type type name)
name)
result))
(defun anaconda-mode-complete-annotation (candidate)
"Get annotation for CANDIDATE."
(--when-let (get-text-property 0 'type candidate)
(concat " <" it ">")))
;;; View documentation.
(defun anaconda-mode-show-doc ()
"Show documentation for context at point."
(interactive)
(anaconda-mode-call "show_doc" 'anaconda-mode-show-doc-callback))
(defun anaconda-mode-show-doc-callback (result)
"Process view doc RESULT."
(if (> (length result) 0)
(if (and anaconda-mode-use-posframe-show-doc
(require 'posframe nil 'noerror)
(posframe-workable-p))
(anaconda-mode-documentation-posframe-view result)
(pop-to-buffer (anaconda-mode-documentation-view result) t))
(message "No documentation available")))
(defun anaconda-mode-documentation-view (result)
"Show documentation view for rpc RESULT, and return buffer."
(let ((buf (get-buffer-create "*Anaconda*")))
(with-current-buffer buf
(view-mode -1)
(erase-buffer)
(mapc
(lambda (it)
(insert (propertize (aref it 0) 'face 'bold))
(insert "\n")
(insert (s-trim-right (aref it 1)))
(insert "\n\n"))
result)
(view-mode 1)
(goto-char (point-min))
buf)))
(defun anaconda-mode-documentation-posframe-view (result)
"Show documentation view in posframe for rpc RESULT."
(with-current-buffer (get-buffer-create anaconda-mode-doc-frame-name)
(erase-buffer)
(mapc
(lambda (it)
(insert (propertize (aref it 0) 'face 'bold))
(insert "\n")
(insert (s-trim-left (aref it 1)))
(insert "\n\n"))
result))
(posframe-show anaconda-mode-doc-frame-name
:position (point)
:internal-border-width 10
:background-color anaconda-mode-doc-frame-background
:foreground-color anaconda-mode-doc-frame-foreground)
(add-hook 'post-command-hook 'anaconda-mode-hide-frame)
(setq anaconda-mode-frame-last-point (point))
(setq anaconda-mode-frame-last-scroll-offset (window-start)))
(defun anaconda-mode-hide-frame ()
"Hide posframe when window scroll or move point."
(ignore-errors
(when (get-buffer anaconda-mode-doc-frame-name)
(unless (and (equal (point) anaconda-mode-frame-last-point)
(equal (window-start) anaconda-mode-frame-last-scroll-offset))
(posframe-hide anaconda-mode-doc-frame-name)
(remove-hook 'post-command-hook 'anaconda-mode-hide-frame)))))
;;; Find definitions.
(defun anaconda-mode-find-definitions ()
"Find definitions for thing at point."
(interactive)
(anaconda-mode-call
"infer"
(lambda (result)
(anaconda-mode-show-xrefs result nil "No definitions found"))))
(defun anaconda-mode-find-definitions-other-window ()
"Find definitions for thing at point."
(interactive)
(anaconda-mode-call
"infer"
(lambda (result)
(anaconda-mode-show-xrefs result 'window "No definitions found"))))
(defun anaconda-mode-find-definitions-other-frame ()
"Find definitions for thing at point."
(interactive)
(anaconda-mode-call
"infer"
(lambda (result)
(anaconda-mode-show-xrefs result 'frame "No definitions found"))))
;;; Find assignments.
(defun anaconda-mode-find-assignments ()
"Find assignments for thing at point."
(interactive)
(anaconda-mode-call
"goto"
(lambda (result)
(anaconda-mode-show-xrefs result nil "No assignments found"))))
(defun anaconda-mode-find-assignments-other-window ()
"Find assignments for thing at point."
(interactive)
(anaconda-mode-call
"goto"
(lambda (result)
(anaconda-mode-show-xrefs result 'window "No assignments found"))))
(defun anaconda-mode-find-assignments-other-frame ()
"Find assignments for thing at point."
(interactive)
(anaconda-mode-call
"goto"
(lambda (result)
(anaconda-mode-show-xrefs result 'frame "No assignments found"))))
;;; Find references.
(defun anaconda-mode-find-references ()
"Find references for thing at point."
(interactive)
(anaconda-mode-call
"get_references"
(lambda (result)
(anaconda-mode-show-xrefs result nil "No references found"))))
(defun anaconda-mode-find-references-other-window ()
"Find references for thing at point."
(interactive)
(anaconda-mode-call
"get_references"
(lambda (result)
(anaconda-mode-show-xrefs result 'window "No references found"))))
(defun anaconda-mode-find-references-other-frame ()
"Find references for thing at point."
(interactive)
(anaconda-mode-call
"get_references"
(lambda (result)
(anaconda-mode-show-xrefs result 'frame "No references found"))))
;;; Xref.
(defun anaconda-mode-xref-backend ()
"Integrate `anaconda-mode' with xref."
'anaconda)
(cl-defmethod xref-backend-definitions ((_backend (eql anaconda)) _identifier)
"Find definitions for thing at point."
(anaconda-mode-call-sync
"infer"
(lambda (result)
(if result
(if (stringp result)
(progn
(message result)
nil)
(anaconda-mode-make-xrefs result))))))
(cl-defmethod xref-backend-references ((_backend (eql anaconda)) _identifier)
"Find references for thing at point."
(anaconda-mode-call-sync
"get_references"
(lambda (result)
(if result
(if (stringp result)
(progn
(message result)
nil)
(anaconda-mode-make-xrefs result))))))
(cl-defmethod xref-backend-apropos ((_backend (eql anaconda)) _pattern)
"Not implemented."
nil)
(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql anaconda)))
"Not implemented."
nil)
(defun anaconda-mode-show-xrefs (result display-action error-message)
"Show xref from RESULT using DISPLAY-ACTION.
Show ERROR-MESSAGE if result is empty."
(if result
(if (stringp result)
(message result)
(let ((xrefs (anaconda-mode-make-xrefs result)))
(if (not (cdr xrefs))
(progn
(xref-push-marker-stack)
(funcall (if (fboundp 'xref-pop-to-location)
'xref-pop-to-location
'xref--pop-to-location)
(cl-first xrefs)
display-action))
(xref--show-xrefs (if (functionp 'xref--create-fetcher)
(lambda (&rest _) xrefs)
xrefs)
display-action))))
(message error-message)))
(defun anaconda-mode-make-xrefs (result)
"Return a list of x-reference candidates created from RESULT."
(--map
(xref-make
(aref it 3)
(xref-make-file-location (pythonic-emacs-readable-file-name (aref it 0)) (aref it 1) (aref it 2)))
result))
;;; Eldoc.
(defun anaconda-mode-eldoc-function ()
"Show eldoc for context at point."
(anaconda-mode-call "eldoc" 'anaconda-mode-eldoc-callback)
;; Don't show response buffer name as ElDoc message.
nil)
(defun anaconda-mode-eldoc-callback (result)
"Display eldoc from server RESULT."
(eldoc-message (anaconda-mode-eldoc-format result)))
(defun anaconda-mode-eldoc-format (result)
"Format eldoc string from RESULT."
(when result
(let ((doc (anaconda-mode-eldoc-format-definition
(aref result 0)
(aref result 1)
(aref result 2))))
(if anaconda-mode-eldoc-as-single-line
(substring doc 0 (min (frame-width) (length doc)))
doc))))
(defun anaconda-mode-eldoc-format-definition (name index params)
"Format function definition from NAME, INDEX and PARAMS."
(when index
(aset params index (propertize (aref params index) 'face 'eldoc-highlight-function-argument)))
(concat (propertize name 'face 'font-lock-function-name-face) "(" (mapconcat 'identity params ", ") ")"))
;;; Anaconda minor mode.
(defvar anaconda-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-M-i") 'anaconda-mode-complete)
(define-key map (kbd "M-.") 'anaconda-mode-find-definitions)
(define-key map (kbd "C-x 4 .") 'anaconda-mode-find-definitions-other-window)
(define-key map (kbd "C-x 5 .") 'anaconda-mode-find-definitions-other-frame)
(define-key map (kbd "M-=") 'anaconda-mode-find-assignments)
(define-key map (kbd "C-x 4 =") 'anaconda-mode-find-assignments-other-window)
(define-key map (kbd "C-x 5 =") 'anaconda-mode-find-assignments-other-frame)
(define-key map (kbd "M-r") 'anaconda-mode-find-references)
(define-key map (kbd "C-x 4 r") 'anaconda-mode-find-references-other-window)
(define-key map (kbd "C-x 5 r") 'anaconda-mode-find-references-other-frame)
(define-key map (kbd "M-,") 'xref-pop-marker-stack)
(define-key map (kbd "M-?") 'anaconda-mode-show-doc)
map)
"Keymap for `anaconda-mode'.")
;;;###autoload
(define-minor-mode anaconda-mode
"Code navigation, documentation lookup and completion for Python.
\\{anaconda-mode-map}"
:lighter anaconda-mode-lighter
:keymap anaconda-mode-map
(setq-local url-http-attempt-keepalives nil)
(if anaconda-mode
(add-hook 'xref-backend-functions #'anaconda-mode-xref-backend nil t)
(remove-hook 'xref-backend-functions #'anaconda-mode-xref-backend t)))
;;;###autoload
(define-minor-mode anaconda-eldoc-mode
"Toggle echo area display of Python objects at point."
:lighter ""
(if anaconda-eldoc-mode
(turn-on-anaconda-eldoc-mode)
(turn-off-anaconda-eldoc-mode)))
(defun turn-on-anaconda-eldoc-mode ()
"Turn on `anaconda-eldoc-mode'."
(make-local-variable 'eldoc-documentation-function)
(setq-local eldoc-documentation-function 'anaconda-mode-eldoc-function)
(eldoc-mode +1))
(defun turn-off-anaconda-eldoc-mode ()
"Turn off `anaconda-eldoc-mode'."
(kill-local-variable 'eldoc-documentation-function)
(eldoc-mode -1))
(provide 'anaconda-mode)
;;; anaconda-mode.el ends here

Binary file not shown.

View File

@@ -0,0 +1,194 @@
from __future__ import print_function
import sys
import os
from distutils.version import LooseVersion
# CLI arguments.
assert len(sys.argv) > 3, 'CLI arguments: %s' % sys.argv
server_directory = sys.argv[-3]
server_address = sys.argv[-2]
virtual_environment = sys.argv[-1]
# Ensure directory.
server_directory = os.path.expanduser(server_directory)
virtual_environment = os.path.expanduser(virtual_environment)
# Installation check.
IS_PY2 = sys.version_info[0] == 2
# jedi versions >= 0.18 don't support Python 2
if IS_PY2:
jedi_dep = ('jedi', '0.17.2')
server_directory += '-py2'
else:
jedi_dep = ('jedi', '0.18.1')
server_directory += '-py3'
service_factory_dep = ('service_factory', '0.1.6')
if not os.path.exists(server_directory):
os.makedirs(server_directory)
sys.path.insert(1, server_directory)
missing_dependencies = []
def is_package_dir(path):
if os.path.isdir(path):
if IS_PY2:
return path.endswith(".egg")
else:
return not (path.endswith(".dist-info") or path.endswith(".egg-info"))
return False
def instrument_installation():
for package in (jedi_dep, service_factory_dep):
package_is_installed = False
for path in os.listdir(server_directory):
path = os.path.join(server_directory, path)
if is_package_dir(path):
if path not in sys.path:
sys.path.insert(0, path)
if package[0] in path:
package_is_installed = True
if not package_is_installed:
missing_dependencies.append('=='.join(package))
instrument_installation()
# Installation.
def install_deps_setuptools():
import setuptools.command.easy_install
cmd = ['--install-dir', server_directory,
'--site-dirs', server_directory,
'--always-copy', '--always-unzip']
cmd.extend(missing_dependencies)
setuptools.command.easy_install.main(cmd)
instrument_installation()
def install_deps_pip():
import subprocess
cmd = [sys.executable, '-m', 'pip', 'install', '--target', server_directory]
cmd.extend(missing_dependencies)
subprocess.check_call(cmd)
instrument_installation()
if missing_dependencies:
if IS_PY2:
install_deps_setuptools()
else:
install_deps_pip()
del missing_dependencies[:]
try:
import jedi
except ImportError:
missing_dependencies.append('=='.join(jedi_dep))
try:
import service_factory
except ImportError:
missing_dependencies.append('>='.join(service_factory_dep))
# Try one more time in case if anaconda installation gets broken somehow
if missing_dependencies:
if IS_PY2:
install_deps_setuptools()
else:
install_deps_pip()
import jedi
import service_factory
# Setup server.
assert LooseVersion(jedi.__version__) >= LooseVersion(jedi_dep[1]), 'Jedi version should be >= %s, current version: %s' % (jedi_dep[1], jedi.__version__)
if virtual_environment:
virtual_environment = jedi.create_environment(virtual_environment, safe=False)
else:
virtual_environment = None
# Define JSON-RPC application.
import functools
import threading
def script_method(f):
@functools.wraps(f)
def wrapper(source, line, column, path):
timer = threading.Timer(30.0, sys.exit)
timer.start()
result = f(jedi.Script(source, path=path, environment=virtual_environment), line, column)
timer.cancel()
return result
return wrapper
def process_definitions(f):
@functools.wraps(f)
def wrapper(script, line, column):
definitions = f(script, line, column)
if len(definitions) == 1 and not definitions[0].module_path:
return '%s is defined in %s compiled module' % (
definitions[0].name, definitions[0].module_name)
return [[str(definition.module_path),
definition.line,
definition.column,
definition.get_line_code().strip()]
for definition in definitions
if definition.module_path] or None
return wrapper
@script_method
def complete(script, line, column):
return [[definition.name, definition.type]
for definition in script.complete(line, column)]
@script_method
def company_complete(script, line, column):
return [[definition.name,
definition.type,
definition.docstring(),
str(definition.module_path),
definition.line]
for definition in script.complete(line, column)]
@script_method
def show_doc(script, line, column):
return [[definition.module_name, definition.docstring()]
for definition in script.infer(line, column)]
@script_method
@process_definitions
def infer(script, line, column):
return script.infer(line, column)
@script_method
@process_definitions
def goto(script, line, column):
return script.goto(line, column)
@script_method
@process_definitions
def get_references(script, line, column):
return script.get_references(line, column)
@script_method
def eldoc(script, line, column):
signatures = script.get_signatures(line, column)
if len(signatures) == 1:
signature = signatures[0]
return [signature.name,
signature.index,
[param.description[6:] for param in signature.params]]
# Run.
app = [complete, company_complete, show_doc, infer, goto, get_references, eldoc]
service_factory.service_factory(app, server_address, 0, 'anaconda_mode port {port}')

View File

@@ -0,0 +1,29 @@
;;; company-anaconda-autoloads.el --- automatically extracted autoloads
;;
;;; Code:
(add-to-list 'load-path (directory-file-name
(or (file-name-directory #$) (car load-path))))
;;;### (autoloads nil "company-anaconda" "company-anaconda.el" (0
;;;;;; 0 0 0))
;;; Generated autoloads from company-anaconda.el
(autoload 'company-anaconda "company-anaconda" "\
Anaconda backend for company-mode.
See `company-backends' for more info about COMMAND and ARG.
\(fn COMMAND &optional ARG &rest ARGS)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "company-anaconda" '("company-anaconda-")))
;;;***
;; Local Variables:
;; version-control: never
;; no-byte-compile: t
;; no-update-autoloads: t
;; coding: utf-8
;; End:
;;; company-anaconda-autoloads.el ends here

View File

@@ -0,0 +1,2 @@
;;; Generated package description from company-anaconda.el -*- no-byte-compile: t -*-
(define-package "company-anaconda" "20200404.1859" "Anaconda backend for company-mode" '((company "0.8.0") (anaconda-mode "0.1.1") (cl-lib "0.5.0") (dash "2.6.0") (s "1.9")) :commit "da1566db41a68809ef7f91ebf2de28118067c89b" :authors '(("Artem Malyshev" . "proofit404@gmail.com")) :maintainer '("Artem Malyshev" . "proofit404@gmail.com") :url "https://github.com/proofit404/anaconda-mode")

View File

@@ -0,0 +1,155 @@
;;; company-anaconda.el --- Anaconda backend for company-mode -*- lexical-binding: t; -*-
;; Copyright (C) 2013-2018 by Artem Malyshev
;; Author: Artem Malyshev <proofit404@gmail.com>
;; URL: https://github.com/proofit404/anaconda-mode
;; Package-Version: 20200404.1859
;; Package-Commit: da1566db41a68809ef7f91ebf2de28118067c89b
;; Version: 0.2.0
;; Package-Requires: ((company "0.8.0") (anaconda-mode "0.1.1") (cl-lib "0.5.0") (dash "2.6.0") (s "1.9"))
;; 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:
;; See the README for more details.
;;; Code:
(require 'anaconda-mode)
(require 'company)
(require 'python)
(require 'cl-lib)
(require 'rx)
(require 'dash)
(require 's)
(defgroup company-anaconda nil
"Company back-end for Python code completion."
:group 'programming)
(defcustom company-anaconda-annotation-function
'company-anaconda-annotation
"Function that returns candidate annotations."
:group 'company-anaconda
:type 'function)
(defcustom company-anaconda-case-insensitive t
"Use case insensitive candidates match."
:group 'company-anaconda
:type 'boolean)
(defun company-anaconda-at-the-end-of-identifier ()
"Check if the cursor at the end of completable identifier."
(let ((limit (line-beginning-position)))
(or
;; We can't determine at this point if we can complete on a space
(looking-back " " limit)
;; At the end of the symbol, but not the end of int number
(and (looking-at "\\_>")
(not (looking-back "\\_<\\(0[bo]\\)?[[:digit:]]+" limit))
(not (looking-back "\\_<0x[[:xdigit:]]+" limit)))
;; After the dot, but not when it's a dot after int number
;; Although identifiers like "foo1.", "foo111.", or "foo1baz2." are ok
(and (looking-back "\\." (- (point) 1))
(not (looking-back "\\_<[[:digit:]]+\\." limit)))
;; After dot in float constant like "1.1." or ".1."
(or (looking-back "\\_<[[:digit:]]+\\.[[:digit:]]+\\." limit)
(looking-back "\\.[[:digit:]]+\\." limit)))))
(defun company-anaconda-prefix ()
"Grab prefix at point."
(and anaconda-mode
(not (company-in-string-or-comment))
(company-anaconda-at-the-end-of-identifier)
(let* ((line-start (line-beginning-position))
(start
(save-excursion
(if (not (re-search-backward
(python-rx
(or whitespace open-paren close-paren string-delimiter))
line-start
t 1))
line-start
(forward-char (length (match-string-no-properties 0)))
(point))))
(symbol (buffer-substring-no-properties start (point))))
(if (or (s-ends-with-p "." symbol)
(string-match-p
(rx (* space) word-start (or "from" "import") word-end space)
(buffer-substring-no-properties line-start (point))))
(cons symbol t)
(if (s-blank-p symbol)
'stop
symbol)))))
(defun company-anaconda-candidates (callback given-prefix)
"Pass candidates list for GIVEN-PREFIX to the CALLBACK asynchronously."
(anaconda-mode-call
"company_complete"
(lambda (result)
(funcall callback
(--map
(let ((candidate (s-concat given-prefix (aref it 0))))
(put-text-property 0 1 'struct it candidate)
candidate)
result)))))
(defun company-anaconda-annotation (candidate)
"Return the description property of CANDIDATE inside chevrons."
(--when-let (aref (get-text-property 0 'struct candidate) 1)
(concat "<" it ">")))
(defun company-anaconda-doc-buffer (candidate)
"Return documentation buffer for chosen CANDIDATE."
(let ((docstring (aref (get-text-property 0 'struct candidate) 2)))
(unless (s-blank? docstring)
(anaconda-mode-documentation-view (vector (vector "" docstring))))))
(defun company-anaconda-meta (candidate)
"Return short documentation string for chosen CANDIDATE."
(let ((docstring (aref (get-text-property 0 'struct candidate) 2)))
(unless (s-blank? docstring)
(car (s-split-up-to "\n" docstring 1)))))
(defun company-anaconda-location (candidate)
"Return location (path . line) for chosen CANDIDATE."
(-when-let* ((struct (get-text-property 0 'struct candidate))
(module-path (pythonic-emacs-readable-file-name (aref struct 3)))
(line (aref struct 4)))
(cons module-path line)))
;;;###autoload
(defun company-anaconda (command &optional arg &rest _args)
"Anaconda backend for company-mode.
See `company-backends' for more info about COMMAND and ARG."
(interactive (list 'interactive))
(cl-case command
(interactive (company-begin-backend 'company-anaconda))
(prefix (company-anaconda-prefix))
(candidates (cons :async
(let ((given-prefix (s-chop-suffix (company-grab-symbol) arg)))
(lambda (callback)
(company-anaconda-candidates callback given-prefix)))))
(doc-buffer (company-anaconda-doc-buffer arg))
(meta (company-anaconda-meta arg))
(annotation (funcall company-anaconda-annotation-function arg))
(location (company-anaconda-location arg))
(ignore-case company-anaconda-case-insensitive)
(sorted t)))
(provide 'company-anaconda)
;;; company-anaconda.el ends here

View File

@@ -0,0 +1,92 @@
;;; elpy-autoloads.el --- automatically extracted autoloads
;;
;;; Code:
(add-to-list 'load-path (directory-file-name
(or (file-name-directory #$) (car load-path))))
;;;### (autoloads nil "elpy" "elpy.el" (0 0 0 0))
;;; Generated autoloads from elpy.el
(autoload 'elpy-enable "elpy" "\
Enable Elpy in all future Python buffers.
\(fn &optional IGNORED)" t nil)
(autoload 'elpy-mode "elpy" "\
Minor mode in Python buffers for the Emacs Lisp Python Environment.
If called interactively, enable Elpy mode if ARG is positive, and
disable it if ARG is zero or negative. If called from Lisp, also
enable the mode if ARG is omitted or nil, and toggle it if ARG is
`toggle'; disable the mode otherwise.
This mode fully supports virtualenvs. Once you switch a
virtualenv using \\[pyvenv-workon], you can use
\\[elpy-rpc-restart] to make the elpy Python process use your
virtualenv.
\\{elpy-mode-map}
\(fn &optional ARG)" t nil)
(autoload 'elpy-config "elpy" "\
Configure Elpy.
This function will pop up a configuration buffer, which is mostly
a customize buffer, but has some more options." t nil)
(autoload 'elpy-version "elpy" "\
Display the version of Elpy." t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "elpy" '("elpy-")))
;;;***
;;;### (autoloads nil "elpy-django" "elpy-django.el" (0 0 0 0))
;;; Generated autoloads from elpy-django.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "elpy-django" '("elpy-")))
;;;***
;;;### (autoloads nil "elpy-profile" "elpy-profile.el" (0 0 0 0))
;;; Generated autoloads from elpy-profile.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "elpy-profile" '("elpy-profile-")))
;;;***
;;;### (autoloads nil "elpy-refactor" "elpy-refactor.el" (0 0 0 0))
;;; Generated autoloads from elpy-refactor.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "elpy-refactor" '("elpy-refactor-")))
;;;***
;;;### (autoloads nil "elpy-rpc" "elpy-rpc.el" (0 0 0 0))
;;; Generated autoloads from elpy-rpc.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "elpy-rpc" '("elpy-" "with-elpy-rpc-virtualenv-activated")))
;;;***
;;;### (autoloads nil "elpy-shell" "elpy-shell.el" (0 0 0 0))
;;; Generated autoloads from elpy-shell.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "elpy-shell" '("elpy-")))
;;;***
;;;### (autoloads nil nil ("elpy-pkg.el") (0 0 0 0))
;;;***
;; Local Variables:
;; version-control: never
;; no-byte-compile: t
;; no-update-autoloads: t
;; coding: utf-8
;; End:
;;; elpy-autoloads.el ends here

View File

@@ -0,0 +1,335 @@
;;; elpy-django.el --- Django extension for elpy
;; Copyright (C) 2013-2019 Jorgen Schaefer
;; Author: Daniel Gopar <gopardaniel@gmail.com>
;; URL: https://github.com/jorgenschaefer/elpy
;; 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:
;; This file serves as an extension to elpy by adding django support
;;; Code:
(require 's)
;;;;;;;;;;;;;;;;;;;;;;
;;; User customization
(defcustom elpy-django-command "django-admin.py"
"Command to use when running Django specific commands.
Best to set it to full path to 'manage.py' if it's available."
:type 'string
:safe 'stringp
:group 'elpy-django)
(make-variable-buffer-local 'elpy-django-command)
(defcustom elpy-django-server-ipaddr "127.0.0.1"
"What address Django will use when running the dev server."
:type 'string
:safe 'stringp
:group 'elpy-django)
(make-variable-buffer-local 'elpy-django-server-ipaddr)
(defcustom elpy-django-server-port "8000"
"What port Django will use when running the dev server."
:type 'string
:safe 'stringp
:group 'elpy-django)
(make-variable-buffer-local 'elpy-django-server-port)
(defcustom elpy-django-server-command "runserver"
"When executing `elpy-django-runserver' what should be the server
command to use."
:type 'string
:safe 'stringp
:group 'elpy-django)
(make-variable-buffer-local 'elpy-django-server-command)
(defcustom elpy-django-always-prompt nil
"When non-nil, it will always prompt for extra arguments
to pass with the chosen command."
:type 'boolean
:safe 'booleanp
:group 'elpy-django)
(make-variable-buffer-local 'elpy-django-always-prompt)
(defcustom elpy-django-commands-with-req-arg '("startapp" "startproject"
"loaddata" "sqlmigrate"
"sqlsequencereset"
"squashmigrations")
"Used to determine if we should prompt for arguments. Some commands
require arguments in order for it to work."
:type 'list
:safe 'listp
:group 'elpy-django)
(make-variable-buffer-local 'elpy-django-commands-with-req-arg)
(defcustom elpy-django-test-runner-formats '(("django_nose.NoseTestSuiteRunner" . ":")
(".*" . "."))
"List of test runners and their format for calling tests.
The keys are the regular expressions to match the runner used in test,
while the values are the separators to use to build test target path.
Some tests runners are called differently. For example, Nose requires a ':' when calling specific tests,
but the default Django test runner uses '.'"
:type 'list
:safe 'listp
:group 'elpy-django)
(make-variable-buffer-local 'elpy-django-test-runner-formats)
(defcustom elpy-django-test-runner-args '("test" "--noinput")
"Arguments to pass to the test runner when calling tests."
:type '(repeat string)
:group 'elpy-django)
(make-variable-buffer-local 'elpy-django-test-runner-args)
(defcustom elpy-test-django-runner-command nil
"Deprecated. Please define Django command in `elpy-django-command' and
test arguments in `elpy-django-test-runner-args'"
:type '(repeat string)
:group 'elpy-django)
(make-obsolete-variable 'elpy-test-django-runner-command nil "March 2018")
(defcustom elpy-test-django-runner-manage-command nil
"Deprecated. Please define Django command in `elpy-django-command' and
test arguments in `elpy-django-test-runner-args'."
:type '(repeat string)
:group 'elpy-django)
(make-obsolete-variable 'elpy-test-django-runner-manage-command nil "March 2018")
(defcustom elpy-test-django-with-manage nil
"Deprecated. Please define Django command in `elpy-django-command' and
test arguments in `elpy-django-test-runner-args'."
:type 'boolean
:group 'elpy-django)
(make-obsolete-variable 'elpy-test-django-with-manage nil "March 2018")
;;;;;;;;;;;;;;;;;;;;;;
;; Key map
(defvar elpy-django-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "c") 'elpy-django-command)
(define-key map (kbd "r") 'elpy-django-runserver)
map)
"Key map for django extension")
;;;;;;;;;;;;;;;;;;;;;;
;;; Helper Functions
(defun elpy-django-setup ()
"Decides whether to start the minor mode or not."
;; Make sure we're in an actual file and we can find
;; manage.py. Otherwise user will have to manually
;; start this mode if they're using 'django-admin.py'
(when (locate-dominating-file default-directory "manage.py")
;; Let's be nice and point to full path of 'manage.py'
;; This only affects the buffer if there's no directory
;; variable overwriting it.
(setq elpy-django-command
(expand-file-name (concat (locate-dominating-file default-directory "manage.py") "manage.py")))
(elpy-django 1)))
(defun elpy-project-find-django-root ()
"Return the current Django project root, if any.
This is marked with 'manage.py' or 'django-admin.py'."
(or (locate-dominating-file default-directory "django-admin.py")
(locate-dominating-file default-directory "manage.py")))
(defun elpy-django--get-commands ()
"Return list of django commands."
(let ((dj-commands-str nil)
(help-output
(shell-command-to-string (concat elpy-django-command " -h"))))
(setq dj-commands-str
(with-temp-buffer
(progn
(insert help-output)
(goto-char (point-min))
(delete-region (point) (search-forward "Available subcommands:" nil nil nil))
;; cleanup [auth] and stuff
(goto-char (point-min))
(save-excursion
(while (re-search-forward "\\[.*\\]" nil t)
(replace-match "" nil nil)))
(buffer-string))))
;; get a list of commands from the output of manage.py -h
;; What would be the pattern to optimize this ?
(setq dj-commands-str (split-string dj-commands-str "\n"))
(setq dj-commands-str (cl-remove-if (lambda (x) (string= x "")) dj-commands-str))
(setq dj-commands-str (mapcar (lambda (x) (s-trim x)) dj-commands-str))
(sort dj-commands-str 'string-lessp)))
(defvar elpy-django--test-runner-cache nil
"Internal cache for elpy-django--get-test-runner.
The cache is keyed on project root and DJANGO_SETTINGS_MODULE env var")
(defvar elpy-django--test-runner-cache-max-size 100
"Maximum number of entries in test runner cache")
(defun elpy-django--get-test-runner ()
"Return the name of the django test runner.
Needs `DJANGO_SETTINGS_MODULE' to be set in order to work.
The result is memoized on project root and `DJANGO_SETTINGS_MODULE'"
(let ((django-import-cmd "import django;django.setup();from django.conf import settings;print(settings.TEST_RUNNER)")
(django-settings-env (getenv "DJANGO_SETTINGS_MODULE"))
(default-directory (elpy-project-root)))
;; If no Django settings has been set, then nothing will work. Warn user
(unless django-settings-env
(error "Please set environment variable `DJANGO_SETTINGS_MODULE' if you'd like to run the test runner"))
(let* ((runner-key (list default-directory django-settings-env))
(runner (or (elpy-django--get-test-runner-from-cache runner-key)
(elpy-django--cache-test-runner
runner-key
(elpy-django--detect-test-runner django-settings-env)))))
(elpy-django--limit-test-runner-cache-size)
runner)))
(defun elpy-django--get-test-format ()
"When running a Django test, some test runners require a different format than others.
Return the correct string format here."
(let ((runner (elpy-django--get-test-runner))
(found nil)
(formats elpy-django-test-runner-formats))
(while (and formats (not found))
(let* ((entry (car formats)) (regex (car entry)))
(when (string-match regex runner)
(setq found (cdr entry))))
(setq formats (cdr formats)))
(or found (error (format "Unable to find test format for `%s'"
(elpy-django--get-test-runner))))))
(defun elpy-django--detect-test-runner (django-settings-env)
"Detects django test runner in current configuration"
;; We have to be able to import the DJANGO_SETTINGS_MODULE to detect test
;; runner; if python process importing settings exits with error,
;; then warn the user that settings is not valid
(unless (= 0 (call-process elpy-rpc-python-command nil nil nil
"-c" (format "import %s" django-settings-env)))
(error (format "Unable to import DJANGO_SETTINGS_MODULE: '%s'"
django-settings-env)))
(s-trim (shell-command-to-string
(format "%s -c '%s'" elpy-rpc-python-command
django-import-cmd))))
(defun elpy-django--get-test-runner-from-cache (key)
"Retrieve from cache test runner with given caching key.
Return nil if the runner is missing in cache"
(let ((runner (cdr (assoc key elpy-django--test-runner-cache))))
;; if present re-add to implement lru cache
(when runner (elpy-django--cache-test-runner key runner))))
(defun elpy-django--cache-test-runner (key runner)
"Store in test runner cache a runner with a key"""
(push (cons key runner) elpy-django--test-runner-cache)
runner)
(defun elpy-django--limit-test-runner-cache-size ()
"Ensure elpy-django--test-runner-cache does not overflow a fixed size"
(while (> (length elpy-django--test-runner-cache)
elpy-django--test-runner-cache-max-size)
(setq elpy-django--test-runner-cache (cdr elpy-django--test-runner-cache))))
;;;;;;;;;;;;;;;;;;;;;;
;;; User Functions
(defun elpy-django-command (cmd)
"Prompt user for Django command. If called with `C-u',
it will prompt for other flags/arguments to run."
(interactive (list (completing-read "Command: " (elpy-django--get-commands) nil nil)))
;; Called with C-u, variable is set or is a cmd that requires an argument
(when (or current-prefix-arg
elpy-django-always-prompt
(member cmd elpy-django-commands-with-req-arg))
(setq cmd (concat cmd " " (read-shell-command (concat cmd ": ") "--noinput"))))
;;
(cond ((string= cmd "shell")
(run-python (concat elpy-django-command " shell -i python") t t))
(t
(let* ((program (car (split-string elpy-django-command)))
(args (cdr (split-string elpy-django-command)))
(buffer-name (format "django-%s" (car (split-string cmd)))))
(when (get-buffer (format "*%s*" buffer-name))
(kill-buffer (format "*%s*" buffer-name)))
(pop-to-buffer
(apply 'make-comint buffer-name program nil
(append args (split-string cmd))))))))
(defun elpy-django-runserver (arg)
"Start the server and automatically add the ipaddr and port.
Also create it's own special buffer so that we can have multiple
servers running per project.
When called with a prefix (C-u), it will prompt for additional args."
(interactive "P")
(let* ((cmd (concat elpy-django-command " " elpy-django-server-command))
(proj-root (if (elpy-project-root)
(file-name-base (directory-file-name
(elpy-project-root)))
(message "Elpy cannot find the root of the current django project. Starting the server in the current directory: '%s'."
default-directory)
default-directory))
(buff-name (format "*runserver[%s]*" proj-root)))
;; Kill any previous instance of runserver since we might be doing something new
(when (get-buffer buff-name)
(kill-buffer buff-name))
(setq cmd (concat cmd " " elpy-django-server-ipaddr ":" elpy-django-server-port))
(when (or arg elpy-django-always-prompt)
(setq cmd (concat cmd " "(read-shell-command (concat cmd ": ")))))
(compile cmd)
(with-current-buffer "*compilation*"
(rename-buffer buff-name))))
(defun elpy-test-django-runner (top _file module test)
"Test the project using the Django discover runner,
or with manage.py if elpy-test-django-with-manage is true.
This requires Django 1.6 or the django-discover-runner package."
(interactive (elpy-test-at-point))
(if module
(apply #'elpy-test-run
top
(append
(list elpy-django-command)
elpy-django-test-runner-args
(list (if test
(format "%s%s%s" module (elpy-django--get-test-format) test)
module))))
(apply #'elpy-test-run
top
(append
(list elpy-django-command)
elpy-django-test-runner-args))))
(put 'elpy-test-django-runner 'elpy-test-runner-p t)
(define-minor-mode elpy-django
"Minor mode for Django commands."
:group 'elpy-django)
(provide 'elpy-django)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; elpy-django.el ends here

Binary file not shown.

View File

@@ -0,0 +1,17 @@
(define-package "elpy" "20220203.108" "Emacs Python Development Environment"
'((company "0.9.2")
(emacs "24.4")
(highlight-indentation "0.5.0")
(pyvenv "1.3")
(yasnippet "0.8.0")
(s "1.11.0"))
:commit "9b458c80dc1bcecb6345e157d8e921c1e4e8a7ea" :authors
'(("Jorgen Schaefer <contact@jorgenschaefer.de>, Gaby Launay" . "gaby.launay@protonmail.com"))
:maintainer
'("Jorgen Schaefer <contact@jorgenschaefer.de>, Gaby Launay" . "gaby.launay@protonmail.com")
:keywords
'("python" "ide" "languages" "tools")
:url "https://github.com/jorgenschaefer/elpy")
;; Local Variables:
;; no-byte-compile: t
;; End:

View File

@@ -0,0 +1,114 @@
;;; elpy-profile.el --- Profiling capabilitiss for elpy
;; Copyright (C) 2013-2019 Jorgen Schaefer
;; Author: Gaby Launay <gaby.launay@tutanota.com>
;; URL: https://github.com/jorgenschaefer/elpy
;; 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:
;; This file serves as an extension to elpy by adding profiling capabilities
;;; Code:
;;;;;;;;;;;;;;;;;;;;;;
;;; User customization
(defcustom elpy-profile-visualizer "snakeviz"
"Visualizer for elpy profile results."
:type '(choice (const :tag "Snakeviz" "snakeviz")
(const :tag "RunSnakeRun" "runsnake")
(const :tag "pyprof2calltree" "pyprof2calltree -k -i")
(string :tag "Other"))
:group 'elpy)
;;;;;;;;;;;;;;;;;;;;;;
;;; Helper Functions
(defun elpy-profile--display-profiling (file)
"Display the profile result FILE using `elpy-profile-visualizer'."
(let ((exec (car (split-string elpy-profile-visualizer " " t)))
(args (append (cdr (split-string elpy-profile-visualizer " " t)) (list file))))
(if (executable-find exec)
(apply 'call-process exec nil 0 nil args)
(message "Elpy profile visualizer '%s' not found" exec))))
(defun elpy-profile--sentinel (process string)
"Elpy profile sentinel."
(let ((filename (file-name-nondirectory (process-get process 'file)))
(prof-file (process-get process 'prof-file))
(dont-display (process-get process 'dont-display)))
(with-current-buffer "*elpy-profile-log*"
(view-mode))
(if (not (string-equal string "finished\n"))
(progn
(message "[%s] Profiling failed" filename)
(display-buffer "*elpy-profile-log*"))
(message "[%s] Profiling succeeded" filename)
(unless dont-display
(elpy-profile--display-profiling prof-file)))))
(defun elpy-profile--file (file &optional in-dir dont-display)
"Profile asynchronously FILE and display the result using
`elpy-profile-visualizer'.
If IN-DIR is non nil, profile result is saved in the same
directory as the script.
If DONT-DISPLAY is non nil, don't display the profile results."
(ignore-errors (kill-buffer "*elpy-profile-log*"))
(let* ((prof-file (if in-dir
(concat (file-name-sans-extension file) ".profile")
(concat (make-temp-file "elpy-profile-" nil ".profile"))))
(proc-name (format "elpy-profile-%s" file))
(proc-cmd (list elpy-rpc-python-command "-m" "cProfile" "-o" prof-file file))
(proc (make-process :name proc-name
:buffer "*elpy-profile-log*"
:sentinel 'elpy-profile--sentinel
:command proc-cmd)))
(message "[%s] Profiling ..." (file-name-nondirectory file))
(process-put proc 'prof-file prof-file)
(process-put proc 'file file)
(process-put proc 'dont-display dont-display)
prof-file))
;;;;;;;;;;;;;;;;;;;;;;
;;; User Functions
(defun elpy-profile-buffer-or-region (&optional in-dir dont-display)
"Profile asynchronously the active region or the current buffer
and display the result using `elpy-profile-visualizer'.
If IN-DIR is non nil, profile result is saved in the same
directory as the script.
If DONT-DISPLAY is non nil, don't display the profile results."
(interactive "P")
(let* ((file-name (buffer-name))
(file-dir (file-name-directory (buffer-file-name)))
(beg (if (region-active-p) (region-beginning) (point-min)))
(end (if (region-active-p) (region-end) (point-max)))
(tmp-file-prefix (if (region-active-p) "_region_" ""))
(tmp-file (if in-dir
(concat file-dir "/" tmp-file-prefix file-name)
(concat (make-temp-file "elpy-profile-" t) "/" tmp-file-prefix file-name)))
(region (python-shell-buffer-substring beg end)))
(with-temp-buffer
(insert region)
(write-region (point-min) (point-max) tmp-file nil t))
(elpy-profile--file tmp-file t dont-display)))
(provide 'elpy-profile)
;;; elpy-profile.el ends here

Binary file not shown.

View File

@@ -0,0 +1,321 @@
;;; elpy-refactor.el --- Refactoring mode for Elpy
;; Copyright (C) 2020 Gaby Launay
;; Author: Gaby Launay <gaby.launay@protonmail.com>
;; URL: https://github.com/jorgenschaefer/elpy
;; 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:
;; This file provides an interface, including a major mode, to use
;; refactoring options provided by the Jedi library.
;;; Code:
;; We require elpy, but elpy loads us, so we shouldn't load it back.
;; (require 'elpy)
(require 'diff-mode)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Refactor mode (for applying diffs)
(defvar elpy-refactor--saved-window-configuration nil
"Saved windows configuration, so that we can restore it after `elpy-refactor' has done its thing.")
(defvar elpy-refactor--saved-pos nil
"Line and column number of the position we were at before starting refactoring.")
(defvar elpy-refactor--modified-buffers '()
"Keep track of the buffers modified by the current refactoring sessions.")
(defun elpy-refactor--apply-diff (proj-path diff)
"Apply DIFF, looking for the files in PROJ-PATH."
(let ((current-line (line-number-at-pos (point)))
(current-col (- (point) (line-beginning-position))))
(with-current-buffer (get-buffer-create " *Elpy Refactor*")
(elpy-refactor-mode)
(let ((inhibit-read-only t))
(erase-buffer)
(insert diff))
(setq default-directory proj-path)
(goto-char (point-min))
(elpy-refactor--apply-whole-diff))
(condition-case nil
(progn
(goto-char (point-min))
(forward-line (- current-line 1))
(beginning-of-line)
(forward-char current-col))
(error))
))
(defun elpy-refactor--display-diff (proj-path diff)
"Display DIFF in a `diff-mode' window.
DIFF files should be relative to PROJ-PATH."
(setq elpy-refactor--saved-window-configuration (current-window-configuration)
elpy-refactor--saved-pos (list (line-number-at-pos (point) t)
(- (point) (line-beginning-position)))
elpy-refactor--modified-buffers '())
(with-current-buffer (get-buffer-create "*Elpy Refactor*")
(elpy-refactor-mode)
(let ((inhibit-read-only t))
(erase-buffer)
(insert (propertize
(substitute-command-keys
(concat
"\\[diff-file-next] and \\[diff-file-prev] -- Move between files\n"
"\\[diff-hunk-next] and \\[diff-hunk-prev] -- Move between hunks\n"
"\\[diff-split-hunk] -- Split the current hunk at point\n"
"\\[elpy-refactor--apply-hunk] -- Apply the current hunk\n"
"\\[diff-kill-hunk] -- Kill the current hunk\n"
"\\[elpy-refactor--apply-whole-diff] -- Apply the whole diff\n"
"\\[elpy-refactor--quit] -- Quit\n"))
'face 'bold)
"\n\n")
(align-regexp (point-min) (point-max) "\\(\\s-*\\) -- ")
(goto-char (point-min))
(while (search-forward " -- " nil t)
(replace-match " " nil t))
(goto-char (point-max))
(insert diff))
(setq default-directory proj-path)
(goto-char (point-min))
(if (diff--some-hunks-p)
(progn
(select-window (display-buffer (current-buffer)))
(diff-hunk-next))
;; quit if not diff at all...
(message "No differences to validate")
(kill-buffer (current-buffer)))))
(defvar elpy-refactor-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-c") 'elpy-refactor--apply-hunk)
(define-key map (kbd "C-c C-a") 'elpy-refactor--apply-whole-diff)
(define-key map (kbd "C-c C-x") 'diff-kill-hunk)
(define-key map (kbd "q") 'elpy-refactor--quit)
(define-key map (kbd "C-c C-k") 'elpy-refactor--quit)
(define-key map (kbd "h") 'describe-mode)
(define-key map (kbd "?") 'describe-mode)
map)
"The key map for `elpy-refactor-mode'.")
(define-derived-mode elpy-refactor-mode diff-mode "Elpy Refactor"
"Mode to display refactoring actions and ask confirmation from the user.
\\{elpy-refactor-mode-map}"
:group 'elpy
(view-mode 1))
(defun elpy-refactor--apply-hunk ()
"Apply the current hunk."
(interactive)
(save-excursion
(diff-apply-hunk))
;; keep track of modified buffers
(let ((buf (find-buffer-visiting (diff-find-file-name))))
(when buf
(add-to-list 'elpy-refactor--modified-buffers buf)))
;;
(diff-hunk-kill)
(unless (diff--some-hunks-p)
(elpy-refactor--quit)))
(defun elpy-refactor--apply-whole-diff ()
"Apply the whole diff and quit."
(interactive)
(goto-char (point-min))
(diff-hunk-next)
(while (diff--some-hunks-p)
(let ((buf (find-buffer-visiting (diff-find-file-name))))
(when buf
(add-to-list 'elpy-refactor--modified-buffers buf)))
(condition-case nil
(progn
(save-excursion
(diff-apply-hunk))
(diff-hunk-kill))
(error (diff-hunk-next)))) ;; if a hunk fail, switch to the next one
;; quit
(elpy-refactor--quit))
(defun elpy-refactor--quit ()
"Quit the refactoring session."
(interactive)
;; save modified buffers
(dolist (buf elpy-refactor--modified-buffers)
(with-current-buffer buf
(basic-save-buffer)))
(setq elpy-refactor--modified-buffers '())
;; kill refactoring buffer
(kill-buffer (current-buffer))
;; Restore window configuration
(when elpy-refactor--saved-window-configuration
(set-window-configuration elpy-refactor--saved-window-configuration)
(setq elpy-refactor--saved-window-configuration nil))
;; Restore cursor position
(when elpy-refactor--saved-pos
(goto-char (point-min))
(forward-line (- (car elpy-refactor--saved-pos) 1))
(forward-char (car (cdr elpy-refactor--saved-pos)))
(setq elpy-refactor--saved-pos nil)))
;;;;;;;;;;;;;;;;;
;; User functions
(defun elpy-refactor-rename (new-name &optional dontask)
"Rename the symbol at point to NEW-NAME.
With a prefix argument (or if DONTASK is non-nil),
do not display the diff before applying."
(interactive (list
(let ((old-name (thing-at-point 'symbol)))
(if (or (not old-name)
(not (elpy-refactor--is-valid-symbol-p old-name)))
(error "No symbol at point")
(read-string
(format "New name for '%s': "
(thing-at-point 'symbol))
(thing-at-point 'symbol))))))
(unless (and new-name
(elpy-refactor--is-valid-symbol-p new-name))
(error "'%s' is not a valid python symbol"))
(message "Gathering occurences of '%s'..."
(thing-at-point 'symbol))
(let* ((elpy-rpc-timeout 10) ;; refactoring can be long...
(diff (elpy-rpc-get-rename-diff new-name))
(proj-path (alist-get 'project_path diff))
(success (alist-get 'success diff))
(diff (alist-get 'diff diff)))
(cond ((not success)
(error "Refactoring failed for some reason"))
((string= success "Not available")
(error "This functionnality needs jedi > 0.17.0, please update"))
((or dontask current-prefix-arg)
(message "Replacing '%s' with '%s'..."
(thing-at-point 'symbol)
new-name)
(elpy-refactor--apply-diff proj-path diff)
(message "Done"))
(t
(elpy-refactor--display-diff proj-path diff)))))
(defun elpy-refactor-extract-variable (new-name)
"Extract the current region to a new variable NEW-NAME."
(interactive "sNew name: ")
(let ((beg (if (region-active-p)
(region-beginning)
(car (or (bounds-of-thing-at-point 'symbol)
(error "No symbol at point")))))
(end (if (region-active-p)
(region-end)
(cdr (bounds-of-thing-at-point 'symbol)))))
(when (or (elpy-refactor--is-valid-symbol-p new-name)
(y-or-n-p "'%s' does not appear to be a valid python symbol. Are you sure you want to use it? "))
(let* ((line-beg (save-excursion
(goto-char beg)
(line-number-at-pos)))
(line-end (save-excursion
(goto-char end)
(line-number-at-pos)))
(col-beg (save-excursion
(goto-char beg)
(- (point) (line-beginning-position))))
(col-end (save-excursion
(goto-char end)
(- (point) (line-beginning-position))))
(diff (elpy-rpc-get-extract-variable-diff
new-name line-beg line-end col-beg col-end))
(proj-path (alist-get 'project_path diff))
(success (alist-get 'success diff))
(diff (alist-get 'diff diff)))
(cond ((not success)
(error "We could not extract the selection as a variable"))
((string= success "Not available")
(error "This functionnality needs jedi > 0.17.0, please update"))
(t
(deactivate-mark)
(elpy-refactor--apply-diff proj-path diff)))))))
(defun elpy-refactor-extract-function (new-name)
"Extract the current region to a new function NEW-NAME."
(interactive "sNew function name: ")
(unless (region-active-p)
(error "No selection"))
(when (or (elpy-refactor--is-valid-symbol-p new-name)
(y-or-n-p "'%s' does not appear to be a valid python symbol. Are you sure you want to use it? "))
(let* ((line-beg (save-excursion
(goto-char (region-beginning))
(line-number-at-pos)))
(line-end (save-excursion
(goto-char (region-end))
(line-number-at-pos)))
(col-beg (save-excursion
(goto-char (region-beginning))
(- (point) (line-beginning-position))))
(col-end (save-excursion
(goto-char (region-end))
(- (point) (line-beginning-position))))
(diff (elpy-rpc-get-extract-function-diff
new-name line-beg line-end col-beg col-end))
(proj-path (alist-get 'project_path diff))
(success (alist-get 'success diff))
(diff (alist-get 'diff diff)))
(cond ((not success)
(error "We could not extract the selection as a function"))
((string= success "Not available")
(error "This functionnality needs jedi > 0.17.0, please update"))
(t
(deactivate-mark)
(elpy-refactor--apply-diff proj-path diff))))))
(defun elpy-refactor-inline ()
"Inline the variable at point."
(interactive)
(let* ((diff (elpy-rpc-get-inline-diff))
(proj-path (alist-get 'project_path diff))
(success (alist-get 'success diff))
(diff (alist-get 'diff diff)))
(cond ((not success)
(error "We could not inline the variable '%s'"
(thing-at-point 'symbol)))
((string= success "Not available")
(error "This functionnality needs jedi > 0.17.0, please update"))
(t
(elpy-refactor--apply-diff proj-path diff)))))
;;;;;;;;;;;;
;; Utilities
(defun elpy-refactor--is-valid-symbol-p (symbol)
"Return t if SYMBOL is a valid python symbol."
(eq 0 (string-match "^[a-zA-Z_][a-zA-Z0-9_]*$" symbol)))
;;;;;;;;;;;;
;; Compatibility
(unless (fboundp 'diff--some-hunks-p)
(defun diff--some-hunks-p ()
(save-excursion
(goto-char (point-min))
(re-search-forward diff-hunk-header-re nil t))))
(provide 'elpy-refactor)
;;; elpy-refactor.el ends here

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,41 @@
# Elpy, the Emacs Lisp Python Environment
# Copyright (C) 2013-2019 Jorgen Schaefer
# Author: Jorgen Schaefer <contact@jorgenschaefer.de>
# URL: http://github.com/jorgenschaefer/elpy
# 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/>.
"""The Emacs Lisp Python Environment.
Elpy is a mode for Emacs to support writing Python code. This package
provides the backend within Python to support auto-completion,
documentation extraction, and navigation.
Emacs will start the protocol by running the module itself, like so:
python -m elpy
This will emit a greeting string on a single line, and then wait for
the protocol to start. Details of the protocol can be found in
elpy.rpc.
This package is unlikely to be useful on its own.
"""
__author__ = "Jorgen Schaefer"
__version__ = "1.35.0"
__license__ = "GPL"

View File

@@ -0,0 +1,25 @@
"""Main interface to the RPC server.
You should be able to just run the following to use this module:
python -m elpy
The first line should be "elpy-rpc ready". If it isn't, something
broke.
"""
import os
import sys
import elpy
from elpy.server import ElpyRPCServer
if __name__ == '__main__':
stdin = sys.stdin
stdout = sys.stdout
sys.stdout = sys.stderr = open(os.devnull, "w")
stdout.write('elpy-rpc ready ({0})\n'
.format(elpy.__version__))
stdout.flush()
ElpyRPCServer(stdin, stdout).serve_forever()

View File

@@ -0,0 +1,27 @@
"""Glue for the "autopep8" library.
"""
from elpy.rpc import Fault
import os
try:
import autopep8
except ImportError: # pragma: no cover
autopep8 = None
def fix_code(code, directory):
"""Formats Python code to conform to the PEP 8 style guide.
"""
if not autopep8:
raise Fault('autopep8 not installed, cannot fix code.',
code=400)
old_dir = os.getcwd()
try:
os.chdir(directory)
return autopep8.fix_code(code, apply_config=True)
finally:
os.chdir(old_dir)

View File

@@ -0,0 +1,79 @@
"""Glue for the "black" library.
"""
import sys
from elpy.rpc import Fault
# in case pkg_resources is not properly installed
# (see https://github.com/jorgenschaefer/elpy/issues/1674).
try:
from pkg_resources import parse_version
except ImportError: # pragma: no cover
def parse_version(*arg, **kwargs):
raise Fault("`pkg_resources` could not be imported, "
"please reinstall Elpy RPC virtualenv with"
" `M-x elpy-rpc-reinstall-virtualenv`", code=400)
import os
try:
import toml
except ImportError:
toml = None
BLACK_NOT_SUPPORTED = sys.version_info < (3, 6)
try:
if BLACK_NOT_SUPPORTED: # pragma: no cover
black = None
else:
import black
current_version = parse_version(black.__version__)
if current_version >= parse_version("21.5b1"):
from black.files import find_pyproject_toml
elif current_version >= parse_version("20.8b0"):
from black import find_pyproject_toml
else:
find_pyproject_toml = None
except ImportError: # pragma: no cover
black = None
def fix_code(code, directory):
"""Formats Python code to conform to the PEP 8 style guide.
"""
if not black:
raise Fault("black not installed", code=400)
# Get black config from pyproject.toml
line_length = black.DEFAULT_LINE_LENGTH
string_normalization = True
if find_pyproject_toml:
pyproject_path = find_pyproject_toml((directory,))
else:
pyproject_path = os.path.join(directory, "pyproject.toml")
if toml and pyproject_path and os.path.exists(pyproject_path):
pyproject_config = toml.load(pyproject_path)
black_config = pyproject_config.get("tool", {}).get("black", {})
if "line-length" in black_config:
line_length = black_config["line-length"]
if "skip-string-normalization" in black_config:
string_normalization = not black_config["skip-string-normalization"]
try:
if parse_version(black.__version__) < parse_version("19.0"):
reformatted_source = black.format_file_contents(
src_contents=code, line_length=line_length, fast=False)
else:
fm = black.FileMode(
line_length=line_length,
string_normalization=string_normalization)
reformatted_source = black.format_file_contents(
src_contents=code, fast=False, mode=fm)
return reformatted_source
except black.NothingChanged:
return code
except Exception as e:
raise Fault("Error during formatting: {}".format(e), code=400)

View File

@@ -0,0 +1,33 @@
"""Python 2/3 compatibility definitions.
These are used by the rest of Elpy to keep compatibility definitions
in one place.
"""
import sys
if sys.version_info >= (3, 0):
PYTHON3 = True
from io import StringIO
def ensure_not_unicode(obj):
return obj
else:
PYTHON3 = False
from StringIO import StringIO # noqa
def ensure_not_unicode(obj):
"""Return obj. If it's a unicode string, convert it to str first.
Pydoc functions simply don't find anything for unicode
strings. No idea why.
"""
if isinstance(obj, unicode):
return obj.encode("utf-8")
else:
return obj

View File

@@ -0,0 +1,738 @@
"""Elpy backend using the Jedi library.
This backend uses the Jedi library:
https://github.com/davidhalter/jedi
"""
import sys
import traceback
import re
import jedi
from elpy import rpc
from elpy.rpc import Fault
# in case pkg_resources is not properly installed
# (see https://github.com/jorgenschaefer/elpy/issues/1674).
try:
from pkg_resources import parse_version
except ImportError: # pragma: no cover
def parse_version(*arg, **kwargs):
raise Fault("`pkg_resources` could not be imported, "
"please reinstall Elpy RPC virtualenv with"
" `M-x elpy-rpc-reinstall-virtualenv`", code=400)
JEDISUP17 = parse_version(jedi.__version__) >= parse_version("0.17.0")
JEDISUP18 = parse_version(jedi.__version__) >= parse_version("0.18.0")
class JediBackend(object):
"""The Jedi backend class.
Implements the RPC calls we can pass on to Jedi.
Documentation: http://jedi.jedidjah.ch/en/latest/docs/plugin-api.html
"""
name = "jedi"
def __init__(self, project_root, environment_binaries_path):
self.project_root = project_root
self.environment = None
if environment_binaries_path is not None:
self.environment = jedi.create_environment(environment_binaries_path,
safe=False)
self.completions = {}
sys.path.append(project_root)
# Backward compatibility to jedi<17
if not JEDISUP17: # pragma: no cover
self.rpc_get_completions = self.rpc_get_completions_jedi16
self.rpc_get_docstring = self.rpc_get_docstring_jedi16
self.rpc_get_definition = self.rpc_get_definition_jedi16
self.rpc_get_assignment = self.rpc_get_assignment_jedi16
self.rpc_get_calltip = self.rpc_get_calltip_jedi16
self.rpc_get_oneline_docstring = self.rpc_get_oneline_docstring_jedi16
self.rpc_get_usages = self.rpc_get_usages_jedi16
self.rpc_get_names = self.rpc_get_names_jedi16
def rpc_get_completions(self, filename, source, offset):
line, column = pos_to_linecol(source, offset)
proposals = run_with_debug(jedi, 'complete', code=source,
path=filename,
environment=self.environment,
fun_kwargs={'line': line, 'column': column})
self.completions = dict((proposal.name, proposal)
for proposal in proposals)
return [{'name': proposal.name.rstrip("="),
'suffix': proposal.complete.rstrip("="),
'annotation': proposal.type,
'meta': proposal.description}
for proposal in proposals]
def rpc_get_completions_jedi16(self, filename, source, offset):
# Backward compatibility to jedi<17
line, column = pos_to_linecol(source, offset)
proposals = run_with_debug(jedi, 'completions',
source=source, line=line, column=column,
path=filename, encoding='utf-8',
environment=self.environment)
if proposals is None:
return []
self.completions = dict((proposal.name, proposal)
for proposal in proposals)
return [{'name': proposal.name.rstrip("="),
'suffix': proposal.complete.rstrip("="),
'annotation': proposal.type,
'meta': proposal.description}
for proposal in proposals]
def rpc_get_completion_docstring(self, completion):
proposal = self.completions.get(completion)
if proposal is None:
return None
else:
return proposal.docstring(fast=False)
def rpc_get_completion_location(self, completion):
proposal = self.completions.get(completion)
if proposal is None:
return None
else:
return (proposal.module_path, proposal.line)
def rpc_get_docstring(self, filename, source, offset):
line, column = pos_to_linecol(source, offset)
locations = run_with_debug(jedi, 'goto',
code=source,
path=filename,
environment=self.environment,
fun_kwargs={'line': line,
'column': column,
'follow_imports': True,
'follow_builtin_imports': True})
if not locations:
return None
# Filter uninteresting things
if locations[-1].name in ["str", "int", "float", "bool", "tuple",
"list", "dict"]:
return None
if locations[-1].docstring():
return ('Documentation for {0}:\n\n'.format(
locations[-1].full_name) + locations[-1].docstring())
else:
return None
def rpc_get_docstring_jedi16(self, filename, source, offset):
# Backward compatibility to jedi<17
line, column = pos_to_linecol(source, offset)
locations = run_with_debug(jedi, 'goto_definitions',
source=source, line=line, column=column,
path=filename, encoding='utf-8',
environment=self.environment)
if not locations:
return None
# Filter uninteresting things
if locations[-1].name in ["str", "int", "float", "bool", "tuple",
"list", "dict"]:
return None
if locations[-1].docstring():
return ('Documentation for {0}:\n\n'.format(
locations[-1].full_name) + locations[-1].docstring())
else:
return None
def rpc_get_definition(self, filename, source, offset):
line, column = pos_to_linecol(source, offset)
locations = run_with_debug(jedi, 'goto',
code=source,
path=filename,
environment=self.environment,
fun_kwargs={'line': line,
'column': column,
'follow_imports': True,
'follow_builtin_imports': True})
if not locations:
return None
# goto_definitions() can return silly stuff like __builtin__
# for int variables, so we remove them. See issue #76.
locations = [
loc for loc in locations
if (loc.module_path is not None
and loc.module_name != 'builtins'
and loc.module_name != '__builtin__')]
if len(locations) == 0:
return None
loc = locations[-1]
try:
if loc.module_path == filename:
offset = linecol_to_pos(source,
loc.line,
loc.column)
else:
with open(loc.module_path) as f:
offset = linecol_to_pos(f.read(),
loc.line,
loc.column)
except IOError: # pragma: no cover
return None
return (loc.module_path, offset)
def rpc_get_definition_jedi16(self, filename, source, offset): # pragma: no cover
# Backward compatibility to jedi<17
line, column = pos_to_linecol(source, offset)
locations = run_with_debug(jedi, 'goto_definitions',
source=source, line=line, column=column,
path=filename, encoding='utf-8',
environment=self.environment)
# goto_definitions() can return silly stuff like __builtin__
# for int variables, so we fall back on goto() in those
# cases. See issue #76.
if (
locations and
(locations[0].module_path is None
or locations[0].module_name == 'builtins'
or locations[0].module_name == '__builtin__')
):
locations = run_with_debug(jedi, 'goto_assignments',
source=source, line=line,
column=column,
path=filename,
encoding='utf-8',
environment=self.environment)
if not locations:
return None
else:
loc = locations[-1]
try:
if loc.module_path:
if loc.module_path == filename:
offset = linecol_to_pos(source,
loc.line,
loc.column)
else:
with open(loc.module_path) as f:
offset = linecol_to_pos(f.read(),
loc.line,
loc.column)
else:
return None
except IOError:
return None
return (loc.module_path, offset)
def rpc_get_assignment(self, filename, source, offset):
raise Fault("Obsolete since jedi 17.0. Please use 'get_definition'.")
def rpc_get_assignment_jedi16(self, filename, source, offset): # pragma: no cover
# Backward compatibility to jedi<17
line, column = pos_to_linecol(source, offset)
locations = run_with_debug(jedi, 'goto_assignments',
source=source, line=line, column=column,
path=filename, encoding='utf-8',
environment=self.environment)
if not locations:
return None
else:
loc = locations[-1]
try:
if loc.module_path:
if loc.module_path == filename:
offset = linecol_to_pos(source,
loc.line,
loc.column)
else:
with open(loc.module_path) as f:
offset = linecol_to_pos(f.read(),
loc.line,
loc.column)
else:
return None
except IOError:
return None
return (loc.module_path, offset)
def rpc_get_calltip(self, filename, source, offset):
line, column = pos_to_linecol(source, offset)
calls = run_with_debug(jedi, 'get_signatures',
code=source,
path=filename,
environment=self.environment,
fun_kwargs={'line': line,
'column': column})
if not calls:
return None
params = [re.sub("^param ", '', param.description)
for param in calls[0].params]
return {"name": calls[0].name,
"index": calls[0].index,
"params": params}
def rpc_get_calltip_jedi16(self, filename, source, offset): # pragma: no cover
# Backward compatibility to jedi<17
line, column = pos_to_linecol(source, offset)
calls = run_with_debug(jedi, 'call_signatures',
source=source, line=line, column=column,
path=filename, encoding='utf-8',
environment=self.environment)
if calls:
call = calls[0]
else:
call = None
if not call:
return None
# Strip 'param' added by jedi at the beginning of
# parameter names. Should be unecessary for jedi > 0.13.0
params = [re.sub("^param ", '', param.description)
for param in call.params]
return {"name": call.name,
"index": call.index,
"params": params}
def rpc_get_calltip_or_oneline_docstring(self, filename, source, offset):
"""
Return the current function calltip or its oneline docstring.
Meant to be used with eldoc.
"""
# Try to get a oneline docstring then
docs = self.rpc_get_oneline_docstring(filename=filename,
source=source,
offset=offset)
if docs is not None:
if docs['doc'] != "No documentation":
docs['kind'] = 'oneline_doc'
return docs
# Try to get a calltip
calltip = self.rpc_get_calltip(filename=filename, source=source,
offset=offset)
if calltip is not None:
calltip['kind'] = 'calltip'
return calltip
# Ok, no calltip, just display the function name
if docs is not None:
docs['kind'] = 'oneline_doc'
return docs
# Giving up...
return None
def rpc_get_oneline_docstring(self, filename, source, offset):
"""Return a oneline docstring for the symbol at offset"""
line, column = pos_to_linecol(source, offset)
definitions = run_with_debug(jedi, 'goto',
code=source,
path=filename,
environment=self.environment,
fun_kwargs={'line': line,
'column': column})
if not definitions:
return None
# avoid unintersting stuff
definitions = [defi for defi in definitions
if defi.name not in
["str", "int", "float", "bool", "tuple",
"list", "dict"]]
if len(definitions) == 0:
return None
definition = definitions[0]
# Get name
if definition.type in ['function', 'class']:
raw_name = definition.name
name = '{}()'.format(raw_name)
doc = definition.docstring().split('\n')
elif definition.type in ['module']:
raw_name = definition.name
name = '{} {}'.format(raw_name, definition.type)
doc = definition.docstring().split('\n')
elif (definition.type in ['instance']
and hasattr(definition, "name")):
raw_name = definition.name
name = raw_name
doc = definition.docstring().split('\n')
else:
return None
# Keep only the first paragraph that is not a function declaration
lines = []
call = "{}(".format(raw_name)
# last line
doc.append('')
for i in range(len(doc)):
if doc[i] == '' and len(lines) != 0:
paragraph = " ".join(lines)
lines = []
if call != paragraph[0:len(call)]:
break
paragraph = ""
continue
lines.append(doc[i])
# Keep only the first sentence
onelinedoc = paragraph.split('. ', 1)
if len(onelinedoc) == 2:
onelinedoc = onelinedoc[0] + '.'
else:
onelinedoc = onelinedoc[0]
if onelinedoc == '':
onelinedoc = "No documentation"
return {"name": name,
"doc": onelinedoc}
def rpc_get_oneline_docstring_jedi16(self, filename, source, offset): # pragma: no cover
"""Return a oneline docstring for the symbol at offset"""
# Backward compatibility to jedi<17
line, column = pos_to_linecol(source, offset)
definitions = run_with_debug(jedi, 'goto_definitions',
source=source, line=line, column=column,
path=filename, encoding='utf-8',
environment=self.environment)
# avoid unintersting stuff
try:
if definitions[0].name in ["str", "int", "float", "bool", "tuple",
"list", "dict"]:
return None
except:
pass
assignments = run_with_debug(jedi, 'goto_assignments',
source=source, line=line, column=column,
path=filename, encoding='utf-8',
environment=self.environment)
if definitions:
definition = definitions[0]
else:
definition = None
if assignments:
assignment = assignments[0]
else:
assignment = None
if definition:
# Get name
if definition.type in ['function', 'class']:
raw_name = definition.name
name = '{}()'.format(raw_name)
doc = definition.docstring().split('\n')
elif definition.type in ['module']:
raw_name = definition.name
name = '{} {}'.format(raw_name, definition.type)
doc = definition.docstring().split('\n')
elif (definition.type in ['instance']
and hasattr(assignment, "name")):
raw_name = assignment.name
name = raw_name
doc = assignment.docstring().split('\n')
else:
return None
# Keep only the first paragraph that is not a function declaration
lines = []
call = "{}(".format(raw_name)
# last line
doc.append('')
for i in range(len(doc)):
if doc[i] == '' and len(lines) != 0:
paragraph = " ".join(lines)
lines = []
if call != paragraph[0:len(call)]:
break
paragraph = ""
continue
lines.append(doc[i])
# Keep only the first sentence
onelinedoc = paragraph.split('. ', 1)
if len(onelinedoc) == 2:
onelinedoc = onelinedoc[0] + '.'
else:
onelinedoc = onelinedoc[0]
if onelinedoc == '':
onelinedoc = "No documentation"
return {"name": name,
"doc": onelinedoc}
return None
def rpc_get_usages(self, filename, source, offset):
"""Return the uses of the symbol at offset.
Returns a list of occurrences of the symbol, as dicts with the
fields name, filename, and offset.
"""
line, column = pos_to_linecol(source, offset)
uses = run_with_debug(jedi, 'get_references',
code=source,
path=filename,
environment=self.environment,
fun_kwargs={'line': line,
'column': column})
if uses is None:
return None
result = []
for use in uses:
if use.module_path == filename:
offset = linecol_to_pos(source, use.line, use.column)
elif use.module_path is not None:
with open(use.module_path) as f:
text = f.read()
offset = linecol_to_pos(text, use.line, use.column)
result.append({"name": use.name,
"filename": use.module_path,
"offset": offset})
return result
def rpc_get_usages_jedi16(self, filename, source, offset): # pragma: no cover
"""Return the uses of the symbol at offset.
Returns a list of occurrences of the symbol, as dicts with the
fields name, filename, and offset.
"""
# Backward compatibility to jedi<17
line, column = pos_to_linecol(source, offset)
uses = run_with_debug(jedi, 'usages',
source=source, line=line, column=column,
path=filename, encoding='utf-8',
environment=self.environment)
if uses is None:
return None
result = []
for use in uses:
if use.module_path == filename:
offset = linecol_to_pos(source, use.line, use.column)
elif use.module_path is not None:
with open(use.module_path) as f:
text = f.read()
offset = linecol_to_pos(text, use.line, use.column)
result.append({"name": use.name,
"filename": use.module_path,
"offset": offset})
return result
def rpc_get_names(self, filename, source, offset):
"""Return the list of possible names"""
names = run_with_debug(jedi, 'get_names',
code=source,
path=filename,
environment=self.environment,
fun_kwargs={'all_scopes': True,
'definitions': True,
'references': True})
result = []
for name in names:
if name.module_path == filename:
offset = linecol_to_pos(source, name.line, name.column)
elif name.module_path is not None:
with open(name.module_path) as f:
text = f.read()
offset = linecol_to_pos(text, name.line, name.column)
result.append({"name": name.name,
"filename": name.module_path,
"offset": offset})
return result
def rpc_get_names_jedi16(self, filename, source, offset): # pragma: no cover
"""Return the list of possible names"""
# Backward compatibility to jedi<17
names = jedi.api.names(source=source,
path=filename, encoding='utf-8',
all_scopes=True,
definitions=True,
references=True)
result = []
for name in names:
if name.module_path == filename:
offset = linecol_to_pos(source, name.line, name.column)
elif name.module_path is not None:
with open(name.module_path) as f:
text = f.read()
offset = linecol_to_pos(text, name.line, name.column)
result.append({"name": name.name,
"filename": name.module_path,
"offset": offset})
return result
def rpc_get_rename_diff(self, filename, source, offset, new_name):
"""Get the diff resulting from renaming the thing at point"""
if not hasattr(jedi.Script, "rename"): # pragma: no cover
return {'success': "Not available"}
line, column = pos_to_linecol(source, offset)
ren = run_with_debug(jedi, 'rename', code=source,
path=filename,
environment=self.environment,
fun_kwargs={'line': line,
'column': column,
'new_name': new_name})
if ren is None:
return {'success': False}
else:
return {'success': True,
'project_path': ren._inference_state.project._path,
'diff': ren.get_diff(),
'changed_files': list(ren.get_changed_files().keys())}
def rpc_get_extract_variable_diff(self, filename, source, offset, new_name,
line_beg, line_end, col_beg, col_end):
"""Get the diff resulting from extracting the selected code"""
if not hasattr(jedi.Script, "extract_variable"): # pragma: no cover
return {'success': "Not available"}
ren = run_with_debug(jedi, 'extract_variable', code=source,
path=filename,
environment=self.environment,
fun_kwargs={'line': line_beg,
'until_line': line_end,
'column': col_beg,
'until_column': col_end,
'new_name': new_name})
if ren is None:
return {'success': False}
else:
return {'success': True,
'project_path': ren._inference_state.project._path,
'diff': ren.get_diff(),
'changed_files': list(ren.get_changed_files().keys())}
def rpc_get_extract_function_diff(self, filename, source, offset, new_name,
line_beg, line_end, col_beg, col_end):
"""Get the diff resulting from extracting the selected code"""
if not hasattr(jedi.Script, "extract_function"): # pragma: no cover
return {'success': "Not available"}
ren = run_with_debug(jedi, 'extract_function', code=source,
path=filename,
environment=self.environment,
fun_kwargs={'line': line_beg,
'until_line': line_end,
'column': col_beg,
'until_column': col_end,
'new_name': new_name})
if ren is None:
return {'success': False}
else:
return {'success': True,
'project_path': ren._inference_state.project._path,
'diff': ren.get_diff(),
'changed_files': list(ren.get_changed_files().keys())}
def rpc_get_inline_diff(self, filename, source, offset):
"""Get the diff resulting from inlining the selected variable"""
if not hasattr(jedi.Script, "inline"): # pragma: no cover
return {'success': "Not available"}
line, column = pos_to_linecol(source, offset)
ren = run_with_debug(jedi, 'inline', code=source,
path=filename,
environment=self.environment,
fun_kwargs={'line': line,
'column': column})
if ren is None:
return {'success': False}
else:
return {'success': True,
'project_path': ren._inference_state.project._path,
'diff': ren.get_diff(),
'changed_files': list(ren.get_changed_files().keys())}
# From the Jedi documentation:
#
# line is the current line you want to perform actions on (starting
# with line #1 as the first line). column represents the current
# column/indent of the cursor (starting with zero). source_path
# should be the path of your file in the file system.
def pos_to_linecol(text, pos):
"""Return a tuple of line and column for offset pos in text.
Lines are one-based, columns zero-based.
This is how Jedi wants it. Don't ask me why.
"""
line_start = text.rfind("\n", 0, pos) + 1
line = text.count("\n", 0, line_start) + 1
col = pos - line_start
return line, col
def linecol_to_pos(text, line, col):
"""Return the offset of this line and column in text.
Lines are one-based, columns zero-based.
This is how Jedi wants it. Don't ask me why.
"""
nth_newline_offset = 0
for i in range(line - 1):
new_offset = text.find("\n", nth_newline_offset)
if new_offset < 0:
raise ValueError("Text does not have {0} lines."
.format(line))
nth_newline_offset = new_offset + 1
offset = nth_newline_offset + col
if offset > len(text):
raise ValueError("Line {0} column {1} is not within the text"
.format(line, col))
return offset
def run_with_debug(jedi, name, fun_kwargs={}, *args, **kwargs):
re_raise = kwargs.pop('re_raise', ())
try:
script = jedi.Script(*args, **kwargs)
return getattr(script, name)(**fun_kwargs)
except Exception as e:
if isinstance(e, re_raise):
raise
if JEDISUP17:
if isinstance(e, jedi.RefactoringError):
return None
# Bug jedi#485
if (
isinstance(e, ValueError) and
"invalid \\x escape" in str(e)
):
return None
# Bug jedi#485 in Python 3
if (
isinstance(e, SyntaxError) and
"truncated \\xXX escape" in str(e)
):
return None
from jedi import debug
debug_info = []
def _debug(level, str_out):
if level == debug.NOTICE:
prefix = "[N]"
elif level == debug.WARNING:
prefix = "[W]"
else:
prefix = "[?]"
debug_info.append(u"{0} {1}".format(prefix, str_out))
jedi.set_debug_function(_debug, speed=False)
try:
script = jedi.Script(*args, **kwargs)
return getattr(script, name)()
except Exception as e:
source = kwargs.get('source')
sc_args = []
sc_args.extend(repr(arg) for arg in args)
sc_args.extend("{0}={1}".format(k, "source" if k == "source"
else repr(v))
for (k, v) in kwargs.items())
data = {
"traceback": traceback.format_exc(),
"jedi_debug_info": {'script_args': ", ".join(sc_args),
'source': source,
'method': name,
'debug_info': debug_info}
}
raise rpc.Fault(message=str(e),
code=500,
data=data)
finally:
jedi.set_debug_function(None)

View File

@@ -0,0 +1,13 @@
import json
from elpy.jedibackend import JEDISUP18
if JEDISUP18:
import pathlib
class JSONEncoder(json.JSONEncoder):
def default(self, o):
if JEDISUP18:
if isinstance(o, pathlib.Path):
return str(o)
return super().default(o)

View File

@@ -0,0 +1,91 @@
import sys
import types
from pydoc import safeimport, resolve, ErrorDuringImport
from pkgutil import iter_modules
from elpy import compat
# Types we want to recurse into (nodes).
CONTAINER_TYPES = (type, types.ModuleType)
# Types of attributes we can get documentation for (leaves).
PYDOC_TYPES = (type,
types.FunctionType,
types.BuiltinFunctionType,
types.BuiltinMethodType,
types.MethodType,
types.ModuleType)
if not compat.PYTHON3: # pragma: nocover
# Python 2 old style classes
CONTAINER_TYPES = tuple(list(CONTAINER_TYPES) + [types.ClassType])
PYDOC_TYPES = tuple(list(PYDOC_TYPES) + [types.ClassType])
def get_pydoc_completions(modulename):
"""Get possible completions for modulename for pydoc.
Returns a list of possible values to be passed to pydoc.
"""
modulename = compat.ensure_not_unicode(modulename)
modulename = modulename.rstrip(".")
if modulename == "":
return sorted(get_modules())
candidates = get_completions(modulename)
if candidates:
return sorted(candidates)
needle = modulename
if "." in needle:
modulename, part = needle.rsplit(".", 1)
candidates = get_completions(modulename)
else:
candidates = get_modules()
return sorted(candidate for candidate in candidates
if candidate.startswith(needle))
def get_completions(modulename):
modules = set("{0}.{1}".format(modulename, module)
for module in get_modules(modulename))
try:
module, name = resolve(modulename)
except ImportError:
return modules
if isinstance(module, CONTAINER_TYPES):
modules.update("{0}.{1}".format(modulename, name)
for name in dir(module)
if not name.startswith("_") and
isinstance(getattr(module, name),
PYDOC_TYPES))
return modules
def get_modules(modulename=None):
"""Return a list of modules and packages under modulename.
If modulename is not given, return a list of all top level modules
and packages.
"""
modulename = compat.ensure_not_unicode(modulename)
if not modulename:
try:
return ([modname for (importer, modname, ispkg)
in iter_modules()
if not modname.startswith("_")] +
list(sys.builtin_module_names))
except OSError:
# Bug in Python 2.6, see #275
return list(sys.builtin_module_names)
try:
module = safeimport(modulename)
except ErrorDuringImport:
return []
if module is None:
return []
if hasattr(module, "__path__"):
return [modname for (importer, modname, ispkg)
in iter_modules(module.__path__)
if not modname.startswith("_")]
return []

View File

@@ -0,0 +1,153 @@
"""A simple JSON-RPC-like server.
The server will read and write lines of JSON-encoded method calls and
responses.
See the documentation of the JSONRPCServer class for further details.
"""
import json
import sys
import traceback
class JSONRPCServer(object):
"""Simple JSON-RPC-like server.
This class will read single-line JSON expressions from stdin,
decode them, and pass them to a handler. Return values from the
handler will be JSON-encoded and written to stdout.
To implement a handler, you need to subclass this class and add
methods starting with "rpc_". Methods then will be found.
Method calls should be encoded like this:
{"id": 23, "method": "method_name", "params": ["foo", "bar"]}
This will call self.rpc_method("foo", "bar").
Responses will be encoded like this:
{"id": 23, "result": "foo"}
Errors will be encoded like this:
{"id": 23, "error": "Simple error message"}
See http://www.jsonrpc.org/ for the inspiration of the protocol.
"""
def __init__(self, stdin=None, stdout=None):
"""Return a new JSON-RPC server object.
It will read lines of JSON data from stdin, and write the
responses to stdout.
"""
if stdin is None:
self.stdin = sys.stdin
else:
self.stdin = stdin
if stdout is None:
self.stdout = sys.stdout
else:
self.stdout = stdout
def read_json(self):
"""Read a single line and decode it as JSON.
Can raise an EOFError() when the input source was closed.
"""
line = self.stdin.readline()
if line == '':
raise EOFError()
return json.loads(line)
def write_json(self, **kwargs):
"""Write an JSON object on a single line.
The keyword arguments are interpreted as a single JSON object.
It's not possible with this method to write non-objects.
"""
from elpy.json_encoder import JSONEncoder
serialized_value = JSONEncoder().encode(kwargs)
self.stdout.write(serialized_value + "\n")
self.stdout.flush()
def handle_request(self):
"""Handle a single JSON-RPC request.
Read a request, call the appropriate handler method, and
return the encoded result. Errors in the handler method are
caught and encoded as error objects. Errors in the decoding
phase are not caught, as we can not respond with an error
response to them.
"""
request = self.read_json()
if 'method' not in request:
raise ValueError("Received a bad request: {0}"
.format(request))
method_name = request['method']
request_id = request.get('id', None)
params = request.get('params') or []
try:
method = getattr(self, "rpc_" + method_name, None)
if method is not None:
result = method(*params)
else:
result = self.handle(method_name, params)
if request_id is not None:
self.write_json(result=result,
id=request_id)
except Fault as fault:
error = {"message": fault.message,
"code": fault.code}
if fault.data is not None:
error["data"] = fault.data
self.write_json(error=error, id=request_id)
except Exception as e:
error = {"message": str(e),
"code": 500,
"data": {"traceback": traceback.format_exc()}}
self.write_json(error=error, id=request_id)
def handle(self, method_name, args):
"""Handle the call to method_name.
You should overwrite this method in a subclass.
"""
raise Fault("Unknown method {0}".format(method_name))
def serve_forever(self):
"""Serve requests forever.
Errors are not caught, so this is a slight misnomer.
"""
while True:
try:
self.handle_request()
except (KeyboardInterrupt, EOFError, SystemExit):
break
class Fault(Exception):
"""RPC Fault instances.
code defines the severity of the warning.
2xx: Normal behavior lead to end of operation, i.e. a warning
4xx: An expected error occurred
5xx: An unexpected error occurred (usually includes a traceback)
"""
def __init__(self, message, code=500, data=None):
super(Fault, self).__init__(message)
self.message = message
self.code = code
self.data = data

View File

@@ -0,0 +1,281 @@
"""Method implementations for the Elpy JSON-RPC server.
This file implements the methods exported by the JSON-RPC server. It
handles backend selection and passes methods on to the selected
backend.
"""
import io
import os
import pydoc
from elpy.pydocutils import get_pydoc_completions
from elpy.rpc import JSONRPCServer, Fault
from elpy.auto_pep8 import fix_code
from elpy.yapfutil import fix_code as fix_code_with_yapf
from elpy.blackutil import fix_code as fix_code_with_black
try:
from elpy import jedibackend
except ImportError: # pragma: no cover
jedibackend = None
class ElpyRPCServer(JSONRPCServer):
"""The RPC server for elpy.
See the rpc_* methods for exported method documentation.
"""
def __init__(self, *args, **kwargs):
super(ElpyRPCServer, self).__init__(*args, **kwargs)
self.backend = None
self.project_root = None
def _call_backend(self, method, default, *args, **kwargs):
"""Call the backend method with args.
If there is currently no backend, return default."""
meth = getattr(self.backend, method, None)
if meth is None:
return default
else:
return meth(*args, **kwargs)
def rpc_echo(self, *args):
"""Return the arguments.
This is a simple test method to see if the protocol is
working.
"""
return args
def rpc_init(self, options):
self.project_root = options["project_root"]
self.env = options["environment"]
if jedibackend:
self.backend = jedibackend.JediBackend(self.project_root, self.env)
else:
self.backend = None
return {
'jedi_available': (self.backend is not None)
}
def rpc_get_calltip(self, filename, source, offset):
"""Get the calltip for the function at the offset.
"""
return self._call_backend("rpc_get_calltip", None, filename,
get_source(source), offset)
def rpc_get_oneline_docstring(self, filename, source, offset):
"""Get a oneline docstring for the symbol at the offset.
"""
return self._call_backend("rpc_get_oneline_docstring", None, filename,
get_source(source), offset)
def rpc_get_calltip_or_oneline_docstring(self, filename, source, offset):
"""Get a calltip or a oneline docstring for the symbol at the offset.
"""
return self._call_backend("rpc_get_calltip_or_oneline_docstring",
None, filename,
get_source(source), offset)
def rpc_get_completions(self, filename, source, offset):
"""Get a list of completion candidates for the symbol at offset.
"""
results = self._call_backend("rpc_get_completions", [], filename,
get_source(source), offset)
# Uniquify by name
results = list(dict((res['name'], res) for res in results)
.values())
results.sort(key=lambda cand: _pysymbol_key(cand["name"]))
return results
def rpc_get_completion_docstring(self, completion):
"""Return documentation for a previously returned completion.
"""
return self._call_backend("rpc_get_completion_docstring",
None, completion)
def rpc_get_completion_location(self, completion):
"""Return the location for a previously returned completion.
This returns a list of [file name, line number].
"""
return self._call_backend("rpc_get_completion_location", None,
completion)
def rpc_get_definition(self, filename, source, offset):
"""Get the location of the definition for the symbol at the offset.
"""
return self._call_backend("rpc_get_definition", None, filename,
get_source(source), offset)
def rpc_get_assignment(self, filename, source, offset):
"""Get the location of the assignment for the symbol at the offset.
"""
return self._call_backend("rpc_get_assignment", None, filename,
get_source(source), offset)
def rpc_get_docstring(self, filename, source, offset):
"""Get the docstring for the symbol at the offset.
"""
return self._call_backend("rpc_get_docstring", None, filename,
get_source(source), offset)
def rpc_get_pydoc_completions(self, name=None):
"""Return a list of possible strings to pass to pydoc.
If name is given, the strings are under name. If not, top
level modules are returned.
"""
return get_pydoc_completions(name)
def rpc_get_pydoc_documentation(self, symbol):
"""Get the Pydoc documentation for the given symbol.
Uses pydoc and can return a string with backspace characters
for bold highlighting.
"""
try:
docstring = pydoc.render_doc(str(symbol),
"Elpy Pydoc Documentation for %s",
False)
except (ImportError, pydoc.ErrorDuringImport):
return None
else:
if isinstance(docstring, bytes):
docstring = docstring.decode("utf-8", "replace")
return docstring
def rpc_get_usages(self, filename, source, offset):
"""Get usages for the symbol at point.
"""
source = get_source(source)
return self._call_backend("rpc_get_usages",
None, filename, source, offset)
def rpc_get_names(self, filename, source, offset):
"""Get all possible names
"""
source = get_source(source)
return self._call_backend("rpc_get_names",
None, filename, source, offset)
def rpc_get_rename_diff(self, filename, source, offset, new_name):
"""Get the diff resulting from renaming the thing at point
"""
source = get_source(source)
return self._call_backend("rpc_get_rename_diff",
None, filename, source, offset, new_name)
def rpc_get_extract_variable_diff(self, filename, source, offset, new_name,
line_beg, line_end, col_beg, col_end):
"""Get the diff resulting from extracting the selected code
"""
source = get_source(source)
return self._call_backend("rpc_get_extract_variable_diff",
None, filename, source, offset,
new_name, line_beg, line_end, col_beg,
col_end)
def rpc_get_extract_function_diff(self, filename, source, offset, new_name,
line_beg, line_end, col_beg, col_end):
"""Get the diff resulting from extracting the selected code
"""
source = get_source(source)
return self._call_backend("rpc_get_extract_function_diff",
None, filename, source, offset, new_name,
line_beg, line_end, col_beg, col_end)
def rpc_get_inline_diff(self, filename, source, offset):
"""Get the diff resulting from inlining the thing at point.
"""
source = get_source(source)
return self._call_backend("rpc_get_inline_diff",
None, filename, source, offset)
def rpc_fix_code(self, source, directory):
"""Formats Python code to conform to the PEP 8 style guide.
"""
source = get_source(source)
return fix_code(source, directory)
def rpc_fix_code_with_yapf(self, source, directory):
"""Formats Python code to conform to the PEP 8 style guide.
"""
source = get_source(source)
return fix_code_with_yapf(source, directory)
def rpc_fix_code_with_black(self, source, directory):
"""Formats Python code to conform to the PEP 8 style guide.
"""
source = get_source(source)
return fix_code_with_black(source, directory)
def get_source(fileobj):
"""Translate fileobj into file contents.
fileobj is either a string or a dict. If it's a string, that's the
file contents. If it's a string, then the filename key contains
the name of the file whose contents we are to use.
If the dict contains a true value for the key delete_after_use,
the file should be deleted once read.
"""
if not isinstance(fileobj, dict):
return fileobj
else:
try:
with io.open(fileobj["filename"], encoding="utf-8",
errors="ignore") as f:
return f.read()
finally:
if fileobj.get('delete_after_use'):
try:
os.remove(fileobj["filename"])
except: # pragma: no cover
pass
def _pysymbol_key(name):
"""Return a sortable key index for name.
Sorting is case-insensitive, with the first underscore counting as
worse than any character, but subsequent underscores do not. This
means that dunder symbols (like __init__) are sorted after symbols
that start with an alphabetic character, but before those that
start with only a single underscore.
"""
if name.startswith("_"):
name = "~" + name[1:]
return name.lower()

View File

@@ -0,0 +1,8 @@
"""Unit tests for elpy."""
try:
import unittest2
import sys
sys.modules['unittest'] = unittest2
except:
pass

View File

@@ -0,0 +1,18 @@
"""Python 2/3 compatibility definitions.
These are used by the rest of Elpy to keep compatibility definitions
in one place.
"""
import sys
if sys.version_info >= (3, 0):
PYTHON3 = True
import builtins
from io import StringIO
else:
PYTHON3 = False
import __builtin__ as builtins # noqa
from StringIO import StringIO # noqa

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
# coding: utf-8
"""Tests for the elpy.autopep8 module"""
import unittest
import os
from elpy import auto_pep8
from elpy.tests.support import BackendTestCase
class Autopep8TestCase(BackendTestCase):
def setUp(self):
if not auto_pep8.autopep8:
raise unittest.SkipTest
def test_fix_code(self):
code_block = 'x= 123\n'
new_block = auto_pep8.fix_code(code_block, os.getcwd())
self.assertEqual(new_block, 'x = 123\n')

View File

@@ -0,0 +1,64 @@
# coding: utf-8
"""Tests for the elpy.black module"""
import unittest
import os
from elpy import blackutil
from elpy.rpc import Fault
from elpy.tests.support import BackendTestCase
@unittest.skipIf(blackutil.BLACK_NOT_SUPPORTED,
'black not supported for current python version')
class BLACKTestCase(BackendTestCase):
def setUp(self):
if blackutil.BLACK_NOT_SUPPORTED:
raise unittest.SkipTest
def test_fix_code_should_throw_error_for_invalid_code(self):
src = 'x = '
self.assertRaises(Fault, blackutil.fix_code, src, os.getcwd())
def test_fix_code_should_throw_error_without_black_installed(self):
black = blackutil.black
blackutil.black = None
src = 'x= 123\n', 'x = 123\n'
with self.assertRaises(Fault):
blackutil.fix_code(src, os.getcwd())
blackutil.black = black
def test_fix_code(self):
testdata = [
('x= 123\n', 'x = 123\n'),
('x=1; \ny=2 \n', 'x = 1\ny = 2\n'),
]
for src, expected in testdata:
self._assert_format(src, expected)
def test_perfect_code(self):
testdata = [
('x = 123\n', 'x = 123\n'),
('x = 1\ny = 2\n', 'x = 1\ny = 2\n'),
]
for src, expected in testdata:
self._assert_format(src, expected)
def _assert_format(self, src, expected):
new_block = blackutil.fix_code(src, os.getcwd())
self.assertEqual(new_block, expected)
def test_should_read_options_from_pyproject_toml(self):
with open('pyproject.toml', 'w') as f:
f.write('[tool.black]\nline-length = 10')
self.addCleanup(os.remove, 'pyproject.toml')
testdata = [('x= 123\n', 'x = 123\n'),
('x=1; \ny=2 \n', 'x = 1\ny = 2\n'),
('x, y, z, a, b, c = 123, 124, 125, 126, 127, 128',
'(\n x,\n y,\n z,\n a,\n b,\n c,\n)'
' = (\n 123,\n 124,\n 125,'
'\n 126,\n 127,\n 128,\n)\n')]
for src, expected in testdata:
self._assert_format(src, expected)

View File

@@ -0,0 +1,383 @@
"""Tests for the elpy.jedibackend module."""
import sys
import unittest
import jedi
try:
from unittest import mock
except ImportError:
import mock
import re
from elpy import jedibackend
from elpy import rpc
from elpy.tests import compat
from elpy.tests.support import BackendTestCase
from elpy.tests.support import RPCGetCompletionsTests
from elpy.tests.support import RPCGetCompletionDocstringTests
from elpy.tests.support import RPCGetCompletionLocationTests
from elpy.tests.support import RPCGetDocstringTests
from elpy.tests.support import RPCGetOnelineDocstringTests
from elpy.tests.support import RPCGetDefinitionTests
from elpy.tests.support import RPCGetCalltipTests
from elpy.tests.support import RPCGetUsagesTests
from elpy.tests.support import RPCGetNamesTests
from elpy.tests.support import RPCGetRenameDiffTests
from elpy.tests.support import RPCGetExtractFunctionDiffTests
from elpy.tests.support import RPCGetExtractVariableDiffTests
from elpy.tests.support import RPCGetInlineDiffTests
from elpy.tests.support import RPCGetAssignmentTests
class JediBackendTestCase(BackendTestCase):
def setUp(self):
super(JediBackendTestCase, self).setUp()
env = jedi.get_default_environment().path
self.backend = jedibackend.JediBackend(self.project_root, env)
class TestInit(JediBackendTestCase):
def test_should_have_jedi_as_name(self):
self.assertEqual(self.backend.name, "jedi")
class TestRPCGetCompletions(RPCGetCompletionsTests,
JediBackendTestCase):
BUILTINS = ['object', 'oct', 'open', 'ord', 'OSError', 'OverflowError']
class TestRPCGetAssignment(RPCGetAssignmentTests,
JediBackendTestCase):
pass
class TestRPCGetCompletionDocstring(RPCGetCompletionDocstringTests,
JediBackendTestCase):
pass
class TestRPCGetCompletionLocation(RPCGetCompletionLocationTests,
JediBackendTestCase):
pass
class TestRPCGetDocstring(RPCGetDocstringTests,
JediBackendTestCase):
def __init__(self, *args, **kwargs):
super(TestRPCGetDocstring, self).__init__(*args, **kwargs)
self.JSON_LOADS_REGEX = (
r'loads\(s.*, encoding.*, cls.*, object_hook.*, parse_float.*, '
r'parse_int.*, .*\)'
)
def check_docstring(self, docstring):
lines = docstring.splitlines()
self.assertEqual(lines[0], 'Documentation for json.loads:')
match = re.match(self.JSON_LOADS_REGEX, lines[2])
self.assertIsNotNone(match)
@mock.patch("elpy.jedibackend.run_with_debug")
def test_should_not_return_empty_docstring(self, run_with_debug):
location = mock.MagicMock()
location.full_name = "testthing"
location.docstring.return_value = ""
run_with_debug.return_value = [location]
filename = self.project_file("test.py", "print")
docstring = self.backend.rpc_get_docstring(filename, "print", 0)
self.assertIsNone(docstring)
class TestRPCGetOnelineDocstring(RPCGetOnelineDocstringTests,
JediBackendTestCase):
def __init__(self, *args, **kwargs):
super(TestRPCGetOnelineDocstring, self).__init__(*args, **kwargs)
if sys.version_info >= (3, 6):
self.JSON_LOADS_DOCSTRING = (
'Deserialize ``s`` (a ``str``, ``bytes`` or'
' ``bytearray`` instance containing a JSON'
' document) to a Python object.'
)
self.JSON_DOCSTRING = (
"JSON (JavaScript Object Notation) <http://json.org>"
" is a subset of JavaScript syntax (ECMA-262"
" 3rd edition) used as a lightweight data interchange format.")
elif sys.version_info >= (3, 0):
self.JSON_LOADS_DOCSTRING = (
'Deserialize ``s`` (a ``str`` instance '
'containing a JSON document) to a Python object.'
)
self.JSON_DOCSTRING = (
"JSON (JavaScript Object Notation) <http://json.org>"
" is a subset of JavaScript syntax (ECMA-262"
" 3rd edition) used as a lightweight data interchange format.")
else:
self.JSON_LOADS_DOCSTRING = (
'Deserialize ``s`` (a ``str`` or ``unicode`` '
'instance containing a JSON document) to a Python object.'
)
self.JSON_DOCSTRING = (
"JSON (JavaScript Object Notation) <http://json.org>"
" is a subset of JavaScript syntax (ECMA-262"
" 3rd edition) used as a lightweight data interchange format.")
@mock.patch("elpy.jedibackend.run_with_debug")
def test_should_not_return_empty_docstring(self, run_with_debug):
location = mock.MagicMock()
location.full_name = "testthing"
location.docstring.return_value = ""
run_with_debug.return_value = [location]
filename = self.project_file("test.py", "print")
docstring = self.backend.rpc_get_oneline_docstring(filename, "print", 0)
self.assertIsNone(docstring)
class TestRPCGetDefinition(RPCGetDefinitionTests,
JediBackendTestCase):
@mock.patch("jedi.Script")
def test_should_not_fail_if_module_path_is_none(self, Script):
"""Do not fail if loc.module_path is None.
This can happen under some circumstances I am unsure about.
See #537 for the issue that reported this.
"""
locations = [
mock.Mock(module_path=None)
]
script = Script.return_value
script.goto_definitions.return_value = locations
script.goto_assignments.return_value = locations
location = self.rpc("", "", 0)
self.assertIsNone(location)
class TestRPCGetRenameDiff(RPCGetRenameDiffTests,
JediBackendTestCase):
pass
class TestRPCGetExtractFunctionDiff(RPCGetExtractFunctionDiffTests,
JediBackendTestCase):
pass
class TestRPCGetExtractVariableDiff(RPCGetExtractVariableDiffTests,
JediBackendTestCase):
pass
class TestRPCGetInlineDiff(RPCGetInlineDiffTests,
JediBackendTestCase):
pass
class TestRPCGetCalltip(RPCGetCalltipTests,
JediBackendTestCase):
KEYS_CALLTIP = {'index': None,
'params': [],
'name': u'keys'}
RADIX_CALLTIP = {'index': None,
'params': [],
'name': u'radix'}
ADD_CALLTIP = {'index': 0,
'params': [u'a', u'b'],
'name': u'add'}
if compat.PYTHON3:
THREAD_CALLTIP = {'name': 'Thread',
'index': 0,
'params': ['group: None=...',
'target: Optional[Callable[..., Any]]=...',
'name: Optional[str]=...',
'args: Iterable[Any]=...',
'kwargs: Mapping[str, Any]=...',
'daemon: Optional[bool]=...']}
else:
THREAD_CALLTIP = {'index': 0,
'name': u'Thread',
'params': [u'group: None=...',
u'target: Optional[Callable[..., Any]]=...',
u'name: Optional[str]=...',
u'args: Iterable[Any]=...',
u'kwargs: Mapping[str, Any]=...']}
def test_should_not_fail_with_get_subscope_by_name(self):
# Bug #677 / jedi#628
source = (
u"my_lambda = lambda x: x+1\n"
u"my_lambda(1)"
)
filename = self.project_file("project.py", source)
offset = 37
sigs = self.backend.rpc_get_calltip(filename, source, offset)
sigs["index"]
class TestRPCGetUsages(RPCGetUsagesTests,
JediBackendTestCase):
def test_should_not_fail_for_missing_module(self):
# This causes use.module_path to be None
source = "import sys\n\nsys.path.\n" # insert()"
offset = 21
filename = self.project_file("project.py", source)
self.rpc(filename, source, offset)
class TestRPCGetNames(RPCGetNamesTests,
JediBackendTestCase):
pass
class TestPosToLinecol(unittest.TestCase):
def test_should_handle_beginning_of_string(self):
self.assertEqual(jedibackend.pos_to_linecol("foo", 0),
(1, 0))
def test_should_handle_end_of_line(self):
self.assertEqual(jedibackend.pos_to_linecol("foo\nbar\nbaz\nqux", 9),
(3, 1))
def test_should_handle_end_of_string(self):
self.assertEqual(jedibackend.pos_to_linecol("foo\nbar\nbaz\nqux", 14),
(4, 2))
class TestLinecolToPos(unittest.TestCase):
def test_should_handle_beginning_of_string(self):
self.assertEqual(jedibackend.linecol_to_pos("foo", 1, 0),
0)
def test_should_handle_end_of_string(self):
self.assertEqual(jedibackend.linecol_to_pos("foo\nbar\nbaz\nqux",
3, 1),
9)
def test_should_return_offset(self):
self.assertEqual(jedibackend.linecol_to_pos("foo\nbar\nbaz\nqux",
4, 2),
14)
def test_should_fail_for_line_past_text(self):
self.assertRaises(ValueError,
jedibackend.linecol_to_pos, "foo\n", 3, 1)
def test_should_fail_for_column_past_text(self):
self.assertRaises(ValueError,
jedibackend.linecol_to_pos, "foo\n", 1, 10)
class TestRunWithDebug(unittest.TestCase):
@mock.patch('jedi.Script')
def test_should_call_method(self, Script):
Script.return_value.test_method.return_value = "test-result"
result = jedibackend.run_with_debug(jedi, 'test_method', {}, 1, 2,
arg=3)
Script.assert_called_with(1, 2, arg=3)
self.assertEqual(result, 'test-result')
@mock.patch('jedi.Script')
def test_should_re_raise(self, Script):
Script.side_effect = RuntimeError
with self.assertRaises(RuntimeError):
jedibackend.run_with_debug(jedi, 'test_method', 1, 2, arg=3,
re_raise=(RuntimeError,))
@mock.patch('jedi.Script')
@mock.patch('jedi.set_debug_function')
def test_should_keep_debug_info(self, set_debug_function, Script):
Script.side_effect = RuntimeError
try:
jedibackend.run_with_debug(jedi, 'test_method', {}, 1, 2, arg=3)
except rpc.Fault as e:
self.assertGreaterEqual(e.code, 400)
self.assertIsNotNone(e.data)
self.assertIn("traceback", e.data)
jedi_debug_info = e.data["jedi_debug_info"]
self.assertIsNotNone(jedi_debug_info)
self.assertEqual(jedi_debug_info["script_args"],
"1, 2, arg=3")
self.assertEqual(jedi_debug_info["source"], None)
self.assertEqual(jedi_debug_info["method"], "test_method")
self.assertEqual(jedi_debug_info["debug_info"], [])
else:
self.fail("Fault not thrown")
@mock.patch('jedi.Script')
@mock.patch('jedi.set_debug_function')
def test_should_keep_error_text(self, set_debug_function, Script):
Script.side_effect = RuntimeError
try:
jedibackend.run_with_debug(jedi, 'test_method', {}, 1, 2, arg=3)
except rpc.Fault as e:
self.assertEqual(str(e), str(RuntimeError()))
self.assertEqual(e.message, str(RuntimeError()))
else:
self.fail("Fault not thrown")
@mock.patch('jedi.Script')
@mock.patch('jedi.set_debug_function')
def test_should_handle_source_special(self, set_debug_function, Script):
Script.side_effect = RuntimeError
try:
jedibackend.run_with_debug(jedi, 'test_method', source="foo")
except rpc.Fault as e:
self.assertEqual(e.data["jedi_debug_info"]["script_args"],
"source=source")
self.assertEqual(e.data["jedi_debug_info"]["source"], "foo")
else:
self.fail("Fault not thrown")
@mock.patch('jedi.Script')
@mock.patch('jedi.set_debug_function')
def test_should_set_debug_info(self, set_debug_function, Script):
the_debug_function = [None]
def my_set_debug_function(debug_function, **kwargs):
the_debug_function[0] = debug_function
def my_script(*args, **kwargs):
the_debug_function[0](jedi.debug.NOTICE, "Notice")
the_debug_function[0](jedi.debug.WARNING, "Warning")
the_debug_function[0]("other", "Other")
raise RuntimeError
set_debug_function.side_effect = my_set_debug_function
Script.return_value.test_method = my_script
try:
jedibackend.run_with_debug(jedi, 'test_method', source="foo")
except rpc.Fault as e:
self.assertEqual(e.data["jedi_debug_info"]["debug_info"],
["[N] Notice",
"[W] Warning",
"[?] Other"])
else:
self.fail("Fault not thrown")
@mock.patch('jedi.set_debug_function')
@mock.patch('jedi.Script')
def test_should_not_fail_with_bad_data(self, Script, set_debug_function):
import jedi.debug
def set_debug(function, speed=True):
if function is not None:
function(jedi.debug.NOTICE, u"\xab")
set_debug_function.side_effect = set_debug
Script.return_value.test_method.side_effect = Exception
with self.assertRaises(rpc.Fault):
jedibackend.run_with_debug(jedi, 'test_method', {}, 1, 2, arg=3)

View File

@@ -0,0 +1,91 @@
import os
import unittest
import shutil
import sys
import tempfile
try:
from unittest import mock
except ImportError:
import mock
import elpy.pydocutils
class TestGetPydocCompletions(unittest.TestCase):
def test_should_return_top_level_modules(self):
modules = elpy.pydocutils.get_pydoc_completions("")
self.assertIn('sys', modules)
self.assertIn('json', modules)
def test_should_return_submodules(self):
modules = elpy.pydocutils.get_pydoc_completions("elpy")
self.assertIn("elpy.rpc", modules)
self.assertIn("elpy.server", modules)
modules = elpy.pydocutils.get_pydoc_completions("os")
self.assertIn("os.path", modules)
def test_should_find_objects_in_module(self):
self.assertIn("elpy.tests.test_pydocutils.TestGetPydocCompletions",
elpy.pydocutils.get_pydoc_completions
("elpy.tests.test_pydocutils"))
def test_should_find_attributes_of_objects(self):
attribs = elpy.pydocutils.get_pydoc_completions(
"elpy.tests.test_pydocutils.TestGetPydocCompletions")
self.assertIn("elpy.tests.test_pydocutils.TestGetPydocCompletions."
"test_should_find_attributes_of_objects",
attribs)
def test_should_return_none_for_inexisting_module(self):
self.assertEqual([],
elpy.pydocutils.get_pydoc_completions
("does_not_exist"))
def test_should_work_for_unicode_strings(self):
self.assertIsNotNone(elpy.pydocutils.get_pydoc_completions
(u"sys"))
def test_should_find_partial_completions(self):
self.assertIn("multiprocessing",
elpy.pydocutils.get_pydoc_completions
("multiprocess"))
self.assertIn("multiprocessing.util",
elpy.pydocutils.get_pydoc_completions
("multiprocessing.ut"))
def test_should_ignore_trailing_dot(self):
self.assertIn("elpy.pydocutils",
elpy.pydocutils.get_pydoc_completions
("elpy."))
class TestGetModules(unittest.TestCase):
def test_should_return_top_level_modules(self):
modules = elpy.pydocutils.get_modules()
self.assertIn('sys', modules)
self.assertIn('json', modules)
def test_should_return_submodules(self):
modules = elpy.pydocutils.get_modules("elpy")
self.assertIn("rpc", modules)
self.assertIn("server", modules)
@mock.patch.object(elpy.pydocutils, 'safeimport')
def test_should_catch_import_errors(self, safeimport):
def raise_function(message):
raise elpy.pydocutils.ErrorDuringImport(message,
(None, None, None))
safeimport.side_effect = raise_function
self.assertEqual([], elpy.pydocutils.get_modules("foo.bar"))
def test_should_not_fail_for_permission_denied(self):
tmpdir = tempfile.mkdtemp(prefix="test-elpy-get-modules-")
sys.path.append(tmpdir)
os.chmod(tmpdir, 0o000)
try:
elpy.pydocutils.get_modules()
finally:
os.chmod(tmpdir, 0o755)
shutil.rmtree(tmpdir)
sys.path.remove(tmpdir)

View File

@@ -0,0 +1,209 @@
"""Tests for elpy.rpc."""
import json
import unittest
import sys
from elpy import rpc
from elpy.tests.compat import StringIO
class TestFault(unittest.TestCase):
def test_should_have_code_and_data(self):
fault = rpc.Fault("Hello", code=250, data="Fnord")
self.assertEqual(str(fault), "Hello")
self.assertEqual(fault.code, 250)
self.assertEqual(fault.data, "Fnord")
def test_should_have_defaults_for_code_and_data(self):
fault = rpc.Fault("Hello")
self.assertEqual(str(fault), "Hello")
self.assertEqual(fault.code, 500)
self.assertIsNone(fault.data)
class TestJSONRPCServer(unittest.TestCase):
def setUp(self):
self.stdin = StringIO()
self.stdout = StringIO()
self.rpc = rpc.JSONRPCServer(self.stdin, self.stdout)
def write(self, s):
self.stdin.seek(0)
self.stdin.truncate()
self.stdout.seek(0)
self.stdout.truncate()
self.stdin.write(s)
self.stdin.seek(0)
def read(self):
value = self.stdout.getvalue()
self.stdin.seek(0)
self.stdin.truncate()
self.stdout.seek(0)
self.stdout.truncate()
return value
class TestInit(TestJSONRPCServer):
def test_should_use_arguments(self):
self.assertEqual(self.rpc.stdin, self.stdin)
self.assertEqual(self.rpc.stdout, self.stdout)
def test_should_default_to_sys(self):
testrpc = rpc.JSONRPCServer()
self.assertEqual(sys.stdin, testrpc.stdin)
self.assertEqual(sys.stdout, testrpc.stdout)
class TestReadJson(TestJSONRPCServer):
def test_should_read_json(self):
objlist = [{'foo': 'bar'},
{'baz': 'qux', 'fnord': 'argl\nbargl'},
"beep\r\nbeep\r\nbeep"]
self.write("".join([(json.dumps(obj) + "\n")
for obj in objlist]))
for obj in objlist:
self.assertEqual(self.rpc.read_json(),
obj)
def test_should_raise_eof_on_eof(self):
self.assertRaises(EOFError, self.rpc.read_json)
def test_should_fail_on_malformed_json(self):
self.write("malformed json\n")
self.assertRaises(ValueError,
self.rpc.read_json)
class TestWriteJson(TestJSONRPCServer):
def test_should_write_json_line(self):
objlist = [{'foo': 'bar'},
{'baz': 'qux', 'fnord': 'argl\nbargl'},
]
for obj in objlist:
self.rpc.write_json(**obj)
self.assertEqual(json.loads(self.read()),
obj)
class TestHandleRequest(TestJSONRPCServer):
def test_should_fail_if_json_does_not_contain_a_method(self):
self.write(json.dumps(dict(params=[],
id=23)))
self.assertRaises(ValueError,
self.rpc.handle_request)
def test_should_call_right_method(self):
self.write(json.dumps(dict(method='foo',
params=[1, 2, 3],
id=23)))
self.rpc.rpc_foo = lambda *params: params
self.rpc.handle_request()
self.assertEqual(json.loads(self.read()),
dict(id=23,
result=[1, 2, 3]))
def test_should_pass_defaults_for_missing_parameters(self):
def test_method(*params):
self.args = params
self.write(json.dumps(dict(method='foo')))
self.rpc.rpc_foo = test_method
self.rpc.handle_request()
self.assertEqual(self.args, ())
self.assertEqual(self.read(), "")
def test_should_return_error_for_missing_method(self):
self.write(json.dumps(dict(method='foo',
id=23)))
self.rpc.handle_request()
result = json.loads(self.read())
self.assertEqual(result["id"], 23)
self.assertEqual(result["error"]["message"],
"Unknown method foo")
def test_should_return_error_for_exception_in_method(self):
def test_method():
raise ValueError("An error was raised")
self.write(json.dumps(dict(method='foo',
id=23)))
self.rpc.rpc_foo = test_method
self.rpc.handle_request()
result = json.loads(self.read())
self.assertEqual(result["id"], 23)
self.assertEqual(result["error"]["message"], "An error was raised")
self.assertIn("traceback", result["error"]["data"])
def test_should_not_include_traceback_for_faults(self):
def test_method():
raise rpc.Fault("This is a fault")
self.write(json.dumps(dict(method="foo",
id=23)))
self.rpc.rpc_foo = test_method
self.rpc.handle_request()
result = json.loads(self.read())
self.assertEqual(result["id"], 23)
self.assertEqual(result["error"]["message"], "This is a fault")
self.assertNotIn("traceback", result["error"])
def test_should_add_data_for_faults(self):
def test_method():
raise rpc.Fault("St. Andreas' Fault",
code=12345, data="Yippieh")
self.write(json.dumps(dict(method="foo", id=23)))
self.rpc.rpc_foo = test_method
self.rpc.handle_request()
result = json.loads(self.read())
self.assertEqual(result["error"]["data"], "Yippieh")
def test_should_call_handle_for_unknown_method(self):
def test_handle(method_name, args):
return "It works"
self.write(json.dumps(dict(method="doesnotexist",
id=23)))
self.rpc.handle = test_handle
self.rpc.handle_request()
self.assertEqual(json.loads(self.read()),
dict(id=23,
result="It works"))
class TestServeForever(TestJSONRPCServer):
def handle_request(self):
self.hr_called += 1
if self.hr_called > 10:
raise self.error()
def setUp(self):
super(TestServeForever, self).setUp()
self.hr_called = 0
self.error = KeyboardInterrupt
self.rpc.handle_request = self.handle_request
def test_should_call_handle_request_repeatedly(self):
self.rpc.serve_forever()
self.assertEqual(self.hr_called, 11)
def test_should_return_on_some_errors(self):
self.error = KeyboardInterrupt
self.rpc.serve_forever()
self.error = EOFError
self.rpc.serve_forever()
self.error = SystemExit
self.rpc.serve_forever()
def test_should_fail_on_most_errors(self):
self.error = RuntimeError
self.assertRaises(RuntimeError,
self.rpc.serve_forever)

View File

@@ -0,0 +1,384 @@
# coding: utf-8
"""Tests for the elpy.server module"""
import os
import tempfile
import unittest
try:
from unittest import mock
except ImportError:
import mock
from elpy import rpc
from elpy import server
from elpy.tests import compat
from elpy.tests.support import BackendTestCase
class ServerTestCase(unittest.TestCase):
def setUp(self):
self.srv = server.ElpyRPCServer()
class BackendCallTestCase(ServerTestCase):
def assert_calls_backend(self, method, add_args=[], add_kwargs={}):
with mock.patch("elpy.server.get_source") as get_source:
with mock.patch.object(self.srv, "backend") as backend:
get_source.return_value = "transformed source"
getattr(self.srv, method)("filename", "source", "offset",
*add_args,
**add_kwargs)
get_source.assert_called_with("source")
getattr(backend, method).assert_called_with(
"filename", "transformed source", "offset",
*add_args,
**add_kwargs
)
class TestInit(ServerTestCase):
def test_should_not_select_a_backend_by_default(self):
self.assertIsNone(self.srv.backend)
class TestRPCEcho(ServerTestCase):
def test_should_return_arguments(self):
self.assertEqual(("hello", "world"),
self.srv.rpc_echo("hello", "world"))
class TestRPCInit(ServerTestCase):
@mock.patch("elpy.jedibackend.JediBackend")
def test_should_set_project_root(self, JediBackend):
self.srv.rpc_init({"project_root": "/project/root",
"environment": "/project/env"})
self.assertEqual("/project/root", self.srv.project_root)
@mock.patch("jedi.create_environment")
def test_should_set_project_env(self, create_environment):
self.srv.rpc_init({"project_root": "/project/root",
"environment": "/project/env"})
create_environment.assert_called_with("/project/env", safe=False)
@mock.patch("elpy.jedibackend.JediBackend")
def test_should_initialize_jedi(self, JediBackend):
self.srv.rpc_init({"project_root": "/project/root",
"environment": "/project/env"})
JediBackend.assert_called_with("/project/root", "/project/env")
@mock.patch("elpy.jedibackend.JediBackend")
def test_should_use_jedi_if_available(self, JediBackend):
JediBackend.return_value.name = "jedi"
self.srv.rpc_init({"project_root": "/project/root",
"environment": "/project/env"})
self.assertEqual("jedi", self.srv.backend.name)
@mock.patch("elpy.jedibackend.JediBackend")
def test_should_use_none_if_nothing_available(
self, JediBackend):
JediBackend.return_value.name = "jedi"
old_jedi = server.jedibackend
server.jedibackend = None
try:
self.srv.rpc_init({"project_root": "/project/root",
"environment": "/project/env"})
finally:
server.jedibackend = old_jedi
self.assertIsNone(self.srv.backend)
class TestRPCGetCalltip(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_calltip")
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_calltip("filname", "source",
"offset"))
class TestRPCGetCompletions(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_completions")
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertEqual([],
self.srv.rpc_get_completions("filname", "source",
"offset"))
def test_should_sort_results(self):
with mock.patch.object(self.srv, 'backend') as backend:
backend.rpc_get_completions.return_value = [
{'name': '_e'},
{'name': '__d'},
{'name': 'c'},
{'name': 'B'},
{'name': 'a'},
]
expected = list(reversed(backend.rpc_get_completions.return_value))
actual = self.srv.rpc_get_completions("filename", "source",
"offset")
self.assertEqual(expected, actual)
def test_should_uniquify_results(self):
with mock.patch.object(self.srv, 'backend') as backend:
backend.rpc_get_completions.return_value = [
{'name': 'a'},
{'name': 'a'},
]
expected = [{'name': 'a'}]
actual = self.srv.rpc_get_completions("filename", "source",
"offset")
self.assertEqual(expected, actual)
class TestRPCGetCompletionDocs(ServerTestCase):
def test_should_call_backend(self):
with mock.patch.object(self.srv, "backend") as backend:
self.srv.rpc_get_completion_docstring("completion")
(backend.rpc_get_completion_docstring
.assert_called_with("completion"))
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_completion_docstring("foo"))
class TestRPCGetCompletionLocation(ServerTestCase):
def test_should_call_backend(self):
with mock.patch.object(self.srv, "backend") as backend:
self.srv.rpc_get_completion_location("completion")
(backend.rpc_get_completion_location
.assert_called_with("completion"))
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_completion_location("foo"))
class TestRPCGetDefinition(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_definition")
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_definition("filname", "source",
"offset"))
class TestRPCGetDocstring(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_docstring")
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_docstring("filname", "source",
"offset"))
class TestRPCGetOnelineDocstring(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_oneline_docstring")
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_oneline_docstring("filname",
"source",
"offset"))
class TestRPCGetCalltipOrOnelineDocstring(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_calltip_or_oneline_docstring")
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(
self.srv.rpc_get_calltip_or_oneline_docstring("filname",
"source",
"offset"))
class TestRPCGetRenameDiff(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_rename_diff",
add_args=['new_name'])
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_rename_diff("filname", "source",
"offset", "new_name"))
class TestRPCGetExtract_VariableDiff(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_extract_variable_diff",
add_args=['name', 12, 13, 3, 5])
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_extract_variable_diff(
"filname", "source", "offset", "new_name", 1, 1, 0, 3))
class TestRPCGetExtract_FunctionDiff(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_extract_function_diff",
add_args=['name', 12, 13, 3, 5])
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_extract_function_diff(
"filname", "source", "offset", "new_name", 1, 1, 0, 4))
class TestRPCGetInlineDiff(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_inline_diff")
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_inline_diff("filname", "source",
"offset"))
class TestRPCGetPydocCompletions(ServerTestCase):
@mock.patch.object(server, 'get_pydoc_completions')
def test_should_call_pydoc_completions(self, get_pydoc_completions):
srv = server.ElpyRPCServer()
srv.rpc_get_pydoc_completions()
get_pydoc_completions.assert_called_with(None)
srv.rpc_get_pydoc_completions("foo")
get_pydoc_completions.assert_called_with("foo")
class TestGetPydocDocumentation(ServerTestCase):
@mock.patch("pydoc.render_doc")
def test_should_find_documentation(self, render_doc):
render_doc.return_value = "expected"
actual = self.srv.rpc_get_pydoc_documentation("open")
render_doc.assert_called_with("open",
"Elpy Pydoc Documentation for %s",
False)
self.assertEqual("expected", actual)
def test_should_return_none_for_unknown_module(self):
actual = self.srv.rpc_get_pydoc_documentation("frob.open")
self.assertIsNone(actual)
def test_should_return_valid_unicode(self):
import json
docstring = self.srv.rpc_get_pydoc_documentation("tarfile")
json.dumps(docstring)
class TestRPCGetUsages(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_usages")
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_usages("filname", "source",
"offset"))
class TestRPCGetNames(BackendCallTestCase):
def test_should_call_backend(self):
self.assert_calls_backend("rpc_get_names")
def test_should_handle_no_backend(self):
self.srv.backend = None
self.assertIsNone(self.srv.rpc_get_names("filname", "source", 0))
class TestGetSource(unittest.TestCase):
def test_should_return_string_by_default(self):
self.assertEqual(server.get_source("foo"),
"foo")
def test_should_return_file_contents(self):
fd, filename = tempfile.mkstemp(prefix="elpy-test-")
self.addCleanup(os.remove, filename)
with open(filename, "w") as f:
f.write("file contents")
fileobj = {'filename': filename}
self.assertEqual(server.get_source(fileobj),
"file contents")
def test_should_clean_up_tempfile(self):
fd, filename = tempfile.mkstemp(prefix="elpy-test-")
with open(filename, "w") as f:
f.write("file contents")
fileobj = {'filename': filename,
'delete_after_use': True}
self.assertEqual(server.get_source(fileobj),
"file contents")
self.assertFalse(os.path.exists(filename))
def test_should_support_utf8(self):
fd, filename = tempfile.mkstemp(prefix="elpy-test-")
self.addCleanup(os.remove, filename)
with open(filename, "wb") as f:
f.write(u"möp".encode("utf-8"))
source = server.get_source({'filename': filename})
self.assertEqual(source, u"möp")
class TestPysymbolKey(BackendTestCase):
def keyLess(self, a, b):
self.assertLess(b, a)
self.assertLess(server._pysymbol_key(a),
server._pysymbol_key(b))
def test_should_be_case_insensitive(self):
self.keyLess("bar", "Foo")
def test_should_sort_private_symbols_after_public_symbols(self):
self.keyLess("foo", "_bar")
def test_should_sort_private_symbols_after_dunder_symbols(self):
self.assertLess(server._pysymbol_key("__foo__"),
server._pysymbol_key("_bar"))
def test_should_sort_dunder_symbols_after_public_symbols(self):
self.keyLess("bar", "__foo")
class Autopep8TestCase(ServerTestCase):
def test_rpc_fix_code_should_return_formatted_string(self):
code_block = 'x= 123\n'
new_block = self.srv.rpc_fix_code(code_block, os.getcwd())
self.assertEqual(new_block, 'x = 123\n')

View File

@@ -0,0 +1,19 @@
"""Tests for elpy.tests.support. Yep, we test test code."""
import unittest
from elpy.tests.support import source_and_offset
class TestSourceAndOffset(unittest.TestCase):
def test_should_return_source_and_offset(self):
self.assertEqual(source_and_offset("hello, _|_world"),
("hello, world", 7))
def test_should_handle_beginning_of_string(self):
self.assertEqual(source_and_offset("_|_hello, world"),
("hello, world", 0))
def test_should_handle_end_of_string(self):
self.assertEqual(source_and_offset("hello, world_|_"),
("hello, world", 12))

View File

@@ -0,0 +1,41 @@
# coding: utf-8
"""Tests for the elpy.yapf module"""
import unittest
import os
from elpy import yapfutil
from elpy.rpc import Fault
from elpy.tests.support import BackendTestCase
@unittest.skipIf(yapfutil.YAPF_NOT_SUPPORTED,
'yapf not supported for current python version')
class YAPFTestCase(BackendTestCase):
def setUp(self):
if yapfutil.YAPF_NOT_SUPPORTED:
raise unittest.SkipTest
def test_fix_code_should_throw_error_for_invalid_code(self):
src = 'x = '
self.assertRaises(Fault, yapfutil.fix_code, src, os.getcwd())
def test_fix_code_should_throw_error_without_yapf_installed(self):
yapf = yapfutil.yapf_api
yapfutil.yapf_api = None
src = 'x= 123\n', 'x = 123\n'
with self.assertRaises(Fault):
yapfutil.fix_code(src, os.getcwd())
yapfutil.yapf_api = yapf
def test_fix_code(self):
testdata = [
('x= 123\n', 'x = 123\n'),
('x=1; \ny=2 \n', 'x = 1\ny = 2\n'),
]
for src, expected in testdata:
self._assert_format(src, expected)
def _assert_format(self, src, expected):
new_block = yapfutil.fix_code(src, os.getcwd())
self.assertEqual(new_block, expected)

View File

@@ -0,0 +1,38 @@
"""Glue for the "yapf" library.
"""
import os
import sys
from elpy.rpc import Fault
YAPF_NOT_SUPPORTED = sys.version_info < (2, 7) or (
sys.version_info >= (3, 0) and sys.version_info < (3, 4))
try:
if YAPF_NOT_SUPPORTED:
yapf_api = None
else:
from yapf.yapflib import yapf_api
from yapf.yapflib import file_resources
except ImportError: # pragma: no cover
yapf_api = None
def fix_code(code, directory):
"""Formats Python code to conform to the PEP 8 style guide.
"""
if not yapf_api:
raise Fault('yapf not installed', code=400)
style_config = file_resources.GetDefaultStyleForDir(directory or os.getcwd())
try:
reformatted_source, _ = yapf_api.FormatCode(code,
filename='<stdin>',
style_config=style_config,
verify=False)
return reformatted_source
except Exception as e:
raise Fault("Error during formatting: {}".format(e),
code=400)

View File

@@ -0,0 +1,74 @@
(defvar elpy-snippet-split-arg-arg-regex
"\\([[:alnum:]*]+\\)\\(:[[:blank:]]*[[:alpha:]]*\\)?\\([[:blank:]]*=[[:blank:]]*[[:alnum:]]*\\)?"
"Regular expression matching an argument of a python function.
First group should give the argument name.")
(defvar elpy-snippet-split-arg-separator
"[[:blank:]]*,[[:blank:]]*"
"Regular expression matching the separator in a list of argument.")
(defun elpy-snippet-split-args (arg-string)
"Split the python argument string ARG-STRING into a tuple of argument names."
(mapcar (lambda (x)
(when (string-match elpy-snippet-split-arg-arg-regex x)
(match-string-no-properties 1 x)))
(split-string arg-string elpy-snippet-split-arg-separator t)))
(defun elpy-snippet-current-method-and-args ()
"Return information on the current definition."
(let ((current-defun (python-info-current-defun))
(current-arglist
(save-excursion
(python-nav-beginning-of-defun)
(when (re-search-forward "(" nil t)
(let* ((start (point))
(end (progn
(forward-char -1)
(forward-sexp)
(- (point) 1))))
(elpy-snippet-split-args
(buffer-substring-no-properties start end))))))
class method args)
(unless current-arglist
(setq current-arglist '("self")))
(if (and current-defun
(string-match "^\\(.*\\)\\.\\(.*\\)$" current-defun))
(setq class (match-string 1 current-defun)
method (match-string 2 current-defun))
(setq class "Class"
method "method"))
(list class method current-arglist)))
(defun elpy-snippet-init-assignments (arg-string)
"Return the typical __init__ assignments for arguments in ARG-STRING."
(let ((indentation (make-string (save-excursion
(goto-char start-point)
(current-indentation))
?\s)))
(mapconcat (lambda (arg)
(if (string-match "^\\*" arg)
""
(format "self.%s = %s\n%s" arg arg indentation)))
(elpy-snippet-split-args arg-string)
"")))
(defun elpy-snippet-super-form ()
"Return (Class, first-arg).method if Py2.
Else return ().method for Py3."
(let* ((defun-info (elpy-snippet-current-method-and-args))
(class (nth 0 defun-info))
(method (nth 1 defun-info))
(args (nth 2 defun-info))
(first-arg (nth 0 args))
(py-version-command " -c 'import sys ; print(sys.version_info.major)'")
;; Get the python version. Either 2 or 3
(py-version-num (substring (shell-command-to-string (concat elpy-rpc-python-command py-version-command))0 1)))
(if (string-match py-version-num "2")
(format "(%s, %s).%s" class first-arg method)
(format "().%s" method))))
(defun elpy-snippet-super-arguments ()
"Return the argument list for the current method."
(mapconcat (lambda (x) x)
(cdr (nth 2 (elpy-snippet-current-method-and-args)))
", "))

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _abs
# key: _abs
# group: Special methods
# --
def __abs__(self):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _add
# key: _add
# group: Special methods
# --
def __add__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _and
# key: _and
# group: Special methods
# --
def __and__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _bool
# key: _bool
# group: Special methods
# --
def __bool__(self):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _call
# key: _call
# group: Special methods
# --
def __call__(self, ${1:*args}):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _cmp
# key: _cmp
# group: Special methods
# --
def __cmp__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _coerce
# key: _coerce
# group: Special methods
# --
def __coerce__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _complex
# key: _complex
# group: Special methods
# --
def __complex__(self):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _contains
# key: _contains
# group: Special methods
# --
def __contains__(self, item):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _del
# key: _del
# group: Special methods
# --
def __del__(self):
$0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _delattr
# key: _delattr
# group: Special methods
# --
def __delattr__(self, name):
$0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _delete
# key: _delete
# group: Special methods
# --
def __delete__(self, instance):
$0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _delitem
# key: _delitem
# group: Special methods
# --
def __delitem__(self, key):
$0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _div
# key: _div
# group: Special methods
# --
def __div__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _divmod
# key: _divmod
# group: Special methods
# --
def __divmod__(self, other):
return $0

View File

@@ -0,0 +1,9 @@
# -*- mode: snippet -*-
# name: _enter
# key: _enter
# group: Special methods
# --
def __enter__(self):
$0
return self

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _eq
# key: _eq
# group: Special methods
# --
def __eq__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _exit
# key: _exit
# group: Special methods
# --
def __exit__(self, exc_type, exc_value, traceback):
$0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _float
# key: _float
# group: Special methods
# --
def __float__(self):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _floordiv
# key: _floordiv
# group: Special methods
# --
def __floordiv__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _ge
# key: _ge
# group: Special methods
# --
def __ge__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _get
# key: _get
# group: Special methods
# --
def __get__(self, instance, owner):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _getattr
# key: _getattr
# group: Special methods
# --
def __getattr__(self, name):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _getattribute
# key: _getattribute
# group: Special methods
# --
def __getattribute__(self, name):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _getitem
# key: _getitem
# group: Special methods
# --
def __getitem__(self, key):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _gt
# key: _gt
# group: Special methods
# --
def __gt__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _hash
# key: _hash
# group: Special methods
# --
def __hash__(self):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _hex
# key: _hex
# group: Special methods
# --
def __hex__(self):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _iadd
# key: _iadd
# group: Special methods
# --
def __iadd__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _iand
# key: _iand
# group: Special methods
# --
def __iand__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _idiv
# key: _idiv
# group: Special methods
# --
def __idiv__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _ifloordiv
# key: _ifloordiv
# group: Special methods
# --
def __ifloordiv__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _ilshift
# key: _ilshift
# group: Special methods
# --
def __ilshift__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _imod
# key: _imod
# group: Special methods
# --
def __imod__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _imul
# key: _imul
# group: Special methods
# --
def __imul__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _index
# key: _index
# group: Special methods
# --
def __index__(self):
return $0

View File

@@ -0,0 +1,10 @@
# -*- mode: snippet -*-
# name: _init with assignment
# key: _init
# group: Special methods
# --
def __init__(self${1:, args}):
"""$2
"""
${1:$(elpy-snippet-init-assignments yas-text)}

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _instancecheck
# key: _instancecheck
# group: Special methods
# --
def __instancecheck__(self, instance):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _int
# key: _int
# group: Special methods
# --
def __int__(self):
$0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _invert
# key: _invert
# group: Special methods
# --
def __invert__(self):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _ior
# key: _ior
# group: Special methods
# --
def __ior__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _ipow
# key: _ipow
# group: Special methods
# --
def __ipow__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _irshift
# key: _irshift
# group: Special methods
# --
def __irshift__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _isub
# key: _isub
# group: Special methods
# --
def __isub__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _iter
# key: _iter
# group: Special methods
# --
def __iter__(self):
$0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _itruediv
# key: _itruediv
# group: Special methods
# --
def __itruediv__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _ixor
# key: _ixor
# group: Special methods
# --
def __ixor__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _le
# key: _le
# group: Special methods
# --
def __le__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _len
# key: _len
# group: Special methods
# --
def __len__(self):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _long
# key: _long
# group: Special methods
# --
def __long__(self):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _lshift
# key: _lshift
# group: Special methods
# --
def __lshift__(self, other):
return $0

View File

@@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: _lt
# key: _lt
# group: Special methods
# --
def __lt__(self, other):
return $0

Some files were not shown because too many files have changed in this diff Show More