Parnassus Bookmarks API
- Important Notes
- Demo, Support, etc
The Bookmarks plugin provides a public API, which allows you, a third-party plugin developer, to access bookmarks, add or delete bookmarks, and register for notifications.
The API uses types from the Common API.
The Delphi / C++Builder IDE loads plugins (wizards) as DLLs or BPLs. To access the Bookmarks plugin API from your own wizard or plugin, you need to find if the Bookmarks DLL has been loaded, and then get a procedure it exports that returns a plugin interface. Using that interface, you can access the plugin’s functionality.
The API is accessed entirely through interface references. The objects will only be freed once the last reference to them has been released. Since the Delphi compiler automatically manages interface references, having the reference is all you need. However, you must manually load and unload the Bookmarks DLL itself.
The plugin interface currently gives access to the plugin version, and to an interface representing the list of normal bookmarks. You can use this to enumerate bookmarks, add or remove bookmarks, and register for notifications about changed bookmarks.
In your plugin, locate and load the Bookmarks plugin with PnAPILoadBookmarksPlugin. (Unload via PnAPIUnloadBookmarksPlugin.)
- This returns an IPnAPIBookmarkPlugin, representing the Bookmarks plugin object. Access the BookmarkList property to get a list of bookmarks.
- Enumerate over this list
- Call AddBookmark and RemoveBookmark
- Register for bookmark notifications (bookmarks added, removed, changed) by adding to the multicast interface event OnBookmarkNotification
Details on the methods to do this, exact interfaces etc are below.
The API is not a direct replacement for the IDE’s bookmarks API. It is structured quite differently, but using it you should be able to achieve everything you can with the IDE’s API, plus more – for example, the IDE has no way of notifying a plugin about changes to bookmarks, requiring a plugin to poll, whereas the Bookmarks API has a notification mechanism.
The API does not currently give access to the ‘temporary’ bookmarks, the ones created with Escape and Shift-Escape. These are the most likely to change in future.
This unit has methods for finding / accessing the Bookmarks plugin, if it is loaded by the IDE.
function PnAPILoadBookmarksPlugin : IPnAPIBookmarkPlugin;
- On success, it returns an IPnAPIBookmarkPlugin interface instance.
- If the IDE has not yet loaded the Bookmarks plugin (for example if it is not installed, or if it is installed but has not yet been loaded by the IDE) it returns nil.
- If the Bookmark plugin exists and is loaded, but was unable to provide a IPnAPIBookmarkPlugin interface because the exported method was not found, an exception is thrown. This should never normally occur.
Each call to PnAPILoadBookmarksPlugin must be matched to a call to PnAPIUnloadBookmarksPlugin. The reason is that each call adds a reference to the Bookmarks DLL.
Remember that Delphi releases references to interfaces at the end of a method, so if you call PnAPIUnloadBookmarksPlugin in a method that has a reference to any Bookmarks interfaces, when the method ends you may get an AV because the DLL those interfaces are located in has been unloaded. This is easy to do by accident, and hard to test for since it depends on the plugin load order whether this call is the one that causes the Bookmarks DLL to be unloaded or not. Be careful you have released all interface references before you unload the plugin.
This unit contains the main bookmark list and bookmark interfaces. You get a reference to the bookmark list when you load the Bookmarks plugin, and using it you can enumerate, add, remove, go to, etc individual bookmarks.
This interface represents the plugin itself, ie acts as the gateway to plugin functionality.
IPnAPIBookmarkPlugin = interface
function GetBookmarkList : IPnAPIBookmarkList;
function GetVersion : TPnAPIFileVersion;
property BookmarkList : IPnAPIBookmarkList read GetBookmarkList;
property Version : TPnAPIFileVersion read GetVersion;
The key part of this interface is the BookmarkList property. This object represents the actual list of normal bookmarks.
The Version property represents the plugin version, and is:
TPnAPIFileVersion = record
Major, Minor, Release, Build : Integer;
function AsString : string;
The version reported is the same as the FileVersion in the Bookmark DLL’s properties / version resource. AsString returns the version number in dotted form (eg, ‘18.104.22.168’.)
This interface, accessed through the BookmarkList property of the plugin interface, represents the list of normal bookmarks – ie is the key interface in which you are probably interested. ‘Normal’ bookmarks are the numbered bookmarks that draw as a red ribbon in the code editor gutter.
Through this interface, you can enumerate all bookmarks the plugin is aware of, accessing each individual bookmark; add and remove bookmarks; and register for notifications about bookmarks being added, removed, or changed.
IPnAPIBookmarkList = interface(IPnAPIEnumerable<IPnAPIBookmark>)
function AddBookmark(const Filename : string; const Line : Integer;
const SpecificNumberKey : Integer = -1; const Silent : Boolean = false) : IPnAPIBookmark;
procedure DeleteBookmark(const Bookmark : IPnAPIBookmark; const Silent : Boolean = false);
function GetBookmarkNotification : IPnAPIMulticastInterfaceList<IPnAPIBookmarkEvents>;
function GetItems : IPnAPIReadOnlyList<IPnAPIBookmark>;
property OnBookmarkNotification : IPnAPIMulticastInterfaceList<IPnAPIBookmarkEvents>
property Items : IPnAPIReadOnlyList<IPnAPIBookmark> read GetItems;
You can enumerate all bookmarks (with for..in syntax) directly over this object, since it implements IPnAPIEnumerable<IPnAPIBookmark>.
Items represents a read-only list of bookmarks. You can enumerate over this too, or access a specific bookmark by index. It holds 0..Items.Count-1 bookmarks.
AddBookmark adds a bookmark. Specify:
- The filename in which it should be added. This is the fully-qualified path and filename. If interacting with the ToolsAPI, the filename should match the IOTAView.Buffer.Filename property of the view displaying the file. Case is not important.
- The line number where it should be added in the above file. There is no error checking: you can add a bookmark at line 100 of a thirty-line file.
- Optionally, specify a ‘number key’ – that is, an integer between 1 and 10, or -1. This represents the keyboard number the bookmark should be added for: 1-9 or 0. Bookmark 10 maps to keyboard 0, so pass in 10 to create a bookmark labeled 0. If a bookmark with the specified number already exists, the method will fail and return nil (ditto if a bookmark already exists on the same line.) The default, -1, asks the plugin to create a bookmark using the first available number, if any.
- Optionally, ‘Silent’ prevents all UI notifications about a bookmark being added or removed, even if the user settings request those notifications be shown. For example, when creating a bookmark, a small animation can be shown at the location of the new bookmark; if Silent is true, the animation will never be shown regardless of the user setting.
You should leave this false for all normal user interaction – for example if a button in your plugin creates a bookmark, your plugin should respect the user’s preferences and not silence the UI notification. But if, for example, you are loading list of bookmarks and want to add them all to the file, you might want to add them quietly. In that case set Silent to true.
- The method returns a new IPnAPIBookmark representing the newly created bookmark, or nil if it fails. One common reason for failure is that a bookmark already exists at the specified location, or a bookmark with the specified number already exists elsewhere in the same file.
DeleteBookmark deletes a bookmark. Pass in a valid IPnAPIBookmark.
- Optionally, ‘Silent’ suppresses user interface notifications, just as with AddBookmark. Usually, leave this as the default, false.
OnBookmarkNotification is a multicast event list and allows you to register an interface callback for bookmark events. Call OnBookmarkNotification.Add to add your object to the list of objects to be notified. You must call Remove for every object you add. See IPnAPIBookmarkEvents (below) for the interface you must implement.
IPnAPIBookmarkList2 = interface(IPnAPIBookmarkList)
procedure RenameBookmark(const Bookmark : IPnAPIBookmark2; NewName : string);
Bookmarks version 1.2 introduced the ability to name bookmarks. Use this method to assign a bookmark a name. You can read the existing name via the IPnAPIBookmark2 interface.
Use Supports() to test if the installed version of Bookmarks supports this interface.
IPnAPIBookmarkList3 = interface(IPnAPIBookmarkList2)
function AddBookmark3(const Filename : string; const Line, Character : Integer;
const SpecificNumberKey : Integer = -1; const Silent : Boolean = false) : IPnAPIBookmark
Bookmarks version 1.3 introduced bookmarks that remembered the column or character position at which they were created, and allowed bookmarks to have an ID outside the 1-10 range. Previously, bookmarks that could not be addressed by shortcuts to the number keys 1-9 and 0 were assigned an internal number key of -1. This still happens, but now bookmarks remember the number you pass in using this method, allowing you to create and find bookmarks with a specific number. Users see no difference: this feature is purely for those who use the API.
Use Supports() to test if the installed version of Bookmarks supports this interface.
This interface is implemented by you, and allows you to receive notifications when something happens in the bookmark list. Implement it, and register it by calling IPnAPIBookmarkList.OnBookmarkNotification.Add. Make sure you call Remove as well.
IPnAPIBookmarkEvents = interface
procedure BookmarkAdded(const Bookmark : IPnAPIBookmark);
procedure BookmarkRemoved(const Bookmark : IPnAPIBookmark);
procedure BookmarkBeforeChange(const Bookmark : IPnAPIBookmark; const Change
procedure BookmarkAfterChange(const Bookmark : IPnAPIBookmark; const Change
- BookmarkAdded is called each time a bookmark is added to the internal list. This can be because the user created a bookmark; a user of this API called IPnAPIBookmarkList.AddBookmark; or a file was opened in the IDE which contains bookmarks, and so the Bookmarks plugin loaded them and added them to the internal list.
- BookmarkRemoved is called each time a bookmark is removed from the internal list. This can be because the user deleted it; a user of this API called IPnAPIBookmarkList.AddBookmark; a file was closed in the IDE and its bookmarks saved and then purged from the internal list; or the IDE is closing down and is closing all files.
- BookmarkBeforeChange is called for a specific bookmark, before the change specified in the Change parameter is applied. See below for TPnAPIBookmarkChange and details about changes to bookmarks.
- BookmarkAfterChange is called for a specific bookmark, after the change is applied.
TPnAPIBookmarkChange = (pnbcFilename, pnbcLineNumber);
The change methods are called every time a bookmark changes. A change can be one of two things: the name of the file in which the bookmark is located changes (for example, the user saved it via Save As), or the line number changes. This last is by far the most common: every time the user inserts a new line, deletes a line, etc, the line numbers of all bookmarks affected by the change are updated.
Bookmarks are static objects. Their number key, for example, cannot be changed – to do that, delete and re-add a bookmark. Changes reflect only the bookmark’s environment: they say that the file in which the bookmark is located has changed, or the line to which it is attached has moved.
The pair of before/after events allows you to prepare for a change, such as by invalidating where a bookmark was in the BookmarkBeforeChange method, before painting it in its new location in BookmarkAfterChange.
This interface represents a normal bookmark.
IPnAPIBookmark = interface
function GetFilename : string;
function GetLine : Integer;
function GetNumberKey : Integer;
property Filename : string read GetFilename;
property Line : Integer read GetLine;
property NumberKey : Integer read GetNumberKey;
- GoToBookmark navigates to the bookmark. If necessary, the file will be switched to, and if it is a form and code unit, it will switch to the code view. The view will be scrolled until the line is visible.
- Filename represents the fully qualified path and filename of the file in which the bookmark is located.
- Line represents the line number where the bookmark is located.
- NumberKey represents which keyboard number key should be displayed for the bookmark, and therefore what keyboard shortcut will navigate to the bookmark (Ctrl+n.) This can be 1-9 or 10 (10 represents keyboard number 0), or -1 indicating it no number is assigned.
A bookmark is a static object. Once created, it cannot be modified: its number key, the file to which it is attached, etc, is set. To change, delete the bookmark and create a new one. You can receive notifications when something about a bookmark’s environment changes (the name of the file where it’s located, or the line number of the line to which it is attached), by registering an interface to the IPnAPIBookmarkList.OnBookmarkNotification multicast event.
There are several newer interfaces added in new versions of Bookmarks which extend IPnAPIBookmark. You can use Supports() to test if the bookmark reference you have supports these interfaces.
IPnAPIBookmark2 = interface(IPnAPIBookmark)
function GetUserAssignedName : string;
property UserAssignedName : string read GetUserAssignedName;
Use Supports() with an IPnAPIBookmark to test if the bookmark supports this interface.
IPnAPIBookmark3 = interface(IPnAPIBookmark2)
function GetInternalNumber : Integer;
function GetCharacter : Integer;
property InternalNumber : Integer read GetInternalNumber;
property Character : Integer read GetCharacter;
- The ability of bookmarks to store the character position or column where they were created. Bookmarks now store this, regardless of the option in the Settings dialog, which simply controls the navigation behaviour (does it navigate to the line, or line and character.) Not all bookmarks have a column, such as ones created with older versions of Bookmarks, or ones created by clicking in the editor gutter. You can now read a bookmark’s character index (or column) via the Character property. It is 1-indexed, so <= 0 is invalid. To create a bookmark with a specific character / column, see IPnAPIBookmarkList3.
- The ability to create bookmarks with a number outside the range 1-10 (where 10 is shown to the user as 0.) Users see no difference here, but if using the API a bookmark is created with a number outside this range, it will now remember it. Use the new InternalNumber property to read this. The old NumberKey property works unchanged, returning only 1-10 or invalid (for bookmarks displayed as #.)
Use Supports() with an IPnAPIBookmark to test if the bookmark supports this interface.
Don’t use this. It contains internal methods used by the PnPluginAPIBookmarksLoader unit.
Some methods have a counterpart that must always be called if you call the first. For example, if you call PnAPILoadBookmarksPlugin, you must call PnAPIUnloadBookmarksPlugin at some later time. Another example is that if you add an event sink to a multicast event list, you must remove it.
Failure to do so can result in unexpected behaviour, from access violations to problems closing the IDE.
Each of these paired methods are noted in the documentation.
Loading and unloading DLLs
An important note about accessing code located in one DLL from another DLL or BPL: The IDE can load its plugin in any order. Your plugin might be loaded before the Bookmarks plugin is. That means that if you call the PnAPILoadBookmarksPlugin method (detailed above) to locate the Bookmarks plugin, and it is not yet loaded, the method will return failure (since it is unable to locate the plugin; it will never load the DLL into the IDE process, only locate it if the IDE has loaded it.) You should attempt to locate the Bookmarks plugin as late as possible – for example, the first time your plugin needs to display information in the UI, because by that time all plugins are guaranteed to have been loaded by the IDE.
Similarly, you should release your reference to the Bookmarks plugin interface and call the unload method as early as possible. The PnAPILoadBookmarksPlugin method adds a reference to the Bookmarks plugin DLL, meaning it will stay loaded inside the IDE even after the IDE itself has indicated it can be unloaded, because your plugin, by using that method, has added a refcount to the Bookmarks DLL. This means if your plugin leaves it a long time before calling PnAPIUnloadBookmarksPlugin, it might have held the last refcount on the DLL, prolonging its lifetime longer than the IDE expected, and, importantly, meaning that your unload call may be the one that actually triggers the DLL to unload from the IDE process.
This is not a problem, but is something you should be aware of. Specifically, you should be careful to release all references to Bookmarks objects (and interfaces) before you unload the DLL, since after that they will reference code and memory that may no longer be valid.
This is a specific case of the ‘always match paired methods’ rule above.
Demo, Support, etc
For support, questions, or help, please use the Contact form.
You can download the Bookmarks API’s required units, including an example plugin that communicates with the Bookmarks plugin, on the main API page.