From c423f77d535b33a07d5930ea73499d0e621069f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Jun 2023 15:34:33 +0900 Subject: [PATCH] Add support for matching full terms using suffixed `!` --- osu.Game/Screens/Select/FilterCriteria.cs | 53 +++++++++++++++++--- osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 22780acfc3..c8804528c7 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using osu.Game.Beatmaps; @@ -62,7 +63,7 @@ namespace osu.Game.Screens.Select string remainingText = value; // First handle quoted segments to ensure we keep inline spaces in exact matches. - foreach (Match quotedSegment in Regex.Matches(searchText, "(\"[^\"]+\")")) + foreach (Match quotedSegment in Regex.Matches(searchText, "(\"[^\"]+\"[!]?)")) { terms.Add(new OptionalTextFilter { SearchTerm = quotedSegment.Value }); remainingText = remainingText.Replace(quotedSegment.Value, string.Empty); @@ -138,7 +139,7 @@ namespace osu.Game.Screens.Select { public bool HasFilter => !string.IsNullOrEmpty(SearchTerm); - public bool Exact { get; private set; } + public MatchMode MatchMode { get; private set; } public bool Matches(string value) { @@ -149,10 +150,18 @@ namespace osu.Game.Screens.Select if (string.IsNullOrEmpty(value)) return false; - if (Exact) - return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + switch (MatchMode) + { + default: + case MatchMode.None: + return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase); - return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase); + case MatchMode.IsolatedPhrase: + return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + case MatchMode.FullPhrase: + return CultureInfo.InvariantCulture.CompareInfo.Compare(value, searchTerm, CompareOptions.IgnoreCase) == 0; + } } private string searchTerm; @@ -162,12 +171,42 @@ namespace osu.Game.Screens.Select get => searchTerm; set { - searchTerm = value.Trim('"'); - Exact = searchTerm != value; + searchTerm = value; + + if (searchTerm.EndsWith("\"!", StringComparison.Ordinal)) + { + searchTerm = searchTerm.Trim('!', '\"'); + MatchMode = MatchMode.FullPhrase; + } + else if (searchTerm.StartsWith('\"')) + { + searchTerm = searchTerm.Trim('\"'); + MatchMode = MatchMode.IsolatedPhrase; + } + else + MatchMode = MatchMode.None; } } public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm; } + + public enum MatchMode + { + /// + /// Match using a simple "contains" substring match. + /// + None, + + /// + /// Match for the search phrase being isolated by spaces, or at the start or end of the text. + /// + IsolatedPhrase, + + /// + /// Match for the search phrase matching the full text in completion. + /// + FullPhrase, + } } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 474a9fdfea..4bc4448291 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Select public static class FilterQueryParser { private static readonly Regex query_syntax_regex = new Regex( - @"\b(?\w+)(?(:|=|(>|<)(:|=)?))(?("".*"")|(\S*))", + @"\b(?\w+)(?(:|=|(>|<)(:|=)?))(?("".*""[!]?)|(\S*))", RegexOptions.Compiled | RegexOptions.IgnoreCase); internal static void ApplyQueries(FilterCriteria criteria, string query)