From c7392ceecd4cf88e256b13b2b89fadeb31777c29 Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Thu, 25 Dec 2025 14:05:09 -0500 Subject: [PATCH] search: Restore implied 'AND' behavior in non-regex file match mode. If multiple words are encountered, they will be treated as separate search terms and be matched separately, requiring all to 'hit' for a file to be included in results. Full wildcard matching will still work for each word. ref: https://github.com/linuxmint/mint22.3-beta/issues/29 --- libnemo-private/nemo-search-engine-advanced.c | 49 +++++++++++++++++-- src/nemo-query-editor.c | 27 +++++----- 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/libnemo-private/nemo-search-engine-advanced.c b/libnemo-private/nemo-search-engine-advanced.c index 8f16c5aa1..b28b1e395 100644 --- a/libnemo-private/nemo-search-engine-advanced.c +++ b/libnemo-private/nemo-search-engine-advanced.c @@ -66,7 +66,7 @@ typedef struct { GRegex *newline_re; GRegex *filename_re; - GPatternSpec *filename_glob_pattern; + GList *filename_glob_patterns; GMutex hit_list_lock; GList *hit_list; // holds FileSearchResults @@ -445,6 +445,7 @@ search_thread_data_new (NemoSearchEngineAdvanced *engine, } } else { gchar *text, *normalized, *cased; + gchar **words; text = nemo_query_get_file_pattern (query); normalized = g_utf8_normalize (text, -1, G_NORMALIZE_NFD); @@ -455,7 +456,37 @@ search_thread_data_new (NemoSearchEngineAdvanced *engine, cased = g_strdup (normalized); } - data->filename_glob_pattern = g_pattern_spec_new (cased); + /* In (< 6.6), whitespace between words was treated as AND, and implied a filename + * must include *this* AND *that* AND *other*. To preserve this behavior, split + * the search text and wrap segments individually (*segment*) if no wildcard characters + * already included, and generate a GPatternSpec for each segment. */ + words = g_strsplit_set (cased, " \t\r\n", -1); + data->filename_glob_patterns = NULL; + + for (gint i = 0; words[i] != NULL; i++) { + if (words[i][0] != '\0') { + g_autofree gchar *word_pattern = NULL; + + if (strchr (words[i], '*') == NULL && strchr (words[i], '?') == NULL) { + word_pattern = g_strdup_printf ("*%s*", words[i]); + } else { + word_pattern = g_strdup (words[i]); + } + + DEBUG ("pattern is '%s'", word_pattern); + + data->filename_glob_patterns = g_list_prepend (data->filename_glob_patterns, + g_pattern_spec_new (word_pattern)); + } + } + + g_strfreev (words); + + data->filename_glob_patterns = g_list_reverse (data->filename_glob_patterns); + + if (data->filename_glob_patterns == NULL) { + data->filename_glob_patterns = g_list_prepend (NULL, g_pattern_spec_new ("*")); + } g_free (text); g_free (normalized); @@ -540,7 +571,7 @@ search_thread_data_free (SearchThreadData *data) g_clear_pointer (&data->content_re, g_regex_unref); g_clear_pointer (&data->newline_re, g_regex_unref); g_clear_pointer (&data->filename_re, g_regex_unref); - g_clear_pointer (&data->filename_glob_pattern, g_pattern_spec_free); + g_list_free_full (data->filename_glob_patterns, (GDestroyNotify) g_pattern_spec_free); g_timer_destroy (data->timer); g_mutex_clear (&data->hit_list_lock); @@ -1011,7 +1042,17 @@ visit_directory (GFile *dir, SearchThreadData *data) } gchar *cased_reversed = g_utf8_strreverse (cased, -1); - hit = g_pattern_spec_match (data->filename_glob_pattern, strlen (cased), cased, cased_reversed); + gsize len = strlen (cased); + + hit = TRUE; + for (GList *l = data->filename_glob_patterns; l != NULL; l = l->next) { + GPatternSpec *pattern = l->data; + if (!g_pattern_spec_match (pattern, len, cased, cased_reversed)) { + hit = FALSE; + break; + } + } + g_free (cased); g_free (cased_reversed); } diff --git a/src/nemo-query-editor.c b/src/nemo-query-editor.c index e8114ce42..1e1e607a7 100644 --- a/src/nemo-query-editor.c +++ b/src/nemo-query-editor.c @@ -511,31 +511,26 @@ nemo_query_editor_get_query (NemoQueryEditor *editor) nemo_query_set_location (query, editor->priv->current_uri); sanitized = get_sanitized_file_search_string (editor); - GString *file_string = g_string_new (sanitized); - g_free (sanitized); /* - Search for 'all' needs to be different depending on whether regex is enabled for files. - - Our non-regex search traditionally implied '*words*', not 'words'. Now that we're using - GPatternSpec, we need to add those ourselves. */ + - For regex: use '.*' for empty search + - For non-regex: the search engine will handle splitting by whitespace and wrapping + each word with asterisks for user convenience. */ if (nemo_query_get_use_file_regex (query)) { - if (g_strcmp0 (file_string->str, "") == 0) { - g_string_assign (file_string, ".*"); + if (g_strcmp0 (sanitized, "") == 0) { + nemo_query_set_file_pattern (query, ".*"); + } else { + nemo_query_set_file_pattern (query, sanitized); } } else { - if (g_strcmp0 (file_string->str, "") == 0) { - g_string_assign (file_string, "*"); + if (g_strcmp0 (sanitized, "") == 0) { + nemo_query_set_file_pattern (query, "*"); } else { - if (!g_str_has_prefix (file_string->str, "*")) { - g_string_insert_c (file_string, 0, '*'); - } - if (!g_str_has_suffix (file_string->str, "*")) { - g_string_append_c (file_string, '*'); - } + nemo_query_set_file_pattern (query, sanitized); } } - nemo_query_set_file_pattern (query, file_string->str); - g_string_free (file_string, TRUE); + g_free (sanitized); return query; }