X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBuffer.cpp;h=61b89200c17a7b784205965985077d02fdb2e9a6;hb=28be7d552f62cc02fa86d7f79201d089bfb2d7b5;hp=d8d93b0afd3b48521589069d543b267001f95bd6;hpb=f6d4bce12303a2f30ea129ee86e7f7d879668260;p=lyx.git diff --git a/src/Buffer.cpp b/src/Buffer.cpp index d8d93b0afd..61b89200c1 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -91,6 +91,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" @@ -263,9 +264,8 @@ public: /// Container for all sort of Buffer dependant errors. map errorLists; - /// timestamp and checksum used to test if the file has been externally - /// modified. (Used to properly enable 'File->Revert to saved', bug 4114). - time_t timestamp_; + /// checksum used to test if the file has been externally modified. Used to + /// double check whether the file had been externally modified when saving. unsigned long checksum_; /// @@ -376,6 +376,15 @@ public: // display the review toolbar, for instance) mutable bool tracked_changes_present_; + // Make sure the file monitor monitors the good file. + void refreshFileMonitor(); + + /// Notify or clear of external modification + void fileExternallyModified(bool exists); + + /// has been externally modified? Can be reset by the user. + mutable bool externally_modified_; + private: /// So we can force access via the accessors. mutable Buffer const * parent_buffer; @@ -384,6 +393,7 @@ private: int char_count_; int blank_count_; + FileMonitorPtr file_monitor_; }; @@ -419,13 +429,15 @@ Buffer::Impl::Impl(Buffer * owner, FileName const & file, bool readonly_, : owner_(owner), lyx_clean(true), bak_clean(true), unnamed(false), internal_buffer(false), read_only(readonly_), filename(file), file_fully_loaded(false), file_format(LYX_FORMAT), need_format_backup(false), - ignore_parent(false), toc_backend(owner), macro_lock(false), timestamp_(0), + ignore_parent(false), toc_backend(owner), macro_lock(false), checksum_(0), wa_(0), gui_(0), undo_(*owner), bibinfo_cache_valid_(false), bibfile_cache_valid_(false), cite_labels_valid_(false), preview_error_(false), inset(0), preview_loader_(0), cloned_buffer_(cloned_buffer), - clone_list_(0), doing_export(false), parent_buffer(0), + 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_); @@ -534,7 +546,11 @@ Buffer::~Buffer() 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); } @@ -706,7 +722,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; } @@ -815,7 +831,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; @@ -863,6 +879,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()); @@ -912,6 +929,10 @@ int Buffer::readHeader(Lexer & lex) params().output_sync_macro.erase(); 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]; @@ -1061,7 +1082,7 @@ 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 @@ -1175,7 +1196,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; } @@ -1289,7 +1310,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(); @@ -1370,7 +1391,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); @@ -1385,12 +1406,12 @@ bool Buffer::save() const // if the file does not yet exist, none of the backup activity // that follows is necessary - if (!fileName().exists()) { + if (!fileName().exists()) { if (!writeFile(fileName())) - return false; - markClean(); - return true; - } + return false; + markClean(); + return true; + } // we first write the file to a new name, then move it to its // proper location once that has been done successfully. that @@ -1581,7 +1602,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; @@ -1716,6 +1737,7 @@ bool Buffer::makeLaTeXFile(FileName const & fname, catch (exception const & e) { errorList.push_back(ErrorItem(_("conversion failed"), _(e.what()))); + lyxerr << e.what() << endl; failed_export = true; } catch (...) { @@ -1875,7 +1897,15 @@ void Buffer::writeLaTeXSource(otexstream & os, // Write the preamble runparams.use_babel = params().writeLaTeX(os, features, d->filename.onlyPath()); - + + // Biblatex bibliographies are loaded here + if (params().useBiblatex()) { + vector const bibfiles = + prepareBibFilePaths(runparams, getBibfilesCache(), true); + for (docstring const & file: bibfiles) + os << "\\addbibresource{" << file << "}\n"; + } + if (!runparams.dryrun && features.hasPolyglossiaExclusiveLanguages() && !features.hasOnlyPolyglossiaLanguages()) { docstring blangs; @@ -2023,7 +2053,7 @@ 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\" " + os << "PUBLIC \"-//OASIS//DTD DocBook XML V4.2//EN\" " << "\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\""; else os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\""; @@ -2502,7 +2532,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. @@ -2521,7 +2551,8 @@ 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); @@ -2625,15 +2656,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; } @@ -2652,7 +2685,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) 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!" @@ -2811,7 +2844,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) } case LFUN_BUFFER_VIEW_CACHE: - if (!formats.view(*this, d->preview_file_, + if (!theFormats().view(*this, d->preview_file_, d->preview_format_)) dr.setMessage(_("Error viewing the output file.")); break; @@ -2979,29 +3012,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. } @@ -3015,6 +3038,7 @@ void Buffer::markClean() const // autosave d->bak_clean = true; d->undo_.markDirty(); + clearExternalModification(); } @@ -3104,6 +3128,114 @@ DocFileName Buffer::getReferencedFileName(string const & fn) const } +string const Buffer::prepareFileNameForLaTeX(string const & name, + string const & ext, bool nice) const +{ + string const fname = makeAbsPath(name, filePath()).absFileName(); + if (FileName::isAbsolute(name) || !FileName(fname + ext).isReadableFile()) + return name; + if (!nice) + return fname; + + // FIXME UNICODE + return to_utf8(makeRelPath(from_utf8(fname), + from_utf8(masterBuffer()->filePath()))); +} + + +vector const Buffer::prepareBibFilePaths(OutputParams const & runparams, + FileNamePairList const bibfilelist, + bool const add_extension) const +{ + // If we are processing the LaTeX file in a temp directory then + // copy the .bib databases to this temp directory, mangling their + // names in the process. Store this mangled name in the list of + // all databases. + // (We need to do all this because BibTeX *really*, *really* + // can't handle "files with spaces" and Windows users tend to + // use such filenames.) + // Otherwise, store the (maybe absolute) path to the original, + // unmangled database name. + + vector res; + + // determine the export format + string const tex_format = flavor2format(runparams.flavor); + + // check for spaces in paths + bool found_space = false; + + FileNamePairList::const_iterator it = bibfilelist.begin(); + FileNamePairList::const_iterator en = bibfilelist.end(); + for (; it != en; ++it) { + string utf8input = to_utf8(it->first); + string database = + prepareFileNameForLaTeX(utf8input, ".bib", runparams.nice); + FileName const try_in_file = + makeAbsPath(database + ".bib", filePath()); + bool const not_from_texmf = try_in_file.isReadableFile(); + + 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; @@ -3127,12 +3259,18 @@ void Buffer::setLayoutPos(string const & path) } -bool Buffer::isReadonly() const +bool Buffer::hasReadonlyFlag() const { return d->read_only; } +bool Buffer::isReadonly() const +{ + return hasReadonlyFlag() || notifiesExternalModification(); +} + + void Buffer::setParent(Buffer const * buffer) { // Avoids recursive include. @@ -3803,6 +3941,7 @@ unique_ptr Buffer::getSourceCode(odocstream & os, string const & format, ots.texrow().newlines(2); if (master) runparams.is_child = true; + updateBuffer(); writeLaTeXSource(ots, string(), runparams, output); texrow = ots.releaseTexRow(); } @@ -4009,7 +4148,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...")); @@ -4107,6 +4246,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); @@ -4137,7 +4278,7 @@ 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; } @@ -4167,7 +4308,7 @@ 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 @@ -4213,7 +4354,7 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir 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, @@ -4281,7 +4422,7 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir 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 const fmt = theFormats().getFormatFromFile(it->sourceName); string fixedName = it->exportName; if (!runparams.export_folder.empty()) { // Relative pathnames starting with ../ will be sanitized @@ -4317,13 +4458,13 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir } else { message(bformat(_("Document exported as %1$s " "to file `%2$s'"), - formats.prettyName(format), + theFormats().prettyName(format), makeDisplayPath(result_file))); } } else { // This must be a dummy converter like fax (bug 1888) message(bformat(_("Document exported as %1$s"), - formats.prettyName(format))); + theFormats().prettyName(format))); } return success ? ExportSuccess : ExportConverterError; @@ -4362,7 +4503,7 @@ Buffer::ExportStatus Buffer::preview(string const & format, bool includeall) con return status; if (previewFile.exists()) - return formats.view(*this, previewFile, format) ? + return theFormats().view(*this, previewFile, format) ? PreviewSuccess : PreviewError; // Successful export but no output file? @@ -4404,7 +4545,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. " @@ -4467,7 +4608,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. " @@ -4521,7 +4662,7 @@ Buffer::ReadStatus Buffer::loadThisLyXFile(FileName const & fn) void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const { for (auto const & err : terr) { - TexRow::TextEntry start, end = TexRow::text_none; + TexRow::TextEntry start = TexRow::text_none, end = TexRow::text_none; int errorRow = err.error_in_line; Buffer const * buf = 0; Impl const * p = d; @@ -4615,7 +4756,10 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const updateBuffer(parit, utype); if (master != this) - // TocBackend update will be done later. + // 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. return; d->bibinfo_cache_valid_ = true; @@ -5170,5 +5314,60 @@ void Buffer::updateChangesPresent() const } +void Buffer::Impl::refreshFileMonitor() +{ + if (file_monitor_ && file_monitor_->filename() == filename.absFileName()) { + file_monitor_->refresh(); + return; + } + + // The previous file monitor is invalid + // This also destroys the previous file monitor and all its connections + file_monitor_ = FileSystemWatcher::monitor(filename); + // file_monitor_ will be destroyed with *this, so it is not going to call a + // destroyed object method. + file_monitor_->connect([this](bool exists) { + fileExternallyModified(exists); + }); +} + + +void Buffer::Impl::fileExternallyModified(bool const exists) +{ + // ignore notifications after our own saving operations + if (checksum_ == filename.checksum()) { + LYXERR(Debug::FILES, "External modification but " + "checksum unchanged: " << filename); + return; + } + lyx_clean = bak_clean = false; + // 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