X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FUndo.cpp;h=31eaec3dbd84977ee7d52e3dca4c86b3726df7f9;hb=e52a38549328a58b6fe8efeecef21a71fb9c8d65;hp=7045765769f7682121e0048cc5b52eb6491a869e;hpb=a24439b154004099ad0f02ebe81002197dd293ca;p=lyx.git diff --git a/src/Undo.cpp b/src/Undo.cpp index 7045765769..31eaec3dbd 100644 --- a/src/Undo.cpp +++ b/src/Undo.cpp @@ -4,10 +4,10 @@ * Licence details can be found in the file COPYING. * * \author Asger Alstrup - * \author Lars Gullik Bjønnes + * \author Lars Gullik Bjønnes * \author John Levon - * \author André Pönitz - * \author Jürgen Vigna + * \author André Pönitz + * \author Jürgen Vigna * \author Abdelrazak Younes * * Full author contact details are available in file CREDITS. @@ -20,7 +20,7 @@ #include "Buffer.h" #include "BufferParams.h" #include "buffer_funcs.h" -#include "DocIterator.h" +#include "Cursor.h" #include "Paragraph.h" #include "ParagraphList.h" #include "Text.h" @@ -30,8 +30,9 @@ #include "insets/Inset.h" -#include "support/lassert.h" #include "support/debug.h" +#include "support/gettext.h" +#include "support/lassert.h" #include #include @@ -43,47 +44,69 @@ using namespace lyx::support; namespace lyx { /** -These are the elements put on the undo stack. Each object contains complete -paragraphs from some cell and sufficient information to restore the cursor -state. +These are the elements put on the undo stack. Each object contains +complete paragraphs from some cell and sufficient information to +restore the cursor state. -The cell is given by a DocIterator pointing to this cell, the 'interesting' -range of paragraphs by counting them from begin and end of cell, -respectively. +The cell is given by a DocIterator pointing to this cell, the +'interesting' range of paragraphs by counting them from begin and end +of cell, respectively. -The cursor is also given as DocIterator and should point to some place in -the stored paragraph range. In case of math, we simply store the whole -cell, as there usually is just a simple paragraph in a cell. +The cursor is also given as DocIterator and should point to some place +in the stored paragraph range. In case of math, we simply store the +whole cell, as there usually is just a simple paragraph in a cell. The idea is to store the contents of 'interesting' paragraphs in some structure ('Undo') _before_ it is changed in some edit operation. -Obviously, the stored ranged should be as small as possible. However, it -there is a lower limit: The StableDocIterator pointing stored in the undo -class must be valid after the changes, too, as it will used as a pointer +Obviously, the stored range should be as small as possible. However, +there is a lower limit: The StableDocIterator stored in the undo class +must be valid after the changes, too, as it will used as a pointer where to insert the stored bits when performining undo. */ - - struct UndoElement { /// - UndoElement(UndoKind kin, StableDocIterator const & cur, + UndoElement(UndoKind kin, CursorData const & cb, StableDocIterator const & cel, - pit_type fro, pit_type en, ParagraphList * pl, - MathData * ar, BufferParams const & bp, - bool ifb) : - kind(kin), cursor(cur), cell(cel), from(fro), end(en), - pars(pl), array(ar), bparams(0), isFullBuffer(ifb) + pit_type fro, pit_type en, ParagraphList * pl, + MathData * ar, BufferParams const & bp, + bool ifb, bool lc, size_t gid) : + kind(kin), cur_before(cb), cell(cel), from(fro), end(en), + pars(pl), array(ar), bparams(0), isFullBuffer(ifb), + lyx_clean(lc), group_id(gid) { if (isFullBuffer) bparams = new BufferParams(bp); } /// - ~UndoElement() { delete bparams; } + UndoElement(UndoElement const & ue) + { + kind = ue.kind; + cur_before = ue.cur_before; + cur_after = ue.cur_after; + cell = ue.cell; + from = ue.from; + end = ue.end; + pars = ue.pars; + array = ue.array; + bparams = ue.isFullBuffer + ? new BufferParams(*ue.bparams) : ue.bparams; + isFullBuffer = ue.isFullBuffer; + lyx_clean = ue.lyx_clean; + group_id = ue.group_id; + } + /// + ~UndoElement() + { + if (isFullBuffer) + delete bparams; + } /// Which kind of operation are we recording for? UndoKind kind; - /// the position of the cursor - StableDocIterator cursor; + /// the position of the cursor before recordUndo + CursorData cur_before; + /// the position of the cursor at the end of the undo group + CursorData cur_after; /// the position of the cell described StableDocIterator cell; /// counted from begin of cell @@ -98,13 +121,17 @@ struct UndoElement BufferParams const * bparams; /// Only used in case of full backups bool isFullBuffer; + /// Was the buffer clean at this point? + bool lyx_clean; + /// the element's group id + size_t group_id; private: /// Protect construction - UndoElement(); + UndoElement(); }; -class UndoElementStack +class UndoElementStack { public: /// limit is the maximum size of the stack @@ -130,12 +157,26 @@ public: c_.clear(); } - /// Push an item on to the stack, deleting the - /// bottom item on overflow. + /// Push an item on to the stack, deleting the bottom group on + /// overflow. void push(UndoElement const & v) { + // Remove some entries if the limit has been reached. + // However, if the only group on the stack is the one + // we are currently populating, do nothing. + if (c_.size() >= limit_ + && c_.front().group_id != v.group_id) { + // remove a whole group at once. + const size_t gid = c_.back().group_id; + while (!c_.empty() && c_.back().group_id == gid) + c_.pop_back(); + } c_.push_front(v); - if (c_.size() > limit_) - c_.pop_back(); + } + + /// Mark all the elements of the stack as dirty + void markDirty() { + for (size_t i = 0; i != c_.size(); ++i) + c_[i].lyx_clean = false; } private: @@ -148,24 +189,30 @@ private: struct Undo::Private { - Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true) {} - - // Returns false if no undo possible. - bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation); + Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true), + group_id(0), group_level(0) {} + + // Do one undo/redo step + void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, + UndoElementStack & otherStack); + // Apply one undo/redo group. Returns false if no undo possible. + bool textUndoOrRedo(CursorData & cur, bool isUndoOperation); + /// void doRecordUndo(UndoKind kind, DocIterator const & cell, pit_type first_pit, pit_type last_pit, - DocIterator const & cur, + CursorData const & cur, bool isFullBuffer, - bool isUndoOperation); - + UndoElementStack & stack); /// void recordUndo(UndoKind kind, - DocIterator & cur, + DocIterator const & cell, pit_type first_pit, - pit_type last_pit); + pit_type last_pit, + CursorData const & cur, + bool isFullBuffer); /// Buffer & buffer_; @@ -176,6 +223,11 @@ struct Undo::Private /// The flag used by Undo::finishUndo(). bool undo_finished_; + + /// Current group Id. + size_t group_id; + /// Current group nesting nevel. + size_t group_level; }; @@ -197,6 +249,18 @@ Undo::~Undo() } +void Undo::clear() +{ + d->undostack_.clear(); + d->redostack_.clear(); + d->undo_finished_ = true; + // We used to do that, but I believe it is better to keep + // groups (only used in Buffer::reload for now (JMarc) + //d->group_id = 0; + //d->group_level = 0; +} + + bool Undo::hasUndoStack() const { return !d->undostack_.empty(); @@ -209,11 +273,11 @@ bool Undo::hasRedoStack() const } -static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2) +void Undo::markDirty() { - StableDocIterator tmpi2 = i2; - tmpi2.pos() = i1.pos(); - return i1 == tmpi2; + d->undo_finished_ = true; + d->undostack_.markDirty(); + d->redostack_.markDirty(); } @@ -223,13 +287,26 @@ static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2) // /////////////////////////////////////////////////////////////////////// +static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2) +{ + StableDocIterator tmpi2 = i2; + tmpi2.pos() = i1.pos(); + return i1 == tmpi2; +} + + void Undo::Private::doRecordUndo(UndoKind kind, DocIterator const & cell, pit_type first_pit, pit_type last_pit, - DocIterator const & cur, + CursorData const & cur_before, bool isFullBuffer, - bool isUndoOperation) + UndoElementStack & stack) { + if (!group_level) { + LYXERR0("There is no group open (creating one)"); + ++group_id; + } + if (first_pit > last_pit) swap(first_pit, last_pit); @@ -238,30 +315,37 @@ void Undo::Private::doRecordUndo(UndoKind kind, // we want combine 'similar' non-ATOMIC undo recordings to one. pit_type from = first_pit; pit_type end = cell.lastpit() - last_pit; - UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_; if (!undo_finished_ && kind != ATOMIC_UNDO && !stack.empty() && samePar(stack.top().cell, cell) && stack.top().kind == kind && stack.top().from == from - && stack.top().end == end) + && stack.top().end == end) { + // reset cur_after; it will be filled correctly by endUndoGroup. + stack.top().cur_after = CursorData(); return; + } + if (isFullBuffer) + LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id); + else + LYXERR(Debug::UNDO, "Create undo element of group " << group_id); // create the position information of the Undo entry - UndoElement undo(kind, cur, cell, from, end, 0, 0, - buffer_.params(), isFullBuffer); + UndoElement undo(kind, cur_before, cell, from, end, 0, 0, + buffer_.params(), isFullBuffer, buffer_.isClean(), group_id); // fill in the real data to be saved if (cell.inMathed()) { // simply use the whole cell - undo.array = new MathData(cell.cell()); + MathData & ar = cell.cell(); + undo.array = new MathData(ar.buffer(), ar.begin(), ar.end()); } else { // some more effort needed here as 'the whole cell' of the // main Text _is_ the whole document. // record the relevant paragraphs Text const * text = cell.text(); - LASSERT(text, /**/); + LBUFERR(text); ParagraphList const & plist = text->paragraphs(); ParagraphList::const_iterator first = plist.begin(); advance(first, first_pit); @@ -273,22 +357,27 @@ void Undo::Private::doRecordUndo(UndoKind kind, // push the undo entry to undo stack stack.push(undo); //lyxerr << "undo record: " << stack.top() << endl; - - // next time we'll try again to combine entries if possible - undo_finished_ = false; } -void Undo::Private::recordUndo(UndoKind kind, DocIterator & cur, - pit_type first_pit, pit_type last_pit) +void Undo::Private::recordUndo(UndoKind kind, + DocIterator const & cell, + pit_type first_pit, pit_type last_pit, + CursorData const & cur, + bool isFullBuffer) { - LASSERT(first_pit <= cur.lastpit(), /**/); - LASSERT(last_pit <= cur.lastpit(), /**/); + LASSERT(first_pit <= cell.lastpit(), return); + LASSERT(last_pit <= cell.lastpit(), return); - doRecordUndo(kind, cur, first_pit, last_pit, cur, - false, true); + doRecordUndo(kind, cell, first_pit, last_pit, cur, + isFullBuffer, undostack_); + // next time we'll try again to combine entries if possible undo_finished_ = false; + + // If we ran recordUndo, it means that we plan to change the buffer + buffer_.markDirty(); + redostack_.clear(); //lyxerr << "undostack:\n"; //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i) @@ -296,36 +385,27 @@ void Undo::Private::recordUndo(UndoKind kind, DocIterator & cur, } -bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation) +void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack) { - undo_finished_ = true; - - UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_; - - if (stack.empty()) - // Nothing to do. - return false; - - UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_; - // Adjust undo stack and get hold of current undo data. UndoElement & undo = stack.top(); + LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id); // We'll pop the stack only when we're done with this element. So do NOT // try to return early. // We will store in otherstack the part of the document under 'undo' - DocIterator cell_dit = undo.cell.asDocIterator(&buffer_.inset()); + DocIterator cell_dit = undo.cell.asDocIterator(&buffer_); doRecordUndo(ATOMIC_UNDO, cell_dit, - undo.from, cell_dit.lastpit() - undo.end, cur, - undo.isFullBuffer, !isUndoOperation); + undo.from, cell_dit.lastpit() - undo.end, undo.cur_after, + undo.isFullBuffer, otherstack); + otherstack.top().cur_after = undo.cur_before; // This does the actual undo/redo. //LYXERR0("undo, performing: " << undo); - bool labelsUpdateNeeded = false; - DocIterator dit = undo.cell.asDocIterator(&buffer_.inset()); + DocIterator dit = undo.cell.asDocIterator(&buffer_); if (undo.isFullBuffer) { - LASSERT(undo.pars, /**/); + LBUFERR(undo.pars); // This is a full document delete otherstack.top().bparams; otherstack.top().bparams = new BufferParams(buffer_.params()); @@ -338,15 +418,15 @@ bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation) // gained by storing just 'a few' paragraphs (most if not // all math inset cells have just one paragraph!) //LYXERR0("undo.array: " << *undo.array); - LASSERT(undo.array, /**/); + LBUFERR(undo.array); dit.cell().swap(*undo.array); delete undo.array; undo.array = 0; } else { // Some finer machinery is needed here. Text * text = dit.text(); - LASSERT(text, /**/); - LASSERT(undo.pars, /**/); + LBUFERR(text); + LBUFERR(undo.pars); ParagraphList & plist = text->paragraphs(); // remove new stuff between first and last @@ -369,18 +449,41 @@ bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation) plist.insert(first, undo.pars->begin(), undo.pars->end()); delete undo.pars; undo.pars = 0; - labelsUpdateNeeded = true; } - LASSERT(undo.pars == 0, /**/); - LASSERT(undo.array == 0, /**/); - cur = undo.cursor.asDocIterator(&buffer_.inset()); + // We'll clean up in release mode. + LASSERT(undo.pars == 0, undo.pars = 0); + LASSERT(undo.array == 0, undo.array = 0); + + if (!undo.cur_before.empty()) + cur = undo.cur_before; + if (undo.lyx_clean) + buffer_.markClean(); + else + buffer_.markDirty(); // Now that we're done with undo, we pop it off the stack. stack.pop(); +} + - if (labelsUpdateNeeded) - updateLabels(buffer_); +bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation) +{ undo_finished_ = true; + + UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_; + + if (stack.empty()) + // Nothing to do. + return false; + + UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_; + + const size_t gid = stack.top().group_id; + while (!stack.empty() && stack.top().group_id == gid) + doTextUndoOrRedo(cur, stack, otherstack); + + // Adapt the new material to current buffer. + buffer_.setBuffersForInsets(); // FIXME This shouldn't be here. return true; } @@ -392,55 +495,96 @@ void Undo::finishUndo() } -bool Undo::textUndo(DocIterator & cur) +bool Undo::textUndo(CursorData & cur) { return d->textUndoOrRedo(cur, true); } -bool Undo::textRedo(DocIterator & cur) +bool Undo::textRedo(CursorData & cur) { return d->textUndoOrRedo(cur, false); } -void Undo::recordUndo(DocIterator & cur, UndoKind kind) +void Undo::beginUndoGroup() +{ + if (d->group_level == 0) { + // create a new group + ++d->group_id; + LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id); + } + ++d->group_level; +} + + +void Undo::endUndoGroup() +{ + if (d->group_level == 0) { + LYXERR0("There is no undo group to end here"); + return; + } + --d->group_level; + if (d->group_level == 0) { + // real end of the group + LYXERR(Debug::UNDO, "-------End of group " << d->group_id); + } +} + + +void Undo::endUndoGroup(CursorData const & cur) +{ + endUndoGroup(); + if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty()) + d->undostack_.top().cur_after = cur; +} + + +// FIXME: remove these convenience functions and make +// Private::recordUndo public as sole interface. The code in the +// convenience functions can move to Cursor.cpp. + +void Undo::recordUndo(CursorData const & cur, UndoKind kind) { - d->recordUndo(kind, cur, cur.pit(), cur.pit()); + d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur, false); } -void Undo::recordUndoInset(DocIterator & cur, UndoKind kind) +void Undo::recordUndoInset(CursorData const & cur, UndoKind kind, + Inset const * inset) { - DocIterator c = cur; - c.pop_back(); - d->doRecordUndo(kind, c, c.pit(), c.pit(), cur, false, true); + if (!inset || inset == &cur.inset()) { + DocIterator c = cur; + c.pop_back(); + d->recordUndo(kind, c, c.pit(), c.pit(), cur, false); + } else if (inset == cur.nextInset()) + recordUndo(cur, kind); + else + LYXERR0("Inset not found, no undo stack added."); } -void Undo::recordUndo(DocIterator & cur, UndoKind kind, pit_type from) +void Undo::recordUndo(CursorData const & cur, UndoKind kind, pit_type from) { - d->recordUndo(kind, cur, cur.pit(), from); + d->recordUndo(kind, cur, cur.pit(), from, cur, false); } -void Undo::recordUndo(DocIterator & cur, UndoKind kind, +void Undo::recordUndo(CursorData const & cur, UndoKind kind, pit_type from, pit_type to) { - d->recordUndo(kind, cur, from, to); + d->recordUndo(kind, cur, from, to, cur, false); } -void Undo::recordUndoFullDocument(DocIterator & cur) +void Undo::recordUndoFullDocument(CursorData const & cur) { - d->doRecordUndo( - ATOMIC_UNDO, - doc_iterator_begin(d->buffer_.inset()), - 0, d->buffer_.paragraphs().size() - 1, - cur, - true, - true - ); + // This one may happen outside of the main undo group, so we + // put it in its own subgroup to avoid complaints. + beginUndoGroup(); + d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_), + 0, d->buffer_.paragraphs().size() - 1, cur, true); + endUndoGroup(); }