Ruby On Rails Code Completion #2 - The Faster The Better!
When you’re buying a house, the three things you look for are “location, location and location”. With IntelliSense (aka code completion), it’s rather different. In order, the three things you want are speed, accuracy and relevance. Let’s look at these in turn.
First off - speed. Bluntly stated, if the code completion information isn’t there NOW, I’m not going to use it. And by NOW I mean less than half a second. I use Microsoft’s C# IntelliSense all the time when developing Ruby in Steel and it’s just there. I don’t have to think about it and it doesn’t distract me from typing. If I had to wait two seconds, I wouldn’t use it: it would interfere with my typing too much. I really can’t emphasise this enough: if code completion isn’t fast, it’s useless.
Secondly, accuracy. It’s important just to have only the correct information displayed. For example, the information displayed after a dot is different from that displayed if there is just whitespace and you type CTRL-SPACE. Also, the methods available in ‘class’ context are different to those displayed in ‘method’ context (I’ll explain and demonstrate this below).
Lastly, relevance. If you look at some of the ‘code completion’ from our competitors, you’ll see right at the top of the list __FILE__. It is actually in scope most of the time, but when was the last time you wanted to use this identifier? Right. Yet, it’s at the top of the list. Similarly with all those operator methods like <==>. Relevance means trying to be reasonable in what is displayed in a completion list.
Now, let’s see how we stack up. Speed wise, I cannot detect any delay whatsoever when I CTRL-SPACE for a completion list. Here’s the result:
You might (reasonably) say that you’d expect no delay on a 2.4GHz Core Duo with 3GB of memory (which is what I use most of the time). Ok. So I tried exactly the same program on a somewhat older machine – an arthritic seven year old 1GHz Celeron with 1GB memory which is well overdue for its appointment with the scrapheap but which I keep in a dusty corner in order to be able to run tests like this. And the result ... exactly the same. No delay whatsoever.
So how do we do this? Well, first off we don’t use Ruby. I repeat: we do not use Ruby (or Rails) in any way to get code completion data. Ruby is just way, way too slow.
What we do instead is read in all the relevant parts of Rails, parse them in exactly the same way as we do for any Ruby program that you might write. Then we output the resulting data as ‘stubs’, removing the innards of methods, since the interior of a Ruby method (generally) can’t be seen externally. Having got the stubs, we then re-parse them and compile them to a binary format. This is then the IntelliSense database. This is done once (only once) when you first start Ruby In Steel and it takes less than 10 seconds on my machine. It’s this ‘compiled IntelliSense’ that gives us the speed.
So to summarize:
We don’t use Ruby
We parse the Rails files using exactly the same parser algorithms that are used in our standard Ruby parsing.
We compile the IntelliSense for code completion.
Now it also turns out that parsing Rails is a very good test of the parser. If you can parse Rails, you’re in good shape. While we don’t handle everything Ruby can throw at us yet (that will have to wait until I incorporate Antlr 3), we deal with over 99% of it.
This brings us on to accuracy. A good example of this can be found in the little example I used above. Notice that I did the CTRL-SPACE in the ‘class context’, not in a method. But if you look at the definition of has_many say, you’ll find that it isn’t a class method at all: it isn’t a singleton. It should only be visible in a method like this:
If you look into Rails, you’ll find that Rails makes methods like has_many available as class methods by using extend. Here’s an example:
Here, the methods of M are being included in class Y as singleton or class methods.
In fact Rails does something like this:
And here’s what we get:
Which is right (of course!).
Just a couple of points here. I’ve colored the entries in the completion list to bring out the various class type (inheritied, included and so on). We haven’t decided whether to release this as a feature yet – it’s more of a diagnostic aid to help in development. Also, we’re still testing and debugging this release, so while I think the completion lists are correct, I’m still checking them and fixing things. Finally, all of this – repeat all - applies to standard Ruby. We have improved the base Ruby IntelliSense system to deal with Rails - and not hacked the Ruby side to do the Rails stuff. If you only use Ruby and never use Rails, you still benefit!
In the next article I’ll cover the final point “relevance”, and the difference between CTRL-SPACE and “dot” operations, some “killer” IntelliSense constructs that I know only Ruby In Steel can deal with and show you some other very nice new features.
And this is before we even get onto the Visual Rails Workbench!
seems awesome :)
I am currently not entirely happy with NetBeans/IntelliJ etc and their Rails support and therefor re-thinking about purchasing your plugin, but what I was wondering is whether it already handles edge-rails properly in terms of... e.g. I have ruby 1.8.6 installed on machine and the 1.2.3 rails gem.
However, for all my projects I freeze (or use svn:externals) edge rails into vendor/rails.. what would Ruby in Steel do in this case with intellisense - use the methods etc that standard/system-wide rails provides (including the parameters etc) or will it use the (in this case) proper methods etc from the locally frozen edge rails?
What about plugins which I change / add/remove etc more or less frequently.. will RiS get the changes there, too?
With 1.2 you will be able to select the Rails IntelliSense directories and re-generate the Rails IntelliSense database using the Librarian:
This is a bit simple right now, but it will get a lot better and more flexible in the future.
However, I haven’t made any provision for switching an IntelliSense database on the fly. Dynamic inclusion of IntelliSense databases is planned for a future release (it will work a bit like the way you include assemblies in Visual Studio).
To do what you want to do, you would have to copy the existing Rails database file somewhere safe then create a new database file for the Rails version you were interested in. Restarting VS would the give you the IS from the new database.
So I think the answer to your question is "yes" you can do it but you would have to manually copy the compiled database file into the right location to switch between two Rails versions. A better solution would be to use a virtual machine with another Visual Studio installed.
Hope that helps
So basically I can manually trigger the regeneration with the dirs specified. That’s not nice, but useable :)
Does Steel recognize and re-index additions of gems / plugins automatically?
The Librarian is at an early stage (basically it’s an adaptation of a tool we originally only intended for internal use) so it should get more seamless over time... ;-)
We don’t currently do any special handling of gems other than allow people to install gems using a simple dialog. Can you give us some concrete examples of the sort of integration you would need? (i.e. if you can step us through the kind of work you would have to do ’long hand’ in order to recognise/re-index, that might give us a better idea of what we can do to make the process smoother).
I’d love to provide you with some typical usecases so you know how I or typically many rails devs work with gems/plugins etc. Do you use any kind of messaging client we can use to talk? or send me a quick e-mail and we can go through the Qs there...