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 <[email protected]>
   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.