IntelliSense in depth
IntelliSense can mean different things to different people. When it’s capitalised like ’IntelliSense’, it usually means Microsoft’s Visual Studio version. But what actually constitutes IntelliSense/intellisense?
One of the things I really miss when I switch from writing in C# to Ruby is IntelliSense. It’s there all the time in C# and I take it absolutely for granted. It’s a shock therefore to go to working in a Ruby editor where you type a dot – and nothing happens.
When we first roughed out the Ruby In Steel roadmap, we decided that IntelliSense was a ‘must-have’. The only problem was that we didn’t know how to do it – or even whether it was possible. If you Google for Ruby and IntelliSense, you tend to get “impossible!”, “can’t be done!”, “don’t even think of it!” and so on. Ok, I exaggerate a bit but it does seem to be generally held that Ruby is so dynamic that it can’t be done very well.
I can tell you that after a great deal of hard work, we’ve now got a decent IntelliSense system almost ready for beta testing. It’s not only possible to do Ruby IntelliSense – it’s also possible to do a pretty complete one (but naturally, I’m biased … ).
Putting The ’Sense’ Into IntelliSense
IntelliSense can mean different things to different people. For me, Ruby IntelliSense most certainly is not the semi-random production of some recently used words when you type a ‘dot’. Proper Ruby IntelliSense produces the correct member list (or whatever) deduced from applying Ruby scoping rules, Ruby include and require calls, singleton method definitions, nested classes, mixins, modules, hierarchical classes and so on.
This is quite different to how it works in a ‘static’ language like C# or Java. In those, when you have a declaration, List x say, then it’s simple to get the methods of a List object: you just look it up. With Ruby you have to first work out what the object is – a non-trivial operation - and also figure out what methods it can take by working through Ruby’s complex scoping and resolution rules.
When it works, Ruby IntelliSense is brilliant – just like C#. Though I have to admit that when it doesn’t, it can take some considerable time to figure out what the heck is going on.
Microsoft IntelliSense is generally considered to have a number of components. First – and most obviously, there’s ’member completion’. When you hit a ’.’ (or additionally ’::’ for Ruby), Visual Studio displays a list of members appropriate to the object to the left of the dot. In Ruby this is the ‘receiver’.
The key phrase here is ’appropriate to’. It’s pretty easy to do a superficial scan of a file and come up with the base Object class methods plus one or two that you’ve picked up in passing. But that is most emphatically not what Ruby In Steel does. Instead, the Steel symbol table builder does a thorough parse of the Ruby file - including all the ’required’ files - and figures out the right set of methods to display using the correct Ruby scoping rules. Here’s an example to show what I mean (apologies if this sounds like a script from Groucho Marx!):
Looking at the code, you can see that the module YAML itself contains a module BaseNode which is then included in the module Syck. In Syck, a class Node is defined which includes BaseNode. So the question for the IntelliSense system is: what are the methods to display for the object x created by the code
x = Syck::Node.new
The correct answer is b1 and b2 as shown.
Now let’s define a new object:
y = Syck::Node::InnerNode.new
Here’s the IntelliSense picture:
You can see that the InnerNode displays the correct IntelliSense for creating a new Object. You can also see that it correctly includes the class variable in2.
The objects displayed for y itself are just in1. This again is correct because the InnerNode, while encapsulated by Node does not descend from it.
Now lets make InnerNode a descendent of Node:
You can see that y now has b1and b2 as well.
I really want to emphasize again that to do this requires a far, far more complex system than is needed for the crude ’intellisense’ used by some editors. In addition, to get it useably fast isn’t that easy either. It requires careful attention to both coding and design. But as you can see, it can be done! There’s a good bit of work to do yet - this is still beta software and there are bugs (naturally). I don’t deal with Ruby DLL files (yet). Of course, there are some things the IntelliSense system won’t do, because it doesn’t run the Ruby code (in fact, it doesn’t use the Ruby interpreter at all). Here’s a pathological example:
It’s impossible without actually running the interpreter to determine the type of x. However, you can actually make reasonable deductions – even in this case – about what the class of x is and display its methods. The worst case (as here) is always Object, but I can usually do a lot better than this with realistic code.
I’ll cover other Visual Studio IntelliSense features (method tips, QuickInfo tips, Snippets and error ‘squigglies’ in the next few weeks. I haven’t fully implemented all of these yet, but the core systems are there and working. Well, mostly …
I’m so excited for this project. I will surely try it out. I think its worth the money.