Audience Note

This document is written for consumption by anyone who has written a BBEdit language module, either codeless or compiled. It documents the changes in the language module API as well as information that is essential for developing language modules that make the most of the improvements in BBEdit 11.0 and later.

This document supplements the information provided in the Codeless Language Module Reference as well as the information in the "Writing Language Modules" document included as part of the BBEdit SDK.

Useful Debugging Tip

Run Kinds and Spell Checking

Run Kinds and Completion

Keywords, Run Kind Patterns, and More

Function Menu Badging

Generating and Storing Internal Data

Beginning with BBEdit 13.0, compiled language modules now have the ability to generate and use their own document-specific data. (Unless you're writing a compiled language module, you can skip this note.)

This can be for any suitable purpose; for example, if a hypothetical C-family language module wanted to generate an abstract syntax tree for the document using clang, it could do so.

BBEdit does not inspect or use any data created by the language module, nor does it inspect it nor make any assumptions about what's in it. The only rule is that it will be treated as an NSObject and passed through the API boundary as such, but the language module can instantiate it as any NSObject subclass (including one defined by the module itself) and assume that it will be of that type.

The main BBLMParamBlock structure gains the following top-level fields:

There are four new messages relating to the management and lifetime of parse data:

Important Notes About Object Lifetimes

Under no circumstances should you attempt to assume ownership of the NSObject subclass that you return in fDocumentParseData, even if you are changing its value and setting fOutDocumentParseDataIsNew. If you return a new parse data object, BBEdit will release the old one for you.

Considerations for non-refcounted data

In some cases, your parse data might be a C++ class instance, or even an allocated C structure. In order to pass it back and forth across the API boundary, you must wrap it in an NSValue as a pointer value. In that case, you must also take some care to manage the object lifetime yourself, since BBEdit can't otherwise know what needs to be done with it. Thus, given some hypothetical ParseTree C++ class, you would write something like:

myParseTree = new ParseTree;
/* ...do some parsing... */
params.fDocumentParseData = [NSValue valueWithPointer: myParseTree];
params.fDocumentParseDataIsNew = true;

You would use this pattern in response to kBBLMInitParseDataMessage, but also if you calculated a new parse tree in response to kBBLMRecalculateParseDataMessage or kBBLMUpdateParseDataMessage.

One additional wrinkle, though: when recalculating or updating, if you make a new C++ object, you need to dispose of the old one, but not release the NSValue instance itself. This is because BBEdit doesn't know what's wrapped up in the NSValue, or how it should be managed.

So in the case where you're changing the object during update or recalculate, you'd have code like this:

ParseTree   *oldParseTree = NULL;
ParseTree   *newParseTree = NULL;

oldParseTree = static_cast<ParseTree*>(params.fDocumentParseData.pointerValue);
delete oldParseTree;    //  clean up the old data

myParseTree = new ParseTree;
/* ...do some parsing... */
params.fDocumentParseData = [NSValue valueWithPointer: myParseTree];
params.fDocumentParseDataIsNew = true;

When receiving a kBBLMDisposeParseDataMessage, you'll have to do the same:

ParseTree   *oldParseTree = NULL;

oldParseTree = static_cast<ParseTree*>(params.fDocumentParseData.pointerValue);
delete oldParseTree;    //  clean up the old data

Note that you do not ever release params.fDocumentParseData! BBEdit will manage it for you once you've created it. (If you do release it, you'll rapidly find out what a bad idea that was.)

fin