You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

280 lines
8.6 KiB

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...) ---@type TSM
local SyntaxHelpers = TSM.Init("UI.SyntaxHelpers") ---@class UI.SyntaxHelpers
local CustomPrice = TSM.Include("Service.CustomPrice")
local Theme = TSM.Include("Util.Theme")
local Money = TSM.Include("Util.Money")
local CustomString = TSM.Include("Util.CustomString")
local private = {
result = {},
resultLen = nil,
pos = nil,
cursor = nil,
newCursor = nil,
prevTokenLength = nil,
prevTokenColored = nil,
tokenList = CustomString.CreateTokenList(),
tokenListRow = nil,
}
local NEWLINE_CHAR = "\n"
local INDENT_STR = " "
local TEXT_COLOR_SUFFIX = "|r"
local DIVIDER_COLOR = "GROUP_ONE"
local MATH_COLOR = "GROUP_FOUR"
local FUNCTION_COLOR = "GROUP_THREE"
local SOURCE_COLOR = "GROUP_TWO"
local CUSTOM_SOURCE_COLOR = "GROUP_FIVE"
local NUMBER_COLOR = "TEXT"
local UNKNOWN_COLOR = "FEEDBACK_RED"
-- ============================================================================
-- Module Functions
-- ============================================================================
function SyntaxHelpers.StripWhitespace(text)
text = gsub(text, "[\n\r ]", "")
return text
end
function SyntaxHelpers.StripColors(value)
value = gsub(value, "|c%x%x%x%x%x%x%x%x", "")
value = gsub(value, TEXT_COLOR_SUFFIX, "")
return value
end
function SyntaxHelpers.SetAutoIndent(text)
assert(#private.result == 0)
local writeIndex = 1
for readIndex = 1, #text do
local char = strsub(text, readIndex, readIndex)
local nextChar = strsub(text, readIndex + 1, readIndex + 1)
local shouldAddNewline = nil
if (char == "(" or char == ",") and nextChar ~= "\n" then
shouldAddNewline = true
elseif char ~= "\n" and char ~= " " and nextChar == ")" then
shouldAddNewline = true
elseif char == ")" and nextChar ~= "\n" and nextChar ~= "," then
shouldAddNewline = true
else
shouldAddNewline = false
end
if shouldAddNewline then
tinsert(private.result, strsub(text, writeIndex, readIndex))
tinsert(private.result, NEWLINE_CHAR)
writeIndex = readIndex + 1
end
end
tinsert(private.result, strsub(text, writeIndex))
local result = table.concat(private.result)
wipe(private.result)
return result
end
function SyntaxHelpers.SetSyntaxColor(text, cursor)
private.PrepareContext(private.StripColorsAtPos(text, cursor))
private.SetSyntaxColor()
return private.ClearContextAndReturnResult()
end
function SyntaxHelpers.SetIndent(text, cursor)
private.PrepareContext(private.StripColorsAtPos(text, cursor))
private.SetIndent()
return private.ClearContextAndReturnResult()
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.PrepareContext(text, cursor)
assert(#private.result == 0)
assert(private.tokenList:GetNumRows() == 0)
private.pos = 1
private.cursor = cursor
private.newCursor = nil
private.prevTokenLength = 0
private.prevTokenColored = false
private.resultLen = 0
CustomString.PopulateTokenList(private.tokenList, strlower(text))
private.tokenListRow = 1
end
function private.ClearContextAndReturnResult()
local result = table.concat(private.result)
wipe(private.result)
private.tokenList:Wipe()
return result, private.newCursor
end
function private.SetSyntaxColor()
while true do
private.UpdateNewCursor()
local tokenType, tokenStr = private.NextToken()
if not tokenType then
break
end
private.prevTokenColored = false
if tokenType == CustomString.TOKEN_TYPE.NEWLINE or tokenType == CustomString.TOKEN_TYPE.WHITESPACE then
private.InsertResult(tokenStr)
else
private.InsertColoredToken(tokenType, tokenStr)
end
private.prevTokenLength = #tokenStr
end
end
function private.SetIndent()
local newCursorPosFinalized = false
local level = 0
local hitNonWhitespace = false
local hitIndentRight = false
local preIndent = 0
local postIndent = 0
local bufferStartIndex = 1
while true do
private.UpdateNewCursor()
private.prevTokenColored = false
private.prevTokenLength = 0
local tokenType, tokenStr = private.NextToken()
if not tokenType or tokenType == CustomString.TOKEN_TYPE.NEWLINE then
level = max(level + preIndent, 0)
local indentStr = strrep(INDENT_STR, level)
private.InsertResult(indentStr, bufferStartIndex)
if private.newCursor and not newCursorPosFinalized then
private.newCursor = private.newCursor + #indentStr
newCursorPosFinalized = true
end
if not tokenType then
break
end
private.InsertResult(tokenStr)
bufferStartIndex = #private.result + 1
level = max(level + postIndent, 0)
hitNonWhitespace = false
hitIndentRight = false
preIndent = 0
postIndent = 0
elseif tokenType ~= CustomString.TOKEN_TYPE.WHITESPACE then
hitNonWhitespace = true
private.prevTokenLength = #tokenStr
if tokenType == CustomString.TOKEN_TYPE.LEFT_PAREN then
if hitIndentRight then
postIndent = postIndent + 1
else
hitIndentRight = true
postIndent = postIndent + 1
end
elseif tokenType == CustomString.TOKEN_TYPE.RIGHT_PAREN then
if hitIndentRight then
postIndent = postIndent - 1
else
preIndent = preIndent - 1
end
end
private.InsertColoredToken(tokenType, tokenStr)
elseif hitNonWhitespace then
private.InsertResult(tokenStr)
private.prevTokenLength = #tokenStr
end
end
end
function private.StripColorsAtPos(text, pos)
local left = SyntaxHelpers.StripColors(strsub(text, 1, pos))
local right = SyntaxHelpers.StripColors(strsub(text, pos + 1))
return left..right, #left + 1
end
function private.UpdateNewCursor()
if private.newCursor or private.pos < private.cursor then
return
end
private.newCursor = private.resultLen
if private.pos ~= private.cursor then
local diff = private.pos - private.cursor
if diff > private.prevTokenLength then
diff = private.prevTokenLength
end
if private.prevTokenColored then
diff = diff + #TEXT_COLOR_SUFFIX
end
private.newCursor = private.newCursor - diff
end
end
function private.NextToken()
if private.tokenListRow > private.tokenList:GetNumRows() then
return
end
local tokenType, tokenStr = private.tokenList:GetRow(private.tokenListRow)
private.tokenListRow = private.tokenListRow + 1
private.pos = private.pos + #tokenStr
return tokenType, tokenStr
end
function private.InsertColoredToken(tokenType, str)
local color = nil
if tokenType == CustomString.TOKEN_TYPE.NUMBER or tokenType == CustomString.TOKEN_TYPE.NEGATIVE_OPERATOR then
color = NUMBER_COLOR
elseif tokenType == CustomString.TOKEN_TYPE.MONEY then
color = NUMBER_COLOR
local symbol = strsub(str, -1)
local coloredSymbol = nil
if symbol == "g" then
coloredSymbol = Money.GetGoldText()
elseif symbol == "s" then
coloredSymbol = Money.GetSilverText()
elseif symbol == "c" then
coloredSymbol = Money.GetCopperText()
else
error("Unexpected currency symbol: "..symbol)
end
private.InsertResult(strsub(str, 1, -2))
private.InsertResult(coloredSymbol)
private.prevTokenColored = true
return
elseif tokenType == CustomString.TOKEN_TYPE.MATH_OPERATOR then
color = MATH_COLOR
elseif tokenType == CustomString.TOKEN_TYPE.LEFT_PAREN or tokenType == CustomString.TOKEN_TYPE.RIGHT_PAREN or tokenType == CustomString.TOKEN_TYPE.COMMA then
color = DIVIDER_COLOR
elseif tokenType == CustomString.TOKEN_TYPE.IDENTIFIER or tokenType == CustomString.TOKEN_TYPE.FUNCTION then
if CustomPrice.IsMathFunction(str) then
color = FUNCTION_COLOR
elseif CustomPrice.IsSource(str) then
color = SOURCE_COLOR
elseif CustomPrice.IsCustomSource(str) then
color = CUSTOM_SOURCE_COLOR
elseif strlower(str) == "baseitem" or strmatch(str, "^[ip]:%d+") then
color = CUSTOM_SOURCE_COLOR
else
color = UNKNOWN_COLOR
end
elseif tokenType == CustomString.TOKEN_TYPE.UNKNOWN then
color = UNKNOWN_COLOR
else
error("Unexpected token: "..tostring(tokenType))
end
private.prevTokenColored = true
local prefix = Theme.GetColor(color):GetTextColorPrefix()
private.InsertResult(prefix)
private.InsertResult(str)
private.InsertResult(TEXT_COLOR_SUFFIX)
end
function private.InsertResult(str, index)
if index then
tinsert(private.result, index, str)
else
tinsert(private.result, str)
end
private.resultLen = private.resultLen + #str
end