From fa7d743f02c16fba1cf6c9185073320aed652c0c Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Thu, 28 May 2026 15:37:47 -0400 Subject: [PATCH] Minor history list changes --- .../src/main/java/util/HistoryList.java | 40 +++++++++++-- .../src/test/java/util/HistoryListTest.java | 56 ++++++++++++++++++- 2 files changed, 89 insertions(+), 7 deletions(-) diff --git a/Ghidra/Framework/Generic/src/main/java/util/HistoryList.java b/Ghidra/Framework/Generic/src/main/java/util/HistoryList.java index afe2aebf27..ab291b276f 100644 --- a/Ghidra/Framework/Generic/src/main/java/util/HistoryList.java +++ b/Ghidra/Framework/Generic/src/main/java/util/HistoryList.java @@ -22,7 +22,7 @@ import java.util.function.Consumer; import org.apache.commons.lang3.StringUtils; import ghidra.util.Msg; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; import ghidra.util.datastruct.FixedSizeStack; /** @@ -121,7 +121,7 @@ public class HistoryList { /** * True signals that the client allows null items to be used. When this is true, a null - * value will be stored in this list only as the last item. See the javadoc for + * value will be stored in this list only as the last item. See the class javadoc for * more info. * * @param allowNulls true to allow nulls; the default is false @@ -133,9 +133,12 @@ public class HistoryList { /** * Adds an item to this history list. null values are ignored. * + *

After this call, the {@link #getCurrentHistoryItem() current history item} will be the + * newly added item. + * *

Calls to this method during selection notification will have no effect. If you need * to update the history during a notification, then you must do so at a later time, perhaps - * by using {@link SystemUtilities#runSwingLater(Runnable)}. + * by using {@link Swing#runLater(Runnable)}. * * @param t the item to add. */ @@ -152,7 +155,7 @@ public class HistoryList { dropNull(); // once we add a new item, any old history that was after this item needs to be - // removed, as that is old alternate timeline that no longer makes sense + // removed, as that old alternate timeline that no longer makes sense trimHistoryToCurrentIndex(); handleDuplicate(t); @@ -163,6 +166,35 @@ public class HistoryList { historyIndex = historyStack.size() - 1; } + /** + * Performs an {@link #add(Object)} after removing the last item in the history, which is + * effectively a replace operation. This method is useful for clients that wish to keep the + * history from filling up with transient items as the user is navigating. The client may + * decide to replace the previous history marker with the newest history marker, such as when + * the navigations are frequent and not necessarily important, such as when using the down arrow + * key to cursor through a text file. + * + *

After this call, the {@link #getCurrentHistoryItem() current history item} will be the + * newly added item. + * + *

Calls to this method during selection notification will have no effect. If you need + * to update the history during a notification, then you must do so at a later time, perhaps + * by using {@link Swing#runLater(Runnable)}. + * + * @param t the item to add. + */ + public void addReplace(T t) { + + if (isBroadcasting) { + return; + } + + historyStack.pop(); + historyIndex = historyStack.size() - 1; + + add(t); + } + /** * Returns true if this history list's current item pointer is not at the end of the list. * diff --git a/Ghidra/Framework/Generic/src/test/java/util/HistoryListTest.java b/Ghidra/Framework/Generic/src/test/java/util/HistoryListTest.java index 831e0f9fe3..4a74df4da1 100644 --- a/Ghidra/Framework/Generic/src/test/java/util/HistoryListTest.java +++ b/Ghidra/Framework/Generic/src/test/java/util/HistoryListTest.java @@ -19,8 +19,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.*; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.function.Consumer; import org.junit.Test; @@ -153,6 +152,52 @@ public class HistoryListTest { assertCannotGoBack(); } + @Test + public void testAddReplace() { + + addHistory(A); + addHistory(B); + addHistory(C); + replaceHistory(D); // C -> D + replaceHistory(E); // D -> E + + assertHistory(A, B, E); + + goBack(); + assertNotified(B); + assertCurrentItem(B); + + goBack(); + assertNotified(A); + assertCannotGoBack(); + + goForward(); + assertNotified(B); + + goForward(); + assertNotified(E); + } + + @Test + public void testAddReplace_NotAtEnd() { + + addHistory(A); + addHistory(B); + addHistory(C); + addHistory(D); + assertHistory(A, B, C, D); + + goBack(); + goBack(); + assertNotified(B); + assertCurrentItem(B); + + replaceHistory(E); // D -> E + assertHistory(A, B, C, E); + + assertCurrentItem(E); + } + @Test public void testNavigationMixedWithHistoryAddition() { // @@ -503,10 +548,15 @@ public class HistoryListTest { historyList.add(item); } + private void replaceHistory(String item) { + historyList.addReplace(item); + } + private void assertHistory(String... names) { FixedSizeStack stack = historyList.getHistoryStack(); - assertEquals(names.length, stack.size()); + assertEquals("History size is wrong: " + Arrays.toString(names) + " vs " + stack, + names.length, stack.size()); for (int i = 0; i < stack.size(); i++) { assertEquals("Unexpected item in history", names[i], stack.get(i)); }