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, bool local)
3751 Buffer & buf = *cur.buffer();
3752 Text & text = *cur.text();
3753 pit_type & pit = cur.pit();
3754 ParagraphList & pars = text.paragraphs();
3755 ParagraphList::iterator const bgn = pars.begin();
3756 // The first paragraph of the area to be copied:
3757 ParagraphList::iterator start = pars.iterator_at(pit);
3758 // The final paragraph of area to be copied:
3759 ParagraphList::iterator finish = start;
3760 ParagraphList::iterator const end = pars.end();
3761 depth_type const current_depth = cur.paragraph().params().depth();
3763 int const thistoclevel = text.getTocLevel(distance(bgn, start));
3766 // Move out (down) from this section header
3770 if (!local || (mode != OutlineIn && mode != OutlineOut)) {
3771 // Seek the one (on same level) below
3772 for (; finish != end; ++finish) {
3773 toclevel = text.getTocLevel(distance(bgn, finish));
3774 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3781 if (start == pars.begin())
3784 ParagraphList::iterator dest = start;
3785 // Move out (up) from this header
3788 // Search previous same-level header above
3791 toclevel = text.getTocLevel(distance(bgn, dest));
3793 && (toclevel == Layout::NOT_IN_TOC
3794 || toclevel > thistoclevel));
3795 // Not found; do nothing
3796 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3798 pit_type newpit = distance(bgn, dest);
3799 pit_type const len = distance(start, finish);
3800 pit_type const deletepit = pit + len;
3801 buf.undo().recordUndo(cur, newpit, deletepit - 1);
3802 // If we move an environment upwards, make sure it is
3803 // separated from its new neighbour below:
3804 // If an environment of the same layout follows, and the moved
3805 // paragraph sequence does not end with a separator, insert one.
3806 ParagraphList::iterator lastmoved = finish;
3808 if (start->layout().isEnvironment()
3809 && dest->layout() == start->layout()
3810 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3811 cur.pit() = distance(bgn, lastmoved);
3812 cur.pos() = cur.lastpos();
3813 insertSeparator(cur, current_depth);
3816 // Likewise, if we moved an environment upwards, make sure it
3817 // is separated from its new neighbour above.
3818 // The paragraph before the target of movement
3820 ParagraphList::iterator before = dest;
3822 // Get the parent paragraph (outer in nested context)
3823 pit_type const parent =
3824 before->params().depth() > current_depth
3825 ? text.depthHook(distance(bgn, before), current_depth)
3826 : distance(bgn, before);
3827 // If a environment with same layout preceeds the moved one in the new
3828 // position, and there is no separator yet, insert one.
3829 if (start->layout().isEnvironment()
3830 && pars[parent].layout() == start->layout()
3831 && !before->isEnvSeparator(before->beginOfBody())) {
3832 cur.pit() = distance(bgn, before);
3833 cur.pos() = cur.lastpos();
3834 insertSeparator(cur, current_depth);
3838 newpit = distance(bgn, dest);
3839 pars.splice(dest, start, finish);
3847 // Go one down from *this* header:
3848 ParagraphList::iterator dest = next(finish, 1);
3849 // Go further down to find header to insert in front of:
3850 for (; dest != end; ++dest) {
3851 toclevel = text.getTocLevel(distance(bgn, dest));
3852 if (toclevel != Layout::NOT_IN_TOC
3853 && toclevel <= thistoclevel)
3856 // One such was found, so go on...
3857 // If we move an environment downwards, make sure it is
3858 // separated from its new neighbour above.
3859 pit_type newpit = distance(bgn, dest);
3860 buf.undo().recordUndo(cur, pit, newpit - 1);
3861 // The paragraph before the target of movement
3862 ParagraphList::iterator before = dest;
3864 // Get the parent paragraph (outer in nested context)
3865 pit_type const parent =
3866 before->params().depth() > current_depth
3867 ? text.depthHook(distance(bgn, before), current_depth)
3868 : distance(bgn, before);
3869 // If a environment with same layout preceeds the moved one in the new
3870 // position, and there is no separator yet, insert one.
3871 if (start->layout().isEnvironment()
3872 && pars[parent].layout() == start->layout()
3873 && !before->isEnvSeparator(before->beginOfBody())) {
3874 cur.pit() = distance(bgn, before);
3875 cur.pos() = cur.lastpos();
3876 insertSeparator(cur, current_depth);
3879 // Likewise, make sure moved environments are separated
3880 // from their new neighbour below:
3881 // If an environment of the same layout follows, and the moved
3882 // paragraph sequence does not end with a separator, insert one.
3883 ParagraphList::iterator lastmoved = finish;
3886 && start->layout().isEnvironment()
3887 && dest->layout() == start->layout()
3888 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3889 cur.pit() = distance(bgn, lastmoved);
3890 cur.pos() = cur.lastpos();
3891 insertSeparator(cur, current_depth);
3894 newpit = distance(bgn, dest);
3895 pit_type const len = distance(start, finish);
3896 pars.splice(dest, start, finish);
3897 cur.pit() = newpit - len;
3902 // We first iterate without actually doing something
3903 // in order to check whether the action flattens the structure.
3904 // If so, warn (#11178).
3905 ParagraphList::iterator cstart = start;
3906 bool strucchange = false;
3907 for (; cstart != finish; ++cstart) {
3908 toclevel = text.getTocLevel(distance(bgn, cstart));
3909 if (toclevel == Layout::NOT_IN_TOC)
3912 DocumentClass const & tc = buf.params().documentClass();
3913 int const newtoclevel =
3914 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3917 for (auto const & lay : tc) {
3918 if (lay.toclevel == newtoclevel
3919 && lay.isNumHeadingLabelType()
3920 && cstart->layout().isNumHeadingLabelType()) {
3931 && frontend::Alert::prompt(_("Action flattens document structure"),
3932 _("This action will cause some headings that have been "
3933 "on different level before to be on the same level "
3934 "since there is no more lower or higher heading level. "
3937 _("&Yes, continue nonetheless"),
3938 _("&No, quit operation")) == 1)
3941 pit_type const len = distance(start, finish);
3942 buf.undo().recordUndo(cur, pit, pit + len - 1);
3943 for (; start != finish; ++start) {
3944 toclevel = text.getTocLevel(distance(bgn, start));
3945 if (toclevel == Layout::NOT_IN_TOC)
3948 DocumentClass const & tc = buf.params().documentClass();
3949 int const newtoclevel =
3950 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3952 for (auto const & lay : tc) {
3953 if (lay.toclevel == newtoclevel
3954 && lay.isNumHeadingLabelType()
3955 && start->layout().isNumHeadingLabelType()) {
3956 start->setLayout(lay);
3970 void Text::number(Cursor & cur)
3972 FontInfo font = ignore_font;
3973 font.setNumber(FONT_TOGGLE);
3974 toggleAndShow(cur, this, Font(font, ignore_language));
3978 bool Text::isRTL(pit_type const pit) const
3980 Buffer const & buffer = owner_->buffer();
3981 return pars_[pit].isRTL(buffer.params());
3987 Language const * getLanguage(Cursor const & cur, string const & lang)
3989 return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
3993 docstring resolveLayout(docstring layout, DocIterator const & dit)
3995 Paragraph const & par = dit.paragraph();
3996 DocumentClass const & tclass = dit.buffer()->params().documentClass();
3999 layout = tclass.defaultLayoutName();
4001 if (dit.inset().forcePlainLayout(dit.idx()))
4002 // in this case only the empty layout is allowed
4003 layout = tclass.plainLayoutName();
4004 else if (par.usePlainLayout()) {
4005 // in this case, default layout maps to empty layout
4006 if (layout == tclass.defaultLayoutName())
4007 layout = tclass.plainLayoutName();
4009 // otherwise, the empty layout maps to the default
4010 if (layout == tclass.plainLayoutName())
4011 layout = tclass.defaultLayoutName();
4014 // If the entry is obsolete, use the new one instead.
4015 if (tclass.hasLayout(layout)) {
4016 docstring const & obs = tclass[layout].obsoleted_by();
4020 if (!tclass.hasLayout(layout))
4026 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4028 ParagraphList const & pars = cur.text()->paragraphs();
4030 pit_type pit = cur.selBegin().pit();
4031 pit_type const epit = cur.selEnd().pit() + 1;
4032 for ( ; pit != epit; ++pit)
4033 if (pars[pit].layout().name() != layout)
4043 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4045 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4047 // Dispatch if the cursor is inside the text. It is not the
4048 // case for context menus (bug 5797).
4049 if (cur.text() != this) {
4054 BufferView * bv = &cur.bv();
4055 TextMetrics * tm = &bv->textMetrics(this);
4056 if (!tm->contains(cur.pit())) {
4057 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4058 tm = &bv->textMetrics(this);
4061 // FIXME: We use the update flag to indicates wether a singlePar or a
4062 // full screen update is needed. We reset it here but shall we restore it
4064 cur.noScreenUpdate();
4066 LBUFERR(this == cur.text());
4068 // NOTE: This should NOT be a reference. See commit 94a5481a.
4069 CursorSlice const oldTopSlice = cur.top();
4070 bool const oldBoundary = cur.boundary();
4071 bool const oldSelection = cur.selection();
4072 // Signals that, even if needsUpdate == false, an update of the
4073 // cursor paragraph is required
4074 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4075 LyXAction::SingleParUpdate);
4076 // Signals that a full-screen update is required
4077 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4078 LyXAction::NoUpdate) || singleParUpdate);
4079 bool const last_misspelled = lyxrc.spellcheck_continuously
4080 && cur.paragraph().isMisspelled(cur.pos(), true);
4082 FuncCode const act = cmd.action();
4085 case LFUN_PARAGRAPH_MOVE_DOWN: {
4086 pit_type const pit = cur.pit();
4087 cur.recordUndo(pit, pit + 1);
4088 pars_.swap(pit, pit + 1);
4090 cur.forceBufferUpdate();
4095 case LFUN_PARAGRAPH_MOVE_UP: {
4096 pit_type const pit = cur.pit();
4097 cur.recordUndo(pit - 1, pit);
4099 pars_.swap(pit, pit - 1);
4102 cur.forceBufferUpdate();
4106 case LFUN_APPENDIX: {
4107 Paragraph & par = cur.paragraph();
4108 bool start = !par.params().startOfAppendix();
4110 // FIXME: The code below only makes sense at top level.
4111 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4112 // ensure that we have only one start_of_appendix in this document
4113 // FIXME: this don't work for multipart document!
4114 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4115 if (pars_[tmp].params().startOfAppendix()) {
4116 cur.recordUndo(tmp, tmp);
4117 pars_[tmp].params().startOfAppendix(false);
4123 par.params().startOfAppendix(start);
4125 // we can set the refreshing parameters now
4126 cur.forceBufferUpdate();
4130 case LFUN_WORD_DELETE_FORWARD:
4131 if (cur.selection())
4132 cutSelection(cur, false);
4134 deleteWordForward(cur, cmd.getArg(0) != "confirm");
4135 finishChange(cur, false);
4138 case LFUN_WORD_DELETE_BACKWARD:
4139 if (cur.selection())
4140 cutSelection(cur, false);
4142 deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4143 finishChange(cur, false);
4146 case LFUN_LINE_DELETE_FORWARD:
4147 if (cur.selection())
4148 cutSelection(cur, false);
4150 tm->deleteLineForward(cur);
4151 finishChange(cur, false);
4154 case LFUN_BUFFER_BEGIN:
4155 case LFUN_BUFFER_BEGIN_SELECT:
4156 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4157 if (cur.depth() == 1)
4158 needsUpdate |= cursorTop(cur);
4161 cur.screenUpdateFlags(Update::FitCursor);
4164 case LFUN_BUFFER_END:
4165 case LFUN_BUFFER_END_SELECT:
4166 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4167 if (cur.depth() == 1)
4168 needsUpdate |= cursorBottom(cur);
4171 cur.screenUpdateFlags(Update::FitCursor);
4174 case LFUN_INSET_BEGIN:
4175 case LFUN_INSET_BEGIN_SELECT:
4176 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4177 if (cur.depth() == 1 || !cur.top().at_begin())
4178 needsUpdate |= cursorTop(cur);
4181 cur.screenUpdateFlags(Update::FitCursor);
4184 case LFUN_INSET_END:
4185 case LFUN_INSET_END_SELECT:
4186 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4187 if (cur.depth() == 1 || !cur.top().at_end())
4188 needsUpdate |= cursorBottom(cur);
4191 cur.screenUpdateFlags(Update::FitCursor);
4194 case LFUN_CHAR_FORWARD:
4195 case LFUN_CHAR_FORWARD_SELECT: {
4196 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4197 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4198 bool const cur_moved = cursorForward(cur);
4199 needsUpdate |= cur_moved;
4201 if (!cur_moved && cur.depth() > 1
4202 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4204 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4206 // we will be moving out the inset, so we should execute
4207 // the depm-mechanism.
4208 // The cursor hasn't changed yet. To give the DEPM the
4209 // possibility of doing something we must provide it with
4210 // two different cursors.
4212 dummy.pos() = dummy.pit() = 0;
4213 if (cur.bv().checkDepm(dummy, cur))
4214 cur.forceBufferUpdate();
4219 case LFUN_CHAR_BACKWARD:
4220 case LFUN_CHAR_BACKWARD_SELECT: {
4221 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4222 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4223 bool const cur_moved = cursorBackward(cur);
4224 needsUpdate |= cur_moved;
4226 if (!cur_moved && cur.depth() > 1
4227 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4229 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4231 // we will be moving out the inset, so we should execute
4232 // the depm-mechanism.
4233 // The cursor hasn't changed yet. To give the DEPM the
4234 // possibility of doing something we must provide it with
4235 // two different cursors.
4237 dummy.pos() = cur.lastpos();
4238 dummy.pit() = cur.lastpit();
4239 if (cur.bv().checkDepm(dummy, cur))
4240 cur.forceBufferUpdate();
4245 case LFUN_CHAR_LEFT:
4246 case LFUN_CHAR_LEFT_SELECT:
4247 if (lyxrc.visual_cursor) {
4248 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4249 bool const cur_moved = cursorVisLeft(cur);
4250 needsUpdate |= cur_moved;
4251 if (!cur_moved && cur.depth() > 1
4252 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4254 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4257 if (cur.reverseDirectionNeeded()) {
4258 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4259 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4261 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4262 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4269 case LFUN_CHAR_RIGHT:
4270 case LFUN_CHAR_RIGHT_SELECT:
4271 if (lyxrc.visual_cursor) {
4272 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4273 bool const cur_moved = cursorVisRight(cur);
4274 needsUpdate |= cur_moved;
4275 if (!cur_moved && cur.depth() > 1
4276 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4278 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4281 if (cur.reverseDirectionNeeded()) {
4282 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4283 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4285 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4286 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4294 case LFUN_UP_SELECT:
4295 case LFUN_DOWN_SELECT:
4298 // stop/start the selection
4299 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4300 cmd.action() == LFUN_UP_SELECT;
4302 // move cursor up/down
4303 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4304 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4306 if (!atFirstOrLastRow) {
4307 needsUpdate |= cur.selHandle(select);
4308 cur.upDownInText(up, needsUpdate);
4309 needsUpdate |= cur.beforeDispatchCursor().inMathed();
4311 pos_type newpos = up ? 0 : cur.lastpos();
4312 if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4313 needsUpdate |= cur.selHandle(select);
4314 // we do not reset the targetx of the cursor
4316 needsUpdate |= bv->checkDepm(cur, bv->cursor());
4317 cur.updateTextTargetOffset();
4319 cur.forceBufferUpdate();
4323 // if the cursor cannot be moved up or down do not remove
4324 // the selection right now, but wait for the next dispatch.
4326 needsUpdate |= cur.selHandle(select);
4327 cur.upDownInText(up, needsUpdate);
4334 case LFUN_PARAGRAPH_SELECT:
4336 needsUpdate |= setCursor(cur, cur.pit(), 0);
4337 needsUpdate |= cur.selHandle(true);
4338 if (cur.pos() < cur.lastpos())
4339 needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4342 case LFUN_PARAGRAPH_UP:
4343 case LFUN_PARAGRAPH_UP_SELECT:
4344 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4345 needsUpdate |= cursorUpParagraph(cur);
4348 case LFUN_PARAGRAPH_DOWN:
4349 case LFUN_PARAGRAPH_DOWN_SELECT:
4350 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4351 needsUpdate |= cursorDownParagraph(cur);
4354 case LFUN_LINE_BEGIN:
4355 case LFUN_LINE_BEGIN_SELECT:
4356 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4357 needsUpdate |= tm->cursorHome(cur);
4361 case LFUN_LINE_END_SELECT:
4362 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4363 needsUpdate |= tm->cursorEnd(cur);
4366 case LFUN_SECTION_SELECT: {
4367 Buffer const & buf = *cur.buffer();
4368 pit_type const pit = cur.pit();
4369 ParagraphList & pars = buf.text().paragraphs();
4370 ParagraphList::iterator bgn = pars.begin();
4371 // The first paragraph of the area to be selected:
4372 ParagraphList::iterator start = pars.iterator_at(pit);
4373 // The final paragraph of area to be selected:
4374 ParagraphList::iterator finish = start;
4375 ParagraphList::iterator end = pars.end();
4377 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4378 if (thistoclevel == Layout::NOT_IN_TOC)
4382 Cursor const old_cur = cur;
4383 needsUpdate |= cur.selHandle(true);
4385 // Move out (down) from this section header
4389 // Seek the one (on same level) below
4390 for (; finish != end; ++finish, ++cur.pit()) {
4391 int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4392 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4395 cur.pos() = cur.lastpos();
4396 cur.boundary(false);
4397 cur.setCurrentFont();
4399 needsUpdate |= cur != old_cur;
4403 case LFUN_WORD_RIGHT:
4404 case LFUN_WORD_RIGHT_SELECT:
4405 if (lyxrc.visual_cursor) {
4406 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4407 bool const cur_moved = cursorVisRightOneWord(cur);
4408 needsUpdate |= cur_moved;
4409 if (!cur_moved && cur.depth() > 1
4410 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4412 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4415 if (cur.reverseDirectionNeeded()) {
4416 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4417 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4419 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4420 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4427 case LFUN_WORD_FORWARD:
4428 case LFUN_WORD_FORWARD_SELECT: {
4429 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4430 bool const cur_moved = cursorForwardOneWord(cur);
4431 needsUpdate |= cur_moved;
4433 if (!cur_moved && cur.depth() > 1
4434 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4436 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4438 // we will be moving out the inset, so we should execute
4439 // the depm-mechanism.
4440 // The cursor hasn't changed yet. To give the DEPM the
4441 // possibility of doing something we must provide it with
4442 // two different cursors.
4444 dummy.pos() = dummy.pit() = 0;
4445 if (cur.bv().checkDepm(dummy, cur))
4446 cur.forceBufferUpdate();
4451 case LFUN_WORD_LEFT:
4452 case LFUN_WORD_LEFT_SELECT:
4453 if (lyxrc.visual_cursor) {
4454 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4455 bool const cur_moved = cursorVisLeftOneWord(cur);
4456 needsUpdate |= cur_moved;
4457 if (!cur_moved && cur.depth() > 1
4458 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4460 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4463 if (cur.reverseDirectionNeeded()) {
4464 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4465 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4467 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4468 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4475 case LFUN_WORD_BACKWARD:
4476 case LFUN_WORD_BACKWARD_SELECT: {
4477 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4478 bool const cur_moved = cursorBackwardOneWord(cur);
4479 needsUpdate |= cur_moved;
4481 if (!cur_moved && cur.depth() > 1
4482 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4484 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4486 // we will be moving out the inset, so we should execute
4487 // the depm-mechanism.
4488 // The cursor hasn't changed yet. To give the DEPM the
4489 // possibility of doing something we must provide it with
4490 // two different cursors.
4492 dummy.pos() = cur.lastpos();
4493 dummy.pit() = cur.lastpit();
4494 if (cur.bv().checkDepm(dummy, cur))
4495 cur.forceBufferUpdate();
4500 case LFUN_WORD_SELECT: {
4501 selectWord(cur, WHOLE_WORD);
4502 finishChange(cur, true);
4506 case LFUN_NEWLINE_INSERT: {
4507 InsetNewlineParams inp;
4508 docstring const & arg = cmd.argument();
4509 if (arg == "linebreak")
4510 inp.kind = InsetNewlineParams::LINEBREAK;
4512 inp.kind = InsetNewlineParams::NEWLINE;
4513 cap::replaceSelection(cur);
4515 cur.insert(new InsetNewline(inp));
4517 moveCursor(cur, false);
4521 case LFUN_TAB_INSERT: {
4522 bool const multi_par_selection = cur.selection() &&
4523 cur.selBegin().pit() != cur.selEnd().pit();
4524 if (multi_par_selection) {
4525 // If there is a multi-paragraph selection, a tab is inserted
4526 // at the beginning of each paragraph.
4527 cur.recordUndoSelection();
4528 pit_type const pit_end = cur.selEnd().pit();
4529 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4530 pars_[pit].insertChar(0, '\t',
4531 bv->buffer().params().track_changes);
4532 // Update the selection pos to make sure the selection does not
4533 // change as the inserted tab will increase the logical pos.
4534 if (cur.realAnchor().pit() == pit)
4535 cur.realAnchor().forwardPos();
4536 if (cur.pit() == pit)
4541 // Maybe we shouldn't allow tabs within a line, because they
4542 // are not (yet) aligned as one might do expect.
4543 FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4544 dispatch(cur, ncmd);
4549 case LFUN_TAB_DELETE: {
4550 bool const tc = bv->buffer().params().track_changes;
4551 if (cur.selection()) {
4552 // If there is a selection, a tab (if present) is removed from
4553 // the beginning of each paragraph.
4554 cur.recordUndoSelection();
4555 pit_type const pit_end = cur.selEnd().pit();
4556 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4557 Paragraph & par = paragraphs()[pit];
4560 char_type const c = par.getChar(0);
4561 if (c == '\t' || c == ' ') {
4562 // remove either 1 tab or 4 spaces.
4563 int const n = (c == ' ' ? 4 : 1);
4564 for (int i = 0; i < n
4565 && !par.empty() && par.getChar(0) == c; ++i) {
4566 if (cur.pit() == pit)
4568 if (cur.realAnchor().pit() == pit
4569 && cur.realAnchor().pos() > 0 )
4570 cur.realAnchor().backwardPos();
4571 par.eraseChar(0, tc);
4577 // If there is no selection, try to remove a tab or some spaces
4578 // before the position of the cursor.
4579 Paragraph & par = paragraphs()[cur.pit()];
4580 pos_type const pos = cur.pos();
4585 char_type const c = par.getChar(pos - 1);
4589 par.eraseChar(cur.pos(), tc);
4591 for (int n_spaces = 0;
4593 && par.getChar(cur.pos() - 1) == ' '
4597 par.eraseChar(cur.pos(), tc);
4604 case LFUN_CHAR_DELETE_FORWARD:
4605 if (!cur.selection()) {
4606 if (cur.pos() == cur.paragraph().size())
4607 // Par boundary, force full-screen update
4608 singleParUpdate = false;
4609 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4611 cur.selection(true);
4616 needsUpdate |= erase(cur);
4619 cutSelection(cur, false);
4620 cur.setCurrentFont();
4621 singleParUpdate = false;
4623 moveCursor(cur, false);
4626 case LFUN_CHAR_DELETE_BACKWARD:
4627 if (!cur.selection()) {
4628 if (bv->getIntl().getTransManager().backspace()) {
4629 bool par_boundary = cur.pos() == 0;
4630 bool first_par = cur.pit() == 0;
4631 // Par boundary, full-screen update
4633 singleParUpdate = false;
4634 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4636 cur.selection(true);
4641 needsUpdate |= backspace(cur);
4643 if (par_boundary && !first_par && cur.pos() > 0
4644 && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4645 needsUpdate |= backspace(cur);
4650 DocIterator const dit = cur.selectionBegin();
4651 cutSelection(cur, false);
4652 if (cur.buffer()->params().track_changes)
4653 // since we're doing backwards deletion,
4654 // and the selection is not really cut,
4655 // move cursor before selection (#11630)
4657 cur.setCurrentFont();
4658 singleParUpdate = false;
4662 case LFUN_PARAGRAPH_BREAK: {
4663 cap::replaceSelection(cur);
4664 pit_type pit = cur.pit();
4665 Paragraph const & par = pars_[pit];
4666 bool lastpar = (pit == pit_type(pars_.size() - 1));
4667 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4668 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4669 if (prev < pit && cur.pos() == par.beginOfBody()
4670 && par.empty() && !par.isEnvSeparator(cur.pos())
4671 && !par.layout().keepempty
4672 && !par.layout().isCommand()
4673 && pars_[prev].layout() != par.layout()
4674 && pars_[prev].layout().isEnvironment()
4675 && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4676 if (par.layout().isEnvironment()
4677 && pars_[prev].getDepth() == par.getDepth()) {
4678 docstring const layout = par.layout().name();
4679 DocumentClass const & tc = bv->buffer().params().documentClass();
4680 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4681 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4682 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4683 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4685 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4686 breakParagraph(cur);
4688 Font const f(inherit_font, cur.current_font.language());
4689 pars_[cur.pit() - 1].resetFonts(f);
4691 if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4693 breakParagraph(cur, cmd.getArg(0) == "inverse");
4696 // If we have a list and autoinsert item insets,
4698 Layout::LaTeXArgMap args = par.layout().args();
4699 for (auto const & thearg : args) {
4700 Layout::latexarg arg = thearg.second;
4701 if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4702 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4703 lyx::dispatch(cmd2);
4709 case LFUN_INSET_INSERT: {
4712 // We have to avoid triggering InstantPreview loading
4713 // before inserting into the document. See bug #5626.
4714 bool loaded = bv->buffer().isFullyLoaded();
4715 bv->buffer().setFullyLoaded(false);
4716 Inset * inset = createInset(&bv->buffer(), cmd);
4717 bv->buffer().setFullyLoaded(loaded);
4720 // FIXME (Abdel 01/02/2006):
4721 // What follows would be a partial fix for bug 2154:
4722 // http://www.lyx.org/trac/ticket/2154
4723 // This automatically put the label inset _after_ a
4724 // numbered section. It should be possible to extend the mechanism
4725 // to any kind of LateX environement.
4726 // The correct way to fix that bug would be at LateX generation.
4727 // I'll let the code here for reference as it could be used for some
4728 // other feature like "automatic labelling".
4730 Paragraph & par = pars_[cur.pit()];
4731 if (inset->lyxCode() == LABEL_CODE
4732 && !par.layout().counter.empty()) {
4733 // Go to the end of the paragraph
4734 // Warning: Because of Change-Tracking, the last
4735 // position is 'size()' and not 'size()-1':
4736 cur.pos() = par.size();
4737 // Insert a new paragraph
4738 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4742 if (cur.selection())
4743 cutSelection(cur, false);
4745 cur.forceBufferUpdate();
4746 if (inset->editable() && inset->asInsetText())
4747 inset->edit(cur, true);
4751 // trigger InstantPreview now
4752 if (inset->lyxCode() == EXTERNAL_CODE) {
4753 InsetExternal & ins =
4754 static_cast<InsetExternal &>(*inset);
4755 ins.updatePreview();
4762 case LFUN_INSET_DISSOLVE: {
4763 if (dissolveInset(cur)) {
4765 cur.forceBufferUpdate();
4770 case LFUN_INSET_SPLIT: {
4771 if (splitInset(cur)) {
4773 cur.forceBufferUpdate();
4778 case LFUN_GRAPHICS_SET_GROUP: {
4779 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4785 string id = to_utf8(cmd.argument());
4786 string grp = graphics::getGroupParams(bv->buffer(), id);
4787 InsetGraphicsParams tmp, inspar = ins->getParams();
4790 inspar.groupId = to_utf8(cmd.argument());
4792 InsetGraphics::string2params(grp, bv->buffer(), tmp);
4793 tmp.filename = inspar.filename;
4797 ins->setParams(inspar);
4801 case LFUN_SPACE_INSERT:
4802 if (cur.paragraph().layout().free_spacing)
4803 insertChar(cur, ' ');
4805 doInsertInset(cur, this, cmd, false, false);
4808 moveCursor(cur, false);
4811 case LFUN_SPECIALCHAR_INSERT: {
4812 string const name = to_utf8(cmd.argument());
4813 if (name == "hyphenation")
4814 specialChar(cur, InsetSpecialChar::HYPHENATION);
4815 else if (name == "allowbreak")
4816 specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4817 else if (name == "ligature-break")
4818 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4819 else if (name == "slash")
4820 specialChar(cur, InsetSpecialChar::SLASH);
4821 else if (name == "nobreakdash")
4822 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4823 else if (name == "dots")
4824 specialChar(cur, InsetSpecialChar::LDOTS);
4825 else if (name == "end-of-sentence")
4826 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4827 else if (name == "menu-separator")
4828 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4829 else if (name == "lyx")
4830 specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4831 else if (name == "tex")
4832 specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4833 else if (name == "latex")
4834 specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4835 else if (name == "latex2e")
4836 specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4837 else if (name.empty())
4838 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4840 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4844 case LFUN_IPAMACRO_INSERT: {
4845 string const arg = cmd.getArg(0);
4846 if (arg == "deco") {
4847 // Open the inset, and move the current selection
4849 doInsertInset(cur, this, cmd, true, true);
4851 // Some insets are numbered, others are shown in the outline pane so
4852 // let's update the labels and the toc backend.
4853 cur.forceBufferUpdate();
4856 if (arg == "tone-falling")
4857 ipaChar(cur, InsetIPAChar::TONE_FALLING);
4858 else if (arg == "tone-rising")
4859 ipaChar(cur, InsetIPAChar::TONE_RISING);
4860 else if (arg == "tone-high-rising")
4861 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4862 else if (arg == "tone-low-rising")
4863 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4864 else if (arg == "tone-high-rising-falling")
4865 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4866 else if (arg.empty())
4867 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4869 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4873 case LFUN_WORD_UPCASE:
4874 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4877 case LFUN_WORD_LOWCASE:
4878 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4881 case LFUN_WORD_CAPITALIZE:
4882 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4885 case LFUN_CHARS_TRANSPOSE:
4886 charsTranspose(cur);
4890 cur.message(_("Paste"));
4891 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4892 cap::replaceSelection(cur);
4894 // without argument?
4895 string const arg = to_utf8(cmd.argument());
4897 bool tryGraphics = true;
4898 if (theClipboard().isInternal())
4899 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4900 else if (theClipboard().hasTextContents()) {
4901 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"),
4902 !cur.paragraph().parbreakIsNewline(),
4903 Clipboard::AnyTextType))
4904 tryGraphics = false;
4906 if (tryGraphics && theClipboard().hasGraphicsContents())
4907 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4908 } else if (isStrUnsignedInt(arg)) {
4909 // we have a numerical argument
4910 pasteFromStack(cur, bv->buffer().errorList("Paste"),
4911 convert<unsigned int>(arg));
4912 } else if (arg == "html" || arg == "latex") {
4913 Clipboard::TextType type = (arg == "html") ?
4914 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4915 pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4917 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4919 type = Clipboard::PdfGraphicsType;
4920 else if (arg == "png")
4921 type = Clipboard::PngGraphicsType;
4922 else if (arg == "jpeg")
4923 type = Clipboard::JpegGraphicsType;
4924 else if (arg == "linkback")
4925 type = Clipboard::LinkBackGraphicsType;
4926 else if (arg == "emf")
4927 type = Clipboard::EmfGraphicsType;
4928 else if (arg == "wmf")
4929 type = Clipboard::WmfGraphicsType;
4931 // we also check in getStatus()
4932 LYXERR0("Unrecognized graphics type: " << arg);
4934 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4937 bv->buffer().errors("Paste");
4938 bv->buffer().updatePreviews(); // bug 11619
4939 cur.clearSelection(); // bug 393
4945 cutSelection(cur, true);
4946 cur.message(_("Cut"));
4949 case LFUN_SERVER_GET_XY:
4950 cur.message(from_utf8(
4951 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4952 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4955 case LFUN_SERVER_SET_XY: {
4958 istringstream is(to_utf8(cmd.argument()));
4961 lyxerr << "SETXY: Could not parse coordinates in '"
4962 << to_utf8(cmd.argument()) << endl;
4964 tm->setCursorFromCoordinates(cur, x, y);
4968 case LFUN_SERVER_GET_LAYOUT:
4969 cur.message(cur.paragraph().layout().name());
4973 case LFUN_LAYOUT_TOGGLE: {
4974 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
4975 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
4976 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
4978 docstring layout = resolveLayout(req_layout, cur);
4979 if (layout.empty()) {
4980 cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
4981 from_utf8(N_(" not known")));
4985 docstring const old_layout = cur.paragraph().layout().name();
4986 bool change_layout = !isAlreadyLayout(layout, cur);
4988 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
4989 change_layout = true;
4990 layout = resolveLayout(docstring(), cur);
4993 if (change_layout) {
4994 setLayout(cur, layout);
4995 if (cur.pit() > 0 && !ignoreautonests) {
4996 pit_type prev_pit = cur.pit() - 1;
4997 depth_type const cur_depth = pars_[cur.pit()].getDepth();
4998 // Scan for the previous par on same nesting level
4999 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
5001 set<docstring> const & autonests =
5002 pars_[prev_pit].layout().autonests();
5003 set<docstring> const & autonested =
5004 pars_[cur.pit()].layout().isAutonestedBy();
5005 if (autonests.find(layout) != autonests.end()
5006 || autonested.find(old_layout) != autonested.end())
5007 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5011 DocumentClass const & tclass = bv->buffer().params().documentClass();
5012 bool inautoarg = false;
5013 for (auto const & la_pair : tclass[layout].args()) {
5014 Layout::latexarg const & arg = la_pair.second;
5015 if (arg.autoinsert) {
5016 // If we had already inserted an arg automatically,
5017 // leave this now in order to insert the next one.
5019 cur.leaveInset(cur.inset());
5022 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5023 lyx::dispatch(cmd2);
5031 case LFUN_ENVIRONMENT_SPLIT: {
5032 bool const outer = cmd.argument() == "outer";
5033 bool const previous = cmd.argument() == "previous";
5034 bool const before = cmd.argument() == "before";
5035 bool const normal = cmd.argument().empty();
5036 Paragraph const & para = cur.paragraph();
5038 if (para.layout().isEnvironment())
5039 layout = para.layout().name();
5040 depth_type split_depth = cur.paragraph().params().depth();
5041 vector<depth_type> nextpars_depth;
5042 if (outer || previous) {
5043 // check if we have an environment in our scope
5044 pit_type pit = cur.pit();
5045 Paragraph cpar = pars_[pit];
5051 if (layout.empty() && previous
5052 && cpar.layout().isEnvironment()
5053 && cpar.params().depth() <= split_depth)
5054 layout = cpar.layout().name();
5055 if (cpar.params().depth() < split_depth
5056 && cpar.layout().isEnvironment()) {
5058 layout = cpar.layout().name();
5059 split_depth = cpar.params().depth();
5061 if (cpar.params().depth() == 0)
5065 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5066 // save nesting of following paragraphs if they are deeper
5068 pit_type offset = 1;
5069 depth_type cur_depth = pars_[cur.pit()].params().depth();
5070 while (cur.pit() + offset <= cur.lastpit()) {
5071 Paragraph cpar = pars_[cur.pit() + offset];
5072 depth_type nextpar_depth = cpar.params().depth();
5073 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5074 nextpars_depth.push_back(nextpar_depth);
5075 cur_depth = nextpar_depth;
5082 cur.top().setPitPos(cur.pit(), 0);
5083 if (before || cur.pos() > 0)
5084 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5085 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5086 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5088 while (cur.paragraph().params().depth() > split_depth)
5089 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5091 DocumentClass const & tc = bv->buffer().params().documentClass();
5092 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5093 + from_ascii("\" ignoreautonests")));
5094 // FIXME: Bibitem mess!
5095 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5096 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5097 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5100 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5101 while (cur.paragraph().params().depth() < split_depth)
5102 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5105 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5106 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5107 if ((outer || normal) && !nextpars_depth.empty()) {
5108 // restore nesting of following paragraphs
5109 DocIterator scur = cur;
5110 depth_type max_depth = cur.paragraph().params().depth() + 1;
5111 for (auto nextpar_depth : nextpars_depth) {
5113 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5114 depth_type const olddepth = cur.paragraph().params().depth();
5115 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5116 if (olddepth == cur.paragraph().params().depth())
5117 // leave loop if no incrementation happens
5120 max_depth = cur.paragraph().params().depth() + 1;
5122 cur.setCursor(scur);
5128 case LFUN_CLIPBOARD_PASTE:
5129 cap::replaceSelection(cur);
5130 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5131 cmd.argument() == "paragraph");
5132 bv->buffer().errors("Paste");
5135 case LFUN_CLIPBOARD_PASTE_SIMPLE:
5136 cap::replaceSelection(cur);
5137 pasteSimpleText(cur, cmd.argument() == "paragraph");
5140 case LFUN_PRIMARY_SELECTION_PASTE:
5141 cap::replaceSelection(cur);
5142 pasteString(cur, theSelection().get(),
5143 cmd.argument() == "paragraph");
5146 case LFUN_SELECTION_PASTE:
5147 // Copy the selection buffer to the clipboard stack,
5148 // because we want it to appear in the "Edit->Paste
5150 cap::replaceSelection(cur);
5151 cap::copySelectionToStack();
5152 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5153 bv->buffer().errors("Paste");
5156 case LFUN_QUOTE_INSERT: {
5157 cap::replaceSelection(cur);
5160 Paragraph const & par = cur.paragraph();
5161 pos_type pos = cur.pos();
5162 // Ignore deleted text before cursor
5163 while (pos > 0 && par.isDeleted(pos - 1))
5166 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5168 // Guess quote side.
5169 // A space triggers an opening quote. This is passed if the preceding
5170 // char/inset is a space or at paragraph start.
5172 if (pos > 0 && !par.isSpace(pos - 1)) {
5173 if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5174 // If an opening double quotation mark precedes, and this
5175 // is a single quote, make it opening as well
5177 static_cast<InsetQuotes &>(*cur.prevInset());
5178 string const type = ins.getType();
5179 if (!suffixIs(type, "ld") || !inner)
5180 c = par.getChar(pos - 1);
5182 else if (!cur.prevInset()
5183 || (cur.prevInset() && cur.prevInset()->isChar()))
5184 // If a char precedes, pass that and let InsetQuote decide
5185 c = par.getChar(pos - 1);
5188 if (par.getInset(pos - 1)
5189 && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5190 // skip "invisible" insets
5194 c = par.getChar(pos - 1);
5199 QuoteLevel const quote_level = inner
5200 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5201 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5202 cur.buffer()->updateBuffer();
5207 case LFUN_MOUSE_TRIPLE:
5208 if (cmd.button() == mouse_button::button1) {
5210 setCursor(cur, cur.pit(), 0);
5213 if (cur.pos() < cur.lastpos())
5214 setCursor(cur, cur.pit(), cur.lastpos());
5220 case LFUN_MOUSE_DOUBLE:
5221 if (cmd.button() == mouse_button::button1) {
5222 selectWord(cur, WHOLE_WORD);
5227 // Single-click on work area
5228 case LFUN_MOUSE_PRESS: {
5229 // We are not marking a selection with the keyboard in any case.
5230 Cursor & bvcur = cur.bv().cursor();
5231 bvcur.setMark(false);
5232 switch (cmd.button()) {
5233 case mouse_button::button1:
5234 if (!bvcur.selection())
5236 bvcur.resetAnchor();
5237 if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5238 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5239 // FIXME: move this to mouseSetCursor?
5240 if (bvcur.wordSelection() && bvcur.inTexted())
5241 expandWordSel(bvcur);
5244 case mouse_button::button2:
5245 if (lyxrc.mouse_middlebutton_paste) {
5246 // Middle mouse pasting.
5247 bv->mouseSetCursor(cur);
5249 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5250 "selection-paste ; primary-selection-paste paragraph"));
5252 cur.noScreenUpdate();
5255 case mouse_button::button3: {
5256 // Don't do anything if we right-click a
5257 // selection, a context menu will popup.
5258 if (bvcur.selection() && cur >= bvcur.selectionBegin()
5259 && cur <= bvcur.selectionEnd()) {
5260 cur.noScreenUpdate();
5263 if (!bv->mouseSetCursor(cur, false))
5264 cur.screenUpdateFlags(Update::FitCursor);
5270 } // switch (cmd.button())
5273 case LFUN_MOUSE_MOTION: {
5274 // Mouse motion with right or middle mouse do nothing for now.
5275 if (cmd.button() != mouse_button::button1) {
5276 cur.noScreenUpdate();
5279 // ignore motions deeper nested than the real anchor
5280 Cursor & bvcur = cur.bv().cursor();
5281 if (!bvcur.realAnchor().hasPart(cur)) {
5285 CursorSlice old = bvcur.top();
5287 int const wh = bv->workHeight();
5288 int const y = max(0, min(wh - 1, cmd.y()));
5290 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5291 cur.setTargetX(cmd.x());
5292 // Don't allow selecting a separator inset
5293 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5296 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5297 else if (cmd.y() < 0)
5298 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5299 // This is to allow jumping over large insets
5300 if (cur.top() == old) {
5302 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5303 else if (cmd.y() < 0)
5304 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5306 // We continue with our existing selection or start a new one, so don't
5307 // reset the anchor.
5308 bvcur.setCursor(cur);
5309 if (bvcur.wordSelection() && bvcur.inTexted())
5310 expandWordSel(bvcur);
5311 bvcur.selection(true);
5312 bvcur.setCurrentFont();
5313 if (cur.top() == old) {
5314 // We didn't move one iota, so no need to update the screen.
5315 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5316 //cur.noScreenUpdate();
5322 case LFUN_MOUSE_RELEASE:
5323 switch (cmd.button()) {
5324 case mouse_button::button1:
5325 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5326 // If there is a new selection, update persistent selection;
5327 // otherwise, single click does not clear persistent selection
5329 if (cur.selection()) {
5330 // Finish selection. If double click,
5331 // cur is moved to the end of word by
5332 // selectWord but bvcur is current
5334 cur.bv().cursor().setSelection();
5335 // We might have removed an empty but drawn selection
5336 // (probably a margin)
5337 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5339 cur.noScreenUpdate();
5340 // FIXME: We could try to handle drag and drop of selection here.
5343 case mouse_button::button2:
5344 // Middle mouse pasting is handled at mouse press time,
5345 // see LFUN_MOUSE_PRESS.
5346 cur.noScreenUpdate();
5349 case mouse_button::button3:
5350 // Cursor was set at LFUN_MOUSE_PRESS time.
5351 // FIXME: If there is a selection we could try to handle a special
5352 // drag & drop context menu.
5353 cur.noScreenUpdate();
5356 case mouse_button::none:
5357 case mouse_button::button4:
5358 case mouse_button::button5:
5360 } // switch (cmd.button())
5364 case LFUN_SELF_INSERT: {
5365 if (cmd.argument().empty())
5368 // Automatically delete the currently selected
5369 // text and replace it with what is being
5370 // typed in now. Depends on lyxrc settings
5371 // "auto_region_delete", which defaults to
5374 if (lyxrc.auto_region_delete && cur.selection()) {
5375 cutSelection(cur, false);
5376 cur.setCurrentFont();
5378 cur.clearSelection();
5380 for (char_type c : cmd.argument())
5381 bv->translateAndInsert(c, this, cur);
5384 moveCursor(cur, false);
5385 cur.markNewWordPosition();
5386 bv->bookmarkEditPosition();
5390 case LFUN_HREF_INSERT: {
5391 docstring content = cmd.argument();
5392 if (content.empty() && cur.selection())
5393 content = cur.selectionAsString(false);
5395 InsetCommandParams p(HYPERLINK_CODE);
5396 if (!content.empty()){
5397 // if it looks like a link, we'll put it as target,
5398 // otherwise as name (bug #8792).
5401 // regex_match(to_utf8(content), matches, link_re)
5402 // because smatch stores pointers to the substrings rather
5403 // than making copies of them. And those pointers become
5404 // invalid after regex_match returns, since it is then
5405 // being given a temporary object. (Thanks to Georg for
5406 // figuring that out.)
5407 regex const link_re("^(([a-z]+):|www\\.).*");
5409 string const c = to_utf8(lowercase(content));
5411 if (c.substr(0,7) == "mailto:") {
5412 p["target"] = content;
5413 p["type"] = from_ascii("mailto:");
5414 } else if (regex_match(c, matches, link_re)) {
5415 p["target"] = content;
5416 string protocol = matches.str(1);
5417 if (protocol == "file")
5418 p["type"] = from_ascii("file:");
5420 p["name"] = content;
5422 string const data = InsetCommand::params2string(p);
5424 // we need to have a target. if we already have one, then
5425 // that gets used at the default for the name, too, which
5426 // is probably what is wanted.
5427 if (p["target"].empty()) {
5428 bv->showDialog("href", data);
5430 FuncRequest fr(LFUN_INSET_INSERT, data);
5436 case LFUN_LABEL_INSERT: {
5437 InsetCommandParams p(LABEL_CODE);
5438 // Try to generate a valid label
5439 p["name"] = (cmd.argument().empty()) ?
5440 cur.getPossibleLabel() :
5442 string const data = InsetCommand::params2string(p);
5444 if (cmd.argument().empty()) {
5445 bv->showDialog("label", data);
5447 FuncRequest fr(LFUN_INSET_INSERT, data);
5453 case LFUN_INFO_INSERT: {
5454 if (cmd.argument().empty()) {
5455 bv->showDialog("info", cur.current_font.language()->lang());
5458 inset = createInset(cur.buffer(), cmd);
5462 insertInset(cur, inset);
5463 cur.forceBufferUpdate();
5468 case LFUN_CAPTION_INSERT:
5469 case LFUN_FOOTNOTE_INSERT:
5470 case LFUN_NOTE_INSERT:
5471 case LFUN_BOX_INSERT:
5472 case LFUN_BRANCH_INSERT:
5473 case LFUN_PHANTOM_INSERT:
5474 case LFUN_ERT_INSERT:
5475 case LFUN_INDEXMACRO_INSERT:
5476 case LFUN_LISTING_INSERT:
5477 case LFUN_MARGINALNOTE_INSERT:
5478 case LFUN_ARGUMENT_INSERT:
5479 case LFUN_INDEX_INSERT:
5480 case LFUN_PREVIEW_INSERT:
5481 case LFUN_SCRIPT_INSERT:
5482 case LFUN_IPA_INSERT: {
5483 // Indexes reset font formatting (#11961)
5484 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5485 // Open the inset, and move the current selection
5487 doInsertInset(cur, this, cmd, true, true, resetfont);
5489 cur.setCurrentFont();
5490 // Some insets are numbered, others are shown in the outline pane so
5491 // let's update the labels and the toc backend.
5492 cur.forceBufferUpdate();
5496 case LFUN_FLEX_INSERT: {
5497 // Open the inset, and move the current selection
5499 bool const sel = cur.selection();
5500 doInsertInset(cur, this, cmd, true, true);
5501 // Insert auto-insert arguments
5502 bool autoargs = false, inautoarg = false;
5503 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5504 for (auto const & argt : args) {
5505 Layout::latexarg arg = argt.second;
5506 if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5507 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5508 lyx::dispatch(cmd2);
5510 if (arg.autoinsert) {
5511 // The cursor might have been invalidated by the replaceSelection.
5512 cur.buffer()->changed(true);
5513 // If we had already inserted an arg automatically,
5514 // leave this now in order to insert the next one.
5516 cur.leaveInset(cur.inset());
5517 cur.setCurrentFont();
5519 if (arg.insertonnewline && cur.pos() > 0) {
5520 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5521 lyx::dispatch(cmd2);
5524 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5525 lyx::dispatch(cmd2);
5532 cur.leaveInset(cur.inset());
5535 // Some insets are numbered, others are shown in the outline pane so
5536 // let's update the labels and the toc backend.
5537 cur.forceBufferUpdate();
5541 case LFUN_TABULAR_INSERT: {
5542 // if there were no arguments, just open the dialog
5543 if (cmd.argument().empty()) {
5544 bv->showDialog("tabularcreate");
5546 } else if (cur.buffer()->masterParams().tablestyle != "default"
5547 || bv->buffer().params().documentClass().tablestyle() != "default") {
5548 string tabstyle = cur.buffer()->masterParams().tablestyle;
5549 if (tabstyle == "default")
5550 tabstyle = bv->buffer().params().documentClass().tablestyle();
5551 if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5552 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5553 tabstyle + " " + to_ascii(cmd.argument()));
5557 // Unknown style. Report and fall back to default.
5558 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5559 from_utf8(N_(" not known")));
5561 if (doInsertInset(cur, this, cmd, false, true))
5566 case LFUN_TABULAR_STYLE_INSERT: {
5567 string const style = cmd.getArg(0);
5568 string const rows = cmd.getArg(1);
5569 string const cols = cmd.getArg(2);
5570 if (cols.empty() || !isStrInt(cols)
5571 || rows.empty() || !isStrInt(rows))
5573 int const r = convert<int>(rows);
5574 int const c = convert<int>(cols);
5581 FileName const tabstyle = libFileSearch("tabletemplates",
5582 style + suffix + ".lyx", "lyx");
5583 if (tabstyle.empty())
5585 UndoGroupHelper ugh(cur.buffer());
5587 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5588 lyx::dispatch(cmd2);
5592 // move one cell up to middle cell
5594 // add the missing rows
5595 int const addrows = r - 3;
5596 for (int i = 0 ; i < addrows ; ++i) {
5597 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5601 // add the missing columns
5602 int const addcols = c - 1;
5603 for (int i = 0 ; i < addcols ; ++i) {
5604 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5613 case LFUN_FLOAT_INSERT:
5614 case LFUN_FLOAT_WIDE_INSERT:
5615 case LFUN_WRAP_INSERT: {
5616 // will some content be moved into the inset?
5617 bool const content = cur.selection();
5618 // does the content consist of multiple paragraphs?
5619 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5621 doInsertInset(cur, this, cmd, true, true);
5624 // If some single-par content is moved into the inset,
5625 // doInsertInset puts the cursor outside the inset.
5626 // To insert the caption we put it back into the inset.
5627 // FIXME cleanup doInsertInset to avoid such dances!
5628 if (content && singlepar)
5631 ParagraphList & pars = cur.text()->paragraphs();
5633 DocumentClass const & tclass = bv->buffer().params().documentClass();
5635 // add a separate paragraph for the caption inset
5636 pars.push_back(Paragraph());
5637 pars.back().setInsetOwner(&cur.text()->inset());
5638 pars.back().setPlainOrDefaultLayout(tclass);
5639 int cap_pit = pars.size() - 1;
5641 // if an empty inset was created, we create an additional empty
5642 // paragraph at the bottom so that the user can choose where to put
5643 // the graphics (or table).
5645 pars.push_back(Paragraph());
5646 pars.back().setInsetOwner(&cur.text()->inset());
5647 pars.back().setPlainOrDefaultLayout(tclass);
5650 // reposition the cursor to the caption
5651 cur.pit() = cap_pit;
5653 // FIXME: This Text/Cursor dispatch handling is a mess!
5654 // We cannot use Cursor::dispatch here it needs access to up to
5656 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5657 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5658 cur.forceBufferUpdate();
5659 cur.screenUpdateFlags(Update::Force);
5660 // FIXME: When leaving the Float (or Wrap) inset we should
5661 // delete any empty paragraph left above or below the
5666 case LFUN_NOMENCL_INSERT: {
5667 InsetCommandParams p(NOMENCL_CODE);
5668 if (cmd.argument().empty()) {
5670 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5671 cur.clearSelection();
5673 p["symbol"] = cmd.argument();
5674 string const data = InsetCommand::params2string(p);
5675 bv->showDialog("nomenclature", data);
5679 case LFUN_INDEX_PRINT: {
5680 InsetCommandParams p(INDEX_PRINT_CODE);
5681 if (cmd.argument().empty())
5682 p["type"] = from_ascii("idx");
5684 p["type"] = cmd.argument();
5685 string const data = InsetCommand::params2string(p);
5686 FuncRequest fr(LFUN_INSET_INSERT, data);
5691 case LFUN_NOMENCL_PRINT:
5692 case LFUN_NEWPAGE_INSERT:
5694 doInsertInset(cur, this, cmd, false, false);
5698 case LFUN_SEPARATOR_INSERT: {
5699 doInsertInset(cur, this, cmd, false, false);
5701 // remove a following space
5702 Paragraph & par = cur.paragraph();
5703 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5704 par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5708 case LFUN_DEPTH_DECREMENT:
5709 changeDepth(cur, DEC_DEPTH);
5712 case LFUN_DEPTH_INCREMENT:
5713 changeDepth(cur, INC_DEPTH);
5716 case LFUN_REGEXP_MODE:
5717 regexpDispatch(cur, cmd);
5720 case LFUN_MATH_MODE: {
5721 if (cmd.argument() == "on" || cmd.argument() == "") {
5722 // don't pass "on" as argument
5723 // (it would appear literally in the first cell)
5724 docstring sel = cur.selectionAsString(false);
5725 InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5726 // create a macro template if we see "\\newcommand" somewhere, and
5727 // an ordinary formula otherwise
5729 && (sel.find(from_ascii("\\newcommand")) != string::npos
5730 || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5731 || sel.find(from_ascii("\\def")) != string::npos)
5732 && macro->fromString(sel)) {
5734 replaceSelection(cur);
5737 // no meaningful macro template was found
5739 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5742 // The argument is meaningful
5743 // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5744 // has a different meaning in math mode
5745 mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5749 case LFUN_MATH_MACRO:
5750 if (cmd.argument().empty())
5751 cur.errorMessage(from_utf8(N_("Missing argument")));
5754 string s = to_utf8(cmd.argument());
5755 string const s1 = token(s, ' ', 1);
5756 int const nargs = s1.empty() ? 0 : convert<int>(s1);
5757 string const s2 = token(s, ' ', 2);
5758 MacroType type = MacroTypeNewcommand;
5760 type = MacroTypeDef;
5761 InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5762 from_utf8(token(s, ' ', 0)), nargs, false, type);
5763 inset->setBuffer(bv->buffer());
5764 insertInset(cur, inset);
5766 // enter macro inset and select the name
5768 cur.top().pos() = cur.top().lastpos();
5770 cur.selection(true);
5771 cur.top().pos() = 0;
5775 case LFUN_MATH_DISPLAY:
5776 case LFUN_MATH_SUBSCRIPT:
5777 case LFUN_MATH_SUPERSCRIPT:
5778 case LFUN_MATH_INSERT:
5779 case LFUN_MATH_AMS_MATRIX:
5780 case LFUN_MATH_MATRIX:
5781 case LFUN_MATH_DELIM:
5782 case LFUN_MATH_BIGDELIM:
5783 mathDispatch(cur, cmd);
5786 case LFUN_FONT_EMPH: {
5787 Font font(ignore_font, ignore_language);
5788 font.fontInfo().setEmph(FONT_TOGGLE);
5789 toggleAndShow(cur, this, font);
5793 case LFUN_FONT_ITAL: {
5794 Font font(ignore_font, ignore_language);
5795 font.fontInfo().setShape(ITALIC_SHAPE);
5796 toggleAndShow(cur, this, font);
5800 case LFUN_FONT_BOLD:
5801 case LFUN_FONT_BOLDSYMBOL: {
5802 Font font(ignore_font, ignore_language);
5803 font.fontInfo().setSeries(BOLD_SERIES);
5804 toggleAndShow(cur, this, font);
5808 case LFUN_FONT_NOUN: {
5809 Font font(ignore_font, ignore_language);
5810 font.fontInfo().setNoun(FONT_TOGGLE);
5811 toggleAndShow(cur, this, font);
5815 case LFUN_FONT_TYPEWRITER: {
5816 Font font(ignore_font, ignore_language);
5817 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5818 toggleAndShow(cur, this, font);
5822 case LFUN_FONT_SANS: {
5823 Font font(ignore_font, ignore_language);
5824 font.fontInfo().setFamily(SANS_FAMILY);
5825 toggleAndShow(cur, this, font);
5829 case LFUN_FONT_ROMAN: {
5830 Font font(ignore_font, ignore_language);
5831 font.fontInfo().setFamily(ROMAN_FAMILY);
5832 toggleAndShow(cur, this, font);
5836 case LFUN_FONT_DEFAULT: {
5837 Font font(inherit_font, ignore_language);
5838 toggleAndShow(cur, this, font);
5842 case LFUN_FONT_STRIKEOUT: {
5843 Font font(ignore_font, ignore_language);
5844 font.fontInfo().setStrikeout(FONT_TOGGLE);
5845 toggleAndShow(cur, this, font);
5849 case LFUN_FONT_CROSSOUT: {
5850 Font font(ignore_font, ignore_language);
5851 font.fontInfo().setXout(FONT_TOGGLE);
5852 toggleAndShow(cur, this, font);
5856 case LFUN_FONT_UNDERUNDERLINE: {
5857 Font font(ignore_font, ignore_language);
5858 font.fontInfo().setUuline(FONT_TOGGLE);
5859 toggleAndShow(cur, this, font);
5863 case LFUN_FONT_UNDERWAVE: {
5864 Font font(ignore_font, ignore_language);
5865 font.fontInfo().setUwave(FONT_TOGGLE);
5866 toggleAndShow(cur, this, font);
5870 case LFUN_FONT_UNDERLINE: {
5871 Font font(ignore_font, ignore_language);
5872 font.fontInfo().setUnderbar(FONT_TOGGLE);
5873 toggleAndShow(cur, this, font);
5877 case LFUN_FONT_NO_SPELLCHECK: {
5878 Font font(ignore_font, ignore_language);
5879 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5880 toggleAndShow(cur, this, font);
5884 case LFUN_FONT_SIZE: {
5885 Font font(ignore_font, ignore_language);
5886 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5887 toggleAndShow(cur, this, font);
5891 case LFUN_LANGUAGE: {
5892 string const lang_arg = cmd.getArg(0);
5893 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5894 Language const * lang =
5895 reset ? cur.bv().buffer().params().language
5896 : languages.getLanguage(lang_arg);
5897 // we allow reset_language, which is 0, but only if it
5898 // was requested via empty or "reset" arg.
5899 if (!lang && !reset)
5901 bool const toggle = (cmd.getArg(1) != "set");
5902 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5903 Font font(ignore_font, lang);
5904 toggleAndShow(cur, this, font, toggle);
5908 case LFUN_TEXTSTYLE_APPLY: {
5909 unsigned int num = 0;
5910 string const arg = to_utf8(cmd.argument());
5913 if (isStrUnsignedInt(arg)) {
5914 num = convert<uint>(arg);
5915 if (num >= freeFonts.size()) {
5916 cur.message(_("Invalid argument (number exceeds stack size)!"));
5920 cur.message(_("Invalid argument (must be a non-negative number)!"));
5924 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5925 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5929 // Set the freefont using the contents of \param data dispatched from
5930 // the frontends and apply it at the current cursor location.
5931 case LFUN_TEXTSTYLE_UPDATE: {
5932 Font font(ignore_font, ignore_language);
5934 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5935 docstring const props = font.stateText(&bv->buffer().params(), true);
5936 freeFonts.push(make_pair(props, font));
5938 toggleAndShow(cur, this, font, toggleall);
5939 cur.message(bformat(_("Text properties applied: %1$s"), props));
5941 LYXERR0("Invalid argument of textstyle-update");
5945 case LFUN_FINISHED_LEFT:
5946 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5947 // We're leaving an inset, going left. If the inset is LTR, we're
5948 // leaving from the front, so we should not move (remain at --- but
5949 // not in --- the inset). If the inset is RTL, move left, without
5950 // entering the inset itself; i.e., move to after the inset.
5951 if (cur.paragraph().getFontSettings(
5952 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5953 cursorVisLeft(cur, true);
5956 case LFUN_FINISHED_RIGHT:
5957 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
5958 // We're leaving an inset, going right. If the inset is RTL, we're
5959 // leaving from the front, so we should not move (remain at --- but
5960 // not in --- the inset). If the inset is LTR, move right, without
5961 // entering the inset itself; i.e., move to after the inset.
5962 if (!cur.paragraph().getFontSettings(
5963 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5964 cursorVisRight(cur, true);
5967 case LFUN_FINISHED_BACKWARD:
5968 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
5969 cur.setCurrentFont();
5972 case LFUN_FINISHED_FORWARD:
5973 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
5975 cur.setCurrentFont();
5978 case LFUN_LAYOUT_PARAGRAPH: {
5980 params2string(cur.paragraph(), data);
5981 data = "show\n" + data;
5982 bv->showDialog("paragraph", data);
5986 case LFUN_PARAGRAPH_UPDATE: {
5988 params2string(cur.paragraph(), data);
5990 // Will the paragraph accept changes from the dialog?
5992 cur.inset().allowParagraphCustomization(cur.idx());
5994 data = "update " + convert<string>(accept) + '\n' + data;
5995 bv->updateDialog("paragraph", data);
5999 case LFUN_ACCENT_UMLAUT:
6000 case LFUN_ACCENT_CIRCUMFLEX:
6001 case LFUN_ACCENT_GRAVE:
6002 case LFUN_ACCENT_ACUTE:
6003 case LFUN_ACCENT_TILDE:
6004 case LFUN_ACCENT_PERISPOMENI:
6005 case LFUN_ACCENT_CEDILLA:
6006 case LFUN_ACCENT_MACRON:
6007 case LFUN_ACCENT_DOT:
6008 case LFUN_ACCENT_UNDERDOT:
6009 case LFUN_ACCENT_UNDERBAR:
6010 case LFUN_ACCENT_CARON:
6011 case LFUN_ACCENT_BREVE:
6012 case LFUN_ACCENT_TIE:
6013 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6014 case LFUN_ACCENT_CIRCLE:
6015 case LFUN_ACCENT_OGONEK:
6016 theApp()->handleKeyFunc(cmd.action());
6017 if (!cmd.argument().empty())
6018 // FIXME: Are all these characters encoded in one byte in utf8?
6019 bv->translateAndInsert(cmd.argument()[0], this, cur);
6020 cur.screenUpdateFlags(Update::FitCursor);
6023 case LFUN_FLOAT_LIST_INSERT: {
6024 DocumentClass const & tclass = bv->buffer().params().documentClass();
6025 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6027 if (cur.selection())
6028 cutSelection(cur, false);
6029 breakParagraph(cur);
6031 if (cur.lastpos() != 0) {
6032 cursorBackward(cur);
6033 breakParagraph(cur);
6036 docstring const laystr = cur.inset().usePlainLayout() ?
6037 tclass.plainLayoutName() :
6038 tclass.defaultLayoutName();
6039 setLayout(cur, laystr);
6040 ParagraphParameters p;
6041 // FIXME If this call were replaced with one to clearParagraphParams(),
6042 // then we could get rid of this method altogether.
6043 setParagraphs(cur, p);
6044 // FIXME This should be simplified when InsetFloatList takes a
6045 // Buffer in its constructor.
6046 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6047 ifl->setBuffer(bv->buffer());
6048 insertInset(cur, ifl);
6051 lyxerr << "Non-existent float type: "
6052 << to_utf8(cmd.argument()) << endl;
6057 case LFUN_CHANGE_ACCEPT: {
6058 acceptOrRejectChanges(cur, ACCEPT);
6062 case LFUN_CHANGE_REJECT: {
6063 acceptOrRejectChanges(cur, REJECT);
6067 case LFUN_THESAURUS_ENTRY: {
6068 Language const * language = cur.getFont().language();
6069 docstring arg = cmd.argument();
6071 arg = cur.selectionAsString(false);
6072 // Too large. We unselect if needed and try to get
6073 // the first word in selection or under cursor
6074 if (arg.size() > 100 || arg.empty()) {
6075 if (cur.selection()) {
6076 DocIterator selbeg = cur.selectionBegin();
6077 cur.clearSelection();
6078 setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6079 cur.screenUpdateFlags(Update::Force);
6081 // Get word or selection
6082 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6083 arg = cur.selectionAsString(false);
6084 arg += " lang=" + from_ascii(language->lang());
6087 string lang = cmd.getArg(1);
6088 // This duplicates the code in GuiThesaurus::initialiseParams
6089 if (prefixIs(lang, "lang=")) {
6090 language = languages.getLanguage(lang.substr(5));
6092 language = cur.getFont().language();
6095 string lang = language->code();
6096 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6097 LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6098 frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6099 _("The path to the thesaurus directory has not been specified.\n"
6100 "The thesaurus is not functional.\n"
6101 "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6104 bv->showDialog("thesaurus", to_utf8(arg));
6108 case LFUN_SPELLING_ADD: {
6109 Language const * language = getLanguage(cur, cmd.getArg(1));
6110 docstring word = from_utf8(cmd.getArg(0));
6112 word = cur.selectionAsString(false);
6114 if (word.size() > 100 || word.empty()) {
6115 // Get word or selection
6116 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6117 word = cur.selectionAsString(false);
6120 WordLangTuple wl(word, language);
6121 theSpellChecker()->insert(wl);
6125 case LFUN_SPELLING_ADD_LOCAL: {
6126 Language const * language = getLanguage(cur, cmd.getArg(1));
6127 docstring word = from_utf8(cmd.getArg(0));
6129 word = cur.selectionAsString(false);
6130 if (word.size() > 100)
6133 // Get word or selection
6134 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6135 word = cur.selectionAsString(false);
6138 WordLangTuple wl(word, language);
6139 if (!bv->buffer().params().spellignored(wl)) {
6140 cur.recordUndoBufferParams();
6141 bv->buffer().params().spellignore().push_back(wl);
6143 // trigger re-check of whole buffer
6144 bv->buffer().requestSpellcheck();
6149 case LFUN_SPELLING_REMOVE_LOCAL: {
6150 Language const * language = getLanguage(cur, cmd.getArg(1));
6151 docstring word = from_utf8(cmd.getArg(0));
6153 word = cur.selectionAsString(false);
6154 if (word.size() > 100)
6157 // Get word or selection
6158 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6159 word = cur.selectionAsString(false);
6162 WordLangTuple wl(word, language);
6163 bool has_item = false;
6164 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6165 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6166 if (it->lang()->code() != wl.lang()->code())
6168 if (it->word() == wl.word()) {
6174 cur.recordUndoBufferParams();
6175 bv->buffer().params().spellignore().erase(it);
6177 // trigger re-check of whole buffer
6178 bv->buffer().requestSpellcheck();
6184 case LFUN_SPELLING_IGNORE: {
6185 Language const * language = getLanguage(cur, cmd.getArg(1));
6186 docstring word = from_utf8(cmd.getArg(0));
6188 word = cur.selectionAsString(false);
6190 if (word.size() > 100 || word.empty()) {
6191 // Get word or selection
6192 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6193 word = cur.selectionAsString(false);
6196 WordLangTuple wl(word, language);
6197 theSpellChecker()->accept(wl);
6201 case LFUN_SPELLING_REMOVE: {
6202 Language const * language = getLanguage(cur, cmd.getArg(1));
6203 docstring word = from_utf8(cmd.getArg(0));
6205 word = cur.selectionAsString(false);
6207 if (word.size() > 100 || word.empty()) {
6208 // Get word or selection
6209 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6210 word = cur.selectionAsString(false);
6213 WordLangTuple wl(word, language);
6214 theSpellChecker()->remove(wl);
6218 case LFUN_PARAGRAPH_PARAMS_APPLY: {
6219 // Given data, an encoding of the ParagraphParameters
6220 // generated in the Paragraph dialog, this function sets
6221 // the current paragraph, or currently selected paragraphs,
6223 // NOTE: This function overrides all existing settings.
6224 setParagraphs(cur, cmd.argument());
6225 cur.message(_("Paragraph layout set"));
6229 case LFUN_PARAGRAPH_PARAMS: {
6230 // Given data, an encoding of the ParagraphParameters as we'd
6231 // find them in a LyX file, this function modifies the current paragraph,
6232 // or currently selected paragraphs.
6233 // NOTE: This function only modifies, and does not override, existing
6235 setParagraphs(cur, cmd.argument(), true);
6236 cur.message(_("Paragraph layout set"));
6241 if (cur.selection()) {
6242 cur.selection(false);
6245 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6246 // correct, but I'm not 100% sure -- dov, 071019
6247 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6251 case LFUN_OUTLINE_UP: {
6252 pos_type const opos = cur.pos();
6253 outline(OutlineUp, cur, false);
6254 setCursor(cur, cur.pit(), opos);
6255 cur.forceBufferUpdate();
6260 case LFUN_OUTLINE_DOWN: {
6261 pos_type const opos = cur.pos();
6262 outline(OutlineDown, cur, false);
6263 setCursor(cur, cur.pit(), opos);
6264 cur.forceBufferUpdate();
6269 case LFUN_OUTLINE_IN:
6270 outline(OutlineIn, cur, cmd.getArg(0) == "local");
6271 cur.forceBufferUpdate();
6275 case LFUN_OUTLINE_OUT:
6276 outline(OutlineOut, cur, cmd.getArg(0) == "local");
6277 cur.forceBufferUpdate();
6281 case LFUN_SERVER_GET_STATISTICS: {
6282 DocIterator from, to;
6283 if (cur.selection()) {
6284 from = cur.selectionBegin();
6285 to = cur.selectionEnd();
6287 from = doc_iterator_begin(cur.buffer());
6288 to = doc_iterator_end(cur.buffer());
6291 cur.buffer()->updateStatistics(from, to);
6292 string const arg0 = cmd.getArg(0);
6293 if (arg0 == "words") {
6294 cur.message(convert<docstring>(cur.buffer()->wordCount()));
6295 } else if (arg0 == "chars") {
6296 cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6297 } else if (arg0 == "chars-space") {
6298 cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6300 cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6301 + convert<docstring>(cur.buffer()->charCount(false)) + " "
6302 + convert<docstring>(cur.buffer()->charCount(true)));
6308 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6313 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6315 if (lyxrc.spellcheck_continuously && !needsUpdate) {
6316 // Check for misspelled text
6317 // The redraw is useful because of the painting of
6318 // misspelled markers depends on the cursor position.
6319 // Trigger a redraw for cursor moves inside misspelled text.
6320 if (!cur.inTexted()) {
6321 // move from regular text to math
6322 needsUpdate = last_misspelled;
6323 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6324 // move inside regular text
6325 needsUpdate = last_misspelled
6326 || cur.paragraph().isMisspelled(cur.pos(), true);
6330 // FIXME: The cursor flag is reset two lines below
6331 // so we need to check here if some of the LFUN did touch that.
6332 // for now only Text::erase() and Text::backspace() do that.
6333 // The plan is to verify all the LFUNs and then to remove this
6334 // singleParUpdate boolean altogether.
6335 if (cur.result().screenUpdate() & Update::Force) {
6336 singleParUpdate = false;
6340 // FIXME: the following code should go in favor of fine grained
6341 // update flag treatment.
6342 if (singleParUpdate) {
6343 // Inserting characters does not change par height in general. So, try
6344 // to update _only_ this paragraph. BufferView will detect if a full
6345 // metrics update is needed anyway.
6346 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6350 && &oldTopSlice.inset() == &cur.inset()
6351 && oldTopSlice.idx() == cur.idx()
6352 && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
6353 && !cur.selection())
6354 // FIXME: it would be better if we could just do this
6356 //if (cur.result().update() != Update::FitCursor)
6357 // cur.noScreenUpdate();
6359 // But some LFUNs do not set Update::FitCursor when needed, so we
6360 // do it for all. This is not very harmfull as FitCursor will provoke
6361 // a full redraw only if needed but still, a proper review of all LFUN
6362 // should be done and this needsUpdate boolean can then be removed.
6363 cur.screenUpdateFlags(Update::FitCursor);
6365 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6369 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6370 FuncStatus & status) const
6372 LBUFERR(this == cur.text());
6374 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6376 bool allow_in_passthru = false;
6377 InsetCode code = NO_CODE;
6379 switch (cmd.action()) {
6381 case LFUN_DEPTH_DECREMENT:
6382 enable = changeDepthAllowed(cur, DEC_DEPTH);
6385 case LFUN_DEPTH_INCREMENT:
6386 enable = changeDepthAllowed(cur, INC_DEPTH);
6390 // FIXME We really should not allow this to be put, e.g.,
6391 // in a footnote, or in ERT. But it would make sense in a
6392 // branch, so I'm not sure what to do.
6393 status.setOnOff(cur.paragraph().params().startOfAppendix());
6396 case LFUN_DIALOG_SHOW_NEW_INSET:
6397 if (cmd.argument() == "bibitem")
6398 code = BIBITEM_CODE;
6399 else if (cmd.argument() == "bibtex") {
6401 // not allowed in description items
6402 enable = !inDescriptionItem(cur);
6404 else if (cmd.argument() == "box")
6406 else if (cmd.argument() == "branch")
6408 else if (cmd.argument() == "citation")
6410 else if (cmd.argument() == "counter")
6411 code = COUNTER_CODE;
6412 else if (cmd.argument() == "ert")
6414 else if (cmd.argument() == "external")
6415 code = EXTERNAL_CODE;
6416 else if (cmd.argument() == "float")
6418 else if (cmd.argument() == "graphics")
6419 code = GRAPHICS_CODE;
6420 else if (cmd.argument() == "href")
6421 code = HYPERLINK_CODE;
6422 else if (cmd.argument() == "include")
6423 code = INCLUDE_CODE;
6424 else if (cmd.argument() == "index")
6426 else if (cmd.argument() == "index_print")
6427 code = INDEX_PRINT_CODE;
6428 else if (cmd.argument() == "listings")
6429 code = LISTINGS_CODE;
6430 else if (cmd.argument() == "mathspace")
6431 code = MATH_HULL_CODE;
6432 else if (cmd.argument() == "nomenclature")
6433 code = NOMENCL_CODE;
6434 else if (cmd.argument() == "nomencl_print")
6435 code = NOMENCL_PRINT_CODE;
6436 else if (cmd.argument() == "label")
6438 else if (cmd.argument() == "line")
6440 else if (cmd.argument() == "note")
6442 else if (cmd.argument() == "phantom")
6443 code = PHANTOM_CODE;
6444 else if (cmd.argument() == "ref")
6446 else if (cmd.argument() == "space")
6448 else if (cmd.argument() == "toc")
6450 else if (cmd.argument() == "vspace")
6452 else if (cmd.argument() == "wrap")
6456 case LFUN_ERT_INSERT:
6459 case LFUN_LISTING_INSERT:
6460 code = LISTINGS_CODE;
6461 // not allowed in description items
6462 enable = !inDescriptionItem(cur);
6464 case LFUN_FOOTNOTE_INSERT:
6467 case LFUN_TABULAR_INSERT:
6468 code = TABULAR_CODE;
6470 case LFUN_TABULAR_STYLE_INSERT:
6471 code = TABULAR_CODE;
6473 case LFUN_MARGINALNOTE_INSERT:
6476 case LFUN_FLOAT_INSERT:
6477 case LFUN_FLOAT_WIDE_INSERT:
6478 // FIXME: If there is a selection, we should check whether there
6479 // are floats in the selection, but this has performance issues, see
6480 // LFUN_CHANGE_ACCEPT/REJECT.
6482 if (inDescriptionItem(cur))
6483 // not allowed in description items
6486 InsetCode const inset_code = cur.inset().lyxCode();
6488 // algorithm floats cannot be put in another float
6489 if (to_utf8(cmd.argument()) == "algorithm") {
6490 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6494 // for figures and tables: only allow in another
6495 // float or wrap if it is of the same type and
6496 // not a subfloat already
6497 if(cur.inset().lyxCode() == code) {
6498 InsetFloat const & ins =
6499 static_cast<InsetFloat const &>(cur.inset());
6500 enable = ins.params().type == to_utf8(cmd.argument())
6501 && !ins.params().subfloat;
6502 } else if(cur.inset().lyxCode() == WRAP_CODE) {
6503 InsetWrap const & ins =
6504 static_cast<InsetWrap const &>(cur.inset());
6505 enable = ins.params().type == to_utf8(cmd.argument());
6509 case LFUN_WRAP_INSERT:
6511 // not allowed in description items
6512 enable = !inDescriptionItem(cur);
6514 case LFUN_FLOAT_LIST_INSERT: {
6515 code = FLOAT_LIST_CODE;
6516 // not allowed in description items
6517 enable = !inDescriptionItem(cur);
6519 FloatList const & floats = cur.buffer()->params().documentClass().floats();
6520 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6521 // make sure we know about such floats
6522 if (cit == floats.end() ||
6523 // and that we know how to generate a list of them
6524 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6525 status.setUnknown(true);
6526 // probably not necessary, but...
6532 case LFUN_CAPTION_INSERT: {
6533 code = CAPTION_CODE;
6534 string arg = cmd.getArg(0);
6535 bool varia = arg != "Unnumbered"
6536 && cur.inset().allowsCaptionVariation(arg);
6537 // not allowed in description items,
6538 // and in specific insets
6539 enable = !inDescriptionItem(cur)
6540 && (varia || arg.empty() || arg == "Standard");
6543 case LFUN_NOTE_INSERT:
6546 case LFUN_FLEX_INSERT: {
6548 docstring s = from_utf8(cmd.getArg(0));
6549 // Prepend "Flex:" prefix if not there
6550 if (!prefixIs(s, from_ascii("Flex:")))
6551 s = from_ascii("Flex:") + s;
6552 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6556 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6557 if (ilt != InsetLyXType::CHARSTYLE
6558 && ilt != InsetLyXType::CUSTOM
6559 && ilt != InsetLyXType::STANDARD)
6564 case LFUN_BOX_INSERT:
6567 case LFUN_BRANCH_INSERT:
6569 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6570 && cur.buffer()->params().branchlist().empty())
6573 case LFUN_IPA_INSERT:
6576 case LFUN_PHANTOM_INSERT:
6577 code = PHANTOM_CODE;
6579 case LFUN_LABEL_INSERT:
6582 case LFUN_INFO_INSERT:
6584 enable = cmd.argument().empty()
6585 || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6587 case LFUN_ARGUMENT_INSERT: {
6589 allow_in_passthru = true;
6590 string const arg = cmd.getArg(0);
6595 Layout const & lay = cur.paragraph().layout();
6596 Layout::LaTeXArgMap args = lay.args();
6597 Layout::LaTeXArgMap::const_iterator const lait =
6599 if (lait != args.end()) {
6601 pit_type pit = cur.pit();
6602 pit_type lastpit = cur.pit();
6603 if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6604 // In a sequence of "merged" environment layouts, we only allow
6605 // non-item arguments once.
6606 lastpit = cur.lastpit();
6607 // get the first paragraph in sequence with this layout
6608 depth_type const current_depth = cur.paragraph().params().depth();
6612 Paragraph cpar = pars_[pit - 1];
6613 if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6619 for (; pit <= lastpit; ++pit) {
6620 if (pars_[pit].layout() != lay)
6622 for (auto const & table : pars_[pit].insetList())
6623 if (InsetArgument const * ins = table.inset->asInsetArgument())
6624 if (ins->name() == arg) {
6625 // we have this already
6634 case LFUN_INDEX_INSERT:
6637 case LFUN_INDEX_PRINT:
6638 code = INDEX_PRINT_CODE;
6639 // not allowed in description items
6640 enable = !inDescriptionItem(cur);
6642 case LFUN_NOMENCL_INSERT:
6643 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6647 code = NOMENCL_CODE;
6649 case LFUN_NOMENCL_PRINT:
6650 code = NOMENCL_PRINT_CODE;
6651 // not allowed in description items
6652 enable = !inDescriptionItem(cur);
6654 case LFUN_HREF_INSERT:
6655 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6659 code = HYPERLINK_CODE;
6661 case LFUN_INDEXMACRO_INSERT: {
6662 string const arg = cmd.getArg(0);
6663 if (arg == "sortkey")
6664 code = INDEXMACRO_SORTKEY_CODE;
6666 code = INDEXMACRO_CODE;
6669 case LFUN_IPAMACRO_INSERT: {
6670 string const arg = cmd.getArg(0);
6672 code = IPADECO_CODE;
6674 code = IPACHAR_CODE;
6677 case LFUN_QUOTE_INSERT:
6678 // always allow this, since we will inset a raw quote
6679 // if an inset is not allowed.
6680 allow_in_passthru = true;
6682 case LFUN_SPECIALCHAR_INSERT:
6683 code = SPECIALCHAR_CODE;
6685 case LFUN_SPACE_INSERT:
6686 // slight hack: we know this is allowed in math mode
6690 case LFUN_PREVIEW_INSERT:
6691 code = PREVIEW_CODE;
6693 case LFUN_SCRIPT_INSERT:
6697 case LFUN_MATH_INSERT:
6698 case LFUN_MATH_AMS_MATRIX:
6699 case LFUN_MATH_MATRIX:
6700 case LFUN_MATH_DELIM:
6701 case LFUN_MATH_BIGDELIM:
6702 case LFUN_MATH_DISPLAY:
6703 case LFUN_MATH_MODE:
6704 case LFUN_MATH_MACRO:
6705 case LFUN_MATH_SUBSCRIPT:
6706 case LFUN_MATH_SUPERSCRIPT:
6707 code = MATH_HULL_CODE;
6710 case LFUN_REGEXP_MODE:
6711 code = MATH_HULL_CODE;
6712 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6715 case LFUN_INSET_MODIFY:
6716 // We need to disable this, because we may get called for a
6718 // InsetTabular::getStatus() -> InsetText::getStatus()
6719 // and we don't handle LFUN_INSET_MODIFY.
6723 case LFUN_FONT_EMPH:
6724 status.setOnOff(fontinfo.emph() == FONT_ON);
6725 enable = !cur.paragraph().isPassThru();
6728 case LFUN_FONT_ITAL:
6729 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6730 enable = !cur.paragraph().isPassThru();
6733 case LFUN_FONT_NOUN:
6734 status.setOnOff(fontinfo.noun() == FONT_ON);
6735 enable = !cur.paragraph().isPassThru();
6738 case LFUN_FONT_BOLD:
6739 case LFUN_FONT_BOLDSYMBOL:
6740 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6741 enable = !cur.paragraph().isPassThru();
6744 case LFUN_FONT_SANS:
6745 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6746 enable = !cur.paragraph().isPassThru();
6749 case LFUN_FONT_ROMAN:
6750 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6751 enable = !cur.paragraph().isPassThru();
6754 case LFUN_FONT_TYPEWRITER:
6755 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6756 enable = !cur.paragraph().isPassThru();
6760 enable = cur.selection();
6764 if (cmd.argument().empty()) {
6765 if (theClipboard().isInternal())
6766 enable = cap::numberOfSelections() > 0;
6768 enable = !theClipboard().empty();
6772 // we have an argument
6773 string const arg = to_utf8(cmd.argument());
6774 if (isStrUnsignedInt(arg)) {
6775 // it's a number and therefore means the internal stack
6776 unsigned int n = convert<unsigned int>(arg);
6777 enable = cap::numberOfSelections() > n;
6781 // explicit text type?
6782 if (arg == "html") {
6783 // Do not enable for PlainTextType, since some tidying in the
6784 // frontend is needed for HTML, which is too unsafe for plain text.
6785 enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6787 } else if (arg == "latex") {
6788 // LaTeX is usually not available on the clipboard with
6789 // the correct MIME type, but in plain text.
6790 enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6791 theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6795 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6797 type = Clipboard::PdfGraphicsType;
6798 else if (arg == "png")
6799 type = Clipboard::PngGraphicsType;
6800 else if (arg == "jpeg")
6801 type = Clipboard::JpegGraphicsType;
6802 else if (arg == "linkback")
6803 type = Clipboard::LinkBackGraphicsType;
6804 else if (arg == "emf")
6805 type = Clipboard::EmfGraphicsType;
6806 else if (arg == "wmf")
6807 type = Clipboard::WmfGraphicsType;
6810 LYXERR0("Unrecognized graphics type: " << arg);
6811 // we don't want to assert if the user just mistyped the LFUN
6812 LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6816 enable = theClipboard().hasGraphicsContents(type);
6820 case LFUN_CLIPBOARD_PASTE:
6821 case LFUN_CLIPBOARD_PASTE_SIMPLE:
6822 enable = !theClipboard().empty();
6825 case LFUN_PRIMARY_SELECTION_PASTE:
6826 status.setUnknown(!theSelection().supported());
6827 enable = cur.selection() || !theSelection().empty();
6830 case LFUN_SELECTION_PASTE:
6831 enable = cap::selection();
6834 case LFUN_PARAGRAPH_MOVE_UP:
6835 enable = cur.pit() > 0 && !cur.selection();
6838 case LFUN_PARAGRAPH_MOVE_DOWN:
6839 enable = cur.pit() < cur.lastpit() && !cur.selection();
6842 case LFUN_CHANGE_ACCEPT:
6843 case LFUN_CHANGE_REJECT:
6844 if (!cur.selection())
6845 enable = cur.paragraph().isChanged(cur.pos());
6847 // will enable if there is a change in the selection
6850 // cheap improvement for efficiency: using cached
6851 // buffer variable, if there is no change in the
6852 // document, no need to check further.
6853 if (!cur.buffer()->areChangesPresent())
6856 for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6857 pos_type const beg = it.pos();
6859 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6860 it.idx() == cur.selectionEnd().idx());
6862 end = cur.selectionEnd().pos();
6864 // the +1 is needed for cases, e.g., where there is a
6865 // paragraph break. See #11629.
6866 end = it.lastpos() + 1;
6867 if (beg != end && it.paragraph().isChanged(beg, end)) {
6871 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6881 case LFUN_OUTLINE_UP:
6882 case LFUN_OUTLINE_DOWN:
6883 case LFUN_OUTLINE_IN:
6884 case LFUN_OUTLINE_OUT:
6885 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6888 case LFUN_NEWLINE_INSERT:
6889 // LaTeX restrictions (labels or empty par)
6890 enable = !cur.paragraph().isPassThru()
6891 && cur.pos() > cur.paragraph().beginOfBody();
6894 case LFUN_SEPARATOR_INSERT:
6895 // Always enabled for now
6899 case LFUN_TAB_INSERT:
6900 case LFUN_TAB_DELETE:
6901 enable = cur.paragraph().isPassThru();
6904 case LFUN_GRAPHICS_SET_GROUP: {
6905 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6909 status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6913 case LFUN_NEWPAGE_INSERT:
6914 // not allowed in description items
6915 code = NEWPAGE_CODE;
6916 enable = !inDescriptionItem(cur);
6920 enable = !cur.paragraph().isPassThru();
6921 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6924 case LFUN_PARAGRAPH_BREAK:
6925 enable = inset().allowMultiPar();
6928 case LFUN_SPELLING_ADD:
6929 case LFUN_SPELLING_ADD_LOCAL:
6930 case LFUN_SPELLING_REMOVE_LOCAL:
6931 case LFUN_SPELLING_IGNORE:
6932 case LFUN_SPELLING_REMOVE:
6933 enable = theSpellChecker() != nullptr;
6934 if (enable && !cmd.getArg(1).empty()) {
6935 // validate explicitly given language
6936 Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6937 enable &= lang != nullptr;
6942 case LFUN_LAYOUT_TOGGLE: {
6943 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
6944 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
6945 docstring const layout = resolveLayout(req_layout, cur);
6947 // FIXME: make this work in multicell selection case
6948 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
6949 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
6950 && isAlreadyLayout(layout, cur));
6954 case LFUN_ENVIRONMENT_SPLIT: {
6955 if (cmd.argument() == "outer") {
6956 // check if we have an environment in our nesting hierarchy
6958 depth_type const current_depth = cur.paragraph().params().depth();
6959 pit_type pit = cur.pit();
6960 Paragraph cpar = pars_[pit];
6962 if (pit == 0 || cpar.params().depth() == 0)
6966 if (cpar.params().depth() < current_depth)
6967 res = cpar.layout().isEnvironment();
6972 else if (cmd.argument() == "previous") {
6973 // look if we have an environment in the previous par
6974 pit_type pit = cur.pit();
6975 Paragraph cpar = pars_[pit];
6979 enable = cpar.layout().isEnvironment();
6985 else if (cur.paragraph().layout().isEnvironment()) {
6986 enable = cmd.argument() == "before"
6987 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
6994 case LFUN_LAYOUT_PARAGRAPH:
6995 case LFUN_PARAGRAPH_PARAMS:
6996 case LFUN_PARAGRAPH_PARAMS_APPLY:
6997 case LFUN_PARAGRAPH_UPDATE:
6998 enable = owner_->allowParagraphCustomization();
7001 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
7002 // Because they insert COMBINING DIACRITICAL Unicode characters,
7003 // that cannot be handled by LaTeX but must be converted according
7004 // to the definition in lib/unicodesymbols?
7005 case LFUN_ACCENT_ACUTE:
7006 case LFUN_ACCENT_BREVE:
7007 case LFUN_ACCENT_CARON:
7008 case LFUN_ACCENT_CEDILLA:
7009 case LFUN_ACCENT_CIRCLE:
7010 case LFUN_ACCENT_CIRCUMFLEX:
7011 case LFUN_ACCENT_DOT:
7012 case LFUN_ACCENT_GRAVE:
7013 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7014 case LFUN_ACCENT_MACRON:
7015 case LFUN_ACCENT_OGONEK:
7016 case LFUN_ACCENT_TIE:
7017 case LFUN_ACCENT_TILDE:
7018 case LFUN_ACCENT_PERISPOMENI:
7019 case LFUN_ACCENT_UMLAUT:
7020 case LFUN_ACCENT_UNDERBAR:
7021 case LFUN_ACCENT_UNDERDOT:
7022 case LFUN_FONT_FRAK:
7023 case LFUN_FONT_SIZE:
7024 case LFUN_FONT_STATE:
7025 case LFUN_FONT_UNDERLINE:
7026 case LFUN_FONT_STRIKEOUT:
7027 case LFUN_FONT_CROSSOUT:
7028 case LFUN_FONT_UNDERUNDERLINE:
7029 case LFUN_FONT_UNDERWAVE:
7030 case LFUN_FONT_NO_SPELLCHECK:
7031 case LFUN_TEXTSTYLE_UPDATE:
7032 enable = !cur.paragraph().isPassThru();
7035 case LFUN_FONT_DEFAULT: {
7036 Font font(inherit_font, ignore_language);
7037 BufferParams const & bp = cur.buffer()->masterParams();
7038 if (cur.selection()) {
7040 // Check if we have a non-default font attribute
7041 // in the selection range.
7042 DocIterator const from = cur.selectionBegin();
7043 DocIterator const to = cur.selectionEnd();
7044 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7045 if (!dit.inTexted()) {
7049 Paragraph const & par = dit.paragraph();
7050 pos_type const pos = dit.pos();
7051 Font tmp = par.getFontSettings(bp, pos);
7052 if (tmp.fontInfo() != font.fontInfo()
7053 || tmp.language() != bp.language) {
7061 // Disable if all is default already.
7062 enable = (cur.current_font.fontInfo() != font.fontInfo()
7063 || cur.current_font.language() != bp.language);
7067 case LFUN_TEXTSTYLE_APPLY:
7068 enable = !freeFonts.empty();
7071 case LFUN_WORD_DELETE_FORWARD:
7072 case LFUN_WORD_DELETE_BACKWARD:
7073 case LFUN_LINE_DELETE_FORWARD:
7074 case LFUN_WORD_FORWARD:
7075 case LFUN_WORD_BACKWARD:
7076 case LFUN_WORD_RIGHT:
7077 case LFUN_WORD_LEFT:
7078 case LFUN_CHAR_FORWARD:
7079 case LFUN_CHAR_FORWARD_SELECT:
7080 case LFUN_CHAR_BACKWARD:
7081 case LFUN_CHAR_BACKWARD_SELECT:
7082 case LFUN_CHAR_LEFT:
7083 case LFUN_CHAR_LEFT_SELECT:
7084 case LFUN_CHAR_RIGHT:
7085 case LFUN_CHAR_RIGHT_SELECT:
7087 case LFUN_UP_SELECT:
7089 case LFUN_DOWN_SELECT:
7090 case LFUN_PARAGRAPH_SELECT:
7091 case LFUN_PARAGRAPH_UP_SELECT:
7092 case LFUN_PARAGRAPH_DOWN_SELECT:
7093 case LFUN_LINE_BEGIN_SELECT:
7094 case LFUN_LINE_END_SELECT:
7095 case LFUN_WORD_FORWARD_SELECT:
7096 case LFUN_WORD_BACKWARD_SELECT:
7097 case LFUN_WORD_RIGHT_SELECT:
7098 case LFUN_WORD_LEFT_SELECT:
7099 case LFUN_WORD_SELECT:
7100 case LFUN_SECTION_SELECT:
7101 case LFUN_BUFFER_BEGIN:
7102 case LFUN_BUFFER_END:
7103 case LFUN_BUFFER_BEGIN_SELECT:
7104 case LFUN_BUFFER_END_SELECT:
7105 case LFUN_INSET_BEGIN:
7106 case LFUN_INSET_END:
7107 case LFUN_INSET_BEGIN_SELECT:
7108 case LFUN_INSET_END_SELECT:
7109 case LFUN_PARAGRAPH_UP:
7110 case LFUN_PARAGRAPH_DOWN:
7111 case LFUN_LINE_BEGIN:
7113 case LFUN_CHAR_DELETE_FORWARD:
7114 case LFUN_CHAR_DELETE_BACKWARD:
7115 case LFUN_WORD_UPCASE:
7116 case LFUN_WORD_LOWCASE:
7117 case LFUN_WORD_CAPITALIZE:
7118 case LFUN_CHARS_TRANSPOSE:
7119 case LFUN_SERVER_GET_XY:
7120 case LFUN_SERVER_SET_XY:
7121 case LFUN_SERVER_GET_LAYOUT:
7122 case LFUN_SELF_INSERT:
7123 case LFUN_UNICODE_INSERT:
7124 case LFUN_THESAURUS_ENTRY:
7126 case LFUN_SERVER_GET_STATISTICS:
7127 // these are handled in our dispatch()
7131 case LFUN_INSET_INSERT: {
7132 string const type = cmd.getArg(0);
7133 if (type == "toc") {
7135 // not allowed in description items
7136 //FIXME: couldn't this be merged in Inset::insetAllowed()?
7137 enable = !inDescriptionItem(cur);
7144 case LFUN_SEARCH_IGNORE: {
7145 bool const value = cmd.getArg(1) == "true";
7146 setIgnoreFormat(cmd.getArg(0), value);
7156 || !cur.inset().insetAllowed(code)
7157 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7160 status.setEnabled(enable);
7165 void Text::pasteString(Cursor & cur, docstring const & clip,
7168 if (!clip.empty()) {
7171 insertStringAsParagraphs(cur, clip, cur.current_font);
7173 insertStringAsLines(cur, clip, cur.current_font);
7178 // FIXME: an item inset would make things much easier.
7179 bool Text::inDescriptionItem(Cursor const & cur) const
7181 Paragraph const & par = cur.paragraph();
7182 pos_type const pos = cur.pos();
7183 pos_type const body_pos = par.beginOfBody();
7185 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7186 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7187 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7190 return (pos < body_pos
7192 && (pos == 0 || par.getChar(pos - 1) != ' ')));
7196 std::vector<docstring> Text::getFreeFonts() const
7198 vector<docstring> ffList;
7200 for (auto const & f : freeFonts)
7201 ffList.push_back(f.first);