Permalink
Browse files

Initial revision.

  • Loading branch information...
nonsequitur committed May 14, 2010
0 parents commit 60f9cfa1818f279b281fa1f1db27028136c04de0
Showing with 350 additions and 0 deletions.
  1. +350 −0 inf-ruby.el
@@ -0,0 +1,350 @@
+;;; inf-ruby.el --- Run a ruby process in a buffer
+
+;; Copyright (C) 1999-2008 Yukihiro Matsumoto, Nobuyoshi Nakada
+
+;; Authors: Yukihiro Matsumoto, Nobuyoshi Nakada
+;; URL: http://www.emacswiki.org/cgi-bin/wiki/RubyMode
+;; Created: 8 April 1998
+;; Keywords: languages ruby
+;; Version: 2.1
+;; Package-Requires: ((ruby-mode "1.1"))
+
+;;; Commentary:
+;;
+;; inf-ruby.el provides a REPL buffer connected to an IRB subprocess.
+;;
+;; If you're installing manually, you'll need to:
+;; * drop the file somewhere on your load path (perhaps ~/.emacs.d)
+;; * Add the following lines to your .emacs file:
+;; (autoload 'inf-ruby "inf-ruby" "Run an inferior Ruby process" t)
+;; (autoload 'inf-ruby-keys "inf-ruby" "" t)
+;; (eval-after-load 'ruby-mode
+;; '(add-hook 'ruby-mode-hook 'inf-ruby-keys))
+
+;;; TODO:
+;;
+;; inferior-ruby-error-regexp-alist doesn't match this example
+;; SyntaxError: /home/eschulte/united/org/work/arf/arf/lib/cluster.rb:35: syntax error, unexpected '~', expecting kEND
+;; similarity = comparison_cache[m][n] ||= clusters[m] ~ clusters[n]
+;;
+;; M-p skips the first entry in the input ring.
+;;
+
+(require 'comint)
+(require 'compile)
+(require 'ruby-mode)
+
+(defvar inf-ruby-default-implementation "ruby"
+ "Which ruby implementation to use if none is specified.")
+
+(defvar inf-ruby-first-prompt-pattern "^irb(.*)[0-9:]+0> *"
+ "First prompt regex pattern of ruby interpreter.")
+
+(defvar inf-ruby-prompt-pattern "^\\(irb(.*)[0-9:]+[>*\"'] *\\)+"
+ "Prompt regex pattern of ruby interpreter.")
+
+(defvar inf-ruby-mode-hook nil
+ "*Hook for customising inf-ruby mode.")
+
+(defvar inf-ruby-mode-map
+ (let ((map (copy-keymap comint-mode-map)))
+ (define-key map (kbd "C-c C-l") 'inf-ruby-load-file)
+ (define-key map (kbd "C-x C-e") 'ruby-send-last-sexp)
+ (define-key map (kbd "TAB") 'inf-ruby-complete-or-tab)
+ map)
+ "*Mode map for inf-ruby-mode")
+
+(defvar inf-ruby-implementations
+ '(("ruby" . "irb --inf-ruby-mode -r irb/completion")
+ ("jruby" . "jruby -S irb -r irb/completion")
+ ("rubinius" . "rbx -r irb/completion")
+ ("yarv" . "irb1.9 --inf-ruby-mode -r irb/completion")) ;; TODO: ironruby?
+ "An alist of ruby implementations to irb executable names.")
+
+;; TODO: do we need these two defvars?
+(defvar ruby-source-modes '(ruby-mode)
+ "*Used to determine if a buffer contains Ruby source code.
+If it's loaded into a buffer that is in one of these major modes, it's
+considered a ruby source file by ruby-load-file.
+Used by these commands to determine defaults.")
+
+(defvar ruby-prev-l/c-dir/file nil
+ "Caches the last (directory . file) pair.
+Caches the last pair used in the last ruby-load-file command.
+Used for determining the default in the
+next one.")
+
+(defconst inf-ruby-error-regexp-alist
+ '(("SyntaxError: compile error\n^\\([^\(].*\\):\\([1-9][0-9]*\\):" 1 2)
+ ("^\tfrom \\([^\(].*\\):\\([1-9][0-9]*\\)\\(:in `.*'\\)?$" 1 2)))
+
+;;;###autoload
+(defun inf-ruby-keys ()
+ "Set local key defs to invoke inf-ruby from ruby-mode."
+ (define-key ruby-mode-map "\M-\C-x" 'ruby-send-definition)
+ (define-key ruby-mode-map "\C-x\C-e" 'ruby-send-last-sexp)
+ (define-key ruby-mode-map "\C-c\C-b" 'ruby-send-block)
+ (define-key ruby-mode-map "\C-c\M-b" 'ruby-send-block-and-go)
+ (define-key ruby-mode-map "\C-c\C-x" 'ruby-send-definition)
+ (define-key ruby-mode-map "\C-c\M-x" 'ruby-send-definition-and-go)
+ (define-key ruby-mode-map "\C-c\C-r" 'ruby-send-region)
+ (define-key ruby-mode-map "\C-c\M-r" 'ruby-send-region-and-go)
+ (define-key ruby-mode-map "\C-c\C-z" 'ruby-switch-to-inf)
+ (define-key ruby-mode-map "\C-c\C-l" 'ruby-load-file)
+ (define-key ruby-mode-map "\C-c\C-s" 'inf-ruby))
+
+(defvar inf-ruby-buffer nil "Current irb process buffer.")
+
+(defun inf-ruby-mode ()
+ "Major mode for interacting with an inferior ruby (irb) process.
+
+The following commands are available:
+\\{inf-ruby-mode-map}
+
+A ruby process can be fired up with M-x inf-ruby.
+
+Customisation: Entry to this mode runs the hooks on comint-mode-hook and
+inf-ruby-mode-hook (in that order).
+
+You can send text to the inferior ruby process from other buffers containing
+Ruby source.
+ ruby-switch-to-inf switches the current buffer to the ruby process buffer.
+ ruby-send-definition sends the current definition to the ruby process.
+ ruby-send-region sends the current region to the ruby process.
+
+ ruby-send-definition-and-go, ruby-send-region-and-go,
+ switch to the ruby process buffer after sending their text.
+
+Commands:
+Return after the end of the process' output sends the text from the
+ end of process to point.
+Return before the end of the process' output copies the sexp ending at point
+ to the end of the process' output, and sends it.
+Delete converts tabs to spaces as it moves back.
+Tab indents for ruby; with arugment, shifts rest
+ of expression rigidly with the current line.
+C-M-q does Tab on each line starting within following expression.
+Paragraphs are separated only by blank lines. # start comments.
+If you accidentally suspend your process, use \\[comint-continue-subjob]
+to continue it."
+ (interactive)
+ (comint-mode)
+ (setq comint-prompt-regexp inf-ruby-prompt-pattern)
+ (ruby-mode-variables)
+ (setq major-mode 'inf-ruby-mode)
+ (setq mode-name "Inf-Ruby")
+ (setq mode-line-process '(":%s"))
+ (use-local-map inf-ruby-mode-map)
+ (setq comint-input-filter (function inf-ruby-input-filter))
+ (setq comint-get-old-input (function inf-ruby-get-old-input))
+ (make-local-variable 'compilation-error-regexp-alist)
+ (setq compilation-error-regexp-alist inf-ruby-error-regexp-alist)
+ (compilation-shell-minor-mode t)
+ (run-hooks 'inf-ruby-mode-hook))
+
+(defvar inf-ruby-filter-regexp "\\`\\s *\\S ?\\S ?\\s *\\'"
+ "*Input matching this regexp are not saved on the history list.
+Defaults to a regexp ignoring all inputs of 0, 1, or 2 letters.")
+
+(defun inf-ruby-input-filter (str)
+ "Don't save anything matching inf-ruby-filter-regexp"
+ (not (string-match inf-ruby-filter-regexp str)))
+
+;; adapted from replace-in-string in XEmacs (subr.el)
+(defun inf-ruby-remove-in-string (str regexp)
+ "Remove all matches in STR for REGEXP and returns the new string."
+ (let ((rtn-str "") (start 0) match prev-start)
+ (while (setq match (string-match regexp str start))
+ (setq prev-start start
+ start (match-end 0)
+ rtn-str (concat rtn-str (substring str prev-start match))))
+ (concat rtn-str (substring str start))))
+
+(defun inf-ruby-get-old-input ()
+ "Snarf the sexp ending at point"
+ (save-excursion
+ (let ((end (point)))
+ (re-search-backward inf-ruby-first-prompt-pattern)
+ (inf-ruby-remove-in-string (buffer-substring (point) end)
+ inf-ruby-prompt-pattern))))
+
+;;;###autoload
+(defun inf-ruby (&optional impl)
+ "Run an inferior Ruby process in a buffer.
+With prefix argument, prompts for which Ruby implementation
+\(from the list `inf-ruby-implementations') to use. Runs the
+hooks `inf-ruby-mode-hook' \(after the `comint-mode-hook' is
+run)."
+
+ (interactive (list (if current-prefix-arg
+ (completing-read "Ruby Implementation: "
+ (mapc #'car inf-ruby-implementations))
+ inf-ruby-default-implementation)))
+ (setq impl (or impl "ruby"))
+
+ (let ((command (cdr (assoc impl inf-ruby-implementations))))
+ (run-ruby command impl)))
+
+;;;###autoload
+(defun run-ruby (&optional command name)
+ "Run an inferior Ruby process, input and output via buffer *ruby*.
+If there is a process already running in `*ruby*', switch to that buffer.
+With argument, allows you to edit the command line (default is value
+of `ruby-program-name'). Runs the hooks `inferior-ruby-mode-hook'
+\(after the `comint-mode-hook' is run).
+\(Type \\[describe-mode] in the process buffer for a list of commands.)"
+
+ (interactive)
+ (setq command (or command (cdr (assoc inf-ruby-default-implementation
+ inf-ruby-implementations))))
+ (setq name (or name "ruby"))
+
+ (if (not (comint-check-proc inf-ruby-buffer))
+ (let ((commandlist (split-string command)))
+ (set-buffer (apply 'make-comint name (car commandlist)
+ nil (cdr commandlist)))
+ (inf-ruby-mode)))
+ (pop-to-buffer (setq inf-ruby-buffer (format "*%s*" name))))
+
+(defun inf-ruby-proc ()
+ "Returns the current IRB process. See variable inf-ruby-buffer."
+ (or (get-buffer-process (if (eq major-mode 'inf-ruby-mode)
+ (current-buffer)
+ inf-ruby-buffer))
+ (error "No current process. See variable inf-ruby-buffer")))
+
+;; These commands are added to the ruby-mode keymap:
+
+(defconst ruby-send-terminator "--inf-ruby-%x-%d-%d-%d--"
+ "Template for irb here document terminator.
+Must not contain ruby meta characters.")
+
+(defconst ruby-eval-separator "")
+
+(defun ruby-send-region (start end)
+ "Send the current region to the inferior Ruby process."
+ (interactive "r")
+ (let (term (file (buffer-file-name)) line)
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char start)
+ (setq line (+ start (forward-line (- start)) 1))
+ (goto-char start)
+ (while (progn
+ (setq term (apply 'format ruby-send-terminator (random) (current-time)))
+ (re-search-forward (concat "^" (regexp-quote term) "$") end t)))))
+ ;; compilation-parse-errors parses from second line.
+ (save-excursion
+ (let ((m (process-mark (inf-ruby-proc))))
+ (set-buffer (marker-buffer m))
+ (goto-char m)
+ (insert ruby-eval-separator "\n")
+ (set-marker m (point))))
+ (comint-send-string (inf-ruby-proc) (format "eval <<'%s', nil, %S, %d\n" term file line))
+ (comint-send-region (inf-ruby-proc) start end)
+ (comint-send-string (inf-ruby-proc) (concat "\n" term "\n"))))
+
+(defun ruby-send-definition ()
+ "Send the current definition to the inferior Ruby process."
+ (interactive)
+ (save-excursion
+ (ruby-end-of-defun)
+ (let ((end (point)))
+ (ruby-beginning-of-defun)
+ (ruby-send-region (point) end))))
+
+(defun ruby-send-last-sexp ()
+ "Send the previous sexp to the inferior Ruby process."
+ (interactive)
+ (ruby-send-region (save-excursion (backward-sexp) (point)) (point)))
+
+(defun ruby-send-block ()
+ "Send the current block to the inferior Ruby process."
+ (interactive)
+ (save-excursion
+ (ruby-end-of-block)
+ (end-of-line)
+ (let ((end (point)))
+ (ruby-beginning-of-block)
+ (ruby-send-region (point) end))))
+
+(defun ruby-switch-to-inf (eob-p)
+ "Switch to the ruby process buffer.
+With argument, positions cursor at end of buffer."
+ (interactive "P")
+ (if (get-buffer inf-ruby-buffer)
+ (pop-to-buffer inf-ruby-buffer)
+ (error "No current process buffer. See variable inf-ruby-buffer."))
+ (cond (eob-p
+ (push-mark)
+ (goto-char (point-max)))))
+
+(defun ruby-send-region-and-go (start end)
+ "Send the current region to the inferior Ruby process.
+Then switch to the process buffer."
+ (interactive "r")
+ (ruby-send-region start end)
+ (ruby-switch-to-inf t))
+
+(defun ruby-send-definition-and-go ()
+ "Send the current definition to the inferior Ruby.
+Then switch to the process buffer."
+ (interactive)
+ (ruby-send-definition)
+ (ruby-switch-to-inf t))
+
+(defun ruby-send-block-and-go ()
+ "Send the current block to the inferior Ruby.
+Then switch to the process buffer."
+ (interactive)
+ (ruby-send-block)
+ (ruby-switch-to-inf t))
+
+(defun ruby-load-file (file-name)
+ "Load a Ruby file into the inferior Ruby process."
+ (interactive (comint-get-source "Load Ruby file: " ruby-prev-l/c-dir/file
+ ruby-source-modes t)) ;; T because LOAD needs an exact name
+ (comint-check-source file-name) ; Check to see if buffer needs saved.
+ (setq ruby-prev-l/c-dir/file (cons (file-name-directory file-name)
+ (file-name-nondirectory file-name)))
+ (comint-send-string (inf-ruby-proc) (concat "(load \""
+ file-name
+ "\"\)\n")))
+
+(defun inf-ruby-completions (seed)
+ "Return a list of completions for the line of ruby code starting with SEED."
+ (let* ((proc (get-buffer-process inf-ruby-buffer))
+ (comint-filt (process-filter proc))
+ (kept "") completions)
+ (set-process-filter proc (lambda (proc string) (setf kept (concat kept string))))
+ (process-send-string proc (format "puts IRB::InputCompletor::CompletionProc.call('%s').compact\n" seed))
+ (while (not (string-match inf-ruby-prompt-pattern kept)) (accept-process-output proc))
+ (if (string-match "^[[:alpha:]]+?Error: " kept) (error kept))
+ (setf completions (butlast (split-string kept "[\r\n]") 2))
+ (set-process-filter proc comint-filt)
+ completions))
+
+(defun inf-ruby-complete-or-tab (&optional command)
+ "Either complete the ruby code at point or call
+`indent-for-tab-command' if no completion is available. Relies
+on the irb/completion Module used by readline when running irb
+through a terminal."
+ (interactive (list (let* ((curr (thing-at-point 'line))
+ (completions (inf-ruby-completions curr)))
+ (case (length completions)
+ (0 nil)
+ (1 (car completions))
+ (t (completing-read "possible completions: " completions nil 'confirm-only curr))))))
+ (if (not command)
+ (call-interactively 'indent-for-tab-command)
+ (move-beginning-of-line 1)
+ (kill-line 1)
+ (insert command)))
+
+;;;###autoload
+(eval-after-load 'ruby-mode
+ '(add-hook 'ruby-mode-hook 'inf-ruby-keys))
+
+(provide 'inf-ruby)
+;;; inf-ruby.el ends here

0 comments on commit 60f9cfa

Please sign in to comment.