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"
27 #include "DocIterator.h"
28 #include "EmbeddedFiles.h"
30 #include "ErrorList.h"
33 #include "FuncRequest.h"
35 #include "InsetIterator.h"
36 #include "InsetList.h"
38 #include "LaTeXFeatures.h"
42 #include "LyXAction.h"
47 #include "output_docbook.h"
49 #include "output_latex.h"
50 #include "output_plaintext.h"
51 #include "paragraph_funcs.h"
52 #include "Paragraph.h"
53 #include "ParagraphParameters.h"
54 #include "ParIterator.h"
55 #include "PDFOptions.h"
59 #include "TexStream.h"
60 #include "TextClassList.h"
62 #include "TocBackend.h"
64 #include "VCBackend.h"
67 #include "insets/InsetBibitem.h"
68 #include "insets/InsetBibtex.h"
69 #include "insets/InsetInclude.h"
70 #include "insets/InsetText.h"
72 #include "mathed/MacroTable.h"
73 #include "mathed/MathMacroTemplate.h"
74 #include "mathed/MathSupport.h"
76 #include "frontends/alert.h"
77 #include "frontends/Delegates.h"
78 #include "frontends/WorkAreaManager.h"
79 #include "frontends/FileDialog.h"
81 #include "graphics/Previews.h"
83 #include "support/types.h"
84 #include "support/lyxalgo.h"
85 #include "support/FileFilterList.h"
86 #include "support/filetools.h"
87 #include "support/Forkedcall.h"
88 #include "support/gzstream.h"
89 #include "support/lyxlib.h"
90 #include "support/os.h"
91 #include "support/Path.h"
92 #include "support/textutils.h"
93 #include "support/convert.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 = 299; // Uwe: Hyperlink types
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();
236 params.filepath = onlyPath(file.absFilename());
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()" << endl;
255 LYXERR(Debug::INFO) << "Buffer::~Buffer()" << endl;
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 string const name = changeExtension(makeLatexName(absFileName()), ".tex");
396 return no_path ? onlyFilename(name) : name;
400 string Buffer::logName(LogType * type) const
402 string const filename = latexName(false);
404 if (filename.empty()) {
410 string const path = temppath();
412 FileName const fname(addName(temppath(),
413 onlyFilename(changeExtension(filename,
415 FileName const bname(
416 addName(path, onlyFilename(
417 changeExtension(filename,
418 formats.extension("literate") + ".out"))));
420 // If no Latex log or Build log is newer, show Build log
422 if (bname.exists() &&
423 (!fname.exists() || fname.lastModified() < bname.lastModified())) {
424 LYXERR(Debug::FILES) << "Log name calculated as: " << bname << endl;
427 return bname.absFilename();
429 LYXERR(Debug::FILES) << "Log name calculated as: " << fname << endl;
432 return fname.absFilename();
436 void Buffer::setReadonly(bool const flag)
438 if (pimpl_->read_only != flag) {
439 pimpl_->read_only = flag;
445 void Buffer::setFileName(string const & newfile)
447 pimpl_->filename = makeAbsPath(newfile);
448 params().filepath = onlyPath(pimpl_->filename.absFilename());
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: `"
502 << token << '\'' << endl;
504 string unknown = params().readToken(lex, token);
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() << endl;
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.toFilesystemEncoding();
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 '"
806 << command_str << '\''
809 cmd_ret const ret = runCommand(command_str);
810 if (ret.first != 0) {
811 Alert::error(_("Conversion script failed"),
812 bformat(_("%1$s is from a different version"
813 " of LyX, but the lyx2lyx script"
814 " failed to convert it."),
815 from_utf8(filename.absFilename())));
818 bool const ret = readFile(tmpfile);
819 // Do stuff with tmpfile name and buffer name here.
820 return ret ? success : failure;
825 if (readDocument(lex)) {
826 Alert::error(_("Document format failure"),
827 bformat(_("%1$s ended unexpectedly, which means"
828 " that it is probably corrupted."),
829 from_utf8(filename.absFilename())));
832 pimpl_->file_fully_loaded = true;
837 // Should probably be moved to somewhere else: BufferView? LyXView?
838 bool Buffer::save() const
840 // We don't need autosaves in the immediate future. (Asger)
841 resetAutosaveTimers();
843 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
846 bool madeBackup = false;
848 // make a backup if the file already exists
849 if (lyxrc.make_backup && fileName().exists()) {
850 backupName = FileName(absFileName() + '~');
851 if (!lyxrc.backupdir_path.empty()) {
852 string const mangledName =
853 subst(subst(os::internal_path(
854 backupName.absFilename()), '/', '!'), ':', '!');
855 backupName = FileName(addName(lyxrc.backupdir_path,
858 if (fileName().copyTo(backupName, false)) {
861 Alert::error(_("Backup failure"),
862 bformat(_("Cannot create backup file %1$s.\n"
863 "Please check whether the directory exists and is writeable."),
864 from_utf8(backupName.absFilename())));
865 //LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
869 // ask if the disk file has been externally modified (use checksum method)
870 if (fileName().exists() && isExternallyModified(checksum_method)) {
871 docstring const file = makeDisplayPath(absFileName(), 20);
872 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
873 "you want to overwrite this file?"), file);
874 int const ret = Alert::prompt(_("Overwrite modified file?"),
875 text, 1, 1, _("&Overwrite"), _("&Cancel"));
880 if (writeFile(pimpl_->filename)) {
882 removeAutosaveFile(absFileName());
883 saveCheckSum(pimpl_->filename);
886 // Saving failed, so backup is not backup
888 rename(backupName, pimpl_->filename);
894 bool Buffer::writeFile(FileName const & fname) const
896 if (pimpl_->read_only && fname == pimpl_->filename)
902 if (params().embedded)
903 // first write the .lyx file to the temporary directory
904 content = FileName(addName(temppath(), "content.lyx"));
908 if (params().compressed) {
909 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
915 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
922 if (retval && params().embedded) {
923 // write file.lyx and all the embedded files to the zip file fname
924 // if embedding is enabled
925 return pimpl_->embedded_files.writeFile(fname);
931 bool Buffer::write(ostream & ofs) const
934 // Use the standard "C" locale for file output.
935 ofs.imbue(std::locale::classic());
938 // The top of the file should not be written by params().
940 // write out a comment in the top of the file
941 ofs << "#LyX " << lyx_version
942 << " created this file. For more info see http://www.lyx.org/\n"
943 << "\\lyxformat " << LYX_FORMAT << "\n"
944 << "\\begin_document\n";
947 /// For each author, set 'used' to true if there is a change
948 /// by this author in the document; otherwise set it to 'false'.
949 AuthorList::Authors::const_iterator a_it = params().authors().begin();
950 AuthorList::Authors::const_iterator a_end = params().authors().end();
951 for (; a_it != a_end; ++a_it)
952 a_it->second.setUsed(false);
954 ParIterator const end = par_iterator_end();
955 ParIterator it = par_iterator_begin();
956 for ( ; it != end; ++it)
957 it->checkAuthors(params().authors());
959 // now write out the buffer parameters.
960 ofs << "\\begin_header\n";
961 params().writeFile(ofs);
962 ofs << "\\end_header\n";
965 ofs << "\n\\begin_body\n";
966 text().write(*this, ofs);
967 ofs << "\n\\end_body\n";
969 // Write marker that shows file is complete
970 ofs << "\\end_document" << endl;
972 // Shouldn't really be needed....
975 // how to check if close went ok?
976 // Following is an attempt... (BE 20001011)
978 // good() returns false if any error occured, including some
980 // bad() returns true if something bad happened in the buffer,
981 // which should include file system full errors.
986 lyxerr << "File was not closed properly." << endl;
993 bool Buffer::makeLaTeXFile(FileName const & fname,
994 string const & original_path,
995 OutputParams const & runparams,
996 bool output_preamble, bool output_body)
998 string const encoding = runparams.encoding->iconvName();
999 LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: "
1000 << encoding << "..." << endl;
1002 odocfstream ofs(encoding);
1003 if (!openFileWrite(ofs, fname))
1006 //TexStream ts(ofs.rdbuf(), &texrow());
1008 bool failed_export = false;
1011 writeLaTeXSource(ofs, original_path,
1012 runparams, output_preamble, output_body);
1014 catch (iconv_codecvt_facet_exception & e) {
1015 lyxerr << "Caught iconv exception: " << e.what() << endl;
1016 failed_export = true;
1018 catch (std::exception const & e) {
1019 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1020 failed_export = true;
1023 lyxerr << "Caught some really weird exception..." << endl;
1024 LyX::cref().emergencyCleanup();
1030 failed_export = true;
1031 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1034 if (failed_export) {
1035 Alert::error(_("Encoding error"),
1036 _("Some characters of your document are probably not "
1037 "representable in the chosen encoding.\n"
1038 "Changing the document encoding to utf8 could help."));
1045 void Buffer::writeLaTeXSource(odocstream & os,
1046 string const & original_path,
1047 OutputParams const & runparams_in,
1048 bool const output_preamble, bool const output_body)
1050 OutputParams runparams = runparams_in;
1052 // validate the buffer.
1053 LYXERR(Debug::LATEX) << " Validating buffer..." << endl;
1054 LaTeXFeatures features(*this, params(), runparams);
1056 LYXERR(Debug::LATEX) << " Buffer validation done." << endl;
1058 // The starting paragraph of the coming rows is the
1059 // first paragraph of the document. (Asger)
1060 if (output_preamble && runparams.nice) {
1061 os << "%% LyX " << lyx_version << " created this file. "
1062 "For more info, see http://www.lyx.org/.\n"
1063 "%% Do not edit unless you really know what "
1068 LYXERR(Debug::INFO) << "lyx document header finished" << endl;
1069 // There are a few differences between nice LaTeX and usual files:
1070 // usual is \batchmode and has a
1071 // special input@path to allow the including of figures
1072 // with either \input or \includegraphics (what figinsets do).
1073 // input@path is set when the actual parameter
1074 // original_path is set. This is done for usual tex-file, but not
1075 // for nice-latex-file. (Matthias 250696)
1076 // Note that input@path is only needed for something the user does
1077 // in the preamble, included .tex files or ERT, files included by
1078 // LyX work without it.
1079 if (output_preamble) {
1080 if (!runparams.nice) {
1081 // code for usual, NOT nice-latex-file
1082 os << "\\batchmode\n"; // changed
1083 // from \nonstopmode
1086 if (!original_path.empty()) {
1088 // We don't know the encoding of inputpath
1089 docstring const inputpath = from_utf8(latex_path(original_path));
1090 os << "\\makeatletter\n"
1091 << "\\def\\input@path{{"
1092 << inputpath << "/}}\n"
1093 << "\\makeatother\n";
1099 // Write the preamble
1100 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1106 os << "\\begin{document}\n";
1108 } // output_preamble
1110 texrow().start(paragraphs().begin()->id(), 0);
1112 LYXERR(Debug::INFO) << "preamble finished, now the body." << endl;
1114 if (!lyxrc.language_auto_begin &&
1115 !params().language->babel().empty()) {
1117 os << from_utf8(subst(lyxrc.language_command_begin,
1119 params().language->babel()))
1124 Encoding const & encoding = params().encoding();
1125 if (encoding.package() == Encoding::CJK) {
1126 // Open a CJK environment, since in contrast to the encodings
1127 // handled by inputenc the document encoding is not set in
1128 // the preamble if it is handled by CJK.sty.
1129 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1134 // if we are doing a real file with body, even if this is the
1135 // child of some other buffer, let's cut the link here.
1136 // This happens for example if only a child document is printed.
1137 string save_parentname;
1138 if (output_preamble) {
1139 save_parentname = params().parentname;
1140 params().parentname.erase();
1143 loadChildDocuments();
1146 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1148 // Restore the parenthood if needed
1149 if (output_preamble)
1150 params().parentname = save_parentname;
1152 // add this just in case after all the paragraphs
1156 if (encoding.package() == Encoding::CJK) {
1157 // Close the open CJK environment.
1158 // latexParagraphs will have opened one even if the last text
1160 os << "\\end{CJK}\n";
1164 if (!lyxrc.language_auto_end &&
1165 !params().language->babel().empty()) {
1166 os << from_utf8(subst(lyxrc.language_command_end,
1168 params().language->babel()))
1173 if (output_preamble) {
1174 os << "\\end{document}\n";
1177 LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl;
1179 LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made."
1182 runparams_in.encoding = runparams.encoding;
1184 // Just to be sure. (Asger)
1187 LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl;
1188 LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1
1193 bool Buffer::isLatex() const
1195 return params().getTextClass().outputType() == LATEX;
1199 bool Buffer::isLiterate() const
1201 return params().getTextClass().outputType() == LITERATE;
1205 bool Buffer::isDocBook() const
1207 return params().getTextClass().outputType() == DOCBOOK;
1211 void Buffer::makeDocBookFile(FileName const & fname,
1212 OutputParams const & runparams,
1213 bool const body_only)
1215 LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl;
1219 if (!openFileWrite(ofs, fname))
1222 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1226 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1230 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1231 OutputParams const & runparams,
1232 bool const only_body)
1234 LaTeXFeatures features(*this, params(), runparams);
1239 TextClass const & tclass = params().getTextClass();
1240 string const top_element = tclass.latexname();
1243 if (runparams.flavor == OutputParams::XML)
1244 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1247 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1250 if (! tclass.class_header().empty())
1251 os << from_ascii(tclass.class_header());
1252 else if (runparams.flavor == OutputParams::XML)
1253 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1254 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1256 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1258 docstring preamble = from_utf8(params().preamble);
1259 if (runparams.flavor != OutputParams::XML ) {
1260 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1261 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1262 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1263 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1266 string const name = runparams.nice
1267 ? changeExtension(absFileName(), ".sgml") : fname;
1268 preamble += features.getIncludedFiles(name);
1269 preamble += features.getLyXSGMLEntities();
1271 if (!preamble.empty()) {
1272 os << "\n [ " << preamble << " ]";
1277 string top = top_element;
1279 if (runparams.flavor == OutputParams::XML)
1280 top += params().language->code();
1282 top += params().language->code().substr(0,2);
1285 if (!params().options.empty()) {
1287 top += params().options;
1290 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1291 << " file was created by LyX " << lyx_version
1292 << "\n See http://www.lyx.org/ for more information -->\n";
1294 params().getTextClass().counters().reset();
1296 loadChildDocuments();
1298 sgml::openTag(os, top);
1300 docbookParagraphs(paragraphs(), *this, os, runparams);
1301 sgml::closeTag(os, top_element);
1305 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1306 // Other flags: -wall -v0 -x
1307 int Buffer::runChktex()
1311 // get LaTeX-Filename
1312 FileName const path(temppath());
1313 string const name = addName(path.absFilename(), latexName());
1314 string const org_path = filePath();
1316 support::Path p(path); // path to LaTeX file
1317 message(_("Running chktex..."));
1319 // Generate the LaTeX file if neccessary
1320 OutputParams runparams(¶ms().encoding());
1321 runparams.flavor = OutputParams::LATEX;
1322 runparams.nice = false;
1323 makeLaTeXFile(FileName(name), org_path, runparams);
1326 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1327 int const res = chktex.run(terr); // run chktex
1330 Alert::error(_("chktex failure"),
1331 _("Could not run chktex successfully."));
1332 } else if (res > 0) {
1333 ErrorList & errlist = pimpl_->errorLists["ChkTeX"];
1335 bufferErrors(terr, errlist);
1346 void Buffer::validate(LaTeXFeatures & features) const
1348 TextClass const & tclass = params().getTextClass();
1350 if (params().outputChanges) {
1351 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1352 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1353 LaTeXFeatures::isAvailable("xcolor");
1355 if (features.runparams().flavor == OutputParams::LATEX) {
1357 features.require("ct-dvipost");
1358 features.require("dvipost");
1359 } else if (xcolorsoul) {
1360 features.require("ct-xcolor-soul");
1361 features.require("soul");
1362 features.require("xcolor");
1364 features.require("ct-none");
1366 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1368 features.require("ct-xcolor-soul");
1369 features.require("soul");
1370 features.require("xcolor");
1371 features.require("pdfcolmk"); // improves color handling in PDF output
1373 features.require("ct-none");
1378 // AMS Style is at document level
1379 if (params().use_amsmath == BufferParams::package_on
1380 || tclass.provides("amsmath"))
1381 features.require("amsmath");
1382 if (params().use_esint == BufferParams::package_on)
1383 features.require("esint");
1385 loadChildDocuments();
1387 for_each(paragraphs().begin(), paragraphs().end(),
1388 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1390 // the bullet shapes are buffer level not paragraph level
1391 // so they are tested here
1392 for (int i = 0; i < 4; ++i) {
1393 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1394 int const font = params().user_defined_bullet(i).getFont();
1396 int const c = params()
1397 .user_defined_bullet(i)
1404 features.require("latexsym");
1406 } else if (font == 1) {
1407 features.require("amssymb");
1408 } else if ((font >= 2 && font <= 5)) {
1409 features.require("pifont");
1414 if (lyxerr.debugging(Debug::LATEX)) {
1415 features.showStruct();
1420 void Buffer::getLabelList(vector<docstring> & list) const
1422 /// if this is a child document and the parent is already loaded
1423 /// Use the parent's list instead [ale990407]
1424 Buffer const * tmp = masterBuffer();
1426 lyxerr << "masterBuffer() failed!" << endl;
1430 tmp->getLabelList(list);
1434 loadChildDocuments();
1436 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1437 it.nextInset()->getLabelList(*this, list);
1441 void Buffer::updateBibfilesCache()
1443 // if this is a child document and the parent is already loaded
1444 // update the parent's cache instead
1445 Buffer * tmp = masterBuffer();
1448 tmp->updateBibfilesCache();
1452 bibfilesCache_.clear();
1453 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1454 if (it->lyxCode() == BIBTEX_CODE) {
1455 InsetBibtex const & inset =
1456 static_cast<InsetBibtex const &>(*it);
1457 vector<FileName> const bibfiles = inset.getFiles(*this);
1458 bibfilesCache_.insert(bibfilesCache_.end(),
1461 } else if (it->lyxCode() == INCLUDE_CODE) {
1462 InsetInclude & inset =
1463 static_cast<InsetInclude &>(*it);
1464 inset.updateBibfilesCache(*this);
1465 vector<FileName> const & bibfiles =
1466 inset.getBibfilesCache(*this);
1467 bibfilesCache_.insert(bibfilesCache_.end(),
1475 vector<FileName> const & Buffer::getBibfilesCache() const
1477 // if this is a child document and the parent is already loaded
1478 // use the parent's cache instead
1479 Buffer const * tmp = masterBuffer();
1482 return tmp->getBibfilesCache();
1484 // We update the cache when first used instead of at loading time.
1485 if (bibfilesCache_.empty())
1486 const_cast<Buffer *>(this)->updateBibfilesCache();
1488 return bibfilesCache_;
1492 bool Buffer::isDepClean(string const & name) const
1494 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1495 if (it == pimpl_->dep_clean.end())
1501 void Buffer::markDepClean(string const & name)
1503 pimpl_->dep_clean[name] = true;
1507 bool Buffer::dispatch(string const & command, bool * result)
1509 return dispatch(lyxaction.lookupFunc(command), result);
1513 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1515 bool dispatched = true;
1517 switch (func.action) {
1518 case LFUN_BUFFER_EXPORT: {
1519 bool const tmp = doExport(to_utf8(func.argument()), false);
1532 void Buffer::changeLanguage(Language const * from, Language const * to)
1537 for_each(par_iterator_begin(),
1539 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1543 bool Buffer::isMultiLingual() const
1545 ParConstIterator end = par_iterator_end();
1546 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1547 if (it->isMultiLingual(params()))
1554 ParIterator Buffer::getParFromID(int const id) const
1556 ParConstIterator it = par_iterator_begin();
1557 ParConstIterator const end = par_iterator_end();
1560 // John says this is called with id == -1 from undo
1561 lyxerr << "getParFromID(), id: " << id << endl;
1565 for (; it != end; ++it)
1573 bool Buffer::hasParWithID(int const id) const
1575 ParConstIterator const it = getParFromID(id);
1576 return it != par_iterator_end();
1580 ParIterator Buffer::par_iterator_begin()
1582 return lyx::par_iterator_begin(inset());
1586 ParIterator Buffer::par_iterator_end()
1588 return lyx::par_iterator_end(inset());
1592 ParConstIterator Buffer::par_iterator_begin() const
1594 return lyx::par_const_iterator_begin(inset());
1598 ParConstIterator Buffer::par_iterator_end() const
1600 return lyx::par_const_iterator_end(inset());
1604 Language const * Buffer::language() const
1606 return params().language;
1610 docstring const Buffer::B_(string const & l10n) const
1612 return params().B_(l10n);
1616 bool Buffer::isClean() const
1618 return pimpl_->lyx_clean;
1622 bool Buffer::isBakClean() const
1624 return pimpl_->bak_clean;
1628 bool Buffer::isExternallyModified(CheckMethod method) const
1630 BOOST_ASSERT(pimpl_->filename.exists());
1631 // if method == timestamp, check timestamp before checksum
1632 return (method == checksum_method
1633 || pimpl_->timestamp_ != pimpl_->filename.lastModified())
1634 && pimpl_->checksum_ != sum(pimpl_->filename);
1638 void Buffer::saveCheckSum(FileName const & file) const
1640 if (file.exists()) {
1641 pimpl_->timestamp_ = file.lastModified();
1642 pimpl_->checksum_ = sum(file);
1644 // in the case of save to a new file.
1645 pimpl_->timestamp_ = 0;
1646 pimpl_->checksum_ = 0;
1651 void Buffer::markClean() const
1653 if (!pimpl_->lyx_clean) {
1654 pimpl_->lyx_clean = true;
1657 // if the .lyx file has been saved, we don't need an
1659 pimpl_->bak_clean = true;
1663 void Buffer::markBakClean() const
1665 pimpl_->bak_clean = true;
1669 void Buffer::setUnnamed(bool flag)
1671 pimpl_->unnamed = flag;
1675 bool Buffer::isUnnamed() const
1677 return pimpl_->unnamed;
1681 // FIXME: this function should be moved to buffer_pimpl.C
1682 void Buffer::markDirty()
1684 if (pimpl_->lyx_clean) {
1685 pimpl_->lyx_clean = false;
1688 pimpl_->bak_clean = false;
1690 DepClean::iterator it = pimpl_->dep_clean.begin();
1691 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1693 for (; it != end; ++it)
1698 FileName Buffer::fileName() const
1700 return pimpl_->filename;
1704 string Buffer::absFileName() const
1706 return pimpl_->filename.absFilename();
1710 string const & Buffer::filePath() const
1712 return params().filepath;
1716 bool Buffer::isReadonly() const
1718 return pimpl_->read_only;
1722 void Buffer::setParentName(string const & name)
1724 if (name == pimpl_->filename.absFilename())
1725 // Avoids recursive include.
1726 params().parentname.clear();
1728 params().parentname = name;
1732 Buffer const * Buffer::masterBuffer() const
1734 if (!params().parentname.empty()
1735 && theBufferList().exists(params().parentname)) {
1736 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1737 //We need to check if the parent is us...
1738 //FIXME RECURSIVE INCLUDE
1739 //This is not sufficient, since recursive includes could be downstream.
1740 if (buf && buf != this)
1741 return buf->masterBuffer();
1748 Buffer * Buffer::masterBuffer()
1750 if (!params().parentname.empty()
1751 && theBufferList().exists(params().parentname)) {
1752 Buffer * buf = theBufferList().getBuffer(params().parentname);
1753 //We need to check if the parent is us...
1754 //FIXME RECURSIVE INCLUDE
1755 //This is not sufficient, since recursive includes could be downstream.
1756 if (buf && buf != this)
1757 return buf->masterBuffer();
1764 bool Buffer::hasMacro(docstring const & name, Paragraph const & par) const
1766 Impl::PositionToMacroMap::iterator it;
1767 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1768 if (it != pimpl_->macros[name].end())
1771 // If there is a master buffer, query that
1772 const Buffer * master = masterBuffer();
1773 if (master && master != this)
1774 return master->hasMacro(name);
1776 return MacroTable::globalMacros().has(name);
1780 bool Buffer::hasMacro(docstring const & name) const
1782 if( !pimpl_->macros[name].empty() )
1785 // If there is a master buffer, query that
1786 const Buffer * master = masterBuffer();
1787 if (master && master != this)
1788 return master->hasMacro(name);
1790 return MacroTable::globalMacros().has(name);
1794 MacroData const & Buffer::getMacro(docstring const & name,
1795 Paragraph const & par) const
1797 Impl::PositionToMacroMap::iterator it;
1798 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1799 if( it != pimpl_->macros[name].end() )
1802 // If there is a master buffer, query that
1803 const Buffer * master = masterBuffer();
1804 if (master && master != this)
1805 return master->getMacro(name);
1807 return MacroTable::globalMacros().get(name);
1811 MacroData const & Buffer::getMacro(docstring const & name) const
1813 Impl::PositionToMacroMap::iterator it;
1814 it = pimpl_->macros[name].begin();
1815 if( it != pimpl_->macros[name].end() )
1818 // If there is a master buffer, query that
1819 const Buffer * master = masterBuffer();
1820 if (master && master != this)
1821 return master->getMacro(name);
1823 return MacroTable::globalMacros().get(name);
1827 void Buffer::updateMacros()
1829 // start with empty table
1830 pimpl_->macros = Impl::NameToPositionMacroMap();
1832 // Iterate over buffer
1833 ParagraphList & pars = text().paragraphs();
1834 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1835 // set position again
1836 pars[i].setMacrocontextPosition(i);
1838 //lyxerr << "searching main par " << i
1839 // << " for macro definitions" << std::endl;
1840 InsetList const & insets = pars[i].insetList();
1841 InsetList::const_iterator it = insets.begin();
1842 InsetList::const_iterator end = insets.end();
1843 for ( ; it != end; ++it) {
1844 if (it->inset->lyxCode() != MATHMACRO_CODE)
1848 MathMacroTemplate const & macroTemplate
1849 = static_cast<MathMacroTemplate const &>(*it->inset);
1852 if (macroTemplate.validMacro()) {
1853 MacroData macro = macroTemplate.asMacroData();
1856 // call hasMacro here instead of directly querying mc to
1857 // also take the master document into consideration
1858 macro.setRedefinition(hasMacro(macroTemplate.name()));
1860 // register macro (possibly overwrite the previous one of this paragraph)
1861 pimpl_->macros[macroTemplate.name()][i] = macro;
1868 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1871 //FIXME: This does not work for child documents yet.
1872 BOOST_ASSERT(code == CITE_CODE || code == REF_CODE);
1873 // Check if the label 'from' appears more than once
1874 vector<docstring> labels;
1877 if (code == CITE_CODE) {
1879 keys.fillWithBibKeys(this);
1880 BiblioInfo::const_iterator bit = keys.begin();
1881 BiblioInfo::const_iterator bend = keys.end();
1883 for (; bit != bend; ++bit)
1885 labels.push_back(bit->first);
1888 getLabelList(labels);
1889 paramName = "reference";
1892 if (std::count(labels.begin(), labels.end(), from) > 1)
1895 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1896 if (it->lyxCode() == code) {
1897 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1898 docstring const oldValue = inset.getParam(paramName);
1899 if (oldValue == from)
1900 inset.setParam(paramName, to);
1906 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1907 pit_type par_end, bool full_source)
1909 OutputParams runparams(¶ms().encoding());
1910 runparams.nice = true;
1911 runparams.flavor = OutputParams::LATEX;
1912 runparams.linelen = lyxrc.plaintext_linelen;
1913 // No side effect of file copying and image conversion
1914 runparams.dryrun = true;
1918 os << "% " << _("Preview source code") << "\n\n";
1922 writeLaTeXSource(os, filePath(), runparams, true, true);
1924 writeDocBookSource(os, absFileName(), runparams, false);
1927 runparams.par_begin = par_begin;
1928 runparams.par_end = par_end;
1929 if (par_begin + 1 == par_end)
1931 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1935 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1936 convert<docstring>(par_begin),
1937 convert<docstring>(par_end - 1))
1941 // output paragraphs
1943 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1946 docbookParagraphs(paragraphs(), *this, os, runparams);
1952 ErrorList const & Buffer::errorList(string const & type) const
1954 static ErrorList const emptyErrorList;
1955 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1956 if (I == pimpl_->errorLists.end())
1957 return emptyErrorList;
1963 ErrorList & Buffer::errorList(string const & type)
1965 return pimpl_->errorLists[type];
1969 void Buffer::structureChanged() const
1972 gui_->structureChanged();
1976 void Buffer::errors(std::string const & err) const
1983 void Buffer::message(docstring const & msg) const
1990 void Buffer::setBusy(bool on) const
1997 void Buffer::setReadOnly(bool on) const
2000 gui_->setReadOnly(on);
2004 void Buffer::updateTitles() const
2007 gui_->updateTitles();
2011 void Buffer::resetAutosaveTimers() const
2014 gui_->resetAutosaveTimers();
2018 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
2027 class AutoSaveBuffer : public support::ForkedProcess {
2030 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
2031 : buffer_(buffer), fname_(fname) {}
2033 virtual boost::shared_ptr<ForkedProcess> clone() const
2035 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
2040 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
2041 from_utf8(fname_.absFilename())));
2042 return run(DontWait);
2046 virtual int generateChild();
2048 Buffer const & buffer_;
2053 #if !defined (HAVE_FORK)
2057 int AutoSaveBuffer::generateChild()
2059 // tmp_ret will be located (usually) in /tmp
2060 // will that be a problem?
2061 pid_t const pid = fork();
2062 // If you want to debug the autosave
2063 // you should set pid to -1, and comment out the fork.
2064 if (pid == 0 || pid == -1) {
2065 // pid = -1 signifies that lyx was unable
2066 // to fork. But we will do the save
2068 bool failed = false;
2070 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2071 if (!tmp_ret.empty()) {
2072 buffer_.writeFile(tmp_ret);
2073 // assume successful write of tmp_ret
2074 if (!rename(tmp_ret, fname_)) {
2076 // most likely couldn't move between
2077 // filesystems unless write of tmp_ret
2078 // failed so remove tmp file (if it
2087 // failed to write/rename tmp_ret so try writing direct
2088 if (!buffer_.writeFile(fname_)) {
2089 // It is dangerous to do this in the child,
2090 // but safe in the parent, so...
2091 if (pid == -1) // emit message signal.
2092 buffer_.message(_("Autosave failed!"));
2095 if (pid == 0) { // we are the child so...
2105 // Perfect target for a thread...
2106 void Buffer::autoSave() const
2108 if (isBakClean() || isReadonly()) {
2109 // We don't save now, but we'll try again later
2110 resetAutosaveTimers();
2114 // emit message signal.
2115 message(_("Autosaving current document..."));
2117 // create autosave filename
2118 string fname = filePath();
2120 fname += onlyFilename(absFileName());
2123 AutoSaveBuffer autosave(*this, FileName(fname));
2127 resetAutosaveTimers();
2131 /** Write a buffer to a new file name and rename the buffer
2132 according to the new file name.
2134 This function is e.g. used by menu callbacks and
2135 LFUN_BUFFER_WRITE_AS.
2137 If 'newname' is empty (the default), the user is asked via a
2138 dialog for the buffer's new name and location.
2140 If 'newname' is non-empty and has an absolute path, that is used.
2141 Otherwise the base directory of the buffer is used as the base
2142 for any relative path in 'newname'.
2145 bool Buffer::writeAs(string const & newname)
2147 string fname = absFileName();
2148 string const oldname = fname;
2150 if (newname.empty()) { /// No argument? Ask user through dialog
2153 FileDialog dlg(_("Choose a filename to save document as"),
2154 LFUN_BUFFER_WRITE_AS);
2155 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
2156 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
2158 if (!support::isLyXFilename(fname))
2161 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2163 FileDialog::Result result =
2164 dlg.save(from_utf8(onlyPath(fname)),
2166 from_utf8(onlyFilename(fname)));
2168 if (result.first == FileDialog::Later)
2171 fname = to_utf8(result.second);
2176 // Make sure the absolute filename ends with appropriate suffix
2177 fname = makeAbsPath(fname).absFilename();
2178 if (!support::isLyXFilename(fname))
2182 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2184 if (FileName(fname).exists()) {
2185 docstring const file = makeDisplayPath(fname, 30);
2186 docstring text = bformat(_("The document %1$s already "
2187 "exists.\n\nDo you want to "
2188 "overwrite that document?"),
2190 int const ret = Alert::prompt(_("Overwrite document?"),
2191 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2197 // Ok, change the name of the buffer
2200 bool unnamed = isUnnamed();
2202 saveCheckSum(FileName(fname));
2205 setFileName(oldname);
2206 setUnnamed(unnamed);
2207 saveCheckSum(FileName(oldname));
2211 removeAutosaveFile(oldname);
2216 bool Buffer::menuWrite()
2219 LyX::ref().session().lastFiles().add(FileName(absFileName()));
2223 // FIXME: we don't tell the user *WHY* the save failed !!
2225 docstring const file = makeDisplayPath(absFileName(), 30);
2227 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2228 "Do you want to rename the document and "
2229 "try again?"), file);
2230 int const ret = Alert::prompt(_("Rename and save?"),
2231 text, 0, 1, _("&Rename"), _("&Cancel"));
2240 void Buffer::loadChildDocuments() const
2242 bool parse_error = false;
2244 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2245 if (it->lyxCode() != INCLUDE_CODE)
2247 InsetCommand const & inset = static_cast<InsetCommand const &>(*it);
2248 InsetCommandParams const & ip = inset.params();
2249 Buffer * child = loadIfNeeded(*this, ip);
2252 parse_error |= !child->errorList("Parse").empty();
2253 child->loadChildDocuments();
2256 if (use_gui && masterBuffer() == this)
2257 updateLabels(*this);
2261 string Buffer::bufferFormat() const
2271 bool Buffer::doExport(string const & format, bool put_in_tempdir,
2272 string & result_file)
2274 string backend_format;
2275 OutputParams runparams(¶ms().encoding());
2276 runparams.flavor = OutputParams::LATEX;
2277 runparams.linelen = lyxrc.plaintext_linelen;
2278 vector<string> backs = backends();
2279 if (find(backs.begin(), backs.end(), format) == backs.end()) {
2280 // Get shortest path to format
2281 Graph::EdgePath path;
2282 for (vector<string>::const_iterator it = backs.begin();
2283 it != backs.end(); ++it) {
2284 Graph::EdgePath p = theConverters().getPath(*it, format);
2285 if (!p.empty() && (path.empty() || p.size() < path.size())) {
2286 backend_format = *it;
2291 runparams.flavor = theConverters().getFlavor(path);
2293 Alert::error(_("Couldn't export file"),
2294 bformat(_("No information for exporting the format %1$s."),
2295 formats.prettyName(format)));
2299 backend_format = format;
2300 // FIXME: Don't hardcode format names here, but use a flag
2301 if (backend_format == "pdflatex")
2302 runparams.flavor = OutputParams::PDFLATEX;
2305 string filename = latexName(false);
2306 filename = addName(temppath(), filename);
2307 filename = changeExtension(filename,
2308 formats.extension(backend_format));
2310 // Plain text backend
2311 if (backend_format == "text")
2312 writePlaintextFile(*this, FileName(filename), runparams);
2314 else if (backend_format == "lyx")
2315 writeFile(FileName(filename));
2317 else if (isDocBook()) {
2318 runparams.nice = !put_in_tempdir;
2319 makeDocBookFile(FileName(filename), runparams);
2322 else if (backend_format == format) {
2323 runparams.nice = true;
2324 if (!makeLaTeXFile(FileName(filename), string(), runparams))
2326 } else if (!lyxrc.tex_allows_spaces
2327 && support::contains(filePath(), ' ')) {
2328 Alert::error(_("File name error"),
2329 _("The directory path to the document cannot contain spaces."));
2332 runparams.nice = false;
2333 if (!makeLaTeXFile(FileName(filename), filePath(), runparams))
2337 string const error_type = (format == "program")
2338 ? "Build" : bufferFormat();
2339 string const ext = formats.extension(format);
2340 FileName const tmp_result_file(changeExtension(filename, ext));
2341 bool const success = theConverters().convert(this, FileName(filename),
2342 tmp_result_file, FileName(absFileName()), backend_format, format,
2343 errorList(error_type));
2344 // Emit the signal to show the error list.
2345 if (format != backend_format)
2351 result_file = tmp_result_file.absFilename();
2353 result_file = changeExtension(absFileName(), ext);
2354 // We need to copy referenced files (e. g. included graphics
2355 // if format == "dvi") to the result dir.
2356 vector<ExportedFile> const files =
2357 runparams.exportdata->externalFiles(format);
2358 string const dest = onlyPath(result_file);
2359 CopyStatus status = SUCCESS;
2360 for (vector<ExportedFile>::const_iterator it = files.begin();
2361 it != files.end() && status != CANCEL; ++it) {
2363 formats.getFormatFromFile(it->sourceName);
2364 status = copyFile(fmt, it->sourceName,
2365 makeAbsPath(it->exportName, dest),
2366 it->exportName, status == FORCE);
2368 if (status == CANCEL) {
2369 message(_("Document export cancelled."));
2370 } else if (tmp_result_file.exists()) {
2371 // Finally copy the main file
2372 status = copyFile(format, tmp_result_file,
2373 FileName(result_file), result_file,
2375 message(bformat(_("Document exported as %1$s "
2377 formats.prettyName(format),
2378 makeDisplayPath(result_file)));
2380 // This must be a dummy converter like fax (bug 1888)
2381 message(bformat(_("Document exported as %1$s"),
2382 formats.prettyName(format)));
2390 bool Buffer::doExport(string const & format, bool put_in_tempdir)
2393 return doExport(format, put_in_tempdir, result_file);
2397 bool Buffer::preview(string const & format)
2400 if (!doExport(format, true, result_file))
2402 return formats.view(*this, FileName(result_file), format);
2406 bool Buffer::isExportable(string const & format) const
2408 vector<string> backs = backends();
2409 for (vector<string>::const_iterator it = backs.begin();
2410 it != backs.end(); ++it)
2411 if (theConverters().isReachable(*it, format))
2417 vector<Format const *> Buffer::exportableFormats(bool only_viewable) const
2419 vector<string> backs = backends();
2420 vector<Format const *> result =
2421 theConverters().getReachable(backs[0], only_viewable, true);
2422 for (vector<string>::const_iterator it = backs.begin() + 1;
2423 it != backs.end(); ++it) {
2424 vector<Format const *> r =
2425 theConverters().getReachable(*it, only_viewable, false);
2426 result.insert(result.end(), r.begin(), r.end());
2432 vector<string> Buffer::backends() const
2435 if (params().getTextClass().isTeXClassAvailable()) {
2436 v.push_back(bufferFormat());
2437 // FIXME: Don't hardcode format names here, but use a flag
2438 if (v.back() == "latex")
2439 v.push_back("pdflatex");
2441 v.push_back("text");
2447 bool Buffer::readFileHelper(FileName const & s)
2449 // File information about normal file
2451 docstring const file = makeDisplayPath(s.absFilename(), 50);
2452 docstring text = bformat(_("The specified document\n%1$s"
2453 "\ncould not be read."), file);
2454 Alert::error(_("Could not read document"), text);
2458 // Check if emergency save file exists and is newer.
2459 FileName const e(s.absFilename() + ".emergency");
2461 if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) {
2462 docstring const file = makeDisplayPath(s.absFilename(), 20);
2463 docstring const text =
2464 bformat(_("An emergency save of the document "
2466 "Recover emergency save?"), file);
2467 switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
2468 _("&Recover"), _("&Load Original"),
2472 // the file is not saved if we load the emergency file.
2482 // Now check if autosave file is newer.
2483 FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#');
2485 if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) {
2486 docstring const file = makeDisplayPath(s.absFilename(), 20);
2487 docstring const text =
2488 bformat(_("The backup of the document "
2489 "%1$s is newer.\n\nLoad the "
2490 "backup instead?"), file);
2491 switch (Alert::prompt(_("Load backup?"), text, 0, 2,
2492 _("&Load backup"), _("Load &original"),
2496 // the file is not saved if we load the autosave file.
2500 // Here we delete the autosave
2511 bool Buffer::loadLyXFile(FileName const & s)
2513 if (s.isReadable()) {
2514 if (readFileHelper(s)) {
2515 lyxvc().file_found_hook(s);
2516 if (!s.isWritable())
2521 docstring const file = makeDisplayPath(s.absFilename(), 20);
2522 // Here we probably should run
2523 if (LyXVC::file_not_found_hook(s)) {
2524 docstring const text =
2525 bformat(_("Do you want to retrieve the document"
2526 " %1$s from version control?"), file);
2527 int const ret = Alert::prompt(_("Retrieve from version control?"),
2528 text, 0, 1, _("&Retrieve"), _("&Cancel"));
2531 // How can we know _how_ to do the checkout?
2532 // With the current VC support it has to be,
2533 // a RCS file since CVS do not have special ,v files.
2535 return loadLyXFile(s);
2543 void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
2545 TeXErrors::Errors::const_iterator cit = terr.begin();
2546 TeXErrors::Errors::const_iterator end = terr.end();
2548 for (; cit != end; ++cit) {
2551 int errorRow = cit->error_in_line;
2552 bool found = texrow().getIdFromRow(errorRow, id_start,
2558 found = texrow().getIdFromRow(errorRow, id_end, pos_end);
2559 } while (found && id_start == id_end && pos_start == pos_end);
2561 errorList.push_back(ErrorItem(cit->error_desc,
2562 cit->error_text, id_start, pos_start, pos_end));