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