Two AppleScripts for Perl/PHP Developers using BBEdit
Developer — 1 Apr 2008 10:21 — 848 days ago

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:

Perl error message in Terminal

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:

BBEdit text window

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:

BBEdit Results Browser

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 :-)


Comments
Posted by chad on 3 Apr 2008 19:51

You could jump to the first error line with nano as well:

nano +177 /path/to/Context.pm

Posted by Marc on 3 Apr 2008 20:54

Well, this is a hint for BBEdit :-)

Part of the fun is local editing but remote execution.

Powered By blojsom