]> git.lyx.org Git - lyx.git/blobdiff - src/Buffer.cpp
Fix bug #2213 (part 1): GuiChanges lacks "Previous Change" button.
[lyx.git] / src / Buffer.cpp
index 14b502825dd7daf2edd76d7ead116ba28cf29692..90ce575534cfa13f7ac00c5ad3c89f2c74014ea9 100644 (file)
@@ -50,6 +50,7 @@
 #include "ParagraphParameters.h"
 #include "ParIterator.h"
 #include "PDFOptions.h"
+#include "SpellChecker.h"
 #include "sgml.h"
 #include "TexRow.h"
 #include "TexStream.h"
@@ -59,6 +60,7 @@
 #include "Undo.h"
 #include "VCBackend.h"
 #include "version.h"
+#include "WordLangTuple.h"
 #include "WordList.h"
 
 #include "insets/InsetBibitem.h"
@@ -79,6 +81,7 @@
 #include "support/lassert.h"
 #include "support/convert.h"
 #include "support/debug.h"
+#include "support/docstring_list.h"
 #include "support/ExceptionMessage.h"
 #include "support/FileName.h"
 #include "support/FileNameList.h"
@@ -145,7 +148,6 @@ public:
        LyXVC lyxvc;
        FileName temppath;
        mutable TexRow texrow;
-       Buffer const * parent_buffer;
 
        /// need to regenerate .tex?
        DepClean dep_clean;
@@ -225,6 +227,22 @@ public:
 
        /// our Text that should be wrapped in an InsetText
        InsetText * inset;
+
+       /// This is here to force the test to be done whenever parent_buffer
+       /// is accessed.
+       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.
+               if (!theBufferList().isLoaded(parent_buffer))
+                       parent_buffer = 0;
+               return parent_buffer; 
+       }
+       ///
+       void setParent(Buffer const * pb) { parent_buffer = pb; }
+private:
+       /// So we can force access via the accessors.
+       mutable Buffer const * parent_buffer;
 };
 
 
@@ -248,10 +266,11 @@ static FileName createBufferTmpDir()
 
 
 Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_)
-       : parent_buffer(0), lyx_clean(true), bak_clean(true), unnamed(false),
+       : lyx_clean(true), bak_clean(true), unnamed(false),
          read_only(readonly_), filename(file), file_fully_loaded(false),
          toc_backend(&parent), macro_lock(false), timestamp_(0),
-         checksum_(0), wa_(0), undo_(parent), bibinfoCacheValid_(false)
+         checksum_(0), wa_(0), undo_(parent), bibinfoCacheValid_(false),
+         parent_buffer(0)
 {
        temppath = createBufferTmpDir();
        lyxvc.setBuffer(&parent);
@@ -290,9 +309,10 @@ Buffer::~Buffer()
        Impl::BufferPositionMap::iterator it = d->children_positions.begin();
        Impl::BufferPositionMap::iterator end = d->children_positions.end();
        for (; it != end; ++it) {
+               Buffer * child = const_cast<Buffer *>(it->first);
                // The child buffer might have been closed already.
                if (theBufferList().isLoaded(child))
-                       theBufferList().releaseChild(this, const_cast<Buffer *>(it->first));
+                       theBufferList().releaseChild(this, child);
        }
 
        // clear references to children in macro tables
@@ -594,6 +614,11 @@ bool Buffer::readDocument(Lexer & lex)
                        Buffer * master = 
                                checkAndLoadLyXFile(master_file, true);
                        if (master) {
+                               // necessary e.g. after a reload
+                               // to re-register the child (bug 5873)
+                               // FIXME: clean up updateMacros (here, only
+                               // child registering is needed).
+                               master->updateMacros();
                                // set master as master buffer, but only
                                // if we are a real child
                                if (master->isChild(this))
@@ -604,9 +629,9 @@ bool Buffer::readDocument(Lexer & lex)
                                else if (master->isFullyLoaded())
                                        LYXERR0("The master '"
                                                << params().master
-                                               << "' assigned to this document '"
+                                               << "' assigned to this document ("
                                                << absFileName()
-                                               << "' does not include "
+                                               << ") does not include "
                                                "this document. Ignoring the master assignment.");
                        }
                }
