Better BBEdit Completion With ctags
Mac OS X — 2 Apr 2009 12:38 — 1044 days ago

ctags is an old-school UNIX program that indexes language objects such as functions, methods, classes etc. in source code files. Text editors read the index (called a “tags” file) to locate these language objects quickly.

When BBEdit finds a tags file, it enables these features:

  • opening the contextual menu for a selected language object shows a submenu with all definitions of that object
  • the completion list is populated with language objects that match what you typed so far
  • your custom language objects are colorized in the source code
  • the “Find Definition” menu command jumps directly to the definition of the selected word, if there’s only one match, or shows a list with possible matches if there are several

Let’s look at these four features with and without a tags file.

First the contextual menu for a selection. Here I selected the rootdir method call and opened the contextual menu, without a tags file present:

BBEdit ctags feature contextual menu without tags file

Here’s the same menu with a tags file:

bbedit-ctags-contextual-with-tags.png

The rootdir method is defined in four files and selecting an entry from the menu brings you directly to that definition.

On to the completion list. I type the word “root” and hit the completion shortcut without a tags file present:

bbedit-ctags-completion-no-tags.png

BBEdit offers no completions because what I’m looking for, the rootdir method, does not occur in the current file.

With a tags file, it looks like this:

bbedit-ctags-completion-with-tags.png.png

Here’s how your own symbols look without a tags file:

bbedit-ctags-keywords-without-tags.png

There’s no difference between the language objects and any other text, they’re all black. With a tags file, the language objects are highlighted with a separate color:

bbedit-ctags-keywords-with-tags.png

Finally, the “Find Definition” command in the Search menu. You select a word and invoke this command to show the definition of the selected language object. Without a tags file, nothing happens. With a tags file present, BBEdit either opens the definition directly if there’s only one candidate or it presents a list with all matches if there are several:

bbedit-ctags-find-definition.png

Creating the Tags File

To create the tags file, open the Terminal and cd to the toplevel directory of your project, the one that contains all related source code. Issue this command:
/Applications/BBEdit.app/Contents/MacOS/ctags --excmd=number --tag-relative=no --fields=+a+m+n+S -f tags -R "$PWD"

I made myself an alias called “bbtags” with some Perl-specific excludes and added it to my $HOME/.bashrc file:

alias bbtags='/Applications/BBEdit.app/Contents/MacOS/ctags --excmd=number --tag-relative=no --fields=+a+m+n+S -f tags --exclude=blib --exclude=pod -R "$PWD"

Now I can just type bbtags and I’m all set.

As you edit and save your files, the index gets out of sync. You need to rebuild the tags file from time to time so it matches the changed files. If you work in a programming language that uses a build/compilation phase such as C or Java, you can run the ctags update command during the build process, as Seth notes in the comments below.

If you work in a dynamic programming language like Perl or PHP, you can try the following BBEdit menu AppleScript:

on MenuSelect(mycommand, myname)
	return false
end MenuSelect

on PostMenuSelect()
	tell application "BBEdit"
		if not my isKnownLanguage(source language of text document 1) then return
		set docFile to file of text document 1
	end tell
	set docPath to POSIX path of docFile
	repeat while docPath is not equal to "/"
		set docPath to (do shell script "dirname " & (quoted form of docPath))
		if checkAndRebuildTagsFile(docPath) then return
	end repeat
end PostMenuSelect

on isKnownLanguage(sourceLanguage)
	return {"PHP", "Perl"} contains sourceLanguage
end isKnownLanguage

on checkAndRebuildTagsFile(tagsParentPath)
	set tagsPath to tagsParentPath & "/tags"
	set tagsFile to POSIX file tagsPath
	tell application "Finder"
		if not (exists tagsFile) then return false
	end tell
	if folder of (info for tagsFile) then return false
	set updateCommand to "/Applications/BBEdit.app/Contents/MacOS/ctags 2>&1 --excmd=number --tag-relative=no --fields=+a+m+n+S+l+K+i -f tags --exclude=blib -R " & quoted form of tagsParentPath
	set ctagsResult to do shell script updateCommand
	do shell script "syslog -l notice -s rebuilt tags file with command " & updateCommand & ", result: " & quoted form of ctagsResult
	return true
end checkAndRebuildTagsFile

Save it in your $HOME/Library/Application Support/BBEdit/Menu Scripts folder with the name “File•Save” to run it whenever you save the document. It looks for a tags file in all parent directories of the file you just saved, and if it finds one it runs the ctags update command. It seems to be a bit slow on the big source code trees I work on, but just give it a try, it might work better for you.

If you want to exclude certain directories permanently in all projects, I suggest you create a $HOME/.ctags file with exclude lines in it:

--exclude=blib
--exclude=unittest

See the ctags documentation for the details.

In conclusion, if you use BBEdit in multifile projects, you should try the ctags feature. I suspect that it is seriously under-used and unknown even among experienced BBEdit users.


Comments
Posted by Seth Dillingham on 3 Apr 2009 04:00

If you're using some sort of a build, auto-test, or deployment system with your code, you can usually run ctags from there so that your tags file is always up to date.

Posted by Paul Burney on 6 Apr 2009 16:04

This is a fantastic tip, Marc. I've been using BBEdit since version 4 and I hadn't known about the ctags feature. I'm not sure what version of BBEdit first started using it, but it definitely works in BBEdit 8.7 which I'm currently on (the ctags executable is in the "Resources" directory in this version instead of a MacOS directory).

The AppleScript works but is a bit too slow with the codebases I use because it locks up BBEdit while it's running. I also had to add HTML as a known type because some of my PHP files use that format because it enhances the syntax highlighting.

Thanks again for the great tip!

-Paul Burney

Powered By blojsom