Mercurial > dotfiles
comparison .elisp/doctest-mode.el @ 0:c30d68fbd368
Initial import from svn.
author | Augie Fackler <durin42@gmail.com> |
---|---|
date | Wed, 26 Nov 2008 10:56:09 -0600 |
parents | |
children | b5d75594b356 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c30d68fbd368 |
---|---|
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 |