Attachment 'sage-mode.el'
Download 1 ;;;_* sage-mode.el --- Major mode for editing SAGE code
2
3 ;; Copyright (C) 2007 Nick Alexander
4
5 ;; Author: Nick Alexander <ncalexander@gmail.com>
6 ;; Keywords: sage ipython python math
7
8 ;; This file is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2, or (at your option)
11 ;; any later version.
12
13 ;; This file is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs; see the file COPYING. If not, write to
20 ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 ;; Boston, MA 02110-1301, USA.
22
23 ;;; Commentary:
24
25 ;;; Code:
26
27 (require 'python)
28 (require 'comint)
29 (require 'ansi-color)
30 (require 'compile)
31
32 ;;;_* Inferior SAGE major mode for interacting with a slave SAGE process
33
34 (defcustom inferior-sage-prompt (rx line-start (1+ (and (or "sage:" "....." ">>>" "...") " ")))
35 "Regular expression matching the SAGE prompt."
36 :group 'sage
37 :type 'regexp)
38
39 (defcustom inferior-sage-timeout 500000
40 "How long to wait for a SAGE prompt."
41 :group 'sage
42 :type 'integer)
43
44 (define-derived-mode
45 inferior-sage-mode
46 inferior-python-mode
47 "Inferior SAGE"
48 "Major mode for interacting with an inferior SAGE process."
49
50 (setq comint-prompt-regexp inferior-sage-prompt)
51 (setq comint-redirect-finished-regexp "sage:") ; comint-prompt-regexp)
52
53 (setq comint-input-sender 'ipython-input-sender)
54 ;; I type x? a lot
55 (set 'comint-input-filter 'sage-input-filter)
56
57 (make-local-variable 'compilation-error-regexp-alist)
58 (make-local-variable 'compilation-error-regexp-alist-alist)
59 (add-to-list 'compilation-error-regexp-alist-alist sage-test-compilation-regexp)
60 (add-to-list 'compilation-error-regexp-alist 'sage-test-compilation)
61 (add-to-list 'compilation-error-regexp-alist-alist sage-build-compilation-regexp)
62 (add-to-list 'compilation-error-regexp-alist 'sage-build-compilation)
63
64 (compilation-shell-minor-mode 1))
65
66 (defun inferior-sage-wait-for-prompt ()
67 "Wait until the SAGE process is ready for input."
68 (with-current-buffer sage-buffer
69 (let* ((sprocess (get-buffer-process sage-buffer))
70 (success nil)
71 (timeout 0))
72 (while (progn
73 (if (not (eq (process-status sprocess) 'run))
74 (error "SAGE process has died unexpectedly.")
75 (if (> (setq timeout (1+ timeout)) inferior-sage-timeout)
76 (error "Timeout waiting for SAGE prompt. Check inferior-sage-timeout."))
77 (accept-process-output nil 0 1)
78 (sit-for 0)
79 (goto-char (point-max))
80 (forward-line 0)
81 (setq success (looking-at inferior-sage-prompt))
82 (not (or success (looking-at ".*\\?\\s *"))))))
83 (goto-char (point-max))
84 success)))
85
86 (defun sage-input-filter (string)
87 "A `comint-input-filter' that keeps all input in the history."
88 t)
89
90 ;;;_* IPython magic commands
91
92 (defcustom ipython-input-handle-magic-p t
93 "Non-nil means handle IPython magic commands specially."
94 :group 'ipython
95 :type 'boolean)
96
97 (defvar ipython-input-string-is-magic-regexp
98 "[^*?]\\(\\?\\??\\)\\'"
99 "Regexp matching IPython magic input.
100
101 The first match group is used to dispatch handlers in
102 `ipython-input-handle-magic'.")
103
104 (defun ipython-input-string-is-magic-p (string)
105 "Return non-nil if STRING is IPython magic."
106 (string-match ipython-input-string-is-magic-regexp string))
107
108 (defvar ipython-input-magic-handlers '(("?" . ipython-handle-magic-?)
109 ("??" . ipython-handle-magic-??))
110 "Association list (STRING . FUNCTION) of IPython magic handlers.
111
112 Each FUNCTION should take arguments (PROC STRING MATCH) and
113 return non-nil if magic input was handled, nil if input should be
114 sent normally.")
115
116 (defun ipython-handle-magic-? (proc string &optional match)
117 "Handle IPython magic ?."
118 (when (string-match "\\(.*?\\)\\?" string)
119 (ipython-describe-symbol (match-string 1 string))))
120
121 (defun ipython-handle-magic-?? (proc string &optional match)
122 "Handle IPython magic ??."
123 (when (string-match "\\(.*?\\)\\?\\?" string)
124 (sage-find-symbol-other-window (match-string 1 string))))
125
126 (defun ipython-input-handle-magic (proc string)
127 "Handle IPython magic input STRING in process PROC.
128
129 Return non-nil if input was handled; nil if input should be sent
130 normally."
131 (when (string-match ipython-input-string-is-magic-regexp string)
132 (let* ((match (match-string 1 string))
133 (handler (cdr (assoc match ipython-input-magic-handlers))))
134 (when handler
135 (condition-case ()
136 ;; I can't explain why, but this seems to work perfectly with ??
137 (save-excursion
138 (funcall handler proc string match))
139 ;; XXX print error message?
140 (error nil))))))
141
142 (defun ipython-input-sender (proc string)
143 "Function for sending to process PROC input STRING.
144
145 When `ipython-input-handle-magic-p' is non-nil, this uses
146 `ipython-input-string-is-magic-p' to look for ipython magic
147 commands, such as %prun, etc, and magic suffixes, such as ? and
148 ??, and handles them... magically? It hands them off to
149 `ipython-input-handle-magic' for special treatment.
150
151 Otherwise, `comint-simple-send' just sends STRING plus a newline."
152 (if (and ipython-input-handle-magic-p ; must want to handle magic
153 (ipython-input-string-is-magic-p string) ; ... be magic
154 (ipython-input-handle-magic proc string)) ; and be handled
155 ;; To have just an input history creating, clearing new line entered
156 (comint-simple-send proc "")
157 (comint-simple-send proc string))) ; otherwise, you're sent
158
159 ;;;_* SAGE process management
160
161 (defcustom sage-command (expand-file-name "~/bin/sage")
162 "Actual command used to run SAGE.
163 Additional arguments are added when the command is used by `run-sage' et al."
164 :group 'sage
165 :type 'string)
166
167 (defcustom sage-startup-command "import sage.misc.sage_emacs as emacs"
168 "Run this command each time SAGE slave is executed by `run-sage'."
169 :group 'sage
170 :type 'string)
171
172 (defvar sage-buffer nil
173 "*The current SAGE process buffer.
174
175 Commands that send text from source buffers to SAGE processes have
176 to choose a process to send to. This is determined by buffer-local
177 value of `sage-buffer'. If its value in the current buffer,
178 i.e. both any local value and the default one, is nil, `run-sage'
179 and commands that send to the Python process will start a new process.
180
181 Whenever \\[run-sage] starts a new process, it resets the default
182 value of `sage-buffer' to be the new process's buffer and sets the
183 buffer-local value similarly if the current buffer is in SAGE mode
184 or Inferior SAGE mode, so that source buffer stays associated with a
185 specific sub-process.
186
187 Use \\[sage-set-proc] to set the default value from a buffer with a
188 local value.")
189 (make-variable-buffer-local 'sage-buffer)
190
191 ;;;###autoload
192 (defun run-sage (&optional cmd noshow new)
193 "Run an inferior SAGE process, input and output via buffer *SAGE*.
194 CMD is the SAGE command to run. NOSHOW non-nil means don't show the
195 buffer automatically.
196
197 Normally, if there is a process already running in `sage-buffer',
198 switch to that buffer. Interactively, a prefix arg allows you to edit
199 the initial command line (default is `sage-command'); `-i' etc. args
200 will be added to this as appropriate. A new process is started if:
201 one isn't running attached to `sage-buffer', or interactively the
202 default `sage-command', or argument NEW is non-nil. See also the
203 documentation for `sage-buffer'.
204
205 Runs the hook `inferior-sage-mode-hook' \(after the
206 `comint-mode-hook' is run). \(Type \\[describe-mode] in the process
207 buffer for a list of commands.)"
208 (interactive (if current-prefix-arg
209 (list (read-string "Run SAGE: " sage-command) nil t)
210 (list sage-command)))
211 (unless cmd (setq cmd sage-command))
212 (setq sage-command cmd)
213 ;; Fixme: Consider making `sage-buffer' buffer-local as a buffer
214 ;; (not a name) in SAGE buffers from which `run-sage' &c is
215 ;; invoked. Would support multiple processes better.
216 (let ((create-new-sage-p
217 (or new ; if you ask for it
218 (null sage-buffer) ; or there isn't a running sage
219 (not (comint-check-proc sage-buffer)) ; or there is a sage
220 ; buffer, but it's dead
221 )))
222 (when create-new-sage-p
223 (with-current-buffer
224 (let* ((cmdlist (python-args-to-list cmd))
225 ;; Set PYTHONPATH to import module emacs from emacs.py,
226 ;; but ensure that a user specified PYTHONPATH will
227 ;; override our setting, so that emacs.py can be
228 ;; customized easily.
229 (orig-path (getenv "PYTHONPATH"))
230 (path-sep (if (and orig-path (length orig-path)) ":" ""))
231 (data-path (concat "PYTHONPATH=" orig-path path-sep data-directory))
232 (process-environment
233 (cons data-path process-environment)))
234 (apply 'make-comint-in-buffer "SAGE"
235 (if new (generate-new-buffer "*SAGE*") "*SAGE*")
236 (car cmdlist) nil (cdr cmdlist)))
237 ;; Show progress
238 (unless noshow (pop-to-buffer (current-buffer)))
239 ;; Update default SAGE buffers
240 (setq-default sage-buffer (current-buffer))
241 ;; Update python-buffer too, so that evaluation keys work
242 (setq-default python-buffer (current-buffer))
243 ;; Set up sensible prompt defaults, etc
244 (inferior-sage-mode)
245 (when (inferior-sage-wait-for-prompt)
246 ;; Ensure we're at a prompt before loading the functions we use
247 ;; XXX: add more error-checking?
248 (sage-send-command sage-startup-command t)
249 (sage-find-current-branch)))))
250
251 ;; If we're coming from a sage-mode buffer, update inferior buffer
252 (when (derived-mode-p 'sage-mode)
253 (setq sage-buffer (default-value 'sage-buffer)) ; buffer-local
254 ;; Update python-buffer too, so that evaluation keys work
255 (setq python-buffer (default-value 'sage-buffer))) ; buffer-local
256
257 ;; No matter how we got here, we want this inferior buffer to be the master
258 ;; (when (comint-check-proc sage-buffer)
259 ;; (setq-default sage-buffer sage-buffer)
260 ;; (setq-default python-buffer sage-buffer))
261 ;; Without this, help output goes into the inferior python buffer if
262 ;; the process isn't already running.
263 ;; (sit-for 0) ;Should we use accept-process-output instead? --Stef
264 (unless noshow (pop-to-buffer sage-buffer)))
265
266 (defun sage-find-current-branch ()
267 (interactive)
268 "Change the current SAGE buffer name to include the current branch."
269 (save-excursion
270 (point-max)
271 (search-backward-regexp "Current Mercurial branch is: \\(.*\\)$")
272 (when (match-string 1)
273 (rename-uniquely (format "*SAGE-%s*" (match-string 1))))))
274
275 ;;;_* SAGE major mode for editing SAGE library code
276
277 (provide 'sage)
278
279 (define-derived-mode
280 sage-mode
281 python-mode
282 "SAGE"
283 "Major mode for editing SAGE files."
284 )
285
286 ;;;_* Treat SAGE code as Python source code
287
288 ;;;###autoload
289 (add-to-list 'interpreter-mode-alist '("sage" . sage-mode))
290 ;;;###autoload
291 (add-to-list 'auto-mode-alist '("\\.sage\\'" . sage-mode))
292 ;;;###autoload
293 (add-to-list 'python-source-modes 'sage-mode)
294
295 ;;;_* Integrate SAGE mode with Emacs
296
297 ;;;_ + SAGE mode key bindings
298
299 ;;;###autoload
300 (defun sage-bindings ()
301 "Install SAGE bindings locally."
302 (interactive)
303 (local-set-key [(control c) (control t)] 'sage-test-file)
304 (local-set-key [(control h) (control f)] 'ipython-describe-symbol)
305 (local-set-key [(control h) (control g)] 'sage-find-symbol-other-window))
306
307 (add-hook 'sage-mode 'sage-bindings)
308 (add-hook 'inferior-sage-mode 'sage-bindings)
309
310 ;;;_ + Set better grep defaults for SAGE and Pyrex code
311
312 (add-to-list 'grep-files-aliases '("py" . "{*.py,*.pyx}"))
313 (add-to-list 'grep-files-aliases '("pyx" . "{*.py,*.pyx}"))
314
315 ;;;_ + Make devel/sage files play nicely, and don't jump into site-packages if possible
316
317 ;;; It's annoying to get lost in sage/.../site-packages version of files when
318 ;;; `sage-find-symbol' and friends jump to files. It's even more annoying when
319 ;;; the file is not correctly recognized as sage source!
320
321 (add-to-list 'auto-mode-alist '("devel/sage.*?\\.py\\'" . sage-mode))
322 (add-to-list 'auto-mode-alist '("devel/sage.*?\\.pyx\\'" . pyrex-mode))
323
324 (defvar sage-site-packages-regexp "\\(local.*?site-packages.*?\\)/sage"
325 "Regexp to match sage site-packages files.
326
327 Match group 1 will be replaced with devel/sage-branch")
328
329 (add-hook 'find-file-hook 'sage-warn-if-site-packages-file)
330 (defun sage-warn-if-site-packages-file()
331 "Warn if sage FILE is in site-packages and offer to find current branch version."
332 (let ((f (buffer-file-name (current-buffer))))
333 (and f (string-match sage-site-packages-regexp f)
334 (if (y-or-n-p "This is a sage site-packages file, open the real file? ")
335 (sage-jump-to-development-version)
336 (push '(:propertize "SAGE-SITE-PACKAGES-FILE:" face font-lock-warning-face)
337 mode-line-buffer-identification)))))
338
339 (defun sage-development-version (filename)
340 "If FILENAME is in site-packages, current branch version, else FILENAME."
341 (save-match-data
342 (let* ((match (string-match sage-site-packages-regexp filename)))
343 (if (and filename match)
344 ;; handle current branch somewhat intelligiently
345 (let* ((base (concat (substring filename 0 (match-beginning 1)) "devel/"))
346 (branch (or (file-symlink-p (concat base "sage")) "sage")))
347 (concat base branch (substring filename (match-end 1))))
348 filename))))
349
350 (defun sage-jump-to-development-version ()
351 "Jump to current branch version of current FILE if we're in site-packages version."
352 (interactive)
353 (let ((filename (sage-development-version (buffer-file-name (current-buffer))))
354 (maybe-buf (find-buffer-visiting filename)))
355 (if maybe-buf (pop-to-buffer maybe-buf)
356 (find-alternate-file filename))))
357
358 (defadvice compilation-find-file
359 (before sage-compilation-find-file (marker filename directory &rest formats))
360 "Always try to find compilation errors in FILENAME in the current branch version."
361 (ad-set-arg 1 (sage-development-version filename)))
362 (ad-activate 'compilation-find-file)
363
364 ;;;_ + Integrate with eshell
365
366 (defconst sage-test-compilation-regexp
367 (list 'sage-test-compilation
368 "^File \"\\(.*\\)\", line \\([0-9]+\\)"
369 1
370 2))
371
372 (defconst sage-build-compilation-regexp
373 (list 'sage-build-compilation
374 "^\\(.*\\):\\([0-9]+\\):\\([0-9]+\\):"
375 1 2 3))
376
377 ;; To add support for SAGE build and test errors to *compilation* buffers by
378 ;; default, evaluate the following four lines.
379 ;;
380 ;; (add-to-list 'compilation-error-regexp-alist-alist sage-test-compilation-regexp)
381 ;; (add-to-list 'compilation-error-regexp-alist 'sage-test-compilation)
382 ;; (add-to-list 'compilation-error-regexp-alist-alist sage-build-compilation-regexp)
383 ;; (add-to-list 'compilation-error-regexp-alist 'sage-build-compilation)
384
385 (defun eshell-sage-command-hook (command args)
386 "Handle some SAGE invocations specially.
387
388 Without ARGS, run SAGE in an emacs `sage-mode' buffer.
389
390 With first ARGS starting with \"-b\" or \"-t\", run SAGE in an
391 emacs `compilation-mode' buffer.
392
393 Otherwise (for example, with ARGS \"-hg\", run SAGE at the eshell
394 prompt as normal.
395
396 This is an `eshell-named-command-hook' because only some parameters modify the
397 command; other times, it has to execute as a standard eshell command."
398 (when (equal command "sage")
399 (cond ((not args)
400 ;; run sage inside emacs
401 (run-sage sage-command nil t)
402 t)
403 ((member (substring (car args) 0 2) '("-t" "-b"))
404 ;; echo sage build into compilation buffer
405 (throw 'eshell-replace-command
406 (eshell-parse-command
407 "compile"
408 (cons "sage" (eshell-flatten-list args))))))))
409 (add-hook 'eshell-named-command-hook 'eshell-sage-command-hook)
410
411 ;; From http://www.emacswiki.org/cgi-bin/wiki?EshellFunctions
412 (defun eshell/compile (&rest args)
413 "Use `compile' to do background makes."
414 (if (eshell-interactive-output-p)
415 (let ((compilation-process-setup-function
416 (list 'lambda nil
417 (list 'setq 'process-environment
418 (list 'quote (eshell-copy-environment))))))
419 (compile (eshell-flatten-and-stringify args))
420 (pop-to-buffer compilation-last-buffer))
421 (throw 'eshell-replace-command
422 (let ((l (eshell-stringify-list (eshell-flatten-list args))))
423 (eshell-parse-command (car l) (cdr l))))))
424 (put 'eshell/compile 'eshell-no-numeric-conversions t)
425
426 ;;;_* Load relative modules correctly
427
428 (defun python-qualified-module-name (file)
429 "Find the qualified module name for filename FILE.
430
431 This recurses down the directory tree as long as there are __init__.py
432 files there, signalling that we are inside a package.
433
434 Returns a pair (PACKAGE . MODULE). The first is the top level
435 package directory; the second is the dotted Python module name.
436
437 Adapted from a patch posted to the python-mode trac."
438 (let ((rec #'(lambda (d f)
439 (let* ((dir (file-name-directory d))
440 (initpy (concat dir "__init__.py")))
441 (if (file-exists-p initpy)
442 (let ((d2 (directory-file-name d)))
443 (funcall rec (file-name-directory d2)
444 (concat (file-name-nondirectory d2) "." f)))
445 (list d f))))))
446 (funcall rec (file-name-directory file)
447 (file-name-sans-extension (file-name-nondirectory file)))))
448
449 ;;; Replace original `python-load-file' to use xreload and packages.
450 (defadvice python-load-file
451 (around nca-python-load-file first (file-name &optional no-xreload))
452 "Load a Python file FILE-NAME into the inferior Python process.
453
454 Without prefix argument, use fancy new xreload. With prefix
455 argument, use default Python reload.
456
457 THIS REPLACES THE ORIGINAL `python-load-file'.
458
459 If the file has extension `.py' import or reload it as a module.
460 Treating it as a module keeps the global namespace clean, provides
461 function location information for debugging, and supports users of
462 module-qualified names."
463 (interactive
464 (append (comint-get-source
465 (format "%s Python file: " (if current-prefix-arg "reload" "xreload"))
466 python-prev-dir/file
467 python-source-modes
468 t)
469 current-prefix-arg)) ; because execfile needs exact name
470 (comint-check-source file-name) ; Check to see if buffer needs saving.
471 (setq python-prev-dir/file (cons (file-name-directory file-name)
472 (file-name-nondirectory file-name)))
473 (with-current-buffer (process-buffer (python-proc)) ;Runs python if needed.
474 ;; Fixme: I'm not convinced by this logic from python-mode.el.
475 (python-send-command
476 (if (string-match "\\.py\\'" file-name)
477 (let* ((directory-module (python-qualified-module-name file-name))
478 (directory (car directory-module))
479 (module (cdr directory-module))
480 (xreload-flag (if no-xreload "False" "True")))
481 (format "emacs.eimport(%S, %S, use_xreload=%s)"
482 module directory xreload-flag))
483 (format "execfile(%S)" file-name)))
484 (message "%s loaded" file-name)))
485 (ad-activate 'python-load-file)
486
487 ;;;_* Convenient *programmatic* Python interaction
488
489 (defvar python-default-tag-noerror "_XXX1XXX_NOERROR")
490 (defvar python-default-tag-error "_XXX1XXX_ERROR")
491
492 (defun python-protect-command (command &optional tag-noerror tag-error)
493 "Wrap Python COMMAND in a try-except block and signal error conditions.
494
495 Print TAG-NOERROR on successful Python execution and TAG-ERROR on
496 error conditions."
497 (let* ((tag-noerror (or tag-noerror python-default-tag-noerror))
498 (tag-error (or tag-error python-default-tag-error))
499 (lines (split-string command "\n"))
500 (indented-lines
501 (mapconcat (lambda (x) (concat " " x)) lines "\n")))
502 (format "try:
503 %s
504 print %S,
505 except e:
506 print e
507 print %S,
508 " indented-lines tag-noerror tag-error)))
509
510 (defmacro with-python-output-to-buffer (buffer command &rest body)
511 "Send COMMAND to inferior Python, redirect output to BUFFER, and execute
512 BODY in that buffer.
513
514 The value returned is the value of the last form in body.
515
516 Block while waiting for output."
517 (declare (indent 2) (debug t))
518 `(with-current-buffer ,buffer
519 ;; Grab what Python has to say
520 (comint-redirect-send-command-to-process
521 (python-protect-command ,command)
522 (current-buffer) (python-proc) nil t)
523 ;; Wait for the redirection to complete
524 (with-current-buffer (process-buffer (python-proc))
525 (while (null comint-redirect-completed)
526 (accept-process-output nil 1)))
527 (message (buffer-name))
528 ;; Execute BODY
529 ,@body
530 ))
531
532 (defmacro with-python-output-buffer (command &rest body)
533 "Send COMMAND to inferior Python and execute BODY in temp buffer with
534 output.
535
536 The value returned is the value of the last form in body.
537
538 Block while waiting for output."
539 (declare (indent 1) (debug t))
540 `(with-temp-buffer
541 (with-python-output-to-buffer (current-buffer) ,command
542 ,@body)))
543
544 ;; (with-python-output-to-buffer "*scratch*" "x?\x?"
545 ;; (message "%s" (buffer-name)))
546
547 (defun sage-send-command (command &optional echo-input)
548 "Evaluate COMMAND in inferior Python process.
549
550 If ECHO-INPUT is non-nil, echo input in process buffer."
551 (interactive "sCommand: ")
552 (if echo-input
553 (with-current-buffer (process-buffer (python-proc))
554 ;; Insert and evaluate input string in place
555 (insert command)
556 (comint-send-input nil t))
557 (python-send-command command)))
558
559 (defun python-send-receive-to-buffer (command buffer &optional echo-output)
560 "Send COMMAND to inferior Python (if any) and send output to BUFFER.
561
562 If ECHO-OUTPUT is non-nil, echo output to process buffer.
563
564 This is an alternate `python-send-receive' that uses temporary buffers and
565 `comint-redirect-send-command-to-process'. Block while waiting for output.
566 This implementation handles multi-line output strings gracefully. At this
567 time, it does not handle multi-line input strings at all."
568 (interactive "sCommand: ")
569 (with-current-buffer buffer
570 ;; Grab what Python has to say
571 (comint-redirect-send-command-to-process
572 command (current-buffer) (python-proc) echo-output t)
573 ;; Wait for the redirection to complete
574 (with-current-buffer (process-buffer (python-proc))
575 (while (null comint-redirect-completed)
576 (accept-process-output nil 1)))))
577
578 (defun python-send-receive-multiline (command)
579 "Send COMMAND to inferior Python (if any) and return result as a string.
580
581 This is an alternate `python-send-receive' that uses temporary buffers and
582 `comint-redirect-send-command-to-process'. Block while waiting for output.
583 This implementation handles multi-line output strings gracefully. At this
584 time, it does not handle multi-line input strings at all."
585 (interactive "sCommand: ")
586 (with-temp-buffer
587 ;; Grab what Python has to say
588 (comint-redirect-send-command-to-process
589 command (current-buffer) (python-proc) nil t)
590 ;; Wait for the redirection to complete
591 (with-current-buffer (process-buffer (python-proc))
592 (while (null comint-redirect-completed)
593 (accept-process-output nil 1)))
594 ;; Return the output
595 (let ((output (buffer-substring-no-properties (point-min) (point-max))))
596 (when (interactive-p)
597 (message output))
598 output)))
599
600 ;;;_* Generally useful tidbits
601
602 (defun python-current-word ()
603 "Return python symbol at point."
604 (with-syntax-table python-dotty-syntax-table
605 (current-word)))
606
607 ;;;_* IPython and SAGE completing reads
608
609 ;;;_ + `ipython-completing-read-symbol' is `completing-read' for python symbols
610 ;;; using IPython's *? mechanism
611
612 (defvar ipython-completing-read-symbol-history ()
613 "List of Python symbols recently queried.")
614
615 (defvar ipython-completing-read-symbol-pred nil
616 "Default predicate for filtering queried Python symbols.")
617
618 (defvar ipython-completing-read-symbol-command "%s*?"
619 "IPython command for generating completions.
620 Each completion should appear separated by whitespace.")
621
622 (defvar ipython-completing-read-symbol-cache ()
623 "A pair (LAST-QUERIED-STRING . COMPLETIONS).")
624
625 (defun ipython-completing-read-symbol-clear-cache ()
626 "Clear the IPython completing read cache."
627 (interactive)
628 (setq ipython-completing-read-symbol-cache ()))
629
630 (defun ipython-completing-read-symbol-make-completions (string)
631 "Query IPython for completions of STRING.
632
633 Return a list of completion strings.
634 Uses `ipython-completing-read-symbol-command' to query IPython."
635 (let* ((command (format ipython-completing-read-symbol-command string))
636 (output (python-send-receive-multiline command)))
637 (condition-case ()
638 (split-string output)
639 (error nil))))
640
641 (defun ipython-completing-read-symbol-function (string predicate action)
642 "A `completing-read' programmable completion function for querying IPython.
643
644 See `try-completion' and `all-completions' for interface details."
645 (let ((cached-string (car ipython-completing-read-symbol-cache))
646 (completions (cdr ipython-completing-read-symbol-cache)))
647 ;; Recompute table using IPython if neccessary
648 (when (or (null completions)
649 (not (equal string cached-string)))
650 (setq ipython-completing-read-symbol-cache
651 (cons string (ipython-completing-read-symbol-make-completions string)))
652 (setq completions
653 (cdr ipython-completing-read-symbol-cache)))
654 ;; Complete as necessary
655 (cond
656 ((eq action 'lambda) (test-completion string completions)) ; 'lambda
657 (action (all-completions string completions predicate)) ; t
658 (t (try-completion string completions predicate))))) ; nil
659
660 (defun ipython-completing-read-symbol
661 (&optional prompt def require-match predicate)
662 "Read a Python symbol (default: DEF) from user, completing with IPython.
663
664 Return a single element list, suitable for use in `interactive' forms.
665 PROMPT is the prompt to display, without colon or space.
666 If DEF is nil, default is `python-current-word'.
667 PREDICATE returns non-nil for potential completions.
668 See `completing-read' for REQUIRE-MATCH."
669 (let* ((default (or def (python-current-word)))
670 (prompt (if (null default) (concat prompt ": ")
671 (concat prompt (format " (default %s): " default))))
672 (func 'ipython-completing-read-symbol-function)
673 (pred (or predicate ipython-completing-read-symbol-pred))
674 (hist 'ipython-completing-read-symbol-history)
675 (enable-recursive-minibuffers t))
676 (ipython-completing-read-symbol-clear-cache)
677 (list (completing-read prompt func pred require-match nil hist default))))
678
679 ;;; `ipython-describe-symbol' is `find-function' for python symbols using
680 ;;; IPython's ? magic mechanism.
681
682 (defvar ipython-describe-symbol-not-found-regexp "Object `.*?` not found."
683 "Regexp that matches IPython's 'symbol not found' warning.")
684
685 (defvar ipython-describe-symbol-command "%s?")
686
687 (defvar ipython-describe-symbol-temp-buffer-show-hook
688 (lambda () ; avoid xref stuff
689 (toggle-read-only 1)
690 (setq view-return-to-alist
691 (list (cons (selected-window) help-return-method))))
692 "`temp-buffer-show-hook' installed for `ipython-describe-symbol' output.")
693
694 (defun ipython-describe-symbol-markup-function (string)
695 "Markup IPython's inspection (?) for display."
696 (when (string-match "[ \t\n]+\\'" string)
697 (concat (substring string 0 (match-beginning 0)) "\n")))
698
699 (defun ipython-describe-symbol (symbol)
700 "Get help on SYMBOL using IPython's inspection (?).
701 Interactively, prompt for SYMBOL."
702 ;; Note that we do this in the inferior process, not a separate one, to
703 ;; ensure the environment is appropriate.
704 (interactive (ipython-completing-read-symbol "Describe symbol" nil t))
705 (when (or (null symbol) (equal "" symbol))
706 (error "No symbol"))
707 (let* ((command (format ipython-describe-symbol-command symbol))
708 (raw-contents (python-send-receive-multiline command))
709 (help-contents
710 (or (ipython-describe-symbol-markup-function raw-contents)
711 raw-contents))
712 (temp-buffer-show-hook ipython-describe-symbol-temp-buffer-show-hook))
713 ;; XXX Handle exceptions; perhaps (with-python-output ...) or similar
714 ;; Handle symbol not found gracefully
715 (when (string-match ipython-describe-symbol-not-found-regexp raw-contents)
716 (error "Symbol not found"))
717 (when (= 0 (length help-contents))
718 (error "Symbol has no description"))
719 ;; Ensure we have a suitable help buffer.
720 (with-output-to-temp-buffer (help-buffer)
721 (with-current-buffer standard-output
722 ;; Fixme: Is this actually useful?
723 (help-setup-xref (list 'ipython-describe-symbol symbol) (interactive-p))
724 (set (make-local-variable 'comint-redirect-subvert-readonly) t)
725 (print-help-return-message)
726 ;; Finally, display help contents
727 (princ help-contents)))))
728
729 ;;;_ + `sage-find-symbol' is `find-function' for SAGE.
730
731 (defun sage-find-symbol-command (symbol)
732 "Return SAGE command to fetch position of SYMBOL."
733 (format
734 (concat "sage.misc.sageinspect.sage_getfile(%s), "
735 "sage.misc.sageinspect.sage_getsourcelines(%s)[-1] + 1")
736 symbol symbol))
737
738 (defvar sage-find-symbol-regexp "('\\(.*?\\)',[ \t\n]+\\([0-9]+\\))"
739 "Match (FILENAME . LINE) from `sage-find-symbol-command'.")
740
741 (defun sage-find-symbol-noselect (symbol)
742 "Return a pair (BUFFER . POINT) pointing to the definition of SYMBOL.
743
744 Queries SAGE to find the source file containing the definition of
745 FUNCTION in a buffer and the point of the definition. The buffer
746 is not selected.
747
748 At this time, there is no error checking. Later, if the function
749 definition can't be found in the buffer, returns (BUFFER)."
750 (when (not symbol)
751 (error "You didn't specify a symbol"))
752 (let* ((command (sage-find-symbol-command symbol))
753 (raw-contents (python-send-receive-multiline command)))
754 (unless (string-match sage-find-symbol-regexp raw-contents)
755 (error "Symbol source not found"))
756 (let* ((raw-filename (match-string 1 raw-contents))
757 (filename (sage-development-version raw-filename))
758 (line-num (string-to-number (match-string 2 raw-contents))))
759 (with-current-buffer (find-file-noselect filename)
760 (goto-line line-num) ; XXX error checking?
761 (cons (current-buffer) (point))))))
762
763 (defun sage-find-symbol-do-it (symbol switch-fn)
764 "Find definition of SYMBOL in a buffer and display it.
765
766 SWITCH-FN is the function to call to display and select the
767 buffer."
768 (let* ((orig-point (point))
769 (orig-buf (window-buffer))
770 (orig-buffers (buffer-list))
771 (buffer-point (save-excursion
772 (sage-find-symbol-noselect symbol)))
773 (new-buf (car buffer-point))
774 (new-point (cdr buffer-point)))
775 (when buffer-point
776 (when (memq new-buf orig-buffers)
777 (push-mark orig-point))
778 (funcall switch-fn new-buf)
779 (when new-point (goto-char new-point))
780 (recenter find-function-recenter-line)
781 ;; (run-hooks 'find-function-after-hook)
782 t)))
783
784 ;;;###autoload
785 (defun sage-find-symbol (symbol)
786 "Find the definition of the SYMBOL near point.
787
788 Finds the source file containing the defintion of the SYMBOL near point and
789 places point before the definition.
790 Set mark before moving, if the buffer already existed."
791 (interactive (ipython-completing-read-symbol "Find symbol" nil t))
792 (when (or (null symbol) (equal "" symbol))
793 (error "No symbol"))
794 (sage-find-symbol-do-it symbol 'switch-to-buffer))
795
796 ;;;###autoload
797 (defun sage-find-symbol-other-window (symbol)
798 "Find, in another window, the definition of SYMBOL near point.
799
800 See `sage-find-symbol' for details."
801 (interactive (ipython-completing-read-symbol "Find symbol" nil t))
802 (when (or (null symbol) (equal "" symbol))
803 (error "No symbol"))
804 (sage-find-symbol-do-it symbol 'switch-to-buffer-other-window))
805
806 ;;;###autoload
807 (defun sage-find-symbol-other-frame (symbol)
808 "Find, in another frame, the definition of SYMBOL near point.
809
810 See `sage-find-symbol' for details."
811 (interactive (ipython-completing-read-symbol "Find symbol" nil t))
812 (when (or (null symbol) (equal "" symbol))
813 (error "No symbol"))
814 (sage-find-symbol-do-it symbol 'switch-to-buffer-other-frame))
815
816 ;;;_* Make it easy to sagetest files and methods
817
818 (defun sage-test-file-inline (file-name &optional method)
819 "Run sage-test on file FILE-NAME, with output to underlying the SAGE buffer.
820
821 We take pains to test the correct module.
822
823 If METHOD is non-nil, try to test only the single method named METHOD.
824 Interactively, try to find current method at point."
825 (interactive
826 (append
827 (comint-get-source "Load SAGE file: "
828 python-prev-dir/file python-source-modes t))
829 (list current-prefix-arg))
830 (comint-check-source file-name) ; Check to see if buffer needs saving.
831 (setq python-prev-dir/file (cons (file-name-directory file-name)
832 (file-name-nondirectory file-name)))
833 (let* ((directory-module (python-qualified-module-name file-name))
834 (directory (car directory-module))
835 (module (cdr directory-module))
836 (command (format "sage.misc.sagetest.sagetest(%s)" module)))
837 (sage-send-command command nil)))
838
839 (defun sage-test-file-to-buffer (file-name &optional method)
840 "Run sage-test on file FILE-NAME, with output to a new buffer.
841
842 We take pains to test the correct module.
843
844 If METHOD is non-nil, try to test only the single method named METHOD.
845 Interactively, try to find current method at point."
846 (interactive
847 (append
848 (comint-get-source "Load SAGE file: "
849 python-prev-dir/file python-source-modes t))
850 (list current-prefix-arg))
851 (comint-check-source file-name) ; Check to see if buffer needs saving.
852 (setq python-prev-dir/file (cons (file-name-directory file-name)
853 (file-name-nondirectory file-name)))
854 (let* ((directory-module (python-qualified-module-name file-name))
855 (directory (car directory-module))
856 (module (cdr directory-module))
857 (command (format "sage.misc.sagetest.sagetest(%s)" module))
858 (compilation-error-regexp-alist '(sage-test sage-build)))
859 (with-temp-buffer
860 (compile (eshell-flatten-and-stringify args))
861 (python-send-receive-to-buffer command (current-buffer)))))
862
863 (defvar sage-test-file 'sage-test-file-to-buffer)
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.