447 lines
18 KiB
EmacsLisp
447 lines
18 KiB
EmacsLisp
|
;;; Complete symbols at point using Pymacs.
|
|||
|
|
|||
|
;; Copyright (C) 2007 Skip Montanaro
|
|||
|
|
|||
|
;; Original Author: Skip Montanaro
|
|||
|
;; Maintainer: Urs Fleisch <ufleisch@users.sourceforge.net>
|
|||
|
;; Created: Oct 2004
|
|||
|
;; Keywords: python pymacs emacs
|
|||
|
|
|||
|
;; 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:
|
|||
|
|
|||
|
;; Along with pycomplete.py this file allows programmers to complete Python
|
|||
|
;; symbols within the current buffer. See pycomplete.py for the Python side
|
|||
|
;; of things and a short description of what to expect.
|
|||
|
|
|||
|
;; BAW 2012-09-28: pymacs may not be installed on Debian.
|
|||
|
|
|||
|
;; WRT compiler warnings:
|
|||
|
;; ‘ac-sources’: is provided by auto-complete, which is required by auto-complete-pycomplete.
|
|||
|
;; ‘goto-line’: ‘forward-line’ doesn't look like an appropriate replacement.
|
|||
|
|
|||
|
|
|||
|
(require 'python-mode)
|
|||
|
|
|||
|
(condition-case nil
|
|||
|
(require 'pymacs)
|
|||
|
(file-error nil))
|
|||
|
|
|||
|
(eval-when-compile (require 'cl))
|
|||
|
|
|||
|
(pymacs-load "pycomplete")
|
|||
|
|
|||
|
(defcustom py-complete-set-keymap-p nil
|
|||
|
"If keys shall be defined when calling `py-complete-initialize'.
|
|||
|
Default is nil.
|
|||
|
|
|||
|
See also `py-complete-set-keymap'"
|
|||
|
:type 'boolean
|
|||
|
:group 'python-mode)
|
|||
|
|
|||
|
(defvar py-complete-variable-index nil
|
|||
|
"An alist with mappings of local variable names to types.")
|
|||
|
(defvar py-complete-variable-index-position 0
|
|||
|
"The line-beginning-position when py-complete-variable-index was last updated.
|
|||
|
This can be used to skip updating the index when still on the same line.")
|
|||
|
|
|||
|
(defun py-complete-type-for-value (val)
|
|||
|
"Return name of type for variable assignment value.
|
|||
|
If the type cannot be deduced, nil is returned."
|
|||
|
(let ((firstchar (string-to-char val))
|
|||
|
(case-fold-search nil))
|
|||
|
(cond
|
|||
|
((or (equal 0 firstchar) (string= val "None")) nil)
|
|||
|
((equal ?\[ firstchar) "list")
|
|||
|
((equal ?{ firstchar) "dict")
|
|||
|
((or (equal ?' firstchar) (equal ?\" firstchar)) "str")
|
|||
|
((string-match "^[rR]['\"]" val) "str")
|
|||
|
((string-match "^[uU][rR]?['\"]" val) "unicode")
|
|||
|
((or (string= val "True") (string= val "False")) "bool")
|
|||
|
((string-match "^[+\\-]?[0-9]+$" val) "int")
|
|||
|
((string-match "^[+\\-]?[0-9]+[lL]$" val) "long")
|
|||
|
((string-match "^[+\\-]?[0-9]+\\(?:\\.[0-9]+\\)?" val) "float")
|
|||
|
((string-match "^\\(\\(?:[[:word:]\\.]+\\.\\)?_?[A-Z][A-Za-z0-9]+\\)($" val)
|
|||
|
(match-string-no-properties 1 val))
|
|||
|
((string= "(" (substring-no-properties val -1))
|
|||
|
(concat "_PYCFRT(" (substring-no-properties val 0 -1) ")")))))
|
|||
|
|
|||
|
(defun py-complete-variables-in-def (&optional limit)
|
|||
|
"Return an alist with mappings of local variable names to types.
|
|||
|
Local variable assignments and parameters of the current function are
|
|||
|
parsed. If limit is given, it limits the size of the returned alist."
|
|||
|
(let ((pos (point))
|
|||
|
(i 0)
|
|||
|
(beg 0)
|
|||
|
candidates
|
|||
|
(variable-assignment-re (concat "^[ \t]+\\(\\w+\\)[ \t]*\\(?:=\\|+=\\|*=\\|%=\\|&=\\|^=\\|<<=\\|-=\\|/=\\|**=\\||=\\|>>=\\|//=\\)[ \t]*\\([({[]\\|[rRuU]*['\"]\\|[+\\-]?[[:word:].]+(?\\)")))
|
|||
|
(save-excursion
|
|||
|
;; First get the current def and its parameters
|
|||
|
(py-backward-def)
|
|||
|
(when (looking-at (concat py-def-re " *\\([^( ]+\\) *\\(([^:]+\\) *:"))
|
|||
|
(setq beg (match-end 0))
|
|||
|
(let ((params (replace-regexp-in-string
|
|||
|
"[ )]+$" ""
|
|||
|
(replace-regexp-in-string
|
|||
|
"^[ (]+" ""
|
|||
|
(match-string-no-properties 3)))))
|
|||
|
(dolist (param (split-string params "[ \t\r\n]*,[ \t\r\n]*"))
|
|||
|
;; Transform param into an assignment string
|
|||
|
(setq param (concat " " param
|
|||
|
(unless (memq ?= (string-to-list param))
|
|||
|
"=None")))
|
|||
|
(if (string-match variable-assignment-re param)
|
|||
|
(push `(,(match-string-no-properties 1 param) .
|
|||
|
,(py-complete-type-for-value (match-string-no-properties 2 param)))
|
|||
|
candidates)))))
|
|||
|
;; Search backward
|
|||
|
(goto-char pos)
|
|||
|
(while (and (or (not (integerp limit)) (< i limit))
|
|||
|
(re-search-backward variable-assignment-re nil t)
|
|||
|
(> (point) beg))
|
|||
|
(let* ((candidate (match-string-no-properties 1))
|
|||
|
(entry (assoc candidate candidates)))
|
|||
|
(cond ((null entry)
|
|||
|
(push `(,(match-string-no-properties 1) .
|
|||
|
,(py-complete-type-for-value (match-string-no-properties 2)))
|
|||
|
candidates)
|
|||
|
(incf i))
|
|||
|
((not (cdr entry))
|
|||
|
(setcdr entry (py-complete-type-for-value (match-string-no-properties 2)))))))
|
|||
|
(nreverse candidates))))
|
|||
|
|
|||
|
(defun py-complete-update-variable-index (&optional limit)
|
|||
|
"Update py-complete-variable-index from the local variables of the current
|
|||
|
function. An update is only performed if point was on a different line for
|
|||
|
the last update. If limit is given, it limits the size of the index."
|
|||
|
(unless (local-variable-p 'py-complete-variable-index)
|
|||
|
(make-local-variable 'py-complete-variable-index))
|
|||
|
(unless (local-variable-p 'py-complete-variable-index-position)
|
|||
|
(make-local-variable 'py-complete-variable-index-position))
|
|||
|
(let ((linebeg (line-beginning-position)))
|
|||
|
(unless (eq linebeg py-complete-variable-index-position)
|
|||
|
(setq py-complete-variable-index (py-complete-variables-in-def limit))
|
|||
|
(setq py-complete-variable-index-position linebeg))))
|
|||
|
|
|||
|
(defun py-complete-variable-completions-for-symbol (sym)
|
|||
|
"Get completions for local variables in current def.
|
|||
|
If sym is an empty string, all local variables are returned,
|
|||
|
else those starting with sym."
|
|||
|
(when (and (stringp sym) (string-match "^\\w*$" sym))
|
|||
|
(py-complete-update-variable-index)
|
|||
|
(let ((symlen (length sym)))
|
|||
|
(if (zerop symlen)
|
|||
|
(mapcar 'car py-complete-variable-index)
|
|||
|
(remove-if-not
|
|||
|
#'(lambda (s) (and (>= (length s) symlen) (string= sym (substring s 0 symlen))))
|
|||
|
(mapcar 'car py-complete-variable-index))))))
|
|||
|
|
|||
|
(defun py-complete-which-class ()
|
|||
|
"Return current class name based on point.
|
|||
|
If no class name is found, return nil."
|
|||
|
(interactive)
|
|||
|
(let (classname)
|
|||
|
(save-excursion
|
|||
|
(save-restriction
|
|||
|
(py-backward-class)
|
|||
|
(when (looking-at (concat py-class-re " *\\([^( ]+\\)"))
|
|||
|
(setq classname (match-string-no-properties 2))
|
|||
|
(if (called-interactively-p 'interactive)
|
|||
|
(message "%s" classname)))))
|
|||
|
classname))
|
|||
|
|
|||
|
(defun py-complete-type-before-dotexpr (&optional pos)
|
|||
|
"Get type for expression before dot expression.
|
|||
|
The character after pos (point if omitted) must be a dot.
|
|||
|
Returns list, str or dict if such an expression is before
|
|||
|
the dot, else nil."
|
|||
|
(let ((dotchar (char-after pos)))
|
|||
|
(if (and dotchar (char-equal ?. dotchar))
|
|||
|
(save-excursion
|
|||
|
(if pos
|
|||
|
(goto-char pos))
|
|||
|
(cond
|
|||
|
((looking-back "\\(\\[\\|,[^[]*\\)\\]" "list" (point-min)))
|
|||
|
((looking-back "['\"]" "str" (point-min)))
|
|||
|
((looking-back "}" "dict" (point-min))))))))
|
|||
|
|
|||
|
(defun py-complete-substitute-type-for-var (word)
|
|||
|
"Substitute the type for the variable starting the dot-expression word.
|
|||
|
Returns the word with replaced variable if known, else the unchanged word."
|
|||
|
(let* (type
|
|||
|
(firstsym (car (split-string word "\\.")))
|
|||
|
(firstlen (length firstsym)))
|
|||
|
(if (string= firstsym "self")
|
|||
|
(setq type (py-complete-which-class))
|
|||
|
(py-complete-update-variable-index)
|
|||
|
(setq type (cdr (assoc firstsym py-complete-variable-index))))
|
|||
|
(if (stringp type)
|
|||
|
(concat type (substring word firstlen))
|
|||
|
word)))
|
|||
|
|
|||
|
(defun py-complete-python-dotexpr-begin nil
|
|||
|
(re-search-backward "[^a-zA-Z_0-9\\.]")
|
|||
|
(forward-char))
|
|||
|
|
|||
|
(defun py-complete-python-dotexpr-end nil
|
|||
|
(re-search-forward "[a-zA-Z_0-9\\.]*"))
|
|||
|
|
|||
|
(put 'python-dotexpr 'beginning-op 'py-complete-python-dotexpr-begin)
|
|||
|
(put 'python-dotexpr 'end-op 'py-complete-python-dotexpr-end)
|
|||
|
|
|||
|
(defun py-complete-enhanced-dotexpr-at-point ()
|
|||
|
"Enhanced (thing-at-point 'python-dotexpr).
|
|||
|
The returned word starts with a type if an expression is found before the dot
|
|||
|
or if the dot-expression starts with a variable for which the type is known."
|
|||
|
(require 'thingatpt)
|
|||
|
(let ((bounds (bounds-of-thing-at-point 'python-dotexpr)))
|
|||
|
(if bounds
|
|||
|
(let* ((beg (car bounds))
|
|||
|
(end (cdr bounds))
|
|||
|
(word (buffer-substring-no-properties beg end))
|
|||
|
(prefix (py-complete-type-before-dotexpr beg)))
|
|||
|
(if prefix
|
|||
|
(concat prefix word)
|
|||
|
(py-complete-substitute-type-for-var word))))))
|
|||
|
|
|||
|
(defun py-complete-enhanced-symbol-before-point ()
|
|||
|
"Return the dotted python symbol before point.
|
|||
|
The returned word starts with a type if an expression is found before the dot
|
|||
|
or if the dot-expression starts with a variable for which the type is known."
|
|||
|
(let* (prefix
|
|||
|
(word (buffer-substring-no-properties
|
|||
|
(save-excursion
|
|||
|
(skip-chars-backward "a-zA-Z0-9_.")
|
|||
|
(setq prefix (py-complete-type-before-dotexpr))
|
|||
|
(point))
|
|||
|
(point))))
|
|||
|
(if prefix
|
|||
|
(concat prefix word)
|
|||
|
(py-complete-substitute-type-for-var word))))
|
|||
|
|
|||
|
;; Not used anymore
|
|||
|
(defun py-find-global-imports ()
|
|||
|
"Find Python import statements in buffer."
|
|||
|
(save-excursion
|
|||
|
(let (first-class-or-def imports)
|
|||
|
(goto-char (point-min))
|
|||
|
(setq first-class-or-def
|
|||
|
(re-search-forward "^ *\\(def\\|class\\) " nil t))
|
|||
|
(goto-char (point-min))
|
|||
|
(setq imports nil)
|
|||
|
(while (re-search-forward
|
|||
|
"^\\(import \\|from \\([A-Za-z_\\.][A-Za-z_0-9\\.]*\\) import \\).*"
|
|||
|
first-class-or-def t)
|
|||
|
(setq imports (cons (buffer-substring-no-properties
|
|||
|
(match-beginning 0)
|
|||
|
(match-end 0))
|
|||
|
imports)))
|
|||
|
(nreverse imports))))
|
|||
|
|
|||
|
(defun py-complete ()
|
|||
|
"Complete symbol before point using Pymacs. "
|
|||
|
(interactive)
|
|||
|
(setq py-last-window-configuration
|
|||
|
(current-window-configuration))
|
|||
|
(let ((symbol (py-complete-enhanced-symbol-before-point)))
|
|||
|
(if (string= "" symbol)
|
|||
|
(tab-to-tab-stop)
|
|||
|
(let ((completions
|
|||
|
(py-complete-completions-for-symbol symbol)))
|
|||
|
(if completions
|
|||
|
(let* (completion
|
|||
|
(lastsym (car (last (split-string symbol "\\."))))
|
|||
|
(lastlen (length lastsym)))
|
|||
|
(cond ((null (cdr completions))
|
|||
|
(setq completion (car completions)))
|
|||
|
(t
|
|||
|
(setq completion (try-completion lastsym completions))
|
|||
|
(message "Making completion list...")
|
|||
|
(with-output-to-temp-buffer "*PythonCompletions*"
|
|||
|
(display-completion-list completions))
|
|||
|
(message "Making completion list...%s" "done")))
|
|||
|
(when (and (stringp completion)
|
|||
|
(> (length completion) lastlen))
|
|||
|
(insert (substring completion lastlen))))
|
|||
|
(message "Can't find completion for \"%s\"" symbol)
|
|||
|
(ding))))))
|
|||
|
|
|||
|
(defun py-complete-completions-for-symbol (sym &optional imports)
|
|||
|
"Get possible completions for symbol using statements given in imports."
|
|||
|
(let ((pymacs-forget-mutability t))
|
|||
|
(append
|
|||
|
(py-complete-variable-completions-for-symbol sym)
|
|||
|
(pycomplete-pycompletions
|
|||
|
sym (buffer-file-name)
|
|||
|
imports))))
|
|||
|
|
|||
|
(defun py-complete-docstring-for-symbol (sym &optional imports)
|
|||
|
"Get docstring for symbol using statements given in imports."
|
|||
|
(let ((pymacs-forget-mutability t))
|
|||
|
(pycomplete-pydocstring
|
|||
|
sym (buffer-file-name)
|
|||
|
imports)))
|
|||
|
|
|||
|
(defun py-complete-completions ()
|
|||
|
"Get possible completions for current statement."
|
|||
|
(py-complete-completions-for-symbol
|
|||
|
(py-complete-enhanced-symbol-before-point)))
|
|||
|
|
|||
|
(defun py-complete-completion-at-point ()
|
|||
|
"Return a (start end collection) list, so that this function
|
|||
|
can be used as a hook for completion-at-point-functions."
|
|||
|
(setq py-last-window-configuration
|
|||
|
(current-window-configuration))
|
|||
|
(let ((symbol (py-complete-enhanced-symbol-before-point)))
|
|||
|
(when (not (string= "" symbol))
|
|||
|
(let ((completions (py-complete-completions-for-symbol symbol)))
|
|||
|
(when completions
|
|||
|
(when (> (length completions) 1)
|
|||
|
;; this-command is changed to avoid the following situation:
|
|||
|
;; This function is invoked via indent-for-tab-command (because
|
|||
|
;; tab-always-indent is complete) and there is a "Complete, but
|
|||
|
;; not unique" case (e.g. "for" is completed and the next TAB key
|
|||
|
;; press shall display a list with "for", "format"). In such a
|
|||
|
;; case, py-indent-line would detect a repeated indentation
|
|||
|
;; request and thus change the indentation. The changed
|
|||
|
;; indentation would then prevent indent-for-tab-command
|
|||
|
;; from calling the completion function.
|
|||
|
(setq this-command 'py-complete-completion-at-point))
|
|||
|
(list (- (point) (length (car (last (split-string symbol "\\.")))))
|
|||
|
(point)
|
|||
|
completions))))))
|
|||
|
|
|||
|
(defun py-complete-show (string)
|
|||
|
(display-message-or-buffer string "*PythonHelp*"))
|
|||
|
|
|||
|
(defun py-complete-help (string)
|
|||
|
"get help on a python expression"
|
|||
|
(interactive "sHelp: ")
|
|||
|
(let* ((pymacs-forget-mutability t)
|
|||
|
(help-string
|
|||
|
(pycomplete-pyhelp string (buffer-file-name))))
|
|||
|
(if (and help-string (> (length help-string) 300))
|
|||
|
(with-output-to-temp-buffer "*Python Help*"
|
|||
|
(princ help-string))
|
|||
|
(py-complete-show help-string))))
|
|||
|
|
|||
|
(defun py-complete-help-thing-at-point nil
|
|||
|
(interactive)
|
|||
|
(let ((sym (py-complete-enhanced-dotexpr-at-point)))
|
|||
|
(if sym
|
|||
|
(py-complete-help sym))))
|
|||
|
|
|||
|
(defvar py-complete-current-signature nil
|
|||
|
"Internally used by pycomplete.el")
|
|||
|
|
|||
|
(defun py-complete-signature (function)
|
|||
|
"get signature of a python function or method"
|
|||
|
(let ((pymacs-forget-mutability t))
|
|||
|
(set 'py-complete-current-signature
|
|||
|
(pycomplete-pysignature function (buffer-file-name)))))
|
|||
|
|
|||
|
(defun py-complete-signature-show nil
|
|||
|
(let ((sym (py-complete-enhanced-dotexpr-at-point)))
|
|||
|
(if sym
|
|||
|
(progn
|
|||
|
(py-complete-show (py-complete-signature sym))))))
|
|||
|
|
|||
|
(defun py-complete-signature-expr nil
|
|||
|
(interactive)
|
|||
|
(let ((dotexpr (read-string "signature on: "
|
|||
|
(py-complete-enhanced-dotexpr-at-point))))
|
|||
|
(if dotexpr
|
|||
|
(py-complete-show
|
|||
|
(py-complete-signature dotexpr)))))
|
|||
|
|
|||
|
(defun py-complete-electric-lparen nil
|
|||
|
"electricly insert '(', and try to get a signature for the stuff to the left"
|
|||
|
(interactive)
|
|||
|
(py-complete-signature-show)
|
|||
|
(self-insert-command 1))
|
|||
|
|
|||
|
(defun py-complete-electric-comma nil
|
|||
|
"electricly insert ',', and redisplay latest signature"
|
|||
|
(interactive)
|
|||
|
(self-insert-command 1)
|
|||
|
(if py-complete-current-signature
|
|||
|
(py-complete-show (format "%s" py-complete-current-signature))))
|
|||
|
|
|||
|
(defun py-complete-location (sym)
|
|||
|
"Get definition location of sym in cons form (FILE . LINE)."
|
|||
|
(let ((location (pycomplete-pylocation sym (buffer-file-name))))
|
|||
|
(when (and location (vectorp location) (= (length location) 2))
|
|||
|
(cons (aref location 0) (aref location 1)))))
|
|||
|
|
|||
|
(defun py-complete-goto-definition nil
|
|||
|
"Got to definition of Python function."
|
|||
|
(interactive)
|
|||
|
(let ((sym (py-complete-enhanced-dotexpr-at-point)))
|
|||
|
(if sym
|
|||
|
(let ((location
|
|||
|
(pycomplete-pylocation sym (buffer-file-name))))
|
|||
|
(if (and location (vectorp location) (= (length location) 2))
|
|||
|
(progn
|
|||
|
(find-file (aref location 0))
|
|||
|
(goto-line (aref location 1)))
|
|||
|
(message "Cannot find the definition!"))))))
|
|||
|
|
|||
|
(defun py-complete-parse-source ()
|
|||
|
"Parse source code of Python file to get imports and completions."
|
|||
|
(let ((errstr (pycomplete-pyparse (buffer-file-name) t)))
|
|||
|
(if errstr
|
|||
|
(message "%s" errstr))))
|
|||
|
|
|||
|
(defun py-complete-set-keymap ()
|
|||
|
"Define key map with pycomplete functions."
|
|||
|
(interactive)
|
|||
|
(define-key python-mode-map [C-tab] 'py-complete)
|
|||
|
(define-key python-mode-map [f1] 'py-complete-help-thing-at-point)
|
|||
|
(define-key python-mode-map "(" 'py-complete-electric-lparen)
|
|||
|
(define-key python-mode-map "," 'py-complete-electric-comma)
|
|||
|
(define-key python-mode-map [S-f1] 'py-complete-signature-expr)
|
|||
|
(define-key python-mode-map [f2] 'py-complete-goto-definition)
|
|||
|
(define-key python-mode-map [f3] 'py-complete-help))
|
|||
|
|
|||
|
(defun py-complete-initialize ()
|
|||
|
"Initialize pycomplete hooks.
|
|||
|
Should be called from python-mode-hook. Keys are set when
|
|||
|
`py-complete-set-keymap-p' is non-nil."
|
|||
|
(interactive)
|
|||
|
(when py-set-complete-keymap-p
|
|||
|
(py-complete-set-keymap))
|
|||
|
(when py-complete-set-keymap-p
|
|||
|
(py-complete-set-keymap))
|
|||
|
;; Parse source file after it is saved
|
|||
|
(add-hook 'after-save-hook 'py-complete-parse-source nil 'local)
|
|||
|
;; Set up auto-complete or company if enabled
|
|||
|
(cond
|
|||
|
((fboundp 'auto-complete-mode)
|
|||
|
(require 'auto-complete-pycomplete)
|
|||
|
(setq ac-sources
|
|||
|
(if (boundp 'py-complete-ac-sources)
|
|||
|
py-complete-ac-sources
|
|||
|
'(ac-source-pycomplete))))
|
|||
|
((fboundp 'company-mode)
|
|||
|
(company-mode t)
|
|||
|
(require 'company-pycomplete)
|
|||
|
(set (make-local-variable 'company-backends)
|
|||
|
'((company-pycomplete))))
|
|||
|
((or py-set-complete-keymap-p py-complete-set-keymap-p)
|
|||
|
(set (make-local-variable 'tab-always-indent) 'complete)
|
|||
|
(define-key python-mode-map [tab] 'indent-for-tab-command))))
|
|||
|
|
|||
|
(provide 'pycomplete)
|