X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FUndo.cpp;h=143af4e83ff0b9f29eac7ecc2f1abbf5e6c8ceb4;hb=28be7d552f62cc02fa86d7f79201d089bfb2d7b5;hp=a80c01c701154d6e393f2475759208743d5746b2;hpb=bcd6990ecaff12cb5437a4717d91dfe619389546;p=lyx.git diff --git a/src/Undo.cpp b/src/Undo.cpp index a80c01c701..143af4e83f 100644 --- a/src/Undo.cpp +++ b/src/Undo.cpp @@ -20,19 +20,23 @@ #include "Buffer.h" #include "BufferParams.h" #include "buffer_funcs.h" -#include "DocIterator.h" +#include "Cursor.h" +#include "CutAndPaste.h" +#include "ErrorList.h" #include "Paragraph.h" #include "ParagraphList.h" -#include "ParagraphParameters.h" #include "Text.h" #include "mathed/MathSupport.h" #include "mathed/MathData.h" #include "insets/Inset.h" +#include "insets/InsetText.h" -#include "support/lassert.h" #include "support/debug.h" +#include "support/gettext.h" +#include "support/lassert.h" +#include "support/lyxtime.h" #include #include @@ -61,25 +65,30 @@ structure ('Undo') _before_ it is changed in some edit operation. 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. +where to insert the stored bits when performining undo. */ struct UndoElement { /// - UndoElement(UndoKind kin, StableDocIterator const & cb, + UndoElement(UndoKind kin, CursorData const & cb, StableDocIterator const & cel, - 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) + pit_type fro, pit_type en, ParagraphList * pl, MathData * ar, + bool lc, size_t gid) : + kind(kin), cur_before(cb), cell(cel), from(fro), end(en), + pars(pl), array(ar), bparams(0), + lyx_clean(lc), group_id(gid), time(current_time()) + { + } + /// + UndoElement(CursorData const & cb, BufferParams const & bp, + bool lc, size_t gid) : + kind(ATOMIC_UNDO), cur_before(cb), cell(), from(0), end(0), + pars(0), array(0), bparams(new BufferParams(bp)), + lyx_clean(lc), group_id(gid), time(current_time()) { - if (isFullBuffer) - bparams = new BufferParams(bp); } /// - UndoElement(UndoElement const & ue) + UndoElement(UndoElement const & ue) : time(current_time()) { kind = ue.kind; cur_before = ue.cur_before; @@ -89,24 +98,23 @@ struct UndoElement end = ue.end; pars = ue.pars; array = ue.array; - bparams = ue.isFullBuffer - ? new BufferParams(*ue.bparams) : ue.bparams; - isFullBuffer = ue.isFullBuffer; + bparams = ue.bparams + ? new BufferParams(*ue.bparams) : 0; lyx_clean = ue.lyx_clean; group_id = ue.group_id; } /// ~UndoElement() { - if (isFullBuffer) + if (bparams) delete bparams; } /// Which kind of operation are we recording for? UndoKind kind; /// the position of the cursor before recordUndo - StableDocIterator cur_before; + CursorData cur_before; /// the position of the cursor at the end of the undo group - StableDocIterator cur_after; + CursorData cur_after; /// the position of the cell described StableDocIterator cell; /// counted from begin of cell @@ -117,21 +125,21 @@ struct UndoElement ParagraphList * pars; /// the contents of the saved MathData (for mathed) MathData * array; - /// Only used in case of full backups + /// Only used in case of params undo 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; + /// timestamp + time_t time; private: /// Protect construction - UndoElement(); + UndoElement(); }; -class UndoElementStack +class UndoElementStack { public: /// limit is the maximum size of the stack @@ -160,20 +168,24 @@ public: /// Push an item on to the stack, deleting the bottom group on /// overflow. void push(UndoElement const & v) { - c_.push_front(v); - if (c_.size() > limit_) { + // 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); } /// 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: /// Internal contents. @@ -185,30 +197,32 @@ private: struct Undo::Private { - Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true), - group_id(0), group_level(0) {} - + Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true), + group_id_(0), group_level_(0) {} + // Do one undo/redo step - void doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, + void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherStack); // Apply one undo/redo group. Returns false if no undo possible. - bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation); + bool textUndoOrRedo(CursorData & cur, bool isUndoOperation); /// void doRecordUndo(UndoKind kind, DocIterator const & cell, pit_type first_pit, pit_type last_pit, - StableDocIterator const & cur, - bool isFullBuffer, + CursorData const & cur, UndoElementStack & stack); /// void recordUndo(UndoKind kind, DocIterator const & cell, pit_type first_pit, pit_type last_pit, - DocIterator const & cur, - bool isFullBuffer); + CursorData const & cur); + /// + void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack); + /// + void recordUndoBufferParams(CursorData const & cur); /// Buffer & buffer_; @@ -221,9 +235,11 @@ struct Undo::Private bool undo_finished_; /// Current group Id. - size_t group_id; + size_t group_id_; /// Current group nesting nevel. - size_t group_level; + size_t group_level_; + /// the position of cursor before the group was created + CursorData group_cur_before_; }; @@ -250,8 +266,10 @@ void Undo::clear() d->undostack_.clear(); d->redostack_.clear(); d->undo_finished_ = true; - d->group_id = 0; - d->group_level = 0; + // 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; } @@ -271,7 +289,7 @@ void Undo::markDirty() { d->undo_finished_ = true; d->undostack_.markDirty(); - d->redostack_.markDirty(); + d->redostack_.markDirty(); } @@ -292,13 +310,12 @@ static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2) void Undo::Private::doRecordUndo(UndoKind kind, DocIterator const & cell, pit_type first_pit, pit_type last_pit, - StableDocIterator const & cur_before, - bool isFullBuffer, + CursorData const & cur_before, UndoElementStack & stack) { - if (!group_level) { + if (!group_level_) { LYXERR0("There is no group open (creating one)"); - ++group_id; + ++group_id_; } if (first_pit > last_pit) @@ -312,22 +329,25 @@ void Undo::Private::doRecordUndo(UndoKind kind, if (!undo_finished_ && kind != ATOMIC_UNDO && !stack.empty() + && !stack.top().bparams && samePar(stack.top().cell, cell) && stack.top().kind == kind && stack.top().from == from - && stack.top().end == end) { + && stack.top().end == end + && stack.top().cur_after == cur_before + && current_time() - stack.top().time <= 2) { // reset cur_after; it will be filled correctly by endUndoGroup. - stack.top().cur_after = StableDocIterator(); + stack.top().cur_after = CursorData(); + // update the timestamp of the undo element + stack.top().time = current_time(); 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); + LYXERR(Debug::UNDO, "Create undo element of group " << group_id_); // create the position information of the Undo entry - UndoElement undo(kind, cur_before, cell, from, end, 0, 0, - buffer_.params(), isFullBuffer, buffer_.isClean(), group_id); + UndoElement undo(kind, + group_cur_before_.empty() ? cur_before : group_cur_before_, + cell, from, end, 0, 0, buffer_.isClean(), group_id_); // fill in the real data to be saved if (cell.inMathed()) { @@ -339,21 +359,12 @@ void Undo::Private::doRecordUndo(UndoKind kind, // 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); ParagraphList::const_iterator last = plist.begin(); advance(last, last_pit + 1); - // If the paragraphs after the last one have a - // non-zero depth and the depth of last paragraph is - // decremented, then these paragraphs may be affected - // (ticket #8159). We guard against that by saving - // these extra paragraphs. - while (last != plist.end() && last->params().depth() > 0) { - ++last; - --undo.end; - } undo.pars = new ParagraphList(first, last); } @@ -366,14 +377,13 @@ void Undo::Private::doRecordUndo(UndoKind kind, void Undo::Private::recordUndo(UndoKind kind, DocIterator const & cell, pit_type first_pit, pit_type last_pit, - DocIterator const & cur, - bool isFullBuffer) + CursorData const & cur) { - LASSERT(first_pit <= cell.lastpit(), /**/); - LASSERT(last_pit <= cell.lastpit(), /**/); + LASSERT(first_pit <= cell.lastpit(), return); + LASSERT(last_pit <= cell.lastpit(), return); doRecordUndo(kind, cell, first_pit, last_pit, cur, - isFullBuffer, undostack_); + undostack_); // next time we'll try again to combine entries if possible undo_finished_ = false; @@ -382,13 +392,43 @@ void Undo::Private::recordUndo(UndoKind kind, buffer_.markDirty(); redostack_.clear(); - //lyxerr << "undostack:\n"; - //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i) - // lyxerr << " " << i << ": " << buf.undostack()[i] << endl; } -void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack) +void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before, + UndoElementStack & stack) +{ + if (!group_level_) { + LYXERR0("There is no group open (creating one)"); + ++group_id_; + } + + LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_); + // create the position information of the Undo entry + UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_, + buffer_.params(), buffer_.isClean(), + group_id_); + + // push the undo entry to undo stack + stack.push(undo); +} + + +void Undo::Private::recordUndoBufferParams(CursorData const & cur) +{ + doRecordUndoBufferParams(cur, 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(); +} + + +void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack) { // Adjust undo stack and get hold of current undo data. UndoElement & undo = stack.top(); @@ -399,37 +439,44 @@ void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack // We will store in otherstack the part of the document under 'undo' DocIterator cell_dit = undo.cell.asDocIterator(&buffer_); - doRecordUndo(ATOMIC_UNDO, cell_dit, - undo.from, cell_dit.lastpit() - undo.end, undo.cur_after, - undo.isFullBuffer, otherstack); + if (undo.bparams) + doRecordUndoBufferParams(undo.cur_after, otherstack); + else { + LATTEST(undo.end <= cell_dit.lastpit()); + doRecordUndo(ATOMIC_UNDO, cell_dit, + undo.from, cell_dit.lastpit() - undo.end, undo.cur_after, + otherstack); + } otherstack.top().cur_after = undo.cur_before; // This does the actual undo/redo. //LYXERR0("undo, performing: " << undo); DocIterator dit = undo.cell.asDocIterator(&buffer_); - if (undo.isFullBuffer) { - LASSERT(undo.pars, /**/); - // This is a full document + if (undo.bparams) { + // This is a params undo element delete otherstack.top().bparams; otherstack.top().bparams = new BufferParams(buffer_.params()); + DocumentClassConstPtr olddc = buffer_.params().documentClassPtr(); buffer_.params() = *undo.bparams; - swap(buffer_.paragraphs(), *undo.pars); - delete undo.pars; - undo.pars = 0; + // The error list is not supposed to be helpful here. + ErrorList el; + cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(), + static_cast(buffer_.inset()), el); + LATTEST(el.empty()); } else if (dit.inMathed()) { // We stored the full cell here as there is not much to be // 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 @@ -453,11 +500,13 @@ void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack delete undo.pars; undo.pars = 0; } - LASSERT(undo.pars == 0, /**/); - LASSERT(undo.array == 0, /**/); - if (undo.cur_before.size()) - cur = undo.cur_before.asDocIterator(&buffer_); + // 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 @@ -467,7 +516,7 @@ void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack } -bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation) +bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation) { undo_finished_ = true; @@ -496,13 +545,13 @@ 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); } @@ -510,81 +559,100 @@ bool Undo::textRedo(DocIterator & cur) void Undo::beginUndoGroup() { - if (d->group_level == 0) { + if (d->group_level_ == 0) { // create a new group - ++d->group_id; - LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id); + ++d->group_id_; + LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id_); } - ++d->group_level; + ++d->group_level_; +} + + +void Undo::beginUndoGroup(CursorData const & cur_before) +{ + beginUndoGroup(); + if (d->group_cur_before_.empty()) + d->group_cur_before_ = cur_before; } void Undo::endUndoGroup() { - if (d->group_level == 0) + if (d->group_level_ == 0) { LYXERR0("There is no undo group to end here"); - --d->group_level; - if (d->group_level == 0) { + return; + } + --d->group_level_; + if (d->group_level_ == 0) { // real end of the group - LYXERR(Debug::UNDO, "-------End of group " << d->group_id); + d->group_cur_before_ = CursorData(); + LYXERR(Debug::UNDO, "-------End of group " << d->group_id_); } } -void Undo::endUndoGroup(DocIterator const & cur) +void Undo::endUndoGroup(CursorData const & cur_after) { endUndoGroup(); - if (!d->undostack_.empty() && !d->undostack_.top().cur_after.size()) - d->undostack_.top().cur_after = cur; + if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty()) + d->undostack_.top().cur_after = cur_after; } -// 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(), cur); +} -void Undo::recordUndo(DocIterator const & cur, UndoKind kind) + +void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to) { - d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur, false); + d->recordUndo(ATOMIC_UNDO, cur, from, to, cur); } -void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind, - Inset const * inset) +void Undo::recordUndoInset(CursorData const & cur, Inset const * inset) { if (!inset || inset == &cur.inset()) { DocIterator c = cur; c.pop_back(); - d->recordUndo(kind, c, c.pit(), c.pit(), cur, false); + d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur); } else if (inset == cur.nextInset()) - recordUndo(cur, kind); + recordUndo(cur); else LYXERR0("Inset not found, no undo stack added."); } -void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from) -{ - d->recordUndo(kind, cur, cur.pit(), from, cur, false); -} - - -void Undo::recordUndo(DocIterator const & cur, UndoKind kind, - pit_type from, pit_type to) +void Undo::recordUndoBufferParams(CursorData const & cur) { - d->recordUndo(kind, cur, from, to, cur, false); + d->recordUndoBufferParams(cur); } -void Undo::recordUndoFullDocument(DocIterator const & cur) +void Undo::recordUndoFullBuffer(CursorData const & cur) { // 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); + d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_), + 0, d->buffer_.paragraphs().size() - 1, cur); + d->recordUndoBufferParams(cur); endUndoGroup(); } +/// UndoGroupHelper class stuff + +void UndoGroupHelper::resetBuffer(Buffer * buf) +{ + if (buf == buffer_) + return; + if (buffer_) + buffer_->undo().endUndoGroup(); + buffer_ = buf; + if (buffer_) + buffer_->undo().beginUndoGroup(); +} + } // namespace lyx