Skip to content

Commit 3e16854

Browse files
committed
Limit ChangeHistory size to 1000 last changes
1 parent 41b903c commit 3e16854

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

app/src/main/java/com/philkes/notallyx/utils/changehistory/ChangeHistory.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import android.util.Log
44
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
55
import kotlin.IllegalStateException
66

7-
class ChangeHistory {
7+
class ChangeHistory(
8+
/** Maximum number of changes to keep in memory. Oldest entries are evicted when full. */
9+
private val maxSize: Int = 1000
10+
) {
811
private val changeStack = ArrayList<Change>()
912
var stackPointer = NotNullLiveData(-1)
1013

@@ -19,9 +22,19 @@ class ChangeHistory {
1922
}
2023

2124
fun push(change: Change) {
25+
// Drop all redo entries after current pointer
2226
popRedos()
27+
// If full, evict the oldest entry and shift the pointer accordingly
28+
var newStackPointer = stackPointer.value
29+
if (changeStack.size >= maxSize) {
30+
if (changeStack.isNotEmpty()) {
31+
changeStack.removeAt(0)
32+
// Shift pointer left because we removed the head
33+
newStackPointer = (newStackPointer - 1).coerceAtLeast(-1)
34+
}
35+
}
2336
changeStack.add(change)
24-
stackPointer.value += 1
37+
stackPointer.value = newStackPointer + 1
2538
Log.d(TAG, "addChange: $change")
2639
}
2740

app/src/test/kotlin/com/philkes/notallyx/changehistory/ChangeHistoryTest.kt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,67 @@ class ChangeHistoryTest {
138138

139139
override fun undo() {}
140140
}
141+
142+
@Test
143+
fun `bounded history evicts oldest when capacity reached`() {
144+
// Use a small capacity to test eviction behavior
145+
changeHistory = ChangeHistory(maxSize = 3)
146+
val c1 = TestChange()
147+
val c2 = TestChange()
148+
val c3 = TestChange()
149+
val c4 = TestChange()
150+
151+
changeHistory.push(c1)
152+
changeHistory.push(c2)
153+
changeHistory.push(c3)
154+
// Next push should evict c1
155+
changeHistory.push(c4)
156+
157+
assertTrue(changeHistory.canUndo.value)
158+
// Top of stack is c4
159+
assertEquals(c4, changeHistory.lookUp())
160+
// Next is c3
161+
assertEquals(c3, changeHistory.lookUp(1))
162+
// Next is c2
163+
assertEquals(c2, changeHistory.lookUp(2))
164+
// c1 should be gone
165+
assertThrows(ChangeHistory.ChangeHistoryException::class.java) { changeHistory.lookUp(3) }
166+
}
167+
168+
@Test
169+
fun `pushing after undo with full capacity drops redos then evicts oldest if needed`() {
170+
changeHistory = ChangeHistory(maxSize = 3)
171+
val c1 = TestChange()
172+
val c2 = TestChange()
173+
val c3 = TestChange()
174+
val c4 = TestChange()
175+
val c5 = TestChange()
176+
177+
// Fill to capacity
178+
changeHistory.push(c1)
179+
changeHistory.push(c2)
180+
changeHistory.push(c3)
181+
182+
// Undo 1 -> pointer at c2
183+
changeHistory.undo()
184+
assertTrue(changeHistory.canRedo.value)
185+
186+
// Push new change: should drop redo (c3) first, then possibly evict if full when pushing
187+
changeHistory.push(c4)
188+
189+
// Stack should now have [c1, c2, c4] with pointer at top
190+
assertEquals(c4, changeHistory.lookUp())
191+
assertEquals(c2, changeHistory.lookUp(1))
192+
assertEquals(c1, changeHistory.lookUp(2))
193+
194+
// Push another to force eviction of c1
195+
changeHistory.push(c5)
196+
assertEquals(c5, changeHistory.lookUp())
197+
assertEquals(c4, changeHistory.lookUp(1))
198+
assertEquals(c2, changeHistory.lookUp(2))
199+
assertThrows(ChangeHistory.ChangeHistoryException::class.java) { changeHistory.lookUp(3) }
200+
201+
// No redo available after pushes
202+
assertFalse(changeHistory.canRedo.value)
203+
}
141204
}

documentation/docs/features/rich-text.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ NotallyX provides undo and redo functionality to help you recover from mistakes
4040
- **Undo**: Tap the undo icon (curved arrow pointing left) in the toolbar
4141
- **Redo**: Tap the redo icon (curved arrow pointing right) in the toolbar
4242

43+
_Note: Undo/Redo is limited to the last 1,000 changes_
44+
4345
## Related Features
4446

4547
- [Task Lists and Subtasks](./task-lists.mdx): Learn how to create and manage task lists

0 commit comments

Comments
 (0)