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.

779 lines
28 KiB

local MAJ, REV, COMPAT, _, T = 1, 3, select(4,GetBuildInfo()), ...
if T.SkipLocalActionBook then return end
local EV, AB, RW = T.Evie, T.ActionBook:compatible(2,34), T.ActionBook:compatible("Rewire", 1,27)
assert(EV and AB and RW and 1, "Incompatible library bundle")
local MODERN, CF_WRATH, CI_ERA = COMPAT >= 10e4, COMPAT < 10e4 and COMPAT >= 3e4, COMPAT < 2e4
local IM, L, CreateEdge = {}, T.ActionBook.L, T.CreateEdge
local function assert(condition, text, level, ...)
return condition or error(tostring(text):format(...), 1 + (level or 1))((0)[0])
end
local commandType, addCommandType = {["#show"]=0, ["#showtooltip"]=0, ["#imp"]=-1} do
function addCommandType(slashToken, ct)
local idx, s = 1
while 1 do
s = _G["SLASH_" .. slashToken .. idx]
if not s then break end
commandType[s], idx = commandType[s] or ct, idx + 1
end
end
for n, ct in ("CAST:1 USE:1 CASTSEQUENCE:2 CASTRANDOM:3 USERANDOM:3"):gmatch("(%a+):(%d+)") do
addCommandType(n, ct+0)
end
end
local toMacroText, quantizeMacro, formatMacro, formatToken, setMountPreference do
local genParser do
local doRewrite, replaceFunc, critFail, critLine
local function replaceAlternatives(ctype, args)
local ret, alt2, rfCtx
for alt, cpos in (args .. ","):gmatch("(.-),()") do
alt2, rfCtx = replaceFunc(ctype, alt, rfCtx, args, cpos)
if alt == alt2 or (alt2 and alt2:match("%S")) then
if doRewrite then
alt2 = alt2:match("^%s*(.-)%s*$")
ret = ret and ret .. ", " .. alt2 or alt2
else
ret = ret and ret .. "," .. alt2 or alt2
end
end
end
return ret
end
local function hasTautCondition(cond)
return (cond:match("%[%s*%]") or cond:match("%[%s*@[^,]*%]")) and true
end
local function procImpOptions(args)
if not doRewrite then
return
end
if args:match("^%s*critical%s*$") then
critLine = true
end
return ""
end
local function procLine(commandPrefix, nlc, command, args)
if critFail or (nlc ~= "" and nlc ~= "\n") then return end
local ctype = commandType[command:lower()]
if ctype == -1 then
return procImpOptions(args)
elseif not ctype then
return
end
local isCritical, pos, len1, ret = critLine and ctype > 0, 1, #args+1
critLine = critLine and not isCritical
repeat
local cstart, cend, vend = pos
repeat
local ce, cc = args:match("()([%[;])", pos)
if cc == "[" then
pos = args:match("%]()", ce)
else
ce = ce or len1
cend, vend, pos = pos, ce-1, ce + 1
end
until cend or not pos
if not pos then return end
local cval = args:sub(cend, vend)
if ctype < 2 then
cval = replaceFunc(ctype, args:sub(cend, vend))
else
local val, reset = args:sub(cend, vend)
if ctype == 2 then
local r, r2, v = val:match("^(%s*(reset=%S+)%s*)(.*)")
reset, val = r and (doRewrite and r2 .. " " or r), v or val
end
val = replaceAlternatives(ctype, val)
cval = val and ((reset or "") .. val) or nil
end
if cval or ctype == 0 then
local cond = cstart < cend and args:sub(cstart, cend-1)
if doRewrite then
cond = cond and cond:match("^%s*(.-)%s*$")
cval = cval and cval:match("^%s*(.-)%s*$")
cond = cond and cval and (cond .. " ") or cond
end
ret = ret and (ret .. (doRewrite and "; " or ";")) or commandPrefix
ret = ret .. (cond or "") .. (cval or "")
if doRewrite and (not cond or hasTautCondition(cond)) then
break
end
end
until pos > len1
critFail = critFail or isCritical and (ret or "") == ""
return ret or ""
end
function genParser(pReplaceFunc)
return function(aDoRewrite, text)
doRewrite, replaceFunc, critFail, critLine = aDoRewrite, pReplaceFunc
text = text:gsub("((.?)([#/]%S+) ?)([^\n]*)", procLine)
return critFail and "" or text
end
end
end
local function replaceSpellID(ctype, sidlist, prefix, tk)
local noEscapes, sr, ar = ctype == 2
for id, sn in sidlist:gmatch("%d+") do
id = id + 0
sn, sr = GetSpellInfo(id), GetSpellSubtext(id)
ar = GetSpellSubtext(sn)
local isCastable, castFlag = RW:IsSpellCastable(id, noEscapes)
if not MODERN and not isCastable and tk ~= "spellr" then
local id2 = select(7,GetSpellInfo(sn))
if id2 then
id, isCastable, castFlag = id2, RW:IsSpellCastable(id2, noEscapes)
end
end
if isCastable then
if castFlag == "forced-id-cast" and (ctype == 1 or ctype == 3) then
sn = "spell:" .. id
elseif ctype == 3 and sn and sn:match(",") then
sn = "spell:" .. id
elseif sr and sr ~= "" and (MODERN or tk == "spellr") then
sn = sn .. "(" .. sr .. ")"
elseif tk == "spell" and not MODERN and ar ~= sr and ar then
sn = sn .. "(" .. ar .. ")"
end
return prefix .. sn
end
end
end
local replaceMountTag do -- +setMountPreference
local skip, gmSid, gmPref, fmSid, fmPref, drSid, drPref = {[44153]=1, [44151]=1, [61451]=1, [75596]=1, [61309]=1, [169952]=1, [171844]=1, [213339]=1,}
local function IsKnownSpell(sid)
local sn, sr = GetSpellInfo(sid or 0), GetSpellSubtext(sid or 0)
return GetSpellInfo(sn, sr) ~= nil and sid or (RW:GetCastEscapeAction(sn) and sid)
end
local function findMount(prefSID, mtype, ctype)
local wantDragonriding, escapeContext = mtype == 402, ctype == 2 and 0 or 1
local idm, myFactionId, nc, cs = C_MountJournal.GetMountIDs(), UnitFactionGroup("player") == "Horde" and 0 or 1, 0
local gmi, gmiex = C_MountJournal.GetMountInfoByID, C_MountJournal.GetMountInfoExtraByID
for i=1, #idm do
i = idm[i]
local _1, sid, _3, active, _5, _6, _7, factionLocked, factionId, hide, have, _12, isDragonriding = gmi(i)
if have and not hide
and (not factionLocked or factionId == myFactionId)
and RW:IsSpellCastable(sid, escapeContext)
then
local _, _, _, _, t = gmiex(i)
local isTypeMatch = t == mtype or (wantDragonriding and isDragonriding)
if sid == prefSID or (active and isTypeMatch and prefSID == nil) then
return sid
elseif isTypeMatch and not skip[sid] then
nc = nc + 1
if math.random(1,nc) == 1 then
cs = sid
end
end
end
end
return cs
end
function replaceMountTag(ctype, tag, prefix)
if not MODERN then
elseif tag == "ground" then
gmSid = gmSid and IsKnownSpell(gmSid) or findMount(gmPref or gmSid, 230, ctype)
return replaceSpellID(ctype, tostring(gmSid), prefix)
elseif tag == "air" then
fmSid = fmSid and IsKnownSpell(fmSid) or findMount(fmPref or fmSid, 248, ctype)
return replaceSpellID(ctype, tostring(fmSid), prefix)
elseif tag == "dragon" then
drSid = drSid and IsKnownSpell(drSid) or findMount(drPref or drSid, 402, ctype)
return replaceSpellID(ctype, tostring(drSid), prefix)
end
return nil
end
local function editPreference(orig, new)
return type(new) == "number" and new or new ~= false and orig or nil
end
function setMountPreference(groundSpellID, flyingSpellID, dragonSpellID)
gmPref = editPreference(gmPref, groundSpellID)
fmPref = editPreference(fmPref, flyingSpellID)
drPref = editPreference(drPref, dragonSpellID)
return gmPref, fmPref, drPref
end
end
toMacroText = genParser(function(ctype, value)
local prefix, tkey, tval = value:match("^%s*(!?){{(%a+):([%a%d/]+)}}%s*$")
if tkey == "spell" or tkey == "spellr" then
return replaceSpellID(ctype, tval, prefix, tkey)
elseif tkey == "mount" then
return replaceMountTag(ctype, tval, prefix)
elseif value:match('^%s*!?|Hiptok|h|h%s*$') then
return '-'
end
return value
end)
local toImpText, prepareQuantizer do
local spells, specialTokens, OTHER_SPELL_IDS = {}, {}, {150544, 243819}
local abTokens = {["Ground Mount"]="{{mount:ground}}", ["Flying Mount"]="{{mount:air}}", ["Dragonriding Mount"]="{{mount:dragon}}"}
toImpText = genParser(function(ctype, value, ctx, args, cpos)
if type(ctx) == "number" and ctx > 0 then
return nil, ctx-1
end
local noEscapes, cc, pre, name, tws = ctype == 2, 0, value:match("^(%s*!?)(.-)(%s*)$")
repeat
local lowname = name:lower()
local sid, peek, cnpos = spells[lowname]
if sid and noEscapes and RW:IsCastEscape(lowname, true) then
-- Don't tokenize escapes in contexts they wont't work in
elseif sid then
if not MODERN then
local rname = name:gsub("%s*%([^)]+%)$", "")
local sid2 = rname ~= name and spells[rname:lower()]
if sid2 then
return (pre .. "{{spellr:" .. sid .. "}}" .. tws), cc
end
end
return (pre .. "{{spell:" .. sid .. "}}" .. tws), cc
elseif specialTokens[lowname] then
return pre .. specialTokens[lowname] .. tws, cc
elseif name:match("^{{.*}}$") then
return pre .. name .. tws, cc
end
if ctype >= 2 and args then
peek, cnpos = args:match("^([^,]+),?()", cpos)
if peek then
cc, cpos, name, tws = cc + 1, cnpos, (name .. "," .. peek):match("^(.-)(%s*)$")
end
end
until not peek or cc > 5
return value
end)
local function addModernSpells()
local gmi, idm = C_MountJournal.GetMountInfoByID, C_MountJournal.GetMountIDs()
for i=1, #idm do
local _, sid = gmi(idm[i])
local sname = GetSpellInfo(sid)
if sname then
spells[sname:lower()] = sid
end
end
local cid = C_ClassTalents.GetActiveConfigID()
if not cid then
local spec = GetSpecializationInfo(GetSpecialization())
local cc = C_ClassTalents.GetConfigIDsBySpecID(spec)
cid = cc and cc[1]
end
local conf = cid and C_Traits.GetConfigInfo(cid)
local tree = conf and conf.treeIDs and conf.treeIDs[1]
local nodes = tree and C_Traits.GetTreeNodes(tree)
for i=1,nodes and #nodes or 0 do
local node = C_Traits.GetNodeInfo(cid, nodes[i])
for i=1,#node.entryIDs do
local entry = C_Traits.GetEntryInfo(cid, node.entryIDs[i])
local def = C_Traits.GetDefinitionInfo(entry.definitionID)
local sid = def and def.spellID and not IsPassiveSpell(def.spellID) and def.spellID
if sid then
local name, name2 = GetSpellInfo(sid), def.overrideName
if name then
spells[name:lower()] = sid
end
if name2 and name2 ~= name then
spells[name2:lower()] = sid
end
end
end
end
end
local function addSpell(n, id, allowGenericOverwrite)
local nl, sr, k = n:lower(), GetSpellSubtext(id)
spells[nl] = allowGenericOverwrite and id or spells[nl] or id
if sr and sr ~= "" then
k = nl .. "(" .. sr:lower() .. ")"; spells[k] = spells[k] or id
k = nl .. " (" .. sr:lower() .. ")"; spells[k] = spells[k] or id
end
end
local function addSpellBookTab(ofs, c, allowGenericOverwrite)
for j=ofs+1,ofs+c do
local n, st, id = GetSpellBookItemName(j, "spell"), GetSpellBookItemInfo(j, "spell")
if type(n) ~= "string" or not id then
elseif st == "SPELL" or st == "FUTURESPELL" then
addSpell(n, id, allowGenericOverwrite and st == "SPELL")
elseif st == "FLYOUT" then
for j=1,select(3,GetFlyoutInfo(id)) do
local sid, _, _, sname = GetFlyoutSlotInfo(id, j)
if sid and type(sname) == "string" then
addSpell(sname, sid)
end
end
end
end
end
function prepareQuantizer(skipCacheRefresh)
if skipCacheRefresh and next(spells) then return end
wipe(spells)
wipe(specialTokens)
for i=1,#OTHER_SPELL_IDS do
local sn = GetSpellInfo(OTHER_SPELL_IDS[i])
if sn then
spells[sn:lower()] = OTHER_SPELL_IDS[i]
end
end
if MODERN then
addModernSpells()
local L = T.ActionBook.L
for k, tok in pairs(abTokens) do
specialTokens[k:lower()], specialTokens[L(k):lower()] = tok, tok
end
elseif CF_WRATH then
for k=1,2 do
k = k == 1 and "MOUNT" or "CRITTER"
for i=1,GetNumCompanions(k) do
local _, _, sid = GetCompanionInfo(k, i)
local sn = GetSpellInfo(sid)
if sn then
addSpell(sn, sid)
end
end
end
end
for curSpec=0,1 do
for i=GetNumSpellTabs()+12,1,-1 do
local _, _, ofs, c, _, sid = GetSpellTabInfo(i)
if ((curSpec == 0) == (sid == 0)) then
addSpellBookTab(ofs, c, not MODERN)
end
end
end
end
end
function quantizeMacro(macro, skipCacheRefresh)
if type(macro) ~= "string" then
return macro
end
prepareQuantizer(skipCacheRefresh)
return toImpText(false, macro)
end
do -- formatMacro/formatToken
local formatTokenInner do
local names, tag, mountTokens = {}, 0, {
ground="|cff71d5ff|Hiltmount:ground|h" .. L"Ground Mount" .. "|h|r",
air ="|cff71d5ff|Hiltmount:air|h" .. L"Flying Mount" .. "|h|r",
dragon="|cff71d5ff|Hiltmount:dragon|h" .. L"Dragonriding Mount" .. "|h|r",
}
function formatTokenInner(token, targ)
if token == "spell" or token == "spellr" then
local forceRank, tname = token == "spellr"
tag = tag + 1
for id in targ:gmatch("%d+") do
id = id+0
local name, sr = GetSpellInfo(id), GetSpellSubtext(id)
if sr and sr ~= "" and (forceRank or MODERN) then
name = name .. "(" .. sr .. ")"
end
if name and names[name] ~= tag then
names[name], tname = tag, tname and (tname .. " / " .. name) or name
end
end
if tname then
return "|cff71d5ff|Hilt" .. token .. ":" .. targ .. "|h" .. tname .. "|h|r"
end
elseif token == "mount" then
return mountTokens[targ]
end
return '{{' .. token .. ':' .. targ .. '}}'
end
end
local toUIText = genParser(function(_ctype, value)
local prefix, token, targ, suf = value:match("^(%s*!?){{(%a+):([%a%d/]+)}}(%s*)$")
local v = token and formatTokenInner(token, targ)
return v and (prefix .. v .. suf) or value
end)
local linkTag = 0
local function tagLinks(p)
linkTag = linkTag + 1
return p .. linkTag .. ":"
end
function formatToken(lastLink, tk, targ)
linkTag, tk = lastLink or 0, formatTokenInner(tk, targ)
tk = tk:gsub("%f[|]|Hilt", tagLinks)
return tk, linkTag
end
function formatMacro(macro)
if type(macro) == "string" then
linkTag, macro = 0, toUIText(false, macro)
macro = macro:gsub("%f[|]|Hilt", tagLinks)
end
return macro, linkTag
end
end
end
local encodeMacro, decodeMacro do
local skipCacheRefresh
local function encodeSlash(nl, command, lead)
if nl ~= "\n" and nl ~= "" then
return
elseif lead == "!" then
return nl .. "!" .. command
end
local cu = command:upper()
if not (skipCacheRefresh or next(SlashCmdList) == nil) then
skipCacheRefresh = true
ChatFrame_ImportListToHash(SlashCmdList, hash_SlashCmdList)
end
if type(hash_ChatTypeInfoList[cu]) == "string" and not hash_ChatTypeInfoList[cu]:match("!") then
return nl .. "!" .. hash_ChatTypeInfoList[cu] .. "!" .. command
elseif type(hash_EmoteTokenList[cu]) == "string" and not hash_EmoteTokenList[cu]:match("!") then
return nl .. "!" .. hash_EmoteTokenList[cu] .. "!" .. command
end
end
local function decodeSlash(nl, key, command)
if nl ~= "\n" and nl ~= "" then
return
elseif key == "" then
return nl .. "!" .. command
end
local cu = command:upper()
if not (skipCacheRefresh or next(SlashCmdList) == nil) then
skipCacheRefresh = true
ChatFrame_ImportListToHash(SlashCmdList, hash_SlashCmdList)
end
if hash_ChatTypeInfoList[cu] == key or hash_EmoteTokenList[cu] == key then
elseif _G["SLASH_" .. key .. 1] then
return nl .. _G["SLASH_" .. key .. 1]
else
local i, v = 2, EMOTE1_TOKEN
while v do
if v == key then
return nl .. _G["EMOTE" .. (i-1) .. "_CMD1"]
end
i, v = i + 1, _G["EMOTE" .. i .. "_TOKEN"]
end
end
return nl .. command
end
function encodeMacro(m, skipImport)
skipCacheRefresh = skipImport
return (m:gsub("(.?)(([/!])%S*)", encodeSlash))
end
function decodeMacro(m, skipImport)
skipCacheRefresh = skipImport
return (m:gsub("(.?)!([^\n!]*)!(%S*)", decodeSlash))
end
end
do -- AB imptext action type
local function findAnySpellTokenIcon(imptext)
local argMod = 2^31
for sl in imptext:gmatch("{{spell:([%d/]+)}}") do
for sid in sl:gmatch("%d+") do
local _,_, sico = GetSpellInfo(sid % argMod)
if sico then
return sico
end
end
end
end
local function createImpMacro(macrotext)
if type(macrotext) ~= "string" then return end
local dt = toMacroText(true, macrotext)
return dt and dt:match("%S") and AB:GetActionSlot("macrotext", dt) or nil
end
local function describeImpMacro(imptext)
if type(imptext) ~= "string" then return end
if imptext == "" then return L"Custom Macro", L"New Macro", "Interface/Icons/INV_Misc_Note_03" end
local _, _, ico = RW:GetMacroAction(toMacroText(true, imptext))
local lp = ico and type(ico) == "string" and ico:gsub("\\", "/"):lower()
if ico == nil or lp == "interface/icons/temp" or lp == "interface/icons/inv_misc_questionmark" then
ico = findAnySpellTokenIcon(imptext)
end
return L"Custom Macro", "", ico or "interface/icons/inv_misc_questionmark"
end
AB:RegisterActionType("imptext", createImpMacro, describeImpMacro)
end
do -- Editor UI
local multilineInput do
local function onNavigate(self, _x,y, _w,h)
local scroller, insT, insB = self.scroll, 2, 2
local occH, occP, y = scroller:GetHeight(), scroller:GetVerticalScroll(), -y
if occP > y-insT then
occP = y > insT and y-insT or 0 -- too far
elseif occP < y+h-occH+insB+insT then
occP = y+h-occH+insB+insT -- not far enough
else
return
end
local _, mx = scroller.ScrollBar:GetMinMaxValues()
occP = (mx-occP)^2 < 1 and mx or math.floor(occP)
scroller.ScrollBar:SetMinMaxValues(0, occP < mx and mx or occP)
scroller.ScrollBar:SetValue(occP)
scroller.ScrollBar:Hide() -- BUG[10.0.7]: Thumb sometimes waits until next frame to move
scroller.ScrollBar:Show()
end
local function onSFClick(self)
self.input:SetCursorPosition(#self.input:GetText())
self.input:SetFocus()
end
local function onSCSizeChange(self)
local sc, eb = self.scroll or self, self.input or self
local yr = eb:GetHeight()-sc:GetHeight() + (eb:GetText():sub(-1) == "\n" and 12+2/9 or 0)
yr = yr < 0 and 0 or math.floor(yr+0.5)
if sc._vrange ~= yr then
sc._vrange = yr
ScrollFrame_OnScrollRangeChanged(sc, 0, yr)
eb:GetTop() -- BUG[10.0.7]: Don't vanish now, editbox.
end
end
local function onSFSizeChange(self)
self.input:SetWidth(self:GetWidth())
onSCSizeChange(self)
end
function multilineInput(name, parent, width)
local scroller = CreateFrame("ScrollFrame", name .. "Scroll", parent, "UIPanelScrollFrameTemplate")
local input = CreateFrame("Editbox", name, scroller)
input:SetWidth(width)
input:SetMultiLine(true)
input:SetAutoFocus(false)
input:SetTextInsets(2,4,2,2)
input:SetFontObject(GameFontHighlight)
input:SetScript("OnCursorChanged", onNavigate)
input:SetScript("OnSizeChanged", onSCSizeChange)
scroller:SetScript("OnScrollRangeChanged", onSCSizeChange)
scroller:SetScript("OnSizeChanged", onSFSizeChange)
scroller:SetScript("OnMouseDown", onSFClick)
scroller:EnableMouse(1)
scroller:SetScrollChild(input)
input.scroll, scroller.input = scroller, input
return input, scroller
end
end
local function hasStickyFocus()
return true
end
local function GetHighlightText(editBox)
local text, curPos = editBox:GetText(), editBox:GetCursorPosition()
editBox:Insert("")
local text2, selStart = editBox:GetText(), editBox:GetCursorPosition()
local selEnd = selStart + #text - #text2
if text ~= text2 then
editBox:SetText(text)
editBox:SetCursorPosition(curPos)
editBox:HighlightText(selStart, selEnd)
end
return text:sub(selStart+1, selEnd), selStart
end
local function removeEditorLinks(text)
return (text:gsub("|c%x+|Hilt%d+:([%a:%d/]+)|h.-|h|r", "{{%1}}"))
end
local setImpText, getImpText, newImpToken do
local nextLinkID
function setImpText(box, imptext)
local uitext
uitext, nextLinkID = formatMacro(imptext)
return box:SetText(uitext)
end
function getImpText(box)
return quantizeMacro(removeEditorLinks(box:GetText()))
end
function newImpToken(_forBox, tok, targ)
local uitext
uitext, nextLinkID = formatToken(nextLinkID, tok, targ)
return uitext
end
end
local bg = CreateFrame("Frame")
CreateEdge(bg, {edgeFile="Interface/Tooltips/UI-Tooltip-Border", bgFile="Interface/DialogFrame/UI-DialogBox-Background-Dark", tile=true, edgeSize=16, tileSize=16, insets={left=4,right=4,bottom=4,top=4}}, 0xb2000000, 0xb2b2b2)
bg:Hide()
local eb, scroll = multilineInput("ABE_MacroInput", bg, 100)
eb:SetScript("OnEscapePressed", eb.ClearFocus)
eb:SetScript("OnEditFocusLost", function(self)
setImpText(self, getImpText(self))
local p = bg:GetParent()
if p and type(p.OnActionChanged) == "function" then
p:OnActionChanged(bg)
end
end)
scroll:SetPoint("TOPLEFT", 5, -4)
scroll:SetPoint("BOTTOMRIGHT", -26, 4)
eb.HasStickyFocus = hasStickyFocus
eb:SetHyperlinksEnabled(true)
eb:SetScript("OnHyperlinkClick", function(self, link, text, button)
local pos = string.find(self:GetText(), text, 1, 1)-1
self:HighlightText(pos, pos + #text)
if button == "RightButton" and link:match("^ilt%d+:") then
local replace = IsAltKeyDown() and text:match("|h(.-)|h") or removeEditorLinks(text)
self:Insert(replace)
self:HighlightText(pos, pos + #replace)
else
self:SetCursorPosition(pos + #text)
end
self:SetFocus()
end)
local function ReplaceSelection(editBox, newSelText)
editBox:Insert(newSelText)
local cur = editBox:GetCursorPosition()
editBox:HighlightText(cur-#newSelText, cur)
end
eb:SetScript("OnKeyDown", function(self, key)
if (key == "C" or key == "X") and (IsMacClient() and IsMetaKeyDown or IsControlKeyDown)() then
local stext = GetHighlightText(self)
if stext:match("[^|]|H.+|h.*|h") then
ReplaceSelection(self, removeEditorLinks(stext))
if key == "C" then
self._rsText = stext
end
end
end
end)
eb:SetScript("OnUpdate", function(self)
if self._rsText then
ReplaceSelection(self, self._rsText)
self._rsText = nil
end
end)
eb:SetScript("OnTabPressed", function()
local p = bg:GetParent()
if p and type(p.OnTabPressed) then
p:OnTabPressed()
end
end)
local function isImpTokenParsed(prefixText)
local suf = '|Hiptok|h|h'
return toMacroText(false, prefixText .. suf):sub(-#suf) ~= suf
end
local function stripUIEscapeCheck(preseq)
return (#preseq % 2) == 0 and preseq or nil
end
local function stripUIEscapes(link)
return (link:gsub("(|*)|H.-|h", stripUIEscapeCheck):gsub("(|*)|[hr]", stripUIEscapeCheck):gsub("(|*)|c%x%x%x%x%x%x%x%x", stripUIEscapeCheck))
end
hooksecurefunc("ChatEdit_InsertLink", function(link)
local kbf = GetCurrentKeyBoardFocus()
if kbf ~= eb then return end
local isItemLink = link:match("%f[|]|Hitem:")
local sid = not isItemLink and link:match("%f[|]|Hspell:(%d+)") or link:match("%f[|]|Htrade:[^:]+:(%d+)") or (CI_ERA and select(7,GetSpellInfo(link)))
local isCastableLink = sid and not IsPassiveSpell(sid+0)
local prefix, atext, skipPrefixSpace
if isItemLink or isCastableLink then
kbf:Insert("") -- Inserting the link will clobber selection; do it now to i.a. converge cursor position
local cursor, text = kbf:GetCursorPosition(), kbf:GetText()
local isOnEmptyLineStart, lineCommand, lineStart do
local lep, sp, wep, ap = 0
while 1 do
sp, wep, ap, lep = text:match("()%S*()[^\n%S]*()[^\n]*()", lep+1)
if lep > cursor then
isOnEmptyLineStart = cursor < sp and ap == lep
lineCommand = cursor >= wep-1 and text:sub(sp, wep-1):lower()
lineStart = sp
break
end
end
end
local canTokenize, tokPrefix, tokSuffix, tokNoPreSpace do
if not isCastableLink then
elseif isOnEmptyLineStart then
canTokenize = true
elseif commandType[lineCommand] then
canTokenize = isImpTokenParsed(removeEditorLinks(text:sub(1, cursor)) .. ' ;')
if canTokenize then
local ct, nc = commandType[lineCommand], text:match('^[^%S\n]*(.?)', cursor+1)
tokSuffix = nc ~= '' and nc ~= '\n' and nc ~= ';' and (ct < 2 or nc ~= ',') and ((ct < 2 or nc == '[') and ';' or ',')
local excPrefix = text:sub(cursor, cursor) == '!'
local baseLineText = removeEditorLinks(text:sub(lineStart, cursor)) .. (excPrefix and '' or ' ')
tokPrefix = not isImpTokenParsed(baseLineText) and (ct >= 2 and isImpTokenParsed(baseLineText .. ',') and ',' or ';')
tokNoPreSpace = excPrefix and not tokPrefix
end
end
end
if isItemLink then
prefix = isOnEmptyLineStart and (GetItemSpell(link) and SLASH_USE1 or SLASH_EQUIP1)
atext = GetItemInfo(link)
else
prefix = isOnEmptyLineStart and SLASH_CAST1
if canTokenize then
atext = newImpToken(kbf, "spell", tostring(sid))
prefix = prefix or (atext and tokPrefix)
atext = atext and atext .. (tokSuffix or "")
skipPrefixSpace = tokNoPreSpace
end
end
end
prefix = skipPrefixSpace and (prefix or "") or (prefix and prefix .. " " or " ")
kbf:Insert(prefix .. (atext or link:match("|h%[?(.-[^%]])%]?|h") or stripUIEscapes(link)))
end)
function bg:SetAction(owner, action)
local op = bg:GetParent()
if op and op ~= owner and type(op.OnEditorRelease) == "function" then
securecall(op.OnEditorRelease, op, bg)
end
bg:SetParent(nil)
bg:ClearAllPoints()
bg:SetAllPoints(owner)
bg:SetParent(owner)
setImpText(eb, action[1] == "imptext" and action[2] or "")
eb:SetCursorPosition(0)
bg:Show()
end
function bg:GetAction(into)
into[1], into[2] = "imptext", getImpText(eb)
end
function bg:Release(owner)
if bg:IsOwned(owner) then
bg:SetParent(nil)
bg:ClearAllPoints()
bg:Hide()
end
end
function bg:IsOwned(owner)
return bg:GetParent() == owner
end
function bg:GetTabFocusWidget(_which)
return eb
end
bg.editBox, bg.scrollFrame = bg, eb
AB:RegisterEditorPanel("imptext", bg)
end
function IM:EncodeCommands(macrotext, skipCacheRefresh)
assert(type(macrotext) == "string" and (skipCacheRefresh == nil or type(skipCacheRefresh) == "boolean"),
'Syntax: enctext = IM:EncodeCommands("macrotext"[, skipCacheRefresh])', 2)
return encodeMacro(macrotext, skipCacheRefresh)
end
function IM:DecodeCommands(enctext, skipCacheRefresh)
assert(type(enctext) == "string" and (skipCacheRefresh == nil or type(skipCacheRefresh) == "boolean"),
'Syntax: macrotext = IM:DecodeCommands("enctext"[, skipCacheRefresh])', 2)
return decodeMacro(enctext, skipCacheRefresh)
end
function IM:EncodeTokens(imptext, skipCacheRefresh)
assert(type(imptext) == "string" and (skipCacheRefresh == nil or type(skipCacheRefresh) == "boolean"),
'Syntax: imptext = IM:EncodeTokens("macrotext"[, skipCacheRefresh])', 2)
return quantizeMacro(imptext, skipCacheRefresh)
end
function IM:DecodeTokens(imptext)
assert(type(imptext) == "string", 'Syntax: macrotext = IM:DecodeTokens("imptext")', 2)
return toMacroText(true, imptext)
end
function IM:FormatTokens(imptext)
assert(type(imptext) == "string", 'Syntax: uitext = IM:FormatTokens("imptext")', 2)
return formatMacro(imptext)
end
function IM:AddTokenizableCommand(slashKey, behavesLikeCommand)
assert(type(slashKey) == 'string' and type(behavesLikeCommand) == 'string',
'Syntax: IM:AddTokenizableCommand("slashKey"[, "behavesLikeCommand"])', 2)
assert(type(_G['SLASH_' .. slashKey .. '1']) == 'string', 'Invalid slash command key %q', 2, slashKey)
assert((commandType[behavesLikeCommand] or -1) >= 0, 'Unrecognized behaves-like command %q', 2, behavesLikeCommand)
addCommandType(slashKey, commandType[behavesLikeCommand])
AB:NotifyObservers("imptext")
end
function IM:SetMountPreference(groundSpellID, flyingSpellID, dragonSpellID)
assert((type(groundSpellID) == "number" or not groundSpellID) and
(type(flyingSpellID) == "number" or not flyingSpellID) and
(type(dragonSpellID) == "number" or not dragonSpellID),
'Syntax: groundSpellID, flyingSpellID, dragonSpellID = IM:SetMountPreference(groundSpellID|false|nil, flyingSpellID|false|nil, dragonSpellID|false|nil)', 2)
return setMountPreference(groundSpellID, flyingSpellID, dragonSpellID)
end
AB:RegisterModule("Imp", {
compatible=function(_, maj, rev)
if maj == MAJ and (rev == nil or rev <= REV) then
return IM
end
end
})