# HG changeset patch # User Augie Fackler # Date 1261025856 21600 # Node ID 014e745b2d04ccbea2b7109980b2d62a188d9240 # Parent fd92c15701ae96c6948132dfd995fcfbc62217c4 python-mode: updating to current bzr version This is bzr revision 351, which is revision-id: barry@python.org-20090320013721-awwzwv1mfl7hicdf diff --git a/.elisp/doctest-mode.el b/.elisp/doctest-mode.el --- a/.elisp/doctest-mode.el +++ b/.elisp/doctest-mode.el @@ -1,13 +1,13 @@ ;;; doctest-mode.el --- Major mode for editing Python doctest files -;; Copyright (C) 2004 Edward Loper +;; Copyright (C) 2004-2007 Edward Loper ;; Author: Edward Loper ;; Maintainer: edloper@alum.mit.edu ;; Created: Aug 2004 ;; Keywords: python doctest unittest test docstring -(defconst doctest-version "0.2" +(defconst doctest-version "0.5 alpha" "`doctest-mode' version number.") ;; This software is provided as-is, without express or implied @@ -22,17 +22,52 @@ ;; command. For more information, see the Python library reference: ;; -;; Known bugs: -;; - Some places assume prompts are 4 chars (but they can be 3 -;; if they're bare). -;; - String literals are not colored correctly. (We need to color -;; string literals on source lines, but *not* output lines or -;; text lines; this is hard to do.) -;; - Output lines starting with "..." are mistakenly interpreted -;; as (continuation) source lines. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Table of Contents +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 1. Customization: use-editable variables to customize +;; doctest-mode. +;; +;; 2. Fonts: defines new font-lock faces. +;; +;; 3. Constants: various consts (mainly regexps) used by the rest +;; of the code. +;; +;; 4. Syntax Highlighting: defines variables and functions used by +;; font-lock-mode to perform syntax highlighting. +;; +;; 5. Source code editing & indentation: commands used to +;; automatically indent, dedent, & handle prompts. +;; +;; 6. Code Execution: commands used to start doctest processes, +;; and handle their output. +;; +;; 7. Markers: functions used to insert markers at the start of +;; doctest examples. These are used to keep track of the +;; correspondence between examples in the source buffer and +;; results in the output buffer. +;; +;; 8. Navigation: commands used to navigate between failed examples. +;; +;; 9. Replace Output: command used to replace a doctest example's +;; expected output with its actual output. +;; +;; 10. Helper functions: various helper functions used by the rest +;; of the code. +;; +;; 11. Emacs compatibility functions: defines compatible versions of +;; functions that are defined for some versions of emacs but not +;; others. +;; +;; 12. Doctest Results Mode: defines doctest-results-mode, which is +;; used for the output generated by doctest. +;; +;; 13. Doctest Mode: defines doctest-mode itself. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Customizable Constants +;;; Customizable Constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defgroup doctest nil @@ -63,26 +98,82 @@ (funcall ok "/var/tmp") (funcall ok ".") (error (concat "Couldn't find a usable temp directory -- " - "set `doctest-temp-directory'")))) - - "*Directory used for temporary files created when running doctest. + "set `doctest-temp-directory'")))) + "Directory used for temporary files created when running doctest. By default, the first directory from this list that exists and that you can write into: the value (if any) of the environment variable TMPDIR, /usr/tmp, /tmp, /var/tmp, or the current directory." :type 'string :group 'doctest) -(defcustom hide-example-source t +(defcustom doctest-hide-example-source nil "If true, then don't display the example source code for each failure in the results buffer." :type 'boolean :group 'doctest) (defcustom doctest-python-command "python" - "Shell command used to start the python interpreter") + "Shell command used to start the python interpreter" + :type 'string + :group 'doctest) + +(defcustom doctest-results-buffer-name "*doctest-output (%N)*" + "The name of the buffer used to store the output of the doctest +command. This name can contain the following special sequences: + %n -- replaced by the doctest buffer's name. + %N -- replaced by the doctest buffer's name, with '.doctest' + stripped off. + %f -- replaced by the doctest buffer's filename." + :type 'string + :group 'doctest) + +(defcustom doctest-optionflags '() + "Option flags for doctest" + :group 'doctest + :type '(repeat (choice (const :tag "Select an option..." "") + (const :tag "Normalize whitespace" + "NORMALIZE_WHITESPACE") + (const :tag "Ellipsis" + "ELLIPSIS") + (const :tag "Don't accept True for 1" + "DONT_ACCEPT_TRUE_FOR_1") + (const :tag "Don't accept " + "DONT_ACCEPT_BLANKLINE") + (const :tag "Ignore Exception detail" + "IGNORE_EXCEPTION_DETAIL") + (const :tag "Report only first failure" + "REPORT_ONLY_FIRST_FAILURE") + ))) + +(defcustom doctest-async t + "If true, then doctest will be run asynchronously." + :type 'boolean + :group 'doctest) + +(defcustom doctest-trim-exceptions t + "If true, then any exceptions inserted by doctest-replace-output +will have the stack trace lines trimmed." + :type 'boolean + :group 'doctest) + +(defcustom doctest-highlight-strings t + "If true, then highlight strings. If you find that doctest-mode +is responding slowly when you type, turning this off might help." + :type 'boolean + :group 'doctest) + +(defcustom doctest-follow-output t + "If true, then when doctest is run asynchronously, the output buffer +will scroll to display its output as it is generated. If false, then +the output buffer not scroll." + :type 'boolean + :group 'doctest) + +(defvar doctest-mode-hook nil + "Hook called by `doctest-mode'.") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Fonts +;;; Fonts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defface doctest-prompt-face @@ -149,11 +240,11 @@ failure in the results buffer." :group 'doctest) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Constants +;;; Constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defconst doctest-prompt-re - "^\\([ \t]*\\)\\(>>> ?\\|[.][.][.] ?\\)\\([ \t]*\\)" + "^\\(?:\\([ \t]*\\)\\(>>> ?\\|[.][.][.] ?\\)\\([ \t]*\\)\\)" "Regular expression for doctest prompts. It defines three groups: the pre-prompt margin; the prompt; and the post-prompt indentation.") @@ -165,6 +256,62 @@ the pre-prompt margin; the prompt; and t "\\(return\\|raise\\|break\\|continue\\|pass\\)\\b" "Regular expression for a line that closes a block") +(defconst doctest-example-source-re + "^Failed example:\n\\(\n\\| [^\n]*\n\\)+" + "Regular expression for example source in doctest's output.") + +(defconst doctest-results-divider-re + "^\\([*]\\{60,\\}\\)$" + "Regular expression for example dividers in doctest's output.") + +(defconst doctest-py24-results-loc-re + "^File \"[^\"]+\", line \\([0-9]+\\), in [^\n]+" + "Regular expression for example location markers in doctest's output.") + +(defconst doctest-py21-results-loc-re + "^from line #\\([0-9]+\\) of [^\n]*" + "Regular expression for example location markers in doctest's output, +when the output was generated by an old version of doctest.") + +(defconst doctest-results-header-re + "^\\([^ \n\t].+:\\|Expected nothing\\|Got nothing\\)$" + "Regular expression for example headers in doctest's output.") + +(defconst doctest-traceback-header-re + "^[ \t]*Traceback (\\(most recent call last\\|innermost last\\)):" + "Regular expression for Python traceback headers.") + +(defconst doctest-py21-results-re + "^from line #" + "Regular expression used to test which version of doctest was used.") + +;; nb: There's a bug in Python's traceback.print_exception function +;; which causes SyntaxError exceptions to be displayed incorrectly; +;; which prevents this regexp from matching. But there shouldn't be +;; too many people testing for SyntaxErrors, so I won't worry about +;; it. +(defconst doctest-traceback-re + (let ((nonprompt + ;; This matches any non-blank line that doesn't start with + ;; a prompt (... or >>>). + (concat + "\\(?:[.][.][^.\n]\\|[>][>][^>\n]\\|" + "[.][^.\n]\\|[>][^>\n]\\|[^.>\n \t]\\)[^\n]*"))) + (concat + "^\\(\\([ \t]*\\)Traceback " + "(\\(?:most recent call last\\|innermost last\\)):\n\\)" + "\\(?:\\2[ \t]+[^ \t\n][^\n]*\n\\)*" + "\\(\\(?:\\2" nonprompt "\n\\)" + "\\(?:\\2[ \t]*" nonprompt "\n\\)*\\)")) + "Regular expression that matches a complete exception traceback. +It contains three groups: group 1 is the header line; group 2 is +the indentation; and group 3 is the exception message.") + +(defconst doctest-blankline-re + "^[ \t]*" + "Regular expression that matches blank line markers in doctest +output.") + (defconst doctest-outdent-re (concat "\\(" (mapconcat 'identity '("else:" @@ -177,6 +324,9 @@ the pre-prompt margin; the prompt; and t that matches `doctest-outdent-re', but does not follow a line matching `doctest-no-outdent-re', will be outdented.") +;; It's not clear to me *why* the behavior given by this definition of +;; doctest-no-outdent-re is desirable; but it's basically just copied +;; from python-mode. (defconst doctest-no-outdent-re (concat "\\(" @@ -195,151 +345,260 @@ that matches `doctest-outdent-re', but d that matches `doctest-outdent-re', but does not follow a line matching `doctest-no-outdent-re', will be outdented.") +(defconst doctest-script + "\ +from doctest import * +import sys +if '%m': + import imp + try: + m = imp.load_source('__imported__', '%m') + globs = m.__dict__ + except Exception, e: + print ('doctest-mode encountered an error while importing ' + 'the current buffer:\\n\\n %s' % e) + sys.exit(1) +else: + globs = {} +doc = open('%t').read() +if sys.version_info[:2] >= (2,4): + test = DocTestParser().get_doctest(doc, globs, '%n', '%f', 0) + r = DocTestRunner(optionflags=%l) + r.run(test) +else: + Tester(globs=globs).runstring(doc, '%f')" + ;; Docstring: + "Python script used to run doctest. +The following special sequences are defined: + %n -- replaced by the doctest buffer's name. + %f -- replaced by the doctest buffer's filename. + %l -- replaced by the doctest flags string. + %t -- replaced by the name of the tempfile containing the doctests." + ) + +(defconst doctest-keyword-re + (let* ((kw1 (mapconcat 'identity + '("and" "assert" "break" "class" + "continue" "def" "del" "elif" + "else" "except" "exec" "for" + "from" "global" "if" "import" + "in" "is" "lambda" "not" + "or" "pass" "print" "raise" + "return" "while" "yield" + ) + "\\|")) + (kw2 (mapconcat 'identity + '("else:" "except:" "finally:" "try:") + "\\|")) + (kw3 (mapconcat 'identity + '("ArithmeticError" "AssertionError" + "AttributeError" "DeprecationWarning" "EOFError" + "Ellipsis" "EnvironmentError" "Exception" "False" + "FloatingPointError" "FutureWarning" "IOError" + "ImportError" "IndentationError" "IndexError" + "KeyError" "KeyboardInterrupt" "LookupError" + "MemoryError" "NameError" "None" "NotImplemented" + "NotImplementedError" "OSError" "OverflowError" + "OverflowWarning" "PendingDeprecationWarning" + "ReferenceError" "RuntimeError" "RuntimeWarning" + "StandardError" "StopIteration" "SyntaxError" + "SyntaxWarning" "SystemError" "SystemExit" + "TabError" "True" "TypeError" "UnboundLocalError" + "UnicodeDecodeError" "UnicodeEncodeError" + "UnicodeError" "UnicodeTranslateError" + "UserWarning" "ValueError" "Warning" + "ZeroDivisionError" "__debug__" + "__import__" "__name__" "abs" "apply" "basestring" + "bool" "buffer" "callable" "chr" "classmethod" + "cmp" "coerce" "compile" "complex" "copyright" + "delattr" "dict" "dir" "divmod" + "enumerate" "eval" "execfile" "exit" "file" + "filter" "float" "getattr" "globals" "hasattr" + "hash" "hex" "id" "input" "int" "intern" + "isinstance" "issubclass" "iter" "len" "license" + "list" "locals" "long" "map" "max" "min" "object" + "oct" "open" "ord" "pow" "property" "range" + "raw_input" "reduce" "reload" "repr" "round" + "setattr" "slice" "staticmethod" "str" "sum" + "super" "tuple" "type" "unichr" "unicode" "vars" + "xrange" "zip") + "\\|")) + (pseudokw (mapconcat 'identity + '("self" "None" "True" "False" "Ellipsis") + "\\|")) + (string (concat "'\\(?:\\\\[^\n]\\|[^\n']*\\)'" "\\|" + "\"\\(?:\\\\[^\n]\\|[^\n\"]*\\)\"")) + (brk "\\(?:[ \t(]\\|$\\)")) + (concat + ;; Comments (group 1) + "\\(#.*\\)" + ;; Function & Class Definitions (groups 2-3) + "\\|\\b\\(class\\|def\\)" + "[ \t]+\\([a-zA-Z_][a-zA-Z0-9_]*\\)" + ;; Builtins preceeded by '.'(group 4) + "\\|[.][\t ]*\\(" kw3 "\\)" + ;; Keywords & builtins (group 5) + "\\|\\b\\(" kw1 "\\|" kw2 "\\|" + kw3 "\\|" pseudokw "\\)" brk + ;; Decorators (group 6) + "\\|\\(@[a-zA-Z_][a-zA-Z0-9_]*\\)" + )) + "A regular expression used for syntax highlighting of Python +source code.") + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Colorization support (font-lock mode) +;;; Syntax Highlighting (font-lock mode) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Define the font-lock keyword table. (defconst doctest-font-lock-keywords - (let ((prompt "^[ \t]*\\(>>>\\|\\.\\.\\.\\)") - (kw1 (mapconcat 'identity - '("and" "assert" "break" "class" - "continue" "def" "del" "elif" - "else" "except" "exec" "for" - "from" "global" "if" "import" - "in" "is" "lambda" "not" - "or" "pass" "print" "raise" - "return" "while" "yield" - ) - "\\|")) - (kw2 (mapconcat 'identity - '("else:" "except:" "finally:" "try:") - "\\|")) - (kw3 (mapconcat 'identity - '("ArithmeticError" "AssertionError" - "AttributeError" "DeprecationWarning" "EOFError" - "Ellipsis" "EnvironmentError" "Exception" "False" - "FloatingPointError" "FutureWarning" "IOError" - "ImportError" "IndentationError" "IndexError" - "KeyError" "KeyboardInterrupt" "LookupError" - "MemoryError" "NameError" "None" "NotImplemented" - "NotImplementedError" "OSError" "OverflowError" - "OverflowWarning" "PendingDeprecationWarning" - "ReferenceError" "RuntimeError" "RuntimeWarning" - "StandardError" "StopIteration" "SyntaxError" - "SyntaxWarning" "SystemError" "SystemExit" - "TabError" "True" "TypeError" "UnboundLocalError" - "UnicodeDecodeError" "UnicodeEncodeError" - "UnicodeError" "UnicodeTranslateError" - "UserWarning" "ValueError" "Warning" - "ZeroDivisionError" "__debug__" - "__import__" "__name__" "abs" "apply" "basestring" - "bool" "buffer" "callable" "chr" "classmethod" - "cmp" "coerce" "compile" "complex" "copyright" - "delattr" "dict" "dir" "divmod" - "enumerate" "eval" "execfile" "exit" "file" - "filter" "float" "getattr" "globals" "hasattr" - "hash" "hex" "id" "input" "int" "intern" - "isinstance" "issubclass" "iter" "len" "license" - "list" "locals" "long" "map" "max" "min" "object" - "oct" "open" "ord" "pow" "property" "range" - "raw_input" "reduce" "reload" "repr" "round" - "setattr" "slice" "staticmethod" "str" "sum" - "super" "tuple" "type" "unichr" "unicode" "vars" - "xrange" "zip") - "\\|")) - (pseudokw (mapconcat 'identity - '("self" "None" "True" "False" "Ellipsis") - "\\|")) - (brk "\\([ \t(]\\|$\\)") - ) - `( - ;; The following pattern colorizes source lines. In particular, - ;; it first matches prompts, and then looks for any of the - ;; following matches *on the same line* as the prompt. It uses - ;; the form: - ;; - ;; (MATCHER MATCH-HIGHLIGHT - ;; (ANCHOR-MATCHER nil nil MATCH-HIGHLIGHT) - ;; ... - ;; (ANCHOR-MATCHER nil nil MATCH-HIGHLIGHT)) - ;; - ;; See the variable documentation for font-lock-keywords for a - ;; description of what each of those means. - (,prompt (1 'doctest-prompt-face) - ;; classes - ("\\b\\(class\\)[ \t]+\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)" - nil nil (1 'font-lock-keyword-face) - (2 'font-lock-type-face)) - ;; functions - ("\\b\\(def\\)[ \t]+\\([a-zA-Z_]+[a-zA-Z0-9_]*\\)" - nil nil (1 'font-lock-keyword-face) (2 'font-lock-type-face)) - ;; keywords - (,(concat "\\b\\(" kw1 "\\)" brk) - nil nil (1 'font-lock-keyword-face)) - ;; builtins when they don't appear as object attributes - (,(concat "\\(\\b\\|[.]\\)\\(" kw3 "\\)" brk) - nil nil (2 'font-lock-keyword-face)) - ;; block introducing keywords with immediately - ;; following colons. Yes "except" is in both lists. - (,(concat "\\b\\(" kw2 "\\)" brk) - nil nil (1 'font-lock-keyword-face)) - ;; `as' but only in "import foo as bar" - ("[ \t]*\\(\\bfrom\\b.*\\)?\\bimport\\b.*\\b\\(as\\)\\b" - nil nil (2 'font-lock-keyword-face)) - ;; pseudo-keywords - (,(concat "\\b\\(" pseudokw "\\)" brk) - nil nil (1 'font-lock-keyword-face)) - ;; comments - ("\\(#.*\\)" - nil nil (1 'font-lock-comment-face))) - - ;; The following pattern colorizes output lines. In particular, - ;; it uses doctest-output-line-matcher to check if this is an - ;; output line, and if so, it colorizes it, and any special - ;; markers it contains. - (doctest-output-line-matcher - (0 'doctest-output-face t) - ("\\.\\.\\." (beginning-of-line) (end-of-line) - (0 'doctest-output-marker-face t)) - ("" (beginning-of-line) (end-of-line) - (0 'doctest-output-marker-face t)) - ("^Traceback (most recent call last):" (beginning-of-line) (end-of-line) - (0 'doctest-output-traceback-face t)) - ("^Traceback (innermost last):" (beginning-of-line) (end-of-line) - (0 'doctest-output-traceback-face t)) - ) - - ;; A PS1 prompt followed by a non-space is an error. - ("^[ \t]*\\(>>>[^ \t\n][^\n]*\\)" (1 'font-lock-warning-face t)) - - ;; Selected example (to highlight selected failure) - (doctest-selection-matcher (0 'doctest-selection-face t)) + `( + ;; The following pattern colorizes source lines. In particular, + ;; it first matches prompts, and then looks for any of the + ;; following matches *on the same line* as the prompt. It uses + ;; the form: + ;; + ;; (MATCHER MATCH-HIGHLIGHT + ;; (ANCHOR-MATCHER nil nil MATCH-HIGHLIGHT)) + ;; + ;; See the variable documentation for font-lock-keywords for a + ;; description of what each of those means. + ("^[ \t]*\\(>>>\\|\\.\\.\\.\\)" + (1 'doctest-prompt-face) + (doctest-source-matcher + nil nil + (1 'font-lock-comment-face t t) ; comments + (2 'font-lock-keyword-face t t) ; def/class + (3 'font-lock-type-face t t) ; func/class name + ;; group 4 (builtins preceeded by '.') gets no colorization. + (5 'font-lock-keyword-face t t) ; keywords & builtins + (6 'font-lock-preprocessor-face t t) ; decorators + (7 'font-lock-string-face t t) ; strings )) - "Expressions to highlight in Doctest mode.") + + ;; The following pattern colorizes output lines. In particular, + ;; it uses doctest-output-line-matcher to check if this is an + ;; output line, and if so, it colorizes it, and any special + ;; markers it contains. + (doctest-output-line-matcher + (0 'doctest-output-face t) + ("\\.\\.\\." (beginning-of-line) (end-of-line) + (0 'doctest-output-marker-face t)) + (,doctest-blankline-re (beginning-of-line) (end-of-line) + (0 'doctest-output-marker-face t)) + (doctest-traceback-line-matcher (beginning-of-line) (end-of-line) + (0 'doctest-output-traceback-face t)) + (,doctest-traceback-header-re (beginning-of-line) (end-of-line) + (0 'doctest-output-traceback-face t)) + ) + + ;; A PS1 prompt followed by a non-space is an error. + ("^[ \t]*\\(>>>[^ \t\n][^\n]*\\)" (1 'font-lock-warning-face t)) + ) + "Expressions to highlight in doctest-mode.") + +(defconst doctest-results-font-lock-keywords + `((,doctest-results-divider-re + (0 'doctest-results-divider-face)) + (,doctest-py24-results-loc-re + (0 'doctest-results-loc-face)) + (,doctest-results-header-re + (0 'doctest-results-header-face)) + (doctest-results-selection-matcher + (0 'doctest-results-selection-face t))) + "Expressions to highlight in doctest-results-mode.") (defun doctest-output-line-matcher (limit) "A `font-lock-keyword' MATCHER that returns t if the current line is the expected output for a doctest example, and if so, sets `match-data' so that group 0 spans the current line." - ;; The real work is done by find-doctest-output-line. - (when (find-doctest-output-line limit) + ;; The real work is done by doctest-find-output-line. + (when (doctest-find-output-line limit) ;; If we found one, then mark the entire line. (beginning-of-line) - (search-forward-regexp "[^\n]*" limit))) - -;; [XX] Under construction. -(defun doctest-selection-matcher (limit) - (let (found-it) - (while (and (not found-it) - (search-forward-regexp "^[ \t]*\\(>>>\\|[.][.][.]\\)" - limit t)) - (if (get-text-property (point) 'doctest-selected) - (setq found-it t))) - found-it)) + (re-search-forward "[^\n]*" limit))) + +(defun doctest-traceback-line-matcher (limit) + "A `font-lock-keyword' MATCHER that returns t if the current line is +the beginning of a traceback, and if so, sets `match-data' so that +group 0 spans the entire traceback. n.b.: limit is ignored." + (beginning-of-line) + (when (looking-at doctest-traceback-re) + (goto-char (match-end 0)) + t)) + +(defun doctest-source-matcher (limit) + "A `font-lock-keyword' MATCHER that returns t if the current line +contains a Python source expression that should be highlighted +after the point. If so, it sets `match-data' to cover the string +literal. The groups in `match-data' should be interpreted as follows: + + Group 1: comments + Group 2: def/class + Group 3: function/class name + Group 4: builtins preceeded by '.' + Group 5: keywords & builtins + Group 6: decorators + Group 7: strings +" + (let ((matchdata nil)) + ;; First, look for string literals. + (when doctest-highlight-strings + (save-excursion + (when (doctest-string-literal-matcher limit) + (setq matchdata + (list (match-beginning 0) (match-end 0) + nil nil nil nil nil nil nil nil nil nil nil nil + (match-beginning 0) (match-end 0)))))) + ;; Then, look for other keywords. If they occur before the + ;; string literal, then they take precedence. + (save-excursion + (when (and (re-search-forward doctest-keyword-re limit t) + (or (null matchdata) + (< (match-beginning 0) (car matchdata)))) + (setq matchdata (match-data)))) + (when matchdata + (set-match-data matchdata) + (goto-char (match-end 0)) + t))) + +(defun doctest-string-literal-matcher (limit &optional debug) + "A `font-lock-keyword' MATCHER that returns t if the current line +contains a string literal starting at or after the point. If so, it +expands `match-data' to cover the string literal. This matcher uses +`doctest-statement-info' to collect information about strings that +continue over multiple lines. It therefore might be a little slow for +very large statements." + (let* ((stmt-info (doctest-statement-info)) + (quotes (reverse (nth 5 stmt-info))) + (result nil)) + (if debug (doctest-debug "quotes %s" quotes)) + (while (and quotes (null result)) + (let* ((quote (pop quotes)) + (start (car quote)) + (end (min limit (or (cdr quote) limit)))) + (if debug (doctest-debug "quote %s-%s pt=%s lim=%s" + start end (point) limit)) + (when (or (and (<= (point) start) (< start limit)) + (and (< start (point)) (< (point) end))) + (setq start (max start (point))) + (set-match-data (list start end)) + (if debug (doctest-debug "marking string %s" (match-data))) + (goto-char end) + (setq result t)))) + result)) + +(defun doctest-results-selection-matcher (limit) + "Matches from `doctest-selected-failure' to the end of the +line. This is used to highlight the currently selected failure." + (when (and doctest-selected-failure + (<= (point) doctest-selected-failure) + (< doctest-selected-failure limit)) + (goto-char doctest-selected-failure) + (re-search-forward "[^\n]+" limit))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Source line indentation +;;; Source code editing & indentation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun doctest-indent-source-line (&optional dedent-only) @@ -356,8 +615,8 @@ then don't increase the indentation leve (line-had-prompt (looking-at doctest-prompt-re))) ;; Delete the old prompt (if any). (when line-had-prompt - (goto-char (match-end 1)) - (delete-char 4)) + (goto-char (match-beginning 2)) + (delete-char (- (match-end 2) (match-beginning 2)))) ;; Delete the old indentation. (delete-backward-char (skip-chars-forward " \t")) ;; If it's a continuation line, or a new PS1 prompt, @@ -379,56 +638,171 @@ then don't increase the indentation leve "Return the post-prompt indent to use for this line. This is an integer for a continuation lines, and nil for non-continuation lines." (save-excursion - (let ((prev-line-indent 0) - (curr-line-indent 0) - (prev-line-opens-block nil) - (prev-line-closes-block nil) - (curr-line-outdented nil)) + ;; Examine the previous doctest line (if present). + (let* ((prev-stmt-info (doctest-prev-statement-info)) + (prev-stmt-indent (nth 0 prev-stmt-info)) + (continuation-indent (nth 1 prev-stmt-info)) + (prev-stmt-opens-block (nth 2 prev-stmt-info)) + (prev-stmt-closes-block (nth 3 prev-stmt-info)) + (prev-stmt-blocks-outdent (nth 4 prev-stmt-info)) + ) ;; Examine this doctest line. - (beginning-of-line) - (when (looking-at doctest-prompt-re) + (let* ((curr-line-indent 0) + (curr-line-outdented nil)) + (beginning-of-line) + (when (looking-at doctest-prompt-re) (setq curr-line-indent (- (match-end 3) (match-beginning 3))) - (goto-char (match-end 3))) - (setq curr-line-outdented (looking-at doctest-outdent-re)) - ;; Examine the previous line. - (when (= (forward-line -1) 0) ; move up a line - (when (looking-at doctest-prompt-re) ; is it a source line? - (let ((indent-beg (column-at-char (match-beginning 3))) - (indent-end (column-at-char (match-end 3)))) - (setq prev-line-indent (- indent-end indent-beg)) - (goto-char (match-end 3)) - (if (looking-at doctest-open-block-re) - (setq prev-line-opens-block t)) - (if (looking-at doctest-close-block-re) - (setq prev-line-closes-block t)) - (if (looking-at doctest-no-outdent-re) - (setq curr-line-outdented nil)) - ))) - (let ((indent (+ prev-line-indent - (if curr-line-outdented -4 0) - (if prev-line-opens-block 4 0) - (if prev-line-closes-block -4 0)))) - ;; If dedent-only is true, then make sure we don't indent. - (when dedent-only - (setq indent (min indent curr-line-indent))) - ;; If indent=0 and we're not outdented, then set indent to - ;; nil (to signify the start of a new source example). - (when (and (= indent 0) (not curr-line-outdented)) - (setq indent nil)) - ;; Return the indentation. - indent)))) + (goto-char (match-end 3))) + (setq curr-line-outdented (and (looking-at doctest-outdent-re) + (not prev-stmt-blocks-outdent))) + ;; Compute the overall indent. + (let ((indent (or continuation-indent + (+ prev-stmt-indent + (if curr-line-outdented -4 0) + (if prev-stmt-opens-block 4 0) + (if prev-stmt-closes-block -4 0))))) + ;; If dedent-only is true, then make sure we don't indent. + (when dedent-only + (setq indent (min indent curr-line-indent))) + ;; If indent=0 and we're not outdented, then set indent to + ;; nil (to signify the start of a new source example). + (when (and (= indent 0) + (not (or curr-line-outdented continuation-indent))) + (setq indent nil)) + ;; Return the indentation. + indent))))) + +(defun doctest-prev-statement-info (&optional debug) + (save-excursion + (forward-line -1) + (doctest-statement-info debug))) + +(defun doctest-statement-info (&optional debug) + "Collect info about the previous statement, and return it as a list: + + (INDENT, CONTINUATION, OPENS-BLOCK, CLOSES-BLOCK, BLOCKS-OUTDENT, + QUOTES) + +INDENT -- The indentation of the previous statement (after the prompt) + +CONTINUATION -- If the previous statement is incomplete (e.g., has an +open paren or quote), then this is the appropriate indentation +level; otherwise, it's nil. + +OPENS-BLOCK -- True if the previous statement opens a Python control +block. + +CLOSES-BLOCK -- True if the previous statement closes a Python control +block. + +BLOCKS-OUTDENT -- True if the previous statement should 'block the +next statement from being considered an outdent. + +QUOTES -- A list of (START . END) pairs for all quotation strings. +" + (save-excursion + (end-of-line) + (let ((end (point))) + (while (and (doctest-on-source-line-p "...") (= (forward-line -1) 0))) + (cond + ;; If there's no previous >>> line, then give up. + ((not (doctest-on-source-line-p ">>>")) + '(0 nil nil nil nil)) + + ;; If there is a previous statement, walk through the source + ;; code, checking for operators that may be of interest. + (t + (beginning-of-line) + (let* ((quote-mark nil) (nesting 0) (indent-stack '()) + (stmt-indent 0) + (stmt-opens-block nil) + (stmt-closes-block nil) + (stmt-blocks-outdent nil) + (quotes '()) + (elt-re (concat "\\\\[^\n]\\|" + "(\\|)\\|\\[\\|\\]\\|{\\|}\\|" + "\"\"\"\\|\'\'\'\\|\"\\|\'\\|" + "#[^\n]*\\|" doctest-prompt-re))) + (while (re-search-forward elt-re end t) + (let* ((elt (match-string 0)) + (elt-first-char (substring elt 0 1))) + (if debug (doctest-debug "Debug: %s" elt)) + (cond + ;; Close quote -- set quote-mark back to nil. The + ;; second case is for cases like: ' ''' + (quote-mark + (cond + ((equal quote-mark elt) + (setq quote-mark nil) + (setcdr (car quotes) (point))) + ((equal quote-mark elt-first-char) + (setq quote-mark nil) + (setcdr (car quotes) (point)) + (backward-char 2)))) + ;; Prompt -- check if we're starting a new stmt. If so, + ;; then collect various info about it. + ((string-match doctest-prompt-re elt) + (when (and (null quote-mark) (= nesting 0)) + (let ((indent (- (match-end 3) (match-end 2)))) + (unless (looking-at "[ \t]*\n") + (setq stmt-indent indent) + (setq stmt-opens-block + (looking-at doctest-open-block-re)) + (setq stmt-closes-block + (looking-at doctest-close-block-re)) + (setq stmt-blocks-outdent + (looking-at doctest-no-outdent-re)))))) + ;; Open paren -- increment nesting, and update indent-stack. + ((string-match "(\\|\\[\\|{" elt-first-char) + (let ((elt-pos (point)) + (at-eol (looking-at "[ \t]*\n")) + (indent 0)) + (save-excursion + (re-search-backward doctest-prompt-re) + (if at-eol + (setq indent (+ 4 (- (match-end 3) (match-end 2)))) + (setq indent (- elt-pos (match-end 2)))) + (push indent indent-stack))) + (setq nesting (+ nesting 1))) + ;; Close paren -- decrement nesting, and pop indent-stack. + ((string-match ")\\|\\]\\|}" elt-first-char) + (setq indent-stack (cdr indent-stack)) + (setq nesting (max 0 (- nesting 1)))) + ;; Open quote -- set quote-mark. + ((string-match "\"\\|\'" elt-first-char) + (push (cons (- (point) (length elt)) nil) quotes) + (setq quote-mark elt))))) + + (let* ((continuation-indent + (cond + (quote-mark 0) + ((> nesting 0) (if (null indent-stack) 0 (car indent-stack))) + (t nil))) + (result + (list stmt-indent continuation-indent + stmt-opens-block stmt-closes-block + stmt-blocks-outdent quotes))) + (if debug (doctest-debug "Debug: %s" result)) + result))))))) (defun doctest-current-source-line-margin () "Return the pre-prompt margin to use for this source line. This is copied from the most recent source line, or set to `doctest-default-margin' if there are no preceeding source lines." (save-excursion - (beginning-of-line) - (if (search-backward-regexp doctest-prompt-re nil t) - (let ((margin-beg (column-at-char (match-beginning 1))) - (margin-end (column-at-char (match-end 1)))) - (- margin-end margin-beg)) - doctest-default-margin))) + (save-restriction + (when (doctest-in-mmm-docstring-overlay) + (doctest-narrow-to-mmm-overlay)) + (beginning-of-line) + (forward-line -1) + (while (and (not (doctest-on-source-line-p)) + (re-search-backward doctest-prompt-re nil t)))) + (cond ((looking-at doctest-prompt-re) + (- (match-end 1) (match-beginning 1))) + ((doctest-in-mmm-docstring-overlay) + (doctest-default-margin-in-mmm-docstring-overlay)) + (t + doctest-default-margin)))) (defun doctest-electric-backspace () "Delete the preceeding character, level of indentation, or @@ -508,15 +882,15 @@ whitespace to the left of the point befo (delete-char (- (skip-chars-backward " \t")))) (cond ;; If we're on an empty prompt, delete it. - ((on-empty-doctest-source-line) + ((doctest-on-empty-source-line-p) (delete-region (match-beginning 0) (match-end 0)) (insert-char ?\n 1)) ;; If we're on a doctest line, add a new prompt. - ((on-doctest-source-line) + ((doctest-on-source-line-p) (insert-char ?\n 1) (doctest-indent-source-line)) ;; If we're in doctest output, indent to the margin. - ((on-doctest-output-line) + ((doctest-on-output-line-p) (insert-char ?\n 1) (insert-char ?\ (doctest-current-source-line-margin))) ;; Otherwise, just add a newline. @@ -526,111 +900,346 @@ whitespace to the left of the point befo "Insert a colon, and dedent the line when appropriate." (interactive "*") (insert-char ?: 1) - (when (on-doctest-source-line) + (when (doctest-on-source-line-p) (doctest-indent-source-line t))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Code Execution +;;; Code Execution ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Add support for options (eg diff!) +(defun doctest-execute () + "Run doctest on the current buffer, or on the current docstring +if the point is inside an `mmm-mode' `doctest-docstring' region. +Display the results in the *doctest-output* buffer." + (interactive) + (doctest-execute-region (point-min) (point-max) nil t)) + +(defun doctest-execute-with-diff () + "Run doctest on the current buffer, or on the current docstring +if the point is inside an `mmm-mode' `doctest-docstring' region. +Display the results in the *doctest-output* buffer, using diff format." + (interactive) + (doctest-execute-region (point-min) (point-max) t t)) + +(defun doctest-execute-buffer-with-diff () + "Run doctest on the current buffer, and display the results in the +*doctest-output* buffer, using the diff format." + (interactive) + (doctest-execute-region (point-min) (point-max) t nil)) + (defun doctest-execute-buffer () "Run doctest on the current buffer, and display the results in the *doctest-output* buffer." - (interactive "*") - (setq doctest-results-buffer (get-buffer-create "*doctest-output*")) - (let* ((temp (concat (doctest-temp-name) ".py")) - (tempfile (expand-file-name temp doctest-temp-directory)) - (cur-buf (current-buffer)) - (in-buf (get-buffer-create "*doctest-input*")) - (beg (point-min)) (end (point-max)) - (script (concat "from doctest import *\n" - "doc = open('" tempfile "').read()\n" - "test = DocTestParser().get_doctest(" - "doc, {}, '" (buffer-name) "', '" - (buffer-file-name) "', 0)\n" - "r = DocTestRunner()\n" - "r.run(test)\n")) - (cmd (concat doctest-python-command " -c \"" script "\""))) - ;; Write buffer to a file. - (save-excursion - (set-buffer in-buf) - (insert-buffer-substring cur-buf beg end) - (write-file tempfile)) - ;; Run doctest - (shell-command cmd doctest-results-buffer) - ;; Delete the temp file - (delete-file tempfile) - ;; Set mode on output buffer. + (interactive) + (doctest-execute-region (point-min) (point-max) nil nil)) + +(defun doctest-execute-region (start end &optional diff + check-for-mmm-docstring-overlay) + "Run doctest on the current buffer, and display the results in the +*doctest-output* buffer." + (interactive "r") + ;; If it's already running, give the user a chance to restart it. + (when (doctest-process-live-p doctest-async-process) + (when (y-or-n-p "Doctest is already running. Restart it? ") + (doctest-cancel-async-process) + (message "Killing doctest..."))) + (cond + ((and doctest-async (doctest-process-live-p doctest-async-process)) + (message "Can't run two doctest processes at once!")) + (t + (let* ((results-buf-name (doctest-results-buffer-name)) + (in-docstring (and check-for-mmm-docstring-overlay + (doctest-in-mmm-docstring-overlay))) + (temp (doctest-temp-name)) (dir doctest-temp-directory) + (input-file (expand-file-name (concat temp ".py") dir)) + (globs-file (when in-docstring + (expand-file-name (concat temp "-globs.py") dir))) + (cur-buf (current-buffer)) + (in-buf (get-buffer-create "*doctest-input*")) + (script (doctest-script input-file globs-file diff))) + ;; If we're in a docstring, narrow start & end. + (when in-docstring + (let ((bounds (doctest-mmm-overlay-bounds))) + (setq start (max start (car bounds)) + end (min end (cdr bounds))))) + ;; Write the doctests to a file. + (save-excursion + (goto-char (min start end)) + (let ((lineno (doctest-line-number))) + (set-buffer in-buf) + ;; Add blank lines, to keep line numbers the same: + (dotimes (n (- lineno 1)) (insert-string "\n")) + ;; Add the selected region + (insert-buffer-substring cur-buf start end) + ;; Write it to a file + (write-file input-file))) + ;; If requested, write the buffer to a file for use as globs. + (when globs-file + (let ((cur-buf-start (point-min)) (cur-buf-end (point-max))) + (save-excursion + (set-buffer in-buf) + (delete-region (point-min) (point-max)) + (insert-buffer-substring cur-buf cur-buf-start cur-buf-end) + (write-file globs-file)))) + ;; Dispose of in-buf (we're done with it now. + (kill-buffer in-buf) + ;; Prepare the results buffer. Clear it, if it contains + ;; anything, and set its mode. + (setq doctest-results-buffer (get-buffer-create results-buf-name)) + (save-excursion + (set-buffer doctest-results-buffer) + (toggle-read-only 0) + (delete-region (point-min) (point-max)) + (doctest-results-mode) + (setq doctest-source-buffer cur-buf) + ) + ;; Add markers to examples, and record what line number each one + ;; starts at. That way, if the input buffer is edited, we can + ;; still find corresponding examples in the output. + (doctest-mark-examples) + + ;; Run doctest + (cond (doctest-async + ;; Asynchronous mode: + (let ((process (start-process "*doctest*" doctest-results-buffer + doctest-python-command + "-c" script))) + ;; Store some information about the process. + (setq doctest-async-process-buffer cur-buf) + (setq doctest-async-process process) + (push input-file doctest-async-process-tempfiles) + (when globs-file + (push globs-file doctest-async-process-tempfiles)) + ;; Set up a sentinel to respond when it's done running. + (set-process-sentinel process 'doctest-async-process-sentinel) + + ;; Show the output window. + (let ((w (display-buffer doctest-results-buffer))) + (when doctest-follow-output + ;; Insert a newline, which will move the buffer's + ;; point past the process's mark -- this causes the + ;; window to scroll as new output is generated. + (save-current-buffer + (set-buffer doctest-results-buffer) + (insert-string "\n") + (set-window-point w (point))))) + + ;; Let the user know the process is running. + (doctest-update-mode-line ":running") + (message "Running doctest..."))) + (t + ;; Synchronous mode: + (call-process doctest-python-command nil + doctest-results-buffer t "-c" script) + (doctest-handle-output) + (delete-file input-file) + (when globs-file + (delete-file globs-file)))))))) + +(defun doctest-handle-output () + "This function, which is called after the 'doctest' process spawned +by doctest-execute-buffer has finished, checks the doctest results +buffer. If that buffer is empty, it reports no errors and hides it; +if that buffer is not empty, it reports that errors occured, displays +the buffer, and runs doctest-postprocess-results." + ;; If any tests failed, display them. + (cond ((not (buffer-live-p doctest-results-buffer)) + (doctest-warn "Results buffer not found!")) + ((> (buffer-size doctest-results-buffer) 1) + (display-buffer doctest-results-buffer) + (doctest-postprocess-results) + (let ((num (length doctest-example-markers))) + (message "%d doctest example%s failed!" num + (if (= num 1) "" "s")))) + (t + (display-buffer doctest-results-buffer) + (delete-windows-on doctest-results-buffer) + (message "All doctest examples passed!")))) + +(defun doctest-async-process-sentinel (process state) + "A process sentinel, called when the asynchronous doctest process +completes, which calls doctest-handle-output." + ;; Check to make sure we got the process we're expecting. On + ;; some operating systems, this will end up getting called twice + ;; when we use doctest-cancel-async-process; this check keeps us + ;; from trying to clean up after the same process twice (since we + ;; set doctest-async-process to nil when we're done). + (when (and (equal process doctest-async-process) + (buffer-live-p doctest-async-process-buffer)) + (save-current-buffer + (set-buffer doctest-async-process-buffer) + (cond ((not (buffer-live-p doctest-results-buffer)) + (doctest-warn "Results buffer not found!")) + ((equal state "finished\n") + (doctest-handle-output) + (let ((window (get-buffer-window + doctest-async-process-buffer t))) + (when window (set-window-point window (point))))) + ((equal state "killed\n") + (message "Doctest killed.")) + (t + (message "Doctest failed -- %s" state) + (display-buffer doctest-results-buffer))) + (doctest-update-mode-line "") + (while doctest-async-process-tempfiles + (delete-file (pop doctest-async-process-tempfiles))) + (setq doctest-async-process nil)))) + +(defun doctest-cancel-async-process () + "If a doctest process is running, then kill it." + (interactive "") + (when (doctest-process-live-p doctest-async-process) + ;; Update the modeline + (doctest-update-mode-line ":killing") + ;; Kill the process. + (kill-process doctest-async-process) + ;; Run the sentinel. (Depending on what OS we're on, the sentinel + ;; may end up getting called once or twice.) + (doctest-async-process-sentinel doctest-async-process "killed\n") + )) + +(defun doctest-postprocess-results () + "Post-process the doctest results buffer: check what version of +doctest was used, and set doctest-results-py-version accordingly; +turn on read-only mode; filter the example markers; hide the example +source (if `doctest-hide-example-source' is non-nil); and select the +first failure." + (save-excursion + (set-buffer doctest-results-buffer) + ;; Check if we're using an old doctest version. + (goto-char (point-min)) + (if (re-search-forward doctest-py21-results-re nil t) + (setq doctest-results-py-version 'py21) + (setq doctest-results-py-version 'py24)) + ;; Turn on read-only mode. + (toggle-read-only t)) + + (doctest-filter-example-markers) + (if doctest-hide-example-source + (doctest-hide-example-source)) + (doctest-next-failure 1)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Markers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun doctest-mark-examples () + "Add a marker at the beginning of every (likely) example in the +input buffer; and create a list, `doctest-example-markers', +which maps from markers to the line numbers they originally occured +on. This will allow us to find the corresponding example in the +doctest output, even if the input buffer is edited." + (dolist (marker-info doctest-example-markers) + (set-marker (car marker-info) nil)) + (setq doctest-example-markers '()) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "^ *>>> " nil t) + (backward-char 4) + (push (cons (point-marker) (doctest-line-number)) + doctest-example-markers) + (forward-char 4)))) + +(defun doctest-filter-example-markers () + "Remove any entries from `doctest-example-markers' that do not +correspond to a failed example." + (let ((filtered nil) (markers doctest-example-markers)) (save-excursion (set-buffer doctest-results-buffer) - (doctest-results-mode)) - ;; If any tests failed, display them. - (cond ((> (buffer-size doctest-results-buffer) 0) - (message "Test failed!") - (display-buffer doctest-results-buffer) - (doctest-postprocess-results)) - (t - (message "Test passed!") - (if (get-buffer-window doctest-results-buffer) - (delete-window (get-buffer-window doctest-results-buffer))))))) + (goto-char (point-max)) + (while (re-search-backward (doctest-results-loc-re) nil t) + (let ((lineno (string-to-int (match-string 1)))) + (when (equal doctest-results-py-version 'py21) + (setq lineno (+ lineno 1))) + (while (and markers (< lineno (cdar markers))) + (set-marker (caar markers) nil) + (setq markers (cdr markers))) + (if (and markers (= lineno (cdar markers))) + (push (pop markers) filtered) + (doctest-warn "Example expected on line %d but not found %s" + lineno markers))))) + (dolist (marker-info markers) + (set-marker (car marker-info) nil)) + (setq doctest-example-markers filtered))) + +(defun doctest-prev-example-marker () + "Helper for doctest-replace-output: move to the preceeding example +marker, and return the corresponding 'original' lineno. If none is +found, return nil." + (let ((lineno nil) + (pos nil)) + (save-excursion + (end-of-line) + (when (re-search-backward "^\\( *\\)>>> " nil t) + (goto-char (match-end 1)) + (dolist (marker-info doctest-example-markers) + (when (= (marker-position (car marker-info)) (point)) + (setq lineno (cdr marker-info)) + (setq pos (point)))))) + (unless (null lineno) + (goto-char pos) + lineno))) -(defun doctest-postprocess-results () - (doctest-next-failure 1) - (if hide-example-source - (hide-example-source))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Navigation +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun doctest-next-failure (count) "Move to the top of the next failing example, and highlight the example's failure description in *doctest-output*." (interactive "p") - (let (lineno) - (cond - ((not (buffer-live-p doctest-results-buffer)) - (message "Run doctest first! (C-c C-c)")) - (t + (cond + ((and doctest-async (doctest-process-live-p doctest-async-process)) + (message "Wait for doctest to finish running!")) + ((not (doctest-results-buffer-valid-p)) + (message "Run doctest first! (C-c C-c)")) + ((equal count 0) + t) + (t + (let ((marker nil) (example-markers doctest-example-markers) + (results-window (display-buffer doctest-results-buffer))) (save-excursion - (let ((orig-window (selected-window)) - (results-window (display-buffer doctest-results-buffer))) - ;; Switch to the results window (so its point gets updated) - (if results-window (select-window results-window)) - ;; Pick up where we left off. - ;; (nb: doctest-selected-failure is buffer-local) - (goto-char (or doctest-selected-failure (point-min))) - ;; Skip past anything on *this* line. - (if (>= count 0) (end-of-line) (beginning-of-line)) - ;; Look for the next failure - (if (>= count 0) - (re-search-forward doctest-results-loc-re nil t count) - (re-search-backward doctest-results-loc-re nil t (- count))) - (cond - ;; We found a failure: - ((match-string 2) - (let ((old-selected-failure doctest-selected-failure)) - ;; Extract the line number for the doctest file. - (setq lineno (string-to-int (match-string 2))) - ;; Store our position for next time. - (beginning-of-line) - (setq doctest-selected-failure (point)) - ;; Update selection. - (doctest-fontify-line old-selected-failure) - (doctest-fontify-line doctest-selected-failure))) - ;; We didn't find a failure: - (t - (message "No failures found!"))) - ;; Return to the original window - (select-window orig-window))))) - - (when lineno - ;; Move point to the selected failure. - (goto-line lineno) -; ;; Highlight it. [XX] Under construction. -; (let ((beg (save-excursion (beginning-of-line) (point))) -; (end (save-excursion (end-of-line) (point)))) -; (add-text-properties (point-min) (point-max) '(doctest-selected nil)) -; (add-text-properties beg end '(doctest-selected t)) -; (doctest-fontify-line (point))) - ))) + (set-buffer doctest-results-buffer) + ;; Pick up where we left off. + ;; (nb: doctest-selected-failure is buffer-local) + (goto-char (or doctest-selected-failure (point-min))) + ;; Skip past anything on *this* line. + (if (>= count 0) (end-of-line) (beginning-of-line)) + ;; Look for the next failure + (when + (if (>= count 0) + (re-search-forward (doctest-results-loc-re) nil t count) + (re-search-backward (doctest-results-loc-re) nil t (- count))) + ;; We found a failure: + (let ((old-selected-failure doctest-selected-failure)) + (beginning-of-line) + ;; Extract the line number for the doctest file. + (let ((orig-lineno (string-to-int (match-string 1)))) + (when (equal doctest-results-py-version 'py21) + (setq orig-lineno (+ orig-lineno 1))) + (dolist (marker-info example-markers) + (when (= orig-lineno (cdr marker-info)) + (setq marker (car marker-info))))) + + ;; Update the window cursor. + (beginning-of-line) + (set-window-point results-window (point)) + ;; Store our position for next time. + (setq doctest-selected-failure (point)) + ;; Update selection. + (doctest-fontify-line old-selected-failure) + (doctest-fontify-line doctest-selected-failure)))) + + (cond + ;; We found a failure -- move point to the selected failure. + (marker + (goto-char (marker-position marker)) + (beginning-of-line)) + ;; We didn't find a failure, but there is one -- wrap. + ((> (length doctest-example-markers) 0) + (if (>= count 0) (doctest-first-failure) (doctest-last-failure))) + ;; We didn't find a failure -- alert the user. + (t (message "No failures found!"))))))) (defun doctest-prev-failure (count) "Move to the top of the previous failing example, and highlight @@ -639,7 +1248,9 @@ the example's failure description in *do (doctest-next-failure (- count))) (defun doctest-first-failure () - (interactive "") + "Move to the top of the first failing example, and highlight +the example's failure description in *doctest-output*." + (interactive) (if (buffer-live-p doctest-results-buffer) (save-excursion (set-buffer doctest-results-buffer) @@ -649,7 +1260,9 @@ the example's failure description in *do (doctest-next-failure 1)) (defun doctest-last-failure () - (interactive "") + "Move to the top of the last failing example, and highlight +the example's failure description in *doctest-output*." + (interactive) (if (buffer-live-p doctest-results-buffer) (save-excursion (set-buffer doctest-results-buffer) @@ -658,107 +1271,380 @@ the example's failure description in *do (doctest-fontify-line old-selected-failure)))) (doctest-next-failure -1)) -(defconst doctest-example-source-re - "^Failed example:\n\\(\n\\| [^\n]*\n\\)+") -(defun hide-example-source () - "Delete the source code listings from the results buffer (since it's -easy enough to see them in the original buffer)" - (save-excursion - (set-buffer doctest-results-buffer) - (toggle-read-only nil) - (beginning-of-buffer) - (while (re-search-forward doctest-example-source-re nil t) - (replace-match "" nil nil)) - (toggle-read-only t))) +(defun doctest-select-failure () + "Move to the top of the currently selected example, and select that +example in the source buffer. Intended for use in the results +buffer." + (interactive) + (re-search-backward doctest-results-divider-re) + (let ((old-selected-failure doctest-selected-failure)) + (setq doctest-selected-failure (point)) + (doctest-fontify-line doctest-selected-failure) + (doctest-fontify-line old-selected-failure)) + (pop-to-buffer doctest-source-buffer) + (doctest-next-failure 1)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Doctest Results Mode (output of doctest-execute-buffer) +;;; Replace Output ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; [XX] Todo: -;; - Make it read-only? -;; - Hitting enter goes to the corresponding error -;; - Clicking goes to corresponding error (not as useful) - - -(defconst doctest-results-divider-re - "^\\([*]\\{60,\\}\\)$") - -(defconst doctest-results-loc-re - "^File \"\\([^\"]+\\)\", line \\([0-9]+\\), in \\([^\n]+\\)") - -(defconst doctest-results-header-re - "^\\([a-zA-Z0-9 ]+:\\)$") - -(defconst doctest-results-font-lock-keywords - `((,doctest-results-divider-re - (0 'doctest-results-divider-face)) - (,doctest-results-loc-re - (0 'doctest-results-loc-face)) - (,doctest-results-header-re - (0 'doctest-results-header-face)) - (doctest-results-selection-matcher - (0 'doctest-results-selection-face t)))) - -(defun doctest-results-selection-matcher (limit) - "Matches from `doctest-selected-failure' to the end of the -line. This is used to highlight the currently selected failure." - (when (and doctest-selected-failure - (<= (point) doctest-selected-failure) - (< doctest-selected-failure limit)) - (goto-char doctest-selected-failure) - (search-forward-regexp "[^\n]+" limit))) -;; Register the font-lock keywords (xemacs) -(put 'doctest-results-mode 'font-lock-defaults - '(doctest-results-font-lock-keywords)) +(defun doctest-replace-output () + "Move to the top of the closest example, and replace its output +with the 'got' output from the *doctest-output* buffer. An error is +displayed if the chosen example is not listed in *doctest-output*, or +if the 'expected' output for the example does not exactly match the +output listed in the source buffer. The user is asked to confirm the +replacement." + (interactive) + ;; Move to the beginning of the example. + (cond + ((and doctest-async (doctest-process-live-p doctest-async-process)) + (message "Wait for doctest to finish running!")) + ((not (doctest-results-buffer-valid-p)) + (message "Run doctest first! (C-c C-c)")) + ((save-excursion (set-buffer doctest-results-buffer) + (equal doctest-results-py-version 'py21)) + (error "doctest-replace-output requires python 2.4+")) + (t + (save-excursion + (save-restriction + (when (doctest-in-mmm-docstring-overlay) + (doctest-narrow-to-mmm-overlay)) + + (let* ((orig-buffer (current-buffer)) + ;; Find an example, and look up its original lineno. + (lineno (doctest-prev-example-marker)) + ;; Find the example's indentation. + (prompt-indent (doctest-line-indentation))) + + ;; Switch to the output buffer, and look for the example. + ;; If we don't find one, complain. + (cond + ((null lineno) (message "Doctest example not found")) + (t + (set-buffer doctest-results-buffer) + (goto-char (point-min)) + (let ((output-re (format "^File .*, line %s," lineno))) + (when (not (re-search-forward output-re nil t)) + (message "This doctest example did not fail") + (setq lineno nil))))) + + ;; If we didn't find an example, give up. + (when (not (null lineno)) + ;; Get the output's 'expected' & 'got' texts. + (let ((doctest-got nil) (doctest-expected nil) (header nil)) + (while (setq header (doctest-results-next-header)) + (cond + ((equal header "Failed example:") + t) + ((equal header "Expected nothing") + (setq doctest-expected "")) + ((equal header "Expected:") + (unless (re-search-forward "^\\(\\( \\).*\n\\)*" nil t) + (error "Error parsing doctest output")) + (setq doctest-expected (doctest-replace-regexp-in-string + "^ " prompt-indent + (match-string 0)))) + ((equal header "Got nothing") + (setq doctest-got "")) + ((or (equal header "Got:") (equal header "Exception raised:")) + (unless (re-search-forward "^\\(\\( \\).*\n\\)*" nil t) + (error "Error parsing doctest output")) + (setq doctest-got (doctest-replace-regexp-in-string + "^ " prompt-indent (match-string 0)))) + ((string-match "^Differences" header) + (error (concat "doctest-replace-output can not be used " + "with diff style output"))) + (t (error "Unexpected header %s" header)))) + + ;; Go back to the source buffer. + (set-buffer orig-buffer) + + ;; Skip ahead to the output. + (beginning-of-line) + (unless (re-search-forward "^ *>>>.*") + (error "Error parsing doctest output")) + (re-search-forward "\\(\n *\\.\\.\\..*\\)*\n?") + (when (looking-at "\\'") (insert-char ?\n)) + + ;; Check that the output matches. + (let ((start (point)) end) + (cond ((re-search-forward "^ *\\(>>>.*\\|$\\)" nil t) + (setq end (match-beginning 0))) + (t + (goto-char (point-max)) + (insert-string "\n") + (setq end (point-max)))) + (when (and doctest-expected + (not (equal (buffer-substring start end) + doctest-expected))) + (warn "{%s} {%s}" (buffer-substring start end) + doctest-expected) + (error (concat "This example's output has been modified " + "since doctest was last run"))) + (setq doctest-expected (buffer-substring start end)) + (goto-char end)) + + ;; Trim exceptions + (when (and doctest-trim-exceptions + (string-match doctest-traceback-re + doctest-got)) + (let ((s1 0) (e1 (match-end 1)) + (s2 (match-beginning 2)) (e2 (match-end 2)) + (s3 (match-beginning 3)) (e3 (length doctest-got))) + (setq doctest-got + (concat (substring doctest-got s1 e1) + (substring doctest-got s2 e2) " . . .\n" + (substring doctest-got s3 e3))))) + + ;; Confirm it with the user. + (let ((confirm-buffer (get-buffer-create "*doctest-confirm*"))) + (set-buffer confirm-buffer) + ;; Erase anything left over in the buffer. + (delete-region (point-min) (point-max)) + ;; Write a confirmation message + (if (equal doctest-expected "") + (insert-string "Replace nothing\n") + (insert-string (concat "Replace:\n" doctest-expected))) + (if (equal doctest-got "") + (insert-string "With nothing\n") + (insert-string (concat "With:\n" doctest-got))) + (let ((confirm-window (display-buffer confirm-buffer))) + ;; Shrink the confirm window. + (shrink-window-if-larger-than-buffer confirm-window) + ;; Return to the original buffer. + (set-buffer orig-buffer) + ;; Match the old expected region. + (when doctest-expected + (search-backward doctest-expected)) + (when (equal doctest-expected "") (backward-char 1)) + ;; Get confirmation & do the replacement + (widen) + (cond ((y-or-n-p "Ok to replace? ") + (when (equal doctest-expected "") (forward-char 1)) + (replace-match doctest-got t t) + (message "Replaced.")) + (t + (message "Replace cancelled."))) + ;; Clean up our confirm window + (kill-buffer confirm-buffer) + (delete-window confirm-window))))))))))) + +(defun doctest-results-next-header () + "Move to the next header in the doctest results buffer, and return +the string contents of that header. If no header is found, return +nil." + (if (re-search-forward (concat doctest-results-header-re "\\|" + doctest-results-divider-re) nil t) + (let ((result (match-string 0))) + (if (string-match doctest-results-header-re result) + result + nil)) + nil)) -;; Register the font-lock keywords (gnu emacs) -(defvar font-lock-defaults-alist nil) ; in case we're in xemacs -(setq font-lock-defaults-alist - (append font-lock-defaults-alist - `((doctest-results-mode - doctest-results-font-lock-keywords - nil nil nil nil)))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; mmm-mode support +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; MMM Mode is a minor mode for Emacs which allows Multiple Major +;; Modes to coexist in a single buffer. -;; Define the mode -(define-derived-mode doctest-results-mode text-mode "Doctest Results" - "docstring" - ;; Enable font-lock mode. - (if (featurep 'font-lock) (font-lock-mode 1)) - ;; Keep track of which failure is selected - (set (make-local-variable 'doctest-selected-failure) nil) - ;; Make the buffer read-only. - (toggle-read-only t)) +;;;###autoload +(defun doctest-register-mmm-classes (&optional add-mode-ext-classes + fix-mmm-fontify-region-bug) + "Register doctest's mmm classes, allowing doctest to be used as a +submode region in other major modes, such as python-mode and rst-mode. +Two classes are registered: + +`doctest-docstring' + + Used to edit docstrings containing doctest examples in python- + mode. Docstring submode regions start and end with triple-quoted + strings (\"\"\"). In order to avoid confusing start-string + markers and end-string markers, all triple-quote strings in the + buffer are treated as submode regions (even if they're not + actually docstrings). Use (C-c % C-d) to insert a new doctest- + docstring region. When `doctest-execute' (C-c C-c) is called + inside a doctest-docstring region, it executes just the current + docstring. The globals for this execution are constructed by + importing the current buffer's contents in Python. + +`doctest-example' + + Used to edit doctest examples in text-editing modes, such as + `rst-mode' or `text-mode'. Docstring submode regions start with + optionally indented prompts (>>>) and end with blank lines. Use + (C-c % C-e) to insert a new doctest-example region. When + `doctest-execute' (C-c C-c) is called inside a doctest-example + region, it executes all examples in the buffer. + +If ADD-MODE-EXT-CLASSES is true, then register the new classes in +`mmm-mode-ext-classes-alist', which will cause them to be used by +default in the following modes: + + doctest-docstring: python-mode + doctest-example: rst-mode + +If FIX-MMM-FONTIFY-REGION-BUG is true, then register a hook that will +fix a bug in `mmm-fontify-region' that affects some (but not all) +versions of emacs. (See `doctest-fixed-mmm-fontify-region' for more +info.)" + (interactive) + (require 'mmm-auto) + (mmm-add-classes + '( + ;; === doctest-docstring === + (doctest-docstring :submode doctest-mode + + ;; The front is any triple-quote. Include it in the submode region, + ;; to prevent clashes between the two syntax tables over quotes. + :front "\\(\"\"\"\\|'''\\)" :include-front t + + ;; The back matches the front. Include just the first character + ;; of the quote. If we didn't include at least one quote, then + ;; the outer modes quote-counting would be thrown off. But if + ;; we include all three, we run into a bug in mmm-mode. See + ;; for more info about the bug. + :save-matches t :back "~1" :back-offset 1 :end-not-begin t + + ;; Define a skeleton for entering new docstrings. + :insert ((?d docstring nil @ "\"\"" @ "\"" \n + _ \n "\"" @ "\"\"" @))) + + ;; === doctest-example === + (doctest-example + :submode doctest-mode + ;; The front is an optionally indented prompt. + :front "^[ \t]*>>>" :include-front t + ;; The back is a blank line. + :back "^[ \t]*$" + ;; Define a skeleton for entering new docstrings. + :insert ((?e doctest-example nil + @ @ " >>> " _ "\n\n" @ @))))) + + ;; Register some local variables that need to be saved. + (add-to-list 'mmm-save-local-variables + '(doctest-results-buffer buffer)) + (add-to-list 'mmm-save-local-variables + '(doctest-example-markers buffer)) + + ;; Register association with modes, if requested. + (when add-mode-ext-classes + (mmm-add-mode-ext-class 'python-mode nil 'doctest-docstring) + (mmm-add-mode-ext-class 'rst-mode nil 'doctest-example)) + + ;; Fix the buggy mmm-fontify-region, if requested. + (when fix-mmm-fontify-region-bug + (add-hook 'mmm-mode-hook 'doctest-fix-mmm-fontify-region-bug))) + +(defvar doctest-old-mmm-fontify-region 'nil + "Used to hold the original definition of `mmm-fontify-region' when it +is rebound by `doctest-fix-mmm-fontify-region-bug'.") + +(defun doctest-fix-mmm-fontify-region-bug () + "A function for `mmm-mode-hook' which fixes a potential bug in +`mmm-fontify-region' by using `doctest-fixed-mmm-fontify-region' +instead. (See `doctest-fixed-mmm-fontify-region' for more info.)" + (setq font-lock-fontify-region-function + 'doctest-fixed-mmm-fontify-region)) + +(defun doctest-fixed-mmm-fontify-region (start stop &optional loudly) + "A replacement for `mmm-fontify-region', which fixes a bug caused by +versions of emacs where post-command-hooks are run *before* +fontification. `mmm-mode' assumes that its post-command-hook will be +run after fontification; and if it's not, then mmm-mode can end up +with the wrong local variables, keymap, etc. after fontification. We +fix that here by redefining `mmm-fontify-region' to remember what +submode overlay it started in; and to return to that overlay after +fontification is complete. The original definition of +`mmm-fontify-region' is stored in `doctest-old-mmm-fontify-region'." + (let ((overlay mmm-current-overlay)) + (mmm-fontify-region start stop loudly) + (if (and overlay (or (< (point) (overlay-start overlay)) + (> (point) (overlay-end overlay)))) + (goto-char (overlay-start overlay))) + (mmm-update-submode-region))) + +(defun doctest-in-mmm-docstring-overlay () + (and (featurep 'mmm-auto) + (mmm-overlay-at (point)) + (save-excursion + (goto-char (overlay-start (mmm-overlay-at (point)))) + (looking-at "\"\"\"\\|\'\'\'")))) + +(defun doctest-narrow-to-mmm-overlay () + "If we're in an mmm-mode overlay, then narrow to that overlay. +This is useful, e.g., to keep from interpreting the close-quote of a +docstring as part of the example's output." + (let ((bounds (doctest-mmm-overlay-bounds))) + (when bounds (narrow-to-region (car bounds) (cdr bounds))))) + +(defun doctest-default-margin-in-mmm-docstring-overlay () + (save-excursion + (let ((pos (car (doctest-mmm-overlay-bounds)))) + (goto-char pos) + (when (doctest-looking-back "\"\"\"\\|\'\'\'") + (setq pos (- pos 3))) + (beginning-of-line) + (- pos (point))))) + +(defun doctest-mmm-overlay-bounds () + (when (featurep 'mmm-auto) + (let ((overlay (mmm-overlay-at (point)))) + (when overlay + (let ((start (overlay-start overlay)) + (end (overlay-end overlay))) + (when (doctest-in-mmm-docstring-overlay) + (save-excursion + (goto-char start) + (re-search-forward "[\"\']*") + (setq start (point)) + (goto-char end) + (while (doctest-looking-back "[\"\']") + (backward-char 1)) + (setq end (point)))) + (cons start end)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Helper functions +;;; Helper functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defun on-doctest-source-line () - "Return true if the current line is a source line." +(defun doctest-on-source-line-p (&optional prompt) + "Return true if the current line is a source line. The optional +argument prompt can be used to specify which type of source +line (... or >>>)." (save-excursion (beginning-of-line) - (looking-at doctest-prompt-re))) - -(defun on-empty-doctest-source-line () + ;; Check if we're looking at a prompt (of the right type). + (when (and (looking-at doctest-prompt-re) + (or (null prompt) + (equal prompt (substring (match-string 2) 0 3)))) + ;; Scan backwards to make sure there's a >>> somewhere. Otherwise, + ;; this might be a '...' in the text or in an example's output. + (while (looking-at "^[ \t]*[.][.][.]") + (forward-line -1)) + (looking-at "^[ \t]*>>>")))) + +(defun doctest-on-empty-source-line-p () "Return true if the current line contains a bare prompt." (save-excursion (beginning-of-line) - (looking-at (concat doctest-prompt-re "$")))) + (and (doctest-on-source-line-p) + (looking-at (concat doctest-prompt-re "$"))))) -(defun on-doctest-output-line () +(defun doctest-on-output-line-p () "Return true if the current line is an output line." (save-excursion (beginning-of-line) - (let ((prompt-or-blankline (concat doctest-prompt-re "\\|" "^[ \t]*\n"))) - ;; The line must not be blank or start with a prompt. - (when (not (looking-at prompt-or-blankline)) - ;; The line must follow a line starting with a prompt, with - ;; no intervening blank lines. - (search-backward-regexp prompt-or-blankline nil t) - (looking-at doctest-prompt-re))))) - -(defun find-doctest-output-line (&optional limit) + ;; The line must not be blank or a source line. + (when (not (or (doctest-on-source-line-p) (looking-at "[ \t]*$"))) + ;; The line must follow a source line, with no intervening blank + ;; lines. + (while (not (or (doctest-on-source-line-p) (looking-at "[ \t]*$") + (= (point) (point-min)))) + (forward-line -1)) + (doctest-on-source-line-p)))) + +(defun doctest-find-output-line (&optional limit) "Move forward to the next doctest output line (staying within the given bounds). Return the character position of the doctest output line if one was found, and false otherwise." @@ -768,23 +1654,115 @@ output line if one was found, and false ;; Keep moving forward, one line at a time, until we find a ;; doctest output line. (while (and (not found-it) (< (point) limit) (not (eobp))) - (if (and (not (eolp)) (on-doctest-output-line)) + (if (and (not (eolp)) (doctest-on-output-line-p)) (setq found-it (point)) (forward-line)))) ;; If we found a doctest output line, then go to it. (if found-it (goto-char found-it)))) +(defun doctest-line-indentation () + "Helper for doctest-replace-output: return the whitespace indentation +at the beginning of this line." + (save-excursion + (end-of-line) + (re-search-backward "^\\( *\\)" nil t) + (match-string 1))) + +(defun doctest-optionflags (&optional diff) + "Return a string describing the optionflags that should be used +by doctest. If DIFF is non-nil, then add the REPORT_UDIFF flag." + (let ((flags "0")) + (dolist (flag doctest-optionflags) + (setq flags (concat flags "|" flag))) + (if diff (concat flags "|" "REPORT_UDIFF") flags))) + +(defun doctest-results-loc-re () + "Return the regexp that should be used to look for doctest example +location markers in doctest's output (based on which version of +doctest was used" + (cond + ((equal doctest-results-py-version 'py21) + doctest-py21-results-loc-re) + ((equal doctest-results-py-version 'py24) + doctest-py24-results-loc-re) + (t (error "bad value for doctest-results-py-version")))) + +(defun doctest-results-buffer-name () + "Return the buffer name that should be used for the doctest results +buffer. This is computed from the variable +`doctest-results-buffer-name'." + (doctest-replace-regexp-in-string + "%[nfN]" + (lambda (sym) + (cond ((equal sym "%n") (buffer-name)) + ((equal sym "%N") (doctest-replace-regexp-in-string + "[.]doctest$" "" (buffer-name) t)) + ((equal sym "%f") (buffer-file-name)))) + doctest-results-buffer-name t)) + +(defun doctest-script (input-file globs-file diff) + "..." + (doctest-replace-regexp-in-string + "%[tnflm]" + (lambda (sym) + (cond ((equal sym "%n") (buffer-name)) + ((equal sym "%f") (buffer-file-name)) + ((equal sym "%l") (doctest-optionflags diff)) + ((equal sym "%t") input-file) + ((equal sym "%m") (or globs-file "")))) + doctest-script t)) + +(defun doctest-hide-example-source () + "Delete the source code listings from the results buffer (since it's +easy enough to see them in the original buffer)" + (save-excursion + (set-buffer doctest-results-buffer) + (toggle-read-only 0) + (goto-char (point-min)) + (while (re-search-forward doctest-example-source-re nil t) + (replace-match "" nil nil)) + (toggle-read-only t))) + +(defun doctest-results-buffer-valid-p () + "Return true if this buffer has a live results buffer; and that +results buffer reports this buffer as its source buffer. (Two +buffers in doctest-mode might point to the same results buffer; +but only one of them will be equal to that results buffer's +source buffer." + (let ((cur-buf (current-buffer))) + (and (buffer-live-p doctest-results-buffer) + (save-excursion + (set-buffer doctest-results-buffer) + (equal cur-buf doctest-source-buffer))))) + +(defun doctest-update-mode-line (value) + "Update the doctest mode line with the given string value. This +is used to display information about asynchronous processes that +are run by doctest-mode." + (setq doctest-mode-line-process + value) + (force-mode-line-update t)) + (defun doctest-version () "Echo the current version of `doctest-mode' in the minibuffer." (interactive) (message "Using `doctest-mode' version %s" doctest-version)) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Utility functions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun doctest-warn (msg &rest args) + "Display a doctest warning message." + (if (fboundp 'display-warning) + (display-warning 'doctest (apply 'format msg args)) + (apply 'message msg args))) + +(defun doctest-debug (msg &rest args) + "Display a doctest debug message." + (if (fboundp 'display-warning) + (display-warning 'doctest (apply 'format msg args) 'debug) + (apply 'message msg args))) (defvar doctest-serial-number 0) ;used if broken-temp-names. (defun doctest-temp-name () + "Return a new temporary filename, for use in calling doctest." (if (memq 'broken-temp-names features) (let ((sn doctest-serial-number) @@ -795,56 +1773,202 @@ output line if one was found, and false (format "doctest-%d" sn))) (make-temp-name "doctest-"))) -(defun column-at-char (pos) - "Return the column of the given character position" - (save-excursion (goto-char pos) (current-column))) - -(defun doctest-looking-back (regexp) - "Return True if the text before point matches the given regular -expression. Like looking-at except backwards and slower. (This -is available as `looking-back' in GNU emacs and -`looking-at-backwards' in XEmacs, but it's easy enough to define -from scratch such that it works under both.)" - (save-excursion - (let ((orig-pos (point))) - ;; Search backwards for the regexp. - (if (re-search-backward regexp nil t) - ;; Check if it ends at the original point. - (= orig-pos (match-end 0)))))) - (defun doctest-fontify-line (charpos) "Run font-lock-fontify-region on the line containing the given position." - (if charpos + (if (and charpos (functionp 'font-lock-fontify-region)) (save-excursion (goto-char charpos) (let ((beg (progn (beginning-of-line) (point))) (end (progn (end-of-line) (point)))) (font-lock-fontify-region beg end))))) - + +(defun doctest-do-auto-fill () + "If the current line is a soucre line or an output line, do nothing. +Otherwise, call (do-auto-fill)." + (cond + ;; Don't wrap source lines. + ((doctest-on-source-line-p) nil) + ;; Don't wrap output lines + ((doctest-on-output-line-p) nil) + ;; Wrap all other lines + (t (do-auto-fill)))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Syntax Table +;;; Emacs Compatibility Functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Define compatible versions of functions that are defined +;; for some versions of emacs but not others. + +;; Backwards compatibility: looking-back +(cond ((fboundp 'looking-back) ;; Emacs 22.x + (defalias 'doctest-looking-back 'looking-back)) + (t + (defun doctest-looking-back (regexp) + "Return true if text before point matches REGEXP." + (save-excursion + (let ((orig-pos (point))) + ;; Search backwards for the regexp. + (if (re-search-backward regexp nil t) + ;; Check if it ends at the original point. + (= orig-pos (match-end 0)))))))) + +;; Backwards compatibility: replace-regexp-in-string +(cond ((fboundp 'replace-regexp-in-string) + (defalias 'doctest-replace-regexp-in-string 'replace-regexp-in-string)) + (t ;; XEmacs 21.x or Emacs 20.x + (defun doctest-replace-regexp-in-string + (regexp rep string &optional fixedcase literal) + "Replace all matches for REGEXP with REP in STRING." + (let ((start 0)) + (while (and (< start (length string)) + (string-match regexp string start)) + (setq start (+ (match-end 0) 1)) + (let ((newtext (if (functionp rep) + (save-match-data + (funcall rep (match-string 0 string))) + rep))) + (setq string (replace-match newtext fixedcase + literal string))))) + string))) + +;; Backwards compatibility: line-number +(cond ((fboundp 'line-number) ;; XEmacs + (defalias 'doctest-line-number 'line-number)) + ((fboundp 'line-number-at-pos) ;; Emacs 22.x + (defalias 'doctest-line-number 'line-number-at-pos)) + (t ;; Emacs 21.x + (defun doctest-line-number (&optional pos) + "Return the line number of POS (default=point)." + (1+ (count-lines 1 + (save-excursion (progn (beginning-of-line) + (or pos (point))))))))) + +;; Backwards compatibility: process-live-p +(cond ((fboundp 'process-live-p) ;; XEmacs + (defalias 'doctest-process-live-p 'process-live-p)) + (t ;; Emacs + (defun doctest-process-live-p (process) + (and (processp process) + (equal (process-status process) 'run))))) -;; We do *NOT* currently use this, because it applies too -;; indiscrimanantly. In particular, we don't want "'" and '"' treated -;; as quote marks on text lines. But there's no good way to prevent -;; it. -(defvar doctest-syntax-alist nil - "Syntax alist used in `doctest-mode' buffers.") -(setq doctest-syntax-alist '((?\( . "()") (?\[ . "(]") (?\{ . "(}") - (?\) . ")(") (?\] . ")[") (?\} . "){") - (?\$ . "." ) (?\% . "." ) (?\& . "." ) - (?\* . "." ) (?\+ . "." ) (?\- . "." ) - (?\/ . "." ) (?\< . "." ) (?\= . "." ) - (?\> . "." ) (?\| . "." ) (?\_ . "w" ) - (?\' . "\"") (?\" . "\"") (?\` . "$" ) - (?\# . "<" ) (?\n . ">" ))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Doctest Results Mode (output of doctest-execute-buffer) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Register the font-lock keywords (xemacs) +(put 'doctest-results-mode 'font-lock-defaults + '(doctest-results-font-lock-keywords)) + +;; Register the font-lock keywords (older versions of gnu emacs) +(when (boundp 'font-lock-defaults-alist) + (add-to-list 'font-lock-defaults-alist + '(doctest-results-mode doctest-results-font-lock-keywords + nil nil nil nil))) + +(defvar doctest-selected-failure nil + "The location of the currently selected failure. +This variable is uffer-local to doctest-results-mode buffers.") + +(defvar doctest-source-buffer nil + "The buffer that spawned this one. +This variable is uffer-local to doctest-results-mode buffers.") + +(defvar doctest-results-py-version nil + "A symbol indicating which version of Python was used to generate +the results in a doctest-results-mode buffer. Can be either the +symbol `py21' or the symbol `py24'. +This variable is uffer-local to doctest-results-mode buffers.") + +;; Keymap for doctest-results-mode. +(defconst doctest-results-mode-map + (let ((map (make-keymap))) + (define-key map [return] 'doctest-select-failure) + map) + "Keymap for doctest-results-mode.") + +;; Syntax table for doctest-results-mode. +(defvar doctest-results-mode-syntax-table nil + "Syntax table used in `doctest-results-mode' buffers.") +(when (not doctest-results-mode-syntax-table) + (setq doctest-results-mode-syntax-table (make-syntax-table)) + (dolist (entry '(("(" . "()") ("[" . "(]") ("{" . "(}") + (")" . ")(") ("]" . ")[") ("}" . "){") + ("$%&*+-/<=>|'\"`" . ".") ("_" . "w"))) + (dolist (char (string-to-list (car entry))) + (modify-syntax-entry char (cdr entry) + doctest-results-mode-syntax-table)))) + +;; Define the mode +(defun doctest-results-mode () + "A major mode used to display the results of running doctest. +See `doctest-mode'. + +\\{doctest-results-mode-map}" + (interactive) + + ;; Declare local variables. + (kill-all-local-variables) + (make-local-variable 'font-lock-defaults) + (make-local-variable 'doctest-selected-failure) + (make-local-variable 'doctest-source-buffer) + (make-local-variable 'doctest-results-py-version) + + ;; Define local variables. + (setq major-mode 'doctest-results-mode + mode-name "Doctest-Results" + mode-line-process 'doctest-mode-line-process + font-lock-defaults '(doctest-results-font-lock-keywords + nil nil nil nil)) + ;; Define keymap. + (use-local-map doctest-results-mode-map) + + ;; Define the syntax table. + (set-syntax-table doctest-results-mode-syntax-table) + + ;; Enable font-lock mode. + (if (featurep 'font-lock) (font-lock-mode 1))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Key Bindings +;;; Doctest Mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Register the font-lock keywords (xemacs) +(put 'doctest-mode 'font-lock-defaults '(doctest-font-lock-keywords + nil nil nil nil)) + +;; Register the font-lock keywords (older versions of gnu emacs) +(when (boundp 'font-lock-defaults-alist) + (add-to-list 'font-lock-defaults-alist + '(doctest-mode doctest-font-lock-keywords + nil nil nil nil))) + +(defvar doctest-results-buffer nil + "The output buffer for doctest-mode. +This variable is buffer-local to doctest-mode buffers.") + +(defvar doctest-example-markers nil + "A list mapping markers to the line numbers at which they appeared +in the buffer at the time doctest was last run. This is used to find +'original' line numbers, which can be used to search the doctest +output buffer. It's encoded as a list of (MARKER . POS) tuples, in +reverse POS order. +This variable is buffer-local to doctest-mode buffers.") + +;; These are global, since we only one run process at a time: +(defvar doctest-async-process nil + "The process object created by the asynchronous doctest process") +(defvar doctest-async-process-tempfiles nil + "A list of tempfile names created by the asynchronous doctest process") +(defvar doctest-async-process-buffer nil + "The source buffer for the asynchronous doctest process") +(defvar doctest-mode-line-process "" + "A string displayed on the modeline, to indicate when doctest is +running asynchronously.") + +;; Keymap for doctest-mode. n.b.: we intentionally define [tab] +;; rather than overriding indent-line-function, since we don't want +;; doctest-indent-source-line to be called by do-auto-fill. (defconst doctest-mode-map (let ((map (make-keymap))) (define-key map [backspace] 'doctest-electric-backspace) @@ -852,33 +1976,35 @@ position." (define-key map [tab] 'doctest-indent-source-line) (define-key map ":" 'doctest-electric-colon) (define-key map "\C-c\C-v" 'doctest-version) - (define-key map "\C-c\C-c" 'doctest-execute-buffer) + (define-key map "\C-c\C-c" 'doctest-execute) + (define-key map "\C-c\C-d" 'doctest-execute-with-diff) (define-key map "\C-c\C-n" 'doctest-next-failure) (define-key map "\C-c\C-p" 'doctest-prev-failure) (define-key map "\C-c\C-a" 'doctest-first-failure) + (define-key map "\C-c\C-e" 'doctest-last-failure) (define-key map "\C-c\C-z" 'doctest-last-failure) + (define-key map "\C-c\C-r" 'doctest-replace-output) + (define-key map "\C-c|" 'doctest-execute-region) map) "Keymap for doctest-mode.") -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Define the mode -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; Register the font-lock keywords (xemacs) -(put 'doctest-mode 'font-lock-defaults '(doctest-font-lock-keywords)) - -;; Register the font-lock keywords (gnu emacs) -(defvar font-lock-defaults-alist nil) ; in case we're in xemacs -(setq font-lock-defaults-alist - (append font-lock-defaults-alist - `((doctest-mode doctest-font-lock-keywords nil nil nil nil)))) +;; Syntax table for doctest-mode. +(defvar doctest-mode-syntax-table nil + "Syntax table used in `doctest-mode' buffers.") +(when (not doctest-mode-syntax-table) + (setq doctest-mode-syntax-table (make-syntax-table)) + (dolist (entry '(("(" . "()") ("[" . "(]") ("{" . "(}") + (")" . ")(") ("]" . ")[") ("}" . "){") + ("$%&*+-/<=>|'\"`" . ".") ("_" . "w"))) + (dolist (char (string-to-list (car entry))) + (modify-syntax-entry char (cdr entry) doctest-mode-syntax-table)))) ;; Use doctest mode for files ending in .doctest ;;;###autoload (add-to-list 'auto-mode-alist '("\\.doctest$" . doctest-mode)) ;;;###autoload -(define-derived-mode doctest-mode text-mode "Doctest" +(defun doctest-mode () "A major mode for editing text files that contain Python doctest examples. Doctest is a testing framework for Python that emulates an interactive session, and checks the result of each @@ -899,21 +2025,37 @@ treated differently: - 'Text lines' are any other lines. They are not processed in any special way. -\\{doctest-mode-map} -" +\\{doctest-mode-map}" + (interactive) + + ;; Declare local variables. + (kill-all-local-variables) + (make-local-variable 'font-lock-defaults) + (make-local-variable 'doctest-results-buffer) + (make-local-variable 'doctest-example-markers) + + ;; Define local variables. + (setq major-mode 'doctest-mode + mode-name "Doctest" + mode-line-process 'doctest-mode-line-process + font-lock-defaults '(doctest-font-lock-keywords + nil nil nil nil)) + + ;; Define keymap. + (use-local-map doctest-mode-map) + + ;; Define the syntax table. + (set-syntax-table doctest-mode-syntax-table) + ;; Enable auto-fill mode. (auto-fill-mode 1) + (setq auto-fill-function 'doctest-do-auto-fill) ;; Enable font-lock mode. (if (featurep 'font-lock) (font-lock-mode 1)) - - ;; Register our indentation function. - (set (make-local-variable 'indent-line-function) - 'doctest-indent-source-line) - ;; Keep track of our results buffer. - (set (make-local-variable 'doctest-results-buffer) nil) - ) + ;; Run the mode hook. + (run-hooks 'doctest-mode-hook)) (provide 'doctest-mode) ;;; doctest-mode.el ends here diff --git a/.elisp/pycomplete.el b/.elisp/pycomplete.el --- a/.elisp/pycomplete.el +++ b/.elisp/pycomplete.el @@ -1,7 +1,21 @@ ;;; Complete symbols at point using Pymacs. -;;; See pycomplete.py for the Python side of things and a short description -;;; of what to expect. +;; Copyright (C) 2007 Skip Montanaro + +;; Author: Skip Montanaro +;; Maintainer: skip@pobox.com +;; Created: Oct 2004 +;; Keywords: python pymacs emacs + +;; This software is provided as-is, without express or implied warranty. +;; Permission to use, copy, modify, distribute or sell this software, +;; without fee, for any purpose and by any individual or organization, is +;; hereby granted, provided that the above copyright notice and this +;; paragraph appear in all copies. + +;; Along with pycomplete.py this file allows programmers to complete Python +;; symbols within the current buffer. See pycomplete.py for the Python side +;; of things and a short description of what to expect. (require 'pymacs) (require 'python-mode) diff --git a/.elisp/pycomplete.py b/.elisp/pycomplete.py --- a/.elisp/pycomplete.py +++ b/.elisp/pycomplete.py @@ -21,6 +21,20 @@ do it right. See pycomplete.el for the Emacs Lisp side of things. """ +# Author: Skip Montanaro +# Maintainer: skip@pobox.com +# Created: Oct 2004 +# Keywords: python pymacs emacs + +# This software is provided as-is, without express or implied warranty. +# Permission to use, copy, modify, distribute or sell this software, without +# fee, for any purpose and by any individual or organization, is hereby +# granted, provided that the above copyright notice and this paragraph +# appear in all copies. + +# Along with pycomplete.el this file allows programmers to complete Python +# symbols within the current buffer. + import sys import os.path diff --git a/.elisp/python-mode.el b/.elisp/python-mode.el --- a/.elisp/python-mode.el +++ b/.elisp/python-mode.el @@ -2,39 +2,53 @@ ;; Copyright (C) 1992,1993,1994 Tim Peters -;; Author: 2003-2004 http://sf.net/projects/python-mode +;; Author: 2003-2009 https://launchpad.net/python-mode ;; 1995-2002 Barry A. Warsaw ;; 1992-1994 Tim Peters ;; Maintainer: python-mode@python.org ;; Created: Feb 1992 ;; Keywords: python languages oop -(defconst py-version "$Revision: 4.75 $" +(defconst py-version "5.1.0+" "`python-mode' version number.") -;; This software is provided as-is, without express or implied -;; warranty. Permission to use, copy, modify, distribute or sell this -;; software, without fee, for any purpose and by any individual or -;; organization, is hereby granted, provided that the above copyright -;; notice and this paragraph appear in all copies. +;; This file is part of python-mode.el. +;; +;; python-mode.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. +;; +;; python-mode.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. +;; +;; You should have received a copy of the GNU General Public License along +;; with python-mode.el. If not, see . ;;; Commentary: ;; This is a major mode for editing Python programs. It was developed by Tim ;; Peters after an original idea by Michael A. Guravage. Tim subsequently -;; left the net and in 1995, Barry Warsaw inherited the mode. Tim's now back -;; but disavows all responsibility for the mode. In fact, we suspect he -;; doesn't even use Emacs any more. In 2003, python-mode.el was moved to its -;; own SourceForge project apart from the Python project, and now is -;; maintained by the volunteers at the python-mode@python.org mailing list. +;; left the net and in 1995, Barry Warsaw inherited the mode. Tim came back +;; but disavowed all responsibility for the mode. In fact, we suspect he +;; doesn't even use Emacs any more . In 2003, python-mode.el was moved +;; to its own SourceForge project apart from the Python project, and in 2008 +;; it was moved to Launchpad for all project administration. python-mode.el +;; is maintained by the volunteers at the python-mode@python.org mailing +;; list. + +;; python-mode.el is different than, and pre-dates by many years, the +;; python.el that comes with FSF Emacs. We'd like to merge the two modes but +;; have few cycles to do so. Volunteers are welcome. ;; pdbtrack support contributed by Ken Manheimer, April 2001. Skip Montanaro ;; has also contributed significantly to python-mode's development. -;; Please use the SourceForge Python project to submit bugs or -;; patches: +;; Please use Launchpad to submit bugs or patches: ;; -;; http://sourceforge.net/projects/python +;; https://launchpad.net/python-mode ;; INSTALLATION: @@ -52,27 +66,18 @@ ;; (global-font-lock-mode t) ;; (setq font-lock-maximum-decoration t) -;; FOR MORE INFORMATION: - -;; There is some information on python-mode.el at - -;; http://www.python.org/emacs/python-mode/ -;; -;; It does contain links to other packages that you might find useful, -;; such as pdb interfaces, OO-Browser links, etc. - ;; BUG REPORTING: -;; As mentioned above, please use the SourceForge Python project for -;; submitting bug reports or patches. The old recommendation, to use -;; C-c C-b will still work, but those reports have a higher chance of -;; getting buried in my mailbox. Please include a complete, but -;; concise code sample and a recipe for reproducing the bug. Send -;; suggestions and other comments to python-mode@python.org. +;; As mentioned above, please use the Launchpad python-mode project for +;; submitting bug reports or patches. The old recommendation, to use C-c C-b +;; will still work, but those reports have a higher chance of getting buried +;; in our inboxes. Please include a complete, but concise code sample and a +;; recipe for reproducing the bug. Send suggestions and other comments to +;; python-mode@python.org. -;; When in a Python mode buffer, do a C-h m for more help. It's -;; doubtful that a texinfo manual would be very useful, but if you -;; want to contribute one, I'll certainly accept it! +;; When in a Python mode buffer, do a C-h m for more help. It's doubtful that +;; a texinfo manual would be very useful, but if you want to contribute one, +;; we'll certainly accept it! ;;; Code: @@ -125,7 +130,7 @@ Note that this variable is consulted onl mode buffer is visited during an Emacs session. After that, use \\[py-toggle-shells] to change the interpreter shell." :type '(choice (const :tag "Python (a.k.a. CPython)" cpython) - (const :tag "Jython" jython)) + (const :tag "Jython" jython)) :group 'python) (defcustom py-python-command-args '("-i") @@ -183,7 +188,7 @@ When this flag is non-nil, continuation preceding line's indentation. When this flag is nil, continuation lines are aligned to column zero." :type '(choice (const :tag "Align under preceding line" t) - (const :tag "Align to column zero" nil)) + (const :tag "Align to column zero" nil)) :group 'python) (defcustom py-block-comment-prefix "##" @@ -211,27 +216,27 @@ purposes. When not nil or t, comment lines that begin with a single `#' are used as indentation hints, unless the comment character is in column zero." :type '(choice - (const :tag "Skip all comment lines (fast)" nil) - (const :tag "Single # `sets' indentation for next line" t) - (const :tag "Single # `sets' indentation except at column zero" - other) - ) + (const :tag "Skip all comment lines (fast)" nil) + (const :tag "Single # `sets' indentation for next line" t) + (const :tag "Single # `sets' indentation except at column zero" + other) + ) :group 'python) (defcustom py-temp-directory (let ((ok '(lambda (x) - (and x - (setq x (expand-file-name x)) ; always true - (file-directory-p x) - (file-writable-p x) - x)))) + (and x + (setq x (expand-file-name x)) ; always true + (file-directory-p x) + (file-writable-p x) + x)))) (or (funcall ok (getenv "TMPDIR")) - (funcall ok "/usr/tmp") - (funcall ok "/tmp") - (funcall ok "/var/tmp") - (funcall ok ".") - (error - "Couldn't find a usable temp directory -- set `py-temp-directory'"))) + (funcall ok "/usr/tmp") + (funcall ok "/tmp") + (funcall ok "/var/tmp") + (funcall ok ".") + (error + "Couldn't find a usable temp directory -- set `py-temp-directory'"))) "*Directory used for temporary files created by a *Python* process. By default, the first directory from this list that exists and that you can write into: the value (if any) of the environment variable TMPDIR, @@ -402,6 +407,11 @@ support for features needed by `python-m "Face for builtins like TypeError, object, open, and exec.") (make-face 'py-builtins-face) +;; XXX, TODO, and FIXME comments and such +(defvar py-XXX-tag-face 'py-XXX-tag-face + "Face for XXX, TODO, and FIXME tags") +(make-face 'py-XXX-tag-face) + (defun py-font-lock-mode-hook () (or (face-differs-from-default-p 'py-pseudo-keyword-face) (copy-face 'font-lock-keyword-face 'py-pseudo-keyword-face)) @@ -409,87 +419,90 @@ support for features needed by `python-m (copy-face 'font-lock-keyword-face 'py-builtins-face)) (or (face-differs-from-default-p 'py-decorators-face) (copy-face 'py-pseudo-keyword-face 'py-decorators-face)) + (or (face-differs-from-default-p 'py-XXX-tag-face) + (copy-face 'font-lock-comment-face 'py-XXX-tag-face)) ) (add-hook 'font-lock-mode-hook 'py-font-lock-mode-hook) (defvar python-font-lock-keywords (let ((kw1 (mapconcat 'identity - '("and" "assert" "break" "class" - "continue" "def" "del" "elif" - "else" "except" "exec" "for" - "from" "global" "if" "import" - "in" "is" "lambda" "not" - "or" "pass" "print" "raise" - "return" "while" "yield" - ) - "\\|")) - (kw2 (mapconcat 'identity - '("else:" "except:" "finally:" "try:") - "\\|")) - (kw3 (mapconcat 'identity - ;; Don't include True, False, None, or - ;; Ellipsis in this list, since they are - ;; already defined as pseudo keywords. - '("__debug__" - "__import__" "__name__" "abs" "apply" "basestring" - "bool" "buffer" "callable" "chr" "classmethod" - "cmp" "coerce" "compile" "complex" "copyright" - "delattr" "dict" "dir" "divmod" - "enumerate" "eval" "execfile" "exit" "file" - "filter" "float" "getattr" "globals" "hasattr" - "hash" "hex" "id" "input" "int" "intern" - "isinstance" "issubclass" "iter" "len" "license" - "list" "locals" "long" "map" "max" "min" "object" - "oct" "open" "ord" "pow" "property" "range" - "raw_input" "reduce" "reload" "repr" "round" - "setattr" "slice" "staticmethod" "str" "sum" - "super" "tuple" "type" "unichr" "unicode" "vars" - "xrange" "zip") - "\\|")) - (kw4 (mapconcat 'identity - ;; Exceptions and warnings - '("ArithmeticError" "AssertionError" - "AttributeError" "DeprecationWarning" "EOFError" - "EnvironmentError" "Exception" - "FloatingPointError" "FutureWarning" "IOError" - "ImportError" "IndentationError" "IndexError" - "KeyError" "KeyboardInterrupt" "LookupError" - "MemoryError" "NameError" "NotImplemented" - "NotImplementedError" "OSError" "OverflowError" - "OverflowWarning" "PendingDeprecationWarning" - "ReferenceError" "RuntimeError" "RuntimeWarning" - "StandardError" "StopIteration" "SyntaxError" - "SyntaxWarning" "SystemError" "SystemExit" - "TabError" "TypeError" "UnboundLocalError" - "UnicodeDecodeError" "UnicodeEncodeError" - "UnicodeError" "UnicodeTranslateError" - "UserWarning" "ValueError" "Warning" - "ZeroDivisionError") - "\\|")) - ) + '("and" "assert" "break" "class" + "continue" "def" "del" "elif" + "else" "except" "for" "from" + "global" "if" "import" "in" + "is" "lambda" "not" "or" + "pass" "raise" "as" "return" + "while" "with" "yield" + ) + "\\|")) + (kw2 (mapconcat 'identity + '("else:" "except:" "finally:" "try:") + "\\|")) + (kw3 (mapconcat 'identity + ;; Don't include Ellipsis in this list, since it is + ;; already defined as a pseudo keyword. + '("__debug__" + "__import__" "__name__" "abs" "all" "any" "apply" + "basestring" "bin" "bool" "buffer" "bytearray" + "callable" "chr" "classmethod" "cmp" "coerce" + "compile" "complex" "copyright" "credits" + "delattr" "dict" "dir" "divmod" "enumerate" "eval" + "exec" "execfile" "exit" "file" "filter" "float" + "format" "getattr" "globals" "hasattr" "hash" "help" + "hex" "id" "input" "int" "intern" "isinstance" + "issubclass" "iter" "len" "license" "list" "locals" + "long" "map" "max" "memoryview" "min" "next" + "object" "oct" "open" "ord" "pow" "print" "property" + "quit" "range" "raw_input" "reduce" "reload" "repr" + "round" "set" "setattr" "slice" "sorted" + "staticmethod" "str" "sum" "super" "tuple" "type" + "unichr" "unicode" "vars" "xrange" "zip") + "\\|")) + (kw4 (mapconcat 'identity + ;; Exceptions and warnings + '("ArithmeticError" "AssertionError" + "AttributeError" "BaseException" "BufferError" + "BytesWarning" "DeprecationWarning" "EOFError" + "EnvironmentError" "Exception" + "FloatingPointError" "FutureWarning" "GeneratorExit" + "IOError" "ImportError" "ImportWarning" + "IndentationError" "IndexError" + "KeyError" "KeyboardInterrupt" "LookupError" + "MemoryError" "NameError" "NotImplemented" + "NotImplementedError" "OSError" "OverflowError" + "PendingDeprecationWarning" "ReferenceError" + "RuntimeError" "RuntimeWarning" "StandardError" + "StopIteration" "SyntaxError" "SyntaxWarning" + "SystemError" "SystemExit" "TabError" "TypeError" + "UnboundLocalError" "UnicodeDecodeError" + "UnicodeEncodeError" "UnicodeError" + "UnicodeTranslateError" "UnicodeWarning" + "UserWarning" "ValueError" "Warning" + "ZeroDivisionError") + "\\|")) + ) (list '("^[ \t]*\\(@.+\\)" 1 'py-decorators-face) ;; keywords (cons (concat "\\<\\(" kw1 "\\)\\>[ \n\t(]") 1) ;; builtins when they don't appear as object attributes (list (concat "\\([^. \t]\\|^\\)[ \t]*\\<\\(" kw3 "\\)\\>[ \n\t(]") 2 - 'py-builtins-face) + 'py-builtins-face) ;; block introducing keywords with immediately following colons. ;; Yes "except" is in both lists. (cons (concat "\\<\\(" kw2 "\\)[ \n\t(]") 1) ;; Exceptions (list (concat "\\<\\(" kw4 "\\)[ \n\t:,(]") 1 'py-builtins-face) - ;; `as' but only in "import foo as bar" - '("[ \t]*\\(\\.*\\)?\\.*\\<\\(as\\)\\>" . 2) - ;; classes '("\\" + '("\\<\\(self\\|Ellipsis\\|True\\|False\\|None\\)\\>" 1 py-pseudo-keyword-face) + ;; XXX, TODO, and FIXME tags + '("XXX\\|TODO\\|FIXME" 0 py-XXX-tag-face t) )) "Additional expressions to highlight in Python mode.") (put 'python-mode 'font-lock-defaults '(python-font-lock-keywords)) @@ -521,9 +534,9 @@ Currently-active file is at the head of ;; with potential embedded double quotes "[rR]?\"\"\"[^\"]*\\(\\(\"[^\"]\\|\"\"[^\"]\\)[^\"]*\\)*\"\"\"" "\\|" - "[rR]?'\\([^'\n\\]\\|\\\\.\\)*'" ; single-quoted - "\\|" ; or - "[rR]?\"\\([^\"\n\\]\\|\\\\.\\)*\"" ; double-quoted + "[rR]?'\\([^'\n\\]\\|\\\\.\\)*'" ; single-quoted + "\\|" ; or + "[rR]?\"\\([^\"\n\\]\\|\\\\.\\)*\"" ; double-quoted ) "Regular expression matching a Python string literal.") @@ -540,12 +553,12 @@ Currently-active file is at the head of (defconst py-outdent-re (concat "\\(" (mapconcat 'identity - '("else:" - "except\\(\\s +.*\\)?:" - "finally:" - "elif\\s +.*:") - "\\|") - "\\)") + '("else:" + "except\\(\\s +.*\\)?:" + "finally:" + "elif\\s +.*:") + "\\|") + "\\)") "Regular expression matching statements to be dedented one level.") (defconst py-block-closing-keywords-re @@ -556,16 +569,16 @@ Currently-active file is at the head of (concat "\\(" (mapconcat 'identity - (list "try:" - "except\\(\\s +.*\\)?:" - "while\\s +.*:" - "for\\s +.*:" - "if\\s +.*:" - "elif\\s +.*:" - (concat py-block-closing-keywords-re "[ \t\n]") - ) - "\\|") - "\\)") + (list "try:" + "except\\(\\s +.*\\)?:" + "while\\s +.*:" + "for\\s +.*:" + "if\\s +.*:" + "elif\\s +.*:" + (concat py-block-closing-keywords-re "[ \t\n]") + ) + "\\|") + "\\)") "Regular expression matching lines not to dedent after.") (defvar py-traceback-line-re @@ -654,6 +667,7 @@ Currently-active file is at the head of (define-key py-mode-map "\C-c:" 'py-guess-indent-offset) (define-key py-mode-map "\C-c\t" 'py-indent-region) (define-key py-mode-map "\C-c\C-d" 'py-pdbtrack-toggle-stack-tracking) + (define-key py-mode-map "\C-c\C-f" 'py-sort-imports) (define-key py-mode-map "\C-c\C-n" 'py-next-statement) (define-key py-mode-map "\C-c\C-p" 'py-previous-statement) (define-key py-mode-map "\C-c\C-u" 'py-goto-block-up) @@ -674,9 +688,9 @@ Currently-active file is at the head of ;; shadow global bindings for newline-and-indent w/ the py- version. ;; BAW - this is extremely bad form, but I'm not going to change it ;; for now. - (mapcar #'(lambda (key) - (define-key py-mode-map key 'py-newline-and-indent)) - (where-is-internal 'newline-and-indent)) + (mapc #'(lambda (key) + (define-key py-mode-map key 'py-newline-and-indent)) + (where-is-internal 'newline-and-indent)) ;; Force RET to be py-newline-and-indent even if it didn't get ;; mapped by the above code. motivation: Emacs' default binding for ;; RET is `newline' and C-j is `newline-and-indent'. Most Pythoneers @@ -697,10 +711,10 @@ Currently-active file is at the head of (define-key py-mode-output-map "\C-c\C-c" 'py-goto-exception) ;; TBD: Disable all self-inserting keys. This is bogus, we should ;; really implement this as *Python Output* buffer being read-only - (mapcar #' (lambda (key) - (define-key py-mode-output-map key - #'(lambda () (interactive) (beep)))) - (where-is-internal 'self-insert-command)) + (mapc #' (lambda (key) + (define-key py-mode-output-map key + #'(lambda () (interactive) (beep)))) + (where-is-internal 'self-insert-command)) ) (defvar py-shell-map nil @@ -762,7 +776,7 @@ Currently-active file is at the head of "Syntax table used to identify Python dotted expressions.") (when (not py-dotted-expression-syntax-table) (setq py-dotted-expression-syntax-table - (copy-syntax-table py-mode-syntax-table)) + (copy-syntax-table py-mode-syntax-table)) (modify-syntax-entry ?_ "_" py-dotted-expression-syntax-table) (modify-syntax-entry ?. "_" py-dotted-expression-syntax-table)) @@ -771,9 +785,9 @@ Currently-active file is at the head of ;; Utilities (defmacro py-safe (&rest body) "Safely execute BODY, return nil if an error occurred." - (` (condition-case nil - (progn (,@ body)) - (error nil)))) + `(condition-case nil + (progn ,@ body) + (error nil))) (defsubst py-keep-region-active () "Keep the region active in XEmacs." @@ -804,14 +818,14 @@ This function does not modify point or m ((eq position 'bod) (py-beginning-of-def-or-class 'either)) ((eq position 'eod) (py-end-of-def-or-class 'either)) ;; Kind of funny, I know, but useful for py-up-exception. - ((eq position 'bob) (beginning-of-buffer)) - ((eq position 'eob) (end-of-buffer)) + ((eq position 'bob) (goto-char (point-min))) + ((eq position 'eob) (goto-char (point-max))) ((eq position 'boi) (back-to-indentation)) ((eq position 'bos) (py-goto-initial-line)) (t (error "Unknown buffer position requested: %s" position)) ) (prog1 - (point) + (point) (goto-char here)))) (defsubst py-highlight-line (from to file line) @@ -836,7 +850,7 @@ i.e. the limit on how far back to scan." ;; ;; WARNING: Watch out for infinite recursion. (let* ((lim (or lim (py-point 'bod))) - (state (parse-partial-sexp lim (point)))) + (state (parse-partial-sexp lim (point)))) (cond ((nth 3 state) 'string) ((nth 4 state) 'comment) @@ -866,44 +880,44 @@ package. Note that the latest X/Emacs r (easy-menu-define py-menu py-mode-map "Python Mode menu" '("Python" - ["Comment Out Region" py-comment-region (mark)] - ["Uncomment Region" (py-comment-region (point) (mark) '(4)) (mark)] - "-" - ["Mark current block" py-mark-block t] - ["Mark current def" py-mark-def-or-class t] - ["Mark current class" (py-mark-def-or-class t) t] - "-" - ["Shift region left" py-shift-region-left (mark)] - ["Shift region right" py-shift-region-right (mark)] - "-" - ["Import/reload file" py-execute-import-or-reload t] - ["Execute buffer" py-execute-buffer t] - ["Execute region" py-execute-region (mark)] - ["Execute def or class" py-execute-def-or-class (mark)] - ["Execute string" py-execute-string t] - ["Start interpreter..." py-shell t] - "-" - ["Go to start of block" py-goto-block-up t] - ["Go to start of class" (py-beginning-of-def-or-class t) t] - ["Move to end of class" (py-end-of-def-or-class t) t] - ["Move to start of def" py-beginning-of-def-or-class t] - ["Move to end of def" py-end-of-def-or-class t] - "-" - ["Describe mode" py-describe-mode t] - ))) + ["Comment Out Region" py-comment-region (mark)] + ["Uncomment Region" (py-comment-region (point) (mark) '(4)) (mark)] + "-" + ["Mark current block" py-mark-block t] + ["Mark current def" py-mark-def-or-class t] + ["Mark current class" (py-mark-def-or-class t) t] + "-" + ["Shift region left" py-shift-region-left (mark)] + ["Shift region right" py-shift-region-right (mark)] + "-" + ["Import/reload file" py-execute-import-or-reload t] + ["Execute buffer" py-execute-buffer t] + ["Execute region" py-execute-region (mark)] + ["Execute def or class" py-execute-def-or-class (mark)] + ["Execute string" py-execute-string t] + ["Start interpreter..." py-shell t] + "-" + ["Go to start of block" py-goto-block-up t] + ["Go to start of class" (py-beginning-of-def-or-class t) t] + ["Move to end of class" (py-end-of-def-or-class t) t] + ["Move to start of def" py-beginning-of-def-or-class t] + ["Move to end of def" py-end-of-def-or-class t] + "-" + ["Describe mode" py-describe-mode t] + ))) ;; Imenu definitions (defvar py-imenu-class-regexp - (concat ; <> - "\\(" ; - "^[ \t]*" ; newline and maybe whitespace - "\\(class[ \t]+[a-zA-Z0-9_]+\\)" ; class name - ; possibly multiple superclasses + (concat ; <> + "\\(" ; + "^[ \t]*" ; newline and maybe whitespace + "\\(class[ \t]+[a-zA-Z0-9_]+\\)" ; class name + ; possibly multiple superclasses "\\([ \t]*\\((\\([a-zA-Z0-9_,. \t\n]\\)*)\\)?\\)" - "[ \t]*:" ; and the final : - "\\)" ; >>classes<< + "[ \t]*:" ; and the final : + "\\)" ; >>classes<< ) "Regexp for Python classes for use with the Imenu package." ) @@ -914,7 +928,7 @@ package. Note that the latest X/Emacs r "^[ \t]*" ; new line and maybe whitespace "\\(def[ \t]+" ; function definitions start with def "\\([a-zA-Z0-9_]+\\)" ; name is here - ; function arguments... + ; function arguments... ;; "[ \t]*(\\([-+/a-zA-Z0-9_=,\* \t\n.()\"'#]*\\))" "[ \t]*(\\([^:#]*\\))" "\\)" ; end of def @@ -948,7 +962,7 @@ information.") (cons (concat py-imenu-class-regexp - "\\|" ; or... + "\\|" ; or... py-imenu-method-regexp ) py-imenu-method-no-arg-parens) @@ -970,9 +984,9 @@ Finds all Python classes and functions/m \\[py-imenu-create-index-engine]. See that function for the details of how this works." (setq py-imenu-generic-regexp (car py-imenu-generic-expression) - py-imenu-generic-parens (if py-imenu-show-method-args-p - py-imenu-method-arg-parens - py-imenu-method-no-arg-parens)) + py-imenu-generic-parens (if py-imenu-show-method-args-p + py-imenu-method-arg-parens + py-imenu-method-no-arg-parens)) (goto-char (point-min)) ;; Warning: When the buffer has no classes or functions, this will ;; return nil, which seems proper according to the Imenu API, but @@ -987,12 +1001,12 @@ file for the Imenu package. Returns a possibly nested alist of the form - (INDEX-NAME . INDEX-POSITION) + (INDEX-NAME . INDEX-POSITION) The second element of the alist may be an alist, producing a nested list as in - (INDEX-NAME . INDEX-ALIST) + (INDEX-NAME . INDEX-ALIST) This function should not be called directly, as it calls itself recursively and requires some setup. Rather this is the engine for @@ -1011,41 +1025,41 @@ at which to continue looking for Python functions. If this is not supplied, the function uses the indentation of the first definition found." (let (index-alist - sub-method-alist - looking-p - def-name prev-name - cur-indent def-pos - (class-paren (first py-imenu-generic-parens)) - (def-paren (second py-imenu-generic-parens))) + sub-method-alist + looking-p + def-name prev-name + cur-indent def-pos + (class-paren (first py-imenu-generic-parens)) + (def-paren (second py-imenu-generic-parens))) (setq looking-p - (re-search-forward py-imenu-generic-regexp (point-max) t)) + (re-search-forward py-imenu-generic-regexp (point-max) t)) (while looking-p (save-excursion - ;; used to set def-name to this value but generic-extract-name - ;; is new to imenu-1.14. this way it still works with - ;; imenu-1.11 - ;;(imenu--generic-extract-name py-imenu-generic-parens)) - (let ((cur-paren (if (match-beginning class-paren) - class-paren def-paren))) - (setq def-name - (buffer-substring-no-properties (match-beginning cur-paren) - (match-end cur-paren)))) - (save-match-data - (py-beginning-of-def-or-class 'either)) - (beginning-of-line) - (setq cur-indent (current-indentation))) + ;; used to set def-name to this value but generic-extract-name + ;; is new to imenu-1.14. this way it still works with + ;; imenu-1.11 + ;;(imenu--generic-extract-name py-imenu-generic-parens)) + (let ((cur-paren (if (match-beginning class-paren) + class-paren def-paren))) + (setq def-name + (buffer-substring-no-properties (match-beginning cur-paren) + (match-end cur-paren)))) + (save-match-data + (py-beginning-of-def-or-class 'either)) + (beginning-of-line) + (setq cur-indent (current-indentation))) ;; HACK: want to go to the next correct definition location. We ;; explicitly list them here but it would be better to have them ;; in a list. (setq def-pos - (or (match-beginning class-paren) - (match-beginning def-paren))) + (or (match-beginning class-paren) + (match-beginning def-paren))) ;; if we don't have a starting indent level, take this one (or start-indent - (setq start-indent cur-indent)) + (setq start-indent cur-indent)) ;; if we don't have class name yet, take this one (or prev-name - (setq prev-name def-name)) + (setq prev-name def-name)) ;; what level is the next definition on? must be same, deeper ;; or shallower indentation (cond @@ -1053,32 +1067,32 @@ of the first definition found." ((py-in-literal)) ;; at the same indent level, add it to the list... ((= start-indent cur-indent) - (push (cons def-name def-pos) index-alist)) + (push (cons def-name def-pos) index-alist)) ;; deeper indented expression, recurse ((< start-indent cur-indent) - ;; the point is currently on the expression we're supposed to - ;; start on, so go back to the last expression. The recursive - ;; call will find this place again and add it to the correct - ;; list - (re-search-backward py-imenu-generic-regexp (point-min) 'move) - (setq sub-method-alist (py-imenu-create-index-engine cur-indent)) - (if sub-method-alist - ;; we put the last element on the index-alist on the start - ;; of the submethod alist so the user can still get to it. - (let ((save-elmt (pop index-alist))) - (push (cons prev-name - (cons save-elmt sub-method-alist)) - index-alist)))) + ;; the point is currently on the expression we're supposed to + ;; start on, so go back to the last expression. The recursive + ;; call will find this place again and add it to the correct + ;; list + (re-search-backward py-imenu-generic-regexp (point-min) 'move) + (setq sub-method-alist (py-imenu-create-index-engine cur-indent)) + (if sub-method-alist + ;; we put the last element on the index-alist on the start + ;; of the submethod alist so the user can still get to it. + (let ((save-elmt (pop index-alist))) + (push (cons prev-name + (cons save-elmt sub-method-alist)) + index-alist)))) ;; found less indented expression, we're done. (t - (setq looking-p nil) - (re-search-backward py-imenu-generic-regexp (point-min) t))) + (setq looking-p nil) + (re-search-backward py-imenu-generic-regexp (point-min) t))) ;; end-cond (setq prev-name def-name) (and looking-p - (setq looking-p - (re-search-forward py-imenu-generic-regexp - (point-max) 'move)))) + (setq looking-p + (re-search-forward py-imenu-generic-regexp + (point-max) 'move)))) (nreverse index-alist))) @@ -1091,18 +1105,18 @@ Used by `py-choose-shell', and similar t ;; look for an interpreter specified in the first line ;; similar to set-auto-mode (files.el) (let* ((re (if (boundp 'auto-mode-interpreter-regexp) - auto-mode-interpreter-regexp - ;; stolen from Emacs 21.2 - "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)")) - (interpreter (save-excursion - (goto-char (point-min)) - (if (looking-at re) - (match-string 2) - ""))) - elt) + auto-mode-interpreter-regexp + ;; stolen from Emacs 21.2 + "#![ \t]?\\([^ \t\n]*/bin/env[ \t]\\)?\\([^ \t\n]+\\)")) + (interpreter (save-excursion + (goto-char (point-min)) + (if (looking-at re) + (match-string 2) + ""))) + elt) ;; Map interpreter name to a mode. (setq elt (assoc (file-name-nondirectory interpreter) - py-shell-alist)) + py-shell-alist)) (and elt (caddr elt)))) @@ -1116,12 +1130,12 @@ return `jython', otherwise return nil." (save-excursion (goto-char (point-min)) (while (and (not mode) - (search-forward-regexp - "^\\(\\(from\\)\\|\\(import\\)\\) \\([^ \t\n.]+\\)" - py-import-check-point-max t)) - (setq mode (and (member (match-string 4) py-jython-packages) - 'jython - )))) + (search-forward-regexp + "^\\(\\(from\\)\\|\\(import\\)\\) \\([^ \t\n.]+\\)" + py-import-check-point-max t)) + (setq mode (and (member (match-string 4) py-jython-packages) + 'jython + )))) mode)) @@ -1179,24 +1193,24 @@ py-beep-if-tab-change\t\tring the bell i ;; (set-syntax-table py-mode-syntax-table) (setq major-mode 'python-mode - mode-name "Python" - local-abbrev-table python-mode-abbrev-table - font-lock-defaults '(python-font-lock-keywords) - paragraph-separate "^[ \t]*$" - paragraph-start "^[ \t]*$" - require-final-newline t - comment-start "# " - comment-end "" - comment-start-skip "# *" - comment-column 40 - comment-indent-function 'py-comment-indent-function - indent-region-function 'py-indent-region - indent-line-function 'py-indent-line - ;; tell add-log.el how to find the current function/method/variable - add-log-current-defun-function 'py-current-defun - - fill-paragraph-function 'py-fill-paragraph - ) + mode-name "Python" + local-abbrev-table python-mode-abbrev-table + font-lock-defaults '(python-font-lock-keywords) + paragraph-separate "^[ \t]*$" + paragraph-start "^[ \t]*$" + require-final-newline t + comment-start "# " + comment-end "" + comment-start-skip "# *" + comment-column 40 + comment-indent-function 'py-comment-indent-function + indent-region-function 'py-indent-region + indent-line-function 'py-indent-line + ;; tell add-log.el how to find the current function/method/variable + add-log-current-defun-function 'py-current-defun + + fill-paragraph-function 'py-fill-paragraph + ) (use-local-map py-mode-map) ;; add the menu (if py-menu @@ -1209,7 +1223,7 @@ py-beep-if-tab-change\t\tring the bell i (setq imenu-create-index-function #'py-imenu-create-index-function) (setq imenu-generic-expression py-imenu-generic-expression) (if (fboundp 'imenu-add-to-menubar) - (imenu-add-to-menubar (format "%s-%s" "IM" mode-name))) + (imenu-add-to-menubar (format "%s-%s" "IM" mode-name))) ) ;; Run the mode hook. Note that py-mode-hook is deprecated. (if python-mode-hook @@ -1220,15 +1234,15 @@ py-beep-if-tab-change\t\tring the bell i (let ((offset py-indent-offset)) ;; It's okay if this fails to guess a good value (if (and (py-safe (py-guess-indent-offset)) - (<= py-indent-offset 8) - (>= py-indent-offset 2)) - (setq offset py-indent-offset)) + (<= py-indent-offset 8) + (>= py-indent-offset 2)) + (setq offset py-indent-offset)) (setq py-indent-offset offset) ;; Only turn indent-tabs-mode off if tab-width != ;; py-indent-offset. Never turn it on, because the user must ;; have explicitly turned it off. (if (/= tab-width py-indent-offset) - (setq indent-tabs-mode nil)) + (setq indent-tabs-mode nil)) )) ;; Set the default shell if not already set (when (null py-which-shell) @@ -1256,14 +1270,14 @@ It is added to `interpreter-mode-alist' ;; already added. ;;;###autoload (let ((modes '(("jython" . jython-mode) - ("python" . python-mode)))) + ("python" . python-mode)))) (while modes (when (not (assoc (car modes) interpreter-mode-alist)) (push (car modes) interpreter-mode-alist)) (setq modes (cdr modes)))) ;;;###autoload (when (not (or (rassq 'python-mode auto-mode-alist) - (rassq 'jython-mode auto-mode-alist))) + (rassq 'jython-mode auto-mode-alist))) (push '("\\.py$" . python-mode) auto-mode-alist)) @@ -1273,17 +1287,17 @@ It is added to `interpreter-mode-alist' "Returns non-nil if the current line should dedent one level." (save-excursion (and (progn (back-to-indentation) - (looking-at py-outdent-re)) - ;; short circuit infloop on illegal construct - (not (bobp)) - (progn (forward-line -1) - (py-goto-initial-line) - (back-to-indentation) - (while (or (looking-at py-blank-or-comment-re) - (bobp)) - (backward-to-indentation 1)) - (not (looking-at py-no-outdent-re))) - ))) + (looking-at py-outdent-re)) + ;; short circuit infloop on illegal construct + (not (bobp)) + (progn (forward-line -1) + (py-goto-initial-line) + (back-to-indentation) + (while (or (looking-at py-blank-or-comment-re) + (bobp)) + (backward-to-indentation 1)) + (not (looking-at py-no-outdent-re))) + ))) (defun py-electric-colon (arg) "Insert a colon. @@ -1295,34 +1309,34 @@ comment." (self-insert-command (prefix-numeric-value arg)) ;; are we in a string or comment? (if (save-excursion - (let ((pps (parse-partial-sexp (save-excursion - (py-beginning-of-def-or-class) - (point)) - (point)))) - (not (or (nth 3 pps) (nth 4 pps))))) + (let ((pps (parse-partial-sexp (save-excursion + (py-beginning-of-def-or-class) + (point)) + (point)))) + (not (or (nth 3 pps) (nth 4 pps))))) (save-excursion - (let ((here (point)) - (outdent 0) - (indent (py-compute-indentation t))) - (if (and (not arg) - (py-outdent-p) - (= indent (save-excursion - (py-next-statement -1) - (py-compute-indentation t))) - ) - (setq outdent py-indent-offset)) - ;; Don't indent, only dedent. This assumes that any lines - ;; that are already dedented relative to - ;; py-compute-indentation were put there on purpose. It's - ;; highly annoying to have `:' indent for you. Use TAB, C-c - ;; C-l or C-c C-r to adjust. TBD: Is there a better way to - ;; determine this??? - (if (< (current-indentation) indent) nil - (goto-char here) - (beginning-of-line) - (delete-horizontal-space) - (indent-to (- indent outdent)) - ))))) + (let ((here (point)) + (outdent 0) + (indent (py-compute-indentation t))) + (if (and (not arg) + (py-outdent-p) + (= indent (save-excursion + (py-next-statement -1) + (py-compute-indentation t))) + ) + (setq outdent py-indent-offset)) + ;; Don't indent, only dedent. This assumes that any lines + ;; that are already dedented relative to + ;; py-compute-indentation were put there on purpose. It's + ;; highly annoying to have `:' indent for you. Use TAB, C-c + ;; C-l or C-c C-r to adjust. TBD: Is there a better way to + ;; determine this??? + (if (< (current-indentation) indent) nil + (goto-char here) + (beginning-of-line) + (delete-horizontal-space) + (indent-to (- indent outdent)) + ))))) ;; Python subprocess utilities and filters @@ -1332,17 +1346,17 @@ Make that process's buffer visible and f comint believe the user typed this string so that `kill-output-from-shell' does The Right Thing." (let ((curbuf (current-buffer)) - (procbuf (process-buffer proc)) -; (comint-scroll-to-bottom-on-output t) - (msg (format "## working on region in file %s...\n" filename)) + (procbuf (process-buffer proc)) +; (comint-scroll-to-bottom-on-output t) + (msg (format "## working on region in file %s...\n" filename)) ;; add some comment, so that we can filter it out of history - (cmd (format "execfile(r'%s') # PYTHON-MODE\n" filename))) + (cmd (format "execfile(r'%s') # PYTHON-MODE\n" filename))) (unwind-protect - (save-excursion - (set-buffer procbuf) - (goto-char (point-max)) - (move-marker (process-mark proc) (point)) - (funcall (process-filter proc) proc msg)) + (save-excursion + (set-buffer procbuf) + (goto-char (point-max)) + (move-marker (process-mark proc) (point)) + (funcall (process-filter proc) proc msg)) (set-buffer curbuf)) (process-send-string proc cmd))) @@ -1359,22 +1373,22 @@ This function is appropriate for `comint (py-safe (delete-file (car py-file-queue))) (setq py-file-queue (cdr py-file-queue)) (if py-file-queue - (let ((pyproc (get-buffer-process (current-buffer)))) - (py-execute-file pyproc (car py-file-queue)))) + (let ((pyproc (get-buffer-process (current-buffer)))) + (py-execute-file pyproc (car py-file-queue)))) )) (defun py-pdbtrack-overlay-arrow (activation) "Activate or de arrow at beginning-of-line in current buffer." ;; This was derived/simplified from edebug-overlay-arrow (cond (activation - (setq overlay-arrow-position (make-marker)) - (setq overlay-arrow-string "=>") - (set-marker overlay-arrow-position (py-point 'bol) (current-buffer)) - (setq py-pdbtrack-is-tracking-p t)) - (overlay-arrow-position - (setq overlay-arrow-position nil) - (setq py-pdbtrack-is-tracking-p nil)) - )) + (setq overlay-arrow-position (make-marker)) + (setq overlay-arrow-string "=>") + (set-marker overlay-arrow-position (py-point 'bol) (current-buffer)) + (setq py-pdbtrack-is-tracking-p t)) + (overlay-arrow-position + (setq overlay-arrow-position nil) + (setq py-pdbtrack-is-tracking-p nil)) + )) (defun py-pdbtrack-track-stack-file (text) "Show the file indicated by the pdb stack entry line, in a separate window. @@ -1401,7 +1415,7 @@ script, and set to python-mode, and pdbt ;; other pdb commands wipe out the highlight. You can always do a ;; 'where' (aka 'w') command to reveal the overlay arrow. (let* ((origbuf (current-buffer)) - (currproc (get-buffer-process origbuf))) + (currproc (get-buffer-process origbuf))) (if (not (and currproc py-pdbtrack-do-tracking-p)) (py-pdbtrack-overlay-arrow nil) @@ -1449,7 +1463,7 @@ problem as best as we can determine." "Traceback cue not found" (let* ((filename (match-string 1 block)) - (lineno (string-to-int (match-string 2 block))) + (lineno (string-to-number (match-string 2 block))) (funcname (match-string 3 block)) funcbuffer) @@ -1509,12 +1523,12 @@ If an exception occurred return t, other (let (line file bol err-p) (save-excursion (set-buffer buf) - (beginning-of-buffer) + (goto-char (point-min)) (while (re-search-forward py-traceback-line-re nil t) - (setq file (match-string 1) - line (string-to-int (match-string 2)) - bol (py-point 'bol)) - (py-highlight-line bol (py-point 'eol) file line))) + (setq file (match-string 1) + line (string-to-number (match-string 2)) + bol (py-point 'bol)) + (py-highlight-line bol (py-point 'eol) file line))) (when (and py-jump-on-exception line) (beep) (py-jump-to-exception file line) @@ -1528,7 +1542,7 @@ If an exception occurred return t, other ;; only used when (memq 'broken-temp-names py-emacs-features) (defvar py-serial-number 0) (defvar py-exception-buffer nil) -(defconst py-output-buffer "*Python Output*") +(defvar py-output-buffer "*Python Output*") (make-variable-buffer-local 'py-output-buffer) ;; for toggling between CPython and Jython @@ -1557,7 +1571,7 @@ Programmatically, ARG can also be one of ((equal arg 0) ;; toggle (if (string-equal py-which-bufname "Python") - (setq arg -1) + (setq arg -1) (setq arg 1))) ((equal arg 'cpython) (setq arg 1)) ((equal arg 'jython) (setq arg -1))) @@ -1566,18 +1580,18 @@ Programmatically, ARG can also be one of ((< 0 arg) ;; set to CPython (setq py-which-shell py-python-command - py-which-args py-python-command-args - py-which-bufname "Python" - msg "CPython") + py-which-args py-python-command-args + py-which-bufname "Python" + msg "CPython") (if (string-equal py-which-bufname "Jython") - (setq mode-name "Python"))) + (setq mode-name "Python"))) ((> 0 arg) (setq py-which-shell py-jython-command - py-which-args py-jython-command-args - py-which-bufname "Jython" - msg "Jython") + py-which-args py-jython-command-args + py-which-bufname "Jython" + msg "Jython") (if (string-equal py-which-bufname "Python") - (setq mode-name "Jython"))) + (setq mode-name "Jython"))) ) (message "Using the %s shell" msg) (setq py-output-buffer (format "*%s Output*" py-which-bufname)))) @@ -1626,15 +1640,15 @@ filter." (py-toggle-shells py-default-interpreter)) (let ((args py-which-args)) (when (and argprompt - (interactive-p) - (fboundp 'split-string)) + (interactive-p) + (fboundp 'split-string)) ;; TBD: Perhaps force "-i" in the final list? (setq args (split-string - (read-string (concat py-which-bufname - " arguments: ") - (concat - (mapconcat 'identity py-which-args " ") " ") - )))) + (read-string (concat py-which-bufname + " arguments: ") + (concat + (mapconcat 'identity py-which-args " ") " ") + )))) (if (not (equal (buffer-name) "*Python*")) (switch-to-buffer-other-window (apply 'make-comint py-which-bufname py-which-shell nil args)) @@ -1644,7 +1658,7 @@ filter." py-shell-input-prompt-2-regexp "\\|" "^([Pp]db) ")) (add-hook 'comint-output-filter-functions - 'py-comint-output-filter-function) + 'py-comint-output-filter-function) ;; pdbtrack (add-hook 'comint-output-filter-functions 'py-pdbtrack-track-stack-file) (setq py-pdbtrack-do-tracking-p t) @@ -1657,7 +1671,7 @@ filter." "Clear the queue of temporary files waiting to execute." (interactive) (let ((n (length py-file-queue))) - (mapcar 'delete-file py-file-queue) + (mapc 'delete-file py-file-queue) (setq py-file-queue nil) (message "%d pending files de-queued." n))) @@ -1690,90 +1704,90 @@ is inserted at the end. See also the co (interactive "r\nP") ;; Skip ahead to the first non-blank line (let* ((proc (get-process py-which-bufname)) - (temp (if (memq 'broken-temp-names py-emacs-features) - (let - ((sn py-serial-number) - (pid (and (fboundp 'emacs-pid) (emacs-pid)))) - (setq py-serial-number (1+ py-serial-number)) - (if pid - (format "python-%d-%d" sn pid) - (format "python-%d" sn))) - (make-temp-name "python-"))) - (file (concat (expand-file-name temp py-temp-directory) ".py")) - (cur (current-buffer)) - (buf (get-buffer-create file)) - shell) + (temp (if (memq 'broken-temp-names py-emacs-features) + (let + ((sn py-serial-number) + (pid (and (fboundp 'emacs-pid) (emacs-pid)))) + (setq py-serial-number (1+ py-serial-number)) + (if pid + (format "python-%d-%d" sn pid) + (format "python-%d" sn))) + (make-temp-name "python-"))) + (file (concat (expand-file-name temp py-temp-directory) ".py")) + (cur (current-buffer)) + (buf (get-buffer-create file)) + shell) ;; Write the contents of the buffer, watching out for indented regions. (save-excursion (goto-char start) (beginning-of-line) (while (and (looking-at "\\s *$") - (< (point) end)) - (forward-line 1)) + (< (point) end)) + (forward-line 1)) (setq start (point)) (or (< start end) - (error "Region is empty")) + (error "Region is empty")) (setq py-line-number-offset (count-lines 1 start)) (let ((needs-if (/= (py-point 'bol) (py-point 'boi)))) - (set-buffer buf) - (python-mode) - (when needs-if - (insert "if 1:\n") - (setq py-line-number-offset (- py-line-number-offset 1))) - (insert-buffer-substring cur start end) - ;; Set the shell either to the #! line command, or to the - ;; py-which-shell buffer local variable. - (setq shell (or (py-choose-shell-by-shebang) - (py-choose-shell-by-import) - py-which-shell)))) + (set-buffer buf) + (python-mode) + (when needs-if + (insert "if 1:\n") + (setq py-line-number-offset (- py-line-number-offset 1))) + (insert-buffer-substring cur start end) + ;; Set the shell either to the #! line command, or to the + ;; py-which-shell buffer local variable. + (setq shell (or (py-choose-shell-by-shebang) + (py-choose-shell-by-import) + py-which-shell)))) (cond ;; always run the code in its own asynchronous subprocess (async ;; User explicitly wants this to run in its own async subprocess (save-excursion - (set-buffer buf) - (write-region (point-min) (point-max) file nil 'nomsg)) + (set-buffer buf) + (write-region (point-min) (point-max) file nil 'nomsg)) (let* ((buf (generate-new-buffer-name py-output-buffer)) - ;; TBD: a horrible hack, but why create new Custom variables? - (arg (if (string-equal py-which-bufname "Python") - "-u" ""))) - (start-process py-which-bufname buf shell arg file) - (pop-to-buffer buf) - (py-postprocess-output-buffer buf) - ;; TBD: clean up the temporary file! - )) + ;; TBD: a horrible hack, but why create new Custom variables? + (arg (if (string-equal py-which-bufname "Python") + "-u" ""))) + (start-process py-which-bufname buf shell arg file) + (pop-to-buffer buf) + (py-postprocess-output-buffer buf) + ;; TBD: clean up the temporary file! + )) ;; if the Python interpreter shell is running, queue it up for ;; execution there. (proc ;; use the existing python shell (save-excursion - (set-buffer buf) - (write-region (point-min) (point-max) file nil 'nomsg)) + (set-buffer buf) + (write-region (point-min) (point-max) file nil 'nomsg)) (if (not py-file-queue) - (py-execute-file proc file) - (message "File %s queued for execution" file)) + (py-execute-file proc file) + (message "File %s queued for execution" file)) (setq py-file-queue (append py-file-queue (list file))) (setq py-exception-buffer (cons file (current-buffer)))) (t ;; TBD: a horrible hack, but why create new Custom variables? (let ((cmd (concat py-which-shell (if (string-equal py-which-bufname - "Jython") - " -" "")))) - ;; otherwise either run it synchronously in a subprocess - (save-excursion - (set-buffer buf) - (shell-command-on-region (point-min) (point-max) - cmd py-output-buffer)) - ;; shell-command-on-region kills the output buffer if it never - ;; existed and there's no output from the command - (if (not (get-buffer py-output-buffer)) - (message "No output.") - (setq py-exception-buffer (current-buffer)) - (let ((err-p (py-postprocess-output-buffer py-output-buffer))) - (pop-to-buffer py-output-buffer) - (if err-p - (pop-to-buffer py-exception-buffer))) - )) + "Jython") + " -" "")))) + ;; otherwise either run it synchronously in a subprocess + (save-excursion + (set-buffer buf) + (shell-command-on-region (point-min) (point-max) + cmd py-output-buffer)) + ;; shell-command-on-region kills the output buffer if it never + ;; existed and there's no output from the command + (if (not (get-buffer py-output-buffer)) + (message "No output.") + (setq py-exception-buffer (current-buffer)) + (let ((err-p (py-postprocess-output-buffer py-output-buffer))) + (pop-to-buffer py-output-buffer) + (if err-p + (pop-to-buffer py-exception-buffer))) + )) )) ;; Clean up after ourselves. (kill-buffer buf))) @@ -1836,12 +1850,12 @@ This may be preferable to `\\[py-execute (let ((file (buffer-file-name (current-buffer)))) (if file (progn - ;; Maybe save some buffers - (save-some-buffers (not py-ask-about-save) nil) + ;; Maybe save some buffers + (save-some-buffers (not py-ask-about-save) nil) (py-execute-string (if (string-match "\\.py$" file) (let ((f (file-name-sans-extension - (file-name-nondirectory file)))) + (file-name-nondirectory file)))) (format "if globals().has_key('%s'):\n reload(%s)\nelse:\n import %s\n" f f f)) (format "execfile(r'%s')\n" file)) @@ -1883,25 +1897,25 @@ subtleties, including the use of the opt (defun py-jump-to-exception (file line) "Jump to the Python code in FILE at LINE." (let ((buffer (cond ((string-equal file "") - (if (consp py-exception-buffer) - (cdr py-exception-buffer) - py-exception-buffer)) - ((and (consp py-exception-buffer) - (string-equal file (car py-exception-buffer))) - (cdr py-exception-buffer)) - ((py-safe (find-file-noselect file))) - ;; could not figure out what file the exception - ;; is pointing to, so prompt for it - (t (find-file (read-file-name "Exception file: " - nil - file t)))))) + (if (consp py-exception-buffer) + (cdr py-exception-buffer) + py-exception-buffer)) + ((and (consp py-exception-buffer) + (string-equal file (car py-exception-buffer))) + (cdr py-exception-buffer)) + ((py-safe (find-file-noselect file))) + ;; could not figure out what file the exception + ;; is pointing to, so prompt for it + (t (find-file (read-file-name "Exception file: " + nil + file t)))))) ;; Fiddle about with line number (setq line (+ py-line-number-offset line)) (pop-to-buffer buffer) ;; Force Python mode (if (not (eq major-mode 'python-mode)) - (python-mode)) + (python-mode)) (goto-line line) (message "Jumping to exception in file %s on line %d" file line))) @@ -1913,12 +1927,12 @@ EVENT is usually a mouse click." ((fboundp 'event-point) ;; XEmacs (let* ((point (event-point event)) - (buffer (event-buffer event)) - (e (and point buffer (extent-at point buffer 'py-exc-info))) - (info (and e (extent-property e 'py-exc-info)))) + (buffer (event-buffer event)) + (e (and point buffer (extent-at point buffer 'py-exc-info))) + (info (and e (extent-property e 'py-exc-info)))) (message "Event point: %d, info: %s" point info) (and info - (py-jump-to-exception (car info) (cdr info))) + (py-jump-to-exception (car info) (cdr info))) )) ;; Emacs -- Please port this! )) @@ -1930,10 +1944,10 @@ EVENT is usually a mouse click." (save-excursion (beginning-of-line) (if (looking-at py-traceback-line-re) - (setq file (match-string 1) - line (string-to-int (match-string 2))))) + (setq file (match-string 1) + line (string-to-number (match-string 2))))) (if (not file) - (error "Not on a traceback line")) + (error "Not on a traceback line")) (py-jump-to-exception file line))) (defun py-find-next-exception (start buffer searchdir errwhere) @@ -1948,10 +1962,10 @@ bottom) of the trackback stack is encoun (set-buffer buffer) (goto-char (py-point start)) (if (funcall searchdir py-traceback-line-re nil t) - (setq file (match-string 1) - line (string-to-int (match-string 2))))) + (setq file (match-string 1) + line (string-to-number (match-string 2))))) (if (and file line) - (py-jump-to-exception file line) + (py-jump-to-exception file line) (error "%s of traceback" errwhere)))) (defun py-down-exception (&optional bottom) @@ -1961,9 +1975,9 @@ BOTTOM), jump to the bottom (innermost) stack." (interactive "P") (let* ((proc (get-process "Python")) - (buffer (if proc "*Python*" py-output-buffer))) + (buffer (if proc "*Python*" py-output-buffer))) (if bottom - (py-find-next-exception 'eob buffer 're-search-backward "Bottom") + (py-find-next-exception 'eob buffer 're-search-backward "Bottom") (py-find-next-exception 'eol buffer 're-search-forward "Bottom")))) (defun py-up-exception (&optional top) @@ -1972,9 +1986,9 @@ With \\[universal-argument] (programmati jump to the top (outermost) exception in the exception stack." (interactive "P") (let* ((proc (get-process "Python")) - (buffer (if proc "*Python*" py-output-buffer))) + (buffer (if proc "*Python*" py-output-buffer))) (if top - (py-find-next-exception 'bob buffer 're-search-forward "Top") + (py-find-next-exception 'bob buffer 're-search-forward "Top") (py-find-next-exception 'bol buffer 're-search-backward "Top")))) @@ -2005,34 +2019,34 @@ blocks to dedent, or the number of chara above." (interactive "*p") (if (or (/= (current-indentation) (current-column)) - (bolp) - (py-continuation-line-p) -; (not py-honor-comment-indentation) -; (looking-at "#[^ \t\n]") ; non-indenting # - ) + (bolp) + (py-continuation-line-p) +; (not py-honor-comment-indentation) +; (looking-at "#[^ \t\n]") ; non-indenting # + ) (funcall py-backspace-function arg) ;; else indent the same as the colon line that opened the block ;; force non-blank so py-goto-block-up doesn't ignore it (insert-char ?* 1) (backward-char) - (let ((base-indent 0) ; indentation of base line - (base-text "") ; and text of base line - (base-found-p nil)) + (let ((base-indent 0) ; indentation of base line + (base-text "") ; and text of base line + (base-found-p nil)) (save-excursion - (while (< 0 arg) - (condition-case nil ; in case no enclosing block - (progn - (py-goto-block-up 'no-mark) - (setq base-indent (current-indentation) - base-text (py-suck-up-leading-text) - base-found-p t)) - (error nil)) - (setq arg (1- arg)))) - (delete-char 1) ; toss the dummy character + (while (< 0 arg) + (condition-case nil ; in case no enclosing block + (progn + (py-goto-block-up 'no-mark) + (setq base-indent (current-indentation) + base-text (py-suck-up-leading-text) + base-found-p t)) + (error nil)) + (setq arg (1- arg)))) + (delete-char 1) ; toss the dummy character (delete-horizontal-space) (indent-to base-indent) (if base-found-p - (message "Closes block: %s" base-text))))) + (message "Closes block: %s" base-text))))) (defun py-electric-delete (arg) @@ -2051,9 +2065,9 @@ function in `py-delete-function'. number of characters to delete (default is 1)." (interactive "*p") (if (or (and (fboundp 'delete-forward-p) ;XEmacs 21 - (delete-forward-p)) - (and (boundp 'delete-key-deletes-forward) ;XEmacs 20 - delete-key-deletes-forward)) + (delete-forward-p)) + (and (boundp 'delete-key-deletes-forward) ;XEmacs 20 + delete-key-deletes-forward)) (funcall py-delete-function arg) (py-electric-backspace arg))) @@ -2077,8 +2091,8 @@ This function is normally bound to `inde \\[indent-for-tab-command] will call it." (interactive "P") (let* ((ci (current-indentation)) - (move-to-indentation-p (<= (current-column) ci)) - (need (py-compute-indentation (not arg))) + (move-to-indentation-p (<= (current-column) ci)) + (need (py-compute-indentation (not arg))) (cc (current-column))) ;; dedent out a level if previous command was the same unless we're in ;; column 1 @@ -2089,18 +2103,18 @@ This function is normally bound to `inde (delete-horizontal-space) (indent-to (* (/ (- cc 1) py-indent-offset) py-indent-offset))) (progn - ;; see if we need to dedent - (if (py-outdent-p) - (setq need (- need py-indent-offset))) - (if (or py-tab-always-indent - move-to-indentation-p) - (progn (if (/= ci need) - (save-excursion - (beginning-of-line) - (delete-horizontal-space) - (indent-to need))) - (if move-to-indentation-p (back-to-indentation))) - (insert-tab)))))) + ;; see if we need to dedent + (if (py-outdent-p) + (setq need (- need py-indent-offset))) + (if (or py-tab-always-indent + move-to-indentation-p) + (progn (if (/= ci need) + (save-excursion + (beginning-of-line) + (delete-horizontal-space) + (indent-to need))) + (if move-to-indentation-p (back-to-indentation))) + (insert-tab)))))) (defun py-newline-and-indent () "Strives to act like the Emacs `newline-and-indent'. @@ -2110,8 +2124,8 @@ point, inserts a newline, and takes an e the new line indented." (interactive) (let ((ci (current-indentation))) - (if (< ci (current-column)) ; if point beyond indentation - (newline-and-indent) + (if (< ci (current-column)) ; if point beyond indentation + (newline-and-indent) ;; else try to act like newline-and-indent "normally" acts (beginning-of-line) (insert-char ?\n 1) @@ -2125,100 +2139,84 @@ dedenting." (save-excursion (beginning-of-line) (let* ((bod (py-point 'bod)) - (pps (parse-partial-sexp bod (point))) - (boipps (parse-partial-sexp bod (py-point 'boi))) - placeholder) + (pps (parse-partial-sexp bod (point))) + (boipps (parse-partial-sexp bod (py-point 'boi))) + placeholder) (cond ;; are we inside a multi-line string or comment? ((or (and (nth 3 pps) (nth 3 boipps)) - (and (nth 4 pps) (nth 4 boipps))) - (save-excursion - (if (not py-align-multiline-strings-p) 0 - ;; skip back over blank & non-indenting comment lines - ;; note: will skip a blank or non-indenting comment line - ;; that happens to be a continuation line too - (re-search-backward "^[ \t]*\\([^ \t\n#]\\|#[ \t\n]\\)" nil 'move) - (back-to-indentation) - (current-column)))) + (and (nth 4 pps) (nth 4 boipps))) + (save-excursion + (if (not py-align-multiline-strings-p) 0 + ;; skip back over blank & non-indenting comment lines + ;; note: will skip a blank or non-indenting comment line + ;; that happens to be a continuation line too + (re-search-backward "^[ \t]*\\([^ \t\n#]\\|#[ \t\n]\\)" nil 'move) + (back-to-indentation) + (current-column)))) ;; are we on a continuation line? ((py-continuation-line-p) - (let ((startpos (point)) - (open-bracket-pos (py-nesting-level)) - endpos searching found state) - (if open-bracket-pos - (progn - ;; align with first item in list; else a normal - ;; indent beyond the line with the open bracket - (goto-char (1+ open-bracket-pos)) ; just beyond bracket - ;; is the first list item on the same line? - (skip-chars-forward " \t") - (if (null (memq (following-char) '(?\n ?# ?\\))) - ; yes, so line up with it - (current-column) - ;; first list item on another line, or doesn't exist yet - (forward-line 1) - (while (and (< (point) startpos) - (looking-at "[ \t]*[#\n\\\\]")) ; skip noise - (forward-line 1)) - (if (and (< (point) startpos) - (/= startpos - (save-excursion - (goto-char (1+ open-bracket-pos)) - (forward-comment (point-max)) - (point)))) - ;; again mimic the first list item - (current-indentation) - ;; else they're about to enter the first item - (goto-char open-bracket-pos) - (setq placeholder (point)) - (py-goto-initial-line) - (py-goto-beginning-of-tqs - (save-excursion (nth 3 (parse-partial-sexp - placeholder (point))))) - (+ (current-indentation) py-indent-offset)))) - - ;; else on backslash continuation line - (forward-line -1) - (if (py-continuation-line-p) ; on at least 3rd line in block - (current-indentation) ; so just continue the pattern - ;; else started on 2nd line in block, so indent more. - ;; if base line is an assignment with a start on a RHS, - ;; indent to 2 beyond the leftmost "="; else skip first - ;; chunk of non-whitespace characters on base line, + 1 more - ;; column - (end-of-line) - (setq endpos (point) - searching t) - (back-to-indentation) - (setq startpos (point)) - ;; look at all "=" from left to right, stopping at first - ;; one not nested in a list or string - (while searching - (skip-chars-forward "^=" endpos) - (if (= (point) endpos) - (setq searching nil) - (forward-char 1) - (setq state (parse-partial-sexp startpos (point))) - (if (and (zerop (car state)) ; not in a bracket - (null (nth 3 state))) ; & not in a string - (progn - (setq searching nil) ; done searching in any case - (setq found - (not (or - (eq (following-char) ?=) - (memq (char-after (- (point) 2)) - '(?< ?> ?!))))))))) - (if (or (not found) ; not an assignment - (looking-at "[ \t]*\\\\")) ; <=> - (progn - (goto-char startpos) - (skip-chars-forward "^ \t\n"))) - ;; if this is a continuation for a block opening - ;; statement, add some extra offset. - (+ (current-column) (if (py-statement-opens-block-p) - py-continuation-offset 0) - 1) - )))) + (let ((startpos (point)) + (open-bracket-pos (py-nesting-level)) + endpos searching found state cind cline) + (if open-bracket-pos + (progn + (setq endpos (py-point 'bol)) + (py-goto-initial-line) + (setq cind (current-indentation)) + (setq cline cind) + (dolist (bp + (nth 9 (save-excursion + (parse-partial-sexp (point) endpos))) + cind) + (if (search-forward "\n" bp t) (setq cline cind)) + (goto-char (1+ bp)) + (skip-chars-forward " \t") + (setq cind (if (memq (following-char) '(?\n ?# ?\\)) + (+ cline py-indent-offset) + (current-column))))) + ;; else on backslash continuation line + (forward-line -1) + (if (py-continuation-line-p) ; on at least 3rd line in block + (current-indentation) ; so just continue the pattern + ;; else started on 2nd line in block, so indent more. + ;; if base line is an assignment with a start on a RHS, + ;; indent to 2 beyond the leftmost "="; else skip first + ;; chunk of non-whitespace characters on base line, + 1 more + ;; column + (end-of-line) + (setq endpos (point) + searching t) + (back-to-indentation) + (setq startpos (point)) + ;; look at all "=" from left to right, stopping at first + ;; one not nested in a list or string + (while searching + (skip-chars-forward "^=" endpos) + (if (= (point) endpos) + (setq searching nil) + (forward-char 1) + (setq state (parse-partial-sexp startpos (point))) + (if (and (zerop (car state)) ; not in a bracket + (null (nth 3 state))) ; & not in a string + (progn + (setq searching nil) ; done searching in any case + (setq found + (not (or + (eq (following-char) ?=) + (memq (char-after (- (point) 2)) + '(?< ?> ?!))))))))) + (if (or (not found) ; not an assignment + (looking-at "[ \t]*\\\\")) ; <=> + (progn + (goto-char startpos) + (skip-chars-forward "^ \t\n"))) + ;; if this is a continuation for a block opening + ;; statement, add some extra offset. + (+ (current-column) (if (py-statement-opens-block-p) + py-continuation-offset 0) + 1) + )))) ;; not on a continuation line ((bobp) (current-indentation)) @@ -2248,65 +2246,65 @@ dedenting." ;; Indenting comment lines are aligned as statements down ;; below. ((and (looking-at "[ \t]*#[^ \t\n]") - ;; NOTE: this test will not be performed in older Emacsen - (fboundp 'forward-comment) - (<= (current-indentation) - (save-excursion - (forward-comment (- (point-max))) - (current-indentation)))) - (current-indentation)) + ;; NOTE: this test will not be performed in older Emacsen + (fboundp 'forward-comment) + (<= (current-indentation) + (save-excursion + (forward-comment (- (point-max))) + (current-indentation)))) + (current-indentation)) ;; else indentation based on that of the statement that ;; precedes us; use the first line of that statement to ;; establish the base, in case the user forced a non-std ;; indentation for the continuation lines (if any) (t - ;; skip back over blank & non-indenting comment lines note: - ;; will skip a blank or non-indenting comment line that - ;; happens to be a continuation line too. use fast Emacs 19 - ;; function if it's there. - (if (and (eq py-honor-comment-indentation nil) - (fboundp 'forward-comment)) - (forward-comment (- (point-max))) - (let ((prefix-re (concat py-block-comment-prefix "[ \t]*")) - done) - (while (not done) - (re-search-backward "^[ \t]*\\([^ \t\n#]\\|#\\)" nil 'move) - (setq done (or (bobp) - (and (eq py-honor-comment-indentation t) - (save-excursion - (back-to-indentation) - (not (looking-at prefix-re)) - )) - (and (not (eq py-honor-comment-indentation t)) - (save-excursion - (back-to-indentation) - (and (not (looking-at prefix-re)) - (or (looking-at "[^#]") - (not (zerop (current-column))) - )) - )) - )) - ))) - ;; if we landed inside a string, go to the beginning of that - ;; string. this handles triple quoted, multi-line spanning - ;; strings. - (py-goto-beginning-of-tqs (nth 3 (parse-partial-sexp bod (point)))) - ;; now skip backward over continued lines - (setq placeholder (point)) - (py-goto-initial-line) - ;; we may *now* have landed in a TQS, so find the beginning of - ;; this string. - (py-goto-beginning-of-tqs - (save-excursion (nth 3 (parse-partial-sexp - placeholder (point))))) - (+ (current-indentation) - (if (py-statement-opens-block-p) - py-indent-offset - (if (and honor-block-close-p (py-statement-closes-block-p)) - (- py-indent-offset) - 0))) - ))))) + ;; skip back over blank & non-indenting comment lines note: + ;; will skip a blank or non-indenting comment line that + ;; happens to be a continuation line too. use fast Emacs 19 + ;; function if it's there. + (if (and (eq py-honor-comment-indentation nil) + (fboundp 'forward-comment)) + (forward-comment (- (point-max))) + (let ((prefix-re (concat py-block-comment-prefix "[ \t]*")) + done) + (while (not done) + (re-search-backward "^[ \t]*\\([^ \t\n#]\\|#\\)" nil 'move) + (setq done (or (bobp) + (and (eq py-honor-comment-indentation t) + (save-excursion + (back-to-indentation) + (not (looking-at prefix-re)) + )) + (and (not (eq py-honor-comment-indentation t)) + (save-excursion + (back-to-indentation) + (and (not (looking-at prefix-re)) + (or (looking-at "[^#]") + (not (zerop (current-column))) + )) + )) + )) + ))) + ;; if we landed inside a string, go to the beginning of that + ;; string. this handles triple quoted, multi-line spanning + ;; strings. + (py-goto-beginning-of-tqs (nth 3 (parse-partial-sexp bod (point)))) + ;; now skip backward over continued lines + (setq placeholder (point)) + (py-goto-initial-line) + ;; we may *now* have landed in a TQS, so find the beginning of + ;; this string. + (py-goto-beginning-of-tqs + (save-excursion (nth 3 (parse-partial-sexp + placeholder (point))))) + (+ (current-indentation) + (if (py-statement-opens-block-p) + py-indent-offset + (if (and honor-block-close-p (py-statement-closes-block-p)) + (- py-indent-offset) + 0))) + ))))) (defun py-guess-indent-offset (&optional global) "Guess a good value for, and change, `py-indent-offset'. @@ -2329,42 +2327,42 @@ looking for a line that opens a block of set to the difference in indentation between that line and the Python statement following it. If the search doesn't succeed going forward, it's tried again going backward." - (interactive "P") ; raw prefix arg + (interactive "P") ; raw prefix arg (let (new-value - (start (point)) - (restart (point)) - (found nil) - colon-indent) + (start (point)) + (restart (point)) + (found nil) + colon-indent) (py-goto-initial-line) (while (not (or found (eobp))) (when (and (re-search-forward ":[ \t]*\\($\\|[#\\]\\)" nil 'move) - (not (py-in-literal restart))) - (setq restart (point)) - (py-goto-initial-line) - (if (py-statement-opens-block-p) - (setq found t) - (goto-char restart)))) + (not (py-in-literal restart))) + (setq restart (point)) + (py-goto-initial-line) + (if (py-statement-opens-block-p) + (setq found t) + (goto-char restart)))) (unless found (goto-char start) (py-goto-initial-line) (while (not (or found (bobp))) - (setq found (and - (re-search-backward ":[ \t]*\\($\\|[#\\]\\)" nil 'move) - (or (py-goto-initial-line) t) ; always true -- side effect - (py-statement-opens-block-p))))) + (setq found (and + (re-search-backward ":[ \t]*\\($\\|[#\\]\\)" nil 'move) + (or (py-goto-initial-line) t) ; always true -- side effect + (py-statement-opens-block-p))))) (setq colon-indent (current-indentation) - found (and found (zerop (py-next-statement 1))) - new-value (- (current-indentation) colon-indent)) + found (and found (zerop (py-next-statement 1))) + new-value (- (current-indentation) colon-indent)) (goto-char start) (if (not found) - (error "Sorry, couldn't guess a value for py-indent-offset") + (error "Sorry, couldn't guess a value for py-indent-offset") (funcall (if global 'kill-local-variable 'make-local-variable) - 'py-indent-offset) + 'py-indent-offset) (setq py-indent-offset new-value) (or noninteractive - (message "%s value of py-indent-offset set to %d" - (if global "Global" "Local") - py-indent-offset))) + (message "%s value of py-indent-offset set to %d" + (if global "Global" "Local") + py-indent-offset))) )) (defun py-comment-indent-function () @@ -2376,8 +2374,8 @@ it's tried again going backward." (beginning-of-line) (let ((eol (py-point 'eol))) (and comment-start-skip - (re-search-forward comment-start-skip eol t) - (setq eol (match-beginning 0))) + (re-search-forward comment-start-skip eol t) + (setq eol (match-beginning 0))) (goto-char eol) (skip-chars-backward " \t") (max comment-column (+ (current-column) (if (bolp) 0 1))) @@ -2405,7 +2403,8 @@ Optional CLASS is passed directly to `py (goto-char start) (beginning-of-line) (setq start (point)) - (indent-rigidly start end count))) + (let (deactivate-mark) + (indent-rigidly start end count)))) (defun py-shift-region-left (start end &optional count) "Shift region of Python code to the left. @@ -2418,10 +2417,10 @@ many columns. With no active region, de You cannot dedent the region if any line is already at column zero." (interactive (let ((p (point)) - (m (mark)) - (arg current-prefix-arg)) + (m (condition-case nil (mark) (mark-inactive nil))) + (arg current-prefix-arg)) (if m - (list (min p m) (max p m) arg) + (list (min p m) (max p m) arg) (list p (save-excursion (forward-line 1) (point)) arg)))) ;; if any line is at column zero, don't shift the region (save-excursion @@ -2429,11 +2428,11 @@ You cannot dedent the region if any line (while (< (point) end) (back-to-indentation) (if (and (zerop (current-column)) - (not (looking-at "\\s *$"))) - (error "Region is at left edge")) + (not (looking-at "\\s *$"))) + (error "Region is at left edge")) (forward-line 1))) (py-shift-region start end (- (prefix-numeric-value - (or count py-indent-offset)))) + (or count py-indent-offset)))) (py-keep-region-active)) (defun py-shift-region-right (start end &optional count) @@ -2446,13 +2445,13 @@ If a prefix argument is given, the regio many columns. With no active region, indent only the current line." (interactive (let ((p (point)) - (m (mark)) - (arg current-prefix-arg)) + (m (condition-case nil (mark) (mark-inactive nil))) + (arg current-prefix-arg)) (if m - (list (min p m) (max p m) arg) + (list (min p m) (max p m) arg) (list p (save-excursion (forward-line 1) (point)) arg)))) (py-shift-region start end (prefix-numeric-value - (or count py-indent-offset))) + (or count py-indent-offset))) (py-keep-region-active)) (defun py-indent-region (start end &optional indent-offset) @@ -2487,48 +2486,48 @@ Special cases: whitespace is deleted fro lines are shifted by the same amount their initial line was shifted, in order to preserve their relative indentation with respect to their initial line; and comment lines beginning in column 1 are ignored." - (interactive "*r\nP") ; region; raw prefix arg + (interactive "*r\nP") ; region; raw prefix arg (save-excursion (goto-char end) (beginning-of-line) (setq end (point-marker)) (goto-char start) (beginning-of-line) (let ((py-indent-offset (prefix-numeric-value - (or indent-offset py-indent-offset))) - (indents '(-1)) ; stack of active indent levels - (target-column 0) ; column to which to indent - (base-shifted-by 0) ; amount last base line was shifted - (indent-base (if (looking-at "[ \t\n]") - (py-compute-indentation t) - 0)) - ci) + (or indent-offset py-indent-offset))) + (indents '(-1)) ; stack of active indent levels + (target-column 0) ; column to which to indent + (base-shifted-by 0) ; amount last base line was shifted + (indent-base (if (looking-at "[ \t\n]") + (py-compute-indentation t) + 0)) + ci) (while (< (point) end) - (setq ci (current-indentation)) - ;; figure out appropriate target column - (cond - ((or (eq (following-char) ?#) ; comment in column 1 - (looking-at "[ \t]*$")) ; entirely blank - (setq target-column 0)) - ((py-continuation-line-p) ; shift relative to base line - (setq target-column (+ ci base-shifted-by))) - (t ; new base line - (if (> ci (car indents)) ; going deeper; push it - (setq indents (cons ci indents)) - ;; else we should have seen this indent before - (setq indents (memq ci indents)) ; pop deeper indents - (if (null indents) - (error "Bad indentation in region, at line %d" - (save-restriction - (widen) - (1+ (count-lines 1 (point))))))) - (setq target-column (+ indent-base - (* py-indent-offset - (- (length indents) 2)))) - (setq base-shifted-by (- target-column ci)))) - ;; shift as needed - (if (/= ci target-column) - (progn - (delete-horizontal-space) - (indent-to target-column))) - (forward-line 1)))) + (setq ci (current-indentation)) + ;; figure out appropriate target column + (cond + ((or (eq (following-char) ?#) ; comment in column 1 + (looking-at "[ \t]*$")) ; entirely blank + (setq target-column 0)) + ((py-continuation-line-p) ; shift relative to base line + (setq target-column (+ ci base-shifted-by))) + (t ; new base line + (if (> ci (car indents)) ; going deeper; push it + (setq indents (cons ci indents)) + ;; else we should have seen this indent before + (setq indents (memq ci indents)) ; pop deeper indents + (if (null indents) + (error "Bad indentation in region, at line %d" + (save-restriction + (widen) + (1+ (count-lines 1 (point))))))) + (setq target-column (+ indent-base + (* py-indent-offset + (- (length indents) 2)))) + (setq base-shifted-by (- target-column ci)))) + ;; shift as needed + (if (/= ci target-column) + (progn + (delete-horizontal-space) + (indent-to target-column))) + (forward-line 1)))) (set-marker end nil)) (defun py-comment-region (beg end &optional arg) @@ -2537,6 +2536,52 @@ initial line; and comment lines beginnin (let ((comment-start py-block-comment-prefix)) (comment-region beg end arg))) +(defun py-join-words-wrapping (words separator line-prefix line-length) + (let ((lines ()) + (current-line line-prefix)) + (while words + (let* ((word (car words)) + (maybe-line (concat current-line word separator))) + (if (> (length maybe-line) line-length) + (setq lines (cons (substring current-line 0 -1) lines) + current-line (concat line-prefix word separator " ")) + (setq current-line (concat maybe-line " ")))) + (setq words (cdr words))) + (setq lines (cons (substring + current-line 0 (- 0 (length separator) 1)) lines)) + (mapconcat 'identity (nreverse lines) "\n"))) + +(defun py-sort-imports () + "Sort multiline imports. +Put point inside the parentheses of a multiline import and hit +\\[py-sort-imports] to sort the imports lexicographically" + (interactive) + (save-excursion + (let ((open-paren (save-excursion (progn (up-list -1) (point)))) + (close-paren (save-excursion (progn (up-list 1) (point)))) + sorted-imports) + (goto-char (1+ open-paren)) + (skip-chars-forward " \n\t") + (setq sorted-imports + (sort + (delete-dups + (split-string (buffer-substring + (point) + (save-excursion (goto-char (1- close-paren)) + (skip-chars-backward " \n\t") + (point))) + ", *\\(\n *\\)?")) + ;; XXX Should this sort case insensitively? + 'string-lessp)) + ;; Remove empty strings. + (delete-region open-paren close-paren) + (goto-char open-paren) + (insert "(\n") + (insert (py-join-words-wrapping (remove "" sorted-imports) "," " " 78)) + (insert ")") + ))) + + ;; Functions for moving point (defun py-previous-statement (count) @@ -2545,16 +2590,16 @@ By default, goes to the previous stateme statement, goes to the first statement. Return count of statements left to move. `Statements' do not include blank, comment, or continuation lines." - (interactive "p") ; numeric prefix arg + (interactive "p") ; numeric prefix arg (if (< count 0) (py-next-statement (- count)) (py-goto-initial-line) (let (start) (while (and - (setq start (point)) ; always true -- side effect - (> count 0) - (zerop (forward-line -1)) - (py-goto-statement-at-or-above)) - (setq count (1- count))) + (setq start (point)) ; always true -- side effect + (> count 0) + (zerop (forward-line -1)) + (py-goto-statement-at-or-above)) + (setq count (1- count))) (if (> count 0) (goto-char start))) count)) @@ -2564,15 +2609,15 @@ If the statement at point is the i'th Py start of statement i+COUNT. If there is no such statement, goes to the last statement. Returns count of statements left to move. `Statements' do not include blank, comment, or continuation lines." - (interactive "p") ; numeric prefix arg + (interactive "p") ; numeric prefix arg (if (< count 0) (py-previous-statement (- count)) (beginning-of-line) (let (start) (while (and - (setq start (point)) ; always true -- side effect - (> count 0) - (py-goto-statement-below)) - (setq count (1- count))) + (setq start (point)) ; always true -- side effect + (> count 0) + (py-goto-statement-below)) + (setq count (1- count))) (if (> count 0) (goto-char start))) count)) @@ -2590,30 +2635,30 @@ If called from a program, the mark will NOMARK is not nil." (interactive) (let ((start (point)) - (found nil) - initial-indent) + (found nil) + initial-indent) (py-goto-initial-line) ;; if on blank or non-indenting comment line, use the preceding stmt (if (looking-at "[ \t]*\\($\\|#[^ \t\n]\\)") - (progn - (py-goto-statement-at-or-above) - (setq found (py-statement-opens-block-p)))) + (progn + (py-goto-statement-at-or-above) + (setq found (py-statement-opens-block-p)))) ;; search back for colon line indented less (setq initial-indent (current-indentation)) (if (zerop initial-indent) - ;; force fast exit - (goto-char (point-min))) + ;; force fast exit + (goto-char (point-min))) (while (not (or found (bobp))) (setq found - (and - (re-search-backward ":[ \t]*\\($\\|[#\\]\\)" nil 'move) - (or (py-goto-initial-line) t) ; always true -- side effect - (< (current-indentation) initial-indent) - (py-statement-opens-block-p)))) + (and + (re-search-backward ":[ \t]*\\($\\|[#\\]\\)" nil 'move) + (or (py-goto-initial-line) t) ; always true -- side effect + (< (current-indentation) initial-indent) + (py-statement-opens-block-p)))) (if found - (progn - (or nomark (push-mark start)) - (back-to-indentation)) + (progn + (or nomark (push-mark start)) + (back-to-indentation)) (goto-char start) (error "Enclosing block not found")))) @@ -2643,27 +2688,27 @@ Note that doing this command repeatedly start of the buffer each time. To mark the current `def', see `\\[py-mark-def-or-class]'." - (interactive "P") ; raw prefix arg + (interactive "P") ; raw prefix arg (setq count (or count 1)) (let ((at-or-before-p (<= (current-column) (current-indentation))) - (start-of-line (goto-char (py-point 'bol))) - (start-of-stmt (goto-char (py-point 'bos))) - (start-re (cond ((eq class 'either) "^[ \t]*\\(class\\|def\\)\\>") - (class "^[ \t]*class\\>") - (t "^[ \t]*def\\>"))) - ) + (start-of-line (goto-char (py-point 'bol))) + (start-of-stmt (goto-char (py-point 'bos))) + (start-re (cond ((eq class 'either) "^[ \t]*\\(class\\|def\\)\\>") + (class "^[ \t]*class\\>") + (t "^[ \t]*def\\>"))) + ) ;; searching backward (if (and (< 0 count) - (or (/= start-of-stmt start-of-line) - (not at-or-before-p))) - (end-of-line)) + (or (/= start-of-stmt start-of-line) + (not at-or-before-p))) + (end-of-line)) ;; search forward (if (and (> 0 count) - (zerop (current-column)) - (looking-at start-re)) - (end-of-line)) + (zerop (current-column)) + (looking-at start-re)) + (end-of-line)) (if (re-search-backward start-re nil 'move count) - (goto-char (match-beginning 0))))) + (goto-char (match-beginning 0))))) ;; Backwards compatibility (defalias 'beginning-of-python-def-or-class 'py-beginning-of-def-or-class) @@ -2697,27 +2742,27 @@ Note that doing this command repeatedly end of the buffer each time. To mark the current `def', see `\\[py-mark-def-or-class]'." - (interactive "P") ; raw prefix arg + (interactive "P") ; raw prefix arg (if (and count (/= count 1)) (py-beginning-of-def-or-class (- 1 count))) (let ((start (progn (py-goto-initial-line) (point))) - (which (cond ((eq class 'either) "\\(class\\|def\\)") - (class "class") - (t "def"))) - (state 'not-found)) + (which (cond ((eq class 'either) "\\(class\\|def\\)") + (class "class") + (t "def"))) + (state 'not-found)) ;; move point to start of appropriate def/class (if (looking-at (concat "[ \t]*" which "\\>")) ; already on one - (setq state 'at-beginning) + (setq state 'at-beginning) ;; else see if py-beginning-of-def-or-class hits container (if (and (py-beginning-of-def-or-class class) - (progn (py-goto-beyond-block) - (> (point) start))) - (setq state 'at-end) - ;; else search forward - (goto-char start) - (if (re-search-forward (concat "^[ \t]*" which "\\>") nil 'move) - (progn (setq state 'at-beginning) - (beginning-of-line))))) + (progn (py-goto-beyond-block) + (> (point) start))) + (setq state 'at-end) + ;; else search forward + (goto-char start) + (if (re-search-forward (concat "^[ \t]*" which "\\>") nil 'move) + (progn (setq state 'at-beginning) + (beginning-of-line))))) (cond ((eq state 'at-beginning) (py-goto-beyond-block) t) ((eq state 'at-end) t) @@ -2775,66 +2820,66 @@ area; or do `\\[exchange-point-and-mark] If called from a program, optional argument EXTEND plays the role of the prefix arg, and if optional argument JUST-MOVE is not nil, just moves to the end of the block (& does not set mark or display a msg)." - (interactive "P") ; raw prefix arg + (interactive "P") ; raw prefix arg (py-goto-initial-line) ;; skip over blank lines (while (and - (looking-at "[ \t]*$") ; while blank line - (not (eobp))) ; & somewhere to go + (looking-at "[ \t]*$") ; while blank line + (not (eobp))) ; & somewhere to go (forward-line 1)) (if (eobp) (error "Hit end of buffer without finding a non-blank stmt")) (let ((initial-pos (point)) - (initial-indent (current-indentation)) - last-pos ; position of last stmt in region - (followers - '((if elif else) (elif elif else) (else) - (try except finally) (except except) (finally) - (for else) (while else) - (def) (class) ) ) - first-symbol next-symbol) + (initial-indent (current-indentation)) + last-pos ; position of last stmt in region + (followers + '((if elif else) (elif elif else) (else) + (try except finally) (except except) (finally) + (for else) (while else) + (def) (class) ) ) + first-symbol next-symbol) (cond ;; if comment line, suck up the following comment lines ((looking-at "[ \t]*#") (re-search-forward "^[ \t]*[^ \t#]" nil 'move) ; look for non-comment - (re-search-backward "^[ \t]*#") ; and back to last comment in block + (re-search-backward "^[ \t]*#") ; and back to last comment in block (setq last-pos (point))) ;; else if line is a block line and EXTEND given, suck up ;; the whole structure ((and extend - (setq first-symbol (py-suck-up-first-keyword) ) - (assq first-symbol followers)) + (setq first-symbol (py-suck-up-first-keyword) ) + (assq first-symbol followers)) (while (and - (or (py-goto-beyond-block) t) ; side effect - (forward-line -1) ; side effect - (setq last-pos (point)) ; side effect - (py-goto-statement-below) - (= (current-indentation) initial-indent) - (setq next-symbol (py-suck-up-first-keyword)) - (memq next-symbol (cdr (assq first-symbol followers)))) - (setq first-symbol next-symbol))) + (or (py-goto-beyond-block) t) ; side effect + (forward-line -1) ; side effect + (setq last-pos (point)) ; side effect + (py-goto-statement-below) + (= (current-indentation) initial-indent) + (setq next-symbol (py-suck-up-first-keyword)) + (memq next-symbol (cdr (assq first-symbol followers)))) + (setq first-symbol next-symbol))) ;; else if line *opens* a block, search for next stmt indented <= ((py-statement-opens-block-p) (while (and - (setq last-pos (point)) ; always true -- side effect - (py-goto-statement-below) - (> (current-indentation) initial-indent) - ))) + (setq last-pos (point)) ; always true -- side effect + (py-goto-statement-below) + (> (current-indentation) initial-indent) + ))) ;; else plain code line; stop at next blank line, or stmt or ;; indenting comment line indented < (t (while (and - (setq last-pos (point)) ; always true -- side effect - (or (py-goto-beyond-final-line) t) - (not (looking-at "[ \t]*$")) ; stop at blank line - (or - (>= (current-indentation) initial-indent) - (looking-at "[ \t]*#[^ \t\n]"))) ; ignore non-indenting # - nil))) + (setq last-pos (point)) ; always true -- side effect + (or (py-goto-beyond-final-line) t) + (not (looking-at "[ \t]*$")) ; stop at blank line + (or + (>= (current-indentation) initial-indent) + (looking-at "[ \t]*#[^ \t\n]"))) ; ignore non-indenting # + nil))) ;; skip to end of last stmt (goto-char last-pos) @@ -2842,7 +2887,7 @@ moves to the end of the block (& does no ;; set mark & display (if just-move - () ; just return + () ; just return (push-mark (point) 'no-msg) (forward-line -1) (message "Mark set after: %s" (py-suck-up-leading-text)) @@ -2886,38 +2931,38 @@ point is left at its start. The intent is to mark the containing def/class and its associated documentation, to make moving and duplicating functions and classes pleasant." - (interactive "P") ; raw prefix arg + (interactive "P") ; raw prefix arg (let ((start (point)) - (which (cond ((eq class 'either) "\\(class\\|def\\)") - (class "class") - (t "def")))) + (which (cond ((eq class 'either) "\\(class\\|def\\)") + (class "class") + (t "def")))) (push-mark start) (if (not (py-go-up-tree-to-keyword which)) - (progn (goto-char start) - (error "Enclosing %s not found" - (if (eq class 'either) - "def or class" - which))) + (progn (goto-char start) + (error "Enclosing %s not found" + (if (eq class 'either) + "def or class" + which))) ;; else enclosing def/class found (setq start (point)) (py-goto-beyond-block) (push-mark (point)) (goto-char start) - (if (zerop (forward-line -1)) ; if there is a preceding line - (progn - (if (looking-at "[ \t]*$") ; it's blank - (setq start (point)) ; so reset start point - (goto-char start)) ; else try again - (if (zerop (forward-line -1)) - (if (looking-at "[ \t]*#") ; a comment - ;; look back for non-comment line - ;; tricky: note that the regexp matches a blank - ;; line, cuz \n is in the 2nd character class - (and - (re-search-backward "^[ \t]*[^ \t#]" nil 'move) - (forward-line 1)) - ;; no comment, so go back - (goto-char start))))))) + (if (zerop (forward-line -1)) ; if there is a preceding line + (progn + (if (looking-at "[ \t]*$") ; it's blank + (setq start (point)) ; so reset start point + (goto-char start)) ; else try again + (if (zerop (forward-line -1)) + (if (looking-at "[ \t]*#") ; a comment + ;; look back for non-comment line + ;; tricky: note that the regexp matches a blank + ;; line, cuz \n is in the 2nd character class + (and + (re-search-backward "^[ \t]*[^ \t#]" nil 'move) + (forward-line 1)) + ;; no comment, so go back + (goto-char start))))))) (exchange-point-and-mark) (py-keep-region-active)) @@ -2931,15 +2976,15 @@ A `nomenclature' is a fancy way of sayin (interactive "p") (let ((case-fold-search nil)) (if (> arg 0) - (re-search-forward - "\\(\\W\\|[_]\\)*\\([A-Z]*[a-z0-9]*\\)" - (point-max) t arg) + (re-search-forward + "\\(\\W\\|[_]\\)*\\([A-Z]*[a-z0-9]*\\)" + (point-max) t arg) (while (and (< arg 0) - (re-search-backward - "\\(\\W\\|[a-z0-9]\\)[A-Z]+\\|\\(\\W\\|[_]\\)\\w+" - (point-min) 0)) - (forward-char 1) - (setq arg (1+ arg))))) + (re-search-backward + "\\(\\W\\|[a-z0-9]\\)[A-Z]+\\|\\(\\W\\|[_]\\)\\w+" + (point-min) 0)) + (forward-char 1) + (setq arg (1+ arg))))) (py-keep-region-active)) (defun py-backward-into-nomenclature (&optional arg) @@ -2961,7 +3006,7 @@ A `nomenclature' is a fancy way of sayin (error "No process associated with buffer '%s'" (current-buffer))) ;; missing or 0 is toggle, >0 turn on, <0 turn off (if (or (not arg) - (zerop (setq arg (prefix-numeric-value arg)))) + (zerop (setq arg (prefix-numeric-value arg)))) (setq py-pdbtrack-do-tracking-p (not py-pdbtrack-do-tracking-p)) (setq py-pdbtrack-do-tracking-p (> arg 0))) (message "%sabled Python's pdbtrack" @@ -2988,29 +3033,33 @@ A `nomenclature' is a fancy way of sayin (interactive (let ((default (format "%s %s %s" py-pychecker-command - (mapconcat 'identity py-pychecker-command-args " ") - (buffer-file-name))) - (last (when py-pychecker-history - (let* ((lastcmd (car py-pychecker-history)) - (cmd (cdr (reverse (split-string lastcmd)))) - (newcmd (reverse (cons (buffer-file-name) cmd)))) - (mapconcat 'identity newcmd " "))))) + (mapconcat 'identity py-pychecker-command-args " ") + (buffer-file-name))) + (last (when py-pychecker-history + (let* ((lastcmd (car py-pychecker-history)) + (cmd (cdr (reverse (split-string lastcmd)))) + (newcmd (reverse (cons (buffer-file-name) cmd)))) + (mapconcat 'identity newcmd " "))))) (list (if (fboundp 'read-shell-command) - (read-shell-command "Run pychecker like this: " - (if last - last - default) - 'py-pychecker-history) - (read-string "Run pychecker like this: " - (if last - last - default) - 'py-pychecker-history)) - ))) + (read-shell-command "Run pychecker like this: " + (if last + last + default) + 'py-pychecker-history) + (read-string "Run pychecker like this: " + (if last + last + default) + 'py-pychecker-history)) + ))) (save-some-buffers (not py-ask-about-save) nil) - (compile-internal command "No more errors")) + (if (fboundp 'compilation-start) + ;; Emacs. + (compilation-start command) + ;; XEmacs. + (compile-internal command "No more errors"))) @@ -3026,31 +3075,31 @@ A `nomenclature' is a fancy way of sayin (save-excursion (with-syntax-table py-dotted-expression-syntax-table (if (or (bobp) (not (memq (char-syntax (char-before)) '(?w ?_)))) - (while (not (looking-at "\\sw\\|\\s_\\|\\'")) - (forward-char 1))) + (while (not (looking-at "\\sw\\|\\s_\\|\\'")) + (forward-char 1))) (while (looking-at "\\sw\\|\\s_") - (forward-char 1)) + (forward-char 1)) (if (re-search-backward "\\sw\\|\\s_" nil t) - (progn (forward-char 1) - (buffer-substring (point) - (progn (forward-sexp -1) - (while (looking-at "\\s'") - (forward-char 1)) - (point)))) - nil)))) + (progn (forward-char 1) + (buffer-substring (point) + (progn (forward-sexp -1) + (while (looking-at "\\s'") + (forward-char 1)) + (point)))) + nil)))) (defun py-help-at-point () "Get help from Python based on the symbol nearest point." (interactive) (let* ((sym (py-symbol-near-point)) - (base (substring sym 0 (or (search "." sym :from-end t) 0))) - cmd) + (base (substring sym 0 (or (search "." sym :from-end t) 0))) + cmd) (if (not (equal base "")) (setq cmd (concat "import " base "\n"))) (setq cmd (concat "import pydoc\n" cmd - "try: pydoc.help('" sym "')\n" - "except: print 'No help available on:', \"" sym "\"")) + "try: pydoc.help('" sym "')\n" + "except: print 'No help available on:', \"" sym "\"")) (message cmd) (py-execute-string cmd) (set-buffer "*Python Output*") @@ -3068,42 +3117,42 @@ A `nomenclature' is a fancy way of sayin (defun py-dump-help-string (str) (with-output-to-temp-buffer "*Help*" (let ((locals (buffer-local-variables)) - funckind funcname func funcdoc - (start 0) mstart end - keys ) + funckind funcname func funcdoc + (start 0) mstart end + keys ) (while (string-match "^%\\([vc]\\):\\(.+\\)\n" str start) - (setq mstart (match-beginning 0) end (match-end 0) - funckind (substring str (match-beginning 1) (match-end 1)) - funcname (substring str (match-beginning 2) (match-end 2)) - func (intern funcname)) - (princ (substitute-command-keys (substring str start mstart))) - (cond - ((equal funckind "c") ; command - (setq funcdoc (documentation func) - keys (concat - "Key(s): " - (mapconcat 'key-description - (where-is-internal func py-mode-map) - ", ")))) - ((equal funckind "v") ; variable - (setq funcdoc (documentation-property func 'variable-documentation) - keys (if (assq func locals) - (concat - "Local/Global values: " - (prin1-to-string (symbol-value func)) - " / " - (prin1-to-string (default-value func))) - (concat - "Value: " - (prin1-to-string (symbol-value func)))))) - (t ; unexpected - (error "Error in py-dump-help-string, tag `%s'" funckind))) - (princ (format "\n-> %s:\t%s\t%s\n\n" - (if (equal funckind "c") "Command" "Variable") - funcname keys)) - (princ funcdoc) - (terpri) - (setq start end)) + (setq mstart (match-beginning 0) end (match-end 0) + funckind (substring str (match-beginning 1) (match-end 1)) + funcname (substring str (match-beginning 2) (match-end 2)) + func (intern funcname)) + (princ (substitute-command-keys (substring str start mstart))) + (cond + ((equal funckind "c") ; command + (setq funcdoc (documentation func) + keys (concat + "Key(s): " + (mapconcat 'key-description + (where-is-internal func py-mode-map) + ", ")))) + ((equal funckind "v") ; variable + (setq funcdoc (documentation-property func 'variable-documentation) + keys (if (assq func locals) + (concat + "Local/Global values: " + (prin1-to-string (symbol-value func)) + " / " + (prin1-to-string (default-value func))) + (concat + "Value: " + (prin1-to-string (symbol-value func)))))) + (t ; unexpected + (error "Error in py-dump-help-string, tag `%s'" funckind))) + (princ (format "\n-> %s:\t%s\t%s\n\n" + (if (equal funckind "c") "Command" "Variable") + funcname keys)) + (princ funcdoc) + (terpri) + (setq start end)) (princ (substitute-command-keys (substring str start)))) (print-help-return-message))) @@ -3365,9 +3414,9 @@ local bindings to py-newline-and-indent. :mode 'python-mode :regexp "[a-zA-Z0-9_]+" :doc-spec '(("(python-lib)Module Index") - ("(python-lib)Class-Exception-Object Index") - ("(python-lib)Function-Method-Variable Index") - ("(python-lib)Miscellaneous Index"))) + ("(python-lib)Class-Exception-Object Index") + ("(python-lib)Function-Method-Variable Index") + ("(python-lib)Miscellaneous Index"))) ) @@ -3382,31 +3431,31 @@ local bindings to py-newline-and-indent. "Return the parse state at point (see `parse-partial-sexp' docs)." (save-excursion (let ((here (point)) - pps done) + pps done) (while (not done) - ;; back up to the first preceding line (if any; else start of - ;; buffer) that begins with a popular Python keyword, or a - ;; non- whitespace and non-comment character. These are good - ;; places to start parsing to see whether where we started is - ;; at a non-zero nesting level. It may be slow for people who - ;; write huge code blocks or huge lists ... tough beans. - (re-search-backward py-parse-state-re nil 'move) - (beginning-of-line) - ;; In XEmacs, we have a much better way to test for whether - ;; we're in a triple-quoted string or not. Emacs does not - ;; have this built-in function, which is its loss because - ;; without scanning from the beginning of the buffer, there's - ;; no accurate way to determine this otherwise. - (save-excursion (setq pps (parse-partial-sexp (point) here))) - ;; make sure we don't land inside a triple-quoted string - (setq done (or (not (nth 3 pps)) - (bobp))) - ;; Just go ahead and short circuit the test back to the - ;; beginning of the buffer. This will be slow, but not - ;; nearly as slow as looping through many - ;; re-search-backwards. - (if (not done) - (goto-char (point-min)))) + ;; back up to the first preceding line (if any; else start of + ;; buffer) that begins with a popular Python keyword, or a + ;; non- whitespace and non-comment character. These are good + ;; places to start parsing to see whether where we started is + ;; at a non-zero nesting level. It may be slow for people who + ;; write huge code blocks or huge lists ... tough beans. + (re-search-backward py-parse-state-re nil 'move) + (beginning-of-line) + ;; In XEmacs, we have a much better way to test for whether + ;; we're in a triple-quoted string or not. Emacs does not + ;; have this built-in function, which is its loss because + ;; without scanning from the beginning of the buffer, there's + ;; no accurate way to determine this otherwise. + (save-excursion (setq pps (parse-partial-sexp (point) here))) + ;; make sure we don't land inside a triple-quoted string + (setq done (or (not (nth 3 pps)) + (bobp))) + ;; Just go ahead and short circuit the test back to the + ;; beginning of the buffer. This will be slow, but not + ;; nearly as slow as looping through many + ;; re-search-backwards. + (if (not done) + (goto-char (point-min)))) pps))) (defun py-nesting-level () @@ -3414,8 +3463,8 @@ local bindings to py-newline-and-indent. If nesting level is zero, return nil." (let ((status (py-parse-state))) (if (zerop (car status)) - nil ; not in a nest - (car (cdr status))))) ; char# of open bracket + nil ; not in a nest + (car (cdr status))))) ; char# of open bracket (defun py-backslash-continuation-line-p () "Return t iff preceding line ends with backslash that is not in a comment." @@ -3426,7 +3475,7 @@ If nesting level is zero, return nil." ;; use 'eq' because char-after may return nil (eq (char-after (- (point) 2)) ?\\ ) ;; make sure; since eq test passed, there is a preceding line - (forward-line -1) ; always true -- side effect + (forward-line -1) ; always true -- side effect (looking-at py-continued-re)))) (defun py-continuation-line-p () @@ -3434,23 +3483,23 @@ If nesting level is zero, return nil." (save-excursion (beginning-of-line) (or (py-backslash-continuation-line-p) - (py-nesting-level)))) + (py-nesting-level)))) (defun py-goto-beginning-of-tqs (delim) "Go to the beginning of the triple quoted string we find ourselves in. DELIM is the TQS string delimiter character we're searching backwards for." (let ((skip (and delim (make-string 1 delim))) - (continue t)) + (continue t)) (when skip (save-excursion - (while continue - (py-safe (search-backward skip)) - (setq continue (and (not (bobp)) - (= (char-before) ?\\)))) - (if (and (= (char-before) delim) - (= (char-before (1- (point))) delim)) - (setq skip (make-string 3 delim)))) + (while continue + (py-safe (search-backward skip)) + (setq continue (and (not (bobp)) + (= (char-before) ?\\)))) + (if (and (= (char-before) delim) + (= (char-before (1- (point))) delim)) + (setq skip (make-string 3 delim)))) ;; we're looking at a triple-quoted string (py-safe (search-backward skip))))) @@ -3470,11 +3519,11 @@ line of the block." (while (py-continuation-line-p) (beginning-of-line) (if (py-backslash-continuation-line-p) - (while (py-backslash-continuation-line-p) - (forward-line -1)) - ;; else zip out of nested brackets/braces/parens - (while (setq open-bracket-pos (py-nesting-level)) - (goto-char open-bracket-pos))))) + (while (py-backslash-continuation-line-p) + (forward-line -1)) + ;; else zip out of nested brackets/braces/parens + (while (setq open-bracket-pos (py-nesting-level)) + (goto-char open-bracket-pos))))) (beginning-of-line)) (defun py-goto-beyond-final-line () @@ -3492,18 +3541,18 @@ multi-line statement we need to skip ove (forward-line 1) (let (state) (while (and (py-continuation-line-p) - (not (eobp))) + (not (eobp))) ;; skip over the backslash flavor (while (and (py-backslash-continuation-line-p) - (not (eobp))) - (forward-line 1)) + (not (eobp))) + (forward-line 1)) ;; if in nest, zip to the end of the nest (setq state (py-parse-state)) (if (and (not (zerop (car state))) - (not (eobp))) - (progn - (parse-partial-sexp (point) (point-max) 0 nil state) - (forward-line 1)))))) + (not (eobp))) + (progn + (parse-partial-sexp (point) (point-max) 0 nil state) + (forward-line 1)))))) (defun py-statement-opens-block-p () "Return t iff the current statement opens a block. @@ -3511,28 +3560,28 @@ I.e., iff it ends with a colon that is n be at the start of a statement." (save-excursion (let ((start (point)) - (finish (progn (py-goto-beyond-final-line) (1- (point)))) - (searching t) - (answer nil) - state) + (finish (progn (py-goto-beyond-final-line) (1- (point)))) + (searching t) + (answer nil) + state) (goto-char start) (while searching - ;; look for a colon with nothing after it except whitespace, and - ;; maybe a comment - (if (re-search-forward ":\\([ \t]\\|\\\\\n\\)*\\(#.*\\)?$" - finish t) - (if (eq (point) finish) ; note: no `else' clause; just - ; keep searching if we're not at - ; the end yet - ;; sure looks like it opens a block -- but it might - ;; be in a comment - (progn - (setq searching nil) ; search is done either way - (setq state (parse-partial-sexp start - (match-beginning 0))) - (setq answer (not (nth 4 state))))) - ;; search failed: couldn't find another interesting colon - (setq searching nil))) + ;; look for a colon with nothing after it except whitespace, and + ;; maybe a comment + (if (re-search-forward ":\\([ \t]\\|\\\\\n\\)*\\(#.*\\)?$" + finish t) + (if (eq (point) finish) ; note: no `else' clause; just + ; keep searching if we're not at + ; the end yet + ;; sure looks like it opens a block -- but it might + ;; be in a comment + (progn + (setq searching nil) ; search is done either way + (setq state (parse-partial-sexp start + (match-beginning 0))) + (setq answer (not (nth 4 state))))) + ;; search failed: couldn't find another interesting colon + (setq searching nil))) answer))) (defun py-statement-closes-block-p () @@ -3543,7 +3592,7 @@ and `pass'. This doesn't catch embedded (py-goto-initial-line) (back-to-indentation) (prog1 - (looking-at (concat py-block-closing-keywords-re "\\>")) + (looking-at (concat py-block-closing-keywords-re "\\>")) (goto-char here)))) (defun py-goto-beyond-block () @@ -3565,8 +3614,8 @@ does not include blank lines, comments, ;; note: will skip a blank or comment line that happens to be ;; a continuation line too (if (re-search-backward "^[ \t]*[^ \t#\n]" nil t) - (progn (py-goto-initial-line) t) - nil) + (progn (py-goto-initial-line) t) + nil) t)) (defun py-goto-statement-below () @@ -3577,12 +3626,12 @@ does not include blank lines, comments, (let ((start (point))) (py-goto-beyond-final-line) (while (and - (or (looking-at py-blank-or-comment-re) - (py-in-literal)) - (not (eobp))) + (or (looking-at py-blank-or-comment-re) + (py-in-literal)) + (not (eobp))) (forward-line 1)) (if (eobp) - (progn (goto-char start) nil) + (progn (goto-char start) nil) t))) (defun py-go-up-tree-to-keyword (key) @@ -3596,18 +3645,18 @@ return t. Otherwise, leave point at an ;; skip blanks and non-indenting # (py-goto-initial-line) (while (and - (looking-at "[ \t]*\\($\\|#[^ \t\n]\\)") - (zerop (forward-line -1))) ; go back + (looking-at "[ \t]*\\($\\|#[^ \t\n]\\)") + (zerop (forward-line -1))) ; go back nil) (py-goto-initial-line) (let* ((re (concat "[ \t]*" key "\\>")) - (case-fold-search nil) ; let* so looking-at sees this - (found (looking-at re)) - (dead nil)) + (case-fold-search nil) ; let* so looking-at sees this + (found (looking-at re)) + (dead nil)) (while (not (or found dead)) - (condition-case nil ; in case no enclosing block - (py-goto-block-up 'no-mark) - (error (setq dead t))) + (condition-case nil ; in case no enclosing block + (py-goto-block-up 'no-mark) + (error (setq dead t))) (or dead (setq found (looking-at re)))) (beginning-of-line) found)) @@ -3627,7 +3676,7 @@ Prefix with \"...\" if leading whitespac ([a-z]+). Returns nil if none was found." (let ((case-fold-search nil)) (if (looking-at "[ \t]*\\([a-z]+\\)\\>") - (intern (buffer-substring (match-beginning 1) (match-end 1))) + (intern (buffer-substring (match-beginning 1) (match-end 1))) nil))) (defun py-current-defun () @@ -3640,42 +3689,42 @@ This tells add-log.el how to find the cu (py-goto-initial-line) (back-to-indentation) (while (and (or (looking-at py-blank-or-comment-re) - (py-in-literal)) - (not (bobp))) + (py-in-literal)) + (not (bobp))) (backward-to-indentation 1)) (py-goto-initial-line) (let ((scopes "") - (sep "") - dead assignment) + (sep "") + dead assignment) ;; Check for an assignment. If this assignment exists inside a ;; def, it will be overwritten inside the while loop. If it ;; exists at top lever or inside a class, it will be preserved. (when (looking-at "[ \t]*\\([a-zA-Z0-9_]+\\)[ \t]*=") - (setq scopes (buffer-substring (match-beginning 1) (match-end 1))) - (setq assignment t) - (setq sep ".")) + (setq scopes (buffer-substring (match-beginning 1) (match-end 1))) + (setq assignment t) + (setq sep ".")) ;; Prepend the name of each outer socpe (def or class). (while (not dead) - (if (and (py-go-up-tree-to-keyword "\\(class\\|def\\)") - (looking-at - "[ \t]*\\(class\\|def\\)[ \t]*\\([a-zA-Z0-9_]+\\)[ \t]*")) - (let ((name (buffer-substring (match-beginning 2) (match-end 2)))) - (if (and assignment (looking-at "[ \t]*def")) - (setq scopes name) - (setq scopes (concat name sep scopes)) - (setq sep ".")))) - (setq assignment nil) - (condition-case nil ; Terminate nicely at top level. - (py-goto-block-up 'no-mark) - (error (setq dead t)))) + (if (and (py-go-up-tree-to-keyword "\\(class\\|def\\)") + (looking-at + "[ \t]*\\(class\\|def\\)[ \t]*\\([a-zA-Z0-9_]+\\)[ \t]*")) + (let ((name (buffer-substring (match-beginning 2) (match-end 2)))) + (if (and assignment (looking-at "[ \t]*def")) + (setq scopes name) + (setq scopes (concat name sep scopes)) + (setq sep ".")))) + (setq assignment nil) + (condition-case nil ; Terminate nicely at top level. + (py-goto-block-up 'no-mark) + (error (setq dead t)))) (if (string= scopes "") - nil - scopes)))) + nil + scopes)))) @@ -3698,24 +3747,24 @@ With \\[universal-argument] (programmati non-nil) just submit an enhancement request." (interactive (list (not (y-or-n-p - "Is this a bug report (hit `n' to send other comments)? ")))) + "Is this a bug report (hit `n' to send other comments)? ")))) (let ((reporter-prompt-for-summary-p (if enhancement-p - "(Very) brief summary: " - t))) + "(Very) brief summary: " + t))) (require 'reporter) (reporter-submit-bug-report - py-help-address ;address - (concat "python-mode " py-version) ;pkgname + py-help-address ;address + (concat "python-mode " py-version) ;pkgname ;; varlist (if enhancement-p nil '(py-python-command - py-indent-offset - py-block-comment-prefix - py-temp-directory - py-beep-if-tab-change)) - nil ;pre-hooks - nil ;post-hooks - "Dear Barry,") ;salutation + py-indent-offset + py-block-comment-prefix + py-temp-directory + py-beep-if-tab-change)) + nil ;pre-hooks + nil ;post-hooks + "Dear Barry,") ;salutation (if enhancement-p nil (set-mark (point)) (insert @@ -3729,9 +3778,9 @@ to do so may mean a greater delay in fix (defun py-kill-emacs-hook () "Delete files in `py-file-queue'. These are Python temporary files awaiting execution." - (mapcar #'(lambda (filename) - (py-safe (delete-file filename))) - py-file-queue)) + (mapc #'(lambda (filename) + (py-safe (delete-file filename))) + py-file-queue)) ;; arrange to kill temp files when Emacs exists (add-hook 'kill-emacs-hook 'py-kill-emacs-hook) @@ -3740,7 +3789,7 @@ These are Python temporary files awaitin ;; Add a designator to the minor mode strings (or (assq 'py-pdbtrack-is-tracking-p minor-mode-alist) (push '(py-pdbtrack-is-tracking-p py-pdbtrack-minor-mode-string) - minor-mode-alist)) + minor-mode-alist)) @@ -3750,10 +3799,10 @@ These are Python temporary files awaitin (defun py-fill-comment (&optional justify) "Fill the comment paragraph around point" (let (;; Non-nil if the current line contains a comment. - has-comment + has-comment - ;; If has-comment, the appropriate fill-prefix for the comment. - comment-fill-prefix) + ;; If has-comment, the appropriate fill-prefix for the comment. + comment-fill-prefix) ;; Figure out what kind of comment we are looking at. (save-excursion @@ -3761,54 +3810,54 @@ These are Python temporary files awaitin (cond ;; A line with nothing but a comment on it? ((looking-at "[ \t]*#[# \t]*") - (setq has-comment t - comment-fill-prefix (buffer-substring (match-beginning 0) - (match-end 0)))) + (setq has-comment t + comment-fill-prefix (buffer-substring (match-beginning 0) + (match-end 0)))) ;; A line with some code, followed by a comment? Remember that the hash ;; which starts the comment shouldn't be part of a string or character. ((progn - (while (not (looking-at "#\\|$")) - (skip-chars-forward "^#\n\"'\\") - (cond - ((eq (char-after (point)) ?\\) (forward-char 2)) - ((memq (char-after (point)) '(?\" ?')) (forward-sexp 1)))) - (looking-at "#+[\t ]*")) - (setq has-comment t) - (setq comment-fill-prefix - (concat (make-string (current-column) ? ) - (buffer-substring (match-beginning 0) (match-end 0))))))) + (while (not (looking-at "#\\|$")) + (skip-chars-forward "^#\n\"'\\") + (cond + ((eq (char-after (point)) ?\\) (forward-char 2)) + ((memq (char-after (point)) '(?\" ?')) (forward-sexp 1)))) + (looking-at "#+[\t ]*")) + (setq has-comment t) + (setq comment-fill-prefix + (concat (make-string (current-column) ? ) + (buffer-substring (match-beginning 0) (match-end 0))))))) (if (not has-comment) - (fill-paragraph justify) + (fill-paragraph justify) ;; Narrow to include only the comment, and then fill the region. (save-restriction - (narrow-to-region - - ;; Find the first line we should include in the region to fill. - (save-excursion - (while (and (zerop (forward-line -1)) - (looking-at "^[ \t]*#"))) - - ;; We may have gone to far. Go forward again. - (or (looking-at "^[ \t]*#") - (forward-line 1)) - (point)) - - ;; Find the beginning of the first line past the region to fill. - (save-excursion - (while (progn (forward-line 1) - (looking-at "^[ \t]*#"))) - (point))) - - ;; Lines with only hashes on them can be paragraph boundaries. - (let ((paragraph-start (concat paragraph-start "\\|[ \t#]*$")) - (paragraph-separate (concat paragraph-separate "\\|[ \t#]*$")) - (fill-prefix comment-fill-prefix)) - ;;(message "paragraph-start %S paragraph-separate %S" - ;;paragraph-start paragraph-separate) - (fill-paragraph justify)))) + (narrow-to-region + + ;; Find the first line we should include in the region to fill. + (save-excursion + (while (and (zerop (forward-line -1)) + (looking-at "^[ \t]*#"))) + + ;; We may have gone to far. Go forward again. + (or (looking-at "^[ \t]*#") + (forward-line 1)) + (point)) + + ;; Find the beginning of the first line past the region to fill. + (save-excursion + (while (progn (forward-line 1) + (looking-at "^[ \t]*#"))) + (point))) + + ;; Lines with only hashes on them can be paragraph boundaries. + (let ((paragraph-start (concat paragraph-start "\\|[ \t#]*$")) + (paragraph-separate (concat paragraph-separate "\\|[ \t#]*$")) + (fill-prefix comment-fill-prefix)) + ;;(message "paragraph-start %S paragraph-separate %S" + ;;paragraph-start paragraph-separate) + (fill-paragraph justify)))) t)) @@ -3817,32 +3866,32 @@ These are Python temporary files awaitin ;; basic strategy: narrow to the string and call the default ;; implementation (let (;; the start of the string's contents - string-start - ;; the end of the string's contents - string-end - ;; length of the string's delimiter - delim-length - ;; The string delimiter - delim - ) + string-start + ;; the end of the string's contents + string-end + ;; length of the string's delimiter + delim-length + ;; The string delimiter + delim + ) (save-excursion (goto-char start) (if (looking-at "\\('''\\|\"\"\"\\|'\\|\"\\)\\\\?\n?") - (setq string-start (match-end 0) - delim-length (- (match-end 1) (match-beginning 1)) - delim (buffer-substring-no-properties (match-beginning 1) - (match-end 1))) - (error "The parameter start is not the beginning of a python string")) + (setq string-start (match-end 0) + delim-length (- (match-end 1) (match-beginning 1)) + delim (buffer-substring-no-properties (match-beginning 1) + (match-end 1))) + (error "The parameter start is not the beginning of a python string")) ;; if the string is the first token on a line and doesn't start with ;; a newline, fill as if the string starts at the beginning of the ;; line. this helps with one line docstrings (save-excursion - (beginning-of-line) - (and (/= (char-before string-start) ?\n) - (looking-at (concat "[ \t]*" delim)) - (setq string-start (point)))) + (beginning-of-line) + (and (/= (char-before string-start) ?\n) + (looking-at (concat "[ \t]*" delim)) + (setq string-start (point)))) (forward-sexp (if (= delim-length 3) 2 1)) @@ -3857,14 +3906,14 @@ These are Python temporary files awaitin (save-restriction (narrow-to-region string-start string-end) (let ((ends-with-newline (= (char-before (point-max)) ?\n))) - (fill-paragraph justify) - (if (and (not ends-with-newline) - (= (char-before (point-max)) ?\n)) - ;; the default fill-paragraph implementation has inserted a - ;; newline at the end. Remove it again. - (save-excursion - (goto-char (point-max)) - (delete-char -1))))) + (fill-paragraph justify) + (if (and (not ends-with-newline) + (= (char-before (point-max)) ?\n)) + ;; the default fill-paragraph implementation has inserted a + ;; newline at the end. Remove it again. + (save-excursion + (goto-char (point-max)) + (delete-char -1))))) ;; return t to indicate that we've done our work t)) @@ -3877,32 +3926,35 @@ and initial `#'s. If point is inside a string, narrow to that string and fill. " (interactive "P") - (let* ((bod (py-point 'bod)) - (pps (parse-partial-sexp bod (point)))) - (cond - ;; are we inside a comment or on a line with only whitespace before - ;; the comment start? - ((or (nth 4 pps) - (save-excursion (beginning-of-line) (looking-at "[ \t]*#"))) - (py-fill-comment justify)) - ;; are we inside a string? - ((nth 3 pps) - (py-fill-string (nth 8 pps))) - ;; are we at the opening quote of a string, or in the indentation? - ((save-excursion - (forward-word 1) - (eq (py-in-literal) 'string)) - (save-excursion - (py-fill-string (py-point 'boi)))) - ;; are we at or after the closing quote of a string? - ((save-excursion - (backward-word 1) - (eq (py-in-literal) 'string)) - (save-excursion - (py-fill-string (py-point 'boi)))) - ;; otherwise use the default - (t - (fill-paragraph justify))))) + ;; fill-paragraph will narrow incorrectly + (save-restriction + (widen) + (let* ((bod (py-point 'bod)) + (pps (parse-partial-sexp bod (point)))) + (cond + ;; are we inside a comment or on a line with only whitespace before + ;; the comment start? + ((or (nth 4 pps) + (save-excursion (beginning-of-line) (looking-at "[ \t]*#"))) + (py-fill-comment justify)) + ;; are we inside a string? + ((nth 3 pps) + (py-fill-string (nth 8 pps))) + ;; are we at the opening quote of a string, or in the indentation? + ((save-excursion + (forward-word 1) + (eq (py-in-literal) 'string)) + (save-excursion + (py-fill-string (py-point 'boi)))) + ;; are we at or after the closing quote of a string? + ((save-excursion + (backward-word 1) + (eq (py-in-literal) 'string)) + (save-excursion + (py-fill-string (py-point 'boi)))) + ;; otherwise use the default + (t + (fill-paragraph justify))))))