diff --git a/src/Cursor.cpp b/src/Cursor.cpp index 54a3b8bb7e..3fa8a4af07 100644 --- a/src/Cursor.cpp +++ b/src/Cursor.cpp @@ -38,10 +38,11 @@ #include "insets/InsetTabular.h" #include "insets/InsetText.h" -#include "mathed/MathData.h" #include "mathed/InsetMath.h" #include "mathed/InsetMathScript.h" #include "mathed/MacroTable.h" +#include "mathed/MathData.h" +#include "mathed/MathMacro.h" #include #include @@ -943,7 +944,13 @@ bool Cursor::macroModeClose() InsetMathNest * const in = inset().asInsetMath()->asNestInset(); if (in && in->interpretString(*this, s)) return true; - plainInsert(createInsetMath(name)); + MathAtom atom = createInsetMath(name); + if (atom.nucleus()->asMacro()) { + // make non-greedy, i.e. don't eat parameters from the right + MathMacro * macro = atom.nucleus()->asMacro(); + macro->setDisplayMode(MathMacro::DISPLAY_NONGREEDY_INIT); + } + plainInsert(atom); return true; } diff --git a/src/mathed/MathData.cpp b/src/mathed/MathData.cpp index f44f287095..b417e4b30a 100644 --- a/src/mathed/MathData.cpp +++ b/src/mathed/MathData.cpp @@ -378,12 +378,18 @@ void MathData::updateMacros(MetricsInfo & mi) || macroInset->optionals() != macroOptionals)) { // is it a virgin macro which was never attached to parameters? bool fromInitToNormalMode - = oldDisplayMode == MathMacro::DISPLAY_INIT + = (oldDisplayMode == MathMacro::DISPLAY_INIT + || oldDisplayMode == MathMacro::DISPLAY_NONGREEDY_INIT) && newDisplayMode == MathMacro::DISPLAY_NORMAL; + bool greedy = (oldDisplayMode != MathMacro::DISPLAY_NONGREEDY_INIT); // attach parameters attachMacroParameters(cur, i, macroNumArgs, macroOptionals, - fromInitToNormalMode); + fromInitToNormalMode, greedy); + + // FIXME: proper anchor handling, this removes the selection + cur.updateInsets(&cur.bottom().inset()); + cur.clearSelection(); } // give macro the chance to adapt to new situation @@ -512,15 +518,15 @@ void MathData::detachMacroParameters(Cursor & cur, const size_type macroPos) void MathData::attachMacroParameters(Cursor & cur, const size_type macroPos, const size_type macroNumArgs, - const int macroOptionals, const bool fromInitToNormalMode) + const int macroOptionals, const bool fromInitToNormalMode, + const bool greedy) { MathMacro * macroInset = operator[](macroPos).nucleus()->asMacro(); // start at atom behind the macro again, maybe with some new arguments from above // to add them back into the macro inset size_t p = macroPos + 1; - size_t j = 0; - std::vectordetachedArgs; + std::vector detachedArgs; MathAtom scriptToPutAround; // find cursor slice again @@ -529,112 +535,15 @@ void MathData::attachMacroParameters(Cursor & cur, if (thisSlice != -1) thisPos = cur[thisSlice].pos(); - // insert optional arguments? - for (; j < macroOptionals && p < size(); ++j) { - // is a [] block following which could be an optional parameter? - if (operator[](p)->getChar() != '[') { - detachedArgs.push_back(MathData()); - continue; - } - - // found optional argument, look for "]" - size_t right = p + 1; - for (; right < size(); ++right) { - if (operator[](right)->getChar() == ']') - // found right end - break; - } - - // found? - if (right < size()) { - // add everything between [ and ] as optional argument - MathData optarg(begin() + p + 1, begin() + right); - // a brace? - bool brace = false; - if (optarg.size() == 1 && optarg[0]->asBraceInset()) { - brace = true; - detachedArgs.push_back(optarg[0]->asBraceInset()->cell(0)); - } else - detachedArgs.push_back(optarg); - // place cursor in optional argument of macro - if (thisPos >= int(p) && thisPos <= int(right)) { - int pos = std::max(0, thisPos - int(p) - 1); - std::vector x; - cur.cutOff(thisSlice, x); - cur[thisSlice].pos() = macroPos; - if (brace) { - pos = x[0].pos(); - x.erase(x.begin()); - } - cur.append(0, pos); - cur.append(x); - } - p = right + 1; - } else { - // no ] found, so it's not an optional argument - // Note: This was "macroPos = p" before, which probably - // does not make sense. We want to stop with optional - // argument handling instead, so go back to the beginning. - j = 0; - break; - } + // find arguments behind the macro + if (greedy) { + collectOptionalParameters(cur, macroOptionals, detachedArgs, p, + macroPos, thisPos, thisSlice); + collectParameters(cur, macroNumArgs, detachedArgs, p, + scriptToPutAround, + macroPos, thisPos, thisSlice); } - - // insert normal arguments - for (; j < macroNumArgs && p < size(); ++j) { - MathAtom & cell = operator[](p); - - // fix cursor - std::vector argSlices; - int argPos = 0; - if (thisPos == int(p)) { - cur.cutOff(thisSlice, argSlices); - } - InsetMathBrace const * brace = cell->asBraceInset(); - if (brace) { - // found brace, convert into argument - detachedArgs.push_back(brace->cell(0)); - - // cursor inside of the brace or just in front of? - if (thisPos == int(p) && !argSlices.empty()) { - argPos = argSlices[0].pos(); - argSlices.erase(argSlices.begin()); - } - } else if (cell->asScriptInset() && j + 1 == macroNumArgs) { - // last inset with scripts without braces - // -> they belong to the macro, not the argument - InsetMathScript * script = cell.nucleus()->asScriptInset(); - if (script->nuc().size() == 1 && script->nuc()[0]->asBraceInset()) - // nucleus in brace? Unpack! - detachedArgs.push_back(script->nuc()[0]->asBraceInset()->cell(0)); - else - detachedArgs.push_back(script->nuc()); - - // script will be put around below - scriptToPutAround = cell; - - // this should only happen after loading, so make cursor handling simple - if (thisPos >= int(macroPos) && thisPos <= int(macroPos + macroNumArgs)) { - argSlices.clear(); - cur.append(0, 0); - } - } else { - MathData array; - array.insert(0, cell); - detachedArgs.push_back(array); - } - - // put cursor in argument again - if (thisPos == int(p)) { - cur.append(j, argPos); - cur.append(argSlices); - cur[thisSlice].pos() = macroPos; - } - - ++p; - } - // attach arguments back to macro inset macroInset->attachArguments(detachedArgs, macroNumArgs, macroOptionals); @@ -666,10 +575,128 @@ void MathData::attachMacroParameters(Cursor & cur, cur.push_back(CursorSlice(*macroInset)); macroInset->idxFirst(cur); } - - // FIXME: proper anchor handling, this removes the selection - cur.updateInsets(&cur.bottom().inset()); - cur.clearSelection(); +} + + +void MathData::collectOptionalParameters(Cursor & cur, + const size_type numOptionalParams, std::vector & params, + size_t & pos, const pos_type macroPos, const int thisPos, const int thisSlice) +{ + // insert optional arguments? + while (params.size() < numOptionalParams && pos < size()) { + // 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) { + if (operator[](right)->getChar() == ']') + // found right end + break; + } + + // found? + if (right >= size()) { + // no ] found, so it's not an optional argument + break; + } + + // add everything between [ and ] as optional argument + MathData optarg(begin() + pos + 1, begin() + right); + + // a brace? + bool brace = false; + if (optarg.size() == 1 && optarg[0]->asBraceInset()) { + brace = true; + params.push_back(optarg[0]->asBraceInset()->cell(0)); + } else + params.push_back(optarg); + + // place cursor in optional argument of macro + if (thisPos >= int(pos) && thisPos <= int(right)) { + int paramPos = std::max(0, thisPos - int(pos) - 1); + std::vector x; + 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); + } + pos = right + 1; + } + + // fill up empty optional parameters + while (params.size() < numOptionalParams) { + params.push_back(MathData()); + } +} + + +void MathData::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) +{ + // insert normal arguments + for (; params.size() < numParams && pos < size();) { + MathAtom & cell = operator[](pos); + + // fix cursor + std::vector argSlices; + int argPos = 0; + if (thisPos == int(pos)) { + cur.cutOff(thisSlice, argSlices); + } + + // which kind of parameter is it? In {}? With index x^n? + InsetMathBrace const * brace = cell->asBraceInset(); + if (brace) { + // found brace, convert into argument + params.push_back(brace->cell(0)); + + // cursor inside of the brace or just in front of? + if (thisPos == int(pos) && !argSlices.empty()) { + argPos = argSlices[0].pos(); + argSlices.erase(argSlices.begin()); + } + } else if (cell->asScriptInset() && params.size() + 1 == numParams) { + // last inset with scripts without braces + // -> they belong to the macro, not the argument + InsetMathScript * script = cell.nucleus()->asScriptInset(); + if (script->nuc().size() == 1 && script->nuc()[0]->asBraceInset()) + // nucleus in brace? Unpack! + params.push_back(script->nuc()[0]->asBraceInset()->cell(0)); + else + params.push_back(script->nuc()); + + // script will be put around below + scriptToPutAround = cell; + + // 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); + } + } else { + // the simplest case: plain inset + MathData array; + array.insert(0, cell); + params.push_back(array); + } + + // put cursor in argument again + if (thisPos == int(pos)) { + 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 0e217a2f31..e6c4c378a4 100644 --- a/src/mathed/MathData.h +++ b/src/mathed/MathData.h @@ -173,7 +173,16 @@ private: /// void attachMacroParameters(Cursor & cur, const size_type macroPos, const size_type macroNumArgs, const int macroOptionals, - const bool fromInitToNormalMode); + const bool fromInitToNormalMode, const bool greedy); + /// + 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, + const size_type numParams, std::vector & params, + size_t & pos, MathAtom & scriptToPutAround, + const pos_type macroPos, const int thisPos, const int thisSlice); /// mutable std::vector atom_dims_; }; diff --git a/src/mathed/MathMacro.cpp b/src/mathed/MathMacro.cpp index e664cf0cab..67a97cbd4d 100644 --- a/src/mathed/MathMacro.cpp +++ b/src/mathed/MathMacro.cpp @@ -158,7 +158,7 @@ bool MathMacro::editMode(Cursor const & cur) const { void MathMacro::metrics(MetricsInfo & mi, Dimension & dim) const { // calculate new metrics according to display mode - if (displayMode_ == DISPLAY_INIT) { + if (displayMode_ == DISPLAY_INIT || displayMode_ == DISPLAY_NONGREEDY_INIT) { mathed_string_dim(mi.base.font, from_ascii("\\") + name(), dim); } else if (displayMode_ == DISPLAY_UNFOLDED) { cell(0).metrics(mi, dim); @@ -273,7 +273,7 @@ void MathMacro::draw(PainterInfo & pi, int x, int y) const int expx = x; int expy = y; - if (displayMode_ == DISPLAY_INIT) { + if (displayMode_ == DISPLAY_INIT || displayMode_ == DISPLAY_NONGREEDY_INIT) { PainterInfo pi2(pi.base.bv, pi.pain); pi2.base.font.setColor(macro_ ? Color_latex : Color_error); //pi2.base.style = LM_ST_TEXT; diff --git a/src/mathed/MathMacro.h b/src/mathed/MathMacro.h index 88abdd0d85..45239e83eb 100644 --- a/src/mathed/MathMacro.h +++ b/src/mathed/MathMacro.h @@ -87,6 +87,7 @@ public: enum DisplayMode { DISPLAY_INIT, + DISPLAY_NONGREEDY_INIT, DISPLAY_UNFOLDED, DISPLAY_NORMAL, }; @@ -121,6 +122,7 @@ public: protected: friend class MathData; friend class ArgumentProxy; + friend class Cursor; /// update the display mode (should only be called after detaching arguments) void setDisplayMode(DisplayMode mode);