]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
Make document_language non-mutable
[lyx.git] / src / Undo.cpp
1 /**
2  * \file Undo.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author John Levon
9  * \author André Pönitz
10  * \author Jürgen Vigna
11  * \author Abdelrazak Younes
12  *
13  * Full author contact details are available in file CREDITS.
14  */
15
16 #include <config.h>
17
18 #include "Undo.h"
19
20 #include "Buffer.h"
21 #include "BufferParams.h"
22 #include "buffer_funcs.h"
23 #include "Cursor.h"
24 #include "Paragraph.h"
25 #include "ParagraphList.h"
26 #include "Text.h"
27
28 #include "mathed/MathSupport.h"
29 #include "mathed/MathData.h"
30
31 #include "insets/Inset.h"
32
33 #include "support/debug.h"
34 #include "support/gettext.h"
35 #include "support/lassert.h"
36
37 #include <algorithm>
38 #include <deque>
39
40 using namespace std;
41 using namespace lyx::support;
42
43
44 namespace lyx {
45
46 /**
47 These are the elements put on the undo stack. Each object contains
48 complete paragraphs from some cell and sufficient information to
49 restore the cursor state.
50
51 The cell is given by a DocIterator pointing to this cell, the
52 'interesting' range of paragraphs by counting them from begin and end
53 of cell, respectively.
54
55 The cursor is also given as DocIterator and should point to some place
56 in the stored paragraph range. In case of math, we simply store the
57 whole cell, as there usually is just a simple paragraph in a cell.
58
59 The idea is to store the contents of 'interesting' paragraphs in some
60 structure ('Undo') _before_ it is changed in some edit operation.
61 Obviously, the stored range should be as small as possible. However,
62 there is a lower limit: The StableDocIterator stored in the undo class
63 must be valid after the changes, too, as it will used as a pointer
64 where to insert the stored bits when performining undo.
65 */
66 struct UndoElement
67 {
68         ///
69         UndoElement(UndoKind kin, CursorData const & cb,
70                     StableDocIterator const & cel,
71                     pit_type fro, pit_type en, ParagraphList * pl,
72                     MathData * ar, BufferParams const & bp,
73                     bool ifb, bool lc, size_t gid) :
74                 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
75                 pars(pl), array(ar), bparams(0), isFullBuffer(ifb),
76                 lyx_clean(lc), group_id(gid)
77         {
78                 if (isFullBuffer)
79                         bparams = new BufferParams(bp);
80         }
81         ///
82         UndoElement(UndoElement const & ue)
83         {
84                 kind = ue.kind;
85                 cur_before = ue.cur_before;
86                 cur_after = ue.cur_after;
87                 cell = ue.cell;
88                 from = ue.from;
89                 end = ue.end;
90                 pars = ue.pars;
91                 array = ue.array;
92                 bparams = ue.isFullBuffer
93                         ? new BufferParams(*ue.bparams) : ue.bparams;
94                 isFullBuffer = ue.isFullBuffer;
95                 lyx_clean = ue.lyx_clean;
96                 group_id = ue.group_id;
97         }
98         ///
99         ~UndoElement()
100         {
101                 if (isFullBuffer)
102                         delete bparams;
103         }
104         /// Which kind of operation are we recording for?
105         UndoKind kind;
106         /// the position of the cursor before recordUndo
107         CursorData cur_before;
108         /// the position of the cursor at the end of the undo group
109         CursorData cur_after;
110         /// the position of the cell described
111         StableDocIterator cell;
112         /// counted from begin of cell
113         pit_type from;
114         /// complement to end of this cell
115         pit_type end;
116         /// the contents of the saved Paragraphs (for texted)
117         ParagraphList * pars;
118         /// the contents of the saved MathData (for mathed)
119         MathData * array;
120         /// Only used in case of full backups
121         BufferParams const * bparams;
122         /// Only used in case of full backups
123         bool isFullBuffer;
124         /// Was the buffer clean at this point?
125         bool lyx_clean;
126         /// the element's group id
127         size_t group_id;
128 private:
129         /// Protect construction
130         UndoElement();
131 };
132
133
134 class UndoElementStack
135 {
136 public:
137         /// limit is the maximum size of the stack
138         UndoElementStack(size_t limit = 100) { limit_ = limit; }
139         /// limit is the maximum size of the stack
140         ~UndoElementStack() { clear(); }
141
142         /// Return the top element.
143         UndoElement & top() { return c_.front(); }
144
145         /// Pop and throw away the top element.
146         void pop() { c_.pop_front(); }
147
148         /// Return true if the stack is empty.
149         bool empty() const { return c_.empty(); }
150
151         /// Clear all elements, deleting them.
152         void clear() {
153                 for (size_t i = 0; i != c_.size(); ++i) {
154                         delete c_[i].array;
155                         delete c_[i].pars;
156                 }
157                 c_.clear();
158         }
159
160         /// Push an item on to the stack, deleting the bottom group on
161         /// overflow.
162         void push(UndoElement const & v) {
163                 // Remove some entries if the limit has been reached.
164                 // However, if the only group on the stack is the one
165                 // we are currently populating, do nothing.
166                 if (c_.size() >= limit_
167                     && c_.front().group_id != v.group_id) {
168                         // remove a whole group at once.
169                         const size_t gid = c_.back().group_id;
170                         while (!c_.empty() && c_.back().group_id == gid)
171                                 c_.pop_back();
172                 }
173                 c_.push_front(v);
174         }
175
176         /// Mark all the elements of the stack as dirty
177         void markDirty() {
178                 for (size_t i = 0; i != c_.size(); ++i)
179                         c_[i].lyx_clean = false;
180         }
181
182 private:
183         /// Internal contents.
184         std::deque<UndoElement> c_;
185         /// The maximum number elements stored.
186         size_t limit_;
187 };
188
189
190 struct Undo::Private
191 {
192         Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
193                                    group_id(0), group_level(0) {}
194
195         // Do one undo/redo step
196         void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack,
197                               UndoElementStack & otherStack);
198         // Apply one undo/redo group. Returns false if no undo possible.
199         bool textUndoOrRedo(CursorData & cur, bool isUndoOperation);
200
201         ///
202         void doRecordUndo(UndoKind kind,
203                 DocIterator const & cell,
204                 pit_type first_pit,
205                 pit_type last_pit,
206                 CursorData const & cur,
207                 bool isFullBuffer,
208                 UndoElementStack & stack);
209         ///
210         void recordUndo(UndoKind kind,
211                 DocIterator const & cell,
212                 pit_type first_pit,
213                 pit_type last_pit,
214                 CursorData const & cur,
215                 bool isFullBuffer);
216
217         ///
218         Buffer & buffer_;
219         /// Undo stack.
220         UndoElementStack undostack_;
221         /// Redo stack.
222         UndoElementStack redostack_;
223
224         /// The flag used by Undo::finishUndo().
225         bool undo_finished_;
226
227         /// Current group Id.
228         size_t group_id;
229         /// Current group nesting nevel.
230         size_t group_level;
231 };
232
233
234 /////////////////////////////////////////////////////////////////////
235 //
236 // Undo
237 //
238 /////////////////////////////////////////////////////////////////////
239
240
241 Undo::Undo(Buffer & buffer)
242         : d(new Undo::Private(buffer))
243 {}
244
245
246 Undo::~Undo()
247 {
248         delete d;
249 }
250
251
252 void Undo::clear()
253 {
254         d->undostack_.clear();
255         d->redostack_.clear();
256         d->undo_finished_ = true;
257         // We used to do that, but I believe it is better to keep
258         // groups (only used in Buffer::reload for now (JMarc)
259         //d->group_id = 0;
260         //d->group_level = 0;
261 }
262
263
264 bool Undo::hasUndoStack() const
265 {
266         return !d->undostack_.empty();
267 }
268
269
270 bool Undo::hasRedoStack() const
271 {
272         return !d->redostack_.empty();
273 }
274
275
276 void Undo::markDirty()
277 {
278         d->undo_finished_ = true;
279         d->undostack_.markDirty();
280         d->redostack_.markDirty();
281 }
282
283
284 /////////////////////////////////////////////////////////////////////
285 //
286 // Undo::Private
287 //
288 ///////////////////////////////////////////////////////////////////////
289
290 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
291 {
292         StableDocIterator tmpi2 = i2;
293         tmpi2.pos() = i1.pos();
294         return i1 == tmpi2;
295 }
296
297
298 void Undo::Private::doRecordUndo(UndoKind kind,
299         DocIterator const & cell,
300         pit_type first_pit, pit_type last_pit,
301         CursorData const & cur_before,
302         bool isFullBuffer,
303         UndoElementStack & stack)
304 {
305         if (!group_level) {
306                 LYXERR0("There is no group open (creating one)");
307                 ++group_id;
308         }
309
310         if (first_pit > last_pit)
311                 swap(first_pit, last_pit);
312
313         // Undo::ATOMIC are always recorded (no overlapping there).
314         // As nobody wants all removed character appear one by one when undoing,
315         // we want combine 'similar' non-ATOMIC undo recordings to one.
316         pit_type from = first_pit;
317         pit_type end = cell.lastpit() - last_pit;
318         if (!undo_finished_
319             && kind != ATOMIC_UNDO
320             && !stack.empty()
321             && samePar(stack.top().cell, cell)
322             && stack.top().kind == kind
323             && stack.top().from == from
324             && stack.top().end == end) {
325                 // reset cur_after; it will be filled correctly by endUndoGroup.
326                 stack.top().cur_after = CursorData();
327                 return;
328         }
329
330         if (isFullBuffer)
331                 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
332         else
333                 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
334         // create the position information of the Undo entry
335         UndoElement undo(kind, cur_before, cell, from, end, 0, 0,
336                          buffer_.params(), isFullBuffer, buffer_.isClean(), group_id);
337
338         // fill in the real data to be saved
339         if (cell.inMathed()) {
340                 // simply use the whole cell
341                 MathData & ar = cell.cell();
342                 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
343         } else {
344                 // some more effort needed here as 'the whole cell' of the
345                 // main Text _is_ the whole document.
346                 // record the relevant paragraphs
347                 Text const * text = cell.text();
348                 LBUFERR(text);
349                 ParagraphList const & plist = text->paragraphs();
350                 ParagraphList::const_iterator first = plist.begin();
351                 advance(first, first_pit);
352                 ParagraphList::const_iterator last = plist.begin();
353                 advance(last, last_pit + 1);
354                 undo.pars = new ParagraphList(first, last);
355         }
356
357         // push the undo entry to undo stack
358         stack.push(undo);
359         //lyxerr << "undo record: " << stack.top() << endl;
360 }
361
362
363 void Undo::Private::recordUndo(UndoKind kind,
364                                DocIterator const & cell,
365                                pit_type first_pit, pit_type last_pit,
366                                CursorData const & cur,
367                                bool isFullBuffer)
368 {
369         LASSERT(first_pit <= cell.lastpit(), return);
370         LASSERT(last_pit <= cell.lastpit(), return);
371
372         doRecordUndo(kind, cell, first_pit, last_pit, cur,
373                 isFullBuffer, undostack_);
374
375         // next time we'll try again to combine entries if possible
376         undo_finished_ = false;
377
378         // If we ran recordUndo, it means that we plan to change the buffer
379         buffer_.markDirty();
380
381         redostack_.clear();
382         //lyxerr << "undostack:\n";
383         //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
384         //      lyxerr << "  " << i << ": " << buf.undostack()[i] << endl;
385 }
386
387
388 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
389 {
390         // Adjust undo stack and get hold of current undo data.
391         UndoElement & undo = stack.top();
392         LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
393         // We'll pop the stack only when we're done with this element. So do NOT
394         // try to return early.
395
396         // We will store in otherstack the part of the document under 'undo'
397         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
398
399         doRecordUndo(ATOMIC_UNDO, cell_dit,
400                 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
401                 undo.isFullBuffer, otherstack);
402         otherstack.top().cur_after = undo.cur_before;
403
404         // This does the actual undo/redo.
405         //LYXERR0("undo, performing: " << undo);
406         DocIterator dit = undo.cell.asDocIterator(&buffer_);
407         if (undo.isFullBuffer) {
408                 LBUFERR(undo.pars);
409                 // This is a full document
410                 delete otherstack.top().bparams;
411                 otherstack.top().bparams = new BufferParams(buffer_.params());
412                 buffer_.params() = *undo.bparams;
413                 swap(buffer_.paragraphs(), *undo.pars);
414                 delete undo.pars;
415                 undo.pars = 0;
416         } else if (dit.inMathed()) {
417                 // We stored the full cell here as there is not much to be
418                 // gained by storing just 'a few' paragraphs (most if not
419                 // all math inset cells have just one paragraph!)
420                 //LYXERR0("undo.array: " << *undo.array);
421                 LBUFERR(undo.array);
422                 dit.cell().swap(*undo.array);
423                 delete undo.array;
424                 undo.array = 0;
425         } else {
426                 // Some finer machinery is needed here.
427                 Text * text = dit.text();
428                 LBUFERR(text);
429                 LBUFERR(undo.pars);
430                 ParagraphList & plist = text->paragraphs();
431
432                 // remove new stuff between first and last
433                 ParagraphList::iterator first = plist.begin();
434                 advance(first, undo.from);
435                 ParagraphList::iterator last = plist.begin();
436                 advance(last, plist.size() - undo.end);
437                 plist.erase(first, last);
438
439                 // re-insert old stuff instead
440                 first = plist.begin();
441                 advance(first, undo.from);
442
443                 // this ugly stuff is needed until we get rid of the
444                 // inset_owner backpointer
445                 ParagraphList::iterator pit = undo.pars->begin();
446                 ParagraphList::iterator const end = undo.pars->end();
447                 for (; pit != end; ++pit)
448                         pit->setInsetOwner(dit.realInset());
449                 plist.insert(first, undo.pars->begin(), undo.pars->end());
450                 delete undo.pars;
451                 undo.pars = 0;
452         }
453
454         // We'll clean up in release mode.
455         LASSERT(undo.pars == 0, undo.pars = 0);
456         LASSERT(undo.array == 0, undo.array = 0);
457
458         if (!undo.cur_before.empty())
459                 cur = undo.cur_before;
460         if (undo.lyx_clean)
461                 buffer_.markClean();
462         else
463                 buffer_.markDirty();
464         // Now that we're done with undo, we pop it off the stack.
465         stack.pop();
466 }
467
468
469 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
470 {
471         undo_finished_ = true;
472
473         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
474
475         if (stack.empty())
476                 // Nothing to do.
477                 return false;
478
479         UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
480
481         const size_t gid = stack.top().group_id;
482         while (!stack.empty() && stack.top().group_id == gid)
483                 doTextUndoOrRedo(cur, stack, otherstack);
484
485         // Adapt the new material to current buffer.
486         buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
487         return true;
488 }
489
490
491 void Undo::finishUndo()
492 {
493         // Make sure the next operation will be stored.
494         d->undo_finished_ = true;
495 }
496
497
498 bool Undo::textUndo(CursorData & cur)
499 {
500         return d->textUndoOrRedo(cur, true);
501 }
502
503
504 bool Undo::textRedo(CursorData & cur)
505 {
506         return d->textUndoOrRedo(cur, false);
507 }
508
509
510 void Undo::beginUndoGroup()
511 {
512         if (d->group_level == 0) {
513                 // create a new group
514                 ++d->group_id;
515                 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
516         }
517         ++d->group_level;
518 }
519
520
521 void Undo::endUndoGroup()
522 {
523         if (d->group_level == 0) {
524                 LYXERR0("There is no undo group to end here");
525                 return;
526         }
527         --d->group_level;
528         if (d->group_level == 0) {
529                 // real end of the group
530                 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
531         }
532 }
533
534
535 void Undo::endUndoGroup(CursorData const & cur)
536 {
537         endUndoGroup();
538         if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
539                 d->undostack_.top().cur_after = cur;
540 }
541
542
543 // FIXME: remove these convenience functions and make
544 // Private::recordUndo public as sole interface. The code in the
545 // convenience functions can move to Cursor.cpp.
546
547 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
548 {
549         d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur, false);
550 }
551
552
553 void Undo::recordUndoInset(CursorData const & cur, UndoKind kind,
554                            Inset const * inset)
555 {
556         if (!inset || inset == &cur.inset()) {
557                 DocIterator c = cur;
558                 c.pop_back();
559                 d->recordUndo(kind, c, c.pit(), c.pit(), cur, false);
560         } else if (inset == cur.nextInset())
561                 recordUndo(cur, kind);
562         else
563                 LYXERR0("Inset not found, no undo stack added.");
564 }
565
566
567 void Undo::recordUndo(CursorData const & cur, UndoKind kind, pit_type from)
568 {
569         d->recordUndo(kind, cur, cur.pit(), from, cur, false);
570 }
571
572
573 void Undo::recordUndo(CursorData const & cur, UndoKind kind,
574         pit_type from, pit_type to)
575 {
576         d->recordUndo(kind, cur, from, to, cur, false);
577 }
578
579
580 void Undo::recordUndoFullDocument(CursorData const & cur)
581 {
582         // This one may happen outside of the main undo group, so we
583         // put it in its own subgroup to avoid complaints.
584         beginUndoGroup();
585         d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
586                       0, d->buffer_.paragraphs().size() - 1, cur, true);
587         endUndoGroup();
588 }
589
590
591 } // namespace lyx