IntelliSense (phase 1)
- Code completion - lists of methods and accessors that drop down alongside a class or object - are just one part of the IntelliSense which we’re putting into the Developer Edition of Ruby In Steel.
IntelliSense is progressing nicely, though it’s taken me somewhat longer to get it integrated into Visual Studio than I was expecting. The main problem has been that I initially designed the symbol table as a ‘one off’. Why one earth would anyone need two of the things?
Well, it turns out that you do indeed need more than one if you want to do IntelliSense: if you have an error in your Ruby code or are typing happily away, then your Ruby file won’t parse and unless you have a copy of the symbol table, you don’t get any IntelliSense. An object lesson in why it’s a good idea to engage the brain before engaging the keyboard.
After unscrambling all the global variables (sorry, static variables), the next problem was how to copy a symbol table. It’s not that easy in .NET and it turns out that the only reasonable way of doing it is to use ‘serialization’. With serialization, you write all the data out as a binary stream to some sort of backing store, then read it back in again – ‘deserialize’ it. Unfortunately, this is slow – my first attempt took 3 seconds to cycle a symbol table of 2500 symbols. Put it another way, that’s about 1ms per symbol or, on my PC, about 3 million instructions per symbol!
After a good bit of tweaking, I got this down to half a second – ok, but not brilliant. This doesn’t affect the speed at which the drop-down ‘code completion’ list appears, incidentally – that’s pretty near instantaneous. It just affects the speed of the analysis that goes on silently ‘behind the scenes’ and, for my own satisfaction, I want that to be done as quickly and efficiently as possible.
I’m somewhat surprised that the .NET designers at Microsoft have not come up with a decent way of simply deep copying an object. The parsing itself seems to run at around 1ms per line of Ruby code. I’m pretty sure I can improve greatly on that later on.
Anyway, here’s a picture of what you get from doing self. in a class X which has included a module N.
I’ve turned off all of the methods you get as default from the mixing in of the Kernel module with Object. There are about 60 methods in there and with them all on, you can’t easily see which ones you want. Currently, I’m experimenting with various ways of managing this list.
Here’s another picture of Ruby IntelliSense showing inheritance of a class Y from X. Module M is now mixed in and you can also see an accessor definition, name.
Finally (just to show I’m not faking it!), here’s a picture of an ‘IntelliSensed’ Array object.
In general, the Steel IntelliSense system figures out what the class of an expression is and then displays all the available methods, including singletons, inherited classes, included modules and those added via extend. And it does it all without calling Ruby. Obviously, it can’t deal with all Ruby’s dynamic behaviour, but it does seem to be good at handling most - if not all - of the things that are useful as you type or edit a Ruby program.
I’m going to be doing a good bit more testing over the next week and then if it stills looks ok, it’s over to Huw and the beta test team he’s assembling.
PS: I decided to take time out today to get Smart Indenting to work – I decided that I couldn’t live any longer without it. Pretty cool too – it’s now getting to be like working in C#, except that it’s Ruby. Strange feeling!
Looks like you are making great progress - congratulations! I can’t imagine how much work is going into this.
A couple of suggestions for managing the methods displayed:
* If intellisense supports multi-level menus, you could show the modules and ancestors of a class as menu items, and each opens a sub-menu containing the methods inherited via that module/class. e.g., on a new class that inheritfs from Object and has one method, foo: * foo * Kernel * puts * ... 60 kernel methods ... * Object * object_id * .. remaining object methods ... * Prepend each inherited method with the base class (i.e. "Kernel::puts"). Ugly though. * Just dont’ worry about it - people will figure it out pretty quick. The same problem exists when using classes extended from base classes like Windows.Forms.Control (try DataGridView sometime). It sucks but there isn’t much you can do.
Finally, will your intellisense include method arguments and (crossing fingers) RDoc comments associated with each method/classs?
Unfortunately, VS IntelliSense supports only one level of menu (as far as I can see).
I hadn’t though about incorporating RDoc. Initially, I thought it wasn’t a flier, but I’ve been thinking about it quite a bit today, and I think it’s quite doable. In fact, it’s more than doable - it’s a great idea!
The reason is that a Ruby def needs a bit of help for IntelliSense. I can infer what it returns quite often, but equally ofen it’s not at all clear. With RDoc, you can specify what the method returns and I can pick that up and use it.
The IntelliSense bit comes from a mouse ’hover’ over a method name. That’s reasonably easy to do. I’ve already got a lot of RDoc stuff in for the base class library (Object, Kernel, etc.). All I need to do is link it all up.
I’ll have to think about where this fits into the project plan - but as I say, I think incorporationg RDoc is a pretty neat idea.