bool Client::handleUndoCommand(protocol::Undo &undo) { // First check if user context override is used if(undo.overrideId()) { undo.setContextId(undo.overrideId()); } // Undo history is limited to last UNDO_HISTORY_LIMIT undopoints // or the latest snapshot point, whichever comes first // Clients usually store more than that, but to ensure a consistent // experience, we enforce the limit here. if(undo.points()>0) { // Undo mode: iterator towards the beginning of the history, // marking not-undone UndoPoints as undone. int limit = protocol::UNDO_HISTORY_LIMIT; int pos = _session->mainstream().end()-1; int points = 0; while(limit>0 && points<undo.points() && _session->mainstream().isValidIndex(pos)) { MessagePtr msg = _session->mainstream().at(pos); if(msg->type() == protocol::MSG_SNAPSHOTPOINT) break; if(msg->type() == protocol::MSG_UNDOPOINT) { --limit; if(msg->contextId() == undo.contextId() && msg->undoState()==protocol::DONE) { msg->setUndoState(protocol::UNDONE); ++points; } } --pos; } // Did we undo anything? if(points==0) return false; // Number of undoable actions may be less than expected, but undo what we got. undo.setPoints(points); return true; } else if(undo.points()<0) { // Redo mode: find the start of the latest undo sequence, then mark // (points) UndoPoints as redone. int redostart = _session->mainstream().end(); int limit = protocol::UNDO_HISTORY_LIMIT; int pos = _session->mainstream().end(); while(_session->mainstream().isValidIndex(--pos) && limit>0) { protocol::MessagePtr msg = _session->mainstream().at(pos); if(msg->type() == protocol::MSG_UNDOPOINT) { --limit; if(msg->contextId() == undo.contextId()) { if(msg->undoState() != protocol::DONE) redostart = pos; else break; } } } // There may be nothing to redo if(redostart == _session->mainstream().end()) return false; pos = redostart; // Mark undone actions as done again int actions = -undo.points() + 1; int points = 0; while(pos < _session->mainstream().end()) { protocol::MessagePtr msg = _session->mainstream().at(pos); if(msg->contextId() == undo.contextId() && msg->type() == protocol::MSG_UNDOPOINT) { if(msg->undoState() == protocol::UNDONE) { if(--actions==0) break; msg->setUndoState(protocol::DONE); ++points; } } ++pos; } // Did we redo anything if(points==0) return false; undo.setPoints(-points); return true; } else { // points==0 is invalid return false; } }