3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Stefan Schimanski
9 * Full author contact details are available in file CREDITS.
17 #include "BiblioInfo.h"
18 #include "BranchList.h"
19 #include "buffer_funcs.h"
20 #include "BufferList.h"
21 #include "BufferParams.h"
24 #include "Converter.h"
26 #include "DocIterator.h"
27 #include "EmbeddedFiles.h"
29 #include "ErrorList.h"
32 #include "FuncRequest.h"
33 #include "InsetIterator.h"
34 #include "InsetList.h"
36 #include "LaTeXFeatures.h"
40 #include "LyXAction.h"
44 #include "output_docbook.h"
46 #include "output_latex.h"
47 #include "output_plaintext.h"
48 #include "paragraph_funcs.h"
49 #include "Paragraph.h"
50 #include "ParagraphParameters.h"
51 #include "ParIterator.h"
52 #include "PDFOptions.h"
56 #include "TexStream.h"
57 #include "TextClassList.h"
59 #include "TocBackend.h"
61 #include "VCBackend.h"
64 #include "insets/InsetBibitem.h"
65 #include "insets/InsetBibtex.h"
66 #include "insets/InsetInclude.h"
67 #include "insets/InsetText.h"
69 #include "mathed/MacroTable.h"
70 #include "mathed/MathMacroTemplate.h"
71 #include "mathed/MathSupport.h"
73 #include "frontends/alert.h"
74 #include "frontends/Delegates.h"
75 #include "frontends/WorkAreaManager.h"
76 #include "frontends/FileDialog.h"
78 #include "graphics/Previews.h"
80 #include "support/convert.h"
81 #include "support/debug.h"
82 #include "support/FileFilterList.h"
83 #include "support/filetools.h"
84 #include "support/Forkedcall.h"
85 #include "support/gettext.h"
86 #include "support/gzstream.h"
87 #include "support/lstrings.h"
88 #include "support/lyxalgo.h"
89 #include "support/lyxlib.h"
90 #include "support/os.h"
91 #include "support/Path.h"
92 #include "support/textutils.h"
93 #include "support/types.h"
95 #if !defined (HAVE_FORK)
99 #include <boost/bind.hpp>
100 #include <boost/shared_ptr.hpp>
110 using std::make_pair;
115 using std::ostringstream;
126 using support::addName;
127 using support::bformat;
128 using support::changeExtension;
129 using support::cmd_ret;
130 using support::createBufferTmpDir;
131 using support::FileName;
132 using support::libFileSearch;
133 using support::latex_path;
134 using support::ltrim;
135 using support::makeAbsPath;
136 using support::makeDisplayPath;
137 using support::makeLatexName;
138 using support::onlyFilename;
139 using support::onlyPath;
140 using support::quoteName;
141 using support::removeAutosaveFile;
142 using support::rename;
143 using support::runCommand;
144 using support::split;
145 using support::subst;
146 using support::tempName;
149 using support::suffixIs;
151 namespace Alert = frontend::Alert;
152 namespace os = support::os;
156 int const LYX_FORMAT = 303; // Uwe: Serbocroatian
161 typedef std::map<string, bool> DepClean;
166 Impl(Buffer & parent, FileName const & file, bool readonly);
173 /// need to regenerate .tex?
177 mutable bool lyx_clean;
179 /// is autosave needed?
180 mutable bool bak_clean;
182 /// is this a unnamed file (New...)?
188 /// name of the file the buffer is associated with.
191 /** Set to true only when the file is fully loaded.
192 * Used to prevent the premature generation of previews
193 * and by the citation inset.
195 bool file_fully_loaded;
197 /// our Text that should be wrapped in an InsetText
201 TocBackend toc_backend;
204 typedef std::map<unsigned int, MacroData, std::greater<int> > PositionToMacroMap;
205 typedef std::map<docstring, PositionToMacroMap> NameToPositionMacroMap;
206 NameToPositionMacroMap macros;
208 /// Container for all sort of Buffer dependant errors.
209 map<string, ErrorList> errorLists;
211 /// all embedded files of this buffer
212 EmbeddedFiles embedded_files;
214 /// timestamp and checksum used to test if the file has been externally
215 /// modified. (Used to properly enable 'File->Revert to saved', bug 4114).
217 unsigned long checksum_;
220 frontend::WorkAreaManager * wa_;
227 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
228 : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
229 filename(file), file_fully_loaded(false), inset(params),
230 toc_backend(&parent), embedded_files(&parent), timestamp_(0),
231 checksum_(0), wa_(0), undo_(parent)
233 inset.setAutoBreakRows(true);
234 lyxvc.setBuffer(&parent);
235 temppath = createBufferTmpDir();
237 // FIXME: And now do something if temppath == string(), because we
238 // assume from now on that temppath points to a valid temp dir.
239 // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html
242 wa_ = new frontend::WorkAreaManager;
246 Buffer::Buffer(string const & file, bool readonly)
247 : pimpl_(new Impl(*this, FileName(file), readonly)), gui_(0)
249 LYXERR(Debug::INFO, "Buffer::Buffer()");
255 LYXERR(Debug::INFO, "Buffer::~Buffer()");
256 // here the buffer should take care that it is
257 // saved properly, before it goes into the void.
259 Buffer * master = masterBuffer();
260 if (master != this && use_gui)
261 // We are closing buf which was a child document so we
262 // must update the labels and section numbering of its master
264 updateLabels(*master);
266 if (!temppath().empty() && !FileName(temppath()).destroyDirectory()) {
267 Alert::warning(_("Could not remove temporary directory"),
268 bformat(_("Could not remove the temporary directory %1$s"),
269 from_utf8(temppath())));
272 // Remove any previewed LaTeX snippets associated with this buffer.
273 graphics::Previews::get().removeLoader(*this);
276 pimpl_->wa_->closeAll();
283 void Buffer::changed() const
286 pimpl_->wa_->redrawAll();
290 frontend::WorkAreaManager & Buffer::workAreaManager() const
292 BOOST_ASSERT(pimpl_->wa_);
297 Text & Buffer::text() const
299 return const_cast<Text &>(pimpl_->inset.text_);
303 Inset & Buffer::inset() const
305 return const_cast<InsetText &>(pimpl_->inset);
309 BufferParams & Buffer::params()
311 return pimpl_->params;
315 BufferParams const & Buffer::params() const
317 return pimpl_->params;
321 ParagraphList & Buffer::paragraphs()
323 return text().paragraphs();
327 ParagraphList const & Buffer::paragraphs() const
329 return text().paragraphs();
333 LyXVC & Buffer::lyxvc()
335 return pimpl_->lyxvc;
339 LyXVC const & Buffer::lyxvc() const
341 return pimpl_->lyxvc;
345 string const & Buffer::temppath() const
347 return pimpl_->temppath;
351 TexRow & Buffer::texrow()
353 return pimpl_->texrow;
357 TexRow const & Buffer::texrow() const
359 return pimpl_->texrow;
363 TocBackend & Buffer::tocBackend()
365 return pimpl_->toc_backend;
369 TocBackend const & Buffer::tocBackend() const
371 return pimpl_->toc_backend;
375 EmbeddedFiles & Buffer::embeddedFiles()
377 return pimpl_->embedded_files;
381 EmbeddedFiles const & Buffer::embeddedFiles() const
383 return pimpl_->embedded_files;
387 Undo & Buffer::undo()
389 return pimpl_->undo_;
393 string Buffer::latexName(bool const no_path) const
395 FileName latex_name = makeLatexName(pimpl_->filename);
396 return no_path ? latex_name.onlyFileName()
397 : latex_name.absFilename();
401 string Buffer::logName(LogType * type) const
403 string const filename = latexName(false);
405 if (filename.empty()) {
411 string const path = temppath();
413 FileName const fname(addName(temppath(),
414 onlyFilename(changeExtension(filename,
416 FileName const bname(
417 addName(path, onlyFilename(
418 changeExtension(filename,
419 formats.extension("literate") + ".out"))));
421 // If no Latex log or Build log is newer, show Build log
423 if (bname.exists() &&
424 (!fname.exists() || fname.lastModified() < bname.lastModified())) {
425 LYXERR(Debug::FILES, "Log name calculated as: " << bname);
428 return bname.absFilename();
430 LYXERR(Debug::FILES, "Log name calculated as: " << fname);
433 return fname.absFilename();
437 void Buffer::setReadonly(bool const flag)
439 if (pimpl_->read_only != flag) {
440 pimpl_->read_only = flag;
446 void Buffer::setFileName(string const & newfile)
448 pimpl_->filename = makeAbsPath(newfile);
449 setReadonly(pimpl_->filename.isReadOnly());
454 int Buffer::readHeader(Lexer & lex)
456 int unknown_tokens = 0;
458 int begin_header_line = -1;
460 // Initialize parameters that may be/go lacking in header:
461 params().branchlist().clear();
462 params().preamble.erase();
463 params().options.erase();
464 params().float_placement.erase();
465 params().paperwidth.erase();
466 params().paperheight.erase();
467 params().leftmargin.erase();
468 params().rightmargin.erase();
469 params().topmargin.erase();
470 params().bottommargin.erase();
471 params().headheight.erase();
472 params().headsep.erase();
473 params().footskip.erase();
474 params().listings_params.clear();
475 params().clearLayoutModules();
476 params().pdfoptions().clear();
478 for (int i = 0; i < 4; ++i) {
479 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
480 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
483 ErrorList & errorList = pimpl_->errorLists["Parse"];
487 string const token = lex.getString();
492 if (token == "\\end_header")
496 if (token == "\\begin_header") {
497 begin_header_line = line;
501 LYXERR(Debug::PARSER, "Handling document header token: `"
504 string unknown = params().readToken(lex, token, pimpl_->filename.onlyPath());
505 if (!unknown.empty()) {
506 if (unknown[0] != '\\' && token == "\\textclass") {
507 Alert::warning(_("Unknown document class"),
508 bformat(_("Using the default document class, because the "
509 "class %1$s is unknown."), from_utf8(unknown)));
512 docstring const s = bformat(_("Unknown token: "
516 errorList.push_back(ErrorItem(_("Document header error"),
521 if (begin_header_line) {
522 docstring const s = _("\\begin_header is missing");
523 errorList.push_back(ErrorItem(_("Document header error"),
527 return unknown_tokens;
532 // changed to be public and have one parameter
533 // Returns false if "\end_document" is not read (Asger)
534 bool Buffer::readDocument(Lexer & lex)
536 ErrorList & errorList = pimpl_->errorLists["Parse"];
540 string const token = lex.getString();
541 if (token != "\\begin_document") {
542 docstring const s = _("\\begin_document is missing");
543 errorList.push_back(ErrorItem(_("Document header error"),
547 // we are reading in a brand new document
548 BOOST_ASSERT(paragraphs().empty());
551 TextClass const & baseClass = textclasslist[params().getBaseClass()];
552 if (!baseClass.load(filePath())) {
553 string theclass = baseClass.name();
554 Alert::error(_("Can't load document class"), bformat(
555 _("Using the default document class, because the "
556 "class %1$s could not be loaded."), from_utf8(theclass)));
557 params().setBaseClass(defaultTextclass());
560 if (params().outputChanges) {
561 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
562 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
563 LaTeXFeatures::isAvailable("xcolor");
565 if (!dvipost && !xcolorsoul) {
566 Alert::warning(_("Changes not shown in LaTeX output"),
567 _("Changes will not be highlighted in LaTeX output, "
568 "because neither dvipost nor xcolor/soul are installed.\n"
569 "Please install these packages or redefine "
570 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
571 } else if (!xcolorsoul) {
572 Alert::warning(_("Changes not shown in LaTeX output"),
573 _("Changes will not be highlighted in LaTeX output "
574 "when using pdflatex, because xcolor and soul are not installed.\n"
575 "Please install both packages or redefine "
576 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
581 bool const res = text().read(*this, lex, errorList);
582 for_each(text().paragraphs().begin(),
583 text().paragraphs().end(),
584 bind(&Paragraph::setInsetOwner, _1, &inset()));
590 // needed to insert the selection
591 void Buffer::insertStringAsLines(ParagraphList & pars,
592 pit_type & pit, pos_type & pos,
593 Font const & fn, docstring const & str, bool autobreakrows)
597 // insert the string, don't insert doublespace
598 bool space_inserted = true;
599 for (docstring::const_iterator cit = str.begin();
600 cit != str.end(); ++cit) {
601 Paragraph & par = pars[pit];
603 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
604 breakParagraph(params(), pars, pit, pos,
605 par.layout()->isEnvironment());
608 space_inserted = true;
612 // do not insert consecutive spaces if !free_spacing
613 } else if ((*cit == ' ' || *cit == '\t') &&
614 space_inserted && !par.isFreeSpacing()) {
616 } else if (*cit == '\t') {
617 if (!par.isFreeSpacing()) {
618 // tabs are like spaces here
619 par.insertChar(pos, ' ', font, params().trackChanges);
621 space_inserted = true;
623 const pos_type n = 8 - pos % 8;
624 for (pos_type i = 0; i < n; ++i) {
625 par.insertChar(pos, ' ', font, params().trackChanges);
628 space_inserted = true;
630 } else if (!isPrintable(*cit)) {
631 // Ignore unprintables
634 // just insert the character
635 par.insertChar(pos, *cit, font, params().trackChanges);
637 space_inserted = (*cit == ' ');
644 bool Buffer::readString(std::string const & s)
646 params().compressed = false;
648 // remove dummy empty par
649 paragraphs().clear();
651 std::istringstream is(s);
653 FileName const name(tempName());
654 switch (readFile(lex, name, true)) {
658 // We need to call lyx2lyx, so write the input to a file
659 std::ofstream os(name.toFilesystemEncoding().c_str());
662 return readFile(name);
672 bool Buffer::readFile(FileName const & filename)
674 FileName fname(filename);
675 // Check if the file is compressed.
676 string format = filename.guessFormatFromContents();
677 if (format == "zip") {
678 // decompress to a temp directory
679 LYXERR(Debug::FILES, filename << " is in zip format. Unzip to " << temppath());
680 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
682 FileName lyxfile(addName(temppath(), "content.lyx"));
683 // if both manifest.txt and file.lyx exist, this is am embedded file
684 if (lyxfile.exists()) {
685 params().embedded = true;
689 // The embedded lyx file can also be compressed, for backward compatibility
690 format = fname.guessFormatFromContents();
691 if (format == "gzip" || format == "zip" || format == "compress")
692 params().compressed = true;
694 // remove dummy empty par
695 paragraphs().clear();
698 if (readFile(lex, fname) != success)
705 bool Buffer::isFullyLoaded() const
707 return pimpl_->file_fully_loaded;
711 void Buffer::setFullyLoaded(bool value)
713 pimpl_->file_fully_loaded = value;
717 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
720 BOOST_ASSERT(!filename.empty());
723 Alert::error(_("Document could not be read"),
724 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
729 string const token = lex.getString();
732 Alert::error(_("Document could not be read"),
733 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
737 // the first token _must_ be...
738 if (token != "\\lyxformat") {
739 lyxerr << "Token: " << token << endl;
741 Alert::error(_("Document format failure"),
742 bformat(_("%1$s is not a LyX document."),
743 from_utf8(filename.absFilename())));
748 string tmp_format = lex.getString();
749 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
750 // if present remove ".," from string.
751 string::size_type dot = tmp_format.find_first_of(".,");
752 //lyxerr << " dot found at " << dot << endl;
753 if (dot != string::npos)
754 tmp_format.erase(dot, 1);
755 int const file_format = convert<int>(tmp_format);
756 //lyxerr << "format: " << file_format << endl;
758 // save timestamp and checksum of the original disk file, making sure
759 // to not overwrite them with those of the file created in the tempdir
760 // when it has to be converted to the current format.
761 if (!pimpl_->checksum_) {
762 // Save the timestamp and checksum of disk file. If filename is an
763 // emergency file, save the timestamp and sum of the original lyx file
764 // because isExternallyModified will check for this file. (BUG4193)
765 string diskfile = filename.absFilename();
766 if (suffixIs(diskfile, ".emergency"))
767 diskfile = diskfile.substr(0, diskfile.size() - 10);
768 saveCheckSum(FileName(diskfile));
771 if (file_format != LYX_FORMAT) {
774 // lyx2lyx would fail
777 FileName const tmpfile(tempName());
778 if (tmpfile.empty()) {
779 Alert::error(_("Conversion failed"),
780 bformat(_("%1$s is from a different"
781 " version of LyX, but a temporary"
782 " file for converting it could"
784 from_utf8(filename.absFilename())));
787 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
788 if (lyx2lyx.empty()) {
789 Alert::error(_("Conversion script not found"),
790 bformat(_("%1$s is from a different"
791 " version of LyX, but the"
792 " conversion script lyx2lyx"
793 " could not be found."),
794 from_utf8(filename.absFilename())));
797 ostringstream command;
798 command << os::python()
799 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
800 << " -t " << convert<string>(LYX_FORMAT)
801 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
802 << ' ' << quoteName(filename.toFilesystemEncoding());
803 string const command_str = command.str();
805 LYXERR(Debug::INFO, "Running '" << command_str << '\'');
807 cmd_ret const ret = runCommand(command_str);
808 if (ret.first != 0) {
809 Alert::error(_("Conversion script failed"),
810 bformat(_("%1$s is from a different version"
811 " of LyX, but the lyx2lyx script"
812 " failed to convert it."),
813 from_utf8(filename.absFilename())));
816 bool const ret = readFile(tmpfile);
817 // Do stuff with tmpfile name and buffer name here.
818 return ret ? success : failure;
823 if (readDocument(lex)) {
824 Alert::error(_("Document format failure"),
825 bformat(_("%1$s ended unexpectedly, which means"
826 " that it is probably corrupted."),
827 from_utf8(filename.absFilename())));
830 pimpl_->file_fully_loaded = true;
835 // Should probably be moved to somewhere else: BufferView? LyXView?
836 bool Buffer::save() const
838 // We don't need autosaves in the immediate future. (Asger)
839 resetAutosaveTimers();
841 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
844 bool madeBackup = false;
846 // make a backup if the file already exists
847 if (lyxrc.make_backup && fileName().exists()) {
848 backupName = FileName(absFileName() + '~');
849 if (!lyxrc.backupdir_path.empty()) {
850 string const mangledName =
851 subst(subst(backupName.absFilename(), '/', '!'), ':', '!');
852 backupName = FileName(addName(lyxrc.backupdir_path,
855 if (fileName().copyTo(backupName, true)) {
858 Alert::error(_("Backup failure"),
859 bformat(_("Cannot create backup file %1$s.\n"
860 "Please check whether the directory exists and is writeable."),
861 from_utf8(backupName.absFilename())));
862 //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
866 // ask if the disk file has been externally modified (use checksum method)
867 if (fileName().exists() && isExternallyModified(checksum_method)) {
868 docstring const file = makeDisplayPath(absFileName(), 20);
869 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
870 "you want to overwrite this file?"), file);
871 int const ret = Alert::prompt(_("Overwrite modified file?"),
872 text, 1, 1, _("&Overwrite"), _("&Cancel"));
877 if (writeFile(pimpl_->filename)) {
879 removeAutosaveFile(absFileName());
880 saveCheckSum(pimpl_->filename);
883 // Saving failed, so backup is not backup
885 rename(backupName, pimpl_->filename);
891 bool Buffer::writeFile(FileName const & fname) const
893 if (pimpl_->read_only && fname == pimpl_->filename)
899 if (params().embedded)
900 // first write the .lyx file to the temporary directory
901 content = FileName(addName(temppath(), "content.lyx"));
905 if (params().compressed) {
906 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
912 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
919 if (retval && params().embedded) {
920 // write file.lyx and all the embedded files to the zip file fname
921 // if embedding is enabled
922 return pimpl_->embedded_files.writeFile(fname);
928 bool Buffer::write(ostream & ofs) const
931 // Use the standard "C" locale for file output.
932 ofs.imbue(std::locale::classic());
935 // The top of the file should not be written by params().
937 // write out a comment in the top of the file
938 ofs << "#LyX " << lyx_version
939 << " created this file. For more info see http://www.lyx.org/\n"
940 << "\\lyxformat " << LYX_FORMAT << "\n"
941 << "\\begin_document\n";
944 /// For each author, set 'used' to true if there is a change
945 /// by this author in the document; otherwise set it to 'false'.
946 AuthorList::Authors::const_iterator a_it = params().authors().begin();
947 AuthorList::Authors::const_iterator a_end = params().authors().end();
948 for (; a_it != a_end; ++a_it)
949 a_it->second.setUsed(false);
951 ParIterator const end = par_iterator_end();
952 ParIterator it = par_iterator_begin();
953 for ( ; it != end; ++it)
954 it->checkAuthors(params().authors());
956 // now write out the buffer parameters.
957 ofs << "\\begin_header\n";
958 params().writeFile(ofs);
959 ofs << "\\end_header\n";
962 ofs << "\n\\begin_body\n";
963 text().write(*this, ofs);
964 ofs << "\n\\end_body\n";
966 // Write marker that shows file is complete
967 ofs << "\\end_document" << endl;
969 // Shouldn't really be needed....
972 // how to check if close went ok?
973 // Following is an attempt... (BE 20001011)
975 // good() returns false if any error occured, including some
977 // bad() returns true if something bad happened in the buffer,
978 // which should include file system full errors.
983 lyxerr << "File was not closed properly." << endl;
990 bool Buffer::makeLaTeXFile(FileName const & fname,
991 string const & original_path,
992 OutputParams const & runparams,
993 bool output_preamble, bool output_body)
995 string const encoding = runparams.encoding->iconvName();
996 LYXERR(Debug::LATEX, "makeLaTeXFile encoding: " << encoding << "...");
998 odocfstream ofs(encoding);
999 if (!openFileWrite(ofs, fname))
1002 //TexStream ts(ofs.rdbuf(), &texrow());
1004 bool failed_export = false;
1007 writeLaTeXSource(ofs, original_path,
1008 runparams, output_preamble, output_body);
1010 catch (iconv_codecvt_facet_exception & e) {
1011 lyxerr << "Caught iconv exception: " << e.what() << endl;
1012 failed_export = true;
1014 catch (std::exception const & e) {
1015 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1016 failed_export = true;
1019 lyxerr << "Caught some really weird exception..." << endl;
1020 LyX::cref().emergencyCleanup();
1026 failed_export = true;
1027 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1030 if (failed_export) {
1031 Alert::error(_("Encoding error"),
1032 _("Some characters of your document are probably not "
1033 "representable in the chosen encoding.\n"
1034 "Changing the document encoding to utf8 could help."));
1041 void Buffer::writeLaTeXSource(odocstream & os,
1042 string const & original_path,
1043 OutputParams const & runparams_in,
1044 bool const output_preamble, bool const output_body)
1046 OutputParams runparams = runparams_in;
1048 // validate the buffer.
1049 LYXERR(Debug::LATEX, " Validating buffer...");
1050 LaTeXFeatures features(*this, params(), runparams);
1052 LYXERR(Debug::LATEX, " Buffer validation done.");
1054 // The starting paragraph of the coming rows is the
1055 // first paragraph of the document. (Asger)
1056 if (output_preamble && runparams.nice) {
1057 os << "%% LyX " << lyx_version << " created this file. "
1058 "For more info, see http://www.lyx.org/.\n"
1059 "%% Do not edit unless you really know what "
1064 LYXERR(Debug::INFO, "lyx document header finished");
1065 // There are a few differences between nice LaTeX and usual files:
1066 // usual is \batchmode and has a
1067 // special input@path to allow the including of figures
1068 // with either \input or \includegraphics (what figinsets do).
1069 // input@path is set when the actual parameter
1070 // original_path is set. This is done for usual tex-file, but not
1071 // for nice-latex-file. (Matthias 250696)
1072 // Note that input@path is only needed for something the user does
1073 // in the preamble, included .tex files or ERT, files included by
1074 // LyX work without it.
1075 if (output_preamble) {
1076 if (!runparams.nice) {
1077 // code for usual, NOT nice-latex-file
1078 os << "\\batchmode\n"; // changed
1079 // from \nonstopmode
1082 if (!original_path.empty()) {
1084 // We don't know the encoding of inputpath
1085 docstring const inputpath = from_utf8(latex_path(original_path));
1086 os << "\\makeatletter\n"
1087 << "\\def\\input@path{{"
1088 << inputpath << "/}}\n"
1089 << "\\makeatother\n";
1095 // Write the preamble
1096 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1102 os << "\\begin{document}\n";
1104 } // output_preamble
1106 texrow().start(paragraphs().begin()->id(), 0);
1108 LYXERR(Debug::INFO, "preamble finished, now the body.");
1110 if (!lyxrc.language_auto_begin &&
1111 !params().language->babel().empty()) {
1113 os << from_utf8(subst(lyxrc.language_command_begin,
1115 params().language->babel()))
1120 Encoding const & encoding = params().encoding();
1121 if (encoding.package() == Encoding::CJK) {
1122 // Open a CJK environment, since in contrast to the encodings
1123 // handled by inputenc the document encoding is not set in
1124 // the preamble if it is handled by CJK.sty.
1125 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1130 // if we are doing a real file with body, even if this is the
1131 // child of some other buffer, let's cut the link here.
1132 // This happens for example if only a child document is printed.
1133 string save_parentname;
1134 if (output_preamble) {
1135 save_parentname = params().parentname;
1136 params().parentname.erase();
1139 loadChildDocuments();
1142 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1144 // Restore the parenthood if needed
1145 if (output_preamble)
1146 params().parentname = save_parentname;
1148 // add this just in case after all the paragraphs
1152 if (encoding.package() == Encoding::CJK) {
1153 // Close the open CJK environment.
1154 // latexParagraphs will have opened one even if the last text
1156 os << "\\end{CJK}\n";
1160 if (!lyxrc.language_auto_end &&
1161 !params().language->babel().empty()) {
1162 os << from_utf8(subst(lyxrc.language_command_end,
1164 params().language->babel()))
1169 if (output_preamble) {
1170 os << "\\end{document}\n";
1172 LYXERR(Debug::LATEX, "makeLaTeXFile...done");
1174 LYXERR(Debug::LATEX, "LaTeXFile for inclusion made.");
1176 runparams_in.encoding = runparams.encoding;
1178 // Just to be sure. (Asger)
1181 LYXERR(Debug::INFO, "Finished making LaTeX file.");
1182 LYXERR(Debug::INFO, "Row count was " << texrow().rows() - 1 << '.');
1186 bool Buffer::isLatex() const
1188 return params().getTextClass().outputType() == LATEX;
1192 bool Buffer::isLiterate() const
1194 return params().getTextClass().outputType() == LITERATE;
1198 bool Buffer::isDocBook() const
1200 return params().getTextClass().outputType() == DOCBOOK;
1204 void Buffer::makeDocBookFile(FileName const & fname,
1205 OutputParams const & runparams,
1206 bool const body_only)
1208 LYXERR(Debug::LATEX, "makeDocBookFile...");
1212 if (!openFileWrite(ofs, fname))
1215 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1219 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1223 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1224 OutputParams const & runparams,
1225 bool const only_body)
1227 LaTeXFeatures features(*this, params(), runparams);
1232 TextClass const & tclass = params().getTextClass();
1233 string const top_element = tclass.latexname();
1236 if (runparams.flavor == OutputParams::XML)
1237 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1240 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1243 if (! tclass.class_header().empty())
1244 os << from_ascii(tclass.class_header());
1245 else if (runparams.flavor == OutputParams::XML)
1246 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1247 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1249 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1251 docstring preamble = from_utf8(params().preamble);
1252 if (runparams.flavor != OutputParams::XML ) {
1253 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1254 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1255 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1256 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1259 string const name = runparams.nice
1260 ? changeExtension(absFileName(), ".sgml") : fname;
1261 preamble += features.getIncludedFiles(name);
1262 preamble += features.getLyXSGMLEntities();
1264 if (!preamble.empty()) {
1265 os << "\n [ " << preamble << " ]";
1270 string top = top_element;
1272 if (runparams.flavor == OutputParams::XML)
1273 top += params().language->code();
1275 top += params().language->code().substr(0,2);
1278 if (!params().options.empty()) {
1280 top += params().options;
1283 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1284 << " file was created by LyX " << lyx_version
1285 << "\n See http://www.lyx.org/ for more information -->\n";
1287 params().getTextClass().counters().reset();
1289 loadChildDocuments();
1291 sgml::openTag(os, top);
1293 docbookParagraphs(paragraphs(), *this, os, runparams);
1294 sgml::closeTag(os, top_element);
1298 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1299 // Other flags: -wall -v0 -x
1300 int Buffer::runChktex()
1304 // get LaTeX-Filename
1305 FileName const path(temppath());
1306 string const name = addName(path.absFilename(), latexName());
1307 string const org_path = filePath();
1309 support::PathChanger p(path); // path to LaTeX file
1310 message(_("Running chktex..."));
1312 // Generate the LaTeX file if neccessary
1313 OutputParams runparams(¶ms().encoding());
1314 runparams.flavor = OutputParams::LATEX;
1315 runparams.nice = false;
1316 makeLaTeXFile(FileName(name), org_path, runparams);
1319 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1320 int const res = chktex.run(terr); // run chktex
1323 Alert::error(_("chktex failure"),
1324 _("Could not run chktex successfully."));
1325 } else if (res > 0) {
1326 ErrorList & errlist = pimpl_->errorLists["ChkTeX"];
1328 bufferErrors(terr, errlist);
1339 void Buffer::validate(LaTeXFeatures & features) const
1341 TextClass const & tclass = params().getTextClass();
1343 if (params().outputChanges) {
1344 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1345 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1346 LaTeXFeatures::isAvailable("xcolor");
1348 if (features.runparams().flavor == OutputParams::LATEX) {
1350 features.require("ct-dvipost");
1351 features.require("dvipost");
1352 } else if (xcolorsoul) {
1353 features.require("ct-xcolor-soul");
1354 features.require("soul");
1355 features.require("xcolor");
1357 features.require("ct-none");
1359 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1361 features.require("ct-xcolor-soul");
1362 features.require("soul");
1363 features.require("xcolor");
1364 features.require("pdfcolmk"); // improves color handling in PDF output
1366 features.require("ct-none");
1371 // AMS Style is at document level
1372 if (params().use_amsmath == BufferParams::package_on
1373 || tclass.provides("amsmath"))
1374 features.require("amsmath");
1375 if (params().use_esint == BufferParams::package_on)
1376 features.require("esint");
1378 loadChildDocuments();
1380 for_each(paragraphs().begin(), paragraphs().end(),
1381 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1383 // the bullet shapes are buffer level not paragraph level
1384 // so they are tested here
1385 for (int i = 0; i < 4; ++i) {
1386 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1387 int const font = params().user_defined_bullet(i).getFont();
1389 int const c = params()
1390 .user_defined_bullet(i)
1397 features.require("latexsym");
1399 } else if (font == 1) {
1400 features.require("amssymb");
1401 } else if ((font >= 2 && font <= 5)) {
1402 features.require("pifont");
1407 if (lyxerr.debugging(Debug::LATEX)) {
1408 features.showStruct();
1413 void Buffer::getLabelList(vector<docstring> & list) const
1415 /// if this is a child document and the parent is already loaded
1416 /// Use the parent's list instead [ale990407]
1417 Buffer const * tmp = masterBuffer();
1419 lyxerr << "masterBuffer() failed!" << endl;
1423 tmp->getLabelList(list);
1427 loadChildDocuments();
1429 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1430 it.nextInset()->getLabelList(*this, list);
1434 void Buffer::updateBibfilesCache()
1436 // if this is a child document and the parent is already loaded
1437 // update the parent's cache instead
1438 Buffer * tmp = masterBuffer();
1441 tmp->updateBibfilesCache();
1445 bibfilesCache_.clear();
1446 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1447 if (it->lyxCode() == BIBTEX_CODE) {
1448 InsetBibtex const & inset =
1449 static_cast<InsetBibtex const &>(*it);
1450 vector<FileName> const bibfiles = inset.getFiles(*this);
1451 bibfilesCache_.insert(bibfilesCache_.end(),
1454 } else if (it->lyxCode() == INCLUDE_CODE) {
1455 InsetInclude & inset =
1456 static_cast<InsetInclude &>(*it);
1457 inset.updateBibfilesCache(*this);
1458 vector<FileName> const & bibfiles =
1459 inset.getBibfilesCache(*this);
1460 bibfilesCache_.insert(bibfilesCache_.end(),
1468 vector<FileName> const & Buffer::getBibfilesCache() const
1470 // if this is a child document and the parent is already loaded
1471 // use the parent's cache instead
1472 Buffer const * tmp = masterBuffer();
1475 return tmp->getBibfilesCache();
1477 // We update the cache when first used instead of at loading time.
1478 if (bibfilesCache_.empty())
1479 const_cast<Buffer *>(this)->updateBibfilesCache();
1481 return bibfilesCache_;
1485 bool Buffer::isDepClean(string const & name) const
1487 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1488 if (it == pimpl_->dep_clean.end())
1494 void Buffer::markDepClean(string const & name)
1496 pimpl_->dep_clean[name] = true;
1500 bool Buffer::dispatch(string const & command, bool * result)
1502 return dispatch(lyxaction.lookupFunc(command), result);
1506 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1508 bool dispatched = true;
1510 switch (func.action) {
1511 case LFUN_BUFFER_EXPORT: {
1512 bool const tmp = doExport(to_utf8(func.argument()), false);
1525 void Buffer::changeLanguage(Language const * from, Language const * to)
1530 for_each(par_iterator_begin(),
1532 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1536 bool Buffer::isMultiLingual() const
1538 ParConstIterator end = par_iterator_end();
1539 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1540 if (it->isMultiLingual(params()))
1547 ParIterator Buffer::getParFromID(int const id) const
1549 ParConstIterator it = par_iterator_begin();
1550 ParConstIterator const end = par_iterator_end();
1553 // John says this is called with id == -1 from undo
1554 lyxerr << "getParFromID(), id: " << id << endl;
1558 for (; it != end; ++it)
1566 bool Buffer::hasParWithID(int const id) const
1568 ParConstIterator const it = getParFromID(id);
1569 return it != par_iterator_end();
1573 ParIterator Buffer::par_iterator_begin()
1575 return lyx::par_iterator_begin(inset());
1579 ParIterator Buffer::par_iterator_end()
1581 return lyx::par_iterator_end(inset());
1585 ParConstIterator Buffer::par_iterator_begin() const
1587 return lyx::par_const_iterator_begin(inset());
1591 ParConstIterator Buffer::par_iterator_end() const
1593 return lyx::par_const_iterator_end(inset());
1597 Language const * Buffer::language() const
1599 return params().language;
1603 docstring const Buffer::B_(string const & l10n) const
1605 return params().B_(l10n);
1609 bool Buffer::isClean() const
1611 return pimpl_->lyx_clean;
1615 bool Buffer::isBakClean() const
1617 return pimpl_->bak_clean;
1621 bool Buffer::isExternallyModified(CheckMethod method) const
1623 BOOST_ASSERT(pimpl_->filename.exists());
1624 // if method == timestamp, check timestamp before checksum
1625 return (method == checksum_method
1626 || pimpl_->timestamp_ != pimpl_->filename.lastModified())
1627 && pimpl_->checksum_ != sum(pimpl_->filename);
1631 void Buffer::saveCheckSum(FileName const & file) const
1633 if (file.exists()) {
1634 pimpl_->timestamp_ = file.lastModified();
1635 pimpl_->checksum_ = sum(file);
1637 // in the case of save to a new file.
1638 pimpl_->timestamp_ = 0;
1639 pimpl_->checksum_ = 0;
1644 void Buffer::markClean() const
1646 if (!pimpl_->lyx_clean) {
1647 pimpl_->lyx_clean = true;
1650 // if the .lyx file has been saved, we don't need an
1652 pimpl_->bak_clean = true;
1656 void Buffer::markBakClean() const
1658 pimpl_->bak_clean = true;
1662 void Buffer::setUnnamed(bool flag)
1664 pimpl_->unnamed = flag;
1668 bool Buffer::isUnnamed() const
1670 return pimpl_->unnamed;
1674 // FIXME: this function should be moved to buffer_pimpl.C
1675 void Buffer::markDirty()
1677 if (pimpl_->lyx_clean) {
1678 pimpl_->lyx_clean = false;
1681 pimpl_->bak_clean = false;
1683 DepClean::iterator it = pimpl_->dep_clean.begin();
1684 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1686 for (; it != end; ++it)
1691 FileName Buffer::fileName() const
1693 return pimpl_->filename;
1697 string Buffer::absFileName() const
1699 return pimpl_->filename.absFilename();
1703 string Buffer::filePath() const
1705 return pimpl_->filename.onlyPath().absFilename();
1709 bool Buffer::isReadonly() const
1711 return pimpl_->read_only;
1715 void Buffer::setParentName(string const & name)
1717 if (name == pimpl_->filename.absFilename())
1718 // Avoids recursive include.
1719 params().parentname.clear();
1721 params().parentname = name;
1725 Buffer const * Buffer::masterBuffer() const
1727 if (!params().parentname.empty()
1728 && theBufferList().exists(params().parentname)) {
1729 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1730 //We need to check if the parent is us...
1731 //FIXME RECURSIVE INCLUDE
1732 //This is not sufficient, since recursive includes could be downstream.
1733 if (buf && buf != this)
1734 return buf->masterBuffer();
1741 Buffer * Buffer::masterBuffer()
1743 if (!params().parentname.empty()
1744 && theBufferList().exists(params().parentname)) {
1745 Buffer * buf = theBufferList().getBuffer(params().parentname);
1746 //We need to check if the parent is us...
1747 //FIXME RECURSIVE INCLUDE
1748 //This is not sufficient, since recursive includes could be downstream.
1749 if (buf && buf != this)
1750 return buf->masterBuffer();
1757 bool Buffer::hasMacro(docstring const & name, Paragraph const & par) const
1759 Impl::PositionToMacroMap::iterator it;
1760 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1761 if (it != pimpl_->macros[name].end())
1764 // If there is a master buffer, query that
1765 const Buffer * master = masterBuffer();
1766 if (master && master != this)
1767 return master->hasMacro(name);
1769 return MacroTable::globalMacros().has(name);
1773 bool Buffer::hasMacro(docstring const & name) const
1775 if( !pimpl_->macros[name].empty() )
1778 // If there is a master buffer, query that
1779 const Buffer * master = masterBuffer();
1780 if (master && master != this)
1781 return master->hasMacro(name);
1783 return MacroTable::globalMacros().has(name);
1787 MacroData const & Buffer::getMacro(docstring const & name,
1788 Paragraph const & par) const
1790 Impl::PositionToMacroMap::iterator it;
1791 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1792 if( it != pimpl_->macros[name].end() )
1795 // If there is a master buffer, query that
1796 const Buffer * master = masterBuffer();
1797 if (master && master != this)
1798 return master->getMacro(name);
1800 return MacroTable::globalMacros().get(name);
1804 MacroData const & Buffer::getMacro(docstring const & name) const
1806 Impl::PositionToMacroMap::iterator it;
1807 it = pimpl_->macros[name].begin();
1808 if( it != pimpl_->macros[name].end() )
1811 // If there is a master buffer, query that
1812 const Buffer * master = masterBuffer();
1813 if (master && master != this)
1814 return master->getMacro(name);
1816 return MacroTable::globalMacros().get(name);
1820 void Buffer::updateMacros()
1822 // start with empty table
1823 pimpl_->macros = Impl::NameToPositionMacroMap();
1825 // Iterate over buffer
1826 ParagraphList & pars = text().paragraphs();
1827 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1828 // set position again
1829 pars[i].setMacrocontextPosition(i);
1831 //lyxerr << "searching main par " << i
1832 // << " for macro definitions" << std::endl;
1833 InsetList const & insets = pars[i].insetList();
1834 InsetList::const_iterator it = insets.begin();
1835 InsetList::const_iterator end = insets.end();
1836 for ( ; it != end; ++it) {
1837 if (it->inset->lyxCode() != MATHMACRO_CODE)
1841 MathMacroTemplate const & macroTemplate
1842 = static_cast<MathMacroTemplate const &>(*it->inset);
1845 if (macroTemplate.validMacro()) {
1846 MacroData macro = macroTemplate.asMacroData();
1849 // call hasMacro here instead of directly querying mc to
1850 // also take the master document into consideration
1851 macro.setRedefinition(hasMacro(macroTemplate.name()));
1853 // register macro (possibly overwrite the previous one of this paragraph)
1854 pimpl_->macros[macroTemplate.name()][i] = macro;
1861 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1864 //FIXME: This does not work for child documents yet.
1865 BOOST_ASSERT(code == CITE_CODE || code == REF_CODE);
1866 // Check if the label 'from' appears more than once
1867 vector<docstring> labels;
1870 if (code == CITE_CODE) {
1872 keys.fillWithBibKeys(this);
1873 BiblioInfo::const_iterator bit = keys.begin();
1874 BiblioInfo::const_iterator bend = keys.end();
1876 for (; bit != bend; ++bit)
1878 labels.push_back(bit->first);
1881 getLabelList(labels);
1882 paramName = "reference";
1885 if (std::count(labels.begin(), labels.end(), from) > 1)
1888 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1889 if (it->lyxCode() == code) {
1890 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1891 docstring const oldValue = inset.getParam(paramName);
1892 if (oldValue == from)
1893 inset.setParam(paramName, to);
1899 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1900 pit_type par_end, bool full_source)
1902 OutputParams runparams(¶ms().encoding());
1903 runparams.nice = true;
1904 runparams.flavor = OutputParams::LATEX;
1905 runparams.linelen = lyxrc.plaintext_linelen;
1906 // No side effect of file copying and image conversion
1907 runparams.dryrun = true;
1911 os << "% " << _("Preview source code") << "\n\n";
1915 writeLaTeXSource(os, filePath(), runparams, true, true);
1917 writeDocBookSource(os, absFileName(), runparams, false);
1920 runparams.par_begin = par_begin;
1921 runparams.par_end = par_end;
1922 if (par_begin + 1 == par_end)
1924 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1928 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1929 convert<docstring>(par_begin),
1930 convert<docstring>(par_end - 1))
1934 // output paragraphs
1936 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1939 docbookParagraphs(paragraphs(), *this, os, runparams);
1945 ErrorList const & Buffer::errorList(string const & type) const
1947 static ErrorList const emptyErrorList;
1948 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1949 if (I == pimpl_->errorLists.end())
1950 return emptyErrorList;
1956 ErrorList & Buffer::errorList(string const & type)
1958 return pimpl_->errorLists[type];
1962 void Buffer::structureChanged() const
1965 gui_->structureChanged();
1969 void Buffer::errors(std::string const & err) const
1976 void Buffer::message(docstring const & msg) const
1983 void Buffer::setBusy(bool on) const
1990 void Buffer::setReadOnly(bool on) const
1993 pimpl_->wa_->setReadOnly(on);
1997 void Buffer::updateTitles() const
2000 pimpl_->wa_->updateTitles();
2004 void Buffer::resetAutosaveTimers() const
2007 gui_->resetAutosaveTimers();
2011 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
2020 class AutoSaveBuffer : public support::ForkedProcess {
2023 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
2024 : buffer_(buffer), fname_(fname) {}
2026 virtual boost::shared_ptr<ForkedProcess> clone() const
2028 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
2033 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
2034 from_utf8(fname_.absFilename())));
2035 return run(DontWait);
2039 virtual int generateChild();
2041 Buffer const & buffer_;
2046 #if !defined (HAVE_FORK)
2050 int AutoSaveBuffer::generateChild()
2052 // tmp_ret will be located (usually) in /tmp
2053 // will that be a problem?
2054 pid_t const pid = fork();
2055 // If you want to debug the autosave
2056 // you should set pid to -1, and comment out the fork.
2057 if (pid == 0 || pid == -1) {
2058 // pid = -1 signifies that lyx was unable
2059 // to fork. But we will do the save
2061 bool failed = false;
2063 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2064 if (!tmp_ret.empty()) {
2065 buffer_.writeFile(tmp_ret);
2066 // assume successful write of tmp_ret
2067 if (!rename(tmp_ret, fname_)) {
2069 // most likely couldn't move between
2070 // filesystems unless write of tmp_ret
2071 // failed so remove tmp file (if it
2073 tmp_ret.removeFile();
2080 // failed to write/rename tmp_ret so try writing direct
2081 if (!buffer_.writeFile(fname_)) {
2082 // It is dangerous to do this in the child,
2083 // but safe in the parent, so...
2084 if (pid == -1) // emit message signal.
2085 buffer_.message(_("Autosave failed!"));
2088 if (pid == 0) { // we are the child so...
2098 // Perfect target for a thread...
2099 void Buffer::autoSave() const
2101 if (isBakClean() || isReadonly()) {
2102 // We don't save now, but we'll try again later
2103 resetAutosaveTimers();
2107 // emit message signal.
2108 message(_("Autosaving current document..."));
2110 // create autosave filename
2111 string fname = filePath();
2113 fname += onlyFilename(absFileName());
2116 AutoSaveBuffer autosave(*this, FileName(fname));
2120 resetAutosaveTimers();
2124 /** Write a buffer to a new file name and rename the buffer
2125 according to the new file name.
2127 This function is e.g. used by menu callbacks and
2128 LFUN_BUFFER_WRITE_AS.
2130 If 'newname' is empty (the default), the user is asked via a
2131 dialog for the buffer's new name and location.
2133 If 'newname' is non-empty and has an absolute path, that is used.
2134 Otherwise the base directory of the buffer is used as the base
2135 for any relative path in 'newname'.
2138 bool Buffer::writeAs(string const & newname)
2140 string fname = absFileName();
2141 string const oldname = fname;
2143 if (newname.empty()) { /// No argument? Ask user through dialog
2146 FileDialog dlg(_("Choose a filename to save document as"),
2147 LFUN_BUFFER_WRITE_AS);
2148 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
2149 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
2151 if (!support::isLyXFilename(fname))
2154 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2156 FileDialog::Result result =
2157 dlg.save(from_utf8(onlyPath(fname)),
2159 from_utf8(onlyFilename(fname)));
2161 if (result.first == FileDialog::Later)
2164 fname = to_utf8(result.second);
2169 // Make sure the absolute filename ends with appropriate suffix
2170 fname = makeAbsPath(fname).absFilename();
2171 if (!support::isLyXFilename(fname))
2175 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2177 if (FileName(fname).exists()) {
2178 docstring const file = makeDisplayPath(fname, 30);
2179 docstring text = bformat(_("The document %1$s already "
2180 "exists.\n\nDo you want to "
2181 "overwrite that document?"),
2183 int const ret = Alert::prompt(_("Overwrite document?"),
2184 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2190 // Ok, change the name of the buffer
2193 bool unnamed = isUnnamed();
2195 saveCheckSum(FileName(fname));
2198 setFileName(oldname);
2199 setUnnamed(unnamed);
2200 saveCheckSum(FileName(oldname));
2204 removeAutosaveFile(oldname);
2209 bool Buffer::menuWrite()
2212 LyX::ref().session().lastFiles().add(FileName(absFileName()));
2216 // FIXME: we don't tell the user *WHY* the save failed !!
2218 docstring const file = makeDisplayPath(absFileName(), 30);
2220 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2221 "Do you want to rename the document and "
2222 "try again?"), file);
2223 int const ret = Alert::prompt(_("Rename and save?"),
2224 text, 0, 1, _("&Rename"), _("&Cancel"));
2233 void Buffer::loadChildDocuments() const
2235 bool parse_error = false;
2237 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2238 if (it->lyxCode() != INCLUDE_CODE)
2240 InsetCommand const & inset = static_cast<InsetCommand const &>(*it);
2241 InsetCommandParams const & ip = inset.params();
2242 Buffer * child = loadIfNeeded(*this, ip);
2245 parse_error |= !child->errorList("Parse").empty();
2246 child->loadChildDocuments();
2249 if (use_gui && masterBuffer() == this)
2250 updateLabels(*this);
2254 string Buffer::bufferFormat() const
2264 bool Buffer::doExport(string const & format, bool put_in_tempdir,
2265 string & result_file)
2267 string backend_format;
2268 OutputParams runparams(¶ms().encoding());
2269 runparams.flavor = OutputParams::LATEX;
2270 runparams.linelen = lyxrc.plaintext_linelen;
2271 vector<string> backs = backends();
2272 if (find(backs.begin(), backs.end(), format) == backs.end()) {
2273 // Get shortest path to format
2274 Graph::EdgePath path;
2275 for (vector<string>::const_iterator it = backs.begin();
2276 it != backs.end(); ++it) {
2277 Graph::EdgePath p = theConverters().getPath(*it, format);
2278 if (!p.empty() && (path.empty() || p.size() < path.size())) {
2279 backend_format = *it;
2284 runparams.flavor = theConverters().getFlavor(path);
2286 Alert::error(_("Couldn't export file"),
2287 bformat(_("No information for exporting the format %1$s."),
2288 formats.prettyName(format)));
2292 backend_format = format;
2293 // FIXME: Don't hardcode format names here, but use a flag
2294 if (backend_format == "pdflatex")
2295 runparams.flavor = OutputParams::PDFLATEX;
2298 string filename = latexName(false);
2299 filename = addName(temppath(), filename);
2300 filename = changeExtension(filename,
2301 formats.extension(backend_format));
2303 // Plain text backend
2304 if (backend_format == "text")
2305 writePlaintextFile(*this, FileName(filename), runparams);
2307 else if (backend_format == "lyx")
2308 writeFile(FileName(filename));
2310 else if (isDocBook()) {
2311 runparams.nice = !put_in_tempdir;
2312 makeDocBookFile(FileName(filename), runparams);
2315 else if (backend_format == format) {
2316 runparams.nice = true;
2317 if (!makeLaTeXFile(FileName(filename), string(), runparams))
2319 } else if (!lyxrc.tex_allows_spaces
2320 && support::contains(filePath(), ' ')) {
2321 Alert::error(_("File name error"),
2322 _("The directory path to the document cannot contain spaces."));
2325 runparams.nice = false;
2326 if (!makeLaTeXFile(FileName(filename), filePath(), runparams))
2330 string const error_type = (format == "program")
2331 ? "Build" : bufferFormat();
2332 string const ext = formats.extension(format);
2333 FileName const tmp_result_file(changeExtension(filename, ext));
2334 bool const success = theConverters().convert(this, FileName(filename),
2335 tmp_result_file, FileName(absFileName()), backend_format, format,
2336 errorList(error_type));
2337 // Emit the signal to show the error list.
2338 if (format != backend_format)
2344 result_file = tmp_result_file.absFilename();
2346 result_file = changeExtension(absFileName(), ext);
2347 // We need to copy referenced files (e. g. included graphics
2348 // if format == "dvi") to the result dir.
2349 vector<ExportedFile> const files =
2350 runparams.exportdata->externalFiles(format);
2351 string const dest = onlyPath(result_file);
2352 CopyStatus status = SUCCESS;
2353 for (vector<ExportedFile>::const_iterator it = files.begin();
2354 it != files.end() && status != CANCEL; ++it) {
2356 formats.getFormatFromFile(it->sourceName);
2357 status = copyFile(fmt, it->sourceName,
2358 makeAbsPath(it->exportName, dest),
2359 it->exportName, status == FORCE);
2361 if (status == CANCEL) {
2362 message(_("Document export cancelled."));
2363 } else if (tmp_result_file.exists()) {
2364 // Finally copy the main file
2365 status = copyFile(format, tmp_result_file,
2366 FileName(result_file), result_file,
2368 message(bformat(_("Document exported as %1$s "
2370 formats.prettyName(format),
2371 makeDisplayPath(result_file)));
2373 // This must be a dummy converter like fax (bug 1888)
2374 message(bformat(_("Document exported as %1$s"),
2375 formats.prettyName(format)));
2383 bool Buffer::doExport(string const & format, bool put_in_tempdir)
2386 return doExport(format, put_in_tempdir, result_file);
2390 bool Buffer::preview(string const & format)
2393 if (!doExport(format, true, result_file))
2395 return formats.view(*this, FileName(result_file), format);
2399 bool Buffer::isExportable(string const & format) const
2401 vector<string> backs = backends();
2402 for (vector<string>::const_iterator it = backs.begin();
2403 it != backs.end(); ++it)
2404 if (theConverters().isReachable(*it, format))
2410 vector<Format const *> Buffer::exportableFormats(bool only_viewable) const
2412 vector<string> backs = backends();
2413 vector<Format const *> result =
2414 theConverters().getReachable(backs[0], only_viewable, true);
2415 for (vector<string>::const_iterator it = backs.begin() + 1;
2416 it != backs.end(); ++it) {
2417 vector<Format const *> r =
2418 theConverters().getReachable(*it, only_viewable, false);
2419 result.insert(result.end(), r.begin(), r.end());
2425 vector<string> Buffer::backends() const
2428 if (params().getTextClass().isTeXClassAvailable()) {
2429 v.push_back(bufferFormat());
2430 // FIXME: Don't hardcode format names here, but use a flag
2431 if (v.back() == "latex")
2432 v.push_back("pdflatex");
2434 v.push_back("text");
2440 bool Buffer::readFileHelper(FileName const & s)
2442 // File information about normal file
2444 docstring const file = makeDisplayPath(s.absFilename(), 50);
2445 docstring text = bformat(_("The specified document\n%1$s"
2446 "\ncould not be read."), file);
2447 Alert::error(_("Could not read document"), text);
2451 // Check if emergency save file exists and is newer.
2452 FileName const e(s.absFilename() + ".emergency");
2454 if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) {
2455 docstring const file = makeDisplayPath(s.absFilename(), 20);
2456 docstring const text =
2457 bformat(_("An emergency save of the document "
2459 "Recover emergency save?"), file);
2460 switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
2461 _("&Recover"), _("&Load Original"),
2465 // the file is not saved if we load the emergency file.
2475 // Now check if autosave file is newer.
2476 FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#');
2478 if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) {
2479 docstring const file = makeDisplayPath(s.absFilename(), 20);
2480 docstring const text =
2481 bformat(_("The backup of the document "
2482 "%1$s is newer.\n\nLoad the "
2483 "backup instead?"), file);
2484 switch (Alert::prompt(_("Load backup?"), text, 0, 2,
2485 _("&Load backup"), _("Load &original"),
2489 // the file is not saved if we load the autosave file.
2493 // Here we delete the autosave
2504 bool Buffer::loadLyXFile(FileName const & s)
2506 if (s.isReadableFile()) {
2507 if (readFileHelper(s)) {
2508 lyxvc().file_found_hook(s);
2509 if (!s.isWritable())
2514 docstring const file = makeDisplayPath(s.absFilename(), 20);
2515 // Here we probably should run
2516 if (LyXVC::file_not_found_hook(s)) {
2517 docstring const text =
2518 bformat(_("Do you want to retrieve the document"
2519 " %1$s from version control?"), file);
2520 int const ret = Alert::prompt(_("Retrieve from version control?"),
2521 text, 0, 1, _("&Retrieve"), _("&Cancel"));
2524 // How can we know _how_ to do the checkout?
2525 // With the current VC support it has to be,
2526 // a RCS file since CVS do not have special ,v files.
2528 return loadLyXFile(s);
2536 void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
2538 TeXErrors::Errors::const_iterator cit = terr.begin();
2539 TeXErrors::Errors::const_iterator end = terr.end();
2541 for (; cit != end; ++cit) {
2544 int errorRow = cit->error_in_line;
2545 bool found = texrow().getIdFromRow(errorRow, id_start,
2551 found = texrow().getIdFromRow(errorRow, id_end, pos_end);
2552 } while (found && id_start == id_end && pos_start == pos_end);
2554 errorList.push_back(ErrorItem(cit->error_desc,
2555 cit->error_text, id_start, pos_start, pos_end));