summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
Diffstat (limited to 'misc')
-rw-r--r--misc/ruby-electric.el417
1 files changed, 243 insertions, 174 deletions
diff --git a/misc/ruby-electric.el b/misc/ruby-electric.el
index ca89a65302..419d83afd7 100644
--- a/misc/ruby-electric.el
+++ b/misc/ruby-electric.el
@@ -1,52 +1,35 @@
-;; -*-Emacs-Lisp-*-
+;;; ruby-electric.el --- Minor mode for electrically editing ruby code
;;
-;; ruby-electric.el --- electric editing commands for ruby files
+;; Authors: Dee Zsombor <dee dot zsombor at gmail dot com>
+;; Yukihiro Matsumoto
+;; Nobuyoshi Nakada
+;; Akinori MUSHA <knu@iDaemons.org>
+;; Jakub Kuźma <qoobaa@gmail.com>
+;; Maintainer: Akinori MUSHA <knu@iDaemons.org>
+;; Created: 6 Mar 2005
+;; URL: https://github.com/knu/ruby-electric.el
+;; Keywords: languages ruby
+;; License: The same license terms as Ruby
+;; Version: 2.0
+
+;;; Commentary:
;;
-;; Copyright (C) 2005 by Dee Zsombor <dee dot zsombor at gmail dot com>.
-;; Released under same license terms as Ruby.
+;; `ruby-electric-mode' accelerates code writing in ruby by making
+;; some keys "electric" and automatically supplying with closing
+;; parentheses and "end" as appropriate.
;;
-;; Due credit: this work was inspired by a code snippet posted by
-;; Frederick Ros at http://rubygarden.org/ruby?EmacsExtensions.
+;; This work was originally inspired by a code snippet posted by
+;; [Frederick Ros](https://github.com/sleeper).
;;
-;; Following improvements where added:
+;; Add the following line to enable ruby-electric-mode under
+;; ruby-mode.
;;
-;; - handling of strings of type 'here document'
-;; - more keywords, with special handling for 'do'
-;; - packaged into a minor mode
+;; (eval-after-load "ruby-mode"
+;; '(add-hook 'ruby-mode-hook 'ruby-electric-mode))
;;
-;; Usage:
-;;
-;; 0) copy ruby-electric.el into directory where emacs can find it.
-;;
-;; 1) modify your startup file (.emacs or whatever) by adding
-;; following line:
-;;
-;; (require 'ruby-electric)
-;;
-;; note that you need to have font lock enabled beforehand.
-;;
-;; 2) toggle Ruby Electric Mode on/off with ruby-electric-mode.
-;;
-;; Changelog:
-;;
-;; 2005/Jan/14: inserts matching pair delimiters like {, [, (, ', ",
-;; ' and | .
-;;
-;; 2005/Jan/14: added basic Custom support for configuring keywords
-;; with electric closing.
-;;
-;; 2005/Jan/18: more Custom support for configuring characters for
-;; which matching expansion should occur.
-;;
-;; 2005/Jan/18: no longer uses 'looking-back' or regexp character
-;; classes like [:space:] since they are not implemented on XEmacs.
-;;
-;; 2005/Feb/01: explicitly provide default argument of 1 to
-;; 'backward-word' as it requires it on Emacs 21.3
-;;
-;; 2005/Mar/06: now stored inside ruby CVS; customize pages now have
-;; ruby as parent; cosmetic fixes.
+;; Type M-x customize-group ruby-electric for configuration.
+;;; Code:
(require 'ruby-mode)
@@ -54,64 +37,162 @@
"Minor mode providing electric editing commands for ruby files"
:group 'ruby)
-(defconst ruby-electric-expandable-bar
- "\\s-\\(do\\|{\\)\\s-+|")
+(defconst ruby-electric-expandable-bar-re
+ "\\s-\\(do\\|{\\)\\s-*|")
-(defvar ruby-electric-matching-delimeter-alist
- '((?\[ . ?\])
- (?\( . ?\))
- (?\' . ?\')
- (?\` . ?\`)
- (?\" . ?\")))
+(defconst ruby-electric-delimiters-alist
+ '((?\{ :name "Curly brace" :handler ruby-electric-curlies :closing ?\})
+ (?\[ :name "Square brace" :handler ruby-electric-matching-char :closing ?\])
+ (?\( :name "Round brace" :handler ruby-electric-matching-char :closing ?\))
+ (?\' :name "Quote" :handler ruby-electric-matching-char)
+ (?\" :name "Double quote" :handler ruby-electric-matching-char)
+ (?\` :name "Back quote" :handler ruby-electric-matching-char)
+ (?\| :name "Vertical bar" :handler ruby-electric-bar)
+ (?\# :name "Hash" :handler ruby-electric-hash)))
-(defvar ruby-electric-expandable-do-re)
+(defvar ruby-electric-matching-delimeter-alist
+ (apply 'nconc
+ (mapcar #'(lambda (x)
+ (let ((delim (car x))
+ (plist (cdr x)))
+ (if (eq (plist-get plist :handler) 'ruby-electric-matching-char)
+ (list (cons delim (or (plist-get plist :closing)
+ delim))))))
+ ruby-electric-delimiters-alist)))
(defvar ruby-electric-expandable-keyword-re)
-(defcustom ruby-electric-keywords
- '("begin"
- "case"
- "class"
- "def"
- "do"
- "for"
- "if"
- "module"
- "unless"
- "until"
- "while")
- "List of keywords for which closing 'end' is to be inserted
-after typing a space."
- :type '(repeat string)
+(defmacro ruby-electric--try-insert-and-do (string &rest body)
+ (declare (indent 1))
+ `(let ((before (point))
+ (after (progn
+ (insert ,string)
+ (point))))
+ (unwind-protect
+ (progn ,@body)
+ (delete-region before after)
+ (goto-char before))))
+
+(defconst ruby-modifier-beg-symbol-re
+ (regexp-opt ruby-modifier-beg-keywords 'symbols))
+
+(defun ruby-electric--modifier-keyword-at-point-p ()
+ "Test if there is a modifier keyword at point."
+ (and (looking-at ruby-modifier-beg-symbol-re)
+ (not (looking-back "\\."))
+ (save-excursion
+ (let ((indent1 (ruby-electric--try-insert-and-do "\n"
+ (ruby-calculate-indent)))
+ (indent2 (save-excursion
+ (ruby-forward-sexp 1)
+ (ruby-electric--try-insert-and-do " x\n"
+ (ruby-calculate-indent)))))
+ (= indent1 indent2)))))
+
+(defconst ruby-block-mid-symbol-re
+ (regexp-opt ruby-block-mid-keywords 'symbols))
+
+(defun ruby-electric--block-mid-keyword-at-point-p ()
+ "Test if there is a block mid keyword at point."
+ (and (looking-at ruby-block-mid-symbol-re)
+ (looking-back "^\\s-*")))
+
+(defconst ruby-block-beg-symbol-re
+ (regexp-opt ruby-block-beg-keywords 'symbols))
+
+(defun ruby-electric--block-beg-keyword-at-point-p ()
+ "Test if there is a block beginning keyword at point."
+ (and (looking-at ruby-block-beg-symbol-re)
+ (if (string= (match-string 1) "do")
+ (looking-back "\\s-")
+ (not (looking-back "[^.]")))
+ ;; (not (ruby-electric--modifier-keyword-at-point-p)) ;; implicit assumption
+ ))
+
+(defcustom ruby-electric-keywords-alist
+ '(("begin" . end)
+ ("case" . end)
+ ("class" . end)
+ ("def" . end)
+ ("do" . end)
+ ("else" . reindent)
+ ("elsif" . reindent)
+ ("end" . reindent)
+ ("ensure" . reindent)
+ ("for" . end)
+ ("if" . end)
+ ("module" . end)
+ ("rescue" . reindent)
+ ("unless" . end)
+ ("until" . end)
+ ("when" . reindent)
+ ("while" . end))
+ "Alist of keywords and actions to define how to react to space
+or return right after each keyword. In each (KEYWORD . ACTION)
+cons, ACTION can be set to one of the following values:
+
+ `reindent' Reindent the line.
+
+ `end' Reindent the line and auto-close the keyword with
+ end if applicable.
+
+ `nil' Do nothing.
+"
+ :type '(repeat (cons (string :tag "Keyword")
+ (choice :tag "Action"
+ :menu-tag "Action"
+ (const :tag "Auto-close with end"
+ :value end)
+ (const :tag "Auto-reindent"
+ :value reindent)
+ (const :tag "None"
+ :value nil))))
:set (lambda (sym val)
(set sym val)
- (setq ruby-electric-expandable-do-re
- (and (member "do" val)
- "\\S-\\s-+\\(do\\)\\s-?$")
- ruby-electric-expandable-keyword-re
- (concat "^\\s-*"
- (regexp-opt (remove "do" val) t)
- "\\s-?$")))
+ (let (keywords)
+ (dolist (x val)
+ (let ((keyword (car x))
+ (action (cdr x)))
+ (if action
+ (setq keywords (cons keyword keywords)))))
+ (setq ruby-electric-expandable-keyword-re
+ (concat (regexp-opt keywords 'symbols)
+ "\\s-*$"))))
:group 'ruby-electric)
(defcustom ruby-electric-simple-keywords-re nil
- "Obsolete and ignored. Customize `ruby-electric-keywords'
+ "Obsolete and ignored. Customize `ruby-electric-keywords-alist'
instead."
:type 'regexp :group 'ruby-electric)
+(defvar ruby-electric-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map " " 'ruby-electric-space/return)
+ (define-key map [remap delete-backward-char] 'ruby-electric-delete-backward-char)
+ (define-key map [remap newline] 'ruby-electric-space/return)
+ (define-key map [remap newline-and-indent] 'ruby-electric-space/return)
+ (dolist (x ruby-electric-delimiters-alist)
+ (let* ((delim (car x))
+ (plist (cdr x))
+ (name (plist-get plist :name))
+ (func (plist-get plist :handler))
+ (closing (plist-get plist :closing)))
+ (define-key map (char-to-string delim) func)
+ (if closing
+ (define-key map (char-to-string closing) 'ruby-electric-closing-char))))
+ map)
+ "Keymap used in ruby-electric-mode")
+
(defcustom ruby-electric-expand-delimiters-list '(all)
"*List of contexts where matching delimiter should be
inserted. The word 'all' will do all insertions."
- :type '(set :extra-offset 8
- (const :tag "Everything" all )
- (const :tag "Curly brace" ?\{ )
- (const :tag "Square brace" ?\[ )
- (const :tag "Round brace" ?\( )
- (const :tag "Quote" ?\' )
- (const :tag "Double quote" ?\" )
- (const :tag "Back quote" ?\` )
- (const :tag "Vertical bar" ?\| )
- (const :tag "Hash" ?\# ))
+ :type `(set :extra-offset 8
+ (const :tag "Everything" all)
+ ,@(apply 'list
+ (mapcar #'(lambda (x)
+ `(const :tag ,(plist-get (cdr x) :name)
+ ,(car x)))
+ ruby-electric-delimiters-alist)))
:group 'ruby-electric)
(defcustom ruby-electric-newline-before-closing-bracket nil
@@ -119,6 +200,9 @@ inserted. The word 'all' will do all insertions."
closing bracket or not."
:type 'boolean :group 'ruby-electric)
+(defvar ruby-electric-mode-hook nil
+ "Called after `ruby-electric-mode' is turned on.")
+
;;;###autoload
(define-minor-mode ruby-electric-mode
"Toggle Ruby Electric minor mode.
@@ -138,32 +222,47 @@ enabled."
;;indicator for the mode line.
" REl"
;;keymap
- ruby-mode-map
- (ruby-electric-setup-keymap))
-
-(defun ruby-electric-setup-keymap()
- (define-key ruby-mode-map " " 'ruby-electric-space)
- (define-key ruby-mode-map "{" 'ruby-electric-curlies)
- (define-key ruby-mode-map "(" 'ruby-electric-matching-char)
- (define-key ruby-mode-map "[" 'ruby-electric-matching-char)
- (define-key ruby-mode-map "\"" 'ruby-electric-matching-char)
- (define-key ruby-mode-map "\'" 'ruby-electric-matching-char)
- (define-key ruby-mode-map "`" 'ruby-electric-matching-char)
- (define-key ruby-mode-map "}" 'ruby-electric-closing-char)
- (define-key ruby-mode-map ")" 'ruby-electric-closing-char)
- (define-key ruby-mode-map "]" 'ruby-electric-closing-char)
- (define-key ruby-mode-map "|" 'ruby-electric-bar)
- (define-key ruby-mode-map "#" 'ruby-electric-hash)
- (define-key ruby-mode-map (kbd "DEL") 'ruby-electric-delete-backward-char))
-
-(defun ruby-electric-space (arg)
- (interactive "P")
- (insert (make-string (prefix-numeric-value arg) last-command-event))
- (if (ruby-electric-space-can-be-expanded-p)
- (save-excursion
- (ruby-indent-line t)
- (newline)
- (ruby-insert-end))))
+ ruby-electric-mode-map
+ (if ruby-electric-mode
+ (run-hooks 'ruby-electric-mode-hook)))
+
+(defun ruby-electric-space/return (arg)
+ (interactive "*P")
+ (and (boundp 'sp-last-operation)
+ (setq sp-delayed-pair nil))
+ (cond (arg
+ (insert (make-string (prefix-numeric-value arg) last-command-event)))
+ ((ruby-electric-space-can-be-expanded-p)
+ (let (action)
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (let* ((keyword (match-string 1))
+ (allowed-actions
+ (cond ((ruby-electric--modifier-keyword-at-point-p)
+ '(reindent)) ;; no end necessary
+ ((ruby-electric--block-mid-keyword-at-point-p)
+ '(reindent)) ;; ditto
+ ((ruby-electric--block-beg-keyword-at-point-p)
+ '(end reindent)))))
+ (if allowed-actions
+ (setq action
+ (let ((action (cdr (assoc keyword ruby-electric-keywords-alist))))
+ (and (memq action allowed-actions)
+ action))))))
+ (cond ((eq action 'end)
+ (ruby-indent-line)
+ (save-excursion
+ (newline)
+ (ruby-insert-end)))
+ ((eq action 'reindent)
+ (ruby-indent-line)))
+ (if (char-equal last-command-event ?\s)
+ (insert " ")
+ (funcall this-original-command))))
+ (t
+ (if (char-equal last-command-event ?\s)
+ (insert " ")
+ (funcall (setq this-command this-original-command))))))
(defun ruby-electric-code-at-point-p()
(and ruby-electric-mode
@@ -187,33 +286,9 @@ enabled."
(or (memq 'all ruby-electric-expand-delimiters-list)
(memq char ruby-electric-expand-delimiters-list)))
-(defun ruby-electric-is-last-command-char-expandable-punct-p()
- (or (memq 'all ruby-electric-expand-delimiters-list)
- (memq last-command-event ruby-electric-expand-delimiters-list)))
-
(defun ruby-electric-space-can-be-expanded-p()
- (if (ruby-electric-code-at-point-p)
- (cond ((and ruby-electric-expandable-do-re
- (looking-back ruby-electric-expandable-do-re))
- (not (ruby-electric-space--sp-has-pair-p "do")))
- ((looking-back ruby-electric-expandable-keyword-re)
- (not (ruby-electric-space--sp-has-pair-p (match-string 1)))))))
-
-(defun ruby-electric-space--sp-has-pair-p(keyword)
- (and (boundp 'smartparens-mode)
- smartparens-mode
- (let ((plist (sp-get-pair keyword)))
- (and plist
- ;; Check for :actions '(insert)
- (memq 'insert (plist-get plist :actions))
- ;; Check for :when '(("SPC" "RET" "<evil-ret>"))
- (let ((x (plist-get plist :when)) when-space)
- (while (and x
- (not (let ((it (car x)))
- (setq when-space (and (listp it)
- (member "SPC" it))))))
- (setq x (cdr x)))
- when-space)))))
+ (and (ruby-electric-code-at-point-p)
+ (looking-back ruby-electric-expandable-keyword-re)))
(defun ruby-electric-cua-replace-region-maybe()
(let ((func (key-binding [remap self-insert-command])))
@@ -223,18 +298,11 @@ enabled."
(funcall (setq this-command func))
t)))
-(defun ruby-electric-cua-delete-region-maybe()
- (let ((func (key-binding [remap delete-backward-char])))
- (when (eq func 'cua-delete-region)
- (setq this-original-command 'delete-backward-char)
- (funcall (setq this-command func))
- t)))
-
(defmacro ruby-electric-insert (arg &rest body)
`(cond ((ruby-electric-cua-replace-region-maybe))
((and
(null ,arg)
- (ruby-electric-is-last-command-char-expandable-punct-p))
+ (ruby-electric-command-char-expandable-punct-p last-command-event))
(insert last-command-event)
,@body)
(t
@@ -242,7 +310,7 @@ enabled."
(insert (make-string (prefix-numeric-value ,arg) last-command-event)))))
(defun ruby-electric-curlies(arg)
- (interactive "P")
+ (interactive "*P")
(ruby-electric-insert
arg
(cond
@@ -281,7 +349,7 @@ enabled."
(insert "}"))))))))
(defun ruby-electric-hash(arg)
- (interactive "P")
+ (interactive "*P")
(ruby-electric-insert
arg
(and (ruby-electric-string-at-point-p)
@@ -305,7 +373,7 @@ enabled."
,@body))
(defun ruby-electric-matching-char(arg)
- (interactive "P")
+ (interactive "*P")
(ruby-electric-insert
arg
(let ((closing (cdr (assoc last-command-event
@@ -325,7 +393,7 @@ enabled."
(save-excursion (insert closing)))))))
(defun ruby-electric-closing-char(arg)
- (interactive "P")
+ (interactive "*P")
(cond
((ruby-electric-cua-replace-region-maybe))
(arg
@@ -347,36 +415,37 @@ enabled."
(self-insert-command 1))))
(defun ruby-electric-bar(arg)
- (interactive "P")
+ (interactive "*P")
(ruby-electric-insert
arg
- (and (ruby-electric-code-at-point-p)
- (save-excursion (re-search-backward ruby-electric-expandable-bar nil t))
- (= (point) (match-end 0)) ;; looking-back is missing on XEmacs
- (save-excursion
- (insert "|")))))
+ (cond ((and (ruby-electric-code-at-point-p)
+ (looking-back ruby-electric-expandable-bar-re))
+ (save-excursion (insert "|")))
+ (t
+ (setq this-command 'self-insert-command)))))
(defun ruby-electric-delete-backward-char(arg)
- (interactive "P")
- (unless (ruby-electric-cua-delete-region-maybe)
- (cond ((memq last-command '(ruby-electric-matching-char
- ruby-electric-bar))
- (delete-char 1))
- ((eq last-command 'ruby-electric-curlies)
- (cond ((eolp)
- (cond ((char-equal (preceding-char) ?\s)
- (setq this-command last-command))
- ((char-equal (preceding-char) ?{)
- (and (looking-at "[ \t\n]*}")
- (delete-char (- (match-end 0) (match-beginning 0)))))))
- ((char-equal (following-char) ?\s)
- (setq this-command last-command)
- (delete-char 1))
- ((char-equal (following-char) ?})
- (delete-char 1))))
- ((eq last-command 'ruby-electric-hash)
- (and (char-equal (preceding-char) ?{)
+ (interactive "*P")
+ (cond ((memq last-command '(ruby-electric-matching-char
+ ruby-electric-bar))
+ (delete-char 1))
+ ((eq last-command 'ruby-electric-curlies)
+ (cond ((eolp)
+ (cond ((char-equal (preceding-char) ?\s)
+ (setq this-command last-command))
+ ((char-equal (preceding-char) ?{)
+ (and (looking-at "[ \t\n]*}")
+ (delete-char (- (match-end 0) (match-beginning 0)))))))
+ ((char-equal (following-char) ?\s)
+ (setq this-command last-command)
+ (delete-char 1))
+ ((char-equal (following-char) ?})
(delete-char 1))))
- (delete-char -1)))
+ ((eq last-command 'ruby-electric-hash)
+ (and (char-equal (preceding-char) ?{)
+ (delete-char 1))))
+ (delete-char -1))
(provide 'ruby-electric)
+
+;;; ruby-electric.el ends here