X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBuffer.cpp;h=c525ec2f21459a966326c5cdeafaf89c87544cc4;hb=0385ef0e192a779aed13aab0fd4a9bd4a03f37e0;hp=ad119bdbf172a0d372756547eadab05a6cfeb8b1;hpb=93ebb7a863377f6dbcaf724972b747b5c3f20c24;p=lyx.git diff --git a/src/Buffer.cpp b/src/Buffer.cpp index ad119bdbf1..c525ec2f21 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -24,9 +24,11 @@ #include "Chktex.h" #include "Converter.h" #include "Counters.h" +#include "Cursor.h" +#include "CutAndPaste.h" #include "DispatchResult.h" #include "DocIterator.h" -#include "Encoding.h" +#include "BufferEncodings.h" #include "ErrorList.h" #include "Exporter.h" #include "Format.h" @@ -56,7 +58,6 @@ #include "SpellChecker.h" #include "sgml.h" #include "TexRow.h" -#include "TexStream.h" #include "Text.h" #include "TextClass.h" #include "TocBackend.h" @@ -96,10 +97,12 @@ #include "support/gzstream.h" #include "support/lstrings.h" #include "support/lyxalgo.h" +#include "support/mutex.h" #include "support/os.h" #include "support/Package.h" -#include "support/Path.h" +#include "support/PathChanger.h" #include "support/Systemcall.h" +#include "support/TempFile.h" #include "support/textutils.h" #include "support/types.h" @@ -112,7 +115,6 @@ #include #include #include -#include #include using namespace std; @@ -210,6 +212,9 @@ public: */ bool file_fully_loaded; + /// Ignore the parent (e.g. when exporting a child standalone)? + bool ignore_parent; + /// mutable TocBackend toc_backend; @@ -279,7 +284,12 @@ public: /// This is here to force the test to be done whenever parent_buffer /// is accessed. - Buffer const * parent() const { + Buffer const * parent() const + { + // ignore_parent temporarily "orphans" a buffer + // (e.g. if a child is compiled standalone) + if (ignore_parent) + return 0; // if parent_buffer is not loaded, then it has been unloaded, // which means that parent_buffer is an invalid pointer. So we // set it to null in that case. @@ -292,7 +302,8 @@ public: } /// - void setParent(Buffer const * pb) { + void setParent(Buffer const * pb) + { if (parent_buffer == pb) // nothing to do return; @@ -312,15 +323,19 @@ public: CloneList * clone_list_; /// are we in the process of exporting this buffer? mutable bool doing_export; - + /// compute statistics /// \p from initial position /// \p to points to the end position void updateStatistics(DocIterator & from, DocIterator & to, - bool skipNoOutput = true); + bool skipNoOutput = true); /// statistics accessor functions - int wordCount() const { return word_count_; } - int charCount(bool with_blanks) const { + int wordCount() const + { + return word_count_; + } + int charCount(bool with_blanks) const + { return char_count_ + (with_blanks ? blank_count_ : 0); } @@ -339,12 +354,20 @@ private: /// Creates the per buffer temporary directory static FileName createBufferTmpDir() { - static int count; + // FIXME This would be the ideal application for a TempDir class (like + // TempFile but for directories) + string counter; + { + static int count; + static Mutex mutex; + Mutex::Locker locker(&mutex); + counter = convert(count++); + } // We are in our own directory. Why bother to mangle name? // In fact I wrote this code to circumvent a problematic behaviour // (bug?) of EMX mkstemp(). FileName tmpfl(package().temp_dir().absFileName() + "/lyx_tmpbuf" + - convert(count++)); + counter); if (!tmpfl.createDirectory(0777)) { throw ExceptionMessage(WarningException, _("Disk Error: "), bformat( @@ -359,9 +382,9 @@ 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), toc_backend(owner), macro_lock(false), - timestamp_(0), checksum_(0), wa_(0), gui_(0), undo_(*owner), - bibinfo_cache_valid_(false), bibfile_cache_valid_(false), + 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) @@ -437,13 +460,17 @@ Buffer::~Buffer() // if we're the master buffer, then we should get rid of the list // of clones if (!parent()) { - // if this is not empty, we have leaked something. worse, one of the - // children still has a reference to this list. - LASSERT(d->clone_list_->empty(), /* */); + // If this is not empty, we have leaked something. Worse, one of the + // children still has a reference to this list. But we will try to + // continue, rather than shut down. + LATTEST(d->clone_list_->empty()); list::iterator it = find(cloned_buffers.begin(), cloned_buffers.end(), d->clone_list_); - LASSERT(it != cloned_buffers.end(), /* */); - cloned_buffers.erase(it); + if (it == cloned_buffers.end()) { + // We will leak in this case, but it is safe to continue. + LATTEST(false); + } else + cloned_buffers.erase(it); delete d->clone_list_; } // FIXME Do we really need to do this right before we delete d? @@ -486,7 +513,7 @@ Buffer::~Buffer() Buffer * Buffer::cloneFromMaster() const { BufferMap bufmap; - cloned_buffers.push_back(new CloneList()); + cloned_buffers.push_back(new CloneList); CloneList * clones = cloned_buffers.back(); masterBuffer()->cloneWithChildren(bufmap, clones); @@ -507,11 +534,21 @@ void Buffer::cloneWithChildren(BufferMap & bufmap, CloneList * clones) const return; Buffer * buffer_clone = new Buffer(fileName().absFileName(), false, this); + + // The clone needs its own DocumentClass, since running updateBuffer() will + // modify it, and we would otherwise be sharing it with the original Buffer. + buffer_clone->params().makeDocumentClass(true); + ErrorList el; + cap::switchBetweenClasses( + params().documentClassPtr(), buffer_clone->params().documentClassPtr(), + static_cast(buffer_clone->inset()), el); + bufmap[this] = buffer_clone; clones->insert(buffer_clone); buffer_clone->d->clone_list_ = clones; buffer_clone->d->macro_lock = true; buffer_clone->d->children_positions.clear(); + // FIXME (Abdel 09/01/2010): this is too complicated. The whole children_positions and // math macro caches need to be rethought and simplified. // I am not sure wether we should handle Buffer cloning here or in BufferList. @@ -542,11 +579,21 @@ void Buffer::cloneWithChildren(BufferMap & bufmap, CloneList * clones) const Buffer * Buffer::cloneBufferOnly() const { - cloned_buffers.push_back(new CloneList()); + cloned_buffers.push_back(new CloneList); CloneList * clones = cloned_buffers.back(); Buffer * buffer_clone = new Buffer(fileName().absFileName(), false, this); + + // The clone needs its own DocumentClass, since running updateBuffer() will + // modify it, and we would otherwise be sharing it with the original Buffer. + buffer_clone->params().makeDocumentClass(true); + ErrorList el; + cap::switchBetweenClasses( + params().documentClassPtr(), buffer_clone->params().documentClassPtr(), + static_cast(buffer_clone->inset()), el); + clones->insert(buffer_clone); buffer_clone->d->clone_list_ = clones; + // we won't be cloning the children buffer_clone->d->children_positions.clear(); return buffer_clone; @@ -568,7 +615,7 @@ void Buffer::changed(bool update_metrics) const frontend::WorkAreaManager & Buffer::workAreaManager() const { - LASSERT(d->wa_, /**/); + LBUFERR(d->wa_); return *d->wa_; } @@ -597,6 +644,28 @@ BufferParams const & Buffer::params() const } +BufferParams const & Buffer::masterParams() const +{ + if (masterBuffer() == this) + return params(); + + 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); + return mparams; +} + + +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; +} + + ParagraphList & Buffer::paragraphs() { return text().paragraphs(); @@ -748,7 +817,10 @@ void Buffer::setReadonly(bool const flag) void Buffer::setFileName(FileName const & fname) { + bool const changed = fname != d->filename; d->filename = fname; + if (changed) + lyxvc().file_found_hook(fname); setReadonly(d->filename.isReadOnly()); saveCheckSum(); updateTitles(); @@ -794,7 +866,8 @@ int Buffer::readHeader(Lexer & lex) params().html_latex_end.clear(); params().html_math_img_scale = 1.0; params().output_sync_macro.erase(); - params().local_layout.clear(); + params().setLocalLayout(string(), false); + params().setLocalLayout(string(), true); for (int i = 0; i < 4; ++i) { params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i]; @@ -870,7 +943,7 @@ bool Buffer::readDocument(Lexer & lex) readHeader(lex); - if (params().outputChanges) { + if (params().output_changes) { bool dvipost = LaTeXFeatures::isAvailable("dvipost"); bool xcolorulem = LaTeXFeatures::isAvailable("ulem") && LaTeXFeatures::isAvailable("xcolor"); @@ -890,7 +963,7 @@ bool Buffer::readDocument(Lexer & lex) } } - if (!params().master.empty()) { + if (!parent() && !params().master.empty()) { FileName const master_file = makeAbsPath(params().master, onlyPath(absFileName())); if (isLyXFileName(master_file.absFileName())) { @@ -941,6 +1014,50 @@ bool Buffer::readDocument(Lexer & lex) } +bool Buffer::importString(string const & format, docstring const & contents, ErrorList & errorList) +{ + Format const * fmt = formats.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()); + 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(); + os.close(); + + bool converted = false; + if (success) { + params().compressed = false; + + // remove dummy empty par + paragraphs().clear(); + + converted = importFile(format, name, errorList); + } + + if (name.exists()) + name.removeFile(); + return converted; +} + + +bool Buffer::importFile(string const & format, FileName const & name, ErrorList & 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; + + return false; +} + + bool Buffer::readString(string const & s) { params().compressed = false; @@ -948,25 +1065,25 @@ bool Buffer::readString(string const & s) Lexer lex; istringstream is(s); lex.setStream(is); - FileName const fn = FileName::tempName("Buffer_readString"); + TempFile tempfile("Buffer_readStringXXXXXX.lyx"); + FileName const fn = tempfile.name(); int file_format; - ReadStatus const ret_plf = parseLyXFormat(lex, fn, file_format); - if (ret_plf != ReadSuccess) - return ret_plf; + bool success = parseLyXFormat(lex, fn, file_format) == ReadSuccess; - if (file_format != LYX_FORMAT) { + if (success && file_format != LYX_FORMAT) { // We need to call lyx2lyx, so write the input to a file ofstream os(fn.toFilesystemEncoding().c_str()); os << s; os.close(); // lyxvc in readFile - return readFile(fn) == ReadSuccess; + if (readFile(fn) != ReadSuccess) + success = false; } - - if (readDocument(lex)) - return false; - return true; + else if (success) + if (readDocument(lex)) + success = false; + return success; } @@ -1086,7 +1203,9 @@ Buffer::ReadStatus Buffer::parseLyXFormat(Lexer & lex, Buffer::ReadStatus Buffer::convertLyXFormat(FileName const & fn, FileName & tmpfile, int from_format) { - tmpfile = FileName::tempName("Buffer_convertLyXFormat"); + TempFile tempfile("Buffer_convertLyXFormatXXXXXX.lyx"); + tempfile.setAutoRemove(false); + tmpfile = tempfile.name(); if(tmpfile.empty()) { Alert::error(_("Conversion failed"), bformat(_("%1$s is from a different" @@ -1173,12 +1292,32 @@ bool Buffer::save() const // We don't need autosaves in the immediate future. (Asger) resetAutosaveTimers(); - FileName backupName; - bool madeBackup = false; + // if the file does not yet exist, none of the backup activity + // that follows is necessary + if (!fileName().exists()) + return writeFile(fileName()); + + // 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")); + bool const symlink = fileName().isSymLink(); + if (!symlink) + tempfile->setAutoRemove(false); + + FileName savefile(tempfile->name()); + LYXERR(Debug::FILES, "Saving to " << savefile.absFileName()); + if (!writeFile(savefile)) + return false; + + // we will set this to false if we fail + bool made_backup = true; - // make a backup if the file already exists - if (lyxrc.make_backup && fileName().exists()) { - backupName = FileName(absFileName() + '~'); + FileName backupName(absFileName() + '~'); + if (lyxrc.make_backup) { if (!lyxrc.backupdir_path.empty()) { string const mangledName = subst(subst(backupName.absFileName(), '/', '!'), ':', '!'); @@ -1186,14 +1325,15 @@ bool Buffer::save() const mangledName)); } + LYXERR(Debug::FILES, "Backing up original file to " << + backupName.absFileName()); // Except file is symlink do not copy because of #6587. // Hard links have bad luck. - if (fileName().isSymLink()) - madeBackup = fileName().copyTo(backupName); - else - madeBackup = fileName().moveTo(backupName); + made_backup = symlink ? + fileName().copyTo(backupName): + fileName().moveTo(backupName); - if (!madeBackup) { + if (!made_backup) { Alert::error(_("Backup failure"), bformat(_("Cannot create backup file %1$s.\n" "Please check whether the directory exists and is writable."), @@ -1202,25 +1342,51 @@ bool Buffer::save() const } } - if (writeFile(d->filename)) { + // Destroy tempfile since it keeps the file locked on windows (bug 9234) + // Only do this if tempfile is not in autoremove mode + if (!symlink) + tempfile.reset(); + // If we have no symlink, we can simply rename the temp file. + // Otherwise, we need to copy it so the symlink stays intact. + if (made_backup && symlink ? savefile.copyTo(fileName(), true) : + savefile.moveTo(fileName())) + { + // saveCheckSum() was already called by writeFile(), but the + // time stamp is invalidated by copying/moving + saveCheckSum(); markClean(); 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 + // 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()))); } else { - // Saving failed, so backup is not backup - if (madeBackup) - backupName.moveTo(d->filename); - return false; + // 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, + // not moved. so the original file is intact. + Alert::error(_("Write failure"), + bformat(_("Cannot move saved file to:\n %1$s.\n" + "But the file has successfully been saved as:\n %2$s."), + from_utf8(fileName().absFileName()), + from_utf8(savefile.absFileName()))); } + return false; } bool Buffer::writeFile(FileName const & fname) const { - // FIXME Do we need to do these here? I don't think writing - // the LyX file depends upon it. (RGH) - // updateBuffer(); - // updateMacroInstances(); - if (d->read_only && fname == d->filename) return false; @@ -1322,6 +1488,8 @@ bool Buffer::write(ostream & ofs) const // The top of the file should not be written by params(). // write out a comment in the top of the file + // 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" << "\\lyxformat " << LYX_FORMAT << "\n" @@ -1390,7 +1558,7 @@ bool Buffer::makeLaTeXFile(FileName const & fname, ofdocstream ofs; try { ofs.reset(encoding); } - catch (iconv_codecvt_facet_exception & e) { + catch (iconv_codecvt_facet_exception const & e) { lyxerr << "Caught iconv exception: " << e.what() << endl; Alert::error(_("Iconv software exception Detected"), bformat(_("Please " "verify that the support software for your encoding (%1$s) is " @@ -1400,7 +1568,6 @@ bool Buffer::makeLaTeXFile(FileName const & fname, if (!openFileWrite(ofs, fname)) return false; - //TexStream ts(ofs.rdbuf(), &texrow()); ErrorList & errorList = d->errorLists["Export"]; errorList.clear(); bool failed_export = false; @@ -1417,7 +1584,7 @@ bool Buffer::makeLaTeXFile(FileName const & fname, os.texrow().reset(); writeLaTeXSource(os, original_path, runparams, output); } - catch (EncodingException & e) { + catch (EncodingException const & e) { odocstringstream ods; ods.put(e.failed_char); ostringstream oss; @@ -1431,7 +1598,7 @@ bool Buffer::makeLaTeXFile(FileName const & fname, e.par_id, e.pos, e.pos + 1)); failed_export = true; } - catch (iconv_codecvt_facet_exception & e) { + catch (iconv_codecvt_facet_exception const & e) { errorList.push_back(ErrorItem(_("iconv conversion failed"), _(e.what()), -1, 0, 0)); failed_export = true; @@ -1452,7 +1619,10 @@ bool Buffer::makeLaTeXFile(FileName const & fname, lyxerr << "File '" << fname << "' was not closed properly." << endl; } - errors("Export"); + if (runparams_in.silent) + errorList.clear(); + else + errors("Export"); return !failed_export; } @@ -1460,25 +1630,35 @@ bool Buffer::makeLaTeXFile(FileName const & fname, void Buffer::writeLaTeXSource(otexstream & os, string const & original_path, OutputParams const & runparams_in, - OutputWhat output) const + OutputWhat output) const { // The child documents, if any, shall be already loaded at this point. OutputParams runparams = runparams_in; + // If we are compiling a file standalone, even if this is the + // child of some other buffer, let's cut the link here, so the + // file is really independent and no concurring settings from + // the master (e.g. branch state) interfere (see #8100). + if (!runparams.is_child) + d->ignore_parent = true; + // Classify the unicode characters appearing in math insets - Encodings::initUnicodeMath(*this); + BufferEncodings::initUnicodeMath(*this); // validate the buffer. LYXERR(Debug::LATEX, " Validating buffer..."); LaTeXFeatures features(*this, params(), runparams); validate(features); + // This is only set once per document (in master) + if (!runparams.is_child) + runparams.use_polyglossia = features.usePolyglossia(); LYXERR(Debug::LATEX, " Buffer validation done."); bool const output_preamble = output == FullSource || output == OnlyPreamble; bool const output_body = - output == FullSource || output == OnlyBody; + output == FullSource || output == OnlyBody; // The starting paragraph of the coming rows is the // first paragraph of the document. (Asger) @@ -1570,15 +1750,20 @@ void Buffer::writeLaTeXSource(otexstream & os, MacroSet parentMacros; listParentMacros(parentMacros, features); - runparams.use_polyglossia = features.usePolyglossia(); // Write the preamble runparams.use_babel = params().writeLaTeX(os, features, d->filename.onlyPath()); - runparams.use_japanese = features.isRequired("japanese"); + // 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"); - if (!output_body) + if (!output_body) { + // Restore the parenthood if needed + if (!runparams.is_child) + d->ignore_parent = false; return; + } // make the body. os << "\\begin{document}\n"; @@ -1597,21 +1782,12 @@ void Buffer::writeLaTeXSource(otexstream & os, LYXERR(Debug::INFO, "preamble finished, now the body."); - // if we are doing a real file with body, even if this is the - // child of some other buffer, let's cut the link here. - // This happens for example if only a child document is printed. - Buffer const * save_parent = 0; - if (output_preamble) { - save_parent = d->parent(); - d->setParent(0); - } - // the real stuff latexParagraphs(*this, text(), os, runparams); // Restore the parenthood if needed - if (output_preamble) - d->setParent(save_parent); + if (!runparams.is_child) + d->ignore_parent = false; // add this just in case after all the paragraphs os << endl; @@ -1778,7 +1954,7 @@ void Buffer::writeLyXHTMLSource(odocstream & os, if (output_preamble) { os << "\n" - << "\n" + << "\n" // FIXME Language should be set properly. << "\n" << "\n" @@ -1826,7 +2002,7 @@ void Buffer::writeLyXHTMLSource(odocstream & os, << ";\n"; css << "}\n"; } - + docstring const dstyles = css.str(); if (!dstyles.empty()) { bool written = false; @@ -1861,7 +2037,9 @@ void Buffer::writeLyXHTMLSource(odocstream & os, if (output_body_tag) os << "\n"; XHTMLStream xs(os); - params().documentClass().counters().reset(); + if (output != IncludedFile) + // if we're an included file, the counters are in the master. + params().documentClass().counters().reset(); xhtmlParagraphs(text(), *this, xs, runparams); if (output_body_tag) os << "\n"; @@ -1900,7 +2078,7 @@ int Buffer::runChktex() if (res == -1) { Alert::error(_("chktex failure"), _("Could not run chktex successfully.")); - } else if (res > 0) { + } else { ErrorList & errlist = d->errorLists["ChkTeX"]; errlist.clear(); bufferErrors(terr, errlist); @@ -1908,7 +2086,10 @@ int Buffer::runChktex() setBusy(false); - errors("ChkTeX"); + if (runparams.silent) + d->errorLists["ChkTeX"].clear(); + else + errors("ChkTeX"); return res; } @@ -1916,7 +2097,11 @@ int Buffer::runChktex() void Buffer::validate(LaTeXFeatures & features) const { - params().validate(features); + // Validate the buffer params, but not for included + // files, since they also use the parent buffer's + // params (# 5941) + if (!features.runparams().is_child) + params().validate(features); for_each(paragraphs().begin(), paragraphs().end(), bind(&Paragraph::validate, _1, ref(features))); @@ -2099,6 +2284,13 @@ void Buffer::addBibTeXInfo(docstring const & key, BibTeXInfo const & bi) const } +void Buffer::makeCitationLabels() const +{ + Buffer const * const master = masterBuffer(); + return d->bibinfo_.makeCitationLabels(*master); +} + + bool Buffer::citeLabelsValid() const { return masterBuffer()->d->cite_labels_valid_; @@ -2150,57 +2342,70 @@ bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) switch (cmd.action()) { - case LFUN_BUFFER_TOGGLE_READ_ONLY: - flag.setOnOff(isReadonly()); - break; + case LFUN_BUFFER_TOGGLE_READ_ONLY: + flag.setOnOff(isReadonly()); + break; // FIXME: There is need for a command-line import. //case LFUN_BUFFER_IMPORT: - case LFUN_BUFFER_AUTO_SAVE: - break; + case LFUN_BUFFER_AUTO_SAVE: + break; - case LFUN_BUFFER_EXPORT_CUSTOM: - // FIXME: Nothing to check here? - break; + case LFUN_BUFFER_EXPORT_CUSTOM: + // FIXME: Nothing to check here? + break; - case LFUN_BUFFER_EXPORT: { - docstring const arg = cmd.argument(); - if (arg == "custom") { - enable = true; - break; - } - string format = to_utf8(arg); - size_t pos = format.find(' '); - if (pos != string::npos) - format = format.substr(0, pos); - enable = params().isExportable(format); - if (!enable) - flag.message(bformat( - _("Don't know how to export to format: %1$s"), arg)); + case LFUN_BUFFER_EXPORT: { + docstring const arg = cmd.argument(); + if (arg == "custom") { + enable = true; break; } + string format = to_utf8(arg); + size_t pos = format.find(' '); + if (pos != string::npos) + format = format.substr(0, pos); + enable = params().isExportable(format); + 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_BUFFER_CHKTEX: + enable = params().isLatex() && !lyxrc.chktex_command.empty(); + break; - case LFUN_BUILD_PROGRAM: - enable = params().isExportable("program"); - break; + case LFUN_BUILD_PROGRAM: + enable = params().isExportable("program"); + break; - 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; + case LFUN_BRANCH_ACTIVATE: + case LFUN_BRANCH_DEACTIVATE: + case LFUN_BRANCH_MASTER_ACTIVATE: + case LFUN_BRANCH_MASTER_DEACTIVATE: { + bool const master = (cmd.action() == LFUN_BRANCH_MASTER_ACTIVATE + || cmd.action() == LFUN_BRANCH_MASTER_DEACTIVATE); + BranchList const & branchList = master ? masterBuffer()->params().branchlist() + : params().branchlist(); + docstring const branchName = cmd.argument(); + flag.setEnabled(!branchName.empty() && branchList.find(branchName)); + break; + } - case LFUN_BUFFER_LANGUAGE: - enable = !isReadonly(); - break; + 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; - default: - return false; + case LFUN_BUFFER_LANGUAGE: + enable = !isReadonly(); + break; + + default: + return false; } flag.setEnabled(enable); return true; @@ -2231,8 +2436,11 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) switch (func.action()) { case LFUN_BUFFER_TOGGLE_READ_ONLY: - if (lyxvc().inUse()) - lyxvc().toggleReadOnly(); + if (lyxvc().inUse()) { + string log = lyxvc().toggleReadOnly(); + if (!log.empty()) + dr.setMessage(log); + } else setReadonly(!isReadonly()); break; @@ -2246,9 +2454,13 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) break; } - case LFUN_BUILD_PROGRAM: - doExport("program", true); + case LFUN_BUILD_PROGRAM: { + ExportStatus const status = doExport("program", true); + dr.setError(status != ExportSuccess); + if (status != ExportSuccess) + dr.setMessage(_("Error generating literate programming code.")); break; + } case LFUN_BUFFER_CHKTEX: runChktex(); @@ -2305,6 +2517,42 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) resetAutosaveTimers(); break; + case LFUN_BRANCH_ACTIVATE: + case LFUN_BRANCH_DEACTIVATE: + case LFUN_BRANCH_MASTER_ACTIVATE: + case LFUN_BRANCH_MASTER_DEACTIVATE: { + bool const master = (func.action() == LFUN_BRANCH_MASTER_ACTIVATE + || func.action() == LFUN_BRANCH_MASTER_DEACTIVATE); + Buffer * buf = master ? const_cast(masterBuffer()) + : this; + + docstring const branch_name = func.argument(); + // the case without a branch name is handled elsewhere + if (branch_name.empty()) { + dispatched = false; + break; + } + Branch * branch = buf->params().branchlist().find(branch_name); + if (!branch) { + LYXERR0("Branch " << branch_name << " does not exist."); + dr.setError(true); + docstring const msg = + bformat(_("Branch \"%1$s\" does not exist."), branch_name); + dr.setMessage(msg); + break; + } + bool const activate = (func.action() == LFUN_BRANCH_ACTIVATE + || func.action() == LFUN_BRANCH_MASTER_ACTIVATE); + if (branch->isSelected() != activate) { + buf->undo().recordUndoFullDocument(CursorData()); + branch->setSelected(activate); + dr.setError(false); + dr.screenUpdate(Update::Force); + dr.forceBufferUpdate(); + } + break; + } + case LFUN_BRANCH_ADD: { docstring branch_name = func.argument(); if (branch_name.empty()) { @@ -2326,6 +2574,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) msg += ("\n"); msg += bformat(_("Branch \"%1$s\" already exists."), branch_name); } else { + undo().recordUndoFullDocument(CursorData()); branch_list.add(branch_name); branch = branch_list.find(branch_name); string const x11hexname = X11hexname(branch->color()); @@ -2353,7 +2602,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) if (it->lyxCode() == BRANCH_CODE) { InsetBranch & ins = static_cast(*it); if (ins.branch() == oldname) { - undo().recordUndo(it); + undo().recordUndo(CursorData(it)); ins.rename(newname); success = true; continue; @@ -2401,7 +2650,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) break; } - if (!doExport("dvi", true)) { + if (doExport("dvi", true) != ExportSuccess) { showPrintError(absFileName()); dr.setMessage(_("Error exporting to DVI.")); break; @@ -2505,8 +2754,8 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) void Buffer::changeLanguage(Language const * from, Language const * to) { - LASSERT(from, /**/); - LASSERT(to, /**/); + LASSERT(from, return); + LASSERT(to, return); for_each(par_iterator_begin(), par_iterator_end(), @@ -2617,7 +2866,7 @@ bool Buffer::isClean() const bool Buffer::isExternallyModified(CheckMethod method) const { - LASSERT(d->filename.exists(), /**/); + LASSERT(d->filename.exists(), return false); // if method == timestamp, check timestamp before checksum return (method == checksum_method || d->timestamp_ != d->filename.lastModified()) @@ -2713,11 +2962,12 @@ string Buffer::absFileName() const string Buffer::filePath() const { - int last = d->filename.onlyPath().absFileName().length() - 1; + string const abs = d->filename.onlyPath().absFileName(); + if (abs.empty()) + return abs; + int last = abs.length() - 1; - return d->filename.onlyPath().absFileName()[last] == '/' - ? d->filename.onlyPath().absFileName() - : d->filename.onlyPath().absFileName() + "/"; + return abs[last] == '/' ? abs : abs + '/'; } @@ -2879,7 +3129,7 @@ MacroData const * Buffer::Impl::getBufferMacro(docstring const & name, // try previous macro if there is one if (it == nameIt->second.begin()) break; - it--; + --it; } } } @@ -3214,10 +3464,10 @@ void Buffer::listParentMacros(MacroSet & macros, LaTeXFeatures & features) const } -Buffer::References & Buffer::references(docstring const & label) +Buffer::References & Buffer::getReferenceCache(docstring const & label) { if (d->parent()) - return const_cast(masterBuffer())->references(label); + return const_cast(masterBuffer())->getReferenceCache(label); RefCache::iterator it = d->ref_cache_.find(label); if (it != d->ref_cache_.end()) @@ -3233,7 +3483,14 @@ Buffer::References & Buffer::references(docstring const & label) Buffer::References const & Buffer::references(docstring const & label) const { - return const_cast(this)->references(label); + return const_cast(this)->getReferenceCache(label); +} + + +void Buffer::addReference(docstring const & label, Inset * inset, ParIterator it) +{ + References & refs = getReferenceCache(label); + refs.push_back(make_pair(inset, it)); } @@ -3256,12 +3513,9 @@ void Buffer::clearReferenceCache() const } -void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to, - InsetCode code) +void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to) { //FIXME: This does not work for child documents yet. - LASSERT(code == CITE_CODE, /**/); - reloadBibInfoCache(); // Check if the label 'from' appears more than once @@ -3279,21 +3533,19 @@ void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to, string const paramName = "key"; for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) { - if (it->lyxCode() == code) { - InsetCommand * inset = it->asInsetCommand(); - if (!inset) - continue; - docstring const oldValue = inset->getParam(paramName); - if (oldValue == from) - inset->setParam(paramName, to); - } + if (it->lyxCode() != CITE_CODE) + continue; + InsetCommand * inset = it->asInsetCommand(); + docstring const oldValue = inset->getParam(paramName); + if (oldValue == from) + inset->setParam(paramName, to); } } -void Buffer::getSourceCode(odocstream & os, string const format, +void Buffer::getSourceCode(odocstream & os, string const & format, pit_type par_begin, pit_type par_end, - OutputWhat output) const + OutputWhat output, bool master) const { OutputParams runparams(¶ms().encoding()); runparams.nice = true; @@ -3316,12 +3568,14 @@ void Buffer::getSourceCode(odocstream & os, string const format, convert(par_end - 1)) << "\n\n"; } - TexRow texrow; - texrow.reset(); - texrow.newline(); - texrow.newline(); // output paragraphs - if (runparams.flavor == OutputParams::HTML) { + if (runparams.flavor == OutputParams::LYX) { + Paragraph const & par = text().paragraphs()[par_begin]; + ostringstream ods; + depth_type dt = par.getDepth(); + par.write(ods, params(), dt); + os << from_utf8(ods.str()); + } else if (runparams.flavor == OutputParams::HTML) { XHTMLStream xs(os); setMathFlavor(runparams); xhtmlParagraphs(text(), *this, xs, runparams); @@ -3334,9 +3588,31 @@ void Buffer::getSourceCode(odocstream & os, string const format, } else if (params().isDocBook()) { docbookParagraphs(text(), *this, os, runparams); } else { + // If we are previewing a paragraph, even if this is the + // child of some other buffer, let's cut the link here, + // so that no concurring settings from the master + // (e.g. branch state) interfere (see #8101). + if (!master) + d->ignore_parent = true; + // We need to validate the Buffer params' features here + // in order to know if we should output polyglossia + // macros (instead of babel macros) + LaTeXFeatures features(*this, params(), runparams); + params().validate(features); + runparams.use_polyglossia = features.usePolyglossia(); + TexRow texrow; + texrow.reset(); + texrow.newline(); + texrow.newline(); // latex or literate otexstream ots(os, texrow); + + // the real stuff latexParagraphs(*this, text(), ots, runparams); + + // Restore the parenthood + if (!master) + d->ignore_parent = false; } } else { os << "% "; @@ -3347,21 +3623,32 @@ void Buffer::getSourceCode(odocstream & os, string const format, else if (output == OnlyBody) os << _("Preview body"); os << "\n\n"; - d->texrow.reset(); - d->texrow.newline(); - d->texrow.newline(); - if (runparams.flavor == OutputParams::HTML) { + if (runparams.flavor == OutputParams::LYX) { + ostringstream ods; + if (output == FullSource) + write(ods); + else if (output == OnlyPreamble) + params().writeFile(ods); + else if (output == OnlyBody) + text().write(ods); + os << from_utf8(ods.str()); + } else if (runparams.flavor == OutputParams::HTML) { writeLyXHTMLSource(os, runparams, output); } else if (runparams.flavor == OutputParams::TEXT) { if (output == OnlyPreamble) { - os << _("% Plaintext does not have a preamble."); + os << "% "<< _("Plain text does not have a preamble."); } else writePlaintextFile(*this, os, runparams); } else if (params().isDocBook()) { writeDocBookSource(os, absFileName(), runparams, output); } else { // latex or literate + d->texrow.reset(); + d->texrow.newline(); + d->texrow.newline(); otexstream ots(os, d->texrow); + if (master) + runparams.is_child = true; writeLaTeXSource(ots, string(), runparams, output); } } @@ -3491,11 +3778,13 @@ int AutoSaveBuffer::generateChild() // to fork. But we will do the save // anyway. bool failed = false; - FileName const tmp_ret = FileName::tempName("lyxauto"); + TempFile tempfile("lyxautoXXXXXX.lyx"); + tempfile.setAutoRemove(false); + FileName const tmp_ret = tempfile.name(); if (!tmp_ret.empty()) { - buffer_.writeFile(tmp_ret); - // assume successful write of tmp_ret - if (!tmp_ret.moveTo(fname_)) + if (!buffer_.writeFile(tmp_ret)) + failed = true; + else if (!tmp_ret.moveTo(fname_)) failed = true; } else failed = true; @@ -3571,45 +3860,22 @@ bool Buffer::autoSave() const buf->d->bak_clean = true; FileName const fname = getAutosaveFileName(); - if (d->cloned_buffer_) { - // If this buffer is cloned, we assume that - // we are running in a separate thread already. - FileName const tmp_ret = FileName::tempName("lyxauto"); - if (!tmp_ret.empty()) { - writeFile(tmp_ret); - // assume successful write of tmp_ret - if (tmp_ret.moveTo(fname)) - return true; - } - // failed to write/rename tmp_ret so try writing direct - return writeFile(fname); - } else { - /// This function is deprecated as the frontend needs to take care - /// of cloning the buffer and autosaving it in another thread. It - /// is still here to allow (QT_VERSION < 0x040400). - AutoSaveBuffer autosave(*this, fname); - autosave.start(); - return true; - } -} + LASSERT(d->cloned_buffer_, return false); - -// helper class, to guarantee this gets reset properly -class Buffer::MarkAsExporting { -public: - MarkAsExporting(Buffer const * buf) : buf_(buf) - { - LASSERT(buf_, /* */); - buf_->setExportStatus(true); - } - ~MarkAsExporting() - { - buf_->setExportStatus(false); + // If this buffer is cloned, we assume that + // we are running in a separate thread already. + TempFile tempfile("lyxautoXXXXXX.lyx"); + tempfile.setAutoRemove(false); + FileName const tmp_ret = tempfile.name(); + if (!tmp_ret.empty()) { + writeFile(tmp_ret); + // assume successful write of tmp_ret + if (tmp_ret.moveTo(fname)) + return true; } -private: - Buffer const * const buf_; -}; - + // failed to write/rename tmp_ret so try writing direct + return writeFile(fname); +} void Buffer::setExportStatus(bool e) const @@ -3696,6 +3962,7 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir runparams.includeall = includeall; vector backs = params().backends(); Converters converters = theConverters(); + bool need_nice_file = false; if (find(backs.begin(), backs.end(), format) == backs.end()) { // Get shortest path to format converters.buildGraph(); @@ -3719,6 +3986,13 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir 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) { + need_nice_file = true; + break; + } } else { backend_format = format; @@ -3758,7 +4032,7 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir makeDocBookFile(FileName(filename), runparams); } // LaTeX backend - else if (backend_format == format) { + else if (backend_format == format || need_nice_file) { runparams.nice = true; bool const success = makeLaTeXFile(FileName(filename), string(), runparams); if (d->cloned_buffer_) @@ -3792,17 +4066,21 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir // Emit the signal to show the error list or copy it back to the // cloned Buffer so that it can be emitted afterwards. if (format != backend_format) { - if (d->cloned_buffer_) { + if (runparams.silent) + error_list.clear(); + else if (d->cloned_buffer_) d->cloned_buffer_->d->errorLists[error_type] = d->errorLists[error_type]; - } else + else 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) { - if (d->cloned_buffer_) { + if (runparams.silent) + (*cit)->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. @@ -3906,6 +4184,7 @@ Buffer::ExportStatus Buffer::preview(string const & format) const return preview(format, update_unincluded); } + Buffer::ExportStatus Buffer::preview(string const & format, bool includeall) const { MarkAsExporting exporting(this); @@ -3966,6 +4245,7 @@ Buffer::ReadStatus Buffer::loadEmergency() "file."), from_utf8(d->filename.absFileName()))); } markDirty(); + lyxvc().file_found_hook(d->filename); str = _("Document was successfully recovered."); } else str = _("Document was NOT successfully recovered."); @@ -4029,6 +4309,7 @@ Buffer::ReadStatus Buffer::loadAutosave() from_utf8(d->filename.absFileName()))); } markDirty(); + lyxvc().file_found_hook(d->filename); return ReadSuccess; } return ReadAutosaveFailure; @@ -4134,13 +4415,17 @@ void Buffer::setBuffersForInsets() const void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const { + LBUFERR(!text().paragraphs().empty()); + // Use the master text class also for child documents Buffer const * const master = masterBuffer(); DocumentClass const & textclass = master->params().documentClass(); // do this only if we are the top-level Buffer - if (master == this) + if (master == this) { + textclass.counters().reset(from_ascii("bibitem")); reloadBibInfoCache(); + } // keep the buffers to be children in this set. If the call from the // master comes back we can see which of them were actually seen (i.e. @@ -4151,9 +4436,15 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const if (master != this) { bufToUpdate.insert(this); master->updateBuffer(UpdateMaster, utype); - // Do this here in case the master has no gui associated with it. Then, - // the TocModel is not updated and TocModel::toc_ is invalid (bug 5699). - if (!master->d->gui_) + // 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 + // 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. + if (master->d->gui_ != d->gui_) structureChanged(); // was buf referenced from the master (i.e. not in bufToUpdate anymore)? @@ -4174,8 +4465,6 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const Buffer & cbuf = const_cast(*this); - LASSERT(!text().paragraphs().empty(), /**/); - // do the real work ParIterator parit = cbuf.par_iterator_begin(); updateBuffer(parit, utype); @@ -4186,7 +4475,7 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const d->bibinfo_cache_valid_ = true; d->cite_labels_valid_ = true; - cbuf.tocBackend().update(); + cbuf.tocBackend().update(utype == OutputUpdate); if (scope == UpdateMaster) cbuf.structureChanged(); } @@ -4247,7 +4536,7 @@ static depth_type getItemDepth(ParIterator const & it) static bool needEnumCounterReset(ParIterator const & it) { Paragraph const & par = *it; - LASSERT(par.layout().labeltype == LABEL_ENUMERATE, /**/); + LASSERT(par.layout().labeltype == LABEL_ENUMERATE, return false); depth_type const cur_depth = par.getDepth(); ParIterator prev_it = it; while (prev_it.pit()) { @@ -4271,9 +4560,11 @@ void Buffer::Impl::setLabel(ParIterator & it, UpdateType utype) const Counters & counters = textclass.counters(); if (par.params().startOfAppendix()) { - // FIXME: only the counter corresponding to toplevel - // sectioning should be reset - counters.reset(); + // We want to reset the counter corresponding to toplevel sectioning + Layout const & lay = textclass.getTOCLayout(); + docstring const cnt = lay.counter; + if (!cnt.empty()) + counters.reset(cnt); counters.appendix(true); } par.params().appendix(counters.appendix()); @@ -4292,17 +4583,6 @@ void Buffer::Impl::setLabel(ParIterator & it, UpdateType utype) const } switch(layout.labeltype) { - case LABEL_COUNTER: - if (layout.toclevel <= bp.secnumdepth - && (layout.latextype != LATEX_ENVIRONMENT - || it.text()->isFirstInSequence(it.pit()))) { - if (counters.hasCounter(layout.counter)) - counters.step(layout.counter, utype); - par.params().labelString(par.expandLabel(layout, bp)); - } else - par.params().labelString(docstring()); - break; - case LABEL_ITEMIZE: { // At some point of time we should do something more // clever here, like: @@ -4382,19 +4662,34 @@ void Buffer::Impl::setLabel(ParIterator & it, UpdateType utype) const par.params().labelString(docstring()); break; + case LABEL_ABOVE: + case LABEL_CENTERED: + case LABEL_STATIC: { + docstring const & lcounter = layout.counter; + if (!lcounter.empty()) { + if (layout.toclevel <= bp.secnumdepth + && (layout.latextype != LATEX_ENVIRONMENT + || it.text()->isFirstInSequence(it.pit()))) { + if (counters.hasCounter(lcounter)) + counters.step(lcounter, utype); + par.params().labelString(par.expandLabel(layout, bp)); + } else + par.params().labelString(docstring()); + } else + par.params().labelString(par.expandLabel(layout, bp)); + break; + } + case LABEL_MANUAL: - case LABEL_TOP_ENVIRONMENT: - case LABEL_CENTERED_TOP_ENVIRONMENT: - case LABEL_STATIC: case LABEL_BIBLIO: par.params().labelString(par.expandLabel(layout, bp)); - break; } } void Buffer::updateBuffer(ParIterator & parit, UpdateType utype) const { + // LASSERT: Is it safe to continue here, or should we just return? LASSERT(parit.pit() == 0, /**/); // Set the position of the text in the buffer to be able @@ -4405,7 +4700,17 @@ void Buffer::updateBuffer(ParIterator & parit, UpdateType utype) const pit_type const lastpit = parit.lastpit(); for ( ; parit.pit() <= lastpit ; ++parit.pit()) { // reduce depth if necessary - parit->params().depth(min(parit->params().depth(), maxdepth)); + if (parit->params().depth() > maxdepth) { + /** FIXME: this function is const, but + * nevertheless it modifies the buffer. To be + * cleaner, one should modify the buffer in + * another function, which is actually + * non-const. This would however be costly in + * terms of code duplication. + */ + const_cast(this)->undo().recordUndo(CursorData(parit)); + parit->params().depth(maxdepth); + } maxdepth = parit->getMaxDepthAfter(); if (utype == OutputUpdate) { @@ -4475,16 +4780,16 @@ void Buffer::Impl::updateStatistics(DocIterator & from, DocIterator & to, bool s word_count_ = 0; char_count_ = 0; blank_count_ = 0; - + for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) { if (!dit.inTexted()) { dit.forwardPos(); continue; } - + Paragraph const & par = dit.paragraph(); pos_type const pos = dit.pos(); - + // Copied and adapted from isWordSeparator() in Paragraph if (pos == dit.lastpos()) { inword = false; @@ -4498,7 +4803,7 @@ void Buffer::Impl::updateStatistics(DocIterator & from, DocIterator & to, bool s break; continue; } else if (!par.isDeleted(pos)) { - if (par.isWordSeparator(pos)) + if (par.isWordSeparator(pos)) inword = false; else if (!inword) { ++word_count_; @@ -4591,6 +4896,7 @@ bool Buffer::saveAs(FileName const & fn) // we need to check that the locations of child buffers // are still valid. checkChildBuffers(); + checkMasterBuffer(); return true; } else { // save failed @@ -4639,4 +4945,25 @@ void Buffer::checkChildBuffers() d->position_to_children.clear(); } + +// If a child has been saved under a different name/path, it might have been +// orphaned. Therefore the master needs to be reset (bug 8161). +void Buffer::checkMasterBuffer() +{ + Buffer const * const master = masterBuffer(); + if (master == this) + return; + + // necessary to re-register the child (bug 5873) + // FIXME: clean up updateMacros (here, only + // child registering is needed). + master->updateMacros(); + // (re)set master as master buffer, but only + // if we are a real child + if (master->isChild(this)) + setParent(master); + else + setParent(0); +} + } // namespace lyx