iup-stack/iup/srcluaconsole/indent.lua

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