]> git.lyx.org Git - lyx.git/blobdiff - src/mathed/MathData.cpp
Fix a crash with uninitialized buffer member of MathData
[lyx.git] / src / mathed / MathData.cpp
index bcc9895e7203deb5abd80ea00c365a68863c218a..9bfd558fe2856d8e1475648b8c9b1963255cf25a 100644 (file)
@@ -17,7 +17,7 @@
 #include "InsetMathFont.h"
 #include "InsetMathScript.h"
 #include "MacroTable.h"
-#include "MathMacro.h"
+#include "InsetMathMacro.h"
 #include "MathStream.h"
 #include "MathSupport.h"
 #include "MetricsInfo.h"
 
 #include "mathed/InsetMathUnknown.h"
 
-#include "support/debug.h"
-#include "support/docstream.h"
-
 #include "frontends/FontMetrics.h"
 #include "frontends/Painter.h"
 
+#include "support/debug.h"
+#include "support/docstream.h"
 #include "support/gettext.h"
 #include "support/lassert.h"
 #include "support/lyxalgo.h"
@@ -49,10 +48,18 @@ namespace lyx {
 
 MathData::MathData(Buffer * buf, const_iterator from, const_iterator to)
        : base_type(from, to), minasc_(0), mindes_(0), slevel_(0),
-         sshift_(0), kerning_(0), buffer_(buf)
+         sshift_(0), buffer_(buf)
 {}
 
 
+void MathData::setBuffer(Buffer & b)
+{
+       buffer_ = &b;
+       for (MathAtom & at : *this)
+               at.nucleus()->setBuffer(b);
+}
+
+
 MathAtom & MathData::operator[](pos_type pos)
 {
        LBUFERR(pos < size());
@@ -213,8 +220,32 @@ bool MathData::contains(MathData const & ar) const
 }
 
 
-void MathData::touch() const
+bool MathData::addToMathRow(MathRow & mrow, MetricsInfo & mi) const
 {
+       bool has_contents = false;
+       BufferView * bv = mi.base.bv;
+       MathData * ar = const_cast<MathData*>(this);
+       ar->updateMacros(&bv->cursor(), mi.macrocontext,
+                        InternalUpdate, mi.base.macro_nesting);
+
+
+       // FIXME: for completion, try to insert the relevant data in the
+       // mathrow (like is done for text rows). We could add a pair of
+       // InsetMathColor inset, but these come with extra spacing of
+       // their own.
+       DocIterator const & inlineCompletionPos = bv->inlineCompletionPos();
+       bool const has_completion = inlineCompletionPos.inMathed()
+               && &inlineCompletionPos.cell() == this;
+       size_t const compl_pos = has_completion ? inlineCompletionPos.pos() : 0;
+
+       for (size_t i = 0 ; i < size() ; ++i) {
+               has_contents |= (*this)[i]->addToMathRow(mrow, mi);
+               if (i + 1 == compl_pos) {
+                       mrow.back().compl_text = bv->inlineCompletion();
+                       mrow.back().compl_unique_to = bv->inlineCompletionUniqueChars();
+               }
+       }
+       return has_contents;
 }
 
 
@@ -236,125 +267,90 @@ bool isInside(DocIterator const & it, MathData const & ar,
 #endif
 
 
-void MathData::metrics(MetricsInfo & mi, Dimension & dim) const
+void MathData::metrics(MetricsInfo & mi, Dimension & dim, bool tight) const
 {
        frontend::FontMetrics const & fm = theFontMetrics(mi.base.font);
-       dim = fm.dimension('I');
-       int xascent = fm.dimension('x').ascent();
-       if (xascent >= dim.asc)
-               xascent = (2 * dim.asc) / 3;
+       BufferView * bv = mi.base.bv;
+       int const Iascent = fm.dimension('I').ascent();
+       int xascent = fm.xHeight();
+       if (xascent >= Iascent)
+               xascent = (2 * Iascent) / 3;
        minasc_ = xascent;
        mindes_ = (3 * xascent) / 4;
        slevel_ = (4 * xascent) / 5;
        sshift_ = xascent / 4;
-       kerning_ = 0;
 
-       if (empty()) {
-               // Cache the dimension.
-               mi.base.bv->coordCache().arrays().add(this, dim);
-               return;
+       MathRow mrow(mi, this);
+       mrow.metrics(mi, dim);
+
+       // Set a minimal ascent/descent for the cell
+       if (tight)
+               // FIXME: this is the minimal ascent seen empirically, check
+               // what the TeXbook says.
+               dim.asc = max(dim.asc, fm.xHeight());
+       else {
+               dim.asc = max(dim.asc, fm.maxAscent());
+               dim.des = max(dim.des, fm.maxDescent());
        }
 
-       Cursor & cur = mi.base.bv->cursor();
-       const_cast<MathData*>(this)->updateMacros(&cur, mi.macrocontext, InternalUpdate);
-
-       DocIterator const & inlineCompletionPos = mi.base.bv->inlineCompletionPos();
-       MathData const * inlineCompletionData = 0;
-       if (inlineCompletionPos.inMathed())
-               inlineCompletionData = &inlineCompletionPos.cell();
+       // This is one of the the few points where the drawing font is known,
+       // so that we can set the caret vertical dimensions.
+       mrow.caret_ascent = min(dim.asc, fm.maxAscent());
+       mrow.caret_descent = min(dim.des, fm.maxDescent());
+       /// do the same for math cells linearized in the row
+       MathRow caret_row = MathRow(mrow.caret_ascent, mrow.caret_descent);
+       for (auto const & e : mrow)
+               if (e.type == MathRow::BEGIN && e.ar)
+                       bv->setMathRow(e.ar, caret_row);
 
-       dim.asc = 0;
-       dim.wid = 0;
-       Dimension d;
-       CoordCacheBase<Inset> & coords = mi.base.bv->coordCache().insets();
-       for (pos_type i = 0, n = size(); i != n; ++i) {
-               MathAtom const & at = operator[](i);
-               at->metrics(mi, d);
-               coords.add(at.nucleus(), d);
-               dim += d;
-               if (i == n - 1)
-                       kerning_ = at->kerning(mi.base.bv);
+       // Cache row and dimension.
+       bv->setMathRow(this, mrow);
+       bv->coordCache().arrays().add(this, dim);
+}
 
-               // HACK to draw completion suggestion inline
-               if (inlineCompletionData != this
-                   || size_t(inlineCompletionPos.pos()) != i + 1)
-                       continue;
 
-               docstring const & completion = mi.base.bv->inlineCompletion();
-               if (completion.length() == 0)
-                       continue;
+void MathData::drawSelection(PainterInfo & pi, int const x, int const y) const
+{
+       BufferView const * bv = pi.base.bv;
+       Cursor const & cur = bv->cursor();
+       InsetMath const * inset = cur.inset().asInsetMath();
+       if (!cur.selection() || !inset || inset->nargs() == 0)
+               return;
 
-               FontInfo font = mi.base.font;
-               augmentFont(font, from_ascii("mathnormal"));
-               dim.wid += mathed_string_width(font, completion);
+       CursorSlice const s1 = cur.selBegin();
+       CursorSlice const s2 = cur.selEnd();
+       MathData const & c1 = inset->cell(s1.idx());
+
+       if (s1.idx() == s2.idx() && &c1 == this) {
+               // selection indide cell
+               Dimension const dim = bv->coordCache().getArrays().dim(&c1);
+               int const beg = c1.pos2x(bv, s1.pos());
+               int const end = c1.pos2x(bv, s2.pos());
+               pi.pain.fillRectangle(x + beg, y - dim.ascent(),
+                                     end - beg, dim.height(), Color_selection);
+       } else {
+               for (idx_type i = 0; i < inset->nargs(); ++i) {
+                       MathData const & c = inset->cell(i);
+                       if (&c == this && inset->idxBetween(i, s1.idx(), s2.idx())) {
+                               // The whole cell is selected
+                               Dimension const dim = bv->coordCache().getArrays().dim(&c);
+                               pi.pain.fillRectangle(x, y - dim.ascent(),
+                                                     dim.width(), dim.height(),
+                                                     Color_selection);
+                       }
+               }
        }
-       // Cache the dimension.
-       mi.base.bv->coordCache().arrays().add(this, dim);
 }
 
 
-void MathData::draw(PainterInfo & pi, int x, int y) const
+void MathData::draw(PainterInfo & pi, int const x, int const y) const
 {
        //lyxerr << "MathData::draw: x: " << x << " y: " << y << endl;
-       BufferView & bv  = *pi.base.bv;
-       setXY(bv, x, y);
-
-       Dimension const & dim = bv.coordCache().getArrays().dim(this);
-
-       if (empty()) {
-               pi.pain.rectangle(x, y - dim.ascent(), dim.width(), dim.height(), Color_mathline);
-               return;
-       }
-
-       // don't draw outside the workarea
-       if (y + dim.descent() <= 0
-               || y - dim.ascent() >= bv.workHeight()
-               || x + dim.width() <= 0
-               || x >= bv. workWidth())
-               return;
+       setXY(*pi.base.bv, x, y);
 
-       DocIterator const & inlineCompletionPos = bv.inlineCompletionPos();
-       MathData const * inlineCompletionData = 0;
-       if (inlineCompletionPos.inMathed())
-               inlineCompletionData = &inlineCompletionPos.cell();
-
-       CoordCacheBase<Inset> & coords = pi.base.bv->coordCache().insets();
-       for (size_t i = 0, n = size(); i != n; ++i) {
-               MathAtom const & at = operator[](i);
-               coords.add(at.nucleus(), x, y);
-               at->drawSelection(pi, x, y);
-               at->draw(pi, x, y);
-               x += coords.dim(at.nucleus()).wid;
-
-               // Is the inline completion here?
-               if (inlineCompletionData != this
-                   || size_t(inlineCompletionPos.pos()) != i + 1)
-                       continue;
-               docstring const & completion = bv.inlineCompletion();
-               if (completion.length() == 0)
-                       continue;
-               FontInfo f = pi.base.font;
-               augmentFont(f, from_ascii("mathnormal"));
-
-               // draw the unique and the non-unique completion part
-               // Note: this is not time-critical as it is
-               // only done once per screen.
-               size_t uniqueTo = bv.inlineCompletionUniqueChars();
-               docstring s1 = completion.substr(0, uniqueTo);
-               docstring s2 = completion.substr(uniqueTo);
-
-               if (!s1.empty()) {
-                       f.setColor(Color_inlinecompletion);
-                       pi.pain.text(x, y, s1, f);
-                       x += mathed_string_width(f, s1);
-               }
-
-               if (!s2.empty()) {
-                       f.setColor(Color_nonunique_inlinecompletion);
-                       pi.pain.text(x, y, s2, f);
-                       x += mathed_string_width(f, s2);
-               }
-       }
+       drawSelection(pi, x, y);
+       MathRow const & mrow = pi.base.bv->mathRow(this);
+       mrow.draw(pi, x, y);
 }
 
 
@@ -384,6 +380,12 @@ void MathData::drawT(TextPainter & pain, int x, int y) const
 }
 
 
+int MathData::kerning(BufferView const * bv) const
+{
+       return  bv->mathRow(this).kerning(bv);
+}
+
+
 void MathData::updateBuffer(ParIterator const & it, UpdateType utype)
 {
        // pass down
@@ -395,23 +397,23 @@ void MathData::updateBuffer(ParIterator const & it, UpdateType utype)
 
 
 void MathData::updateMacros(Cursor * cur, MacroContext const & mc,
-               UpdateType utype)
+               UpdateType utype, int nesting)
 {
        // If we are editing a macro, we cannot update it immediately,
        // otherwise wrong undo steps will be recorded (bug 6208).
        InsetMath const * inmath = cur ? cur->inset().asInsetMath() : 0;
-       MathMacro const * inmacro = inmath ? inmath->asMacro() : 0;
+       InsetMathMacro const * inmacro = inmath ? inmath->asMacro() : 0;
        docstring const edited_name = inmacro ? inmacro->name() : docstring();
 
        // go over the array and look for macros
        for (size_t i = 0; i < size(); ++i) {
-               MathMacro * macroInset = operator[](i).nucleus()->asMacro();
+               InsetMathMacro * macroInset = operator[](i).nucleus()->asMacro();
                if (!macroInset || macroInset->macroName().empty()
                                || macroInset->macroName()[0] == '^'
                                || macroInset->macroName()[0] == '_'
                                || (macroInset->name() == edited_name
                                    && macroInset->displayMode() ==
-                                               MathMacro::DISPLAY_UNFOLDED))
+                                               InsetMathMacro::DISPLAY_UNFOLDED))
                        continue;
 
                // get macro
@@ -425,28 +427,23 @@ void MathData::updateMacros(Cursor * cur, MacroContext const & mc,
                }
 
                // store old and compute new display mode
-               MathMacro::DisplayMode newDisplayMode;
-               MathMacro::DisplayMode oldDisplayMode = macroInset->displayMode();
+               InsetMathMacro::DisplayMode newDisplayMode;
+               InsetMathMacro::DisplayMode oldDisplayMode = macroInset->displayMode();
                newDisplayMode = macroInset->computeDisplayMode();
 
                // arity changed or other reason to detach?
-               if (oldDisplayMode == MathMacro::DISPLAY_NORMAL
+               if (oldDisplayMode == InsetMathMacro::DISPLAY_NORMAL
                    && (macroInset->arity() != macroNumArgs
                        || macroInset->optionals() != macroOptionals
-                       || newDisplayMode == MathMacro::DISPLAY_UNFOLDED)) {
-
+                       || newDisplayMode == InsetMathMacro::DISPLAY_UNFOLDED))
                        detachMacroParameters(cur, i);
-                       // FIXME: proper anchor handling, this removes the selection
-                       if (cur)
-                               cur->clearSelection();
-               }
 
                // the macro could have been copied while resizing this
                macroInset = operator[](i).nucleus()->asMacro();
 
                // Cursor in \label?
-               if (newDisplayMode != MathMacro::DISPLAY_UNFOLDED
-                   && oldDisplayMode == MathMacro::DISPLAY_UNFOLDED) {
+               if (newDisplayMode != InsetMathMacro::DISPLAY_UNFOLDED
+                   && oldDisplayMode == InsetMathMacro::DISPLAY_UNFOLDED) {
                        // put cursor in front of macro
                        if (cur) {
                                int macroSlice = cur->find(macroInset);
@@ -460,29 +457,26 @@ void MathData::updateMacros(Cursor * cur, MacroContext const & mc,
                macroInset->setDisplayMode(newDisplayMode);
 
                // arity changed?
-               if (newDisplayMode == MathMacro::DISPLAY_NORMAL
+               if (newDisplayMode == InsetMathMacro::DISPLAY_NORMAL
                    && (macroInset->arity() != macroNumArgs
                        || macroInset->optionals() != macroOptionals)) {
                        // is it a virgin macro which was never attached to parameters?
                        bool fromInitToNormalMode
-                       = (oldDisplayMode == MathMacro::DISPLAY_INIT
-                          || oldDisplayMode == MathMacro::DISPLAY_INTERACTIVE_INIT)
-                         && newDisplayMode == MathMacro::DISPLAY_NORMAL;
+                       = (oldDisplayMode == InsetMathMacro::DISPLAY_INIT
+                          || oldDisplayMode == InsetMathMacro::DISPLAY_INTERACTIVE_INIT)
+                         && newDisplayMode == InsetMathMacro::DISPLAY_NORMAL;
 
                        // if the macro was entered interactively (i.e. not by paste or during
                        // loading), it should not be greedy, but the cursor should
                        // automatically jump into the macro when behind
-                       bool interactive = (oldDisplayMode == MathMacro::DISPLAY_INTERACTIVE_INIT);
+                       bool interactive = (oldDisplayMode == InsetMathMacro::DISPLAY_INTERACTIVE_INIT);
 
                        // attach parameters
                        attachMacroParameters(cur, i, macroNumArgs, macroOptionals,
                                fromInitToNormalMode, interactive, appetite);
 
-                       if (cur) {
-                               // FIXME: proper anchor handling, this removes the selection
+                       if (cur)
                                cur->updateInsets(&cur->bottom().inset());
-                               cur->clearSelection();
-                       }
                }
 
                // Give macro the chance to adapt to new situation.
@@ -492,14 +486,16 @@ void MathData::updateMacros(Cursor * cur, MacroContext const & mc,
                if (inset->asScriptInset())
                        inset = inset->asScriptInset()->nuc()[0].nucleus();
                LASSERT(inset->asMacro(), continue);
-               inset->asMacro()->updateRepresentation(cur, mc, utype);
+               inset->asMacro()->updateRepresentation(cur, mc, utype, nesting + 1);
        }
 }
 
 
 void MathData::detachMacroParameters(DocIterator * cur, const size_type macroPos)
 {
-       MathMacro * macroInset = operator[](macroPos).nucleus()->asMacro();
+       InsetMathMacro * macroInset = operator[](macroPos).nucleus()->asMacro();
+       // We store this now, because the inset pointer will be invalidated in the scond loop below
+       size_t const optionals = macroInset->optionals();
 
        // detach all arguments
        vector<MathData> detachedArgs;
@@ -525,7 +521,7 @@ void MathData::detachMacroParameters(DocIterator * cur, const size_type macroPos
 
        // 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) {
+       for (size_t l = 0; l < detachedArgs.size() && l < optionals; ++l) {
                if (!detachedArgs[l].empty())
                        lastNonEmptyOptional = l;
        }
@@ -533,7 +529,10 @@ void MathData::detachMacroParameters(DocIterator * cur, const size_type macroPos
        // optional arguments to be put back?
        pos_type p = macroPos + 1;
        size_t j = 0;
-       for (; j < detachedArgs.size() && j < macroInset->optionals(); ++j) {
+       // We do not want to use macroInset below, the insert() call in
+       // the loop will invalidate it.
+       macroInset = 0;
+       for (; j < detachedArgs.size() && j < optionals; ++j) {
                // another non-empty parameter follows?
                bool canDropEmptyOptional = j >= lastNonEmptyOptional;
 
@@ -625,7 +624,7 @@ void MathData::attachMacroParameters(Cursor * cur,
        const int macroOptionals, const bool fromInitToNormalMode,
        const bool interactiveInit, const size_t appetite)
 {
-       MathMacro * macroInset = operator[](macroPos).nucleus()->asMacro();
+       InsetMathMacro * macroInset = operator[](macroPos).nucleus()->asMacro();
 
        // start at atom behind the macro again, maybe with some new arguments
        // from the detach phase above, to add them back into the macro inset
@@ -716,12 +715,15 @@ void MathData::collectOptionalParameters(Cursor * cur,
                if (operator[](pos)->getChar() != '[')
                        break;
 
-               // found possible optional argument, look for "]"
+               // found possible optional argument, look for pairing "]"
+               int count = 1;
                size_t right = pos + 1;
                for (; right < size(); ++right) {
                        MathAtom & cell = operator[](right);
 
-                       if (cell->getChar() == ']')
+                       if (cell->getChar() == '[')
+                               ++count;
+                       else if (cell->getChar() == ']' && --count == 0)
                                // found right end
                                break;
 
@@ -756,7 +758,9 @@ void MathData::collectOptionalParameters(Cursor * cur,
                        params.push_back(optarg);
 
                // place cursor in optional argument of macro
-               if (thisSlice != -1
+               // Note: The two expressions on the first line are equivalent
+               // (see caller), but making this explicit pleases coverity.
+               if (cur && thisSlice != -1
                    && thisPos >= int(pos) && thisPos <= int(right)) {
                        int paramPos = max(0, thisPos - int(pos) - 1);
                        vector<CursorSlice> x;
@@ -796,7 +800,10 @@ void MathData::collectParameters(Cursor * cur,
                // fix cursor
                vector<CursorSlice> argSlices;
                int argPos = 0;
-               if (thisSlice != -1 && thisPos == int(pos))
+               // Note: The two expressions on the first line are equivalent
+               // (see caller), but making this explicit pleases coverity.
+               if (cur && thisSlice != -1
+                       && thisPos == int(pos))
                        cur->cutOff(thisSlice, argSlices);
 
                // which kind of parameter is it? In {}? With index x^n?
@@ -837,7 +844,10 @@ void MathData::collectParameters(Cursor * cur,
                }
 
                // put cursor in argument again
-               if (thisSlice != - 1 && thisPos == int(pos)) {
+               // Note: The first two expressions on the first line are
+               // equivalent (see caller), but making this explicit pleases
+               // coverity.
+               if (cur && thisSlice != -1 && thisPos == int(pos)) {
                        cur->append(params.size() - 1, argPos);
                        cur->append(argSlices);
                        (*cur)[thisSlice].pos() = macroPos;
@@ -849,20 +859,12 @@ void MathData::collectParameters(Cursor * cur,
 
 
 int MathData::pos2x(BufferView const * bv, size_type pos) const
-{
-       return pos2x(bv, pos, 0);
-}
-
-
-int MathData::pos2x(BufferView const * bv, size_type pos, int glue) const
 {
        int x = 0;
        size_type target = min(pos, size());
-       CoordCacheBase<Inset> const & coords = bv->coordCache().getInsets();
+       CoordCache::Insets const & coords = bv->coordCache().getInsets();
        for (size_type i = 0; i < target; ++i) {
                const_iterator it = begin() + i;
-               if ((*it)->getChar() == ' ')
-                       x += glue;
                //lyxerr << "char: " << (*it)->getChar()
                //      << "width: " << (*it)->width() << endl;
                x += coords.dim((*it).nucleus()).wid;
@@ -872,22 +874,14 @@ int MathData::pos2x(BufferView const * bv, size_type pos, int glue) const
 
 
 MathData::size_type MathData::x2pos(BufferView const * bv, int targetx) const
-{
-       return x2pos(bv, targetx, 0);
-}
-
-
-MathData::size_type MathData::x2pos(BufferView const * bv, int targetx, int glue) const
 {
        const_iterator it = begin();
        int lastx = 0;
        int currx = 0;
-       CoordCacheBase<Inset> const & coords = bv->coordCache().getInsets();
+       CoordCache::Insets const & coords = bv->coordCache().getInsets();
        // find first position after targetx
        for (; currx < targetx && it != end(); ++it) {
                lastx = currx;
-               if ((*it)->getChar() == ' ')
-                       currx += glue;
                currx += coords.dim((*it).nucleus()).wid;
        }
 
@@ -958,6 +952,20 @@ int MathData::yo(BufferView const & bv) const
 }
 
 
+MathClass MathData::mathClass() const
+{
+       MathClass res = MC_UNKNOWN;
+       for (MathAtom const & at : *this) {
+               MathClass mc = at->mathClass();
+               if (res == MC_UNKNOWN)
+                       res = mc;
+               else if (mc != MC_UNKNOWN && res != mc)
+                       return MC_ORD;
+       }
+       return res == MC_UNKNOWN ? MC_ORD : res;
+}
+
+
 ostream & operator<<(ostream & os, MathData const & ar)
 {
        odocstringstream oss;