flickerstreak@1: --[[ flickerstreak@1: Name: AceTab-2.0 flickerstreak@1: Revision: $Rev: 18708 $ flickerstreak@1: Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) flickerstreak@1: Website: http://www.wowace.com/ flickerstreak@1: Documentation: http://www..wowace.com/index.php/AceTab-2.0 flickerstreak@1: SVN: http://svn.wowace.com/root/trunk/Ace2/AceTab-2.0 flickerstreak@1: Description: A tab-completion library flickerstreak@1: Dependencies: AceLibrary, AceEvent-2.0 flickerstreak@1: ]] flickerstreak@1: flickerstreak@1: local MAJOR_VERSION = "AceTab-2.0" flickerstreak@1: local MINOR_VERSION = "$Revision: 18708 $" flickerstreak@1: flickerstreak@1: if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end flickerstreak@1: if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end flickerstreak@1: flickerstreak@1: local AceEvent flickerstreak@1: local AceTab = {} flickerstreak@1: local _G = getfenv() flickerstreak@1: flickerstreak@1: local hookedFrames = {} flickerstreak@1: local framesHooked = {} flickerstreak@1: flickerstreak@1: function AceTab:RegisterTabCompletion(descriptor, regex, wlfunc, usage, editframes) flickerstreak@1: self:argCheck(descriptor, 2, "string") flickerstreak@1: self:argCheck(regex, 3, "string", "table") flickerstreak@1: self:argCheck(wlfunc, 4, "string", "function", "nil") flickerstreak@1: self:argCheck(usage, 5, "string", "function", "boolean", "nil") flickerstreak@1: self:argCheck(editframe, 6, "string", "table", "nil") flickerstreak@1: flickerstreak@1: if type(regex) == "string" then regex = {regex} end flickerstreak@1: flickerstreak@1: if type(wlfunc) == "string" and type(self[wlfunc]) ~= "function" then flickerstreak@1: self:error("Cannot register function %q; it does not exist", wlfunc) flickerstreak@1: end flickerstreak@1: flickerstreak@1: if type(usage) == "string" and type(self[usage]) ~= "function" then flickerstreak@1: self:error("Cannot register usage function %q; it does not exist", usage) flickerstreak@1: end flickerstreak@1: flickerstreak@1: if not editframes then editframes = {"ChatFrameEditBox"} end flickerstreak@1: flickerstreak@1: if type(editframes) == "table" and editframes.Show then editframes = {editframes:GetName()} end flickerstreak@1: flickerstreak@1: for _, frame in pairs(editframes) do flickerstreak@1: local Gframe flickerstreak@1: if type(frame) == "table" then flickerstreak@1: Gframe = frame flickerstreak@1: frame = frame:GetName() flickerstreak@1: else flickerstreak@1: Gframe = _G[frame] flickerstreak@1: end flickerstreak@1: flickerstreak@1: if type(Gframe) ~= "table" or not Gframe.Show then flickerstreak@1: self:error("Cannot register frame %q; it does not exist", frame) flickerstreak@1: frame = nil flickerstreak@1: end flickerstreak@1: flickerstreak@1: if frame then flickerstreak@1: if Gframe:GetFrameType() ~= "EditBox" then flickerstreak@1: self:error("Cannot register frame %q; it is not an EditBox", frame) flickerstreak@1: frame = nil flickerstreak@1: else flickerstreak@1: if AceEvent and AceEvent:IsFullyInitialized() then flickerstreak@1: if not framesHooked[Gframe] then flickerstreak@1: framesHooked[Gframe] = true flickerstreak@1: local orig = Gframe:GetScript("OnTabPressed") flickerstreak@1: if type(orig) ~= "function" then flickerstreak@1: orig = function() end flickerstreak@1: end flickerstreak@1: Gframe:SetScript("OnTabPressed", function() flickerstreak@1: if self:OnTabPressed(Gframe) then flickerstreak@1: return orig() flickerstreak@1: end flickerstreak@1: end) flickerstreak@1: Gframe.curMatch = 0 flickerstreak@1: Gframe.matches = {} flickerstreak@1: Gframe.pMatchLen = 0 flickerstreak@1: end flickerstreak@1: else flickerstreak@1: hookedFrames[frame] = true flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: if not self.registry[descriptor] then flickerstreak@1: self.registry[descriptor] = {} flickerstreak@1: end flickerstreak@1: flickerstreak@1: if not self.registry[descriptor][self] then flickerstreak@1: self.registry[descriptor][self] = {} flickerstreak@1: end flickerstreak@1: self.registry[descriptor][self] = {patterns = regex, wlfunc = wlfunc, usage = usage, frames = editframes} flickerstreak@1: flickerstreak@1: flickerstreak@1: if not AceEvent and AceLibrary:HasInstance("AceEvent-2.0") then flickerstreak@1: external(AceTab, "AceEvent-2.0", AceLibrary("AceEvent-2.0")) flickerstreak@1: end flickerstreak@1: if AceEvent then flickerstreak@1: if not self:IsEventRegistered("AceEvent_FullyInitialized") then flickerstreak@1: self:RegisterEvent("AceEvent_FullyInitialized", "AceEvent_FullyInitialized", true) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: function AceTab:IsTabCompletionRegistered(descriptor) flickerstreak@1: self:argCheck(descriptor, 2, "string") flickerstreak@1: return self.registry[descriptor] and self.registry[descriptor][self] flickerstreak@1: end flickerstreak@1: flickerstreak@1: function AceTab:UnregisterTabCompletion(descriptor) flickerstreak@1: self:argCheck(descriptor, 2, "string") flickerstreak@1: if self.registry[descriptor] and self.registry[descriptor][self] then flickerstreak@1: self.registry[descriptor][self] = nil flickerstreak@1: else flickerstreak@1: self:error("Cannot unregister a tab completion (%s) that you have not registered.", descriptor) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: local GCS flickerstreak@1: GCS = function(s1, s2) flickerstreak@1: if not s1 and not s2 then return end flickerstreak@1: if not s1 then s1 = s2 end flickerstreak@1: if not s2 then s2 = s1 end flickerstreak@1: local s1len, s2len = string.len(s1), string.len(s2) flickerstreak@1: if s2len < s1len then flickerstreak@1: s1, s2 = s2, s1 flickerstreak@1: end flickerstreak@1: if string.find(string.lower(s2), string.lower(s1)) then flickerstreak@1: return s1 flickerstreak@1: else flickerstreak@1: return GCS(string.sub(s1, 1, -2), s2) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: local pos flickerstreak@1: local function CycleTab() flickerstreak@1: this.pMatchLen = string.len(this.lMatch) flickerstreak@1: local cMatch = 0 flickerstreak@1: local matched = false flickerstreak@1: for desc, mList in pairs(this.matches) do flickerstreak@1: if not matched then flickerstreak@1: for _, m in ipairs(mList) do flickerstreak@1: cMatch = cMatch + 1 flickerstreak@1: if cMatch == this.curMatch then flickerstreak@1: this.lMatch = m flickerstreak@1: this.curMatch = this.curMatch + 1 flickerstreak@1: matched = true flickerstreak@1: break flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: if not matched then flickerstreak@1: this.curMatch = 1 flickerstreak@1: this.lMatch = this.origWord flickerstreak@1: end flickerstreak@1: this:HighlightText(pos - this.pMatchLen, pos) flickerstreak@1: this:Insert(this.lMatch) flickerstreak@1: end flickerstreak@1: flickerstreak@1: function AceTab:OnTabPressed() flickerstreak@1: local ost = this:GetScript("OnTextSet") flickerstreak@1: if type(ost) ~= "function" then flickerstreak@1: ost = nil flickerstreak@1: end flickerstreak@1: if ost then this:SetScript("OnTextSet", nil) end flickerstreak@1: if this:GetText() == "" then return true end flickerstreak@1: this:Insert("\255") flickerstreak@1: pos = string.find(this:GetText(), "\255", 1) - 1 flickerstreak@1: this:HighlightText(pos, pos+1) flickerstreak@1: this:Insert("\0") flickerstreak@1: if ost then this:SetScript("OnTextSet", ost) end flickerstreak@1: local fulltext = this:GetText() flickerstreak@1: local text = string.sub(fulltext, 0, pos) or "" flickerstreak@1: flickerstreak@1: local left = string.find(string.sub(text, 1, pos), "%w+$") flickerstreak@1: left = left and left-1 or pos flickerstreak@1: if not left or left == 1 and string.sub(text, 1, 1) == "/" then return true end flickerstreak@1: flickerstreak@1: local _, _, word = string.find(string.sub(text, left, pos), "(%w+)") flickerstreak@1: word = word or "" flickerstreak@1: this.lMatch = this.curMatch > 0 and (this.lMatch or this.origWord) flickerstreak@1: flickerstreak@1: if this.lMatch and this.lMatch ~= "" and string.find(string.sub(text, 1, pos), this.lMatch.."$") then flickerstreak@1: return CycleTab() flickerstreak@1: else flickerstreak@1: this.matches = {} flickerstreak@1: this.curMatch = 0 flickerstreak@1: this.lMatch = nil flickerstreak@1: end flickerstreak@1: flickerstreak@1: local completions = {} flickerstreak@1: local numMatches = 0 flickerstreak@1: local firstMatch, hasNonFallback flickerstreak@1: flickerstreak@1: for desc, entry in pairs(AceTab.registry) do flickerstreak@1: for _, s in pairs(entry) do flickerstreak@1: for _, f in pairs(s.frames) do flickerstreak@1: if _G[f] == this then flickerstreak@1: for _, regex in ipairs(s.patterns) do flickerstreak@1: local cands = {} flickerstreak@1: if string.find(string.sub(text, 1, left), regex.."$") then flickerstreak@1: local c = s.wlfunc(cands, fulltext, left) flickerstreak@1: if c ~= false then flickerstreak@1: local mtemp = {} flickerstreak@1: this.matches[desc] = this.matches[desc] or {} flickerstreak@1: for _, cand in ipairs(cands) do flickerstreak@1: if string.find(string.lower(cand), string.lower(word), 1, 1) == 1 then flickerstreak@1: mtemp[cand] = true flickerstreak@1: numMatches = numMatches + 1 flickerstreak@1: if numMatches == 1 then firstMatch = cand end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: for i in pairs(mtemp) do flickerstreak@1: table.insert(this.matches[desc], i) flickerstreak@1: end flickerstreak@1: this.matches[desc].usage = s.usage flickerstreak@1: if regex ~= "" and this.matches[desc][1] then flickerstreak@1: hasNonFallback = true flickerstreak@1: this.matches[desc].notFallback = true flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: local _, set = next(this.matches) flickerstreak@1: if not set or numMatches == 0 and not hasNonFallback then return true end flickerstreak@1: flickerstreak@1: this:HighlightText(left, left + string.len(word)) flickerstreak@1: if numMatches == 1 then flickerstreak@1: this:Insert(firstMatch) flickerstreak@1: this:Insert(" ") flickerstreak@1: else flickerstreak@1: if this.curMatch == 0 then flickerstreak@1: this.curMatch = 1 flickerstreak@1: this.origWord = word flickerstreak@1: this.lMatch = word flickerstreak@1: CycleTab() flickerstreak@1: end flickerstreak@1: local gcs flickerstreak@1: for h, c in pairs(this.matches) do flickerstreak@1: if hasNonFallback and not c.notFallback then break end flickerstreak@1: local u = c.usage flickerstreak@1: c.usage = nil flickerstreak@1: local candUsage = u and {} flickerstreak@1: local gcs2 flickerstreak@1: if next(c) then flickerstreak@1: if not u then DEFAULT_CHAT_FRAME:AddMessage(h..":") end flickerstreak@1: for _, m in ipairs(c) do flickerstreak@1: if not u then DEFAULT_CHAT_FRAME:AddMessage(m) end flickerstreak@1: gcs2 = GCS(gcs2, m) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: gcs = GCS(gcs, gcs2) flickerstreak@1: if u then flickerstreak@1: if type(u) == "function" then flickerstreak@1: local us = u(candUsage, c, gcs2, string.sub(text, 1, left)) flickerstreak@1: if candUsage and next(candUsage) then us = candUsage end flickerstreak@1: if type(us) == "string" then flickerstreak@1: DEFAULT_CHAT_FRAME:AddMessage(us) flickerstreak@1: elseif type(us) == "table" and numMatches > 0 then flickerstreak@1: for _, v in ipairs(c) do flickerstreak@1: if us[v] then DEFAULT_CHAT_FRAME:AddMessage(string.format("%s - %s", v, us[v])) end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: if curMatch == 0 then flickerstreak@1: this:Insert(gcs or word) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: function AceTab:AceEvent_FullyInitialized() flickerstreak@1: for frame in pairs(hookedFrames) do flickerstreak@1: local Gframe = _G[frame] flickerstreak@1: if not framesHooked[Gframe] then flickerstreak@1: framesHooked[Gframe] = true flickerstreak@1: local orig = Gframe:GetScript("OnTabPressed") flickerstreak@1: if type(orig) ~= "function" then flickerstreak@1: orig = function() end flickerstreak@1: end flickerstreak@1: Gframe:SetScript("OnTabPressed", function() flickerstreak@1: if self:OnTabPressed(Gframe) then flickerstreak@1: return orig() flickerstreak@1: end flickerstreak@1: end) flickerstreak@1: Gframe.curMatch = 0 flickerstreak@1: Gframe.matches = {} flickerstreak@1: Gframe.pMatchLen = 0 flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: local function external(self, major, instance) flickerstreak@1: if major == "AceEvent-2.0" then flickerstreak@1: if not AceEvent then flickerstreak@1: AceEvent = instance flickerstreak@1: flickerstreak@1: AceEvent:embed(self) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: local function activate(self, oldLib, oldDeactivate) flickerstreak@1: if oldLib then flickerstreak@1: self.registry = oldLib.registry flickerstreak@1: end flickerstreak@1: flickerstreak@1: if not self.registry then flickerstreak@1: self.registry = {} flickerstreak@1: end flickerstreak@1: flickerstreak@1: if oldDeactivate then flickerstreak@1: oldDeactivate(oldLib) flickerstreak@1: end flickerstreak@1: end flickerstreak@1: flickerstreak@1: AceLibrary:Register(AceTab, MAJOR_VERSION, MINOR_VERSION, activate, nil, external) flickerstreak@1: AceTab = AceLibrary(MAJOR_VERSION)