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.