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.
511 lines
16 KiB
511 lines
16 KiB
local MAJOR, MINOR = "LibAPIAutoComplete-1.0", 3
|
|
local lib = LibStub:NewLibrary(MAJOR, MINOR)
|
|
if not lib then return end
|
|
|
|
local SharedMedia = LibStub("LibSharedMedia-3.0")
|
|
|
|
local config = {}
|
|
|
|
local skipWords = {
|
|
["local"] = true,
|
|
["print"] = true,
|
|
["player"] = true,
|
|
["display"] = true,
|
|
["return"] = true,
|
|
["function"] = true
|
|
}
|
|
|
|
local maxMatches = 100
|
|
|
|
for k in pairs(skipWords) do
|
|
for i = #k, 5, -1 do
|
|
skipWords[k:sub(1, i)] = true
|
|
end
|
|
end
|
|
|
|
local function LoadBlizzard_APIDocumentation()
|
|
local apiAddonName = "Blizzard_APIDocumentation"
|
|
local _, loaded = C_AddOns.IsAddOnLoaded(apiAddonName)
|
|
if not loaded then
|
|
C_AddOns.LoadAddOn(apiAddonName)
|
|
end
|
|
if #APIDocumentation.systems == 0 then
|
|
-- workaround nil errors when loading PetConstantsDocumentation.lua
|
|
Constants.PetConsts = Constants.PetConsts or {
|
|
MAX_STABLE_SLOTS = 200,
|
|
MAX_SUMMONABLE_PETS = 25,
|
|
MAX_SUMMONABLE_HUNTER_PETS = 5,
|
|
NUM_PET_SLOTS_THAT_NEED_LEARNED_SPELL = 5,
|
|
NUM_PET_SLOTS = 205,
|
|
EXTRA_PET_STABLE_SLOT = 5,
|
|
STABLED_PETS_FIRST_SLOT_INDEX = 6
|
|
}
|
|
MAX_STABLE_SLOTS = MAX_STABLE_SLOTS or 2
|
|
NUM_PET_SLOTS_THAT_NEED_LEARNED_SPELL = NUM_PET_SLOTS_THAT_NEED_LEARNED_SPELL or 1
|
|
EXTRA_PET_STABLE_SLOT = EXTRA_PET_STABLE_SLOT or 0
|
|
-- end of workaround
|
|
APIDocumentation_LoadUI()
|
|
end
|
|
end
|
|
|
|
function lib:Hide()
|
|
self.scrollBox:Hide()
|
|
self.scrollBar:Hide()
|
|
end
|
|
|
|
---Create APIDoc widget and ensure Blizzard_APIDocumentation is loaded
|
|
local isInit = false
|
|
local function Init()
|
|
if isInit then
|
|
return
|
|
end
|
|
isInit = true
|
|
|
|
-- load Blizzard_APIDocumentation
|
|
LoadBlizzard_APIDocumentation()
|
|
|
|
local scrollBox = CreateFrame("Frame", nil, UIParent, "WowScrollBoxList")
|
|
scrollBox:SetSize(400, 150)
|
|
scrollBox:Hide()
|
|
|
|
local background = scrollBox:CreateTexture(nil, "BACKGROUND")
|
|
background:SetAllPoints()
|
|
scrollBox.background = background
|
|
|
|
local scrollBar = CreateFrame("EventFrame", nil, UIParent, "WowTrimScrollBar")
|
|
scrollBar:SetPoint("TOPLEFT", scrollBox, "TOPRIGHT")
|
|
scrollBar:SetPoint("BOTTOMLEFT", scrollBox, "BOTTOMRIGHT")
|
|
scrollBar:Hide()
|
|
|
|
local view = CreateScrollBoxListLinearView()
|
|
view:SetElementExtentCalculator(function(dataIndex, elementData)
|
|
return 20
|
|
end)
|
|
view:SetElementInitializer("button", function(frame, elementData)
|
|
Mixin(frame, APIAutoCompleteLineMixin)
|
|
frame:Init(elementData)
|
|
end)
|
|
ScrollUtil.InitScrollBoxListWithScrollBar(scrollBox, scrollBar, view)
|
|
local selectionBehaviour = ScrollUtil.AddSelectionBehavior(scrollBox, SelectionBehaviorFlags.Deselectable, SelectionBehaviorFlags.Intrusive)
|
|
selectionBehaviour:RegisterCallback(SelectionBehaviorMixin.Event.OnSelectionChanged, function(o, elementData, selected)
|
|
local elementFrame = scrollBox:FindFrame(elementData)
|
|
if elementFrame then
|
|
elementFrame:SetSelected(selected)
|
|
end
|
|
|
|
if selected and lib.editbox and config[lib.editbox] then
|
|
local maxLinesShown = config[lib.editbox].maxLinesShown
|
|
local index = lib.data:FindIndex(elementData)
|
|
local divisor = lib.data:GetSize() - maxLinesShown
|
|
if divisor == 0 then
|
|
divisor = 1
|
|
end
|
|
local percent = (index - maxLinesShown / 2) / divisor
|
|
if percent < 0 then
|
|
percent = 0
|
|
elseif percent > 1 then
|
|
percent = 1
|
|
end
|
|
scrollBar:SetScrollPercentage(percent)
|
|
end
|
|
end)
|
|
|
|
lib.data = CreateDataProvider()
|
|
scrollBox:SetDataProvider(lib.data)
|
|
|
|
lib.scrollBar = scrollBar
|
|
lib.scrollBox = scrollBox
|
|
lib.selectionBehaviour = selectionBehaviour
|
|
|
|
scrollBox.selectionBehaviour = selectionBehaviour
|
|
|
|
scrollBox:SetScript("OnKeyDown", function(self, key)
|
|
if key == "DOWN" then
|
|
lib.scrollBox:SetPropagateKeyboardInput(false)
|
|
if not self.selectionBehaviour:HasSelection() then
|
|
self.selectionBehaviour:SelectFirstElementData()
|
|
else
|
|
self.selectionBehaviour:SelectNextElementData()
|
|
end
|
|
elseif key == "UP" then
|
|
lib.scrollBox:SetPropagateKeyboardInput(false)
|
|
if not self.selectionBehaviour:HasSelection() then
|
|
self.selectionBehaviour:SelectFirstElementData()
|
|
else
|
|
self.selectionBehaviour:SelectPreviousElementData()
|
|
end
|
|
elseif key == "ENTER" and not IsModifierKeyDown() then
|
|
local selectedElementData = self.selectionBehaviour:GetFirstSelectedElementData()
|
|
if selectedElementData then
|
|
lib.scrollBox:SetPropagateKeyboardInput(false)
|
|
local elementFrame = scrollBox:FindFrame(selectedElementData)
|
|
elementFrame:Insert()
|
|
end
|
|
elseif key == "ESCAPE" then
|
|
lib.scrollBox:SetPropagateKeyboardInput(false)
|
|
lib.data:Flush()
|
|
lib:UpdateWidget(lib.editbox)
|
|
else
|
|
lib.scrollBox:SetPropagateKeyboardInput(true)
|
|
lib.data:Flush()
|
|
lib:UpdateWidget(lib.editbox)
|
|
end
|
|
end)
|
|
end
|
|
|
|
local lastPosition
|
|
|
|
---@private
|
|
---@param editbox EditBox
|
|
---@param x number
|
|
---@param y number
|
|
---@param w number
|
|
---@param h number
|
|
local function OnCursorChanged(editbox, x, y, w, h)
|
|
local cursorPosition = editbox:GetCursorPosition()
|
|
if cursorPosition ~= lastPosition then
|
|
lib:Hide()
|
|
lib.scrollBox:ClearAllPoints()
|
|
lib.scrollBox:SetPoint("TOPLEFT", editbox, "TOPLEFT", x, y - h)
|
|
local currentWord = lib:GetWord(editbox)
|
|
if #currentWord > 4 and not skipWords[currentWord] then
|
|
lib:Search(currentWord, config[editbox])
|
|
if lib.data:GetSize() == 1 and lib.data:Find(1).name == currentWord then
|
|
lib.data:Flush()
|
|
end
|
|
lib:UpdateWidget(editbox)
|
|
end
|
|
end
|
|
lastPosition = cursorPosition
|
|
end
|
|
|
|
---@class Color
|
|
---@field r integer
|
|
---@field g integer
|
|
---@field b integer
|
|
---@field a integer?
|
|
|
|
---@class Params
|
|
---@field backgroundColor Color?
|
|
---@field maxLinesShown integer?
|
|
---@field disableFunctions boolean?
|
|
---@field disableEvents boolean?
|
|
---@field disableSystems boolean?
|
|
|
|
---Enable APIDoc widget on editbox
|
|
---ForAllIndentsAndPurpose replace GetText, APIDoc must be enabled before FAIAP
|
|
---@param editbox EditBox
|
|
---@param params Params
|
|
function lib:enable(editbox, params)
|
|
if config[editbox] then
|
|
return
|
|
end
|
|
config[editbox] = {
|
|
backgroundColor = params and params.backgroundColor or {.3, .3, .3, .9},
|
|
maxLinesShown = params and params.maxLinesShown or 7,
|
|
disableFunctions = params and params.disableFunctions or false,
|
|
disableEvents = params and params.disableEvents or false,
|
|
disableSystems = params and params.disableSystems or false,
|
|
}
|
|
Init()
|
|
-- hack for WeakAuras
|
|
editbox.APIDoc_originalGetText = editbox.GetText
|
|
-- hack for WowLua
|
|
if editbox == WowLuaFrameEditBox then
|
|
editbox.APIDoc_originalGetText = function()
|
|
return WowLua.indent.coloredGetText(editbox)
|
|
end
|
|
end
|
|
editbox.APIDoc_oldOnCursorChanged = editbox:GetScript("OnCursorChanged")
|
|
editbox:SetScript("OnCursorChanged", function(...)
|
|
if editbox.APIDoc_oldOnCursorChanged then
|
|
editbox.APIDoc_oldOnCursorChanged(...)
|
|
end
|
|
OnCursorChanged(...)
|
|
end)
|
|
editbox:SetScript("OnHide", function(...)
|
|
lib:Hide()
|
|
end)
|
|
editbox.APIDoc_hiddenString = editbox:CreateFontString()
|
|
end
|
|
|
|
---Disable APIDoc widget on editbox
|
|
---@param editbox EditBox
|
|
function lib:disable(editbox)
|
|
if not config[editbox] then
|
|
return
|
|
end
|
|
config[editbox] = nil
|
|
editbox:SetScript("OnCursorChanged", editbox.APIDoc_oldOnCursorChanged)
|
|
editbox.APIDoc_oldOnCursorChanged = nil
|
|
end
|
|
|
|
function lib:addLine(apiInfo)
|
|
local name
|
|
if apiInfo.Type == "System" then
|
|
name = apiInfo.Namespace
|
|
elseif apiInfo.Type == "Function" then
|
|
name = apiInfo:GetFullName()
|
|
elseif apiInfo.Type == "Event" then
|
|
name = apiInfo.LiteralName
|
|
end
|
|
self.data:Insert({ name = name, apiInfo = apiInfo })
|
|
end
|
|
|
|
---Search a word in documentation, set results in lib.data
|
|
---@param word string
|
|
---@param config Params
|
|
function lib:Search(word, config)
|
|
self.data:Flush()
|
|
if word and #word > 3 then
|
|
local lowerWord = word:lower();
|
|
local nsName, rest = lowerWord:match("^([%w%_]+)(.*)")
|
|
local funcName = rest and rest:match("^%.([%w%_]+)")
|
|
for _, systemInfo in ipairs(APIDocumentation.systems) do
|
|
local systemMatch = (not config.disableSystems)
|
|
and (nsName and #nsName >= 4)
|
|
and (systemInfo.Namespace and systemInfo.Namespace:lower():match(nsName))
|
|
|
|
if not config.disableFunctions then
|
|
for _, apiInfo in ipairs(systemInfo.Functions) do
|
|
if systemMatch then
|
|
if funcName then
|
|
if apiInfo:MatchesSearchString(funcName) then
|
|
self:addLine(apiInfo)
|
|
end
|
|
else
|
|
self:addLine(apiInfo)
|
|
end
|
|
else
|
|
if apiInfo:MatchesSearchString(lowerWord) then
|
|
self:addLine(apiInfo)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not config.disableEvents then
|
|
if systemMatch and rest == "" then
|
|
for _, apiInfo in ipairs(systemInfo.Events) do
|
|
self:addLine(apiInfo)
|
|
end
|
|
else
|
|
for _, apiInfo in ipairs(systemInfo.Events) do
|
|
if apiInfo:MatchesSearchString(lowerWord) then
|
|
self:addLine(apiInfo)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.data:GetSize() > maxMatches then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---set in lib.data the list of systems
|
|
function lib:ListSystems()
|
|
self.data:Flush()
|
|
for i, systemInfo in ipairs(APIDocumentation.systems) do
|
|
if systemInfo.Namespace and #systemInfo.Functions > 0 then
|
|
self:addLine(systemInfo)
|
|
end
|
|
end
|
|
end
|
|
|
|
---Hide, or Show and fill APIDoc widget, using lib.data data
|
|
---@param editbox EditBox
|
|
function lib:UpdateWidget(editbox)
|
|
if self.data:IsEmpty() then
|
|
self:Hide()
|
|
self.editbox = nil
|
|
else
|
|
-- fix size
|
|
local maxLinesShown = config[editbox].maxLinesShown
|
|
local lines = self.data:GetSize()
|
|
local height = math.min(lines, maxLinesShown) * 20
|
|
local width = 0
|
|
local hiddenString = editbox.APIDoc_hiddenString
|
|
local fontPath = SharedMedia:Fetch("font", "Fira Mono Medium")
|
|
hiddenString:SetFont(fontPath, 12, "")
|
|
for _, elementData in self.data:Enumerate() do
|
|
hiddenString:SetText(elementData.name)
|
|
width = math.max(width, hiddenString:GetStringWidth())
|
|
end
|
|
self.scrollBox:SetSize(width, height)
|
|
|
|
-- fix look
|
|
local backgroundColor = config[editbox].backgroundColor
|
|
self.scrollBox.background:SetColorTexture(unpack(backgroundColor))
|
|
|
|
-- show
|
|
self.scrollBox:SetParent(UIParent)
|
|
self.scrollBar:SetParent(UIParent)
|
|
self.scrollBox:SetFrameStrata("TOOLTIP")
|
|
self.scrollBar:SetFrameStrata("TOOLTIP")
|
|
self.scrollBox:Show()
|
|
self.scrollBar:SetShown(lines > maxLinesShown)
|
|
self.editbox = editbox
|
|
end
|
|
end
|
|
|
|
local function OnClickCallback(self)
|
|
local name
|
|
if IndentationLib then
|
|
name = IndentationLib.stripWowColors(self.name)
|
|
elseif WowLua and WowLua.indent then
|
|
name = WowLua.indent.stripWowColors(self.name)
|
|
end
|
|
lib:SetWord(lib.editbox, name)
|
|
lib:Hide()
|
|
lib.editbox:SetFocus()
|
|
end
|
|
|
|
---@param editbox EditBox
|
|
---@return string currentWord
|
|
---@return integer startPosition
|
|
---@return integer endPosition
|
|
function lib:GetWord(editbox)
|
|
-- get cursor position
|
|
local cursorPosition = editbox:GetCursorPosition()
|
|
local text = editbox:APIDoc_originalGetText()
|
|
if IndentationLib then
|
|
text, cursorPosition = IndentationLib.stripWowColorsWithPos(text, cursorPosition)
|
|
end
|
|
|
|
-- get start position of current word
|
|
local startPosition = cursorPosition
|
|
while startPosition - 1 > 0 and text:sub(startPosition - 1, startPosition - 1):find("[%w%.%_]") do
|
|
startPosition = startPosition - 1
|
|
end
|
|
|
|
-- get end position of current word
|
|
local endPosition = startPosition
|
|
while endPosition < #text and text:sub(endPosition + 1, endPosition + 1):find("[%w%.%_]") do
|
|
endPosition = endPosition + 1
|
|
end
|
|
|
|
local nextChar = text:sub(cursorPosition, cursorPosition)
|
|
if nextChar ~= "" and nextChar ~= " " and nextChar ~= "\n" then
|
|
return "", nil, nil
|
|
end
|
|
|
|
local currentWord = text:sub(startPosition, endPosition)
|
|
return currentWord, startPosition, endPosition
|
|
end
|
|
|
|
---@param editbox EditBox
|
|
---@param word string
|
|
function lib:SetWord(editbox, word)
|
|
-- get cursor position
|
|
local cursorPosition = editbox:GetCursorPosition()
|
|
local text = editbox:APIDoc_originalGetText()
|
|
if IndentationLib then
|
|
text, cursorPosition = IndentationLib.stripWowColorsWithPos(text, cursorPosition)
|
|
end
|
|
|
|
-- get start position of current word
|
|
local startPosition = cursorPosition
|
|
while startPosition > 0 and text:sub(startPosition - 1, startPosition - 1):find("[%w%.%_]") do
|
|
startPosition = startPosition - 1
|
|
end
|
|
|
|
-- get end position of current word
|
|
local endPosition = startPosition
|
|
while endPosition < #text and text:sub(endPosition + 1, endPosition + 1):find("[%w%.%_]") do
|
|
endPosition = endPosition + 1
|
|
end
|
|
|
|
-- check if replacement word looks like a function and has args
|
|
local funcName, argsString = word:match("([%w%.%_]+)%(([%w%.%_,\"%s]*)%)")
|
|
local funcArgs = {}
|
|
if funcName and argsString then
|
|
for arg in argsString:gmatch("([%w%.%_\"]+),?") do
|
|
table.insert(funcArgs, arg)
|
|
end
|
|
end
|
|
|
|
-- check if current word has parentheses and args
|
|
local oldFuncArgs = {}
|
|
if funcName then
|
|
local currentWordArgs = text:sub(endPosition + 1, #text):match("^%(([%w%.%_,\"%s]*)%)")
|
|
if currentWordArgs then
|
|
for arg in currentWordArgs:gmatch("([%w%.%_\"]+),?") do
|
|
table.insert(oldFuncArgs, arg)
|
|
end
|
|
-- move endPosition
|
|
endPosition = endPosition + #currentWordArgs + 2
|
|
end
|
|
end
|
|
|
|
-- replace replacement word's args with args from current word
|
|
if funcName then
|
|
local concatArgs = {}
|
|
for i = 1, math.max(#funcArgs, #oldFuncArgs) do
|
|
concatArgs[i] = oldFuncArgs[i] or funcArgs[i]
|
|
end
|
|
word = funcName .. "(" .. table.concat(concatArgs, ", ") .. ")"
|
|
end
|
|
|
|
-- replace word
|
|
text = text:sub(1, startPosition - 1) .. word .. text:sub(endPosition + 1, #text)
|
|
editbox:SetText(text)
|
|
-- SetText triggers the OnTextChanged handler without the "userInput" flag. We need that flag set to true, so run the handler again
|
|
local script = editbox:GetScript("OnTextChanged")
|
|
if script then
|
|
script(editbox, true)
|
|
end
|
|
|
|
-- move cursor at end of word or start of parenthese
|
|
local parenthesePosition = word:find("%(")
|
|
editbox:SetCursorPosition(startPosition - 1 + (parenthesePosition or #word))
|
|
end
|
|
|
|
local function showTooltip(self)
|
|
if self.apiInfo then
|
|
GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT", 20, 20)
|
|
GameTooltip:ClearLines()
|
|
for _, line in ipairs(self.apiInfo:GetDetailedOutputLines()) do
|
|
GameTooltip:AddLine(line)
|
|
end
|
|
GameTooltip:Show()
|
|
end
|
|
end
|
|
|
|
local function hideTooltip(self)
|
|
GameTooltip:Hide()
|
|
GameTooltip:ClearLines()
|
|
end
|
|
|
|
APIAutoCompleteLineMixin = {}
|
|
function APIAutoCompleteLineMixin:Init(elementData)
|
|
self.name = elementData.name
|
|
self.apiInfo = elementData.apiInfo
|
|
self:SetText(elementData.name)
|
|
self:SetScript("OnClick", OnClickCallback)
|
|
self:SetScript("OnEnter", showTooltip)
|
|
self:SetScript("OnLeave", hideTooltip)
|
|
local fontString = self:GetFontString()
|
|
fontString:ClearAllPoints()
|
|
local fontPath = SharedMedia:Fetch("font", "Fira Mono Medium")
|
|
fontString:SetFont(fontPath, 12, "")
|
|
fontString:SetPoint("LEFT")
|
|
fontString:SetTextColor(0.973, 0.902, 0.581)
|
|
if not self:GetHighlightTexture() then
|
|
local texture = self:CreateTexture()
|
|
texture:SetColorTexture(0.4,0.4,0.4,0.5)
|
|
texture:SetAllPoints()
|
|
self:SetHighlightTexture(texture)
|
|
end
|
|
self:SetSelected(false)
|
|
end
|
|
|
|
function APIAutoCompleteLineMixin:SetSelected(selected)
|
|
self:SetHighlightLocked(selected)
|
|
end
|
|
|
|
function APIAutoCompleteLineMixin:Insert()
|
|
OnClickCallback(self)
|
|
end
|
|
|