1 /**
2 * \file Buffer.cpp
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 * \author Stefan Schimanski
8 *
9 * Full author contact details are available in file CREDITS.
10 */
11
12 #include <config.h>
13
14 #include "Buffer.h"
15
16 #include "Author.h"
17 #include "LayoutFile.h"
18 #include "BiblioInfo.h"
19 #include "BranchList.h"
20 #include "buffer_funcs.h"
21 #include "BufferList.h"
22 #include "BufferParams.h"
23 #include "Bullet.h"
24 #include "Chktex.h"
25 #include "Converter.h"
26 #include "Counters.h"
27 #include "Cursor.h"
28 #include "CutAndPaste.h"
29 #include "DispatchResult.h"
30 #include "DocIterator.h"
31 #include "BufferEncodings.h"
32 #include "ErrorList.h"
33 #include "Exporter.h"
34 #include "Format.h"
35 #include "FuncRequest.h"
36 #include "FuncStatus.h"
37 #include "IndicesList.h"
38 #include "InsetIterator.h"
39 #include "InsetList.h"
40 #include "Language.h"
41 #include "LaTeXFeatures.h"
42 #include "LaTeX.h"
43 #include "Layout.h"
44 #include "Lexer.h"
45 #include "LyXAction.h"
46 #include "LyX.h"
47 #include "LyXRC.h"
48 #include "LyXVC.h"
49 #include "output_docbook.h"
50 #include "output.h"
51 #include "output_latex.h"
52 #include "output_xhtml.h"
53 #include "output_plaintext.h"
54 #include "Paragraph.h"
55 #include "ParagraphParameters.h"
56 #include "ParIterator.h"
57 #include "PDFOptions.h"
58 #include "Session.h"
59 #include "SpellChecker.h"
60 #include "sgml.h"
61 #include "texstream.h"
62 #include "TexRow.h"
63 #include "Text.h"
64 #include "TextClass.h"
65 #include "TocBackend.h"
66 #include "Undo.h"
67 #include "VCBackend.h"
68 #include "version.h"
69 #include "WordLangTuple.h"
70 #include "WordList.h"
71
72 #include "insets/InsetBibtex.h"
73 #include "insets/InsetBranch.h"
74 #include "insets/InsetInclude.h"
75 #include "insets/InsetTabular.h"
76 #include "insets/InsetText.h"
77
78 #include "mathed/InsetMathHull.h"
79 #include "mathed/MacroTable.h"
80 #include "mathed/InsetMathMacroTemplate.h"
81 #include "mathed/MathSupport.h"
82
83 #include "graphics/GraphicsCache.h"
84 #include "graphics/PreviewLoader.h"
85
86 #include "frontends/alert.h"
87 #include "frontends/Delegates.h"
88 #include "frontends/WorkAreaManager.h"
89
90 #include "support/lassert.h"
91 #include "support/convert.h"
92 #include "support/debug.h"
93 #include "support/docstring_list.h"
94 #include "support/ExceptionMessage.h"
95 #include "support/FileMonitor.h"
96 #include "support/FileName.h"
97 #include "support/FileNameList.h"
98 #include "support/filetools.h"
99 #include "support/ForkedCalls.h"
100 #include "support/gettext.h"
101 #include "support/gzstream.h"
102 #include "support/lstrings.h"
103 #include "support/lyxalgo.h"
104 #include "support/mutex.h"
105 #include "support/os.h"
106 #include "support/Package.h"
107 #include "support/PathChanger.h"
108 #include "support/Systemcall.h"
109 #include "support/TempFile.h"
110 #include "support/textutils.h"
111 #include "support/types.h"
112
113 #include "support/bind.h"
114
115 #include <algorithm>
116 #include <fstream>
117 #include <iomanip>
118 #include <map>
119 #include <memory>
120 #include <set>
121 #include <sstream>
122 #include <vector>
123
124 using namespace std;
125 using namespace lyx::support;
126 using namespace lyx::graphics;
127
128 namespace lyx {
129
130 namespace Alert = frontend::Alert;
131 namespace os = support::os;
132
133 namespace {
134
135 int const LYX_FORMAT = LYX_FORMAT_LYX;
136
137 typedef map<string, bool> DepClean;
138
139 // Information about labels and their associated refs
140 struct LabelInfo {
141 /// label string
142 docstring label;
143 /// label inset
144 InsetLabel const * inset;
145 /// associated references cache
146 Buffer::References references;
147 /// whether this label is active (i.e., not deleted)
148 bool active;
149 };
150
151 typedef vector<LabelInfo> LabelCache;
152
153 typedef map<docstring, Buffer::References> RefCache;
154
155 } // namespace
156
157
158 // A storehouse for the cloned buffers.
159 list<CloneList *> cloned_buffers;
160
161
162 class Buffer::Impl
163 {
164 public:
165 Impl(Buffer * owner, FileName const & file, bool readonly, Buffer const * cloned_buffer);
166
~Impl()167 ~Impl()
168 {
169 delete preview_loader_;
170 if (wa_) {
171 wa_->closeAll();
172 delete wa_;
173 }
174 delete inset;
175 }
176
177 /// search for macro in local (buffer) table or in children
178 MacroData const * getBufferMacro(docstring const & name,
179 DocIterator const & pos) const;
180
181 /// Update macro table starting with position of it \param it in some
182 /// text inset.
183 void updateMacros(DocIterator & it, DocIterator & scope);
184 ///
185 void setLabel(ParIterator & it, UpdateType utype) const;
186
187 /** If we have branches that use the file suffix
188 feature, return the file name with suffix appended.
189 */
190 support::FileName exportFileName() const;
191
192 Buffer * owner_;
193
194 BufferParams params;
195 LyXVC lyxvc;
196 FileName temppath;
197 mutable TexRow texrow;
198
199 /// need to regenerate .tex?
200 DepClean dep_clean;
201
202 /// is save needed?
203 mutable bool lyx_clean;
204
205 /// is autosave needed?
206 mutable bool bak_clean;
207
208 /// is this an unnamed file (New...)?
209 bool unnamed;
210
211 /// is this an internal bufffer?
212 bool internal_buffer;
213
214 /// buffer is r/o
215 bool read_only;
216
217 /// name of the file the buffer is associated with.
218 FileName filename;
219
220 /** Set to true only when the file is fully loaded.
221 * Used to prevent the premature generation of previews
222 * and by the citation inset.
223 */
224 bool file_fully_loaded;
225
226 /// original format of loaded file
227 int file_format;
228
229 /// if the file was originally loaded from an older format, do
230 /// we need to back it up still?
231 bool need_format_backup;
232
233 /// Ignore the parent (e.g. when exporting a child standalone)?
234 bool ignore_parent;
235
236 ///
237 mutable TocBackend toc_backend;
238
239 /// macro tables
240 struct ScopeMacro {
ScopeMacrolyx::Buffer::Impl::ScopeMacro241 ScopeMacro() {}
ScopeMacrolyx::Buffer::Impl::ScopeMacro242 ScopeMacro(DocIterator const & s, MacroData const & m)
243 : scope(s), macro(m) {}
244 DocIterator scope;
245 MacroData macro;
246 };
247 typedef map<DocIterator, ScopeMacro> PositionScopeMacroMap;
248 typedef map<docstring, PositionScopeMacroMap> NamePositionScopeMacroMap;
249 /// map from the macro name to the position map,
250 /// which maps the macro definition position to the scope and the MacroData.
251 NamePositionScopeMacroMap macros;
252 /// This seem to change the way Buffer::getMacro() works
253 mutable bool macro_lock;
254
255 /// positions of child buffers in the buffer
256 typedef map<Buffer const * const, DocIterator> BufferPositionMap;
257 struct ScopeBuffer {
ScopeBufferlyx::Buffer::Impl::ScopeBuffer258 ScopeBuffer() : buffer(0) {}
ScopeBufferlyx::Buffer::Impl::ScopeBuffer259 ScopeBuffer(DocIterator const & s, Buffer const * b)
260 : scope(s), buffer(b) {}
261 DocIterator scope;
262 Buffer const * buffer;
263 };
264 typedef map<DocIterator, ScopeBuffer> PositionScopeBufferMap;
265 /// position of children buffers in this buffer
266 BufferPositionMap children_positions;
267 /// map from children inclusion positions to their scope and their buffer
268 PositionScopeBufferMap position_to_children;
269
270 /// Contains the old buffer filePath() while saving-as, or the
271 /// directory where the document was last saved while loading.
272 string old_position;
273
274 /** Keeps track of the path of local layout files.
275 * If possible, it is always relative to the buffer path.
276 * Empty for layouts in system or user directory.
277 */
278 string layout_position;
279
280 /// Container for all sort of Buffer dependant errors.
281 map<string, ErrorList> errorLists;
282
283 /// checksum used to test if the file has been externally modified. Used to
284 /// double check whether the file had been externally modified when saving.
285 unsigned long checksum_;
286
287 ///
288 frontend::WorkAreaManager * wa_;
289 ///
290 frontend::GuiBufferDelegate * gui_;
291
292 ///
293 Undo undo_;
294
295 /// A cache for the bibfiles (including bibfiles of loaded child
296 /// documents), needed for appropriate update of natbib labels.
297 mutable docstring_list bibfiles_cache_;
298
299 // FIXME The caching mechanism could be improved. At present, we have a
300 // cache for each Buffer, that caches all the bibliography info for that
301 // Buffer. A more efficient solution would be to have a global cache per
302 // file, and then to construct the Buffer's bibinfo from that.
303 /// A cache for bibliography info
304 mutable BiblioInfo bibinfo_;
305 /// whether the bibinfo cache is valid
306 mutable bool bibinfo_cache_valid_;
307 /// whether the bibfile cache is valid
308 mutable bool bibfile_cache_valid_;
309 /// Cache of timestamps of .bib files
310 map<FileName, time_t> bibfile_status_;
311 /// Indicates whether the bibinfo has changed since the last time
312 /// we ran updateBuffer(), i.e., whether citation labels may need
313 /// to be updated.
314 mutable bool cite_labels_valid_;
315 /// Do we have a bibliography environment?
316 mutable bool have_bibitems_;
317
318 /// These two hold the file name and format, written to by
319 /// Buffer::preview and read from by LFUN_BUFFER_VIEW_CACHE.
320 FileName preview_file_;
321 string preview_format_;
322 /// If there was an error when previewing, on the next preview we do
323 /// a fresh compile (e.g. in case the user installed a package that
324 /// was missing).
325 bool preview_error_;
326
327 /// Cache the references associated to a label and their positions
328 /// in the buffer.
329 mutable RefCache ref_cache_;
330 /// Cache the label insets and their activity status.
331 mutable LabelCache label_cache_;
332
333 /// our Text that should be wrapped in an InsetText
334 InsetText * inset;
335
336 ///
337 PreviewLoader * preview_loader_;
338
339 /// This is here to force the test to be done whenever parent_buffer
340 /// is accessed.
parent() const341 Buffer const * parent() const
342 {
343 // ignore_parent temporarily "orphans" a buffer
344 // (e.g. if a child is compiled standalone)
345 if (ignore_parent)
346 return 0;
347 // if parent_buffer is not loaded, then it has been unloaded,
348 // which means that parent_buffer is an invalid pointer. So we
349 // set it to null in that case.
350 // however, the BufferList doesn't know about cloned buffers, so
351 // they will always be regarded as unloaded. in that case, we hope
352 // for the best.
353 if (!cloned_buffer_ && !theBufferList().isLoaded(parent_buffer))
354 parent_buffer = 0;
355 return parent_buffer;
356 }
357
358 ///
setParent(Buffer const * pb)359 void setParent(Buffer const * pb)
360 {
361 if (parent_buffer == pb)
362 // nothing to do
363 return;
364 if (!cloned_buffer_ && parent_buffer && pb)
365 LYXERR0("Warning: a buffer should not have two parents!");
366 parent_buffer = pb;
367 if (!cloned_buffer_ && parent_buffer) {
368 parent_buffer->invalidateBibinfoCache();
369 }
370 }
371
372 /// If non zero, this buffer is a clone of existing buffer \p cloned_buffer_
373 /// This one is useful for preview detached in a thread.
374 Buffer const * cloned_buffer_;
375 ///
376 CloneList * clone_list_;
377 /// are we in the process of exporting this buffer?
378 mutable bool doing_export;
379
380 /// compute statistics
381 /// \p from initial position
382 /// \p to points to the end position
383 void updateStatistics(DocIterator & from, DocIterator & to,
384 bool skipNoOutput = true);
385 /// statistics accessor functions
wordCount() const386 int wordCount() const
387 {
388 return word_count_;
389 }
charCount(bool with_blanks) const390 int charCount(bool with_blanks) const
391 {
392 return char_count_
393 + (with_blanks ? blank_count_ : 0);
394 }
395
396 // does the buffer contain tracked changes? (if so, we automatically
397 // display the review toolbar, for instance)
398 mutable bool tracked_changes_present_;
399
400 // Make sure the file monitor monitors the good file.
401 void refreshFileMonitor();
402
403 /// Notify or clear of external modification
404 void fileExternallyModified(bool exists);
405
406 /// has been externally modified? Can be reset by the user.
407 mutable bool externally_modified_;
408
409 private:
410 /// So we can force access via the accessors.
411 mutable Buffer const * parent_buffer;
412
413 int word_count_;
414 int char_count_;
415 int blank_count_;
416
417 FileMonitorPtr file_monitor_;
418 };
419
420
421 /// Creates the per buffer temporary directory
createBufferTmpDir()422 static FileName createBufferTmpDir()
423 {
424 // FIXME This would be the ideal application for a TempDir class (like
425 // TempFile but for directories)
426 string counter;
427 {
428 static int count;
429 static Mutex mutex;
430 Mutex::Locker locker(&mutex);
431 counter = convert<string>(count++);
432 }
433 // We are in our own directory. Why bother to mangle name?
434 // In fact I wrote this code to circumvent a problematic behaviour
435 // (bug?) of EMX mkstemp().
436 FileName tmpfl(package().temp_dir().absFileName() + "/lyx_tmpbuf" +
437 counter);
438
439 if (!tmpfl.createDirectory(0777)) {
440 throw ExceptionMessage(WarningException, _("Disk Error: "), bformat(
441 _("LyX could not create the temporary directory '%1$s' (Disk is full maybe?)"),
442 from_utf8(tmpfl.absFileName())));
443 }
444 return tmpfl;
445 }
446
447
Impl(Buffer * owner,FileName const & file,bool readonly_,Buffer const * cloned_buffer)448 Buffer::Impl::Impl(Buffer * owner, FileName const & file, bool readonly_,
449 Buffer const * cloned_buffer)
450 : owner_(owner), lyx_clean(true), bak_clean(true), unnamed(false),
451 internal_buffer(false), read_only(readonly_), filename(file),
452 file_fully_loaded(false), file_format(LYX_FORMAT), need_format_backup(false),
453 ignore_parent(false), toc_backend(owner), macro_lock(false),
454 checksum_(0), wa_(0), gui_(0), undo_(*owner), bibinfo_cache_valid_(false),
455 bibfile_cache_valid_(false), cite_labels_valid_(false), have_bibitems_(false),
456 preview_error_(false), inset(0), preview_loader_(0), cloned_buffer_(cloned_buffer),
457 clone_list_(0), doing_export(false),
458 tracked_changes_present_(0), externally_modified_(false), parent_buffer(0),
459 word_count_(0), char_count_(0), blank_count_(0)
460 {
461 refreshFileMonitor();
462 if (!cloned_buffer_) {
463 temppath = createBufferTmpDir();
464 lyxvc.setBuffer(owner_);
465 if (use_gui)
466 wa_ = new frontend::WorkAreaManager;
467 return;
468 }
469 temppath = cloned_buffer_->d->temppath;
470 file_fully_loaded = true;
471 params = cloned_buffer_->d->params;
472 bibfiles_cache_ = cloned_buffer_->d->bibfiles_cache_;
473 bibinfo_ = cloned_buffer_->d->bibinfo_;
474 bibinfo_cache_valid_ = cloned_buffer_->d->bibinfo_cache_valid_;
475 bibfile_cache_valid_ = cloned_buffer_->d->bibfile_cache_valid_;
476 bibfile_status_ = cloned_buffer_->d->bibfile_status_;
477 cite_labels_valid_ = cloned_buffer_->d->cite_labels_valid_;
478 have_bibitems_ = cloned_buffer_->d->have_bibitems_;
479 unnamed = cloned_buffer_->d->unnamed;
480 internal_buffer = cloned_buffer_->d->internal_buffer;
481 layout_position = cloned_buffer_->d->layout_position;
482 preview_file_ = cloned_buffer_->d->preview_file_;
483 preview_format_ = cloned_buffer_->d->preview_format_;
484 preview_error_ = cloned_buffer_->d->preview_error_;
485 tracked_changes_present_ = cloned_buffer_->d->tracked_changes_present_;
486 }
487
488
Buffer(string const & file,bool readonly,Buffer const * cloned_buffer)489 Buffer::Buffer(string const & file, bool readonly, Buffer const * cloned_buffer)
490 : d(new Impl(this, FileName(file), readonly, cloned_buffer))
491 {
492 LYXERR(Debug::INFO, "Buffer::Buffer()");
493 if (cloned_buffer) {
494 d->inset = new InsetText(*cloned_buffer->d->inset);
495 d->inset->setBuffer(*this);
496 // FIXME: optimize this loop somewhat, maybe by creating a new
497 // general recursive Inset::setId().
498 DocIterator it = doc_iterator_begin(this);
499 DocIterator cloned_it = doc_iterator_begin(cloned_buffer);
500 for (; !it.atEnd(); it.forwardPar(), cloned_it.forwardPar())
501 it.paragraph().setId(cloned_it.paragraph().id());
502 } else
503 d->inset = new InsetText(this);
504 d->inset->getText(0)->setMacrocontextPosition(par_iterator_begin());
505 }
506
507
~Buffer()508 Buffer::~Buffer()
509 {
510 LYXERR(Debug::INFO, "Buffer::~Buffer()");
511 // here the buffer should take care that it is
512 // saved properly, before it goes into the void.
513
514 // GuiView already destroyed
515 d->gui_ = 0;
516
517 if (isInternal()) {
518 // No need to do additional cleanups for internal buffer.
519 delete d;
520 return;
521 }
522
523 if (isClone()) {
524 // this is in case of recursive includes: we won't try to delete
525 // ourselves as a child.
526 d->clone_list_->erase(this);
527 // loop over children
528 Impl::BufferPositionMap::iterator it = d->children_positions.begin();
529 Impl::BufferPositionMap::iterator end = d->children_positions.end();
530 for (; it != end; ++it) {
531 Buffer * child = const_cast<Buffer *>(it->first);
532 if (d->clone_list_->erase(child))
533 delete child;
534 }
535 // if we're the master buffer, then we should get rid of the list
536 // of clones
537 if (!parent()) {
538 // If this is not empty, we have leaked something. Worse, one of the
539 // children still has a reference to this list. But we will try to
540 // continue, rather than shut down.
541 LATTEST(d->clone_list_->empty());
542 list<CloneList *>::iterator it =
543 find(cloned_buffers.begin(), cloned_buffers.end(), d->clone_list_);
544 if (it == cloned_buffers.end()) {
545 // We will leak in this case, but it is safe to continue.
546 LATTEST(false);
547 } else
548 cloned_buffers.erase(it);
549 delete d->clone_list_;
550 }
551 // FIXME Do we really need to do this right before we delete d?
552 // clear references to children in macro tables
553 d->children_positions.clear();
554 d->position_to_children.clear();
555 } else {
556 // loop over children
557 Impl::BufferPositionMap::iterator it = d->children_positions.begin();
558 Impl::BufferPositionMap::iterator end = d->children_positions.end();
559 for (; it != end; ++it) {
560 Buffer * child = const_cast<Buffer *>(it->first);
561 if (theBufferList().isLoaded(child)) {
562 if (theBufferList().isOthersChild(this, child))
563 child->setParent(0);
564 else
565 theBufferList().release(child);
566 }
567 }
568
569 if (!isClean()) {
570 docstring msg = _("LyX attempted to close a document that had unsaved changes!\n");
571 try {
572 msg += emergencyWrite();
573 } catch (...) {
574 msg += " " + _("Save failed! Document is lost.");
575 }
576 Alert::warning(_("Attempting to close changed document!"), msg);
577 }
578
579 // FIXME Do we really need to do this right before we delete d?
580 // clear references to children in macro tables
581 d->children_positions.clear();
582 d->position_to_children.clear();
583
584 if (!d->temppath.destroyDirectory()) {
585 LYXERR0(bformat(_("Could not remove the temporary directory %1$s"),
586 from_utf8(d->temppath.absFileName())));
587 }
588 removePreviews();
589 }
590
591 delete d;
592 }
593
594
cloneWithChildren() const595 Buffer * Buffer::cloneWithChildren() const
596 {
597 BufferMap bufmap;
598 cloned_buffers.push_back(new CloneList);
599 CloneList * clones = cloned_buffers.back();
600
601 cloneWithChildren(bufmap, clones);
602
603 // make sure we got cloned
604 BufferMap::const_iterator bit = bufmap.find(this);
605 LASSERT(bit != bufmap.end(), return 0);
606 Buffer * cloned_buffer = bit->second;
607
608 return cloned_buffer;
609 }
610
611
cloneWithChildren(BufferMap & bufmap,CloneList * clones) const612 void Buffer::cloneWithChildren(BufferMap & bufmap, CloneList * clones) const
613 {
614 // have we already been cloned?
615 if (bufmap.find(this) != bufmap.end())
616 return;
617
618 Buffer * buffer_clone = new Buffer(fileName().absFileName(), false, this);
619
620 // The clone needs its own DocumentClass, since running updateBuffer() will
621 // modify it, and we would otherwise be sharing it with the original Buffer.
622 buffer_clone->params().makeDocumentClass(true);
623 ErrorList el;
624 cap::switchBetweenClasses(
625 params().documentClassPtr(), buffer_clone->params().documentClassPtr(),
626 static_cast<InsetText &>(buffer_clone->inset()), el);
627
628 bufmap[this] = buffer_clone;
629 clones->insert(buffer_clone);
630 buffer_clone->d->clone_list_ = clones;
631 buffer_clone->d->macro_lock = true;
632 buffer_clone->d->children_positions.clear();
633
634 // FIXME (Abdel 09/01/2010): this is too complicated. The whole children_positions and
635 // math macro caches need to be rethought and simplified.
636 // I am not sure wether we should handle Buffer cloning here or in BufferList.
637 // Right now BufferList knows nothing about buffer clones.
638 Impl::PositionScopeBufferMap::iterator it = d->position_to_children.begin();
639 Impl::PositionScopeBufferMap::iterator end = d->position_to_children.end();
640 for (; it != end; ++it) {
641 DocIterator dit = it->first.clone(buffer_clone);
642 dit.setBuffer(buffer_clone);
643 Buffer * child = const_cast<Buffer *>(it->second.buffer);
644
645 child->cloneWithChildren(bufmap, clones);
646 BufferMap::iterator const bit = bufmap.find(child);
647 LASSERT(bit != bufmap.end(), continue);
648 Buffer * child_clone = bit->second;
649
650 Inset * inset = dit.nextInset();
651 LASSERT(inset && inset->lyxCode() == INCLUDE_CODE, continue);
652 InsetInclude * inset_inc = static_cast<InsetInclude *>(inset);
653 inset_inc->setChildBuffer(child_clone);
654 child_clone->d->setParent(buffer_clone);
655 // FIXME Do we need to do this now, or can we wait until we run updateMacros()?
656 buffer_clone->setChild(dit, child_clone);
657 }
658 buffer_clone->d->macro_lock = false;
659 return;
660 }
661
662
cloneBufferOnly() const663 Buffer * Buffer::cloneBufferOnly() const {
664 cloned_buffers.push_back(new CloneList);
665 CloneList * clones = cloned_buffers.back();
666 Buffer * buffer_clone = new Buffer(fileName().absFileName(), false, this);
667
668 // The clone needs its own DocumentClass, since running updateBuffer() will
669 // modify it, and we would otherwise be sharing it with the original Buffer.
670 buffer_clone->params().makeDocumentClass(true);
671 ErrorList el;
672 cap::switchBetweenClasses(
673 params().documentClassPtr(), buffer_clone->params().documentClassPtr(),
674 static_cast<InsetText &>(buffer_clone->inset()), el);
675
676 clones->insert(buffer_clone);
677 buffer_clone->d->clone_list_ = clones;
678
679 // we won't be cloning the children
680 buffer_clone->d->children_positions.clear();
681 return buffer_clone;
682 }
683
684
isClone() const685 bool Buffer::isClone() const
686 {
687 return d->cloned_buffer_;
688 }
689
690
changed(bool update_metrics) const691 void Buffer::changed(bool update_metrics) const
692 {
693 if (d->wa_)
694 d->wa_->redrawAll(update_metrics);
695 }
696
697
workAreaManager() const698 frontend::WorkAreaManager & Buffer::workAreaManager() const
699 {
700 LBUFERR(d->wa_);
701 return *d->wa_;
702 }
703
704
text() const705 Text & Buffer::text() const
706 {
707 return d->inset->text();
708 }
709
710
inset() const711 Inset & Buffer::inset() const
712 {
713 return *d->inset;
714 }
715
716
params()717 BufferParams & Buffer::params()
718 {
719 return d->params;
720 }
721
722
params() const723 BufferParams const & Buffer::params() const
724 {
725 return d->params;
726 }
727
728
masterParams() const729 BufferParams const & Buffer::masterParams() const
730 {
731 if (masterBuffer() == this)
732 return params();
733
734 BufferParams & mparams = const_cast<Buffer *>(masterBuffer())->params();
735 // Copy child authors to the params. We need those pointers.
736 AuthorList const & child_authors = params().authors();
737 AuthorList::Authors::const_iterator it = child_authors.begin();
738 for (; it != child_authors.end(); ++it)
739 mparams.authors().record(*it);
740 return mparams;
741 }
742
743
fontScalingFactor() const744 double Buffer::fontScalingFactor() const
745 {
746 return isExporting() ? 75.0 * params().html_math_img_scale
747 : 0.01 * lyxrc.dpi * lyxrc.currentZoom * lyxrc.preview_scale_factor * params().display_pixel_ratio;
748 }
749
750
paragraphs()751 ParagraphList & Buffer::paragraphs()
752 {
753 return text().paragraphs();
754 }
755
756
paragraphs() const757 ParagraphList const & Buffer::paragraphs() const
758 {
759 return text().paragraphs();
760 }
761
762
lyxvc()763 LyXVC & Buffer::lyxvc()
764 {
765 return d->lyxvc;
766 }
767
768
lyxvc() const769 LyXVC const & Buffer::lyxvc() const
770 {
771 return d->lyxvc;
772 }
773
774
temppath() const775 string const Buffer::temppath() const
776 {
777 return d->temppath.absFileName();
778 }
779
780
texrow()781 TexRow & Buffer::texrow()
782 {
783 return d->texrow;
784 }
785
786
texrow() const787 TexRow const & Buffer::texrow() const
788 {
789 return d->texrow;
790 }
791
792
tocBackend() const793 TocBackend & Buffer::tocBackend() const
794 {
795 return d->toc_backend;
796 }
797
798
undo()799 Undo & Buffer::undo()
800 {
801 return d->undo_;
802 }
803
804
setChild(DocIterator const & dit,Buffer * child)805 void Buffer::setChild(DocIterator const & dit, Buffer * child)
806 {
807 d->children_positions[child] = dit;
808 }
809
810
latexName(bool const no_path) const811 string Buffer::latexName(bool const no_path) const
812 {
813 FileName latex_name =
814 makeLatexName(d->exportFileName());
815 return no_path ? latex_name.onlyFileName()
816 : latex_name.absFileName();
817 }
818
819
exportFileName() const820 FileName Buffer::Impl::exportFileName() const
821 {
822 docstring const branch_suffix =
823 params.branchlist().getFileNameSuffix();
824 if (branch_suffix.empty())
825 return filename;
826
827 string const name = addExtension(filename.onlyFileNameWithoutExt()
828 + to_utf8(branch_suffix), filename.extension());
829 FileName res(filename.onlyPath().absFileName() + "/" + name);
830
831 return res;
832 }
833
834
logName(LogType * type) const835 string Buffer::logName(LogType * type) const
836 {
837 string const filename = latexName(false);
838
839 if (filename.empty()) {
840 if (type)
841 *type = latexlog;
842 return string();
843 }
844
845 string const path = temppath();
846
847 FileName const fname(addName(temppath(),
848 onlyFileName(changeExtension(filename,
849 ".log"))));
850
851 // FIXME: how do we know this is the name of the build log?
852 FileName const bname(
853 addName(path, onlyFileName(
854 changeExtension(filename,
855 theFormats().extension(params().bufferFormat()) + ".out"))));
856
857 // Also consider the master buffer log file
858 FileName masterfname = fname;
859 LogType mtype = latexlog;
860 if (masterBuffer() != this) {
861 string const mlogfile = masterBuffer()->logName(&mtype);
862 masterfname = FileName(mlogfile);
863 }
864
865 // If no Latex log or Build log is newer, show Build log
866 if (bname.exists() &&
867 ((!fname.exists() && !masterfname.exists())
868 || (fname.lastModified() < bname.lastModified()
869 && masterfname.lastModified() < bname.lastModified()))) {
870 LYXERR(Debug::FILES, "Log name calculated as: " << bname);
871 if (type)
872 *type = buildlog;
873 return bname.absFileName();
874 // If we have a newer master file log or only a master log, show this
875 } else if (fname != masterfname
876 && (!fname.exists() && (masterfname.exists()
877 || fname.lastModified() < masterfname.lastModified()))) {
878 LYXERR(Debug::FILES, "Log name calculated as: " << masterfname);
879 if (type)
880 *type = mtype;
881 return masterfname.absFileName();
882 }
883 LYXERR(Debug::FILES, "Log name calculated as: " << fname);
884 if (type)
885 *type = latexlog;
886 return fname.absFileName();
887 }
888
889
setReadonly(bool const flag)890 void Buffer::setReadonly(bool const flag)
891 {
892 if (d->read_only != flag) {
893 d->read_only = flag;
894 changed(false);
895 }
896 }
897
898
setFileName(FileName const & fname)899 void Buffer::setFileName(FileName const & fname)
900 {
901 bool const changed = fname != d->filename;
902 d->filename = fname;
903 d->refreshFileMonitor();
904 if (changed)
905 lyxvc().file_found_hook(fname);
906 setReadonly(d->filename.isReadOnly());
907 saveCheckSum();
908 updateTitles();
909 }
910
911
readHeader(Lexer & lex)912 int Buffer::readHeader(Lexer & lex)
913 {
914 int unknown_tokens = 0;
915 int line = -1;
916 int begin_header_line = -1;
917
918 // Initialize parameters that may be/go lacking in header:
919 params().branchlist().clear();
920 params().preamble.erase();
921 params().options.erase();
922 params().master.erase();
923 params().float_placement.erase();
924 params().paperwidth.erase();
925 params().paperheight.erase();
926 params().leftmargin.erase();
927 params().rightmargin.erase();
928 params().topmargin.erase();
929 params().bottommargin.erase();
930 params().headheight.erase();
931 params().headsep.erase();
932 params().footskip.erase();
933 params().columnsep.erase();
934 params().fonts_cjk.erase();
935 params().listings_params.clear();
936 params().clearLayoutModules();
937 params().clearRemovedModules();
938 params().clearIncludedChildren();
939 params().pdfoptions().clear();
940 params().indiceslist().clear();
941 params().backgroundcolor = lyx::rgbFromHexName("#ffffff");
942 params().isbackgroundcolor = false;
943 params().fontcolor = RGBColor(0, 0, 0);
944 params().isfontcolor = false;
945 params().notefontcolor = RGBColor(0xCC, 0xCC, 0xCC);
946 params().boxbgcolor = RGBColor(0xFF, 0, 0);
947 params().html_latex_start.clear();
948 params().html_latex_end.clear();
949 params().html_math_img_scale = 1.0;
950 params().output_sync_macro.erase();
951 params().setLocalLayout(docstring(), false);
952 params().setLocalLayout(docstring(), true);
953 params().biblio_opts.erase();
954 params().biblatex_bibstyle.erase();
955 params().biblatex_citestyle.erase();
956 params().multibib.erase();
957
958 for (int i = 0; i < 4; ++i) {
959 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
960 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
961 }
962
963 ErrorList & errorList = d->errorLists["Parse"];
964
965 while (lex.isOK()) {
966 string token;
967 lex >> token;
968
969 if (token.empty())
970 continue;
971
972 if (token == "\\end_header")
973 break;
974
975 ++line;
976 if (token == "\\begin_header") {
977 begin_header_line = line;
978 continue;
979 }
980
981 LYXERR(Debug::PARSER, "Handling document header token: `"
982 << token << '\'');
983
984 string const result =
985 params().readToken(lex, token, d->filename.onlyPath());
986 if (!result.empty()) {
987 if (token == "\\textclass") {
988 d->layout_position = result;
989 } else {
990 ++unknown_tokens;
991 docstring const s = bformat(_("Unknown token: "
992 "%1$s %2$s\n"),
993 from_utf8(token),
994 lex.getDocString());
995 errorList.push_back(ErrorItem(_("Document header error"), s));
996 }
997 }
998 }
999 if (begin_header_line) {
1000 docstring const s = _("\\begin_header is missing");
1001 errorList.push_back(ErrorItem(_("Document header error"), s));
1002 }
1003
1004 params().shell_escape = theSession().shellescapeFiles().find(absFileName());
1005
1006 params().makeDocumentClass();
1007
1008 return unknown_tokens;
1009 }
1010
1011
1012 // Uwe C. Schroeder
1013 // changed to be public and have one parameter
1014 // Returns true if "\end_document" is not read (Asger)
readDocument(Lexer & lex)1015 bool Buffer::readDocument(Lexer & lex)
1016 {
1017 ErrorList & errorList = d->errorLists["Parse"];
1018 errorList.clear();
1019
1020 // remove dummy empty par
1021 paragraphs().clear();
1022
1023 if (!lex.checkFor("\\begin_document")) {
1024 docstring const s = _("\\begin_document is missing");
1025 errorList.push_back(ErrorItem(_("Document header error"), s));
1026 }
1027
1028 readHeader(lex);
1029
1030 if (params().output_changes) {
1031 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1032 bool xcolorulem = LaTeXFeatures::isAvailable("ulem") &&
1033 LaTeXFeatures::isAvailable("xcolor");
1034
1035 if (!dvipost && !xcolorulem) {
1036 Alert::warning(_("Changes not shown in LaTeX output"),
1037 _("Changes will not be highlighted in LaTeX output, "
1038 "because neither dvipost nor xcolor/ulem are installed.\n"
1039 "Please install these packages or redefine "
1040 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
1041 } else if (!xcolorulem) {
1042 Alert::warning(_("Changes not shown in LaTeX output"),
1043 _("Changes will not be highlighted in LaTeX output "
1044 "when using pdflatex, because xcolor and ulem are not installed.\n"
1045 "Please install both packages or redefine "
1046 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
1047 }
1048 }
1049
1050 if (!parent() && !params().master.empty()) {
1051 FileName const master_file = makeAbsPath(params().master,
1052 onlyPath(absFileName()));
1053 if (isLyXFileName(master_file.absFileName())) {
1054 Buffer * master =
1055 checkAndLoadLyXFile(master_file, true);
1056 if (master) {
1057 // necessary e.g. after a reload
1058 // to re-register the child (bug 5873)
1059 // FIXME: clean up updateMacros (here, only
1060 // child registering is needed).
1061 master->updateMacros();
1062 // set master as master buffer, but only
1063 // if we are a real child
1064 if (master->isChild(this))
1065 setParent(master);
1066 // if the master is not fully loaded
1067 // it is probably just loading this
1068 // child. No warning needed then.
1069 else if (master->isFullyLoaded())
1070 LYXERR0("The master '"
1071 << params().master
1072 << "' assigned to this document ("
1073 << absFileName()
1074 << ") does not include "
1075 "this document. Ignoring the master assignment.");
1076 // If the master has just been created, un-hide it (#11162)
1077 if (!master->fileName().exists())
1078 lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
1079 master->absFileName()));
1080 }
1081 }
1082 }
1083
1084 // assure we have a default index
1085 params().indiceslist().addDefault(B_("Index"));
1086
1087 // read main text
1088 if (FileName::isAbsolute(params().origin))
1089 d->old_position = params().origin;
1090 else
1091 d->old_position = filePath();
1092 bool const res = text().read(lex, errorList, d->inset);
1093 d->old_position.clear();
1094
1095 // inform parent buffer about local macros
1096 if (parent()) {
1097 Buffer const * pbuf = parent();
1098 UserMacroSet::const_iterator cit = usermacros.begin();
1099 UserMacroSet::const_iterator end = usermacros.end();
1100 for (; cit != end; ++cit)
1101 pbuf->usermacros.insert(*cit);
1102 }
1103 usermacros.clear();
1104 updateMacros();
1105 updateMacroInstances(InternalUpdate);
1106 return res;
1107 }
1108
1109
importString(string const & format,docstring const & contents,ErrorList & errorList)1110 bool Buffer::importString(string const & format, docstring const & contents, ErrorList & errorList)
1111 {
1112 Format const * fmt = theFormats().getFormat(format);
1113 if (!fmt)
1114 return false;
1115 // It is important to use the correct extension here, since some
1116 // converters create a wrong output file otherwise (e.g. html2latex)
1117 FileName const name = tempFileName("Buffer_importStringXXXXXX." + fmt->extension());
1118 ofdocstream os(name.toFilesystemEncoding().c_str());
1119 // Do not convert os implicitly to bool, since that is forbidden in C++11.
1120 bool const success = !(os << contents).fail();
1121 os.close();
1122
1123 bool converted = false;
1124 if (success) {
1125 params().compressed = false;
1126
1127 // remove dummy empty par
1128 paragraphs().clear();
1129
1130 converted = importFile(format, name, errorList);
1131 }
1132
1133 removeTempFile(name);
1134 return converted;
1135 }
1136
1137
importFile(string const & format,FileName const & name,ErrorList & errorList)1138 bool Buffer::importFile(string const & format, FileName const & name, ErrorList & errorList)
1139 {
1140 if (!theConverters().isReachable(format, "lyx"))
1141 return false;
1142
1143 FileName const lyx = tempFileName("Buffer_importFileXXXXXX.lyx");
1144 if (theConverters().convert(0, name, lyx, name, format, "lyx", errorList)) {
1145 bool const success = readFile(lyx) == ReadSuccess;
1146 removeTempFile(lyx);
1147 return success;
1148 }
1149
1150 return false;
1151 }
1152
1153
readString(string const & s)1154 bool Buffer::readString(string const & s)
1155 {
1156 params().compressed = false;
1157
1158 Lexer lex;
1159 istringstream is(s);
1160 lex.setStream(is);
1161 TempFile tempfile("Buffer_readStringXXXXXX.lyx");
1162 FileName const fn = tempfile.name();
1163
1164 int file_format;
1165 bool success = parseLyXFormat(lex, fn, file_format) == ReadSuccess;
1166
1167 if (success && file_format != LYX_FORMAT) {
1168 // We need to call lyx2lyx, so write the input to a file
1169 ofstream os(fn.toFilesystemEncoding().c_str());
1170 os << s;
1171 os.close();
1172 // lyxvc in readFile
1173 if (readFile(fn) != ReadSuccess)
1174 success = false;
1175 }
1176 else if (success)
1177 if (readDocument(lex))
1178 success = false;
1179 return success;
1180 }
1181
1182
readFile(FileName const & fn)1183 Buffer::ReadStatus Buffer::readFile(FileName const & fn)
1184 {
1185 FileName fname(fn);
1186 Lexer lex;
1187 if (!lex.setFile(fname)) {
1188 Alert::error(_("File Not Found"),
1189 bformat(_("Unable to open file `%1$s'."),
1190 from_utf8(fn.absFileName())));
1191 return ReadFileNotFound;
1192 }
1193
1194 int file_format;
1195 ReadStatus const ret_plf = parseLyXFormat(lex, fn, file_format);
1196 if (ret_plf != ReadSuccess)
1197 return ret_plf;
1198
1199 if (file_format != LYX_FORMAT) {
1200 FileName tmpFile;
1201 ReadStatus ret_clf = convertLyXFormat(fn, tmpFile, file_format);
1202 if (ret_clf != ReadSuccess)
1203 return ret_clf;
1204 ret_clf = readFile(tmpFile);
1205 if (ret_clf == ReadSuccess) {
1206 d->file_format = file_format;
1207 d->need_format_backup = true;
1208 }
1209 return ret_clf;
1210 }
1211
1212 // FIXME: InsetInfo needs to know whether the file is under VCS
1213 // during the parse process, so this has to be done before.
1214 lyxvc().file_found_hook(d->filename);
1215
1216 if (readDocument(lex)) {
1217 Alert::error(_("Document format failure"),
1218 bformat(_("%1$s ended unexpectedly, which means"
1219 " that it is probably corrupted."),
1220 from_utf8(fn.absFileName())));
1221 return ReadDocumentFailure;
1222 }
1223
1224 d->file_fully_loaded = true;
1225 d->read_only = !d->filename.isWritable();
1226 params().compressed = theFormats().isZippedFile(d->filename);
1227 saveCheckSum();
1228 return ReadSuccess;
1229 }
1230
1231
isFullyLoaded() const1232 bool Buffer::isFullyLoaded() const
1233 {
1234 return d->file_fully_loaded;
1235 }
1236
1237
setFullyLoaded(bool value)1238 void Buffer::setFullyLoaded(bool value)
1239 {
1240 d->file_fully_loaded = value;
1241 }
1242
1243
lastPreviewError() const1244 bool Buffer::lastPreviewError() const
1245 {
1246 return d->preview_error_;
1247 }
1248
1249
loader() const1250 PreviewLoader * Buffer::loader() const
1251 {
1252 if (!isExporting() && lyxrc.preview == LyXRC::PREVIEW_OFF)
1253 return 0;
1254 if (!d->preview_loader_)
1255 d->preview_loader_ = new PreviewLoader(*this);
1256 return d->preview_loader_;
1257 }
1258
1259
removePreviews() const1260 void Buffer::removePreviews() const
1261 {
1262 delete d->preview_loader_;
1263 d->preview_loader_ = 0;
1264 }
1265
1266
updatePreviews() const1267 void Buffer::updatePreviews() const
1268 {
1269 PreviewLoader * ploader = loader();
1270 if (!ploader)
1271 return;
1272
1273 InsetIterator it = inset_iterator_begin(*d->inset);
1274 InsetIterator const end = inset_iterator_end(*d->inset);
1275 for (; it != end; ++it)
1276 it->addPreview(it, *ploader);
1277
1278 ploader->startLoading();
1279 }
1280
1281
parseLyXFormat(Lexer & lex,FileName const & fn,int & file_format) const1282 Buffer::ReadStatus Buffer::parseLyXFormat(Lexer & lex,
1283 FileName const & fn, int & file_format) const
1284 {
1285 if(!lex.checkFor("\\lyxformat")) {
1286 Alert::error(_("Document format failure"),
1287 bformat(_("%1$s is not a readable LyX document."),
1288 from_utf8(fn.absFileName())));
1289 return ReadNoLyXFormat;
1290 }
1291
1292 string tmp_format;
1293 lex >> tmp_format;
1294
1295 // LyX formats 217 and earlier were written as 2.17. This corresponds
1296 // to files from LyX versions < 1.1.6.3. We just remove the dot in
1297 // these cases. See also: www.lyx.org/trac/changeset/1313.
1298 size_t dot = tmp_format.find_first_of(".,");
1299 if (dot != string::npos)
1300 tmp_format.erase(dot, 1);
1301
1302 file_format = convert<int>(tmp_format);
1303 return ReadSuccess;
1304 }
1305
1306
convertLyXFormat(FileName const & fn,FileName & tmpfile,int from_format)1307 Buffer::ReadStatus Buffer::convertLyXFormat(FileName const & fn,
1308 FileName & tmpfile, int from_format)
1309 {
1310 TempFile tempfile("Buffer_convertLyXFormatXXXXXX.lyx");
1311 tempfile.setAutoRemove(false);
1312 tmpfile = tempfile.name();
1313 if(tmpfile.empty()) {
1314 Alert::error(_("Conversion failed"),
1315 bformat(_("%1$s is from a different"
1316 " version of LyX, but a temporary"
1317 " file for converting it could"
1318 " not be created."),
1319 from_utf8(fn.absFileName())));
1320 return LyX2LyXNoTempFile;
1321 }
1322
1323 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
1324 if (lyx2lyx.empty()) {
1325 Alert::error(_("Conversion script not found"),
1326 bformat(_("%1$s is from a different"
1327 " version of LyX, but the"
1328 " conversion script lyx2lyx"
1329 " could not be found."),
1330 from_utf8(fn.absFileName())));
1331 return LyX2LyXNotFound;
1332 }
1333
1334 // Run lyx2lyx:
1335 // $python$ "$lyx2lyx$" -t $LYX_FORMAT$ -o "$tempfile$" "$filetoread$"
1336 ostringstream command;
1337 command << os::python()
1338 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
1339 << " -t " << convert<string>(LYX_FORMAT)
1340 << " -o " << quoteName(tmpfile.toSafeFilesystemEncoding())
1341 << ' ' << quoteName(fn.toSafeFilesystemEncoding());
1342 string const command_str = command.str();
1343
1344 LYXERR(Debug::INFO, "Running '" << command_str << '\'');
1345
1346 cmd_ret const ret = runCommand(command_str);
1347 if (ret.first != 0) {
1348 if (from_format < LYX_FORMAT) {
1349 Alert::error(_("Conversion script failed"),
1350 bformat(_("%1$s is from an older version"
1351 " of LyX and the lyx2lyx script"
1352 " failed to convert it."),
1353 from_utf8(fn.absFileName())));
1354 return LyX2LyXOlderFormat;
1355 } else {
1356 Alert::error(_("Conversion script failed"),
1357 bformat(_("%1$s is from a newer version"
1358 " of LyX and the lyx2lyx script"
1359 " failed to convert it."),
1360 from_utf8(fn.absFileName())));
1361 return LyX2LyXNewerFormat;
1362 }
1363 }
1364 return ReadSuccess;
1365 }
1366
1367
getBackupName() const1368 FileName Buffer::getBackupName() const {
1369 FileName const & fn = fileName();
1370 string const fname = fn.onlyFileNameWithoutExt();
1371 string const fext = fn.extension() + "~";
1372 string const fpath = lyxrc.backupdir_path.empty() ?
1373 fn.onlyPath().absFileName() :
1374 lyxrc.backupdir_path;
1375 string const fform = convert<string>(d->file_format);
1376 string const backname = fname + "-lyxformat-" + fform;
1377 FileName backup(addName(fpath, addExtension(backname, fext)));
1378
1379 // limit recursion, just in case
1380 int v = 1;
1381 unsigned long orig_checksum = 0;
1382 while (backup.exists() && v < 100) {
1383 if (orig_checksum == 0)
1384 orig_checksum = fn.checksum();
1385 unsigned long new_checksum = backup.checksum();
1386 if (orig_checksum == new_checksum) {
1387 LYXERR(Debug::FILES, "Not backing up " << fn <<
1388 "since " << backup << "has the same checksum.");
1389 // a bit of a hack, but we have to check this anyway
1390 // below, and setting this is simpler than introducing
1391 // a special boolean for this purpose.
1392 v = 1000;
1393 break;
1394 }
1395 string const newbackname = backname + "-" + convert<string>(v);
1396 backup.set(addName(fpath, addExtension(newbackname, fext)));
1397 v++;
1398 }
1399 return v < 100 ? backup : FileName();
1400 }
1401
1402
1403 // Should probably be moved to somewhere else: BufferView? GuiView?
save() const1404 bool Buffer::save() const
1405 {
1406 docstring const file = makeDisplayPath(absFileName(), 20);
1407 d->filename.refresh();
1408
1409 // check the read-only status before moving the file as a backup
1410 if (d->filename.exists()) {
1411 bool const read_only = !d->filename.isWritable();
1412 if (read_only) {
1413 Alert::warning(_("File is read-only"),
1414 bformat(_("The file %1$s cannot be written because it "
1415 "is marked as read-only."), file));
1416 return false;
1417 }
1418 }
1419
1420 // ask if the disk file has been externally modified (use checksum method)
1421 if (fileName().exists() && isChecksumModified()) {
1422 docstring text =
1423 bformat(_("Document %1$s has been externally modified. "
1424 "Are you sure you want to overwrite this file?"), file);
1425 int const ret = Alert::prompt(_("Overwrite modified file?"),
1426 text, 1, 1, _("&Overwrite"), _("&Cancel"));
1427 if (ret == 1)
1428 return false;
1429 }
1430
1431 // We don't need autosaves in the immediate future. (Asger)
1432 resetAutosaveTimers();
1433
1434 // if the file does not yet exist, none of the backup activity
1435 // that follows is necessary
1436 if (!fileName().exists()) {
1437 if (!writeFile(fileName()))
1438 return false;
1439 markClean();
1440 return true;
1441 }
1442
1443 // we first write the file to a new name, then move it to its
1444 // proper location once that has been done successfully. that
1445 // way we preserve the original file if something goes wrong.
1446 string const justname = fileName().onlyFileNameWithoutExt();
1447 auto tempfile = make_unique<TempFile>(fileName().onlyPath(),
1448 justname + "-XXXXXX.lyx");
1449 bool const symlink = fileName().isSymLink();
1450 if (!symlink)
1451 tempfile->setAutoRemove(false);
1452
1453 FileName savefile(tempfile->name());
1454 LYXERR(Debug::FILES, "Saving to " << savefile.absFileName());
1455 if (!savefile.clonePermissions(fileName()))
1456 LYXERR0("Failed to clone the permission from " << fileName().absFileName() << " to " << savefile.absFileName());
1457
1458 if (!writeFile(savefile))
1459 return false;
1460
1461 // we will set this to false if we fail
1462 bool made_backup = true;
1463
1464 FileName backupName;
1465 bool const needBackup = lyxrc.make_backup || d->need_format_backup;
1466 if (needBackup) {
1467 if (d->need_format_backup)
1468 backupName = getBackupName();
1469
1470 // If we for some reason failed to find a backup name in case of
1471 // a format change, this will still set one. It's the best we can
1472 // do in this case.
1473 if (backupName.empty()) {
1474 backupName.set(fileName().absFileName() + "~");
1475 if (!lyxrc.backupdir_path.empty()) {
1476 string const mangledName =
1477 subst(subst(backupName.absFileName(), '/', '!'), ':', '!');
1478 backupName.set(addName(lyxrc.backupdir_path, mangledName));
1479 }
1480 }
1481
1482 LYXERR(Debug::FILES, "Backing up original file to " <<
1483 backupName.absFileName());
1484 // Except file is symlink do not copy because of #6587.
1485 // Hard links have bad luck.
1486 made_backup = symlink ?
1487 fileName().copyTo(backupName):
1488 fileName().moveTo(backupName);
1489
1490 if (!made_backup) {
1491 Alert::error(_("Backup failure"),
1492 bformat(_("Cannot create backup file %1$s.\n"
1493 "Please check whether the directory exists and is writable."),
1494 from_utf8(backupName.absFileName())));
1495 //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
1496 } else if (d->need_format_backup) {
1497 // the original file has been backed up successfully, so we
1498 // will not need to do that again
1499 d->need_format_backup = false;
1500 }
1501 }
1502
1503 // Destroy tempfile since it keeps the file locked on windows (bug 9234)
1504 // Only do this if tempfile is not in autoremove mode
1505 if (!symlink)
1506 tempfile.reset();
1507 // If we have no symlink, we can simply rename the temp file.
1508 // Otherwise, we need to copy it so the symlink stays intact.
1509 if (made_backup && symlink ? savefile.copyTo(fileName(), true) :
1510 savefile.moveTo(fileName()))
1511 {
1512 // saveCheckSum() was already called by writeFile(), but the
1513 // time stamp is invalidated by copying/moving
1514 saveCheckSum();
1515 markClean();
1516 if (d->file_format != LYX_FORMAT)
1517 // the file associated with this buffer is now in the current format
1518 d->file_format = LYX_FORMAT;
1519 return true;
1520 }
1521 // else we saved the file, but failed to move it to the right location.
1522
1523 if (needBackup && made_backup && !symlink) {
1524 // the original file was moved to some new location, so it will look
1525 // to the user as if it was deleted. (see bug #9234.) we could try
1526 // to restore it, but that would basically mean trying to do again
1527 // what we just failed to do. better to leave things as they are.
1528 Alert::error(_("Write failure"),
1529 bformat(_("The file has successfully been saved as:\n %1$s.\n"
1530 "But LyX could not move it to:\n %2$s.\n"
1531 "Your original file has been backed up to:\n %3$s"),
1532 from_utf8(savefile.absFileName()),
1533 from_utf8(fileName().absFileName()),
1534 from_utf8(backupName.absFileName())));
1535 } else {
1536 // either we did not try to make a backup, or else we tried and failed,
1537 // or else the original file was a symlink, in which case it was copied,
1538 // not moved. so the original file is intact.
1539 Alert::error(_("Write failure"),
1540 bformat(_("Cannot move saved file to:\n %1$s.\n"
1541 "But the file has successfully been saved as:\n %2$s."),
1542 from_utf8(fileName().absFileName()),
1543 from_utf8(savefile.absFileName())));
1544 }
1545 return false;
1546 }
1547
1548
writeFile(FileName const & fname) const1549 bool Buffer::writeFile(FileName const & fname) const
1550 {
1551 if (d->read_only && fname == d->filename)
1552 return false;
1553
1554 bool retval = false;
1555
1556 docstring const str = bformat(_("Saving document %1$s..."),
1557 makeDisplayPath(fname.absFileName()));
1558 message(str);
1559
1560 string const encoded_fname = fname.toSafeFilesystemEncoding(os::CREATE);
1561
1562 if (params().compressed) {
1563 gz::ogzstream ofs(encoded_fname.c_str(), ios::out|ios::trunc);
1564 retval = ofs && write(ofs);
1565 } else {
1566 ofstream ofs(encoded_fname.c_str(), ios::out|ios::trunc);
1567 retval = ofs && write(ofs);
1568 }
1569
1570 if (!retval) {
1571 message(str + _(" could not write file!"));
1572 return false;
1573 }
1574
1575 // see bug 6587
1576 // removeAutosaveFile();
1577
1578 saveCheckSum();
1579 message(str + _(" done."));
1580
1581 return true;
1582 }
1583
1584
emergencyWrite()1585 docstring Buffer::emergencyWrite()
1586 {
1587 // No need to save if the buffer has not changed.
1588 if (isClean())
1589 return docstring();
1590
1591 string const doc = isUnnamed() ? onlyFileName(absFileName()) : absFileName();
1592
1593 docstring user_message = bformat(
1594 _("LyX: Attempting to save document %1$s\n"), from_utf8(doc));
1595
1596 // We try to save three places:
1597 // 1) Same place as document. Unless it is an unnamed doc.
1598 if (!isUnnamed()) {
1599 string s = absFileName();
1600 s += ".emergency";
1601 LYXERR0(" " << s);
1602 if (writeFile(FileName(s))) {
1603 markClean();
1604 user_message += " " + bformat(_("Saved to %1$s. Phew.\n"), from_utf8(s));
1605 return user_message;
1606 } else {
1607 user_message += " " + _("Save failed! Trying again...\n");
1608 }
1609 }
1610
1611 // 2) In HOME directory.
1612 string s = addName(Package::get_home_dir().absFileName(), absFileName());
1613 s += ".emergency";
1614 lyxerr << ' ' << s << endl;
1615 if (writeFile(FileName(s))) {
1616 markClean();
1617 user_message += " " + bformat(_("Saved to %1$s. Phew.\n"), from_utf8(s));
1618 return user_message;
1619 }
1620
1621 user_message += " " + _("Save failed! Trying yet again...\n");
1622
1623 // 3) In "/tmp" directory.
1624 // MakeAbsPath to prepend the current
1625 // drive letter on OS/2
1626 s = addName(package().temp_dir().absFileName(), absFileName());
1627 s += ".emergency";
1628 lyxerr << ' ' << s << endl;
1629 if (writeFile(FileName(s))) {
1630 markClean();
1631 user_message += " " + bformat(_("Saved to %1$s. Phew.\n"), from_utf8(s));
1632 return user_message;
1633 }
1634
1635 user_message += " " + _("Save failed! Document is lost.");
1636 // Don't try again.
1637 markClean();
1638 return user_message;
1639 }
1640
1641
write(ostream & ofs) const1642 bool Buffer::write(ostream & ofs) const
1643 {
1644 #ifdef HAVE_LOCALE
1645 // Use the standard "C" locale for file output.
1646 ofs.imbue(locale::classic());
1647 #endif
1648
1649 // The top of the file should not be written by params().
1650
1651 // write out a comment in the top of the file
1652 // Important: Keep the version formatting in sync with lyx2lyx and
1653 // tex2lyx (bug 7951)
1654 ofs << "#LyX " << lyx_version_major << "." << lyx_version_minor
1655 << " created this file. For more info see http://www.lyx.org/\n"
1656 << "\\lyxformat " << LYX_FORMAT << "\n"
1657 << "\\begin_document\n";
1658
1659 /// For each author, set 'used' to true if there is a change
1660 /// by this author in the document; otherwise set it to 'false'.
1661 AuthorList::Authors::const_iterator a_it = params().authors().begin();
1662 AuthorList::Authors::const_iterator a_end = params().authors().end();
1663 for (; a_it != a_end; ++a_it)
1664 a_it->setUsed(false);
1665
1666 ParIterator const end = const_cast<Buffer *>(this)->par_iterator_end();
1667 ParIterator it = const_cast<Buffer *>(this)->par_iterator_begin();
1668 for ( ; it != end; ++it)
1669 it->checkAuthors(params().authors());
1670
1671 // now write out the buffer parameters.
1672 ofs << "\\begin_header\n";
1673 params().writeFile(ofs, this);
1674 ofs << "\\end_header\n";
1675
1676 // write the text
1677 ofs << "\n\\begin_body\n";
1678 text().write(ofs);
1679 ofs << "\n\\end_body\n";
1680
1681 // Write marker that shows file is complete
1682 ofs << "\\end_document" << endl;
1683
1684 // Shouldn't really be needed....
1685 //ofs.close();
1686
1687 // how to check if close went ok?
1688 // Following is an attempt... (BE 20001011)
1689
1690 // good() returns false if any error occurred, including some
1691 // formatting error.
1692 // bad() returns true if something bad happened in the buffer,
1693 // which should include file system full errors.
1694
1695 bool status = true;
1696 if (!ofs) {
1697 status = false;
1698 lyxerr << "File was not closed properly." << endl;
1699 }
1700
1701 return status;
1702 }
1703
1704
makeLaTeXFile(FileName const & fname,string const & original_path,OutputParams const & runparams_in,OutputWhat output) const1705 bool Buffer::makeLaTeXFile(FileName const & fname,
1706 string const & original_path,
1707 OutputParams const & runparams_in,
1708 OutputWhat output) const
1709 {
1710 OutputParams runparams = runparams_in;
1711
1712 // XeTeX with TeX fonts is only safe with ASCII encoding (see also #9740),
1713 // Check here, because the "flavor" is not known in BufferParams::encoding()
1714 // (power users can override this safety measure selecting "utf8-plain").
1715 if (!params().useNonTeXFonts && (runparams.flavor == OutputParams::XETEX)
1716 && (runparams.encoding->name() != "utf8-plain"))
1717 runparams.encoding = encodings.fromLyXName("ascii");
1718
1719 string const encoding = runparams.encoding->iconvName();
1720 LYXERR(Debug::LATEX, "makeLaTeXFile encoding: " << encoding << ", fname=" << fname.realPath());
1721
1722 ofdocstream ofs;
1723 try { ofs.reset(encoding); }
1724 catch (iconv_codecvt_facet_exception const & e) {
1725 lyxerr << "Caught iconv exception: " << e.what() << endl;
1726 Alert::error(_("Iconv software exception Detected"), bformat(_("Please "
1727 "verify that the support software for your encoding (%1$s) is "
1728 "properly installed"), from_ascii(encoding)));
1729 return false;
1730 }
1731 if (!openFileWrite(ofs, fname))
1732 return false;
1733
1734 ErrorList & errorList = d->errorLists["Export"];
1735 errorList.clear();
1736 bool failed_export = false;
1737 otexstream os(ofs);
1738
1739 // make sure we are ready to export
1740 // this needs to be done before we validate
1741 // FIXME Do we need to do this all the time? I.e., in children
1742 // of a master we are exporting?
1743 updateBuffer();
1744 updateMacroInstances(OutputUpdate);
1745
1746 try {
1747 writeLaTeXSource(os, original_path, runparams, output);
1748 }
1749 catch (EncodingException const & e) {
1750 docstring const failed(1, e.failed_char);
1751 ostringstream oss;
1752 oss << "0x" << hex << e.failed_char << dec;
1753 docstring msg = bformat(_("Could not find LaTeX command for character '%1$s'"
1754 " (code point %2$s)"),
1755 failed, from_utf8(oss.str()));
1756 errorList.push_back(ErrorItem(msg, _("Some characters of your document are probably not "
1757 "representable in the chosen encoding.\n"
1758 "Changing the document encoding to utf8 could help."),
1759 {e.par_id, e.pos}, {e.par_id, e.pos + 1}));
1760 failed_export = true;
1761 }
1762 catch (iconv_codecvt_facet_exception const & e) {
1763 errorList.push_back(ErrorItem(_("iconv conversion failed"),
1764 _(e.what())));
1765 failed_export = true;
1766 }
1767 catch (exception const & e) {
1768 errorList.push_back(ErrorItem(_("conversion failed"),
1769 _(e.what())));
1770 lyxerr << e.what() << endl;
1771 failed_export = true;
1772 }
1773 catch (...) {
1774 lyxerr << "Caught some really weird exception..." << endl;
1775 lyx_exit(1);
1776 }
1777
1778 d->texrow = move(os.texrow());
1779
1780 ofs.close();
1781 if (ofs.fail()) {
1782 failed_export = true;
1783 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1784 }
1785
1786 if (runparams_in.silent)
1787 errorList.clear();
1788 else
1789 errors("Export");
1790 return !failed_export;
1791 }
1792
1793
writeLaTeXSource(otexstream & os,string const & original_path,OutputParams const & runparams_in,OutputWhat output) const1794 void Buffer::writeLaTeXSource(otexstream & os,
1795 string const & original_path,
1796 OutputParams const & runparams_in,
1797 OutputWhat output) const
1798 {
1799 // The child documents, if any, shall be already loaded at this point.
1800
1801 OutputParams runparams = runparams_in;
1802
1803 // XeTeX with TeX fonts is only safe with ASCII encoding,
1804 // Check here, because the "flavor" is not known in BufferParams::encoding()
1805 // (power users can override this safety measure selecting "utf8-plain").
1806 if (!params().useNonTeXFonts && (runparams.flavor == OutputParams::XETEX)
1807 && (runparams.encoding->name() != "utf8-plain"))
1808 runparams.encoding = encodings.fromLyXName("ascii");
1809 // FIXME: when only the current paragraph is shown, this is ignored
1810 // (or not reached) and characters encodable in the current
1811 // encoding are not converted to ASCII-representation.
1812
1813 // If we are compiling a file standalone, even if this is the
1814 // child of some other buffer, let's cut the link here, so the
1815 // file is really independent and no concurring settings from
1816 // the master (e.g. branch state) interfere (see #8100).
1817 if (!runparams.is_child)
1818 d->ignore_parent = true;
1819
1820 // Classify the unicode characters appearing in math insets
1821 BufferEncodings::initUnicodeMath(*this);
1822
1823 // validate the buffer.
1824 LYXERR(Debug::LATEX, " Validating buffer...");
1825 LaTeXFeatures features(*this, params(), runparams);
1826 validate(features);
1827 // This is only set once per document (in master)
1828 if (!runparams.is_child)
1829 runparams.use_polyglossia = features.usePolyglossia();
1830 LYXERR(Debug::LATEX, " Buffer validation done.");
1831
1832 bool const output_preamble =
1833 output == FullSource || output == OnlyPreamble;
1834 bool const output_body =
1835 output == FullSource || output == OnlyBody;
1836
1837 // The starting paragraph of the coming rows is the
1838 // first paragraph of the document. (Asger)
1839 if (output_preamble && runparams.nice) {
1840 os << "%% LyX " << lyx_version << " created this file. "
1841 "For more info, see http://www.lyx.org/.\n"
1842 "%% Do not edit unless you really know what "
1843 "you are doing.\n";
1844 }
1845 LYXERR(Debug::INFO, "lyx document header finished");
1846
1847 // There are a few differences between nice LaTeX and usual files:
1848 // usual files have \batchmode and special input@path to allow
1849 // inclusion of figures specified by an explicitly relative path
1850 // (i.e., a path starting with './' or '../') with either \input or
1851 // \includegraphics, as the TEXINPUTS method doesn't work in this case.
1852 // input@path is set when the actual parameter original_path is set.
1853 // This is done for usual tex-file, but not for nice-latex-file.
1854 // (Matthias 250696)
1855 // Note that input@path is only needed for something the user does
1856 // in the preamble, included .tex files or ERT, files included by
1857 // LyX work without it.
1858 if (output_preamble) {
1859 if (!runparams.nice) {
1860 // code for usual, NOT nice-latex-file
1861 os << "\\batchmode\n"; // changed from \nonstopmode
1862 }
1863 if (!original_path.empty()) {
1864 // FIXME UNICODE
1865 // We don't know the encoding of inputpath
1866 docstring const inputpath = from_utf8(original_path);
1867 docstring uncodable_glyphs;
1868 Encoding const * const enc = runparams.encoding;
1869 if (enc) {
1870 for (size_t n = 0; n < inputpath.size(); ++n) {
1871 if (!enc->encodable(inputpath[n])) {
1872 docstring const glyph(1, inputpath[n]);
1873 LYXERR0("Uncodable character '"
1874 << glyph
1875 << "' in input path!");
1876 uncodable_glyphs += glyph;
1877 }
1878 }
1879 }
1880
1881 // warn user if we found uncodable glyphs.
1882 if (!uncodable_glyphs.empty()) {
1883 frontend::Alert::warning(
1884 _("Uncodable character in file path"),
1885 support::bformat(
1886 _("The path of your document\n"
1887 "(%1$s)\n"
1888 "contains glyphs that are unknown "
1889 "in the current document encoding "
1890 "(namely %2$s). This may result in "
1891 "incomplete output, unless "
1892 "TEXINPUTS contains the document "
1893 "directory and you don't use "
1894 "explicitly relative paths (i.e., "
1895 "paths starting with './' or "
1896 "'../') in the preamble or in ERT."
1897 "\n\nIn case of problems, choose "
1898 "an appropriate document encoding\n"
1899 "(such as utf8) or change the "
1900 "file path name."),
1901 inputpath, uncodable_glyphs));
1902 } else {
1903 string docdir = os::latex_path(original_path);
1904 if (contains(docdir, '#')) {
1905 docdir = subst(docdir, "#", "\\#");
1906 os << "\\catcode`\\#=11"
1907 "\\def\\#{#}\\catcode`\\#=6\n";
1908 }
1909 if (contains(docdir, '%')) {
1910 docdir = subst(docdir, "%", "\\%");
1911 os << "\\catcode`\\%=11"
1912 "\\def\\%{%}\\catcode`\\%=14\n";
1913 }
1914 bool const detokenize = !isAscii(from_utf8(docdir))
1915 || contains(docdir, '~');
1916 bool const quote = contains(docdir, ' ');
1917 os << "\\makeatletter\n"
1918 << "\\def\\input@path{{";
1919 if (detokenize)
1920 os << "\\detokenize{";
1921 if (quote)
1922 os << "\"";
1923 os << docdir;
1924 if (quote)
1925 os << "\"";
1926 if (detokenize)
1927 os << "}";
1928 os << "}}\n"
1929 << "\\makeatother\n";
1930 }
1931 }
1932
1933 // get parent macros (if this buffer has a parent) which will be
1934 // written at the document begin further down.
1935 MacroSet parentMacros;
1936 listParentMacros(parentMacros, features);
1937
1938 // Write the preamble
1939 runparams.use_babel = params().writeLaTeX(os, features,
1940 d->filename.onlyPath());
1941
1942 // Biblatex bibliographies are loaded here
1943 if (params().useBiblatex()) {
1944 vector<docstring> const bibfiles =
1945 prepareBibFilePaths(runparams, getBibfiles(), true);
1946 for (docstring const & file: bibfiles)
1947 os << "\\addbibresource{" << file << "}\n";
1948 }
1949
1950 if (!runparams.dryrun && features.hasPolyglossiaExclusiveLanguages()
1951 && !features.hasOnlyPolyglossiaLanguages()) {
1952 docstring blangs;
1953 docstring plangs;
1954 vector<string> bll = features.getBabelExclusiveLanguages();
1955 vector<string> pll = features.getPolyglossiaExclusiveLanguages();
1956 if (!bll.empty()) {
1957 docstring langs;
1958 for (vector<string>::const_iterator it = bll.begin(); it != bll.end(); ++it) {
1959 if (!langs.empty())
1960 langs += ", ";
1961 langs += _(*it);
1962 }
1963 blangs = bll.size() > 1 ?
1964 support::bformat(_("The languages %1$s are only supported by Babel."), langs)
1965 : support::bformat(_("The language %1$s is only supported by Babel."), langs);
1966 }
1967 if (!pll.empty()) {
1968 docstring langs;
1969 for (vector<string>::const_iterator it = pll.begin(); it != pll.end(); ++it) {
1970 if (!langs.empty())
1971 langs += ", ";
1972 langs += _(*it);
1973 }
1974 plangs = pll.size() > 1 ?
1975 support::bformat(_("The languages %1$s are only supported by Polyglossia."), langs)
1976 : support::bformat(_("The language %1$s is only supported by Polyglossia."), langs);
1977 if (!blangs.empty())
1978 plangs += "\n";
1979 }
1980
1981 frontend::Alert::warning(
1982 _("Incompatible Languages!"),
1983 support::bformat(
1984 _("You cannot use the following languages "
1985 "together in one LaTeX document because "
1986 "they require conflicting language packages:\n"
1987 "%1$s%2$s"),
1988 plangs, blangs));
1989 }
1990
1991 // Japanese might be required only in some children of a document,
1992 // but once required, we must keep use_japanese true.
1993 runparams.use_japanese |= features.isRequired("japanese");
1994
1995 if (!output_body) {
1996 // Restore the parenthood if needed
1997 if (!runparams.is_child)
1998 d->ignore_parent = false;
1999 return;
2000 }
2001
2002 // make the body.
2003 // mark the beginning of the body to separate it from InPreamble insets
2004 os.texrow().start(TexRow::beginDocument());
2005 os << "\\begin{document}\n";
2006
2007 // mark the start of a new paragraph by simulating a newline,
2008 // so that os.afterParbreak() returns true at document start
2009 os.lastChar('\n');
2010
2011 // output the parent macros
2012 MacroSet::iterator it = parentMacros.begin();
2013 MacroSet::iterator end = parentMacros.end();
2014 for (; it != end; ++it) {
2015 int num_lines = (*it)->write(os.os(), true);
2016 os.texrow().newlines(num_lines);
2017 }
2018
2019 } // output_preamble
2020
2021 LYXERR(Debug::INFO, "preamble finished, now the body.");
2022
2023 // the real stuff
2024 latexParagraphs(*this, text(), os, runparams);
2025
2026 // Restore the parenthood if needed
2027 if (!runparams.is_child)
2028 d->ignore_parent = false;
2029
2030 // add this just in case after all the paragraphs
2031 os << endl;
2032
2033 if (output_preamble) {
2034 os << "\\end{document}\n";
2035 LYXERR(Debug::LATEX, "makeLaTeXFile...done");
2036 } else {
2037 LYXERR(Debug::LATEX, "LaTeXFile for inclusion made.");
2038 }
2039 runparams_in.encoding = runparams.encoding;
2040
2041 LYXERR(Debug::INFO, "Finished making LaTeX file.");
2042 LYXERR(Debug::INFO, "Row count was " << os.texrow().rows() - 1 << '.');
2043 }
2044
2045
makeDocBookFile(FileName const & fname,OutputParams const & runparams,OutputWhat output) const2046 void Buffer::makeDocBookFile(FileName const & fname,
2047 OutputParams const & runparams,
2048 OutputWhat output) const
2049 {
2050 LYXERR(Debug::LATEX, "makeDocBookFile...");
2051
2052 ofdocstream ofs;
2053 if (!openFileWrite(ofs, fname))
2054 return;
2055
2056 // make sure we are ready to export
2057 // this needs to be done before we validate
2058 updateBuffer();
2059 updateMacroInstances(OutputUpdate);
2060
2061 writeDocBookSource(ofs, fname.absFileName(), runparams, output);
2062
2063 ofs.close();
2064 if (ofs.fail())
2065 lyxerr << "File '" << fname << "' was not closed properly." << endl;
2066 }
2067
2068
writeDocBookSource(odocstream & os,string const & fname,OutputParams const & runparams,OutputWhat output) const2069 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
2070 OutputParams const & runparams,
2071 OutputWhat output) const
2072 {
2073 LaTeXFeatures features(*this, params(), runparams);
2074 validate(features);
2075
2076 d->texrow.reset();
2077
2078 DocumentClass const & tclass = params().documentClass();
2079 string const & top_element = tclass.latexname();
2080
2081 bool const output_preamble =
2082 output == FullSource || output == OnlyPreamble;
2083 bool const output_body =
2084 output == FullSource || output == OnlyBody;
2085
2086 if (output_preamble) {
2087 if (runparams.flavor == OutputParams::XML)
2088 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
2089
2090 // FIXME UNICODE
2091 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
2092
2093 // FIXME UNICODE
2094 if (! tclass.class_header().empty())
2095 os << from_ascii(tclass.class_header());
2096 else if (runparams.flavor == OutputParams::XML)
2097 os << "PUBLIC \"-//OASIS//DTD DocBook XML V4.2//EN\" "
2098 << "\"https://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
2099 else
2100 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
2101
2102 docstring preamble = params().preamble;
2103 if (runparams.flavor != OutputParams::XML ) {
2104 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
2105 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
2106 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
2107 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
2108 }
2109
2110 string const name = runparams.nice
2111 ? changeExtension(absFileName(), ".sgml") : fname;
2112 preamble += features.getIncludedFiles(name);
2113 preamble += features.getLyXSGMLEntities();
2114
2115 if (!preamble.empty()) {
2116 os << "\n [ " << preamble << " ]";
2117 }
2118 os << ">\n\n";
2119 }
2120
2121 if (output_body) {
2122 string top = top_element;
2123 top += " lang=\"";
2124 if (runparams.flavor == OutputParams::XML)
2125 top += params().language->code();
2126 else
2127 top += params().language->code().substr(0, 2);
2128 top += '"';
2129
2130 if (!params().options.empty()) {
2131 top += ' ';
2132 top += params().options;
2133 }
2134
2135 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
2136 << " file was created by LyX " << lyx_version
2137 << "\n See http://www.lyx.org/ for more information -->\n";
2138
2139 params().documentClass().counters().reset();
2140
2141 sgml::openTag(os, top);
2142 os << '\n';
2143 docbookParagraphs(text(), *this, os, runparams);
2144 sgml::closeTag(os, top_element);
2145 }
2146 }
2147
2148
makeLyXHTMLFile(FileName const & fname,OutputParams const & runparams) const2149 void Buffer::makeLyXHTMLFile(FileName const & fname,
2150 OutputParams const & runparams) const
2151 {
2152 LYXERR(Debug::LATEX, "makeLyXHTMLFile...");
2153
2154 ofdocstream ofs;
2155 if (!openFileWrite(ofs, fname))
2156 return;
2157
2158 // make sure we are ready to export
2159 // this has to be done before we validate
2160 updateBuffer(UpdateMaster, OutputUpdate);
2161 updateMacroInstances(OutputUpdate);
2162
2163 writeLyXHTMLSource(ofs, runparams, FullSource);
2164
2165 ofs.close();
2166 if (ofs.fail())
2167 lyxerr << "File '" << fname << "' was not closed properly." << endl;
2168 }
2169
2170
writeLyXHTMLSource(odocstream & os,OutputParams const & runparams,OutputWhat output) const2171 void Buffer::writeLyXHTMLSource(odocstream & os,
2172 OutputParams const & runparams,
2173 OutputWhat output) const
2174 {
2175 LaTeXFeatures features(*this, params(), runparams);
2176 validate(features);
2177 d->bibinfo_.makeCitationLabels(*this);
2178
2179 bool const output_preamble =
2180 output == FullSource || output == OnlyPreamble;
2181 bool const output_body =
2182 output == FullSource || output == OnlyBody || output == IncludedFile;
2183
2184 if (output_preamble) {
2185 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2186 << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN\" \"http://www.w3.org/Math/DTD/mathml2/xhtml-math11-f.dtd\">\n"
2187 // FIXME Language should be set properly.
2188 << "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
2189 << "<head>\n"
2190 << "<meta name=\"GENERATOR\" content=\"" << PACKAGE_STRING << "\" />\n"
2191 // FIXME Presumably need to set this right
2192 << "<meta http-equiv=\"Content-type\" content=\"text/html;charset=UTF-8\" />\n";
2193
2194 docstring const & doctitle = features.htmlTitle();
2195 os << "<title>"
2196 << (doctitle.empty() ?
2197 from_ascii("LyX Document") :
2198 html::htmlize(doctitle, XHTMLStream::ESCAPE_ALL))
2199 << "</title>\n";
2200
2201 docstring styles = features.getTClassHTMLPreamble();
2202 if (!styles.empty())
2203 os << "\n<!-- Text Class Preamble -->\n" << styles << '\n';
2204
2205 styles = features.getPreambleSnippets().str;
2206 if (!styles.empty())
2207 os << "\n<!-- Preamble Snippets -->\n" << styles << '\n';
2208
2209 // we will collect CSS information in a stream, and then output it
2210 // either here, as part of the header, or else in a separate file.
2211 odocstringstream css;
2212 styles = features.getCSSSnippets();
2213 if (!styles.empty())
2214 css << "/* LyX Provided Styles */\n" << styles << '\n';
2215
2216 styles = features.getTClassHTMLStyles();
2217 if (!styles.empty())
2218 css << "/* Layout-provided Styles */\n" << styles << '\n';
2219
2220 bool const needfg = params().fontcolor != RGBColor(0, 0, 0);
2221 bool const needbg = params().backgroundcolor != RGBColor(0xFF, 0xFF, 0xFF);
2222 if (needfg || needbg) {
2223 css << "\nbody {\n";
2224 if (needfg)
2225 css << " color: "
2226 << from_ascii(X11hexname(params().fontcolor))
2227 << ";\n";
2228 if (needbg)
2229 css << " background-color: "
2230 << from_ascii(X11hexname(params().backgroundcolor))
2231 << ";\n";
2232 css << "}\n";
2233 }
2234
2235 docstring const dstyles = css.str();
2236 if (!dstyles.empty()) {
2237 bool written = false;
2238 if (params().html_css_as_file) {
2239 // open a file for CSS info
2240 ofdocstream ocss;
2241 string const fcssname = addName(temppath(), "docstyle.css");
2242 FileName const fcssfile = FileName(fcssname);
2243 if (openFileWrite(ocss, fcssfile)) {
2244 ocss << dstyles;
2245 ocss.close();
2246 written = true;
2247 // write link to header
2248 os << "<link rel='stylesheet' href='docstyle.css' type='text/css' />\n";
2249 // register file
2250 runparams.exportdata->addExternalFile("xhtml", fcssfile);
2251 }
2252 }
2253 // we are here if the CSS is supposed to be written to the header
2254 // or if we failed to write it to an external file.
2255 if (!written) {
2256 os << "<style type='text/css'>\n"
2257 << dstyles
2258 << "\n</style>\n";
2259 }
2260 }
2261 os << "</head>\n";
2262 }
2263
2264 if (output_body) {
2265 bool const output_body_tag = (output != IncludedFile);
2266 if (output_body_tag)
2267 os << "<body dir=\"auto\">\n";
2268 XHTMLStream xs(os);
2269 if (output != IncludedFile)
2270 // if we're an included file, the counters are in the master.
2271 params().documentClass().counters().reset();
2272 xhtmlParagraphs(text(), *this, xs, runparams);
2273 if (output_body_tag)
2274 os << "</body>\n";
2275 }
2276
2277 if (output_preamble)
2278 os << "</html>\n";
2279 }
2280
2281
2282 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
2283 // Other flags: -wall -v0 -x
runChktex()2284 int Buffer::runChktex()
2285 {
2286 setBusy(true);
2287
2288 // get LaTeX-Filename
2289 FileName const path(temppath());
2290 string const name = addName(path.absFileName(), latexName());
2291 string const org_path = filePath();
2292
2293 PathChanger p(path); // path to LaTeX file
2294 message(_("Running chktex..."));
2295
2296 // Generate the LaTeX file if neccessary
2297 OutputParams runparams(¶ms().encoding());
2298 runparams.flavor = OutputParams::LATEX;
2299 runparams.nice = false;
2300 runparams.linelen = lyxrc.plaintext_linelen;
2301 makeLaTeXFile(FileName(name), org_path, runparams);
2302
2303 TeXErrors terr;
2304 Chktex chktex(lyxrc.chktex_command, onlyFileName(name), filePath());
2305 int const res = chktex.run(terr); // run chktex
2306
2307 if (res == -1) {
2308 Alert::error(_("chktex failure"),
2309 _("Could not run chktex successfully."));
2310 } else {
2311 ErrorList & errlist = d->errorLists["ChkTeX"];
2312 errlist.clear();
2313 bufferErrors(terr, errlist);
2314 }
2315
2316 setBusy(false);
2317
2318 if (runparams.silent)
2319 d->errorLists["ChkTeX"].clear();
2320 else
2321 errors("ChkTeX");
2322
2323 return res;
2324 }
2325
2326
validate(LaTeXFeatures & features) const2327 void Buffer::validate(LaTeXFeatures & features) const
2328 {
2329 // Validate the buffer params, but not for included
2330 // files, since they also use the parent buffer's
2331 // params (# 5941)
2332 if (!features.runparams().is_child)
2333 params().validate(features);
2334
2335 for (Paragraph const & p : paragraphs())
2336 p.validate(features);
2337
2338 if (lyxerr.debugging(Debug::LATEX)) {
2339 features.showStruct();
2340 }
2341 }
2342
2343
getLabelList(vector<docstring> & list) const2344 void Buffer::getLabelList(vector<docstring> & list) const
2345 {
2346 // If this is a child document, use the master's list instead.
2347 if (parent()) {
2348 masterBuffer()->getLabelList(list);
2349 return;
2350 }
2351
2352 list.clear();
2353 shared_ptr<Toc> toc = d->toc_backend.toc("label");
2354 Toc::const_iterator toc_it = toc->begin();
2355 Toc::const_iterator end = toc->end();
2356 for (; toc_it != end; ++toc_it) {
2357 if (toc_it->depth() == 0)
2358 list.push_back(toc_it->str());
2359 }
2360 }
2361
2362
2363 // This is not in master, so it has been removed at 2126d5a3, on
2364 // 14 December 2018, so as to test whether it's needed. If there
2365 // aren't any complaints, it can be fully removed.
2366 #if 0
2367 void Buffer::updateBibfilesCache(UpdateScope scope) const
2368 {
2369 // FIXME This is probably unnecssary, given where we call this.
2370 // If this is a child document, use the parent's cache instead.
2371 if (parent() && scope != UpdateChildOnly) {
2372 masterBuffer()->updateBibfilesCache();
2373 return;
2374 }
2375
2376 docstring_list old_cache = d->bibfiles_cache_;
2377 d->bibfiles_cache_.clear();
2378 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2379 if (it->lyxCode() == BIBTEX_CODE) {
2380 InsetBibtex const & inset = static_cast<InsetBibtex const &>(*it);
2381 docstring_list const bibfiles = inset.getBibFiles();
2382 d->bibfiles_cache_.insert(d->bibfiles_cache_.end(),
2383 bibfiles.begin(),
2384 bibfiles.end());
2385 } else if (it->lyxCode() == INCLUDE_CODE) {
2386 InsetInclude & inset = static_cast<InsetInclude &>(*it);
2387 Buffer const * const incbuf = inset.getChildBuffer();
2388 if (!incbuf)
2389 continue;
2390 docstring_list const & bibfiles =
2391 incbuf->getBibfiles(UpdateChildOnly);
2392 if (!bibfiles.empty()) {
2393 d->bibfiles_cache_.insert(d->bibfiles_cache_.end(),
2394 bibfiles.begin(),
2395 bibfiles.end());
2396 }
2397 }
2398 }
2399 d->bibfile_cache_valid_ = true;
2400 d->cite_labels_valid_ = false;
2401 if (d->bibfiles_cache_ != old_cache)
2402 d->bibinfo_cache_valid_ = false;
2403 }
2404 #endif
2405
2406
invalidateBibinfoCache() const2407 void Buffer::invalidateBibinfoCache() const
2408 {
2409 d->bibinfo_cache_valid_ = false;
2410 d->cite_labels_valid_ = false;
2411 removeBiblioTempFiles();
2412 // also invalidate the cache for the parent buffer
2413 Buffer const * const pbuf = d->parent();
2414 if (pbuf)
2415 pbuf->invalidateBibinfoCache();
2416 }
2417
2418
getBibfiles(UpdateScope scope) const2419 docstring_list const & Buffer::getBibfiles(UpdateScope scope) const
2420 {
2421 // FIXME This is probably unnecessary, given where we call this.
2422 // If this is a child document, use the master's cache instead.
2423 Buffer const * const pbuf = masterBuffer();
2424 if (pbuf != this && scope != UpdateChildOnly)
2425 return pbuf->getBibfiles();
2426
2427 return d->bibfiles_cache_;
2428 }
2429
2430
masterBibInfo() const2431 BiblioInfo const & Buffer::masterBibInfo() const
2432 {
2433 Buffer const * const tmp = masterBuffer();
2434 if (tmp != this)
2435 return tmp->masterBibInfo();
2436 return d->bibinfo_;
2437 }
2438
2439
bibInfo() const2440 BiblioInfo const & Buffer::bibInfo() const
2441 {
2442 return d->bibinfo_;
2443 }
2444
2445
registerBibfiles(const docstring_list & bf) const2446 void Buffer::registerBibfiles(const docstring_list & bf) const
2447 {
2448 // We register the bib files in the master buffer,
2449 // if there is one, but also in every single buffer,
2450 // in case a child is compiled alone.
2451 Buffer const * const tmp = masterBuffer();
2452 if (tmp != this)
2453 tmp->registerBibfiles(bf);
2454
2455 for (auto const & p : bf) {
2456 docstring_list::const_iterator temp =
2457 find(d->bibfiles_cache_.begin(), d->bibfiles_cache_.end(), p);
2458 if (temp == d->bibfiles_cache_.end())
2459 d->bibfiles_cache_.push_back(p);
2460 }
2461 }
2462
2463
2464 static map<docstring, FileName> bibfileCache;
2465
getBibfilePath(docstring const & bibid) const2466 FileName Buffer::getBibfilePath(docstring const & bibid) const
2467 {
2468 map<docstring, FileName>::const_iterator it =
2469 bibfileCache.find(bibid);
2470 if (it != bibfileCache.end()) {
2471 // i.e., return bibfileCache[bibid];
2472 return it->second;
2473 }
2474
2475 LYXERR(Debug::FILES, "Reading file location for " << bibid);
2476 string const texfile = changeExtension(to_utf8(bibid), "bib");
2477 // we need to check first if this file exists where it's said to be.
2478 // there's a weird bug that occurs otherwise: if the file is in the
2479 // Buffer's directory but has the same name as some file that would be
2480 // found by kpsewhich, then we find the latter, not the former.
2481 FileName const local_file = makeAbsPath(texfile, filePath());
2482 FileName file = local_file;
2483 if (!file.exists()) {
2484 // there's no need now to check whether the file can be found
2485 // locally
2486 file = findtexfile(texfile, "bib", true);
2487 if (file.empty())
2488 file = local_file;
2489 }
2490 LYXERR(Debug::FILES, "Found at: " << file);
2491
2492 bibfileCache[bibid] = file;
2493 return bibfileCache[bibid];
2494 }
2495
2496
checkIfBibInfoCacheIsValid() const2497 void Buffer::checkIfBibInfoCacheIsValid() const
2498 {
2499 // use the master's cache
2500 Buffer const * const tmp = masterBuffer();
2501 if (tmp != this) {
2502 tmp->checkIfBibInfoCacheIsValid();
2503 return;
2504 }
2505
2506 // If we already know the cache is invalid, stop here.
2507 // This is important in the case when the bibliography
2508 // environment (rather than Bib[la]TeX) is used.
2509 // In that case, the timestamp check below gives no
2510 // sensible result. Rather than that, the cache will
2511 // be invalidated explicitly via invalidateBibInfoCache()
2512 // by the Bibitem inset.
2513 // Same applies for bib encoding changes, which trigger
2514 // invalidateBibInfoCache() by InsetBibtex.
2515 if (!d->bibinfo_cache_valid_)
2516 return;
2517
2518 if (d->have_bibitems_) {
2519 // We have a bibliography environment.
2520 // Invalidate the bibinfo cache unconditionally.
2521 // Cite labels will get invalidated by the inset if needed.
2522 d->bibinfo_cache_valid_ = false;
2523 return;
2524 }
2525
2526 // OK. This is with Bib(la)tex. We'll assume the cache
2527 // is valid and change this if we find changes in the bibs.
2528 d->bibinfo_cache_valid_ = true;
2529 d->cite_labels_valid_ = true;
2530 // compare the cached timestamps with the actual ones.
2531 docstring_list const & bibfiles_cache = getBibfiles();
2532 for (auto const & bf : bibfiles_cache) {
2533 FileName const fn = getBibfilePath(bf);
2534 time_t lastw = fn.lastModified();
2535 time_t prevw = d->bibfile_status_[fn];
2536 if (lastw != prevw) {
2537 d->bibinfo_cache_valid_ = false;
2538 d->cite_labels_valid_ = false;
2539 d->bibfile_status_[fn] = lastw;
2540 }
2541 }
2542 }
2543
2544
clearBibFileCache() const2545 void Buffer::clearBibFileCache() const
2546 {
2547 bibfileCache.clear();
2548 }
2549
2550
reloadBibInfoCache() const2551 void Buffer::reloadBibInfoCache() const
2552 {
2553 if (isInternal())
2554 return;
2555
2556 // use the master's cache
2557 Buffer const * const tmp = masterBuffer();
2558 if (tmp != this) {
2559 tmp->reloadBibInfoCache();
2560 return;
2561 }
2562
2563 checkIfBibInfoCacheIsValid();
2564 if (d->bibinfo_cache_valid_)
2565 return;
2566 LYXERR(Debug::FILES, "Bibinfo cache was invalid.");
2567
2568 // re-read file locations when this info changes
2569 // FIXME Is this sufficient? Or should we also force that
2570 // in some other cases? If so, then it is easy enough to
2571 // add the following line in some other places.
2572 clearBibFileCache();
2573 d->bibinfo_.clear();
2574 FileNameList checkedFiles;
2575 d->have_bibitems_ = false;
2576 collectBibKeys(checkedFiles);
2577 d->bibinfo_cache_valid_ = true;
2578 }
2579
2580
collectBibKeys(FileNameList & checkedFiles) const2581 void Buffer::collectBibKeys(FileNameList & checkedFiles) const
2582 {
2583 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2584 it->collectBibKeys(it, checkedFiles);
2585 if (it->lyxCode() == BIBITEM_CODE) {
2586 if (parent() != 0)
2587 parent()->d->have_bibitems_ = true;
2588 else
2589 d->have_bibitems_ = true;
2590 }
2591 }
2592 }
2593
2594
addBiblioInfo(BiblioInfo const & bin) const2595 void Buffer::addBiblioInfo(BiblioInfo const & bin) const
2596 {
2597 // We add the biblio info to the master buffer,
2598 // if there is one, but also to every single buffer,
2599 // in case a child is compiled alone.
2600 BiblioInfo & bi = d->bibinfo_;
2601 bi.mergeBiblioInfo(bin);
2602
2603 if (parent() != 0) {
2604 BiblioInfo & masterbi = parent()->d->bibinfo_;
2605 masterbi.mergeBiblioInfo(bin);
2606 }
2607 }
2608
2609
addBibTeXInfo(docstring const & key,BibTeXInfo const & bin) const2610 void Buffer::addBibTeXInfo(docstring const & key, BibTeXInfo const & bin) const
2611 {
2612 // We add the bibtex info to the master buffer,
2613 // if there is one, but also to every single buffer,
2614 // in case a child is compiled alone.
2615 BiblioInfo & bi = d->bibinfo_;
2616 bi[key] = bin;
2617
2618 if (parent() != 0) {
2619 BiblioInfo & masterbi = parent()->d->bibinfo_;
2620 masterbi[key] = bin;
2621 }
2622 }
2623
2624
makeCitationLabels() const2625 void Buffer::makeCitationLabels() const
2626 {
2627 Buffer const * const master = masterBuffer();
2628 return d->bibinfo_.makeCitationLabels(*master);
2629 }
2630
2631
invalidateCiteLabels() const2632 void Buffer::invalidateCiteLabels() const
2633 {
2634 masterBuffer()->d->cite_labels_valid_ = false;
2635 }
2636
citeLabelsValid() const2637 bool Buffer::citeLabelsValid() const
2638 {
2639 return masterBuffer()->d->cite_labels_valid_;
2640 }
2641
2642
removeBiblioTempFiles() const2643 void Buffer::removeBiblioTempFiles() const
2644 {
2645 // We remove files that contain LaTeX commands specific to the
2646 // particular bibliographic style being used, in order to avoid
2647 // LaTeX errors when we switch style.
2648 FileName const aux_file(addName(temppath(), changeExtension(latexName(),".aux")));
2649 FileName const bbl_file(addName(temppath(), changeExtension(latexName(),".bbl")));
2650 LYXERR(Debug::FILES, "Removing the .aux file " << aux_file);
2651 aux_file.removeFile();
2652 LYXERR(Debug::FILES, "Removing the .bbl file " << bbl_file);
2653 bbl_file.removeFile();
2654 // Also for the parent buffer
2655 Buffer const * const pbuf = parent();
2656 if (pbuf)
2657 pbuf->removeBiblioTempFiles();
2658 }
2659
2660
isDepClean(string const & name) const2661 bool Buffer::isDepClean(string const & name) const
2662 {
2663 DepClean::const_iterator const it = d->dep_clean.find(name);
2664 if (it == d->dep_clean.end())
2665 return true;
2666 return it->second;
2667 }
2668
2669
markDepClean(string const & name)2670 void Buffer::markDepClean(string const & name)
2671 {
2672 d->dep_clean[name] = true;
2673 }
2674
2675
getStatus(FuncRequest const & cmd,FuncStatus & flag)2676 bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag)
2677 {
2678 if (isInternal()) {
2679 // FIXME? if there is an Buffer LFUN that can be dispatched even
2680 // if internal, put a switch '(cmd.action)' here.
2681 return false;
2682 }
2683
2684 bool enable = true;
2685
2686 switch (cmd.action()) {
2687
2688 case LFUN_BUFFER_TOGGLE_READ_ONLY:
2689 flag.setOnOff(hasReadonlyFlag());
2690 break;
2691
2692 // FIXME: There is need for a command-line import.
2693 //case LFUN_BUFFER_IMPORT:
2694
2695 case LFUN_BUFFER_AUTO_SAVE:
2696 break;
2697
2698 case LFUN_BUFFER_EXPORT_CUSTOM:
2699 // FIXME: Nothing to check here?
2700 break;
2701
2702 case LFUN_BUFFER_EXPORT: {
2703 docstring const arg = cmd.argument();
2704 if (arg == "custom") {
2705 enable = true;
2706 break;
2707 }
2708 string format = (arg.empty() || arg == "default") ?
2709 params().getDefaultOutputFormat() : to_utf8(arg);
2710 size_t pos = format.find(' ');
2711 if (pos != string::npos)
2712 format = format.substr(0, pos);
2713 enable = params().isExportable(format, false);
2714 if (!enable)
2715 flag.message(bformat(
2716 _("Don't know how to export to format: %1$s"), arg));
2717 break;
2718 }
2719
2720 case LFUN_BUILD_PROGRAM:
2721 enable = params().isExportable("program", false);
2722 break;
2723
2724 case LFUN_BRANCH_ACTIVATE:
2725 case LFUN_BRANCH_DEACTIVATE:
2726 case LFUN_BRANCH_MASTER_ACTIVATE:
2727 case LFUN_BRANCH_MASTER_DEACTIVATE: {
2728 bool const master = (cmd.action() == LFUN_BRANCH_MASTER_ACTIVATE
2729 || cmd.action() == LFUN_BRANCH_MASTER_DEACTIVATE);
2730 BranchList const & branchList = master ? masterBuffer()->params().branchlist()
2731 : params().branchlist();
2732 docstring const branchName = cmd.argument();
2733 flag.setEnabled(!branchName.empty() && branchList.find(branchName));
2734 break;
2735 }
2736
2737 case LFUN_BRANCH_ADD:
2738 case LFUN_BRANCHES_RENAME:
2739 // if no Buffer is present, then of course we won't be called!
2740 break;
2741
2742 case LFUN_BUFFER_LANGUAGE:
2743 enable = !isReadonly();
2744 break;
2745
2746 case LFUN_BUFFER_VIEW_CACHE:
2747 (d->preview_file_).refresh();
2748 enable = (d->preview_file_).exists() && !(d->preview_file_).isFileEmpty();
2749 break;
2750
2751 case LFUN_CHANGES_TRACK:
2752 flag.setEnabled(true);
2753 flag.setOnOff(params().track_changes);
2754 break;
2755
2756 case LFUN_CHANGES_OUTPUT:
2757 flag.setEnabled(true);
2758 flag.setOnOff(params().output_changes);
2759 break;
2760
2761 case LFUN_BUFFER_TOGGLE_COMPRESSION:
2762 flag.setOnOff(params().compressed);
2763 break;
2764
2765 case LFUN_BUFFER_TOGGLE_OUTPUT_SYNC:
2766 flag.setOnOff(params().output_sync);
2767 break;
2768
2769 case LFUN_BUFFER_ANONYMIZE:
2770 break;
2771
2772 default:
2773 return false;
2774 }
2775 flag.setEnabled(enable);
2776 return true;
2777 }
2778
2779
dispatch(string const & command,DispatchResult & result)2780 void Buffer::dispatch(string const & command, DispatchResult & result)
2781 {
2782 return dispatch(lyxaction.lookupFunc(command), result);
2783 }
2784
2785
2786 // NOTE We can end up here even if we have no GUI, because we are called
2787 // by LyX::exec to handled command-line requests. So we may need to check
2788 // whether we have a GUI or not. The boolean use_gui holds this information.
dispatch(FuncRequest const & func,DispatchResult & dr)2789 void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr)
2790 {
2791 if (isInternal()) {
2792 // FIXME? if there is an Buffer LFUN that can be dispatched even
2793 // if internal, put a switch '(cmd.action())' here.
2794 dr.dispatched(false);
2795 return;
2796 }
2797 string const argument = to_utf8(func.argument());
2798 // We'll set this back to false if need be.
2799 bool dispatched = true;
2800 // This handles undo groups automagically
2801 UndoGroupHelper ugh(this);
2802
2803 switch (func.action()) {
2804 case LFUN_BUFFER_TOGGLE_READ_ONLY:
2805 if (lyxvc().inUse()) {
2806 string log = lyxvc().toggleReadOnly();
2807 if (!log.empty())
2808 dr.setMessage(log);
2809 }
2810 else
2811 setReadonly(!hasReadonlyFlag());
2812 break;
2813
2814 case LFUN_BUFFER_EXPORT: {
2815 string const format = (argument.empty() || argument == "default") ?
2816 params().getDefaultOutputFormat() : argument;
2817 ExportStatus const status = doExport(format, false);
2818 dr.setError(status != ExportSuccess);
2819 if (status != ExportSuccess)
2820 dr.setMessage(bformat(_("Error exporting to format: %1$s."),
2821 from_utf8(format)));
2822 break;
2823 }
2824
2825 case LFUN_BUILD_PROGRAM: {
2826 ExportStatus const status = doExport("program", true);
2827 dr.setError(status != ExportSuccess);
2828 if (status != ExportSuccess)
2829 dr.setMessage(_("Error generating literate programming code."));
2830 break;
2831 }
2832
2833 case LFUN_BUFFER_EXPORT_CUSTOM: {
2834 string format_name;
2835 string command = split(argument, format_name, ' ');
2836 Format const * format = theFormats().getFormat(format_name);
2837 if (!format) {
2838 lyxerr << "Format \"" << format_name
2839 << "\" not recognized!"
2840 << endl;
2841 break;
2842 }
2843
2844 // The name of the file created by the conversion process
2845 string filename;
2846
2847 // Output to filename
2848 if (format->name() == "lyx") {
2849 string const latexname = latexName(false);
2850 filename = changeExtension(latexname,
2851 format->extension());
2852 filename = addName(temppath(), filename);
2853
2854 if (!writeFile(FileName(filename)))
2855 break;
2856
2857 } else {
2858 doExport(format_name, true, filename);
2859 }
2860
2861 // Substitute $$FName for filename
2862 if (!contains(command, "$$FName"))
2863 command = "( " + command + " ) < $$FName";
2864 command = subst(command, "$$FName", filename);
2865
2866 // Execute the command in the background
2867 Systemcall call;
2868 call.startscript(Systemcall::DontWait, command,
2869 filePath(), layoutPos());
2870 break;
2871 }
2872
2873 // FIXME: There is need for a command-line import.
2874 /*
2875 case LFUN_BUFFER_IMPORT:
2876 doImport(argument);
2877 break;
2878 */
2879
2880 case LFUN_BUFFER_AUTO_SAVE:
2881 autoSave();
2882 resetAutosaveTimers();
2883 break;
2884
2885 case LFUN_BRANCH_ACTIVATE:
2886 case LFUN_BRANCH_DEACTIVATE:
2887 case LFUN_BRANCH_MASTER_ACTIVATE:
2888 case LFUN_BRANCH_MASTER_DEACTIVATE: {
2889 bool const master = (func.action() == LFUN_BRANCH_MASTER_ACTIVATE
2890 || func.action() == LFUN_BRANCH_MASTER_DEACTIVATE);
2891 Buffer * buf = master ? const_cast<Buffer *>(masterBuffer())
2892 : this;
2893
2894 docstring const branch_name = func.argument();
2895 // the case without a branch name is handled elsewhere
2896 if (branch_name.empty()) {
2897 dispatched = false;
2898 break;
2899 }
2900 Branch * branch = buf->params().branchlist().find(branch_name);
2901 if (!branch) {
2902 LYXERR0("Branch " << branch_name << " does not exist.");
2903 dr.setError(true);
2904 docstring const msg =
2905 bformat(_("Branch \"%1$s\" does not exist."), branch_name);
2906 dr.setMessage(msg);
2907 break;
2908 }
2909 bool const activate = (func.action() == LFUN_BRANCH_ACTIVATE
2910 || func.action() == LFUN_BRANCH_MASTER_ACTIVATE);
2911 if (branch->isSelected() != activate) {
2912 buf->undo().recordUndoBufferParams(CursorData());
2913 branch->setSelected(activate);
2914 dr.setError(false);
2915 dr.screenUpdate(Update::Force);
2916 dr.forceBufferUpdate();
2917 }
2918 break;
2919 }
2920
2921 case LFUN_BRANCH_ADD: {
2922 docstring branch_name = func.argument();
2923 if (branch_name.empty()) {
2924 dispatched = false;
2925 break;
2926 }
2927 BranchList & branch_list = params().branchlist();
2928 vector<docstring> const branches =
2929 getVectorFromString(branch_name, branch_list.separator());
2930 docstring msg;
2931 for (vector<docstring>::const_iterator it = branches.begin();
2932 it != branches.end(); ++it) {
2933 branch_name = *it;
2934 Branch * branch = branch_list.find(branch_name);
2935 if (branch) {
2936 LYXERR0("Branch " << branch_name << " already exists.");
2937 dr.setError(true);
2938 if (!msg.empty())
2939 msg += ("\n");
2940 msg += bformat(_("Branch \"%1$s\" already exists."), branch_name);
2941 } else {
2942 undo().recordUndoBufferParams(CursorData());
2943 branch_list.add(branch_name);
2944 branch = branch_list.find(branch_name);
2945 string const x11hexname = X11hexname(branch->color());
2946 docstring const str = branch_name + ' ' + from_ascii(x11hexname);
2947 lyx::dispatch(FuncRequest(LFUN_SET_COLOR, str));
2948 dr.setError(false);
2949 dr.screenUpdate(Update::Force);
2950 }
2951 }
2952 if (!msg.empty())
2953 dr.setMessage(msg);
2954 break;
2955 }
2956
2957 case LFUN_BRANCHES_RENAME: {
2958 if (func.argument().empty())
2959 break;
2960
2961 docstring const oldname = from_utf8(func.getArg(0));
2962 docstring const newname = from_utf8(func.getArg(1));
2963 InsetIterator it = inset_iterator_begin(inset());
2964 InsetIterator const end = inset_iterator_end(inset());
2965 bool success = false;
2966 for (; it != end; ++it) {
2967 if (it->lyxCode() == BRANCH_CODE) {
2968 InsetBranch & ins = static_cast<InsetBranch &>(*it);
2969 if (ins.branch() == oldname) {
2970 undo().recordUndo(CursorData(it));
2971 ins.rename(newname);
2972 success = true;
2973 continue;
2974 }
2975 }
2976 if (it->lyxCode() == INCLUDE_CODE) {
2977 // get buffer of external file
2978 InsetInclude const & ins =
2979 static_cast<InsetInclude const &>(*it);
2980 Buffer * child = ins.getChildBuffer();
2981 if (!child)
2982 continue;
2983 child->dispatch(func, dr);
2984 }
2985 }
2986
2987 if (success) {
2988 dr.screenUpdate(Update::Force);
2989 dr.forceBufferUpdate();
2990 }
2991 break;
2992 }
2993
2994 case LFUN_BUFFER_VIEW_CACHE:
2995 if (!theFormats().view(*this, d->preview_file_,
2996 d->preview_format_))
2997 dr.setMessage(_("Error viewing the output file."));
2998 break;
2999
3000 case LFUN_CHANGES_TRACK:
3001 if (params().save_transient_properties)
3002 undo().recordUndoBufferParams(CursorData());
3003 params().track_changes = !params().track_changes;
3004 if (!params().track_changes)
3005 dr.forceChangesUpdate();
3006 break;
3007
3008 case LFUN_CHANGES_OUTPUT:
3009 if (params().save_transient_properties)
3010 undo().recordUndoBufferParams(CursorData());
3011 params().output_changes = !params().output_changes;
3012 if (params().output_changes) {
3013 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
3014 bool xcolorulem = LaTeXFeatures::isAvailable("ulem") &&
3015 LaTeXFeatures::isAvailable("xcolor");
3016
3017 if (!dvipost && !xcolorulem) {
3018 Alert::warning(_("Changes not shown in LaTeX output"),
3019 _("Changes will not be highlighted in LaTeX output, "
3020 "because neither dvipost nor xcolor/ulem are installed.\n"
3021 "Please install these packages or redefine "
3022 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
3023 } else if (!xcolorulem) {
3024 Alert::warning(_("Changes not shown in LaTeX output"),
3025 _("Changes will not be highlighted in LaTeX output "
3026 "when using pdflatex, because xcolor and ulem are not installed.\n"
3027 "Please install both packages or redefine "
3028 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
3029 }
3030 }
3031 break;
3032
3033 case LFUN_BUFFER_TOGGLE_COMPRESSION:
3034 // turn compression on/off
3035 undo().recordUndoBufferParams(CursorData());
3036 params().compressed = !params().compressed;
3037 break;
3038
3039 case LFUN_BUFFER_TOGGLE_OUTPUT_SYNC:
3040 undo().recordUndoBufferParams(CursorData());
3041 params().output_sync = !params().output_sync;
3042 break;
3043
3044 case LFUN_BUFFER_ANONYMIZE: {
3045 undo().recordUndoFullBuffer(CursorData());
3046 CursorData cur(doc_iterator_begin(this));
3047 for ( ; cur ; cur.forwardPar())
3048 cur.paragraph().anonymize();
3049 dr.forceBufferUpdate();
3050 dr.screenUpdate(Update::Force);
3051 break;
3052 }
3053
3054 default:
3055 dispatched = false;
3056 break;
3057 }
3058 dr.dispatched(dispatched);
3059 }
3060
3061
changeLanguage(Language const * from,Language const * to)3062 void Buffer::changeLanguage(Language const * from, Language const * to)
3063 {
3064 LASSERT(from, return);
3065 LASSERT(to, return);
3066
3067 for_each(par_iterator_begin(),
3068 par_iterator_end(),
3069 bind(&Paragraph::changeLanguage, _1, params(), from, to));
3070 }
3071
3072
isMultiLingual() const3073 bool Buffer::isMultiLingual() const
3074 {
3075 ParConstIterator end = par_iterator_end();
3076 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
3077 if (it->isMultiLingual(params()))
3078 return true;
3079
3080 return false;
3081 }
3082
3083
getLanguages() const3084 std::set<Language const *> Buffer::getLanguages() const
3085 {
3086 std::set<Language const *> languages;
3087 getLanguages(languages);
3088 return languages;
3089 }
3090
3091
getLanguages(std::set<Language const * > & languages) const3092 void Buffer::getLanguages(std::set<Language const *> & languages) const
3093 {
3094 ParConstIterator end = par_iterator_end();
3095 // add the buffer language, even if it's not actively used
3096 languages.insert(language());
3097 // iterate over the paragraphs
3098 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
3099 it->getLanguages(languages);
3100 // also children
3101 ListOfBuffers clist = getDescendents();
3102 ListOfBuffers::const_iterator cit = clist.begin();
3103 ListOfBuffers::const_iterator const cen = clist.end();
3104 for (; cit != cen; ++cit)
3105 (*cit)->getLanguages(languages);
3106 }
3107
3108
getParFromID(int const id) const3109 DocIterator Buffer::getParFromID(int const id) const
3110 {
3111 Buffer * buf = const_cast<Buffer *>(this);
3112 if (id < 0)
3113 // This means non-existent
3114 return doc_iterator_end(buf);
3115
3116 for (DocIterator it = doc_iterator_begin(buf); !it.atEnd(); it.forwardPar())
3117 if (it.paragraph().id() == id)
3118 return it;
3119
3120 return doc_iterator_end(buf);
3121 }
3122
3123
hasParWithID(int const id) const3124 bool Buffer::hasParWithID(int const id) const
3125 {
3126 return !getParFromID(id).atEnd();
3127 }
3128
3129
par_iterator_begin()3130 ParIterator Buffer::par_iterator_begin()
3131 {
3132 return ParIterator(doc_iterator_begin(this));
3133 }
3134
3135
par_iterator_end()3136 ParIterator Buffer::par_iterator_end()
3137 {
3138 return ParIterator(doc_iterator_end(this));
3139 }
3140
3141
par_iterator_begin() const3142 ParConstIterator Buffer::par_iterator_begin() const
3143 {
3144 return ParConstIterator(doc_iterator_begin(this));
3145 }
3146
3147
par_iterator_end() const3148 ParConstIterator Buffer::par_iterator_end() const
3149 {
3150 return ParConstIterator(doc_iterator_end(this));
3151 }
3152
3153
language() const3154 Language const * Buffer::language() const
3155 {
3156 return params().language;
3157 }
3158
3159
B_(string const & l10n) const3160 docstring const Buffer::B_(string const & l10n) const
3161 {
3162 return params().B_(l10n);
3163 }
3164
3165
isClean() const3166 bool Buffer::isClean() const
3167 {
3168 return d->lyx_clean;
3169 }
3170
3171
isChecksumModified() const3172 bool Buffer::isChecksumModified() const
3173 {
3174 LASSERT(d->filename.exists(), return false);
3175 return d->checksum_ != d->filename.checksum();
3176 }
3177
3178
saveCheckSum() const3179 void Buffer::saveCheckSum() const
3180 {
3181 FileName const & file = d->filename;
3182 file.refresh();
3183 d->checksum_ = file.exists() ? file.checksum()
3184 : 0; // in the case of save to a new file.
3185 }
3186
3187
markClean() const3188 void Buffer::markClean() const
3189 {
3190 if (!d->lyx_clean) {
3191 d->lyx_clean = true;
3192 updateTitles();
3193 }
3194 // if the .lyx file has been saved, we don't need an
3195 // autosave
3196 d->bak_clean = true;
3197 d->undo_.markDirty();
3198 clearExternalModification();
3199 }
3200
3201
setUnnamed(bool flag)3202 void Buffer::setUnnamed(bool flag)
3203 {
3204 d->unnamed = flag;
3205 }
3206
3207
isUnnamed() const3208 bool Buffer::isUnnamed() const
3209 {
3210 return d->unnamed;
3211 }
3212
3213
3214 /// \note
3215 /// Don't check unnamed, here: isInternal() is used in
3216 /// newBuffer(), where the unnamed flag has not been set by anyone
3217 /// yet. Also, for an internal buffer, there should be no need for
3218 /// retrieving fileName() nor for checking if it is unnamed or not.
isInternal() const3219 bool Buffer::isInternal() const
3220 {
3221 return d->internal_buffer;
3222 }
3223
3224
setInternal(bool flag)3225 void Buffer::setInternal(bool flag)
3226 {
3227 d->internal_buffer = flag;
3228 }
3229
3230
markDirty()3231 void Buffer::markDirty()
3232 {
3233 if (d->lyx_clean) {
3234 d->lyx_clean = false;
3235 updateTitles();
3236 }
3237 d->bak_clean = false;
3238
3239 DepClean::iterator it = d->dep_clean.begin();
3240 DepClean::const_iterator const end = d->dep_clean.end();
3241
3242 for (; it != end; ++it)
3243 it->second = false;
3244 }
3245
3246
fileName() const3247 FileName Buffer::fileName() const
3248 {
3249 return d->filename;
3250 }
3251
3252
absFileName() const3253 string Buffer::absFileName() const
3254 {
3255 return d->filename.absFileName();
3256 }
3257
3258
filePath() const3259 string Buffer::filePath() const
3260 {
3261 string const abs = d->filename.onlyPath().absFileName();
3262 if (abs.empty())
3263 return abs;
3264 int last = abs.length() - 1;
3265
3266 return abs[last] == '/' ? abs : abs + '/';
3267 }
3268
3269
getReferencedFileName(string const & fn) const3270 DocFileName Buffer::getReferencedFileName(string const & fn) const
3271 {
3272 DocFileName result;
3273 if (FileName::isAbsolute(fn) || !FileName::isAbsolute(params().origin))
3274 result.set(fn, filePath());
3275 else {
3276 // filePath() ends with a path separator
3277 FileName const test(filePath() + fn);
3278 if (test.exists())
3279 result.set(fn, filePath());
3280 else
3281 result.set(fn, params().origin);
3282 }
3283
3284 return result;
3285 }
3286
3287
prepareFileNameForLaTeX(string const & name,string const & ext,bool nice) const3288 string const Buffer::prepareFileNameForLaTeX(string const & name,
3289 string const & ext, bool nice) const
3290 {
3291 string const fname = makeAbsPath(name, filePath()).absFileName();
3292 if (FileName::isAbsolute(name) || !FileName(fname + ext).isReadableFile())
3293 return name;
3294 if (!nice)
3295 return fname;
3296
3297 // FIXME UNICODE
3298 return to_utf8(makeRelPath(from_utf8(fname),
3299 from_utf8(masterBuffer()->filePath())));
3300 }
3301
3302
prepareBibFilePaths(OutputParams const & runparams,docstring_list const & bibfilelist,bool const add_extension) const3303 vector<docstring> const Buffer::prepareBibFilePaths(OutputParams const & runparams,
3304 docstring_list const & bibfilelist,
3305 bool const add_extension) const
3306 {
3307 // If we are processing the LaTeX file in a temp directory then
3308 // copy the .bib databases to this temp directory, mangling their
3309 // names in the process. Store this mangled name in the list of
3310 // all databases.
3311 // (We need to do all this because BibTeX *really*, *really*
3312 // can't handle "files with spaces" and Windows users tend to
3313 // use such filenames.)
3314 // Otherwise, store the (maybe absolute) path to the original,
3315 // unmangled database name.
3316
3317 vector<docstring> res;
3318
3319 // determine the export format
3320 string const tex_format = flavor2format(runparams.flavor);
3321
3322 // check for spaces in paths
3323 bool found_space = false;
3324
3325 for (auto const & bit : bibfilelist) {
3326 string utf8input = to_utf8(bit);
3327 string database =
3328 prepareFileNameForLaTeX(utf8input, ".bib", runparams.nice);
3329 FileName try_in_file =
3330 makeAbsPath(database + ".bib", filePath());
3331 bool not_from_texmf = try_in_file.isReadableFile();
3332 // If the file has not been found, try with the real file name
3333 // (it might come from a child in a sub-directory)
3334 if (!not_from_texmf) {
3335 try_in_file = getBibfilePath(bit);
3336 if (try_in_file.isReadableFile()) {
3337 // Check if the file is in texmf
3338 FileName kpsefile(findtexfile(changeExtension(utf8input, "bib"), "bib", true));
3339 not_from_texmf = kpsefile.empty()
3340 || kpsefile.absFileName() != try_in_file.absFileName();
3341 if (not_from_texmf)
3342 // If this exists, make path relative to the master
3343 // FIXME Unicode
3344 database = removeExtension(
3345 prepareFileNameForLaTeX(to_utf8(makeRelPath(from_utf8(try_in_file.absFileName()),
3346 from_utf8(filePath()))),
3347 ".bib", runparams.nice));
3348 }
3349 }
3350
3351 if (!runparams.inComment && !runparams.dryrun && !runparams.nice &&
3352 not_from_texmf) {
3353 // mangledFileName() needs the extension
3354 DocFileName const in_file = DocFileName(try_in_file);
3355 database = removeExtension(in_file.mangledFileName());
3356 FileName const out_file = makeAbsPath(database + ".bib",
3357 masterBuffer()->temppath());
3358 bool const success = in_file.copyTo(out_file);
3359 if (!success) {
3360 LYXERR0("Failed to copy '" << in_file
3361 << "' to '" << out_file << "'");
3362 }
3363 } else if (!runparams.inComment && runparams.nice && not_from_texmf) {
3364 runparams.exportdata->addExternalFile(tex_format, try_in_file, database + ".bib");
3365 if (!isValidLaTeXFileName(database)) {
3366 frontend::Alert::warning(_("Invalid filename"),
3367 _("The following filename will cause troubles "
3368 "when running the exported file through LaTeX: ") +
3369 from_utf8(database));
3370 }
3371 if (!isValidDVIFileName(database)) {
3372 frontend::Alert::warning(_("Problematic filename for DVI"),
3373 _("The following filename can cause troubles "
3374 "when running the exported file through LaTeX "
3375 "and opening the resulting DVI: ") +
3376 from_utf8(database), true);
3377 }
3378 }
3379
3380 if (add_extension)
3381 database += ".bib";
3382
3383 // FIXME UNICODE
3384 docstring const path = from_utf8(latex_path(database));
3385
3386 if (contains(path, ' '))
3387 found_space = true;
3388
3389 if (find(res.begin(), res.end(), path) == res.end())
3390 res.push_back(path);
3391 }
3392
3393 // Check if there are spaces in the path and warn BibTeX users, if so.
3394 // (biber can cope with such paths)
3395 if (!prefixIs(runparams.bibtex_command, "biber")) {
3396 // Post this warning only once.
3397 static bool warned_about_spaces = false;
3398 if (!warned_about_spaces &&
3399 runparams.nice && found_space) {
3400 warned_about_spaces = true;
3401 Alert::warning(_("Export Warning!"),
3402 _("There are spaces in the paths to your BibTeX databases.\n"
3403 "BibTeX will be unable to find them."));
3404 }
3405 }
3406
3407 return res;
3408 }
3409
3410
3411
layoutPos() const3412 string Buffer::layoutPos() const
3413 {
3414 return d->layout_position;
3415 }
3416
3417
setLayoutPos(string const & path)3418 void Buffer::setLayoutPos(string const & path)
3419 {
3420 if (path.empty()) {
3421 d->layout_position.clear();
3422 return;
3423 }
3424
3425 LATTEST(FileName::isAbsolute(path));
3426
3427 d->layout_position =
3428 to_utf8(makeRelPath(from_utf8(path), from_utf8(filePath())));
3429
3430 if (d->layout_position.empty())
3431 d->layout_position = ".";
3432 }
3433
3434
hasReadonlyFlag() const3435 bool Buffer::hasReadonlyFlag() const
3436 {
3437 return d->read_only;
3438 }
3439
3440
isReadonly() const3441 bool Buffer::isReadonly() const
3442 {
3443 return hasReadonlyFlag() || notifiesExternalModification();
3444 }
3445
3446
setParent(Buffer const * buffer)3447 void Buffer::setParent(Buffer const * buffer)
3448 {
3449 // Avoids recursive include.
3450 d->setParent(buffer == this ? 0 : buffer);
3451 updateMacros();
3452 }
3453
3454
parent() const3455 Buffer const * Buffer::parent() const
3456 {
3457 return d->parent();
3458 }
3459
3460
allRelatives() const3461 ListOfBuffers Buffer::allRelatives() const
3462 {
3463 ListOfBuffers lb = masterBuffer()->getDescendents();
3464 lb.push_front(const_cast<Buffer *>(masterBuffer()));
3465 return lb;
3466 }
3467
3468
masterBuffer() const3469 Buffer const * Buffer::masterBuffer() const
3470 {
3471 // FIXME Should be make sure we are not in some kind
3472 // of recursive include? A -> B -> A will crash this.
3473 Buffer const * const pbuf = d->parent();
3474 if (!pbuf)
3475 return this;
3476
3477 return pbuf->masterBuffer();
3478 }
3479
3480
isChild(Buffer * child) const3481 bool Buffer::isChild(Buffer * child) const
3482 {
3483 return d->children_positions.find(child) != d->children_positions.end();
3484 }
3485
3486
firstChildPosition(Buffer const * child)3487 DocIterator Buffer::firstChildPosition(Buffer const * child)
3488 {
3489 Impl::BufferPositionMap::iterator it;
3490 it = d->children_positions.find(child);
3491 if (it == d->children_positions.end())
3492 return DocIterator(this);
3493 return it->second;
3494 }
3495
3496
hasChildren() const3497 bool Buffer::hasChildren() const
3498 {
3499 return !d->children_positions.empty();
3500 }
3501
3502
collectChildren(ListOfBuffers & clist,bool grand_children) const3503 void Buffer::collectChildren(ListOfBuffers & clist, bool grand_children) const
3504 {
3505 // loop over children
3506 Impl::BufferPositionMap::iterator it = d->children_positions.begin();
3507 Impl::BufferPositionMap::iterator end = d->children_positions.end();
3508 for (; it != end; ++it) {
3509 Buffer * child = const_cast<Buffer *>(it->first);
3510 // No duplicates
3511 ListOfBuffers::const_iterator bit = find(clist.begin(), clist.end(), child);
3512 if (bit != clist.end())
3513 continue;
3514 clist.push_back(child);
3515 if (grand_children)
3516 // there might be grandchildren
3517 child->collectChildren(clist, true);
3518 }
3519 }
3520
3521
getChildren() const3522 ListOfBuffers Buffer::getChildren() const
3523 {
3524 ListOfBuffers v;
3525 collectChildren(v, false);
3526 // Make sure we have not included ourselves.
3527 ListOfBuffers::iterator bit = find(v.begin(), v.end(), this);
3528 if (bit != v.end()) {
3529 LYXERR0("Recursive include detected in `" << fileName() << "'.");
3530 v.erase(bit);
3531 }
3532 return v;
3533 }
3534
3535
getDescendents() const3536 ListOfBuffers Buffer::getDescendents() const
3537 {
3538 ListOfBuffers v;
3539 collectChildren(v, true);
3540 // Make sure we have not included ourselves.
3541 ListOfBuffers::iterator bit = find(v.begin(), v.end(), this);
3542 if (bit != v.end()) {
3543 LYXERR0("Recursive include detected in `" << fileName() << "'.");
3544 v.erase(bit);
3545 }
3546 return v;
3547 }
3548
3549
3550 template<typename M>
greatest_below(M & m,typename M::key_type const & x)3551 typename M::const_iterator greatest_below(M & m, typename M::key_type const & x)
3552 {
3553 if (m.empty())
3554 return m.end();
3555
3556 typename M::const_iterator it = m.lower_bound(x);
3557 if (it == m.begin())
3558 return m.end();
3559
3560 it--;
3561 return it;
3562 }
3563
3564
getBufferMacro(docstring const & name,DocIterator const & pos) const3565 MacroData const * Buffer::Impl::getBufferMacro(docstring const & name,
3566 DocIterator const & pos) const
3567 {
3568 LYXERR(Debug::MACROS, "Searching for " << to_ascii(name) << " at " << pos);
3569
3570 // if paragraphs have no macro context set, pos will be empty
3571 if (pos.empty())
3572 return 0;
3573
3574 // we haven't found anything yet
3575 DocIterator bestPos = owner_->par_iterator_begin();
3576 MacroData const * bestData = 0;
3577
3578 // find macro definitions for name
3579 NamePositionScopeMacroMap::const_iterator nameIt = macros.find(name);
3580 if (nameIt != macros.end()) {
3581 // find last definition in front of pos or at pos itself
3582 PositionScopeMacroMap::const_iterator it
3583 = greatest_below(nameIt->second, pos);
3584 if (it != nameIt->second.end()) {
3585 while (true) {
3586 // scope ends behind pos?
3587 if (pos < it->second.scope) {
3588 // Looks good, remember this. If there
3589 // is no external macro behind this,
3590 // we found the right one already.
3591 bestPos = it->first;
3592 bestData = &it->second.macro;
3593 break;
3594 }
3595
3596 // try previous macro if there is one
3597 if (it == nameIt->second.begin())
3598 break;
3599 --it;
3600 }
3601 }
3602 }
3603
3604 // find macros in included files
3605 PositionScopeBufferMap::const_iterator it
3606 = greatest_below(position_to_children, pos);
3607 if (it == position_to_children.end())
3608 // no children before
3609 return bestData;
3610
3611 while (true) {
3612 // do we know something better (i.e. later) already?
3613 if (it->first < bestPos )
3614 break;
3615
3616 // scope ends behind pos?
3617 if (pos < it->second.scope
3618 && (cloned_buffer_ ||
3619 theBufferList().isLoaded(it->second.buffer))) {
3620 // look for macro in external file
3621 macro_lock = true;
3622 MacroData const * data
3623 = it->second.buffer->getMacro(name, false);
3624 macro_lock = false;
3625 if (data) {
3626 bestPos = it->first;
3627 bestData = data;
3628 break;
3629 }
3630 }
3631
3632 // try previous file if there is one
3633 if (it == position_to_children.begin())
3634 break;
3635 --it;
3636 }
3637
3638 // return the best macro we have found
3639 return bestData;
3640 }
3641
3642
getMacro(docstring const & name,DocIterator const & pos,bool global) const3643 MacroData const * Buffer::getMacro(docstring const & name,
3644 DocIterator const & pos, bool global) const
3645 {
3646 if (d->macro_lock)
3647 return 0;
3648
3649 // query buffer macros
3650 MacroData const * data = d->getBufferMacro(name, pos);
3651 if (data != 0)
3652 return data;
3653
3654 // If there is a master buffer, query that
3655 Buffer const * const pbuf = d->parent();
3656 if (pbuf) {
3657 d->macro_lock = true;
3658 MacroData const * macro = pbuf->getMacro(
3659 name, *this, false);
3660 d->macro_lock = false;
3661 if (macro)
3662 return macro;
3663 }
3664
3665 if (global) {
3666 data = MacroTable::globalMacros().get(name);
3667 if (data != 0)
3668 return data;
3669 }
3670
3671 return 0;
3672 }
3673
3674
getMacro(docstring const & name,bool global) const3675 MacroData const * Buffer::getMacro(docstring const & name, bool global) const
3676 {
3677 // set scope end behind the last paragraph
3678 DocIterator scope = par_iterator_begin();
3679 scope.pit() = scope.lastpit() + 1;
3680
3681 return getMacro(name, scope, global);
3682 }
3683
3684
getMacro(docstring const & name,Buffer const & child,bool global) const3685 MacroData const * Buffer::getMacro(docstring const & name,
3686 Buffer const & child, bool global) const
3687 {
3688 // look where the child buffer is included first
3689 Impl::BufferPositionMap::iterator it = d->children_positions.find(&child);
3690 if (it == d->children_positions.end())
3691 return 0;
3692
3693 // check for macros at the inclusion position
3694 return getMacro(name, it->second, global);
3695 }
3696
3697
updateMacros(DocIterator & it,DocIterator & scope)3698 void Buffer::Impl::updateMacros(DocIterator & it, DocIterator & scope)
3699 {
3700 pit_type const lastpit = it.lastpit();
3701
3702 // look for macros in each paragraph
3703 while (it.pit() <= lastpit) {
3704 Paragraph & par = it.paragraph();
3705
3706 // iterate over the insets of the current paragraph
3707 InsetList const & insets = par.insetList();
3708 InsetList::const_iterator iit = insets.begin();
3709 InsetList::const_iterator end = insets.end();
3710 for (; iit != end; ++iit) {
3711 it.pos() = iit->pos;
3712
3713 // is it a nested text inset?
3714 if (iit->inset->asInsetText()) {
3715 // Inset needs its own scope?
3716 InsetText const * itext = iit->inset->asInsetText();
3717 bool newScope = itext->isMacroScope();
3718
3719 // scope which ends just behind the inset
3720 DocIterator insetScope = it;
3721 ++insetScope.pos();
3722
3723 // collect macros in inset
3724 it.push_back(CursorSlice(*iit->inset));
3725 updateMacros(it, newScope ? insetScope : scope);
3726 it.pop_back();
3727 continue;
3728 }
3729
3730 if (iit->inset->asInsetTabular()) {
3731 CursorSlice slice(*iit->inset);
3732 size_t const numcells = slice.nargs();
3733 for (; slice.idx() < numcells; slice.forwardIdx()) {
3734 it.push_back(slice);
3735 updateMacros(it, scope);
3736 it.pop_back();
3737 }
3738 continue;
3739 }
3740
3741 // is it an external file?
3742 if (iit->inset->lyxCode() == INCLUDE_CODE) {
3743 // get buffer of external file
3744 InsetInclude const & inset =
3745 static_cast<InsetInclude const &>(*iit->inset);
3746 macro_lock = true;
3747 Buffer * child = inset.getChildBuffer();
3748 macro_lock = false;
3749 if (!child)
3750 continue;
3751
3752 // register its position, but only when it is
3753 // included first in the buffer
3754 if (children_positions.find(child) ==
3755 children_positions.end())
3756 children_positions[child] = it;
3757
3758 // register child with its scope
3759 position_to_children[it] = Impl::ScopeBuffer(scope, child);
3760 continue;
3761 }
3762
3763 InsetMath * im = iit->inset->asInsetMath();
3764 if (doing_export && im) {
3765 InsetMathHull * hull = im->asHullInset();
3766 if (hull)
3767 hull->recordLocation(it);
3768 }
3769
3770 if (iit->inset->lyxCode() != MATHMACRO_CODE)
3771 continue;
3772
3773 // get macro data
3774 InsetMathMacroTemplate & macroTemplate =
3775 *iit->inset->asInsetMath()->asMacroTemplate();
3776 MacroContext mc(owner_, it);
3777 macroTemplate.updateToContext(mc);
3778
3779 // valid?
3780 bool valid = macroTemplate.validMacro();
3781 // FIXME: Should be fixNameAndCheckIfValid() in fact,
3782 // then the BufferView's cursor will be invalid in
3783 // some cases which leads to crashes.
3784 if (!valid)
3785 continue;
3786
3787 // register macro
3788 // FIXME (Abdel), I don't understand why we pass 'it' here
3789 // instead of 'macroTemplate' defined above... is this correct?
3790 macros[macroTemplate.name()][it] =
3791 Impl::ScopeMacro(scope, MacroData(const_cast<Buffer *>(owner_), it));
3792 }
3793
3794 // next paragraph
3795 it.pit()++;
3796 it.pos() = 0;
3797 }
3798 }
3799
3800
updateMacros() const3801 void Buffer::updateMacros() const
3802 {
3803 if (d->macro_lock)
3804 return;
3805
3806 LYXERR(Debug::MACROS, "updateMacro of " << d->filename.onlyFileName());
3807
3808 // start with empty table
3809 d->macros.clear();
3810 d->children_positions.clear();
3811 d->position_to_children.clear();
3812
3813 // Iterate over buffer, starting with first paragraph
3814 // The scope must be bigger than any lookup DocIterator
3815 // later. For the global lookup, lastpit+1 is used, hence
3816 // we use lastpit+2 here.
3817 DocIterator it = par_iterator_begin();
3818 DocIterator outerScope = it;
3819 outerScope.pit() = outerScope.lastpit() + 2;
3820 d->updateMacros(it, outerScope);
3821 }
3822
3823
getUsedBranches(std::list<docstring> & result,bool const from_master) const3824 void Buffer::getUsedBranches(std::list<docstring> & result, bool const from_master) const
3825 {
3826 InsetIterator it = inset_iterator_begin(inset());
3827 InsetIterator const end = inset_iterator_end(inset());
3828 for (; it != end; ++it) {
3829 if (it->lyxCode() == BRANCH_CODE) {
3830 InsetBranch & br = static_cast<InsetBranch &>(*it);
3831 docstring const name = br.branch();
3832 if (!from_master && !params().branchlist().find(name))
3833 result.push_back(name);
3834 else if (from_master && !masterBuffer()->params().branchlist().find(name))
3835 result.push_back(name);
3836 continue;
3837 }
3838 if (it->lyxCode() == INCLUDE_CODE) {
3839 // get buffer of external file
3840 InsetInclude const & ins =
3841 static_cast<InsetInclude const &>(*it);
3842 Buffer * child = ins.getChildBuffer();
3843 if (!child)
3844 continue;
3845 child->getUsedBranches(result, true);
3846 }
3847 }
3848 // remove duplicates
3849 result.unique();
3850 }
3851
3852
updateMacroInstances(UpdateType utype) const3853 void Buffer::updateMacroInstances(UpdateType utype) const
3854 {
3855 LYXERR(Debug::MACROS, "updateMacroInstances for "
3856 << d->filename.onlyFileName());
3857 DocIterator it = doc_iterator_begin(this);
3858 it.forwardInset();
3859 DocIterator const end = doc_iterator_end(this);
3860 for (; it != end; it.forwardInset()) {
3861 // look for MathData cells in InsetMathNest insets
3862 InsetMath * minset = it.nextInset()->asInsetMath();
3863 if (!minset)
3864 continue;
3865
3866 // update macro in all cells of the InsetMathNest
3867 DocIterator::idx_type n = minset->nargs();
3868 MacroContext mc = MacroContext(this, it);
3869 for (DocIterator::idx_type i = 0; i < n; ++i) {
3870 MathData & data = minset->cell(i);
3871 data.updateMacros(0, mc, utype, 0);
3872 }
3873 }
3874 }
3875
3876
listMacroNames(MacroNameSet & macros) const3877 void Buffer::listMacroNames(MacroNameSet & macros) const
3878 {
3879 if (d->macro_lock)
3880 return;
3881
3882 d->macro_lock = true;
3883
3884 // loop over macro names
3885 Impl::NamePositionScopeMacroMap::iterator nameIt = d->macros.begin();
3886 Impl::NamePositionScopeMacroMap::iterator nameEnd = d->macros.end();
3887 for (; nameIt != nameEnd; ++nameIt)
3888 macros.insert(nameIt->first);
3889
3890 // loop over children
3891 Impl::BufferPositionMap::iterator it = d->children_positions.begin();
3892 Impl::BufferPositionMap::iterator end = d->children_positions.end();
3893 for (; it != end; ++it) {
3894 Buffer * child = const_cast<Buffer *>(it->first);
3895 // The buffer might have been closed (see #10766).
3896 if (theBufferList().isLoaded(child))
3897 child->listMacroNames(macros);
3898 }
3899
3900 // call parent
3901 Buffer const * const pbuf = d->parent();
3902 if (pbuf)
3903 pbuf->listMacroNames(macros);
3904
3905 d->macro_lock = false;
3906 }
3907
3908
listParentMacros(MacroSet & macros,LaTeXFeatures & features) const3909 void Buffer::listParentMacros(MacroSet & macros, LaTeXFeatures & features) const
3910 {
3911 Buffer const * const pbuf = d->parent();
3912 if (!pbuf)
3913 return;
3914
3915 MacroNameSet names;
3916 pbuf->listMacroNames(names);
3917
3918 // resolve macros
3919 MacroNameSet::iterator it = names.begin();
3920 MacroNameSet::iterator end = names.end();
3921 for (; it != end; ++it) {
3922 // defined?
3923 MacroData const * data =
3924 pbuf->getMacro(*it, *this, false);
3925 if (data) {
3926 macros.insert(data);
3927
3928 // we cannot access the original InsetMathMacroTemplate anymore
3929 // here to calls validate method. So we do its work here manually.
3930 // FIXME: somehow make the template accessible here.
3931 if (data->optionals() > 0)
3932 features.require("xargs");
3933 }
3934 }
3935 }
3936
3937
getReferenceCache(docstring const & label)3938 Buffer::References & Buffer::getReferenceCache(docstring const & label)
3939 {
3940 if (d->parent())
3941 return const_cast<Buffer *>(masterBuffer())->getReferenceCache(label);
3942
3943 RefCache::iterator it = d->ref_cache_.find(label);
3944 if (it != d->ref_cache_.end())
3945 return it->second;
3946
3947 static References const dummy_refs = References();
3948 it = d->ref_cache_.insert(
3949 make_pair(label, dummy_refs)).first;
3950 return it->second;
3951 }
3952
3953
references(docstring const & label) const3954 Buffer::References const & Buffer::references(docstring const & label) const
3955 {
3956 return const_cast<Buffer *>(this)->getReferenceCache(label);
3957 }
3958
3959
addReference(docstring const & label,Inset * inset,ParIterator it)3960 void Buffer::addReference(docstring const & label, Inset * inset, ParIterator it)
3961 {
3962 References & refs = getReferenceCache(label);
3963 refs.push_back(make_pair(inset, it));
3964 }
3965
3966
setInsetLabel(docstring const & label,InsetLabel const * il,bool const active)3967 void Buffer::setInsetLabel(docstring const & label, InsetLabel const * il,
3968 bool const active)
3969 {
3970 LabelInfo linfo;
3971 linfo.label = label;
3972 linfo.inset = il;
3973 linfo.active = active;
3974 masterBuffer()->d->label_cache_.push_back(linfo);
3975 }
3976
3977
insetLabel(docstring const & label,bool const active) const3978 InsetLabel const * Buffer::insetLabel(docstring const & label,
3979 bool const active) const
3980 {
3981 for (auto & rc : masterBuffer()->d->label_cache_) {
3982 if (rc.label == label && (rc.active || !active))
3983 return rc.inset;
3984 }
3985 return nullptr;
3986 }
3987
3988
activeLabel(docstring const & label) const3989 bool Buffer::activeLabel(docstring const & label) const
3990 {
3991 if (!insetLabel(label, true))
3992 return false;
3993
3994 return true;
3995 }
3996
3997
clearReferenceCache() const3998 void Buffer::clearReferenceCache() const
3999 {
4000 if (!d->parent()) {
4001 d->ref_cache_.clear();
4002 d->label_cache_.clear();
4003 }
4004 }
4005
4006
changeRefsIfUnique(docstring const & from,docstring const & to)4007 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to)
4008 {
4009 //FIXME: This does not work for child documents yet.
4010 reloadBibInfoCache();
4011
4012 // Check if the label 'from' appears more than once
4013 BiblioInfo const & keys = masterBibInfo();
4014 BiblioInfo::const_iterator bit = keys.begin();
4015 BiblioInfo::const_iterator bend = keys.end();
4016 vector<docstring> labels;
4017
4018 for (; bit != bend; ++bit)
4019 // FIXME UNICODE
4020 labels.push_back(bit->first);
4021
4022 if (count(labels.begin(), labels.end(), from) > 1)
4023 return;
4024
4025 string const paramName = "key";
4026 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
4027 if (it->lyxCode() != CITE_CODE)
4028 continue;
4029 InsetCommand * inset = it->asInsetCommand();
4030 docstring const oldValue = inset->getParam(paramName);
4031 if (oldValue == from)
4032 inset->setParam(paramName, to);
4033 }
4034 }
4035
4036 // returns NULL if id-to-row conversion is unsupported
getSourceCode(odocstream & os,string const & format,pit_type par_begin,pit_type par_end,OutputWhat output,bool master) const4037 unique_ptr<TexRow> Buffer::getSourceCode(odocstream & os, string const & format,
4038 pit_type par_begin, pit_type par_end,
4039 OutputWhat output, bool master) const
4040 {
4041 unique_ptr<TexRow> texrow;
4042 OutputParams runparams(¶ms().encoding());
4043 runparams.nice = true;
4044 runparams.flavor = params().getOutputFlavor(format);
4045 runparams.linelen = lyxrc.plaintext_linelen;
4046 // No side effect of file copying and image conversion
4047 runparams.dryrun = true;
4048
4049 if (output == CurrentParagraph) {
4050 runparams.par_begin = par_begin;
4051 runparams.par_end = par_end;
4052 if (par_begin + 1 == par_end) {
4053 os << "% "
4054 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
4055 << "\n\n";
4056 } else {
4057 os << "% "
4058 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
4059 convert<docstring>(par_begin),
4060 convert<docstring>(par_end - 1))
4061 << "\n\n";
4062 }
4063 // output paragraphs
4064 if (runparams.flavor == OutputParams::LYX) {
4065 Paragraph const & par = text().paragraphs()[par_begin];
4066 ostringstream ods;
4067 depth_type dt = par.getDepth();
4068 par.write(ods, params(), dt);
4069 os << from_utf8(ods.str());
4070 } else if (runparams.flavor == OutputParams::HTML) {
4071 XHTMLStream xs(os);
4072 setMathFlavor(runparams);
4073 xhtmlParagraphs(text(), *this, xs, runparams);
4074 } else if (runparams.flavor == OutputParams::TEXT) {
4075 bool dummy = false;
4076 // FIXME Handles only one paragraph, unlike the others.
4077 // Probably should have some routine with a signature like them.
4078 writePlaintextParagraph(*this,
4079 text().paragraphs()[par_begin], os, runparams, dummy);
4080 } else if (params().isDocBook()) {
4081 docbookParagraphs(text(), *this, os, runparams);
4082 } else {
4083 // If we are previewing a paragraph, even if this is the
4084 // child of some other buffer, let's cut the link here,
4085 // so that no concurring settings from the master
4086 // (e.g. branch state) interfere (see #8101).
4087 if (!master)
4088 d->ignore_parent = true;
4089 // We need to validate the Buffer params' features here
4090 // in order to know if we should output polyglossia
4091 // macros (instead of babel macros)
4092 LaTeXFeatures features(*this, params(), runparams);
4093 validate(features);
4094 runparams.use_polyglossia = features.usePolyglossia();
4095 // latex or literate
4096 otexstream ots(os);
4097 // output above
4098 ots.texrow().newlines(2);
4099 // the real stuff
4100 latexParagraphs(*this, text(), ots, runparams);
4101 texrow = ots.releaseTexRow();
4102
4103 // Restore the parenthood
4104 if (!master)
4105 d->ignore_parent = false;
4106 }
4107 } else {
4108 os << "% ";
4109 if (output == FullSource)
4110 os << _("Preview source code");
4111 else if (output == OnlyPreamble)
4112 os << _("Preview preamble");
4113 else if (output == OnlyBody)
4114 os << _("Preview body");
4115 os << "\n\n";
4116 if (runparams.flavor == OutputParams::LYX) {
4117 ostringstream ods;
4118 if (output == FullSource)
4119 write(ods);
4120 else if (output == OnlyPreamble)
4121 params().writeFile(ods, this);
4122 else if (output == OnlyBody)
4123 text().write(ods);
4124 os << from_utf8(ods.str());
4125 } else if (runparams.flavor == OutputParams::HTML) {
4126 writeLyXHTMLSource(os, runparams, output);
4127 } else if (runparams.flavor == OutputParams::TEXT) {
4128 if (output == OnlyPreamble) {
4129 os << "% "<< _("Plain text does not have a preamble.");
4130 } else
4131 writePlaintextFile(*this, os, runparams);
4132 } else if (params().isDocBook()) {
4133 writeDocBookSource(os, absFileName(), runparams, output);
4134 } else {
4135 // latex or literate
4136 otexstream ots(os);
4137 // output above
4138 ots.texrow().newlines(2);
4139 if (master)
4140 runparams.is_child = true;
4141 updateBuffer();
4142 writeLaTeXSource(ots, string(), runparams, output);
4143 texrow = ots.releaseTexRow();
4144 }
4145 }
4146 return texrow;
4147 }
4148
4149
errorList(string const & type) const4150 ErrorList & Buffer::errorList(string const & type) const
4151 {
4152 return d->errorLists[type];
4153 }
4154
4155
updateTocItem(std::string const & type,DocIterator const & dit) const4156 void Buffer::updateTocItem(std::string const & type,
4157 DocIterator const & dit) const
4158 {
4159 if (d->gui_)
4160 d->gui_->updateTocItem(type, dit);
4161 }
4162
4163
structureChanged() const4164 void Buffer::structureChanged() const
4165 {
4166 if (d->gui_)
4167 d->gui_->structureChanged();
4168 }
4169
4170
errors(string const & err,bool from_master) const4171 void Buffer::errors(string const & err, bool from_master) const
4172 {
4173 if (d->gui_)
4174 d->gui_->errors(err, from_master);
4175 }
4176
4177
message(docstring const & msg) const4178 void Buffer::message(docstring const & msg) const
4179 {
4180 if (d->gui_)
4181 d->gui_->message(msg);
4182 }
4183
4184
setBusy(bool on) const4185 void Buffer::setBusy(bool on) const
4186 {
4187 if (d->gui_)
4188 d->gui_->setBusy(on);
4189 }
4190
4191
updateTitles() const4192 void Buffer::updateTitles() const
4193 {
4194 if (d->wa_)
4195 d->wa_->updateTitles();
4196 }
4197
4198
resetAutosaveTimers() const4199 void Buffer::resetAutosaveTimers() const
4200 {
4201 if (d->gui_)
4202 d->gui_->resetAutosaveTimers();
4203 }
4204
4205
hasGuiDelegate() const4206 bool Buffer::hasGuiDelegate() const
4207 {
4208 return d->gui_;
4209 }
4210
4211
setGuiDelegate(frontend::GuiBufferDelegate * gui)4212 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
4213 {
4214 d->gui_ = gui;
4215 }
4216
4217
4218
4219 namespace {
4220
4221 class AutoSaveBuffer : public ForkedProcess {
4222 public:
4223 ///
AutoSaveBuffer(Buffer const & buffer,FileName const & fname)4224 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
4225 : buffer_(buffer), fname_(fname) {}
4226 ///
clone() const4227 virtual shared_ptr<ForkedProcess> clone() const
4228 {
4229 return make_shared<AutoSaveBuffer>(*this);
4230 }
4231 ///
start()4232 int start()
4233 {
4234 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
4235 from_utf8(fname_.absFileName())));
4236 return run(DontWait);
4237 }
4238 private:
4239 ///
4240 virtual int generateChild();
4241 ///
4242 Buffer const & buffer_;
4243 FileName fname_;
4244 };
4245
4246
generateChild()4247 int AutoSaveBuffer::generateChild()
4248 {
4249 #if defined(__APPLE__)
4250 /* FIXME fork() is not usable for autosave on Mac OS X 10.6 (snow leopard)
4251 * We should use something else like threads.
4252 *
4253 * Since I do not know how to determine at run time what is the OS X
4254 * version, I just disable forking altogether for now (JMarc)
4255 */
4256 pid_t const pid = -1;
4257 #else
4258 // tmp_ret will be located (usually) in /tmp
4259 // will that be a problem?
4260 // Note that this calls ForkedCalls::fork(), so it's
4261 // ok cross-platform.
4262 pid_t const pid = fork();
4263 // If you want to debug the autosave
4264 // you should set pid to -1, and comment out the fork.
4265 if (pid != 0 && pid != -1)
4266 return pid;
4267 #endif
4268
4269 // pid = -1 signifies that lyx was unable
4270 // to fork. But we will do the save
4271 // anyway.
4272 bool failed = false;
4273 TempFile tempfile("lyxautoXXXXXX.lyx");
4274 tempfile.setAutoRemove(false);
4275 FileName const tmp_ret = tempfile.name();
4276 if (!tmp_ret.empty()) {
4277 if (!buffer_.writeFile(tmp_ret))
4278 failed = true;
4279 else if (!tmp_ret.moveTo(fname_))
4280 failed = true;
4281 } else
4282 failed = true;
4283
4284 if (failed) {
4285 // failed to write/rename tmp_ret so try writing direct
4286 if (!buffer_.writeFile(fname_)) {
4287 // It is dangerous to do this in the child,
4288 // but safe in the parent, so...
4289 if (pid == -1) // emit message signal.
4290 buffer_.message(_("Autosave failed!"));
4291 }
4292 }
4293
4294 if (pid == 0) // we are the child so...
4295 _exit(0);
4296
4297 return pid;
4298 }
4299
4300 } // namespace
4301
4302
getEmergencyFileName() const4303 FileName Buffer::getEmergencyFileName() const
4304 {
4305 return FileName(d->filename.absFileName() + ".emergency");
4306 }
4307
4308
getAutosaveFileName() const4309 FileName Buffer::getAutosaveFileName() const
4310 {
4311 // if the document is unnamed try to save in the backup dir, else
4312 // in the default document path, and as a last try in the filePath,
4313 // which will most often be the temporary directory
4314 string fpath;
4315 if (isUnnamed())
4316 fpath = lyxrc.backupdir_path.empty() ? lyxrc.document_path
4317 : lyxrc.backupdir_path;
4318 if (!isUnnamed() || fpath.empty() || !FileName(fpath).exists())
4319 fpath = filePath();
4320
4321 string const fname = "#" + d->filename.onlyFileName() + "#";
4322
4323 return makeAbsPath(fname, fpath);
4324 }
4325
4326
removeAutosaveFile() const4327 void Buffer::removeAutosaveFile() const
4328 {
4329 FileName const f = getAutosaveFileName();
4330 if (f.exists())
4331 f.removeFile();
4332 }
4333
4334
moveAutosaveFile(support::FileName const & oldauto) const4335 void Buffer::moveAutosaveFile(support::FileName const & oldauto) const
4336 {
4337 FileName const newauto = getAutosaveFileName();
4338 oldauto.refresh();
4339 if (newauto != oldauto && oldauto.exists())
4340 if (!oldauto.moveTo(newauto))
4341 LYXERR0("Unable to move autosave file `" << oldauto << "'!");
4342 }
4343
4344
autoSave() const4345 bool Buffer::autoSave() const
4346 {
4347 Buffer const * buf = d->cloned_buffer_ ? d->cloned_buffer_ : this;
4348 if (buf->d->bak_clean || hasReadonlyFlag())
4349 return true;
4350
4351 message(_("Autosaving current document..."));
4352 buf->d->bak_clean = true;
4353
4354 FileName const fname = getAutosaveFileName();
4355 LASSERT(d->cloned_buffer_, return false);
4356
4357 // If this buffer is cloned, we assume that
4358 // we are running in a separate thread already.
4359 TempFile tempfile("lyxautoXXXXXX.lyx");
4360 tempfile.setAutoRemove(false);
4361 FileName const tmp_ret = tempfile.name();
4362 if (!tmp_ret.empty()) {
4363 writeFile(tmp_ret);
4364 // assume successful write of tmp_ret
4365 if (tmp_ret.moveTo(fname))
4366 return true;
4367 }
4368 // failed to write/rename tmp_ret so try writing direct
4369 return writeFile(fname);
4370 }
4371
4372
setExportStatus(bool e) const4373 void Buffer::setExportStatus(bool e) const
4374 {
4375 d->doing_export = e;
4376 ListOfBuffers clist = getDescendents();
4377 ListOfBuffers::const_iterator cit = clist.begin();
4378 ListOfBuffers::const_iterator const cen = clist.end();
4379 for (; cit != cen; ++cit)
4380 (*cit)->d->doing_export = e;
4381 }
4382
4383
isExporting() const4384 bool Buffer::isExporting() const
4385 {
4386 return d->doing_export;
4387 }
4388
4389
doExport(string const & target,bool put_in_tempdir) const4390 Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir)
4391 const
4392 {
4393 string result_file;
4394 return doExport(target, put_in_tempdir, result_file);
4395 }
4396
doExport(string const & target,bool put_in_tempdir,string & result_file) const4397 Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir,
4398 string & result_file) const
4399 {
4400 bool const update_unincluded =
4401 params().maintain_unincluded_children
4402 && !params().getIncludedChildren().empty();
4403
4404 // (1) export with all included children (omit \includeonly)
4405 if (update_unincluded) {
4406 ExportStatus const status =
4407 doExport(target, put_in_tempdir, true, result_file);
4408 if (status != ExportSuccess)
4409 return status;
4410 }
4411 // (2) export with included children only
4412 return doExport(target, put_in_tempdir, false, result_file);
4413 }
4414
4415
setMathFlavor(OutputParams & op) const4416 void Buffer::setMathFlavor(OutputParams & op) const
4417 {
4418 switch (params().html_math_output) {
4419 case BufferParams::MathML:
4420 op.math_flavor = OutputParams::MathAsMathML;
4421 break;
4422 case BufferParams::HTML:
4423 op.math_flavor = OutputParams::MathAsHTML;
4424 break;
4425 case BufferParams::Images:
4426 op.math_flavor = OutputParams::MathAsImages;
4427 break;
4428 case BufferParams::LaTeX:
4429 op.math_flavor = OutputParams::MathAsLaTeX;
4430 break;
4431 }
4432 }
4433
4434
doExport(string const & target,bool put_in_tempdir,bool includeall,string & result_file) const4435 Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir,
4436 bool includeall, string & result_file) const
4437 {
4438 LYXERR(Debug::FILES, "target=" << target);
4439 OutputParams runparams(¶ms().encoding());
4440 string format = target;
4441 string dest_filename;
4442 size_t pos = target.find(' ');
4443 if (pos != string::npos) {
4444 dest_filename = target.substr(pos + 1, target.length() - pos - 1);
4445 format = target.substr(0, pos);
4446 if (format == "default")
4447 format = params().getDefaultOutputFormat();
4448 runparams.export_folder = FileName(dest_filename).onlyPath().realPath();
4449 FileName(dest_filename).onlyPath().createPath();
4450 LYXERR(Debug::FILES, "format=" << format << ", dest_filename=" << dest_filename << ", export_folder=" << runparams.export_folder);
4451 }
4452 MarkAsExporting exporting(this);
4453 string backend_format;
4454 runparams.flavor = OutputParams::LATEX;
4455 runparams.linelen = lyxrc.plaintext_linelen;
4456 runparams.includeall = includeall;
4457 vector<string> backs = params().backends();
4458 Converters converters = theConverters();
4459 bool need_nice_file = false;
4460 if (find(backs.begin(), backs.end(), format) == backs.end()) {
4461 // Get shortest path to format
4462 converters.buildGraph();
4463 Graph::EdgePath path;
4464 for (vector<string>::const_iterator it = backs.begin();
4465 it != backs.end(); ++it) {
4466 Graph::EdgePath p = converters.getPath(*it, format);
4467 if (!p.empty() && (path.empty() || p.size() < path.size())) {
4468 backend_format = *it;
4469 path = p;
4470 }
4471 }
4472 if (path.empty()) {
4473 if (!put_in_tempdir) {
4474 // Only show this alert if this is an export to a non-temporary
4475 // file (not for previewing).
4476 Alert::error(_("Couldn't export file"), bformat(
4477 _("No information for exporting the format %1$s."),
4478 theFormats().prettyName(format)));
4479 }
4480 return ExportNoPathToFormat;
4481 }
4482 runparams.flavor = converters.getFlavor(path, this);
4483 runparams.hyperref_driver = converters.getHyperrefDriver(path);
4484 Graph::EdgePath::const_iterator it = path.begin();
4485 Graph::EdgePath::const_iterator en = path.end();
4486 for (; it != en; ++it)
4487 if (theConverters().get(*it).nice()) {
4488 need_nice_file = true;
4489 break;
4490 }
4491
4492 } else {
4493 backend_format = format;
4494 LYXERR(Debug::FILES, "backend_format=" << backend_format);
4495 // FIXME: Don't hardcode format names here, but use a flag
4496 if (backend_format == "pdflatex")
4497 runparams.flavor = OutputParams::PDFLATEX;
4498 else if (backend_format == "luatex")
4499 runparams.flavor = OutputParams::LUATEX;
4500 else if (backend_format == "dviluatex")
4501 runparams.flavor = OutputParams::DVILUATEX;
4502 else if (backend_format == "xetex")
4503 runparams.flavor = OutputParams::XETEX;
4504 }
4505
4506 string filename = latexName(false);
4507 filename = addName(temppath(), filename);
4508 filename = changeExtension(filename,
4509 theFormats().extension(backend_format));
4510 LYXERR(Debug::FILES, "filename=" << filename);
4511
4512 // Plain text backend
4513 if (backend_format == "text") {
4514 runparams.flavor = OutputParams::TEXT;
4515 writePlaintextFile(*this, FileName(filename), runparams);
4516 }
4517 // HTML backend
4518 else if (backend_format == "xhtml") {
4519 runparams.flavor = OutputParams::HTML;
4520 setMathFlavor(runparams);
4521 makeLyXHTMLFile(FileName(filename), runparams);
4522 } else if (backend_format == "lyx")
4523 writeFile(FileName(filename));
4524 // Docbook backend
4525 else if (params().isDocBook()) {
4526 runparams.nice = !put_in_tempdir;
4527 makeDocBookFile(FileName(filename), runparams);
4528 }
4529 // LaTeX backend
4530 else if (backend_format == format || need_nice_file) {
4531 runparams.nice = true;
4532 bool const success = makeLaTeXFile(FileName(filename), string(), runparams);
4533 if (d->cloned_buffer_)
4534 d->cloned_buffer_->d->errorLists["Export"] = d->errorLists["Export"];
4535 if (!success)
4536 return ExportError;
4537 } else if (!lyxrc.tex_allows_spaces
4538 && contains(filePath(), ' ')) {
4539 Alert::error(_("File name error"),
4540 bformat(_("The directory path to the document\n%1$s\n"
4541 "contains spaces, but your TeX installation does "
4542 "not allow them. You should save the file to a directory "
4543 "whose name does not contain spaces."), from_utf8(filePath())));
4544 return ExportTexPathHasSpaces;
4545 } else {
4546 runparams.nice = false;
4547 bool const success = makeLaTeXFile(
4548 FileName(filename), filePath(), runparams);
4549 if (d->cloned_buffer_)
4550 d->cloned_buffer_->d->errorLists["Export"] = d->errorLists["Export"];
4551 if (!success)
4552 return ExportError;
4553 }
4554
4555 string const error_type = (format == "program")
4556 ? "Build" : params().bufferFormat();
4557 ErrorList & error_list = d->errorLists[error_type];
4558 string const ext = theFormats().extension(format);
4559 FileName const tmp_result_file(changeExtension(filename, ext));
4560 bool const success = converters.convert(this, FileName(filename),
4561 tmp_result_file, FileName(absFileName()), backend_format, format,
4562 error_list);
4563
4564 // Emit the signal to show the error list or copy it back to the
4565 // cloned Buffer so that it can be emitted afterwards.
4566 if (format != backend_format) {
4567 if (runparams.silent)
4568 error_list.clear();
4569 else if (d->cloned_buffer_)
4570 d->cloned_buffer_->d->errorLists[error_type] =
4571 d->errorLists[error_type];
4572 else
4573 errors(error_type);
4574 // also to the children, in case of master-buffer-view
4575 ListOfBuffers clist = getDescendents();
4576 ListOfBuffers::const_iterator cit = clist.begin();
4577 ListOfBuffers::const_iterator const cen = clist.end();
4578 for (; cit != cen; ++cit) {
4579 if (runparams.silent)
4580 (*cit)->d->errorLists[error_type].clear();
4581 else if (d->cloned_buffer_) {
4582 // Enable reverse search by copying back the
4583 // texrow object to the cloned buffer.
4584 // FIXME: this is not thread safe.
4585 (*cit)->d->cloned_buffer_->d->texrow = (*cit)->d->texrow;
4586 (*cit)->d->cloned_buffer_->d->errorLists[error_type] =
4587 (*cit)->d->errorLists[error_type];
4588 } else
4589 (*cit)->errors(error_type, true);
4590 }
4591 }
4592
4593 if (d->cloned_buffer_) {
4594 // Enable reverse dvi or pdf to work by copying back the texrow
4595 // object to the cloned buffer.
4596 // FIXME: There is a possibility of concurrent access to texrow
4597 // here from the main GUI thread that should be securized.
4598 d->cloned_buffer_->d->texrow = d->texrow;
4599 string const error_type = params().bufferFormat();
4600 d->cloned_buffer_->d->errorLists[error_type] = d->errorLists[error_type];
4601 }
4602
4603
4604 if (put_in_tempdir) {
4605 result_file = tmp_result_file.absFileName();
4606 return success ? ExportSuccess : ExportConverterError;
4607 }
4608
4609 if (dest_filename.empty())
4610 result_file = changeExtension(d->exportFileName().absFileName(), ext);
4611 else
4612 result_file = dest_filename;
4613 // We need to copy referenced files (e. g. included graphics
4614 // if format == "dvi") to the result dir.
4615 vector<ExportedFile> const files =
4616 runparams.exportdata->externalFiles(format);
4617 string const dest = runparams.export_folder.empty() ?
4618 onlyPath(result_file) : runparams.export_folder;
4619 bool use_force = use_gui ? lyxrc.export_overwrite == ALL_FILES
4620 : force_overwrite == ALL_FILES;
4621 CopyStatus status = use_force ? FORCE : SUCCESS;
4622
4623 vector<ExportedFile>::const_iterator it = files.begin();
4624 vector<ExportedFile>::const_iterator const en = files.end();
4625 for (; it != en && status != CANCEL; ++it) {
4626 string const fmt = theFormats().getFormatFromFile(it->sourceName);
4627 string fixedName = it->exportName;
4628 if (!runparams.export_folder.empty()) {
4629 // Relative pathnames starting with ../ will be sanitized
4630 // if exporting to a different folder
4631 while (fixedName.substr(0, 3) == "../")
4632 fixedName = fixedName.substr(3, fixedName.length() - 3);
4633 }
4634 FileName fixedFileName = makeAbsPath(fixedName, dest);
4635 fixedFileName.onlyPath().createPath();
4636 status = copyFile(fmt, it->sourceName,
4637 fixedFileName,
4638 it->exportName, status == FORCE,
4639 runparams.export_folder.empty());
4640 }
4641
4642 if (status == CANCEL) {
4643 message(_("Document export cancelled."));
4644 return ExportCancel;
4645 }
4646
4647 if (tmp_result_file.exists()) {
4648 // Finally copy the main file
4649 use_force = use_gui ? lyxrc.export_overwrite != NO_FILES
4650 : force_overwrite != NO_FILES;
4651 if (status == SUCCESS && use_force)
4652 status = FORCE;
4653 status = copyFile(format, tmp_result_file,
4654 FileName(result_file), result_file,
4655 status == FORCE);
4656 if (status == CANCEL) {
4657 message(_("Document export cancelled."));
4658 return ExportCancel;
4659 } else {
4660 message(bformat(_("Document exported as %1$s "
4661 "to file `%2$s'"),
4662 theFormats().prettyName(format),
4663 makeDisplayPath(result_file)));
4664 }
4665 } else {
4666 // This must be a dummy converter like fax (bug 1888)
4667 message(bformat(_("Document exported as %1$s"),
4668 theFormats().prettyName(format)));
4669 }
4670
4671 return success ? ExportSuccess : ExportConverterError;
4672 }
4673
4674
preview(string const & format) const4675 Buffer::ExportStatus Buffer::preview(string const & format) const
4676 {
4677 bool const update_unincluded =
4678 params().maintain_unincluded_children
4679 && !params().getIncludedChildren().empty();
4680 return preview(format, update_unincluded);
4681 }
4682
4683
preview(string const & format,bool includeall) const4684 Buffer::ExportStatus Buffer::preview(string const & format, bool includeall) const
4685 {
4686 MarkAsExporting exporting(this);
4687 string result_file;
4688 // (1) export with all included children (omit \includeonly)
4689 if (includeall) {
4690 ExportStatus const status = doExport(format, true, true, result_file);
4691 if (status != ExportSuccess)
4692 return status;
4693 }
4694 // (2) export with included children only
4695 ExportStatus const status = doExport(format, true, false, result_file);
4696 FileName const previewFile(result_file);
4697
4698 Impl * theimpl = isClone() ? d->cloned_buffer_->d : d;
4699 theimpl->preview_file_ = previewFile;
4700 theimpl->preview_format_ = format;
4701 theimpl->preview_error_ = (status != ExportSuccess);
4702
4703 if (status != ExportSuccess)
4704 return status;
4705
4706 if (previewFile.exists())
4707 return theFormats().view(*this, previewFile, format) ?
4708 PreviewSuccess : PreviewError;
4709
4710 // Successful export but no output file?
4711 // Probably a bug in error detection.
4712 LATTEST(status != ExportSuccess);
4713 return status;
4714 }
4715
4716
extractFromVC()4717 Buffer::ReadStatus Buffer::extractFromVC()
4718 {
4719 bool const found = LyXVC::file_not_found_hook(d->filename);
4720 if (!found)
4721 return ReadFileNotFound;
4722 if (!d->filename.isReadableFile())
4723 return ReadVCError;
4724 return ReadSuccess;
4725 }
4726
4727
loadEmergency()4728 Buffer::ReadStatus Buffer::loadEmergency()
4729 {
4730 FileName const emergencyFile = getEmergencyFileName();
4731 if (!emergencyFile.exists()
4732 || emergencyFile.lastModified() <= d->filename.lastModified())
4733 return ReadFileNotFound;
4734
4735 docstring const file = makeDisplayPath(d->filename.absFileName(), 20);
4736 docstring const text = bformat(_("An emergency save of the document "
4737 "%1$s exists.\n\nRecover emergency save?"), file);
4738
4739 int const load_emerg = Alert::prompt(_("Load emergency save?"), text,
4740 0, 2, _("&Recover"), _("&Load Original"), _("&Cancel"));
4741
4742 switch (load_emerg)
4743 {
4744 case 0: {
4745 docstring str;
4746 ReadStatus const ret_llf = loadThisLyXFile(emergencyFile);
4747 bool const success = (ret_llf == ReadSuccess);
4748 if (success) {
4749 if (hasReadonlyFlag()) {
4750 Alert::warning(_("File is read-only"),
4751 bformat(_("An emergency file is successfully loaded, "
4752 "but the original file %1$s is marked read-only. "
4753 "Please make sure to save the document as a different "
4754 "file."), from_utf8(d->filename.absFileName())));
4755 }
4756 markDirty();
4757 lyxvc().file_found_hook(d->filename);
4758 str = _("Document was successfully recovered.");
4759 } else
4760 str = _("Document was NOT successfully recovered.");
4761 str += "\n\n" + bformat(_("Remove emergency file now?\n(%1$s)"),
4762 makeDisplayPath(emergencyFile.absFileName()));
4763
4764 int const del_emerg =
4765 Alert::prompt(_("Delete emergency file?"), str, 1, 1,
4766 _("&Remove"), _("&Keep"));
4767 if (del_emerg == 0) {
4768 emergencyFile.removeFile();
4769 if (success)
4770 Alert::warning(_("Emergency file deleted"),
4771 _("Do not forget to save your file now!"), true);
4772 }
4773 return success ? ReadSuccess : ReadEmergencyFailure;
4774 }
4775 case 1: {
4776 int const del_emerg =
4777 Alert::prompt(_("Delete emergency file?"),
4778 _("Remove emergency file now?"), 1, 1,
4779 _("&Remove"), _("&Keep"));
4780 if (del_emerg == 0)
4781 emergencyFile.removeFile();
4782 else {
4783 // See bug #11464
4784 FileName newname;
4785 string const ename = emergencyFile.absFileName();
4786 bool noname = true;
4787 // Surely we can find one in 100 tries?
4788 for (int i = 1; i < 100; ++i) {
4789 newname.set(ename + to_string(i) + ".lyx");
4790 if (!newname.exists()) {
4791 noname = false;
4792 break;
4793 }
4794 }
4795 if (!noname) {
4796 // renameTo returns true on success. So inverting that
4797 // will give us true if we fail.
4798 noname = !emergencyFile.renameTo(newname);
4799 }
4800 if (noname) {
4801 Alert::warning(_("Can't rename emergency file!"),
4802 _("LyX was unable to rename the emergency file. "
4803 "You should do so manually. Otherwise, you will be"
4804 "asked about it again the next time you try to load"
4805 "this file, and may over-write your own work."));
4806 }
4807 }
4808 return ReadOriginal;
4809 }
4810
4811 default:
4812 break;
4813 }
4814 return ReadCancel;
4815 }
4816
4817
loadAutosave()4818 Buffer::ReadStatus Buffer::loadAutosave()
4819 {
4820 // Now check if autosave file is newer.
4821 FileName const autosaveFile = getAutosaveFileName();
4822 if (!autosaveFile.exists()
4823 || autosaveFile.lastModified() <= d->filename.lastModified())
4824 return ReadFileNotFound;
4825
4826 docstring const file = makeDisplayPath(d->filename.absFileName(), 20);
4827 docstring const text = bformat(_("The backup of the document %1$s "
4828 "is newer.\n\nLoad the backup instead?"), file);
4829 int const ret = Alert::prompt(_("Load backup?"), text, 0, 2,
4830 _("&Load backup"), _("Load &original"), _("&Cancel"));
4831
4832 switch (ret)
4833 {
4834 case 0: {
4835 ReadStatus const ret_llf = loadThisLyXFile(autosaveFile);
4836 // the file is not saved if we load the autosave file.
4837 if (ret_llf == ReadSuccess) {
4838 if (hasReadonlyFlag()) {
4839 Alert::warning(_("File is read-only"),
4840 bformat(_("A backup file is successfully loaded, "
4841 "but the original file %1$s is marked read-only. "
4842 "Please make sure to save the document as a "
4843 "different file."),
4844 from_utf8(d->filename.absFileName())));
4845 }
4846 markDirty();
4847 lyxvc().file_found_hook(d->filename);
4848 return ReadSuccess;
4849 }
4850 return ReadAutosaveFailure;
4851 }
4852 case 1:
4853 // Here we delete the autosave
4854 autosaveFile.removeFile();
4855 return ReadOriginal;
4856 default:
4857 break;
4858 }
4859 return ReadCancel;
4860 }
4861
4862
loadLyXFile()4863 Buffer::ReadStatus Buffer::loadLyXFile()
4864 {
4865 if (!d->filename.isReadableFile()) {
4866 ReadStatus const ret_rvc = extractFromVC();
4867 if (ret_rvc != ReadSuccess)
4868 return ret_rvc;
4869 }
4870
4871 ReadStatus const ret_re = loadEmergency();
4872 if (ret_re == ReadSuccess || ret_re == ReadCancel)
4873 return ret_re;
4874
4875 ReadStatus const ret_ra = loadAutosave();
4876 if (ret_ra == ReadSuccess || ret_ra == ReadCancel)
4877 return ret_ra;
4878
4879 return loadThisLyXFile(d->filename);
4880 }
4881
4882
loadThisLyXFile(FileName const & fn)4883 Buffer::ReadStatus Buffer::loadThisLyXFile(FileName const & fn)
4884 {
4885 return readFile(fn);
4886 }
4887
4888
bufferErrors(TeXErrors const & terr,ErrorList & errorList) const4889 void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
4890 {
4891 for (auto const & err : terr) {
4892 TexRow::TextEntry start = TexRow::text_none, end = TexRow::text_none;
4893 int errorRow = err.error_in_line;
4894 Buffer const * buf = 0;
4895 Impl const * p = d;
4896 if (err.child_name.empty())
4897 tie(start, end) = p->texrow.getEntriesFromRow(errorRow);
4898 else {
4899 // The error occurred in a child
4900 for (Buffer const * child : getDescendents()) {
4901 string const child_name =
4902 DocFileName(changeExtension(child->absFileName(), "tex")).
4903 mangledFileName();
4904 if (err.child_name != child_name)
4905 continue;
4906 tie(start, end) = child->d->texrow.getEntriesFromRow(errorRow);
4907 if (!TexRow::isNone(start)) {
4908 buf = d->cloned_buffer_
4909 ? child->d->cloned_buffer_->d->owner_
4910 : child->d->owner_;
4911 p = child->d;
4912 break;
4913 }
4914 }
4915 }
4916 errorList.push_back(ErrorItem(err.error_desc, err.error_text,
4917 start, end, buf));
4918 }
4919 }
4920
4921
setBuffersForInsets() const4922 void Buffer::setBuffersForInsets() const
4923 {
4924 inset().setBuffer(const_cast<Buffer &>(*this));
4925 }
4926
4927
updateBuffer(UpdateScope scope,UpdateType utype) const4928 void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const
4929 {
4930 LBUFERR(!text().paragraphs().empty());
4931
4932 // Use the master text class also for child documents
4933 Buffer const * const master = masterBuffer();
4934 DocumentClass const & textclass = master->params().documentClass();
4935
4936 docstring_list old_bibfiles;
4937 // Do this only if we are the top-level Buffer. We also need to account
4938 // for the case of a previewed child with ignored parent here.
4939 if (master == this && !d->ignore_parent) {
4940 textclass.counters().reset(from_ascii("bibitem"));
4941 reloadBibInfoCache();
4942 // we will re-read this cache as we go through, but we need
4943 // to know whether it's changed to know whether we need to
4944 // update the bibinfo cache.
4945 old_bibfiles = d->bibfiles_cache_;
4946 d->bibfiles_cache_.clear();
4947 }
4948
4949 // keep the buffers to be children in this set. If the call from the
4950 // master comes back we can see which of them were actually seen (i.e.
4951 // via an InsetInclude). The remaining ones in the set need still be updated.
4952 static std::set<Buffer const *> bufToUpdate;
4953 if (scope == UpdateMaster) {
4954 // If this is a child document start with the master
4955 if (master != this) {
4956 bufToUpdate.insert(this);
4957 master->updateBuffer(UpdateMaster, utype);
4958 // If the master buffer has no gui associated with it, then the TocModel is
4959 // not updated during the updateBuffer call and TocModel::toc_ is invalid
4960 // (bug 5699). The same happens if the master buffer is open in a different
4961 // window. This test catches both possibilities.
4962 // See: https://marc.info/?l=lyx-devel&m=138590578911716&w=2
4963 // There remains a problem here: If there is another child open in yet a third
4964 // window, that TOC is not updated. So some more general solution is needed at
4965 // some point.
4966 if (master->d->gui_ != d->gui_)
4967 structureChanged();
4968
4969 // was buf referenced from the master (i.e. not in bufToUpdate anymore)?
4970 if (bufToUpdate.find(this) == bufToUpdate.end())
4971 return;
4972 }
4973
4974 // start over the counters in the master
4975 textclass.counters().reset();
4976 }
4977
4978 // update will be done below for this buffer
4979 bufToUpdate.erase(this);
4980
4981 // update all caches
4982 clearReferenceCache();
4983 updateMacros();
4984 setChangesPresent(false);
4985
4986 Buffer & cbuf = const_cast<Buffer &>(*this);
4987
4988 // do the real work
4989 ParIterator parit = cbuf.par_iterator_begin();
4990 updateBuffer(parit, utype);
4991
4992 if (master != this)
4993 // If this document has siblings, then update the TocBackend later. The
4994 // reason is to ensure that later siblings are up to date when e.g. the
4995 // broken or not status of references is computed. The update is called
4996 // in InsetInclude::addToToc.
4997 return;
4998
4999 // if the bibfiles changed, the cache of bibinfo is invalid
5000 docstring_list new_bibfiles = d->bibfiles_cache_;
5001 // this is a trick to determine whether the two vectors have
5002 // the same elements.
5003 sort(new_bibfiles.begin(), new_bibfiles.end());
5004 sort(old_bibfiles.begin(), old_bibfiles.end());
5005 if (old_bibfiles != new_bibfiles) {
5006 LYXERR(Debug::FILES, "Reloading bibinfo cache.");
5007 invalidateBibinfoCache();
5008 reloadBibInfoCache();
5009 // We relied upon the bibinfo cache when recalculating labels. But that
5010 // cache was invalid, although we didn't find that out until now. So we
5011 // have to do it all again.
5012 // That said, the only thing we really need to do is update the citation
5013 // labels. Nothing else will have changed. So we could create a new
5014 // UpdateType that would signal that fact, if we needed to do so.
5015 parit = cbuf.par_iterator_begin();
5016 // we will be re-doing the counters and references and such.
5017 textclass.counters().reset();
5018 clearReferenceCache();
5019 // we should not need to do this again?
5020 // updateMacros();
5021 setChangesPresent(false);
5022 updateBuffer(parit, utype);
5023 // this will already have been done by reloadBibInfoCache();
5024 // d->bibinfo_cache_valid_ = true;
5025 }
5026 else {
5027 LYXERR(Debug::FILES, "Bibfiles unchanged.");
5028 // this is also set to true on the other path, by reloadBibInfoCache.
5029 d->bibinfo_cache_valid_ = true;
5030 }
5031 d->cite_labels_valid_ = true;
5032 /// FIXME: Perf
5033 cbuf.tocBackend().update(true, utype);
5034 if (scope == UpdateMaster)
5035 cbuf.structureChanged();
5036 }
5037
5038
getDepth(DocIterator const & it)5039 static depth_type getDepth(DocIterator const & it)
5040 {
5041 depth_type depth = 0;
5042 for (size_t i = 0 ; i < it.depth() ; ++i)
5043 if (!it[i].inset().inMathed())
5044 depth += it[i].paragraph().getDepth() + 1;
5045 // remove 1 since the outer inset does not count
5046 // we should have at least one non-math inset, so
5047 // depth should nevery be 0. but maybe it is worth
5048 // marking this, just in case.
5049 LATTEST(depth > 0);
5050 // coverity[INTEGER_OVERFLOW]
5051 return depth - 1;
5052 }
5053
getItemDepth(ParIterator const & it)5054 static depth_type getItemDepth(ParIterator const & it)
5055 {
5056 Paragraph const & par = *it;
5057 LabelType const labeltype = par.layout().labeltype;
5058
5059 if (labeltype != LABEL_ENUMERATE && labeltype != LABEL_ITEMIZE)
5060 return 0;
5061
5062 // this will hold the lowest depth encountered up to now.
5063 depth_type min_depth = getDepth(it);
5064 ParIterator prev_it = it;
5065 while (true) {
5066 if (prev_it.pit())
5067 --prev_it.top().pit();
5068 else {
5069 // start of nested inset: go to outer par
5070 prev_it.pop_back();
5071 if (prev_it.empty()) {
5072 // start of document: nothing to do
5073 return 0;
5074 }
5075 }
5076
5077 // We search for the first paragraph with same label
5078 // that is not more deeply nested.
5079 Paragraph & prev_par = *prev_it;
5080 depth_type const prev_depth = getDepth(prev_it);
5081 if (labeltype == prev_par.layout().labeltype) {
5082 if (prev_depth < min_depth)
5083 return prev_par.itemdepth + 1;
5084 if (prev_depth == min_depth)
5085 return prev_par.itemdepth;
5086 }
5087 min_depth = min(min_depth, prev_depth);
5088 // small optimization: if we are at depth 0, we won't
5089 // find anything else
5090 if (prev_depth == 0)
5091 return 0;
5092 }
5093 }
5094
5095
needEnumCounterReset(ParIterator const & it)5096 static bool needEnumCounterReset(ParIterator const & it)
5097 {
5098 Paragraph const & par = *it;
5099 LASSERT(par.layout().labeltype == LABEL_ENUMERATE, return false);
5100 depth_type const cur_depth = par.getDepth();
5101 ParIterator prev_it = it;
5102 while (prev_it.pit()) {
5103 --prev_it.top().pit();
5104 Paragraph const & prev_par = *prev_it;
5105 if (prev_par.getDepth() <= cur_depth)
5106 return prev_par.layout().name() != par.layout().name();
5107 }
5108 // start of nested inset: reset
5109 return true;
5110 }
5111
5112
5113 // set the label of a paragraph. This includes the counters.
setLabel(ParIterator & it,UpdateType utype) const5114 void Buffer::Impl::setLabel(ParIterator & it, UpdateType utype) const
5115 {
5116 BufferParams const & bp = owner_->masterBuffer()->params();
5117 DocumentClass const & textclass = bp.documentClass();
5118 Paragraph & par = it.paragraph();
5119 Layout const & layout = par.layout();
5120 Counters & counters = textclass.counters();
5121
5122 if (par.params().startOfAppendix()) {
5123 // We want to reset the counter corresponding to toplevel sectioning
5124 Layout const & lay = textclass.getTOCLayout();
5125 docstring const cnt = lay.counter;
5126 if (!cnt.empty())
5127 counters.reset(cnt);
5128 counters.appendix(true);
5129 }
5130 par.params().appendix(counters.appendix());
5131
5132 // Compute the item depth of the paragraph
5133 par.itemdepth = getItemDepth(it);
5134
5135 if (layout.margintype == MARGIN_MANUAL) {
5136 if (par.params().labelWidthString().empty())
5137 par.params().labelWidthString(par.expandLabel(layout, bp));
5138 } else if (layout.latextype == LATEX_BIB_ENVIRONMENT) {
5139 // we do not need to do anything here, since the empty case is
5140 // handled during export.
5141 } else {
5142 par.params().labelWidthString(docstring());
5143 }
5144
5145 switch(layout.labeltype) {
5146 case LABEL_ITEMIZE: {
5147 // At some point of time we should do something more
5148 // clever here, like:
5149 // par.params().labelString(
5150 // bp.user_defined_bullet(par.itemdepth).getText());
5151 // for now, use a simple hardcoded label
5152 docstring itemlabel;
5153 switch (par.itemdepth) {
5154 case 0:
5155 itemlabel = char_type(0x2022);
5156 break;
5157 case 1:
5158 itemlabel = char_type(0x2013);
5159 break;
5160 case 2:
5161 itemlabel = char_type(0x2217);
5162 break;
5163 case 3:
5164 itemlabel = char_type(0x2219); // or 0x00b7
5165 break;
5166 }
5167 par.params().labelString(itemlabel);
5168 break;
5169 }
5170
5171 case LABEL_ENUMERATE: {
5172 docstring enumcounter = layout.counter.empty() ? from_ascii("enum") : layout.counter;
5173
5174 switch (par.itemdepth) {
5175 case 2:
5176 enumcounter += 'i';
5177 // fall through
5178 case 1:
5179 enumcounter += 'i';
5180 // fall through
5181 case 0:
5182 enumcounter += 'i';
5183 break;
5184 case 3:
5185 enumcounter += "iv";
5186 break;
5187 default:
5188 // not a valid enumdepth...
5189 break;
5190 }
5191
5192 // Increase the master counter?
5193 if (layout.stepmastercounter && needEnumCounterReset(it))
5194 counters.stepMaster(enumcounter, utype);
5195
5196 // Maybe we have to reset the enumeration counter.
5197 if (!layout.resumecounter && needEnumCounterReset(it))
5198 counters.reset(enumcounter);
5199 counters.step(enumcounter, utype);
5200
5201 string const & lang = par.getParLanguage(bp)->code();
5202 par.params().labelString(counters.theCounter(enumcounter, lang));
5203
5204 break;
5205 }
5206
5207 case LABEL_SENSITIVE: {
5208 string const & type = counters.current_float();
5209 docstring full_label;
5210 if (type.empty())
5211 full_label = owner_->B_("Senseless!!! ");
5212 else {
5213 docstring name = owner_->B_(textclass.floats().getType(type).name());
5214 if (counters.hasCounter(from_utf8(type))) {
5215 string const & lang = par.getParLanguage(bp)->code();
5216 counters.step(from_utf8(type), utype);
5217 full_label = bformat(from_ascii("%1$s %2$s:"),
5218 name,
5219 counters.theCounter(from_utf8(type), lang));
5220 } else
5221 full_label = bformat(from_ascii("%1$s #:"), name);
5222 }
5223 par.params().labelString(full_label);
5224 break;
5225 }
5226
5227 case LABEL_NO_LABEL:
5228 par.params().labelString(docstring());
5229 break;
5230
5231 case LABEL_ABOVE:
5232 case LABEL_CENTERED:
5233 case LABEL_STATIC: {
5234 docstring const & lcounter = layout.counter;
5235 if (!lcounter.empty()) {
5236 if (layout.toclevel <= bp.secnumdepth
5237 && (layout.latextype != LATEX_ENVIRONMENT
5238 || it.text()->isFirstInSequence(it.pit()))) {
5239 if (counters.hasCounter(lcounter))
5240 counters.step(lcounter, utype);
5241 par.params().labelString(par.expandLabel(layout, bp));
5242 } else
5243 par.params().labelString(docstring());
5244 } else
5245 par.params().labelString(par.expandLabel(layout, bp));
5246 break;
5247 }
5248
5249 case LABEL_MANUAL:
5250 case LABEL_BIBLIO:
5251 par.params().labelString(par.expandLabel(layout, bp));
5252 }
5253 }
5254
5255
updateBuffer(ParIterator & parit,UpdateType utype) const5256 void Buffer::updateBuffer(ParIterator & parit, UpdateType utype) const
5257 {
5258 // LASSERT: Is it safe to continue here, or should we just return?
5259 LASSERT(parit.pit() == 0, /**/);
5260
5261 // Set the position of the text in the buffer to be able
5262 // to resolve macros in it.
5263 parit.text()->setMacrocontextPosition(parit);
5264
5265 // Reset bibitem counter in master (#8499)
5266 Buffer const * const master = masterBuffer();
5267 if (master == this && !d->ignore_parent)
5268 master->params().documentClass().counters().reset(from_ascii("bibitem"));
5269
5270 depth_type maxdepth = 0;
5271 pit_type const lastpit = parit.lastpit();
5272 for ( ; parit.pit() <= lastpit ; ++parit.pit()) {
5273 // reduce depth if necessary
5274 if (parit->params().depth() > maxdepth) {
5275 /** FIXME: this function is const, but
5276 * nevertheless it modifies the buffer. To be
5277 * cleaner, one should modify the buffer in
5278 * another function, which is actually
5279 * non-const. This would however be costly in
5280 * terms of code duplication.
5281 */
5282 const_cast<Buffer *>(this)->undo().recordUndo(CursorData(parit));
5283 parit->params().depth(maxdepth);
5284 }
5285 maxdepth = parit->getMaxDepthAfter();
5286
5287 if (utype == OutputUpdate) {
5288 // track the active counters
5289 // we have to do this for the master buffer, since the local
5290 // buffer isn't tracking anything.
5291 masterBuffer()->params().documentClass().counters().
5292 setActiveLayout(parit->layout());
5293 }
5294
5295 // set the counter for this paragraph
5296 d->setLabel(parit, utype);
5297
5298 // update change-tracking flag
5299 parit->addChangesToBuffer(*this);
5300
5301 // now the insets
5302 InsetList::const_iterator iit = parit->insetList().begin();
5303 InsetList::const_iterator end = parit->insetList().end();
5304 for (; iit != end; ++iit) {
5305 parit.pos() = iit->pos;
5306 iit->inset->updateBuffer(parit, utype);
5307 }
5308 }
5309 }
5310
5311
spellCheck(DocIterator & from,DocIterator & to,WordLangTuple & word_lang,docstring_list & suggestions) const5312 int Buffer::spellCheck(DocIterator & from, DocIterator & to,
5313 WordLangTuple & word_lang, docstring_list & suggestions) const
5314 {
5315 int progress = 0;
5316 WordLangTuple wl;
5317 suggestions.clear();
5318 word_lang = WordLangTuple();
5319 bool const to_end = to.empty();
5320 DocIterator const end = to_end ? doc_iterator_end(this) : to;
5321 // OK, we start from here.
5322 for (; from != end; from.forwardPos()) {
5323 // This skips all insets with spell check disabled.
5324 while (!from.allowSpellCheck()) {
5325 from.pop_back();
5326 from.pos()++;
5327 }
5328 // If from is at the end of the document (which is possible
5329 // when "from" was changed above) LyX will crash later otherwise.
5330 if (from.atEnd() || (!to_end && from >= end))
5331 break;
5332 to = from;
5333 from.paragraph().spellCheck();
5334 SpellChecker::Result res = from.paragraph().spellCheck(from.pos(), to.pos(), wl, suggestions);
5335 if (SpellChecker::misspelled(res)) {
5336 word_lang = wl;
5337 break;
5338 }
5339 // Do not increase progress when from == to, otherwise the word
5340 // count will be wrong.
5341 if (from != to) {
5342 from = to;
5343 ++progress;
5344 }
5345 }
5346 return progress;
5347 }
5348
5349
updateStatistics(DocIterator & from,DocIterator & to,bool skipNoOutput)5350 void Buffer::Impl::updateStatistics(DocIterator & from, DocIterator & to, bool skipNoOutput)
5351 {
5352 bool inword = false;
5353 word_count_ = 0;
5354 char_count_ = 0;
5355 blank_count_ = 0;
5356
5357 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
5358 if (!dit.inTexted()) {
5359 dit.forwardPos();
5360 continue;
5361 }
5362
5363 Paragraph const & par = dit.paragraph();
5364 pos_type const pos = dit.pos();
5365
5366 // Copied and adapted from isWordSeparator() in Paragraph
5367 if (pos == dit.lastpos()) {
5368 inword = false;
5369 } else {
5370 Inset const * ins = par.getInset(pos);
5371 if (ins && skipNoOutput && !ins->producesOutput()) {
5372 // skip this inset
5373 ++dit.top().pos();
5374 // stop if end of range was skipped
5375 if (!to.atEnd() && dit >= to)
5376 break;
5377 continue;
5378 } else if (!par.isDeleted(pos)) {
5379 if (par.isWordSeparator(pos))
5380 inword = false;
5381 else if (!inword) {
5382 ++word_count_;
5383 inword = true;
5384 }
5385 if (ins && ins->isLetter())
5386 ++char_count_;
5387 else if (ins && ins->isSpace())
5388 ++blank_count_;
5389 else {
5390 char_type const c = par.getChar(pos);
5391 if (isPrintableNonspace(c))
5392 ++char_count_;
5393 else if (isSpace(c))
5394 ++blank_count_;
5395 }
5396 }
5397 }
5398 dit.forwardPos();
5399 }
5400 }
5401
5402
updateStatistics(DocIterator & from,DocIterator & to,bool skipNoOutput) const5403 void Buffer::updateStatistics(DocIterator & from, DocIterator & to, bool skipNoOutput) const
5404 {
5405 d->updateStatistics(from, to, skipNoOutput);
5406 }
5407
5408
wordCount() const5409 int Buffer::wordCount() const
5410 {
5411 return d->wordCount();
5412 }
5413
5414
charCount(bool with_blanks) const5415 int Buffer::charCount(bool with_blanks) const
5416 {
5417 return d->charCount(with_blanks);
5418 }
5419
5420
reload()5421 Buffer::ReadStatus Buffer::reload()
5422 {
5423 setBusy(true);
5424 // c.f. bug https://www.lyx.org/trac/ticket/6587
5425 removeAutosaveFile();
5426 // e.g., read-only status could have changed due to version control
5427 d->filename.refresh();
5428 docstring const disp_fn = makeDisplayPath(d->filename.absFileName());
5429
5430 // clear parent. this will get reset if need be.
5431 d->setParent(0);
5432 ReadStatus const status = loadLyXFile();
5433 if (status == ReadSuccess) {
5434 updateBuffer();
5435 changed(true);
5436 updateTitles();
5437 markClean();
5438 message(bformat(_("Document %1$s reloaded."), disp_fn));
5439 d->undo_.clear();
5440 } else {
5441 message(bformat(_("Could not reload document %1$s."), disp_fn));
5442 }
5443 setBusy(false);
5444 removePreviews();
5445 updatePreviews();
5446 errors("Parse");
5447 return status;
5448 }
5449
5450
saveAs(FileName const & fn)5451 bool Buffer::saveAs(FileName const & fn)
5452 {
5453 FileName const old_name = fileName();
5454 FileName const old_auto = getAutosaveFileName();
5455 bool const old_unnamed = isUnnamed();
5456 bool success = true;
5457 d->old_position = filePath();
5458
5459 setFileName(fn);
5460 markDirty();
5461 setUnnamed(false);
5462
5463 if (save()) {
5464 // bring the autosave file with us, just in case.
5465 moveAutosaveFile(old_auto);
5466 // validate version control data and
5467 // correct buffer title
5468 lyxvc().file_found_hook(fileName());
5469 updateTitles();
5470 // the file has now been saved to the new location.
5471 // we need to check that the locations of child buffers
5472 // are still valid.
5473 checkChildBuffers();
5474 checkMasterBuffer();
5475 } else {
5476 // save failed
5477 // reset the old filename and unnamed state
5478 setFileName(old_name);
5479 setUnnamed(old_unnamed);
5480 success = false;
5481 }
5482
5483 d->old_position.clear();
5484 return success;
5485 }
5486
5487
checkChildBuffers()5488 void Buffer::checkChildBuffers()
5489 {
5490 Impl::BufferPositionMap::iterator it = d->children_positions.begin();
5491 Impl::BufferPositionMap::iterator const en = d->children_positions.end();
5492 for (; it != en; ++it) {
5493 DocIterator dit = it->second;
5494 Buffer * cbuf = const_cast<Buffer *>(it->first);
5495 if (!cbuf || !theBufferList().isLoaded(cbuf))
5496 continue;
5497 Inset * inset = dit.nextInset();
5498 LASSERT(inset && inset->lyxCode() == INCLUDE_CODE, continue);
5499 InsetInclude * inset_inc = static_cast<InsetInclude *>(inset);
5500 docstring const & incfile = inset_inc->getParam("filename");
5501 string oldloc = cbuf->absFileName();
5502 string newloc = makeAbsPath(to_utf8(incfile),
5503 onlyPath(absFileName())).absFileName();
5504 if (oldloc == newloc)
5505 continue;
5506 // the location of the child file is incorrect.
5507 cbuf->setParent(0);
5508 inset_inc->setChildBuffer(0);
5509 }
5510 // invalidate cache of children
5511 d->children_positions.clear();
5512 d->position_to_children.clear();
5513 }
5514
5515
5516 // If a child has been saved under a different name/path, it might have been
5517 // orphaned. Therefore the master needs to be reset (bug 8161).
checkMasterBuffer()5518 void Buffer::checkMasterBuffer()
5519 {
5520 Buffer const * const master = masterBuffer();
5521 if (master == this)
5522 return;
5523
5524 // necessary to re-register the child (bug 5873)
5525 // FIXME: clean up updateMacros (here, only
5526 // child registering is needed).
5527 master->updateMacros();
5528 // (re)set master as master buffer, but only
5529 // if we are a real child
5530 if (master->isChild(this))
5531 setParent(master);
5532 else
5533 setParent(0);
5534 }
5535
5536
includedFilePath(string const & name,string const & ext) const5537 string Buffer::includedFilePath(string const & name, string const & ext) const
5538 {
5539 if (d->old_position.empty() ||
5540 equivalent(FileName(d->old_position), FileName(filePath())))
5541 return name;
5542
5543 bool isabsolute = FileName::isAbsolute(name);
5544 // both old_position and filePath() end with a path separator
5545 string absname = isabsolute ? name : d->old_position + name;
5546
5547 // if old_position is set to origin, we need to do the equivalent of
5548 // getReferencedFileName() (see readDocument())
5549 if (!isabsolute && d->old_position == params().origin) {
5550 FileName const test(addExtension(filePath() + name, ext));
5551 if (test.exists())
5552 absname = filePath() + name;
5553 }
5554
5555 if (!FileName(addExtension(absname, ext)).exists())
5556 return name;
5557
5558 if (isabsolute)
5559 return to_utf8(makeRelPath(from_utf8(name), from_utf8(filePath())));
5560
5561 return to_utf8(makeRelPath(from_utf8(FileName(absname).realPath()),
5562 from_utf8(filePath())));
5563 }
5564
5565
setChangesPresent(bool b) const5566 void Buffer::setChangesPresent(bool b) const
5567 {
5568 d->tracked_changes_present_ = b;
5569 }
5570
5571
areChangesPresent() const5572 bool Buffer::areChangesPresent() const
5573 {
5574 return d->tracked_changes_present_;
5575 }
5576
5577
updateChangesPresent() const5578 void Buffer::updateChangesPresent() const
5579 {
5580 LYXERR(Debug::CHANGES, "Buffer::updateChangesPresent");
5581 setChangesPresent(false);
5582 ParConstIterator it = par_iterator_begin();
5583 ParConstIterator const end = par_iterator_end();
5584 for (; !areChangesPresent() && it != end; ++it)
5585 it->addChangesToBuffer(*this);
5586 }
5587
5588
refreshFileMonitor()5589 void Buffer::Impl::refreshFileMonitor()
5590 {
5591 if (file_monitor_ && file_monitor_->filename() == filename.absFileName()) {
5592 file_monitor_->refresh();
5593 return;
5594 }
5595
5596 // The previous file monitor is invalid
5597 // This also destroys the previous file monitor and all its connections
5598 file_monitor_ = FileSystemWatcher::monitor(filename);
5599 // file_monitor_ will be destroyed with *this, so it is not going to call a
5600 // destroyed object method.
5601 file_monitor_->connect([this](bool exists) {
5602 fileExternallyModified(exists);
5603 });
5604 }
5605
5606
fileExternallyModified(bool const exists)5607 void Buffer::Impl::fileExternallyModified(bool const exists)
5608 {
5609 // ignore notifications after our own saving operations
5610 if (checksum_ == filename.checksum()) {
5611 LYXERR(Debug::FILES, "External modification but "
5612 "checksum unchanged: " << filename);
5613 return;
5614 }
5615 // If the file has been deleted, only mark the file as dirty since it is
5616 // pointless to prompt for reloading. If later a file is moved into this
5617 // location, then the externally modified warning will appear then.
5618 if (exists)
5619 externally_modified_ = true;
5620 // Update external modification notification.
5621 // Dirty buffers must be visible at all times.
5622 if (wa_ && wa_->unhide(owner_))
5623 wa_->updateTitles();
5624 else
5625 // Unable to unhide the buffer (e.g. no GUI or not current View)
5626 lyx_clean = true;
5627 }
5628
5629
notifiesExternalModification() const5630 bool Buffer::notifiesExternalModification() const
5631 {
5632 return d->externally_modified_;
5633 }
5634
5635
clearExternalModification() const5636 void Buffer::clearExternalModification() const
5637 {
5638 d->externally_modified_ = false;
5639 if (d->wa_)
5640 d->wa_->updateTitles();
5641 }
5642
5643
5644 } // namespace lyx
5645