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 && fs::exists(encodedFilename)) {
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 (fs::exists(encodedFilename) && 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 string Buffer::absFileName() const
1705 return pimpl_->filename.absFilename();
1709 string const & Buffer::filePath() const
1711 return params().filepath;
1715 bool Buffer::isReadonly() const
1717 return pimpl_->read_only;
1721 void Buffer::setParentName(string const & name)
1723 if (name == pimpl_->filename.absFilename())
1724 // Avoids recursive include.
1725 params().parentname.clear();
1727 params().parentname = name;
1731 Buffer const * Buffer::masterBuffer() const
1733 if (!params().parentname.empty()
1734 && theBufferList().exists(params().parentname)) {
1735 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1736 //We need to check if the parent is us...
1737 //FIXME RECURSIVE INCLUDE
1738 //This is not sufficient, since recursive includes could be downstream.
1739 if (buf && buf != this)
1740 return buf->masterBuffer();
1747 Buffer * Buffer::masterBuffer()
1749 if (!params().parentname.empty()
1750 && theBufferList().exists(params().parentname)) {
1751 Buffer * buf = theBufferList().getBuffer(params().parentname);
1752 //We need to check if the parent is us...
1753 //FIXME RECURSIVE INCLUDE
1754 //This is not sufficient, since recursive includes could be downstream.
1755 if (buf && buf != this)
1756 return buf->masterBuffer();
1763 bool Buffer::hasMacro(docstring const & name, Paragraph const & par) const
1765 Impl::PositionToMacroMap::iterator it;
1766 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1767 if( it != pimpl_->macros[name].end() )
1770 // If there is a master buffer, query that
1771 const Buffer *master = masterBuffer();
1772 if (master && master!=this)
1773 return master->hasMacro(name);
1775 return MacroTable::globalMacros().has(name);
1779 bool Buffer::hasMacro(docstring const & name) const
1781 if( !pimpl_->macros[name].empty() )
1784 // If there is a master buffer, query that
1785 const Buffer *master = masterBuffer();
1786 if (master && master!=this)
1787 return master->hasMacro(name);
1789 return MacroTable::globalMacros().has(name);
1793 MacroData const & Buffer::getMacro(docstring const & name, Paragraph const & par) const
1795 Impl::PositionToMacroMap::iterator it;
1796 it = pimpl_->macros[name].upper_bound(par.macrocontextPosition());
1797 if( it != pimpl_->macros[name].end() )
1800 // If there is a master buffer, query that
1801 const Buffer *master = masterBuffer();
1802 if (master && master!=this)
1803 return master->getMacro(name);
1805 return MacroTable::globalMacros().get(name);
1809 MacroData const & Buffer::getMacro(docstring const & name) const
1811 Impl::PositionToMacroMap::iterator it;
1812 it = pimpl_->macros[name].begin();
1813 if( it != pimpl_->macros[name].end() )
1816 // If there is a master buffer, query that
1817 const Buffer *master = masterBuffer();
1818 if (master && master!=this)
1819 return master->getMacro(name);
1821 return MacroTable::globalMacros().get(name);
1825 void Buffer::updateMacros()
1827 // start with empty table
1828 pimpl_->macros = Impl::NameToPositionMacroMap();
1830 // Iterate over buffer
1831 ParagraphList & pars = text().paragraphs();
1832 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1833 // set position again
1834 pars[i].setMacrocontextPosition(i);
1836 //lyxerr << "searching main par " << i
1837 // << " for macro definitions" << std::endl;
1838 InsetList const & insets = pars[i].insetList();
1839 InsetList::const_iterator it = insets.begin();
1840 InsetList::const_iterator end = insets.end();
1841 for ( ; it != end; ++it) {
1842 if (it->inset->lyxCode() != MATHMACRO_CODE)
1846 MathMacroTemplate const & macroTemplate
1847 = static_cast<MathMacroTemplate const &>(*it->inset);
1850 if (macroTemplate.validMacro()) {
1851 MacroData macro = macroTemplate.asMacroData();
1854 // call hasMacro here instead of directly querying mc to
1855 // also take the master document into consideration
1856 macro.setRedefinition(hasMacro(macroTemplate.name()));
1858 // register macro (possibly overwrite the previous one of this paragraph)
1859 pimpl_->macros[macroTemplate.name()][i] = macro;
1866 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1869 //FIXME: This does not work for child documents yet.
1870 BOOST_ASSERT(code == CITE_CODE || code == REF_CODE);
1871 // Check if the label 'from' appears more than once
1872 vector<docstring> labels;
1875 if (code == CITE_CODE) {
1877 keys.fillWithBibKeys(this);
1878 BiblioInfo::const_iterator bit = keys.begin();
1879 BiblioInfo::const_iterator bend = keys.end();
1881 for (; bit != bend; ++bit)
1883 labels.push_back(bit->first);
1886 getLabelList(labels);
1887 paramName = "reference";
1890 if (std::count(labels.begin(), labels.end(), from) > 1)
1893 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1894 if (it->lyxCode() == code) {
1895 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1896 docstring const oldValue = inset.getParam(paramName);
1897 if (oldValue == from)
1898 inset.setParam(paramName, to);
1904 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1905 pit_type par_end, bool full_source)
1907 OutputParams runparams(¶ms().encoding());
1908 runparams.nice = true;
1909 runparams.flavor = OutputParams::LATEX;
1910 runparams.linelen = lyxrc.plaintext_linelen;
1911 // No side effect of file copying and image conversion
1912 runparams.dryrun = true;
1916 os << "% " << _("Preview source code") << "\n\n";
1920 writeLaTeXSource(os, filePath(), runparams, true, true);
1922 writeDocBookSource(os, absFileName(), runparams, false);
1925 runparams.par_begin = par_begin;
1926 runparams.par_end = par_end;
1927 if (par_begin + 1 == par_end)
1929 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1933 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1934 convert<docstring>(par_begin),
1935 convert<docstring>(par_end - 1))
1939 // output paragraphs
1941 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1944 docbookParagraphs(paragraphs(), *this, os, runparams);
1950 ErrorList const & Buffer::errorList(string const & type) const
1952 static ErrorList const emptyErrorList;
1953 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1954 if (I == pimpl_->errorLists.end())
1955 return emptyErrorList;
1961 ErrorList & Buffer::errorList(string const & type)
1963 return pimpl_->errorLists[type];
1967 void Buffer::structureChanged() const
1970 gui_->structureChanged();
1974 void Buffer::errors(std::string const & err) const
1981 void Buffer::message(docstring const & msg) const
1988 void Buffer::setBusy(bool on) const
1995 void Buffer::setReadOnly(bool on) const
1998 gui_->setReadOnly(on);
2002 void Buffer::updateTitles() const
2005 gui_->updateTitles();
2009 void Buffer::resetAutosaveTimers() const
2012 gui_->resetAutosaveTimers();
2016 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
2025 class AutoSaveBuffer : public support::ForkedProcess {
2028 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
2029 : buffer_(buffer), fname_(fname) {}
2031 virtual boost::shared_ptr<ForkedProcess> clone() const
2033 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
2038 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
2039 from_utf8(fname_.absFilename())));
2040 return run(DontWait);
2044 virtual int generateChild();
2046 Buffer const & buffer_;
2051 #if !defined (HAVE_FORK)
2055 int AutoSaveBuffer::generateChild()
2057 // tmp_ret will be located (usually) in /tmp
2058 // will that be a problem?
2059 pid_t const pid = fork();
2060 // If you want to debug the autosave
2061 // you should set pid to -1, and comment out the fork.
2062 if (pid == 0 || pid == -1) {
2063 // pid = -1 signifies that lyx was unable
2064 // to fork. But we will do the save
2066 bool failed = false;
2068 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2069 if (!tmp_ret.empty()) {
2070 buffer_.writeFile(tmp_ret);
2071 // assume successful write of tmp_ret
2072 if (!rename(tmp_ret, fname_)) {
2074 // most likely couldn't move between
2075 // filesystems unless write of tmp_ret
2076 // failed so remove tmp file (if it
2085 // failed to write/rename tmp_ret so try writing direct
2086 if (!buffer_.writeFile(fname_)) {
2087 // It is dangerous to do this in the child,
2088 // but safe in the parent, so...
2089 if (pid == -1) // emit message signal.
2090 buffer_.message(_("Autosave failed!"));
2093 if (pid == 0) { // we are the child so...
2103 // Perfect target for a thread...
2104 void Buffer::autoSave() const
2106 if (isBakClean() || isReadonly()) {
2107 // We don't save now, but we'll try again later
2108 resetAutosaveTimers();
2112 // emit message signal.
2113 message(_("Autosaving current document..."));
2115 // create autosave filename
2116 string fname = filePath();
2118 fname += onlyFilename(absFileName());
2121 AutoSaveBuffer autosave(*this, FileName(fname));
2125 resetAutosaveTimers();
2129 /** Write a buffer to a new file name and rename the buffer
2130 according to the new file name.
2132 This function is e.g. used by menu callbacks and
2133 LFUN_BUFFER_WRITE_AS.
2135 If 'newname' is empty (the default), the user is asked via a
2136 dialog for the buffer's new name and location.
2138 If 'newname' is non-empty and has an absolute path, that is used.
2139 Otherwise the base directory of the buffer is used as the base
2140 for any relative path in 'newname'.
2143 bool Buffer::writeAs(string const & newname)
2145 string fname = absFileName();
2146 string const oldname = fname;
2148 if (newname.empty()) { /// No argument? Ask user through dialog
2151 FileDialog dlg(_("Choose a filename to save document as"),
2152 LFUN_BUFFER_WRITE_AS);
2153 dlg.setButton1(_("Documents|#o#O"), from_utf8(lyxrc.document_path));
2154 dlg.setButton2(_("Templates|#T#t"), from_utf8(lyxrc.template_path));
2156 if (!support::isLyXFilename(fname))
2159 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2161 FileDialog::Result result =
2162 dlg.save(from_utf8(onlyPath(fname)),
2164 from_utf8(onlyFilename(fname)));
2166 if (result.first == FileDialog::Later)
2169 fname = to_utf8(result.second);
2174 // Make sure the absolute filename ends with appropriate suffix
2175 fname = makeAbsPath(fname).absFilename();
2176 if (!support::isLyXFilename(fname))
2180 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2182 if (FileName(fname).exists()) {
2183 docstring const file = makeDisplayPath(fname, 30);
2184 docstring text = bformat(_("The document %1$s already "
2185 "exists.\n\nDo you want to "
2186 "overwrite that document?"),
2188 int const ret = Alert::prompt(_("Overwrite document?"),
2189 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2195 // Ok, change the name of the buffer
2198 bool unnamed = isUnnamed();
2200 saveCheckSum(FileName(fname));
2203 setFileName(oldname);
2204 setUnnamed(unnamed);
2205 saveCheckSum(FileName(oldname));
2209 removeAutosaveFile(oldname);
2214 bool Buffer::menuWrite()
2217 LyX::ref().session().lastFiles().add(FileName(absFileName()));
2221 // FIXME: we don't tell the user *WHY* the save failed !!
2223 docstring const file = makeDisplayPath(absFileName(), 30);
2225 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2226 "Do you want to rename the document and "
2227 "try again?"), file);
2228 int const ret = Alert::prompt(_("Rename and save?"),
2229 text, 0, 1, _("&Rename"), _("&Cancel"));
2238 void Buffer::loadChildDocuments() const
2240 bool parse_error = false;
2242 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2243 if (it->lyxCode() != INCLUDE_CODE)
2245 InsetCommand const & inset = static_cast<InsetCommand const &>(*it);
2246 InsetCommandParams const & ip = inset.params();
2247 Buffer * child = loadIfNeeded(*this, ip);
2250 parse_error |= !child->errorList("Parse").empty();
2251 child->loadChildDocuments();
2254 if (use_gui && masterBuffer() == this)
2255 updateLabels(*this);
2259 string Buffer::bufferFormat() const
2269 bool Buffer::doExport(string const & format, bool put_in_tempdir,
2270 string & result_file)
2272 string backend_format;
2273 OutputParams runparams(¶ms().encoding());
2274 runparams.flavor = OutputParams::LATEX;
2275 runparams.linelen = lyxrc.plaintext_linelen;
2276 vector<string> backs = backends();
2277 if (find(backs.begin(), backs.end(), format) == backs.end()) {
2278 // Get shortest path to format
2279 Graph::EdgePath path;
2280 for (vector<string>::const_iterator it = backs.begin();
2281 it != backs.end(); ++it) {
2282 Graph::EdgePath p = theConverters().getPath(*it, format);
2283 if (!p.empty() && (path.empty() || p.size() < path.size())) {
2284 backend_format = *it;
2289 runparams.flavor = theConverters().getFlavor(path);
2291 Alert::error(_("Couldn't export file"),
2292 bformat(_("No information for exporting the format %1$s."),
2293 formats.prettyName(format)));
2297 backend_format = format;
2298 // FIXME: Don't hardcode format names here, but use a flag
2299 if (backend_format == "pdflatex")
2300 runparams.flavor = OutputParams::PDFLATEX;
2303 string filename = latexName(false);
2304 filename = addName(temppath(), filename);
2305 filename = changeExtension(filename,
2306 formats.extension(backend_format));
2308 // Plain text backend
2309 if (backend_format == "text")
2310 writePlaintextFile(*this, FileName(filename), runparams);
2312 else if (backend_format == "lyx")
2313 writeFile(FileName(filename));
2315 else if (isDocBook()) {
2316 runparams.nice = !put_in_tempdir;
2317 makeDocBookFile(FileName(filename), runparams);
2320 else if (backend_format == format) {
2321 runparams.nice = true;
2322 if (!makeLaTeXFile(FileName(filename), string(), runparams))
2324 } else if (!lyxrc.tex_allows_spaces
2325 && support::contains(filePath(), ' ')) {
2326 Alert::error(_("File name error"),
2327 _("The directory path to the document cannot contain spaces."));
2330 runparams.nice = false;
2331 if (!makeLaTeXFile(FileName(filename), filePath(), runparams))
2335 string const error_type = (format == "program")
2336 ? "Build" : bufferFormat();
2337 string const ext = formats.extension(format);
2338 FileName const tmp_result_file(changeExtension(filename, ext));
2339 bool const success = theConverters().convert(this, FileName(filename),
2340 tmp_result_file, FileName(absFileName()), backend_format, format,
2341 errorList(error_type));
2342 // Emit the signal to show the error list.
2343 if (format != backend_format)
2349 result_file = tmp_result_file.absFilename();
2351 result_file = changeExtension(absFileName(), ext);
2352 // We need to copy referenced files (e. g. included graphics
2353 // if format == "dvi") to the result dir.
2354 vector<ExportedFile> const files =
2355 runparams.exportdata->externalFiles(format);
2356 string const dest = onlyPath(result_file);
2357 CopyStatus status = SUCCESS;
2358 for (vector<ExportedFile>::const_iterator it = files.begin();
2359 it != files.end() && status != CANCEL; ++it) {
2361 formats.getFormatFromFile(it->sourceName);
2362 status = copyFile(fmt, it->sourceName,
2363 makeAbsPath(it->exportName, dest),
2364 it->exportName, status == FORCE);
2366 if (status == CANCEL) {
2367 message(_("Document export cancelled."));
2368 } else if (tmp_result_file.exists()) {
2369 // Finally copy the main file
2370 status = copyFile(format, tmp_result_file,
2371 FileName(result_file), result_file,
2373 message(bformat(_("Document exported as %1$s "
2375 formats.prettyName(format),
2376 makeDisplayPath(result_file)));
2378 // This must be a dummy converter like fax (bug 1888)
2379 message(bformat(_("Document exported as %1$s"),
2380 formats.prettyName(format)));
2388 bool Buffer::doExport(string const & format, bool put_in_tempdir)
2391 return doExport(format, put_in_tempdir, result_file);
2395 bool Buffer::preview(string const & format)
2398 if (!doExport(format, true, result_file))
2400 return formats.view(*this, FileName(result_file), format);
2404 bool Buffer::isExportable(string const & format) const
2406 vector<string> backs = backends();
2407 for (vector<string>::const_iterator it = backs.begin();
2408 it != backs.end(); ++it)
2409 if (theConverters().isReachable(*it, format))
2415 vector<Format const *> Buffer::exportableFormats(bool only_viewable) const
2417 vector<string> backs = backends();
2418 vector<Format const *> result =
2419 theConverters().getReachable(backs[0], only_viewable, true);
2420 for (vector<string>::const_iterator it = backs.begin() + 1;
2421 it != backs.end(); ++it) {
2422 vector<Format const *> r =
2423 theConverters().getReachable(*it, only_viewable, false);
2424 result.insert(result.end(), r.begin(), r.end());
2430 vector<string> Buffer::backends() const
2433 if (params().getTextClass().isTeXClassAvailable()) {
2434 v.push_back(bufferFormat());
2435 // FIXME: Don't hardcode format names here, but use a flag
2436 if (v.back() == "latex")
2437 v.push_back("pdflatex");
2439 v.push_back("text");
2445 bool Buffer::readFileHelper(FileName const & s)
2447 // File information about normal file
2449 docstring const file = makeDisplayPath(s.absFilename(), 50);
2450 docstring text = bformat(_("The specified document\n%1$s"
2451 "\ncould not be read."), file);
2452 Alert::error(_("Could not read document"), text);
2456 // Check if emergency save file exists and is newer.
2457 FileName const e(s.absFilename() + ".emergency");
2459 if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) {
2460 docstring const file = makeDisplayPath(s.absFilename(), 20);
2461 docstring const text =
2462 bformat(_("An emergency save of the document "
2464 "Recover emergency save?"), file);
2465 switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
2466 _("&Recover"), _("&Load Original"),
2470 // the file is not saved if we load the emergency file.
2480 // Now check if autosave file is newer.
2481 FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#');
2483 if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) {
2484 docstring const file = makeDisplayPath(s.absFilename(), 20);
2485 docstring const text =
2486 bformat(_("The backup of the document "
2487 "%1$s is newer.\n\nLoad the "
2488 "backup instead?"), file);
2489 switch (Alert::prompt(_("Load backup?"), text, 0, 2,
2490 _("&Load backup"), _("Load &original"),
2494 // the file is not saved if we load the autosave file.
2498 // Here we delete the autosave
2509 bool Buffer::loadLyXFile(FileName const & s)
2511 if (s.isReadable()) {
2512 if (readFileHelper(s)) {
2513 lyxvc().file_found_hook(s);
2514 if (!s.isWritable())
2519 docstring const file = makeDisplayPath(s.absFilename(), 20);
2520 // Here we probably should run
2521 if (LyXVC::file_not_found_hook(s)) {
2522 docstring const text =
2523 bformat(_("Do you want to retrieve the document"
2524 " %1$s from version control?"), file);
2525 int const ret = Alert::prompt(_("Retrieve from version control?"),
2526 text, 0, 1, _("&Retrieve"), _("&Cancel"));
2529 // How can we know _how_ to do the checkout?
2530 // With the current VC support it has to be,
2531 // a RCS file since CVS do not have special ,v files.
2533 return loadLyXFile(s);
2541 void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
2543 TeXErrors::Errors::const_iterator cit = terr.begin();
2544 TeXErrors::Errors::const_iterator end = terr.end();
2546 for (; cit != end; ++cit) {
2549 int errorRow = cit->error_in_line;
2550 bool found = texrow().getIdFromRow(errorRow, id_start,
2556 found = texrow().getIdFromRow(errorRow, id_end, pos_end);
2557 } while (found && id_start == id_end && pos_start == pos_end);
2559 errorList.push_back(ErrorItem(cit->error_desc,
2560 cit->error_text, id_start, pos_start, pos_end));