@@ -778,7 +803,7 @@ Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
                                     bformat(_("%1$s is from a different"
                                              " version of LyX, but a temporary"
                                              " file for converting it could"
-                                                           " not be created."),
+                                             " not be created."),
                                              from_utf8(filename.absFilename())));
                        return failure;
                }
@@ -788,7 +813,7 @@ Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
                                     bformat(_("%1$s is from a different"
                                               " version of LyX, but the"
                                               " conversion script lyx2lyx"
-                                                           " could not be found."),
+                                              " could not be found."),
                                               from_utf8(filename.absFilename())));
                        return failure;
                }
@@ -807,7 +832,7 @@ Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename,
                        Alert::error(_("Conversion script failed"),
                                     bformat(_("%1$s is from a different version"
                                              " of LyX, but the lyx2lyx script"
-                                                           " failed to convert it."),
+                                             " failed to convert it."),
                                              from_utf8(filename.absFilename())));
                        return failure;
                } else {
@@ -908,7 +933,7 @@ bool Buffer::writeFile(FileName const & fname) const
                return false;
        }
 
-       removeAutosaveFile(d->filename.absFilename());
+       removeAutosaveFile();
 
        saveCheckSum(d->filename);
        message(str + _(" done."));
@@ -1149,8 +1174,8 @@ void Buffer::writeLaTeXSource(odocstream & os,
        // This happens for example if only a child document is printed.
        Buffer const * save_parent = 0;
        if (output_preamble) {
-               save_parent = d->parent_buffer;
-               d->parent_buffer = 0;
+               save_parent = d->parent();
+               d->setParent(0);
        }
 
        // the real stuff
@@ -1158,7 +1183,7 @@ void Buffer::writeLaTeXSource(odocstream & os,
 
        // Restore the parenthood if needed
        if (output_preamble)
-               d->parent_buffer = save_parent;
+               d->setParent(save_parent);
 
        // add this just in case after all the paragraphs
        os << endl;
@@ -1351,8 +1376,9 @@ void Buffer::validate(LaTeXFeatures & features) const
 void Buffer::getLabelList(vector<docstring> & list) const
 {
        // If this is a child document, use the parent's list instead.
-       if (d->parent_buffer) {
-               d->parent_buffer->getLabelList(list);
+       Buffer const * const pbuf = d->parent();
+       if (pbuf) {
+               pbuf->getLabelList(list);
                return;
        }
 
@@ -1367,11 +1393,12 @@ void Buffer::getLabelList(vector<docstring> & list) const
 }
 
 
-void Buffer::updateBibfilesCache() const
+void Buffer::updateBibfilesCache(UpdateScope scope) const
 {
        // If this is a child document, use the parent's cache instead.
-       if (d->parent_buffer) {
-               d->parent_buffer->updateBibfilesCache();
+       Buffer const * const pbuf = d->parent();
+       if (pbuf && scope != UpdateChildOnly) {
+               pbuf->updateBibfilesCache();
                return;
        }
 
@@ -1389,7 +1416,7 @@ void Buffer::updateBibfilesCache() const
                                static_cast<InsetInclude &>(*it);
                        inset.updateBibfilesCache();
                        support::FileNameList const & bibfiles =
-                                       inset.getBibfilesCache(*this);
+                                       inset.getBibfilesCache();
                        d->bibfilesCache_.insert(d->bibfilesCache_.end(),
                                bibfiles.begin(),
                                bibfiles.end());
@@ -1406,15 +1433,16 @@ void Buffer::invalidateBibinfoCache()
 }
 
 
-support::FileNameList const & Buffer::getBibfilesCache() const
+support::FileNameList const & Buffer::getBibfilesCache(UpdateScope scope) const
 {
        // If this is a child document, use the parent's cache instead.
-       if (d->parent_buffer)
-               return d->parent_buffer->getBibfilesCache();
+       Buffer const * const pbuf = d->parent();
+       if (pbuf && scope != UpdateChildOnly)
+               return pbuf->getBibfilesCache();
 
        // We update the cache when first used instead of at loading time.
        if (d->bibfilesCache_.empty())
-               const_cast<Buffer *>(this)->updateBibfilesCache();
+               const_cast<Buffer *>(this)->updateBibfilesCache(scope);
 
        return d->bibfilesCache_;
 }
@@ -1496,6 +1524,11 @@ bool Buffer::dispatch(FuncRequest const & func, bool * result)
                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.");
@@ -1702,14 +1735,14 @@ bool Buffer::isReadonly() const
 void Buffer::setParent(Buffer const * buffer)
 {
        // Avoids recursive include.
-       d->parent_buffer = buffer == this ? 0 : buffer;
+       d->setParent(buffer == this ? 0 : buffer);
        updateMacros();
 }
 
 
 Buffer const * Buffer::parent() const
 {
-       return d->parent_buffer;
+       return d->parent();
 }
 
 
@@ -1741,10 +1774,11 @@ std::vector<Buffer const *> Buffer::allRelatives() const
 
 Buffer const * Buffer::masterBuffer() const
 {
-       if (!d->parent_buffer)
+       Buffer const * const pbuf = d->parent();
+       if (!pbuf)
                return this;
 
-       return d->parent_buffer->masterBuffer();
+       return pbuf->masterBuffer();
 }
 
 
@@ -1764,6 +1798,25 @@ DocIterator Buffer::firstChildPosition(Buffer const * child)
 }
 
 
+std::vector<Buffer *> Buffer::getChildren() const
+{
+       std::vector<Buffer *> clist;
+       // 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<Buffer *>(it->first);
+               clist.push_back(child);
+               // there might be grandchildren
+               std::vector<Buffer *> glist = child->getChildren();
+               for (vector<Buffer *>::const_iterator git = glist.begin();
+                    git != glist.end(); ++git)
+                       clist.push_back(*git);
+       }
+       return clist;
+}
+
+
 template<typename M>
 typename M::iterator greatest_below(M & m, typename M::key_type const & x)
 {
@@ -1868,9 +1921,10 @@ MacroData const * Buffer::getMacro(docstring const & name,
                return data;
 
        // If there is a master buffer, query that
-       if (d->parent_buffer) {
+       Buffer const * const pbuf = d->parent();
+       if (pbuf) {
                d->macro_lock = true;
-               MacroData const * macro = d->parent_buffer->getMacro(
+               MacroData const * macro = pbuf->getMacro(
                        name, *this, false);
                d->macro_lock = false;
                if (macro)
@@ -1928,8 +1982,7 @@ void Buffer::updateMacros(DocIterator & it, DocIterator & scope) const
                        // is it a nested text inset?
                        if (iit->inset->asInsetText()) {
                                // Inset needs its own scope?
-                               InsetText const * itext
-                               = iit->inset->asInsetText();
+                               InsetText const * itext = iit->inset->asInsetText();
                                bool newScope = itext->isMacroScope();
 
                                // scope which ends just behind the inset
@@ -1946,19 +1999,19 @@ void Buffer::updateMacros(DocIterator & it, DocIterator & scope) const
                        // is it an external file?
                        if (iit->inset->lyxCode() == INCLUDE_CODE) {
                                // get buffer of external file
-                               InsetInclude const & inset
-                                       static_cast<InsetInclude const &>(*iit->inset);
+                               InsetInclude const & inset =
+                                       static_cast<InsetInclude const &>(*iit->inset);
                                d->macro_lock = true;
-                               Buffer * child = inset.loadIfNeeded(*this);
+                               Buffer * child = inset.getChildBuffer();
                                d->macro_lock = false;
                                if (!child)
                                        continue;
 
                                // register its position, but only when it is
                                // included first in the buffer
-                               if (d->children_positions.find(child)
-                                       == d->children_positions.end())
-                                       d->children_positions[child] = it;
+                               if (d->children_positions.find(child) ==
+                                       d->children_positions.end())
+                                               d->children_positions[child] = it;
 
                                // register child with its scope
                                d->position_to_children[it] = Impl::ScopeBuffer(scope, child);
@@ -1969,8 +2022,8 @@ void Buffer::updateMacros(DocIterator & it, DocIterator & scope) const
                                continue;
 
                        // get macro data
-                       MathMacroTemplate & macroTemplate
-                       = static_cast<MathMacroTemplate &>(*iit->inset);
+                       MathMacroTemplate & macroTemplate =
+                               static_cast<MathMacroTemplate &>(*iit->inset);
                        MacroContext mc(*this, it);
                        macroTemplate.updateToContext(mc);
 
@@ -2064,8 +2117,9 @@ void Buffer::listMacroNames(MacroNameSet & macros) const
                it->first->listMacroNames(macros);
 
        // call parent
-       if (d->parent_buffer)
-               d->parent_buffer->listMacroNames(macros);
+       Buffer const * const pbuf = d->parent();
+       if (pbuf)
+               pbuf->listMacroNames(macros);
 
        d->macro_lock = false;
 }
@@ -2073,11 +2127,12 @@ void Buffer::listMacroNames(MacroNameSet & macros) const
 
 void Buffer::listParentMacros(MacroSet & macros, LaTeXFeatures & features) const
 {
-       if (!d->parent_buffer)
+       Buffer const * const pbuf = d->parent();
+       if (!pbuf)
                return;
 
        MacroNameSet names;
-       d->parent_buffer->listMacroNames(names);
+       pbuf->listMacroNames(names);
 
        // resolve macros
        MacroNameSet::iterator it = names.begin();
@@ -2085,7 +2140,7 @@ void Buffer::listParentMacros(MacroSet & macros, LaTeXFeatures & features) const
        for (; it != end; ++it) {
                // defined?
                MacroData const * data =
-               d->parent_buffer->getMacro(*it, *this, false);
+               pbuf->getMacro(*it, *this, false);
                if (data) {
                        macros.insert(data);
 
@@ -2101,7 +2156,7 @@ void Buffer::listParentMacros(MacroSet & macros, LaTeXFeatures & features) const
 
 Buffer::References & Buffer::references(docstring const & label)
 {
-       if (d->parent_buffer)
+       if (d->parent())
                return const_cast<Buffer *>(masterBuffer())->references(label);
 
        RefCache::iterator it = d->ref_cache_.find(label);
@@ -2136,7 +2191,7 @@ InsetLabel const * Buffer::insetLabel(docstring const & label) const
 
 void Buffer::clearReferenceCache() const
 {
-       if (!d->parent_buffer)
+       if (!d->parent())
                d->ref_cache_.clear();
 }
 
@@ -2286,6 +2341,12 @@ void Buffer::resetAutosaveTimers() const
 }
 
 
+bool Buffer::hasGuiDelegate() const
+{
+       return gui_;
+}
+
+
 void Buffer::setGuiDelegate(frontend::GuiBufferDelegate * gui)
 {
        gui_ = gui;
@@ -2365,6 +2426,22 @@ int AutoSaveBuffer::generateChild()
 } // namespace anon
 
 
+FileName Buffer::getAutosaveFilename() const
+{
+       string const fpath = isUnnamed() ? lyxrc.document_path : filePath();
+       string const fname = "#" + d->filename.onlyFileName() + "#";
+       return makeAbsPath(fname, fpath);
+}
+
+
+void Buffer::removeAutosaveFile() const
+{
+       FileName const f = getAutosaveFilename();
+       if (f.exists())
+               f.removeFile();
+}
+
+
 // Perfect target for a thread...
 void Buffer::autoSave() const
 {
@@ -2376,14 +2453,7 @@ void Buffer::autoSave() const
 
        // emit message signal.
        message(_("Autosaving current document..."));
-
-       // create autosave filename
-       string fname = filePath();
-       fname += '#';
-       fname += d->filename.onlyFileName();
-       fname += '#';
-
-       AutoSaveBuffer autosave(*this, FileName(fname));
+       AutoSaveBuffer autosave(*this, getAutosaveFilename());
        autosave.start();
 
        markBakClean();
@@ -2703,7 +2773,13 @@ void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
 }
 
 
-void Buffer::updateLabels(bool childonly) const
+void Buffer::setBuffersForInsets() const
+{
+       inset().setBuffer(const_cast<Buffer &>(*this)); 
+}
+
+
+void Buffer::updateLabels(UpdateScope scope) const
 {
        // Use the master text class also for child documents
        Buffer const * const master = masterBuffer();
@@ -2713,11 +2789,15 @@ void Buffer::updateLabels(bool childonly) const
        // master comes back we can see which of them were actually seen (i.e.
        // via an InsetInclude). The remaining ones in the set need still be updated.
        static std::set<Buffer const *> bufToUpdate;
-       if (!childonly) {
+       if (scope == UpdateMaster) {
                // If this is a child document start with the master
                if (master != this) {
                        bufToUpdate.insert(this);
-                       master->updateLabels(false);
+                       master->updateLabels();
+                       // 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->gui_)
+                               structureChanged();     
 
                        // was buf referenced from the master (i.e. not in bufToUpdate anymore)?
                        if (bufToUpdate.find(this) == bufToUpdate.end())
@@ -2733,7 +2813,6 @@ void Buffer::updateLabels(bool childonly) const
 
        // update all caches
        clearReferenceCache();
-       inset().setBuffer(const_cast<Buffer &>(*this));
        updateMacros();
 
        Buffer & cbuf = const_cast<Buffer &>(*this);
@@ -2749,7 +2828,7 @@ void Buffer::updateLabels(bool childonly) const
                return;
 
        cbuf.tocBackend().update();
-       if (!childonly)
+       if (scope == UpdateMaster)
                cbuf.structureChanged();
 }
 
@@ -3005,4 +3084,79 @@ void Buffer::updateLabels(ParIterator & parit) const
        }
 }
 
+
+bool Buffer::nextWord(DocIterator & from, DocIterator & to,
+       docstring & word) const
+{
+       bool inword = false;
+       bool ignoreword = false;
+       string lang_code;
+       // Go backward a bit if needed in order to return the word currently
+       // pointed by 'from'.
+       while (from && from.pos() && isLetter(from))
+               from.backwardPos();
+       // OK, we start from here.
+       to = from;
+       while (to.depth()) {
+               if (isLetter(to)) {
+                       if (!inword) {
+                               inword = true;
+                               ignoreword = false;
+                               from = to;
+                               word.clear();
+                               lang_code = to.paragraph().getFontSettings(params(),
+                                       to.pos()).language()->code();
+                       }
+                       // Insets like optional hyphens and ligature
+                       // break are part of a word.
+                       if (!to.paragraph().isInset(to.pos())) {
+                               char_type const c = to.paragraph().getChar(to.pos());
+                               word += c;
+                               if (isDigit(c))
+                                       ignoreword = true;
+                       }
+               } else { // !isLetter(cur)
+                       if (inword && !word.empty() && !ignoreword)
+                               return true;
+                       inword = false;
+               }
+               to.forwardPos();
+       }
+       from = to;
+       word.clear();
+       return false;
+}
+
+
+int Buffer::spellCheck(DocIterator & from, DocIterator & to,
+       WordLangTuple & word_lang, docstring_list & suggestions) const
+{
+       int progress = 0;
+       SpellChecker::Result res = SpellChecker::OK;
+       SpellChecker * speller = theSpellChecker();
+       suggestions.clear();
+       docstring word;
+       while (nextWord(from, to, word)) {
+               ++progress;
+               string lang_code = lyxrc.spellchecker_use_alt_lang
+                     ? lyxrc.spellchecker_alt_lang
+                     : from.paragraph().getFontSettings(params(), from.pos()).language()->code();
+               WordLangTuple wl(word, lang_code);
+               res = speller->check(wl);
+               // ... just bail out if the spellchecker reports an error.
+               if (!speller->error().empty()) {
+                       throw ExceptionMessage(WarningException,
+                               _("The spellchecker has failed."), speller->error());
+               }
+               if (res != SpellChecker::OK && res != SpellChecker::IGNORED_WORD) {
+                       word_lang = wl;
+                       break;
+               }
+               from = to;
+       }
+       while (!(word = speller->nextMiss()).empty())
+               suggestions.push_back(word);
+       return progress;
+}
+
 } // namespace lyx