X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBuffer.cpp;h=7adab9fabb2e63f99a98985ff2778cdd9372433a;hb=6b095e2b7107110bec593edd02bbdcf25adae76a;hp=b8e233a78120e140f64fc688bcf3c403bc9649e4;hpb=2694fde47dfd61bdcdcb32d086f1aa85ba453784;p=lyx.git diff --git a/src/Buffer.cpp b/src/Buffer.cpp index b8e233a781..74d5ac8deb 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -55,8 +55,10 @@ #include "ParagraphParameters.h" #include "ParIterator.h" #include "PDFOptions.h" +#include "Session.h" #include "SpellChecker.h" #include "sgml.h" +#include "texstream.h" #include "TexRow.h" #include "Text.h" #include "TextClass.h" @@ -75,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" @@ -89,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" @@ -107,12 +111,12 @@ #include "support/types.h" #include "support/bind.h" -#include "support/shared_ptr.h" #include #include #include #include +#include #include #include #include @@ -133,15 +137,7 @@ int const LYX_FORMAT = LYX_FORMAT_LYX; typedef map DepClean; typedef map > RefCache; -void showPrintError(string const & name) -{ - docstring str = bformat(_("Could not print the document %1$s.\n" - "Check that your printer is set up correctly."), - makeDisplayPath(name, 50)); - Alert::error(_("Print document failed"), str); -} - -} // namespace anon +} // namespace // A storehouse for the cloned buffers. @@ -176,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_; @@ -212,6 +208,13 @@ public: */ bool file_fully_loaded; + /// original format of loaded file + int file_format; + + /// if the file was originally loaded from an older format, do + /// we need to back it up still? + bool need_format_backup; + /// Ignore the parent (e.g. when exporting a child standalone)? bool ignore_parent; @@ -219,7 +222,13 @@ public: mutable TocBackend toc_backend; /// macro tables - typedef pair ScopeMacro; + struct ScopeMacro { + ScopeMacro() {} + ScopeMacro(DocIterator const & s, MacroData const & m) + : scope(s), macro(m) {} + DocIterator scope; + MacroData macro; + }; typedef map PositionScopeMacroMap; typedef map NamePositionScopeMacroMap; /// map from the macro name to the position map, @@ -230,19 +239,34 @@ public: /// positions of child buffers in the buffer typedef map BufferPositionMap; - typedef pair ScopeBuffer; + struct ScopeBuffer { + ScopeBuffer() : buffer(0) {} + ScopeBuffer(DocIterator const & s, Buffer const * b) + : scope(s), buffer(b) {} + DocIterator scope; + Buffer const * buffer; + }; typedef map PositionScopeBufferMap; /// position of children buffers in this buffer BufferPositionMap children_positions; /// map from children inclusion positions to their scope and their buffer PositionScopeBufferMap position_to_children; + /// Contains the old buffer filePath() while saving-as, or the + /// directory where the document was last saved while loading. + string old_position; + + /** Keeps track of the path of local layout files. + * If possible, it is always relative to the buffer path. + * Empty for layouts in system or user directory. + */ + string layout_position; + /// 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_; /// @@ -255,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 @@ -265,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 @@ -274,6 +296,15 @@ public: /// to be updated. mutable bool cite_labels_valid_; + /// These two hold the file name and format, written to by + /// Buffer::preview and read from by LFUN_BUFFER_VIEW_CACHE. + FileName preview_file_; + string preview_format_; + /// If there was an error when previewing, on the next preview we do + /// a fresh compile (e.g. in case the user installed a package that + /// was missing). + bool preview_error_; + mutable RefCache ref_cache_; /// our Text that should be wrapped in an InsetText @@ -310,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_ @@ -340,6 +369,19 @@ public: + (with_blanks ? blank_count_ : 0); } + // does the buffer contain tracked changes? (if so, we automatically + // 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; @@ -348,6 +390,7 @@ private: int char_count_; int blank_count_; + FileMonitorPtr file_monitor_; }; @@ -355,7 +398,7 @@ private: static FileName createBufferTmpDir() { // FIXME This would be the ideal application for a TempDir class (like - // TempFile but for directories) + // TempFile but for directories) string counter; { static int count; @@ -382,13 +425,16 @@ Buffer::Impl::Impl(Buffer * owner, FileName const & file, bool readonly_, Buffer const * cloned_buffer) : owner_(owner), lyx_clean(true), bak_clean(true), unnamed(false), internal_buffer(false), read_only(readonly_), filename(file), - file_fully_loaded(false), ignore_parent(false), toc_backend(owner), - macro_lock(false), timestamp_(0), checksum_(0), wa_(0), gui_(0), - undo_(*owner), bibinfo_cache_valid_(false), bibfile_cache_valid_(false), - cite_labels_valid_(false), preview_loader_(0), - cloned_buffer_(cloned_buffer), clone_list_(0), - doing_export(false), parent_buffer(0) -{ + file_fully_loaded(false), file_format(LYX_FORMAT), need_format_backup(false), + ignore_parent(false), toc_backend(owner), macro_lock(false), + checksum_(0), wa_(0), gui_(0), undo_(*owner), bibinfo_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), + 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_); @@ -402,11 +448,15 @@ 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; internal_buffer = cloned_buffer_->d->internal_buffer; + layout_position = cloned_buffer_->d->layout_position; + preview_file_ = cloned_buffer_->d->preview_file_; + preview_format_ = cloned_buffer_->d->preview_format_; + preview_error_ = cloned_buffer_->d->preview_error_; + tracked_changes_present_ = cloned_buffer_->d->tracked_changes_present_; } @@ -425,7 +475,6 @@ Buffer::Buffer(string const & file, bool readonly, Buffer const * cloned_buffer) it.paragraph().setId(cloned_it.paragraph().id()); } else d->inset = new InsetText(this); - d->inset->setAutoBreakRows(true); d->inset->getText(0)->setMacrocontextPosition(par_iterator_begin()); } @@ -450,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; } @@ -479,17 +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)) - theBufferList().releaseChild(this, 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); } @@ -499,8 +552,7 @@ Buffer::~Buffer() d->position_to_children.clear(); if (!d->temppath.destroyDirectory()) { - Alert::warning(_("Could not remove temporary directory"), - bformat(_("Could not remove the temporary directory %1$s"), + LYXERR0(bformat(_("Could not remove the temporary directory %1$s"), from_utf8(d->temppath.absFileName()))); } removePreviews(); @@ -553,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.second); + Buffer * child = const_cast(p.second.buffer); child->cloneWithChildren(bufmap, clones); BufferMap::iterator const bit = bufmap.find(child); @@ -651,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; } @@ -662,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; } @@ -771,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; @@ -819,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()); @@ -866,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]; @@ -895,29 +948,28 @@ int Buffer::readHeader(Lexer & lex) LYXERR(Debug::PARSER, "Handling document header token: `" << token << '\''); - string unknown = params().readToken(lex, token, d->filename.onlyPath()); - if (!unknown.empty()) { - if (unknown[0] != '\\' && token == "\\textclass") { - Alert::warning(_("Unknown document class"), - bformat(_("Using the default document class, because the " - "class %1$s is unknown."), from_utf8(unknown))); + string const result = + params().readToken(lex, token, d->filename.onlyPath()); + if (!result.empty()) { + if (token == "\\textclass") { + d->layout_position = result; } else { ++unknown_tokens; docstring const s = bformat(_("Unknown token: " "%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; @@ -937,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); @@ -997,15 +1048,18 @@ bool Buffer::readDocument(Lexer & lex) params().indiceslist().addDefault(B_("Index")); // read main text + if (FileName::isAbsolute(params().origin)) + d->old_position = params().origin; + else + d->old_position = filePath(); bool const res = text().read(lex, errorList, d->inset); + d->old_position.clear(); // 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(); @@ -1016,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(); @@ -1038,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; } @@ -1049,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; } @@ -1105,10 +1161,15 @@ Buffer::ReadStatus Buffer::readFile(FileName const & fn) if (file_format != LYX_FORMAT) { FileName tmpFile; - ReadStatus const ret_clf = convertLyXFormat(fn, tmpFile, file_format); + ReadStatus ret_clf = convertLyXFormat(fn, tmpFile, file_format); if (ret_clf != ReadSuccess) return ret_clf; - return readFile(tmpFile); + ret_clf = readFile(tmpFile); + if (ret_clf == ReadSuccess) { + d->file_format = file_format; + d->need_format_backup = true; + } + return ret_clf; } // FIXME: InsetInfo needs to know whether the file is under VCS @@ -1125,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; } @@ -1143,6 +1204,12 @@ void Buffer::setFullyLoaded(bool value) } +bool Buffer::lastPreviewError() const +{ + return d->preview_error_; +} + + PreviewLoader * Buffer::loader() const { if (!isExporting() && lyxrc.preview == LyXRC::PREVIEW_OFF) @@ -1233,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(); @@ -1261,6 +1328,41 @@ Buffer::ReadStatus Buffer::convertLyXFormat(FileName const & fn, } +FileName Buffer::getBackupName() const { + FileName const & fn = fileName(); + string const fname = fn.onlyFileNameWithoutExt(); + string const fext = fn.extension() + "~"; + string const fpath = lyxrc.backupdir_path.empty() ? + fn.onlyPath().absFileName() : + lyxrc.backupdir_path; + string const fform = convert(d->file_format); + string const backname = fname + "-lyxformat-" + fform; + FileName backup(addName(fpath, addExtension(backname, fext))); + + // limit recursion, just in case + int v = 1; + unsigned long orig_checksum = 0; + while (backup.exists() && v < 100) { + if (orig_checksum == 0) + orig_checksum = fn.checksum(); + unsigned long new_checksum = backup.checksum(); + if (orig_checksum == new_checksum) { + LYXERR(Debug::FILES, "Not backing up " << fn << + "since " << backup << "has the same checksum."); + // a bit of a hack, but we have to check this anyway + // below, and setting this is simpler than introducing + // a special boolean for this purpose. + v = 1000; + break; + } + string const newbackname = backname + "-" + convert(v); + backup.set(addName(fpath, addExtension(newbackname, fext))); + v++; + } + return v < 100 ? backup : FileName(); +} + + // Should probably be moved to somewhere else: BufferView? GuiView? bool Buffer::save() const { @@ -1279,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); @@ -1294,16 +1396,19 @@ bool Buffer::save() const // if the file does not yet exist, none of the backup activity // that follows is necessary - if (!fileName().exists()) - return writeFile(fileName()); + if (!fileName().exists()) { + if (!writeFile(fileName())) + 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 // way we preserve the original file if something goes wrong. string const justname = fileName().onlyFileNameWithoutExt(); - boost::scoped_ptr - tempfile(new TempFile(fileName().onlyPath(), - justname + "-XXXXXX.lyx")); + auto tempfile = make_unique(fileName().onlyPath(), + justname + "-XXXXXX.lyx"); bool const symlink = fileName().isSymLink(); if (!symlink) tempfile->setAutoRemove(false); @@ -1316,13 +1421,22 @@ bool Buffer::save() const // we will set this to false if we fail bool made_backup = true; - FileName backupName(absFileName() + '~'); - if (lyxrc.make_backup) { - if (!lyxrc.backupdir_path.empty()) { - string const mangledName = - subst(subst(backupName.absFileName(), '/', '!'), ':', '!'); - backupName = FileName(addName(lyxrc.backupdir_path, - mangledName)); + FileName backupName; + bool const needBackup = lyxrc.make_backup || d->need_format_backup; + if (needBackup) { + if (d->need_format_backup) + backupName = getBackupName(); + + // If we for some reason failed to find a backup name in case of + // a format change, this will still set one. It's the best we can + // do in this case. + if (backupName.empty()) { + backupName.set(fileName().absFileName() + "~"); + if (!lyxrc.backupdir_path.empty()) { + string const mangledName = + subst(subst(backupName.absFileName(), '/', '!'), ':', '!'); + backupName.set(addName(lyxrc.backupdir_path, mangledName)); + } } LYXERR(Debug::FILES, "Backing up original file to " << @@ -1339,6 +1453,10 @@ bool Buffer::save() const "Please check whether the directory exists and is writable."), from_utf8(backupName.absFileName()))); //LYXERR(Debug::DEBUG, "Fs error: " << fe.what()); + } else if (d->need_format_backup) { + // the original file has been backed up successfully, so we + // will not need to do that again + d->need_format_backup = false; } } @@ -1355,22 +1473,25 @@ bool Buffer::save() const // time stamp is invalidated by copying/moving saveCheckSum(); markClean(); + if (d->file_format != LYX_FORMAT) + // the file associated with this buffer is now in the current format + d->file_format = LYX_FORMAT; return true; } // else we saved the file, but failed to move it to the right location. - if (lyxrc.make_backup && made_backup && !symlink) { - // the original file was moved to filename.lyx~, so it will look + if (needBackup && made_backup && !symlink) { + // the original file was moved to some new location, so it will look // to the user as if it was deleted. (see bug #9234.) we could try // to restore it, but that would basically mean trying to do again // what we just failed to do. better to leave things as they are. Alert::error(_("Write failure"), - bformat(_("The file has successfully been saved as:\n %1$s.\n" - "But LyX could not move it to:\n %2$s.\n" - "Your original file has been backed up to:\n %3$s"), - from_utf8(savefile.absFileName()), - from_utf8(fileName().absFileName()), - from_utf8(backupName.absFileName()))); + bformat(_("The file has successfully been saved as:\n %1$s.\n" + "But LyX could not move it to:\n %2$s.\n" + "Your original file has been backed up to:\n %3$s"), + from_utf8(savefile.absFileName()), + from_utf8(fileName().absFileName()), + from_utf8(backupName.absFileName()))); } else { // either we did not try to make a backup, or else we tried and failed, // or else the original file was a symlink, in which case it was copied, @@ -1471,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; @@ -1491,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(); @@ -1509,7 +1628,7 @@ bool Buffer::write(ostream & ofs) const // now write out the buffer parameters. ofs << "\\begin_header\n"; - params().writeFile(ofs); + params().writeFile(ofs, this); ofs << "\\end_header\n"; // write the text @@ -1541,17 +1660,19 @@ 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 { OutputParams runparams = runparams_in; - // This is necessary for LuaTeX/XeTeX with tex fonts. - // See FIXME in BufferParams::encoding() - if (runparams.isFullUnicode()) - runparams.encoding = encodings.fromLyXName("utf8-plain"); + // XeTeX with TeX fonts is only safe with ASCII encoding (see also #9740), + // Check here, because the "flavor" is not known in BufferParams::encoding() + // (power users can override this safety measure selecting "utf8-plain"). + if (!params().useNonTeXFonts && (runparams.flavor == OutputParams::XETEX) + && (runparams.encoding->name() != "utf8-plain")) + runparams.encoding = encodings.fromLyXName("ascii"); string const encoding = runparams.encoding->iconvName(); LYXERR(Debug::LATEX, "makeLaTeXFile encoding: " << encoding << ", fname=" << fname.realPath()); @@ -1563,15 +1684,15 @@ 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; - otexstream os(ofs, d->texrow); + ExportStatus status = ExportSuccess; + otexstream os(ofs); // make sure we are ready to export // this needs to be done before we validate @@ -1580,41 +1701,56 @@ bool Buffer::makeLaTeXFile(FileName const & fname, updateBuffer(); updateMacroInstances(OutputUpdate); + ExportStatus retval; try { - os.texrow().reset(); - 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; lyx_exit(1); } + d->texrow = move(os.texrow()); + ofs.close(); if (ofs.fail()) { - failed_export = true; + status = ExportError; lyxerr << "File '" << fname << "' was not closed properly." << endl; } @@ -1622,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 @@ -1635,10 +1771,18 @@ void Buffer::writeLaTeXSource(otexstream & os, OutputParams runparams = runparams_in; - // This is necessary for LuaTeX/XeTeX with tex fonts. - // See FIXME in BufferParams::encoding() - if (runparams.isFullUnicode()) - runparams.encoding = encodings.fromLyXName("utf8-plain"); + // XeTeX with TeX fonts is only safe with ASCII encoding, + // Check here, because the "flavor" is not known in BufferParams::encoding() + // (power users can override this safety measure selecting "utf8-plain"). + if (!params().useNonTeXFonts && (runparams.flavor == OutputParams::XETEX) + && (runparams.encoding->name() != "utf8-plain")) + runparams.encoding = encodings.fromLyXName("ascii"); + // FIXME: when only the current paragraph is shown, this is ignored + // (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 @@ -1668,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"; } @@ -1712,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 " @@ -1731,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" @@ -1744,7 +1888,7 @@ void Buffer::writeLaTeXSource(otexstream & os, } os << "\\makeatletter\n" << "\\def\\input@path{{" - << docdir << "/}}\n" + << docdir << "}}\n" << "\\makeatother\n"; } } @@ -1758,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"); @@ -1766,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) @@ -1804,21 +2002,13 @@ void Buffer::writeLaTeXSource(otexstream & os, } runparams_in.encoding = runparams.encoding; - // Just to be sure. (Asger) - os.texrow().newline(); - - //for (int i = 0; itexrow.rows(); i++) { - // int id,pos; - // if (d->texrow.getIdFromRow(i+1,id,pos) && id>0) - // lyxerr << i+1 << ":" << id << ":" << getParFromID(id).paragraph().asString()<<"\n"; - //} - 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 { @@ -1826,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 { @@ -1851,7 +2045,7 @@ void Buffer::writeDocBookSource(odocstream & os, string const & fname, d->texrow.reset(); DocumentClass const & tclass = params().documentClass(); - string const top_element = tclass.latexname(); + string const & top_element = tclass.latexname(); bool const output_preamble = output == FullSource || output == OnlyPreamble; @@ -1869,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"; @@ -1909,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 { @@ -1977,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'; @@ -2039,18 +2240,23 @@ void Buffer::writeLyXHTMLSource(odocstream & os, if (output_body) { bool const output_body_tag = (output != IncludedFile); if (output_body_tag) - os << "\n"; + os << "\n"; XHTMLStream xs(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; } @@ -2073,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()); @@ -2107,8 +2318,8 @@ void Buffer::validate(LaTeXFeatures & features) const if (!features.runparams().is_child) params().validate(features); - for_each(paragraphs().begin(), paragraphs().end(), - bind(&Paragraph::validate, _1, ref(features))); + for (Paragraph const & p : paragraphs()) + p.validate(features); if (lyxerr.debugging(Debug::LATEX)) { features.showStruct(); @@ -2125,57 +2336,19 @@ void Buffer::getLabelList(vector & list) const } list.clear(); - Toc & toc = d->toc_backend.toc("label"); - TocIterator toc_it = toc.begin(); - TocIterator end = toc.end(); - for (; toc_it != end; ++toc_it) { - if (toc_it->depth() == 0) - list.push_back(toc_it->str()); + shared_ptr toc = d->toc_backend.toc("label"); + for (auto const & tocit : *toc) { + if (tocit.depth() == 0) + list.push_back(tocit.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()); - } - } - } - d->bibfile_cache_valid_ = true; - d->bibinfo_cache_valid_ = false; - d->cite_labels_valid_ = false; -} - - 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) @@ -2183,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_; } @@ -2219,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 @@ -2228,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; } } } @@ -2258,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; + } } @@ -2295,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_; @@ -2347,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. @@ -2366,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: @@ -2400,7 +2601,6 @@ bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_BRANCH_ADD: case LFUN_BRANCHES_RENAME: - case LFUN_BUFFER_PRINT: // if no Buffer is present, then of course we won't be called! break; @@ -2408,6 +2608,32 @@ bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) enable = !isReadonly(); break; + case LFUN_BUFFER_VIEW_CACHE: + (d->preview_file_).refresh(); + enable = (d->preview_file_).exists() && !(d->preview_file_).isFileEmpty(); + break; + + case LFUN_CHANGES_TRACK: + flag.setEnabled(true); + flag.setOnOff(params().track_changes); + break; + + case LFUN_CHANGES_OUTPUT: + flag.setEnabled(true); + flag.setOnOff(params().output_changes); + break; + + case LFUN_BUFFER_TOGGLE_COMPRESSION: + flag.setOnOff(params().compressed); + break; + + case LFUN_BUFFER_TOGGLE_OUTPUT_SYNC: + flag.setOnOff(params().output_sync); + break; + + case LFUN_BUFFER_ANONYMIZE: + break; + default: return false; } @@ -2436,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: @@ -2446,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; } @@ -2466,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!" @@ -2505,7 +2730,8 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) // Execute the command in the background Systemcall call; - call.startscript(Systemcall::DontWait, command, filePath()); + call.startscript(Systemcall::DontWait, command, + filePath(), layoutPos()); break; } @@ -2558,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."); @@ -2630,120 +2854,62 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) break; } - case LFUN_BUFFER_PRINT: { - // we'll assume there's a problem until we succeed - dr.setError(true); - string target = func.getArg(0); - string target_name = func.getArg(1); - string command = func.getArg(2); + case LFUN_BUFFER_VIEW_CACHE: + if (!theFormats().view(*this, d->preview_file_, + d->preview_format_)) + dr.setMessage(_("Error viewing the output file.")); + break; - if (target.empty() - || target_name.empty() - || command.empty()) { - LYXERR0("Unable to parse " << func.argument()); - docstring const msg = - bformat(_("Unable to parse \"%1$s\""), func.argument()); - dr.setMessage(msg); - break; - } - if (target != "printer" && target != "file") { - LYXERR0("Unrecognized target \"" << target << '"'); - docstring const msg = - bformat(_("Unrecognized target \"%1$s\""), from_utf8(target)); - dr.setMessage(msg); - break; - } + case LFUN_CHANGES_TRACK: + if (params().save_transient_properties) + undo().recordUndoBufferParams(CursorData()); + params().track_changes = !params().track_changes; + if (!params().track_changes) + dr.forceChangesUpdate(); + break; - if (doExport("dvi", true) != ExportSuccess) { - showPrintError(absFileName()); - dr.setMessage(_("Error exporting to DVI.")); - break; + case LFUN_CHANGES_OUTPUT: + if (params().save_transient_properties) + undo().recordUndoBufferParams(CursorData()); + params().output_changes = !params().output_changes; + if (params().output_changes) { + bool dvipost = LaTeXFeatures::isAvailable("dvipost"); + bool xcolorulem = LaTeXFeatures::isAvailable("ulem") && + LaTeXFeatures::isAvailable("xcolor"); + + if (!dvipost && !xcolorulem) { + Alert::warning(_("Changes not shown in LaTeX output"), + _("Changes will not be highlighted in LaTeX output, " + "because neither dvipost nor xcolor/ulem are installed.\n" + "Please install these packages or redefine " + "\\lyxadded and \\lyxdeleted in the LaTeX preamble.")); + } else if (!xcolorulem) { + Alert::warning(_("Changes not shown in LaTeX output"), + _("Changes will not be highlighted in LaTeX output " + "when using pdflatex, because xcolor and ulem are not installed.\n" + "Please install both packages or redefine " + "\\lyxadded and \\lyxdeleted in the LaTeX preamble.")); + } } + break; - // Push directory path. - string const path = temppath(); - // Prevent the compiler from optimizing away p - FileName pp(path); - PathChanger p(pp); - - // there are three cases here: - // 1. we print to a file - // 2. we print directly to a printer - // 3. we print using a spool command (print to file first) - Systemcall one; - int res = 0; - string const dviname = changeExtension(latexName(true), "dvi"); - - if (target == "printer") { - if (!lyxrc.print_spool_command.empty()) { - // case 3: print using a spool - string const psname = changeExtension(dviname,".ps"); - command += ' ' + lyxrc.print_to_file - + quoteName(psname) - + ' ' - + quoteName(dviname); - - string command2 = lyxrc.print_spool_command + ' '; - if (target_name != "default") { - command2 += lyxrc.print_spool_printerprefix - + target_name - + ' '; - } - command2 += quoteName(psname); - // First run dvips. - // If successful, then spool command - res = one.startscript(Systemcall::Wait, command, - filePath()); - - if (res == 0) { - // If there's no GUI, we have to wait on this command. Otherwise, - // LyX deletes the temporary directory, and with it the spooled - // file, before it can be printed!! - Systemcall::Starttype stype = use_gui ? - Systemcall::DontWait : Systemcall::Wait; - res = one.startscript(stype, command2, - filePath()); - } - } else { - // case 2: print directly to a printer - if (target_name != "default") - command += ' ' + lyxrc.print_to_printer + target_name + ' '; - // as above.... - Systemcall::Starttype stype = use_gui ? - Systemcall::DontWait : Systemcall::Wait; - res = one.startscript(stype, command + - quoteName(dviname), filePath()); - } + case LFUN_BUFFER_TOGGLE_COMPRESSION: + // turn compression on/off + undo().recordUndoBufferParams(CursorData()); + params().compressed = !params().compressed; + break; - } else { - // case 1: print to a file - FileName const filename(makeAbsPath(target_name, filePath())); - FileName const dvifile(makeAbsPath(dviname, path)); - if (filename.exists()) { - docstring text = bformat( - _("The file %1$s already exists.\n\n" - "Do you want to overwrite that file?"), - makeDisplayPath(filename.absFileName())); - if (Alert::prompt(_("Overwrite file?"), - text, 0, 1, _("&Overwrite"), _("&Cancel")) != 0) - break; - } - command += ' ' + lyxrc.print_to_file - + quoteName(filename.toFilesystemEncoding()) - + ' ' - + quoteName(dvifile.toFilesystemEncoding()); - // as above.... - Systemcall::Starttype stype = use_gui ? - Systemcall::DontWait : Systemcall::Wait; - res = one.startscript(stype, command, filePath()); - } + case LFUN_BUFFER_TOGGLE_OUTPUT_SYNC: + undo().recordUndoBufferParams(CursorData()); + params().output_sync = !params().output_sync; + break; - if (res == 0) - dr.setError(false); - else { - dr.setMessage(_("Error running external commands.")); - showPrintError(absFileName()); - } + case LFUN_BUFFER_ANONYMIZE: { + undo().recordUndoFullBuffer(CursorData()); + CursorData cur(doc_iterator_begin(this)); + for ( ; cur ; cur.forwardPar()) + cur.paragraph().anonymize(); + dr.forceBufferUpdate(); break; } @@ -2752,7 +2918,6 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) break; } dr.dispatched(dispatched); - undo().endUndoGroup(); } @@ -2780,37 +2945,33 @@ 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); } DocIterator Buffer::getParFromID(int const id) const { Buffer * buf = const_cast(this); - if (id < 0) { - // John says this is called with id == -1 from undo - lyxerr << "getParFromID(), id: " << id << endl; + if (id < 0) + // This means non-existent return doc_iterator_end(buf); - } for (DocIterator it = doc_iterator_begin(buf); !it.atEnd(); it.forwardPar()) if (it.paragraph().id() == id) @@ -2868,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. } @@ -2904,6 +3055,7 @@ void Buffer::markClean() const // autosave d->bak_clean = true; d->undo_.markDirty(); + clearExternalModification(); } @@ -2944,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; } @@ -2975,12 +3124,184 @@ string Buffer::filePath() const } -bool Buffer::isReadonly() const +DocFileName Buffer::getReferencedFileName(string const & fn) const +{ + DocFileName result; + if (FileName::isAbsolute(fn) || !FileName::isAbsolute(params().origin)) + result.set(fn, filePath()); + else { + // filePath() ends with a path separator + FileName const test(filePath() + fn); + if (test.exists()) + result.set(fn, filePath()); + else + result.set(fn, params().origin); + } + + return result; +} + + +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; +} + + +void Buffer::setLayoutPos(string const & path) +{ + if (path.empty()) { + d->layout_position.clear(); + return; + } + + LATTEST(FileName::isAbsolute(path)); + + d->layout_position = + to_utf8(makeRelPath(from_utf8(path), from_utf8(filePath()))); + + if (d->layout_position.empty()) + d->layout_position = "."; +} + + +bool Buffer::hasReadonlyFlag() const { return d->read_only; } +bool Buffer::isReadonly() const +{ + return hasReadonlyFlag() || notifiesExternalModification(); +} + + void Buffer::setParent(Buffer const * buffer) { // Avoids recursive include. @@ -3040,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()) @@ -3121,12 +3440,12 @@ MacroData const * Buffer::Impl::getBufferMacro(docstring const & name, if (it != nameIt->second.end()) { while (true) { // scope ends behind pos? - if (pos < it->second.first) { + if (pos < it->second.scope) { // Looks good, remember this. If there // is no external macro behind this, // we found the right one already. bestPos = it->first; - bestData = &it->second.second; + bestData = &it->second.macro; break; } @@ -3151,13 +3470,13 @@ MacroData const * Buffer::Impl::getBufferMacro(docstring const & name, break; // scope ends behind pos? - if (pos < it->second.first + if (pos < it->second.scope && (cloned_buffer_ || - theBufferList().isLoaded(it->second.second))) { + theBufferList().isLoaded(it->second.buffer))) { // look for macro in external file macro_lock = true; MacroData const * data - = it->second.second->getMacro(name, false); + = it->second.buffer->getMacro(name, false); macro_lock = false; if (data) { bestPos = it->first; @@ -3240,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 @@ -3258,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); @@ -3276,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; @@ -3297,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); @@ -3322,7 +3639,7 @@ void Buffer::Impl::updateMacros(DocIterator & it, DocIterator & scope) continue; // register macro - // FIXME (Abdel), I don't understandt why we pass 'it' here + // FIXME (Abdel), I don't understand why we pass 'it' here // instead of 'macroTemplate' defined above... is this correct? macros[macroTemplate.name()][it] = Impl::ScopeMacro(scope, MacroData(const_cast(owner_), it)); @@ -3405,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); } } } @@ -3419,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(); @@ -3449,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) @@ -3478,7 +3792,7 @@ Buffer::References & Buffer::getReferenceCache(docstring const & label) return it->second.second; static InsetLabel const * dummy_il = 0; - static References const dummy_refs; + static References const dummy_refs = References(); it = d->ref_cache_.insert( make_pair(label, make_pair(dummy_il, dummy_refs))).first; return it->second.second; @@ -3523,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(); @@ -3546,11 +3856,12 @@ void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to) } } - -void Buffer::getSourceCode(odocstream & os, string const & format, - pit_type par_begin, pit_type par_end, - OutputWhat output, bool master) const +// returns NULL if id-to-row conversion is unsupported +unique_ptr Buffer::getSourceCode(odocstream & os, string const & format, + pit_type par_begin, pit_type par_end, + OutputWhat output, bool master) const { + unique_ptr texrow; OutputParams runparams(¶ms().encoding()); runparams.nice = true; runparams.flavor = params().getOutputFlavor(format); @@ -3602,17 +3913,15 @@ void 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(); - TexRow texrow; - texrow.reset(); - texrow.newline(); - texrow.newline(); // latex or literate - otexstream ots(os, texrow); - + otexstream ots(os); + // output above + ots.texrow().newlines(2); // the real stuff latexParagraphs(*this, text(), ots, runparams); + texrow = ots.releaseTexRow(); // Restore the parenthood if (!master) @@ -3632,7 +3941,7 @@ void Buffer::getSourceCode(odocstream & os, string const & format, if (output == FullSource) write(ods); else if (output == OnlyPreamble) - params().writeFile(ods); + params().writeFile(ods, this); else if (output == OnlyBody) text().write(ods); os << from_utf8(ods.str()); @@ -3647,15 +3956,17 @@ void Buffer::getSourceCode(odocstream & os, string const & format, writeDocBookSource(os, absFileName(), runparams, output); } else { // latex or literate - d->texrow.reset(); - d->texrow.newline(); - d->texrow.newline(); - otexstream ots(os, d->texrow); + otexstream ots(os); + // output above + ots.texrow().newlines(2); if (master) runparams.is_child = true; + updateBuffer(); writeLaTeXSource(ots, string(), runparams, output); + texrow = ots.releaseTexRow(); } } + return texrow; } @@ -3738,7 +4049,7 @@ public: /// virtual shared_ptr clone() const { - return shared_ptr(new AutoSaveBuffer(*this)); + return make_shared(*this); } /// int start() @@ -3809,7 +4120,7 @@ int AutoSaveBuffer::generateChild() return pid; } -} // namespace anon +} // namespace FileName Buffer::getEmergencyFileName() const @@ -3844,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(); @@ -3857,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...")); @@ -3886,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; } @@ -3955,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); @@ -3971,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; } } @@ -3985,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; } @@ -4015,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. @@ -4079,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); } } @@ -4102,16 +4424,14 @@ 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]; } - if (!success) - return ExportConverterError; if (put_in_tempdir) { result_file = tmp_result_file.absFileName(); - return ExportSuccess; + return success ? ExportSuccess : ExportConverterError; } if (dest_filename.empty()) @@ -4128,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 @@ -4141,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 @@ -4167,16 +4485,16 @@ 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 ExportSuccess; + return success ? ExportSuccess : ExportConverterError; } @@ -4201,11 +4519,24 @@ Buffer::ExportStatus Buffer::preview(string const & format, bool includeall) con } // (2) export with included children only ExportStatus const status = doExport(format, true, false, result_file); + FileName const previewFile(result_file); + + Impl * theimpl = isClone() ? d->cloned_buffer_->d : d; + theimpl->preview_file_ = previewFile; + theimpl->preview_format_ = format; + theimpl->preview_error_ = (status != ExportSuccess); + if (status != ExportSuccess) return status; - if (!formats.view(*this, FileName(result_file), format)) - return PreviewError; - return PreviewSuccess; + + if (previewFile.exists()) + return theFormats().view(*this, previewFile, format) ? + PreviewSuccess : PreviewError; + + // Successful export but no output file? + // Probably a bug in error detection. + LATTEST(status != ExportSuccess); + return status; } @@ -4241,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. " @@ -4304,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. " @@ -4357,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()); @@ -4425,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 @@ -4440,11 +4749,11 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const if (master != this) { bufToUpdate.insert(this); master->updateBuffer(UpdateMaster, utype); - // If the master buffer has no gui associated with it, then the TocModel is - // 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 + // If the master buffer has no gui associated with it, then the TocModel is + // 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. @@ -4466,6 +4775,7 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const // update all caches clearReferenceCache(); updateMacros(); + setChangesPresent(false); Buffer & cbuf = const_cast(*this); @@ -4473,13 +4783,48 @@ 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; - cbuf.tocBackend().update(utype == OutputUpdate); + /// FIXME: Perf + cbuf.tocBackend().update(true, utype); if (scope == UpdateMaster) cbuf.structureChanged(); } @@ -4492,6 +4837,11 @@ static depth_type getDepth(DocIterator const & it) if (!it[i].inset().inMathed()) depth += it[i].paragraph().getDepth() + 1; // remove 1 since the outer inset does not count + // we should have at least one non-math inset, so + // depth should nevery be 0. but maybe it is worth + // marking this, just in case. + LATTEST(depth > 0); + // coverity[INTEGER_OVERFLOW] return depth - 1; } @@ -4547,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; @@ -4618,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; @@ -4631,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(); @@ -4712,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(); @@ -4728,12 +5085,13 @@ void Buffer::updateBuffer(ParIterator & parit, UpdateType utype) const // set the counter for this paragraph d->setLabel(parit, utype); + // 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); } } } @@ -4750,13 +5108,13 @@ int Buffer::spellCheck(DocIterator & from, DocIterator & to, DocIterator const end = to_end ? doc_iterator_end(this) : to; // OK, we start from here. for (; from != end; from.forwardPos()) { - // We are only interested in text so remove the math CursorSlice. - while (from.inMathed()) { + // This skips all insets with spell check disabled. + while (!from.allowSpellCheck()) { from.pop_back(); from.pos()++; } // If from is at the end of the document (which is possible - // when leaving the mathed) LyX will crash later otherwise. + // when "from" was changed above) LyX will crash later otherwise. if (from.atEnd() || (!to_end && from >= end)) break; to = from; @@ -4766,7 +5124,6 @@ int Buffer::spellCheck(DocIterator & from, DocIterator & to, word_lang = wl; break; } - // Do not increase progress when from == to, otherwise the word // count will be wrong. if (from != to) { @@ -4852,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(); @@ -4884,6 +5241,8 @@ bool Buffer::saveAs(FileName const & fn) FileName const old_name = fileName(); FileName const old_auto = getAutosaveFileName(); bool const old_unnamed = isUnnamed(); + bool success = true; + d->old_position = filePath(); setFileName(fn); markDirty(); @@ -4901,29 +5260,24 @@ bool Buffer::saveAs(FileName const & fn) // are still valid. checkChildBuffers(); checkMasterBuffer(); - return true; } else { // save failed // reset the old filename and unnamed state setFileName(old_name); setUnnamed(old_unnamed); - return false; + success = false; } + + d->old_position.clear(); + return success; } -// FIXME We could do better here, but it is complicated. What would be -// nice is to offer either (a) to save the child buffer to an appropriate -// location, so that it would "move with the master", or else (b) to update -// the InsetInclude so that it pointed to the same file. But (a) is a bit -// complicated, because the code for this lives in GuiView. 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(); @@ -4936,11 +5290,6 @@ void Buffer::checkChildBuffers() if (oldloc == newloc) continue; // the location of the child file is incorrect. - Alert::warning(_("Included File Invalid"), - bformat(_("Saving this document to a new location has made the file:\n" - " %1$s\n" - "inaccessible. You will need to update the included filename."), - from_utf8(oldloc))); cbuf->setParent(0); inset_inc->setChildBuffer(0); } @@ -4970,4 +5319,112 @@ void Buffer::checkMasterBuffer() setParent(0); } + +string Buffer::includedFilePath(string const & name, string const & ext) const +{ + if (d->old_position.empty() || + equivalent(FileName(d->old_position), FileName(filePath()))) + return name; + + bool isabsolute = FileName::isAbsolute(name); + // both old_position and filePath() end with a path separator + string absname = isabsolute ? name : d->old_position + name; + + // if old_position is set to origin, we need to do the equivalent of + // getReferencedFileName() (see readDocument()) + if (!isabsolute && d->old_position == params().origin) { + FileName const test(addExtension(filePath() + name, ext)); + if (test.exists()) + absname = filePath() + name; + } + + if (!FileName(addExtension(absname, ext)).exists()) + return name; + + if (isabsolute) + return to_utf8(makeRelPath(from_utf8(name), from_utf8(filePath()))); + + return to_utf8(makeRelPath(from_utf8(FileName(absname).realPath()), + from_utf8(filePath()))); +} + + +void Buffer::setChangesPresent(bool b) const +{ + d->tracked_changes_present_ = b; +} + + +bool Buffer::areChangesPresent() const +{ + return d->tracked_changes_present_; +} + + +void Buffer::updateChangesPresent() const +{ + LYXERR(Debug::CHANGES, "Buffer::updateChangesPresent"); + setChangesPresent(false); + ParConstIterator it = par_iterator_begin(); + ParConstIterator const end = par_iterator_end(); + for (; !areChangesPresent() && it != end; ++it) + it->addChangesToBuffer(*this); +} + + +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