3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Alfredo Braunstein
9 * \author Angus Leeming
10 * \author Asger Alstrup
12 * \author Dov Feldstern
13 * \author Jean-Marc Lasgouttes
15 * \author Jürgen Vigna
16 * \author Lars Gullik Bjønnes
17 * \author Stefan Schimanski
19 * Full author contact details are available in file CREDITS.
27 #include "BranchList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
32 #include "CompletionList.h"
34 #include "CursorSlice.h"
35 #include "CutAndPaste.h"
36 #include "DispatchResult.h"
38 #include "ErrorList.h"
40 #include "FloatList.h"
42 #include "FuncRequest.h"
43 #include "FuncStatus.h"
44 #include "InsetList.h"
49 #include "LyXAction.h"
52 #include "Paragraph.h"
53 #include "ParagraphParameters.h"
54 #include "SpellChecker.h"
55 #include "TextClass.h"
56 #include "TextMetrics.h"
57 #include "Thesaurus.h"
58 #include "WordLangTuple.h"
61 #include "frontends/alert.h"
62 #include "frontends/Application.h"
63 #include "frontends/Clipboard.h"
64 #include "frontends/Selection.h"
66 #include "mathed/InsetMathHull.h"
67 #include "mathed/InsetMathMacroTemplate.h"
69 #include "insets/Inset.h"
70 #include "insets/InsetArgument.h"
71 #include "insets/InsetCaption.h"
72 #include "insets/InsetCollapsible.h"
73 #include "insets/InsetCommand.h"
74 #include "insets/InsetExternal.h"
75 #include "insets/InsetFloat.h"
76 #include "insets/InsetFloatList.h"
77 #include "insets/InsetGraphics.h"
78 #include "insets/InsetGraphicsParams.h"
79 #include "insets/InsetIndexMacro.h"
80 #include "insets/InsetInfo.h"
81 #include "insets/InsetIPAMacro.h"
82 #include "insets/InsetNewline.h"
83 #include "insets/InsetQuotes.h"
84 #include "insets/InsetSpecialChar.h"
85 #include "insets/InsetTabular.h"
86 #include "insets/InsetText.h"
87 #include "insets/InsetWrap.h"
89 #include "support/convert.h"
90 #include "support/debug.h"
91 #include "support/docstream.h"
92 #include "support/docstring.h"
93 #include "support/docstring_list.h"
94 #include "support/filetools.h"
95 #include "support/gettext.h"
96 #include "support/lassert.h"
97 #include "support/Lexer.h"
98 #include "support/limited_stack.h"
99 #include "support/lstrings.h"
100 #include "support/lyxtime.h"
101 #include "support/textutils.h"
102 #include "support/unique_ptr.h"
111 using namespace support;
113 using frontend::Clipboard;
118 bool moveItem(Paragraph & fromPar, pos_type fromPos,
119 Paragraph & toPar, pos_type toPos, BufferParams const & params)
121 // Note: moveItem() does not honour change tracking!
122 // Therefore, it should only be used for breaking and merging paragraphs
124 // We need a copy here because the character at fromPos is going to be erased.
125 Font const tmpFont = fromPar.getFontSettings(params, fromPos);
126 Change const tmpChange = fromPar.lookupChange(fromPos);
128 if (Inset * tmpInset = fromPar.getInset(fromPos)) {
129 fromPar.releaseInset(fromPos);
130 // The inset is not in fromPar any more.
131 if (!toPar.insertInset(toPos, tmpInset, tmpFont, tmpChange)) {
138 char_type const tmpChar = fromPar.getChar(fromPos);
139 fromPar.eraseChar(fromPos, false);
140 toPar.insertChar(toPos, tmpChar, tmpFont, tmpChange);
148 void breakParagraphConservative(BufferParams const & bparams,
149 ParagraphList & pars, pit_type pit, pos_type pos)
151 // create a new paragraph
152 Paragraph & tmp = *pars.insert(pars.iterator_at(pit + 1), Paragraph());
153 Paragraph & par = pars[pit];
155 tmp.setInsetOwner(&par.inInset());
156 tmp.makeSameLayout(par);
158 LASSERT(pos <= par.size(), return);
160 if (pos < par.size()) {
161 // move everything behind the break position to the new paragraph
162 pos_type pos_end = par.size() - 1;
164 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
165 if (moveItem(par, pos, tmp, j, bparams)) {
169 // Move over the end-of-par change information
170 tmp.setChange(tmp.size(), par.lookupChange(par.size()));
171 par.setChange(par.size(), Change(bparams.track_changes ?
172 Change::INSERTED : Change::UNCHANGED));
177 void mergeParagraph(BufferParams const & bparams,
178 ParagraphList & pars, pit_type par_offset)
180 Paragraph & next = pars[par_offset + 1];
181 Paragraph & par = pars[par_offset];
183 pos_type pos_end = next.size() - 1;
184 pos_type pos_insert = par.size();
186 // the imaginary end-of-paragraph character (at par.size()) has to be
187 // marked as unmodified. Otherwise, its change is adopted by the first
188 // character of the next paragraph.
189 if (par.isChanged(par.size())) {
190 LYXERR(Debug::CHANGES,
191 "merging par with inserted/deleted end-of-par character");
192 par.setChange(par.size(), Change(Change::UNCHANGED));
195 Change change = next.lookupChange(next.size());
197 // move the content of the second paragraph to the end of the first one
198 for (pos_type i = 0, j = pos_insert; i <= pos_end; ++i) {
199 if (moveItem(next, 0, par, j, bparams)) {
204 // move the change of the end-of-paragraph character
205 par.setChange(par.size(), change);
207 pars.erase(pars.iterator_at(par_offset + 1));
211 Text::Text(InsetText * owner, bool use_default_layout)
214 pars_.push_back(Paragraph());
215 Paragraph & par = pars_.back();
216 par.setInsetOwner(owner);
217 DocumentClass const & dc = owner->buffer().params().documentClass();
218 if (use_default_layout)
219 par.setDefaultLayout(dc);
221 par.setPlainLayout(dc);
225 Text::Text(InsetText * owner, Text const & text)
226 : owner_(owner), pars_(text.pars_)
228 for (auto & p : pars_)
229 p.setInsetOwner(owner);
233 pit_type Text::depthHook(pit_type pit, depth_type depth) const
235 pit_type newpit = pit;
240 while (newpit != 0 && pars_[newpit].getDepth() > depth)
243 if (pars_[newpit].getDepth() > depth)
250 pit_type Text::outerHook(pit_type par_offset) const
252 Paragraph const & par = pars_[par_offset];
254 if (par.getDepth() == 0)
256 return depthHook(par_offset, par.getDepth() - 1);
260 bool Text::isFirstInSequence(pit_type par_offset) const
262 Paragraph const & par = pars_[par_offset];
264 pit_type dhook_offset = depthHook(par_offset, par.getDepth());
266 if (dhook_offset == par_offset)
269 Paragraph const & dhook = pars_[dhook_offset];
271 return dhook.layout() != par.layout()
272 || dhook.getDepth() != par.getDepth();
276 pit_type Text::lastInSequence(pit_type pit) const
278 depth_type const depth = pars_[pit].getDepth();
279 pit_type newpit = pit;
281 while (size_t(newpit + 1) < pars_.size() &&
282 (pars_[newpit + 1].getDepth() > depth ||
283 (pars_[newpit + 1].getDepth() == depth &&
284 pars_[newpit + 1].layout() == pars_[pit].layout())))
291 int Text::getTocLevel(pit_type par_offset) const
293 Paragraph const & par = pars_[par_offset];
295 if (par.layout().isEnvironment() && !isFirstInSequence(par_offset))
296 return Layout::NOT_IN_TOC;
298 return par.layout().toclevel;
302 Font const Text::outerFont(pit_type par_offset) const
304 depth_type par_depth = pars_[par_offset].getDepth();
305 FontInfo tmpfont = inherit_font;
306 depth_type prev_par_depth = 0;
307 // Resolve against environment font information
308 while (par_offset != pit_type(pars_.size())
309 && par_depth != prev_par_depth
311 && !tmpfont.resolved()) {
312 prev_par_depth = par_depth;
313 par_offset = outerHook(par_offset);
314 if (par_offset != pit_type(pars_.size())) {
315 tmpfont.realize(pars_[par_offset].layout().font);
316 par_depth = pars_[par_offset].getDepth();
320 return Font(tmpfont);
324 int Text::getEndLabel(pit_type p) const
327 depth_type par_depth = pars_[p].getDepth();
328 while (pit != pit_type(pars_.size())) {
329 Layout const & layout = pars_[pit].layout();
330 int const endlabeltype = layout.endlabeltype;
332 if (endlabeltype != END_LABEL_NO_LABEL) {
333 if (p + 1 == pit_type(pars_.size()))
336 depth_type const next_depth =
337 pars_[p + 1].getDepth();
338 if (par_depth > next_depth ||
339 (par_depth == next_depth && layout != pars_[p + 1].layout()))
345 pit = outerHook(pit);
346 if (pit != pit_type(pars_.size()))
347 par_depth = pars_[pit].getDepth();
349 return END_LABEL_NO_LABEL;
353 static void acceptOrRejectChanges(ParagraphList & pars,
354 BufferParams const & bparams, Text::ChangeOp op)
356 pit_type pars_size = static_cast<pit_type>(pars.size());
358 // first, accept or reject changes within each individual
359 // paragraph (do not consider end-of-par)
360 for (pit_type pit = 0; pit < pars_size; ++pit) {
361 // prevent assertion failure
362 if (!pars[pit].empty()) {
363 if (op == Text::ACCEPT)
364 pars[pit].acceptChanges(0, pars[pit].size());
366 pars[pit].rejectChanges(0, pars[pit].size());
370 // next, accept or reject imaginary end-of-par characters
371 for (pit_type pit = 0; pit < pars_size; ++pit) {
372 pos_type pos = pars[pit].size();
373 if (pars[pit].isChanged(pos)) {
374 // keep the end-of-par char if it is inserted and accepted
375 // or when it is deleted and rejected.
376 if (pars[pit].isInserted(pos) == (op == Text::ACCEPT)) {
377 pars[pit].setChange(pos, Change(Change::UNCHANGED));
379 if (pit == pars_size - 1) {
380 // we cannot remove a par break at the end of the last
381 // paragraph; instead, we mark it unchanged
382 pars[pit].setChange(pos, Change(Change::UNCHANGED));
384 mergeParagraph(bparams, pars, pit);
394 void acceptChanges(ParagraphList & pars, BufferParams const & bparams)
396 acceptOrRejectChanges(pars, bparams, Text::ACCEPT);
400 void rejectChanges(ParagraphList & pars, BufferParams const & bparams)
402 acceptOrRejectChanges(pars, bparams, Text::REJECT);
406 InsetText const & Text::inset() const
413 void Text::readParToken(Paragraph & par, Lexer & lex,
414 string const & token, Font & font, Change & change, ErrorList & errorList)
416 Buffer * buf = &owner_->buffer();
417 BufferParams & bp = buf->params();
419 if (token[0] != '\\') {
420 docstring dstr = lex.getDocString();
421 par.appendString(dstr, font, change);
423 } else if (token == "\\begin_layout") {
425 docstring layoutname = lex.getDocString();
427 font = Font(inherit_font, bp.language);
428 change = Change(Change::UNCHANGED);
430 DocumentClass const & tclass = bp.documentClass();
432 if (layoutname.empty())
433 layoutname = tclass.defaultLayoutName();
435 if (owner_->forcePlainLayout()) {
436 // in this case only the empty layout is allowed
437 layoutname = tclass.plainLayoutName();
438 } else if (par.usePlainLayout()) {
439 // in this case, default layout maps to empty layout
440 if (layoutname == tclass.defaultLayoutName())
441 layoutname = tclass.plainLayoutName();
443 // otherwise, the empty layout maps to the default
444 if (layoutname == tclass.plainLayoutName())
445 layoutname = tclass.defaultLayoutName();
448 // When we apply an unknown layout to a document, we add this layout to the textclass
449 // of this document. For example, when you apply class article to a beamer document,
450 // all unknown layouts such as frame will be added to document class article so that
451 // these layouts can keep their original names.
452 bool const added_one = tclass.addLayoutIfNeeded(layoutname);
455 docstring const s = bformat(_("Layout `%1$s' was not found."), layoutname);
456 errorList.push_back(ErrorItem(_("Layout Not Found"), s,
457 {par.id(), 0}, {par.id(), -1}));
460 par.setLayout(bp.documentClass()[layoutname]);
462 // Test whether the layout is obsolete.
463 Layout const & layout = par.layout();
464 if (!layout.obsoleted_by().empty())
465 par.setLayout(bp.documentClass()[layout.obsoleted_by()]);
467 par.params().read(lex);
469 } else if (token == "\\end_layout") {
470 LYXERR0("Solitary \\end_layout in line " << lex.lineNumber() << "\n"
471 << "Missing \\begin_layout ?");
472 } else if (token == "\\end_inset") {
473 LYXERR0("Solitary \\end_inset in line " << lex.lineNumber() << "\n"
474 << "Missing \\begin_inset ?");
475 } else if (token == "\\begin_inset") {
476 Inset * inset = readInset(lex, buf);
478 par.insertInset(par.size(), inset, font, change);
481 docstring line = lex.getDocString();
482 errorList.push_back(ErrorItem(_("Unknown Inset"), line,
483 {par.id(), 0}, {par.id(), -1}));
485 } else if (token == "\\family") {
487 setLyXFamily(lex.getString(), font.fontInfo());
488 } else if (token == "\\series") {
490 setLyXSeries(lex.getString(), font.fontInfo());
491 } else if (token == "\\shape") {
493 setLyXShape(lex.getString(), font.fontInfo());
494 } else if (token == "\\size") {
496 setLyXSize(lex.getString(), font.fontInfo());
497 } else if (token == "\\lang") {
499 string const tok = lex.getString();
500 Language const * lang = languages.getLanguage(tok);
502 font.setLanguage(lang);
504 font.setLanguage(bp.language);
505 lex.printError("Unknown language `$$Token'");
507 } else if (token == "\\numeric") {
509 font.fontInfo().setNumber(setLyXMisc(lex.getString()));
510 } else if (token == "\\nospellcheck") {
512 font.fontInfo().setNoSpellcheck(setLyXMisc(lex.getString()));
513 } else if (token == "\\emph") {
515 font.fontInfo().setEmph(setLyXMisc(lex.getString()));
516 } else if (token == "\\bar") {
518 string const tok = lex.getString();
521 font.fontInfo().setUnderbar(FONT_ON);
522 else if (tok == "no")
523 font.fontInfo().setUnderbar(FONT_OFF);
524 else if (tok == "default")
525 font.fontInfo().setUnderbar(FONT_INHERIT);
527 lex.printError("Unknown bar font flag "
529 } else if (token == "\\strikeout") {
531 font.fontInfo().setStrikeout(setLyXMisc(lex.getString()));
532 } else if (token == "\\xout") {
534 font.fontInfo().setXout(setLyXMisc(lex.getString()));
535 } else if (token == "\\uuline") {
537 font.fontInfo().setUuline(setLyXMisc(lex.getString()));
538 } else if (token == "\\uwave") {
540 font.fontInfo().setUwave(setLyXMisc(lex.getString()));
541 } else if (token == "\\noun") {
543 font.fontInfo().setNoun(setLyXMisc(lex.getString()));
544 } else if (token == "\\color") {
546 setLyXColor(lex.getString(), font.fontInfo());
547 } else if (token == "\\SpecialChar" ||
548 (token == "\\SpecialCharNoPassThru" &&
549 !par.layout().pass_thru && !inset().isPassThru())) {
550 auto inset = make_unique<InsetSpecialChar>();
552 inset->setBuffer(*buf);
553 par.insertInset(par.size(), inset.release(), font, change);
554 } else if (token == "\\SpecialCharNoPassThru") {
556 docstring const s = ltrim(lex.getDocString(), "\\");
557 par.insert(par.size(), s, font, change);
558 } else if (token == "\\IPAChar") {
559 auto inset = make_unique<InsetIPAChar>();
561 inset->setBuffer(*buf);
562 par.insertInset(par.size(), inset.release(), font, change);
563 } else if (token == "\\twohyphens" || token == "\\threehyphens") {
564 // Ideally, this should be done by lyx2lyx, but lyx2lyx does not know the
565 // running font and does not know anything about layouts (and CopyStyle).
566 Layout const & layout(par.layout());
567 FontInfo info = font.fontInfo();
568 info.realize(layout.resfont);
569 if (layout.pass_thru || inset().isPassThru() ||
570 info.family() == TYPEWRITER_FAMILY) {
571 if (token == "\\twohyphens")
572 par.insert(par.size(), from_ascii("--"), font, change);
574 par.insert(par.size(), from_ascii("---"), font, change);
576 if (token == "\\twohyphens")
577 par.insertChar(par.size(), 0x2013, font, change);
579 par.insertChar(par.size(), 0x2014, font, change);
581 } else if (token == "\\backslash") {
582 par.appendChar('\\', font, change);
583 } else if (token == "\\LyXTable") {
584 auto inset = make_unique<InsetTabular>(buf);
586 par.insertInset(par.size(), inset.release(), font, change);
587 } else if (token == "\\change_unchanged") {
588 change = Change(Change::UNCHANGED);
589 } else if (token == "\\change_inserted" || token == "\\change_deleted") {
591 istringstream is(lex.getString());
595 BufferParams::AuthorMap const & am = bp.author_map_;
596 if (am.find(aid) == am.end()) {
597 errorList.push_back(ErrorItem(
598 _("Change tracking author index missing"),
599 bformat(_("A change tracking author information for index "
600 "%1$d is missing. This can happen after a wrong "
601 "merge by a version control system. In this case, "
602 "either fix the merge, or have this information "
603 "missing until the corresponding tracked changes "
604 "are merged or this user edits the file again.\n"),
606 {par.id(), par.size()}, {par.id(), par.size() + 1}));
607 bp.addAuthor(Author(aid));
609 if (token == "\\change_inserted")
610 change = Change(Change::INSERTED, am.find(aid)->second, ct);
612 change = Change(Change::DELETED, am.find(aid)->second, ct);
615 errorList.push_back(ErrorItem(_("Unknown token"),
616 bformat(_("Unknown token: %1$s %2$s\n"),
619 {par.id(), 0}, {par.id(), -1}));
624 void Text::readParagraph(Paragraph & par, Lexer & lex,
625 ErrorList & errorList)
628 string token = lex.getString();
630 Change change(Change::UNCHANGED);
633 readParToken(par, lex, token, font, change, errorList);
636 token = lex.getString();
641 if (token == "\\end_layout") {
642 //Ok, paragraph finished
646 LYXERR(Debug::PARSER, "Handling paragraph token: `" << token << '\'');
647 if (token == "\\begin_layout" || token == "\\end_document"
648 || token == "\\end_inset" || token == "\\begin_deeper"
649 || token == "\\end_deeper") {
650 lex.pushToken(token);
651 lyxerr << "Paragraph ended in line "
652 << lex.lineNumber() << "\n"
653 << "Missing \\end_layout.\n";
657 // Final change goes to paragraph break:
658 if (inset().allowMultiPar())
659 par.setChange(par.size(), change);
661 // Initialize begin_of_body_ on load; redoParagraph maintains
662 par.setBeginOfBody();
664 // mark paragraph for spell checking on load
665 // par.requestSpellCheck();
669 class TextCompletionList : public CompletionList
673 TextCompletionList(Cursor const & cur, WordList const & list)
674 : buffer_(cur.buffer()), list_(list)
677 virtual ~TextCompletionList() {}
680 bool sorted() const override { return true; }
682 size_t size() const override
687 docstring const & data(size_t idx) const override
689 return list_.word(idx);
694 Buffer const * buffer_;
696 WordList const & list_;
700 bool Text::empty() const
702 return pars_.empty() || (pars_.size() == 1 && pars_[0].empty()
703 // FIXME: Should we consider the labeled type as empty too?
704 && pars_[0].layout().labeltype == LABEL_NO_LABEL);
708 double Text::spacing(Paragraph const & par) const
710 if (par.params().spacing().isDefault())
711 return owner_->buffer().params().spacing().getValue();
712 return par.params().spacing().getValue();
717 * This breaks a paragraph at the specified position.
718 * The new paragraph will:
719 * - change layout to default layout when keep_layout == false
720 * - keep layout when keep_layout == true
722 static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
725 BufferParams const & bparams = text.inset().buffer().params();
726 ParagraphList & pars = text.paragraphs();
727 // create a new paragraph, and insert into the list
728 ParagraphList::iterator tmp =
729 pars.insert(pars.iterator_at(par_offset + 1), Paragraph());
731 Paragraph & par = pars[par_offset];
733 // remember to set the inset_owner
734 tmp->setInsetOwner(&par.inInset());
735 tmp->params().depth(par.params().depth());
738 tmp->setLayout(par.layout());
739 tmp->setLabelWidthString(par.params().labelWidthString());
741 tmp->setPlainOrDefaultLayout(bparams.documentClass());
743 bool const isempty = (par.allowEmpty() && par.empty());
745 if (!isempty && (par.size() > pos || par.empty())) {
746 tmp->setLayout(par.layout());
747 tmp->params().align(par.params().align());
748 tmp->setLabelWidthString(par.params().labelWidthString());
750 tmp->params().depth(par.params().depth());
751 tmp->params().noindent(par.params().noindent());
752 tmp->params().spacing(par.params().spacing());
754 // move everything behind the break position
755 // to the new paragraph
757 /* Note: if !keepempty, empty() == true, then we reach
758 * here with size() == 0. So pos_end becomes - 1. This
759 * doesn't cause problems because both loops below
760 * enforce pos <= pos_end and 0 <= pos
762 pos_type pos_end = par.size() - 1;
764 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
765 if (moveItem(par, pos, *tmp, j, bparams)) {
771 // Move over the end-of-par change information
772 tmp->setChange(tmp->size(), par.lookupChange(par.size()));
773 par.setChange(par.size(), Change(bparams.track_changes ?
774 Change::INSERTED : Change::UNCHANGED));
777 // Make sure that we keep the language when
778 // breaking paragraph.
780 Font changed = tmp->getFirstFontSettings(bparams);
781 Font const & old = par.getFontSettings(bparams, par.size());
782 changed.setLanguage(old.language());
783 tmp->setFont(0, changed);
790 bool const soa = par.params().startOfAppendix();
791 par.params().clear();
792 // do not lose start of appendix marker (bug 4212)
793 par.params().startOfAppendix(soa);
794 par.setPlainOrDefaultLayout(bparams.documentClass());
798 par.setLayout(tmp->layout());
799 par.setLabelWidthString(tmp->params().labelWidthString());
800 par.params().depth(tmp->params().depth());
805 void Text::breakParagraph(Cursor & cur, bool inverse_logic)
807 LBUFERR(this == cur.text());
809 Paragraph & cpar = cur.paragraph();
810 pit_type cpit = cur.pit();
812 DocumentClass const & tclass = cur.buffer()->params().documentClass();
813 Layout const & layout = cpar.layout();
815 if (cur.lastpos() == 0 && !cpar.allowEmpty()) {
816 if (changeDepthAllowed(cur, DEC_DEPTH)) {
817 changeDepth(cur, DEC_DEPTH);
818 pit_type const prev = depthHook(cpit, cpar.getDepth());
819 docstring const & lay = pars_[prev].layout().name();
820 if (lay != layout.name())
823 docstring const & lay = cur.paragraph().usePlainLayout()
824 ? tclass.plainLayoutName() : tclass.defaultLayoutName();
825 if (lay != layout.name())
833 // Always break behind a space
834 // It is better to erase the space (Dekel)
835 if (cur.pos() != cur.lastpos() && cpar.isLineSeparator(cur.pos()))
836 cpar.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
838 // What should the layout for the new paragraph be?
839 bool keep_layout = layout.isEnvironment()
840 || (layout.isParagraph() && layout.parbreak_is_newline);
842 keep_layout = !keep_layout;
844 // We need to remember this before we break the paragraph, because
845 // that invalidates the layout variable
846 bool sensitive = layout.labeltype == LABEL_SENSITIVE;
848 // we need to set this before we insert the paragraph.
849 bool const isempty = cpar.allowEmpty() && cpar.empty();
851 lyx::breakParagraph(*this, cpit, cur.pos(), keep_layout);
853 // After this, neither paragraph contains any rows!
856 pit_type next_par = cpit + 1;
858 // well this is the caption hack since one caption is really enough
861 // set to standard-layout
862 //FIXME Check if this should be plainLayout() in some cases
863 pars_[cpit].applyLayout(tclass.defaultLayout());
865 // set to standard-layout
866 //FIXME Check if this should be plainLayout() in some cases
867 pars_[next_par].applyLayout(tclass.defaultLayout());
870 while (!pars_[next_par].empty() && pars_[next_par].isNewline(0)) {
871 if (!pars_[next_par].eraseChar(0, cur.buffer()->params().track_changes))
872 break; // the character couldn't be deleted physically due to change tracking
875 // A singlePar update is not enough in this case.
876 cur.screenUpdateFlags(Update::Force);
877 cur.forceBufferUpdate();
879 // This check is necessary. Otherwise the new empty paragraph will
880 // be deleted automatically. And it is more friendly for the user!
881 if (cur.pos() != 0 || isempty)
882 setCursor(cur, cur.pit() + 1, 0);
884 setCursor(cur, cur.pit(), 0);
888 // needed to insert the selection
889 void Text::insertStringAsLines(Cursor & cur, docstring const & str,
892 BufferParams const & bparams = owner_->buffer().params();
893 pit_type pit = cur.pit();
894 pos_type pos = cur.pos();
896 // The special chars we handle
897 static map<wchar_t, InsetSpecialChar::Kind> specialchars = {
898 { 0x200c, InsetSpecialChar::LIGATURE_BREAK },
899 { 0x200b, InsetSpecialChar::ALLOWBREAK },
900 { 0x2026, InsetSpecialChar::LDOTS },
901 { 0x2011, InsetSpecialChar::NOBREAKDASH }
904 // insert the string, don't insert doublespace
905 bool space_inserted = true;
906 for (auto const & ch : str) {
907 Paragraph & par = pars_[pit];
909 if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) {
910 lyx::breakParagraph(*this, pit, pos,
911 par.layout().isEnvironment());
914 space_inserted = true;
918 // do not insert consecutive spaces if !free_spacing
919 } else if ((ch == ' ' || ch == '\t') &&
920 space_inserted && !par.isFreeSpacing()) {
922 } else if (ch == '\t') {
923 if (!par.isFreeSpacing()) {
924 // tabs are like spaces here
925 par.insertChar(pos, ' ', font, bparams.track_changes);
927 space_inserted = true;
929 par.insertChar(pos, ch, font, bparams.track_changes);
931 space_inserted = true;
933 } else if (specialchars.find(ch) != specialchars.end()
934 && (par.insertInset(pos, new InsetSpecialChar(specialchars.find(ch)->second),
935 font, bparams.track_changes
936 ? Change(Change::INSERTED)
937 : Change(Change::UNCHANGED)))) {
939 space_inserted = false;
940 } else if (!isPrintable(ch)) {
941 // Ignore (other) unprintables
944 // just insert the character
945 par.insertChar(pos, ch, font, bparams.track_changes);
947 space_inserted = (ch == ' ');
950 setCursor(cur, pit, pos);
954 // turn double CR to single CR, others are converted into one
955 // blank. Then insertStringAsLines is called
956 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str,
959 docstring linestr = str;
960 bool newline_inserted = false;
962 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
963 if (linestr[i] == '\n') {
964 if (newline_inserted) {
965 // we know that \r will be ignored by
966 // insertStringAsLines. Of course, it is a dirty
967 // trick, but it works...
968 linestr[i - 1] = '\r';
972 newline_inserted = true;
974 } else if (isPrintable(linestr[i])) {
975 newline_inserted = false;
978 insertStringAsLines(cur, linestr, font);
984 bool canInsertChar(Cursor const & cur, char_type c)
986 Paragraph const & par = cur.paragraph();
987 // If not in free spacing mode, check if there will be two blanks together or a blank at
988 // the beginning of a paragraph.
989 if (!par.isFreeSpacing() && isLineSeparatorChar(c)) {
990 if (cur.pos() == 0) {
992 "You cannot insert a space at the "
993 "beginning of a paragraph. Please read the Tutorial."));
996 // If something is wrong, ignore this character.
997 LASSERT(cur.pos() > 0, return false);
998 if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1))
999 && !par.isDeleted(cur.pos() - 1)) {
1001 "You cannot type two spaces this way. "
1002 "Please read the Tutorial."));
1007 // Prevent to insert uncodable characters in verbatim and ERT.
1008 // The encoding is inherited from the context here.
1009 if (par.isPassThru() && cur.getEncoding()) {
1010 Encoding const * e = cur.getEncoding();
1011 if (!e->encodable(c)) {
1012 cur.message(_("Character is uncodable in this verbatim context."));
1022 // insert a character, moves all the following breaks in the
1023 // same Paragraph one to the right and make a rebreak
1024 void Text::insertChar(Cursor & cur, char_type c)
1026 LBUFERR(this == cur.text());
1028 if (!canInsertChar(cur,c))
1031 cur.recordUndo(INSERT_UNDO);
1033 TextMetrics const & tm = cur.bv().textMetrics(this);
1034 Buffer const & buffer = *cur.buffer();
1035 Paragraph & par = cur.paragraph();
1036 // try to remove this
1037 pit_type const pit = cur.pit();
1039 if (lyxrc.auto_number) {
1040 static docstring const number_operators = from_ascii("+-/*");
1041 static docstring const number_unary_operators = from_ascii("+-");
1043 // Common Number Separators: comma, dot etc.
1044 // European Number Terminators: percent, permille, degree, euro etc.
1045 if (cur.current_font.fontInfo().number() == FONT_ON) {
1046 if (!isDigitASCII(c) && !contains(number_operators, c) &&
1047 !(isCommonNumberSeparator(c) &&
1049 cur.pos() != cur.lastpos() &&
1050 tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1051 tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON) &&
1052 !(isEuropeanNumberTerminator(c) &&
1054 tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1055 tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON)
1057 number(cur); // Set current_font.number to OFF
1058 } else if (isDigitASCII(c) &&
1059 cur.real_current_font.isVisibleRightToLeft()) {
1060 number(cur); // Set current_font.number to ON
1062 if (cur.pos() != 0) {
1063 char_type const ch = par.getChar(cur.pos() - 1);
1064 if (contains(number_unary_operators, ch) &&
1066 || par.isSeparator(cur.pos() - 2)
1067 || par.isEnvSeparator(cur.pos() - 2)
1068 || par.isNewline(cur.pos() - 2))
1070 setCharFont(pit, cur.pos() - 1, cur.current_font,
1072 } else if (isCommonNumberSeparator(ch)
1074 && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) {
1075 setCharFont(pit, cur.pos() - 1, cur.current_font,
1082 // In Bidi text, we want spaces to be treated in a special way: spaces
1083 // which are between words in different languages should get the
1084 // paragraph's language; otherwise, spaces should keep the language
1085 // they were originally typed in. This is only in effect while typing;
1086 // after the text is already typed in, the user can always go back and
1087 // explicitly set the language of a space as desired. But 99.9% of the
1088 // time, what we're doing here is what the user actually meant.
1090 // The following cases are the ones in which the language of the space
1091 // should be changed to match that of the containing paragraph. In the
1092 // depictions, lowercase is LTR, uppercase is RTL, underscore (_)
1093 // represents a space, pipe (|) represents the cursor position (so the
1094 // character before it is the one just typed in). The different cases
1095 // are depicted logically (not visually), from left to right:
1100 // Theoretically, there are other situations that we should, perhaps, deal
1101 // with (e.g.: a|_A, A|_a). In practice, though, there really isn't any
1102 // point (to understand why, just try to create this situation...).
1104 if ((cur.pos() >= 2) && (par.isLineSeparator(cur.pos() - 1))) {
1105 // get font in front and behind the space in question. But do NOT
1106 // use getFont(cur.pos()) because the character c is not inserted yet
1107 Font const pre_space_font = tm.displayFont(cur.pit(), cur.pos() - 2);
1108 Font const & post_space_font = cur.real_current_font;
1109 bool pre_space_rtl = pre_space_font.isVisibleRightToLeft();
1110 bool post_space_rtl = post_space_font.isVisibleRightToLeft();
1112 if (pre_space_rtl != post_space_rtl) {
1113 // Set the space's language to match the language of the
1114 // adjacent character whose direction is the paragraph's
1115 // direction; don't touch other properties of the font
1116 Language const * lang =
1117 (pre_space_rtl == par.isRTL(buffer.params())) ?
1118 pre_space_font.language() : post_space_font.language();
1120 Font space_font = tm.displayFont(cur.pit(), cur.pos() - 1);
1121 space_font.setLanguage(lang);
1122 par.setFont(cur.pos() - 1, space_font);
1126 pos_type pos = cur.pos();
1127 if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE &&
1128 cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY &&
1129 c == '-' && pos > 0) {
1130 pos_type prev_pos = pos - 1;
1131 while (prev_pos > 0 && par.isDeleted(prev_pos))
1133 if (!par.isDeleted(prev_pos) && par.getChar(prev_pos) == '-') {
1134 // convert "--" to endash
1135 par.eraseChar(prev_pos, cur.buffer()->params().track_changes);
1138 } else if (!par.isDeleted(prev_pos) && par.getChar(prev_pos) == 0x2013) {
1139 // convert "---" to emdash
1140 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1146 par.insertChar(pos, c, cur.current_font,
1147 cur.buffer()->params().track_changes);
1148 cur.checkBufferStructure();
1150 // cur.screenUpdateFlags(Update::Force);
1151 bool boundary = cur.boundary()
1152 || tm.isRTLBoundary(cur.pit(), pos + 1);
1153 setCursor(cur, cur.pit(), pos + 1, false, boundary);
1158 void Text::charInserted(Cursor & cur)
1160 Paragraph & par = cur.paragraph();
1162 // register word if a non-letter was entered
1164 && !par.isWordSeparator(cur.pos() - 2)
1165 && par.isWordSeparator(cur.pos() - 1)) {
1166 // get the word in front of cursor
1167 LBUFERR(this == cur.text());
1173 // the cursor set functions have a special mechanism. When they
1174 // realize, that you left an empty paragraph, they will delete it.
1176 bool Text::cursorForwardOneWord(Cursor & cur)
1178 LBUFERR(this == cur.text());
1180 if (lyxrc.mac_like_cursor_movement) {
1181 DocIterator dit(cur);
1182 DocIterator prv(cur);
1183 bool inword = false;
1184 bool intext = dit.inTexted();
1185 while (!dit.atEnd()) {
1186 if (dit.inTexted()) { // no paragraphs in mathed
1187 Paragraph const & par = dit.paragraph();
1188 pos_type const pos = dit.pos();
1190 if (!par.isDeleted(pos)) {
1191 bool wordsep = par.isWordSeparator(pos);
1192 if (inword && wordsep)
1193 break; // stop at word end
1194 else if (!inword && !wordsep)
1198 } else if (intext) {
1199 // move to end of math
1200 while (!dit.inTexted() && !dit.atEnd()) dit.forwardPos();
1204 dit.forwardPosIgnoreCollapsed();
1206 if (dit.atEnd()) dit = prv;
1207 if (dit == cur) return false; // we didn't move
1210 // see comment above
1211 cur.bv().checkDepm(cur, orig);
1214 pos_type const lastpos = cur.lastpos();
1215 pit_type pit = cur.pit();
1216 pos_type pos = cur.pos();
1217 Paragraph const & par = cur.paragraph();
1219 // Paragraph boundary is a word boundary
1220 if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
1221 if (pit != cur.lastpit())
1222 return setCursor(cur, pit + 1, 0);
1227 LASSERT(pos < lastpos, return false); // see above
1228 if (!par.isWordSeparator(pos))
1229 while (pos != lastpos && !par.isWordSeparator(pos))
1231 else if (par.isChar(pos))
1232 while (pos != lastpos && par.isChar(pos))
1234 else if (!par.isSpace(pos)) // non-char inset
1237 // Skip over white space
1238 while (pos != lastpos && par.isSpace(pos))
1241 // Don't skip a separator inset at the end of a paragraph
1242 if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
1245 return setCursor(cur, pit, pos);
1250 bool Text::cursorBackwardOneWord(Cursor & cur)
1252 LBUFERR(this == cur.text());
1254 if (lyxrc.mac_like_cursor_movement) {
1255 DocIterator dit(cur);
1256 bool inword = false;
1257 bool intext = dit.inTexted();
1258 while (!dit.atBegin()) {
1259 DocIterator prv(dit);
1260 dit.backwardPosIgnoreCollapsed();
1261 if (dit.inTexted()) { // no paragraphs in mathed
1262 Paragraph const & par = dit.paragraph();
1263 pos_type pos = dit.pos();
1265 if (!par.isDeleted(pos)) {
1266 bool wordsep = par.isWordSeparator(pos);
1267 if (inword && wordsep) {
1269 break; // stop at word begin
1270 } else if (!inword && !wordsep)
1274 } else if (intext) {
1275 // move to begin of math
1276 while (!dit.inTexted() && !dit.atBegin()) dit.backwardPos();
1280 if (dit == cur) return false; // we didn't move
1283 // see comment above cursorForwardOneWord
1284 cur.bv().checkDepm(cur, orig);
1287 Paragraph const & par = cur.paragraph();
1288 pit_type const pit = cur.pit();
1289 pos_type pos = cur.pos();
1291 // Paragraph boundary is a word boundary
1292 if (pos == 0 && pit != 0) {
1293 Paragraph & prevpar = getPar(pit - 1);
1294 pos = prevpar.size();
1295 // Don't stop after an environment separator
1296 if (pos && prevpar.isEnvSeparator(pos - 1))
1298 return setCursor(cur, pit - 1, pos);
1300 // Skip over white space
1301 while (pos != 0 && par.isSpace(pos - 1))
1304 if (pos != 0 && !par.isWordSeparator(pos - 1))
1305 while (pos != 0 && !par.isWordSeparator(pos - 1))
1307 else if (pos != 0 && par.isChar(pos - 1))
1308 while (pos != 0 && par.isChar(pos - 1))
1310 else if (pos != 0 && !par.isSpace(pos - 1)) // non-char inset
1313 return setCursor(cur, pit, pos);
1318 bool Text::cursorVisLeftOneWord(Cursor & cur)
1320 LBUFERR(this == cur.text());
1322 pos_type left_pos, right_pos;
1324 Cursor temp_cur = cur;
1326 // always try to move at least once...
1327 while (temp_cur.posVisLeft(true /* skip_inset */)) {
1329 // collect some information about current cursor position
1330 temp_cur.getSurroundingPos(left_pos, right_pos);
1331 bool left_is_letter =
1332 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1333 bool right_is_letter =
1334 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1336 // if we're not at a letter/non-letter boundary, continue moving
1337 if (left_is_letter == right_is_letter)
1340 // we should stop when we have an LTR word on our right or an RTL word
1342 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1343 temp_cur.buffer()->params(), left_pos).isRightToLeft())
1344 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1345 temp_cur.buffer()->params(), right_pos).isRightToLeft()))
1349 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1350 true, temp_cur.boundary());
1354 bool Text::cursorVisRightOneWord(Cursor & cur)
1356 LBUFERR(this == cur.text());
1358 pos_type left_pos, right_pos;
1360 Cursor temp_cur = cur;
1362 // always try to move at least once...
1363 while (temp_cur.posVisRight(true /* skip_inset */)) {
1365 // collect some information about current cursor position
1366 temp_cur.getSurroundingPos(left_pos, right_pos);
1367 bool left_is_letter =
1368 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1369 bool right_is_letter =
1370 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1372 // if we're not at a letter/non-letter boundary, continue moving
1373 if (left_is_letter == right_is_letter)
1376 // we should stop when we have an LTR word on our right or an RTL word
1378 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1379 temp_cur.buffer()->params(),
1380 left_pos).isRightToLeft())
1381 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1382 temp_cur.buffer()->params(),
1383 right_pos).isRightToLeft()))
1387 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1388 true, temp_cur.boundary());
1392 void Text::selectWord(Cursor & cur, word_location loc)
1394 LBUFERR(this == cur.text());
1395 CursorSlice from = cur.top();
1397 getWord(from, to, loc);
1398 if (cur.top() != from)
1399 setCursor(cur, from.pit(), from.pos());
1402 if (!cur.selection())
1404 setCursor(cur, to.pit(), to.pos());
1406 cur.setWordSelection(true);
1410 void Text::expandWordSel(Cursor & cur)
1412 // get selection of word around cur
1415 c.text()->selectWord(c, WHOLE_WORD);
1416 // get selection around anchor too.
1417 // FIXME: this cursor is not a proper one. normalAnchor() should
1418 // return a DocIterator.
1420 a.push_back(cur.normalAnchor());
1421 a.text()->selectWord(a, WHOLE_WORD);
1422 // use the correct word boundary, depending on selection direction
1423 if (cur.top() > cur.normalAnchor()) {
1424 cur.top() = a.selBegin();
1426 cur.top() = c.selEnd();
1428 cur.top() = a.selEnd();
1430 cur.top() = c.selBegin();
1435 void Text::selectAll(Cursor & cur)
1437 LBUFERR(this == cur.text());
1438 if (cur.lastpos() == 0 && cur.lastpit() == 0)
1440 // If the cursor is at the beginning, make sure the cursor ends there
1441 if (cur.pit() == 0 && cur.pos() == 0) {
1442 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1444 setCursor(cur, 0, 0);
1446 setCursor(cur, 0, 0);
1448 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1454 // Select the word currently under the cursor when no
1455 // selection is currently set
1456 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
1458 LBUFERR(this == cur.text());
1459 if (cur.selection())
1461 selectWord(cur, loc);
1462 return cur.selection();
1466 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
1468 LBUFERR(this == cur.text());
1470 if (!cur.selection()) {
1471 if (!selectChange(cur))
1475 cur.recordUndoSelection();
1477 pit_type begPit = cur.selectionBegin().pit();
1478 pit_type endPit = cur.selectionEnd().pit();
1480 pos_type begPos = cur.selectionBegin().pos();
1481 pos_type endPos = cur.selectionEnd().pos();
1483 // keep selection info, because endPos becomes invalid after the first loop
1484 bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size());
1486 // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
1487 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1488 pos_type parSize = pars_[pit].size();
1490 // ignore empty paragraphs; otherwise, an assertion will fail for
1491 // acceptChanges(bparams, 0, 0) or rejectChanges(bparams, 0, 0)
1495 // do not consider first paragraph if the cursor starts at pos size()
1496 if (pit == begPit && begPos == parSize)
1499 // do not consider last paragraph if the cursor ends at pos 0
1500 if (pit == endPit && endPos == 0)
1501 break; // last iteration anyway
1503 pos_type const left = (pit == begPit ? begPos : 0);
1504 pos_type const right = (pit == endPit ? endPos : parSize);
1507 // there is no change here
1511 pars_[pit].acceptChanges(left, right);
1513 pars_[pit].rejectChanges(left, right);
1517 // next, accept/reject imaginary end-of-par characters
1519 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1520 pos_type pos = pars_[pit].size();
1522 // skip if the selection ends before the end-of-par
1523 if (pit == endPit && endsBeforeEndOfPar)
1524 break; // last iteration anyway
1526 // skip if this is not the last paragraph of the document
1527 // note: the user should be able to accept/reject the par break of the last par!
1528 if (pit == endPit && pit + 1 != int(pars_.size()))
1529 break; // last iteration anway
1532 if (pars_[pit].isInserted(pos)) {
1533 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1534 } else if (pars_[pit].isDeleted(pos)) {
1535 if (pit + 1 == int(pars_.size())) {
1536 // we cannot remove a par break at the end of the last paragraph;
1537 // instead, we mark it unchanged
1538 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1540 mergeParagraph(cur.buffer()->params(), pars_, pit);
1546 if (pars_[pit].isDeleted(pos)) {
1547 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1548 } else if (pars_[pit].isInserted(pos)) {
1549 if (pit + 1 == int(pars_.size())) {
1550 // we mark the par break at the end of the last paragraph unchanged
1551 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1553 mergeParagraph(cur.buffer()->params(), pars_, pit);
1561 // finally, invoke the DEPM
1562 deleteEmptyParagraphMechanism(begPit, endPit, begPos, endPos,
1563 cur.buffer()->params().track_changes);
1566 cur.clearSelection();
1567 setCursorIntern(cur, begPit, begPos);
1568 cur.screenUpdateFlags(Update::Force);
1569 cur.forceBufferUpdate();
1573 void Text::acceptChanges()
1575 BufferParams const & bparams = owner_->buffer().params();
1576 lyx::acceptChanges(pars_, bparams);
1577 deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes);
1581 void Text::rejectChanges()
1583 BufferParams const & bparams = owner_->buffer().params();
1584 pit_type pars_size = static_cast<pit_type>(pars_.size());
1586 // first, reject changes within each individual paragraph
1587 // (do not consider end-of-par)
1588 for (pit_type pit = 0; pit < pars_size; ++pit) {
1589 if (!pars_[pit].empty()) // prevent assertion failure
1590 pars_[pit].rejectChanges(0, pars_[pit].size());
1593 // next, reject imaginary end-of-par characters
1594 for (pit_type pit = 0; pit < pars_size; ++pit) {
1595 pos_type pos = pars_[pit].size();
1597 if (pars_[pit].isDeleted(pos)) {
1598 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1599 } else if (pars_[pit].isInserted(pos)) {
1600 if (pit == pars_size - 1) {
1601 // we mark the par break at the end of the last
1602 // paragraph unchanged
1603 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1605 mergeParagraph(bparams, pars_, pit);
1612 // finally, invoke the DEPM
1613 deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes);
1617 void Text::deleteWordForward(Cursor & cur, bool const force)
1619 LBUFERR(this == cur.text());
1620 if (cur.lastpos() == 0)
1624 cur.selection(true);
1625 cursorForwardOneWord(cur);
1627 if (force || !cur.confirmDeletion()) {
1628 cutSelection(cur, false);
1629 cur.checkBufferStructure();
1635 void Text::deleteWordBackward(Cursor & cur, bool const force)
1637 LBUFERR(this == cur.text());
1638 if (cur.lastpos() == 0)
1639 cursorBackward(cur);
1642 cur.selection(true);
1643 cursorBackwardOneWord(cur);
1645 if (force || !cur.confirmDeletion()) {
1646 cutSelection(cur, false);
1647 cur.checkBufferStructure();
1653 // Kill to end of line.
1654 void Text::changeCase(Cursor & cur, TextCase action, bool partial)
1656 LBUFERR(this == cur.text());
1660 bool const gotsel = cur.selection();
1662 from = cur.selBegin();
1666 getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
1667 cursorForwardOneWord(cur);
1670 cur.recordUndoSelection();
1672 pit_type begPit = from.pit();
1673 pit_type endPit = to.pit();
1675 pos_type begPos = from.pos();
1676 pos_type endPos = to.pos();
1678 pos_type right = 0; // needed after the for loop
1680 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1681 Paragraph & par = pars_[pit];
1682 pos_type const pos = (pit == begPit ? begPos : 0);
1683 right = (pit == endPit ? endPos : par.size());
1684 par.changeCase(cur.buffer()->params(), pos, right, action);
1687 // the selection may have changed due to logically-only deleted chars
1689 setCursor(cur, begPit, begPos);
1691 setCursor(cur, endPit, right);
1694 setCursor(cur, endPit, right);
1696 cur.checkBufferStructure();
1700 bool Text::handleBibitems(Cursor & cur)
1702 if (cur.paragraph().layout().labeltype != LABEL_BIBLIO)
1708 BufferParams const & bufparams = cur.buffer()->params();
1709 Paragraph const & par = cur.paragraph();
1710 Cursor prevcur = cur;
1711 if (cur.pit() > 0) {
1713 prevcur.pos() = prevcur.lastpos();
1715 Paragraph const & prevpar = prevcur.paragraph();
1717 // if a bibitem is deleted, merge with previous paragraph
1718 // if this is a bibliography item as well
1719 if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
1720 cur.recordUndo(prevcur.pit());
1721 mergeParagraph(bufparams, cur.text()->paragraphs(),
1723 cur.forceBufferUpdate();
1724 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1725 cur.screenUpdateFlags(Update::Force);
1729 // otherwise reset to default
1730 cur.paragraph().setPlainOrDefaultLayout(bufparams.documentClass());
1735 bool Text::erase(Cursor & cur)
1737 LASSERT(this == cur.text(), return false);
1738 bool needsUpdate = false;
1739 Paragraph & par = cur.paragraph();
1741 if (cur.pos() != cur.lastpos()) {
1742 // this is the code for a normal delete, not pasting
1744 cur.recordUndo(DELETE_UNDO);
1745 bool const was_inset = cur.paragraph().isInset(cur.pos());
1746 if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes))
1747 // the character has been logically deleted only => skip it
1748 cur.top().forwardPos();
1751 cur.forceBufferUpdate();
1753 cur.checkBufferStructure();
1756 if (cur.pit() == cur.lastpit())
1757 return dissolveInset(cur);
1759 if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1760 cur.recordUndo(DELETE_UNDO);
1761 par.setChange(cur.pos(), Change(Change::DELETED));
1765 setCursorIntern(cur, cur.pit() + 1, 0);
1766 needsUpdate = backspacePos0(cur);
1770 needsUpdate |= handleBibitems(cur);
1773 // Make sure the cursor is correct. Is this really needed?
1774 // No, not really... at least not here!
1775 cur.top().setPitPos(cur.pit(), cur.pos());
1776 cur.checkBufferStructure();
1783 bool Text::backspacePos0(Cursor & cur)
1785 LBUFERR(this == cur.text());
1789 BufferParams const & bufparams = cur.buffer()->params();
1790 ParagraphList & plist = cur.text()->paragraphs();
1791 Paragraph const & par = cur.paragraph();
1792 Cursor prevcur = cur;
1794 prevcur.pos() = prevcur.lastpos();
1795 Paragraph const & prevpar = prevcur.paragraph();
1797 // is it an empty paragraph?
1798 if (cur.lastpos() == 0
1799 || (cur.lastpos() == 1 && par.isSeparator(0))) {
1800 cur.recordUndo(prevcur.pit());
1801 plist.erase(plist.iterator_at(cur.pit()));
1803 // is previous par empty?
1804 else if (prevcur.lastpos() == 0
1805 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
1806 cur.recordUndo(prevcur.pit());
1807 plist.erase(plist.iterator_at(prevcur.pit()));
1809 // FIXME: Do we really not want to allow this???
1810 // Pasting is not allowed, if the paragraphs have different
1811 // layouts. I think it is a real bug of all other
1812 // word processors to allow it. It confuses the user.
1813 // Correction: Pasting is always allowed with standard-layout
1814 // or the empty layout.
1816 cur.recordUndo(prevcur.pit());
1817 mergeParagraph(bufparams, plist, prevcur.pit());
1820 cur.forceBufferUpdate();
1821 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1827 bool Text::backspace(Cursor & cur)
1829 LBUFERR(this == cur.text());
1830 bool needsUpdate = false;
1831 if (cur.pos() == 0) {
1833 return dissolveInset(cur);
1835 Cursor prev_cur = cur;
1838 if (!cur.paragraph().empty()
1839 && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1840 cur.recordUndo(prev_cur.pit(), prev_cur.pit());
1841 prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
1842 setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
1845 // The cursor is at the beginning of a paragraph, so
1846 // the backspace will collapse two paragraphs into one.
1847 needsUpdate = backspacePos0(cur);
1850 // this is the code for a normal backspace, not pasting
1852 cur.recordUndo(DELETE_UNDO);
1853 // We used to do cursorBackwardIntern() here, but it is
1854 // not a good idea since it triggers the auto-delete
1855 // mechanism. So we do a cursorBackwardIntern()-lite,
1856 // without the dreaded mechanism. (JMarc)
1857 setCursorIntern(cur, cur.pit(), cur.pos() - 1,
1858 false, cur.boundary());
1859 bool const was_inset = cur.paragraph().isInset(cur.pos());
1860 cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes);
1862 cur.forceBufferUpdate();
1864 cur.checkBufferStructure();
1867 if (cur.pos() == cur.lastpos())
1868 cur.setCurrentFont();
1870 needsUpdate |= handleBibitems(cur);
1872 // A singlePar update is not enough in this case.
1873 // cur.screenUpdateFlags(Update::Force);
1874 cur.top().setPitPos(cur.pit(), cur.pos());
1880 bool Text::dissolveInset(Cursor & cur)
1882 LASSERT(this == cur.text(), return false);
1884 if (isMainText() || cur.inset().nargs() != 1)
1887 cur.recordUndoInset();
1889 cur.selHandle(false);
1890 // save position inside inset
1891 pos_type spos = cur.pos();
1892 pit_type spit = cur.pit();
1893 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1895 // update cursor offset
1899 // remember position outside inset to delete inset later
1900 // we do not do it now to avoid memory reuse issues (see #10667).
1901 DocIterator inset_it = cur;
1905 Buffer & b = *cur.buffer();
1906 // Is there anything in this text?
1907 if (inset_non_empty) {
1909 // we clear the cache so that we won't get conflicts with labels
1910 // that get pasted into the buffer. we should update this before
1911 // its being empty matters. if not (i.e., if we encounter bugs),
1912 // then this should instead be:
1913 // cur.buffer().updateBuffer();
1914 // but we'll try the cheaper solution here.
1915 cur.buffer()->clearReferenceCache();
1917 ParagraphList & plist = paragraphs();
1918 if (!lyxrc.ct_markup_copied)
1919 // Do not revive deleted text
1920 lyx::acceptChanges(plist, b.params());
1922 // ERT paragraphs have the Language latex_language.
1923 // This is invalid outside of ERT, so we need to
1924 // change it to the buffer language.
1925 for (auto & p : plist)
1926 p.changeLanguage(b.params(), latex_language, b.language());
1928 /* If the inset is the only thing in paragraph and the layout
1929 * is not plain, then the layout of the first paragraph of
1930 * inset should be remembered.
1931 * FIXME: this does not work as expected when change tracking
1932 * is on However, we do not really know what to do in this
1935 DocumentClass const & tclass = cur.buffer()->params().documentClass();
1936 if (inset_it.lastpos() == 1
1937 && !tclass.isPlainLayout(plist[0].layout())
1938 && !tclass.isDefaultLayout(plist[0].layout())) {
1939 // Copy all parameters except depth.
1940 Paragraph & par = cur.paragraph();
1941 par.setLayout(plist[0].layout());
1942 depth_type const dpth = par.getDepth();
1943 par.params() = plist[0].params();
1944 par.params().depth(dpth);
1947 pasteParagraphList(cur, plist, b.params().documentClassPtr(),
1948 b.params().authors(),
1949 b.errorList("Paste"));
1952 // delete the inset now
1953 inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes);
1956 cur.pit() = min(cur.lastpit(), spit);
1957 cur.pos() = min(cur.lastpos(), spos);
1958 // Ensure the current language is set correctly (bug 6292)
1959 cur.text()->setCursor(cur, cur.pit(), cur.pos());
1960 cur.clearSelection();
1962 cur.forceBufferUpdate();
1968 bool Text::splitInset(Cursor & cur)
1970 LASSERT(this == cur.text(), return false);
1972 if (isMainText() || cur.inset().nargs() != 1)
1976 if (cur.selection()) {
1977 // start from selection begin
1978 setCursor(cur, cur.selBegin().pit(), cur.selBegin().pos());
1979 cur.clearSelection();
1981 // save split position inside inset
1982 // (we need to copy the whole inset first)
1983 pos_type spos = cur.pos();
1984 pit_type spit = cur.pit();
1985 // some things only need to be done if the inset has content
1986 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1988 // move right before the inset
1991 // remember position outside inset
1992 pos_type ipos = cur.pos();
1993 pit_type ipit = cur.pit();
1998 cap::copySelectionToTemp(cur);
1999 cur.clearSelection();
2001 // paste copied inset
2002 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
2003 cur.forceBufferUpdate();
2005 // if the inset has text, cut after split position
2006 // and paste to new inset
2007 if (inset_non_empty) {
2008 // go back to first inset
2009 cur.text()->setCursor(cur, ipit, ipos);
2011 setCursor(cur, spit, spos);
2013 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
2015 // Remember whether there was something cut that has to be pasted below
2017 bool const hasCut = cur.selection();
2018 cap::cutSelectionToTemp(cur);
2020 cur.selHandle(false);
2022 bool atlastpos = false;
2023 if (cur.pos() == 0 && cur.pit() > 0) {
2024 // if we are at par start, remove this par
2025 cur.text()->backspace(cur);
2026 cur.forceBufferUpdate();
2027 } else if (cur.pos() == cur.lastpos())
2029 // Move out of and jump over inset
2037 cur.text()->selectAll(cur);
2038 cutSelection(cur, false);
2039 // If there was something cut paste it
2041 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
2042 cur.text()->setCursor(cur, 0, 0);
2043 if (atlastpos && cur.paragraph().isFreeSpacing() && cur.paragraph().empty()) {
2044 // We started from par end, remove extra empty par in free spacing insets
2045 cur.text()->erase(cur);
2046 cur.forceBufferUpdate();
2055 void Text::getWord(CursorSlice & from, CursorSlice & to,
2056 word_location const loc) const
2059 pars_[to.pit()].locateWord(from.pos(), to.pos(), loc);
2063 void Text::write(ostream & os) const
2065 Buffer const & buf = owner_->buffer();
2066 ParagraphList::const_iterator pit = paragraphs().begin();
2067 ParagraphList::const_iterator end = paragraphs().end();
2069 for (; pit != end; ++pit)
2070 pit->write(os, buf.params(), dth);
2072 // Close begin_deeper
2073 for(; dth > 0; --dth)
2074 os << "\n\\end_deeper";
2078 bool Text::read(Lexer & lex,
2079 ErrorList & errorList, InsetText * insetPtr)
2081 Buffer const & buf = owner_->buffer();
2082 depth_type depth = 0;
2085 while (lex.isOK()) {
2087 string const token = lex.getString();
2092 if (token == "\\end_inset")
2095 if (token == "\\end_body")
2098 if (token == "\\begin_body")
2101 if (token == "\\end_document") {
2106 if (token == "\\begin_layout") {
2107 lex.pushToken(token);
2110 par.setInsetOwner(insetPtr);
2111 par.params().depth(depth);
2112 par.setFont(0, Font(inherit_font, buf.params().language));
2113 pars_.push_back(par);
2114 readParagraph(pars_.back(), lex, errorList);
2116 // register the words in the global word list
2117 pars_.back().updateWords();
2118 } else if (token == "\\begin_deeper") {
2120 } else if (token == "\\end_deeper") {
2122 lex.printError("\\end_deeper: " "depth is already null");
2126 LYXERR0("Handling unknown body token: `" << token << '\'');
2130 // avoid a crash on weird documents (bug 4859)
2131 if (pars_.empty()) {
2133 par.setInsetOwner(insetPtr);
2134 par.params().depth(depth);
2135 par.setFont(0, Font(inherit_font,
2136 buf.params().language));
2137 par.setPlainOrDefaultLayout(buf.params().documentClass());
2138 pars_.push_back(par);
2145 // Returns the current state (font, depth etc.) as a message for status bar.
2146 docstring Text::currentState(CursorData const & cur, bool devel_mode) const
2148 LBUFERR(this == cur.text());
2149 Buffer & buf = *cur.buffer();
2150 Paragraph const & par = cur.paragraph();
2151 odocstringstream os;
2153 if (buf.params().track_changes)
2154 os << _("[Change Tracking] ");
2156 Change change = par.lookupChange(cur.pos());
2158 if (change.changed()) {
2159 docstring const author =
2160 buf.params().authors().get(change.author).nameAndEmail();
2161 docstring const date = formatted_datetime(change.changetime);
2162 os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
2166 // I think we should only show changes from the default
2168 // No, from the document font (MV)
2169 Font font = cur.real_current_font;
2170 font.fontInfo().reduce(buf.params().getFont().fontInfo());
2172 os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
2174 // The paragraph depth
2175 int depth = par.getDepth();
2177 os << bformat(_(", Depth: %1$d"), depth);
2179 // The paragraph spacing, but only if different from
2181 Spacing const & spacing = par.params().spacing();
2182 if (!spacing.isDefault()) {
2183 os << _(", Spacing: ");
2184 switch (spacing.getSpace()) {
2185 case Spacing::Single:
2188 case Spacing::Onehalf:
2191 case Spacing::Double:
2194 case Spacing::Other:
2195 os << _("Other (") << from_ascii(spacing.getValueAsString()) << ')';
2197 case Spacing::Default:
2198 // should never happen, do nothing
2203 // Custom text style
2204 InsetLayout const & layout = cur.inset().getLayout();
2205 if (layout.lyxtype() == InsetLyXType::CHARSTYLE)
2206 os << _(", Style: ") << translateIfPossible(layout.labelstring());
2209 os << _(", Inset: ") << &cur.inset();
2210 if (cur.lastidx() > 0)
2211 os << _(", Cell: ") << cur.idx();
2212 os << _(", Paragraph: ") << cur.pit();
2213 os << _(", Id: ") << par.id();
2214 os << _(", Position: ") << cur.pos();
2215 // FIXME: Why is the check for par.size() needed?
2216 // We are called with cur.pos() == par.size() quite often.
2217 if (!par.empty() && cur.pos() < par.size()) {
2218 // Force output of code point, not character
2219 size_t const c = par.getChar(cur.pos());
2220 if (c == META_INSET)
2221 os << ", Char: INSET";
2223 os << _(", Char: 0x") << hex << c;
2225 os << _(", Boundary: ") << cur.boundary();
2226 // Row & row = cur.textRow();
2227 // os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
2233 docstring Text::getPossibleLabel(DocIterator const & cur) const
2235 pit_type textpit = cur.pit();
2236 Layout const * layout = &(pars_[textpit].layout());
2238 // Will contain the label prefix.
2241 // For captions, we just take the caption type
2242 Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
2243 if (caption_inset) {
2244 string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
2245 FloatList const & fl = cur.buffer()->params().documentClass().floats();
2246 if (fl.typeExist(ftype)) {
2247 Floating const & flt = fl.getType(ftype);
2248 name = from_utf8(flt.refPrefix());
2251 name = from_utf8(ftype.substr(0,3));
2253 // For section, subsection, etc...
2254 if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) {
2255 Layout const * layout2 = &(pars_[textpit - 1].layout());
2256 if (layout2->latextype != LATEX_PARAGRAPH) {
2261 if (layout->latextype != LATEX_PARAGRAPH)
2262 name = layout->refprefix;
2264 // If none of the above worked, see if the inset knows.
2266 InsetLayout const & il = cur.inset().getLayout();
2267 name = il.refprefix();
2272 docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE);
2274 // The return string of math matrices might contain linebreaks
2275 par_text = subst(par_text, '\n', '-');
2276 int const numwords = 3;
2277 for (int i = 0; i < numwords; ++i) {
2278 if (par_text.empty())
2281 par_text = split(par_text, head, ' ');
2282 // Is it legal to use spaces in labels ?
2288 // Make sure it isn't too long
2289 unsigned int const max_label_length = 32;
2290 if (text.size() > max_label_length)
2291 text.resize(max_label_length);
2294 text = name + ':' + text;
2296 // We need a unique label
2297 docstring label = text;
2299 while (cur.buffer()->activeLabel(label)) {
2300 label = text + '-' + convert<docstring>(i);
2308 docstring Text::asString(int options) const
2310 return asString(0, pars_.size(), options);
2314 docstring Text::asString(pit_type beg, pit_type end, int options) const
2316 size_t i = size_t(beg);
2317 docstring str = pars_[i].asString(options);
2318 for (++i; i != size_t(end); ++i) {
2320 str += pars_[i].asString(options);
2326 void Text::shortenForOutliner(docstring & str, size_t const maxlen)
2328 support::truncateWithEllipsis(str, maxlen);
2329 for (char_type & c : str)
2330 if (c == L'\n' || c == L'\t')
2335 void Text::forOutliner(docstring & os, size_t const maxlen,
2336 bool const shorten) const
2338 pit_type end = pars_.size() - 1;
2339 if (0 <= end && !pars_[0].labelString().empty())
2340 os += pars_[0].labelString() + ' ';
2341 forOutliner(os, maxlen, 0, end, shorten);
2345 void Text::forOutliner(docstring & os, size_t const maxlen,
2346 pit_type pit_start, pit_type pit_end,
2347 bool const shorten) const
2349 size_t tmplen = shorten ? maxlen + 1 : maxlen;
2350 pit_type end = min(size_t(pit_end), pars_.size() - 1);
2352 for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) {
2355 // This function lets the first label be treated separately
2356 pars_[i].forOutliner(os, tmplen, false, !first);
2360 shortenForOutliner(os, maxlen);
2364 void Text::charsTranspose(Cursor & cur)
2366 LBUFERR(this == cur.text());
2368 pos_type pos = cur.pos();
2370 // If cursor is at beginning or end of paragraph, do nothing.
2371 if (pos == cur.lastpos() || pos == 0)
2374 Paragraph & par = cur.paragraph();
2376 // Get the positions of the characters to be transposed.
2377 pos_type pos1 = pos - 1;
2378 pos_type pos2 = pos;
2380 // In change tracking mode, ignore deleted characters.
2381 while (pos2 < cur.lastpos() && par.isDeleted(pos2))
2383 if (pos2 == cur.lastpos())
2386 while (pos1 >= 0 && par.isDeleted(pos1))
2391 // Don't do anything if one of the "characters" is not regular text.
2392 if (par.isInset(pos1) || par.isInset(pos2))
2395 // Store the characters to be transposed (including font information).
2396 char_type const char1 = par.getChar(pos1);
2398 par.getFontSettings(cur.buffer()->params(), pos1);
2400 char_type const char2 = par.getChar(pos2);
2402 par.getFontSettings(cur.buffer()->params(), pos2);
2404 // And finally, we are ready to perform the transposition.
2405 // Track the changes if Change Tracking is enabled.
2406 bool const trackChanges = cur.buffer()->params().track_changes;
2410 par.eraseChar(pos2, trackChanges);
2411 par.eraseChar(pos1, trackChanges);
2412 par.insertChar(pos1, char2, font2, trackChanges);
2413 par.insertChar(pos2, char1, font1, trackChanges);
2415 cur.checkBufferStructure();
2417 // After the transposition, move cursor to after the transposition.
2418 setCursor(cur, cur.pit(), pos2);
2423 DocIterator Text::macrocontextPosition() const
2425 return macrocontext_position_;
2429 void Text::setMacrocontextPosition(DocIterator const & pos)
2431 macrocontext_position_ = pos;
2435 bool Text::completionSupported(Cursor const & cur) const
2437 Paragraph const & par = cur.paragraph();
2438 return !cur.buffer()->isReadonly()
2441 && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos()))
2442 && !par.isWordSeparator(cur.pos() - 1);
2446 CompletionList const * Text::createCompletionList(Cursor const & cur) const
2448 WordList const & list = theWordList(cur.getFont().language()->lang());
2449 return new TextCompletionList(cur, list);
2453 bool Text::insertCompletion(Cursor & cur, docstring const & s)
2455 LBUFERR(cur.bv().cursor() == cur);
2456 if (cur.buffer()->isReadonly())
2460 cur.bv().cursor() = cur;
2461 if (!(cur.result().screenUpdate() & Update::Force))
2462 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
2467 docstring Text::completionPrefix(Cursor const & cur) const
2469 CursorSlice from = cur.top();
2470 CursorSlice to = from;
2471 getWord(from, to, PREVIOUS_WORD);
2473 return cur.paragraph().asString(from.pos(), to.pos());
2476 bool Text::isMainText() const
2478 return &owner_->buffer().text() == this;
2482 // Note that this is supposed to return a fully realized font.
2483 FontInfo Text::layoutFont(pit_type const pit) const
2485 Layout const & layout = pars_[pit].layout();
2487 if (!pars_[pit].getDepth()) {
2488 FontInfo lf = layout.resfont;
2489 // In case the default family has been customized
2490 if (layout.font.family() == INHERIT_FAMILY)
2491 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
2492 FontInfo icf = (!isMainText())
2493 // inside insets, we call the getFont() method
2495 // outside, we access the layout font directly
2496 : owner_->getLayout().font();
2501 FontInfo font = layout.font;
2502 // Realize with the fonts of lesser depth.
2503 //font.realize(outerFont(pit));
2504 font.realize(owner_->buffer().params().getFont().fontInfo());
2510 // Note that this is supposed to return a fully realized font.
2511 FontInfo Text::labelFont(Paragraph const & par) const
2513 Buffer const & buffer = owner_->buffer();
2514 Layout const & layout = par.layout();
2516 if (!par.getDepth()) {
2517 FontInfo lf = layout.reslabelfont;
2518 // In case the default family has been customized
2519 if (layout.labelfont.family() == INHERIT_FAMILY)
2520 lf.setFamily(buffer.params().getFont().fontInfo().family());
2524 FontInfo font = layout.labelfont;
2525 // Realize with the fonts of lesser depth.
2526 font.realize(buffer.params().getFont().fontInfo());
2532 void Text::setCharFont(pit_type pit,
2533 pos_type pos, Font const & fnt, Font const & display_font)
2535 Buffer const & buffer = owner_->buffer();
2537 Layout const & layout = pars_[pit].layout();
2539 // Get concrete layout font to reduce against
2540 FontInfo layoutfont;
2542 if (pos < pars_[pit].beginOfBody())
2543 layoutfont = layout.labelfont;
2545 layoutfont = layout.font;
2547 // Realize against environment font information
2548 if (pars_[pit].getDepth()) {
2550 while (!layoutfont.resolved() &&
2551 tp != pit_type(paragraphs().size()) &&
2552 pars_[tp].getDepth()) {
2554 if (tp != pit_type(paragraphs().size()))
2555 layoutfont.realize(pars_[tp].layout().font);
2559 // Inside inset, apply the inset's font attributes if any
2562 layoutfont.realize(display_font.fontInfo());
2564 layoutfont.realize(buffer.params().getFont().fontInfo());
2566 // Now, reduce font against full layout font
2567 font.fontInfo().reduce(layoutfont);
2569 pars_[pit].setFont(pos, font);
2573 void Text::setInsetFont(BufferView const & bv, pit_type pit,
2574 pos_type pos, Font const & font)
2576 Inset * const inset = pars_[pit].getInset(pos);
2577 LASSERT(inset && inset->resetFontEdit(), return);
2579 idx_type endidx = inset->nargs();
2580 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
2581 Text * text = cs.text();
2583 // last position of the cell
2584 CursorSlice cellend = cs;
2585 cellend.pit() = cellend.lastpit();
2586 cellend.pos() = cellend.lastpos();
2587 text->setFont(bv, cs, cellend, font);
2593 void Text::setLayout(pit_type start, pit_type end,
2594 docstring const & layout)
2596 // FIXME: make this work in multicell selection case
2597 LASSERT(start != end, return);
2599 Buffer const & buffer = owner_->buffer();
2600 BufferParams const & bp = buffer.params();
2601 Layout const & lyxlayout = bp.documentClass()[layout];
2603 for (pit_type pit = start; pit != end; ++pit) {
2604 Paragraph & par = pars_[pit];
2605 // Is this a separating paragraph? If so,
2606 // this needs to be standard layout
2607 bool const is_separator = par.size() == 1
2608 && par.isEnvSeparator(0);
2609 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
2610 if (lyxlayout.margintype == MARGIN_MANUAL)
2611 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
2614 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
2618 // set layout over selection and make a total rebreak of those paragraphs
2619 void Text::setLayout(Cursor & cur, docstring const & layout)
2621 LBUFERR(this == cur.text());
2623 pit_type start = cur.selBegin().pit();
2624 pit_type end = cur.selEnd().pit() + 1;
2625 cur.recordUndoSelection();
2626 setLayout(start, end, layout);
2628 cur.setCurrentFont();
2629 cur.forceBufferUpdate();
2633 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
2634 Paragraph const & par, int max_depth)
2636 int const depth = par.params().depth();
2637 if (type == Text::INC_DEPTH && depth < max_depth)
2639 if (type == Text::DEC_DEPTH && depth > 0)
2645 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
2647 LBUFERR(this == cur.text());
2648 // this happens when selecting several cells in tabular (bug 2630)
2649 if (cur.selBegin().idx() != cur.selEnd().idx())
2652 pit_type const beg = cur.selBegin().pit();
2653 pit_type const end = cur.selEnd().pit() + 1;
2654 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2656 for (pit_type pit = beg; pit != end; ++pit) {
2657 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
2659 max_depth = pars_[pit].getMaxDepthAfter();
2665 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
2667 LBUFERR(this == cur.text());
2668 pit_type const beg = cur.selBegin().pit();
2669 pit_type const end = cur.selEnd().pit() + 1;
2670 cur.recordUndoSelection();
2671 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2673 for (pit_type pit = beg; pit != end; ++pit) {
2674 Paragraph & par = pars_[pit];
2675 if (lyx::changeDepthAllowed(type, par, max_depth)) {
2676 int const depth = par.params().depth();
2677 if (type == INC_DEPTH)
2678 par.params().depth(depth + 1);
2680 par.params().depth(depth - 1);
2682 max_depth = par.getMaxDepthAfter();
2684 cur.setCurrentFont();
2685 // this handles the counter labels, and also fixes up
2686 // depth values for follow-on (child) paragraphs
2687 cur.forceBufferUpdate();
2691 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
2693 LASSERT(this == cur.text(), return);
2695 // If there is a selection, record undo before the cursor font is changed.
2696 if (cur.selection())
2697 cur.recordUndoSelection();
2699 // Set the current_font
2700 // Determine basis font
2701 FontInfo layoutfont;
2702 pit_type pit = cur.pit();
2703 if (cur.pos() < pars_[pit].beginOfBody())
2704 layoutfont = labelFont(pars_[pit]);
2706 layoutfont = layoutFont(pit);
2708 // Update current font
2709 cur.real_current_font.update(font,
2710 cur.buffer()->params().language,
2713 // Reduce to implicit settings
2714 cur.current_font = cur.real_current_font;
2715 cur.current_font.fontInfo().reduce(layoutfont);
2716 // And resolve it completely
2717 cur.real_current_font.fontInfo().realize(layoutfont);
2719 // if there is no selection that's all we need to do
2720 if (!cur.selection())
2723 // Ok, we have a selection.
2724 Font newfont = font;
2727 // Toggling behaves as follows: We check the first character of the
2728 // selection. If it's (say) got EMPH on, then we set to off; if off,
2729 // then to on. With families and the like, we set it to INHERIT, if
2730 // we already have it.
2731 CursorSlice const & sl = cur.selBegin();
2732 Text const & text = *sl.text();
2733 Paragraph const & par = text.getPar(sl.pit());
2735 // get font at the position
2736 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
2737 text.outerFont(sl.pit()));
2738 FontInfo const & oldfi = oldfont.fontInfo();
2740 FontInfo & newfi = newfont.fontInfo();
2742 FontFamily newfam = newfi.family();
2743 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
2744 newfam == oldfi.family())
2745 newfi.setFamily(INHERIT_FAMILY);
2747 FontSeries newser = newfi.series();
2748 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
2749 newfi.setSeries(INHERIT_SERIES);
2751 FontShape newshp = newfi.shape();
2752 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
2753 newshp == oldfi.shape())
2754 newfi.setShape(INHERIT_SHAPE);
2756 ColorCode newcol = newfi.color();
2757 if (newcol != Color_none && newcol != Color_inherit
2758 && newcol != Color_ignore && newcol == oldfi.color())
2759 newfi.setColor(Color_none);
2762 if (newfi.emph() == FONT_TOGGLE)
2763 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
2764 if (newfi.underbar() == FONT_TOGGLE)
2765 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
2766 if (newfi.strikeout() == FONT_TOGGLE)
2767 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
2768 if (newfi.xout() == FONT_TOGGLE)
2769 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
2770 if (newfi.uuline() == FONT_TOGGLE)
2771 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
2772 if (newfi.uwave() == FONT_TOGGLE)
2773 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
2774 if (newfi.noun() == FONT_TOGGLE)
2775 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
2776 if (newfi.number() == FONT_TOGGLE)
2777 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
2778 if (newfi.nospellcheck() == FONT_TOGGLE)
2779 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
2782 setFont(cur.bv(), cur.selectionBegin().top(),
2783 cur.selectionEnd().top(), newfont);
2787 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
2788 CursorSlice const & end, Font const & font)
2790 Buffer const & buffer = bv.buffer();
2792 // Don't use forwardChar here as ditend might have
2793 // pos() == lastpos() and forwardChar would miss it.
2794 // Can't use forwardPos either as this descends into
2796 Language const * language = buffer.params().language;
2797 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
2798 if (dit.pos() == dit.lastpos())
2800 pit_type const pit = dit.pit();
2801 pos_type const pos = dit.pos();
2802 Inset * inset = pars_[pit].getInset(pos);
2803 if (inset && inset->resetFontEdit()) {
2804 // We need to propagate the font change to all
2805 // text cells of the inset (bugs 1973, 6919).
2806 setInsetFont(bv, pit, pos, font);
2808 TextMetrics const & tm = bv.textMetrics(this);
2809 Font f = tm.displayFont(pit, pos);
2810 f.update(font, language);
2811 setCharFont(pit, pos, f, tm.font_);
2812 // font change may change language...
2813 // spell checker has to know that
2814 pars_[pit].requestSpellCheck(pos);
2819 bool Text::cursorTop(Cursor & cur)
2821 LBUFERR(this == cur.text());
2822 return setCursor(cur, 0, 0);
2826 bool Text::cursorBottom(Cursor & cur)
2828 LBUFERR(this == cur.text());
2829 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
2833 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
2835 LBUFERR(this == cur.text());
2836 // If the mask is completely neutral, tell user
2837 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
2838 // Could only happen with user style
2839 cur.message(_("No font change defined."));
2843 // Try implicit word selection
2844 // If there is a change in the language the implicit word selection
2846 CursorSlice const resetCursor = cur.top();
2847 bool const implicitSelection =
2848 font.language() == ignore_language
2849 && font.fontInfo().number() == FONT_IGNORE
2850 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
2853 setFont(cur, font, toggleall);
2855 // Implicit selections are cleared afterwards
2856 // and cursor is set to the original position.
2857 if (implicitSelection) {
2858 cur.clearSelection();
2859 cur.top() = resetCursor;
2863 // if there was no selection at all, the point was to change cursor font.
2864 // Otherwise, we want to reset it to local text font.
2865 if (cur.selection() || implicitSelection)
2866 cur.setCurrentFont();
2870 docstring Text::getStringForDialog(Cursor & cur)
2872 LBUFERR(this == cur.text());
2874 if (cur.selection())
2875 return cur.selectionAsString(false);
2877 // Try implicit word selection. If there is a change
2878 // in the language the implicit word selection is
2880 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2881 docstring const & retval = cur.selectionAsString(false);
2882 cur.clearSelection();
2887 void Text::setLabelWidthStringToSequence(Cursor const & cur,
2888 docstring const & s)
2891 // Find first of same layout in sequence
2892 while (!isFirstInSequence(c.pit())) {
2893 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
2896 // now apply label width string to every par
2898 depth_type const depth = c.paragraph().getDepth();
2899 Layout const & layout = c.paragraph().layout();
2900 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
2901 while (c.paragraph().getDepth() > depth) {
2903 if (c.pit() > c.lastpit())
2906 if (c.paragraph().getDepth() < depth)
2908 if (c.paragraph().layout() != layout)
2911 c.paragraph().setLabelWidthString(s);
2916 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
2918 LBUFERR(cur.text());
2921 string const argument = to_utf8(arg);
2922 depth_type priordepth = -1;
2925 c.setCursor(cur.selectionBegin());
2926 pit_type const last_pit = cur.selectionEnd().pit();
2927 for ( ; c.pit() <= last_pit ; ++c.pit()) {
2928 Paragraph & par = c.paragraph();
2929 ParagraphParameters params = par.params();
2930 params.read(argument, merge);
2931 // Changes to label width string apply to all paragraphs
2932 // with same layout in a sequence.
2933 // Do this only once for a selected range of paragraphs
2934 // of the same layout and depth.
2936 par.params().apply(params, par.layout());
2937 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2938 setLabelWidthStringToSequence(c, params.labelWidthString());
2939 priordepth = par.getDepth();
2940 priorlayout = par.layout();
2945 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
2947 LBUFERR(cur.text());
2949 depth_type priordepth = -1;
2952 c.setCursor(cur.selectionBegin());
2953 pit_type const last_pit = cur.selectionEnd().pit();
2954 for ( ; c.pit() <= last_pit ; ++c.pit()) {
2955 Paragraph & par = c.paragraph();
2956 // Changes to label width string apply to all paragraphs
2957 // with same layout in a sequence.
2958 // Do this only once for a selected range of paragraphs
2959 // of the same layout and depth.
2961 par.params().apply(p, par.layout());
2962 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2963 setLabelWidthStringToSequence(c,
2964 par.params().labelWidthString());
2965 priordepth = par.getDepth();
2966 priorlayout = par.layout();
2971 // just insert the inset and not move the cursor.
2972 bool Text::insertInset(Cursor & cur, Inset * inset)
2974 LBUFERR(this == cur.text());
2976 return cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
2977 Change(cur.buffer()->params().track_changes
2978 ? Change::INSERTED : Change::UNCHANGED));
2982 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
2983 bool setfont, bool boundary)
2985 TextMetrics const & tm = cur.bv().textMetrics(this);
2986 bool const update_needed = !tm.contains(pit);
2988 setCursorIntern(cur, pit, pos, setfont, boundary);
2989 return cur.bv().checkDepm(cur, old) || update_needed;
2993 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
2994 bool setfont, bool boundary)
2996 LBUFERR(this == cur.text());
2997 cur.boundary(boundary);
2998 cur.top().setPitPos(pit, pos);
3000 cur.setCurrentFont();
3004 bool Text::checkAndActivateInset(Cursor & cur, bool front)
3006 if (front && cur.pos() == cur.lastpos())
3008 if (!front && cur.pos() == 0)
3010 Inset * inset = front ? cur.nextInset() : cur.prevInset();
3011 if (!inset || !inset->editable())
3013 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3016 * Apparently, when entering an inset we are expected to be positioned
3017 * *before* it in the containing paragraph, regardless of the direction
3018 * from which we are entering. Otherwise, cursor placement goes awry,
3019 * and when we exit from the beginning, we'll be placed *after* the
3024 inset->edit(cur, front);
3025 cur.setCurrentFont();
3026 cur.boundary(false);
3031 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
3033 if (cur.pos() == -1)
3035 if (cur.pos() == cur.lastpos())
3037 Paragraph & par = cur.paragraph();
3038 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
3039 if (!inset || !inset->editable())
3041 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3043 inset->edit(cur, movingForward,
3044 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
3045 cur.setCurrentFont();
3046 cur.boundary(false);
3051 bool Text::cursorBackward(Cursor & cur)
3053 // Tell BufferView to test for FitCursor in any case!
3054 cur.screenUpdateFlags(Update::FitCursor);
3056 // not at paragraph start?
3057 if (cur.pos() > 0) {
3058 // if on right side of boundary (i.e. not at paragraph end, but line end)
3059 // -> skip it, i.e. set boundary to true, i.e. go only logically left
3060 // there are some exceptions to ignore this: lineseps, newlines, spaces
3062 // some effectless debug code to see the values in the debugger
3063 bool bound = cur.boundary();
3064 int rowpos = cur.textRow().pos();
3065 int pos = cur.pos();
3066 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
3067 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
3068 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
3070 if (!cur.boundary() &&
3071 cur.textRow().pos() == cur.pos() &&
3072 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
3073 !cur.paragraph().isNewline(cur.pos() - 1) &&
3074 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
3075 !cur.paragraph().isSeparator(cur.pos() - 1)) {
3076 return setCursor(cur, cur.pit(), cur.pos(), true, true);
3079 // go left and try to enter inset
3080 if (checkAndActivateInset(cur, false))
3083 // normal character left
3084 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
3087 // move to the previous paragraph or do nothing
3088 if (cur.pit() > 0) {
3089 Paragraph & par = getPar(cur.pit() - 1);
3090 pos_type lastpos = par.size();
3091 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
3092 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
3094 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
3100 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
3102 Cursor temp_cur = cur;
3103 temp_cur.posVisLeft(skip_inset);
3104 if (temp_cur.depth() > cur.depth()) {
3108 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3109 true, temp_cur.boundary());
3113 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
3115 Cursor temp_cur = cur;
3116 temp_cur.posVisRight(skip_inset);
3117 if (temp_cur.depth() > cur.depth()) {
3121 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3122 true, temp_cur.boundary());
3126 bool Text::cursorForward(Cursor & cur)
3128 // Tell BufferView to test for FitCursor in any case!
3129 cur.screenUpdateFlags(Update::FitCursor);
3131 // not at paragraph end?
3132 if (cur.pos() != cur.lastpos()) {
3133 // in front of editable inset, i.e. jump into it?
3134 if (checkAndActivateInset(cur, true))
3137 TextMetrics const & tm = cur.bv().textMetrics(this);
3138 // if left of boundary -> just jump to right side
3139 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
3140 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
3141 return setCursor(cur, cur.pit(), cur.pos(), true, false);
3143 // next position is left of boundary,
3144 // but go to next line for special cases like space, newline, linesep
3146 // some effectless debug code to see the values in the debugger
3147 int endpos = cur.textRow().endpos();
3148 int lastpos = cur.lastpos();
3149 int pos = cur.pos();
3150 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
3151 bool newline = cur.paragraph().isNewline(cur.pos());
3152 bool sep = cur.paragraph().isSeparator(cur.pos());
3153 if (cur.pos() != cur.lastpos()) {
3154 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
3155 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
3156 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
3159 if (cur.textRow().endpos() == cur.pos() + 1) {
3160 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
3161 cur.pos() + 1 == cur.lastpos() &&
3162 cur.pit() != cur.lastpit()) {
3163 // move to next paragraph
3164 return setCursor(cur, cur.pit() + 1, 0, true, false);
3165 } else if (cur.textRow().endpos() != cur.lastpos() &&
3166 !cur.paragraph().isNewline(cur.pos()) &&
3167 !cur.paragraph().isEnvSeparator(cur.pos()) &&
3168 !cur.paragraph().isLineSeparator(cur.pos()) &&
3169 !cur.paragraph().isSeparator(cur.pos())) {
3170 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3174 // in front of RTL boundary? Stay on this side of the boundary because:
3175 // ab|cDDEEFFghi -> abc|DDEEFFghi
3176 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
3177 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3180 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
3183 // move to next paragraph
3184 if (cur.pit() != cur.lastpit())
3185 return setCursor(cur, cur.pit() + 1, 0, true, false);
3190 bool Text::cursorUpParagraph(Cursor & cur)
3192 bool updated = false;
3194 updated = setCursor(cur, cur.pit(), 0);
3195 else if (cur.pit() != 0)
3196 updated = setCursor(cur, cur.pit() - 1, 0);
3201 bool Text::cursorDownParagraph(Cursor & cur)
3203 bool updated = false;
3204 if (cur.pit() != cur.lastpit())
3205 if (lyxrc.mac_like_cursor_movement)
3206 if (cur.pos() == cur.lastpos())
3207 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
3209 updated = setCursor(cur, cur.pit(), cur.lastpos());
3211 updated = setCursor(cur, cur.pit() + 1, 0);
3213 updated = setCursor(cur, cur.pit(), cur.lastpos());
3219 /** delete num_spaces characters between from and to. Return the
3220 * number of spaces that got physically deleted (not marked as
3222 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
3223 int num_spaces, bool const trackChanges)
3225 if (num_spaces <= 0)
3228 // First, delete spaces marked as inserted
3230 while (pos < to && num_spaces > 0) {
3231 Change const & change = par.lookupChange(pos);
3232 if (change.inserted() && !change.currentAuthor()) {
3233 par.eraseChar(pos, trackChanges);
3240 // Then remove remaining spaces
3241 int const psize = par.size();
3242 par.eraseChars(from, from + num_spaces, trackChanges);
3243 return psize - par.size();
3249 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
3250 Cursor & old, bool & need_anchor_change)
3252 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
3254 Paragraph & oldpar = old.paragraph();
3255 bool const trackChanges = cur.buffer()->params().track_changes;
3256 bool result = false;
3258 // We do nothing if cursor did not move
3259 if (cur.top() == old.top())
3262 // We do not do anything on read-only documents
3263 if (cur.buffer()->isReadonly())
3266 // Whether a common inset is found and whether the cursor is still in
3267 // the same paragraph (possibly nested).
3268 int const depth = cur.find(&old.inset());
3269 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
3270 && old.pit() == cur[depth].pit();
3273 * (1) If the chars around the old cursor were spaces and the
3274 * paragraph is not in free spacing mode, delete some of them, but
3275 * only if the cursor has really moved.
3278 /* There are still some small problems that can lead to
3279 double spaces stored in the document file or space at
3280 the beginning of paragraphs(). This happens if you have
3281 the cursor between two spaces and then save. Or if you
3282 cut and paste and the selection has a space at the
3283 beginning and then save right after the paste. (Lgb)
3285 if (!oldpar.isFreeSpacing()) {
3286 // find range of spaces around cursors
3287 pos_type from = old.pos();
3289 && oldpar.isLineSeparator(from - 1)
3290 && !oldpar.isDeleted(from - 1))
3292 pos_type to = old.pos();
3293 while (to < old.lastpos()
3294 && oldpar.isLineSeparator(to)
3295 && !oldpar.isDeleted(to))
3298 int num_spaces = to - from;
3299 // If we are not at the start of the paragraph, keep one space
3300 if (from != to && from > 0)
3303 // If cursor is inside range, keep one additional space
3304 if (same_par && cur.pos() > from && cur.pos() < to)
3307 // Remove spaces and adapt cursor.
3308 if (num_spaces > 0) {
3311 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
3312 // correct cur position
3313 // FIXME: there can be other cursors pointing there, we should update them
3315 if (cur[depth].pos() >= to)
3316 cur[depth].pos() -= deleted;
3317 else if (cur[depth].pos() > from)
3318 cur[depth].pos() = min(from + 1, old.lastpos());
3319 need_anchor_change = true;
3326 * (2) If the paragraph where the cursor was is empty, delete it
3329 // only do our other magic if we changed paragraph
3333 // only do our magic if the paragraph is empty
3334 if (!oldpar.empty())
3337 // don't delete anything if this is the ONLY paragraph!
3338 if (old.lastpit() == 0)
3341 // Do not delete empty paragraphs with keepempty set.
3342 if (oldpar.allowEmpty())
3346 old.recordUndo(max(old.pit() - 1, pit_type(0)),
3347 min(old.pit() + 1, old.lastpit()));
3348 ParagraphList & plist = old.text()->paragraphs();
3349 bool const soa = oldpar.params().startOfAppendix();
3350 plist.erase(plist.iterator_at(old.pit()));
3351 // do not lose start of appendix marker (bug 4212)
3352 if (soa && old.pit() < pit_type(plist.size()))
3353 plist[old.pit()].params().startOfAppendix(true);
3355 // see #warning (FIXME?) above
3356 if (cur.depth() >= old.depth()) {
3357 CursorSlice & curslice = cur[old.depth() - 1];
3358 if (&curslice.inset() == &old.inset()
3359 && curslice.idx() == old.idx()
3360 && curslice.pit() > old.pit()) {
3362 // since a paragraph has been deleted, all the
3363 // insets after `old' have been copied and
3364 // their address has changed. Therefore we
3365 // need to `regenerate' cur. (JMarc)
3366 cur.updateInsets(&(cur.bottom().inset()));
3367 need_anchor_change = true;
3375 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
3377 pos_type last_pos = pars_[last].size() - 1;
3378 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
3382 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
3383 pos_type first_pos, pos_type last_pos,
3386 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
3388 for (pit_type pit = first; pit <= last; ++pit) {
3389 Paragraph & par = pars_[pit];
3392 * (1) Delete consecutive spaces
3394 if (!par.isFreeSpacing()) {
3395 pos_type from = (pit == first) ? first_pos : 0;
3396 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
3397 while (from < to_pos) {
3399 while (from < par.size()
3400 && (!par.isLineSeparator(from) || par.isDeleted(from)))
3402 // find string of spaces
3404 while (to < par.size()
3405 && par.isLineSeparator(to) && !par.isDeleted(to))
3407 // empty? We are done
3411 int num_spaces = to - from;
3413 // If we are not at the extremity of the paragraph, keep one space
3414 if (from != to && from > 0 && to < par.size())
3417 // Remove spaces if needed
3418 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
3419 from = to - deleted;
3424 * (2) Delete empty pragraphs
3427 // don't delete anything if this is the only remaining paragraph
3428 // within the given range. Note: Text::acceptOrRejectChanges()
3429 // sets the cursor to 'first' after calling DEPM
3433 // don't delete empty paragraphs with keepempty set
3434 if (par.allowEmpty())
3437 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
3438 pars_.erase(pars_.iterator_at(pit));
3450 typedef limited_stack<pair<docstring, Font>> FontStack;
3451 static FontStack freeFonts(15);
3452 static bool toggleall = false;
3454 void toggleAndShow(Cursor & cur, Text * text,
3455 Font const & font, bool togall = true)
3457 text->toggleFree(cur, font, togall);
3459 if (font.language() != ignore_language ||
3460 font.fontInfo().number() != FONT_IGNORE) {
3461 TextMetrics const & tm = cur.bv().textMetrics(text);
3462 if (cur.boundary() != tm.isRTLBoundary(cur.pit(), cur.pos(),
3463 cur.real_current_font))
3464 text->setCursor(cur, cur.pit(), cur.pos(),
3465 false, !cur.boundary());
3466 if (font.language() != ignore_language)
3467 // We need a buffer update if we change the language
3468 // (e.g., with info insets or if the selection contains
3470 cur.forceBufferUpdate();
3475 void moveCursor(Cursor & cur, bool selecting)
3477 if (selecting || cur.mark())
3482 void finishChange(Cursor & cur, bool selecting)
3485 moveCursor(cur, selecting);
3489 void mathDispatch(Cursor & cur, FuncRequest const & cmd)
3492 docstring sel = cur.selectionAsString(false);
3494 // It may happen that sel is empty but there is a selection
3495 replaceSelection(cur);
3497 // Is this a valid formula?
3501 #ifdef ENABLE_ASSERTIONS
3502 const int old_pos = cur.pos();
3504 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
3505 #ifdef ENABLE_ASSERTIONS
3506 LATTEST(old_pos == cur.pos());
3508 cur.nextInset()->edit(cur, true);
3509 if (cmd.action() != LFUN_MATH_MODE)
3510 // LFUN_MATH_MODE has a different meaning in math mode
3513 InsetMathHull * formula = new InsetMathHull(cur.buffer());
3514 string const selstr = to_utf8(sel);
3515 istringstream is(selstr);
3518 if (!formula->readQuiet(lex)) {
3519 // No valid formula, let's try with delims
3520 is.str("$" + selstr + "$");
3522 if (!formula->readQuiet(lex)) {
3523 // Still not valid, leave it as is
3530 cur.insert(formula);
3531 cur.nextInset()->edit(cur, true);
3532 LASSERT(cur.inMathed(), return);
3535 cur.selection(true);
3536 cur.pos() = cur.lastpos();
3537 if (cmd.action() != LFUN_MATH_MODE)
3538 // LFUN_MATH_MODE has a different meaning in math mode
3540 cur.clearSelection();
3541 cur.pos() = cur.lastpos();
3545 cur.message(from_utf8(N_("Math editor mode")));
3547 cur.message(from_utf8(N_("No valid math formula")));
3551 void regexpDispatch(Cursor & cur, FuncRequest const & cmd)
3553 LASSERT(cmd.action() == LFUN_REGEXP_MODE, return);
3554 if (cur.inRegexped()) {
3555 cur.message(_("Already in regular expression mode"));
3559 docstring sel = cur.selectionAsString(false);
3561 // It may happen that sel is empty but there is a selection
3562 replaceSelection(cur);
3564 cur.insert(new InsetMathHull(cur.buffer(), hullRegexp));
3565 cur.nextInset()->edit(cur, true);
3566 cur.niceInsert(sel);
3568 cur.message(_("Regexp editor mode"));
3572 void specialChar(Cursor & cur, InsetSpecialChar::Kind kind)
3575 cap::replaceSelection(cur);
3576 cur.insert(new InsetSpecialChar(kind));
3581 void ipaChar(Cursor & cur, InsetIPAChar::Kind kind)
3584 cap::replaceSelection(cur);
3585 cur.insert(new InsetIPAChar(kind));
3590 bool doInsertInset(Cursor & cur, Text * text,
3591 FuncRequest const & cmd, bool edit,
3592 bool pastesel, bool resetfont = false)
3594 Buffer & buffer = cur.bv().buffer();
3595 BufferParams const & bparams = buffer.params();
3596 Inset * inset = createInset(&buffer, cmd);
3600 if (InsetCollapsible * ci = inset->asInsetCollapsible())
3601 ci->setButtonLabel();
3604 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3605 bool cotextinsert = false;
3606 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3607 Layout const & lay = cur.paragraph().layout();
3608 Layout::LaTeXArgMap args = lay.args();
3609 Layout::LaTeXArgMap::const_iterator const lait = args.find(ia->name());
3610 if (lait != args.end())
3611 cotextinsert = (*lait).second.insertcotext;
3613 InsetLayout const & il = cur.inset().getLayout();
3615 Layout::LaTeXArgMap::const_iterator const ilait = args.find(ia->name());
3616 if (ilait != args.end())
3617 cotextinsert = (*ilait).second.insertcotext;
3619 // The argument requests to insert a copy of the co-text to the inset
3622 // If we have a selection within a paragraph, use this
3623 if (cur.selection() && cur.selBegin().pit() == cur.selEnd().pit())
3624 ds = cur.selectionAsString(false);
3625 // else use the whole paragraph
3627 ds = cur.paragraph().asString();
3628 text->insertInset(cur, inset);
3629 ia->init(cur.paragraph());
3631 inset->edit(cur, true);
3632 // Now put co-text into inset
3633 Font const f(inherit_font, cur.current_font.language());
3635 cur.text()->insertStringAsLines(cur, ds, f);
3636 cur.leaveInset(*inset);
3642 bool gotsel = false;
3643 bool move_layout = false;
3644 if (cur.selection()) {
3645 if (cmd.action() == LFUN_INDEX_INSERT)
3646 copySelectionToTemp(cur);
3648 cutSelectionToTemp(cur, pastesel);
3649 /* Move layout information inside the inset if the whole
3650 * paragraph and the inset allows setting layout
3651 * FIXME: figure out a good test in the environment case (see #12251).
3653 if (cur.paragraph().layout().isCommand()
3654 && (cur.paragraph().empty()
3655 || cur.paragraph().isDeleted(0, cur.paragraph().size()))
3656 && !inset->forcePlainLayout()) {
3657 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3661 cur.clearSelection();
3663 } else if (cmd.action() == LFUN_INDEX_INSERT) {
3664 gotsel = text->selectWordWhenUnderCursor(cur, WHOLE_WORD);
3665 copySelectionToTemp(cur);
3666 cur.clearSelection();
3668 text->insertInset(cur, inset);
3670 InsetText * inset_text = inset->asInsetText();
3672 Font const & font = inset->inheritFont()
3673 ? cur.bv().textMetrics(text).displayFont(cur.pit(), cur.pos())
3674 : bparams.getFont();
3675 inset_text->setOuterFont(cur.bv(), font.fontInfo());
3678 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3679 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3680 ia->init(cur.paragraph());
3684 inset->edit(cur, true);
3686 if (!gotsel || !pastesel)
3689 pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
3690 cur.buffer()->errors("Paste");
3691 cur.clearSelection(); // bug 393
3695 // Reset of font (not language) is requested.
3696 // Used by InsetIndex (#11961).
3697 Language const * lang = cur.getFont().language();
3698 Font font(bparams.getFont().fontInfo(), lang);
3699 cur.paragraph().resetFonts(font);
3701 inset_text->fixParagraphsFont();
3704 /* If the containing paragraph has kept its layout, reset the
3705 * layout of the first paragraph of the inset.
3708 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3709 // FIXME: what does this do?
3710 if (cmd.action() == LFUN_FLEX_INSERT)
3713 cur.leaveInset(*inset);
3714 if (cmd.action() == LFUN_PREVIEW_INSERT
3715 || cmd.action() == LFUN_IPA_INSERT)
3717 notifyCursorLeavesOrEnters(old, cur);
3719 cur.leaveInset(*inset);
3720 // reset surrounding par to default
3721 DocumentClass const & dc = bparams.documentClass();
3722 docstring const layoutname = inset->usePlainLayout()
3723 ? dc.plainLayoutName()
3724 : dc.defaultLayoutName();
3725 text->setLayout(cur, layoutname);
3731 /// the type of outline operation
3733 OutlineUp, // Move this header with text down
3734 OutlineDown, // Move this header with text up
3735 OutlineIn, // Make this header deeper
3736 OutlineOut // Make this header shallower
3740 void insertSeparator(Cursor const & cur, depth_type const depth)
3742 Buffer & buf = *cur.buffer();
3743 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
3744 DocumentClass const & tc = buf.params().documentClass();
3745 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
3746 + from_ascii("\" ignoreautonests")));
3747 // FIXME: Bibitem mess!
3748 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
3749 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
3750 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
3751 while (cur.paragraph().params().depth() > depth)
3752 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
3756 void outline(OutlineOp mode, Cursor & cur, bool local)
3758 Buffer & buf = *cur.buffer();
3759 Text & text = *cur.text();
3760 pit_type & pit = cur.pit();
3761 ParagraphList & pars = text.paragraphs();
3762 ParagraphList::iterator const bgn = pars.begin();
3763 // The first paragraph of the area to be copied:
3764 ParagraphList::iterator start = pars.iterator_at(pit);
3765 // The final paragraph of area to be copied:
3766 ParagraphList::iterator finish = start;
3767 ParagraphList::iterator const end = pars.end();
3768 depth_type const current_depth = cur.paragraph().params().depth();
3770 int const thistoclevel = text.getTocLevel(distance(bgn, start));
3773 // Move out (down) from this section header
3777 if (!local || (mode != OutlineIn && mode != OutlineOut)) {
3778 // Seek the one (on same level) below
3779 for (; finish != end; ++finish) {
3780 toclevel = text.getTocLevel(distance(bgn, finish));
3781 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3788 if (start == pars.begin())
3791 ParagraphList::iterator dest = start;
3792 // Move out (up) from this header
3795 // Search previous same-level header above
3798 toclevel = text.getTocLevel(distance(bgn, dest));
3800 && (toclevel == Layout::NOT_IN_TOC
3801 || toclevel > thistoclevel));
3802 // Not found; do nothing
3803 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3805 pit_type newpit = distance(bgn, dest);
3806 pit_type const len = distance(start, finish);
3807 pit_type const deletepit = pit + len;
3808 buf.undo().recordUndo(cur, newpit, deletepit - 1);
3809 // If we move an environment upwards, make sure it is
3810 // separated from its new neighbour below:
3811 // If an environment of the same layout follows, and the moved
3812 // paragraph sequence does not end with a separator, insert one.
3813 ParagraphList::iterator lastmoved = finish;
3815 if (start->layout().isEnvironment()
3816 && dest->layout() == start->layout()
3817 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3818 cur.pit() = distance(bgn, lastmoved);
3819 cur.pos() = cur.lastpos();
3820 insertSeparator(cur, current_depth);
3823 // Likewise, if we moved an environment upwards, make sure it
3824 // is separated from its new neighbour above.
3825 // The paragraph before the target of movement
3827 ParagraphList::iterator before = dest;
3829 // Get the parent paragraph (outer in nested context)
3830 pit_type const parent =
3831 before->params().depth() > current_depth
3832 ? text.depthHook(distance(bgn, before), current_depth)
3833 : distance(bgn, before);
3834 // If a environment with same layout preceeds the moved one in the new
3835 // position, and there is no separator yet, insert one.
3836 if (start->layout().isEnvironment()
3837 && pars[parent].layout() == start->layout()
3838 && !before->isEnvSeparator(before->beginOfBody())) {
3839 cur.pit() = distance(bgn, before);
3840 cur.pos() = cur.lastpos();
3841 insertSeparator(cur, current_depth);
3845 newpit = distance(bgn, dest);
3846 pars.splice(dest, start, finish);
3854 // Go one down from *this* header:
3855 ParagraphList::iterator dest = next(finish, 1);
3856 // Go further down to find header to insert in front of:
3857 for (; dest != end; ++dest) {
3858 toclevel = text.getTocLevel(distance(bgn, dest));
3859 if (toclevel != Layout::NOT_IN_TOC
3860 && toclevel <= thistoclevel)
3863 // One such was found, so go on...
3864 // If we move an environment downwards, make sure it is
3865 // separated from its new neighbour above.
3866 pit_type newpit = distance(bgn, dest);
3867 buf.undo().recordUndo(cur, pit, newpit - 1);
3868 // The paragraph before the target of movement
3869 ParagraphList::iterator before = dest;
3871 // Get the parent paragraph (outer in nested context)
3872 pit_type const parent =
3873 before->params().depth() > current_depth
3874 ? text.depthHook(distance(bgn, before), current_depth)
3875 : distance(bgn, before);
3876 // If a environment with same layout preceeds the moved one in the new
3877 // position, and there is no separator yet, insert one.
3878 if (start->layout().isEnvironment()
3879 && pars[parent].layout() == start->layout()
3880 && !before->isEnvSeparator(before->beginOfBody())) {
3881 cur.pit() = distance(bgn, before);
3882 cur.pos() = cur.lastpos();
3883 insertSeparator(cur, current_depth);
3886 // Likewise, make sure moved environments are separated
3887 // from their new neighbour below:
3888 // If an environment of the same layout follows, and the moved
3889 // paragraph sequence does not end with a separator, insert one.
3890 ParagraphList::iterator lastmoved = finish;
3893 && start->layout().isEnvironment()
3894 && dest->layout() == start->layout()
3895 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3896 cur.pit() = distance(bgn, lastmoved);
3897 cur.pos() = cur.lastpos();
3898 insertSeparator(cur, current_depth);
3901 newpit = distance(bgn, dest);
3902 pit_type const len = distance(start, finish);
3903 pars.splice(dest, start, finish);
3904 cur.pit() = newpit - len;
3909 // We first iterate without actually doing something
3910 // in order to check whether the action flattens the structure.
3911 // If so, warn (#11178).
3912 ParagraphList::iterator cstart = start;
3913 bool strucchange = false;
3914 for (; cstart != finish; ++cstart) {
3915 toclevel = text.getTocLevel(distance(bgn, cstart));
3916 if (toclevel == Layout::NOT_IN_TOC)
3919 DocumentClass const & tc = buf.params().documentClass();
3920 int newtoclevel = -1;
3921 if (mode == OutlineIn) {
3922 if (toclevel == -1 && tc.getTOCLayout().toclevel > 0)
3923 // we are at part but don't have a chapter
3924 newtoclevel = tc.getTOCLayout().toclevel;
3926 newtoclevel = toclevel + 1;
3928 if (tc.getTOCLayout().toclevel == toclevel && tc.min_toclevel() < toclevel)
3929 // we are at highest level, but there is still part
3930 newtoclevel = tc.min_toclevel();
3932 newtoclevel = toclevel - 1;
3936 for (auto const & lay : tc) {
3937 if (lay.toclevel == newtoclevel
3938 && lay.isNumHeadingLabelType()
3939 && cstart->layout().isNumHeadingLabelType()) {
3950 && frontend::Alert::prompt(_("Action flattens document structure"),
3951 _("This action will cause some headings that have been "
3952 "on different level before to be on the same level "
3953 "since there is no more lower or higher heading level. "
3956 _("&Yes, continue nonetheless"),
3957 _("&No, quit operation")) == 1)
3960 pit_type const len = distance(start, finish);
3961 buf.undo().recordUndo(cur, pit, pit + len - 1);
3962 for (; start != finish; ++start) {
3963 toclevel = text.getTocLevel(distance(bgn, start));
3964 if (toclevel == Layout::NOT_IN_TOC)
3967 DocumentClass const & tc = buf.params().documentClass();
3968 int newtoclevel = -1;
3969 if (mode == OutlineIn) {
3970 if (toclevel == -1 && tc.getTOCLayout().toclevel > 0)
3971 // we are at part but don't have a chapter
3972 newtoclevel = tc.getTOCLayout().toclevel;
3974 newtoclevel = toclevel + 1;
3976 if (tc.getTOCLayout().toclevel == toclevel && tc.min_toclevel() < toclevel)
3977 // we are at highest level, but there is still part
3978 newtoclevel = tc.min_toclevel();
3980 newtoclevel = toclevel - 1;
3983 for (auto const & lay : tc) {
3984 if (lay.toclevel == newtoclevel
3985 && lay.isNumHeadingLabelType()
3986 && start->layout().isNumHeadingLabelType()) {
3987 start->setLayout(lay);
4001 void Text::number(Cursor & cur)
4003 FontInfo font = ignore_font;
4004 font.setNumber(FONT_TOGGLE);
4005 toggleAndShow(cur, this, Font(font, ignore_language));
4009 bool Text::isRTL(pit_type const pit) const
4011 Buffer const & buffer = owner_->buffer();
4012 return pars_[pit].isRTL(buffer.params());
4018 Language const * getLanguage(Cursor const & cur, string const & lang)
4020 return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
4024 docstring resolveLayout(docstring layout, DocIterator const & dit)
4026 Paragraph const & par = dit.paragraph();
4027 DocumentClass const & tclass = dit.buffer()->params().documentClass();
4030 layout = tclass.defaultLayoutName();
4032 if (dit.inset().forcePlainLayout(dit.idx()))
4033 // in this case only the empty layout is allowed
4034 layout = tclass.plainLayoutName();
4035 else if (par.usePlainLayout()) {
4036 // in this case, default layout maps to empty layout
4037 if (layout == tclass.defaultLayoutName())
4038 layout = tclass.plainLayoutName();
4040 // otherwise, the empty layout maps to the default
4041 if (layout == tclass.plainLayoutName())
4042 layout = tclass.defaultLayoutName();
4045 // If the entry is obsolete, use the new one instead.
4046 if (tclass.hasLayout(layout)) {
4047 docstring const & obs = tclass[layout].obsoleted_by();
4051 if (!tclass.hasLayout(layout))
4057 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4059 ParagraphList const & pars = cur.text()->paragraphs();
4061 pit_type pit = cur.selBegin().pit();
4062 pit_type const epit = cur.selEnd().pit() + 1;
4063 for ( ; pit != epit; ++pit)
4064 if (pars[pit].layout().name() != layout)
4074 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4076 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4078 // Dispatch if the cursor is inside the text. It is not the
4079 // case for context menus (bug 5797).
4080 if (cur.text() != this) {
4085 BufferView * bv = &cur.bv();
4086 TextMetrics * tm = &bv->textMetrics(this);
4087 if (!tm->contains(cur.pit())) {
4088 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4089 tm = &bv->textMetrics(this);
4092 // FIXME: We use the update flag to indicates wether a singlePar or a
4093 // full screen update is needed. We reset it here but shall we restore it
4095 cur.noScreenUpdate();
4097 LBUFERR(this == cur.text());
4099 // NOTE: This should NOT be a reference. See commit 94a5481a.
4100 CursorSlice const oldTopSlice = cur.top();
4101 bool const oldBoundary = cur.boundary();
4102 bool const oldSelection = cur.selection();
4103 // Signals that, even if needsUpdate == false, an update of the
4104 // cursor paragraph is required
4105 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4106 LyXAction::SingleParUpdate);
4107 // Signals that a full-screen update is required
4108 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4109 LyXAction::NoUpdate) || singleParUpdate);
4110 bool const last_misspelled = lyxrc.spellcheck_continuously
4111 && cur.paragraph().isMisspelled(cur.pos(), true);
4113 FuncCode const act = cmd.action();
4116 case LFUN_PARAGRAPH_MOVE_DOWN: {
4117 pit_type const pit = cur.pit();
4118 cur.recordUndo(pit, pit + 1);
4119 pars_.swap(pit, pit + 1);
4121 cur.forceBufferUpdate();
4126 case LFUN_PARAGRAPH_MOVE_UP: {
4127 pit_type const pit = cur.pit();
4128 cur.recordUndo(pit - 1, pit);
4130 pars_.swap(pit, pit - 1);
4133 cur.forceBufferUpdate();
4137 case LFUN_APPENDIX: {
4138 Paragraph & par = cur.paragraph();
4139 bool start = !par.params().startOfAppendix();
4141 // FIXME: The code below only makes sense at top level.
4142 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4143 // ensure that we have only one start_of_appendix in this document
4144 // FIXME: this don't work for multipart document!
4145 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4146 if (pars_[tmp].params().startOfAppendix()) {
4147 cur.recordUndo(tmp, tmp);
4148 pars_[tmp].params().startOfAppendix(false);
4154 par.params().startOfAppendix(start);
4156 // we can set the refreshing parameters now
4157 cur.forceBufferUpdate();
4161 case LFUN_WORD_DELETE_FORWARD:
4162 if (cur.selection())
4163 cutSelection(cur, false);
4165 deleteWordForward(cur, cmd.getArg(0) != "confirm");
4166 finishChange(cur, false);
4169 case LFUN_WORD_DELETE_BACKWARD:
4170 if (cur.selection())
4171 cutSelection(cur, false);
4173 deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4174 finishChange(cur, false);
4177 case LFUN_LINE_DELETE_FORWARD:
4178 if (cur.selection())
4179 cutSelection(cur, false);
4181 tm->deleteLineForward(cur);
4182 finishChange(cur, false);
4185 case LFUN_BUFFER_BEGIN:
4186 case LFUN_BUFFER_BEGIN_SELECT:
4187 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4188 if (cur.depth() == 1)
4189 needsUpdate |= cursorTop(cur);
4192 cur.screenUpdateFlags(Update::FitCursor);
4195 case LFUN_BUFFER_END:
4196 case LFUN_BUFFER_END_SELECT:
4197 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4198 if (cur.depth() == 1)
4199 needsUpdate |= cursorBottom(cur);
4202 cur.screenUpdateFlags(Update::FitCursor);
4205 case LFUN_INSET_BEGIN:
4206 case LFUN_INSET_BEGIN_SELECT:
4207 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4208 if (cur.depth() == 1 || !cur.top().at_begin())
4209 needsUpdate |= cursorTop(cur);
4212 cur.screenUpdateFlags(Update::FitCursor);
4215 case LFUN_INSET_END:
4216 case LFUN_INSET_END_SELECT:
4217 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4218 if (cur.depth() == 1 || !cur.top().at_end())
4219 needsUpdate |= cursorBottom(cur);
4222 cur.screenUpdateFlags(Update::FitCursor);
4225 case LFUN_CHAR_FORWARD:
4226 case LFUN_CHAR_FORWARD_SELECT: {
4227 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4228 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4229 bool const cur_moved = cursorForward(cur);
4230 needsUpdate |= cur_moved;
4232 if (!cur_moved && cur.depth() > 1
4233 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4235 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4237 // we will be moving out the inset, so we should execute
4238 // the depm-mechanism.
4239 // The cursor hasn't changed yet. To give the DEPM the
4240 // possibility of doing something we must provide it with
4241 // two different cursors.
4243 dummy.pos() = dummy.pit() = 0;
4244 if (cur.bv().checkDepm(dummy, cur))
4245 cur.forceBufferUpdate();
4250 case LFUN_CHAR_BACKWARD:
4251 case LFUN_CHAR_BACKWARD_SELECT: {
4252 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4253 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4254 bool const cur_moved = cursorBackward(cur);
4255 needsUpdate |= cur_moved;
4257 if (!cur_moved && cur.depth() > 1
4258 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4260 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4262 // we will be moving out the inset, so we should execute
4263 // the depm-mechanism.
4264 // The cursor hasn't changed yet. To give the DEPM the
4265 // possibility of doing something we must provide it with
4266 // two different cursors.
4268 dummy.pos() = cur.lastpos();
4269 dummy.pit() = cur.lastpit();
4270 if (cur.bv().checkDepm(dummy, cur))
4271 cur.forceBufferUpdate();
4276 case LFUN_CHAR_LEFT:
4277 case LFUN_CHAR_LEFT_SELECT:
4278 if (lyxrc.visual_cursor) {
4279 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4280 bool const cur_moved = cursorVisLeft(cur);
4281 needsUpdate |= cur_moved;
4282 if (!cur_moved && cur.depth() > 1
4283 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4285 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4288 if (cur.reverseDirectionNeeded()) {
4289 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4290 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4292 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4293 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4300 case LFUN_CHAR_RIGHT:
4301 case LFUN_CHAR_RIGHT_SELECT:
4302 if (lyxrc.visual_cursor) {
4303 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4304 bool const cur_moved = cursorVisRight(cur);
4305 needsUpdate |= cur_moved;
4306 if (!cur_moved && cur.depth() > 1
4307 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4309 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4312 if (cur.reverseDirectionNeeded()) {
4313 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4314 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4316 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4317 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4325 case LFUN_UP_SELECT:
4326 case LFUN_DOWN_SELECT:
4329 // stop/start the selection
4330 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4331 cmd.action() == LFUN_UP_SELECT;
4333 // move cursor up/down
4334 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4335 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4337 if (!atFirstOrLastRow) {
4338 needsUpdate |= cur.selHandle(select);
4339 needsUpdate |= cur.upDownInText(up);
4340 needsUpdate |= cur.beforeDispatchCursor().inMathed();
4342 pos_type newpos = up ? 0 : cur.lastpos();
4343 if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4344 needsUpdate |= cur.selHandle(select);
4345 // we do not reset the targetx of the cursor
4347 if (bv->checkDepm(cur, bv->cursor())) {
4349 cur.forceBufferUpdate();
4351 cur.updateTextTargetOffset();
4355 // if the cursor cannot be moved up or down do not remove
4356 // the selection right now, but wait for the next dispatch.
4358 needsUpdate |= cur.selHandle(select);
4359 needsUpdate |= cur.upDownInText(up);
4366 case LFUN_PARAGRAPH_SELECT:
4368 needsUpdate |= setCursor(cur, cur.pit(), 0);
4369 needsUpdate |= cur.selHandle(true);
4370 if (cur.pos() < cur.lastpos())
4371 needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4374 case LFUN_PARAGRAPH_UP:
4375 case LFUN_PARAGRAPH_UP_SELECT:
4376 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4377 needsUpdate |= cursorUpParagraph(cur);
4380 case LFUN_PARAGRAPH_DOWN:
4381 case LFUN_PARAGRAPH_DOWN_SELECT:
4382 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4383 needsUpdate |= cursorDownParagraph(cur);
4386 case LFUN_LINE_BEGIN:
4387 case LFUN_LINE_BEGIN_SELECT:
4388 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4389 needsUpdate |= tm->cursorHome(cur);
4393 case LFUN_LINE_END_SELECT:
4394 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4395 needsUpdate |= tm->cursorEnd(cur);
4398 case LFUN_SECTION_SELECT: {
4399 Buffer const & buf = *cur.buffer();
4400 pit_type const pit = cur.pit();
4401 ParagraphList & pars = buf.text().paragraphs();
4402 ParagraphList::iterator bgn = pars.begin();
4403 // The first paragraph of the area to be selected:
4404 ParagraphList::iterator start = pars.iterator_at(pit);
4405 // The final paragraph of area to be selected:
4406 ParagraphList::iterator finish = start;
4407 ParagraphList::iterator end = pars.end();
4409 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4410 if (thistoclevel == Layout::NOT_IN_TOC)
4414 Cursor const old_cur = cur;
4415 needsUpdate |= cur.selHandle(true);
4417 // Move out (down) from this section header
4421 // Seek the one (on same level) below
4422 for (; finish != end; ++finish, ++cur.pit()) {
4423 int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4424 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4427 cur.pos() = cur.lastpos();
4428 cur.boundary(false);
4429 cur.setCurrentFont();
4431 needsUpdate |= cur != old_cur;
4435 case LFUN_WORD_RIGHT:
4436 case LFUN_WORD_RIGHT_SELECT:
4437 if (lyxrc.visual_cursor) {
4438 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4439 bool const cur_moved = cursorVisRightOneWord(cur);
4440 needsUpdate |= cur_moved;
4441 if (!cur_moved && cur.depth() > 1
4442 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4444 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4447 if (cur.reverseDirectionNeeded()) {
4448 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4449 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4451 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4452 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4459 case LFUN_WORD_FORWARD:
4460 case LFUN_WORD_FORWARD_SELECT: {
4461 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4462 bool const cur_moved = cursorForwardOneWord(cur);
4463 needsUpdate |= cur_moved;
4465 if (!cur_moved && cur.depth() > 1
4466 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4468 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4470 // we will be moving out the inset, so we should execute
4471 // the depm-mechanism.
4472 // The cursor hasn't changed yet. To give the DEPM the
4473 // possibility of doing something we must provide it with
4474 // two different cursors.
4476 dummy.pos() = dummy.pit() = 0;
4477 if (cur.bv().checkDepm(dummy, cur))
4478 cur.forceBufferUpdate();
4483 case LFUN_WORD_LEFT:
4484 case LFUN_WORD_LEFT_SELECT:
4485 if (lyxrc.visual_cursor) {
4486 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4487 bool const cur_moved = cursorVisLeftOneWord(cur);
4488 needsUpdate |= cur_moved;
4489 if (!cur_moved && cur.depth() > 1
4490 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4492 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4495 if (cur.reverseDirectionNeeded()) {
4496 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4497 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4499 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4500 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4507 case LFUN_WORD_BACKWARD:
4508 case LFUN_WORD_BACKWARD_SELECT: {
4509 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4510 bool const cur_moved = cursorBackwardOneWord(cur);
4511 needsUpdate |= cur_moved;
4513 if (!cur_moved && cur.depth() > 1
4514 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4516 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4518 // we will be moving out the inset, so we should execute
4519 // the depm-mechanism.
4520 // The cursor hasn't changed yet. To give the DEPM the
4521 // possibility of doing something we must provide it with
4522 // two different cursors.
4524 dummy.pos() = cur.lastpos();
4525 dummy.pit() = cur.lastpit();
4526 if (cur.bv().checkDepm(dummy, cur))
4527 cur.forceBufferUpdate();
4532 case LFUN_WORD_SELECT: {
4533 selectWord(cur, WHOLE_WORD);
4534 finishChange(cur, true);
4538 case LFUN_NEWLINE_INSERT: {
4539 InsetNewlineParams inp;
4540 docstring const & arg = cmd.argument();
4541 if (arg == "linebreak")
4542 inp.kind = InsetNewlineParams::LINEBREAK;
4544 inp.kind = InsetNewlineParams::NEWLINE;
4545 cap::replaceSelection(cur);
4547 cur.insert(new InsetNewline(inp));
4549 moveCursor(cur, false);
4553 case LFUN_TAB_INSERT: {
4554 bool const multi_par_selection = cur.selection() &&
4555 cur.selBegin().pit() != cur.selEnd().pit();
4556 if (multi_par_selection) {
4557 // If there is a multi-paragraph selection, a tab is inserted
4558 // at the beginning of each paragraph.
4559 cur.recordUndoSelection();
4560 pit_type const pit_end = cur.selEnd().pit();
4561 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4562 pars_[pit].insertChar(0, '\t',
4563 bv->buffer().params().track_changes);
4564 // Update the selection pos to make sure the selection does not
4565 // change as the inserted tab will increase the logical pos.
4566 if (cur.realAnchor().pit() == pit)
4567 cur.realAnchor().forwardPos();
4568 if (cur.pit() == pit)
4573 // Maybe we shouldn't allow tabs within a line, because they
4574 // are not (yet) aligned as one might do expect.
4575 FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4576 dispatch(cur, ncmd);
4581 case LFUN_TAB_DELETE: {
4582 bool const tc = bv->buffer().params().track_changes;
4583 if (cur.selection()) {
4584 // If there is a selection, a tab (if present) is removed from
4585 // the beginning of each paragraph.
4586 cur.recordUndoSelection();
4587 pit_type const pit_end = cur.selEnd().pit();
4588 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4589 Paragraph & par = paragraphs()[pit];
4592 char_type const c = par.getChar(0);
4593 if (c == '\t' || c == ' ') {
4594 // remove either 1 tab or 4 spaces.
4595 int const n = (c == ' ' ? 4 : 1);
4596 for (int i = 0; i < n
4597 && !par.empty() && par.getChar(0) == c; ++i) {
4598 if (cur.pit() == pit)
4600 if (cur.realAnchor().pit() == pit
4601 && cur.realAnchor().pos() > 0 )
4602 cur.realAnchor().backwardPos();
4603 par.eraseChar(0, tc);
4609 // If there is no selection, try to remove a tab or some spaces
4610 // before the position of the cursor.
4611 Paragraph & par = paragraphs()[cur.pit()];
4612 pos_type const pos = cur.pos();
4617 char_type const c = par.getChar(pos - 1);
4621 par.eraseChar(cur.pos(), tc);
4623 for (int n_spaces = 0;
4625 && par.getChar(cur.pos() - 1) == ' '
4629 par.eraseChar(cur.pos(), tc);
4636 case LFUN_CHAR_DELETE_FORWARD:
4637 if (!cur.selection()) {
4638 if (cur.pos() == cur.paragraph().size())
4639 // Par boundary, force full-screen update
4640 singleParUpdate = false;
4641 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4643 cur.selection(true);
4648 needsUpdate |= erase(cur);
4651 cutSelection(cur, false);
4652 cur.setCurrentFont();
4653 singleParUpdate = false;
4655 moveCursor(cur, false);
4658 case LFUN_CHAR_DELETE_BACKWARD:
4659 if (!cur.selection()) {
4660 if (bv->getIntl().getTransManager().backspace()) {
4661 bool par_boundary = cur.pos() == 0;
4662 bool first_par = cur.pit() == 0;
4663 // Par boundary, full-screen update
4665 singleParUpdate = false;
4666 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4668 cur.selection(true);
4673 needsUpdate |= backspace(cur);
4675 if (par_boundary && !first_par && cur.pos() > 0
4676 && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4677 needsUpdate |= backspace(cur);
4682 DocIterator const dit = cur.selectionBegin();
4683 cutSelection(cur, false);
4684 if (cur.buffer()->params().track_changes)
4685 // since we're doing backwards deletion,
4686 // and the selection is not really cut,
4687 // move cursor before selection (#11630)
4689 cur.setCurrentFont();
4690 singleParUpdate = false;
4694 case LFUN_PARAGRAPH_BREAK: {
4695 cap::replaceSelection(cur);
4696 pit_type pit = cur.pit();
4697 Paragraph const & par = pars_[pit];
4698 bool lastpar = (pit == pit_type(pars_.size() - 1));
4699 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4700 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4701 if (prev < pit && cur.pos() == par.beginOfBody()
4702 && par.empty() && !par.isEnvSeparator(cur.pos())
4703 && !par.layout().keepempty
4704 && !par.layout().isCommand()
4705 && pars_[prev].layout() != par.layout()
4706 && pars_[prev].layout().isEnvironment()
4707 && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4708 if (par.layout().isEnvironment()
4709 && pars_[prev].getDepth() == par.getDepth()) {
4710 docstring const layout = par.layout().name();
4711 DocumentClass const & tc = bv->buffer().params().documentClass();
4712 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4713 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4714 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4715 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4717 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4718 breakParagraph(cur);
4720 Font const f(inherit_font, cur.current_font.language());
4721 pars_[cur.pit() - 1].resetFonts(f);
4723 if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4725 breakParagraph(cur, cmd.getArg(0) == "inverse");
4728 // If we have a list and autoinsert item insets,
4730 Layout::LaTeXArgMap args = par.layout().args();
4731 for (auto const & thearg : args) {
4732 Layout::latexarg arg = thearg.second;
4733 if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4734 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4735 lyx::dispatch(cmd2);
4741 case LFUN_INSET_INSERT: {
4744 // We have to avoid triggering InstantPreview loading
4745 // before inserting into the document. See bug #5626.
4746 bool loaded = bv->buffer().isFullyLoaded();
4747 bv->buffer().setFullyLoaded(false);
4748 Inset * inset = createInset(&bv->buffer(), cmd);
4749 bv->buffer().setFullyLoaded(loaded);
4752 // FIXME (Abdel 01/02/2006):
4753 // What follows would be a partial fix for bug 2154:
4754 // http://www.lyx.org/trac/ticket/2154
4755 // This automatically put the label inset _after_ a
4756 // numbered section. It should be possible to extend the mechanism
4757 // to any kind of LateX environement.
4758 // The correct way to fix that bug would be at LateX generation.
4759 // I'll let the code here for reference as it could be used for some
4760 // other feature like "automatic labelling".
4762 Paragraph & par = pars_[cur.pit()];
4763 if (inset->lyxCode() == LABEL_CODE
4764 && !par.layout().counter.empty()) {
4765 // Go to the end of the paragraph
4766 // Warning: Because of Change-Tracking, the last
4767 // position is 'size()' and not 'size()-1':
4768 cur.pos() = par.size();
4769 // Insert a new paragraph
4770 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4774 if (cur.selection())
4775 cutSelection(cur, false);
4777 cur.forceBufferUpdate();
4778 if (inset->editable() && inset->asInsetText())
4779 inset->edit(cur, true);
4783 // trigger InstantPreview now
4784 if (inset->lyxCode() == EXTERNAL_CODE) {
4785 InsetExternal & ins =
4786 static_cast<InsetExternal &>(*inset);
4787 ins.updatePreview();
4794 case LFUN_INSET_DISSOLVE: {
4795 if (dissolveInset(cur)) {
4797 cur.forceBufferUpdate();
4802 case LFUN_INSET_SPLIT: {
4803 if (splitInset(cur)) {
4805 cur.forceBufferUpdate();
4810 case LFUN_GRAPHICS_SET_GROUP: {
4811 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4817 string id = to_utf8(cmd.argument());
4818 string grp = graphics::getGroupParams(bv->buffer(), id);
4819 InsetGraphicsParams tmp, inspar = ins->getParams();
4822 inspar.groupId = to_utf8(cmd.argument());
4824 InsetGraphics::string2params(grp, bv->buffer(), tmp);
4825 tmp.filename = inspar.filename;
4829 ins->setParams(inspar);
4833 case LFUN_SPACE_INSERT:
4834 if (cur.paragraph().layout().free_spacing)
4835 insertChar(cur, ' ');
4837 doInsertInset(cur, this, cmd, false, false);
4840 moveCursor(cur, false);
4843 case LFUN_SPECIALCHAR_INSERT: {
4844 string const name = to_utf8(cmd.argument());
4845 if (name == "hyphenation")
4846 specialChar(cur, InsetSpecialChar::HYPHENATION);
4847 else if (name == "allowbreak")
4848 specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4849 else if (name == "ligature-break")
4850 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4851 else if (name == "slash")
4852 specialChar(cur, InsetSpecialChar::SLASH);
4853 else if (name == "nobreakdash")
4854 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4855 else if (name == "dots")
4856 specialChar(cur, InsetSpecialChar::LDOTS);
4857 else if (name == "end-of-sentence")
4858 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4859 else if (name == "menu-separator")
4860 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4861 else if (name == "lyx")
4862 specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4863 else if (name == "tex")
4864 specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4865 else if (name == "latex")
4866 specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4867 else if (name == "latex2e")
4868 specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4869 else if (name.empty())
4870 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4872 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4876 case LFUN_IPAMACRO_INSERT: {
4877 string const arg = cmd.getArg(0);
4878 if (arg == "deco") {
4879 // Open the inset, and move the current selection
4881 doInsertInset(cur, this, cmd, true, true);
4883 // Some insets are numbered, others are shown in the outline pane so
4884 // let's update the labels and the toc backend.
4885 cur.forceBufferUpdate();
4888 if (arg == "tone-falling")
4889 ipaChar(cur, InsetIPAChar::TONE_FALLING);
4890 else if (arg == "tone-rising")
4891 ipaChar(cur, InsetIPAChar::TONE_RISING);
4892 else if (arg == "tone-high-rising")
4893 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4894 else if (arg == "tone-low-rising")
4895 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4896 else if (arg == "tone-high-rising-falling")
4897 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4898 else if (arg.empty())
4899 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4901 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4905 case LFUN_WORD_UPCASE:
4906 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4909 case LFUN_WORD_LOWCASE:
4910 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4913 case LFUN_WORD_CAPITALIZE:
4914 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4917 case LFUN_CHARS_TRANSPOSE:
4918 charsTranspose(cur);
4922 cur.message(_("Paste"));
4923 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4924 cap::replaceSelection(cur);
4926 // without argument?
4927 string const arg = to_utf8(cmd.argument());
4929 bool tryGraphics = true;
4930 if (theClipboard().isInternal())
4931 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4932 else if (theClipboard().hasTextContents()) {
4933 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"), 0,
4934 Clipboard::AnyTextType))
4935 tryGraphics = false;
4937 if (tryGraphics && theClipboard().hasGraphicsContents())
4938 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4939 } else if (isStrUnsignedInt(arg)) {
4940 // we have a numerical argument
4941 pasteFromStack(cur, bv->buffer().errorList("Paste"),
4942 convert<unsigned int>(arg));
4943 } else if (arg == "html" || arg == "latex") {
4944 Clipboard::TextType type = (arg == "html") ?
4945 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4946 pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4948 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4950 type = Clipboard::PdfGraphicsType;
4951 else if (arg == "png")
4952 type = Clipboard::PngGraphicsType;
4953 else if (arg == "jpeg")
4954 type = Clipboard::JpegGraphicsType;
4955 else if (arg == "linkback")
4956 type = Clipboard::LinkBackGraphicsType;
4957 else if (arg == "emf")
4958 type = Clipboard::EmfGraphicsType;
4959 else if (arg == "wmf")
4960 type = Clipboard::WmfGraphicsType;
4962 // we also check in getStatus()
4963 LYXERR0("Unrecognized graphics type: " << arg);
4965 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4968 bv->buffer().errors("Paste");
4969 bv->buffer().updatePreviews(); // bug 11619
4970 cur.clearSelection(); // bug 393
4976 cutSelection(cur, true);
4977 cur.message(_("Cut"));
4980 case LFUN_SERVER_GET_XY:
4981 cur.message(from_utf8(
4982 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4983 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4986 case LFUN_SERVER_SET_XY: {
4989 istringstream is(to_utf8(cmd.argument()));
4992 lyxerr << "SETXY: Could not parse coordinates in '"
4993 << to_utf8(cmd.argument()) << endl;
4995 tm->setCursorFromCoordinates(cur, x, y);
4999 case LFUN_SERVER_GET_LAYOUT:
5000 cur.message(cur.paragraph().layout().name());
5004 case LFUN_LAYOUT_TOGGLE: {
5005 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
5006 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
5007 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
5009 docstring layout = resolveLayout(req_layout, cur);
5010 if (layout.empty()) {
5011 cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
5012 from_utf8(N_(" not known")));
5016 docstring const old_layout = cur.paragraph().layout().name();
5017 bool change_layout = !isAlreadyLayout(layout, cur);
5019 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
5020 change_layout = true;
5021 layout = resolveLayout(docstring(), cur);
5024 if (change_layout) {
5025 setLayout(cur, layout);
5026 if (cur.pit() > 0 && !ignoreautonests) {
5027 pit_type prev_pit = cur.pit() - 1;
5028 depth_type const cur_depth = pars_[cur.pit()].getDepth();
5029 // Scan for the previous par on same nesting level
5030 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
5032 set<docstring> const & autonests =
5033 pars_[prev_pit].layout().autonests();
5034 set<docstring> const & autonested =
5035 pars_[cur.pit()].layout().isAutonestedBy();
5036 if (autonests.find(layout) != autonests.end()
5037 || autonested.find(old_layout) != autonested.end())
5038 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5042 DocumentClass const & tclass = bv->buffer().params().documentClass();
5043 bool inautoarg = false;
5044 for (auto const & la_pair : tclass[layout].args()) {
5045 Layout::latexarg const & arg = la_pair.second;
5046 if (arg.autoinsert) {
5047 // If we had already inserted an arg automatically,
5048 // leave this now in order to insert the next one.
5050 cur.leaveInset(cur.inset());
5053 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5054 lyx::dispatch(cmd2);
5062 case LFUN_ENVIRONMENT_SPLIT: {
5063 bool const outer = cmd.argument() == "outer";
5064 bool const previous = cmd.argument() == "previous";
5065 bool const before = cmd.argument() == "before";
5066 bool const normal = cmd.argument().empty();
5067 Paragraph const & para = cur.paragraph();
5069 if (para.layout().isEnvironment())
5070 layout = para.layout().name();
5071 depth_type split_depth = cur.paragraph().params().depth();
5072 vector<depth_type> nextpars_depth;
5073 if (outer || previous) {
5074 // check if we have an environment in our scope
5075 pit_type pit = cur.pit();
5076 Paragraph cpar = pars_[pit];
5082 if (layout.empty() && previous
5083 && cpar.layout().isEnvironment()
5084 && cpar.params().depth() <= split_depth)
5085 layout = cpar.layout().name();
5086 if (cpar.params().depth() < split_depth
5087 && cpar.layout().isEnvironment()) {
5089 layout = cpar.layout().name();
5090 split_depth = cpar.params().depth();
5092 if (cpar.params().depth() == 0)
5096 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5097 // save nesting of following paragraphs if they are deeper
5099 pit_type offset = 1;
5100 depth_type cur_depth = pars_[cur.pit()].params().depth();
5101 while (cur.pit() + offset <= cur.lastpit()) {
5102 Paragraph cpar = pars_[cur.pit() + offset];
5103 depth_type nextpar_depth = cpar.params().depth();
5104 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5105 nextpars_depth.push_back(nextpar_depth);
5106 cur_depth = nextpar_depth;
5113 cur.top().setPitPos(cur.pit(), 0);
5114 if (before || cur.pos() > 0)
5115 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5116 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5117 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5119 while (cur.paragraph().params().depth() > split_depth)
5120 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5122 DocumentClass const & tc = bv->buffer().params().documentClass();
5123 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5124 + from_ascii("\" ignoreautonests")));
5125 // FIXME: Bibitem mess!
5126 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5127 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5128 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5131 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5132 while (cur.paragraph().params().depth() < split_depth)
5133 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5136 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5137 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5138 if ((outer || normal) && !nextpars_depth.empty()) {
5139 // restore nesting of following paragraphs
5140 DocIterator scur = cur;
5141 depth_type max_depth = cur.paragraph().params().depth() + 1;
5142 for (auto nextpar_depth : nextpars_depth) {
5144 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5145 depth_type const olddepth = cur.paragraph().params().depth();
5146 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5147 if (olddepth == cur.paragraph().params().depth())
5148 // leave loop if no incrementation happens
5151 max_depth = cur.paragraph().params().depth() + 1;
5153 cur.setCursor(scur);
5159 case LFUN_CLIPBOARD_PASTE:
5160 cap::replaceSelection(cur);
5161 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5162 cmd.argument() == "paragraph");
5163 bv->buffer().errors("Paste");
5166 case LFUN_CLIPBOARD_PASTE_SIMPLE:
5167 cap::replaceSelection(cur);
5168 pasteSimpleText(cur, cmd.argument() == "paragraph");
5171 case LFUN_PRIMARY_SELECTION_PASTE:
5172 cap::replaceSelection(cur);
5173 pasteString(cur, theSelection().get(),
5174 cmd.argument() == "paragraph");
5177 case LFUN_SELECTION_PASTE:
5178 // Copy the selection buffer to the clipboard stack,
5179 // because we want it to appear in the "Edit->Paste
5181 cap::replaceSelection(cur);
5182 cap::copySelectionToStack();
5183 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5184 bv->buffer().errors("Paste");
5187 case LFUN_QUOTE_INSERT: {
5188 cap::replaceSelection(cur);
5191 Paragraph const & par = cur.paragraph();
5192 pos_type pos = cur.pos();
5193 // Ignore deleted text before cursor
5194 while (pos > 0 && par.isDeleted(pos - 1))
5197 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5199 // Guess quote side.
5200 // A space triggers an opening quote. This is passed if the preceding
5201 // char/inset is a space or at paragraph start.
5203 if (pos > 0 && !par.isSpace(pos - 1)) {
5204 if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5205 // If an opening double quotation mark precedes, and this
5206 // is a single quote, make it opening as well
5208 static_cast<InsetQuotes &>(*cur.prevInset());
5209 string const type = ins.getType();
5210 if (!suffixIs(type, "ld") || !inner)
5211 c = par.getChar(pos - 1);
5213 else if (!cur.prevInset()
5214 || (cur.prevInset() && cur.prevInset()->isChar()))
5215 // If a char precedes, pass that and let InsetQuote decide
5216 c = par.getChar(pos - 1);
5219 if (par.getInset(pos - 1)
5220 && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5221 // skip "invisible" insets
5225 c = par.getChar(pos - 1);
5230 QuoteLevel const quote_level = inner
5231 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5232 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5233 cur.buffer()->updateBuffer();
5238 case LFUN_MOUSE_TRIPLE:
5239 if (cmd.button() == mouse_button::button1) {
5241 setCursor(cur, cur.pit(), 0);
5244 if (cur.pos() < cur.lastpos())
5245 setCursor(cur, cur.pit(), cur.lastpos());
5251 case LFUN_MOUSE_DOUBLE:
5252 if (cmd.button() == mouse_button::button1) {
5253 selectWord(cur, WHOLE_WORD);
5258 // Single-click on work area
5259 case LFUN_MOUSE_PRESS: {
5260 // We are not marking a selection with the keyboard in any case.
5261 Cursor & bvcur = cur.bv().cursor();
5262 bvcur.setMark(false);
5263 switch (cmd.button()) {
5264 case mouse_button::button1:
5265 bvcur.setClickPos(cmd.x(), cmd.y());
5266 if (!bvcur.selection())
5268 bvcur.resetAnchor();
5269 if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5270 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5271 // FIXME: move this to mouseSetCursor?
5272 if (bvcur.wordSelection() && bvcur.inTexted())
5273 expandWordSel(bvcur);
5276 case mouse_button::button2:
5277 if (lyxrc.mouse_middlebutton_paste) {
5278 // Middle mouse pasting.
5279 bv->mouseSetCursor(cur);
5281 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5282 "selection-paste ; primary-selection-paste paragraph"));
5284 cur.noScreenUpdate();
5287 case mouse_button::button3: {
5288 // Don't do anything if we right-click a
5289 // selection, a context menu will popup.
5290 if (bvcur.selection() && cur >= bvcur.selectionBegin()
5291 && cur <= bvcur.selectionEnd()) {
5292 cur.noScreenUpdate();
5295 if (!bv->mouseSetCursor(cur, false))
5296 cur.screenUpdateFlags(Update::FitCursor);
5302 } // switch (cmd.button())
5305 case LFUN_MOUSE_MOTION: {
5306 // Mouse motion with right or middle mouse do nothing for now.
5307 if (cmd.button() != mouse_button::button1) {
5308 cur.noScreenUpdate();
5311 // ignore motions deeper nested than the real anchor
5312 Cursor & bvcur = cur.bv().cursor();
5313 if (!bvcur.realAnchor().hasPart(cur)) {
5317 CursorSlice old = bvcur.top();
5319 int const wh = bv->workHeight();
5320 int const y = max(0, min(wh - 1, cmd.y()));
5322 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5323 cur.setTargetX(cmd.x());
5324 // Don't allow selecting a separator inset
5325 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5328 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5329 else if (cmd.y() < 0)
5330 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5331 // This is to allow jumping over large insets
5332 if (cur.top() == old) {
5334 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5335 else if (cmd.y() < 0)
5336 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5338 // We continue with our existing selection or start a new one, so don't
5339 // reset the anchor.
5340 bvcur.setCursor(cur);
5341 if (bvcur.wordSelection() && bvcur.inTexted())
5342 expandWordSel(bvcur);
5343 bvcur.selection(true);
5344 bvcur.setCurrentFont();
5345 if (cur.top() == old) {
5346 // We didn't move one iota, so no need to update the screen.
5347 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5348 //cur.noScreenUpdate();
5354 case LFUN_MOUSE_RELEASE:
5355 switch (cmd.button()) {
5356 case mouse_button::button1:
5357 // unregister last mouse press position
5358 cur.bv().cursor().setClickPos(-1, -1);
5359 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5360 // If there is a new selection, update persistent selection;
5361 // otherwise, single click does not clear persistent selection
5363 if (cur.selection()) {
5364 // Finish selection. If double click,
5365 // cur is moved to the end of word by
5366 // selectWord but bvcur is current
5368 cur.bv().cursor().setSelection();
5369 // We might have removed an empty but drawn selection
5370 // (probably a margin)
5371 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5373 cur.noScreenUpdate();
5374 // FIXME: We could try to handle drag and drop of selection here.
5377 case mouse_button::button2:
5378 // Middle mouse pasting is handled at mouse press time,
5379 // see LFUN_MOUSE_PRESS.
5380 cur.noScreenUpdate();
5383 case mouse_button::button3:
5384 // Cursor was set at LFUN_MOUSE_PRESS time.
5385 // FIXME: If there is a selection we could try to handle a special
5386 // drag & drop context menu.
5387 cur.noScreenUpdate();
5390 case mouse_button::none:
5391 case mouse_button::button4:
5392 case mouse_button::button5:
5394 } // switch (cmd.button())
5398 case LFUN_SELF_INSERT: {
5399 if (cmd.argument().empty())
5402 // Automatically delete the currently selected
5403 // text and replace it with what is being
5404 // typed in now. Depends on lyxrc settings
5405 // "auto_region_delete", which defaults to
5408 if (lyxrc.auto_region_delete && cur.selection()) {
5409 cutSelection(cur, false);
5410 cur.setCurrentFont();
5412 cur.clearSelection();
5414 for (char_type c : cmd.argument())
5415 bv->translateAndInsert(c, this, cur);
5418 moveCursor(cur, false);
5419 cur.markNewWordPosition();
5420 bv->bookmarkEditPosition();
5424 case LFUN_HREF_INSERT: {
5425 docstring content = cmd.argument();
5426 if (content.empty() && cur.selection())
5427 content = cur.selectionAsString(false);
5429 InsetCommandParams p(HYPERLINK_CODE);
5430 if (!content.empty()){
5431 // if it looks like a link, we'll put it as target,
5432 // otherwise as name (bug #8792).
5435 // regex_match(to_utf8(content), matches, link_re)
5436 // because smatch stores pointers to the substrings rather
5437 // than making copies of them. And those pointers become
5438 // invalid after regex_match returns, since it is then
5439 // being given a temporary object. (Thanks to Georg for
5440 // figuring that out.)
5441 regex const link_re("^(([a-z]+):|www\\.).*");
5443 string const c = to_utf8(lowercase(content));
5445 if (c.substr(0,7) == "mailto:") {
5446 p["target"] = content;
5447 p["type"] = from_ascii("mailto:");
5448 } else if (regex_match(c, matches, link_re)) {
5449 p["target"] = content;
5450 string protocol = matches.str(1);
5451 if (protocol == "file")
5452 p["type"] = from_ascii("file:");
5454 p["name"] = content;
5456 string const data = InsetCommand::params2string(p);
5458 // we need to have a target. if we already have one, then
5459 // that gets used at the default for the name, too, which
5460 // is probably what is wanted.
5461 if (p["target"].empty()) {
5462 bv->showDialog("href", data);
5464 FuncRequest fr(LFUN_INSET_INSERT, data);
5470 case LFUN_LABEL_INSERT: {
5471 InsetCommandParams p(LABEL_CODE);
5472 // Try to generate a valid label
5473 p["name"] = (cmd.argument().empty()) ?
5474 cur.getPossibleLabel() :
5476 string const data = InsetCommand::params2string(p);
5478 if (cmd.argument().empty()) {
5479 bv->showDialog("label", data);
5481 FuncRequest fr(LFUN_INSET_INSERT, data);
5487 case LFUN_INFO_INSERT: {
5488 if (cmd.argument().empty()) {
5489 bv->showDialog("info", cur.current_font.language()->lang());
5492 inset = createInset(cur.buffer(), cmd);
5496 insertInset(cur, inset);
5497 cur.forceBufferUpdate();
5502 case LFUN_CAPTION_INSERT:
5503 case LFUN_FOOTNOTE_INSERT:
5504 case LFUN_NOTE_INSERT:
5505 case LFUN_BOX_INSERT:
5506 case LFUN_BRANCH_INSERT:
5507 case LFUN_PHANTOM_INSERT:
5508 case LFUN_ERT_INSERT:
5509 case LFUN_INDEXMACRO_INSERT:
5510 case LFUN_LISTING_INSERT:
5511 case LFUN_MARGINALNOTE_INSERT:
5512 case LFUN_ARGUMENT_INSERT:
5513 case LFUN_INDEX_INSERT:
5514 case LFUN_PREVIEW_INSERT:
5515 case LFUN_SCRIPT_INSERT:
5516 case LFUN_IPA_INSERT: {
5517 // Indexes reset font formatting (#11961)
5518 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5519 // Open the inset, and move the current selection
5521 doInsertInset(cur, this, cmd, true, true, resetfont);
5523 cur.setCurrentFont();
5524 // Some insets are numbered, others are shown in the outline pane so
5525 // let's update the labels and the toc backend.
5526 cur.forceBufferUpdate();
5530 case LFUN_FLEX_INSERT: {
5531 // Open the inset, and move the current selection
5533 bool const sel = cur.selection();
5534 doInsertInset(cur, this, cmd, true, true);
5535 // Insert auto-insert arguments
5536 bool autoargs = false, inautoarg = false;
5537 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5538 for (auto const & argt : args) {
5539 Layout::latexarg arg = argt.second;
5540 if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5541 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5542 lyx::dispatch(cmd2);
5544 if (arg.autoinsert) {
5545 // The cursor might have been invalidated by the replaceSelection.
5546 cur.buffer()->changed(true);
5547 // If we had already inserted an arg automatically,
5548 // leave this now in order to insert the next one.
5550 cur.leaveInset(cur.inset());
5551 cur.setCurrentFont();
5553 if (arg.insertonnewline && cur.pos() > 0) {
5554 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5555 lyx::dispatch(cmd2);
5558 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5559 lyx::dispatch(cmd2);
5566 cur.leaveInset(cur.inset());
5569 // Some insets are numbered, others are shown in the outline pane so
5570 // let's update the labels and the toc backend.
5571 cur.forceBufferUpdate();
5575 case LFUN_TABULAR_INSERT: {
5576 // if there were no arguments, just open the dialog
5577 if (cmd.argument().empty()) {
5578 bv->showDialog("tabularcreate");
5580 } else if (cur.buffer()->masterParams().tablestyle != "default"
5581 || bv->buffer().params().documentClass().tablestyle() != "default") {
5582 string tabstyle = cur.buffer()->masterParams().tablestyle;
5583 if (tabstyle == "default")
5584 tabstyle = bv->buffer().params().documentClass().tablestyle();
5585 if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5586 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5587 tabstyle + " " + to_ascii(cmd.argument()));
5591 // Unknown style. Report and fall back to default.
5592 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5593 from_utf8(N_(" not known")));
5595 if (doInsertInset(cur, this, cmd, false, true))
5597 (void) checkAndActivateInset(cur, true);
5601 case LFUN_TABULAR_STYLE_INSERT: {
5602 string const style = cmd.getArg(0);
5603 string const rows = cmd.getArg(1);
5604 string const cols = cmd.getArg(2);
5605 if (cols.empty() || !isStrInt(cols)
5606 || rows.empty() || !isStrInt(rows))
5608 int const r = convert<int>(rows);
5609 int const c = convert<int>(cols);
5616 FileName const tabstyle = libFileSearch("tabletemplates",
5617 style + suffix + ".lyx", "lyx");
5618 if (tabstyle.empty())
5620 UndoGroupHelper ugh(cur.buffer());
5622 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5623 lyx::dispatch(cmd2);
5627 // move one cell up to middle cell
5629 // add the missing rows
5630 int const addrows = r - 3;
5631 for (int i = 0 ; i < addrows ; ++i) {
5632 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5636 // add the missing columns
5637 int const addcols = c - 1;
5638 for (int i = 0 ; i < addcols ; ++i) {
5639 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5648 case LFUN_FLOAT_INSERT:
5649 case LFUN_FLOAT_WIDE_INSERT:
5650 case LFUN_WRAP_INSERT: {
5651 // will some content be moved into the inset?
5652 bool const content = cur.selection();
5653 // does the content consist of multiple paragraphs?
5654 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5656 doInsertInset(cur, this, cmd, true, true);
5659 // If some single-par content is moved into the inset,
5660 // doInsertInset puts the cursor outside the inset.
5661 // To insert the caption we put it back into the inset.
5662 // FIXME cleanup doInsertInset to avoid such dances!
5663 if (content && singlepar)
5666 ParagraphList & pars = cur.text()->paragraphs();
5668 DocumentClass const & tclass = bv->buffer().params().documentClass();
5670 // add a separate paragraph for the caption inset
5671 pars.push_back(Paragraph());
5672 pars.back().setInsetOwner(&cur.text()->inset());
5673 pars.back().setPlainOrDefaultLayout(tclass);
5674 int cap_pit = pars.size() - 1;
5676 // if an empty inset was created, we create an additional empty
5677 // paragraph at the bottom so that the user can choose where to put
5678 // the graphics (or table).
5680 pars.push_back(Paragraph());
5681 pars.back().setInsetOwner(&cur.text()->inset());
5682 pars.back().setPlainOrDefaultLayout(tclass);
5685 // reposition the cursor to the caption
5686 cur.pit() = cap_pit;
5688 // FIXME: This Text/Cursor dispatch handling is a mess!
5689 // We cannot use Cursor::dispatch here it needs access to up to
5691 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5692 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5693 cur.forceBufferUpdate();
5694 cur.screenUpdateFlags(Update::Force);
5695 // FIXME: When leaving the Float (or Wrap) inset we should
5696 // delete any empty paragraph left above or below the
5701 case LFUN_NOMENCL_INSERT: {
5702 InsetCommandParams p(NOMENCL_CODE);
5703 if (cmd.argument().empty()) {
5705 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5706 cur.clearSelection();
5708 p["symbol"] = cmd.argument();
5709 string const data = InsetCommand::params2string(p);
5710 bv->showDialog("nomenclature", data);
5714 case LFUN_INDEX_PRINT: {
5715 InsetCommandParams p(INDEX_PRINT_CODE);
5716 if (cmd.argument().empty())
5717 p["type"] = from_ascii("idx");
5719 p["type"] = cmd.argument();
5720 string const data = InsetCommand::params2string(p);
5721 FuncRequest fr(LFUN_INSET_INSERT, data);
5726 case LFUN_NOMENCL_PRINT:
5728 doInsertInset(cur, this, cmd, false, false);
5732 case LFUN_NEWPAGE_INSERT: {
5733 // When we are in a heading, put the page break in a standard
5734 // paragraph before the heading (if cur.pos() == 0) or after
5735 // (if cur.pos() == cur.lastpos())
5736 if (cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC) {
5737 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5738 DocumentClass const & tc = bv->buffer().params().documentClass();
5739 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5740 + from_ascii("\" ignoreautonests")));
5743 doInsertInset(cur, this, cmd, false, false);
5748 case LFUN_SEPARATOR_INSERT: {
5749 doInsertInset(cur, this, cmd, false, false);
5751 // remove a following space
5752 Paragraph & par = cur.paragraph();
5753 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5754 par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5758 case LFUN_DEPTH_DECREMENT:
5759 changeDepth(cur, DEC_DEPTH);
5762 case LFUN_DEPTH_INCREMENT:
5763 changeDepth(cur, INC_DEPTH);
5766 case LFUN_REGEXP_MODE:
5767 regexpDispatch(cur, cmd);
5770 case LFUN_MATH_MODE: {
5771 if (cmd.argument() == "on" || cmd.argument() == "") {
5772 // don't pass "on" as argument
5773 // (it would appear literally in the first cell)
5774 docstring sel = cur.selectionAsString(false);
5775 InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5776 // create a macro template if we see "\\newcommand" somewhere, and
5777 // an ordinary formula otherwise
5779 && (sel.find(from_ascii("\\newcommand")) != string::npos
5780 || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5781 || sel.find(from_ascii("\\def")) != string::npos)
5782 && macro->fromString(sel)) {
5784 replaceSelection(cur);
5787 // no meaningful macro template was found
5789 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5792 // The argument is meaningful
5793 // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5794 // has a different meaning in math mode
5795 mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5799 case LFUN_MATH_MACRO:
5800 if (cmd.argument().empty())
5801 cur.errorMessage(from_utf8(N_("Missing argument")));
5804 string s = to_utf8(cmd.argument());
5805 string const s1 = token(s, ' ', 1);
5806 int const nargs = s1.empty() ? 0 : convert<int>(s1);
5807 string const s2 = token(s, ' ', 2);
5808 MacroType type = MacroTypeNewcommand;
5810 type = MacroTypeDef;
5811 InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5812 from_utf8(token(s, ' ', 0)), nargs, false, type);
5813 inset->setBuffer(bv->buffer());
5814 if (insertInset(cur, inset)) {
5815 // If insertion is successful, enter macro inset and select the name
5817 cur.top().pos() = cur.top().lastpos();
5819 cur.selection(true);
5820 cur.top().pos() = 0;
5826 case LFUN_MATH_DISPLAY:
5827 case LFUN_MATH_SUBSCRIPT:
5828 case LFUN_MATH_SUPERSCRIPT:
5829 case LFUN_MATH_INSERT:
5830 case LFUN_MATH_AMS_MATRIX:
5831 case LFUN_MATH_MATRIX:
5832 case LFUN_MATH_DELIM:
5833 case LFUN_MATH_BIGDELIM:
5834 mathDispatch(cur, cmd);
5837 case LFUN_FONT_EMPH: {
5838 Font font(ignore_font, ignore_language);
5839 font.fontInfo().setEmph(FONT_TOGGLE);
5840 toggleAndShow(cur, this, font);
5844 case LFUN_FONT_ITAL: {
5845 Font font(ignore_font, ignore_language);
5846 font.fontInfo().setShape(ITALIC_SHAPE);
5847 toggleAndShow(cur, this, font);
5851 case LFUN_FONT_BOLD:
5852 case LFUN_FONT_BOLDSYMBOL: {
5853 Font font(ignore_font, ignore_language);
5854 font.fontInfo().setSeries(BOLD_SERIES);
5855 toggleAndShow(cur, this, font);
5859 case LFUN_FONT_NOUN: {
5860 Font font(ignore_font, ignore_language);
5861 font.fontInfo().setNoun(FONT_TOGGLE);
5862 toggleAndShow(cur, this, font);
5866 case LFUN_FONT_TYPEWRITER: {
5867 Font font(ignore_font, ignore_language);
5868 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5869 toggleAndShow(cur, this, font);
5873 case LFUN_FONT_SANS: {
5874 Font font(ignore_font, ignore_language);
5875 font.fontInfo().setFamily(SANS_FAMILY);
5876 toggleAndShow(cur, this, font);
5880 case LFUN_FONT_ROMAN: {
5881 Font font(ignore_font, ignore_language);
5882 font.fontInfo().setFamily(ROMAN_FAMILY);
5883 toggleAndShow(cur, this, font);
5887 case LFUN_FONT_DEFAULT: {
5888 Font font(inherit_font, ignore_language);
5889 toggleAndShow(cur, this, font);
5893 case LFUN_FONT_STRIKEOUT: {
5894 Font font(ignore_font, ignore_language);
5895 font.fontInfo().setStrikeout(FONT_TOGGLE);
5896 toggleAndShow(cur, this, font);
5900 case LFUN_FONT_CROSSOUT: {
5901 Font font(ignore_font, ignore_language);
5902 font.fontInfo().setXout(FONT_TOGGLE);
5903 toggleAndShow(cur, this, font);
5907 case LFUN_FONT_UNDERUNDERLINE: {
5908 Font font(ignore_font, ignore_language);
5909 font.fontInfo().setUuline(FONT_TOGGLE);
5910 toggleAndShow(cur, this, font);
5914 case LFUN_FONT_UNDERWAVE: {
5915 Font font(ignore_font, ignore_language);
5916 font.fontInfo().setUwave(FONT_TOGGLE);
5917 toggleAndShow(cur, this, font);
5921 case LFUN_FONT_UNDERLINE: {
5922 Font font(ignore_font, ignore_language);
5923 font.fontInfo().setUnderbar(FONT_TOGGLE);
5924 toggleAndShow(cur, this, font);
5928 case LFUN_FONT_NO_SPELLCHECK: {
5929 Font font(ignore_font, ignore_language);
5930 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5931 toggleAndShow(cur, this, font);
5935 case LFUN_FONT_SIZE: {
5936 Font font(ignore_font, ignore_language);
5937 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5938 toggleAndShow(cur, this, font);
5942 case LFUN_LANGUAGE: {
5943 string const lang_arg = cmd.getArg(0);
5944 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5945 Language const * lang =
5946 reset ? cur.bv().buffer().params().language
5947 : languages.getLanguage(lang_arg);
5948 // we allow reset_language, which is 0, but only if it
5949 // was requested via empty or "reset" arg.
5950 if (!lang && !reset)
5952 bool const toggle = (cmd.getArg(1) != "set");
5953 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5954 Font font(ignore_font, lang);
5955 toggleAndShow(cur, this, font, toggle);
5959 case LFUN_TEXTSTYLE_APPLY: {
5960 unsigned int num = 0;
5961 string const arg = to_utf8(cmd.argument());
5964 if (isStrUnsignedInt(arg)) {
5965 num = convert<unsigned int>(arg);
5966 if (num >= freeFonts.size()) {
5967 cur.message(_("Invalid argument (number exceeds stack size)!"));
5971 cur.message(_("Invalid argument (must be a non-negative number)!"));
5975 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5976 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5980 // Set the freefont using the contents of \param data dispatched from
5981 // the frontends and apply it at the current cursor location.
5982 case LFUN_TEXTSTYLE_UPDATE: {
5983 Font font(ignore_font, ignore_language);
5985 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5986 docstring const props = font.stateText(&bv->buffer().params(), true);
5987 freeFonts.push(make_pair(props, font));
5989 toggleAndShow(cur, this, font, toggleall);
5990 cur.message(bformat(_("Text properties applied: %1$s"), props));
5992 cur.message(_("Invalid argument of textstyle-update"));
5996 case LFUN_FINISHED_LEFT:
5997 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5998 // We're leaving an inset, going left. If the inset is LTR, we're
5999 // leaving from the front, so we should not move (remain at --- but
6000 // not in --- the inset). If the inset is RTL, move left, without
6001 // entering the inset itself; i.e., move to after the inset.
6002 if (cur.paragraph().getFontSettings(
6003 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
6004 cursorVisLeft(cur, true);
6007 case LFUN_FINISHED_RIGHT:
6008 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
6009 // We're leaving an inset, going right. If the inset is RTL, we're
6010 // leaving from the front, so we should not move (remain at --- but
6011 // not in --- the inset). If the inset is LTR, move right, without
6012 // entering the inset itself; i.e., move to after the inset.
6013 if (!cur.paragraph().getFontSettings(
6014 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
6015 cursorVisRight(cur, true);
6018 case LFUN_FINISHED_BACKWARD:
6019 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
6020 cur.setCurrentFont();
6023 case LFUN_FINISHED_FORWARD:
6024 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
6026 cur.setCurrentFont();
6029 case LFUN_LAYOUT_PARAGRAPH: {
6031 params2string(cur.paragraph(), data);
6032 data = "show\n" + data;
6033 bv->showDialog("paragraph", data);
6037 case LFUN_PARAGRAPH_UPDATE: {
6039 params2string(cur.paragraph(), data);
6041 // Will the paragraph accept changes from the dialog?
6043 cur.inset().allowParagraphCustomization(cur.idx());
6045 data = "update " + convert<string>(accept) + '\n' + data;
6046 bv->updateDialog("paragraph", data);
6050 case LFUN_ACCENT_UMLAUT:
6051 case LFUN_ACCENT_CIRCUMFLEX:
6052 case LFUN_ACCENT_GRAVE:
6053 case LFUN_ACCENT_ACUTE:
6054 case LFUN_ACCENT_TILDE:
6055 case LFUN_ACCENT_PERISPOMENI:
6056 case LFUN_ACCENT_CEDILLA:
6057 case LFUN_ACCENT_MACRON:
6058 case LFUN_ACCENT_DOT:
6059 case LFUN_ACCENT_UNDERDOT:
6060 case LFUN_ACCENT_UNDERBAR:
6061 case LFUN_ACCENT_CARON:
6062 case LFUN_ACCENT_BREVE:
6063 case LFUN_ACCENT_TIE:
6064 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6065 case LFUN_ACCENT_CIRCLE:
6066 case LFUN_ACCENT_OGONEK:
6067 theApp()->handleKeyFunc(cmd.action());
6068 if (!cmd.argument().empty())
6069 // FIXME: Are all these characters encoded in one byte in utf8?
6070 bv->translateAndInsert(cmd.argument()[0], this, cur);
6071 cur.screenUpdateFlags(Update::FitCursor);
6074 case LFUN_FLOAT_LIST_INSERT: {
6075 DocumentClass const & tclass = bv->buffer().params().documentClass();
6076 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6078 if (cur.selection())
6079 cutSelection(cur, false);
6080 breakParagraph(cur);
6082 if (cur.lastpos() != 0) {
6083 cursorBackward(cur);
6084 breakParagraph(cur);
6087 docstring const laystr = cur.inset().usePlainLayout() ?
6088 tclass.plainLayoutName() :
6089 tclass.defaultLayoutName();
6090 setLayout(cur, laystr);
6091 ParagraphParameters p;
6092 // FIXME If this call were replaced with one to clearParagraphParams(),
6093 // then we could get rid of this method altogether.
6094 setParagraphs(cur, p);
6095 // FIXME This should be simplified when InsetFloatList takes a
6096 // Buffer in its constructor.
6097 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6098 ifl->setBuffer(bv->buffer());
6099 insertInset(cur, ifl);
6102 lyxerr << "Non-existent float type: "
6103 << to_utf8(cmd.argument()) << endl;
6108 case LFUN_CHANGE_ACCEPT: {
6109 acceptOrRejectChanges(cur, ACCEPT);
6113 case LFUN_CHANGE_REJECT: {
6114 acceptOrRejectChanges(cur, REJECT);
6118 case LFUN_THESAURUS_ENTRY: {
6119 Language const * language = cur.getFont().language();
6120 docstring arg = cmd.argument();
6122 arg = cur.selectionAsString(false);
6123 // Too large. We unselect if needed and try to get
6124 // the first word in selection or under cursor
6125 if (arg.size() > 100 || arg.empty()) {
6126 if (cur.selection()) {
6127 DocIterator selbeg = cur.selectionBegin();
6128 cur.clearSelection();
6129 setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6130 cur.screenUpdateFlags(Update::Force);
6132 // Get word or selection
6133 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6134 arg = cur.selectionAsString(false);
6135 arg += " lang=" + from_ascii(language->lang());
6138 string lang = cmd.getArg(1);
6139 // This duplicates the code in GuiThesaurus::initialiseParams
6140 if (prefixIs(lang, "lang=")) {
6141 language = languages.getLanguage(lang.substr(5));
6143 language = cur.getFont().language();
6146 string lang = language->code();
6147 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6148 LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6149 frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6150 _("The path to the thesaurus directory has not been specified.\n"
6151 "The thesaurus is not functional.\n"
6152 "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6155 bv->showDialog("thesaurus", to_utf8(arg));
6159 case LFUN_SPELLING_ADD: {
6160 Language const * language = getLanguage(cur, cmd.getArg(1));
6161 docstring word = from_utf8(cmd.getArg(0));
6163 word = cur.selectionAsString(false);
6165 if (word.size() > 100 || word.empty()) {
6166 // Get word or selection
6167 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6168 word = cur.selectionAsString(false);
6171 WordLangTuple wl(word, language);
6172 theSpellChecker()->insert(wl);
6176 case LFUN_SPELLING_ADD_LOCAL: {
6177 Language const * language = getLanguage(cur, cmd.getArg(1));
6178 docstring word = from_utf8(cmd.getArg(0));
6180 word = cur.selectionAsString(false);
6181 if (word.size() > 100)
6184 // Get word or selection
6185 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6186 word = cur.selectionAsString(false);
6189 WordLangTuple wl(word, language);
6190 if (!bv->buffer().params().spellignored(wl)) {
6191 cur.recordUndoBufferParams();
6192 bv->buffer().params().spellignore().push_back(wl);
6194 // trigger re-check of whole buffer
6195 bv->buffer().requestSpellcheck();
6200 case LFUN_SPELLING_REMOVE_LOCAL: {
6201 Language const * language = getLanguage(cur, cmd.getArg(1));
6202 docstring word = from_utf8(cmd.getArg(0));
6204 word = cur.selectionAsString(false);
6205 if (word.size() > 100)
6208 // Get word or selection
6209 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6210 word = cur.selectionAsString(false);
6213 WordLangTuple wl(word, language);
6214 bool has_item = false;
6215 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6216 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6217 if (it->lang()->code() != wl.lang()->code())
6219 if (it->word() == wl.word()) {
6225 cur.recordUndoBufferParams();
6226 bv->buffer().params().spellignore().erase(it);
6228 // trigger re-check of whole buffer
6229 bv->buffer().requestSpellcheck();
6235 case LFUN_SPELLING_IGNORE: {
6236 Language const * language = getLanguage(cur, cmd.getArg(1));
6237 docstring word = from_utf8(cmd.getArg(0));
6239 word = cur.selectionAsString(false);
6241 if (word.size() > 100 || word.empty()) {
6242 // Get word or selection
6243 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6244 word = cur.selectionAsString(false);
6247 WordLangTuple wl(word, language);
6248 theSpellChecker()->accept(wl);
6252 case LFUN_SPELLING_REMOVE: {
6253 Language const * language = getLanguage(cur, cmd.getArg(1));
6254 docstring word = from_utf8(cmd.getArg(0));
6256 word = cur.selectionAsString(false);
6258 if (word.size() > 100 || word.empty()) {
6259 // Get word or selection
6260 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6261 word = cur.selectionAsString(false);
6264 WordLangTuple wl(word, language);
6265 theSpellChecker()->remove(wl);
6269 case LFUN_PARAGRAPH_PARAMS_APPLY: {
6270 // Given data, an encoding of the ParagraphParameters
6271 // generated in the Paragraph dialog, this function sets
6272 // the current paragraph, or currently selected paragraphs,
6274 // NOTE: This function overrides all existing settings.
6275 setParagraphs(cur, cmd.argument());
6276 cur.message(_("Paragraph layout set"));
6280 case LFUN_PARAGRAPH_PARAMS: {
6281 // Given data, an encoding of the ParagraphParameters as we'd
6282 // find them in a LyX file, this function modifies the current paragraph,
6283 // or currently selected paragraphs.
6284 // NOTE: This function only modifies, and does not override, existing
6286 setParagraphs(cur, cmd.argument(), true);
6287 cur.message(_("Paragraph layout set"));
6292 if (cur.selection()) {
6293 cur.selection(false);
6296 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6297 // correct, but I'm not 100% sure -- dov, 071019
6298 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6302 case LFUN_OUTLINE_UP: {
6303 pos_type const opos = cur.pos();
6304 outline(OutlineUp, cur, false);
6305 setCursor(cur, cur.pit(), opos);
6306 cur.forceBufferUpdate();
6311 case LFUN_OUTLINE_DOWN: {
6312 pos_type const opos = cur.pos();
6313 outline(OutlineDown, cur, false);
6314 setCursor(cur, cur.pit(), opos);
6315 cur.forceBufferUpdate();
6320 case LFUN_OUTLINE_IN:
6321 outline(OutlineIn, cur, cmd.getArg(0) == "local");
6322 cur.forceBufferUpdate();
6326 case LFUN_OUTLINE_OUT:
6327 outline(OutlineOut, cur, cmd.getArg(0) == "local");
6328 cur.forceBufferUpdate();
6332 case LFUN_SERVER_GET_STATISTICS: {
6333 DocIterator from, to;
6334 if (cur.selection()) {
6335 from = cur.selectionBegin();
6336 to = cur.selectionEnd();
6338 from = doc_iterator_begin(cur.buffer());
6339 to = doc_iterator_end(cur.buffer());
6342 cur.buffer()->updateStatistics(from, to);
6343 string const arg0 = cmd.getArg(0);
6344 if (arg0 == "words") {
6345 cur.message(convert<docstring>(cur.buffer()->wordCount()));
6346 } else if (arg0 == "chars") {
6347 cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6348 } else if (arg0 == "chars-space") {
6349 cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6351 cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6352 + convert<docstring>(cur.buffer()->charCount(false)) + " "
6353 + convert<docstring>(cur.buffer()->charCount(true)));
6359 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6364 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6366 if (lyxrc.spellcheck_continuously && !needsUpdate) {
6367 // Check for misspelled text
6368 // The redraw is useful because of the painting of
6369 // misspelled markers depends on the cursor position.
6370 // Trigger a redraw for cursor moves inside misspelled text.
6371 if (!cur.inTexted()) {
6372 // move from regular text to math
6373 needsUpdate = last_misspelled;
6374 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6375 // move inside regular text
6376 needsUpdate = last_misspelled
6377 || cur.paragraph().isMisspelled(cur.pos(), true);
6381 // FIXME: the following code should go in favor of fine grained
6382 // update flag treatment.
6383 if (needsUpdate || cur.result().screenUpdate() & Update::Force)
6384 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6385 else if (singleParUpdate || cur.result().screenUpdate() & Update::SinglePar) {
6386 // Inserting characters does not change par height in general. So, try
6387 // to update _only_ this paragraph. BufferView will detect if a full
6388 // metrics update is needed anyway.
6389 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6393 // oldSelection is a backup of cur.selection() at the beginning of the function.
6394 if (!oldSelection && !cur.selection())
6395 // FIXME: it would be better if we could just do this
6397 //if (cur.result().update() != Update::FitCursor)
6398 // cur.noScreenUpdate();
6400 // But some LFUNs do not set Update::FitCursor when needed, so we
6401 // do it for all. This is not very harmfull as FitCursor will provoke
6402 // a full redraw only if needed but still, a proper review of all LFUN
6403 // should be done and this needsUpdate boolean can then be removed.
6404 cur.screenUpdateFlags(Update::FitCursor);
6406 cur.screenUpdateFlags(Update::ForceDraw | Update::FitCursor);
6411 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6412 FuncStatus & status) const
6414 LBUFERR(this == cur.text());
6416 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6418 bool allow_in_passthru = false;
6419 InsetCode code = NO_CODE;
6421 switch (cmd.action()) {
6423 case LFUN_DEPTH_DECREMENT:
6424 enable = changeDepthAllowed(cur, DEC_DEPTH);
6427 case LFUN_DEPTH_INCREMENT:
6428 enable = changeDepthAllowed(cur, INC_DEPTH);
6432 // FIXME We really should not allow this to be put, e.g.,
6433 // in a footnote, or in ERT. But it would make sense in a
6434 // branch, so I'm not sure what to do.
6435 status.setOnOff(cur.paragraph().params().startOfAppendix());
6438 case LFUN_DIALOG_SHOW_NEW_INSET:
6439 if (cmd.argument() == "bibitem")
6440 code = BIBITEM_CODE;
6441 else if (cmd.argument() == "bibtex") {
6443 // not allowed in description items
6444 enable = !inDescriptionItem(cur);
6446 else if (cmd.argument() == "box")
6448 else if (cmd.argument() == "branch")
6450 else if (cmd.argument() == "citation")
6452 else if (cmd.argument() == "counter")
6453 code = COUNTER_CODE;
6454 else if (cmd.argument() == "ert")
6456 else if (cmd.argument() == "external")
6457 code = EXTERNAL_CODE;
6458 else if (cmd.argument() == "float")
6460 else if (cmd.argument() == "graphics")
6461 code = GRAPHICS_CODE;
6462 else if (cmd.argument() == "href")
6463 code = HYPERLINK_CODE;
6464 else if (cmd.argument() == "include")
6465 code = INCLUDE_CODE;
6466 else if (cmd.argument() == "index")
6468 else if (cmd.argument() == "index_print")
6469 code = INDEX_PRINT_CODE;
6470 else if (cmd.argument() == "listings")
6471 code = LISTINGS_CODE;
6472 else if (cmd.argument() == "mathspace")
6473 code = MATH_HULL_CODE;
6474 else if (cmd.argument() == "nomenclature")
6475 code = NOMENCL_CODE;
6476 else if (cmd.argument() == "nomencl_print")
6477 code = NOMENCL_PRINT_CODE;
6478 else if (cmd.argument() == "label")
6480 else if (cmd.argument() == "line")
6482 else if (cmd.argument() == "note")
6484 else if (cmd.argument() == "phantom")
6485 code = PHANTOM_CODE;
6486 else if (cmd.argument() == "ref")
6488 else if (cmd.argument() == "space")
6490 else if (cmd.argument() == "toc")
6492 else if (cmd.argument() == "vspace")
6494 else if (cmd.argument() == "wrap")
6498 case LFUN_ERT_INSERT:
6501 case LFUN_LISTING_INSERT:
6502 code = LISTINGS_CODE;
6503 // not allowed in description items
6504 enable = !inDescriptionItem(cur);
6506 case LFUN_FOOTNOTE_INSERT:
6509 case LFUN_TABULAR_INSERT:
6510 code = TABULAR_CODE;
6512 case LFUN_TABULAR_STYLE_INSERT:
6513 code = TABULAR_CODE;
6515 case LFUN_MARGINALNOTE_INSERT:
6518 case LFUN_FLOAT_INSERT:
6519 case LFUN_FLOAT_WIDE_INSERT:
6520 // FIXME: If there is a selection, we should check whether there
6521 // are floats in the selection, but this has performance issues, see
6522 // LFUN_CHANGE_ACCEPT/REJECT.
6524 if (inDescriptionItem(cur))
6525 // not allowed in description items
6528 InsetCode const inset_code = cur.inset().lyxCode();
6530 // algorithm floats cannot be put in another float
6531 if (to_utf8(cmd.argument()) == "algorithm") {
6532 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6536 // for figures and tables: only allow in another
6537 // float or wrap if it is of the same type and
6538 // not a subfloat already
6539 if(cur.inset().lyxCode() == code) {
6540 InsetFloat const & ins =
6541 static_cast<InsetFloat const &>(cur.inset());
6542 enable = ins.params().type == to_utf8(cmd.argument())
6543 && !ins.params().subfloat;
6544 } else if(cur.inset().lyxCode() == WRAP_CODE) {
6545 InsetWrap const & ins =
6546 static_cast<InsetWrap const &>(cur.inset());
6547 enable = ins.params().type == to_utf8(cmd.argument());
6551 case LFUN_WRAP_INSERT:
6553 // not allowed in description items
6554 enable = !inDescriptionItem(cur);
6556 case LFUN_FLOAT_LIST_INSERT: {
6557 code = FLOAT_LIST_CODE;
6558 // not allowed in description items
6559 enable = !inDescriptionItem(cur);
6561 FloatList const & floats = cur.buffer()->params().documentClass().floats();
6562 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6563 // make sure we know about such floats
6564 if (cit == floats.end() ||
6565 // and that we know how to generate a list of them
6566 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6567 status.setUnknown(true);
6568 // probably not necessary, but...
6574 case LFUN_CAPTION_INSERT: {
6575 code = CAPTION_CODE;
6576 string arg = cmd.getArg(0);
6577 bool varia = arg != "Unnumbered"
6578 && cur.inset().allowsCaptionVariation(arg);
6579 // not allowed in description items,
6580 // and in specific insets
6581 enable = !inDescriptionItem(cur)
6582 && (varia || arg.empty() || arg == "Standard");
6585 case LFUN_NOTE_INSERT:
6588 case LFUN_FLEX_INSERT: {
6590 docstring s = from_utf8(cmd.getArg(0));
6591 // Prepend "Flex:" prefix if not there
6592 if (!prefixIs(s, from_ascii("Flex:")))
6593 s = from_ascii("Flex:") + s;
6594 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6596 else if (!cur.paragraph().allowedInContext(cur, cur.buffer()->params().documentClass().insetLayout(s)))
6600 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6601 if (ilt != InsetLyXType::CHARSTYLE
6602 && ilt != InsetLyXType::CUSTOM
6603 && ilt != InsetLyXType::STANDARD)
6608 case LFUN_BOX_INSERT:
6611 case LFUN_BRANCH_INSERT:
6613 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6614 && cur.buffer()->params().branchlist().empty())
6617 case LFUN_IPA_INSERT:
6620 case LFUN_PHANTOM_INSERT:
6621 code = PHANTOM_CODE;
6623 case LFUN_LABEL_INSERT:
6626 case LFUN_INFO_INSERT:
6628 enable = cmd.argument().empty()
6629 || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6631 case LFUN_ARGUMENT_INSERT: {
6633 allow_in_passthru = true;
6634 string const arg = cmd.getArg(0);
6639 Layout const & lay = cur.paragraph().layout();
6640 Layout::LaTeXArgMap args = lay.args();
6641 Layout::LaTeXArgMap::const_iterator const lait =
6643 if (lait != args.end()) {
6645 pit_type pit = cur.pit();
6646 pit_type lastpit = cur.pit();
6647 if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6648 // In a sequence of "merged" environment layouts, we only allow
6649 // non-item arguments once.
6650 lastpit = cur.lastpit();
6651 // get the first paragraph in sequence with this layout
6652 depth_type const current_depth = cur.paragraph().params().depth();
6656 Paragraph cpar = pars_[pit - 1];
6657 if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6663 for (; pit <= lastpit; ++pit) {
6664 if (pars_[pit].layout() != lay)
6666 for (auto const & table : pars_[pit].insetList())
6667 if (InsetArgument const * ins = table.inset->asInsetArgument())
6668 if (ins->name() == arg) {
6669 // we have this already
6678 case LFUN_INDEX_INSERT:
6681 case LFUN_INDEX_PRINT:
6682 code = INDEX_PRINT_CODE;
6683 // not allowed in description items
6684 enable = !inDescriptionItem(cur);
6686 case LFUN_NOMENCL_INSERT:
6687 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6691 code = NOMENCL_CODE;
6693 case LFUN_NOMENCL_PRINT:
6694 code = NOMENCL_PRINT_CODE;
6695 // not allowed in description items
6696 enable = !inDescriptionItem(cur);
6698 case LFUN_HREF_INSERT:
6699 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6703 code = HYPERLINK_CODE;
6705 case LFUN_INDEXMACRO_INSERT: {
6706 string const arg = cmd.getArg(0);
6707 if (arg == "sortkey")
6708 code = INDEXMACRO_SORTKEY_CODE;
6710 code = INDEXMACRO_CODE;
6713 case LFUN_IPAMACRO_INSERT: {
6714 string const arg = cmd.getArg(0);
6716 code = IPADECO_CODE;
6718 code = IPACHAR_CODE;
6721 case LFUN_QUOTE_INSERT:
6722 // always allow this, since we will inset a raw quote
6723 // if an inset is not allowed.
6724 allow_in_passthru = true;
6726 case LFUN_SPECIALCHAR_INSERT:
6727 code = SPECIALCHAR_CODE;
6729 case LFUN_SPACE_INSERT:
6730 // slight hack: we know this is allowed in math mode
6734 case LFUN_PREVIEW_INSERT:
6735 code = PREVIEW_CODE;
6737 case LFUN_SCRIPT_INSERT:
6741 case LFUN_MATH_INSERT:
6742 case LFUN_MATH_AMS_MATRIX:
6743 case LFUN_MATH_MATRIX:
6744 case LFUN_MATH_DELIM:
6745 case LFUN_MATH_BIGDELIM:
6746 case LFUN_MATH_DISPLAY:
6747 case LFUN_MATH_MODE:
6748 case LFUN_MATH_SUBSCRIPT:
6749 case LFUN_MATH_SUPERSCRIPT:
6750 code = MATH_HULL_CODE;
6753 case LFUN_MATH_MACRO:
6754 code = MATHMACRO_CODE;
6757 case LFUN_REGEXP_MODE:
6758 code = MATH_HULL_CODE;
6759 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6762 case LFUN_INSET_MODIFY:
6763 // We need to disable this, because we may get called for a
6765 // InsetTabular::getStatus() -> InsetText::getStatus()
6766 // and we don't handle LFUN_INSET_MODIFY.
6770 case LFUN_FONT_EMPH:
6771 status.setOnOff(fontinfo.emph() == FONT_ON);
6772 enable = !cur.paragraph().isPassThru();
6775 case LFUN_FONT_ITAL:
6776 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6777 enable = !cur.paragraph().isPassThru();
6780 case LFUN_FONT_NOUN:
6781 status.setOnOff(fontinfo.noun() == FONT_ON);
6782 enable = !cur.paragraph().isPassThru();
6785 case LFUN_FONT_BOLD:
6786 case LFUN_FONT_BOLDSYMBOL:
6787 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6788 enable = !cur.paragraph().isPassThru();
6791 case LFUN_FONT_SANS:
6792 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6793 enable = !cur.paragraph().isPassThru();
6796 case LFUN_FONT_ROMAN:
6797 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6798 enable = !cur.paragraph().isPassThru();
6801 case LFUN_FONT_TYPEWRITER:
6802 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6803 enable = !cur.paragraph().isPassThru();
6807 enable = cur.selection();
6811 if (cmd.argument().empty()) {
6812 if (theClipboard().isInternal())
6813 enable = cap::numberOfSelections() > 0;
6815 enable = !theClipboard().empty();
6819 // we have an argument
6820 string const arg = to_utf8(cmd.argument());
6821 if (isStrUnsignedInt(arg)) {
6822 // it's a number and therefore means the internal stack
6823 unsigned int n = convert<unsigned int>(arg);
6824 enable = cap::numberOfSelections() > n;
6828 // explicit text type?
6829 if (arg == "html") {
6830 // Do not enable for PlainTextType, since some tidying in the
6831 // frontend is needed for HTML, which is too unsafe for plain text.
6832 enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6834 } else if (arg == "latex") {
6835 // LaTeX is usually not available on the clipboard with
6836 // the correct MIME type, but in plain text.
6837 enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6838 theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6842 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6844 type = Clipboard::PdfGraphicsType;
6845 else if (arg == "png")
6846 type = Clipboard::PngGraphicsType;
6847 else if (arg == "jpeg")
6848 type = Clipboard::JpegGraphicsType;
6849 else if (arg == "linkback")
6850 type = Clipboard::LinkBackGraphicsType;
6851 else if (arg == "emf")
6852 type = Clipboard::EmfGraphicsType;
6853 else if (arg == "wmf")
6854 type = Clipboard::WmfGraphicsType;
6857 LYXERR0("Unrecognized graphics type: " << arg);
6858 // we don't want to assert if the user just mistyped the LFUN
6859 LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6863 enable = theClipboard().hasGraphicsContents(type);
6867 case LFUN_CLIPBOARD_PASTE:
6868 case LFUN_CLIPBOARD_PASTE_SIMPLE:
6869 enable = !theClipboard().empty();
6872 case LFUN_PRIMARY_SELECTION_PASTE:
6873 status.setUnknown(!theSelection().supported());
6874 enable = cur.selection() || !theSelection().empty();
6877 case LFUN_SELECTION_PASTE:
6878 enable = cap::selection();
6881 case LFUN_PARAGRAPH_MOVE_UP:
6882 enable = cur.pit() > 0 && !cur.selection();
6885 case LFUN_PARAGRAPH_MOVE_DOWN:
6886 enable = cur.pit() < cur.lastpit() && !cur.selection();
6889 case LFUN_CHANGE_ACCEPT:
6890 case LFUN_CHANGE_REJECT:
6891 if (!cur.selection())
6892 enable = cur.paragraph().isChanged(cur.pos());
6894 // will enable if there is a change in the selection
6897 // cheap improvement for efficiency: using cached
6898 // buffer variable, if there is no change in the
6899 // document, no need to check further.
6900 if (!cur.buffer()->areChangesPresent())
6903 for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6904 pos_type const beg = it.pos();
6906 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6907 it.idx() == cur.selectionEnd().idx());
6909 end = cur.selectionEnd().pos();
6911 // the +1 is needed for cases, e.g., where there is a
6912 // paragraph break. See #11629.
6913 end = it.lastpos() + 1;
6914 if (beg != end && it.paragraph().isChanged(beg, end)) {
6918 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6928 case LFUN_OUTLINE_UP:
6929 case LFUN_OUTLINE_DOWN:
6930 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6932 case LFUN_OUTLINE_IN:
6933 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC
6934 && cur.text()->getTocLevel(cur.pit()) !=
6935 cur.buffer()->params().documentClass().max_toclevel();
6937 case LFUN_OUTLINE_OUT:
6938 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC
6939 && cur.text()->getTocLevel(cur.pit()) !=
6940 cur.buffer()->params().documentClass().min_toclevel();
6943 case LFUN_NEWLINE_INSERT:
6944 // LaTeX restrictions (labels or empty par)
6945 enable = !cur.paragraph().isPassThru()
6946 && cur.pos() > cur.paragraph().beginOfBody();
6949 case LFUN_SEPARATOR_INSERT:
6950 // Always enabled for now
6954 case LFUN_TAB_INSERT:
6955 case LFUN_TAB_DELETE:
6956 enable = cur.paragraph().isPassThru();
6959 case LFUN_GRAPHICS_SET_GROUP: {
6960 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6964 status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6968 case LFUN_NEWPAGE_INSERT:
6969 // not allowed in description items and in the midst of sections
6970 code = NEWPAGE_CODE;
6971 enable = !inDescriptionItem(cur)
6972 && (cur.text()->getTocLevel(cur.pit()) == Layout::NOT_IN_TOC
6973 || cur.pos() == 0 || cur.pos() == cur.lastpos());
6977 enable = !cur.paragraph().isPassThru();
6978 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6981 case LFUN_PARAGRAPH_BREAK:
6982 enable = inset().allowMultiPar();
6985 case LFUN_SPELLING_ADD:
6986 case LFUN_SPELLING_ADD_LOCAL:
6987 case LFUN_SPELLING_REMOVE_LOCAL:
6988 case LFUN_SPELLING_IGNORE:
6989 case LFUN_SPELLING_REMOVE:
6990 enable = theSpellChecker() != nullptr;
6991 if (enable && !cmd.getArg(1).empty()) {
6992 // validate explicitly given language
6993 Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6994 enable &= lang != nullptr;
6999 case LFUN_LAYOUT_TOGGLE: {
7000 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
7001 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
7002 docstring const layout = resolveLayout(req_layout, cur);
7004 // FIXME: make this work in multicell selection case
7005 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
7006 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
7007 && isAlreadyLayout(layout, cur));
7011 case LFUN_ENVIRONMENT_SPLIT: {
7012 if (cmd.argument() == "outer") {
7013 // check if we have an environment in our nesting hierarchy
7015 depth_type const current_depth = cur.paragraph().params().depth();
7016 pit_type pit = cur.pit();
7017 Paragraph cpar = pars_[pit];
7019 if (pit == 0 || cpar.params().depth() == 0)
7023 if (cpar.params().depth() < current_depth)
7024 res = cpar.layout().isEnvironment();
7029 else if (cmd.argument() == "previous") {
7030 // look if we have an environment in the previous par
7031 pit_type pit = cur.pit();
7032 Paragraph cpar = pars_[pit];
7036 enable = cpar.layout().isEnvironment();
7042 else if (cur.paragraph().layout().isEnvironment()) {
7043 enable = cmd.argument() == "before"
7044 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
7051 case LFUN_LAYOUT_PARAGRAPH:
7052 case LFUN_PARAGRAPH_PARAMS:
7053 case LFUN_PARAGRAPH_PARAMS_APPLY:
7054 case LFUN_PARAGRAPH_UPDATE:
7055 enable = owner_->allowParagraphCustomization();
7058 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
7059 // Because they insert COMBINING DIACRITICAL Unicode characters,
7060 // that cannot be handled by LaTeX but must be converted according
7061 // to the definition in lib/unicodesymbols?
7062 case LFUN_ACCENT_ACUTE:
7063 case LFUN_ACCENT_BREVE:
7064 case LFUN_ACCENT_CARON:
7065 case LFUN_ACCENT_CEDILLA:
7066 case LFUN_ACCENT_CIRCLE:
7067 case LFUN_ACCENT_CIRCUMFLEX:
7068 case LFUN_ACCENT_DOT:
7069 case LFUN_ACCENT_GRAVE:
7070 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7071 case LFUN_ACCENT_MACRON:
7072 case LFUN_ACCENT_OGONEK:
7073 case LFUN_ACCENT_TIE:
7074 case LFUN_ACCENT_TILDE:
7075 case LFUN_ACCENT_PERISPOMENI:
7076 case LFUN_ACCENT_UMLAUT:
7077 case LFUN_ACCENT_UNDERBAR:
7078 case LFUN_ACCENT_UNDERDOT:
7079 case LFUN_FONT_FRAK:
7080 case LFUN_FONT_SIZE:
7081 case LFUN_FONT_STATE:
7082 case LFUN_FONT_UNDERLINE:
7083 case LFUN_FONT_STRIKEOUT:
7084 case LFUN_FONT_CROSSOUT:
7085 case LFUN_FONT_UNDERUNDERLINE:
7086 case LFUN_FONT_UNDERWAVE:
7087 case LFUN_FONT_NO_SPELLCHECK:
7088 case LFUN_TEXTSTYLE_UPDATE:
7089 enable = !cur.paragraph().isPassThru();
7092 case LFUN_FONT_DEFAULT: {
7093 Font font(inherit_font, ignore_language);
7094 BufferParams const & bp = cur.buffer()->masterParams();
7095 if (cur.selection()) {
7097 // Check if we have a non-default font attribute
7098 // in the selection range.
7099 DocIterator const from = cur.selectionBegin();
7100 DocIterator const to = cur.selectionEnd();
7101 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7102 if (!dit.inTexted()) {
7106 Paragraph const & par = dit.paragraph();
7107 pos_type const pos = dit.pos();
7108 Font tmp = par.getFontSettings(bp, pos);
7109 if (tmp.fontInfo() != font.fontInfo()
7110 || tmp.language() != bp.language) {
7118 // Disable if all is default already.
7119 enable = (cur.current_font.fontInfo() != font.fontInfo()
7120 || cur.current_font.language() != bp.language);
7124 case LFUN_TEXTSTYLE_APPLY:
7125 enable = !freeFonts.empty();
7128 case LFUN_WORD_DELETE_FORWARD:
7129 case LFUN_WORD_DELETE_BACKWARD:
7130 case LFUN_LINE_DELETE_FORWARD:
7131 case LFUN_WORD_FORWARD:
7132 case LFUN_WORD_BACKWARD:
7133 case LFUN_WORD_RIGHT:
7134 case LFUN_WORD_LEFT:
7135 case LFUN_CHAR_FORWARD:
7136 case LFUN_CHAR_FORWARD_SELECT:
7137 case LFUN_CHAR_BACKWARD:
7138 case LFUN_CHAR_BACKWARD_SELECT:
7139 case LFUN_CHAR_LEFT:
7140 case LFUN_CHAR_LEFT_SELECT:
7141 case LFUN_CHAR_RIGHT:
7142 case LFUN_CHAR_RIGHT_SELECT:
7144 case LFUN_UP_SELECT:
7146 case LFUN_DOWN_SELECT:
7147 case LFUN_PARAGRAPH_SELECT:
7148 case LFUN_PARAGRAPH_UP_SELECT:
7149 case LFUN_PARAGRAPH_DOWN_SELECT:
7150 case LFUN_LINE_BEGIN_SELECT:
7151 case LFUN_LINE_END_SELECT:
7152 case LFUN_WORD_FORWARD_SELECT:
7153 case LFUN_WORD_BACKWARD_SELECT:
7154 case LFUN_WORD_RIGHT_SELECT:
7155 case LFUN_WORD_LEFT_SELECT:
7156 case LFUN_WORD_SELECT:
7157 case LFUN_SECTION_SELECT:
7158 case LFUN_BUFFER_BEGIN:
7159 case LFUN_BUFFER_END:
7160 case LFUN_BUFFER_BEGIN_SELECT:
7161 case LFUN_BUFFER_END_SELECT:
7162 case LFUN_INSET_BEGIN:
7163 case LFUN_INSET_END:
7164 case LFUN_INSET_BEGIN_SELECT:
7165 case LFUN_INSET_END_SELECT:
7166 case LFUN_PARAGRAPH_UP:
7167 case LFUN_PARAGRAPH_DOWN:
7168 case LFUN_LINE_BEGIN:
7170 case LFUN_CHAR_DELETE_FORWARD:
7171 case LFUN_CHAR_DELETE_BACKWARD:
7172 case LFUN_WORD_UPCASE:
7173 case LFUN_WORD_LOWCASE:
7174 case LFUN_WORD_CAPITALIZE:
7175 case LFUN_CHARS_TRANSPOSE:
7176 case LFUN_SERVER_GET_XY:
7177 case LFUN_SERVER_SET_XY:
7178 case LFUN_SERVER_GET_LAYOUT:
7179 case LFUN_SELF_INSERT:
7180 case LFUN_UNICODE_INSERT:
7181 case LFUN_THESAURUS_ENTRY:
7183 case LFUN_SERVER_GET_STATISTICS:
7184 // these are handled in our dispatch()
7188 case LFUN_INSET_INSERT: {
7189 string const type = cmd.getArg(0);
7190 if (type == "toc") {
7192 // not allowed in description items
7193 //FIXME: couldn't this be merged in Inset::insetAllowed()?
7194 enable = !inDescriptionItem(cur);
7201 case LFUN_SEARCH_IGNORE: {
7202 bool const value = cmd.getArg(1) == "true";
7203 setIgnoreFormat(cmd.getArg(0), value);
7213 || !cur.inset().insetAllowed(code)
7214 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7217 status.setEnabled(enable);
7222 void Text::pasteString(Cursor & cur, docstring const & clip,
7225 if (!clip.empty()) {
7228 insertStringAsParagraphs(cur, clip, cur.current_font);
7230 insertStringAsLines(cur, clip, cur.current_font);
7235 // FIXME: an item inset would make things much easier.
7236 bool Text::inDescriptionItem(Cursor const & cur) const
7238 Paragraph const & par = cur.paragraph();
7239 pos_type const pos = cur.pos();
7240 pos_type const body_pos = par.beginOfBody();
7242 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7243 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7244 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7247 return (pos < body_pos
7249 && (pos == 0 || par.getChar(pos - 1) != ' ')));
7253 std::vector<docstring> Text::getFreeFonts() const
7255 vector<docstring> ffList;
7257 for (auto const & f : freeFonts)
7258 ffList.push_back(f.first);