Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Emacs, ruby: convert do end block to curly braces and vice versa

Tags:

emacs

ruby

I often find myself converting code like this:

before do 
  :something
end

to

before { :something }

Is there a way to automate this task in emacs? I use ruby-mode and rinary, but they're not too helpful here.

like image 686
iosadchii Avatar asked Jul 15 '11 19:07

iosadchii


3 Answers

ruby-mode in Emacs 24.3 and newer has the command ruby-toggle-block.

The default binding is C-c {.

like image 50
Dmitry Avatar answered Nov 14 '22 08:11

Dmitry


I am sure it can be made shorter and better, but for now I've got the following:

(defun ruby-get-containing-block ()
  (let ((pos (point))
        (block nil))
    (save-match-data
      (save-excursion
        (catch 'break
          ;; If in the middle of or at end of do, go back until at start
          (while (and (not (looking-at "do"))
                      (string-equal (word-at-point) "do"))
            (backward-char 1))
          ;; Keep searching for the containing block (i.e. the block that begins
          ;; before our point, and ends after it)
          (while (not block)
            (if (looking-at "do\\|{")
                (let ((start (point)))
                  (ruby-forward-sexp)
                  (if (> (point) pos)
                      (setq block (cons start (point)))
                    (goto-char start))))
            (if (not (search-backward-regexp "do\\|{" (point-min) t))
                (throw 'break nil))))))
        block))

(defun ruby-goto-containing-block-start ()
  (interactive)
  (let ((block (ruby-get-containing-block)))
    (if block
        (goto-char (car block)))))

(defun ruby-flip-containing-block-type ()
  (interactive)
  (save-excursion
    (let ((block (ruby-get-containing-block)))
      (goto-char (car block))
      (save-match-data
        (let ((strings (if (looking-at "do")
                           (cons
                            (if (= 3 (count-lines (car block) (cdr block)))
                                "do\\( *|[^|]+|\\)? *\n *\\(.*?\\) *\n *end"
                              "do\\( *|[^|]+|\\)? *\\(\\(.*\n?\\)+\\) *end")
                            "{\\1 \\2 }")
                         (cons
                          "{\\( *|[^|]+|\\)? *\\(\\(.*\n?\\)+\\) *}"
                          (if (= 1 (count-lines (car block) (cdr block)))
                              "do\\1\n\\2\nend"
                            "do\\1\\2end")))))
          (when (re-search-forward (car strings) (cdr block) t)
            (replace-match (cdr strings) t)
            (delete-trailing-whitespace (match-beginning 0) (match-end 0))
            (indent-region (match-beginning 0) (match-end 0))))))))

There are two functions to be bound to keys: ruby-goto-containing-block-start and ruby-flip-containing-block-type.

Either command works anywhere inside a block, and hopefully they can skip blocks that should be skipped - although that shouldn't be an issue if you are converting to a short block format.

The ruby-flip-containing-block-type collapses three line do .. end blocks to single line {} and vice versa. If the blocks are not exactly 3 lines and 1 line long, it should leave them alone.

I am using this on my ruby setup now, so I would appreciate improvements.

like image 37
vhallac Avatar answered Nov 14 '22 10:11

vhallac


You could use a regular expression that crosses newlines.

/do(C-q C-j\?)*(.*)(C-q C-j\?)*end/

and replace with

{\2 } 

Something like that could work. You could then customize it until it does exactly what you need and bind it to a macro so that you can whip it out and impress your friends anytime!

I tested the above regexes in vi (my editor of choice) and they worked. So something similar should work for you.

For more information, make sure to checkout the emacs wiki!

like image 42
Ziggy Avatar answered Nov 14 '22 08:11

Ziggy