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/lstrings.h"
90 #include "support/lyxlib.h"
91 #include "support/os.h"
92 #include "support/Path.h"
93 #include "support/textutils.h"
94 #include "support/convert.h"
96 #if !defined (HAVE_FORK)
100 #include <boost/bind.hpp>
101 #include <boost/shared_ptr.hpp>
111 using std::make_pair;
116 using std::ostringstream;
127 using support::addName;
128 using support::bformat;
129 using support::changeExtension;
130 using support::cmd_ret;
131 using support::createBufferTmpDir;
132 using support::FileName;
133 using support::libFileSearch;
134 using support::latex_path;
135 using support::ltrim;
136 using support::makeAbsPath;
137 using support::makeDisplayPath;
138 using support::makeLatexName;
139 using support::onlyFilename;
140 using support::onlyPath;
141 using support::quoteName;
142 using support::removeAutosaveFile;
143 using support::rename;
144 using support::runCommand;
145 using support::split;
146 using support::subst;
147 using support::tempName;
150 using support::suffixIs;
152 namespace Alert = frontend::Alert;
153 namespace os = support::os;
157 int const LYX_FORMAT = 299; // Uwe: Hyperlink types
162 typedef std::map<string, bool> DepClean;
167 Impl(Buffer & parent, FileName const & file, bool readonly);
174 /// need to regenerate .tex?
178 mutable bool lyx_clean;
180 /// is autosave needed?
181 mutable bool bak_clean;
183 /// is this a unnamed file (New...)?
189 /// name of the file the buffer is associated with.
192 /** Set to true only when the file is fully loaded.
193 * Used to prevent the premature generation of previews
194 * and by the citation inset.
196 bool file_fully_loaded;
198 /// our Text that should be wrapped in an InsetText
202 TocBackend toc_backend;
205 typedef std::map<unsigned int, MacroData, std::greater<int> > PositionToMacroMap;
206 typedef std::map<docstring, PositionToMacroMap> NameToPositionMacroMap;
207 NameToPositionMacroMap macros;
209 /// Container for all sort of Buffer dependant errors.
210 map<string, ErrorList> errorLists;
212 /// all embedded files of this buffer
213 EmbeddedFiles embedded_files;
215 /// timestamp and checksum used to test if the file has been externally
216 /// modified. (Used to properly enable 'File->Revert to saved', bug 4114).
218 unsigned long checksum_;
221 frontend::WorkAreaManager * wa_;
228 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
229 : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
230 filename(file), file_fully_loaded(false), inset(params),
231 toc_backend(&parent), embedded_files(&parent), timestamp_(0),
232 checksum_(0), wa_(0), undo_(parent)
234 inset.setAutoBreakRows(true);
235 lyxvc.setBuffer(&parent);
236 temppath = createBufferTmpDir();
237 params.filepath = onlyPath(file.absFilename());
238 // FIXME: And now do something if temppath == string(), because we
239 // assume from now on that temppath points to a valid temp dir.
240 // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html
243 wa_ = new frontend::WorkAreaManager;
247 Buffer::Buffer(string const & file, bool readonly)
248 : pimpl_(new Impl(*this, FileName(file), readonly)), gui_(0)
250 LYXERR(Debug::INFO) << "Buffer::Buffer()" << endl;
256 LYXERR(Debug::INFO) << "Buffer::~Buffer()" << endl;
257 // here the buffer should take care that it is
258 // saved properly, before it goes into the void.
260 Buffer * master = masterBuffer();
261 if (master != this && use_gui)
262 // We are closing buf which was a child document so we
263 // must update the labels and section numbering of its master
265 updateLabels(*master);
267 if (!temppath().empty() && !FileName(temppath()).destroyDirectory()) {
268 Alert::warning(_("Could not remove temporary directory"),
269 bformat(_("Could not remove the temporary directory %1$s"),
270 from_utf8(temppath())));
273 // Remove any previewed LaTeX snippets associated with this buffer.
274 graphics::Previews::get().removeLoader(*this);
277 pimpl_->wa_->closeAll();
284 void Buffer::changed() const
287 pimpl_->wa_->redrawAll();
291 frontend::WorkAreaManager & Buffer::workAreaManager() const
293 BOOST_ASSERT(pimpl_->wa_);
298 Text & Buffer::text() const
300 return const_cast<Text &>(pimpl_->inset.text_);
304 Inset & Buffer::inset() const
306 return const_cast<InsetText &>(pimpl_->inset);
310 BufferParams & Buffer::params()
312 return pimpl_->params;
316 BufferParams const & Buffer::params() const
318 return pimpl_->params;
322 ParagraphList & Buffer::paragraphs()
324 return text().paragraphs();
328 ParagraphList const & Buffer::paragraphs() const
330 return text().paragraphs();
334 LyXVC & Buffer::lyxvc()
336 return pimpl_->lyxvc;
340 LyXVC const & Buffer::lyxvc() const
342 return pimpl_->lyxvc;
346 string const & Buffer::temppath() const
348 return pimpl_->temppath;
352 TexRow & Buffer::texrow()
354 return pimpl_->texrow;
358 TexRow const & Buffer::texrow() const
360 return pimpl_->texrow;
364 TocBackend & Buffer::tocBackend()
366 return pimpl_->toc_backend;
370 TocBackend const & Buffer::tocBackend() const
372 return pimpl_->toc_backend;
376 EmbeddedFiles & Buffer::embeddedFiles()
378 return pimpl_->embedded_files;
382 EmbeddedFiles const & Buffer::embeddedFiles() const
384 return pimpl_->embedded_files;
388 Undo & Buffer::undo()
390 return pimpl_->undo_;
394 string Buffer::latexName(bool const no_path) const
396 string const name = changeExtension(makeLatexName(absFileName()), ".tex");
397 return no_path ? onlyFilename(name) : name;
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 << endl;
428 return bname.absFilename();
430 LYXERR(Debug::FILES) << "Log name calculated as: " << fname << endl;
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 params().filepath = onlyPath(pimpl_->filename.absFilename());
450 setReadonly(pimpl_->filename.isReadOnly());
455 int Buffer::readHeader(Lexer & lex)
457 int unknown_tokens = 0;
459 int begin_header_line = -1;
461 // Initialize parameters that may be/go lacking in header:
462 params().branchlist().clear();
463 params().preamble.erase();
464 params().options.erase();
465 params().float_placement.erase();
466 params().paperwidth.erase();
467 params().paperheight.erase();
468 params().leftmargin.erase();
469 params().rightmargin.erase();
470 params().topmargin.erase();
471 params().bottommargin.erase();
472 params().headheight.erase();
473 params().headsep.erase();
474 params().footskip.erase();
475 params().listings_params.clear();
476 params().clearLayoutModules();
477 params().pdfoptions().clear();
479 for (int i = 0; i < 4; ++i) {
480 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
481 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
484 ErrorList & errorList = pimpl_->errorLists["Parse"];
488 string const token = lex.getString();
493 if (token == "\\end_header")
497 if (token == "\\begin_header") {
498 begin_header_line = line;
502 LYXERR(Debug::PARSER) << "Handling document header token: `"
503 << token << '\'' << endl;
505 string unknown = params().readToken(lex, token);
506 if (!unknown.empty()) {
507 if (unknown[0] != '\\' && token == "\\textclass") {
508 Alert::warning(_("Unknown document class"),
509 bformat(_("Using the default document class, because the "
510 "class %1$s is unknown."), from_utf8(unknown)));
513 docstring const s = bformat(_("Unknown token: "
517 errorList.push_back(ErrorItem(_("Document header error"),
522 if (begin_header_line) {
523 docstring const s = _("\\begin_header is missing");
524 errorList.push_back(ErrorItem(_("Document header error"),
528 return unknown_tokens;
533 // changed to be public and have one parameter
534 // Returns false if "\end_document" is not read (Asger)
535 bool Buffer::readDocument(Lexer & lex)
537 ErrorList & errorList = pimpl_->errorLists["Parse"];
541 string const token = lex.getString();
542 if (token != "\\begin_document") {
543 docstring const s = _("\\begin_document is missing");
544 errorList.push_back(ErrorItem(_("Document header error"),
548 // we are reading in a brand new document
549 BOOST_ASSERT(paragraphs().empty());
552 TextClass const & baseClass = textclasslist[params().getBaseClass()];
553 if (!baseClass.load(filePath())) {
554 string theclass = baseClass.name();
555 Alert::error(_("Can't load document class"), bformat(
556 _("Using the default document class, because the "
557 "class %1$s could not be loaded."), from_utf8(theclass)));
558 params().setBaseClass(defaultTextclass());
561 if (params().outputChanges) {
562 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
563 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
564 LaTeXFeatures::isAvailable("xcolor");
566 if (!dvipost && !xcolorsoul) {
567 Alert::warning(_("Changes not shown in LaTeX output"),
568 _("Changes will not be highlighted in LaTeX output, "
569 "because neither dvipost nor xcolor/soul are installed.\n"
570 "Please install these packages or redefine "
571 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
572 } else if (!xcolorsoul) {
573 Alert::warning(_("Changes not shown in LaTeX output"),
574 _("Changes will not be highlighted in LaTeX output "
575 "when using pdflatex, because xcolor and soul are not installed.\n"
576 "Please install both packages or redefine "
577 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
582 bool const res = text().read(*this, lex, errorList);
583 for_each(text().paragraphs().begin(),
584 text().paragraphs().end(),
585 bind(&Paragraph::setInsetOwner, _1, &inset()));
591 // needed to insert the selection
592 void Buffer::insertStringAsLines(ParagraphList & pars,
593 pit_type & pit, pos_type & pos,
594 Font const & fn, docstring const & str, bool autobreakrows)
598 // insert the string, don't insert doublespace
599 bool space_inserted = true;
600 for (docstring::const_iterator cit = str.begin();
601 cit != str.end(); ++cit) {
602 Paragraph & par = pars[pit];
604 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
605 breakParagraph(params(), pars, pit, pos,
606 par.layout()->isEnvironment());
609 space_inserted = true;
613 // do not insert consecutive spaces if !free_spacing
614 } else if ((*cit == ' ' || *cit == '\t') &&
615 space_inserted && !par.isFreeSpacing()) {
617 } else if (*cit == '\t') {
618 if (!par.isFreeSpacing()) {
619 // tabs are like spaces here
620 par.insertChar(pos, ' ', font, params().trackChanges);
622 space_inserted = true;
624 const pos_type n = 8 - pos % 8;
625 for (pos_type i = 0; i < n; ++i) {
626 par.insertChar(pos, ' ', font, params().trackChanges);
629 space_inserted = true;
631 } else if (!isPrintable(*cit)) {
632 // Ignore unprintables
635 // just insert the character
636 par.insertChar(pos, *cit, font, params().trackChanges);
638 space_inserted = (*cit == ' ');
645 bool Buffer::readString(std::string const & s)
647 params().compressed = false;
649 // remove dummy empty par
650 paragraphs().clear();
652 std::istringstream is(s);
654 FileName const name(tempName());
655 switch (readFile(lex, name, true)) {
659 // We need to call lyx2lyx, so write the input to a file
660 std::ofstream os(name.toFilesystemEncoding().c_str());
663 return readFile(name);
673 bool Buffer::readFile(FileName const & filename)
675 FileName fname(filename);
676 // Check if the file is compressed.
677 string format = filename.guessFormatFromContents();
678 if (format == "zip") {
679 // decompress to a temp directory
680 LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl;
681 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
683 FileName lyxfile(addName(temppath(), "content.lyx"));
684 // if both manifest.txt and file.lyx exist, this is am embedded file
685 if (lyxfile.exists()) {
686 params().embedded = true;
690 // The embedded lyx file can also be compressed, for backward compatibility
691 format = fname.guessFormatFromContents();
692 if (format == "gzip" || format == "zip" || format == "compress")
693 params().compressed = true;
695 // remove dummy empty par
696 paragraphs().clear();
699 if (readFile(lex, fname) != success)
706 bool Buffer::isFullyLoaded() const
708 return pimpl_->file_fully_loaded;
712 void Buffer::setFullyLoaded(bool value)
714 pimpl_->file_fully_loaded = value;
718 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
721 BOOST_ASSERT(!filename.empty());
724 Alert::error(_("Document could not be read"),
725 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
730 string const token = lex.getString();
733 Alert::error(_("Document could not be read"),
734 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
738 // the first token _must_ be...
739 if (token != "\\lyxformat") {
740 lyxerr << "Token: " << token << endl;
742 Alert::error(_("Document format failure"),
743 bformat(_("%1$s is not a LyX document."),
744 from_utf8(filename.absFilename())));
749 string tmp_format = lex.getString();
750 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
751 // if present remove ".," from string.
752 string::size_type dot = tmp_format.find_first_of(".,");
753 //lyxerr << " dot found at " << dot << endl;
754 if (dot != string::npos)
755 tmp_format.erase(dot, 1);
756 int const file_format = convert<int>(tmp_format);
757 //lyxerr << "format: " << file_format << endl;
759 // save timestamp and checksum of the original disk file, making sure
760 // to not overwrite them with those of the file created in the tempdir
761 // when it has to be converted to the current format.
762 if (!pimpl_->checksum_) {
763 // Save the timestamp and checksum of disk file. If filename is an
764 // emergency file, save the timestamp and sum of the original lyx file
765 // because isExternallyModified will check for this file. (BUG4193)
766 string diskfile = filename.toFilesystemEncoding();
767 if (suffixIs(diskfile, ".emergency"))
768 diskfile = diskfile.substr(0, diskfile.size() - 10);
769 saveCheckSum(FileName(diskfile));
772 if (file_format != LYX_FORMAT) {
775 // lyx2lyx would fail
778 FileName const tmpfile(tempName());
779 if (tmpfile.empty()) {
780 Alert::error(_("Conversion failed"),
781 bformat(_("%1$s is from a different"
782 " version of LyX, but a temporary"
783 " file for converting it could"
785 from_utf8(filename.absFilename())));
788 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
789 if (lyx2lyx.empty()) {
790 Alert::error(_("Conversion script not found"),
791 bformat(_("%1$s is from a different"
792 " version of LyX, but the"
793 " conversion script lyx2lyx"
794 " could not be found."),
795 from_utf8(filename.absFilename())));
798 ostringstream command;
799 command << os::python()
800 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
801 << " -t " << convert<string>(LYX_FORMAT)
802 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
803 << ' ' << quoteName(filename.toFilesystemEncoding());
804 string const command_str = command.str();
806 LYXERR(Debug::INFO) << "Running '"
807 << command_str << '\''
810 cmd_ret const ret = runCommand(command_str);
811 if (ret.first != 0) {
812 Alert::error(_("Conversion script failed"),
813 bformat(_("%1$s is from a different version"
814 " of LyX, but the lyx2lyx script"
815 " failed to convert it."),
816 from_utf8(filename.absFilename())));
819 bool const ret = readFile(tmpfile);
820 // Do stuff with tmpfile name and buffer name here.
821 return ret ? success : failure;
826 if (readDocument(lex)) {
827 Alert::error(_("Document format failure"),
828 bformat(_("%1$s ended unexpectedly, which means"
829 " that it is probably corrupted."),
830 from_utf8(filename.absFilename())));
833 pimpl_->file_fully_loaded = true;
838 // Should probably be moved to somewhere else: BufferView? LyXView?
839 bool Buffer::save() const
841 // We don't need autosaves in the immediate future. (Asger)
842 resetAutosaveTimers();
844 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
847 bool madeBackup = false;
849 // make a backup if the file already exists
850 if (lyxrc.make_backup && fileName().exists()) {
851 backupName = FileName(absFileName() + '~');
852 if (!lyxrc.backupdir_path.empty()) {
853 string const mangledName =
854 subst(subst(os::internal_path(
855 backupName.absFilename()), '/', '!'), ':', '!');
856 backupName = FileName(addName(lyxrc.backupdir_path,
859 if (fileName().copyTo(backupName, false)) {
862 Alert::error(_("Backup failure"),
863 bformat(_("Cannot create backup file %1$s.\n"
864 "Please check whether the directory exists and is writeable."),
865 from_utf8(backupName.absFilename())));
866 //LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
870 // ask if the disk file has been externally modified (use checksum method)
871 if (fileName().exists() && isExternallyModified(checksum_method)) {
872 docstring const file = makeDisplayPath(absFileName(), 20);
873 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
874 "you want to overwrite this file?"), file);
875 int const ret = Alert::prompt(_("Overwrite modified file?"),
876 text, 1, 1, _("&Overwrite"), _("&Cancel"));
881 if (writeFile(pimpl_->filename)) {
883 removeAutosaveFile(absFileName());
884 saveCheckSum(pimpl_->filename);
887 // Saving failed, so backup is not backup
889 rename(backupName, pimpl_->filename);
895 bool Buffer::writeFile(FileName const & fname) const
897 if (pimpl_->read_only && fname == pimpl_->filename)
903 if (params().embedded)
904 // first write the .lyx file to the temporary directory
905 content = FileName(addName(temppath(), "content.lyx"));
909 if (params().compressed) {
910 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
916 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
923 if (retval && params().embedded) {
924 // write file.lyx and all the embedded files to the zip file fname
925 // if embedding is enabled
926 return pimpl_->embedded_files.writeFile(fname);
932 bool Buffer::write(ostream & ofs) const
935 // Use the standard "C" locale for file output.
936 ofs.imbue(std::locale::classic());
939 // The top of the file should not be written by params().
941 // write out a comment in the top of the file
942 ofs << "#LyX " << lyx_version
943 << " created this file. For more info see http://www.lyx.org/\n"
944 << "\\lyxformat " << LYX_FORMAT << "\n"
945 << "\\begin_document\n";
948 /// For each author, set 'used' to true if there is a change
949 /// by this author in the document; otherwise set it to 'false'.
950 AuthorList::Authors::const_iterator a_it = params().authors().begin();
951 AuthorList::Authors::const_iterator a_end = params().authors().end();
952 for (; a_it != a_end; ++a_it)
953 a_it->second.setUsed(false);
955 ParIterator const end = par_iterator_end();
956 ParIterator it = par_iterator_begin();
957 for ( ; it != end; ++it)
958 it->checkAuthors(params().authors());
960 // now write out the buffer parameters.
961 ofs << "\\begin_header\n";
962 params().writeFile(ofs);
963 ofs << "\\end_header\n";
966 ofs << "\n\\begin_body\n";
967 text().write(*this, ofs);
968 ofs << "\n\\end_body\n";
970 // Write marker that shows file is complete
971 ofs << "\\end_document" << endl;
973 // Shouldn't really be needed....
976 // how to check if close went ok?
977 // Following is an attempt... (BE 20001011)
979 // good() returns false if any error occured, including some
981 // bad() returns true if something bad happened in the buffer,
982 // which should include file system full errors.
987 lyxerr << "File was not closed properly." << endl;
994 bool Buffer::makeLaTeXFile(FileName const & fname,
995 string const & original_path,
996 OutputParams const & runparams,
997 bool output_preamble, bool output_body)
999 string const encoding = runparams.encoding->iconvName();
1000 LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: "
1001 << encoding << "..." << endl;
1003 odocfstream ofs(encoding);
1004 if (!openFileWrite(ofs, fname))
1007 //TexStream ts(ofs.rdbuf(), &texrow());
1009 bool failed_export = false;
1012 writeLaTeXSource(ofs, original_path,
1013 runparams, output_preamble, output_body);
1015 catch (iconv_codecvt_facet_exception & e) {
1016 lyxerr << "Caught iconv exception: " << e.what() << endl;
1017 failed_export = true;
1019 catch (std::exception const & e) {
1020 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1021 failed_export = true;
1024 lyxerr << "Caught some really weird exception..." << endl;
1025 LyX::cref().emergencyCleanup();
1031 failed_export = true;
1032 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1035 if (failed_export) {
1036 Alert::error(_("Encoding error"),
1037 _("Some characters of your document are probably not "
1038 "representable in the chosen encoding.\n"
1039 "Changing the document encoding to utf8 could help."));
1046 void Buffer::writeLaTeXSource(odocstream & os,
1047 string const & original_path,
1048 OutputParams const & runparams_in,
1049 bool const output_preamble, bool const output_body)
1051 OutputParams runparams = runparams_in;
1053 // validate the buffer.
1054 LYXERR(Debug::LATEX) << " Validating buffer..." << endl;
1055 LaTeXFeatures features(*this, params(), runparams);
1057 LYXERR(Debug::LATEX) << " Buffer validation done." << endl;
1059 // The starting paragraph of the coming rows is the
1060 // first paragraph of the document. (Asger)
1061 if (output_preamble && runparams.nice) {
1062 os << "%% LyX " << lyx_version << " created this file. "
1063 "For more info, see http://www.lyx.org/.\n"
1064 "%% Do not edit unless you really know what "
1069 LYXERR(Debug::INFO) << "lyx document header finished" << endl;
1070 // There are a few differences between nice LaTeX and usual files:
1071 // usual is \batchmode and has a
1072 // special input@path to allow the including of figures
1073 // with either \input or \includegraphics (what figinsets do).
1074 // input@path is set when the actual parameter
1075 // original_path is set. This is done for usual tex-file, but not
1076 // for nice-latex-file. (Matthias 250696)
1077 // Note that input@path is only needed for something the user does
1078 // in the preamble, included .tex files or ERT, files included by
1079 // LyX work without it.
1080 if (output_preamble) {
1081 if (!runparams.nice) {
1082 // code for usual, NOT nice-latex-file
1083 os << "\\batchmode\n"; // changed
1084 // from \nonstopmode
1087 if (!original_path.empty()) {
1089 // We don't know the encoding of inputpath
1090 docstring const inputpath = from_utf8(latex_path(original_path));
1091 os << "\\makeatletter\n"
1092 << "\\def\\input@path{{"
1093 << inputpath << "/}}\n"
1094 << "\\makeatother\n";
1100 // Write the preamble
1101 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1107 os << "\\begin{document}\n";
1109 } // output_preamble
1111 texrow().start(paragraphs().begin()->id(), 0);
1113 LYXERR(Debug::INFO) << "preamble finished, now the body." << endl;
1115 if (!lyxrc.language_auto_begin &&
1116 !params().language->babel().empty()) {
1118 os << from_utf8(subst(lyxrc.language_command_begin,
1120 params().language->babel()))
1125 Encoding const & encoding = params().encoding();
1126 if (encoding.package() == Encoding::CJK) {
1127 // Open a CJK environment, since in contrast to the encodings
1128 // handled by inputenc the document encoding is not set in
1129 // the preamble if it is handled by CJK.sty.
1130 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1135 // if we are doing a real file with body, even if this is the
1136 // child of some other buffer, let's cut the link here.
1137 // This happens for example if only a child document is printed.
1138 string save_parentname;
1139 if (output_preamble) {
1140 save_parentname = params().parentname;
1141 params().parentname.erase();
1144 loadChildDocuments();
1147 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1149 // Restore the parenthood if needed
1150 if (output_preamble)
1151 params().parentname = save_parentname;
1153 // add this just in case after all the paragraphs
1157 if (encoding.package() == Encoding::CJK) {
1158 // Close the open CJK environment.
1159 // latexParagraphs will have opened one even if the last text
1161 os << "\\end{CJK}\n";
1165 if (!lyxrc.language_auto_end &&
1166 !params().language->babel().empty()) {
1167 os << from_utf8(subst(lyxrc.language_command_end,
1169 params().language->babel()))
1174 if (output_preamble) {
1175 os << "\\end{document}\n";
1178 LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl;
1180 LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made."
1183 runparams_in.encoding = runparams.encoding;
1185 // Just to be sure. (Asger)
1188 LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl;
1189 LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1
1194 bool Buffer::isLatex() const
1196 return params().getTextClass().outputType() == LATEX;
1200 bool Buffer::isLiterate() const
1202 return params().getTextClass().outputType() == LITERATE;
1206 bool Buffer::isDocBook() const
1208 return params().getTextClass().outputType() == DOCBOOK;
1212 void Buffer::makeDocBookFile(FileName const & fname,
1213 OutputParams const & runparams,
1214 bool const body_only)
1216 LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl;
1220 if (!openFileWrite(ofs, fname))
1223 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1227 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1231 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1232 OutputParams const & runparams,
1233 bool const only_body)
1235 LaTeXFeatures features(*this, params(), runparams);
1240 TextClass const & tclass = params().getTextClass();
1241 string const top_element = tclass.latexname();
1244 if (runparams.flavor == OutputParams::XML)
1245 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1248 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1251 if (! tclass.class_header().empty())
1252 os << from_ascii(tclass.class_header());
1253 else if (runparams.flavor == OutputParams::XML)
1254 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1255 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1257 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1259 docstring preamble = from_utf8(params().preamble);
1260 if (runparams.flavor != OutputParams::XML ) {
1261 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1262 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1263 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1264 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1267 string const name = runparams.nice
1268 ? changeExtension(absFileName(), ".sgml") : fname;
1269 preamble += features.getIncludedFiles(name);
1270 preamble += features.getLyXSGMLEntities();
1272 if (!preamble.empty()) {
1273 os << "\n [ " << preamble << " ]";
1278 string top = top_element;
1280 if (runparams.flavor == OutputParams::XML)
1281 top += params().language->code();
1283 top += params().language->code().substr(0,2);
1286 if (!params().options.empty()) {
1288 top += params().options;
1291 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1292 << " file was created by LyX " << lyx_version
1293 << "\n See http://www.lyx.org/ for more information -->\n";
1295 params().getTextClass().counters().reset();
1297 loadChildDocuments();
1299 sgml::openTag(os, top);
1301 docbookParagraphs(paragraphs(), *this, os, runparams);
1302 sgml::closeTag(os, top_element);
1306 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1307 // Other flags: -wall -v0 -x
1308 int Buffer::runChktex()
1312 // get LaTeX-Filename
1313 FileName const path(temppath());
1314 string const name = addName(path.absFilename(), latexName());
1315 string const org_path = filePath();
1317 support::PathChanger p(path); // path to LaTeX file
1318 message(_("Running chktex..."));
1320 // Generate the LaTeX file if neccessary
1321 OutputParams runparams(¶ms().encoding());
1322 runparams.flavor = OutputParams::LATEX;
1323 runparams.nice = false;
1324 makeLaTeXFile(FileName(name), org_path, runparams);
1327 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1328 int const res = chktex.run(terr); // run chktex
1331 Alert::error(_("chktex failure"),
1332 _("Could not run chktex successfully."));
1333 } else if (res > 0) {
1334 ErrorList & errlist = pimpl_->errorLists["ChkTeX"];
1336 bufferErrors(terr, errlist);
1347 void Buffer::validate(LaTeXFeatures & features) const
1349 TextClass const & tclass = params().getTextClass();
1351 if (params().outputChanges) {
1352 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1353 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1354 LaTeXFeatures::isAvailable("xcolor");
1356 if (features.runparams().flavor == OutputParams::LATEX) {
1358 features.require("ct-dvipost");
1359 features.require("dvipost");
1360 } else if (xcolorsoul) {
1361 features.require("ct-xcolor-soul");
1362 features.require("soul");
1363 features.require("xcolor");
1365 features.require("ct-none");
1367 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1369 features.require("ct-xcolor-soul");
1370 features.require("soul");
1371 features.require("xcolor");
1372 features.require("pdfcolmk"); // improves color handling in PDF output
1374 features.require("ct-none");
1379 // AMS Style is at document level
1380 if (params().use_amsmath == BufferParams::package_on
1381 || tclass.provides("amsmath"))
1382 features.require("amsmath");
1383 if (params().use_esint == BufferParams::package_on)
1384 features.require("esint");
1386 loadChildDocuments();
1388 for_each(paragraphs().begin(), paragraphs().end(),
1389 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1391 // the bullet shapes are buffer level not paragraph level
1392 // so they are tested here
1393 for (int i = 0; i < 4; ++i) {
1394 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1395 int const font = params().user_defined_bullet(i).getFont();
1397 int const c = params()
1398 .user_defined_bullet(i)
1405 features.require("latexsym");
1407 } else if (font == 1) {
1408 features.require("amssymb");
1409 } else if ((font >= 2 && font <= 5)) {
1410 features.require("pifont");
1415 if (lyxerr.debugging(Debug::LATEX)) {
1416 features.showStruct();
1421 void Buffer::getLabelList(vector<docstring> & list) const
1423 /// if this is a child document and the parent is already loaded
1424 /// Use the parent's list instead [ale990407]
1425 Buffer const * tmp = masterBuffer();
1427 lyxerr << "masterBuffer() failed!" << endl;
1431 tmp->getLabelList(list);
1435 loadChildDocuments();
1437 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1438 it.nextInset()->getLabelList(*this, list);
1442 void Buffer::updateBibfilesCache()
1444 // if this is a child document and the parent is already loaded
1445 // update the parent's cache instead
1446 Buffer * tmp = masterBuffer();
1449 tmp->updateBibfilesCache();
1453 bibfilesCache_.clear();
1454 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1455 if (it->lyxCode() == BIBTEX_CODE) {
1456 InsetBibtex const & inset =
1457 static_cast<InsetBibtex const &>(*it);
1458 vector<FileName> const bibfiles = inset.getFiles(*this);
1459 bibfilesCache_.insert(bibfilesCache_.end(),
1462 } else if (it->lyxCode() == INCLUDE_CODE) {
1463 InsetInclude & inset =
1464 static_cast<InsetInclude &>(*it);
1465 inset.updateBibfilesCache(*this);
1466 vector<FileName> const & bibfiles =
1467 inset.getBibfilesCache(*this);
1468 bibfilesCache_.insert(bibfilesCache_.end(),
1476 vector<FileName> const & Buffer::getBibfilesCache() const
1478 // if this is a child document and the parent is already loaded
1479 // use the parent's cache instead
1480 Buffer const * tmp = masterBuffer();
1483 return tmp->getBibfilesCache();
1485 // We update the cache when first used instead of at loading time.
1486 if (bibfilesCache_.empty())
1487 const_cast<Buffer *>(this)->updateBibfilesCache();
1489 return bibfilesCache_;
1493 bool Buffer::isDepClean(string const & name) const
1495 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1496 if (it == pimpl_->dep_clean.end())
1502 void Buffer::markDepClean(string const & name)
1504 pimpl_->dep_clean[name] = true;
1508 bool Buffer::dispatch(string const & command, bool * result)
1510 return dispatch(lyxaction.lookupFunc(command), result);
1514 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1516 bool dispatched = true;
1518 switch (func.action) {
1519 case LFUN_BUFFER_EXPORT: {
1520 bool const tmp = doExport(to_utf8(func.argument()), false);
1533 void Buffer::changeLanguage(Language const * from, Language const * to)
1538 for_each(par_iterator_begin(),
1540 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1544 bool Buffer::isMultiLingual() const
1546 ParConstIterator end = par_iterator_end();
1547 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1548 if (it->isMultiLingual(params()))
1555 ParIterator Buffer::getParFromID(int const id) const
1557 ParConstIterator it = par_iterator_begin();
1558 ParConstIterator const end = par_iterator_end();
1561 // John says this is called with id == -1 from undo
1562 lyxerr << "getParFromID(), id: " << id << endl;
1566 for (; it != end; ++it)
1574 bool Buffer::hasParWithID(int const id) const
1576 ParConstIterator const it = getParFromID(id);
1577 return it != par_iterator_end();
1581 ParIterator Buffer::par_iterator_begin()
1583 return lyx::par_iterator_begin(inset());
1587 ParIterator Buffer::par_iterator_end()
1589 return lyx::par_iterator_end(inset());
1593 ParConstIterator Buffer::par_iterator_begin() const
1595 return lyx::par_const_iterator_begin(inset());
1599 ParConstIterator Buffer::par_iterator_end() const
1601 return lyx::par_const_iterator_end(inset());
1605 Language const * Buffer::language() const
1607 return params().language;
1611 docstring const Buffer::B_(string const & l10n) const
1613 return params().B_(l10n);
1617 bool Buffer::isClean() const
1619 return pimpl_->lyx_clean;
1623 bool Buffer::isBakClean() const
1625 return pimpl_->bak_clean;
1629 bool Buffer::isExternallyModified(CheckMethod method) const
1631 BOOST_ASSERT(pimpl_->filename.exists());
1632 // if method == timestamp, check timestamp before checksum
1633 return (method == checksum_method
1634 || pimpl_->timestamp_ != pimpl_->filename.lastModified())
1635 && pimpl_->checksum_ != sum(pimpl_->filename);
1639 void Buffer::saveCheckSum(FileName const & file) const
1641 if (file.exists()) {
1642 pimpl_->timestamp_ = file.lastModified();
1643 pimpl_->checksum_ = sum(file);
1645 // in the case of save to a new file.
1646 pimpl_->timestamp_ = 0;
1647 pimpl_->checksum_ = 0;
1652 void Buffer::markClean() const
1654 if (!pimpl_->lyx_clean) {
1655 pimpl_->lyx_clean = true;
1658 // if the .lyx file has been saved, we don't need an
1660 pimpl_->bak_clean = true;
1664 void Buffer::markBakClean() const
1666 pimpl_->bak_clean = true;
1670 void Buffer::setUnnamed(bool flag)
1672 pimpl_->unnamed = flag;
1676 bool Buffer::isUnnamed() const
1678 return pimpl_->unnamed;
1682 // FIXME: this function should be moved to buffer_pimpl.C
1683 void Buffer::markDirty()
1685 if (pimpl_->lyx_clean) {
1686 pimpl_->lyx_clean = false;
1689 pimpl_->bak_clean = false;
1691 DepClean::iterator it = pimpl_->dep_clean.begin();
1692 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1694 for (; it != end; ++it)
1699 FileName Buffer::fileName() const
1701 return pimpl_->filename;
1705 string Buffer::absFileName() const
1707 return pimpl_->filename.absFilename();
1711 string const & Buffer::filePath() const
1713 return params().filepath;
1717 bool Buffer::isReadonly() const
1719 return pimpl_->read_only;
1723 void Buffer::setParentName(string const & name)
1725 if (name == pimpl_->filename.absFilename())
1726 // Avoids recursive include.
1727 params().parentname.clear();
1729 params().parentname = name;
1733 Buffer const * Buffer::masterBuffer() const
1735 if (!params().parentname.empty()
1736 && theBufferList().exists(params().parentname)) {
1737 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1738 //We need to check if the parent is us...
1739 //FIXME RECURSIVE INCLUDE
1740 //This is not sufficient, since recursive includes could be downstream.
1741 if (buf && buf != this)
1742 return buf->masterBuffer();
1749 Buffer * Buffer::masterBuffer()
1751 if (!params().parentname.empty()
1752 && theBufferList().exists(params().parentname)) {
1753 Buffer * buf = theBufferList().getBuffer(params().parentname);
1754 //We need to check if the parent is us...
1755 //FIXME RECURSIVE INCLUDE
1756 //This is not sufficient, since recursive includes could be downstream.
1757 if (buf && buf != this)
1758 return buf->masterBuffer();
1765 bool Buffer::hasMacro(docstring const & name, Paragraph const & par) const
1767 Impl::PositionToMacroMap::iterator it;
1768 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1769 if (it != pimpl_->macros[name].end())
1772 // If there is a master buffer, query that
1773 const Buffer * master = masterBuffer();
1774 if (master && master != this)
1775 return master->hasMacro(name);
1777 return MacroTable::globalMacros().has(name);
1781 bool Buffer::hasMacro(docstring const & name) const
1783 if( !pimpl_->macros[name].empty() )
1786 // If there is a master buffer, query that
1787 const Buffer * master = masterBuffer();
1788 if (master && master != this)
1789 return master->hasMacro(name);
1791 return MacroTable::globalMacros().has(name);
1795 MacroData const & Buffer::getMacro(docstring const & name,
1796 Paragraph const & par) const
1798 Impl::PositionToMacroMap::iterator it;
1799 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1800 if( it != pimpl_->macros[name].end() )
1803 // If there is a master buffer, query that
1804 const Buffer * master = masterBuffer();
1805 if (master && master != this)
1806 return master->getMacro(name);
1808 return MacroTable::globalMacros().get(name);
1812 MacroData const & Buffer::getMacro(docstring const & name) const
1814 Impl::PositionToMacroMap::iterator it;
1815 it = pimpl_->macros[name].begin();
1816 if( it != pimpl_->macros[name].end() )
1819 // If there is a master buffer, query that
1820 const Buffer * master = masterBuffer();
1821 if (master && master != this)
1822 return master->getMacro(name);
1824 return MacroTable::globalMacros().get(name);
1828 void Buffer::updateMacros()
1830 // start with empty table
1831 pimpl_->macros = Impl::NameToPositionMacroMap();
1833 // Iterate over buffer
1834 ParagraphList & pars = text().paragraphs();
1835 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1836 // set position again
1837 pars[i].setMacrocontextPosition(i);
1839 //lyxerr << "searching main par " << i
1840 // << " for macro definitions" << std::endl;
1841 InsetList const & insets = pars[i].insetList();
1842 InsetList::const_iterator it = insets.begin();
1843 InsetList::const_iterator end = insets.end();
1844 for ( ; it != end; ++it) {
1845 if (it->inset->lyxCode() != MATHMACRO_CODE)
1849 MathMacroTemplate const & macroTemplate
1850 = static_cast<MathMacroTemplate const &>(*it->inset);
1853 if (macroTemplate.validMacro()) {
1854 MacroData macro = macroTemplate.asMacroData();
1857 // call hasMacro here instead of directly querying mc to
1858 // also take the master document into consideration
1859 macro.setRedefinition(hasMacro(macroTemplate.name()));
1861 // register macro (possibly overwrite the previous one of this paragraph)
1862 pimpl_->macros[macroTemplate.name()][i] = macro;
1869 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1872 //FIXME: This does not work for child documents yet.
1873 BOOST_ASSERT(code == CITE_CODE || code == REF_CODE);
1874 // Check if the label 'from' appears more than once
1875 vector<docstring> labels;
1878 if (code == CITE_CODE) {
1880 keys.fillWithBibKeys(this);
1881 BiblioInfo::const_iterator bit = keys.begin();
1882 BiblioInfo::const_iterator bend = keys.end();
1884 for (; bit != bend; ++bit)
1886 labels.push_back(bit->first);
1889 getLabelList(labels);
1890 paramName = "reference";
1893 if (std::count(labels.begin(), labels.end(), from) > 1)
1896 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1897 if (it->lyxCode() == code) {
1898 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1899 docstring const oldValue = inset.getParam(paramName);
1900 if (oldValue == from)
1901 inset.setParam(paramName, to);
1907 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1908 pit_type par_end, bool full_source)
1910 OutputParams runparams(¶ms().encoding());
1911 runparams.nice = true;
1912 runparams.flavor = OutputParams::LATEX;
1913 runparams.linelen = lyxrc.plaintext_linelen;
1914 // No side effect of file copying and image conversion
1915 runparams.dryrun = true;
1919 os << "% " << _("Preview source code") << "\n\n";
1923 writeLaTeXSource(os, filePath(), runparams, true, true);
1925 writeDocBookSource(os, absFileName(), runparams, false);
1928 runparams.par_begin = par_begin;
1929 runparams.par_end = par_end;
1930 if (par_begin + 1 == par_end)
1932 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1936 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1937 convert<docstring>(par_begin),
1938 convert<docstring>(par_end - 1))
1942 // output paragraphs
1944 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1947 docbookParagraphs(paragraphs(), *this, os, runparams);
1953 ErrorList const & Buffer::errorList(string const & type) const
1955 static ErrorList const emptyErrorList;
1956 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1957 if (I == pimpl_->errorLists.end())
1958 return emptyErrorList;
1964 ErrorList & Buffer::errorList(string const & type)
1966 return pimpl_->errorLists[type];
1970 void Buffer::structureChanged() const
1973 gui_->structureChanged();
1977 void Buffer::errors(std::string const & err) const
1984 void Buffer::message(docstring const & msg) const
1991 void Buffer::setBusy(bool on) const
1998 void Buffer::setReadOnly(bool on) const
2001 pimpl_->wa_->setReadOnly(on);
2005 void Buffer::updateTitles() const
2008 pimpl_->wa_->updateTitles();
2012 void Buffer::resetAutosaveTimers() const
2015 gui_->resetAutosaveTimers();
2019 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
2028 class AutoSaveBuffer : public support::ForkedProcess {
2031 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
2032 : buffer_(buffer), fname_(fname) {}
2034 virtual boost::shared_ptr<ForkedProcess> clone() const
2036 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
2041 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
2042 from_utf8(fname_.absFilename())));
2043 return run(DontWait);
2047 virtual int generateChild();
2049 Buffer const & buffer_;
2054 #if !defined (HAVE_FORK)
2058 int AutoSaveBuffer::generateChild()
2060 // tmp_ret will be located (usually) in /tmp
2061 // will that be a problem?
2062 pid_t const pid = fork();
2063 // If you want to debug the autosave
2064 // you should set pid to -1, and comment out the fork.
2065 if (pid == 0 || pid == -1) {
2066 // pid = -1 signifies that lyx was unable
2067 // to fork. But we will do the save
2069 bool failed = false;
2071 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2072 if (!tmp_ret.empty()) {
2073 buffer_.writeFile(tmp_ret);
2074 // assume successful write of tmp_ret
2075 if (!rename(tmp_ret, fname_)) {
2077 // most likely couldn't move between
2078 // filesystems unless write of tmp_ret
2079 // failed so remove tmp file (if it
2088 // failed to write/rename tmp_ret so try writing direct
2089 if (!buffer_.writeFile(fname_)) {
2090 // It is dangerous to do this in the child,
2091 // but safe in the parent, so...
2092 if (pid == -1) // emit message signal.
2093 buffer_.message(_("Autosave failed!"));
2096 if (pid == 0) { // we are the child so...
2106 // Perfect target for a thread...
2107 void Buffer::autoSave() const
2109 if (isBakClean() || isReadonly()) {
2110 // We don't save now, but we'll try again later
2111 resetAutosaveTimers();
2115 // emit message signal.
2116 message(_("Autosaving current document..."));
2118 // create autosave filename
2119 string fname = filePath();
2121 fname += onlyFilename(absFileName());
2124 AutoSaveBuffer autosave(*this, FileName(fname));
2128 resetAutosaveTimers();
2132 /** Write a buffer to a new file name and rename the buffer
2133 according to the new file name.
2135 This function is e.g. used by menu callbacks and
2136 LFUN_BUFFER_WRITE_AS.
2138 If 'newname' is empty (the default), the user is asked via a
2139 dialog for the buffer's new name and location.
2141 If 'newname' is non-empty and has an absolute path, that is used.
2142 Otherwise the base directory of the buffer is used as the base
2143 for any relative path in 'newname'.
2146 bool Buffer::writeAs(string const & newname)
2148 string fname = absFileName();
2149 string const oldname = fname;
2151 if (newname.empty()) { /// No argument? Ask user through dialog
2154 FileDialog dlg(_("Choose a filename to save document as"),
2155 LFUN_BUFFER_WRITE_AS);
2156 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
2157 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
2159 if (!support::isLyXFilename(fname))
2162 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2164 FileDialog::Result result =
2165 dlg.save(from_utf8(onlyPath(fname)),
2167 from_utf8(onlyFilename(fname)));
2169 if (result.first == FileDialog::Later)
2172 fname = to_utf8(result.second);
2177 // Make sure the absolute filename ends with appropriate suffix
2178 fname = makeAbsPath(fname).absFilename();
2179 if (!support::isLyXFilename(fname))
2183 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2185 if (FileName(fname).exists()) {
2186 docstring const file = makeDisplayPath(fname, 30);
2187 docstring text = bformat(_("The document %1$s already "
2188 "exists.\n\nDo you want to "
2189 "overwrite that document?"),
2191 int const ret = Alert::prompt(_("Overwrite document?"),
2192 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2198 // Ok, change the name of the buffer
2201 bool unnamed = isUnnamed();
2203 saveCheckSum(FileName(fname));
2206 setFileName(oldname);
2207 setUnnamed(unnamed);
2208 saveCheckSum(FileName(oldname));
2212 removeAutosaveFile(oldname);
2217 bool Buffer::menuWrite()
2220 LyX::ref().session().lastFiles().add(FileName(absFileName()));
2224 // FIXME: we don't tell the user *WHY* the save failed !!
2226 docstring const file = makeDisplayPath(absFileName(), 30);
2228 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2229 "Do you want to rename the document and "
2230 "try again?"), file);
2231 int const ret = Alert::prompt(_("Rename and save?"),
2232 text, 0, 1, _("&Rename"), _("&Cancel"));
2241 void Buffer::loadChildDocuments() const
2243 bool parse_error = false;
2245 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2246 if (it->lyxCode() != INCLUDE_CODE)
2248 InsetCommand const & inset = static_cast<InsetCommand const &>(*it);
2249 InsetCommandParams const & ip = inset.params();
2250 Buffer * child = loadIfNeeded(*this, ip);
2253 parse_error |= !child->errorList("Parse").empty();
2254 child->loadChildDocuments();
2257 if (use_gui && masterBuffer() == this)
2258 updateLabels(*this);
2262 string Buffer::bufferFormat() const
2272 bool Buffer::doExport(string const & format, bool put_in_tempdir,
2273 string & result_file)
2275 string backend_format;
2276 OutputParams runparams(¶ms().encoding());
2277 runparams.flavor = OutputParams::LATEX;
2278 runparams.linelen = lyxrc.plaintext_linelen;
2279 vector<string> backs = backends();
2280 if (find(backs.begin(), backs.end(), format) == backs.end()) {
2281 // Get shortest path to format
2282 Graph::EdgePath path;
2283 for (vector<string>::const_iterator it = backs.begin();
2284 it != backs.end(); ++it) {
2285 Graph::EdgePath p = theConverters().getPath(*it, format);
2286 if (!p.empty() && (path.empty() || p.size() < path.size())) {
2287 backend_format = *it;
2292 runparams.flavor = theConverters().getFlavor(path);
2294 Alert::error(_("Couldn't export file"),
2295 bformat(_("No information for exporting the format %1$s."),
2296 formats.prettyName(format)));
2300 backend_format = format;
2301 // FIXME: Don't hardcode format names here, but use a flag
2302 if (backend_format == "pdflatex")
2303 runparams.flavor = OutputParams::PDFLATEX;
2306 string filename = latexName(false);
2307 filename = addName(temppath(), filename);
2308 filename = changeExtension(filename,
2309 formats.extension(backend_format));
2311 // Plain text backend
2312 if (backend_format == "text")
2313 writePlaintextFile(*this, FileName(filename), runparams);
2315 else if (backend_format == "lyx")
2316 writeFile(FileName(filename));
2318 else if (isDocBook()) {
2319 runparams.nice = !put_in_tempdir;
2320 makeDocBookFile(FileName(filename), runparams);
2323 else if (backend_format == format) {
2324 runparams.nice = true;
2325 if (!makeLaTeXFile(FileName(filename), string(), runparams))
2327 } else if (!lyxrc.tex_allows_spaces
2328 && support::contains(filePath(), ' ')) {
2329 Alert::error(_("File name error"),
2330 _("The directory path to the document cannot contain spaces."));
2333 runparams.nice = false;
2334 if (!makeLaTeXFile(FileName(filename), filePath(), runparams))
2338 string const error_type = (format == "program")
2339 ? "Build" : bufferFormat();
2340 string const ext = formats.extension(format);
2341 FileName const tmp_result_file(changeExtension(filename, ext));
2342 bool const success = theConverters().convert(this, FileName(filename),
2343 tmp_result_file, FileName(absFileName()), backend_format, format,
2344 errorList(error_type));
2345 // Emit the signal to show the error list.
2346 if (format != backend_format)
2352 result_file = tmp_result_file.absFilename();
2354 result_file = changeExtension(absFileName(), ext);
2355 // We need to copy referenced files (e. g. included graphics
2356 // if format == "dvi") to the result dir.
2357 vector<ExportedFile> const files =
2358 runparams.exportdata->externalFiles(format);
2359 string const dest = onlyPath(result_file);
2360 CopyStatus status = SUCCESS;
2361 for (vector<ExportedFile>::const_iterator it = files.begin();
2362 it != files.end() && status != CANCEL; ++it) {
2364 formats.getFormatFromFile(it->sourceName);
2365 status = copyFile(fmt, it->sourceName,
2366 makeAbsPath(it->exportName, dest),
2367 it->exportName, status == FORCE);
2369 if (status == CANCEL) {
2370 message(_("Document export cancelled."));
2371 } else if (tmp_result_file.exists()) {
2372 // Finally copy the main file
2373 status = copyFile(format, tmp_result_file,
2374 FileName(result_file), result_file,
2376 message(bformat(_("Document exported as %1$s "
2378 formats.prettyName(format),
2379 makeDisplayPath(result_file)));
2381 // This must be a dummy converter like fax (bug 1888)
2382 message(bformat(_("Document exported as %1$s"),
2383 formats.prettyName(format)));
2391 bool Buffer::doExport(string const & format, bool put_in_tempdir)
2394 return doExport(format, put_in_tempdir, result_file);
2398 bool Buffer::preview(string const & format)
2401 if (!doExport(format, true, result_file))
2403 return formats.view(*this, FileName(result_file), format);
2407 bool Buffer::isExportable(string const & format) const
2409 vector<string> backs = backends();
2410 for (vector<string>::const_iterator it = backs.begin();
2411 it != backs.end(); ++it)
2412 if (theConverters().isReachable(*it, format))
2418 vector<Format const *> Buffer::exportableFormats(bool only_viewable) const
2420 vector<string> backs = backends();
2421 vector<Format const *> result =
2422 theConverters().getReachable(backs[0], only_viewable, true);
2423 for (vector<string>::const_iterator it = backs.begin() + 1;
2424 it != backs.end(); ++it) {
2425 vector<Format const *> r =
2426 theConverters().getReachable(*it, only_viewable, false);
2427 result.insert(result.end(), r.begin(), r.end());
2433 vector<string> Buffer::backends() const
2436 if (params().getTextClass().isTeXClassAvailable()) {
2437 v.push_back(bufferFormat());
2438 // FIXME: Don't hardcode format names here, but use a flag
2439 if (v.back() == "latex")
2440 v.push_back("pdflatex");
2442 v.push_back("text");
2448 bool Buffer::readFileHelper(FileName const & s)
2450 // File information about normal file
2452 docstring const file = makeDisplayPath(s.absFilename(), 50);
2453 docstring text = bformat(_("The specified document\n%1$s"
2454 "\ncould not be read."), file);
2455 Alert::error(_("Could not read document"), text);
2459 // Check if emergency save file exists and is newer.
2460 FileName const e(s.absFilename() + ".emergency");
2462 if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) {
2463 docstring const file = makeDisplayPath(s.absFilename(), 20);
2464 docstring const text =
2465 bformat(_("An emergency save of the document "
2467 "Recover emergency save?"), file);
2468 switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
2469 _("&Recover"), _("&Load Original"),
2473 // the file is not saved if we load the emergency file.
2483 // Now check if autosave file is newer.
2484 FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#');
2486 if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) {
2487 docstring const file = makeDisplayPath(s.absFilename(), 20);
2488 docstring const text =
2489 bformat(_("The backup of the document "
2490 "%1$s is newer.\n\nLoad the "
2491 "backup instead?"), file);
2492 switch (Alert::prompt(_("Load backup?"), text, 0, 2,
2493 _("&Load backup"), _("Load &original"),
2497 // the file is not saved if we load the autosave file.
2501 // Here we delete the autosave
2512 bool Buffer::loadLyXFile(FileName const & s)
2514 if (s.isReadable()) {
2515 if (readFileHelper(s)) {
2516 lyxvc().file_found_hook(s);
2517 if (!s.isWritable())
2522 docstring const file = makeDisplayPath(s.absFilename(), 20);
2523 // Here we probably should run
2524 if (LyXVC::file_not_found_hook(s)) {
2525 docstring const text =
2526 bformat(_("Do you want to retrieve the document"
2527 " %1$s from version control?"), file);
2528 int const ret = Alert::prompt(_("Retrieve from version control?"),
2529 text, 0, 1, _("&Retrieve"), _("&Cancel"));
2532 // How can we know _how_ to do the checkout?
2533 // With the current VC support it has to be,
2534 // a RCS file since CVS do not have special ,v files.
2536 return loadLyXFile(s);
2544 void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
2546 TeXErrors::Errors::const_iterator cit = terr.begin();
2547 TeXErrors::Errors::const_iterator end = terr.end();
2549 for (; cit != end; ++cit) {
2552 int errorRow = cit->error_in_line;
2553 bool found = texrow().getIdFromRow(errorRow, id_start,
2559 found = texrow().getIdFromRow(errorRow, id_end, pos_end);
2560 } while (found && id_start == id_end && pos_start == pos_end);
2562 errorList.push_back(ErrorItem(cit->error_desc,
2563 cit->error_text, id_start, pos_start, pos_end));