]> git.lyx.org Git - lyx.git/blob - src/bufferlist.C
warning on revert
[lyx.git] / src / bufferlist.C
1 /**
2  * \file bufferlist.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  *
8  * Full author contact details are available in file CREDITS
9  */
10
11 #include <config.h>
12
13 #include "bufferlist.h"
14 #include "lyx_main.h"
15 #include "lastfiles.h"
16 #include "debug.h"
17 #include "lyxrc.h"
18 #include "lyxtext.h"
19 #include "lyx_cb.h"
20 #include "bufferview_funcs.h"
21 #include "BufferView.h"
22 #include "gettext.h"
23 #include "frontends/LyXView.h"
24 #include "vc-backend.h"
25 #include "TextCache.h"
26 #include "lyxlex.h"
27
28 #include "frontends/Alert.h"
29
30 #include "support/FileInfo.h"
31 #include "support/filetools.h"
32 #include "support/lyxmanip.h"
33 #include "support/lyxfunctional.h"
34 #include "support/LAssert.h"
35
36 #include <boost/bind.hpp>
37 #include "BoostFormat.h"
38
39 #include <cassert>
40 #include <algorithm>
41 #include <functional>
42
43
44 using std::vector;
45 using std::find;
46 using std::endl;
47 using std::find_if;
48 using std::for_each;
49 using std::mem_fun;
50
51 extern BufferView * current_view;
52
53
54 BufferList::BufferList()
55 {}
56
57
58 bool BufferList::empty() const
59 {
60         return bstore.empty();
61 }
62
63
64 bool BufferList::quitWriteBuffer(Buffer * buf)
65 {
66         string file;
67         if (buf->isUnnamed())
68                 file = OnlyFilename(buf->fileName());
69         else
70                 file = MakeDisplayPath(buf->fileName(), 30);
71
72 #if USE_BOOST_FORMAT
73         boost::format fmt(_("The document %1$s has unsaved changes.\n\nDo you want to save the document?"));
74         fmt % file;
75         string text = fmt.str();
76 #else
77         string text = _("The document ");
78         text += file + _(" has unsaved changes.\n\nDo you want to save the document?");
79 #endif
80         int const ret = Alert::prompt(_("Save changed document?"),
81                 text, 0, _("&Save"), _("&Discard"), _("&Cancel quit"));
82
83         if (ret == 0) {
84                 // FIXME: WriteAs can be asynch !
85                 // but not right now...maybe we should remove that
86
87                 bool succeeded;
88
89                 if (buf->isUnnamed())
90                         succeeded = !WriteAs(current_view, buf);
91                 else
92                         succeeded = !MenuWrite(current_view, buf);
93
94                 if (!succeeded)
95                         return false;
96         } else if (ret == 1) {
97                 // if we crash after this we could
98                 // have no autosave file but I guess
99                 // this is really inprobable (Jug)
100                 if (buf->isUnnamed())
101                         removeAutosaveFile(buf->fileName());
102
103         } else {
104                 return false;
105         }
106
107         return true;
108 }
109
110
111 bool BufferList::quitWriteAll()
112 {
113         BufferStorage::iterator it = bstore.begin();
114         BufferStorage::iterator end = bstore.end();
115         for (; it != end; ++it) {
116                 if ((*it)->isClean())
117                         continue;
118
119                 if (!quitWriteBuffer(*it))
120                         return false;
121         }
122
123         return true;
124 }
125
126
127 void BufferList::release(Buffer * buf)
128 {
129         lyx::Assert(buf);
130         BufferStorage::iterator it = find(bstore.begin(), bstore.end(), buf);
131         if (it != bstore.end()) {
132                 // Make sure that we don't store a LyXText in
133                 // the textcache that points to the buffer
134                 // we just deleted.
135                 Buffer * tmp = (*it);
136                 bstore.erase(it);
137                 textcache.removeAllWithBuffer(tmp);
138                 delete tmp;
139         }
140 }
141
142
143 Buffer * BufferList::newBuffer(string const & s, bool ronly)
144 {
145         Buffer * tmpbuf = new Buffer(s, ronly);
146         tmpbuf->params.useClassDefaults();
147         lyxerr[Debug::INFO] << "Assigning to buffer "
148                             << bstore.size() << endl;
149         bstore.push_back(tmpbuf);
150         return tmpbuf;
151 }
152
153
154 void BufferList::closeAll()
155 {
156         // Since we are closing we can just as well delete all
157         // in the textcache this will also speed the closing/quiting up a bit.
158         textcache.clear();
159
160         while (!bstore.empty()) {
161                 close(bstore.front(), true);
162         }
163 }
164
165
166 bool BufferList::close(Buffer * buf, bool ask)
167 {
168         lyx::Assert(buf);
169
170         if (!ask || buf->isClean() || quitting || buf->paragraphs.empty()) {
171                 release(buf);
172                 return true;
173         }
174
175         string fname;
176         if (buf->isUnnamed())
177                 fname = OnlyFilename(buf->fileName());
178         else
179                 fname = MakeDisplayPath(buf->fileName(), 30);
180 #if USE_BOOST_FORMAT
181         boost::format fmt(_("The document %1$s has unsaved changes.\n\nDo you want to save the document?"));
182         fmt % fname;
183         string text = fmt.str();
184 #else
185         string text = _("The document ");
186         text += fname + _(" has unsaved changes.\n\nDo you want to save the document?");
187 #endif
188         int const ret = Alert::prompt(_("Save changed document?"),
189                 text, 0, _("&Save"), _("&Discard"));
190
191         if (ret == 0) {
192                 if (buf->isUnnamed()) {
193                         if (!WriteAs(current_view, buf))
194                                 return false;
195                 } else if (buf->save()) {
196                         lastfiles->newFile(buf->fileName());
197                 } else {
198                         return false;
199                 }
200         }
201         
202         if (buf->isUnnamed()) {
203                 removeAutosaveFile(buf->fileName());
204         }
205
206         release(buf);
207         return true;
208 }
209
210
211 vector<string> const BufferList::getFileNames() const
212 {
213         vector<string> nvec;
214         std::copy(bstore.begin(), bstore.end(),
215                   lyx::back_inserter_fun(nvec, &Buffer::fileName));
216         return nvec;
217 }
218
219
220 Buffer * BufferList::first()
221 {
222         if (bstore.empty())
223                 return 0;
224         return bstore.front();
225 }
226
227
228 Buffer * BufferList::getBuffer(unsigned int choice)
229 {
230         if (choice >= bstore.size())
231                 return 0;
232         return bstore[choice];
233 }
234
235
236 void BufferList::updateIncludedTeXfiles(string const & mastertmpdir)
237 {
238         BufferStorage::iterator it = bstore.begin();
239         BufferStorage::iterator end = bstore.end();
240         for (; it != end; ++it) {
241                 if (!(*it)->isDepClean(mastertmpdir)) {
242                         string writefile = mastertmpdir;
243                         writefile += '/';
244                         writefile += (*it)->getLatexName();
245                         (*it)->makeLaTeXFile(writefile, mastertmpdir,
246                                              false, true);
247                         (*it)->markDepClean(mastertmpdir);
248                 }
249         }
250 }
251
252
253 void BufferList::emergencyWriteAll()
254 {
255         for_each(bstore.begin(), bstore.end(),
256                  boost::bind(&BufferList::emergencyWrite, this, _1));
257 }
258
259
260 void BufferList::emergencyWrite(Buffer * buf)
261 {
262         // assert(buf) // this is not good since C assert takes an int
263                        // and a pointer is a long (JMarc)
264         assert(buf != 0); // use c assert to avoid a loop
265
266
267         // No need to save if the buffer has not changed.
268         if (buf->isClean())
269                 return;
270
271         string const doc = buf->isUnnamed()
272                 ? OnlyFilename(buf->fileName()) : buf->fileName();
273
274 #if USE_BOOST_FORMAT
275         lyxerr << boost::format(_("LyX: Attempting to save document %1$s"))
276                 % doc
277                << endl;
278 #else
279         lyxerr << _("LyX: Attempting to save document ") << doc << endl;
280 #endif
281         // We try to save three places:
282
283         // 1) Same place as document. Unless it is an unnamed doc.
284         if (!buf->isUnnamed()) {
285                 string s = buf->fileName();
286                 s += ".emergency";
287                 lyxerr << "  " << s << endl;
288                 if (buf->writeFile(s)) {
289                         buf->markClean();
290                         lyxerr << _("  Save seems successful. Phew.") << endl;
291                         return;
292                 } else {
293                         lyxerr << _("  Save failed! Trying...") << endl;
294                 }
295         }
296
297         // 2) In HOME directory.
298         string s = AddName(GetEnvPath("HOME"), buf->fileName());
299         s += ".emergency";
300         lyxerr << ' ' << s << endl;
301         if (buf->writeFile(s)) {
302                 buf->markClean();
303                 lyxerr << _("  Save seems successful. Phew.") << endl;
304                 return;
305         }
306
307         lyxerr << _("  Save failed! Trying...") << endl;
308
309         // 3) In "/tmp" directory.
310         // MakeAbsPath to prepend the current
311         // drive letter on OS/2
312         s = AddName(MakeAbsPath("/tmp/"), buf->fileName());
313         s += ".emergency";
314         lyxerr << ' ' << s << endl;
315         if (buf->writeFile(s)) {
316                 buf->markClean();
317                 lyxerr << _("  Save seems successful. Phew.") << endl;
318                 return;
319         }
320         lyxerr << _("  Save failed! Bummer. Document is lost.") << endl;
321 }
322
323
324
325 Buffer * BufferList::readFile(string const & s, bool ronly)
326 {
327         string ts(s);
328         string e = OnlyPath(s);
329         string a = e;
330         // File information about normal file
331         FileInfo fileInfo2(s);
332
333         if (!fileInfo2.exist()) {
334                 Alert::alert(_("Error!"), _("Cannot open file"),
335                         MakeDisplayPath(s));
336                 return 0;
337         }
338
339         Buffer * b = newBuffer(s, ronly);
340
341         // Check if emergency save file exists and is newer.
342         e += OnlyFilename(s) + ".emergency";
343         FileInfo fileInfoE(e);
344
345         bool use_emergency = false;
346
347         if (fileInfoE.exist() && fileInfo2.exist()) {
348                 if (fileInfoE.getModificationTime()
349                     > fileInfo2.getModificationTime()) {
350                         string const file = MakeDisplayPath(s, 20);
351 #if USE_BOOST_FORMAT
352                         boost::format fmt(_("An emergency save of the document %1$s exists.\n\nRecover emergency save?"));
353                         fmt % file;
354                         string text = fmt.str();
355 #else
356                         string text = _("An emergency save of the document ");
357                         text += file + _(" exists.\n\nRecover emergency save?");
358 #endif
359                         int const ret = Alert::prompt(_("Load emergency save?"),
360                                 text, 0, _("&Recover"), _("&Load original"));
361
362                         if (ret == 0) {
363                                 ts = e;
364                                 // the file is not saved if we load the
365                                 // emergency file.
366                                 b->markDirty();
367                                 use_emergency = true;
368                         } else {
369                                 // Here, we should delete the emergency save
370                                 lyx::unlink(e);
371                         }
372                 }
373         }
374
375         if (!use_emergency) {
376                 // Now check if autosave file is newer.
377                 a += '#';
378                 a += OnlyFilename(s);
379                 a += '#';
380                 FileInfo fileInfoA(a);
381                 if (fileInfoA.exist() && fileInfo2.exist()) {
382                         if (fileInfoA.getModificationTime()
383                             > fileInfo2.getModificationTime()) {
384                                 string const file = MakeDisplayPath(s, 20);
385 #if USE_BOOST_FORMAT
386                                 boost::format fmt(_("The backup of the document %1$s is newer.\n\nLoad the backup instead?"));
387                                 fmt % file;
388                                 string text = fmt.str();
389 #else
390                                 string text = _("The backup of the document ");
391                                 text += file + _(" is newer.\n\nLoad the backup instead?");
392 #endif
393                                 int const ret = Alert::prompt(_("Load backup?"),
394                                         text, 0, _("&Load backup"), _("Load &original"));
395
396                                 if (ret == 0) {
397                                         ts = a;
398                                         // the file is not saved if we load the
399                                         // autosave file.
400                                         b->markDirty();
401                                 } else {
402                                         // Here, we should delete the autosave
403                                         lyx::unlink(a);
404                                 }
405                         }
406                 }
407         }
408         // not sure if this is the correct place to begin LyXLex
409         LyXLex lex(0, 0);
410         lex.setFile(ts);
411         if (b->readFile(lex, ts))
412                 return b;
413         else {
414                 release(b);
415                 return 0;
416         }
417 }
418
419
420 bool BufferList::exists(string const & s) const
421 {
422         return find_if(bstore.begin(), bstore.end(),
423                        lyx::compare_memfun(&Buffer::fileName, s))
424                 != bstore.end();
425 }
426
427
428 bool BufferList::isLoaded(Buffer const * b) const
429 {
430         lyx::Assert(b);
431
432         BufferStorage::const_iterator cit =
433                 find(bstore.begin(), bstore.end(), b);
434         return cit != bstore.end();
435 }
436
437
438 Buffer * BufferList::getBuffer(string const & s)
439 {
440         BufferStorage::iterator it =
441                 find_if(bstore.begin(), bstore.end(),
442                         lyx::compare_memfun(&Buffer::fileName, s));
443         return it != bstore.end() ? (*it) : 0;
444 }
445
446
447 Buffer * BufferList::newFile(string const & name, string tname, bool isNamed)
448 {
449         // get a free buffer
450         Buffer * b = newBuffer(name);
451
452         // use defaults.lyx as a default template if it exists.
453         if (tname.empty()) {
454                 tname = LibFileSearch("templates", "defaults.lyx");
455         }
456         if (!tname.empty()) {
457                 bool templateok = false;
458                 LyXLex lex(0, 0);
459                 lex.setFile(tname);
460                 if (lex.isOK()) {
461                         if (b->readFile(lex, tname)) {
462                                 templateok = true;
463                         }
464                 }
465                 if (!templateok) {
466                         Alert::alert(_("Error!"), _("Unable to open template"),
467                                    MakeDisplayPath(tname));
468                         // no template, start with empty buffer
469                         b->paragraphs.set(new Paragraph);
470                         b->paragraphs.begin()->layout(b->params.getLyXTextClass().defaultLayout());
471                 }
472         } else {  // start with empty buffer
473                 b->paragraphs.set(new Paragraph);
474                 b->paragraphs.begin()->layout(b->params.getLyXTextClass().defaultLayout());
475         }
476
477         if (!isNamed) {
478                 b->setUnnamed();
479                 b->setFileName(name);
480         }
481
482         b->setReadonly(false);
483
484         return b;
485 }
486
487
488 Buffer * BufferList::loadLyXFile(string const & filename, bool tolastfiles)
489 {
490         // get absolute path of file and add ".lyx" to the filename if
491         // necessary
492         string s = FileSearch(string(), filename, "lyx");
493         if (s.empty()) {
494                 s = filename;
495         }
496
497         // file already open?
498         if (exists(s)) {
499                 string const file = MakeDisplayPath(s, 20);
500 #if USE_BOOST_FORMAT
501                 boost::format fmt(_("The document %1$s is already loaded.\n\nDo you want to revert to the saved version?"));
502                 fmt % file;
503                 string text = fmt.str();
504 #else
505                 string text = _("The document ");
506                 text += file + _(" is already loaded.\n\nDo you want to revert to the saved version?");
507 #endif
508                 int const ret = Alert::prompt(_("Revert to saved document?"),
509                         text, 1, _("&Revert"), _("&Switch to document"));
510
511                 if (ret == 0) {
512                         // FIXME: should be LFUN_REVERT
513                         if (!close(getBuffer(s), false)) {
514                                 return 0;
515                         }
516                         // Fall through to new load. (Asger)
517                 } else {
518                         // Here, we pretend that we just loaded the
519                         // open document
520                         return getBuffer(s);
521                 }
522         }
523
524         Buffer * b = 0;
525         bool ro = false;
526         switch (IsFileWriteable(s)) {
527         case 0:
528                 ro = true;
529                 // Fall through
530         case 1:
531                 b = readFile(s, ro);
532                 if (b) {
533                         b->lyxvc.file_found_hook(s);
534                 }
535                 break; //fine- it's r/w
536         case -1: {
537                 string const file = MakeDisplayPath(s, 20);
538                 // Here we probably should run
539                 if (LyXVC::file_not_found_hook(s)) {
540 #if USE_BOOST_FORMAT
541                         boost::format fmt(_("Do you want to retrieve the document %1$s from version control?"));
542                         fmt % file;
543                         string text = fmt.str();
544 #else
545                         string text = _("Do you want to retrieve the document ");
546                         text += file + _(" from version control?");
547 #endif
548                         int const ret = Alert::prompt(_("Retrieve from version control?"),
549                                 text, 0, _("&Retrieve"), _("&Cancel"));
550
551                         if (ret == 0) {
552                                 // How can we know _how_ to do the checkout?
553                                 // With the current VC support it has to be,
554                                 // a RCS file since CVS do not have special ,v files.
555                                 RCS::retrieve(s);
556                                 return loadLyXFile(filename, tolastfiles);
557                         }
558                 }
559
560 #if USE_BOOST_FORMAT
561                 boost::format fmt(_("The document %1$s does not yet exist.\n\nDo you want to create a new document?"));
562                 fmt % file;
563                 string text = fmt.str();
564 #else
565                 string text = _("The document ");
566                 text += file + _(" does not yet exist.\n\nDo you want to create a new document?");
567 #endif
568                 int const ret = Alert::prompt(_("Create new document?"),
569                         text, 0, _("&Create"), _("Cancel"));
570
571                 if (ret == 0)
572                         b = newFile(s, string(), true);
573
574                 break;
575                 }
576         }
577
578         if (b && tolastfiles)
579                 lastfiles->newFile(b->fileName());
580
581         return b;
582 }
583
584
585 void BufferList::setCurrentAuthor(string const & name, string const & email)
586 {
587         BufferStorage::iterator it = bstore.begin();
588         BufferStorage::iterator end = bstore.end();
589         for (; it != end; ++it) {
590                 (*it)->authors().record(0, Author(name, email));
591         }
592 }