/ ruby-mode-expansions.el
ruby-mode-expansions.el
  1  ;;; ruby-mode-expansions.el --- ruby-specific expansions for expand-region  -*- lexical-binding: t; -*-
  2  
  3  ;; Copyright (C) 2011-2023  Free Software Foundation, Inc
  4  
  5  ;; Author: Matt Briggs
  6  ;; Based on js-mode-expansions by: Magnar Sveen <magnars@gmail.com>
  7  ;; Keywords: marking region
  8  
  9  ;; This program is free software; you can redistribute it and/or modify
 10  ;; it under the terms of the GNU General Public License as published by
 11  ;; the Free Software Foundation, either version 3 of the License, or
 12  ;; (at your option) any later version.
 13  
 14  ;; This program is distributed in the hope that it will be useful,
 15  ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 16  ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17  ;; GNU General Public License for more details.
 18  
 19  ;; You should have received a copy of the GNU General Public License
 20  ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
 21  
 22  ;;; Commentary:
 23  
 24  
 25  ;; LeWang:
 26  ;;
 27  ;;      I think `er/ruby-backward-up' and `er/ruby-forward-up' are nifty
 28  ;;      functions in their own right.
 29  ;;
 30  ;;      I would bind them to C-M-u and C-M-d respectively.
 31  
 32  ;; Expansions:
 33  ;;
 34  ;;
 35  ;;  er/mark-ruby-block-up
 36  ;;
 37  
 38  ;;; Code:
 39  (eval-when-compile (require 'cl-lib))
 40  (require 'expand-region-core)
 41  (require 'er-basic-expansions)
 42  (require 'ruby-mode)
 43  
 44  (defvar er/ruby-block-end-re
 45    (concat ruby-block-end-re "\\|}")
 46    "like ruby-mode's but also for '}'")
 47  
 48  (defun er/ruby-skip-past-block-end ()
 49    "If line is blockend, move point to next line."
 50    (when (looking-at er/ruby-block-end-re)
 51      (forward-line 1)))
 52  
 53  (defun er/ruby-end-of-block (&optional arg)
 54    "By default `ruby-end-of-block' goes to BOL of line containing end-re.
 55  
 56  This moves point to the next line to include the end of the block"
 57    (interactive "p")
 58    ;; Workaround for `ruby-end-of-block' in Emacs 23.
 59    (when (re-search-forward (concat "\\<\\(" ruby-block-beg-re "\\)\\>")
 60                             (line-end-position) t)
 61      (goto-char (match-beginning 0)))
 62    (ruby-end-of-block (or arg 1))
 63    (er/ruby-skip-past-block-end))
 64  
 65  (defun er/point-at-indentation ()
 66    "Return the point where current line's indentation ends."
 67    (save-excursion
 68      (back-to-indentation)
 69      (point)))
 70  
 71  (defun er/ruby-backward-up ()
 72    "a la `paredit-backward-up'"
 73    (interactive)
 74    ;; if our current line ends a block, we back a line, otherwise we
 75    (when (save-excursion
 76            (back-to-indentation)
 77            (looking-at-p ruby-block-end-re))
 78      (forward-line -1))
 79    (let ((orig-point (point))
 80          progress-beg
 81          progress-end)
 82  
 83      ;; cover the case when point is in the line of beginning of block
 84      (unless (progn (ruby-end-of-block)
 85                     (ruby-beginning-of-block)
 86                     ;; "Block beginning" is often not at indentation in Emacs 24.
 87                     (< (er/point-at-indentation) orig-point))
 88        (cl-loop
 89         (ruby-beginning-of-block)
 90         (setq progress-beg (point))
 91         (when (= (point) (point-min))
 92           (cl-return))
 93         (ruby-end-of-block)
 94         (setq progress-end (line-beginning-position
 95                             (if (looking-at-p er/ruby-block-end-re) 0 1)))
 96         (goto-char progress-beg)
 97         (when (> progress-end orig-point)
 98           (cl-return))))))
 99  
100  ;; This command isn't used here explicitly, but it's symmetrical with
101  ;; `er/ruby-backward-up', and nifty for interactive use.
102  (defun er/ruby-forward-up ()
103    "a la `paredit-forward-up'"
104    (interactive)
105    (er/ruby-backward-up)
106    (er/ruby-end-of-block))
107  
108  (defun er/get-ruby-block (&optional pos)
109    "return (beg . end) of current block"
110    (setq pos (or pos (point)))
111    (save-excursion
112      (goto-char pos)
113      (cons (progn
114              (er/ruby-backward-up)
115              (er/point-at-indentation))
116            (progn
117              (er/ruby-end-of-block)
118              (point)))))
119  
120  (defun er/mark-ruby-block-up-1 ()
121    (er/ruby-backward-up)
122    (set-mark (er/point-at-indentation))
123    (er/ruby-end-of-block)
124    (exchange-point-and-mark))
125  
126  (defun er/mark-ruby-block-up (&optional no-recurse)
127    "mark the next level up."
128    (interactive)
129    (if (use-region-p)
130        (let* ((orig-end (region-end))
131               (orig-beg (region-beginning))
132               (orig-len (- orig-end orig-beg))
133               (prev-block-point
134                (or (save-excursion
135                      (goto-char orig-end)
136                      (forward-line 0)
137                      (back-to-indentation)
138                      (cond ((looking-at-p er/ruby-block-end-re)
139                             (line-beginning-position 0))
140                            ((re-search-forward
141                              (concat "\\<\\(" ruby-block-beg-re "\\)\\>")
142                              (line-end-position)
143                              t)
144                             (line-beginning-position 2))) )
145                    (point)))
146               (prev-block-info (er/get-ruby-block prev-block-point))
147               (prev-block-beg (car prev-block-info))
148               (prev-block-end (cdr prev-block-info))
149               (prev-block-len (- prev-block-end prev-block-beg)))
150          (if (and (>= orig-beg prev-block-beg)
151                   (<= orig-end prev-block-end)
152                   (< orig-len prev-block-len))
153              ;; expand to previous block if it contains and grows current
154              ;; region
155              (progn
156                (deactivate-mark)
157                (goto-char prev-block-point)
158                (or no-recurse
159                    (er/mark-ruby-block-up 'no-recurse)))
160            (er/mark-ruby-block-up-1)))
161      (er/mark-ruby-block-up-1)))
162  
163  (defun er/mark-ruby-instance-variable ()
164    "Marks instance variables in ruby.
165  Assumes that point is at the @ - if it is inside the word, that will
166  be marked first anyway."
167    (when (looking-at "@")
168      (forward-char 1))
169    (when (er/looking-back-exact "@")
170      (er/mark-symbol)
171      (forward-char -1)))
172  
173  (defun er/mark-ruby-heredoc ()
174    "Marks a heredoc, since `er/mark-inside-quotes' assumes single quote chars."
175    (let ((ppss (syntax-ppss)))
176      (when (elt ppss 3)
177        (let ((s-start (elt ppss 8)))
178          (goto-char s-start)
179          (when (save-excursion
180                  (beginning-of-line)
181                  (re-search-forward "<<\\(-?\\)['\"]?\\([a-zA-Z0-9_]+\\)" s-start nil))
182            (let ((allow-indent (string= "-" (match-string 1)))
183                  (terminator (match-string 2))
184                  (heredoc-start (save-excursion
185                                   (forward-line)
186                                   (point))))
187              (forward-sexp 1)
188              (forward-line -1)
189              (when (looking-at (concat "^" (if allow-indent "[ \t]*" "") terminator "$"))
190                (set-mark heredoc-start)
191                (exchange-point-and-mark))))))))
192  
193  (defun er/add-ruby-mode-expansions ()
194    "Adds Ruby-specific expansions for buffers in ruby-mode"
195    (set (make-local-variable 'er/try-expand-list)
196         (remove 'er/mark-defun 
197                 (append
198                  (default-value 'er/try-expand-list)
199                  '(er/mark-ruby-instance-variable
200                    er/mark-ruby-block-up
201                    er/mark-ruby-heredoc)))))
202  
203  (er/enable-mode-expansions 'ruby-mode #'er/add-ruby-mode-expansions)
204  (provide 'ruby-mode-expansions)