From fe9048299bf642cfa3549a95e417fa7ae884029c Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Fri, 24 Apr 2026 16:29:16 +0100 Subject: [PATCH] add tests for 'df' motion, uncover bug found by test where new match was not recognised after deletion if match involved last character in buffer, and fix that bug by checking (in fcore/search-list/search-list.sm) if we are at a dead state before we check if we are in the last index of the buffer --- shf/fcore/search-list/search-list.sml | 44 ++++---- shf/temp.txt | 3 +- shf/test/normal-delete-tests.sml | 147 ++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 24 deletions(-) diff --git a/shf/fcore/search-list/search-list.sml b/shf/fcore/search-list/search-list.sml index e05f0bd..9c006d6 100644 --- a/shf/fcore/search-list/search-list.sml +++ b/shf/fcore/search-list/search-list.sml @@ -131,20 +131,7 @@ struct fun insertUntilMatch (idx, buffer, searchList, dfa, curState, startPos, prevFinalPos) = - if idx = #textLength buffer then - if prevFinalPos < 0 then - (buffer, searchList) - else if PersistentVector.isInRange (prevFinalPos, searchList) then - (buffer, searchList) - else - let - val searchList = - PersistentVector.insertMatchKeepingAbsoluteInddices - (startPos, prevFinalPos, searchList) - in - (buffer, searchList) - end - else if Dfa.isDead curState then + if Dfa.isDead curState then if prevFinalPos = ~1 then (* no match found: restart search from `startPos + 1` *) insertUntilMatch @@ -161,6 +148,19 @@ struct in insertUntilMatch (newStart, buffer, searchList, dfa, 0, newStart, ~1) end + else if idx = #textLength buffer then + if prevFinalPos < 0 then + (buffer, searchList) + else if PersistentVector.isInRange (prevFinalPos, searchList) then + (buffer, searchList) + else + let + val searchList = + PersistentVector.insertMatchKeepingAbsoluteInddices + (startPos, prevFinalPos, searchList) + in + (buffer, searchList) + end else let val buffer = LineGap.goToIdx (idx, buffer) @@ -176,14 +176,7 @@ struct fun tryExtendingPrevMatch (idx, buffer, searchList, dfa, finalPos, curState, start) = - if idx = #textLength buffer then - let - val searchList = - PersistentVector.extendExistingMatch (start, finalPos, searchList) - in - (buffer, searchList) - end - else if Dfa.isDead curState then + if Dfa.isDead curState then let val searchList = PersistentVector.extendExistingMatch (start, finalPos, searchList) @@ -191,6 +184,13 @@ struct insertUntilMatch (finalPos + 1, buffer, searchList, dfa, 0, finalPos + 1, ~1) end + else if idx = #textLength buffer then + let + val searchList = + PersistentVector.extendExistingMatch (start, finalPos, searchList) + in + (buffer, searchList) + end else let val buffer = LineGap.goToIdx (idx, buffer) diff --git a/shf/temp.txt b/shf/temp.txt index a2c1105..93f9fd7 100644 --- a/shf/temp.txt +++ b/shf/temp.txt @@ -1,2 +1 @@ -hello hello hello -world world world +hello again world diff --git a/shf/test/normal-delete-tests.sml b/shf/test/normal-delete-tests.sml index a88537f..36aa39f 100644 --- a/shf/test/normal-delete-tests.sml +++ b/shf/test/normal-delete-tests.sml @@ -6296,6 +6296,153 @@ struct Expect.isTrue (actualString = expectedString andalso cursorIdx = expectedCursorIdx) end) + , test "has same searchList when deleting after all matches" (fn _ => + let + (* arrange *) + val originalIdx = 9 + val originalString = "hello world again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dfn") + in + (* assert *) + Expect.isTrue (#searchList app = #searchList newApp) + end) + , test "decrements search list when we delete text section preceding match" + (fn _ => + let + (* arrange *) + val originalIdx = 0 + val originalString = "hello\nworld\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dfl") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [{start = 3, finish = 7}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test "recognises new match when there is a match after deletion" (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello again world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/hello world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dfn") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [] + val expectedNewSearchList = [{start = 0, finish = 10}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "extends existing match when existing match should extend after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 5 + val originalString = "hello world\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/o+") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dfw") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = + [{start = 4, finish = 4}, {start = 7, finish = 7}] + val expectedNewSearchList = [{start = 4, finish = 5}] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) + , test + "deletes match in search list \ + \when match no longer exists in buffer after deletion" + (fn _ => + let + (* arrange *) + val originalIdx = 3 + val originalString = "hello world test again\n" + + val app = TestUtils.init originalString + val app = AppWith.idx (app, originalIdx) + + val app = TestUtils.updateMany (app, "/world") + val app = AppUpdate.update (app, InputMsg.KEY_ENTER, Time.now ()) + + (* act *) + val newApp = TestUtils.updateMany (app, "dft") + + (* assert *) + val oldSearchList = #searchList app + val oldSearchList = PersistentVector.toList oldSearchList + val newSearchList = #searchList newApp + val newSearchList = PersistentVector.toList newSearchList + + val expectedOldSearchList = [{start = 6, finish = 10}] + val expectedNewSearchList = [] + + val assertion = + oldSearchList = expectedOldSearchList + andalso newSearchList = expectedNewSearchList + in + Expect.isTrue assertion + end) ] val dtDelete = describe "delete motion 'dt'"