-;;; python.el -- Python's flying circus support for Emacs
+;;; python.el --- Python's flying circus support for Emacs
-;; Copyright (C) 2010 Free Software Foundation, Inc.
+;; Copyright (C) 2010, 2011 Free Software Foundation, Inc.
;; Author: Fabián E. Gallina <fabian@anue.biz>
+;; URL: https://github.com/fgallina/python.el
+;; Version: 0.23.1
;; Maintainer: FSF
;; Created: Jul 2010
;; Keywords: languages
;; Implements Syntax highlighting, Indentation, Movement, Shell
;; interaction, Shell completion, Pdb tracking, Symbol completion,
-;; Eldoc.
+;; Skeletons, FFAP, Code Check, Eldoc, imenu.
;; Syntax highlighting: Fontification of code is provided and supports
;; python's triple quoted strings properly.
;; causes the current line to be dedented automatically if needed.
;; Movement: `beginning-of-defun' and `end-of-defun' functions are
-;; properly implemented. A `beginning-of-innermost-defun' is defined
-;; to navigate nested defuns.
+;; properly implemented. Also there are specialized
+;; `forward-sentence' and `backward-sentence' replacements
+;; (`python-nav-forward-sentence', `python-nav-backward-sentence'
+;; respectively). Extra functions `python-nav-sentence-start' and
+;; `python-nav-sentence-end' are included to move to the beginning and
+;; to the end of a setence while taking care of multiline definitions.
;; Shell interaction: is provided and allows you easily execute any
;; block of code of your current buffer in an inferior Python process.
;; IPython) it should be easy to integrate another way to calculate
;; completions. You just need to specify your custom
;; `python-shell-completion-setup-code' and
-;; `python-shell-completion-string-code'
+;; `python-shell-completion-string-code'.
;; Here is a complete example of the settings you would use for
;; iPython
;; pyreadline from http://ipython.scipy.org/moin/PyReadline/Intro and
;; you should be good to go.
+;; The shell also contains support for virtualenvs and other special
+;; environment modification thanks to
+;; `python-shell-process-environment' and `python-shell-exec-path'.
+;; These two variables allows you to modify execution paths and
+;; enviroment variables to make easy for you to setup virtualenv rules
+;; or behaviors modifications when running shells. Here is an example
+;; of how to make shell processes to be run using the /path/to/env/
+;; virtualenv:
+
+;; (setq python-shell-process-environment
+;; (list
+;; (format "PATH=%s" (mapconcat
+;; 'identity
+;; (reverse
+;; (cons (getenv "PATH")
+;; '("/path/to/env/bin/")))
+;; ":"))
+;; "VIRTUAL_ENV=/path/to/env/"))
+;; (python-shell-exec-path . ("/path/to/env/bin/"))
+
;; Pdb tracking: when you execute a block of code that contains some
;; call to pdb (or ipdb) it will prompt the block of code and will
;; follow the execution of pdb marking the current line with an arrow.
;; out of the box. This feature needs an inferior python shell
;; running.
-;; Code check: Check the current file for errors using
-;; `python-check-command'
+;; Code check: Check the current file for errors with `python-check'
+;; using the program defined in `python-check-command'.
;; Eldoc: returns documentation for object at point by using the
;; inferior python subprocess to inspect its documentation. As you
;; might guessed you should run `python-shell-send-buffer' from time
;; to time to get better results too.
+;; imenu: This mode supports imenu. It builds a plain or tree menu
+;; depending on the value of `python-imenu-make-tree'. Also you can
+;; customize if menu items should include its type using
+;; `python-imenu-include-defun-type'.
+
;; If you used python-mode.el you probably will miss auto-indentation
;; when inserting newlines. To achieve the same behavior you have
;; two options:
;; Ordered by priority:
-;; Better decorator support for beginning of defun
-
-;; Review code and cleanup
+;; Give a better interface for virtualenv support in interactive
+;; shells
;;; Code:
(defvar python-mode-map
(let ((map (make-sparse-keymap)))
+ ;; Movement
+ (substitute-key-definition 'backward-sentence
+ 'python-nav-backward-sentence
+ map global-map)
+ (substitute-key-definition 'forward-sentence
+ 'python-nav-forward-sentence
+ map global-map)
;; Indent specific
(define-key map "\177" 'python-indent-dedent-line-backspace)
(define-key map (kbd "<backtab>") 'python-indent-dedent-line)
"-"
["Start of def/class" beginning-of-defun
:help "Go to start of outermost definition around point"]
- ["Start of def/class" python-beginning-of-innermost-defun
- :help "Go to start of innermost definition around point"]
["End of def/class" end-of-defun
:help "Go to end of definition around point"]
"-"
(or "def" "class" "if" "elif" "else" "try"
"except" "finally" "for" "while" "with")
symbol-end))
+ `(decorator . ,(rx line-start (* space) ?@ (any letter ?_)
+ (* (any word ?_))))
`(defun . ,(rx symbol-start (or "def" "class") symbol-end))
+ `(symbol-name . ,(rx (any letter ?_) (* (any word ?_))))
`(open-paren . ,(rx (or "{" "[" "(")))
`(close-paren . ,(rx (or "}" "]" ")")))
`(simple-operator . ,(rx (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%)))
"=" "%" "**" "//" "<<" ">>" "<=" "!="
"==" ">=" "is" "not")))
`(assignment-operator . ,(rx (or "=" "+=" "-=" "*=" "/=" "//=" "%=" "**="
- ">>=" "<<=" "&=" "^=" "|="))))))
+ ">>=" "<<=" "&=" "^=" "|="))))
+ "Additional Python specific sexps for `python-rx'"))
(defmacro python-rx (&rest regexps)
- "Python mode especialized rx macro which supports common python named REGEXPS."
+ "Python mode specialized rx macro which supports common python named REGEXPS."
(let ((rx-constituents (append python-rx-constituents rx-constituents)))
(cond ((null regexps)
(error "No regexp"))
(,(rx symbol-start "class" (1+ space) (group (1+ (or word ?_))))
(1 font-lock-type-face))
;; Constants
- (,(rx symbol-start (group "None" symbol-end))
- (1 font-lock-constant-face))
+ (,(rx symbol-start
+ ;; copyright, license, credits, quit, exit are added by the
+ ;; site module and since they are not intended to be used in
+ ;; programs they are not added here either.
+ (or "None" "True" "False" "Ellipsis" "__debug__" "NotImplemented")
+ symbol-end) . font-lock-constant-face)
;; Decorators.
(,(rx line-start (* (any " \t")) (group "@" (1+ (or word ?_))
(0+ "." (1+ (or word ?_)))))
"FutureWarning" "GeneratorExit" "IOError" "ImportError"
"ImportWarning" "IndentationError" "IndexError" "KeyError"
"KeyboardInterrupt" "LookupError" "MemoryError" "NameError"
- "NotImplemented" "NotImplementedError" "OSError" "OverflowError"
+ "NotImplementedError" "OSError" "OverflowError"
"PendingDeprecationWarning" "ReferenceError" "RuntimeError"
"RuntimeWarning" "StandardError" "StopIteration" "SyntaxError"
"SyntaxWarning" "SystemError" "SystemExit" "TabError" "TypeError"
"UserWarning" "ValueError" "Warning" "ZeroDivisionError")
symbol-end) . font-lock-type-face)
;; Builtins
- (,(rx (or line-start (not (any ". \t"))) (* (any " \t")) symbol-start
- (group
- (or "_" "__debug__" "__doc__" "__import__" "__name__" "__package__"
- "abs" "all" "any" "apply" "basestring" "bin" "bool" "buffer"
- "bytearray" "bytes" "callable" "chr" "classmethod" "cmp" "coerce"
- "compile" "complex" "copyright" "credits" "delattr" "dict" "dir"
- "divmod" "enumerate" "eval" "execfile" "exit" "file" "filter"
- "float" "format" "frozenset" "getattr" "globals" "hasattr" "hash"
- "help" "hex" "id" "input" "int" "intern" "isinstance" "issubclass"
- "iter" "len" "license" "list" "locals" "long" "map" "max" "min"
- "next" "object" "oct" "open" "ord" "pow" "print" "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"
- "True" "False" "Ellipsis")) symbol-end)
- (1 font-lock-builtin-face))
+ (,(rx symbol-start
+ (or "_" "__doc__" "__import__" "__name__" "__package__" "abs" "all"
+ "any" "apply" "basestring" "bin" "bool" "buffer" "bytearray"
+ "bytes" "callable" "chr" "classmethod" "cmp" "coerce" "compile"
+ "complex" "delattr" "dict" "dir" "divmod" "enumerate" "eval"
+ "execfile" "file" "filter" "float" "format" "frozenset"
+ "getattr" "globals" "hasattr" "hash" "help" "hex" "id" "input"
+ "int" "intern" "isinstance" "issubclass" "iter" "len" "list"
+ "locals" "long" "map" "max" "min" "next" "object" "oct" "open"
+ "ord" "pow" "print" "property" "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) . font-lock-builtin-face)
;; asignations
;; support for a = b = c = 5
(,(lambda (limit)
(set-match-data nil)))))
(1 font-lock-variable-name-face nil nil))))
-;; Fixme: Is there a better way?
(defconst python-font-lock-syntactic-keywords
+ ;; Make outer chars of matching triple-quote sequences into generic
+ ;; string delimiters. Fixme: Is there a better way?
;; 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
- (group (backref 2))) ; maybe gets property
- (1 (python-quote-syntax 1))
- (2 (python-quote-syntax 2))
- (3 (python-quote-syntax 3))))
- "Make outer chars of triple-quote strings into generic string delimiters.")
-
-(defun python-quote-syntax (n)
+ `((,(concat "\\(?:\\([RUru]\\)[Rr]?\\|^\\|[^\\]\\(?:\\\\.\\)*\\)" ;Prefix.
+ "\\(?:\\('\\)'\\('\\)\\|\\(?2:\"\\)\"\\(?3:\"\\)\\)")
+ (3 (python-quote-syntax)))))
+
+(defun python-quote-syntax ()
"Put `syntax-table' property correctly on triple quote.
Used for syntactic keywords. N is the match number (1, 2 or 3)."
;; Given a triple quote, we have to check the context to know
;; x '"""' x """ \"""" x
(save-excursion
(goto-char (match-beginning 0))
- (cond
- ;; Consider property for the last char if in a fenced string.
- ((= n 3)
- (let* ((font-lock-syntactic-keywords nil)
- (syntax (syntax-ppss)))
- (when (eq t (nth 3 syntax)) ; after unclosed fence
- (goto-char (nth 8 syntax)) ; fence position
- (skip-chars-forward "uUrR") ; skip any prefix
- ;; Is it a matching sequence?
- (if (eq (char-after) (char-after (match-beginning 2)))
- (eval-when-compile (string-to-syntax "|"))))))
- ;; Consider property for initial char, accounting for prefixes.
- ((or (and (= n 2) ; leading quote (not prefix)
- (= (match-beginning 1) (match-end 1))) ; prefix is null
- (and (= n 1) ; prefix
- (/= (match-beginning 1) (match-end 1)))) ; non-empty
- (let ((font-lock-syntactic-keywords nil))
- (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.
- )))
+ (let ((syntax (save-match-data (syntax-ppss))))
+ (cond
+ ((eq t (nth 3 syntax)) ; after unclosed fence
+ ;; Consider property for the last char if in a fenced string.
+ (goto-char (nth 8 syntax)) ; fence position
+ (skip-chars-forward "uUrR") ; skip any prefix
+ ;; Is it a matching sequence?
+ (if (eq (char-after) (char-after (match-beginning 2)))
+ (put-text-property (match-beginning 3) (match-end 3)
+ 'syntax-table (string-to-syntax "|"))))
+ ((match-end 1)
+ ;; Consider property for initial char, accounting for prefixes.
+ (put-text-property (match-beginning 1) (match-end 1)
+ 'syntax-table (string-to-syntax "|")))
+ (t
+ ;; Consider property for initial char, accounting for prefixes.
+ (put-text-property (match-beginning 2) (match-end 2)
+ 'syntax-table (string-to-syntax "|"))))
+ )))
(defvar python-mode-syntax-table
(let ((table (make-syntax-table)))
(not (python-info-ppss-context 'comment))
(progn
(goto-char (line-end-position))
- (forward-comment -1)
+ (forward-comment -9999)
(eq ?: (char-before))))
(setq found-block t)))
(if (not found-block)
(not (eobp)))
(forward-line 1))
(forward-line 1)
- (forward-comment 1)
+ (forward-comment 9999)
(let ((indent-offset (current-indentation)))
(when (> indent-offset 0)
(setq python-indent-offset indent-offset))))))))
-(defun python-indent-context (&optional stop)
- "Return information on indentation context.
-Optional argument STOP serves to stop recursive calls.
-
-Returns a cons with the form:
-
-\(STATUS . START)
+(defun python-indent-context ()
+ "Get information on indentation context.
+Context information is returned with a cons with the form:
+ \(STATUS . START)
Where status can be any of the following symbols:
-
* inside-paren: If point in between (), {} or []
* inside-string: If point is inside a string
* after-backslash: Previous line ends in a backslash
* after-beginning-of-block: Point is after beginning of block
* after-line: Point is after normal line
* no-indent: Point is at beginning of buffer or other special case
-
START is the buffer position where the sexp starts."
(save-restriction
(widen)
(let ((block-regexp (python-rx block-start))
(block-start-line-end ":[[:space:]]*$"))
(back-to-indentation)
- (while (and (forward-comment -1) (not (bobp))))
+ (while (and (forward-comment -9999) (not (bobp))))
(back-to-indentation)
(when (or (python-info-continuation-line-p)
(and (not (looking-at block-regexp))
'after-beginning-of-block)
;; After normal line
((setq start (save-excursion
- (while (and (forward-comment -1) (not (bobp))))
- (while (and (not (back-to-indentation))
- (not (bobp))
- (if (python-info-ppss-context 'paren)
- (forward-line -1)
- (if (save-excursion
- (forward-line -1)
- (python-info-line-ends-backslash-p))
- (forward-line -1)))))
+ (while (and (forward-comment -9999) (not (bobp))))
+ (python-nav-sentence-start)
(point-marker)))
'after-line)
;; Do not indent
(back-to-indentation)
(when (looking-at "\\.")
(forward-line -1)
- (back-to-indentation)
- (forward-char (length
- (with-syntax-table python-dotty-syntax-table
- (current-word))))
- (re-search-backward "\\." (line-beginning-position) t 1)
- (current-column))))
- (indentation (cond (block-continuation
- (goto-char block-continuation)
- (re-search-forward
- (python-rx block-start (* space))
- (line-end-position) t)
- (current-column))
- (assignment-continuation
- (goto-char assignment-continuation)
- (re-search-forward
- (python-rx simple-operator)
- (line-end-position) t)
- (forward-char 1)
- (re-search-forward
- (python-rx (* space))
- (line-end-position) t)
- (current-column))
- (dot-continuation
- dot-continuation)
- (t
- (goto-char context-start)
- (current-indentation)))))
+ (goto-char (line-end-position))
+ (while (and (re-search-backward "\\." (line-beginning-position) t)
+ (or (python-info-ppss-context 'comment)
+ (python-info-ppss-context 'string)
+ (python-info-ppss-context 'paren))))
+ (if (and (looking-at "\\.")
+ (not (or (python-info-ppss-context 'comment)
+ (python-info-ppss-context 'string)
+ (python-info-ppss-context 'paren))))
+ (current-column)
+ (+ (current-indentation) python-indent-offset)))))
+ (indentation (cond
+ (dot-continuation
+ dot-continuation)
+ (block-continuation
+ (goto-char block-continuation)
+ (re-search-forward
+ (python-rx block-start (* space))
+ (line-end-position) t)
+ (current-column))
+ (assignment-continuation
+ (goto-char assignment-continuation)
+ (re-search-forward
+ (python-rx simple-operator)
+ (line-end-position) t)
+ (forward-char 1)
+ (re-search-forward
+ (python-rx (* space))
+ (line-end-position) t)
+ (current-column))
+ (t
+ (goto-char context-start)
+ (if (not
+ (save-excursion
+ (back-to-indentation)
+ (looking-at
+ "\\(?:return\\|from\\|import\\)\s+")))
+ (current-indentation)
+ (+ (current-indentation)
+ (length
+ (match-string-no-properties 0))))))))
indentation))
('inside-paren
(or (save-excursion
- (forward-comment 1)
- (looking-at (regexp-opt '(")" "]" "}")))
- (forward-char 1)
- (when (not (python-info-ppss-context 'paren))
+ (skip-syntax-forward "\s" (line-end-position))
+ (when (and (looking-at (regexp-opt '(")" "]" "}")))
+ (not (forward-char 1))
+ (not (python-info-ppss-context 'paren)))
(goto-char context-start)
(back-to-indentation)
(current-column)))
(narrow-to-region
(line-beginning-position)
(line-end-position))
- (forward-comment 1))
+ (forward-comment 9999))
(if (looking-at "$")
(+ (current-indentation) python-indent-offset)
- (forward-comment 1)
+ (forward-comment 9999)
(current-column)))
(if (progn
(back-to-indentation)
(let* ((indentation (python-indent-calculate-indentation))
(remainder (% indentation python-indent-offset))
(steps (/ (- indentation remainder) python-indent-offset)))
- (setq python-indent-levels '())
- (setq python-indent-levels (cons 0 python-indent-levels))
+ (setq python-indent-levels (list 0))
(dotimes (step steps)
- (setq python-indent-levels
- (cons (* python-indent-offset (1+ step)) python-indent-levels)))
+ (push (* python-indent-offset (1+ step)) python-indent-levels))
(when (not (eq 0 remainder))
- (setq python-indent-levels
- (cons (+ (* python-indent-offset steps) remainder)
- python-indent-levels)))
+ (push (+ (* python-indent-offset steps) remainder) python-indent-levels))
(setq python-indent-levels (nreverse python-indent-levels))
(setq python-indent-current-level (1- (length python-indent-levels)))))
(defun python-indent-line (&optional force-toggle)
"Internal implementation of `python-indent-line-function'.
-
Uses the offset calculated in
`python-indent-calculate-indentation' and available levels
-indicated by the variable `python-indent-levels'.
+indicated by the variable `python-indent-levels' to set the
+current indentation.
When the variable `last-command' is equal to
-`indent-for-tab-command' or FORCE-TOGGLE is non-nil:
-
-* Cycles levels indicated in the variable `python-indent-levels'
- by setting the current level in the variable
- `python-indent-current-level'.
+`indent-for-tab-command' or FORCE-TOGGLE is non-nil it cycles
+levels indicated in the variable `python-indent-levels' by
+setting the current level in the variable
+`python-indent-current-level'.
When the variable `last-command' is not equal to
-`indent-for-tab-command' and FORCE-TOGGLE is nil:
-
-* calculates possible indentation levels and saves it in the
- variable `python-indent-levels'.
-
-* sets the variable `python-indent-current-level' correctly so
- offset is equal to (`nth' `python-indent-current-level'
- `python-indent-levels')"
+`indent-for-tab-command' and FORCE-TOGGLE is nil it calculates
+possible indentation levels and saves it in the variable
+`python-indent-levels'. Afterwards it sets the variable
+`python-indent-current-level' correctly so offset is equal
+to (`nth' `python-indent-current-level' `python-indent-levels')"
(if (or (and (eq this-command 'indent-for-tab-command)
(eq last-command this-command))
force-toggle)
- (python-indent-toggle-levels)
+ (if (not (equal python-indent-levels '(0)))
+ (python-indent-toggle-levels)
+ (python-indent-calculate-levels))
(python-indent-calculate-levels))
(beginning-of-line)
(delete-horizontal-space)
(defun python-indent-line-function ()
"`indent-line-function' for Python mode.
-Internally just calls `python-indent-line'."
+See `python-indent-line' for details."
(python-indent-line))
(defun python-indent-dedent-line ()
- "Dedent current line."
+ "De-indent current line."
(interactive "*")
(when (and (not (or (python-info-ppss-context 'string)
(python-info-ppss-context 'comment)))
t))
(defun python-indent-dedent-line-backspace (arg)
- "Dedent current line.
+ "De-indent current line.
Argument ARG is passed to `backward-delete-char-untabify' when
-point is not in between the indentation."
+point is not in between the indentation."
(interactive "*p")
(when (not (python-indent-dedent-line))
(backward-delete-char-untabify arg)))
(defun python-indent-shift-left (start end &optional count)
"Shift lines contained in region START END by COUNT columns to the left.
-
-COUNT defaults to `python-indent-offset'.
-
-If region isn't active, the current line is shifted.
-
-The shifted region includes the lines in which START and END lie.
-
-An error is signaled if any lines in the region are indented less
-than COUNT columns."
+COUNT defaults to `python-indent-offset'. If region isn't
+active, the current line is shifted. The shifted region includes
+the lines in which START and END lie. An error is signaled if
+any lines in the region are indented less than COUNT columns."
(interactive
(if mark-active
(list (region-beginning) (region-end) current-prefix-arg)
(defun python-indent-shift-right (start end &optional count)
"Shift lines contained in region START END by COUNT columns to the left.
-
-COUNT defaults to `python-indent-offset'.
-
-If region isn't active, the current line is shifted.
-
-The shifted region includes the lines in which START and END
-lie."
+COUNT defaults to `python-indent-offset'. If region isn't
+active, the current line is shifted. The shifted region includes
+the lines in which START and END lie."
(interactive
(if mark-active
(list (region-beginning) (region-end) current-prefix-arg)
(indent-rigidly start end count)))
(defun python-indent-electric-colon (arg)
- "Insert a colon and maybe outdent the line if it is a statement like `else'.
-With numeric ARG, just insert that many colons. With \\[universal-argument],
-just insert a single colon."
+ "Insert a colon and maybe de-indent the current line.
+With numeric ARG, just insert that many colons. With
+\\[universal-argument], just insert a single colon."
(interactive "*P")
(self-insert-command (if (not (integerp arg)) 1 arg))
(when (and (not arg)
\f
;;; Navigation
-(defvar python-beginning-of-defun-regexp
- "^\\(def\\|class\\)[[:space:]]+[[:word:]]+"
- "Regular expresion matching beginning of outermost class or function.")
-
-(defvar python-beginning-of-innermost-defun-regexp
- "^[[:space:]]*\\(def\\|class\\)[[:space:]]+[[:word:]]+"
- "Regular expresion matching beginning of innermost class or function.")
-
-(defun python-beginning-of-defun (&optional innermost)
- "Move point to the beginning of innermost/outermost def or class.
-If INNERMOST is non-nil then move to the beginning of the
-innermost definition."
- (let ((starting-point (point-marker))
- (nonblank-line-indent)
- (defun-indent)
- (defun-point)
- (regexp (if innermost
- python-beginning-of-innermost-defun-regexp
- python-beginning-of-defun-regexp)))
- (back-to-indentation)
- (if (and (not (looking-at "@"))
- (not (looking-at regexp)))
- (forward-comment -1)
- (while (and (not (eobp))
- (forward-line 1)
- (not (back-to-indentation))
- (looking-at "@"))))
- (when (not (looking-at regexp))
- (re-search-backward regexp nil t))
- (setq nonblank-line-indent (+ (current-indentation) python-indent-offset))
- (setq defun-indent (current-indentation))
- (setq defun-point (point-marker))
- (if (> nonblank-line-indent defun-indent)
+(defvar python-nav-beginning-of-defun-regexp
+ (python-rx line-start (* space) defun (+ space) (group symbol-name))
+ "Regular expresion matching beginning of class or function.
+The name of the defun should be grouped so it can be retrieved
+via `match-string'.")
+
+(defun python-nav-beginning-of-defun (&optional nodecorators)
+ "Move point to `beginning-of-defun'.
+When NODECORATORS is non-nil decorators are not included. This
+is the main part of`python-beginning-of-defun-function'
+implementation. Return non-nil if point is moved to the
+`beginning-of-defun'."
+ (let ((indent-pos (save-excursion
+ (back-to-indentation)
+ (point-marker)))
+ (found)
+ (include-decorators
+ (lambda ()
+ (when (not nodecorators)
+ (when (save-excursion
+ (forward-line -1)
+ (looking-at (python-rx decorator)))
+ (while (and (not (bobp))
+ (forward-line -1)
+ (looking-at (python-rx decorator))))
+ (when (not (bobp)) (forward-line 1)))))))
+ (if (and (> (point) indent-pos)
+ (save-excursion
+ (goto-char (line-beginning-position))
+ (looking-at python-nav-beginning-of-defun-regexp)))
(progn
- (goto-char defun-point)
- (forward-line -1)
- (while (and (looking-at "@")
- (forward-line -1)
- (not (bobp))
- (not (back-to-indentation))))
- (unless (bobp)
- (forward-line 1))
- (point-marker))
- (if innermost
- (python-beginning-of-defun)
- (goto-char starting-point)
- nil))))
-
-(defun python-beginning-of-defun-function ()
- "Move point to the beginning of outermost def or class.
-Returns nil if point is not in a def or class."
- (python-beginning-of-defun nil))
-
-(defun python-beginning-of-innermost-defun ()
- "Move point to the beginning of innermost def or class.
-Returns nil if point is not in a def or class."
- (interactive)
- (python-beginning-of-defun t))
+ (goto-char (line-beginning-position))
+ (funcall include-decorators)
+ (setq found t))
+ (goto-char (line-beginning-position))
+ (when (re-search-backward python-nav-beginning-of-defun-regexp nil t)
+ (setq found t))
+ (goto-char (or (python-info-ppss-context 'string) (point)))
+ (funcall include-decorators))
+ found))
+
+(defun python-beginning-of-defun-function (&optional arg nodecorators)
+ "Move point to the beginning of def or class.
+With positive ARG move that number of functions forward. With
+negative do the same but backwards. When NODECORATORS is non-nil
+decorators are not included. Return non-nil if point is moved to the
+`beginning-of-defun'."
+ (when (or (null arg) (= arg 0)) (setq arg 1))
+ (if (> arg 0)
+ (dotimes (i arg (python-nav-beginning-of-defun nodecorators)))
+ (let ((found))
+ (dotimes (i (- arg) found)
+ (python-end-of-defun-function)
+ (forward-comment 9999)
+ (goto-char (line-end-position))
+ (when (not (eobp))
+ (setq found
+ (python-nav-beginning-of-defun nodecorators)))))))
(defun python-end-of-defun-function ()
"Move point to the end of def or class.
Returns nil if point is not in a def or class."
- (let ((starting-point (point-marker))
- (defun-regexp (python-rx defun))
- (beg-defun-indent))
- (back-to-indentation)
- (if (looking-at "@")
- (while (and (not (eobp))
- (forward-line 1)
- (not (back-to-indentation))
- (looking-at "@")))
- (while (and (not (bobp))
- (not (progn (back-to-indentation) (current-word)))
- (forward-line -1))))
- (when (or (not (equal (current-indentation) 0))
- (string-match defun-regexp (current-word)))
- (setq beg-defun-indent (save-excursion
- (or (looking-at defun-regexp)
- (python-beginning-of-innermost-defun))
- (current-indentation)))
- (while (and (forward-line 1)
- (not (eobp))
- (or (not (current-word))
- (> (current-indentation) beg-defun-indent))))
- (while (and (forward-comment -1)
- (not (bobp))))
- (forward-line 1)
- (point-marker))))
+ (interactive)
+ (let ((beg-defun-indent)
+ (decorator-regexp "[[:space:]]*@"))
+ (when (looking-at decorator-regexp)
+ (while (and (not (eobp))
+ (forward-line 1)
+ (looking-at decorator-regexp))))
+ (when (not (looking-at python-nav-beginning-of-defun-regexp))
+ (python-beginning-of-defun-function))
+ (setq beg-defun-indent (current-indentation))
+ (forward-line 1)
+ (while (and (forward-line 1)
+ (not (eobp))
+ (or (not (current-word))
+ (> (current-indentation) beg-defun-indent))))
+ (forward-comment 9999)
+ (goto-char (line-beginning-position))))
+
+(defun python-nav-sentence-start ()
+ "Move to start of current sentence."
+ (interactive "^")
+ (while (and (not (back-to-indentation))
+ (not (bobp))
+ (when (or
+ (save-excursion
+ (forward-line -1)
+ (python-info-line-ends-backslash-p))
+ (python-info-ppss-context 'string)
+ (python-info-ppss-context 'paren))
+ (forward-line -1)))))
+
+(defun python-nav-sentence-end ()
+ "Move to end of current sentence."
+ (interactive "^")
+ (while (and (goto-char (line-end-position))
+ (not (eobp))
+ (when (or
+ (python-info-line-ends-backslash-p)
+ (python-info-ppss-context 'string)
+ (python-info-ppss-context 'paren))
+ (forward-line 1)))))
+
+(defun python-nav-backward-sentence (&optional arg)
+ "Move backward to start of sentence. With ARG, do it arg times.
+See `python-nav-forward-sentence' for more information."
+ (interactive "^p")
+ (or arg (setq arg 1))
+ (python-nav-forward-sentence (- arg)))
+
+(defun python-nav-forward-sentence (&optional arg)
+ "Move forward to next end of sentence. With ARG, repeat.
+With negative argument, move backward repeatedly to start of sentence."
+ (interactive "^p")
+ (or arg (setq arg 1))
+ (while (> arg 0)
+ (forward-comment 9999)
+ (python-nav-sentence-end)
+ (forward-line 1)
+ (setq arg (1- arg)))
+ (while (< arg 0)
+ (python-nav-sentence-end)
+ (forward-comment -9999)
+ (python-nav-sentence-start)
+ (forward-line -1)
+ (setq arg (1+ arg))))
\f
;;; Shell integration
:group 'python
:safe 'stringp)
+(defvar python-shell-internal-buffer-name "Python Internal"
+ "Default buffer name for the Internal Python interpreter.")
+
(defcustom python-shell-interpreter-args "-i"
"Default arguments for the Python interpreter."
:type 'string
:safe 'stringp)
(defcustom python-shell-prompt-regexp ">>> "
- "Regex matching top\-level input prompt of python shell.
-The regex should not contain a caret (^) at the beginning."
+ "Regular Expression matching top\-level input prompt of python shell.
+It should not contain a caret (^) at the beginning."
:type 'string
:group 'python
:safe 'stringp)
(defcustom python-shell-prompt-block-regexp "[.][.][.] "
- "Regex matching block input prompt of python shell.
-The regex should not contain a caret (^) at the beginning."
+ "Regular Expression matching block input prompt of python shell.
+It should not contain a caret (^) at the beginning."
:type 'string
:group 'python
:safe 'stringp)
(defcustom python-shell-prompt-output-regexp nil
- "Regex matching output prompt of python shell.
-The regex should not contain a caret (^) at the beginning."
+ "Regular Expression matching output prompt of python shell.
+It should not contain a caret (^) at the beginning."
:type 'string
:group 'python
:safe 'stringp)
(defcustom python-shell-prompt-pdb-regexp "[(<]*[Ii]?[Pp]db[>)]+ "
- "Regex matching pdb input prompt of python shell.
-The regex should not contain a caret (^) at the beginning."
+ "Regular Expression matching pdb input prompt of python shell.
+It should not contain a caret (^) at the beginning."
:type 'string
:group 'python
:safe 'stringp)
+(defcustom python-shell-send-setup-max-wait 5
+ "Seconds to wait for process output before code setup.
+If output is received before the especified time then control is
+returned in that moment and not after waiting."
+ :type 'number
+ :group 'python
+ :safe 'numberp)
+
+(defcustom python-shell-process-environment nil
+ "List of enviroment variables for Python shell.
+This variable follows the same rules as `process-enviroment'
+since it merges with it before the process creation routines are
+called. When this variable is nil, the Python shell is run with
+the default `process-enviroment'."
+ :type '(repeat string)
+ :group 'python
+ :safe 'listp)
+
+(defcustom python-shell-exec-path nil
+ "List of path to search for binaries.
+This variable follows the same rules as `exec-path' since it
+merges with it before the process creation routines are called.
+When this variable is nil, the Python shell is run with the
+default `exec-path'."
+ :type '(repeat string)
+ :group 'python
+ :safe 'listp)
+
(defcustom python-shell-setup-codes '(python-shell-completion-setup-code
python-ffap-setup-code
python-eldoc-setup-code)
"List of code run by `python-shell-send-setup-codes'.
-Each variable can be either a simple string with the code to
+Each variable can contain either a simple string with the code to
execute or a cons with the form (CODE . DESCRIPTION), where CODE
is a string with the code to execute and DESCRIPTION is the
description of it."
(defun python-shell-get-process-name (dedicated)
"Calculate the appropiate process name for inferior Python process.
-
If DEDICATED is t and the variable `buffer-file-name' is non-nil
returns a string with the form
`python-shell-buffer-name'[variable `buffer-file-name'] else
-returns the value of `python-shell-buffer-name'.
-
-After calculating the process name add the buffer name for the
-process in the `same-window-buffer-names' list"
+returns the value of `python-shell-buffer-name'. After
+calculating the process name adds the buffer name for the process
+in the `same-window-buffer-names' list."
(let ((process-name
(if (and dedicated
buffer-file-name)
(format "*%s*" process-name)))
process-name))
+(defun python-shell-internal-get-process-name ()
+ "Calculate the appropiate process name for Internal Python process.
+The name is calculated from `python-shell-global-buffer-name' and
+a hash of all relevant global shell settings in order to ensure
+uniqueness for different types of configurations."
+ (format "%s [%s]"
+ python-shell-internal-buffer-name
+ (md5
+ (concat
+ (python-shell-parse-command)
+ (mapconcat #'symbol-value python-shell-setup-codes "")
+ (mapconcat #'indentity python-shell-process-environment "")
+ (mapconcat #'indentity python-shell-exec-path "")))))
+
(defun python-shell-parse-command ()
- "Calculates the string used to execute the inferior Python process."
+ "Calculate the string used to execute the inferior Python process."
(format "%s %s" python-shell-interpreter python-shell-interpreter-args))
(defun python-comint-output-filter-function (output)
(define-derived-mode inferior-python-mode comint-mode "Inferior Python"
"Major mode for Python inferior process.
-Adds `python-shell-completion-complete-at-point' to the
-`comint-dynamic-complete-functions' list. Also binds <tab> to
-`python-shell-complete-or-indent' in the
-`inferior-python-mode-map'."
+Runs a Python interpreter as a subprocess of Emacs, with Python
+I/O through an Emacs buffer. Variables
+`python-shell-interpreter' and `python-shell-interpreter-args'
+controls which Python interpreter is run. Variables
+`python-shell-prompt-regexp',
+`python-shell-prompt-output-regexp',
+`python-shell-prompt-block-regexp',
+`python-shell-completion-setup-code',
+`python-shell-completion-string-code', `python-eldoc-setup-code',
+`python-eldoc-string-code', `python-ffap-setup-code' and
+`python-ffap-string-code' can customize this mode for different
+Python interpreters.
+
+You can also add additional setup code to be run at
+initialization of the interpreter via `python-shell-setup-codes'
+variable.
+
+\(Type \\[describe-mode] in the process buffer for a list of commands.)"
(set-syntax-table python-mode-syntax-table)
(setq mode-line-process '(":%s"))
(setq comint-prompt-regexp (format "^\\(?:%s\\|%s\\|%s\\)"
(defun run-python (dedicated cmd)
"Run an inferior Python process.
-
-Input and output via buffer *\\[python-shell-buffer-name]*.
-
-If there is a process already running in
-*\\[python-shell-buffer-name]*, switch to that buffer.
-
-With argument, allows you to:
-
- * Define DEDICATED so a dedicated process for the current buffer
- is open.
-
- * Define CMD so you can edit the command used to call the
-interpreter (default is value of `python-shell-interpreter' and
-arguments defined in `python-shell-interpreter-args').
-
-Runs the hook `inferior-python-mode-hook' (after the
-`comint-mode-hook' is run).
-
-\(Type \\[describe-mode] in the process buffer for a list of
-commands.)"
+Input and output via buffer named after
+`python-shell-buffer-name'. If there is a process already
+running in that buffer, just switch to it.
+With argument, allows you to define DEDICATED, so a dedicated
+process for the current buffer is open, and define CMD so you can
+edit the command used to call the interpreter (default is value
+of `python-shell-interpreter' and arguments defined in
+`python-shell-interpreter-args'). Runs the hook
+`inferior-python-mode-hook' (after the `comint-mode-hook' is
+run).
+\(Type \\[describe-mode] in the process buffer for a list of commands.)"
(interactive
(if current-prefix-arg
(list
(read-string "Run Python: " (python-shell-parse-command)))
(list nil (python-shell-parse-command))))
(let* ((proc-name (python-shell-get-process-name dedicated))
- (proc-buffer-name (format "*%s*" proc-name)))
+ (proc-buffer-name (format "*%s*" proc-name))
+ (process-environment
+ (if python-shell-process-environment
+ (python-util-merge 'list python-shell-process-environment
+ process-environment 'string=)
+ process-environment))
+ (exec-path
+ (if python-shell-exec-path
+ (python-util-merge 'list python-shell-exec-path
+ exec-path 'string=)
+ exec-path)))
(when (not (comint-check-proc proc-buffer-name))
(let ((cmdlist (split-string-and-unquote cmd)))
(set-buffer
(pop-to-buffer proc-buffer-name))
dedicated)
+(defun run-python-internal ()
+ "Run an inferior Internal Python process.
+Input and output via buffer named after
+`python-shell-internal-buffer-name' and what
+`python-shell-internal-get-process-name' returns. This new kind
+of shell is intended to be used for generic communication related
+to defined configurations. The main difference with global or
+dedicated shells is that these ones are attached to a
+configuration, not a buffer. This means that can be used for
+example to retrieve the sys.path and other stuff, without messing
+with user shells. Runs the hook
+`inferior-python-mode-hook' (after the `comint-mode-hook' is
+run). \(Type \\[describe-mode] in the process buffer for a list
+of commands.)"
+ (interactive)
+ (save-excursion
+ (let* ((cmd (python-shell-parse-command))
+ (proc-name (python-shell-internal-get-process-name))
+ (proc-buffer-name (format "*%s*" proc-name))
+ (process-environment
+ (if python-shell-process-environment
+ (python-util-merge 'list python-shell-process-environment
+ process-environment 'string=)
+ process-environment))
+ (exec-path
+ (if python-shell-exec-path
+ (python-util-merge 'list python-shell-exec-path
+ exec-path 'string=)
+ exec-path)))
+ (when (not (comint-check-proc proc-buffer-name))
+ (let ((cmdlist (split-string-and-unquote cmd)))
+ (set-buffer
+ (apply 'make-comint proc-name (car cmdlist) nil
+ (cdr cmdlist)))
+ (inferior-python-mode))))))
+
(defun python-shell-get-process ()
"Get inferior Python process for current buffer and return it."
(let* ((dedicated-proc-name (python-shell-get-process-name t))
dedicated-proc-buffer-name
global-proc-buffer-name))))
+(defun python-shell-internal-get-or-create-process ()
+ "Get or create an inferior Internal Python process."
+ (let* ((proc-name (python-shell-internal-get-process-name))
+ (proc-buffer-name (format "*%s*" proc-name)))
+ (run-python-internal)
+ (get-buffer-process proc-buffer-name)))
+
(defun python-shell-send-string (string &optional process msg)
"Send STRING to inferior Python PROCESS.
When MSG is non-nil messages the first line of STRING."
(defun python-shell-send-string-no-output (string &optional process msg)
"Send STRING to PROCESS and inhibit output.
-When MSG is non-nil messages the first line of STRING.
-Return the output."
+When MSG is non-nil messages the first line of STRING. Return
+the output."
(let* ((output-buffer)
(process (or process (python-shell-get-or-create-process)))
(comint-preoutput-filter-functions
(with-temp-buffer
(insert output-buffer)
(goto-char (point-min))
- (forward-comment 1)
+ (forward-comment 9999)
(buffer-substring-no-properties
(or
(and (looking-at python-shell-prompt-output-regexp)
(lambda (string) string)
(butlast (split-string output-buffer "\n")) "\n")))
+(defun python-shell-internal-send-string (string)
+ "Send STRING to the Internal Python interpreter.
+Returns the output. See `python-shell-send-string-no-output'."
+ (python-shell-send-string-no-output
+ ;; Makes this function compatible with the old
+ ;; python-send-receive. (At least for CEDET).
+ (replace-regexp-in-string "_emacs_out +" "" string)
+ (python-shell-internal-get-or-create-process) nil))
+
+(define-obsolete-function-alias
+ 'python-send-receive 'python-shell-internal-send-string "23.3"
+ "Send STRING to inferior Python (if any) and return result.
+The result is what follows `_emacs_out' in the output.
+This is a no-op if `python-check-comint-prompt' returns nil.")
+
(defun python-shell-send-region (start end)
"Send the region delimited by START and END to inferior Python process."
(interactive "r")
(python-shell-send-region (point-min) (point-max))))
(defun python-shell-send-defun (arg)
- "Send the (inner|outer)most def or class to inferior Python process.
+ "Send the current defun to inferior Python process.
When argument ARG is non-nil sends the innermost defun."
(interactive "P")
(save-excursion
- (python-shell-send-region (progn
- (or (if arg
- (python-beginning-of-innermost-defun)
- (python-beginning-of-defun-function))
- (progn (beginning-of-line) (point-marker))))
- (progn
- (or (python-end-of-defun-function)
- (progn (end-of-line) (point-marker)))))))
+ (python-shell-send-region
+ (progn
+ (or (python-beginning-of-defun-function)
+ (progn (beginning-of-line) (point-marker))))
+ (progn
+ (or (python-end-of-defun-function)
+ (progn (end-of-line) (point-marker)))))))
(defun python-shell-send-file (file-name &optional process temp-file-name)
"Send FILE-NAME to inferior Python PROCESS.
`python-shell-setup-codes' list."
(let ((msg "Sent %s")
(process (get-buffer-process (current-buffer))))
- (accept-process-output process 1)
+ (accept-process-output process python-shell-send-setup-max-wait)
(dolist (code python-shell-setup-codes)
(when code
(when (consp code)
(defun python-shell-completion-complete-or-indent ()
"Complete or indent depending on the context.
-If content before pointer is all whitespace indent. If not try to
-complete."
+If content before pointer is all whitespace indent. If not try
+to complete."
(interactive)
(if (string-match "^[[:space:]]*$"
(buffer-substring (comint-line-beginning-position)
(defvar python-pdbtrack-stacktrace-info-regexp
"> %s(\\([0-9]+\\))\\([?a-zA-Z0-9_<>]+\\)()"
- "Regexp matching stacktrace information.
-It is used to extract the current line and module beign
-inspected.
-The regexp should not start with a caret (^) and can contain a
-string placeholder (\%s) which is replaced with the filename
-beign inspected (so other files in the debugging process are not
+ "Regular Expression matching stacktrace information.
+Used to extract the current line and module beign inspected. The
+regexp should not start with a caret (^) and can contain a string
+placeholder (\%s) which is replaced with the filename beign
+inspected (so other files in the debugging process are not
opened)")
(defvar python-pdbtrack-tracking-buffers '()
\f
;;; Fill paragraph
+(defcustom python-fill-comment-function 'python-fill-comment
+ "Function to fill comments.
+This is the function used by `python-fill-paragraph-function' to
+fill comments."
+ :type 'symbol
+ :group 'python
+ :safe 'symbolp)
+
+(defcustom python-fill-string-function 'python-fill-string
+ "Function to fill strings.
+This is the function used by `python-fill-paragraph-function' to
+fill strings."
+ :type 'symbol
+ :group 'python
+ :safe 'symbolp)
+
+(defcustom python-fill-decorator-function 'python-fill-decorator
+ "Function to fill decorators.
+This is the function used by `python-fill-paragraph-function' to
+fill decorators."
+ :type 'symbol
+ :group 'python
+ :safe 'symbolp)
+
+(defcustom python-fill-paren-function 'python-fill-paren
+ "Function to fill parens.
+This is the function used by `python-fill-paragraph-function' to
+fill parens."
+ :type 'symbol
+ :group 'python
+ :safe 'symbolp)
+
(defun python-fill-paragraph-function (&optional justify)
"`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,
(back-to-indentation)
(cond
;; Comments
- ((fill-comment-paragraph justify))
- ;; Docstrings
+ ((funcall python-fill-comment-function justify))
+ ;; Strings/Docstrings
((save-excursion (skip-chars-forward "\"'uUrR")
(python-info-ppss-context 'string))
- (let ((marker (point-marker))
- (string-start-marker
- (progn
- (skip-chars-forward "\"'uUrR")
- (goto-char (python-info-ppss-context 'string))
- (skip-chars-forward "\"'uUrR")
- (point-marker)))
- (reg-start (line-beginning-position))
- (string-end-marker
- (progn
- (while (python-info-ppss-context 'string)
- (goto-char (1+ (point-marker))))
- (skip-chars-backward "\"'")
- (point-marker)))
- (reg-end (line-end-position))
- (fill-paragraph-function))
- (save-restriction
- (narrow-to-region reg-start reg-end)
- (save-excursion
- (goto-char string-start-marker)
- (delete-region (point-marker) (progn
- (skip-syntax-forward "> ")
- (point-marker)))
- (goto-char string-end-marker)
- (delete-region (point-marker) (progn
- (skip-syntax-backward "> ")
- (point-marker)))
- (save-excursion
- (goto-char marker)
- (fill-paragraph justify))
- ;; If there is a newline in the docstring lets put triple
- ;; quote in it's own line to follow pep 8
- (when (save-excursion
- (re-search-backward "\n" string-start-marker t))
- (newline)
- (newline-and-indent))
- (fill-paragraph justify)))) t)
+ (funcall python-fill-string-function justify))
;; Decorators
((equal (char-after (save-excursion
(back-to-indentation)
- (point-marker))) ?@) t)
+ (point-marker))) ?@)
+ (funcall python-fill-decorator-function justify))
;; Parens
((or (python-info-ppss-context 'paren)
(looking-at (python-rx open-paren))
(save-excursion
(skip-syntax-forward "^(" (line-end-position))
(looking-at (python-rx open-paren))))
- (save-restriction
- (narrow-to-region (progn
- (while (python-info-ppss-context 'paren)
- (goto-char (1- (point-marker))))
- (point-marker)
- (line-beginning-position))
- (progn
- (when (not (python-info-ppss-context 'paren))
- (end-of-line)
- (when (not (python-info-ppss-context 'paren))
- (skip-syntax-backward "^)")))
- (while (python-info-ppss-context 'paren)
- (goto-char (1+ (point-marker))))
- (point-marker)))
- (let ((paragraph-start "\f\\|[ \t]*$")
- (paragraph-separate ",")
- (fill-paragraph-function))
- (goto-char (point-min))
- (fill-paragraph justify))
- (while (not (eobp))
- (forward-line 1)
- (python-indent-line)
- (goto-char (line-end-position)))) t)
+ (funcall python-fill-paren-function justify))
(t t))))
+(defun python-fill-comment (&optional justify)
+ "Comment fill function for `python-fill-paragraph-function'.
+JUSTIFY should be used (if applicable) as in `fill-paragraph'."
+ (fill-comment-paragraph justify))
+
+(defun python-fill-string (&optional justify)
+ "String fill function for `python-fill-paragraph-function'.
+JUSTIFY should be used (if applicable) as in `fill-paragraph'."
+ (let ((marker (point-marker))
+ (string-start-marker
+ (progn
+ (skip-chars-forward "\"'uUrR")
+ (goto-char (python-info-ppss-context 'string))
+ (skip-chars-forward "\"'uUrR")
+ (point-marker)))
+ (reg-start (line-beginning-position))
+ (string-end-marker
+ (progn
+ (while (python-info-ppss-context 'string)
+ (goto-char (1+ (point-marker))))
+ (skip-chars-backward "\"'")
+ (point-marker)))
+ (reg-end (line-end-position))
+ (fill-paragraph-function))
+ (save-restriction
+ (narrow-to-region reg-start reg-end)
+ (save-excursion
+ (goto-char string-start-marker)
+ (delete-region (point-marker) (progn
+ (skip-syntax-forward "> ")
+ (point-marker)))
+ (goto-char string-end-marker)
+ (delete-region (point-marker) (progn
+ (skip-syntax-backward "> ")
+ (point-marker)))
+ (save-excursion
+ (goto-char marker)
+ (fill-paragraph justify))
+ ;; If there is a newline in the docstring lets put triple
+ ;; quote in it's own line to follow pep 8
+ (when (save-excursion
+ (re-search-backward "\n" string-start-marker t))
+ (newline)
+ (newline-and-indent))
+ (fill-paragraph justify)))) t)
+
+(defun python-fill-decorator (&optional justify)
+ "Decorator fill function for `python-fill-paragraph-function'.
+JUSTIFY should be used (if applicable) as in `fill-paragraph'."
+ t)
+
+(defun python-fill-paren (&optional justify)
+ "Paren fill function for `python-fill-paragraph-function'.
+JUSTIFY should be used (if applicable) as in `fill-paragraph'."
+ (save-restriction
+ (narrow-to-region (progn
+ (while (python-info-ppss-context 'paren)
+ (goto-char (1- (point-marker))))
+ (point-marker)
+ (line-beginning-position))
+ (progn
+ (when (not (python-info-ppss-context 'paren))
+ (end-of-line)
+ (when (not (python-info-ppss-context 'paren))
+ (skip-syntax-backward "^)")))
+ (while (python-info-ppss-context 'paren)
+ (goto-char (1+ (point-marker))))
+ (point-marker)))
+ (let ((paragraph-start "\f\\|[ \t]*$")
+ (paragraph-separate ",")
+ (fill-paragraph-function))
+ (goto-char (point-min))
+ (fill-paragraph justify))
+ (while (not (eobp))
+ (forward-line 1)
+ (python-indent-line)
+ (goto-char (line-end-position)))) t)
+
\f
;;; Skeletons
(defvar python-eldoc-setup-code
"def __PYDOC_get_help(obj):
try:
- import pydoc
+ import inspect
if hasattr(obj, 'startswith'):
obj = eval(obj, globals())
- doc = pydoc.getdoc(obj)
+ doc = inspect.getdoc(obj)
+ if not doc and callable(obj):
+ target = None
+ if inspect.isclass(obj) and hasattr(obj, '__init__'):
+ target = obj.__init__
+ objtype = 'class'
+ else:
+ target = obj
+ objtype = 'def'
+ if target:
+ args = inspect.formatargspec(
+ *inspect.getargspec(target)
+ )
+ name = obj.__name__
+ doc = '{objtype} {name}{args}'.format(
+ objtype=objtype, name=name, args=args
+ )
+ else:
+ doc = doc.splitlines()[0]
except:
doc = ''
try:
(let ((process (python-shell-get-process)))
(if (not process)
(message "Eldoc needs an inferior Python process running.")
- (let ((temp-buffer-show-hook
- (lambda ()
- (toggle-read-only 1)
- (setq view-return-to-alist
- (list (cons (selected-window) help-return-method))))))
- (with-output-to-temp-buffer (help-buffer)
- (with-current-buffer standard-output
- (insert
- (python-eldoc--get-doc-at-point symbol process))
- (help-print-return-message)))))))
+ (message (python-eldoc--get-doc-at-point symbol process)))))
+
+\f
+;;; Imenu
+
+(defcustom python-imenu-include-defun-type t
+ "Non-nil make imenu items to include its type."
+ :type 'boolean
+ :group 'python
+ :safe 'booleanp)
+
+(defcustom python-imenu-make-tree t
+ "Non-nil make imenu to build a tree menu.
+Set to nil for speed."
+ :type 'boolean
+ :group 'python
+ :safe 'booleanp)
+
+(defcustom python-imenu-subtree-root-label "<Jump to %s>"
+ "Label displayed to navigate to root from a subtree.
+It can contain a \"%s\" which will be replaced with the root name."
+ :type 'string
+ :group 'python
+ :safe 'stringp)
+
+(defvar python-imenu-index-alist nil
+ "Calculated index tree for imenu.")
+
+(defun python-imenu-tree-assoc (keylist tree)
+ "Using KEYLIST traverse TREE."
+ (if keylist
+ (python-imenu-tree-assoc (cdr keylist)
+ (ignore-errors (assoc (car keylist) tree)))
+ tree))
+
+(defun python-imenu-make-element-tree (element-list full-element plain-index)
+ "Make a tree from plain alist of module names.
+ELEMENT-LIST is the defun name splitted by \".\" and FULL-ELEMENT
+is the same thing, the difference is that FULL-ELEMENT remains
+untouched in all recursive calls.
+Argument PLAIN-INDEX is the calculated plain index used to build the tree."
+ (when (not (python-imenu-tree-assoc full-element python-imenu-index-alist))
+ (when element-list
+ (let* ((subelement-point (cdr (assoc
+ (mapconcat #'identity full-element ".")
+ plain-index)))
+ (subelement-name (car element-list))
+ (subelement-position (python-util-position
+ subelement-name full-element))
+ (subelement-path (when subelement-position
+ (butlast
+ full-element
+ (- (length full-element)
+ subelement-position)))))
+ (let ((path-ref (python-imenu-tree-assoc subelement-path
+ python-imenu-index-alist)))
+ (if (not path-ref)
+ (push (cons subelement-name subelement-point)
+ python-imenu-index-alist)
+ (when (not (listp (cdr path-ref)))
+ ;; Modifiy root cdr to be a list
+ (setcdr path-ref
+ (list (cons (format python-imenu-subtree-root-label
+ (car path-ref))
+ (cdr (assoc
+ (mapconcat #'identity
+ subelement-path ".")
+ plain-index))))))
+ (when (not (assoc subelement-name path-ref))
+ (push (cons subelement-name subelement-point) (cdr path-ref))))))
+ (python-imenu-make-element-tree (cdr element-list)
+ full-element plain-index))))
+
+(defun python-imenu-make-tree (index)
+"Build the imenu alist tree from plain INDEX.
+
+The idea of this function is that given the alist:
+
+ '((\"Test\" . 100)
+ (\"Test.__init__\" . 200)
+ (\"Test.some_method\" . 300)
+ (\"Test.some_method.another\" . 400)
+ (\"Test.something_else\" . 500)
+ (\"test\" . 600)
+ (\"test.reprint\" . 700)
+ (\"test.reprint\" . 800))
+
+This tree gets built:
+
+ '((\"Test\" . ((\"jump to...\" . 100)
+ (\"__init__\" . 200)
+ (\"some_method\" . ((\"jump to...\" . 300)
+ (\"another\" . 400)))
+ (\"something_else\" . 500)))
+ (\"test\" . ((\"jump to...\" . 600)
+ (\"reprint\" . 700)
+ (\"reprint\" . 800))))
+
+Internally it uses `python-imenu-make-element-tree' to create all
+branches for each element."
+(setq python-imenu-index-alist nil)
+(mapc (lambda (element)
+ (python-imenu-make-element-tree element element index))
+ (mapcar (lambda (element)
+ (split-string (car element) "\\." t)) index))
+python-imenu-index-alist)
+
+(defun python-imenu-create-index ()
+ "`imenu-create-index-function' for Python."
+ (let ((index))
+ (goto-char (point-max))
+ (while (python-beginning-of-defun-function 1 t)
+ (let ((defun-dotted-name
+ (python-info-current-defun python-imenu-include-defun-type)))
+ (push (cons defun-dotted-name (point)) index)))
+ (if python-imenu-make-tree
+ (python-imenu-make-tree index)
+ index)))
\f
;;; Misc helpers
-(defun python-info-current-defun ()
+(defun python-info-current-defun (&optional include-type)
"Return name of surrounding function with Python compatible dotty syntax.
+Optional argument INCLUDE-TYPE indicates to include the type of the defun.
This function is compatible to be used as
`add-log-current-defun-function' since it returns nil if point is
not inside a defun."
- (let ((names '()))
+ (let ((names '())
+ (min-indent)
+ (first-run t))
(save-restriction
(widen)
(save-excursion
- (beginning-of-line)
- (when (not (>= (current-indentation) python-indent-offset))
- (while (and (not (eobp)) (forward-comment 1))))
- (while (and (not (equal 0 (current-indentation)))
- (python-beginning-of-innermost-defun))
- (back-to-indentation)
- (looking-at "\\(?:def\\|class\\) +\\([^(]+\\)[^:]+:\\s-*\n")
- (setq names (cons (match-string-no-properties 1) names)))))
+ (goto-char (line-end-position))
+ (forward-comment -9999)
+ (setq min-indent (current-indentation))
+ (while (python-beginning-of-defun-function 1 t)
+ (when (or (< (current-indentation) min-indent)
+ first-run)
+ (setq first-run nil)
+ (setq min-indent (current-indentation))
+ (looking-at python-nav-beginning-of-defun-regexp)
+ (setq names (cons
+ (if (not include-type)
+ (match-string-no-properties 1)
+ (mapconcat 'identity
+ (split-string
+ (match-string-no-properties 0)) " "))
+ names))))))
(when names
(mapconcat (lambda (string) string) names "."))))
(defun python-info-closing-block ()
- "Return the point of the block that the current line closes."
+ "Return the point of the block the current line closes."
(let ((closing-word (save-excursion
(back-to-indentation)
(current-word)))
(t nil))))
\f
+;;; Utility functions
+
+;; Stolen from GNUS
+(defun python-util-merge (type list1 list2 pred)
+ "Destructively merge lists to produce a new one.
+Argument TYPE is for compatibility and ignored. LIST1 and LIST2
+are the list to be merged. Ordering of the elements is preserved
+according to PRED, a `less-than' predicate on the elements."
+ (let ((res nil))
+ (while (and list1 list2)
+ (if (funcall pred (car list2) (car list1))
+ (push (pop list2) res)
+ (push (pop list1) res)))
+ (nconc (nreverse res) list1 list2)))
+
+(defun python-util-position (item seq)
+ "Find the first occurrence of ITEM in SEQ.
+Return the index of the matching item, or nil if not found."
+ (let ((member-result (member item seq)))
+ (when member-result
+ (- (length seq) (length member-result)))))
+
+\f
;;;###autoload
(define-derived-mode python-mode fundamental-mode "Python"
- "A major mode for editing Python files."
+ "Major mode for editing Python files.
+
+\\{python-mode-map}
+Entry to this mode calls the value of `python-mode-hook'
+if that value is non-nil."
(set (make-local-variable 'tab-width) 8)
(set (make-local-variable 'indent-tabs-mode) nil)
(add-hook 'completion-at-point-functions
'python-completion-complete-at-point nil 'local)
+ (setq imenu-create-index-function #'python-imenu-create-index)
+
(set (make-local-variable 'add-log-current-defun-function)
#'python-info-current-defun)
,(lambda (arg)
(python-end-of-defun-function)) nil))
+ (set (make-local-variable 'mode-require-final-newline) t)
+
(set (make-local-variable 'outline-regexp)
(python-rx (* space) block-start))
(set (make-local-variable 'outline-heading-end-regexp) ":\\s-*\n")