-- 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] = "" colorTable[indenterTokens.TOKEN_IDENTIFIER] = "" colorTable[indenterTokens.TOKEN_COMMENT_SHORT] = "" colorTable["function"] = "" 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