summaryrefslogtreecommitdiff
path: root/jni/ruby/misc/ruby-electric.el
diff options
context:
space:
mode:
Diffstat (limited to 'jni/ruby/misc/ruby-electric.el')
-rw-r--r--jni/ruby/misc/ruby-electric.el475
1 files changed, 475 insertions, 0 deletions
diff --git a/jni/ruby/misc/ruby-electric.el b/jni/ruby/misc/ruby-electric.el
new file mode 100644
index 0000000..b8897e7
--- /dev/null
+++ b/jni/ruby/misc/ruby-electric.el
@@ -0,0 +1,475 @@
+;;; ruby-electric.el --- Minor mode for electrically editing ruby code
+;;
+;; 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.1.1
+
+;;; Commentary:
+;;
+;; `ruby-electric-mode' accelerates code writing in ruby by making
+;; some keys "electric" and automatically supplying with closing
+;; parentheses and "end" as appropriate.
+;;
+;; This work was originally inspired by a code snippet posted by
+;; [Frederick Ros](https://github.com/sleeper).
+;;
+;; Add the following line to enable ruby-electric-mode under
+;; ruby-mode.
+;;
+;; (eval-after-load "ruby-mode"
+;; '(add-hook 'ruby-mode-hook 'ruby-electric-mode))
+;;
+;; Type M-x customize-group ruby-electric for configuration.
+
+;;; Code:
+
+(require 'ruby-mode)
+
+(defgroup ruby-electric nil
+ "Minor mode providing electric editing commands for ruby files"
+ :group 'ruby)
+
+(defconst ruby-electric-expandable-bar-re
+ "\\s-\\(do\\|{\\)\\s-*|")
+
+(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-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)
+
+(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)
+ (let ((end (match-end 1)))
+ (not (looking-back "\\."))
+ (save-excursion
+ (let ((indent1 (ruby-electric--try-insert-and-do "\n"
+ (ruby-calculate-indent)))
+ (indent2 (save-excursion
+ (goto-char end)
+ (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)
+ (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)
+ "$"))))
+ :group 'ruby-electric)
+
+(defcustom ruby-electric-simple-keywords-re nil
+ "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)
+ ,@(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
+ "*Non-nil means a newline should be inserted before an
+automatically inserted closing bracket."
+ :type 'boolean :group 'ruby-electric)
+
+(defcustom ruby-electric-autoindent-on-closing-char nil
+ "*Non-nil means the current line should be automatically
+indented when a closing character is manually typed in."
+ :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.
+With no argument, this command toggles the mode. Non-null prefix
+argument turns on the mode. Null prefix argument turns off the
+mode.
+
+When Ruby Electric mode is enabled, an indented 'end' is
+heuristicaly inserted whenever typing a word like 'module',
+'class', 'def', 'if', 'unless', 'case', 'until', 'for', 'begin',
+'do' followed by a space. Single, double and back quotes as well
+as braces are paired auto-magically. Expansion does not occur
+inside comments and strings. Note that you must have Font Lock
+enabled."
+ ;; initial value.
+ nil
+ ;;indicator for the mode line.
+ " REl"
+ ;;keymap
+ ruby-electric-mode-map
+ (if ruby-electric-mode
+ (run-hooks 'ruby-electric-mode-hook)))
+
+(defun ruby-electric-space/return-fallback ()
+ (if (or (eq this-original-command 'ruby-electric-space/return)
+ (null (ignore-errors
+ ;; ac-complete may fail if there is nothing left to complete
+ (call-interactively this-original-command)
+ (setq this-command this-original-command))))
+ ;; fall back to a globally bound command
+ (let ((command (global-key-binding (char-to-string last-command-event) t)))
+ (and command
+ (call-interactively (setq this-command command))))))
+
+(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/return-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)))
+ (ruby-electric-space/return-fallback)))
+ ((and (eq this-original-command 'newline-and-indent)
+ (ruby-electric-comment-at-point-p))
+ (call-interactively (setq this-command 'comment-indent-new-line)))
+ (t
+ (ruby-electric-space/return-fallback))))
+
+(defun ruby-electric-code-at-point-p()
+ (and ruby-electric-mode
+ (let* ((properties (text-properties-at (point))))
+ (and (null (memq 'font-lock-string-face properties))
+ (null (memq 'font-lock-comment-face properties))))))
+
+(defun ruby-electric-string-at-point-p()
+ (and ruby-electric-mode
+ (consp (memq 'font-lock-string-face (text-properties-at (point))))))
+
+(defun ruby-electric-comment-at-point-p()
+ (and ruby-electric-mode
+ (consp (memq 'font-lock-comment-face (text-properties-at (point))))))
+
+(defun ruby-electric-escaped-p()
+ (let ((f nil))
+ (save-excursion
+ (while (char-equal ?\\ (preceding-char))
+ (backward-char 1)
+ (setq f (not f))))
+ f))
+
+(defun ruby-electric-command-char-expandable-punct-p(char)
+ (or (memq 'all ruby-electric-expand-delimiters-list)
+ (memq char ruby-electric-expand-delimiters-list)))
+
+(defun ruby-electric-space/return-can-be-expanded-p()
+ (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])))
+ (when (memq func '(cua-replace-region
+ sp--cua-replace-region))
+ (setq this-original-command 'self-insert-command)
+ (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-command-char-expandable-punct-p last-command-event))
+ (insert last-command-event)
+ ,@body)
+ (t
+ (setq this-command 'self-insert-command)
+ (insert (make-string (prefix-numeric-value ,arg) last-command-event)))))
+
+(defun ruby-electric-curlies(arg)
+ (interactive "*P")
+ (ruby-electric-insert
+ arg
+ (cond
+ ((ruby-electric-code-at-point-p)
+ (insert "}")
+ (backward-char 1)
+ (redisplay)
+ (cond
+ ((ruby-electric-string-at-point-p) ;; %w{}, %r{}, etc.
+ t)
+ (ruby-electric-newline-before-closing-bracket
+ (insert " ")
+ (save-excursion
+ (newline)
+ (ruby-indent-line t)))
+ (t
+ (insert " ")
+ (backward-char 1))))
+ ((ruby-electric-string-at-point-p)
+ (save-excursion
+ (backward-char 1)
+ (cond
+ ((char-equal ?\# (preceding-char))
+ (unless (save-excursion
+ (backward-char 1)
+ (ruby-electric-escaped-p))
+ (forward-char 1)
+ (insert "}")))
+ ((or
+ (ruby-electric-command-char-expandable-punct-p ?\#)
+ (ruby-electric-escaped-p))
+ (setq this-command 'self-insert-command))
+ (t
+ (insert "#")
+ (forward-char 1)
+ (insert "}")))))
+ (t
+ (setq this-command 'self-insert-command)))))
+
+(defun ruby-electric-hash(arg)
+ (interactive "*P")
+ (ruby-electric-insert
+ arg
+ (and (ruby-electric-string-at-point-p)
+ (or (char-equal (following-char) ?') ;; likely to be in ''
+ (save-excursion
+ (backward-char 1)
+ (ruby-electric-escaped-p))
+ (progn
+ (insert "{}")
+ (backward-char 1))))))
+
+(defmacro ruby-electric-avoid-eob(&rest body)
+ `(if (eobp)
+ (save-excursion
+ (insert "\n")
+ (backward-char)
+ ,@body
+ (prog1
+ (ruby-electric-string-at-point-p)
+ (delete-char 1)))
+ ,@body))
+
+(defun ruby-electric-matching-char(arg)
+ (interactive "*P")
+ (ruby-electric-insert
+ arg
+ (let ((closing (cdr (assoc last-command-event
+ ruby-electric-matching-delimeter-alist))))
+ (cond
+ ((char-equal closing last-command-event)
+ (if (and (not (ruby-electric-string-at-point-p))
+ (ruby-electric-avoid-eob
+ (redisplay)
+ (ruby-electric-string-at-point-p)))
+ (save-excursion (insert closing))
+ (and (eq last-command 'ruby-electric-matching-char)
+ (char-equal (following-char) closing) ;; repeated quotes
+ (delete-forward-char 1))
+ (setq this-command 'self-insert-command)))
+ ((ruby-electric-code-at-point-p)
+ (save-excursion (insert closing)))))))
+
+(defun ruby-electric-closing-char(arg)
+ (interactive "*P")
+ (cond
+ ((ruby-electric-cua-replace-region-maybe))
+ (arg
+ (setq this-command 'self-insert-command)
+ (insert (make-string (prefix-numeric-value arg) last-command-event)))
+ ((and
+ (eq last-command 'ruby-electric-curlies)
+ (= last-command-event ?})) ;; {}
+ (if (char-equal (following-char) ?\n) (delete-char 1))
+ (delete-horizontal-space)
+ (forward-char))
+ ((and
+ (= last-command-event (following-char))
+ (memq last-command '(ruby-electric-matching-char
+ ruby-electric-closing-char))) ;; ()/[] and (())/[[]]
+ (forward-char))
+ (t
+ (setq this-command 'self-insert-command)
+ (self-insert-command 1)
+ (if ruby-electric-autoindent-on-closing-char
+ (ruby-indent-line)))))
+
+(defun ruby-electric-bar(arg)
+ (interactive "*P")
+ (ruby-electric-insert
+ arg
+ (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")
+ (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) ?{)
+ (delete-char 1))))
+ (delete-char (- arg)))
+
+(provide 'ruby-electric)
+
+;;; ruby-electric.el ends here