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 * - Decrease depth by one (or change layout to default layout) when
720 * keep_layout == false
721 * - keep current depth and layout when keep_layout == true
723 static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
726 BufferParams const & bparams = text.inset().buffer().params();
727 ParagraphList & pars = text.paragraphs();
728 // create a new paragraph, and insert into the list
729 ParagraphList::iterator tmp =
730 pars.insert(pars.iterator_at(par_offset + 1), Paragraph());
732 Paragraph & par = pars[par_offset];
734 // remember to set the inset_owner
735 tmp->setInsetOwner(&par.inInset());
736 // without doing that we get a crash when typing <Return> at the
737 // end of a paragraph
738 tmp->setPlainOrDefaultLayout(bparams.documentClass());
741 tmp->setLayout(par.layout());
742 tmp->setLabelWidthString(par.params().labelWidthString());
743 tmp->params().depth(par.params().depth());
744 } else if (par.params().depth() > 0) {
745 Paragraph const & hook = pars[text.outerHook(par_offset)];
746 tmp->setLayout(hook.layout());
747 // not sure the line below is useful
748 tmp->setLabelWidthString(par.params().labelWidthString());
749 tmp->params().depth(hook.params().depth());
752 bool const isempty = (par.allowEmpty() && par.empty());
754 if (!isempty && (par.size() > pos || par.empty())) {
755 tmp->setLayout(par.layout());
756 tmp->params().align(par.params().align());
757 tmp->setLabelWidthString(par.params().labelWidthString());
759 tmp->params().depth(par.params().depth());
760 tmp->params().noindent(par.params().noindent());
761 tmp->params().spacing(par.params().spacing());
763 // move everything behind the break position
764 // to the new paragraph
766 /* Note: if !keepempty, empty() == true, then we reach
767 * here with size() == 0. So pos_end becomes - 1. This
768 * doesn't cause problems because both loops below
769 * enforce pos <= pos_end and 0 <= pos
771 pos_type pos_end = par.size() - 1;
773 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
774 if (moveItem(par, pos, *tmp, j, bparams)) {
780 // Move over the end-of-par change information
781 tmp->setChange(tmp->size(), par.lookupChange(par.size()));
782 par.setChange(par.size(), Change(bparams.track_changes ?
783 Change::INSERTED : Change::UNCHANGED));
786 // Make sure that we keep the language when
787 // breaking paragraph.
789 Font changed = tmp->getFirstFontSettings(bparams);
790 Font const & old = par.getFontSettings(bparams, par.size());
791 changed.setLanguage(old.language());
792 tmp->setFont(0, changed);
799 bool const soa = par.params().startOfAppendix();
800 par.params().clear();
801 // do not lose start of appendix marker (bug 4212)
802 par.params().startOfAppendix(soa);
803 par.setPlainOrDefaultLayout(bparams.documentClass());
807 par.setLayout(tmp->layout());
808 par.setLabelWidthString(tmp->params().labelWidthString());
809 par.params().depth(tmp->params().depth());
814 void Text::breakParagraph(Cursor & cur, bool inverse_logic)
816 LBUFERR(this == cur.text());
818 Paragraph & cpar = cur.paragraph();
819 pit_type cpit = cur.pit();
821 DocumentClass const & tclass = cur.buffer()->params().documentClass();
822 Layout const & layout = cpar.layout();
824 if (cur.lastpos() == 0 && !cpar.allowEmpty()) {
825 if (changeDepthAllowed(cur, DEC_DEPTH)) {
826 changeDepth(cur, DEC_DEPTH);
827 pit_type const prev = depthHook(cpit, cpar.getDepth());
828 docstring const & lay = pars_[prev].layout().name();
829 if (lay != layout.name())
832 docstring const & lay = cur.paragraph().usePlainLayout()
833 ? tclass.plainLayoutName() : tclass.defaultLayoutName();
834 if (lay != layout.name())
842 // Always break behind a space
843 // It is better to erase the space (Dekel)
844 if (cur.pos() != cur.lastpos() && cpar.isLineSeparator(cur.pos()))
845 cpar.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
847 // What should the layout for the new paragraph be?
848 bool keep_layout = layout.isEnvironment()
849 || (layout.isParagraph() && layout.parbreak_is_newline);
851 keep_layout = !keep_layout;
853 // We need to remember this before we break the paragraph, because
854 // that invalidates the layout variable
855 bool sensitive = layout.labeltype == LABEL_SENSITIVE;
857 // we need to set this before we insert the paragraph.
858 bool const isempty = cpar.allowEmpty() && cpar.empty();
860 lyx::breakParagraph(*this, cpit, cur.pos(), keep_layout);
862 // After this, neither paragraph contains any rows!
865 pit_type next_par = cpit + 1;
867 // well this is the caption hack since one caption is really enough
870 // set to standard-layout
871 //FIXME Check if this should be plainLayout() in some cases
872 pars_[cpit].applyLayout(tclass.defaultLayout());
874 // set to standard-layout
875 //FIXME Check if this should be plainLayout() in some cases
876 pars_[next_par].applyLayout(tclass.defaultLayout());
879 while (!pars_[next_par].empty() && pars_[next_par].isNewline(0)) {
880 if (!pars_[next_par].eraseChar(0, cur.buffer()->params().track_changes))
881 break; // the character couldn't be deleted physically due to change tracking
884 // A singlePar update is not enough in this case.
885 cur.screenUpdateFlags(Update::Force);
886 cur.forceBufferUpdate();
888 // This check is necessary. Otherwise the new empty paragraph will
889 // be deleted automatically. And it is more friendly for the user!
890 if (cur.pos() != 0 || isempty)
891 setCursor(cur, cur.pit() + 1, 0);
893 setCursor(cur, cur.pit(), 0);
897 // needed to insert the selection
898 void Text::insertStringAsLines(Cursor & cur, docstring const & str,
901 BufferParams const & bparams = owner_->buffer().params();
902 pit_type pit = cur.pit();
903 pos_type pos = cur.pos();
905 // The special chars we handle
906 static map<wchar_t, InsetSpecialChar::Kind> specialchars = {
907 { 0x200c, InsetSpecialChar::LIGATURE_BREAK },
908 { 0x200b, InsetSpecialChar::ALLOWBREAK },
909 { 0x2026, InsetSpecialChar::LDOTS },
910 { 0x2011, InsetSpecialChar::NOBREAKDASH }
913 // insert the string, don't insert doublespace
914 bool space_inserted = true;
915 for (auto const & ch : str) {
916 Paragraph & par = pars_[pit];
918 if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) {
919 lyx::breakParagraph(*this, pit, pos,
920 par.layout().isEnvironment());
923 space_inserted = true;
927 // do not insert consecutive spaces if !free_spacing
928 } else if ((ch == ' ' || ch == '\t') &&
929 space_inserted && !par.isFreeSpacing()) {
931 } else if (ch == '\t') {
932 if (!par.isFreeSpacing()) {
933 // tabs are like spaces here
934 par.insertChar(pos, ' ', font, bparams.track_changes);
936 space_inserted = true;
938 par.insertChar(pos, ch, font, bparams.track_changes);
940 space_inserted = true;
942 } else if (specialchars.find(ch) != specialchars.end()
943 && (par.insertInset(pos, new InsetSpecialChar(specialchars.find(ch)->second),
944 font, bparams.track_changes
945 ? Change(Change::INSERTED)
946 : Change(Change::UNCHANGED)))) {
948 space_inserted = false;
949 } else if (!isPrintable(ch)) {
950 // Ignore (other) unprintables
953 // just insert the character
954 par.insertChar(pos, ch, font, bparams.track_changes);
956 space_inserted = (ch == ' ');
959 setCursor(cur, pit, pos);
963 // turn double CR to single CR, others are converted into one
964 // blank. Then insertStringAsLines is called
965 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str,
968 docstring linestr = str;
969 bool newline_inserted = false;
971 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
972 if (linestr[i] == '\n') {
973 if (newline_inserted) {
974 // we know that \r will be ignored by
975 // insertStringAsLines. Of course, it is a dirty
976 // trick, but it works...
977 linestr[i - 1] = '\r';
981 newline_inserted = true;
983 } else if (isPrintable(linestr[i])) {
984 newline_inserted = false;
987 insertStringAsLines(cur, linestr, font);
993 bool canInsertChar(Cursor const & cur, char_type c)
995 Paragraph const & par = cur.paragraph();
996 // If not in free spacing mode, check if there will be two blanks together or a blank at
997 // the beginning of a paragraph.
998 if (!par.isFreeSpacing() && isLineSeparatorChar(c)) {
999 if (cur.pos() == 0) {
1001 "You cannot insert a space at the "
1002 "beginning of a paragraph. Please read the Tutorial."));
1005 // If something is wrong, ignore this character.
1006 LASSERT(cur.pos() > 0, return false);
1007 if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1))
1008 && !par.isDeleted(cur.pos() - 1)) {
1010 "You cannot type two spaces this way. "
1011 "Please read the Tutorial."));
1016 // Prevent to insert uncodable characters in verbatim and ERT.
1017 // The encoding is inherited from the context here.
1018 if (par.isPassThru() && cur.getEncoding()) {
1019 Encoding const * e = cur.getEncoding();
1020 if (!e->encodable(c)) {
1021 cur.message(_("Character is uncodable in this verbatim context."));
1031 // insert a character, moves all the following breaks in the
1032 // same Paragraph one to the right and make a rebreak
1033 void Text::insertChar(Cursor & cur, char_type c)
1035 LBUFERR(this == cur.text());
1037 if (!canInsertChar(cur,c))
1040 cur.recordUndo(INSERT_UNDO);
1042 TextMetrics const & tm = cur.bv().textMetrics(this);
1043 Buffer const & buffer = *cur.buffer();
1044 Paragraph & par = cur.paragraph();
1045 // try to remove this
1046 pit_type const pit = cur.pit();
1048 if (lyxrc.auto_number) {
1049 static docstring const number_operators = from_ascii("+-/*");
1050 static docstring const number_unary_operators = from_ascii("+-");
1052 // Common Number Separators: comma, dot etc.
1053 // European Number Terminators: percent, permille, degree, euro etc.
1054 if (cur.current_font.fontInfo().number() == FONT_ON) {
1055 if (!isDigitASCII(c) && !contains(number_operators, c) &&
1056 !(isCommonNumberSeparator(c) &&
1058 cur.pos() != cur.lastpos() &&
1059 tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1060 tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON) &&
1061 !(isEuropeanNumberTerminator(c) &&
1063 tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1064 tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON)
1066 number(cur); // Set current_font.number to OFF
1067 } else if (isDigitASCII(c) &&
1068 cur.real_current_font.isVisibleRightToLeft()) {
1069 number(cur); // Set current_font.number to ON
1071 if (cur.pos() != 0) {
1072 char_type const ch = par.getChar(cur.pos() - 1);
1073 if (contains(number_unary_operators, ch) &&
1075 || par.isSeparator(cur.pos() - 2)
1076 || par.isEnvSeparator(cur.pos() - 2)
1077 || par.isNewline(cur.pos() - 2))
1079 setCharFont(pit, cur.pos() - 1, cur.current_font,
1081 } else if (isCommonNumberSeparator(ch)
1083 && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) {
1084 setCharFont(pit, cur.pos() - 1, cur.current_font,
1091 // In Bidi text, we want spaces to be treated in a special way: spaces
1092 // which are between words in different languages should get the
1093 // paragraph's language; otherwise, spaces should keep the language
1094 // they were originally typed in. This is only in effect while typing;
1095 // after the text is already typed in, the user can always go back and
1096 // explicitly set the language of a space as desired. But 99.9% of the
1097 // time, what we're doing here is what the user actually meant.
1099 // The following cases are the ones in which the language of the space
1100 // should be changed to match that of the containing paragraph. In the
1101 // depictions, lowercase is LTR, uppercase is RTL, underscore (_)
1102 // represents a space, pipe (|) represents the cursor position (so the
1103 // character before it is the one just typed in). The different cases
1104 // are depicted logically (not visually), from left to right:
1109 // Theoretically, there are other situations that we should, perhaps, deal
1110 // with (e.g.: a|_A, A|_a). In practice, though, there really isn't any
1111 // point (to understand why, just try to create this situation...).
1113 if ((cur.pos() >= 2) && (par.isLineSeparator(cur.pos() - 1))) {
1114 // get font in front and behind the space in question. But do NOT
1115 // use getFont(cur.pos()) because the character c is not inserted yet
1116 Font const pre_space_font = tm.displayFont(cur.pit(), cur.pos() - 2);
1117 Font const & post_space_font = cur.real_current_font;
1118 bool pre_space_rtl = pre_space_font.isVisibleRightToLeft();
1119 bool post_space_rtl = post_space_font.isVisibleRightToLeft();
1121 if (pre_space_rtl != post_space_rtl) {
1122 // Set the space's language to match the language of the
1123 // adjacent character whose direction is the paragraph's
1124 // direction; don't touch other properties of the font
1125 Language const * lang =
1126 (pre_space_rtl == par.isRTL(buffer.params())) ?
1127 pre_space_font.language() : post_space_font.language();
1129 Font space_font = tm.displayFont(cur.pit(), cur.pos() - 1);
1130 space_font.setLanguage(lang);
1131 par.setFont(cur.pos() - 1, space_font);
1135 pos_type pos = cur.pos();
1136 if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE &&
1137 cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY &&
1138 c == '-' && pos > 0) {
1139 if (par.getChar(pos - 1) == '-') {
1140 // convert "--" to endash
1141 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1144 } else if (par.getChar(pos - 1) == 0x2013) {
1145 // convert "---" to emdash
1146 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1152 par.insertChar(pos, c, cur.current_font,
1153 cur.buffer()->params().track_changes);
1154 cur.checkBufferStructure();
1156 // cur.screenUpdateFlags(Update::Force);
1157 bool boundary = cur.boundary()
1158 || tm.isRTLBoundary(cur.pit(), pos + 1);
1159 setCursor(cur, cur.pit(), pos + 1, false, boundary);
1164 void Text::charInserted(Cursor & cur)
1166 Paragraph & par = cur.paragraph();
1168 // register word if a non-letter was entered
1170 && !par.isWordSeparator(cur.pos() - 2)
1171 && par.isWordSeparator(cur.pos() - 1)) {
1172 // get the word in front of cursor
1173 LBUFERR(this == cur.text());
1179 // the cursor set functions have a special mechanism. When they
1180 // realize, that you left an empty paragraph, they will delete it.
1182 bool Text::cursorForwardOneWord(Cursor & cur)
1184 LBUFERR(this == cur.text());
1186 if (lyxrc.mac_like_cursor_movement) {
1187 DocIterator dit(cur);
1188 DocIterator prv(cur);
1189 bool inword = false;
1190 bool intext = dit.inTexted();
1191 while (!dit.atEnd()) {
1192 if (dit.inTexted()) { // no paragraphs in mathed
1193 Paragraph const & par = dit.paragraph();
1194 pos_type const pos = dit.pos();
1196 if (!par.isDeleted(pos)) {
1197 bool wordsep = par.isWordSeparator(pos);
1198 if (inword && wordsep)
1199 break; // stop at word end
1200 else if (!inword && !wordsep)
1204 } else if (intext) {
1205 // move to end of math
1206 while (!dit.inTexted() && !dit.atEnd()) dit.forwardPos();
1210 dit.forwardPosIgnoreCollapsed();
1212 if (dit.atEnd()) dit = prv;
1213 if (dit == cur) return false; // we didn't move
1216 // see comment above
1217 cur.bv().checkDepm(cur, orig);
1220 pos_type const lastpos = cur.lastpos();
1221 pit_type pit = cur.pit();
1222 pos_type pos = cur.pos();
1223 Paragraph const & par = cur.paragraph();
1225 // Paragraph boundary is a word boundary
1226 if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
1227 if (pit != cur.lastpit())
1228 return setCursor(cur, pit + 1, 0);
1233 LASSERT(pos < lastpos, return false); // see above
1234 if (!par.isWordSeparator(pos))
1235 while (pos != lastpos && !par.isWordSeparator(pos))
1237 else if (par.isChar(pos))
1238 while (pos != lastpos && par.isChar(pos))
1240 else if (!par.isSpace(pos)) // non-char inset
1243 // Skip over white space
1244 while (pos != lastpos && par.isSpace(pos))
1247 // Don't skip a separator inset at the end of a paragraph
1248 if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
1251 return setCursor(cur, pit, pos);
1256 bool Text::cursorBackwardOneWord(Cursor & cur)
1258 LBUFERR(this == cur.text());
1260 if (lyxrc.mac_like_cursor_movement) {
1261 DocIterator dit(cur);
1262 bool inword = false;
1263 bool intext = dit.inTexted();
1264 while (!dit.atBegin()) {
1265 DocIterator prv(dit);
1266 dit.backwardPosIgnoreCollapsed();
1267 if (dit.inTexted()) { // no paragraphs in mathed
1268 Paragraph const & par = dit.paragraph();
1269 pos_type pos = dit.pos();
1271 if (!par.isDeleted(pos)) {
1272 bool wordsep = par.isWordSeparator(pos);
1273 if (inword && wordsep) {
1275 break; // stop at word begin
1276 } else if (!inword && !wordsep)
1280 } else if (intext) {
1281 // move to begin of math
1282 while (!dit.inTexted() && !dit.atBegin()) dit.backwardPos();
1286 if (dit == cur) return false; // we didn't move
1289 // see comment above cursorForwardOneWord
1290 cur.bv().checkDepm(cur, orig);
1293 Paragraph const & par = cur.paragraph();
1294 pit_type const pit = cur.pit();
1295 pos_type pos = cur.pos();
1297 // Paragraph boundary is a word boundary
1298 if (pos == 0 && pit != 0) {
1299 Paragraph & prevpar = getPar(pit - 1);
1300 pos = prevpar.size();
1301 // Don't stop after an environment separator
1302 if (pos && prevpar.isEnvSeparator(pos - 1))
1304 return setCursor(cur, pit - 1, pos);
1306 // Skip over white space
1307 while (pos != 0 && par.isSpace(pos - 1))
1310 if (pos != 0 && !par.isWordSeparator(pos - 1))
1311 while (pos != 0 && !par.isWordSeparator(pos - 1))
1313 else if (pos != 0 && par.isChar(pos - 1))
1314 while (pos != 0 && par.isChar(pos - 1))
1316 else if (pos != 0 && !par.isSpace(pos - 1)) // non-char inset
1319 return setCursor(cur, pit, pos);
1324 bool Text::cursorVisLeftOneWord(Cursor & cur)
1326 LBUFERR(this == cur.text());
1328 pos_type left_pos, right_pos;
1330 Cursor temp_cur = cur;
1332 // always try to move at least once...
1333 while (temp_cur.posVisLeft(true /* skip_inset */)) {
1335 // collect some information about current cursor position
1336 temp_cur.getSurroundingPos(left_pos, right_pos);
1337 bool left_is_letter =
1338 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1339 bool right_is_letter =
1340 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1342 // if we're not at a letter/non-letter boundary, continue moving
1343 if (left_is_letter == right_is_letter)
1346 // we should stop when we have an LTR word on our right or an RTL word
1348 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1349 temp_cur.buffer()->params(), left_pos).isRightToLeft())
1350 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1351 temp_cur.buffer()->params(), right_pos).isRightToLeft()))
1355 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1356 true, temp_cur.boundary());
1360 bool Text::cursorVisRightOneWord(Cursor & cur)
1362 LBUFERR(this == cur.text());
1364 pos_type left_pos, right_pos;
1366 Cursor temp_cur = cur;
1368 // always try to move at least once...
1369 while (temp_cur.posVisRight(true /* skip_inset */)) {
1371 // collect some information about current cursor position
1372 temp_cur.getSurroundingPos(left_pos, right_pos);
1373 bool left_is_letter =
1374 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1375 bool right_is_letter =
1376 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1378 // if we're not at a letter/non-letter boundary, continue moving
1379 if (left_is_letter == right_is_letter)
1382 // we should stop when we have an LTR word on our right or an RTL word
1384 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1385 temp_cur.buffer()->params(),
1386 left_pos).isRightToLeft())
1387 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1388 temp_cur.buffer()->params(),
1389 right_pos).isRightToLeft()))
1393 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1394 true, temp_cur.boundary());
1398 void Text::selectWord(Cursor & cur, word_location loc)
1400 LBUFERR(this == cur.text());
1401 CursorSlice from = cur.top();
1403 getWord(from, to, loc);
1404 if (cur.top() != from)
1405 setCursor(cur, from.pit(), from.pos());
1408 if (!cur.selection())
1410 setCursor(cur, to.pit(), to.pos());
1412 cur.setWordSelection(true);
1416 void Text::expandWordSel(Cursor & cur)
1418 // get selection of word around cur
1421 c.text()->selectWord(c, WHOLE_WORD);
1422 // use the correct word boundary, depending on selection direction
1423 if (cur.top() > cur.normalAnchor())
1424 cur.pos() = c.selEnd().pos();
1426 cur.pos() = c.selBegin().pos();
1430 void Text::selectAll(Cursor & cur)
1432 LBUFERR(this == cur.text());
1433 if (cur.lastpos() == 0 && cur.lastpit() == 0)
1435 // If the cursor is at the beginning, make sure the cursor ends there
1436 if (cur.pit() == 0 && cur.pos() == 0) {
1437 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1439 setCursor(cur, 0, 0);
1441 setCursor(cur, 0, 0);
1443 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1449 // Select the word currently under the cursor when no
1450 // selection is currently set
1451 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
1453 LBUFERR(this == cur.text());
1454 if (cur.selection())
1456 selectWord(cur, loc);
1457 return cur.selection();
1461 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
1463 LBUFERR(this == cur.text());
1465 if (!cur.selection()) {
1466 if (!selectChange(cur))
1470 cur.recordUndoSelection();
1472 pit_type begPit = cur.selectionBegin().pit();
1473 pit_type endPit = cur.selectionEnd().pit();
1475 pos_type begPos = cur.selectionBegin().pos();
1476 pos_type endPos = cur.selectionEnd().pos();
1478 // keep selection info, because endPos becomes invalid after the first loop
1479 bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size());
1481 // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
1482 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1483 pos_type parSize = pars_[pit].size();
1485 // ignore empty paragraphs; otherwise, an assertion will fail for
1486 // acceptChanges(bparams, 0, 0) or rejectChanges(bparams, 0, 0)
1490 // do not consider first paragraph if the cursor starts at pos size()
1491 if (pit == begPit && begPos == parSize)
1494 // do not consider last paragraph if the cursor ends at pos 0
1495 if (pit == endPit && endPos == 0)
1496 break; // last iteration anyway
1498 pos_type const left = (pit == begPit ? begPos : 0);
1499 pos_type const right = (pit == endPit ? endPos : parSize);
1502 // there is no change here
1506 pars_[pit].acceptChanges(left, right);
1508 pars_[pit].rejectChanges(left, right);
1512 // next, accept/reject imaginary end-of-par characters
1514 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1515 pos_type pos = pars_[pit].size();
1517 // skip if the selection ends before the end-of-par
1518 if (pit == endPit && endsBeforeEndOfPar)
1519 break; // last iteration anyway
1521 // skip if this is not the last paragraph of the document
1522 // note: the user should be able to accept/reject the par break of the last par!
1523 if (pit == endPit && pit + 1 != int(pars_.size()))
1524 break; // last iteration anway
1527 if (pars_[pit].isInserted(pos)) {
1528 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1529 } else if (pars_[pit].isDeleted(pos)) {
1530 if (pit + 1 == int(pars_.size())) {
1531 // we cannot remove a par break at the end of the last paragraph;
1532 // instead, we mark it unchanged
1533 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1535 mergeParagraph(cur.buffer()->params(), pars_, pit);
1541 if (pars_[pit].isDeleted(pos)) {
1542 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1543 } else if (pars_[pit].isInserted(pos)) {
1544 if (pit + 1 == int(pars_.size())) {
1545 // we mark the par break at the end of the last paragraph unchanged
1546 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1548 mergeParagraph(cur.buffer()->params(), pars_, pit);
1556 // finally, invoke the DEPM
1557 deleteEmptyParagraphMechanism(begPit, endPit, begPos, endPos,
1558 cur.buffer()->params().track_changes);
1561 cur.clearSelection();
1562 setCursorIntern(cur, begPit, begPos);
1563 cur.screenUpdateFlags(Update::Force);
1564 cur.forceBufferUpdate();
1568 void Text::acceptChanges()
1570 BufferParams const & bparams = owner_->buffer().params();
1571 lyx::acceptChanges(pars_, bparams);
1572 deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes);
1576 void Text::rejectChanges()
1578 BufferParams const & bparams = owner_->buffer().params();
1579 pit_type pars_size = static_cast<pit_type>(pars_.size());
1581 // first, reject changes within each individual paragraph
1582 // (do not consider end-of-par)
1583 for (pit_type pit = 0; pit < pars_size; ++pit) {
1584 if (!pars_[pit].empty()) // prevent assertion failure
1585 pars_[pit].rejectChanges(0, pars_[pit].size());
1588 // next, reject imaginary end-of-par characters
1589 for (pit_type pit = 0; pit < pars_size; ++pit) {
1590 pos_type pos = pars_[pit].size();
1592 if (pars_[pit].isDeleted(pos)) {
1593 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1594 } else if (pars_[pit].isInserted(pos)) {
1595 if (pit == pars_size - 1) {
1596 // we mark the par break at the end of the last
1597 // paragraph unchanged
1598 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1600 mergeParagraph(bparams, pars_, pit);
1607 // finally, invoke the DEPM
1608 deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes);
1612 void Text::deleteWordForward(Cursor & cur, bool const force)
1614 LBUFERR(this == cur.text());
1615 if (cur.lastpos() == 0)
1619 cur.selection(true);
1620 cursorForwardOneWord(cur);
1622 if (force || !cur.confirmDeletion()) {
1623 cutSelection(cur, false);
1624 cur.checkBufferStructure();
1630 void Text::deleteWordBackward(Cursor & cur, bool const force)
1632 LBUFERR(this == cur.text());
1633 if (cur.lastpos() == 0)
1634 cursorBackward(cur);
1637 cur.selection(true);
1638 cursorBackwardOneWord(cur);
1640 if (force || !cur.confirmDeletion()) {
1641 cutSelection(cur, false);
1642 cur.checkBufferStructure();
1648 // Kill to end of line.
1649 void Text::changeCase(Cursor & cur, TextCase action, bool partial)
1651 LBUFERR(this == cur.text());
1655 bool const gotsel = cur.selection();
1657 from = cur.selBegin();
1661 getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
1662 cursorForwardOneWord(cur);
1665 cur.recordUndoSelection();
1667 pit_type begPit = from.pit();
1668 pit_type endPit = to.pit();
1670 pos_type begPos = from.pos();
1671 pos_type endPos = to.pos();
1673 pos_type right = 0; // needed after the for loop
1675 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1676 Paragraph & par = pars_[pit];
1677 pos_type const pos = (pit == begPit ? begPos : 0);
1678 right = (pit == endPit ? endPos : par.size());
1679 par.changeCase(cur.buffer()->params(), pos, right, action);
1682 // the selection may have changed due to logically-only deleted chars
1684 setCursor(cur, begPit, begPos);
1686 setCursor(cur, endPit, right);
1689 setCursor(cur, endPit, right);
1691 cur.checkBufferStructure();
1695 bool Text::handleBibitems(Cursor & cur)
1697 if (cur.paragraph().layout().labeltype != LABEL_BIBLIO)
1703 BufferParams const & bufparams = cur.buffer()->params();
1704 Paragraph const & par = cur.paragraph();
1705 Cursor prevcur = cur;
1706 if (cur.pit() > 0) {
1708 prevcur.pos() = prevcur.lastpos();
1710 Paragraph const & prevpar = prevcur.paragraph();
1712 // if a bibitem is deleted, merge with previous paragraph
1713 // if this is a bibliography item as well
1714 if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
1715 cur.recordUndo(prevcur.pit());
1716 mergeParagraph(bufparams, cur.text()->paragraphs(),
1718 cur.forceBufferUpdate();
1719 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1720 cur.screenUpdateFlags(Update::Force);
1724 // otherwise reset to default
1725 cur.paragraph().setPlainOrDefaultLayout(bufparams.documentClass());
1730 bool Text::erase(Cursor & cur)
1732 LASSERT(this == cur.text(), return false);
1733 bool needsUpdate = false;
1734 Paragraph & par = cur.paragraph();
1736 if (cur.pos() != cur.lastpos()) {
1737 // this is the code for a normal delete, not pasting
1739 cur.recordUndo(DELETE_UNDO);
1740 bool const was_inset = cur.paragraph().isInset(cur.pos());
1741 if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes))
1742 // the character has been logically deleted only => skip it
1743 cur.top().forwardPos();
1746 cur.forceBufferUpdate();
1748 cur.checkBufferStructure();
1751 if (cur.pit() == cur.lastpit())
1752 return dissolveInset(cur);
1754 if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1755 cur.recordUndo(DELETE_UNDO);
1756 par.setChange(cur.pos(), Change(Change::DELETED));
1760 setCursorIntern(cur, cur.pit() + 1, 0);
1761 needsUpdate = backspacePos0(cur);
1765 needsUpdate |= handleBibitems(cur);
1768 // Make sure the cursor is correct. Is this really needed?
1769 // No, not really... at least not here!
1770 cur.top().setPitPos(cur.pit(), cur.pos());
1771 cur.checkBufferStructure();
1778 bool Text::backspacePos0(Cursor & cur)
1780 LBUFERR(this == cur.text());
1784 BufferParams const & bufparams = cur.buffer()->params();
1785 ParagraphList & plist = cur.text()->paragraphs();
1786 Paragraph const & par = cur.paragraph();
1787 Cursor prevcur = cur;
1789 prevcur.pos() = prevcur.lastpos();
1790 Paragraph const & prevpar = prevcur.paragraph();
1792 // is it an empty paragraph?
1793 if (cur.lastpos() == 0
1794 || (cur.lastpos() == 1 && par.isSeparator(0))) {
1795 cur.recordUndo(prevcur.pit());
1796 plist.erase(plist.iterator_at(cur.pit()));
1798 // is previous par empty?
1799 else if (prevcur.lastpos() == 0
1800 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
1801 cur.recordUndo(prevcur.pit());
1802 plist.erase(plist.iterator_at(prevcur.pit()));
1804 // FIXME: Do we really not want to allow this???
1805 // Pasting is not allowed, if the paragraphs have different
1806 // layouts. I think it is a real bug of all other
1807 // word processors to allow it. It confuses the user.
1808 // Correction: Pasting is always allowed with standard-layout
1809 // or the empty layout.
1811 cur.recordUndo(prevcur.pit());
1812 mergeParagraph(bufparams, plist, prevcur.pit());
1815 cur.forceBufferUpdate();
1816 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1822 bool Text::backspace(Cursor & cur)
1824 LBUFERR(this == cur.text());
1825 bool needsUpdate = false;
1826 if (cur.pos() == 0) {
1828 return dissolveInset(cur);
1830 Cursor prev_cur = cur;
1833 if (!cur.paragraph().empty()
1834 && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1835 cur.recordUndo(prev_cur.pit(), prev_cur.pit());
1836 prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
1837 setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
1840 // The cursor is at the beginning of a paragraph, so
1841 // the backspace will collapse two paragraphs into one.
1842 needsUpdate = backspacePos0(cur);
1845 // this is the code for a normal backspace, not pasting
1847 cur.recordUndo(DELETE_UNDO);
1848 // We used to do cursorBackwardIntern() here, but it is
1849 // not a good idea since it triggers the auto-delete
1850 // mechanism. So we do a cursorBackwardIntern()-lite,
1851 // without the dreaded mechanism. (JMarc)
1852 setCursorIntern(cur, cur.pit(), cur.pos() - 1,
1853 false, cur.boundary());
1854 bool const was_inset = cur.paragraph().isInset(cur.pos());
1855 cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes);
1857 cur.forceBufferUpdate();
1859 cur.checkBufferStructure();
1862 if (cur.pos() == cur.lastpos())
1863 cur.setCurrentFont();
1865 needsUpdate |= handleBibitems(cur);
1867 // A singlePar update is not enough in this case.
1868 // cur.screenUpdateFlags(Update::Force);
1869 cur.top().setPitPos(cur.pit(), cur.pos());
1875 bool Text::dissolveInset(Cursor & cur)
1877 LASSERT(this == cur.text(), return false);
1879 if (isMainText() || cur.inset().nargs() != 1)
1882 cur.recordUndoInset();
1884 cur.selHandle(false);
1885 // save position inside inset
1886 pos_type spos = cur.pos();
1887 pit_type spit = cur.pit();
1888 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1890 // update cursor offset
1894 // remember position outside inset to delete inset later
1895 // we do not do it now to avoid memory reuse issues (see #10667).
1896 DocIterator inset_it = cur;
1900 Buffer & b = *cur.buffer();
1901 // Is there anything in this text?
1902 if (inset_non_empty) {
1904 // we clear the cache so that we won't get conflicts with labels
1905 // that get pasted into the buffer. we should update this before
1906 // its being empty matters. if not (i.e., if we encounter bugs),
1907 // then this should instead be:
1908 // cur.buffer().updateBuffer();
1909 // but we'll try the cheaper solution here.
1910 cur.buffer()->clearReferenceCache();
1912 ParagraphList & plist = paragraphs();
1913 if (!lyxrc.ct_markup_copied)
1914 // Do not revive deleted text
1915 lyx::acceptChanges(plist, b.params());
1917 // ERT paragraphs have the Language latex_language.
1918 // This is invalid outside of ERT, so we need to
1919 // change it to the buffer language.
1920 for (auto & p : plist)
1921 p.changeLanguage(b.params(), latex_language, b.language());
1923 /* If the inset is the only thing in paragraph and the layout
1924 * is not plain, then the layout of the first paragraph of
1925 * inset should be remembered.
1926 * FIXME: this does not work as expected when change tracking
1927 * is on However, we do not really know what to do in this
1930 DocumentClass const & tclass = cur.buffer()->params().documentClass();
1931 if (inset_it.lastpos() == 1
1932 && !tclass.isPlainLayout(plist[0].layout())
1933 && !tclass.isDefaultLayout(plist[0].layout())) {
1934 // Copy all parameters except depth.
1935 Paragraph & par = cur.paragraph();
1936 par.setLayout(plist[0].layout());
1937 depth_type const dpth = par.getDepth();
1938 par.params() = plist[0].params();
1939 par.params().depth(dpth);
1942 pasteParagraphList(cur, plist, b.params().documentClassPtr(),
1943 b.params().authors(),
1944 b.errorList("Paste"));
1947 // delete the inset now
1948 inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes);
1951 cur.pit() = min(cur.lastpit(), spit);
1952 cur.pos() = min(cur.lastpos(), spos);
1953 // Ensure the current language is set correctly (bug 6292)
1954 cur.text()->setCursor(cur, cur.pit(), cur.pos());
1955 cur.clearSelection();
1957 cur.forceBufferUpdate();
1963 bool Text::splitInset(Cursor & cur)
1965 LASSERT(this == cur.text(), return false);
1967 if (isMainText() || cur.inset().nargs() != 1)
1971 if (cur.selection()) {
1972 // start from selection begin
1973 setCursor(cur, cur.selBegin().pit(), cur.selBegin().pos());
1974 cur.clearSelection();
1976 // save split position inside inset
1977 // (we need to copy the whole inset first)
1978 pos_type spos = cur.pos();
1979 pit_type spit = cur.pit();
1980 // some things only need to be done if the inset has content
1981 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1983 // move right before the inset
1986 // remember position outside inset
1987 pos_type ipos = cur.pos();
1988 pit_type ipit = cur.pit();
1993 cap::copySelectionToTemp(cur);
1994 cur.clearSelection();
1996 // paste copied inset
1997 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
1998 cur.forceBufferUpdate();
2000 // if the inset has text, cut after split position
2001 // and paste to new inset
2002 if (inset_non_empty) {
2003 // go back to first inset
2004 cur.text()->setCursor(cur, ipit, ipos);
2006 setCursor(cur, spit, spos);
2008 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
2010 // Remember whether there was something cut that has to be pasted below
2012 bool const hasCut = cur.selection();
2013 cap::cutSelectionToTemp(cur);
2015 cur.selHandle(false);
2017 bool atlastpos = false;
2018 if (cur.pos() == 0 && cur.pit() > 0) {
2019 // if we are at par start, remove this par
2020 cur.text()->backspace(cur);
2021 cur.forceBufferUpdate();
2022 } else if (cur.pos() == cur.lastpos())
2024 // Move out of and jump over inset
2032 cur.text()->selectAll(cur);
2033 cutSelection(cur, false);
2034 // If there was something cut paste it
2036 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
2037 cur.text()->setCursor(cur, 0, 0);
2038 if (atlastpos && cur.paragraph().isFreeSpacing() && cur.paragraph().empty()) {
2039 // We started from par end, remove extra empty par in free spacing insets
2040 cur.text()->erase(cur);
2041 cur.forceBufferUpdate();
2050 void Text::getWord(CursorSlice & from, CursorSlice & to,
2051 word_location const loc) const
2054 pars_[to.pit()].locateWord(from.pos(), to.pos(), loc);
2058 void Text::write(ostream & os) const
2060 Buffer const & buf = owner_->buffer();
2061 ParagraphList::const_iterator pit = paragraphs().begin();
2062 ParagraphList::const_iterator end = paragraphs().end();
2064 for (; pit != end; ++pit)
2065 pit->write(os, buf.params(), dth);
2067 // Close begin_deeper
2068 for(; dth > 0; --dth)
2069 os << "\n\\end_deeper";
2073 bool Text::read(Lexer & lex,
2074 ErrorList & errorList, InsetText * insetPtr)
2076 Buffer const & buf = owner_->buffer();
2077 depth_type depth = 0;
2080 while (lex.isOK()) {
2082 string const token = lex.getString();
2087 if (token == "\\end_inset")
2090 if (token == "\\end_body")
2093 if (token == "\\begin_body")
2096 if (token == "\\end_document") {
2101 if (token == "\\begin_layout") {
2102 lex.pushToken(token);
2105 par.setInsetOwner(insetPtr);
2106 par.params().depth(depth);
2107 par.setFont(0, Font(inherit_font, buf.params().language));
2108 pars_.push_back(par);
2109 readParagraph(pars_.back(), lex, errorList);
2111 // register the words in the global word list
2112 pars_.back().updateWords();
2113 } else if (token == "\\begin_deeper") {
2115 } else if (token == "\\end_deeper") {
2117 lex.printError("\\end_deeper: " "depth is already null");
2121 LYXERR0("Handling unknown body token: `" << token << '\'');
2125 // avoid a crash on weird documents (bug 4859)
2126 if (pars_.empty()) {
2128 par.setInsetOwner(insetPtr);
2129 par.params().depth(depth);
2130 par.setFont(0, Font(inherit_font,
2131 buf.params().language));
2132 par.setPlainOrDefaultLayout(buf.params().documentClass());
2133 pars_.push_back(par);
2140 // Returns the current state (font, depth etc.) as a message for status bar.
2141 docstring Text::currentState(CursorData const & cur, bool devel_mode) const
2143 LBUFERR(this == cur.text());
2144 Buffer & buf = *cur.buffer();
2145 Paragraph const & par = cur.paragraph();
2146 odocstringstream os;
2148 if (buf.params().track_changes)
2149 os << _("[Change Tracking] ");
2151 Change change = par.lookupChange(cur.pos());
2153 if (change.changed()) {
2154 docstring const author =
2155 buf.params().authors().get(change.author).nameAndEmail();
2156 docstring const date = formatted_datetime(change.changetime);
2157 os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
2161 // I think we should only show changes from the default
2163 // No, from the document font (MV)
2164 Font font = cur.real_current_font;
2165 font.fontInfo().reduce(buf.params().getFont().fontInfo());
2167 os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
2169 // The paragraph depth
2170 int depth = par.getDepth();
2172 os << bformat(_(", Depth: %1$d"), depth);
2174 // The paragraph spacing, but only if different from
2176 Spacing const & spacing = par.params().spacing();
2177 if (!spacing.isDefault()) {
2178 os << _(", Spacing: ");
2179 switch (spacing.getSpace()) {
2180 case Spacing::Single:
2183 case Spacing::Onehalf:
2186 case Spacing::Double:
2189 case Spacing::Other:
2190 os << _("Other (") << from_ascii(spacing.getValueAsString()) << ')';
2192 case Spacing::Default:
2193 // should never happen, do nothing
2198 // Custom text style
2199 InsetLayout const & layout = cur.inset().getLayout();
2200 if (layout.lyxtype() == InsetLyXType::CHARSTYLE)
2201 os << _(", Style: ") << translateIfPossible(layout.labelstring());
2204 os << _(", Inset: ") << &cur.inset();
2205 if (cur.lastidx() > 0)
2206 os << _(", Cell: ") << cur.idx();
2207 os << _(", Paragraph: ") << cur.pit();
2208 os << _(", Id: ") << par.id();
2209 os << _(", Position: ") << cur.pos();
2210 // FIXME: Why is the check for par.size() needed?
2211 // We are called with cur.pos() == par.size() quite often.
2212 if (!par.empty() && cur.pos() < par.size()) {
2213 // Force output of code point, not character
2214 size_t const c = par.getChar(cur.pos());
2215 if (c == META_INSET)
2216 os << ", Char: INSET";
2218 os << _(", Char: 0x") << hex << c;
2220 os << _(", Boundary: ") << cur.boundary();
2221 // Row & row = cur.textRow();
2222 // os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
2228 docstring Text::getPossibleLabel(DocIterator const & cur) const
2230 pit_type textpit = cur.pit();
2231 Layout const * layout = &(pars_[textpit].layout());
2233 // Will contain the label prefix.
2236 // For captions, we just take the caption type
2237 Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
2238 if (caption_inset) {
2239 string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
2240 FloatList const & fl = cur.buffer()->params().documentClass().floats();
2241 if (fl.typeExist(ftype)) {
2242 Floating const & flt = fl.getType(ftype);
2243 name = from_utf8(flt.refPrefix());
2246 name = from_utf8(ftype.substr(0,3));
2248 // For section, subsection, etc...
2249 if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) {
2250 Layout const * layout2 = &(pars_[textpit - 1].layout());
2251 if (layout2->latextype != LATEX_PARAGRAPH) {
2256 if (layout->latextype != LATEX_PARAGRAPH)
2257 name = layout->refprefix;
2259 // If none of the above worked, see if the inset knows.
2261 InsetLayout const & il = cur.inset().getLayout();
2262 name = il.refprefix();
2267 docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE);
2269 // The return string of math matrices might contain linebreaks
2270 par_text = subst(par_text, '\n', '-');
2271 int const numwords = 3;
2272 for (int i = 0; i < numwords; ++i) {
2273 if (par_text.empty())
2276 par_text = split(par_text, head, ' ');
2277 // Is it legal to use spaces in labels ?
2283 // Make sure it isn't too long
2284 unsigned int const max_label_length = 32;
2285 if (text.size() > max_label_length)
2286 text.resize(max_label_length);
2289 text = name + ':' + text;
2291 // We need a unique label
2292 docstring label = text;
2294 while (cur.buffer()->activeLabel(label)) {
2295 label = text + '-' + convert<docstring>(i);
2303 docstring Text::asString(int options) const
2305 return asString(0, pars_.size(), options);
2309 docstring Text::asString(pit_type beg, pit_type end, int options) const
2311 size_t i = size_t(beg);
2312 docstring str = pars_[i].asString(options);
2313 for (++i; i != size_t(end); ++i) {
2315 str += pars_[i].asString(options);
2321 void Text::shortenForOutliner(docstring & str, size_t const maxlen)
2323 support::truncateWithEllipsis(str, maxlen);
2324 for (char_type & c : str)
2325 if (c == L'\n' || c == L'\t')
2330 void Text::forOutliner(docstring & os, size_t const maxlen,
2331 bool const shorten) const
2333 pit_type end = pars_.size() - 1;
2334 if (0 <= end && !pars_[0].labelString().empty())
2335 os += pars_[0].labelString() + ' ';
2336 forOutliner(os, maxlen, 0, end, shorten);
2340 void Text::forOutliner(docstring & os, size_t const maxlen,
2341 pit_type pit_start, pit_type pit_end,
2342 bool const shorten) const
2344 size_t tmplen = shorten ? maxlen + 1 : maxlen;
2345 pit_type end = min(size_t(pit_end), pars_.size() - 1);
2347 for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) {
2350 // This function lets the first label be treated separately
2351 pars_[i].forOutliner(os, tmplen, false, !first);
2355 shortenForOutliner(os, maxlen);
2359 void Text::charsTranspose(Cursor & cur)
2361 LBUFERR(this == cur.text());
2363 pos_type pos = cur.pos();
2365 // If cursor is at beginning or end of paragraph, do nothing.
2366 if (pos == cur.lastpos() || pos == 0)
2369 Paragraph & par = cur.paragraph();
2371 // Get the positions of the characters to be transposed.
2372 pos_type pos1 = pos - 1;
2373 pos_type pos2 = pos;
2375 // In change tracking mode, ignore deleted characters.
2376 while (pos2 < cur.lastpos() && par.isDeleted(pos2))
2378 if (pos2 == cur.lastpos())
2381 while (pos1 >= 0 && par.isDeleted(pos1))
2386 // Don't do anything if one of the "characters" is not regular text.
2387 if (par.isInset(pos1) || par.isInset(pos2))
2390 // Store the characters to be transposed (including font information).
2391 char_type const char1 = par.getChar(pos1);
2393 par.getFontSettings(cur.buffer()->params(), pos1);
2395 char_type const char2 = par.getChar(pos2);
2397 par.getFontSettings(cur.buffer()->params(), pos2);
2399 // And finally, we are ready to perform the transposition.
2400 // Track the changes if Change Tracking is enabled.
2401 bool const trackChanges = cur.buffer()->params().track_changes;
2405 par.eraseChar(pos2, trackChanges);
2406 par.eraseChar(pos1, trackChanges);
2407 par.insertChar(pos1, char2, font2, trackChanges);
2408 par.insertChar(pos2, char1, font1, trackChanges);
2410 cur.checkBufferStructure();
2412 // After the transposition, move cursor to after the transposition.
2413 setCursor(cur, cur.pit(), pos2);
2418 DocIterator Text::macrocontextPosition() const
2420 return macrocontext_position_;
2424 void Text::setMacrocontextPosition(DocIterator const & pos)
2426 macrocontext_position_ = pos;
2430 bool Text::completionSupported(Cursor const & cur) const
2432 Paragraph const & par = cur.paragraph();
2433 return !cur.buffer()->isReadonly()
2436 && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos()))
2437 && !par.isWordSeparator(cur.pos() - 1);
2441 CompletionList const * Text::createCompletionList(Cursor const & cur) const
2443 WordList const & list = theWordList(cur.getFont().language()->lang());
2444 return new TextCompletionList(cur, list);
2448 bool Text::insertCompletion(Cursor & cur, docstring const & s)
2450 LBUFERR(cur.bv().cursor() == cur);
2451 if (cur.buffer()->isReadonly())
2455 cur.bv().cursor() = cur;
2456 if (!(cur.result().screenUpdate() & Update::Force))
2457 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
2462 docstring Text::completionPrefix(Cursor const & cur) const
2464 CursorSlice from = cur.top();
2465 CursorSlice to = from;
2466 getWord(from, to, PREVIOUS_WORD);
2468 return cur.paragraph().asString(from.pos(), to.pos());
2471 bool Text::isMainText() const
2473 return &owner_->buffer().text() == this;
2477 // Note that this is supposed to return a fully realized font.
2478 FontInfo Text::layoutFont(pit_type const pit) const
2480 Layout const & layout = pars_[pit].layout();
2482 if (!pars_[pit].getDepth()) {
2483 FontInfo lf = layout.resfont;
2484 // In case the default family has been customized
2485 if (layout.font.family() == INHERIT_FAMILY)
2486 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
2487 FontInfo icf = (!isMainText())
2488 // inside insets, we call the getFont() method
2490 // outside, we access the layout font directly
2491 : owner_->getLayout().font();
2496 FontInfo font = layout.font;
2497 // Realize with the fonts of lesser depth.
2498 //font.realize(outerFont(pit));
2499 font.realize(owner_->buffer().params().getFont().fontInfo());
2505 // Note that this is supposed to return a fully realized font.
2506 FontInfo Text::labelFont(Paragraph const & par) const
2508 Buffer const & buffer = owner_->buffer();
2509 Layout const & layout = par.layout();
2511 if (!par.getDepth()) {
2512 FontInfo lf = layout.reslabelfont;
2513 // In case the default family has been customized
2514 if (layout.labelfont.family() == INHERIT_FAMILY)
2515 lf.setFamily(buffer.params().getFont().fontInfo().family());
2519 FontInfo font = layout.labelfont;
2520 // Realize with the fonts of lesser depth.
2521 font.realize(buffer.params().getFont().fontInfo());
2527 void Text::setCharFont(pit_type pit,
2528 pos_type pos, Font const & fnt, Font const & display_font)
2530 Buffer const & buffer = owner_->buffer();
2532 Layout const & layout = pars_[pit].layout();
2534 // Get concrete layout font to reduce against
2535 FontInfo layoutfont;
2537 if (pos < pars_[pit].beginOfBody())
2538 layoutfont = layout.labelfont;
2540 layoutfont = layout.font;
2542 // Realize against environment font information
2543 if (pars_[pit].getDepth()) {
2545 while (!layoutfont.resolved() &&
2546 tp != pit_type(paragraphs().size()) &&
2547 pars_[tp].getDepth()) {
2549 if (tp != pit_type(paragraphs().size()))
2550 layoutfont.realize(pars_[tp].layout().font);
2554 // Inside inset, apply the inset's font attributes if any
2557 layoutfont.realize(display_font.fontInfo());
2559 layoutfont.realize(buffer.params().getFont().fontInfo());
2561 // Now, reduce font against full layout font
2562 font.fontInfo().reduce(layoutfont);
2564 pars_[pit].setFont(pos, font);
2568 void Text::setInsetFont(BufferView const & bv, pit_type pit,
2569 pos_type pos, Font const & font)
2571 Inset * const inset = pars_[pit].getInset(pos);
2572 LASSERT(inset && inset->resetFontEdit(), return);
2574 idx_type endidx = inset->nargs();
2575 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
2576 Text * text = cs.text();
2578 // last position of the cell
2579 CursorSlice cellend = cs;
2580 cellend.pit() = cellend.lastpit();
2581 cellend.pos() = cellend.lastpos();
2582 text->setFont(bv, cs, cellend, font);
2588 void Text::setLayout(pit_type start, pit_type end,
2589 docstring const & layout)
2591 // FIXME: make this work in multicell selection case
2592 LASSERT(start != end, return);
2594 Buffer const & buffer = owner_->buffer();
2595 BufferParams const & bp = buffer.params();
2596 Layout const & lyxlayout = bp.documentClass()[layout];
2598 for (pit_type pit = start; pit != end; ++pit) {
2599 Paragraph & par = pars_[pit];
2600 // Is this a separating paragraph? If so,
2601 // this needs to be standard layout
2602 bool const is_separator = par.size() == 1
2603 && par.isEnvSeparator(0);
2604 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
2605 if (lyxlayout.margintype == MARGIN_MANUAL)
2606 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
2609 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
2613 // set layout over selection and make a total rebreak of those paragraphs
2614 void Text::setLayout(Cursor & cur, docstring const & layout)
2616 LBUFERR(this == cur.text());
2618 pit_type start = cur.selBegin().pit();
2619 pit_type end = cur.selEnd().pit() + 1;
2620 cur.recordUndoSelection();
2621 setLayout(start, end, layout);
2623 cur.setCurrentFont();
2624 cur.forceBufferUpdate();
2628 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
2629 Paragraph const & par, int max_depth)
2631 int const depth = par.params().depth();
2632 if (type == Text::INC_DEPTH && depth < max_depth)
2634 if (type == Text::DEC_DEPTH && depth > 0)
2640 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
2642 LBUFERR(this == cur.text());
2643 // this happens when selecting several cells in tabular (bug 2630)
2644 if (cur.selBegin().idx() != cur.selEnd().idx())
2647 pit_type const beg = cur.selBegin().pit();
2648 pit_type const end = cur.selEnd().pit() + 1;
2649 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2651 for (pit_type pit = beg; pit != end; ++pit) {
2652 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
2654 max_depth = pars_[pit].getMaxDepthAfter();
2660 void Text::changeDepth(Cursor const & cur, DEPTH_CHANGE type)
2662 LBUFERR(this == cur.text());
2663 pit_type const beg = cur.selBegin().pit();
2664 pit_type const end = cur.selEnd().pit() + 1;
2665 cur.recordUndoSelection();
2666 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2668 for (pit_type pit = beg; pit != end; ++pit) {
2669 Paragraph & par = pars_[pit];
2670 if (lyx::changeDepthAllowed(type, par, max_depth)) {
2671 int const depth = par.params().depth();
2672 if (type == INC_DEPTH)
2673 par.params().depth(depth + 1);
2675 par.params().depth(depth - 1);
2677 max_depth = par.getMaxDepthAfter();
2679 // this handles the counter labels, and also fixes up
2680 // depth values for follow-on (child) paragraphs
2681 cur.forceBufferUpdate();
2685 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
2687 LASSERT(this == cur.text(), return);
2689 // If there is a selection, record undo before the cursor font is changed.
2690 if (cur.selection())
2691 cur.recordUndoSelection();
2693 // Set the current_font
2694 // Determine basis font
2695 FontInfo layoutfont;
2696 pit_type pit = cur.pit();
2697 if (cur.pos() < pars_[pit].beginOfBody())
2698 layoutfont = labelFont(pars_[pit]);
2700 layoutfont = layoutFont(pit);
2702 // Update current font
2703 cur.real_current_font.update(font,
2704 cur.buffer()->params().language,
2707 // Reduce to implicit settings
2708 cur.current_font = cur.real_current_font;
2709 cur.current_font.fontInfo().reduce(layoutfont);
2710 // And resolve it completely
2711 cur.real_current_font.fontInfo().realize(layoutfont);
2713 // if there is no selection that's all we need to do
2714 if (!cur.selection())
2717 // Ok, we have a selection.
2718 Font newfont = font;
2721 // Toggling behaves as follows: We check the first character of the
2722 // selection. If it's (say) got EMPH on, then we set to off; if off,
2723 // then to on. With families and the like, we set it to INHERIT, if
2724 // we already have it.
2725 CursorSlice const & sl = cur.selBegin();
2726 Text const & text = *sl.text();
2727 Paragraph const & par = text.getPar(sl.pit());
2729 // get font at the position
2730 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
2731 text.outerFont(sl.pit()));
2732 FontInfo const & oldfi = oldfont.fontInfo();
2734 FontInfo & newfi = newfont.fontInfo();
2736 FontFamily newfam = newfi.family();
2737 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
2738 newfam == oldfi.family())
2739 newfi.setFamily(INHERIT_FAMILY);
2741 FontSeries newser = newfi.series();
2742 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
2743 newfi.setSeries(INHERIT_SERIES);
2745 FontShape newshp = newfi.shape();
2746 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
2747 newshp == oldfi.shape())
2748 newfi.setShape(INHERIT_SHAPE);
2750 ColorCode newcol = newfi.color();
2751 if (newcol != Color_none && newcol != Color_inherit
2752 && newcol != Color_ignore && newcol == oldfi.color())
2753 newfi.setColor(Color_none);
2756 if (newfi.emph() == FONT_TOGGLE)
2757 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
2758 if (newfi.underbar() == FONT_TOGGLE)
2759 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
2760 if (newfi.strikeout() == FONT_TOGGLE)
2761 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
2762 if (newfi.xout() == FONT_TOGGLE)
2763 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
2764 if (newfi.uuline() == FONT_TOGGLE)
2765 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
2766 if (newfi.uwave() == FONT_TOGGLE)
2767 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
2768 if (newfi.noun() == FONT_TOGGLE)
2769 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
2770 if (newfi.number() == FONT_TOGGLE)
2771 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
2772 if (newfi.nospellcheck() == FONT_TOGGLE)
2773 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
2776 setFont(cur.bv(), cur.selectionBegin().top(),
2777 cur.selectionEnd().top(), newfont);
2781 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
2782 CursorSlice const & end, Font const & font)
2784 Buffer const & buffer = bv.buffer();
2786 // Don't use forwardChar here as ditend might have
2787 // pos() == lastpos() and forwardChar would miss it.
2788 // Can't use forwardPos either as this descends into
2790 Language const * language = buffer.params().language;
2791 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
2792 if (dit.pos() == dit.lastpos())
2794 pit_type const pit = dit.pit();
2795 pos_type const pos = dit.pos();
2796 Inset * inset = pars_[pit].getInset(pos);
2797 if (inset && inset->resetFontEdit()) {
2798 // We need to propagate the font change to all
2799 // text cells of the inset (bugs 1973, 6919).
2800 setInsetFont(bv, pit, pos, font);
2802 TextMetrics const & tm = bv.textMetrics(this);
2803 Font f = tm.displayFont(pit, pos);
2804 f.update(font, language);
2805 setCharFont(pit, pos, f, tm.font_);
2806 // font change may change language...
2807 // spell checker has to know that
2808 pars_[pit].requestSpellCheck(pos);
2813 bool Text::cursorTop(Cursor & cur)
2815 LBUFERR(this == cur.text());
2816 return setCursor(cur, 0, 0);
2820 bool Text::cursorBottom(Cursor & cur)
2822 LBUFERR(this == cur.text());
2823 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
2827 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
2829 LBUFERR(this == cur.text());
2830 // If the mask is completely neutral, tell user
2831 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
2832 // Could only happen with user style
2833 cur.message(_("No font change defined."));
2837 // Try implicit word selection
2838 // If there is a change in the language the implicit word selection
2840 CursorSlice const resetCursor = cur.top();
2841 bool const implicitSelection =
2842 font.language() == ignore_language
2843 && font.fontInfo().number() == FONT_IGNORE
2844 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
2847 setFont(cur, font, toggleall);
2849 // Implicit selections are cleared afterwards
2850 // and cursor is set to the original position.
2851 if (implicitSelection) {
2852 cur.clearSelection();
2853 cur.top() = resetCursor;
2857 // if there was no selection at all, the point was to change cursor font.
2858 // Otherwise, we want to reset it to local text font.
2859 if (cur.selection() || implicitSelection)
2860 cur.setCurrentFont();
2864 docstring Text::getStringForDialog(Cursor & cur)
2866 LBUFERR(this == cur.text());
2868 if (cur.selection())
2869 return cur.selectionAsString(false);
2871 // Try implicit word selection. If there is a change
2872 // in the language the implicit word selection is
2874 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2875 docstring const & retval = cur.selectionAsString(false);
2876 cur.clearSelection();
2881 void Text::setLabelWidthStringToSequence(Cursor const & cur,
2882 docstring const & s)
2885 // Find first of same layout in sequence
2886 while (!isFirstInSequence(c.pit())) {
2887 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
2890 // now apply label width string to every par
2892 depth_type const depth = c.paragraph().getDepth();
2893 Layout const & layout = c.paragraph().layout();
2894 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
2895 while (c.paragraph().getDepth() > depth) {
2897 if (c.pit() > c.lastpit())
2900 if (c.paragraph().getDepth() < depth)
2902 if (c.paragraph().layout() != layout)
2905 c.paragraph().setLabelWidthString(s);
2910 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
2912 LBUFERR(cur.text());
2915 string const argument = to_utf8(arg);
2916 depth_type priordepth = -1;
2919 c.setCursor(cur.selectionBegin());
2920 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
2921 Paragraph & par = c.paragraph();
2922 ParagraphParameters params = par.params();
2923 params.read(argument, merge);
2924 // Changes to label width string apply to all paragraphs
2925 // with same layout in a sequence.
2926 // Do this only once for a selected range of paragraphs
2927 // of the same layout and depth.
2929 par.params().apply(params, par.layout());
2930 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2931 setLabelWidthStringToSequence(c, params.labelWidthString());
2932 priordepth = par.getDepth();
2933 priorlayout = par.layout();
2938 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
2940 LBUFERR(cur.text());
2942 depth_type priordepth = -1;
2945 c.setCursor(cur.selectionBegin());
2946 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
2947 Paragraph & par = c.paragraph();
2948 // Changes to label width string apply to all paragraphs
2949 // with same layout in a sequence.
2950 // Do this only once for a selected range of paragraphs
2951 // of the same layout and depth.
2953 par.params().apply(p, par.layout());
2954 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2955 setLabelWidthStringToSequence(c,
2956 par.params().labelWidthString());
2957 priordepth = par.getDepth();
2958 priorlayout = par.layout();
2963 // this really should just insert the inset and not move the cursor.
2964 void Text::insertInset(Cursor & cur, Inset * inset)
2966 LBUFERR(this == cur.text());
2968 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
2969 Change(cur.buffer()->params().track_changes
2970 ? Change::INSERTED : Change::UNCHANGED));
2974 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
2975 bool setfont, bool boundary)
2977 TextMetrics const & tm = cur.bv().textMetrics(this);
2978 bool const update_needed = !tm.contains(pit);
2980 setCursorIntern(cur, pit, pos, setfont, boundary);
2981 return cur.bv().checkDepm(cur, old) || update_needed;
2985 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
2986 bool setfont, bool boundary)
2988 LBUFERR(this == cur.text());
2989 cur.boundary(boundary);
2990 cur.top().setPitPos(pit, pos);
2992 cur.setCurrentFont();
2996 bool Text::checkAndActivateInset(Cursor & cur, bool front)
2998 if (front && cur.pos() == cur.lastpos())
3000 if (!front && cur.pos() == 0)
3002 Inset * inset = front ? cur.nextInset() : cur.prevInset();
3003 if (!inset || !inset->editable())
3005 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3008 * Apparently, when entering an inset we are expected to be positioned
3009 * *before* it in the containing paragraph, regardless of the direction
3010 * from which we are entering. Otherwise, cursor placement goes awry,
3011 * and when we exit from the beginning, we'll be placed *after* the
3016 inset->edit(cur, front);
3017 cur.setCurrentFont();
3018 cur.boundary(false);
3023 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
3025 if (cur.pos() == -1)
3027 if (cur.pos() == cur.lastpos())
3029 Paragraph & par = cur.paragraph();
3030 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
3031 if (!inset || !inset->editable())
3033 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3035 inset->edit(cur, movingForward,
3036 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
3037 cur.setCurrentFont();
3038 cur.boundary(false);
3043 bool Text::cursorBackward(Cursor & cur)
3045 // Tell BufferView to test for FitCursor in any case!
3046 cur.screenUpdateFlags(Update::FitCursor);
3048 // not at paragraph start?
3049 if (cur.pos() > 0) {
3050 // if on right side of boundary (i.e. not at paragraph end, but line end)
3051 // -> skip it, i.e. set boundary to true, i.e. go only logically left
3052 // there are some exceptions to ignore this: lineseps, newlines, spaces
3054 // some effectless debug code to see the values in the debugger
3055 bool bound = cur.boundary();
3056 int rowpos = cur.textRow().pos();
3057 int pos = cur.pos();
3058 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
3059 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
3060 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
3062 if (!cur.boundary() &&
3063 cur.textRow().pos() == cur.pos() &&
3064 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
3065 !cur.paragraph().isNewline(cur.pos() - 1) &&
3066 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
3067 !cur.paragraph().isSeparator(cur.pos() - 1)) {
3068 return setCursor(cur, cur.pit(), cur.pos(), true, true);
3071 // go left and try to enter inset
3072 if (checkAndActivateInset(cur, false))
3075 // normal character left
3076 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
3079 // move to the previous paragraph or do nothing
3080 if (cur.pit() > 0) {
3081 Paragraph & par = getPar(cur.pit() - 1);
3082 pos_type lastpos = par.size();
3083 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
3084 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
3086 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
3092 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
3094 Cursor temp_cur = cur;
3095 temp_cur.posVisLeft(skip_inset);
3096 if (temp_cur.depth() > cur.depth()) {
3100 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3101 true, temp_cur.boundary());
3105 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
3107 Cursor temp_cur = cur;
3108 temp_cur.posVisRight(skip_inset);
3109 if (temp_cur.depth() > cur.depth()) {
3113 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3114 true, temp_cur.boundary());
3118 bool Text::cursorForward(Cursor & cur)
3120 // Tell BufferView to test for FitCursor in any case!
3121 cur.screenUpdateFlags(Update::FitCursor);
3123 // not at paragraph end?
3124 if (cur.pos() != cur.lastpos()) {
3125 // in front of editable inset, i.e. jump into it?
3126 if (checkAndActivateInset(cur, true))
3129 TextMetrics const & tm = cur.bv().textMetrics(this);
3130 // if left of boundary -> just jump to right side
3131 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
3132 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
3133 return setCursor(cur, cur.pit(), cur.pos(), true, false);
3135 // next position is left of boundary,
3136 // but go to next line for special cases like space, newline, linesep
3138 // some effectless debug code to see the values in the debugger
3139 int endpos = cur.textRow().endpos();
3140 int lastpos = cur.lastpos();
3141 int pos = cur.pos();
3142 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
3143 bool newline = cur.paragraph().isNewline(cur.pos());
3144 bool sep = cur.paragraph().isSeparator(cur.pos());
3145 if (cur.pos() != cur.lastpos()) {
3146 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
3147 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
3148 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
3151 if (cur.textRow().endpos() == cur.pos() + 1) {
3152 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
3153 cur.pos() + 1 == cur.lastpos() &&
3154 cur.pit() != cur.lastpit()) {
3155 // move to next paragraph
3156 return setCursor(cur, cur.pit() + 1, 0, true, false);
3157 } else if (cur.textRow().endpos() != cur.lastpos() &&
3158 !cur.paragraph().isNewline(cur.pos()) &&
3159 !cur.paragraph().isEnvSeparator(cur.pos()) &&
3160 !cur.paragraph().isLineSeparator(cur.pos()) &&
3161 !cur.paragraph().isSeparator(cur.pos())) {
3162 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3166 // in front of RTL boundary? Stay on this side of the boundary because:
3167 // ab|cDDEEFFghi -> abc|DDEEFFghi
3168 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
3169 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3172 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
3175 // move to next paragraph
3176 if (cur.pit() != cur.lastpit())
3177 return setCursor(cur, cur.pit() + 1, 0, true, false);
3182 bool Text::cursorUpParagraph(Cursor & cur)
3184 bool updated = false;
3186 updated = setCursor(cur, cur.pit(), 0);
3187 else if (cur.pit() != 0)
3188 updated = setCursor(cur, cur.pit() - 1, 0);
3193 bool Text::cursorDownParagraph(Cursor & cur)
3195 bool updated = false;
3196 if (cur.pit() != cur.lastpit())
3197 if (lyxrc.mac_like_cursor_movement)
3198 if (cur.pos() == cur.lastpos())
3199 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
3201 updated = setCursor(cur, cur.pit(), cur.lastpos());
3203 updated = setCursor(cur, cur.pit() + 1, 0);
3205 updated = setCursor(cur, cur.pit(), cur.lastpos());
3211 /** delete num_spaces characters between from and to. Return the
3212 * number of spaces that got physically deleted (not marked as
3214 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
3215 int num_spaces, bool const trackChanges)
3217 if (num_spaces <= 0)
3220 // First, delete spaces marked as inserted
3222 while (pos < to && num_spaces > 0) {
3223 Change const & change = par.lookupChange(pos);
3224 if (change.inserted() && change.currentAuthor()) {
3225 par.eraseChar(pos, trackChanges);
3232 // Then remove remaining spaces
3233 int const psize = par.size();
3234 par.eraseChars(from, from + num_spaces, trackChanges);
3235 return psize - par.size();
3241 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
3242 Cursor & old, bool & need_anchor_change)
3244 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
3246 Paragraph & oldpar = old.paragraph();
3247 bool const trackChanges = cur.buffer()->params().track_changes;
3248 bool result = false;
3250 // We do nothing if cursor did not move
3251 if (cur.top() == old.top())
3254 // We do not do anything on read-only documents
3255 if (cur.buffer()->isReadonly())
3258 // Whether a common inset is found and whether the cursor is still in
3259 // the same paragraph (possibly nested).
3260 int const depth = cur.find(&old.inset());
3261 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
3262 && old.pit() == cur[depth].pit();
3265 * (1) If the chars around the old cursor were spaces and the
3266 * paragraph is not in free spacing mode, delete some of them, but
3267 * only if the cursor has really moved.
3270 /* There are still some small problems that can lead to
3271 double spaces stored in the document file or space at
3272 the beginning of paragraphs(). This happens if you have
3273 the cursor between two spaces and then save. Or if you
3274 cut and paste and the selection has a space at the
3275 beginning and then save right after the paste. (Lgb)
3277 if (!oldpar.isFreeSpacing()) {
3278 // find range of spaces around cursors
3279 pos_type from = old.pos();
3281 && oldpar.isLineSeparator(from - 1)
3282 && !oldpar.isDeleted(from - 1))
3284 pos_type to = old.pos();
3285 while (to < old.lastpos()
3286 && oldpar.isLineSeparator(to)
3287 && !oldpar.isDeleted(to))
3290 int num_spaces = to - from;
3291 // If we are not at the start of the paragraph, keep one space
3292 if (from != to && from > 0)
3295 // If cursor is inside range, keep one additional space
3296 if (same_par && cur.pos() > from && cur.pos() < to)
3299 // Remove spaces and adapt cursor.
3300 if (num_spaces > 0) {
3303 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
3304 // correct cur position
3305 // FIXME: there can be other cursors pointing there, we should update them
3307 if (cur[depth].pos() >= to)
3308 cur[depth].pos() -= deleted;
3309 else if (cur[depth].pos() > from)
3310 cur[depth].pos() = min(from + 1, old.lastpos());
3311 need_anchor_change = true;
3318 * (2) If the paragraph where the cursor was is empty, delete it
3321 // only do our other magic if we changed paragraph
3325 // only do our magic if the paragraph is empty
3326 if (!oldpar.empty())
3329 // don't delete anything if this is the ONLY paragraph!
3330 if (old.lastpit() == 0)
3333 // Do not delete empty paragraphs with keepempty set.
3334 if (oldpar.allowEmpty())
3338 old.recordUndo(max(old.pit() - 1, pit_type(0)),
3339 min(old.pit() + 1, old.lastpit()));
3340 ParagraphList & plist = old.text()->paragraphs();
3341 bool const soa = oldpar.params().startOfAppendix();
3342 plist.erase(plist.iterator_at(old.pit()));
3343 // do not lose start of appendix marker (bug 4212)
3344 if (soa && old.pit() < pit_type(plist.size()))
3345 plist[old.pit()].params().startOfAppendix(true);
3347 // see #warning (FIXME?) above
3348 if (cur.depth() >= old.depth()) {
3349 CursorSlice & curslice = cur[old.depth() - 1];
3350 if (&curslice.inset() == &old.inset()
3351 && curslice.idx() == old.idx()
3352 && curslice.pit() > old.pit()) {
3354 // since a paragraph has been deleted, all the
3355 // insets after `old' have been copied and
3356 // their address has changed. Therefore we
3357 // need to `regenerate' cur. (JMarc)
3358 cur.updateInsets(&(cur.bottom().inset()));
3359 need_anchor_change = true;
3367 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
3369 pos_type last_pos = pars_[last].size() - 1;
3370 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
3374 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
3375 pos_type first_pos, pos_type last_pos,
3378 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
3380 for (pit_type pit = first; pit <= last; ++pit) {
3381 Paragraph & par = pars_[pit];
3384 * (1) Delete consecutive spaces
3386 if (!par.isFreeSpacing()) {
3387 pos_type from = (pit == first) ? first_pos : 0;
3388 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
3389 while (from < to_pos) {
3391 while (from < par.size()
3392 && (!par.isLineSeparator(from) || par.isDeleted(from)))
3394 // find string of spaces
3396 while (to < par.size()
3397 && par.isLineSeparator(to) && !par.isDeleted(to))
3399 // empty? We are done
3403 int num_spaces = to - from;
3405 // If we are not at the extremity of the paragraph, keep one space
3406 if (from != to && from > 0 && to < par.size())
3409 // Remove spaces if needed
3410 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
3411 from = to - deleted;
3416 * (2) Delete empty pragraphs
3419 // don't delete anything if this is the only remaining paragraph
3420 // within the given range. Note: Text::acceptOrRejectChanges()
3421 // sets the cursor to 'first' after calling DEPM
3425 // don't delete empty paragraphs with keepempty set
3426 if (par.allowEmpty())
3429 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
3430 pars_.erase(pars_.iterator_at(pit));
3442 typedef limited_stack<pair<docstring, Font>> FontStack;
3443 static FontStack freeFonts(15);
3444 static bool toggleall = false;
3446 void toggleAndShow(Cursor & cur, Text * text,
3447 Font const & font, bool togall = true)
3449 text->toggleFree(cur, font, togall);
3451 if (font.language() != ignore_language ||
3452 font.fontInfo().number() != FONT_IGNORE) {
3453 TextMetrics const & tm = cur.bv().textMetrics(text);
3454 if (cur.boundary() != tm.isRTLBoundary(cur.pit(), cur.pos(),
3455 cur.real_current_font))
3456 text->setCursor(cur, cur.pit(), cur.pos(),
3457 false, !cur.boundary());
3458 if (font.language() != ignore_language)
3459 // We need a buffer update if we change the language
3460 // (e.g., with info insets or if the selection contains
3462 cur.forceBufferUpdate();
3467 void moveCursor(Cursor & cur, bool selecting)
3469 if (selecting || cur.mark())
3474 void finishChange(Cursor & cur, bool selecting)
3477 moveCursor(cur, selecting);
3481 void mathDispatch(Cursor & cur, FuncRequest const & cmd)
3484 docstring sel = cur.selectionAsString(false);
3486 // It may happen that sel is empty but there is a selection
3487 replaceSelection(cur);
3489 // Is this a valid formula?
3493 #ifdef ENABLE_ASSERTIONS
3494 const int old_pos = cur.pos();
3496 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
3497 #ifdef ENABLE_ASSERTIONS
3498 LATTEST(old_pos == cur.pos());
3500 cur.nextInset()->edit(cur, true);
3501 if (cmd.action() != LFUN_MATH_MODE)
3502 // LFUN_MATH_MODE has a different meaning in math mode
3505 InsetMathHull * formula = new InsetMathHull(cur.buffer());
3506 string const selstr = to_utf8(sel);
3507 istringstream is(selstr);
3510 if (!formula->readQuiet(lex)) {
3511 // No valid formula, let's try with delims
3512 is.str("$" + selstr + "$");
3514 if (!formula->readQuiet(lex)) {
3515 // Still not valid, leave it as is
3522 cur.insert(formula);
3523 cur.nextInset()->edit(cur, true);
3524 LASSERT(cur.inMathed(), return);
3527 cur.selection(true);
3528 cur.pos() = cur.lastpos();
3529 if (cmd.action() != LFUN_MATH_MODE)
3530 // LFUN_MATH_MODE has a different meaning in math mode
3532 cur.clearSelection();
3533 cur.pos() = cur.lastpos();
3537 cur.message(from_utf8(N_("Math editor mode")));
3539 cur.message(from_utf8(N_("No valid math formula")));
3543 void regexpDispatch(Cursor & cur, FuncRequest const & cmd)
3545 LASSERT(cmd.action() == LFUN_REGEXP_MODE, return);
3546 if (cur.inRegexped()) {
3547 cur.message(_("Already in regular expression mode"));
3551 docstring sel = cur.selectionAsString(false);
3553 // It may happen that sel is empty but there is a selection
3554 replaceSelection(cur);
3556 cur.insert(new InsetMathHull(cur.buffer(), hullRegexp));
3557 cur.nextInset()->edit(cur, true);
3558 cur.niceInsert(sel);
3560 cur.message(_("Regexp editor mode"));
3564 void specialChar(Cursor & cur, InsetSpecialChar::Kind kind)
3567 cap::replaceSelection(cur);
3568 cur.insert(new InsetSpecialChar(kind));
3573 void ipaChar(Cursor & cur, InsetIPAChar::Kind kind)
3576 cap::replaceSelection(cur);
3577 cur.insert(new InsetIPAChar(kind));
3582 bool doInsertInset(Cursor & cur, Text * text,
3583 FuncRequest const & cmd, bool edit,
3584 bool pastesel, bool resetfont = false)
3586 Buffer & buffer = cur.bv().buffer();
3587 BufferParams const & bparams = buffer.params();
3588 Inset * inset = createInset(&buffer, cmd);
3592 if (InsetCollapsible * ci = inset->asInsetCollapsible())
3593 ci->setButtonLabel();
3596 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3597 bool cotextinsert = false;
3598 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3599 Layout const & lay = cur.paragraph().layout();
3600 Layout::LaTeXArgMap args = lay.args();
3601 Layout::LaTeXArgMap::const_iterator const lait = args.find(ia->name());
3602 if (lait != args.end())
3603 cotextinsert = (*lait).second.insertcotext;
3605 InsetLayout const & il = cur.inset().getLayout();
3607 Layout::LaTeXArgMap::const_iterator const ilait = args.find(ia->name());
3608 if (ilait != args.end())
3609 cotextinsert = (*ilait).second.insertcotext;
3611 // The argument requests to insert a copy of the co-text to the inset
3614 // If we have a selection within a paragraph, use this
3615 if (cur.selection() && cur.selBegin().pit() == cur.selEnd().pit())
3616 ds = cur.selectionAsString(false);
3617 // else use the whole paragraph
3619 ds = cur.paragraph().asString();
3620 text->insertInset(cur, inset);
3621 ia->init(cur.paragraph());
3623 inset->edit(cur, true);
3624 // Now put co-text into inset
3625 Font const f(inherit_font, cur.current_font.language());
3627 cur.text()->insertStringAsLines(cur, ds, f);
3628 cur.leaveInset(*inset);
3634 bool gotsel = false;
3635 bool move_layout = false;
3636 if (cur.selection()) {
3637 if (cmd.action() == LFUN_INDEX_INSERT)
3638 copySelectionToTemp(cur);
3640 cutSelectionToTemp(cur, pastesel);
3641 /* Move layout information inside the inset if the whole
3642 * paragraph and the inset allows setting layout
3643 * FIXME: this does not work as expected when change tracking is on
3644 * However, we do not really know what to do in this case.
3645 * FIXME: figure out a good test in the environment case (see #12251).
3647 if (cur.paragraph().layout().isCommand()
3648 && cur.paragraph().empty()
3649 && !inset->forcePlainLayout()) {
3650 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3654 cur.clearSelection();
3656 } else if (cmd.action() == LFUN_INDEX_INSERT) {
3657 gotsel = text->selectWordWhenUnderCursor(cur, WHOLE_WORD);
3658 copySelectionToTemp(cur);
3659 cur.clearSelection();
3661 text->insertInset(cur, inset);
3663 InsetText * inset_text = inset->asInsetText();
3665 Font const & font = inset->inheritFont()
3666 ? cur.bv().textMetrics(text).displayFont(cur.pit(), cur.pos())
3667 : bparams.getFont();
3668 inset_text->setOuterFont(cur.bv(), font.fontInfo());
3671 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3672 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3673 ia->init(cur.paragraph());
3677 inset->edit(cur, true);
3679 if (!gotsel || !pastesel)
3682 pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
3683 cur.buffer()->errors("Paste");
3684 cur.clearSelection(); // bug 393
3688 // Reset of font (not language) is requested.
3689 // Used by InsetIndex (#11961).
3690 Language const * lang = cur.getFont().language();
3691 Font font(bparams.getFont().fontInfo(), lang);
3692 cur.paragraph().resetFonts(font);
3694 inset_text->fixParagraphsFont();
3697 /* If the containing paragraph has kept its layout, reset the
3698 * layout of the first paragraph of the inset.
3701 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3702 // FIXME: what does this do?
3703 if (cmd.action() == LFUN_FLEX_INSERT)
3706 cur.leaveInset(*inset);
3707 if (cmd.action() == LFUN_PREVIEW_INSERT
3708 || cmd.action() == LFUN_IPA_INSERT)
3710 notifyCursorLeavesOrEnters(old, cur);
3712 cur.leaveInset(*inset);
3713 // reset surrounding par to default
3714 DocumentClass const & dc = bparams.documentClass();
3715 docstring const layoutname = inset->usePlainLayout()
3716 ? dc.plainLayoutName()
3717 : dc.defaultLayoutName();
3718 text->setLayout(cur, layoutname);
3724 /// the type of outline operation
3726 OutlineUp, // Move this header with text down
3727 OutlineDown, // Move this header with text up
3728 OutlineIn, // Make this header deeper
3729 OutlineOut // Make this header shallower
3733 void insertSeparator(Cursor const & cur, depth_type const depth)
3735 Buffer & buf = *cur.buffer();
3736 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
3737 DocumentClass const & tc = buf.params().documentClass();
3738 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
3739 + from_ascii("\" ignoreautonests")));
3740 // FIXME: Bibitem mess!
3741 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
3742 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
3743 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
3744 while (cur.paragraph().params().depth() > depth)
3745 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
3749 void outline(OutlineOp mode, Cursor & cur, Text * text)
3751 Buffer & buf = *cur.buffer();
3752 pit_type & pit = cur.pit();
3753 ParagraphList & pars = buf.text().paragraphs();
3754 ParagraphList::iterator const bgn = pars.begin();
3755 // The first paragraph of the area to be copied:
3756 ParagraphList::iterator start = pars.iterator_at(pit);
3757 // The final paragraph of area to be copied:
3758 ParagraphList::iterator finish = start;
3759 ParagraphList::iterator const end = pars.end();
3760 depth_type const current_depth = cur.paragraph().params().depth();
3762 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
3765 // Move out (down) from this section header
3769 // Seek the one (on same level) below
3770 for (; finish != end; ++finish) {
3771 toclevel = buf.text().getTocLevel(distance(bgn, finish));
3772 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3778 if (start == pars.begin())
3781 ParagraphList::iterator dest = start;
3782 // Move out (up) from this header
3785 // Search previous same-level header above
3788 toclevel = buf.text().getTocLevel(distance(bgn, dest));
3790 && (toclevel == Layout::NOT_IN_TOC
3791 || toclevel > thistoclevel));
3792 // Not found; do nothing
3793 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3795 pit_type newpit = distance(bgn, dest);
3796 pit_type const len = distance(start, finish);
3797 pit_type const deletepit = pit + len;
3798 buf.undo().recordUndo(cur, newpit, deletepit - 1);
3799 // If we move an environment upwards, make sure it is
3800 // separated from its new neighbour below:
3801 // If an environment of the same layout follows, and the moved
3802 // paragraph sequence does not end with a separator, insert one.
3803 ParagraphList::iterator lastmoved = finish;
3805 if (start->layout().isEnvironment()
3806 && dest->layout() == start->layout()
3807 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3808 cur.pit() = distance(bgn, lastmoved);
3809 cur.pos() = cur.lastpos();
3810 insertSeparator(cur, current_depth);
3813 // Likewise, if we moved an environment upwards, make sure it
3814 // is separated from its new neighbour above.
3815 // The paragraph before the target of movement
3817 ParagraphList::iterator before = dest;
3819 // Get the parent paragraph (outer in nested context)
3820 pit_type const parent =
3821 before->params().depth() > current_depth
3822 ? text->depthHook(distance(bgn, before), current_depth)
3823 : distance(bgn, before);
3824 // If a environment with same layout preceeds the moved one in the new
3825 // position, and there is no separator yet, insert one.
3826 if (start->layout().isEnvironment()
3827 && pars[parent].layout() == start->layout()
3828 && !before->isEnvSeparator(before->beginOfBody())) {
3829 cur.pit() = distance(bgn, before);
3830 cur.pos() = cur.lastpos();
3831 insertSeparator(cur, current_depth);
3835 newpit = distance(bgn, dest);
3836 pars.splice(dest, start, finish);
3844 // Go one down from *this* header:
3845 ParagraphList::iterator dest = next(finish, 1);
3846 // Go further down to find header to insert in front of:
3847 for (; dest != end; ++dest) {
3848 toclevel = buf.text().getTocLevel(distance(bgn, dest));
3849 if (toclevel != Layout::NOT_IN_TOC
3850 && toclevel <= thistoclevel)
3853 // One such was found, so go on...
3854 // If we move an environment downwards, make sure it is
3855 // separated from its new neighbour above.
3856 pit_type newpit = distance(bgn, dest);
3857 buf.undo().recordUndo(cur, pit, newpit - 1);
3858 // The paragraph before the target of movement
3859 ParagraphList::iterator before = dest;
3861 // Get the parent paragraph (outer in nested context)
3862 pit_type const parent =
3863 before->params().depth() > current_depth
3864 ? text->depthHook(distance(bgn, before), current_depth)
3865 : distance(bgn, before);
3866 // If a environment with same layout preceeds the moved one in the new
3867 // position, and there is no separator yet, insert one.
3868 if (start->layout().isEnvironment()
3869 && pars[parent].layout() == start->layout()
3870 && !before->isEnvSeparator(before->beginOfBody())) {
3871 cur.pit() = distance(bgn, before);
3872 cur.pos() = cur.lastpos();
3873 insertSeparator(cur, current_depth);
3876 // Likewise, make sure moved environments are separated
3877 // from their new neighbour below:
3878 // If an environment of the same layout follows, and the moved
3879 // paragraph sequence does not end with a separator, insert one.
3880 ParagraphList::iterator lastmoved = finish;
3883 && start->layout().isEnvironment()
3884 && dest->layout() == start->layout()
3885 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3886 cur.pit() = distance(bgn, lastmoved);
3887 cur.pos() = cur.lastpos();
3888 insertSeparator(cur, current_depth);
3891 newpit = distance(bgn, dest);
3892 pit_type const len = distance(start, finish);
3893 pars.splice(dest, start, finish);
3894 cur.pit() = newpit - len;
3899 // We first iterate without actually doing something
3900 // in order to check whether the action flattens the structure.
3901 // If so, warn (#11178).
3902 ParagraphList::iterator cstart = start;
3903 bool strucchange = false;
3904 for (; cstart != finish; ++cstart) {
3905 toclevel = buf.text().getTocLevel(distance(bgn, cstart));
3906 if (toclevel == Layout::NOT_IN_TOC)
3909 DocumentClass const & tc = buf.params().documentClass();
3910 int const newtoclevel =
3911 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3914 for (auto const & lay : tc) {
3915 if (lay.toclevel == newtoclevel
3916 && lay.isNumHeadingLabelType()
3917 && cstart->layout().isNumHeadingLabelType()) {
3928 && frontend::Alert::prompt(_("Action flattens document structure"),
3929 _("This action will cause some headings that have been "
3930 "on different level before to be on the same level "
3931 "since there is no more lower or higher heading level. "
3934 _("&Yes, continue nonetheless"),
3935 _("&No, quit operation")) == 1)
3938 pit_type const len = distance(start, finish);
3939 buf.undo().recordUndo(cur, pit, pit + len - 1);
3940 for (; start != finish; ++start) {
3941 toclevel = buf.text().getTocLevel(distance(bgn, start));
3942 if (toclevel == Layout::NOT_IN_TOC)
3945 DocumentClass const & tc = buf.params().documentClass();
3946 int const newtoclevel =
3947 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3949 for (auto const & lay : tc) {
3950 if (lay.toclevel == newtoclevel
3951 && lay.isNumHeadingLabelType()
3952 && start->layout().isNumHeadingLabelType()) {
3953 start->setLayout(lay);
3967 void Text::number(Cursor & cur)
3969 FontInfo font = ignore_font;
3970 font.setNumber(FONT_TOGGLE);
3971 toggleAndShow(cur, this, Font(font, ignore_language));
3975 bool Text::isRTL(pit_type const pit) const
3977 Buffer const & buffer = owner_->buffer();
3978 return pars_[pit].isRTL(buffer.params());
3984 Language const * getLanguage(Cursor const & cur, string const & lang)
3986 return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
3990 docstring resolveLayout(docstring layout, DocIterator const & dit)
3992 Paragraph const & par = dit.paragraph();
3993 DocumentClass const & tclass = dit.buffer()->params().documentClass();
3996 layout = tclass.defaultLayoutName();
3998 if (dit.inset().forcePlainLayout(dit.idx()))
3999 // in this case only the empty layout is allowed
4000 layout = tclass.plainLayoutName();
4001 else if (par.usePlainLayout()) {
4002 // in this case, default layout maps to empty layout
4003 if (layout == tclass.defaultLayoutName())
4004 layout = tclass.plainLayoutName();
4006 // otherwise, the empty layout maps to the default
4007 if (layout == tclass.plainLayoutName())
4008 layout = tclass.defaultLayoutName();
4011 // If the entry is obsolete, use the new one instead.
4012 if (tclass.hasLayout(layout)) {
4013 docstring const & obs = tclass[layout].obsoleted_by();
4017 if (!tclass.hasLayout(layout))
4023 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4025 ParagraphList const & pars = cur.text()->paragraphs();
4027 pit_type pit = cur.selBegin().pit();
4028 pit_type const epit = cur.selEnd().pit() + 1;
4029 for ( ; pit != epit; ++pit)
4030 if (pars[pit].layout().name() != layout)
4040 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4042 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4044 // Dispatch if the cursor is inside the text. It is not the
4045 // case for context menus (bug 5797).
4046 if (cur.text() != this) {
4051 BufferView * bv = &cur.bv();
4052 TextMetrics * tm = &bv->textMetrics(this);
4053 if (!tm->contains(cur.pit())) {
4054 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4055 tm = &bv->textMetrics(this);
4058 // FIXME: We use the update flag to indicates wether a singlePar or a
4059 // full screen update is needed. We reset it here but shall we restore it
4061 cur.noScreenUpdate();
4063 LBUFERR(this == cur.text());
4065 // NOTE: This should NOT be a reference. See commit 94a5481a.
4066 CursorSlice const oldTopSlice = cur.top();
4067 bool const oldBoundary = cur.boundary();
4068 bool const oldSelection = cur.selection();
4069 // Signals that, even if needsUpdate == false, an update of the
4070 // cursor paragraph is required
4071 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4072 LyXAction::SingleParUpdate);
4073 // Signals that a full-screen update is required
4074 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4075 LyXAction::NoUpdate) || singleParUpdate);
4076 bool const last_misspelled = lyxrc.spellcheck_continuously
4077 && cur.paragraph().isMisspelled(cur.pos(), true);
4079 FuncCode const act = cmd.action();
4082 case LFUN_PARAGRAPH_MOVE_DOWN: {
4083 pit_type const pit = cur.pit();
4084 cur.recordUndo(pit, pit + 1);
4085 pars_.swap(pit, pit + 1);
4087 cur.forceBufferUpdate();
4092 case LFUN_PARAGRAPH_MOVE_UP: {
4093 pit_type const pit = cur.pit();
4094 cur.recordUndo(pit - 1, pit);
4096 pars_.swap(pit, pit - 1);
4099 cur.forceBufferUpdate();
4103 case LFUN_APPENDIX: {
4104 Paragraph & par = cur.paragraph();
4105 bool start = !par.params().startOfAppendix();
4107 // FIXME: The code below only makes sense at top level.
4108 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4109 // ensure that we have only one start_of_appendix in this document
4110 // FIXME: this don't work for multipart document!
4111 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4112 if (pars_[tmp].params().startOfAppendix()) {
4113 cur.recordUndo(tmp, tmp);
4114 pars_[tmp].params().startOfAppendix(false);
4120 par.params().startOfAppendix(start);
4122 // we can set the refreshing parameters now
4123 cur.forceBufferUpdate();
4127 case LFUN_WORD_DELETE_FORWARD:
4128 if (cur.selection())
4129 cutSelection(cur, false);
4131 deleteWordForward(cur, cmd.getArg(0) != "confirm");
4132 finishChange(cur, false);
4135 case LFUN_WORD_DELETE_BACKWARD:
4136 if (cur.selection())
4137 cutSelection(cur, false);
4139 deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4140 finishChange(cur, false);
4143 case LFUN_LINE_DELETE_FORWARD:
4144 if (cur.selection())
4145 cutSelection(cur, false);
4147 tm->deleteLineForward(cur);
4148 finishChange(cur, false);
4151 case LFUN_BUFFER_BEGIN:
4152 case LFUN_BUFFER_BEGIN_SELECT:
4153 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4154 if (cur.depth() == 1)
4155 needsUpdate |= cursorTop(cur);
4158 cur.screenUpdateFlags(Update::FitCursor);
4161 case LFUN_BUFFER_END:
4162 case LFUN_BUFFER_END_SELECT:
4163 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4164 if (cur.depth() == 1)
4165 needsUpdate |= cursorBottom(cur);
4168 cur.screenUpdateFlags(Update::FitCursor);
4171 case LFUN_INSET_BEGIN:
4172 case LFUN_INSET_BEGIN_SELECT:
4173 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4174 if (cur.depth() == 1 || !cur.top().at_begin())
4175 needsUpdate |= cursorTop(cur);
4178 cur.screenUpdateFlags(Update::FitCursor);
4181 case LFUN_INSET_END:
4182 case LFUN_INSET_END_SELECT:
4183 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4184 if (cur.depth() == 1 || !cur.top().at_end())
4185 needsUpdate |= cursorBottom(cur);
4188 cur.screenUpdateFlags(Update::FitCursor);
4191 case LFUN_CHAR_FORWARD:
4192 case LFUN_CHAR_FORWARD_SELECT: {
4193 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4194 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4195 bool const cur_moved = cursorForward(cur);
4196 needsUpdate |= cur_moved;
4198 if (!cur_moved && cur.depth() > 1
4199 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4201 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4203 // we will be moving out the inset, so we should execute
4204 // the depm-mechanism.
4205 // The cursor hasn't changed yet. To give the DEPM the
4206 // possibility of doing something we must provide it with
4207 // two different cursors.
4209 dummy.pos() = dummy.pit() = 0;
4210 if (cur.bv().checkDepm(dummy, cur))
4211 cur.forceBufferUpdate();
4216 case LFUN_CHAR_BACKWARD:
4217 case LFUN_CHAR_BACKWARD_SELECT: {
4218 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4219 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4220 bool const cur_moved = cursorBackward(cur);
4221 needsUpdate |= cur_moved;
4223 if (!cur_moved && cur.depth() > 1
4224 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4226 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4228 // we will be moving out the inset, so we should execute
4229 // the depm-mechanism.
4230 // The cursor hasn't changed yet. To give the DEPM the
4231 // possibility of doing something we must provide it with
4232 // two different cursors.
4234 dummy.pos() = cur.lastpos();
4235 dummy.pit() = cur.lastpit();
4236 if (cur.bv().checkDepm(dummy, cur))
4237 cur.forceBufferUpdate();
4242 case LFUN_CHAR_LEFT:
4243 case LFUN_CHAR_LEFT_SELECT:
4244 if (lyxrc.visual_cursor) {
4245 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4246 bool const cur_moved = cursorVisLeft(cur);
4247 needsUpdate |= cur_moved;
4248 if (!cur_moved && cur.depth() > 1
4249 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4251 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4254 if (cur.reverseDirectionNeeded()) {
4255 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4256 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4258 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4259 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4266 case LFUN_CHAR_RIGHT:
4267 case LFUN_CHAR_RIGHT_SELECT:
4268 if (lyxrc.visual_cursor) {
4269 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4270 bool const cur_moved = cursorVisRight(cur);
4271 needsUpdate |= cur_moved;
4272 if (!cur_moved && cur.depth() > 1
4273 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4275 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4278 if (cur.reverseDirectionNeeded()) {
4279 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4280 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4282 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4283 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4291 case LFUN_UP_SELECT:
4292 case LFUN_DOWN_SELECT:
4295 // stop/start the selection
4296 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4297 cmd.action() == LFUN_UP_SELECT;
4299 // move cursor up/down
4300 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4301 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4303 if (!atFirstOrLastRow) {
4304 needsUpdate |= cur.selHandle(select);
4305 cur.upDownInText(up, needsUpdate);
4306 needsUpdate |= cur.beforeDispatchCursor().inMathed();
4308 pos_type newpos = up ? 0 : cur.lastpos();
4309 if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4310 needsUpdate |= cur.selHandle(select);
4311 // we do not reset the targetx of the cursor
4313 needsUpdate |= bv->checkDepm(cur, bv->cursor());
4314 cur.updateTextTargetOffset();
4316 cur.forceBufferUpdate();
4320 // if the cursor cannot be moved up or down do not remove
4321 // the selection right now, but wait for the next dispatch.
4323 needsUpdate |= cur.selHandle(select);
4324 cur.upDownInText(up, needsUpdate);
4331 case LFUN_PARAGRAPH_SELECT:
4333 needsUpdate |= setCursor(cur, cur.pit(), 0);
4334 needsUpdate |= cur.selHandle(true);
4335 if (cur.pos() < cur.lastpos())
4336 needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4339 case LFUN_PARAGRAPH_UP:
4340 case LFUN_PARAGRAPH_UP_SELECT:
4341 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4342 needsUpdate |= cursorUpParagraph(cur);
4345 case LFUN_PARAGRAPH_DOWN:
4346 case LFUN_PARAGRAPH_DOWN_SELECT:
4347 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4348 needsUpdate |= cursorDownParagraph(cur);
4351 case LFUN_LINE_BEGIN:
4352 case LFUN_LINE_BEGIN_SELECT:
4353 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4354 needsUpdate |= tm->cursorHome(cur);
4358 case LFUN_LINE_END_SELECT:
4359 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4360 needsUpdate |= tm->cursorEnd(cur);
4363 case LFUN_SECTION_SELECT: {
4364 Buffer const & buf = *cur.buffer();
4365 pit_type const pit = cur.pit();
4366 ParagraphList & pars = buf.text().paragraphs();
4367 ParagraphList::iterator bgn = pars.begin();
4368 // The first paragraph of the area to be selected:
4369 ParagraphList::iterator start = pars.iterator_at(pit);
4370 // The final paragraph of area to be selected:
4371 ParagraphList::iterator finish = start;
4372 ParagraphList::iterator end = pars.end();
4374 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4375 if (thistoclevel == Layout::NOT_IN_TOC)
4379 Cursor const old_cur = cur;
4380 needsUpdate |= cur.selHandle(true);
4382 // Move out (down) from this section header
4386 // Seek the one (on same level) below
4387 for (; finish != end; ++finish, ++cur.pit()) {
4388 int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4389 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4392 cur.pos() = cur.lastpos();
4393 cur.boundary(false);
4394 cur.setCurrentFont();
4396 needsUpdate |= cur != old_cur;
4400 case LFUN_WORD_RIGHT:
4401 case LFUN_WORD_RIGHT_SELECT:
4402 if (lyxrc.visual_cursor) {
4403 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4404 bool const cur_moved = cursorVisRightOneWord(cur);
4405 needsUpdate |= cur_moved;
4406 if (!cur_moved && cur.depth() > 1
4407 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4409 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4412 if (cur.reverseDirectionNeeded()) {
4413 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4414 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4416 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4417 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4424 case LFUN_WORD_FORWARD:
4425 case LFUN_WORD_FORWARD_SELECT: {
4426 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4427 bool const cur_moved = cursorForwardOneWord(cur);
4428 needsUpdate |= cur_moved;
4430 if (!cur_moved && cur.depth() > 1
4431 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4433 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4435 // we will be moving out the inset, so we should execute
4436 // the depm-mechanism.
4437 // The cursor hasn't changed yet. To give the DEPM the
4438 // possibility of doing something we must provide it with
4439 // two different cursors.
4441 dummy.pos() = dummy.pit() = 0;
4442 if (cur.bv().checkDepm(dummy, cur))
4443 cur.forceBufferUpdate();
4448 case LFUN_WORD_LEFT:
4449 case LFUN_WORD_LEFT_SELECT:
4450 if (lyxrc.visual_cursor) {
4451 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4452 bool const cur_moved = cursorVisLeftOneWord(cur);
4453 needsUpdate |= cur_moved;
4454 if (!cur_moved && cur.depth() > 1
4455 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4457 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4460 if (cur.reverseDirectionNeeded()) {
4461 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4462 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4464 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4465 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4472 case LFUN_WORD_BACKWARD:
4473 case LFUN_WORD_BACKWARD_SELECT: {
4474 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4475 bool const cur_moved = cursorBackwardOneWord(cur);
4476 needsUpdate |= cur_moved;
4478 if (!cur_moved && cur.depth() > 1
4479 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4481 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4483 // we will be moving out the inset, so we should execute
4484 // the depm-mechanism.
4485 // The cursor hasn't changed yet. To give the DEPM the
4486 // possibility of doing something we must provide it with
4487 // two different cursors.
4489 dummy.pos() = cur.lastpos();
4490 dummy.pit() = cur.lastpit();
4491 if (cur.bv().checkDepm(dummy, cur))
4492 cur.forceBufferUpdate();
4497 case LFUN_WORD_SELECT: {
4498 selectWord(cur, WHOLE_WORD);
4499 finishChange(cur, true);
4503 case LFUN_NEWLINE_INSERT: {
4504 InsetNewlineParams inp;
4505 docstring const & arg = cmd.argument();
4506 if (arg == "linebreak")
4507 inp.kind = InsetNewlineParams::LINEBREAK;
4509 inp.kind = InsetNewlineParams::NEWLINE;
4510 cap::replaceSelection(cur);
4512 cur.insert(new InsetNewline(inp));
4514 moveCursor(cur, false);
4518 case LFUN_TAB_INSERT: {
4519 bool const multi_par_selection = cur.selection() &&
4520 cur.selBegin().pit() != cur.selEnd().pit();
4521 if (multi_par_selection) {
4522 // If there is a multi-paragraph selection, a tab is inserted
4523 // at the beginning of each paragraph.
4524 cur.recordUndoSelection();
4525 pit_type const pit_end = cur.selEnd().pit();
4526 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4527 pars_[pit].insertChar(0, '\t',
4528 bv->buffer().params().track_changes);
4529 // Update the selection pos to make sure the selection does not
4530 // change as the inserted tab will increase the logical pos.
4531 if (cur.realAnchor().pit() == pit)
4532 cur.realAnchor().forwardPos();
4533 if (cur.pit() == pit)
4538 // Maybe we shouldn't allow tabs within a line, because they
4539 // are not (yet) aligned as one might do expect.
4540 FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4541 dispatch(cur, ncmd);
4546 case LFUN_TAB_DELETE: {
4547 bool const tc = bv->buffer().params().track_changes;
4548 if (cur.selection()) {
4549 // If there is a selection, a tab (if present) is removed from
4550 // the beginning of each paragraph.
4551 cur.recordUndoSelection();
4552 pit_type const pit_end = cur.selEnd().pit();
4553 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4554 Paragraph & par = paragraphs()[pit];
4557 char_type const c = par.getChar(0);
4558 if (c == '\t' || c == ' ') {
4559 // remove either 1 tab or 4 spaces.
4560 int const n = (c == ' ' ? 4 : 1);
4561 for (int i = 0; i < n
4562 && !par.empty() && par.getChar(0) == c; ++i) {
4563 if (cur.pit() == pit)
4565 if (cur.realAnchor().pit() == pit
4566 && cur.realAnchor().pos() > 0 )
4567 cur.realAnchor().backwardPos();
4568 par.eraseChar(0, tc);
4574 // If there is no selection, try to remove a tab or some spaces
4575 // before the position of the cursor.
4576 Paragraph & par = paragraphs()[cur.pit()];
4577 pos_type const pos = cur.pos();
4582 char_type const c = par.getChar(pos - 1);
4586 par.eraseChar(cur.pos(), tc);
4588 for (int n_spaces = 0;
4590 && par.getChar(cur.pos() - 1) == ' '
4594 par.eraseChar(cur.pos(), tc);
4601 case LFUN_CHAR_DELETE_FORWARD:
4602 if (!cur.selection()) {
4603 if (cur.pos() == cur.paragraph().size())
4604 // Par boundary, force full-screen update
4605 singleParUpdate = false;
4606 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4608 cur.selection(true);
4613 needsUpdate |= erase(cur);
4616 cutSelection(cur, false);
4617 cur.setCurrentFont();
4618 singleParUpdate = false;
4620 moveCursor(cur, false);
4623 case LFUN_CHAR_DELETE_BACKWARD:
4624 if (!cur.selection()) {
4625 if (bv->getIntl().getTransManager().backspace()) {
4626 bool par_boundary = cur.pos() == 0;
4627 bool first_par = cur.pit() == 0;
4628 // Par boundary, full-screen update
4630 singleParUpdate = false;
4631 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4633 cur.selection(true);
4638 needsUpdate |= backspace(cur);
4640 if (par_boundary && !first_par && cur.pos() > 0
4641 && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4642 needsUpdate |= backspace(cur);
4647 DocIterator const dit = cur.selectionBegin();
4648 cutSelection(cur, false);
4649 if (cur.buffer()->params().track_changes)
4650 // since we're doing backwards deletion,
4651 // and the selection is not really cut,
4652 // move cursor before selection (#11630)
4654 cur.setCurrentFont();
4655 singleParUpdate = false;
4659 case LFUN_PARAGRAPH_BREAK: {
4660 cap::replaceSelection(cur);
4661 pit_type pit = cur.pit();
4662 Paragraph const & par = pars_[pit];
4663 bool lastpar = (pit == pit_type(pars_.size() - 1));
4664 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4665 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4666 if (prev < pit && cur.pos() == par.beginOfBody()
4667 && par.empty() && !par.isEnvSeparator(cur.pos())
4668 && !par.layout().keepempty
4669 && !par.layout().isCommand()
4670 && pars_[prev].layout() != par.layout()
4671 && pars_[prev].layout().isEnvironment()
4672 && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4673 if (par.layout().isEnvironment()
4674 && pars_[prev].getDepth() == par.getDepth()) {
4675 docstring const layout = par.layout().name();
4676 DocumentClass const & tc = bv->buffer().params().documentClass();
4677 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4678 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4679 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4680 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4682 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4683 breakParagraph(cur);
4685 Font const f(inherit_font, cur.current_font.language());
4686 pars_[cur.pit() - 1].resetFonts(f);
4688 if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4690 breakParagraph(cur, cmd.getArg(0) == "inverse");
4693 // If we have a list and autoinsert item insets,
4695 Layout::LaTeXArgMap args = par.layout().args();
4696 for (auto const & thearg : args) {
4697 Layout::latexarg arg = thearg.second;
4698 if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4699 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4700 lyx::dispatch(cmd2);
4706 case LFUN_INSET_INSERT: {
4709 // We have to avoid triggering InstantPreview loading
4710 // before inserting into the document. See bug #5626.
4711 bool loaded = bv->buffer().isFullyLoaded();
4712 bv->buffer().setFullyLoaded(false);
4713 Inset * inset = createInset(&bv->buffer(), cmd);
4714 bv->buffer().setFullyLoaded(loaded);
4717 // FIXME (Abdel 01/02/2006):
4718 // What follows would be a partial fix for bug 2154:
4719 // http://www.lyx.org/trac/ticket/2154
4720 // This automatically put the label inset _after_ a
4721 // numbered section. It should be possible to extend the mechanism
4722 // to any kind of LateX environement.
4723 // The correct way to fix that bug would be at LateX generation.
4724 // I'll let the code here for reference as it could be used for some
4725 // other feature like "automatic labelling".
4727 Paragraph & par = pars_[cur.pit()];
4728 if (inset->lyxCode() == LABEL_CODE
4729 && !par.layout().counter.empty()) {
4730 // Go to the end of the paragraph
4731 // Warning: Because of Change-Tracking, the last
4732 // position is 'size()' and not 'size()-1':
4733 cur.pos() = par.size();
4734 // Insert a new paragraph
4735 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4739 if (cur.selection())
4740 cutSelection(cur, false);
4742 cur.forceBufferUpdate();
4743 if (inset->editable() && inset->asInsetText())
4744 inset->edit(cur, true);
4748 // trigger InstantPreview now
4749 if (inset->lyxCode() == EXTERNAL_CODE) {
4750 InsetExternal & ins =
4751 static_cast<InsetExternal &>(*inset);
4752 ins.updatePreview();
4759 case LFUN_INSET_DISSOLVE: {
4760 if (dissolveInset(cur)) {
4762 cur.forceBufferUpdate();
4767 case LFUN_INSET_SPLIT: {
4768 if (splitInset(cur)) {
4770 cur.forceBufferUpdate();
4775 case LFUN_GRAPHICS_SET_GROUP: {
4776 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4782 string id = to_utf8(cmd.argument());
4783 string grp = graphics::getGroupParams(bv->buffer(), id);
4784 InsetGraphicsParams tmp, inspar = ins->getParams();
4787 inspar.groupId = to_utf8(cmd.argument());
4789 InsetGraphics::string2params(grp, bv->buffer(), tmp);
4790 tmp.filename = inspar.filename;
4794 ins->setParams(inspar);
4798 case LFUN_SPACE_INSERT:
4799 if (cur.paragraph().layout().free_spacing)
4800 insertChar(cur, ' ');
4802 doInsertInset(cur, this, cmd, false, false);
4805 moveCursor(cur, false);
4808 case LFUN_SPECIALCHAR_INSERT: {
4809 string const name = to_utf8(cmd.argument());
4810 if (name == "hyphenation")
4811 specialChar(cur, InsetSpecialChar::HYPHENATION);
4812 else if (name == "allowbreak")
4813 specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4814 else if (name == "ligature-break")
4815 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4816 else if (name == "slash")
4817 specialChar(cur, InsetSpecialChar::SLASH);
4818 else if (name == "nobreakdash")
4819 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4820 else if (name == "dots")
4821 specialChar(cur, InsetSpecialChar::LDOTS);
4822 else if (name == "end-of-sentence")
4823 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4824 else if (name == "menu-separator")
4825 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4826 else if (name == "lyx")
4827 specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4828 else if (name == "tex")
4829 specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4830 else if (name == "latex")
4831 specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4832 else if (name == "latex2e")
4833 specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4834 else if (name.empty())
4835 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4837 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4841 case LFUN_IPAMACRO_INSERT: {
4842 string const arg = cmd.getArg(0);
4843 if (arg == "deco") {
4844 // Open the inset, and move the current selection
4846 doInsertInset(cur, this, cmd, true, true);
4848 // Some insets are numbered, others are shown in the outline pane so
4849 // let's update the labels and the toc backend.
4850 cur.forceBufferUpdate();
4853 if (arg == "tone-falling")
4854 ipaChar(cur, InsetIPAChar::TONE_FALLING);
4855 else if (arg == "tone-rising")
4856 ipaChar(cur, InsetIPAChar::TONE_RISING);
4857 else if (arg == "tone-high-rising")
4858 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4859 else if (arg == "tone-low-rising")
4860 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4861 else if (arg == "tone-high-rising-falling")
4862 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4863 else if (arg.empty())
4864 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4866 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4870 case LFUN_WORD_UPCASE:
4871 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4874 case LFUN_WORD_LOWCASE:
4875 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4878 case LFUN_WORD_CAPITALIZE:
4879 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4882 case LFUN_CHARS_TRANSPOSE:
4883 charsTranspose(cur);
4887 cur.message(_("Paste"));
4888 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4889 cap::replaceSelection(cur);
4891 // without argument?
4892 string const arg = to_utf8(cmd.argument());
4894 bool tryGraphics = true;
4895 if (theClipboard().isInternal())
4896 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4897 else if (theClipboard().hasTextContents()) {
4898 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"),
4899 !cur.paragraph().parbreakIsNewline(),
4900 Clipboard::AnyTextType))
4901 tryGraphics = false;
4903 if (tryGraphics && theClipboard().hasGraphicsContents())
4904 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4905 } else if (isStrUnsignedInt(arg)) {
4906 // we have a numerical argument
4907 pasteFromStack(cur, bv->buffer().errorList("Paste"),
4908 convert<unsigned int>(arg));
4909 } else if (arg == "html" || arg == "latex") {
4910 Clipboard::TextType type = (arg == "html") ?
4911 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4912 pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4914 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4916 type = Clipboard::PdfGraphicsType;
4917 else if (arg == "png")
4918 type = Clipboard::PngGraphicsType;
4919 else if (arg == "jpeg")
4920 type = Clipboard::JpegGraphicsType;
4921 else if (arg == "linkback")
4922 type = Clipboard::LinkBackGraphicsType;
4923 else if (arg == "emf")
4924 type = Clipboard::EmfGraphicsType;
4925 else if (arg == "wmf")
4926 type = Clipboard::WmfGraphicsType;
4928 // we also check in getStatus()
4929 LYXERR0("Unrecognized graphics type: " << arg);
4931 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4934 bv->buffer().errors("Paste");
4935 bv->buffer().updatePreviews(); // bug 11619
4936 cur.clearSelection(); // bug 393
4942 cutSelection(cur, true);
4943 cur.message(_("Cut"));
4946 case LFUN_SERVER_GET_XY:
4947 cur.message(from_utf8(
4948 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4949 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4952 case LFUN_SERVER_SET_XY: {
4955 istringstream is(to_utf8(cmd.argument()));
4958 lyxerr << "SETXY: Could not parse coordinates in '"
4959 << to_utf8(cmd.argument()) << endl;
4961 tm->setCursorFromCoordinates(cur, x, y);
4965 case LFUN_SERVER_GET_LAYOUT:
4966 cur.message(cur.paragraph().layout().name());
4970 case LFUN_LAYOUT_TOGGLE: {
4971 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
4972 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
4973 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
4975 docstring layout = resolveLayout(req_layout, cur);
4976 if (layout.empty()) {
4977 cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
4978 from_utf8(N_(" not known")));
4982 docstring const old_layout = cur.paragraph().layout().name();
4983 bool change_layout = !isAlreadyLayout(layout, cur);
4985 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
4986 change_layout = true;
4987 layout = resolveLayout(docstring(), cur);
4990 if (change_layout) {
4991 setLayout(cur, layout);
4992 if (cur.pit() > 0 && !ignoreautonests) {
4993 pit_type prev_pit = cur.pit() - 1;
4994 depth_type const cur_depth = pars_[cur.pit()].getDepth();
4995 // Scan for the previous par on same nesting level
4996 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
4998 set<docstring> const & autonests =
4999 pars_[prev_pit].layout().autonests();
5000 set<docstring> const & autonested =
5001 pars_[cur.pit()].layout().isAutonestedBy();
5002 if (autonests.find(layout) != autonests.end()
5003 || autonested.find(old_layout) != autonested.end())
5004 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5008 DocumentClass const & tclass = bv->buffer().params().documentClass();
5009 bool inautoarg = false;
5010 for (auto const & la_pair : tclass[layout].args()) {
5011 Layout::latexarg const & arg = la_pair.second;
5012 if (arg.autoinsert) {
5013 // If we had already inserted an arg automatically,
5014 // leave this now in order to insert the next one.
5016 cur.leaveInset(cur.inset());
5019 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5020 lyx::dispatch(cmd2);
5028 case LFUN_ENVIRONMENT_SPLIT: {
5029 bool const outer = cmd.argument() == "outer";
5030 bool const previous = cmd.argument() == "previous";
5031 bool const before = cmd.argument() == "before";
5032 bool const normal = cmd.argument().empty();
5033 Paragraph const & para = cur.paragraph();
5035 if (para.layout().isEnvironment())
5036 layout = para.layout().name();
5037 depth_type split_depth = cur.paragraph().params().depth();
5038 vector<depth_type> nextpars_depth;
5039 if (outer || previous) {
5040 // check if we have an environment in our scope
5041 pit_type pit = cur.pit();
5042 Paragraph cpar = pars_[pit];
5048 if (layout.empty() && previous
5049 && cpar.layout().isEnvironment()
5050 && cpar.params().depth() <= split_depth)
5051 layout = cpar.layout().name();
5052 if (cpar.params().depth() < split_depth
5053 && cpar.layout().isEnvironment()) {
5055 layout = cpar.layout().name();
5056 split_depth = cpar.params().depth();
5058 if (cpar.params().depth() == 0)
5062 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5063 // save nesting of following paragraphs if they are deeper
5065 pit_type offset = 1;
5066 depth_type cur_depth = pars_[cur.pit()].params().depth();
5067 while (cur.pit() + offset <= cur.lastpit()) {
5068 Paragraph cpar = pars_[cur.pit() + offset];
5069 depth_type nextpar_depth = cpar.params().depth();
5070 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5071 nextpars_depth.push_back(nextpar_depth);
5072 cur_depth = nextpar_depth;
5079 cur.top().setPitPos(cur.pit(), 0);
5080 if (before || cur.pos() > 0)
5081 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5082 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5083 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5085 while (cur.paragraph().params().depth() > split_depth)
5086 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5088 DocumentClass const & tc = bv->buffer().params().documentClass();
5089 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5090 + from_ascii("\" ignoreautonests")));
5091 // FIXME: Bibitem mess!
5092 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5093 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5094 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5097 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5098 while (cur.paragraph().params().depth() < split_depth)
5099 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5102 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5103 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5104 if ((outer || normal) && !nextpars_depth.empty()) {
5105 // restore nesting of following paragraphs
5106 DocIterator scur = cur;
5107 depth_type max_depth = cur.paragraph().params().depth() + 1;
5108 for (auto nextpar_depth : nextpars_depth) {
5110 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5111 depth_type const olddepth = cur.paragraph().params().depth();
5112 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5113 if (olddepth == cur.paragraph().params().depth())
5114 // leave loop if no incrementation happens
5117 max_depth = cur.paragraph().params().depth() + 1;
5119 cur.setCursor(scur);
5125 case LFUN_CLIPBOARD_PASTE:
5126 cap::replaceSelection(cur);
5127 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5128 cmd.argument() == "paragraph");
5129 bv->buffer().errors("Paste");
5132 case LFUN_CLIPBOARD_PASTE_SIMPLE:
5133 cap::replaceSelection(cur);
5134 pasteSimpleText(cur, cmd.argument() == "paragraph");
5137 case LFUN_PRIMARY_SELECTION_PASTE:
5138 cap::replaceSelection(cur);
5139 pasteString(cur, theSelection().get(),
5140 cmd.argument() == "paragraph");
5143 case LFUN_SELECTION_PASTE:
5144 // Copy the selection buffer to the clipboard stack,
5145 // because we want it to appear in the "Edit->Paste
5147 cap::replaceSelection(cur);
5148 cap::copySelectionToStack();
5149 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5150 bv->buffer().errors("Paste");
5153 case LFUN_QUOTE_INSERT: {
5154 cap::replaceSelection(cur);
5157 Paragraph const & par = cur.paragraph();
5158 pos_type pos = cur.pos();
5159 // Ignore deleted text before cursor
5160 while (pos > 0 && par.isDeleted(pos - 1))
5163 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5165 // Guess quote side.
5166 // A space triggers an opening quote. This is passed if the preceding
5167 // char/inset is a space or at paragraph start.
5169 if (pos > 0 && !par.isSpace(pos - 1)) {
5170 if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5171 // If an opening double quotation mark precedes, and this
5172 // is a single quote, make it opening as well
5174 static_cast<InsetQuotes &>(*cur.prevInset());
5175 string const type = ins.getType();
5176 if (!suffixIs(type, "ld") || !inner)
5177 c = par.getChar(pos - 1);
5179 else if (!cur.prevInset()
5180 || (cur.prevInset() && cur.prevInset()->isChar()))
5181 // If a char precedes, pass that and let InsetQuote decide
5182 c = par.getChar(pos - 1);
5185 if (par.getInset(pos - 1)
5186 && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5187 // skip "invisible" insets
5191 c = par.getChar(pos - 1);
5196 QuoteLevel const quote_level = inner
5197 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5198 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5199 cur.buffer()->updateBuffer();
5204 case LFUN_MOUSE_TRIPLE:
5205 if (cmd.button() == mouse_button::button1) {
5207 setCursor(cur, cur.pit(), 0);
5210 if (cur.pos() < cur.lastpos())
5211 setCursor(cur, cur.pit(), cur.lastpos());
5217 case LFUN_MOUSE_DOUBLE:
5218 if (cmd.button() == mouse_button::button1) {
5219 selectWord(cur, WHOLE_WORD);
5224 // Single-click on work area
5225 case LFUN_MOUSE_PRESS: {
5226 // We are not marking a selection with the keyboard in any case.
5227 Cursor & bvcur = cur.bv().cursor();
5228 bvcur.setMark(false);
5229 switch (cmd.button()) {
5230 case mouse_button::button1:
5231 if (!bvcur.selection())
5233 bvcur.resetAnchor();
5234 if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5235 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5236 // FIXME: move this to mouseSetCursor?
5237 if (bvcur.wordSelection() && bvcur.inTexted())
5238 expandWordSel(bvcur);
5241 case mouse_button::button2:
5242 if (lyxrc.mouse_middlebutton_paste) {
5243 // Middle mouse pasting.
5244 bv->mouseSetCursor(cur);
5246 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5247 "selection-paste ; primary-selection-paste paragraph"));
5249 cur.noScreenUpdate();
5252 case mouse_button::button3: {
5253 // Don't do anything if we right-click a
5254 // selection, a context menu will popup.
5255 if (bvcur.selection() && cur >= bvcur.selectionBegin()
5256 && cur <= bvcur.selectionEnd()) {
5257 cur.noScreenUpdate();
5260 if (!bv->mouseSetCursor(cur, false))
5261 cur.screenUpdateFlags(Update::FitCursor);
5267 } // switch (cmd.button())
5270 case LFUN_MOUSE_MOTION: {
5271 // Mouse motion with right or middle mouse do nothing for now.
5272 if (cmd.button() != mouse_button::button1) {
5273 cur.noScreenUpdate();
5276 // ignore motions deeper nested than the real anchor
5277 Cursor & bvcur = cur.bv().cursor();
5278 if (!bvcur.realAnchor().hasPart(cur)) {
5282 CursorSlice old = bvcur.top();
5284 int const wh = bv->workHeight();
5285 int const y = max(0, min(wh - 1, cmd.y()));
5287 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5288 cur.setTargetX(cmd.x());
5289 // Don't allow selecting a separator inset
5290 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5293 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5294 else if (cmd.y() < 0)
5295 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5296 // This is to allow jumping over large insets
5297 if (cur.top() == old) {
5299 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5300 else if (cmd.y() < 0)
5301 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5303 // We continue with our existing selection or start a new one, so don't
5304 // reset the anchor.
5305 bvcur.setCursor(cur);
5306 if (bvcur.wordSelection() && bvcur.inTexted())
5307 expandWordSel(bvcur);
5308 bvcur.selection(true);
5309 bvcur.setCurrentFont();
5310 if (cur.top() == old) {
5311 // We didn't move one iota, so no need to update the screen.
5312 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5313 //cur.noScreenUpdate();
5319 case LFUN_MOUSE_RELEASE:
5320 switch (cmd.button()) {
5321 case mouse_button::button1:
5322 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5323 // If there is a new selection, update persistent selection;
5324 // otherwise, single click does not clear persistent selection
5326 if (cur.selection()) {
5327 // Finish selection. If double click,
5328 // cur is moved to the end of word by
5329 // selectWord but bvcur is current
5331 cur.bv().cursor().setSelection();
5332 // We might have removed an empty but drawn selection
5333 // (probably a margin)
5334 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5336 cur.noScreenUpdate();
5337 // FIXME: We could try to handle drag and drop of selection here.
5340 case mouse_button::button2:
5341 // Middle mouse pasting is handled at mouse press time,
5342 // see LFUN_MOUSE_PRESS.
5343 cur.noScreenUpdate();
5346 case mouse_button::button3:
5347 // Cursor was set at LFUN_MOUSE_PRESS time.
5348 // FIXME: If there is a selection we could try to handle a special
5349 // drag & drop context menu.
5350 cur.noScreenUpdate();
5353 case mouse_button::none:
5354 case mouse_button::button4:
5355 case mouse_button::button5:
5357 } // switch (cmd.button())
5361 case LFUN_SELF_INSERT: {
5362 if (cmd.argument().empty())
5365 // Automatically delete the currently selected
5366 // text and replace it with what is being
5367 // typed in now. Depends on lyxrc settings
5368 // "auto_region_delete", which defaults to
5371 if (lyxrc.auto_region_delete && cur.selection()) {
5372 cutSelection(cur, false);
5373 cur.setCurrentFont();
5375 cur.clearSelection();
5377 for (char_type c : cmd.argument())
5378 bv->translateAndInsert(c, this, cur);
5381 moveCursor(cur, false);
5382 cur.markNewWordPosition();
5383 bv->bookmarkEditPosition();
5387 case LFUN_HREF_INSERT: {
5388 docstring content = cmd.argument();
5389 if (content.empty() && cur.selection())
5390 content = cur.selectionAsString(false);
5392 InsetCommandParams p(HYPERLINK_CODE);
5393 if (!content.empty()){
5394 // if it looks like a link, we'll put it as target,
5395 // otherwise as name (bug #8792).
5398 // regex_match(to_utf8(content), matches, link_re)
5399 // because smatch stores pointers to the substrings rather
5400 // than making copies of them. And those pointers become
5401 // invalid after regex_match returns, since it is then
5402 // being given a temporary object. (Thanks to Georg for
5403 // figuring that out.)
5404 regex const link_re("^(([a-z]+):|www\\.).*");
5406 string const c = to_utf8(lowercase(content));
5408 if (c.substr(0,7) == "mailto:") {
5409 p["target"] = content;
5410 p["type"] = from_ascii("mailto:");
5411 } else if (regex_match(c, matches, link_re)) {
5412 p["target"] = content;
5413 string protocol = matches.str(1);
5414 if (protocol == "file")
5415 p["type"] = from_ascii("file:");
5417 p["name"] = content;
5419 string const data = InsetCommand::params2string(p);
5421 // we need to have a target. if we already have one, then
5422 // that gets used at the default for the name, too, which
5423 // is probably what is wanted.
5424 if (p["target"].empty()) {
5425 bv->showDialog("href", data);
5427 FuncRequest fr(LFUN_INSET_INSERT, data);
5433 case LFUN_LABEL_INSERT: {
5434 InsetCommandParams p(LABEL_CODE);
5435 // Try to generate a valid label
5436 p["name"] = (cmd.argument().empty()) ?
5437 cur.getPossibleLabel() :
5439 string const data = InsetCommand::params2string(p);
5441 if (cmd.argument().empty()) {
5442 bv->showDialog("label", data);
5444 FuncRequest fr(LFUN_INSET_INSERT, data);
5450 case LFUN_INFO_INSERT: {
5451 if (cmd.argument().empty()) {
5452 bv->showDialog("info", cur.current_font.language()->lang());
5455 inset = createInset(cur.buffer(), cmd);
5459 insertInset(cur, inset);
5460 cur.forceBufferUpdate();
5465 case LFUN_CAPTION_INSERT:
5466 case LFUN_FOOTNOTE_INSERT:
5467 case LFUN_NOTE_INSERT:
5468 case LFUN_BOX_INSERT:
5469 case LFUN_BRANCH_INSERT:
5470 case LFUN_PHANTOM_INSERT:
5471 case LFUN_ERT_INSERT:
5472 case LFUN_INDEXMACRO_INSERT:
5473 case LFUN_LISTING_INSERT:
5474 case LFUN_MARGINALNOTE_INSERT:
5475 case LFUN_ARGUMENT_INSERT:
5476 case LFUN_INDEX_INSERT:
5477 case LFUN_PREVIEW_INSERT:
5478 case LFUN_SCRIPT_INSERT:
5479 case LFUN_IPA_INSERT: {
5480 // Indexes reset font formatting (#11961)
5481 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5482 // Open the inset, and move the current selection
5484 doInsertInset(cur, this, cmd, true, true, resetfont);
5486 cur.setCurrentFont();
5487 // Some insets are numbered, others are shown in the outline pane so
5488 // let's update the labels and the toc backend.
5489 cur.forceBufferUpdate();
5493 case LFUN_FLEX_INSERT: {
5494 // Open the inset, and move the current selection
5496 bool const sel = cur.selection();
5497 doInsertInset(cur, this, cmd, true, true);
5498 // Insert auto-insert arguments
5499 bool autoargs = false, inautoarg = false;
5500 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5501 for (auto const & argt : args) {
5502 Layout::latexarg arg = argt.second;
5503 if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5504 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5505 lyx::dispatch(cmd2);
5507 if (arg.autoinsert) {
5508 // The cursor might have been invalidated by the replaceSelection.
5509 cur.buffer()->changed(true);
5510 // If we had already inserted an arg automatically,
5511 // leave this now in order to insert the next one.
5513 cur.leaveInset(cur.inset());
5514 cur.setCurrentFont();
5516 if (arg.insertonnewline && cur.pos() > 0) {
5517 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5518 lyx::dispatch(cmd2);
5521 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5522 lyx::dispatch(cmd2);
5529 cur.leaveInset(cur.inset());
5532 // Some insets are numbered, others are shown in the outline pane so
5533 // let's update the labels and the toc backend.
5534 cur.forceBufferUpdate();
5538 case LFUN_TABULAR_INSERT: {
5539 // if there were no arguments, just open the dialog
5540 if (cmd.argument().empty()) {
5541 bv->showDialog("tabularcreate");
5543 } else if (cur.buffer()->masterParams().tablestyle != "default"
5544 || bv->buffer().params().documentClass().tablestyle() != "default") {
5545 string tabstyle = cur.buffer()->masterParams().tablestyle;
5546 if (tabstyle == "default")
5547 tabstyle = bv->buffer().params().documentClass().tablestyle();
5548 if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5549 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5550 tabstyle + " " + to_ascii(cmd.argument()));
5554 // Unknown style. Report and fall back to default.
5555 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5556 from_utf8(N_(" not known")));
5558 if (doInsertInset(cur, this, cmd, false, true))
5563 case LFUN_TABULAR_STYLE_INSERT: {
5564 string const style = cmd.getArg(0);
5565 string const rows = cmd.getArg(1);
5566 string const cols = cmd.getArg(2);
5567 if (cols.empty() || !isStrInt(cols)
5568 || rows.empty() || !isStrInt(rows))
5570 int const r = convert<int>(rows);
5571 int const c = convert<int>(cols);
5578 FileName const tabstyle = libFileSearch("tabletemplates",
5579 style + suffix + ".lyx", "lyx");
5580 if (tabstyle.empty())
5582 UndoGroupHelper ugh(cur.buffer());
5584 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5585 lyx::dispatch(cmd2);
5589 // move one cell up to middle cell
5591 // add the missing rows
5592 int const addrows = r - 3;
5593 for (int i = 0 ; i < addrows ; ++i) {
5594 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5598 // add the missing columns
5599 int const addcols = c - 1;
5600 for (int i = 0 ; i < addcols ; ++i) {
5601 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5610 case LFUN_FLOAT_INSERT:
5611 case LFUN_FLOAT_WIDE_INSERT:
5612 case LFUN_WRAP_INSERT: {
5613 // will some content be moved into the inset?
5614 bool const content = cur.selection();
5615 // does the content consist of multiple paragraphs?
5616 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5618 doInsertInset(cur, this, cmd, true, true);
5621 // If some single-par content is moved into the inset,
5622 // doInsertInset puts the cursor outside the inset.
5623 // To insert the caption we put it back into the inset.
5624 // FIXME cleanup doInsertInset to avoid such dances!
5625 if (content && singlepar)
5628 ParagraphList & pars = cur.text()->paragraphs();
5630 DocumentClass const & tclass = bv->buffer().params().documentClass();
5632 // add a separate paragraph for the caption inset
5633 pars.push_back(Paragraph());
5634 pars.back().setInsetOwner(&cur.text()->inset());
5635 pars.back().setPlainOrDefaultLayout(tclass);
5636 int cap_pit = pars.size() - 1;
5638 // if an empty inset was created, we create an additional empty
5639 // paragraph at the bottom so that the user can choose where to put
5640 // the graphics (or table).
5642 pars.push_back(Paragraph());
5643 pars.back().setInsetOwner(&cur.text()->inset());
5644 pars.back().setPlainOrDefaultLayout(tclass);
5647 // reposition the cursor to the caption
5648 cur.pit() = cap_pit;
5650 // FIXME: This Text/Cursor dispatch handling is a mess!
5651 // We cannot use Cursor::dispatch here it needs access to up to
5653 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5654 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5655 cur.forceBufferUpdate();
5656 cur.screenUpdateFlags(Update::Force);
5657 // FIXME: When leaving the Float (or Wrap) inset we should
5658 // delete any empty paragraph left above or below the
5663 case LFUN_NOMENCL_INSERT: {
5664 InsetCommandParams p(NOMENCL_CODE);
5665 if (cmd.argument().empty()) {
5667 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5668 cur.clearSelection();
5670 p["symbol"] = cmd.argument();
5671 string const data = InsetCommand::params2string(p);
5672 bv->showDialog("nomenclature", data);
5676 case LFUN_INDEX_PRINT: {
5677 InsetCommandParams p(INDEX_PRINT_CODE);
5678 if (cmd.argument().empty())
5679 p["type"] = from_ascii("idx");
5681 p["type"] = cmd.argument();
5682 string const data = InsetCommand::params2string(p);
5683 FuncRequest fr(LFUN_INSET_INSERT, data);
5688 case LFUN_NOMENCL_PRINT:
5689 case LFUN_NEWPAGE_INSERT:
5691 doInsertInset(cur, this, cmd, false, false);
5695 case LFUN_SEPARATOR_INSERT: {
5696 doInsertInset(cur, this, cmd, false, false);
5698 // remove a following space
5699 Paragraph & par = cur.paragraph();
5700 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5701 par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5705 case LFUN_DEPTH_DECREMENT:
5706 changeDepth(cur, DEC_DEPTH);
5709 case LFUN_DEPTH_INCREMENT:
5710 changeDepth(cur, INC_DEPTH);
5713 case LFUN_REGEXP_MODE:
5714 regexpDispatch(cur, cmd);
5717 case LFUN_MATH_MODE: {
5718 if (cmd.argument() == "on" || cmd.argument() == "") {
5719 // don't pass "on" as argument
5720 // (it would appear literally in the first cell)
5721 docstring sel = cur.selectionAsString(false);
5722 InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5723 // create a macro template if we see "\\newcommand" somewhere, and
5724 // an ordinary formula otherwise
5726 && (sel.find(from_ascii("\\newcommand")) != string::npos
5727 || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5728 || sel.find(from_ascii("\\def")) != string::npos)
5729 && macro->fromString(sel)) {
5731 replaceSelection(cur);
5734 // no meaningful macro template was found
5736 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5739 // The argument is meaningful
5740 // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5741 // has a different meaning in math mode
5742 mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5746 case LFUN_MATH_MACRO:
5747 if (cmd.argument().empty())
5748 cur.errorMessage(from_utf8(N_("Missing argument")));
5751 string s = to_utf8(cmd.argument());
5752 string const s1 = token(s, ' ', 1);
5753 int const nargs = s1.empty() ? 0 : convert<int>(s1);
5754 string const s2 = token(s, ' ', 2);
5755 MacroType type = MacroTypeNewcommand;
5757 type = MacroTypeDef;
5758 InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5759 from_utf8(token(s, ' ', 0)), nargs, false, type);
5760 inset->setBuffer(bv->buffer());
5761 insertInset(cur, inset);
5763 // enter macro inset and select the name
5765 cur.top().pos() = cur.top().lastpos();
5767 cur.selection(true);
5768 cur.top().pos() = 0;
5772 case LFUN_MATH_DISPLAY:
5773 case LFUN_MATH_SUBSCRIPT:
5774 case LFUN_MATH_SUPERSCRIPT:
5775 case LFUN_MATH_INSERT:
5776 case LFUN_MATH_AMS_MATRIX:
5777 case LFUN_MATH_MATRIX:
5778 case LFUN_MATH_DELIM:
5779 case LFUN_MATH_BIGDELIM:
5780 mathDispatch(cur, cmd);
5783 case LFUN_FONT_EMPH: {
5784 Font font(ignore_font, ignore_language);
5785 font.fontInfo().setEmph(FONT_TOGGLE);
5786 toggleAndShow(cur, this, font);
5790 case LFUN_FONT_ITAL: {
5791 Font font(ignore_font, ignore_language);
5792 font.fontInfo().setShape(ITALIC_SHAPE);
5793 toggleAndShow(cur, this, font);
5797 case LFUN_FONT_BOLD:
5798 case LFUN_FONT_BOLDSYMBOL: {
5799 Font font(ignore_font, ignore_language);
5800 font.fontInfo().setSeries(BOLD_SERIES);
5801 toggleAndShow(cur, this, font);
5805 case LFUN_FONT_NOUN: {
5806 Font font(ignore_font, ignore_language);
5807 font.fontInfo().setNoun(FONT_TOGGLE);
5808 toggleAndShow(cur, this, font);
5812 case LFUN_FONT_TYPEWRITER: {
5813 Font font(ignore_font, ignore_language);
5814 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5815 toggleAndShow(cur, this, font);
5819 case LFUN_FONT_SANS: {
5820 Font font(ignore_font, ignore_language);
5821 font.fontInfo().setFamily(SANS_FAMILY);
5822 toggleAndShow(cur, this, font);
5826 case LFUN_FONT_ROMAN: {
5827 Font font(ignore_font, ignore_language);
5828 font.fontInfo().setFamily(ROMAN_FAMILY);
5829 toggleAndShow(cur, this, font);
5833 case LFUN_FONT_DEFAULT: {
5834 Font font(inherit_font, ignore_language);
5835 toggleAndShow(cur, this, font);
5839 case LFUN_FONT_STRIKEOUT: {
5840 Font font(ignore_font, ignore_language);
5841 font.fontInfo().setStrikeout(FONT_TOGGLE);
5842 toggleAndShow(cur, this, font);
5846 case LFUN_FONT_CROSSOUT: {
5847 Font font(ignore_font, ignore_language);
5848 font.fontInfo().setXout(FONT_TOGGLE);
5849 toggleAndShow(cur, this, font);
5853 case LFUN_FONT_UNDERUNDERLINE: {
5854 Font font(ignore_font, ignore_language);
5855 font.fontInfo().setUuline(FONT_TOGGLE);
5856 toggleAndShow(cur, this, font);
5860 case LFUN_FONT_UNDERWAVE: {
5861 Font font(ignore_font, ignore_language);
5862 font.fontInfo().setUwave(FONT_TOGGLE);
5863 toggleAndShow(cur, this, font);
5867 case LFUN_FONT_UNDERLINE: {
5868 Font font(ignore_font, ignore_language);
5869 font.fontInfo().setUnderbar(FONT_TOGGLE);
5870 toggleAndShow(cur, this, font);
5874 case LFUN_FONT_NO_SPELLCHECK: {
5875 Font font(ignore_font, ignore_language);
5876 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5877 toggleAndShow(cur, this, font);
5881 case LFUN_FONT_SIZE: {
5882 Font font(ignore_font, ignore_language);
5883 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5884 toggleAndShow(cur, this, font);
5888 case LFUN_LANGUAGE: {
5889 string const lang_arg = cmd.getArg(0);
5890 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5891 Language const * lang =
5892 reset ? cur.bv().buffer().params().language
5893 : languages.getLanguage(lang_arg);
5894 // we allow reset_language, which is 0, but only if it
5895 // was requested via empty or "reset" arg.
5896 if (!lang && !reset)
5898 bool const toggle = (cmd.getArg(1) != "set");
5899 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5900 Font font(ignore_font, lang);
5901 toggleAndShow(cur, this, font, toggle);
5905 case LFUN_TEXTSTYLE_APPLY: {
5906 unsigned int num = 0;
5907 string const arg = to_utf8(cmd.argument());
5910 if (isStrUnsignedInt(arg)) {
5911 num = convert<uint>(arg);
5912 if (num >= freeFonts.size()) {
5913 cur.message(_("Invalid argument (number exceeds stack size)!"));
5917 cur.message(_("Invalid argument (must be a non-negative number)!"));
5921 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5922 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5926 // Set the freefont using the contents of \param data dispatched from
5927 // the frontends and apply it at the current cursor location.
5928 case LFUN_TEXTSTYLE_UPDATE: {
5929 Font font(ignore_font, ignore_language);
5931 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5932 docstring const props = font.stateText(&bv->buffer().params(), true);
5933 freeFonts.push(make_pair(props, font));
5935 toggleAndShow(cur, this, font, toggleall);
5936 cur.message(bformat(_("Text properties applied: %1$s"), props));
5938 LYXERR0("Invalid argument of textstyle-update");
5942 case LFUN_FINISHED_LEFT:
5943 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5944 // We're leaving an inset, going left. If the inset is LTR, we're
5945 // leaving from the front, so we should not move (remain at --- but
5946 // not in --- the inset). If the inset is RTL, move left, without
5947 // entering the inset itself; i.e., move to after the inset.
5948 if (cur.paragraph().getFontSettings(
5949 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5950 cursorVisLeft(cur, true);
5953 case LFUN_FINISHED_RIGHT:
5954 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
5955 // We're leaving an inset, going right. If the inset is RTL, we're
5956 // leaving from the front, so we should not move (remain at --- but
5957 // not in --- the inset). If the inset is LTR, move right, without
5958 // entering the inset itself; i.e., move to after the inset.
5959 if (!cur.paragraph().getFontSettings(
5960 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5961 cursorVisRight(cur, true);
5964 case LFUN_FINISHED_BACKWARD:
5965 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
5966 cur.setCurrentFont();
5969 case LFUN_FINISHED_FORWARD:
5970 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
5972 cur.setCurrentFont();
5975 case LFUN_LAYOUT_PARAGRAPH: {
5977 params2string(cur.paragraph(), data);
5978 data = "show\n" + data;
5979 bv->showDialog("paragraph", data);
5983 case LFUN_PARAGRAPH_UPDATE: {
5985 params2string(cur.paragraph(), data);
5987 // Will the paragraph accept changes from the dialog?
5989 cur.inset().allowParagraphCustomization(cur.idx());
5991 data = "update " + convert<string>(accept) + '\n' + data;
5992 bv->updateDialog("paragraph", data);
5996 case LFUN_ACCENT_UMLAUT:
5997 case LFUN_ACCENT_CIRCUMFLEX:
5998 case LFUN_ACCENT_GRAVE:
5999 case LFUN_ACCENT_ACUTE:
6000 case LFUN_ACCENT_TILDE:
6001 case LFUN_ACCENT_PERISPOMENI:
6002 case LFUN_ACCENT_CEDILLA:
6003 case LFUN_ACCENT_MACRON:
6004 case LFUN_ACCENT_DOT:
6005 case LFUN_ACCENT_UNDERDOT:
6006 case LFUN_ACCENT_UNDERBAR:
6007 case LFUN_ACCENT_CARON:
6008 case LFUN_ACCENT_BREVE:
6009 case LFUN_ACCENT_TIE:
6010 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6011 case LFUN_ACCENT_CIRCLE:
6012 case LFUN_ACCENT_OGONEK:
6013 theApp()->handleKeyFunc(cmd.action());
6014 if (!cmd.argument().empty())
6015 // FIXME: Are all these characters encoded in one byte in utf8?
6016 bv->translateAndInsert(cmd.argument()[0], this, cur);
6017 cur.screenUpdateFlags(Update::FitCursor);
6020 case LFUN_FLOAT_LIST_INSERT: {
6021 DocumentClass const & tclass = bv->buffer().params().documentClass();
6022 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6024 if (cur.selection())
6025 cutSelection(cur, false);
6026 breakParagraph(cur);
6028 if (cur.lastpos() != 0) {
6029 cursorBackward(cur);
6030 breakParagraph(cur);
6033 docstring const laystr = cur.inset().usePlainLayout() ?
6034 tclass.plainLayoutName() :
6035 tclass.defaultLayoutName();
6036 setLayout(cur, laystr);
6037 ParagraphParameters p;
6038 // FIXME If this call were replaced with one to clearParagraphParams(),
6039 // then we could get rid of this method altogether.
6040 setParagraphs(cur, p);
6041 // FIXME This should be simplified when InsetFloatList takes a
6042 // Buffer in its constructor.
6043 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6044 ifl->setBuffer(bv->buffer());
6045 insertInset(cur, ifl);
6048 lyxerr << "Non-existent float type: "
6049 << to_utf8(cmd.argument()) << endl;
6054 case LFUN_CHANGE_ACCEPT: {
6055 acceptOrRejectChanges(cur, ACCEPT);
6059 case LFUN_CHANGE_REJECT: {
6060 acceptOrRejectChanges(cur, REJECT);
6064 case LFUN_THESAURUS_ENTRY: {
6065 Language const * language = cur.getFont().language();
6066 docstring arg = cmd.argument();
6068 arg = cur.selectionAsString(false);
6069 // Too large. We unselect if needed and try to get
6070 // the first word in selection or under cursor
6071 if (arg.size() > 100 || arg.empty()) {
6072 if (cur.selection()) {
6073 DocIterator selbeg = cur.selectionBegin();
6074 cur.clearSelection();
6075 setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6076 cur.screenUpdateFlags(Update::Force);
6078 // Get word or selection
6079 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6080 arg = cur.selectionAsString(false);
6081 arg += " lang=" + from_ascii(language->lang());
6084 string lang = cmd.getArg(1);
6085 // This duplicates the code in GuiThesaurus::initialiseParams
6086 if (prefixIs(lang, "lang=")) {
6087 language = languages.getLanguage(lang.substr(5));
6089 language = cur.getFont().language();
6092 string lang = language->code();
6093 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6094 LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6095 frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6096 _("The path to the thesaurus directory has not been specified.\n"
6097 "The thesaurus is not functional.\n"
6098 "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6101 bv->showDialog("thesaurus", to_utf8(arg));
6105 case LFUN_SPELLING_ADD: {
6106 Language const * language = getLanguage(cur, cmd.getArg(1));
6107 docstring word = from_utf8(cmd.getArg(0));
6109 word = cur.selectionAsString(false);
6111 if (word.size() > 100 || word.empty()) {
6112 // Get word or selection
6113 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6114 word = cur.selectionAsString(false);
6117 WordLangTuple wl(word, language);
6118 theSpellChecker()->insert(wl);
6122 case LFUN_SPELLING_ADD_LOCAL: {
6123 Language const * language = getLanguage(cur, cmd.getArg(1));
6124 docstring word = from_utf8(cmd.getArg(0));
6126 word = cur.selectionAsString(false);
6127 if (word.size() > 100)
6130 // Get word or selection
6131 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6132 word = cur.selectionAsString(false);
6135 WordLangTuple wl(word, language);
6136 if (!bv->buffer().params().spellignored(wl)) {
6137 cur.recordUndoBufferParams();
6138 bv->buffer().params().spellignore().push_back(wl);
6140 // trigger re-check of whole buffer
6141 bv->buffer().requestSpellcheck();
6146 case LFUN_SPELLING_REMOVE_LOCAL: {
6147 Language const * language = getLanguage(cur, cmd.getArg(1));
6148 docstring word = from_utf8(cmd.getArg(0));
6150 word = cur.selectionAsString(false);
6151 if (word.size() > 100)
6154 // Get word or selection
6155 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6156 word = cur.selectionAsString(false);
6159 WordLangTuple wl(word, language);
6160 bool has_item = false;
6161 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6162 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6163 if (it->lang()->code() != wl.lang()->code())
6165 if (it->word() == wl.word()) {
6171 cur.recordUndoBufferParams();
6172 bv->buffer().params().spellignore().erase(it);
6174 // trigger re-check of whole buffer
6175 bv->buffer().requestSpellcheck();
6181 case LFUN_SPELLING_IGNORE: {
6182 Language const * language = getLanguage(cur, cmd.getArg(1));
6183 docstring word = from_utf8(cmd.getArg(0));
6185 word = cur.selectionAsString(false);
6187 if (word.size() > 100 || word.empty()) {
6188 // Get word or selection
6189 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6190 word = cur.selectionAsString(false);
6193 WordLangTuple wl(word, language);
6194 theSpellChecker()->accept(wl);
6198 case LFUN_SPELLING_REMOVE: {
6199 Language const * language = getLanguage(cur, cmd.getArg(1));
6200 docstring word = from_utf8(cmd.getArg(0));
6202 word = cur.selectionAsString(false);
6204 if (word.size() > 100 || word.empty()) {
6205 // Get word or selection
6206 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6207 word = cur.selectionAsString(false);
6210 WordLangTuple wl(word, language);
6211 theSpellChecker()->remove(wl);
6215 case LFUN_PARAGRAPH_PARAMS_APPLY: {
6216 // Given data, an encoding of the ParagraphParameters
6217 // generated in the Paragraph dialog, this function sets
6218 // the current paragraph, or currently selected paragraphs,
6220 // NOTE: This function overrides all existing settings.
6221 setParagraphs(cur, cmd.argument());
6222 cur.message(_("Paragraph layout set"));
6226 case LFUN_PARAGRAPH_PARAMS: {
6227 // Given data, an encoding of the ParagraphParameters as we'd
6228 // find them in a LyX file, this function modifies the current paragraph,
6229 // or currently selected paragraphs.
6230 // NOTE: This function only modifies, and does not override, existing
6232 setParagraphs(cur, cmd.argument(), true);
6233 cur.message(_("Paragraph layout set"));
6238 if (cur.selection()) {
6239 cur.selection(false);
6242 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6243 // correct, but I'm not 100% sure -- dov, 071019
6244 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6248 case LFUN_OUTLINE_UP: {
6249 pos_type const opos = cur.pos();
6250 outline(OutlineUp, cur, this);
6251 setCursor(cur, cur.pit(), opos);
6252 cur.forceBufferUpdate();
6257 case LFUN_OUTLINE_DOWN: {
6258 pos_type const opos = cur.pos();
6259 outline(OutlineDown, cur, this);
6260 setCursor(cur, cur.pit(), opos);
6261 cur.forceBufferUpdate();
6266 case LFUN_OUTLINE_IN:
6267 outline(OutlineIn, cur, this);
6268 cur.forceBufferUpdate();
6272 case LFUN_OUTLINE_OUT:
6273 outline(OutlineOut, cur, this);
6274 cur.forceBufferUpdate();
6278 case LFUN_SERVER_GET_STATISTICS: {
6279 DocIterator from, to;
6280 if (cur.selection()) {
6281 from = cur.selectionBegin();
6282 to = cur.selectionEnd();
6284 from = doc_iterator_begin(cur.buffer());
6285 to = doc_iterator_end(cur.buffer());
6288 cur.buffer()->updateStatistics(from, to);
6289 string const arg0 = cmd.getArg(0);
6290 if (arg0 == "words") {
6291 cur.message(convert<docstring>(cur.buffer()->wordCount()));
6292 } else if (arg0 == "chars") {
6293 cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6294 } else if (arg0 == "chars-space") {
6295 cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6297 cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6298 + convert<docstring>(cur.buffer()->charCount(false)) + " "
6299 + convert<docstring>(cur.buffer()->charCount(true)));
6305 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6310 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6312 if (lyxrc.spellcheck_continuously && !needsUpdate) {
6313 // Check for misspelled text
6314 // The redraw is useful because of the painting of
6315 // misspelled markers depends on the cursor position.
6316 // Trigger a redraw for cursor moves inside misspelled text.
6317 if (!cur.inTexted()) {
6318 // move from regular text to math
6319 needsUpdate = last_misspelled;
6320 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6321 // move inside regular text
6322 needsUpdate = last_misspelled
6323 || cur.paragraph().isMisspelled(cur.pos(), true);
6327 // FIXME: The cursor flag is reset two lines below
6328 // so we need to check here if some of the LFUN did touch that.
6329 // for now only Text::erase() and Text::backspace() do that.
6330 // The plan is to verify all the LFUNs and then to remove this
6331 // singleParUpdate boolean altogether.
6332 if (cur.result().screenUpdate() & Update::Force) {
6333 singleParUpdate = false;
6337 // FIXME: the following code should go in favor of fine grained
6338 // update flag treatment.
6339 if (singleParUpdate) {
6340 // Inserting characters does not change par height in general. So, try
6341 // to update _only_ this paragraph. BufferView will detect if a full
6342 // metrics update is needed anyway.
6343 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6347 && &oldTopSlice.inset() == &cur.inset()
6348 && oldTopSlice.idx() == cur.idx()
6349 && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
6350 && !cur.selection())
6351 // FIXME: it would be better if we could just do this
6353 //if (cur.result().update() != Update::FitCursor)
6354 // cur.noScreenUpdate();
6356 // But some LFUNs do not set Update::FitCursor when needed, so we
6357 // do it for all. This is not very harmfull as FitCursor will provoke
6358 // a full redraw only if needed but still, a proper review of all LFUN
6359 // should be done and this needsUpdate boolean can then be removed.
6360 cur.screenUpdateFlags(Update::FitCursor);
6362 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6366 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6367 FuncStatus & status) const
6369 LBUFERR(this == cur.text());
6371 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6373 bool allow_in_passthru = false;
6374 InsetCode code = NO_CODE;
6376 switch (cmd.action()) {
6378 case LFUN_DEPTH_DECREMENT:
6379 enable = changeDepthAllowed(cur, DEC_DEPTH);
6382 case LFUN_DEPTH_INCREMENT:
6383 enable = changeDepthAllowed(cur, INC_DEPTH);
6387 // FIXME We really should not allow this to be put, e.g.,
6388 // in a footnote, or in ERT. But it would make sense in a
6389 // branch, so I'm not sure what to do.
6390 status.setOnOff(cur.paragraph().params().startOfAppendix());
6393 case LFUN_DIALOG_SHOW_NEW_INSET:
6394 if (cmd.argument() == "bibitem")
6395 code = BIBITEM_CODE;
6396 else if (cmd.argument() == "bibtex") {
6398 // not allowed in description items
6399 enable = !inDescriptionItem(cur);
6401 else if (cmd.argument() == "box")
6403 else if (cmd.argument() == "branch")
6405 else if (cmd.argument() == "citation")
6407 else if (cmd.argument() == "counter")
6408 code = COUNTER_CODE;
6409 else if (cmd.argument() == "ert")
6411 else if (cmd.argument() == "external")
6412 code = EXTERNAL_CODE;
6413 else if (cmd.argument() == "float")
6415 else if (cmd.argument() == "graphics")
6416 code = GRAPHICS_CODE;
6417 else if (cmd.argument() == "href")
6418 code = HYPERLINK_CODE;
6419 else if (cmd.argument() == "include")
6420 code = INCLUDE_CODE;
6421 else if (cmd.argument() == "index")
6423 else if (cmd.argument() == "index_print")
6424 code = INDEX_PRINT_CODE;
6425 else if (cmd.argument() == "listings")
6426 code = LISTINGS_CODE;
6427 else if (cmd.argument() == "mathspace")
6428 code = MATH_HULL_CODE;
6429 else if (cmd.argument() == "nomenclature")
6430 code = NOMENCL_CODE;
6431 else if (cmd.argument() == "nomencl_print")
6432 code = NOMENCL_PRINT_CODE;
6433 else if (cmd.argument() == "label")
6435 else if (cmd.argument() == "line")
6437 else if (cmd.argument() == "note")
6439 else if (cmd.argument() == "phantom")
6440 code = PHANTOM_CODE;
6441 else if (cmd.argument() == "ref")
6443 else if (cmd.argument() == "space")
6445 else if (cmd.argument() == "toc")
6447 else if (cmd.argument() == "vspace")
6449 else if (cmd.argument() == "wrap")
6453 case LFUN_ERT_INSERT:
6456 case LFUN_LISTING_INSERT:
6457 code = LISTINGS_CODE;
6458 // not allowed in description items
6459 enable = !inDescriptionItem(cur);
6461 case LFUN_FOOTNOTE_INSERT:
6464 case LFUN_TABULAR_INSERT:
6465 code = TABULAR_CODE;
6467 case LFUN_TABULAR_STYLE_INSERT:
6468 code = TABULAR_CODE;
6470 case LFUN_MARGINALNOTE_INSERT:
6473 case LFUN_FLOAT_INSERT:
6474 case LFUN_FLOAT_WIDE_INSERT:
6475 // FIXME: If there is a selection, we should check whether there
6476 // are floats in the selection, but this has performance issues, see
6477 // LFUN_CHANGE_ACCEPT/REJECT.
6479 if (inDescriptionItem(cur))
6480 // not allowed in description items
6483 InsetCode const inset_code = cur.inset().lyxCode();
6485 // algorithm floats cannot be put in another float
6486 if (to_utf8(cmd.argument()) == "algorithm") {
6487 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6491 // for figures and tables: only allow in another
6492 // float or wrap if it is of the same type and
6493 // not a subfloat already
6494 if(cur.inset().lyxCode() == code) {
6495 InsetFloat const & ins =
6496 static_cast<InsetFloat const &>(cur.inset());
6497 enable = ins.params().type == to_utf8(cmd.argument())
6498 && !ins.params().subfloat;
6499 } else if(cur.inset().lyxCode() == WRAP_CODE) {
6500 InsetWrap const & ins =
6501 static_cast<InsetWrap const &>(cur.inset());
6502 enable = ins.params().type == to_utf8(cmd.argument());
6506 case LFUN_WRAP_INSERT:
6508 // not allowed in description items
6509 enable = !inDescriptionItem(cur);
6511 case LFUN_FLOAT_LIST_INSERT: {
6512 code = FLOAT_LIST_CODE;
6513 // not allowed in description items
6514 enable = !inDescriptionItem(cur);
6516 FloatList const & floats = cur.buffer()->params().documentClass().floats();
6517 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6518 // make sure we know about such floats
6519 if (cit == floats.end() ||
6520 // and that we know how to generate a list of them
6521 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6522 status.setUnknown(true);
6523 // probably not necessary, but...
6529 case LFUN_CAPTION_INSERT: {
6530 code = CAPTION_CODE;
6531 string arg = cmd.getArg(0);
6532 bool varia = arg != "Unnumbered"
6533 && cur.inset().allowsCaptionVariation(arg);
6534 // not allowed in description items,
6535 // and in specific insets
6536 enable = !inDescriptionItem(cur)
6537 && (varia || arg.empty() || arg == "Standard");
6540 case LFUN_NOTE_INSERT:
6543 case LFUN_FLEX_INSERT: {
6545 docstring s = from_utf8(cmd.getArg(0));
6546 // Prepend "Flex:" prefix if not there
6547 if (!prefixIs(s, from_ascii("Flex:")))
6548 s = from_ascii("Flex:") + s;
6549 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6553 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6554 if (ilt != InsetLyXType::CHARSTYLE
6555 && ilt != InsetLyXType::CUSTOM
6556 && ilt != InsetLyXType::STANDARD)
6561 case LFUN_BOX_INSERT:
6564 case LFUN_BRANCH_INSERT:
6566 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6567 && cur.buffer()->params().branchlist().empty())
6570 case LFUN_IPA_INSERT:
6573 case LFUN_PHANTOM_INSERT:
6574 code = PHANTOM_CODE;
6576 case LFUN_LABEL_INSERT:
6579 case LFUN_INFO_INSERT:
6581 enable = cmd.argument().empty()
6582 || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6584 case LFUN_ARGUMENT_INSERT: {
6586 allow_in_passthru = true;
6587 string const arg = cmd.getArg(0);
6592 Layout const & lay = cur.paragraph().layout();
6593 Layout::LaTeXArgMap args = lay.args();
6594 Layout::LaTeXArgMap::const_iterator const lait =
6596 if (lait != args.end()) {
6598 pit_type pit = cur.pit();
6599 pit_type lastpit = cur.pit();
6600 if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6601 // In a sequence of "merged" environment layouts, we only allow
6602 // non-item arguments once.
6603 lastpit = cur.lastpit();
6604 // get the first paragraph in sequence with this layout
6605 depth_type const current_depth = cur.paragraph().params().depth();
6609 Paragraph cpar = pars_[pit - 1];
6610 if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6616 for (; pit <= lastpit; ++pit) {
6617 if (pars_[pit].layout() != lay)
6619 for (auto const & table : pars_[pit].insetList())
6620 if (InsetArgument const * ins = table.inset->asInsetArgument())
6621 if (ins->name() == arg) {
6622 // we have this already
6631 case LFUN_INDEX_INSERT:
6634 case LFUN_INDEX_PRINT:
6635 code = INDEX_PRINT_CODE;
6636 // not allowed in description items
6637 enable = !inDescriptionItem(cur);
6639 case LFUN_NOMENCL_INSERT:
6640 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6644 code = NOMENCL_CODE;
6646 case LFUN_NOMENCL_PRINT:
6647 code = NOMENCL_PRINT_CODE;
6648 // not allowed in description items
6649 enable = !inDescriptionItem(cur);
6651 case LFUN_HREF_INSERT:
6652 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6656 code = HYPERLINK_CODE;
6658 case LFUN_INDEXMACRO_INSERT: {
6659 string const arg = cmd.getArg(0);
6660 if (arg == "sortkey")
6661 code = INDEXMACRO_SORTKEY_CODE;
6663 code = INDEXMACRO_CODE;
6666 case LFUN_IPAMACRO_INSERT: {
6667 string const arg = cmd.getArg(0);
6669 code = IPADECO_CODE;
6671 code = IPACHAR_CODE;
6674 case LFUN_QUOTE_INSERT:
6675 // always allow this, since we will inset a raw quote
6676 // if an inset is not allowed.
6677 allow_in_passthru = true;
6679 case LFUN_SPECIALCHAR_INSERT:
6680 code = SPECIALCHAR_CODE;
6682 case LFUN_SPACE_INSERT:
6683 // slight hack: we know this is allowed in math mode
6687 case LFUN_PREVIEW_INSERT:
6688 code = PREVIEW_CODE;
6690 case LFUN_SCRIPT_INSERT:
6694 case LFUN_MATH_INSERT:
6695 case LFUN_MATH_AMS_MATRIX:
6696 case LFUN_MATH_MATRIX:
6697 case LFUN_MATH_DELIM:
6698 case LFUN_MATH_BIGDELIM:
6699 case LFUN_MATH_DISPLAY:
6700 case LFUN_MATH_MODE:
6701 case LFUN_MATH_MACRO:
6702 case LFUN_MATH_SUBSCRIPT:
6703 case LFUN_MATH_SUPERSCRIPT:
6704 code = MATH_HULL_CODE;
6707 case LFUN_REGEXP_MODE:
6708 code = MATH_HULL_CODE;
6709 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6712 case LFUN_INSET_MODIFY:
6713 // We need to disable this, because we may get called for a
6715 // InsetTabular::getStatus() -> InsetText::getStatus()
6716 // and we don't handle LFUN_INSET_MODIFY.
6720 case LFUN_FONT_EMPH:
6721 status.setOnOff(fontinfo.emph() == FONT_ON);
6722 enable = !cur.paragraph().isPassThru();
6725 case LFUN_FONT_ITAL:
6726 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6727 enable = !cur.paragraph().isPassThru();
6730 case LFUN_FONT_NOUN:
6731 status.setOnOff(fontinfo.noun() == FONT_ON);
6732 enable = !cur.paragraph().isPassThru();
6735 case LFUN_FONT_BOLD:
6736 case LFUN_FONT_BOLDSYMBOL:
6737 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6738 enable = !cur.paragraph().isPassThru();
6741 case LFUN_FONT_SANS:
6742 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6743 enable = !cur.paragraph().isPassThru();
6746 case LFUN_FONT_ROMAN:
6747 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6748 enable = !cur.paragraph().isPassThru();
6751 case LFUN_FONT_TYPEWRITER:
6752 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6753 enable = !cur.paragraph().isPassThru();
6757 enable = cur.selection();
6761 if (cmd.argument().empty()) {
6762 if (theClipboard().isInternal())
6763 enable = cap::numberOfSelections() > 0;
6765 enable = !theClipboard().empty();
6769 // we have an argument
6770 string const arg = to_utf8(cmd.argument());
6771 if (isStrUnsignedInt(arg)) {
6772 // it's a number and therefore means the internal stack
6773 unsigned int n = convert<unsigned int>(arg);
6774 enable = cap::numberOfSelections() > n;
6778 // explicit text type?
6779 if (arg == "html") {
6780 // Do not enable for PlainTextType, since some tidying in the
6781 // frontend is needed for HTML, which is too unsafe for plain text.
6782 enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6784 } else if (arg == "latex") {
6785 // LaTeX is usually not available on the clipboard with
6786 // the correct MIME type, but in plain text.
6787 enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6788 theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6792 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6794 type = Clipboard::PdfGraphicsType;
6795 else if (arg == "png")
6796 type = Clipboard::PngGraphicsType;
6797 else if (arg == "jpeg")
6798 type = Clipboard::JpegGraphicsType;
6799 else if (arg == "linkback")
6800 type = Clipboard::LinkBackGraphicsType;
6801 else if (arg == "emf")
6802 type = Clipboard::EmfGraphicsType;
6803 else if (arg == "wmf")
6804 type = Clipboard::WmfGraphicsType;
6807 LYXERR0("Unrecognized graphics type: " << arg);
6808 // we don't want to assert if the user just mistyped the LFUN
6809 LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6813 enable = theClipboard().hasGraphicsContents(type);
6817 case LFUN_CLIPBOARD_PASTE:
6818 case LFUN_CLIPBOARD_PASTE_SIMPLE:
6819 enable = !theClipboard().empty();
6822 case LFUN_PRIMARY_SELECTION_PASTE:
6823 status.setUnknown(!theSelection().supported());
6824 enable = cur.selection() || !theSelection().empty();
6827 case LFUN_SELECTION_PASTE:
6828 enable = cap::selection();
6831 case LFUN_PARAGRAPH_MOVE_UP:
6832 enable = cur.pit() > 0 && !cur.selection();
6835 case LFUN_PARAGRAPH_MOVE_DOWN:
6836 enable = cur.pit() < cur.lastpit() && !cur.selection();
6839 case LFUN_CHANGE_ACCEPT:
6840 case LFUN_CHANGE_REJECT:
6841 if (!cur.selection())
6842 enable = cur.paragraph().isChanged(cur.pos());
6844 // will enable if there is a change in the selection
6847 // cheap improvement for efficiency: using cached
6848 // buffer variable, if there is no change in the
6849 // document, no need to check further.
6850 if (!cur.buffer()->areChangesPresent())
6853 for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6854 pos_type const beg = it.pos();
6856 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6857 it.idx() == cur.selectionEnd().idx());
6859 end = cur.selectionEnd().pos();
6861 // the +1 is needed for cases, e.g., where there is a
6862 // paragraph break. See #11629.
6863 end = it.lastpos() + 1;
6864 if (beg != end && it.paragraph().isChanged(beg, end)) {
6868 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6878 case LFUN_OUTLINE_UP:
6879 case LFUN_OUTLINE_DOWN:
6880 case LFUN_OUTLINE_IN:
6881 case LFUN_OUTLINE_OUT:
6882 // FIXME: LyX is not ready for outlining within inset.
6883 enable = isMainText()
6884 && cur.buffer()->text().getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6887 case LFUN_NEWLINE_INSERT:
6888 // LaTeX restrictions (labels or empty par)
6889 enable = !cur.paragraph().isPassThru()
6890 && cur.pos() > cur.paragraph().beginOfBody();
6893 case LFUN_SEPARATOR_INSERT:
6894 // Always enabled for now
6898 case LFUN_TAB_INSERT:
6899 case LFUN_TAB_DELETE:
6900 enable = cur.paragraph().isPassThru();
6903 case LFUN_GRAPHICS_SET_GROUP: {
6904 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6908 status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6912 case LFUN_NEWPAGE_INSERT:
6913 // not allowed in description items
6914 code = NEWPAGE_CODE;
6915 enable = !inDescriptionItem(cur);
6919 enable = !cur.paragraph().isPassThru();
6920 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6923 case LFUN_PARAGRAPH_BREAK:
6924 enable = inset().allowMultiPar();
6927 case LFUN_SPELLING_ADD:
6928 case LFUN_SPELLING_ADD_LOCAL:
6929 case LFUN_SPELLING_REMOVE_LOCAL:
6930 case LFUN_SPELLING_IGNORE:
6931 case LFUN_SPELLING_REMOVE:
6932 enable = theSpellChecker() != nullptr;
6933 if (enable && !cmd.getArg(1).empty()) {
6934 // validate explicitly given language
6935 Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6936 enable &= lang != nullptr;
6941 case LFUN_LAYOUT_TOGGLE: {
6942 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
6943 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
6944 docstring const layout = resolveLayout(req_layout, cur);
6946 // FIXME: make this work in multicell selection case
6947 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
6948 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
6949 && isAlreadyLayout(layout, cur));
6953 case LFUN_ENVIRONMENT_SPLIT: {
6954 if (cmd.argument() == "outer") {
6955 // check if we have an environment in our nesting hierarchy
6957 depth_type const current_depth = cur.paragraph().params().depth();
6958 pit_type pit = cur.pit();
6959 Paragraph cpar = pars_[pit];
6961 if (pit == 0 || cpar.params().depth() == 0)
6965 if (cpar.params().depth() < current_depth)
6966 res = cpar.layout().isEnvironment();
6971 else if (cmd.argument() == "previous") {
6972 // look if we have an environment in the previous par
6973 pit_type pit = cur.pit();
6974 Paragraph cpar = pars_[pit];
6978 enable = cpar.layout().isEnvironment();
6984 else if (cur.paragraph().layout().isEnvironment()) {
6985 enable = cmd.argument() == "before"
6986 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
6993 case LFUN_LAYOUT_PARAGRAPH:
6994 case LFUN_PARAGRAPH_PARAMS:
6995 case LFUN_PARAGRAPH_PARAMS_APPLY:
6996 case LFUN_PARAGRAPH_UPDATE:
6997 enable = owner_->allowParagraphCustomization();
7000 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
7001 // Because they insert COMBINING DIACRITICAL Unicode characters,
7002 // that cannot be handled by LaTeX but must be converted according
7003 // to the definition in lib/unicodesymbols?
7004 case LFUN_ACCENT_ACUTE:
7005 case LFUN_ACCENT_BREVE:
7006 case LFUN_ACCENT_CARON:
7007 case LFUN_ACCENT_CEDILLA:
7008 case LFUN_ACCENT_CIRCLE:
7009 case LFUN_ACCENT_CIRCUMFLEX:
7010 case LFUN_ACCENT_DOT:
7011 case LFUN_ACCENT_GRAVE:
7012 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7013 case LFUN_ACCENT_MACRON:
7014 case LFUN_ACCENT_OGONEK:
7015 case LFUN_ACCENT_TIE:
7016 case LFUN_ACCENT_TILDE:
7017 case LFUN_ACCENT_PERISPOMENI:
7018 case LFUN_ACCENT_UMLAUT:
7019 case LFUN_ACCENT_UNDERBAR:
7020 case LFUN_ACCENT_UNDERDOT:
7021 case LFUN_FONT_FRAK:
7022 case LFUN_FONT_SIZE:
7023 case LFUN_FONT_STATE:
7024 case LFUN_FONT_UNDERLINE:
7025 case LFUN_FONT_STRIKEOUT:
7026 case LFUN_FONT_CROSSOUT:
7027 case LFUN_FONT_UNDERUNDERLINE:
7028 case LFUN_FONT_UNDERWAVE:
7029 case LFUN_FONT_NO_SPELLCHECK:
7030 case LFUN_TEXTSTYLE_UPDATE:
7031 enable = !cur.paragraph().isPassThru();
7034 case LFUN_FONT_DEFAULT: {
7035 Font font(inherit_font, ignore_language);
7036 BufferParams const & bp = cur.buffer()->masterParams();
7037 if (cur.selection()) {
7039 // Check if we have a non-default font attribute
7040 // in the selection range.
7041 DocIterator const from = cur.selectionBegin();
7042 DocIterator const to = cur.selectionEnd();
7043 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7044 if (!dit.inTexted()) {
7048 Paragraph const & par = dit.paragraph();
7049 pos_type const pos = dit.pos();
7050 Font tmp = par.getFontSettings(bp, pos);
7051 if (tmp.fontInfo() != font.fontInfo()
7052 || tmp.language() != bp.language) {
7060 // Disable if all is default already.
7061 enable = (cur.current_font.fontInfo() != font.fontInfo()
7062 || cur.current_font.language() != bp.language);
7066 case LFUN_TEXTSTYLE_APPLY:
7067 enable = !freeFonts.empty();
7070 case LFUN_WORD_DELETE_FORWARD:
7071 case LFUN_WORD_DELETE_BACKWARD:
7072 case LFUN_LINE_DELETE_FORWARD:
7073 case LFUN_WORD_FORWARD:
7074 case LFUN_WORD_BACKWARD:
7075 case LFUN_WORD_RIGHT:
7076 case LFUN_WORD_LEFT:
7077 case LFUN_CHAR_FORWARD:
7078 case LFUN_CHAR_FORWARD_SELECT:
7079 case LFUN_CHAR_BACKWARD:
7080 case LFUN_CHAR_BACKWARD_SELECT:
7081 case LFUN_CHAR_LEFT:
7082 case LFUN_CHAR_LEFT_SELECT:
7083 case LFUN_CHAR_RIGHT:
7084 case LFUN_CHAR_RIGHT_SELECT:
7086 case LFUN_UP_SELECT:
7088 case LFUN_DOWN_SELECT:
7089 case LFUN_PARAGRAPH_SELECT:
7090 case LFUN_PARAGRAPH_UP_SELECT:
7091 case LFUN_PARAGRAPH_DOWN_SELECT:
7092 case LFUN_LINE_BEGIN_SELECT:
7093 case LFUN_LINE_END_SELECT:
7094 case LFUN_WORD_FORWARD_SELECT:
7095 case LFUN_WORD_BACKWARD_SELECT:
7096 case LFUN_WORD_RIGHT_SELECT:
7097 case LFUN_WORD_LEFT_SELECT:
7098 case LFUN_WORD_SELECT:
7099 case LFUN_SECTION_SELECT:
7100 case LFUN_BUFFER_BEGIN:
7101 case LFUN_BUFFER_END:
7102 case LFUN_BUFFER_BEGIN_SELECT:
7103 case LFUN_BUFFER_END_SELECT:
7104 case LFUN_INSET_BEGIN:
7105 case LFUN_INSET_END:
7106 case LFUN_INSET_BEGIN_SELECT:
7107 case LFUN_INSET_END_SELECT:
7108 case LFUN_PARAGRAPH_UP:
7109 case LFUN_PARAGRAPH_DOWN:
7110 case LFUN_LINE_BEGIN:
7112 case LFUN_CHAR_DELETE_FORWARD:
7113 case LFUN_CHAR_DELETE_BACKWARD:
7114 case LFUN_WORD_UPCASE:
7115 case LFUN_WORD_LOWCASE:
7116 case LFUN_WORD_CAPITALIZE:
7117 case LFUN_CHARS_TRANSPOSE:
7118 case LFUN_SERVER_GET_XY:
7119 case LFUN_SERVER_SET_XY:
7120 case LFUN_SERVER_GET_LAYOUT:
7121 case LFUN_SELF_INSERT:
7122 case LFUN_UNICODE_INSERT:
7123 case LFUN_THESAURUS_ENTRY:
7125 case LFUN_SERVER_GET_STATISTICS:
7126 // these are handled in our dispatch()
7130 case LFUN_INSET_INSERT: {
7131 string const type = cmd.getArg(0);
7132 if (type == "toc") {
7134 // not allowed in description items
7135 //FIXME: couldn't this be merged in Inset::insetAllowed()?
7136 enable = !inDescriptionItem(cur);
7143 case LFUN_SEARCH_IGNORE: {
7144 bool const value = cmd.getArg(1) == "true";
7145 setIgnoreFormat(cmd.getArg(0), value);
7155 || !cur.inset().insetAllowed(code)
7156 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7159 status.setEnabled(enable);
7164 void Text::pasteString(Cursor & cur, docstring const & clip,
7167 if (!clip.empty()) {
7170 insertStringAsParagraphs(cur, clip, cur.current_font);
7172 insertStringAsLines(cur, clip, cur.current_font);
7177 // FIXME: an item inset would make things much easier.
7178 bool Text::inDescriptionItem(Cursor const & cur) const
7180 Paragraph const & par = cur.paragraph();
7181 pos_type const pos = cur.pos();
7182 pos_type const body_pos = par.beginOfBody();
7184 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7185 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7186 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7189 return (pos < body_pos
7191 && (pos == 0 || par.getChar(pos - 1) != ' ')));
7195 std::vector<docstring> Text::getFreeFonts() const
7197 vector<docstring> ffList;
7199 for (auto const & f : freeFonts)
7200 ffList.push_back(f.first);