Quantcast

Added options screen among other features

James Whitehead II [08-14-08 - 15:07]
Added options screen among other features
Filename
DressToKill.lua
DressToKill.toc
DressToKillOptions.lua
FAIAP.lua
fonts/COPYRIGHT.TXT
fonts/VeraMono.ttf
diff --git a/DressToKill.lua b/DressToKill.lua
index bd0173d..055c7bb 100644
--- a/DressToKill.lua
+++ b/DressToKill.lua
@@ -4,7 +4,7 @@
 DressToKill = {}
 local L = DressToKillLocals

-local DEBUG = true
+local DEBUG = false
 local function debug(fmt, ...)
 	if DEBUG then
 		local msg = "|cffffff78DressToKill:|r "
@@ -17,6 +17,16 @@ local function debug(fmt, ...)
 	end
 end

+local function print(fmt, ...)
+	local msg = "|cffffff78DressToKill:|r "
+	if select("#", ...) > 0 then
+		msg = msg .. fmt:format(...)
+	else
+		msg = msg .. fmt
+	end
+	ChatFrame1:AddMessage(msg)
+end
+
 local slotNames = {
 	BackSlot = L["Back"],
 	ChestSlot = L["Chest"],
@@ -40,7 +50,10 @@ local slotNames = {
 local slotIds = {}

 function DressToKill:Initialize()
-	debug(L["Scanning for inventory slot Ids"])
+	-- Only run this code once
+	if self.initDone then return end
+	self.initDone = true
+
 	for slot in pairs(slotNames) do
 		local id = GetInventorySlotInfo(slot)
 		if id then
@@ -51,6 +64,21 @@ function DressToKill:Initialize()
 			return
 		end
 	end
+
+	DressToKillDB = DressToKillDB or {}
+	self.profile = DressToKillDB
+	self.profile.weightFuncs = self.profile.weightFuncs or {
+		["Healing/Mana"] = {handler = [[local healing = GetSpellBonusHealing()
+local mana = UnitManaMax("player")
+return (healing * 1.0) + (mana * 0.7)]]},
+		["Health/Armor"] = {handler = [[local health = UnitHealthMax("player")
+local base, pos, neg = UnitArmor("player")
+local armor = base + pos - neg
+return (health * 1.0) + (armor * 0.8)]]},
+		["Attack Power"] = {handler = [[local base, pos, neg = UnitAttackPower("player")
+local apower = base + pos - neg
+return apower]]},
+	}
 end

 function DressToKill:FindEmptySlot()
@@ -63,8 +91,9 @@ function DressToKill:FindEmptySlot()
 	end
 end

-local function scanFunction()
+local function scanFunction(weightFunction)
 	debug(L["Beginning an inventory scan"])
+	UIErrorsFrame:AddMessage("Please do not change buffs or forums during scan...", 1, 0.2, 0.2)

 	local blacklist = {}
 	local slotAvail = {}
@@ -84,7 +113,7 @@ local function scanFunction()
 			-- We current have an item equipped, so stash that item elsewhere
 			local bag,slot = DressToKill:FindEmptySlot()
 			if not bag or not slot then
-				debug(L["Out of inventory space, cannot proceed"])
+				print(L["Out of inventory space, cannot proceed"])
 				return
 			end

@@ -100,7 +129,8 @@ local function scanFunction()
 			if not blacklist[mask] then
 				local equipped = DressToKill:EquipItem(slotId, mask, stash)
 				if equipped then
-					local score = DressToKill:EvaluateScore()
+					local link = GetInventoryItemLink(slotId)
+					local score = weightFunction(link)
 					if score >= maxScore then
 						maxScore = score
 						winner = mask
@@ -116,33 +146,12 @@ local function scanFunction()
 			end
 		end
 		-- Now equip the item that won this round and blacklist
-		debug(L["Equipping winning item"])
 		DressToKill:EquipItem(slotId, winner, stash)
+		local link = GetInventoryItemLink(slotId)
+		print("Chose %s as the winning item for this slot", link)
 		blacklist[winner] = true
 	end
-	debug(L["All done trying clothes on!"])
-end
-
-function DressToKill:EvaluateScore()
-	--local base,pos,neg = UnitAttackPower("player")
-	--return base + pos - neg
-
-	--[[
-	local health = UnitHealthMax("player")
-	local base, pos, neg = UnitArmor("player")
-	local armor = base + pos - neg
-	local score = (health * 0.8) + (armor * 1.0)
-	return score
-
-	-- 1.0/0.8 - 7430 health, 7910 armor
-	-- 1.0/1.0 - 7430 health, 7910 armor
-	-- 0.8/1.0 - 7390 health, 7948 armor
-	--]]
-
-	local mana = UnitManaMax("player")
-	local healing = GetSpellBonusHealing()
-	local score = (mana * 1.0) + (healing * 1.0)
-	return score
+	print("Evaluation complete!")
 end

 function DressToKill:EquipItem(slotId, mask, stash)
@@ -212,10 +221,49 @@ SLASH_DRESSTOKILL2 = "/dress"
 SLASH_DRESSTOKILL3 = "/dresstokill"

 SlashCmdList["DRESSTOKILL"] = function(msg, editbox)
-	debug(L["Dressing to kill..."])
 	DressToKill:Initialize()
+	if msg and msg:lower():match("%s*config%s*") then
+		InterfaceOptionsFrame_OpenToFrame(DressToKillOptionsFrame)
+		return
+	end
+
+	-- Check to see if a function was specified on the commandline
+	local weightName = msg:match("^%s*(.+)$")
+	if not weightName then
+		if DressToKill.profile.selected then
+			weightName = DressToKill.profile.selected
+		else
+			print(L["You don't have a default weight function selected"])
+			return
+		end
+	end
+
+	-- Make sure the function actually exist
+	if not DressToKill.profile.weightFuncs[weightName] then
+		print(L["The weight function '%s' doesn't exist"], weightName)
+		return
+	end
+
+	-- Compile the function
+	local source = DressToKill.profile.weightFuncs[weightName].handler
+	local weightFunction,err = loadstring("return function (link) " .. source .. " end")
+
+	if not weightFunction then
+		print("Failed to compile weight function: " .. tostring(err))
+		return
+	else
+		local succ,err = pcall(weightFunction)
+		if not succ then
+			print("Failed when running weight function: " .. tostring(err))
+			return
+		else
+			weightFunction = err
+		end
+	end
+
+	print(L["Dressing to kill with %s..."], weightName)
 	DressToKill.currentThread = coroutine.create(scanFunction)
-	coroutine.resume(DressToKill.currentThread)
+	coroutine.resume(DressToKill.currentThread, weightFunction)
 end

 local ITEM_INVENTORY_PLAYER = 0x00100000
diff --git a/DressToKill.toc b/DressToKill.toc
index f1f1d5d..269d54e 100644
--- a/DressToKill.toc
+++ b/DressToKill.toc
@@ -3,8 +3,10 @@
 ## Version: wowi:revision
 ## Notes: When you go out, make sure you're prepared to kill..
 ## Author: Cladhaire
-## SavedVariables: DressToKillDB
+## SavedVariablesPerCharacter: DressToKillDB

 Localization.enUS.lua

 DressToKill.lua
+FAIAP.lua
+DressToKillOptions.lua
diff --git a/DressToKillOptions.lua b/DressToKillOptions.lua
new file mode 100644
index 0000000..4dfee9f
--- /dev/null
+++ b/DressToKillOptions.lua
@@ -0,0 +1,260 @@
+local L = DressToKillLocals
+
+local frame = CreateFrame("Frame", "DressToKillOptionsFrame", UIParent)
+frame.name = L["Dress to Kill"]
+frame:Hide()
+frame:SetScript("OnShow", function(frame)
+	local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
+	title:SetPoint("TOPLEFT", frame, "TOPLEFT", 15, -15)
+	title:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", 10, -45)
+	title:SetJustifyH("LEFT")
+	title:SetJustifyV("TOP")
+	title:SetText(L["Dress to Kill"])
+
+	--[[
+	local check = CreateFrame("CheckButton", "DressToKillEnableButton", frame, "UICheckButtonTemplate")
+	check:SetPoint("TOPLEFT", frame, "TOPLEFT", 15, -50)
+	check:SetWidth(24)
+	check:SetHeight(24)
+	check:SetChecked(AddonLoaderSV.silent)
+	AddonLoaderCheckButtonText:SetText("Hide Loading Messages")
+	AddonLoaderCheckButtonText:SetPoint("LEFT", check, "RIGHT")
+	check:SetScript("OnClick", function()
+		AddonLoaderSV.silent = not AddonLoaderSV.silent
+		check:SetChecked(AddonLoaderSV.silent)
+	end)
+	--]]
+
+	local explain = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
+	explain:SetTextColor(1,1,1)
+	explain:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, 0)
+	explain:SetPoint("RIGHT", frame, "RIGHT", -15, 0)
+	explain:SetJustifyH("LEFT")
+	explain:SetJustifyV("TOP")
+	explain:SetHeight(100)
+	explain:SetText(L["Dress to Kill is a simple addon which takes the guesswork out of building equipment sets for your character.  Rather than use a complicated library to scan your items for potential bonuses it sacrifices speed for optimal results.  It will scan through your inventory trying items on until it find the most effective mix.  You can customize the way equipment is evaluated through a custom evaluation function or you can simply use one of the built in functions."])
+
+	local function dropdown_onclick()
+		local selected = this.value
+
+		if selected == "__NEW__" then
+			-- Make a new entry
+			StaticPopup_Show("DRESSTOKILL_NEW_FUNCTION")
+		else
+			DressToKill.profile.selected = selected
+			UIDropDownMenu_SetSelectedValue(dropdown, selected)
+			editbox:SetText(DressToKill.profile.weightFuncs[selected].handler)
+			DressToKillDeleteButton:Enable()
+		end
+	end
+
+	local function initdropdown()
+		local sort = {}
+		for k,v in pairs(DressToKill.profile.weightFuncs) do
+			table.insert(sort, k)
+		end
+		table.sort(sort)
+
+		local info = UIDropDownMenu_CreateInfo()
+
+		for idx,name in ipairs(sort) do
+			info.text = name
+			info.value = name
+			info.func = dropdown_onclick
+			info.checked = false
+			UIDropDownMenu_AddButton(info)
+		end
+
+		info.text = L["New weight function"]
+		info.value = "__NEW__"
+		info.func = dropdown_onclick
+		info.checked = false
+		UIDropDownMenu_AddButton(info)
+	end
+
+	local dropdownlabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
+	dropdownlabel:SetPoint("TOPLEFT", explain, "BOTTOMLEFT", 0, -10)
+	dropdownlabel:SetText("Weight Function:")
+	dropdownlabel:SetHeight(15)
+	dropdownlabel:SetWidth(100)
+
+	dropdown = CreateFrame("Frame", "DressToKillDropDown", frame, "UIDropDownMenuTemplate")
+	dropdown:EnableMouse(true)
+	dropdown:SetPoint("LEFT", dropdownlabel, "RIGHT")
+	UIDropDownMenu_Initialize(dropdown, initdropdown)
+	UIDropDownMenu_SetSelectedValue(dropdown, DressToKill.profile.selected)
+	UIDropDownMenu_SetWidth(160, dropdown)
+	UIDropDownMenu_JustifyText("LEFT", dropdown)
+
+	editbox = CreateFrame("EditBox", "DressToKillEditBox", frame)
+	editbox:SetPoint("TOPLEFT", dropdownlabel, "BOTTOMLEFT", 0, -15)
+	editbox:SetPoint("RIGHT", -15, 0)
+	editbox:SetPoint("BOTTOM", 0, 45)
+	editbox:SetFont("Interface\\AddOns\\WowLua\\fonts\\VeraMono.ttf", 12)
+	editbox:SetTextInsets(8,8,8,8)
+	editbox:SetBackdrop({
+		bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
+		edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
+		edgeSize = 16,
+		insets = {left = 4, right = 4, top = 4, bottom = 4}
+	})
+	editbox:SetBackdropColor(.1,.1,.1,.3)
+	editbox:SetBackdropBorderColor(.5,.5,.5)
+	editbox:SetMultiLine(true)
+	editbox:SetAutoFocus(false)
+	editbox:ClearFocus()
+	editbox:SetScript("OnEscapePressed", function(self)
+		self:ClearFocus()
+	end)
+
+	local selected = DressToKill.profile.selected
+	if selected then
+		editbox:SetText(DressToKill.profile.weightFuncs[selected].handler or "")
+	else
+		editbox:SetText("-- Select an existing weight function from the above dropdown or create a new function.")
+	end
+
+	IndentationLib.enable(editbox)
+
+	reset = CreateFrame("Button", "DressToKillResetButton", frame, "UIPanelButtonTemplate2")
+	reset:SetText(L["Reset"])
+	reset:SetWidth(80)
+	reset:SetPoint("TOPRIGHT", editbox, "BOTTOMRIGHT", 0, -5)
+	reset:SetScript("OnClick", function(self)
+		local selected = DressToKill.profile.selected
+		local handler = DressToKill.profile.weightFuncs[selected].handler
+		editbox:SetText(handler)
+		self:Disable()
+	end)
+	reset.counter = 0
+	reset:SetScript("OnUpdate", function(self, elapsed)
+		self.counter = self.counter + elapsed
+		if self.counter > 0.2 then
+			self.counter = 0
+			-- Do check
+			local selected = DressToKill.profile.selected
+			if not selected then
+				self:Disable()
+				return
+			end
+			local source = DressToKill.profile.weightFuncs[selected].handler
+			if editbox:GetText() ~= source then
+				self:Enable()
+			else
+				self:Disable()
+			end
+		end
+	end)
+
+	save = CreateFrame("Button", "DressToKillSaveButton", frame, "UIPanelButtonTemplate2")
+	save:SetText(L["Save"])
+	save:SetWidth(80)
+	save:SetPoint("RIGHT", reset, "LEFT", -3, 0)
+	save:SetScript("OnClick", function(self)
+		local selected = DressToKill.profile.selected
+		local entry = DressToKill.profile.weightFuncs[selected]
+		entry.handler = editbox:GetText()
+		self:Disable()
+	end)
+	save.counter = 0
+	save:SetScript("OnUpdate", function(self, elapsed)
+		self.counter = self.counter + elapsed
+		if self.counter > 0.2 then
+			self.counter = 0
+			-- Do check
+			local selected = DressToKill.profile.selected
+			if not selected then
+				self:Disable()
+				return
+			end
+			local source = DressToKill.profile.weightFuncs[selected].handler
+			if editbox:GetText() ~= source then
+				self:Enable()
+			else
+				self:Disable()
+			end
+		end
+	end)
+
+	delete = CreateFrame("Button", "DressToKillDeleteButton", frame, "UIPanelButtonTemplate2")
+	delete:SetText(L["Delete"])
+	delete:SetWidth(80)
+	delete:SetPoint("RIGHT", save, "LEFT", -3, 0)
+	delete:SetScript("OnClick", function(self)
+		local selected = DressToKill.profile.selected
+		DressToKill.profile.selected = nil
+		DressToKill.profile.weightFuncs[selected] = nil
+		UIDropDownMenu_Initialize(dropdown, initdropdown)
+		UIDropDownMenu_SetSelectedValue(dropdown, nil)
+		editbox:SetText("")
+		self:Disable()
+	end)
+
+	local function OnAccept()
+		local name = this:GetParent():GetName().."EditBox"
+		local button = getglobal(name)
+		local text = button:GetText()
+		DressToKill.profile.selected = text
+		DressToKill.profile.weightFuncs[text] = {
+			handler = ""
+		}
+		UIDropDownMenu_Initialize(dropdown, initdropdown)
+		UIDropDownMenu_SetSelectedValue(dropdown, text)
+		editbox:SetText([[
+--This function receives a single parameter
+--'link', which is the link of the item
+--being tested.  Example usage to max
+--the player's health:
+--
+-- return UnitHealthMax("player")]])
+	end
+
+	-- Create a static popup
+	StaticPopupDialogs["DRESSTOKILL_NEW_FUNCTION"] = {
+		text = TEXT("Enter the name of the function you'd like to create"),
+		button1 = TEXT(OKAY),
+		button2 = TEXT(CANCEL),
+		OnAccept = OnAccept,
+		timeout = 0,
+		whileDead = 1,
+		exclusive = 1,
+		showAlert = 1,
+		hideOnEscape = 1,
+		hasEditBox = 1,
+		maxLetters = 32,
+		OnShow = function()
+			getglobal(this:GetName().."Button1"):Disable();
+			getglobal(this:GetName().."EditBox"):SetFocus();
+		end,
+		OnHide = function()
+			if ( ChatFrameEditBox:IsVisible() ) then
+				ChatFrameEditBox:SetFocus();
+			end
+			getglobal(this:GetName().."EditBox"):SetText("");
+		end,
+		EditBoxOnEnterPressed = function()
+			if ( getglobal(this:GetParent():GetName().."Button1"):IsEnabled() == 1 ) then
+				OnAccept()
+				this:GetParent():Hide()
+			end
+		end,
+		EditBoxOnTextChanged = function ()
+			local editBox = getglobal(this:GetParent():GetName().."EditBox");
+			local txt = editBox:GetText()
+			if #txt > 0 and not DressToKill.profile.weightFuncs[txt] then
+				getglobal(this:GetParent():GetName().."Button1"):Enable();
+			else
+				getglobal(this:GetParent():GetName().."Button1"):Disable();
+			end
+		end,
+		EditBoxOnEscapePressed = function()
+			this:GetParent():Hide();
+			ClearCursor();
+		end
+	}
+
+	frame:SetScript("OnShow", nil)
+end )
+
+InterfaceOptions_AddCategory(frame)
+
diff --git a/FAIAP.lua b/FAIAP.lua
new file mode 100755
index 0000000..40f1678
--- /dev/null
+++ b/FAIAP.lua
@@ -0,0 +1,1326 @@
+-- For All Indents And Purposes
+local revision = 18
+-- Maintainer: kristofer.karlsson@gmail.com
+
+-- For All Indents And Purposes -
+-- a indentation + syntax highlighting library
+-- All valid lua code should be processed correctly.
+
+-- Usage (for developers)
+--------
+-- Variant 1: - non embedded
+-- 1) Add ForAllIndentsAndPurposes to your dependencies (or optional dependencies)
+
+-- Variant 2: - embedded
+-- 1.a) Copy indent.lua to your addon directory
+-- 1.b) Put indent.lua first in your list of files in the TOC
+
+-- For both variants:
+-- 2) hook the editboxes that you want to have indentation like this:
+-- IndentationLib.enable(editbox [, colorTable [, tabWidth] ])
+-- if you don't select a color table, it will use the default.
+-- Read through this code for further usage help.
+-- (The documentation IS the code)
+
+if not IndentationLib then
+   IndentationLib = {}
+end
+
+if not IndentationLib.revision or revision > IndentationLib.revision then
+   local lib = IndentationLib
+   lib.revision = revision
+
+   local stringlen = string.len
+   local stringformat = string.format
+   local stringfind = string.find
+   local stringsub = string.sub
+   local stringbyte = string.byte
+   local stringchar = string.char
+   local stringrep = string.rep
+   local stringgsub = string.gsub
+
+   local workingTable = {}
+   local workingTable2 = {}
+   local function tableclear(t)
+      for k in next,t do
+	 t[k] = nil
+      end
+   end
+
+   local function stringinsert(s, pos, insertStr)
+      return stringsub(s, 1, pos) .. insertStr .. stringsub(s, pos + 1)
+   end
+   lib.stringinsert = stringinsert
+
+   local function stringdelete(s, pos1, pos2)
+      return stringsub(s, 1, pos1 - 1) .. stringsub(s, pos2 + 1)
+   end
+   lib.stringdelete = stringdelete
+
+   -- token types
+   local tokens = {}
+   lib.tokens = tokens
+
+   tokens.TOKEN_UNKNOWN = 0
+   tokens.TOKEN_NUMBER = 1
+   tokens.TOKEN_LINEBREAK = 2
+   tokens.TOKEN_WHITESPACE = 3
+   tokens.TOKEN_IDENTIFIER = 4
+   tokens.TOKEN_ASSIGNMENT = 5
+   tokens.TOKEN_EQUALITY = 6
+   tokens.TOKEN_MINUS = 7
+   tokens.TOKEN_COMMENT_SHORT = 8
+   tokens.TOKEN_COMMENT_LONG = 9
+   tokens.TOKEN_STRING = 10
+   tokens.TOKEN_LEFTBRACKET = 11
+   tokens.TOKEN_PERIOD = 12
+   tokens.TOKEN_DOUBLEPERIOD = 13
+   tokens.TOKEN_TRIPLEPERIOD = 14
+   tokens.TOKEN_LTE = 15
+   tokens.TOKEN_LT = 16
+   tokens.TOKEN_GTE = 17
+   tokens.TOKEN_GT = 18
+   tokens.TOKEN_NOTEQUAL = 19
+   tokens.TOKEN_COMMA = 20
+   tokens.TOKEN_SEMICOLON = 21
+   tokens.TOKEN_COLON = 22
+   tokens.TOKEN_LEFTPAREN = 23
+   tokens.TOKEN_RIGHTPAREN = 24
+   tokens.TOKEN_PLUS = 25
+   tokens.TOKEN_SLASH = 27
+   tokens.TOKEN_LEFTWING = 28
+   tokens.TOKEN_RIGHTWING = 29
+   tokens.TOKEN_CIRCUMFLEX = 30
+   tokens.TOKEN_ASTERISK = 31
+   tokens.TOKEN_RIGHTBRACKET = 32
+   tokens.TOKEN_KEYWORD = 33
+   tokens.TOKEN_SPECIAL = 34
+   tokens.TOKEN_VERTICAL = 35
+   tokens.TOKEN_TILDE = 36
+
+   -- WoW specific tokens
+   tokens.TOKEN_COLORCODE_START = 37
+   tokens.TOKEN_COLORCODE_STOP = 38
+
+-- new as of lua 5.1
+   tokens.TOKEN_HASH = 39
+   tokens.TOKEN_PERCENT = 40
+
+
+   -- ascii codes
+   local bytes = {}
+   lib.bytes = bytes
+   bytes.BYTE_LINEBREAK_UNIX = stringbyte("\n")
+   bytes.BYTE_LINEBREAK_MAC = stringbyte("\r")
+   bytes.BYTE_SINGLE_QUOTE = stringbyte("'")
+   bytes.BYTE_DOUBLE_QUOTE = stringbyte('"')
+   bytes.BYTE_0 = stringbyte("0")
+   bytes.BYTE_9 = stringbyte("9")
+   bytes.BYTE_PERIOD = stringbyte(".")
+   bytes.BYTE_SPACE = stringbyte(" ")
+   bytes.BYTE_TAB = stringbyte("\t")
+   bytes.BYTE_E = stringbyte("E")
+   bytes.BYTE_e = stringbyte("e")
+   bytes.BYTE_MINUS = stringbyte("-")
+   bytes.BYTE_EQUALS = stringbyte("=")
+   bytes.BYTE_LEFTBRACKET = stringbyte("[")
+   bytes.BYTE_RIGHTBRACKET = stringbyte("]")
+   bytes.BYTE_BACKSLASH = stringbyte("\\")
+   bytes.BYTE_COMMA = stringbyte(",")
+   bytes.BYTE_SEMICOLON = stringbyte(";")
+   bytes.BYTE_COLON = stringbyte(":")
+   bytes.BYTE_LEFTPAREN = stringbyte("(")
+   bytes.BYTE_RIGHTPAREN = stringbyte(")")
+   bytes.BYTE_TILDE = stringbyte("~")
+   bytes.BYTE_PLUS = stringbyte("+")
+   bytes.BYTE_SLASH = stringbyte("/")
+   bytes.BYTE_LEFTWING = stringbyte("{")
+   bytes.BYTE_RIGHTWING = stringbyte("}")
+   bytes.BYTE_CIRCUMFLEX = stringbyte("^")
+   bytes.BYTE_ASTERISK = stringbyte("*")
+   bytes.BYTE_LESSTHAN = stringbyte("<")
+   bytes.BYTE_GREATERTHAN = stringbyte(">")
+   -- WoW specific chars
+   bytes.BYTE_VERTICAL = stringbyte("|")
+   bytes.BYTE_r = stringbyte("r")
+   bytes.BYTE_c = stringbyte("c")
+
+-- new as of lua 5.1
+   bytes.BYTE_HASH = stringbyte("#")
+   bytes.BYTE_PERCENT = stringbyte("%")
+
+
+   local linebreakCharacters = {}
+   lib.linebreakCharacters = linebreakCharacters
+   linebreakCharacters[bytes.BYTE_LINEBREAK_UNIX] = 1
+   linebreakCharacters[bytes.BYTE_LINEBREAK_MAC] = 1
+
+   local whitespaceCharacters = {}
+   lib.whitespaceCharacters = whitespaceCharacters
+   whitespaceCharacters[bytes.BYTE_SPACE] = 1
+   whitespaceCharacters[bytes.BYTE_TAB] = 1
+
+   local specialCharacters = {}
+   lib.specialCharacters = specialCharacters
+   specialCharacters[bytes.BYTE_PERIOD] = -1
+   specialCharacters[bytes.BYTE_LESSTHAN] = -1
+   specialCharacters[bytes.BYTE_GREATERTHAN] = -1
+   specialCharacters[bytes.BYTE_LEFTBRACKET] = -1
+   specialCharacters[bytes.BYTE_EQUALS] = -1
+   specialCharacters[bytes.BYTE_MINUS] = -1
+   specialCharacters[bytes.BYTE_SINGLE_QUOTE] = -1
+   specialCharacters[bytes.BYTE_DOUBLE_QUOTE] = -1
+   specialCharacters[bytes.BYTE_TILDE] = -1
+   specialCharacters[bytes.BYTE_RIGHTBRACKET] = tokens.TOKEN_RIGHTBRACKET
+   specialCharacters[bytes.BYTE_COMMA] = tokens.TOKEN_COMMA
+   specialCharacters[bytes.BYTE_COLON] = tokens.TOKEN_COLON
+   specialCharacters[bytes.BYTE_SEMICOLON] = tokens.TOKEN_SEMICOLON
+   specialCharacters[bytes.BYTE_LEFTPAREN] = tokens.TOKEN_LEFTPAREN
+   specialCharacters[bytes.BYTE_RIGHTPAREN] = tokens.TOKEN_RIGHTPAREN
+   specialCharacters[bytes.BYTE_PLUS] = tokens.TOKEN_PLUS
+   specialCharacters[bytes.BYTE_SLASH] = tokens.TOKEN_SLASH
+   specialCharacters[bytes.BYTE_LEFTWING] = tokens.TOKEN_LEFTWING
+   specialCharacters[bytes.BYTE_RIGHTWING] = tokens.TOKEN_RIGHTWING
+   specialCharacters[bytes.BYTE_CIRCUMFLEX] = tokens.TOKEN_CIRCUMFLEX
+   specialCharacters[bytes.BYTE_ASTERISK] = tokens.TOKEN_ASTERISK
+   -- WoW specific
+   specialCharacters[bytes.BYTE_VERTICAL] = -1
+-- new as of lua 5.1
+   specialCharacters[bytes.BYTE_HASH] = tokens.TOKEN_HASH
+   specialCharacters[bytes.BYTE_PERCENT] = tokens.TOKEN_PERCENT
+
+   local function nextNumberExponentPartInt(text, pos)
+      while true do
+	 local byte = stringbyte(text, pos)
+	 if not byte then
+	    return tokens.TOKEN_NUMBER, pos
+	 end
+
+	 if byte >= bytes.BYTE_0 and byte <= bytes.BYTE_9 then
+	    pos = pos + 1
+	 else
+	    return tokens.TOKEN_NUMBER, pos
+	 end
+      end
+   end
+
+   local function nextNumberExponentPart(text, pos)
+      local byte = stringbyte(text, pos)
+      if not byte then
+	 return tokens.TOKEN_NUMBER, pos
+      end
+
+      if byte == bytes.BYTE_MINUS then
+	 -- handle this case: a = 1.2e-- some comment
+	 -- i decide to let 1.2e be parsed as a a number
+	 byte = stringbyte(text, pos + 1)
+	 if byte == bytes.BYTE_MINUS then
+	    return tokens.TOKEN_NUMBER, pos
+	 end
+	 return nextNumberExponentPartInt(text, pos + 1)
+      end
+
+      return nextNumberExponentPartInt(text, pos)
+   end
+
+   local function nextNumberFractionPart(text, pos)
+      while true do
+	 local byte = stringbyte(text, pos)
+	 if not byte then
+	    return tokens.TOKEN_NUMBER, pos
+	 end
+
+	 if byte >= bytes.BYTE_0 and byte <= bytes.BYTE_9 then
+	    pos = pos + 1
+	 elseif byte == bytes.BYTE_E or byte == bytes.BYTE_e then
+	    return nextNumberExponentPart(text, pos + 1)
+	 else
+	    return tokens.TOKEN_NUMBER, pos
+	 end
+      end
+   end
+
+   local function nextNumberIntPart(text, pos)
+      while true do
+	 local byte = stringbyte(text, pos)
+	 if not byte then
+	    return tokens.TOKEN_NUMBER, pos
+	 end
+
+	 if byte >= bytes.BYTE_0 and byte <= bytes.BYTE_9 then
+	    pos = pos + 1
+	 elseif byte == bytes.BYTE_PERIOD then
+	    return nextNumberFractionPart(text, pos + 1)
+	 elseif byte == bytes.BYTE_E or byte == bytes.BYTE_e then
+	    return nextNumberExponentPart(text, pos + 1)
+	 else
+	    return tokens.TOKEN_NUMBER, pos
+	 end
+      end
+   end
+
+   local function nextIdentifier(text, pos)
+      while true do
+	 local byte = stringbyte(text, pos)
+
+	 if not byte or
+	    linebreakCharacters[byte] or
+	    whitespaceCharacters[byte] or
+	    specialCharacters[byte] then
+	    return tokens.TOKEN_IDENTIFIER, pos
+	 end
+	 pos = pos + 1
+      end
+   end
+
+   -- returns false or: true, nextPos, equalsCount
+   local function isBracketStringNext(text, pos)
+      local byte = stringbyte(text, pos)
+      if byte == bytes.BYTE_LEFTBRACKET then
+	 local pos2 = pos + 1
+	 byte = stringbyte(text, pos2)
+	 while byte == bytes.BYTE_EQUALS do
+	    pos2 = pos2 + 1
+	    byte = stringbyte(text, pos2)
+	 end
+	 if byte == bytes.BYTE_LEFTBRACKET then
+	    return true, pos2 + 1, (pos2 - 1) - pos
+	 else
+	    return false
+	 end
+      else
+	 return false
+      end
+   end
+
+
+   -- Already parsed the [==[ part when get here
+   local function nextBracketString(text, pos, equalsCount)
+      local state = 0
+      while true do
+	 local byte = stringbyte(text, pos)
+	 if not byte then
+	    return tokens.TOKEN_STRING, pos
+	 end
+
+	 if byte == bytes.BYTE_RIGHTBRACKET then
+	    if state == 0 then
+	       state = 1
+	    elseif state == equalsCount + 1 then
+	       return tokens.TOKEN_STRING, pos + 1
+	    else
+	       state = 0
+	    end
+	 elseif byte == bytes.BYTE_EQUALS then
+	    if state > 0 then
+	       state = state + 1
+	    end
+	 else
+	    state = 0
+	 end
+	 pos = pos + 1
+      end
+   end
+
+   local function nextComment(text, pos)
+      -- When we get here we have already parsed the "--"
+      -- Check for long comment
+      local isBracketString, nextPos, equalsCount = isBracketStringNext(text, pos)
+      if isBracketString then
+	 local tokenType, nextPos2 = nextBracketString(text, nextPos, equalsCount)
+	 return tokens.TOKEN_COMMENT_LONG, nextPos2
+      end
+
+      local byte = stringbyte(text, pos)
+
+      -- Short comment, find the first linebreak
+      while true do
+	 byte = stringbyte(text, pos)
+	 if not byte then
+	    return tokens.TOKEN_COMMENT_SHORT, pos
+	 end
+	 if linebreakCharacters[byte] then
+	    return tokens.TOKEN_COMMENT_SHORT, pos
+	 end
+	 pos = pos + 1
+      end
+   end
+
+   local function nextString(text, pos, character)
+      local even = true
+      while true do
+	 local byte = stringbyte(text, pos)
+	 if not byte then
+	    return tokens.TOKEN_STRING, pos
+	 end
+
+	 if byte == character then
+	    if even then
+	       return tokens.TOKEN_STRING, pos + 1
+	    end
+	 end
+	 if byte == bytes.BYTE_BACKSLASH then
+	    even = not even
+	 else
+	    even = true
+	 end
+
+	 pos = pos + 1
+      end
+   end
+
+   -- INPUT
+   -- 1: text: text to search in
+   -- 2: tokenPos:  where to start searching
+   -- OUTPUT
+   -- 1: token type
+   -- 2: position after the last character of the token
+   function nextToken(text, pos)
+      local byte = stringbyte(text, pos)
+      if not byte then
+	 return nil
+      end
+
+      if linebreakCharacters[byte] then
+	 return tokens.TOKEN_LINEBREAK, pos + 1
+      end
+
+      if whitespaceCharacters[byte] then
+	 while true do
+	    pos = pos + 1
+	    byte = stringbyte(text, pos)
+	    if not byte or not whitespaceCharacters[byte] then
+	       return tokens.TOKEN_WHITESPACE, pos
+	    end
+	 end
+      end
+
+      local token = specialCharacters[byte]
+      if token then
+	 if token ~= -1 then
+	    return token, pos + 1
+	 end
+
+	 -- WoW specific (for color codes)
+	 if byte == bytes.BYTE_VERTICAL then
+	    byte = stringbyte(text, pos + 1)
+	    if byte == bytes.BYTE_VERTICAL then
+	       return tokens.TOKEN_VERTICAL, pos + 2
+	    end
+	    if byte == bytes.BYTE_c then
+	       return tokens.TOKEN_COLORCODE_START, pos + 10
+	    end
+	    if byte == bytes.BYTE_r then
+	       return tokens.TOKEN_COLORCODE_STOP, pos + 2
+	    end
+	    return tokens.TOKEN_UNKNOWN, pos + 1
+	 end
+
+	 if byte == bytes.BYTE_MINUS then
+	    byte = stringbyte(text, pos + 1)
+	    if byte == bytes.BYTE_MINUS then
+	       return nextComment(text, pos + 2)
+	    end
+	    return tokens.TOKEN_MINUS, pos + 1
+	 end
+
+	 if byte == bytes.BYTE_SINGLE_QUOTE then
+	    return nextString(text, pos + 1, bytes.BYTE_SINGLE_QUOTE)
+	 end
+
+	 if byte == bytes.BYTE_DOUBLE_QUOTE then
+	    return nextString(text, pos + 1, bytes.BYTE_DOUBLE_QUOTE)
+	 end
+
+	 if byte == bytes.BYTE_LEFTBRACKET then
+	    local isBracketString, nextPos, equalsCount = isBracketStringNext(text, pos)
+	    if isBracketString then
+	       return nextBracketString(text, nextPos, equalsCount)
+	    else
+	       return tokens.TOKEN_LEFTBRACKET, pos + 1
+	    end
+	 end
+
+	 if byte == bytes.BYTE_EQUALS then
+	    byte = stringbyte(text, pos + 1)
+	    if not byte then
+	       return tokens.TOKEN_ASSIGNMENT, pos + 1
+	    end
+	    if byte == bytes.BYTE_EQUALS then
+	       return tokens.TOKEN_EQUALITY, pos + 2
+	    end
+	    return tokens.TOKEN_ASSIGNMENT, pos + 1
+	 end
+
+	 if byte == bytes.BYTE_PERIOD then
+	    byte = stringbyte(text, pos + 1)
+	    if not byte then
+	       return tokens.TOKEN_PERIOD, pos + 1
+	    end
+	    if byte == bytes.BYTE_PERIOD then
+	       byte = stringbyte(text, pos + 2)
+	       if byte == bytes.BYTE_PERIOD then
+		  return tokens.TOKEN_TRIPLEPERIOD, pos + 3
+	       end
+	       return tokens.TOKEN_DOUBLEPERIOD, pos + 2
+	    elseif byte >= bytes.BYTE_0 and byte <= bytes.BYTE_9 then
+	       return nextNumberFractionPart(text, pos + 2)
+	    end
+	    return tokens.TOKEN_PERIOD, pos + 1
+	 end
+
+	 if byte == bytes.BYTE_LESSTHAN then
+	    byte = stringbyte(text, pos + 1)
+	    if byte == bytes.BYTE_EQUALS then
+	       return tokens.TOKEN_LTE, pos + 2
+	    end
+	    return tokens.TOKEN_LT, pos + 1
+	 end
+
+	 if byte == bytes.BYTE_GREATERTHAN then
+	    byte = stringbyte(text, pos + 1)
+	    if byte == bytes.BYTE_EQUALS then
+	       return tokens.TOKEN_GTE, pos + 2
+	    end
+	    return tokens.TOKEN_GT, pos + 1
+	 end
+
+	 if byte == bytes.BYTE_TILDE then
+	    byte = stringbyte(text, pos + 1)
+	    if byte == bytes.BYTE_EQUALS then
+	       return tokens.TOKEN_NOTEQUAL, pos + 2
+	    end
+	    return tokens.TOKEN_TILDE, pos + 1
+	 end
+
+	 return tokens.TOKEN_UNKNOWN, pos + 1
+      elseif byte >= bytes.BYTE_0 and byte <= bytes.BYTE_9 then
+	 return nextNumberIntPart(text, pos + 1)
+      else
+	 return nextIdentifier(text, pos + 1)
+      end
+   end
+
+   -- Cool stuff begins here! (indentation and highlighting)
+
+   local noIndentEffect = {0, 0}
+   local indentLeft = {-1, 0}
+   local indentRight = {0, 1}
+   local indentBoth = {-1, 1}
+
+   local keywords = {}
+   lib.keywords = keywords
+   keywords["and"] = noIndentEffect
+   keywords["break"] = noIndentEffect
+   keywords["false"] = noIndentEffect
+   keywords["for"] = noIndentEffect
+   keywords["if"] = noIndentEffect
+   keywords["in"] = noIndentEffect
+   keywords["local"] = noIndentEffect
+   keywords["nil"] = noIndentEffect
+   keywords["not"] = noIndentEffect
+   keywords["or"] = noIndentEffect
+   keywords["return"] = noIndentEffect
+   keywords["true"] = noIndentEffect
+   keywords["while"] = noIndentEffect
+
+   keywords["until"] = indentLeft
+   keywords["elseif"] = indentLeft
+   keywords["end"] = indentLeft
+
+   keywords["do"] = indentRight
+   keywords["then"] = indentRight
+   keywords["repeat"] = indentRight
+   keywords["function"] = indentRight
+
+   keywords["else"] = indentBoth
+
+   tokenIndentation = {}
+   lib.tokenIndentation = tokenIndentation
+   tokenIndentation[tokens.TOKEN_LEFTPAREN] = indentRight
+   tokenIndentation[tokens.TOKEN_LEFTBRACKET] = indentRight
+   tokenIndentation[tokens.TOKEN_LEFTWING] = indentRight
+
+   tokenIndentation[tokens.TOKEN_RIGHTPAREN] = indentLeft
+   tokenIndentation[tokens.TOKEN_RIGHTBRACKET] = indentLeft
+   tokenIndentation[tokens.TOKEN_RIGHTWING] = indentLeft
+
+   local function fillWithTabs(n)
+      return stringrep("\t", n)
+   end
+
+   local function fillWithSpaces(a, b)
+      return stringrep(" ", a*b)
+   end
+
+   function lib.colorCodeCode(code, colorTable, caretPosition)
+      local stopColor = colorTable and colorTable[0]
+      if not stopColor then
+	 return code, caretPosition
+      end
+
+      local stopColorLen = stringlen(stopColor)
+
+      tableclear(workingTable)
+      local tsize = 0
+      local totalLen = 0
+
+      local numLines = 0
+      local newCaretPosition
+      local prevTokenWasColored = false
+      local prevTokenWidth = 0
+
+      local pos = 1
+      local level = 0
+
+      while true do
+	 if caretPosition and not newCaretPosition and pos >= caretPosition then
+	    if pos == caretPosition then
+	       newCaretPosition = totalLen
+	    else
+	       newCaretPosition = totalLen
+	       local diff = pos - caretPosition
+	       if diff > prevTokenWidth then
+		  diff = prevTokenWidth
+	       end
+	       if prevTokenWasColored then
+		  diff = diff + stopColorLen
+	       end
+	       newCaretPosition = newCaretPosition - diff
+	    end
+	 end
+
+	 prevTokenWasColored = false
+	 prevTokenWidth = 0
+
+	 local tokenType, nextPos = nextToken(code, pos)
+
+	 if not tokenType then
+	    break
+	 end
+
+	 if tokenType == tokens.TOKEN_COLORCODE_START or tokenType == tokens.TOKEN_COLORCODE_STOP or tokenType == tokens.TOKEN_UNKNOWN then
+	    -- ignore color codes
+
+	 elseif tokenType == tokens.TOKEN_LINEBREAK or tokenType == tokens.TOKEN_WHITESPACE then
+	    if tokenType == tokens.TOKEN_LINEBREAK then
+	       numLines = numLines + 1
+	    end
+	    local str = stringsub(code, pos, nextPos - 1)
+	    prevTokenWidth = nextPos - pos
+
+	    tsize = tsize + 1
+	    workingTable[tsize] = str
+	    totalLen = totalLen + stringlen(str)
+	 else
+	    local str = stringsub(code, pos, nextPos - 1)
+
+	    prevTokenWidth = nextPos - pos
+
+	    -- Add coloring
+	    if keywords[str] then
+	       tokenType = tokens.TOKEN_KEYWORD
+	    end
+
+	    local color
+	    if stopColor then
+	       color = colorTable[str]
+	       if not color then
+		  color = colorTable[tokenType]
+		  if not color then
+		     if tokenType == tokens.TOKEN_IDENTIFIER then
+			color = colorTable[tokens.TOKEN_IDENTIFIER]
+		     else
+			color = colorTable[tokens.TOKEN_SPECIAL]
+		     end
+		  end
+	       end
+	    end
+
+	    if color then
+	       tsize = tsize + 1
+	       workingTable[tsize] = color
+	       tsize = tsize + 1
+	       workingTable[tsize] = str
+	       tsize = tsize + 1
+	       workingTable[tsize] = stopColor
+
+	       totalLen = totalLen + stringlen(color) + (nextPos - pos) + stopColorLen
+	       prevTokenWasColored = true
+	    else
+	       tsize = tsize + 1
+	       workingTable[tsize] = str
+
+	       totalLen = totalLen + stringlen(str)
+	    end
+	 end
+
+	 pos = nextPos
+      end
+      return table.concat(workingTable), newCaretPosition, numLines
+   end
+
+   function lib.indentCode(code, tabWidth, colorTable, caretPosition)
+      local fillFunction
+      if tabWidth == nil then
+	 tabWidth = defaultTabWidth
+      end
+      if tabWidth then
+	 fillFunction = fillWithSpaces
+      else
+	 fillFunction = fillWithTabs
+      end
+
+      tableclear(workingTable)
+      local tsize = 0
+      local totalLen = 0
+
+      tableclear(workingTable2)
+      local tsize2 = 0
+      local totalLen2 = 0
+
+
+      local stopColor = colorTable and colorTable[0]
+      local stopColorLen = not stopColor or stringlen(stopColor)
+
+      local newCaretPosition
+      local newCaretPositionFinalized = false
+      local prevTokenWasColored = false
+      local prevTokenWidth = 0
+
+      local pos = 1
+      local level = 0
+
+      local hitNonWhitespace = false
+      local hitIndentRight = false
+      local preIndent = 0
+      local postIndent = 0
+      while true do
+	 if caretPosition and not newCaretPosition and pos >= caretPosition then
+	    if pos == caretPosition then
+	       newCaretPosition = totalLen + totalLen2
+	    else
+	       newCaretPosition = totalLen + totalLen2
+	       local diff = pos - caretPosition
+	       if diff > prevTokenWidth then
+		  diff = prevTokenWidth
+	       end
+	       if prevTokenWasColored then
+		  diff = diff + stopColorLen
+	       end
+	       newCaretPosition = newCaretPosition - diff
+	    end
+	 end
+
+	 prevTokenWasColored = false
+	 prevTokenWidth = 0
+
+	 local tokenType, nextPos = nextToken(code, pos)
+
+	 if not tokenType or tokenType == tokens.TOKEN_LINEBREAK then
+	    level = level + preIndent
+	    if level < 0 then level = 0 end
+
+	    local s = fillFunction(level, tabWidth)
+
+	    tsize = tsize + 1
+	    workingTable[tsize] = s
+	    totalLen = totalLen + stringlen(s)
+
+	    if newCaretPosition and not newCaretPositionFinalized then
+	       newCaretPosition = newCaretPosition + stringlen(s)
+	       newCaretPositionFinalized = true
+	    end
+
+
+	    for k, v in next,workingTable2 do
+	       tsize = tsize + 1
+	       workingTable[tsize] = v
+	       totalLen = totalLen + stringlen(v)
+	    end
+
+	    if not tokenType then
+	       break
+	    end
+
+	    tsize = tsize + 1
+	    workingTable[tsize] = stringsub(code, pos, nextPos - 1)
+	    totalLen = totalLen + nextPos - pos
+
+	    level = level + postIndent
+	    if level < 0 then level = 0 end
+
+	    tableclear(workingTable2)
+	    tsize2 = 0
+	    totalLen2 = 0
+
+	    hitNonWhitespace = false
+	    hitIndentRight = false
+	    preIndent = 0
+	    postIndent = 0
+	 elseif tokenType == tokens.TOKEN_WHITESPACE then
+	    if hitNonWhitespace then
+	       prevTokenWidth = nextPos - pos
+
+	       tsize2 = tsize2 + 1
+	       local s = stringsub(code, pos, nextPos - 1)
+	       workingTable2[tsize2] = s
+	       totalLen2 = totalLen2 + stringlen(s)
+	    end
+	 elseif tokenType == tokens.TOKEN_COLORCODE_START or tokenType == tokens.TOKEN_COLORCODE_STOP or tokenType == tokens.TOKEN_UNKNOWN then
+	    -- skip these, though they shouldn't be encountered here anyway
+	 else
+	    hitNonWhitespace = true
+
+	    local str = stringsub(code, pos, nextPos - 1)
+
+	    prevTokenWidth = nextPos - pos
+
+	    -- See if this is an indent-modifier
+	    local indentTable
+	    if tokenType == tokens.TOKEN_IDENTIFIER then
+	       indentTable = keywords[str]
+	    else
+	       indentTable = tokenIndentation[tokenType]
+	    end
+
+	    if indentTable then
+	       if hitIndentRight then
+		  postIndent = postIndent + indentTable[1] + indentTable[2]
+	       else
+		  local pre = indentTable[1]
+		  local post = indentTable[2]
+		  if post > 0 then
+		     hitIndentRight = true
+		  end
+		  preIndent = preIndent + pre
+		  postIndent = postIndent + post
+	       end
+	    end
+
+	    -- Add coloring
+	    if keywords[str] then
+	       tokenType = tokens.TOKEN_KEYWORD
+	    end
+
+	    local color
+	    if stopColor then
+	       color = colorTable[str]
+	       if not color then
+		  color = colorTable[tokenType]
+		  if not color then
+		     if tokenType == tokens.TOKEN_IDENTIFIER then
+			color = colorTable[tokens.TOKEN_IDENTIFIER]
+		     else
+			color = colorTable[tokens.TOKEN_SPECIAL]
+		     end
+		  end
+	       end
+	    end
+
+	    if color then
+	       tsize2 = tsize2 + 1
+	       workingTable2[tsize2] = color
+	       totalLen2 = totalLen2 + stringlen(color)
+
+	       tsize2 = tsize2 + 1
+	       workingTable2[tsize2] = str
+	       totalLen2 = totalLen2 + nextPos - pos
+
+	       tsize2 = tsize2 + 1
+	       workingTable2[tsize2] = stopColor
+	       totalLen2 = totalLen2 + stopColorLen
+
+	       prevTokenWasColored = true
+	    else
+	       tsize2 = tsize2 + 1
+	       workingTable2[tsize2] = str
+	       totalLen2 = totalLen2 + nextPos - pos
+
+	    end
+	 end
+	 pos = nextPos
+      end
+      return table.concat(workingTable), newCaretPosition
+   end
+
+
+
+   -- WoW specific code:
+   local GetTime = GetTime
+
+   local editboxSetText
+   local editboxGetText
+
+   -- Caret code (thanks Tem!)
+   local function critical_enter(editbox)
+      local script = editbox:GetScript("OnTextSet")
+      if script then
+	 editbox:SetScript("OnTextSet", nil)
+      end
+      return script
+   end
+
+   local function critical_leave(editbox, script)
+      if script then
+	 editbox:SetScript("OnTextSet", script)
+      end
+   end
+
+   local function setCaretPos_main(editbox, pos)
+      local text = editboxGetText(editbox)
+
+      if stringlen(text) > 0 then
+	 editboxSetText(editbox, stringinsert(text, pos, "a"))
+	 editbox:HighlightText(pos, pos + 1)
+	 editbox:Insert("\0")
+      end
+   end
+
+   local function getCaretPos(editbox)
+      local script = critical_enter(editbox)
+
+      local text = editboxGetText(editbox)
+      editbox:Insert("\1")
+      local pos = stringfind(editboxGetText(editbox), "\1", 1, 1)
+      editboxSetText(editbox, text)
+
+      if pos then
+	 setCaretPos_main(editbox, pos - 1)
+      end
+      critical_leave(editbox, script)
+
+      return (pos or 0) - 1
+   end
+
+   local function setCaretPos(editbox, pos)
+      local script = critical_enter(editbox)
+      setCaretPos_main(editbox, pos)
+      critical_leave(editbox, script, script2)
+   end
+   -- end of caret code
+
+   function lib.stripWowColors(code)
+
+      -- HACK!
+      -- This is a fix for a bug, where an unfinished string causes a lot of newlines to be created.
+      -- The reason for the bug, is that a |r\n\n gets converted to \n\n|r after the next indent-run
+      -- The fix is to remove those last two linebreaks when stripping
+      code = stringgsub(code, "|r\n\n$", "|r")
+
+      tableclear(workingTable)
+      local tsize = 0
+
+      local pos = 1
+
+      local prevVertical = false
+      local even = true
+      local selectionStart = 1
+
+      while true do
+	 local byte = stringbyte(code, pos)
+	 if not byte then
+	    break
+	 end
+	 if byte == bytes.BYTE_VERTICAL then
+	    even = not even
+	    prevVertical = true
+	 else
+	    if prevVertical and not even then
+	       if byte == bytes.BYTE_c then
+
+		  if pos - 2 >= selectionStart then
+		     tsize = tsize + 1
+		     workingTable[tsize] = stringsub(code, selectionStart, pos - 2)
+		  end
+
+		  pos = pos + 8
+		  selectionStart = pos + 1
+	       elseif byte == bytes.BYTE_r then
+
+		  if pos - 2 >= selectionStart then
+		     tsize = tsize + 1
+		     workingTable[tsize] = stringsub(code, selectionStart, pos - 2)
+		  end
+		  selectionStart = pos + 1
+	       end
+	    end
+	    prevVertical = false
+	    even = true
+	 end
+	 pos = pos + 1
+      end
+      if pos >= selectionStart then
+	 tsize = tsize + 1
+	 workingTable[tsize] = stringsub(code, selectionStart, pos - 1)
+      end
+      return table.concat(workingTable)
+   end
+
+   function lib.decode(code)
+      if code then
+	 code = lib.stripWowColors(code)
+	 code = stringgsub(code, "||", "|")
+      end
+      return code or ""
+   end
+
+   function lib.encode(code)
+      if code then
+	 code = stringgsub(code, "|", "||")
+      end
+      return code or ""
+   end
+
+   function lib.stripWowColorsWithPos(code, pos)
+      code = stringinsert(code, pos, "\2")
+      code = lib.stripWowColors(code)
+      pos = stringfind(code, "\2", 1, 1)
+      code = stringdelete(code, pos, pos)
+      return code, pos
+   end
+
+   -- returns the padded code, and true if modified, false if unmodified
+   local linebreak = stringbyte("\n")
+   function lib.padWithLinebreaks(code)
+      local len = stringlen(code)
+      if stringbyte(code, len) == linebreak then
+         if stringbyte(code, len - 1) == linebreak then
+            return code, false
+         end
+         return code .. "\n", true
+      end
+      return code, true
+
+   end
+
+   local defaultTabWidth = 2
+   local defaultColorTable
+
+   -- Data tables
+   -- No weak table magic, since editboxes can never be removed in WoW
+   local enabled = {}
+   local dirty = {}
+
+   local editboxIndentCache = {}
+   local decodeCache = {}
+   local editboxStringCache = {}
+   local editboxNumLinesCache = {}
+
+   function lib.colorCodeEditbox(editbox)
+      dirty[editbox] = nil
+
+      local colorTable = editbox.faiap_colorTable or defaultColorTable
+      local tabWidth = editbox.faiap_tabWidth
+
+      local orgCode = editboxGetText(editbox)
+      local prevCode = editboxStringCache[editbox]
+      if prevCode == orgCode then
+	 return
+      end
+
+      local pos = getCaretPos(editbox)
+
+      local code
+      code, pos = lib.stripWowColorsWithPos(orgCode, pos)
+
+      colorTable[0] = "|r"
+
+      local newCode, newPos, numLines = lib.colorCodeCode(code, colorTable, pos)
+      newCode = lib.padWithLinebreaks(newCode)
+
+      editboxStringCache[editbox] = newCode
+      if orgCode ~= newCode then
+	 local script, script2 = critical_enter(editbox)
+	 decodeCache[editbox] = nil
+	 local stringlenNewCode = stringlen(newCode)
+
+	 editboxSetText(editbox, newCode)
+	 if newPos then
+	    if newPos < 0 then newPos = 0 end
+	    if newPos > stringlenNewCode then newPos = stringlenNewCode end
+
+	    setCaretPos(editbox, newPos)
+	 end
+	 critical_leave(editbox, script, script2)
+      end
+
+      if editboxNumLinesCache[editbox] ~= numLines then
+	 lib.indentEditbox(editbox)
+      end
+      editboxNumLinesCache[editbox] = numLines
+   end
+
+   function lib.indentEditbox(editbox)
+      dirty[editbox] = nil
+
+      local colorTable = editbox.faiap_colorTable or defaultColorTable
+      local tabWidth = editbox.faiap_tabWidth
+
+      local orgCode = editboxGetText(editbox)
+      local prevCode = editboxIndentCache[editbox]
+      if prevCode == orgCode then
+	 return
+      end
+
+      local pos = getCaretPos(editbox)
+
+      local code
+      code, pos = lib.stripWowColorsWithPos(orgCode, pos)
+
+      colorTable[0] = "|r"
+      local newCode, newPos = lib.indentCode(code, tabWidth, colorTable, pos)
+      newCode = lib.padWithLinebreaks(newCode)
+      editboxIndentCache[editbox] = newCode
+      if code ~= newCode then
+	 local script, script2 = critical_enter(editbox)
+	 decodeCache[editbox] = nil
+
+	 local stringlenNewCode = stringlen(newCode)
+
+	 editboxSetText(editbox, newCode)
+
+	 if newPos then
+	    if newPos < 0 then newPos = 0 end
+	    if newPos > stringlenNewCode then newPos = stringlenNewCode end
+
+	    setCaretPos(editbox, newPos)
+	 end
+	 critical_leave(editbox, script, script2)
+      end
+   end
+
+   local function hookHandler(editbox, handler, newFun)
+      local oldFun = editbox:GetScript(handler)
+      if oldFun == newFun then
+	 -- already hooked, ignore it
+	 return
+      end
+      editbox["faiap_old_" .. handler] = oldFun
+      editbox:SetScript(handler, newFun)
+   end
+
+   local function textChangedHook(editbox, ...)
+      local oldFun = editbox["faiap_old_OnTextChanged"]
+      if oldFun then
+	 oldFun(editbox, ...)
+      end
+      if enabled[editbox] then
+	 dirty[editbox] = GetTime()
+      end
+   end
+
+   local function tabPressedHook(editbox, ...)
+      local oldFun = editbox["faiap_old_OnTabPressed"]
+      if oldFun then
+	 oldFun(editbox, ...)
+      end
+      if enabled[editbox] then
+	 return lib.indentEditbox(editbox)
+      end
+   end
+
+   local function onUpdateHook(editbox, ...)
+      local oldFun = editbox["faiap_old_OnUpdate"]
+      if oldFun then
+	 oldFun(editbox, ...)
+      end
+      if enabled[editbox] then
+	 local now = GetTime()
+	 local lastUpdate = dirty[editbox] or now
+	 if now - lastUpdate > 0.2 then
+	    decodeCache[editbox] = nil
+	    return lib.colorCodeEditbox(editbox)
+	 end
+      end
+   end
+
+   local function newGetText(editbox)
+      local decoded = decodeCache[editbox]
+      if not decoded then
+	 decoded = lib.decode(editboxGetText(editbox))
+	 decodeCache[editbox] = decoded
+      end
+      return decoded or ""
+   end
+
+   local function newSetText(editbox, text)
+      decodeCache[editbox] = nil
+      if text then
+	 local encoded = lib.encode(text)
+
+	 return editboxSetText(editbox, encoded)
+      end
+   end
+
+   function lib.enable(editbox, colorTable, tabWidth)
+      if not editboxSetText then
+	 editboxSetText = editbox.SetText
+	 editboxGetText = editbox.GetText
+      end
+
+      local modified
+      if editbox.faiap_colorTable ~= colorTable then
+	 editbox.faiap_colorTable = colorTable
+	 modified = true
+      end
+      if editbox.faiap_tabWidth ~= tabWidth then
+	 editbox.faiap_tabWidth = tabWidth
+	 modified = true
+      end
+
+      if enabled[editbox] then
+	 if modified then
+	    lib.indentEditbox(editbox)
+	 end
+	 return
+      end
+
+      -- Editbox is possibly hooked, but disabled
+      enabled[editbox] = true
+
+      editbox.oldMaxBytes = editbox:GetMaxBytes()
+      editbox.oldMaxLetters = editbox:GetMaxLetters()
+      editbox:SetMaxBytes(0)
+      editbox:SetMaxLetters(0)
+
+      editbox.GetText = newGetText
+      editbox.SetText = newSetText
+
+      hookHandler(editbox, "OnTextChanged", textChangedHook)
+      hookHandler(editbox, "OnTabPressed", tabPressedHook)
+      hookHandler(editbox, "OnUpdate", onUpdateHook)
+
+      lib.indentEditbox(editbox)
+   end
+
+   -- Deprecated function
+   lib.addSmartCode = lib.enable
+
+   function lib.disable(editbox)
+      if not enabled[editbox] then
+	 return
+      end
+      enabled[editbox] = nil
+
+      -- revert settings for max bytes / letters
+      editbox:SetMaxBytes(editbox.oldMaxBytes)
+      editbox:SetMaxLetters(editbox.oldMaxLetters)
+
+      -- try a real unhooking, if possible
+      if editbox:GetScript("OnTextChanged") == textChangedHook then
+	 editbox:SetScript("OnTextChanged", editbox.faiap_old_OnTextChanged)
+	 editbox.faiap_old_OnTextChanged = nil
+      end
+
+      if editbox:GetScript("OnTabPressed") == tabPressedHook then
+	 editbox:SetScript("OnTabPressed", editbox.faiap_old_OnTabPressed)
+	 editbox.faiap_old_OnTabPressed = nil
+      end
+
+      if editbox:GetScript("OnUpdate") == onUpdateHook then
+	 editbox:SetScript("OnUpdate", editbox.faiap_old_OnUpdate)
+	 editbox.faiap_old_OnUpdate = nil
+      end
+
+      editbox.GetText = nil
+      editbox.SetText = nil
+
+      -- change the text back to unformatted
+      editbox:SetText(newGetText(editbox))
+
+      -- clear caches
+      editboxIndentCache[editbox] = nil
+      decodeCache[editbox] = nil
+      editboxStringCache[editbox] = nil
+      editboxNumLinesCache[editbox] = nil
+   end
+
+   defaultColorTable = {}
+   lib.defaultColorTable = defaultColorTable
+   defaultColorTable[tokens.TOKEN_SPECIAL] = "|c00ff99ff"
+   defaultColorTable[tokens.TOKEN_KEYWORD] = "|c006666ff"
+   defaultColorTable[tokens.TOKEN_COMMENT_SHORT] = "|c00999999"
+   defaultColorTable[tokens.TOKEN_COMMENT_LONG] = "|c00999999"
+
+   local stringColor = "|c00ffff77"
+   defaultColorTable[tokens.TOKEN_STRING] = stringColor
+   defaultColorTable[".."] = stringColor
+
+   local tableColor = "|c00ff9900"
+   defaultColorTable["..."] = tableColor
+   defaultColorTable["{"] = tableColor
+   defaultColorTable["}"] = tableColor
+   defaultColorTable["["] = tableColor
+   defaultColorTable["]"] = tableColor
+
+   local arithmeticColor = "|c0033ff55"
+   defaultColorTable[tokens.TOKEN_NUMBER] = arithmeticColor
+   defaultColorTable["+"] = arithmeticColor
+   defaultColorTable["-"] = arithmeticColor
+   defaultColorTable["/"] = arithmeticColor
+   defaultColorTable["*"] = arithmeticColor
+
+   local logicColor1 = "|c0055ff88"
+   defaultColorTable["=="] = logicColor1
+   defaultColorTable["<"] = logicColor1
+   defaultColorTable["<="] = logicColor1
+   defaultColorTable[">"] = logicColor1
+   defaultColorTable[">="] = logicColor1
+   defaultColorTable["~="] = logicColor1
+
+   local logicColor2 = "|c0088ffbb"
+   defaultColorTable["and"] = logicColor2
+   defaultColorTable["or"] = logicColor2
+   defaultColorTable["not"] = logicColor2
+
+   defaultColorTable[0] = "|r"
+
+end
+
+-- just for testing
+--[[
+function testTokenizer()
+   local str = ""
+   for line in io.lines("indent.lua") do
+      str = str .. line .. "\n"
+   end
+
+   local pos = 1
+
+   while true do
+      local tokenType, nextPos = nextToken(str, pos)
+
+      if not tokenType then
+	 break
+      end
+
+      if true or tokenType ~= tokens.TOKEN_WHITESPACE and tokenType ~= tokens.TOKEN_LINEBREAK then
+	 print(stringformat("Found token %d (%d-%d): (%s)", tokenType, pos, nextPos - 1, stringsub(str, pos, nextPos - 1)))
+      end
+
+      if tokenType == tokens.TOKEN_UNKNOWN then
+	 print("unknown token!")
+	 break
+      end
+
+      pos = nextPos
+   end
+end
+
+
+function testIndenter(i)
+   local lib = IndentationLib
+   local str = ""
+   for line in io.lines("test.lua") do
+      str = str .. line .. "\n"
+   end
+
+   local colorTable = lib.defaultColorTable
+   print(lib.indentCode(str, 4, colorTable, i))
+end
+
+
+testIndenter()
+
+--]]
diff --git a/fonts/COPYRIGHT.TXT b/fonts/COPYRIGHT.TXT
new file mode 100644
index 0000000..e651be1
--- /dev/null
+++ b/fonts/COPYRIGHT.TXT
@@ -0,0 +1,124 @@
+Bitstream Vera Fonts Copyright
+
+The fonts have a generous copyright, allowing derivative works (as
+long as "Bitstream" or "Vera" are not in the names), and full
+redistribution (so long as they are not *sold* by themselves). They
+can be be bundled, redistributed and sold with any software.
+
+The fonts are distributed under the following copyright:
+
+Copyright
+=========
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
+Vera is a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute
+the Font Software, including without limitation the rights to use,
+copy, merge, publish, distribute, and/or sell copies of the Font
+Software, and to permit persons to whom the Font Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Bitstream" or the word "Vera".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the
+"Bitstream Vera" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
+OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
+SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font
+Software without prior written authorization from the Gnome Foundation
+or Bitstream Inc., respectively. For further information, contact:
+fonts at gnome dot org.
+
+Copyright FAQ
+=============
+
+   1. I don't understand the resale restriction... What gives?
+
+      Bitstream is giving away these fonts, but wishes to ensure its
+      competitors can't just drop the fonts as is into a font sale system
+      and sell them as is. It seems fair that if Bitstream can't make money
+      from the Bitstream Vera fonts, their competitors should not be able to
+      do so either. You can sell the fonts as part of any software package,
+      however.
+
+   2. I want to package these fonts separately for distribution and
+      sale as part of a larger software package or system.  Can I do so?
+
+      Yes. A RPM or Debian package is a "larger software package" to begin
+      with, and you aren't selling them independently by themselves.
+      See 1. above.
+
+   3. Are derivative works allowed?
+      Yes!
+
+   4. Can I change or add to the font(s)?
+      Yes, but you must change the name(s) of the font(s).
+
+   5. Under what terms are derivative works allowed?
+
+      You must change the name(s) of the fonts. This is to ensure the
+      quality of the fonts, both to protect Bitstream and Gnome. We want to
+      ensure that if an application has opened a font specifically of these
+      names, it gets what it expects (though of course, using fontconfig,
+      substitutions could still could have occurred during font
+      opening). You must include the Bitstream copyright. Additional
+      copyrights can be added, as per copyright law. Happy Font Hacking!
+
+   6. If I have improvements for Bitstream Vera, is it possible they might get
+       adopted in future versions?
+
+      Yes. The contract between the Gnome Foundation and Bitstream has
+      provisions for working with Bitstream to ensure quality additions to
+      the Bitstream Vera font family. Please contact us if you have such
+      additions. Note, that in general, we will want such additions for the
+      entire family, not just a single font, and that you'll have to keep
+      both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
+      glyphs to the font, they must be stylistically in keeping with Vera's
+      design. Vera cannot become a "ransom note" font. Jim Lyles will be
+      providing a document describing the design elements used in Vera, as a
+      guide and aid for people interested in contributing to Vera.
+
+   7. I want to sell a software package that uses these fonts: Can I do so?
+
+      Sure. Bundle the fonts with your software and sell your software
+      with the fonts. That is the intent of the copyright.
+
+   8. If applications have built the names "Bitstream Vera" into them,
+      can I override this somehow to use fonts of my choosing?
+
+      This depends on exact details of the software. Most open source
+      systems and software (e.g., Gnome, KDE, etc.) are now converting to
+      use fontconfig (see www.fontconfig.org) to handle font configuration,
+      selection and substitution; it has provisions for overriding font
+      names and subsituting alternatives. An example is provided by the
+      supplied local.conf file, which chooses the family Bitstream Vera for
+      "sans", "serif" and "monospace".  Other software (e.g., the XFree86
+      core server) has other mechanisms for font substitution.
+
diff --git a/fonts/VeraMono.ttf b/fonts/VeraMono.ttf
new file mode 100644
index 0000000..139f0b4
Binary files /dev/null and b/fonts/VeraMono.ttf differ