X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBuffer.cpp;h=43b5ecd792f76065950c61e4439f156426260392;hb=3d4076b598deb18660e50ec9c327efc3b15f15d0;hp=aa6d936aec8e6fa00b4c92a4e71c386280e21078;hpb=94f0968e9212f24997cfee0d17c6f060210edbc6;p=lyx.git diff --git a/src/Buffer.cpp b/src/Buffer.cpp index aa6d936aec..43b5ecd792 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -24,6 +24,8 @@ #include "Chktex.h" #include "Converter.h" #include "Counters.h" +#include "Cursor.h" +#include "CutAndPaste.h" #include "DispatchResult.h" #include "DocIterator.h" #include "Encoding.h" @@ -98,8 +100,9 @@ #include "support/lyxalgo.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; @@ -142,6 +144,10 @@ void showPrintError(string const & name) } // namespace anon +// A storehouse for the cloned buffers. +list cloned_buffers; + + class Buffer::Impl { public: @@ -156,7 +162,7 @@ public: } delete inset; } - + /// search for macro in local (buffer) table or in children MacroData const * getBufferMacro(docstring const & name, DocIterator const & pos) const; @@ -191,6 +197,9 @@ public: /// is this an unnamed file (New...)? bool unnamed; + /// is this an internal bufffer? + bool internal_buffer; + /// buffer is r/o bool read_only; @@ -203,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; @@ -272,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. @@ -281,11 +298,12 @@ public: // for the best. if (!cloned_buffer_ && !theBufferList().isLoaded(parent_buffer)) parent_buffer = 0; - return parent_buffer; + return parent_buffer; } - + /// - void setParent(Buffer const * pb) { + void setParent(Buffer const * pb) + { if (parent_buffer == pb) // nothing to do return; @@ -301,13 +319,35 @@ public: /// If non zero, this buffer is a clone of existing buffer \p cloned_buffer_ /// This one is useful for preview detached in a thread. Buffer const * cloned_buffer_; + /// + 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); + /// statistics accessor functions + int wordCount() const + { + return word_count_; + } + int charCount(bool with_blanks) const + { + return char_count_ + + (with_blanks ? blank_count_ : 0); + } + private: /// So we can force access via the accessors. mutable Buffer const * parent_buffer; + int word_count_; + int char_count_; + int blank_count_; + }; @@ -333,11 +373,12 @@ static FileName createBufferTmpDir() Buffer::Impl::Impl(Buffer * owner, FileName const & file, bool readonly_, Buffer const * cloned_buffer) : owner_(owner), lyx_clean(true), bak_clean(true), unnamed(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), cite_labels_valid_(false), - preview_loader_(0), cloned_buffer_(cloned_buffer), + 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) { if (!cloned_buffer_) { @@ -357,6 +398,7 @@ Buffer::Impl::Impl(Buffer * owner, FileName const & file, bool readonly_, 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; } @@ -395,61 +437,110 @@ Buffer::~Buffer() return; } - // 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 (isClone()) - delete child; - // The child buffer might have been closed already. - else if (theBufferList().isLoaded(child)) - theBufferList().releaseChild(this, child); - } + if (isClone()) { + // this is in case of recursive includes: we won't try to delete + // 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); + if (d->clone_list_->erase(child)) + delete child; + } + // 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. 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_); + 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? + // clear references to children in macro tables + d->children_positions.clear(); + 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); + } - if (!isClean()) { - docstring msg = _("LyX attempted to close a document that had unsaved changes!\n"); - msg += emergencyWrite(); - Alert::warning(_("Attempting to close changed document!"), msg); - } - - // clear references to children in macro tables - d->children_positions.clear(); - d->position_to_children.clear(); + if (!isClean()) { + docstring msg = _("LyX attempted to close a document that had unsaved changes!\n"); + msg += emergencyWrite(); + Alert::warning(_("Attempting to close changed document!"), msg); + } - if (!d->cloned_buffer_ && !d->temppath.destroyDirectory()) { - Alert::warning(_("Could not remove temporary directory"), - bformat(_("Could not remove the temporary directory %1$s"), - from_utf8(d->temppath.absFileName()))); - } + // FIXME Do we really need to do this right before we delete d? + // clear references to children in macro tables + d->children_positions.clear(); + d->position_to_children.clear(); - if (!isClone()) + if (!d->temppath.destroyDirectory()) { + Alert::warning(_("Could not remove temporary directory"), + bformat(_("Could not remove the temporary directory %1$s"), + from_utf8(d->temppath.absFileName()))); + } removePreviews(); + } delete d; } -Buffer * Buffer::clone() const +Buffer * Buffer::cloneFromMaster() const { BufferMap bufmap; - masterBuffer()->clone(bufmap); - BufferMap::iterator it = bufmap.find(this); - LASSERT(it != bufmap.end(), return 0); - return it->second; + cloned_buffers.push_back(new CloneList); + CloneList * clones = cloned_buffers.back(); + + masterBuffer()->cloneWithChildren(bufmap, clones); + + // make sure we got cloned + BufferMap::const_iterator bit = bufmap.find(this); + LASSERT(bit != bufmap.end(), return 0); + Buffer * cloned_buffer = bit->second; + + return cloned_buffer; } -void Buffer::clone(BufferMap & bufmap) const +void Buffer::cloneWithChildren(BufferMap & bufmap, CloneList * clones) const { // have we already been cloned? if (bufmap.find(this) != bufmap.end()) 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(); + 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. @@ -461,7 +552,7 @@ void Buffer::clone(BufferMap & bufmap) const dit.setBuffer(buffer_clone); Buffer * child = const_cast(it->second.second); - child->clone(bufmap); + child->cloneWithChildren(bufmap, clones); BufferMap::iterator const bit = bufmap.find(child); LASSERT(bit != bufmap.end(), continue); Buffer * child_clone = bit->second; @@ -479,6 +570,28 @@ void Buffer::clone(BufferMap & bufmap) const } +Buffer * Buffer::cloneBufferOnly() const { + 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(); + 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; +} + + bool Buffer::isClone() const { return d->cloned_buffer_; @@ -494,7 +607,7 @@ void Buffer::changed(bool update_metrics) const frontend::WorkAreaManager & Buffer::workAreaManager() const { - LASSERT(d->wa_, /**/); + LBUFERR(d->wa_); return *d->wa_; } @@ -674,7 +787,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(); @@ -720,6 +836,7 @@ 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(); for (int i = 0; i < 4; ++i) { params().user_defined_bullet(i) = ITEMIZE_DEFAULTS[i]; @@ -819,7 +936,7 @@ bool Buffer::readDocument(Lexer & lex) FileName const master_file = makeAbsPath(params().master, onlyPath(absFileName())); if (isLyXFileName(master_file.absFileName())) { - Buffer * master = + Buffer * master = checkAndLoadLyXFile(master_file, true); if (master) { // necessary e.g. after a reload @@ -844,7 +961,7 @@ bool Buffer::readDocument(Lexer & lex) } } } - + // assure we have a default index params().indiceslist().addDefault(B_("Index")); @@ -866,6 +983,49 @@ 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()); + bool const success = (os << contents); + 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; @@ -876,22 +1036,23 @@ bool Buffer::readString(string const & s) FileName const fn = FileName::tempName("Buffer_readString"); 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; + if (fn.exists()) + fn.removeFile(); + return success; } @@ -901,7 +1062,7 @@ Buffer::ReadStatus Buffer::readFile(FileName const & fn) Lexer lex; if (!lex.setFile(fname)) { Alert::error(_("File Not Found"), - bformat(_("Unable to open file `%1$s'."), + bformat(_("Unable to open file `%1$s'."), from_utf8(fn.absFileName()))); return ReadFileNotFound; } @@ -919,7 +1080,7 @@ Buffer::ReadStatus Buffer::readFile(FileName const & fn) return readFile(tmpFile); } - // FIXME: InsetInfo needs to know whether the file is under VCS + // FIXME: InsetInfo needs to know whether the file is under VCS // during the parse process, so this has to be done before. lyxvc().file_found_hook(d->filename); @@ -933,7 +1094,7 @@ Buffer::ReadStatus Buffer::readFile(FileName const & fn) d->file_fully_loaded = true; d->read_only = !d->filename.isWritable(); - params().compressed = d->filename.isZippedFile(); + params().compressed = formats.isZippedFile(d->filename); saveCheckSum(); return ReadSuccess; } @@ -991,7 +1152,7 @@ Buffer::ReadStatus Buffer::parseLyXFormat(Lexer & lex, bformat(_("%1$s is not a readable LyX document."), from_utf8(fn.absFileName()))); return ReadNoLyXFormat; - } + } string tmp_format; lex >> tmp_format; @@ -1008,10 +1169,10 @@ Buffer::ReadStatus Buffer::parseLyXFormat(Lexer & lex, } -Buffer::ReadStatus Buffer::convertLyXFormat(FileName const & fn, +Buffer::ReadStatus Buffer::convertLyXFormat(FileName const & fn, FileName & tmpfile, int from_format) { - tmpfile = FileName::tempName("Buffer_convertLyXFormat"); + tmpfile = FileName::tempName("Buffer_convertLyXFormatXXXXXX.lyx"); if(tmpfile.empty()) { Alert::error(_("Conversion failed"), bformat(_("%1$s is from a different" @@ -1086,7 +1247,7 @@ bool Buffer::save() const // ask if the disk file has been externally modified (use checksum method) if (fileName().exists() && isExternallyModified(checksum_method)) { - docstring text = + docstring text = bformat(_("Document %1$s has been externally modified. " "Are you sure you want to overwrite this file?"), file); int const ret = Alert::prompt(_("Overwrite modified file?"), @@ -1141,11 +1302,6 @@ bool Buffer::save() const 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; @@ -1315,7 +1471,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 " @@ -1342,7 +1498,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; @@ -1356,7 +1512,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; @@ -1385,12 +1541,19 @@ 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); @@ -1398,12 +1561,13 @@ void Buffer::writeLaTeXSource(otexstream & os, LYXERR(Debug::LATEX, " Validating buffer..."); LaTeXFeatures features(*this, params(), runparams); validate(features); + 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) @@ -1439,9 +1603,8 @@ void Buffer::writeLaTeXSource(otexstream & os, Encoding const * const enc = runparams.encoding; if (enc) { for (size_t n = 0; n < inputpath.size(); ++n) { - docstring const glyph = - docstring(1, inputpath[n]); - if (enc->latexChar(inputpath[n], true) != glyph) { + if (!enc->encodable(inputpath[n])) { + docstring const glyph(1, inputpath[n]); LYXERR0("Uncodable character '" << glyph << "' in input path!"); @@ -1496,15 +1659,18 @@ 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"); - 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"; @@ -1516,28 +1682,19 @@ void Buffer::writeLaTeXSource(otexstream & os, int num_lines = (*it)->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."); - // 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; @@ -1647,18 +1804,18 @@ void Buffer::writeDocBookSource(odocstream & os, string const & fname, else top += params().language->code().substr(0, 2); top += '"'; - + if (!params().options.empty()) { top += ' '; top += params().options; } - + os << "\n"; - + params().documentClass().counters().reset(); - + sgml::openTag(os, top); os << '\n'; docbookParagraphs(text(), *this, os, runparams); @@ -1700,11 +1857,11 @@ void Buffer::writeLyXHTMLSource(odocstream & os, bool const output_preamble = output == FullSource || output == OnlyPreamble; bool const output_body = - output == FullSource || output == OnlyBody; + output == FullSource || output == OnlyBody || output == IncludedFile; if (output_preamble) { os << "\n" - << "\n" + << "\n" // FIXME Language should be set properly. << "\n" << "\n" @@ -1714,47 +1871,85 @@ void Buffer::writeLyXHTMLSource(odocstream & os, docstring const & doctitle = features.htmlTitle(); os << "" - << (doctitle.empty() ? - from_ascii("LyX Document") : + << (doctitle.empty() ? + from_ascii("LyX Document") : html::htmlize(doctitle, XHTMLStream::ESCAPE_ALL)) << "\n"; - os << "\n\n" - << features.getTClassHTMLPreamble() - << "\n\n" - << from_utf8(features.getPreambleSnippets()); - - os << "\n\n"; - docstring const styleinfo = features.getTClassHTMLStyles(); - if (!styleinfo.empty()) { - os << "\n"; - } + docstring styles = features.getTClassHTMLPreamble(); + if (!styles.empty()) + os << "\n\n" << styles << '\n'; + + styles = from_utf8(features.getPreambleSnippets()); + 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()); + if (!styles.empty()) + css << "/* LyX Provided Styles */\n" << styles << '\n'; + + styles = features.getTClassHTMLStyles(); + if (!styles.empty()) + css << "/* Layout-provided Styles */\n" << styles << '\n'; bool const needfg = params().fontcolor != RGBColor(0, 0, 0); bool const needbg = params().backgroundcolor != RGBColor(0xFF, 0xFF, 0xFF); if (needfg || needbg) { - os << "\n"; + css << "}\n"; + } + + docstring const dstyles = css.str(); + if (!dstyles.empty()) { + bool written = false; + if (params().html_css_as_file) { + // open a file for CSS info + ofdocstream ocss; + string const fcssname = addName(temppath(), "docstyle.css"); + FileName const fcssfile = FileName(fcssname); + if (openFileWrite(ocss, fcssfile)) { + ocss << dstyles; + ocss.close(); + written = true; + // write link to header + os << "\n"; + // register file + runparams.exportdata->addExternalFile("xhtml", fcssfile); + } + } + // we are here if the CSS is supposed to be written to the header + // or if we failed to write it to an external file. + if (!written) { + os << "\n"; + } } os << "\n"; } if (output_body) { - os << "\n"; + bool const output_body_tag = (output != IncludedFile); + 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); - os << "\n"; + if (output_body_tag) + os << "\n"; } if (output_preamble) @@ -1790,7 +1985,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); @@ -1806,7 +2001,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))); @@ -1989,12 +2188,37 @@ 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_; } +void Buffer::removeBiblioTempFiles() const +{ + // We remove files that contain LaTeX commands specific to the + // particular bibliographic style being used, in order to avoid + // LaTeX errors when we switch style. + FileName const aux_file(addName(temppath(), changeExtension(latexName(),".aux"))); + FileName const bbl_file(addName(temppath(), changeExtension(latexName(),".bbl"))); + LYXERR(Debug::FILES, "Removing the .aux file " << aux_file); + aux_file.removeFile(); + LYXERR(Debug::FILES, "Removing the .bbl file " << bbl_file); + bbl_file.removeFile(); + // Also for the parent buffer + Buffer const * const pbuf = parent(); + if (pbuf) + pbuf->removeBiblioTempFiles(); +} + + bool Buffer::isDepClean(string const & name) const { DepClean::const_iterator const it = d->dep_clean.find(name); @@ -2022,65 +2246,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_ACTIVATE: - case LFUN_BRANCH_DEACTIVATE: { - BranchList const & branchList = params().branchlist(); - docstring const branchName = cmd.argument(); - enable = !branchName.empty() && branchList.find(branchName); - 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_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_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_BUFFER_LANGUAGE: - enable = !isReadonly(); - break; + case LFUN_BUFFER_LANGUAGE: + enable = !isReadonly(); + break; - default: - return false; + default: + return false; } flag.setEnabled(enable); return true; @@ -2094,7 +2323,7 @@ void Buffer::dispatch(string const & command, DispatchResult & result) // NOTE We can end up here even if we have no GUI, because we are called -// by LyX::exec to handled command-line requests. So we may need to check +// by LyX::exec to handled command-line requests. So we may need to check // whether we have a GUI or not. The boolean use_gui holds this information. void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) { @@ -2111,8 +2340,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; @@ -2121,7 +2353,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) ExportStatus const status = doExport(argument, false); dr.setError(status != ExportSuccess); if (status != ExportSuccess) - dr.setMessage(bformat(_("Error exporting to format: %1$s."), + dr.setMessage(bformat(_("Error exporting to format: %1$s."), func.argument())); break; } @@ -2185,6 +2417,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()) { @@ -2220,31 +2488,6 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) break; } - case LFUN_BRANCH_ACTIVATE: - case LFUN_BRANCH_DEACTIVATE: { - BranchList & branchList = params().branchlist(); - docstring const branchName = func.argument(); - // the case without a branch name is handled elsewhere - if (branchName.empty()) { - dispatched = false; - break; - } - Branch * branch = branchList.find(branchName); - if (!branch) { - LYXERR0("Branch " << branchName << " does not exist."); - dr.setError(true); - docstring const msg = - bformat(_("Branch \"%1$s\" does not exist."), branchName); - dr.setMessage(msg); - } else { - branch->setSelected(func.action() == LFUN_BRANCH_ACTIVATE); - dr.setError(false); - dr.screenUpdate(Update::Force); - dr.forceBufferUpdate(); - } - break; - } - case LFUN_BRANCHES_RENAME: { if (func.argument().empty()) break; @@ -2258,7 +2501,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; @@ -2284,7 +2527,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) case LFUN_BUFFER_PRINT: { // we'll assume there's a problem until we succeed - dr.setError(true); + dr.setError(true); string target = func.getArg(0); string target_name = func.getArg(1); string command = func.getArg(2); @@ -2293,14 +2536,14 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) || target_name.empty() || command.empty()) { LYXERR0("Unable to parse " << func.argument()); - docstring const msg = + 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 = + docstring const msg = bformat(_("Unrecognized target \"%1$s\""), from_utf8(target)); dr.setMessage(msg); break; @@ -2390,7 +2633,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) res = one.startscript(stype, command, filePath()); } - if (res == 0) + if (res == 0) dr.setError(false); else { dr.setMessage(_("Error running external commands.")); @@ -2410,8 +2653,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(), @@ -2522,7 +2765,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()) @@ -2578,7 +2821,13 @@ bool Buffer::isUnnamed() const /// retrieving fileName() nor for checking if it is unnamed or not. bool Buffer::isInternal() const { - return fileName().extension() == "internal"; + return d->internal_buffer; +} + + +void Buffer::setInternal(bool flag) +{ + d->internal_buffer = flag; } @@ -2612,7 +2861,12 @@ string Buffer::absFileName() const string Buffer::filePath() const { - return d->filename.onlyPath().absFileName() + "/"; + string const abs = d->filename.onlyPath().absFileName(); + if (abs.empty()) + return abs; + int last = abs.length() - 1; + + return abs[last] == '/' ? abs : abs + '/'; } @@ -2674,7 +2928,7 @@ DocIterator Buffer::firstChildPosition(Buffer const * child) bool Buffer::hasChildren() const { - return !d->children_positions.empty(); + return !d->children_positions.empty(); } @@ -2690,7 +2944,7 @@ void Buffer::collectChildren(ListOfBuffers & clist, bool grand_children) const if (bit != clist.end()) continue; clist.push_back(child); - if (grand_children) + if (grand_children) // there might be grandchildren child->collectChildren(clist, true); } @@ -2774,7 +3028,7 @@ MacroData const * Buffer::Impl::getBufferMacro(docstring const & name, // try previous macro if there is one if (it == nameIt->second.begin()) break; - it--; + --it; } } } @@ -2904,7 +3158,7 @@ void Buffer::Impl::updateMacros(DocIterator & it, DocIterator & scope) it.pop_back(); continue; } - + if (iit->inset->asInsetTabular()) { CursorSlice slice(*iit->inset); size_t const numcells = slice.nargs(); @@ -3109,10 +3363,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()) @@ -3128,7 +3382,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)); } @@ -3151,12 +3412,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 @@ -3174,21 +3432,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, pit_type par_begin, pit_type par_end, - OutputWhat output) const + OutputWhat output, bool master) const { OutputParams runparams(¶ms().encoding()); runparams.nice = true; @@ -3211,40 +3467,87 @@ 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 (params().isDocBook()) - docbookParagraphs(text(), *this, os, runparams); - else 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); + } else if (runparams.flavor == OutputParams::TEXT) { + bool dummy; + // FIXME Handles only one paragraph, unlike the others. + // Probably should have some routine with a signature like them. + writePlaintextParagraph(*this, + text().paragraphs()[par_begin], os, runparams, dummy); + } 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 << "% "; - if (output == FullSource) + if (output == FullSource) os << _("Preview source code"); else if (output == OnlyPreamble) os << _("Preview preamble"); else if (output == OnlyBody) os << _("Preview body"); os << "\n\n"; - d->texrow.reset(); - d->texrow.newline(); - d->texrow.newline(); - if (params().isDocBook()) - writeDocBookSource(os, absFileName(), runparams, output); - else 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 { + } else if (runparams.flavor == OutputParams::TEXT) { + if (output == OnlyPreamble) { + 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); } } @@ -3351,7 +3654,7 @@ private: int AutoSaveBuffer::generateChild() { #if defined(__APPLE__) - /* FIXME fork() is not usable for autosave on Mac OS X 10.6 (snow leopard) + /* FIXME fork() is not usable for autosave on Mac OS X 10.6 (snow leopard) * We should use something else like threads. * * Since I do not know how to determine at run time what is the OS X @@ -3411,7 +3714,7 @@ FileName Buffer::getEmergencyFileName() const FileName Buffer::getAutosaveFileName() const { // if the document is unnamed try to save in the backup dir, else - // in the default document path, and as a last try in the filePath, + // in the default document path, and as a last try in the filePath, // which will most often be the temporary directory string fpath; if (isUnnamed()) @@ -3444,7 +3747,7 @@ void Buffer::moveAutosaveFile(support::FileName const & oldauto) const } -bool Buffer::autoSave() const +bool Buffer::autoSave() const { Buffer const * buf = d->cloned_buffer_ ? d->cloned_buffer_ : this; if (buf->d->bak_clean || isReadonly()) @@ -3452,52 +3755,27 @@ bool Buffer::autoSave() const message(_("Autosaving current document...")); 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; - } -} + FileName const fname = getAutosaveFileName(); + 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. + 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; } -private: - Buffer const * const buf_; -}; - + // failed to write/rename tmp_ret so try writing direct + return writeFile(fname); +} void Buffer::setExportStatus(bool e) const { - d->doing_export = e; + d->doing_export = e; ListOfBuffers clist = getDescendents(); ListOfBuffers::const_iterator cit = clist.begin(); ListOfBuffers::const_iterator const cen = clist.end(); @@ -3527,8 +3805,8 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir && !params().getIncludedChildren().empty(); // (1) export with all included children (omit \includeonly) - if (update_unincluded) { - ExportStatus const status = + if (update_unincluded) { + ExportStatus const status = doExport(target, put_in_tempdir, true, result_file); if (status != ExportSuccess) return status; @@ -3538,6 +3816,25 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir } +void Buffer::setMathFlavor(OutputParams & op) const +{ + switch (params().html_math_output) { + case BufferParams::MathML: + op.math_flavor = OutputParams::MathAsMathML; + break; + case BufferParams::HTML: + op.math_flavor = OutputParams::MathAsHTML; + break; + case BufferParams::Images: + op.math_flavor = OutputParams::MathAsImages; + break; + case BufferParams::LaTeX: + op.math_flavor = OutputParams::MathAsLaTeX; + break; + } +} + + Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir, bool includeall, string & result_file) const { @@ -3560,6 +3857,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(); @@ -3582,7 +3880,14 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir } return ExportNoPathToFormat; } - runparams.flavor = converters.getFlavor(path); + 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; @@ -3612,20 +3917,7 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir // HTML backend else if (backend_format == "xhtml") { runparams.flavor = OutputParams::HTML; - switch (params().html_math_output) { - case BufferParams::MathML: - runparams.math_flavor = OutputParams::MathAsMathML; - break; - case BufferParams::HTML: - runparams.math_flavor = OutputParams::MathAsHTML; - break; - case BufferParams::Images: - runparams.math_flavor = OutputParams::MathAsImages; - break; - case BufferParams::LaTeX: - runparams.math_flavor = OutputParams::MathAsLaTeX; - break; - } + setMathFlavor(runparams); makeLyXHTMLFile(FileName(filename), runparams); } else if (backend_format == "lyx") writeFile(FileName(filename)); @@ -3635,15 +3927,13 @@ 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; - if (!makeLaTeXFile(FileName(filename), string(), runparams)) { - if (d->cloned_buffer_) { - d->cloned_buffer_->d->errorLists["Export"] = - d->errorLists["Export"]; - } + bool const success = makeLaTeXFile(FileName(filename), string(), runparams); + if (d->cloned_buffer_) + d->cloned_buffer_->d->errorLists["Export"] = d->errorLists["Export"]; + if (!success) return ExportError; - } } else if (!lyxrc.tex_allows_spaces && contains(filePath(), ' ')) { Alert::error(_("File name error"), @@ -3651,13 +3941,12 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir return ExportTexPathHasSpaces; } else { runparams.nice = false; - if (!makeLaTeXFile(FileName(filename), filePath(), runparams)) { - if (d->cloned_buffer_) { - d->cloned_buffer_->d->errorLists["Export"] = - d->errorLists["Export"]; - } + bool const success = makeLaTeXFile( + FileName(filename), filePath(), runparams); + if (d->cloned_buffer_) + d->cloned_buffer_->d->errorLists["Export"] = d->errorLists["Export"]; + if (!success) return ExportError; - } } string const error_type = (format == "program") @@ -3673,9 +3962,9 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir // cloned Buffer so that it can be emitted afterwards. if (format != backend_format) { if (d->cloned_buffer_) { - d->cloned_buffer_->d->errorLists[error_type] = + 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(); @@ -3687,7 +3976,7 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir // 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->cloned_buffer_->d->errorLists[error_type] = (*cit)->d->errorLists[error_type]; } else (*cit)->errors(error_type, true); @@ -3725,7 +4014,7 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir bool use_force = use_gui ? lyxrc.export_overwrite == ALL_FILES : 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) { @@ -3748,8 +4037,8 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir if (status == CANCEL) { message(_("Document export cancelled.")); return ExportCancel; - } - + } + if (tmp_result_file.exists()) { // Finally copy the main file use_force = use_gui ? lyxrc.export_overwrite != NO_FILES @@ -3791,7 +4080,7 @@ Buffer::ExportStatus Buffer::preview(string const & format, bool includeall) con MarkAsExporting exporting(this); string result_file; // (1) export with all included children (omit \includeonly) - if (includeall) { + if (includeall) { ExportStatus const status = doExport(format, true, true, result_file); if (status != ExportSuccess) return status; @@ -3820,14 +4109,14 @@ Buffer::ReadStatus Buffer::extractFromVC() Buffer::ReadStatus Buffer::loadEmergency() { FileName const emergencyFile = getEmergencyFileName(); - if (!emergencyFile.exists() + if (!emergencyFile.exists() || emergencyFile.lastModified() <= d->filename.lastModified()) return ReadFileNotFound; docstring const file = makeDisplayPath(d->filename.absFileName(), 20); docstring const text = bformat(_("An emergency save of the document " "%1$s exists.\n\nRecover emergency save?"), file); - + int const load_emerg = Alert::prompt(_("Load emergency save?"), text, 0, 2, _("&Recover"), _("&Load Original"), _("&Cancel")); @@ -3846,13 +4135,14 @@ 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."); str += "\n\n" + bformat(_("Remove emergency file now?\n(%1$s)"), makeDisplayPath(emergencyFile.absFileName())); - int const del_emerg = + int const del_emerg = Alert::prompt(_("Delete emergency file?"), str, 1, 1, _("&Remove"), _("&Keep")); if (del_emerg == 0) { @@ -3884,16 +4174,16 @@ Buffer::ReadStatus Buffer::loadAutosave() { // Now check if autosave file is newer. FileName const autosaveFile = getAutosaveFileName(); - if (!autosaveFile.exists() - || autosaveFile.lastModified() <= d->filename.lastModified()) + if (!autosaveFile.exists() + || autosaveFile.lastModified() <= d->filename.lastModified()) return ReadFileNotFound; docstring const file = makeDisplayPath(d->filename.absFileName(), 20); - docstring const text = bformat(_("The backup of the document %1$s " + docstring const text = bformat(_("The backup of the document %1$s " "is newer.\n\nLoad the backup instead?"), file); int const ret = Alert::prompt(_("Load backup?"), text, 0, 2, _("&Load backup"), _("Load &original"), _("&Cancel")); - + switch (ret) { case 0: { @@ -3905,10 +4195,11 @@ Buffer::ReadStatus Buffer::loadAutosave() bformat(_("A backup file is successfully loaded, " "but the original file %1$s is marked read-only. " "Please make sure to save the document as a " - "different file."), + "different file."), from_utf8(d->filename.absFileName()))); } markDirty(); + lyxvc().file_found_hook(d->filename); return ReadSuccess; } return ReadAutosaveFailure; @@ -3935,7 +4226,7 @@ Buffer::ReadStatus Buffer::loadLyXFile() ReadStatus const ret_re = loadEmergency(); if (ret_re == ReadSuccess || ret_re == ReadCancel) return ret_re; - + ReadStatus const ret_ra = loadAutosave(); if (ret_ra == ReadSuccess || ret_ra == ReadCancel) return ret_ra; @@ -4008,16 +4299,18 @@ void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const void Buffer::setBuffersForInsets() const { - inset().setBuffer(const_cast(*this)); + inset().setBuffer(const_cast(*this)); } 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) reloadBibInfoCache(); @@ -4031,7 +4324,7 @@ 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, + // 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_) structureChanged(); @@ -4054,8 +4347,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); @@ -4066,7 +4357,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(); } @@ -4127,7 +4418,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()) { @@ -4151,9 +4442,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()); @@ -4172,17 +4465,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: @@ -4248,13 +4530,13 @@ void Buffer::Impl::setLabel(ParIterator & it, UpdateType utype) const if (counters.hasCounter(from_utf8(type))) { string const & lang = par.getParLanguage(bp)->code(); counters.step(from_utf8(type), utype); - full_label = bformat(from_ascii("%1$s %2$s:"), - name, + full_label = bformat(from_ascii("%1$s %2$s:"), + name, counters.theCounter(from_utf8(type), lang)); } else - full_label = bformat(from_ascii("%1$s #:"), name); + full_label = bformat(from_ascii("%1$s #:"), name); } - par.params().labelString(full_label); + par.params().labelString(full_label); break; } @@ -4262,19 +4544,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 @@ -4285,7 +4582,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) { @@ -4295,7 +4602,7 @@ void Buffer::updateBuffer(ParIterator & parit, UpdateType utype) const masterBuffer()->params().documentClass().counters(). setActiveLayout(parit->layout()); } - + // set the counter for this paragraph d->setLabel(parit, utype); @@ -4349,7 +4656,78 @@ int Buffer::spellCheck(DocIterator & from, DocIterator & to, } -Buffer::ReadStatus Buffer::reload() +void Buffer::Impl::updateStatistics(DocIterator & from, DocIterator & to, bool skipNoOutput) +{ + bool inword = false; + 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; + } else { + Inset const * ins = par.getInset(pos); + if (ins && skipNoOutput && !ins->producesOutput()) { + // skip this inset + ++dit.top().pos(); + // stop if end of range was skipped + if (!to.atEnd() && dit >= to) + break; + continue; + } else if (!par.isDeleted(pos)) { + if (par.isWordSeparator(pos)) + inword = false; + else if (!inword) { + ++word_count_; + inword = true; + } + if (ins && ins->isLetter()) + ++char_count_; + else if (ins && ins->isSpace()) + ++blank_count_; + else { + char_type const c = par.getChar(pos); + if (isPrintableNonspace(c)) + ++char_count_; + else if (isSpace(c)) + ++blank_count_; + } + } + } + dit.forwardPos(); + } +} + + +void Buffer::updateStatistics(DocIterator & from, DocIterator & to, bool skipNoOutput) const +{ + d->updateStatistics(from, to, skipNoOutput); +} + + +int Buffer::wordCount() const +{ + return d->wordCount(); +} + + +int Buffer::charCount(bool with_blanks) const +{ + return d->charCount(with_blanks); +} + + +Buffer::ReadStatus Buffer::reload(bool clearUndo) { setBusy(true); // c.f. bug http://www.lyx.org/trac/ticket/6587 @@ -4358,6 +4736,8 @@ Buffer::ReadStatus Buffer::reload() d->filename.refresh(); docstring const disp_fn = makeDisplayPath(d->filename.absFileName()); + // clear parent. this will get reset if need be. + d->setParent(0); ReadStatus const status = loadLyXFile(); if (status == ReadSuccess) { updateBuffer(); @@ -4365,10 +4745,11 @@ Buffer::ReadStatus Buffer::reload() updateTitles(); markClean(); message(bformat(_("Document %1$s reloaded."), disp_fn)); - d->undo_.clear(); + if (clearUndo) + d->undo_.clear(); } else { message(bformat(_("Could not reload document %1$s."), disp_fn)); - } + } setBusy(false); removePreviews(); updatePreviews(); @@ -4398,6 +4779,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 @@ -4446,4 +4828,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