Saturday, February 20, 2010

Finding Definitions with Ctags in SubEthaEdit

As with using Ctags for text completion, finding definitions for symbols can be expressed largely in terms of the shell scripts and AppleScript handlers already presented. Another handler, openTaggedSources, is needed, which will open files to the location of the selected tag or tags.

The resulting AppleScript is again quite concise:
on seescriptsettings()
{displayName:"Find Definition using Ctags", shortDisplayName:"Ctags Definition", keyboardShortcut:"@^f", inContextMenu:"yes"}
end seescriptsettings

try
requireValidDocumentForCtags()
set tagfilepath to findTagFile()
set searchTerm to determineSearchTerm with userIntervention
set taglist to (pipeMatches of searchTerm out of tagfilepath thru "")
set tagsToOpen to (pickTags from taglist with multipleSelectionsAllowed)
openTaggedSources for tagsToOpen from tagfilepath
on error errMsg number errNum
if errNum is equal to 901 then
return
else if errNum is equal to 902 then
beep
return
else
error errMsg number errNum
end if
end try
The structure directly parallels that used for the text completion script.

Let's take a look inside the openTaggedSources handler. My approach is to dump all the selected tags back to the shell, where the shell script open-tag-files will finish the job. Here's the handler:
to openTaggedSources for tags from tagfile
--pass tags to external script that opens them in SEE
set exportTagsFile to "export TAGDIR=\"$(dirname " & (quoted form of tagfile) & ")\";"
set openTagFilesPipeline to join of {"printf " & quoted form of tags, "open-tag-files RelTo=\"$TAGDIR\""} by "|"
set openTagFilesScript to join of {UnixPath, exportTagsFile, openTagFilesPipeline, "&> /dev/null &"} by space
do shell script openTagFilesScript
end openTaggedSources
I pass the location of the tag file to the script, so that either absolute or relative paths can be used in the tag files. Otherwise, it's just passing the selected tags out as stdin to open-tag-files in a straightforward way.

So let's look at open-tag-files:
#! /usr/bin/awk -f

BEGIN {
FS="\t"
}

{
# Treat relative filenames as relative to RelTo
if ($2 ~ /^\//) {
filePath = $2
} else {
filePath = RelTo "/" $2
}
# Handle both numeric and regex patterns
if ($3 ~ /^[[:digit:]]+(;\")?$/) {
match($3, /^[[:digit:]]+/)
gotoLine = "-g " substr($3, RSTART, RLENGTH)
} else {
patternPlusExtras = substr($0, index($0, $3))
numTokens = split(patternPlusExtras, token, "/")
if (length(token[1])) {
# Pattern looks invalid, so can't specify the line
gotoLine = ""
} else {
exQuery = ""
for (n=2; n<=numTokens; n++) {
exQuery = exQuery "/" token[n]
if (token[n] !~ /[^\\](\\\\)*\\$/) {
break
}
}
exQuery = exQuery "/"
command = "cat '"filePath"' | sed -e '"exQuery" q' | wc -l"
command | getline lineCount
close(command)
gotoLine = "-g "lineCount
}
}
#printf("see %s \"%s\" &\n", gotoLine, filePath)
system("altsee "gotoLine" \""filePath"\" &")
}
This is an awk script which mostly consists of handling different ways that the tag file can be structured. Since the point is to provide a platform for experimenting with Ctags, it seems premature to commit to specific choices of absolute or relative paths, numeric line references or ex patterns, extended fields from Exuberant Ctags or just vanilla Ctags lines. For what it is worth, I'm invoking Exuberant Ctags as ctags -n --fields=+a+m+n+S -R (but there may well be better choices).

At the end open-tag-files, I use altsee to open the source files. This is a replacement for the see command line tool that comes with SubEthaEdit. I find that see is a bit of a hassle for this sort of use, so gave up on it for here (if you can get open-tag-files to work cleanly with multiple selected files, I'd love to hear about how!).

All the scripts and handlers need to be assembled into a compiled AppleScript in ~/Library/Application Support/SubEthaEdit/Scripts/ with the shell scripts set to be executable and on the path defined in the AppleScripts. If you're not sure where to put the shell scripts, I'd suggest creating a ~/Library/Application Support/SubEthaEdit/bin/ directory for SubEthaEdit-related shell scripts, and putting the scripts there. A compiled script with the needed shell script support is available for download.

No comments: