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
8 * Full author contact details are available in file CREDITS.
16 #include "BiblioInfo.h"
17 #include "BranchList.h"
18 #include "buffer_funcs.h"
19 #include "BufferList.h"
20 #include "BufferParams.h"
23 #include "Converter.h"
26 #include "DocIterator.h"
27 #include "EmbeddedFiles.h"
29 #include "ErrorList.h"
32 #include "FuncRequest.h"
34 #include "InsetIterator.h"
35 #include "InsetList.h"
37 #include "LaTeXFeatures.h"
41 #include "LyXAction.h"
46 #include "output_docbook.h"
48 #include "output_latex.h"
49 #include "output_plaintext.h"
50 #include "paragraph_funcs.h"
51 #include "Paragraph.h"
52 #include "ParagraphParameters.h"
53 #include "ParIterator.h"
54 #include "PDFOptions.h"
58 #include "TexStream.h"
59 #include "TextClassList.h"
61 #include "TocBackend.h"
63 #include "VCBackend.h"
66 #include "insets/InsetBibitem.h"
67 #include "insets/InsetBibtex.h"
68 #include "insets/InsetInclude.h"
69 #include "insets/InsetText.h"
71 #include "mathed/MathMacroTemplate.h"
72 #include "mathed/MacroTable.h"
73 #include "mathed/MathSupport.h"
75 #include "frontends/alert.h"
76 #include "frontends/Delegates.h"
77 #include "frontends/WorkAreaManager.h"
78 #include "frontends/FileDialog.h"
80 #include "graphics/Previews.h"
82 #include "support/types.h"
83 #include "support/lyxalgo.h"
84 #include "support/FileFilterList.h"
85 #include "support/filetools.h"
86 #include "support/Forkedcall.h"
87 #include "support/fs_extras.h"
88 #include "support/gzstream.h"
89 #include "support/lyxlib.h"
90 #include "support/os.h"
91 #include "support/Path.h"
92 #include "support/textutils.h"
93 #include "support/convert.h"
95 #if !defined (HAVE_FORK)
99 #include <boost/bind.hpp>
100 #include <boost/filesystem/exception.hpp>
101 #include <boost/filesystem/operations.hpp>
102 #include <boost/shared_ptr.hpp>
112 using std::make_pair;
117 using std::ostringstream;
128 using support::addName;
129 using support::bformat;
130 using support::changeExtension;
131 using support::cmd_ret;
132 using support::createBufferTmpDir;
133 using support::FileName;
134 using support::libFileSearch;
135 using support::latex_path;
136 using support::ltrim;
137 using support::makeAbsPath;
138 using support::makeDisplayPath;
139 using support::makeLatexName;
140 using support::onlyFilename;
141 using support::onlyPath;
142 using support::quoteName;
143 using support::removeAutosaveFile;
144 using support::rename;
145 using support::runCommand;
146 using support::split;
147 using support::subst;
148 using support::tempName;
151 using support::suffixIs;
153 namespace Alert = frontend::Alert;
154 namespace os = support::os;
155 namespace fs = boost::filesystem;
159 int const LYX_FORMAT = 295; //Uwe: htmlurl, href
164 typedef std::map<string, bool> DepClean;
169 Impl(Buffer & parent, FileName const & file, bool readonly);
176 /// need to regenerate .tex?
180 mutable bool lyx_clean;
182 /// is autosave needed?
183 mutable bool bak_clean;
185 /// is this a unnamed file (New...)?
191 /// name of the file the buffer is associated with.
194 /** Set to true only when the file is fully loaded.
195 * Used to prevent the premature generation of previews
196 * and by the citation inset.
198 bool file_fully_loaded;
200 /// our Text that should be wrapped in an InsetText
207 TocBackend toc_backend;
209 /// Container for all sort of Buffer dependant errors.
210 map<string, ErrorList> errorLists;
212 /// all embedded files of this buffer
213 EmbeddedFiles embedded_files;
215 /// timestamp and checksum used to test if the file has been externally
216 /// modified. (Used to properly enable 'File->Revert to saved', bug 4114).
218 unsigned long checksum_;
221 frontend::WorkAreaManager * wa_;
228 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
229 : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_),
230 filename(file), file_fully_loaded(false), inset(params),
231 toc_backend(&parent), embedded_files(&parent), timestamp_(0),
232 checksum_(0), wa_(0), undo_(parent)
234 inset.setAutoBreakRows(true);
235 lyxvc.setBuffer(&parent);
236 temppath = createBufferTmpDir();
237 params.filepath = onlyPath(file.absFilename());
238 // FIXME: And now do something if temppath == string(), because we
239 // assume from now on that temppath points to a valid temp dir.
240 // See http://www.mail-archive.com/lyx-devel@lists.lyx.org/msg67406.html
243 wa_ = new frontend::WorkAreaManager;
247 Buffer::Buffer(string const & file, bool readonly)
248 : pimpl_(new Impl(*this, FileName(file), readonly)), gui_(0)
250 LYXERR(Debug::INFO) << "Buffer::Buffer()" << endl;
256 LYXERR(Debug::INFO) << "Buffer::~Buffer()" << endl;
257 // here the buffer should take care that it is
258 // saved properly, before it goes into the void.
260 Buffer * master = masterBuffer();
261 if (master != this && use_gui)
262 // We are closing buf which was a child document so we
263 // must update the labels and section numbering of its master
265 updateLabels(*master);
267 if (!temppath().empty() && !FileName(temppath()).destroyDirectory()) {
268 Alert::warning(_("Could not remove temporary directory"),
269 bformat(_("Could not remove the temporary directory %1$s"),
270 from_utf8(temppath())));
273 // Remove any previewed LaTeX snippets associated with this buffer.
274 graphics::Previews::get().removeLoader(*this);
277 pimpl_->wa_->closeAll();
284 void Buffer::changed() const
287 pimpl_->wa_->redrawAll();
291 frontend::WorkAreaManager & Buffer::workAreaManager() const
293 BOOST_ASSERT(pimpl_->wa_);
298 Text & Buffer::text() const
300 return const_cast<Text &>(pimpl_->inset.text_);
304 Inset & Buffer::inset() const
306 return const_cast<InsetText &>(pimpl_->inset);
310 BufferParams & Buffer::params()
312 return pimpl_->params;
316 BufferParams const & Buffer::params() const
318 return pimpl_->params;
322 ParagraphList & Buffer::paragraphs()
324 return text().paragraphs();
328 ParagraphList const & Buffer::paragraphs() const
330 return text().paragraphs();
334 LyXVC & Buffer::lyxvc()
336 return pimpl_->lyxvc;
340 LyXVC const & Buffer::lyxvc() const
342 return pimpl_->lyxvc;
346 string const & Buffer::temppath() const
348 return pimpl_->temppath;
352 TexRow & Buffer::texrow()
354 return pimpl_->texrow;
358 TexRow const & Buffer::texrow() const
360 return pimpl_->texrow;
364 TocBackend & Buffer::tocBackend()
366 return pimpl_->toc_backend;
370 TocBackend const & Buffer::tocBackend() const
372 return pimpl_->toc_backend;
376 EmbeddedFiles & Buffer::embeddedFiles()
378 return pimpl_->embedded_files;
382 EmbeddedFiles const & Buffer::embeddedFiles() const
384 return pimpl_->embedded_files;
388 Undo & Buffer::undo()
390 return pimpl_->undo_;
394 string Buffer::latexName(bool const no_path) const
396 string const name = changeExtension(makeLatexName(absFileName()), ".tex");
397 return no_path ? onlyFilename(name) : name;
401 pair<Buffer::LogType, string> Buffer::logName() const
403 string const filename = latexName(false);
405 if (filename.empty())
406 return make_pair(Buffer::latexlog, string());
408 string const path = temppath();
410 FileName const fname(addName(temppath(),
411 onlyFilename(changeExtension(filename,
413 FileName const bname(
414 addName(path, onlyFilename(
415 changeExtension(filename,
416 formats.extension("literate") + ".out"))));
418 // If no Latex log or Build log is newer, show Build log
420 if (bname.exists() &&
421 (!fname.exists() || fname.lastModified() < bname.lastModified())) {
422 LYXERR(Debug::FILES) << "Log name calculated as: " << bname << endl;
423 return make_pair(Buffer::buildlog, bname.absFilename());
425 LYXERR(Debug::FILES) << "Log name calculated as: " << fname << endl;
426 return make_pair(Buffer::latexlog, fname.absFilename());
430 void Buffer::setReadonly(bool const flag)
432 if (pimpl_->read_only != flag) {
433 pimpl_->read_only = flag;
439 void Buffer::setFileName(string const & newfile)
441 pimpl_->filename = makeAbsPath(newfile);
442 params().filepath = onlyPath(pimpl_->filename.absFilename());
443 setReadonly(pimpl_->filename.isReadOnly());
448 int Buffer::readHeader(Lexer & lex)
450 int unknown_tokens = 0;
452 int begin_header_line = -1;
454 // Initialize parameters that may be/go lacking in header:
455 params().branchlist().clear();
456 params().preamble.erase();
457 params().options.erase();
458 params().float_placement.erase();
459 params().paperwidth.erase();
460 params().paperheight.erase();
461 params().leftmargin.erase();
462 params().rightmargin.erase();
463 params().topmargin.erase();
464 params().bottommargin.erase();
465 params().headheight.erase();
466 params().headsep.erase();
467 params().footskip.erase();
468 params().listings_params.clear();
469 params().clearLayoutModules();
470 params().pdfoptions().clear();
472 for (int i = 0; i < 4; ++i) {
473 params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i];
474 params().temp_bullet(i) = ITEMIZE_DEFAULTS[i];
477 ErrorList & errorList = pimpl_->errorLists["Parse"];
481 string const token = lex.getString();
486 if (token == "\\end_header")
490 if (token == "\\begin_header") {
491 begin_header_line = line;
495 LYXERR(Debug::PARSER) << "Handling document header token: `"
496 << token << '\'' << endl;
498 string unknown = params().readToken(lex, token);
499 if (!unknown.empty()) {
500 if (unknown[0] != '\\' && token == "\\textclass") {
501 Alert::warning(_("Unknown document class"),
502 bformat(_("Using the default document class, because the "
503 "class %1$s is unknown."), from_utf8(unknown)));
506 docstring const s = bformat(_("Unknown token: "
510 errorList.push_back(ErrorItem(_("Document header error"),
515 if (begin_header_line) {
516 docstring const s = _("\\begin_header is missing");
517 errorList.push_back(ErrorItem(_("Document header error"),
521 return unknown_tokens;
526 // changed to be public and have one parameter
527 // Returns false if "\end_document" is not read (Asger)
528 bool Buffer::readDocument(Lexer & lex)
530 ErrorList & errorList = pimpl_->errorLists["Parse"];
534 string const token = lex.getString();
535 if (token != "\\begin_document") {
536 docstring const s = _("\\begin_document is missing");
537 errorList.push_back(ErrorItem(_("Document header error"),
541 // we are reading in a brand new document
542 BOOST_ASSERT(paragraphs().empty());
545 TextClass const & baseClass = textclasslist[params().getBaseClass()];
546 if (!baseClass.load(filePath())) {
547 string theclass = baseClass.name();
548 Alert::error(_("Can't load document class"), bformat(
549 _("Using the default document class, because the "
550 "class %1$s could not be loaded."), from_utf8(theclass)));
551 params().setBaseClass(defaultTextclass());
554 if (params().outputChanges) {
555 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
556 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
557 LaTeXFeatures::isAvailable("xcolor");
559 if (!dvipost && !xcolorsoul) {
560 Alert::warning(_("Changes not shown in LaTeX output"),
561 _("Changes will not be highlighted in LaTeX output, "
562 "because neither dvipost nor xcolor/soul are installed.\n"
563 "Please install these packages or redefine "
564 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
565 } else if (!xcolorsoul) {
566 Alert::warning(_("Changes not shown in LaTeX output"),
567 _("Changes will not be highlighted in LaTeX output "
568 "when using pdflatex, because xcolor and soul are not installed.\n"
569 "Please install both packages or redefine "
570 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
573 // read manifest after header
574 embeddedFiles().readManifest(lex, errorList);
577 bool const res = text().read(*this, lex, errorList);
578 for_each(text().paragraphs().begin(),
579 text().paragraphs().end(),
580 bind(&Paragraph::setInsetOwner, _1, &inset()));
586 // needed to insert the selection
587 void Buffer::insertStringAsLines(ParagraphList & pars,
588 pit_type & pit, pos_type & pos,
589 Font const & fn, docstring const & str, bool autobreakrows)
593 // insert the string, don't insert doublespace
594 bool space_inserted = true;
595 for (docstring::const_iterator cit = str.begin();
596 cit != str.end(); ++cit) {
597 Paragraph & par = pars[pit];
599 if (autobreakrows && (!par.empty() || par.allowEmpty())) {
600 breakParagraph(params(), pars, pit, pos,
601 par.layout()->isEnvironment());
604 space_inserted = true;
608 // do not insert consecutive spaces if !free_spacing
609 } else if ((*cit == ' ' || *cit == '\t') &&
610 space_inserted && !par.isFreeSpacing()) {
612 } else if (*cit == '\t') {
613 if (!par.isFreeSpacing()) {
614 // tabs are like spaces here
615 par.insertChar(pos, ' ', font, params().trackChanges);
617 space_inserted = true;
619 const pos_type n = 8 - pos % 8;
620 for (pos_type i = 0; i < n; ++i) {
621 par.insertChar(pos, ' ', font, params().trackChanges);
624 space_inserted = true;
626 } else if (!isPrintable(*cit)) {
627 // Ignore unprintables
630 // just insert the character
631 par.insertChar(pos, *cit, font, params().trackChanges);
633 space_inserted = (*cit == ' ');
640 bool Buffer::readString(std::string const & s)
642 params().compressed = false;
644 // remove dummy empty par
645 paragraphs().clear();
647 std::istringstream is(s);
649 FileName const name(tempName());
650 switch (readFile(lex, name, true)) {
654 // We need to call lyx2lyx, so write the input to a file
655 std::ofstream os(name.toFilesystemEncoding().c_str());
658 return readFile(name);
668 bool Buffer::readFile(FileName const & filename)
670 FileName fname(filename);
671 // Check if the file is compressed.
672 string format = filename.guessFormatFromContents();
673 if (format == "zip") {
674 // decompress to a temp directory
675 LYXERR(Debug::FILES) << filename << " is in zip format. Unzip to " << temppath() << endl;
676 ::unzipToDir(filename.toFilesystemEncoding(), temppath());
678 FileName lyxfile(addName(temppath(), "content.lyx"));
679 // if both manifest.txt and file.lyx exist, this is am embedded file
680 if (lyxfile.exists()) {
681 params().embedded = true;
685 // The embedded lyx file can also be compressed, for backward compatibility
686 format = fname.guessFormatFromContents();
687 if (format == "gzip" || format == "zip" || format == "compress")
688 params().compressed = true;
690 // remove dummy empty par
691 paragraphs().clear();
694 if (readFile(lex, fname) != success)
701 bool Buffer::isFullyLoaded() const
703 return pimpl_->file_fully_loaded;
707 void Buffer::setFullyLoaded(bool value)
709 pimpl_->file_fully_loaded = value;
713 Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
716 BOOST_ASSERT(!filename.empty());
719 Alert::error(_("Document could not be read"),
720 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
725 string const token = lex.getString();
728 Alert::error(_("Document could not be read"),
729 bformat(_("%1$s could not be read."), from_utf8(filename.absFilename())));
733 // the first token _must_ be...
734 if (token != "\\lyxformat") {
735 lyxerr << "Token: " << token << endl;
737 Alert::error(_("Document format failure"),
738 bformat(_("%1$s is not a LyX document."),
739 from_utf8(filename.absFilename())));
744 string tmp_format = lex.getString();
745 //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl;
746 // if present remove ".," from string.
747 string::size_type dot = tmp_format.find_first_of(".,");
748 //lyxerr << " dot found at " << dot << endl;
749 if (dot != string::npos)
750 tmp_format.erase(dot, 1);
751 int const file_format = convert<int>(tmp_format);
752 //lyxerr << "format: " << file_format << endl;
754 // save timestamp and checksum of the original disk file, making sure
755 // to not overwrite them with those of the file created in the tempdir
756 // when it has to be converted to the current format.
757 if (!pimpl_->checksum_) {
758 // Save the timestamp and checksum of disk file. If filename is an
759 // emergency file, save the timestamp and sum of the original lyx file
760 // because isExternallyModified will check for this file. (BUG4193)
761 string diskfile = filename.toFilesystemEncoding();
762 if (suffixIs(diskfile, ".emergency"))
763 diskfile = diskfile.substr(0, diskfile.size() - 10);
764 saveCheckSum(FileName(diskfile));
767 if (file_format != LYX_FORMAT) {
770 // lyx2lyx would fail
773 FileName const tmpfile(tempName());
774 if (tmpfile.empty()) {
775 Alert::error(_("Conversion failed"),
776 bformat(_("%1$s is from a different"
777 " version of LyX, but a temporary"
778 " file for converting it could"
780 from_utf8(filename.absFilename())));
783 FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx");
784 if (lyx2lyx.empty()) {
785 Alert::error(_("Conversion script not found"),
786 bformat(_("%1$s is from a different"
787 " version of LyX, but the"
788 " conversion script lyx2lyx"
789 " could not be found."),
790 from_utf8(filename.absFilename())));
793 ostringstream command;
794 command << os::python()
795 << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
796 << " -t " << convert<string>(LYX_FORMAT)
797 << " -o " << quoteName(tmpfile.toFilesystemEncoding())
798 << ' ' << quoteName(filename.toFilesystemEncoding());
799 string const command_str = command.str();
801 LYXERR(Debug::INFO) << "Running '"
802 << command_str << '\''
805 cmd_ret const ret = runCommand(command_str);
806 if (ret.first != 0) {
807 Alert::error(_("Conversion script failed"),
808 bformat(_("%1$s is from a different version"
809 " of LyX, but the lyx2lyx script"
810 " failed to convert it."),
811 from_utf8(filename.absFilename())));
814 bool const ret = readFile(tmpfile);
815 // Do stuff with tmpfile name and buffer name here.
816 return ret ? success : failure;
821 if (readDocument(lex)) {
822 Alert::error(_("Document format failure"),
823 bformat(_("%1$s ended unexpectedly, which means"
824 " that it is probably corrupted."),
825 from_utf8(filename.absFilename())));
828 //lyxerr << "removing " << MacroTable::localMacros().size()
829 // << " temporary macro entries" << endl;
830 //MacroTable::localMacros().clear();
832 pimpl_->file_fully_loaded = true;
837 // Should probably be moved to somewhere else: BufferView? LyXView?
838 bool Buffer::save() const
840 // We don't need autosaves in the immediate future. (Asger)
841 resetAutosaveTimers();
843 string const encodedFilename = pimpl_->filename.toFilesystemEncoding();
846 bool madeBackup = false;
848 // make a backup if the file already exists
849 if (lyxrc.make_backup && fs::exists(encodedFilename)) {
850 backupName = FileName(absFileName() + '~');
851 if (!lyxrc.backupdir_path.empty()) {
852 string const mangledName =
853 subst(subst(os::internal_path(
854 backupName.absFilename()), '/', '!'), ':', '!');
855 backupName = FileName(addName(lyxrc.backupdir_path,
859 fs::copy_file(encodedFilename, backupName.toFilesystemEncoding(), false);
861 } catch (fs::filesystem_error const & fe) {
862 Alert::error(_("Backup failure"),
863 bformat(_("Cannot create backup file %1$s.\n"
864 "Please check whether the directory exists and is writeable."),
865 from_utf8(backupName.absFilename())));
866 LYXERR(Debug::DEBUG) << "Fs error: " << fe.what() << endl;
870 // ask if the disk file has been externally modified (use checksum method)
871 if (fs::exists(encodedFilename) && isExternallyModified(checksum_method)) {
872 docstring const file = makeDisplayPath(absFileName(), 20);
873 docstring text = bformat(_("Document %1$s has been externally modified. Are you sure "
874 "you want to overwrite this file?"), file);
875 int const ret = Alert::prompt(_("Overwrite modified file?"),
876 text, 1, 1, _("&Overwrite"), _("&Cancel"));
881 if (writeFile(pimpl_->filename)) {
883 removeAutosaveFile(absFileName());
884 saveCheckSum(pimpl_->filename);
887 // Saving failed, so backup is not backup
889 rename(backupName, pimpl_->filename);
895 bool Buffer::writeFile(FileName const & fname) const
897 if (pimpl_->read_only && fname == pimpl_->filename)
903 if (params().embedded)
904 // first write the .lyx file to the temporary directory
905 content = FileName(addName(temppath(), "content.lyx"));
909 if (params().compressed) {
910 gz::ogzstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
916 ofstream ofs(content.toFilesystemEncoding().c_str(), ios::out|ios::trunc);
923 if (retval && params().embedded) {
924 // write file.lyx and all the embedded files to the zip file fname
925 // if embedding is enabled
926 return pimpl_->embedded_files.writeFile(fname);
932 bool Buffer::write(ostream & ofs) const
935 // Use the standard "C" locale for file output.
936 ofs.imbue(std::locale::classic());
939 // The top of the file should not be written by params().
941 // write out a comment in the top of the file
942 ofs << "#LyX " << lyx_version
943 << " created this file. For more info see http://www.lyx.org/\n"
944 << "\\lyxformat " << LYX_FORMAT << "\n"
945 << "\\begin_document\n";
948 /// For each author, set 'used' to true if there is a change
949 /// by this author in the document; otherwise set it to 'false'.
950 AuthorList::Authors::const_iterator a_it = params().authors().begin();
951 AuthorList::Authors::const_iterator a_end = params().authors().end();
952 for (; a_it != a_end; ++a_it)
953 a_it->second.used(false);
955 ParIterator const end = par_iterator_end();
956 ParIterator it = par_iterator_begin();
957 for ( ; it != end; ++it)
958 it->checkAuthors(params().authors());
960 // now write out the buffer parameters.
961 ofs << "\\begin_header\n";
962 params().writeFile(ofs);
963 ofs << "\\end_header\n";
965 // write the manifest after header
966 ofs << "\n\\begin_manifest\n";
967 pimpl_->embedded_files.update();
968 embeddedFiles().writeManifest(ofs);
969 ofs << "\\end_manifest\n";
972 ofs << "\n\\begin_body\n";
973 text().write(*this, ofs);
974 ofs << "\n\\end_body\n";
976 // Write marker that shows file is complete
977 ofs << "\\end_document" << endl;
979 // Shouldn't really be needed....
982 // how to check if close went ok?
983 // Following is an attempt... (BE 20001011)
985 // good() returns false if any error occured, including some
987 // bad() returns true if something bad happened in the buffer,
988 // which should include file system full errors.
993 lyxerr << "File was not closed properly." << endl;
1000 bool Buffer::makeLaTeXFile(FileName const & fname,
1001 string const & original_path,
1002 OutputParams const & runparams,
1003 bool output_preamble, bool output_body)
1005 string const encoding = runparams.encoding->iconvName();
1006 LYXERR(Debug::LATEX) << "makeLaTeXFile encoding: "
1007 << encoding << "..." << endl;
1009 odocfstream ofs(encoding);
1010 if (!openFileWrite(ofs, fname))
1013 //TexStream ts(ofs.rdbuf(), &texrow());
1015 bool failed_export = false;
1018 writeLaTeXSource(ofs, original_path,
1019 runparams, output_preamble, output_body);
1021 catch (iconv_codecvt_facet_exception & e) {
1022 lyxerr << "Caught iconv exception: " << e.what() << endl;
1023 failed_export = true;
1025 catch (std::exception const & e) {
1026 lyxerr << "Caught \"normal\" exception: " << e.what() << endl;
1027 failed_export = true;
1030 lyxerr << "Caught some really weird exception..." << endl;
1031 LyX::cref().emergencyCleanup();
1037 failed_export = true;
1038 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1041 if (failed_export) {
1042 Alert::error(_("Encoding error"),
1043 _("Some characters of your document are probably not "
1044 "representable in the chosen encoding.\n"
1045 "Changing the document encoding to utf8 could help."));
1052 void Buffer::writeLaTeXSource(odocstream & os,
1053 string const & original_path,
1054 OutputParams const & runparams_in,
1055 bool const output_preamble, bool const output_body)
1057 OutputParams runparams = runparams_in;
1059 // validate the buffer.
1060 LYXERR(Debug::LATEX) << " Validating buffer..." << endl;
1061 LaTeXFeatures features(*this, params(), runparams);
1063 LYXERR(Debug::LATEX) << " Buffer validation done." << endl;
1065 // The starting paragraph of the coming rows is the
1066 // first paragraph of the document. (Asger)
1067 if (output_preamble && runparams.nice) {
1068 os << "%% LyX " << lyx_version << " created this file. "
1069 "For more info, see http://www.lyx.org/.\n"
1070 "%% Do not edit unless you really know what "
1075 LYXERR(Debug::INFO) << "lyx document header finished" << endl;
1076 // There are a few differences between nice LaTeX and usual files:
1077 // usual is \batchmode and has a
1078 // special input@path to allow the including of figures
1079 // with either \input or \includegraphics (what figinsets do).
1080 // input@path is set when the actual parameter
1081 // original_path is set. This is done for usual tex-file, but not
1082 // for nice-latex-file. (Matthias 250696)
1083 // Note that input@path is only needed for something the user does
1084 // in the preamble, included .tex files or ERT, files included by
1085 // LyX work without it.
1086 if (output_preamble) {
1087 if (!runparams.nice) {
1088 // code for usual, NOT nice-latex-file
1089 os << "\\batchmode\n"; // changed
1090 // from \nonstopmode
1093 if (!original_path.empty()) {
1095 // We don't know the encoding of inputpath
1096 docstring const inputpath = from_utf8(latex_path(original_path));
1097 os << "\\makeatletter\n"
1098 << "\\def\\input@path{{"
1099 << inputpath << "/}}\n"
1100 << "\\makeatother\n";
1106 // Write the preamble
1107 runparams.use_babel = params().writeLaTeX(os, features, texrow());
1113 os << "\\begin{document}\n";
1115 } // output_preamble
1117 texrow().start(paragraphs().begin()->id(), 0);
1119 LYXERR(Debug::INFO) << "preamble finished, now the body." << endl;
1121 if (!lyxrc.language_auto_begin &&
1122 !params().language->babel().empty()) {
1124 os << from_utf8(subst(lyxrc.language_command_begin,
1126 params().language->babel()))
1131 Encoding const & encoding = params().encoding();
1132 if (encoding.package() == Encoding::CJK) {
1133 // Open a CJK environment, since in contrast to the encodings
1134 // handled by inputenc the document encoding is not set in
1135 // the preamble if it is handled by CJK.sty.
1136 os << "\\begin{CJK}{" << from_ascii(encoding.latexName())
1141 // if we are doing a real file with body, even if this is the
1142 // child of some other buffer, let's cut the link here.
1143 // This happens for example if only a child document is printed.
1144 string save_parentname;
1145 if (output_preamble) {
1146 save_parentname = params().parentname;
1147 params().parentname.erase();
1150 loadChildDocuments();
1153 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1155 // Restore the parenthood if needed
1156 if (output_preamble)
1157 params().parentname = save_parentname;
1159 // add this just in case after all the paragraphs
1163 if (encoding.package() == Encoding::CJK) {
1164 // Close the open CJK environment.
1165 // latexParagraphs will have opened one even if the last text
1167 os << "\\end{CJK}\n";
1171 if (!lyxrc.language_auto_end &&
1172 !params().language->babel().empty()) {
1173 os << from_utf8(subst(lyxrc.language_command_end,
1175 params().language->babel()))
1180 if (output_preamble) {
1181 os << "\\end{document}\n";
1184 LYXERR(Debug::LATEX) << "makeLaTeXFile...done" << endl;
1186 LYXERR(Debug::LATEX) << "LaTeXFile for inclusion made."
1189 runparams_in.encoding = runparams.encoding;
1191 // Just to be sure. (Asger)
1194 LYXERR(Debug::INFO) << "Finished making LaTeX file." << endl;
1195 LYXERR(Debug::INFO) << "Row count was " << texrow().rows() - 1
1200 bool Buffer::isLatex() const
1202 return params().getTextClass().outputType() == LATEX;
1206 bool Buffer::isLiterate() const
1208 return params().getTextClass().outputType() == LITERATE;
1212 bool Buffer::isDocBook() const
1214 return params().getTextClass().outputType() == DOCBOOK;
1218 void Buffer::makeDocBookFile(FileName const & fname,
1219 OutputParams const & runparams,
1220 bool const body_only)
1222 LYXERR(Debug::LATEX) << "makeDocBookFile..." << endl;
1226 if (!openFileWrite(ofs, fname))
1229 writeDocBookSource(ofs, fname.absFilename(), runparams, body_only);
1233 lyxerr << "File '" << fname << "' was not closed properly." << endl;
1237 void Buffer::writeDocBookSource(odocstream & os, string const & fname,
1238 OutputParams const & runparams,
1239 bool const only_body)
1241 LaTeXFeatures features(*this, params(), runparams);
1246 TextClass const & tclass = params().getTextClass();
1247 string const top_element = tclass.latexname();
1250 if (runparams.flavor == OutputParams::XML)
1251 os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1254 os << "<!DOCTYPE " << from_ascii(top_element) << ' ';
1257 if (! tclass.class_header().empty())
1258 os << from_ascii(tclass.class_header());
1259 else if (runparams.flavor == OutputParams::XML)
1260 os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" "
1261 << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\"";
1263 os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\"";
1265 docstring preamble = from_utf8(params().preamble);
1266 if (runparams.flavor != OutputParams::XML ) {
1267 preamble += "<!ENTITY % output.print.png \"IGNORE\">\n";
1268 preamble += "<!ENTITY % output.print.pdf \"IGNORE\">\n";
1269 preamble += "<!ENTITY % output.print.eps \"IGNORE\">\n";
1270 preamble += "<!ENTITY % output.print.bmp \"IGNORE\">\n";
1273 string const name = runparams.nice
1274 ? changeExtension(absFileName(), ".sgml") : fname;
1275 preamble += features.getIncludedFiles(name);
1276 preamble += features.getLyXSGMLEntities();
1278 if (!preamble.empty()) {
1279 os << "\n [ " << preamble << " ]";
1284 string top = top_element;
1286 if (runparams.flavor == OutputParams::XML)
1287 top += params().language->code();
1289 top += params().language->code().substr(0,2);
1292 if (!params().options.empty()) {
1294 top += params().options;
1297 os << "<!-- " << ((runparams.flavor == OutputParams::XML)? "XML" : "SGML")
1298 << " file was created by LyX " << lyx_version
1299 << "\n See http://www.lyx.org/ for more information -->\n";
1301 params().getTextClass().counters().reset();
1303 loadChildDocuments();
1305 sgml::openTag(os, top);
1307 docbookParagraphs(paragraphs(), *this, os, runparams);
1308 sgml::closeTag(os, top_element);
1312 // chktex should be run with these flags disabled: 3, 22, 25, 30, 38(?)
1313 // Other flags: -wall -v0 -x
1314 int Buffer::runChktex()
1318 // get LaTeX-Filename
1319 FileName const path(temppath());
1320 string const name = addName(path.absFilename(), latexName());
1321 string const org_path = filePath();
1323 support::Path p(path); // path to LaTeX file
1324 message(_("Running chktex..."));
1326 // Generate the LaTeX file if neccessary
1327 OutputParams runparams(¶ms().encoding());
1328 runparams.flavor = OutputParams::LATEX;
1329 runparams.nice = false;
1330 makeLaTeXFile(FileName(name), org_path, runparams);
1333 Chktex chktex(lyxrc.chktex_command, onlyFilename(name), filePath());
1334 int const res = chktex.run(terr); // run chktex
1337 Alert::error(_("chktex failure"),
1338 _("Could not run chktex successfully."));
1339 } else if (res > 0) {
1340 ErrorList & errlist = pimpl_->errorLists["ChkTeX"];
1342 bufferErrors(terr, errlist);
1353 void Buffer::validate(LaTeXFeatures & features) const
1355 TextClass const & tclass = params().getTextClass();
1357 if (params().outputChanges) {
1358 bool dvipost = LaTeXFeatures::isAvailable("dvipost");
1359 bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
1360 LaTeXFeatures::isAvailable("xcolor");
1362 if (features.runparams().flavor == OutputParams::LATEX) {
1364 features.require("ct-dvipost");
1365 features.require("dvipost");
1366 } else if (xcolorsoul) {
1367 features.require("ct-xcolor-soul");
1368 features.require("soul");
1369 features.require("xcolor");
1371 features.require("ct-none");
1373 } else if (features.runparams().flavor == OutputParams::PDFLATEX ) {
1375 features.require("ct-xcolor-soul");
1376 features.require("soul");
1377 features.require("xcolor");
1378 features.require("pdfcolmk"); // improves color handling in PDF output
1380 features.require("ct-none");
1385 // AMS Style is at document level
1386 if (params().use_amsmath == BufferParams::package_on
1387 || tclass.provides("amsmath"))
1388 features.require("amsmath");
1389 if (params().use_esint == BufferParams::package_on)
1390 features.require("esint");
1392 loadChildDocuments();
1394 for_each(paragraphs().begin(), paragraphs().end(),
1395 boost::bind(&Paragraph::validate, _1, boost::ref(features)));
1397 // the bullet shapes are buffer level not paragraph level
1398 // so they are tested here
1399 for (int i = 0; i < 4; ++i) {
1400 if (params().user_defined_bullet(i) != ITEMIZE_DEFAULTS[i]) {
1401 int const font = params().user_defined_bullet(i).getFont();
1403 int const c = params()
1404 .user_defined_bullet(i)
1411 features.require("latexsym");
1413 } else if (font == 1) {
1414 features.require("amssymb");
1415 } else if ((font >= 2 && font <= 5)) {
1416 features.require("pifont");
1421 if (lyxerr.debugging(Debug::LATEX)) {
1422 features.showStruct();
1427 void Buffer::getLabelList(vector<docstring> & list) const
1429 /// if this is a child document and the parent is already loaded
1430 /// Use the parent's list instead [ale990407]
1431 Buffer const * tmp = masterBuffer();
1433 lyxerr << "masterBuffer() failed!" << endl;
1437 tmp->getLabelList(list);
1441 loadChildDocuments();
1443 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it)
1444 it.nextInset()->getLabelList(*this, list);
1448 void Buffer::updateBibfilesCache()
1450 // if this is a child document and the parent is already loaded
1451 // update the parent's cache instead
1452 Buffer * tmp = masterBuffer();
1455 tmp->updateBibfilesCache();
1459 bibfilesCache_.clear();
1460 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1461 if (it->lyxCode() == BIBTEX_CODE) {
1462 InsetBibtex const & inset =
1463 static_cast<InsetBibtex const &>(*it);
1464 vector<FileName> const bibfiles = inset.getFiles(*this);
1465 bibfilesCache_.insert(bibfilesCache_.end(),
1468 } else if (it->lyxCode() == INCLUDE_CODE) {
1469 InsetInclude & inset =
1470 static_cast<InsetInclude &>(*it);
1471 inset.updateBibfilesCache(*this);
1472 vector<FileName> const & bibfiles =
1473 inset.getBibfilesCache(*this);
1474 bibfilesCache_.insert(bibfilesCache_.end(),
1482 vector<FileName> const & Buffer::getBibfilesCache() const
1484 // if this is a child document and the parent is already loaded
1485 // use the parent's cache instead
1486 Buffer const * tmp = masterBuffer();
1489 return tmp->getBibfilesCache();
1491 // We update the cache when first used instead of at loading time.
1492 if (bibfilesCache_.empty())
1493 const_cast<Buffer *>(this)->updateBibfilesCache();
1495 return bibfilesCache_;
1499 bool Buffer::isDepClean(string const & name) const
1501 DepClean::const_iterator const it = pimpl_->dep_clean.find(name);
1502 if (it == pimpl_->dep_clean.end())
1508 void Buffer::markDepClean(string const & name)
1510 pimpl_->dep_clean[name] = true;
1514 bool Buffer::dispatch(string const & command, bool * result)
1516 return dispatch(lyxaction.lookupFunc(command), result);
1520 bool Buffer::dispatch(FuncRequest const & func, bool * result)
1522 bool dispatched = true;
1524 switch (func.action) {
1525 case LFUN_BUFFER_EXPORT: {
1526 bool const tmp = doExport(to_utf8(func.argument()), false);
1539 void Buffer::changeLanguage(Language const * from, Language const * to)
1544 for_each(par_iterator_begin(),
1546 bind(&Paragraph::changeLanguage, _1, params(), from, to));
1550 bool Buffer::isMultiLingual() const
1552 ParConstIterator end = par_iterator_end();
1553 for (ParConstIterator it = par_iterator_begin(); it != end; ++it)
1554 if (it->isMultiLingual(params()))
1561 ParIterator Buffer::getParFromID(int const id) const
1563 ParConstIterator it = par_iterator_begin();
1564 ParConstIterator const end = par_iterator_end();
1567 // John says this is called with id == -1 from undo
1568 lyxerr << "getParFromID(), id: " << id << endl;
1572 for (; it != end; ++it)
1580 bool Buffer::hasParWithID(int const id) const
1582 ParConstIterator const it = getParFromID(id);
1583 return it != par_iterator_end();
1587 ParIterator Buffer::par_iterator_begin()
1589 return lyx::par_iterator_begin(inset());
1593 ParIterator Buffer::par_iterator_end()
1595 return lyx::par_iterator_end(inset());
1599 ParConstIterator Buffer::par_iterator_begin() const
1601 return lyx::par_const_iterator_begin(inset());
1605 ParConstIterator Buffer::par_iterator_end() const
1607 return lyx::par_const_iterator_end(inset());
1611 Language const * Buffer::language() const
1613 return params().language;
1617 docstring const Buffer::B_(string const & l10n) const
1619 return params().B_(l10n);
1623 bool Buffer::isClean() const
1625 return pimpl_->lyx_clean;
1629 bool Buffer::isBakClean() const
1631 return pimpl_->bak_clean;
1635 bool Buffer::isExternallyModified(CheckMethod method) const
1637 BOOST_ASSERT(pimpl_->filename.exists());
1638 // if method == timestamp, check timestamp before checksum
1639 return (method == checksum_method
1640 || pimpl_->timestamp_ != pimpl_->filename.lastModified())
1641 && pimpl_->checksum_ != sum(pimpl_->filename);
1645 void Buffer::saveCheckSum(FileName const & file) const
1647 if (file.exists()) {
1648 pimpl_->timestamp_ = file.lastModified();
1649 pimpl_->checksum_ = sum(file);
1651 // in the case of save to a new file.
1652 pimpl_->timestamp_ = 0;
1653 pimpl_->checksum_ = 0;
1658 void Buffer::markClean() const
1660 if (!pimpl_->lyx_clean) {
1661 pimpl_->lyx_clean = true;
1664 // if the .lyx file has been saved, we don't need an
1666 pimpl_->bak_clean = true;
1670 void Buffer::markBakClean() const
1672 pimpl_->bak_clean = true;
1676 void Buffer::setUnnamed(bool flag)
1678 pimpl_->unnamed = flag;
1682 bool Buffer::isUnnamed() const
1684 return pimpl_->unnamed;
1688 // FIXME: this function should be moved to buffer_pimpl.C
1689 void Buffer::markDirty()
1691 if (pimpl_->lyx_clean) {
1692 pimpl_->lyx_clean = false;
1695 pimpl_->bak_clean = false;
1697 DepClean::iterator it = pimpl_->dep_clean.begin();
1698 DepClean::const_iterator const end = pimpl_->dep_clean.end();
1700 for (; it != end; ++it)
1705 string Buffer::absFileName() const
1707 return pimpl_->filename.absFilename();
1711 string const & Buffer::filePath() const
1713 return params().filepath;
1717 bool Buffer::isReadonly() const
1719 return pimpl_->read_only;
1723 void Buffer::setParentName(string const & name)
1725 if (name == pimpl_->filename.absFilename())
1726 // Avoids recursive include.
1727 params().parentname.clear();
1729 params().parentname = name;
1733 Buffer const * Buffer::masterBuffer() const
1735 if (!params().parentname.empty()
1736 && theBufferList().exists(params().parentname)) {
1737 Buffer const * buf = theBufferList().getBuffer(params().parentname);
1738 //We need to check if the parent is us...
1739 //FIXME RECURSIVE INCLUDE
1740 //This is not sufficient, since recursive includes could be downstream.
1741 if (buf && buf != this)
1742 return buf->masterBuffer();
1749 Buffer * Buffer::masterBuffer()
1751 if (!params().parentname.empty()
1752 && theBufferList().exists(params().parentname)) {
1753 Buffer * buf = theBufferList().getBuffer(params().parentname);
1754 //We need to check if the parent is us...
1755 //FIXME RECURSIVE INCLUDE
1756 //This is not sufficient, since recursive includes could be downstream.
1757 if (buf && buf != this)
1758 return buf->masterBuffer();
1765 MacroData const & Buffer::getMacro(docstring const & name) const
1767 return pimpl_->macros.get(name);
1771 bool Buffer::hasMacro(docstring const & name) const
1773 return pimpl_->macros.has(name);
1777 void Buffer::insertMacro(docstring const & name, MacroData const & data)
1779 MacroTable::globalMacros().insert(name, data);
1780 pimpl_->macros.insert(name, data);
1784 void Buffer::buildMacros()
1786 // Start with global table.
1787 pimpl_->macros = MacroTable::globalMacros();
1790 ParagraphList const & pars = text().paragraphs();
1791 for (size_t i = 0, n = pars.size(); i != n; ++i) {
1792 //lyxerr << "searching main par " << i
1793 // << " for macro definitions" << std::endl;
1794 InsetList const & insets = pars[i].insetList();
1795 InsetList::const_iterator it = insets.begin();
1796 InsetList::const_iterator end = insets.end();
1797 for ( ; it != end; ++it) {
1798 //lyxerr << "found inset code " << it->inset->lyxCode() << std::endl;
1799 if (it->inset->lyxCode() == MATHMACRO_CODE) {
1800 MathMacroTemplate const & mac
1801 = static_cast<MathMacroTemplate const &>(*it->inset);
1802 insertMacro(mac.name(), mac.asMacroData());
1809 void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to,
1812 //FIXME: This does not work for child documents yet.
1813 BOOST_ASSERT(code == CITE_CODE || code == REF_CODE);
1814 // Check if the label 'from' appears more than once
1815 vector<docstring> labels;
1817 if (code == CITE_CODE) {
1819 keys.fillWithBibKeys(this);
1820 BiblioInfo::const_iterator bit = keys.begin();
1821 BiblioInfo::const_iterator bend = keys.end();
1823 for (; bit != bend; ++bit)
1825 labels.push_back(bit->first);
1827 getLabelList(labels);
1829 if (std::count(labels.begin(), labels.end(), from) > 1)
1832 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
1833 if (it->lyxCode() == code) {
1834 InsetCommand & inset = static_cast<InsetCommand &>(*it);
1835 inset.replaceContents(to_utf8(from), to_utf8(to));
1841 void Buffer::getSourceCode(odocstream & os, pit_type par_begin,
1842 pit_type par_end, bool full_source)
1844 OutputParams runparams(¶ms().encoding());
1845 runparams.nice = true;
1846 runparams.flavor = OutputParams::LATEX;
1847 runparams.linelen = lyxrc.plaintext_linelen;
1848 // No side effect of file copying and image conversion
1849 runparams.dryrun = true;
1853 os << "% " << _("Preview source code") << "\n\n";
1857 writeLaTeXSource(os, filePath(), runparams, true, true);
1859 writeDocBookSource(os, absFileName(), runparams, false);
1862 runparams.par_begin = par_begin;
1863 runparams.par_end = par_end;
1864 if (par_begin + 1 == par_end)
1866 << bformat(_("Preview source code for paragraph %1$d"), par_begin)
1870 << bformat(_("Preview source code from paragraph %1$s to %2$s"),
1871 convert<docstring>(par_begin),
1872 convert<docstring>(par_end - 1))
1876 // output paragraphs
1878 latexParagraphs(*this, paragraphs(), os, texrow(), runparams);
1881 docbookParagraphs(paragraphs(), *this, os, runparams);
1887 ErrorList const & Buffer::errorList(string const & type) const
1889 static ErrorList const emptyErrorList;
1890 std::map<string, ErrorList>::const_iterator I = pimpl_->errorLists.find(type);
1891 if (I == pimpl_->errorLists.end())
1892 return emptyErrorList;
1898 ErrorList & Buffer::errorList(string const & type)
1900 return pimpl_->errorLists[type];
1904 void Buffer::structureChanged() const
1907 gui_->structureChanged();
1911 void Buffer::embeddingChanged() const
1914 gui_->embeddingChanged();
1918 void Buffer::errors(std::string const & err) const
1925 void Buffer::message(docstring const & msg) const
1932 void Buffer::setBusy(bool on) const
1939 void Buffer::readonly(bool on) const
1946 void Buffer::updateTitles() const
1949 gui_->updateTitles();
1953 void Buffer::resetAutosaveTimers() const
1956 gui_->resetAutosaveTimers();
1960 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
1969 class AutoSaveBuffer : public support::ForkedProcess {
1972 AutoSaveBuffer(Buffer const & buffer, FileName const & fname)
1973 : buffer_(buffer), fname_(fname) {}
1975 virtual boost::shared_ptr<ForkedProcess> clone() const
1977 return boost::shared_ptr<ForkedProcess>(new AutoSaveBuffer(*this));
1982 command_ = to_utf8(bformat(_("Auto-saving %1$s"),
1983 from_utf8(fname_.absFilename())));
1984 return run(DontWait);
1988 virtual int generateChild();
1990 Buffer const & buffer_;
1995 #if !defined (HAVE_FORK)
1999 int AutoSaveBuffer::generateChild()
2001 // tmp_ret will be located (usually) in /tmp
2002 // will that be a problem?
2003 pid_t const pid = fork();
2004 // If you want to debug the autosave
2005 // you should set pid to -1, and comment out the fork.
2006 if (pid == 0 || pid == -1) {
2007 // pid = -1 signifies that lyx was unable
2008 // to fork. But we will do the save
2010 bool failed = false;
2012 FileName const tmp_ret(tempName(FileName(), "lyxauto"));
2013 if (!tmp_ret.empty()) {
2014 buffer_.writeFile(tmp_ret);
2015 // assume successful write of tmp_ret
2016 if (!rename(tmp_ret, fname_)) {
2018 // most likely couldn't move between
2019 // filesystems unless write of tmp_ret
2020 // failed so remove tmp file (if it
2029 // failed to write/rename tmp_ret so try writing direct
2030 if (!buffer_.writeFile(fname_)) {
2031 // It is dangerous to do this in the child,
2032 // but safe in the parent, so...
2033 if (pid == -1) // emit message signal.
2034 buffer_.message(_("Autosave failed!"));
2037 if (pid == 0) { // we are the child so...
2047 // Perfect target for a thread...
2048 void Buffer::autoSave() const
2050 if (isBakClean() || isReadonly()) {
2051 // We don't save now, but we'll try again later
2052 resetAutosaveTimers();
2056 // emit message signal.
2057 message(_("Autosaving current document..."));
2059 // create autosave filename
2060 string fname = filePath();
2062 fname += onlyFilename(absFileName());
2065 AutoSaveBuffer autosave(*this, FileName(fname));
2069 resetAutosaveTimers();
2073 /** Write a buffer to a new file name and rename the buffer
2074 according to the new file name.
2076 This function is e.g. used by menu callbacks and
2077 LFUN_BUFFER_WRITE_AS.
2079 If 'newname' is empty (the default), the user is asked via a
2080 dialog for the buffer's new name and location.
2082 If 'newname' is non-empty and has an absolute path, that is used.
2083 Otherwise the base directory of the buffer is used as the base
2084 for any relative path in 'newname'.
2087 bool Buffer::writeAs(string const & newname)
2089 string fname = absFileName();
2090 string const oldname = fname;
2092 if (newname.empty()) { /// No argument? Ask user through dialog
2095 FileDialog fileDlg(_("Choose a filename to save document as"),
2096 LFUN_BUFFER_WRITE_AS,
2097 make_pair(_("Documents|#o#O"),
2098 from_utf8(lyxrc.document_path)),
2099 make_pair(_("Templates|#T#t"),
2100 from_utf8(lyxrc.template_path)));
2102 if (!support::isLyXFilename(fname))
2105 support::FileFilterList const filter(_("LyX Documents (*.lyx)"));
2107 FileDialog::Result result =
2108 fileDlg.save(from_utf8(onlyPath(fname)),
2110 from_utf8(onlyFilename(fname)));
2112 if (result.first == FileDialog::Later)
2115 fname = to_utf8(result.second);
2120 // Make sure the absolute filename ends with appropriate suffix
2121 fname = makeAbsPath(fname).absFilename();
2122 if (!support::isLyXFilename(fname))
2126 fname = makeAbsPath(newname, onlyPath(oldname)).absFilename();
2128 if (FileName(fname).exists()) {
2129 docstring const file = makeDisplayPath(fname, 30);
2130 docstring text = bformat(_("The document %1$s already "
2131 "exists.\n\nDo you want to "
2132 "overwrite that document?"),
2134 int const ret = Alert::prompt(_("Overwrite document?"),
2135 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2141 // Ok, change the name of the buffer
2144 bool unnamed = isUnnamed();
2146 saveCheckSum(FileName(fname));
2149 setFileName(oldname);
2150 setUnnamed(unnamed);
2151 saveCheckSum(FileName(oldname));
2155 removeAutosaveFile(oldname);
2160 bool Buffer::menuWrite()
2163 LyX::ref().session().lastFiles().add(FileName(absFileName()));
2167 // FIXME: we don't tell the user *WHY* the save failed !!
2169 docstring const file = makeDisplayPath(absFileName(), 30);
2171 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2172 "Do you want to rename the document and "
2173 "try again?"), file);
2174 int const ret = Alert::prompt(_("Rename and save?"),
2175 text, 0, 1, _("&Rename"), _("&Cancel"));
2184 void Buffer::loadChildDocuments() const
2186 bool parse_error = false;
2188 for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) {
2189 if (it->lyxCode() != INCLUDE_CODE)
2191 InsetInclude const & inset = static_cast<InsetInclude const &>(*it);
2192 InsetCommandParams const & ip = inset.params();
2193 Buffer * child = loadIfNeeded(*this, ip);
2196 parse_error |= !child->errorList("Parse").empty();
2197 child->loadChildDocuments();
2200 if (use_gui && masterBuffer() == this)
2201 updateLabels(*this);
2205 string Buffer::bufferFormat() const
2215 bool Buffer::doExport(string const & format,
2216 bool put_in_tempdir, string & result_file)
2218 string backend_format;
2219 OutputParams runparams(¶ms().encoding());
2220 runparams.flavor = OutputParams::LATEX;
2221 runparams.linelen = lyxrc.plaintext_linelen;
2222 vector<string> backs = backends();
2223 if (find(backs.begin(), backs.end(), format) == backs.end()) {
2224 // Get shortest path to format
2225 Graph::EdgePath path;
2226 for (vector<string>::const_iterator it = backs.begin();
2227 it != backs.end(); ++it) {
2228 Graph::EdgePath p = theConverters().getPath(*it, format);
2229 if (!p.empty() && (path.empty() || p.size() < path.size())) {
2230 backend_format = *it;
2235 runparams.flavor = theConverters().getFlavor(path);
2237 Alert::error(_("Couldn't export file"),
2238 bformat(_("No information for exporting the format %1$s."),
2239 formats.prettyName(format)));
2243 backend_format = format;
2244 // FIXME: Don't hardcode format names here, but use a flag
2245 if (backend_format == "pdflatex")
2246 runparams.flavor = OutputParams::PDFLATEX;
2249 string filename = latexName(false);
2250 filename = addName(temppath(), filename);
2251 filename = changeExtension(filename,
2252 formats.extension(backend_format));
2254 // Plain text backend
2255 if (backend_format == "text")
2256 writePlaintextFile(*this, FileName(filename), runparams);
2258 else if (backend_format == "lyx")
2259 writeFile(FileName(filename));
2261 else if (isDocBook()) {
2262 runparams.nice = !put_in_tempdir;
2263 makeDocBookFile(FileName(filename), runparams);
2266 else if (backend_format == format) {
2267 runparams.nice = true;
2268 if (!makeLaTeXFile(FileName(filename), string(), runparams))
2270 } else if (!lyxrc.tex_allows_spaces
2271 && support::contains(filePath(), ' ')) {
2272 Alert::error(_("File name error"),
2273 _("The directory path to the document cannot contain spaces."));
2276 runparams.nice = false;
2277 if (!makeLaTeXFile(FileName(filename), filePath(), runparams))
2281 string const error_type = (format == "program")
2282 ? "Build" : bufferFormat();
2283 string const ext = formats.extension(format);
2284 FileName const tmp_result_file(changeExtension(filename, ext));
2285 bool const success = theConverters().convert(this, FileName(filename),
2286 tmp_result_file, FileName(absFileName()), backend_format, format,
2287 errorList(error_type));
2288 // Emit the signal to show the error list.
2289 if (format != backend_format)
2295 result_file = tmp_result_file.absFilename();
2297 result_file = changeExtension(absFileName(), ext);
2298 // We need to copy referenced files (e. g. included graphics
2299 // if format == "dvi") to the result dir.
2300 vector<ExportedFile> const files =
2301 runparams.exportdata->externalFiles(format);
2302 string const dest = onlyPath(result_file);
2303 CopyStatus status = SUCCESS;
2304 for (vector<ExportedFile>::const_iterator it = files.begin();
2305 it != files.end() && status != CANCEL; ++it) {
2307 formats.getFormatFromFile(it->sourceName);
2308 status = copyFile(fmt, it->sourceName,
2309 makeAbsPath(it->exportName, dest),
2310 it->exportName, status == FORCE);
2312 if (status == CANCEL) {
2313 message(_("Document export cancelled."));
2314 } else if (tmp_result_file.exists()) {
2315 // Finally copy the main file
2316 status = copyFile(format, tmp_result_file,
2317 FileName(result_file), result_file,
2319 message(bformat(_("Document exported as %1$s "
2321 formats.prettyName(format),
2322 makeDisplayPath(result_file)));
2324 // This must be a dummy converter like fax (bug 1888)
2325 message(bformat(_("Document exported as %1$s"),
2326 formats.prettyName(format)));
2334 bool Buffer::doExport(string const & format, bool put_in_tempdir)
2337 return doExport(format, put_in_tempdir, result_file);
2341 bool Buffer::preview(string const & format)
2344 if (!doExport(format, true, result_file))
2346 return formats.view(*this, FileName(result_file), format);
2350 bool Buffer::isExportable(string const & format) const
2352 vector<string> backs = backends();
2353 for (vector<string>::const_iterator it = backs.begin();
2354 it != backs.end(); ++it)
2355 if (theConverters().isReachable(*it, format))
2361 vector<Format const *> Buffer::exportableFormats(bool only_viewable) const
2363 vector<string> backs = backends();
2364 vector<Format const *> result =
2365 theConverters().getReachable(backs[0], only_viewable, true);
2366 for (vector<string>::const_iterator it = backs.begin() + 1;
2367 it != backs.end(); ++it) {
2368 vector<Format const *> r =
2369 theConverters().getReachable(*it, only_viewable, false);
2370 result.insert(result.end(), r.begin(), r.end());
2376 vector<string> Buffer::backends() const
2379 if (params().getTextClass().isTeXClassAvailable()) {
2380 v.push_back(bufferFormat());
2381 // FIXME: Don't hardcode format names here, but use a flag
2382 if (v.back() == "latex")
2383 v.push_back("pdflatex");
2385 v.push_back("text");
2391 bool Buffer::readFileHelper(FileName const & s)
2393 // File information about normal file
2395 docstring const file = makeDisplayPath(s.absFilename(), 50);
2396 docstring text = bformat(_("The specified document\n%1$s"
2397 "\ncould not be read."), file);
2398 Alert::error(_("Could not read document"), text);
2402 // Check if emergency save file exists and is newer.
2403 FileName const e(s.absFilename() + ".emergency");
2405 if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) {
2406 docstring const file = makeDisplayPath(s.absFilename(), 20);
2407 docstring const text =
2408 bformat(_("An emergency save of the document "
2410 "Recover emergency save?"), file);
2411 switch (Alert::prompt(_("Load emergency save?"), text, 0, 2,
2412 _("&Recover"), _("&Load Original"),
2416 // the file is not saved if we load the emergency file.
2426 // Now check if autosave file is newer.
2427 FileName const a(onlyPath(s.absFilename()) + '#' + onlyFilename(s.absFilename()) + '#');
2429 if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) {
2430 docstring const file = makeDisplayPath(s.absFilename(), 20);
2431 docstring const text =
2432 bformat(_("The backup of the document "
2433 "%1$s is newer.\n\nLoad the "
2434 "backup instead?"), file);
2435 switch (Alert::prompt(_("Load backup?"), text, 0, 2,
2436 _("&Load backup"), _("Load &original"),
2440 // the file is not saved if we load the autosave file.
2444 // Here we delete the autosave
2455 bool Buffer::loadLyXFile(FileName const & s)
2457 if (s.isReadable()) {
2458 if (readFileHelper(s)) {
2459 lyxvc().file_found_hook(s);
2460 if (!s.isWritable())
2465 docstring const file = makeDisplayPath(s.absFilename(), 20);
2466 // Here we probably should run
2467 if (LyXVC::file_not_found_hook(s)) {
2468 docstring const text =
2469 bformat(_("Do you want to retrieve the document"
2470 " %1$s from version control?"), file);
2471 int const ret = Alert::prompt(_("Retrieve from version control?"),
2472 text, 0, 1, _("&Retrieve"), _("&Cancel"));
2475 // How can we know _how_ to do the checkout?
2476 // With the current VC support it has to be,
2477 // a RCS file since CVS do not have special ,v files.
2479 return loadLyXFile(s);
2487 void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
2489 TeXErrors::Errors::const_iterator cit = terr.begin();
2490 TeXErrors::Errors::const_iterator end = terr.end();
2492 for (; cit != end; ++cit) {
2495 int errorRow = cit->error_in_line;
2496 bool found = texrow().getIdFromRow(errorRow, id_start,
2502 found = texrow().getIdFromRow(errorRow, id_end, pos_end);
2503 } while (found && id_start == id_end && pos_start == pos_end);
2505 errorList.push_back(ErrorItem(cit->error_desc,
2506 cit->error_text, id_start, pos_start, pos_end));