7. Programming with TextMate

If you’ve been writing Ruby code using TextMate, then you might have wished for an automatic code formatting capability. I didn’t find it built into TextMate, but I did find that it was very easy to add.

TextMate is Allan Odgaard’s popular and powerful text editor for Mac OS X. It has great support for projects involving multiple files and has some very powerful hooks allowing customization. Using language or application-specific “bundles”, TextMate users can add macros, shortcuts, and even run external programs to parse, modify, and replace all or part of a file being edited.

Although TextMate currently has no built-in Ruby code beautifier, Paul Lutus has written a Ruby code beautifier in Ruby. To add it to TextMate, open TextMate’s Bundle Editor window using the Window → Show Bundle Editor menu command. You’ll then see a window like the one below.

In the pane on the left-hand side, scroll down to the Ruby item and expand it. From the + menu at the bottom left, select the New Command menu item. You can name your command anything you like; I called mine “Beautify”. Click on the text field to the right of Activation: and press the key combination that you want to use to activate your command (I’m using Command-B). Then set the Scope Selector: field to “source.ruby” to ensure that your new command will only be run when you are editing Ruby source code. Finally, paste my slightly-modified version of Paul’s beautifier script into the command window (it’s below). When you’re finished, the Bundle Editor window should look like this:

Now any time you are editing a Ruby file, you can beautify it by pressing Command-B (or your chosen activation key). If something goes wrong or you don’t like the results, just use TextMate’s undo command (Command-Z) to go back to the original version.

The beautification code is below. I’ve made some trivial changes to Paul’s version to make it read from STDIN and write to STDOUT; since his version was released under the GPL, I believe that the GPL license terms still apply, but I waive all rights to my changes. With thanks to Paul and Allan, Happy Rubying!

Ruby Beautifier [ruby]
#!/usr/bin/env ruby

# Ruby beautifier, version 1.3, 04/03/2006
# Copyright (c) 2006, P. Lutus
# TextMate modifications by T. Burks
# Released under the GPL

$tabSize = 2
$tabStr = " "

# indent regexp tests

$indentExp = [
   /^module\b/,
   /(=\s*|^)if\b/,
   /(=\s*|^)until\b/,
   /(=\s*|^)for\b/,
   /(=\s*|^)unless\b/,
   /(=\s*|^)while\b/,
   /(=\s*|^)begin\b/,
   /(=\s*|^)case\b/,
   /\bthen\b/,
   /^class\b/,
   /^rescue\b/,
   /^def\b/,
   /\bdo\b/,
   /^else\b/,
   /^elsif\b/,
   /^ensure\b/,
   /\bwhen\b/,
   /\{[^\}]*$/,
   /\[[^\]]*$/
]

# outdent regexp tests

$outdentExp = [
   /^rescue\b/,
   /^ensure\b/,
   /^elsif\b/,
   /^end\b/,
   /^else\b/,
   /\bwhen\b/,
   /^[^\{]*\}/,
   /^[^\[]*\]/
]

def makeTab(tab)
   return (tab < 0)?"":$tabStr * $tabSize * tab
end

def addLine(line,tab)
   line.strip!
   line = makeTab(tab)+line if line.length > 0
   return line + "\n"
end

