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)
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 // Seek the one (on same level) below
3771 for (; finish != end; ++finish) {
3772 toclevel = text.getTocLevel(distance(bgn, finish));
3773 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3779 if (start == pars.begin())
3782 ParagraphList::iterator dest = start;
3783 // Move out (up) from this header
3786 // Search previous same-level header above
3789 toclevel = text.getTocLevel(distance(bgn, dest));
3791 && (toclevel == Layout::NOT_IN_TOC
3792 || toclevel > thistoclevel));
3793 // Not found; do nothing
3794 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3796 pit_type newpit = distance(bgn, dest);
3797 pit_type const len = distance(start, finish);
3798 pit_type const deletepit = pit + len;
3799 buf.undo().recordUndo(cur, newpit, deletepit - 1);
3800 // If we move an environment upwards, make sure it is
3801 // separated from its new neighbour below:
3802 // If an environment of the same layout follows, and the moved
3803 // paragraph sequence does not end with a separator, insert one.
3804 ParagraphList::iterator lastmoved = finish;
3806 if (start->layout().isEnvironment()
3807 && dest->layout() == start->layout()
3808 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3809 cur.pit() = distance(bgn, lastmoved);
3810 cur.pos() = cur.lastpos();
3811 insertSeparator(cur, current_depth);
3814 // Likewise, if we moved an environment upwards, make sure it
3815 // is separated from its new neighbour above.
3816 // The paragraph before the target of movement
3818 ParagraphList::iterator before = dest;
3820 // Get the parent paragraph (outer in nested context)
3821 pit_type const parent =
3822 before->params().depth() > current_depth
3823 ? text.depthHook(distance(bgn, before), current_depth)
3824 : distance(bgn, before);
3825 // If a environment with same layout preceeds the moved one in the new
3826 // position, and there is no separator yet, insert one.
3827 if (start->layout().isEnvironment()
3828 && pars[parent].layout() == start->layout()
3829 && !before->isEnvSeparator(before->beginOfBody())) {
3830 cur.pit() = distance(bgn, before);
3831 cur.pos() = cur.lastpos();
3832 insertSeparator(cur, current_depth);
3836 newpit = distance(bgn, dest);
3837 pars.splice(dest, start, finish);
3845 // Go one down from *this* header:
3846 ParagraphList::iterator dest = next(finish, 1);
3847 // Go further down to find header to insert in front of:
3848 for (; dest != end; ++dest) {
3849 toclevel = text.getTocLevel(distance(bgn, dest));
3850 if (toclevel != Layout::NOT_IN_TOC
3851 && toclevel <= thistoclevel)
3854 // One such was found, so go on...
3855 // If we move an environment downwards, make sure it is
3856 // separated from its new neighbour above.
3857 pit_type newpit = distance(bgn, dest);
3858 buf.undo().recordUndo(cur, pit, newpit - 1);
3859 // The paragraph before the target of movement
3860 ParagraphList::iterator before = dest;
3862 // Get the parent paragraph (outer in nested context)
3863 pit_type const parent =
3864 before->params().depth() > current_depth
3865 ? text.depthHook(distance(bgn, before), current_depth)
3866 : distance(bgn, before);
3867 // If a environment with same layout preceeds the moved one in the new
3868 // position, and there is no separator yet, insert one.
3869 if (start->layout().isEnvironment()
3870 && pars[parent].layout() == start->layout()
3871 && !before->isEnvSeparator(before->beginOfBody())) {
3872 cur.pit() = distance(bgn, before);
3873 cur.pos() = cur.lastpos();
3874 insertSeparator(cur, current_depth);
3877 // Likewise, make sure moved environments are separated
3878 // from their new neighbour below:
3879 // If an environment of the same layout follows, and the moved
3880 // paragraph sequence does not end with a separator, insert one.
3881 ParagraphList::iterator lastmoved = finish;
3884 && start->layout().isEnvironment()
3885 && dest->layout() == start->layout()
3886 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3887 cur.pit() = distance(bgn, lastmoved);
3888 cur.pos() = cur.lastpos();
3889 insertSeparator(cur, current_depth);
3892 newpit = distance(bgn, dest);
3893 pit_type const len = distance(start, finish);
3894 pars.splice(dest, start, finish);
3895 cur.pit() = newpit - len;
3900 // We first iterate without actually doing something
3901 // in order to check whether the action flattens the structure.
3902 // If so, warn (#11178).
3903 ParagraphList::iterator cstart = start;
3904 bool strucchange = false;
3905 for (; cstart != finish; ++cstart) {
3906 toclevel = text.getTocLevel(distance(bgn, cstart));
3907 if (toclevel == Layout::NOT_IN_TOC)
3910 DocumentClass const & tc = buf.params().documentClass();
3911 int const newtoclevel =
3912 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3915 for (auto const & lay : tc) {
3916 if (lay.toclevel == newtoclevel
3917 && lay.isNumHeadingLabelType()
3918 && cstart->layout().isNumHeadingLabelType()) {
3929 && frontend::Alert::prompt(_("Action flattens document structure"),
3930 _("This action will cause some headings that have been "
3931 "on different level before to be on the same level "
3932 "since there is no more lower or higher heading level. "
3935 _("&Yes, continue nonetheless"),
3936 _("&No, quit operation")) == 1)
3939 pit_type const len = distance(start, finish);
3940 buf.undo().recordUndo(cur, pit, pit + len - 1);
3941 for (; start != finish; ++start) {
3942 toclevel = text.getTocLevel(distance(bgn, start));
3943 if (toclevel == Layout::NOT_IN_TOC)
3946 DocumentClass const & tc = buf.params().documentClass();
3947 int const newtoclevel =
3948 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3950 for (auto const & lay : tc) {
3951 if (lay.toclevel == newtoclevel
3952 && lay.isNumHeadingLabelType()
3953 && start->layout().isNumHeadingLabelType()) {
3954 start->setLayout(lay);
3968 void Text::number(Cursor & cur)
3970 FontInfo font = ignore_font;
3971 font.setNumber(FONT_TOGGLE);
3972 toggleAndShow(cur, this, Font(font, ignore_language));
3976 bool Text::isRTL(pit_type const pit) const
3978 Buffer const & buffer = owner_->buffer();
3979 return pars_[pit].isRTL(buffer.params());
3985 Language const * getLanguage(Cursor const & cur, string const & lang)
3987 return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
3991 docstring resolveLayout(docstring layout, DocIterator const & dit)
3993 Paragraph const & par = dit.paragraph();
3994 DocumentClass const & tclass = dit.buffer()->params().documentClass();
3997 layout = tclass.defaultLayoutName();
3999 if (dit.inset().forcePlainLayout(dit.idx()))
4000 // in this case only the empty layout is allowed
4001 layout = tclass.plainLayoutName();
4002 else if (par.usePlainLayout()) {
4003 // in this case, default layout maps to empty layout
4004 if (layout == tclass.defaultLayoutName())
4005 layout = tclass.plainLayoutName();
4007 // otherwise, the empty layout maps to the default
4008 if (layout == tclass.plainLayoutName())
4009 layout = tclass.defaultLayoutName();
4012 // If the entry is obsolete, use the new one instead.
4013 if (tclass.hasLayout(layout)) {
4014 docstring const & obs = tclass[layout].obsoleted_by();
4018 if (!tclass.hasLayout(layout))
4024 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4026 ParagraphList const & pars = cur.text()->paragraphs();
4028 pit_type pit = cur.selBegin().pit();
4029 pit_type const epit = cur.selEnd().pit() + 1;
4030 for ( ; pit != epit; ++pit)
4031 if (pars[pit].layout().name() != layout)
4041 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4043 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4045 // Dispatch if the cursor is inside the text. It is not the
4046 // case for context menus (bug 5797).
4047 if (cur.text() != this) {
4052 BufferView * bv = &cur.bv();
4053 TextMetrics * tm = &bv->textMetrics(this);
4054 if (!tm->contains(cur.pit())) {
4055 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4056 tm = &bv->textMetrics(this);
4059 // FIXME: We use the update flag to indicates wether a singlePar or a
4060 // full screen update is needed. We reset it here but shall we restore it
4062 cur.noScreenUpdate();
4064 LBUFERR(this == cur.text());
4066 // NOTE: This should NOT be a reference. See commit 94a5481a.
4067 CursorSlice const oldTopSlice = cur.top();
4068 bool const oldBoundary = cur.boundary();
4069 bool const oldSelection = cur.selection();
4070 // Signals that, even if needsUpdate == false, an update of the
4071 // cursor paragraph is required
4072 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4073 LyXAction::SingleParUpdate);
4074 // Signals that a full-screen update is required
4075 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4076 LyXAction::NoUpdate) || singleParUpdate);
4077 bool const last_misspelled = lyxrc.spellcheck_continuously
4078 && cur.paragraph().isMisspelled(cur.pos(), true);
4080 FuncCode const act = cmd.action();
4083 case LFUN_PARAGRAPH_MOVE_DOWN: {
4084 pit_type const pit = cur.pit();
4085 cur.recordUndo(pit, pit + 1);
4086 pars_.swap(pit, pit + 1);
4088 cur.forceBufferUpdate();
4093 case LFUN_PARAGRAPH_MOVE_UP: {
4094 pit_type const pit = cur.pit();
4095 cur.recordUndo(pit - 1, pit);
4097 pars_.swap(pit, pit - 1);
4100 cur.forceBufferUpdate();
4104 case LFUN_APPENDIX: {
4105 Paragraph & par = cur.paragraph();
4106 bool start = !par.params().startOfAppendix();
4108 // FIXME: The code below only makes sense at top level.
4109 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4110 // ensure that we have only one start_of_appendix in this document
4111 // FIXME: this don't work for multipart document!
4112 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4113 if (pars_[tmp].params().startOfAppendix()) {
4114 cur.recordUndo(tmp, tmp);
4115 pars_[tmp].params().startOfAppendix(false);
4121 par.params().startOfAppendix(start);
4123 // we can set the refreshing parameters now
4124 cur.forceBufferUpdate();
4128 case LFUN_WORD_DELETE_FORWARD:
4129 if (cur.selection())
4130 cutSelection(cur, false);
4132 deleteWordForward(cur, cmd.getArg(0) != "confirm");
4133 finishChange(cur, false);
4136 case LFUN_WORD_DELETE_BACKWARD:
4137 if (cur.selection())
4138 cutSelection(cur, false);
4140 deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4141 finishChange(cur, false);
4144 case LFUN_LINE_DELETE_FORWARD:
4145 if (cur.selection())
4146 cutSelection(cur, false);
4148 tm->deleteLineForward(cur);
4149 finishChange(cur, false);
4152 case LFUN_BUFFER_BEGIN:
4153 case LFUN_BUFFER_BEGIN_SELECT:
4154 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4155 if (cur.depth() == 1)
4156 needsUpdate |= cursorTop(cur);
4159 cur.screenUpdateFlags(Update::FitCursor);
4162 case LFUN_BUFFER_END:
4163 case LFUN_BUFFER_END_SELECT:
4164 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4165 if (cur.depth() == 1)
4166 needsUpdate |= cursorBottom(cur);
4169 cur.screenUpdateFlags(Update::FitCursor);
4172 case LFUN_INSET_BEGIN:
4173 case LFUN_INSET_BEGIN_SELECT:
4174 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4175 if (cur.depth() == 1 || !cur.top().at_begin())
4176 needsUpdate |= cursorTop(cur);
4179 cur.screenUpdateFlags(Update::FitCursor);
4182 case LFUN_INSET_END:
4183 case LFUN_INSET_END_SELECT:
4184 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4185 if (cur.depth() == 1 || !cur.top().at_end())
4186 needsUpdate |= cursorBottom(cur);
4189 cur.screenUpdateFlags(Update::FitCursor);
4192 case LFUN_CHAR_FORWARD:
4193 case LFUN_CHAR_FORWARD_SELECT: {
4194 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4195 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4196 bool const cur_moved = cursorForward(cur);
4197 needsUpdate |= cur_moved;
4199 if (!cur_moved && cur.depth() > 1
4200 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4202 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4204 // we will be moving out the inset, so we should execute
4205 // the depm-mechanism.
4206 // The cursor hasn't changed yet. To give the DEPM the
4207 // possibility of doing something we must provide it with
4208 // two different cursors.
4210 dummy.pos() = dummy.pit() = 0;
4211 if (cur.bv().checkDepm(dummy, cur))
4212 cur.forceBufferUpdate();
4217 case LFUN_CHAR_BACKWARD:
4218 case LFUN_CHAR_BACKWARD_SELECT: {
4219 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4220 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4221 bool const cur_moved = cursorBackward(cur);
4222 needsUpdate |= cur_moved;
4224 if (!cur_moved && cur.depth() > 1
4225 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4227 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4229 // we will be moving out the inset, so we should execute
4230 // the depm-mechanism.
4231 // The cursor hasn't changed yet. To give the DEPM the
4232 // possibility of doing something we must provide it with
4233 // two different cursors.
4235 dummy.pos() = cur.lastpos();
4236 dummy.pit() = cur.lastpit();
4237 if (cur.bv().checkDepm(dummy, cur))
4238 cur.forceBufferUpdate();
4243 case LFUN_CHAR_LEFT:
4244 case LFUN_CHAR_LEFT_SELECT:
4245 if (lyxrc.visual_cursor) {
4246 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4247 bool const cur_moved = cursorVisLeft(cur);
4248 needsUpdate |= cur_moved;
4249 if (!cur_moved && cur.depth() > 1
4250 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4252 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4255 if (cur.reverseDirectionNeeded()) {
4256 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4257 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4259 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4260 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4267 case LFUN_CHAR_RIGHT:
4268 case LFUN_CHAR_RIGHT_SELECT:
4269 if (lyxrc.visual_cursor) {
4270 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4271 bool const cur_moved = cursorVisRight(cur);
4272 needsUpdate |= cur_moved;
4273 if (!cur_moved && cur.depth() > 1
4274 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4276 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4279 if (cur.reverseDirectionNeeded()) {
4280 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4281 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4283 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4284 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4292 case LFUN_UP_SELECT:
4293 case LFUN_DOWN_SELECT:
4296 // stop/start the selection
4297 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4298 cmd.action() == LFUN_UP_SELECT;
4300 // move cursor up/down
4301 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4302 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4304 if (!atFirstOrLastRow) {
4305 needsUpdate |= cur.selHandle(select);
4306 cur.upDownInText(up, needsUpdate);
4307 needsUpdate |= cur.beforeDispatchCursor().inMathed();
4309 pos_type newpos = up ? 0 : cur.lastpos();
4310 if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4311 needsUpdate |= cur.selHandle(select);
4312 // we do not reset the targetx of the cursor
4314 needsUpdate |= bv->checkDepm(cur, bv->cursor());
4315 cur.updateTextTargetOffset();
4317 cur.forceBufferUpdate();
4321 // if the cursor cannot be moved up or down do not remove
4322 // the selection right now, but wait for the next dispatch.
4324 needsUpdate |= cur.selHandle(select);
4325 cur.upDownInText(up, needsUpdate);
4332 case LFUN_PARAGRAPH_SELECT:
4334 needsUpdate |= setCursor(cur, cur.pit(), 0);
4335 needsUpdate |= cur.selHandle(true);
4336 if (cur.pos() < cur.lastpos())
4337 needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4340 case LFUN_PARAGRAPH_UP:
4341 case LFUN_PARAGRAPH_UP_SELECT:
4342 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4343 needsUpdate |= cursorUpParagraph(cur);
4346 case LFUN_PARAGRAPH_DOWN:
4347 case LFUN_PARAGRAPH_DOWN_SELECT:
4348 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4349 needsUpdate |= cursorDownParagraph(cur);
4352 case LFUN_LINE_BEGIN:
4353 case LFUN_LINE_BEGIN_SELECT:
4354 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4355 needsUpdate |= tm->cursorHome(cur);
4359 case LFUN_LINE_END_SELECT:
4360 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4361 needsUpdate |= tm->cursorEnd(cur);
4364 case LFUN_SECTION_SELECT: {
4365 Buffer const & buf = *cur.buffer();
4366 pit_type const pit = cur.pit();
4367 ParagraphList & pars = buf.text().paragraphs();
4368 ParagraphList::iterator bgn = pars.begin();
4369 // The first paragraph of the area to be selected:
4370 ParagraphList::iterator start = pars.iterator_at(pit);
4371 // The final paragraph of area to be selected:
4372 ParagraphList::iterator finish = start;
4373 ParagraphList::iterator end = pars.end();
4375 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4376 if (thistoclevel == Layout::NOT_IN_TOC)
4380 Cursor const old_cur = cur;
4381 needsUpdate |= cur.selHandle(true);
4383 // Move out (down) from this section header
4387 // Seek the one (on same level) below
4388 for (; finish != end; ++finish, ++cur.pit()) {
4389 int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4390 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4393 cur.pos() = cur.lastpos();
4394 cur.boundary(false);
4395 cur.setCurrentFont();
4397 needsUpdate |= cur != old_cur;
4401 case LFUN_WORD_RIGHT:
4402 case LFUN_WORD_RIGHT_SELECT:
4403 if (lyxrc.visual_cursor) {
4404 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4405 bool const cur_moved = cursorVisRightOneWord(cur);
4406 needsUpdate |= cur_moved;
4407 if (!cur_moved && cur.depth() > 1
4408 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4410 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4413 if (cur.reverseDirectionNeeded()) {
4414 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4415 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4417 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4418 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4425 case LFUN_WORD_FORWARD:
4426 case LFUN_WORD_FORWARD_SELECT: {
4427 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4428 bool const cur_moved = cursorForwardOneWord(cur);
4429 needsUpdate |= cur_moved;
4431 if (!cur_moved && cur.depth() > 1
4432 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4434 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4436 // we will be moving out the inset, so we should execute
4437 // the depm-mechanism.
4438 // The cursor hasn't changed yet. To give the DEPM the
4439 // possibility of doing something we must provide it with
4440 // two different cursors.
4442 dummy.pos() = dummy.pit() = 0;
4443 if (cur.bv().checkDepm(dummy, cur))
4444 cur.forceBufferUpdate();
4449 case LFUN_WORD_LEFT:
4450 case LFUN_WORD_LEFT_SELECT:
4451 if (lyxrc.visual_cursor) {
4452 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4453 bool const cur_moved = cursorVisLeftOneWord(cur);
4454 needsUpdate |= cur_moved;
4455 if (!cur_moved && cur.depth() > 1
4456 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4458 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4461 if (cur.reverseDirectionNeeded()) {
4462 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4463 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4465 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4466 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4473 case LFUN_WORD_BACKWARD:
4474 case LFUN_WORD_BACKWARD_SELECT: {
4475 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4476 bool const cur_moved = cursorBackwardOneWord(cur);
4477 needsUpdate |= cur_moved;
4479 if (!cur_moved && cur.depth() > 1
4480 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4482 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4484 // we will be moving out the inset, so we should execute
4485 // the depm-mechanism.
4486 // The cursor hasn't changed yet. To give the DEPM the
4487 // possibility of doing something we must provide it with
4488 // two different cursors.
4490 dummy.pos() = cur.lastpos();
4491 dummy.pit() = cur.lastpit();
4492 if (cur.bv().checkDepm(dummy, cur))
4493 cur.forceBufferUpdate();
4498 case LFUN_WORD_SELECT: {
4499 selectWord(cur, WHOLE_WORD);
4500 finishChange(cur, true);
4504 case LFUN_NEWLINE_INSERT: {
4505 InsetNewlineParams inp;
4506 docstring const & arg = cmd.argument();
4507 if (arg == "linebreak")
4508 inp.kind = InsetNewlineParams::LINEBREAK;
4510 inp.kind = InsetNewlineParams::NEWLINE;
4511 cap::replaceSelection(cur);
4513 cur.insert(new InsetNewline(inp));
4515 moveCursor(cur, false);
4519 case LFUN_TAB_INSERT: {
4520 bool const multi_par_selection = cur.selection() &&
4521 cur.selBegin().pit() != cur.selEnd().pit();
4522 if (multi_par_selection) {
4523 // If there is a multi-paragraph selection, a tab is inserted
4524 // at the beginning of each paragraph.
4525 cur.recordUndoSelection();
4526 pit_type const pit_end = cur.selEnd().pit();
4527 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4528 pars_[pit].insertChar(0, '\t',
4529 bv->buffer().params().track_changes);
4530 // Update the selection pos to make sure the selection does not
4531 // change as the inserted tab will increase the logical pos.
4532 if (cur.realAnchor().pit() == pit)
4533 cur.realAnchor().forwardPos();
4534 if (cur.pit() == pit)
4539 // Maybe we shouldn't allow tabs within a line, because they
4540 // are not (yet) aligned as one might do expect.
4541 FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4542 dispatch(cur, ncmd);
4547 case LFUN_TAB_DELETE: {
4548 bool const tc = bv->buffer().params().track_changes;
4549 if (cur.selection()) {
4550 // If there is a selection, a tab (if present) is removed from
4551 // the beginning of each paragraph.
4552 cur.recordUndoSelection();
4553 pit_type const pit_end = cur.selEnd().pit();
4554 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4555 Paragraph & par = paragraphs()[pit];
4558 char_type const c = par.getChar(0);
4559 if (c == '\t' || c == ' ') {
4560 // remove either 1 tab or 4 spaces.
4561 int const n = (c == ' ' ? 4 : 1);
4562 for (int i = 0; i < n
4563 && !par.empty() && par.getChar(0) == c; ++i) {
4564 if (cur.pit() == pit)
4566 if (cur.realAnchor().pit() == pit
4567 && cur.realAnchor().pos() > 0 )
4568 cur.realAnchor().backwardPos();
4569 par.eraseChar(0, tc);
4575 // If there is no selection, try to remove a tab or some spaces
4576 // before the position of the cursor.
4577 Paragraph & par = paragraphs()[cur.pit()];
4578 pos_type const pos = cur.pos();
4583 char_type const c = par.getChar(pos - 1);
4587 par.eraseChar(cur.pos(), tc);
4589 for (int n_spaces = 0;
4591 && par.getChar(cur.pos() - 1) == ' '
4595 par.eraseChar(cur.pos(), tc);
4602 case LFUN_CHAR_DELETE_FORWARD:
4603 if (!cur.selection()) {
4604 if (cur.pos() == cur.paragraph().size())
4605 // Par boundary, force full-screen update
4606 singleParUpdate = false;
4607 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4609 cur.selection(true);
4614 needsUpdate |= erase(cur);
4617 cutSelection(cur, false);
4618 cur.setCurrentFont();
4619 singleParUpdate = false;
4621 moveCursor(cur, false);
4624 case LFUN_CHAR_DELETE_BACKWARD:
4625 if (!cur.selection()) {
4626 if (bv->getIntl().getTransManager().backspace()) {
4627 bool par_boundary = cur.pos() == 0;
4628 bool first_par = cur.pit() == 0;
4629 // Par boundary, full-screen update
4631 singleParUpdate = false;
4632 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4634 cur.selection(true);
4639 needsUpdate |= backspace(cur);
4641 if (par_boundary && !first_par && cur.pos() > 0
4642 && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4643 needsUpdate |= backspace(cur);
4648 DocIterator const dit = cur.selectionBegin();
4649 cutSelection(cur, false);
4650 if (cur.buffer()->params().track_changes)
4651 // since we're doing backwards deletion,
4652 // and the selection is not really cut,
4653 // move cursor before selection (#11630)
4655 cur.setCurrentFont();
4656 singleParUpdate = false;
4660 case LFUN_PARAGRAPH_BREAK: {
4661 cap::replaceSelection(cur);
4662 pit_type pit = cur.pit();
4663 Paragraph const & par = pars_[pit];
4664 bool lastpar = (pit == pit_type(pars_.size() - 1));
4665 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4666 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4667 if (prev < pit && cur.pos() == par.beginOfBody()
4668 && par.empty() && !par.isEnvSeparator(cur.pos())
4669 && !par.layout().keepempty
4670 && !par.layout().isCommand()
4671 && pars_[prev].layout() != par.layout()
4672 && pars_[prev].layout().isEnvironment()
4673 && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4674 if (par.layout().isEnvironment()
4675 && pars_[prev].getDepth() == par.getDepth()) {
4676 docstring const layout = par.layout().name();
4677 DocumentClass const & tc = bv->buffer().params().documentClass();
4678 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4679 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4680 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4681 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4683 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4684 breakParagraph(cur);
4686 Font const f(inherit_font, cur.current_font.language());
4687 pars_[cur.pit() - 1].resetFonts(f);
4689 if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4691 breakParagraph(cur, cmd.getArg(0) == "inverse");
4694 // If we have a list and autoinsert item insets,
4696 Layout::LaTeXArgMap args = par.layout().args();
4697 for (auto const & thearg : args) {
4698 Layout::latexarg arg = thearg.second;
4699 if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4700 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4701 lyx::dispatch(cmd2);
4707 case LFUN_INSET_INSERT: {
4710 // We have to avoid triggering InstantPreview loading
4711 // before inserting into the document. See bug #5626.
4712 bool loaded = bv->buffer().isFullyLoaded();
4713 bv->buffer().setFullyLoaded(false);
4714 Inset * inset = createInset(&bv->buffer(), cmd);
4715 bv->buffer().setFullyLoaded(loaded);
4718 // FIXME (Abdel 01/02/2006):
4719 // What follows would be a partial fix for bug 2154:
4720 // http://www.lyx.org/trac/ticket/2154
4721 // This automatically put the label inset _after_ a
4722 // numbered section. It should be possible to extend the mechanism
4723 // to any kind of LateX environement.
4724 // The correct way to fix that bug would be at LateX generation.
4725 // I'll let the code here for reference as it could be used for some
4726 // other feature like "automatic labelling".
4728 Paragraph & par = pars_[cur.pit()];
4729 if (inset->lyxCode() == LABEL_CODE
4730 && !par.layout().counter.empty()) {
4731 // Go to the end of the paragraph
4732 // Warning: Because of Change-Tracking, the last
4733 // position is 'size()' and not 'size()-1':
4734 cur.pos() = par.size();
4735 // Insert a new paragraph
4736 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4740 if (cur.selection())
4741 cutSelection(cur, false);
4743 cur.forceBufferUpdate();
4744 if (inset->editable() && inset->asInsetText())
4745 inset->edit(cur, true);
4749 // trigger InstantPreview now
4750 if (inset->lyxCode() == EXTERNAL_CODE) {
4751 InsetExternal & ins =
4752 static_cast<InsetExternal &>(*inset);
4753 ins.updatePreview();
4760 case LFUN_INSET_DISSOLVE: {
4761 if (dissolveInset(cur)) {
4763 cur.forceBufferUpdate();
4768 case LFUN_INSET_SPLIT: {
4769 if (splitInset(cur)) {
4771 cur.forceBufferUpdate();
4776 case LFUN_GRAPHICS_SET_GROUP: {
4777 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4783 string id = to_utf8(cmd.argument());
4784 string grp = graphics::getGroupParams(bv->buffer(), id);
4785 InsetGraphicsParams tmp, inspar = ins->getParams();
4788 inspar.groupId = to_utf8(cmd.argument());
4790 InsetGraphics::string2params(grp, bv->buffer(), tmp);
4791 tmp.filename = inspar.filename;
4795 ins->setParams(inspar);
4799 case LFUN_SPACE_INSERT:
4800 if (cur.paragraph().layout().free_spacing)
4801 insertChar(cur, ' ');
4803 doInsertInset(cur, this, cmd, false, false);
4806 moveCursor(cur, false);
4809 case LFUN_SPECIALCHAR_INSERT: {
4810 string const name = to_utf8(cmd.argument());
4811 if (name == "hyphenation")
4812 specialChar(cur, InsetSpecialChar::HYPHENATION);
4813 else if (name == "allowbreak")
4814 specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4815 else if (name == "ligature-break")
4816 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4817 else if (name == "slash")
4818 specialChar(cur, InsetSpecialChar::SLASH);
4819 else if (name == "nobreakdash")
4820 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4821 else if (name == "dots")
4822 specialChar(cur, InsetSpecialChar::LDOTS);
4823 else if (name == "end-of-sentence")
4824 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4825 else if (name == "menu-separator")
4826 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4827 else if (name == "lyx")
4828 specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4829 else if (name == "tex")
4830 specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4831 else if (name == "latex")
4832 specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4833 else if (name == "latex2e")
4834 specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4835 else if (name.empty())
4836 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4838 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4842 case LFUN_IPAMACRO_INSERT: {
4843 string const arg = cmd.getArg(0);
4844 if (arg == "deco") {
4845 // Open the inset, and move the current selection
4847 doInsertInset(cur, this, cmd, true, true);
4849 // Some insets are numbered, others are shown in the outline pane so
4850 // let's update the labels and the toc backend.
4851 cur.forceBufferUpdate();
4854 if (arg == "tone-falling")
4855 ipaChar(cur, InsetIPAChar::TONE_FALLING);
4856 else if (arg == "tone-rising")
4857 ipaChar(cur, InsetIPAChar::TONE_RISING);
4858 else if (arg == "tone-high-rising")
4859 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4860 else if (arg == "tone-low-rising")
4861 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4862 else if (arg == "tone-high-rising-falling")
4863 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4864 else if (arg.empty())
4865 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4867 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4871 case LFUN_WORD_UPCASE:
4872 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4875 case LFUN_WORD_LOWCASE:
4876 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4879 case LFUN_WORD_CAPITALIZE:
4880 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4883 case LFUN_CHARS_TRANSPOSE:
4884 charsTranspose(cur);
4888 cur.message(_("Paste"));
4889 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4890 cap::replaceSelection(cur);
4892 // without argument?
4893 string const arg = to_utf8(cmd.argument());
4895 bool tryGraphics = true;
4896 if (theClipboard().isInternal())
4897 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4898 else if (theClipboard().hasTextContents()) {
4899 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"),
4900 !cur.paragraph().parbreakIsNewline(),
4901 Clipboard::AnyTextType))
4902 tryGraphics = false;
4904 if (tryGraphics && theClipboard().hasGraphicsContents())
4905 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4906 } else if (isStrUnsignedInt(arg)) {
4907 // we have a numerical argument
4908 pasteFromStack(cur, bv->buffer().errorList("Paste"),
4909 convert<unsigned int>(arg));
4910 } else if (arg == "html" || arg == "latex") {
4911 Clipboard::TextType type = (arg == "html") ?
4912 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4913 pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4915 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4917 type = Clipboard::PdfGraphicsType;
4918 else if (arg == "png")
4919 type = Clipboard::PngGraphicsType;
4920 else if (arg == "jpeg")
4921 type = Clipboard::JpegGraphicsType;
4922 else if (arg == "linkback")
4923 type = Clipboard::LinkBackGraphicsType;
4924 else if (arg == "emf")
4925 type = Clipboard::EmfGraphicsType;
4926 else if (arg == "wmf")
4927 type = Clipboard::WmfGraphicsType;
4929 // we also check in getStatus()
4930 LYXERR0("Unrecognized graphics type: " << arg);
4932 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4935 bv->buffer().errors("Paste");
4936 bv->buffer().updatePreviews(); // bug 11619
4937 cur.clearSelection(); // bug 393
4943 cutSelection(cur, true);
4944 cur.message(_("Cut"));
4947 case LFUN_SERVER_GET_XY:
4948 cur.message(from_utf8(
4949 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4950 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4953 case LFUN_SERVER_SET_XY: {
4956 istringstream is(to_utf8(cmd.argument()));
4959 lyxerr << "SETXY: Could not parse coordinates in '"
4960 << to_utf8(cmd.argument()) << endl;
4962 tm->setCursorFromCoordinates(cur, x, y);
4966 case LFUN_SERVER_GET_LAYOUT:
4967 cur.message(cur.paragraph().layout().name());
4971 case LFUN_LAYOUT_TOGGLE: {
4972 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
4973 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
4974 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
4976 docstring layout = resolveLayout(req_layout, cur);
4977 if (layout.empty()) {
4978 cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
4979 from_utf8(N_(" not known")));
4983 docstring const old_layout = cur.paragraph().layout().name();
4984 bool change_layout = !isAlreadyLayout(layout, cur);
4986 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
4987 change_layout = true;
4988 layout = resolveLayout(docstring(), cur);
4991 if (change_layout) {
4992 setLayout(cur, layout);
4993 if (cur.pit() > 0 && !ignoreautonests) {
4994 pit_type prev_pit = cur.pit() - 1;
4995 depth_type const cur_depth = pars_[cur.pit()].getDepth();
4996 // Scan for the previous par on same nesting level
4997 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
4999 set<docstring> const & autonests =
5000 pars_[prev_pit].layout().autonests();
5001 set<docstring> const & autonested =
5002 pars_[cur.pit()].layout().isAutonestedBy();
5003 if (autonests.find(layout) != autonests.end()
5004 || autonested.find(old_layout) != autonested.end())
5005 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5009 DocumentClass const & tclass = bv->buffer().params().documentClass();
5010 bool inautoarg = false;
5011 for (auto const & la_pair : tclass[layout].args()) {
5012 Layout::latexarg const & arg = la_pair.second;
5013 if (arg.autoinsert) {
5014 // If we had already inserted an arg automatically,
5015 // leave this now in order to insert the next one.
5017 cur.leaveInset(cur.inset());
5020 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5021 lyx::dispatch(cmd2);
5029 case LFUN_ENVIRONMENT_SPLIT: {
5030 bool const outer = cmd.argument() == "outer";
5031 bool const previous = cmd.argument() == "previous";
5032 bool const before = cmd.argument() == "before";
5033 bool const normal = cmd.argument().empty();
5034 Paragraph const & para = cur.paragraph();
5036 if (para.layout().isEnvironment())
5037 layout = para.layout().name();
5038 depth_type split_depth = cur.paragraph().params().depth();
5039 vector<depth_type> nextpars_depth;
5040 if (outer || previous) {
5041 // check if we have an environment in our scope
5042 pit_type pit = cur.pit();
5043 Paragraph cpar = pars_[pit];
5049 if (layout.empty() && previous
5050 && cpar.layout().isEnvironment()
5051 && cpar.params().depth() <= split_depth)
5052 layout = cpar.layout().name();
5053 if (cpar.params().depth() < split_depth
5054 && cpar.layout().isEnvironment()) {
5056 layout = cpar.layout().name();
5057 split_depth = cpar.params().depth();
5059 if (cpar.params().depth() == 0)
5063 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5064 // save nesting of following paragraphs if they are deeper
5066 pit_type offset = 1;
5067 depth_type cur_depth = pars_[cur.pit()].params().depth();
5068 while (cur.pit() + offset <= cur.lastpit()) {
5069 Paragraph cpar = pars_[cur.pit() + offset];
5070 depth_type nextpar_depth = cpar.params().depth();
5071 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5072 nextpars_depth.push_back(nextpar_depth);
5073 cur_depth = nextpar_depth;
5080 cur.top().setPitPos(cur.pit(), 0);
5081 if (before || cur.pos() > 0)
5082 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5083 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5084 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5086 while (cur.paragraph().params().depth() > split_depth)
5087 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5089 DocumentClass const & tc = bv->buffer().params().documentClass();
5090 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5091 + from_ascii("\" ignoreautonests")));
5092 // FIXME: Bibitem mess!
5093 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5094 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5095 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5098 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5099 while (cur.paragraph().params().depth() < split_depth)
5100 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5103 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5104 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5105 if ((outer || normal) && !nextpars_depth.empty()) {
5106 // restore nesting of following paragraphs
5107 DocIterator scur = cur;
5108 depth_type max_depth = cur.paragraph().params().depth() + 1;
5109 for (auto nextpar_depth : nextpars_depth) {
5111 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5112 depth_type const olddepth = cur.paragraph().params().depth();
5113 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5114 if (olddepth == cur.paragraph().params().depth())
5115 // leave loop if no incrementation happens
5118 max_depth = cur.paragraph().params().depth() + 1;
5120 cur.setCursor(scur);
5126 case LFUN_CLIPBOARD_PASTE:
5127 cap::replaceSelection(cur);
5128 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5129 cmd.argument() == "paragraph");
5130 bv->buffer().errors("Paste");
5133 case LFUN_CLIPBOARD_PASTE_SIMPLE:
5134 cap::replaceSelection(cur);
5135 pasteSimpleText(cur, cmd.argument() == "paragraph");
5138 case LFUN_PRIMARY_SELECTION_PASTE:
5139 cap::replaceSelection(cur);
5140 pasteString(cur, theSelection().get(),
5141 cmd.argument() == "paragraph");
5144 case LFUN_SELECTION_PASTE:
5145 // Copy the selection buffer to the clipboard stack,
5146 // because we want it to appear in the "Edit->Paste
5148 cap::replaceSelection(cur);
5149 cap::copySelectionToStack();
5150 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5151 bv->buffer().errors("Paste");
5154 case LFUN_QUOTE_INSERT: {
5155 cap::replaceSelection(cur);
5158 Paragraph const & par = cur.paragraph();
5159 pos_type pos = cur.pos();
5160 // Ignore deleted text before cursor
5161 while (pos > 0 && par.isDeleted(pos - 1))
5164 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5166 // Guess quote side.
5167 // A space triggers an opening quote. This is passed if the preceding
5168 // char/inset is a space or at paragraph start.
5170 if (pos > 0 && !par.isSpace(pos - 1)) {
5171 if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5172 // If an opening double quotation mark precedes, and this
5173 // is a single quote, make it opening as well
5175 static_cast<InsetQuotes &>(*cur.prevInset());
5176 string const type = ins.getType();
5177 if (!suffixIs(type, "ld") || !inner)
5178 c = par.getChar(pos - 1);
5180 else if (!cur.prevInset()
5181 || (cur.prevInset() && cur.prevInset()->isChar()))
5182 // If a char precedes, pass that and let InsetQuote decide
5183 c = par.getChar(pos - 1);
5186 if (par.getInset(pos - 1)
5187 && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5188 // skip "invisible" insets
5192 c = par.getChar(pos - 1);
5197 QuoteLevel const quote_level = inner
5198 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5199 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5200 cur.buffer()->updateBuffer();
5205 case LFUN_MOUSE_TRIPLE:
5206 if (cmd.button() == mouse_button::button1) {
5208 setCursor(cur, cur.pit(), 0);
5211 if (cur.pos() < cur.lastpos())
5212 setCursor(cur, cur.pit(), cur.lastpos());
5218 case LFUN_MOUSE_DOUBLE:
5219 if (cmd.button() == mouse_button::button1) {
5220 selectWord(cur, WHOLE_WORD);
5225 // Single-click on work area
5226 case LFUN_MOUSE_PRESS: {
5227 // We are not marking a selection with the keyboard in any case.
5228 Cursor & bvcur = cur.bv().cursor();
5229 bvcur.setMark(false);
5230 switch (cmd.button()) {
5231 case mouse_button::button1:
5232 if (!bvcur.selection())
5234 bvcur.resetAnchor();
5235 if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5236 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5237 // FIXME: move this to mouseSetCursor?
5238 if (bvcur.wordSelection() && bvcur.inTexted())
5239 expandWordSel(bvcur);
5242 case mouse_button::button2:
5243 if (lyxrc.mouse_middlebutton_paste) {
5244 // Middle mouse pasting.
5245 bv->mouseSetCursor(cur);
5247 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5248 "selection-paste ; primary-selection-paste paragraph"));
5250 cur.noScreenUpdate();
5253 case mouse_button::button3: {
5254 // Don't do anything if we right-click a
5255 // selection, a context menu will popup.
5256 if (bvcur.selection() && cur >= bvcur.selectionBegin()
5257 && cur <= bvcur.selectionEnd()) {
5258 cur.noScreenUpdate();
5261 if (!bv->mouseSetCursor(cur, false))
5262 cur.screenUpdateFlags(Update::FitCursor);
5268 } // switch (cmd.button())
5271 case LFUN_MOUSE_MOTION: {
5272 // Mouse motion with right or middle mouse do nothing for now.
5273 if (cmd.button() != mouse_button::button1) {
5274 cur.noScreenUpdate();
5277 // ignore motions deeper nested than the real anchor
5278 Cursor & bvcur = cur.bv().cursor();
5279 if (!bvcur.realAnchor().hasPart(cur)) {
5283 CursorSlice old = bvcur.top();
5285 int const wh = bv->workHeight();
5286 int const y = max(0, min(wh - 1, cmd.y()));
5288 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5289 cur.setTargetX(cmd.x());
5290 // Don't allow selecting a separator inset
5291 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5294 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5295 else if (cmd.y() < 0)
5296 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5297 // This is to allow jumping over large insets
5298 if (cur.top() == old) {
5300 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5301 else if (cmd.y() < 0)
5302 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5304 // We continue with our existing selection or start a new one, so don't
5305 // reset the anchor.
5306 bvcur.setCursor(cur);
5307 if (bvcur.wordSelection() && bvcur.inTexted())
5308 expandWordSel(bvcur);
5309 bvcur.selection(true);
5310 bvcur.setCurrentFont();
5311 if (cur.top() == old) {
5312 // We didn't move one iota, so no need to update the screen.
5313 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5314 //cur.noScreenUpdate();
5320 case LFUN_MOUSE_RELEASE:
5321 switch (cmd.button()) {
5322 case mouse_button::button1:
5323 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5324 // If there is a new selection, update persistent selection;
5325 // otherwise, single click does not clear persistent selection
5327 if (cur.selection()) {
5328 // Finish selection. If double click,
5329 // cur is moved to the end of word by
5330 // selectWord but bvcur is current
5332 cur.bv().cursor().setSelection();
5333 // We might have removed an empty but drawn selection
5334 // (probably a margin)
5335 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5337 cur.noScreenUpdate();
5338 // FIXME: We could try to handle drag and drop of selection here.
5341 case mouse_button::button2:
5342 // Middle mouse pasting is handled at mouse press time,
5343 // see LFUN_MOUSE_PRESS.
5344 cur.noScreenUpdate();
5347 case mouse_button::button3:
5348 // Cursor was set at LFUN_MOUSE_PRESS time.
5349 // FIXME: If there is a selection we could try to handle a special
5350 // drag & drop context menu.
5351 cur.noScreenUpdate();
5354 case mouse_button::none:
5355 case mouse_button::button4:
5356 case mouse_button::button5:
5358 } // switch (cmd.button())
5362 case LFUN_SELF_INSERT: {
5363 if (cmd.argument().empty())
5366 // Automatically delete the currently selected
5367 // text and replace it with what is being
5368 // typed in now. Depends on lyxrc settings
5369 // "auto_region_delete", which defaults to
5372 if (lyxrc.auto_region_delete && cur.selection()) {
5373 cutSelection(cur, false);
5374 cur.setCurrentFont();
5376 cur.clearSelection();
5378 for (char_type c : cmd.argument())
5379 bv->translateAndInsert(c, this, cur);
5382 moveCursor(cur, false);
5383 cur.markNewWordPosition();
5384 bv->bookmarkEditPosition();
5388 case LFUN_HREF_INSERT: {
5389 docstring content = cmd.argument();
5390 if (content.empty() && cur.selection())
5391 content = cur.selectionAsString(false);
5393 InsetCommandParams p(HYPERLINK_CODE);
5394 if (!content.empty()){
5395 // if it looks like a link, we'll put it as target,
5396 // otherwise as name (bug #8792).
5399 // regex_match(to_utf8(content), matches, link_re)
5400 // because smatch stores pointers to the substrings rather
5401 // than making copies of them. And those pointers become
5402 // invalid after regex_match returns, since it is then
5403 // being given a temporary object. (Thanks to Georg for
5404 // figuring that out.)
5405 regex const link_re("^(([a-z]+):|www\\.).*");
5407 string const c = to_utf8(lowercase(content));
5409 if (c.substr(0,7) == "mailto:") {
5410 p["target"] = content;
5411 p["type"] = from_ascii("mailto:");
5412 } else if (regex_match(c, matches, link_re)) {
5413 p["target"] = content;
5414 string protocol = matches.str(1);
5415 if (protocol == "file")
5416 p["type"] = from_ascii("file:");
5418 p["name"] = content;
5420 string const data = InsetCommand::params2string(p);
5422 // we need to have a target. if we already have one, then
5423 // that gets used at the default for the name, too, which
5424 // is probably what is wanted.
5425 if (p["target"].empty()) {
5426 bv->showDialog("href", data);
5428 FuncRequest fr(LFUN_INSET_INSERT, data);
5434 case LFUN_LABEL_INSERT: {
5435 InsetCommandParams p(LABEL_CODE);
5436 // Try to generate a valid label
5437 p["name"] = (cmd.argument().empty()) ?
5438 cur.getPossibleLabel() :
5440 string const data = InsetCommand::params2string(p);
5442 if (cmd.argument().empty()) {
5443 bv->showDialog("label", data);
5445 FuncRequest fr(LFUN_INSET_INSERT, data);
5451 case LFUN_INFO_INSERT: {
5452 if (cmd.argument().empty()) {
5453 bv->showDialog("info", cur.current_font.language()->lang());
5456 inset = createInset(cur.buffer(), cmd);
5460 insertInset(cur, inset);
5461 cur.forceBufferUpdate();
5466 case LFUN_CAPTION_INSERT:
5467 case LFUN_FOOTNOTE_INSERT:
5468 case LFUN_NOTE_INSERT:
5469 case LFUN_BOX_INSERT:
5470 case LFUN_BRANCH_INSERT:
5471 case LFUN_PHANTOM_INSERT:
5472 case LFUN_ERT_INSERT:
5473 case LFUN_INDEXMACRO_INSERT:
5474 case LFUN_LISTING_INSERT:
5475 case LFUN_MARGINALNOTE_INSERT:
5476 case LFUN_ARGUMENT_INSERT:
5477 case LFUN_INDEX_INSERT:
5478 case LFUN_PREVIEW_INSERT:
5479 case LFUN_SCRIPT_INSERT:
5480 case LFUN_IPA_INSERT: {
5481 // Indexes reset font formatting (#11961)
5482 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5483 // Open the inset, and move the current selection
5485 doInsertInset(cur, this, cmd, true, true, resetfont);
5487 cur.setCurrentFont();
5488 // Some insets are numbered, others are shown in the outline pane so
5489 // let's update the labels and the toc backend.
5490 cur.forceBufferUpdate();
5494 case LFUN_FLEX_INSERT: {
5495 // Open the inset, and move the current selection
5497 bool const sel = cur.selection();
5498 doInsertInset(cur, this, cmd, true, true);
5499 // Insert auto-insert arguments
5500 bool autoargs = false, inautoarg = false;
5501 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5502 for (auto const & argt : args) {
5503 Layout::latexarg arg = argt.second;
5504 if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5505 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5506 lyx::dispatch(cmd2);
5508 if (arg.autoinsert) {
5509 // The cursor might have been invalidated by the replaceSelection.
5510 cur.buffer()->changed(true);
5511 // If we had already inserted an arg automatically,
5512 // leave this now in order to insert the next one.
5514 cur.leaveInset(cur.inset());
5515 cur.setCurrentFont();
5517 if (arg.insertonnewline && cur.pos() > 0) {
5518 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5519 lyx::dispatch(cmd2);
5522 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5523 lyx::dispatch(cmd2);
5530 cur.leaveInset(cur.inset());
5533 // Some insets are numbered, others are shown in the outline pane so
5534 // let's update the labels and the toc backend.
5535 cur.forceBufferUpdate();
5539 case LFUN_TABULAR_INSERT: {
5540 // if there were no arguments, just open the dialog
5541 if (cmd.argument().empty()) {
5542 bv->showDialog("tabularcreate");
5544 } else if (cur.buffer()->masterParams().tablestyle != "default"
5545 || bv->buffer().params().documentClass().tablestyle() != "default") {
5546 string tabstyle = cur.buffer()->masterParams().tablestyle;
5547 if (tabstyle == "default")
5548 tabstyle = bv->buffer().params().documentClass().tablestyle();
5549 if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5550 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5551 tabstyle + " " + to_ascii(cmd.argument()));
5555 // Unknown style. Report and fall back to default.
5556 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5557 from_utf8(N_(" not known")));
5559 if (doInsertInset(cur, this, cmd, false, true))
5564 case LFUN_TABULAR_STYLE_INSERT: {
5565 string const style = cmd.getArg(0);
5566 string const rows = cmd.getArg(1);
5567 string const cols = cmd.getArg(2);
5568 if (cols.empty() || !isStrInt(cols)
5569 || rows.empty() || !isStrInt(rows))
5571 int const r = convert<int>(rows);
5572 int const c = convert<int>(cols);
5579 FileName const tabstyle = libFileSearch("tabletemplates",
5580 style + suffix + ".lyx", "lyx");
5581 if (tabstyle.empty())
5583 UndoGroupHelper ugh(cur.buffer());
5585 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5586 lyx::dispatch(cmd2);
5590 // move one cell up to middle cell
5592 // add the missing rows
5593 int const addrows = r - 3;
5594 for (int i = 0 ; i < addrows ; ++i) {
5595 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5599 // add the missing columns
5600 int const addcols = c - 1;
5601 for (int i = 0 ; i < addcols ; ++i) {
5602 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5611 case LFUN_FLOAT_INSERT:
5612 case LFUN_FLOAT_WIDE_INSERT:
5613 case LFUN_WRAP_INSERT: {
5614 // will some content be moved into the inset?
5615 bool const content = cur.selection();
5616 // does the content consist of multiple paragraphs?
5617 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5619 doInsertInset(cur, this, cmd, true, true);
5622 // If some single-par content is moved into the inset,
5623 // doInsertInset puts the cursor outside the inset.
5624 // To insert the caption we put it back into the inset.
5625 // FIXME cleanup doInsertInset to avoid such dances!
5626 if (content && singlepar)
5629 ParagraphList & pars = cur.text()->paragraphs();
5631 DocumentClass const & tclass = bv->buffer().params().documentClass();
5633 // add a separate paragraph for the caption inset
5634 pars.push_back(Paragraph());
5635 pars.back().setInsetOwner(&cur.text()->inset());
5636 pars.back().setPlainOrDefaultLayout(tclass);
5637 int cap_pit = pars.size() - 1;
5639 // if an empty inset was created, we create an additional empty
5640 // paragraph at the bottom so that the user can choose where to put
5641 // the graphics (or table).
5643 pars.push_back(Paragraph());
5644 pars.back().setInsetOwner(&cur.text()->inset());
5645 pars.back().setPlainOrDefaultLayout(tclass);
5648 // reposition the cursor to the caption
5649 cur.pit() = cap_pit;
5651 // FIXME: This Text/Cursor dispatch handling is a mess!
5652 // We cannot use Cursor::dispatch here it needs access to up to
5654 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5655 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5656 cur.forceBufferUpdate();
5657 cur.screenUpdateFlags(Update::Force);
5658 // FIXME: When leaving the Float (or Wrap) inset we should
5659 // delete any empty paragraph left above or below the
5664 case LFUN_NOMENCL_INSERT: {
5665 InsetCommandParams p(NOMENCL_CODE);
5666 if (cmd.argument().empty()) {
5668 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5669 cur.clearSelection();
5671 p["symbol"] = cmd.argument();
5672 string const data = InsetCommand::params2string(p);
5673 bv->showDialog("nomenclature", data);
5677 case LFUN_INDEX_PRINT: {
5678 InsetCommandParams p(INDEX_PRINT_CODE);
5679 if (cmd.argument().empty())
5680 p["type"] = from_ascii("idx");
5682 p["type"] = cmd.argument();
5683 string const data = InsetCommand::params2string(p);
5684 FuncRequest fr(LFUN_INSET_INSERT, data);
5689 case LFUN_NOMENCL_PRINT:
5690 case LFUN_NEWPAGE_INSERT:
5692 doInsertInset(cur, this, cmd, false, false);
5696 case LFUN_SEPARATOR_INSERT: {
5697 doInsertInset(cur, this, cmd, false, false);
5699 // remove a following space
5700 Paragraph & par = cur.paragraph();
5701 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5702 par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5706 case LFUN_DEPTH_DECREMENT:
5707 changeDepth(cur, DEC_DEPTH);
5710 case LFUN_DEPTH_INCREMENT:
5711 changeDepth(cur, INC_DEPTH);
5714 case LFUN_REGEXP_MODE:
5715 regexpDispatch(cur, cmd);
5718 case LFUN_MATH_MODE: {
5719 if (cmd.argument() == "on" || cmd.argument() == "") {
5720 // don't pass "on" as argument
5721 // (it would appear literally in the first cell)
5722 docstring sel = cur.selectionAsString(false);
5723 InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5724 // create a macro template if we see "\\newcommand" somewhere, and
5725 // an ordinary formula otherwise
5727 && (sel.find(from_ascii("\\newcommand")) != string::npos
5728 || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5729 || sel.find(from_ascii("\\def")) != string::npos)
5730 && macro->fromString(sel)) {
5732 replaceSelection(cur);
5735 // no meaningful macro template was found
5737 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5740 // The argument is meaningful
5741 // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5742 // has a different meaning in math mode
5743 mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5747 case LFUN_MATH_MACRO:
5748 if (cmd.argument().empty())
5749 cur.errorMessage(from_utf8(N_("Missing argument")));
5752 string s = to_utf8(cmd.argument());
5753 string const s1 = token(s, ' ', 1);
5754 int const nargs = s1.empty() ? 0 : convert<int>(s1);
5755 string const s2 = token(s, ' ', 2);
5756 MacroType type = MacroTypeNewcommand;
5758 type = MacroTypeDef;
5759 InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5760 from_utf8(token(s, ' ', 0)), nargs, false, type);
5761 inset->setBuffer(bv->buffer());
5762 insertInset(cur, inset);
5764 // enter macro inset and select the name
5766 cur.top().pos() = cur.top().lastpos();
5768 cur.selection(true);
5769 cur.top().pos() = 0;
5773 case LFUN_MATH_DISPLAY:
5774 case LFUN_MATH_SUBSCRIPT:
5775 case LFUN_MATH_SUPERSCRIPT:
5776 case LFUN_MATH_INSERT:
5777 case LFUN_MATH_AMS_MATRIX:
5778 case LFUN_MATH_MATRIX:
5779 case LFUN_MATH_DELIM:
5780 case LFUN_MATH_BIGDELIM:
5781 mathDispatch(cur, cmd);
5784 case LFUN_FONT_EMPH: {
5785 Font font(ignore_font, ignore_language);
5786 font.fontInfo().setEmph(FONT_TOGGLE);
5787 toggleAndShow(cur, this, font);
5791 case LFUN_FONT_ITAL: {
5792 Font font(ignore_font, ignore_language);
5793 font.fontInfo().setShape(ITALIC_SHAPE);
5794 toggleAndShow(cur, this, font);
5798 case LFUN_FONT_BOLD:
5799 case LFUN_FONT_BOLDSYMBOL: {
5800 Font font(ignore_font, ignore_language);
5801 font.fontInfo().setSeries(BOLD_SERIES);
5802 toggleAndShow(cur, this, font);
5806 case LFUN_FONT_NOUN: {
5807 Font font(ignore_font, ignore_language);
5808 font.fontInfo().setNoun(FONT_TOGGLE);
5809 toggleAndShow(cur, this, font);
5813 case LFUN_FONT_TYPEWRITER: {
5814 Font font(ignore_font, ignore_language);
5815 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5816 toggleAndShow(cur, this, font);
5820 case LFUN_FONT_SANS: {
5821 Font font(ignore_font, ignore_language);
5822 font.fontInfo().setFamily(SANS_FAMILY);
5823 toggleAndShow(cur, this, font);
5827 case LFUN_FONT_ROMAN: {
5828 Font font(ignore_font, ignore_language);
5829 font.fontInfo().setFamily(ROMAN_FAMILY);
5830 toggleAndShow(cur, this, font);
5834 case LFUN_FONT_DEFAULT: {
5835 Font font(inherit_font, ignore_language);
5836 toggleAndShow(cur, this, font);
5840 case LFUN_FONT_STRIKEOUT: {
5841 Font font(ignore_font, ignore_language);
5842 font.fontInfo().setStrikeout(FONT_TOGGLE);
5843 toggleAndShow(cur, this, font);
5847 case LFUN_FONT_CROSSOUT: {
5848 Font font(ignore_font, ignore_language);
5849 font.fontInfo().setXout(FONT_TOGGLE);
5850 toggleAndShow(cur, this, font);
5854 case LFUN_FONT_UNDERUNDERLINE: {
5855 Font font(ignore_font, ignore_language);
5856 font.fontInfo().setUuline(FONT_TOGGLE);
5857 toggleAndShow(cur, this, font);
5861 case LFUN_FONT_UNDERWAVE: {
5862 Font font(ignore_font, ignore_language);
5863 font.fontInfo().setUwave(FONT_TOGGLE);
5864 toggleAndShow(cur, this, font);
5868 case LFUN_FONT_UNDERLINE: {
5869 Font font(ignore_font, ignore_language);
5870 font.fontInfo().setUnderbar(FONT_TOGGLE);
5871 toggleAndShow(cur, this, font);
5875 case LFUN_FONT_NO_SPELLCHECK: {
5876 Font font(ignore_font, ignore_language);
5877 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5878 toggleAndShow(cur, this, font);
5882 case LFUN_FONT_SIZE: {
5883 Font font(ignore_font, ignore_language);
5884 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5885 toggleAndShow(cur, this, font);
5889 case LFUN_LANGUAGE: {
5890 string const lang_arg = cmd.getArg(0);
5891 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5892 Language const * lang =
5893 reset ? cur.bv().buffer().params().language
5894 : languages.getLanguage(lang_arg);
5895 // we allow reset_language, which is 0, but only if it
5896 // was requested via empty or "reset" arg.
5897 if (!lang && !reset)
5899 bool const toggle = (cmd.getArg(1) != "set");
5900 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5901 Font font(ignore_font, lang);
5902 toggleAndShow(cur, this, font, toggle);
5906 case LFUN_TEXTSTYLE_APPLY: {
5907 unsigned int num = 0;
5908 string const arg = to_utf8(cmd.argument());
5911 if (isStrUnsignedInt(arg)) {
5912 num = convert<uint>(arg);
5913 if (num >= freeFonts.size()) {
5914 cur.message(_("Invalid argument (number exceeds stack size)!"));
5918 cur.message(_("Invalid argument (must be a non-negative number)!"));
5922 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5923 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5927 // Set the freefont using the contents of \param data dispatched from
5928 // the frontends and apply it at the current cursor location.
5929 case LFUN_TEXTSTYLE_UPDATE: {
5930 Font font(ignore_font, ignore_language);
5932 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5933 docstring const props = font.stateText(&bv->buffer().params(), true);
5934 freeFonts.push(make_pair(props, font));
5936 toggleAndShow(cur, this, font, toggleall);
5937 cur.message(bformat(_("Text properties applied: %1$s"), props));
5939 LYXERR0("Invalid argument of textstyle-update");
5943 case LFUN_FINISHED_LEFT:
5944 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5945 // We're leaving an inset, going left. If the inset is LTR, we're
5946 // leaving from the front, so we should not move (remain at --- but
5947 // not in --- the inset). If the inset is RTL, move left, without
5948 // entering the inset itself; i.e., move to after the inset.
5949 if (cur.paragraph().getFontSettings(
5950 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5951 cursorVisLeft(cur, true);
5954 case LFUN_FINISHED_RIGHT:
5955 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
5956 // We're leaving an inset, going right. If the inset is RTL, we're
5957 // leaving from the front, so we should not move (remain at --- but
5958 // not in --- the inset). If the inset is LTR, move right, without
5959 // entering the inset itself; i.e., move to after the inset.
5960 if (!cur.paragraph().getFontSettings(
5961 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5962 cursorVisRight(cur, true);
5965 case LFUN_FINISHED_BACKWARD:
5966 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
5967 cur.setCurrentFont();
5970 case LFUN_FINISHED_FORWARD:
5971 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
5973 cur.setCurrentFont();
5976 case LFUN_LAYOUT_PARAGRAPH: {
5978 params2string(cur.paragraph(), data);
5979 data = "show\n" + data;
5980 bv->showDialog("paragraph", data);
5984 case LFUN_PARAGRAPH_UPDATE: {
5986 params2string(cur.paragraph(), data);
5988 // Will the paragraph accept changes from the dialog?
5990 cur.inset().allowParagraphCustomization(cur.idx());
5992 data = "update " + convert<string>(accept) + '\n' + data;
5993 bv->updateDialog("paragraph", data);
5997 case LFUN_ACCENT_UMLAUT:
5998 case LFUN_ACCENT_CIRCUMFLEX:
5999 case LFUN_ACCENT_GRAVE:
6000 case LFUN_ACCENT_ACUTE:
6001 case LFUN_ACCENT_TILDE:
6002 case LFUN_ACCENT_PERISPOMENI:
6003 case LFUN_ACCENT_CEDILLA:
6004 case LFUN_ACCENT_MACRON:
6005 case LFUN_ACCENT_DOT:
6006 case LFUN_ACCENT_UNDERDOT:
6007 case LFUN_ACCENT_UNDERBAR:
6008 case LFUN_ACCENT_CARON:
6009 case LFUN_ACCENT_BREVE:
6010 case LFUN_ACCENT_TIE:
6011 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6012 case LFUN_ACCENT_CIRCLE:
6013 case LFUN_ACCENT_OGONEK:
6014 theApp()->handleKeyFunc(cmd.action());
6015 if (!cmd.argument().empty())
6016 // FIXME: Are all these characters encoded in one byte in utf8?
6017 bv->translateAndInsert(cmd.argument()[0], this, cur);
6018 cur.screenUpdateFlags(Update::FitCursor);
6021 case LFUN_FLOAT_LIST_INSERT: {
6022 DocumentClass const & tclass = bv->buffer().params().documentClass();
6023 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6025 if (cur.selection())
6026 cutSelection(cur, false);
6027 breakParagraph(cur);
6029 if (cur.lastpos() != 0) {
6030 cursorBackward(cur);
6031 breakParagraph(cur);
6034 docstring const laystr = cur.inset().usePlainLayout() ?
6035 tclass.plainLayoutName() :
6036 tclass.defaultLayoutName();
6037 setLayout(cur, laystr);
6038 ParagraphParameters p;
6039 // FIXME If this call were replaced with one to clearParagraphParams(),
6040 // then we could get rid of this method altogether.
6041 setParagraphs(cur, p);
6042 // FIXME This should be simplified when InsetFloatList takes a
6043 // Buffer in its constructor.
6044 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6045 ifl->setBuffer(bv->buffer());
6046 insertInset(cur, ifl);
6049 lyxerr << "Non-existent float type: "
6050 << to_utf8(cmd.argument()) << endl;
6055 case LFUN_CHANGE_ACCEPT: {
6056 acceptOrRejectChanges(cur, ACCEPT);
6060 case LFUN_CHANGE_REJECT: {
6061 acceptOrRejectChanges(cur, REJECT);
6065 case LFUN_THESAURUS_ENTRY: {
6066 Language const * language = cur.getFont().language();
6067 docstring arg = cmd.argument();
6069 arg = cur.selectionAsString(false);
6070 // Too large. We unselect if needed and try to get
6071 // the first word in selection or under cursor
6072 if (arg.size() > 100 || arg.empty()) {
6073 if (cur.selection()) {
6074 DocIterator selbeg = cur.selectionBegin();
6075 cur.clearSelection();
6076 setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6077 cur.screenUpdateFlags(Update::Force);
6079 // Get word or selection
6080 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6081 arg = cur.selectionAsString(false);
6082 arg += " lang=" + from_ascii(language->lang());
6085 string lang = cmd.getArg(1);
6086 // This duplicates the code in GuiThesaurus::initialiseParams
6087 if (prefixIs(lang, "lang=")) {
6088 language = languages.getLanguage(lang.substr(5));
6090 language = cur.getFont().language();
6093 string lang = language->code();
6094 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6095 LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6096 frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6097 _("The path to the thesaurus directory has not been specified.\n"
6098 "The thesaurus is not functional.\n"
6099 "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6102 bv->showDialog("thesaurus", to_utf8(arg));
6106 case LFUN_SPELLING_ADD: {
6107 Language const * language = getLanguage(cur, cmd.getArg(1));
6108 docstring word = from_utf8(cmd.getArg(0));
6110 word = cur.selectionAsString(false);
6112 if (word.size() > 100 || word.empty()) {
6113 // Get word or selection
6114 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6115 word = cur.selectionAsString(false);
6118 WordLangTuple wl(word, language);
6119 theSpellChecker()->insert(wl);
6123 case LFUN_SPELLING_ADD_LOCAL: {
6124 Language const * language = getLanguage(cur, cmd.getArg(1));
6125 docstring word = from_utf8(cmd.getArg(0));
6127 word = cur.selectionAsString(false);
6128 if (word.size() > 100)
6131 // Get word or selection
6132 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6133 word = cur.selectionAsString(false);
6136 WordLangTuple wl(word, language);
6137 if (!bv->buffer().params().spellignored(wl)) {
6138 cur.recordUndoBufferParams();
6139 bv->buffer().params().spellignore().push_back(wl);
6141 // trigger re-check of whole buffer
6142 bv->buffer().requestSpellcheck();
6147 case LFUN_SPELLING_REMOVE_LOCAL: {
6148 Language const * language = getLanguage(cur, cmd.getArg(1));
6149 docstring word = from_utf8(cmd.getArg(0));
6151 word = cur.selectionAsString(false);
6152 if (word.size() > 100)
6155 // Get word or selection
6156 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6157 word = cur.selectionAsString(false);
6160 WordLangTuple wl(word, language);
6161 bool has_item = false;
6162 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6163 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6164 if (it->lang()->code() != wl.lang()->code())
6166 if (it->word() == wl.word()) {
6172 cur.recordUndoBufferParams();
6173 bv->buffer().params().spellignore().erase(it);
6175 // trigger re-check of whole buffer
6176 bv->buffer().requestSpellcheck();
6182 case LFUN_SPELLING_IGNORE: {
6183 Language const * language = getLanguage(cur, cmd.getArg(1));
6184 docstring word = from_utf8(cmd.getArg(0));
6186 word = cur.selectionAsString(false);
6188 if (word.size() > 100 || word.empty()) {
6189 // Get word or selection
6190 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6191 word = cur.selectionAsString(false);
6194 WordLangTuple wl(word, language);
6195 theSpellChecker()->accept(wl);
6199 case LFUN_SPELLING_REMOVE: {
6200 Language const * language = getLanguage(cur, cmd.getArg(1));
6201 docstring word = from_utf8(cmd.getArg(0));
6203 word = cur.selectionAsString(false);
6205 if (word.size() > 100 || word.empty()) {
6206 // Get word or selection
6207 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6208 word = cur.selectionAsString(false);
6211 WordLangTuple wl(word, language);
6212 theSpellChecker()->remove(wl);
6216 case LFUN_PARAGRAPH_PARAMS_APPLY: {
6217 // Given data, an encoding of the ParagraphParameters
6218 // generated in the Paragraph dialog, this function sets
6219 // the current paragraph, or currently selected paragraphs,
6221 // NOTE: This function overrides all existing settings.
6222 setParagraphs(cur, cmd.argument());
6223 cur.message(_("Paragraph layout set"));
6227 case LFUN_PARAGRAPH_PARAMS: {
6228 // Given data, an encoding of the ParagraphParameters as we'd
6229 // find them in a LyX file, this function modifies the current paragraph,
6230 // or currently selected paragraphs.
6231 // NOTE: This function only modifies, and does not override, existing
6233 setParagraphs(cur, cmd.argument(), true);
6234 cur.message(_("Paragraph layout set"));
6239 if (cur.selection()) {
6240 cur.selection(false);
6243 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6244 // correct, but I'm not 100% sure -- dov, 071019
6245 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6249 case LFUN_OUTLINE_UP: {
6250 pos_type const opos = cur.pos();
6251 outline(OutlineUp, cur);
6252 setCursor(cur, cur.pit(), opos);
6253 cur.forceBufferUpdate();
6258 case LFUN_OUTLINE_DOWN: {
6259 pos_type const opos = cur.pos();
6260 outline(OutlineDown, cur);
6261 setCursor(cur, cur.pit(), opos);
6262 cur.forceBufferUpdate();
6267 case LFUN_OUTLINE_IN:
6268 outline(OutlineIn, cur);
6269 cur.forceBufferUpdate();
6273 case LFUN_OUTLINE_OUT:
6274 outline(OutlineOut, cur);
6275 cur.forceBufferUpdate();
6279 case LFUN_SERVER_GET_STATISTICS: {
6280 DocIterator from, to;
6281 if (cur.selection()) {
6282 from = cur.selectionBegin();
6283 to = cur.selectionEnd();
6285 from = doc_iterator_begin(cur.buffer());
6286 to = doc_iterator_end(cur.buffer());
6289 cur.buffer()->updateStatistics(from, to);
6290 string const arg0 = cmd.getArg(0);
6291 if (arg0 == "words") {
6292 cur.message(convert<docstring>(cur.buffer()->wordCount()));
6293 } else if (arg0 == "chars") {
6294 cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6295 } else if (arg0 == "chars-space") {
6296 cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6298 cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6299 + convert<docstring>(cur.buffer()->charCount(false)) + " "
6300 + convert<docstring>(cur.buffer()->charCount(true)));
6306 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6311 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6313 if (lyxrc.spellcheck_continuously && !needsUpdate) {
6314 // Check for misspelled text
6315 // The redraw is useful because of the painting of
6316 // misspelled markers depends on the cursor position.
6317 // Trigger a redraw for cursor moves inside misspelled text.
6318 if (!cur.inTexted()) {
6319 // move from regular text to math
6320 needsUpdate = last_misspelled;
6321 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6322 // move inside regular text
6323 needsUpdate = last_misspelled
6324 || cur.paragraph().isMisspelled(cur.pos(), true);
6328 // FIXME: The cursor flag is reset two lines below
6329 // so we need to check here if some of the LFUN did touch that.
6330 // for now only Text::erase() and Text::backspace() do that.
6331 // The plan is to verify all the LFUNs and then to remove this
6332 // singleParUpdate boolean altogether.
6333 if (cur.result().screenUpdate() & Update::Force) {
6334 singleParUpdate = false;
6338 // FIXME: the following code should go in favor of fine grained
6339 // update flag treatment.
6340 if (singleParUpdate) {
6341 // Inserting characters does not change par height in general. So, try
6342 // to update _only_ this paragraph. BufferView will detect if a full
6343 // metrics update is needed anyway.
6344 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6348 && &oldTopSlice.inset() == &cur.inset()
6349 && oldTopSlice.idx() == cur.idx()
6350 && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
6351 && !cur.selection())
6352 // FIXME: it would be better if we could just do this
6354 //if (cur.result().update() != Update::FitCursor)
6355 // cur.noScreenUpdate();
6357 // But some LFUNs do not set Update::FitCursor when needed, so we
6358 // do it for all. This is not very harmfull as FitCursor will provoke
6359 // a full redraw only if needed but still, a proper review of all LFUN
6360 // should be done and this needsUpdate boolean can then be removed.
6361 cur.screenUpdateFlags(Update::FitCursor);
6363 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6367 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6368 FuncStatus & status) const
6370 LBUFERR(this == cur.text());
6372 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6374 bool allow_in_passthru = false;
6375 InsetCode code = NO_CODE;
6377 switch (cmd.action()) {
6379 case LFUN_DEPTH_DECREMENT:
6380 enable = changeDepthAllowed(cur, DEC_DEPTH);
6383 case LFUN_DEPTH_INCREMENT:
6384 enable = changeDepthAllowed(cur, INC_DEPTH);
6388 // FIXME We really should not allow this to be put, e.g.,
6389 // in a footnote, or in ERT. But it would make sense in a
6390 // branch, so I'm not sure what to do.
6391 status.setOnOff(cur.paragraph().params().startOfAppendix());
6394 case LFUN_DIALOG_SHOW_NEW_INSET:
6395 if (cmd.argument() == "bibitem")
6396 code = BIBITEM_CODE;
6397 else if (cmd.argument() == "bibtex") {
6399 // not allowed in description items
6400 enable = !inDescriptionItem(cur);
6402 else if (cmd.argument() == "box")
6404 else if (cmd.argument() == "branch")
6406 else if (cmd.argument() == "citation")
6408 else if (cmd.argument() == "counter")
6409 code = COUNTER_CODE;
6410 else if (cmd.argument() == "ert")
6412 else if (cmd.argument() == "external")
6413 code = EXTERNAL_CODE;
6414 else if (cmd.argument() == "float")
6416 else if (cmd.argument() == "graphics")
6417 code = GRAPHICS_CODE;
6418 else if (cmd.argument() == "href")
6419 code = HYPERLINK_CODE;
6420 else if (cmd.argument() == "include")
6421 code = INCLUDE_CODE;
6422 else if (cmd.argument() == "index")
6424 else if (cmd.argument() == "index_print")
6425 code = INDEX_PRINT_CODE;
6426 else if (cmd.argument() == "listings")
6427 code = LISTINGS_CODE;
6428 else if (cmd.argument() == "mathspace")
6429 code = MATH_HULL_CODE;
6430 else if (cmd.argument() == "nomenclature")
6431 code = NOMENCL_CODE;
6432 else if (cmd.argument() == "nomencl_print")
6433 code = NOMENCL_PRINT_CODE;
6434 else if (cmd.argument() == "label")
6436 else if (cmd.argument() == "line")
6438 else if (cmd.argument() == "note")
6440 else if (cmd.argument() == "phantom")
6441 code = PHANTOM_CODE;
6442 else if (cmd.argument() == "ref")
6444 else if (cmd.argument() == "space")
6446 else if (cmd.argument() == "toc")
6448 else if (cmd.argument() == "vspace")
6450 else if (cmd.argument() == "wrap")
6454 case LFUN_ERT_INSERT:
6457 case LFUN_LISTING_INSERT:
6458 code = LISTINGS_CODE;
6459 // not allowed in description items
6460 enable = !inDescriptionItem(cur);
6462 case LFUN_FOOTNOTE_INSERT:
6465 case LFUN_TABULAR_INSERT:
6466 code = TABULAR_CODE;
6468 case LFUN_TABULAR_STYLE_INSERT:
6469 code = TABULAR_CODE;
6471 case LFUN_MARGINALNOTE_INSERT:
6474 case LFUN_FLOAT_INSERT:
6475 case LFUN_FLOAT_WIDE_INSERT:
6476 // FIXME: If there is a selection, we should check whether there
6477 // are floats in the selection, but this has performance issues, see
6478 // LFUN_CHANGE_ACCEPT/REJECT.
6480 if (inDescriptionItem(cur))
6481 // not allowed in description items
6484 InsetCode const inset_code = cur.inset().lyxCode();
6486 // algorithm floats cannot be put in another float
6487 if (to_utf8(cmd.argument()) == "algorithm") {
6488 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6492 // for figures and tables: only allow in another
6493 // float or wrap if it is of the same type and
6494 // not a subfloat already
6495 if(cur.inset().lyxCode() == code) {
6496 InsetFloat const & ins =
6497 static_cast<InsetFloat const &>(cur.inset());
6498 enable = ins.params().type == to_utf8(cmd.argument())
6499 && !ins.params().subfloat;
6500 } else if(cur.inset().lyxCode() == WRAP_CODE) {
6501 InsetWrap const & ins =
6502 static_cast<InsetWrap const &>(cur.inset());
6503 enable = ins.params().type == to_utf8(cmd.argument());
6507 case LFUN_WRAP_INSERT:
6509 // not allowed in description items
6510 enable = !inDescriptionItem(cur);
6512 case LFUN_FLOAT_LIST_INSERT: {
6513 code = FLOAT_LIST_CODE;
6514 // not allowed in description items
6515 enable = !inDescriptionItem(cur);
6517 FloatList const & floats = cur.buffer()->params().documentClass().floats();
6518 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6519 // make sure we know about such floats
6520 if (cit == floats.end() ||
6521 // and that we know how to generate a list of them
6522 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6523 status.setUnknown(true);
6524 // probably not necessary, but...
6530 case LFUN_CAPTION_INSERT: {
6531 code = CAPTION_CODE;
6532 string arg = cmd.getArg(0);
6533 bool varia = arg != "Unnumbered"
6534 && cur.inset().allowsCaptionVariation(arg);
6535 // not allowed in description items,
6536 // and in specific insets
6537 enable = !inDescriptionItem(cur)
6538 && (varia || arg.empty() || arg == "Standard");
6541 case LFUN_NOTE_INSERT:
6544 case LFUN_FLEX_INSERT: {
6546 docstring s = from_utf8(cmd.getArg(0));
6547 // Prepend "Flex:" prefix if not there
6548 if (!prefixIs(s, from_ascii("Flex:")))
6549 s = from_ascii("Flex:") + s;
6550 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6554 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6555 if (ilt != InsetLyXType::CHARSTYLE
6556 && ilt != InsetLyXType::CUSTOM
6557 && ilt != InsetLyXType::STANDARD)
6562 case LFUN_BOX_INSERT:
6565 case LFUN_BRANCH_INSERT:
6567 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6568 && cur.buffer()->params().branchlist().empty())
6571 case LFUN_IPA_INSERT:
6574 case LFUN_PHANTOM_INSERT:
6575 code = PHANTOM_CODE;
6577 case LFUN_LABEL_INSERT:
6580 case LFUN_INFO_INSERT:
6582 enable = cmd.argument().empty()
6583 || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6585 case LFUN_ARGUMENT_INSERT: {
6587 allow_in_passthru = true;
6588 string const arg = cmd.getArg(0);
6593 Layout const & lay = cur.paragraph().layout();
6594 Layout::LaTeXArgMap args = lay.args();
6595 Layout::LaTeXArgMap::const_iterator const lait =
6597 if (lait != args.end()) {
6599 pit_type pit = cur.pit();
6600 pit_type lastpit = cur.pit();
6601 if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6602 // In a sequence of "merged" environment layouts, we only allow
6603 // non-item arguments once.
6604 lastpit = cur.lastpit();
6605 // get the first paragraph in sequence with this layout
6606 depth_type const current_depth = cur.paragraph().params().depth();
6610 Paragraph cpar = pars_[pit - 1];
6611 if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6617 for (; pit <= lastpit; ++pit) {
6618 if (pars_[pit].layout() != lay)
6620 for (auto const & table : pars_[pit].insetList())
6621 if (InsetArgument const * ins = table.inset->asInsetArgument())
6622 if (ins->name() == arg) {
6623 // we have this already
6632 case LFUN_INDEX_INSERT:
6635 case LFUN_INDEX_PRINT:
6636 code = INDEX_PRINT_CODE;
6637 // not allowed in description items
6638 enable = !inDescriptionItem(cur);
6640 case LFUN_NOMENCL_INSERT:
6641 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6645 code = NOMENCL_CODE;
6647 case LFUN_NOMENCL_PRINT:
6648 code = NOMENCL_PRINT_CODE;
6649 // not allowed in description items
6650 enable = !inDescriptionItem(cur);
6652 case LFUN_HREF_INSERT:
6653 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6657 code = HYPERLINK_CODE;
6659 case LFUN_INDEXMACRO_INSERT: {
6660 string const arg = cmd.getArg(0);
6661 if (arg == "sortkey")
6662 code = INDEXMACRO_SORTKEY_CODE;
6664 code = INDEXMACRO_CODE;
6667 case LFUN_IPAMACRO_INSERT: {
6668 string const arg = cmd.getArg(0);
6670 code = IPADECO_CODE;
6672 code = IPACHAR_CODE;
6675 case LFUN_QUOTE_INSERT:
6676 // always allow this, since we will inset a raw quote
6677 // if an inset is not allowed.
6678 allow_in_passthru = true;
6680 case LFUN_SPECIALCHAR_INSERT:
6681 code = SPECIALCHAR_CODE;
6683 case LFUN_SPACE_INSERT:
6684 // slight hack: we know this is allowed in math mode
6688 case LFUN_PREVIEW_INSERT:
6689 code = PREVIEW_CODE;
6691 case LFUN_SCRIPT_INSERT:
6695 case LFUN_MATH_INSERT:
6696 case LFUN_MATH_AMS_MATRIX:
6697 case LFUN_MATH_MATRIX:
6698 case LFUN_MATH_DELIM:
6699 case LFUN_MATH_BIGDELIM:
6700 case LFUN_MATH_DISPLAY:
6701 case LFUN_MATH_MODE:
6702 case LFUN_MATH_MACRO:
6703 case LFUN_MATH_SUBSCRIPT:
6704 case LFUN_MATH_SUPERSCRIPT:
6705 code = MATH_HULL_CODE;
6708 case LFUN_REGEXP_MODE:
6709 code = MATH_HULL_CODE;
6710 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6713 case LFUN_INSET_MODIFY:
6714 // We need to disable this, because we may get called for a
6716 // InsetTabular::getStatus() -> InsetText::getStatus()
6717 // and we don't handle LFUN_INSET_MODIFY.
6721 case LFUN_FONT_EMPH:
6722 status.setOnOff(fontinfo.emph() == FONT_ON);
6723 enable = !cur.paragraph().isPassThru();
6726 case LFUN_FONT_ITAL:
6727 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6728 enable = !cur.paragraph().isPassThru();
6731 case LFUN_FONT_NOUN:
6732 status.setOnOff(fontinfo.noun() == FONT_ON);
6733 enable = !cur.paragraph().isPassThru();
6736 case LFUN_FONT_BOLD:
6737 case LFUN_FONT_BOLDSYMBOL:
6738 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6739 enable = !cur.paragraph().isPassThru();
6742 case LFUN_FONT_SANS:
6743 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6744 enable = !cur.paragraph().isPassThru();
6747 case LFUN_FONT_ROMAN:
6748 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6749 enable = !cur.paragraph().isPassThru();
6752 case LFUN_FONT_TYPEWRITER:
6753 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6754 enable = !cur.paragraph().isPassThru();
6758 enable = cur.selection();
6762 if (cmd.argument().empty()) {
6763 if (theClipboard().isInternal())
6764 enable = cap::numberOfSelections() > 0;
6766 enable = !theClipboard().empty();
6770 // we have an argument
6771 string const arg = to_utf8(cmd.argument());
6772 if (isStrUnsignedInt(arg)) {
6773 // it's a number and therefore means the internal stack
6774 unsigned int n = convert<unsigned int>(arg);
6775 enable = cap::numberOfSelections() > n;
6779 // explicit text type?
6780 if (arg == "html") {
6781 // Do not enable for PlainTextType, since some tidying in the
6782 // frontend is needed for HTML, which is too unsafe for plain text.
6783 enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6785 } else if (arg == "latex") {
6786 // LaTeX is usually not available on the clipboard with
6787 // the correct MIME type, but in plain text.
6788 enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6789 theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6793 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6795 type = Clipboard::PdfGraphicsType;
6796 else if (arg == "png")
6797 type = Clipboard::PngGraphicsType;
6798 else if (arg == "jpeg")
6799 type = Clipboard::JpegGraphicsType;
6800 else if (arg == "linkback")
6801 type = Clipboard::LinkBackGraphicsType;
6802 else if (arg == "emf")
6803 type = Clipboard::EmfGraphicsType;
6804 else if (arg == "wmf")
6805 type = Clipboard::WmfGraphicsType;
6808 LYXERR0("Unrecognized graphics type: " << arg);
6809 // we don't want to assert if the user just mistyped the LFUN
6810 LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6814 enable = theClipboard().hasGraphicsContents(type);
6818 case LFUN_CLIPBOARD_PASTE:
6819 case LFUN_CLIPBOARD_PASTE_SIMPLE:
6820 enable = !theClipboard().empty();
6823 case LFUN_PRIMARY_SELECTION_PASTE:
6824 status.setUnknown(!theSelection().supported());
6825 enable = cur.selection() || !theSelection().empty();
6828 case LFUN_SELECTION_PASTE:
6829 enable = cap::selection();
6832 case LFUN_PARAGRAPH_MOVE_UP:
6833 enable = cur.pit() > 0 && !cur.selection();
6836 case LFUN_PARAGRAPH_MOVE_DOWN:
6837 enable = cur.pit() < cur.lastpit() && !cur.selection();
6840 case LFUN_CHANGE_ACCEPT:
6841 case LFUN_CHANGE_REJECT:
6842 if (!cur.selection())
6843 enable = cur.paragraph().isChanged(cur.pos());
6845 // will enable if there is a change in the selection
6848 // cheap improvement for efficiency: using cached
6849 // buffer variable, if there is no change in the
6850 // document, no need to check further.
6851 if (!cur.buffer()->areChangesPresent())
6854 for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6855 pos_type const beg = it.pos();
6857 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6858 it.idx() == cur.selectionEnd().idx());
6860 end = cur.selectionEnd().pos();
6862 // the +1 is needed for cases, e.g., where there is a
6863 // paragraph break. See #11629.
6864 end = it.lastpos() + 1;
6865 if (beg != end && it.paragraph().isChanged(beg, end)) {
6869 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6879 case LFUN_OUTLINE_UP:
6880 case LFUN_OUTLINE_DOWN:
6881 case LFUN_OUTLINE_IN:
6882 case LFUN_OUTLINE_OUT:
6883 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6886 case LFUN_NEWLINE_INSERT:
6887 // LaTeX restrictions (labels or empty par)
6888 enable = !cur.paragraph().isPassThru()
6889 && cur.pos() > cur.paragraph().beginOfBody();
6892 case LFUN_SEPARATOR_INSERT:
6893 // Always enabled for now
6897 case LFUN_TAB_INSERT:
6898 case LFUN_TAB_DELETE:
6899 enable = cur.paragraph().isPassThru();
6902 case LFUN_GRAPHICS_SET_GROUP: {
6903 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6907 status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6911 case LFUN_NEWPAGE_INSERT:
6912 // not allowed in description items
6913 code = NEWPAGE_CODE;
6914 enable = !inDescriptionItem(cur);
6918 enable = !cur.paragraph().isPassThru();
6919 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6922 case LFUN_PARAGRAPH_BREAK:
6923 enable = inset().allowMultiPar();
6926 case LFUN_SPELLING_ADD:
6927 case LFUN_SPELLING_ADD_LOCAL:
6928 case LFUN_SPELLING_REMOVE_LOCAL:
6929 case LFUN_SPELLING_IGNORE:
6930 case LFUN_SPELLING_REMOVE:
6931 enable = theSpellChecker() != nullptr;
6932 if (enable && !cmd.getArg(1).empty()) {
6933 // validate explicitly given language
6934 Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6935 enable &= lang != nullptr;
6940 case LFUN_LAYOUT_TOGGLE: {
6941 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
6942 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
6943 docstring const layout = resolveLayout(req_layout, cur);
6945 // FIXME: make this work in multicell selection case
6946 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
6947 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
6948 && isAlreadyLayout(layout, cur));
6952 case LFUN_ENVIRONMENT_SPLIT: {
6953 if (cmd.argument() == "outer") {
6954 // check if we have an environment in our nesting hierarchy
6956 depth_type const current_depth = cur.paragraph().params().depth();
6957 pit_type pit = cur.pit();
6958 Paragraph cpar = pars_[pit];
6960 if (pit == 0 || cpar.params().depth() == 0)
6964 if (cpar.params().depth() < current_depth)
6965 res = cpar.layout().isEnvironment();
6970 else if (cmd.argument() == "previous") {
6971 // look if we have an environment in the previous par
6972 pit_type pit = cur.pit();
6973 Paragraph cpar = pars_[pit];
6977 enable = cpar.layout().isEnvironment();
6983 else if (cur.paragraph().layout().isEnvironment()) {
6984 enable = cmd.argument() == "before"
6985 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
6992 case LFUN_LAYOUT_PARAGRAPH:
6993 case LFUN_PARAGRAPH_PARAMS:
6994 case LFUN_PARAGRAPH_PARAMS_APPLY:
6995 case LFUN_PARAGRAPH_UPDATE:
6996 enable = owner_->allowParagraphCustomization();
6999 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
7000 // Because they insert COMBINING DIACRITICAL Unicode characters,
7001 // that cannot be handled by LaTeX but must be converted according
7002 // to the definition in lib/unicodesymbols?
7003 case LFUN_ACCENT_ACUTE:
7004 case LFUN_ACCENT_BREVE:
7005 case LFUN_ACCENT_CARON:
7006 case LFUN_ACCENT_CEDILLA:
7007 case LFUN_ACCENT_CIRCLE:
7008 case LFUN_ACCENT_CIRCUMFLEX:
7009 case LFUN_ACCENT_DOT:
7010 case LFUN_ACCENT_GRAVE:
7011 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7012 case LFUN_ACCENT_MACRON:
7013 case LFUN_ACCENT_OGONEK:
7014 case LFUN_ACCENT_TIE:
7015 case LFUN_ACCENT_TILDE:
7016 case LFUN_ACCENT_PERISPOMENI:
7017 case LFUN_ACCENT_UMLAUT:
7018 case LFUN_ACCENT_UNDERBAR:
7019 case LFUN_ACCENT_UNDERDOT:
7020 case LFUN_FONT_FRAK:
7021 case LFUN_FONT_SIZE:
7022 case LFUN_FONT_STATE:
7023 case LFUN_FONT_UNDERLINE:
7024 case LFUN_FONT_STRIKEOUT:
7025 case LFUN_FONT_CROSSOUT:
7026 case LFUN_FONT_UNDERUNDERLINE:
7027 case LFUN_FONT_UNDERWAVE:
7028 case LFUN_FONT_NO_SPELLCHECK:
7029 case LFUN_TEXTSTYLE_UPDATE:
7030 enable = !cur.paragraph().isPassThru();
7033 case LFUN_FONT_DEFAULT: {
7034 Font font(inherit_font, ignore_language);
7035 BufferParams const & bp = cur.buffer()->masterParams();
7036 if (cur.selection()) {
7038 // Check if we have a non-default font attribute
7039 // in the selection range.
7040 DocIterator const from = cur.selectionBegin();
7041 DocIterator const to = cur.selectionEnd();
7042 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7043 if (!dit.inTexted()) {
7047 Paragraph const & par = dit.paragraph();
7048 pos_type const pos = dit.pos();
7049 Font tmp = par.getFontSettings(bp, pos);
7050 if (tmp.fontInfo() != font.fontInfo()
7051 || tmp.language() != bp.language) {
7059 // Disable if all is default already.
7060 enable = (cur.current_font.fontInfo() != font.fontInfo()
7061 || cur.current_font.language() != bp.language);
7065 case LFUN_TEXTSTYLE_APPLY:
7066 enable = !freeFonts.empty();
7069 case LFUN_WORD_DELETE_FORWARD:
7070 case LFUN_WORD_DELETE_BACKWARD:
7071 case LFUN_LINE_DELETE_FORWARD:
7072 case LFUN_WORD_FORWARD:
7073 case LFUN_WORD_BACKWARD:
7074 case LFUN_WORD_RIGHT:
7075 case LFUN_WORD_LEFT:
7076 case LFUN_CHAR_FORWARD:
7077 case LFUN_CHAR_FORWARD_SELECT:
7078 case LFUN_CHAR_BACKWARD:
7079 case LFUN_CHAR_BACKWARD_SELECT:
7080 case LFUN_CHAR_LEFT:
7081 case LFUN_CHAR_LEFT_SELECT:
7082 case LFUN_CHAR_RIGHT:
7083 case LFUN_CHAR_RIGHT_SELECT:
7085 case LFUN_UP_SELECT:
7087 case LFUN_DOWN_SELECT:
7088 case LFUN_PARAGRAPH_SELECT:
7089 case LFUN_PARAGRAPH_UP_SELECT:
7090 case LFUN_PARAGRAPH_DOWN_SELECT:
7091 case LFUN_LINE_BEGIN_SELECT:
7092 case LFUN_LINE_END_SELECT:
7093 case LFUN_WORD_FORWARD_SELECT:
7094 case LFUN_WORD_BACKWARD_SELECT:
7095 case LFUN_WORD_RIGHT_SELECT:
7096 case LFUN_WORD_LEFT_SELECT:
7097 case LFUN_WORD_SELECT:
7098 case LFUN_SECTION_SELECT:
7099 case LFUN_BUFFER_BEGIN:
7100 case LFUN_BUFFER_END:
7101 case LFUN_BUFFER_BEGIN_SELECT:
7102 case LFUN_BUFFER_END_SELECT:
7103 case LFUN_INSET_BEGIN:
7104 case LFUN_INSET_END:
7105 case LFUN_INSET_BEGIN_SELECT:
7106 case LFUN_INSET_END_SELECT:
7107 case LFUN_PARAGRAPH_UP:
7108 case LFUN_PARAGRAPH_DOWN:
7109 case LFUN_LINE_BEGIN:
7111 case LFUN_CHAR_DELETE_FORWARD:
7112 case LFUN_CHAR_DELETE_BACKWARD:
7113 case LFUN_WORD_UPCASE:
7114 case LFUN_WORD_LOWCASE:
7115 case LFUN_WORD_CAPITALIZE:
7116 case LFUN_CHARS_TRANSPOSE:
7117 case LFUN_SERVER_GET_XY:
7118 case LFUN_SERVER_SET_XY:
7119 case LFUN_SERVER_GET_LAYOUT:
7120 case LFUN_SELF_INSERT:
7121 case LFUN_UNICODE_INSERT:
7122 case LFUN_THESAURUS_ENTRY:
7124 case LFUN_SERVER_GET_STATISTICS:
7125 // these are handled in our dispatch()
7129 case LFUN_INSET_INSERT: {
7130 string const type = cmd.getArg(0);
7131 if (type == "toc") {
7133 // not allowed in description items
7134 //FIXME: couldn't this be merged in Inset::insetAllowed()?
7135 enable = !inDescriptionItem(cur);
7142 case LFUN_SEARCH_IGNORE: {
7143 bool const value = cmd.getArg(1) == "true";
7144 setIgnoreFormat(cmd.getArg(0), value);
7154 || !cur.inset().insetAllowed(code)
7155 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7158 status.setEnabled(enable);
7163 void Text::pasteString(Cursor & cur, docstring const & clip,
7166 if (!clip.empty()) {
7169 insertStringAsParagraphs(cur, clip, cur.current_font);
7171 insertStringAsLines(cur, clip, cur.current_font);
7176 // FIXME: an item inset would make things much easier.
7177 bool Text::inDescriptionItem(Cursor const & cur) const
7179 Paragraph const & par = cur.paragraph();
7180 pos_type const pos = cur.pos();
7181 pos_type const body_pos = par.beginOfBody();
7183 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7184 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7185 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7188 return (pos < body_pos
7190 && (pos == 0 || par.getChar(pos - 1) != ' ')));
7194 std::vector<docstring> Text::getFreeFonts() const
7196 vector<docstring> ffList;
7198 for (auto const & f : freeFonts)
7199 ffList.push_back(f.first);