add tests for 'df<char>' 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

This commit is contained in:
2026-04-24 16:29:16 +01:00
parent 504b5309f4
commit fe9048299b
3 changed files with 170 additions and 24 deletions

View File

@@ -131,20 +131,7 @@ struct
fun insertUntilMatch fun insertUntilMatch
(idx, buffer, searchList, dfa, curState, startPos, prevFinalPos) = (idx, buffer, searchList, dfa, curState, startPos, prevFinalPos) =
if idx = #textLength buffer then if Dfa.isDead curState 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 prevFinalPos = ~1 then if prevFinalPos = ~1 then
(* no match found: restart search from `startPos + 1` *) (* no match found: restart search from `startPos + 1` *)
insertUntilMatch insertUntilMatch
@@ -161,6 +148,19 @@ struct
in in
insertUntilMatch (newStart, buffer, searchList, dfa, 0, newStart, ~1) insertUntilMatch (newStart, buffer, searchList, dfa, 0, newStart, ~1)
end 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 else
let let
val buffer = LineGap.goToIdx (idx, buffer) val buffer = LineGap.goToIdx (idx, buffer)
@@ -176,14 +176,7 @@ struct
fun tryExtendingPrevMatch fun tryExtendingPrevMatch
(idx, buffer, searchList, dfa, finalPos, curState, start) = (idx, buffer, searchList, dfa, finalPos, curState, start) =
if idx = #textLength buffer then if Dfa.isDead curState then
let
val searchList =
PersistentVector.extendExistingMatch (start, finalPos, searchList)
in
(buffer, searchList)
end
else if Dfa.isDead curState then
let let
val searchList = val searchList =
PersistentVector.extendExistingMatch (start, finalPos, searchList) PersistentVector.extendExistingMatch (start, finalPos, searchList)
@@ -191,6 +184,13 @@ struct
insertUntilMatch insertUntilMatch
(finalPos + 1, buffer, searchList, dfa, 0, finalPos + 1, ~1) (finalPos + 1, buffer, searchList, dfa, 0, finalPos + 1, ~1)
end end
else if idx = #textLength buffer then
let
val searchList =
PersistentVector.extendExistingMatch (start, finalPos, searchList)
in
(buffer, searchList)
end
else else
let let
val buffer = LineGap.goToIdx (idx, buffer) val buffer = LineGap.goToIdx (idx, buffer)

View File

@@ -1,2 +1 @@
hello hello hello hello again world
world world world

View File

@@ -6296,6 +6296,153 @@ struct
Expect.isTrue Expect.isTrue
(actualString = expectedString andalso cursorIdx = expectedCursorIdx) (actualString = expectedString andalso cursorIdx = expectedCursorIdx)
end) 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<char>'" val dtDelete = describe "delete motion 'dt<char>'"