def beautifyRuby
   commentBlock = false
   multiLineArray = Array.new
   multiLineStr = ""
   tab = 0
   source = STDIN.read
   dest = ""
   source.split("\n").each do |line|
      # combine continuing lines
      if(!(line =~ /^\s*#/) && line =~ /[^\\]\\\s*$/)
         multiLineArray.push line
         multiLineStr += line.sub(/^(.*)\\\s*$/,"\\1")
         next
      end

      # add final line
      if(multiLineStr.length > 0)
         multiLineArray.push line
         multiLineStr += line.sub(/^(.*)\\\s*$/,"\\1")
      end

      tline = ((multiLineStr.length > 0)?multiLineStr:line).strip
      if(tline =~ /^=begin/)
         commentBlock = true
      end
      if(commentBlock)
         # add the line unchanged
         dest += line + "\n"
      else
         commentLine = (tline =~ /^#/)
         if(!commentLine)
            # throw out sequences that will
            # only sow confusion
            tline.gsub!(/\/.*?\//,"")
            tline.gsub!(/%r\{.*?\}/,"")
            tline.gsub!(/%r(.).*?\1/,"")
            tline.gsub!(/\\\"/,"'")
            tline.gsub!(/".*?"/,"\"\"")
            tline.gsub!(/'.*?'/,"''")
            tline.gsub!(/#\{.*?\}/,"")
            $outdentExp.each do |re|
               if(tline =~ re)
                  tab -= 1
                  break
               end
            end
         end
         if (multiLineArray.length > 0)
            multiLineArray.each do |ml|
               dest += addLine(ml,tab)
            end
            multiLineArray.clear
            multiLineStr = ""
         else
            dest += addLine(line,tab)
         end
         if(!commentLine)
            $indentExp.each do |re|
               if(tline =~ re && !(tline =~ /\s+end\s*$/))
                  tab += 1
                  break
               end
            end
         end
      end
      if(tline =~ /^=end/)
         commentBlock = false
      end
   end
   STDOUT.write(dest)
   # uncomment this to complain about mismatched blocks
   #if(tab != 0)
   #  STDERR.puts "Indentation error: #{tab}"
   #end 
end

beautifyRuby 

Not long after I started beautifying my Ruby in TextMate, I realized that I also needed a way to keep my C and Objective-C code in shape. Here’s how I do it using MacPorts and bcpp.

bcpp is a C and C++ source beautifier by Steven De Toni and maintained by Thomas E. Dickey. It also works fine on Objective-C code. You can get the source from the link above and build it yourself, or easily install it with MacPorts. To learn more about MacPorts, see James Duncan Davidson’s introduction. If you’ve installed MacPorts, you can just type “sudo port install bcpp” to install bcpp on your system. If you’re using the default MacPorts configuration, it will go into /opt/local/bin.

To use bcpp from TextMate, open the TextMate bundle editor (Bundles → Bundle Editor → Show Bundle Editor) and use the ”+” menu in the bottom left of the window to add a new command. I called the command “beautify” and configured it as shown in the window below.

The configuration refers to a bcpp config file that I’ve put in my home directory. There’s a listing of my .bcpp file below. You can also download it from this link.

; This file contains configuration parameters that are used
; within the bcpp program.

; There are two types of parameter types : Boolean, and Integer.
; Boolean types can have only two valid values [On, Yes, or Off, No].
; Integer types can have a valid range of 0 - 5000.

;------------------------------------------------------------------------
; This parameter specifies how many lines separate between two
; functions.
;------------------------------------------------------------------------
  function_spacing            = 1        ; Integer

;------------------------------------------------------------------------
; Specifies whether to use tabs in indenting code.
;------------------------------------------------------------------------
  use_tabs                    = no       ; Boolean

;------------------------------------------------------------------------
; Specifies how many spaces to indent. This parameter is also used
; for tab indenting, as 1 tab may be worth 8 spaces if so desired.
; This parameter is used to position comments in TAB mode, and expanding
; of tabs within code!
;------------------------------------------------------------------------
  indent_spacing              = 4        ; Integer

;------------------------------------------------------------------------
; Specifies whether to indent preprocessor controls to match the code
;------------------------------------------------------------------------
  indent_preprocessor         = yes      ; Boolean

;------------------------------------------------------------------------
; Specifies whether to indent embedded SQL statements
;------------------------------------------------------------------------
  indent_exec_sql             = yes      ; Boolean

;------------------------------------------------------------------------
; Defines at what start position comments that have code on the
; same line to be placed.
;------------------------------------------------------------------------
  comments_with_code          = 50       ; Integer

;------------------------------------------------------------------------
; Defines at what start position comments with no code start.
;------------------------------------------------------------------------
  comments_with_nocode        = 0        ; Integer

;------------------------------------------------------------------------
; Set this option to ON turns off setting indentation position of parameter
; "comments_with_nocode". Indentation is then set according to code
; position.
;------------------------------------------------------------------------
  leave_comments_nocode       = yes      ; Boolean

;------------------------------------------------------------------------
; Use this option is used to change non-ascii (non-printable) chars to
; octal notation if they lie within quotes. Either
; Ascii_Chars_Only, XOR Leave_Graphic_Chars parameters need to be set
; as a True value for this parameter to take effect.
;------------------------------------------------------------------------
  NonAscii_Quotes_to_Octal    = yes      ; Boolean

;------------------------------------------------------------------------
; Setting this parameter to yes will strip non-printable characters
; from the source files, but leave any character that are IBM
; graphics alone. Any non-printable characters that lie within
; quotes will be transformed into octal/character notation, if
; NonAscii_Quotes_To_Octal parameter is set to True.
;------------------------------------------------------------------------
;  leave_graphic_chars        = yes      ; Boolean

;------------------------------------------------------------------------
; Setting this parameter to yes will strip any non-printable,
; non-ascii characters from the input file. Any non-printable
; octal/character notation if NonAscii_Quotes_To_Octal is set to
; True. Comment out this parameter if you are using
; Leave_Graphic_Chars parameter, as this parameter will override
; it.
;------------------------------------------------------------------------
  ascii_chars_only            = no       ; Boolean

;------------------------------------------------------------------------
; This parameter will place open braces on a new line after it's
; associated code if set on/yes. Else the brace will be place on
; next above line if possible, with it's code.
;------------------------------------------------------------------------
  place_brace_on_new_line     = no      ; Boolean

;------------------------------------------------------------------------
; This parameter will stop output from the program corrupting output
; that may exit from the program via the standard output.
; If this parameter is set to off/no then no output is generated from
; the program, unless an error is encountered
;------------------------------------------------------------------------
  program_output              = no      ; Boolean

;------------------------------------------------------------------------
; Specifies what the internal memory requirements will be in size of the
; line processing buffer. This essentially is used only for open brace
; relocation in kernighan/ritchie style.
;------------------------------------------------------------------------
  Queue_Buffer                = 10      ; Integer

;------------------------------------------------------------------------
; If this option is set to true then the input file will be backup into a
; another file with a ".bac" extension added to the end of the file
; name.
;------------------------------------------------------------------------
  Backup_File                 = yes    ; Boolean

Did you find an error? Is something missing? Post your comment or suggestion below!

Comments (0) post