Advanced ctags hacking

The BBEdit user manual (chapter 14) describes how to use BBEdit with ctags, but leaves some things out:

  • The “bbedit --maketags” command does much of what the example script describes (although the example is still useful as an Xcode build phase);
  • The example script has an error: it points to BBEdit.app/Contents/MacOS/ctags, but BBEdit no longer includes a ctags executable. Instead, go to https://ctags.io/ to get Universal Ctags, or use brew install universal-ctags. Then, your script should point to wherever ctags ends up (probably /usr/local/bin/ctags) or rely on it being in your $PATH.

The completion support in the PHP language module uses some nonstandard (but compatible) extensions to the tags file format. Here are some examples:

__soapCall *.php 0;" kind:f class:SoapClient access:public signature:(string function_name, array arguments, [array options], [mixed input_headers], [array output_headers])

The above is a tag line for a function __soapCall(). The only two fields that are really different from convention are the file path/name and the line number.

In the above example, “*.php” is placed in as the file name so that there is a placeholder for the file name field, and also so that BBEdit’s internal mapping of the name establishes the correct language. If you’re generating completion data for Python, you’d want to use “*.py” instead (for example).

The next is the line number; ordinarily this would point to a line in the file indicated by the previous field, but there isn’t one. ðŸ™‚ Use a “0” in place of an actual line number, and BBEdit knows that it’s special and will alter its internal behaviors accordingly.

The use of a wildcarded suffix and a zero line number is specifically for supporting code libraries for which it’s easier to prefabricate the tags file than it is to download, install, and manually scan the source with ctags.

The “signature:” field is standard for Exuberant Ctags, but BBEdit does some special things with it. First, it parses out the comma-separated arguments and wraps placeholders around them, which you can navigate with the Tab key (or with the Previous/Next Placeholder commands.) Second, there’s an additional (optional) field named “optional_args“. Here’s another example from the PHP tags:

__construct *.php 0;" kind:f class:AMQPConnection signature:([array credentials]) optional_args:(*)
add *.php 0;" kind:f class:Memcache signature:(string key, mixed var, [int flag], [int expire]) optional_args:(3,4)
add *.php 0;" kind:f class:Memcached access:public signature:(string key, mixed value, [int expiration]) optional_args:(3)
add *.php 0;" kind:f class:SolrModifiableParams access:public signature:(string name, string value) optional_args:(-)

Putting the argument names in [square brackets] is just a PHP convention and has no behavior effect, but the optional arguments can be specified. Use a “*” to indicate that all of them are optional, a “-” to indicate that none are optional, and otherwise use the positional placement of each optional argument (a single number, as in the “3” above, or an ordered list: “3, 4, 5”, etc).

Ctags does not generate the optional_args field, but ctags is not required to generate tags files in the correct format; so any automated tool will do the job, as long as the output is correctly recognizable as a tags file. To accomplish this, your tags file must begin with a valid header. Here’s an example:

!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED 0 /0=unsorted, 1=sorted, 2=foldcase/

You should always use “2” for !_TAG_FILE_FORMAT, and an appropriate value for !_TAG_FILE_SORTED.

You can add some additional fields to the header as well, if you wish. Specifically, information to identify the program that generated the tags and provide contact information for the author:

!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/
!_TAG_PROGRAM_NAME Exuberant Ctags //
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
!_TAG_PROGRAM_VERSION 5.7 //

(Naturally, if you write a custom tag generator, you should use your own name, email address, web site, etc.)


If you’ve generated an enhanced tags file, there are a couple of places it can go:

  • If you’re writing a compiled language module using the BBEdit SDK, put it in your bundle’s Resources folder, and name it tags. (You should only do this if your symbols use the wildcard-suffix-and-zero-line-number trick, *or* if you can guarantee that the files and lines referenced by your tags file will exist on the machine running your language module.)
  • If you’re making a BBEdit Package, it can go inside the package, in Contents/Completion Data/-name of language-/ to provide completion data for package users. Example: Contents/Completion Data/PHP/tags
  • If you’re using it only for yourself, or distributing it standalone, the completion data can go inside of ~/Library/Application Support/BBEdit/Completion Data/-name of language-/ (adjust ~/Library/ to your Dropbox folder if you’re using that feature.
  • As the BBEdit manual says, tags files can also go at the root of a source folder hierarchy. This is appropriate for tags that were generated using your own sources and which you update frequently. Tags for a standard library, application framework, or some other widely used code should go in one of the above locations instead.