From af64d4aad2ea2d8eb655f378ec8af2ddfb3f6efc Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Sun, 9 Dec 2018 13:07:14 -0600 Subject: [PATCH] implement find/goto for pointers --- src/Gen3Hex.Core/Models/DataFormatModel.cs | 4 ++- src/Gen3Hex.Core/ViewModels/Selection.cs | 28 +++++++++------ src/Gen3Hex.Core/ViewModels/ViewPort.cs | 40 +++++++++++++++++----- src/Gen3Hex.Tests/ModelTests.cs | 31 +++++++++++++++-- src/Gen3Hex.Tests/ViewPortCursorTests.cs | 2 +- 5 files changed, 82 insertions(+), 23 deletions(-) diff --git a/src/Gen3Hex.Core/Models/DataFormatModel.cs b/src/Gen3Hex.Core/Models/DataFormatModel.cs index 20bc558b..6331d46d 100644 --- a/src/Gen3Hex.Core/Models/DataFormatModel.cs +++ b/src/Gen3Hex.Core/Models/DataFormatModel.cs @@ -102,6 +102,8 @@ namespace HavenSoft.Gen3Hex.Core.Models { return address; } + if (requestSource < 0) return Pointer.NULL; + // the named anchor does not exist! Add it to the list of desired anchors if (!unmappedNameToSources.ContainsKey(anchor)) { unmappedNameToSources[anchor] = new List(); @@ -264,7 +266,7 @@ namespace HavenSoft.Gen3Hex.Core.Models { public class BasicModel : IModel { public static IModel Instance { get; } = new BasicModel(); - public int GetAddressFromAnchor(int requestSource, string anchor) => 0; + public int GetAddressFromAnchor(int requestSource, string anchor) => Pointer.NULL; public string GetAnchorFromAddress(int requestSource, int destination) => string.Empty; public IFormattedRun GetNextRun(int dataIndex) => null; public void ObserveRunWritten(byte[] data, IFormattedRun run) { } diff --git a/src/Gen3Hex.Core/ViewModels/Selection.cs b/src/Gen3Hex.Core/ViewModels/Selection.cs index 38456815..113e1e63 100644 --- a/src/Gen3Hex.Core/ViewModels/Selection.cs +++ b/src/Gen3Hex.Core/ViewModels/Selection.cs @@ -1,4 +1,5 @@ using HavenSoft.Gen3Hex.Core.Models; +using HavenSoft.Gen3Hex.Core.ViewModels.DataFormats; using System; using System.Collections.Generic; using System.Globalization; @@ -65,7 +66,18 @@ namespace HavenSoft.Gen3Hex.Core.ViewModels { /// public event EventHandler PreviewSelectionStartChanged; - public Selection(ScrollRegion scrollRegion) { + private void GotoAddress(int address) { + backStack.Push(scroll.DataIndex); + if (backStack.Count == 1) backward.CanExecuteChanged.Invoke(backward, EventArgs.Empty); + if (forwardStack.Count > 0) { + forward.CanExecuteChanged.Invoke(forward, EventArgs.Empty); + forwardStack.Clear(); + } + SelectionStart = scroll.DataIndexToViewPoint(address); + scroll.ScrollValue += selectionStart.Y; + } + + public Selection(ScrollRegion scrollRegion, IModel model) { scroll = scrollRegion; scroll.ScrollChanged += (sender, e) => ShiftSelectionFromScroll(e); @@ -78,15 +90,11 @@ namespace HavenSoft.Gen3Hex.Core.ViewModels { CanExecute = args => true, Execute = args => { var address = args.ToString(); - if (int.TryParse(address, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out int result)) { - backStack.Push(scroll.DataIndex); - if (backStack.Count == 1) backward.CanExecuteChanged.Invoke(backward, EventArgs.Empty); - if (forwardStack.Count > 0) { - forward.CanExecuteChanged.Invoke(forward, EventArgs.Empty); - forwardStack.Clear(); - } - SelectionStart = scroll.DataIndexToViewPoint(result); - scroll.ScrollValue += selectionStart.Y; + var anchor = model.GetAddressFromAnchor(-1, address); + if (anchor != Pointer.NULL) { + GotoAddress(anchor); + } else if (int.TryParse(address, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out int result)) { + GotoAddress(result); } else { OnError?.Invoke(this, $"Unable to goto address '{address}'"); } diff --git a/src/Gen3Hex.Core/ViewModels/ViewPort.cs b/src/Gen3Hex.Core/ViewModels/ViewPort.cs index cdeec19e..b763e614 100644 --- a/src/Gen3Hex.Core/ViewModels/ViewPort.cs +++ b/src/Gen3Hex.Core/ViewModels/ViewPort.cs @@ -222,7 +222,7 @@ namespace HavenSoft.Gen3Hex.Core.ViewModels { scroll = new ScrollRegion { DataLength = data.Length }; scroll.PropertyChanged += ScrollPropertyChanged; - selection = new Selection(scroll); + selection = new Selection(scroll, Model); selection.PropertyChanged += SelectionPropertyChanged; selection.PreviewSelectionStartChanged += ClearActiveEditBeforeSelectionChanges; selection.OnError += (sender, e) => OnError?.Invoke(this, e); @@ -321,19 +321,41 @@ namespace HavenSoft.Gen3Hex.Core.ViewModels { } } + private byte[] Parse(string content) { + var hex = "0123456789ABCDEF"; + var result = new byte[content.Length / 2]; + for (int i = 0; i < result.Length; i++) { + var thisByte = content.Substring(i * 2, 2); + result[i] += (byte)(hex.IndexOf(thisByte[0]) * 0x10); + result[i] += (byte)hex.IndexOf(thisByte[1]); + } + return result; + } + public IReadOnlyList Find(string rawSearch) { var results = new List(); - - // basic attempt: see if the search term is a string of bytes var cleanedSearch = rawSearch.Replace(" ", string.Empty).ToUpper(); var hex = "0123456789ABCDEF"; - if (cleanedSearch.All(hex.Contains) && cleanedSearch.Length % 2 == 0) { - var search = new byte[cleanedSearch.Length / 2]; - for (int i = 0; i < search.Length; i++) { - var thisByte = cleanedSearch.Substring(i * 2, 2); - search[i] += (byte)(hex.IndexOf(thisByte[0]) * 0x10); - search[i] += (byte)hex.IndexOf(thisByte[1]); + + // pointer attempt: see if the search term is a pointer + if (cleanedSearch.StartsWith("<") && cleanedSearch.EndsWith(">")) { + var content = cleanedSearch.Substring(1, cleanedSearch.Length - 2); + int destination = 0; + if (content.All(hex.Contains) && content.Length == 6) { + var bytes = Parse(content); + destination = (bytes[0] << 16) + (bytes[1] << 8) + (bytes[2] << 0); } + for (var run = Model.GetNextRun(0); run != null; run = Model.GetNextRun(run.Start + run.Length)) { + if (run is PointerRun pointerRun) { + var pointer = (Pointer)pointerRun.CreateDataFormat(data, pointerRun.Start); + if (pointer.Destination == destination || pointer.DestinationName == content) results.Add(run.Start); + } + } + } + + // basic attempt: see if the search term is a string of bytes + if (cleanedSearch.All(hex.Contains) && cleanedSearch.Length % 2 == 0) { + var search = Parse(cleanedSearch); for (int i = 0; i < data.Length - search.Length; i++) { for (int j = 0; j < search.Length; j++) { if (data[i + j] != search[j]) break; diff --git a/src/Gen3Hex.Tests/ModelTests.cs b/src/Gen3Hex.Tests/ModelTests.cs index 92ebb6fd..cb00a7f1 100644 --- a/src/Gen3Hex.Tests/ModelTests.cs +++ b/src/Gen3Hex.Tests/ModelTests.cs @@ -353,9 +353,36 @@ namespace HavenSoft.Gen3Hex.Tests { Assert.IsNotType(viewPort[4, 1].Format); } + [Fact] + public void CanGotoAnchorName() { + var buffer = new byte[0x100]; + var model = new PointerModel(buffer); + var viewPort = new ViewPort(new LoadedFile("test.txt", buffer), model) { Width = 0x10, Height = 0x10 }; + + int errorCalls = 0; + viewPort.OnError += (sender, e) => errorCalls++; + + viewPort.SelectionStart = new Point(2, 1); + viewPort.Edit("^bob "); + viewPort.Goto.Execute("bob"); + + Assert.Equal(0, errorCalls); + } + + [Fact] + public void CanFindPointer() { + var buffer = new byte[0x100]; + var model = new PointerModel(buffer); + var viewPort = new ViewPort(new LoadedFile("test.txt", buffer), model) { Width = 0x10, Height = 0x10 }; + + viewPort.SelectionStart = new Point(4, 1); + viewPort.Edit("<000058>"); + var results = viewPort.Find("<000058>"); + + Assert.Single(results); + } + // TODO putting a new anchor with the same name: delete any run that starts at that anchor, repoint everything from the old location to the new location - // TODO goto named anchor - // TODO find pointer // TODO undo/redo } diff --git a/src/Gen3Hex.Tests/ViewPortCursorTests.cs b/src/Gen3Hex.Tests/ViewPortCursorTests.cs index e54ee264..a318a67d 100644 --- a/src/Gen3Hex.Tests/ViewPortCursorTests.cs +++ b/src/Gen3Hex.Tests/ViewPortCursorTests.cs @@ -203,7 +203,7 @@ namespace HavenSoft.Gen3Hex.Tests { [Fact] public void CannotMoveSelectEndFarPassedEndOfFile() { - var selection = new Selection(new ScrollRegion { DataLength = 8 }); + var selection = new Selection(new ScrollRegion { DataLength = 8 }, new BasicModel()); selection.SelectionEnd = new Point(3, 3);