X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBuffer.cpp;h=74d5ac8deb428621552f968fb5e24fd25b339aac;hb=6b095e2b7107110bec593edd02bbdcf25adae76a;hp=99641e278c5af05157316f3208703c269dda8d69;hpb=3fefbf7917f66660f38d266f834b98945cff6903;p=lyx.git diff --git a/src/Buffer.cpp b/src/Buffer.cpp index 99641e278c..74d5ac8deb 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -55,6 +55,7 @@ #include "ParagraphParameters.h" #include "ParIterator.h" #include "PDFOptions.h" +#include "Session.h" #include "SpellChecker.h" #include "sgml.h" #include "texstream.h" @@ -76,9 +77,10 @@ #include "mathed/InsetMathHull.h" #include "mathed/MacroTable.h" -#include "mathed/MathMacroTemplate.h" +#include "mathed/InsetMathMacroTemplate.h" #include "mathed/MathSupport.h" +#include "graphics/GraphicsCache.h" #include "graphics/PreviewLoader.h" #include "frontends/alert.h" @@ -90,6 +92,7 @@ #include "support/debug.h" #include "support/docstring_list.h" #include "support/ExceptionMessage.h" +#include "support/FileMonitor.h" #include "support/FileName.h" #include "support/FileNameList.h" #include "support/filetools.h" @@ -134,7 +137,7 @@ int const LYX_FORMAT = LYX_FORMAT_LYX; typedef map DepClean; typedef map > RefCache; -} // namespace anon +} // namespace // A storehouse for the cloned buffers. @@ -169,7 +172,7 @@ public: /** If we have branches that use the file suffix feature, return the file name with suffix appended. */ - support::FileName exportFileName() const; + FileName exportFileName() const; Buffer * owner_; @@ -262,9 +265,8 @@ public: /// Container for all sort of Buffer dependant errors. map errorLists; - /// timestamp and checksum used to test if the file has been externally - /// modified. (Used to properly enable 'File->Revert to saved', bug 4114). - time_t timestamp_; + /// checksum used to test if the file has been externally modified. Used to + /// double check whether the file had been externally modified when saving. unsigned long checksum_; /// @@ -277,7 +279,7 @@ public: /// A cache for the bibfiles (including bibfiles of loaded child /// documents), needed for appropriate update of natbib labels. - mutable support::FileNameList bibfiles_cache_; + mutable FileNamePairList bibfiles_cache_; // FIXME The caching mechanism could be improved. At present, we have a // cache for each Buffer, that caches all the bibliography info for that @@ -287,8 +289,6 @@ public: mutable BiblioInfo bibinfo_; /// whether the bibinfo cache is valid mutable bool bibinfo_cache_valid_; - /// whether the bibfile cache is valid - mutable bool bibfile_cache_valid_; /// Cache of timestamps of .bib files map bibfile_status_; /// Indicates whether the bibinfo has changed since the last time @@ -341,10 +341,8 @@ public: if (!cloned_buffer_ && parent_buffer && pb) LYXERR0("Warning: a buffer should not have two parents!"); parent_buffer = pb; - if (!cloned_buffer_ && parent_buffer) { - parent_buffer->invalidateBibfileCache(); + if (!cloned_buffer_ && parent_buffer) parent_buffer->invalidateBibinfoCache(); - } } /// If non zero, this buffer is a clone of existing buffer \p cloned_buffer_ @@ -375,6 +373,15 @@ public: // display the review toolbar, for instance) mutable bool tracked_changes_present_; + // Make sure the file monitor monitors the good file. + void refreshFileMonitor(); + + /// Notify or clear of external modification + void fileExternallyModified(bool exists); + + /// has been externally modified? Can be reset by the user. + mutable bool externally_modified_; + private: /// So we can force access via the accessors. mutable Buffer const * parent_buffer; @@ -383,6 +390,7 @@ private: int char_count_; int blank_count_; + FileMonitorPtr file_monitor_; }; @@ -418,13 +426,15 @@ Buffer::Impl::Impl(Buffer * owner, FileName const & file, bool readonly_, : owner_(owner), lyx_clean(true), bak_clean(true), unnamed(false), internal_buffer(false), read_only(readonly_), filename(file), file_fully_loaded(false), file_format(LYX_FORMAT), need_format_backup(false), - ignore_parent(false), toc_backend(owner), macro_lock(false), timestamp_(0), + ignore_parent(false), toc_backend(owner), macro_lock(false), checksum_(0), wa_(0), gui_(0), undo_(*owner), bibinfo_cache_valid_(false), - bibfile_cache_valid_(false), cite_labels_valid_(false), preview_error_(false), - inset(0), preview_loader_(0), cloned_buffer_(cloned_buffer), clone_list_(0), - doing_export(false), parent_buffer(0), + cite_labels_valid_(false), preview_error_(false), + inset(0), preview_loader_(0), cloned_buffer_(cloned_buffer), + clone_list_(0), doing_export(false), + tracked_changes_present_(0), externally_modified_(false), parent_buffer(0), word_count_(0), char_count_(0), blank_count_(0) { + refreshFileMonitor(); if (!cloned_buffer_) { temppath = createBufferTmpDir(); lyxvc.setBuffer(owner_); @@ -438,7 +448,6 @@ Buffer::Impl::Impl(Buffer * owner, FileName const & file, bool readonly_, bibfiles_cache_ = cloned_buffer_->d->bibfiles_cache_; bibinfo_ = cloned_buffer_->d->bibinfo_; bibinfo_cache_valid_ = cloned_buffer_->d->bibinfo_cache_valid_; - bibfile_cache_valid_ = cloned_buffer_->d->bibfile_cache_valid_; bibfile_status_ = cloned_buffer_->d->bibfile_status_; cite_labels_valid_ = cloned_buffer_->d->cite_labels_valid_; unnamed = cloned_buffer_->d->unnamed; @@ -490,10 +499,8 @@ Buffer::~Buffer() // ourselves as a child. d->clone_list_->erase(this); // loop over children - Impl::BufferPositionMap::iterator it = d->children_positions.begin(); - Impl::BufferPositionMap::iterator end = d->children_positions.end(); - for (; it != end; ++it) { - Buffer * child = const_cast(it->first); + for (auto const & p : d->children_positions) { + Buffer * child = const_cast(p.first); if (d->clone_list_->erase(child)) delete child; } @@ -519,21 +526,23 @@ Buffer::~Buffer() d->position_to_children.clear(); } else { // loop over children - Impl::BufferPositionMap::iterator it = d->children_positions.begin(); - Impl::BufferPositionMap::iterator end = d->children_positions.end(); - for (; it != end; ++it) { - Buffer * child = const_cast(it->first); - if (theBufferList().isLoaded(child)) { - if (theBufferList().isOthersChild(this, child)) - child->setParent(0); - else - theBufferList().release(child); + for (auto const & p : d->children_positions) { + Buffer * child = const_cast(p.first); + if (theBufferList().isLoaded(child)) { + if (theBufferList().isOthersChild(this, child)) + child->setParent(0); + else + theBufferList().release(child); } } if (!isClean()) { docstring msg = _("LyX attempted to close a document that had unsaved changes!\n"); - msg += emergencyWrite(); + try { + msg += emergencyWrite(); + } catch (...) { + msg += " " + _("Save failed! Document is lost."); + } Alert::warning(_("Attempting to close changed document!"), msg); } @@ -596,12 +605,10 @@ void Buffer::cloneWithChildren(BufferMap & bufmap, CloneList * clones) const // math macro caches need to be rethought and simplified. // I am not sure wether we should handle Buffer cloning here or in BufferList. // Right now BufferList knows nothing about buffer clones. - Impl::PositionScopeBufferMap::iterator it = d->position_to_children.begin(); - Impl::PositionScopeBufferMap::iterator end = d->position_to_children.end(); - for (; it != end; ++it) { - DocIterator dit = it->first.clone(buffer_clone); + for (auto const & p : d->position_to_children) { + DocIterator dit = p.first.clone(buffer_clone); dit.setBuffer(buffer_clone); - Buffer * child = const_cast(it->second.buffer); + Buffer * child = const_cast(p.second.buffer); child->cloneWithChildren(bufmap, clones); BufferMap::iterator const bit = bufmap.find(child); @@ -694,10 +701,8 @@ BufferParams const & Buffer::masterParams() const BufferParams & mparams = const_cast(masterBuffer())->params(); // Copy child authors to the params. We need those pointers. - AuthorList const & child_authors = params().authors(); - AuthorList::Authors::const_iterator it = child_authors.begin(); - for (; it != child_authors.end(); ++it) - mparams.authors().record(*it); + for (Author const & a : params().authors()) + mparams.authors().record(a); return mparams; } @@ -705,7 +710,7 @@ BufferParams const & Buffer::masterParams() const double Buffer::fontScalingFactor() const { return isExporting() ? 75.0 * params().html_math_img_scale - : 0.01 * lyxrc.dpi * lyxrc.zoom * lyxrc.preview_scale_factor * params().display_pixel_ratio; + : 0.01 * lyxrc.dpi * lyxrc.currentZoom * lyxrc.preview_scale_factor * params().display_pixel_ratio; } @@ -814,7 +819,7 @@ string Buffer::logName(LogType * type) const FileName const bname( addName(path, onlyFileName( changeExtension(filename, - formats.extension(params().bufferFormat()) + ".out")))); + theFormats().extension(params().bufferFormat()) + ".out")))); // Also consider the master buffer log file FileName masterfname = fname; @@ -862,6 +867,7 @@ void Buffer::setFileName(FileName const & fname) { bool const changed = fname != d->filename; d->filename = fname; + d->refreshFileMonitor(); if (changed) lyxvc().file_found_hook(fname); setReadonly(d->filename.isReadOnly()); @@ -909,8 +915,12 @@ int Buffer::readHeader(Lexer & lex) params().html_latex_end.clear(); params().html_math_img_scale = 1.0; params().output_sync_macro.erase(); - params().setLocalLayout(string(), false); - params().setLocalLayout(string(), true); + params().setLocalLayout(docstring(), false); + params().setLocalLayout(docstring(), true); + params().biblio_opts.erase(); + params().biblatex_bibstyle.erase(); + params().biblatex_citestyle.erase(); + params().multibib.erase(); for (int i = 0; i < 4; ++i) { params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i]; @@ -949,17 +959,17 @@ int Buffer::readHeader(Lexer & lex) "%1$s %2$s\n"), from_utf8(token), lex.getDocString()); - errorList.push_back(ErrorItem(_("Document header error"), - s, -1, 0, 0)); + errorList.push_back(ErrorItem(_("Document header error"), s)); } } } if (begin_header_line) { docstring const s = _("\\begin_header is missing"); - errorList.push_back(ErrorItem(_("Document header error"), - s, -1, 0, 0)); + errorList.push_back(ErrorItem(_("Document header error"), s)); } + params().shell_escape = theSession().shellescapeFiles().find(absFileName()); + params().makeDocumentClass(); return unknown_tokens; @@ -979,8 +989,7 @@ bool Buffer::readDocument(Lexer & lex) if (!lex.checkFor("\\begin_document")) { docstring const s = _("\\begin_document is missing"); - errorList.push_back(ErrorItem(_("Document header error"), - s, -1, 0, 0)); + errorList.push_back(ErrorItem(_("Document header error"), s)); } readHeader(lex); @@ -1049,10 +1058,8 @@ bool Buffer::readDocument(Lexer & lex) // inform parent buffer about local macros if (parent()) { Buffer const * pbuf = parent(); - UserMacroSet::const_iterator cit = usermacros.begin(); - UserMacroSet::const_iterator end = usermacros.end(); - for (; cit != end; ++cit) - pbuf->usermacros.insert(*cit); + for (auto const & m : usermacros) + pbuf->usermacros.insert(m); } usermacros.clear(); updateMacros(); @@ -1063,13 +1070,12 @@ bool Buffer::readDocument(Lexer & lex) bool Buffer::importString(string const & format, docstring const & contents, ErrorList & errorList) { - Format const * fmt = formats.getFormat(format); + Format const * fmt = theFormats().getFormat(format); if (!fmt) return false; // It is important to use the correct extension here, since some // converters create a wrong output file otherwise (e.g. html2latex) - TempFile const tempfile("Buffer_importStringXXXXXX." + fmt->extension()); - FileName const name(tempfile.name()); + FileName const name = tempFileName("Buffer_importStringXXXXXX." + fmt->extension()); ofdocstream os(name.toFilesystemEncoding().c_str()); // Do not convert os implicitly to bool, since that is forbidden in C++11. bool const success = !(os << contents).fail(); @@ -1085,8 +1091,7 @@ bool Buffer::importString(string const & format, docstring const & contents, Err converted = importFile(format, name, errorList); } - if (name.exists()) - name.removeFile(); + removeTempFile(name); return converted; } @@ -1096,10 +1101,14 @@ bool Buffer::importFile(string const & format, FileName const & name, ErrorList if (!theConverters().isReachable(format, "lyx")) return false; - TempFile const tempfile("Buffer_importFileXXXXXX.lyx"); - FileName const lyx(tempfile.name()); - if (theConverters().convert(0, name, lyx, name, format, "lyx", errorList)) - return readFile(lyx) == ReadSuccess; + FileName const lyx = tempFileName("Buffer_importFileXXXXXX.lyx"); + Converters::RetVal const retval = + theConverters().convert(0, name, lyx, name, format, "lyx", errorList); + if (retval == Converters::SUCCESS) { + bool const success = readFile(lyx) == ReadSuccess; + removeTempFile(lyx); + return success; + } return false; } @@ -1177,7 +1186,7 @@ Buffer::ReadStatus Buffer::readFile(FileName const & fn) d->file_fully_loaded = true; d->read_only = !d->filename.isWritable(); - params().compressed = formats.isZippedFile(d->filename); + params().compressed = theFormats().isZippedFile(d->filename); saveCheckSum(); return ReadSuccess; } @@ -1291,7 +1300,7 @@ Buffer::ReadStatus Buffer::convertLyXFormat(FileName const & fn, command << os::python() << ' ' << quoteName(lyx2lyx.toFilesystemEncoding()) << " -t " << convert(LYX_FORMAT) - << " -o " << quoteName(tmpfile.toFilesystemEncoding()) + << " -o " << quoteName(tmpfile.toSafeFilesystemEncoding()) << ' ' << quoteName(fn.toSafeFilesystemEncoding()); string const command_str = command.str(); @@ -1372,7 +1381,7 @@ bool Buffer::save() const } // ask if the disk file has been externally modified (use checksum method) - if (fileName().exists() && isExternallyModified(checksum_method)) { + if (fileName().exists() && isChecksumModified()) { docstring text = bformat(_("Document %1$s has been externally modified. " "Are you sure you want to overwrite this file?"), file); @@ -1387,12 +1396,12 @@ bool Buffer::save() const // if the file does not yet exist, none of the backup activity // that follows is necessary - if (!fileName().exists()) { + if (!fileName().exists()) { if (!writeFile(fileName())) - return false; - markClean(); - return true; - } + return false; + markClean(); + return true; + } // we first write the file to a new name, then move it to its // proper location once that has been done successfully. that @@ -1583,7 +1592,7 @@ docstring Buffer::emergencyWrite() return user_message; } - user_message += " " + _("Save failed! Bummer. Document is lost."); + user_message += " " + _("Save failed! Document is lost."); // Don't try again. markClean(); return user_message; @@ -1603,16 +1612,14 @@ bool Buffer::write(ostream & ofs) const // Important: Keep the version formatting in sync with lyx2lyx and // tex2lyx (bug 7951) ofs << "#LyX " << lyx_version_major << "." << lyx_version_minor - << " created this file. For more info see http://www.lyx.org/\n" + << " created this file. For more info see https://www.lyx.org/\n" << "\\lyxformat " << LYX_FORMAT << "\n" << "\\begin_document\n"; /// For each author, set 'used' to true if there is a change /// by this author in the document; otherwise set it to 'false'. - AuthorList::Authors::const_iterator a_it = params().authors().begin(); - AuthorList::Authors::const_iterator a_end = params().authors().end(); - for (; a_it != a_end; ++a_it) - a_it->setUsed(false); + for (Author const & a : params().authors()) + a.setUsed(false); ParIterator const end = const_cast(this)->par_iterator_end(); ParIterator it = const_cast(this)->par_iterator_begin(); @@ -1653,7 +1660,7 @@ bool Buffer::write(ostream & ofs) const } -bool Buffer::makeLaTeXFile(FileName const & fname, +Buffer::ExportStatus Buffer::makeLaTeXFile(FileName const & fname, string const & original_path, OutputParams const & runparams_in, OutputWhat output) const @@ -1677,14 +1684,14 @@ bool Buffer::makeLaTeXFile(FileName const & fname, Alert::error(_("Iconv software exception Detected"), bformat(_("Please " "verify that the support software for your encoding (%1$s) is " "properly installed"), from_ascii(encoding))); - return false; + return ExportError; } if (!openFileWrite(ofs, fname)) - return false; + return ExportError; ErrorList & errorList = d->errorLists["Export"]; errorList.clear(); - bool failed_export = false; + ExportStatus status = ExportSuccess; otexstream os(ofs); // make sure we are ready to export @@ -1694,31 +1701,45 @@ bool Buffer::makeLaTeXFile(FileName const & fname, updateBuffer(); updateMacroInstances(OutputUpdate); + ExportStatus retval; try { - writeLaTeXSource(os, original_path, runparams, output); + retval = writeLaTeXSource(os, original_path, runparams, output); + if (retval == ExportKilled) + return ExportKilled; } catch (EncodingException const & e) { docstring const failed(1, e.failed_char); ostringstream oss; oss << "0x" << hex << e.failed_char << dec; - docstring msg = bformat(_("Could not find LaTeX command for character '%1$s'" - " (code point %2$s)"), - failed, from_utf8(oss.str())); - errorList.push_back(ErrorItem(msg, _("Some characters of your document are probably not " - "representable in the chosen encoding.\n" - "Changing the document encoding to utf8 could help."), - e.par_id, e.pos, e.pos + 1)); - failed_export = true; + if (getParFromID(e.par_id).paragraph().layout().pass_thru) { + docstring msg = bformat(_("Uncodable character '%1$s'" + " (code point %2$s)"), + failed, from_utf8(oss.str())); + errorList.push_back(ErrorItem(msg, _("Some characters of your document are not " + "representable in specific verbatim contexts.\n" + "Changing the document encoding to utf8 could help."), + {e.par_id, e.pos}, {e.par_id, e.pos + 1})); + } else { + docstring msg = bformat(_("Could not find LaTeX command for character '%1$s'" + " (code point %2$s)"), + failed, from_utf8(oss.str())); + errorList.push_back(ErrorItem(msg, _("Some characters of your document are probably not " + "representable in the chosen encoding.\n" + "Changing the document encoding to utf8 could help."), + {e.par_id, e.pos}, {e.par_id, e.pos + 1})); + } + status = ExportError; } catch (iconv_codecvt_facet_exception const & e) { errorList.push_back(ErrorItem(_("iconv conversion failed"), - _(e.what()), -1, 0, 0)); - failed_export = true; + _(e.what()))); + status = ExportError; } catch (exception const & e) { errorList.push_back(ErrorItem(_("conversion failed"), - _(e.what()), -1, 0, 0)); - failed_export = true; + _(e.what()))); + lyxerr << e.what() << endl; + status = ExportError; } catch (...) { lyxerr << "Caught some really weird exception..." << endl; @@ -1729,7 +1750,7 @@ bool Buffer::makeLaTeXFile(FileName const & fname, ofs.close(); if (ofs.fail()) { - failed_export = true; + status = ExportError; lyxerr << "File '" << fname << "' was not closed properly." << endl; } @@ -1737,11 +1758,11 @@ bool Buffer::makeLaTeXFile(FileName const & fname, errorList.clear(); else errors("Export"); - return !failed_export; + return status; } -void Buffer::writeLaTeXSource(otexstream & os, +Buffer::ExportStatus Buffer::writeLaTeXSource(otexstream & os, string const & original_path, OutputParams const & runparams_in, OutputWhat output) const @@ -1760,6 +1781,9 @@ void Buffer::writeLaTeXSource(otexstream & os, // (or not reached) and characters encodable in the current // encoding are not converted to ASCII-representation. + // Some macros rely on font encoding + runparams.main_fontenc = params().main_font_encoding(); + // If we are compiling a file standalone, even if this is the // child of some other buffer, let's cut the link here, so the // file is really independent and no concurring settings from @@ -1788,7 +1812,7 @@ void Buffer::writeLaTeXSource(otexstream & os, // first paragraph of the document. (Asger) if (output_preamble && runparams.nice) { os << "%% LyX " << lyx_version << " created this file. " - "For more info, see http://www.lyx.org/.\n" + "For more info, see https://www.lyx.org/.\n" "%% Do not edit unless you really know what " "you are doing.\n"; } @@ -1832,7 +1856,7 @@ void Buffer::writeLaTeXSource(otexstream & os, if (!uncodable_glyphs.empty()) { frontend::Alert::warning( _("Uncodable character in file path"), - support::bformat( + bformat( _("The path of your document\n" "(%1$s)\n" "contains glyphs that are unknown " @@ -1851,7 +1875,7 @@ void Buffer::writeLaTeXSource(otexstream & os, inputpath, uncodable_glyphs)); } else { string docdir = - support::latex_path(original_path); + latex_path(original_path); if (contains(docdir, '#')) { docdir = subst(docdir, "#", "\\#"); os << "\\catcode`\\#=11" @@ -1878,6 +1902,55 @@ void Buffer::writeLaTeXSource(otexstream & os, runparams.use_babel = params().writeLaTeX(os, features, d->filename.onlyPath()); + // Biblatex bibliographies are loaded here + if (params().useBiblatex()) { + vector const bibfiles = + prepareBibFilePaths(runparams, getBibfiles(), true); + for (docstring const & file: bibfiles) + os << "\\addbibresource{" << file << "}\n"; + } + + if (!runparams.dryrun && features.hasPolyglossiaExclusiveLanguages() + && !features.hasOnlyPolyglossiaLanguages()) { + docstring blangs; + docstring plangs; + vector bll = features.getBabelExclusiveLanguages(); + vector pll = features.getPolyglossiaExclusiveLanguages(); + if (!bll.empty()) { + docstring langs; + for (string const & sit : bll) { + if (!langs.empty()) + langs += ", "; + langs += _(sit); + } + blangs = bll.size() > 1 ? + bformat(_("The languages %1$s are only supported by Babel."), langs) + : bformat(_("The language %1$s is only supported by Babel."), langs); + } + if (!pll.empty()) { + docstring langs; + for (string const & pit : pll) { + if (!langs.empty()) + langs += ", "; + langs += _(pit); + } + plangs = pll.size() > 1 ? + bformat(_("The languages %1$s are only supported by Polyglossia."), langs) + : bformat(_("The language %1$s is only supported by Polyglossia."), langs); + if (!blangs.empty()) + plangs += "\n"; + } + + frontend::Alert::warning( + _("Incompatible Languages!"), + bformat( + _("You cannot use the following languages " + "together in one LaTeX document because " + "they require conflicting language packages:\n" + "%1$s%2$s"), + plangs, blangs)); + } + // Japanese might be required only in some children of a document, // but once required, we must keep use_japanese true. runparams.use_japanese |= features.isRequired("japanese"); @@ -1886,28 +1959,33 @@ void Buffer::writeLaTeXSource(otexstream & os, // Restore the parenthood if needed if (!runparams.is_child) d->ignore_parent = false; - return; + return ExportSuccess; } // make the body. + // mark the beginning of the body to separate it from InPreamble insets + os.texrow().start(TexRow::beginDocument()); os << "\\begin{document}\n"; + // mark the start of a new paragraph by simulating a newline, + // so that os.afterParbreak() returns true at document start + os.lastChar('\n'); + // output the parent macros - MacroSet::iterator it = parentMacros.begin(); - MacroSet::iterator end = parentMacros.end(); - for (; it != end; ++it) { - int num_lines = (*it)->write(os.os(), true); + for (auto const & mac : parentMacros) { + int num_lines = mac->write(os.os(), true); os.texrow().newlines(num_lines); } } // output_preamble - os.texrow().start(paragraphs().begin()->id(), 0); - LYXERR(Debug::INFO, "preamble finished, now the body."); // the real stuff - latexParagraphs(*this, text(), os, runparams); + try { + latexParagraphs(*this, text(), os, runparams); + } + catch (ConversionException const &) { return ExportKilled; } // Restore the parenthood if needed if (!runparams.is_child) @@ -1926,10 +2004,11 @@ void Buffer::writeLaTeXSource(otexstream & os, LYXERR(Debug::INFO, "Finished making LaTeX file."); LYXERR(Debug::INFO, "Row count was " << os.texrow().rows() - 1 << '.'); + return ExportSuccess; } -void Buffer::makeDocBookFile(FileName const & fname, +Buffer::ExportStatus Buffer::makeDocBookFile(FileName const & fname, OutputParams const & runparams, OutputWhat output) const { @@ -1937,22 +2016,26 @@ void Buffer::makeDocBookFile(FileName const & fname, ofdocstream ofs; if (!openFileWrite(ofs, fname)) - return; + return ExportError; // make sure we are ready to export // this needs to be done before we validate updateBuffer(); updateMacroInstances(OutputUpdate); - writeDocBookSource(ofs, fname.absFileName(), runparams, output); + ExportStatus const retval = + writeDocBookSource(ofs, fname.absFileName(), runparams, output); + if (retval == ExportKilled) + return ExportKilled; ofs.close(); if (ofs.fail()) lyxerr << "File '" << fname << "' was not closed properly." << endl; + return ExportSuccess; } -void Buffer::writeDocBookSource(odocstream & os, string const & fname, +Buffer::ExportStatus Buffer::writeDocBookSource(odocstream & os, string const & fname, OutputParams const & runparams, OutputWhat output) const { @@ -1980,12 +2063,12 @@ void Buffer::writeDocBookSource(odocstream & os, string const & fname, if (! tclass.class_header().empty()) os << from_ascii(tclass.class_header()); else if (runparams.flavor == OutputParams::XML) - os << "PUBLIC \"-//OASIS//DTD DocBook XML//EN\" " - << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\""; + os << "PUBLIC \"-//OASIS//DTD DocBook XML V4.2//EN\" " + << "\"https://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\""; else os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\""; - docstring preamble = from_utf8(params().preamble); + docstring preamble = params().preamble; if (runparams.flavor != OutputParams::XML ) { preamble += "\n"; preamble += "\n"; @@ -2020,41 +2103,48 @@ void Buffer::writeDocBookSource(odocstream & os, string const & fname, os << "\n"; + << "\n See https://www.lyx.org/ for more information -->\n"; params().documentClass().counters().reset(); sgml::openTag(os, top); os << '\n'; - docbookParagraphs(text(), *this, os, runparams); + try { + docbookParagraphs(text(), *this, os, runparams); + } + catch (ConversionException const &) { return ExportKilled; } sgml::closeTag(os, top_element); } + return ExportSuccess; } -void Buffer::makeLyXHTMLFile(FileName const & fname, +Buffer::ExportStatus Buffer::makeLyXHTMLFile(FileName const & fname, OutputParams const & runparams) const { LYXERR(Debug::LATEX, "makeLyXHTMLFile..."); ofdocstream ofs; if (!openFileWrite(ofs, fname)) - return; + return ExportError; // make sure we are ready to export // this has to be done before we validate updateBuffer(UpdateMaster, OutputUpdate); updateMacroInstances(OutputUpdate); - writeLyXHTMLSource(ofs, runparams, FullSource); + ExportStatus const retval = writeLyXHTMLSource(ofs, runparams, FullSource); + if (retval == ExportKilled) + return retval; ofs.close(); if (ofs.fail()) lyxerr << "File '" << fname << "' was not closed properly." << endl; + return retval; } -void Buffer::writeLyXHTMLSource(odocstream & os, +Buffer::ExportStatus Buffer::writeLyXHTMLSource(odocstream & os, OutputParams const & runparams, OutputWhat output) const { @@ -2088,14 +2178,14 @@ void Buffer::writeLyXHTMLSource(odocstream & os, if (!styles.empty()) os << "\n\n" << styles << '\n'; - styles = from_utf8(features.getPreambleSnippets()); + styles = features.getPreambleSnippets().str; if (!styles.empty()) os << "\n\n" << styles << '\n'; // we will collect CSS information in a stream, and then output it // either here, as part of the header, or else in a separate file. odocstringstream css; - styles = from_utf8(features.getCSSSnippets()); + styles = features.getCSSSnippets(); if (!styles.empty()) css << "/* LyX Provided Styles */\n" << styles << '\n'; @@ -2155,13 +2245,18 @@ void Buffer::writeLyXHTMLSource(odocstream & os, if (output != IncludedFile) // if we're an included file, the counters are in the master. params().documentClass().counters().reset(); - xhtmlParagraphs(text(), *this, xs, runparams); + try { + xhtmlParagraphs(text(), *this, xs, runparams); + } + catch (ConversionException const &) { return ExportKilled; } if (output_body_tag) os << "\n"; } if (output_preamble) os << "\n"; + + return ExportSuccess; } @@ -2184,7 +2279,12 @@ int Buffer::runChktex() runparams.flavor = OutputParams::LATEX; runparams.nice = false; runparams.linelen = lyxrc.plaintext_linelen; - makeLaTeXFile(FileName(name), org_path, runparams); + ExportStatus const retval = + makeLaTeXFile(FileName(name), org_path, runparams); + if (retval != ExportSuccess) { + // error code on failure + return -1; + } TeXErrors terr; Chktex chktex(lyxrc.chktex_command, onlyFileName(name), filePath()); @@ -2237,49 +2337,10 @@ void Buffer::getLabelList(vector & list) const list.clear(); shared_ptr toc = d->toc_backend.toc("label"); - Toc::const_iterator toc_it = toc->begin(); - Toc::const_iterator end = toc->end(); - for (; toc_it != end; ++toc_it) { - if (toc_it->depth() == 0) - list.push_back(toc_it->str()); - } -} - - -void Buffer::updateBibfilesCache(UpdateScope scope) const -{ - // FIXME This is probably unnecssary, given where we call this. - // If this is a child document, use the parent's cache instead. - if (parent() && scope != UpdateChildOnly) { - masterBuffer()->updateBibfilesCache(); - return; - } - - d->bibfiles_cache_.clear(); - for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) { - if (it->lyxCode() == BIBTEX_CODE) { - InsetBibtex const & inset = static_cast(*it); - support::FileNameList const bibfiles = inset.getBibFiles(); - d->bibfiles_cache_.insert(d->bibfiles_cache_.end(), - bibfiles.begin(), - bibfiles.end()); - } else if (it->lyxCode() == INCLUDE_CODE) { - InsetInclude & inset = static_cast(*it); - Buffer const * const incbuf = inset.getChildBuffer(); - if (!incbuf) - continue; - support::FileNameList const & bibfiles = - incbuf->getBibfilesCache(UpdateChildOnly); - if (!bibfiles.empty()) { - d->bibfiles_cache_.insert(d->bibfiles_cache_.end(), - bibfiles.begin(), - bibfiles.end()); - } - } + for (auto const & tocit : *toc) { + if (tocit.depth() == 0) + list.push_back(tocit.str()); } - d->bibfile_cache_valid_ = true; - d->bibinfo_cache_valid_ = false; - d->cite_labels_valid_ = false; } @@ -2287,6 +2348,7 @@ void Buffer::invalidateBibinfoCache() const { d->bibinfo_cache_valid_ = false; d->cite_labels_valid_ = false; + removeBiblioTempFiles(); // also invalidate the cache for the parent buffer Buffer const * const pbuf = d->parent(); if (pbuf) @@ -2294,29 +2356,13 @@ void Buffer::invalidateBibinfoCache() const } -void Buffer::invalidateBibfileCache() const -{ - d->bibfile_cache_valid_ = false; - d->bibinfo_cache_valid_ = false; - d->cite_labels_valid_ = false; - // also invalidate the cache for the parent buffer - Buffer const * const pbuf = d->parent(); - if (pbuf) - pbuf->invalidateBibfileCache(); -} - - -support::FileNameList const & Buffer::getBibfilesCache(UpdateScope scope) const +FileNamePairList const & Buffer::getBibfiles(UpdateScope scope) const { // FIXME This is probably unnecessary, given where we call this. - // If this is a child document, use the master's cache instead. + // If this is a child document, use the master instead. Buffer const * const pbuf = masterBuffer(); if (pbuf != this && scope != UpdateChildOnly) - return pbuf->getBibfilesCache(); - - if (!d->bibfile_cache_valid_) - this->updateBibfilesCache(scope); - + return pbuf->getBibfiles(); return d->bibfiles_cache_; } @@ -2330,6 +2376,29 @@ BiblioInfo const & Buffer::masterBibInfo() const } +BiblioInfo const & Buffer::bibInfo() const +{ + return d->bibinfo_; +} + + +void Buffer::registerBibfiles(FileNamePairList const & bf) const { + // We register the bib files in the master buffer, + // if there is one, but also in every single buffer, + // in case a child is compiled alone. + Buffer const * const tmp = masterBuffer(); + if (tmp != this) + tmp->registerBibfiles(bf); + + for (auto const & p : bf) { + FileNamePairList::const_iterator temp = + find(d->bibfiles_cache_.begin(), d->bibfiles_cache_.end(), p); + if (temp == d->bibfiles_cache_.end()) + d->bibfiles_cache_.push_back(p); + } +} + + void Buffer::checkIfBibInfoCacheIsValid() const { // use the master's cache @@ -2339,17 +2408,23 @@ void Buffer::checkIfBibInfoCacheIsValid() const return; } + // if we already know the cache is invalid, no need to check + // the timestamps + if (!d->bibinfo_cache_valid_) + return; + // compare the cached timestamps with the actual ones. - FileNameList const & bibfiles_cache = getBibfilesCache(); - FileNameList::const_iterator ei = bibfiles_cache.begin(); - FileNameList::const_iterator en = bibfiles_cache.end(); + FileNamePairList const & bibfiles_cache = getBibfiles(); + FileNamePairList::const_iterator ei = bibfiles_cache.begin(); + FileNamePairList::const_iterator en = bibfiles_cache.end(); for (; ei != en; ++ ei) { - time_t lastw = ei->lastModified(); - time_t prevw = d->bibfile_status_[*ei]; + FileName const fn = ei->second; + time_t lastw = fn.lastModified(); + time_t prevw = d->bibfile_status_[fn]; if (lastw != prevw) { d->bibinfo_cache_valid_ = false; d->cite_labels_valid_ = false; - d->bibfile_status_[*ei] = lastw; + d->bibfile_status_[fn] = lastw; } } } @@ -2369,33 +2444,46 @@ void Buffer::reloadBibInfoCache() const return; d->bibinfo_.clear(); - collectBibKeys(); + FileNameList checkedFiles; + collectBibKeys(checkedFiles); d->bibinfo_cache_valid_ = true; } -void Buffer::collectBibKeys() const +void Buffer::collectBibKeys(FileNameList & checkedFiles) const { for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) - it->collectBibKeys(it); + it->collectBibKeys(it, checkedFiles); } -void Buffer::addBiblioInfo(BiblioInfo const & bi) const +void Buffer::addBiblioInfo(BiblioInfo const & bin) const { - Buffer const * tmp = masterBuffer(); - BiblioInfo & masterbi = (tmp == this) ? - d->bibinfo_ : tmp->d->bibinfo_; - masterbi.mergeBiblioInfo(bi); + // We add the biblio info to the master buffer, + // if there is one, but also to every single buffer, + // in case a child is compiled alone. + BiblioInfo & bi = d->bibinfo_; + bi.mergeBiblioInfo(bin); + + if (parent() != 0) { + BiblioInfo & masterbi = parent()->d->bibinfo_; + masterbi.mergeBiblioInfo(bin); + } } -void Buffer::addBibTeXInfo(docstring const & key, BibTeXInfo const & bi) const +void Buffer::addBibTeXInfo(docstring const & key, BibTeXInfo const & bin) const { - Buffer const * tmp = masterBuffer(); - BiblioInfo & masterbi = (tmp == this) ? - d->bibinfo_ : tmp->d->bibinfo_; - masterbi[key] = bi; + // We add the bibtex info to the master buffer, + // if there is one, but also to every single buffer, + // in case a child is compiled alone. + BiblioInfo & bi = d->bibinfo_; + bi[key] = bin; + + if (parent() != 0) { + BiblioInfo & masterbi = parent()->d->bibinfo_; + masterbi[key] = bin; + } } @@ -2406,6 +2494,11 @@ void Buffer::makeCitationLabels() const } +void Buffer::invalidateCiteLabels() const +{ + masterBuffer()->d->cite_labels_valid_ = false; +} + bool Buffer::citeLabelsValid() const { return masterBuffer()->d->cite_labels_valid_; @@ -2458,7 +2551,7 @@ bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) switch (cmd.action()) { case LFUN_BUFFER_TOGGLE_READ_ONLY: - flag.setOnOff(isReadonly()); + flag.setOnOff(hasReadonlyFlag()); break; // FIXME: There is need for a command-line import. @@ -2477,23 +2570,20 @@ bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) enable = true; break; } - string format = to_utf8(arg); + string format = (arg.empty() || arg == "default") ? + params().getDefaultOutputFormat() : to_utf8(arg); size_t pos = format.find(' '); if (pos != string::npos) format = format.substr(0, pos); - enable = params().isExportable(format); + enable = params().isExportable(format, false); if (!enable) flag.message(bformat( _("Don't know how to export to format: %1$s"), arg)); break; } - case LFUN_BUFFER_CHKTEX: - enable = params().isLatex() && !lyxrc.chktex_command.empty(); - break; - case LFUN_BUILD_PROGRAM: - enable = params().isExportable("program"); + enable = params().isExportable("program", false); break; case LFUN_BRANCH_ACTIVATE: @@ -2533,15 +2623,16 @@ bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) flag.setOnOff(params().output_changes); break; - case LFUN_BUFFER_TOGGLE_COMPRESSION: { + case LFUN_BUFFER_TOGGLE_COMPRESSION: flag.setOnOff(params().compressed); break; - } - case LFUN_BUFFER_TOGGLE_OUTPUT_SYNC: { + case LFUN_BUFFER_TOGGLE_OUTPUT_SYNC: flag.setOnOff(params().output_sync); break; - } + + case LFUN_BUFFER_ANONYMIZE: + break; default: return false; @@ -2571,7 +2662,8 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) string const argument = to_utf8(func.argument()); // We'll set this back to false if need be. bool dispatched = true; - undo().beginUndoGroup(); + // This handles undo groups automagically + UndoGroupHelper ugh(this); switch (func.action()) { case LFUN_BUFFER_TOGGLE_READ_ONLY: @@ -2581,15 +2673,17 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) dr.setMessage(log); } else - setReadonly(!isReadonly()); + setReadonly(!hasReadonlyFlag()); break; case LFUN_BUFFER_EXPORT: { - ExportStatus const status = doExport(argument, false); + string const format = (argument.empty() || argument == "default") ? + params().getDefaultOutputFormat() : argument; + ExportStatus const status = doExport(format, false); dr.setError(status != ExportSuccess); if (status != ExportSuccess) dr.setMessage(bformat(_("Error exporting to format: %1$s."), - func.argument())); + from_utf8(format))); break; } @@ -2601,14 +2695,10 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) break; } - case LFUN_BUFFER_CHKTEX: - runChktex(); - break; - case LFUN_BUFFER_EXPORT_CUSTOM: { string format_name; string command = split(argument, format_name, ' '); - Format const * format = formats.getFormat(format_name); + Format const * format = theFormats().getFormat(format_name); if (!format) { lyxerr << "Format \"" << format_name << "\" not recognized!" @@ -2694,18 +2784,16 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) } case LFUN_BRANCH_ADD: { - docstring branch_name = func.argument(); - if (branch_name.empty()) { + docstring branchnames = func.argument(); + if (branchnames.empty()) { dispatched = false; break; } BranchList & branch_list = params().branchlist(); vector const branches = - getVectorFromString(branch_name, branch_list.separator()); + getVectorFromString(branchnames, branch_list.separator()); docstring msg; - for (vector::const_iterator it = branches.begin(); - it != branches.end(); ++it) { - branch_name = *it; + for (docstring const & branch_name : branches) { Branch * branch = branch_list.find(branch_name); if (branch) { LYXERR0("Branch " << branch_name << " already exists."); @@ -2767,7 +2855,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) } case LFUN_BUFFER_VIEW_CACHE: - if (!formats.view(*this, d->preview_file_, + if (!theFormats().view(*this, d->preview_file_, d->preview_format_)) dr.setMessage(_("Error viewing the output file.")); break; @@ -2816,12 +2904,20 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) params().output_sync = !params().output_sync; break; + case LFUN_BUFFER_ANONYMIZE: { + undo().recordUndoFullBuffer(CursorData()); + CursorData cur(doc_iterator_begin(this)); + for ( ; cur ; cur.forwardPar()) + cur.paragraph().anonymize(); + dr.forceBufferUpdate(); + break; + } + default: dispatched = false; break; } dr.dispatched(dispatched); - undo().endUndoGroup(); } @@ -2849,26 +2945,24 @@ bool Buffer::isMultiLingual() const std::set Buffer::getLanguages() const { - std::set languages; - getLanguages(languages); - return languages; + std::set langs; + getLanguages(langs); + return langs; } -void Buffer::getLanguages(std::set & languages) const +void Buffer::getLanguages(std::set & langs) const { ParConstIterator end = par_iterator_end(); // add the buffer language, even if it's not actively used - languages.insert(language()); + langs.insert(language()); // iterate over the paragraphs for (ParConstIterator it = par_iterator_begin(); it != end; ++it) - it->getLanguages(languages); + it->getLanguages(langs); // also children ListOfBuffers clist = getDescendents(); - ListOfBuffers::const_iterator cit = clist.begin(); - ListOfBuffers::const_iterator const cen = clist.end(); - for (; cit != cen; ++cit) - (*cit)->getLanguages(languages); + for (auto const & cit : clist) + cit->getLanguages(langs); } @@ -2935,29 +3029,19 @@ bool Buffer::isClean() const } -bool Buffer::isExternallyModified(CheckMethod method) const +bool Buffer::isChecksumModified() const { LASSERT(d->filename.exists(), return false); - // if method == timestamp, check timestamp before checksum - return (method == checksum_method - || d->timestamp_ != d->filename.lastModified()) - && d->checksum_ != d->filename.checksum(); + return d->checksum_ != d->filename.checksum(); } void Buffer::saveCheckSum() const { FileName const & file = d->filename; - file.refresh(); - if (file.exists()) { - d->timestamp_ = file.lastModified(); - d->checksum_ = file.checksum(); - } else { - // in the case of save to a new file. - d->timestamp_ = 0; - d->checksum_ = 0; - } + d->checksum_ = file.exists() ? file.checksum() + : 0; // in the case of save to a new file. } @@ -2971,6 +3055,7 @@ void Buffer::markClean() const // autosave d->bak_clean = true; d->undo_.markDirty(); + clearExternalModification(); } @@ -3011,11 +3096,8 @@ void Buffer::markDirty() } d->bak_clean = false; - DepClean::iterator it = d->dep_clean.begin(); - DepClean::const_iterator const end = d->dep_clean.end(); - - for (; it != end; ++it) - it->second = false; + for (auto & depit : d->dep_clean) + depit.second = false; } @@ -3060,6 +3142,131 @@ DocFileName Buffer::getReferencedFileName(string const & fn) const } +string const Buffer::prepareFileNameForLaTeX(string const & name, + string const & ext, bool nice) const +{ + string const fname = makeAbsPath(name, filePath()).absFileName(); + if (FileName::isAbsolute(name) || !FileName(fname + ext).isReadableFile()) + return name; + if (!nice) + return fname; + + // FIXME UNICODE + return to_utf8(makeRelPath(from_utf8(fname), + from_utf8(masterBuffer()->filePath()))); +} + + +vector const Buffer::prepareBibFilePaths(OutputParams const & runparams, + FileNamePairList const bibfilelist, + bool const add_extension) const +{ + // If we are processing the LaTeX file in a temp directory then + // copy the .bib databases to this temp directory, mangling their + // names in the process. Store this mangled name in the list of + // all databases. + // (We need to do all this because BibTeX *really*, *really* + // can't handle "files with spaces" and Windows users tend to + // use such filenames.) + // Otherwise, store the (maybe absolute) path to the original, + // unmangled database name. + + vector res; + + // determine the export format + string const tex_format = flavor2format(runparams.flavor); + + // check for spaces in paths + bool found_space = false; + + for (auto const & bit : bibfilelist) { + string utf8input = to_utf8(bit.first); + string database = + prepareFileNameForLaTeX(utf8input, ".bib", runparams.nice); + FileName try_in_file = + makeAbsPath(database + ".bib", filePath()); + bool not_from_texmf = try_in_file.isReadableFile(); + // If the file has not been found, try with the real file name + // (it might come from a child in a sub-directory) + if (!not_from_texmf) { + try_in_file = bit.second; + if (try_in_file.isReadableFile()) { + // Check if the file is in texmf + FileName kpsefile(findtexfile(changeExtension(utf8input, "bib"), "bib", true)); + not_from_texmf = kpsefile.empty() + || kpsefile.absFileName() != try_in_file.absFileName(); + if (not_from_texmf) + // If this exists, make path relative to the master + // FIXME Unicode + database = + removeExtension(prepareFileNameForLaTeX( + to_utf8(makeRelPath(from_utf8(try_in_file.absFileName()), + from_utf8(filePath()))), + ".bib", runparams.nice)); + } + } + + if (!runparams.inComment && !runparams.dryrun && !runparams.nice && + not_from_texmf) { + // mangledFileName() needs the extension + DocFileName const in_file = DocFileName(try_in_file); + database = removeExtension(in_file.mangledFileName()); + FileName const out_file = makeAbsPath(database + ".bib", + masterBuffer()->temppath()); + bool const success = in_file.copyTo(out_file); + if (!success) { + LYXERR0("Failed to copy '" << in_file + << "' to '" << out_file << "'"); + } + } else if (!runparams.inComment && runparams.nice && not_from_texmf) { + runparams.exportdata->addExternalFile(tex_format, try_in_file, database + ".bib"); + if (!isValidLaTeXFileName(database)) { + frontend::Alert::warning(_("Invalid filename"), + _("The following filename will cause troubles " + "when running the exported file through LaTeX: ") + + from_utf8(database)); + } + if (!isValidDVIFileName(database)) { + frontend::Alert::warning(_("Problematic filename for DVI"), + _("The following filename can cause troubles " + "when running the exported file through LaTeX " + "and opening the resulting DVI: ") + + from_utf8(database), true); + } + } + + if (add_extension) + database += ".bib"; + + // FIXME UNICODE + docstring const path = from_utf8(latex_path(database)); + + if (contains(path, ' ')) + found_space = true; + + if (find(res.begin(), res.end(), path) == res.end()) + res.push_back(path); + } + + // Check if there are spaces in the path and warn BibTeX users, if so. + // (biber can cope with such paths) + if (!prefixIs(runparams.bibtex_command, "biber")) { + // Post this warning only once. + static bool warned_about_spaces = false; + if (!warned_about_spaces && + runparams.nice && found_space) { + warned_about_spaces = true; + Alert::warning(_("Export Warning!"), + _("There are spaces in the paths to your BibTeX databases.\n" + "BibTeX will be unable to find them.")); + } + } + + return res; +} + + + string Buffer::layoutPos() const { return d->layout_position; @@ -3083,12 +3290,18 @@ void Buffer::setLayoutPos(string const & path) } -bool Buffer::isReadonly() const +bool Buffer::hasReadonlyFlag() const { return d->read_only; } +bool Buffer::isReadonly() const +{ + return hasReadonlyFlag() || notifiesExternalModification(); +} + + void Buffer::setParent(Buffer const * buffer) { // Avoids recursive include. @@ -3148,10 +3361,8 @@ bool Buffer::hasChildren() const void Buffer::collectChildren(ListOfBuffers & clist, bool grand_children) const { // loop over children - Impl::BufferPositionMap::iterator it = d->children_positions.begin(); - Impl::BufferPositionMap::iterator end = d->children_positions.end(); - for (; it != end; ++it) { - Buffer * child = const_cast(it->first); + for (auto const & p : d->children_positions) { + Buffer * child = const_cast(p.first); // No duplicates ListOfBuffers::const_iterator bit = find(clist.begin(), clist.end(), child); if (bit != clist.end()) @@ -3348,17 +3559,15 @@ void Buffer::Impl::updateMacros(DocIterator & it, DocIterator & scope) while (it.pit() <= lastpit) { Paragraph & par = it.paragraph(); + // FIXME Can this be done with the new-style iterators? // iterate over the insets of the current paragraph - InsetList const & insets = par.insetList(); - InsetList::const_iterator iit = insets.begin(); - InsetList::const_iterator end = insets.end(); - for (; iit != end; ++iit) { - it.pos() = iit->pos; + for (auto const & insit : par.insetList()) { + it.pos() = insit.pos; // is it a nested text inset? - if (iit->inset->asInsetText()) { + if (insit.inset->asInsetText()) { // Inset needs its own scope? - InsetText const * itext = iit->inset->asInsetText(); + InsetText const * itext = insit.inset->asInsetText(); bool newScope = itext->isMacroScope(); // scope which ends just behind the inset @@ -3366,14 +3575,14 @@ void Buffer::Impl::updateMacros(DocIterator & it, DocIterator & scope) ++insetScope.pos(); // collect macros in inset - it.push_back(CursorSlice(*iit->inset)); + it.push_back(CursorSlice(*insit.inset)); updateMacros(it, newScope ? insetScope : scope); it.pop_back(); continue; } - if (iit->inset->asInsetTabular()) { - CursorSlice slice(*iit->inset); + if (insit.inset->asInsetTabular()) { + CursorSlice slice(*insit.inset); size_t const numcells = slice.nargs(); for (; slice.idx() < numcells; slice.forwardIdx()) { it.push_back(slice); @@ -3384,12 +3593,12 @@ void Buffer::Impl::updateMacros(DocIterator & it, DocIterator & scope) } // is it an external file? - if (iit->inset->lyxCode() == INCLUDE_CODE) { + if (insit.inset->lyxCode() == INCLUDE_CODE) { // get buffer of external file - InsetInclude const & inset = - static_cast(*iit->inset); + InsetInclude const & incinset = + static_cast(*insit.inset); macro_lock = true; - Buffer * child = inset.getChildBuffer(); + Buffer * child = incinset.getChildBuffer(); macro_lock = false; if (!child) continue; @@ -3405,19 +3614,19 @@ void Buffer::Impl::updateMacros(DocIterator & it, DocIterator & scope) continue; } - InsetMath * im = iit->inset->asInsetMath(); + InsetMath * im = insit.inset->asInsetMath(); if (doing_export && im) { InsetMathHull * hull = im->asHullInset(); if (hull) hull->recordLocation(it); } - if (iit->inset->lyxCode() != MATHMACRO_CODE) + if (insit.inset->lyxCode() != MATHMACRO_CODE) continue; // get macro data - MathMacroTemplate & macroTemplate = - *iit->inset->asInsetMath()->asMacroTemplate(); + InsetMathMacroTemplate & macroTemplate = + *insit.inset->asInsetMath()->asMacroTemplate(); MacroContext mc(owner_, it); macroTemplate.updateToContext(mc); @@ -3513,7 +3722,7 @@ void Buffer::updateMacroInstances(UpdateType utype) const MacroContext mc = MacroContext(this, it); for (DocIterator::idx_type i = 0; i < n; ++i) { MathData & data = minset->cell(i); - data.updateMacros(0, mc, utype); + data.updateMacros(0, mc, utype, 0); } } } @@ -3527,16 +3736,16 @@ void Buffer::listMacroNames(MacroNameSet & macros) const d->macro_lock = true; // loop over macro names - Impl::NamePositionScopeMacroMap::iterator nameIt = d->macros.begin(); - Impl::NamePositionScopeMacroMap::iterator nameEnd = d->macros.end(); - for (; nameIt != nameEnd; ++nameIt) - macros.insert(nameIt->first); + for (auto const & nameit : d->macros) + macros.insert(nameit.first); // loop over children - Impl::BufferPositionMap::iterator it = d->children_positions.begin(); - Impl::BufferPositionMap::iterator end = d->children_positions.end(); - for (; it != end; ++it) - it->first->listMacroNames(macros); + for (auto const & p : d->children_positions) { + Buffer * child = const_cast(p.first); + // The buffer might have been closed (see #10766). + if (theBufferList().isLoaded(child)) + child->listMacroNames(macros); + } // call parent Buffer const * const pbuf = d->parent(); @@ -3557,16 +3766,13 @@ void Buffer::listParentMacros(MacroSet & macros, LaTeXFeatures & features) const pbuf->listMacroNames(names); // resolve macros - MacroNameSet::iterator it = names.begin(); - MacroNameSet::iterator end = names.end(); - for (; it != end; ++it) { + for (auto const & mit : names) { // defined? - MacroData const * data = - pbuf->getMacro(*it, *this, false); + MacroData const * data = pbuf->getMacro(mit, *this, false); if (data) { macros.insert(data); - // we cannot access the original MathMacroTemplate anymore + // we cannot access the original InsetMathMacroTemplate anymore // here to calls validate method. So we do its work here manually. // FIXME: somehow make the template accessible here. if (data->optionals() > 0) @@ -3631,20 +3837,16 @@ void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to) reloadBibInfoCache(); // Check if the label 'from' appears more than once - BiblioInfo const & keys = masterBibInfo(); - BiblioInfo::const_iterator bit = keys.begin(); - BiblioInfo::const_iterator bend = keys.end(); vector labels; - - for (; bit != bend; ++bit) - // FIXME UNICODE - labels.push_back(bit->first); + for (auto const & bibit : masterBibInfo()) + labels.push_back(bibit.first); if (count(labels.begin(), labels.end(), from) > 1) return; string const paramName = "key"; - for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) { + InsetIterator it = inset_iterator_begin(inset()); + for (; it; ++it) { if (it->lyxCode() != CITE_CODE) continue; InsetCommand * inset = it->asInsetCommand(); @@ -3711,7 +3913,7 @@ unique_ptr Buffer::getSourceCode(odocstream & os, string const & format, // in order to know if we should output polyglossia // macros (instead of babel macros) LaTeXFeatures features(*this, params(), runparams); - params().validate(features); + validate(features); runparams.use_polyglossia = features.usePolyglossia(); // latex or literate otexstream ots(os); @@ -3759,6 +3961,7 @@ unique_ptr Buffer::getSourceCode(odocstream & os, string const & format, ots.texrow().newlines(2); if (master) runparams.is_child = true; + updateBuffer(); writeLaTeXSource(ots, string(), runparams, output); texrow = ots.releaseTexRow(); } @@ -3917,7 +4120,7 @@ int AutoSaveBuffer::generateChild() return pid; } -} // namespace anon +} // namespace FileName Buffer::getEmergencyFileName() const @@ -3952,7 +4155,7 @@ void Buffer::removeAutosaveFile() const } -void Buffer::moveAutosaveFile(support::FileName const & oldauto) const +void Buffer::moveAutosaveFile(FileName const & oldauto) const { FileName const newauto = getAutosaveFileName(); oldauto.refresh(); @@ -3965,7 +4168,7 @@ void Buffer::moveAutosaveFile(support::FileName const & oldauto) const bool Buffer::autoSave() const { Buffer const * buf = d->cloned_buffer_ ? d->cloned_buffer_ : this; - if (buf->d->bak_clean || isReadonly()) + if (buf->d->bak_clean || hasReadonlyFlag()) return true; message(_("Autosaving current document...")); @@ -3994,10 +4197,8 @@ void Buffer::setExportStatus(bool e) const { d->doing_export = e; ListOfBuffers clist = getDescendents(); - ListOfBuffers::const_iterator cit = clist.begin(); - ListOfBuffers::const_iterator const cen = clist.end(); - for (; cit != cen; ++cit) - (*cit)->d->doing_export = e; + for (auto const & bit : clist) + bit->d->doing_export = e; } @@ -4063,6 +4264,8 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir if (pos != string::npos) { dest_filename = target.substr(pos + 1, target.length() - pos - 1); format = target.substr(0, pos); + if (format == "default") + format = params().getDefaultOutputFormat(); runparams.export_folder = FileName(dest_filename).onlyPath().realPath(); FileName(dest_filename).onlyPath().createPath(); LYXERR(Debug::FILES, "format=" << format << ", dest_filename=" << dest_filename << ", export_folder=" << runparams.export_folder); @@ -4079,11 +4282,10 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir // Get shortest path to format converters.buildGraph(); Graph::EdgePath path; - for (vector::const_iterator it = backs.begin(); - it != backs.end(); ++it) { - Graph::EdgePath p = converters.getPath(*it, format); + for (string const & sit : backs) { + Graph::EdgePath p = converters.getPath(sit, format); if (!p.empty() && (path.empty() || p.size() < path.size())) { - backend_format = *it; + backend_format = sit; path = p; } } @@ -4093,15 +4295,14 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir // file (not for previewing). Alert::error(_("Couldn't export file"), bformat( _("No information for exporting the format %1$s."), - formats.prettyName(format))); + theFormats().prettyName(format))); } return ExportNoPathToFormat; } runparams.flavor = converters.getFlavor(path, this); - Graph::EdgePath::const_iterator it = path.begin(); - Graph::EdgePath::const_iterator en = path.end(); - for (; it != en; ++it) - if (theConverters().get(*it).nice()) { + runparams.hyperref_driver = converters.getHyperrefDriver(path); + for (auto const & edge : path) + if (theConverters().get(edge).nice()) { need_nice_file = true; break; } @@ -4123,57 +4324,72 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir string filename = latexName(false); filename = addName(temppath(), filename); filename = changeExtension(filename, - formats.extension(backend_format)); + theFormats().extension(backend_format)); LYXERR(Debug::FILES, "filename=" << filename); // Plain text backend if (backend_format == "text") { runparams.flavor = OutputParams::TEXT; - writePlaintextFile(*this, FileName(filename), runparams); + try { + writePlaintextFile(*this, FileName(filename), runparams); + } + catch (ConversionException const &) { return ExportCancel; } } // HTML backend else if (backend_format == "xhtml") { runparams.flavor = OutputParams::HTML; setMathFlavor(runparams); - makeLyXHTMLFile(FileName(filename), runparams); + if (makeLyXHTMLFile(FileName(filename), runparams) == ExportKilled) + return ExportKilled; } else if (backend_format == "lyx") writeFile(FileName(filename)); // Docbook backend else if (params().isDocBook()) { runparams.nice = !put_in_tempdir; - makeDocBookFile(FileName(filename), runparams); + if (makeDocBookFile(FileName(filename), runparams) == ExportKilled) + return ExportKilled; } // LaTeX backend else if (backend_format == format || need_nice_file) { runparams.nice = true; - bool const success = makeLaTeXFile(FileName(filename), string(), runparams); + ExportStatus const retval = + makeLaTeXFile(FileName(filename), string(), runparams); + if (retval == ExportKilled) + return ExportKilled; if (d->cloned_buffer_) d->cloned_buffer_->d->errorLists["Export"] = d->errorLists["Export"]; - if (!success) - return ExportError; + if (retval != ExportSuccess) + return retval; } else if (!lyxrc.tex_allows_spaces && contains(filePath(), ' ')) { Alert::error(_("File name error"), - _("The directory path to the document cannot contain spaces.")); + bformat(_("The directory path to the document\n%1$s\n" + "contains spaces, but your TeX installation does " + "not allow them."), from_ascii(filePath()))); return ExportTexPathHasSpaces; } else { runparams.nice = false; - bool const success = makeLaTeXFile( - FileName(filename), filePath(), runparams); + ExportStatus const retval = + makeLaTeXFile(FileName(filename), filePath(), runparams); + if (retval == ExportKilled) + return ExportKilled; if (d->cloned_buffer_) d->cloned_buffer_->d->errorLists["Export"] = d->errorLists["Export"]; - if (!success) + if (retval != ExportSuccess) return ExportError; } string const error_type = (format == "program") ? "Build" : params().bufferFormat(); ErrorList & error_list = d->errorLists[error_type]; - string const ext = formats.extension(format); + string const ext = theFormats().extension(format); FileName const tmp_result_file(changeExtension(filename, ext)); - bool const success = converters.convert(this, FileName(filename), - tmp_result_file, FileName(absFileName()), backend_format, format, - error_list); + Converters::RetVal const retval = + converters.convert(this, FileName(filename), tmp_result_file, + FileName(absFileName()), backend_format, format, error_list); + if (retval == Converters::KILLED) + return ExportCancel; + bool success = (retval == Converters::SUCCESS); // Emit the signal to show the error list or copy it back to the // cloned Buffer so that it can be emitted afterwards. @@ -4187,20 +4403,18 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir errors(error_type); // also to the children, in case of master-buffer-view ListOfBuffers clist = getDescendents(); - ListOfBuffers::const_iterator cit = clist.begin(); - ListOfBuffers::const_iterator const cen = clist.end(); - for (; cit != cen; ++cit) { + for (auto const & bit : clist) { if (runparams.silent) - (*cit)->d->errorLists[error_type].clear(); + bit->d->errorLists[error_type].clear(); else if (d->cloned_buffer_) { // Enable reverse search by copying back the // texrow object to the cloned buffer. // FIXME: this is not thread safe. - (*cit)->d->cloned_buffer_->d->texrow = (*cit)->d->texrow; - (*cit)->d->cloned_buffer_->d->errorLists[error_type] = - (*cit)->d->errorLists[error_type]; + bit->d->cloned_buffer_->d->texrow = bit->d->texrow; + bit->d->cloned_buffer_->d->errorLists[error_type] = + bit->d->errorLists[error_type]; } else - (*cit)->errors(error_type, true); + bit->errors(error_type, true); } } @@ -4210,8 +4424,8 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir // FIXME: There is a possibility of concurrent access to texrow // here from the main GUI thread that should be securized. d->cloned_buffer_->d->texrow = d->texrow; - string const error_type = params().bufferFormat(); - d->cloned_buffer_->d->errorLists[error_type] = d->errorLists[error_type]; + string const err_type = params().bufferFormat(); + d->cloned_buffer_->d->errorLists[error_type] = d->errorLists[err_type]; } @@ -4234,11 +4448,13 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir : force_overwrite == ALL_FILES; CopyStatus status = use_force ? FORCE : SUCCESS; - vector::const_iterator it = files.begin(); - vector::const_iterator const en = files.end(); - for (; it != en && status != CANCEL; ++it) { - string const fmt = formats.getFormatFromFile(it->sourceName); - string fixedName = it->exportName; + for (ExportedFile const & exp : files) { + if (status == CANCEL) { + message(_("Document export cancelled.")); + return ExportCancel; + } + string const fmt = theFormats().getFormatFromFile(exp.sourceName); + string fixedName = exp.exportName; if (!runparams.export_folder.empty()) { // Relative pathnames starting with ../ will be sanitized // if exporting to a different folder @@ -4247,16 +4463,12 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir } FileName fixedFileName = makeAbsPath(fixedName, dest); fixedFileName.onlyPath().createPath(); - status = copyFile(fmt, it->sourceName, + status = copyFile(fmt, exp.sourceName, fixedFileName, - it->exportName, status == FORCE, + exp.exportName, status == FORCE, runparams.export_folder.empty()); } - if (status == CANCEL) { - message(_("Document export cancelled.")); - return ExportCancel; - } if (tmp_result_file.exists()) { // Finally copy the main file @@ -4273,13 +4485,13 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir } else { message(bformat(_("Document exported as %1$s " "to file `%2$s'"), - formats.prettyName(format), + theFormats().prettyName(format), makeDisplayPath(result_file))); } } else { // This must be a dummy converter like fax (bug 1888) message(bformat(_("Document exported as %1$s"), - formats.prettyName(format))); + theFormats().prettyName(format))); } return success ? ExportSuccess : ExportConverterError; @@ -4318,7 +4530,7 @@ Buffer::ExportStatus Buffer::preview(string const & format, bool includeall) con return status; if (previewFile.exists()) - return formats.view(*this, previewFile, format) ? + return theFormats().view(*this, previewFile, format) ? PreviewSuccess : PreviewError; // Successful export but no output file? @@ -4360,7 +4572,7 @@ Buffer::ReadStatus Buffer::loadEmergency() ReadStatus const ret_llf = loadThisLyXFile(emergencyFile); bool const success = (ret_llf == ReadSuccess); if (success) { - if (isReadonly()) { + if (hasReadonlyFlag()) { Alert::warning(_("File is read-only"), bformat(_("An emergency file is successfully loaded, " "but the original file %1$s is marked read-only. " @@ -4423,7 +4635,7 @@ Buffer::ReadStatus Buffer::loadAutosave() ReadStatus const ret_llf = loadThisLyXFile(autosaveFile); // the file is not saved if we load the autosave file. if (ret_llf == ReadSuccess) { - if (isReadonly()) { + if (hasReadonlyFlag()) { Alert::warning(_("File is read-only"), bformat(_("A backup file is successfully loaded, " "but the original file %1$s is marked read-only. " @@ -4476,66 +4688,37 @@ Buffer::ReadStatus Buffer::loadThisLyXFile(FileName const & fn) void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const { - TeXErrors::Errors::const_iterator it = terr.begin(); - TeXErrors::Errors::const_iterator end = terr.end(); - ListOfBuffers clist = getDescendents(); - ListOfBuffers::const_iterator cen = clist.end(); - - for (; it != end; ++it) { - int id_start = -1; - int pos_start = -1; - int errorRow = it->error_in_line; + for (auto const & err : terr) { + TexRow::TextEntry start = TexRow::text_none, end = TexRow::text_none; + int errorRow = err.error_in_line; Buffer const * buf = 0; Impl const * p = d; - if (it->child_name.empty()) - p->texrow.getIdFromRow(errorRow, id_start, pos_start); + if (err.child_name.empty()) + tie(start, end) = p->texrow.getEntriesFromRow(errorRow); else { // The error occurred in a child - ListOfBuffers::const_iterator cit = clist.begin(); - for (; cit != cen; ++cit) { + for (Buffer const * child : getDescendents()) { string const child_name = - DocFileName(changeExtension( - (*cit)->absFileName(), "tex")). - mangledFileName(); - if (it->child_name != child_name) + DocFileName(changeExtension(child->absFileName(), "tex")). + mangledFileName(); + if (err.child_name != child_name) continue; - (*cit)->d->texrow.getIdFromRow(errorRow, - id_start, pos_start); - if (id_start != -1) { + tie(start, end) = child->d->texrow.getEntriesFromRow(errorRow); + if (!TexRow::isNone(start)) { buf = d->cloned_buffer_ - ? (*cit)->d->cloned_buffer_->d->owner_ - : (*cit)->d->owner_; - p = (*cit)->d; + ? child->d->cloned_buffer_->d->owner_ + : child->d->owner_; + p = child->d; break; } } } - int id_end = -1; - int pos_end = -1; - bool found; - do { - ++errorRow; - found = p->texrow.getIdFromRow(errorRow, id_end, pos_end); - } while (found && id_start == id_end && pos_start == pos_end); - - if (id_start != id_end) { - // Next registered position is outside the inset where - // the error occurred, so signal end-of-paragraph - pos_end = 0; - } - - errorList.push_back(ErrorItem(it->error_desc, - it->error_text, id_start, pos_start, pos_end, buf)); + errorList.push_back(ErrorItem(err.error_desc, err.error_text, + start, end, buf)); } } -void Buffer::setBuffersForInsets() const -{ - inset().setBuffer(const_cast(*this)); -} - - void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const { LBUFERR(!text().paragraphs().empty()); @@ -4544,10 +4727,17 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const Buffer const * const master = masterBuffer(); DocumentClass const & textclass = master->params().documentClass(); - // do this only if we are the top-level Buffer - if (master == this) { + FileNamePairList old_bibfiles; + // Do this only if we are the top-level Buffer. We also need to account + // for the case of a previewed child with ignored parent here. + if (master == this && !d->ignore_parent) { textclass.counters().reset(from_ascii("bibitem")); reloadBibInfoCache(); + // we will re-read this cache as we go through, but we need + // to know whether it's changed to know whether we need to + // update the bibinfo cache. + old_bibfiles = d->bibfiles_cache_; + d->bibfiles_cache_.clear(); } // keep the buffers to be children in this set. If the call from the @@ -4563,7 +4753,7 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const // not updated during the updateBuffer call and TocModel::toc_ is invalid // (bug 5699). The same happens if the master buffer is open in a different // window. This test catches both possibilities. - // See: http://marc.info/?l=lyx-devel&m=138590578911716&w=2 + // See: https://marc.info/?l=lyx-devel&m=138590578911716&w=2 // There remains a problem here: If there is another child open in yet a third // window, that TOC is not updated. So some more general solution is needed at // some point. @@ -4593,11 +4783,45 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const ParIterator parit = cbuf.par_iterator_begin(); updateBuffer(parit, utype); + // If this document has siblings, then update the TocBackend later. The + // reason is to ensure that later siblings are up to date when e.g. the + // broken or not status of references is computed. The update is called + // in InsetInclude::addToToc. if (master != this) - // TocBackend update will be done later. return; - d->bibinfo_cache_valid_ = true; + // if the bibfiles changed, the cache of bibinfo is invalid + FileNamePairList new_bibfiles = d->bibfiles_cache_; + // this is a trick to determine whether the two vectors have + // the same elements. + sort(new_bibfiles.begin(), new_bibfiles.end()); + sort(old_bibfiles.begin(), old_bibfiles.end()); + if (old_bibfiles != new_bibfiles) { + LYXERR(Debug::FILES, "Reloading bibinfo cache."); + invalidateBibinfoCache(); + reloadBibInfoCache(); + // We relied upon the bibinfo cache when recalculating labels. But that + // cache was invalid, although we didn't find that out until now. So we + // have to do it all again. + // That said, the only thing we really need to do is update the citation + // labels. Nothing else will have changed. So we could create a new + // UpdateType that would signal that fact, if we needed to do so. + parit = cbuf.par_iterator_begin(); + // we will be re-doing the counters and references and such. + textclass.counters().reset(); + clearReferenceCache(); + // we should not need to do this again? + // updateMacros(); + setChangesPresent(false); + updateBuffer(parit, utype); + // this will already have been done by reloadBibInfoCache(); + // d->bibinfo_cache_valid_ = true; + } + else { + LYXERR(Debug::FILES, "Bibfiles unchanged."); + // this is also set to true on the other path, by reloadBibInfoCache. + d->bibinfo_cache_valid_ = true; + } d->cite_labels_valid_ = true; /// FIXME: Perf cbuf.tocBackend().update(true, utype); @@ -4673,7 +4897,7 @@ static bool needEnumCounterReset(ParIterator const & it) --prev_it.top().pit(); Paragraph const & prev_par = *prev_it; if (prev_par.getDepth() <= cur_depth) - return prev_par.layout().labeltype != LABEL_ENUMERATE; + return prev_par.layout().name() != par.layout().name(); } // start of nested inset: reset return true; @@ -4744,8 +4968,10 @@ void Buffer::Impl::setLabel(ParIterator & it, UpdateType utype) const switch (par.itemdepth) { case 2: enumcounter += 'i'; + // fall through case 1: enumcounter += 'i'; + // fall through case 0: enumcounter += 'i'; break; @@ -4757,9 +4983,14 @@ void Buffer::Impl::setLabel(ParIterator & it, UpdateType utype) const break; } - // Maybe we have to reset the enumeration counter. - if (needEnumCounterReset(it)) - counters.reset(enumcounter); + if (needEnumCounterReset(it)) { + // Increase the master counter? + if (layout.stepmastercounter) + counters.stepMaster(enumcounter, utype); + // Maybe we have to reset the enumeration counter. + if (!layout.resumecounter) + counters.reset(enumcounter); + } counters.step(enumcounter, utype); string const & lang = par.getParLanguage(bp)->code(); @@ -4838,7 +5069,7 @@ void Buffer::updateBuffer(ParIterator & parit, UpdateType utype) const * non-const. This would however be costly in * terms of code duplication. */ - const_cast(this)->undo().recordUndo(CursorData(parit)); + CursorData(parit).recordUndo(); parit->params().depth(maxdepth); } maxdepth = parit->getMaxDepthAfter(); @@ -4854,15 +5085,13 @@ void Buffer::updateBuffer(ParIterator & parit, UpdateType utype) const // set the counter for this paragraph d->setLabel(parit, utype); - // update change-tracking flag + // update change-tracking flag parit->addChangesToBuffer(*this); // now the insets - InsetList::const_iterator iit = parit->insetList().begin(); - InsetList::const_iterator end = parit->insetList().end(); - for (; iit != end; ++iit) { - parit.pos() = iit->pos; - iit->inset->updateBuffer(parit, utype); + for (auto const & insit : parit->insetList()) { + parit.pos() = insit.pos; + insit.inset->updateBuffer(parit, utype); } } } @@ -4980,7 +5209,7 @@ int Buffer::charCount(bool with_blanks) const Buffer::ReadStatus Buffer::reload() { setBusy(true); - // c.f. bug http://www.lyx.org/trac/ticket/6587 + // c.f. bug https://www.lyx.org/trac/ticket/6587 removeAutosaveFile(); // e.g., read-only status could have changed due to version control d->filename.refresh(); @@ -5046,11 +5275,9 @@ bool Buffer::saveAs(FileName const & fn) void Buffer::checkChildBuffers() { - Impl::BufferPositionMap::iterator it = d->children_positions.begin(); - Impl::BufferPositionMap::iterator const en = d->children_positions.end(); - for (; it != en; ++it) { - DocIterator dit = it->second; - Buffer * cbuf = const_cast(it->first); + for (auto const & bit : d->children_positions) { + DocIterator dit = bit.second; + Buffer * cbuf = const_cast(bit.first); if (!cbuf || !theBufferList().isLoaded(cbuf)) continue; Inset * inset = dit.nextInset(); @@ -5145,5 +5372,59 @@ void Buffer::updateChangesPresent() const } +void Buffer::Impl::refreshFileMonitor() +{ + if (file_monitor_ && file_monitor_->filename() == filename.absFileName()) { + file_monitor_->refresh(); + return; + } + + // The previous file monitor is invalid + // This also destroys the previous file monitor and all its connections + file_monitor_ = FileSystemWatcher::monitor(filename); + // file_monitor_ will be destroyed with *this, so it is not going to call a + // destroyed object method. + file_monitor_->connect([this](bool exists) { + fileExternallyModified(exists); + }); +} + + +void Buffer::Impl::fileExternallyModified(bool const exists) +{ + // ignore notifications after our own saving operations + if (checksum_ == filename.checksum()) { + LYXERR(Debug::FILES, "External modification but " + "checksum unchanged: " << filename); + return; + } + // If the file has been deleted, only mark the file as dirty since it is + // pointless to prompt for reloading. If later a file is moved into this + // location, then the externally modified warning will appear then. + if (exists) + externally_modified_ = true; + // Update external modification notification. + // Dirty buffers must be visible at all times. + if (wa_ && wa_->unhide(owner_)) + wa_->updateTitles(); + else + // Unable to unhide the buffer (e.g. no GUI or not current View) + lyx_clean = true; +} + + +bool Buffer::notifiesExternalModification() const +{ + return d->externally_modified_; +} + + +void Buffer::clearExternalModification() const +{ + d->externally_modified_ = false; + if (d->wa_) + d->wa_->updateTitles(); +} + } // namespace lyx