897 lines
31 KiB
Lua
Executable File
897 lines
31 KiB
Lua
Executable File
-- For All Indents And Purposes
|
|
local revision = 17
|
|
-- Original author: kristofer.karlsson@gmail.com
|
|
-- IUP adaptation: pixel@grumpycoder.net
|
|
|
|
-- For All Indents And Purposes -
|
|
-- a indentation + syntax highlighting library
|
|
-- All valid lua code should be processed correctly.
|
|
|
|
-- Usage
|
|
--------
|
|
-- hook the textboxes 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
|
|
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(">")
|
|
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
|
|
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
|
|
|
|
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
|
|
|
|
local defaultColorTable
|
|
|
|
function lib.colorCodeCode(code, colorTable)
|
|
local stopColor = colorTable and colorTable[0]
|
|
if not stopColor then
|
|
return {}, 0
|
|
end
|
|
|
|
local formatTable = {}
|
|
local totalLen = 0
|
|
|
|
local numLines = 0
|
|
local prevTokenWasColored = false
|
|
local prevTokenWidth = 0
|
|
|
|
local pos = 1
|
|
local level = 0
|
|
|
|
while true do
|
|
prevTokenWasColored = false
|
|
prevTokenWidth = 0
|
|
|
|
local tokenType, nextPos = nextToken(code, pos)
|
|
|
|
if not tokenType then
|
|
break
|
|
end
|
|
|
|
if 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
|
|
|
|
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] or defaultColorTable[str]
|
|
if not color then
|
|
color = colorTable[tokenType] or defaultColorTable[tokenType]
|
|
if not color then
|
|
if tokenType == tokens.TOKEN_IDENTIFIER then
|
|
color = colorTable[tokens.TOKEN_IDENTIFIER] or defaultColorTable[tokens.TOKEN_IDENTIFIER]
|
|
else
|
|
color = colorTable[tokens.TOKEN_SPECIAL] or defaultColorTable[tokens.TOKEN_SPECIAL]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if color then
|
|
table.insert(formatTable, { pos = totalLen, val = color })
|
|
totalLen = totalLen + (nextPos - pos)
|
|
table.insert(formatTable, { pos = totalLen, val = stopColor })
|
|
prevTokenWasColored = true
|
|
else
|
|
totalLen = totalLen + stringlen(str)
|
|
end
|
|
end
|
|
|
|
pos = nextPos
|
|
end
|
|
return formatTable, 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 formatTable = {}
|
|
|
|
|
|
local stopColor = colorTable and colorTable[0]
|
|
|
|
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
|
|
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
|
|
if stringsub(v, 1, 2) == "|c" then
|
|
table.insert(formatTable, { pos = totalLen, val = stringsub(v, 3) })
|
|
else
|
|
tsize = tsize + 1
|
|
workingTable[tsize] = v
|
|
totalLen = totalLen + stringlen(v)
|
|
end
|
|
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_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] or defaultColorTable[str]
|
|
if not color then
|
|
color = colorTable[tokenType] or defaultColorTable[tokenType]
|
|
if not color then
|
|
if tokenType == tokens.TOKEN_IDENTIFIER then
|
|
color = colorTable[tokens.TOKEN_IDENTIFIER] or defaultColorTable[tokens.TOKEN_IDENTIFIER]
|
|
else
|
|
color = colorTable[tokens.TOKEN_SPECIAL] or defaultColorTable[tokens.TOKEN_SPECIAL]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if color then
|
|
tsize2 = tsize2 + 1
|
|
workingTable2[tsize2] = "|c" .. color
|
|
|
|
tsize2 = tsize2 + 1
|
|
workingTable2[tsize2] = str
|
|
totalLen2 = totalLen2 + nextPos - pos
|
|
|
|
tsize2 = tsize2 + 1
|
|
workingTable2[tsize2] = "|c" .. stopColor
|
|
|
|
prevTokenWasColored = true
|
|
else
|
|
tsize2 = tsize2 + 1
|
|
workingTable2[tsize2] = str
|
|
totalLen2 = totalLen2 + nextPos - pos
|
|
|
|
end
|
|
end
|
|
pos = nextPos
|
|
end
|
|
return table.concat(workingTable), formatTable, newCaretPosition
|
|
end
|
|
|
|
local defaultTabWidth = 4
|
|
|
|
function lib.textboxReindent(textbox)
|
|
textbox.indenting = true
|
|
local prevPos, prevColor, v, newCaretPos, formatTable = 0, "0"
|
|
textbox.value, formatTable, newCaret = lib.indentCode(textbox.value, textbox.tabWidth + 0, textbox.colorTable, textbox.caretpos + 0)
|
|
local tags = iup.user { bulk = "Yes", cleanout = "Yes" }
|
|
for i = 1, #formatTable do
|
|
local v = formatTable[i]
|
|
if prevPos ~= v.pos and prevColor ~= "0" then
|
|
iup.Append(tags, iup.user { selectionpos = prevPos .. ":" .. v.pos, fgcolor = prevColor })
|
|
end
|
|
prevPos, prevColor = v.pos, v.val
|
|
end
|
|
textbox.addformattag = tags
|
|
textbox.caretpos = newCaret
|
|
textbox.indenting = nil
|
|
end
|
|
|
|
function lib.textboxRecolor(textbox)
|
|
if textbox.indenting then return end
|
|
textbox.indenting = true
|
|
local prevPos, prevColor, oldCaret, v, formatTable = 0, "0", textbox.caretpos
|
|
formatTable = lib.colorCodeCode(textbox.value, textbox.colorTable)
|
|
local tags = iup.user { bulk = "Yes", cleanout = "Yes" }
|
|
for i = 1, #formatTable do
|
|
local v = formatTable[i]
|
|
if prevPos ~= v.pos and prevColor ~= "0" then
|
|
iup.Append(tags, iup.user { selectionpos = prevPos .. ":" .. v.pos, fgcolor = prevColor })
|
|
end
|
|
prevPos, prevColor = v.pos, v.val
|
|
end
|
|
textbox.addformattag = tags
|
|
textbox.caretpos = oldCaret
|
|
textbox.indenting = nil
|
|
end
|
|
|
|
function lib.enable(textbox, colorTable, tabWidth)
|
|
if not colorTable then colorTable = defaultColorTable end
|
|
if not tabWidth then tabWidth = defaultTabWidth end
|
|
|
|
textbox.colorTable = colorTable
|
|
textbox.tabWidth = tabWidth
|
|
textbox.formatting = "Yes"
|
|
textbox.multiline = "Yes"
|
|
|
|
textbox.timer = iup.timer
|
|
{
|
|
action_cb = function(timer)
|
|
timer.run = "No"
|
|
-- TODO: optimize this to recolor only in the changed lines
|
|
lib.textboxRecolor(textbox)
|
|
end,
|
|
time = 800
|
|
}
|
|
|
|
-- AVOID: redefinition of common callbacks
|
|
-- textbox.map_cb = function(textbox)
|
|
-- lib.textboxRecolor(textbox)
|
|
-- end
|
|
|
|
textbox.valuechanged_cb = function(textbox)
|
|
textbox.timer.run = "No" -- stop to start again if already running
|
|
textbox.timer.run = "Yes"
|
|
end
|
|
|
|
-- AVOID: redefinition of common callbacks
|
|
-- textbox.k_any = function(textbox, key)
|
|
-- if key == iup.K_cF then
|
|
-- lib.textboxReindent(textbox)
|
|
-- return IUP_IGNORE
|
|
-- end
|
|
-- return IUP_CONTINUE
|
|
-- end
|
|
end
|
|
|
|
defaultColorTable = {}
|
|
lib.defaultColorTable = defaultColorTable
|
|
defaultColorTable[tokens.TOKEN_SPECIAL] = "0 0 192"
|
|
defaultColorTable[tokens.TOKEN_KEYWORD] = "0 0 255"
|
|
defaultColorTable[tokens.TOKEN_COMMENT_SHORT] = "0 128 0"
|
|
defaultColorTable[tokens.TOKEN_COMMENT_LONG] = "0 128 0"
|
|
|
|
local stringColor = "128 128 128"
|
|
defaultColorTable[tokens.TOKEN_STRING] = stringColor
|
|
defaultColorTable[".."] = "0 0 128"
|
|
|
|
local tableColor = "0 0 192"
|
|
defaultColorTable["..."] = tableColor
|
|
defaultColorTable["{"] = tableColor
|
|
defaultColorTable["}"] = tableColor
|
|
defaultColorTable["["] = tableColor
|
|
defaultColorTable["]"] = tableColor
|
|
|
|
local arithmeticColor = "0 0 192"
|
|
defaultColorTable[tokens.TOKEN_NUMBER] = "255 128 0"
|
|
defaultColorTable["+"] = arithmeticColor
|
|
defaultColorTable["-"] = arithmeticColor
|
|
defaultColorTable["/"] = arithmeticColor
|
|
defaultColorTable["*"] = arithmeticColor
|
|
|
|
local logicColor1 = "0 0 192"
|
|
defaultColorTable["=="] = logicColor1
|
|
defaultColorTable["<"] = logicColor1
|
|
defaultColorTable["<="] = logicColor1
|
|
defaultColorTable[">"] = logicColor1
|
|
defaultColorTable[">="] = logicColor1
|
|
defaultColorTable["~="] = logicColor1
|
|
|
|
local logicColor2 = "0 0 255"
|
|
defaultColorTable["and"] = logicColor2
|
|
defaultColorTable["or"] = logicColor2
|
|
defaultColorTable["not"] = logicColor2
|
|
|
|
defaultColorTable[0] = "0"
|
|
end
|