When I develop my Perl programs, I often run the programs or their tests in the Terminal, either locally or on a remote server. At the same time, I have the Perl class .pm files open in BBEdit, usually lots of them, again either from a local sandbox or from the remote server using SFTP (via Interarchy or ExpanDrive).
Very often during the edit/run/debug cycle I want to jump to the specific location (file and line number) of an error as displayed in the Terminal in BBEdit.
I define an error location as a line in the Terminal history with this pattern:
<some message text> at <some file path ending in .pm> line <a number>
For example:
It’s tedious to switch to BBEdit and hunt down the right document and line manually, so I wrote two AppleScripts to do it for me. Both scripts only consider documents which are already open in BBEdit so they won’t go and open documents for you. That would be hard to do correctly in all cases for remote files.
This first script will only consider the very first error line in the history of the frontmost Terminal window. This means you do not have to select the message, I only did that for illustration purposes in the screenshot above.
Here’s the script:
tell application "Terminal"
set myhistory to history of window 1
end tell
set location to do shell script "echo " & quoted form of myhistory & " | perl -n -e 'm#(\\w+/\\w+\\.(?:php|pm|pl))(?::(\\d+)| (?:on )?line (\\d+))# && print qq#$1 # . ($2 || $3) . qq#\\n#' | head -1"
set locationInfo to |splittext|(" ", location)
if (count of locationInfo) < 2 then
doGrowl()
return
end if
set {locationFile, locationLine} to locationInfo
tell application "BBEdit"
activate
set hits to (text documents whose on disk is true and URL contains locationFile)
--set hits to (documents whose URL contains locationFile)
if (count of hits) > 0 then
set mydoc to first item of hits
activate
select mydoc
select line (locationLine as number) of mydoc
else
display dialog "No file called “" & locationFile & "” is open in BBEdit"
end if
end tell
on |splittext|(delimiter, someText)
set prevTIDs to AppleScript's text item delimiters
set AppleScript's text item delimiters to delimiter
set output to text items of someText
set AppleScript's text item delimiters to prevTIDs
return output
end |splittext|
on doGrowl()
tell application "GrowlHelperApp"
-- Make a list of all the notification types
-- that this script will ever send:
set the allNotificationsList to ¬
{"No Error Location Found"}
-- Make a list of the notifications
-- that will be enabled by default.
-- Those not enabled by default can be enabled later
-- in the 'Applications' tab of the growl prefpane.
set the enabledNotificationsList to ¬
{"No Error Location Found"}
-- Register our script with growl.
-- You can optionally (as here) set a default icon
-- for this script's notifications.
register as application ¬
"Show Error Location in BBEdit Script" all notifications allNotificationsList ¬
default notifications enabledNotificationsList ¬
icon of application "Terminal"
-- Send a Notification...
notify with name ¬
"No Error Location Found" title ¬
"No Error Location Found" description ¬
"No Error Location was found in the current Terminal window" application name "Show Error Location in BBEdit Script"
end tell
end doGrowl
(I took splitText() from here).
Running it jumps directly to this location in BBEdit:
The second script finds all error locations in the Terminal history and puts them in a Result Browser window in BBEdit. Result browsers look like this:
Here’s the script to do that:
tell application "Terminal"
set myhistory to history of window 1
end tell
set locationText to do shell script "echo " & quoted form of myhistory & " | perl -e '$files{qq#$_->[1]:$_->[2]#} ||= $_ foreach grep {@$_ > 1} map {[$_->[0], $_->[1], ($_->[2] || $_->[3])]} map {[m#(.+) (?:at|in) .+/(\\w+/\\w+\\.(?:php|pm|pl))(?::(\\d+)| (?:on )?line (\\d+))#, $i++]} <>; print map {qq#$_->[0] --- $_->[1] --- $_->[2]\\n#} sort {$a->[3] <=> $b->[3]} values %files'"
set locationData to {}
repeat with location in |splittext|("
", locationText)
copy |splittext|(" --- ", location) to end of locationData
end repeat
tell application "BBEdit"
set resultItems to {}
repeat with location in locationData
set {locationMessage, locationFile, locationLine} to location
set hits to (text documents whose on disk is true and URL contains locationFile)
if (count of hits) > 0 then
set mydoc to first item of hits
set locationLine to locationLine as number
set myLine to line locationLine of mydoc
set s_offset to characterOffset of myLine
if (count of characters of myLine) > 0 then
set e_offset to characterOffset of last character of myLine
else
set e_offset to s_offset
end if
set resultEntry to {start_offset:(s_offset - 1), end_offset:e_offset, message:locationMessage, result_kind:error_kind, result_file:file of mydoc, result_line:locationLine}
copy resultEntry to end of resultItems
end if
end repeat
make new results browser with data resultItems with properties {name:"Errors in Terminal"}
end tell
on |splittext|(delimiter, someText)
set prevTIDs to AppleScript's text item delimiters
set AppleScript's text item delimiters to delimiter
set output to text items of someText
set AppleScript's text item delimiters to prevTIDs
return output
end |splittext|
(I found example code for populating result browsers at Daring Fireball)
I put these scripts into $HOME/Library/Scripts/Applications/Terminal. They are more useful if they have keyboard shortcuts; you can use FastScripts ($14.95) or Proxi (free) to assign those.
Since both scripts scan the Terminal history from the top, it makes sense to clear the buffer before running a program or test, so I hit the Cmd-K many times during the day. You can put this at the start of your test driver script to do it automatically:
which -s osascript && osascript -e 'tell application "System Events" to keystroke "k" using command down'
The which -s osascript part makes sure you can leave it in there without bothering your Linux-using friends :-)
Well, this is a hint for BBEdit :-)
Part of the fun is local editing but remote execution.
|



You could jump to the first error line with nano as well:
nano +177 /path/to/Context.pm