iosadchii
iosadchii

Reputation: 63

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

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.

Upvotes: 6

Views: 1136

Answers (4)

Dmitry
Dmitry

Reputation: 3665

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

The default binding is C-c {.

Upvotes: 17

vhallac
vhallac

Reputation: 13947

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.

Upvotes: 5

teaforthecat
teaforthecat

Reputation: 5093

Here is a function. I am an elisp beginner. It only goes one way; from do to {. let me know if it works for you.

Upvotes: 1

Ziggy
Ziggy

Reputation: 22385

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!

Upvotes: 1

Related Questions