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 // use the correct word boundary, depending on selection direction
1414 if (cur.top() > cur.normalAnchor())
1415 cur.pos() = c.selEnd().pos();
1417 cur.pos() = c.selBegin().pos();
1421 void Text::selectAll(Cursor & cur)
1423 LBUFERR(this == cur.text());
1424 if (cur.lastpos() == 0 && cur.lastpit() == 0)
1426 // If the cursor is at the beginning, make sure the cursor ends there
1427 if (cur.pit() == 0 && cur.pos() == 0) {
1428 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1430 setCursor(cur, 0, 0);
1432 setCursor(cur, 0, 0);
1434 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1440 // Select the word currently under the cursor when no
1441 // selection is currently set
1442 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
1444 LBUFERR(this == cur.text());
1445 if (cur.selection())
1447 selectWord(cur, loc);
1448 return cur.selection();
1452 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
1454 LBUFERR(this == cur.text());
1456 if (!cur.selection()) {
1457 if (!selectChange(cur))
1461 cur.recordUndoSelection();
1463 pit_type begPit = cur.selectionBegin().pit();
1464 pit_type endPit = cur.selectionEnd().pit();
1466 pos_type begPos = cur.selectionBegin().pos();
1467 pos_type endPos = cur.selectionEnd().pos();
1469 // keep selection info, because endPos becomes invalid after the first loop
1470 bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size());
1472 // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
1473 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1474 pos_type parSize = pars_[pit].size();
1476 // ignore empty paragraphs; otherwise, an assertion will fail for
1477 // acceptChanges(bparams, 0, 0) or rejectChanges(bparams, 0, 0)
1481 // do not consider first paragraph if the cursor starts at pos size()
1482 if (pit == begPit && begPos == parSize)
1485 // do not consider last paragraph if the cursor ends at pos 0
1486 if (pit == endPit && endPos == 0)
1487 break; // last iteration anyway
1489 pos_type const left = (pit == begPit ? begPos : 0);
1490 pos_type const right = (pit == endPit ? endPos : parSize);
1493 // there is no change here
1497 pars_[pit].acceptChanges(left, right);
1499 pars_[pit].rejectChanges(left, right);
1503 // next, accept/reject imaginary end-of-par characters
1505 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1506 pos_type pos = pars_[pit].size();
1508 // skip if the selection ends before the end-of-par
1509 if (pit == endPit && endsBeforeEndOfPar)
1510 break; // last iteration anyway
1512 // skip if this is not the last paragraph of the document
1513 // note: the user should be able to accept/reject the par break of the last par!
1514 if (pit == endPit && pit + 1 != int(pars_.size()))
1515 break; // last iteration anway
1518 if (pars_[pit].isInserted(pos)) {
1519 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1520 } else if (pars_[pit].isDeleted(pos)) {
1521 if (pit + 1 == int(pars_.size())) {
1522 // we cannot remove a par break at the end of the last paragraph;
1523 // instead, we mark it unchanged
1524 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1526 mergeParagraph(cur.buffer()->params(), pars_, pit);
1532 if (pars_[pit].isDeleted(pos)) {
1533 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1534 } else if (pars_[pit].isInserted(pos)) {
1535 if (pit + 1 == int(pars_.size())) {
1536 // we mark the par break at the end of the last paragraph unchanged
1537 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1539 mergeParagraph(cur.buffer()->params(), pars_, pit);
1547 // finally, invoke the DEPM
1548 deleteEmptyParagraphMechanism(begPit, endPit, begPos, endPos,
1549 cur.buffer()->params().track_changes);
1552 cur.clearSelection();
1553 setCursorIntern(cur, begPit, begPos);
1554 cur.screenUpdateFlags(Update::Force);
1555 cur.forceBufferUpdate();
1559 void Text::acceptChanges()
1561 BufferParams const & bparams = owner_->buffer().params();
1562 lyx::acceptChanges(pars_, bparams);
1563 deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes);
1567 void Text::rejectChanges()
1569 BufferParams const & bparams = owner_->buffer().params();
1570 pit_type pars_size = static_cast<pit_type>(pars_.size());
1572 // first, reject changes within each individual paragraph
1573 // (do not consider end-of-par)
1574 for (pit_type pit = 0; pit < pars_size; ++pit) {
1575 if (!pars_[pit].empty()) // prevent assertion failure
1576 pars_[pit].rejectChanges(0, pars_[pit].size());
1579 // next, reject imaginary end-of-par characters
1580 for (pit_type pit = 0; pit < pars_size; ++pit) {
1581 pos_type pos = pars_[pit].size();
1583 if (pars_[pit].isDeleted(pos)) {
1584 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1585 } else if (pars_[pit].isInserted(pos)) {
1586 if (pit == pars_size - 1) {
1587 // we mark the par break at the end of the last
1588 // paragraph unchanged
1589 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1591 mergeParagraph(bparams, pars_, pit);
1598 // finally, invoke the DEPM
1599 deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes);
1603 void Text::deleteWordForward(Cursor & cur, bool const force)
1605 LBUFERR(this == cur.text());
1606 if (cur.lastpos() == 0)
1610 cur.selection(true);
1611 cursorForwardOneWord(cur);
1613 if (force || !cur.confirmDeletion()) {
1614 cutSelection(cur, false);
1615 cur.checkBufferStructure();
1621 void Text::deleteWordBackward(Cursor & cur, bool const force)
1623 LBUFERR(this == cur.text());
1624 if (cur.lastpos() == 0)
1625 cursorBackward(cur);
1628 cur.selection(true);
1629 cursorBackwardOneWord(cur);
1631 if (force || !cur.confirmDeletion()) {
1632 cutSelection(cur, false);
1633 cur.checkBufferStructure();
1639 // Kill to end of line.
1640 void Text::changeCase(Cursor & cur, TextCase action, bool partial)
1642 LBUFERR(this == cur.text());
1646 bool const gotsel = cur.selection();
1648 from = cur.selBegin();
1652 getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
1653 cursorForwardOneWord(cur);
1656 cur.recordUndoSelection();
1658 pit_type begPit = from.pit();
1659 pit_type endPit = to.pit();
1661 pos_type begPos = from.pos();
1662 pos_type endPos = to.pos();
1664 pos_type right = 0; // needed after the for loop
1666 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1667 Paragraph & par = pars_[pit];
1668 pos_type const pos = (pit == begPit ? begPos : 0);
1669 right = (pit == endPit ? endPos : par.size());
1670 par.changeCase(cur.buffer()->params(), pos, right, action);
1673 // the selection may have changed due to logically-only deleted chars
1675 setCursor(cur, begPit, begPos);
1677 setCursor(cur, endPit, right);
1680 setCursor(cur, endPit, right);
1682 cur.checkBufferStructure();
1686 bool Text::handleBibitems(Cursor & cur)
1688 if (cur.paragraph().layout().labeltype != LABEL_BIBLIO)
1694 BufferParams const & bufparams = cur.buffer()->params();
1695 Paragraph const & par = cur.paragraph();
1696 Cursor prevcur = cur;
1697 if (cur.pit() > 0) {
1699 prevcur.pos() = prevcur.lastpos();
1701 Paragraph const & prevpar = prevcur.paragraph();
1703 // if a bibitem is deleted, merge with previous paragraph
1704 // if this is a bibliography item as well
1705 if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
1706 cur.recordUndo(prevcur.pit());
1707 mergeParagraph(bufparams, cur.text()->paragraphs(),
1709 cur.forceBufferUpdate();
1710 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1711 cur.screenUpdateFlags(Update::Force);
1715 // otherwise reset to default
1716 cur.paragraph().setPlainOrDefaultLayout(bufparams.documentClass());
1721 bool Text::erase(Cursor & cur)
1723 LASSERT(this == cur.text(), return false);
1724 bool needsUpdate = false;
1725 Paragraph & par = cur.paragraph();
1727 if (cur.pos() != cur.lastpos()) {
1728 // this is the code for a normal delete, not pasting
1730 cur.recordUndo(DELETE_UNDO);
1731 bool const was_inset = cur.paragraph().isInset(cur.pos());
1732 if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes))
1733 // the character has been logically deleted only => skip it
1734 cur.top().forwardPos();
1737 cur.forceBufferUpdate();
1739 cur.checkBufferStructure();
1742 if (cur.pit() == cur.lastpit())
1743 return dissolveInset(cur);
1745 if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1746 cur.recordUndo(DELETE_UNDO);
1747 par.setChange(cur.pos(), Change(Change::DELETED));
1751 setCursorIntern(cur, cur.pit() + 1, 0);
1752 needsUpdate = backspacePos0(cur);
1756 needsUpdate |= handleBibitems(cur);
1759 // Make sure the cursor is correct. Is this really needed?
1760 // No, not really... at least not here!
1761 cur.top().setPitPos(cur.pit(), cur.pos());
1762 cur.checkBufferStructure();
1769 bool Text::backspacePos0(Cursor & cur)
1771 LBUFERR(this == cur.text());
1775 BufferParams const & bufparams = cur.buffer()->params();
1776 ParagraphList & plist = cur.text()->paragraphs();
1777 Paragraph const & par = cur.paragraph();
1778 Cursor prevcur = cur;
1780 prevcur.pos() = prevcur.lastpos();
1781 Paragraph const & prevpar = prevcur.paragraph();
1783 // is it an empty paragraph?
1784 if (cur.lastpos() == 0
1785 || (cur.lastpos() == 1 && par.isSeparator(0))) {
1786 cur.recordUndo(prevcur.pit());
1787 plist.erase(plist.iterator_at(cur.pit()));
1789 // is previous par empty?
1790 else if (prevcur.lastpos() == 0
1791 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
1792 cur.recordUndo(prevcur.pit());
1793 plist.erase(plist.iterator_at(prevcur.pit()));
1795 // FIXME: Do we really not want to allow this???
1796 // Pasting is not allowed, if the paragraphs have different
1797 // layouts. I think it is a real bug of all other
1798 // word processors to allow it. It confuses the user.
1799 // Correction: Pasting is always allowed with standard-layout
1800 // or the empty layout.
1802 cur.recordUndo(prevcur.pit());
1803 mergeParagraph(bufparams, plist, prevcur.pit());
1806 cur.forceBufferUpdate();
1807 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1813 bool Text::backspace(Cursor & cur)
1815 LBUFERR(this == cur.text());
1816 bool needsUpdate = false;
1817 if (cur.pos() == 0) {
1819 return dissolveInset(cur);
1821 Cursor prev_cur = cur;
1824 if (!cur.paragraph().empty()
1825 && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1826 cur.recordUndo(prev_cur.pit(), prev_cur.pit());
1827 prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
1828 setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
1831 // The cursor is at the beginning of a paragraph, so
1832 // the backspace will collapse two paragraphs into one.
1833 needsUpdate = backspacePos0(cur);
1836 // this is the code for a normal backspace, not pasting
1838 cur.recordUndo(DELETE_UNDO);
1839 // We used to do cursorBackwardIntern() here, but it is
1840 // not a good idea since it triggers the auto-delete
1841 // mechanism. So we do a cursorBackwardIntern()-lite,
1842 // without the dreaded mechanism. (JMarc)
1843 setCursorIntern(cur, cur.pit(), cur.pos() - 1,
1844 false, cur.boundary());
1845 bool const was_inset = cur.paragraph().isInset(cur.pos());
1846 cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes);
1848 cur.forceBufferUpdate();
1850 cur.checkBufferStructure();
1853 if (cur.pos() == cur.lastpos())
1854 cur.setCurrentFont();
1856 needsUpdate |= handleBibitems(cur);
1858 // A singlePar update is not enough in this case.
1859 // cur.screenUpdateFlags(Update::Force);
1860 cur.top().setPitPos(cur.pit(), cur.pos());
1866 bool Text::dissolveInset(Cursor & cur)
1868 LASSERT(this == cur.text(), return false);
1870 if (isMainText() || cur.inset().nargs() != 1)
1873 cur.recordUndoInset();
1875 cur.selHandle(false);
1876 // save position inside inset
1877 pos_type spos = cur.pos();
1878 pit_type spit = cur.pit();
1879 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1881 // update cursor offset
1885 // remember position outside inset to delete inset later
1886 // we do not do it now to avoid memory reuse issues (see #10667).
1887 DocIterator inset_it = cur;
1891 Buffer & b = *cur.buffer();
1892 // Is there anything in this text?
1893 if (inset_non_empty) {
1895 // we clear the cache so that we won't get conflicts with labels
1896 // that get pasted into the buffer. we should update this before
1897 // its being empty matters. if not (i.e., if we encounter bugs),
1898 // then this should instead be:
1899 // cur.buffer().updateBuffer();
1900 // but we'll try the cheaper solution here.
1901 cur.buffer()->clearReferenceCache();
1903 ParagraphList & plist = paragraphs();
1904 if (!lyxrc.ct_markup_copied)
1905 // Do not revive deleted text
1906 lyx::acceptChanges(plist, b.params());
1908 // ERT paragraphs have the Language latex_language.
1909 // This is invalid outside of ERT, so we need to
1910 // change it to the buffer language.
1911 for (auto & p : plist)
1912 p.changeLanguage(b.params(), latex_language, b.language());
1914 /* If the inset is the only thing in paragraph and the layout
1915 * is not plain, then the layout of the first paragraph of
1916 * inset should be remembered.
1917 * FIXME: this does not work as expected when change tracking
1918 * is on However, we do not really know what to do in this
1921 DocumentClass const & tclass = cur.buffer()->params().documentClass();
1922 if (inset_it.lastpos() == 1
1923 && !tclass.isPlainLayout(plist[0].layout())
1924 && !tclass.isDefaultLayout(plist[0].layout())) {
1925 // Copy all parameters except depth.
1926 Paragraph & par = cur.paragraph();
1927 par.setLayout(plist[0].layout());
1928 depth_type const dpth = par.getDepth();
1929 par.params() = plist[0].params();
1930 par.params().depth(dpth);
1933 pasteParagraphList(cur, plist, b.params().documentClassPtr(),
1934 b.params().authors(),
1935 b.errorList("Paste"));
1938 // delete the inset now
1939 inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes);
1942 cur.pit() = min(cur.lastpit(), spit);
1943 cur.pos() = min(cur.lastpos(), spos);
1944 // Ensure the current language is set correctly (bug 6292)
1945 cur.text()->setCursor(cur, cur.pit(), cur.pos());
1946 cur.clearSelection();
1948 cur.forceBufferUpdate();
1954 bool Text::splitInset(Cursor & cur)
1956 LASSERT(this == cur.text(), return false);
1958 if (isMainText() || cur.inset().nargs() != 1)
1962 if (cur.selection()) {
1963 // start from selection begin
1964 setCursor(cur, cur.selBegin().pit(), cur.selBegin().pos());
1965 cur.clearSelection();
1967 // save split position inside inset
1968 // (we need to copy the whole inset first)
1969 pos_type spos = cur.pos();
1970 pit_type spit = cur.pit();
1971 // some things only need to be done if the inset has content
1972 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1974 // move right before the inset
1977 // remember position outside inset
1978 pos_type ipos = cur.pos();
1979 pit_type ipit = cur.pit();
1984 cap::copySelectionToTemp(cur);
1985 cur.clearSelection();
1987 // paste copied inset
1988 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
1989 cur.forceBufferUpdate();
1991 // if the inset has text, cut after split position
1992 // and paste to new inset
1993 if (inset_non_empty) {
1994 // go back to first inset
1995 cur.text()->setCursor(cur, ipit, ipos);
1997 setCursor(cur, spit, spos);
1999 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
2001 // Remember whether there was something cut that has to be pasted below
2003 bool const hasCut = cur.selection();
2004 cap::cutSelectionToTemp(cur);
2006 cur.selHandle(false);
2008 bool atlastpos = false;
2009 if (cur.pos() == 0 && cur.pit() > 0) {
2010 // if we are at par start, remove this par
2011 cur.text()->backspace(cur);
2012 cur.forceBufferUpdate();
2013 } else if (cur.pos() == cur.lastpos())
2015 // Move out of and jump over inset
2023 cur.text()->selectAll(cur);
2024 cutSelection(cur, false);
2025 // If there was something cut paste it
2027 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
2028 cur.text()->setCursor(cur, 0, 0);
2029 if (atlastpos && cur.paragraph().isFreeSpacing() && cur.paragraph().empty()) {
2030 // We started from par end, remove extra empty par in free spacing insets
2031 cur.text()->erase(cur);
2032 cur.forceBufferUpdate();
2041 void Text::getWord(CursorSlice & from, CursorSlice & to,
2042 word_location const loc) const
2045 pars_[to.pit()].locateWord(from.pos(), to.pos(), loc);
2049 void Text::write(ostream & os) const
2051 Buffer const & buf = owner_->buffer();
2052 ParagraphList::const_iterator pit = paragraphs().begin();
2053 ParagraphList::const_iterator end = paragraphs().end();
2055 for (; pit != end; ++pit)
2056 pit->write(os, buf.params(), dth);
2058 // Close begin_deeper
2059 for(; dth > 0; --dth)
2060 os << "\n\\end_deeper";
2064 bool Text::read(Lexer & lex,
2065 ErrorList & errorList, InsetText * insetPtr)
2067 Buffer const & buf = owner_->buffer();
2068 depth_type depth = 0;
2071 while (lex.isOK()) {
2073 string const token = lex.getString();
2078 if (token == "\\end_inset")
2081 if (token == "\\end_body")
2084 if (token == "\\begin_body")
2087 if (token == "\\end_document") {
2092 if (token == "\\begin_layout") {
2093 lex.pushToken(token);
2096 par.setInsetOwner(insetPtr);
2097 par.params().depth(depth);
2098 par.setFont(0, Font(inherit_font, buf.params().language));
2099 pars_.push_back(par);
2100 readParagraph(pars_.back(), lex, errorList);
2102 // register the words in the global word list
2103 pars_.back().updateWords();
2104 } else if (token == "\\begin_deeper") {
2106 } else if (token == "\\end_deeper") {
2108 lex.printError("\\end_deeper: " "depth is already null");
2112 LYXERR0("Handling unknown body token: `" << token << '\'');
2116 // avoid a crash on weird documents (bug 4859)
2117 if (pars_.empty()) {
2119 par.setInsetOwner(insetPtr);
2120 par.params().depth(depth);
2121 par.setFont(0, Font(inherit_font,
2122 buf.params().language));
2123 par.setPlainOrDefaultLayout(buf.params().documentClass());
2124 pars_.push_back(par);
2131 // Returns the current state (font, depth etc.) as a message for status bar.
2132 docstring Text::currentState(CursorData const & cur, bool devel_mode) const
2134 LBUFERR(this == cur.text());
2135 Buffer & buf = *cur.buffer();
2136 Paragraph const & par = cur.paragraph();
2137 odocstringstream os;
2139 if (buf.params().track_changes)
2140 os << _("[Change Tracking] ");
2142 Change change = par.lookupChange(cur.pos());
2144 if (change.changed()) {
2145 docstring const author =
2146 buf.params().authors().get(change.author).nameAndEmail();
2147 docstring const date = formatted_datetime(change.changetime);
2148 os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
2152 // I think we should only show changes from the default
2154 // No, from the document font (MV)
2155 Font font = cur.real_current_font;
2156 font.fontInfo().reduce(buf.params().getFont().fontInfo());
2158 os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
2160 // The paragraph depth
2161 int depth = par.getDepth();
2163 os << bformat(_(", Depth: %1$d"), depth);
2165 // The paragraph spacing, but only if different from
2167 Spacing const & spacing = par.params().spacing();
2168 if (!spacing.isDefault()) {
2169 os << _(", Spacing: ");
2170 switch (spacing.getSpace()) {
2171 case Spacing::Single:
2174 case Spacing::Onehalf:
2177 case Spacing::Double:
2180 case Spacing::Other:
2181 os << _("Other (") << from_ascii(spacing.getValueAsString()) << ')';
2183 case Spacing::Default:
2184 // should never happen, do nothing
2189 // Custom text style
2190 InsetLayout const & layout = cur.inset().getLayout();
2191 if (layout.lyxtype() == InsetLyXType::CHARSTYLE)
2192 os << _(", Style: ") << translateIfPossible(layout.labelstring());
2195 os << _(", Inset: ") << &cur.inset();
2196 if (cur.lastidx() > 0)
2197 os << _(", Cell: ") << cur.idx();
2198 os << _(", Paragraph: ") << cur.pit();
2199 os << _(", Id: ") << par.id();
2200 os << _(", Position: ") << cur.pos();
2201 // FIXME: Why is the check for par.size() needed?
2202 // We are called with cur.pos() == par.size() quite often.
2203 if (!par.empty() && cur.pos() < par.size()) {
2204 // Force output of code point, not character
2205 size_t const c = par.getChar(cur.pos());
2206 if (c == META_INSET)
2207 os << ", Char: INSET";
2209 os << _(", Char: 0x") << hex << c;
2211 os << _(", Boundary: ") << cur.boundary();
2212 // Row & row = cur.textRow();
2213 // os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
2219 docstring Text::getPossibleLabel(DocIterator const & cur) const
2221 pit_type textpit = cur.pit();
2222 Layout const * layout = &(pars_[textpit].layout());
2224 // Will contain the label prefix.
2227 // For captions, we just take the caption type
2228 Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
2229 if (caption_inset) {
2230 string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
2231 FloatList const & fl = cur.buffer()->params().documentClass().floats();
2232 if (fl.typeExist(ftype)) {
2233 Floating const & flt = fl.getType(ftype);
2234 name = from_utf8(flt.refPrefix());
2237 name = from_utf8(ftype.substr(0,3));
2239 // For section, subsection, etc...
2240 if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) {
2241 Layout const * layout2 = &(pars_[textpit - 1].layout());
2242 if (layout2->latextype != LATEX_PARAGRAPH) {
2247 if (layout->latextype != LATEX_PARAGRAPH)
2248 name = layout->refprefix;
2250 // If none of the above worked, see if the inset knows.
2252 InsetLayout const & il = cur.inset().getLayout();
2253 name = il.refprefix();
2258 docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE);
2260 // The return string of math matrices might contain linebreaks
2261 par_text = subst(par_text, '\n', '-');
2262 int const numwords = 3;
2263 for (int i = 0; i < numwords; ++i) {
2264 if (par_text.empty())
2267 par_text = split(par_text, head, ' ');
2268 // Is it legal to use spaces in labels ?
2274 // Make sure it isn't too long
2275 unsigned int const max_label_length = 32;
2276 if (text.size() > max_label_length)
2277 text.resize(max_label_length);
2280 text = name + ':' + text;
2282 // We need a unique label
2283 docstring label = text;
2285 while (cur.buffer()->activeLabel(label)) {
2286 label = text + '-' + convert<docstring>(i);
2294 docstring Text::asString(int options) const
2296 return asString(0, pars_.size(), options);
2300 docstring Text::asString(pit_type beg, pit_type end, int options) const
2302 size_t i = size_t(beg);
2303 docstring str = pars_[i].asString(options);
2304 for (++i; i != size_t(end); ++i) {
2306 str += pars_[i].asString(options);
2312 void Text::shortenForOutliner(docstring & str, size_t const maxlen)
2314 support::truncateWithEllipsis(str, maxlen);
2315 for (char_type & c : str)
2316 if (c == L'\n' || c == L'\t')
2321 void Text::forOutliner(docstring & os, size_t const maxlen,
2322 bool const shorten) const
2324 pit_type end = pars_.size() - 1;
2325 if (0 <= end && !pars_[0].labelString().empty())
2326 os += pars_[0].labelString() + ' ';
2327 forOutliner(os, maxlen, 0, end, shorten);
2331 void Text::forOutliner(docstring & os, size_t const maxlen,
2332 pit_type pit_start, pit_type pit_end,
2333 bool const shorten) const
2335 size_t tmplen = shorten ? maxlen + 1 : maxlen;
2336 pit_type end = min(size_t(pit_end), pars_.size() - 1);
2338 for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) {
2341 // This function lets the first label be treated separately
2342 pars_[i].forOutliner(os, tmplen, false, !first);
2346 shortenForOutliner(os, maxlen);
2350 void Text::charsTranspose(Cursor & cur)
2352 LBUFERR(this == cur.text());
2354 pos_type pos = cur.pos();
2356 // If cursor is at beginning or end of paragraph, do nothing.
2357 if (pos == cur.lastpos() || pos == 0)
2360 Paragraph & par = cur.paragraph();
2362 // Get the positions of the characters to be transposed.
2363 pos_type pos1 = pos - 1;
2364 pos_type pos2 = pos;
2366 // In change tracking mode, ignore deleted characters.
2367 while (pos2 < cur.lastpos() && par.isDeleted(pos2))
2369 if (pos2 == cur.lastpos())
2372 while (pos1 >= 0 && par.isDeleted(pos1))
2377 // Don't do anything if one of the "characters" is not regular text.
2378 if (par.isInset(pos1) || par.isInset(pos2))
2381 // Store the characters to be transposed (including font information).
2382 char_type const char1 = par.getChar(pos1);
2384 par.getFontSettings(cur.buffer()->params(), pos1);
2386 char_type const char2 = par.getChar(pos2);
2388 par.getFontSettings(cur.buffer()->params(), pos2);
2390 // And finally, we are ready to perform the transposition.
2391 // Track the changes if Change Tracking is enabled.
2392 bool const trackChanges = cur.buffer()->params().track_changes;
2396 par.eraseChar(pos2, trackChanges);
2397 par.eraseChar(pos1, trackChanges);
2398 par.insertChar(pos1, char2, font2, trackChanges);
2399 par.insertChar(pos2, char1, font1, trackChanges);
2401 cur.checkBufferStructure();
2403 // After the transposition, move cursor to after the transposition.
2404 setCursor(cur, cur.pit(), pos2);
2409 DocIterator Text::macrocontextPosition() const
2411 return macrocontext_position_;
2415 void Text::setMacrocontextPosition(DocIterator const & pos)
2417 macrocontext_position_ = pos;
2421 bool Text::completionSupported(Cursor const & cur) const
2423 Paragraph const & par = cur.paragraph();
2424 return !cur.buffer()->isReadonly()
2427 && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos()))
2428 && !par.isWordSeparator(cur.pos() - 1);
2432 CompletionList const * Text::createCompletionList(Cursor const & cur) const
2434 WordList const & list = theWordList(cur.getFont().language()->lang());
2435 return new TextCompletionList(cur, list);
2439 bool Text::insertCompletion(Cursor & cur, docstring const & s)
2441 LBUFERR(cur.bv().cursor() == cur);
2442 if (cur.buffer()->isReadonly())
2446 cur.bv().cursor() = cur;
2447 if (!(cur.result().screenUpdate() & Update::Force))
2448 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
2453 docstring Text::completionPrefix(Cursor const & cur) const
2455 CursorSlice from = cur.top();
2456 CursorSlice to = from;
2457 getWord(from, to, PREVIOUS_WORD);
2459 return cur.paragraph().asString(from.pos(), to.pos());
2462 bool Text::isMainText() const
2464 return &owner_->buffer().text() == this;
2468 // Note that this is supposed to return a fully realized font.
2469 FontInfo Text::layoutFont(pit_type const pit) const
2471 Layout const & layout = pars_[pit].layout();
2473 if (!pars_[pit].getDepth()) {
2474 FontInfo lf = layout.resfont;
2475 // In case the default family has been customized
2476 if (layout.font.family() == INHERIT_FAMILY)
2477 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
2478 FontInfo icf = (!isMainText())
2479 // inside insets, we call the getFont() method
2481 // outside, we access the layout font directly
2482 : owner_->getLayout().font();
2487 FontInfo font = layout.font;
2488 // Realize with the fonts of lesser depth.
2489 //font.realize(outerFont(pit));
2490 font.realize(owner_->buffer().params().getFont().fontInfo());
2496 // Note that this is supposed to return a fully realized font.
2497 FontInfo Text::labelFont(Paragraph const & par) const
2499 Buffer const & buffer = owner_->buffer();
2500 Layout const & layout = par.layout();
2502 if (!par.getDepth()) {
2503 FontInfo lf = layout.reslabelfont;
2504 // In case the default family has been customized
2505 if (layout.labelfont.family() == INHERIT_FAMILY)
2506 lf.setFamily(buffer.params().getFont().fontInfo().family());
2510 FontInfo font = layout.labelfont;
2511 // Realize with the fonts of lesser depth.
2512 font.realize(buffer.params().getFont().fontInfo());
2518 void Text::setCharFont(pit_type pit,
2519 pos_type pos, Font const & fnt, Font const & display_font)
2521 Buffer const & buffer = owner_->buffer();
2523 Layout const & layout = pars_[pit].layout();
2525 // Get concrete layout font to reduce against
2526 FontInfo layoutfont;
2528 if (pos < pars_[pit].beginOfBody())
2529 layoutfont = layout.labelfont;
2531 layoutfont = layout.font;
2533 // Realize against environment font information
2534 if (pars_[pit].getDepth()) {
2536 while (!layoutfont.resolved() &&
2537 tp != pit_type(paragraphs().size()) &&
2538 pars_[tp].getDepth()) {
2540 if (tp != pit_type(paragraphs().size()))
2541 layoutfont.realize(pars_[tp].layout().font);
2545 // Inside inset, apply the inset's font attributes if any
2548 layoutfont.realize(display_font.fontInfo());
2550 layoutfont.realize(buffer.params().getFont().fontInfo());
2552 // Now, reduce font against full layout font
2553 font.fontInfo().reduce(layoutfont);
2555 pars_[pit].setFont(pos, font);
2559 void Text::setInsetFont(BufferView const & bv, pit_type pit,
2560 pos_type pos, Font const & font)
2562 Inset * const inset = pars_[pit].getInset(pos);
2563 LASSERT(inset && inset->resetFontEdit(), return);
2565 idx_type endidx = inset->nargs();
2566 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
2567 Text * text = cs.text();
2569 // last position of the cell
2570 CursorSlice cellend = cs;
2571 cellend.pit() = cellend.lastpit();
2572 cellend.pos() = cellend.lastpos();
2573 text->setFont(bv, cs, cellend, font);
2579 void Text::setLayout(pit_type start, pit_type end,
2580 docstring const & layout)
2582 // FIXME: make this work in multicell selection case
2583 LASSERT(start != end, return);
2585 Buffer const & buffer = owner_->buffer();
2586 BufferParams const & bp = buffer.params();
2587 Layout const & lyxlayout = bp.documentClass()[layout];
2589 for (pit_type pit = start; pit != end; ++pit) {
2590 Paragraph & par = pars_[pit];
2591 // Is this a separating paragraph? If so,
2592 // this needs to be standard layout
2593 bool const is_separator = par.size() == 1
2594 && par.isEnvSeparator(0);
2595 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
2596 if (lyxlayout.margintype == MARGIN_MANUAL)
2597 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
2600 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
2604 // set layout over selection and make a total rebreak of those paragraphs
2605 void Text::setLayout(Cursor & cur, docstring const & layout)
2607 LBUFERR(this == cur.text());
2609 pit_type start = cur.selBegin().pit();
2610 pit_type end = cur.selEnd().pit() + 1;
2611 cur.recordUndoSelection();
2612 setLayout(start, end, layout);
2614 cur.setCurrentFont();
2615 cur.forceBufferUpdate();
2619 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
2620 Paragraph const & par, int max_depth)
2622 int const depth = par.params().depth();
2623 if (type == Text::INC_DEPTH && depth < max_depth)
2625 if (type == Text::DEC_DEPTH && depth > 0)
2631 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
2633 LBUFERR(this == cur.text());
2634 // this happens when selecting several cells in tabular (bug 2630)
2635 if (cur.selBegin().idx() != cur.selEnd().idx())
2638 pit_type const beg = cur.selBegin().pit();
2639 pit_type const end = cur.selEnd().pit() + 1;
2640 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2642 for (pit_type pit = beg; pit != end; ++pit) {
2643 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
2645 max_depth = pars_[pit].getMaxDepthAfter();
2651 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
2653 LBUFERR(this == cur.text());
2654 pit_type const beg = cur.selBegin().pit();
2655 pit_type const end = cur.selEnd().pit() + 1;
2656 cur.recordUndoSelection();
2657 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2659 for (pit_type pit = beg; pit != end; ++pit) {
2660 Paragraph & par = pars_[pit];
2661 if (lyx::changeDepthAllowed(type, par, max_depth)) {
2662 int const depth = par.params().depth();
2663 if (type == INC_DEPTH)
2664 par.params().depth(depth + 1);
2666 par.params().depth(depth - 1);
2668 max_depth = par.getMaxDepthAfter();
2670 cur.setCurrentFont();
2671 // this handles the counter labels, and also fixes up
2672 // depth values for follow-on (child) paragraphs
2673 cur.forceBufferUpdate();
2677 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
2679 LASSERT(this == cur.text(), return);
2681 // If there is a selection, record undo before the cursor font is changed.
2682 if (cur.selection())
2683 cur.recordUndoSelection();
2685 // Set the current_font
2686 // Determine basis font
2687 FontInfo layoutfont;
2688 pit_type pit = cur.pit();
2689 if (cur.pos() < pars_[pit].beginOfBody())
2690 layoutfont = labelFont(pars_[pit]);
2692 layoutfont = layoutFont(pit);
2694 // Update current font
2695 cur.real_current_font.update(font,
2696 cur.buffer()->params().language,
2699 // Reduce to implicit settings
2700 cur.current_font = cur.real_current_font;
2701 cur.current_font.fontInfo().reduce(layoutfont);
2702 // And resolve it completely
2703 cur.real_current_font.fontInfo().realize(layoutfont);
2705 // if there is no selection that's all we need to do
2706 if (!cur.selection())
2709 // Ok, we have a selection.
2710 Font newfont = font;
2713 // Toggling behaves as follows: We check the first character of the
2714 // selection. If it's (say) got EMPH on, then we set to off; if off,
2715 // then to on. With families and the like, we set it to INHERIT, if
2716 // we already have it.
2717 CursorSlice const & sl = cur.selBegin();
2718 Text const & text = *sl.text();
2719 Paragraph const & par = text.getPar(sl.pit());
2721 // get font at the position
2722 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
2723 text.outerFont(sl.pit()));
2724 FontInfo const & oldfi = oldfont.fontInfo();
2726 FontInfo & newfi = newfont.fontInfo();
2728 FontFamily newfam = newfi.family();
2729 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
2730 newfam == oldfi.family())
2731 newfi.setFamily(INHERIT_FAMILY);
2733 FontSeries newser = newfi.series();
2734 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
2735 newfi.setSeries(INHERIT_SERIES);
2737 FontShape newshp = newfi.shape();
2738 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
2739 newshp == oldfi.shape())
2740 newfi.setShape(INHERIT_SHAPE);
2742 ColorCode newcol = newfi.color();
2743 if (newcol != Color_none && newcol != Color_inherit
2744 && newcol != Color_ignore && newcol == oldfi.color())
2745 newfi.setColor(Color_none);
2748 if (newfi.emph() == FONT_TOGGLE)
2749 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
2750 if (newfi.underbar() == FONT_TOGGLE)
2751 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
2752 if (newfi.strikeout() == FONT_TOGGLE)
2753 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
2754 if (newfi.xout() == FONT_TOGGLE)
2755 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
2756 if (newfi.uuline() == FONT_TOGGLE)
2757 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
2758 if (newfi.uwave() == FONT_TOGGLE)
2759 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
2760 if (newfi.noun() == FONT_TOGGLE)
2761 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
2762 if (newfi.number() == FONT_TOGGLE)
2763 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
2764 if (newfi.nospellcheck() == FONT_TOGGLE)
2765 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
2768 setFont(cur.bv(), cur.selectionBegin().top(),
2769 cur.selectionEnd().top(), newfont);
2773 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
2774 CursorSlice const & end, Font const & font)
2776 Buffer const & buffer = bv.buffer();
2778 // Don't use forwardChar here as ditend might have
2779 // pos() == lastpos() and forwardChar would miss it.
2780 // Can't use forwardPos either as this descends into
2782 Language const * language = buffer.params().language;
2783 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
2784 if (dit.pos() == dit.lastpos())
2786 pit_type const pit = dit.pit();
2787 pos_type const pos = dit.pos();
2788 Inset * inset = pars_[pit].getInset(pos);
2789 if (inset && inset->resetFontEdit()) {
2790 // We need to propagate the font change to all
2791 // text cells of the inset (bugs 1973, 6919).
2792 setInsetFont(bv, pit, pos, font);
2794 TextMetrics const & tm = bv.textMetrics(this);
2795 Font f = tm.displayFont(pit, pos);
2796 f.update(font, language);
2797 setCharFont(pit, pos, f, tm.font_);
2798 // font change may change language...
2799 // spell checker has to know that
2800 pars_[pit].requestSpellCheck(pos);
2805 bool Text::cursorTop(Cursor & cur)
2807 LBUFERR(this == cur.text());
2808 return setCursor(cur, 0, 0);
2812 bool Text::cursorBottom(Cursor & cur)
2814 LBUFERR(this == cur.text());
2815 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
2819 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
2821 LBUFERR(this == cur.text());
2822 // If the mask is completely neutral, tell user
2823 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
2824 // Could only happen with user style
2825 cur.message(_("No font change defined."));
2829 // Try implicit word selection
2830 // If there is a change in the language the implicit word selection
2832 CursorSlice const resetCursor = cur.top();
2833 bool const implicitSelection =
2834 font.language() == ignore_language
2835 && font.fontInfo().number() == FONT_IGNORE
2836 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
2839 setFont(cur, font, toggleall);
2841 // Implicit selections are cleared afterwards
2842 // and cursor is set to the original position.
2843 if (implicitSelection) {
2844 cur.clearSelection();
2845 cur.top() = resetCursor;
2849 // if there was no selection at all, the point was to change cursor font.
2850 // Otherwise, we want to reset it to local text font.
2851 if (cur.selection() || implicitSelection)
2852 cur.setCurrentFont();
2856 docstring Text::getStringForDialog(Cursor & cur)
2858 LBUFERR(this == cur.text());
2860 if (cur.selection())
2861 return cur.selectionAsString(false);
2863 // Try implicit word selection. If there is a change
2864 // in the language the implicit word selection is
2866 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2867 docstring const & retval = cur.selectionAsString(false);
2868 cur.clearSelection();
2873 void Text::setLabelWidthStringToSequence(Cursor const & cur,
2874 docstring const & s)
2877 // Find first of same layout in sequence
2878 while (!isFirstInSequence(c.pit())) {
2879 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
2882 // now apply label width string to every par
2884 depth_type const depth = c.paragraph().getDepth();
2885 Layout const & layout = c.paragraph().layout();
2886 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
2887 while (c.paragraph().getDepth() > depth) {
2889 if (c.pit() > c.lastpit())
2892 if (c.paragraph().getDepth() < depth)
2894 if (c.paragraph().layout() != layout)
2897 c.paragraph().setLabelWidthString(s);
2902 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
2904 LBUFERR(cur.text());
2907 string const argument = to_utf8(arg);
2908 depth_type priordepth = -1;
2911 c.setCursor(cur.selectionBegin());
2912 pit_type const last_pit = cur.selectionEnd().pit();
2913 for ( ; c.pit() <= last_pit ; ++c.pit()) {
2914 Paragraph & par = c.paragraph();
2915 ParagraphParameters params = par.params();
2916 params.read(argument, merge);
2917 // Changes to label width string apply to all paragraphs
2918 // with same layout in a sequence.
2919 // Do this only once for a selected range of paragraphs
2920 // of the same layout and depth.
2922 par.params().apply(params, par.layout());
2923 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2924 setLabelWidthStringToSequence(c, params.labelWidthString());
2925 priordepth = par.getDepth();
2926 priorlayout = par.layout();
2931 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
2933 LBUFERR(cur.text());
2935 depth_type priordepth = -1;
2938 c.setCursor(cur.selectionBegin());
2939 pit_type const last_pit = cur.selectionEnd().pit();
2940 for ( ; c.pit() <= last_pit ; ++c.pit()) {
2941 Paragraph & par = c.paragraph();
2942 // Changes to label width string apply to all paragraphs
2943 // with same layout in a sequence.
2944 // Do this only once for a selected range of paragraphs
2945 // of the same layout and depth.
2947 par.params().apply(p, par.layout());
2948 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2949 setLabelWidthStringToSequence(c,
2950 par.params().labelWidthString());
2951 priordepth = par.getDepth();
2952 priorlayout = par.layout();
2957 // this really should just insert the inset and not move the cursor.
2958 void Text::insertInset(Cursor & cur, Inset * inset)
2960 LBUFERR(this == cur.text());
2962 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
2963 Change(cur.buffer()->params().track_changes
2964 ? Change::INSERTED : Change::UNCHANGED));
2968 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
2969 bool setfont, bool boundary)
2971 TextMetrics const & tm = cur.bv().textMetrics(this);
2972 bool const update_needed = !tm.contains(pit);
2974 setCursorIntern(cur, pit, pos, setfont, boundary);
2975 return cur.bv().checkDepm(cur, old) || update_needed;
2979 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
2980 bool setfont, bool boundary)
2982 LBUFERR(this == cur.text());
2983 cur.boundary(boundary);
2984 cur.top().setPitPos(pit, pos);
2986 cur.setCurrentFont();
2990 bool Text::checkAndActivateInset(Cursor & cur, bool front)
2992 if (front && cur.pos() == cur.lastpos())
2994 if (!front && cur.pos() == 0)
2996 Inset * inset = front ? cur.nextInset() : cur.prevInset();
2997 if (!inset || !inset->editable())
2999 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3002 * Apparently, when entering an inset we are expected to be positioned
3003 * *before* it in the containing paragraph, regardless of the direction
3004 * from which we are entering. Otherwise, cursor placement goes awry,
3005 * and when we exit from the beginning, we'll be placed *after* the
3010 inset->edit(cur, front);
3011 cur.setCurrentFont();
3012 cur.boundary(false);
3017 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
3019 if (cur.pos() == -1)
3021 if (cur.pos() == cur.lastpos())
3023 Paragraph & par = cur.paragraph();
3024 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
3025 if (!inset || !inset->editable())
3027 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3029 inset->edit(cur, movingForward,
3030 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
3031 cur.setCurrentFont();
3032 cur.boundary(false);
3037 bool Text::cursorBackward(Cursor & cur)
3039 // Tell BufferView to test for FitCursor in any case!
3040 cur.screenUpdateFlags(Update::FitCursor);
3042 // not at paragraph start?
3043 if (cur.pos() > 0) {
3044 // if on right side of boundary (i.e. not at paragraph end, but line end)
3045 // -> skip it, i.e. set boundary to true, i.e. go only logically left
3046 // there are some exceptions to ignore this: lineseps, newlines, spaces
3048 // some effectless debug code to see the values in the debugger
3049 bool bound = cur.boundary();
3050 int rowpos = cur.textRow().pos();
3051 int pos = cur.pos();
3052 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
3053 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
3054 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
3056 if (!cur.boundary() &&
3057 cur.textRow().pos() == cur.pos() &&
3058 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
3059 !cur.paragraph().isNewline(cur.pos() - 1) &&
3060 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
3061 !cur.paragraph().isSeparator(cur.pos() - 1)) {
3062 return setCursor(cur, cur.pit(), cur.pos(), true, true);
3065 // go left and try to enter inset
3066 if (checkAndActivateInset(cur, false))
3069 // normal character left
3070 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
3073 // move to the previous paragraph or do nothing
3074 if (cur.pit() > 0) {
3075 Paragraph & par = getPar(cur.pit() - 1);
3076 pos_type lastpos = par.size();
3077 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
3078 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
3080 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
3086 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
3088 Cursor temp_cur = cur;
3089 temp_cur.posVisLeft(skip_inset);
3090 if (temp_cur.depth() > cur.depth()) {
3094 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3095 true, temp_cur.boundary());
3099 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
3101 Cursor temp_cur = cur;
3102 temp_cur.posVisRight(skip_inset);
3103 if (temp_cur.depth() > cur.depth()) {
3107 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3108 true, temp_cur.boundary());
3112 bool Text::cursorForward(Cursor & cur)
3114 // Tell BufferView to test for FitCursor in any case!
3115 cur.screenUpdateFlags(Update::FitCursor);
3117 // not at paragraph end?
3118 if (cur.pos() != cur.lastpos()) {
3119 // in front of editable inset, i.e. jump into it?
3120 if (checkAndActivateInset(cur, true))
3123 TextMetrics const & tm = cur.bv().textMetrics(this);
3124 // if left of boundary -> just jump to right side
3125 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
3126 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
3127 return setCursor(cur, cur.pit(), cur.pos(), true, false);
3129 // next position is left of boundary,
3130 // but go to next line for special cases like space, newline, linesep
3132 // some effectless debug code to see the values in the debugger
3133 int endpos = cur.textRow().endpos();
3134 int lastpos = cur.lastpos();
3135 int pos = cur.pos();
3136 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
3137 bool newline = cur.paragraph().isNewline(cur.pos());
3138 bool sep = cur.paragraph().isSeparator(cur.pos());
3139 if (cur.pos() != cur.lastpos()) {
3140 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
3141 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
3142 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
3145 if (cur.textRow().endpos() == cur.pos() + 1) {
3146 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
3147 cur.pos() + 1 == cur.lastpos() &&
3148 cur.pit() != cur.lastpit()) {
3149 // move to next paragraph
3150 return setCursor(cur, cur.pit() + 1, 0, true, false);
3151 } else if (cur.textRow().endpos() != cur.lastpos() &&
3152 !cur.paragraph().isNewline(cur.pos()) &&
3153 !cur.paragraph().isEnvSeparator(cur.pos()) &&
3154 !cur.paragraph().isLineSeparator(cur.pos()) &&
3155 !cur.paragraph().isSeparator(cur.pos())) {
3156 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3160 // in front of RTL boundary? Stay on this side of the boundary because:
3161 // ab|cDDEEFFghi -> abc|DDEEFFghi
3162 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
3163 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3166 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
3169 // move to next paragraph
3170 if (cur.pit() != cur.lastpit())
3171 return setCursor(cur, cur.pit() + 1, 0, true, false);
3176 bool Text::cursorUpParagraph(Cursor & cur)
3178 bool updated = false;
3180 updated = setCursor(cur, cur.pit(), 0);
3181 else if (cur.pit() != 0)
3182 updated = setCursor(cur, cur.pit() - 1, 0);
3187 bool Text::cursorDownParagraph(Cursor & cur)
3189 bool updated = false;
3190 if (cur.pit() != cur.lastpit())
3191 if (lyxrc.mac_like_cursor_movement)
3192 if (cur.pos() == cur.lastpos())
3193 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
3195 updated = setCursor(cur, cur.pit(), cur.lastpos());
3197 updated = setCursor(cur, cur.pit() + 1, 0);
3199 updated = setCursor(cur, cur.pit(), cur.lastpos());
3205 /** delete num_spaces characters between from and to. Return the
3206 * number of spaces that got physically deleted (not marked as
3208 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
3209 int num_spaces, bool const trackChanges)
3211 if (num_spaces <= 0)
3214 // First, delete spaces marked as inserted
3216 while (pos < to && num_spaces > 0) {
3217 Change const & change = par.lookupChange(pos);
3218 if (change.inserted() && change.currentAuthor()) {
3219 par.eraseChar(pos, trackChanges);
3226 // Then remove remaining spaces
3227 int const psize = par.size();
3228 par.eraseChars(from, from + num_spaces, trackChanges);
3229 return psize - par.size();
3235 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
3236 Cursor & old, bool & need_anchor_change)
3238 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
3240 Paragraph & oldpar = old.paragraph();
3241 bool const trackChanges = cur.buffer()->params().track_changes;
3242 bool result = false;
3244 // We do nothing if cursor did not move
3245 if (cur.top() == old.top())
3248 // We do not do anything on read-only documents
3249 if (cur.buffer()->isReadonly())
3252 // Whether a common inset is found and whether the cursor is still in
3253 // the same paragraph (possibly nested).
3254 int const depth = cur.find(&old.inset());
3255 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
3256 && old.pit() == cur[depth].pit();
3259 * (1) If the chars around the old cursor were spaces and the
3260 * paragraph is not in free spacing mode, delete some of them, but
3261 * only if the cursor has really moved.
3264 /* There are still some small problems that can lead to
3265 double spaces stored in the document file or space at
3266 the beginning of paragraphs(). This happens if you have
3267 the cursor between two spaces and then save. Or if you
3268 cut and paste and the selection has a space at the
3269 beginning and then save right after the paste. (Lgb)
3271 if (!oldpar.isFreeSpacing()) {
3272 // find range of spaces around cursors
3273 pos_type from = old.pos();
3275 && oldpar.isLineSeparator(from - 1)
3276 && !oldpar.isDeleted(from - 1))
3278 pos_type to = old.pos();
3279 while (to < old.lastpos()
3280 && oldpar.isLineSeparator(to)
3281 && !oldpar.isDeleted(to))
3284 int num_spaces = to - from;
3285 // If we are not at the start of the paragraph, keep one space
3286 if (from != to && from > 0)
3289 // If cursor is inside range, keep one additional space
3290 if (same_par && cur.pos() > from && cur.pos() < to)
3293 // Remove spaces and adapt cursor.
3294 if (num_spaces > 0) {
3297 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
3298 // correct cur position
3299 // FIXME: there can be other cursors pointing there, we should update them
3301 if (cur[depth].pos() >= to)
3302 cur[depth].pos() -= deleted;
3303 else if (cur[depth].pos() > from)
3304 cur[depth].pos() = min(from + 1, old.lastpos());
3305 need_anchor_change = true;
3312 * (2) If the paragraph where the cursor was is empty, delete it
3315 // only do our other magic if we changed paragraph
3319 // only do our magic if the paragraph is empty
3320 if (!oldpar.empty())
3323 // don't delete anything if this is the ONLY paragraph!
3324 if (old.lastpit() == 0)
3327 // Do not delete empty paragraphs with keepempty set.
3328 if (oldpar.allowEmpty())
3332 old.recordUndo(max(old.pit() - 1, pit_type(0)),
3333 min(old.pit() + 1, old.lastpit()));
3334 ParagraphList & plist = old.text()->paragraphs();
3335 bool const soa = oldpar.params().startOfAppendix();
3336 plist.erase(plist.iterator_at(old.pit()));
3337 // do not lose start of appendix marker (bug 4212)
3338 if (soa && old.pit() < pit_type(plist.size()))
3339 plist[old.pit()].params().startOfAppendix(true);
3341 // see #warning (FIXME?) above
3342 if (cur.depth() >= old.depth()) {
3343 CursorSlice & curslice = cur[old.depth() - 1];
3344 if (&curslice.inset() == &old.inset()
3345 && curslice.idx() == old.idx()
3346 && curslice.pit() > old.pit()) {
3348 // since a paragraph has been deleted, all the
3349 // insets after `old' have been copied and
3350 // their address has changed. Therefore we
3351 // need to `regenerate' cur. (JMarc)
3352 cur.updateInsets(&(cur.bottom().inset()));
3353 need_anchor_change = true;
3361 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
3363 pos_type last_pos = pars_[last].size() - 1;
3364 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
3368 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
3369 pos_type first_pos, pos_type last_pos,
3372 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
3374 for (pit_type pit = first; pit <= last; ++pit) {
3375 Paragraph & par = pars_[pit];
3378 * (1) Delete consecutive spaces
3380 if (!par.isFreeSpacing()) {
3381 pos_type from = (pit == first) ? first_pos : 0;
3382 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
3383 while (from < to_pos) {
3385 while (from < par.size()
3386 && (!par.isLineSeparator(from) || par.isDeleted(from)))
3388 // find string of spaces
3390 while (to < par.size()
3391 && par.isLineSeparator(to) && !par.isDeleted(to))
3393 // empty? We are done
3397 int num_spaces = to - from;
3399 // If we are not at the extremity of the paragraph, keep one space
3400 if (from != to && from > 0 && to < par.size())
3403 // Remove spaces if needed
3404 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
3405 from = to - deleted;
3410 * (2) Delete empty pragraphs
3413 // don't delete anything if this is the only remaining paragraph
3414 // within the given range. Note: Text::acceptOrRejectChanges()
3415 // sets the cursor to 'first' after calling DEPM
3419 // don't delete empty paragraphs with keepempty set
3420 if (par.allowEmpty())
3423 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
3424 pars_.erase(pars_.iterator_at(pit));
3436 typedef limited_stack<pair<docstring, Font>> FontStack;
3437 static FontStack freeFonts(15);
3438 static bool toggleall = false;
3440 void toggleAndShow(Cursor & cur, Text * text,
3441 Font const & font, bool togall = true)
3443 text->toggleFree(cur, font, togall);
3445 if (font.language() != ignore_language ||
3446 font.fontInfo().number() != FONT_IGNORE) {
3447 TextMetrics const & tm = cur.bv().textMetrics(text);
3448 if (cur.boundary() != tm.isRTLBoundary(cur.pit(), cur.pos(),
3449 cur.real_current_font))
3450 text->setCursor(cur, cur.pit(), cur.pos(),
3451 false, !cur.boundary());
3452 if (font.language() != ignore_language)
3453 // We need a buffer update if we change the language
3454 // (e.g., with info insets or if the selection contains
3456 cur.forceBufferUpdate();
3461 void moveCursor(Cursor & cur, bool selecting)
3463 if (selecting || cur.mark())
3468 void finishChange(Cursor & cur, bool selecting)
3471 moveCursor(cur, selecting);
3475 void mathDispatch(Cursor & cur, FuncRequest const & cmd)
3478 docstring sel = cur.selectionAsString(false);
3480 // It may happen that sel is empty but there is a selection
3481 replaceSelection(cur);
3483 // Is this a valid formula?
3487 #ifdef ENABLE_ASSERTIONS
3488 const int old_pos = cur.pos();
3490 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
3491 #ifdef ENABLE_ASSERTIONS
3492 LATTEST(old_pos == cur.pos());
3494 cur.nextInset()->edit(cur, true);
3495 if (cmd.action() != LFUN_MATH_MODE)
3496 // LFUN_MATH_MODE has a different meaning in math mode
3499 InsetMathHull * formula = new InsetMathHull(cur.buffer());
3500 string const selstr = to_utf8(sel);
3501 istringstream is(selstr);
3504 if (!formula->readQuiet(lex)) {
3505 // No valid formula, let's try with delims
3506 is.str("$" + selstr + "$");
3508 if (!formula->readQuiet(lex)) {
3509 // Still not valid, leave it as is
3516 cur.insert(formula);
3517 cur.nextInset()->edit(cur, true);
3518 LASSERT(cur.inMathed(), return);
3521 cur.selection(true);
3522 cur.pos() = cur.lastpos();
3523 if (cmd.action() != LFUN_MATH_MODE)
3524 // LFUN_MATH_MODE has a different meaning in math mode
3526 cur.clearSelection();
3527 cur.pos() = cur.lastpos();
3531 cur.message(from_utf8(N_("Math editor mode")));
3533 cur.message(from_utf8(N_("No valid math formula")));
3537 void regexpDispatch(Cursor & cur, FuncRequest const & cmd)
3539 LASSERT(cmd.action() == LFUN_REGEXP_MODE, return);
3540 if (cur.inRegexped()) {
3541 cur.message(_("Already in regular expression mode"));
3545 docstring sel = cur.selectionAsString(false);
3547 // It may happen that sel is empty but there is a selection
3548 replaceSelection(cur);
3550 cur.insert(new InsetMathHull(cur.buffer(), hullRegexp));
3551 cur.nextInset()->edit(cur, true);
3552 cur.niceInsert(sel);
3554 cur.message(_("Regexp editor mode"));
3558 void specialChar(Cursor & cur, InsetSpecialChar::Kind kind)
3561 cap::replaceSelection(cur);
3562 cur.insert(new InsetSpecialChar(kind));
3567 void ipaChar(Cursor & cur, InsetIPAChar::Kind kind)
3570 cap::replaceSelection(cur);
3571 cur.insert(new InsetIPAChar(kind));
3576 bool doInsertInset(Cursor & cur, Text * text,
3577 FuncRequest const & cmd, bool edit,
3578 bool pastesel, bool resetfont = false)
3580 Buffer & buffer = cur.bv().buffer();
3581 BufferParams const & bparams = buffer.params();
3582 Inset * inset = createInset(&buffer, cmd);
3586 if (InsetCollapsible * ci = inset->asInsetCollapsible())
3587 ci->setButtonLabel();
3590 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3591 bool cotextinsert = false;
3592 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3593 Layout const & lay = cur.paragraph().layout();
3594 Layout::LaTeXArgMap args = lay.args();
3595 Layout::LaTeXArgMap::const_iterator const lait = args.find(ia->name());
3596 if (lait != args.end())
3597 cotextinsert = (*lait).second.insertcotext;
3599 InsetLayout const & il = cur.inset().getLayout();
3601 Layout::LaTeXArgMap::const_iterator const ilait = args.find(ia->name());
3602 if (ilait != args.end())
3603 cotextinsert = (*ilait).second.insertcotext;
3605 // The argument requests to insert a copy of the co-text to the inset
3608 // If we have a selection within a paragraph, use this
3609 if (cur.selection() && cur.selBegin().pit() == cur.selEnd().pit())
3610 ds = cur.selectionAsString(false);
3611 // else use the whole paragraph
3613 ds = cur.paragraph().asString();
3614 text->insertInset(cur, inset);
3615 ia->init(cur.paragraph());
3617 inset->edit(cur, true);
3618 // Now put co-text into inset
3619 Font const f(inherit_font, cur.current_font.language());
3621 cur.text()->insertStringAsLines(cur, ds, f);
3622 cur.leaveInset(*inset);
3628 bool gotsel = false;
3629 bool move_layout = false;
3630 if (cur.selection()) {
3631 if (cmd.action() == LFUN_INDEX_INSERT)
3632 copySelectionToTemp(cur);
3634 cutSelectionToTemp(cur, pastesel);
3635 /* Move layout information inside the inset if the whole
3636 * paragraph and the inset allows setting layout
3637 * FIXME: this does not work as expected when change tracking is on
3638 * However, we do not really know what to do in this case.
3639 * FIXME: figure out a good test in the environment case (see #12251).
3641 if (cur.paragraph().layout().isCommand()
3642 && cur.paragraph().empty()
3643 && !inset->forcePlainLayout()) {
3644 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3648 cur.clearSelection();
3650 } else if (cmd.action() == LFUN_INDEX_INSERT) {
3651 gotsel = text->selectWordWhenUnderCursor(cur, WHOLE_WORD);
3652 copySelectionToTemp(cur);
3653 cur.clearSelection();
3655 text->insertInset(cur, inset);
3657 InsetText * inset_text = inset->asInsetText();
3659 Font const & font = inset->inheritFont()
3660 ? cur.bv().textMetrics(text).displayFont(cur.pit(), cur.pos())
3661 : bparams.getFont();
3662 inset_text->setOuterFont(cur.bv(), font.fontInfo());
3665 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3666 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3667 ia->init(cur.paragraph());
3671 inset->edit(cur, true);
3673 if (!gotsel || !pastesel)
3676 pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
3677 cur.buffer()->errors("Paste");
3678 cur.clearSelection(); // bug 393
3682 // Reset of font (not language) is requested.
3683 // Used by InsetIndex (#11961).
3684 Language const * lang = cur.getFont().language();
3685 Font font(bparams.getFont().fontInfo(), lang);
3686 cur.paragraph().resetFonts(font);
3688 inset_text->fixParagraphsFont();
3691 /* If the containing paragraph has kept its layout, reset the
3692 * layout of the first paragraph of the inset.
3695 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3696 // FIXME: what does this do?
3697 if (cmd.action() == LFUN_FLEX_INSERT)
3700 cur.leaveInset(*inset);
3701 if (cmd.action() == LFUN_PREVIEW_INSERT
3702 || cmd.action() == LFUN_IPA_INSERT)
3704 notifyCursorLeavesOrEnters(old, cur);
3706 cur.leaveInset(*inset);
3707 // reset surrounding par to default
3708 DocumentClass const & dc = bparams.documentClass();
3709 docstring const layoutname = inset->usePlainLayout()
3710 ? dc.plainLayoutName()
3711 : dc.defaultLayoutName();
3712 text->setLayout(cur, layoutname);
3718 /// the type of outline operation
3720 OutlineUp, // Move this header with text down
3721 OutlineDown, // Move this header with text up
3722 OutlineIn, // Make this header deeper
3723 OutlineOut // Make this header shallower
3727 void insertSeparator(Cursor const & cur, depth_type const depth)
3729 Buffer & buf = *cur.buffer();
3730 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
3731 DocumentClass const & tc = buf.params().documentClass();
3732 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
3733 + from_ascii("\" ignoreautonests")));
3734 // FIXME: Bibitem mess!
3735 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
3736 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
3737 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
3738 while (cur.paragraph().params().depth() > depth)
3739 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
3743 void outline(OutlineOp mode, Cursor & cur, bool local)
3745 Buffer & buf = *cur.buffer();
3746 Text & text = *cur.text();
3747 pit_type & pit = cur.pit();
3748 ParagraphList & pars = text.paragraphs();
3749 ParagraphList::iterator const bgn = pars.begin();
3750 // The first paragraph of the area to be copied:
3751 ParagraphList::iterator start = pars.iterator_at(pit);
3752 // The final paragraph of area to be copied:
3753 ParagraphList::iterator finish = start;
3754 ParagraphList::iterator const end = pars.end();
3755 depth_type const current_depth = cur.paragraph().params().depth();
3757 int const thistoclevel = text.getTocLevel(distance(bgn, start));
3760 // Move out (down) from this section header
3764 if (!local || (mode != OutlineIn && mode != OutlineOut)) {
3765 // Seek the one (on same level) below
3766 for (; finish != end; ++finish) {
3767 toclevel = text.getTocLevel(distance(bgn, finish));
3768 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3775 if (start == pars.begin())
3778 ParagraphList::iterator dest = start;
3779 // Move out (up) from this header
3782 // Search previous same-level header above
3785 toclevel = text.getTocLevel(distance(bgn, dest));
3787 && (toclevel == Layout::NOT_IN_TOC
3788 || toclevel > thistoclevel));
3789 // Not found; do nothing
3790 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3792 pit_type newpit = distance(bgn, dest);
3793 pit_type const len = distance(start, finish);
3794 pit_type const deletepit = pit + len;
3795 buf.undo().recordUndo(cur, newpit, deletepit - 1);
3796 // If we move an environment upwards, make sure it is
3797 // separated from its new neighbour below:
3798 // If an environment of the same layout follows, and the moved
3799 // paragraph sequence does not end with a separator, insert one.
3800 ParagraphList::iterator lastmoved = finish;
3802 if (start->layout().isEnvironment()
3803 && dest->layout() == start->layout()
3804 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3805 cur.pit() = distance(bgn, lastmoved);
3806 cur.pos() = cur.lastpos();
3807 insertSeparator(cur, current_depth);
3810 // Likewise, if we moved an environment upwards, make sure it
3811 // is separated from its new neighbour above.
3812 // The paragraph before the target of movement
3814 ParagraphList::iterator before = dest;
3816 // Get the parent paragraph (outer in nested context)
3817 pit_type const parent =
3818 before->params().depth() > current_depth
3819 ? text.depthHook(distance(bgn, before), current_depth)
3820 : distance(bgn, before);
3821 // If a environment with same layout preceeds the moved one in the new
3822 // position, and there is no separator yet, insert one.
3823 if (start->layout().isEnvironment()
3824 && pars[parent].layout() == start->layout()
3825 && !before->isEnvSeparator(before->beginOfBody())) {
3826 cur.pit() = distance(bgn, before);
3827 cur.pos() = cur.lastpos();
3828 insertSeparator(cur, current_depth);
3832 newpit = distance(bgn, dest);
3833 pars.splice(dest, start, finish);
3841 // Go one down from *this* header:
3842 ParagraphList::iterator dest = next(finish, 1);
3843 // Go further down to find header to insert in front of:
3844 for (; dest != end; ++dest) {
3845 toclevel = text.getTocLevel(distance(bgn, dest));
3846 if (toclevel != Layout::NOT_IN_TOC
3847 && toclevel <= thistoclevel)
3850 // One such was found, so go on...
3851 // If we move an environment downwards, make sure it is
3852 // separated from its new neighbour above.
3853 pit_type newpit = distance(bgn, dest);
3854 buf.undo().recordUndo(cur, pit, newpit - 1);
3855 // The paragraph before the target of movement
3856 ParagraphList::iterator before = dest;
3858 // Get the parent paragraph (outer in nested context)
3859 pit_type const parent =
3860 before->params().depth() > current_depth
3861 ? text.depthHook(distance(bgn, before), current_depth)
3862 : distance(bgn, before);
3863 // If a environment with same layout preceeds the moved one in the new
3864 // position, and there is no separator yet, insert one.
3865 if (start->layout().isEnvironment()
3866 && pars[parent].layout() == start->layout()
3867 && !before->isEnvSeparator(before->beginOfBody())) {
3868 cur.pit() = distance(bgn, before);
3869 cur.pos() = cur.lastpos();
3870 insertSeparator(cur, current_depth);
3873 // Likewise, make sure moved environments are separated
3874 // from their new neighbour below:
3875 // If an environment of the same layout follows, and the moved
3876 // paragraph sequence does not end with a separator, insert one.
3877 ParagraphList::iterator lastmoved = finish;
3880 && start->layout().isEnvironment()
3881 && dest->layout() == start->layout()
3882 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3883 cur.pit() = distance(bgn, lastmoved);
3884 cur.pos() = cur.lastpos();
3885 insertSeparator(cur, current_depth);
3888 newpit = distance(bgn, dest);
3889 pit_type const len = distance(start, finish);
3890 pars.splice(dest, start, finish);
3891 cur.pit() = newpit - len;
3896 // We first iterate without actually doing something
3897 // in order to check whether the action flattens the structure.
3898 // If so, warn (#11178).
3899 ParagraphList::iterator cstart = start;
3900 bool strucchange = false;
3901 for (; cstart != finish; ++cstart) {
3902 toclevel = text.getTocLevel(distance(bgn, cstart));
3903 if (toclevel == Layout::NOT_IN_TOC)
3906 DocumentClass const & tc = buf.params().documentClass();
3907 int const newtoclevel =
3908 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3911 for (auto const & lay : tc) {
3912 if (lay.toclevel == newtoclevel
3913 && lay.isNumHeadingLabelType()
3914 && cstart->layout().isNumHeadingLabelType()) {
3925 && frontend::Alert::prompt(_("Action flattens document structure"),
3926 _("This action will cause some headings that have been "
3927 "on different level before to be on the same level "
3928 "since there is no more lower or higher heading level. "
3931 _("&Yes, continue nonetheless"),
3932 _("&No, quit operation")) == 1)
3935 pit_type const len = distance(start, finish);
3936 buf.undo().recordUndo(cur, pit, pit + len - 1);
3937 for (; start != finish; ++start) {
3938 toclevel = text.getTocLevel(distance(bgn, start));
3939 if (toclevel == Layout::NOT_IN_TOC)
3942 DocumentClass const & tc = buf.params().documentClass();
3943 int const newtoclevel =
3944 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3946 for (auto const & lay : tc) {
3947 if (lay.toclevel == newtoclevel
3948 && lay.isNumHeadingLabelType()
3949 && start->layout().isNumHeadingLabelType()) {
3950 start->setLayout(lay);
3964 void Text::number(Cursor & cur)
3966 FontInfo font = ignore_font;
3967 font.setNumber(FONT_TOGGLE);
3968 toggleAndShow(cur, this, Font(font, ignore_language));
3972 bool Text::isRTL(pit_type const pit) const
3974 Buffer const & buffer = owner_->buffer();
3975 return pars_[pit].isRTL(buffer.params());
3981 Language const * getLanguage(Cursor const & cur, string const & lang)
3983 return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
3987 docstring resolveLayout(docstring layout, DocIterator const & dit)
3989 Paragraph const & par = dit.paragraph();
3990 DocumentClass const & tclass = dit.buffer()->params().documentClass();
3993 layout = tclass.defaultLayoutName();
3995 if (dit.inset().forcePlainLayout(dit.idx()))
3996 // in this case only the empty layout is allowed
3997 layout = tclass.plainLayoutName();
3998 else if (par.usePlainLayout()) {
3999 // in this case, default layout maps to empty layout
4000 if (layout == tclass.defaultLayoutName())
4001 layout = tclass.plainLayoutName();
4003 // otherwise, the empty layout maps to the default
4004 if (layout == tclass.plainLayoutName())
4005 layout = tclass.defaultLayoutName();
4008 // If the entry is obsolete, use the new one instead.
4009 if (tclass.hasLayout(layout)) {
4010 docstring const & obs = tclass[layout].obsoleted_by();
4014 if (!tclass.hasLayout(layout))
4020 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4022 ParagraphList const & pars = cur.text()->paragraphs();
4024 pit_type pit = cur.selBegin().pit();
4025 pit_type const epit = cur.selEnd().pit() + 1;
4026 for ( ; pit != epit; ++pit)
4027 if (pars[pit].layout().name() != layout)
4037 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4039 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4041 // Dispatch if the cursor is inside the text. It is not the
4042 // case for context menus (bug 5797).
4043 if (cur.text() != this) {
4048 BufferView * bv = &cur.bv();
4049 TextMetrics * tm = &bv->textMetrics(this);
4050 if (!tm->contains(cur.pit())) {
4051 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4052 tm = &bv->textMetrics(this);
4055 // FIXME: We use the update flag to indicates wether a singlePar or a
4056 // full screen update is needed. We reset it here but shall we restore it
4058 cur.noScreenUpdate();
4060 LBUFERR(this == cur.text());
4062 // NOTE: This should NOT be a reference. See commit 94a5481a.
4063 CursorSlice const oldTopSlice = cur.top();
4064 bool const oldBoundary = cur.boundary();
4065 bool const oldSelection = cur.selection();
4066 // Signals that, even if needsUpdate == false, an update of the
4067 // cursor paragraph is required
4068 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4069 LyXAction::SingleParUpdate);
4070 // Signals that a full-screen update is required
4071 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4072 LyXAction::NoUpdate) || singleParUpdate);
4073 bool const last_misspelled = lyxrc.spellcheck_continuously
4074 && cur.paragraph().isMisspelled(cur.pos(), true);
4076 FuncCode const act = cmd.action();
4079 case LFUN_PARAGRAPH_MOVE_DOWN: {
4080 pit_type const pit = cur.pit();
4081 cur.recordUndo(pit, pit + 1);
4082 pars_.swap(pit, pit + 1);
4084 cur.forceBufferUpdate();
4089 case LFUN_PARAGRAPH_MOVE_UP: {
4090 pit_type const pit = cur.pit();
4091 cur.recordUndo(pit - 1, pit);
4093 pars_.swap(pit, pit - 1);
4096 cur.forceBufferUpdate();
4100 case LFUN_APPENDIX: {
4101 Paragraph & par = cur.paragraph();
4102 bool start = !par.params().startOfAppendix();
4104 // FIXME: The code below only makes sense at top level.
4105 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4106 // ensure that we have only one start_of_appendix in this document
4107 // FIXME: this don't work for multipart document!
4108 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4109 if (pars_[tmp].params().startOfAppendix()) {
4110 cur.recordUndo(tmp, tmp);
4111 pars_[tmp].params().startOfAppendix(false);
4117 par.params().startOfAppendix(start);
4119 // we can set the refreshing parameters now
4120 cur.forceBufferUpdate();
4124 case LFUN_WORD_DELETE_FORWARD:
4125 if (cur.selection())
4126 cutSelection(cur, false);
4128 deleteWordForward(cur, cmd.getArg(0) != "confirm");
4129 finishChange(cur, false);
4132 case LFUN_WORD_DELETE_BACKWARD:
4133 if (cur.selection())
4134 cutSelection(cur, false);
4136 deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4137 finishChange(cur, false);
4140 case LFUN_LINE_DELETE_FORWARD:
4141 if (cur.selection())
4142 cutSelection(cur, false);
4144 tm->deleteLineForward(cur);
4145 finishChange(cur, false);
4148 case LFUN_BUFFER_BEGIN:
4149 case LFUN_BUFFER_BEGIN_SELECT:
4150 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4151 if (cur.depth() == 1)
4152 needsUpdate |= cursorTop(cur);
4155 cur.screenUpdateFlags(Update::FitCursor);
4158 case LFUN_BUFFER_END:
4159 case LFUN_BUFFER_END_SELECT:
4160 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4161 if (cur.depth() == 1)
4162 needsUpdate |= cursorBottom(cur);
4165 cur.screenUpdateFlags(Update::FitCursor);
4168 case LFUN_INSET_BEGIN:
4169 case LFUN_INSET_BEGIN_SELECT:
4170 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4171 if (cur.depth() == 1 || !cur.top().at_begin())
4172 needsUpdate |= cursorTop(cur);
4175 cur.screenUpdateFlags(Update::FitCursor);
4178 case LFUN_INSET_END:
4179 case LFUN_INSET_END_SELECT:
4180 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4181 if (cur.depth() == 1 || !cur.top().at_end())
4182 needsUpdate |= cursorBottom(cur);
4185 cur.screenUpdateFlags(Update::FitCursor);
4188 case LFUN_CHAR_FORWARD:
4189 case LFUN_CHAR_FORWARD_SELECT: {
4190 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4191 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4192 bool const cur_moved = cursorForward(cur);
4193 needsUpdate |= cur_moved;
4195 if (!cur_moved && cur.depth() > 1
4196 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4198 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4200 // we will be moving out the inset, so we should execute
4201 // the depm-mechanism.
4202 // The cursor hasn't changed yet. To give the DEPM the
4203 // possibility of doing something we must provide it with
4204 // two different cursors.
4206 dummy.pos() = dummy.pit() = 0;
4207 if (cur.bv().checkDepm(dummy, cur))
4208 cur.forceBufferUpdate();
4213 case LFUN_CHAR_BACKWARD:
4214 case LFUN_CHAR_BACKWARD_SELECT: {
4215 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4216 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4217 bool const cur_moved = cursorBackward(cur);
4218 needsUpdate |= cur_moved;
4220 if (!cur_moved && cur.depth() > 1
4221 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4223 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4225 // we will be moving out the inset, so we should execute
4226 // the depm-mechanism.
4227 // The cursor hasn't changed yet. To give the DEPM the
4228 // possibility of doing something we must provide it with
4229 // two different cursors.
4231 dummy.pos() = cur.lastpos();
4232 dummy.pit() = cur.lastpit();
4233 if (cur.bv().checkDepm(dummy, cur))
4234 cur.forceBufferUpdate();
4239 case LFUN_CHAR_LEFT:
4240 case LFUN_CHAR_LEFT_SELECT:
4241 if (lyxrc.visual_cursor) {
4242 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4243 bool const cur_moved = cursorVisLeft(cur);
4244 needsUpdate |= cur_moved;
4245 if (!cur_moved && cur.depth() > 1
4246 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4248 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4251 if (cur.reverseDirectionNeeded()) {
4252 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4253 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4255 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4256 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4263 case LFUN_CHAR_RIGHT:
4264 case LFUN_CHAR_RIGHT_SELECT:
4265 if (lyxrc.visual_cursor) {
4266 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4267 bool const cur_moved = cursorVisRight(cur);
4268 needsUpdate |= cur_moved;
4269 if (!cur_moved && cur.depth() > 1
4270 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4272 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4275 if (cur.reverseDirectionNeeded()) {
4276 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4277 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4279 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4280 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4288 case LFUN_UP_SELECT:
4289 case LFUN_DOWN_SELECT:
4292 // stop/start the selection
4293 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4294 cmd.action() == LFUN_UP_SELECT;
4296 // move cursor up/down
4297 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4298 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4300 if (!atFirstOrLastRow) {
4301 needsUpdate |= cur.selHandle(select);
4302 cur.upDownInText(up, needsUpdate);
4303 needsUpdate |= cur.beforeDispatchCursor().inMathed();
4305 pos_type newpos = up ? 0 : cur.lastpos();
4306 if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4307 needsUpdate |= cur.selHandle(select);
4308 // we do not reset the targetx of the cursor
4310 needsUpdate |= bv->checkDepm(cur, bv->cursor());
4311 cur.updateTextTargetOffset();
4313 cur.forceBufferUpdate();
4317 // if the cursor cannot be moved up or down do not remove
4318 // the selection right now, but wait for the next dispatch.
4320 needsUpdate |= cur.selHandle(select);
4321 cur.upDownInText(up, needsUpdate);
4328 case LFUN_PARAGRAPH_SELECT:
4330 needsUpdate |= setCursor(cur, cur.pit(), 0);
4331 needsUpdate |= cur.selHandle(true);
4332 if (cur.pos() < cur.lastpos())
4333 needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4336 case LFUN_PARAGRAPH_UP:
4337 case LFUN_PARAGRAPH_UP_SELECT:
4338 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4339 needsUpdate |= cursorUpParagraph(cur);
4342 case LFUN_PARAGRAPH_DOWN:
4343 case LFUN_PARAGRAPH_DOWN_SELECT:
4344 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4345 needsUpdate |= cursorDownParagraph(cur);
4348 case LFUN_LINE_BEGIN:
4349 case LFUN_LINE_BEGIN_SELECT:
4350 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4351 needsUpdate |= tm->cursorHome(cur);
4355 case LFUN_LINE_END_SELECT:
4356 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4357 needsUpdate |= tm->cursorEnd(cur);
4360 case LFUN_SECTION_SELECT: {
4361 Buffer const & buf = *cur.buffer();
4362 pit_type const pit = cur.pit();
4363 ParagraphList & pars = buf.text().paragraphs();
4364 ParagraphList::iterator bgn = pars.begin();
4365 // The first paragraph of the area to be selected:
4366 ParagraphList::iterator start = pars.iterator_at(pit);
4367 // The final paragraph of area to be selected:
4368 ParagraphList::iterator finish = start;
4369 ParagraphList::iterator end = pars.end();
4371 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4372 if (thistoclevel == Layout::NOT_IN_TOC)
4376 Cursor const old_cur = cur;
4377 needsUpdate |= cur.selHandle(true);
4379 // Move out (down) from this section header
4383 // Seek the one (on same level) below
4384 for (; finish != end; ++finish, ++cur.pit()) {
4385 int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4386 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4389 cur.pos() = cur.lastpos();
4390 cur.boundary(false);
4391 cur.setCurrentFont();
4393 needsUpdate |= cur != old_cur;
4397 case LFUN_WORD_RIGHT:
4398 case LFUN_WORD_RIGHT_SELECT:
4399 if (lyxrc.visual_cursor) {
4400 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4401 bool const cur_moved = cursorVisRightOneWord(cur);
4402 needsUpdate |= cur_moved;
4403 if (!cur_moved && cur.depth() > 1
4404 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4406 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4409 if (cur.reverseDirectionNeeded()) {
4410 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4411 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4413 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4414 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4421 case LFUN_WORD_FORWARD:
4422 case LFUN_WORD_FORWARD_SELECT: {
4423 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4424 bool const cur_moved = cursorForwardOneWord(cur);
4425 needsUpdate |= cur_moved;
4427 if (!cur_moved && cur.depth() > 1
4428 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4430 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4432 // we will be moving out the inset, so we should execute
4433 // the depm-mechanism.
4434 // The cursor hasn't changed yet. To give the DEPM the
4435 // possibility of doing something we must provide it with
4436 // two different cursors.
4438 dummy.pos() = dummy.pit() = 0;
4439 if (cur.bv().checkDepm(dummy, cur))
4440 cur.forceBufferUpdate();
4445 case LFUN_WORD_LEFT:
4446 case LFUN_WORD_LEFT_SELECT:
4447 if (lyxrc.visual_cursor) {
4448 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4449 bool const cur_moved = cursorVisLeftOneWord(cur);
4450 needsUpdate |= cur_moved;
4451 if (!cur_moved && cur.depth() > 1
4452 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4454 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4457 if (cur.reverseDirectionNeeded()) {
4458 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4459 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4461 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4462 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4469 case LFUN_WORD_BACKWARD:
4470 case LFUN_WORD_BACKWARD_SELECT: {
4471 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4472 bool const cur_moved = cursorBackwardOneWord(cur);
4473 needsUpdate |= cur_moved;
4475 if (!cur_moved && cur.depth() > 1
4476 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4478 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4480 // we will be moving out the inset, so we should execute
4481 // the depm-mechanism.
4482 // The cursor hasn't changed yet. To give the DEPM the
4483 // possibility of doing something we must provide it with
4484 // two different cursors.
4486 dummy.pos() = cur.lastpos();
4487 dummy.pit() = cur.lastpit();
4488 if (cur.bv().checkDepm(dummy, cur))
4489 cur.forceBufferUpdate();
4494 case LFUN_WORD_SELECT: {
4495 selectWord(cur, WHOLE_WORD);
4496 finishChange(cur, true);
4500 case LFUN_NEWLINE_INSERT: {
4501 InsetNewlineParams inp;
4502 docstring const & arg = cmd.argument();
4503 if (arg == "linebreak")
4504 inp.kind = InsetNewlineParams::LINEBREAK;
4506 inp.kind = InsetNewlineParams::NEWLINE;
4507 cap::replaceSelection(cur);
4509 cur.insert(new InsetNewline(inp));
4511 moveCursor(cur, false);
4515 case LFUN_TAB_INSERT: {
4516 bool const multi_par_selection = cur.selection() &&
4517 cur.selBegin().pit() != cur.selEnd().pit();
4518 if (multi_par_selection) {
4519 // If there is a multi-paragraph selection, a tab is inserted
4520 // at the beginning of each paragraph.
4521 cur.recordUndoSelection();
4522 pit_type const pit_end = cur.selEnd().pit();
4523 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4524 pars_[pit].insertChar(0, '\t',
4525 bv->buffer().params().track_changes);
4526 // Update the selection pos to make sure the selection does not
4527 // change as the inserted tab will increase the logical pos.
4528 if (cur.realAnchor().pit() == pit)
4529 cur.realAnchor().forwardPos();
4530 if (cur.pit() == pit)
4535 // Maybe we shouldn't allow tabs within a line, because they
4536 // are not (yet) aligned as one might do expect.
4537 FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4538 dispatch(cur, ncmd);
4543 case LFUN_TAB_DELETE: {
4544 bool const tc = bv->buffer().params().track_changes;
4545 if (cur.selection()) {
4546 // If there is a selection, a tab (if present) is removed from
4547 // the beginning of each paragraph.
4548 cur.recordUndoSelection();
4549 pit_type const pit_end = cur.selEnd().pit();
4550 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4551 Paragraph & par = paragraphs()[pit];
4554 char_type const c = par.getChar(0);
4555 if (c == '\t' || c == ' ') {
4556 // remove either 1 tab or 4 spaces.
4557 int const n = (c == ' ' ? 4 : 1);
4558 for (int i = 0; i < n
4559 && !par.empty() && par.getChar(0) == c; ++i) {
4560 if (cur.pit() == pit)
4562 if (cur.realAnchor().pit() == pit
4563 && cur.realAnchor().pos() > 0 )
4564 cur.realAnchor().backwardPos();
4565 par.eraseChar(0, tc);
4571 // If there is no selection, try to remove a tab or some spaces
4572 // before the position of the cursor.
4573 Paragraph & par = paragraphs()[cur.pit()];
4574 pos_type const pos = cur.pos();
4579 char_type const c = par.getChar(pos - 1);
4583 par.eraseChar(cur.pos(), tc);
4585 for (int n_spaces = 0;
4587 && par.getChar(cur.pos() - 1) == ' '
4591 par.eraseChar(cur.pos(), tc);
4598 case LFUN_CHAR_DELETE_FORWARD:
4599 if (!cur.selection()) {
4600 if (cur.pos() == cur.paragraph().size())
4601 // Par boundary, force full-screen update
4602 singleParUpdate = false;
4603 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4605 cur.selection(true);
4610 needsUpdate |= erase(cur);
4613 cutSelection(cur, false);
4614 cur.setCurrentFont();
4615 singleParUpdate = false;
4617 moveCursor(cur, false);
4620 case LFUN_CHAR_DELETE_BACKWARD:
4621 if (!cur.selection()) {
4622 if (bv->getIntl().getTransManager().backspace()) {
4623 bool par_boundary = cur.pos() == 0;
4624 bool first_par = cur.pit() == 0;
4625 // Par boundary, full-screen update
4627 singleParUpdate = false;
4628 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4630 cur.selection(true);
4635 needsUpdate |= backspace(cur);
4637 if (par_boundary && !first_par && cur.pos() > 0
4638 && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4639 needsUpdate |= backspace(cur);
4644 DocIterator const dit = cur.selectionBegin();
4645 cutSelection(cur, false);
4646 if (cur.buffer()->params().track_changes)
4647 // since we're doing backwards deletion,
4648 // and the selection is not really cut,
4649 // move cursor before selection (#11630)
4651 cur.setCurrentFont();
4652 singleParUpdate = false;
4656 case LFUN_PARAGRAPH_BREAK: {
4657 cap::replaceSelection(cur);
4658 pit_type pit = cur.pit();
4659 Paragraph const & par = pars_[pit];
4660 bool lastpar = (pit == pit_type(pars_.size() - 1));
4661 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4662 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4663 if (prev < pit && cur.pos() == par.beginOfBody()
4664 && par.empty() && !par.isEnvSeparator(cur.pos())
4665 && !par.layout().keepempty
4666 && !par.layout().isCommand()
4667 && pars_[prev].layout() != par.layout()
4668 && pars_[prev].layout().isEnvironment()
4669 && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4670 if (par.layout().isEnvironment()
4671 && pars_[prev].getDepth() == par.getDepth()) {
4672 docstring const layout = par.layout().name();
4673 DocumentClass const & tc = bv->buffer().params().documentClass();
4674 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4675 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4676 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4677 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4679 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4680 breakParagraph(cur);
4682 Font const f(inherit_font, cur.current_font.language());
4683 pars_[cur.pit() - 1].resetFonts(f);
4685 if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4687 breakParagraph(cur, cmd.getArg(0) == "inverse");
4690 // If we have a list and autoinsert item insets,
4692 Layout::LaTeXArgMap args = par.layout().args();
4693 for (auto const & thearg : args) {
4694 Layout::latexarg arg = thearg.second;
4695 if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4696 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4697 lyx::dispatch(cmd2);
4703 case LFUN_INSET_INSERT: {
4706 // We have to avoid triggering InstantPreview loading
4707 // before inserting into the document. See bug #5626.
4708 bool loaded = bv->buffer().isFullyLoaded();
4709 bv->buffer().setFullyLoaded(false);
4710 Inset * inset = createInset(&bv->buffer(), cmd);
4711 bv->buffer().setFullyLoaded(loaded);
4714 // FIXME (Abdel 01/02/2006):
4715 // What follows would be a partial fix for bug 2154:
4716 // http://www.lyx.org/trac/ticket/2154
4717 // This automatically put the label inset _after_ a
4718 // numbered section. It should be possible to extend the mechanism
4719 // to any kind of LateX environement.
4720 // The correct way to fix that bug would be at LateX generation.
4721 // I'll let the code here for reference as it could be used for some
4722 // other feature like "automatic labelling".
4724 Paragraph & par = pars_[cur.pit()];
4725 if (inset->lyxCode() == LABEL_CODE
4726 && !par.layout().counter.empty()) {
4727 // Go to the end of the paragraph
4728 // Warning: Because of Change-Tracking, the last
4729 // position is 'size()' and not 'size()-1':
4730 cur.pos() = par.size();
4731 // Insert a new paragraph
4732 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4736 if (cur.selection())
4737 cutSelection(cur, false);
4739 cur.forceBufferUpdate();
4740 if (inset->editable() && inset->asInsetText())
4741 inset->edit(cur, true);
4745 // trigger InstantPreview now
4746 if (inset->lyxCode() == EXTERNAL_CODE) {
4747 InsetExternal & ins =
4748 static_cast<InsetExternal &>(*inset);
4749 ins.updatePreview();
4756 case LFUN_INSET_DISSOLVE: {
4757 if (dissolveInset(cur)) {
4759 cur.forceBufferUpdate();
4764 case LFUN_INSET_SPLIT: {
4765 if (splitInset(cur)) {
4767 cur.forceBufferUpdate();
4772 case LFUN_GRAPHICS_SET_GROUP: {
4773 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4779 string id = to_utf8(cmd.argument());
4780 string grp = graphics::getGroupParams(bv->buffer(), id);
4781 InsetGraphicsParams tmp, inspar = ins->getParams();
4784 inspar.groupId = to_utf8(cmd.argument());
4786 InsetGraphics::string2params(grp, bv->buffer(), tmp);
4787 tmp.filename = inspar.filename;
4791 ins->setParams(inspar);
4795 case LFUN_SPACE_INSERT:
4796 if (cur.paragraph().layout().free_spacing)
4797 insertChar(cur, ' ');
4799 doInsertInset(cur, this, cmd, false, false);
4802 moveCursor(cur, false);
4805 case LFUN_SPECIALCHAR_INSERT: {
4806 string const name = to_utf8(cmd.argument());
4807 if (name == "hyphenation")
4808 specialChar(cur, InsetSpecialChar::HYPHENATION);
4809 else if (name == "allowbreak")
4810 specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4811 else if (name == "ligature-break")
4812 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4813 else if (name == "slash")
4814 specialChar(cur, InsetSpecialChar::SLASH);
4815 else if (name == "nobreakdash")
4816 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4817 else if (name == "dots")
4818 specialChar(cur, InsetSpecialChar::LDOTS);
4819 else if (name == "end-of-sentence")
4820 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4821 else if (name == "menu-separator")
4822 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4823 else if (name == "lyx")
4824 specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4825 else if (name == "tex")
4826 specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4827 else if (name == "latex")
4828 specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4829 else if (name == "latex2e")
4830 specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4831 else if (name.empty())
4832 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4834 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4838 case LFUN_IPAMACRO_INSERT: {
4839 string const arg = cmd.getArg(0);
4840 if (arg == "deco") {
4841 // Open the inset, and move the current selection
4843 doInsertInset(cur, this, cmd, true, true);
4845 // Some insets are numbered, others are shown in the outline pane so
4846 // let's update the labels and the toc backend.
4847 cur.forceBufferUpdate();
4850 if (arg == "tone-falling")
4851 ipaChar(cur, InsetIPAChar::TONE_FALLING);
4852 else if (arg == "tone-rising")
4853 ipaChar(cur, InsetIPAChar::TONE_RISING);
4854 else if (arg == "tone-high-rising")
4855 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4856 else if (arg == "tone-low-rising")
4857 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4858 else if (arg == "tone-high-rising-falling")
4859 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4860 else if (arg.empty())
4861 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4863 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4867 case LFUN_WORD_UPCASE:
4868 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4871 case LFUN_WORD_LOWCASE:
4872 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4875 case LFUN_WORD_CAPITALIZE:
4876 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4879 case LFUN_CHARS_TRANSPOSE:
4880 charsTranspose(cur);
4884 cur.message(_("Paste"));
4885 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4886 cap::replaceSelection(cur);
4888 // without argument?
4889 string const arg = to_utf8(cmd.argument());
4891 bool tryGraphics = true;
4892 if (theClipboard().isInternal())
4893 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4894 else if (theClipboard().hasTextContents()) {
4895 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"), 0,
4896 Clipboard::AnyTextType))
4897 tryGraphics = false;
4899 if (tryGraphics && theClipboard().hasGraphicsContents())
4900 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4901 } else if (isStrUnsignedInt(arg)) {
4902 // we have a numerical argument
4903 pasteFromStack(cur, bv->buffer().errorList("Paste"),
4904 convert<unsigned int>(arg));
4905 } else if (arg == "html" || arg == "latex") {
4906 Clipboard::TextType type = (arg == "html") ?
4907 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4908 pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4910 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4912 type = Clipboard::PdfGraphicsType;
4913 else if (arg == "png")
4914 type = Clipboard::PngGraphicsType;
4915 else if (arg == "jpeg")
4916 type = Clipboard::JpegGraphicsType;
4917 else if (arg == "linkback")
4918 type = Clipboard::LinkBackGraphicsType;
4919 else if (arg == "emf")
4920 type = Clipboard::EmfGraphicsType;
4921 else if (arg == "wmf")
4922 type = Clipboard::WmfGraphicsType;
4924 // we also check in getStatus()
4925 LYXERR0("Unrecognized graphics type: " << arg);
4927 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4930 bv->buffer().errors("Paste");
4931 bv->buffer().updatePreviews(); // bug 11619
4932 cur.clearSelection(); // bug 393
4938 cutSelection(cur, true);
4939 cur.message(_("Cut"));
4942 case LFUN_SERVER_GET_XY:
4943 cur.message(from_utf8(
4944 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4945 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4948 case LFUN_SERVER_SET_XY: {
4951 istringstream is(to_utf8(cmd.argument()));
4954 lyxerr << "SETXY: Could not parse coordinates in '"
4955 << to_utf8(cmd.argument()) << endl;
4957 tm->setCursorFromCoordinates(cur, x, y);
4961 case LFUN_SERVER_GET_LAYOUT:
4962 cur.message(cur.paragraph().layout().name());
4966 case LFUN_LAYOUT_TOGGLE: {
4967 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
4968 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
4969 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
4971 docstring layout = resolveLayout(req_layout, cur);
4972 if (layout.empty()) {
4973 cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
4974 from_utf8(N_(" not known")));
4978 docstring const old_layout = cur.paragraph().layout().name();
4979 bool change_layout = !isAlreadyLayout(layout, cur);
4981 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
4982 change_layout = true;
4983 layout = resolveLayout(docstring(), cur);
4986 if (change_layout) {
4987 setLayout(cur, layout);
4988 if (cur.pit() > 0 && !ignoreautonests) {
4989 pit_type prev_pit = cur.pit() - 1;
4990 depth_type const cur_depth = pars_[cur.pit()].getDepth();
4991 // Scan for the previous par on same nesting level
4992 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
4994 set<docstring> const & autonests =
4995 pars_[prev_pit].layout().autonests();
4996 set<docstring> const & autonested =
4997 pars_[cur.pit()].layout().isAutonestedBy();
4998 if (autonests.find(layout) != autonests.end()
4999 || autonested.find(old_layout) != autonested.end())
5000 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5004 DocumentClass const & tclass = bv->buffer().params().documentClass();
5005 bool inautoarg = false;
5006 for (auto const & la_pair : tclass[layout].args()) {
5007 Layout::latexarg const & arg = la_pair.second;
5008 if (arg.autoinsert) {
5009 // If we had already inserted an arg automatically,
5010 // leave this now in order to insert the next one.
5012 cur.leaveInset(cur.inset());
5015 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5016 lyx::dispatch(cmd2);
5024 case LFUN_ENVIRONMENT_SPLIT: {
5025 bool const outer = cmd.argument() == "outer";
5026 bool const previous = cmd.argument() == "previous";
5027 bool const before = cmd.argument() == "before";
5028 bool const normal = cmd.argument().empty();
5029 Paragraph const & para = cur.paragraph();
5031 if (para.layout().isEnvironment())
5032 layout = para.layout().name();
5033 depth_type split_depth = cur.paragraph().params().depth();
5034 vector<depth_type> nextpars_depth;
5035 if (outer || previous) {
5036 // check if we have an environment in our scope
5037 pit_type pit = cur.pit();
5038 Paragraph cpar = pars_[pit];
5044 if (layout.empty() && previous
5045 && cpar.layout().isEnvironment()
5046 && cpar.params().depth() <= split_depth)
5047 layout = cpar.layout().name();
5048 if (cpar.params().depth() < split_depth
5049 && cpar.layout().isEnvironment()) {
5051 layout = cpar.layout().name();
5052 split_depth = cpar.params().depth();
5054 if (cpar.params().depth() == 0)
5058 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5059 // save nesting of following paragraphs if they are deeper
5061 pit_type offset = 1;
5062 depth_type cur_depth = pars_[cur.pit()].params().depth();
5063 while (cur.pit() + offset <= cur.lastpit()) {
5064 Paragraph cpar = pars_[cur.pit() + offset];
5065 depth_type nextpar_depth = cpar.params().depth();
5066 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5067 nextpars_depth.push_back(nextpar_depth);
5068 cur_depth = nextpar_depth;
5075 cur.top().setPitPos(cur.pit(), 0);
5076 if (before || cur.pos() > 0)
5077 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5078 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5079 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5081 while (cur.paragraph().params().depth() > split_depth)
5082 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5084 DocumentClass const & tc = bv->buffer().params().documentClass();
5085 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5086 + from_ascii("\" ignoreautonests")));
5087 // FIXME: Bibitem mess!
5088 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5089 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5090 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5093 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5094 while (cur.paragraph().params().depth() < split_depth)
5095 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5098 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5099 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5100 if ((outer || normal) && !nextpars_depth.empty()) {
5101 // restore nesting of following paragraphs
5102 DocIterator scur = cur;
5103 depth_type max_depth = cur.paragraph().params().depth() + 1;
5104 for (auto nextpar_depth : nextpars_depth) {
5106 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5107 depth_type const olddepth = cur.paragraph().params().depth();
5108 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5109 if (olddepth == cur.paragraph().params().depth())
5110 // leave loop if no incrementation happens
5113 max_depth = cur.paragraph().params().depth() + 1;
5115 cur.setCursor(scur);
5121 case LFUN_CLIPBOARD_PASTE:
5122 cap::replaceSelection(cur);
5123 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5124 cmd.argument() == "paragraph");
5125 bv->buffer().errors("Paste");
5128 case LFUN_CLIPBOARD_PASTE_SIMPLE:
5129 cap::replaceSelection(cur);
5130 pasteSimpleText(cur, cmd.argument() == "paragraph");
5133 case LFUN_PRIMARY_SELECTION_PASTE:
5134 cap::replaceSelection(cur);
5135 pasteString(cur, theSelection().get(),
5136 cmd.argument() == "paragraph");
5139 case LFUN_SELECTION_PASTE:
5140 // Copy the selection buffer to the clipboard stack,
5141 // because we want it to appear in the "Edit->Paste
5143 cap::replaceSelection(cur);
5144 cap::copySelectionToStack();
5145 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5146 bv->buffer().errors("Paste");
5149 case LFUN_QUOTE_INSERT: {
5150 cap::replaceSelection(cur);
5153 Paragraph const & par = cur.paragraph();
5154 pos_type pos = cur.pos();
5155 // Ignore deleted text before cursor
5156 while (pos > 0 && par.isDeleted(pos - 1))
5159 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5161 // Guess quote side.
5162 // A space triggers an opening quote. This is passed if the preceding
5163 // char/inset is a space or at paragraph start.
5165 if (pos > 0 && !par.isSpace(pos - 1)) {
5166 if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5167 // If an opening double quotation mark precedes, and this
5168 // is a single quote, make it opening as well
5170 static_cast<InsetQuotes &>(*cur.prevInset());
5171 string const type = ins.getType();
5172 if (!suffixIs(type, "ld") || !inner)
5173 c = par.getChar(pos - 1);
5175 else if (!cur.prevInset()
5176 || (cur.prevInset() && cur.prevInset()->isChar()))
5177 // If a char precedes, pass that and let InsetQuote decide
5178 c = par.getChar(pos - 1);
5181 if (par.getInset(pos - 1)
5182 && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5183 // skip "invisible" insets
5187 c = par.getChar(pos - 1);
5192 QuoteLevel const quote_level = inner
5193 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5194 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5195 cur.buffer()->updateBuffer();
5200 case LFUN_MOUSE_TRIPLE:
5201 if (cmd.button() == mouse_button::button1) {
5203 setCursor(cur, cur.pit(), 0);
5206 if (cur.pos() < cur.lastpos())
5207 setCursor(cur, cur.pit(), cur.lastpos());
5213 case LFUN_MOUSE_DOUBLE:
5214 if (cmd.button() == mouse_button::button1) {
5215 selectWord(cur, WHOLE_WORD);
5220 // Single-click on work area
5221 case LFUN_MOUSE_PRESS: {
5222 // We are not marking a selection with the keyboard in any case.
5223 Cursor & bvcur = cur.bv().cursor();
5224 bvcur.setMark(false);
5225 switch (cmd.button()) {
5226 case mouse_button::button1:
5227 bvcur.setClickPos(cmd.x(), cmd.y());
5228 if (!bvcur.selection())
5230 bvcur.resetAnchor();
5231 if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5232 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5233 // FIXME: move this to mouseSetCursor?
5234 if (bvcur.wordSelection() && bvcur.inTexted())
5235 expandWordSel(bvcur);
5238 case mouse_button::button2:
5239 if (lyxrc.mouse_middlebutton_paste) {
5240 // Middle mouse pasting.
5241 bv->mouseSetCursor(cur);
5243 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5244 "selection-paste ; primary-selection-paste paragraph"));
5246 cur.noScreenUpdate();
5249 case mouse_button::button3: {
5250 // Don't do anything if we right-click a
5251 // selection, a context menu will popup.
5252 if (bvcur.selection() && cur >= bvcur.selectionBegin()
5253 && cur <= bvcur.selectionEnd()) {
5254 cur.noScreenUpdate();
5257 if (!bv->mouseSetCursor(cur, false))
5258 cur.screenUpdateFlags(Update::FitCursor);
5264 } // switch (cmd.button())
5267 case LFUN_MOUSE_MOTION: {
5268 // Mouse motion with right or middle mouse do nothing for now.
5269 if (cmd.button() != mouse_button::button1) {
5270 cur.noScreenUpdate();
5273 // ignore motions deeper nested than the real anchor
5274 Cursor & bvcur = cur.bv().cursor();
5275 if (!bvcur.realAnchor().hasPart(cur)) {
5279 CursorSlice old = bvcur.top();
5281 int const wh = bv->workHeight();
5282 int const y = max(0, min(wh - 1, cmd.y()));
5284 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5285 cur.setTargetX(cmd.x());
5286 // Don't allow selecting a separator inset
5287 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5290 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5291 else if (cmd.y() < 0)
5292 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5293 // This is to allow jumping over large insets
5294 if (cur.top() == old) {
5296 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5297 else if (cmd.y() < 0)
5298 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5300 // We continue with our existing selection or start a new one, so don't
5301 // reset the anchor.
5302 bvcur.setCursor(cur);
5303 if (bvcur.wordSelection() && bvcur.inTexted())
5304 expandWordSel(bvcur);
5305 bvcur.selection(true);
5306 bvcur.setCurrentFont();
5307 if (cur.top() == old) {
5308 // We didn't move one iota, so no need to update the screen.
5309 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5310 //cur.noScreenUpdate();
5316 case LFUN_MOUSE_RELEASE:
5317 switch (cmd.button()) {
5318 case mouse_button::button1:
5319 // unregister last mouse press position
5320 cur.bv().cursor().setClickPos(-1, -1);
5321 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5322 // If there is a new selection, update persistent selection;
5323 // otherwise, single click does not clear persistent selection
5325 if (cur.selection()) {
5326 // Finish selection. If double click,
5327 // cur is moved to the end of word by
5328 // selectWord but bvcur is current
5330 cur.bv().cursor().setSelection();
5331 // We might have removed an empty but drawn selection
5332 // (probably a margin)
5333 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5335 cur.noScreenUpdate();
5336 // FIXME: We could try to handle drag and drop of selection here.
5339 case mouse_button::button2:
5340 // Middle mouse pasting is handled at mouse press time,
5341 // see LFUN_MOUSE_PRESS.
5342 cur.noScreenUpdate();
5345 case mouse_button::button3:
5346 // Cursor was set at LFUN_MOUSE_PRESS time.
5347 // FIXME: If there is a selection we could try to handle a special
5348 // drag & drop context menu.
5349 cur.noScreenUpdate();
5352 case mouse_button::none:
5353 case mouse_button::button4:
5354 case mouse_button::button5:
5356 } // switch (cmd.button())
5360 case LFUN_SELF_INSERT: {
5361 if (cmd.argument().empty())
5364 // Automatically delete the currently selected
5365 // text and replace it with what is being
5366 // typed in now. Depends on lyxrc settings
5367 // "auto_region_delete", which defaults to
5370 if (lyxrc.auto_region_delete && cur.selection()) {
5371 cutSelection(cur, false);
5372 cur.setCurrentFont();
5374 cur.clearSelection();
5376 for (char_type c : cmd.argument())
5377 bv->translateAndInsert(c, this, cur);
5380 moveCursor(cur, false);
5381 cur.markNewWordPosition();
5382 bv->bookmarkEditPosition();
5386 case LFUN_HREF_INSERT: {
5387 docstring content = cmd.argument();
5388 if (content.empty() && cur.selection())
5389 content = cur.selectionAsString(false);
5391 InsetCommandParams p(HYPERLINK_CODE);
5392 if (!content.empty()){
5393 // if it looks like a link, we'll put it as target,
5394 // otherwise as name (bug #8792).
5397 // regex_match(to_utf8(content), matches, link_re)
5398 // because smatch stores pointers to the substrings rather
5399 // than making copies of them. And those pointers become
5400 // invalid after regex_match returns, since it is then
5401 // being given a temporary object. (Thanks to Georg for
5402 // figuring that out.)
5403 regex const link_re("^(([a-z]+):|www\\.).*");
5405 string const c = to_utf8(lowercase(content));
5407 if (c.substr(0,7) == "mailto:") {
5408 p["target"] = content;
5409 p["type"] = from_ascii("mailto:");
5410 } else if (regex_match(c, matches, link_re)) {
5411 p["target"] = content;
5412 string protocol = matches.str(1);
5413 if (protocol == "file")
5414 p["type"] = from_ascii("file:");
5416 p["name"] = content;
5418 string const data = InsetCommand::params2string(p);
5420 // we need to have a target. if we already have one, then
5421 // that gets used at the default for the name, too, which
5422 // is probably what is wanted.
5423 if (p["target"].empty()) {
5424 bv->showDialog("href", data);
5426 FuncRequest fr(LFUN_INSET_INSERT, data);
5432 case LFUN_LABEL_INSERT: {
5433 InsetCommandParams p(LABEL_CODE);
5434 // Try to generate a valid label
5435 p["name"] = (cmd.argument().empty()) ?
5436 cur.getPossibleLabel() :
5438 string const data = InsetCommand::params2string(p);
5440 if (cmd.argument().empty()) {
5441 bv->showDialog("label", data);
5443 FuncRequest fr(LFUN_INSET_INSERT, data);
5449 case LFUN_INFO_INSERT: {
5450 if (cmd.argument().empty()) {
5451 bv->showDialog("info", cur.current_font.language()->lang());
5454 inset = createInset(cur.buffer(), cmd);
5458 insertInset(cur, inset);
5459 cur.forceBufferUpdate();
5464 case LFUN_CAPTION_INSERT:
5465 case LFUN_FOOTNOTE_INSERT:
5466 case LFUN_NOTE_INSERT:
5467 case LFUN_BOX_INSERT:
5468 case LFUN_BRANCH_INSERT:
5469 case LFUN_PHANTOM_INSERT:
5470 case LFUN_ERT_INSERT:
5471 case LFUN_INDEXMACRO_INSERT:
5472 case LFUN_LISTING_INSERT:
5473 case LFUN_MARGINALNOTE_INSERT:
5474 case LFUN_ARGUMENT_INSERT:
5475 case LFUN_INDEX_INSERT:
5476 case LFUN_PREVIEW_INSERT:
5477 case LFUN_SCRIPT_INSERT:
5478 case LFUN_IPA_INSERT: {
5479 // Indexes reset font formatting (#11961)
5480 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5481 // Open the inset, and move the current selection
5483 doInsertInset(cur, this, cmd, true, true, resetfont);
5485 cur.setCurrentFont();
5486 // Some insets are numbered, others are shown in the outline pane so
5487 // let's update the labels and the toc backend.
5488 cur.forceBufferUpdate();
5492 case LFUN_FLEX_INSERT: {
5493 // Open the inset, and move the current selection
5495 bool const sel = cur.selection();
5496 doInsertInset(cur, this, cmd, true, true);
5497 // Insert auto-insert arguments
5498 bool autoargs = false, inautoarg = false;
5499 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5500 for (auto const & argt : args) {
5501 Layout::latexarg arg = argt.second;
5502 if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5503 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5504 lyx::dispatch(cmd2);
5506 if (arg.autoinsert) {
5507 // The cursor might have been invalidated by the replaceSelection.
5508 cur.buffer()->changed(true);
5509 // If we had already inserted an arg automatically,
5510 // leave this now in order to insert the next one.
5512 cur.leaveInset(cur.inset());
5513 cur.setCurrentFont();
5515 if (arg.insertonnewline && cur.pos() > 0) {
5516 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5517 lyx::dispatch(cmd2);
5520 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5521 lyx::dispatch(cmd2);
5528 cur.leaveInset(cur.inset());
5531 // Some insets are numbered, others are shown in the outline pane so
5532 // let's update the labels and the toc backend.
5533 cur.forceBufferUpdate();
5537 case LFUN_TABULAR_INSERT: {
5538 // if there were no arguments, just open the dialog
5539 if (cmd.argument().empty()) {
5540 bv->showDialog("tabularcreate");
5542 } else if (cur.buffer()->masterParams().tablestyle != "default"
5543 || bv->buffer().params().documentClass().tablestyle() != "default") {
5544 string tabstyle = cur.buffer()->masterParams().tablestyle;
5545 if (tabstyle == "default")
5546 tabstyle = bv->buffer().params().documentClass().tablestyle();
5547 if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5548 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5549 tabstyle + " " + to_ascii(cmd.argument()));
5553 // Unknown style. Report and fall back to default.
5554 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5555 from_utf8(N_(" not known")));
5557 if (doInsertInset(cur, this, cmd, false, true))
5562 case LFUN_TABULAR_STYLE_INSERT: {
5563 string const style = cmd.getArg(0);
5564 string const rows = cmd.getArg(1);
5565 string const cols = cmd.getArg(2);
5566 if (cols.empty() || !isStrInt(cols)
5567 || rows.empty() || !isStrInt(rows))
5569 int const r = convert<int>(rows);
5570 int const c = convert<int>(cols);
5577 FileName const tabstyle = libFileSearch("tabletemplates",
5578 style + suffix + ".lyx", "lyx");
5579 if (tabstyle.empty())
5581 UndoGroupHelper ugh(cur.buffer());
5583 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5584 lyx::dispatch(cmd2);
5588 // move one cell up to middle cell
5590 // add the missing rows
5591 int const addrows = r - 3;
5592 for (int i = 0 ; i < addrows ; ++i) {
5593 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5597 // add the missing columns
5598 int const addcols = c - 1;
5599 for (int i = 0 ; i < addcols ; ++i) {
5600 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5609 case LFUN_FLOAT_INSERT:
5610 case LFUN_FLOAT_WIDE_INSERT:
5611 case LFUN_WRAP_INSERT: {
5612 // will some content be moved into the inset?
5613 bool const content = cur.selection();
5614 // does the content consist of multiple paragraphs?
5615 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5617 doInsertInset(cur, this, cmd, true, true);
5620 // If some single-par content is moved into the inset,
5621 // doInsertInset puts the cursor outside the inset.
5622 // To insert the caption we put it back into the inset.
5623 // FIXME cleanup doInsertInset to avoid such dances!
5624 if (content && singlepar)
5627 ParagraphList & pars = cur.text()->paragraphs();
5629 DocumentClass const & tclass = bv->buffer().params().documentClass();
5631 // add a separate paragraph for the caption inset
5632 pars.push_back(Paragraph());
5633 pars.back().setInsetOwner(&cur.text()->inset());
5634 pars.back().setPlainOrDefaultLayout(tclass);
5635 int cap_pit = pars.size() - 1;
5637 // if an empty inset was created, we create an additional empty
5638 // paragraph at the bottom so that the user can choose where to put
5639 // the graphics (or table).
5641 pars.push_back(Paragraph());
5642 pars.back().setInsetOwner(&cur.text()->inset());
5643 pars.back().setPlainOrDefaultLayout(tclass);
5646 // reposition the cursor to the caption
5647 cur.pit() = cap_pit;
5649 // FIXME: This Text/Cursor dispatch handling is a mess!
5650 // We cannot use Cursor::dispatch here it needs access to up to
5652 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5653 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5654 cur.forceBufferUpdate();
5655 cur.screenUpdateFlags(Update::Force);
5656 // FIXME: When leaving the Float (or Wrap) inset we should
5657 // delete any empty paragraph left above or below the
5662 case LFUN_NOMENCL_INSERT: {
5663 InsetCommandParams p(NOMENCL_CODE);
5664 if (cmd.argument().empty()) {
5666 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5667 cur.clearSelection();
5669 p["symbol"] = cmd.argument();
5670 string const data = InsetCommand::params2string(p);
5671 bv->showDialog("nomenclature", data);
5675 case LFUN_INDEX_PRINT: {
5676 InsetCommandParams p(INDEX_PRINT_CODE);
5677 if (cmd.argument().empty())
5678 p["type"] = from_ascii("idx");
5680 p["type"] = cmd.argument();
5681 string const data = InsetCommand::params2string(p);
5682 FuncRequest fr(LFUN_INSET_INSERT, data);
5687 case LFUN_NOMENCL_PRINT:
5688 case LFUN_NEWPAGE_INSERT:
5690 doInsertInset(cur, this, cmd, false, false);
5694 case LFUN_SEPARATOR_INSERT: {
5695 doInsertInset(cur, this, cmd, false, false);
5697 // remove a following space
5698 Paragraph & par = cur.paragraph();
5699 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5700 par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5704 case LFUN_DEPTH_DECREMENT:
5705 changeDepth(cur, DEC_DEPTH);
5708 case LFUN_DEPTH_INCREMENT:
5709 changeDepth(cur, INC_DEPTH);
5712 case LFUN_REGEXP_MODE:
5713 regexpDispatch(cur, cmd);
5716 case LFUN_MATH_MODE: {
5717 if (cmd.argument() == "on" || cmd.argument() == "") {
5718 // don't pass "on" as argument
5719 // (it would appear literally in the first cell)
5720 docstring sel = cur.selectionAsString(false);
5721 InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5722 // create a macro template if we see "\\newcommand" somewhere, and
5723 // an ordinary formula otherwise
5725 && (sel.find(from_ascii("\\newcommand")) != string::npos
5726 || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5727 || sel.find(from_ascii("\\def")) != string::npos)
5728 && macro->fromString(sel)) {
5730 replaceSelection(cur);
5733 // no meaningful macro template was found
5735 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5738 // The argument is meaningful
5739 // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5740 // has a different meaning in math mode
5741 mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5745 case LFUN_MATH_MACRO:
5746 if (cmd.argument().empty())
5747 cur.errorMessage(from_utf8(N_("Missing argument")));
5750 string s = to_utf8(cmd.argument());
5751 string const s1 = token(s, ' ', 1);
5752 int const nargs = s1.empty() ? 0 : convert<int>(s1);
5753 string const s2 = token(s, ' ', 2);
5754 MacroType type = MacroTypeNewcommand;
5756 type = MacroTypeDef;
5757 InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5758 from_utf8(token(s, ' ', 0)), nargs, false, type);
5759 inset->setBuffer(bv->buffer());
5760 insertInset(cur, inset);
5762 // enter macro inset and select the name
5764 cur.top().pos() = cur.top().lastpos();
5766 cur.selection(true);
5767 cur.top().pos() = 0;
5771 case LFUN_MATH_DISPLAY:
5772 case LFUN_MATH_SUBSCRIPT:
5773 case LFUN_MATH_SUPERSCRIPT:
5774 case LFUN_MATH_INSERT:
5775 case LFUN_MATH_AMS_MATRIX:
5776 case LFUN_MATH_MATRIX:
5777 case LFUN_MATH_DELIM:
5778 case LFUN_MATH_BIGDELIM:
5779 mathDispatch(cur, cmd);
5782 case LFUN_FONT_EMPH: {
5783 Font font(ignore_font, ignore_language);
5784 font.fontInfo().setEmph(FONT_TOGGLE);
5785 toggleAndShow(cur, this, font);
5789 case LFUN_FONT_ITAL: {
5790 Font font(ignore_font, ignore_language);
5791 font.fontInfo().setShape(ITALIC_SHAPE);
5792 toggleAndShow(cur, this, font);
5796 case LFUN_FONT_BOLD:
5797 case LFUN_FONT_BOLDSYMBOL: {
5798 Font font(ignore_font, ignore_language);
5799 font.fontInfo().setSeries(BOLD_SERIES);
5800 toggleAndShow(cur, this, font);
5804 case LFUN_FONT_NOUN: {
5805 Font font(ignore_font, ignore_language);
5806 font.fontInfo().setNoun(FONT_TOGGLE);
5807 toggleAndShow(cur, this, font);
5811 case LFUN_FONT_TYPEWRITER: {
5812 Font font(ignore_font, ignore_language);
5813 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5814 toggleAndShow(cur, this, font);
5818 case LFUN_FONT_SANS: {
5819 Font font(ignore_font, ignore_language);
5820 font.fontInfo().setFamily(SANS_FAMILY);
5821 toggleAndShow(cur, this, font);
5825 case LFUN_FONT_ROMAN: {
5826 Font font(ignore_font, ignore_language);
5827 font.fontInfo().setFamily(ROMAN_FAMILY);
5828 toggleAndShow(cur, this, font);
5832 case LFUN_FONT_DEFAULT: {
5833 Font font(inherit_font, ignore_language);
5834 toggleAndShow(cur, this, font);
5838 case LFUN_FONT_STRIKEOUT: {
5839 Font font(ignore_font, ignore_language);
5840 font.fontInfo().setStrikeout(FONT_TOGGLE);
5841 toggleAndShow(cur, this, font);
5845 case LFUN_FONT_CROSSOUT: {
5846 Font font(ignore_font, ignore_language);
5847 font.fontInfo().setXout(FONT_TOGGLE);
5848 toggleAndShow(cur, this, font);
5852 case LFUN_FONT_UNDERUNDERLINE: {
5853 Font font(ignore_font, ignore_language);
5854 font.fontInfo().setUuline(FONT_TOGGLE);
5855 toggleAndShow(cur, this, font);
5859 case LFUN_FONT_UNDERWAVE: {
5860 Font font(ignore_font, ignore_language);
5861 font.fontInfo().setUwave(FONT_TOGGLE);
5862 toggleAndShow(cur, this, font);
5866 case LFUN_FONT_UNDERLINE: {
5867 Font font(ignore_font, ignore_language);
5868 font.fontInfo().setUnderbar(FONT_TOGGLE);
5869 toggleAndShow(cur, this, font);
5873 case LFUN_FONT_NO_SPELLCHECK: {
5874 Font font(ignore_font, ignore_language);
5875 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5876 toggleAndShow(cur, this, font);
5880 case LFUN_FONT_SIZE: {
5881 Font font(ignore_font, ignore_language);
5882 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5883 toggleAndShow(cur, this, font);
5887 case LFUN_LANGUAGE: {
5888 string const lang_arg = cmd.getArg(0);
5889 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5890 Language const * lang =
5891 reset ? cur.bv().buffer().params().language
5892 : languages.getLanguage(lang_arg);
5893 // we allow reset_language, which is 0, but only if it
5894 // was requested via empty or "reset" arg.
5895 if (!lang && !reset)
5897 bool const toggle = (cmd.getArg(1) != "set");
5898 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5899 Font font(ignore_font, lang);
5900 toggleAndShow(cur, this, font, toggle);
5904 case LFUN_TEXTSTYLE_APPLY: {
5905 unsigned int num = 0;
5906 string const arg = to_utf8(cmd.argument());
5909 if (isStrUnsignedInt(arg)) {
5910 num = convert<uint>(arg);
5911 if (num >= freeFonts.size()) {
5912 cur.message(_("Invalid argument (number exceeds stack size)!"));
5916 cur.message(_("Invalid argument (must be a non-negative number)!"));
5920 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5921 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5925 // Set the freefont using the contents of \param data dispatched from
5926 // the frontends and apply it at the current cursor location.
5927 case LFUN_TEXTSTYLE_UPDATE: {
5928 Font font(ignore_font, ignore_language);
5930 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5931 docstring const props = font.stateText(&bv->buffer().params(), true);
5932 freeFonts.push(make_pair(props, font));
5934 toggleAndShow(cur, this, font, toggleall);
5935 cur.message(bformat(_("Text properties applied: %1$s"), props));
5937 LYXERR0("Invalid argument of textstyle-update");
5941 case LFUN_FINISHED_LEFT:
5942 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5943 // We're leaving an inset, going left. If the inset is LTR, we're
5944 // leaving from the front, so we should not move (remain at --- but
5945 // not in --- the inset). If the inset is RTL, move left, without
5946 // entering the inset itself; i.e., move to after the inset.
5947 if (cur.paragraph().getFontSettings(
5948 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5949 cursorVisLeft(cur, true);
5952 case LFUN_FINISHED_RIGHT:
5953 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
5954 // We're leaving an inset, going right. If the inset is RTL, we're
5955 // leaving from the front, so we should not move (remain at --- but
5956 // not in --- the inset). If the inset is LTR, move right, without
5957 // entering the inset itself; i.e., move to after the inset.
5958 if (!cur.paragraph().getFontSettings(
5959 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5960 cursorVisRight(cur, true);
5963 case LFUN_FINISHED_BACKWARD:
5964 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
5965 cur.setCurrentFont();
5968 case LFUN_FINISHED_FORWARD:
5969 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
5971 cur.setCurrentFont();
5974 case LFUN_LAYOUT_PARAGRAPH: {
5976 params2string(cur.paragraph(), data);
5977 data = "show\n" + data;
5978 bv->showDialog("paragraph", data);
5982 case LFUN_PARAGRAPH_UPDATE: {
5984 params2string(cur.paragraph(), data);
5986 // Will the paragraph accept changes from the dialog?
5988 cur.inset().allowParagraphCustomization(cur.idx());
5990 data = "update " + convert<string>(accept) + '\n' + data;
5991 bv->updateDialog("paragraph", data);
5995 case LFUN_ACCENT_UMLAUT:
5996 case LFUN_ACCENT_CIRCUMFLEX:
5997 case LFUN_ACCENT_GRAVE:
5998 case LFUN_ACCENT_ACUTE:
5999 case LFUN_ACCENT_TILDE:
6000 case LFUN_ACCENT_PERISPOMENI:
6001 case LFUN_ACCENT_CEDILLA:
6002 case LFUN_ACCENT_MACRON:
6003 case LFUN_ACCENT_DOT:
6004 case LFUN_ACCENT_UNDERDOT:
6005 case LFUN_ACCENT_UNDERBAR:
6006 case LFUN_ACCENT_CARON:
6007 case LFUN_ACCENT_BREVE:
6008 case LFUN_ACCENT_TIE:
6009 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6010 case LFUN_ACCENT_CIRCLE:
6011 case LFUN_ACCENT_OGONEK:
6012 theApp()->handleKeyFunc(cmd.action());
6013 if (!cmd.argument().empty())
6014 // FIXME: Are all these characters encoded in one byte in utf8?
6015 bv->translateAndInsert(cmd.argument()[0], this, cur);
6016 cur.screenUpdateFlags(Update::FitCursor);
6019 case LFUN_FLOAT_LIST_INSERT: {
6020 DocumentClass const & tclass = bv->buffer().params().documentClass();
6021 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6023 if (cur.selection())
6024 cutSelection(cur, false);
6025 breakParagraph(cur);
6027 if (cur.lastpos() != 0) {
6028 cursorBackward(cur);
6029 breakParagraph(cur);
6032 docstring const laystr = cur.inset().usePlainLayout() ?
6033 tclass.plainLayoutName() :
6034 tclass.defaultLayoutName();
6035 setLayout(cur, laystr);
6036 ParagraphParameters p;
6037 // FIXME If this call were replaced with one to clearParagraphParams(),
6038 // then we could get rid of this method altogether.
6039 setParagraphs(cur, p);
6040 // FIXME This should be simplified when InsetFloatList takes a
6041 // Buffer in its constructor.
6042 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6043 ifl->setBuffer(bv->buffer());
6044 insertInset(cur, ifl);
6047 lyxerr << "Non-existent float type: "
6048 << to_utf8(cmd.argument()) << endl;
6053 case LFUN_CHANGE_ACCEPT: {
6054 acceptOrRejectChanges(cur, ACCEPT);
6058 case LFUN_CHANGE_REJECT: {
6059 acceptOrRejectChanges(cur, REJECT);
6063 case LFUN_THESAURUS_ENTRY: {
6064 Language const * language = cur.getFont().language();
6065 docstring arg = cmd.argument();
6067 arg = cur.selectionAsString(false);
6068 // Too large. We unselect if needed and try to get
6069 // the first word in selection or under cursor
6070 if (arg.size() > 100 || arg.empty()) {
6071 if (cur.selection()) {
6072 DocIterator selbeg = cur.selectionBegin();
6073 cur.clearSelection();
6074 setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6075 cur.screenUpdateFlags(Update::Force);
6077 // Get word or selection
6078 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6079 arg = cur.selectionAsString(false);
6080 arg += " lang=" + from_ascii(language->lang());
6083 string lang = cmd.getArg(1);
6084 // This duplicates the code in GuiThesaurus::initialiseParams
6085 if (prefixIs(lang, "lang=")) {
6086 language = languages.getLanguage(lang.substr(5));
6088 language = cur.getFont().language();
6091 string lang = language->code();
6092 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6093 LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6094 frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6095 _("The path to the thesaurus directory has not been specified.\n"
6096 "The thesaurus is not functional.\n"
6097 "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6100 bv->showDialog("thesaurus", to_utf8(arg));
6104 case LFUN_SPELLING_ADD: {
6105 Language const * language = getLanguage(cur, cmd.getArg(1));
6106 docstring word = from_utf8(cmd.getArg(0));
6108 word = cur.selectionAsString(false);
6110 if (word.size() > 100 || word.empty()) {
6111 // Get word or selection
6112 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6113 word = cur.selectionAsString(false);
6116 WordLangTuple wl(word, language);
6117 theSpellChecker()->insert(wl);
6121 case LFUN_SPELLING_ADD_LOCAL: {
6122 Language const * language = getLanguage(cur, cmd.getArg(1));
6123 docstring word = from_utf8(cmd.getArg(0));
6125 word = cur.selectionAsString(false);
6126 if (word.size() > 100)
6129 // Get word or selection
6130 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6131 word = cur.selectionAsString(false);
6134 WordLangTuple wl(word, language);
6135 if (!bv->buffer().params().spellignored(wl)) {
6136 cur.recordUndoBufferParams();
6137 bv->buffer().params().spellignore().push_back(wl);
6139 // trigger re-check of whole buffer
6140 bv->buffer().requestSpellcheck();
6145 case LFUN_SPELLING_REMOVE_LOCAL: {
6146 Language const * language = getLanguage(cur, cmd.getArg(1));
6147 docstring word = from_utf8(cmd.getArg(0));
6149 word = cur.selectionAsString(false);
6150 if (word.size() > 100)
6153 // Get word or selection
6154 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6155 word = cur.selectionAsString(false);
6158 WordLangTuple wl(word, language);
6159 bool has_item = false;
6160 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6161 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6162 if (it->lang()->code() != wl.lang()->code())
6164 if (it->word() == wl.word()) {
6170 cur.recordUndoBufferParams();
6171 bv->buffer().params().spellignore().erase(it);
6173 // trigger re-check of whole buffer
6174 bv->buffer().requestSpellcheck();
6180 case LFUN_SPELLING_IGNORE: {
6181 Language const * language = getLanguage(cur, cmd.getArg(1));
6182 docstring word = from_utf8(cmd.getArg(0));
6184 word = cur.selectionAsString(false);
6186 if (word.size() > 100 || word.empty()) {
6187 // Get word or selection
6188 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6189 word = cur.selectionAsString(false);
6192 WordLangTuple wl(word, language);
6193 theSpellChecker()->accept(wl);
6197 case LFUN_SPELLING_REMOVE: {
6198 Language const * language = getLanguage(cur, cmd.getArg(1));
6199 docstring word = from_utf8(cmd.getArg(0));
6201 word = cur.selectionAsString(false);
6203 if (word.size() > 100 || word.empty()) {
6204 // Get word or selection
6205 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6206 word = cur.selectionAsString(false);
6209 WordLangTuple wl(word, language);
6210 theSpellChecker()->remove(wl);
6214 case LFUN_PARAGRAPH_PARAMS_APPLY: {
6215 // Given data, an encoding of the ParagraphParameters
6216 // generated in the Paragraph dialog, this function sets
6217 // the current paragraph, or currently selected paragraphs,
6219 // NOTE: This function overrides all existing settings.
6220 setParagraphs(cur, cmd.argument());
6221 cur.message(_("Paragraph layout set"));
6225 case LFUN_PARAGRAPH_PARAMS: {
6226 // Given data, an encoding of the ParagraphParameters as we'd
6227 // find them in a LyX file, this function modifies the current paragraph,
6228 // or currently selected paragraphs.
6229 // NOTE: This function only modifies, and does not override, existing
6231 setParagraphs(cur, cmd.argument(), true);
6232 cur.message(_("Paragraph layout set"));
6237 if (cur.selection()) {
6238 cur.selection(false);
6241 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6242 // correct, but I'm not 100% sure -- dov, 071019
6243 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6247 case LFUN_OUTLINE_UP: {
6248 pos_type const opos = cur.pos();
6249 outline(OutlineUp, cur, false);
6250 setCursor(cur, cur.pit(), opos);
6251 cur.forceBufferUpdate();
6256 case LFUN_OUTLINE_DOWN: {
6257 pos_type const opos = cur.pos();
6258 outline(OutlineDown, cur, false);
6259 setCursor(cur, cur.pit(), opos);
6260 cur.forceBufferUpdate();
6265 case LFUN_OUTLINE_IN:
6266 outline(OutlineIn, cur, cmd.getArg(0) == "local");
6267 cur.forceBufferUpdate();
6271 case LFUN_OUTLINE_OUT:
6272 outline(OutlineOut, cur, cmd.getArg(0) == "local");
6273 cur.forceBufferUpdate();
6277 case LFUN_SERVER_GET_STATISTICS: {
6278 DocIterator from, to;
6279 if (cur.selection()) {
6280 from = cur.selectionBegin();
6281 to = cur.selectionEnd();
6283 from = doc_iterator_begin(cur.buffer());
6284 to = doc_iterator_end(cur.buffer());
6287 cur.buffer()->updateStatistics(from, to);
6288 string const arg0 = cmd.getArg(0);
6289 if (arg0 == "words") {
6290 cur.message(convert<docstring>(cur.buffer()->wordCount()));
6291 } else if (arg0 == "chars") {
6292 cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6293 } else if (arg0 == "chars-space") {
6294 cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6296 cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6297 + convert<docstring>(cur.buffer()->charCount(false)) + " "
6298 + convert<docstring>(cur.buffer()->charCount(true)));
6304 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6309 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6311 if (lyxrc.spellcheck_continuously && !needsUpdate) {
6312 // Check for misspelled text
6313 // The redraw is useful because of the painting of
6314 // misspelled markers depends on the cursor position.
6315 // Trigger a redraw for cursor moves inside misspelled text.
6316 if (!cur.inTexted()) {
6317 // move from regular text to math
6318 needsUpdate = last_misspelled;
6319 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6320 // move inside regular text
6321 needsUpdate = last_misspelled
6322 || cur.paragraph().isMisspelled(cur.pos(), true);
6326 // FIXME: The cursor flag is reset two lines below
6327 // so we need to check here if some of the LFUN did touch that.
6328 // for now only Text::erase() and Text::backspace() do that.
6329 // The plan is to verify all the LFUNs and then to remove this
6330 // singleParUpdate boolean altogether.
6331 if (cur.result().screenUpdate() & Update::Force) {
6332 singleParUpdate = false;
6336 // FIXME: the following code should go in favor of fine grained
6337 // update flag treatment.
6338 if (singleParUpdate) {
6339 // Inserting characters does not change par height in general. So, try
6340 // to update _only_ this paragraph. BufferView will detect if a full
6341 // metrics update is needed anyway.
6342 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6346 && &oldTopSlice.inset() == &cur.inset()
6347 && oldTopSlice.idx() == cur.idx()
6348 && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
6349 && !cur.selection())
6350 // FIXME: it would be better if we could just do this
6352 //if (cur.result().update() != Update::FitCursor)
6353 // cur.noScreenUpdate();
6355 // But some LFUNs do not set Update::FitCursor when needed, so we
6356 // do it for all. This is not very harmfull as FitCursor will provoke
6357 // a full redraw only if needed but still, a proper review of all LFUN
6358 // should be done and this needsUpdate boolean can then be removed.
6359 cur.screenUpdateFlags(Update::FitCursor);
6361 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6365 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6366 FuncStatus & status) const
6368 LBUFERR(this == cur.text());
6370 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6372 bool allow_in_passthru = false;
6373 InsetCode code = NO_CODE;
6375 switch (cmd.action()) {
6377 case LFUN_DEPTH_DECREMENT:
6378 enable = changeDepthAllowed(cur, DEC_DEPTH);
6381 case LFUN_DEPTH_INCREMENT:
6382 enable = changeDepthAllowed(cur, INC_DEPTH);
6386 // FIXME We really should not allow this to be put, e.g.,
6387 // in a footnote, or in ERT. But it would make sense in a
6388 // branch, so I'm not sure what to do.
6389 status.setOnOff(cur.paragraph().params().startOfAppendix());
6392 case LFUN_DIALOG_SHOW_NEW_INSET:
6393 if (cmd.argument() == "bibitem")
6394 code = BIBITEM_CODE;
6395 else if (cmd.argument() == "bibtex") {
6397 // not allowed in description items
6398 enable = !inDescriptionItem(cur);
6400 else if (cmd.argument() == "box")
6402 else if (cmd.argument() == "branch")
6404 else if (cmd.argument() == "citation")
6406 else if (cmd.argument() == "counter")
6407 code = COUNTER_CODE;
6408 else if (cmd.argument() == "ert")
6410 else if (cmd.argument() == "external")
6411 code = EXTERNAL_CODE;
6412 else if (cmd.argument() == "float")
6414 else if (cmd.argument() == "graphics")
6415 code = GRAPHICS_CODE;
6416 else if (cmd.argument() == "href")
6417 code = HYPERLINK_CODE;
6418 else if (cmd.argument() == "include")
6419 code = INCLUDE_CODE;
6420 else if (cmd.argument() == "index")
6422 else if (cmd.argument() == "index_print")
6423 code = INDEX_PRINT_CODE;
6424 else if (cmd.argument() == "listings")
6425 code = LISTINGS_CODE;
6426 else if (cmd.argument() == "mathspace")
6427 code = MATH_HULL_CODE;
6428 else if (cmd.argument() == "nomenclature")
6429 code = NOMENCL_CODE;
6430 else if (cmd.argument() == "nomencl_print")
6431 code = NOMENCL_PRINT_CODE;
6432 else if (cmd.argument() == "label")
6434 else if (cmd.argument() == "line")
6436 else if (cmd.argument() == "note")
6438 else if (cmd.argument() == "phantom")
6439 code = PHANTOM_CODE;
6440 else if (cmd.argument() == "ref")
6442 else if (cmd.argument() == "space")
6444 else if (cmd.argument() == "toc")
6446 else if (cmd.argument() == "vspace")
6448 else if (cmd.argument() == "wrap")
6452 case LFUN_ERT_INSERT:
6455 case LFUN_LISTING_INSERT:
6456 code = LISTINGS_CODE;
6457 // not allowed in description items
6458 enable = !inDescriptionItem(cur);
6460 case LFUN_FOOTNOTE_INSERT:
6463 case LFUN_TABULAR_INSERT:
6464 code = TABULAR_CODE;
6466 case LFUN_TABULAR_STYLE_INSERT:
6467 code = TABULAR_CODE;
6469 case LFUN_MARGINALNOTE_INSERT:
6472 case LFUN_FLOAT_INSERT:
6473 case LFUN_FLOAT_WIDE_INSERT:
6474 // FIXME: If there is a selection, we should check whether there
6475 // are floats in the selection, but this has performance issues, see
6476 // LFUN_CHANGE_ACCEPT/REJECT.
6478 if (inDescriptionItem(cur))
6479 // not allowed in description items
6482 InsetCode const inset_code = cur.inset().lyxCode();
6484 // algorithm floats cannot be put in another float
6485 if (to_utf8(cmd.argument()) == "algorithm") {
6486 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6490 // for figures and tables: only allow in another
6491 // float or wrap if it is of the same type and
6492 // not a subfloat already
6493 if(cur.inset().lyxCode() == code) {
6494 InsetFloat const & ins =
6495 static_cast<InsetFloat const &>(cur.inset());
6496 enable = ins.params().type == to_utf8(cmd.argument())
6497 && !ins.params().subfloat;
6498 } else if(cur.inset().lyxCode() == WRAP_CODE) {
6499 InsetWrap const & ins =
6500 static_cast<InsetWrap const &>(cur.inset());
6501 enable = ins.params().type == to_utf8(cmd.argument());
6505 case LFUN_WRAP_INSERT:
6507 // not allowed in description items
6508 enable = !inDescriptionItem(cur);
6510 case LFUN_FLOAT_LIST_INSERT: {
6511 code = FLOAT_LIST_CODE;
6512 // not allowed in description items
6513 enable = !inDescriptionItem(cur);
6515 FloatList const & floats = cur.buffer()->params().documentClass().floats();
6516 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6517 // make sure we know about such floats
6518 if (cit == floats.end() ||
6519 // and that we know how to generate a list of them
6520 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6521 status.setUnknown(true);
6522 // probably not necessary, but...
6528 case LFUN_CAPTION_INSERT: {
6529 code = CAPTION_CODE;
6530 string arg = cmd.getArg(0);
6531 bool varia = arg != "Unnumbered"
6532 && cur.inset().allowsCaptionVariation(arg);
6533 // not allowed in description items,
6534 // and in specific insets
6535 enable = !inDescriptionItem(cur)
6536 && (varia || arg.empty() || arg == "Standard");
6539 case LFUN_NOTE_INSERT:
6542 case LFUN_FLEX_INSERT: {
6544 docstring s = from_utf8(cmd.getArg(0));
6545 // Prepend "Flex:" prefix if not there
6546 if (!prefixIs(s, from_ascii("Flex:")))
6547 s = from_ascii("Flex:") + s;
6548 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6550 else if (!cur.paragraph().allowedInContext(cur, cur.buffer()->params().documentClass().insetLayout(s)))
6554 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6555 if (ilt != InsetLyXType::CHARSTYLE
6556 && ilt != InsetLyXType::CUSTOM
6557 && ilt != InsetLyXType::STANDARD)
6562 case LFUN_BOX_INSERT:
6565 case LFUN_BRANCH_INSERT:
6567 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6568 && cur.buffer()->params().branchlist().empty())
6571 case LFUN_IPA_INSERT:
6574 case LFUN_PHANTOM_INSERT:
6575 code = PHANTOM_CODE;
6577 case LFUN_LABEL_INSERT:
6580 case LFUN_INFO_INSERT:
6582 enable = cmd.argument().empty()
6583 || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6585 case LFUN_ARGUMENT_INSERT: {
6587 allow_in_passthru = true;
6588 string const arg = cmd.getArg(0);
6593 Layout const & lay = cur.paragraph().layout();
6594 Layout::LaTeXArgMap args = lay.args();
6595 Layout::LaTeXArgMap::const_iterator const lait =
6597 if (lait != args.end()) {
6599 pit_type pit = cur.pit();
6600 pit_type lastpit = cur.pit();
6601 if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6602 // In a sequence of "merged" environment layouts, we only allow
6603 // non-item arguments once.
6604 lastpit = cur.lastpit();
6605 // get the first paragraph in sequence with this layout
6606 depth_type const current_depth = cur.paragraph().params().depth();
6610 Paragraph cpar = pars_[pit - 1];
6611 if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6617 for (; pit <= lastpit; ++pit) {
6618 if (pars_[pit].layout() != lay)
6620 for (auto const & table : pars_[pit].insetList())
6621 if (InsetArgument const * ins = table.inset->asInsetArgument())
6622 if (ins->name() == arg) {
6623 // we have this already
6632 case LFUN_INDEX_INSERT:
6635 case LFUN_INDEX_PRINT:
6636 code = INDEX_PRINT_CODE;
6637 // not allowed in description items
6638 enable = !inDescriptionItem(cur);
6640 case LFUN_NOMENCL_INSERT:
6641 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6645 code = NOMENCL_CODE;
6647 case LFUN_NOMENCL_PRINT:
6648 code = NOMENCL_PRINT_CODE;
6649 // not allowed in description items
6650 enable = !inDescriptionItem(cur);
6652 case LFUN_HREF_INSERT:
6653 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6657 code = HYPERLINK_CODE;
6659 case LFUN_INDEXMACRO_INSERT: {
6660 string const arg = cmd.getArg(0);
6661 if (arg == "sortkey")
6662 code = INDEXMACRO_SORTKEY_CODE;
6664 code = INDEXMACRO_CODE;
6667 case LFUN_IPAMACRO_INSERT: {
6668 string const arg = cmd.getArg(0);
6670 code = IPADECO_CODE;
6672 code = IPACHAR_CODE;
6675 case LFUN_QUOTE_INSERT:
6676 // always allow this, since we will inset a raw quote
6677 // if an inset is not allowed.
6678 allow_in_passthru = true;
6680 case LFUN_SPECIALCHAR_INSERT:
6681 code = SPECIALCHAR_CODE;
6683 case LFUN_SPACE_INSERT:
6684 // slight hack: we know this is allowed in math mode
6688 case LFUN_PREVIEW_INSERT:
6689 code = PREVIEW_CODE;
6691 case LFUN_SCRIPT_INSERT:
6695 case LFUN_MATH_INSERT:
6696 case LFUN_MATH_AMS_MATRIX:
6697 case LFUN_MATH_MATRIX:
6698 case LFUN_MATH_DELIM:
6699 case LFUN_MATH_BIGDELIM:
6700 case LFUN_MATH_DISPLAY:
6701 case LFUN_MATH_MODE:
6702 case LFUN_MATH_MACRO:
6703 case LFUN_MATH_SUBSCRIPT:
6704 case LFUN_MATH_SUPERSCRIPT:
6705 code = MATH_HULL_CODE;
6708 case LFUN_REGEXP_MODE:
6709 code = MATH_HULL_CODE;
6710 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6713 case LFUN_INSET_MODIFY:
6714 // We need to disable this, because we may get called for a
6716 // InsetTabular::getStatus() -> InsetText::getStatus()
6717 // and we don't handle LFUN_INSET_MODIFY.
6721 case LFUN_FONT_EMPH:
6722 status.setOnOff(fontinfo.emph() == FONT_ON);
6723 enable = !cur.paragraph().isPassThru();
6726 case LFUN_FONT_ITAL:
6727 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6728 enable = !cur.paragraph().isPassThru();
6731 case LFUN_FONT_NOUN:
6732 status.setOnOff(fontinfo.noun() == FONT_ON);
6733 enable = !cur.paragraph().isPassThru();
6736 case LFUN_FONT_BOLD:
6737 case LFUN_FONT_BOLDSYMBOL:
6738 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6739 enable = !cur.paragraph().isPassThru();
6742 case LFUN_FONT_SANS:
6743 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6744 enable = !cur.paragraph().isPassThru();
6747 case LFUN_FONT_ROMAN:
6748 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6749 enable = !cur.paragraph().isPassThru();
6752 case LFUN_FONT_TYPEWRITER:
6753 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6754 enable = !cur.paragraph().isPassThru();
6758 enable = cur.selection();
6762 if (cmd.argument().empty()) {
6763 if (theClipboard().isInternal())
6764 enable = cap::numberOfSelections() > 0;
6766 enable = !theClipboard().empty();
6770 // we have an argument
6771 string const arg = to_utf8(cmd.argument());
6772 if (isStrUnsignedInt(arg)) {
6773 // it's a number and therefore means the internal stack
6774 unsigned int n = convert<unsigned int>(arg);
6775 enable = cap::numberOfSelections() > n;
6779 // explicit text type?
6780 if (arg == "html") {
6781 // Do not enable for PlainTextType, since some tidying in the
6782 // frontend is needed for HTML, which is too unsafe for plain text.
6783 enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6785 } else if (arg == "latex") {
6786 // LaTeX is usually not available on the clipboard with
6787 // the correct MIME type, but in plain text.
6788 enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6789 theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6793 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6795 type = Clipboard::PdfGraphicsType;
6796 else if (arg == "png")
6797 type = Clipboard::PngGraphicsType;
6798 else if (arg == "jpeg")
6799 type = Clipboard::JpegGraphicsType;
6800 else if (arg == "linkback")
6801 type = Clipboard::LinkBackGraphicsType;
6802 else if (arg == "emf")
6803 type = Clipboard::EmfGraphicsType;
6804 else if (arg == "wmf")
6805 type = Clipboard::WmfGraphicsType;
6808 LYXERR0("Unrecognized graphics type: " << arg);
6809 // we don't want to assert if the user just mistyped the LFUN
6810 LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6814 enable = theClipboard().hasGraphicsContents(type);
6818 case LFUN_CLIPBOARD_PASTE:
6819 case LFUN_CLIPBOARD_PASTE_SIMPLE:
6820 enable = !theClipboard().empty();
6823 case LFUN_PRIMARY_SELECTION_PASTE:
6824 status.setUnknown(!theSelection().supported());
6825 enable = cur.selection() || !theSelection().empty();
6828 case LFUN_SELECTION_PASTE:
6829 enable = cap::selection();
6832 case LFUN_PARAGRAPH_MOVE_UP:
6833 enable = cur.pit() > 0 && !cur.selection();
6836 case LFUN_PARAGRAPH_MOVE_DOWN:
6837 enable = cur.pit() < cur.lastpit() && !cur.selection();
6840 case LFUN_CHANGE_ACCEPT:
6841 case LFUN_CHANGE_REJECT:
6842 if (!cur.selection())
6843 enable = cur.paragraph().isChanged(cur.pos());
6845 // will enable if there is a change in the selection
6848 // cheap improvement for efficiency: using cached
6849 // buffer variable, if there is no change in the
6850 // document, no need to check further.
6851 if (!cur.buffer()->areChangesPresent())
6854 for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6855 pos_type const beg = it.pos();
6857 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6858 it.idx() == cur.selectionEnd().idx());
6860 end = cur.selectionEnd().pos();
6862 // the +1 is needed for cases, e.g., where there is a
6863 // paragraph break. See #11629.
6864 end = it.lastpos() + 1;
6865 if (beg != end && it.paragraph().isChanged(beg, end)) {
6869 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6879 case LFUN_OUTLINE_UP:
6880 case LFUN_OUTLINE_DOWN:
6881 case LFUN_OUTLINE_IN:
6882 case LFUN_OUTLINE_OUT:
6883 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6886 case LFUN_NEWLINE_INSERT:
6887 // LaTeX restrictions (labels or empty par)
6888 enable = !cur.paragraph().isPassThru()
6889 && cur.pos() > cur.paragraph().beginOfBody();
6892 case LFUN_SEPARATOR_INSERT:
6893 // Always enabled for now
6897 case LFUN_TAB_INSERT:
6898 case LFUN_TAB_DELETE:
6899 enable = cur.paragraph().isPassThru();
6902 case LFUN_GRAPHICS_SET_GROUP: {
6903 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6907 status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6911 case LFUN_NEWPAGE_INSERT:
6912 // not allowed in description items
6913 code = NEWPAGE_CODE;
6914 enable = !inDescriptionItem(cur);
6918 enable = !cur.paragraph().isPassThru();
6919 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6922 case LFUN_PARAGRAPH_BREAK:
6923 enable = inset().allowMultiPar();
6926 case LFUN_SPELLING_ADD:
6927 case LFUN_SPELLING_ADD_LOCAL:
6928 case LFUN_SPELLING_REMOVE_LOCAL:
6929 case LFUN_SPELLING_IGNORE:
6930 case LFUN_SPELLING_REMOVE:
6931 enable = theSpellChecker() != nullptr;
6932 if (enable && !cmd.getArg(1).empty()) {
6933 // validate explicitly given language
6934 Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6935 enable &= lang != nullptr;
6940 case LFUN_LAYOUT_TOGGLE: {
6941 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
6942 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
6943 docstring const layout = resolveLayout(req_layout, cur);
6945 // FIXME: make this work in multicell selection case
6946 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
6947 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
6948 && isAlreadyLayout(layout, cur));
6952 case LFUN_ENVIRONMENT_SPLIT: {
6953 if (cmd.argument() == "outer") {
6954 // check if we have an environment in our nesting hierarchy
6956 depth_type const current_depth = cur.paragraph().params().depth();
6957 pit_type pit = cur.pit();
6958 Paragraph cpar = pars_[pit];
6960 if (pit == 0 || cpar.params().depth() == 0)
6964 if (cpar.params().depth() < current_depth)
6965 res = cpar.layout().isEnvironment();
6970 else if (cmd.argument() == "previous") {
6971 // look if we have an environment in the previous par
6972 pit_type pit = cur.pit();
6973 Paragraph cpar = pars_[pit];
6977 enable = cpar.layout().isEnvironment();
6983 else if (cur.paragraph().layout().isEnvironment()) {
6984 enable = cmd.argument() == "before"
6985 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
6992 case LFUN_LAYOUT_PARAGRAPH:
6993 case LFUN_PARAGRAPH_PARAMS:
6994 case LFUN_PARAGRAPH_PARAMS_APPLY:
6995 case LFUN_PARAGRAPH_UPDATE:
6996 enable = owner_->allowParagraphCustomization();
6999 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
7000 // Because they insert COMBINING DIACRITICAL Unicode characters,
7001 // that cannot be handled by LaTeX but must be converted according
7002 // to the definition in lib/unicodesymbols?
7003 case LFUN_ACCENT_ACUTE:
7004 case LFUN_ACCENT_BREVE:
7005 case LFUN_ACCENT_CARON:
7006 case LFUN_ACCENT_CEDILLA:
7007 case LFUN_ACCENT_CIRCLE:
7008 case LFUN_ACCENT_CIRCUMFLEX:
7009 case LFUN_ACCENT_DOT:
7010 case LFUN_ACCENT_GRAVE:
7011 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7012 case LFUN_ACCENT_MACRON:
7013 case LFUN_ACCENT_OGONEK:
7014 case LFUN_ACCENT_TIE:
7015 case LFUN_ACCENT_TILDE:
7016 case LFUN_ACCENT_PERISPOMENI:
7017 case LFUN_ACCENT_UMLAUT:
7018 case LFUN_ACCENT_UNDERBAR:
7019 case LFUN_ACCENT_UNDERDOT:
7020 case LFUN_FONT_FRAK:
7021 case LFUN_FONT_SIZE:
7022 case LFUN_FONT_STATE:
7023 case LFUN_FONT_UNDERLINE:
7024 case LFUN_FONT_STRIKEOUT:
7025 case LFUN_FONT_CROSSOUT:
7026 case LFUN_FONT_UNDERUNDERLINE:
7027 case LFUN_FONT_UNDERWAVE:
7028 case LFUN_FONT_NO_SPELLCHECK:
7029 case LFUN_TEXTSTYLE_UPDATE:
7030 enable = !cur.paragraph().isPassThru();
7033 case LFUN_FONT_DEFAULT: {
7034 Font font(inherit_font, ignore_language);
7035 BufferParams const & bp = cur.buffer()->masterParams();
7036 if (cur.selection()) {
7038 // Check if we have a non-default font attribute
7039 // in the selection range.
7040 DocIterator const from = cur.selectionBegin();
7041 DocIterator const to = cur.selectionEnd();
7042 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7043 if (!dit.inTexted()) {
7047 Paragraph const & par = dit.paragraph();
7048 pos_type const pos = dit.pos();
7049 Font tmp = par.getFontSettings(bp, pos);
7050 if (tmp.fontInfo() != font.fontInfo()
7051 || tmp.language() != bp.language) {
7059 // Disable if all is default already.
7060 enable = (cur.current_font.fontInfo() != font.fontInfo()
7061 || cur.current_font.language() != bp.language);
7065 case LFUN_TEXTSTYLE_APPLY:
7066 enable = !freeFonts.empty();
7069 case LFUN_WORD_DELETE_FORWARD:
7070 case LFUN_WORD_DELETE_BACKWARD:
7071 case LFUN_LINE_DELETE_FORWARD:
7072 case LFUN_WORD_FORWARD:
7073 case LFUN_WORD_BACKWARD:
7074 case LFUN_WORD_RIGHT:
7075 case LFUN_WORD_LEFT:
7076 case LFUN_CHAR_FORWARD:
7077 case LFUN_CHAR_FORWARD_SELECT:
7078 case LFUN_CHAR_BACKWARD:
7079 case LFUN_CHAR_BACKWARD_SELECT:
7080 case LFUN_CHAR_LEFT:
7081 case LFUN_CHAR_LEFT_SELECT:
7082 case LFUN_CHAR_RIGHT:
7083 case LFUN_CHAR_RIGHT_SELECT:
7085 case LFUN_UP_SELECT:
7087 case LFUN_DOWN_SELECT:
7088 case LFUN_PARAGRAPH_SELECT:
7089 case LFUN_PARAGRAPH_UP_SELECT:
7090 case LFUN_PARAGRAPH_DOWN_SELECT:
7091 case LFUN_LINE_BEGIN_SELECT:
7092 case LFUN_LINE_END_SELECT:
7093 case LFUN_WORD_FORWARD_SELECT:
7094 case LFUN_WORD_BACKWARD_SELECT:
7095 case LFUN_WORD_RIGHT_SELECT:
7096 case LFUN_WORD_LEFT_SELECT:
7097 case LFUN_WORD_SELECT:
7098 case LFUN_SECTION_SELECT:
7099 case LFUN_BUFFER_BEGIN:
7100 case LFUN_BUFFER_END:
7101 case LFUN_BUFFER_BEGIN_SELECT:
7102 case LFUN_BUFFER_END_SELECT:
7103 case LFUN_INSET_BEGIN:
7104 case LFUN_INSET_END:
7105 case LFUN_INSET_BEGIN_SELECT:
7106 case LFUN_INSET_END_SELECT:
7107 case LFUN_PARAGRAPH_UP:
7108 case LFUN_PARAGRAPH_DOWN:
7109 case LFUN_LINE_BEGIN:
7111 case LFUN_CHAR_DELETE_FORWARD:
7112 case LFUN_CHAR_DELETE_BACKWARD:
7113 case LFUN_WORD_UPCASE:
7114 case LFUN_WORD_LOWCASE:
7115 case LFUN_WORD_CAPITALIZE:
7116 case LFUN_CHARS_TRANSPOSE:
7117 case LFUN_SERVER_GET_XY:
7118 case LFUN_SERVER_SET_XY:
7119 case LFUN_SERVER_GET_LAYOUT:
7120 case LFUN_SELF_INSERT:
7121 case LFUN_UNICODE_INSERT:
7122 case LFUN_THESAURUS_ENTRY:
7124 case LFUN_SERVER_GET_STATISTICS:
7125 // these are handled in our dispatch()
7129 case LFUN_INSET_INSERT: {
7130 string const type = cmd.getArg(0);
7131 if (type == "toc") {
7133 // not allowed in description items
7134 //FIXME: couldn't this be merged in Inset::insetAllowed()?
7135 enable = !inDescriptionItem(cur);
7142 case LFUN_SEARCH_IGNORE: {
7143 bool const value = cmd.getArg(1) == "true";
7144 setIgnoreFormat(cmd.getArg(0), value);
7154 || !cur.inset().insetAllowed(code)
7155 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7158 status.setEnabled(enable);
7163 void Text::pasteString(Cursor & cur, docstring const & clip,
7166 if (!clip.empty()) {
7169 insertStringAsParagraphs(cur, clip, cur.current_font);
7171 insertStringAsLines(cur, clip, cur.current_font);
7176 // FIXME: an item inset would make things much easier.
7177 bool Text::inDescriptionItem(Cursor const & cur) const
7179 Paragraph const & par = cur.paragraph();
7180 pos_type const pos = cur.pos();
7181 pos_type const body_pos = par.beginOfBody();
7183 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7184 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7185 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7188 return (pos < body_pos
7190 && (pos == 0 || par.getChar(pos - 1) != ' ')));
7194 std::vector<docstring> Text::getFreeFonts() const
7196 vector<docstring> ffList;
7198 for (auto const & f : freeFonts)
7199 ffList.push_back(f.first);