X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBuffer.cpp;h=5831df1a61b9509583da2b069aacb10ab0c7f507;hb=5279d5a8a58b9e59442b9f3f504ea397274e2b19;hp=204d9045b0a9233047027f442bac79336e99c11a;hpb=d4634167cc2fa047c5f88d78e46b63459cdd0c85;p=lyx.git diff --git a/src/Buffer.cpp b/src/Buffer.cpp index 204d9045b0..1dc5d592e5 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -66,10 +66,10 @@ #include "WordLangTuple.h" #include "WordList.h" -#include "insets/InsetBibitem.h" #include "insets/InsetBibtex.h" #include "insets/InsetBranch.h" #include "insets/InsetInclude.h" +#include "insets/InsetTabular.h" #include "insets/InsetText.h" #include "mathed/InsetMathHull.h" @@ -77,12 +77,12 @@ #include "mathed/MathMacroTemplate.h" #include "mathed/MathSupport.h" +#include "graphics/PreviewLoader.h" + #include "frontends/alert.h" #include "frontends/Delegates.h" #include "frontends/WorkAreaManager.h" -#include "graphics/Previews.h" - #include "support/lassert.h" #include "support/convert.h" #include "support/debug.h" @@ -117,6 +117,7 @@ using namespace std; using namespace lyx::support; +using namespace lyx::graphics; namespace lyx { @@ -125,9 +126,7 @@ namespace os = support::os; namespace { -// Do not remove the comment below, so we get merge conflict in -// independent branches. Instead add your own. -int const LYX_FORMAT = 403; // rgh: Dummy format for InsetFlex name conversion +int const LYX_FORMAT = LYX_FORMAT_LYX; typedef map DepClean; typedef map > RefCache; @@ -143,6 +142,10 @@ void showPrintError(string const & name) } // namespace anon +// A storehouse for the cloned buffers. +list cloned_buffers; + + class Buffer::Impl { public: @@ -150,13 +153,14 @@ public: ~Impl() { + delete preview_loader_; if (wa_) { wa_->closeAll(); delete wa_; } delete inset; } - + /// search for macro in local (buffer) table or in children MacroData const * getBufferMacro(docstring const & name, DocIterator const & pos) const; @@ -188,9 +192,12 @@ public: /// is autosave needed? mutable bool bak_clean; - /// is this a unnamed file (New...)? + /// is this an unnamed file (New...)? bool unnamed; + /// is this an internal bufffer? + bool internal_buffer; + /// buffer is r/o bool read_only; @@ -257,15 +264,22 @@ public: mutable bool bibfile_cache_valid_; /// Cache of timestamps of .bib files map bibfile_status_; + /// Indicates whether the bibinfo has changed since the last time + /// we ran updateBuffer(), i.e., whether citation labels may need + /// to be updated. + mutable bool cite_labels_valid_; mutable RefCache ref_cache_; /// our Text that should be wrapped in an InsetText InsetText * inset; + /// + PreviewLoader * preview_loader_; + /// This is here to force the test to be done whenever parent_buffer /// is accessed. - Buffer const * parent() const { + Buffer const * parent() const { // 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. @@ -274,9 +288,9 @@ 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) { if (parent_buffer == pb) @@ -294,13 +308,31 @@ 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_; + }; @@ -326,11 +358,13 @@ 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), cloned_buffer_(cloned_buffer), - doing_export(false), parent_buffer(0) + 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), + cite_labels_valid_(false), preview_loader_(0), + cloned_buffer_(cloned_buffer), clone_list_(0), + doing_export(false), parent_buffer(0) { if (!cloned_buffer_) { temppath = createBufferTmpDir(); @@ -347,6 +381,9 @@ Buffer::Impl::Impl(Buffer * owner, FileName const & file, bool readonly_, bibinfo_cache_valid_ = cloned_buffer_->d->bibinfo_cache_valid_; bibfile_cache_valid_ = cloned_buffer_->d->bibfile_cache_valid_; bibfile_status_ = cloned_buffer_->d->bibfile_status_; + cite_labels_valid_ = cloned_buffer_->d->cite_labels_valid_; + unnamed = cloned_buffer_->d->unnamed; + internal_buffer = cloned_buffer_->d->internal_buffer; } @@ -385,66 +422,133 @@ 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 (d->cloned_buffer_) - 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. + LASSERT(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); + 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(); - // Remove any previewed LaTeX snippets associated with this buffer. - if (!isClone()) - thePreviews().removeLoader(*this); + 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; + 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::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); + 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. // Right now BufferList knows nothing about buffer clones. - Impl::BufferPositionMap::iterator it = d->children_positions.begin(); - Impl::BufferPositionMap::iterator end = d->children_positions.end(); + Impl::PositionScopeBufferMap::iterator it = d->position_to_children.begin(); + Impl::PositionScopeBufferMap::iterator end = d->position_to_children.end(); for (; it != end; ++it) { - DocIterator dit = it->second.clone(buffer_clone); + DocIterator dit = it->first.clone(buffer_clone); dit.setBuffer(buffer_clone); - Buffer * child = const_cast(it->first); - Buffer * child_clone = child->clone(); + Buffer * child = const_cast(it->second.second); + + child->cloneWithChildren(bufmap, clones); + BufferMap::iterator const bit = bufmap.find(child); + LASSERT(bit != bufmap.end(), continue); + Buffer * child_clone = bit->second; + Inset * inset = dit.nextInset(); LASSERT(inset && inset->lyxCode() == INCLUDE_CODE, continue); InsetInclude * inset_inc = static_cast(inset); inset_inc->setChildBuffer(child_clone); child_clone->d->setParent(buffer_clone); + // FIXME Do we need to do this now, or can we wait until we run updateMacros()? buffer_clone->setChild(dit, child_clone); } buffer_clone->d->macro_lock = false; + return; +} + + +Buffer * Buffer::cloneBufferOnly() const { + cloned_buffers.push_back(new CloneList()); + CloneList * clones = cloned_buffers.back(); + Buffer * buffer_clone = new Buffer(fileName().absFileName(), false, this); + 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; } @@ -598,7 +702,7 @@ string Buffer::logName(LogType * type) const FileName const bname( addName(path, onlyFileName( changeExtension(filename, - formats.extension(bufferFormat()) + ".out")))); + formats.extension(params().bufferFormat()) + ".out")))); // Also consider the master buffer log file FileName masterfname = fname; @@ -642,10 +746,11 @@ void Buffer::setReadonly(bool const flag) } -void Buffer::setFileName(string const & newfile) +void Buffer::setFileName(FileName const & fname) { - d->filename = makeAbsPath(newfile); + d->filename = fname; setReadonly(d->filename.isReadOnly()); + saveCheckSum(); updateTitles(); } @@ -672,7 +777,7 @@ int Buffer::readHeader(Lexer & lex) params().headsep.erase(); params().footskip.erase(); params().columnsep.erase(); - params().fontsCJK.erase(); + params().fonts_cjk.erase(); params().listings_params.clear(); params().clearLayoutModules(); params().clearRemovedModules(); @@ -681,14 +786,15 @@ int Buffer::readHeader(Lexer & lex) params().indiceslist().clear(); params().backgroundcolor = lyx::rgbFromHexName("#ffffff"); params().isbackgroundcolor = false; - params().fontcolor = lyx::rgbFromHexName("#000000"); + params().fontcolor = RGBColor(0, 0, 0); params().isfontcolor = false; - params().notefontcolor = lyx::rgbFromHexName("#cccccc"); - params().boxbgcolor = lyx::rgbFromHexName("#ff0000"); + params().notefontcolor = RGBColor(0xCC, 0xCC, 0xCC); + params().boxbgcolor = RGBColor(0xFF, 0, 0); params().html_latex_start.clear(); 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]; @@ -753,15 +859,15 @@ bool Buffer::readDocument(Lexer & lex) ErrorList & errorList = d->errorLists["Parse"]; errorList.clear(); + // remove dummy empty par + paragraphs().clear(); + if (!lex.checkFor("\\begin_document")) { docstring const s = _("\\begin_document is missing"); errorList.push_back(ErrorItem(_("Document header error"), s, -1, 0, 0)); } - // we are reading in a brand new document - LASSERT(paragraphs().empty(), /**/); - readHeader(lex); if (params().outputChanges) { @@ -788,7 +894,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 @@ -813,16 +919,24 @@ bool Buffer::readDocument(Lexer & lex) } } } - + // assure we have a default index params().indiceslist().addDefault(B_("Index")); // read main text bool const res = text().read(lex, errorList, d->inset); + // inform parent buffer about local macros + if (parent()) { + Buffer const * pbuf = parent(); + UserMacroSet::const_iterator cit = usermacros.begin(); + UserMacroSet::const_iterator end = usermacros.end(); + for (; cit != end; ++cit) + pbuf->usermacros.insert(*cit); + } usermacros.clear(); updateMacros(); - updateMacroInstances(); + updateMacroInstances(InternalUpdate); return res; } @@ -831,44 +945,72 @@ bool Buffer::readString(string const & s) { params().compressed = false; - // remove dummy empty par - paragraphs().clear(); Lexer lex; istringstream is(s); lex.setStream(is); - FileName const name = FileName::tempName("Buffer_readString"); - switch (readFile(lex, name, true)) { - case failure: - return false; - case wrongversion: { + 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; + + if (file_format != LYX_FORMAT) { // We need to call lyx2lyx, so write the input to a file - ofstream os(name.toFilesystemEncoding().c_str()); + ofstream os(fn.toFilesystemEncoding().c_str()); os << s; os.close(); - return readFile(name); - } - case success: - break; + // lyxvc in readFile + return readFile(fn) == ReadSuccess; } + if (readDocument(lex)) + return false; return true; } -bool Buffer::readFile(FileName const & filename) +Buffer::ReadStatus Buffer::readFile(FileName const & fn) { - FileName fname(filename); + FileName fname(fn); + Lexer lex; + if (!lex.setFile(fname)) { + Alert::error(_("File Not Found"), + bformat(_("Unable to open file `%1$s'."), + from_utf8(fn.absFileName()))); + return ReadFileNotFound; + } - params().compressed = fname.isZippedFile(); + int file_format; + ReadStatus const ret_plf = parseLyXFormat(lex, fn, file_format); + if (ret_plf != ReadSuccess) + return ret_plf; - // remove dummy empty par - paragraphs().clear(); - Lexer lex; - lex.setFile(fname); - if (readFile(lex, fname) != success) - return false; + if (file_format != LYX_FORMAT) { + FileName tmpFile; + ReadStatus const ret_clf = convertLyXFormat(fn, tmpFile, file_format); + if (ret_clf != ReadSuccess) + return ret_clf; + return readFile(tmpFile); + } - return true; + // 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); + + if (readDocument(lex)) { + Alert::error(_("Document format failure"), + bformat(_("%1$s ended unexpectedly, which means" + " that it is probably corrupted."), + from_utf8(fn.absFileName()))); + return ReadDocumentFailure; + } + + d->file_fully_loaded = true; + d->read_only = !d->filename.isWritable(); + params().compressed = formats.isZippedFile(d->filename); + saveCheckSum(); + return ReadSuccess; } @@ -884,123 +1026,144 @@ void Buffer::setFullyLoaded(bool value) } -Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename, - bool fromstring) +PreviewLoader * Buffer::loader() const { - LASSERT(!filename.empty(), /**/); + if (!isExporting() && lyxrc.preview == LyXRC::PREVIEW_OFF) + return 0; + if (!d->preview_loader_) + d->preview_loader_ = new PreviewLoader(*this); + return d->preview_loader_; +} + + +void Buffer::removePreviews() const +{ + delete d->preview_loader_; + d->preview_loader_ = 0; +} - // the first (non-comment) token _must_ be... - if (!lex.checkFor("\\lyxformat")) { + +void Buffer::updatePreviews() const +{ + PreviewLoader * ploader = loader(); + if (!ploader) + return; + + InsetIterator it = inset_iterator_begin(*d->inset); + InsetIterator const end = inset_iterator_end(*d->inset); + for (; it != end; ++it) + it->addPreview(it, *ploader); + + ploader->startLoading(); +} + + +Buffer::ReadStatus Buffer::parseLyXFormat(Lexer & lex, + FileName const & fn, int & file_format) const +{ + if(!lex.checkFor("\\lyxformat")) { Alert::error(_("Document format failure"), - bformat(_("%1$s is not a readable LyX document."), - from_utf8(filename.absFileName()))); - return failure; + bformat(_("%1$s is not a readable LyX document."), + from_utf8(fn.absFileName()))); + return ReadNoLyXFormat; } string tmp_format; lex >> tmp_format; - //lyxerr << "LyX Format: `" << tmp_format << '\'' << endl; - // if present remove ".," from string. + + // LyX formats 217 and earlier were written as 2.17. This corresponds + // to files from LyX versions < 1.1.6.3. We just remove the dot in + // these cases. See also: www.lyx.org/trac/changeset/1313. size_t dot = tmp_format.find_first_of(".,"); - //lyxerr << " dot found at " << dot << endl; if (dot != string::npos) - tmp_format.erase(dot, 1); - int const file_format = convert(tmp_format); - //lyxerr << "format: " << file_format << endl; - - // save timestamp and checksum of the original disk file, making sure - // to not overwrite them with those of the file created in the tempdir - // when it has to be converted to the current format. - if (!d->checksum_) { - // Save the timestamp and checksum of disk file. If filename is an - // emergency file, save the timestamp and checksum of the original lyx file - // because isExternallyModified will check for this file. (BUG4193) - string diskfile = filename.absFileName(); - if (suffixIs(diskfile, ".emergency")) - diskfile = diskfile.substr(0, diskfile.size() - 10); - saveCheckSum(FileName(diskfile)); - } + tmp_format.erase(dot, 1); - if (file_format != LYX_FORMAT) { + file_format = convert(tmp_format); + return ReadSuccess; +} - if (fromstring) - // lyx2lyx would fail - return wrongversion; - - FileName const tmpfile = FileName::tempName("Buffer_readFile"); - if (tmpfile.empty()) { - Alert::error(_("Conversion failed"), - bformat(_("%1$s is from a different" - " version of LyX, but a temporary" - " file for converting it could" - " not be created."), - from_utf8(filename.absFileName()))); - return failure; - } - FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx"); - if (lyx2lyx.empty()) { - Alert::error(_("Conversion script not found"), - bformat(_("%1$s is from a different" - " version of LyX, but the" - " conversion script lyx2lyx" - " could not be found."), - from_utf8(filename.absFileName()))); - return failure; - } - ostringstream command; - command << os::python() - << ' ' << quoteName(lyx2lyx.toFilesystemEncoding()) - << " -t " << convert(LYX_FORMAT) - << " -o " << quoteName(tmpfile.toFilesystemEncoding()) - << ' ' << quoteName(filename.toSafeFilesystemEncoding()); - string const command_str = command.str(); - - LYXERR(Debug::INFO, "Running '" << command_str << '\''); - - cmd_ret const ret = runCommand(command_str); - if (ret.first != 0) { - if (file_format < LYX_FORMAT) - Alert::error(_("Conversion script failed"), - bformat(_("%1$s is from an older version" - " of LyX, but the lyx2lyx script" - " failed to convert it."), - from_utf8(filename.absFileName()))); - else - Alert::error(_("Conversion script failed"), - bformat(_("%1$s is from a newer version" - " of LyX and cannot be converted by the" - " lyx2lyx script."), - from_utf8(filename.absFileName()))); - return failure; - } else { - bool const ret = readFile(tmpfile); - // Do stuff with tmpfile name and buffer name here. - return ret ? success : failure; - } +Buffer::ReadStatus Buffer::convertLyXFormat(FileName const & fn, + FileName & tmpfile, int from_format) +{ + tmpfile = FileName::tempName("Buffer_convertLyXFormat"); + if(tmpfile.empty()) { + Alert::error(_("Conversion failed"), + bformat(_("%1$s is from a different" + " version of LyX, but a temporary" + " file for converting it could" + " not be created."), + from_utf8(fn.absFileName()))); + return LyX2LyXNoTempFile; } - if (readDocument(lex)) { - Alert::error(_("Document format failure"), - bformat(_("%1$s ended unexpectedly, which means" - " that it is probably corrupted."), - from_utf8(filename.absFileName()))); - return failure; + FileName const lyx2lyx = libFileSearch("lyx2lyx", "lyx2lyx"); + if (lyx2lyx.empty()) { + Alert::error(_("Conversion script not found"), + bformat(_("%1$s is from a different" + " version of LyX, but the" + " conversion script lyx2lyx" + " could not be found."), + from_utf8(fn.absFileName()))); + return LyX2LyXNotFound; } - d->file_fully_loaded = true; - return success; + // Run lyx2lyx: + // $python$ "$lyx2lyx$" -t $LYX_FORMAT$ -o "$tempfile$" "$filetoread$" + ostringstream command; + command << os::python() + << ' ' << quoteName(lyx2lyx.toFilesystemEncoding()) + << " -t " << convert(LYX_FORMAT) + << " -o " << quoteName(tmpfile.toFilesystemEncoding()) + << ' ' << quoteName(fn.toSafeFilesystemEncoding()); + string const command_str = command.str(); + + LYXERR(Debug::INFO, "Running '" << command_str << '\''); + + cmd_ret const ret = runCommand(command_str); + if (ret.first != 0) { + if (from_format < LYX_FORMAT) { + Alert::error(_("Conversion script failed"), + bformat(_("%1$s is from an older version" + " of LyX and the lyx2lyx script" + " failed to convert it."), + from_utf8(fn.absFileName()))); + return LyX2LyXOlderFormat; + } else { + Alert::error(_("Conversion script failed"), + bformat(_("%1$s is from a newer version" + " of LyX and the lyx2lyx script" + " failed to convert it."), + from_utf8(fn.absFileName()))); + return LyX2LyXNewerFormat; + } + } + return ReadSuccess; } // Should probably be moved to somewhere else: BufferView? GuiView? bool Buffer::save() const { + docstring const file = makeDisplayPath(absFileName(), 20); + d->filename.refresh(); + + // check the read-only status before moving the file as a backup + if (d->filename.exists()) { + bool const read_only = !d->filename.isWritable(); + if (read_only) { + Alert::warning(_("File is read-only"), + bformat(_("The file %1$s cannot be written because it " + "is marked as read-only."), file)); + return false; + } + } + // ask if the disk file has been externally modified (use checksum method) if (fileName().exists() && isExternallyModified(checksum_method)) { - docstring const file = makeDisplayPath(absFileName(), 20); - docstring text = bformat(_("Document %1$s has been externally modified. Are you sure " - "you want to overwrite this file?"), file); + 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?"), text, 1, 1, _("&Overwrite"), _("&Cancel")); if (ret == 1) @@ -1022,13 +1185,18 @@ bool Buffer::save() const backupName = FileName(addName(lyxrc.backupdir_path, mangledName)); } - // do not copy because of #6587 - if (fileName().moveTo(backupName)) { - madeBackup = true; - } else { + + // 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); + + if (!madeBackup) { Alert::error(_("Backup failure"), bformat(_("Cannot create backup file %1$s.\n" - "Please check whether the directory exists and is writeable."), + "Please check whether the directory exists and is writable."), from_utf8(backupName.absFileName()))); //LYXERR(Debug::DEBUG, "Fs error: " << fe.what()); } @@ -1048,6 +1216,11 @@ 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; @@ -1075,7 +1248,7 @@ bool Buffer::writeFile(FileName const & fname) const // see bug 6587 // removeAutosaveFile(); - saveCheckSum(d->filename); + saveCheckSum(); message(str + _(" done.")); return true; @@ -1101,24 +1274,24 @@ docstring Buffer::emergencyWrite() LYXERR0(" " << s); if (writeFile(FileName(s))) { markClean(); - user_message += bformat(_(" Saved to %1$s. Phew.\n"), from_utf8(s)); + user_message += " " + bformat(_("Saved to %1$s. Phew.\n"), from_utf8(s)); return user_message; } else { - user_message += _(" Save failed! Trying again...\n"); + user_message += " " + _("Save failed! Trying again...\n"); } } // 2) In HOME directory. - string s = addName(package().home_dir().absFileName(), absFileName()); + string s = addName(Package::get_home_dir().absFileName(), absFileName()); s += ".emergency"; lyxerr << ' ' << s << endl; if (writeFile(FileName(s))) { markClean(); - user_message += bformat(_(" Saved to %1$s. Phew.\n"), from_utf8(s)); + user_message += " " + bformat(_("Saved to %1$s. Phew.\n"), from_utf8(s)); return user_message; } - user_message += _(" Save failed! Trying yet again...\n"); + user_message += " " + _("Save failed! Trying yet again...\n"); // 3) In "/tmp" directory. // MakeAbsPath to prepend the current @@ -1128,11 +1301,11 @@ docstring Buffer::emergencyWrite() lyxerr << ' ' << s << endl; if (writeFile(FileName(s))) { markClean(); - user_message += bformat(_(" Saved to %1$s. Phew.\n"), from_utf8(s)); + user_message += " " + bformat(_("Saved to %1$s. Phew.\n"), from_utf8(s)); return user_message; } - user_message += _(" Save failed! Bummer. Document is lost."); + user_message += " " + _("Save failed! Bummer. Document is lost."); // Don't try again. markClean(); return user_message; @@ -1149,7 +1322,7 @@ 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 - ofs << "#LyX " << lyx_version + ofs << "#LyX " << lyx_version_major << "." << lyx_version_minor << " created this file. For more info see http://www.lyx.org/\n" << "\\lyxformat " << LYX_FORMAT << "\n" << "\\begin_document\n"; @@ -1203,14 +1376,17 @@ bool Buffer::write(ostream & ofs) const bool Buffer::makeLaTeXFile(FileName const & fname, string const & original_path, OutputParams const & runparams_in, - bool output_preamble, bool output_body) const + OutputWhat output) const { OutputParams runparams = runparams_in; - if (params().useXetex) - runparams.flavor = OutputParams::XETEX; + + // This is necessary for LuaTeX/XeTeX with tex fonts. + // See FIXME in BufferParams::encoding() + if (runparams.isFullUnicode()) + runparams.encoding = encodings.fromLyXName("utf8-plain"); string const encoding = runparams.encoding->iconvName(); - LYXERR(Debug::LATEX, "makeLaTeXFile encoding: " << encoding << "..."); + LYXERR(Debug::LATEX, "makeLaTeXFile encoding: " << encoding << ", fname=" << fname.realPath()); ofdocstream ofs; try { ofs.reset(encoding); } @@ -1228,10 +1404,18 @@ bool Buffer::makeLaTeXFile(FileName const & fname, ErrorList & errorList = d->errorLists["Export"]; errorList.clear(); bool failed_export = false; + otexstream os(ofs, d->texrow); + + // make sure we are ready to export + // this needs to be done before we validate + // FIXME Do we need to do this all the time? I.e., in children + // of a master we are exporting? + updateBuffer(); + updateMacroInstances(OutputUpdate); + try { - d->texrow.reset(); - writeLaTeXSource(ofs, original_path, - runparams, output_preamble, output_body); + os.texrow().reset(); + writeLaTeXSource(os, original_path, runparams, output); } catch (EncodingException & e) { odocstringstream ods; @@ -1273,10 +1457,10 @@ bool Buffer::makeLaTeXFile(FileName const & fname, } -void Buffer::writeLaTeXSource(odocstream & os, +void Buffer::writeLaTeXSource(otexstream & os, string const & original_path, OutputParams const & runparams_in, - bool const output_preamble, bool const output_body) const + OutputWhat output) const { // The child documents, if any, shall be already loaded at this point. @@ -1291,6 +1475,11 @@ void Buffer::writeLaTeXSource(odocstream & os, validate(features); LYXERR(Debug::LATEX, " Buffer validation done."); + bool const output_preamble = + output == FullSource || output == OnlyPreamble; + bool const output_body = + output == FullSource || output == OnlyBody; + // The starting paragraph of the coming rows is the // first paragraph of the document. (Asger) if (output_preamble && runparams.nice) { @@ -1298,48 +1487,35 @@ void Buffer::writeLaTeXSource(odocstream & os, "For more info, see http://www.lyx.org/.\n" "%% Do not edit unless you really know what " "you are doing.\n"; - d->texrow.newline(); - d->texrow.newline(); } LYXERR(Debug::INFO, "lyx document header finished"); - // Don't move this behind the parent_buffer=0 code below, - // because then the macros will not get the right "redefinition" - // flag as they don't see the parent macros which are output before. - updateMacros(); - - // fold macros if possible, still with parent buffer as the - // macros will be put in the prefix anyway. - updateMacroInstances(); - // There are a few differences between nice LaTeX and usual files: - // usual is \batchmode and has a - // special input@path to allow the including of figures - // with either \input or \includegraphics (what figinsets do). - // input@path is set when the actual parameter - // original_path is set. This is done for usual tex-file, but not - // for nice-latex-file. (Matthias 250696) + // usual files have \batchmode and special input@path to allow + // inclusion of figures specified by an explicitly relative path + // (i.e., a path starting with './' or '../') with either \input or + // \includegraphics, as the TEXINPUTS method doesn't work in this case. + // input@path is set when the actual parameter original_path is set. + // This is done for usual tex-file, but not for nice-latex-file. + // (Matthias 250696) // Note that input@path is only needed for something the user does // in the preamble, included .tex files or ERT, files included by // LyX work without it. if (output_preamble) { if (!runparams.nice) { // code for usual, NOT nice-latex-file - os << "\\batchmode\n"; // changed - // from \nonstopmode - d->texrow.newline(); + os << "\\batchmode\n"; // changed from \nonstopmode } if (!original_path.empty()) { // FIXME UNICODE // We don't know the encoding of inputpath - docstring const inputpath = from_utf8(support::latex_path(original_path)); + docstring const inputpath = from_utf8(original_path); docstring uncodable_glyphs; 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!"); @@ -1350,22 +1526,42 @@ void Buffer::writeLaTeXSource(odocstream & os, // warn user if we found uncodable glyphs. if (!uncodable_glyphs.empty()) { - frontend::Alert::warning(_("Uncodable character in file path"), - support::bformat(_("The path of your document\n" - "(%1$s)\n" - "contains glyphs that are unknown in the\n" - "current document encoding (namely %2$s).\n" - "This will likely result in incomplete output.\n\n" - "Choose an appropriate document encoding (such as utf8)\n" - "or change the file path name."), inputpath, uncodable_glyphs)); + frontend::Alert::warning( + _("Uncodable character in file path"), + support::bformat( + _("The path of your document\n" + "(%1$s)\n" + "contains glyphs that are unknown " + "in the current document encoding " + "(namely %2$s). This may result in " + "incomplete output, unless " + "TEXINPUTS contains the document " + "directory and you don't use " + "explicitly relative paths (i.e., " + "paths starting with './' or " + "'../') in the preamble or in ERT." + "\n\nIn case of problems, choose " + "an appropriate document encoding\n" + "(such as utf8) or change the " + "file path name."), + inputpath, uncodable_glyphs)); } else { + string docdir = + support::latex_path(original_path); + if (contains(docdir, '#')) { + docdir = subst(docdir, "#", "\\#"); + os << "\\catcode`\\#=11" + "\\def\\#{#}\\catcode`\\#=6\n"; + } + if (contains(docdir, '%')) { + docdir = subst(docdir, "%", "\\%"); + os << "\\catcode`\\%=11" + "\\def\\%{%}\\catcode`\\%=14\n"; + } os << "\\makeatletter\n" << "\\def\\input@path{{" - << inputpath << "/}}\n" + << docdir << "/}}\n" << "\\makeatother\n"; - d->texrow.newline(); - d->texrow.newline(); - d->texrow.newline(); } } @@ -1374,9 +1570,9 @@ void Buffer::writeLaTeXSource(odocstream & os, MacroSet parentMacros; listParentMacros(parentMacros, features); + runparams.use_polyglossia = features.usePolyglossia(); // Write the preamble runparams.use_babel = params().writeLaTeX(os, features, - d->texrow, d->filename.onlyPath()); runparams.use_japanese = features.isRequired("japanese"); @@ -1386,19 +1582,18 @@ void Buffer::writeLaTeXSource(odocstream & os, // make the body. os << "\\begin{document}\n"; - d->texrow.newline(); // output the parent macros MacroSet::iterator it = parentMacros.begin(); MacroSet::iterator end = parentMacros.end(); for (; it != end; ++it) { - int num_lines = (*it)->write(os, true); - d->texrow.newlines(num_lines); + int num_lines = (*it)->write(os.os(), true); + os.texrow().newlines(num_lines); } - + } // output_preamble - d->texrow.start(paragraphs().begin()->id(), 0); + os.texrow().start(paragraphs().begin()->id(), 0); LYXERR(Debug::INFO, "preamble finished, now the body."); @@ -1412,7 +1607,7 @@ void Buffer::writeLaTeXSource(odocstream & os, } // the real stuff - latexParagraphs(*this, text(), os, d->texrow, runparams); + latexParagraphs(*this, text(), os, runparams); // Restore the parenthood if needed if (output_preamble) @@ -1420,11 +1615,9 @@ void Buffer::writeLaTeXSource(odocstream & os, // add this just in case after all the paragraphs os << endl; - d->texrow.newline(); if (output_preamble) { os << "\\end{document}\n"; - d->texrow.newline(); LYXERR(Debug::LATEX, "makeLaTeXFile...done"); } else { LYXERR(Debug::LATEX, "LaTeXFile for inclusion made."); @@ -1432,7 +1625,7 @@ void Buffer::writeLaTeXSource(odocstream & os, runparams_in.encoding = runparams.encoding; // Just to be sure. (Asger) - d->texrow.newline(); + os.texrow().newline(); //for (int i = 0; itexrow.rows(); i++) { // int id,pos; @@ -1441,31 +1634,13 @@ void Buffer::writeLaTeXSource(odocstream & os, //} LYXERR(Debug::INFO, "Finished making LaTeX file."); - LYXERR(Debug::INFO, "Row count was " << d->texrow.rows() - 1 << '.'); -} - - -bool Buffer::isLatex() const -{ - return params().documentClass().outputType() == LATEX; -} - - -bool Buffer::isLiterate() const -{ - return params().documentClass().outputType() == LITERATE; -} - - -bool Buffer::isDocBook() const -{ - return params().documentClass().outputType() == DOCBOOK; + LYXERR(Debug::INFO, "Row count was " << os.texrow().rows() - 1 << '.'); } void Buffer::makeDocBookFile(FileName const & fname, OutputParams const & runparams, - bool const body_only) const + OutputWhat output) const { LYXERR(Debug::LATEX, "makeDocBookFile..."); @@ -1473,7 +1648,12 @@ void Buffer::makeDocBookFile(FileName const & fname, if (!openFileWrite(ofs, fname)) return; - writeDocBookSource(ofs, fname.absFileName(), runparams, body_only); + // make sure we are ready to export + // this needs to be done before we validate + updateBuffer(); + updateMacroInstances(OutputUpdate); + + writeDocBookSource(ofs, fname.absFileName(), runparams, output); ofs.close(); if (ofs.fail()) @@ -1483,7 +1663,7 @@ void Buffer::makeDocBookFile(FileName const & fname, void Buffer::writeDocBookSource(odocstream & os, string const & fname, OutputParams const & runparams, - bool const only_body) const + OutputWhat output) const { LaTeXFeatures features(*this, params(), runparams); validate(features); @@ -1493,7 +1673,12 @@ void Buffer::writeDocBookSource(odocstream & os, string const & fname, DocumentClass const & tclass = params().documentClass(); string const top_element = tclass.latexname(); - if (!only_body) { + bool const output_preamble = + output == FullSource || output == OnlyPreamble; + bool const output_body = + output == FullSource || output == OnlyBody; + + if (output_preamble) { if (runparams.flavor == OutputParams::XML) os << "\n"; @@ -1528,37 +1713,36 @@ void Buffer::writeDocBookSource(odocstream & os, string const & fname, os << ">\n\n"; } - string top = top_element; - top += " lang=\""; - if (runparams.flavor == OutputParams::XML) - top += params().language->code(); - else - top += params().language->code().substr(0, 2); - top += '"'; - - if (!params().options.empty()) { - top += ' '; - top += params().options; - } + if (output_body) { + string top = top_element; + top += " lang=\""; + if (runparams.flavor == OutputParams::XML) + top += params().language->code(); + else + top += params().language->code().substr(0, 2); + top += '"'; - os << "\n"; + if (!params().options.empty()) { + top += ' '; + top += params().options; + } - params().documentClass().counters().reset(); + os << "\n"; - updateMacros(); + params().documentClass().counters().reset(); - sgml::openTag(os, top); - os << '\n'; - docbookParagraphs(text(), *this, os, runparams); - sgml::closeTag(os, top_element); + sgml::openTag(os, top); + os << '\n'; + docbookParagraphs(text(), *this, os, runparams); + sgml::closeTag(os, top_element); + } } void Buffer::makeLyXHTMLFile(FileName const & fname, - OutputParams const & runparams, - bool const body_only) const + OutputParams const & runparams) const { LYXERR(Debug::LATEX, "makeLyXHTMLFile..."); @@ -1566,7 +1750,12 @@ void Buffer::makeLyXHTMLFile(FileName const & fname, if (!openFileWrite(ofs, fname)) return; - writeLyXHTMLSource(ofs, runparams, body_only); + // make sure we are ready to export + // this has to be done before we validate + updateBuffer(UpdateMaster, OutputUpdate); + updateMacroInstances(OutputUpdate); + + writeLyXHTMLSource(ofs, runparams, FullSource); ofs.close(); if (ofs.fail()) @@ -1576,16 +1765,18 @@ void Buffer::makeLyXHTMLFile(FileName const & fname, void Buffer::writeLyXHTMLSource(odocstream & os, OutputParams const & runparams, - bool const only_body) const + OutputWhat output) const { LaTeXFeatures features(*this, params(), runparams); validate(features); - updateBuffer(UpdateMaster, OutputUpdate); d->bibinfo_.makeCitationLabels(*this); - updateMacros(); - updateMacroInstances(); - if (!only_body) { + bool const output_preamble = + output == FullSource || output == OnlyPreamble; + bool const output_body = + output == FullSource || output == OnlyBody || output == IncludedFile; + + if (output_preamble) { os << "\n" << "\n" // FIXME Language should be set properly. @@ -1597,29 +1788,87 @@ void Buffer::writeLyXHTMLSource(odocstream & os, docstring const & doctitle = features.htmlTitle(); os << "" - << (doctitle.empty() ? from_ascii("LyX Document") : doctitle) + << (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) { + css << "\nbody {\n"; + if (needfg) + css << " color: " + << from_ascii(X11hexname(params().fontcolor)) + << ";\n"; + if (needbg) + css << " background-color: " + << from_ascii(X11hexname(params().backgroundcolor)) + << ";\n"; + css << "}\n"; } - os << "\n\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) { + bool const output_body_tag = (output != IncludedFile); + if (output_body_tag) + os << "\n"; + XHTMLStream xs(os); + params().documentClass().counters().reset(); + xhtmlParagraphs(text(), *this, xs, runparams); + if (output_body_tag) + os << "\n"; } - XHTMLStream xs(os); - params().documentClass().counters().reset(); - xhtmlParagraphs(text(), *this, xs, runparams); - if (!only_body) - os << "\n\n"; + if (output_preamble) + os << "\n"; } @@ -1669,8 +1918,6 @@ void Buffer::validate(LaTeXFeatures & features) const { params().validate(features); - updateMacros(); - for_each(paragraphs().begin(), paragraphs().end(), bind(&Paragraph::validate, _1, ref(features))); @@ -1711,15 +1958,13 @@ void Buffer::updateBibfilesCache(UpdateScope scope) const d->bibfiles_cache_.clear(); for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) { if (it->lyxCode() == BIBTEX_CODE) { - InsetBibtex const & inset = - static_cast(*it); + InsetBibtex const & inset = static_cast(*it); support::FileNameList const bibfiles = inset.getBibFiles(); d->bibfiles_cache_.insert(d->bibfiles_cache_.end(), bibfiles.begin(), bibfiles.end()); } else if (it->lyxCode() == INCLUDE_CODE) { - InsetInclude & inset = - static_cast(*it); + InsetInclude & inset = static_cast(*it); Buffer const * const incbuf = inset.getChildBuffer(); if (!incbuf) continue; @@ -1734,12 +1979,14 @@ void Buffer::updateBibfilesCache(UpdateScope scope) const } d->bibfile_cache_valid_ = true; d->bibinfo_cache_valid_ = false; + d->cite_labels_valid_ = false; } void Buffer::invalidateBibinfoCache() const { d->bibinfo_cache_valid_ = false; + d->cite_labels_valid_ = false; // also invalidate the cache for the parent buffer Buffer const * const pbuf = d->parent(); if (pbuf) @@ -1751,6 +1998,7 @@ void Buffer::invalidateBibfileCache() const { d->bibfile_cache_valid_ = false; d->bibinfo_cache_valid_ = false; + d->cite_labels_valid_ = false; // also invalidate the cache for the parent buffer Buffer const * const pbuf = d->parent(); if (pbuf) @@ -1782,43 +2030,96 @@ BiblioInfo const & Buffer::masterBibInfo() const } -void Buffer::checkBibInfoCache() const +void Buffer::checkIfBibInfoCacheIsValid() const { // use the master's cache Buffer const * const tmp = masterBuffer(); if (tmp != this) { - tmp->checkBibInfoCache(); + tmp->checkIfBibInfoCacheIsValid(); return; } - // this will also reload the cache if it is invalid - support::FileNameList const & bibfiles_cache = getBibfilesCache(); - // compare the cached timestamps with the actual ones. - support::FileNameList::const_iterator ei = bibfiles_cache.begin(); - support::FileNameList::const_iterator en = bibfiles_cache.end(); + FileNameList const & bibfiles_cache = getBibfilesCache(); + FileNameList::const_iterator ei = bibfiles_cache.begin(); + FileNameList::const_iterator en = bibfiles_cache.end(); for (; ei != en; ++ ei) { time_t lastw = ei->lastModified(); time_t prevw = d->bibfile_status_[*ei]; if (lastw != prevw) { d->bibinfo_cache_valid_ = false; + d->cite_labels_valid_ = false; d->bibfile_status_[*ei] = lastw; } } - - // if not valid, then reload the info - if (!d->bibinfo_cache_valid_) { - d->bibinfo_.clear(); - fillWithBibKeys(d->bibinfo_); - d->bibinfo_cache_valid_ = true; +} + + +void Buffer::reloadBibInfoCache() const +{ + // use the master's cache + Buffer const * const tmp = masterBuffer(); + if (tmp != this) { + tmp->reloadBibInfoCache(); + return; } + + checkIfBibInfoCacheIsValid(); + if (d->bibinfo_cache_valid_) + return; + + d->bibinfo_.clear(); + collectBibKeys(); + d->bibinfo_cache_valid_ = true; } -void Buffer::fillWithBibKeys(BiblioInfo & keys) const +void Buffer::collectBibKeys() const { for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) - it->fillWithBibKeys(keys, it); + it->collectBibKeys(it); +} + + +void Buffer::addBiblioInfo(BiblioInfo const & bi) const +{ + Buffer const * tmp = masterBuffer(); + BiblioInfo & masterbi = (tmp == this) ? + d->bibinfo_ : tmp->d->bibinfo_; + masterbi.mergeBiblioInfo(bi); +} + + +void Buffer::addBibTeXInfo(docstring const & key, BibTeXInfo const & bi) const +{ + Buffer const * tmp = masterBuffer(); + BiblioInfo & masterbi = (tmp == this) ? + d->bibinfo_ : tmp->d->bibinfo_; + masterbi[key] = bi; +} + + +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(); } @@ -1837,21 +2138,6 @@ void Buffer::markDepClean(string const & name) } -bool Buffer::isExportableFormat(string const & format) const -{ - typedef vector Formats; - Formats formats; - formats = exportableFormats(true); - Formats::const_iterator fit = formats.begin(); - Formats::const_iterator end = formats.end(); - for (; fit != end ; ++fit) { - if ((*fit)->name() == format) - return true; - } - return false; -} - - bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) { if (isInternal()) { @@ -1880,7 +2166,15 @@ bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_BUFFER_EXPORT: { docstring const arg = cmd.argument(); - enable = arg == "custom" || isExportable(to_utf8(arg)); + 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)); @@ -1888,21 +2182,13 @@ bool Buffer::getStatus(FuncRequest const & cmd, FuncStatus & flag) } case LFUN_BUFFER_CHKTEX: - enable = isLatex() && !lyxrc.chktex_command.empty(); + enable = params().isLatex() && !lyxrc.chktex_command.empty(); break; case LFUN_BUILD_PROGRAM: - enable = isExportable("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_ADD: case LFUN_BRANCHES_RENAME: case LFUN_BUFFER_PRINT: @@ -1928,7 +2214,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) { @@ -1952,16 +2238,16 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) break; case LFUN_BUFFER_EXPORT: { - bool success = doExport(argument, false, false); - dr.setError(!success); - if (!success) - dr.setMessage(bformat(_("Error exporting to format: %1$s."), + ExportStatus const status = doExport(argument, false); + dr.setError(status != ExportSuccess); + if (status != ExportSuccess) + dr.setMessage(bformat(_("Error exporting to format: %1$s."), func.argument())); break; } case LFUN_BUILD_PROGRAM: - doExport("program", true, false); + doExport("program", true); break; case LFUN_BUFFER_CHKTEX: @@ -1993,7 +2279,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) break; } else { - doExport(format_name, true, false, filename); + doExport(format_name, true, filename); } // Substitute $$FName for filename @@ -2003,7 +2289,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) // Execute the command in the background Systemcall call; - call.startscript(Systemcall::DontWait, command); + call.startscript(Systemcall::DontWait, command, filePath()); break; } @@ -2016,6 +2302,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) case LFUN_BUFFER_AUTO_SAVE: autoSave(); + resetAutosaveTimers(); break; case LFUN_BRANCH_ADD: { @@ -2045,7 +2332,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) docstring const str = branch_name + ' ' + from_ascii(x11hexname); lyx::dispatch(FuncRequest(LFUN_SET_COLOR, str)); dr.setError(false); - dr.update(Update::Force); + dr.screenUpdate(Update::Force); } } if (!msg.empty()) @@ -2053,31 +2340,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.update(Update::Force); - dr.forceBufferUpdate(); - } - break; - } - case LFUN_BRANCHES_RENAME: { if (func.argument().empty()) break; @@ -2109,7 +2371,7 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) } if (success) { - dr.update(Update::Force); + dr.screenUpdate(Update::Force); dr.forceBufferUpdate(); } break; @@ -2117,7 +2379,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); @@ -2126,23 +2388,20 @@ 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; } - bool const update_unincluded = - params().maintain_unincluded_children - && !params().getIncludedChildren().empty(); - if (!doExport("dvi", true, update_unincluded)) { + if (!doExport("dvi", true)) { showPrintError(absFileName()); dr.setMessage(_("Error exporting to DVI.")); break; @@ -2180,7 +2439,8 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) command2 += quoteName(psname); // First run dvips. // If successful, then spool command - res = one.startscript(Systemcall::Wait, command); + res = one.startscript(Systemcall::Wait, command, + filePath()); if (res == 0) { // If there's no GUI, we have to wait on this command. Otherwise, @@ -2188,7 +2448,8 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) // file, before it can be printed!! Systemcall::Starttype stype = use_gui ? Systemcall::DontWait : Systemcall::Wait; - res = one.startscript(stype, command2); + res = one.startscript(stype, command2, + filePath()); } } else { // case 2: print directly to a printer @@ -2197,7 +2458,8 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) // as above.... Systemcall::Starttype stype = use_gui ? Systemcall::DontWait : Systemcall::Wait; - res = one.startscript(stype, command + quoteName(dviname)); + res = one.startscript(stype, command + + quoteName(dviname), filePath()); } } else { @@ -2220,10 +2482,10 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) // as above.... Systemcall::Starttype stype = use_gui ? Systemcall::DontWait : Systemcall::Wait; - res = one.startscript(stype, command); + res = one.startscript(stype, command, filePath()); } - if (res == 0) + if (res == 0) dr.setError(false); else { dr.setMessage(_("Error running external commands.")); @@ -2232,18 +2494,6 @@ void Buffer::dispatch(FuncRequest const & func, DispatchResult & dr) break; } - case LFUN_BUFFER_LANGUAGE: { - Language const * oldL = params().language; - Language const * newL = languages.getLanguage(argument); - if (!newL || oldL == newL) - break; - if (oldL->rightToLeft() == newL->rightToLeft() && !isMultiLingual()) { - changeLanguage(oldL, newL); - dr.forceBufferUpdate(); - } - break; - } - default: dispatched = false; break; @@ -2375,8 +2625,11 @@ bool Buffer::isExternallyModified(CheckMethod method) const } -void Buffer::saveCheckSum(FileName const & file) const +void Buffer::saveCheckSum() const { + FileName const & file = d->filename; + + file.refresh(); if (file.exists()) { d->timestamp_ = file.lastModified(); d->checksum_ = file.checksum(); @@ -2420,7 +2673,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; } @@ -2481,7 +2740,7 @@ Buffer const * Buffer::parent() const ListOfBuffers Buffer::allRelatives() const { ListOfBuffers lb = masterBuffer()->getDescendents(); - lb.push_front(const_cast(this)); + lb.push_front(const_cast(masterBuffer())); return lb; } @@ -2516,7 +2775,7 @@ DocIterator Buffer::firstChildPosition(Buffer const * child) bool Buffer::hasChildren() const { - return !d->children_positions.empty(); + return !d->children_positions.empty(); } @@ -2532,7 +2791,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); } @@ -2543,6 +2802,12 @@ ListOfBuffers Buffer::getChildren() const { ListOfBuffers v; collectChildren(v, false); + // Make sure we have not included ourselves. + ListOfBuffers::iterator bit = find(v.begin(), v.end(), this); + if (bit != v.end()) { + LYXERR0("Recursive include detected in `" << fileName() << "'."); + v.erase(bit); + } return v; } @@ -2551,6 +2816,12 @@ ListOfBuffers Buffer::getDescendents() const { ListOfBuffers v; collectChildren(v, true); + // Make sure we have not included ourselves. + ListOfBuffers::iterator bit = find(v.begin(), v.end(), this); + if (bit != v.end()) { + LYXERR0("Recursive include detected in `" << fileName() << "'."); + v.erase(bit); + } return v; } @@ -2622,7 +2893,9 @@ MacroData const * Buffer::Impl::getBufferMacro(docstring const & name, break; // scope ends behind pos? - if (pos < it->second.first) { + if (pos < it->second.first + && (cloned_buffer_ || + theBufferList().isLoaded(it->second.second))) { // look for macro in external file macro_lock = true; MacroData const * data @@ -2733,6 +3006,17 @@ void Buffer::Impl::updateMacros(DocIterator & it, DocIterator & scope) continue; } + if (iit->inset->asInsetTabular()) { + CursorSlice slice(*iit->inset); + size_t const numcells = slice.nargs(); + for (; slice.idx() < numcells; slice.forwardIdx()) { + it.push_back(slice); + updateMacros(it, scope); + it.pop_back(); + } + continue; + } + // is it an external file? if (iit->inset->lyxCode() == INCLUDE_CODE) { // get buffer of external file @@ -2755,12 +3039,11 @@ void Buffer::Impl::updateMacros(DocIterator & it, DocIterator & scope) continue; } - if (doing_export && iit->inset->asInsetMath()) { - InsetMath * im = static_cast(iit->inset); - if (im->asHullInset()) { - InsetMathHull * hull = static_cast(im); + InsetMath * im = iit->inset->asInsetMath(); + if (doing_export && im) { + InsetMathHull * hull = im->asHullInset(); + if (hull) hull->recordLocation(it); - } } if (iit->inset->lyxCode() != MATHMACRO_CODE) @@ -2768,7 +3051,7 @@ void Buffer::Impl::updateMacros(DocIterator & it, DocIterator & scope) // get macro data MathMacroTemplate & macroTemplate = - static_cast(*iit->inset); + *iit->inset->asInsetMath()->asMacroTemplate(); MacroContext mc(owner_, it); macroTemplate.updateToContext(mc); @@ -2846,7 +3129,7 @@ void Buffer::getUsedBranches(std::list & result, bool const from_mast } -void Buffer::updateMacroInstances() const +void Buffer::updateMacroInstances(UpdateType utype) const { LYXERR(Debug::MACROS, "updateMacroInstances for " << d->filename.onlyFileName()); @@ -2864,7 +3147,7 @@ void Buffer::updateMacroInstances() const MacroContext mc = MacroContext(this, it); for (DocIterator::idx_type i = 0; i < n; ++i) { MathData & data = minset->cell(i); - data.updateMacros(0, mc); + data.updateMacros(0, mc, utype); } } } @@ -2974,55 +3257,48 @@ 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 - vector labels; - string paramName; - checkBibInfoCache(); BiblioInfo const & keys = masterBibInfo(); BiblioInfo::const_iterator bit = keys.begin(); BiblioInfo::const_iterator bend = keys.end(); + vector labels; for (; bit != bend; ++bit) // FIXME UNICODE labels.push_back(bit->first); - paramName = "key"; if (count(labels.begin(), labels.end(), from) > 1) return; + string const paramName = "key"; for (InsetIterator it = inset_iterator_begin(inset()); it; ++it) { if (it->lyxCode() == code) { - InsetCommand & inset = static_cast(*it); - docstring const oldValue = inset.getParam(paramName); + InsetCommand * inset = it->asInsetCommand(); + if (!inset) + continue; + docstring const oldValue = inset->getParam(paramName); if (oldValue == from) - inset.setParam(paramName, to); + inset->setParam(paramName, to); } } } -void Buffer::getSourceCode(odocstream & os, pit_type par_begin, - pit_type par_end, bool full_source) const +void Buffer::getSourceCode(odocstream & os, string const format, + pit_type par_begin, pit_type par_end, + OutputWhat output) const { OutputParams runparams(¶ms().encoding()); runparams.nice = true; - runparams.flavor = params().useXetex ? - OutputParams::XETEX : OutputParams::LATEX; + runparams.flavor = params().getOutputFlavor(format); runparams.linelen = lyxrc.plaintext_linelen; // No side effect of file copying and image conversion runparams.dryrun = true; - if (full_source) { - os << "% " << _("Preview source code") << "\n\n"; - d->texrow.reset(); - d->texrow.newline(); - d->texrow.newline(); - if (isDocBook()) - writeDocBookSource(os, absFileName(), runparams, false); - else - // latex or literate - writeLaTeXSource(os, string(), runparams, true, true); - } else { + if (output == CurrentParagraph) { runparams.par_begin = par_begin; runparams.par_end = par_end; if (par_begin + 1 == par_end) { @@ -3041,23 +3317,56 @@ void Buffer::getSourceCode(odocstream & os, pit_type par_begin, texrow.newline(); texrow.newline(); // output paragraphs - if (isDocBook()) + 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 + } else { + // latex or literate + otexstream ots(os, texrow); + latexParagraphs(*this, text(), ots, runparams); + } + } else { + os << "% "; + 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 (runparams.flavor == OutputParams::HTML) { + writeLyXHTMLSource(os, runparams, output); + } else if (runparams.flavor == OutputParams::TEXT) { + if (output == OnlyPreamble) { + os << _("% Plaintext does not have a preamble."); + } else + writePlaintextFile(*this, os, runparams); + } else if (params().isDocBook()) { + writeDocBookSource(os, absFileName(), runparams, output); + } else { // latex or literate - latexParagraphs(*this, text(), os, texrow, runparams); + otexstream ots(os, d->texrow); + writeLaTeXSource(ots, string(), runparams, output); + } } } ErrorList & Buffer::errorList(string const & type) const { - static ErrorList emptyErrorList; - map::iterator it = d->errorLists.find(type); - if (it == d->errorLists.end()) - return emptyErrorList; - - return it->second; + return d->errorLists[type]; } @@ -3155,7 +3464,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 @@ -3206,10 +3515,16 @@ int AutoSaveBuffer::generateChild() } // namespace anon +FileName Buffer::getEmergencyFileName() const +{ + return FileName(d->filename.absFileName() + ".emergency"); +} + + 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()) @@ -3219,6 +3534,7 @@ FileName Buffer::getAutosaveFileName() const fpath = filePath(); string const fname = "#" + d->filename.onlyFileName() + "#"; + return makeAbsPath(fname, fpath); } @@ -3241,106 +3557,148 @@ void Buffer::moveAutosaveFile(support::FileName const & oldauto) const } -// Perfect target for a thread... -void Buffer::autoSave() const +bool Buffer::autoSave() const { - if (d->bak_clean || isReadonly()) { - // We don't save now, but we'll try again later - resetAutosaveTimers(); - return; - } + Buffer const * buf = d->cloned_buffer_ ? d->cloned_buffer_ : this; + if (buf->d->bak_clean || isReadonly()) + return true; - // emit message signal. message(_("Autosaving current document...")); - AutoSaveBuffer autosave(*this, getAutosaveFileName()); - autosave.start(); - - d->bak_clean = true; + buf->d->bak_clean = true; - resetAutosaveTimers(); + 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; + } } -string Buffer::bufferFormat() const -{ - string format = params().documentClass().outputFormat(); - if (format == "latex") { - if (params().useXetex) - return "xetex"; - if (params().encoding().package() == Encoding::japanese) - return "platex"; +// helper class, to guarantee this gets reset properly +class Buffer::MarkAsExporting { +public: + MarkAsExporting(Buffer const * buf) : buf_(buf) + { + LASSERT(buf_, /* */); + buf_->setExportStatus(true); } - return format; -} + ~MarkAsExporting() + { + buf_->setExportStatus(false); + } +private: + Buffer const * const buf_; +}; -string Buffer::getDefaultOutputFormat() const + +void Buffer::setExportStatus(bool e) const { - if (!params().defaultOutputFormat.empty() - && params().defaultOutputFormat != "default") - return params().defaultOutputFormat; - typedef vector Formats; - Formats formats = exportableFormats(true); - if (isDocBook() - || isLiterate() - || params().useXetex - || params().encoding().package() == Encoding::japanese) { - if (formats.empty()) - return string(); - // return the first we find - return formats.front()->name(); - } - return lyxrc.default_view_format; + d->doing_export = e; + ListOfBuffers clist = getDescendents(); + ListOfBuffers::const_iterator cit = clist.begin(); + ListOfBuffers::const_iterator const cen = clist.end(); + for (; cit != cen; ++cit) + (*cit)->d->doing_export = e; } -namespace { - // helper class, to guarantee this gets reset properly - class MarkAsExporting { - public: - MarkAsExporting(Buffer const * buf) : buf_(buf) - { - LASSERT(buf_, /* */); - buf_->setExportStatus(true); - } - ~MarkAsExporting() - { - buf_->setExportStatus(false); - } - private: - Buffer const * const buf_; - }; +bool Buffer::isExporting() const +{ + return d->doing_export; } -void Buffer::setExportStatus(bool e) const +Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir) + const { - d->doing_export = e; + string result_file; + return doExport(target, put_in_tempdir, result_file); } +Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir, + string & result_file) const +{ + bool const update_unincluded = + params().maintain_unincluded_children + && !params().getIncludedChildren().empty(); -bool Buffer::isExporting() const + // (1) export with all included children (omit \includeonly) + if (update_unincluded) { + ExportStatus const status = + doExport(target, put_in_tempdir, true, result_file); + if (status != ExportSuccess) + return status; + } + // (2) export with included children only + return doExport(target, put_in_tempdir, false, result_file); +} + + +void Buffer::setMathFlavor(OutputParams & op) const { - return d->doing_export; + 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; + } } -bool Buffer::doExport(string const & format, bool put_in_tempdir, +Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir, bool includeall, string & result_file) const { + LYXERR(Debug::FILES, "target=" << target); + OutputParams runparams(¶ms().encoding()); + string format = target; + string dest_filename; + size_t pos = target.find(' '); + if (pos != string::npos) { + dest_filename = target.substr(pos + 1, target.length() - pos - 1); + format = target.substr(0, pos); + runparams.export_folder = FileName(dest_filename).onlyPath().realPath(); + FileName(dest_filename).onlyPath().createPath(); + LYXERR(Debug::FILES, "format=" << format << ", dest_filename=" << dest_filename << ", export_folder=" << runparams.export_folder); + } MarkAsExporting exporting(this); string backend_format; - OutputParams runparams(¶ms().encoding()); runparams.flavor = OutputParams::LATEX; runparams.linelen = lyxrc.plaintext_linelen; runparams.includeall = includeall; - vector backs = backends(); + vector backs = params().backends(); + Converters converters = theConverters(); if (find(backs.begin(), backs.end(), format) == backs.end()) { // Get shortest path to format + converters.buildGraph(); Graph::EdgePath path; for (vector::const_iterator it = backs.begin(); it != backs.end(); ++it) { - Graph::EdgePath p = theConverters().getPath(*it, format); + Graph::EdgePath p = converters.getPath(*it, format); if (!p.empty() && (path.empty() || p.size() < path.size())) { backend_format = *it; path = p; @@ -3354,24 +3712,29 @@ bool Buffer::doExport(string const & format, bool put_in_tempdir, _("No information for exporting the format %1$s."), formats.prettyName(format))); } - return false; + return ExportNoPathToFormat; } - runparams.flavor = theConverters().getFlavor(path); + runparams.flavor = converters.getFlavor(path, this); } else { backend_format = format; + LYXERR(Debug::FILES, "backend_format=" << backend_format); // FIXME: Don't hardcode format names here, but use a flag if (backend_format == "pdflatex") runparams.flavor = OutputParams::PDFLATEX; + else if (backend_format == "luatex") + runparams.flavor = OutputParams::LUATEX; + else if (backend_format == "dviluatex") + runparams.flavor = OutputParams::DVILUATEX; + else if (backend_format == "xetex") + runparams.flavor = OutputParams::XETEX; } string filename = latexName(false); filename = addName(temppath(), filename); filename = changeExtension(filename, formats.extension(backend_format)); - - // fix macros - updateMacroInstances(); + LYXERR(Debug::FILES, "filename=" << filename); // Plain text backend if (backend_format == "text") { @@ -3381,61 +3744,54 @@ bool Buffer::doExport(string const & format, 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") + } else if (backend_format == "lyx") writeFile(FileName(filename)); // Docbook backend - else if (isDocBook()) { + else if (params().isDocBook()) { runparams.nice = !put_in_tempdir; makeDocBookFile(FileName(filename), runparams); } // LaTeX backend else if (backend_format == format) { runparams.nice = true; - if (!makeLaTeXFile(FileName(filename), string(), runparams)) - return false; + 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"), _("The directory path to the document cannot contain spaces.")); - return false; + return ExportTexPathHasSpaces; } else { runparams.nice = false; - if (!makeLaTeXFile(FileName(filename), filePath(), runparams)) - return false; + 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") - ? "Build" : bufferFormat(); + ? "Build" : params().bufferFormat(); ErrorList & error_list = d->errorLists[error_type]; string const ext = formats.extension(format); FileName const tmp_result_file(changeExtension(filename, ext)); - bool const success = theConverters().convert(this, FileName(filename), + bool const success = converters.convert(this, FileName(filename), tmp_result_file, FileName(absFileName()), backend_format, format, error_list); // Emit the signal to show the error list or copy it back to the - // cloned Buffer so that it cab be emitted afterwards. + // 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(); @@ -3443,7 +3799,11 @@ bool Buffer::doExport(string const & format, bool put_in_tempdir, ListOfBuffers::const_iterator const cen = clist.end(); for (; cit != cen; ++cit) { if (d->cloned_buffer_) { - (*cit)->d->cloned_buffer_->d->errorLists[error_type] = + // Enable reverse search by copying back the + // texrow object to the cloned buffer. + // FIXME: this is not thread safe. + (*cit)->d->cloned_buffer_->d->texrow = (*cit)->d->texrow; + (*cit)->d->cloned_buffer_->d->errorLists[error_type] = (*cit)->d->errorLists[error_type]; } else (*cit)->errors(error_type, true); @@ -3456,40 +3816,57 @@ bool Buffer::doExport(string const & format, bool put_in_tempdir, // FIXME: There is a possibility of concurrent access to texrow // here from the main GUI thread that should be securized. d->cloned_buffer_->d->texrow = d->texrow; - string const error_type = bufferFormat(); + string const error_type = params().bufferFormat(); d->cloned_buffer_->d->errorLists[error_type] = d->errorLists[error_type]; } if (!success) - return false; + return ExportConverterError; if (put_in_tempdir) { result_file = tmp_result_file.absFileName(); - return true; + return ExportSuccess; } - result_file = changeExtension(d->exportFileName().absFileName(), ext); + if (dest_filename.empty()) + result_file = changeExtension(d->exportFileName().absFileName(), ext); + else + result_file = dest_filename; // We need to copy referenced files (e. g. included graphics // if format == "dvi") to the result dir. vector const files = runparams.exportdata->externalFiles(format); - string const dest = onlyPath(result_file); + string const dest = runparams.export_folder.empty() ? + onlyPath(result_file) : runparams.export_folder; 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) { string const fmt = formats.getFormatFromFile(it->sourceName); + string fixedName = it->exportName; + if (!runparams.export_folder.empty()) { + // Relative pathnames starting with ../ will be sanitized + // if exporting to a different folder + while (fixedName.substr(0, 3) == "../") + fixedName = fixedName.substr(3, fixedName.length() - 3); + } + FileName fixedFileName = makeAbsPath(fixedName, dest); + fixedFileName.onlyPath().createPath(); status = copyFile(fmt, it->sourceName, - makeAbsPath(it->exportName, dest), - it->exportName, status == FORCE); + fixedFileName, + it->exportName, status == FORCE, + runparams.export_folder.empty()); } if (status == CANCEL) { message(_("Document export cancelled.")); - } else if (tmp_result_file.exists()) { + return ExportCancel; + } + + if (tmp_result_file.exists()) { // Finally copy the main file use_force = use_gui ? lyxrc.export_overwrite != NO_FILES : force_overwrite != NO_FILES; @@ -3498,219 +3875,256 @@ bool Buffer::doExport(string const & format, bool put_in_tempdir, status = copyFile(format, tmp_result_file, FileName(result_file), result_file, status == FORCE); - message(bformat(_("Document exported as %1$s " - "to file `%2$s'"), - formats.prettyName(format), - makeDisplayPath(result_file))); + if (status == CANCEL) { + message(_("Document export cancelled.")); + return ExportCancel; + } else { + message(bformat(_("Document exported as %1$s " + "to file `%2$s'"), + formats.prettyName(format), + makeDisplayPath(result_file))); + } } else { // This must be a dummy converter like fax (bug 1888) message(bformat(_("Document exported as %1$s"), formats.prettyName(format))); } - return true; + return ExportSuccess; } -bool Buffer::doExport(string const & format, bool put_in_tempdir, - bool includeall) const +Buffer::ExportStatus Buffer::preview(string const & format) const { - string result_file; - // (1) export with all included children (omit \includeonly) - if (includeall && !doExport(format, put_in_tempdir, true, result_file)) - return false; - // (2) export with included children only - return doExport(format, put_in_tempdir, false, result_file); + bool const update_unincluded = + params().maintain_unincluded_children + && !params().getIncludedChildren().empty(); + return preview(format, update_unincluded); } - -bool Buffer::preview(string const & format, bool includeall) const +Buffer::ExportStatus Buffer::preview(string const & format, bool includeall) const { MarkAsExporting exporting(this); string result_file; // (1) export with all included children (omit \includeonly) - if (includeall && !doExport(format, true, true)) - return false; + if (includeall) { + ExportStatus const status = doExport(format, true, true, result_file); + if (status != ExportSuccess) + return status; + } // (2) export with included children only - if (!doExport(format, true, false, result_file)) - return false; - return formats.view(*this, FileName(result_file), format); + ExportStatus const status = doExport(format, true, false, result_file); + if (status != ExportSuccess) + return status; + if (!formats.view(*this, FileName(result_file), format)) + return PreviewError; + return PreviewSuccess; } -bool Buffer::isExportable(string const & format) const +Buffer::ReadStatus Buffer::extractFromVC() { - vector backs = backends(); - for (vector::const_iterator it = backs.begin(); - it != backs.end(); ++it) - if (theConverters().isReachable(*it, format)) - return true; - return false; + bool const found = LyXVC::file_not_found_hook(d->filename); + if (!found) + return ReadFileNotFound; + if (!d->filename.isReadableFile()) + return ReadVCError; + return ReadSuccess; } -vector Buffer::exportableFormats(bool only_viewable) const +Buffer::ReadStatus Buffer::loadEmergency() { - vector const backs = backends(); - vector result = - theConverters().getReachable(backs[0], only_viewable, true); - for (vector::const_iterator it = backs.begin() + 1; - it != backs.end(); ++it) { - vector r = - theConverters().getReachable(*it, only_viewable, false); - result.insert(result.end(), r.begin(), r.end()); - } - return result; -} - + FileName const emergencyFile = getEmergencyFileName(); + if (!emergencyFile.exists() + || emergencyFile.lastModified() <= d->filename.lastModified()) + return ReadFileNotFound; -vector Buffer::backends() const -{ - vector v; - v.push_back(bufferFormat()); - // FIXME: Don't hardcode format names here, but use a flag - if (v.back() == "latex") - v.push_back("pdflatex"); - v.push_back("xhtml"); - v.push_back("text"); - v.push_back("lyx"); - return v; -} + 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")); -bool Buffer::readFileHelper(FileName const & s) -{ - // File information about normal file - if (!s.exists()) { - docstring const file = makeDisplayPath(s.absFileName(), 50); - docstring text = bformat(_("The specified document\n%1$s" - "\ncould not be read."), file); - Alert::error(_("Could not read document"), text); - return false; + switch (load_emerg) + { + case 0: { + docstring str; + ReadStatus const ret_llf = loadThisLyXFile(emergencyFile); + bool const success = (ret_llf == ReadSuccess); + if (success) { + if (isReadonly()) { + Alert::warning(_("File is read-only"), + bformat(_("An emergency 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."), from_utf8(d->filename.absFileName()))); + } + markDirty(); + 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 = + Alert::prompt(_("Delete emergency file?"), str, 1, 1, + _("&Remove"), _("&Keep")); + if (del_emerg == 0) { + emergencyFile.removeFile(); + if (success) + Alert::warning(_("Emergency file deleted"), + _("Do not forget to save your file now!"), true); + } + return success ? ReadSuccess : ReadEmergencyFailure; + } + case 1: { + int const del_emerg = + Alert::prompt(_("Delete emergency file?"), + _("Remove emergency file now?"), 1, 1, + _("&Remove"), _("&Keep")); + if (del_emerg == 0) + emergencyFile.removeFile(); + return ReadOriginal; } - // Check if emergency save file exists and is newer. - FileName const e(s.absFileName() + ".emergency"); - - if (e.exists() && s.exists() && e.lastModified() > s.lastModified()) { - docstring const file = makeDisplayPath(s.absFileName(), 20); - docstring const text = - bformat(_("An emergency save of the document " - "%1$s exists.\n\n" - "Recover emergency save?"), file); - switch (Alert::prompt(_("Load emergency save?"), text, 0, 2, - _("&Recover"), _("&Load Original"), - _("&Cancel"))) - { - case 0: { - // the file is not saved if we load the emergency file. - markDirty(); - docstring str; - bool res; - - if ((res = readFile(e)) == success) - str = _("Document was successfully recovered."); - else - str = _("Document was NOT successfully recovered."); - str += "\n\n" + bformat(_("Remove emergency file now?\n(%1$s)"), - makeDisplayPath(e.absFileName())); - - if (!Alert::prompt(_("Delete emergency file?"), str, 1, 1, - _("&Remove"), _("&Keep it"))) { - e.removeFile(); - if (res == success) - Alert::warning(_("Emergency file deleted"), - _("Do not forget to save your file now!"), true); - } - return res; - } - case 1: - if (!Alert::prompt(_("Delete emergency file?"), - _("Remove emergency file now?"), 1, 1, - _("&Remove"), _("&Keep it"))) - e.removeFile(); - break; - default: - return false; - } + default: + break; } + return ReadCancel; +} + +Buffer::ReadStatus Buffer::loadAutosave() +{ // Now check if autosave file is newer. - FileName const a(onlyPath(s.absFileName()) + '#' + onlyFileName(s.absFileName()) + '#'); - - if (a.exists() && s.exists() && a.lastModified() > s.lastModified()) { - docstring const file = makeDisplayPath(s.absFileName(), 20); - docstring const text = - bformat(_("The backup of the document " - "%1$s is newer.\n\nLoad the " - "backup instead?"), file); - switch (Alert::prompt(_("Load backup?"), text, 0, 2, - _("&Load backup"), _("Load &original"), - _("&Cancel") )) - { - case 0: - // the file is not saved if we load the autosave file. + FileName const autosaveFile = getAutosaveFileName(); + 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 " + "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: { + ReadStatus const ret_llf = loadThisLyXFile(autosaveFile); + // the file is not saved if we load the autosave file. + if (ret_llf == ReadSuccess) { + if (isReadonly()) { + Alert::warning(_("File is read-only"), + 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."), + from_utf8(d->filename.absFileName()))); + } markDirty(); - return readFile(a); - case 1: - // Here we delete the autosave - a.removeFile(); - break; - default: - return false; + return ReadSuccess; } + return ReadAutosaveFailure; + } + case 1: + // Here we delete the autosave + autosaveFile.removeFile(); + return ReadOriginal; + default: + break; } - return readFile(s); + return ReadCancel; } -bool Buffer::loadLyXFile(FileName const & s) +Buffer::ReadStatus Buffer::loadLyXFile() { - // If the file is not readable, we try to - // retrieve the file from version control. - if (!s.isReadableFile() - && !LyXVC::file_not_found_hook(s)) - return false; - - if (s.isReadableFile()){ - // InsetInfo needs to know if file is under VCS - lyxvc().file_found_hook(s); - if (readFileHelper(s)) { - d->read_only = !s.isWritable(); - return true; - } + if (!d->filename.isReadableFile()) { + ReadStatus const ret_rvc = extractFromVC(); + if (ret_rvc != ReadSuccess) + return ret_rvc; } - return false; + + 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; + + return loadThisLyXFile(d->filename); +} + + +Buffer::ReadStatus Buffer::loadThisLyXFile(FileName const & fn) +{ + return readFile(fn); } void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const { - TeXErrors::Errors::const_iterator cit = terr.begin(); + TeXErrors::Errors::const_iterator it = terr.begin(); TeXErrors::Errors::const_iterator end = terr.end(); + ListOfBuffers clist = getDescendents(); + ListOfBuffers::const_iterator cen = clist.end(); - for (; cit != end; ++cit) { + for (; it != end; ++it) { int id_start = -1; int pos_start = -1; - int errorRow = cit->error_in_line; - bool found = d->texrow.getIdFromRow(errorRow, id_start, - pos_start); + int errorRow = it->error_in_line; + Buffer const * buf = 0; + Impl const * p = d; + if (it->child_name.empty()) + p->texrow.getIdFromRow(errorRow, id_start, pos_start); + else { + // The error occurred in a child + ListOfBuffers::const_iterator cit = clist.begin(); + for (; cit != cen; ++cit) { + string const child_name = + DocFileName(changeExtension( + (*cit)->absFileName(), "tex")). + mangledFileName(); + if (it->child_name != child_name) + continue; + (*cit)->d->texrow.getIdFromRow(errorRow, + id_start, pos_start); + if (id_start != -1) { + buf = d->cloned_buffer_ + ? (*cit)->d->cloned_buffer_->d->owner_ + : (*cit)->d->owner_; + p = (*cit)->d; + break; + } + } + } int id_end = -1; int pos_end = -1; + bool found; do { ++errorRow; - found = d->texrow.getIdFromRow(errorRow, id_end, pos_end); + found = p->texrow.getIdFromRow(errorRow, id_end, pos_end); } while (found && id_start == id_end && pos_start == pos_end); - errorList.push_back(ErrorItem(cit->error_desc, - cit->error_text, id_start, pos_start, pos_end)); + if (id_start != id_end) { + // Next registered position is outside the inset where + // the error occurred, so signal end-of-paragraph + pos_end = 0; + } + + errorList.push_back(ErrorItem(it->error_desc, + it->error_text, id_start, pos_start, pos_end, buf)); } } void Buffer::setBuffersForInsets() const { - inset().setBuffer(const_cast(*this)); + inset().setBuffer(const_cast(*this)); } @@ -3719,10 +4133,10 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const // 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) - checkBibInfoCache(); + 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. @@ -3733,7 +4147,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(); @@ -3766,6 +4180,8 @@ void Buffer::updateBuffer(UpdateScope scope, UpdateType utype) const // TocBackend update will be done later. return; + d->bibinfo_cache_valid_ = true; + d->cite_labels_valid_ = true; cbuf.tocBackend().update(); if (scope == UpdateMaster) cbuf.structureChanged(); @@ -3861,10 +4277,12 @@ void Buffer::Impl::setLabel(ParIterator & it, UpdateType utype) const // Compute the item depth of the paragraph par.itemdepth = getItemDepth(it); - if (layout.margintype == MARGIN_MANUAL - || layout.latextype == LATEX_BIB_ENVIRONMENT) { + if (layout.margintype == MARGIN_MANUAL) { if (par.params().labelWidthString().empty()) par.params().labelWidthString(par.expandLabel(layout, bp)); + } else if (layout.latextype == LATEX_BIB_ENVIRONMENT) { + // we do not need to do anything here, since the empty case is + // handled during export. } else { par.params().labelWidthString(docstring()); } @@ -3872,11 +4290,11 @@ 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()))) { - counters.step(layout.counter, utype); - par.params().labelString( - par.expandLabel(layout, bp)); + && (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; @@ -3946,13 +4364,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; } @@ -3963,7 +4381,7 @@ void Buffer::Impl::setLabel(ParIterator & it, UpdateType utype) const case LABEL_MANUAL: case LABEL_TOP_ENVIRONMENT: case LABEL_CENTERED_TOP_ENVIRONMENT: - case LABEL_STATIC: + case LABEL_STATIC: case LABEL_BIBLIO: par.params().labelString(par.expandLabel(layout, bp)); break; @@ -3975,10 +4393,8 @@ void Buffer::updateBuffer(ParIterator & parit, UpdateType utype) const { LASSERT(parit.pit() == 0, /**/); - // set the position of the text in the buffer to be able - // to resolve macros in it. This has nothing to do with - // labels, but by putting it here we avoid implementing - // a whole bunch of traversal routines just for this call. + // Set the position of the text in the buffer to be able + // to resolve macros in it. parit.text()->setMacrocontextPosition(parit); depth_type maxdepth = 0; @@ -3995,7 +4411,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); @@ -4017,8 +4433,9 @@ int Buffer::spellCheck(DocIterator & from, DocIterator & to, WordLangTuple wl; suggestions.clear(); word_lang = WordLangTuple(); + bool const to_end = to.empty(); + DocIterator const end = to_end ? doc_iterator_end(this) : to; // OK, we start from here. - DocIterator const end = doc_iterator_end(this); for (; from != end; from.forwardPos()) { // We are only interested in text so remove the math CursorSlice. while (from.inMathed()) { @@ -4026,8 +4443,8 @@ int Buffer::spellCheck(DocIterator & from, DocIterator & to, from.pos()++; } // If from is at the end of the document (which is possible - // when leaving the mathed) LyX will crash later. - if (from == end) + // when leaving the mathed) LyX will crash later otherwise. + if (from.atEnd() || (!to_end && from >= end)) break; to = from; from.paragraph().spellCheck(); @@ -4048,31 +4465,136 @@ int Buffer::spellCheck(DocIterator & from, DocIterator & to, } -bool 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() { setBusy(true); - // c.f. bug 6587 + // c.f. bug http://www.lyx.org/trac/ticket/6587 removeAutosaveFile(); // e.g., read-only status could have changed due to version control d->filename.refresh(); docstring const disp_fn = makeDisplayPath(d->filename.absFileName()); - bool const success = loadLyXFile(d->filename); - if (success) { + // clear parent. this will get reset if need be. + d->setParent(0); + ReadStatus const status = loadLyXFile(); + if (status == ReadSuccess) { updateBuffer(); changed(true); updateTitles(); markClean(); message(bformat(_("Document %1$s reloaded."), disp_fn)); + d->undo_.clear(); } else { message(bformat(_("Could not reload document %1$s."), disp_fn)); - } + } setBusy(false); - thePreviews().removeLoader(*this); - if (graphics::Previews::status() != LyXRC::PREVIEW_OFF) - thePreviews().generateBufferPreviews(*this); + removePreviews(); + updatePreviews(); errors("Parse"); - return success; + return status; +} + + +bool Buffer::saveAs(FileName const & fn) +{ + FileName const old_name = fileName(); + FileName const old_auto = getAutosaveFileName(); + bool const old_unnamed = isUnnamed(); + + setFileName(fn); + markDirty(); + setUnnamed(false); + + if (save()) { + // bring the autosave file with us, just in case. + moveAutosaveFile(old_auto); + // validate version control data and + // correct buffer title + lyxvc().file_found_hook(fileName()); + updateTitles(); + // the file has now been saved to the new location. + // we need to check that the locations of child buffers + // are still valid. + checkChildBuffers(); + return true; + } else { + // save failed + // reset the old filename and unnamed state + setFileName(old_name); + setUnnamed(old_unnamed); + return false; + } }