;;; python.el --- Python's flying circus support for Emacs
-;; Copyright (C) 2010, 2011 Free Software Foundation, Inc.
+;; Copyright (C) 2003-2012 Free Software Foundation, Inc.
;; Author: Fabián E. Gallina <fabian@anue.biz>
;; URL: https://github.com/fgallina/python.el
-;; Version: 0.23.1
+;; Version: 0.24.2
;; Maintainer: FSF
;; Created: Jul 2010
;; Keywords: languages
-;; This file is NOT part of GNU Emacs.
+;; This file is part of GNU Emacs.
-;; python.el is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published
+;; by the Free Software Foundation, either version 3 of the License,
+;; or (at your option) any later version.
-;; python.el is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-;; GNU General Public License for more details.
+;; GNU Emacs is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
;; You should have received a copy of the GNU General Public License
-;; along with python.el. If not, see <http://www.gnu.org/licenses/>.
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; indentation bits extracted from original Dave Love's python.el
;; found in GNU/Emacs.
-;; While it probably has less features than Dave Love's python.el and
-;; PSF's python-mode.el it provides the main stuff you'll need while
-;; keeping it simple :)
-
;; Implements Syntax highlighting, Indentation, Movement, Shell
;; interaction, Shell completion, Shell virtualenv support, Pdb
;; tracking, Symbol completion, Skeletons, FFAP, Code Check, Eldoc,
;; Movement: `beginning-of-defun' and `end-of-defun' functions are
;; properly implemented. There are also 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.
-;; `python-nav-jump-to-defun' is provided and allows jumping to a
-;; function or class definition quickly in the current buffer.
+;; `forward-sentence' and `backward-sentence' replacements called
+;; `python-nav-forward-block', `python-nav-backward-block'
+;; respectively which navigate between beginning of blocks of code.
+;; Extra functions `python-nav-forward-statement',
+;; `python-nav-backward-statement',
+;; `python-nav-beginning-of-statement', `python-nav-end-of-statement',
+;; `python-nav-beginning-of-block' and `python-nav-end-of-block' are
+;; included but no bound to any key. At last but not least the
+;; specialized `python-nav-forward-sexp' allows easy
+;; navigation between code blocks.
;; Shell interaction: is provided and allows you to execute easily any
;; block of code of your current buffer in an inferior Python process.
;; "VIRTUAL_ENV=/path/to/env/"))
;; (python-shell-exec-path . ("/path/to/env/bin/"))
-;; Since the above is cumbersome and can be programatically
+;; Since the above is cumbersome and can be programmatically
;; calculated, the variable `python-shell-virtualenv-path' is
;; provided. When this variable is set with the path of the
;; virtualenv to use, `process-environment' and `exec-path' get proper
;; 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'.
+;; imenu: This mode supports imenu in its most basic form, letting it
+;; build the necessary alist via `imenu-default-create-index-function'
+;; by having set `imenu-extract-index-name-function' to
+;; `python-info-current-defun'.
;; If you used python-mode.el you probably will miss auto-indentation
;; when inserting newlines. To achieve the same behavior you have
(let ((map (make-sparse-keymap)))
;; Movement
(substitute-key-definition 'backward-sentence
- 'python-nav-backward-sentence
+ 'python-nav-backward-block
map global-map)
(substitute-key-definition 'forward-sentence
- 'python-nav-forward-sentence
+ 'python-nav-forward-block
map global-map)
- (define-key map "\C-c\C-j" 'python-nav-jump-to-defun)
+ (define-key map "\C-c\C-j" 'imenu)
;; Indent specific
(define-key map "\177" 'python-indent-dedent-line-backspace)
(define-key map (kbd "<backtab>") 'python-indent-dedent-line)
(define-key map "\C-c\C-tt" 'python-skeleton-try)
(define-key map "\C-c\C-tw" 'python-skeleton-while)
;; Shell interaction
+ (define-key map "\C-c\C-p" 'run-python)
(define-key map "\C-c\C-s" 'python-shell-send-string)
(define-key map "\C-c\C-r" 'python-shell-send-region)
(define-key map "\C-\M-x" 'python-shell-send-defun)
:help "Go to end of definition around point"]
["Mark def/class" mark-defun
:help "Mark outermost definition around point"]
- ["Jump to def/class" python-nav-jump-to-defun
+ ["Jump to def/class" imenu
:help "Jump to a class or function definition"]
"--"
("Skeletons")
(eval-when-compile
(defconst python-rx-constituents
- (list
- `(block-start . ,(rx symbol-start
+ `((block-start . ,(rx symbol-start
(or "def" "class" "if" "elif" "else" "try"
"except" "finally" "for" "while" "with")
symbol-end))
- `(decorator . ,(rx line-start (* space) ?@ (any letter ?_)
+ (decorator . ,(rx line-start (* space) ?@ (any letter ?_)
(* (any word ?_))))
- `(defun . ,(rx symbol-start (or "def" "class") symbol-end))
- `(if-name-main . ,(rx line-start "if" (+ space) "__name__"
+ (defun . ,(rx symbol-start (or "def" "class") symbol-end))
+ (if-name-main . ,(rx line-start "if" (+ space) "__name__"
(+ space) "==" (+ space)
(any ?' ?\") "__main__" (any ?' ?\")
(* space) ?:))
- `(symbol-name . ,(rx (any letter ?_) (* (any word ?_))))
- `(open-paren . ,(rx (or "{" "[" "(")))
- `(close-paren . ,(rx (or "}" "]" ")")))
- `(simple-operator . ,(rx (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%)))
- `(not-simple-operator . ,(rx
+ (symbol-name . ,(rx (any letter ?_) (* (any word ?_))))
+ (open-paren . ,(rx (or "{" "[" "(")))
+ (close-paren . ,(rx (or "}" "]" ")")))
+ (simple-operator . ,(rx (any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%)))
+ ;; FIXME: rx should support (not simple-operator).
+ (not-simple-operator . ,(rx
(not
(any ?+ ?- ?/ ?& ?^ ?~ ?| ?* ?< ?> ?= ?%))))
- `(operator . ,(rx (or "+" "-" "/" "&" "^" "~" "|" "*" "<" ">"
+ ;; FIXME: Use regexp-opt.
+ (operator . ,(rx (or "+" "-" "/" "&" "^" "~" "|" "*" "<" ">"
"=" "%" "**" "//" "<<" ">>" "<=" "!="
"==" ">=" "is" "not")))
- `(assignment-operator . ,(rx (or "=" "+=" "-=" "*=" "/=" "//=" "%=" "**="
+ ;; FIXME: Use regexp-opt.
+ (assignment-operator . ,(rx (or "=" "+=" "-=" "*=" "/=" "//=" "%=" "**="
">>=" "<<=" "&=" "^=" "|="))))
"Additional Python specific sexps for `python-rx'"))
\f
;;; Font-lock and syntax
+
+(defun python-syntax-context (type &optional syntax-ppss)
+ "Return non-nil if point is on TYPE using SYNTAX-PPSS.
+TYPE can be `comment', `string' or `paren'. It returns the start
+character address of the specified TYPE."
+ (let ((ppss (or syntax-ppss (syntax-ppss))))
+ (case type
+ (comment (and (nth 4 ppss) (nth 8 ppss)))
+ (string (and (not (nth 4 ppss)) (nth 8 ppss)))
+ (paren (nth 1 ppss))
+ (t nil))))
+
+(defun python-syntax-context-type (&optional syntax-ppss)
+ "Return the context type using SYNTAX-PPSS.
+The type returned can be `comment', `string' or `paren'."
+ (let ((ppss (or syntax-ppss (syntax-ppss))))
+ (cond
+ ((nth 8 ppss) (if (nth 4 ppss) 'comment 'string))
+ ((nth 1 ppss) 'paren))))
+
+(defsubst python-syntax-comment-or-string-p ()
+ "Return non-nil if point is inside 'comment or 'string."
+ (nth 8 (syntax-ppss)))
+
+(define-obsolete-function-alias
+ 'python-info-ppss-context #'python-syntax-context "24.3")
+
+(define-obsolete-function-alias
+ 'python-info-ppss-context-type #'python-syntax-context-type "24.3")
+
+(define-obsolete-function-alias
+ 'python-info-ppss-comment-or-string-p
+ #'python-syntax-comment-or-string-p "24.3")
+
(defvar python-font-lock-keywords
;; Keywords
`(,(rx symbol-start
;; Extra:
"__all__" "__doc__" "__name__" "__package__")
symbol-end) . font-lock-builtin-face)
- ;; asignations
+ ;; assignments
;; support for a = b = c = 5
(,(lambda (limit)
(let ((re (python-rx (group (+ (any word ?. ?_)))
(? ?\[ (+ (not (any ?\]))) ?\]) (* space)
assignment-operator)))
(when (re-search-forward re limit t)
- (while (and (python-info-ppss-context 'paren)
+ (while (and (python-syntax-context 'paren)
(re-search-forward re limit t)))
- (if (and (not (python-info-ppss-context 'paren))
+ (if (and (not (python-syntax-context 'paren))
(not (equal (char-after (point-marker)) ?=)))
t
(set-match-data nil)))))
assignment-operator)))
(when (and (re-search-forward re limit t)
(goto-char (nth 3 (match-data))))
- (while (and (python-info-ppss-context 'paren)
+ (while (and (python-syntax-context 'paren)
(re-search-forward re limit t))
(goto-char (nth 3 (match-data))))
- (if (not (python-info-ppss-context 'paren))
+ (if (not (python-syntax-context 'paren))
t
(set-match-data nil)))))
(1 font-lock-variable-name-face nil nil))))
-(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.
- `((,(concat "\\(?:\\([RUru]\\)[Rr]?\\|^\\|[^\\]\\(?:\\\\.\\)*\\)" ;Prefix.
- "\\(?:''''''\\|\"\"\"\"\"\"\\)" ; Empty triple-quote
- "\\(?:\\('\\)'\\('\\)\\|\\(?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
- ;; whether this is an opening or closing triple or whether it's
- ;; quoted anyhow, and should be ignored. (For that we need to do
- ;; the same job as `syntax-ppss' to be correct and it seems to be OK
- ;; to use it here despite initial worries.) We also have to sort
- ;; out a possible prefix -- well, we don't _have_ to, but I think it
- ;; should be treated as part of the string.
-
- ;; Test cases:
- ;; ur"""ar""" x='"' # """
- ;; x = ''' """ ' a
- ;; '''
- ;; x '"""' x """ \"""" x
- (save-excursion
- (goto-char (match-beginning 0))
- (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 "|"))))
- )))
+(defconst python-syntax-propertize-function
+ (syntax-propertize-rules
+ ((rx
+ (and
+ ;; Match even number of backslashes.
+ (or (not (any ?\\ ?\' ?\")) point
+ ;; Quotes might be preceded by a escaped quote.
+ (and (or (not (any ?\\)) point) ?\\
+ (* ?\\ ?\\) (any ?\' ?\")))
+ (* ?\\ ?\\)
+ ;; Match single or triple quotes of any kind.
+ (group (or "\"" "\"\"\"" "'" "'''"))))
+ (0 (ignore (python-syntax-stringify))))))
+
+(defsubst python-syntax-count-quotes (quote-char &optional point limit)
+ "Count number of quotes around point (max is 3).
+QUOTE-CHAR is the quote char to count. Optional argument POINT is
+the point where scan starts (defaults to current point) and LIMIT
+is used to limit the scan."
+ (let ((i 0))
+ (while (and (< i 3)
+ (or (not limit) (< (+ point i) limit))
+ (eq (char-after (+ point i)) quote-char))
+ (incf i))
+ i))
+
+(defun python-syntax-stringify ()
+ "Put `syntax-table' property correctly on single/triple quotes."
+ (let* ((num-quotes (length (match-string-no-properties 1)))
+ (ppss (prog2
+ (backward-char num-quotes)
+ (syntax-ppss)
+ (forward-char num-quotes)))
+ (string-start (and (not (nth 4 ppss)) (nth 8 ppss)))
+ (quote-starting-pos (- (point) num-quotes))
+ (quote-ending-pos (point))
+ (num-closing-quotes
+ (and string-start
+ (python-syntax-count-quotes
+ (char-before) string-start quote-starting-pos))))
+ (cond ((and string-start (= num-closing-quotes 0))
+ ;; This set of quotes doesn't match the string starting
+ ;; kind. Do nothing.
+ nil)
+ ((not string-start)
+ ;; This set of quotes delimit the start of a string.
+ (put-text-property quote-starting-pos (1+ quote-starting-pos)
+ 'syntax-table (string-to-syntax "|")))
+ ((= num-quotes num-closing-quotes)
+ ;; This set of quotes delimit the end of a string.
+ (put-text-property (1- quote-ending-pos) quote-ending-pos
+ 'syntax-table (string-to-syntax "|")))
+ ((> num-quotes num-closing-quotes)
+ ;; This may only happen whenever a triple quote is closing
+ ;; a single quoted string. Add string delimiter syntax to
+ ;; all three quotes.
+ (put-text-property quote-starting-pos quote-ending-pos
+ 'syntax-table (string-to-syntax "|"))))))
(defvar python-mode-syntax-table
(let ((table (make-syntax-table)))
:group 'python
:safe 'booleanp)
+(define-obsolete-variable-alias
+ 'python-indent 'python-indent-offset "24.3")
+
+(define-obsolete-variable-alias
+ 'python-guess-indent 'python-indent-guess-indent-offset "24.3")
+
(defvar python-indent-current-level 0
"Current indentation level `python-indent-line-function' is using.")
(defun python-indent-guess-indent-offset ()
"Guess and set `python-indent-offset' for the current buffer."
+ (interactive)
(save-excursion
(save-restriction
(widen)
(re-search-forward
(python-rx line-start block-start) nil t))
(when (and
- (not (python-info-ppss-context-type))
+ (not (python-syntax-context-type))
(progn
(goto-char (line-end-position))
(python-util-forward-comment -1)
(bobp))
'no-indent)
;; Inside a paren
- ((setq start (python-info-ppss-context 'paren ppss))
+ ((setq start (python-syntax-context 'paren ppss))
'inside-paren)
;; Inside string
- ((setq start (python-info-ppss-context 'string ppss))
+ ((setq start (python-syntax-context 'string ppss))
'inside-string)
;; After backslash
- ((setq start (when (not (or (python-info-ppss-context 'string ppss)
- (python-info-ppss-context 'comment ppss)))
+ ((setq start (when (not (or (python-syntax-context 'string ppss)
+ (python-syntax-context 'comment ppss)))
(let ((line-beg-pos (line-beginning-position)))
(when (python-info-line-ends-backslash-p
(1- line-beg-pos))
(while (and (re-search-backward
(python-rx block-start) nil t)
(or
- (python-info-ppss-context 'string)
- (python-info-ppss-context 'comment)
- (python-info-ppss-context 'paren)
+ (python-syntax-context-type)
(python-info-continuation-line-p))))
(when (looking-at (python-rx block-start))
(point-marker)))))
((setq start (save-excursion
(back-to-indentation)
(python-util-forward-comment -1)
- (python-nav-sentence-start)
+ (python-nav-beginning-of-statement)
(point-marker)))
'after-line)
;; Do not indent
('inside-string
(goto-char context-start)
(current-indentation))
- ;; After backslash we have several posibilities
+ ;; After backslash we have several possibilities.
('after-backslash
(cond
;; Check if current line is a dot continuation. For this
(while (prog2
(forward-line -1)
(and (not (bobp))
- (python-info-ppss-context 'paren))))
+ (python-syntax-context 'paren))))
(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))))
+ (python-syntax-context-type)))
(if (and (looking-at "\\.")
- (not (or (python-info-ppss-context 'comment)
- (python-info-ppss-context 'string)
- (python-info-ppss-context 'paren))))
+ (not (python-syntax-context-type)))
;; The indentation is the same column of the
;; first matching dot that's not inside a
;; comment, a string or a paren
(current-column))))
(t
(forward-line -1)
- (goto-char (python-info-beginning-of-backlash))
+ (goto-char (python-info-beginning-of-backslash))
(if (save-excursion
(and
(forward-line -1)
(goto-char
- (or (python-info-beginning-of-backlash) (point)))
+ (or (python-info-beginning-of-backslash) (point)))
(python-info-line-ends-backslash-p)))
;; The two previous lines ended in a backslash so we must
;; respect previous line indentation.
;; correctly
('inside-paren
(cond
- ;; If current line closes the outtermost open paren use the
+ ;; If current line closes the outermost open paren use the
;; current indentation of the context-start line.
((save-excursion
(skip-syntax-forward "\s" (line-end-position))
(when (and (looking-at (regexp-opt '(")" "]" "}")))
(progn
(forward-char 1)
- (not (python-info-ppss-context 'paren))))
+ (not (python-syntax-context 'paren))))
(goto-char context-start)
(current-indentation))))
;; If open paren is contained on a line by itself add another
`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)
- (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)
- (indent-to (nth python-indent-current-level python-indent-levels))
+ (or
+ (and (or (and (eq this-command 'indent-for-tab-command)
+ (eq last-command this-command))
+ force-toggle)
+ (not (equal python-indent-levels '(0)))
+ (or (python-indent-toggle-levels) t))
+ (python-indent-calculate-levels))
+ (let* ((starting-pos (point-marker))
+ (indent-ending-position
+ (+ (line-beginning-position) (current-indentation)))
+ (follow-indentation-p
+ (or (bolp)
+ (and (<= (line-beginning-position) starting-pos)
+ (>= indent-ending-position starting-pos))))
+ (next-indent (nth python-indent-current-level python-indent-levels)))
+ (unless (= next-indent (current-indentation))
+ (beginning-of-line)
+ (delete-horizontal-space)
+ (indent-to next-indent)
+ (goto-char starting-pos))
+ (and follow-indentation-p (back-to-indentation)))
(python-info-closing-block-message))
(defun python-indent-line-function ()
(defun python-indent-dedent-line ()
"De-indent current line."
(interactive "*")
- (when (and (not (or (python-info-ppss-context 'string)
- (python-info-ppss-context 'comment)))
+ (when (and (not (python-syntax-comment-or-string-p))
(<= (point-marker) (save-excursion
(back-to-indentation)
(point-marker)))
(when (and (not arg)
(eolp)
(not (equal ?: (char-after (- (point-marker) 2))))
- (not (or (python-info-ppss-context 'string)
- (python-info-ppss-context 'comment))))
+ (not (python-syntax-comment-or-string-p)))
(let ((indentation (current-indentation))
(calculated-indentation (python-indent-calculate-indentation)))
(python-info-closing-block-message)
(goto-char (line-beginning-position))
;; If after going to the beginning of line the point
;; is still inside a paren it's ok to do the trick
- (when (python-info-ppss-context 'paren)
+ (when (python-syntax-context 'paren)
(let ((indentation (python-indent-calculate-indentation)))
(when (< (current-indentation) indentation)
(indent-line-to indentation)))))))
(end-of-line 1))
(while (and (funcall re-search-fn
python-nav-beginning-of-defun-regexp nil t)
- (python-info-ppss-context-type)))
+ (python-syntax-context-type)))
(and (python-info-looking-at-beginning-of-defun)
(or (not (= (line-number-at-pos pos)
(line-number-at-pos)))
(equal (char-after (+ (point) (current-indentation))) ?#)
(<= (current-indentation) beg-defun-indent)
(looking-at (python-rx decorator))
- (python-info-ppss-context-type))))
+ (python-syntax-context-type))))
(forward-line 1)
;; If point falls inside a paren or string context the point is
;; forwarded at the end of it (or end of buffer if its not closed)
- (let ((context-type (python-info-ppss-context-type)))
+ (let ((context-type (python-syntax-context-type)))
(when (memq context-type '(paren string))
;; Slow but safe.
(while (and (not (eobp))
- (python-info-ppss-context-type))
+ (python-syntax-context-type))
(forward-line 1)))))))
-(defun python-nav-sentence-start ()
- "Move to start of current sentence."
+(defun python-nav-beginning-of-statement ()
+ "Move to start of current statement."
(interactive "^")
- (while (and (not (back-to-indentation))
+ (while (and (or (back-to-indentation) t)
(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))
+ (python-syntax-context 'string)
+ (python-syntax-context 'paren))
(forward-line -1)))))
-(defun python-nav-sentence-end ()
- "Move to end of current sentence."
+(defun python-nav-end-of-statement ()
+ "Move to end of current statement."
(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))
+ (python-syntax-context 'string)
+ (python-syntax-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."
+(defun python-nav-backward-statement (&optional arg)
+ "Move backward to previous statement.
+With ARG, repeat. See `python-nav-forward-statement'."
(interactive "^p")
(or arg (setq arg 1))
- (python-nav-forward-sentence (- arg)))
+ (python-nav-forward-statement (- 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."
+(defun python-nav-forward-statement (&optional arg)
+ "Move forward to next statement.
+With ARG, repeat. With negative argument, move ARG times
+backward to previous statement."
(interactive "^p")
(or arg (setq arg 1))
(while (> arg 0)
+ (python-nav-end-of-statement)
(python-util-forward-comment)
- (python-nav-sentence-end)
- (forward-line 1)
+ (python-nav-beginning-of-statement)
(setq arg (1- arg)))
(while (< arg 0)
- (python-nav-sentence-end)
+ (python-nav-beginning-of-statement)
(python-util-forward-comment -1)
- (python-nav-sentence-start)
- (forward-line -1)
+ (python-nav-beginning-of-statement)
(setq arg (1+ arg))))
-(defvar python-nav-list-defun-positions-cache nil)
-(make-variable-buffer-local 'python-nav-list-defun-positions-cache)
-
-(defun python-nav-list-defun-positions (&optional include-type rescan)
- "Make an Alist of defun names and point markers for current buffer.
-When optional argument INCLUDE-TYPE is non-nil the type is
-included the defun name. With optional argument RESCAN the
-`python-nav-list-defun-positions-cache' is invalidated and the
-list of defun is regenerated again."
- (if (and python-nav-list-defun-positions-cache (not rescan))
- python-nav-list-defun-positions-cache
- (let ((defs))
- (save-restriction
- (widen)
- (save-excursion
- (goto-char (point-max))
- (while (re-search-backward python-nav-beginning-of-defun-regexp nil t)
- (when (and (not (python-info-ppss-context 'string))
- (not (python-info-ppss-context 'comment))
- (not (python-info-ppss-context 'parent)))
- (add-to-list
- 'defs (cons
- (python-info-current-defun include-type)
- (point-marker)))))
- (setq python-nav-list-defun-positions-cache defs))))))
-
-(defun python-nav-read-defun (&optional rescan)
- "Read a defun name of current buffer and return its point marker.
-A cons cell with the form (DEFUN-NAME . POINT-MARKER) is returned
-when defun is completed, else nil. With optional argument RESCAN
-forces `python-nav-list-defun-positions' to invalidate its
-cache."
- (let ((defs (python-nav-list-defun-positions nil rescan)))
- (minibuffer-with-setup-hook
- (lambda ()
- (setq minibuffer-completion-table (mapcar 'car defs)))
- (let ((stringdef
- (read-from-minibuffer
- "Jump to definition: " nil
- minibuffer-local-must-match-map)))
- (when (not (string= stringdef ""))
- (assoc-string stringdef defs))))))
-
-(defun python-nav-jump-to-defun (def)
- "Jump to the definition of DEF in current file.
-Locations are cached; use a `C-u' prefix argument to force a
-rescan."
- (interactive
- (list (python-nav-read-defun current-prefix-arg)))
- (when (not (called-interactively-p 'interactive))
- (setq def (assoc-string def (python-nav-list-defun-positions))))
- (let ((def-marker (cdr def)))
- (when (markerp def-marker)
- (goto-char (marker-position def-marker))
- (back-to-indentation))))
+(defun python-nav-beginning-of-block ()
+ "Move to start of current block."
+ (interactive "^")
+ (let ((starting-pos (point))
+ (block-regexp (python-rx
+ line-start (* whitespace) block-start)))
+ (if (progn
+ (python-nav-beginning-of-statement)
+ (looking-at (python-rx block-start)))
+ (point-marker)
+ ;; Go to first line beginning a statement
+ (while (and (not (bobp))
+ (or (and (python-nav-beginning-of-statement) nil)
+ (python-info-current-line-comment-p)
+ (python-info-current-line-empty-p)))
+ (forward-line -1))
+ (let ((block-matching-indent
+ (- (current-indentation) python-indent-offset)))
+ (while
+ (and (python-nav-backward-block)
+ (> (current-indentation) block-matching-indent)))
+ (if (and (looking-at (python-rx block-start))
+ (= (current-indentation) block-matching-indent))
+ (point-marker)
+ (and (goto-char starting-pos) nil))))))
+
+(defun python-nav-end-of-block ()
+ "Move to end of current block."
+ (interactive "^")
+ (when (python-nav-beginning-of-block)
+ (let ((block-indentation (current-indentation)))
+ (python-nav-end-of-statement)
+ (while (and (forward-line 1)
+ (not (eobp))
+ (or (and (> (current-indentation) block-indentation)
+ (or (python-nav-end-of-statement) t))
+ (python-info-current-line-comment-p)
+ (python-info-current-line-empty-p))))
+ (python-util-forward-comment -1)
+ (point-marker))))
+
+(defun python-nav-backward-block (&optional arg)
+ "Move backward to previous block of code.
+With ARG, repeat. See `python-nav-forward-block'."
+ (interactive "^p")
+ (or arg (setq arg 1))
+ (python-nav-forward-block (- arg)))
+
+(defun python-nav-forward-block (&optional arg)
+ "Move forward to next block of code.
+With ARG, repeat. With negative argument, move ARG times
+backward to previous block."
+ (interactive "^p")
+ (or arg (setq arg 1))
+ (let ((block-start-regexp
+ (python-rx line-start (* whitespace) block-start))
+ (starting-pos (point)))
+ (while (> arg 0)
+ (python-nav-end-of-statement)
+ (while (and
+ (re-search-forward block-start-regexp nil t)
+ (python-syntax-context-type)))
+ (setq arg (1- arg)))
+ (while (< arg 0)
+ (python-nav-beginning-of-statement)
+ (while (and
+ (re-search-backward block-start-regexp nil t)
+ (python-syntax-context-type)))
+ (setq arg (1+ arg)))
+ (python-nav-beginning-of-statement)
+ (if (not (looking-at (python-rx block-start)))
+ (and (goto-char starting-pos) nil)
+ (and (not (= (point) starting-pos)) (point-marker)))))
+
+(defun python-nav-lisp-forward-sexp-safe (&optional arg)
+ "Safe version of standard `forward-sexp'.
+When ARG > 0 move forward, else if ARG is < 0."
+ (or arg (setq arg 1))
+ (let ((forward-sexp-function nil)
+ (paren-regexp
+ (if (> arg 0) (python-rx close-paren) (python-rx open-paren)))
+ (search-fn
+ (if (> arg 0) #'re-search-forward #'re-search-backward)))
+ (condition-case nil
+ (forward-sexp arg)
+ (error
+ (while (and (funcall search-fn paren-regexp nil t)
+ (python-syntax-context 'paren)))))))
+
+(defun python-nav--forward-sexp ()
+ "Move to forward sexp."
+ (case (python-syntax-context-type)
+ (string
+ ;; Inside of a string, get out of it.
+ (while (and (re-search-forward "[\"']" nil t)
+ (python-syntax-context 'string))))
+ (comment
+ ;; Inside of a comment, just move forward.
+ (python-util-forward-comment))
+ (paren
+ (python-nav-lisp-forward-sexp-safe 1))
+ (t
+ (if (and (not (eobp))
+ (= (syntax-class (syntax-after (point))) 4))
+ ;; Looking an open-paren
+ (python-nav-lisp-forward-sexp-safe 1)
+ (let ((block-starting-pos
+ (save-excursion (python-nav-beginning-of-block)))
+ (block-ending-pos
+ (save-excursion (python-nav-end-of-block)))
+ (next-block-starting-pos
+ (save-excursion (python-nav-forward-block))))
+ (cond
+ ((not block-starting-pos)
+ ;; Not inside a block, move to closest one.
+ (and next-block-starting-pos
+ (goto-char next-block-starting-pos)))
+ ((= (point) block-starting-pos)
+ ;; Point is at beginning of block
+ (if (and next-block-starting-pos
+ (< next-block-starting-pos block-ending-pos))
+ ;; Beginning of next block is closer than current's
+ ;; end, move to it.
+ (goto-char next-block-starting-pos)
+ (goto-char block-ending-pos)))
+ ((= block-ending-pos (point))
+ ;; Point is at end of current block
+ (let ((parent-block-end-pos
+ (save-excursion
+ (python-util-forward-comment)
+ (python-nav-beginning-of-block)
+ (python-nav-end-of-block))))
+ (if (and parent-block-end-pos
+ (or (not next-block-starting-pos)
+ (> next-block-starting-pos parent-block-end-pos)))
+ ;; If the parent block ends before next block
+ ;; starts move to it.
+ (goto-char parent-block-end-pos)
+ (and next-block-starting-pos
+ (goto-char next-block-starting-pos)))))
+ (t (python-nav-end-of-block))))))))
+
+(defun python-nav--backward-sexp ()
+ "Move to backward sexp."
+ (case (python-syntax-context-type)
+ (string
+ ;; Inside of a string, get out of it.
+ (while (and (re-search-backward "[\"']" nil t)
+ (python-syntax-context 'string))))
+ (comment
+ ;; Inside of a comment, just move backward.
+ (python-util-forward-comment -1))
+ (paren
+ ;; Handle parens like we are lisp.
+ (python-nav-lisp-forward-sexp-safe -1))
+ (t
+ (let* ((block-starting-pos
+ (save-excursion (python-nav-beginning-of-block)))
+ (block-ending-pos
+ (save-excursion (python-nav-end-of-block)))
+ (prev-block-ending-pos
+ (save-excursion (when (python-nav-backward-block)
+ (python-nav-end-of-block))))
+ (prev-block-parent-ending-pos
+ (save-excursion
+ (when prev-block-ending-pos
+ (goto-char prev-block-ending-pos)
+ (python-util-forward-comment)
+ (python-nav-beginning-of-block)
+ (python-nav-end-of-block)))))
+ (if (and (not (bobp))
+ (= (syntax-class (syntax-after (1- (point)))) 5))
+ ;; Char before point is a paren closing char, handle it
+ ;; like we are lisp.
+ (python-nav-lisp-forward-sexp-safe -1)
+ (cond
+ ((not block-ending-pos)
+ ;; Not in and ending pos, move to end of previous block.
+ (and (python-nav-backward-block)
+ (python-nav-end-of-block)))
+ ((= (point) block-ending-pos)
+ ;; In ending pos, we need to search backwards for the
+ ;; closest point looking the list of candidates from here.
+ (let ((candidates))
+ (dolist (name
+ '(prev-block-parent-ending-pos
+ prev-block-ending-pos
+ block-ending-pos
+ block-starting-pos))
+ (when (and (symbol-value name)
+ (< (symbol-value name) (point)))
+ (add-to-list 'candidates (symbol-value name))))
+ (goto-char (apply 'max candidates))))
+ ((> (point) block-ending-pos)
+ ;; After an ending position, move to it.
+ (goto-char block-ending-pos))
+ ((= (point) block-starting-pos)
+ ;; On a block starting position.
+ (if (not (> (point) (or prev-block-ending-pos (point))))
+ ;; Point is after the end position of the block that
+ ;; wraps the current one, just move a block backward.
+ (python-nav-backward-block)
+ ;; If we got here we are facing a case like this one:
+ ;;
+ ;; try:
+ ;; return here()
+ ;; except Exception as e:
+ ;;
+ ;; Where point is on the "except" and must move to the
+ ;; end of "here()".
+ (goto-char prev-block-ending-pos)
+ (let ((parent-block-ending-pos
+ (save-excursion
+ (python-nav-forward-sexp)
+ (and (not (looking-at (python-rx block-start)))
+ (point)))))
+ (when (and parent-block-ending-pos
+ (> parent-block-ending-pos prev-block-ending-pos))
+ ;; If we got here we are facing a case like this one:
+ ;;
+ ;; except ImportError:
+ ;; if predicate():
+ ;; processing()
+ ;; here()
+ ;; except AttributeError:
+ ;;
+ ;; Where point is on the "except" and must move to
+ ;; the end of "here()". Without this extra step we'd
+ ;; just get to the end of processing().
+ (goto-char parent-block-ending-pos)))))
+ (t
+ (if (and prev-block-ending-pos (< prev-block-ending-pos (point)))
+ (goto-char prev-block-ending-pos)
+ (python-nav-beginning-of-block)))))))))
+
+(defun python-nav-forward-sexp (&optional arg)
+ "Move forward across one block of code.
+With ARG, do it that many times. Negative arg -N means
+move backward N times."
+ (interactive "^p")
+ (or arg (setq arg 1))
+ (while (> arg 0)
+ (python-nav--forward-sexp)
+ (setq arg (1- arg)))
+ (while (< arg 0)
+ (python-nav--backward-sexp)
+ (setq arg (1+ arg))))
\f
;;; Shell integration
:group 'python
:safe 'booleanp)
-(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 'integer
- :group 'python
- :safe 'integerp)
-
(defcustom python-shell-process-environment nil
"List of environment variables for Python shell.
This variable follows the same rules as `process-environment'
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 adds the buffer name for the process
-in the `same-window-buffer-names' list."
+returns the value of `python-shell-buffer-name'."
(let ((process-name
(if (and dedicated
buffer-file-name)
(format "%s[%s]" python-shell-buffer-name buffer-file-name)
(format "%s" python-shell-buffer-name))))
- (add-to-list 'same-window-buffer-names (purecopy
- (format "*%s*" process-name)))
process-name))
(defun python-shell-internal-get-process-name ()
'python-shell-completion-complete-at-point nil 'local)
(add-to-list (make-local-variable 'comint-dynamic-complete-functions)
'python-shell-completion-complete-at-point)
- (define-key inferior-python-mode-map (kbd "<tab>")
+ (define-key inferior-python-mode-map "\t"
'python-shell-completion-complete-or-indent)
+ (make-local-variable 'python-pdbtrack-buffers-to-kill)
+ (make-local-variable 'python-pdbtrack-tracked-buffer)
+ (make-local-variable 'python-shell-internal-last-output)
(when python-shell-enable-font-lock
- (set
- (make-local-variable 'font-lock-defaults)
- '(python-font-lock-keywords
- nil nil nil nil
- (font-lock-syntactic-keywords . python-font-lock-syntactic-keywords))))
+ (set (make-local-variable 'font-lock-defaults)
+ '(python-font-lock-keywords nil nil nil nil))
+ (set (make-local-variable 'syntax-propertize-function)
+ python-syntax-propertize-function))
(compilation-shell-minor-mode 1))
-(defun python-shell-make-comint (cmd proc-name &optional pop)
+(defun python-shell-make-comint (cmd proc-name &optional pop internal)
"Create a python shell comint buffer.
CMD is the python command to be executed and PROC-NAME is the
process name the comint buffer will get. After the comint buffer
-is created the `inferior-python-mode' is activated. If POP is
-non-nil the buffer is shown."
+is created the `inferior-python-mode' is activated. When
+optional argument POP is non-nil the buffer is shown. When
+optional argument INTERNAL is non-nil this process is run on a
+buffer with a name that starts with a space, following the Emacs
+convention for temporary/internal buffers, and also makes sure
+the user is not queried for confirmation when the process is
+killed."
(save-excursion
- (let* ((proc-buffer-name (format "*%s*" proc-name))
+ (let* ((proc-buffer-name
+ (format (if (not internal) "*%s*" " *%s*") proc-name))
(process-environment (python-shell-calculate-process-environment))
(exec-path (python-shell-calculate-exec-path)))
(when (not (comint-check-proc proc-buffer-name))
(let* ((cmdlist (split-string-and-unquote cmd))
- (buffer (apply 'make-comint proc-name (car cmdlist) nil
- (cdr cmdlist)))
- (current-buffer (current-buffer)))
+ (buffer (apply #'make-comint-in-buffer proc-name proc-buffer-name
+ (car cmdlist) nil (cdr cmdlist)))
+ (current-buffer (current-buffer))
+ (process (get-buffer-process buffer)))
(with-current-buffer buffer
(inferior-python-mode)
- (python-util-clone-local-variables current-buffer))))
- (when pop
- (pop-to-buffer proc-buffer-name))
+ (python-util-clone-local-variables current-buffer))
+ (accept-process-output process)
+ (and pop (pop-to-buffer buffer t))
+ (and internal (set-process-query-on-exit-flag process nil))))
proc-buffer-name)))
-(defun run-python (dedicated cmd)
+;;;###autoload
+(defun run-python (cmd &optional dedicated show)
"Run an inferior Python process.
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.)"
+
+With argument, allows you to define CMD so you can edit the
+command used to call the interpreter and define DEDICATED, so a
+dedicated process for the current buffer is open. When numeric
+prefix arg is other than 0 or 4 do not SHOW.
+
+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))
(y-or-n-p "Make dedicated process? ")
- (read-string "Run Python: " (python-shell-parse-command)))
- (list nil (python-shell-parse-command))))
- (python-shell-make-comint cmd (python-shell-get-process-name dedicated))
+ (= (prefix-numeric-value current-prefix-arg) 4))
+ (list (python-shell-parse-command) nil t)))
+ (python-shell-make-comint
+ cmd (python-shell-get-process-name dedicated) show)
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)
- (set-process-query-on-exit-flag
- (get-buffer-process
- (python-shell-make-comint
- (python-shell-parse-command)
- (python-shell-internal-get-process-name))) nil))
+`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. Note that
+`python-shell-enable-font-lock' and `inferior-python-mode-hook'
+are set to nil for these shells, so setup codes are not sent at
+startup."
+ (let ((python-shell-enable-font-lock nil)
+ (inferior-python-mode-hook nil))
+ (get-buffer-process
+ (python-shell-make-comint
+ (python-shell-parse-command)
+ (python-shell-internal-get-process-name) nil t))))
(defun python-shell-get-process ()
"Get inferior Python process for current buffer and return it."
(global-proc-buffer-name (format "*%s*" global-proc-name))
(dedicated-running (comint-check-proc dedicated-proc-buffer-name))
(global-running (comint-check-proc global-proc-buffer-name))
- (current-prefix-arg 4))
+ (current-prefix-arg 16))
(when (and (not dedicated-running) (not global-running))
(if (call-interactively 'run-python)
(setq dedicated-running t)
"Current internal shell buffer for the current buffer.
This is really not necessary at all for the code to work but it's
there for compatibility with CEDET.")
-(make-variable-buffer-local 'python-shell-internal-buffer)
+
+(defvar python-shell-internal-last-output nil
+ "Last output captured by the internal shell.
+This is really not necessary at all for the code to work but it's
+there for compatibility with CEDET.")
(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)
- (setq python-shell-internal-buffer proc-buffer-name)
+ (proc-buffer-name (format " *%s*" proc-name)))
+ (when (not (process-live-p proc-name))
+ (run-python-internal)
+ (setq python-shell-internal-buffer proc-buffer-name)
+ ;; XXX: Why is this `sit-for' needed?
+ ;; `python-shell-make-comint' calls `accept-process-output'
+ ;; already but it is not helping to get proper output on
+ ;; 'gnu/linux when the internal shell process is not running and
+ ;; a call to `python-shell-internal-send-string' is issued.
+ (sit-for 0.1 t))
(get-buffer-process proc-buffer-name)))
(define-obsolete-function-alias
- 'python-proc 'python-shell-internal-get-or-create-process "23.3")
+ 'python-proc 'python-shell-internal-get-or-create-process "24.3")
+
+(define-obsolete-variable-alias
+ 'python-buffer 'python-shell-internal-buffer "24.3")
(define-obsolete-variable-alias
- 'python-buffer 'python-shell-internal-buffer "23.3")
+ 'python-preoutput-result 'python-shell-internal-last-output "24.3")
(defun python-shell-send-string (string &optional process msg)
"Send STRING to inferior Python PROCESS.
(interactive "sPython command: ")
(let ((process (or process (python-shell-get-or-create-process)))
(lines (split-string string "\n" t)))
- (when msg
- (message (format "Sent: %s..." (nth 0 lines))))
+ (and msg (message "Sent: %s..." (nth 0 lines)))
(if (> (length lines) 1)
- (let* ((temp-file-name (make-temp-file "py"))
+ (let* ((temporary-file-directory
+ (if (file-remote-p default-directory)
+ (concat (file-remote-p default-directory) "/tmp")
+ temporary-file-directory))
+ (temp-file-name (make-temp-file "py"))
(file-name (or (buffer-file-name) temp-file-name)))
(with-temp-file temp-file-name
(insert string)
(string-match "\n[ \t].*\n?$" string))
(comint-send-string process "\n")))))
+;; Shell output catching stolen from gud-gdb
+(defvar python-shell-fetch-lines-in-progress nil)
+(defvar python-shell-fetch-lines-string nil)
+(defvar python-shell-fetched-lines nil)
+
+(defun python-shell-fetch-lines-filter (string)
+ "Filter used to read the list of lines output by a command.
+STRING is the output to filter."
+ (setq string (concat python-shell-fetch-lines-string string))
+ (while (string-match "\n" string)
+ (push (substring string 0 (match-beginning 0))
+ python-shell-fetched-lines)
+ (setq string (substring string (match-end 0))))
+ (if (equal (string-match comint-prompt-regexp string) 0)
+ (progn
+ (setq python-shell-fetch-lines-in-progress nil)
+ string)
+ (progn
+ (setq python-shell-fetch-lines-string 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."
- (let* ((output-buffer)
- (process (or process (python-shell-get-or-create-process)))
- (comint-preoutput-filter-functions
- (append comint-preoutput-filter-functions
- '(ansi-color-filter-apply
- (lambda (string)
- (setq output-buffer (concat output-buffer string))
- "")))))
- (python-shell-send-string string process msg)
- (accept-process-output process)
- (replace-regexp-in-string
- (if (> (length python-shell-prompt-output-regexp) 0)
- (format "\n*%s$\\|^%s\\|\n$"
- python-shell-prompt-regexp
- (or python-shell-prompt-output-regexp ""))
- (format "\n*$\\|^%s\\|\n$"
- python-shell-prompt-regexp))
- "" output-buffer)))
+ (let ((process (or process (python-shell-get-or-create-process)))
+ (comint-preoutput-filter-functions
+ '(python-shell-fetch-lines-filter))
+ (python-shell-fetch-lines-in-progress t)
+ (inhibit-quit t))
+ (or
+ (with-local-quit
+ (python-shell-send-string string process msg)
+ (while python-shell-fetch-lines-in-progress
+ (accept-process-output process))
+ (prog1
+ (mapconcat #'identity
+ (reverse python-shell-fetched-lines) "\n")
+ (setq python-shell-fetched-lines nil)))
+ (with-current-buffer (process-buffer process)
+ (comint-interrupt-subjob)))))
(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))
+ ;; XXX Remove `python-shell-internal-last-output' once CEDET is
+ ;; updated to support this new mode.
+ (setq python-shell-internal-last-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")
+ 'python-send-receive 'python-shell-internal-send-string "24.3")
(define-obsolete-function-alias
- 'python-send-string 'python-shell-internal-send-string "23.3")
+ 'python-send-string 'python-shell-internal-send-string "24.3")
(defun python-shell-send-region (start end)
"Send the region delimited by START and END to inferior Python process."
(defun python-shell-send-buffer (&optional arg)
"Send the entire buffer to inferior Python process.
-
-With prefix ARG include lines surrounded by \"if __name__ == '__main__':\""
+With prefix ARG allow execution of code inside blocks delimited
+by \"if __name__== '__main__':\""
(interactive "P")
(save-restriction
(widen)
- (python-shell-send-region
- (point-min)
- (or (and
- (not arg)
- (save-excursion
- (re-search-forward (python-rx if-name-main) nil t))
- (match-beginning 0))
- (point-max)))))
+ (let ((str (buffer-substring (point-min) (point-max))))
+ (and
+ (not arg)
+ (setq str (replace-regexp-in-string
+ (python-rx if-name-main)
+ "if __name__ == '__main__ ':" str)))
+ (python-shell-send-string str))))
(defun python-shell-send-defun (arg)
"Send the current defun to inferior Python process.
(interactive "fFile to send: ")
(let* ((process (or process (python-shell-get-or-create-process)))
(temp-file-name (when temp-file-name
- (expand-file-name temp-file-name)))
- (file-name (or (expand-file-name file-name) temp-file-name)))
+ (expand-file-name
+ (or (file-remote-p temp-file-name 'localname)
+ temp-file-name))))
+ (file-name (or (when file-name
+ (expand-file-name
+ (or (file-remote-p file-name 'localname)
+ file-name)))
+ temp-file-name)))
(when (not file-name)
(error "If FILE-NAME is nil then TEMP-FILE-NAME must be non-nil"))
(python-shell-send-string
"Send all setup code for shell.
This function takes the list of setup code to send from the
`python-shell-setup-codes' list."
- (let ((msg "Sent %s")
- (process (get-buffer-process (current-buffer))))
- (accept-process-output process python-shell-send-setup-max-wait)
+ (let ((process (get-buffer-process (current-buffer))))
(dolist (code python-shell-setup-codes)
(when code
- (message (format msg code))
+ (message "Sent %s" code)
(python-shell-send-string
(symbol-value code) process)))))
:type 'string
:group 'python)
-(defun python-shell-completion--get-completions (input process completion-code)
- "Retrieve available completions for INPUT using PROCESS.
-Argument COMPLETION-CODE is the python code used to get
-completions on the current context."
- (with-current-buffer (process-buffer process)
- (let ((completions (python-shell-send-string-no-output
- (format completion-code input) process)))
- (when (> (length completions) 2)
- (split-string completions "^'\\|^\"\\|;\\|'$\\|\"$" t)))))
-
-(defun python-shell-completion--do-completion-at-point (process)
- "Do completion at point for PROCESS."
- (with-syntax-table python-dotty-syntax-table
- (let* ((beg
- (save-excursion
+(defun python-shell-completion-get-completions (process line input)
+ "Do completion at point for PROCESS.
+LINE is used to detect the context on how to complete given
+INPUT."
+ (let* ((prompt
+ ;; Get the last prompt for the inferior process
+ ;; buffer. This is used for the completion code selection
+ ;; heuristic.
+ (with-current-buffer (process-buffer process)
+ (buffer-substring-no-properties
+ (overlay-start comint-last-prompt-overlay)
+ (overlay-end comint-last-prompt-overlay))))
+ (completion-context
+ ;; Check whether a prompt matches a pdb string, an import
+ ;; statement or just the standard prompt and use the
+ ;; correct python-shell-completion-*-code string
+ (cond ((and (> (length python-shell-completion-pdb-string-code) 0)
+ (string-match
+ (concat "^" python-shell-prompt-pdb-regexp) prompt))
+ 'pdb)
+ ((and (>
+ (length python-shell-completion-module-string-code) 0)
+ (string-match
+ (concat "^" python-shell-prompt-regexp) prompt)
+ (string-match "^[ \t]*\\(from\\|import\\)[ \t]" line))
+ 'import)
+ ((string-match
+ (concat "^" python-shell-prompt-regexp) prompt)
+ 'default)
+ (t nil)))
+ (completion-code
+ (case completion-context
+ (pdb python-shell-completion-pdb-string-code)
+ (import python-shell-completion-module-string-code)
+ (default python-shell-completion-string-code)
+ (t nil)))
+ (input
+ (if (eq completion-context 'import)
+ (replace-regexp-in-string "^[ \t]+" "" line)
+ input)))
+ (and completion-code
+ (> (length input) 0)
+ (with-current-buffer (process-buffer process)
+ (let ((completions (python-shell-send-string-no-output
+ (format completion-code input) process)))
+ (and (> (length completions) 2)
+ (split-string completions
+ "^'\\|^\"\\|;\\|'$\\|\"$" t)))))))
+
+(defun python-shell-completion-complete-at-point (&optional process)
+ "Perform completion at point in inferior Python.
+Optional argument PROCESS forces completions to be retrieved
+using that one instead of current buffer's process."
+ (setq process (or process (get-buffer-process (current-buffer))))
+ (let* ((start
+ (save-excursion
+ (with-syntax-table python-dotty-syntax-table
(let* ((paren-depth (car (syntax-ppss)))
(syntax-string "w_")
(syntax-list (string-to-syntax syntax-string)))
- ;; Stop scanning for the beginning of the completion subject
- ;; after the char before point matches a delimiter
- (while (member (car (syntax-after (1- (point)))) syntax-list)
+ ;; Stop scanning for the beginning of the completion
+ ;; subject after the char before point matches a
+ ;; delimiter
+ (while (member
+ (car (syntax-after (1- (point)))) syntax-list)
(skip-syntax-backward syntax-string)
(when (or (equal (char-before) ?\))
(equal (char-before) ?\"))
(while (or
;; honor initial paren depth
(> (car (syntax-ppss)) paren-depth)
- (python-info-ppss-context 'string))
- (forward-char -1))))
- (point)))
- (end (point))
- (line (buffer-substring-no-properties (point-at-bol) end))
- (input (buffer-substring-no-properties beg end))
- ;; Get the last prompt for the inferior process buffer. This is
- ;; used for the completion code selection heuristic.
- (prompt
- (with-current-buffer (process-buffer process)
- (buffer-substring-no-properties
- (overlay-start comint-last-prompt-overlay)
- (overlay-end comint-last-prompt-overlay))))
- (completion-context
- ;; Check wether a prompt matches a pdb string, an import statement
- ;; or just the standard prompt and use the correct
- ;; python-shell-completion-*-code string
- (cond ((and (> (length python-shell-completion-pdb-string-code) 0)
- (string-match
- (concat "^" python-shell-prompt-pdb-regexp) prompt))
- 'pdb)
- ((and (>
- (length python-shell-completion-module-string-code) 0)
- (string-match
- (concat "^" python-shell-prompt-regexp) prompt)
- (string-match "^[ \t]*\\(from\\|import\\)[ \t]" line))
- 'import)
- ((string-match
- (concat "^" python-shell-prompt-regexp) prompt)
- 'default)
- (t nil)))
- (completion-code
- (case completion-context
- ('pdb python-shell-completion-pdb-string-code)
- ('import python-shell-completion-module-string-code)
- ('default python-shell-completion-string-code)
- (t nil)))
- (input
- (if (eq completion-context 'import)
- (replace-regexp-in-string "^[ \t]+" "" line)
- input))
- (completions
- (and completion-code (> (length input) 0)
- (python-shell-completion--get-completions
- input process completion-code))))
- (list beg end completions))))
-
-(defun python-shell-completion-complete-at-point ()
- "Perform completion at point in inferior Python process."
- (interactive)
- (and comint-last-prompt-overlay
- (> (point-marker) (overlay-end comint-last-prompt-overlay))
- (python-shell-completion--do-completion-at-point
- (get-buffer-process (current-buffer)))))
+ (python-syntax-context 'string))
+ (forward-char -1)))
+ (point)))))
+ (end (point)))
+ (list start end
+ (completion-table-dynamic
+ (apply-partially
+ #'python-shell-completion-get-completions
+ process (buffer-substring-no-properties
+ (line-beginning-position) end))))))
(defun python-shell-completion-complete-or-indent ()
"Complete or indent depending on the context.
"Variable containing the value of the current tracked buffer.
Never set this variable directly, use
`python-pdbtrack-set-tracked-buffer' instead.")
-(make-variable-buffer-local 'python-pdbtrack-tracked-buffer)
(defvar python-pdbtrack-buffers-to-kill nil
"List of buffers to be deleted after tracking finishes.")
-(make-variable-buffer-local 'python-pdbtrack-buffers-to-kill)
(defun python-pdbtrack-set-tracked-buffer (file-name)
"Set the buffer for FILE-NAME as the tracked buffer.
For this to work the best as possible you should call
`python-shell-send-buffer' from time to time so context in
inferior python process is updated properly."
- (interactive)
(let ((process (python-shell-get-process)))
(if (not process)
(error "Completion needs an inferior Python process running")
- (python-shell-completion--do-completion-at-point process))))
+ (python-shell-completion-complete-at-point process))))
(add-to-list 'debug-ignored-errors
"^Completion needs an inferior Python process running.")
This is the function used by `python-fill-paragraph-function' to
fill comments."
:type 'symbol
- :group 'python
- :safe 'symbolp)
+ :group 'python)
(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)
+ :group 'python)
(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)
+ :group 'python)
(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)
+
+(defcustom python-fill-string-style 'pep-257
+ "Style used to fill docstrings.
+This affects `python-fill-string' behavior with regards to
+triple quotes positioning.
+
+Possible values are DJANGO, PEP-257, PEP-257-NN, SYMMETRIC and
+NIL. A value of NIL won't care about quotes position, will do
+what `fill-paragraph' does, any other value may result in one of
+the following docstring styles:
+
+DJANGO:
+
+ \"\"\"
+ Process foo, return bar.
+ \"\"\"
+
+ \"\"\"
+ Process foo, return bar.
+
+ If processing fails throw ProcessingError.
+ \"\"\"
+
+PEP-257:
+
+ \"\"\"Process foo, return bar.\"\"\"
+
+ \"\"\"Process foo, return bar.
+
+ If processing fails throw ProcessingError.
+
+ \"\"\"
+
+PEP-257-NN:
+
+ \"\"\"Process foo, return bar.\"\"\"
+
+ \"\"\"Process foo, return bar.
+
+ If processing fails throw ProcessingError.
+ \"\"\"
+
+SYMMETRIC:
+
+ \"\"\"Process foo, return bar.\"\"\"
+
+ \"\"\"
+ Process foo, return bar.
+
+ If processing fails throw ProcessingError.
+ \"\"\""
+ :type 'symbol
:group 'python
- :safe 'symbolp)
+ :safe (lambda (val) (memq val '(django pep-257 pep-257-nn symmetric nil))))
(defun python-fill-paragraph-function (&optional justify)
"`fill-paragraph-function' handling multi-line strings and possibly comments.
Optional argument JUSTIFY defines if the paragraph should be justified."
(interactive "P")
(save-excursion
- (back-to-indentation)
(cond
;; Comments
- ((funcall python-fill-comment-function justify))
+ ((python-syntax-context 'comment)
+ (funcall python-fill-comment-function justify))
;; Strings/Docstrings
- ((save-excursion (skip-chars-forward "\"'uUrR")
- (python-info-ppss-context 'string))
+ ((save-excursion (or (python-syntax-context 'string)
+ (equal (string-to-syntax "|")
+ (syntax-after (point)))))
(funcall python-fill-string-function justify))
;; Decorators
((equal (char-after (save-excursion
(back-to-indentation)
- (point-marker))) ?@)
+ (point))) ?@)
(funcall python-fill-decorator-function justify))
;; Parens
- ((or (python-info-ppss-context 'paren)
+ ((or (python-syntax-context 'paren)
(looking-at (python-rx open-paren))
(save-excursion
(skip-syntax-forward "^(" (line-end-position))
(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))
+ (let* ((marker (point-marker))
+ (str-start-pos
+ (let ((m (make-marker)))
+ (setf (marker-position m)
+ (or (python-syntax-context 'string)
+ (and (equal (string-to-syntax "|")
+ (syntax-after (point)))
+ (point)))) m))
+ (num-quotes (python-syntax-count-quotes
+ (char-after str-start-pos) str-start-pos))
+ (str-end-pos
+ (save-excursion
+ (goto-char (+ str-start-pos num-quotes))
+ (or (re-search-forward (rx (syntax string-delimiter)) nil t)
+ (goto-char (point-max)))
+ (point-marker)))
+ (multi-line-p
+ ;; Docstring styles may vary for oneliners and multi-liners.
+ (> (count-matches "\n" str-start-pos str-end-pos) 0))
+ (delimiters-style
+ (case python-fill-string-style
+ ;; delimiters-style is a cons cell with the form
+ ;; (START-NEWLINES . END-NEWLINES). When any of the sexps
+ ;; is NIL means to not add any newlines for start or end
+ ;; of docstring. See `python-fill-string-style' for a
+ ;; graphic idea of each style.
+ (pep-257 (and multi-line-p (cons nil 2)))
+ (pep-257-nn (and multi-line-p (cons nil 1)))
+ (django (cons 1 1))
+ (symmetric (and multi-line-p (cons 1 1)))))
+ (docstring-p (save-excursion
+ ;; Consider docstrings those strings which
+ ;; start on a line by themselves.
+ (goto-char str-start-pos)
+ (skip-chars-backward (rx whitespace))
+ (= (point) (line-beginning-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)
+ (narrow-to-region str-start-pos str-end-pos)
+ (fill-paragraph justify))
+ (save-excursion
+ (when (and docstring-p python-fill-string-style)
+ ;; Add the number of newlines indicated by the selected style
+ ;; at the start of the docstring.
+ (goto-char (+ str-start-pos num-quotes))
+ (delete-region (point) (progn
+ (skip-syntax-forward "> ")
+ (point)))
+ (and (car delimiters-style)
+ (or (newline (car delimiters-style)) t)
+ ;; Indent only if a newline is added.
+ (indent-according-to-mode))
+ ;; Add the number of newlines indicated by the selected style
+ ;; at the end of the docstring.
+ (goto-char (if (not (= str-end-pos (point-max)))
+ (- str-end-pos num-quotes)
+ str-end-pos))
+ (delete-region (point) (progn
+ (skip-syntax-backward "> ")
+ (point)))
+ (and (cdr delimiters-style)
+ ;; Add newlines only if string ends.
+ (not (= str-end-pos (point-max)))
+ (or (newline (cdr delimiters-style)) t)
+ ;; Again indent only if a newline is added.
+ (indent-according-to-mode))))) 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'."
(save-restriction
(narrow-to-region (progn
- (while (python-info-ppss-context 'paren)
+ (while (python-syntax-context 'paren)
(goto-char (1- (point-marker))))
(point-marker)
(line-beginning-position))
(progn
- (when (not (python-info-ppss-context 'paren))
+ (when (not (python-syntax-context 'paren))
(end-of-line)
- (when (not (python-info-ppss-context 'paren))
+ (when (not (python-syntax-context 'paren))
(skip-syntax-backward "^)")))
- (while (python-info-ppss-context 'paren)
+ (while (python-syntax-context 'paren)
(goto-char (1+ (point-marker))))
(point-marker)))
(let ((paragraph-start "\f\\|[ \t]*$")
:group 'python
:safe 'booleanp)
+(define-obsolete-variable-alias
+ 'python-use-skeletons 'python-skeleton-autoinsert "24.3")
+
(defvar python-skeleton-available '()
"Internal list of available skeletons.")
;; Only expand in code.
:enable-function (lambda ()
(and
- (not (or (python-info-ppss-context 'string)
- (python-info-ppss-context 'comment)))
+ (not (python-syntax-comment-or-string-p))
python-skeleton-autoinsert)))
(defmacro python-skeleton-define (name doc &rest skel)
"Define a `python-mode' skeleton using NAME DOC and SKEL.
The skeleton will be bound to python-skeleton-NAME and will
be added to `python-mode-abbrev-table'."
+ (declare (indent 2))
(let* ((name (symbol-name name))
(function-name (intern (concat "python-skeleton-" name))))
`(progn
- (define-abbrev python-mode-abbrev-table ,name "" ',function-name)
+ (define-abbrev python-mode-abbrev-table ,name "" ',function-name
+ :system t)
(setq python-skeleton-available
(cons ',function-name python-skeleton-available))
(define-skeleton ,function-name
,(or doc
(format "Insert %s statement." name))
,@skel))))
-(put 'python-skeleton-define 'lisp-indent-function 2)
(defmacro python-define-auxiliary-skeleton (name doc &optional &rest skel)
"Define a `python-mode' auxiliary skeleton using NAME DOC and SKEL.
The skeleton will be bound to python-skeleton-NAME."
+ (declare (indent 2))
(let* ((name (symbol-name name))
(function-name (intern (concat "python-skeleton--" name)))
(msg (format
(unless (y-or-n-p ,msg)
(signal 'quit t))
,@skel)))
-(put 'python-define-auxiliary-skeleton 'lisp-indent-function 2)
(python-define-auxiliary-skeleton else nil)
(defun python-eldoc--get-doc-at-point (&optional force-input force-process)
"Internal implementation to get documentation at point.
-If not FORCE-INPUT is passed then what `current-word' returns
-will be used. If not FORCE-PROCESS is passed what
-`python-shell-get-process' returns is used."
+If not FORCE-INPUT is passed then what
+`python-info-current-symbol' returns will be used. If not
+FORCE-PROCESS is passed what `python-shell-get-process' returns
+is used."
(let ((process (or force-process (python-shell-get-process))))
(if (not process)
- "Eldoc needs an inferior Python process running."
- (let* ((current-defun (python-info-current-defun))
- (input (or force-input
- (with-syntax-table python-dotty-syntax-table
- (if (not current-defun)
- (current-word)
- (concat current-defun "." (current-word))))))
- (ppss (syntax-ppss))
- (help (when (and
- input
- (not (string= input (concat current-defun ".")))
- (not (or (python-info-ppss-context 'string ppss)
- (python-info-ppss-context 'comment ppss))))
- (when (string-match
- (concat
- (regexp-quote (concat current-defun "."))
- "self\\.") input)
- (with-temp-buffer
- (insert input)
- (goto-char (point-min))
- (forward-word)
- (forward-char)
- (delete-region
- (point-marker) (search-forward "self."))
- (setq input (buffer-substring
- (point-min) (point-max)))))
- (python-shell-send-string-no-output
- (format python-eldoc-string-code input) process))))
- (with-current-buffer (process-buffer process)
- (when comint-last-prompt-overlay
- (delete-region comint-last-input-end
- (overlay-start comint-last-prompt-overlay))))
- (when (and help
- (not (string= help "\n")))
- help)))))
+ (error "Eldoc needs an inferior Python process running")
+ (let ((input (or force-input
+ (python-info-current-symbol t))))
+ (and input
+ (python-shell-send-string-no-output
+ (format python-eldoc-string-code input)
+ process))))))
(defun python-eldoc-function ()
"`eldoc-documentation-function' for Python.
"Get help on SYMBOL using `help'.
Interactively, prompt for symbol."
(interactive
- (let ((symbol (with-syntax-table python-dotty-syntax-table
- (current-word)))
+ (let ((symbol (python-info-current-symbol t))
(enable-recursive-minibuffers t))
(list (read-string (if symbol
(format "Describe symbol (default %s): " symbol)
"Describe symbol: ")
nil nil symbol))))
- (let ((process (python-shell-get-process)))
- (if (not process)
- (message "Eldoc needs an inferior Python process running.")
- (message (python-eldoc--get-doc-at-point symbol process)))))
-
-\f
-;;; Imenu
+ (message (python-eldoc--get-doc-at-point symbol)))
-(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
- (python-nav-list-defun-positions python-imenu-include-defun-type)))
- (if python-imenu-make-tree
- (python-imenu-make-tree index)
- index)))
+(add-to-list 'debug-ignored-errors
+ "^Eldoc needs an inferior Python process running.")
\f
;;; Misc helpers
`add-log-current-defun-function' since it returns nil if point is
not inside a defun."
(let ((names '())
- (min-indent)
+ (starting-indentation)
+ (starting-point)
(first-run t))
(save-restriction
(widen)
(save-excursion
+ (setq starting-point (point-marker))
+ (setq starting-indentation (save-excursion
+ (python-nav-beginning-of-statement)
+ (current-indentation)))
(end-of-line 1)
- (setq min-indent (current-indentation))
(while (python-beginning-of-defun-function 1)
- (when (or (< (current-indentation) min-indent)
- first-run)
+ (when (or (< (current-indentation) starting-indentation)
+ (and first-run
+ (<
+ starting-point
+ (save-excursion
+ (python-end-of-defun-function)
+ (point-marker)))))
(setq first-run nil)
- (setq min-indent (current-indentation))
+ (setq starting-indentation (current-indentation))
(looking-at python-nav-beginning-of-defun-regexp)
(setq names (cons
(if (not include-type)
(when names
(mapconcat (lambda (string) string) names "."))))
+(defun python-info-current-symbol (&optional replace-self)
+ "Return current symbol using dotty syntax.
+With optional argument REPLACE-SELF convert \"self\" to current
+parent defun name."
+ (let ((name
+ (and (not (python-syntax-comment-or-string-p))
+ (with-syntax-table python-dotty-syntax-table
+ (let ((sym (symbol-at-point)))
+ (and sym
+ (substring-no-properties (symbol-name sym))))))))
+ (when name
+ (if (not replace-self)
+ name
+ (let ((current-defun (python-info-current-defun)))
+ (if (not current-defun)
+ name
+ (replace-regexp-in-string
+ (python-rx line-start word-start "self" word-end ?.)
+ (concat
+ (mapconcat 'identity
+ (butlast (split-string current-defun "\\."))
+ ".") ".")
+ name)))))))
+
+(defsubst python-info-beginning-of-block-statement-p ()
+ "Return non-nil if current statement opens a block."
+ (save-excursion
+ (python-nav-beginning-of-statement)
+ (looking-at (python-rx block-start))))
+
(defun python-info-closing-block ()
"Return the point of the block the current line closes."
(let ((closing-word (save-excursion
(goto-char line-number))
(while (and (not (eobp))
(goto-char (line-end-position))
- (python-info-ppss-context 'paren)
+ (python-syntax-context 'paren)
(not (equal (char-before (point)) ?\\)))
(forward-line 1))
(when (equal (char-before) ?\\)
(point-marker)))))
-(defun python-info-beginning-of-backlash (&optional line-number)
- "Return the point where the backlashed line start.
+(defun python-info-beginning-of-backslash (&optional line-number)
+ "Return the point where the backslashed line start.
Optional argument LINE-NUMBER forces the line number to check against."
(save-excursion
(save-restriction
(when (python-info-line-ends-backslash-p)
(while (save-excursion
(goto-char (line-beginning-position))
- (python-info-ppss-context 'paren))
+ (python-syntax-context 'paren))
(forward-line -1))
(back-to-indentation)
(point-marker)))))
(widen)
(let* ((context-type (progn
(back-to-indentation)
- (python-info-ppss-context-type)))
+ (python-syntax-context-type)))
(line-start (line-number-at-pos))
(context-start (when context-type
- (python-info-ppss-context context-type))))
+ (python-syntax-context context-type))))
(cond ((equal context-type 'paren)
;; Lines inside a paren are always a continuation line
;; (except the first one).
- (when (equal (python-info-ppss-context-type) 'paren)
- (python-util-forward-comment -1)
- (python-util-forward-comment -1)
- (point-marker)))
- ((or (equal context-type 'comment)
- (equal context-type 'string))
+ (python-util-forward-comment -1)
+ (point-marker))
+ ((member context-type '(string comment))
;; move forward an roll again
(goto-char context-start)
(python-util-forward-comment)
(python-info-continuation-line-p))
(t
- ;; Not within a paren, string or comment, the only way we are
- ;; dealing with a continuation line is that previous line
- ;; contains a backslash, and this can only be the previous line
- ;; from current
+ ;; Not within a paren, string or comment, the only way
+ ;; we are dealing with a continuation line is that
+ ;; previous line contains a backslash, and this can
+ ;; only be the previous line from current
(back-to-indentation)
(python-util-forward-comment -1)
- (python-util-forward-comment -1)
(when (and (equal (1- line-start) (line-number-at-pos))
(python-info-line-ends-backslash-p))
(point-marker))))))))
assignment-operator
not-simple-operator)
(line-end-position) t)
- (not (or (python-info-ppss-context 'string)
- (python-info-ppss-context 'paren)
- (python-info-ppss-context 'comment)))))
+ (not (python-syntax-context-type))))
(skip-syntax-forward "\s")
(point-marker)))))
-(defun python-info-ppss-context (type &optional syntax-ppss)
- "Return non-nil if point is on TYPE using SYNTAX-PPSS.
-TYPE can be 'comment, 'string or 'paren. It returns the start
-character address of the specified TYPE."
- (let ((ppss (or syntax-ppss (syntax-ppss))))
- (case type
- ('comment
- (and (nth 4 ppss)
- (nth 8 ppss)))
- ('string
- (nth 8 ppss))
- ('paren
- (nth 1 ppss))
- (t nil))))
-
-(defun python-info-ppss-context-type (&optional syntax-ppss)
- "Return the context type using SYNTAX-PPSS.
-The type returned can be 'comment, 'string or 'paren."
- (let ((ppss (or syntax-ppss (syntax-ppss))))
- (cond
- ((and (nth 4 ppss)
- (nth 8 ppss))
- 'comment)
- ((nth 8 ppss)
- 'string)
- ((nth 1 ppss)
- 'paren)
- (t nil))))
-
(defun python-info-looking-at-beginning-of-defun (&optional syntax-ppss)
"Check if point is at `beginning-of-defun' using SYNTAX-PPSS."
- (and (not (python-info-ppss-context-type (or syntax-ppss (syntax-ppss))))
+ (and (not (python-syntax-context-type (or syntax-ppss (syntax-ppss))))
(save-excursion
(beginning-of-line 1)
(looking-at python-nav-beginning-of-defun-regexp))))
+(defun python-info-current-line-comment-p ()
+ "Check if current line is a comment line."
+ (char-equal (or (char-after (+ (point) (current-indentation))) ?_) ?#))
+
+(defun python-info-current-line-empty-p ()
+ "Check if current line is empty, ignoring whitespace."
+ (save-excursion
+ (beginning-of-line 1)
+ (looking-at
+ (python-rx line-start (* whitespace)
+ (group (* not-newline))
+ (* whitespace) line-end))
+ (string-equal "" (match-string-no-properties 1))))
+
\f
;;; Utility functions
(defun python-util-forward-comment (&optional direction)
"Python mode specific version of `forward-comment'.
Optional argument DIRECTION defines the direction to move to."
- (let ((comment-start (python-info-ppss-context 'comment))
+ (let ((comment-start (python-syntax-context 'comment))
(factor (if (< (or direction 0) 0)
-99999
99999)))
\f
;;;###autoload
-(define-derived-mode python-mode fundamental-mode "Python"
+(define-derived-mode python-mode prog-mode "Python"
"Major mode for editing Python files.
\\{python-mode-map}
(set (make-local-variable 'parse-sexp-lookup-properties) t)
(set (make-local-variable 'parse-sexp-ignore-comments) t)
+ (set (make-local-variable 'forward-sexp-function)
+ 'python-nav-forward-sexp)
+
(set (make-local-variable 'font-lock-defaults)
- '(python-font-lock-keywords
- nil nil nil nil
- (font-lock-syntactic-keywords . python-font-lock-syntactic-keywords)))
+ '(python-font-lock-keywords nil nil nil nil))
+
+ (set (make-local-variable 'syntax-propertize-function)
+ python-syntax-propertize-function)
(set (make-local-variable 'indent-line-function)
#'python-indent-line-function)
(add-hook 'post-self-insert-hook
'python-indent-post-self-insert-function nil 'local)
- (setq imenu-create-index-function #'python-imenu-create-index)
+ (set (make-local-variable 'imenu-extract-index-name-function)
+ #'python-info-current-defun)
(set (make-local-variable 'add-log-current-defun-function)
#'python-info-current-defun)
(python-skeleton-add-menu-items)
+ (make-local-variable 'python-shell-internal-buffer)
+
(when python-indent-guess-indent-offset
(python-indent-guess-indent-offset)))
(provide 'python)
+
+;; Local Variables:
+;; coding: utf-8
+;; indent-tabs-mode: nil
+;; End:
+
;;; python.el ends here