3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Stefan Schimanski
9 * Full author contact details are available in file CREDITS.
17 #include "BiblioInfo.h"
18 #include "BranchList.h"
19 #include "buffer_funcs.h"
20 #include "BufferList.h"
21 #include "BufferParams.h"
24 #include "Converter.h"
26 #include "DocIterator.h"
27 #include "EmbeddedFiles.h"
29 #include "ErrorList.h"
32 #include "FuncRequest.h"
33 #include "InsetIterator.h"
34 #include "InsetList.h"
36 #include "LaTeXFeatures.h"
40 #include "LyXAction.h"
44 #include "output_docbook.h"
46 #include "output_latex.h"
47 #include "output_plaintext.h"
48 #include "paragraph_funcs.h"
49 #include "Paragraph.h"
50 #include "ParagraphParameters.h"
51 #include "ParIterator.h"
52 #include "PDFOptions.h"
56 #include "TexStream.h"
57 #include "TextClassList.h"
59 #include "TocBackend.h"
61 #include "VCBackend.h"
64 #include "insets/InsetBibitem.h"
65 #include "insets/InsetBibtex.h"
66 #include "insets/InsetInclude.h"
67 #include "insets/InsetText.h"
69 #include "mathed/MacroTable.h"
70 #include "mathed/MathMacroTemplate.h"
71 #include "mathed/MathSupport.h"
73 #include "frontends/alert.h"
74 #include "frontends/Delegates.h"
75 #include "frontends/WorkAreaManager.h"
76 #include "frontends/FileDialog.h"
78 #include "graphics/Previews.h"
80 #include "support/convert.h"
81 #include "support/debug.h"
82 #include "support/FileFilterList.h"
83 #include "support/filetools.h"
84 #include "support/ForkedCalls.h"
85 #include "support/gettext.h"
86 #include "support/gzstream.h"
87 #include "support/lstrings.h"
88 #include "support/lyxalgo.h"
89 #include "support/lyxlib.h"
90 #include "support/os.h"
91 #include "support/Path.h"
92 #include "support/textutils.h"
93 #include "support/types.h"
95 #if !defined (HAVE_FORK)
99 #include <boost/bind.hpp>
100 #include <boost/shared_ptr.hpp>
110 using std::make_pair;
115 using std::ostringstream;
126 using support::addName;
127 using support::bformat;
128 using support::changeExtension;
129 using support::cmd_ret;
130 using support::createBufferTmpDir;
131 using support::FileName;
132 using support::libFileSearch;
133 using support::latex_path;
134 using support::ltrim;
135 using support::makeAbsPath;
136 using support::makeDisplayPath;
137 using support::makeLatexName;
138 using support::onlyFilename;
139 using support::onlyPath;
140 using support::quoteName;
141 using support::removeAutosaveFile;
142 using support::rename;
143 using support::runCommand;
144 using support::split;
145 using support::subst;
146 using support::tempName;
148 using support::suffixIs;
150 namespace Alert = frontend::Alert;
151 namespace os = support::os;
155 int const LYX_FORMAT = 303; // Uwe: Serbocroatian
160 typedef std::map<string, bool> DepClean;
165 Impl(Buffer & parent, FileName const & file, bool readonly);
172 /// need to regenerate .tex?
176 mutable bool lyx_clean;
178 /// is autosave needed?
179 mutable bool bak_clean;
181 /// is this a unnamed file (New...)?
187 /// name of the file the buffer is associated with.
190 /** Set to true only when the file is fully loaded.
191 * Used to prevent the premature generation of previews
192 * and by the citation inset.
194 bool file_fully_loaded;
196 /// our Text that should be wrapped in an InsetText
200 TocBackend toc_backend;
203 typedef std::map<unsigned int, MacroData, std::greater<int> > PositionToMacroMap;
204 typedef std::map<docstring, PositionToMacroMap> NameToPositionMacroMap;
205 NameToPositionMacroMap macros;
207 /// Container for all sort of Buffer dependant errors.
208 map<string, ErrorList> errorLists;
210 /// all embedded files of this buffer
211 EmbeddedFiles embedded_files;
213 /// timestamp and checksum used to test if the file has been externally
214 /// modified. (Used to properly enable 'File->Revert to saved', bug 4114).
216 unsigned long checksum_;
219 frontend::WorkAreaManager * wa_;
226 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
227 : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
228 filename(file), file_fully_loaded(false), inset(params),
229 toc_backend(&parent), embedded_files(&parent), timestamp_(0),
230 checksum_(0), wa_(0), undo_(parent)
232 inset.setAutoBreakRows(true);
233 lyxvc.setBuffer(&parent);
234 temppath = createBufferTmpDir();
236 // FIXME: And now do something if temppath == string(), because we
237 // assume from now on that temppath points to a valid temp dir.
238 // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html
241 wa_ = new frontend::WorkAreaManager;
245 Buffer::Buffer(string const & file, bool readonly)
246 : pimpl_(new Impl(*this, FileName(file), readonly)), gui_(0)
248 LYXERR(Debug::INFO, "Buffer::Buffer()");
254 LYXERR(Debug::INFO, "Buffer::~Buffer()");
255 // here the buffer should take care that it is
256 // saved properly, before it goes into the void.
258 Buffer * master = masterBuffer();
259 if (master != this && use_gui)
260 // We are closing buf which was a child document so we
261 // must update the labels and section numbering of its master
263 updateLabels(*master);
265 if (!temppath().empty() && !FileName(temppath()).destroyDirectory()) {
266 Alert::warning(_("Could not remove temporary directory"),
267 bformat(_("Could not remove the temporary directory %1$s"),
268 from_utf8(temppath())));
271 // Remove any previewed LaTeX snippets associated with this buffer.
272 graphics::Previews::get().removeLoader(*this);
275 pimpl_->wa_->closeAll();
282 void Buffer::changed() const
285 pimpl_->wa_->redrawAll();
289 frontend::WorkAreaManager & Buffer::workAreaManager() const
291 BOOST_ASSERT(pimpl_->wa_);
296 Text & Buffer::text() const
298 return const_cast<Text &>(pimpl_->inset.text_);
302 Inset & Buffer::inset() const
304 return const_cast<InsetText &>(pimpl_->inset);
308 BufferParams & Buffer::params()
310 return pimpl_->params;
314 BufferParams const & Buffer::params() const
316 return pimpl_->params;
320 ParagraphList & Buffer::paragraphs()
322 return text().paragraphs();
326 ParagraphList const & Buffer::paragraphs() const
328 return text().paragraphs();
332 LyXVC & Buffer::lyxvc()
334 return pimpl_->lyxvc;
338 LyXVC const & Buffer::lyxvc() const
340 return pimpl_->lyxvc;
344 string const & Buffer::temppath() const
346 return pimpl_->temppath;
350 TexRow & Buffer::texrow()
352 return pimpl_->texrow;
356 TexRow const & Buffer::texrow() const
358 return pimpl_->texrow;
362 TocBackend & Buffer::tocBackend()
364 return pimpl_->toc_backend;
368 TocBackend const & Buffer::tocBackend() const
370 return pimpl_->toc_backend;
374 EmbeddedFiles & Buffer::embeddedFiles()
376 return pimpl_->embedded_files;
380 EmbeddedFiles const & Buffer::embeddedFiles() const
382 return pimpl_->embedded_files;
386 Undo & Buffer::undo()
388 return pimpl_->undo_;
392 string Buffer::latexName(bool const no_path) const
394 FileName latex_name = makeLatexName(pimpl_->filename);
395 return no_path ? latex_name.onlyFileName()
396 : latex_name.absFilename();
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);
427 return bname.absFilename();
429 LYXERR(Debug::FILES, "Log name calculated as: " << fname);
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 setReadonly(pimpl_->filename.isReadOnly());
453 int Buffer::readHeader(Lexer & lex)
455 int unknown_tokens = 0;
457 int begin_header_line = -1;
459 // Initialize parameters that may be/go lacking in header:
460 params().branchlist().clear();
461 params().preamble.erase();
462 params().options.erase();
463 params().float_placement.erase();
464 params().paperwidth.erase();
465 params().paperheight.erase();
466 params().leftmargin.erase();
467 params().rightmargin.erase();
468 params().topmargin.erase();
469 params().bottommargin.erase();
470 params().headheight.erase();
471 params().headsep.erase();
472 params().footskip.erase();
473 params().listings_params.clear();
474 params().clearLayoutModules();
475 params().pdfoptions().clear();
477 for (int i = 0; i < 4; ++i) {
478 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
479 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
482 ErrorList & errorList = pimpl_->errorLists["Parse"];
486 string const token = lex.getString();
491 if (token == "\\end_header")
495 if (token == "\\begin_header") {
496 begin_header_line = line;
500 LYXERR(Debug::PARSER, "Handling document header token: `"
503 string unknown = params().readToken(lex, token, pimpl_->filename.onlyPath());
504 if (!unknown.empty()) {
505 if (unknown[0] != '\\' && token == "\\textclass") {
506 Alert::warning(_("Unknown document class"),
507 bformat(_("Using the default document class, because the "
508 "class %1$s is unknown."), from_utf8(unknown)));
511 docstring const s = bformat(_("Unknown token: "
515 errorList.push_back(ErrorItem(_("Document header error"),
520 if (begin_header_line) {
521 docstring const s = _("\\begin_header is missing");
522 errorList.push_back(ErrorItem(_("Document header error"),
526 return unknown_tokens;
531 // changed to be public and have one parameter
532 // Returns false if "\end_document" is not read (Asger)
533 bool Buffer::readDocument(Lexer & lex)
535 ErrorList & errorList = pimpl_->errorLists["Parse"];
539 string const token = lex.getString();
540 if (token != "\\begin_document") {
541 docstring const s = _("\\begin_document is missing");
542 errorList.push_back(ErrorItem(_("Document header error"),
546 // we are reading in a brand new document
547 BOOST_ASSERT(paragraphs().empty());
550 TextClass const & baseClass = textclasslist[params().getBaseClass()];
551 if (!baseClass.load(filePath())) {
552 string theclass = baseClass.name();
553 Alert::error(_("Can't load document class"), bformat(
554 _("Using the default document class, because the "
555 "class %1$s could not be loaded."), from_utf8(theclass)));
556 params().setBaseClass(defaultTextclass());
559 if (params().outputChanges) {
560 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
561 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
562 LaTeXFeatures::isAvailable("xcolor");
564 if (!dvipost && !xcolorsoul) {
565 Alert::warning(_("Changes not shown in LaTeX output"),
566 _("Changes will not be highlighted in LaTeX output, "
567 "because neither dvipost nor xcolor/soul are installed.\n"
568 "Please install these packages or redefine "
569 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
570 } else if (!xcolorsoul) {
571 Alert::warning(_("Changes not shown in LaTeX output"),
572 _("Changes will not be highlighted in LaTeX output "
573 "when using pdflatex, because xcolor and soul are not installed.\n"
574 "Please install both packages or redefine "
575 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
580 bool const res = text().read(*this, lex, errorList);
581 for_each(text().paragraphs().begin(),
582 text().paragraphs().end(),
583 bind(&Paragraph::setInsetOwner, _1, &inset()));
589 // needed to insert the selection
590 void Buffer::insertStringAsLines(ParagraphList & pars,
591 pit_type & pit, pos_type & pos,
592 Font const & fn, docstring const & str, bool autobreakrows)
596 // insert the string, don't insert doublespace
597 bool space_inserted = true;
598 for (docstring::const_iterator cit = str.begin();
599 cit != str.end(); ++cit) {
600 Paragraph & par = pars[pit];
602 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
603 breakParagraph(params(), pars, pit, pos,
604 par.layout()->isEnvironment());
607 space_inserted = true;
611 // do not insert consecutive spaces if !free_spacing
612 } else if ((*cit == ' ' || *cit == '\t') &&
613 space_inserted && !par.isFreeSpacing()) {
615 } else if (*cit == '\t') {
616 if (!par.isFreeSpacing()) {
617 // tabs are like spaces here
618 par.insertChar(pos, ' ', font, params().trackChanges);
620 space_inserted = true;
622 const pos_type n = 8 - pos % 8;
623 for (pos_type i = 0; i < n; ++i) {
624 par.insertChar(pos, ' ', font, params().trackChanges);
627 space_inserted = true;
629 } else if (!isPrintable(*cit)) {
630 // Ignore unprintables
633 // just insert the character
634 par.insertChar(pos, *cit, font, params().trackChanges);
636 space_inserted = (*cit == ' ');
643 bool Buffer::readString(std::string const & s)
645 params().compressed = false;
647 // remove dummy empty par
648 paragraphs().clear();
650 std::istringstream is(s);
652 FileName const name(tempName());
653 switch (readFile(lex, name, true)) {
657 // We need to call lyx2lyx, so write the input to a file
658 std::ofstream os(name.toFilesystemEncoding().c_str());
661 return readFile(name);
671 bool Buffer::readFile(FileName const & filename)
673 FileName fname(filename);
674 // Check if the file is compressed.
675 string format = filename.guessFormatFromContents();
676 if (format == "zip") {
677 // decompress to a temp directory
678 LYXERR(Debug::FILES, filename << " is in zip format. Unzip to " << temppath());
679 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
681 FileName lyxfile(addName(temppath(), "content.lyx"));
682 // if both manifest.txt and file.lyx exist, this is am embedded file
683 if (lyxfile.exists()) {
684 params().embedded = true;
688 // The embedded lyx file can also be compressed, for backward compatibility
689 format = fname.guessFormatFromContents();
690 if (format == "gzip" || format == "zip" || format == "compress")
691 params().compressed = true;
693 // remove dummy empty par
694 paragraphs().clear();
697 if (readFile(lex, fname) != success)
704 bool Buffer::isFullyLoaded() const
706 return pimpl_->file_fully_loaded;
710 void Buffer::setFullyLoaded(bool value)
712 pimpl_->file_fully_loaded = value;
716 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
719 BOOST_ASSERT(!filename.empty());
722 Alert::error(_("Document could not be read"),
723 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
728 string const token = lex.getString();
731 Alert::error(_("Document could not be read"),
732 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
736 // the first token _must_ be...
737 if (token != "\\lyxformat") {
738 lyxerr << "Token: " << token << endl;
740 Alert::error(_("Document format failure"),
741 bformat(_("%1$s is not a LyX document."),
742 from_utf8(filename.absFilename())));
747 string tmp_format = lex.getString();
748 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
749 // if present remove ".," from string.
750 string::size_type dot = tmp_format.find_first_of(".,");
751 //lyxerr << " dot found at " << dot << endl;
752 if (dot != string::npos)
753 tmp_format.erase(dot, 1);
754 int const file_format = convert<int>(tmp_format);
755 //lyxerr << "format: " << file_format << endl;
757 // save timestamp and checksum of the original disk file, making sure
758 // to not overwrite them with those of the file created in the tempdir
759 // when it has to be converted to the current format.
760 if (!pimpl_->checksum_) {
761 // Save the timestamp and checksum of disk file. If filename is an
762 // emergency file, save the timestamp and checksum of the original lyx file
763 // because isExternallyModified will check for this file. (BUG4193)
764 string diskfile = filename.absFilename();
765 if (suffixIs(diskfile, ".emergency"))
766 diskfile = diskfile.substr(0, diskfile.size() - 10);
767 saveCheckSum(FileName(diskfile));
770 if (file_format != LYX_FORMAT) {
773 // lyx2lyx would fail
776 FileName const tmpfile(tempName());
777 if (tmpfile.empty()) {
778 Alert::error(_("Conversion failed"),
779 bformat(_("%1$s is from a different"
780 " version of LyX, but a temporary"
781 " file for converting it could"
783 from_utf8(filename.absFilename())));
786 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
787 if (lyx2lyx.empty()) {
788 Alert::error(_("Conversion script not found"),
789 bformat(_("%1$s is from a different"
790 " version of LyX, but the"
791 " conversion script lyx2lyx"
792 " could not be found."),
793 from_utf8(filename.absFilename())));
796 ostringstream command;
797 command << os::python()
798 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
799 << " -t " << convert<string>(LYX_FORMAT)
800 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
801 << ' ' << quoteName(filename.toFilesystemEncoding());
802 string const command_str = command.str();
804 LYXERR(Debug::INFO, "Running '" << command_str << '\'');
806 cmd_ret const ret = runCommand(command_str);
807 if (ret.first != 0) {
808 Alert::error(_("Conversion script failed"),
809 bformat(_("%1$s is from a different version"
810 " of LyX, but the lyx2lyx script"
811 " failed to convert it."),
812 from_utf8(filename.absFilename())));
815 bool const ret = readFile(tmpfile);
816 // Do stuff with tmpfile name and buffer name here.
817 return ret ? success : failure;
822 if (readDocument(lex)) {
823 Alert::error(_("Document format failure"),
824 bformat(_("%1$s ended unexpectedly, which means"
825 " that it is probably corrupted."),
826 from_utf8(filename.absFilename())));
829 pimpl_->file_fully_loaded = true;
834 // Should probably be moved to somewhere else: BufferView? LyXView?
835 bool Buffer::save() const
837 // We don't need autosaves in the immediate future. (Asger)
838 resetAutosaveTimers();
840 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
843 bool madeBackup = false;
845 // make a backup if the file already exists
846 if (lyxrc.make_backup && fileName().exists()) {
847 backupName = FileName(absFileName() + '~');
848 if (!lyxrc.backupdir_path.empty()) {
849 string const mangledName =
850 subst(subst(backupName.absFilename(), '/', '!'), ':', '!');
851 backupName = FileName(addName(lyxrc.backupdir_path,
854 if (fileName().copyTo(backupName, true)) {
857 Alert::error(_("Backup failure"),
858 bformat(_("Cannot create backup file %1$s.\n"
859 "Please check whether the directory exists and is writeable."),
860 from_utf8(backupName.absFilename())));
861 //LYXERR(Debug::DEBUG, "Fs error: " << fe.what());
865 // ask if the disk file has been externally modified (use checksum method)
866 if (fileName().exists() && isExternallyModified(checksum_method)) {
867 docstring const file = makeDisplayPath(absFileName(), 20);
868 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
869 "you want to overwrite this file?"), file);
870 int const ret = Alert::prompt(_("Overwrite modified file?"),
871 text, 1, 1, _("&Overwrite"), _("&Cancel"));
876 if (writeFile(pimpl_->filename)) {
878 removeAutosaveFile(absFileName());
879 saveCheckSum(pimpl_->filename);
882 // Saving failed, so backup is not backup
884 rename(backupName, pimpl_->filename);
890 bool Buffer::writeFile(FileName const & fname) const
892 if (pimpl_->read_only && fname == pimpl_->filename)
898 if (params().embedded)
899 // first write the .lyx file to the temporary directory
900 content = FileName(addName(temppath(), "content.lyx"));
904 if (params().compressed) {
905 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
911 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
918 if (retval && params().embedded) {
919 // write file.lyx and all the embedded files to the zip file fname
920 // if embedding is enabled
921 return pimpl_->embedded_files.writeFile(fname);
927 bool Buffer::write(ostream & ofs) const
930 // Use the standard "C" locale for file output.
931 ofs.imbue(std::locale::classic());
934 // The top of the file should not be written by params().
936 // write out a comment in the top of the file
937 ofs << "#LyX " << lyx_version
938 << " created this file. For more info see http://www.lyx.org/\n"
939 << "\\lyxformat " << LYX_FORMAT << "\n"
940 << "\\begin_document\n";
943 /// For each author, set 'used' to true if there is a change
944 /// by this author in the document; otherwise set it to 'false'.
945 AuthorList::Authors::const_iterator a_it = params().authors().begin();
946 AuthorList::Authors::const_iterator a_end = params().authors().end();
947 for (; a_it != a_end; ++a_it)
948 a_it->second.setUsed(false);
950 ParIterator const end = par_iterator_end();
951 ParIterator it = par_iterator_begin();
952 for ( ; it != end; ++it)
953 it->checkAuthors(params().authors());
955 // now write out the buffer parameters.
956 ofs << "\\begin_header\n";
957 params().writeFile(ofs);
958 ofs << "\\end_header\n";
961 ofs << "\n\\begin_body\n";
962 text().write(*this, ofs);
963 ofs << "\n\\end_body\n";
965 // Write marker that shows file is complete
966 ofs << "\\end_document" << endl;
968 // Shouldn't really be needed....
971 // how to check if close went ok?
972 // Following is an attempt... (BE 20001011)
974 // good() returns false if any error occured, including some
976 // bad() returns true if something bad happened in the buffer,
977 // which should include file system full errors.
982 lyxerr << "File was not closed properly." << endl;
989 bool Buffer::makeLaTeXFile(FileName const & fname,
990 string const & original_path,
991 OutputParams const & runparams,
992 bool output_preamble, bool output_body)
994 string const encoding = runparams.encoding->iconvName();
995 LYXERR(Debug::LATEX, "makeLaTeXFile encoding: " << encoding << "...");
997 odocfstream ofs(encoding);
998 if (!openFileWrite(ofs, fname))
1001 //TexStream ts(ofs.rdbuf(), &texrow());
1003 bool failed_export = false;
1006 writeLaTeXSource(ofs, original_path,
1007 runparams, output_preamble, output_body);
1009 catch (iconv_codecvt_facet_exception & e) {
1010 lyxerr << "Caught iconv exception: " << e.what() << endl;
1011 failed_export = true;
1013 catch (std::exception const & e) {
1014 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1015 failed_export = true;
1018 lyxerr << "Caught some really weird exception..." << endl;
1019 LyX::cref().emergencyCleanup();
1025 failed_export = true;
1026 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1029 if (failed_export) {
1030 Alert::error(_("Encoding error"),
1031 _("Some characters of your document are probably not "
1032 "representable in the chosen encoding.\n"
1033 "Changing the document encoding to utf8 could help."));
1040 void Buffer::writeLaTeXSource(odocstream & os,
1041 string const & original_path,
1042 OutputParams const & runparams_in,
1043 bool const output_preamble, bool const output_body)
1045 OutputParams runparams = runparams_in;
1047 // validate the buffer.
1048 LYXERR(Debug::LATEX, " Validating buffer...");
1049 LaTeXFeatures features(*this, params(), runparams);
1051 LYXERR(Debug::LATEX, " Buffer validation done.");
1053 // The starting paragraph of the coming rows is the
1054 // first paragraph of the document. (Asger)
1055 if (output_preamble && runparams.nice) {
1056 os << "%% LyX " << lyx_version << " created this file. "
1057 "For more info, see http://www.lyx.org/.\n"
1058 "%% Do not edit unless you really know what "
1063 LYXERR(Debug::INFO, "lyx document header finished");
1064 // There are a few differences between nice LaTeX and usual files:
1065 // usual is \batchmode and has a
1066 // special input@path to allow the including of figures
1067 // with either \input or \includegraphics (what figinsets do).
1068 // input@path is set when the actual parameter
1069 // original_path is set. This is done for usual tex-file, but not
1070 // for nice-latex-file. (Matthias 250696)
1071 // Note that input@path is only needed for something the user does
1072 // in the preamble, included .tex files or ERT, files included by
1073 // LyX work without it.
1074 if (output_preamble) {
1075 if (!runparams.nice) {
1076 // code for usual, NOT nice-latex-file
1077 os << "\\batchmode\n"; // changed
1078 // from \nonstopmode
1081 if (!original_path.empty()) {
1083 // We don't know the encoding of inputpath
1084 docstring const inputpath = from_utf8(latex_path(original_path));
1085 os << "\\makeatletter\n"
1086 << "\\def\\input@path{{"
1087 << inputpath << "/}}\n"
1088 << "\\makeatother\n";
1094 // Write the preamble
1095 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1101 os << "\\begin{document}\n";
1103 } // output_preamble
1105 texrow().start(paragraphs().begin()->id(), 0);
1107 LYXERR(Debug::INFO, "preamble finished, now the body.");
1109 if (!lyxrc.language_auto_begin &&
1110 !params().language->babel().empty()) {
1112 os << from_utf8(subst(lyxrc.language_command_begin,
1114 params().language->babel()))
1119 Encoding const & encoding = params().encoding();
1120 if (encoding.package() == Encoding::CJK) {
1121 // Open a CJK environment, since in contrast to the encodings
1122 // handled by inputenc the document encoding is not set in
1123 // the preamble if it is handled by CJK.sty.
1124 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1129 // if we are doing a real file with body, even if this is the
1130 // child of some other buffer, let's cut the link here.
1131 // This happens for example if only a child document is printed.
1132 string save_parentname;
1133 if (output_preamble) {
1134 save_parentname = params().parentname;
1135 params().parentname.erase();
1138 loadChildDocuments();
1141 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1143 // Restore the parenthood if needed
1144 if (output_preamble)
1145 params().parentname = save_parentname;
1147 // add this just in case after all the paragraphs
1151 if (encoding.package() == Encoding::CJK) {
1152 // Close the open CJK environment.
1153 // latexParagraphs will have opened one even if the last text
1155 os << "\\end{CJK}\n";
1159 if (!lyxrc.language_auto_end &&
1160 !params().language->babel().empty()) {
1161 os << from_utf8(subst(lyxrc.language_command_end,
1163 params().language->babel()))
1168 if (output_preamble) {
1169 os << "\\end{document}\n";
1171 LYXERR(Debug::LATEX, "makeLaTeXFile...done");
1173 LYXERR(Debug::LATEX, "LaTeXFile for inclusion made.");
1175 runparams_in.encoding = runparams.encoding;
1177 // Just to be sure. (Asger)
1180 LYXERR(Debug::INFO, "Finished making LaTeX file.");
1181 LYXERR(Debug::INFO, "Row count was " << texrow().rows() - 1 << '.');
1185 bool Buffer::isLatex() const
1187 return params().getTextClass().outputType() == LATEX;
1191 bool Buffer::isLiterate() const
1193 return params().getTextClass().outputType() == LITERATE;
1197 bool Buffer::isDocBook() const
1199 return params().getTextClass().outputType() == DOCBOOK;
1203 void Buffer::makeDocBookFile(FileName const & fname,
1204 OutputParams const & runparams,
1205 bool const body_only)
1207 LYXERR(Debug::LATEX, "makeDocBookFile...");
1211 if (!openFileWrite(ofs, fname))
1214 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1218 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1222 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1223 OutputParams const & runparams,
1224 bool const only_body)
1226 LaTeXFeatures features(*this, params(), runparams);
1231 TextClass const & tclass = params().getTextClass();
1232 string const top_element = tclass.latexname();
1235 if (runparams.flavor == OutputParams::XML)
1236 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1239 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1242 if (! tclass.class_header().empty())
1243 os << from_ascii(tclass.class_header());
1244 else if (runparams.flavor == OutputParams::XML)
1245 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1246 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1248 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1250 docstring preamble = from_utf8(params().preamble);
1251 if (runparams.flavor != OutputParams::XML ) {
1252 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1253 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1254 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1255 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1258 string const name = runparams.nice
1259 ? changeExtension(absFileName(), ".sgml") : fname;
1260 preamble += features.getIncludedFiles(name);
1261 preamble += features.getLyXSGMLEntities();
1263 if (!preamble.empty()) {
1264 os << "\n [ " << preamble << " ]";
1269 string top = top_element;
1271 if (runparams.flavor == OutputParams::XML)
1272 top += params().language->code();
1274 top += params().language->code().substr(0,2);
1277 if (!params().options.empty()) {
1279 top += params().options;
1282 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1283 << " file was created by LyX " << lyx_version
1284 << "\n See http://www.lyx.org/ for more information -->\n";
1286 params().getTextClass().counters().reset();
1288 loadChildDocuments();
1290 sgml::openTag(os, top);
1292 docbookParagraphs(paragraphs(), *this, os, runparams);
1293 sgml::closeTag(os, top_element);
1297 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1298 // Other flags: -wall -v0 -x
1299 int Buffer::runChktex()
1303 // get LaTeX-Filename
1304 FileName const path(temppath());
1305 string const name = addName(path.absFilename(), latexName());
1306 string const org_path = filePath();
1308 support::PathChanger p(path); // path to LaTeX file
1309 message(_("Running chktex..."));
1311 // Generate the LaTeX file if neccessary
1312 OutputParams runparams(¶ms().encoding());
1313 runparams.flavor = OutputParams::LATEX;
1314 runparams.nice = false;
1315 makeLaTeXFile(FileName(name), org_path, runparams);
1318 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1319 int const res = chktex.run(terr); // run chktex
1322 Alert::error(_("chktex failure"),
1323 _("Could not run chktex successfully."));
1324 } else if (res > 0) {
1325 ErrorList & errlist = pimpl_->errorLists["ChkTeX"];
1327 bufferErrors(terr, errlist);
1338 void Buffer::validate(LaTeXFeatures & features) const
1340 TextClass const & tclass = params().getTextClass();
1342 if (params().outputChanges) {
1343 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1344 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1345 LaTeXFeatures::isAvailable("xcolor");
1347 if (features.runparams().flavor == OutputParams::LATEX) {
1349 features.require("ct-dvipost");
1350 features.require("dvipost");
1351 } else if (xcolorsoul) {
1352 features.require("ct-xcolor-soul");
1353 features.require("soul");
1354 features.require("xcolor");
1356 features.require("ct-none");
1358 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1360 features.require("ct-xcolor-soul");
1361 features.require("soul");
1362 features.require("xcolor");
1363 features.require("pdfcolmk"); // improves color handling in PDF output
1365 features.require("ct-none");
1370 // AMS Style is at document level
1371 if (params().use_amsmath == BufferParams::package_on
1372 || tclass.provides("amsmath"))
1373 features.require("amsmath");
1374 if (params().use_esint == BufferParams::package_on)
1375 features.require("esint");
1377 loadChildDocuments();
1379 for_each(paragraphs().begin(), paragraphs().end(),
1380 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1382 // the bullet shapes are buffer level not paragraph level
1383 // so they are tested here
1384 for (int i = 0; i < 4; ++i) {
1385 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1386 int const font = params().user_defined_bullet(i).getFont();
1388 int const c = params()
1389 .user_defined_bullet(i)
1396 features.require("latexsym");
1398 } else if (font == 1) {
1399 features.require("amssymb");
1400 } else if ((font >= 2 && font <= 5)) {
1401 features.require("pifont");
1406 if (lyxerr.debugging(Debug::LATEX)) {
1407 features.showStruct();
1412 void Buffer::getLabelList(vector<docstring> & list) const
1414 /// if this is a child document and the parent is already loaded
1415 /// Use the parent's list instead [ale990407]
1416 Buffer const * tmp = masterBuffer();
1418 lyxerr << "masterBuffer() failed!" << endl;
1422 tmp->getLabelList(list);
1426 loadChildDocuments();
1428 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1429 it.nextInset()->getLabelList(*this, list);
1433 void Buffer::updateBibfilesCache()
1435 // if this is a child document and the parent is already loaded
1436 // update the parent's cache instead
1437 Buffer * tmp = masterBuffer();
1440 tmp->updateBibfilesCache();
1444 bibfilesCache_.clear();
1445 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1446 if (it->lyxCode() == BIBTEX_CODE) {
1447 InsetBibtex const & inset =
1448 static_cast<InsetBibtex const &>(*it);
1449 vector<FileName> const bibfiles = inset.getFiles(*this);
1450 bibfilesCache_.insert(bibfilesCache_.end(),
1453 } else if (it->lyxCode() == INCLUDE_CODE) {
1454 InsetInclude & inset =
1455 static_cast<InsetInclude &>(*it);
1456 inset.updateBibfilesCache(*this);
1457 vector<FileName> const & bibfiles =
1458 inset.getBibfilesCache(*this);
1459 bibfilesCache_.insert(bibfilesCache_.end(),
1467 vector<FileName> const & Buffer::getBibfilesCache() const
1469 // if this is a child document and the parent is already loaded
1470 // use the parent's cache instead
1471 Buffer const * tmp = masterBuffer();
1474 return tmp->getBibfilesCache();
1476 // We update the cache when first used instead of at loading time.
1477 if (bibfilesCache_.empty())
1478 const_cast<Buffer *>(this)->updateBibfilesCache();
1480 return bibfilesCache_;
1484 bool Buffer::isDepClean(string const & name) const
1486 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1487 if (it == pimpl_->dep_clean.end())
1493 void Buffer::markDepClean(string const & name)
1495 pimpl_->dep_clean[name] = true;
1499 bool Buffer::dispatch(string const & command, bool * result)
1501 return dispatch(lyxaction.lookupFunc(command), result);
1505 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1507 bool dispatched = true;
1509 switch (func.action) {
1510 case LFUN_BUFFER_EXPORT: {
1511 bool const tmp = doExport(to_utf8(func.argument()), false);
1524 void Buffer::changeLanguage(Language const * from, Language const * to)
1529 for_each(par_iterator_begin(),
1531 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1535 bool Buffer::isMultiLingual() const
1537 ParConstIterator end = par_iterator_end();
1538 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1539 if (it->isMultiLingual(params()))
1546 ParIterator Buffer::getParFromID(int const id) const
1548 ParConstIterator it = par_iterator_begin();
1549 ParConstIterator const end = par_iterator_end();
1552 // John says this is called with id == -1 from undo
1553 lyxerr << "getParFromID(), id: " << id << endl;
1557 for (; it != end; ++it)
1565 bool Buffer::hasParWithID(int const id) const
1567 ParConstIterator const it = getParFromID(id);
1568 return it != par_iterator_end();
1572 ParIterator Buffer::par_iterator_begin()
1574 return lyx::par_iterator_begin(inset());
1578 ParIterator Buffer::par_iterator_end()
1580 return lyx::par_iterator_end(inset());
1584 ParConstIterator Buffer::par_iterator_begin() const
1586 return lyx::par_const_iterator_begin(inset());
1590 ParConstIterator Buffer::par_iterator_end() const
1592 return lyx::par_const_iterator_end(inset());
1596 Language const * Buffer::language() const
1598 return params().language;
1602 docstring const Buffer::B_(string const & l10n) const
1604 return params().B_(l10n);
1608 bool Buffer::isClean() const
1610 return pimpl_->lyx_clean;
1614 bool Buffer::isBakClean() const
1616 return pimpl_->bak_clean;
1620 bool Buffer::isExternallyModified(CheckMethod method) const
1622 BOOST_ASSERT(pimpl_->filename.exists());
1623 // if method == timestamp, check timestamp before checksum
1624 return (method == checksum_method
1625 || pimpl_->timestamp_ != pimpl_->filename.lastModified())
1626 && pimpl_->checksum_ != pimpl_->filename.checksum();
1630 void Buffer::saveCheckSum(FileName const & file) const
1632 if (file.exists()) {
1633 pimpl_->timestamp_ = file.lastModified();
1634 pimpl_->checksum_ = file.checksum();
1636 // in the case of save to a new file.
1637 pimpl_->timestamp_ = 0;
1638 pimpl_->checksum_ = 0;
1643 void Buffer::markClean() const
1645 if (!pimpl_->lyx_clean) {
1646 pimpl_->lyx_clean = true;
1649 // if the .lyx file has been saved, we don't need an
1651 pimpl_->bak_clean = true;
1655 void Buffer::markBakClean() const
1657 pimpl_->bak_clean = true;
1661 void Buffer::setUnnamed(bool flag)
1663 pimpl_->unnamed = flag;
1667 bool Buffer::isUnnamed() const
1669 return pimpl_->unnamed;
1673 // FIXME: this function should be moved to buffer_pimpl.C
1674 void Buffer::markDirty()
1676 if (pimpl_->lyx_clean) {
1677 pimpl_->lyx_clean = false;
1680 pimpl_->bak_clean = false;
1682 DepClean::iterator it = pimpl_->dep_clean.begin();
1683 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1685 for (; it != end; ++it)
1690 FileName Buffer::fileName() const
1692 return pimpl_->filename;
1696 string Buffer::absFileName() const
1698 return pimpl_->filename.absFilename();
1702 string Buffer::filePath() const
1704 return pimpl_->filename.onlyPath().absFilename();
1708 bool Buffer::isReadonly() const
1710 return pimpl_->read_only;
1714 void Buffer::setParentName(string const & name)
1716 if (name == pimpl_->filename.absFilename())
1717 // Avoids recursive include.
1718 params().parentname.clear();
1720 params().parentname = name;
1724 Buffer const * Buffer::masterBuffer() const
1726 if (!params().parentname.empty()
1727 && theBufferList().exists(params().parentname)) {
1728 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1729 //We need to check if the parent is us...
1730 //FIXME RECURSIVE INCLUDE
1731 //This is not sufficient, since recursive includes could be downstream.
1732 if (buf && buf != this)
1733 return buf->masterBuffer();
1740 Buffer * Buffer::masterBuffer()
1742 if (!params().parentname.empty()
1743 && theBufferList().exists(params().parentname)) {
1744 Buffer * buf = theBufferList().getBuffer(params().parentname);
1745 //We need to check if the parent is us...
1746 //FIXME RECURSIVE INCLUDE
1747 //This is not sufficient, since recursive includes could be downstream.
1748 if (buf && buf != this)
1749 return buf->masterBuffer();
1756 bool Buffer::hasMacro(docstring const & name, Paragraph const & par) const
1758 Impl::PositionToMacroMap::iterator it;
1759 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1760 if (it != pimpl_->macros[name].end())
1763 // If there is a master buffer, query that
1764 const Buffer * master = masterBuffer();
1765 if (master && master != this)
1766 return master->hasMacro(name);
1768 return MacroTable::globalMacros().has(name);
1772 bool Buffer::hasMacro(docstring const & name) const
1774 if( !pimpl_->macros[name].empty() )
1777 // If there is a master buffer, query that
1778 const Buffer * master = masterBuffer();
1779 if (master && master != this)
1780 return master->hasMacro(name);
1782 return MacroTable::globalMacros().has(name);
1786 MacroData const & Buffer::getMacro(docstring const & name,
1787 Paragraph const & par) const
1789 Impl::PositionToMacroMap::iterator it;
1790 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1791 if( it != pimpl_->macros[name].end() )
1794 // If there is a master buffer, query that
1795 const Buffer * master = masterBuffer();
1796 if (master && master != this)
1797 return master->getMacro(name);
1799 return MacroTable::globalMacros().get(name);
1803 MacroData const & Buffer::getMacro(docstring const & name) const
1805 Impl::PositionToMacroMap::iterator it;
1806 it = pimpl_->macros[name].begin();
1807 if( it != pimpl_->macros[name].end() )
1810 // If there is a master buffer, query that
1811 const Buffer * master = masterBuffer();
1812 if (master && master != this)
1813 return master->getMacro(name);
1815 return MacroTable::globalMacros().get(name);
1819 void Buffer::updateMacros()
1821 // start with empty table
1822 pimpl_->macros = Impl::NameToPositionMacroMap();
1824 // Iterate over buffer
1825 ParagraphList & pars = text().paragraphs();
1826 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1827 // set position again
1828 pars[i].setMacrocontextPosition(i);
1830 //lyxerr << "searching main par " << i
1831 // << " for macro definitions" << std::endl;
1832 InsetList const & insets = pars[i].insetList();
1833 InsetList::const_iterator it = insets.begin();
1834 InsetList::const_iterator end = insets.end();
1835 for ( ; it != end; ++it) {
1836 if (it->inset->lyxCode() != MATHMACRO_CODE)
1840 MathMacroTemplate const & macroTemplate
1841 = static_cast<MathMacroTemplate const &>(*it->inset);
1844 if (macroTemplate.validMacro()) {
1845 MacroData macro = macroTemplate.asMacroData();
1848 // call hasMacro here instead of directly querying mc to
1849 // also take the master document into consideration
1850 macro.setRedefinition(hasMacro(macroTemplate.name()));
1852 // register macro (possibly overwrite the previous one of this paragraph)
1853 pimpl_->macros[macroTemplate.name()][i] = macro;
1860 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1863 //FIXME: This does not work for child documents yet.
1864 BOOST_ASSERT(code == CITE_CODE || code == REF_CODE);
1865 // Check if the label 'from' appears more than once
1866 vector<docstring> labels;
1869 if (code == CITE_CODE) {
1871 keys.fillWithBibKeys(this);
1872 BiblioInfo::const_iterator bit = keys.begin();
1873 BiblioInfo::const_iterator bend = keys.end();
1875 for (; bit != bend; ++bit)
1877 labels.push_back(bit->first);
1880 getLabelList(labels);
1881 paramName = "reference";
1884 if (std::count(labels.begin(), labels.end(), from) > 1)
1887 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1888 if (it->lyxCode() == code) {
1889 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1890 docstring const oldValue = inset.getParam(paramName);
1891 if (oldValue == from)
1892 inset.setParam(paramName, to);
1898 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1899 pit_type par_end, bool full_source)
1901 OutputParams runparams(¶ms().encoding());
1902 runparams.nice = true;
1903 runparams.flavor = OutputParams::LATEX;
1904 runparams.linelen = lyxrc.plaintext_linelen;
1905 // No side effect of file copying and image conversion
1906 runparams.dryrun = true;
1910 os << "% " << _("Preview source code") << "\n\n";
1914 writeLaTeXSource(os, filePath(), runparams, true, true);
1916 writeDocBookSource(os, absFileName(), runparams, false);
1919 runparams.par_begin = par_begin;
1920 runparams.par_end = par_end;
1921 if (par_begin + 1 == par_end)
1923 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1927 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1928 convert<docstring>(par_begin),
1929 convert<docstring>(par_end - 1))
1933 // output paragraphs
1935 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1938 docbookParagraphs(paragraphs(), *this, os, runparams);
1944 ErrorList const & Buffer::errorList(string const & type) const
1946 static ErrorList const emptyErrorList;
1947 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1948 if (I == pimpl_->errorLists.end())
1949 return emptyErrorList;
1955 ErrorList & Buffer::errorList(string const & type)
1957 return pimpl_->errorLists[type];
1961 void Buffer::structureChanged() const
1964 gui_->structureChanged();
1968 void Buffer::errors(std::string const & err) const
1975 void Buffer::message(docstring const & msg) const
1982 void Buffer::setBusy(bool on) const
1989 void Buffer::setReadOnly(bool on) const
1992 pimpl_->wa_->setReadOnly(on);
1996 void Buffer::updateTitles() const
1999 pimpl_->wa_->updateTitles();
2003 void Buffer::resetAutosaveTimers() const
2006 gui_->resetAutosaveTimers();
2010 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
2019 class AutoSaveBuffer : public support::ForkedProcess {
2022 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
2023 : buffer_(buffer), fname_(fname) {}
2025 virtual boost::shared_ptr<ForkedProcess> clone() const
2027 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
2032 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
2033 from_utf8(fname_.absFilename())));
2034 return run(DontWait);
2038 virtual int generateChild();
2040 Buffer const & buffer_;
2045 #if !defined (HAVE_FORK)
2049 int AutoSaveBuffer::generateChild()
2051 // tmp_ret will be located (usually) in /tmp
2052 // will that be a problem?
2053 pid_t const pid = fork();
2054 // If you want to debug the autosave
2055 // you should set pid to -1, and comment out the fork.
2056 if (pid == 0 || pid == -1) {
2057 // pid = -1 signifies that lyx was unable
2058 // to fork. But we will do the save
2060 bool failed = false;
2062 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2063 if (!tmp_ret.empty()) {
2064 buffer_.writeFile(tmp_ret);
2065 // assume successful write of tmp_ret
2066 if (!rename(tmp_ret, fname_)) {
2068 // most likely couldn't move between
2069 // filesystems unless write of tmp_ret
2070 // failed so remove tmp file (if it
2072 tmp_ret.removeFile();
2079 // failed to write/rename tmp_ret so try writing direct
2080 if (!buffer_.writeFile(fname_)) {
2081 // It is dangerous to do this in the child,
2082 // but safe in the parent, so...
2083 if (pid == -1) // emit message signal.
2084 buffer_.message(_("Autosave failed!"));
2087 if (pid == 0) { // we are the child so...
2097 // Perfect target for a thread...
2098 void Buffer::autoSave() const
2100 if (isBakClean() || isReadonly()) {
2101 // We don't save now, but we'll try again later
2102 resetAutosaveTimers();
2106 // emit message signal.
2107 message(_("Autosaving current document..."));
2109 // create autosave filename
2110 string fname = filePath();
2112 fname += onlyFilename(absFileName());
2115 AutoSaveBuffer autosave(*this, FileName(fname));
2119 resetAutosaveTimers();
2123 /** Write a buffer to a new file name and rename the buffer
2124 according to the new file name.
2126 This function is e.g. used by menu callbacks and
2127 LFUN_BUFFER_WRITE_AS.
2129 If 'newname' is empty (the default), the user is asked via a
2130 dialog for the buffer's new name and location.
2132 If 'newname' is non-empty and has an absolute path, that is used.
2133 Otherwise the base directory of the buffer is used as the base
2134 for any relative path in 'newname'.
2137 bool Buffer::writeAs(string const & newname)
2139 string fname = absFileName();
2140 string const oldname = fname;
2142 if (newname.empty()) { /// No argument? Ask user through dialog
2145 FileDialog dlg(_("Choose a filename to save document as"),
2146 LFUN_BUFFER_WRITE_AS);
2147 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
2148 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
2150 if (!support::isLyXFilename(fname))
2153 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2155 FileDialog::Result result =
2156 dlg.save(from_utf8(onlyPath(fname)),
2158 from_utf8(onlyFilename(fname)));
2160 if (result.first == FileDialog::Later)
2163 fname = to_utf8(result.second);
2168 // Make sure the absolute filename ends with appropriate suffix
2169 fname = makeAbsPath(fname).absFilename();
2170 if (!support::isLyXFilename(fname))
2174 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2176 if (FileName(fname).exists()) {
2177 docstring const file = makeDisplayPath(fname, 30);
2178 docstring text = bformat(_("The document %1$s already "
2179 "exists.\n\nDo you want to "
2180 "overwrite that document?"),
2182 int const ret = Alert::prompt(_("Overwrite document?"),
2183 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2189 // Ok, change the name of the buffer
2192 bool unnamed = isUnnamed();
2194 saveCheckSum(FileName(fname));
2197 setFileName(oldname);
2198 setUnnamed(unnamed);
2199 saveCheckSum(FileName(oldname));
2203 removeAutosaveFile(oldname);
2208 bool Buffer::menuWrite()
2211 LyX::ref().session().lastFiles().add(FileName(absFileName()));
2215 // FIXME: we don't tell the user *WHY* the save failed !!
2217 docstring const file = makeDisplayPath(absFileName(), 30);
2219 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2220 "Do you want to rename the document and "
2221 "try again?"), file);
2222 int const ret = Alert::prompt(_("Rename and save?"),
2223 text, 0, 1, _("&Rename"), _("&Cancel"));
2232 void Buffer::loadChildDocuments() const
2234 bool parse_error = false;
2236 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2237 if (it->lyxCode() != INCLUDE_CODE)
2239 InsetCommand const & inset = static_cast<InsetCommand const &>(*it);
2240 InsetCommandParams const & ip = inset.params();
2241 Buffer * child = loadIfNeeded(*this, ip);
2244 parse_error |= !child->errorList("Parse").empty();
2245 child->loadChildDocuments();
2248 if (use_gui && masterBuffer() == this)
2249 updateLabels(*this);
2253 string Buffer::bufferFormat() const
2263 bool Buffer::doExport(string const & format, bool put_in_tempdir,
2264 string & result_file)
2266 string backend_format;
2267 OutputParams runparams(¶ms().encoding());
2268 runparams.flavor = OutputParams::LATEX;
2269 runparams.linelen = lyxrc.plaintext_linelen;
2270 vector<string> backs = backends();
2271 if (find(backs.begin(), backs.end(), format) == backs.end()) {
2272 // Get shortest path to format
2273 Graph::EdgePath path;
2274 for (vector<string>::const_iterator it = backs.begin();
2275 it != backs.end(); ++it) {
2276 Graph::EdgePath p = theConverters().getPath(*it, format);
2277 if (!p.empty() && (path.empty() || p.size() < path.size())) {
2278 backend_format = *it;
2283 runparams.flavor = theConverters().getFlavor(path);
2285 Alert::error(_("Couldn't export file"),
2286 bformat(_("No information for exporting the format %1$s."),
2287 formats.prettyName(format)));
2291 backend_format = format;
2292 // FIXME: Don't hardcode format names here, but use a flag
2293 if (backend_format == "pdflatex")
2294 runparams.flavor = OutputParams::PDFLATEX;
2297 string filename = latexName(false);
2298 filename = addName(temppath(), filename);
2299 filename = changeExtension(filename,
2300 formats.extension(backend_format));
2302 // Plain text backend
2303 if (backend_format == "text")
2304 writePlaintextFile(*this, FileName(filename), runparams);
2306 else if (backend_format == "lyx")
2307 writeFile(FileName(filename));
2309 else if (isDocBook()) {
2310 runparams.nice = !put_in_tempdir;
2311 makeDocBookFile(FileName(filename), runparams);
2314 else if (backend_format == format) {
2315 runparams.nice = true;
2316 if (!makeLaTeXFile(FileName(filename), string(), runparams))
2318 } else if (!lyxrc.tex_allows_spaces
2319 && support::contains(filePath(), ' ')) {
2320 Alert::error(_("File name error"),
2321 _("The directory path to the document cannot contain spaces."));
2324 runparams.nice = false;
2325 if (!makeLaTeXFile(FileName(filename), filePath(), runparams))
2329 string const error_type = (format == "program")
2330 ? "Build" : bufferFormat();
2331 string const ext = formats.extension(format);
2332 FileName const tmp_result_file(changeExtension(filename, ext));
2333 bool const success = theConverters().convert(this, FileName(filename),
2334 tmp_result_file, FileName(absFileName()), backend_format, format,
2335 errorList(error_type));
2336 // Emit the signal to show the error list.
2337 if (format != backend_format)
2343 result_file = tmp_result_file.absFilename();
2345 result_file = changeExtension(absFileName(), ext);
2346 // We need to copy referenced files (e. g. included graphics
2347 // if format == "dvi") to the result dir.
2348 vector<ExportedFile> const files =
2349 runparams.exportdata->externalFiles(format);
2350 string const dest = onlyPath(result_file);
2351 CopyStatus status = SUCCESS;
2352 for (vector<ExportedFile>::const_iterator it = files.begin();
2353 it != files.end() && status != CANCEL; ++it) {
2355 formats.getFormatFromFile(it->sourceName);
2356 status = copyFile(fmt, it->sourceName,
2357 makeAbsPath(it->exportName, dest),
2358 it->exportName, status == FORCE);
2360 if (status == CANCEL) {
2361 message(_("Document export cancelled."));
2362 } else if (tmp_result_file.exists()) {
2363 // Finally copy the main file
2364 status = copyFile(format, tmp_result_file,
2365 FileName(result_file), result_file,
2367 message(bformat(_("Document exported as %1$s "
2369 formats.prettyName(format),
2370 makeDisplayPath(result_file)));
2372 // This must be a dummy converter like fax (bug 1888)
2373 message(bformat(_("Document exported as %1$s"),
2374 formats.prettyName(format)));
2382 bool Buffer::doExport(string const & format, bool put_in_tempdir)
2385 return doExport(format, put_in_tempdir, result_file);
2389 bool Buffer::preview(string const & format)
2392 if (!doExport(format, true, result_file))
2394 return formats.view(*this, FileName(result_file), format);
2398 bool Buffer::isExportable(string const & format) const
2400 vector<string> backs = backends();
2401 for (vector<string>::const_iterator it = backs.begin();
2402 it != backs.end(); ++it)
2403 if (theConverters().isReachable(*it, format))
2409 vector<Format const *> Buffer::exportableFormats(bool only_viewable) const
2411 vector<string> backs = backends();
2412 vector<Format const *> result =
2413 theConverters().getReachable(backs[0], only_viewable, true);
2414 for (vector<string>::const_iterator it = backs.begin() + 1;
2415 it != backs.end(); ++it) {
2416 vector<Format const *> r =
2417 theConverters().getReachable(*it, only_viewable, false);
2418 result.insert(result.end(), r.begin(), r.end());
2424 vector<string> Buffer::backends() const
2427 if (params().getTextClass().isTeXClassAvailable()) {
2428 v.push_back(bufferFormat());
2429 // FIXME: Don't hardcode format names here, but use a flag
2430 if (v.back() == "latex")
2431 v.push_back("pdflatex");
2433 v.push_back("text");
2439 bool Buffer::readFileHelper(FileName const & s)
2441 // File information about normal file
2443 docstring const file = makeDisplayPath(s.absFilename(), 50);
2444 docstring text = bformat(_("The specified document\n%1$s"
2445 "\ncould not be read."), file);
2446 Alert::error(_("Could not read document"), text);
2450 // Check if emergency save file exists and is newer.
2451 FileName const e(s.absFilename() + ".emergency");
2453 if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) {
2454 docstring const file = makeDisplayPath(s.absFilename(), 20);
2455 docstring const text =
2456 bformat(_("An emergency save of the document "
2458 "Recover emergency save?"), file);
2459 switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
2460 _("&Recover"), _("&Load Original"),
2464 // the file is not saved if we load the emergency file.
2474 // Now check if autosave file is newer.
2475 FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#');
2477 if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) {
2478 docstring const file = makeDisplayPath(s.absFilename(), 20);
2479 docstring const text =
2480 bformat(_("The backup of the document "
2481 "%1$s is newer.\n\nLoad the "
2482 "backup instead?"), file);
2483 switch (Alert::prompt(_("Load backup?"), text, 0, 2,
2484 _("&Load backup"), _("Load &original"),
2488 // the file is not saved if we load the autosave file.
2492 // Here we delete the autosave
2503 bool Buffer::loadLyXFile(FileName const & s)
2505 if (s.isReadableFile()) {
2506 if (readFileHelper(s)) {
2507 lyxvc().file_found_hook(s);
2508 if (!s.isWritable())
2513 docstring const file = makeDisplayPath(s.absFilename(), 20);
2514 // Here we probably should run
2515 if (LyXVC::file_not_found_hook(s)) {
2516 docstring const text =
2517 bformat(_("Do you want to retrieve the document"
2518 " %1$s from version control?"), file);
2519 int const ret = Alert::prompt(_("Retrieve from version control?"),
2520 text, 0, 1, _("&Retrieve"), _("&Cancel"));
2523 // How can we know _how_ to do the checkout?
2524 // With the current VC support it has to be,
2525 // a RCS file since CVS do not have special ,v files.
2527 return loadLyXFile(s);
2535 void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
2537 TeXErrors::Errors::const_iterator cit = terr.begin();
2538 TeXErrors::Errors::const_iterator end = terr.end();
2540 for (; cit != end; ++cit) {
2543 int errorRow = cit->error_in_line;
2544 bool found = texrow().getIdFromRow(errorRow, id_start,
2550 found = texrow().getIdFromRow(errorRow, id_end, pos_end);
2551 } while (found && id_start == id_end && pos_start == pos_end);
2553 errorList.push_back(ErrorItem(cit->error_desc,
2554 cit->error_text, id_start, pos_start, pos_end));