Merge from Dave Love's 2008-01-20 version.

Require sym-comp.  Add Python buffer to same-window-buffer-names.
Fixup whitespaces.
(python-font-lock-keywords): Add highlighting for Python builtins.
(python-font-lock-syntactic-keywords): Rewrite.
(python-quote-syntax): Use syntax-ppss-context instead of parsing
ppss directly.
(python-mode-map): Add binding for python-find-function.
(python-calculate-indentation): Clean up the logic.
(python-beginning-of-defun): Explicitly set return value.
(python-beginning-of-statement): Stop looping if we get stuck
going backwards.
(python-next-statement): Stop looping if we somehow end up inside
a string while advancing.
(python-preoutput-continuation, python-version-checked): New vars.
(python-check-version): New function.
(run-python): Set default command to python-command instead of
python-python-command.
(run-python): Use python-check-version.  Give PYTHONPATH
precedence over data-directory in the process environment.  Load
function definitions in python process after.
(python-check-comint-prompt): New function.
(python-send-command, python-send-receive): Use it.
(python-complete-symbol, python-try-complete): Functions deleted.
Use symbol-complete instead of python-complete-symbol throughout.
(python-fill-paragraph): Further refine the fenced-string regexp.
(def-python-skeleton): Expand to the original abbrev instead if in
a comment or string.  Tweak skeletons for `if', `while', `for',
`try/except', `try/finally', `name'.
(python-pea-hook, python-abbrev-pc-hook): New functions.
(python-abbrev-syntax-table): New var.
(python-mode): Add python-pea-hook to pre-abbrev-expand-hook.  Use
symbol-completion-try-complete for hippie expansion.  Turn on font
lock unconditionally.
(python-mode-hook): Defcustom it.  No need to use make-local
variable on indent-tabs-mode in "Turn off Indent Tabs mode"
option, since it's buffer-local.
This commit is contained in:
Chong Yidong 2008-02-21 17:03:47 +00:00
parent e21766aa63
commit 2bcda1d5ac

View file

@ -62,6 +62,8 @@
;; TODO: See various Fixmes below.
;; Fixme: This doesn't support (the nascent) Python 3 .
;;; Code:
(require 'comint)
@ -70,6 +72,7 @@
(require 'compile)
(require 'hippie-exp))
(require 'sym-comp)
(autoload 'comint-mode "comint")
(defgroup python nil
@ -84,6 +87,7 @@
(add-to-list 'interpreter-mode-alist '("python" . python-mode))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.py\\'" . python-mode))
(add-to-list 'same-window-buffer-names "*Python*")
;;;; Font lock
@ -98,7 +102,7 @@
;; Not real keywords, but close enough to be fontified as such
"self" "True" "False")
symbol-end)
(,(rx symbol-start "None" symbol-end) ; See § Keywords in 2.5 manual.
(,(rx symbol-start "None" symbol-end) ; see § Keywords in 2.5 manual
. font-lock-constant-face)
;; Definitions
(,(rx symbol-start (group "class") (1+ space) (group (1+ (or word ?_))))
@ -108,15 +112,63 @@
;; Top-level assignments are worth highlighting.
(,(rx line-start (group (1+ (or word ?_))) (0+ space) "=")
(1 font-lock-variable-name-face))
(,(rx "@" (1+ (or word ?_))) ; decorators
(0 font-lock-preprocessor-face))))
(,(rx line-start (* (any " \t")) (group "@" (1+ (or word ?_)))) ; decorators
(1 font-lock-type-face))
;; Built-ins. (The next three blocks are from
;; `__builtin__.__dict__.keys()' in Python 2.5.1.) These patterns
;; are debateable, but they at least help to spot possible
;; shadowing of builtins.
(,(rx symbol-start (or
;; exceptions
"ArithmeticError" "AssertionError" "AttributeError"
"BaseException" "DeprecationWarning" "EOFError"
"EnvironmentError" "Exception" "FloatingPointError"
"FutureWarning" "GeneratorExit" "IOError" "ImportError"
"ImportWarning" "IndentationError" "IndexError" "KeyError"
"KeyboardInterrupt" "LookupError" "MemoryError" "NameError"
"NotImplemented" "NotImplementedError" "OSError"
"OverflowError" "PendingDeprecationWarning" "ReferenceError"
"RuntimeError" "RuntimeWarning" "StandardError"
"StopIteration" "SyntaxError" "SyntaxWarning" "SystemError"
"SystemExit" "TabError" "TypeError" "UnboundLocalError"
"UnicodeDecodeError" "UnicodeEncodeError" "UnicodeError"
"UnicodeTranslateError" "UnicodeWarning" "UserWarning"
"ValueError" "Warning" "ZeroDivisionError") symbol-end)
. font-lock-type-face)
(,(rx (or line-start (not (any ". \t"))) (* (any " \t")) symbol-start
(group (or
;; callable built-ins, fontified when not appearing as
;; object attributes
"abs" "all" "any" "apply" "basestring" "bool" "buffer" "callable"
"chr" "classmethod" "cmp" "coerce" "compile" "complex"
"copyright" "credits" "delattr" "dict" "dir" "divmod"
"enumerate" "eval" "execfile" "exit" "file" "filter" "float"
"frozenset" "getattr" "globals" "hasattr" "hash" "help"
"hex" "id" "input" "int" "intern" "isinstance" "issubclass"
"iter" "len" "license" "list" "locals" "long" "map" "max"
"min" "object" "oct" "open" "ord" "pow" "property" "quit"
"range" "raw_input" "reduce" "reload" "repr" "reversed"
"round" "set" "setattr" "slice" "sorted" "staticmethod"
"str" "sum" "super" "tuple" "type" "unichr" "unicode" "vars"
"xrange" "zip")) symbol-end)
(1 font-lock-builtin-face))
(,(rx symbol-start (or
;; other built-ins
"True" "False" "None" "Ellipsis"
"_" "__debug__" "__doc__" "__import__" "__name__") symbol-end)
. font-lock-builtin-face)))
(defconst python-font-lock-syntactic-keywords
;; Make outer chars of matching triple-quote sequences into generic
;; string delimiters. Fixme: Is there a better way?
`((,(rx (or line-start buffer-start
(not (syntax escape))) ; avoid escaped leading quote
(group (optional (any "uUrR"))) ; prefix gets syntax property
;; First avoid a sequence preceded by an odd number of backslashes.
`((,(rx (not (any ?\\))
?\\ (* (and ?\\ ?\\))
(group (syntax string-quote))
(backref 1)
(group (backref 1)))
(2 ,(string-to-syntax "\""))) ; dummy
(,(rx (group (optional (any "uUrR"))) ; prefix gets syntax property
(optional (any "rR")) ; possible second prefix
(group (syntax string-quote)) ; maybe gets property
(backref 2) ; per first quote
@ -144,8 +196,6 @@ Used for syntactic keywords. N is the match number (1, 2 or 3)."
;; x = ''' """ ' a
;; '''
;; x '"""' x """ \"""" x
;; Fixme: """""" goes wrong (due to syntax-ppss not getting the string
;; fence context).
(save-excursion
(goto-char (match-beginning 0))
(cond
@ -165,8 +215,8 @@ Used for syntactic keywords. N is the match number (1, 2 or 3)."
(and (= n 1) ; prefix
(/= (match-beginning 1) (match-end 1)))) ; non-empty
(let ((font-lock-syntactic-keywords nil))
(unless (nth 3 (syntax-ppss))
(eval-when-compile (string-to-syntax "|")))))
(unless (eq 'string (syntax-ppss-context (syntax-ppss)))
(eval-when-compile (string-to-syntax "|")))))
;; Otherwise (we're in a non-matching string) the property is
;; nil, which is OK.
)))
@ -217,7 +267,7 @@ Used for syntactic keywords. N is the match number (1, 2 or 3)."
(define-key map "\C-c\C-z" 'python-switch-to-python)
(define-key map "\C-c\C-m" 'python-load-file)
(define-key map "\C-c\C-l" 'python-load-file) ; a la cmuscheme
(substitute-key-definition 'complete-symbol 'python-complete-symbol
(substitute-key-definition 'complete-symbol 'symbol-complete
map global-map)
(define-key map "\C-c\C-i" 'python-find-imports)
(define-key map "\C-c\C-t" 'python-expand-template)
@ -270,8 +320,10 @@ Used for syntactic keywords. N is the match number (1, 2 or 3)."
"-"
["Help on symbol" python-describe-symbol
:help "Use pydoc on symbol at point"]
["Complete symbol" python-complete-symbol
["Complete symbol" symbol-complete
:help "Complete (qualified) symbol before point"]
["Find function" python-find-function
:help "Try to find source definition of function at point"]
["Update imports" python-find-imports
:help "Update list of top-level imports for completion"]))
map))
@ -630,39 +682,38 @@ Set `python-indent' locally to the value guessed."
((looking-at (rx (0+ space) (syntax comment-start)
(not (any " \t\n")))) ; non-indentable comment
(current-indentation))
(t (if python-honour-comment-indentation
;; Back over whitespace, newlines, non-indentable comments.
(catch 'done
(while t
(if (cond ((bobp))
;; not at comment start
((not (forward-comment -1))
(python-beginning-of-statement)
t)
;; trailing comment
((/= (current-column) (current-indentation))
(python-beginning-of-statement)
t)
;; indentable comment like python-mode.el
((and (looking-at (rx (syntax comment-start)
(or space line-end)))
(/= 0 (current-column)))))
(throw 'done t)))))
(python-indentation-levels)
;; Prefer to indent comments with an immediately-following
;; statement, e.g.
;; ...
;; # ...
;; def ...
(when (and (> python-indent-list-length 1)
(python-comment-line-p))
(forward-line)
(unless (python-comment-line-p)
(let ((elt (assq (current-indentation) python-indent-list)))
(setq python-indent-list
(nconc (delete elt python-indent-list)
(list elt))))))
(caar (last python-indent-list)))))))
((and python-honour-comment-indentation
;; Back over whitespace, newlines, non-indentable comments.
(catch 'done
(while (cond ((bobp) nil)
((not (forward-comment -1))
nil) ; not at comment start
;; Now at start of comment -- trailing one?
((/= (current-column) (current-indentation))
nil)
;; Indentable comment, like python-mode.el?
((and (looking-at (rx (syntax comment-start)
(or space line-end)))
(/= 0 (current-column)))
(throw 'done (current-column)))
;; Else skip it (loop).
(t))))))
(t
(python-indentation-levels)
;; Prefer to indent comments with an immediately-following
;; statement, e.g.
;; ...
;; # ...
;; def ...
(when (and (> python-indent-list-length 1)
(python-comment-line-p))
(forward-line)
(unless (python-comment-line-p)
(let ((elt (assq (current-indentation) python-indent-list)))
(setq python-indent-list
(nconc (delete elt python-indent-list)
(list elt))))))
(caar (last python-indent-list)))))))
;;;; Cycling through the possible indentations with successive TABs.
@ -855,11 +906,12 @@ reached start of buffer."
;; Not sure why it was like this -- fails in case of
;; last internal function followed by first
;; non-def statement of the main body.
;;(and def-line (= in ci))
;; (and def-line (= in ci))
(= in ci)
(< in ci)))
(not (python-in-string/comment)))
(setq found t)))))
(setq found t)))
found))
(defun python-end-of-defun ()
"`end-of-defun-function' for Python.
@ -913,15 +965,17 @@ Accounts for continuation lines, multi-line strings, and
multi-line bracketed expressions."
(beginning-of-line)
(python-beginning-of-string)
(while (python-continuation-line-p)
(beginning-of-line)
(if (python-backslash-continuation-line-p)
(progn
(forward-line -1)
(while (python-backslash-continuation-line-p)
(forward-line -1)))
(python-beginning-of-string)
(python-skip-out)))
(let ((point (point)))
(while (and (python-continuation-line-p)
(> point (setq point (point))))
(beginning-of-line)
(if (python-backslash-continuation-line-p)
(progn
(forward-line -1)
(while (python-backslash-continuation-line-p)
(forward-line -1)))
(python-beginning-of-string)
(python-skip-out))))
(back-to-indentation))
(defun python-skip-out (&optional forward syntax)
@ -999,11 +1053,14 @@ Return count of statements left to move."
(if (< count 0)
(python-previous-statement (- count))
(beginning-of-line)
(while (and (> count 0) (not (eobp)))
(python-end-of-statement)
(python-skip-comments/blanks)
(unless (eobp)
(setq count (1- count))))
(let (bogus)
(while (and (> count 0) (not (eobp)) (not bogus))
(python-end-of-statement)
(python-skip-comments/blanks)
(if (eq 'string (syntax-ppss-context (syntax-ppss)))
(setq bogus t)
(unless (eobp)
(setq count (1- count))))))
count))
(defun python-beginning-of-block (&optional arg)
@ -1066,7 +1123,7 @@ don't move and return nil. Otherwise return t."
(if (and (zerop ci) (not open))
(not (goto-char point))
(catch 'done
(while (zerop (python-next-statement))
(while (zerop (python-next-statement))
(when (or (and open (<= (current-indentation) ci))
(< (current-indentation) ci))
(python-skip-comments/blanks t)
@ -1083,9 +1140,21 @@ don't move and return nil. Otherwise return t."
(set-text-properties 0 (length function-name) nil function-name)
function-name))
;;;; Imenu.
;; For possibily speeding this up, here's the top of the ELP profile
;; for rescanning pydoc.py (2.2k lines, 90kb):
;; Function Name Call Count Elapsed Time Average Time
;; ==================================== ========== ============= ============
;; python-imenu-create-index 156 2.430906 0.0155827307
;; python-end-of-defun 155 1.2718260000 0.0082053290
;; python-end-of-block 155 1.1898689999 0.0076765741
;; python-next-statement 2970 1.024717 0.0003450225
;; python-end-of-statement 2970 0.4332190000 0.0001458649
;; python-beginning-of-defun 265 0.0918479999 0.0003465962
;; python-skip-comments/blanks 3125 0.0753319999 2.410...e-05
(defvar python-recursing)
(defun python-imenu-create-index ()
"`imenu-create-index-function' for Python.
@ -1361,11 +1430,16 @@ Don't save anything for STR matching `inferior-python-filter-regexp'."
(defvar python-preoutput-result nil
"Data from last `_emacs_out' line seen by the preoutput filter.")
(defvar python-preoutput-continuation nil
"If non-nil, funcall this when `python-preoutput-filter' sees `_emacs_ok'.")
(defvar python-preoutput-leftover nil)
(defvar python-preoutput-skip-next-prompt nil)
;; Using this stops us getting lines in the buffer like
;; >>> ... ... >>>
;; Also look for (and delete) an `_emacs_ok' string and call
;; `python-preoutput-continuation' if we get it.
(defun python-preoutput-filter (s)
"`comint-preoutput-filter-functions' function: ignore prompts not at bol."
(when python-preoutput-leftover
@ -1415,6 +1489,23 @@ Don't save anything for STR matching `inferior-python-filter-regexp'."
(autoload 'comint-check-proc "comint")
(defvar python-version-checked nil)
(defun python-check-version (cmd)
"Check that CMD runs a suitable version of Python."
;; Fixme: Check on Jython.
(unless (or python-version-checked
(equal 0 (string-match (regexp-quote python-python-command)
cmd)))
(unless (shell-command-to-string cmd)
(error "Can't run Python command `%s'" cmd))
(let* ((res (shell-command-to-string (concat cmd " --version"))))
(string-match "Python \\([0-9]\\)\\.\\([0-9]\\)" res)
(unless (and (equal "2" (match-string 1 res))
(match-beginning 2)
(>= (string-to-number (match-string 2 res)) 2))
(error "Only Python versions >= 2.2 and < 3.0 supported")))
(setq python-version-checked t)))
;;;###autoload
(defun run-python (&optional cmd noshow new)
"Run an inferior Python process, input and output via buffer *Python*.
@ -1435,22 +1526,26 @@ buffer for a list of commands.)"
(interactive (if current-prefix-arg
(list (read-string "Run Python: " python-command) nil t)
(list python-command)))
(unless cmd (setq cmd python-python-command))
(unless cmd (setq cmd python-command))
(python-check-version cmd)
(setq python-command cmd)
;; Fixme: Consider making `python-buffer' buffer-local as a buffer
;; (not a name) in Python buffers from which `run-python' &c is
;; invoked. Would support multiple processes better.
(when (or new (not (comint-check-proc python-buffer)))
(with-current-buffer
(let* ((cmdlist (append (python-args-to-list cmd) '("-i")))
(path (getenv "PYTHONPATH"))
(process-environment ; to import emacs.py
(cons (concat "PYTHONPATH=" data-directory
(if path (concat path-separator path)))
process-environment)))
(apply 'make-comint-in-buffer "Python"
(if new (generate-new-buffer "*Python*") "*Python*")
(car cmdlist) nil (cdr cmdlist)))
(let* ((cmdlist (append (python-args-to-list cmd) '("-i")))
(path (getenv "PYTHONPATH"))
(process-environment ; to import emacs.py
(cons (concat "PYTHONPATH="
(if path (concat path path-separator))
data-directory)
process-environment))
;; Suppress use of pager for help output:
(process-connection-type nil))
(apply 'make-comint-in-buffer "Python"
(generate-new-buffer "*Python*")
(car cmdlist) nil (cdr cmdlist)))
(setq-default python-buffer (current-buffer))
(setq python-buffer (current-buffer))
(accept-process-output (get-buffer-process python-buffer) 5)
@ -1462,7 +1557,13 @@ buffer for a list of commands.)"
;; seems worth putting in a separate file, and it's probably cleaner
;; to put it in a module.
;; Ensure we're at a prompt before doing anything else.
(python-send-string "import emacs")))
(python-send-string "import emacs")
;; The following line was meant to ensure that we're at a prompt
;; before doing anything else. However, this can cause Emacs to
;; hang waiting for a response, if that Python function fails
;; (i.e. raises an exception).
;; (python-send-receive "print '_emacs_out ()'")
))
(if (derived-mode-p 'python-mode)
(setq python-buffer (default-value 'python-buffer))) ; buffer-local
;; Without this, help output goes into the inferior python buffer if
@ -1470,30 +1571,14 @@ buffer for a list of commands.)"
(sit-for 1 t) ;Should we use accept-process-output instead? --Stef
(unless noshow (pop-to-buffer python-buffer t)))
;; Fixme: We typically lose if the inferior isn't in the normal REPL,
;; e.g. prompt is `help> '. Probably raise an error if the form of
;; the prompt is unexpected. Actually, it needs to be `>>> ', not
;; `... ', i.e. we're not inputting a block &c. However, this may not
;; be the place to check it, e.g. we might actually want to send
;; commands having set up such a state.
(defun python-send-command (command)
"Like `python-send-string' but resets `compilation-shell-minor-mode'.
COMMAND should be a single statement."
;; (assert (not (string-match "\n" command)))
;; (let ((end (marker-position (process-mark (python-proc)))))
(with-current-buffer (process-buffer (python-proc))
(goto-char (point-max))
(compilation-forget-errors)
(python-send-string command)
(setq compilation-last-buffer (current-buffer)))
;; No idea what this is for but it breaks the call to
;; compilation-fake-loc in python-send-region. -- Stef
;; Must wait until this has completed before re-setting variables below.
;; (python-send-receive "print '_emacs_out ()'")
;; (with-current-buffer python-buffer
;; (set-marker compilation-parsing-end end))
) ;;)
"Like `python-send-string' but resets `compilation-shell-minor-mode'."
(when (python-check-comint-prompt)
(with-current-buffer (process-buffer (python-proc))
(goto-char (point-max))
(compilation-forget-errors)
(python-send-string command)
(setq compilation-last-buffer (current-buffer)))))
(defun python-send-region (start end)
"Send the region to the inferior Python process."
@ -1617,8 +1702,8 @@ See variable `python-buffer'. Starts a new process if necessary."
(unless (comint-check-proc python-buffer)
(run-python nil t))
(get-buffer-process (if (derived-mode-p 'inferior-python-mode)
(current-buffer)
python-buffer)))
(current-buffer)
python-buffer)))
(defun python-set-proc ()
"Set the default value of `python-buffer' to correspond to this buffer.
@ -1693,16 +1778,27 @@ will."
(defun python-send-receive (string)
"Send STRING to inferior Python (if any) and return result.
The result is what follows `_emacs_out' in the output."
The result is what follows `_emacs_out' in the output.
This is a no-op if `python-check-comint-prompt' returns nil."
(python-send-string string)
(let ((proc (python-proc)))
(with-current-buffer (process-buffer proc)
(set (make-local-variable 'python-preoutput-result) nil)
(while (progn
(accept-process-output proc 5)
(null python-preoutput-result)))
(prog1 python-preoutput-result
(kill-local-variable 'python-preoutput-result)))))
(when (python-check-comint-prompt proc)
(set (make-local-variable 'python-preoutput-result) nil)
(while (progn
(accept-process-output proc 5)
(null python-preoutput-result)))
(prog1 python-preoutput-result
(kill-local-variable 'python-preoutput-result))))))
(defun python-check-comint-prompt (&optional proc)
"Return non-nil iff there's a normal prompt in the inferior buffer.
If there isn't, it's probably not appropriate to send input to return
Eldoc information etc. If PROC is non-nil, check the buffer for that
process."
(with-current-buffer (process-buffer (or proc (python-proc)))
(save-excursion
(save-match-data (re-search-backward ">>> \\=" nil t)))))
;; Fixme: Is there anything reasonable we can do with random methods?
;; (Currently only works with functions.)
@ -1716,22 +1812,22 @@ instance. Assumes an inferior Python is running."
(with-local-quit
;; First try the symbol we're on.
(or (and symbol
(python-send-receive (format "emacs.eargs(%S, %s)"
symbol python-imports)))
;; Try moving to symbol before enclosing parens.
(let ((s (syntax-ppss)))
(unless (zerop (car s))
(when (eq ?\( (char-after (nth 1 s)))
(save-excursion
(goto-char (nth 1 s))
(skip-syntax-backward "-")
(let ((point (point)))
(skip-chars-backward "a-zA-Z._")
(if (< (point) point)
(python-send-receive
(format "emacs.eargs(%S, %s)"
(buffer-substring-no-properties (point) point)
python-imports))))))))))))
(python-send-receive (format "emacs.eargs(%S, %s)"
symbol python-imports)))
;; Try moving to symbol before enclosing parens.
(let ((s (syntax-ppss)))
(unless (zerop (car s))
(when (eq ?\( (char-after (nth 1 s)))
(save-excursion
(goto-char (nth 1 s))
(skip-syntax-backward "-")
(let ((point (point)))
(skip-chars-backward "a-zA-Z._")
(if (< (point) point)
(python-send-receive
(format "emacs.eargs(%S, %s)"
(buffer-substring-no-properties (point) point)
python-imports))))))))))))
;;;; Info-look functionality.
@ -1751,9 +1847,9 @@ Used with `eval-after-load'."
(condition-case ()
;; Don't use `info' because it would pop-up a *info* buffer.
(with-no-warnings
(Info-goto-node (format "(python%s-lib)Miscellaneous Index"
version))
t)
(Info-goto-node (format "(python%s-lib)Miscellaneous Index"
version))
t)
(error nil)))))
(info-lookup-maybe-add-help
:mode 'python-mode
@ -1829,7 +1925,7 @@ The criterion is either a match for `jython-mode' via
"`fill-paragraph-function' handling multi-line strings and possibly comments.
If any of the current line is in or at the end of a multi-line string,
fill the string or the paragraph of it that point is in, preserving
the strings's indentation."
the string's indentation."
(interactive "P")
(or (fill-comment-paragraph justify)
(save-excursion
@ -1845,7 +1941,7 @@ the strings's indentation."
;; paragraph in a multi-line string properly, so narrow
;; to the string and then fill around (the end of) the
;; current line.
((eq t (nth 3 syntax)) ; in fenced string
((eq t (nth 3 syntax)) ; in fenced string
(goto-char (nth 8 syntax)) ; string start
(setq start (line-beginning-position))
(setq end (condition-case () ; for unbalanced quotes
@ -1868,14 +1964,25 @@ the strings's indentation."
;; """
;; ...
;; """
(let* ((paragraph-separate
(concat ".*\\s|\"\"$" ; newline after opening quotes
"\\|\\(?:" paragraph-separate "\\)"))
(paragraph-start
(concat ".*\\s|\"\"[ \t]*[^ \t].*" ; not newline after
; opening quotes
"\\|\\(?:" paragraph-separate "\\)"))
(fill-paragraph-function))
(let ((paragraph-separate
;; Note that the string could be part of an
;; expression, so it can have preceding and
;; trailing non-whitespace.
(concat
(rx (or
;; Opening triple quote without following text.
(and (* nonl)
(group (syntax string-delimiter))
(repeat 2 (backref 1))
;; Fixme: Not sure about including
;; trailing whitespace.
(* (any " \t"))
eol)
;; Closing trailing quote without preceding text.
(and (group (any ?\" ?')) (backref 2)
(syntax string-delimiter))))
"\\(?:" paragraph-separate "\\)"))
fill-paragraph-function)
(fill-paragraph justify))))))) t)
(defun python-shift-left (start end &optional count)
@ -1988,7 +2095,8 @@ Uses `python-beginning-of-block', `python-end-of-block'."
(let ((start (line-beginning-position)))
;; Skip over continued lines.
(while (and (eq ?\\ (char-before (line-end-position)))
(= 0 (forward-line 1))))
(= 0 (forward-line 1)))
t)
(push (buffer-substring start (line-beginning-position 2))
lines))))
(setq python-imports
@ -2041,60 +2149,6 @@ Uses `python-imports' to load modules against which to complete."
nil t)
(match-beginning 1)))))
(if start (buffer-substring-no-properties start end))))
(defun python-complete-symbol ()
"Perform completion on the Python symbol preceding point.
Repeating the command scrolls the completion window."
(interactive)
(let ((window (get-buffer-window "*Completions*")))
(if (and (eq last-command this-command)
(window-live-p window) (window-buffer window)
(buffer-name (window-buffer window)))
(with-current-buffer (window-buffer window)
(if (pos-visible-in-window-p (point-max) window)
(set-window-start window (point-min))
(save-selected-window
(select-window window)
(scroll-up))))
;; Do completion.
(let* ((end (point))
(symbol (python-partial-symbol))
(completions (python-symbol-completions symbol))
(completion (if completions
(try-completion symbol completions))))
(when symbol
(cond ((eq completion t))
((null completion)
(message "Can't find completion for \"%s\"" symbol)
(ding))
((not (string= symbol completion))
(delete-region (- end (length symbol)) end)
(insert completion))
(t
(message "Making completion list...")
(with-output-to-temp-buffer "*Completions*"
(display-completion-list completions symbol))
(message "Making completion list...%s" "done"))))))))
(defun python-try-complete (old)
"Completion function for Python for use with `hippie-expand'."
(when (derived-mode-p 'python-mode) ; though we only add it locally
(unless old
(let ((symbol (python-partial-symbol)))
(he-init-string (- (point) (length symbol)) (point))
(if (not (he-string-member he-search-string he-tried-table))
(push he-search-string he-tried-table))
(setq he-expand-list
(and symbol (python-symbol-completions symbol)))))
(while (and he-expand-list
(he-string-member (car he-expand-list) he-tried-table))
(pop he-expand-list))
(if he-expand-list
(progn
(he-substitute-string (pop he-expand-list))
t)
(if old (he-reset-string))
nil)))
;;;; FFAP support
@ -2105,6 +2159,33 @@ Repeating the command scrolls the completion window."
(eval-after-load "ffap"
'(push '(python-mode . python-module-path) ffap-alist))
;;;; Find-function support
;; Fixme: key binding?
(defun python-find-function (name)
"Find source of definition of function NAME.
Interactively, prompt for name."
(interactive
(let ((symbol (with-syntax-table python-dotty-syntax-table
(current-word)))
(enable-recursive-minibuffers t))
(list (read-string (if symbol
(format "Find location of (default %s): " symbol)
"Find location of: ")
nil nil symbol))))
(unless python-imports
(error "Not called from buffer visiting Python file"))
(let* ((loc (python-send-receive (format "emacs.location_of (%S, %s)"
name python-imports)))
(loc (car (read-from-string loc)))
(file (car loc))
(line (cdr loc)))
(unless file (error "Don't know where `%s' is defined" name))
(pop-to-buffer (find-file-noselect file))
(when (integerp line)
(goto-line line))))
;;;; Skeletons
(defcustom python-use-skeletons nil
@ -2132,24 +2213,31 @@ The default contents correspond to the elements of `python-skeletons'."
(function (intern (concat "python-insert-" name))))
`(progn
(add-to-list 'python-skeletons ',(cons name function))
(define-abbrev python-mode-abbrev-table ,name "" ',function
:system t :case-fixed t
:enable-function (lambda () python-use-skeletons))
;; Usual technique for inserting a skeleton, but expand
;; to the original abbrev instead if in a comment or string.
(define-abbrev python-mode-abbrev-table ,name ""
;; Quote this to give a readable abbrev table.
'(lambda ()
(if (python-in-string/comment)
(insert ,name)
(,function)))
nil t) ; system abbrev
(define-skeleton ,function
,(format "Insert Python \"%s\" template." name)
,@elements)))))
(put 'def-python-skeleton 'lisp-indent-function 2)
;; From `skeleton-further-elements':
;; From `skeleton-further-elements' set below:
;; `<': outdent a level;
;; `^': delete indentation on current line and also previous newline.
;; Not quote like `delete-indentation'. Assumes point is at
;; Not quite like `delete-indentation'. Assumes point is at
;; beginning of indentation.
(def-python-skeleton if
"Condition: "
"if " str ":" \n
> _ \n
> -1 ; Fixme: I don't understand the spurious space this removes.
_ \n
("other condition, %s: "
< ; Avoid wrong indentation after block opening.
"elif " str ":" \n
@ -2167,19 +2255,19 @@ The default contents correspond to the elements of `python-skeletons'."
(def-python-skeleton while
"Condition: "
"while " str ":" \n
> _ \n
> -1 _ \n
'(python-else) | ^)
(def-python-skeleton for
"Target, %s: "
"for " str " in " (skeleton-read "Expression, %s: ") ":" \n
> _ \n
> -1 _ \n
'(python-else) | ^)
(def-python-skeleton try/except
nil
"try:" \n
> _ \n
> -1 _ \n
("Exception, %s: "
< "except " str '(python-target) ":" \n
> _ \n nil)
@ -2194,7 +2282,7 @@ The default contents correspond to the elements of `python-skeletons'."
(def-python-skeleton try/finally
nil
"try:" \n
> _ \n
> -1 _ \n
< "finally:" \n
> _ \n)
@ -2202,7 +2290,7 @@ The default contents correspond to the elements of `python-skeletons'."
"Name: "
"def " str " (" ("Parameter, %s: " (unless (equal ?\( (char-before)) ", ")
str) "):" \n
"\"\"\"" @ " \"\"\"" \n ; Fixme: syntaxification wrong for """"""
"\"\"\"" - "\"\"\"" \n ; Fixme: extra space inserted -- why?).
> _ \n)
(def-python-skeleton class
@ -2212,7 +2300,7 @@ The default contents correspond to the elements of `python-skeletons'."
str)
& ")" | -2 ; close list or remove opening
":" \n
"\"\"\"" @ " \"\"\"" \n
"\"\"\"" - "\"\"\"" \n
> _ \n)
(defvar python-default-template "if"
@ -2255,35 +2343,35 @@ without confirmation."
(pymacs-load "bikeemacs" "brm-") ; first line of normal recipe
(let ((py-mode-map (make-sparse-keymap)) ; it assumes this
(features (cons 'python-mode features))) ; and requires this
(brm-init)) ; second line of normal recipe
(remove-hook 'python-mode-hook ; undo this from `brm-init'
'(lambda () (easy-menu-add brm-menu)))
(easy-menu-define
python-brm-menu python-mode-map
"Bicycle Repair Man"
'("BicycleRepairMan"
:help "Interface to navigation and refactoring tool"
"Queries"
["Find References" brm-find-references
:help "Find references to name at point in compilation buffer"]
["Find Definition" brm-find-definition
:help "Find definition of name at point"]
"-"
"Refactoring"
["Rename" brm-rename
:help "Replace name at point with a new name everywhere"]
["Extract Method" brm-extract-method
:active (and mark-active (not buffer-read-only))
:help "Replace statements in region with a method"]
["Extract Local Variable" brm-extract-local-variable
:active (and mark-active (not buffer-read-only))
:help "Replace expression in region with an assignment"]
["Inline Local Variable" brm-inline-local-variable
:help
"Substitute uses of variable at point with its definition"]
;; Fixme: Should check for anything to revert.
["Undo Last Refactoring" brm-undo :help ""])))
(error (error "Bicyclerepairman setup failed: %s" data))))
(brm-init) ; second line of normal recipe
(remove-hook 'python-mode-hook ; undo this from `brm-init'
'(lambda () (easy-menu-add brm-menu)))
(easy-menu-define
python-brm-menu python-mode-map
"Bicycle Repair Man"
'("BicycleRepairMan"
:help "Interface to navigation and refactoring tool"
"Queries"
["Find References" brm-find-references
:help "Find references to name at point in compilation buffer"]
["Find Definition" brm-find-definition
:help "Find definition of name at point"]
"-"
"Refactoring"
["Rename" brm-rename
:help "Replace name at point with a new name everywhere"]
["Extract Method" brm-extract-method
:active (and mark-active (not buffer-read-only))
:help "Replace statements in region with a method"]
["Extract Local Variable" brm-extract-local-variable
:active (and mark-active (not buffer-read-only))
:help "Replace expression in region with an assignment"]
["Inline Local Variable" brm-inline-local-variable
:help
"Substitute uses of variable at point with its definition"]
;; Fixme: Should check for anything to revert.
["Undo Last Refactoring" brm-undo :help ""]))))
(error (error "BicycleRepairMan setup failed: %s" data))))
;;;; Modes.
@ -2291,10 +2379,27 @@ without confirmation."
(defvar eldoc-documentation-function)
(defvar python-mode-running) ;Dynamically scoped var.
;; Stuff to allow expanding abbrevs with non-word constituents.
(defun python-abbrev-pc-hook ()
"Reset the syntax table after possibly expanding abbrevs."
(remove-hook 'post-command-hook 'python-abbrev-pc-hook t)
(set-syntax-table python-mode-syntax-table))
(defvar python-abbrev-syntax-table
(copy-syntax-table python-mode-syntax-table)
"Syntax table used when expanding abbrevs.")
(defun python-pea-hook ()
"Set the syntax table before possibly expanding abbrevs."
(set-syntax-table python-abbrev-syntax-table)
(add-hook 'post-command-hook 'python-abbrev-pc-hook nil t))
(modify-syntax-entry ?/ "w" python-abbrev-syntax-table)
;;;###autoload
(define-derived-mode python-mode fundamental-mode "Python"
"Major mode for editing Python files.
Font Lock mode is currently required for correct parsing of the source.
Turns on Font Lock mode unconditionally since it is currently required
for correct parsing of the source.
See also `jython-mode', which is actually invoked if the buffer appears to
contain Jython code. See also `run-python' and associated Python mode
commands for running Python under Emacs.
@ -2363,24 +2468,31 @@ with skeleton expansions for compound statement templates.
(add-hook 'eldoc-mode-hook
(lambda () (run-python nil t)) ; need it running
nil t)
(set (make-local-variable 'symbol-completion-symbol-function)
'python-partial-symbol)
(set (make-local-variable 'symbol-completion-completions-function)
'python-symbol-completions)
;; Fixme: should be in hideshow. This seems to be of limited use
;; since it isn't (can't be) indentation-based. Also hide-level
;; doesn't seem to work properly.
(add-to-list 'hs-special-modes-alist
`(python-mode "^\\s-*\\(?:def\\|class\\)\\>" nil "#"
,(lambda (arg)
(python-end-of-defun)
(skip-chars-backward " \t\n"))
(python-end-of-defun)
(skip-chars-backward " \t\n"))
nil))
(set (make-local-variable 'skeleton-further-elements)
'((< '(backward-delete-char-untabify (min python-indent
(current-column))))
(^ '(- (1+ (current-indentation))))))
(add-hook 'pre-abbrev-expand-hook 'python-pea-hook nil t)
(if (featurep 'hippie-exp)
(set (make-local-variable 'hippie-expand-try-functions-list)
(cons 'python-try-complete hippie-expand-try-functions-list)))
(cons 'symbol-completion-try-complete
hippie-expand-try-functions-list)))
;; Python defines TABs as being 8-char wide.
(set (make-local-variable 'tab-width) 8)
(unless font-lock-mode (font-lock-mode 1))
(when python-guess-indent (python-guess-indent))
;; Let's make it harder for the user to shoot himself in the foot.
(unless (= tab-width python-indent)
@ -2391,11 +2503,16 @@ with skeleton expansions for compound statement templates.
(let ((python-mode-running t))
(python-maybe-jython))))
;; Not done automatically in Emacs 21 or 22.
(defcustom python-mode-hook nil
"Hook run when entering Python mode."
:group 'python
:type 'hook)
(custom-add-option 'python-mode-hook 'imenu-add-menubar-index)
(custom-add-option 'python-mode-hook
(lambda ()
"Turn off Indent Tabs mode."
(set (make-local-variable 'indent-tabs-mode) nil)))
"Turn off Indent Tabs mode."
(setq indent-tabs-mode nil)))
(custom-add-option 'python-mode-hook 'turn-on-eldoc-mode)
(custom-add-option 'python-mode-hook 'abbrev-mode)
(custom-add-option 'python-mode-hook 'python-setup-brm)
@ -2776,5 +2893,6 @@ filter."
(provide 'python)
(provide 'python-21)
;; arch-tag: 6fce1d99-a704-4de9-ba19-c6e4912b0554
;;; python.el ends here