Ruby On Rails Code Completion #3 - Getting It Right
If you start off with a set of ad-hoc ‘rules’ and ‘templates’ you’ll never achieve good IntelliSense – either for Ruby or Rails. When I first started the design of Ruby In Steel, one of the decisions was to start with Ruby IntelliSense and work towards Rails. However it’s much more difficult to build a coherent IntelliSense parsing system than to use lookup lists and ‘templates’. The upside is that the resulting IntelliSense is vastly superior to that obtained by a simpler approach. I’ll illustrate this with some simple tests.
First of all I’ll do a simple comparison between what Ruby In Steel produces after CTRL-SPACE and what Ruby itself considers to be valid:
This is in Ruby’s ‘main’ context. On the left is what you get from our IntelliSense and on the right are Ruby’s actual methods as obtained by running Ruby. You’ll see that they are pretty much identical (the small differences come about because I do a case-insensitive sort while the Ruby fragment I ran to generate the list uses a case-sensitive one so here, for example, Float is shown in strict alphabetic order in the code-completion list). Also, I only include methods starting with a letter; Ruby has one or two methods (like __id__) which I deliberately exclude.
The thing to notice here is that the list is correct! Only the methods that should be displayed are displayed. Just try this on any other Ruby IDE and see what you get.
Now let’s try something a bit more complicated. What happens after a ‘dot’? You don’t get the same methods as a free-standing CTRL-SPACE because the Ruby object that you are ‘dotting’ can only use its public methods. In a free-standing CTRL-SPACE you can also access the private methods of the class and its superclass – so the methods displayed are different. Here’s what I get - and what Ruby gets:
Apart from the accessor method z1 which I chose to show as one method, because that’s how you use it , they are identical! Again, I want to emphasise that this is what you should get with true IntelliSense: no more - and no less - than what Ruby shows. Ruby In Steel gets it right is because we parse the Ruby code and then ‘execute’ the resulting Abstract Syntax Tree (AST). But instead of producing byte code (or whatever) from the AST, Ruby In Steel generates IntelliSense. It’s a lot of hard work but I think the result is well worth it.
Of course, there are limitations. Because we don’t actually run Ruby (as I explained last week, running Ruby for the purposes of code-completion would be way too slow), I can’t get the details of all the methods available to a Ruby program as it executes. For example, new methods might actually be added at runtime which I can’t deduce at ’edit-time’. I don’t want you to get the impression that we can do IntelliSense that exactly mimics the IntelliSense in C#. Because of the dynamic and typeless nature of Ruby, IntelliSense is always going to be imperfect. However, with every release of Ruby In Steel, I improve the algorithms and techniques I use. And I repeat, they are not simple pattern matching and lookup lists! As a result you will see even better and more complete IntelliSense in the future.
Finally, before moving onto some other cool new features, I’ll look at two ‘killer’ IntelliSense tests. These are designed to torture the poor IntelliSense designer (me!) with common, but in some sense tricky, constructs. Here’s one set:
Here, each of the merge statements should resolve to Hash. This isn’t too difficult when you are doing a ‘deep parse’ of the entire code. It’s pretty easy in fact. However, it’s a lot more difficult to get right as you type. The reason is that you simply don’t have the time to do a full parse of the code - and, what’s more, when code is still in the process of being edited it is almost certain to be syntactically incorrect. So we have to scan and resolve the line that you are on in order to generate the IntelliSense - and we have to do it quickly too. It’s one of the subtle points that need to considered if you want to get a good IntelliSense system.
Now, how about this?
The problem here is new. What are the methods of new? Clearly, new doesn’t have any methods – what you want is the methods of Icalendar::Parser, in this case parse1/parse2. The result of that is an Array as shown by the hovering tooltip (I’ve hovered over the cals variable).
Yes, it’s a simple example. But just try it with any other Ruby IDE of your choice and see how they cope.
Next week, I’ll cover the Rails side of things in more detail.