Advertisement
draugath

tab_complete

Feb 14th, 2021 (edited)
1,040
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 4.11 KB | None | 0 0
  1. declare("tab_complete", {})
  2.  
  3. local Names = {}
  4. local NamesReverse = {}
  5. tab_complete.Names = Names -- TEST only here for testing purposes
  6. tab_complete.NamesReverse = NamesReverse -- TEST only here for testing purposes
  7.  
  8. local function sort_func(a, b) return a:lower() < b:lower() end
  9.  
  10. function tab_complete:OnEvent(event, data)
  11.     local name = data.name
  12.    
  13.     -- Currently only storing a name if it's not been seen.  
  14.     -- Should the timestamp of when a name is seen be updated each time to allow for list pruning?
  15.     if (not NamesReverse[name]) then
  16.         local first_char = name:sub(1,1):lower()
  17.         if (not Names[first_char]) then Names[first_char] = {} end
  18.         table.insert(Names[first_char], tostring(name))
  19.         NamesReverse[name] = true
  20.     end
  21. end
  22.  
  23. RegisterEvent(tab_complete, "CHAT_MSG_CHANNEL")
  24. RegisterEvent(tab_complete, "CHAT_MSG_CHANNEL_ACTIVE")
  25. RegisterEvent(tab_complete, "CHAT_MSG_CHANNEL_EMOTE")
  26. RegisterEvent(tab_complete, "CHAT_MSG_CHANNEL_EMOTE_ACTIVE")
  27.  
  28. -- tabcomplete(<string>, <number>)
  29. --     returns the entire string regardless of modification
  30. -- nextname(<string>, <boolean>)
  31. --     returns the next name eligible for tab-completion or nil
  32. --     isn't a drop-in replacement for tabcomplete().
  33. --     instead it will do the work of getting the name if one is available.  
  34. --     It can also work as an iterator/generator for use in a for loop if any value is passed to the second argument
  35.  
  36.  
  37. local last_part
  38. local last_part_prev_match -- matches are stored lowercase
  39.  
  40. -- Because of the volatility of the list order as names are seen, we can't store index numbers for quick reference.
  41. -- So it is necessary to iterate over all names in the list to find the next match.  The previous match is stored
  42. -- for this purpose.  Inside of the function, the index of the first match will be retained in case the first match
  43. -- after the previous match is out of scope.
  44. local function iterate_names(part, index)
  45.     if (not index and last_part ~= part) then
  46.         last_part = part
  47.         last_part_prev_match = nil
  48.     end
  49.  
  50.     local first_char = part:sub(1,1):lower() -- part should be guaranteed to be <string> since it's vetted in nextname()
  51.     local Names = Names[first_char]
  52.     if (not Names) then return end
  53.    
  54.     local part_len = part:len()
  55.     local name_index
  56.     local first_index
  57.     local stored_name
  58.    
  59.     for i = (index or 0) + 1, #Names do
  60.         stored_name = Names[i]:lower()
  61.         local stored_name_part = stored_name:sub(1, part_len)
  62.  
  63.         if (stored_name_part == part) then
  64.             if (not first_index) then first_index = i end
  65.             if (stored_name > (not index and last_part_prev_match or "")) then
  66.                 name_index = i
  67.                 break
  68.             end
  69.            
  70.         elseif (stored_name_part > part) then
  71.             if (not index) then
  72.                 name_index = first_index
  73.             end
  74.             break
  75.         end
  76.     end
  77.    
  78.     if (index) then
  79.         return name_index, Names[name_index]
  80.     else
  81.         name_index = name_index or first_index
  82.         last_part_prev_match = Names[name_index] and Names[name_index]:lower()
  83.         return Names[name_index]
  84.     end
  85.    
  86. end
  87.  
  88. function tab_complete:nextname(part, list)
  89.     if (type(part) == "number") then part = tostring(part) end
  90.     assert(type(part) == "string", 'Error!  Needs String') -- TODO better error message
  91.  
  92.     part = part:lower()
  93.  
  94.     -- Sort the list here instead of when names are added to reduce potential latency during combat
  95.     -- One thing to consider, since the sort is only being done at the time of use on the small subset of names starting with
  96.     -- the first character, would it be worthwhile updating list positions on these names and storing it in the reverse lookup
  97.     -- table used to verify existence?  Doing this would also allow for cleaning up the output during use in a for loop.
  98.     local first_char = part:sub(1,1):lower()
  99.     if (self.Names[first_char]) then table.sort(self.Names[first_char], sort_func) end
  100.     -- Could abort now if the table is non-existant, but depending on how the function is being called
  101.     -- requires different responses.  It's simpler to just let it run and respond naturally.
  102.    
  103.     if (list) then
  104.         return iterate_names, part, 0 -- starting at 0 each time because we currently aren't updating list positions on sort
  105.     else
  106.         return iterate_names(part)
  107.     end
  108. end
  109.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement