diff options
Diffstat (limited to 'misc')
-rw-r--r-- | misc/ruby-electric.el | 417 |
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 |