diff --git a/src/HexManiac.Core/Models/SearchByte.cs b/src/HexManiac.Core/Models/SearchByte.cs index 874429a2..39dd481d 100644 --- a/src/HexManiac.Core/Models/SearchByte.cs +++ b/src/HexManiac.Core/Models/SearchByte.cs @@ -24,8 +24,10 @@ namespace HavenSoft.HexManiac.Core.Models { private static readonly int CapitalE = PCSString.PCS.IndexOf("E"); private static readonly int LowerE = PCSString.PCS.IndexOf("e"); private readonly byte match1, match2; - public static ISearchByte Create(byte value) { - if (value == CapitalE || value == CapitalEWithAccent || value == LowerE || value == LowerEWithAccent) { + public static ISearchByte Create(byte value, bool matchExactCase) { + if (matchExactCase) { + return new ExactMatchSearchByte(value); + } else if (value == CapitalE || value == CapitalEWithAccent || value == LowerE || value == LowerEWithAccent) { return MatchESearchByte.Instance; } else { return new PCSSearchByte(value); @@ -51,5 +53,11 @@ namespace HavenSoft.HexManiac.Core.Models { private MatchESearchByte() { } public bool Match(byte value) => value == CapitalE || value == CapitalEWithAccent || value == LowerE || value == LowerEWithAccent; } + + private class ExactMatchSearchByte : ISearchByte { + private byte value; + public ExactMatchSearchByte(byte value) => this.value = value; + public bool Match(byte value) => value == this.value; + } } } diff --git a/src/HexManiac.Core/ViewModels/ChildViewPort.cs b/src/HexManiac.Core/ViewModels/ChildViewPort.cs index c963129c..660862c8 100644 --- a/src/HexManiac.Core/ViewModels/ChildViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ChildViewPort.cs @@ -159,7 +159,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public void ExpandSelection(int x, int y) => ForEach(child => child.ExpandSelection(x, y)); - public IReadOnlyList<(int start, int end)> Find(string search) => new (int, int)[0]; + public IReadOnlyList<(int start, int end)> Find(string search, bool matchExactCase = false) => new (int, int)[0]; public void FindAllSources(int x, int y) { } diff --git a/src/HexManiac.Core/ViewModels/DiffViewPort.cs b/src/HexManiac.Core/ViewModels/DiffViewPort.cs index 8dcea3c7..12369926 100644 --- a/src/HexManiac.Core/ViewModels/DiffViewPort.cs +++ b/src/HexManiac.Core/ViewModels/DiffViewPort.cs @@ -139,7 +139,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public void ExpandSelection(int x, int y) { } - public IReadOnlyList<(int start, int end)> Find(string search) => throw new NotImplementedException(); + public IReadOnlyList<(int start, int end)> Find(string search, bool matchExactCase = false) => throw new NotImplementedException(); public void FindAllSources(int x, int y) { } diff --git a/src/HexManiac.Core/ViewModels/IViewPort.cs b/src/HexManiac.Core/ViewModels/IViewPort.cs index 437e2049..b4d1ce75 100644 --- a/src/HexManiac.Core/ViewModels/IViewPort.cs +++ b/src/HexManiac.Core/ViewModels/IViewPort.cs @@ -45,7 +45,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { bool IsSelected(Point point); bool IsTable(Point point); - IReadOnlyList<(int start, int end)> Find(string search); + IReadOnlyList<(int start, int end)> Find(string search, bool matchExactCase = false); IChildViewPort CreateChildView(int startAddress, int endAddress); void FollowLink(int x, int y); void ExpandSelection(int x, int y); diff --git a/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs b/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs index c628d513..df62eb12 100644 --- a/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs +++ b/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs @@ -174,7 +174,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public IChildViewPort CreateChildView(int startAddress, int endAddress) => throw new NotImplementedException(); // if asked to search the search results... just don't - public IReadOnlyList<(int, int)> Find(string search) => new (int, int)[0]; + public IReadOnlyList<(int, int)> Find(string search, bool matchExactCase = false) => new (int, int)[0]; public bool UseCustomHeaders { get => children.FirstOrDefault()?.UseCustomHeaders ?? false; diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index b025b392..b1f4bac2 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -1458,14 +1458,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels { #region Find - public IReadOnlyList<(int start, int end)> Find(string rawSearch) { + public IReadOnlyList<(int start, int end)> Find(string rawSearch, bool matchExactCase = false) { var results = new List<(int start, int end)>(); - var cleanedSearchString = rawSearch.ToUpper(); + var cleanedSearchString = rawSearch; + if (!matchExactCase) cleanedSearchString = rawSearch.ToUpper(); var searchBytes = new List(); // it might be a string with no quotes, we should check for matches for that. if (cleanedSearchString.Length > 3 && !cleanedSearchString.Contains(StringDelimeter) && !cleanedSearchString.All(AllHexCharacters.Contains)) { - results.AddRange(FindUnquotedText(cleanedSearchString, searchBytes)); + results.AddRange(FindUnquotedText(cleanedSearchString, searchBytes, matchExactCase)); } // it might be a matched-word @@ -1548,14 +1549,14 @@ namespace HavenSoft.HexManiac.Core.ViewModels { } } - private IEnumerable<(int start, int end)> FindUnquotedText(string cleanedSearchString, List searchBytes) { + private IEnumerable<(int start, int end)> FindUnquotedText(string cleanedSearchString, List searchBytes, bool matchExactCase) { var pcsBytes = PCSString.Convert(cleanedSearchString); pcsBytes.RemoveAt(pcsBytes.Count - 1); // remove the 0xFF that was added, since we're searching for a string segment instead of a whole string. // only search for the string if every character in the search string is allowed if (pcsBytes.Count != cleanedSearchString.Length) yield break; - searchBytes.AddRange(pcsBytes.Select(PCSSearchByte.Create)); + searchBytes.AddRange(pcsBytes.Select(b => PCSSearchByte.Create(b, matchExactCase))); var textResults = Model.Search(searchBytes).ToList(); Model.ConsiderResultsAsTextRuns(history.CurrentChange, textResults); foreach (var result in textResults) { diff --git a/src/HexManiac.Tests/Before_Baseclass/FindTests.cs b/src/HexManiac.Tests/Before_Baseclass/FindTests.cs index 3e5fc9bb..43c76024 100644 --- a/src/HexManiac.Tests/Before_Baseclass/FindTests.cs +++ b/src/HexManiac.Tests/Before_Baseclass/FindTests.cs @@ -12,6 +12,7 @@ namespace HavenSoft.HexManiac.Tests { public class FindTests { private static Func CreateCreateChildView(Func tab) => (start, end) => new ChildViewPort(tab(), InstantDispatch.Instance, BaseViewModelTestClass.Singletons); private static ViewPort NewViewPort(IDataModel model) => new ViewPort("test.gba", model, InstantDispatch.Instance, BaseViewModelTestClass.Singletons); + private static Func DefaultFind(params (int, int)[] results) => (text, matchExactCase) => results; [Fact] public void FindCanFindSingle() { @@ -45,7 +46,7 @@ namespace HavenSoft.HexManiac.Tests { var tab = new StubViewPort(); var editor = new EditorViewModel(new StubFileSystem()) { tab }; - tab.Find = str => new (int, int)[0]; + tab.Find = DefaultFind(); editor.Find.Execute("something"); Assert.True(editor.ShowError); @@ -58,7 +59,7 @@ namespace HavenSoft.HexManiac.Tests { var editor = new EditorViewModel(new StubFileSystem()) { tab }; string gotoArg = string.Empty; - tab.Find = str => new[] { (0x54, 0x54) }; + tab.Find = DefaultFind((0x54, 0x54)); tab.Goto = new StubCommand { CanExecute = arg => true, Execute = arg => gotoArg = (string)arg }; editor.Find.Execute("something"); @@ -72,7 +73,7 @@ namespace HavenSoft.HexManiac.Tests { var editor = new EditorViewModel(new StubFileSystem()) { tab }; var count = 0; - tab.Find = str => new[] { (0x54, 0x54), (0x154, 0x154) }; + tab.Find = DefaultFind((0x54, 0x54), (0x154, 0x154)); tab.Model = new BasicModel(new byte[0x200]); tab.CreateChildView = (int startAddress, int endAddress) => { var child = new ChildViewPort(tab, InstantDispatch.Instance, BaseViewModelTestClass.Singletons); @@ -92,7 +93,7 @@ namespace HavenSoft.HexManiac.Tests { int gotoCount = 0; StubViewPort tab = null; tab = new StubViewPort { - Find = str => new[] { (0x54, 0x54), (0x154, 0x154) }, + Find = DefaultFind((0x54, 0x54), (0x154, 0x154)), Model = new BasicModel(new byte[0x200]), Goto = new StubCommand { CanExecute = arg => true, Execute = arg => gotoCount++ }, CreateChildView = (start, end) => new ChildViewPort(tab, InstantDispatch.Instance, BaseViewModelTestClass.Singletons), @@ -114,7 +115,7 @@ namespace HavenSoft.HexManiac.Tests { public void EditorFindNextDoesNotSwitchTabs() { StubViewPort tab1 = null; tab1 = new StubViewPort { - Find = query => new[] { (0x60, 0x60) }, + Find = DefaultFind((0x60, 0x60)), Goto = new StubCommand(), Model = new BasicModel(new byte[0x200]), CreateChildView = CreateCreateChildView(() => tab1), @@ -124,7 +125,7 @@ namespace HavenSoft.HexManiac.Tests { }; StubViewPort tab2 = null; tab2 = new StubViewPort { - Find = query => new[] { (0x50, 0x50), (0x70, 0x70) }, + Find = DefaultFind((0x50, 0x50), (0x70, 0x70)), Goto = new StubCommand(), Model = new BasicModel(new byte[0x200]), CreateChildView = CreateCreateChildView(() => tab2), @@ -152,7 +153,7 @@ namespace HavenSoft.HexManiac.Tests { public void FindResultsHasHeadersAndGaps() { StubViewPort tab = null; tab = new StubViewPort { - Find = query => new[] { (0x50, 0x50), (0x70, 0x70) }, + Find = DefaultFind((0x50, 0x50), (0x70, 0x70)), Goto = new StubCommand(), Model = new BasicModel(new byte[0x200]), CreateChildView = CreateCreateChildView(() => tab), @@ -173,7 +174,7 @@ namespace HavenSoft.HexManiac.Tests { public void FindClosesAfterRun() { StubViewPort tab = null; tab = new StubViewPort { - Find = query => new[] { (0x50, 0x50), (0x70, 0x70) }, + Find = DefaultFind((0x50, 0x50), (0x70, 0x70)), Goto = new StubCommand(), Model = new BasicModel(new byte[0x200]), CreateChildView = CreateCreateChildView(() => tab), @@ -234,7 +235,7 @@ namespace HavenSoft.HexManiac.Tests { var tab1 = new StubTabContent(); var editor = new EditorViewModel(new StubFileSystem()) { tab0, tab1 }; - tab0.Find = query => new[] { (0x50, 0x50) }; + tab0.Find = DefaultFind((0x50, 0x50)); editor.Find.Execute("search"); Assert.Equal(0, editor.SelectedIndex); @@ -253,7 +254,7 @@ namespace HavenSoft.HexManiac.Tests { var editor = new EditorViewModel(new StubFileSystem()); StubViewPort tab = null; tab = new StubViewPort { - Find = query => new[] { (0x50, 0x50) }, + Find = DefaultFind((0x50, 0x50)), Goto = new StubCommand(), CreateChildView = CreateCreateChildView(() => tab), Headers = new ObservableCollection { "00", "01", "02", "03" }, @@ -273,7 +274,7 @@ namespace HavenSoft.HexManiac.Tests { var editor = new EditorViewModel(new StubFileSystem()); StubViewPort tab = null; tab = new StubViewPort { - Find = query => new[] { (0x50, 0x50), (0x70, 0x70) }, + Find = DefaultFind((0x50, 0x50), (0x70, 0x70)), Goto = new StubCommand(), Model = new BasicModel(new byte[0x200]), CreateChildView = CreateCreateChildView(() => tab), @@ -404,7 +405,7 @@ namespace HavenSoft.HexManiac.Tests { [Fact] public void SameModelDoesNotGetSearchedMultipleTimes() { int findCalls = 0; - IReadOnlyList<(int, int)> Find(string input) { + IReadOnlyList<(int, int)> Find(string input, bool matchExactCase = false) { findCalls += 1; return new List<(int, int)>(); } @@ -422,7 +423,7 @@ namespace HavenSoft.HexManiac.Tests { [Fact] public void DifferentModelsAllGetSearched() { int findCalls = 0; - IReadOnlyList<(int, int)> Find(string input) { + IReadOnlyList<(int, int)> Find(string input, bool matchExactCase = false) { findCalls += 1; return new List<(int, int)>(); }