My Emacs Setup, pt 8: Ruby and Outline
Published 2013-07-17 @ 12:00
Tagged emacs
I’ve got a bitch of a file I have to maintain. One of the methods is ~650 lines long. It flogs out at 1200 (industry average for non-rails methods is ~10). It is horrible but there is little-to-nothing that I can do in ruby to make it better. No, the refactoring mantra isn’t applicable here… That’s an argument for another day.
The point is, it is huge and I have to maintain / understand / navigate it. Emacs has a bunch of tools to index files and jump around to definitions and the like, but this is inside one method so they don’t help. Emacs also has a ton of various outlining tools, but none of them work with ruby because ruby’s syntax is a serious PITA. I may have finally found a compromise that works well enough.
Add this to your enh-ruby-mode
or ruby-mode
hook:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
(defun ruby-outline-level () (or (and (match-string 1) (or (cdr (assoc (match-string 1) outline-heading-alist)) (- (match-end 1) (match-beginning 1)))) (cdr (assoc (match-string 0) outline-heading-alist)) (- (match-end 0) (match-beginning 0)))) (set (make-local-variable 'outline-level) 'ruby-outline-level) (set (make-local-variable 'outline-regexp) (rx (group (* " ")) bow (or "begin" "case" "class" "def" "else" "elsif" "end" "ensure" "if" "module" "rescue" "when" "unless") eow)) (outline-minor-mode) |
Then add this to your outline-minor-mode
hook:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
(require 'outline-magic) (defun outline-cycle-fast () "Emulates 2 hits of outline-cycle, giving me what I want to see 90% of the time" (interactive) (hide-subtree) (show-entry) (show-children) (setq this-command 'outline-cycle-children)) (let ((map outline-minor-mode-map)) (define-key map (kbd "M-o M-o") 'outline-cycle) (define-key map (kbd "M-o o") 'outline-cycle-fast)) |
Now you can go to the top of a huge if/elseif/else
block and hit
M-o o
and it’ll collapse to the top level items, which is a lot
more navigable for me than a raw 650 line method. Now I can drill down
individual branches and only expand (using M-o M-o
) the subsection I
want to see at the time. Now it looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
loop do # START OF CASE if src.scan(/[\ \t\r\f\v]/) then # \s - \n + \v self.space_seen = true next elsif src.check(/[^a-zA-Z]/) then if src.scan(/\n|#/) then... elsif src.scan(/[\]\)\}]/) then... elsif src.scan(/\!/) then... elsif src.scan(/\.\.\.?|,|![=~]?/) then... elsif src.check(/\./) then... elsif src.scan(/\(/) then... elsif src.check(/\=/) then... elsif src.scan(/\"(#{ESC_RE}|#(#{ESC_RE}|[^\{\#\@\$\"\\])|[^\"\\\#])*\"/o) then... elsif src.scan(/\"/) then # FALLBACK... elsif src.scan(/\@\@?#{IDENT_CHAR_RE}+/o) then... elsif src.scan(/\:\:/) then... elsif ! is_end? && src.scan(/:([a-zA-Z_]#{IDENT_CHAR_RE}*(?:[?!]|=(?==>)|=(?![=>]))?)/) then... elsif src.scan(/\:/) then... elsif src.check(/[0-9]/) then... elsif src.scan(/\[/) then... elsif src.scan(/\'(\\.|[^\'])*\'/) then... elsif src.check(/\|/) then... elsif src.scan(/\{/) then... elsif src.scan(/->/) then... elsif src.scan(/[+-]/) then... elsif src.check(/\*/) then... elsif src.check(/\</) then... elsif src.check(/\>/) then... elsif src.scan(/\`/) then... elsif src.scan(/\?/) then... elsif src.check(/\&/) then... elsif src.scan(/\//) then... elsif src.scan(/\^=/) then... elsif src.scan(/\^/) then... elsif src.scan(/\;/) then... elsif src.scan(/\~/) then... elsif src.scan(/\\/) then... elsif src.scan(/\%/) then... elsif src.check(/\$/) then... elsif src.check(/\_/) then... end end # END OF CASE |