refactor text search routine out into utils fn

develop
Myk Taylor 2023-10-07 18:55:39 -07:00
parent c4b31176a9
commit 5049483330
No known key found for this signature in database
5 changed files with 46 additions and 33 deletions

@ -67,8 +67,10 @@ Template for new versions:
## API ## API
## Lua ## Lua
- ``utils.search_text``: text search routine (generalized from ``widgets.FilteredList``)
## Removed ## 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 # 50.11-r1

@ -3337,6 +3337,20 @@ utils
Exactly like ``erase_sorted_key``, but if field is specified, takes the key from ``item[field]``. 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,...)`` * ``utils.call_with_string(obj,methodname,...)``
Allocates a temporary string object, calls ``obj:method(tmp,...)``, and 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 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 In addition to passing through all attributes supported by List, it
supports: 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_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_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. :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 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 (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 TabBar class
------------ ------------

@ -2017,12 +2017,9 @@ end
-- Filtered List -- -- Filtered List --
------------------- -------------------
FILTER_FULL_TEXT = false
FilteredList = defclass(FilteredList, Widget) FilteredList = defclass(FilteredList, Widget)
FilteredList.ATTRS { FilteredList.ATTRS {
case_sensitive = false,
edit_below = false, edit_below = false,
edit_key = DEFAULT_NIL, edit_key = DEFAULT_NIL,
edit_ignore_keys = DEFAULT_NIL, edit_ignore_keys = DEFAULT_NIL,
@ -2172,7 +2169,6 @@ function FilteredList:setFilter(filter, pos)
pos = nil pos = nil
for i,v in ipairs(self.choices) do for i,v in ipairs(self.choices) do
local ok = true
local search_key = v.search_key local search_key = v.search_key
if not search_key then if not search_key then
if type(v.text) ~= 'table' then if type(v.text) ~= 'table' then
@ -2187,30 +2183,7 @@ function FilteredList:setFilter(filter, pos)
search_key = table.concat(texts, ' ') search_key = table.concat(texts, ' ')
end end
end end
for _,key in ipairs(tokens) do if utils.search_text(search_key, tokens) then
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
table.insert(choices, v) table.insert(choices, v)
cidx[#choices] = i cidx[#choices] = i
if ipos == i then if ipos == i then

@ -460,6 +460,32 @@ function erase_sorted(vector,item,field,cmp)
return erase_sorted_key(vector,key,field,cmp) return erase_sorted_key(vector,key,field,cmp)
end 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 -- Calls a method with a string temporary
function call_with_string(obj,methodname,...) function call_with_string(obj,methodname,...)
return dfhack.with_temp_object( return dfhack.with_temp_object(

@ -151,7 +151,6 @@ function ItemSelection:init()
widgets.FilteredList{ widgets.FilteredList{
view_id='flist', view_id='flist',
frame={t=0, b=0}, frame={t=0, b=0},
case_sensitive=false,
choices=choices, choices=choices,
icon_width=2, icon_width=2,
on_submit=self:callback('toggle_group'), on_submit=self:callback('toggle_group'),