diff --git a/spec/System/TestSearchHost_spec.lua b/spec/System/TestSearchHost_spec.lua new file mode 100644 index 00000000000..408bd176bd0 --- /dev/null +++ b/spec/System/TestSearchHost_spec.lua @@ -0,0 +1,13 @@ +describe("SearchHost", function() + it("merges all overlapping ranges when word order is ignored", function() + local searchHost = new("SearchHost", function() + return { "caster" } + end, nil, true) + + for char in ("caster ast ste"):gmatch(".") do + searchHost:OnSearchChar(char) + end + + assert.same({ { from = 1, to = 6 } }, searchHost.searchInfos[1].ranges) + end) +end) diff --git a/src/Classes/DropDownControl.lua b/src/Classes/DropDownControl.lua index f1245dfc1a0..07c7efb2ad7 100644 --- a/src/Classes/DropDownControl.lua +++ b/src/Classes/DropDownControl.lua @@ -8,7 +8,7 @@ local m_min = math.min local m_max = math.max local m_floor = math.floor -local DropDownClass = newClass("DropDownControl", "Control", "ControlHost", "TooltipHost", "SearchHost", function(self, anchor, rect, list, selFunc, tooltipText) +local DropDownClass = newClass("DropDownControl", "Control", "ControlHost", "TooltipHost", "SearchHost", function(self, anchor, rect, list, selFunc, tooltipText, ignoreSearchOrder) self.Control(anchor, rect) self.ControlHost() self.TooltipHost(tooltipText) @@ -28,7 +28,8 @@ local DropDownClass = newClass("DropDownControl", "Control", "ControlHost", "Too end end return StripEscapes(listVal) - end + end, + ignoreSearchOrder ) self.controls.scrollBar = new("ScrollBarControl", {"TOPRIGHT",self,"TOPRIGHT"}, {-1, 0, 18, 0}, (self.height - 4) * 4) self.controls.scrollBar.height = function() @@ -111,13 +112,14 @@ function DropDownClass:DrawSearchHighlights(label, searchInfo, x, y, width, heig local endX = 0 local last = 0 SetDrawColor(1, 1, 0, 0.2) + local strippedLabel = StripEscapes(label) for _, range in ipairs(searchInfo.ranges) do if range.from - last - 1 > 0 then - startX = DrawStringWidth(height, "VAR", label:sub(last + 1, range.from - 1)) + x + endX + startX = DrawStringWidth(height, "VAR", strippedLabel:sub(last + 1, range.from - 1)) + x + endX else startX = endX end - endX = DrawStringWidth(height, "VAR", label:sub(range.from, range.to)) + x + startX + endX = DrawStringWidth(height, "VAR", strippedLabel:sub(range.from, range.to)) + x + startX last = range.to DrawImage(nil, startX, y, endX - startX, height) diff --git a/src/Classes/SearchHost.lua b/src/Classes/SearchHost.lua index 60a65e64088..8652e6ef387 100644 --- a/src/Classes/SearchHost.lua +++ b/src/Classes/SearchHost.lua @@ -4,11 +4,12 @@ -- Search host -- -local SearchHostClass = newClass("SearchHost", function(self, listAccessor, valueAccessor) +local SearchHostClass = newClass("SearchHost", function(self, listAccessor, valueAccessor, ignoreOrder) self.searchListAccessor = listAccessor self.valueAccessor = valueAccessor self.searchTerm = "" self.searchInfos = {} + self.ignoreOrder = ignoreOrder or false end) local function splitWords(s) @@ -34,7 +35,7 @@ local function wordsToCaselessPatterns(words) return patterns end -local function matchWords(searchWords, entry, valueAccessor) +local function matchWords(searchWords, entry, valueAccessor, ignoreOrder) local value = valueAccessor and valueAccessor(entry) or entry local searchInfo = { ranges = {}, matches = true } local lastMatchEnd = 0 @@ -43,16 +44,37 @@ local function matchWords(searchWords, entry, valueAccessor) if (from) then local range = { from = from, to = to } table.insert(searchInfo.ranges, range) - lastMatchEnd = to + if not ignoreOrder then + lastMatchEnd = to + end else -- at least one search word did not match at least once (respecting order) searchInfo.matches = false end end + if ignoreOrder then + -- sort to be in left to right order + table.sort(searchInfo.ranges, function(a, b) + return a.from < b.from + end) + -- merge overlapping ranges + local i = 1 + while searchInfo.ranges[i] do + local this = searchInfo.ranges[i] + local next = searchInfo.ranges[i + 1] + if next and next.from <= this.to then + this.to = math.max(this.to, next.to) + table.remove(searchInfo.ranges, i + 1) + -- Check this range again because another range may overlap it. + else + i = i + 1 + end + end + end return searchInfo end -local function matchTerm(searchTerm, list, valueAccessor) +local function matchTerm(searchTerm, list, valueAccessor, ignoreOrder) if not searchTerm or searchTerm == "" or not list then return {} end @@ -60,7 +82,7 @@ local function matchTerm(searchTerm, list, valueAccessor) local searchInfos = {} local searchPatterns = wordsToCaselessPatterns(splitWords(searchTerm)) for idx, entry in ipairs(list) do - searchInfos[idx] = matchWords(searchPatterns, entry, valueAccessor) + searchInfos[idx] = matchWords(searchPatterns, entry, valueAccessor, ignoreOrder) end return searchInfos end @@ -110,7 +132,7 @@ end function SearchHostClass:UpdateSearch() if self.searchListAccessor then - self.searchInfos = matchTerm(self.searchTerm, self.searchListAccessor(), self.valueAccessor) + self.searchInfos = matchTerm(self.searchTerm, self.searchListAccessor(), self.valueAccessor, self.ignoreOrder) self:UpdateMatchCount() end end @@ -124,4 +146,4 @@ end function SearchHostClass:GetSearchTermPretty() local color = self:IsSearchActive() and self.matchCount > 0 and "^xFFFFFF" or "^xFF0000" return color .. self.searchTerm -end \ No newline at end of file +end diff --git a/src/Classes/TradeHelpers.lua b/src/Classes/TradeHelpers.lua index 853404e2855..be5292828ea 100644 --- a/src/Classes/TradeHelpers.lua +++ b/src/Classes/TradeHelpers.lua @@ -79,33 +79,6 @@ end local _optionTradeStatMap --- These option stats are still needed for legacy items, but are no longer --- included in the trade API's 3.29 stats response. -local legacyOptionTradeStats = { - { - type = "explicit", - id = "explicit.stat_2878779644", - text = "Grants Level 20 Summon Bestial # Skill", - options = { - { id = 1, text = "rhoa" }, - { id = 2, text = "ursa" }, - { id = 3, text = "snake" }, - }, - }, - { - type = "explicit", - id = "explicit.stat_3642528642", - text = "Only affects Passives in # Ring", - options = { - { id = 1, text = "small" }, - { id = 2, text = "medium" }, - { id = 3, text = "large" }, - { id = 4, text = "very large" }, - { id = 5, text = "massive" }, - }, - }, -} - ---@param tradeStats table table of data from https://www.pathofexile.com/api/trade2/data/stats ---@return table optionTradeStatMap table containing helper data for matching trade option filters local function getOptionTradeStatMap(tradeStats) @@ -139,14 +112,6 @@ local function getOptionTradeStatMap(tradeStats) end end end - for _, entry in ipairs(legacyOptionTradeStats) do - local matchKey = entry.text:gsub("#", "(.*)"):lower() - optionTradeStatMap.patterns[matchKey] = optionTradeStatMap.patterns[matchKey] or { - type = entry.type, - options = entry.options, - tradeId = entry.id, - } - end _optionTradeStatMap = optionTradeStatMap return _optionTradeStatMap diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index a545412c9c9..4a11f0835c6 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -120,6 +120,7 @@ local function getStatEntries(modType) ["Rune"] = "rune", ["HeartOfTheWell"] = "explicit", ["AgainstTheDarkness"] = "explicit", + ["pseudo"] = "pseudo", ["Enchant"] = "enchant", } if tradeStatCategoryIndices[modType] then @@ -1404,6 +1405,25 @@ Remove: %s will be removed from the search results.]], term, term, term) end end end + local pseudoStats = getStatEntries("pseudo") + -- map stats and such which are clearly not relevant here + local ignoredStats = { + "^pseudo.lake", + "^pseudo.pseudo_lake", + "^pseudo.pseudo_logbook", + "^pseudo.pseudo_temple", + "^pseudo.pseudo_map", + "^pseudo.pseudo_ritual", + } + for _, entry in ipairs(pseudoStats or {}) do + for _, ignored in ipairs(ignoredStats) do + if entry.id:find(ignored) then + goto pseudoContinue + end + end + t_insert(mods, { label = s_format("^7%s (Pseudo)", entry.text), tradeId = entry.id }) + ::pseudoContinue:: + end return mods end -- amount of mod selectors: technically we could have 40, but the more we have the fewer @@ -1445,7 +1465,7 @@ Remove: %s will be removed from the search results.]], term, term, term) selectedMods[i] = copyTable(val) end setModSelectors(controls) - end) + end, nil, true) dropdown.shown = function() return not not selectedMods[i - 1] or i == 1 end @@ -1477,4 +1497,4 @@ Remove: %s will be removed from the search results.]], term, term, term) setModSelectors(controls, getModList()) main:OpenPopup(popupWidth, popupHeight, "Query Options", controls) -end \ No newline at end of file +end