# HG changeset patch # User Chong Yidong # Date 1203613427 0 # Node ID 373b00a4775c741946d010fef9afde8b25772bf3 # Parent 6d77acc71bc053d8f54bd1f79d3ed267b0feb72f 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. diff -r 6d77acc71bc0 -r 373b00a4775c lisp/progmodes/python.el --- a/lisp/progmodes/python.el Thu Feb 21 17:03:30 2008 +0000 +++ b/lisp/progmodes/python.el Thu Feb 21 17:03:47 2008 +0000 @@ -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 @@ ;; 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 @@ (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 @@ (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 @@ "-" ["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 @@ ((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 @@ ;; 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 @@ 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 @@ (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 @@ (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,8 +1140,20 @@ (set-text-properties 0 (length function-name) nil function-name) function-name)) + +;;;; Imenu. -;;;; 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 () @@ -1361,11 +1430,16 @@ (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 @@ (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 @@ (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 @@ ;; 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 @@ (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 @@ (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 @@ (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 @@ (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 @@ (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 @@ "`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 @@ ;; 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 @@ ;; """ ;; ... ;; """ - (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 @@ (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 @@ 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 @@ (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 @@ (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 @@ (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 @@ (def-python-skeleton try/finally nil "try:" \n - > _ \n + > -1 _ \n < "finally:" \n > _ \n) @@ -2202,7 +2290,7 @@ "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 @@ str) & ")" | -2 ; close list or remove opening ":" \n - "\"\"\"" @ " \"\"\"" \n + "\"\"\"" - "\"\"\"" \n > _ \n) (defvar python-default-template "if" @@ -2255,35 +2343,35 @@ (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 @@ (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 @@ (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 @@ (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 @@ (provide 'python) (provide 'python-21) + ;; arch-tag: 6fce1d99-a704-4de9-ba19-c6e4912b0554 ;;; python.el ends here