]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathMacro.cpp
Revert "Fixup 0cbe0d7a: avoid double redraw when completion is finished"
[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                                 // Avoid recursion on a recursive macro definition
992                                 docstring const & def = data->definition();
993                                 int pos = tokenPos(def, '\\', name());
994                                 char_type c = pos + name().size() < def.size()
995                                               ? def.at(pos + name().size()) : 0;
996                                 if (pos < 0 || (name().size() > 1 &&
997                                                 ((c >= 'a' && c <= 'z') ||
998                                                  (c >= 'A' && c <= 'Z')))) {
999                                         asArray(def, ar);
1000                                         ar.validate(features);
1001                                 }
1002                         }
1003                 }
1004         }
1005         InsetMathNest::validate(features);
1006 }
1007
1008
1009 void InsetMathMacro::edit(Cursor & cur, bool front, EntryDirection entry_from)
1010 {
1011         cur.screenUpdateFlags(Update::SinglePar);
1012         InsetMathNest::edit(cur, front, entry_from);
1013 }
1014
1015
1016 Inset * InsetMathMacro::editXY(Cursor & cur, int x, int y)
1017 {
1018         // We may have 0 arguments, but InsetMathNest requires at least one.
1019         if (nargs() > 0) {
1020                 cur.screenUpdateFlags(Update::SinglePar);
1021                 return InsetMathNest::editXY(cur, x, y);
1022         } else
1023                 return this;
1024 }
1025
1026
1027 void InsetMathMacro::removeArgument(pos_type pos) {
1028         if (d->displayMode_ == DISPLAY_NORMAL) {
1029                 LASSERT(size_t(pos) < cells_.size(), return);
1030                 cells_.erase(cells_.begin() + pos);
1031                 if (size_t(pos) < d->attachedArgsNum_)
1032                         --d->attachedArgsNum_;
1033                 if (size_t(pos) < d->optionals_) {
1034                         --d->optionals_;
1035                 }
1036
1037                 d->needsUpdate_ = true;
1038         }
1039 }
1040
1041
1042 void InsetMathMacro::insertArgument(pos_type pos) {
1043         if (d->displayMode_ == DISPLAY_NORMAL) {
1044                 LASSERT(size_t(pos) <= cells_.size(), return);
1045                 cells_.insert(cells_.begin() + pos, MathData());
1046                 if (size_t(pos) < d->attachedArgsNum_)
1047                         ++d->attachedArgsNum_;
1048                 if (size_t(pos) < d->optionals_)
1049                         ++d->optionals_;
1050
1051                 d->needsUpdate_ = true;
1052         }
1053 }
1054
1055
1056 void InsetMathMacro::detachArguments(vector<MathData> & args, bool strip)
1057 {
1058         LASSERT(d->displayMode_ == DISPLAY_NORMAL, return);
1059         args = cells_;
1060
1061         // strip off empty cells, but not more than arity-attachedArgsNum_
1062         if (strip) {
1063                 size_t i;
1064                 for (i = cells_.size(); i > d->attachedArgsNum_; --i)
1065                         if (!cell(i - 1).empty()) break;
1066                 args.resize(i);
1067         }
1068
1069         d->attachedArgsNum_ = 0;
1070         d->expanded_ = MathData();
1071         cells_.resize(0);
1072
1073         d->needsUpdate_ = true;
1074 }
1075
1076
1077 void InsetMathMacro::attachArguments(vector<MathData> const & args, size_t arity, int optionals)
1078 {
1079         LASSERT(d->displayMode_ == DISPLAY_NORMAL, return);
1080         cells_ = args;
1081         d->attachedArgsNum_ = args.size();
1082         cells_.resize(arity);
1083         d->expanded_ = MathData();
1084         d->optionals_ = optionals;
1085
1086         d->needsUpdate_ = true;
1087 }
1088
1089
1090 bool InsetMathMacro::idxFirst(Cursor & cur) const
1091 {
1092         cur.screenUpdateFlags(Update::SinglePar);
1093         return InsetMathNest::idxFirst(cur);
1094 }
1095
1096
1097 bool InsetMathMacro::idxLast(Cursor & cur) const
1098 {
1099         cur.screenUpdateFlags(Update::SinglePar);
1100         return InsetMathNest::idxLast(cur);
1101 }
1102
1103
1104 bool InsetMathMacro::notifyCursorLeaves(Cursor const & old, Cursor & cur)
1105 {
1106         if (d->displayMode_ == DISPLAY_UNFOLDED) {
1107                 docstring const & unfolded_name = name();
1108                 if (unfolded_name != d->name_) {
1109                         // The macro name was changed
1110                         Cursor inset_cursor = old;
1111                         int macroSlice = inset_cursor.find(this);
1112                         // returning true means the cursor is "now" invalid,
1113                         // which it was.
1114                         LASSERT(macroSlice != -1, return true);
1115                         inset_cursor.cutOff(macroSlice);
1116                         inset_cursor.recordUndoInset();
1117                         inset_cursor.pop();
1118                         inset_cursor.cell().erase(inset_cursor.pos());
1119                         inset_cursor.cell().insert(inset_cursor.pos(),
1120                                 createInsetMath(unfolded_name, cur.buffer()));
1121                         cur.resetAnchor();
1122                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
1123                         return true;
1124                 }
1125         }
1126         cur.screenUpdateFlags(Update::Force);
1127         return InsetMathNest::notifyCursorLeaves(old, cur);
1128 }
1129
1130
1131 void InsetMathMacro::fold(Cursor & cur)
1132 {
1133         if (!d->nextFoldMode_) {
1134                 d->nextFoldMode_ = true;
1135                 cur.screenUpdateFlags(Update::SinglePar);
1136         }
1137 }
1138
1139
1140 void InsetMathMacro::unfold(Cursor & cur)
1141 {
1142         if (d->nextFoldMode_) {
1143                 d->nextFoldMode_ = false;
1144                 cur.screenUpdateFlags(Update::SinglePar);
1145         }
1146 }
1147
1148
1149 bool InsetMathMacro::folded() const
1150 {
1151         return d->nextFoldMode_;
1152 }
1153
1154
1155 void InsetMathMacro::write(TeXMathStream & os) const
1156 {
1157         mode_type mode = currentMode();
1158         MathEnsurer ensurer(os, mode == MATH_MODE, true, mode == TEXT_MODE);
1159
1160         // Check if the macro name is encodable. Otherwise we might crash (#11855).
1161         docstring const name_in = name();
1162         docstring name_recoded;
1163         docstring uncodable;
1164         for (char_type c : name_in) {
1165                 if (!os.encoding()) {
1166                         name_recoded += c;
1167                         continue;
1168                 }
1169                 if (os.encoding()->encodable(c) || os.output() == TeXMathStream::wsSearchAdv)
1170                         name_recoded += c;
1171                 else {
1172                         switch (os.output()) {
1173                         case TeXMathStream::wsDryrun: {
1174                                 os << "<" << _("LyX Warning: ")
1175                                    << _("uncodable character") << " '";
1176                                 os << docstring(1, c);
1177                                 os << "'>";
1178                                 break;
1179                         }
1180                         case TeXMathStream::wsPreview: {
1181                                 // indicate the encoding error by a boxed '?'
1182                                 os << "{\\fboxsep=1pt\\fbox{?}}";
1183                                 LYXERR0("Uncodable character" << " '"
1184                                         << docstring(1, c)
1185                                         << "'");
1186                                 break;
1187                         }
1188                         case TeXMathStream::wsDefault:
1189                         default:
1190                                 // record for error message
1191                                 uncodable += c;
1192                                 break;
1193                         }
1194                 }
1195         }
1196         
1197         if (!uncodable.empty()) {
1198                 frontend::Alert::warning(
1199                         _("Uncodable characters in math macro"),
1200                         support::bformat(_("The macro name '%1$s' contains a character\n"
1201                                            "that is not encodable in the current encoding (%2$s).\n"
1202                                            "Please fix this macro."), name_in, uncodable));
1203         }
1204
1205         // non-normal mode
1206         if (d->displayMode_ != DISPLAY_NORMAL) {
1207                 os << "\\" << name_recoded;
1208                 if (name_recoded.size() != 1 || isAlphaASCII(name_recoded[0]))
1209                         os.pendingSpace(true);
1210                 return;
1211         }
1212
1213         // normal mode
1214         // we should be ok to continue even if this fails.
1215         LATTEST(d->macro_);
1216
1217         // Always protect macros in a fragile environment
1218         if (os.fragile())
1219                 os << "\\protect";
1220
1221         os << "\\" << name_recoded;
1222         bool first = true;
1223
1224         // Optional arguments:
1225         // First find last non-empty optional argument
1226         idx_type emptyOptFrom = 0;
1227         idx_type i = 0;
1228         for (; i < cells_.size() && i < d->optionals_; ++i) {
1229                 if (!cell(i).empty())
1230                         emptyOptFrom = i + 1;
1231         }
1232
1233         // print out optionals
1234         for (i=0; i < cells_.size() && i < emptyOptFrom; ++i) {
1235                 first = false;
1236                 // For correctly parsing it when a document is reloaded, we
1237                 // need to enclose an optional argument in braces if it starts
1238                 // with a script inset with empty nucleus or ends with a
1239                 // delimiter-size-modifier macro (see #10497 and #11346).
1240                 // We also need to do that when the optional argument
1241                 // contains macros with optionals.
1242                 bool braced = false;
1243                 size_type last = cell(i).size() - 1;
1244                 if (!cell(i).empty() && cell(i)[last]->asUnknownInset()) {
1245                         latexkeys const * l = in_word_set(cell(i)[last]->name());
1246                         braced = (l && l->inset == "big");
1247                 } else if (!cell(i).empty() && cell(i)[0]->asScriptInset()) {
1248                         braced = cell(i)[0]->asScriptInset()->nuc().empty();
1249                 } else {
1250                         for (size_type j = 0; j < cell(i).size(); ++j) {
1251                                 InsetMathMacro const * ma = cell(i)[j]->asMacro();
1252                                 if (ma && ma->optionals()) {
1253                                         braced = true;
1254                                         break;
1255                                 }
1256                         }
1257                 }
1258                 if (braced)
1259                         os << "[{" << cell(i) << "}]";
1260                 else
1261                         os << "[" << cell(i) << "]";
1262         }
1263
1264         // skip the tailing empty optionals
1265         i = d->optionals_;
1266
1267         // Print remaining arguments
1268         for (; i < cells_.size(); ++i) {
1269                 if (cell(i).size() == 1
1270                         && cell(i)[0].nucleus()->asCharInset()
1271                         && isASCII(cell(i)[0].nucleus()->asCharInset()->getChar())) {
1272                         if (first)
1273                                 os << " ";
1274                         os << cell(i);
1275                 } else
1276                         os << "{" << cell(i) << "}";
1277                 first = false;
1278         }
1279
1280         // add space if there was no argument
1281         // or add braces if we have optionals but none are present and [ follows
1282         if (first) {
1283                 os.pendingSpace(true);
1284                 os.useBraces(d->optionals_ > 0);
1285         }
1286
1287         // write \(no)limits modifiers if relevant
1288         writeLimits(os);
1289 }
1290
1291
1292 void InsetMathMacro::maple(MapleStream & os) const
1293 {
1294         lyx::maple(d->expanded_, os);
1295 }
1296
1297
1298 void InsetMathMacro::maxima(MaximaStream & os) const
1299 {
1300         lyx::maxima(d->expanded_, os);
1301 }
1302
1303
1304 void InsetMathMacro::mathematica(MathematicaStream & os) const
1305 {
1306         lyx::mathematica(d->expanded_, os);
1307 }
1308
1309
1310 void InsetMathMacro::mathmlize(MathMLStream & ms) const
1311 {
1312         // macro_ is 0 if this is an unknown macro
1313         LATTEST(d->macro_ || d->displayMode_ != DISPLAY_NORMAL);
1314         if (d->macro_) {
1315                 docstring const xmlname = d->macro_->xmlname();
1316                 if (!xmlname.empty()) {
1317                         char const * type = d->macro_->MathMLtype();
1318                         ms << MTagInline(type) << xmlname << ETagInline(type);
1319                         return;
1320                 }
1321         }
1322         if (d->expanded_.empty()) {
1323                 // this means that we do not recognize the macro
1324                 throw MathExportException();
1325         }
1326         ms << d->expanded_;
1327 }
1328
1329
1330 void InsetMathMacro::htmlize(HtmlStream & os) const
1331 {
1332         // macro_ is 0 if this is an unknown macro
1333         LATTEST(d->macro_ || d->displayMode_ != DISPLAY_NORMAL);
1334         if (d->macro_) {
1335                 docstring const xmlname = d->macro_->xmlname();
1336                 if (!xmlname.empty()) {
1337                         os << ' ' << xmlname << ' ';
1338                         return;
1339                 }
1340         }
1341         if (d->expanded_.empty()) {
1342                 // this means that we do not recognize the macro
1343                 throw MathExportException();
1344         }
1345         os << d->expanded_;
1346 }
1347
1348
1349 void InsetMathMacro::octave(OctaveStream & os) const
1350 {
1351         lyx::octave(d->expanded_, os);
1352 }
1353
1354
1355 void InsetMathMacro::infoize(odocstream & os) const
1356 {
1357         os << bformat(_("Macro: %1$s"), name());
1358 }
1359
1360
1361 void InsetMathMacro::infoize2(odocstream & os) const
1362 {
1363         os << bformat(_("Macro: %1$s"), name());
1364 }
1365
1366
1367 bool InsetMathMacro::completionSupported(Cursor const & cur) const
1368 {
1369         if (cur.buffer()->isReadonly())
1370                 return false;
1371
1372         if (displayMode() != DISPLAY_UNFOLDED)
1373                 return InsetMathNest::completionSupported(cur);
1374
1375         return lyxrc.completion_popup_math
1376                 && cur.bv().cursor().pos() == int(name().size());
1377 }
1378
1379
1380 bool InsetMathMacro::inlineCompletionSupported(Cursor const & cur) const
1381 {
1382         if (cur.buffer()->isReadonly())
1383                 return false;
1384
1385         if (displayMode() != DISPLAY_UNFOLDED)
1386                 return InsetMathNest::inlineCompletionSupported(cur);
1387
1388         return lyxrc.completion_inline_math
1389                 && cur.bv().cursor().pos() == int(name().size());
1390 }
1391
1392
1393 bool InsetMathMacro::automaticInlineCompletion() const
1394 {
1395         if (displayMode() != DISPLAY_UNFOLDED)
1396                 return InsetMathNest::automaticInlineCompletion();
1397
1398         return lyxrc.completion_inline_math;
1399 }
1400
1401
1402 bool InsetMathMacro::automaticPopupCompletion() const
1403 {
1404         if (displayMode() != DISPLAY_UNFOLDED)
1405                 return InsetMathNest::automaticPopupCompletion();
1406
1407         return lyxrc.completion_popup_math;
1408 }
1409
1410
1411 CompletionList const *
1412 InsetMathMacro::createCompletionList(Cursor const & cur) const
1413 {
1414         if (displayMode() != DISPLAY_UNFOLDED)
1415                 return InsetMathNest::createCompletionList(cur);
1416
1417         return new MathCompletionList(cur.bv().cursor());
1418 }
1419
1420
1421 docstring InsetMathMacro::completionPrefix(Cursor const & cur) const
1422 {
1423         if (displayMode() != DISPLAY_UNFOLDED)
1424                 return InsetMathNest::completionPrefix(cur);
1425
1426         return "\\" + name();
1427 }
1428
1429
1430 bool InsetMathMacro::insertCompletion(Cursor & cur, docstring const & s, bool finished)
1431 {
1432         if (cur.buffer()->isReadonly())
1433                 return false;
1434
1435         if (displayMode() != DISPLAY_UNFOLDED)
1436                 return InsetMathNest::insertCompletion(cur, s, finished);
1437
1438         if (!completionSupported(cur))
1439                 return false;
1440
1441         // Contrary to Text, the whole inset should be recorded (#12581).
1442         cur.recordUndoInset();
1443
1444         // append completion
1445         docstring newName = name() + s;
1446         asArray(newName, cell(0));
1447         cur.bv().cursor().pos() = name().size();
1448         cur.screenUpdateFlags(Update::SinglePar);
1449
1450         // finish macro
1451         if (finished) {
1452                 cur.bv().cursor().pop();
1453                 ++cur.bv().cursor().pos();
1454                 cur.screenUpdateFlags(Update::SinglePar);
1455         }
1456
1457         return true;
1458 }
1459
1460
1461 void InsetMathMacro::completionPosAndDim(Cursor const & cur, int & x, int & y,
1462         Dimension & dim) const
1463 {
1464         if (displayMode() != DISPLAY_UNFOLDED)
1465                 InsetMathNest::completionPosAndDim(cur, x, y, dim);
1466
1467         // get inset dimensions
1468         dim = cur.bv().coordCache().insets().dim(this);
1469         // FIXME: these 3 are no accurate, but should depend on the font.
1470         // Now the popup jumps down if you enter a char with descent > 0.
1471         dim.des += 3;
1472         dim.asc += 3;
1473
1474         // and position
1475         Point xy
1476         = cur.bv().coordCache().insets().xy(this);
1477         x = xy.x_;
1478         y = xy.y_;
1479 }
1480
1481
1482 void InsetMathMacro::setBuffer(Buffer & buffer)
1483 {
1484         d->definition_.setBuffer(buffer);
1485         InsetMathNest::setBuffer(buffer);
1486 }
1487
1488 } // namespace lyx