Thursday, October 12, 2006

Brief guide to building a Delphi IDE AddIn

Someone in the Delphi IDE newsgroup asked the question, "Is there a guide anywhere about writing IDE addins"? After experiencing my own pain in building a small add-in (or is it plug-in?), I decided to make up a quick post. The answer to his question is a resounding "no." Well, you might consider this as something of a guide, but I had better luck getting started with these 2 docs:

The first thing to note is that I found that writing the add-in in .Net seemed to make it work for both Win32 and .Net Delphi projects.

When I started mine, I was really naive and thought there'd be some API with calls like GetStartNextMethod or something. You only get the buffer.

So here's the start - getting the source editor (sorry - snippets lost their formatting - too much work to put back in using blogger):

using Borland.Studio.ToolsAPI;

public class SourceViewer // base class
protected IOTASourceEditor fSourceEditor;
public IOTASourceEditor sourceEditor { get { return fSourceEditor; } }
public SourceViewer()
IOTAModuleServices moduleServices = BorlandIDE.ModuleServices;
if ( moduleServices == null )
fSourceEditor = null;
fSourceEditor = moduleServices.CurrentModule.CurrentEditor as IOTASourceEditor;
fSourceEditor = null;

Then you use the source editor to get a reader to get the text of the buffer:

IOTAFileReader reader = fSourceEditor.CreateReader();

private string getRawSourceText( IOTAFileReader reader )
const int READ_CHUNK = 1600;

if (reader == null)
throw new Exception( "No file reader" );

StringBuilder sb = new StringBuilder();
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
Byte[] source = reader.Read( READ_CHUNK, 0 );
while ( source.Length > 0 )
sb.Append( enc.GetString(source) );
source = reader.Read( READ_CHUNK, 0 );

return sb.ToString();

To get the IDE to integrate your AddIn, implement IOTAMenuWizard:

public class TestPlugIn: IOTAMenuWizard

public static void Register()
IOTAWizardService WizService = (IOTAWizardService)BorlandIDE.GetService( typeof( IOTAWizardService ) );
wizardID = WizService.AddWizard( new TestPlugIn() );

Then implement the other methods of IOTAMenuWizard such as execute and enabled. I wanted mine to be enabled when any source file is open (referencing the first class above):

return new SourceViewer().sourceEditor != null;

Once your assembly is ready to go, register it with the IDE (so the IDE will load it on startup):
  • Create a key under HKEY_CURRENT_USER\Software\Borland\BDS\4.0\Known IDE Assemblies.
  • Give the key a name (don't recall if the name matters / where it might show up, but I think it has to have a name).
  • Add the path to your assembly there.

Friday, October 06, 2006

Ridding (not riding) the Range

I added a suggestion to David's list of potential improvements that we all discussed in Minneapolis. He asked me to describe why it's a problem and what I think the solution is...

Why the Range global is a problem
  • It's like a master key that tightly couples all parts of the system. We will not be able to separate annuity from life w/o either eliminating it or duplicating it for both.
  • It's a global (or rather a collection of globals; same thing). Reference my blog post for reasons why this is bad.
  • It prevents the furtherance of OO development in the system.
I Object!
Objects are objects for a reason. They should encapsulate their data. The range object is a circumvention of OO. A given product will manipulate any number of range variables for varying functionality. So rather than being able to look at the product class methods and members to determine it's behaviour, you may have to guess what range variables it manipulates to do so. Variables should be owned by the proper object that manipulates them. Range variables are manipulated by any other object in the system. This leads to conditional code multiplied all over. So instead of this:


We have this:

If Rng.Something then
else if Rng.TheOtherThing then

If the individual objects properly encapsulated their data, we wouldn't have this problem.

I don't see any other way than to:
  • Forbid adding any new variables to the range object (with exceptions for exigent circumstances).
  • Start removing it's variables. For each:
    • Search the system for it's use.
    • Determine which object at which level in the heirarchy should encapsulate the data.
    • Move the variable to the appropriate object.
    • Adjust the code that references it.
  • Whenever sets are encountered, this should be seen as an indicator that a new object may be needed. Sets (and their brother case statements) should be rare in OO (1). Unlike an object, they encapsulate nothing. So if you look at a set in code, you learn nothing. In order to learn how the system manipulates the members of the set, you would have to search the system for all references to the set. When you do this search, you will no doubt encounter many case statements and for loops. If the business logic were instead object based, you would simply call BaseType.DoSomething and polymorphism would handle the conditional behaviour of the members of the set. Thus you don't end up with case statements and for loops that usually start out at a manageble size, but enevitably grow into something that is very difficult to maintain.
No doubt this will initially lead to some other objects becoming bloated, but this is a process. The bloated objects should, in turn, be distilled into more granular objects.

1) Refactoring, p. 34, 82

Tuesday, October 03, 2006

Good Javascript Reference

I was working on something for our new App Wizard Search today and came across this Javascript reference on the Mozilla developers' site.

If you go up a level or two there are all kinds of sections about various web development topics as well. It is all geared for Mozilla but with some consideration it would most likely be applicable across most browsers.