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"
50 #include "LyXAction.h"
53 #include "Paragraph.h"
54 #include "ParagraphParameters.h"
55 #include "SpellChecker.h"
56 #include "TextClass.h"
57 #include "TextMetrics.h"
58 #include "Thesaurus.h"
59 #include "WordLangTuple.h"
62 #include "frontends/alert.h"
63 #include "frontends/Application.h"
64 #include "frontends/Clipboard.h"
65 #include "frontends/Selection.h"
67 #include "mathed/InsetMathHull.h"
68 #include "mathed/InsetMathMacroTemplate.h"
70 #include "insets/Inset.h"
71 #include "insets/InsetArgument.h"
72 #include "insets/InsetCaption.h"
73 #include "insets/InsetCollapsible.h"
74 #include "insets/InsetCommand.h"
75 #include "insets/InsetExternal.h"
76 #include "insets/InsetFloat.h"
77 #include "insets/InsetFloatList.h"
78 #include "insets/InsetGraphics.h"
79 #include "insets/InsetGraphicsParams.h"
80 #include "insets/InsetIndexMacro.h"
81 #include "insets/InsetInfo.h"
82 #include "insets/InsetIPAMacro.h"
83 #include "insets/InsetNewline.h"
84 #include "insets/InsetQuotes.h"
85 #include "insets/InsetSpecialChar.h"
86 #include "insets/InsetTabular.h"
87 #include "insets/InsetText.h"
88 #include "insets/InsetWrap.h"
90 #include "support/convert.h"
91 #include "support/debug.h"
92 #include "support/docstream.h"
93 #include "support/docstring.h"
94 #include "support/docstring_list.h"
95 #include "support/filetools.h"
96 #include "support/gettext.h"
97 #include "support/lassert.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 if (par.getChar(pos - 1) == '-') {
1131 // convert "--" to endash
1132 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1135 } else if (par.getChar(pos - 1) == 0x2013) {
1136 // convert "---" to emdash
1137 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1143 par.insertChar(pos, c, cur.current_font,
1144 cur.buffer()->params().track_changes);
1145 cur.checkBufferStructure();
1147 // cur.screenUpdateFlags(Update::Force);
1148 bool boundary = cur.boundary()
1149 || tm.isRTLBoundary(cur.pit(), pos + 1);
1150 setCursor(cur, cur.pit(), pos + 1, false, boundary);
1155 void Text::charInserted(Cursor & cur)
1157 Paragraph & par = cur.paragraph();
1159 // register word if a non-letter was entered
1161 && !par.isWordSeparator(cur.pos() - 2)
1162 && par.isWordSeparator(cur.pos() - 1)) {
1163 // get the word in front of cursor
1164 LBUFERR(this == cur.text());
1170 // the cursor set functions have a special mechanism. When they
1171 // realize, that you left an empty paragraph, they will delete it.
1173 bool Text::cursorForwardOneWord(Cursor & cur)
1175 LBUFERR(this == cur.text());
1177 if (lyxrc.mac_like_cursor_movement) {
1178 DocIterator dit(cur);
1179 DocIterator prv(cur);
1180 bool inword = false;
1181 bool intext = dit.inTexted();
1182 while (!dit.atEnd()) {
1183 if (dit.inTexted()) { // no paragraphs in mathed
1184 Paragraph const & par = dit.paragraph();
1185 pos_type const pos = dit.pos();
1187 if (!par.isDeleted(pos)) {
1188 bool wordsep = par.isWordSeparator(pos);
1189 if (inword && wordsep)
1190 break; // stop at word end
1191 else if (!inword && !wordsep)
1195 } else if (intext) {
1196 // move to end of math
1197 while (!dit.inTexted() && !dit.atEnd()) dit.forwardPos();
1201 dit.forwardPosIgnoreCollapsed();
1203 if (dit.atEnd()) dit = prv;
1204 if (dit == cur) return false; // we didn't move
1207 // see comment above
1208 cur.bv().checkDepm(cur, orig);
1211 pos_type const lastpos = cur.lastpos();
1212 pit_type pit = cur.pit();
1213 pos_type pos = cur.pos();
1214 Paragraph const & par = cur.paragraph();
1216 // Paragraph boundary is a word boundary
1217 if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
1218 if (pit != cur.lastpit())
1219 return setCursor(cur, pit + 1, 0);
1224 LASSERT(pos < lastpos, return false); // see above
1225 if (!par.isWordSeparator(pos))
1226 while (pos != lastpos && !par.isWordSeparator(pos))
1228 else if (par.isChar(pos))
1229 while (pos != lastpos && par.isChar(pos))
1231 else if (!par.isSpace(pos)) // non-char inset
1234 // Skip over white space
1235 while (pos != lastpos && par.isSpace(pos))
1238 // Don't skip a separator inset at the end of a paragraph
1239 if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
1242 return setCursor(cur, pit, pos);
1247 bool Text::cursorBackwardOneWord(Cursor & cur)
1249 LBUFERR(this == cur.text());
1251 if (lyxrc.mac_like_cursor_movement) {
1252 DocIterator dit(cur);
1253 bool inword = false;
1254 bool intext = dit.inTexted();
1255 while (!dit.atBegin()) {
1256 DocIterator prv(dit);
1257 dit.backwardPosIgnoreCollapsed();
1258 if (dit.inTexted()) { // no paragraphs in mathed
1259 Paragraph const & par = dit.paragraph();
1260 pos_type pos = dit.pos();
1262 if (!par.isDeleted(pos)) {
1263 bool wordsep = par.isWordSeparator(pos);
1264 if (inword && wordsep) {
1266 break; // stop at word begin
1267 } else if (!inword && !wordsep)
1271 } else if (intext) {
1272 // move to begin of math
1273 while (!dit.inTexted() && !dit.atBegin()) dit.backwardPos();
1277 if (dit == cur) return false; // we didn't move
1280 // see comment above cursorForwardOneWord
1281 cur.bv().checkDepm(cur, orig);
1284 Paragraph const & par = cur.paragraph();
1285 pit_type const pit = cur.pit();
1286 pos_type pos = cur.pos();
1288 // Paragraph boundary is a word boundary
1289 if (pos == 0 && pit != 0) {
1290 Paragraph & prevpar = getPar(pit - 1);
1291 pos = prevpar.size();
1292 // Don't stop after an environment separator
1293 if (pos && prevpar.isEnvSeparator(pos - 1))
1295 return setCursor(cur, pit - 1, pos);
1297 // Skip over white space
1298 while (pos != 0 && par.isSpace(pos - 1))
1301 if (pos != 0 && !par.isWordSeparator(pos - 1))
1302 while (pos != 0 && !par.isWordSeparator(pos - 1))
1304 else if (pos != 0 && par.isChar(pos - 1))
1305 while (pos != 0 && par.isChar(pos - 1))
1307 else if (pos != 0 && !par.isSpace(pos - 1)) // non-char inset
1310 return setCursor(cur, pit, pos);
1315 bool Text::cursorVisLeftOneWord(Cursor & cur)
1317 LBUFERR(this == cur.text());
1319 pos_type left_pos, right_pos;
1321 Cursor temp_cur = cur;
1323 // always try to move at least once...
1324 while (temp_cur.posVisLeft(true /* skip_inset */)) {
1326 // collect some information about current cursor position
1327 temp_cur.getSurroundingPos(left_pos, right_pos);
1328 bool left_is_letter =
1329 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1330 bool right_is_letter =
1331 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1333 // if we're not at a letter/non-letter boundary, continue moving
1334 if (left_is_letter == right_is_letter)
1337 // we should stop when we have an LTR word on our right or an RTL word
1339 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1340 temp_cur.buffer()->params(), left_pos).isRightToLeft())
1341 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1342 temp_cur.buffer()->params(), right_pos).isRightToLeft()))
1346 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1347 true, temp_cur.boundary());
1351 bool Text::cursorVisRightOneWord(Cursor & cur)
1353 LBUFERR(this == cur.text());
1355 pos_type left_pos, right_pos;
1357 Cursor temp_cur = cur;
1359 // always try to move at least once...
1360 while (temp_cur.posVisRight(true /* skip_inset */)) {
1362 // collect some information about current cursor position
1363 temp_cur.getSurroundingPos(left_pos, right_pos);
1364 bool left_is_letter =
1365 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1366 bool right_is_letter =
1367 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1369 // if we're not at a letter/non-letter boundary, continue moving
1370 if (left_is_letter == right_is_letter)
1373 // we should stop when we have an LTR word on our right or an RTL word
1375 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1376 temp_cur.buffer()->params(),
1377 left_pos).isRightToLeft())
1378 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1379 temp_cur.buffer()->params(),
1380 right_pos).isRightToLeft()))
1384 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1385 true, temp_cur.boundary());
1389 void Text::selectWord(Cursor & cur, word_location loc)
1391 LBUFERR(this == cur.text());
1392 CursorSlice from = cur.top();
1394 getWord(from, to, loc);
1395 if (cur.top() != from)
1396 setCursor(cur, from.pit(), from.pos());
1399 if (!cur.selection())
1401 setCursor(cur, to.pit(), to.pos());
1403 cur.setWordSelection(true);
1407 void Text::expandWordSel(Cursor & cur)
1409 // get selection of word around cur
1412 c.text()->selectWord(c, WHOLE_WORD);
1413 // get selection around anchor too.
1414 // FIXME: this cursor is not a proper one. normalAnchor() should
1415 // return a DocIterator.
1417 a.push_back(cur.normalAnchor());
1418 a.text()->selectWord(a, WHOLE_WORD);
1419 // use the correct word boundary, depending on selection direction
1420 if (cur.top() > cur.normalAnchor()) {
1421 cur.top() = a.selBegin();
1423 cur.top() = c.selEnd();
1425 cur.top() = a.selEnd();
1427 cur.top() = c.selBegin();
1432 void Text::selectAll(Cursor & cur)
1434 LBUFERR(this == cur.text());
1435 if (cur.lastpos() == 0 && cur.lastpit() == 0)
1437 // If the cursor is at the beginning, make sure the cursor ends there
1438 if (cur.pit() == 0 && cur.pos() == 0) {
1439 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1441 setCursor(cur, 0, 0);
1443 setCursor(cur, 0, 0);
1445 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1451 // Select the word currently under the cursor when no
1452 // selection is currently set
1453 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
1455 LBUFERR(this == cur.text());
1456 if (cur.selection())
1458 selectWord(cur, loc);
1459 return cur.selection();
1463 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
1465 LBUFERR(this == cur.text());
1467 if (!cur.selection()) {
1468 if (!selectChange(cur))
1472 cur.recordUndoSelection();
1474 pit_type begPit = cur.selectionBegin().pit();
1475 pit_type endPit = cur.selectionEnd().pit();
1477 pos_type begPos = cur.selectionBegin().pos();
1478 pos_type endPos = cur.selectionEnd().pos();
1480 // keep selection info, because endPos becomes invalid after the first loop
1481 bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size());
1483 // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
1484 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1485 pos_type parSize = pars_[pit].size();
1487 // ignore empty paragraphs; otherwise, an assertion will fail for
1488 // acceptChanges(bparams, 0, 0) or rejectChanges(bparams, 0, 0)
1492 // do not consider first paragraph if the cursor starts at pos size()
1493 if (pit == begPit && begPos == parSize)
1496 // do not consider last paragraph if the cursor ends at pos 0
1497 if (pit == endPit && endPos == 0)
1498 break; // last iteration anyway
1500 pos_type const left = (pit == begPit ? begPos : 0);
1501 pos_type const right = (pit == endPit ? endPos : parSize);
1504 // there is no change here
1508 pars_[pit].acceptChanges(left, right);
1510 pars_[pit].rejectChanges(left, right);
1514 // next, accept/reject imaginary end-of-par characters
1516 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1517 pos_type pos = pars_[pit].size();
1519 // skip if the selection ends before the end-of-par
1520 if (pit == endPit && endsBeforeEndOfPar)
1521 break; // last iteration anyway
1523 // skip if this is not the last paragraph of the document
1524 // note: the user should be able to accept/reject the par break of the last par!
1525 if (pit == endPit && pit + 1 != int(pars_.size()))
1526 break; // last iteration anway
1529 if (pars_[pit].isInserted(pos)) {
1530 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1531 } else if (pars_[pit].isDeleted(pos)) {
1532 if (pit + 1 == int(pars_.size())) {
1533 // we cannot remove a par break at the end of the last paragraph;
1534 // instead, we mark it unchanged
1535 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1537 mergeParagraph(cur.buffer()->params(), pars_, pit);
1543 if (pars_[pit].isDeleted(pos)) {
1544 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1545 } else if (pars_[pit].isInserted(pos)) {
1546 if (pit + 1 == int(pars_.size())) {
1547 // we mark the par break at the end of the last paragraph unchanged
1548 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1550 mergeParagraph(cur.buffer()->params(), pars_, pit);
1558 // finally, invoke the DEPM
1559 deleteEmptyParagraphMechanism(begPit, endPit, begPos, endPos,
1560 cur.buffer()->params().track_changes);
1563 cur.clearSelection();
1564 setCursorIntern(cur, begPit, begPos);
1565 cur.screenUpdateFlags(Update::Force);
1566 cur.forceBufferUpdate();
1570 void Text::acceptChanges()
1572 BufferParams const & bparams = owner_->buffer().params();
1573 lyx::acceptChanges(pars_, bparams);
1574 deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes);
1578 void Text::rejectChanges()
1580 BufferParams const & bparams = owner_->buffer().params();
1581 pit_type pars_size = static_cast<pit_type>(pars_.size());
1583 // first, reject changes within each individual paragraph
1584 // (do not consider end-of-par)
1585 for (pit_type pit = 0; pit < pars_size; ++pit) {
1586 if (!pars_[pit].empty()) // prevent assertion failure
1587 pars_[pit].rejectChanges(0, pars_[pit].size());
1590 // next, reject imaginary end-of-par characters
1591 for (pit_type pit = 0; pit < pars_size; ++pit) {
1592 pos_type pos = pars_[pit].size();
1594 if (pars_[pit].isDeleted(pos)) {
1595 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1596 } else if (pars_[pit].isInserted(pos)) {
1597 if (pit == pars_size - 1) {
1598 // we mark the par break at the end of the last
1599 // paragraph unchanged
1600 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1602 mergeParagraph(bparams, pars_, pit);
1609 // finally, invoke the DEPM
1610 deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes);
1614 void Text::deleteWordForward(Cursor & cur, bool const force)
1616 LBUFERR(this == cur.text());
1617 if (cur.lastpos() == 0)
1621 cur.selection(true);
1622 cursorForwardOneWord(cur);
1624 if (force || !cur.confirmDeletion()) {
1625 cutSelection(cur, false);
1626 cur.checkBufferStructure();
1632 void Text::deleteWordBackward(Cursor & cur, bool const force)
1634 LBUFERR(this == cur.text());
1635 if (cur.lastpos() == 0)
1636 cursorBackward(cur);
1639 cur.selection(true);
1640 cursorBackwardOneWord(cur);
1642 if (force || !cur.confirmDeletion()) {
1643 cutSelection(cur, false);
1644 cur.checkBufferStructure();
1650 // Kill to end of line.
1651 void Text::changeCase(Cursor & cur, TextCase action, bool partial)
1653 LBUFERR(this == cur.text());
1657 bool const gotsel = cur.selection();
1659 from = cur.selBegin();
1663 getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
1664 cursorForwardOneWord(cur);
1667 cur.recordUndoSelection();
1669 pit_type begPit = from.pit();
1670 pit_type endPit = to.pit();
1672 pos_type begPos = from.pos();
1673 pos_type endPos = to.pos();
1675 pos_type right = 0; // needed after the for loop
1677 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1678 Paragraph & par = pars_[pit];
1679 pos_type const pos = (pit == begPit ? begPos : 0);
1680 right = (pit == endPit ? endPos : par.size());
1681 par.changeCase(cur.buffer()->params(), pos, right, action);
1684 // the selection may have changed due to logically-only deleted chars
1686 setCursor(cur, begPit, begPos);
1688 setCursor(cur, endPit, right);
1691 setCursor(cur, endPit, right);
1693 cur.checkBufferStructure();
1697 bool Text::handleBibitems(Cursor & cur)
1699 if (cur.paragraph().layout().labeltype != LABEL_BIBLIO)
1705 BufferParams const & bufparams = cur.buffer()->params();
1706 Paragraph const & par = cur.paragraph();
1707 Cursor prevcur = cur;
1708 if (cur.pit() > 0) {
1710 prevcur.pos() = prevcur.lastpos();
1712 Paragraph const & prevpar = prevcur.paragraph();
1714 // if a bibitem is deleted, merge with previous paragraph
1715 // if this is a bibliography item as well
1716 if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
1717 cur.recordUndo(prevcur.pit());
1718 mergeParagraph(bufparams, cur.text()->paragraphs(),
1720 cur.forceBufferUpdate();
1721 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1722 cur.screenUpdateFlags(Update::Force);
1726 // otherwise reset to default
1727 cur.paragraph().setPlainOrDefaultLayout(bufparams.documentClass());
1732 bool Text::erase(Cursor & cur)
1734 LASSERT(this == cur.text(), return false);
1735 bool needsUpdate = false;
1736 Paragraph & par = cur.paragraph();
1738 if (cur.pos() != cur.lastpos()) {
1739 // this is the code for a normal delete, not pasting
1741 cur.recordUndo(DELETE_UNDO);
1742 bool const was_inset = cur.paragraph().isInset(cur.pos());
1743 if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes))
1744 // the character has been logically deleted only => skip it
1745 cur.top().forwardPos();
1748 cur.forceBufferUpdate();
1750 cur.checkBufferStructure();
1753 if (cur.pit() == cur.lastpit())
1754 return dissolveInset(cur);
1756 if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1757 cur.recordUndo(DELETE_UNDO);
1758 par.setChange(cur.pos(), Change(Change::DELETED));
1762 setCursorIntern(cur, cur.pit() + 1, 0);
1763 needsUpdate = backspacePos0(cur);
1767 needsUpdate |= handleBibitems(cur);
1770 // Make sure the cursor is correct. Is this really needed?
1771 // No, not really... at least not here!
1772 cur.top().setPitPos(cur.pit(), cur.pos());
1773 cur.checkBufferStructure();
1780 bool Text::backspacePos0(Cursor & cur)
1782 LBUFERR(this == cur.text());
1786 BufferParams const & bufparams = cur.buffer()->params();
1787 ParagraphList & plist = cur.text()->paragraphs();
1788 Paragraph const & par = cur.paragraph();
1789 Cursor prevcur = cur;
1791 prevcur.pos() = prevcur.lastpos();
1792 Paragraph const & prevpar = prevcur.paragraph();
1794 // is it an empty paragraph?
1795 if (cur.lastpos() == 0
1796 || (cur.lastpos() == 1 && par.isSeparator(0))) {
1797 cur.recordUndo(prevcur.pit());
1798 plist.erase(plist.iterator_at(cur.pit()));
1800 // is previous par empty?
1801 else if (prevcur.lastpos() == 0
1802 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
1803 cur.recordUndo(prevcur.pit());
1804 plist.erase(plist.iterator_at(prevcur.pit()));
1806 // FIXME: Do we really not want to allow this???
1807 // Pasting is not allowed, if the paragraphs have different
1808 // layouts. I think it is a real bug of all other
1809 // word processors to allow it. It confuses the user.
1810 // Correction: Pasting is always allowed with standard-layout
1811 // or the empty layout.
1813 cur.recordUndo(prevcur.pit());
1814 mergeParagraph(bufparams, plist, prevcur.pit());
1817 cur.forceBufferUpdate();
1818 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1824 bool Text::backspace(Cursor & cur)
1826 LBUFERR(this == cur.text());
1827 bool needsUpdate = false;
1828 if (cur.pos() == 0) {
1830 return dissolveInset(cur);
1832 Cursor prev_cur = cur;
1835 if (!cur.paragraph().empty()
1836 && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1837 cur.recordUndo(prev_cur.pit(), prev_cur.pit());
1838 prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
1839 setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
1842 // The cursor is at the beginning of a paragraph, so
1843 // the backspace will collapse two paragraphs into one.
1844 needsUpdate = backspacePos0(cur);
1847 // this is the code for a normal backspace, not pasting
1849 cur.recordUndo(DELETE_UNDO);
1850 // We used to do cursorBackwardIntern() here, but it is
1851 // not a good idea since it triggers the auto-delete
1852 // mechanism. So we do a cursorBackwardIntern()-lite,
1853 // without the dreaded mechanism. (JMarc)
1854 setCursorIntern(cur, cur.pit(), cur.pos() - 1,
1855 false, cur.boundary());
1856 bool const was_inset = cur.paragraph().isInset(cur.pos());
1857 cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes);
1859 cur.forceBufferUpdate();
1861 cur.checkBufferStructure();
1864 if (cur.pos() == cur.lastpos())
1865 cur.setCurrentFont();
1867 needsUpdate |= handleBibitems(cur);
1869 // A singlePar update is not enough in this case.
1870 // cur.screenUpdateFlags(Update::Force);
1871 cur.top().setPitPos(cur.pit(), cur.pos());
1877 bool Text::dissolveInset(Cursor & cur)
1879 LASSERT(this == cur.text(), return false);
1881 if (isMainText() || cur.inset().nargs() != 1)
1884 cur.recordUndoInset();
1886 cur.selHandle(false);
1887 // save position inside inset
1888 pos_type spos = cur.pos();
1889 pit_type spit = cur.pit();
1890 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1892 // update cursor offset
1896 // remember position outside inset to delete inset later
1897 // we do not do it now to avoid memory reuse issues (see #10667).
1898 DocIterator inset_it = cur;
1902 Buffer & b = *cur.buffer();
1903 // Is there anything in this text?
1904 if (inset_non_empty) {
1906 // we clear the cache so that we won't get conflicts with labels
1907 // that get pasted into the buffer. we should update this before
1908 // its being empty matters. if not (i.e., if we encounter bugs),
1909 // then this should instead be:
1910 // cur.buffer().updateBuffer();
1911 // but we'll try the cheaper solution here.
1912 cur.buffer()->clearReferenceCache();
1914 ParagraphList & plist = paragraphs();
1915 if (!lyxrc.ct_markup_copied)
1916 // Do not revive deleted text
1917 lyx::acceptChanges(plist, b.params());
1919 // ERT paragraphs have the Language latex_language.
1920 // This is invalid outside of ERT, so we need to
1921 // change it to the buffer language.
1922 for (auto & p : plist)
1923 p.changeLanguage(b.params(), latex_language, b.language());
1925 /* If the inset is the only thing in paragraph and the layout
1926 * is not plain, then the layout of the first paragraph of
1927 * inset should be remembered.
1928 * FIXME: this does not work as expected when change tracking
1929 * is on However, we do not really know what to do in this
1932 DocumentClass const & tclass = cur.buffer()->params().documentClass();
1933 if (inset_it.lastpos() == 1
1934 && !tclass.isPlainLayout(plist[0].layout())
1935 && !tclass.isDefaultLayout(plist[0].layout())) {
1936 // Copy all parameters except depth.
1937 Paragraph & par = cur.paragraph();
1938 par.setLayout(plist[0].layout());
1939 depth_type const dpth = par.getDepth();
1940 par.params() = plist[0].params();
1941 par.params().depth(dpth);
1944 pasteParagraphList(cur, plist, b.params().documentClassPtr(),
1945 b.params().authors(),
1946 b.errorList("Paste"));
1949 // delete the inset now
1950 inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes);
1953 cur.pit() = min(cur.lastpit(), spit);
1954 cur.pos() = min(cur.lastpos(), spos);
1955 // Ensure the current language is set correctly (bug 6292)
1956 cur.text()->setCursor(cur, cur.pit(), cur.pos());
1957 cur.clearSelection();
1959 cur.forceBufferUpdate();
1965 bool Text::splitInset(Cursor & cur)
1967 LASSERT(this == cur.text(), return false);
1969 if (isMainText() || cur.inset().nargs() != 1)
1973 if (cur.selection()) {
1974 // start from selection begin
1975 setCursor(cur, cur.selBegin().pit(), cur.selBegin().pos());
1976 cur.clearSelection();
1978 // save split position inside inset
1979 // (we need to copy the whole inset first)
1980 pos_type spos = cur.pos();
1981 pit_type spit = cur.pit();
1982 // some things only need to be done if the inset has content
1983 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1985 // move right before the inset
1988 // remember position outside inset
1989 pos_type ipos = cur.pos();
1990 pit_type ipit = cur.pit();
1995 cap::copySelectionToTemp(cur);
1996 cur.clearSelection();
1998 // paste copied inset
1999 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
2000 cur.forceBufferUpdate();
2002 // if the inset has text, cut after split position
2003 // and paste to new inset
2004 if (inset_non_empty) {
2005 // go back to first inset
2006 cur.text()->setCursor(cur, ipit, ipos);
2008 setCursor(cur, spit, spos);
2010 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
2012 // Remember whether there was something cut that has to be pasted below
2014 bool const hasCut = cur.selection();
2015 cap::cutSelectionToTemp(cur);
2017 cur.selHandle(false);
2019 bool atlastpos = false;
2020 if (cur.pos() == 0 && cur.pit() > 0) {
2021 // if we are at par start, remove this par
2022 cur.text()->backspace(cur);
2023 cur.forceBufferUpdate();
2024 } else if (cur.pos() == cur.lastpos())
2026 // Move out of and jump over inset
2034 cur.text()->selectAll(cur);
2035 cutSelection(cur, false);
2036 // If there was something cut paste it
2038 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
2039 cur.text()->setCursor(cur, 0, 0);
2040 if (atlastpos && cur.paragraph().isFreeSpacing() && cur.paragraph().empty()) {
2041 // We started from par end, remove extra empty par in free spacing insets
2042 cur.text()->erase(cur);
2043 cur.forceBufferUpdate();
2052 void Text::getWord(CursorSlice & from, CursorSlice & to,
2053 word_location const loc) const
2056 pars_[to.pit()].locateWord(from.pos(), to.pos(), loc);
2060 void Text::write(ostream & os) const
2062 Buffer const & buf = owner_->buffer();
2063 ParagraphList::const_iterator pit = paragraphs().begin();
2064 ParagraphList::const_iterator end = paragraphs().end();
2066 for (; pit != end; ++pit)
2067 pit->write(os, buf.params(), dth);
2069 // Close begin_deeper
2070 for(; dth > 0; --dth)
2071 os << "\n\\end_deeper";
2075 bool Text::read(Lexer & lex,
2076 ErrorList & errorList, InsetText * insetPtr)
2078 Buffer const & buf = owner_->buffer();
2079 depth_type depth = 0;
2082 while (lex.isOK()) {
2084 string const token = lex.getString();
2089 if (token == "\\end_inset")
2092 if (token == "\\end_body")
2095 if (token == "\\begin_body")
2098 if (token == "\\end_document") {
2103 if (token == "\\begin_layout") {
2104 lex.pushToken(token);
2107 par.setInsetOwner(insetPtr);
2108 par.params().depth(depth);
2109 par.setFont(0, Font(inherit_font, buf.params().language));
2110 pars_.push_back(par);
2111 readParagraph(pars_.back(), lex, errorList);
2113 // register the words in the global word list
2114 pars_.back().updateWords();
2115 } else if (token == "\\begin_deeper") {
2117 } else if (token == "\\end_deeper") {
2119 lex.printError("\\end_deeper: " "depth is already null");
2123 LYXERR0("Handling unknown body token: `" << token << '\'');
2127 // avoid a crash on weird documents (bug 4859)
2128 if (pars_.empty()) {
2130 par.setInsetOwner(insetPtr);
2131 par.params().depth(depth);
2132 par.setFont(0, Font(inherit_font,
2133 buf.params().language));
2134 par.setPlainOrDefaultLayout(buf.params().documentClass());
2135 pars_.push_back(par);
2142 // Returns the current state (font, depth etc.) as a message for status bar.
2143 docstring Text::currentState(CursorData const & cur, bool devel_mode) const
2145 LBUFERR(this == cur.text());
2146 Buffer & buf = *cur.buffer();
2147 Paragraph const & par = cur.paragraph();
2148 odocstringstream os;
2150 if (buf.params().track_changes)
2151 os << _("[Change Tracking] ");
2153 Change change = par.lookupChange(cur.pos());
2155 if (change.changed()) {
2156 docstring const author =
2157 buf.params().authors().get(change.author).nameAndEmail();
2158 docstring const date = formatted_datetime(change.changetime);
2159 os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
2163 // I think we should only show changes from the default
2165 // No, from the document font (MV)
2166 Font font = cur.real_current_font;
2167 font.fontInfo().reduce(buf.params().getFont().fontInfo());
2169 os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
2171 // The paragraph depth
2172 int depth = par.getDepth();
2174 os << bformat(_(", Depth: %1$d"), depth);
2176 // The paragraph spacing, but only if different from
2178 Spacing const & spacing = par.params().spacing();
2179 if (!spacing.isDefault()) {
2180 os << _(", Spacing: ");
2181 switch (spacing.getSpace()) {
2182 case Spacing::Single:
2185 case Spacing::Onehalf:
2188 case Spacing::Double:
2191 case Spacing::Other:
2192 os << _("Other (") << from_ascii(spacing.getValueAsString()) << ')';
2194 case Spacing::Default:
2195 // should never happen, do nothing
2200 // Custom text style
2201 InsetLayout const & layout = cur.inset().getLayout();
2202 if (layout.lyxtype() == InsetLyXType::CHARSTYLE)
2203 os << _(", Style: ") << translateIfPossible(layout.labelstring());
2206 os << _(", Inset: ") << &cur.inset();
2207 if (cur.lastidx() > 0)
2208 os << _(", Cell: ") << cur.idx();
2209 os << _(", Paragraph: ") << cur.pit();
2210 os << _(", Id: ") << par.id();
2211 os << _(", Position: ") << cur.pos();
2212 // FIXME: Why is the check for par.size() needed?
2213 // We are called with cur.pos() == par.size() quite often.
2214 if (!par.empty() && cur.pos() < par.size()) {
2215 // Force output of code point, not character
2216 size_t const c = par.getChar(cur.pos());
2217 if (c == META_INSET)
2218 os << ", Char: INSET";
2220 os << _(", Char: 0x") << hex << c;
2222 os << _(", Boundary: ") << cur.boundary();
2223 // Row & row = cur.textRow();
2224 // os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
2230 docstring Text::getPossibleLabel(DocIterator const & cur) const
2232 pit_type textpit = cur.pit();
2233 Layout const * layout = &(pars_[textpit].layout());
2235 // Will contain the label prefix.
2238 // For captions, we just take the caption type
2239 Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
2240 if (caption_inset) {
2241 string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
2242 FloatList const & fl = cur.buffer()->params().documentClass().floats();
2243 if (fl.typeExist(ftype)) {
2244 Floating const & flt = fl.getType(ftype);
2245 name = from_utf8(flt.refPrefix());
2248 name = from_utf8(ftype.substr(0,3));
2250 // For section, subsection, etc...
2251 if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) {
2252 Layout const * layout2 = &(pars_[textpit - 1].layout());
2253 if (layout2->latextype != LATEX_PARAGRAPH) {
2258 if (layout->latextype != LATEX_PARAGRAPH)
2259 name = layout->refprefix;
2261 // If none of the above worked, see if the inset knows.
2263 InsetLayout const & il = cur.inset().getLayout();
2264 name = il.refprefix();
2269 docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE);
2271 // The return string of math matrices might contain linebreaks
2272 par_text = subst(par_text, '\n', '-');
2273 int const numwords = 3;
2274 for (int i = 0; i < numwords; ++i) {
2275 if (par_text.empty())
2278 par_text = split(par_text, head, ' ');
2279 // Is it legal to use spaces in labels ?
2285 // Make sure it isn't too long
2286 unsigned int const max_label_length = 32;
2287 if (text.size() > max_label_length)
2288 text.resize(max_label_length);
2291 text = name + ':' + text;
2293 // We need a unique label
2294 docstring label = text;
2296 while (cur.buffer()->activeLabel(label)) {
2297 label = text + '-' + convert<docstring>(i);
2305 docstring Text::asString(int options) const
2307 return asString(0, pars_.size(), options);
2311 docstring Text::asString(pit_type beg, pit_type end, int options) const
2313 size_t i = size_t(beg);
2314 docstring str = pars_[i].asString(options);
2315 for (++i; i != size_t(end); ++i) {
2317 str += pars_[i].asString(options);
2323 void Text::shortenForOutliner(docstring & str, size_t const maxlen)
2325 support::truncateWithEllipsis(str, maxlen);
2326 for (char_type & c : str)
2327 if (c == L'\n' || c == L'\t')
2332 void Text::forOutliner(docstring & os, size_t const maxlen,
2333 bool const shorten) const
2335 pit_type end = pars_.size() - 1;
2336 if (0 <= end && !pars_[0].labelString().empty())
2337 os += pars_[0].labelString() + ' ';
2338 forOutliner(os, maxlen, 0, end, shorten);
2342 void Text::forOutliner(docstring & os, size_t const maxlen,
2343 pit_type pit_start, pit_type pit_end,
2344 bool const shorten) const
2346 size_t tmplen = shorten ? maxlen + 1 : maxlen;
2347 pit_type end = min(size_t(pit_end), pars_.size() - 1);
2349 for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) {
2352 // This function lets the first label be treated separately
2353 pars_[i].forOutliner(os, tmplen, false, !first);
2357 shortenForOutliner(os, maxlen);
2361 void Text::charsTranspose(Cursor & cur)
2363 LBUFERR(this == cur.text());
2365 pos_type pos = cur.pos();
2367 // If cursor is at beginning or end of paragraph, do nothing.
2368 if (pos == cur.lastpos() || pos == 0)
2371 Paragraph & par = cur.paragraph();
2373 // Get the positions of the characters to be transposed.
2374 pos_type pos1 = pos - 1;
2375 pos_type pos2 = pos;
2377 // In change tracking mode, ignore deleted characters.
2378 while (pos2 < cur.lastpos() && par.isDeleted(pos2))
2380 if (pos2 == cur.lastpos())
2383 while (pos1 >= 0 && par.isDeleted(pos1))
2388 // Don't do anything if one of the "characters" is not regular text.
2389 if (par.isInset(pos1) || par.isInset(pos2))
2392 // Store the characters to be transposed (including font information).
2393 char_type const char1 = par.getChar(pos1);
2395 par.getFontSettings(cur.buffer()->params(), pos1);
2397 char_type const char2 = par.getChar(pos2);
2399 par.getFontSettings(cur.buffer()->params(), pos2);
2401 // And finally, we are ready to perform the transposition.
2402 // Track the changes if Change Tracking is enabled.
2403 bool const trackChanges = cur.buffer()->params().track_changes;
2407 par.eraseChar(pos2, trackChanges);
2408 par.eraseChar(pos1, trackChanges);
2409 par.insertChar(pos1, char2, font2, trackChanges);
2410 par.insertChar(pos2, char1, font1, trackChanges);
2412 cur.checkBufferStructure();
2414 // After the transposition, move cursor to after the transposition.
2415 setCursor(cur, cur.pit(), pos2);
2420 DocIterator Text::macrocontextPosition() const
2422 return macrocontext_position_;
2426 void Text::setMacrocontextPosition(DocIterator const & pos)
2428 macrocontext_position_ = pos;
2432 bool Text::completionSupported(Cursor const & cur) const
2434 Paragraph const & par = cur.paragraph();
2435 return !cur.buffer()->isReadonly()
2438 && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos()))
2439 && !par.isWordSeparator(cur.pos() - 1);
2443 CompletionList const * Text::createCompletionList(Cursor const & cur) const
2445 WordList const & list = theWordList(cur.getFont().language()->lang());
2446 return new TextCompletionList(cur, list);
2450 bool Text::insertCompletion(Cursor & cur, docstring const & s)
2452 LBUFERR(cur.bv().cursor() == cur);
2453 if (cur.buffer()->isReadonly())
2457 cur.bv().cursor() = cur;
2458 if (!(cur.result().screenUpdate() & Update::Force))
2459 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
2464 docstring Text::completionPrefix(Cursor const & cur) const
2466 CursorSlice from = cur.top();
2467 CursorSlice to = from;
2468 getWord(from, to, PREVIOUS_WORD);
2470 return cur.paragraph().asString(from.pos(), to.pos());
2473 bool Text::isMainText() const
2475 return &owner_->buffer().text() == this;
2479 // Note that this is supposed to return a fully realized font.
2480 FontInfo Text::layoutFont(pit_type const pit) const
2482 Layout const & layout = pars_[pit].layout();
2484 if (!pars_[pit].getDepth()) {
2485 FontInfo lf = layout.resfont;
2486 // In case the default family has been customized
2487 if (layout.font.family() == INHERIT_FAMILY)
2488 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
2489 FontInfo icf = (!isMainText())
2490 // inside insets, we call the getFont() method
2492 // outside, we access the layout font directly
2493 : owner_->getLayout().font();
2498 FontInfo font = layout.font;
2499 // Realize with the fonts of lesser depth.
2500 //font.realize(outerFont(pit));
2501 font.realize(owner_->buffer().params().getFont().fontInfo());
2507 // Note that this is supposed to return a fully realized font.
2508 FontInfo Text::labelFont(Paragraph const & par) const
2510 Buffer const & buffer = owner_->buffer();
2511 Layout const & layout = par.layout();
2513 if (!par.getDepth()) {
2514 FontInfo lf = layout.reslabelfont;
2515 // In case the default family has been customized
2516 if (layout.labelfont.family() == INHERIT_FAMILY)
2517 lf.setFamily(buffer.params().getFont().fontInfo().family());
2521 FontInfo font = layout.labelfont;
2522 // Realize with the fonts of lesser depth.
2523 font.realize(buffer.params().getFont().fontInfo());
2529 void Text::setCharFont(pit_type pit,
2530 pos_type pos, Font const & fnt, Font const & display_font)
2532 Buffer const & buffer = owner_->buffer();
2534 Layout const & layout = pars_[pit].layout();
2536 // Get concrete layout font to reduce against
2537 FontInfo layoutfont;
2539 if (pos < pars_[pit].beginOfBody())
2540 layoutfont = layout.labelfont;
2542 layoutfont = layout.font;
2544 // Realize against environment font information
2545 if (pars_[pit].getDepth()) {
2547 while (!layoutfont.resolved() &&
2548 tp != pit_type(paragraphs().size()) &&
2549 pars_[tp].getDepth()) {
2551 if (tp != pit_type(paragraphs().size()))
2552 layoutfont.realize(pars_[tp].layout().font);
2556 // Inside inset, apply the inset's font attributes if any
2559 layoutfont.realize(display_font.fontInfo());
2561 layoutfont.realize(buffer.params().getFont().fontInfo());
2563 // Now, reduce font against full layout font
2564 font.fontInfo().reduce(layoutfont);
2566 pars_[pit].setFont(pos, font);
2570 void Text::setInsetFont(BufferView const & bv, pit_type pit,
2571 pos_type pos, Font const & font)
2573 Inset * const inset = pars_[pit].getInset(pos);
2574 LASSERT(inset && inset->resetFontEdit(), return);
2576 idx_type endidx = inset->nargs();
2577 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
2578 Text * text = cs.text();
2580 // last position of the cell
2581 CursorSlice cellend = cs;
2582 cellend.pit() = cellend.lastpit();
2583 cellend.pos() = cellend.lastpos();
2584 text->setFont(bv, cs, cellend, font);
2590 void Text::setLayout(pit_type start, pit_type end,
2591 docstring const & layout)
2593 // FIXME: make this work in multicell selection case
2594 LASSERT(start != end, return);
2596 Buffer const & buffer = owner_->buffer();
2597 BufferParams const & bp = buffer.params();
2598 Layout const & lyxlayout = bp.documentClass()[layout];
2600 for (pit_type pit = start; pit != end; ++pit) {
2601 Paragraph & par = pars_[pit];
2602 // Is this a separating paragraph? If so,
2603 // this needs to be standard layout
2604 bool const is_separator = par.size() == 1
2605 && par.isEnvSeparator(0);
2606 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
2607 if (lyxlayout.margintype == MARGIN_MANUAL)
2608 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
2611 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
2615 // set layout over selection and make a total rebreak of those paragraphs
2616 void Text::setLayout(Cursor & cur, docstring const & layout)
2618 LBUFERR(this == cur.text());
2620 pit_type start = cur.selBegin().pit();
2621 pit_type end = cur.selEnd().pit() + 1;
2622 cur.recordUndoSelection();
2623 setLayout(start, end, layout);
2625 cur.setCurrentFont();
2626 cur.forceBufferUpdate();
2630 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
2631 Paragraph const & par, int max_depth)
2633 int const depth = par.params().depth();
2634 if (type == Text::INC_DEPTH && depth < max_depth)
2636 if (type == Text::DEC_DEPTH && depth > 0)
2642 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
2644 LBUFERR(this == cur.text());
2645 // this happens when selecting several cells in tabular (bug 2630)
2646 if (cur.selBegin().idx() != cur.selEnd().idx())
2649 pit_type const beg = cur.selBegin().pit();
2650 pit_type const end = cur.selEnd().pit() + 1;
2651 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2653 for (pit_type pit = beg; pit != end; ++pit) {
2654 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
2656 max_depth = pars_[pit].getMaxDepthAfter();
2662 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
2664 LBUFERR(this == cur.text());
2665 pit_type const beg = cur.selBegin().pit();
2666 pit_type const end = cur.selEnd().pit() + 1;
2667 cur.recordUndoSelection();
2668 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2670 for (pit_type pit = beg; pit != end; ++pit) {
2671 Paragraph & par = pars_[pit];
2672 if (lyx::changeDepthAllowed(type, par, max_depth)) {
2673 int const depth = par.params().depth();
2674 if (type == INC_DEPTH)
2675 par.params().depth(depth + 1);
2677 par.params().depth(depth - 1);
2679 max_depth = par.getMaxDepthAfter();
2681 cur.setCurrentFont();
2682 // this handles the counter labels, and also fixes up
2683 // depth values for follow-on (child) paragraphs
2684 cur.forceBufferUpdate();
2688 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
2690 LASSERT(this == cur.text(), return);
2692 // If there is a selection, record undo before the cursor font is changed.
2693 if (cur.selection())
2694 cur.recordUndoSelection();
2696 // Set the current_font
2697 // Determine basis font
2698 FontInfo layoutfont;
2699 pit_type pit = cur.pit();
2700 if (cur.pos() < pars_[pit].beginOfBody())
2701 layoutfont = labelFont(pars_[pit]);
2703 layoutfont = layoutFont(pit);
2705 // Update current font
2706 cur.real_current_font.update(font,
2707 cur.buffer()->params().language,
2710 // Reduce to implicit settings
2711 cur.current_font = cur.real_current_font;
2712 cur.current_font.fontInfo().reduce(layoutfont);
2713 // And resolve it completely
2714 cur.real_current_font.fontInfo().realize(layoutfont);
2716 // if there is no selection that's all we need to do
2717 if (!cur.selection())
2720 // Ok, we have a selection.
2721 Font newfont = font;
2724 // Toggling behaves as follows: We check the first character of the
2725 // selection. If it's (say) got EMPH on, then we set to off; if off,
2726 // then to on. With families and the like, we set it to INHERIT, if
2727 // we already have it.
2728 CursorSlice const & sl = cur.selBegin();
2729 Text const & text = *sl.text();
2730 Paragraph const & par = text.getPar(sl.pit());
2732 // get font at the position
2733 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
2734 text.outerFont(sl.pit()));
2735 FontInfo const & oldfi = oldfont.fontInfo();
2737 FontInfo & newfi = newfont.fontInfo();
2739 FontFamily newfam = newfi.family();
2740 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
2741 newfam == oldfi.family())
2742 newfi.setFamily(INHERIT_FAMILY);
2744 FontSeries newser = newfi.series();
2745 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
2746 newfi.setSeries(INHERIT_SERIES);
2748 FontShape newshp = newfi.shape();
2749 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
2750 newshp == oldfi.shape())
2751 newfi.setShape(INHERIT_SHAPE);
2753 ColorCode newcol = newfi.color();
2754 if (newcol != Color_none && newcol != Color_inherit
2755 && newcol != Color_ignore && newcol == oldfi.color())
2756 newfi.setColor(Color_none);
2759 if (newfi.emph() == FONT_TOGGLE)
2760 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
2761 if (newfi.underbar() == FONT_TOGGLE)
2762 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
2763 if (newfi.strikeout() == FONT_TOGGLE)
2764 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
2765 if (newfi.xout() == FONT_TOGGLE)
2766 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
2767 if (newfi.uuline() == FONT_TOGGLE)
2768 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
2769 if (newfi.uwave() == FONT_TOGGLE)
2770 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
2771 if (newfi.noun() == FONT_TOGGLE)
2772 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
2773 if (newfi.number() == FONT_TOGGLE)
2774 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
2775 if (newfi.nospellcheck() == FONT_TOGGLE)
2776 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
2779 setFont(cur.bv(), cur.selectionBegin().top(),
2780 cur.selectionEnd().top(), newfont);
2784 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
2785 CursorSlice const & end, Font const & font)
2787 Buffer const & buffer = bv.buffer();
2789 // Don't use forwardChar here as ditend might have
2790 // pos() == lastpos() and forwardChar would miss it.
2791 // Can't use forwardPos either as this descends into
2793 Language const * language = buffer.params().language;
2794 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
2795 if (dit.pos() == dit.lastpos())
2797 pit_type const pit = dit.pit();
2798 pos_type const pos = dit.pos();
2799 Inset * inset = pars_[pit].getInset(pos);
2800 if (inset && inset->resetFontEdit()) {
2801 // We need to propagate the font change to all
2802 // text cells of the inset (bugs 1973, 6919).
2803 setInsetFont(bv, pit, pos, font);
2805 TextMetrics const & tm = bv.textMetrics(this);
2806 Font f = tm.displayFont(pit, pos);
2807 f.update(font, language);
2808 setCharFont(pit, pos, f, tm.font_);
2809 // font change may change language...
2810 // spell checker has to know that
2811 pars_[pit].requestSpellCheck(pos);
2816 bool Text::cursorTop(Cursor & cur)
2818 LBUFERR(this == cur.text());
2819 return setCursor(cur, 0, 0);
2823 bool Text::cursorBottom(Cursor & cur)
2825 LBUFERR(this == cur.text());
2826 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
2830 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
2832 LBUFERR(this == cur.text());
2833 // If the mask is completely neutral, tell user
2834 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
2835 // Could only happen with user style
2836 cur.message(_("No font change defined."));
2840 // Try implicit word selection
2841 // If there is a change in the language the implicit word selection
2843 CursorSlice const resetCursor = cur.top();
2844 bool const implicitSelection =
2845 font.language() == ignore_language
2846 && font.fontInfo().number() == FONT_IGNORE
2847 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
2850 setFont(cur, font, toggleall);
2852 // Implicit selections are cleared afterwards
2853 // and cursor is set to the original position.
2854 if (implicitSelection) {
2855 cur.clearSelection();
2856 cur.top() = resetCursor;
2860 // if there was no selection at all, the point was to change cursor font.
2861 // Otherwise, we want to reset it to local text font.
2862 if (cur.selection() || implicitSelection)
2863 cur.setCurrentFont();
2867 docstring Text::getStringForDialog(Cursor & cur)
2869 LBUFERR(this == cur.text());
2871 if (cur.selection())
2872 return cur.selectionAsString(false);
2874 // Try implicit word selection. If there is a change
2875 // in the language the implicit word selection is
2877 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2878 docstring const & retval = cur.selectionAsString(false);
2879 cur.clearSelection();
2884 void Text::setLabelWidthStringToSequence(Cursor const & cur,
2885 docstring const & s)
2888 // Find first of same layout in sequence
2889 while (!isFirstInSequence(c.pit())) {
2890 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
2893 // now apply label width string to every par
2895 depth_type const depth = c.paragraph().getDepth();
2896 Layout const & layout = c.paragraph().layout();
2897 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
2898 while (c.paragraph().getDepth() > depth) {
2900 if (c.pit() > c.lastpit())
2903 if (c.paragraph().getDepth() < depth)
2905 if (c.paragraph().layout() != layout)
2908 c.paragraph().setLabelWidthString(s);
2913 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
2915 LBUFERR(cur.text());
2918 string const argument = to_utf8(arg);
2919 depth_type priordepth = -1;
2922 c.setCursor(cur.selectionBegin());
2923 pit_type const last_pit = cur.selectionEnd().pit();
2924 for ( ; c.pit() <= last_pit ; ++c.pit()) {
2925 Paragraph & par = c.paragraph();
2926 ParagraphParameters params = par.params();
2927 params.read(argument, merge);
2928 // Changes to label width string apply to all paragraphs
2929 // with same layout in a sequence.
2930 // Do this only once for a selected range of paragraphs
2931 // of the same layout and depth.
2933 par.params().apply(params, par.layout());
2934 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2935 setLabelWidthStringToSequence(c, params.labelWidthString());
2936 priordepth = par.getDepth();
2937 priorlayout = par.layout();
2942 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
2944 LBUFERR(cur.text());
2946 depth_type priordepth = -1;
2949 c.setCursor(cur.selectionBegin());
2950 pit_type const last_pit = cur.selectionEnd().pit();
2951 for ( ; c.pit() <= last_pit ; ++c.pit()) {
2952 Paragraph & par = c.paragraph();
2953 // Changes to label width string apply to all paragraphs
2954 // with same layout in a sequence.
2955 // Do this only once for a selected range of paragraphs
2956 // of the same layout and depth.
2958 par.params().apply(p, par.layout());
2959 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2960 setLabelWidthStringToSequence(c,
2961 par.params().labelWidthString());
2962 priordepth = par.getDepth();
2963 priorlayout = par.layout();
2968 // just insert the inset and not move the cursor.
2969 bool Text::insertInset(Cursor & cur, Inset * inset)
2971 LBUFERR(this == cur.text());
2973 return cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
2974 Change(cur.buffer()->params().track_changes
2975 ? Change::INSERTED : Change::UNCHANGED));
2979 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
2980 bool setfont, bool boundary)
2982 TextMetrics const & tm = cur.bv().textMetrics(this);
2983 bool const update_needed = !tm.contains(pit);
2985 setCursorIntern(cur, pit, pos, setfont, boundary);
2986 return cur.bv().checkDepm(cur, old) || update_needed;
2990 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
2991 bool setfont, bool boundary)
2993 LBUFERR(this == cur.text());
2994 cur.boundary(boundary);
2995 cur.top().setPitPos(pit, pos);
2997 cur.setCurrentFont();
3001 bool Text::checkAndActivateInset(Cursor & cur, bool front)
3003 if (front && cur.pos() == cur.lastpos())
3005 if (!front && cur.pos() == 0)
3007 Inset * inset = front ? cur.nextInset() : cur.prevInset();
3008 if (!inset || !inset->editable())
3010 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3013 * Apparently, when entering an inset we are expected to be positioned
3014 * *before* it in the containing paragraph, regardless of the direction
3015 * from which we are entering. Otherwise, cursor placement goes awry,
3016 * and when we exit from the beginning, we'll be placed *after* the
3021 inset->edit(cur, front);
3022 cur.setCurrentFont();
3023 cur.boundary(false);
3028 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
3030 if (cur.pos() == -1)
3032 if (cur.pos() == cur.lastpos())
3034 Paragraph & par = cur.paragraph();
3035 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
3036 if (!inset || !inset->editable())
3038 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3040 inset->edit(cur, movingForward,
3041 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
3042 cur.setCurrentFont();
3043 cur.boundary(false);
3048 bool Text::cursorBackward(Cursor & cur)
3050 // Tell BufferView to test for FitCursor in any case!
3051 cur.screenUpdateFlags(Update::FitCursor);
3053 // not at paragraph start?
3054 if (cur.pos() > 0) {
3055 // if on right side of boundary (i.e. not at paragraph end, but line end)
3056 // -> skip it, i.e. set boundary to true, i.e. go only logically left
3057 // there are some exceptions to ignore this: lineseps, newlines, spaces
3059 // some effectless debug code to see the values in the debugger
3060 bool bound = cur.boundary();
3061 int rowpos = cur.textRow().pos();
3062 int pos = cur.pos();
3063 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
3064 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
3065 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
3067 if (!cur.boundary() &&
3068 cur.textRow().pos() == cur.pos() &&
3069 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
3070 !cur.paragraph().isNewline(cur.pos() - 1) &&
3071 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
3072 !cur.paragraph().isSeparator(cur.pos() - 1)) {
3073 return setCursor(cur, cur.pit(), cur.pos(), true, true);
3076 // go left and try to enter inset
3077 if (checkAndActivateInset(cur, false))
3080 // normal character left
3081 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
3084 // move to the previous paragraph or do nothing
3085 if (cur.pit() > 0) {
3086 Paragraph & par = getPar(cur.pit() - 1);
3087 pos_type lastpos = par.size();
3088 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
3089 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
3091 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
3097 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
3099 Cursor temp_cur = cur;
3100 temp_cur.posVisLeft(skip_inset);
3101 if (temp_cur.depth() > cur.depth()) {
3105 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3106 true, temp_cur.boundary());
3110 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
3112 Cursor temp_cur = cur;
3113 temp_cur.posVisRight(skip_inset);
3114 if (temp_cur.depth() > cur.depth()) {
3118 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3119 true, temp_cur.boundary());
3123 bool Text::cursorForward(Cursor & cur)
3125 // Tell BufferView to test for FitCursor in any case!
3126 cur.screenUpdateFlags(Update::FitCursor);
3128 // not at paragraph end?
3129 if (cur.pos() != cur.lastpos()) {
3130 // in front of editable inset, i.e. jump into it?
3131 if (checkAndActivateInset(cur, true))
3134 TextMetrics const & tm = cur.bv().textMetrics(this);
3135 // if left of boundary -> just jump to right side
3136 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
3137 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
3138 return setCursor(cur, cur.pit(), cur.pos(), true, false);
3140 // next position is left of boundary,
3141 // but go to next line for special cases like space, newline, linesep
3143 // some effectless debug code to see the values in the debugger
3144 int endpos = cur.textRow().endpos();
3145 int lastpos = cur.lastpos();
3146 int pos = cur.pos();
3147 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
3148 bool newline = cur.paragraph().isNewline(cur.pos());
3149 bool sep = cur.paragraph().isSeparator(cur.pos());
3150 if (cur.pos() != cur.lastpos()) {
3151 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
3152 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
3153 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
3156 if (cur.textRow().endpos() == cur.pos() + 1) {
3157 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
3158 cur.pos() + 1 == cur.lastpos() &&
3159 cur.pit() != cur.lastpit()) {
3160 // move to next paragraph
3161 return setCursor(cur, cur.pit() + 1, 0, true, false);
3162 } else if (cur.textRow().endpos() != cur.lastpos() &&
3163 !cur.paragraph().isNewline(cur.pos()) &&
3164 !cur.paragraph().isEnvSeparator(cur.pos()) &&
3165 !cur.paragraph().isLineSeparator(cur.pos()) &&
3166 !cur.paragraph().isSeparator(cur.pos())) {
3167 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3171 // in front of RTL boundary? Stay on this side of the boundary because:
3172 // ab|cDDEEFFghi -> abc|DDEEFFghi
3173 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
3174 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3177 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
3180 // move to next paragraph
3181 if (cur.pit() != cur.lastpit())
3182 return setCursor(cur, cur.pit() + 1, 0, true, false);
3187 bool Text::cursorUpParagraph(Cursor & cur)
3189 bool updated = false;
3191 updated = setCursor(cur, cur.pit(), 0);
3192 else if (cur.pit() != 0)
3193 updated = setCursor(cur, cur.pit() - 1, 0);
3198 bool Text::cursorDownParagraph(Cursor & cur)
3200 bool updated = false;
3201 if (cur.pit() != cur.lastpit())
3202 if (lyxrc.mac_like_cursor_movement)
3203 if (cur.pos() == cur.lastpos())
3204 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
3206 updated = setCursor(cur, cur.pit(), cur.lastpos());
3208 updated = setCursor(cur, cur.pit() + 1, 0);
3210 updated = setCursor(cur, cur.pit(), cur.lastpos());
3216 /** delete num_spaces characters between from and to. Return the
3217 * number of spaces that got physically deleted (not marked as
3219 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
3220 int num_spaces, bool const trackChanges)
3222 if (num_spaces <= 0)
3225 // First, delete spaces marked as inserted
3227 while (pos < to && num_spaces > 0) {
3228 Change const & change = par.lookupChange(pos);
3229 if (change.inserted() && !change.currentAuthor()) {
3230 par.eraseChar(pos, trackChanges);
3237 // Then remove remaining spaces
3238 int const psize = par.size();
3239 par.eraseChars(from, from + num_spaces, trackChanges);
3240 return psize - par.size();
3246 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
3247 Cursor & old, bool & need_anchor_change)
3249 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
3251 Paragraph & oldpar = old.paragraph();
3252 bool const trackChanges = cur.buffer()->params().track_changes;
3253 bool result = false;
3255 // We do nothing if cursor did not move
3256 if (cur.top() == old.top())
3259 // We do not do anything on read-only documents
3260 if (cur.buffer()->isReadonly())
3263 // Whether a common inset is found and whether the cursor is still in
3264 // the same paragraph (possibly nested).
3265 int const depth = cur.find(&old.inset());
3266 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
3267 && old.pit() == cur[depth].pit();
3270 * (1) If the chars around the old cursor were spaces and the
3271 * paragraph is not in free spacing mode, delete some of them, but
3272 * only if the cursor has really moved.
3275 /* There are still some small problems that can lead to
3276 double spaces stored in the document file or space at
3277 the beginning of paragraphs(). This happens if you have
3278 the cursor between two spaces and then save. Or if you
3279 cut and paste and the selection has a space at the
3280 beginning and then save right after the paste. (Lgb)
3282 if (!oldpar.isFreeSpacing()) {
3283 // find range of spaces around cursors
3284 pos_type from = old.pos();
3286 && oldpar.isLineSeparator(from - 1)
3287 && !oldpar.isDeleted(from - 1))
3289 pos_type to = old.pos();
3290 while (to < old.lastpos()
3291 && oldpar.isLineSeparator(to)
3292 && !oldpar.isDeleted(to))
3295 int num_spaces = to - from;
3296 // If we are not at the start of the paragraph, keep one space
3297 if (from != to && from > 0)
3300 // If cursor is inside range, keep one additional space
3301 if (same_par && cur.pos() > from && cur.pos() < to)
3304 // Remove spaces and adapt cursor.
3305 if (num_spaces > 0) {
3308 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
3309 // correct cur position
3310 // FIXME: there can be other cursors pointing there, we should update them
3312 if (cur[depth].pos() >= to)
3313 cur[depth].pos() -= deleted;
3314 else if (cur[depth].pos() > from)
3315 cur[depth].pos() = min(from + 1, old.lastpos());
3316 need_anchor_change = true;
3323 * (2) If the paragraph where the cursor was is empty, delete it
3326 // only do our other magic if we changed paragraph
3330 // only do our magic if the paragraph is empty
3331 if (!oldpar.empty())
3334 // don't delete anything if this is the ONLY paragraph!
3335 if (old.lastpit() == 0)
3338 // Do not delete empty paragraphs with keepempty set.
3339 if (oldpar.allowEmpty())
3343 old.recordUndo(max(old.pit() - 1, pit_type(0)),
3344 min(old.pit() + 1, old.lastpit()));
3345 ParagraphList & plist = old.text()->paragraphs();
3346 bool const soa = oldpar.params().startOfAppendix();
3347 plist.erase(plist.iterator_at(old.pit()));
3348 // do not lose start of appendix marker (bug 4212)
3349 if (soa && old.pit() < pit_type(plist.size()))
3350 plist[old.pit()].params().startOfAppendix(true);
3352 // see #warning (FIXME?) above
3353 if (cur.depth() >= old.depth()) {
3354 CursorSlice & curslice = cur[old.depth() - 1];
3355 if (&curslice.inset() == &old.inset()
3356 && curslice.idx() == old.idx()
3357 && curslice.pit() > old.pit()) {
3359 // since a paragraph has been deleted, all the
3360 // insets after `old' have been copied and
3361 // their address has changed. Therefore we
3362 // need to `regenerate' cur. (JMarc)
3363 cur.updateInsets(&(cur.bottom().inset()));
3364 need_anchor_change = true;
3372 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
3374 pos_type last_pos = pars_[last].size() - 1;
3375 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
3379 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
3380 pos_type first_pos, pos_type last_pos,
3383 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
3385 for (pit_type pit = first; pit <= last; ++pit) {
3386 Paragraph & par = pars_[pit];
3389 * (1) Delete consecutive spaces
3391 if (!par.isFreeSpacing()) {
3392 pos_type from = (pit == first) ? first_pos : 0;
3393 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
3394 while (from < to_pos) {
3396 while (from < par.size()
3397 && (!par.isLineSeparator(from) || par.isDeleted(from)))
3399 // find string of spaces
3401 while (to < par.size()
3402 && par.isLineSeparator(to) && !par.isDeleted(to))
3404 // empty? We are done
3408 int num_spaces = to - from;
3410 // If we are not at the extremity of the paragraph, keep one space
3411 if (from != to && from > 0 && to < par.size())
3414 // Remove spaces if needed
3415 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
3416 from = to - deleted;
3421 * (2) Delete empty pragraphs
3424 // don't delete anything if this is the only remaining paragraph
3425 // within the given range. Note: Text::acceptOrRejectChanges()
3426 // sets the cursor to 'first' after calling DEPM
3430 // don't delete empty paragraphs with keepempty set
3431 if (par.allowEmpty())
3434 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
3435 pars_.erase(pars_.iterator_at(pit));
3447 typedef limited_stack<pair<docstring, Font>> FontStack;
3448 static FontStack freeFonts(15);
3449 static bool toggleall = false;
3451 void toggleAndShow(Cursor & cur, Text * text,
3452 Font const & font, bool togall = true)
3454 text->toggleFree(cur, font, togall);
3456 if (font.language() != ignore_language ||
3457 font.fontInfo().number() != FONT_IGNORE) {
3458 TextMetrics const & tm = cur.bv().textMetrics(text);
3459 if (cur.boundary() != tm.isRTLBoundary(cur.pit(), cur.pos(),
3460 cur.real_current_font))
3461 text->setCursor(cur, cur.pit(), cur.pos(),
3462 false, !cur.boundary());
3463 if (font.language() != ignore_language)
3464 // We need a buffer update if we change the language
3465 // (e.g., with info insets or if the selection contains
3467 cur.forceBufferUpdate();
3472 void moveCursor(Cursor & cur, bool selecting)
3474 if (selecting || cur.mark())
3479 void finishChange(Cursor & cur, bool selecting)
3482 moveCursor(cur, selecting);
3486 void mathDispatch(Cursor & cur, FuncRequest const & cmd)
3489 docstring sel = cur.selectionAsString(false);
3491 // It may happen that sel is empty but there is a selection
3492 replaceSelection(cur);
3494 // Is this a valid formula?
3498 #ifdef ENABLE_ASSERTIONS
3499 const int old_pos = cur.pos();
3501 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
3502 #ifdef ENABLE_ASSERTIONS
3503 LATTEST(old_pos == cur.pos());
3505 cur.nextInset()->edit(cur, true);
3506 if (cmd.action() != LFUN_MATH_MODE)
3507 // LFUN_MATH_MODE has a different meaning in math mode
3510 InsetMathHull * formula = new InsetMathHull(cur.buffer());
3511 string const selstr = to_utf8(sel);
3512 istringstream is(selstr);
3515 if (!formula->readQuiet(lex)) {
3516 // No valid formula, let's try with delims
3517 is.str("$" + selstr + "$");
3519 if (!formula->readQuiet(lex)) {
3520 // Still not valid, leave it as is
3527 cur.insert(formula);
3528 cur.nextInset()->edit(cur, true);
3529 LASSERT(cur.inMathed(), return);
3532 cur.selection(true);
3533 cur.pos() = cur.lastpos();
3534 if (cmd.action() != LFUN_MATH_MODE)
3535 // LFUN_MATH_MODE has a different meaning in math mode
3537 cur.clearSelection();
3538 cur.pos() = cur.lastpos();
3542 cur.message(from_utf8(N_("Math editor mode")));
3544 cur.message(from_utf8(N_("No valid math formula")));
3548 void regexpDispatch(Cursor & cur, FuncRequest const & cmd)
3550 LASSERT(cmd.action() == LFUN_REGEXP_MODE, return);
3551 if (cur.inRegexped()) {
3552 cur.message(_("Already in regular expression mode"));
3556 docstring sel = cur.selectionAsString(false);
3558 // It may happen that sel is empty but there is a selection
3559 replaceSelection(cur);
3561 cur.insert(new InsetMathHull(cur.buffer(), hullRegexp));
3562 cur.nextInset()->edit(cur, true);
3563 cur.niceInsert(sel);
3565 cur.message(_("Regexp editor mode"));
3569 void specialChar(Cursor & cur, InsetSpecialChar::Kind kind)
3572 cap::replaceSelection(cur);
3573 cur.insert(new InsetSpecialChar(kind));
3578 void ipaChar(Cursor & cur, InsetIPAChar::Kind kind)
3581 cap::replaceSelection(cur);
3582 cur.insert(new InsetIPAChar(kind));
3587 bool doInsertInset(Cursor & cur, Text * text,
3588 FuncRequest const & cmd, bool edit,
3589 bool pastesel, bool resetfont = false)
3591 Buffer & buffer = cur.bv().buffer();
3592 BufferParams const & bparams = buffer.params();
3593 Inset * inset = createInset(&buffer, cmd);
3597 if (InsetCollapsible * ci = inset->asInsetCollapsible())
3598 ci->setButtonLabel();
3601 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3602 bool cotextinsert = false;
3603 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3604 Layout const & lay = cur.paragraph().layout();
3605 Layout::LaTeXArgMap args = lay.args();
3606 Layout::LaTeXArgMap::const_iterator const lait = args.find(ia->name());
3607 if (lait != args.end())
3608 cotextinsert = (*lait).second.insertcotext;
3610 InsetLayout const & il = cur.inset().getLayout();
3612 Layout::LaTeXArgMap::const_iterator const ilait = args.find(ia->name());
3613 if (ilait != args.end())
3614 cotextinsert = (*ilait).second.insertcotext;
3616 // The argument requests to insert a copy of the co-text to the inset
3619 // If we have a selection within a paragraph, use this
3620 if (cur.selection() && cur.selBegin().pit() == cur.selEnd().pit())
3621 ds = cur.selectionAsString(false);
3622 // else use the whole paragraph
3624 ds = cur.paragraph().asString();
3625 text->insertInset(cur, inset);
3626 ia->init(cur.paragraph());
3628 inset->edit(cur, true);
3629 // Now put co-text into inset
3630 Font const f(inherit_font, cur.current_font.language());
3632 cur.text()->insertStringAsLines(cur, ds, f);
3633 cur.leaveInset(*inset);
3639 bool gotsel = false;
3640 bool move_layout = false;
3641 if (cur.selection()) {
3642 if (cmd.action() == LFUN_INDEX_INSERT)
3643 copySelectionToTemp(cur);
3645 cutSelectionToTemp(cur, pastesel);
3646 /* Move layout information inside the inset if the whole
3647 * paragraph and the inset allows setting layout
3648 * FIXME: figure out a good test in the environment case (see #12251).
3650 if (cur.paragraph().layout().isCommand()
3651 && (cur.paragraph().empty()
3652 || cur.paragraph().isDeleted(0, cur.paragraph().size()))
3653 && !inset->forcePlainLayout()) {
3654 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3658 cur.clearSelection();
3660 } else if (cmd.action() == LFUN_INDEX_INSERT) {
3661 gotsel = text->selectWordWhenUnderCursor(cur, WHOLE_WORD);
3662 copySelectionToTemp(cur);
3663 cur.clearSelection();
3665 text->insertInset(cur, inset);
3667 InsetText * inset_text = inset->asInsetText();
3669 Font const & font = inset->inheritFont()
3670 ? cur.bv().textMetrics(text).displayFont(cur.pit(), cur.pos())
3671 : bparams.getFont();
3672 inset_text->setOuterFont(cur.bv(), font.fontInfo());
3675 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3676 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3677 ia->init(cur.paragraph());
3681 inset->edit(cur, true);
3683 if (!gotsel || !pastesel)
3686 pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
3687 cur.buffer()->errors("Paste");
3688 cur.clearSelection(); // bug 393
3692 // Reset of font (not language) is requested.
3693 // Used by InsetIndex (#11961).
3694 Language const * lang = cur.getFont().language();
3695 Font font(bparams.getFont().fontInfo(), lang);
3696 cur.paragraph().resetFonts(font);
3698 inset_text->fixParagraphsFont();
3701 /* If the containing paragraph has kept its layout, reset the
3702 * layout of the first paragraph of the inset.
3705 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3706 // FIXME: what does this do?
3707 if (cmd.action() == LFUN_FLEX_INSERT)
3710 cur.leaveInset(*inset);
3711 if (cmd.action() == LFUN_PREVIEW_INSERT
3712 || cmd.action() == LFUN_IPA_INSERT)
3714 notifyCursorLeavesOrEnters(old, cur);
3716 cur.leaveInset(*inset);
3717 // reset surrounding par to default
3718 DocumentClass const & dc = bparams.documentClass();
3719 docstring const layoutname = inset->usePlainLayout()
3720 ? dc.plainLayoutName()
3721 : dc.defaultLayoutName();
3722 text->setLayout(cur, layoutname);
3728 /// the type of outline operation
3730 OutlineUp, // Move this header with text down
3731 OutlineDown, // Move this header with text up
3732 OutlineIn, // Make this header deeper
3733 OutlineOut // Make this header shallower
3737 void insertSeparator(Cursor const & cur, depth_type const depth)
3739 Buffer & buf = *cur.buffer();
3740 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
3741 DocumentClass const & tc = buf.params().documentClass();
3742 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
3743 + from_ascii("\" ignoreautonests")));
3744 // FIXME: Bibitem mess!
3745 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
3746 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
3747 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
3748 while (cur.paragraph().params().depth() > depth)
3749 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
3753 void outline(OutlineOp mode, Cursor & cur, bool local)
3755 Buffer & buf = *cur.buffer();
3756 Text & text = *cur.text();
3757 pit_type & pit = cur.pit();
3758 ParagraphList & pars = text.paragraphs();
3759 ParagraphList::iterator const bgn = pars.begin();
3760 // The first paragraph of the area to be copied:
3761 ParagraphList::iterator start = pars.iterator_at(pit);
3762 // The final paragraph of area to be copied:
3763 ParagraphList::iterator finish = start;
3764 ParagraphList::iterator const end = pars.end();
3765 depth_type const current_depth = cur.paragraph().params().depth();
3767 int const thistoclevel = text.getTocLevel(distance(bgn, start));
3770 // Move out (down) from this section header
3774 if (!local || (mode != OutlineIn && mode != OutlineOut)) {
3775 // Seek the one (on same level) below
3776 for (; finish != end; ++finish) {
3777 toclevel = text.getTocLevel(distance(bgn, finish));
3778 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3785 if (start == pars.begin())
3788 ParagraphList::iterator dest = start;
3789 // Move out (up) from this header
3792 // Search previous same-level header above
3795 toclevel = text.getTocLevel(distance(bgn, dest));
3797 && (toclevel == Layout::NOT_IN_TOC
3798 || toclevel > thistoclevel));
3799 // Not found; do nothing
3800 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3802 pit_type newpit = distance(bgn, dest);
3803 pit_type const len = distance(start, finish);
3804 pit_type const deletepit = pit + len;
3805 buf.undo().recordUndo(cur, newpit, deletepit - 1);
3806 // If we move an environment upwards, make sure it is
3807 // separated from its new neighbour below:
3808 // If an environment of the same layout follows, and the moved
3809 // paragraph sequence does not end with a separator, insert one.
3810 ParagraphList::iterator lastmoved = finish;
3812 if (start->layout().isEnvironment()
3813 && dest->layout() == start->layout()
3814 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3815 cur.pit() = distance(bgn, lastmoved);
3816 cur.pos() = cur.lastpos();
3817 insertSeparator(cur, current_depth);
3820 // Likewise, if we moved an environment upwards, make sure it
3821 // is separated from its new neighbour above.
3822 // The paragraph before the target of movement
3824 ParagraphList::iterator before = dest;
3826 // Get the parent paragraph (outer in nested context)
3827 pit_type const parent =
3828 before->params().depth() > current_depth
3829 ? text.depthHook(distance(bgn, before), current_depth)
3830 : distance(bgn, before);
3831 // If a environment with same layout preceeds the moved one in the new
3832 // position, and there is no separator yet, insert one.
3833 if (start->layout().isEnvironment()
3834 && pars[parent].layout() == start->layout()
3835 && !before->isEnvSeparator(before->beginOfBody())) {
3836 cur.pit() = distance(bgn, before);
3837 cur.pos() = cur.lastpos();
3838 insertSeparator(cur, current_depth);
3842 newpit = distance(bgn, dest);
3843 pars.splice(dest, start, finish);
3851 // Go one down from *this* header:
3852 ParagraphList::iterator dest = next(finish, 1);
3853 // Go further down to find header to insert in front of:
3854 for (; dest != end; ++dest) {
3855 toclevel = text.getTocLevel(distance(bgn, dest));
3856 if (toclevel != Layout::NOT_IN_TOC
3857 && toclevel <= thistoclevel)
3860 // One such was found, so go on...
3861 // If we move an environment downwards, make sure it is
3862 // separated from its new neighbour above.
3863 pit_type newpit = distance(bgn, dest);
3864 buf.undo().recordUndo(cur, pit, newpit - 1);
3865 // The paragraph before the target of movement
3866 ParagraphList::iterator before = dest;
3868 // Get the parent paragraph (outer in nested context)
3869 pit_type const parent =
3870 before->params().depth() > current_depth
3871 ? text.depthHook(distance(bgn, before), current_depth)
3872 : distance(bgn, before);
3873 // If a environment with same layout preceeds the moved one in the new
3874 // position, and there is no separator yet, insert one.
3875 if (start->layout().isEnvironment()
3876 && pars[parent].layout() == start->layout()
3877 && !before->isEnvSeparator(before->beginOfBody())) {
3878 cur.pit() = distance(bgn, before);
3879 cur.pos() = cur.lastpos();
3880 insertSeparator(cur, current_depth);
3883 // Likewise, make sure moved environments are separated
3884 // from their new neighbour below:
3885 // If an environment of the same layout follows, and the moved
3886 // paragraph sequence does not end with a separator, insert one.
3887 ParagraphList::iterator lastmoved = finish;
3890 && start->layout().isEnvironment()
3891 && dest->layout() == start->layout()
3892 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3893 cur.pit() = distance(bgn, lastmoved);
3894 cur.pos() = cur.lastpos();
3895 insertSeparator(cur, current_depth);
3898 newpit = distance(bgn, dest);
3899 pit_type const len = distance(start, finish);
3900 pars.splice(dest, start, finish);
3901 cur.pit() = newpit - len;
3906 // We first iterate without actually doing something
3907 // in order to check whether the action flattens the structure.
3908 // If so, warn (#11178).
3909 ParagraphList::iterator cstart = start;
3910 bool strucchange = false;
3911 for (; cstart != finish; ++cstart) {
3912 toclevel = text.getTocLevel(distance(bgn, cstart));
3913 if (toclevel == Layout::NOT_IN_TOC)
3916 DocumentClass const & tc = buf.params().documentClass();
3917 int newtoclevel = -1;
3918 if (mode == OutlineIn) {
3919 if (toclevel == -1 && tc.getTOCLayout().toclevel > 0)
3920 // we are at part but don't have a chapter
3921 newtoclevel = tc.getTOCLayout().toclevel;
3923 newtoclevel = toclevel + 1;
3925 if (tc.getTOCLayout().toclevel == toclevel && tc.min_toclevel() < toclevel)
3926 // we are at highest level, but there is still part
3927 newtoclevel = tc.min_toclevel();
3929 newtoclevel = toclevel - 1;
3933 for (auto const & lay : tc) {
3934 if (lay.toclevel == newtoclevel
3935 && lay.isNumHeadingLabelType()
3936 && cstart->layout().isNumHeadingLabelType()) {
3947 && frontend::Alert::prompt(_("Action flattens document structure"),
3948 _("This action will cause some headings that have been "
3949 "on different level before to be on the same level "
3950 "since there is no more lower or higher heading level. "
3953 _("&Yes, continue nonetheless"),
3954 _("&No, quit operation")) == 1)
3957 pit_type const len = distance(start, finish);
3958 buf.undo().recordUndo(cur, pit, pit + len - 1);
3959 for (; start != finish; ++start) {
3960 toclevel = text.getTocLevel(distance(bgn, start));
3961 if (toclevel == Layout::NOT_IN_TOC)
3964 DocumentClass const & tc = buf.params().documentClass();
3965 int newtoclevel = -1;
3966 if (mode == OutlineIn) {
3967 if (toclevel == -1 && tc.getTOCLayout().toclevel > 0)
3968 // we are at part but don't have a chapter
3969 newtoclevel = tc.getTOCLayout().toclevel;
3971 newtoclevel = toclevel + 1;
3973 if (tc.getTOCLayout().toclevel == toclevel && tc.min_toclevel() < toclevel)
3974 // we are at highest level, but there is still part
3975 newtoclevel = tc.min_toclevel();
3977 newtoclevel = toclevel - 1;
3980 for (auto const & lay : tc) {
3981 if (lay.toclevel == newtoclevel
3982 && lay.isNumHeadingLabelType()
3983 && start->layout().isNumHeadingLabelType()) {
3984 start->setLayout(lay);
3998 void Text::number(Cursor & cur)
4000 FontInfo font = ignore_font;
4001 font.setNumber(FONT_TOGGLE);
4002 toggleAndShow(cur, this, Font(font, ignore_language));
4006 bool Text::isRTL(pit_type const pit) const
4008 Buffer const & buffer = owner_->buffer();
4009 return pars_[pit].isRTL(buffer.params());
4015 Language const * getLanguage(Cursor const & cur, string const & lang)
4017 return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
4021 docstring resolveLayout(docstring layout, DocIterator const & dit)
4023 Paragraph const & par = dit.paragraph();
4024 DocumentClass const & tclass = dit.buffer()->params().documentClass();
4027 layout = tclass.defaultLayoutName();
4029 if (dit.inset().forcePlainLayout(dit.idx()))
4030 // in this case only the empty layout is allowed
4031 layout = tclass.plainLayoutName();
4032 else if (par.usePlainLayout()) {
4033 // in this case, default layout maps to empty layout
4034 if (layout == tclass.defaultLayoutName())
4035 layout = tclass.plainLayoutName();
4037 // otherwise, the empty layout maps to the default
4038 if (layout == tclass.plainLayoutName())
4039 layout = tclass.defaultLayoutName();
4042 // If the entry is obsolete, use the new one instead.
4043 if (tclass.hasLayout(layout)) {
4044 docstring const & obs = tclass[layout].obsoleted_by();
4048 if (!tclass.hasLayout(layout))
4054 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4056 ParagraphList const & pars = cur.text()->paragraphs();
4058 pit_type pit = cur.selBegin().pit();
4059 pit_type const epit = cur.selEnd().pit() + 1;
4060 for ( ; pit != epit; ++pit)
4061 if (pars[pit].layout().name() != layout)
4071 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4073 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4075 // Dispatch if the cursor is inside the text. It is not the
4076 // case for context menus (bug 5797).
4077 if (cur.text() != this) {
4082 BufferView * bv = &cur.bv();
4083 TextMetrics * tm = &bv->textMetrics(this);
4084 if (!tm->contains(cur.pit())) {
4085 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4086 tm = &bv->textMetrics(this);
4089 // FIXME: We use the update flag to indicates wether a singlePar or a
4090 // full screen update is needed. We reset it here but shall we restore it
4092 cur.noScreenUpdate();
4094 LBUFERR(this == cur.text());
4096 // NOTE: This should NOT be a reference. See commit 94a5481a.
4097 CursorSlice const oldTopSlice = cur.top();
4098 bool const oldBoundary = cur.boundary();
4099 bool const oldSelection = cur.selection();
4100 // Signals that, even if needsUpdate == false, an update of the
4101 // cursor paragraph is required
4102 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4103 LyXAction::SingleParUpdate);
4104 // Signals that a full-screen update is required
4105 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4106 LyXAction::NoUpdate) || singleParUpdate);
4107 bool const last_misspelled = lyxrc.spellcheck_continuously
4108 && cur.paragraph().isMisspelled(cur.pos(), true);
4110 FuncCode const act = cmd.action();
4113 case LFUN_PARAGRAPH_MOVE_DOWN: {
4114 pit_type const pit = cur.pit();
4115 cur.recordUndo(pit, pit + 1);
4116 pars_.swap(pit, pit + 1);
4118 cur.forceBufferUpdate();
4123 case LFUN_PARAGRAPH_MOVE_UP: {
4124 pit_type const pit = cur.pit();
4125 cur.recordUndo(pit - 1, pit);
4127 pars_.swap(pit, pit - 1);
4130 cur.forceBufferUpdate();
4134 case LFUN_APPENDIX: {
4135 Paragraph & par = cur.paragraph();
4136 bool start = !par.params().startOfAppendix();
4138 // FIXME: The code below only makes sense at top level.
4139 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4140 // ensure that we have only one start_of_appendix in this document
4141 // FIXME: this don't work for multipart document!
4142 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4143 if (pars_[tmp].params().startOfAppendix()) {
4144 cur.recordUndo(tmp, tmp);
4145 pars_[tmp].params().startOfAppendix(false);
4151 par.params().startOfAppendix(start);
4153 // we can set the refreshing parameters now
4154 cur.forceBufferUpdate();
4158 case LFUN_WORD_DELETE_FORWARD:
4159 if (cur.selection())
4160 cutSelection(cur, false);
4162 deleteWordForward(cur, cmd.getArg(0) != "confirm");
4163 finishChange(cur, false);
4166 case LFUN_WORD_DELETE_BACKWARD:
4167 if (cur.selection())
4168 cutSelection(cur, false);
4170 deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4171 finishChange(cur, false);
4174 case LFUN_LINE_DELETE_FORWARD:
4175 if (cur.selection())
4176 cutSelection(cur, false);
4178 tm->deleteLineForward(cur);
4179 finishChange(cur, false);
4182 case LFUN_BUFFER_BEGIN:
4183 case LFUN_BUFFER_BEGIN_SELECT:
4184 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4185 if (cur.depth() == 1)
4186 needsUpdate |= cursorTop(cur);
4189 cur.screenUpdateFlags(Update::FitCursor);
4192 case LFUN_BUFFER_END:
4193 case LFUN_BUFFER_END_SELECT:
4194 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4195 if (cur.depth() == 1)
4196 needsUpdate |= cursorBottom(cur);
4199 cur.screenUpdateFlags(Update::FitCursor);
4202 case LFUN_INSET_BEGIN:
4203 case LFUN_INSET_BEGIN_SELECT:
4204 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4205 if (cur.depth() == 1 || !cur.top().at_begin())
4206 needsUpdate |= cursorTop(cur);
4209 cur.screenUpdateFlags(Update::FitCursor);
4212 case LFUN_INSET_END:
4213 case LFUN_INSET_END_SELECT:
4214 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4215 if (cur.depth() == 1 || !cur.top().at_end())
4216 needsUpdate |= cursorBottom(cur);
4219 cur.screenUpdateFlags(Update::FitCursor);
4222 case LFUN_CHAR_FORWARD:
4223 case LFUN_CHAR_FORWARD_SELECT: {
4224 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4225 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4226 bool const cur_moved = cursorForward(cur);
4227 needsUpdate |= cur_moved;
4229 if (!cur_moved && cur.depth() > 1
4230 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4232 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4234 // we will be moving out the inset, so we should execute
4235 // the depm-mechanism.
4236 // The cursor hasn't changed yet. To give the DEPM the
4237 // possibility of doing something we must provide it with
4238 // two different cursors.
4240 dummy.pos() = dummy.pit() = 0;
4241 if (cur.bv().checkDepm(dummy, cur))
4242 cur.forceBufferUpdate();
4247 case LFUN_CHAR_BACKWARD:
4248 case LFUN_CHAR_BACKWARD_SELECT: {
4249 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4250 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4251 bool const cur_moved = cursorBackward(cur);
4252 needsUpdate |= cur_moved;
4254 if (!cur_moved && cur.depth() > 1
4255 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4257 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4259 // we will be moving out the inset, so we should execute
4260 // the depm-mechanism.
4261 // The cursor hasn't changed yet. To give the DEPM the
4262 // possibility of doing something we must provide it with
4263 // two different cursors.
4265 dummy.pos() = cur.lastpos();
4266 dummy.pit() = cur.lastpit();
4267 if (cur.bv().checkDepm(dummy, cur))
4268 cur.forceBufferUpdate();
4273 case LFUN_CHAR_LEFT:
4274 case LFUN_CHAR_LEFT_SELECT:
4275 if (lyxrc.visual_cursor) {
4276 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4277 bool const cur_moved = cursorVisLeft(cur);
4278 needsUpdate |= cur_moved;
4279 if (!cur_moved && cur.depth() > 1
4280 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4282 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4285 if (cur.reverseDirectionNeeded()) {
4286 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4287 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4289 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4290 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4297 case LFUN_CHAR_RIGHT:
4298 case LFUN_CHAR_RIGHT_SELECT:
4299 if (lyxrc.visual_cursor) {
4300 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4301 bool const cur_moved = cursorVisRight(cur);
4302 needsUpdate |= cur_moved;
4303 if (!cur_moved && cur.depth() > 1
4304 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4306 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4309 if (cur.reverseDirectionNeeded()) {
4310 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4311 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4313 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4314 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4322 case LFUN_UP_SELECT:
4323 case LFUN_DOWN_SELECT:
4326 // stop/start the selection
4327 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4328 cmd.action() == LFUN_UP_SELECT;
4330 // move cursor up/down
4331 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4332 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4334 if (!atFirstOrLastRow) {
4335 needsUpdate |= cur.selHandle(select);
4336 cur.upDownInText(up, needsUpdate);
4337 needsUpdate |= cur.beforeDispatchCursor().inMathed();
4339 pos_type newpos = up ? 0 : cur.lastpos();
4340 if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4341 needsUpdate |= cur.selHandle(select);
4342 // we do not reset the targetx of the cursor
4344 needsUpdate |= bv->checkDepm(cur, bv->cursor());
4345 cur.updateTextTargetOffset();
4347 cur.forceBufferUpdate();
4351 // if the cursor cannot be moved up or down do not remove
4352 // the selection right now, but wait for the next dispatch.
4354 needsUpdate |= cur.selHandle(select);
4355 cur.upDownInText(up, needsUpdate);
4362 case LFUN_PARAGRAPH_SELECT:
4364 needsUpdate |= setCursor(cur, cur.pit(), 0);
4365 needsUpdate |= cur.selHandle(true);
4366 if (cur.pos() < cur.lastpos())
4367 needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4370 case LFUN_PARAGRAPH_UP:
4371 case LFUN_PARAGRAPH_UP_SELECT:
4372 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4373 needsUpdate |= cursorUpParagraph(cur);
4376 case LFUN_PARAGRAPH_DOWN:
4377 case LFUN_PARAGRAPH_DOWN_SELECT:
4378 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4379 needsUpdate |= cursorDownParagraph(cur);
4382 case LFUN_LINE_BEGIN:
4383 case LFUN_LINE_BEGIN_SELECT:
4384 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4385 needsUpdate |= tm->cursorHome(cur);
4389 case LFUN_LINE_END_SELECT:
4390 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4391 needsUpdate |= tm->cursorEnd(cur);
4394 case LFUN_SECTION_SELECT: {
4395 Buffer const & buf = *cur.buffer();
4396 pit_type const pit = cur.pit();
4397 ParagraphList & pars = buf.text().paragraphs();
4398 ParagraphList::iterator bgn = pars.begin();
4399 // The first paragraph of the area to be selected:
4400 ParagraphList::iterator start = pars.iterator_at(pit);
4401 // The final paragraph of area to be selected:
4402 ParagraphList::iterator finish = start;
4403 ParagraphList::iterator end = pars.end();
4405 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4406 if (thistoclevel == Layout::NOT_IN_TOC)
4410 Cursor const old_cur = cur;
4411 needsUpdate |= cur.selHandle(true);
4413 // Move out (down) from this section header
4417 // Seek the one (on same level) below
4418 for (; finish != end; ++finish, ++cur.pit()) {
4419 int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4420 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4423 cur.pos() = cur.lastpos();
4424 cur.boundary(false);
4425 cur.setCurrentFont();
4427 needsUpdate |= cur != old_cur;
4431 case LFUN_WORD_RIGHT:
4432 case LFUN_WORD_RIGHT_SELECT:
4433 if (lyxrc.visual_cursor) {
4434 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4435 bool const cur_moved = cursorVisRightOneWord(cur);
4436 needsUpdate |= cur_moved;
4437 if (!cur_moved && cur.depth() > 1
4438 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4440 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4443 if (cur.reverseDirectionNeeded()) {
4444 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4445 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4447 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4448 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4455 case LFUN_WORD_FORWARD:
4456 case LFUN_WORD_FORWARD_SELECT: {
4457 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4458 bool const cur_moved = cursorForwardOneWord(cur);
4459 needsUpdate |= cur_moved;
4461 if (!cur_moved && cur.depth() > 1
4462 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4464 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4466 // we will be moving out the inset, so we should execute
4467 // the depm-mechanism.
4468 // The cursor hasn't changed yet. To give the DEPM the
4469 // possibility of doing something we must provide it with
4470 // two different cursors.
4472 dummy.pos() = dummy.pit() = 0;
4473 if (cur.bv().checkDepm(dummy, cur))
4474 cur.forceBufferUpdate();
4479 case LFUN_WORD_LEFT:
4480 case LFUN_WORD_LEFT_SELECT:
4481 if (lyxrc.visual_cursor) {
4482 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4483 bool const cur_moved = cursorVisLeftOneWord(cur);
4484 needsUpdate |= cur_moved;
4485 if (!cur_moved && cur.depth() > 1
4486 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4488 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4491 if (cur.reverseDirectionNeeded()) {
4492 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4493 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4495 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4496 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4503 case LFUN_WORD_BACKWARD:
4504 case LFUN_WORD_BACKWARD_SELECT: {
4505 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4506 bool const cur_moved = cursorBackwardOneWord(cur);
4507 needsUpdate |= cur_moved;
4509 if (!cur_moved && cur.depth() > 1
4510 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4512 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4514 // we will be moving out the inset, so we should execute
4515 // the depm-mechanism.
4516 // The cursor hasn't changed yet. To give the DEPM the
4517 // possibility of doing something we must provide it with
4518 // two different cursors.
4520 dummy.pos() = cur.lastpos();
4521 dummy.pit() = cur.lastpit();
4522 if (cur.bv().checkDepm(dummy, cur))
4523 cur.forceBufferUpdate();
4528 case LFUN_WORD_SELECT: {
4529 selectWord(cur, WHOLE_WORD);
4530 finishChange(cur, true);
4534 case LFUN_NEWLINE_INSERT: {
4535 InsetNewlineParams inp;
4536 docstring const & arg = cmd.argument();
4537 if (arg == "linebreak")
4538 inp.kind = InsetNewlineParams::LINEBREAK;
4540 inp.kind = InsetNewlineParams::NEWLINE;
4541 cap::replaceSelection(cur);
4543 cur.insert(new InsetNewline(inp));
4545 moveCursor(cur, false);
4549 case LFUN_TAB_INSERT: {
4550 bool const multi_par_selection = cur.selection() &&
4551 cur.selBegin().pit() != cur.selEnd().pit();
4552 if (multi_par_selection) {
4553 // If there is a multi-paragraph selection, a tab is inserted
4554 // at the beginning of each paragraph.
4555 cur.recordUndoSelection();
4556 pit_type const pit_end = cur.selEnd().pit();
4557 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4558 pars_[pit].insertChar(0, '\t',
4559 bv->buffer().params().track_changes);
4560 // Update the selection pos to make sure the selection does not
4561 // change as the inserted tab will increase the logical pos.
4562 if (cur.realAnchor().pit() == pit)
4563 cur.realAnchor().forwardPos();
4564 if (cur.pit() == pit)
4569 // Maybe we shouldn't allow tabs within a line, because they
4570 // are not (yet) aligned as one might do expect.
4571 FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4572 dispatch(cur, ncmd);
4577 case LFUN_TAB_DELETE: {
4578 bool const tc = bv->buffer().params().track_changes;
4579 if (cur.selection()) {
4580 // If there is a selection, a tab (if present) is removed from
4581 // the beginning of each paragraph.
4582 cur.recordUndoSelection();
4583 pit_type const pit_end = cur.selEnd().pit();
4584 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4585 Paragraph & par = paragraphs()[pit];
4588 char_type const c = par.getChar(0);
4589 if (c == '\t' || c == ' ') {
4590 // remove either 1 tab or 4 spaces.
4591 int const n = (c == ' ' ? 4 : 1);
4592 for (int i = 0; i < n
4593 && !par.empty() && par.getChar(0) == c; ++i) {
4594 if (cur.pit() == pit)
4596 if (cur.realAnchor().pit() == pit
4597 && cur.realAnchor().pos() > 0 )
4598 cur.realAnchor().backwardPos();
4599 par.eraseChar(0, tc);
4605 // If there is no selection, try to remove a tab or some spaces
4606 // before the position of the cursor.
4607 Paragraph & par = paragraphs()[cur.pit()];
4608 pos_type const pos = cur.pos();
4613 char_type const c = par.getChar(pos - 1);
4617 par.eraseChar(cur.pos(), tc);
4619 for (int n_spaces = 0;
4621 && par.getChar(cur.pos() - 1) == ' '
4625 par.eraseChar(cur.pos(), tc);
4632 case LFUN_CHAR_DELETE_FORWARD:
4633 if (!cur.selection()) {
4634 if (cur.pos() == cur.paragraph().size())
4635 // Par boundary, force full-screen update
4636 singleParUpdate = false;
4637 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4639 cur.selection(true);
4644 needsUpdate |= erase(cur);
4647 cutSelection(cur, false);
4648 cur.setCurrentFont();
4649 singleParUpdate = false;
4651 moveCursor(cur, false);
4654 case LFUN_CHAR_DELETE_BACKWARD:
4655 if (!cur.selection()) {
4656 if (bv->getIntl().getTransManager().backspace()) {
4657 bool par_boundary = cur.pos() == 0;
4658 bool first_par = cur.pit() == 0;
4659 // Par boundary, full-screen update
4661 singleParUpdate = false;
4662 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4664 cur.selection(true);
4669 needsUpdate |= backspace(cur);
4671 if (par_boundary && !first_par && cur.pos() > 0
4672 && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4673 needsUpdate |= backspace(cur);
4678 DocIterator const dit = cur.selectionBegin();
4679 cutSelection(cur, false);
4680 if (cur.buffer()->params().track_changes)
4681 // since we're doing backwards deletion,
4682 // and the selection is not really cut,
4683 // move cursor before selection (#11630)
4685 cur.setCurrentFont();
4686 singleParUpdate = false;
4690 case LFUN_PARAGRAPH_BREAK: {
4691 cap::replaceSelection(cur);
4692 pit_type pit = cur.pit();
4693 Paragraph const & par = pars_[pit];
4694 bool lastpar = (pit == pit_type(pars_.size() - 1));
4695 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4696 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4697 if (prev < pit && cur.pos() == par.beginOfBody()
4698 && par.empty() && !par.isEnvSeparator(cur.pos())
4699 && !par.layout().keepempty
4700 && !par.layout().isCommand()
4701 && pars_[prev].layout() != par.layout()
4702 && pars_[prev].layout().isEnvironment()
4703 && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4704 if (par.layout().isEnvironment()
4705 && pars_[prev].getDepth() == par.getDepth()) {
4706 docstring const layout = par.layout().name();
4707 DocumentClass const & tc = bv->buffer().params().documentClass();
4708 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4709 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4710 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4711 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4713 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4714 breakParagraph(cur);
4716 Font const f(inherit_font, cur.current_font.language());
4717 pars_[cur.pit() - 1].resetFonts(f);
4719 if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4721 breakParagraph(cur, cmd.getArg(0) == "inverse");
4724 // If we have a list and autoinsert item insets,
4726 Layout::LaTeXArgMap args = par.layout().args();
4727 for (auto const & thearg : args) {
4728 Layout::latexarg arg = thearg.second;
4729 if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4730 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4731 lyx::dispatch(cmd2);
4737 case LFUN_INSET_INSERT: {
4740 // We have to avoid triggering InstantPreview loading
4741 // before inserting into the document. See bug #5626.
4742 bool loaded = bv->buffer().isFullyLoaded();
4743 bv->buffer().setFullyLoaded(false);
4744 Inset * inset = createInset(&bv->buffer(), cmd);
4745 bv->buffer().setFullyLoaded(loaded);
4748 // FIXME (Abdel 01/02/2006):
4749 // What follows would be a partial fix for bug 2154:
4750 // http://www.lyx.org/trac/ticket/2154
4751 // This automatically put the label inset _after_ a
4752 // numbered section. It should be possible to extend the mechanism
4753 // to any kind of LateX environement.
4754 // The correct way to fix that bug would be at LateX generation.
4755 // I'll let the code here for reference as it could be used for some
4756 // other feature like "automatic labelling".
4758 Paragraph & par = pars_[cur.pit()];
4759 if (inset->lyxCode() == LABEL_CODE
4760 && !par.layout().counter.empty()) {
4761 // Go to the end of the paragraph
4762 // Warning: Because of Change-Tracking, the last
4763 // position is 'size()' and not 'size()-1':
4764 cur.pos() = par.size();
4765 // Insert a new paragraph
4766 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4770 if (cur.selection())
4771 cutSelection(cur, false);
4773 cur.forceBufferUpdate();
4774 if (inset->editable() && inset->asInsetText())
4775 inset->edit(cur, true);
4779 // trigger InstantPreview now
4780 if (inset->lyxCode() == EXTERNAL_CODE) {
4781 InsetExternal & ins =
4782 static_cast<InsetExternal &>(*inset);
4783 ins.updatePreview();
4790 case LFUN_INSET_DISSOLVE: {
4791 if (dissolveInset(cur)) {
4793 cur.forceBufferUpdate();
4798 case LFUN_INSET_SPLIT: {
4799 if (splitInset(cur)) {
4801 cur.forceBufferUpdate();
4806 case LFUN_GRAPHICS_SET_GROUP: {
4807 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4813 string id = to_utf8(cmd.argument());
4814 string grp = graphics::getGroupParams(bv->buffer(), id);
4815 InsetGraphicsParams tmp, inspar = ins->getParams();
4818 inspar.groupId = to_utf8(cmd.argument());
4820 InsetGraphics::string2params(grp, bv->buffer(), tmp);
4821 tmp.filename = inspar.filename;
4825 ins->setParams(inspar);
4829 case LFUN_SPACE_INSERT:
4830 if (cur.paragraph().layout().free_spacing)
4831 insertChar(cur, ' ');
4833 doInsertInset(cur, this, cmd, false, false);
4836 moveCursor(cur, false);
4839 case LFUN_SPECIALCHAR_INSERT: {
4840 string const name = to_utf8(cmd.argument());
4841 if (name == "hyphenation")
4842 specialChar(cur, InsetSpecialChar::HYPHENATION);
4843 else if (name == "allowbreak")
4844 specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4845 else if (name == "ligature-break")
4846 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4847 else if (name == "slash")
4848 specialChar(cur, InsetSpecialChar::SLASH);
4849 else if (name == "nobreakdash")
4850 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4851 else if (name == "dots")
4852 specialChar(cur, InsetSpecialChar::LDOTS);
4853 else if (name == "end-of-sentence")
4854 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4855 else if (name == "menu-separator")
4856 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4857 else if (name == "lyx")
4858 specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4859 else if (name == "tex")
4860 specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4861 else if (name == "latex")
4862 specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4863 else if (name == "latex2e")
4864 specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4865 else if (name.empty())
4866 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4868 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4872 case LFUN_IPAMACRO_INSERT: {
4873 string const arg = cmd.getArg(0);
4874 if (arg == "deco") {
4875 // Open the inset, and move the current selection
4877 doInsertInset(cur, this, cmd, true, true);
4879 // Some insets are numbered, others are shown in the outline pane so
4880 // let's update the labels and the toc backend.
4881 cur.forceBufferUpdate();
4884 if (arg == "tone-falling")
4885 ipaChar(cur, InsetIPAChar::TONE_FALLING);
4886 else if (arg == "tone-rising")
4887 ipaChar(cur, InsetIPAChar::TONE_RISING);
4888 else if (arg == "tone-high-rising")
4889 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4890 else if (arg == "tone-low-rising")
4891 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4892 else if (arg == "tone-high-rising-falling")
4893 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4894 else if (arg.empty())
4895 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4897 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4901 case LFUN_WORD_UPCASE:
4902 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4905 case LFUN_WORD_LOWCASE:
4906 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4909 case LFUN_WORD_CAPITALIZE:
4910 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4913 case LFUN_CHARS_TRANSPOSE:
4914 charsTranspose(cur);
4918 cur.message(_("Paste"));
4919 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4920 cap::replaceSelection(cur);
4922 // without argument?
4923 string const arg = to_utf8(cmd.argument());
4925 bool tryGraphics = true;
4926 if (theClipboard().isInternal())
4927 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4928 else if (theClipboard().hasTextContents()) {
4929 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"), 0,
4930 Clipboard::AnyTextType))
4931 tryGraphics = false;
4933 if (tryGraphics && theClipboard().hasGraphicsContents())
4934 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4935 } else if (isStrUnsignedInt(arg)) {
4936 // we have a numerical argument
4937 pasteFromStack(cur, bv->buffer().errorList("Paste"),
4938 convert<unsigned int>(arg));
4939 } else if (arg == "html" || arg == "latex") {
4940 Clipboard::TextType type = (arg == "html") ?
4941 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4942 pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4944 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4946 type = Clipboard::PdfGraphicsType;
4947 else if (arg == "png")
4948 type = Clipboard::PngGraphicsType;
4949 else if (arg == "jpeg")
4950 type = Clipboard::JpegGraphicsType;
4951 else if (arg == "linkback")
4952 type = Clipboard::LinkBackGraphicsType;
4953 else if (arg == "emf")
4954 type = Clipboard::EmfGraphicsType;
4955 else if (arg == "wmf")
4956 type = Clipboard::WmfGraphicsType;
4958 // we also check in getStatus()
4959 LYXERR0("Unrecognized graphics type: " << arg);
4961 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4964 bv->buffer().errors("Paste");
4965 bv->buffer().updatePreviews(); // bug 11619
4966 cur.clearSelection(); // bug 393
4972 cutSelection(cur, true);
4973 cur.message(_("Cut"));
4976 case LFUN_SERVER_GET_XY:
4977 cur.message(from_utf8(
4978 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4979 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4982 case LFUN_SERVER_SET_XY: {
4985 istringstream is(to_utf8(cmd.argument()));
4988 lyxerr << "SETXY: Could not parse coordinates in '"
4989 << to_utf8(cmd.argument()) << endl;
4991 tm->setCursorFromCoordinates(cur, x, y);
4995 case LFUN_SERVER_GET_LAYOUT:
4996 cur.message(cur.paragraph().layout().name());
5000 case LFUN_LAYOUT_TOGGLE: {
5001 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
5002 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
5003 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
5005 docstring layout = resolveLayout(req_layout, cur);
5006 if (layout.empty()) {
5007 cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
5008 from_utf8(N_(" not known")));
5012 docstring const old_layout = cur.paragraph().layout().name();
5013 bool change_layout = !isAlreadyLayout(layout, cur);
5015 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
5016 change_layout = true;
5017 layout = resolveLayout(docstring(), cur);
5020 if (change_layout) {
5021 setLayout(cur, layout);
5022 if (cur.pit() > 0 && !ignoreautonests) {
5023 pit_type prev_pit = cur.pit() - 1;
5024 depth_type const cur_depth = pars_[cur.pit()].getDepth();
5025 // Scan for the previous par on same nesting level
5026 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
5028 set<docstring> const & autonests =
5029 pars_[prev_pit].layout().autonests();
5030 set<docstring> const & autonested =
5031 pars_[cur.pit()].layout().isAutonestedBy();
5032 if (autonests.find(layout) != autonests.end()
5033 || autonested.find(old_layout) != autonested.end())
5034 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5038 DocumentClass const & tclass = bv->buffer().params().documentClass();
5039 bool inautoarg = false;
5040 for (auto const & la_pair : tclass[layout].args()) {
5041 Layout::latexarg const & arg = la_pair.second;
5042 if (arg.autoinsert) {
5043 // If we had already inserted an arg automatically,
5044 // leave this now in order to insert the next one.
5046 cur.leaveInset(cur.inset());
5049 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5050 lyx::dispatch(cmd2);
5058 case LFUN_ENVIRONMENT_SPLIT: {
5059 bool const outer = cmd.argument() == "outer";
5060 bool const previous = cmd.argument() == "previous";
5061 bool const before = cmd.argument() == "before";
5062 bool const normal = cmd.argument().empty();
5063 Paragraph const & para = cur.paragraph();
5065 if (para.layout().isEnvironment())
5066 layout = para.layout().name();
5067 depth_type split_depth = cur.paragraph().params().depth();
5068 vector<depth_type> nextpars_depth;
5069 if (outer || previous) {
5070 // check if we have an environment in our scope
5071 pit_type pit = cur.pit();
5072 Paragraph cpar = pars_[pit];
5078 if (layout.empty() && previous
5079 && cpar.layout().isEnvironment()
5080 && cpar.params().depth() <= split_depth)
5081 layout = cpar.layout().name();
5082 if (cpar.params().depth() < split_depth
5083 && cpar.layout().isEnvironment()) {
5085 layout = cpar.layout().name();
5086 split_depth = cpar.params().depth();
5088 if (cpar.params().depth() == 0)
5092 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5093 // save nesting of following paragraphs if they are deeper
5095 pit_type offset = 1;
5096 depth_type cur_depth = pars_[cur.pit()].params().depth();
5097 while (cur.pit() + offset <= cur.lastpit()) {
5098 Paragraph cpar = pars_[cur.pit() + offset];
5099 depth_type nextpar_depth = cpar.params().depth();
5100 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5101 nextpars_depth.push_back(nextpar_depth);
5102 cur_depth = nextpar_depth;
5109 cur.top().setPitPos(cur.pit(), 0);
5110 if (before || cur.pos() > 0)
5111 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5112 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5113 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5115 while (cur.paragraph().params().depth() > split_depth)
5116 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5118 DocumentClass const & tc = bv->buffer().params().documentClass();
5119 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5120 + from_ascii("\" ignoreautonests")));
5121 // FIXME: Bibitem mess!
5122 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5123 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5124 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5127 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5128 while (cur.paragraph().params().depth() < split_depth)
5129 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5132 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5133 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5134 if ((outer || normal) && !nextpars_depth.empty()) {
5135 // restore nesting of following paragraphs
5136 DocIterator scur = cur;
5137 depth_type max_depth = cur.paragraph().params().depth() + 1;
5138 for (auto nextpar_depth : nextpars_depth) {
5140 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5141 depth_type const olddepth = cur.paragraph().params().depth();
5142 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5143 if (olddepth == cur.paragraph().params().depth())
5144 // leave loop if no incrementation happens
5147 max_depth = cur.paragraph().params().depth() + 1;
5149 cur.setCursor(scur);
5155 case LFUN_CLIPBOARD_PASTE:
5156 cap::replaceSelection(cur);
5157 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5158 cmd.argument() == "paragraph");
5159 bv->buffer().errors("Paste");
5162 case LFUN_CLIPBOARD_PASTE_SIMPLE:
5163 cap::replaceSelection(cur);
5164 pasteSimpleText(cur, cmd.argument() == "paragraph");
5167 case LFUN_PRIMARY_SELECTION_PASTE:
5168 cap::replaceSelection(cur);
5169 pasteString(cur, theSelection().get(),
5170 cmd.argument() == "paragraph");
5173 case LFUN_SELECTION_PASTE:
5174 // Copy the selection buffer to the clipboard stack,
5175 // because we want it to appear in the "Edit->Paste
5177 cap::replaceSelection(cur);
5178 cap::copySelectionToStack();
5179 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5180 bv->buffer().errors("Paste");
5183 case LFUN_QUOTE_INSERT: {
5184 cap::replaceSelection(cur);
5187 Paragraph const & par = cur.paragraph();
5188 pos_type pos = cur.pos();
5189 // Ignore deleted text before cursor
5190 while (pos > 0 && par.isDeleted(pos - 1))
5193 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5195 // Guess quote side.
5196 // A space triggers an opening quote. This is passed if the preceding
5197 // char/inset is a space or at paragraph start.
5199 if (pos > 0 && !par.isSpace(pos - 1)) {
5200 if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5201 // If an opening double quotation mark precedes, and this
5202 // is a single quote, make it opening as well
5204 static_cast<InsetQuotes &>(*cur.prevInset());
5205 string const type = ins.getType();
5206 if (!suffixIs(type, "ld") || !inner)
5207 c = par.getChar(pos - 1);
5209 else if (!cur.prevInset()
5210 || (cur.prevInset() && cur.prevInset()->isChar()))
5211 // If a char precedes, pass that and let InsetQuote decide
5212 c = par.getChar(pos - 1);
5215 if (par.getInset(pos - 1)
5216 && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5217 // skip "invisible" insets
5221 c = par.getChar(pos - 1);
5226 QuoteLevel const quote_level = inner
5227 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5228 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5229 cur.buffer()->updateBuffer();
5234 case LFUN_MOUSE_TRIPLE:
5235 if (cmd.button() == mouse_button::button1) {
5237 setCursor(cur, cur.pit(), 0);
5240 if (cur.pos() < cur.lastpos())
5241 setCursor(cur, cur.pit(), cur.lastpos());
5247 case LFUN_MOUSE_DOUBLE:
5248 if (cmd.button() == mouse_button::button1) {
5249 selectWord(cur, WHOLE_WORD);
5254 // Single-click on work area
5255 case LFUN_MOUSE_PRESS: {
5256 // We are not marking a selection with the keyboard in any case.
5257 Cursor & bvcur = cur.bv().cursor();
5258 bvcur.setMark(false);
5259 switch (cmd.button()) {
5260 case mouse_button::button1:
5261 bvcur.setClickPos(cmd.x(), cmd.y());
5262 if (!bvcur.selection())
5264 bvcur.resetAnchor();
5265 if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5266 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5267 // FIXME: move this to mouseSetCursor?
5268 if (bvcur.wordSelection() && bvcur.inTexted())
5269 expandWordSel(bvcur);
5272 case mouse_button::button2:
5273 if (lyxrc.mouse_middlebutton_paste) {
5274 // Middle mouse pasting.
5275 bv->mouseSetCursor(cur);
5277 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5278 "selection-paste ; primary-selection-paste paragraph"));
5280 cur.noScreenUpdate();
5283 case mouse_button::button3: {
5284 // Don't do anything if we right-click a
5285 // selection, a context menu will popup.
5286 if (bvcur.selection() && cur >= bvcur.selectionBegin()
5287 && cur <= bvcur.selectionEnd()) {
5288 cur.noScreenUpdate();
5291 if (!bv->mouseSetCursor(cur, false))
5292 cur.screenUpdateFlags(Update::FitCursor);
5298 } // switch (cmd.button())
5301 case LFUN_MOUSE_MOTION: {
5302 // Mouse motion with right or middle mouse do nothing for now.
5303 if (cmd.button() != mouse_button::button1) {
5304 cur.noScreenUpdate();
5307 // ignore motions deeper nested than the real anchor
5308 Cursor & bvcur = cur.bv().cursor();
5309 if (!bvcur.realAnchor().hasPart(cur)) {
5313 CursorSlice old = bvcur.top();
5315 int const wh = bv->workHeight();
5316 int const y = max(0, min(wh - 1, cmd.y()));
5318 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5319 cur.setTargetX(cmd.x());
5320 // Don't allow selecting a separator inset
5321 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5324 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5325 else if (cmd.y() < 0)
5326 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5327 // This is to allow jumping over large insets
5328 if (cur.top() == old) {
5330 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5331 else if (cmd.y() < 0)
5332 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5334 // We continue with our existing selection or start a new one, so don't
5335 // reset the anchor.
5336 bvcur.setCursor(cur);
5337 if (bvcur.wordSelection() && bvcur.inTexted())
5338 expandWordSel(bvcur);
5339 bvcur.selection(true);
5340 bvcur.setCurrentFont();
5341 if (cur.top() == old) {
5342 // We didn't move one iota, so no need to update the screen.
5343 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5344 //cur.noScreenUpdate();
5350 case LFUN_MOUSE_RELEASE:
5351 switch (cmd.button()) {
5352 case mouse_button::button1:
5353 // unregister last mouse press position
5354 cur.bv().cursor().setClickPos(-1, -1);
5355 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5356 // If there is a new selection, update persistent selection;
5357 // otherwise, single click does not clear persistent selection
5359 if (cur.selection()) {
5360 // Finish selection. If double click,
5361 // cur is moved to the end of word by
5362 // selectWord but bvcur is current
5364 cur.bv().cursor().setSelection();
5365 // We might have removed an empty but drawn selection
5366 // (probably a margin)
5367 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5369 cur.noScreenUpdate();
5370 // FIXME: We could try to handle drag and drop of selection here.
5373 case mouse_button::button2:
5374 // Middle mouse pasting is handled at mouse press time,
5375 // see LFUN_MOUSE_PRESS.
5376 cur.noScreenUpdate();
5379 case mouse_button::button3:
5380 // Cursor was set at LFUN_MOUSE_PRESS time.
5381 // FIXME: If there is a selection we could try to handle a special
5382 // drag & drop context menu.
5383 cur.noScreenUpdate();
5386 case mouse_button::none:
5387 case mouse_button::button4:
5388 case mouse_button::button5:
5390 } // switch (cmd.button())
5394 case LFUN_SELF_INSERT: {
5395 if (cmd.argument().empty())
5398 // Automatically delete the currently selected
5399 // text and replace it with what is being
5400 // typed in now. Depends on lyxrc settings
5401 // "auto_region_delete", which defaults to
5404 if (lyxrc.auto_region_delete && cur.selection()) {
5405 cutSelection(cur, false);
5406 cur.setCurrentFont();
5408 cur.clearSelection();
5410 for (char_type c : cmd.argument())
5411 bv->translateAndInsert(c, this, cur);
5414 moveCursor(cur, false);
5415 cur.markNewWordPosition();
5416 bv->bookmarkEditPosition();
5420 case LFUN_HREF_INSERT: {
5421 docstring content = cmd.argument();
5422 if (content.empty() && cur.selection())
5423 content = cur.selectionAsString(false);
5425 InsetCommandParams p(HYPERLINK_CODE);
5426 if (!content.empty()){
5427 // if it looks like a link, we'll put it as target,
5428 // otherwise as name (bug #8792).
5431 // regex_match(to_utf8(content), matches, link_re)
5432 // because smatch stores pointers to the substrings rather
5433 // than making copies of them. And those pointers become
5434 // invalid after regex_match returns, since it is then
5435 // being given a temporary object. (Thanks to Georg for
5436 // figuring that out.)
5437 regex const link_re("^(([a-z]+):|www\\.).*");
5439 string const c = to_utf8(lowercase(content));
5441 if (c.substr(0,7) == "mailto:") {
5442 p["target"] = content;
5443 p["type"] = from_ascii("mailto:");
5444 } else if (regex_match(c, matches, link_re)) {
5445 p["target"] = content;
5446 string protocol = matches.str(1);
5447 if (protocol == "file")
5448 p["type"] = from_ascii("file:");
5450 p["name"] = content;
5452 string const data = InsetCommand::params2string(p);
5454 // we need to have a target. if we already have one, then
5455 // that gets used at the default for the name, too, which
5456 // is probably what is wanted.
5457 if (p["target"].empty()) {
5458 bv->showDialog("href", data);
5460 FuncRequest fr(LFUN_INSET_INSERT, data);
5466 case LFUN_LABEL_INSERT: {
5467 InsetCommandParams p(LABEL_CODE);
5468 // Try to generate a valid label
5469 p["name"] = (cmd.argument().empty()) ?
5470 cur.getPossibleLabel() :
5472 string const data = InsetCommand::params2string(p);
5474 if (cmd.argument().empty()) {
5475 bv->showDialog("label", data);
5477 FuncRequest fr(LFUN_INSET_INSERT, data);
5483 case LFUN_INFO_INSERT: {
5484 if (cmd.argument().empty()) {
5485 bv->showDialog("info", cur.current_font.language()->lang());
5488 inset = createInset(cur.buffer(), cmd);
5492 insertInset(cur, inset);
5493 cur.forceBufferUpdate();
5498 case LFUN_CAPTION_INSERT:
5499 case LFUN_FOOTNOTE_INSERT:
5500 case LFUN_NOTE_INSERT:
5501 case LFUN_BOX_INSERT:
5502 case LFUN_BRANCH_INSERT:
5503 case LFUN_PHANTOM_INSERT:
5504 case LFUN_ERT_INSERT:
5505 case LFUN_INDEXMACRO_INSERT:
5506 case LFUN_LISTING_INSERT:
5507 case LFUN_MARGINALNOTE_INSERT:
5508 case LFUN_ARGUMENT_INSERT:
5509 case LFUN_INDEX_INSERT:
5510 case LFUN_PREVIEW_INSERT:
5511 case LFUN_SCRIPT_INSERT:
5512 case LFUN_IPA_INSERT: {
5513 // Indexes reset font formatting (#11961)
5514 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5515 // Open the inset, and move the current selection
5517 doInsertInset(cur, this, cmd, true, true, resetfont);
5519 cur.setCurrentFont();
5520 // Some insets are numbered, others are shown in the outline pane so
5521 // let's update the labels and the toc backend.
5522 cur.forceBufferUpdate();
5526 case LFUN_FLEX_INSERT: {
5527 // Open the inset, and move the current selection
5529 bool const sel = cur.selection();
5530 doInsertInset(cur, this, cmd, true, true);
5531 // Insert auto-insert arguments
5532 bool autoargs = false, inautoarg = false;
5533 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5534 for (auto const & argt : args) {
5535 Layout::latexarg arg = argt.second;
5536 if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5537 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5538 lyx::dispatch(cmd2);
5540 if (arg.autoinsert) {
5541 // The cursor might have been invalidated by the replaceSelection.
5542 cur.buffer()->changed(true);
5543 // If we had already inserted an arg automatically,
5544 // leave this now in order to insert the next one.
5546 cur.leaveInset(cur.inset());
5547 cur.setCurrentFont();
5549 if (arg.insertonnewline && cur.pos() > 0) {
5550 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5551 lyx::dispatch(cmd2);
5554 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5555 lyx::dispatch(cmd2);
5562 cur.leaveInset(cur.inset());
5565 // Some insets are numbered, others are shown in the outline pane so
5566 // let's update the labels and the toc backend.
5567 cur.forceBufferUpdate();
5571 case LFUN_TABULAR_INSERT: {
5572 // if there were no arguments, just open the dialog
5573 if (cmd.argument().empty()) {
5574 bv->showDialog("tabularcreate");
5576 } else if (cur.buffer()->masterParams().tablestyle != "default"
5577 || bv->buffer().params().documentClass().tablestyle() != "default") {
5578 string tabstyle = cur.buffer()->masterParams().tablestyle;
5579 if (tabstyle == "default")
5580 tabstyle = bv->buffer().params().documentClass().tablestyle();
5581 if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5582 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5583 tabstyle + " " + to_ascii(cmd.argument()));
5587 // Unknown style. Report and fall back to default.
5588 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5589 from_utf8(N_(" not known")));
5591 if (doInsertInset(cur, this, cmd, false, true))
5593 (void) checkAndActivateInset(cur, true);
5597 case LFUN_TABULAR_STYLE_INSERT: {
5598 string const style = cmd.getArg(0);
5599 string const rows = cmd.getArg(1);
5600 string const cols = cmd.getArg(2);
5601 if (cols.empty() || !isStrInt(cols)
5602 || rows.empty() || !isStrInt(rows))
5604 int const r = convert<int>(rows);
5605 int const c = convert<int>(cols);
5612 FileName const tabstyle = libFileSearch("tabletemplates",
5613 style + suffix + ".lyx", "lyx");
5614 if (tabstyle.empty())
5616 UndoGroupHelper ugh(cur.buffer());
5618 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5619 lyx::dispatch(cmd2);
5623 // move one cell up to middle cell
5625 // add the missing rows
5626 int const addrows = r - 3;
5627 for (int i = 0 ; i < addrows ; ++i) {
5628 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5632 // add the missing columns
5633 int const addcols = c - 1;
5634 for (int i = 0 ; i < addcols ; ++i) {
5635 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5644 case LFUN_FLOAT_INSERT:
5645 case LFUN_FLOAT_WIDE_INSERT:
5646 case LFUN_WRAP_INSERT: {
5647 // will some content be moved into the inset?
5648 bool const content = cur.selection();
5649 // does the content consist of multiple paragraphs?
5650 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5652 doInsertInset(cur, this, cmd, true, true);
5655 // If some single-par content is moved into the inset,
5656 // doInsertInset puts the cursor outside the inset.
5657 // To insert the caption we put it back into the inset.
5658 // FIXME cleanup doInsertInset to avoid such dances!
5659 if (content && singlepar)
5662 ParagraphList & pars = cur.text()->paragraphs();
5664 DocumentClass const & tclass = bv->buffer().params().documentClass();
5666 // add a separate paragraph for the caption inset
5667 pars.push_back(Paragraph());
5668 pars.back().setInsetOwner(&cur.text()->inset());
5669 pars.back().setPlainOrDefaultLayout(tclass);
5670 int cap_pit = pars.size() - 1;
5672 // if an empty inset was created, we create an additional empty
5673 // paragraph at the bottom so that the user can choose where to put
5674 // the graphics (or table).
5676 pars.push_back(Paragraph());
5677 pars.back().setInsetOwner(&cur.text()->inset());
5678 pars.back().setPlainOrDefaultLayout(tclass);
5681 // reposition the cursor to the caption
5682 cur.pit() = cap_pit;
5684 // FIXME: This Text/Cursor dispatch handling is a mess!
5685 // We cannot use Cursor::dispatch here it needs access to up to
5687 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5688 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5689 cur.forceBufferUpdate();
5690 cur.screenUpdateFlags(Update::Force);
5691 // FIXME: When leaving the Float (or Wrap) inset we should
5692 // delete any empty paragraph left above or below the
5697 case LFUN_NOMENCL_INSERT: {
5698 InsetCommandParams p(NOMENCL_CODE);
5699 if (cmd.argument().empty()) {
5701 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5702 cur.clearSelection();
5704 p["symbol"] = cmd.argument();
5705 string const data = InsetCommand::params2string(p);
5706 bv->showDialog("nomenclature", data);
5710 case LFUN_INDEX_PRINT: {
5711 InsetCommandParams p(INDEX_PRINT_CODE);
5712 if (cmd.argument().empty())
5713 p["type"] = from_ascii("idx");
5715 p["type"] = cmd.argument();
5716 string const data = InsetCommand::params2string(p);
5717 FuncRequest fr(LFUN_INSET_INSERT, data);
5722 case LFUN_NOMENCL_PRINT:
5723 case LFUN_NEWPAGE_INSERT:
5725 doInsertInset(cur, this, cmd, false, false);
5729 case LFUN_SEPARATOR_INSERT: {
5730 doInsertInset(cur, this, cmd, false, false);
5732 // remove a following space
5733 Paragraph & par = cur.paragraph();
5734 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5735 par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5739 case LFUN_DEPTH_DECREMENT:
5740 changeDepth(cur, DEC_DEPTH);
5743 case LFUN_DEPTH_INCREMENT:
5744 changeDepth(cur, INC_DEPTH);
5747 case LFUN_REGEXP_MODE:
5748 regexpDispatch(cur, cmd);
5751 case LFUN_MATH_MODE: {
5752 if (cmd.argument() == "on" || cmd.argument() == "") {
5753 // don't pass "on" as argument
5754 // (it would appear literally in the first cell)
5755 docstring sel = cur.selectionAsString(false);
5756 InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5757 // create a macro template if we see "\\newcommand" somewhere, and
5758 // an ordinary formula otherwise
5760 && (sel.find(from_ascii("\\newcommand")) != string::npos
5761 || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5762 || sel.find(from_ascii("\\def")) != string::npos)
5763 && macro->fromString(sel)) {
5765 replaceSelection(cur);
5768 // no meaningful macro template was found
5770 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5773 // The argument is meaningful
5774 // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5775 // has a different meaning in math mode
5776 mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5780 case LFUN_MATH_MACRO:
5781 if (cmd.argument().empty())
5782 cur.errorMessage(from_utf8(N_("Missing argument")));
5785 string s = to_utf8(cmd.argument());
5786 string const s1 = token(s, ' ', 1);
5787 int const nargs = s1.empty() ? 0 : convert<int>(s1);
5788 string const s2 = token(s, ' ', 2);
5789 MacroType type = MacroTypeNewcommand;
5791 type = MacroTypeDef;
5792 InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5793 from_utf8(token(s, ' ', 0)), nargs, false, type);
5794 inset->setBuffer(bv->buffer());
5795 if (insertInset(cur, inset)) {
5796 // If insertion is successful, enter macro inset and select the name
5798 cur.top().pos() = cur.top().lastpos();
5800 cur.selection(true);
5801 cur.top().pos() = 0;
5807 case LFUN_MATH_DISPLAY:
5808 case LFUN_MATH_SUBSCRIPT:
5809 case LFUN_MATH_SUPERSCRIPT:
5810 case LFUN_MATH_INSERT:
5811 case LFUN_MATH_AMS_MATRIX:
5812 case LFUN_MATH_MATRIX:
5813 case LFUN_MATH_DELIM:
5814 case LFUN_MATH_BIGDELIM:
5815 mathDispatch(cur, cmd);
5818 case LFUN_FONT_EMPH: {
5819 Font font(ignore_font, ignore_language);
5820 font.fontInfo().setEmph(FONT_TOGGLE);
5821 toggleAndShow(cur, this, font);
5825 case LFUN_FONT_ITAL: {
5826 Font font(ignore_font, ignore_language);
5827 font.fontInfo().setShape(ITALIC_SHAPE);
5828 toggleAndShow(cur, this, font);
5832 case LFUN_FONT_BOLD:
5833 case LFUN_FONT_BOLDSYMBOL: {
5834 Font font(ignore_font, ignore_language);
5835 font.fontInfo().setSeries(BOLD_SERIES);
5836 toggleAndShow(cur, this, font);
5840 case LFUN_FONT_NOUN: {
5841 Font font(ignore_font, ignore_language);
5842 font.fontInfo().setNoun(FONT_TOGGLE);
5843 toggleAndShow(cur, this, font);
5847 case LFUN_FONT_TYPEWRITER: {
5848 Font font(ignore_font, ignore_language);
5849 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5850 toggleAndShow(cur, this, font);
5854 case LFUN_FONT_SANS: {
5855 Font font(ignore_font, ignore_language);
5856 font.fontInfo().setFamily(SANS_FAMILY);
5857 toggleAndShow(cur, this, font);
5861 case LFUN_FONT_ROMAN: {
5862 Font font(ignore_font, ignore_language);
5863 font.fontInfo().setFamily(ROMAN_FAMILY);
5864 toggleAndShow(cur, this, font);
5868 case LFUN_FONT_DEFAULT: {
5869 Font font(inherit_font, ignore_language);
5870 toggleAndShow(cur, this, font);
5874 case LFUN_FONT_STRIKEOUT: {
5875 Font font(ignore_font, ignore_language);
5876 font.fontInfo().setStrikeout(FONT_TOGGLE);
5877 toggleAndShow(cur, this, font);
5881 case LFUN_FONT_CROSSOUT: {
5882 Font font(ignore_font, ignore_language);
5883 font.fontInfo().setXout(FONT_TOGGLE);
5884 toggleAndShow(cur, this, font);
5888 case LFUN_FONT_UNDERUNDERLINE: {
5889 Font font(ignore_font, ignore_language);
5890 font.fontInfo().setUuline(FONT_TOGGLE);
5891 toggleAndShow(cur, this, font);
5895 case LFUN_FONT_UNDERWAVE: {
5896 Font font(ignore_font, ignore_language);
5897 font.fontInfo().setUwave(FONT_TOGGLE);
5898 toggleAndShow(cur, this, font);
5902 case LFUN_FONT_UNDERLINE: {
5903 Font font(ignore_font, ignore_language);
5904 font.fontInfo().setUnderbar(FONT_TOGGLE);
5905 toggleAndShow(cur, this, font);
5909 case LFUN_FONT_NO_SPELLCHECK: {
5910 Font font(ignore_font, ignore_language);
5911 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5912 toggleAndShow(cur, this, font);
5916 case LFUN_FONT_SIZE: {
5917 Font font(ignore_font, ignore_language);
5918 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5919 toggleAndShow(cur, this, font);
5923 case LFUN_LANGUAGE: {
5924 string const lang_arg = cmd.getArg(0);
5925 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5926 Language const * lang =
5927 reset ? cur.bv().buffer().params().language
5928 : languages.getLanguage(lang_arg);
5929 // we allow reset_language, which is 0, but only if it
5930 // was requested via empty or "reset" arg.
5931 if (!lang && !reset)
5933 bool const toggle = (cmd.getArg(1) != "set");
5934 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5935 Font font(ignore_font, lang);
5936 toggleAndShow(cur, this, font, toggle);
5940 case LFUN_TEXTSTYLE_APPLY: {
5941 unsigned int num = 0;
5942 string const arg = to_utf8(cmd.argument());
5945 if (isStrUnsignedInt(arg)) {
5946 num = convert<uint>(arg);
5947 if (num >= freeFonts.size()) {
5948 cur.message(_("Invalid argument (number exceeds stack size)!"));
5952 cur.message(_("Invalid argument (must be a non-negative number)!"));
5956 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5957 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5961 // Set the freefont using the contents of \param data dispatched from
5962 // the frontends and apply it at the current cursor location.
5963 case LFUN_TEXTSTYLE_UPDATE: {
5964 Font font(ignore_font, ignore_language);
5966 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5967 docstring const props = font.stateText(&bv->buffer().params(), true);
5968 freeFonts.push(make_pair(props, font));
5970 toggleAndShow(cur, this, font, toggleall);
5971 cur.message(bformat(_("Text properties applied: %1$s"), props));
5973 LYXERR0("Invalid argument of textstyle-update");
5977 case LFUN_FINISHED_LEFT:
5978 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5979 // We're leaving an inset, going left. If the inset is LTR, we're
5980 // leaving from the front, so we should not move (remain at --- but
5981 // not in --- the inset). If the inset is RTL, move left, without
5982 // entering the inset itself; i.e., move to after the inset.
5983 if (cur.paragraph().getFontSettings(
5984 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5985 cursorVisLeft(cur, true);
5988 case LFUN_FINISHED_RIGHT:
5989 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
5990 // We're leaving an inset, going right. If the inset is RTL, we're
5991 // leaving from the front, so we should not move (remain at --- but
5992 // not in --- the inset). If the inset is LTR, move right, without
5993 // entering the inset itself; i.e., move to after the inset.
5994 if (!cur.paragraph().getFontSettings(
5995 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5996 cursorVisRight(cur, true);
5999 case LFUN_FINISHED_BACKWARD:
6000 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
6001 cur.setCurrentFont();
6004 case LFUN_FINISHED_FORWARD:
6005 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
6007 cur.setCurrentFont();
6010 case LFUN_LAYOUT_PARAGRAPH: {
6012 params2string(cur.paragraph(), data);
6013 data = "show\n" + data;
6014 bv->showDialog("paragraph", data);
6018 case LFUN_PARAGRAPH_UPDATE: {
6020 params2string(cur.paragraph(), data);
6022 // Will the paragraph accept changes from the dialog?
6024 cur.inset().allowParagraphCustomization(cur.idx());
6026 data = "update " + convert<string>(accept) + '\n' + data;
6027 bv->updateDialog("paragraph", data);
6031 case LFUN_ACCENT_UMLAUT:
6032 case LFUN_ACCENT_CIRCUMFLEX:
6033 case LFUN_ACCENT_GRAVE:
6034 case LFUN_ACCENT_ACUTE:
6035 case LFUN_ACCENT_TILDE:
6036 case LFUN_ACCENT_PERISPOMENI:
6037 case LFUN_ACCENT_CEDILLA:
6038 case LFUN_ACCENT_MACRON:
6039 case LFUN_ACCENT_DOT:
6040 case LFUN_ACCENT_UNDERDOT:
6041 case LFUN_ACCENT_UNDERBAR:
6042 case LFUN_ACCENT_CARON:
6043 case LFUN_ACCENT_BREVE:
6044 case LFUN_ACCENT_TIE:
6045 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6046 case LFUN_ACCENT_CIRCLE:
6047 case LFUN_ACCENT_OGONEK:
6048 theApp()->handleKeyFunc(cmd.action());
6049 if (!cmd.argument().empty())
6050 // FIXME: Are all these characters encoded in one byte in utf8?
6051 bv->translateAndInsert(cmd.argument()[0], this, cur);
6052 cur.screenUpdateFlags(Update::FitCursor);
6055 case LFUN_FLOAT_LIST_INSERT: {
6056 DocumentClass const & tclass = bv->buffer().params().documentClass();
6057 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6059 if (cur.selection())
6060 cutSelection(cur, false);
6061 breakParagraph(cur);
6063 if (cur.lastpos() != 0) {
6064 cursorBackward(cur);
6065 breakParagraph(cur);
6068 docstring const laystr = cur.inset().usePlainLayout() ?
6069 tclass.plainLayoutName() :
6070 tclass.defaultLayoutName();
6071 setLayout(cur, laystr);
6072 ParagraphParameters p;
6073 // FIXME If this call were replaced with one to clearParagraphParams(),
6074 // then we could get rid of this method altogether.
6075 setParagraphs(cur, p);
6076 // FIXME This should be simplified when InsetFloatList takes a
6077 // Buffer in its constructor.
6078 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6079 ifl->setBuffer(bv->buffer());
6080 insertInset(cur, ifl);
6083 lyxerr << "Non-existent float type: "
6084 << to_utf8(cmd.argument()) << endl;
6089 case LFUN_CHANGE_ACCEPT: {
6090 acceptOrRejectChanges(cur, ACCEPT);
6094 case LFUN_CHANGE_REJECT: {
6095 acceptOrRejectChanges(cur, REJECT);
6099 case LFUN_THESAURUS_ENTRY: {
6100 Language const * language = cur.getFont().language();
6101 docstring arg = cmd.argument();
6103 arg = cur.selectionAsString(false);
6104 // Too large. We unselect if needed and try to get
6105 // the first word in selection or under cursor
6106 if (arg.size() > 100 || arg.empty()) {
6107 if (cur.selection()) {
6108 DocIterator selbeg = cur.selectionBegin();
6109 cur.clearSelection();
6110 setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6111 cur.screenUpdateFlags(Update::Force);
6113 // Get word or selection
6114 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6115 arg = cur.selectionAsString(false);
6116 arg += " lang=" + from_ascii(language->lang());
6119 string lang = cmd.getArg(1);
6120 // This duplicates the code in GuiThesaurus::initialiseParams
6121 if (prefixIs(lang, "lang=")) {
6122 language = languages.getLanguage(lang.substr(5));
6124 language = cur.getFont().language();
6127 string lang = language->code();
6128 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6129 LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6130 frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6131 _("The path to the thesaurus directory has not been specified.\n"
6132 "The thesaurus is not functional.\n"
6133 "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6136 bv->showDialog("thesaurus", to_utf8(arg));
6140 case LFUN_SPELLING_ADD: {
6141 Language const * language = getLanguage(cur, cmd.getArg(1));
6142 docstring word = from_utf8(cmd.getArg(0));
6144 word = cur.selectionAsString(false);
6146 if (word.size() > 100 || word.empty()) {
6147 // Get word or selection
6148 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6149 word = cur.selectionAsString(false);
6152 WordLangTuple wl(word, language);
6153 theSpellChecker()->insert(wl);
6157 case LFUN_SPELLING_ADD_LOCAL: {
6158 Language const * language = getLanguage(cur, cmd.getArg(1));
6159 docstring word = from_utf8(cmd.getArg(0));
6161 word = cur.selectionAsString(false);
6162 if (word.size() > 100)
6165 // Get word or selection
6166 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6167 word = cur.selectionAsString(false);
6170 WordLangTuple wl(word, language);
6171 if (!bv->buffer().params().spellignored(wl)) {
6172 cur.recordUndoBufferParams();
6173 bv->buffer().params().spellignore().push_back(wl);
6175 // trigger re-check of whole buffer
6176 bv->buffer().requestSpellcheck();
6181 case LFUN_SPELLING_REMOVE_LOCAL: {
6182 Language const * language = getLanguage(cur, cmd.getArg(1));
6183 docstring word = from_utf8(cmd.getArg(0));
6185 word = cur.selectionAsString(false);
6186 if (word.size() > 100)
6189 // Get word or selection
6190 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6191 word = cur.selectionAsString(false);
6194 WordLangTuple wl(word, language);
6195 bool has_item = false;
6196 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6197 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6198 if (it->lang()->code() != wl.lang()->code())
6200 if (it->word() == wl.word()) {
6206 cur.recordUndoBufferParams();
6207 bv->buffer().params().spellignore().erase(it);
6209 // trigger re-check of whole buffer
6210 bv->buffer().requestSpellcheck();
6216 case LFUN_SPELLING_IGNORE: {
6217 Language const * language = getLanguage(cur, cmd.getArg(1));
6218 docstring word = from_utf8(cmd.getArg(0));
6220 word = cur.selectionAsString(false);
6222 if (word.size() > 100 || word.empty()) {
6223 // Get word or selection
6224 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6225 word = cur.selectionAsString(false);
6228 WordLangTuple wl(word, language);
6229 theSpellChecker()->accept(wl);
6233 case LFUN_SPELLING_REMOVE: {
6234 Language const * language = getLanguage(cur, cmd.getArg(1));
6235 docstring word = from_utf8(cmd.getArg(0));
6237 word = cur.selectionAsString(false);
6239 if (word.size() > 100 || word.empty()) {
6240 // Get word or selection
6241 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6242 word = cur.selectionAsString(false);
6245 WordLangTuple wl(word, language);
6246 theSpellChecker()->remove(wl);
6250 case LFUN_PARAGRAPH_PARAMS_APPLY: {
6251 // Given data, an encoding of the ParagraphParameters
6252 // generated in the Paragraph dialog, this function sets
6253 // the current paragraph, or currently selected paragraphs,
6255 // NOTE: This function overrides all existing settings.
6256 setParagraphs(cur, cmd.argument());
6257 cur.message(_("Paragraph layout set"));
6261 case LFUN_PARAGRAPH_PARAMS: {
6262 // Given data, an encoding of the ParagraphParameters as we'd
6263 // find them in a LyX file, this function modifies the current paragraph,
6264 // or currently selected paragraphs.
6265 // NOTE: This function only modifies, and does not override, existing
6267 setParagraphs(cur, cmd.argument(), true);
6268 cur.message(_("Paragraph layout set"));
6273 if (cur.selection()) {
6274 cur.selection(false);
6277 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6278 // correct, but I'm not 100% sure -- dov, 071019
6279 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6283 case LFUN_OUTLINE_UP: {
6284 pos_type const opos = cur.pos();
6285 outline(OutlineUp, cur, false);
6286 setCursor(cur, cur.pit(), opos);
6287 cur.forceBufferUpdate();
6292 case LFUN_OUTLINE_DOWN: {
6293 pos_type const opos = cur.pos();
6294 outline(OutlineDown, cur, false);
6295 setCursor(cur, cur.pit(), opos);
6296 cur.forceBufferUpdate();
6301 case LFUN_OUTLINE_IN:
6302 outline(OutlineIn, cur, cmd.getArg(0) == "local");
6303 cur.forceBufferUpdate();
6307 case LFUN_OUTLINE_OUT:
6308 outline(OutlineOut, cur, cmd.getArg(0) == "local");
6309 cur.forceBufferUpdate();
6313 case LFUN_SERVER_GET_STATISTICS: {
6314 DocIterator from, to;
6315 if (cur.selection()) {
6316 from = cur.selectionBegin();
6317 to = cur.selectionEnd();
6319 from = doc_iterator_begin(cur.buffer());
6320 to = doc_iterator_end(cur.buffer());
6323 cur.buffer()->updateStatistics(from, to);
6324 string const arg0 = cmd.getArg(0);
6325 if (arg0 == "words") {
6326 cur.message(convert<docstring>(cur.buffer()->wordCount()));
6327 } else if (arg0 == "chars") {
6328 cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6329 } else if (arg0 == "chars-space") {
6330 cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6332 cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6333 + convert<docstring>(cur.buffer()->charCount(false)) + " "
6334 + convert<docstring>(cur.buffer()->charCount(true)));
6340 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6345 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6347 if (lyxrc.spellcheck_continuously && !needsUpdate) {
6348 // Check for misspelled text
6349 // The redraw is useful because of the painting of
6350 // misspelled markers depends on the cursor position.
6351 // Trigger a redraw for cursor moves inside misspelled text.
6352 if (!cur.inTexted()) {
6353 // move from regular text to math
6354 needsUpdate = last_misspelled;
6355 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6356 // move inside regular text
6357 needsUpdate = last_misspelled
6358 || cur.paragraph().isMisspelled(cur.pos(), true);
6362 // FIXME: The cursor flag is reset two lines below
6363 // so we need to check here if some of the LFUN did touch that.
6364 // for now only Text::erase() and Text::backspace() do that.
6365 // The plan is to verify all the LFUNs and then to remove this
6366 // singleParUpdate boolean altogether.
6367 if (cur.result().screenUpdate() & Update::Force) {
6368 singleParUpdate = false;
6372 // FIXME: the following code should go in favor of fine grained
6373 // update flag treatment.
6374 if (singleParUpdate) {
6375 // Inserting characters does not change par height in general. So, try
6376 // to update _only_ this paragraph. BufferView will detect if a full
6377 // metrics update is needed anyway.
6378 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6382 && &oldTopSlice.inset() == &cur.inset()
6383 && oldTopSlice.idx() == cur.idx()
6384 && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
6385 && !cur.selection())
6386 // FIXME: it would be better if we could just do this
6388 //if (cur.result().update() != Update::FitCursor)
6389 // cur.noScreenUpdate();
6391 // But some LFUNs do not set Update::FitCursor when needed, so we
6392 // do it for all. This is not very harmfull as FitCursor will provoke
6393 // a full redraw only if needed but still, a proper review of all LFUN
6394 // should be done and this needsUpdate boolean can then be removed.
6395 cur.screenUpdateFlags(Update::FitCursor);
6397 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6401 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6402 FuncStatus & status) const
6404 LBUFERR(this == cur.text());
6406 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6408 bool allow_in_passthru = false;
6409 InsetCode code = NO_CODE;
6411 switch (cmd.action()) {
6413 case LFUN_DEPTH_DECREMENT:
6414 enable = changeDepthAllowed(cur, DEC_DEPTH);
6417 case LFUN_DEPTH_INCREMENT:
6418 enable = changeDepthAllowed(cur, INC_DEPTH);
6422 // FIXME We really should not allow this to be put, e.g.,
6423 // in a footnote, or in ERT. But it would make sense in a
6424 // branch, so I'm not sure what to do.
6425 status.setOnOff(cur.paragraph().params().startOfAppendix());
6428 case LFUN_DIALOG_SHOW_NEW_INSET:
6429 if (cmd.argument() == "bibitem")
6430 code = BIBITEM_CODE;
6431 else if (cmd.argument() == "bibtex") {
6433 // not allowed in description items
6434 enable = !inDescriptionItem(cur);
6436 else if (cmd.argument() == "box")
6438 else if (cmd.argument() == "branch")
6440 else if (cmd.argument() == "citation")
6442 else if (cmd.argument() == "counter")
6443 code = COUNTER_CODE;
6444 else if (cmd.argument() == "ert")
6446 else if (cmd.argument() == "external")
6447 code = EXTERNAL_CODE;
6448 else if (cmd.argument() == "float")
6450 else if (cmd.argument() == "graphics")
6451 code = GRAPHICS_CODE;
6452 else if (cmd.argument() == "href")
6453 code = HYPERLINK_CODE;
6454 else if (cmd.argument() == "include")
6455 code = INCLUDE_CODE;
6456 else if (cmd.argument() == "index")
6458 else if (cmd.argument() == "index_print")
6459 code = INDEX_PRINT_CODE;
6460 else if (cmd.argument() == "listings")
6461 code = LISTINGS_CODE;
6462 else if (cmd.argument() == "mathspace")
6463 code = MATH_HULL_CODE;
6464 else if (cmd.argument() == "nomenclature")
6465 code = NOMENCL_CODE;
6466 else if (cmd.argument() == "nomencl_print")
6467 code = NOMENCL_PRINT_CODE;
6468 else if (cmd.argument() == "label")
6470 else if (cmd.argument() == "line")
6472 else if (cmd.argument() == "note")
6474 else if (cmd.argument() == "phantom")
6475 code = PHANTOM_CODE;
6476 else if (cmd.argument() == "ref")
6478 else if (cmd.argument() == "space")
6480 else if (cmd.argument() == "toc")
6482 else if (cmd.argument() == "vspace")
6484 else if (cmd.argument() == "wrap")
6488 case LFUN_ERT_INSERT:
6491 case LFUN_LISTING_INSERT:
6492 code = LISTINGS_CODE;
6493 // not allowed in description items
6494 enable = !inDescriptionItem(cur);
6496 case LFUN_FOOTNOTE_INSERT:
6499 case LFUN_TABULAR_INSERT:
6500 code = TABULAR_CODE;
6502 case LFUN_TABULAR_STYLE_INSERT:
6503 code = TABULAR_CODE;
6505 case LFUN_MARGINALNOTE_INSERT:
6508 case LFUN_FLOAT_INSERT:
6509 case LFUN_FLOAT_WIDE_INSERT:
6510 // FIXME: If there is a selection, we should check whether there
6511 // are floats in the selection, but this has performance issues, see
6512 // LFUN_CHANGE_ACCEPT/REJECT.
6514 if (inDescriptionItem(cur))
6515 // not allowed in description items
6518 InsetCode const inset_code = cur.inset().lyxCode();
6520 // algorithm floats cannot be put in another float
6521 if (to_utf8(cmd.argument()) == "algorithm") {
6522 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6526 // for figures and tables: only allow in another
6527 // float or wrap if it is of the same type and
6528 // not a subfloat already
6529 if(cur.inset().lyxCode() == code) {
6530 InsetFloat const & ins =
6531 static_cast<InsetFloat const &>(cur.inset());
6532 enable = ins.params().type == to_utf8(cmd.argument())
6533 && !ins.params().subfloat;
6534 } else if(cur.inset().lyxCode() == WRAP_CODE) {
6535 InsetWrap const & ins =
6536 static_cast<InsetWrap const &>(cur.inset());
6537 enable = ins.params().type == to_utf8(cmd.argument());
6541 case LFUN_WRAP_INSERT:
6543 // not allowed in description items
6544 enable = !inDescriptionItem(cur);
6546 case LFUN_FLOAT_LIST_INSERT: {
6547 code = FLOAT_LIST_CODE;
6548 // not allowed in description items
6549 enable = !inDescriptionItem(cur);
6551 FloatList const & floats = cur.buffer()->params().documentClass().floats();
6552 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6553 // make sure we know about such floats
6554 if (cit == floats.end() ||
6555 // and that we know how to generate a list of them
6556 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6557 status.setUnknown(true);
6558 // probably not necessary, but...
6564 case LFUN_CAPTION_INSERT: {
6565 code = CAPTION_CODE;
6566 string arg = cmd.getArg(0);
6567 bool varia = arg != "Unnumbered"
6568 && cur.inset().allowsCaptionVariation(arg);
6569 // not allowed in description items,
6570 // and in specific insets
6571 enable = !inDescriptionItem(cur)
6572 && (varia || arg.empty() || arg == "Standard");
6575 case LFUN_NOTE_INSERT:
6578 case LFUN_FLEX_INSERT: {
6580 docstring s = from_utf8(cmd.getArg(0));
6581 // Prepend "Flex:" prefix if not there
6582 if (!prefixIs(s, from_ascii("Flex:")))
6583 s = from_ascii("Flex:") + s;
6584 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6586 else if (!cur.paragraph().allowedInContext(cur, cur.buffer()->params().documentClass().insetLayout(s)))
6590 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6591 if (ilt != InsetLyXType::CHARSTYLE
6592 && ilt != InsetLyXType::CUSTOM
6593 && ilt != InsetLyXType::STANDARD)
6598 case LFUN_BOX_INSERT:
6601 case LFUN_BRANCH_INSERT:
6603 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6604 && cur.buffer()->params().branchlist().empty())
6607 case LFUN_IPA_INSERT:
6610 case LFUN_PHANTOM_INSERT:
6611 code = PHANTOM_CODE;
6613 case LFUN_LABEL_INSERT:
6616 case LFUN_INFO_INSERT:
6618 enable = cmd.argument().empty()
6619 || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6621 case LFUN_ARGUMENT_INSERT: {
6623 allow_in_passthru = true;
6624 string const arg = cmd.getArg(0);
6629 Layout const & lay = cur.paragraph().layout();
6630 Layout::LaTeXArgMap args = lay.args();
6631 Layout::LaTeXArgMap::const_iterator const lait =
6633 if (lait != args.end()) {
6635 pit_type pit = cur.pit();
6636 pit_type lastpit = cur.pit();
6637 if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6638 // In a sequence of "merged" environment layouts, we only allow
6639 // non-item arguments once.
6640 lastpit = cur.lastpit();
6641 // get the first paragraph in sequence with this layout
6642 depth_type const current_depth = cur.paragraph().params().depth();
6646 Paragraph cpar = pars_[pit - 1];
6647 if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6653 for (; pit <= lastpit; ++pit) {
6654 if (pars_[pit].layout() != lay)
6656 for (auto const & table : pars_[pit].insetList())
6657 if (InsetArgument const * ins = table.inset->asInsetArgument())
6658 if (ins->name() == arg) {
6659 // we have this already
6668 case LFUN_INDEX_INSERT:
6671 case LFUN_INDEX_PRINT:
6672 code = INDEX_PRINT_CODE;
6673 // not allowed in description items
6674 enable = !inDescriptionItem(cur);
6676 case LFUN_NOMENCL_INSERT:
6677 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6681 code = NOMENCL_CODE;
6683 case LFUN_NOMENCL_PRINT:
6684 code = NOMENCL_PRINT_CODE;
6685 // not allowed in description items
6686 enable = !inDescriptionItem(cur);
6688 case LFUN_HREF_INSERT:
6689 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6693 code = HYPERLINK_CODE;
6695 case LFUN_INDEXMACRO_INSERT: {
6696 string const arg = cmd.getArg(0);
6697 if (arg == "sortkey")
6698 code = INDEXMACRO_SORTKEY_CODE;
6700 code = INDEXMACRO_CODE;
6703 case LFUN_IPAMACRO_INSERT: {
6704 string const arg = cmd.getArg(0);
6706 code = IPADECO_CODE;
6708 code = IPACHAR_CODE;
6711 case LFUN_QUOTE_INSERT:
6712 // always allow this, since we will inset a raw quote
6713 // if an inset is not allowed.
6714 allow_in_passthru = true;
6716 case LFUN_SPECIALCHAR_INSERT:
6717 code = SPECIALCHAR_CODE;
6719 case LFUN_SPACE_INSERT:
6720 // slight hack: we know this is allowed in math mode
6724 case LFUN_PREVIEW_INSERT:
6725 code = PREVIEW_CODE;
6727 case LFUN_SCRIPT_INSERT:
6731 case LFUN_MATH_INSERT:
6732 case LFUN_MATH_AMS_MATRIX:
6733 case LFUN_MATH_MATRIX:
6734 case LFUN_MATH_DELIM:
6735 case LFUN_MATH_BIGDELIM:
6736 case LFUN_MATH_DISPLAY:
6737 case LFUN_MATH_MODE:
6738 case LFUN_MATH_SUBSCRIPT:
6739 case LFUN_MATH_SUPERSCRIPT:
6740 code = MATH_HULL_CODE;
6743 case LFUN_MATH_MACRO:
6744 code = MATHMACRO_CODE;
6747 case LFUN_REGEXP_MODE:
6748 code = MATH_HULL_CODE;
6749 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6752 case LFUN_INSET_MODIFY:
6753 // We need to disable this, because we may get called for a
6755 // InsetTabular::getStatus() -> InsetText::getStatus()
6756 // and we don't handle LFUN_INSET_MODIFY.
6760 case LFUN_FONT_EMPH:
6761 status.setOnOff(fontinfo.emph() == FONT_ON);
6762 enable = !cur.paragraph().isPassThru();
6765 case LFUN_FONT_ITAL:
6766 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6767 enable = !cur.paragraph().isPassThru();
6770 case LFUN_FONT_NOUN:
6771 status.setOnOff(fontinfo.noun() == FONT_ON);
6772 enable = !cur.paragraph().isPassThru();
6775 case LFUN_FONT_BOLD:
6776 case LFUN_FONT_BOLDSYMBOL:
6777 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6778 enable = !cur.paragraph().isPassThru();
6781 case LFUN_FONT_SANS:
6782 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6783 enable = !cur.paragraph().isPassThru();
6786 case LFUN_FONT_ROMAN:
6787 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6788 enable = !cur.paragraph().isPassThru();
6791 case LFUN_FONT_TYPEWRITER:
6792 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6793 enable = !cur.paragraph().isPassThru();
6797 enable = cur.selection();
6801 if (cmd.argument().empty()) {
6802 if (theClipboard().isInternal())
6803 enable = cap::numberOfSelections() > 0;
6805 enable = !theClipboard().empty();
6809 // we have an argument
6810 string const arg = to_utf8(cmd.argument());
6811 if (isStrUnsignedInt(arg)) {
6812 // it's a number and therefore means the internal stack
6813 unsigned int n = convert<unsigned int>(arg);
6814 enable = cap::numberOfSelections() > n;
6818 // explicit text type?
6819 if (arg == "html") {
6820 // Do not enable for PlainTextType, since some tidying in the
6821 // frontend is needed for HTML, which is too unsafe for plain text.
6822 enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6824 } else if (arg == "latex") {
6825 // LaTeX is usually not available on the clipboard with
6826 // the correct MIME type, but in plain text.
6827 enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6828 theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6832 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6834 type = Clipboard::PdfGraphicsType;
6835 else if (arg == "png")
6836 type = Clipboard::PngGraphicsType;
6837 else if (arg == "jpeg")
6838 type = Clipboard::JpegGraphicsType;
6839 else if (arg == "linkback")
6840 type = Clipboard::LinkBackGraphicsType;
6841 else if (arg == "emf")
6842 type = Clipboard::EmfGraphicsType;
6843 else if (arg == "wmf")
6844 type = Clipboard::WmfGraphicsType;
6847 LYXERR0("Unrecognized graphics type: " << arg);
6848 // we don't want to assert if the user just mistyped the LFUN
6849 LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6853 enable = theClipboard().hasGraphicsContents(type);
6857 case LFUN_CLIPBOARD_PASTE:
6858 case LFUN_CLIPBOARD_PASTE_SIMPLE:
6859 enable = !theClipboard().empty();
6862 case LFUN_PRIMARY_SELECTION_PASTE:
6863 status.setUnknown(!theSelection().supported());
6864 enable = cur.selection() || !theSelection().empty();
6867 case LFUN_SELECTION_PASTE:
6868 enable = cap::selection();
6871 case LFUN_PARAGRAPH_MOVE_UP:
6872 enable = cur.pit() > 0 && !cur.selection();
6875 case LFUN_PARAGRAPH_MOVE_DOWN:
6876 enable = cur.pit() < cur.lastpit() && !cur.selection();
6879 case LFUN_CHANGE_ACCEPT:
6880 case LFUN_CHANGE_REJECT:
6881 if (!cur.selection())
6882 enable = cur.paragraph().isChanged(cur.pos());
6884 // will enable if there is a change in the selection
6887 // cheap improvement for efficiency: using cached
6888 // buffer variable, if there is no change in the
6889 // document, no need to check further.
6890 if (!cur.buffer()->areChangesPresent())
6893 for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6894 pos_type const beg = it.pos();
6896 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6897 it.idx() == cur.selectionEnd().idx());
6899 end = cur.selectionEnd().pos();
6901 // the +1 is needed for cases, e.g., where there is a
6902 // paragraph break. See #11629.
6903 end = it.lastpos() + 1;
6904 if (beg != end && it.paragraph().isChanged(beg, end)) {
6908 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6918 case LFUN_OUTLINE_UP:
6919 case LFUN_OUTLINE_DOWN:
6920 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6922 case LFUN_OUTLINE_IN:
6923 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC
6924 && cur.text()->getTocLevel(cur.pit()) !=
6925 cur.buffer()->params().documentClass().max_toclevel();
6927 case LFUN_OUTLINE_OUT:
6928 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC
6929 && cur.text()->getTocLevel(cur.pit()) !=
6930 cur.buffer()->params().documentClass().min_toclevel();
6933 case LFUN_NEWLINE_INSERT:
6934 // LaTeX restrictions (labels or empty par)
6935 enable = !cur.paragraph().isPassThru()
6936 && cur.pos() > cur.paragraph().beginOfBody();
6939 case LFUN_SEPARATOR_INSERT:
6940 // Always enabled for now
6944 case LFUN_TAB_INSERT:
6945 case LFUN_TAB_DELETE:
6946 enable = cur.paragraph().isPassThru();
6949 case LFUN_GRAPHICS_SET_GROUP: {
6950 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6954 status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6958 case LFUN_NEWPAGE_INSERT:
6959 // not allowed in description items
6960 code = NEWPAGE_CODE;
6961 enable = !inDescriptionItem(cur);
6965 enable = !cur.paragraph().isPassThru();
6966 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6969 case LFUN_PARAGRAPH_BREAK:
6970 enable = inset().allowMultiPar();
6973 case LFUN_SPELLING_ADD:
6974 case LFUN_SPELLING_ADD_LOCAL:
6975 case LFUN_SPELLING_REMOVE_LOCAL:
6976 case LFUN_SPELLING_IGNORE:
6977 case LFUN_SPELLING_REMOVE:
6978 enable = theSpellChecker() != nullptr;
6979 if (enable && !cmd.getArg(1).empty()) {
6980 // validate explicitly given language
6981 Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6982 enable &= lang != nullptr;
6987 case LFUN_LAYOUT_TOGGLE: {
6988 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
6989 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
6990 docstring const layout = resolveLayout(req_layout, cur);
6992 // FIXME: make this work in multicell selection case
6993 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
6994 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
6995 && isAlreadyLayout(layout, cur));
6999 case LFUN_ENVIRONMENT_SPLIT: {
7000 if (cmd.argument() == "outer") {
7001 // check if we have an environment in our nesting hierarchy
7003 depth_type const current_depth = cur.paragraph().params().depth();
7004 pit_type pit = cur.pit();
7005 Paragraph cpar = pars_[pit];
7007 if (pit == 0 || cpar.params().depth() == 0)
7011 if (cpar.params().depth() < current_depth)
7012 res = cpar.layout().isEnvironment();
7017 else if (cmd.argument() == "previous") {
7018 // look if we have an environment in the previous par
7019 pit_type pit = cur.pit();
7020 Paragraph cpar = pars_[pit];
7024 enable = cpar.layout().isEnvironment();
7030 else if (cur.paragraph().layout().isEnvironment()) {
7031 enable = cmd.argument() == "before"
7032 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
7039 case LFUN_LAYOUT_PARAGRAPH:
7040 case LFUN_PARAGRAPH_PARAMS:
7041 case LFUN_PARAGRAPH_PARAMS_APPLY:
7042 case LFUN_PARAGRAPH_UPDATE:
7043 enable = owner_->allowParagraphCustomization();
7046 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
7047 // Because they insert COMBINING DIACRITICAL Unicode characters,
7048 // that cannot be handled by LaTeX but must be converted according
7049 // to the definition in lib/unicodesymbols?
7050 case LFUN_ACCENT_ACUTE:
7051 case LFUN_ACCENT_BREVE:
7052 case LFUN_ACCENT_CARON:
7053 case LFUN_ACCENT_CEDILLA:
7054 case LFUN_ACCENT_CIRCLE:
7055 case LFUN_ACCENT_CIRCUMFLEX:
7056 case LFUN_ACCENT_DOT:
7057 case LFUN_ACCENT_GRAVE:
7058 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7059 case LFUN_ACCENT_MACRON:
7060 case LFUN_ACCENT_OGONEK:
7061 case LFUN_ACCENT_TIE:
7062 case LFUN_ACCENT_TILDE:
7063 case LFUN_ACCENT_PERISPOMENI:
7064 case LFUN_ACCENT_UMLAUT:
7065 case LFUN_ACCENT_UNDERBAR:
7066 case LFUN_ACCENT_UNDERDOT:
7067 case LFUN_FONT_FRAK:
7068 case LFUN_FONT_SIZE:
7069 case LFUN_FONT_STATE:
7070 case LFUN_FONT_UNDERLINE:
7071 case LFUN_FONT_STRIKEOUT:
7072 case LFUN_FONT_CROSSOUT:
7073 case LFUN_FONT_UNDERUNDERLINE:
7074 case LFUN_FONT_UNDERWAVE:
7075 case LFUN_FONT_NO_SPELLCHECK:
7076 case LFUN_TEXTSTYLE_UPDATE:
7077 enable = !cur.paragraph().isPassThru();
7080 case LFUN_FONT_DEFAULT: {
7081 Font font(inherit_font, ignore_language);
7082 BufferParams const & bp = cur.buffer()->masterParams();
7083 if (cur.selection()) {
7085 // Check if we have a non-default font attribute
7086 // in the selection range.
7087 DocIterator const from = cur.selectionBegin();
7088 DocIterator const to = cur.selectionEnd();
7089 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7090 if (!dit.inTexted()) {
7094 Paragraph const & par = dit.paragraph();
7095 pos_type const pos = dit.pos();
7096 Font tmp = par.getFontSettings(bp, pos);
7097 if (tmp.fontInfo() != font.fontInfo()
7098 || tmp.language() != bp.language) {
7106 // Disable if all is default already.
7107 enable = (cur.current_font.fontInfo() != font.fontInfo()
7108 || cur.current_font.language() != bp.language);
7112 case LFUN_TEXTSTYLE_APPLY:
7113 enable = !freeFonts.empty();
7116 case LFUN_WORD_DELETE_FORWARD:
7117 case LFUN_WORD_DELETE_BACKWARD:
7118 case LFUN_LINE_DELETE_FORWARD:
7119 case LFUN_WORD_FORWARD:
7120 case LFUN_WORD_BACKWARD:
7121 case LFUN_WORD_RIGHT:
7122 case LFUN_WORD_LEFT:
7123 case LFUN_CHAR_FORWARD:
7124 case LFUN_CHAR_FORWARD_SELECT:
7125 case LFUN_CHAR_BACKWARD:
7126 case LFUN_CHAR_BACKWARD_SELECT:
7127 case LFUN_CHAR_LEFT:
7128 case LFUN_CHAR_LEFT_SELECT:
7129 case LFUN_CHAR_RIGHT:
7130 case LFUN_CHAR_RIGHT_SELECT:
7132 case LFUN_UP_SELECT:
7134 case LFUN_DOWN_SELECT:
7135 case LFUN_PARAGRAPH_SELECT:
7136 case LFUN_PARAGRAPH_UP_SELECT:
7137 case LFUN_PARAGRAPH_DOWN_SELECT:
7138 case LFUN_LINE_BEGIN_SELECT:
7139 case LFUN_LINE_END_SELECT:
7140 case LFUN_WORD_FORWARD_SELECT:
7141 case LFUN_WORD_BACKWARD_SELECT:
7142 case LFUN_WORD_RIGHT_SELECT:
7143 case LFUN_WORD_LEFT_SELECT:
7144 case LFUN_WORD_SELECT:
7145 case LFUN_SECTION_SELECT:
7146 case LFUN_BUFFER_BEGIN:
7147 case LFUN_BUFFER_END:
7148 case LFUN_BUFFER_BEGIN_SELECT:
7149 case LFUN_BUFFER_END_SELECT:
7150 case LFUN_INSET_BEGIN:
7151 case LFUN_INSET_END:
7152 case LFUN_INSET_BEGIN_SELECT:
7153 case LFUN_INSET_END_SELECT:
7154 case LFUN_PARAGRAPH_UP:
7155 case LFUN_PARAGRAPH_DOWN:
7156 case LFUN_LINE_BEGIN:
7158 case LFUN_CHAR_DELETE_FORWARD:
7159 case LFUN_CHAR_DELETE_BACKWARD:
7160 case LFUN_WORD_UPCASE:
7161 case LFUN_WORD_LOWCASE:
7162 case LFUN_WORD_CAPITALIZE:
7163 case LFUN_CHARS_TRANSPOSE:
7164 case LFUN_SERVER_GET_XY:
7165 case LFUN_SERVER_SET_XY:
7166 case LFUN_SERVER_GET_LAYOUT:
7167 case LFUN_SELF_INSERT:
7168 case LFUN_UNICODE_INSERT:
7169 case LFUN_THESAURUS_ENTRY:
7171 case LFUN_SERVER_GET_STATISTICS:
7172 // these are handled in our dispatch()
7176 case LFUN_INSET_INSERT: {
7177 string const type = cmd.getArg(0);
7178 if (type == "toc") {
7180 // not allowed in description items
7181 //FIXME: couldn't this be merged in Inset::insetAllowed()?
7182 enable = !inDescriptionItem(cur);
7189 case LFUN_SEARCH_IGNORE: {
7190 bool const value = cmd.getArg(1) == "true";
7191 setIgnoreFormat(cmd.getArg(0), value);
7201 || !cur.inset().insetAllowed(code)
7202 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7205 status.setEnabled(enable);
7210 void Text::pasteString(Cursor & cur, docstring const & clip,
7213 if (!clip.empty()) {
7216 insertStringAsParagraphs(cur, clip, cur.current_font);
7218 insertStringAsLines(cur, clip, cur.current_font);
7223 // FIXME: an item inset would make things much easier.
7224 bool Text::inDescriptionItem(Cursor const & cur) const
7226 Paragraph const & par = cur.paragraph();
7227 pos_type const pos = cur.pos();
7228 pos_type const body_pos = par.beginOfBody();
7230 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7231 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7232 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7235 return (pos < body_pos
7237 && (pos == 0 || par.getChar(pos - 1) != ' ')));
7241 std::vector<docstring> Text::getFreeFonts() const
7243 vector<docstring> ffList;
7245 for (auto const & f : freeFonts)
7246 ffList.push_back(f.first);