property snippetsPlistPath : "~/Documents/snippets.plist"
--

(*
Copyright (c) 2006, Grayson Hansard

All rights reserved.

This software is provided “as is” without any expressed or implied warranties. Neither the original author nor any contributors are liable for any damage caused by the use of this program.

You are free to use, distribute, and create derivative works for commercial and noncommercial use.

Derivative works of this software must be made publicly available.
*)

property EndOfWordSet : {" ", "	", return, "
", ";", ","}

-- Entry point to application.  Basically gets the frontmost app and calls another function.
on run
	set n to name of getFrontmostApp()
	if n is "Xcode" then xcode()
	if n is "TextWrangler" then textwrangler()
	textwrangler()
end run

-- Simple function to get the frontmost application.
on getFrontmostApp()
	tell application "System Events"
		repeat with p in processes
			if p is frontmost then return p
		end repeat
	end tell
end getFrontmostApp

-- Convenience method to grab an item from the snippets property list
on fetchSnippet(k)
	if class of k is not equal to list then set k to {k}
	tell application "System Events"
		try
			set plistFile to property list file snippetsPlistPath
			set plItem to contents of plistFile
			repeat with t in k
				set plItem to property list item named t of plItem
			end repeat
			return value of plItem
		on error e
			log e
		end try
	end tell
	return ""
end fetchSnippet

-- Convenience function to get the path extension of a file path
on pathExtension(p)
	set buffer to ""
	repeat with c in (reverse of characters of p)
		set buffer to c & buffer
		if c contains "." then return buffer
	end repeat
	return ""
end pathExtension

-- Convenience method to try for possible variations in storage in plist file.
-- For most editors, this first tries to locate the item under appname, then filetype, then key.  If that fails, it tries appname and key.  If that fails, it tries for filetype and key.  Finally, if all else fails, it looks simply for the key.  This allows the same keys to be used in different contexts.
on tryForSnippets(l)
	set a to item 1 of l
	set b to item 2 of l
	set c to item 3 of l
	set ret to my fetchSnippet({a, b, c})
	if ret is "" then set ret to fetchSnippet({a, c})
	if ret is "" then set ret to fetchSnippet({b, c})
	if ret is "" then set ret to fetchSnippet(c)
	return ret
end tryForSnippets

-- Convenience method to trim whitespace characters from the beginning and ends of words.
on trim(s)
	if length of s is 1 and EndOfWordSet contains s then return ""
	set i to 1
	repeat with c in s
		if EndOfWordSet contains c then set s to (characters (i + 1) thru (length of s)) of s as string
		set i to i + 1
	end repeat
	set i to length of s
	repeat with c in reverse of characters of s
		if EndOfWordSet contains c then set s to characters 1 thru (i - 1) of s as string
		set i to i - 1
	end repeat
	return s
end trim

-- Implementation for Xcode.  Basic code format is followed for the TextWrangler implementation so only this is commented.
on xcode()
	tell application "Xcode"
		-- Initialize the basic variables necessary to get the insertion point in a document
		set x to front text document
		set s to selection of x
		set a to insertion points of x
		
		-- I couldn't figure out a way to get meaningful information from an insertion point in Xcode so this steps through all the insertion points until it matches the current one.  This provides the character index of the current insertion point.
		set i to 1
		repeat with t in a
			if item i of a is equal to s then exit repeat
			set i to i + 1
		end repeat
		set i to i - 1
		if i < 1 then return
		set j to i
		
		-- Step backwards until the beginning of the key is found, creating a buffer that contains the key and a variable (j) that indicates the character index of the start of the key.
		set buffer to character i of x
		set stopSet to EndOfWordSet
		set c to character j of x
		repeat until stopSet contains c
			set j to j - 1
			set c to character j of x
			set buffer to (c as string) & buffer
			if j ≤ 1 then exit repeat
		end repeat
		set buffer to my trim(buffer) -- Trim whitespace out of the key.
		if buffer is equal to "" then return -- Punt if only whitespace was found.
		
		-- Search the snippets property list for any possible values.  See comments on tryForSnippets() for more information.
		set repl to tryForSnippets({"Xcode", pathExtension(path of x), buffer})
		if repl is "" then return -- Punt if not value was found.
		
		-- Get all the characters before and after the key and then replace the key with the value found in the property list.
		set txt to text of x as string
		set temp1 to (characters 1 thru j of txt) as string
		set temp2 to (characters (i + 1) thru (length of txt) of txt) as string
		set text of x to temp1 & repl & temp2
		
		-- Reset the insertion point (note this is a bit of a kludge and the insertion point may be a bit off).  It's just a bit annoying that Xcode will set the insertion point to the end of the document.
		set selection of x to (insertion point (i + (length of repl) - 2) of x)
	end tell
end xcode

-- Implementation for TextWrangler.
on textwrangler()
	tell application "TextWrangler"
		set doc to front document
		set i to (characterOffset of selection) - 1
		if i < 1 then return
		set buffer to character i of doc as string
		set j to i
		set c to character j of doc as string
		repeat until EndOfWordSet contains c
			set j to j - 1
			set c to character j of doc as string
			set buffer to c & buffer
			if j ≤ 1 then exit repeat
		end repeat
		set buffer to my trim(buffer)
		if buffer is equal to "" then return
		
		try
			set ext to my pathExtension(URL of doc)
		on error
			set ext to null
		end try
		set repl to my tryForSnippets({"TextWrangler", ext, buffer})
		if repl is "" then return
		
		if j is equal to 1 then set j to 0
		set characters (j + 1) thru i of doc to repl
	end tell
end textwrangler