Where Developers Matter
Integrated Development Environments for Windows, Java, and Web Developers
| | Log On

Groping in the dark

Abstract: A first look into the Open Tools API to implement an extension to the Alt-[ key mapping. By John Flaxman.

Following a brief discussion at a recent Delphi Development seminar led by Cary Jensen, which touched on some of the features of the Open Tools API and how it should be possible to improve the Alt-[ search inbuilt into the Delphi IDE, I decided to venture into the API myself to see what could be done. This article details the way things went. If you want to try the results, click here for the source.

The problem

Basically the current Alt-[and Alt-] keystrokes in the editor allow you to move to the matching delimiter (Parentheses or Bracket) but gets confused if there are any of the relevant matching delimiters in strings or comments between the real pair, so on the line:

FindNext(Context, ), False, False, False);

pressing Alt-[ moves to the closing parenthesis in the string rather than the one at the end of the procedure call. Also, the function is limited -- it would be useful to be able to move from a "begin" to an "end" in the same way.

The challenge, then, was to use the Open Tools API to build a key binding that would allow a more accurate and flexible delimiter-matching process including begin/end, repeat/until, and so on.

Resources 

Documentation of the API is fairly limited, A book by Ray Lischner, Hidden Paths of Delphi 3, covers the topic in some detail -- find a copy if you can. Failing that, there are some areas on the Web which are fairly easy to find using any of the search engines. There is also an article on the Borland Community Web site by Allen Bauer which has a link to a wizard for creating key bindings in the IDE.

The most readily available resources are two Delphi .pas files. BufferList.pas contains an example of an entire set of key mappings (New Classic) and an "enhancement module" called Buffer List, which allows the Ctrl-B combination to show a list of active buffers. The second file, ToolsAPI.pas, has the definition of various interfaces and enough comments to give good pointers to how to do things.

Armed with these files, I ventured into the Open Tools API.

Groping away

By cannibalizing BufferList.Pas it is fairly easy to get a keystroke to do something other than its standard function. You'll need to create a package with your code and ensure that ToolsAPI.pas is visible to the compiler. The following .pas unit provides the basics:

unit keytest;

interface
  procedure Register;

implementation
  uses Windows, Classes, SysUtils, ToolsAPI, Menus, Forms, Dialogs, Controls;
  type TJWFKeytest = class(TNotifierObject, IUnknown, IOTANotifier, IOTAKeyboardBinding)
  procedure testing(const Context: IOTAKeyContext; KeyCode: TShortCut;
  var BindingResult: TKeyBindingResult);
  function GetBindingType: TBindingType;
  function GetDisplayName: string;
  function GetName: string;
  procedure BindKeyboard(const BindingServices: IOTAKeyBindingServices);
end;

resourcestring
  sTesting = 'Testing';

procedure Register;
begin
  (BorlandIDEServices as IOTAKeyBoardServices).AddKeyboardBinding(TJWFKeytest.Create);
end;

{ TJWFKeytest }
function TJWFKeytest.GetBindingType: TBindingType;
begin
  //tells the system we are simply extending existing 
  //edit functions rather than mapping an entire set
  Result := btPartial;
end;

function TJWFKeytest.GetDisplayName: string;
begin
  //returns the string for the editor options dialog
  Result := sTesting;
end;

function TJWFKeytest.GetName: string;
begin
  //unique name for the bindings (suggested is yourname.bindingname)
  Result := 'Prospect.Testing';
end;

procedure TJWFKeytest.BindKeyboard(const BindingServices: IOTAKeyBindingServices);
begin
  //register the match routines with the key binding
  BindingServices.AddKeyBinding([ShortCut(Ord('['), [ssAlt])], testing, nil);
end;

procedure TJWFKeytest.testing(const Context: IOTAKeyContext;
  KeyCode: TShortCut; var BindingResult: TKeyBindingResult);
begin
  MessageDlg('Hello World', mtInformation, [mbOK], 0);
end;

end.

If you install this package in the IDE, two thing should happen. First, pressing Alt-[ in the editor should cause the test message to appear. Second, if you look at the "key mappings" tab of the IDE's editor options you will notice an enhancement module called Testing which you can switch on and off to control this new feature.

A word of warning here: I assumed the KeyCode passed to the "testing" procedure would be the one that triggered the event. But if you make a  testing routine like this:

If KeyCode = (shortCut(Ord('['), [ssAlt])) then
  MessageDlg('Hello World', mtInformation, [mbOK], 0);

the message does not appear! Can you see why?

Having got something to happen it was now time to look at the "Context" parameter to see what could be obtained from it. A bit of digging revealed several pieces of information which are needed to put together the delimiter-matching project. The root interfaces are:

  • Context.EditBuffer.EditPosition  which gives access to the current text cursor position.
  • Context.EditBuffer.EditBlock  which gives access to selection areas.
  • Context.EditBuffer.TopView  which gives access to the edit view.

EditPosition allows you to move the cursor in various ways, check the character to the right of the cursor, and access the search facilities. TopView allows you to set which part of the file is currently displayed and to access information from the syntax highlighter -- which is crucial to determining if characters are within strings or comments. The ToolsAPI.pas file has each of these interfaces commented fairly well, and most of the properties and procedures are self-explanatory. Only one seemed to give any real trouble. TopView has a function defined like this:

procedure GetAttributeAtPos(const EdPos: TOTAEditPos; 
  IncludeMargin: Boolean; var Element, LineFlag: Integer);

The comments imply that setting the IncludeMargin parameter to false should prevent the function from returning the special "right margin" value. However, on one of my two development machines it made no difference. I added a workaround.

Lighting a single candle

Overall, light is beginning to filter through. The IDE enhancement module works more or less as I wanted it. It still gets thrown now and then when there are conditional IFDEF blocks, as the algorithm assumes all the code that is not commented out is active.

The project code has been fairly well commented and should be fairly understandable. I hope that it, along with this brief article, help shed some light on some of the aspects of the Open Tools API.


Published on: 8/13/2001 12:00:00 AM


Server Response from: BDN9A

 

Borland® Copyright© 1994 - 2008 Borland Software Corporation. All rights reserved. Contact Us  |   Site Map  |   Legal Notices  |   Privacy Policy  |   Report Software Piracy