]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathMacro.cpp
Add missing revert routine to lyx_2_0.py
[lyx.git] / src / mathed / InsetMathMacro.cpp
1 /**
2  * \file InsetMathMacro.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  * \author André Pönitz
8  * \author Stefan Schimanski
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "InsetMathMacro.h"
16
17 #include "InsetMathChar.h"
18 #include "InsetMathScript.h"
19 #include "MathCompletionList.h"
20 #include "MathExtern.h"
21 #include "MathFactory.h"
22 #include "MathRow.h"
23 #include "MathStream.h"
24 #include "MathSupport.h"
25
26 #include "Buffer.h"
27 #include "BufferView.h"
28 #include "CoordCache.h"
29 #include "Cursor.h"
30 #include "Encoding.h"
31 #include "FuncStatus.h"
32 #include "FuncRequest.h"
33 #include "LaTeXFeatures.h"
34 #include "LyX.h"
35 #include "LyXRC.h"
36 #include "MetricsInfo.h"
37
38 #include "frontends/alert.h"
39 #include "frontends/Painter.h"
40
41 #include "support/debug.h"
42 #include "support/gettext.h"
43 #include "support/lassert.h"
44 #include "support/lstrings.h"
45 #include "support/Changer.h"
46 #include "support/textutils.h"
47
48 #include <ostream>
49 #include <vector>
50
51 using namespace lyx::support;
52 using namespace std;
53
54 namespace lyx {
55
56
57 /// A proxy for the macro values
58 class InsetArgumentProxy : public InsetMath {
59 public:
60         ///
61         InsetArgumentProxy(InsetMathMacro * mathMacro, size_t idx)
62                 : mathMacro_(mathMacro), idx_(idx) {}
63         ///
64         InsetArgumentProxy(InsetMathMacro * mathMacro, size_t idx, docstring const & def)
65                 : mathMacro_(mathMacro), idx_(idx)
66         {
67                         asArray(def, def_);
68         }
69         ///
70         void setBuffer(Buffer & buffer) override
71         {
72                 Inset::setBuffer(buffer);
73                 def_.setBuffer(buffer);
74         }
75         ///
76         void setOwner(InsetMathMacro * mathMacro) { mathMacro_ = mathMacro; }
77         ///
78         InsetMathMacro const * owner() { return mathMacro_; }
79         ///
80         marker_type marker(BufferView const *) const override { return marker_type::NO_MARKER; }
81         ///
82         InsetCode lyxCode() const override { return ARGUMENT_PROXY_CODE; }
83         /// The math data to use for display
84         MathData const & displayCell(BufferView const * bv) const
85         {
86                 // handle default macro arguments
87                 bool use_def_arg = !mathMacro_->editMetrics(bv)
88                         && mathMacro_->cell(idx_).empty();
89                 return use_def_arg ? def_ : mathMacro_->cell(idx_);
90         }
91         ///
92         bool addToMathRow(MathRow & mrow, MetricsInfo & mi) const override
93         {
94                 // macro arguments are in macros
95                 LATTEST(mathMacro_->nesting() > 0);
96                 /// The macro nesting can change display of insets. Change it locally.
97                 Changer chg = changeVar(mi.base.macro_nesting,
98                                           mathMacro_->nesting() == 1 ? 0 : mathMacro_->nesting());
99
100                 MathRow::Element e_beg(mi, MathRow::BEGIN);
101                 e_beg.inset = this;
102                 e_beg.ar = &mathMacro_->cell(idx_);
103                 mrow.push_back(e_beg);
104
105                 mathMacro_->macro()->unlock();
106                 bool has_contents = displayCell(mi.base.bv).addToMathRow(mrow, mi);
107                 mathMacro_->macro()->lock();
108
109                 // if there was no contents, and the contents is editable,
110                 // then we insert a box instead.
111                 if (!has_contents && mathMacro_->nesting() == 1) {
112                         // mathclass is ord because it should be spaced as a normal atom
113                         MathRow::Element e(mi, MathRow::BOX, MC_ORD);
114                         e.color = Color_mathline;
115                         mrow.push_back(e);
116                         has_contents = true;
117                 }
118
119                 MathRow::Element e_end(mi, MathRow::END);
120                 e_end.inset = this;
121                 e_end.ar = &mathMacro_->cell(idx_);
122                 mrow.push_back(e_end);
123
124                 return has_contents;
125         }
126         ///
127         void beforeMetrics() const override
128         {
129                 mathMacro_->macro()->unlock();
130         }
131         ///
132         void afterMetrics() const override
133         {
134                 mathMacro_->macro()->lock();
135         }
136         ///
137         void beforeDraw(PainterInfo const & pi) const override
138         {
139                 // if the macro is being edited, then the painter is in
140                 // monochrome mode.
141                 if (mathMacro_->editMetrics(pi.base.bv))
142                         pi.pain.leaveMonochromeMode();
143         }
144         ///
145         void afterDraw(PainterInfo const & pi) const override
146         {
147                 if (mathMacro_->editMetrics(pi.base.bv))
148                         pi.pain.enterMonochromeMode(Color_mathmacroblend);
149         }
150         ///
151         void metrics(MetricsInfo &, Dimension &) const override {
152                 // This should never be invoked, since InsetArgumentProxy insets are linearized
153                 LATTEST(false);
154         }
155         ///
156         void draw(PainterInfo &, int, int) const override {
157                 // This should never be invoked, since InsetArgumentProxy insets are linearized
158                 LATTEST(false);
159         }
160         ///
161         int kerning(BufferView const * bv) const override
162         {
163                 return displayCell(bv).kerning(bv);
164         }
165         // write(), normalize(), infoize() and infoize2() are not needed since
166         // InsetMathMacro uses the definition and not the expanded cells.
167         ///
168         void maple(MapleStream & ms) const override { ms << mathMacro_->cell(idx_); }
169         ///
170         void maxima(MaximaStream & ms) const override { ms << mathMacro_->cell(idx_); }
171         ///
172         void mathematica(MathematicaStream & ms) const override { ms << mathMacro_->cell(idx_); }
173         ///
174         void mathmlize(MathMLStream & ms) const override { ms << mathMacro_->cell(idx_); }
175         ///
176         void htmlize(HtmlStream & ms) const override { ms << mathMacro_->cell(idx_); }
177         ///
178         void octave(OctaveStream & os) const override { os << mathMacro_->cell(idx_); }
179         ///
180         MathClass mathClass() const override
181         {
182                 return MC_UNKNOWN;
183                 // This can be refined once the pointer issues are fixed. I did not
184                 // notice any immediate crash with the following code, but it is risky
185                 // nevertheless:
186                 //return mathMacro_->cell(idx_).mathClass();
187         }
188
189 private:
190         ///
191         Inset * clone() const override
192         {
193                 return new InsetArgumentProxy(*this);
194         }
195         ///
196         InsetMathMacro * mathMacro_;
197         ///
198         size_t idx_;
199         ///
200         MathData def_;
201 };
202
203
204 /// Private implementation of InsetMathMacro
205 class InsetMathMacro::Private {
206 public:
207         Private(Buffer * buf, docstring const & name)
208                 : name_(name), displayMode_(DISPLAY_INIT),
209                   expanded_(buf), definition_(buf), attachedArgsNum_(0),
210                   optionals_(0), nextFoldMode_(true), macroBackup_(buf),
211                   macro_(nullptr), needsUpdate_(false), isUpdating_(false),
212                   appetite_(9), nesting_(0), limits_(AUTO_LIMITS)
213         {
214         }
215         /// Update the pointers to our owner of all expanded macros.
216         /// This needs to be called every time a copy of the owner is created
217         /// (bug 9418).
218         void updateChildren(InsetMathMacro * owner);
219         /// Recursively update the pointers of all expanded macros
220         /// appearing in the arguments of the current macro
221         void updateNestedChildren(InsetMathMacro * owner, InsetMathNest * ni);
222         /// name of macro
223         docstring name_;
224         /// current display mode
225         DisplayMode displayMode_;
226         /// expanded macro with ArgumentProxies
227         MathData expanded_;
228         /// macro definition with #1,#2,.. insets
229         MathData definition_;
230         /// number of arguments that were really attached
231         size_t attachedArgsNum_;
232         /// optional argument attached? (only in DISPLAY_NORMAL mode)
233         size_t optionals_;
234         /// fold mode to be set in next metrics call?
235         bool nextFoldMode_;
236         /// if macro_ == true, then here is a copy of the macro
237         /// don't use it for locking
238         MacroData macroBackup_;
239         /// if macroNotFound_ == false, then here is a reference to the macro
240         /// this might invalidate after metrics was called
241         MacroData const * macro_;
242         ///
243         mutable std::map<BufferView const *, bool> editing_;
244         ///
245         std::string required_;
246         /// update macro representation
247         bool needsUpdate_;
248         ///
249         bool isUpdating_;
250         /// maximal number of arguments the macro is greedy for
251         size_t appetite_;
252         /// Level of nesting in macros (including this one)
253         int nesting_;
254         ///
255         Limits limits_;
256 };
257
258
259 void InsetMathMacro::Private::updateChildren(InsetMathMacro * owner)
260 {
261         for (size_t i = 0; i < expanded_.size(); ++i) {
262                 InsetArgumentProxy * p = dynamic_cast<InsetArgumentProxy *>(expanded_[i].nucleus());
263                 if (p)
264                         p->setOwner(owner);
265
266                 InsetMathNest * ni = expanded_[i].nucleus()->asNestInset();
267                 if (ni)
268                         updateNestedChildren(owner, ni);
269         }
270
271         if (macro_) {
272                 // The macro_ pointer is updated when MathData::metrics() is
273                 // called. However, when instant preview is on or the macro is
274                 // not on screen, MathData::metrics() is not called and we may
275                 // have a dangling pointer. As a safety measure, when a macro
276                 // is copied, always let macro_ point to the backup copy of the
277                 // MacroData structure. This backup is updated every time the
278                 // macro is changed, so it will not become stale.
279                 macro_ = &macroBackup_;
280         }
281 }
282
283
284 void InsetMathMacro::Private::updateNestedChildren(InsetMathMacro * owner, InsetMathNest * ni)
285 {
286         for (size_t i = 0; i < ni->nargs(); ++i) {
287                 MathData & ar = ni->cell(i);
288                 for (size_t j = 0; j < ar.size(); ++j) {
289                         InsetArgumentProxy * ap = dynamic_cast
290                                 <InsetArgumentProxy *>(ar[j].nucleus());
291                         if (ap) {
292                                 InsetMathMacro::Private * md = ap->owner()->d;
293                                 if (md->macro_)
294                                         md->macro_ = &md->macroBackup_;
295                                 ap->setOwner(owner);
296                         }
297                         InsetMathNest * imn = ar[j].nucleus()->asNestInset();
298                         if (imn)
299                                 updateNestedChildren(owner, imn);
300                 }
301         }
302 }
303
304
305 InsetMathMacro::InsetMathMacro(Buffer * buf, docstring const & name)
306         : InsetMathNest(buf, 0), d(new Private(buf, name))
307 {}
308
309
310 InsetMathMacro::InsetMathMacro(InsetMathMacro const & that)
311         : InsetMathNest(that), d(new Private(*that.d))
312 {
313         // FIXME This should not really be necessary, but when we are
314         // initializing the table of global macros, we create macros
315         // with no associated Buffer.
316         if (that.buffer_)
317                 setBuffer(*that.buffer_);
318         d->updateChildren(this);
319 }
320
321
322 InsetMathMacro & InsetMathMacro::operator=(InsetMathMacro const & that)
323 {
324         if (&that == this)
325                 return *this;
326         InsetMathNest::operator=(that);
327         *d = *that.d;
328         d->updateChildren(this);
329         return *this;
330 }
331
332
333 InsetMathMacro::~InsetMathMacro()
334 {
335         delete d;
336 }
337
338
339 bool InsetMathMacro::addToMathRow(MathRow & mrow, MetricsInfo & mi) const
340 {
341         // set edit mode for which we will have calculated row.
342         // This is the same as what is done in metrics().
343         d->editing_[mi.base.bv] = editMode(mi.base.bv);
344
345         // For now we do not linearize in the following cases (can be improved)
346         // - display mode different from normal
347         // - editing with parameter list
348         // - editing with box around macro
349         if (displayMode() != InsetMathMacro::DISPLAY_NORMAL
350                 || (d->editing_[mi.base.bv] && lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_LIST))
351                 return InsetMath::addToMathRow(mrow, mi);
352
353         /// The macro nesting can change display of insets. Change it locally.
354         Changer chg = changeVar(mi.base.macro_nesting, d->nesting_);
355
356         MathRow::Element e_beg(mi, MathRow::BEGIN);
357         e_beg.inset = this;
358         e_beg.marker = (d->nesting_ == 1) ? marker(mi.base.bv) : marker_type::NO_MARKER;
359         mrow.push_back(e_beg);
360
361         d->macro_->lock();
362         bool has_contents = d->expanded_.addToMathRow(mrow, mi);
363         d->macro_->unlock();
364
365         // if there was no contents and the array is editable, then we
366         // insert a grey box instead.
367         if (!has_contents && mi.base.macro_nesting == 1) {
368                 // mathclass is unknown because it is irrelevant for spacing
369                 MathRow::Element e(mi, MathRow::BOX);
370                 e.color = Color_mathmacroblend;
371                 mrow.push_back(e);
372                 has_contents = true;
373         }
374
375         MathRow::Element e_end(mi, MathRow::END);
376         e_end.inset = this;
377         e_end.marker = (d->nesting_ == 1) ? marker(mi.base.bv) : marker_type::NO_MARKER;
378         mrow.push_back(e_end);
379
380         return has_contents;
381 }
382
383
384 /// Whether the inset allows \(no)limits
385 bool InsetMathMacro::allowsLimitsChange() const
386 {
387         // similar to the code in mathClass(), except that we search for
388         // the right-side class.
389         MathClass mc = MC_UNKNOWN;
390         if (MacroData const * m = macroBackup()) {
391                 // If it is a global macro and is defined explicitly
392                 if (m->symbol())
393                         mc = string_to_class(m->symbol()->extra);
394         }
395         // Otherwise guess from the expanded macro
396         if (mc == MC_UNKNOWN)
397                 mc = d->expanded_.lastMathClass();
398
399         return mc == MC_OP;
400 }
401
402
403 Limits InsetMathMacro::defaultLimits(bool display) const
404 {
405         if (d->expanded_.empty())
406                 return NO_LIMITS;
407         // Guess from the expanded macro
408         InsetMath const * in = d->expanded_.back().nucleus();
409         Limits const lim = in->limits() == AUTO_LIMITS
410                 ? in->defaultLimits(display) : in->limits();
411         LATTEST(lim != AUTO_LIMITS);
412         return lim;
413 }
414
415
416 Limits InsetMathMacro::limits() const
417 {
418         return d->limits_;
419 }
420
421
422 void InsetMathMacro::limits(Limits lim)
423 {
424         d->limits_ = lim;
425 }
426
427
428 void InsetMathMacro::beforeMetrics() const
429 {
430         d->macro_->lock();
431 }
432
433
434 void InsetMathMacro::afterMetrics() const
435 {
436         d->macro_->unlock();
437 }
438
439
440 void InsetMathMacro::beforeDraw(PainterInfo const & pi) const
441 {
442         if (d->editing_[pi.base.bv])
443                 pi.pain.enterMonochromeMode(Color_mathmacroblend);
444 }
445
446
447 void InsetMathMacro::afterDraw(PainterInfo const & pi) const
448 {
449         if (d->editing_[pi.base.bv])
450                 pi.pain.leaveMonochromeMode();
451 }
452
453
454 Inset * InsetMathMacro::clone() const
455 {
456         InsetMathMacro * copy = new InsetMathMacro(*this);
457         copy->d->needsUpdate_ = true;
458         //copy->d->expanded_.clear();
459         return copy;
460 }
461
462
463 void InsetMathMacro::normalize(NormalStream & os) const
464 {
465         os << "[macro " << name();
466         for (size_t i = 0; i < nargs(); ++i)
467                 os << ' ' << cell(i);
468         os << ']';
469 }
470
471
472 InsetMathMacro::DisplayMode InsetMathMacro::displayMode() const
473 {
474         return d->displayMode_;
475 }
476
477
478 bool InsetMathMacro::extraBraces() const
479 {
480         return d->displayMode_ == DISPLAY_NORMAL && arity() > 0;
481 }
482
483
484 docstring InsetMathMacro::name() const
485 {
486         if (d->displayMode_ == DISPLAY_UNFOLDED)
487                 return asString(cell(0));
488
489         return d->name_;
490 }
491
492
493 docstring InsetMathMacro::macroName() const
494 {
495         return d->name_;
496 }
497
498
499 int InsetMathMacro::nesting() const
500 {
501         return d->nesting_;
502 }
503
504
505 void InsetMathMacro::cursorPos(BufferView const & bv,
506                 CursorSlice const & sl, bool boundary, int & x, int & y) const
507 {
508         // We may have 0 arguments, but InsetMathNest requires at least one.
509         if (nargs() > 0)
510                 InsetMathNest::cursorPos(bv, sl, boundary, x, y);
511 }
512
513
514 bool InsetMathMacro::editMode(BufferView const * bv) const {
515         // find this in cursor trace
516         DocIterator const & cur =
517                 // Do not move the reference while selecting with the mouse to avoid
518                 // flicker due to changing metrics
519                 bv->mouseSelecting() ? bv->cursor().realAnchor() : bv->cursor();
520         for (size_t i = 0; i != cur.depth(); ++i)
521                 if (&cur[i].inset() == this) {
522                         // look if there is no other macro in edit mode above
523                         ++i;
524                         for (; i != cur.depth(); ++i) {
525                                 InsetMath * im = cur[i].asInsetMath();
526                                 if (im) {
527                                         InsetMathMacro const * macro = im->asMacro();
528                                         if (macro && macro->displayMode() == DISPLAY_NORMAL)
529                                                 return false;
530                                 }
531                         }
532
533                         // ok, none found, I am the highest one
534                         return true;
535                 }
536
537         return false;
538 }
539
540
541 MacroData const * InsetMathMacro::macro() const
542 {
543         return d->macro_;
544 }
545
546
547 bool InsetMathMacro::editMetrics(BufferView const * bv) const
548 {
549         return d->editing_[bv];
550 }
551
552
553 marker_type InsetMathMacro::marker(BufferView const * bv) const
554 {
555         if (nargs() == 0)
556                 return marker_type::NO_MARKER;
557
558         switch (d->displayMode_) {
559         case DISPLAY_INIT:
560         case DISPLAY_INTERACTIVE_INIT:
561                 return marker_type::NO_MARKER;
562         case DISPLAY_UNFOLDED:
563                 return marker_type::MARKER;
564         case DISPLAY_NORMAL:
565                 switch (lyxrc.macro_edit_style) {
566                 case LyXRC::MACRO_EDIT_INLINE:
567                         return marker_type::MARKER2;
568                 case LyXRC::MACRO_EDIT_INLINE_BOX:
569                         return d->editing_[bv] ? marker_type::BOX_MARKER : marker_type::MARKER2;
570                 case LyXRC::MACRO_EDIT_LIST:
571                         return marker_type::MARKER2;
572                 }
573         }
574         // please gcc 4.6
575         return marker_type::NO_MARKER;
576 }
577
578
579 void InsetMathMacro::metrics(MetricsInfo & mi, Dimension & dim) const
580 {
581         /// The macro nesting can change display of insets. Change it locally.
582         Changer chg = changeVar(mi.base.macro_nesting, d->nesting_);
583
584         // set edit mode for which we will have calculated metrics. But only
585         d->editing_[mi.base.bv] = editMode(mi.base.bv);
586
587         // calculate new metrics according to display mode
588         if (d->displayMode_ == DISPLAY_INIT || d->displayMode_ == DISPLAY_INTERACTIVE_INIT) {
589                 Changer dummy = mi.base.changeFontSet("lyxtex");
590                 mathed_string_dim(mi.base.font, from_ascii("\\") + name(), dim);
591         } else if (d->displayMode_ == DISPLAY_UNFOLDED) {
592                 Changer dummy = mi.base.changeFontSet("lyxtex");
593                 cell(0).metrics(mi, dim);
594                 Dimension bsdim;
595                 mathed_string_dim(mi.base.font, from_ascii("\\"), bsdim);
596                 dim.wid += bsdim.width() + 1;
597                 dim.asc = max(bsdim.ascent(), dim.ascent());
598                 dim.des = max(bsdim.descent(), dim.descent());
599         } else if (lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_LIST
600                    && d->editing_[mi.base.bv]) {
601                 // Macro will be edited in a old-style list mode here:
602
603                 LBUFERR(d->macro_);
604                 Dimension fontDim;
605                 FontInfo labelFont = sane_font;
606                 math_font_max_dim(labelFont, fontDim.asc, fontDim.des);
607
608                 // get dimension of components of list view
609                 Dimension nameDim;
610                 nameDim.wid = mathed_string_width(mi.base.font, from_ascii("Macro \\") + name() + ": ");
611                 nameDim.asc = fontDim.asc;
612                 nameDim.des = fontDim.des;
613
614                 Dimension argDim;
615                 argDim.wid = mathed_string_width(labelFont, from_ascii("#9: "));
616                 argDim.asc = fontDim.asc;
617                 argDim.des = fontDim.des;
618
619                 Dimension defDim;
620                 d->definition_.metrics(mi, defDim);
621
622                 // add them up
623                 dim.wid = nameDim.wid + defDim.wid;
624                 dim.asc = max(nameDim.asc, defDim.asc);
625                 dim.des = max(nameDim.des, defDim.des);
626
627                 for (idx_type i = 0; i < nargs(); ++i) {
628                         Dimension cdim;
629                         cell(i).metrics(mi, cdim);
630                         dim.des += max(argDim.height(), cdim.height()) + 1;
631                         dim.wid = max(dim.wid, argDim.wid + cdim.wid);
632                 }
633
634                 // make space for box and markers, 2 pixels
635                 dim.asc += 1;
636                 dim.des += 1;
637                 dim.wid += 2;
638         } else {
639                 // We should not be here, since the macro is linearized in this case.
640                 LBUFERR(false);
641         }
642 }
643
644
645 int InsetMathMacro::kerning(BufferView const * bv) const {
646         if (d->displayMode_ == DISPLAY_NORMAL && !d->editing_[bv])
647                 return d->expanded_.kerning(bv);
648         else
649                 return 0;
650 }
651
652
653 void InsetMathMacro::updateMacro(MacroContext const & mc)
654 {
655         if (validName()) {
656                 d->macro_ = mc.get(name());
657                 if (d->macro_ && d->macroBackup_ != *d->macro_) {
658                         d->macroBackup_ = *d->macro_;
659                         d->needsUpdate_ = true;
660                 }
661         } else {
662                 d->macro_ = nullptr;
663         }
664 }
665
666
667 class InsetMathMacro::UpdateLocker
668 {
669 public:
670         explicit UpdateLocker(InsetMathMacro & mm) : mac(mm)
671         {
672                 mac.d->isUpdating_ = true;
673         }
674         ~UpdateLocker() { mac.d->isUpdating_ = false; }
675 private:
676         InsetMathMacro & mac;
677 };
678 /** Avoid wrong usage of UpdateLocker.
679     To avoid wrong usage:
680     UpdateLocker(...); // wrong
681     UpdateLocker locker(...); // right
682 */
683 #define UpdateLocker(x) unnamed_UpdateLocker;
684 // Tip gotten from Bobby Schmidt's column in C/C++ Users Journal
685
686
687 void InsetMathMacro::updateRepresentation(Cursor * cur, MacroContext const & mc,
688         UpdateType utype, int nesting)
689 {
690         // block recursive calls (bug 8999)
691         if (d->isUpdating_)
692                 return;
693
694         UpdateLocker locker(*this);
695
696         // known macro?
697         if (d->macro_ == nullptr)
698                 return;
699
700         // remember nesting level of this macro
701         d->nesting_ = nesting;
702
703         // update requires
704         d->required_ = d->macro_->required();
705
706         if (!d->needsUpdate_
707                 // non-normal mode? We are done!
708                 || (d->displayMode_ != DISPLAY_NORMAL))
709                 return;
710
711         d->needsUpdate_ = false;
712
713         // get default values of macro
714         vector<docstring> const & defaults = d->macro_->defaults();
715
716         // create MathMacroArgumentValue objects pointing to the cells of the macro
717         vector<MathData> values(nargs());
718         for (size_t i = 0; i < nargs(); ++i) {
719                 InsetArgumentProxy * proxy;
720                 if (i < defaults.size())
721                         proxy = new InsetArgumentProxy(this, i, defaults[i]);
722                 else
723                         proxy = new InsetArgumentProxy(this, i);
724                 values[i].insert(0, MathAtom(proxy));
725         }
726         // expanding macro with the values
727         // Only update the argument macros if anything was expanded or the LyX
728         // representation part does not contain the macro itself, otherwise we
729         // would get an endless loop (bugs 9140 and 11595). UpdateLocker does
730         // not work in this case, since MacroData::expand() creates new
731         // InsetMathMacro objects, so this would be a different recursion path
732         // than the one protected by UpdateLocker.
733         docstring const & display = d->macro_->display();
734         docstring const latexname = from_ascii("\\") + macroName();
735         bool const ret = d->macro_->expand(values, d->expanded_);
736         d->expanded_.setBuffer(buffer());
737         if (ret && !support::contains(display, latexname)) {
738                 if (utype == OutputUpdate && !d->expanded_.empty())
739                         d->expanded_.updateMacros(cur, mc, utype, nesting);
740         }
741         // get definition for list edit mode
742         asArray(display.empty() ? d->macro_->definition() : display,
743                 d->definition_, Parse::QUIET | Parse::MACRODEF);
744 }
745
746
747 void InsetMathMacro::draw(PainterInfo & pi, int x, int y) const
748 {
749         Dimension const dim = dimension(*pi.base.bv);
750
751         int expx = x;
752         int expy = y;
753
754         if (d->displayMode_ == DISPLAY_INIT || d->displayMode_ == DISPLAY_INTERACTIVE_INIT) {
755                 Changer dummy = pi.base.changeFontSet("lyxtex");
756                 pi.pain.text(x, y, from_ascii("\\") + name(), pi.base.font);
757         } else if (d->displayMode_ == DISPLAY_UNFOLDED) {
758                 Changer dummy = pi.base.changeFontSet("lyxtex");
759                 pi.pain.text(x, y, from_ascii("\\"), pi.base.font);
760                 x += mathed_string_width(pi.base.font, from_ascii("\\")) + 1;
761                 cell(0).draw(pi, x, y);
762         } else if (lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_LIST
763                    && d->editing_[pi.base.bv]) {
764                 // Macro will be edited in a old-style list mode here:
765
766                 CoordCache const & coords = pi.base.bv->coordCache();
767                 FontInfo const & labelFont = sane_font;
768
769                 // box needs one pixel
770                 x += 1;
771
772                 // get maximal font height
773                 Dimension fontDim;
774                 math_font_max_dim(pi.base.font, fontDim.asc, fontDim.des);
775
776                 // draw label
777                 docstring label = from_ascii("Macro \\") + name() + from_ascii(": ");
778                 pi.pain.text(x, y, label, labelFont);
779                 x += mathed_string_width(labelFont, label);
780
781                 // draw definition
782                 d->definition_.draw(pi, x, y);
783                 Dimension const & defDim = coords.getArrays().dim(&d->definition_);
784                 y += max(fontDim.des, defDim.des);
785
786                 // draw parameters
787                 docstring str = from_ascii("#9");
788                 int strw1 = mathed_string_width(labelFont, from_ascii("#9"));
789                 int strw2 = mathed_string_width(labelFont, from_ascii(": "));
790
791                 for (idx_type i = 0; i < nargs(); ++i) {
792                         // position of label
793                         Dimension const & cdim = coords.getArrays().dim(&cell(i));
794                         x = expx + 1;
795                         y += max(fontDim.asc, cdim.asc) + 1;
796
797                         // draw label
798                         str[1] = '1' + i;
799                         pi.pain.text(x, y, str, labelFont);
800                         x += strw1;
801                         pi.pain.text(x, y, from_ascii(":"), labelFont);
802                         x += strw2;
803
804                         // draw parameter
805                         cell(i).draw(pi, x, y);
806
807                         // next line
808                         y += max(fontDim.des, cdim.des);
809                 }
810
811                 pi.pain.rectangle(expx, expy - dim.asc + 1, dim.wid - 1,
812                                   dim.height() - 2, Color_mathmacroframe);
813         } else {
814                 // We should not be here, since the macro is linearized in this case.
815                 LBUFERR(false);
816         }
817
818         // edit mode changed?
819         if (d->editing_[pi.base.bv] != editMode(pi.base.bv))
820                 pi.base.bv->cursor().screenUpdateFlags(Update::SinglePar);
821 }
822
823
824 void InsetMathMacro::setDisplayMode(InsetMathMacro::DisplayMode mode, int appetite)
825 {
826         if (d->displayMode_ != mode) {
827                 // transfer name if changing from or to DISPLAY_UNFOLDED
828                 if (mode == DISPLAY_UNFOLDED) {
829                         cells_.resize(1);
830                         asArray(d->name_, cell(0));
831                 } else if (d->displayMode_ == DISPLAY_UNFOLDED) {
832                         d->name_ = asString(cell(0));
833                         cells_.resize(0);
834                 }
835
836                 d->displayMode_ = mode;
837                 d->needsUpdate_ = true;
838         }
839
840         // the interactive init mode is non-greedy by default
841         if (appetite == -1)
842                 d->appetite_ = (mode == DISPLAY_INTERACTIVE_INIT) ? 0 : 9;
843         else
844                 d->appetite_ = size_t(appetite);
845 }
846
847
848 InsetMathMacro::DisplayMode InsetMathMacro::computeDisplayMode() const
849 {
850         if (d->nextFoldMode_ && d->macro_ && !d->macro_->locked())
851                 return DISPLAY_NORMAL;
852         else
853                 return DISPLAY_UNFOLDED;
854 }
855
856
857 bool InsetMathMacro::validName() const
858 {
859         docstring n = name();
860
861         if (n.empty())
862                 return false;
863
864         // converting back and force doesn't swallow anything?
865         /*MathData ma;
866         asArray(n, ma);
867         if (asString(ma) != n)
868                 return false;*/
869
870         // valid characters?
871         if (n.size() > 1) {
872                 for (char_type c : n) {
873                         if (!(c >= 'a' && c <= 'z')
874                             && !(c >= 'A' && c <= 'Z')
875                             && c != '*')
876                                 return false;
877                 }
878         }
879
880         return true;
881 }
882
883
884 size_t InsetMathMacro::arity() const
885 {
886         if (d->displayMode_ == DISPLAY_NORMAL )
887                 return cells_.size();
888         else
889                 return 0;
890 }
891
892
893 size_t InsetMathMacro::optionals() const
894 {
895         return d->optionals_;
896 }
897
898
899 void InsetMathMacro::setOptionals(int n)
900 {
901         if (n <= int(nargs()))
902                 d->optionals_ = n;
903 }
904
905
906 size_t InsetMathMacro::appetite() const
907 {
908         return d->appetite_;
909 }
910
911
912 MathClass InsetMathMacro::mathClass() const
913 {
914         // This can be just a heuristic, since it is only considered for display
915         // when the macro is not linearised. Therefore it affects:
916         // * The spacing of the inset while being edited,
917         // * Intelligent splitting
918         // * Cursor word movement (Ctrl-Arrow).
919         if (MacroData const * m = macroBackup()) {
920                 // If it is a global macro and is defined explicitly
921                 if (m->symbol()) {
922                         MathClass mc = string_to_class(m->symbol()->extra);
923                         if (mc != MC_UNKNOWN)
924                                 return mc;
925                 }
926         }
927         // Otherwise guess from the expanded macro
928         return d->expanded_.mathClass();
929 }
930
931
932 InsetMath::mode_type InsetMathMacro::currentMode() const
933 {
934         // User defined macros are always assumed to be mathmode macros.
935         // Only the global macros defined in lib/symbols may be textmode.
936         if (MacroData const * m = macroBackup()) {
937                 if (m->symbol() && m->symbol()->extra == "textmode")
938                         return TEXT_MODE;
939                 else
940                         return MATH_MODE;
941         }
942         // Unknown macros are undecided.
943         return UNDECIDED_MODE;
944 }
945
946
947 MacroData const * InsetMathMacro::macroBackup() const
948 {
949         if (macro())
950                 return &d->macroBackup_;
951         if (MacroData const * data = MacroTable::globalMacros().get(name()))
952                 return data;
953         return nullptr;
954 }
955
956
957 void InsetMathMacro::validate(LaTeXFeatures & features) const
958 {
959         // Immediately after a document is loaded, in some cases the MacroData
960         // of the global macros defined in the lib/symbols file may still not
961         // be known to the macro machinery because it will be set only after
962         // the first call to updateMacros(). This is not a problem unless
963         // instant preview is on for math, in which case we will be missing
964         // the corresponding requirements.
965         // In this case, we get the required info from the global macro table.
966         if (!d->required_.empty())
967                 features.require(d->required_);
968         else if (!d->macro_) {
969                 // Update requires for known global macros.
970                 MacroData const * data = MacroTable::globalMacros().get(name());
971                 if (data && !data->required().empty())
972                         features.require(data->required());
973         }
974
975         // Validate the cells and the definition.
976         // However, don't validate the definition if the macro is
977         // from the symbols file and has not been redefined, because
978         // in this case the definition is only used for screen display.
979         MathWordList const & words = mathedWordList();
980         MathWordList::const_iterator it = words.find(name());
981         MacroNameSet macros;
982         buffer().listMacroNames(macros);
983         if (it == words.end() || it->second.inset != "macro"
984             || macros.find(name()) != macros.end()) {
985                 if (displayMode() == DISPLAY_NORMAL) {
986                                 d->definition_.validate(features);
987                 } else if (displayMode() == DISPLAY_INIT) {
988                         MathData ar(const_cast<Buffer *>(&buffer()));
989                         MacroData const * data = buffer().getMacro(name());
990                         if (data) {
991                                 asArray(data->definition(), ar);
992                                 ar.validate(features);
993                         }
994                 }
995         }
996         InsetMathNest::validate(features);
997 }
998
999
1000 void InsetMathMacro::edit(Cursor & cur, bool front, EntryDirection entry_from)
1001 {
1002         cur.screenUpdateFlags(Update::SinglePar);
1003         InsetMathNest::edit(cur, front, entry_from);
1004 }
1005
1006
1007 Inset * InsetMathMacro::editXY(Cursor & cur, int x, int y)
1008 {
1009         // We may have 0 arguments, but InsetMathNest requires at least one.
1010         if (nargs() > 0) {
1011                 cur.screenUpdateFlags(Update::SinglePar);
1012                 return InsetMathNest::editXY(cur, x, y);
1013         } else
1014                 return this;
1015 }
1016
1017
1018 void InsetMathMacro::removeArgument(pos_type pos) {
1019         if (d->displayMode_ == DISPLAY_NORMAL) {
1020                 LASSERT(size_t(pos) < cells_.size(), return);
1021                 cells_.erase(cells_.begin() + pos);
1022                 if (size_t(pos) < d->attachedArgsNum_)
1023                         --d->attachedArgsNum_;
1024                 if (size_t(pos) < d->optionals_) {
1025                         --d->optionals_;
1026                 }
1027
1028                 d->needsUpdate_ = true;
1029         }
1030 }
1031
1032
1033 void InsetMathMacro::insertArgument(pos_type pos) {
1034         if (d->displayMode_ == DISPLAY_NORMAL) {
1035                 LASSERT(size_t(pos) <= cells_.size(), return);
1036                 cells_.insert(cells_.begin() + pos, MathData());
1037                 if (size_t(pos) < d->attachedArgsNum_)
1038                         ++d->attachedArgsNum_;
1039                 if (size_t(pos) < d->optionals_)
1040                         ++d->optionals_;
1041
1042                 d->needsUpdate_ = true;
1043         }
1044 }
1045
1046
1047 void InsetMathMacro::detachArguments(vector<MathData> & args, bool strip)
1048 {
1049         LASSERT(d->displayMode_ == DISPLAY_NORMAL, return);
1050         args = cells_;
1051
1052         // strip off empty cells, but not more than arity-attachedArgsNum_
1053         if (strip) {
1054                 size_t i;
1055                 for (i = cells_.size(); i > d->attachedArgsNum_; --i)
1056                         if (!cell(i - 1).empty()) break;
1057                 args.resize(i);
1058         }
1059
1060         d->attachedArgsNum_ = 0;
1061         d->expanded_ = MathData();
1062         cells_.resize(0);
1063
1064         d->needsUpdate_ = true;
1065 }
1066
1067
1068 void InsetMathMacro::attachArguments(vector<MathData> const & args, size_t arity, int optionals)
1069 {
1070         LASSERT(d->displayMode_ == DISPLAY_NORMAL, return);
1071         cells_ = args;
1072         d->attachedArgsNum_ = args.size();
1073         cells_.resize(arity);
1074         d->expanded_ = MathData();
1075         d->optionals_ = optionals;
1076
1077         d->needsUpdate_ = true;
1078 }
1079
1080
1081 bool InsetMathMacro::idxFirst(Cursor & cur) const
1082 {
1083         cur.screenUpdateFlags(Update::SinglePar);
1084         return InsetMathNest::idxFirst(cur);
1085 }
1086
1087
1088 bool InsetMathMacro::idxLast(Cursor & cur) const
1089 {
1090         cur.screenUpdateFlags(Update::SinglePar);
1091         return InsetMathNest::idxLast(cur);
1092 }
1093
1094
1095 bool InsetMathMacro::notifyCursorLeaves(Cursor const & old, Cursor & cur)
1096 {
1097         if (d->displayMode_ == DISPLAY_UNFOLDED) {
1098                 docstring const & unfolded_name = name();
1099                 if (unfolded_name != d->name_) {
1100                         // The macro name was changed
1101                         Cursor inset_cursor = old;
1102                         int macroSlice = inset_cursor.find(this);
1103                         // returning true means the cursor is "now" invalid,
1104                         // which it was.
1105                         LASSERT(macroSlice != -1, return true);
1106                         inset_cursor.cutOff(macroSlice);
1107                         inset_cursor.recordUndoInset();
1108                         inset_cursor.pop();
1109                         inset_cursor.cell().erase(inset_cursor.pos());
1110                         inset_cursor.cell().insert(inset_cursor.pos(),
1111                                 createInsetMath(unfolded_name, cur.buffer()));
1112                         cur.resetAnchor();
1113                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
1114                         return true;
1115                 }
1116         }
1117         cur.screenUpdateFlags(Update::Force);
1118         return InsetMathNest::notifyCursorLeaves(old, cur);
1119 }
1120
1121
1122 void InsetMathMacro::fold(Cursor & cur)
1123 {
1124         if (!d->nextFoldMode_) {
1125                 d->nextFoldMode_ = true;
1126                 cur.screenUpdateFlags(Update::SinglePar);
1127         }
1128 }
1129
1130
1131 void InsetMathMacro::unfold(Cursor & cur)
1132 {
1133         if (d->nextFoldMode_) {
1134                 d->nextFoldMode_ = false;
1135                 cur.screenUpdateFlags(Update::SinglePar);
1136         }
1137 }
1138
1139
1140 bool InsetMathMacro::folded() const
1141 {
1142         return d->nextFoldMode_;
1143 }
1144
1145
1146 void InsetMathMacro::write(TeXMathStream & os) const
1147 {
1148         mode_type mode = currentMode();
1149         MathEnsurer ensurer(os, mode == MATH_MODE, true, mode == TEXT_MODE);
1150
1151         // Check if the macro name is encodable. Otherwise we might crash (#11855).
1152         docstring const name_in = name();
1153         docstring name_recoded;
1154         docstring uncodable;
1155         for (char_type c : name_in) {
1156                 if (!os.encoding()) {
1157                         name_recoded += c;
1158                         continue;
1159                 }
1160                 if (os.encoding()->encodable(c) || os.output() == TeXMathStream::wsSearchAdv)
1161                         name_recoded += c;
1162                 else {
1163                         switch (os.output()) {
1164                         case TeXMathStream::wsDryrun: {
1165                                 os << "<" << _("LyX Warning: ")
1166                                    << _("uncodable character") << " '";
1167                                 os << docstring(1, c);
1168                                 os << "'>";
1169                                 break;
1170                         }
1171                         case TeXMathStream::wsPreview: {
1172                                 // indicate the encoding error by a boxed '?'
1173                                 os << "{\\fboxsep=1pt\\fbox{?}}";
1174                                 LYXERR0("Uncodable character" << " '"
1175                                         << docstring(1, c)
1176                                         << "'");
1177                                 break;
1178                         }
1179                         case TeXMathStream::wsDefault:
1180                         default:
1181                                 // record for error message
1182                                 uncodable += c;
1183                                 break;
1184                         }
1185                 }
1186         }
1187         
1188         if (!uncodable.empty()) {
1189                 frontend::Alert::warning(
1190                         _("Uncodable characters in math macro"),
1191                         support::bformat(_("The macro name '%1$s' contains a character\n"
1192                                            "that is not encodable in the current encoding (%2$s).\n"
1193                                            "Please fix this macro."), name_in, uncodable));
1194         }
1195
1196         // non-normal mode
1197         if (d->displayMode_ != DISPLAY_NORMAL) {
1198                 os << "\\" << name_recoded;
1199                 if (name_recoded.size() != 1 || isAlphaASCII(name_recoded[0]))
1200                         os.pendingSpace(true);
1201                 return;
1202         }
1203
1204         // normal mode
1205         // we should be ok to continue even if this fails.
1206         LATTEST(d->macro_);
1207
1208         // Always protect macros in a fragile environment
1209         if (os.fragile())
1210                 os << "\\protect";
1211
1212         os << "\\" << name_recoded;
1213         bool first = true;
1214
1215         // Optional arguments:
1216         // First find last non-empty optional argument
1217         idx_type emptyOptFrom = 0;
1218         idx_type i = 0;
1219         for (; i < cells_.size() && i < d->optionals_; ++i) {
1220                 if (!cell(i).empty())
1221                         emptyOptFrom = i + 1;
1222         }
1223
1224         // print out optionals
1225         for (i=0; i < cells_.size() && i < emptyOptFrom; ++i) {
1226                 first = false;
1227                 // For correctly parsing it when a document is reloaded, we
1228                 // need to enclose an optional argument in braces if it starts
1229                 // with a script inset with empty nucleus or ends with a
1230                 // delimiter-size-modifier macro (see #10497 and #11346).
1231                 // We also need to do that when the optional argument
1232                 // contains macros with optionals.
1233                 bool braced = false;
1234                 size_type last = cell(i).size() - 1;
1235                 if (!cell(i).empty() && cell(i)[last]->asUnknownInset()) {
1236                         latexkeys const * l = in_word_set(cell(i)[last]->name());
1237                         braced = (l && l->inset == "big");
1238                 } else if (!cell(i).empty() && cell(i)[0]->asScriptInset()) {
1239                         braced = cell(i)[0]->asScriptInset()->nuc().empty();
1240                 } else {
1241                         for (size_type j = 0; j < cell(i).size(); ++j) {
1242                                 InsetMathMacro const * ma = cell(i)[j]->asMacro();
1243                                 if (ma && ma->optionals()) {
1244                                         braced = true;
1245                                         break;
1246                                 }
1247                         }
1248                 }
1249                 if (braced)
1250                         os << "[{" << cell(i) << "}]";
1251                 else
1252                         os << "[" << cell(i) << "]";
1253         }
1254
1255         // skip the tailing empty optionals
1256         i = d->optionals_;
1257
1258         // Print remaining arguments
1259         for (; i < cells_.size(); ++i) {
1260                 if (cell(i).size() == 1
1261                         && cell(i)[0].nucleus()->asCharInset()
1262                         && isASCII(cell(i)[0].nucleus()->asCharInset()->getChar())) {
1263                         if (first)
1264                                 os << " ";
1265                         os << cell(i);
1266                 } else
1267                         os << "{" << cell(i) << "}";
1268                 first = false;
1269         }
1270
1271         // add space if there was no argument
1272         // or add braces if we have optionals but none are present and [ follows
1273         if (first) {
1274                 os.pendingSpace(true);
1275                 os.useBraces(d->optionals_ > 0);
1276         }
1277
1278         // write \(no)limits modifiers if relevant
1279         writeLimits(os);
1280 }
1281
1282
1283 void InsetMathMacro::maple(MapleStream & os) const
1284 {
1285         lyx::maple(d->expanded_, os);
1286 }
1287
1288
1289 void InsetMathMacro::maxima(MaximaStream & os) const
1290 {
1291         lyx::maxima(d->expanded_, os);
1292 }
1293
1294
1295 void InsetMathMacro::mathematica(MathematicaStream & os) const
1296 {
1297         lyx::mathematica(d->expanded_, os);
1298 }
1299
1300
1301 void InsetMathMacro::mathmlize(MathMLStream & ms) const
1302 {
1303         // macro_ is 0 if this is an unknown macro
1304         LATTEST(d->macro_ || d->displayMode_ != DISPLAY_NORMAL);
1305         if (d->macro_) {
1306                 docstring const xmlname = d->macro_->xmlname();
1307                 if (!xmlname.empty()) {
1308                         char const * type = d->macro_->MathMLtype();
1309                         ms << "<" << from_ascii(ms.namespacedTag(type)) << ">"
1310                            << xmlname
1311                            << "</" << from_ascii(ms.namespacedTag(type)) << ">";
1312                         return;
1313                 }
1314         }
1315         if (d->expanded_.empty()) {
1316                 // this means that we do not recognize the macro
1317                 throw MathExportException();
1318         }
1319         ms << d->expanded_;
1320 }
1321
1322
1323 void InsetMathMacro::htmlize(HtmlStream & os) const
1324 {
1325         // macro_ is 0 if this is an unknown macro
1326         LATTEST(d->macro_ || d->displayMode_ != DISPLAY_NORMAL);
1327         if (d->macro_) {
1328                 docstring const xmlname = d->macro_->xmlname();
1329                 if (!xmlname.empty()) {
1330                         os << ' ' << xmlname << ' ';
1331                         return;
1332                 }
1333         }
1334         if (d->expanded_.empty()) {
1335                 // this means that we do not recognize the macro
1336                 throw MathExportException();
1337         }
1338         os << d->expanded_;
1339 }
1340
1341
1342 void InsetMathMacro::octave(OctaveStream & os) const
1343 {
1344         lyx::octave(d->expanded_, os);
1345 }
1346
1347
1348 void InsetMathMacro::infoize(odocstream & os) const
1349 {
1350         os << bformat(_("Macro: %1$s"), name());
1351 }
1352
1353
1354 void InsetMathMacro::infoize2(odocstream & os) const
1355 {
1356         os << bformat(_("Macro: %1$s"), name());
1357 }
1358
1359
1360 bool InsetMathMacro::completionSupported(Cursor const & cur) const
1361 {
1362         if (cur.buffer()->isReadonly())
1363                 return false;
1364
1365         if (displayMode() != DISPLAY_UNFOLDED)
1366                 return InsetMathNest::completionSupported(cur);
1367
1368         return lyxrc.completion_popup_math
1369                 && cur.bv().cursor().pos() == int(name().size());
1370 }
1371
1372
1373 bool InsetMathMacro::inlineCompletionSupported(Cursor const & cur) const
1374 {
1375         if (cur.buffer()->isReadonly())
1376                 return false;
1377
1378         if (displayMode() != DISPLAY_UNFOLDED)
1379                 return InsetMathNest::inlineCompletionSupported(cur);
1380
1381         return lyxrc.completion_inline_math
1382                 && cur.bv().cursor().pos() == int(name().size());
1383 }
1384
1385
1386 bool InsetMathMacro::automaticInlineCompletion() const
1387 {
1388         if (displayMode() != DISPLAY_UNFOLDED)
1389                 return InsetMathNest::automaticInlineCompletion();
1390
1391         return lyxrc.completion_inline_math;
1392 }
1393
1394
1395 bool InsetMathMacro::automaticPopupCompletion() const
1396 {
1397         if (displayMode() != DISPLAY_UNFOLDED)
1398                 return InsetMathNest::automaticPopupCompletion();
1399
1400         return lyxrc.completion_popup_math;
1401 }
1402
1403
1404 CompletionList const *
1405 InsetMathMacro::createCompletionList(Cursor const & cur) const
1406 {
1407         if (displayMode() != DISPLAY_UNFOLDED)
1408                 return InsetMathNest::createCompletionList(cur);
1409
1410         return new MathCompletionList(cur.bv().cursor());
1411 }
1412
1413
1414 docstring InsetMathMacro::completionPrefix(Cursor const & cur) const
1415 {
1416         if (displayMode() != DISPLAY_UNFOLDED)
1417                 return InsetMathNest::completionPrefix(cur);
1418
1419         return "\\" + name();
1420 }
1421
1422
1423 bool InsetMathMacro::insertCompletion(Cursor & cur, docstring const & s, bool finished)
1424 {
1425         if (cur.buffer()->isReadonly())
1426                 return false;
1427
1428         if (displayMode() != DISPLAY_UNFOLDED)
1429                 return InsetMathNest::insertCompletion(cur, s, finished);
1430
1431         if (!completionSupported(cur))
1432                 return false;
1433
1434         // Contrary to Text, the whole inset should be recorded (#12581).
1435         cur.recordUndoInset();
1436
1437         // append completion
1438         docstring newName = name() + s;
1439         asArray(newName, cell(0));
1440         cur.bv().cursor().pos() = name().size();
1441         cur.screenUpdateFlags(Update::SinglePar);
1442
1443         // finish macro
1444         if (finished) {
1445                 cur.bv().cursor().pop();
1446                 ++cur.bv().cursor().pos();
1447                 cur.screenUpdateFlags(Update::SinglePar);
1448         }
1449
1450         return true;
1451 }
1452
1453
1454 void InsetMathMacro::completionPosAndDim(Cursor const & cur, int & x, int & y,
1455         Dimension & dim) const
1456 {
1457         if (displayMode() != DISPLAY_UNFOLDED)
1458                 InsetMathNest::completionPosAndDim(cur, x, y, dim);
1459
1460         // get inset dimensions
1461         dim = cur.bv().coordCache().insets().dim(this);
1462         // FIXME: these 3 are no accurate, but should depend on the font.
1463         // Now the popup jumps down if you enter a char with descent > 0.
1464         dim.des += 3;
1465         dim.asc += 3;
1466
1467         // and position
1468         Point xy
1469         = cur.bv().coordCache().insets().xy(this);
1470         x = xy.x_;
1471         y = xy.y_;
1472 }
1473
1474
1475 void InsetMathMacro::setBuffer(Buffer & buffer)
1476 {
1477         d->definition_.setBuffer(buffer);
1478         InsetMathNest::setBuffer(buffer);
1479 }
1480
1481 } // namespace lyx