diff --git a/docs/changelog.txt b/docs/changelog.txt index 1a97e637d..57b78c087 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -67,8 +67,10 @@ Template for new versions: ## API ## Lua +- ``utils.search_text``: text search routine (generalized from ``widgets.FilteredList``) ## Removed +- ``FILTER_FULL_TEXT``: moved from ``gui.widgets`` to ``utils``; if your full text search preference is lost, please reset it in `gui/control-panel` # 50.11-r1 diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 9f9a3e374..f5314e4fb 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -3337,6 +3337,20 @@ utils Exactly like ``erase_sorted_key``, but if field is specified, takes the key from ``item[field]``. +* ``utils.search_text(text,search_tokens)`` + + Returns true if all the search tokens are found within ``text``. The text and + search tokens are normalized to lower case and special characters (e.g. ``A`` + with a circle on it) are converted to their "basic" forms (e.g. ``a``). + ``search_tokens`` can be a string or a table of strings. If it is a string, + it is split into space-separated tokens before matching. The search tokens + are treated literally, so any special regular expression characters do not + need to be escaped. If ``utils.FILTER_FULL_TEXT`` is ``true``, then the + search tokens can match any part of ``text``. If it is ``false``, then the + matches must happen at the beginning of words within ``text``. You can change + the value of ``utils.FILTER_FULL_TEXT`` in `gui/control-panel` on the + "Preferences" tab. + * ``utils.call_with_string(obj,methodname,...)`` Allocates a temporary string object, calls ``obj:method(tmp,...)``, and @@ -5291,12 +5305,11 @@ FilteredList class ------------------ This widget combines List, EditField and Label into a combo-box like -construction that allows filtering the list by subwords of its items. +construction that allows filtering the list. In addition to passing through all attributes supported by List, it supports: -:case_sensitive: If ``true``, matching is case sensitive. Defaults to ``false``. :edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. :edit_below: If true, the edit field is placed below the list instead of above. :edit_key: If specified, the edit field is disabled until this key is pressed. @@ -5345,9 +5358,9 @@ Filter behavior: By default, the filter matches substrings that start at the beginning of a word (or after any punctuation). You can instead configure filters to match any -substring with a command like:: +substring across the full text with a command like:: - :lua require('gui.widgets').FILTER_FULL_TEXT=true + :lua require('utils').FILTER_FULL_TEXT=true TabBar class ------------ diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 05d237f35..61da7af65 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -2017,12 +2017,9 @@ end -- Filtered List -- ------------------- -FILTER_FULL_TEXT = false - FilteredList = defclass(FilteredList, Widget) FilteredList.ATTRS { - case_sensitive = false, edit_below = false, edit_key = DEFAULT_NIL, edit_ignore_keys = DEFAULT_NIL, @@ -2172,7 +2169,6 @@ function FilteredList:setFilter(filter, pos) pos = nil for i,v in ipairs(self.choices) do - local ok = true local search_key = v.search_key if not search_key then if type(v.text) ~= 'table' then @@ -2187,30 +2183,7 @@ function FilteredList:setFilter(filter, pos) search_key = table.concat(texts, ' ') end end - for _,key in ipairs(tokens) do - key = key:escape_pattern() - if key ~= '' then - search_key = dfhack.toSearchNormalized(search_key) - key = dfhack.toSearchNormalized(key) - if not self.case_sensitive then - search_key = string.lower(search_key) - key = string.lower(key) - end - - -- the separate checks for non-space or non-punctuation allows - -- punctuation itself to be matched if that is useful (e.g. - -- filenames or parameter names) - if not FILTER_FULL_TEXT and not search_key:match('%f[^%p\x00]'..key) - and not search_key:match('%f[^%s\x00]'..key) then - ok = false - break - elseif FILTER_FULL_TEXT and not search_key:find(key) then - ok = false - break - end - end - end - if ok then + if utils.search_text(search_key, tokens) then table.insert(choices, v) cidx[#choices] = i if ipos == i then diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 3883439f1..fb41835da 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -460,6 +460,32 @@ function erase_sorted(vector,item,field,cmp) return erase_sorted_key(vector,key,field,cmp) end +FILTER_FULL_TEXT = false + +function search_text(text, search_tokens) + text = dfhack.toSearchNormalized(text) + if type(search_tokens) ~= 'table' then + search_tokens = search_tokens:split() + end + + for _,search_token in ipairs(search_tokens) do + if search_token == '' then goto continue end + search_token = dfhack.toSearchNormalized(search_token:escape_pattern()) + + -- the separate checks for non-space or non-punctuation allows + -- punctuation itself to be matched if that is useful (e.g. + -- filenames or parameter names) + if not FILTER_FULL_TEXT and not text:match('%f[^%p\x00]'..search_token) + and not text:match('%f[^%s\x00]'..search_token) then + return false + elseif FILTER_FULL_TEXT and not text:find(search_token) then + return false + end + ::continue:: + end + return true +end + -- Calls a method with a string temporary function call_with_string(obj,methodname,...) return dfhack.with_temp_object( diff --git a/plugins/lua/buildingplan/itemselection.lua b/plugins/lua/buildingplan/itemselection.lua index 9dfd0cc69..4b8ee73d8 100644 --- a/plugins/lua/buildingplan/itemselection.lua +++ b/plugins/lua/buildingplan/itemselection.lua @@ -151,7 +151,6 @@ function ItemSelection:init() widgets.FilteredList{ view_id='flist', frame={t=0, b=0}, - case_sensitive=false, choices=choices, icon_width=2, on_submit=self:callback('toggle_group'),