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/fs_extras.h"
89 #include "support/gzstream.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/filesystem/exception.hpp>
102 #include <boost/filesystem/operations.hpp>
103 #include <boost/shared_ptr.hpp>
113 using std::make_pair;
118 using std::ostringstream;
129 using support::addName;
130 using support::bformat;
131 using support::changeExtension;
132 using support::cmd_ret;
133 using support::createBufferTmpDir;
134 using support::FileName;
135 using support::libFileSearch;
136 using support::latex_path;
137 using support::ltrim;
138 using support::makeAbsPath;
139 using support::makeDisplayPath;
140 using support::makeLatexName;
141 using support::onlyFilename;
142 using support::onlyPath;
143 using support::quoteName;
144 using support::removeAutosaveFile;
145 using support::rename;
146 using support::runCommand;
147 using support::split;
148 using support::subst;
149 using support::tempName;
152 using support::suffixIs;
154 namespace Alert = frontend::Alert;
155 namespace os = support::os;
156 namespace fs = boost::filesystem;
160 int const LYX_FORMAT = 299; //Uwe: Hyperlink types
165 typedef std::map<string, bool> DepClean;
170 Impl(Buffer & parent, FileName const & file, bool readonly);
177 /// need to regenerate .tex?
181 mutable bool lyx_clean;
183 /// is autosave needed?
184 mutable bool bak_clean;
186 /// is this a unnamed file (New...)?
192 /// name of the file the buffer is associated with.
195 /** Set to true only when the file is fully loaded.
196 * Used to prevent the premature generation of previews
197 * and by the citation inset.
199 bool file_fully_loaded;
201 /// our Text that should be wrapped in an InsetText
205 TocBackend toc_backend;
208 typedef std::map<unsigned int, MacroData, std::greater<int> > PositionToMacroMap;
209 typedef std::map<docstring, PositionToMacroMap> NameToPositionMacroMap;
210 NameToPositionMacroMap macros;
212 /// Container for all sort of Buffer dependant errors.
213 map<string, ErrorList> errorLists;
215 /// all embedded files of this buffer
216 EmbeddedFiles embedded_files;
218 /// timestamp and checksum used to test if the file has been externally
219 /// modified. (Used to properly enable 'File->Revert to saved', bug 4114).
221 unsigned long checksum_;
224 frontend::WorkAreaManager * wa_;
231 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
232 : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
233 filename(file), file_fully_loaded(false), inset(params),
234 toc_backend(&parent), embedded_files(&parent), timestamp_(0),
235 checksum_(0), wa_(0), undo_(parent)
237 inset.setAutoBreakRows(true);
238 lyxvc.setBuffer(&parent);
239 temppath = createBufferTmpDir();
240 params.filepath = onlyPath(file.absFilename());
241 // FIXME: And now do something if temppath == string(), because we
242 // assume from now on that temppath points to a valid temp dir.
243 // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html
246 wa_ = new frontend::WorkAreaManager;
250 Buffer::Buffer(string const & file, bool readonly)
251 : pimpl_(new Impl(*this, FileName(file), readonly)), gui_(0)
253 LYXERR(Debug::INFO) << "Buffer::Buffer()" << endl;
259 LYXERR(Debug::INFO) << "Buffer::~Buffer()" << endl;
260 // here the buffer should take care that it is
261 // saved properly, before it goes into the void.
263 Buffer * master = masterBuffer();
264 if (master != this && use_gui)
265 // We are closing buf which was a child document so we
266 // must update the labels and section numbering of its master
268 updateLabels(*master);
270 if (!temppath().empty() && !FileName(temppath()).destroyDirectory()) {
271 Alert::warning(_("Could not remove temporary directory"),
272 bformat(_("Could not remove the temporary directory %1$s"),
273 from_utf8(temppath())));
276 // Remove any previewed LaTeX snippets associated with this buffer.
277 graphics::Previews::get().removeLoader(*this);
280 pimpl_->wa_->closeAll();
287 void Buffer::changed() const
290 pimpl_->wa_->redrawAll();
294 frontend::WorkAreaManager & Buffer::workAreaManager() const
296 BOOST_ASSERT(pimpl_->wa_);
301 Text & Buffer::text() const
303 return const_cast<Text &>(pimpl_->inset.text_);
307 Inset & Buffer::inset() const
309 return const_cast<InsetText &>(pimpl_->inset);
313 BufferParams & Buffer::params()
315 return pimpl_->params;
319 BufferParams const & Buffer::params() const
321 return pimpl_->params;
325 ParagraphList & Buffer::paragraphs()
327 return text().paragraphs();
331 ParagraphList const & Buffer::paragraphs() const
333 return text().paragraphs();
337 LyXVC & Buffer::lyxvc()
339 return pimpl_->lyxvc;
343 LyXVC const & Buffer::lyxvc() const
345 return pimpl_->lyxvc;
349 string const & Buffer::temppath() const
351 return pimpl_->temppath;
355 TexRow & Buffer::texrow()
357 return pimpl_->texrow;
361 TexRow const & Buffer::texrow() const
363 return pimpl_->texrow;
367 TocBackend & Buffer::tocBackend()
369 return pimpl_->toc_backend;
373 TocBackend const & Buffer::tocBackend() const
375 return pimpl_->toc_backend;
379 EmbeddedFiles & Buffer::embeddedFiles()
381 return pimpl_->embedded_files;
385 EmbeddedFiles const & Buffer::embeddedFiles() const
387 return pimpl_->embedded_files;
391 Undo & Buffer::undo()
393 return pimpl_->undo_;
397 string Buffer::latexName(bool const no_path) const
399 string const name = changeExtension(makeLatexName(absFileName()), ".tex");
400 return no_path ? onlyFilename(name) : name;
404 string Buffer::logName(LogType * type) const
406 string const filename = latexName(false);
408 if (filename.empty()) {
414 string const path = temppath();
416 FileName const fname(addName(temppath(),
417 onlyFilename(changeExtension(filename,
419 FileName const bname(
420 addName(path, onlyFilename(
421 changeExtension(filename,
422 formats.extension("literate") + ".out"))));
424 // If no Latex log or Build log is newer, show Build log
426 if (bname.exists() &&
427 (!fname.exists() || fname.lastModified() < bname.lastModified())) {
428 LYXERR(Debug::FILES) << "Log name calculated as: " << bname << endl;
431 return bname.absFilename();
433 LYXERR(Debug::FILES) << "Log name calculated as: " << fname << endl;
436 return fname.absFilename();
440 void Buffer::setReadonly(bool const flag)
442 if (pimpl_->read_only != flag) {
443 pimpl_->read_only = flag;
449 void Buffer::setFileName(string const & newfile)
451 pimpl_->filename = makeAbsPath(newfile);
452 params().filepath = onlyPath(pimpl_->filename.absFilename());
453 setReadonly(pimpl_->filename.isReadOnly());
458 int Buffer::readHeader(Lexer & lex)
460 int unknown_tokens = 0;
462 int begin_header_line = -1;
464 // Initialize parameters that may be/go lacking in header:
465 params().branchlist().clear();
466 params().preamble.erase();
467 params().options.erase();
468 params().float_placement.erase();
469 params().paperwidth.erase();
470 params().paperheight.erase();
471 params().leftmargin.erase();
472 params().rightmargin.erase();
473 params().topmargin.erase();
474 params().bottommargin.erase();
475 params().headheight.erase();
476 params().headsep.erase();
477 params().footskip.erase();
478 params().listings_params.clear();
479 params().clearLayoutModules();
480 params().pdfoptions().clear();
482 for (int i = 0; i < 4; ++i) {
483 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
484 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
487 ErrorList & errorList = pimpl_->errorLists["Parse"];
491 string const token = lex.getString();
496 if (token == "\\end_header")
500 if (token == "\\begin_header") {
501 begin_header_line = line;
505 LYXERR(Debug::PARSER) << "Handling document header token: `"
506 << token << '\'' << endl;
508 string unknown = params().readToken(lex, token);
509 if (!unknown.empty()) {
510 if (unknown[0] != '\\' && token == "\\textclass") {
511 Alert::warning(_("Unknown document class"),
512 bformat(_("Using the default document class, because the "
513 "class %1$s is unknown."), from_utf8(unknown)));
516 docstring const s = bformat(_("Unknown token: "
520 errorList.push_back(ErrorItem(_("Document header error"),
525 if (begin_header_line) {
526 docstring const s = _("\\begin_header is missing");
527 errorList.push_back(ErrorItem(_("Document header error"),
531 return unknown_tokens;
536 // changed to be public and have one parameter
537 // Returns false if "\end_document" is not read (Asger)
538 bool Buffer::readDocument(Lexer & lex)
540 ErrorList & errorList = pimpl_->errorLists["Parse"];
544 string const token = lex.getString();
545 if (token != "\\begin_document") {
546 docstring const s = _("\\begin_document is missing");
547 errorList.push_back(ErrorItem(_("Document header error"),
551 // we are reading in a brand new document
552 BOOST_ASSERT(paragraphs().empty());
555 TextClass const & baseClass = textclasslist[params().getBaseClass()];
556 if (!baseClass.load(filePath())) {
557 string theclass = baseClass.name();
558 Alert::error(_("Can't load document class"), bformat(
559 _("Using the default document class, because the "
560 "class %1$s could not be loaded."), from_utf8(theclass)));
561 params().setBaseClass(defaultTextclass());
564 if (params().outputChanges) {
565 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
566 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
567 LaTeXFeatures::isAvailable("xcolor");
569 if (!dvipost && !xcolorsoul) {
570 Alert::warning(_("Changes not shown in LaTeX output"),
571 _("Changes will not be highlighted in LaTeX output, "
572 "because neither dvipost nor xcolor/soul are installed.\n"
573 "Please install these packages or redefine "
574 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
575 } else if (!xcolorsoul) {
576 Alert::warning(_("Changes not shown in LaTeX output"),
577 _("Changes will not be highlighted in LaTeX output "
578 "when using pdflatex, because xcolor and soul are not installed.\n"
579 "Please install both packages or redefine "
580 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
585 bool const res = text().read(*this, lex, errorList);
586 for_each(text().paragraphs().begin(),
587 text().paragraphs().end(),
588 bind(&Paragraph::setInsetOwner, _1, &inset()));
594 // needed to insert the selection
595 void Buffer::insertStringAsLines(ParagraphList & pars,
596 pit_type & pit, pos_type & pos,
597 Font const & fn, docstring const & str, bool autobreakrows)
601 // insert the string, don't insert doublespace
602 bool space_inserted = true;
603 for (docstring::const_iterator cit = str.begin();
604 cit != str.end(); ++cit) {
605 Paragraph & par = pars[pit];
607 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
608 breakParagraph(params(), pars, pit, pos,
609 par.layout()->isEnvironment());
612 space_inserted = true;
616 // do not insert consecutive spaces if !free_spacing
617 } else if ((*cit == ' ' || *cit == '\t') &&
618 space_inserted && !par.isFreeSpacing()) {
620 } else if (*cit == '\t') {
621 if (!par.isFreeSpacing()) {
622 // tabs are like spaces here
623 par.insertChar(pos, ' ', font, params().trackChanges);
625 space_inserted = true;
627 const pos_type n = 8 - pos % 8;
628 for (pos_type i = 0; i < n; ++i) {
629 par.insertChar(pos, ' ', font, params().trackChanges);
632 space_inserted = true;
634 } else if (!isPrintable(*cit)) {
635 // Ignore unprintables
638 // just insert the character
639 par.insertChar(pos, *cit, font, params().trackChanges);
641 space_inserted = (*cit == ' ');
648 bool Buffer::readString(std::string const & s)
650 params().compressed = false;
652 // remove dummy empty par
653 paragraphs().clear();
655 std::istringstream is(s);
657 FileName const name(tempName());
658 switch (readFile(lex, name, true)) {
662 // We need to call lyx2lyx, so write the input to a file
663 std::ofstream os(name.toFilesystemEncoding().c_str());
666 return readFile(name);
676 bool Buffer::readFile(FileName const & filename)
678 FileName fname(filename);
679 // Check if the file is compressed.
680 string format = filename.guessFormatFromContents();
681 if (format == "zip") {
682 // decompress to a temp directory
683 LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl;
684 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
686 FileName lyxfile(addName(temppath(), "content.lyx"));
687 // if both manifest.txt and file.lyx exist, this is am embedded file
688 if (lyxfile.exists()) {
689 params().embedded = true;
693 // The embedded lyx file can also be compressed, for backward compatibility
694 format = fname.guessFormatFromContents();
695 if (format == "gzip" || format == "zip" || format == "compress")
696 params().compressed = true;
698 // remove dummy empty par
699 paragraphs().clear();
702 if (readFile(lex, fname) != success)
709 bool Buffer::isFullyLoaded() const
711 return pimpl_->file_fully_loaded;
715 void Buffer::setFullyLoaded(bool value)
717 pimpl_->file_fully_loaded = value;
721 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
724 BOOST_ASSERT(!filename.empty());
727 Alert::error(_("Document could not be read"),
728 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
733 string const token = lex.getString();
736 Alert::error(_("Document could not be read"),
737 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
741 // the first token _must_ be...
742 if (token != "\\lyxformat") {
743 lyxerr << "Token: " << token << endl;
745 Alert::error(_("Document format failure"),
746 bformat(_("%1$s is not a LyX document."),
747 from_utf8(filename.absFilename())));
752 string tmp_format = lex.getString();
753 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
754 // if present remove ".," from string.
755 string::size_type dot = tmp_format.find_first_of(".,");
756 //lyxerr << " dot found at " << dot << endl;
757 if (dot != string::npos)
758 tmp_format.erase(dot, 1);
759 int const file_format = convert<int>(tmp_format);
760 //lyxerr << "format: " << file_format << endl;
762 // save timestamp and checksum of the original disk file, making sure
763 // to not overwrite them with those of the file created in the tempdir
764 // when it has to be converted to the current format.
765 if (!pimpl_->checksum_) {
766 // Save the timestamp and checksum of disk file. If filename is an
767 // emergency file, save the timestamp and sum of the original lyx file
768 // because isExternallyModified will check for this file. (BUG4193)
769 string diskfile = filename.toFilesystemEncoding();
770 if (suffixIs(diskfile, ".emergency"))
771 diskfile = diskfile.substr(0, diskfile.size() - 10);
772 saveCheckSum(FileName(diskfile));
775 if (file_format != LYX_FORMAT) {
778 // lyx2lyx would fail
781 FileName const tmpfile(tempName());
782 if (tmpfile.empty()) {
783 Alert::error(_("Conversion failed"),
784 bformat(_("%1$s is from a different"
785 " version of LyX, but a temporary"
786 " file for converting it could"
788 from_utf8(filename.absFilename())));
791 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
792 if (lyx2lyx.empty()) {
793 Alert::error(_("Conversion script not found"),
794 bformat(_("%1$s is from a different"
795 " version of LyX, but the"
796 " conversion script lyx2lyx"
797 " could not be found."),
798 from_utf8(filename.absFilename())));
801 ostringstream command;
802 command << os::python()
803 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
804 << " -t " << convert<string>(LYX_FORMAT)
805 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
806 << ' ' << quoteName(filename.toFilesystemEncoding());
807 string const command_str = command.str();
809 LYXERR(Debug::INFO) << "Running '"
810 << command_str << '\''
813 cmd_ret const ret = runCommand(command_str);
814 if (ret.first != 0) {
815 Alert::error(_("Conversion script failed"),
816 bformat(_("%1$s is from a different version"
817 " of LyX, but the lyx2lyx script"
818 " failed to convert it."),
819 from_utf8(filename.absFilename())));
822 bool const ret = readFile(tmpfile);
823 // Do stuff with tmpfile name and buffer name here.
824 return ret ? success : failure;
829 if (readDocument(lex)) {
830 Alert::error(_("Document format failure"),
831 bformat(_("%1$s ended unexpectedly, which means"
832 " that it is probably corrupted."),
833 from_utf8(filename.absFilename())));
836 pimpl_->file_fully_loaded = true;
841 // Should probably be moved to somewhere else: BufferView? LyXView?
842 bool Buffer::save() const
844 // We don't need autosaves in the immediate future. (Asger)
845 resetAutosaveTimers();
847 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
850 bool madeBackup = false;
852 // make a backup if the file already exists
853 if (lyxrc.make_backup && fileName().exists()) {
854 backupName = FileName(absFileName() + '~');
855 if (!lyxrc.backupdir_path.empty()) {
856 string const mangledName =
857 subst(subst(os::internal_path(
858 backupName.absFilename()), '/', '!'), ':', '!');
859 backupName = FileName(addName(lyxrc.backupdir_path,
863 fs::copy_file(encodedFilename, backupName.toFilesystemEncoding(), false);
865 } catch (fs::filesystem_error const & fe) {
866 Alert::error(_("Backup failure"),
867 bformat(_("Cannot create backup file %1$s.\n"
868 "Please check whether the directory exists and is writeable."),
869 from_utf8(backupName.absFilename())));
870 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
874 // ask if the disk file has been externally modified (use checksum method)
875 if (fileName().exists() && isExternallyModified(checksum_method)) {
876 docstring const file = makeDisplayPath(absFileName(), 20);
877 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
878 "you want to overwrite this file?"), file);
879 int const ret = Alert::prompt(_("Overwrite modified file?"),
880 text, 1, 1, _("&Overwrite"), _("&Cancel"));
885 if (writeFile(pimpl_->filename)) {
887 removeAutosaveFile(absFileName());
888 saveCheckSum(pimpl_->filename);
891 // Saving failed, so backup is not backup
893 rename(backupName, pimpl_->filename);
899 bool Buffer::writeFile(FileName const & fname) const
901 if (pimpl_->read_only && fname == pimpl_->filename)
907 if (params().embedded)
908 // first write the .lyx file to the temporary directory
909 content = FileName(addName(temppath(), "content.lyx"));
913 if (params().compressed) {
914 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
920 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
927 if (retval && params().embedded) {
928 // write file.lyx and all the embedded files to the zip file fname
929 // if embedding is enabled
930 return pimpl_->embedded_files.writeFile(fname);
936 bool Buffer::write(ostream & ofs) const
939 // Use the standard "C" locale for file output.
940 ofs.imbue(std::locale::classic());
943 // The top of the file should not be written by params().
945 // write out a comment in the top of the file
946 ofs << "#LyX " << lyx_version
947 << " created this file. For more info see http://www.lyx.org/\n"
948 << "\\lyxformat " << LYX_FORMAT << "\n"
949 << "\\begin_document\n";
952 /// For each author, set 'used' to true if there is a change
953 /// by this author in the document; otherwise set it to 'false'.
954 AuthorList::Authors::const_iterator a_it = params().authors().begin();
955 AuthorList::Authors::const_iterator a_end = params().authors().end();
956 for (; a_it != a_end; ++a_it)
957 a_it->second.setUsed(false);
959 ParIterator const end = par_iterator_end();
960 ParIterator it = par_iterator_begin();
961 for ( ; it != end; ++it)
962 it->checkAuthors(params().authors());
964 // now write out the buffer parameters.
965 ofs << "\\begin_header\n";
966 params().writeFile(ofs);
967 ofs << "\\end_header\n";
970 ofs << "\n\\begin_body\n";
971 text().write(*this, ofs);
972 ofs << "\n\\end_body\n";
974 // Write marker that shows file is complete
975 ofs << "\\end_document" << endl;
977 // Shouldn't really be needed....
980 // how to check if close went ok?
981 // Following is an attempt... (BE 20001011)
983 // good() returns false if any error occured, including some
985 // bad() returns true if something bad happened in the buffer,
986 // which should include file system full errors.
991 lyxerr << "File was not closed properly." << endl;
998 bool Buffer::makeLaTeXFile(FileName const & fname,
999 string const & original_path,
1000 OutputParams const & runparams,
1001 bool output_preamble, bool output_body)
1003 string const encoding = runparams.encoding->iconvName();
1004 LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: "
1005 << encoding << "..." << endl;
1007 odocfstream ofs(encoding);
1008 if (!openFileWrite(ofs, fname))
1011 //TexStream ts(ofs.rdbuf(), &texrow());
1013 bool failed_export = false;
1016 writeLaTeXSource(ofs, original_path,
1017 runparams, output_preamble, output_body);
1019 catch (iconv_codecvt_facet_exception & e) {
1020 lyxerr << "Caught iconv exception: " << e.what() << endl;
1021 failed_export = true;
1023 catch (std::exception const & e) {
1024 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1025 failed_export = true;
1028 lyxerr << "Caught some really weird exception..." << endl;
1029 LyX::cref().emergencyCleanup();
1035 failed_export = true;
1036 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1039 if (failed_export) {
1040 Alert::error(_("Encoding error"),
1041 _("Some characters of your document are probably not "
1042 "representable in the chosen encoding.\n"
1043 "Changing the document encoding to utf8 could help."));
1050 void Buffer::writeLaTeXSource(odocstream & os,
1051 string const & original_path,
1052 OutputParams const & runparams_in,
1053 bool const output_preamble, bool const output_body)
1055 OutputParams runparams = runparams_in;
1057 // validate the buffer.
1058 LYXERR(Debug::LATEX) << " Validating buffer..." << endl;
1059 LaTeXFeatures features(*this, params(), runparams);
1061 LYXERR(Debug::LATEX) << " Buffer validation done." << endl;
1063 // The starting paragraph of the coming rows is the
1064 // first paragraph of the document. (Asger)
1065 if (output_preamble && runparams.nice) {
1066 os << "%% LyX " << lyx_version << " created this file. "
1067 "For more info, see http://www.lyx.org/.\n"
1068 "%% Do not edit unless you really know what "
1073 LYXERR(Debug::INFO) << "lyx document header finished" << endl;
1074 // There are a few differences between nice LaTeX and usual files:
1075 // usual is \batchmode and has a
1076 // special input@path to allow the including of figures
1077 // with either \input or \includegraphics (what figinsets do).
1078 // input@path is set when the actual parameter
1079 // original_path is set. This is done for usual tex-file, but not
1080 // for nice-latex-file. (Matthias 250696)
1081 // Note that input@path is only needed for something the user does
1082 // in the preamble, included .tex files or ERT, files included by
1083 // LyX work without it.
1084 if (output_preamble) {
1085 if (!runparams.nice) {
1086 // code for usual, NOT nice-latex-file
1087 os << "\\batchmode\n"; // changed
1088 // from \nonstopmode
1091 if (!original_path.empty()) {
1093 // We don't know the encoding of inputpath
1094 docstring const inputpath = from_utf8(latex_path(original_path));
1095 os << "\\makeatletter\n"
1096 << "\\def\\input@path{{"
1097 << inputpath << "/}}\n"
1098 << "\\makeatother\n";
1104 // Write the preamble
1105 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1111 os << "\\begin{document}\n";
1113 } // output_preamble
1115 texrow().start(paragraphs().begin()->id(), 0);
1117 LYXERR(Debug::INFO) << "preamble finished, now the body." << endl;
1119 if (!lyxrc.language_auto_begin &&
1120 !params().language->babel().empty()) {
1122 os << from_utf8(subst(lyxrc.language_command_begin,
1124 params().language->babel()))
1129 Encoding const & encoding = params().encoding();
1130 if (encoding.package() == Encoding::CJK) {
1131 // Open a CJK environment, since in contrast to the encodings
1132 // handled by inputenc the document encoding is not set in
1133 // the preamble if it is handled by CJK.sty.
1134 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1139 // if we are doing a real file with body, even if this is the
1140 // child of some other buffer, let's cut the link here.
1141 // This happens for example if only a child document is printed.
1142 string save_parentname;
1143 if (output_preamble) {
1144 save_parentname = params().parentname;
1145 params().parentname.erase();
1148 loadChildDocuments();
1151 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1153 // Restore the parenthood if needed
1154 if (output_preamble)
1155 params().parentname = save_parentname;
1157 // add this just in case after all the paragraphs
1161 if (encoding.package() == Encoding::CJK) {
1162 // Close the open CJK environment.
1163 // latexParagraphs will have opened one even if the last text
1165 os << "\\end{CJK}\n";
1169 if (!lyxrc.language_auto_end &&
1170 !params().language->babel().empty()) {
1171 os << from_utf8(subst(lyxrc.language_command_end,
1173 params().language->babel()))
1178 if (output_preamble) {
1179 os << "\\end{document}\n";
1182 LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl;
1184 LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made."
1187 runparams_in.encoding = runparams.encoding;
1189 // Just to be sure. (Asger)
1192 LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl;
1193 LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1
1198 bool Buffer::isLatex() const
1200 return params().getTextClass().outputType() == LATEX;
1204 bool Buffer::isLiterate() const
1206 return params().getTextClass().outputType() == LITERATE;
1210 bool Buffer::isDocBook() const
1212 return params().getTextClass().outputType() == DOCBOOK;
1216 void Buffer::makeDocBookFile(FileName const & fname,
1217 OutputParams const & runparams,
1218 bool const body_only)
1220 LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl;
1224 if (!openFileWrite(ofs, fname))
1227 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1231 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1235 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1236 OutputParams const & runparams,
1237 bool const only_body)
1239 LaTeXFeatures features(*this, params(), runparams);
1244 TextClass const & tclass = params().getTextClass();
1245 string const top_element = tclass.latexname();
1248 if (runparams.flavor == OutputParams::XML)
1249 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1252 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1255 if (! tclass.class_header().empty())
1256 os << from_ascii(tclass.class_header());
1257 else if (runparams.flavor == OutputParams::XML)
1258 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1259 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1261 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1263 docstring preamble = from_utf8(params().preamble);
1264 if (runparams.flavor != OutputParams::XML ) {
1265 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1266 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1267 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1268 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1271 string const name = runparams.nice
1272 ? changeExtension(absFileName(), ".sgml") : fname;
1273 preamble += features.getIncludedFiles(name);
1274 preamble += features.getLyXSGMLEntities();
1276 if (!preamble.empty()) {
1277 os << "\n [ " << preamble << " ]";
1282 string top = top_element;
1284 if (runparams.flavor == OutputParams::XML)
1285 top += params().language->code();
1287 top += params().language->code().substr(0,2);
1290 if (!params().options.empty()) {
1292 top += params().options;
1295 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1296 << " file was created by LyX " << lyx_version
1297 << "\n See http://www.lyx.org/ for more information -->\n";
1299 params().getTextClass().counters().reset();
1301 loadChildDocuments();
1303 sgml::openTag(os, top);
1305 docbookParagraphs(paragraphs(), *this, os, runparams);
1306 sgml::closeTag(os, top_element);
1310 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1311 // Other flags: -wall -v0 -x
1312 int Buffer::runChktex()
1316 // get LaTeX-Filename
1317 FileName const path(temppath());
1318 string const name = addName(path.absFilename(), latexName());
1319 string const org_path = filePath();
1321 support::Path p(path); // path to LaTeX file
1322 message(_("Running chktex..."));
1324 // Generate the LaTeX file if neccessary
1325 OutputParams runparams(¶ms().encoding());
1326 runparams.flavor = OutputParams::LATEX;
1327 runparams.nice = false;
1328 makeLaTeXFile(FileName(name), org_path, runparams);
1331 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1332 int const res = chktex.run(terr); // run chktex
1335 Alert::error(_("chktex failure"),
1336 _("Could not run chktex successfully."));
1337 } else if (res > 0) {
1338 ErrorList & errlist = pimpl_->errorLists["ChkTeX"];
1340 bufferErrors(terr, errlist);
1351 void Buffer::validate(LaTeXFeatures & features) const
1353 TextClass const & tclass = params().getTextClass();
1355 if (params().outputChanges) {
1356 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1357 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1358 LaTeXFeatures::isAvailable("xcolor");
1360 if (features.runparams().flavor == OutputParams::LATEX) {
1362 features.require("ct-dvipost");
1363 features.require("dvipost");
1364 } else if (xcolorsoul) {
1365 features.require("ct-xcolor-soul");
1366 features.require("soul");
1367 features.require("xcolor");
1369 features.require("ct-none");
1371 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1373 features.require("ct-xcolor-soul");
1374 features.require("soul");
1375 features.require("xcolor");
1376 features.require("pdfcolmk"); // improves color handling in PDF output
1378 features.require("ct-none");
1383 // AMS Style is at document level
1384 if (params().use_amsmath == BufferParams::package_on
1385 || tclass.provides("amsmath"))
1386 features.require("amsmath");
1387 if (params().use_esint == BufferParams::package_on)
1388 features.require("esint");
1390 loadChildDocuments();
1392 for_each(paragraphs().begin(), paragraphs().end(),
1393 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1395 // the bullet shapes are buffer level not paragraph level
1396 // so they are tested here
1397 for (int i = 0; i < 4; ++i) {
1398 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1399 int const font = params().user_defined_bullet(i).getFont();
1401 int const c = params()
1402 .user_defined_bullet(i)
1409 features.require("latexsym");
1411 } else if (font == 1) {
1412 features.require("amssymb");
1413 } else if ((font >= 2 && font <= 5)) {
1414 features.require("pifont");
1419 if (lyxerr.debugging(Debug::LATEX)) {
1420 features.showStruct();
1425 void Buffer::getLabelList(vector<docstring> & list) const
1427 /// if this is a child document and the parent is already loaded
1428 /// Use the parent's list instead [ale990407]
1429 Buffer const * tmp = masterBuffer();
1431 lyxerr << "masterBuffer() failed!" << endl;
1435 tmp->getLabelList(list);
1439 loadChildDocuments();
1441 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1442 it.nextInset()->getLabelList(*this, list);
1446 void Buffer::updateBibfilesCache()
1448 // if this is a child document and the parent is already loaded
1449 // update the parent's cache instead
1450 Buffer * tmp = masterBuffer();
1453 tmp->updateBibfilesCache();
1457 bibfilesCache_.clear();
1458 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1459 if (it->lyxCode() == BIBTEX_CODE) {
1460 InsetBibtex const & inset =
1461 static_cast<InsetBibtex const &>(*it);
1462 vector<FileName> const bibfiles = inset.getFiles(*this);
1463 bibfilesCache_.insert(bibfilesCache_.end(),
1466 } else if (it->lyxCode() == INCLUDE_CODE) {
1467 InsetInclude & inset =
1468 static_cast<InsetInclude &>(*it);
1469 inset.updateBibfilesCache(*this);
1470 vector<FileName> const & bibfiles =
1471 inset.getBibfilesCache(*this);
1472 bibfilesCache_.insert(bibfilesCache_.end(),
1480 vector<FileName> const & Buffer::getBibfilesCache() const
1482 // if this is a child document and the parent is already loaded
1483 // use the parent's cache instead
1484 Buffer const * tmp = masterBuffer();
1487 return tmp->getBibfilesCache();
1489 // We update the cache when first used instead of at loading time.
1490 if (bibfilesCache_.empty())
1491 const_cast<Buffer *>(this)->updateBibfilesCache();
1493 return bibfilesCache_;
1497 bool Buffer::isDepClean(string const & name) const
1499 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1500 if (it == pimpl_->dep_clean.end())
1506 void Buffer::markDepClean(string const & name)
1508 pimpl_->dep_clean[name] = true;
1512 bool Buffer::dispatch(string const & command, bool * result)
1514 return dispatch(lyxaction.lookupFunc(command), result);
1518 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1520 bool dispatched = true;
1522 switch (func.action) {
1523 case LFUN_BUFFER_EXPORT: {
1524 bool const tmp = doExport(to_utf8(func.argument()), false);
1537 void Buffer::changeLanguage(Language const * from, Language const * to)
1542 for_each(par_iterator_begin(),
1544 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1548 bool Buffer::isMultiLingual() const
1550 ParConstIterator end = par_iterator_end();
1551 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1552 if (it->isMultiLingual(params()))
1559 ParIterator Buffer::getParFromID(int const id) const
1561 ParConstIterator it = par_iterator_begin();
1562 ParConstIterator const end = par_iterator_end();
1565 // John says this is called with id == -1 from undo
1566 lyxerr << "getParFromID(), id: " << id << endl;
1570 for (; it != end; ++it)
1578 bool Buffer::hasParWithID(int const id) const
1580 ParConstIterator const it = getParFromID(id);
1581 return it != par_iterator_end();
1585 ParIterator Buffer::par_iterator_begin()
1587 return lyx::par_iterator_begin(inset());
1591 ParIterator Buffer::par_iterator_end()
1593 return lyx::par_iterator_end(inset());
1597 ParConstIterator Buffer::par_iterator_begin() const
1599 return lyx::par_const_iterator_begin(inset());
1603 ParConstIterator Buffer::par_iterator_end() const
1605 return lyx::par_const_iterator_end(inset());
1609 Language const * Buffer::language() const
1611 return params().language;
1615 docstring const Buffer::B_(string const & l10n) const
1617 return params().B_(l10n);
1621 bool Buffer::isClean() const
1623 return pimpl_->lyx_clean;
1627 bool Buffer::isBakClean() const
1629 return pimpl_->bak_clean;
1633 bool Buffer::isExternallyModified(CheckMethod method) const
1635 BOOST_ASSERT(pimpl_->filename.exists());
1636 // if method == timestamp, check timestamp before checksum
1637 return (method == checksum_method
1638 || pimpl_->timestamp_ != pimpl_->filename.lastModified())
1639 && pimpl_->checksum_ != sum(pimpl_->filename);
1643 void Buffer::saveCheckSum(FileName const & file) const
1645 if (file.exists()) {
1646 pimpl_->timestamp_ = file.lastModified();
1647 pimpl_->checksum_ = sum(file);
1649 // in the case of save to a new file.
1650 pimpl_->timestamp_ = 0;
1651 pimpl_->checksum_ = 0;
1656 void Buffer::markClean() const
1658 if (!pimpl_->lyx_clean) {
1659 pimpl_->lyx_clean = true;
1662 // if the .lyx file has been saved, we don't need an
1664 pimpl_->bak_clean = true;
1668 void Buffer::markBakClean() const
1670 pimpl_->bak_clean = true;
1674 void Buffer::setUnnamed(bool flag)
1676 pimpl_->unnamed = flag;
1680 bool Buffer::isUnnamed() const
1682 return pimpl_->unnamed;
1686 // FIXME: this function should be moved to buffer_pimpl.C
1687 void Buffer::markDirty()
1689 if (pimpl_->lyx_clean) {
1690 pimpl_->lyx_clean = false;
1693 pimpl_->bak_clean = false;
1695 DepClean::iterator it = pimpl_->dep_clean.begin();
1696 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1698 for (; it != end; ++it)
1703 FileName Buffer::fileName() const
1705 return pimpl_->filename;
1709 string Buffer::absFileName() const
1711 return pimpl_->filename.absFilename();
1715 string const & Buffer::filePath() const
1717 return params().filepath;
1721 bool Buffer::isReadonly() const
1723 return pimpl_->read_only;
1727 void Buffer::setParentName(string const & name)
1729 if (name == pimpl_->filename.absFilename())
1730 // Avoids recursive include.
1731 params().parentname.clear();
1733 params().parentname = name;
1737 Buffer const * Buffer::masterBuffer() const
1739 if (!params().parentname.empty()
1740 && theBufferList().exists(params().parentname)) {
1741 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1742 //We need to check if the parent is us...
1743 //FIXME RECURSIVE INCLUDE
1744 //This is not sufficient, since recursive includes could be downstream.
1745 if (buf && buf != this)
1746 return buf->masterBuffer();
1753 Buffer * Buffer::masterBuffer()
1755 if (!params().parentname.empty()
1756 && theBufferList().exists(params().parentname)) {
1757 Buffer * buf = theBufferList().getBuffer(params().parentname);
1758 //We need to check if the parent is us...
1759 //FIXME RECURSIVE INCLUDE
1760 //This is not sufficient, since recursive includes could be downstream.
1761 if (buf && buf != this)
1762 return buf->masterBuffer();
1769 bool Buffer::hasMacro(docstring const & name, Paragraph const & par) const
1771 Impl::PositionToMacroMap::iterator it;
1772 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1773 if( it != pimpl_->macros[name].end() )
1776 // If there is a master buffer, query that
1777 const Buffer *master = masterBuffer();
1778 if (master && master!=this)
1779 return master->hasMacro(name);
1781 return MacroTable::globalMacros().has(name);
1785 bool Buffer::hasMacro(docstring const & name) const
1787 if( !pimpl_->macros[name].empty() )
1790 // If there is a master buffer, query that
1791 const Buffer *master = masterBuffer();
1792 if (master && master!=this)
1793 return master->hasMacro(name);
1795 return MacroTable::globalMacros().has(name);
1799 MacroData const & Buffer::getMacro(docstring const & name, Paragraph const & par) const
1801 Impl::PositionToMacroMap::iterator it;
1802 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1803 if( it != pimpl_->macros[name].end() )
1806 // If there is a master buffer, query that
1807 const Buffer *master = masterBuffer();
1808 if (master && master!=this)
1809 return master->getMacro(name);
1811 return MacroTable::globalMacros().get(name);
1815 MacroData const & Buffer::getMacro(docstring const & name) const
1817 Impl::PositionToMacroMap::iterator it;
1818 it = pimpl_->macros[name].begin();
1819 if( it != pimpl_->macros[name].end() )
1822 // If there is a master buffer, query that
1823 const Buffer *master = masterBuffer();
1824 if (master && master!=this)
1825 return master->getMacro(name);
1827 return MacroTable::globalMacros().get(name);
1831 void Buffer::updateMacros()
1833 // start with empty table
1834 pimpl_->macros = Impl::NameToPositionMacroMap();
1836 // Iterate over buffer
1837 ParagraphList & pars = text().paragraphs();
1838 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1839 // set position again
1840 pars[i].setMacrocontextPosition(i);
1842 //lyxerr << "searching main par " << i
1843 // << " for macro definitions" << std::endl;
1844 InsetList const & insets = pars[i].insetList();
1845 InsetList::const_iterator it = insets.begin();
1846 InsetList::const_iterator end = insets.end();
1847 for ( ; it != end; ++it) {
1848 if (it->inset->lyxCode() != MATHMACRO_CODE)
1852 MathMacroTemplate const & macroTemplate
1853 = static_cast<MathMacroTemplate const &>(*it->inset);
1856 if (macroTemplate.validMacro()) {
1857 MacroData macro = macroTemplate.asMacroData();
1860 // call hasMacro here instead of directly querying mc to
1861 // also take the master document into consideration
1862 macro.setRedefinition(hasMacro(macroTemplate.name()));
1864 // register macro (possibly overwrite the previous one of this paragraph)
1865 pimpl_->macros[macroTemplate.name()][i] = macro;
1872 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1875 //FIXME: This does not work for child documents yet.
1876 BOOST_ASSERT(code == CITE_CODE || code == REF_CODE);
1877 // Check if the label 'from' appears more than once
1878 vector<docstring> labels;
1881 if (code == CITE_CODE) {
1883 keys.fillWithBibKeys(this);
1884 BiblioInfo::const_iterator bit = keys.begin();
1885 BiblioInfo::const_iterator bend = keys.end();
1887 for (; bit != bend; ++bit)
1889 labels.push_back(bit->first);
1892 getLabelList(labels);
1893 paramName = "reference";
1896 if (std::count(labels.begin(), labels.end(), from) > 1)
1899 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1900 if (it->lyxCode() == code) {
1901 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1902 docstring const oldValue = inset.getParam(paramName);
1903 if (oldValue == from)
1904 inset.setParam(paramName, to);
1910 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1911 pit_type par_end, bool full_source)
1913 OutputParams runparams(¶ms().encoding());
1914 runparams.nice = true;
1915 runparams.flavor = OutputParams::LATEX;
1916 runparams.linelen = lyxrc.plaintext_linelen;
1917 // No side effect of file copying and image conversion
1918 runparams.dryrun = true;
1922 os << "% " << _("Preview source code") << "\n\n";
1926 writeLaTeXSource(os, filePath(), runparams, true, true);
1928 writeDocBookSource(os, absFileName(), runparams, false);
1931 runparams.par_begin = par_begin;
1932 runparams.par_end = par_end;
1933 if (par_begin + 1 == par_end)
1935 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1939 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1940 convert<docstring>(par_begin),
1941 convert<docstring>(par_end - 1))
1945 // output paragraphs
1947 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1950 docbookParagraphs(paragraphs(), *this, os, runparams);
1956 ErrorList const & Buffer::errorList(string const & type) const
1958 static ErrorList const emptyErrorList;
1959 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1960 if (I == pimpl_->errorLists.end())
1961 return emptyErrorList;
1967 ErrorList & Buffer::errorList(string const & type)
1969 return pimpl_->errorLists[type];
1973 void Buffer::structureChanged() const
1976 gui_->structureChanged();
1980 void Buffer::errors(std::string const & err) const
1987 void Buffer::message(docstring const & msg) const
1994 void Buffer::setBusy(bool on) const
2001 void Buffer::setReadOnly(bool on) const
2004 gui_->setReadOnly(on);
2008 void Buffer::updateTitles() const
2011 gui_->updateTitles();
2015 void Buffer::resetAutosaveTimers() const
2018 gui_->resetAutosaveTimers();
2022 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
2031 class AutoSaveBuffer : public support::ForkedProcess {
2034 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
2035 : buffer_(buffer), fname_(fname) {}
2037 virtual boost::shared_ptr<ForkedProcess> clone() const
2039 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
2044 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
2045 from_utf8(fname_.absFilename())));
2046 return run(DontWait);
2050 virtual int generateChild();
2052 Buffer const & buffer_;
2057 #if !defined (HAVE_FORK)
2061 int AutoSaveBuffer::generateChild()
2063 // tmp_ret will be located (usually) in /tmp
2064 // will that be a problem?
2065 pid_t const pid = fork();
2066 // If you want to debug the autosave
2067 // you should set pid to -1, and comment out the fork.
2068 if (pid == 0 || pid == -1) {
2069 // pid = -1 signifies that lyx was unable
2070 // to fork. But we will do the save
2072 bool failed = false;
2074 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2075 if (!tmp_ret.empty()) {
2076 buffer_.writeFile(tmp_ret);
2077 // assume successful write of tmp_ret
2078 if (!rename(tmp_ret, fname_)) {
2080 // most likely couldn't move between
2081 // filesystems unless write of tmp_ret
2082 // failed so remove tmp file (if it
2091 // failed to write/rename tmp_ret so try writing direct
2092 if (!buffer_.writeFile(fname_)) {
2093 // It is dangerous to do this in the child,
2094 // but safe in the parent, so...
2095 if (pid == -1) // emit message signal.
2096 buffer_.message(_("Autosave failed!"));
2099 if (pid == 0) { // we are the child so...
2109 // Perfect target for a thread...
2110 void Buffer::autoSave() const
2112 if (isBakClean() || isReadonly()) {
2113 // We don't save now, but we'll try again later
2114 resetAutosaveTimers();
2118 // emit message signal.
2119 message(_("Autosaving current document..."));
2121 // create autosave filename
2122 string fname = filePath();
2124 fname += onlyFilename(absFileName());
2127 AutoSaveBuffer autosave(*this, FileName(fname));
2131 resetAutosaveTimers();
2135 /** Write a buffer to a new file name and rename the buffer
2136 according to the new file name.
2138 This function is e.g. used by menu callbacks and
2139 LFUN_BUFFER_WRITE_AS.
2141 If 'newname' is empty (the default), the user is asked via a
2142 dialog for the buffer's new name and location.
2144 If 'newname' is non-empty and has an absolute path, that is used.
2145 Otherwise the base directory of the buffer is used as the base
2146 for any relative path in 'newname'.
2149 bool Buffer::writeAs(string const & newname)
2151 string fname = absFileName();
2152 string const oldname = fname;
2154 if (newname.empty()) { /// No argument? Ask user through dialog
2157 FileDialog dlg(_("Choose a filename to save document as"),
2158 LFUN_BUFFER_WRITE_AS);
2159 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
2160 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
2162 if (!support::isLyXFilename(fname))
2165 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2167 FileDialog::Result result =
2168 dlg.save(from_utf8(onlyPath(fname)),
2170 from_utf8(onlyFilename(fname)));
2172 if (result.first == FileDialog::Later)
2175 fname = to_utf8(result.second);
2180 // Make sure the absolute filename ends with appropriate suffix
2181 fname = makeAbsPath(fname).absFilename();
2182 if (!support::isLyXFilename(fname))
2186 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2188 if (FileName(fname).exists()) {
2189 docstring const file = makeDisplayPath(fname, 30);
2190 docstring text = bformat(_("The document %1$s already "
2191 "exists.\n\nDo you want to "
2192 "overwrite that document?"),
2194 int const ret = Alert::prompt(_("Overwrite document?"),
2195 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2201 // Ok, change the name of the buffer
2204 bool unnamed = isUnnamed();
2206 saveCheckSum(FileName(fname));
2209 setFileName(oldname);
2210 setUnnamed(unnamed);
2211 saveCheckSum(FileName(oldname));
2215 removeAutosaveFile(oldname);
2220 bool Buffer::menuWrite()
2223 LyX::ref().session().lastFiles().add(FileName(absFileName()));
2227 // FIXME: we don't tell the user *WHY* the save failed !!
2229 docstring const file = makeDisplayPath(absFileName(), 30);
2231 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2232 "Do you want to rename the document and "
2233 "try again?"), file);
2234 int const ret = Alert::prompt(_("Rename and save?"),
2235 text, 0, 1, _("&Rename"), _("&Cancel"));
2244 void Buffer::loadChildDocuments() const
2246 bool parse_error = false;
2248 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2249 if (it->lyxCode() != INCLUDE_CODE)
2251 InsetCommand const & inset = static_cast<InsetCommand const &>(*it);
2252 InsetCommandParams const & ip = inset.params();
2253 Buffer * child = loadIfNeeded(*this, ip);
2256 parse_error |= !child->errorList("Parse").empty();
2257 child->loadChildDocuments();
2260 if (use_gui && masterBuffer() == this)
2261 updateLabels(*this);
2265 string Buffer::bufferFormat() const
2275 bool Buffer::doExport(string const & format, bool put_in_tempdir,
2276 string & result_file)
2278 string backend_format;
2279 OutputParams runparams(¶ms().encoding());
2280 runparams.flavor = OutputParams::LATEX;
2281 runparams.linelen = lyxrc.plaintext_linelen;
2282 vector<string> backs = backends();
2283 if (find(backs.begin(), backs.end(), format) == backs.end()) {
2284 // Get shortest path to format
2285 Graph::EdgePath path;
2286 for (vector<string>::const_iterator it = backs.begin();
2287 it != backs.end(); ++it) {
2288 Graph::EdgePath p = theConverters().getPath(*it, format);
2289 if (!p.empty() && (path.empty() || p.size() < path.size())) {
2290 backend_format = *it;
2295 runparams.flavor = theConverters().getFlavor(path);
2297 Alert::error(_("Couldn't export file"),
2298 bformat(_("No information for exporting the format %1$s."),
2299 formats.prettyName(format)));
2303 backend_format = format;
2304 // FIXME: Don't hardcode format names here, but use a flag
2305 if (backend_format == "pdflatex")
2306 runparams.flavor = OutputParams::PDFLATEX;
2309 string filename = latexName(false);
2310 filename = addName(temppath(), filename);
2311 filename = changeExtension(filename,
2312 formats.extension(backend_format));
2314 // Plain text backend
2315 if (backend_format == "text")
2316 writePlaintextFile(*this, FileName(filename), runparams);
2318 else if (backend_format == "lyx")
2319 writeFile(FileName(filename));
2321 else if (isDocBook()) {
2322 runparams.nice = !put_in_tempdir;
2323 makeDocBookFile(FileName(filename), runparams);
2326 else if (backend_format == format) {
2327 runparams.nice = true;
2328 if (!makeLaTeXFile(FileName(filename), string(), runparams))
2330 } else if (!lyxrc.tex_allows_spaces
2331 && support::contains(filePath(), ' ')) {
2332 Alert::error(_("File name error"),
2333 _("The directory path to the document cannot contain spaces."));
2336 runparams.nice = false;
2337 if (!makeLaTeXFile(FileName(filename), filePath(), runparams))
2341 string const error_type = (format == "program")
2342 ? "Build" : bufferFormat();
2343 string const ext = formats.extension(format);
2344 FileName const tmp_result_file(changeExtension(filename, ext));
2345 bool const success = theConverters().convert(this, FileName(filename),
2346 tmp_result_file, FileName(absFileName()), backend_format, format,
2347 errorList(error_type));
2348 // Emit the signal to show the error list.
2349 if (format != backend_format)
2355 result_file = tmp_result_file.absFilename();
2357 result_file = changeExtension(absFileName(), ext);
2358 // We need to copy referenced files (e. g. included graphics
2359 // if format == "dvi") to the result dir.
2360 vector<ExportedFile> const files =
2361 runparams.exportdata->externalFiles(format);
2362 string const dest = onlyPath(result_file);
2363 CopyStatus status = SUCCESS;
2364 for (vector<ExportedFile>::const_iterator it = files.begin();
2365 it != files.end() && status != CANCEL; ++it) {
2367 formats.getFormatFromFile(it->sourceName);
2368 status = copyFile(fmt, it->sourceName,
2369 makeAbsPath(it->exportName, dest),
2370 it->exportName, status == FORCE);
2372 if (status == CANCEL) {
2373 message(_("Document export cancelled."));
2374 } else if (tmp_result_file.exists()) {
2375 // Finally copy the main file
2376 status = copyFile(format, tmp_result_file,
2377 FileName(result_file), result_file,
2379 message(bformat(_("Document exported as %1$s "
2381 formats.prettyName(format),
2382 makeDisplayPath(result_file)));
2384 // This must be a dummy converter like fax (bug 1888)
2385 message(bformat(_("Document exported as %1$s"),
2386 formats.prettyName(format)));
2394 bool Buffer::doExport(string const & format, bool put_in_tempdir)
2397 return doExport(format, put_in_tempdir, result_file);
2401 bool Buffer::preview(string const & format)
2404 if (!doExport(format, true, result_file))
2406 return formats.view(*this, FileName(result_file), format);
2410 bool Buffer::isExportable(string const & format) const
2412 vector<string> backs = backends();
2413 for (vector<string>::const_iterator it = backs.begin();
2414 it != backs.end(); ++it)
2415 if (theConverters().isReachable(*it, format))
2421 vector<Format const *> Buffer::exportableFormats(bool only_viewable) const
2423 vector<string> backs = backends();
2424 vector<Format const *> result =
2425 theConverters().getReachable(backs[0], only_viewable, true);
2426 for (vector<string>::const_iterator it = backs.begin() + 1;
2427 it != backs.end(); ++it) {
2428 vector<Format const *> r =
2429 theConverters().getReachable(*it, only_viewable, false);
2430 result.insert(result.end(), r.begin(), r.end());
2436 vector<string> Buffer::backends() const
2439 if (params().getTextClass().isTeXClassAvailable()) {
2440 v.push_back(bufferFormat());
2441 // FIXME: Don't hardcode format names here, but use a flag
2442 if (v.back() == "latex")
2443 v.push_back("pdflatex");
2445 v.push_back("text");
2451 bool Buffer::readFileHelper(FileName const & s)
2453 // File information about normal file
2455 docstring const file = makeDisplayPath(s.absFilename(), 50);
2456 docstring text = bformat(_("The specified document\n%1$s"
2457 "\ncould not be read."), file);
2458 Alert::error(_("Could not read document"), text);
2462 // Check if emergency save file exists and is newer.
2463 FileName const e(s.absFilename() + ".emergency");
2465 if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) {
2466 docstring const file = makeDisplayPath(s.absFilename(), 20);
2467 docstring const text =
2468 bformat(_("An emergency save of the document "
2470 "Recover emergency save?"), file);
2471 switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
2472 _("&Recover"), _("&Load Original"),
2476 // the file is not saved if we load the emergency file.
2486 // Now check if autosave file is newer.
2487 FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#');
2489 if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) {
2490 docstring const file = makeDisplayPath(s.absFilename(), 20);
2491 docstring const text =
2492 bformat(_("The backup of the document "
2493 "%1$s is newer.\n\nLoad the "
2494 "backup instead?"), file);
2495 switch (Alert::prompt(_("Load backup?"), text, 0, 2,
2496 _("&Load backup"), _("Load &original"),
2500 // the file is not saved if we load the autosave file.
2504 // Here we delete the autosave
2515 bool Buffer::loadLyXFile(FileName const & s)
2517 if (s.isReadable()) {
2518 if (readFileHelper(s)) {
2519 lyxvc().file_found_hook(s);
2520 if (!s.isWritable())
2525 docstring const file = makeDisplayPath(s.absFilename(), 20);
2526 // Here we probably should run
2527 if (LyXVC::file_not_found_hook(s)) {
2528 docstring const text =
2529 bformat(_("Do you want to retrieve the document"
2530 " %1$s from version control?"), file);
2531 int const ret = Alert::prompt(_("Retrieve from version control?"),
2532 text, 0, 1, _("&Retrieve"), _("&Cancel"));
2535 // How can we know _how_ to do the checkout?
2536 // With the current VC support it has to be,
2537 // a RCS file since CVS do not have special ,v files.
2539 return loadLyXFile(s);
2547 void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
2549 TeXErrors::Errors::const_iterator cit = terr.begin();
2550 TeXErrors::Errors::const_iterator end = terr.end();
2552 for (; cit != end; ++cit) {
2555 int errorRow = cit->error_in_line;
2556 bool found = texrow().getIdFromRow(errorRow, id_start,
2562 found = texrow().getIdFromRow(errorRow, id_end, pos_end);
2563 } while (found && id_start == id_end && pos_start == pos_end);
2565 errorList.push_back(ErrorItem(cit->error_desc,
2566 cit->error_text, id_start, pos_start, pos_end));