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