vanilla-wow-addons – Rev 1
?pathlinks?
-- For All Indents And Purposes
local revision = 14
-- Maintainer: krka@kth.se
-- 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.addSmartCode(editbox [, colorTable])
-- 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 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
-- 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")
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
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
local function nextComment(text, pos)
-- When we get here we have already parsed the "--"
local byte = stringbyte(text, pos)
if byte == bytes.BYTE_LEFTBRACKET then
pos = pos + 1
byte = stringbyte(text, pos)
if byte == bytes.BYTE_LEFTBRACKET then
local level = 1
-- Long comment, scan for the ending "]]"
while true do
pos = pos + 1
byte = stringbyte(text, pos)
if not byte then
return tokens.TOKEN_COMMENT_LONG, pos
end
if byte == bytes.BYTE_LEFTBRACKET then
byte = stringbyte(text, pos + 1)
if not byte then
return tokens.TOKEN_COMMENT_LONG, pos + 1
end
if byte == bytes.BYTE_LEFTBRACKET then
level = level + 1
pos = pos + 1
end
elseif byte == bytes.BYTE_RIGHTBRACKET then
byte = stringbyte(text, pos + 1)
if not byte then
return tokens.TOKEN_COMMENT_LONG, pos + 1
end
if byte == bytes.BYTE_RIGHTBRACKET then
if level == 1 then
return tokens.TOKEN_COMMENT_LONG, pos + 2
end
level = level - 1
pos = pos + 1
end
end
end
end
end
-- 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
local function nextBracketString(text, pos)
while true do
local byte = stringbyte(text, pos)
if not byte then
return tokens.TOKEN_STRING, pos
end
if byte == bytes.BYTE_RIGHTBRACKET then
pos = pos + 1
byte = stringbyte(text, pos)
if not byte then
return tokens.TOKEN_STRING, pos
end
if byte == bytes.BYTE_RIGHTBRACKET then
return tokens.TOKEN_STRING, pos + 1
end
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
byte = stringbyte(text, pos + 1)
if byte == bytes.BYTE_LEFTBRACKET then
return nextBracketString(text, pos + 2)
end
return tokens.TOKEN_LEFTBRACKET, pos + 1
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
-- just for testing
--[[
function testTokenizer()
local str = ""
for line in io.lines("newindent.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
]]
-- 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(a)
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 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 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
--[[
function testIndenter(i)
local str = ""
for line in io.lines("test.lua") do
str = str .. line .. "\n"
end
local colorTable = {}
colorTable[0] = "</c>"
colorTable[indenterTokens.TOKEN_IDENTIFIER] = "<c i>"
colorTable[indenterTokens.TOKEN_COMMENT_SHORT] = "<c sc>"
colorTable["function"] = "<c f>"
print(indentCode(str, 4, colorTable, i))
end
--]]
-- WoW specific code:
-- 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 = editbox:oldGetText()
if stringlen(text) > 0 then
editbox:oldSetText(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 = editbox:oldGetText()
editbox:Insert("\1")
local pos = stringfind(editbox:oldGetText(), "\1", 1, 1)
editbox:oldSetText(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
--[[ Not working
function lib.stripWowColors(code)
tableclear(workingTable)
local tsize = 0
local pos = 1
while true do
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
-- strip these
else
tsize = tsize + 1
workingTable[tsize] = stringsub(code, pos, nextPos - 1)
end
pos = nextPos
end
return table.concat(workingTable)
end
--]]
function lib.stripWowColors(code)
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
end
function lib.encode(code)
if code then
code = stringgsub(code, "|", "||")
end
return code
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
local decodeCache = {}
setmetatable(decodeCache, {__mode = "v"})
local editboxStringCache = {}
local editboxNumLinesCache = {}
setmetatable(editboxStringCache, {__mode = "v"})
setmetatable(editboxNumLinesCache, {__mode = "v"})
function lib.colorCodeEditbox(editbox, colorTable)
local orgCode = editbox:oldGetText()
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)
editboxStringCache[editbox] = newCode
if orgCode ~= newCode then
local script, script2 = critical_enter(editbox)
decodeCache[editbox] = nil
editbox:oldSetText(newCode)
if newPos then
if newPos < 0 then newPos = 0 end
local stringlenNewCode = stringlen(newCode)
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, 4, colorTable)
end
editboxNumLinesCache[editbox] = numLines
end
local editboxIndentCache = {}
setmetatable(editboxIndentCache, {__mode = "v"})
function lib.indentEditbox(editbox, tabWidth, colorTable)
local orgCode = editbox:oldGetText()
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)
editboxIndentCache[editbox] = newCode
if code ~= newCode then
local script, script2 = critical_enter(editbox)
decodeCache[editbox] = nil
editbox:oldSetText(newCode)
if newPos then
if newPos < 0 then newPos = 0 end
local stringlenNewCode = stringlen(newCode)
if newPos > stringlenNewCode then newPos = stringlenNewCode end
setCaretPos(editbox, newPos)
end
critical_leave(editbox, script, script2)
end
end
local function hookHandler(frame, handler, newFun)
local oldFun = frame:GetScript(handler)
if oldFun then
frame:SetScript(handler, function() newFun() return oldFun() end)
else
frame:SetScript(handler, newFun)
end
end
local function colorCodeFun(editbox, colorTable)
return function()
decodeCache[editbox] = nil
return lib.colorCodeEditbox(editbox, colorTable)
end
end
local function indentFun(editbox, colorTable)
return function()
return lib.indentEditbox(editbox, 4, colorTable)
end
end
local function newGetText(editbox)
local decoded = decodeCache[editbox]
if not decoded then
decoded = lib.decode(editbox:oldGetText())
decodeCache[editbox] = decoded
end
return decoded
end
local function newSetText(editbox, text)
decodeCache[editbox] = nil
if text then
return editbox:oldSetText(lib.encode(text))
end
end
local hookedFrames = {}
function lib.addSmartCode(editbox, colorTable)
if hookedFrames[editbox] then
return
end
if not colorTable then
colorTable = lib.defaultColorTable
end
hookedFrames[editbox] = 1
local colorCodeFun = colorCodeFun(editbox, colorTable)
local indentFun = indentFun(editbox, colorTable)
hookHandler(editbox, "OnTextChanged", colorCodeFun)
hookHandler(editbox, "OnTabPressed", indentFun)
editbox.oldGetText = editbox.GetText
editbox.oldSetText = editbox.SetText
editbox.GetText = newGetText
editbox.SetText = newSetText
end
local 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