diff --git a/table/filter.go b/table/filter.go index 765e5b3..3809078 100644 --- a/table/filter.go +++ b/table/filter.go @@ -107,6 +107,8 @@ func filterFuncContains(input FilterFuncInput) bool { // filterFuncFuzzy returns a filterFunc that performs case-insensitive fuzzy // matching (subsequence) over the concatenation of all filterable column values. +// it supports multiple filter tokens separated by space which must all match. +// also, if a filter token starts with the quote character (') it has to match literally. func filterFuncFuzzy(input FilterFuncInput) bool { filter := strings.TrimSpace(input.Filter) if filter == "" { @@ -135,6 +137,15 @@ func filterFuncFuzzy(input FilterFuncInput) bool { } for _, token := range strings.Fields(strings.ToLower(filter)) { + if token[0] == '\'' && len(token) > 1 { + // compare literally + if !strings.Contains(haystack, strings.ToLower(token[1:])) { + return false + } + + continue + } + if !fuzzySubsequenceMatch(haystack, token) { return false } diff --git a/table/filter_test.go b/table/filter_test.go index 271553b..8787f88 100644 --- a/table/filter_test.go +++ b/table/filter_test.go @@ -563,3 +563,36 @@ func TestFuzzySubSequenceMatch_EmptyString(t *testing.T) { assert.False(t, fuzzySubsequenceMatch("", "a"), "non-empty needle should not match empty haystack") assert.True(t, fuzzySubsequenceMatch("", ""), "empty needle should match empty haystack") } + +func TestFuzzyFilter_LiteralMatches(t *testing.T) { + cols := []Column{ + NewColumn("name", "Name", 10).WithFiltered(true), + NewColumn("city", "City", 10).WithFiltered(true), + } + row := NewRow(RowData{ + "name": "Acme", + "city": "Stuttgart", + }) + + type testCase struct { + name string + filter string + shouldMatch bool + } + + testCases := []testCase{ + {"literal match", "'Stutt", true}, + {"failing literal match", "'Stutgar", false}, + {"combined literal match", "'Stut tat", true}, + {"invalid literal filter", "'", false}, + {"invalid literal filter plus fuzzy", "' z", false}, + } + + for _, tc := range testCases { + assert.Equal(t, tc.shouldMatch, filterFuncFuzzy(FilterFuncInput{ + Columns: cols, + Row: row, + Filter: tc.filter, + })) + } +}