Examples
Input: ([('apply', 'insert', 0, 'abc'), ('apply', 'insert', 3, 'def'), ('get',), ('undo',), ('get',), ('redo',), ('get',)],)
Expected Output: ('abcdef', ['abcdef', 'abc', 'abcdef'])
Explanation: The two inserts are separate committed units. Undo removes only the second insert, and redo restores it.
Input: ([('beginBatch',), ('apply', 'insert', 0, 'hello'), ('apply', 'insert', 5, ' world'), ('commitBatch',), ('get',), ('undo',), ('get',), ('redo',), ('get',)],)
Expected Output: ('hello world', ['hello world', '', 'hello world'])
Explanation: Both inserts are committed together as one batch. Undo removes the whole batch, and redo reapplies it.
Input: ([('apply', 'insert', 0, 'abc'), ('beginBatch',), ('apply', 'insert', 3, 'd'), ('apply', 'delete', 10, 1), ('get',), ('undo',), ('get',)],)
Expected Output: ('', ['abc', ''])
Explanation: The invalid delete aborts the batch and rolls back the pending insert of 'd'. The earlier committed insert of 'abc' can still be undone.
Input: ([('apply', 'insert', 0, 'x'), ('beginBatch',), ('undo',), ('apply', 'insert', 1, 'y'), ('beginBatch',), ('redo',), ('get',), ('commitBatch',), ('undo',), ('get',), ('redo',), ('get',)],)
Expected Output: ('xy', ['xy', 'x', 'xy'])
Explanation: Undo and redo are ignored while a batch is open, and a nested beginBatch is ignored. After commit, the batch behaves as one undoable unit.
Input: ([('apply', 'insert', 0, 'ab'), ('apply', 'insert', 2, 'c'), ('undo',), ('apply', 'delete', 5, 1), ('redo',), ('get',), ('undo',), ('apply', 'insert', 2, 'd'), ('redo',), ('get',)],)
Expected Output: ('abd', ['abc', 'abd'])
Explanation: The invalid standalone delete does not clear redo, so 'c' can still be redone. After undoing again, the new committed insert of 'd' clears the redo stack.
Input: ([('apply', 'insert', 0, 'hello world'), ('beginBatch',), ('apply', 'delete', 5, 1), ('apply', 'delete', 5, 5), ('commitBatch',), ('get',), ('undo',), ('get',), ('redo',), ('get',)],)
Expected Output: ('hello', ['hello', 'hello world', 'hello'])
Explanation: The batch deletes the space and then 'world'. Undo restores both deletions together, and redo removes them again.
Input: ([('commitBatch',), ('beginBatch',), ('commitBatch',), ('undo',), ('get',)],)
Expected Output: ('', [''])
Explanation: commitBatch with no open batch is ignored, and committing an empty batch adds no history. Undo then has nothing to do.
Input: ([],)
Expected Output: ('', [])
Explanation: With no commands, the document stays empty and no snapshots are recorded.