From 80a7efdc1d395f44a14e7bf1085a5d1b3fe45547 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 23 Mar 2016 18:45:32 +1100 Subject: UI: multi word filtering in search menu D1080 by @rockets, with own improvements to tests --- source/blender/blenlib/BLI_string.h | 6 ++ source/blender/blenlib/intern/string.c | 66 ++++++++++++++ .../editors/interface/interface_templates.c | 16 +++- tests/gtests/blenlib/BLI_string_test.cc | 100 +++++++++++++++++++++ tests/gtests/testing/testing.h | 9 ++ 5 files changed, 196 insertions(+), 1 deletion(-) diff --git a/source/blender/blenlib/BLI_string.h b/source/blender/blenlib/BLI_string.h index 82780394ce7..d137806c575 100644 --- a/source/blender/blenlib/BLI_string.h +++ b/source/blender/blenlib/BLI_string.h @@ -74,6 +74,7 @@ size_t BLI_str_format_int_grouped(char dst[16], int num) ATTR_NONNULL(); int BLI_strcaseeq(const char *a, const char *b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); char *BLI_strcasestr(const char *s, const char *find) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); +char *BLI_strncasestr(const char *s, const char *find, size_t len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); int BLI_strcasecmp(const char *s1, const char *s2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); int BLI_strncasecmp(const char *s1, const char *s2, size_t len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); int BLI_natstrcmp(const char *s1, const char *s2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); @@ -97,6 +98,11 @@ size_t BLI_str_partition_ex( const char *str, const char *end, const char delim[], const char **sep, const char **suf, const bool from_right) ATTR_NONNULL(1, 3, 4, 5); +int BLI_string_find_split_words( + const char *str, const size_t len, + const char delim, int r_words[][2], int words_max) + ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c index aad4649344f..2f67b0e57a3 100644 --- a/source/blender/blenlib/intern/string.c +++ b/source/blender/blenlib/intern/string.c @@ -525,6 +525,26 @@ char *BLI_strcasestr(const char *s, const char *find) return ((char *) s); } +/** + * Variation of #BLI_strcasestr with string length limited to \a len + */ +char *BLI_strncasestr(const char *s, const char *find, size_t len) +{ + register char c, sc; + + if ((c = *find++) != 0) { + c = tolower(c); + do { + do { + if ((sc = *s++) == 0) + return (NULL); + sc = tolower(sc); + } while (sc != c); + } while (BLI_strncasecmp(s, find, len - 1) != 0); + s--; + } + return ((char *)s); +} int BLI_strcasecmp(const char *s1, const char *s2) { @@ -962,3 +982,49 @@ size_t BLI_str_format_int_grouped(char dst[16], int num) return (size_t)(p_dst - dst); } + +/** + * Find the ranges needed to split \a str into its individual words. + * + * \param str: The string to search for words. + * \param len: Size of the string to search. + * \param delim: Character to use as a delimiter. + * \param r_words: Info about the words found. Set to [index, len] pairs. + * \param words_max: Max number of words to find + * \return The number of words found in \a str + */ +int BLI_string_find_split_words( + const char *str, const size_t len, + const char delim, int r_words[][2], int words_max) +{ + int n = 0, i; + bool charsearch = true; + + /* Skip leading spaces */ + for (i = 0; (i < len) && (str[i] != '\0'); i++) { + if (str[i] != delim) { + break; + } + } + + for (; (i < len) && (str[i] != '\0') && (n < words_max); i++) { + if ((str[i] != delim) && (charsearch == true)) { + r_words[n][0] = i; + charsearch = false; + } + else { + if ((str[i] == delim) && (charsearch == false)) { + r_words[n][1] = i - r_words[n][0]; + n++; + charsearch = true; + } + } + } + + if (charsearch == false) { + r_words[n][1] = i - r_words[n][0]; + n++; + } + + return n; +} diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 7bc9beaddb7..e48b09d1858 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -39,6 +39,7 @@ #include "DNA_texture_types.h" #include "BLI_utildefines.h" +#include "BLI_alloca.h" #include "BLI_string.h" #include "BLI_ghash.h" #include "BLI_rect.h" @@ -3273,15 +3274,28 @@ static void operator_call_cb(bContext *C, void *UNUSED(arg1), void *arg2) static void operator_search_cb(const bContext *C, void *UNUSED(arg), const char *str, uiSearchItems *items) { GHashIterator iter; + const size_t str_len = strlen(str); + const int words_max = (str_len / 2) + 1; + int (*words)[2] = BLI_array_alloca(words, words_max); + + const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max); for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter); BLI_ghashIterator_step(&iter)) { wmOperatorType *ot = BLI_ghashIterator_getValue(&iter); const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name); + int index; if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) continue; - if (BLI_strcasestr(ot_ui_name, str)) { + /* match name against all search words */ + for (index = 0; index < words_len; index++) { + if (!BLI_strncasestr(ot_ui_name, str + words[index][0], words[index][1])) { + break; + } + } + + if (index == words_len) { if (WM_operator_poll((bContext *)C, ot)) { char name[256]; int len = strlen(ot_ui_name); diff --git a/tests/gtests/blenlib/BLI_string_test.cc b/tests/gtests/blenlib/BLI_string_test.cc index fa10e21730b..5559b8d7af0 100644 --- a/tests/gtests/blenlib/BLI_string_test.cc +++ b/tests/gtests/blenlib/BLI_string_test.cc @@ -363,3 +363,103 @@ TEST(string, StrFormatIntGrouped) BLI_str_format_int_grouped(num_str, num = -999); EXPECT_STREQ("-999", num_str); } + +#define STRING_FIND_SPLIT_WORDS_EX(word_str_src, word_str_src_len, limit_words, ...) \ +{ \ + int word_info[][2] = \ + {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; \ + const int word_cmp[][2] = __VA_ARGS__; \ + const int word_cmp_size_input = ARRAY_SIZE(word_cmp) - (limit_words ? 1 : 0); \ + const int word_cmp_size = ARRAY_SIZE(word_cmp); \ + const int word_num = BLI_string_find_split_words( \ + word_str_src, word_str_src_len, ' ', word_info, word_cmp_size_input); \ + EXPECT_EQ(word_num, word_cmp_size - 1); \ + EXPECT_EQ_ARRAY_ND(word_cmp, word_info, word_cmp_size, 2); \ +} ((void)0) + +#define STRING_FIND_SPLIT_WORDS(word_str_src, ...) \ + STRING_FIND_SPLIT_WORDS_EX(word_str_src, strlen(word_str_src), false, __VA_ARGS__) + +/* BLI_string_find_split_words */ +TEST(string, StringFindSplitWords_Single) +{ + STRING_FIND_SPLIT_WORDS("t", {{0, 1}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS("test", {{0, 4}, {-1, -1}}); +} +TEST(string, StringFindSplitWords_Triple) +{ + STRING_FIND_SPLIT_WORDS("f t w", {{0, 1}, {2, 1}, {4, 1}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS("find three words", {{0, 4}, {5, 5}, {11, 5}, {-1, -1}}); +} +TEST(string, StringFindSplitWords_Spacing) +{ + STRING_FIND_SPLIT_WORDS("# ## ### ####", {{0, 1}, {2, 2}, {5, 3}, {9, 4}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS("# # # #", {{0, 1}, {3, 1}, {7, 1}, {12, 1}, {-1, -1}}); +} +TEST(string, StringFindSplitWords_Trailing_Left) +{ + STRING_FIND_SPLIT_WORDS(" t", {{3, 1}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS(" test", {{3, 4}, {-1, -1}}); +} +TEST(string, StringFindSplitWords_Trailing_Right) +{ + STRING_FIND_SPLIT_WORDS("t ", {{0, 1}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS("test ", {{0, 4}, {-1, -1}}); +} +TEST(string, StringFindSplitWords_Trailing_LeftRight) +{ + STRING_FIND_SPLIT_WORDS(" surrounding space test 123 ", {{3, 11}, {15, 5}, {21, 4}, {28, 3}, {-1, -1}}); +} +TEST(string, StringFindSplitWords_Blank) +{ + STRING_FIND_SPLIT_WORDS("", {{-1, -1}}); +} +TEST(string, StringFindSplitWords_Whitespace) +{ + STRING_FIND_SPLIT_WORDS(" ", {{-1, -1}}); + STRING_FIND_SPLIT_WORDS(" ", {{-1, -1}}); +} +TEST(string, StringFindSplitWords_LimitWords) +{ + const char *words = "too many words"; + const int words_len = strlen(words); + STRING_FIND_SPLIT_WORDS_EX(words, words_len, false, {{0, 3}, {4, 4}, {9, 5}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS_EX(words, words_len, true, {{0, 3}, {4, 4}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS_EX(words, words_len, true, {{0, 3}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS_EX(words, words_len, true, {{-1, -1}}); +} +TEST(string, StringFindSplitWords_LimitChars) +{ + const char *words = "too many chars"; + const int words_len = strlen(words); + STRING_FIND_SPLIT_WORDS_EX(words, words_len, false, {{0, 3}, {4, 4}, {9, 5}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS_EX(words, words_len - 1, false, {{0, 3}, {4, 4}, {9, 4}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS_EX(words, words_len - 5, false, {{0, 3}, {4, 4}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS_EX(words, 1, false, {{0, 1}, {-1, -1}}); + STRING_FIND_SPLIT_WORDS_EX(words, 0, false, {{-1, -1}}); +} + +#undef STRING_FIND_SPLIT_WORDS + + +/* BLI_strncasestr */ +TEST(string, StringStrncasestr) +{ + const char *str_test0 = "search here"; + const char *res; + + res = BLI_strncasestr(str_test0, "", 0); + EXPECT_EQ(str_test0, res); + + res = BLI_strncasestr(str_test0, "her", 3); + EXPECT_EQ(str_test0 + 7, res); + + res = BLI_strncasestr(str_test0, "ARCh", 4); + EXPECT_EQ(str_test0 + 2, res); + + res = BLI_strncasestr(str_test0, "earcq", 4); + EXPECT_EQ(str_test0 + 1, res); + + res = BLI_strncasestr(str_test0, "not there", 9); + EXPECT_EQ(NULL, res); +} diff --git a/tests/gtests/testing/testing.h b/tests/gtests/testing/testing.h index b0a6379e5c0..1594ed3926c 100644 --- a/tests/gtests/testing/testing.h +++ b/tests/gtests/testing/testing.h @@ -82,4 +82,13 @@ inline void EXPECT_EQ_ARRAY(const T *expected, T *actual, const size_t N) { } } +template +inline void EXPECT_EQ_ARRAY_ND(const T *expected, T *actual, const size_t N, const size_t D) { + for(size_t i = 0; i < N; ++i) { + for(size_t j = 0; j < D; ++j) { + EXPECT_EQ(expected[i][j], actual[i][j]); + } + } +} + #endif // __BLENDER_TESTING_H__ -- cgit v1.2.3