macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Customizing TextMate
Pages: 1, 2

This just creates a Stack object that we can do our manipulations on. The actual implementation is just an Array, since Ruby's Arrays can easily behave as a stack. The object also stores the raw input and error messages, in case it runs into problems.



The push() method reads our stack back in from the input TextMate passes us when it triggers our script. It just pulls off stack-level markers and turns the numbers into actual Ruby objects.

The print() method does the opposite, writing our new stack back out to TextMate. When there is an error, the error message and input we received are sent back. Otherwise, the code calculates some widths so it can print the stack, then does the writing.

Most output is snippet escaped by a helper library that ships with TextMate. This is important because we want to have the command write out a snippet instead of just normal text. Snippets have a couple of uses, but in this case we just want to be able to insert a ${0} tab stop variable, so TextMate will move the caret to the bottom of the stack.

Again, we need to save this code now that we know what it does. Where you save it is important. We need it to be inside our RPN bundle so TextMate can find it when we need it. TextMate will have placed your bundle in ~/Library/Application Support/TextMate/Bundles, in a folder called RPN.tmbundle. Create a Support folder inside the bundle and a lib folder inside that. You should be able to do this by feeding the Terminal the following line:

mkdir -p ~/Library/Application\ Support/TextMate/Bundles/RPN.tmbundle/Support/lib

With the directory created, save this code into it and name it "stack.rb" so we can find it when we wrap it in a TextMate command. In fact, let's do that wrapping now:

  1. Go back into the Bundle Editor and click once on the name of your RPN bundle to highlight it.
  2. Select New Command from the Plus menu button and name it Push.
  3. Set a Key Equivalent of enter and set the Scope Selector to source.rpn so we don't affect any other documents.
  4. Set the command Input to Selected Text or Document and Output to Insert as Snippet. This asks TextMate to hand us the entire document on STDIN and replace it with the snippet we will print to STDOUT after our command is run.

When you get that far, you will be ready for the code of the command itself. All we need to do here is load and print the stack, so this little chunk of code will do:

#!/usr/bin/env ruby -w

require "#{ENV['TM_BUNDLE_SUPPORT']}/lib/stack"

stack = Stack.new($stdin)
stack.print($stdout)

At this point, you should now be able to push values onto the stack. Close the Bundle Editor and go back to your RPN document. Try entering any number and pushing the Return key. The stack should appear and show what you entered. Try pushing another number.

Adding Math Operations

The good news is that we've done all the hard work already just by setting everything up. The rest is pretty trivial. First, let's go back into our Ruby Stack class and add some methods for stack and math operations:

class Stack
  def dup;  unary_operation { |stack, f| stack << f << f }     end
  def pop;  unary_operation { }                                end
  def swap; binary_operation { |stack, l, r| stack << r << l } end

  def add;      binary_operation { |stack, l, r| stack << l + r } end
  def subtract; binary_operation { |stack, l, r| stack << l - r } end
  def multiply; binary_operation { |stack, l, r| stack << l * r } end
  def divide
    binary_operation do |stack, l, r|
      if r.zero?
        @error = "Division by Zero"
      elsif (l % r).nonzero?
        stack << l / r.to_f
      else
        stack << l / r
      end
    end
  end

  private

  def unary_operation
    if @stack.empty?
      @error = "Stack Underflow"
    else
      yield(@stack, @stack.pop)
    end
  end

  def binary_operation
    if @stack.size < 2
      @error = "Stack Underflow"
    else
      right, left = @stack.pop, @stack.pop
      yield(@stack, left, right)
    end
  end
end

These should be pretty straightforward. A couple of helper methods are used to pull the needed number of operands off the stack. Each method then uses those numbers and places results on the stack when needed. The divide() method is the only complicated one, and that's because I need to detect division by zero errors and force Ruby into floating-point arithmetic as needed.

When you have this code in place, make a small edit to your Push command, adding the following line between the code that creates the Stack and the line that prints it:

stack.dup if ENV["TM_CURRENT_WORD"].to_s.empty?

This will allow you to duplicate the first entry on the stack just by pressing Enter on a blank line. That's a super handy trick when entering expressions. The code works by checking an environment variable TextMate sets before running your command to see whether you have entered any content on that line yet. TextMate provides a good deal of information to the automations you build through variables like this.

Finally, we need to wrap the other methods in TextMate commands. These new commands are almost identical, so I'm only going to show one of them. Here's the code for a Subtract command:

#!/usr/bin/env ruby -w

require "#{ENV['TM_BUNDLE_SUPPORT']}/lib/stack"

stack = Stack.new($stdin)
stack.subtract  # change this call for the other commands
stack.print($stdout)

This command should have the same Input, Output, and Scope Selector of the Push command and TextMate has a shortcut to help with that. With the old command selected in the Bundle Editor, use the double Plus button to duplicate it. Then just change the name, code, and Key Equivalent. I used - for the Key Equivalent on this one, though that does make entering negative numbers a hassle.

Here's a list of the other commands I created, the Key Equivalent I set for them, and the code I used to call into my library:

  • Pop (delete): stack.pop
  • Swap (right arrow): stack.swap
  • Add (+): stack.add
  • Multiply (*): stack.multiply
  • Divide (/): stack.divide

Ready to try out your new calculator? Great. Try to find the answer to this expression using the stack:

(4 * 6 - 5) / (1 + (4 * 6 - 5))

I did it with the following keystrokes:

4 enter 6 * 5 - enter 1 + /

Summary

Feel free to download the RPN bundle we created in this article and play around. You can install this bundle by double-clicking it or dragging it onto the TextMate application icon. Try adding in some of your own ideas to get practice customizing TextMate.

While this exercise isn't very sophisticated, my hope is that it gives you ideas about how you might customize TextMate for your own unique purposes. As you can see, it doesn't take very much effort to make some pretty drastic changes.

TextMate has many, many more options for customization than I could begin to cover in the space of this small article. That's why I wrote a book on the subject. The book goes into greater detail on everything I've touched on in this article, and also shows how you can get a lot of value out of TextMate without writing any code. I recommend picking up a copy if you want to learn more about how you can make TextMate exactly what you need it to be.

James Edward Gray II is a contract programmer in Oklahoma and the author of TextMate: Power Editing for the Mac.


Return to Mac DevCenter.