From 5eee39ce645735558426663137dd7c58ef937627 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 22 Jan 2025 23:40:11 +0100 Subject: [PATCH] Use regex to search for all words separately --- Systems/Handbook/Gui/GuiDialogHandbook.cs | 105 ++++++------------ .../Handbook/Gui/GuiHandbookCommandPage.cs | 13 +-- .../Handbook/Gui/GuiHandbookItemStackPage.cs | 14 +-- Systems/Handbook/Gui/GuiHandbookPage.cs | 13 ++- Systems/Handbook/Gui/GuiHandbookTextPage.cs | 13 +-- .../Tutorial/GuiHandbookTutorialPage.cs | 10 -- 6 files changed, 63 insertions(+), 105 deletions(-) diff --git a/Systems/Handbook/Gui/GuiDialogHandbook.cs b/Systems/Handbook/Gui/GuiDialogHandbook.cs index ad128963..5398bfc8 100644 --- a/Systems/Handbook/Gui/GuiDialogHandbook.cs +++ b/Systems/Handbook/Gui/GuiDialogHandbook.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Vintagestory.API.Client; using Vintagestory.API.Common; using Vintagestory.API.Config; @@ -451,87 +452,53 @@ protected void FilterItemsBySearchText(string text) FilterItems(); } - + private int CountMatches(string text, Regex regex) + { + return regex.Matches(text).Count; + } public void FilterItems() { - string text = currentSearchText?.ToLowerInvariant(); - string[] texts; - bool logicalAnd = false; // true if "and" is present; false if "or" or no logical operator is present - if (text == null) - { - texts = new string[0]; - } - else - { - if (text.Contains(" or ", StringComparison.Ordinal)) - { - texts = text.Split(new string[] { " or " }, StringSplitOptions.RemoveEmptyEntries).OrderBy(str => str.Length).ToArray(); - } - else if (text.Contains(" and ", StringComparison.Ordinal)) - { - texts = text.Split(new string[] { " and " }, StringSplitOptions.RemoveEmptyEntries).OrderBy(str => str.Length).ToArray(); - logicalAnd = texts.Length > 1; - } - else - { - texts = new string[] { text }; - } - int countEmpty = 0; - for (int i = 0; i < texts.Length; i++) - { - texts[i] = texts[i].ToSearchFriendly().Trim(); // Only remove diacritical marks etc after splitting on " or ", helps with languages such as Icelandic where "ör" is a word (a type of bow) - if (texts[i].Length == 0) countEmpty++; - } - if (countEmpty > 0) - { - string[] newTexts = new string[texts.Length - countEmpty]; - int j = 0; - for (int i = 0; i < texts.Length; i++) - { - if (texts[i].Length == 0) continue; - newTexts[j++] = texts[i]; - } - texts = newTexts; - logicalAnd = logicalAnd && texts.Length > 1; - } - } - - List foundPages = new List(); shownHandbookPages.Clear(); if (!loadingPagesAsync) { - for (int i = 0; i < allHandbookPages.Count; i++) - { - GuiHandbookPage page = allHandbookPages[i]; - if (currentCatgoryCode != null && page.CategoryCode != currentCatgoryCode) continue; - if (page.IsDuplicate) continue; + string searchText = currentSearchText ?? ""; + string[] searchWords = Regex.Split(searchText, "\\s+", RegexOptions.Multiline); + var pattern = $"({String.Join("|", searchWords.Where(w => w != "").Select(w => $"{Regex.Escape(w.ToSearchFriendly().Trim())}"))})"; + var regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.Multiline); - float weight = 1; - bool matched = logicalAnd; // Normally (for no logical operator or logical operator OR) no match unless any found; if it's logical AND then we have no match if any in texts are not found (and texts length cannot be 0) + var weightedPages = new List(); + allHandbookPages.ForEach(page => + { + if (currentCatgoryCode != null && page.CategoryCode != currentCatgoryCode || page.IsDuplicate) return; - for (int j = 0; j < texts.Length; j++) - { - weight = page.GetTextMatchWeight(texts[j]); - if (weight > 0) - { - if (!logicalAnd) { matched = true; break; } - } - else + var pageText = page.GetPageText(); + var titleMathces = CountMatches(pageText.Title ?? "", regex); + var textMatches = CountMatches(pageText.Text ?? "", regex); + if (titleMathces > 0 || textMatches > 0) + weightedPages.Add(new WeightedHandbookPage { - if (logicalAnd) { matched = false; break; }; - } - } - if (!matched && texts.Length > 0) continue; - - foundPages.Add(new WeightedHandbookPage() { Page = page, Weight = weight }); - } - - foreach (var val in foundPages.OrderByDescending(wpage => wpage.Weight)) + Page = page, + TitleMatches = titleMathces, + TitleLength = pageText.Title?.Length ?? 0, + TextMatches = textMatches, + }); + }); + if (searchText.Length > 0) { - shownHandbookPages.Add(val.Page); + weightedPages.Sort((a, b) => + { + var titleSort = b.TitleMatches - a.TitleMatches; + if (titleSort != 0) return titleSort; + var textSort = b.TextMatches - a.TextMatches; + if (textSort != 0) return textSort; + // Prefer shorter matches, efffectively prioritizing more "exact" matches + // e. g. "Iron Plate" over "Plate Armor (Iron)" when searching "Iron Plate" + return a.TitleLength - b.TitleLength; + }); } + weightedPages.ForEach(page => shownHandbookPages.Add(page.Page)); } GuiElementFlatList stacklist = overviewGui.GetFlatList("stacklist"); diff --git a/Systems/Handbook/Gui/GuiHandbookCommandPage.cs b/Systems/Handbook/Gui/GuiHandbookCommandPage.cs index d7a71cac..565ac71b 100644 --- a/Systems/Handbook/Gui/GuiHandbookCommandPage.cs +++ b/Systems/Handbook/Gui/GuiHandbookCommandPage.cs @@ -75,15 +75,12 @@ protected virtual RichTextComponentBase[] GetPageText(ICoreClientAPI capi, ItemS return VtmlUtil.Richtextify(capi, "" + Command.CallSyntax + "\n\n" + TextCacheAll, CairoFont.WhiteSmallText()); } - public override float GetTextMatchWeight(string searchText) + public override PageText GetPageText() { - string title = TextCacheTitle; - if (title.Equals(searchText, StringComparison.InvariantCultureIgnoreCase)) return searchWeightOffset + 3; - if (title.StartsWith(searchText + " ", StringComparison.InvariantCultureIgnoreCase)) return searchWeightOffset + 2.75f + Math.Max(0, 15 - title.Length) / 100f; - if (title.StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase)) return searchWeightOffset + 2.5f + Math.Max(0, 15 - title.Length) / 100f; - if (title.CaseInsensitiveContains(searchText)) return searchWeightOffset + 2; - if (TextCacheAll.CaseInsensitiveContains(searchText)) return searchWeightOffset + 1; - return 0; + return new PageText { + Title = TextCacheTitle, + Text = TextCacheAll, + }; } } diff --git a/Systems/Handbook/Gui/GuiHandbookItemStackPage.cs b/Systems/Handbook/Gui/GuiHandbookItemStackPage.cs index afe1d9ee..53cce5c3 100644 --- a/Systems/Handbook/Gui/GuiHandbookItemStackPage.cs +++ b/Systems/Handbook/Gui/GuiHandbookItemStackPage.cs @@ -117,15 +117,13 @@ protected virtual RichTextComponentBase[] GetPageText(ICoreClientAPI capi, ItemS return Stack.Collectible.GetBehavior()?.GetHandbookInfo(dummySlot, capi, allStacks, openDetailPageFor) ?? new RichTextComponentBase[0]; } - public override float GetTextMatchWeight(string searchText) + public override PageText GetPageText() { - string title = TextCacheTitle; - if (title.Equals(searchText, StringComparison.InvariantCultureIgnoreCase)) return searchWeightOffset + 3; - if (title.StartsWith(searchText + " ", StringComparison.InvariantCultureIgnoreCase)) return searchWeightOffset + 2.75f + Math.Max(0, 15 - title.Length) / 100f; - if (title.StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase)) return searchWeightOffset + 2.5f + Math.Max(0, 15 - title.Length) / 100f; - if (title.CaseInsensitiveContains(searchText)) return searchWeightOffset + 2; - if (TextCacheAll.CaseInsensitiveContains(searchText)) return searchWeightOffset + 1; - return 0; + return new PageText + { + Title = TextCacheTitle, + Text = TextCacheAll, + }; } } diff --git a/Systems/Handbook/Gui/GuiHandbookPage.cs b/Systems/Handbook/Gui/GuiHandbookPage.cs index 572acbbb..6290071b 100644 --- a/Systems/Handbook/Gui/GuiHandbookPage.cs +++ b/Systems/Handbook/Gui/GuiHandbookPage.cs @@ -5,10 +5,18 @@ namespace Vintagestory.GameContent { public struct WeightedHandbookPage { - public float Weight; + public int TitleMatches; + public int TextMatches; + public int TitleLength; public GuiHandbookPage Page; } + public struct PageText + { + public string Title; + public string Text; + } + public abstract class GuiHandbookPage : IFlatListItem { public int PageNumber; @@ -25,8 +33,7 @@ public abstract class GuiHandbookPage : IFlatListItem public abstract void RenderListEntryTo(ICoreClientAPI capi, float dt, double x, double y, double cellWdith, double cellHeight); public abstract void Dispose(); public bool Visible { get; set; } = true; - - public abstract float GetTextMatchWeight(string text); + public abstract PageText GetPageText(); public abstract bool IsDuplicate { get; } public abstract void ComposePage(GuiComposer detailViewGui, ElementBounds textBounds, ItemStack[] allstacks, ActionConsumable openDetailPageFor); diff --git a/Systems/Handbook/Gui/GuiHandbookTextPage.cs b/Systems/Handbook/Gui/GuiHandbookTextPage.cs index 7d8ed3b7..691ed3b7 100644 --- a/Systems/Handbook/Gui/GuiHandbookTextPage.cs +++ b/Systems/Handbook/Gui/GuiHandbookTextPage.cs @@ -53,14 +53,13 @@ public void Recompose(ICoreClientAPI capi) Texture = new TextTextureUtil(capi).GenTextTexture(Lang.Get(Title), CairoFont.WhiteSmallText()); } - public override float GetTextMatchWeight(string searchText) + public override PageText GetPageText() { - if (titleCached.Equals(searchText, StringComparison.InvariantCultureIgnoreCase)) return 4; - if (titleCached.StartsWith(searchText + " ", StringComparison.InvariantCultureIgnoreCase)) return 3.5f; - if (titleCached.StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase)) return 3f; - if (titleCached.CaseInsensitiveContains(searchText)) return 2.75f; - if (Text.CaseInsensitiveContains(searchText)) return 1.25f; - return 0; + return new PageText + { + Title = titleCached, + Text = Text, + }; } public override void RenderListEntryTo(ICoreClientAPI capi, float dt, double x, double y, double cellWidth, double cellHeight) diff --git a/Systems/Handbook/Tutorial/GuiHandbookTutorialPage.cs b/Systems/Handbook/Tutorial/GuiHandbookTutorialPage.cs index 0e2f6211..5d793f34 100644 --- a/Systems/Handbook/Tutorial/GuiHandbookTutorialPage.cs +++ b/Systems/Handbook/Tutorial/GuiHandbookTutorialPage.cs @@ -149,16 +149,6 @@ public override void ComposePage(GuiComposer detailViewGui, ElementBounds textBo detailViewGui.AddRichtext(comps, textBounds, "richtext"); } - public override float GetTextMatchWeight(string text) - { - /*string title = TextCacheTitle; - if (title.Equals(searchText, StringComparison.InvariantCultureIgnoreCase)) return 3; - if (title.StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase)) return 2.5f; - if (title.CaseInsensitiveContains(searchText)) return 2; - if (TextCacheAll.CaseInsensitiveContains(searchText)) return 1;*/ - return 0; - } - public override void Dispose() {