0
|
1 ;;; doctest-mode.el --- Major mode for editing Python doctest files |
|
2 |
|
3 ;; Copyright (C) 2004-2007 Edward Loper |
|
4 |
|
5 ;; Author: Edward Loper |
|
6 ;; Maintainer: edloper@alum.mit.edu |
|
7 ;; Created: Aug 2004 |
|
8 ;; Keywords: python doctest unittest test docstring |
|
9 |
|
10 (defconst doctest-version "0.5 alpha" |
|
11 "`doctest-mode' version number.") |
|
12 |
|
13 ;; This software is provided as-is, without express or implied |
|
14 ;; warranty. Permission to use, copy, modify, distribute or sell this |
|
15 ;; software, without fee, for any purpose and by any individual or |
|
16 ;; organization, is hereby granted, provided that the above copyright |
|
17 ;; notice and this paragraph appear in all copies. |
|
18 |
|
19 ;; This is a major mode for editing text files that contain Python |
|
20 ;; doctest examples. Doctest is a testing framework for Python that |
|
21 ;; emulates an interactive session, and checks the result of each |
|
22 ;; command. For more information, see the Python library reference: |
|
23 ;; <http://docs.python.org/lib/module-doctest.html> |
|
24 |
|
25 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
26 ;;; Table of Contents |
|
27 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
28 ;; 1. Customization: use-editable variables to customize |
|
29 ;; doctest-mode. |
|
30 ;; |
|
31 ;; 2. Fonts: defines new font-lock faces. |
|
32 ;; |
|
33 ;; 3. Constants: various consts (mainly regexps) used by the rest |
|
34 ;; of the code. |
|
35 ;; |
|
36 ;; 4. Syntax Highlighting: defines variables and functions used by |
|
37 ;; font-lock-mode to perform syntax highlighting. |
|
38 ;; |
|
39 ;; 5. Source code editing & indentation: commands used to |
|
40 ;; automatically indent, dedent, & handle prompts. |
|
41 ;; |
|
42 ;; 6. Code Execution: commands used to start doctest processes, |
|
43 ;; and handle their output. |
|
44 ;; |
|
45 ;; 7. Markers: functions used to insert markers at the start of |
|
46 ;; doctest examples. These are used to keep track of the |
|
47 ;; correspondence between examples in the source buffer and |
|
48 ;; results in the output buffer. |
|
49 ;; |
|
50 ;; 8. Navigation: commands used to navigate between failed examples. |
|
51 ;; |
|
52 ;; 9. Replace Output: command used to replace a doctest example's |
|
53 ;; expected output with its actual output. |
|
54 ;; |
|
55 ;; 10. Helper functions: various helper functions used by the rest |
|
56 ;; of the code. |
|
57 ;; |
|
58 ;; 11. Emacs compatibility functions: defines compatible versions of |
|
59 ;; functions that are defined for some versions of emacs but not |
|
60 ;; others. |
|
61 ;; |
|
62 ;; 12. Doctest Results Mode: defines doctest-results-mode, which is |
|
63 ;; used for the output generated by doctest. |
|
64 ;; |
|
65 ;; 13. Doctest Mode: defines doctest-mode itself. |
|
66 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
67 |
|
68 |
|
69 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
70 ;;; Customizable Constants |
|
71 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
72 |
|
73 (defgroup doctest nil |
|
74 "Support for the Python doctest framework" |
|
75 :group 'languages |
|
76 :prefix "doctest-") |
|
77 |
|
78 (defcustom doctest-default-margin 4 |
|
79 "The default pre-prompt margin for doctest examples." |
|
80 :type 'integer |
|
81 :group 'doctest) |
|
82 |
|
83 (defcustom doctest-avoid-trailing-whitespace t |
|
84 "If true, then delete trailing whitespace when inserting a newline." |
|
85 :type 'boolean |
|
86 :group 'doctest) |
|
87 |
|
88 (defcustom doctest-temp-directory |
|
89 (let ((ok '(lambda (x) |
|
90 (and x |
|
91 (setq x (expand-file-name x)) ; always true |
|
92 (file-directory-p x) |
|
93 (file-writable-p x) |
|
94 x)))) |
|
95 (or (funcall ok (getenv "TMPDIR")) |
|
96 (funcall ok "/usr/tmp") |
|
97 (funcall ok "/tmp") |
|
98 (funcall ok "/var/tmp") |
|
99 (funcall ok ".") |
|
100 (error (concat "Couldn't find a usable temp directory -- " |
|
101 "set `doctest-temp-directory'")))) |
|
102 "Directory used for temporary files created when running doctest. |
|
103 By default, the first directory from this list that exists and that you |
|
104 can write into: the value (if any) of the environment variable TMPDIR, |
|
105 /usr/tmp, /tmp, /var/tmp, or the current directory." |
|
106 :type 'string |
|
107 :group 'doctest) |
|
108 |
|
109 (defcustom doctest-hide-example-source nil |
|
110 "If true, then don't display the example source code for each |
|
111 failure in the results buffer." |
|
112 :type 'boolean |
|
113 :group 'doctest) |
|
114 |
|
115 (defcustom doctest-python-command "python" |
|
116 "Shell command used to start the python interpreter" |
|
117 :type 'string |
|
118 :group 'doctest) |
|
119 |
|
120 (defcustom doctest-results-buffer-name "*doctest-output (%N)*" |
|
121 "The name of the buffer used to store the output of the doctest |
|
122 command. This name can contain the following special sequences: |
|
123 %n -- replaced by the doctest buffer's name. |
|
124 %N -- replaced by the doctest buffer's name, with '.doctest' |
|
125 stripped off. |
|
126 %f -- replaced by the doctest buffer's filename." |
|
127 :type 'string |
|
128 :group 'doctest) |
|
129 |
|
130 (defcustom doctest-optionflags '() |
|
131 "Option flags for doctest" |
|
132 :group 'doctest |
|
133 :type '(repeat (choice (const :tag "Select an option..." "") |
|
134 (const :tag "Normalize whitespace" |
|
135 "NORMALIZE_WHITESPACE") |
|
136 (const :tag "Ellipsis" |
|
137 "ELLIPSIS") |
|
138 (const :tag "Don't accept True for 1" |
|
139 "DONT_ACCEPT_TRUE_FOR_1") |
|
140 (const :tag "Don't accept <BLANKLINE>" |
|
141 "DONT_ACCEPT_BLANKLINE") |
|
142 (const :tag "Ignore Exception detail" |
|
143 "IGNORE_EXCEPTION_DETAIL") |
|
144 (const :tag "Report only first failure" |
|
145 "REPORT_ONLY_FIRST_FAILURE") |
|
146 ))) |
|
147 |
|
148 (defcustom doctest-async t |
|
149 "If true, then doctest will be run asynchronously." |
|
150 :type 'boolean |
|
151 :group 'doctest) |
|
152 |
|
153 (defcustom doctest-trim-exceptions t |
|
154 "If true, then any exceptions inserted by doctest-replace-output |
|
155 will have the stack trace lines trimmed." |
|
156 :type 'boolean |
|
157 :group 'doctest) |
|
158 |
|
159 (defcustom doctest-highlight-strings t |
|
160 "If true, then highlight strings. If you find that doctest-mode |
|
161 is responding slowly when you type, turning this off might help." |
|
162 :type 'boolean |
|
163 :group 'doctest) |
|
164 |
|
165 (defcustom doctest-follow-output t |
|
166 "If true, then when doctest is run asynchronously, the output buffer |
|
167 will scroll to display its output as it is generated. If false, then |
|
168 the output buffer not scroll." |
|
169 :type 'boolean |
|
170 :group 'doctest) |
|
171 |
|
172 (defvar doctest-mode-hook nil |
|
173 "Hook called by `doctest-mode'.") |
|
174 |
|
175 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
176 ;;; Fonts |
|
177 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
178 |
|
179 (defface doctest-prompt-face |
|
180 '((((class color) (background dark)) |
|
181 (:foreground "#68f")) |
|
182 (t (:foreground "#226"))) |
|
183 "Face for Python prompts in doctest examples." |
|
184 :group 'doctest) |
|
185 |
|
186 (defface doctest-output-face |
|
187 '((((class color) (background dark)) |
|
188 (:foreground "#afd")) |
|
189 (t (:foreground "#262"))) |
|
190 "Face for the output of doctest examples." |
|
191 :group 'doctest) |
|
192 |
|
193 (defface doctest-output-marker-face |
|
194 '((((class color) (background dark)) |
|
195 (:foreground "#0f0")) |
|
196 (t (:foreground "#080"))) |
|
197 "Face for markers in the output of doctest examples." |
|
198 :group 'doctest) |
|
199 |
|
200 (defface doctest-output-traceback-face |
|
201 '((((class color) (background dark)) |
|
202 (:foreground "#f88")) |
|
203 (t (:foreground "#622"))) |
|
204 "Face for traceback headers in the output of doctest examples." |
|
205 :group 'doctest) |
|
206 |
|
207 (defface doctest-results-divider-face |
|
208 '((((class color) (background dark)) |
|
209 (:foreground "#08f")) |
|
210 (t (:foreground "#00f"))) |
|
211 "Face for dividers in the doctest results window." |
|
212 :group 'doctest) |
|
213 |
|
214 (defface doctest-results-loc-face |
|
215 '((((class color) (background dark)) |
|
216 (:foreground "#0f8")) |
|
217 (t (:foreground "#084"))) |
|
218 "Face for location headers in the doctest results window." |
|
219 :group 'doctest) |
|
220 |
|
221 (defface doctest-results-header-face |
|
222 '((((class color) (background dark)) |
|
223 (:foreground "#8ff")) |
|
224 (t (:foreground "#088"))) |
|
225 "Face for sub-headers in the doctest results window." |
|
226 :group 'doctest) |
|
227 |
|
228 (defface doctest-results-selection-face |
|
229 '((((class color) (background dark)) |
|
230 (:foreground "#ff0" :background "#008")) |
|
231 (t (:background "#088" :foreground "#fff"))) |
|
232 "Face for selected failure's location header in the results window." |
|
233 :group 'doctest) |
|
234 |
|
235 (defface doctest-selection-face |
|
236 '((((class color) (background dark)) |
|
237 (:foreground "#ff0" :background "#00f" :bold t)) |
|
238 (t (:foreground "#f00"))) |
|
239 "Face for selected example's prompt" |
|
240 :group 'doctest) |
|
241 |
|
242 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
243 ;;; Constants |
|
244 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
245 |
|
246 (defconst doctest-prompt-re |
|
247 "^\\(?:\\([ \t]*\\)\\(>>> ?\\|[.][.][.] ?\\)\\([ \t]*\\)\\)" |
|
248 "Regular expression for doctest prompts. It defines three groups: |
|
249 the pre-prompt margin; the prompt; and the post-prompt indentation.") |
|
250 |
|
251 (defconst doctest-open-block-re |
|
252 "[^\n]+:[ \t]*\\(#.*\\)?$" |
|
253 "Regular expression for a line that opens a block") |
|
254 |
|
255 (defconst doctest-close-block-re |
|
256 "\\(return\\|raise\\|break\\|continue\\|pass\\)\\b" |
|
257 "Regular expression for a line that closes a block") |
|
258 |
|
259 (defconst doctest-example-source-re |
|
260 "^Failed example:\n\\(\n\\| [^\n]*\n\\)+" |
|
261 "Regular expression for example source in doctest's output.") |
|
262 |
|
263 (defconst doctest-results-divider-re |
|
264 "^\\([*]\\{60,\\}\\)$" |
|
265 "Regular expression for example dividers in doctest's output.") |
|
266 |
|
267 (defconst doctest-py24-results-loc-re |
|
268 "^File \"[^\"]+\", line \\([0-9]+\\), in [^\n]+" |
|
269 "Regular expression for example location markers in doctest's output.") |
|
270 |
|
271 (defconst doctest-py21-results-loc-re |
|
272 "^from line #\\([0-9]+\\) of [^\n]*" |
|
273 "Regular expression for example location markers in doctest's output, |
|
274 when the output was generated by an old version of doctest.") |
|
275 |
|
276 (defconst doctest-results-header-re |
|
277 "^\\([^ \n\t].+:\\|Expected nothing\\|Got nothing\\)$" |
|
278 "Regular expression for example headers in doctest's output.") |
|
279 |
|
280 (defconst doctest-traceback-header-re |
|
281 "^[ \t]*Traceback (\\(most recent call last\\|innermost last\\)):" |
|
282 "Regular expression for Python traceback headers.") |
|
283 |
|
284 (defconst doctest-py21-results-re |
|
285 "^from line #" |
|
286 "Regular expression used to test which version of doctest was used.") |
|
287 |
|
288 ;; nb: There's a bug in Python's traceback.print_exception function |
|
289 ;; which causes SyntaxError exceptions to be displayed incorrectly; |
|
290 ;; which prevents this regexp from matching. But there shouldn't be |
|
291 ;; too many people testing for SyntaxErrors, so I won't worry about |
|
292 ;; it. |
|
293 (defconst doctest-traceback-re |
|
294 (let ((nonprompt |
|
295 ;; This matches any non-blank line that doesn't start with |
|
296 ;; a prompt (... or >>>). |
|
297 (concat |
|
298 "\\(?:[.][.][^.\n]\\|[>][>][^>\n]\\|" |
|
299 "[.][^.\n]\\|[>][^>\n]\\|[^.>\n \t]\\)[^\n]*"))) |
|
300 (concat |
|
301 "^\\(\\([ \t]*\\)Traceback " |
|
302 "(\\(?:most recent call last\\|innermost last\\)):\n\\)" |
|
303 "\\(?:\\2[ \t]+[^ \t\n][^\n]*\n\\)*" |
|
304 "\\(\\(?:\\2" nonprompt "\n\\)" |
|
305 "\\(?:\\2[ \t]*" nonprompt "\n\\)*\\)")) |
|
306 "Regular expression that matches a complete exception traceback. |
|
307 It contains three groups: group 1 is the header line; group 2 is |
|
308 the indentation; and group 3 is the exception message.") |
|
309 |
|
310 (defconst doctest-blankline-re |
|
311 "^[ \t]*<BLANKLINE>" |
|
312 "Regular expression that matches blank line markers in doctest |
|
313 output.") |
|
314 |
|
315 (defconst doctest-outdent-re |
|
316 (concat "\\(" (mapconcat 'identity |
|
317 '("else:" |
|
318 "except\\(\\s +.*\\)?:" |
|
319 "finally:" |
|
320 "elif\\s +.*:") |
|
321 "\\|") |
|
322 "\\)") |
|
323 "Regular expression for a line that should be outdented. Any line |
|
324 that matches `doctest-outdent-re', but does not follow a line matching |
|
325 `doctest-no-outdent-re', will be outdented.") |
|
326 |
|
327 ;; It's not clear to me *why* the behavior given by this definition of |
|
328 ;; doctest-no-outdent-re is desirable; but it's basically just copied |
|
329 ;; from python-mode. |
|
330 (defconst doctest-no-outdent-re |
|
331 (concat |
|
332 "\\(" |
|
333 (mapconcat 'identity |
|
334 (list "try:" |
|
335 "except\\(\\s +.*\\)?:" |
|
336 "while\\s +.*:" |
|
337 "for\\s +.*:" |
|
338 "if\\s +.*:" |
|
339 "elif\\s +.*:" |
|
340 "\\(return\\|raise\\|break\\|continue\\|pass\\)[ \t\n]" |
|
341 ) |
|
342 "\\|") |
|
343 "\\)") |
|
344 "Regular expression matching lines not to outdent after. Any line |
|
345 that matches `doctest-outdent-re', but does not follow a line matching |
|
346 `doctest-no-outdent-re', will be outdented.") |
|
347 |
|
348 (defconst doctest-script |
|
349 "\ |
|
350 from doctest import * |
|
351 import sys |
|
352 if '%m': |
|
353 import imp |
|
354 try: |
|
355 m = imp.load_source('__imported__', '%m') |
|
356 globs = m.__dict__ |
|
357 except Exception, e: |
|
358 print ('doctest-mode encountered an error while importing ' |
|
359 'the current buffer:\\n\\n %s' % e) |
|
360 sys.exit(1) |
|
361 else: |
|
362 globs = {} |
|
363 doc = open('%t').read() |
|
364 if sys.version_info[:2] >= (2,4): |
|
365 test = DocTestParser().get_doctest(doc, globs, '%n', '%f', 0) |
|
366 r = DocTestRunner(optionflags=%l) |
|
367 r.run(test) |
|
368 else: |
|
369 Tester(globs=globs).runstring(doc, '%f')" |
|
370 ;; Docstring: |
|
371 "Python script used to run doctest. |
|
372 The following special sequences are defined: |
|
373 %n -- replaced by the doctest buffer's name. |
|
374 %f -- replaced by the doctest buffer's filename. |
|
375 %l -- replaced by the doctest flags string. |
|
376 %t -- replaced by the name of the tempfile containing the doctests." |
|
377 ) |
|
378 |
|
379 (defconst doctest-keyword-re |
|
380 (let* ((kw1 (mapconcat 'identity |
|
381 '("and" "assert" "break" "class" |
|
382 "continue" "def" "del" "elif" |
|
383 "else" "except" "exec" "for" |
|
384 "from" "global" "if" "import" |
|
385 "in" "is" "lambda" "not" |
|
386 "or" "pass" "print" "raise" |
|
387 "return" "while" "yield" |
|
388 ) |
|
389 "\\|")) |
|
390 (kw2 (mapconcat 'identity |
|
391 '("else:" "except:" "finally:" "try:") |
|
392 "\\|")) |
|
393 (kw3 (mapconcat 'identity |
|
394 '("ArithmeticError" "AssertionError" |
|
395 "AttributeError" "DeprecationWarning" "EOFError" |
|
396 "Ellipsis" "EnvironmentError" "Exception" "False" |
|
397 "FloatingPointError" "FutureWarning" "IOError" |
|
398 "ImportError" "IndentationError" "IndexError" |
|
399 "KeyError" "KeyboardInterrupt" "LookupError" |
|
400 "MemoryError" "NameError" "None" "NotImplemented" |
|
401 "NotImplementedError" "OSError" "OverflowError" |
|
402 "OverflowWarning" "PendingDeprecationWarning" |
|
403 "ReferenceError" "RuntimeError" "RuntimeWarning" |
|
404 "StandardError" "StopIteration" "SyntaxError" |
|
405 "SyntaxWarning" "SystemError" "SystemExit" |
|
406 "TabError" "True" "TypeError" "UnboundLocalError" |
|
407 "UnicodeDecodeError" "UnicodeEncodeError" |
|
408 "UnicodeError" "UnicodeTranslateError" |
|
409 "UserWarning" "ValueError" "Warning" |
|
410 "ZeroDivisionError" "__debug__" |
|
411 "__import__" "__name__" "abs" "apply" "basestring" |
|
412 "bool" "buffer" "callable" "chr" "classmethod" |
|
413 "cmp" "coerce" "compile" "complex" "copyright" |
|
414 "delattr" "dict" "dir" "divmod" |
|
415 "enumerate" "eval" "execfile" "exit" "file" |
|
416 "filter" "float" "getattr" "globals" "hasattr" |
|
417 "hash" "hex" "id" "input" "int" "intern" |
|
418 "isinstance" "issubclass" "iter" "len" "license" |
|
419 "list" "locals" "long" "map" "max" "min" "object" |
|
420 "oct" "open" "ord" "pow" "property" "range" |
|
421 "raw_input" "reduce" "reload" "repr" "round" |
|
422 "setattr" "slice" "staticmethod" "str" "sum" |
|
423 "super" "tuple" "type" "unichr" "unicode" "vars" |
|
424 "xrange" "zip") |
|
425 "\\|")) |
|
426 (pseudokw (mapconcat 'identity |
|
427 '("self" "None" "True" "False" "Ellipsis") |
|
428 "\\|")) |
|
429 (string (concat "'\\(?:\\\\[^\n]\\|[^\n']*\\)'" "\\|" |
|
430 "\"\\(?:\\\\[^\n]\\|[^\n\"]*\\)\"")) |
|
431 (brk "\\(?:[ \t(]\\|$\\)")) |
|
432 (concat |
|
433 ;; Comments (group 1) |
|
434 "\\(#.*\\)" |
|
435 ;; Function & Class Definitions (groups 2-3) |
|
436 "\\|\\b\\(class\\|def\\)" |
|
437 "[ \t]+\\([a-zA-Z_][a-zA-Z0-9_]*\\)" |
|
438 ;; Builtins preceeded by '.'(group 4) |
|
439 "\\|[.][\t ]*\\(" kw3 "\\)" |
|
440 ;; Keywords & builtins (group 5) |
|
441 "\\|\\b\\(" kw1 "\\|" kw2 "\\|" |
|
442 kw3 "\\|" pseudokw "\\)" brk |
|
443 ;; Decorators (group 6) |
|
444 "\\|\\(@[a-zA-Z_][a-zA-Z0-9_]*\\)" |
|
445 )) |
|
446 "A regular expression used for syntax highlighting of Python |
|
447 source code.") |
|
448 |
|
449 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
450 ;;; Syntax Highlighting (font-lock mode) |
|
451 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
452 |
|
453 ;; Define the font-lock keyword table. |
|
454 (defconst doctest-font-lock-keywords |
|
455 `( |
|
456 ;; The following pattern colorizes source lines. In particular, |
|
457 ;; it first matches prompts, and then looks for any of the |
|
458 ;; following matches *on the same line* as the prompt. It uses |
|
459 ;; the form: |
|
460 ;; |
|
461 ;; (MATCHER MATCH-HIGHLIGHT |
|
462 ;; (ANCHOR-MATCHER nil nil MATCH-HIGHLIGHT)) |
|
463 ;; |
|
464 ;; See the variable documentation for font-lock-keywords for a |
|
465 ;; description of what each of those means. |
|
466 ("^[ \t]*\\(>>>\\|\\.\\.\\.\\)" |
|
467 (1 'doctest-prompt-face) |
|
468 (doctest-source-matcher |
|
469 nil nil |
|
470 (1 'font-lock-comment-face t t) ; comments |
|
471 (2 'font-lock-keyword-face t t) ; def/class |
|
472 (3 'font-lock-type-face t t) ; func/class name |
|
473 ;; group 4 (builtins preceeded by '.') gets no colorization. |
|
474 (5 'font-lock-keyword-face t t) ; keywords & builtins |
|
475 (6 'font-lock-preprocessor-face t t) ; decorators |
|
476 (7 'font-lock-string-face t t) ; strings |
|
477 )) |
|
478 |
|
479 ;; The following pattern colorizes output lines. In particular, |
|
480 ;; it uses doctest-output-line-matcher to check if this is an |
|
481 ;; output line, and if so, it colorizes it, and any special |
|
482 ;; markers it contains. |
|
483 (doctest-output-line-matcher |
|
484 (0 'doctest-output-face t) |
|
485 ("\\.\\.\\." (beginning-of-line) (end-of-line) |
|
486 (0 'doctest-output-marker-face t)) |
|
487 (,doctest-blankline-re (beginning-of-line) (end-of-line) |
|
488 (0 'doctest-output-marker-face t)) |
|
489 (doctest-traceback-line-matcher (beginning-of-line) (end-of-line) |
|
490 (0 'doctest-output-traceback-face t)) |
|
491 (,doctest-traceback-header-re (beginning-of-line) (end-of-line) |
|
492 (0 'doctest-output-traceback-face t)) |
|
493 ) |
|
494 |
|
495 ;; A PS1 prompt followed by a non-space is an error. |
|
496 ("^[ \t]*\\(>>>[^ \t\n][^\n]*\\)" (1 'font-lock-warning-face t)) |
|
497 ) |
|
498 "Expressions to highlight in doctest-mode.") |
|
499 |
|
500 (defconst doctest-results-font-lock-keywords |
|
501 `((,doctest-results-divider-re |
|
502 (0 'doctest-results-divider-face)) |
|
503 (,doctest-py24-results-loc-re |
|
504 (0 'doctest-results-loc-face)) |
|
505 (,doctest-results-header-re |
|
506 (0 'doctest-results-header-face)) |
|
507 (doctest-results-selection-matcher |
|
508 (0 'doctest-results-selection-face t))) |
|
509 "Expressions to highlight in doctest-results-mode.") |
|
510 |
|
511 (defun doctest-output-line-matcher (limit) |
|
512 "A `font-lock-keyword' MATCHER that returns t if the current |
|
513 line is the expected output for a doctest example, and if so, |
|
514 sets `match-data' so that group 0 spans the current line." |
|
515 ;; The real work is done by doctest-find-output-line. |
|
516 (when (doctest-find-output-line limit) |
|
517 ;; If we found one, then mark the entire line. |
|
518 (beginning-of-line) |
|
519 (re-search-forward "[^\n]*" limit))) |
|
520 |
|
521 (defun doctest-traceback-line-matcher (limit) |
|
522 "A `font-lock-keyword' MATCHER that returns t if the current line is |
|
523 the beginning of a traceback, and if so, sets `match-data' so that |
|
524 group 0 spans the entire traceback. n.b.: limit is ignored." |
|
525 (beginning-of-line) |
|
526 (when (looking-at doctest-traceback-re) |
|
527 (goto-char (match-end 0)) |
|
528 t)) |
|
529 |
|
530 (defun doctest-source-matcher (limit) |
|
531 "A `font-lock-keyword' MATCHER that returns t if the current line |
|
532 contains a Python source expression that should be highlighted |
|
533 after the point. If so, it sets `match-data' to cover the string |
|
534 literal. The groups in `match-data' should be interpreted as follows: |
|
535 |
|
536 Group 1: comments |
|
537 Group 2: def/class |
|
538 Group 3: function/class name |
|
539 Group 4: builtins preceeded by '.' |
|
540 Group 5: keywords & builtins |
|
541 Group 6: decorators |
|
542 Group 7: strings |
|
543 " |
|
544 (let ((matchdata nil)) |
|
545 ;; First, look for string literals. |
|
546 (when doctest-highlight-strings |
|
547 (save-excursion |
|
548 (when (doctest-string-literal-matcher limit) |
|
549 (setq matchdata |
|
550 (list (match-beginning 0) (match-end 0) |
|
551 nil nil nil nil nil nil nil nil nil nil nil nil |
|
552 (match-beginning 0) (match-end 0)))))) |
|
553 ;; Then, look for other keywords. If they occur before the |
|
554 ;; string literal, then they take precedence. |
|
555 (save-excursion |
|
556 (when (and (re-search-forward doctest-keyword-re limit t) |
|
557 (or (null matchdata) |
|
558 (< (match-beginning 0) (car matchdata)))) |
|
559 (setq matchdata (match-data)))) |
|
560 (when matchdata |
|
561 (set-match-data matchdata) |
|
562 (goto-char (match-end 0)) |
|
563 t))) |
|
564 |
|
565 (defun doctest-string-literal-matcher (limit &optional debug) |
|
566 "A `font-lock-keyword' MATCHER that returns t if the current line |
|
567 contains a string literal starting at or after the point. If so, it |
|
568 expands `match-data' to cover the string literal. This matcher uses |
|
569 `doctest-statement-info' to collect information about strings that |
|
570 continue over multiple lines. It therefore might be a little slow for |
|
571 very large statements." |
|
572 (let* ((stmt-info (doctest-statement-info)) |
|
573 (quotes (reverse (nth 5 stmt-info))) |
|
574 (result nil)) |
|
575 (if debug (doctest-debug "quotes %s" quotes)) |
|
576 (while (and quotes (null result)) |
|
577 (let* ((quote (pop quotes)) |
|
578 (start (car quote)) |
|
579 (end (min limit (or (cdr quote) limit)))) |
|
580 (if debug (doctest-debug "quote %s-%s pt=%s lim=%s" |
|
581 start end (point) limit)) |
|
582 (when (or (and (<= (point) start) (< start limit)) |
|
583 (and (< start (point)) (< (point) end))) |
|
584 (setq start (max start (point))) |
|
585 (set-match-data (list start end)) |
|
586 (if debug (doctest-debug "marking string %s" (match-data))) |
|
587 (goto-char end) |
|
588 (setq result t)))) |
|
589 result)) |
|
590 |
|
591 (defun doctest-results-selection-matcher (limit) |
|
592 "Matches from `doctest-selected-failure' to the end of the |
|
593 line. This is used to highlight the currently selected failure." |
|
594 (when (and doctest-selected-failure |
|
595 (<= (point) doctest-selected-failure) |
|
596 (< doctest-selected-failure limit)) |
|
597 (goto-char doctest-selected-failure) |
|
598 (re-search-forward "[^\n]+" limit))) |
|
599 |
|
600 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
601 ;;; Source code editing & indentation |
|
602 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
603 |
|
604 (defun doctest-indent-source-line (&optional dedent-only) |
|
605 "Re-indent the current line, as doctest source code. I.e., add a |
|
606 prompt to the current line if it doesn't have one, and re-indent the |
|
607 source code (to the right of the prompt). If `dedent-only' is true, |
|
608 then don't increase the indentation level any." |
|
609 (interactive "*") |
|
610 (let ((indent-end nil)) |
|
611 (save-excursion |
|
612 (beginning-of-line) |
|
613 (let ((new-indent (doctest-current-source-line-indentation dedent-only)) |
|
614 (new-margin (doctest-current-source-line-margin)) |
|
615 (line-had-prompt (looking-at doctest-prompt-re))) |
|
616 ;; Delete the old prompt (if any). |
|
617 (when line-had-prompt |
|
618 (goto-char (match-beginning 2)) |
|
619 (delete-char (- (match-end 2) (match-beginning 2)))) |
|
620 ;; Delete the old indentation. |
|
621 (delete-backward-char (skip-chars-forward " \t")) |
|
622 ;; If it's a continuation line, or a new PS1 prompt, |
|
623 ;; then copy the margin. |
|
624 (when (or new-indent (not line-had-prompt)) |
|
625 (beginning-of-line) |
|
626 (delete-backward-char (skip-chars-forward " \t")) |
|
627 (insert-char ?\ new-margin)) |
|
628 ;; Add the new prompt. |
|
629 (insert-string (if new-indent "... " ">>> ")) |
|
630 ;; Add the new indentation |
|
631 (if new-indent (insert-char ?\ new-indent)) |
|
632 (setq indent-end (point)))) |
|
633 ;; If we're left of the indentation end, then move up to the |
|
634 ;; indentation end. |
|
635 (if (< (point) indent-end) (goto-char indent-end)))) |
|
636 |
|
637 (defun doctest-current-source-line-indentation (&optional dedent-only) |
|
638 "Return the post-prompt indent to use for this line. This is an |
|
639 integer for a continuation lines, and nil for non-continuation lines." |
|
640 (save-excursion |
|
641 ;; Examine the previous doctest line (if present). |
|
642 (let* ((prev-stmt-info (doctest-prev-statement-info)) |
|
643 (prev-stmt-indent (nth 0 prev-stmt-info)) |
|
644 (continuation-indent (nth 1 prev-stmt-info)) |
|
645 (prev-stmt-opens-block (nth 2 prev-stmt-info)) |
|
646 (prev-stmt-closes-block (nth 3 prev-stmt-info)) |
|
647 (prev-stmt-blocks-outdent (nth 4 prev-stmt-info)) |
|
648 ) |
|
649 ;; Examine this doctest line. |
|
650 (let* ((curr-line-indent 0) |
|
651 (curr-line-outdented nil)) |
|
652 (beginning-of-line) |
|
653 (when (looking-at doctest-prompt-re) |
|
654 (setq curr-line-indent (- (match-end 3) (match-beginning 3))) |
|
655 (goto-char (match-end 3))) |
|
656 (setq curr-line-outdented (and (looking-at doctest-outdent-re) |
|
657 (not prev-stmt-blocks-outdent))) |
|
658 ;; Compute the overall indent. |
|
659 (let ((indent (or continuation-indent |
|
660 (+ prev-stmt-indent |
|
661 (if curr-line-outdented -4 0) |
|
662 (if prev-stmt-opens-block 4 0) |
|
663 (if prev-stmt-closes-block -4 0))))) |
|
664 ;; If dedent-only is true, then make sure we don't indent. |
|
665 (when dedent-only |
|
666 (setq indent (min indent curr-line-indent))) |
|
667 ;; If indent=0 and we're not outdented, then set indent to |
|
668 ;; nil (to signify the start of a new source example). |
|
669 (when (and (= indent 0) |
|
670 (not (or curr-line-outdented continuation-indent))) |
|
671 (setq indent nil)) |
|
672 ;; Return the indentation. |
|
673 indent))))) |
|
674 |
|
675 (defun doctest-prev-statement-info (&optional debug) |
|
676 (save-excursion |
|
677 (forward-line -1) |
|
678 (doctest-statement-info debug))) |
|
679 |
|
680 (defun doctest-statement-info (&optional debug) |
|
681 "Collect info about the previous statement, and return it as a list: |
|
682 |
|
683 (INDENT, CONTINUATION, OPENS-BLOCK, CLOSES-BLOCK, BLOCKS-OUTDENT, |
|
684 QUOTES) |
|
685 |
|
686 INDENT -- The indentation of the previous statement (after the prompt) |
|
687 |
|
688 CONTINUATION -- If the previous statement is incomplete (e.g., has an |
|
689 open paren or quote), then this is the appropriate indentation |
|
690 level; otherwise, it's nil. |
|
691 |
|
692 OPENS-BLOCK -- True if the previous statement opens a Python control |
|
693 block. |
|
694 |
|
695 CLOSES-BLOCK -- True if the previous statement closes a Python control |
|
696 block. |
|
697 |
|
698 BLOCKS-OUTDENT -- True if the previous statement should 'block the |
|
699 next statement from being considered an outdent. |
|
700 |
|
701 QUOTES -- A list of (START . END) pairs for all quotation strings. |
|
702 " |
|
703 (save-excursion |
|
704 (end-of-line) |
|
705 (let ((end (point))) |
|
706 (while (and (doctest-on-source-line-p "...") (= (forward-line -1) 0))) |
|
707 (cond |
|
708 ;; If there's no previous >>> line, then give up. |
|
709 ((not (doctest-on-source-line-p ">>>")) |
|
710 '(0 nil nil nil nil)) |
|
711 |
|
712 ;; If there is a previous statement, walk through the source |
|
713 ;; code, checking for operators that may be of interest. |
|
714 (t |
|
715 (beginning-of-line) |
|
716 (let* ((quote-mark nil) (nesting 0) (indent-stack '()) |
|
717 (stmt-indent 0) |
|
718 (stmt-opens-block nil) |
|
719 (stmt-closes-block nil) |
|
720 (stmt-blocks-outdent nil) |
|
721 (quotes '()) |
|
722 (elt-re (concat "\\\\[^\n]\\|" |
|
723 "(\\|)\\|\\[\\|\\]\\|{\\|}\\|" |
|
724 "\"\"\"\\|\'\'\'\\|\"\\|\'\\|" |
|
725 "#[^\n]*\\|" doctest-prompt-re))) |
|
726 (while (re-search-forward elt-re end t) |
|
727 (let* ((elt (match-string 0)) |
|
728 (elt-first-char (substring elt 0 1))) |
|
729 (if debug (doctest-debug "Debug: %s" elt)) |
|
730 (cond |
|
731 ;; Close quote -- set quote-mark back to nil. The |
|
732 ;; second case is for cases like: ' ''' |
|
733 (quote-mark |
|
734 (cond |
|
735 ((equal quote-mark elt) |
|
736 (setq quote-mark nil) |
|
737 (setcdr (car quotes) (point))) |
|
738 ((equal quote-mark elt-first-char) |
|
739 (setq quote-mark nil) |
|
740 (setcdr (car quotes) (point)) |
|
741 (backward-char 2)))) |
|
742 ;; Prompt -- check if we're starting a new stmt. If so, |
|
743 ;; then collect various info about it. |
|
744 ((string-match doctest-prompt-re elt) |
|
745 (when (and (null quote-mark) (= nesting 0)) |
|
746 (let ((indent (- (match-end 3) (match-end 2)))) |
|
747 (unless (looking-at "[ \t]*\n") |
|
748 (setq stmt-indent indent) |
|
749 (setq stmt-opens-block |
|
750 (looking-at doctest-open-block-re)) |
|
751 (setq stmt-closes-block |
|
752 (looking-at doctest-close-block-re)) |
|
753 (setq stmt-blocks-outdent |
|
754 (looking-at doctest-no-outdent-re)))))) |
|
755 ;; Open paren -- increment nesting, and update indent-stack. |
|
756 ((string-match "(\\|\\[\\|{" elt-first-char) |
|
757 (let ((elt-pos (point)) |
|
758 (at-eol (looking-at "[ \t]*\n")) |
|
759 (indent 0)) |
|
760 (save-excursion |
|
761 (re-search-backward doctest-prompt-re) |
|
762 (if at-eol |
|
763 (setq indent (+ 4 (- (match-end 3) (match-end 2)))) |
|
764 (setq indent (- elt-pos (match-end 2)))) |
|
765 (push indent indent-stack))) |
|
766 (setq nesting (+ nesting 1))) |
|
767 ;; Close paren -- decrement nesting, and pop indent-stack. |
|
768 ((string-match ")\\|\\]\\|}" elt-first-char) |
|
769 (setq indent-stack (cdr indent-stack)) |
|
770 (setq nesting (max 0 (- nesting 1)))) |
|
771 ;; Open quote -- set quote-mark. |
|
772 ((string-match "\"\\|\'" elt-first-char) |
|
773 (push (cons (- (point) (length elt)) nil) quotes) |
|
774 (setq quote-mark elt))))) |
|
775 |
|
776 (let* ((continuation-indent |
|
777 (cond |
|
778 (quote-mark 0) |
|
779 ((> nesting 0) (if (null indent-stack) 0 (car indent-stack))) |
|
780 (t nil))) |
|
781 (result |
|
782 (list stmt-indent continuation-indent |
|
783 stmt-opens-block stmt-closes-block |
|
784 stmt-blocks-outdent quotes))) |
|
785 (if debug (doctest-debug "Debug: %s" result)) |
|
786 result))))))) |
|
787 |
|
788 (defun doctest-current-source-line-margin () |
|
789 "Return the pre-prompt margin to use for this source line. This is |
|
790 copied from the most recent source line, or set to |
|
791 `doctest-default-margin' if there are no preceeding source lines." |
|
792 (save-excursion |
|
793 (save-restriction |
|
794 (when (doctest-in-mmm-docstring-overlay) |
|
795 (doctest-narrow-to-mmm-overlay)) |
|
796 (beginning-of-line) |
|
797 (forward-line -1) |
|
798 (while (and (not (doctest-on-source-line-p)) |
|
799 (re-search-backward doctest-prompt-re nil t)))) |
|
800 (cond ((looking-at doctest-prompt-re) |
|
801 (- (match-end 1) (match-beginning 1))) |
|
802 ((doctest-in-mmm-docstring-overlay) |
|
803 (doctest-default-margin-in-mmm-docstring-overlay)) |
|
804 (t |
|
805 doctest-default-margin)))) |
|
806 |
|
807 (defun doctest-electric-backspace () |
|
808 "Delete the preceeding character, level of indentation, or |
|
809 prompt. |
|
810 |
|
811 If point is at the leftmost column, delete the preceding newline. |
|
812 |
|
813 Otherwise, if point is at the first non-whitespace character |
|
814 following an indented source line's prompt, then reduce the |
|
815 indentation to the next multiple of 4; and update the source line's |
|
816 prompt, when necessary. |
|
817 |
|
818 Otherwise, if point is at the first non-whitespace character |
|
819 following an unindented source line's prompt, then remove the |
|
820 prompt (converting the line to an output line or text line). |
|
821 |
|
822 Otherwise, if point is at the first non-whitespace character of a |
|
823 line, the delete the line's indentation. |
|
824 |
|
825 Otherwise, delete the preceeding character. |
|
826 " |
|
827 (interactive "*") |
|
828 (cond |
|
829 ;; Beginning of line: delete preceeding newline. |
|
830 ((bolp) (backward-delete-char 1)) |
|
831 |
|
832 ;; First non-ws char following prompt: dedent or remove prompt. |
|
833 ((and (looking-at "[^ \t\n]\\|$") (doctest-looking-back doctest-prompt-re)) |
|
834 (let* ((prompt-beg (match-beginning 2)) |
|
835 (indent-beg (match-beginning 3)) (indent-end (match-end 3)) |
|
836 (old-indent (- indent-end indent-beg)) |
|
837 (new-indent (* (/ (- old-indent 1) 4) 4))) |
|
838 (cond |
|
839 ;; Indented source line: dedent it. |
|
840 ((> old-indent 0) |
|
841 (goto-char indent-beg) |
|
842 (delete-region indent-beg indent-end) |
|
843 (insert-char ?\ new-indent) |
|
844 ;; Change prompt to PS1, when appropriate. |
|
845 (when (and (= new-indent 0) (not (looking-at doctest-outdent-re))) |
|
846 (delete-backward-char 4) |
|
847 (insert-string ">>> "))) |
|
848 ;; Non-indented source line: remove prompt. |
|
849 (t |
|
850 (goto-char indent-end) |
|
851 (delete-region prompt-beg indent-end))))) |
|
852 |
|
853 ;; First non-ws char of a line: delete all indentation. |
|
854 ((and (looking-at "[^ \n\t]\\|$") (doctest-looking-back "^[ \t]+")) |
|
855 (delete-region (match-beginning 0) (match-end 0))) |
|
856 |
|
857 ;; Otherwise: delete a character. |
|
858 (t |
|
859 (backward-delete-char 1)))) |
|
860 |
|
861 (defun doctest-newline-and-indent () |
|
862 "Insert a newline, and indent the new line appropriately. |
|
863 |
|
864 If the current line is a source line containing a bare prompt, |
|
865 then clear the current line, and insert a newline. |
|
866 |
|
867 Otherwise, if the current line is a source line, then insert a |
|
868 newline, and add an appropriately indented prompt to the new |
|
869 line. |
|
870 |
|
871 Otherwise, if the current line is an output line, then insert a |
|
872 newline and indent the new line to match the example's margin. |
|
873 |
|
874 Otherwise, insert a newline. |
|
875 |
|
876 If `doctest-avoid-trailing-whitespace' is true, then clear any |
|
877 whitespace to the left of the point before inserting a newline. |
|
878 " |
|
879 (interactive "*") |
|
880 ;; If we're avoiding trailing spaces, then delete WS before point. |
|
881 (if doctest-avoid-trailing-whitespace |
|
882 (delete-char (- (skip-chars-backward " \t")))) |
|
883 (cond |
|
884 ;; If we're on an empty prompt, delete it. |
|
885 ((doctest-on-empty-source-line-p) |
|
886 (delete-region (match-beginning 0) (match-end 0)) |
|
887 (insert-char ?\n 1)) |
|
888 ;; If we're on a doctest line, add a new prompt. |
|
889 ((doctest-on-source-line-p) |
|
890 (insert-char ?\n 1) |
|
891 (doctest-indent-source-line)) |
|
892 ;; If we're in doctest output, indent to the margin. |
|
893 ((doctest-on-output-line-p) |
|
894 (insert-char ?\n 1) |
|
895 (insert-char ?\ (doctest-current-source-line-margin))) |
|
896 ;; Otherwise, just add a newline. |
|
897 (t (insert-char ?\n 1)))) |
|
898 |
|
899 (defun doctest-electric-colon () |
|
900 "Insert a colon, and dedent the line when appropriate." |
|
901 (interactive "*") |
|
902 (insert-char ?: 1) |
|
903 (when (doctest-on-source-line-p) |
|
904 (doctest-indent-source-line t))) |
|
905 |
|
906 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
907 ;;; Code Execution |
|
908 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
909 |
|
910 (defun doctest-execute () |
|
911 "Run doctest on the current buffer, or on the current docstring |
|
912 if the point is inside an `mmm-mode' `doctest-docstring' region. |
|
913 Display the results in the *doctest-output* buffer." |
|
914 (interactive) |
|
915 (doctest-execute-region (point-min) (point-max) nil t)) |
|
916 |
|
917 (defun doctest-execute-with-diff () |
|
918 "Run doctest on the current buffer, or on the current docstring |
|
919 if the point is inside an `mmm-mode' `doctest-docstring' region. |
|
920 Display the results in the *doctest-output* buffer, using diff format." |
|
921 (interactive) |
|
922 (doctest-execute-region (point-min) (point-max) t t)) |
|
923 |
|
924 (defun doctest-execute-buffer-with-diff () |
|
925 "Run doctest on the current buffer, and display the results in the |
|
926 *doctest-output* buffer, using the diff format." |
|
927 (interactive) |
|
928 (doctest-execute-region (point-min) (point-max) t nil)) |
|
929 |
|
930 (defun doctest-execute-buffer () |
|
931 "Run doctest on the current buffer, and display the results in the |
|
932 *doctest-output* buffer." |
|
933 (interactive) |
|
934 (doctest-execute-region (point-min) (point-max) nil nil)) |
|
935 |
|
936 (defun doctest-execute-region (start end &optional diff |
|
937 check-for-mmm-docstring-overlay) |
|
938 "Run doctest on the current buffer, and display the results in the |
|
939 *doctest-output* buffer." |
|
940 (interactive "r") |
|
941 ;; If it's already running, give the user a chance to restart it. |
|
942 (when (doctest-process-live-p doctest-async-process) |
|
943 (when (y-or-n-p "Doctest is already running. Restart it? ") |
|
944 (doctest-cancel-async-process) |
|
945 (message "Killing doctest..."))) |
|
946 (cond |
|
947 ((and doctest-async (doctest-process-live-p doctest-async-process)) |
|
948 (message "Can't run two doctest processes at once!")) |
|
949 (t |
|
950 (let* ((results-buf-name (doctest-results-buffer-name)) |
|
951 (in-docstring (and check-for-mmm-docstring-overlay |
|
952 (doctest-in-mmm-docstring-overlay))) |
|
953 (temp (doctest-temp-name)) (dir doctest-temp-directory) |
|
954 (input-file (expand-file-name (concat temp ".py") dir)) |
|
955 (globs-file (when in-docstring |
|
956 (expand-file-name (concat temp "-globs.py") dir))) |
|
957 (cur-buf (current-buffer)) |
|
958 (in-buf (get-buffer-create "*doctest-input*")) |
|
959 (script (doctest-script input-file globs-file diff))) |
|
960 ;; If we're in a docstring, narrow start & end. |
|
961 (when in-docstring |
|
962 (let ((bounds (doctest-mmm-overlay-bounds))) |
|
963 (setq start (max start (car bounds)) |
|
964 end (min end (cdr bounds))))) |
|
965 ;; Write the doctests to a file. |
|
966 (save-excursion |
|
967 (goto-char (min start end)) |
|
968 (let ((lineno (doctest-line-number))) |
|
969 (set-buffer in-buf) |
|
970 ;; Add blank lines, to keep line numbers the same: |
|
971 (dotimes (n (- lineno 1)) (insert-string "\n")) |
|
972 ;; Add the selected region |
|
973 (insert-buffer-substring cur-buf start end) |
|
974 ;; Write it to a file |
|
975 (write-file input-file))) |
|
976 ;; If requested, write the buffer to a file for use as globs. |
|
977 (when globs-file |
|
978 (let ((cur-buf-start (point-min)) (cur-buf-end (point-max))) |
|
979 (save-excursion |
|
980 (set-buffer in-buf) |
|
981 (delete-region (point-min) (point-max)) |
|
982 (insert-buffer-substring cur-buf cur-buf-start cur-buf-end) |
|
983 (write-file globs-file)))) |
|
984 ;; Dispose of in-buf (we're done with it now. |
|
985 (kill-buffer in-buf) |
|
986 ;; Prepare the results buffer. Clear it, if it contains |
|
987 ;; anything, and set its mode. |
|
988 (setq doctest-results-buffer (get-buffer-create results-buf-name)) |
|
989 (save-excursion |
|
990 (set-buffer doctest-results-buffer) |
|
991 (toggle-read-only 0) |
|
992 (delete-region (point-min) (point-max)) |
|
993 (doctest-results-mode) |
|
994 (setq doctest-source-buffer cur-buf) |
|
995 ) |
|
996 ;; Add markers to examples, and record what line number each one |
|
997 ;; starts at. That way, if the input buffer is edited, we can |
|
998 ;; still find corresponding examples in the output. |
|
999 (doctest-mark-examples) |
|
1000 |
|
1001 ;; Run doctest |
|
1002 (cond (doctest-async |
|
1003 ;; Asynchronous mode: |
|
1004 (let ((process (start-process "*doctest*" doctest-results-buffer |
|
1005 doctest-python-command |
|
1006 "-c" script))) |
|
1007 ;; Store some information about the process. |
|
1008 (setq doctest-async-process-buffer cur-buf) |
|
1009 (setq doctest-async-process process) |
|
1010 (push input-file doctest-async-process-tempfiles) |
|
1011 (when globs-file |
|
1012 (push globs-file doctest-async-process-tempfiles)) |
|
1013 ;; Set up a sentinel to respond when it's done running. |
|
1014 (set-process-sentinel process 'doctest-async-process-sentinel) |
|
1015 |
|
1016 ;; Show the output window. |
|
1017 (let ((w (display-buffer doctest-results-buffer))) |
|
1018 (when doctest-follow-output |
|
1019 ;; Insert a newline, which will move the buffer's |
|
1020 ;; point past the process's mark -- this causes the |
|
1021 ;; window to scroll as new output is generated. |
|
1022 (save-current-buffer |
|
1023 (set-buffer doctest-results-buffer) |
|
1024 (insert-string "\n") |
|
1025 (set-window-point w (point))))) |
|
1026 |
|
1027 ;; Let the user know the process is running. |
|
1028 (doctest-update-mode-line ":running") |
|
1029 (message "Running doctest..."))) |
|
1030 (t |
|
1031 ;; Synchronous mode: |
|
1032 (call-process doctest-python-command nil |
|
1033 doctest-results-buffer t "-c" script) |
|
1034 (doctest-handle-output) |
|
1035 (delete-file input-file) |
|
1036 (when globs-file |
|
1037 (delete-file globs-file)))))))) |
|
1038 |
|
1039 (defun doctest-handle-output () |
|
1040 "This function, which is called after the 'doctest' process spawned |
|
1041 by doctest-execute-buffer has finished, checks the doctest results |
|
1042 buffer. If that buffer is empty, it reports no errors and hides it; |
|
1043 if that buffer is not empty, it reports that errors occured, displays |
|
1044 the buffer, and runs doctest-postprocess-results." |
|
1045 ;; If any tests failed, display them. |
|
1046 (cond ((not (buffer-live-p doctest-results-buffer)) |
|
1047 (doctest-warn "Results buffer not found!")) |
|
1048 ((> (buffer-size doctest-results-buffer) 1) |
|
1049 (display-buffer doctest-results-buffer) |
|
1050 (doctest-postprocess-results) |
|
1051 (let ((num (length doctest-example-markers))) |
|
1052 (message "%d doctest example%s failed!" num |
|
1053 (if (= num 1) "" "s")))) |
|
1054 (t |
|
1055 (display-buffer doctest-results-buffer) |
|
1056 (delete-windows-on doctest-results-buffer) |
|
1057 (message "All doctest examples passed!")))) |
|
1058 |
|
1059 (defun doctest-async-process-sentinel (process state) |
|
1060 "A process sentinel, called when the asynchronous doctest process |
|
1061 completes, which calls doctest-handle-output." |
|
1062 ;; Check to make sure we got the process we're expecting. On |
|
1063 ;; some operating systems, this will end up getting called twice |
|
1064 ;; when we use doctest-cancel-async-process; this check keeps us |
|
1065 ;; from trying to clean up after the same process twice (since we |
|
1066 ;; set doctest-async-process to nil when we're done). |
|
1067 (when (and (equal process doctest-async-process) |
|
1068 (buffer-live-p doctest-async-process-buffer)) |
|
1069 (save-current-buffer |
|
1070 (set-buffer doctest-async-process-buffer) |
|
1071 (cond ((not (buffer-live-p doctest-results-buffer)) |
|
1072 (doctest-warn "Results buffer not found!")) |
|
1073 ((equal state "finished\n") |
|
1074 (doctest-handle-output) |
|
1075 (let ((window (get-buffer-window |
|
1076 doctest-async-process-buffer t))) |
|
1077 (when window (set-window-point window (point))))) |
|
1078 ((equal state "killed\n") |
|
1079 (message "Doctest killed.")) |
|
1080 (t |
|
1081 (message "Doctest failed -- %s" state) |
|
1082 (display-buffer doctest-results-buffer))) |
|
1083 (doctest-update-mode-line "") |
|
1084 (while doctest-async-process-tempfiles |
|
1085 (delete-file (pop doctest-async-process-tempfiles))) |
|
1086 (setq doctest-async-process nil)))) |
|
1087 |
|
1088 (defun doctest-cancel-async-process () |
|
1089 "If a doctest process is running, then kill it." |
|
1090 (interactive "") |
|
1091 (when (doctest-process-live-p doctest-async-process) |
|
1092 ;; Update the modeline |
|
1093 (doctest-update-mode-line ":killing") |
|
1094 ;; Kill the process. |
|
1095 (kill-process doctest-async-process) |
|
1096 ;; Run the sentinel. (Depending on what OS we're on, the sentinel |
|
1097 ;; may end up getting called once or twice.) |
|
1098 (doctest-async-process-sentinel doctest-async-process "killed\n") |
|
1099 )) |
|
1100 |
|
1101 (defun doctest-postprocess-results () |
|
1102 "Post-process the doctest results buffer: check what version of |
|
1103 doctest was used, and set doctest-results-py-version accordingly; |
|
1104 turn on read-only mode; filter the example markers; hide the example |
|
1105 source (if `doctest-hide-example-source' is non-nil); and select the |
|
1106 first failure." |
|
1107 (save-excursion |
|
1108 (set-buffer doctest-results-buffer) |
|
1109 ;; Check if we're using an old doctest version. |
|
1110 (goto-char (point-min)) |
|
1111 (if (re-search-forward doctest-py21-results-re nil t) |
|
1112 (setq doctest-results-py-version 'py21) |
|
1113 (setq doctest-results-py-version 'py24)) |
|
1114 ;; Turn on read-only mode. |
|
1115 (toggle-read-only t)) |
|
1116 |
|
1117 (doctest-filter-example-markers) |
|
1118 (if doctest-hide-example-source |
|
1119 (doctest-hide-example-source)) |
|
1120 (doctest-next-failure 1)) |
|
1121 |
|
1122 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1123 ;;; Markers |
|
1124 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1125 |
|
1126 (defun doctest-mark-examples () |
|
1127 "Add a marker at the beginning of every (likely) example in the |
|
1128 input buffer; and create a list, `doctest-example-markers', |
|
1129 which maps from markers to the line numbers they originally occured |
|
1130 on. This will allow us to find the corresponding example in the |
|
1131 doctest output, even if the input buffer is edited." |
|
1132 (dolist (marker-info doctest-example-markers) |
|
1133 (set-marker (car marker-info) nil)) |
|
1134 (setq doctest-example-markers '()) |
|
1135 (save-excursion |
|
1136 (goto-char (point-min)) |
|
1137 (while (re-search-forward "^ *>>> " nil t) |
|
1138 (backward-char 4) |
|
1139 (push (cons (point-marker) (doctest-line-number)) |
|
1140 doctest-example-markers) |
|
1141 (forward-char 4)))) |
|
1142 |
|
1143 (defun doctest-filter-example-markers () |
|
1144 "Remove any entries from `doctest-example-markers' that do not |
|
1145 correspond to a failed example." |
|
1146 (let ((filtered nil) (markers doctest-example-markers)) |
|
1147 (save-excursion |
|
1148 (set-buffer doctest-results-buffer) |
|
1149 (goto-char (point-max)) |
|
1150 (while (re-search-backward (doctest-results-loc-re) nil t) |
|
1151 (let ((lineno (string-to-int (match-string 1)))) |
|
1152 (when (equal doctest-results-py-version 'py21) |
|
1153 (setq lineno (+ lineno 1))) |
|
1154 (while (and markers (< lineno (cdar markers))) |
|
1155 (set-marker (caar markers) nil) |
|
1156 (setq markers (cdr markers))) |
|
1157 (if (and markers (= lineno (cdar markers))) |
|
1158 (push (pop markers) filtered) |
|
1159 (doctest-warn "Example expected on line %d but not found %s" |
|
1160 lineno markers))))) |
|
1161 (dolist (marker-info markers) |
|
1162 (set-marker (car marker-info) nil)) |
|
1163 (setq doctest-example-markers filtered))) |
|
1164 |
|
1165 (defun doctest-prev-example-marker () |
|
1166 "Helper for doctest-replace-output: move to the preceeding example |
|
1167 marker, and return the corresponding 'original' lineno. If none is |
|
1168 found, return nil." |
|
1169 (let ((lineno nil) |
|
1170 (pos nil)) |
|
1171 (save-excursion |
|
1172 (end-of-line) |
|
1173 (when (re-search-backward "^\\( *\\)>>> " nil t) |
|
1174 (goto-char (match-end 1)) |
|
1175 (dolist (marker-info doctest-example-markers) |
|
1176 (when (= (marker-position (car marker-info)) (point)) |
|
1177 (setq lineno (cdr marker-info)) |
|
1178 (setq pos (point)))))) |
|
1179 (unless (null lineno) |
|
1180 (goto-char pos) |
|
1181 lineno))) |
|
1182 |
|
1183 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1184 ;;; Navigation |
|
1185 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1186 |
|
1187 (defun doctest-next-failure (count) |
|
1188 "Move to the top of the next failing example, and highlight the |
|
1189 example's failure description in *doctest-output*." |
|
1190 (interactive "p") |
|
1191 (cond |
|
1192 ((and doctest-async (doctest-process-live-p doctest-async-process)) |
|
1193 (message "Wait for doctest to finish running!")) |
|
1194 ((not (doctest-results-buffer-valid-p)) |
|
1195 (message "Run doctest first! (C-c C-c)")) |
|
1196 ((equal count 0) |
|
1197 t) |
|
1198 (t |
|
1199 (let ((marker nil) (example-markers doctest-example-markers) |
|
1200 (results-window (display-buffer doctest-results-buffer))) |
|
1201 (save-excursion |
|
1202 (set-buffer doctest-results-buffer) |
|
1203 ;; Pick up where we left off. |
|
1204 ;; (nb: doctest-selected-failure is buffer-local) |
|
1205 (goto-char (or doctest-selected-failure (point-min))) |
|
1206 ;; Skip past anything on *this* line. |
|
1207 (if (>= count 0) (end-of-line) (beginning-of-line)) |
|
1208 ;; Look for the next failure |
|
1209 (when |
|
1210 (if (>= count 0) |
|
1211 (re-search-forward (doctest-results-loc-re) nil t count) |
|
1212 (re-search-backward (doctest-results-loc-re) nil t (- count))) |
|
1213 ;; We found a failure: |
|
1214 (let ((old-selected-failure doctest-selected-failure)) |
|
1215 (beginning-of-line) |
|
1216 ;; Extract the line number for the doctest file. |
|
1217 (let ((orig-lineno (string-to-int (match-string 1)))) |
|
1218 (when (equal doctest-results-py-version 'py21) |
|
1219 (setq orig-lineno (+ orig-lineno 1))) |
|
1220 (dolist (marker-info example-markers) |
|
1221 (when (= orig-lineno (cdr marker-info)) |
|
1222 (setq marker (car marker-info))))) |
|
1223 |
|
1224 ;; Update the window cursor. |
|
1225 (beginning-of-line) |
|
1226 (set-window-point results-window (point)) |
|
1227 ;; Store our position for next time. |
|
1228 (setq doctest-selected-failure (point)) |
|
1229 ;; Update selection. |
|
1230 (doctest-fontify-line old-selected-failure) |
|
1231 (doctest-fontify-line doctest-selected-failure)))) |
|
1232 |
|
1233 (cond |
|
1234 ;; We found a failure -- move point to the selected failure. |
|
1235 (marker |
|
1236 (goto-char (marker-position marker)) |
|
1237 (beginning-of-line)) |
|
1238 ;; We didn't find a failure, but there is one -- wrap. |
|
1239 ((> (length doctest-example-markers) 0) |
|
1240 (if (>= count 0) (doctest-first-failure) (doctest-last-failure))) |
|
1241 ;; We didn't find a failure -- alert the user. |
|
1242 (t (message "No failures found!"))))))) |
|
1243 |
|
1244 (defun doctest-prev-failure (count) |
|
1245 "Move to the top of the previous failing example, and highlight |
|
1246 the example's failure description in *doctest-output*." |
|
1247 (interactive "p") |
|
1248 (doctest-next-failure (- count))) |
|
1249 |
|
1250 (defun doctest-first-failure () |
|
1251 "Move to the top of the first failing example, and highlight |
|
1252 the example's failure description in *doctest-output*." |
|
1253 (interactive) |
|
1254 (if (buffer-live-p doctest-results-buffer) |
|
1255 (save-excursion |
|
1256 (set-buffer doctest-results-buffer) |
|
1257 (let ((old-selected-failure doctest-selected-failure)) |
|
1258 (setq doctest-selected-failure (point-min)) |
|
1259 (doctest-fontify-line old-selected-failure)))) |
|
1260 (doctest-next-failure 1)) |
|
1261 |
|
1262 (defun doctest-last-failure () |
|
1263 "Move to the top of the last failing example, and highlight |
|
1264 the example's failure description in *doctest-output*." |
|
1265 (interactive) |
|
1266 (if (buffer-live-p doctest-results-buffer) |
|
1267 (save-excursion |
|
1268 (set-buffer doctest-results-buffer) |
|
1269 (let ((old-selected-failure doctest-selected-failure)) |
|
1270 (setq doctest-selected-failure (point-max)) |
|
1271 (doctest-fontify-line old-selected-failure)))) |
|
1272 (doctest-next-failure -1)) |
|
1273 |
|
1274 (defun doctest-select-failure () |
|
1275 "Move to the top of the currently selected example, and select that |
|
1276 example in the source buffer. Intended for use in the results |
|
1277 buffer." |
|
1278 (interactive) |
|
1279 (re-search-backward doctest-results-divider-re) |
|
1280 (let ((old-selected-failure doctest-selected-failure)) |
|
1281 (setq doctest-selected-failure (point)) |
|
1282 (doctest-fontify-line doctest-selected-failure) |
|
1283 (doctest-fontify-line old-selected-failure)) |
|
1284 (pop-to-buffer doctest-source-buffer) |
|
1285 (doctest-next-failure 1)) |
|
1286 |
|
1287 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1288 ;;; Replace Output |
|
1289 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1290 |
|
1291 (defun doctest-replace-output () |
|
1292 "Move to the top of the closest example, and replace its output |
|
1293 with the 'got' output from the *doctest-output* buffer. An error is |
|
1294 displayed if the chosen example is not listed in *doctest-output*, or |
|
1295 if the 'expected' output for the example does not exactly match the |
|
1296 output listed in the source buffer. The user is asked to confirm the |
|
1297 replacement." |
|
1298 (interactive) |
|
1299 ;; Move to the beginning of the example. |
|
1300 (cond |
|
1301 ((and doctest-async (doctest-process-live-p doctest-async-process)) |
|
1302 (message "Wait for doctest to finish running!")) |
|
1303 ((not (doctest-results-buffer-valid-p)) |
|
1304 (message "Run doctest first! (C-c C-c)")) |
|
1305 ((save-excursion (set-buffer doctest-results-buffer) |
|
1306 (equal doctest-results-py-version 'py21)) |
|
1307 (error "doctest-replace-output requires python 2.4+")) |
|
1308 (t |
|
1309 (save-excursion |
|
1310 (save-restriction |
|
1311 (when (doctest-in-mmm-docstring-overlay) |
|
1312 (doctest-narrow-to-mmm-overlay)) |
|
1313 |
|
1314 (let* ((orig-buffer (current-buffer)) |
|
1315 ;; Find an example, and look up its original lineno. |
|
1316 (lineno (doctest-prev-example-marker)) |
|
1317 ;; Find the example's indentation. |
|
1318 (prompt-indent (doctest-line-indentation))) |
|
1319 |
|
1320 ;; Switch to the output buffer, and look for the example. |
|
1321 ;; If we don't find one, complain. |
|
1322 (cond |
|
1323 ((null lineno) (message "Doctest example not found")) |
|
1324 (t |
|
1325 (set-buffer doctest-results-buffer) |
|
1326 (goto-char (point-min)) |
|
1327 (let ((output-re (format "^File .*, line %s," lineno))) |
|
1328 (when (not (re-search-forward output-re nil t)) |
|
1329 (message "This doctest example did not fail") |
|
1330 (setq lineno nil))))) |
|
1331 |
|
1332 ;; If we didn't find an example, give up. |
|
1333 (when (not (null lineno)) |
|
1334 ;; Get the output's 'expected' & 'got' texts. |
|
1335 (let ((doctest-got nil) (doctest-expected nil) (header nil)) |
|
1336 (while (setq header (doctest-results-next-header)) |
|
1337 (cond |
|
1338 ((equal header "Failed example:") |
|
1339 t) |
|
1340 ((equal header "Expected nothing") |
|
1341 (setq doctest-expected "")) |
|
1342 ((equal header "Expected:") |
|
1343 (unless (re-search-forward "^\\(\\( \\).*\n\\)*" nil t) |
|
1344 (error "Error parsing doctest output")) |
|
1345 (setq doctest-expected (doctest-replace-regexp-in-string |
|
1346 "^ " prompt-indent |
|
1347 (match-string 0)))) |
|
1348 ((equal header "Got nothing") |
|
1349 (setq doctest-got "")) |
|
1350 ((or (equal header "Got:") (equal header "Exception raised:")) |
|
1351 (unless (re-search-forward "^\\(\\( \\).*\n\\)*" nil t) |
|
1352 (error "Error parsing doctest output")) |
|
1353 (setq doctest-got (doctest-replace-regexp-in-string |
|
1354 "^ " prompt-indent (match-string 0)))) |
|
1355 ((string-match "^Differences" header) |
|
1356 (error (concat "doctest-replace-output can not be used " |
|
1357 "with diff style output"))) |
|
1358 (t (error "Unexpected header %s" header)))) |
|
1359 |
|
1360 ;; Go back to the source buffer. |
|
1361 (set-buffer orig-buffer) |
|
1362 |
|
1363 ;; Skip ahead to the output. |
|
1364 (beginning-of-line) |
|
1365 (unless (re-search-forward "^ *>>>.*") |
|
1366 (error "Error parsing doctest output")) |
|
1367 (re-search-forward "\\(\n *\\.\\.\\..*\\)*\n?") |
|
1368 (when (looking-at "\\'") (insert-char ?\n)) |
|
1369 |
|
1370 ;; Check that the output matches. |
|
1371 (let ((start (point)) end) |
|
1372 (cond ((re-search-forward "^ *\\(>>>.*\\|$\\)" nil t) |
|
1373 (setq end (match-beginning 0))) |
|
1374 (t |
|
1375 (goto-char (point-max)) |
|
1376 (insert-string "\n") |
|
1377 (setq end (point-max)))) |
|
1378 (when (and doctest-expected |
|
1379 (not (equal (buffer-substring start end) |
|
1380 doctest-expected))) |
|
1381 (warn "{%s} {%s}" (buffer-substring start end) |
|
1382 doctest-expected) |
|
1383 (error (concat "This example's output has been modified " |
|
1384 "since doctest was last run"))) |
|
1385 (setq doctest-expected (buffer-substring start end)) |
|
1386 (goto-char end)) |
|
1387 |
|
1388 ;; Trim exceptions |
|
1389 (when (and doctest-trim-exceptions |
|
1390 (string-match doctest-traceback-re |
|
1391 doctest-got)) |
|
1392 (let ((s1 0) (e1 (match-end 1)) |
|
1393 (s2 (match-beginning 2)) (e2 (match-end 2)) |
|
1394 (s3 (match-beginning 3)) (e3 (length doctest-got))) |
|
1395 (setq doctest-got |
|
1396 (concat (substring doctest-got s1 e1) |
|
1397 (substring doctest-got s2 e2) " . . .\n" |
|
1398 (substring doctest-got s3 e3))))) |
|
1399 |
|
1400 ;; Confirm it with the user. |
|
1401 (let ((confirm-buffer (get-buffer-create "*doctest-confirm*"))) |
|
1402 (set-buffer confirm-buffer) |
|
1403 ;; Erase anything left over in the buffer. |
|
1404 (delete-region (point-min) (point-max)) |
|
1405 ;; Write a confirmation message |
|
1406 (if (equal doctest-expected "") |
|
1407 (insert-string "Replace nothing\n") |
|
1408 (insert-string (concat "Replace:\n" doctest-expected))) |
|
1409 (if (equal doctest-got "") |
|
1410 (insert-string "With nothing\n") |
|
1411 (insert-string (concat "With:\n" doctest-got))) |
|
1412 (let ((confirm-window (display-buffer confirm-buffer))) |
|
1413 ;; Shrink the confirm window. |
|
1414 (shrink-window-if-larger-than-buffer confirm-window) |
|
1415 ;; Return to the original buffer. |
|
1416 (set-buffer orig-buffer) |
|
1417 ;; Match the old expected region. |
|
1418 (when doctest-expected |
|
1419 (search-backward doctest-expected)) |
|
1420 (when (equal doctest-expected "") (backward-char 1)) |
|
1421 ;; Get confirmation & do the replacement |
|
1422 (widen) |
|
1423 (cond ((y-or-n-p "Ok to replace? ") |
|
1424 (when (equal doctest-expected "") (forward-char 1)) |
|
1425 (replace-match doctest-got t t) |
|
1426 (message "Replaced.")) |
|
1427 (t |
|
1428 (message "Replace cancelled."))) |
|
1429 ;; Clean up our confirm window |
|
1430 (kill-buffer confirm-buffer) |
|
1431 (delete-window confirm-window))))))))))) |
|
1432 |
|
1433 (defun doctest-results-next-header () |
|
1434 "Move to the next header in the doctest results buffer, and return |
|
1435 the string contents of that header. If no header is found, return |
|
1436 nil." |
|
1437 (if (re-search-forward (concat doctest-results-header-re "\\|" |
|
1438 doctest-results-divider-re) nil t) |
|
1439 (let ((result (match-string 0))) |
|
1440 (if (string-match doctest-results-header-re result) |
|
1441 result |
|
1442 nil)) |
|
1443 nil)) |
|
1444 |
|
1445 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1446 ;;; mmm-mode support |
|
1447 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1448 ;; MMM Mode is a minor mode for Emacs which allows Multiple Major |
|
1449 ;; Modes to coexist in a single buffer. |
|
1450 |
|
1451 ;;;###autoload |
|
1452 (defun doctest-register-mmm-classes (&optional add-mode-ext-classes |
|
1453 fix-mmm-fontify-region-bug) |
|
1454 "Register doctest's mmm classes, allowing doctest to be used as a |
|
1455 submode region in other major modes, such as python-mode and rst-mode. |
|
1456 Two classes are registered: |
|
1457 |
|
1458 `doctest-docstring' |
|
1459 |
|
1460 Used to edit docstrings containing doctest examples in python- |
|
1461 mode. Docstring submode regions start and end with triple-quoted |
|
1462 strings (\"\"\"). In order to avoid confusing start-string |
|
1463 markers and end-string markers, all triple-quote strings in the |
|
1464 buffer are treated as submode regions (even if they're not |
|
1465 actually docstrings). Use (C-c % C-d) to insert a new doctest- |
|
1466 docstring region. When `doctest-execute' (C-c C-c) is called |
|
1467 inside a doctest-docstring region, it executes just the current |
|
1468 docstring. The globals for this execution are constructed by |
|
1469 importing the current buffer's contents in Python. |
|
1470 |
|
1471 `doctest-example' |
|
1472 |
|
1473 Used to edit doctest examples in text-editing modes, such as |
|
1474 `rst-mode' or `text-mode'. Docstring submode regions start with |
|
1475 optionally indented prompts (>>>) and end with blank lines. Use |
|
1476 (C-c % C-e) to insert a new doctest-example region. When |
|
1477 `doctest-execute' (C-c C-c) is called inside a doctest-example |
|
1478 region, it executes all examples in the buffer. |
|
1479 |
|
1480 If ADD-MODE-EXT-CLASSES is true, then register the new classes in |
|
1481 `mmm-mode-ext-classes-alist', which will cause them to be used by |
|
1482 default in the following modes: |
|
1483 |
|
1484 doctest-docstring: python-mode |
|
1485 doctest-example: rst-mode |
|
1486 |
|
1487 If FIX-MMM-FONTIFY-REGION-BUG is true, then register a hook that will |
|
1488 fix a bug in `mmm-fontify-region' that affects some (but not all) |
|
1489 versions of emacs. (See `doctest-fixed-mmm-fontify-region' for more |
|
1490 info.)" |
|
1491 (interactive) |
|
1492 (require 'mmm-auto) |
|
1493 (mmm-add-classes |
|
1494 '( |
|
1495 ;; === doctest-docstring === |
|
1496 (doctest-docstring :submode doctest-mode |
|
1497 |
|
1498 ;; The front is any triple-quote. Include it in the submode region, |
|
1499 ;; to prevent clashes between the two syntax tables over quotes. |
|
1500 :front "\\(\"\"\"\\|'''\\)" :include-front t |
|
1501 |
|
1502 ;; The back matches the front. Include just the first character |
|
1503 ;; of the quote. If we didn't include at least one quote, then |
|
1504 ;; the outer modes quote-counting would be thrown off. But if |
|
1505 ;; we include all three, we run into a bug in mmm-mode. See |
|
1506 ;; <http://tinyurl.com/2fa83w> for more info about the bug. |
|
1507 :save-matches t :back "~1" :back-offset 1 :end-not-begin t |
|
1508 |
|
1509 ;; Define a skeleton for entering new docstrings. |
|
1510 :insert ((?d docstring nil @ "\"\"" @ "\"" \n |
|
1511 _ \n "\"" @ "\"\"" @))) |
|
1512 |
|
1513 ;; === doctest-example === |
|
1514 (doctest-example |
|
1515 :submode doctest-mode |
|
1516 ;; The front is an optionally indented prompt. |
|
1517 :front "^[ \t]*>>>" :include-front t |
|
1518 ;; The back is a blank line. |
|
1519 :back "^[ \t]*$" |
|
1520 ;; Define a skeleton for entering new docstrings. |
|
1521 :insert ((?e doctest-example nil |
|
1522 @ @ " >>> " _ "\n\n" @ @))))) |
|
1523 |
|
1524 ;; Register some local variables that need to be saved. |
|
1525 (add-to-list 'mmm-save-local-variables |
|
1526 '(doctest-results-buffer buffer)) |
|
1527 (add-to-list 'mmm-save-local-variables |
|
1528 '(doctest-example-markers buffer)) |
|
1529 |
|
1530 ;; Register association with modes, if requested. |
|
1531 (when add-mode-ext-classes |
|
1532 (mmm-add-mode-ext-class 'python-mode nil 'doctest-docstring) |
|
1533 (mmm-add-mode-ext-class 'rst-mode nil 'doctest-example)) |
|
1534 |
|
1535 ;; Fix the buggy mmm-fontify-region, if requested. |
|
1536 (when fix-mmm-fontify-region-bug |
|
1537 (add-hook 'mmm-mode-hook 'doctest-fix-mmm-fontify-region-bug))) |
|
1538 |
|
1539 (defvar doctest-old-mmm-fontify-region 'nil |
|
1540 "Used to hold the original definition of `mmm-fontify-region' when it |
|
1541 is rebound by `doctest-fix-mmm-fontify-region-bug'.") |
|
1542 |
|
1543 (defun doctest-fix-mmm-fontify-region-bug () |
|
1544 "A function for `mmm-mode-hook' which fixes a potential bug in |
|
1545 `mmm-fontify-region' by using `doctest-fixed-mmm-fontify-region' |
|
1546 instead. (See `doctest-fixed-mmm-fontify-region' for more info.)" |
|
1547 (setq font-lock-fontify-region-function |
|
1548 'doctest-fixed-mmm-fontify-region)) |
|
1549 |
|
1550 (defun doctest-fixed-mmm-fontify-region (start stop &optional loudly) |
|
1551 "A replacement for `mmm-fontify-region', which fixes a bug caused by |
|
1552 versions of emacs where post-command-hooks are run *before* |
|
1553 fontification. `mmm-mode' assumes that its post-command-hook will be |
|
1554 run after fontification; and if it's not, then mmm-mode can end up |
|
1555 with the wrong local variables, keymap, etc. after fontification. We |
|
1556 fix that here by redefining `mmm-fontify-region' to remember what |
|
1557 submode overlay it started in; and to return to that overlay after |
|
1558 fontification is complete. The original definition of |
|
1559 `mmm-fontify-region' is stored in `doctest-old-mmm-fontify-region'." |
|
1560 (let ((overlay mmm-current-overlay)) |
|
1561 (mmm-fontify-region start stop loudly) |
|
1562 (if (and overlay (or (< (point) (overlay-start overlay)) |
|
1563 (> (point) (overlay-end overlay)))) |
|
1564 (goto-char (overlay-start overlay))) |
|
1565 (mmm-update-submode-region))) |
|
1566 |
|
1567 (defun doctest-in-mmm-docstring-overlay () |
|
1568 (and (featurep 'mmm-auto) |
|
1569 (mmm-overlay-at (point)) |
|
1570 (save-excursion |
|
1571 (goto-char (overlay-start (mmm-overlay-at (point)))) |
|
1572 (looking-at "\"\"\"\\|\'\'\'")))) |
|
1573 |
|
1574 (defun doctest-narrow-to-mmm-overlay () |
|
1575 "If we're in an mmm-mode overlay, then narrow to that overlay. |
|
1576 This is useful, e.g., to keep from interpreting the close-quote of a |
|
1577 docstring as part of the example's output." |
|
1578 (let ((bounds (doctest-mmm-overlay-bounds))) |
|
1579 (when bounds (narrow-to-region (car bounds) (cdr bounds))))) |
|
1580 |
|
1581 (defun doctest-default-margin-in-mmm-docstring-overlay () |
|
1582 (save-excursion |
|
1583 (let ((pos (car (doctest-mmm-overlay-bounds)))) |
|
1584 (goto-char pos) |
|
1585 (when (doctest-looking-back "\"\"\"\\|\'\'\'") |
|
1586 (setq pos (- pos 3))) |
|
1587 (beginning-of-line) |
|
1588 (- pos (point))))) |
|
1589 |
|
1590 (defun doctest-mmm-overlay-bounds () |
|
1591 (when (featurep 'mmm-auto) |
|
1592 (let ((overlay (mmm-overlay-at (point)))) |
|
1593 (when overlay |
|
1594 (let ((start (overlay-start overlay)) |
|
1595 (end (overlay-end overlay))) |
|
1596 (when (doctest-in-mmm-docstring-overlay) |
|
1597 (save-excursion |
|
1598 (goto-char start) |
|
1599 (re-search-forward "[\"\']*") |
|
1600 (setq start (point)) |
|
1601 (goto-char end) |
|
1602 (while (doctest-looking-back "[\"\']") |
|
1603 (backward-char 1)) |
|
1604 (setq end (point)))) |
|
1605 (cons start end)))))) |
|
1606 |
|
1607 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1608 ;;; Helper functions |
|
1609 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1610 |
|
1611 (defun doctest-on-source-line-p (&optional prompt) |
|
1612 "Return true if the current line is a source line. The optional |
|
1613 argument prompt can be used to specify which type of source |
|
1614 line (... or >>>)." |
|
1615 (save-excursion |
|
1616 (beginning-of-line) |
|
1617 ;; Check if we're looking at a prompt (of the right type). |
|
1618 (when (and (looking-at doctest-prompt-re) |
|
1619 (or (null prompt) |
|
1620 (equal prompt (substring (match-string 2) 0 3)))) |
|
1621 ;; Scan backwards to make sure there's a >>> somewhere. Otherwise, |
|
1622 ;; this might be a '...' in the text or in an example's output. |
|
1623 (while (looking-at "^[ \t]*[.][.][.]") |
|
1624 (forward-line -1)) |
|
1625 (looking-at "^[ \t]*>>>")))) |
|
1626 |
|
1627 (defun doctest-on-empty-source-line-p () |
|
1628 "Return true if the current line contains a bare prompt." |
|
1629 (save-excursion |
|
1630 (beginning-of-line) |
|
1631 (and (doctest-on-source-line-p) |
|
1632 (looking-at (concat doctest-prompt-re "$"))))) |
|
1633 |
|
1634 (defun doctest-on-output-line-p () |
|
1635 "Return true if the current line is an output line." |
|
1636 (save-excursion |
|
1637 (beginning-of-line) |
|
1638 ;; The line must not be blank or a source line. |
|
1639 (when (not (or (doctest-on-source-line-p) (looking-at "[ \t]*$"))) |
|
1640 ;; The line must follow a source line, with no intervening blank |
|
1641 ;; lines. |
|
1642 (while (not (or (doctest-on-source-line-p) (looking-at "[ \t]*$") |
|
1643 (= (point) (point-min)))) |
|
1644 (forward-line -1)) |
|
1645 (doctest-on-source-line-p)))) |
|
1646 |
|
1647 (defun doctest-find-output-line (&optional limit) |
|
1648 "Move forward to the next doctest output line (staying within |
|
1649 the given bounds). Return the character position of the doctest |
|
1650 output line if one was found, and false otherwise." |
|
1651 (let ((found-it nil) ; point where we found an output line |
|
1652 (limit (or limit (point-max)))) ; default value for limit |
|
1653 (save-excursion |
|
1654 ;; Keep moving forward, one line at a time, until we find a |
|
1655 ;; doctest output line. |
|
1656 (while (and (not found-it) (< (point) limit) (not (eobp))) |
|
1657 (if (and (not (eolp)) (doctest-on-output-line-p)) |
|
1658 (setq found-it (point)) |
|
1659 (forward-line)))) |
|
1660 ;; If we found a doctest output line, then go to it. |
|
1661 (if found-it (goto-char found-it)))) |
|
1662 |
|
1663 (defun doctest-line-indentation () |
|
1664 "Helper for doctest-replace-output: return the whitespace indentation |
|
1665 at the beginning of this line." |
|
1666 (save-excursion |
|
1667 (end-of-line) |
|
1668 (re-search-backward "^\\( *\\)" nil t) |
|
1669 (match-string 1))) |
|
1670 |
|
1671 (defun doctest-optionflags (&optional diff) |
|
1672 "Return a string describing the optionflags that should be used |
|
1673 by doctest. If DIFF is non-nil, then add the REPORT_UDIFF flag." |
|
1674 (let ((flags "0")) |
|
1675 (dolist (flag doctest-optionflags) |
|
1676 (setq flags (concat flags "|" flag))) |
|
1677 (if diff (concat flags "|" "REPORT_UDIFF") flags))) |
|
1678 |
|
1679 (defun doctest-results-loc-re () |
|
1680 "Return the regexp that should be used to look for doctest example |
|
1681 location markers in doctest's output (based on which version of |
|
1682 doctest was used" |
|
1683 (cond |
|
1684 ((equal doctest-results-py-version 'py21) |
|
1685 doctest-py21-results-loc-re) |
|
1686 ((equal doctest-results-py-version 'py24) |
|
1687 doctest-py24-results-loc-re) |
|
1688 (t (error "bad value for doctest-results-py-version")))) |
|
1689 |
|
1690 (defun doctest-results-buffer-name () |
|
1691 "Return the buffer name that should be used for the doctest results |
|
1692 buffer. This is computed from the variable |
|
1693 `doctest-results-buffer-name'." |
|
1694 (doctest-replace-regexp-in-string |
|
1695 "%[nfN]" |
|
1696 (lambda (sym) |
|
1697 (cond ((equal sym "%n") (buffer-name)) |
|
1698 ((equal sym "%N") (doctest-replace-regexp-in-string |
|
1699 "[.]doctest$" "" (buffer-name) t)) |
|
1700 ((equal sym "%f") (buffer-file-name)))) |
|
1701 doctest-results-buffer-name t)) |
|
1702 |
|
1703 (defun doctest-script (input-file globs-file diff) |
|
1704 "..." |
|
1705 (doctest-replace-regexp-in-string |
|
1706 "%[tnflm]" |
|
1707 (lambda (sym) |
|
1708 (cond ((equal sym "%n") (buffer-name)) |
|
1709 ((equal sym "%f") (buffer-file-name)) |
|
1710 ((equal sym "%l") (doctest-optionflags diff)) |
|
1711 ((equal sym "%t") input-file) |
|
1712 ((equal sym "%m") (or globs-file "")))) |
|
1713 doctest-script t)) |
|
1714 |
|
1715 (defun doctest-hide-example-source () |
|
1716 "Delete the source code listings from the results buffer (since it's |
|
1717 easy enough to see them in the original buffer)" |
|
1718 (save-excursion |
|
1719 (set-buffer doctest-results-buffer) |
|
1720 (toggle-read-only 0) |
|
1721 (goto-char (point-min)) |
|
1722 (while (re-search-forward doctest-example-source-re nil t) |
|
1723 (replace-match "" nil nil)) |
|
1724 (toggle-read-only t))) |
|
1725 |
|
1726 (defun doctest-results-buffer-valid-p () |
|
1727 "Return true if this buffer has a live results buffer; and that |
|
1728 results buffer reports this buffer as its source buffer. (Two |
|
1729 buffers in doctest-mode might point to the same results buffer; |
|
1730 but only one of them will be equal to that results buffer's |
|
1731 source buffer." |
|
1732 (let ((cur-buf (current-buffer))) |
|
1733 (and (buffer-live-p doctest-results-buffer) |
|
1734 (save-excursion |
|
1735 (set-buffer doctest-results-buffer) |
|
1736 (equal cur-buf doctest-source-buffer))))) |
|
1737 |
|
1738 (defun doctest-update-mode-line (value) |
|
1739 "Update the doctest mode line with the given string value. This |
|
1740 is used to display information about asynchronous processes that |
|
1741 are run by doctest-mode." |
|
1742 (setq doctest-mode-line-process |
|
1743 value) |
|
1744 (force-mode-line-update t)) |
|
1745 |
|
1746 (defun doctest-version () |
|
1747 "Echo the current version of `doctest-mode' in the minibuffer." |
|
1748 (interactive) |
|
1749 (message "Using `doctest-mode' version %s" doctest-version)) |
|
1750 |
|
1751 (defun doctest-warn (msg &rest args) |
|
1752 "Display a doctest warning message." |
|
1753 (if (fboundp 'display-warning) |
|
1754 (display-warning 'doctest (apply 'format msg args)) |
|
1755 (apply 'message msg args))) |
|
1756 |
|
1757 (defun doctest-debug (msg &rest args) |
|
1758 "Display a doctest debug message." |
|
1759 (if (fboundp 'display-warning) |
|
1760 (display-warning 'doctest (apply 'format msg args) 'debug) |
|
1761 (apply 'message msg args))) |
|
1762 |
|
1763 (defvar doctest-serial-number 0) ;used if broken-temp-names. |
|
1764 (defun doctest-temp-name () |
|
1765 "Return a new temporary filename, for use in calling doctest." |
|
1766 (if (memq 'broken-temp-names features) |
|
1767 (let |
|
1768 ((sn doctest-serial-number) |
|
1769 (pid (and (fboundp 'emacs-pid) (emacs-pid)))) |
|
1770 (setq doctest-serial-number (1+ doctest-serial-number)) |
|
1771 (if pid |
|
1772 (format "doctest-%d-%d" sn pid) |
|
1773 (format "doctest-%d" sn))) |
|
1774 (make-temp-name "doctest-"))) |
|
1775 |
|
1776 (defun doctest-fontify-line (charpos) |
|
1777 "Run font-lock-fontify-region on the line containing the given |
|
1778 position." |
|
1779 (if (and charpos (functionp 'font-lock-fontify-region)) |
|
1780 (save-excursion |
|
1781 (goto-char charpos) |
|
1782 (let ((beg (progn (beginning-of-line) (point))) |
|
1783 (end (progn (end-of-line) (point)))) |
|
1784 (font-lock-fontify-region beg end))))) |
|
1785 |
|
1786 (defun doctest-do-auto-fill () |
|
1787 "If the current line is a soucre line or an output line, do nothing. |
|
1788 Otherwise, call (do-auto-fill)." |
|
1789 (cond |
|
1790 ;; Don't wrap source lines. |
|
1791 ((doctest-on-source-line-p) nil) |
|
1792 ;; Don't wrap output lines |
|
1793 ((doctest-on-output-line-p) nil) |
|
1794 ;; Wrap all other lines |
|
1795 (t (do-auto-fill)))) |
|
1796 |
|
1797 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1798 ;;; Emacs Compatibility Functions |
|
1799 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1800 ;; Define compatible versions of functions that are defined |
|
1801 ;; for some versions of emacs but not others. |
|
1802 |
|
1803 ;; Backwards compatibility: looking-back |
|
1804 (cond ((fboundp 'looking-back) ;; Emacs 22.x |
|
1805 (defalias 'doctest-looking-back 'looking-back)) |
|
1806 (t |
|
1807 (defun doctest-looking-back (regexp) |
|
1808 "Return true if text before point matches REGEXP." |
|
1809 (save-excursion |
|
1810 (let ((orig-pos (point))) |
|
1811 ;; Search backwards for the regexp. |
|
1812 (if (re-search-backward regexp nil t) |
|
1813 ;; Check if it ends at the original point. |
|
1814 (= orig-pos (match-end 0)))))))) |
|
1815 |
|
1816 ;; Backwards compatibility: replace-regexp-in-string |
|
1817 (cond ((fboundp 'replace-regexp-in-string) |
|
1818 (defalias 'doctest-replace-regexp-in-string 'replace-regexp-in-string)) |
|
1819 (t ;; XEmacs 21.x or Emacs 20.x |
|
1820 (defun doctest-replace-regexp-in-string |
|
1821 (regexp rep string &optional fixedcase literal) |
|
1822 "Replace all matches for REGEXP with REP in STRING." |
|
1823 (let ((start 0)) |
|
1824 (while (and (< start (length string)) |
|
1825 (string-match regexp string start)) |
|
1826 (setq start (+ (match-end 0) 1)) |
|
1827 (let ((newtext (if (functionp rep) |
|
1828 (save-match-data |
|
1829 (funcall rep (match-string 0 string))) |
|
1830 rep))) |
|
1831 (setq string (replace-match newtext fixedcase |
|
1832 literal string))))) |
|
1833 string))) |
|
1834 |
|
1835 ;; Backwards compatibility: line-number |
|
1836 (cond ((fboundp 'line-number) ;; XEmacs |
|
1837 (defalias 'doctest-line-number 'line-number)) |
|
1838 ((fboundp 'line-number-at-pos) ;; Emacs 22.x |
|
1839 (defalias 'doctest-line-number 'line-number-at-pos)) |
|
1840 (t ;; Emacs 21.x |
|
1841 (defun doctest-line-number (&optional pos) |
|
1842 "Return the line number of POS (default=point)." |
|
1843 (1+ (count-lines 1 |
|
1844 (save-excursion (progn (beginning-of-line) |
|
1845 (or pos (point))))))))) |
|
1846 |
|
1847 ;; Backwards compatibility: process-live-p |
|
1848 (cond ((fboundp 'process-live-p) ;; XEmacs |
|
1849 (defalias 'doctest-process-live-p 'process-live-p)) |
|
1850 (t ;; Emacs |
|
1851 (defun doctest-process-live-p (process) |
|
1852 (and (processp process) |
|
1853 (equal (process-status process) 'run))))) |
|
1854 |
|
1855 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1856 ;;; Doctest Results Mode (output of doctest-execute-buffer) |
|
1857 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1858 |
|
1859 ;; Register the font-lock keywords (xemacs) |
|
1860 (put 'doctest-results-mode 'font-lock-defaults |
|
1861 '(doctest-results-font-lock-keywords)) |
|
1862 |
|
1863 ;; Register the font-lock keywords (older versions of gnu emacs) |
|
1864 (when (boundp 'font-lock-defaults-alist) |
|
1865 (add-to-list 'font-lock-defaults-alist |
|
1866 '(doctest-results-mode doctest-results-font-lock-keywords |
|
1867 nil nil nil nil))) |
|
1868 |
|
1869 (defvar doctest-selected-failure nil |
|
1870 "The location of the currently selected failure. |
|
1871 This variable is uffer-local to doctest-results-mode buffers.") |
|
1872 |
|
1873 (defvar doctest-source-buffer nil |
|
1874 "The buffer that spawned this one. |
|
1875 This variable is uffer-local to doctest-results-mode buffers.") |
|
1876 |
|
1877 (defvar doctest-results-py-version nil |
|
1878 "A symbol indicating which version of Python was used to generate |
|
1879 the results in a doctest-results-mode buffer. Can be either the |
|
1880 symbol `py21' or the symbol `py24'. |
|
1881 This variable is uffer-local to doctest-results-mode buffers.") |
|
1882 |
|
1883 ;; Keymap for doctest-results-mode. |
|
1884 (defconst doctest-results-mode-map |
|
1885 (let ((map (make-keymap))) |
|
1886 (define-key map [return] 'doctest-select-failure) |
|
1887 map) |
|
1888 "Keymap for doctest-results-mode.") |
|
1889 |
|
1890 ;; Syntax table for doctest-results-mode. |
|
1891 (defvar doctest-results-mode-syntax-table nil |
|
1892 "Syntax table used in `doctest-results-mode' buffers.") |
|
1893 (when (not doctest-results-mode-syntax-table) |
|
1894 (setq doctest-results-mode-syntax-table (make-syntax-table)) |
|
1895 (dolist (entry '(("(" . "()") ("[" . "(]") ("{" . "(}") |
|
1896 (")" . ")(") ("]" . ")[") ("}" . "){") |
|
1897 ("$%&*+-/<=>|'\"`" . ".") ("_" . "w"))) |
|
1898 (dolist (char (string-to-list (car entry))) |
|
1899 (modify-syntax-entry char (cdr entry) |
|
1900 doctest-results-mode-syntax-table)))) |
|
1901 |
|
1902 ;; Define the mode |
|
1903 (defun doctest-results-mode () |
|
1904 "A major mode used to display the results of running doctest. |
|
1905 See `doctest-mode'. |
|
1906 |
|
1907 \\{doctest-results-mode-map}" |
|
1908 (interactive) |
|
1909 |
|
1910 ;; Declare local variables. |
|
1911 (kill-all-local-variables) |
|
1912 (make-local-variable 'font-lock-defaults) |
|
1913 (make-local-variable 'doctest-selected-failure) |
|
1914 (make-local-variable 'doctest-source-buffer) |
|
1915 (make-local-variable 'doctest-results-py-version) |
|
1916 |
|
1917 ;; Define local variables. |
|
1918 (setq major-mode 'doctest-results-mode |
|
1919 mode-name "Doctest-Results" |
|
1920 mode-line-process 'doctest-mode-line-process |
|
1921 font-lock-defaults '(doctest-results-font-lock-keywords |
|
1922 nil nil nil nil)) |
|
1923 ;; Define keymap. |
|
1924 (use-local-map doctest-results-mode-map) |
|
1925 |
|
1926 ;; Define the syntax table. |
|
1927 (set-syntax-table doctest-results-mode-syntax-table) |
|
1928 |
|
1929 ;; Enable font-lock mode. |
|
1930 (if (featurep 'font-lock) (font-lock-mode 1))) |
|
1931 |
|
1932 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1933 ;;; Doctest Mode |
|
1934 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
1935 |
|
1936 ;; Register the font-lock keywords (xemacs) |
|
1937 (put 'doctest-mode 'font-lock-defaults '(doctest-font-lock-keywords |
|
1938 nil nil nil nil)) |
|
1939 |
|
1940 ;; Register the font-lock keywords (older versions of gnu emacs) |
|
1941 (when (boundp 'font-lock-defaults-alist) |
|
1942 (add-to-list 'font-lock-defaults-alist |
|
1943 '(doctest-mode doctest-font-lock-keywords |
|
1944 nil nil nil nil))) |
|
1945 |
|
1946 (defvar doctest-results-buffer nil |
|
1947 "The output buffer for doctest-mode. |
|
1948 This variable is buffer-local to doctest-mode buffers.") |
|
1949 |
|
1950 (defvar doctest-example-markers nil |
|
1951 "A list mapping markers to the line numbers at which they appeared |
|
1952 in the buffer at the time doctest was last run. This is used to find |
|
1953 'original' line numbers, which can be used to search the doctest |
|
1954 output buffer. It's encoded as a list of (MARKER . POS) tuples, in |
|
1955 reverse POS order. |
|
1956 This variable is buffer-local to doctest-mode buffers.") |
|
1957 |
|
1958 ;; These are global, since we only one run process at a time: |
|
1959 (defvar doctest-async-process nil |
|
1960 "The process object created by the asynchronous doctest process") |
|
1961 (defvar doctest-async-process-tempfiles nil |
|
1962 "A list of tempfile names created by the asynchronous doctest process") |
|
1963 (defvar doctest-async-process-buffer nil |
|
1964 "The source buffer for the asynchronous doctest process") |
|
1965 (defvar doctest-mode-line-process "" |
|
1966 "A string displayed on the modeline, to indicate when doctest is |
|
1967 running asynchronously.") |
|
1968 |
|
1969 ;; Keymap for doctest-mode. n.b.: we intentionally define [tab] |
|
1970 ;; rather than overriding indent-line-function, since we don't want |
|
1971 ;; doctest-indent-source-line to be called by do-auto-fill. |
|
1972 (defconst doctest-mode-map |
|
1973 (let ((map (make-keymap))) |
|
1974 (define-key map [backspace] 'doctest-electric-backspace) |
|
1975 (define-key map [return] 'doctest-newline-and-indent) |
|
1976 (define-key map [tab] 'doctest-indent-source-line) |
|
1977 (define-key map ":" 'doctest-electric-colon) |
|
1978 (define-key map "\C-c\C-v" 'doctest-version) |
|
1979 (define-key map "\C-c\C-c" 'doctest-execute) |
|
1980 (define-key map "\C-c\C-d" 'doctest-execute-with-diff) |
|
1981 (define-key map "\C-c\C-n" 'doctest-next-failure) |
|
1982 (define-key map "\C-c\C-p" 'doctest-prev-failure) |
|
1983 (define-key map "\C-c\C-a" 'doctest-first-failure) |
|
1984 (define-key map "\C-c\C-e" 'doctest-last-failure) |
|
1985 (define-key map "\C-c\C-z" 'doctest-last-failure) |
|
1986 (define-key map "\C-c\C-r" 'doctest-replace-output) |
|
1987 (define-key map "\C-c|" 'doctest-execute-region) |
|
1988 map) |
|
1989 "Keymap for doctest-mode.") |
|
1990 |
|
1991 ;; Syntax table for doctest-mode. |
|
1992 (defvar doctest-mode-syntax-table nil |
|
1993 "Syntax table used in `doctest-mode' buffers.") |
|
1994 (when (not doctest-mode-syntax-table) |
|
1995 (setq doctest-mode-syntax-table (make-syntax-table)) |
|
1996 (dolist (entry '(("(" . "()") ("[" . "(]") ("{" . "(}") |
|
1997 (")" . ")(") ("]" . ")[") ("}" . "){") |
|
1998 ("$%&*+-/<=>|'\"`" . ".") ("_" . "w"))) |
|
1999 (dolist (char (string-to-list (car entry))) |
|
2000 (modify-syntax-entry char (cdr entry) doctest-mode-syntax-table)))) |
|
2001 |
|
2002 ;; Use doctest mode for files ending in .doctest |
|
2003 ;;;###autoload |
|
2004 (add-to-list 'auto-mode-alist '("\\.doctest$" . doctest-mode)) |
|
2005 |
|
2006 ;;;###autoload |
|
2007 (defun doctest-mode () |
|
2008 "A major mode for editing text files that contain Python |
|
2009 doctest examples. Doctest is a testing framework for Python that |
|
2010 emulates an interactive session, and checks the result of each |
|
2011 command. For more information, see the Python library reference: |
|
2012 <http://docs.python.org/lib/module-doctest.html> |
|
2013 |
|
2014 `doctest-mode' defines three kinds of line, each of which is |
|
2015 treated differently: |
|
2016 |
|
2017 - 'Source lines' are lines consisting of a Python prompt |
|
2018 ('>>>' or '...'), followed by source code. Source lines are |
|
2019 colored (similarly to `python-mode') and auto-indented. |
|
2020 |
|
2021 - 'Output lines' are non-blank lines immediately following |
|
2022 source lines. They are colored using several doctest- |
|
2023 specific output faces. |
|
2024 |
|
2025 - 'Text lines' are any other lines. They are not processed in |
|
2026 any special way. |
|
2027 |
|
2028 \\{doctest-mode-map}" |
|
2029 (interactive) |
|
2030 |
|
2031 ;; Declare local variables. |
|
2032 (kill-all-local-variables) |
|
2033 (make-local-variable 'font-lock-defaults) |
|
2034 (make-local-variable 'doctest-results-buffer) |
|
2035 (make-local-variable 'doctest-example-markers) |
|
2036 |
|
2037 ;; Define local variables. |
|
2038 (setq major-mode 'doctest-mode |
|
2039 mode-name "Doctest" |
|
2040 mode-line-process 'doctest-mode-line-process |
|
2041 font-lock-defaults '(doctest-font-lock-keywords |
|
2042 nil nil nil nil)) |
|
2043 |
|
2044 ;; Define keymap. |
|
2045 (use-local-map doctest-mode-map) |
|
2046 |
|
2047 ;; Define the syntax table. |
|
2048 (set-syntax-table doctest-mode-syntax-table) |
|
2049 |
|
2050 ;; Enable auto-fill mode. |
|
2051 (auto-fill-mode 1) |
|
2052 (setq auto-fill-function 'doctest-do-auto-fill) |
|
2053 |
|
2054 ;; Enable font-lock mode. |
|
2055 (if (featurep 'font-lock) (font-lock-mode 1)) |
|
2056 |
|
2057 ;; Run the mode hook. |
|
2058 (run-hooks 'doctest-mode-hook)) |
|
2059 |
|
2060 (provide 'doctest-mode) |
|
2061 ;;; doctest-mode.el ends here |