Undo history that merges typing

Write codeMedium25 min

Your task

A single-line text field keeps an undo history, and you are replaying a recorded session to test it. The field starts empty. Each event is a list:

  • ["type", t, pos, text]: insert text (never empty) at index pos of the current value, at time t in milliseconds.
  • ["delete", t, pos, count]: remove count characters (at least 1) starting at index pos. The range always lies inside the current value.
  • ["undo", t] and ["redo", t].

Times never decrease. Every edit pushes one entry onto the undo stack, with one exception: typing coalesces. A type event is merged into the entry on top of the undo stack, so that a single undo removes both, when all three of these hold:

  1. The event immediately before it was also a type event. Any other event in between, even an undo or redo that changed nothing, starts a new entry.
  2. It inserts exactly where the top entry's text ends: pos equals the position where that entry started inserting plus the length of the text it holds so far.
  3. It comes at most 1000 ms after the previous type event.

Deletes never merge. Any edit (type or delete) clears the redo stack. Undo pops the top entry, reverts it and pushes it onto the redo stack. Redo pops the top of the redo stack, applies it again and pushes it back onto the undo stack. An undo or redo with an empty stack does nothing. Return the field's value after each event.

Examples:

  • replay_edits([["type", 0, 0, "H"], ["type", 200, 1, "i"], ["undo", 900]]) returns ["H", "Hi", ""]: the two keystrokes merged, so one undo removes both.
  • replay_edits([["type", 0, 0, "ab"], ["type", 1500, 2, "c"], ["undo", 1600], ["redo", 1700]]) returns ["ab", "abc", "ab", "abc"]: 1500 ms is too long a pause to merge.
  • replay_edits([["type", 0, 0, "cat"], ["delete", 100, 0, 1], ["undo", 200], ["undo", 300], ["redo", 400]]) returns ["cat", "at", "cat", "", "cat"].

Examples

replay_edits(events: list[list]) -> list[str]
Example 1
Inputreplay_edits([["type", 0, 0, "H"], ["type", 200, 1, "i"], ["undo", 900]])
Output["H", "Hi", ""]
Example 2
Inputreplay_edits([["type", 0, 0, "ab"], ["type", 1500, 2, "c"], ["undo", 1600], ["redo", 1700]])
Output["ab", "abc", "ab", "abc"]
Example 3
Inputreplay_edits([["type", 0, 0, "cat"], ["delete", 100, 0, 1], ["undo", 200], ["undo", 300], ["redo", 400]])
Output["cat", "at", "cat", "", "cat"]