🕷 zenspider.com

by ryan davis



sitemap
Looking for the Ruby Quickref?

My Emacs Setup, pt 8: Ruby and Outline

Published 2013-07-17 @ 12:00

Tagged emacs

This is part of the My Emacs Setup series.

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