From 36dbec45069bf3d9db923200835e44f44d904eb8 Mon Sep 17 00:00:00 2001 From: Stefan Schimanski Date: Fri, 21 Dec 2007 20:42:46 +0000 Subject: [PATCH] * Lazy MathData to avoid unneeded interpretation of macro definitions * DocIterator as MacroPosition * Iterative search for macro in scope until a visible one is found. This include the ability to resolve macro inside nested text insets. * Speed up macro lookups by factor 2: only getMacro(name) call, no further hasMacro(name) call before * Both way child/master support * Correct macro scope for multi-paragraph environments * Correct macro scope for multi-depth-paragraphs * Buffer::updateMacros made const * Update macros when loaded (of master and child) * Do not remove too many braces when unfolding a macro. This could lead to a data loss because the relationship between arguments of macros can be mixed up if nested macros are unfold at once. * Reduce dependencies to MetricsInfo in MathMacro * Update macros when needed. Normally it's done just before doing metrics. But in cases without a brace around some constructs (like \left(bla\right)) there is some help needed. git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@22241 a592a061-630c-0410-9148-cb99ea01b6c8 --- src/Buffer.cpp | 470 ++++++++++++++++++++++++++----- src/Buffer.h | 54 +++- src/BufferView.cpp | 4 +- src/DocIterator.cpp | 23 -- src/DocIterator.h | 26 ++ src/LyXFunc.cpp | 2 +- src/Paragraph.cpp | 17 -- src/Paragraph.h | 9 - src/Text.cpp | 12 + src/Text.h | 12 + src/Text3.cpp | 6 +- src/TextMetrics.cpp | 38 ++- src/buffer_funcs.cpp | 6 + src/insets/InsetBox.cpp | 7 + src/insets/InsetBox.h | 2 + src/insets/InsetBranch.cpp | 6 + src/insets/InsetBranch.h | 4 +- src/insets/InsetCaption.cpp | 1 + src/insets/InsetCollapsable.cpp | 16 ++ src/insets/InsetCollapsable.h | 2 + src/insets/InsetFloat.h | 2 + src/insets/InsetFootlike.h | 2 + src/insets/InsetInclude.cpp | 7 +- src/insets/InsetInclude.h | 4 +- src/insets/InsetListings.h | 2 + src/insets/InsetNote.cpp | 10 + src/insets/InsetNote.h | 2 + src/insets/InsetTabular.cpp | 9 + src/insets/InsetTabular.h | 2 +- src/insets/InsetText.h | 2 + src/insets/InsetTheorem.h | 2 + src/insets/InsetWrap.h | 2 + src/lyxfind.cpp | 1 + src/mathed/InsetMathNest.cpp | 9 +- src/mathed/MacroTable.cpp | 142 ++++++---- src/mathed/MacroTable.h | 99 ++++--- src/mathed/MathData.cpp | 161 ++++++----- src/mathed/MathData.h | 16 +- src/mathed/MathMacro.cpp | 187 ++++++------ src/mathed/MathMacro.h | 6 +- src/mathed/MathMacroTemplate.cpp | 114 ++++++-- src/mathed/MathMacroTemplate.h | 41 ++- src/mathed/MathParser.cpp | 8 +- 43 files changed, 1093 insertions(+), 454 deletions(-) diff --git a/src/Buffer.cpp b/src/Buffer.cpp index 9dc1b28000..7bba41a1cd 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -174,10 +174,19 @@ public: /// mutable TocBackend toc_backend; - /// macro table - typedef map > PositionToMacroMap; - typedef map NameToPositionMacroMap; - NameToPositionMacroMap macros; + /// macro tables + typedef pair ScopeMacro; + typedef map PositionScopeMacroMap; + typedef map NamePositionScopeMacroMap; + NamePositionScopeMacroMap macros; + bool macro_lock; + + /// positions of child buffers in the buffer + typedef map BufferPositionMap; + typedef pair ScopeBuffer; + typedef map PositionScopeBufferMap; + BufferPositionMap children_positions; + PositionScopeBufferMap position_to_children; /// Container for all sort of Buffer dependant errors. map errorLists; @@ -223,8 +232,9 @@ static FileName createBufferTmpDir() Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_) : parent_buffer(0), lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_), filename(file), file_fully_loaded(false), - inset(params), toc_backend(&parent), embedded_files(&parent), - timestamp_(0), checksum_(0), wa_(0), undo_(parent) + inset(params), toc_backend(&parent), macro_lock(false), + embedded_files(&parent), timestamp_(0), checksum_(0), wa_(0), + undo_(parent) { temppath = createBufferTmpDir(); inset.setAutoBreakRows(true); @@ -238,6 +248,8 @@ Buffer::Buffer(string const & file, bool readonly) : d(new Impl(*this, FileName(file), readonly)), gui_(0) { LYXERR(Debug::INFO, "Buffer::Buffer()"); + + d->inset.getText(0)->setMacrocontextPosition(par_iterator_begin()); } @@ -251,11 +263,13 @@ Buffer::~Buffer() gui_ = 0; Buffer const * master = masterBuffer(); - if (master != this && use_gui) + if (master != this && use_gui) { // We are closing buf which was a child document so we // must update the labels and section numbering of its master // Buffer. updateLabels(*master); + master->updateMacros(); + } resetChildDocuments(false); @@ -563,6 +577,8 @@ bool Buffer::readDocument(Lexer & lex) text().paragraphs().end(), bind(&Paragraph::setInsetOwner, _1, &inset())); + updateMacros(); + updateMacroInstances(); return res; } @@ -1111,11 +1127,19 @@ void Buffer::writeLaTeXSource(odocstream & os, LYXERR(Debug::INFO, "preamble finished, now the body."); + // fold macros if possible, still with parent buffer as the + // macros will be put in the prefix anyway. + updateMacros(); + updateMacroInstances(); + // if we are doing a real file with body, even if this is the // child of some other buffer, let's cut the link here. // This happens for example if only a child document is printed. Buffer const * save_parent = 0; if (output_preamble) { + // output the macros visible for this buffer + writeParentMacros(os); + save_parent = d->parent_buffer; d->parent_buffer = 0; } @@ -1126,9 +1150,15 @@ void Buffer::writeLaTeXSource(odocstream & os, latexParagraphs(*this, paragraphs(), os, d->texrow, runparams); // Restore the parenthood if needed - if (output_preamble) + if (output_preamble) { d->parent_buffer = save_parent; + // restore macros with correct parent buffer (especially + // important for the redefinition flag which depends on the + // parent) + updateMacros(); + } + // add this just in case after all the paragraphs os << endl; d->texrow.newline(); @@ -1624,6 +1654,7 @@ void Buffer::setParent(Buffer const * buffer) { // Avoids recursive include. d->parent_buffer = buffer == this ? 0 : buffer; + updateMacros(); } @@ -1642,110 +1673,394 @@ Buffer const * Buffer::masterBuffer() const } -bool Buffer::hasMacro(docstring const & name, Paragraph const & par) const +template +typename M::iterator greatest_below(M & m, typename M::key_type const & x) { - Impl::PositionToMacroMap::iterator it; - it = d->macros[name].upper_bound(par.macrocontextPosition()); - if (it != d->macros[name].end()) - return true; + if (m.empty()) + return m.end(); - // If there is a master buffer, query that - Buffer const * master = masterBuffer(); - if (master && master != this) - return master->hasMacro(name); + typename M::iterator it = m.lower_bound(x); + if (it == m.begin()) + return m.end(); - return MacroTable::globalMacros().has(name); + it--; + return it; } -bool Buffer::hasMacro(docstring const & name) const +MacroData const * Buffer::getBufferMacro(docstring const & name, + DocIterator const & pos) const { - if( !d->macros[name].empty() ) - return true; + LYXERR(Debug::DEBUG, "Searching for " << to_ascii(name) << " at " << pos); - // If there is a master buffer, query that - Buffer const * master = masterBuffer(); - if (master && master != this) - return master->hasMacro(name); + // if paragraphs have no macro context set, pos will be empty + if (pos.empty()) + return 0; - return MacroTable::globalMacros().has(name); + // we haven't found anything yet + DocIterator bestPos = par_iterator_begin(); + MacroData const * bestData = 0; + + // find macro definitions for name + Impl::NamePositionScopeMacroMap::iterator nameIt + = d->macros.find(name); + if (nameIt != d->macros.end()) { + // find last definition in front of pos or at pos itself + Impl::PositionScopeMacroMap::const_iterator it + = greatest_below(nameIt->second, pos); + if (it != nameIt->second.end()) { + while (true) { + // scope ends behind pos? + if (pos < it->second.first) { + // Looks good, remember this. If there + // is no external macro behind this, + // we found the right one already. + bestPos = it->first; + bestData = &it->second.second; + break; + } + + // try previous macro if there is one + if (it == nameIt->second.begin()) + break; + it--; + } + } + } + + // find macros in included files + Impl::PositionScopeBufferMap::const_iterator it + = greatest_below(d->position_to_children, pos); + if (it != d->position_to_children.end()) { + while (true) { + // do we know something better (i.e. later) already? + if (it->first < bestPos ) + break; + + // scope ends behind pos? + if (pos < it->second.first) { + // look for macro in external file + d->macro_lock = true; + MacroData const * data + = it->second.second->getMacro(name, false); + d->macro_lock = false; + if (data) { + bestPos = it->first; + bestData = data; + break; + } + } + + // try previous file if there is one + if (it == d->position_to_children.begin()) + break; + --it; + } + } + + // return the best macro we have found + return bestData; } -MacroData const & Buffer::getMacro(docstring const & name, - Paragraph const & par) const +MacroData const * Buffer::getMacro(docstring const & name, + DocIterator const & pos, bool global) const { - Impl::PositionToMacroMap::iterator it; - it = d->macros[name].upper_bound(par.macrocontextPosition()); - if( it != d->macros[name].end() ) - return it->second; + if (d->macro_lock) + return 0; + + // query buffer macros + MacroData const * data = getBufferMacro(name, pos); + if (data != 0) + return data; // If there is a master buffer, query that - Buffer const * master = masterBuffer(); - if (master && master != this) - return master->getMacro(name); + if (d->parent_buffer) { + d->macro_lock = true; + MacroData const * macro + = d->parent_buffer->getMacro(name, *this, false); + d->macro_lock = false; + if (macro) + return macro; + } + + if (global) { + data = MacroTable::globalMacros().get(name); + if (data != 0) + return data; + } - return MacroTable::globalMacros().get(name); + return 0; } -MacroData const & Buffer::getMacro(docstring const & name) const +MacroData const * Buffer::getMacro(docstring const & name, bool global) const { - Impl::PositionToMacroMap::iterator it; - it = d->macros[name].begin(); - if( it != d->macros[name].end() ) - return it->second; + // set scope end behind the last paragraph + DocIterator scope = par_iterator_begin(); + scope.pit() = scope.lastpit() + 1; - // If there is a master buffer, query that - Buffer const * master = masterBuffer(); - if (master && master != this) - return master->getMacro(name); + return getMacro(name, scope, global); +} - return MacroTable::globalMacros().get(name); + +MacroData const * Buffer::getMacro(docstring const & name, Buffer const & child, bool global) const +{ + // look where the child buffer is included first + Impl::BufferPositionMap::iterator it + = d->children_positions.find(&child); + if (it == d->children_positions.end()) + return 0; + + // check for macros at the inclusion position + return getMacro(name, it->second, global); } -void Buffer::updateMacros() +void Buffer::updateEnvironmentMacros(DocIterator & it, + pit_type lastpit, + DocIterator & scope) const { - // start with empty table - d->macros = Impl::NameToPositionMacroMap(); - - // Iterate over buffer - ParagraphList & pars = text().paragraphs(); - for (size_t i = 0, n = pars.size(); i != n; ++i) { - // set position again - pars[i].setMacrocontextPosition(i); - - //lyxerr << "searching main par " << i - // << " for macro definitions" << endl; - InsetList const & insets = pars[i].insetList(); - InsetList::const_iterator it = insets.begin(); + Paragraph & par = it.paragraph(); + depth_type depth + = par.params().depth(); + Length const & leftIndent + = par.params().leftIndent(); + + // look for macros in each paragraph + while (it.pit() <= lastpit) { + Paragraph & par = it.paragraph(); + + // increased depth? + if ((par.params().depth() > depth + || par.params().leftIndent() != leftIndent) + && par.layout()->isEnvironment()) { + updateBlockMacros(it, scope); + continue; + } + + // iterate over the insets of the current paragraph + InsetList const & insets = par.insetList(); + InsetList::const_iterator iit = insets.begin(); InsetList::const_iterator end = insets.end(); - for ( ; it != end; ++it) { - if (it->inset->lyxCode() != MATHMACRO_CODE) + for (; iit != end; ++iit) { + it.pos() = iit->pos; + + // is it a nested text inset? + if (iit->inset->asInsetText()) { + // Inset needs its own scope? + InsetText const * itext + = iit->inset->asInsetText(); + bool newScope = itext->isMacroScope(*this); + + // scope which ends just behind die inset + DocIterator insetScope = it; + insetScope.pos()++; + + // collect macros in inset + it.push_back(CursorSlice(*iit->inset)); + updateInsetMacros(it, newScope ? insetScope : scope); + it.pop_back(); + continue; + } + + // is it an external file? + if (iit->inset->lyxCode() == INCLUDE_CODE) { + // get buffer of external file + InsetCommand const & inset + = static_cast(*iit->inset); + InsetCommandParams const & ip = inset.params(); + d->macro_lock = true; + Buffer * child = loadIfNeeded(*this, ip); + d->macro_lock = false; + if (!child) + continue; + + // register it, 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; + + // register child with its scope + d->position_to_children[it] = Impl::ScopeBuffer(scope, child); + + continue; + } + + if (iit->inset->lyxCode() != MATHMACRO_CODE) continue; // get macro data - MathMacroTemplate const & macroTemplate - = static_cast(*it->inset); + MathMacroTemplate & macroTemplate + = static_cast(*iit->inset); + MacroContext mc(*this, it); + macroTemplate.updateToContext(mc); // valid? - if (macroTemplate.validMacro()) { - MacroData macro = macroTemplate.asMacroData(); + bool valid = macroTemplate.validMacro(); + // FIXME: Should be fixNameAndCheckIfValid() in fact, + // then the BufferView's cursor will be invalid in + // some cases which leads to crashes. + if (!valid) + continue; - // redefinition? - // call hasMacro here instead of directly querying mc to - // also take the master document into consideration - macro.setRedefinition(hasMacro(macroTemplate.name())); + // register macro + d->macros[macroTemplate.name()][it] + = Impl::ScopeMacro(scope, MacroData(*this, it)); + } - // register macro (possibly overwrite the previous one of this paragraph) - d->macros[macroTemplate.name()][i] = macro; - } + // next paragraph + it.pit()++; + it.pos() = 0; + } +} + + +void Buffer::updateBlockMacros(DocIterator & it, DocIterator & scope) const +{ + Paragraph & par = it.paragraph(); + + // set scope for macros in this paragraph: + // * either the "old" outer scope + // * or the scope ending after the environment + if (par.layout()->isEnvironment()) { + // find end of environment block, + DocIterator envEnd = it; + pit_type n = it.lastpit() + 1; + depth_type depth = par.params().depth(); + Length const & length = par.params().leftIndent(); + // looping through the paragraph, basically until + // the layout changes or the depth gets smaller. + // (the logic of output_latex.cpp's TeXEnvironment) + do { + envEnd.pit()++; + if (envEnd.pit() == n) + break; + } while (par.layout() == envEnd.paragraph().layout() + || depth < envEnd.paragraph().params().depth() + || length != envEnd.paragraph().params().leftIndent()); + + // collect macros from environment block + updateEnvironmentMacros(it, envEnd.pit() - 1, envEnd); + } else { + // collect macros from paragraph + updateEnvironmentMacros(it, it.pit(), scope); + } +} + + +void Buffer::updateInsetMacros(DocIterator & it, DocIterator & scope) const +{ + // look for macros in each paragraph + pit_type n = it.lastpit() + 1; + while (it.pit() < n) + updateBlockMacros(it, scope); +} + + +void Buffer::updateMacros() const +{ + if (d->macro_lock) + return; + + LYXERR(Debug::DEBUG, "updateMacro of " << d->filename.onlyFileName()); + + // start with empty table + d->macros.clear(); + d->children_positions.clear(); + d->position_to_children.clear(); + + // Iterate over buffer, starting with first paragraph + // The scope must be bigger than any lookup DocIterator + // later. For the global lookup, lastpit+1 is used, hence + // we use lastpit+2 here. + DocIterator it = par_iterator_begin(); + DocIterator outerScope = it; + outerScope.pit() = outerScope.lastpit() + 2; + updateInsetMacros(it, outerScope); +} + + +void Buffer::updateMacroInstances() const +{ + LYXERR(Debug::DEBUG, "updateMacroInstances for " << d->filename.onlyFileName()); + ParIterator it = par_iterator_begin(); + ParIterator end = par_iterator_end(); + for (; it != end; it.forwardPos()) { + // look for MathData cells in InsetMathNest insets + Inset * inset = it.nextInset(); + if (!inset) + continue; + + InsetMath * minset = inset->asInsetMath(); + if (!minset) + continue; + + // update macro in all cells of the InsetMathNest + DocIterator::idx_type n = minset->nargs(); + MacroContext mc = MacroContext(*this, it); + for (DocIterator::idx_type i = 0; i < n; ++i) { + MathData & data = minset->cell(i); + data.updateMacros(0, mc); } } } +void Buffer::listMacroNames(MacroNameSet & macros) const +{ + if (d->macro_lock) + return; + + d->macro_lock = true; + + // loop over macro names + Impl::NamePositionScopeMacroMap::iterator nameIt + = d->macros.begin(); + Impl::NamePositionScopeMacroMap::iterator nameEnd + = d->macros.end(); + for (; nameIt != nameEnd; ++nameIt) + macros.insert(nameIt->first); + + // loop over children + Impl::BufferPositionMap::iterator it + = d->children_positions.begin(); + Impl::BufferPositionMap::iterator end + = d->children_positions.end(); + for (; it != end; ++it) + it->first->listMacroNames(macros); + + // call parent + if (d->parent_buffer) + d->parent_buffer->listMacroNames(macros); + + d->macro_lock = false; +} + + +void Buffer::writeParentMacros(odocstream & os) const +{ + if (!d->parent_buffer) + return; + + // collect macro names + MacroNameSet names; + d->parent_buffer->listMacroNames(names); + + // resolve and output them + MacroNameSet::iterator it = names.begin(); + MacroNameSet::iterator end = names.end(); + for (; it != end; ++it) { + // defined? + MacroData const * data = + d->parent_buffer->getMacro(*it, *this, false); + if (data) + data->write(os, true); + } +} + + void Buffer::changeRefsIfUnique(docstring const & from, docstring const & to, InsetCode code) { @@ -2016,6 +2331,10 @@ void Buffer::resetChildDocuments(bool close_them) const if (use_gui && masterBuffer() == this) updateLabels(*this); + + // clear references to children in macro tables + d->children_positions.clear(); + d->position_to_children.clear(); } @@ -2037,6 +2356,8 @@ void Buffer::loadChildDocuments() const if (use_gui && masterBuffer() == this) updateLabels(*this); + + updateMacros(); } @@ -2089,6 +2410,9 @@ bool Buffer::doExport(string const & format, bool put_in_tempdir, filename = changeExtension(filename, formats.extension(backend_format)); + // fix macros + updateMacroInstances(); + // Plain text backend if (backend_format == "text") writePlaintextFile(*this, FileName(filename), runparams); diff --git a/src/Buffer.h b/src/Buffer.h index 2c5a5a1c77..9dd0fc38a4 100644 --- a/src/Buffer.h +++ b/src/Buffer.h @@ -17,6 +17,7 @@ #include "support/strfwd.h" #include "support/types.h" +#include #include #include @@ -25,6 +26,7 @@ namespace lyx { class BufferParams; class EmbeddedFiles; +class DocIterator; class ErrorItem; class ErrorList; class FuncRequest; @@ -359,16 +361,25 @@ public: // // Macro handling // - /// Collect macros in paragraphs - void updateMacros(); - /// Look for macro defined before par (or in the master buffer) - bool hasMacro(docstring const & name, Paragraph const & par) const; - /// Look for macro defined anywhere in the buffer (or in the master buffer) - bool hasMacro(docstring const & name) const; - /// Return macro defined before par (or in the master buffer) - MacroData const & getMacro(docstring const & name, Paragraph const & par) const; + /// Collect macro definitions in paragraphs + void updateMacros() const; + /// Iterate through the whole buffer and try to resolve macros + void updateMacroInstances() const; + + typedef std::set MacroNameSet; + /// List macro names of this buffer. the parent and the children + void listMacroNames(MacroNameSet & macros) const; + /// Write out all macros somewhere defined in the parent, + /// its parents and its children, which are visible at the beginning + /// of this buffer + void writeParentMacros(odocstream & os) const; + + /// Return macro defined before pos (or in the master buffer) + MacroData const * getMacro(docstring const & name, DocIterator const & pos, bool global = true) const; /// Return macro defined anywhere in the buffer (or in the master buffer) - MacroData const & getMacro(docstring const & name) const; + MacroData const * getMacro(docstring const & name, bool global = true) const; + /// Return macro defined before the inclusion of the child + MacroData const * getMacro(docstring const & name, Buffer const & child, bool global = true) const; /// Replace the inset contents for insets which InsetCode is equal /// to the passed \p inset_code. @@ -442,7 +453,30 @@ public: std::vector exportableFormats(bool only_viewable) const; private: - /// + /// search for macro in local (buffer) table or in children + MacroData const * getBufferMacro(docstring const & name, + DocIterator const & pos) const; + /** Update macro table in the whole text inset + \param it at the start of the text inset) + */ + void updateInsetMacros(DocIterator & it, + DocIterator & scope) const; + /** Update macro table for paragraphs until \c lastpit + \param it in some text inset + \param lastpit last processed paragraph + */ + void updateEnvironmentMacros(DocIterator & it, + pit_type lastpit, + DocIterator & scope) const; + /** Update macro table for one paragraph block with + same layout and depth, until \c lastpit + \param it in some text inset + \param lastpit last processed paragraph + */ + void updateBlockMacros(DocIterator & it, + DocIterator & scope) const; + + /// bool readFileHelper(support::FileName const & s); /// std::vector backends() const; diff --git a/src/BufferView.cpp b/src/BufferView.cpp index 19b9c9c54f..e6b2882346 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -349,9 +349,7 @@ void BufferView::processUpdateFlags(Update::flags flags) << ", singlepar = " << (flags & Update::SinglePar) << "] buffer: " << &buffer_); - // Update macro store - if (!(cursor().inMathed() && cursor().inMacroMode())) - buffer_.updateMacros(); + buffer_.updateMacros(); // Now do the first drawing step if needed. This consists on updating // the CoordCache in updateMetrics(). diff --git a/src/DocIterator.cpp b/src/DocIterator.cpp index 3d2c18d634..51d21921a0 100644 --- a/src/DocIterator.cpp +++ b/src/DocIterator.cpp @@ -532,29 +532,6 @@ ostream & operator<<(ostream & os, DocIterator const & dit) } -bool operator<(DocIterator const & p, DocIterator const & q) -{ - size_t depth = min(p.depth(), q.depth()); - for (size_t i = 0 ; i < depth ; ++i) { - if (p[i] != q[i]) - return p[i] < q[i]; - } - return p.depth() < q.depth(); -} - - -bool operator>(DocIterator const & p, DocIterator const & q) -{ - return q < p; -} - - -bool operator<=(DocIterator const & p, DocIterator const & q) -{ - return !(q < p); -} - - /////////////////////////////////////////////////////// StableDocIterator::StableDocIterator(DocIterator const & dit) diff --git a/src/DocIterator.h b/src/DocIterator.h index fc6125186c..6e730ea685 100644 --- a/src/DocIterator.h +++ b/src/DocIterator.h @@ -262,6 +262,32 @@ inline bool operator!=(DocIterator const & di1, DocIterator const & di2) } +inline +bool operator<(DocIterator const & p, DocIterator const & q) +{ + size_t depth = std::min(p.depth(), q.depth()); + for (size_t i = 0 ; i < depth ; ++i) { + if (p[i] != q[i]) + return p[i] < q[i]; + } + return p.depth() < q.depth(); +} + + +inline +bool operator>(DocIterator const & p, DocIterator const & q) +{ + return q < p; +} + + +inline +bool operator<=(DocIterator const & p, DocIterator const & q) +{ + return !(q < p); +} + + // The difference to a ('non stable') DocIterator is the removed // (overwritten by 0...) part of the CursorSlice data items. So this thing // is suitable for external storage, but not for iteration as such. diff --git a/src/LyXFunc.cpp b/src/LyXFunc.cpp index dfde956fb6..61bc51dbc8 100644 --- a/src/LyXFunc.cpp +++ b/src/LyXFunc.cpp @@ -1790,7 +1790,7 @@ void LyXFunc::dispatch(FuncRequest const & cmd) if (flag.enabled() && !lyxaction.funcHasFlag(action, LyXAction::NoBuffer) && !lyxaction.funcHasFlag(action, LyXAction::ReadOnly)) - lyx_view_->buffer()->markDirty(); + lyx_view_->buffer()->markDirty(); //Do we have a selection? theSelection().haveSelection(view()->cursor().selection()); diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index c21b7b349b..61fe11117b 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -183,10 +183,6 @@ public: /// ParagraphParameters params_; - /// position of the paragraph in the buffer. Only macros from - /// paragraphs strictly smaller are visible in this paragraph - unsigned int macrocontext_position_; - /// for recording and looking up changes Changes changes_; @@ -232,7 +228,6 @@ Paragraph::Private::Private(Paragraph * owner) : owner_(owner), inset_owner_(0), begin_of_body_(0) { id_ = paragraph_id++; - macrocontext_position_ = 0; text_.reserve(100); } @@ -2497,18 +2492,6 @@ int Paragraph::checkBiblio(bool track_changes) } -unsigned int Paragraph::macrocontextPosition() const -{ - return d->macrocontext_position_; -} - - -void Paragraph::setMacrocontextPosition(unsigned int pos) -{ - d->macrocontext_position_ = pos; -} - - void Paragraph::checkAuthors(AuthorList const & authorList) { d->changes_.checkAuthors(authorList); diff --git a/src/Paragraph.h b/src/Paragraph.h index 30c033b487..3b9d4be36f 100644 --- a/src/Paragraph.h +++ b/src/Paragraph.h @@ -344,15 +344,6 @@ public: /// was previously past that position. Return 0 otherwise. int checkBiblio(bool track_changes); - /// To resolve macros properly the paragraphs are numbered. - /// Every macro definition is stored with its paragraph number - /// as well. Only those macros with a smaller number become - /// visible in a paragraph (plus those in the same paragraph, but - /// in an earlier inset. - unsigned int macrocontextPosition() const; - /// - void setMacrocontextPosition(unsigned int pos); - /// For each author, set 'used' to true if there is a change /// by this author in the paragraph. void checkAuthors(AuthorList const & authorList); diff --git a/src/Text.cpp b/src/Text.cpp index 6b29809261..12ed1b85d9 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -1392,4 +1392,16 @@ void Text::charsTranspose(Cursor & cur) } +DocIterator Text::macrocontextPosition() const +{ + return macrocontext_position_; +} + + +void Text::setMacrocontextPosition(DocIterator const & pos) +{ + macrocontext_position_ = pos; +} + + } // namespace lyx diff --git a/src/Text.h b/src/Text.h index caf7b65d68..0ebc1eeace 100644 --- a/src/Text.h +++ b/src/Text.h @@ -14,6 +14,7 @@ #ifndef TEXT_H #define TEXT_H +#include "DocIterator.h" #include "ParagraphList.h" namespace lyx { @@ -257,6 +258,14 @@ public: /// from \first to \last paragraph void deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges); + /// To resolve macros properly the texts get their DocIterator. + /// Every macro definition is stored with its DocIterator + /// as well. Only those macros with a smaller iterator become + /// visible in a paragraph. + DocIterator macrocontextPosition() const; + /// + void setMacrocontextPosition(DocIterator const & pos); + public: /// ParagraphList pars_; @@ -287,6 +296,9 @@ private: /// \param asParagraphs whether to paste as paragraphs or as lines void pasteString(Cursor & cur, docstring const & str, bool asParagraphs); + + /// position of the text in the buffer. + DocIterator macrocontext_position_; }; } // namespace lyx diff --git a/src/Text3.cpp b/src/Text3.cpp index 58aaddad90..b90c6c8482 100644 --- a/src/Text3.cpp +++ b/src/Text3.cpp @@ -1435,8 +1435,10 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd) string const s1 = token(s, ' ', 1); int const nargs = s1.empty() ? 0 : convert(s1); string const s2 = token(s, ' ', 2); - string const type = s2.empty() ? "newcommand" : s2; - cur.insert(new MathMacroTemplate(from_utf8(token(s, ' ', 0)), nargs, false, from_utf8(type))); + MacroType type = MacroTypeNewcommand; + if (s2 == "def") + type = MacroTypeDef; + cur.insert(new MathMacroTemplate(from_utf8(token(s, ' ', 0)), nargs, false, type)); //cur.nextInset()->edit(cur, true); } break; diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index a90d0112fd..e1cf6591e4 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -375,35 +375,28 @@ bool TextMetrics::redoParagraph(pit_type const pit) // so better to calculate that once here. int const right_margin = rightMargin(pm); + // iterator pointing to paragraph to resolve macros + DocIterator parPos = text_->macrocontextPosition(); + if (!parPos.empty()) + parPos.pit() = pit; + // redo insets // FIXME: We should always use getFont(), see documentation of // noFontChange() in Inset.h. Font const bufferfont = buffer.params().getFont(); - MacroContext mc(buffer, par); InsetList::const_iterator ii = par.insetList().begin(); InsetList::const_iterator iend = par.insetList().end(); for (; ii != iend; ++ii) { - // the macro must come here, _before_ the metric call, because - // the macro should see itself to detect recursions. To find out - // whether the macro definition is a redefinition it will look - // at the MacroData::redefinition_. So it doesn't get confused - // by the already existing macro definition of itself in the - // macro context. - if (ii->inset->lyxCode() == MATHMACRO_CODE) { - // get macro data - MathMacroTemplate const & macroTemplate - = static_cast(*ii->inset); - - // valid? - if (macroTemplate.validMacro()) { - MacroData macro = macroTemplate.asMacroData(); - - // redefinition? - macro.setRedefinition(mc.has(macroTemplate.name())); - - // register macro (possibly overwrite the previous one of this paragraph) - mc.insert(macroTemplate.name(), macro); - } + // position already initialized? + if (!parPos.empty()) { + parPos.pos() = ii->pos; + + // A macro template would normally not be visible + // by itself. But the tex macro semantics allow + // recursion, so we artifically take the context + // after the macro template to simulate this. + if (ii->inset->lyxCode() == MATHMACRO_CODE) + parPos.pos()++; } // do the metric calculation @@ -412,6 +405,7 @@ bool TextMetrics::redoParagraph(pit_type const pit) - right_margin; Font const & font = ii->inset->noFontChange() ? bufferfont : getDisplayFont(pit, ii->pos); + MacroContext mc(buffer, parPos); MetricsInfo mi(bv_, font.fontInfo(), w, mc); ii->inset->metrics(mi, dim); Dimension const old_dim = pm.insetDimension(ii->inset); diff --git a/src/buffer_funcs.cpp b/src/buffer_funcs.cpp index 05727eafc9..d38e3aaaa7 100644 --- a/src/buffer_funcs.cpp +++ b/src/buffer_funcs.cpp @@ -410,6 +410,12 @@ void updateLabels(Buffer const & buf, ParIterator & parit) { BOOST_ASSERT(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. + parit.text()->setMacrocontextPosition(parit); + depth_type maxdepth = 0; pit_type const lastpit = parit.lastpit(); for ( ; parit.pit() <= lastpit ; ++parit.pit()) { diff --git a/src/insets/InsetBox.cpp b/src/insets/InsetBox.cpp index 91619aa2ab..2636024399 100644 --- a/src/insets/InsetBox.cpp +++ b/src/insets/InsetBox.cpp @@ -239,6 +239,13 @@ bool InsetBox::getStatus(Cursor & cur, FuncRequest const & cmd, } +bool InsetBox::isMacroScope(Buffer const & buf) const +{ + BoxType btype = boxtranslator().find(params_.type); + return btype != Frameless || params_.inner_box; +} + + int InsetBox::latex(Buffer const & buf, odocstream & os, OutputParams const & runparams) const { diff --git a/src/insets/InsetBox.h b/src/insets/InsetBox.h index 337c3bc34b..3ae2c6946e 100644 --- a/src/insets/InsetBox.h +++ b/src/insets/InsetBox.h @@ -86,6 +86,8 @@ public: /// bool noFontChange() const { return true; } /// + bool isMacroScope(Buffer const & buf) const; + /// int latex(Buffer const &, odocstream &, OutputParams const &) const; /// int plaintext(Buffer const &, odocstream &, OutputParams const &) const; diff --git a/src/insets/InsetBranch.cpp b/src/insets/InsetBranch.cpp index b272647b83..0eed816e60 100644 --- a/src/insets/InsetBranch.cpp +++ b/src/insets/InsetBranch.cpp @@ -270,6 +270,12 @@ void InsetBranch::validate(LaTeXFeatures & features) const } +bool InsetBranch::isMacroScope(Buffer const & buf) const +{ + // Its own scope if not selected by buffer + return !isBranchSelected(buf); +} + string const InsetBranchMailer::name_("branch"); diff --git a/src/insets/InsetBranch.h b/src/insets/InsetBranch.h index 754e788cb6..30305d8235 100644 --- a/src/insets/InsetBranch.h +++ b/src/insets/InsetBranch.h @@ -80,8 +80,10 @@ public: bool isBranchSelected(Buffer const & buffer) const; /// bool getStatus(Cursor &, FuncRequest const &, FuncStatus &) const; - // + /// virtual void updateLabels(Buffer const &, ParIterator const &); + /// + bool isMacroScope(Buffer const & buf) const; protected: /// InsetBranch(InsetBranch const &); diff --git a/src/insets/InsetCaption.cpp b/src/insets/InsetCaption.cpp index f741289801..0c3be7102a 100644 --- a/src/insets/InsetCaption.cpp +++ b/src/insets/InsetCaption.cpp @@ -184,6 +184,7 @@ bool InsetCaption::insetAllowed(InsetCode code) const case WRAP_CODE: case CAPTION_CODE: case NEWPAGE_CODE: + case MATHMACRO_CODE: return false; default: return InsetText::insetAllowed(code); diff --git a/src/insets/InsetCollapsable.cpp b/src/insets/InsetCollapsable.cpp index c6ae5a5eee..fe747bc9e8 100644 --- a/src/insets/InsetCollapsable.cpp +++ b/src/insets/InsetCollapsable.cpp @@ -782,6 +782,22 @@ InsetCollapsable::Decoration InsetCollapsable::decoration() const } +bool InsetCollapsable::isMacroScope(Buffer const & buf) const +{ + // layout_ == 0 leads to no latex output, so ignore + // the macros outside + if (!layout_) + return true; + + // see InsetCollapsable::latex(...) below. In these case + // an environment is opened there + if (!layout_->latexname.empty()) + return true; + + return false; +} + + int InsetCollapsable::latex(Buffer const & buf, odocstream & os, OutputParams const & runparams) const { diff --git a/src/insets/InsetCollapsable.h b/src/insets/InsetCollapsable.h index 999315a506..1b6cbc3c45 100644 --- a/src/insets/InsetCollapsable.h +++ b/src/insets/InsetCollapsable.h @@ -76,6 +76,8 @@ public: /// can we go further down on mouse click? bool descendable() const; /// + bool isMacroScope(Buffer const & buf) const; + /// void setLabel(docstring const & l); /// virtual void setButtonLabel() {} diff --git a/src/insets/InsetFloat.h b/src/insets/InsetFloat.h index ee2728a768..228d3130d7 100644 --- a/src/insets/InsetFloat.h +++ b/src/insets/InsetFloat.h @@ -59,6 +59,8 @@ public: /// InsetCode lyxCode() const { return FLOAT_CODE; } /// + bool isMacroScope(Buffer const & buf) const { return true; } + /// int latex(Buffer const &, odocstream &, OutputParams const &) const; /// diff --git a/src/insets/InsetFootlike.h b/src/insets/InsetFootlike.h index 32d0f6c84c..3c7cc09ee4 100644 --- a/src/insets/InsetFootlike.h +++ b/src/insets/InsetFootlike.h @@ -33,6 +33,8 @@ public: /// void write(Buffer const & buf, std::ostream & os) const; /// + bool isMacroScope(Buffer const & buf) const { return true; } + /// bool insetAllowed(InsetCode) const; /** returns true if, when outputing LaTeX, font changes should be closed before generating this inset. This is needed for diff --git a/src/insets/InsetInclude.cpp b/src/insets/InsetInclude.cpp index ec2e81c727..42a61077fc 100644 --- a/src/insets/InsetInclude.cpp +++ b/src/insets/InsetInclude.cpp @@ -268,9 +268,7 @@ docstring const InsetInclude::getScreenLabel(Buffer const & buf) const } -namespace { - -/// return the child buffer if the file is a LyX doc and is loaded + /// return the child buffer if the file is a LyX doc and is loaded Buffer * getChildBuffer(Buffer const & buffer, InsetCommandParams const & params) { if (isVerbatim(params) || isListings(params)) @@ -289,9 +287,6 @@ Buffer * getChildBuffer(Buffer const & buffer, InsetCommandParams const & params return childBuffer; } -} // namespace anon - - /// return true if the file is or got loaded. Buffer * loadIfNeeded(Buffer const & parent, InsetCommandParams const & params) { diff --git a/src/insets/InsetInclude.h b/src/insets/InsetInclude.h index f495d1d9ed..cddb631908 100644 --- a/src/insets/InsetInclude.h +++ b/src/insets/InsetInclude.h @@ -131,7 +131,9 @@ private: mutable docstring listings_label_; }; - +/// return the child buffer if the file is a LyX doc and is loaded +Buffer * getChildBuffer(Buffer const & buffer, InsetCommandParams const & params); + /// return loaded Buffer or zero if the file loading did not proceed. Buffer * loadIfNeeded(Buffer const & parent, InsetCommandParams const & params); diff --git a/src/insets/InsetListings.h b/src/insets/InsetListings.h index d2b63e7110..f5cf33ce88 100644 --- a/src/insets/InsetListings.h +++ b/src/insets/InsetListings.h @@ -45,6 +45,8 @@ public: /// virtual docstring const editMessage() const; /// + bool isMacroScope(Buffer const & buf) const { return true; } + /// int latex(Buffer const &, odocstream &, OutputParams const &) const; /// void validate(LaTeXFeatures &) const; diff --git a/src/insets/InsetNote.cpp b/src/insets/InsetNote.cpp index 9aa037dba3..94463a64de 100644 --- a/src/insets/InsetNote.cpp +++ b/src/insets/InsetNote.cpp @@ -218,6 +218,16 @@ void InsetNote::updateLabels(Buffer const & buf, ParIterator const & it) } +bool InsetNote::isMacroScope(Buffer const & buf) const +{ + // LyX note has no latex output + if (params_.type == InsetNoteParams::Note) + return true; + + return InsetCollapsable::isMacroScope(buf); +} + + int InsetNote::latex(Buffer const & buf, odocstream & os, OutputParams const & runparams_in) const { diff --git a/src/insets/InsetNote.h b/src/insets/InsetNote.h index 11fe68f783..1d51ee74d1 100644 --- a/src/insets/InsetNote.h +++ b/src/insets/InsetNote.h @@ -62,6 +62,8 @@ public: /// show the note dialog bool showInsetDialog(BufferView * bv) const; /// + bool isMacroScope(Buffer const & buf) const; + /// int latex(Buffer const &, odocstream &, OutputParams const &) const; /// int plaintext(Buffer const &, odocstream &, OutputParams const &) const; diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp index fb0dc7b482..b8f8b5562a 100644 --- a/src/insets/InsetTabular.cpp +++ b/src/insets/InsetTabular.cpp @@ -2897,6 +2897,15 @@ void InsetTabular::buffer(Buffer const * b) } +bool InsetTabular::insetAllowed(InsetCode code) const +{ + if (code == MATHMACRO_CODE) + return false; + + return true; +} + + void InsetTabular::write(Buffer const & buf, ostream & os) const { os << "Tabular" << endl; diff --git a/src/insets/InsetTabular.h b/src/insets/InsetTabular.h index 8950077de2..c3a64cea93 100644 --- a/src/insets/InsetTabular.h +++ b/src/insets/InsetTabular.h @@ -679,7 +679,7 @@ public: /// EDITABLE editable() const { return HIGHLY_EDITABLE; } /// - bool insetAllowed(InsetCode) const { return true; } + bool insetAllowed(InsetCode code) const; /// bool allowSpellCheck() const { return true; } /// diff --git a/src/insets/InsetText.h b/src/insets/InsetText.h index f6bb2112a9..40efd2b7aa 100644 --- a/src/insets/InsetText.h +++ b/src/insets/InsetText.h @@ -130,6 +130,8 @@ public: /// should paragraph indendation be ommitted in any case? bool neverIndent(Buffer const &) const; /// + virtual bool isMacroScope(Buffer const & buf) const { return true; } + /// virtual bool allowMultiPar() const { return true; } /// InsetText(InsetText const &); diff --git a/src/insets/InsetTheorem.h b/src/insets/InsetTheorem.h index bf20efbd2d..d22c3451c0 100644 --- a/src/insets/InsetTheorem.h +++ b/src/insets/InsetTheorem.h @@ -35,6 +35,8 @@ public: /// void draw(PainterInfo & pi, int x, int y) const; /// + bool isMacroScope(Buffer const & buf) const { return true; } + /// int latex(Buffer const &, odocstream &, OutputParams const &) const; /// diff --git a/src/insets/InsetWrap.h b/src/insets/InsetWrap.h index 01a9d7645f..4202469f05 100644 --- a/src/insets/InsetWrap.h +++ b/src/insets/InsetWrap.h @@ -57,6 +57,8 @@ public: /// InsetCode lyxCode() const { return WRAP_CODE; } /// + bool isMacroScope(Buffer const & buf) const { return true; } + /// int latex(Buffer const &, odocstream &, OutputParams const &) const; /// diff --git a/src/lyxfind.cpp b/src/lyxfind.cpp index 34ecff40a1..8406c77193 100644 --- a/src/lyxfind.cpp +++ b/src/lyxfind.cpp @@ -208,6 +208,7 @@ int replace(BufferView * bv, docstring const & searchstr, cap::replaceSelectionWithString(cur, replacestr, fw); bv->buffer().markDirty(); find(bv, searchstr, cs, mw, fw, false); + bv->buffer().updateMacros(); bv->processUpdateFlags(Update::Force | Update::FitCursor); return 1; diff --git a/src/mathed/InsetMathNest.cpp b/src/mathed/InsetMathNest.cpp index 0844b4d8a9..00d4e2ac6e 100644 --- a/src/mathed/InsetMathNest.cpp +++ b/src/mathed/InsetMathNest.cpp @@ -994,12 +994,13 @@ void InsetMathNest::doDispatch(Cursor & cur, FuncRequest & cmd) bool fold = cmd.action == LFUN_MATH_MACRO_FOLD; bool found = findMacroToFoldUnfold(it, fold); if (found) { - cur.recordUndo(); + MathMacro * macro = it.nextInset()->asInsetMath()->asMacro(); + cur.recordUndoInset(); if (fold) - it.nextInset()->asInsetMath()->asMacro()->fold(cur); + macro->fold(cur); else - it.nextInset()->asInsetMath()->asMacro()->unfold(cur); - } + macro->unfold(cur); + } break; } diff --git a/src/mathed/MacroTable.cpp b/src/mathed/MacroTable.cpp index 7dea5f9341..406e901c49 100644 --- a/src/mathed/MacroTable.cpp +++ b/src/mathed/MacroTable.cpp @@ -10,14 +10,17 @@ #include +#include "InsetMathSqrt.h" #include "MacroTable.h" #include "MathMacroTemplate.h" #include "MathMacroArgument.h" +#include "MathStream.h" #include "MathSupport.h" -#include "InsetMathSqrt.h" - #include "InsetMathNest.h" + #include "Buffer.h" +#include "InsetList.h" +#include "Text.h" #include "support/debug.h" #include "DocIterator.h" @@ -38,24 +41,32 @@ namespace lyx { ///////////////////////////////////////////////////////////////////// MacroData::MacroData() - : numargs_(0), lockCount_(0), redefinition_(false) + : queried_(true), numargs_(0), optionals_(0), lockCount_(0), + redefinition_(false), type_(MacroTypeNewcommand) {} - -MacroData::MacroData(docstring const & definition, - vector const & defaults, - int numargs, int optionals, docstring const & display, - string const & requires) - : definition_(definition), numargs_(numargs), display_(display), - requires_(requires), lockCount_(0), redefinition_(false), - optionals_(optionals), defaults_(defaults) + + +MacroData::MacroData(Buffer const & buf, DocIterator const & pos) + : buffer_(&buf), pos_(pos), queried_(false), numargs_(0), + optionals_(0), lockCount_(0), redefinition_(false), + type_(MacroTypeNewcommand) { - defaults_.resize(optionals); } + + +MacroData::MacroData(MathMacroTemplate const & macro) + : queried_(false), numargs_(0), optionals_(0), lockCount_(0), + redefinition_(false), type_(MacroTypeNewcommand) +{ + queryData(macro); +} void MacroData::expand(vector const & args, MathData & to) const { + updateData(); + InsetMathSqrt inset; // Hack. Any inset with a cell would do. // FIXME UNICODE asArray(display_.empty() ? definition_ : display_, inset.cell(0)); @@ -81,12 +92,14 @@ void MacroData::expand(vector const & args, MathData & to) const size_t MacroData::optionals() const { + updateData(); return optionals_; } vector const & MacroData::defaults() const { + updateData(); return defaults_; } @@ -98,6 +111,63 @@ void MacroData::unlock() const } +void MacroData::queryData(MathMacroTemplate const & macro) const +{ + if (queried_) + return; + + queried_ = true; + definition_ = macro.definition(); + numargs_ = macro.numArgs(); + display_ = macro.displayDefinition(); + redefinition_ = macro.redefinition(); + type_ = macro.type(); + optionals_ = macro.numOptionals(); + macro.getDefaults(defaults_); +} + + +void MacroData::updateData() const +{ + if (queried_) + return; + + BOOST_ASSERT(buffer_ != 0); + + // Try to fix position DocIterator. Should not do anything in theory. + pos_.fixIfBroken(); + + // find macro template + Inset * inset = pos_.nextInset(); + if (inset == 0 || inset->lyxCode() != MATHMACRO_CODE) { + lyxerr << "BUG: No macro template found by MacroData" << endl; + return; + } + + // query the data from the macro template + queryData(static_cast(*inset)); +} + + +void MacroData::write(odocstream & os, bool overwriteRedefinition) const +{ + updateData(); + + // find macro template + Inset * inset = pos_.nextInset(); + if (inset == 0 || inset->lyxCode() != MATHMACRO_CODE) { + lyxerr << "BUG: No macro template found by MacroData" << endl; + return; + } + + // output template + MathMacroTemplate const & tmpl + = static_cast(*inset); + WriteStream wi(os, false, true); + tmpl.write(wi, overwriteRedefinition); +} + + ///////////////////////////////////////////////////////////////////// // // The global table of macros @@ -111,17 +181,10 @@ MacroTable & MacroTable::globalMacros() } -bool MacroTable::has(docstring const & name) const -{ - return find(name) != end(); -} - - -MacroData const & MacroTable::get(docstring const & name) const +MacroData const * MacroTable::get(docstring const & name) const { const_iterator it = find(name); - BOOST_ASSERT(it != end()); - return it->second; + return it == end() ? 0 : &it->second; } @@ -136,7 +199,7 @@ void MacroTable::insert(docstring const & def, string const & requires) { //lyxerr << "MacroTable::insert, def: " << to_utf8(def) << endl; MathMacroTemplate mac(def); - MacroData data = mac.asMacroData(); + MacroData data(mac); data.requires() = requires; insert(mac.name(), data); } @@ -154,38 +217,21 @@ void MacroTable::dump() } -MacroContext::MacroContext(Buffer const & buf, Paragraph const & par) - : buf_(buf), par_(par) -{ -} - - -bool MacroContext::has(docstring const & name) const -{ - // check if it's a local macro - if (macros_.has(name)) - return true; - - // otherwise ask the buffer - return buf_.hasMacro(name, par_); -} - +///////////////////////////////////////////////////////////////////// +// +// MacroContext +// +///////////////////////////////////////////////////////////////////// -MacroData const & MacroContext::get(docstring const & name) const +MacroContext::MacroContext(Buffer const & buf, DocIterator const & pos) + : buf_(buf), pos_(pos) { - // check if it's a local macro - if (macros_.has(name)) - return macros_.get(name); - - // ask the buffer for its macros - return buf_.getMacro(name, par_); } -void MacroContext::insert(docstring const & name, MacroData const & data) +MacroData const * MacroContext::get(docstring const & name) const { - macros_.insert(name, data); + return buf_.getMacro(name, pos_); } - } // namespace lyx diff --git a/src/mathed/MacroTable.h b/src/mathed/MacroTable.h index 709fd3b61f..5fa88cee2b 100644 --- a/src/mathed/MacroTable.h +++ b/src/mathed/MacroTable.h @@ -13,7 +13,10 @@ #ifndef MATH_MACROTABLE_H #define MATH_MACROTABLE_H +#include "DocIterator.h" + #include "support/docstring.h" +#include "support/types.h" #include #include @@ -22,23 +25,32 @@ namespace lyx { class Buffer; class MathData; +class MathMacroTemplate; class Paragraph; + +enum MacroType { + MacroTypeNewcommand, + MacroTypeDef +}; + + /// class MacroData { public: - /// + /// Constructor to make STL containers happy MacroData(); + /// Create lazy MacroData which only queries the macro template when needed + MacroData(Buffer const & buf, DocIterator const & pos); + /// Create non-lazy MacroData which directly queries the macro template + MacroData(MathMacroTemplate const & macro); + /// - MacroData(docstring const & definition, - std::vector const & defaults, int nargs, int optionals, - docstring const & display, std::string const & requires); - /// - docstring const & definition() const { return definition_; } + docstring const & definition() const { updateData(); return definition_; } /// - docstring const & display() const { return display_; } + docstring const & display() const { updateData(); return display_; } /// arity including optional arguments (if there is any) - size_t numargs() const { return numargs_; } + size_t numargs() const { updateData(); return numargs_; } /// replace #1,#2,... by given MathAtom 0,1,.., _including_ the possible /// optional argument void expand(std::vector const & from, MathData & to) const; @@ -47,9 +59,9 @@ public: /// std::vector const & defaults() const; /// - std::string requires() const { return requires_; } + std::string const & requires() const { updateData(); return requires_; } /// - std::string & requires() { return requires_; } + std::string & requires() { updateData(); return requires_; } /// lock while being drawn to avoid recursions int lock() const { return ++lockCount_; } @@ -62,9 +74,19 @@ public: bool redefinition() const { return redefinition_; } /// void setRedefinition(bool redefined) { redefinition_ = redefined; } + + /// + MacroType type() const { return type_; } + /// + MacroType & type() { return type_; } + /// output as TeX macro, only works for lazy MacroData!!! + void write(odocstream & os, bool overwriteRedefinition) const; + /// bool operator==(MacroData const & x) const { + updateData(); + x.updateData(); return definition_ == x.definition_ && numargs_ == x.numargs_ && display_ == x.display_ @@ -77,24 +99,43 @@ public: private: /// - docstring definition_; + void queryData(MathMacroTemplate const & macro) const; /// - size_t numargs_; + void updateData() const; /// - docstring display_; + Buffer const * buffer_; + /// The position of the definition in the buffer. + /// There is no guarantee it stays valid if the buffer + /// changes. But it (normally) exists only until the + /// next Buffer::updateMacros call where new MacroData + /// objects are created for each macro definition. + /// In the worst case, it is invalidated and the MacroData + /// returns its defaults values and the user sees unfolded + /// macros. + mutable DocIterator pos_; /// - std::string requires_; + mutable bool queried_; /// - mutable int lockCount_; + mutable docstring definition_; + /// + mutable size_t numargs_; + /// + mutable docstring display_; + /// + mutable std::string requires_; + /// + mutable size_t optionals_; + /// + mutable std::vector defaults_; /// - bool redefinition_; + mutable int lockCount_; /// - size_t optionals_; + mutable bool redefinition_; /// - std::vector defaults_; + mutable MacroType type_; }; - + /// A lookup table of macro definitions. /** * This contains a table of "global" macros that are always accessible, @@ -109,10 +150,8 @@ public: void insert(docstring const & definition, std::string const &); /// Insert pre-digested macro definition void insert(docstring const & name, MacroData const & data); - /// Do we have a macro by that name? - bool has(docstring const & name) const; /// - MacroData const & get(docstring const & name) const; + MacroData const * get(docstring const & name) const; /// void dump(); @@ -131,25 +170,17 @@ public: **/ class MacroContext { public: - /// construct context for insets in par (not including the ones - /// defined in par itself) - MacroContext(Buffer const & buf, Paragraph const & par); + /// construct context for the insets at pos + MacroContext(Buffer const & buf, DocIterator const & pos); - /// Look for macro - bool has(docstring const & name) const; /// Lookup macro - MacroData const & get(docstring const & name) const; - - /// Insert pre-digested macro definition - void insert(docstring const & name, MacroData const & data); + MacroData const * get(docstring const & name) const; private: - /// context local macros - MacroTable macros_; /// Buffer const & buf_; /// - Paragraph const & par_; + DocIterator const & pos_; }; } // namespace lyx diff --git a/src/mathed/MathData.cpp b/src/mathed/MathData.cpp index ff0cff0340..1d40598dff 100644 --- a/src/mathed/MathData.cpp +++ b/src/mathed/MathData.cpp @@ -247,7 +247,8 @@ void MathData::metrics(MetricsInfo & mi, Dimension & dim) const return; } - const_cast(this)->updateMacros(mi); + Cursor & cur = mi.base.bv->cursor(); + const_cast(this)->updateMacros(&cur, mi.macrocontext); dim.asc = 0; dim.wid = 0; @@ -322,10 +323,8 @@ void MathData::drawT(TextPainter & pain, int x, int y) const } -void MathData::updateMacros(MetricsInfo & mi) +void MathData::updateMacros(Cursor * cur, MacroContext const & mc) { - Cursor & cur = mi.base.bv->cursor(); - // go over the array and look for macros for (size_t i = 0; i < size(); ++i) { MathMacro * macroInset = operator[](i).nucleus()->asMacro(); @@ -333,7 +332,7 @@ void MathData::updateMacros(MetricsInfo & mi) continue; // get macro - macroInset->updateMacro(mi); + macroInset->updateMacro(mc); size_t macroNumArgs = 0; size_t macroOptionals = 0; MacroData const * macro = macroInset->macro(); @@ -345,13 +344,14 @@ void MathData::updateMacros(MetricsInfo & mi) // store old and compute new display mode MathMacro::DisplayMode newDisplayMode; MathMacro::DisplayMode oldDisplayMode = macroInset->displayMode(); - newDisplayMode = macroInset->computeDisplayMode(mi); + newDisplayMode = macroInset->computeDisplayMode(); // arity changed or other reason to detach? if (oldDisplayMode == MathMacro::DISPLAY_NORMAL && (macroInset->arity() != macroNumArgs || macroInset->optionals() != macroOptionals || newDisplayMode == MathMacro::DISPLAY_UNFOLDED)) { + detachMacroParameters(cur, i); } @@ -362,9 +362,11 @@ void MathData::updateMacros(MetricsInfo & mi) if (newDisplayMode != MathMacro::DISPLAY_UNFOLDED && oldDisplayMode == MathMacro::DISPLAY_UNFOLDED) { // put cursor in front of macro - int macroSlice = cur.find(macroInset); - if (macroSlice != -1) - cur.cutOff(macroSlice - 1); + if (cur) { + int macroSlice = cur->find(macroInset); + if (macroSlice != -1) + cur->cutOff(macroSlice - 1); + } } // update the display mode @@ -389,9 +391,11 @@ void MathData::updateMacros(MetricsInfo & mi) attachMacroParameters(cur, i, macroNumArgs, macroOptionals, fromInitToNormalMode, interactive); - // FIXME: proper anchor handling, this removes the selection - cur.updateInsets(&cur.bottom().inset()); - cur.clearSelection(); + if (cur) { + // FIXME: proper anchor handling, this removes the selection + cur->updateInsets(&cur->bottom().inset()); + cur->clearSelection(); + } } // Give macro the chance to adapt to new situation. @@ -401,40 +405,42 @@ void MathData::updateMacros(MetricsInfo & mi) if (inset->asScriptInset()) inset = inset->asScriptInset()->nuc()[0].nucleus(); BOOST_ASSERT(inset->asMacro()); - inset->asMacro()->updateRepresentation(mi); + inset->asMacro()->updateRepresentation(cur); } } -void MathData::detachMacroParameters(Cursor & cur, const size_type macroPos) +void MathData::detachMacroParameters(Cursor * cur, const size_type macroPos) { MathMacro * macroInset = operator[](macroPos).nucleus()->asMacro(); // detach all arguments vector detachedArgs; if (macroPos + 1 == size()) - // strip arguments if we are at the MathData end - macroInset->detachArguments(detachedArgs, true); + // strip arguments if we are at the MathData end + macroInset->detachArguments(detachedArgs, true); else - macroInset->detachArguments(detachedArgs, false); + macroInset->detachArguments(detachedArgs, false); // find cursor slice - int curMacroSlice = cur.find(macroInset); + int curMacroSlice = -1; + if (cur) + curMacroSlice = cur->find(macroInset); idx_type curMacroIdx = -1; pos_type curMacroPos = -1; vector argSlices; if (curMacroSlice != -1) { - curMacroPos = cur[curMacroSlice].pos(); - curMacroIdx = cur[curMacroSlice].idx(); - cur.cutOff(curMacroSlice, argSlices); - cur.pop_back(); + curMacroPos = (*cur)[curMacroSlice].pos(); + curMacroIdx = (*cur)[curMacroSlice].idx(); + cur->cutOff(curMacroSlice, argSlices); + cur->pop_back(); } // only [] after the last non-empty argument can be dropped later size_t lastNonEmptyOptional = 0; for (size_t l = 0; l < detachedArgs.size() && l < macroInset->optionals(); ++l) { - if (!detachedArgs[l].empty()) - lastNonEmptyOptional = l; + if (!detachedArgs[l].empty()) + lastNonEmptyOptional = l; } // optional arguments to be put back? @@ -447,7 +453,7 @@ void MathData::detachMacroParameters(Cursor & cur, const size_type macroPos) // then we can drop empty optional parameters if (detachedArgs[j].empty() && canDropEmptyOptional) { if (curMacroIdx == j) - cur[curMacroSlice - 1].pos() = macroPos + 1; + (*cur)[curMacroSlice - 1].pos() = macroPos + 1; continue; } @@ -484,24 +490,26 @@ void MathData::detachMacroParameters(Cursor & cur, const size_type macroPos) // cursor in optional argument of macro? if (curMacroIdx == j) { if (brace) { - cur.append(0, curMacroPos); - cur[curMacroSlice - 1].pos() = macroPos + 2; + cur->append(0, curMacroPos); + (*cur)[curMacroSlice - 1].pos() = macroPos + 2; } else - cur[curMacroSlice - 1].pos() = macroPos + 2 + curMacroPos; - cur.append(argSlices); - } else if (cur[curMacroSlice - 1].pos() >= int(p)) + (*cur)[curMacroSlice - 1].pos() = macroPos + 2 + curMacroPos; + cur->append(argSlices); + } else if ((*cur)[curMacroSlice - 1].pos() >= int(p)) // cursor right of macro - cur[curMacroSlice - 1].pos() += optarg.size(); + (*cur)[curMacroSlice - 1].pos() += optarg.size(); } // put them back into the MathData for (; j < detachedArgs.size(); ++j, ++p) { MathData const & arg = detachedArgs[j]; - if (arg.size() == 1 && !arg[0]->asScriptInset()) // && arg[0]->asCharInset()) + if (arg.size() == 1 + && !arg[0]->asScriptInset() + && !(arg[0]->asMacro() && arg[0]->asMacro()->arity() > 0)) insert(p, arg[0]); else insert(p, MathAtom(new InsetMathBrace(arg))); - + // cursor in macro? if (curMacroSlice == -1) continue; @@ -509,24 +517,26 @@ void MathData::detachMacroParameters(Cursor & cur, const size_type macroPos) // cursor in j-th argument of macro? if (curMacroIdx == j) { if (operator[](p).nucleus()->asBraceInset()) { - cur[curMacroSlice - 1].pos() = p; - cur.append(0, curMacroPos); - cur.append(argSlices); + (*cur)[curMacroSlice - 1].pos() = p; + cur->append(0, curMacroPos); + cur->append(argSlices); } else { - cur[curMacroSlice - 1].pos() = p; // + macroPos; - cur.append(argSlices); + (*cur)[curMacroSlice - 1].pos() = p; // + macroPos; + cur->append(argSlices); } - } else if (cur[curMacroSlice - 1].pos() >= int(p)) - ++cur[curMacroSlice - 1].pos(); + } else if ((*cur)[curMacroSlice - 1].pos() >= int(p)) + ++(*cur)[curMacroSlice - 1].pos(); } - // FIXME: proper anchor handling, this removes the selection - cur.clearSelection(); - cur.updateInsets(&cur.bottom().inset()); + if (cur) { + // FIXME: proper anchor handling, this removes the selection + cur->clearSelection(); + cur->updateInsets(&cur->bottom().inset()); + } } -void MathData::attachMacroParameters(Cursor & cur, +void MathData::attachMacroParameters(Cursor * cur, const size_type macroPos, const size_type macroNumArgs, const int macroOptionals, const bool fromInitToNormalMode, const bool interactiveInit) @@ -540,10 +550,12 @@ void MathData::attachMacroParameters(Cursor & cur, MathAtom scriptToPutAround; // find cursor slice again of this MathData - int thisSlice = cur.find(*this); + int thisSlice = -1; + if (cur) + thisSlice = cur->find(*this); int thisPos = -1; if (thisSlice != -1) - thisPos = cur[thisSlice].pos(); + thisPos = (*cur)[thisSlice].pos(); // find arguments behind the macro if (!interactiveInit) { @@ -564,8 +576,8 @@ void MathData::attachMacroParameters(Cursor & cur, operator[](macroPos) = scriptToPutAround; // go into the script inset nucleus - if (thisPos == int(macroPos)) - cur.append(0, 0); + if (cur && thisPos == int(macroPos)) + cur->append(0, 0); // get pointer to "deep" copied macro inset InsetMathScript * scriptInset @@ -582,24 +594,24 @@ void MathData::attachMacroParameters(Cursor & cur, // fix cursor if right of p if (thisPos >= int(p)) - cur[thisSlice].pos() -= p - (macroPos + 1); + (*cur)[thisSlice].pos() -= p - (macroPos + 1); // was the macro inset just inserted interactively and was now folded // and the cursor is just behind? - if (cur[thisSlice].pos() == int(macroPos + 1) - && interactiveInit - && fromInitToNormalMode - && macroInset->arity() > 0 - && thisSlice + 1 == int(cur.depth())) { + if ((*cur)[thisSlice].pos() == int(macroPos + 1) + && interactiveInit + && fromInitToNormalMode + && macroInset->arity() > 0 + && thisSlice + 1 == int(cur->depth())) { // then enter it if the cursor was just behind - cur[thisSlice].pos() = macroPos; - cur.push_back(CursorSlice(*macroInset)); - macroInset->idxFirst(cur); + (*cur)[thisSlice].pos() = macroPos; + cur->push_back(CursorSlice(*macroInset)); + macroInset->idxFirst(*cur); } } -void MathData::collectOptionalParameters(Cursor & cur, +void MathData::collectOptionalParameters(Cursor * cur, const size_type numOptionalParams, vector & params, size_t & pos, const pos_type macroPos, const int thisPos, const int thisSlice) { @@ -608,7 +620,7 @@ void MathData::collectOptionalParameters(Cursor & cur, // is a [] block following which could be an optional parameter? if (operator[](pos)->getChar() != '[') break; - + // found possible optional argument, look for "]" size_t right = pos + 1; for (; right < size(); ++right) { @@ -636,29 +648,28 @@ void MathData::collectOptionalParameters(Cursor & cur, // place cursor in optional argument of macro if (thisSlice != -1 - && thisPos >= int(pos) && thisPos <= int(right)) { + && thisPos >= int(pos) && thisPos <= int(right)) { int paramPos = max(0, thisPos - int(pos) - 1); vector x; - cur.cutOff(thisSlice, x); - cur[thisSlice].pos() = macroPos; + cur->cutOff(thisSlice, x); + (*cur)[thisSlice].pos() = macroPos; if (brace) { paramPos = x[0].pos(); x.erase(x.begin()); } - cur.append(0, paramPos); - cur.append(x); + cur->append(0, paramPos); + cur->append(x); } pos = right + 1; } // fill up empty optional parameters - while (params.size() < numOptionalParams) { - params.push_back(MathData()); - } + while (params.size() < numOptionalParams) + params.push_back(MathData()); } -void MathData::collectParameters(Cursor & cur, +void MathData::collectParameters(Cursor * cur, const size_type numParams, vector & params, size_t & pos, MathAtom & scriptToPutAround, const pos_type macroPos, const int thisPos, const int thisSlice) @@ -670,9 +681,8 @@ void MathData::collectParameters(Cursor & cur, // fix cursor vector argSlices; int argPos = 0; - if (thisSlice != -1 && thisPos == int(pos)) { - cur.cutOff(thisSlice, argSlices); - } + if (thisSlice != -1 && thisPos == int(pos)) + cur->cutOff(thisSlice, argSlices); // which kind of parameter is it? In {}? With index x^n? InsetMathBrace const * brace = cell->asBraceInset(); @@ -701,7 +711,8 @@ void MathData::collectParameters(Cursor & cur, // this should only happen after loading, so make cursor handling simple if (thisPos >= int(macroPos) && thisPos <= int(macroPos + numParams)) { argSlices.clear(); - cur.append(0, 0); + if (cur) + cur->append(0, 0); } } else { // the simplest case: plain inset @@ -712,9 +723,9 @@ void MathData::collectParameters(Cursor & cur, // put cursor in argument again if (thisSlice != - 1 && thisPos == int(pos)) { - cur.append(params.size() - 1, argPos); - cur.append(argSlices); - cur[thisSlice].pos() = macroPos; + cur->append(params.size() - 1, argPos); + cur->append(argSlices); + (*cur)[thisSlice].pos() = macroPos; } ++pos; diff --git a/src/mathed/MathData.h b/src/mathed/MathData.h index 24f08c545b..1ddf623685 100644 --- a/src/mathed/MathData.h +++ b/src/mathed/MathData.h @@ -27,8 +27,10 @@ namespace lyx { class BufferView; class Cursor; +class DocIterator; class LaTeXFeatures; class ReplaceData; +class MacroContext; class MathMacro; class MetricsInfo; class PainterInfo; @@ -154,6 +156,10 @@ public: /// void swap(MathData & ar) { base_type::swap(ar); } + /// attach/detach arguments to macros, updating the cur to + /// stay visually at the same position (cur==0 is allowed) + void updateMacros(Cursor * cur, MacroContext const & mc); + protected: /// cached values for super/subscript placement mutable int minasc_; @@ -166,20 +172,18 @@ private: /// is this an exact match at this position? bool find1(MathData const & ar, size_type pos) const; - /// attach/detach arguments to macros - void updateMacros(MetricsInfo & mi); /// - void detachMacroParameters(Cursor & cur, const size_type macroPos); + void detachMacroParameters(Cursor * cur, const size_type macroPos); /// - void attachMacroParameters(Cursor & cur, const size_type macroPos, + void attachMacroParameters(Cursor * cur, const size_type macroPos, const size_type macroNumArgs, const int macroOptionals, const bool fromInitToNormalMode, const bool interactiveInit); /// - void collectOptionalParameters(Cursor & cur, + void collectOptionalParameters(Cursor * cur, const size_type numOptionalParams, std::vector & params, size_t & pos, const pos_type macroPos, const int thisPos, const int thisSlice); /// - void collectParameters(Cursor & cur, + void collectParameters(Cursor * cur, const size_type numParams, std::vector & params, size_t & pos, MathAtom & scriptToPutAround, const pos_type macroPos, const int thisPos, const int thisSlice); diff --git a/src/mathed/MathMacro.cpp b/src/mathed/MathMacro.cpp index 27904af557..5e8b478459 100644 --- a/src/mathed/MathMacro.cpp +++ b/src/mathed/MathMacro.cpp @@ -213,11 +213,11 @@ int MathMacro::kerning() const { } -void MathMacro::updateMacro(MetricsInfo & mi) +void MathMacro::updateMacro(MacroContext const & mc) { - if (validName() && mi.macrocontext.has(name())) { - macro_ = &mi.macrocontext.get(name()); - if (macroBackup_ != *macro_) { + if (validName()) { + macro_ = mc.get(name()); + if (macro_ && macroBackup_ != *macro_) { macroBackup_ = *macro_; needsUpdate_ = true; } @@ -227,48 +227,60 @@ void MathMacro::updateMacro(MetricsInfo & mi) } -void MathMacro::updateRepresentation(MetricsInfo & mi) +void MathMacro::updateRepresentation(Cursor const * bvCur) { // index of child where the cursor is (or -1 if none is edited) - int curIdx = cursorIdx(mi.base.bv->cursor()); + int curIdx = -1; + if (bvCur) + curIdx = cursorIdx(*bvCur); previousCurIdx_ = curIdx; // known macro? - if (macro_) { - requires_ = macro_->requires(); - - if (displayMode_ == DISPLAY_NORMAL) { - // set edit mode to draw box around if needed - bool prevEditing = editing_; - editing_ = editMode(mi.base.bv->cursor()); - - // editMode changed and we have to switch default value and hole of optional? - if (optionals_ > 0 && nargs() > 0 && - prevEditing != editing_) - needsUpdate_ = true; - - // macro changed? - if (needsUpdate_) { - needsUpdate_ = false; - - // get default values of macro - vector const & defaults = macro_->defaults(); - - // create MathMacroArgumentValue objects pointing to the cells of the macro - vector values(nargs()); - for (size_t i = 0; i < nargs(); ++i) { - if (!cell(i).empty() || i >= defaults.size() || - defaults[i].empty() || curIdx == (int)i) - values[i].insert(0, MathAtom(new ArgumentProxy(*this, i))); - else - values[i].insert(0, MathAtom(new ArgumentProxy(*this, i, defaults[i]))); - } - - // expanding macro with the values - macro_->expand(values, expanded_.cell(0)); - } + if (macro_ == 0) + return; + + // update requires + requires_ = macro_->requires(); + + // non-normal mode? We are done! + if (displayMode_ != DISPLAY_NORMAL) + return; + + // set edit mode to draw box around if needed + bool prevEditing = editing_; + editing_ = false; + if (bvCur) + editing_ = editMode(*bvCur); + + // editMode changed and we have to switch default value and hole of optional? + if (optionals_ > 0 && nargs() > 0 && + prevEditing != editing_) + needsUpdate_ = true; + + // macro changed? + if (needsUpdate_) { + needsUpdate_ = false; + + // get default values of macro + vector const & defaults = macro_->defaults(); + + // create MathMacroArgumentValue objects pointing to the cells of the macro + vector values(nargs()); + for (size_t i = 0; i < nargs(); ++i) { + ArgumentProxy * proxy; + if (!cell(i).empty() + || i >= defaults.size() + || defaults[i].empty() + || curIdx == (int)i) + proxy = new ArgumentProxy(*this, i); + else + proxy = new ArgumentProxy(*this, i, defaults[i]); + values[i].insert(0, MathAtom(proxy)); } - } + + // expanding macro with the values + macro_->expand(values, expanded_.cell(0)); + } } @@ -359,7 +371,7 @@ void MathMacro::setDisplayMode(MathMacro::DisplayMode mode) } -MathMacro::DisplayMode MathMacro::computeDisplayMode(MetricsInfo const &) const +MathMacro::DisplayMode MathMacro::computeDisplayMode() const { if (nextFoldMode_ == true && macro_ && !macro_->locked()) return DISPLAY_NORMAL; @@ -531,54 +543,63 @@ bool MathMacro::folded() const void MathMacro::write(WriteStream & os) const { - if (displayMode_ == DISPLAY_NORMAL) { - BOOST_ASSERT(macro_); - - os << "\\" << name(); - bool first = true; - idx_type i = 0; - - // Use macroBackup_ instead of macro_ here, because - // this is outside the metrics/draw calls, hence the macro_ - // variable can point to a MacroData which was freed already. - vector const & defaults = macroBackup_.defaults(); - - // Optional argument - if (os.latex()) { - if (i < optionals_) { - // the first real optional, the others are non-optional in latex - if (!cell(i).empty()) { - first = false; - os << "[" << cell(0) << "]"; - } - - ++i; - } - } else { - // In lyx mode print all in any case - for (; i < cells_.size() && i < optionals_; ++i) { + // non-normal mode + if (displayMode_ != DISPLAY_NORMAL) { + os << "\\" << name() << " "; + os.pendingSpace(true); + return; + } + + // normal mode + BOOST_ASSERT(macro_); + + os << "\\" << name(); + bool first = true; + idx_type i = 0; + + // Use macroBackup_ instead of macro_ here, because + // this is outside the metrics/draw calls, hence the macro_ + // variable can point to a MacroData which was freed already. + vector const & defaults = macroBackup_.defaults(); + + // Optional argument + if (os.latex()) { + // Print first optional in LaTeX semantics + if (i < optionals_) { + // the first real optional, the others are non-optional in latex + if (!cell(i).empty()) { first = false; - os << "[" << cell(i) << "]"; + os << "[" << cell(0) << "]"; } + + ++i; } - - for (; i < cells_.size(); ++i) { - if (cell(i).empty() && i < optionals_) { - os << "{" << defaults[i] << "}"; - } else if (cell(i).size() == 1 && cell(i)[0].nucleus()->asCharInset()) { - if (first) - os << " "; - os << cell(i); - } else - os << "{" << cell(i) << "}"; + } else { + // In lyx mode print all in any case + for (; i < cells_.size() && i < optionals_; ++i) { first = false; + os << "[" << cell(i) << "]"; } - if (first) - os.pendingSpace(true); - } else { - os << "\\" << name() << " "; - os.pendingSpace(true); } + + // Print remaining macros + // (also the further optional parameters in LaTeX mode!) + for (; i < cells_.size(); ++i) { + if (cell(i).empty() && i < optionals_) + os << "{" << defaults[i] << "}"; + else if (cell(i).size() == 1 + && cell(i)[0].nucleus()->asCharInset()) { + if (first) + os << " "; + os << cell(i); + } else + os << "{" << cell(i) << "}"; + first = false; + } + + // add space if there was no argument + if (first) + os.pendingSpace(true); } diff --git a/src/mathed/MathMacro.h b/src/mathed/MathMacro.h index 0209ac6a36..bab83a4493 100644 --- a/src/mathed/MathMacro.h +++ b/src/mathed/MathMacro.h @@ -127,11 +127,11 @@ protected: /// update the display mode (should only be called after detaching arguments) void setDisplayMode(DisplayMode mode); /// compute the next display mode - DisplayMode computeDisplayMode(MetricsInfo const & mi) const; + DisplayMode computeDisplayMode() const; /// update macro definition - void updateMacro(MetricsInfo & mi); + void updateMacro(MacroContext const & mc); /// check if macro definition changed, argument changed etc. and adapt - void updateRepresentation(MetricsInfo & mi); + void updateRepresentation(Cursor const * bvCur); /// empty macro, put arguments into args, possibly strip arity-attachedArgsNum_ empty ones. /// Includes the optional arguments. void detachArguments(std::vector & args, bool strip); diff --git a/src/mathed/MathMacroTemplate.cpp b/src/mathed/MathMacroTemplate.cpp index fb0b15d659..77cf8350dd 100644 --- a/src/mathed/MathMacroTemplate.cpp +++ b/src/mathed/MathMacroTemplate.cpp @@ -90,18 +90,20 @@ void InsetMathWrapper::draw(PainterInfo & pi, int x, int y) const MathMacroTemplate::MathMacroTemplate() - : InsetMathNest(3), numargs_(0), optionals_(0), type_(from_ascii("newcommand")) + : InsetMathNest(3), numargs_(0), optionals_(0), + type_(MacroTypeNewcommand) { initMath(); } MathMacroTemplate::MathMacroTemplate(docstring const & name, int numargs, - int optionals, docstring const & type, + int optionals, MacroType type, vector const & optionalValues, MathData const & def, MathData const & display) -: InsetMathNest(optionals + 3), numargs_(numargs), - optionals_(optionals), optionalValues_(optionalValues), type_(type) + : InsetMathNest(optionals + 3), numargs_(numargs), + optionals_(optionals), optionalValues_(optionalValues), + type_(type) { initMath(); @@ -150,6 +152,12 @@ docstring MathMacroTemplate::name() const } +void MathMacroTemplate::updateToContext(MacroContext const & mc) const +{ + redefinition_ = mc.get(name()) != 0; +} + + void MathMacroTemplate::metrics(MetricsInfo & mi, Dimension & dim) const { FontSetChanger dummy1(mi.base, from_ascii("mathnormal")); @@ -157,13 +165,11 @@ void MathMacroTemplate::metrics(MetricsInfo & mi, Dimension & dim) const // valid macro? MacroData const * macro = 0; - if (validName() && mi.macrocontext.has(name())) { - macro = &mi.macrocontext.get(name()); - if (type_ == from_ascii("newcommand") || type_ == from_ascii("renewcommand")) { - // use the MacroData::redefinition_ information instead of MacroContext::has - // because the macro is known here already anyway to detect recursive definitions - type_ = macro->redefinition() ? from_ascii("renewcommand") : from_ascii("newcommand"); - } + if (validName()) { + macro = mi.macrocontext.get(name()); + + // updateToContext() - avoids another lookup + redefinition_ = macro != 0; } // create label "{#1}{#2}:=" @@ -220,7 +226,7 @@ void MathMacroTemplate::metrics(MetricsInfo & mi, Dimension & dim) const int real_des = dim.des + dim0.ascent() / 2; dim.asc = max(real_asc, real_des) + dim0.ascent() / 2 + 5; dim.des = max(real_asc, real_des) - dim0.ascent() / 2 + 5; - + setDimCache(mi, dim); } @@ -598,7 +604,8 @@ bool MathMacroTemplate::getStatus(Cursor & /*cur*/, FuncRequest const & cmd, int num = numargs_ + 1; if (arg.size() != 0) num = convert(arg); - bool on = (num >= optionals_ && numargs_ < 9 && num <= numargs_ + 1); + bool on = (num >= optionals_ + && numargs_ < 9 && num <= numargs_ + 1); flag.enabled(on); break; } @@ -616,11 +623,14 @@ bool MathMacroTemplate::getStatus(Cursor & /*cur*/, FuncRequest const & cmd, } case LFUN_MATH_MACRO_MAKE_OPTIONAL: - flag.enabled(numargs_ > 0 && optionals_ < numargs_ && type_ != from_ascii("def")); + flag.enabled(numargs_ > 0 + && optionals_ < numargs_ + && type_ != MacroTypeDef); break; case LFUN_MATH_MACRO_MAKE_NONOPTIONAL: - flag.enabled(optionals_ > 0 && type_ != from_ascii("def")); + flag.enabled(optionals_ > 0 + && type_ != MacroTypeDef); break; case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM: @@ -632,7 +642,8 @@ bool MathMacroTemplate::getStatus(Cursor & /*cur*/, FuncRequest const & cmd, break; case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM: - flag.enabled(numargs_ == 0 && type_ != from_ascii("def")); + flag.enabled(numargs_ == 0 + && type_ != MacroTypeDef); break; default: @@ -668,13 +679,23 @@ void MathMacroTemplate::write(Buffer const &, ostream & os) const void MathMacroTemplate::write(WriteStream & os) const { - if (type_ == "def") { + write(os, false); +} + + +void MathMacroTemplate::write(WriteStream & os, bool overwriteRedefinition) const +{ + if (type_ == MacroTypeDef) { os << "\\def\\" << name().c_str(); for (int i = 1; i <= numargs_; ++i) os << '#' << i; } else { // newcommand or renewcommand - os << "\\" << type_.c_str() << "{\\" << name().c_str() << '}'; + if (redefinition_ && !overwriteRedefinition) + os << "\\renewcommand"; + else + os << "\\newcommand"; + os << "{\\" << name().c_str() << '}'; if (numargs_ > 0) os << '[' << numargs_ << ']'; @@ -754,14 +775,61 @@ bool MathMacroTemplate::validMacro() const } -MacroData MathMacroTemplate::asMacroData() const +bool MathMacroTemplate::fixNameAndCheckIfValid() +{ + // check all the characters/insets in the name cell + size_t i = 0; + MathData & data = cell(0); + while (i < data.size()) { + InsetMathChar const * cinset = data[i]->asCharInset(); + if (cinset) { + // valid character in [a-zA-Z]? + char_type c = cinset->getChar(); + if ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z')) { + ++i; + continue; + } + } + + // throw cell away + data.erase(i); + } + + // now it should be valid if anything in the name survived + return data.size() > 0; +} + + +void MathMacroTemplate::getDefaults(vector & defaults) const { - vector defaults(numargs_); + defaults.resize(numargs_); for (int i = 0; i < optionals_; ++i) - defaults[i] = asString(cell(optIdx(i))); - return MacroData(asString(cell(defIdx())), defaults, - numargs_, optionals_, asString(cell(displayIdx())), string()); + defaults[i] = asString(cell(optIdx(i))); +} + + +docstring MathMacroTemplate::definition() const +{ + return asString(cell(defIdx())); +} + + +docstring MathMacroTemplate::displayDefinition() const +{ + return asString(cell(displayIdx())); } +size_t MathMacroTemplate::numArgs() const +{ + return numargs_; +} + + +size_t MathMacroTemplate::numOptionals() const +{ + return optionals_; +} + } // namespace lyx diff --git a/src/mathed/MathMacroTemplate.h b/src/mathed/MathMacroTemplate.h index b90c8a96cb..c14bfcb3eb 100644 --- a/src/mathed/MathMacroTemplate.h +++ b/src/mathed/MathMacroTemplate.h @@ -29,7 +29,7 @@ public: MathMacroTemplate(); /// MathMacroTemplate(docstring const & name, int nargs, int optional, - docstring const & type, + MacroType type, std::vector const & optionalValues = std::vector(), MathData const & def = MathData(), MathData const & display = MathData()); @@ -43,18 +43,43 @@ public: void write(Buffer const &, std::ostream & os) const; /// void write(WriteStream & os) const; + /// Output LaTeX code, but assume that the macro is not definied yet + /// if overwriteRedefinition is true + void write(WriteStream & os, bool overwriteRedefinition) const; /// int plaintext(Buffer const &, odocstream &, OutputParams const &) const; + /// + bool noFontChange() const { return true; } /// docstring name() const; + /// + void getDefaults(std::vector & defaults) const; + /// + docstring definition() const; + /// + docstring displayDefinition() const; + /// + size_t numArgs() const; + /// + size_t numOptionals() const; + /// + bool redefinition() const { return redefinition_; } + /// + MacroType type() const { return type_; } + /// check name and possible other formal properties bool validMacro() const; /// bool validName() const; - /// - MacroData asMacroData() const; + /// Remove everything from the name which makes it invalid + /// and return true iff it is valid. + bool fixNameAndCheckIfValid(); + + /// decide whether its a redefinition + void updateToContext(MacroContext const & mc) const; + /// void draw(PainterInfo & pi, int x, int y) const; /// @@ -100,10 +125,14 @@ private: mutable int numargs_; /// int optionals_; - /// keeps the old optional default value when an optional argument is disabled + /// keeps the old optional default value when an + /// optional argument is disabled std::vector optionalValues_; - /// newcommand or renewcommand or def - mutable docstring type_; + + /// (re)newcommand or def + mutable MacroType type_; + /// defined before already? + mutable bool redefinition_; }; diff --git a/src/mathed/MathParser.cpp b/src/mathed/MathParser.cpp index d2c4d762ed..ebeebd3652 100644 --- a/src/mathed/MathParser.cpp +++ b/src/mathed/MathParser.cpp @@ -896,12 +896,14 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, t.cs() == "newcommand" || t.cs() == "renewcommand") { - docstring const type = t.cs(); + MacroType type = MacroTypeNewcommand; + if (t.cs() == "def") + type = MacroTypeDef; docstring name; int nargs = 0; int optionals = 0; vector optionalValues; - if (t.cs() == "def") { + if (type == MacroTypeDef) { // get name name = getToken().cs(); @@ -914,7 +916,7 @@ void Parser::parse1(InsetMathGrid & grid, unsigned flags, nargs /= 2; //lyxerr << "read \\def parameter list '" << pars << "'" << endl; - } else { // t.cs() == "newcommand" || t.cs() == "renewcommand" + } else { if (getToken().cat() != catBegin) { error("'{' in \\newcommand expected (1) "); return; -- 2.39.2